目录
- 类的引入
- 类的定义
- 类的访问限定符和封装
- 对象的实例化
- 类对象的大小
- this指针
类的引入
在C语言中,结构体中只能定义变量
但是在C++中,结构体不仅可以定义变量,还可以定义函数
下面就是C++中的一个结构体:
struct Stack
{void init(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;}void destroy(){free(_a);_top = _capacity = 0;_a = nullptr;}int* _a;int _top;int _capacity;
};
可以看到在C++中的结构体中,可以有变量,也可以有函数
C++兼容C语言,结构体以前的用法在C++里仍让可以使用,只是C++中struct
实际上是升级成了类。
在C++中,更喜欢使用class
替代struct
类的定义
类中的内容成为类的成员:类中的变量成为成员变量,类中的函数称为成员函数或者类的方法
类的定义有2中方式:
值得注意的是:成员变量本身只能在类中声明且无定义,成员函数的声明必须在类中,其定义可以在类外也可以在类内
- 成员的声明和定义都在类体中,这时类中定义成员函数,编译可能会把成员函数当作内联函数处理(如果成员函数过长,编译器还是会把成员函数作为普通函数处理,是否内联取决于编译)
class Stack
{
public://成员函数在类中定义void init(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;}private:int* _a;int _top;int _capacity;
};
- 也可以将成员函数定义在类体外,就需要使用
::
作用域操作符指明函数属于哪个类
class Stack
{
public://成员函数在类外定义void init(int capacity);private:int* _a;int _top;int _capacity;
};void Stack::init(int capacity)
{_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;
}
需要用作用域操作符是因为:在定义一个类时,类定义了一个新的作用域
不同的作用域中可能会有重名的函数,所以函数在类外定义时,就必须用::
指明这个函数属于哪个类
类的访问限定符和封装
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选
择性的将其接口提供给外部的用户使用。
访问限定符有三个:公有public
,私有private
,保护protected
public
修饰的成员可以在类外直接被访问protected
和private
修饰的成员不可以在类外直接被访问- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
一般情况下,我们定义类时,把成员函数设为公有,把成员变量设为私有,这时,在类外就无法直接访问成员变量,只能通过访问成员函数,在成员函数中对成员变量进行操作。这样既保护了成员变量,同时使用成员函数保证了成员变量在我们设定好的思路下进行一系列操作。
这也是封装的基本思想
面向对象的三大特性:封装、继承、多态。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便使用类
就像对于一个计算机,我们用户不用去管它的内部各个部件是如何是如果排布的,也不用管CPU,GPU,内存等是如何工作的,厂商都把它们封装在一个壳子里了,对于我们,只能通过厂商给好的键盘,鼠标,和接口对计算机进行操作
同理,对于C++中的封装也是同理,用户不必去管成员函数内部具体逻辑,也更不会操作到成员变量,用户只需要会调用成员函数即可
通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
对象的实例化
用类创建对象的过程,称为类的实例化
对于变量,声明和定义的区别在于是否开辟内存空间,所以在类定义中的成员变量是声明而不是定义
类的实例化:
class Stack
{
public:void init(int capacity);
private:int* _a;int _top;int _capacity;
};void Stack::init(int capacity)
{_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;
}int main()
{//Stack类的实例化Stack s1;Stack s2;
}
一个类是没有空间的,只有它实例化出的对象才有物理空间
类的对象的关系就像图纸和实物的关系,我们可以依照同一个图纸创造出许多一样的实物,这些实物有自己的体积,而图纸没有
类对象的大小
前面说了,一个类没有大小,只有它实例化出的对象有大小,那么它的对象大小怎么计算呢?
事实上,类成员的储存空间中,只保存成员变量,成员函数都放在公共的代码段
这是因为,类实例化出不同的对象,这些对象的成员变量不同,所以必须将每个对象的成员变量单独存储,而不同对象的成员函数是一样的,只不过是传参数的值不同罢了,没有必要再将成员函数单独存储。所以就将成员函数都放在公共的代码段
假设A类中所有成员都是公有的,A实例化一个对象a
,a.print()
实际上是去公共空间里去找,a.num
是到对象里面找
所以,一个对象的大小,就是计算其中成员变量大小“之和”,这里要注意内存对齐原则
这里的内存对齐原则和结构体内存对齐是一样的,内存对齐具体的内容参考:结构体内存对齐
注意空类的大小,空类是指没有成员变量只有成员函数的类
对于空类,编译器给了空类一个字节来唯一标识这个类的对象,是为了占位,表示对象存在
this指针
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(2022, 1, 11);d2.Init(2022, 1, 12);d1.Print();d1.print(&d1)d2.Print();return 0;
}
先来思考一个问题,一个类实例化了2个对象,通过2个对象访问同一个成员函数时,前面知道成员函数存储在公共代码段里,函数体中没有关于不同对象的区分
那么d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢
C++中通过引入this指针解决该问题
即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
所以不同对象虽然调用的函数相同,但是函数中的形参不同.
用Print
函数为例,编译器会处理成员函数中隐藏的this指针
对于调用成员函数,也会被编译器自动处理:
通过d1调用Init函数时,
d1.Init(2022, 1, 11)
,实际上是传递4个参数:d1.Init(&d1,2022, 1, 11)
要注意的是:C++里,不允许在实参或形参中显示使用this指针,但是允许在函数中使用
- this指针的类似:类类型 * const ,所以在成员函数中,不能给this赋值
- this指针只能在成员函数中使用
- this指针是一个隐藏的形参,当调用成员函数时,将对象地址作为实参传递给this实参
- this作为形参,存储在栈区,不存储在对象中,函数结束后,this指针自动销毁
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
分析一下下面的代码是否能正确运行:
class A
{
public:void PrintA(){cout << "Print()" << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA();return 0;
}
p->PrintA()
,p
调用PrintA()
也不会发生解引用错误,因为PrintA()
是成员函数,它储存在公共代码段,不在对象中
这里将A类型的指针p设为空指针,通过p去访问PrintA()
,这里实际上把p
作为实参传递给了PrintA()
里的this
形参,所以在成员函数PrintA()
中的this
指针是空指针,但是在这个函数里并没有对this指针进行解引用,所以这个函数可以正常运行出来
在分析下面这个代码:
class A
{
public:void PrintA(){cout << _a << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA();return 0;
}
和上面代码类似,p作为实参传递给了成员函数中的形参,但是在这个函数中,cout << _a << endl
实际上是cout << this->_a << endl
这里对this
指针进行解引用了,所以这段代码会出错。