C++类和对象(一)
希望文章能对你有所帮助,有不足的地方请在评论区留言指正,一起交流学习!
目录
1.面向对象的引入
2.类和对象定义
3.类的访问限定符及封装
3.1.访问限定符号
3.2封装
4.类的作用域
5.类的实例化
6.类对象模型
6.1.类对象的大小
7.this指针
1.面向对象的引入
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。交互也是要使用到函数的,算是一个过程,但是首先关注的是对象。
2.类和对象定义
(1)类的定义
类是创建对象的蓝图或模板,它定义了一组属性(变量)和方法(函数)的集合。类本身不产生实际数据,而是作为一种数据类型的抽象定义。在C++中结构体升级为类,其中可以包含了变量和函数;可以当作数类型来创建变量。
(2)对象的定义
对象是类的实例,通过类创建。每个对象都有自己独立的属性值,但共享类定义的方法。
对象就是通过类实例化的过程;每个对象都有独特的实例,具有唯一性
举个例子:在C语言中可以使用结构体将一组不同类型的数据封装在一起;在C++中struc也可以用来封装函数。
typedef int DataType;struct Stack //或者使用 class Stack
{//成员函数void Init(size_t capacity){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc fail");return;}_capacity = capacity;_size = 0;}void Push(const DataType& data){// 扩容_array[_size] = data;++_size;}DataType Top(){return _array[_size - 1];}void Destroy(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}//成员变量DataType* _array;size_t _capacity;size_t _size;
};
上述代码就是定义了栈的类,然后我门使用类创建实际的一个栈,就是对象的创建。以及函数的调用和结构体调用变量相同,使用 . 符号调用 指针情况使用->符号调用。
Stack s; //创建对象
s.Init(10); //对对象进行操作
C++中,花括号定义的也是一个域 ,在不同的域中函数名字可以是相同的;每个类属于不同的域,类域是一个整体。在C语言中, 由于没有类域的区分,只能在初始化前面需要加上 Que或者Stack来做区分,但是 C++的类域中是不用的。
注意的是类域是一个整体,成员变量和成员函数的顺序是没有关系的,
(3)使用class定义类
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
就像上述使用struct创建的栈的类一样;这种情况下编译器可能会将一些函数当成内联函数处理。内联函数的特性:定义和声明是不可以分开的。
class Stack
这种情况函数的定义,需要在函数名前加上类的名字加以区分,防止命名的冲突。
// class.h 中的程序
#include <iostream>
using namespace std;
typedef int DataType;
class Stack
{
private:DataType* _array = nullptr;//空指针使用nullptrsize_t _size ;// size_t _capacity = 0;
public:// 初始化 给栈区的容量void Init(const DataType& capacity);// 常数的引用// 插入数据void Push(const DataType& data);// 出栈void Pop();// 栈顶元素DataType Top();// 销毁 栈void Destory();// 判断栈区是否为空bool Empty();};
// class.cpp 中的程序
// 初始化 给栈区的容量
void Stack::Init(const DataType& capacity)// 常数的引用
{_array = (DataType*)malloc(sizeof(DataType)*capacity);if (_array == nullptr){perror("malloc fail");}_capacity = capacity;_size = 0;
}
// 插入数据
void Stack::Push(const DataType& data)
{// 扩容if (_size >= _capacity){_array = (DataType*)realloc(_array, sizeof(DataType)*_capacity * 2);_capacity = _capacity * 2;}if (_array == nullptr){perror("realloc fail");}_array[_size] = data;_size++;
}
// 出栈
void Stack::Pop()
{_size--;
}// 栈顶元素
DataType Stack::Top()
{return _array[_size - 1];
}
// 销毁 栈
void Stack::Destory()
{free(_array);_array = nullptr;_size = 0;_capacity = 0;
}
// 判断栈区是否为空
bool Stack::Empty()
{if (_size == 0)return true;elsereturn false;}
总结,类中的成员函数一般需要在其他文件定义,避免定义为内联函数。其定义形式在函数名前加上 类的名字;类也是一种类域。
如果内联函数定义在类中,可能会存在链接不上的问题,内联函数在编译过程中不会进入符号。
类域是一个整体,成员变量和成员函数的顺序是没有关系的,
struct和class的区别
权限问题, struct 默认是共有的,class默认是私有的,不建议使用默认属性,一定要显示定义类。
3.类的访问限定符及封装
3.1.访问限定符号
- public(公有) 其修饰的成员在类外可以直接访问;
- protected(保护)
- private(私有).protected和private修饰的成员在类外不能直接被访问,二者类似。
注意访问限定的作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止;如果后面没有访问限定符,作用域就到 } 即类结束。
3.2封装
4.类的作用域
class person
{
private:int age;char name[20];
public:// 输出名字void Push_name();void Pop_name(){cout << name << endl;}
};
//这里需要指定Push_nameo是属于person这个类域
// 输出名字
void person::Push_name()
{cin >> name;
}
5.类的实例化
使用创建的类来创建对象的过程,就是类的实例化
Stack pa;
Stack为类创建的类型;pa为类的类型创建的对象。
注意
1..类是对对象进行描述的,定义类并没 有分配实际的内存空间来存储它;
2.一个类可以实例化出多个对象,对象占用实际的物理空间,存储类成员变量;
3.类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图。
class Stack
{
public:void Init(){}int top;int* _array;int capacity;};
int main()
{Stack s1;Stack s2;s2.top = 2;// 下述方式不可以,在类中只是声明,没有定义开辟空间,因此不可以直接使用Stack::top = 0;return 0;
}
静态变量是可以的,因为静态变量在声明的时候已经创建了内存地址,在全局数据区(静态区)。
6.类对象模型
6.1.类对象的大小
在计算对象大小的时候,仅需要计算成员变量的大小即可;因为成员变量是每一个对象所具有的,存在着不同;而成员函数是在公共区域的,调用会前往公共区域调用。
使用上述栈的例子;计算创建对象的大小;其大小是为12 字节;遵循结构体中的内存对齐原则。
class Stack
{
public:
// 所有对象公有的void Init(){cout << "Init Success "<< endl;}void Push(int& data){cout << "Push Success " << endl;}
// 每个对象私有int top;int* _array;int capacity;};
int main()
{Stack st1;Stack st2;Stack st3;Stack st4;cout << sizeof(st1) << endl;cout << sizeof(st2) << endl;return 0;
}
再有一个例子
#include <iostream>
using namespace std;class A
{
public:void PrintA(){cout << _a << endl;}char _a;
};int main()
{A aa1;A aa2;A aa3;cout << sizeof(aa1) << endl;cout << sizeof(A) << endl;aa1._a = 0;//直接可以在对象中找到aa1.PrintA();//需要前往公共代码区虚招调用函数}
使用sizeof计算类和对象的大小是一样的;类是蓝图,对象是房子,可以计算出大小。
// 类中仅有成员函数
class A1 {
public:void f1() {}
};
// 类中什么都没有---空类
class A2
{};
int main()
{cout << sizeof(A1) << endl;cout << sizeof(A2) << endl;A1 a1;A2 a2;cout << &a1 << endl;cout << &a2 << endl;return 0;
}
空类或者没有成员函数的类也是会占用一个字节的,为了占位,表示对象存在,不存储有效的数据;便于创建对象的时候开辟内存空间,可以查询对象的地址。
7.this指针
(1) 问题的引入,请看下述的代码
#include <iostream>
using namespace std;class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1, d2;d1.Init(2025, 7, 13);d2.Init(2025, 7, 14);d1.Print();d2.Print();return 0;
}
上述代码中的两次调用Print()函数;调用的是同样的函数为什么输出的结果是不一样的。
在编译过程中生成的call指针调用函数的地址也是一样的。我们直接看二者的反汇编
上述两行代码的反汇编,在调用函数之前会将对象的地址传到ecx中,方便传递参数,起始已经传递了对象的指针,但是在程序中是没有显示也没有写的。我们称这样的指针为this指针。
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。非静态的成员函数的理解:
目前仅仅需要知道,静态的成员函数的属于类的本身,不属于任何对象的实例化;它们不与特定的对象关联,因此不需要this
指针来指向某个对象。总结:就是在调用处于公共区代码中函数的时候,会传递一个隐藏的this指针,这个指针就是对象的地址。
其真实的情况类似于上述的情况,存在着隐含指针this,真正调用函数的形式类似于下述的代码。
void Print(){cout << _year << "-" << _month << "-" << _day << endl;}void Print(Data* this){cout << this->_year << "-" << this->_month << "-" << this->_day << endl;}
// 这里的this 指针就是 上面代码中的传递的对象的地址。
(2)this指针的特性
this在c++中也是一个关键字
- 规定this指针不能在形参和实参中显示,但是可以在函数内部显示使用。
void Print(){cout << this << endl;cout << _year << "-" << _month << "-" << _day << endl;}
运行结果如下
2.this指针是无法被修改的,其指针类型是指针常量,指针无法被修改,指向的内容是可以被修改的。
// 上述例子中对应的this指针类型
Data* cons this
总结
- this指针的类型:类型* const,即成员函数中,不能给this指针赋值。即 指针常量。
- 只能在成员函数的内部使用
- this指针本质上是成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针;所以this指针和普通的参数一样存储在栈区中,一旦栈帧销毁,this指针也会销毁。
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递;
- this指针的传递,将对象的地址放在ecx中,使用ecx存储this指针的值。
问题 :
1.this指针存储在栈区2.this可以为空,但是在成员函数内部能再调用this指针。
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void Print(){cout << "Print()" << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void PrintA(){cout << _a << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA();return 0;
}
程序1 运行正常,程序2运行运行崩溃。
原因是程序2对p 空指针进行了解引用的操作;因此程序崩溃。在调用函数PrintA的时候,只需要前往公共区代码区调用即可,没有p的解引用,在这里的this指针就是 p 空指针。当然使用 A::Print()来调用成员函数是不可行的,因为没有this指针,而且A也没有开辟空间,没有对应的地址。