# 面向对象 ## 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; } ``` 当我们调用函数时,可以看到构造函数和析构函数会被调用 因为函数对象在栈区调用完就会被释放,当我们在main函数中直接调用Person,会出现另一种情况 int main() { //test01(); Person p2; system("pause"); return 0; } 之所以会出现这种情况是因为我们的main函数在运行到`system("pause");`时会中断在这,当我们按任意键之后会继续运行,我们在return这打个断点,会出现以下情况: ## 拷贝构造函数 拷贝挂在调用时机: 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 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; } ``` 运行结果: 可以看到p2在编译器默认提供的拷贝构造函数下进行了隐式的拷贝 ```c++ #include 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; } } 在运行到析构函数时,我们定义的释放 把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) //指向一个新的空间 } ``` 运行结果: 可以看到没有报错正常运行,析构被调用了两次,第一次是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() { } ``` 运行结果: 静态成员函数 ``` 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`关键字来把类成员定义为静态 #### 要点: 静态变量,是在编译阶段就分配空间,对象还没有创建时,就已经分配空间。 静态成员变量必须在类中声明,在类外定义。 静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间。 # 简单对象模型  第一个模型十分简单,尽可能减低C++编译器的设计复杂度而开发的,相对的赔上的则是空间和执行效率。在这个简单模型中,一个object是一系列的slots(插槽),每个slots指向一个members。Members按其声明顺序,各被指定一个slots。每一个data/function members都有一个slots。 在这个模型下,object存放的是“指向member的指针”,可以避免“members”有不同的类型导致需要不同的储存空间。Object中的members是以slot的索引值来寻址的。 计算class object的大小:指针大小 乘以 class中所声明的members的个数。 [^这个模型并没有被应用于实际产品上,不过关于索引或slot个数的观念被应用到C++的“指向成员的指针”]: # 表格驱动对象模型  这一种对象模型是把所有与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。 # 多态 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`关键字来把类成员定义为静态 #### 要点: 静态变量,是在编译阶段就分配空间,对象还没有创建时,就已经分配空间。 静态成员变量必须在类中声明,在类外定义。 静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间。 # 简单对象模型  第一个模型十分简单,尽可能减低C++编译器的设计复杂度而开发的,相对的赔上的则是空间和执行效率。在这个简单模型中,一个object是一系列的slots(插槽),每个slots指向一个members。Members按其声明顺序,各被指定一个slots。每一个data/function members都有一个slots。 在这个模型下,object存放的是“指向member的指针”,可以避免“members”有不同的类型导致需要不同的储存空间。Object中的members是以slot的索引值来寻址的。 计算class object的大小:指针大小 乘以 class中所声明的members的个数。 [^这个模型并没有被应用于实际产品上,不过关于索引或slot个数的观念被应用到C++的“指向成员的指针”]: # 表格驱动对象模型  这一种对象模型是把所有与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 日 © 允许规范转载 打赏 赞赏作者 支付宝 赞 如果觉得我的文章对你有用,请随意赞赏
86 条评论
银河护卫队3
她想要什么圣诞礼物
我们之间的爱
亨利休格的神奇故事
卫斯理传奇粤配
LOL惊喜冬季时装秀
罗伯特唐尼自成一格
大冒险家粤配
狼人杀启源
真实泰勒斯威夫特和特拉维斯凯尔西爱情故事还是反英雄
恶魔娜塔丽
我相信圣诞老人
罗斯坎普黑手党和英国
卡萨托里亚
杀死托尼笑不死偿命
飞龙斩1976
我的西门小故事
聊斋之极道天师
圣帕特里克的驱魔
猫步2回归的猫
我们之间的爱
我是狼之火龙山大冒险
静静的绿河
燃爱之高岭之花第二季
到货即损doa
谍影重重5
隔墙有情人
黎明的河边
周围有婴儿的哭声吗
一代爱国高僧圆瑛
狂暴3击倒总统
关机一小时
飞龙斩1976
我心脏机器人
康奈尔之盒
误杀瞒天记
唐人街探案3
东北大仙儿
揭开圣诞节的面纱蒂娜的奇迹
银河护卫队3
搭秋千的人
最后一根稻草
远离迷幻2
金秋与时间赛跑
桑苏扎德克内迪梅
洪福齐天粤配
乜代宗师粤配
罗宾汉也疯狂
只有我能喜欢你
帕洛玛之旅
吞噬星空剧场血洛大陆
世界上最好的
南少林之怒目金刚
水班长许东奎
寻宝搭档圣诞特别篇
圣诞大赢家
罗宾汉也疯狂
鬼娃回魂2
燃爱之高岭之花第二季
404宿灵速速逃
你好爱再次
超級鯊魚大道港
长江7号超萌特攻队
罗宾汉也疯狂
妈妈走了GoneMom
深海狂鲨2
亲近的人却离得很远
眼镜里的海
李碧华鬼魅系列奇幻夜粤配
秦岭诡事之守护者
佳节好声音
告诉我你想要什么
木匠的祈祷
我听说你买灵魂
皇家圣诞假期
动物的秘密生活
好像也没那么热血沸腾
哈利波特与魔法石
哈莉奎茵问题多多的情人节特集
伊格尔和鹤的旅程
ktv火热的女人2
广东小老虎
大块头有大智慧粤配
长江7号超萌特攻队
谍影重重4
这篇文章提供了宝贵的经验和见解,对读者有很大的启发和帮助。