Loading... # 面向对象 ## struct和class区别 - struct默认权限为公共(public) - class默认权限为私有(private) ## 构造函数和析构函数(重点) - 析构函数:主要用于在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用 - 构造函数:主要作用于在对象**销毁前**系统自动调用,执行清理工作。 ### 语法: 构造函数:`类名(){}` 1. 没有返回值,不写void 2. 函数名称与类名相同 3. 构造函数可以有参数,可发生重载 析构函数:`~类名(){}` 1. 没有返回值,不写void 2. 函数名称与类名相同,在名称前加上符号`~` 3. 析构函数不可以有参数,不可发生重载 ### 实例: ```Cpp class Person { public: Person(){ cout << "Person的构造函数" << endl; } ~Person() { cout << "Person的析构函数" << endl; } }; void test01(){ Person p; } int main(){ test01(); system("pause"); return 0; } ``` 当我们调用函数时,可以看到构造函数和析构函数会被调用 <img src="C:\Users\20649\AppData\Roaming\Typora\typora-user-images\image-20230831162408347.png" alt="image-20230831162408347" style="zoom:50%;" style=""> 因为函数对象在栈区调用完就会被释放,当我们在main函数中直接调用Person,会出现另一种情况 int main() { //test01(); Person p2; system("pause"); return 0; } <img src="C:\Users\20649\AppData\Roaming\Typora\typora-user-images\image-20230831163600720.png" alt="image-20230831163600720" style="zoom:50%;" style=""> 之所以会出现这种情况是因为我们的main函数在运行到`system("pause");`时会中断在这,当我们按任意键之后会继续运行,我们在return这打个断点,会出现以下情况: <img src="C:\Users\20649\AppData\Roaming\Typora\typora-user-images\image-20230831164159224.png" alt="image-20230831164159224" style="zoom:50%;" style=""> ## 拷贝构造函数 拷贝挂在调用时机: 1. 使用一个已经创建完成的对象来初始化一个新对象 2. 值传递的方式给函数参数传值 3. 以值的方式返回局部对象 ```cpp class Person { public: Person(){ cout << "Person的构造函数" << endl; } Person(int age) { cout << "Person有参构造函数调用" << endl; m_Age = age; } Person(const Person& p) { cout << "Person拷贝构造函数调用" << endl; m_Age = p.m_Age; } ~Person() { cout << "Person的析构函数" << endl; } int m_Age; }; void test01() { Person p1(20); Person p2(p1); //将p1拷贝至p2,从而调用拷贝构造函数 } //////////////////////// void int main() { test01(); system("pause"); return 0; } ``` ## 函数调用规则 1. 默认构造函数(无参,函数体为空) 2. 默认析构函数(无参,函数体为空) 3. 默认拷贝函数 也就是说创建一个类,编译器就会自动添加这三个函数 - 如果用户定义有参构造函数 C++不在提供默认无参构造 但是会提供默认拷贝构造 - 如果定义拷贝构造函数 C++不会再提供其他构造函数 ## 深拷贝 浅拷贝(重点) 深拷贝:在堆区重新申请空间,进行拷贝操作 浅拷贝:简单的复制拷贝操作 ```c++ #include<iostream> using namespace std; class Person { public: Person() { cout << "Person的默认构造函数调用" << endl; } Person(int age) { m_Age = age; cout << "Person有参构造函数调用" << endl; } ~Person() { cout << "Person析构函数调用" << endl; } int m_Age; }; void test01() { Person p1(18); cout << p1.m_Age << endl; Person p2(p1); cout << p2.m_Age << endl; } int main() { test01(); system("pause"); return 0; } ``` 运行结果: <img src="C:\Users\20649\AppData\Roaming\Typora\typora-user-images\image-20230901082707472.png" alt="image-20230901082707472" style="zoom:50%;" style=""> 可以看到p2在编译器默认提供的拷贝构造函数下进行了隐式的拷贝 ```c++ #include<iostream> using namespace std; class Person { public: Person() { cout << "Person的默认构造函数调用" << endl; } Person(int age , int Height) { m_Age = age; m_Height = new int(Height); cout << "Person有参构造函数调用" << endl; } ~Person() { cout << "Person析构函数调用" << endl; if (m_Height != NULL) { delete m_Height; m_Height = NULL; } } int m_Age; int* m_Height; }; void test01() { Person p1(18,170); cout << p1.m_Age << *p1.m_Height << endl; Person p2(p1); cout << p2.m_Age << *p2.m_Height << endl; } int main() { test01(); system("pause"); return 0; } ``` 上面的代码运行之后会报错,之所以会报错,是因为我们的m_Height开辟在堆区,我们需要手动释放,下面这段代码实现;这就导致一个问题:浅拷贝会将同样的地址拷贝到p2,就导致我们的p1和p2指向的是同一个对象;当我们用以下代码在释放时,会导致另一个指针找不到对象,就会导致报错。 ~Person() { cout << "Person析构函数调用" << endl; if (m_Height != NULL) { delete m_Height; m_Height = NULL; } } <img src="C:\Users\20649\AppData\Roaming\Typora\typora-user-images\image-20230901100148313.png" alt="image-20230901100148313" style="zoom:50%;" style=""> 在运行到析构函数时,我们定义的释放 <img src="C:\Users\20649\AppData\Roaming\Typora\typora-user-images\image-20230901100251148.png" alt="image-20230901100251148" style="zoom:50%;" style=""> 把p2下的h_Height释放,导致p1找不到对象报错;解决方法也十分简单,我们需要把默认拷贝函数修改一下: 这是编辑器自动给你写的: ``` Person(const Person& p) { cout << "Person拷贝构造函数" << endl; m_Age = p.m_Age; m_Height = p.m_Height; //指向原空间 } ``` 我们需要将m_Height指向一个新的空间: ``` Person(const Person& p) { cout << "Person拷贝构造函数" << endl; m_Age = p.m_Age; //m_Height = p.m_Height; //指向原空间 m_Height = new int(*p.m_Height) //指向一个新的空间 } ``` 运行结果: <img src="C:\Users\20649\AppData\Roaming\Typora\typora-user-images\image-20230901111802238.png" alt="image-20230901111802238" style="zoom:50%;" style=""> 可以看到没有报错正常运行,析构被调用了两次,第一次是p1的析构,第二次是p2的析构。 ## 静态成员 ### 语法: 成员变量和成员函数前加上关键字static * 静态成员变量 * 所有对象共享同`一份`数据 * 在编译阶段分配内存 * 类内声明,类外初始化 * 静态成员函数 * 所有对象共享同一个函数 * 静态成员函数只能访问静态成员变量 静态成员变量: ``` class Person { public: static int m_A; //静态成员变量 //静态成员变量特点: //1 在编译阶段分配内存 //2 类内声明,类外初始化 //3 所有对象共享同一份数据 private: static int m_B; //静态成员变量也是有访问权限的 }; int Person::m_A = 10; //声明静态变量 int Person::m_B = 10; void test01() { //静态成员变量两种访问方式 //1、通过对象 Person p1; p1.m_A = 100; cout << "p1.m_A = " << p1.m_A << endl; Person p2; p2.m_A = 200; cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据 cout << "p2.m_A = " << p2.m_A << endl; //2、通过类名 cout << "m_A = " << Person::m_A << endl; //cout << "m_B = " << Person::m_B << endl; //私有权限访问不到 } int main() { } ``` 运行结果: <img src="C:\Users\20649\AppData\Roaming\Typora\typora-user-images\image-20230903155413795.png" alt="image-20230903155413795" style="zoom:50%;" style=""> 静态成员函数 ``` class Person { public: //静态成员函数特点: //1 程序共享一个函数 //2 静态成员函数只能访问静态成员变量 static void func() { cout << "func调用" << endl; m_A = 100; //m_B = 100; //错误,不可以访问非静态成员变量 } static int m_A; //静态成员变量 int m_B; // private: //静态成员函数也是有访问权限的 static void func2() { cout << "func2调用" << endl; } }; int Person::m_A = 10; void test01() { //静态成员变量两种访问方式 //1、通过对象 Person p1; p1.func(); Person p2; p2.func2(); //访问失败,静态函数也是有访问权限的 //2、通过类名 Person::func(); //Person::func2(); //私有权限访问不到 } ``` # 对象布局 ## 阅前理解: ### 析构函数 #### 什么是析构函数: 析构函数(destructor) 与[构造函数](https://www.runoob.com/cplusplus/cpp-constructor-destructor.html)相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作 #### 声明: 析构函数是具有与类相同的名称但前面是波形符 (`~`) 的函数 #### 要点: - 不接受参数 - 不要(或void)返回值 - 不可以声明为**`const`**、**`volatile`**或 **`static`**,但是,可以为声明为 **`const`**、**`volatile`** 或 **`static`** 的对象的析构调用它们。 - 可以声明为 **`virtual`**。 使用虚拟析构函数,可以在不知道对象类型的情况下销毁对象 - 使用虚拟函数机制调用对象的正确析构函数。 还可以将析构函数声明为抽象类的纯虚拟函数。 ### 虚函数 #### 什么是虚函数: 虚函数的主要目的是实现`多态(polymorphism)`,虚函数是一种由virtual关键字修饰的一种类内函数,可分为虚函数和纯虚函数。 #### 声明: 关键字:`virtual` #### 要点: 多态,可扩展性,代码重用 ### 静态成员变量 #### 什么是静态成员变量: 不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象共享。 #### 声明: `static`关键字来把类成员定义为静态 #### 要点: 静态变量,是在编译阶段就分配空间,对象还没有创建时,就已经分配空间。 静态成员变量必须在类中声明,在类外定义。 静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间。 # 简单对象模型 ![](https://img-1301015273.cos.ap-guangzhou.myqcloud.com/Obsindian/%E7%AE%80%E5%8D%95%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B.jpg) 第一个模型十分简单,尽可能减低C++编译器的设计复杂度而开发的,相对的赔上的则是空间和执行效率。在这个简单模型中,一个object是一系列的slots(插槽),每个slots指向一个members。Members按其声明顺序,各被指定一个slots。每一个data/function members都有一个slots。 在这个模型下,object存放的是“指向member的指针”,可以避免“members”有不同的类型导致需要不同的储存空间。Object中的members是以slot的索引值来寻址的。 计算class object的大小:指针大小 乘以 class中所声明的members的个数。 [^这个模型并没有被应用于实际产品上,不过关于索引或slot个数的观念被应用到C++的“指向成员的指针”]: # 表格驱动对象模型 ![](https://img-1301015273.cos.ap-guangzhou.myqcloud.com/Obsindian/%E8%A1%A8%E6%A0%BC%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B.jpg) 这一种对象模型是把所有与members相关的信息抽出来,放在一个data member table和一个member function table之中,class object本身则包含指向这两个表格的指针。Members function table是一系列的slots,每一个slot指出一个member function;Data member table则直接持有data本身。 再此模型中,Nonstatic data members被配置于每一个class objeck内,static data members则被存放在个别的class object外,Static和nonstatic function members也被放在个别class object外。 Virtual function则以两个步骤支持: 1. 每个class产生一堆指向virtual function的指针,放在表格之中。表格称为virtual table(**vtbl**)。 2. 每一个class object被安插一个指针,指向相关的virtual table。通常这个指针被称为**vptr**,每一个class所关联的type_info object也经由virtual table被指出来,通常放在表格第一个slots。 # 多态 最后修改:2024 年 12 月 06 日 © 允许规范转载 打赏 赞赏作者 支付宝 赞 如果觉得我的文章对你有用,请随意赞赏