- 9.4 类和对象
- C++面向对象的三大特征:封装、继承、多态
- C++认为万物皆为对象,对象上有属性和行为
- 具有相同性质的对象,可以抽象为类
- 人属于人类,车属于车类
- 9.4.1 封装
- 9.4.1.1 封装的意义
- (1)封装的意义
- 1. 将属性和行为作为一个整体,表现生活中的事物
- 2. 将属性和行为加以权限控制
- (2)封装意义一:
- 在设计类的之后,属性和行为写在一起,表现事物
- 语法:class 类名 { 访问权限: 属性 / 行为 }
- 类中的属性和行为统称为成员
- 1. 属性 -> 成员属性 -> 成员变量
- 2. 行为 -> 成员函数 -> 成员方法
- 类中的属性和行为统称为成员
- 示例1:设计一个圆类,求圆的周长
-
const double PI = 3.14; // 圆周率 class Circle { public: // 公共的访问权限 int m_r; // 属性:半径 double calculateZC() { return 2 * m_r * PI; } private: // 私有的访问权限 }; int main() { Circle c1; // 实例化: 通过圆类创建具体的圆(对象) c1.m_r = 10; // 圆对象的属性半径进行赋值 cout << "圆的周长: " << c1.calculateZC() << endl; }
-
- 示例2:设计一个学生类,属性有姓名和学号
-
// 学生类 class Student { public: string m_name; int m_id; void showStudent() { cout << " 姓名: " << m_name << " 学号: " << m_id << endl; } void setName(string name) { m_name = name; } void setID(int id) { m_id = id; } }; int main() { Student s1; s1.m_name = "张三"; s1.m_id = 1; s1.showStudent(); s1.m_name = "李四"; s1.m_id = 2; s1.showStudent(); }
-
- (3)封装意义二:
- 类在设计时,可以把属性和行为放在不同的权限下,加以控制
- 访问权限有三种
- 1. public 公共权限 类内可以访问 类外可以访问
- 2. protected 保护权限 类内可以访问 类外不可以访问 儿子可以访问父类中的保护内容
- 3. private 私有权限 类内可以访问 类外不可以访问 儿子不可以访问父类中的保护内容
- 示例:
-
class Person { public: string m_Name; // 姓名 protected: string m_Car; // 汽车 private: int m_psw; // 银行卡密码 public: void func() { m_Name = "张三"; m_Car = "拖拉机"; m_psw = 1234; } }; int main() { Person p1; p1.m_Name = "李四"; // p1.m_Car = "奔驰"; // protected权限在类外不能访问 // p1.m_psw = 123; // private权限在类外不能访问 p1.func(); }
-
- (1)封装的意义
- 9.4.1.2 struct和class区别
- 在C++中 struct 和 class 唯一的区别就在于默认的访问权限不同
- 区别:
- struct 默认权限为公共
- class 默认权限为私有
- 示例
-
class C1 { int m_A; // 默认是私有权限 }; struct C2 { int m_A; // 默认是公共权限 }; int main() { C1 c1; // c1.m_A = 100; // 表述错误 默认权限是私有,类外无法访问 C2 c2; c2.m_A = 100; // 默认权限是共有,类外可以访问 }
-
- 9.4.1.3 成员属性设置为私有
- 优点1:将所有成员属性设置为私有,可以控制读写权限
- 优点2:对于写权限,我们可以检测数据的有效性
- 示例:
-
class Person { public: void setName(string name) { m_Name = name; } string getName() { return m_Name; } void setAge(int age) { if (age < 0 || age > 150) { // 优点2:检测数据的有效性 cout << " 年龄不合理 " << endl; return; } m_Age = age; } int getAge() { return m_Age; } void setLover(string lover) { m_Lover = lover; } private: // 优点1: 控制读写权限 string m_Name; // 姓名 可读可写 int m_Age; // 年龄 可读可写 string m_Lover; // 朋友 只写 }; int main() { Person p; p.setName("张三"); cout << "名字是:" << p.getName() << endl; }
-
- 9.4.1.1 封装的意义
- 9.4.2 对象的初始化和清理
- 9.4.2.1 构造函数和析构函数
- 1. 对象的初始化和清理是两个非常重要的安全问题
- 1. 一个对象或者变量没有初始化状态,对其是用后果是未知的
- 2. 统一的使用完一个对象或者变量,没有及时清理,也会造成一定的安全问题
- 2. 如果不提供构造函数和析构函数的实现,则编译器会提供构造函数和析构函数的空实现
- 1. 构造函数
- 作用:主要用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
- 语法:类名( ) { }
- 1. 构造函数,没有返回值也不写void
- 2. 函数名与类名相同
- 3. 构造函数可以有参数,也可以重载
- 4. 程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次
- 2. 析构函数
- 作用:主要作用在于对象销毁前系统自动调用,执行一些清理工作
- 语法:~ 类名( ) { }
- 1. 析造函数,没有返回值也不写void
- 2. 析造函数名与类名相同,在名称前加~
- 3. 析造函数没有参数,不可以重载
- 4. 程序在销毁对象前会自动调用析造,无须手动调用,而且只会调用一次
- 示例:构造函数和析构函数
-
class Person { public: Person() { cout << "构造函数" << endl; } ~Person() { cout << "析构函数" << endl; } }; int main() { Person p; system("pause"); return 0; }
-
- 1. 构造函数
- 1. 对象的初始化和清理是两个非常重要的安全问题
- 9.4.2.2 构造函数的分类和使用
- 两种分类方式:
- 1. 按参数分为:有参构造和无参构造
- 2. 按类型分为:普通构造和拷贝构造
- 三种调用方式:
- 1. 括号法
- 2. 显示法
- 3. 隐式转换法
- 示例
-
// 构造函数的分类 class Person { public: Person() // 无参构造 { cout << "构造函数-无参构造" << endl; } Person(int a) // 有参构造 { age = a; cout << "构造函数-有参构造" << endl; } Person(const Person &p) // 拷贝构造函数 { age = p.age; // 将传入人身上所有的属性拷贝到我身上 cout << "拷贝构造函数" << endl; } ~Person() { cout << "析构函数" << endl; } int age; }; // 调用:三种调用方式 void test01() { // 1. 括号法 //Person p; // 默认构造函数 //Person p1(); // 无参构造函数 //Person p2(10); // 有参构造函数 //Person p3(p2); // 拷贝构造函数 //cout << "p2的年龄为:" << p2.age << endl; //cout << "p3的年龄为:" << p3.age << endl; // 注意事项1 // 调用默认构造函数的时候,不要加() // 2. 显示法 Person p; // 默认构造函数 Person p2 = Person(10); // 有参构造函数 Person p3 = Person(p2); // 拷贝构造函数 Person(10); // 匿名对象,特点:当前执行结束狗,系统会马上回收内存 // 注意事项2 // 不要利用拷贝构造函数,初始化匿名对象,编译器会认为这是一个对象的声明 // Person(p3); // 3. 隐式转换法 Person p4 = 10; // 相当于写了Person p4 = Person(10) // 有参构造函数 Person p5 = p4; // 拷贝构造函数 } int main() { test01(); system("pause"); return 0; }
-
- 两种分类方式:
- 9.4.2.3 拷贝构造函数调用时机
- C++中拷贝构造函数调用时机通常有三种情况
- 1. 使用一个已经创建完毕的对象来初始化一个新对象
- 2. 值传递的方式给函数参数传值
- 3. 以值方式返回局部对象
- 示例
-
// 调用:三种调用方式, C++中拷贝构造函数调用时机通常有三种情况 //1. 使用一个已经创建完毕的对象来初始化一个新对象 void test01() { Person p1(20); Person p2(p1); cout << "p2的年龄:" << p2.age << endl; // 20 } //2. 值传递的方式给函数参数传值 void doWork(Person p) { } void test02() { Person p; doWork(p); } //3. 以值方式返回局部对象 Person doWork2() { Person p1; cout << "doWork2 p1的地址:" << (int*)&p1 << endl; // 006AFAB8 return p1; } void test03() { Person p = doWork2(); cout << "test03 p1的地址:" << (int*)&p << endl; // 006AFBB0 } int main() { test01(); test02(); test03(); system("pause"); return 0; }
-
- C++中拷贝构造函数调用时机通常有三种情况
- 9.4.2.4 构造函数调用规则
- 创建一个类,C++编译器会给每个类添加只是三个函数
- 1. 默认构造(空实现)
- 2. 析构函数(空实现)
- 3. 拷贝构造(值拷贝)
- 默认情况下,C++编译器至少给一个类添加3个函数
- 1. 默认构造函数(无参,函数体为空)
- 2. 默认析构函数(无参,函数体为空)
- 3. 默认拷贝构造函数,对属性进行值拷贝
- 构造函数调用规则如下:
- 1. 如果用户定义有参构造函数,C++不再提供无参构造,但是会提供默认拷贝构造
- 2. 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
- 9.4.2.5 深拷贝和浅拷贝
- 深拷贝:在堆区重新申请空间,进行拷操作
- 浅拷贝:简单的赋值拷贝操作(浅拷贝的问题是堆区的内容重复释放)
- 总结:如果属性是在堆区开辟的,一定要在代码中提供深拷贝函数,防止浅拷贝带来的问题
- 示例
-
int* m_Height; Person(const Person &p) // 拷贝构造函数 { age = p.age; // 将传入人身上所有的属性拷贝到我身上 // m_Height = p.m_Height; // 浅拷贝的实现方式,也是编译器默认的实现方式 m_Height = new int(*p.m_Height); // 深拷贝的实现方式,开辟一块新法内存空间 cout << "拷贝构造函数" << endl; }
-
-
- 9.4.2.6 初始化列表
- 作用:C++提供了初始化列表语法,用来初始化属性
- 语法:构造函数():属性1(值1),属性2(值2)... { }
- 示例
-
class Person { public: /* 传统的初始化操作 Person(int a, int b, int c) { m_A = a; m_B = b; m_C = c; }*/ // 初始化列表,初始化属性值 Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) { } int m_A; int m_B; int m_C; }; void test01() { Person p(100,200,300); cout << p.m_A << p.m_B << p.m_C << endl; }
-
- 9.4.2.7 类对象作为类成员
- C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
- 例如:B类中有对象A作为成员,A为对象成员
- class A {}
- class B{
- A a;
- }
- 例如:B类中有对象A作为成员,A为对象成员
- 那么当创建B对象的时候,A与B的构造和析构谁先谁后?
- 结论:A的构造 -> B的构造 -> B的析构 -> A的析构
- 构造的时候,先构造类内的对象,在构造自身,析构的时候相反。
- C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
- 9.4.2.8 静态成员
- 1 静态成员就是在成员变量和成员函数前加上关键字static,称其为静态成员
- 2 静态成员分为:
- 1. 静态成员变量
- 1) 所有对象共享同一份数据
- 2) 在编译阶段分配内存
- 3) 类内声明,类外初始化
- 2. 静态成员函数
- 1) 所有对象共享同一个函数
- 2) 静态成员函数只能访问静态成员变量
- 1. 静态成员变量
- 3 访问静态成员的方式
- 1. 通过对象访问
- 2. 通过类名访问
- 示例
-
class Person { public: static void func() { // 静态成员函数 m_A = 100; // 静态成员函数可以访问静态成员变量 // m_B = 200; // 会报错,因为静态成员函数不可以访问非静态成员变量 // 因为非静态成员变量,要通过对象进行访问,直接访问的话无法区分是哪个对象的成员变量 cout << "static void func() 调用" << endl; } static int m_A; // 静态成员变量 int m_B; // 非静态成员变量 private: // 静态成员函数也是有访问权限的 static void func2() { cout << "static void func2() 调用" << endl; } }; int Person::m_A = 0; // 静态成员变量类外初始化 void test01() { Person p; p.func(); // 1. 通过对象访问 Person::func(); // 2. 通过类名访问 // Person::func2(); // 报错,类外无权访问 }
-
- 9.4.3 C++对象模型和this指针
- 9.4.3.1 成员变量和成员函数分开存储
- 在C++中,类的成员变量和成员函数分开存储
- 只有非静态成员变量才属于类的对象上
- tip
- 1. 空对象占用内存空间为 1
- 1 C++编译器为每个空对象分配一字节空间,是为了区分空对象栈内存的位置
- 2 每个空对象也有独一无二的内存地址
- 2.
- 1. 空对象占用内存空间为 1
- 示例
-
class Person { public: static int m_A; // 静态成员变量,不属于类对象上 int m_B; // 非静态成员变量,属于类对象上 int m_C; // 非静态成员变量,属于类对象上 void func() {} // 非静态成员函数,不属于类对象上 }; void test01() { Person p; cout << "size of P = " << sizeof(p) << endl; // 输出8,8字节是m_B和m_C占用的内存 }
-
- 9.4.3.2 this指针
- 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
- 问题是:这一块代码是如何区分哪个对象调用自己的呢?
- C++通过提供特殊的对象指针,this指针,解决上述问题
- this指针指向被调用的成员函数所属的对象
- this指针是隐含每一个非静态成员函数内的一种指针
- this指针不需要定义,直接使用即可
- this指针的用途
- 1. 当形参和成员变量同名是,可用this指针来区分
- 2. 在类的非静态成员函数中返回对象本身,可使用return *this;
- 示例
-
class Person { public: Person(int age) { this->age = age; } // 返回值必须是引用方式,如果不是引用方式返回,则无法返回对象本身,而是创建了一个新的对象返回 Person& PersonAddAge(Person& p) { this->age += p.age; return *this; } int age; }; void test01() { Person p1(10); Person p2(10); p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); // 链式编程 cout << "p2 age = " << p2.age << endl; // 输出40 }
-
- 9.4.3.3 空指针访问成员函数
- C++空指针也可以调用成员函数的,但是也要注意有没有用到this指针
- 如果用到this 指针,需要加以判断保证代码的健壮性
- 示例
-
class Person { public: void showClassName() { cout << "This is a Person class!" << endl; } void showPerson() { if (this == NULL) { return; } cout << "age = " << m_Age << endl; } int m_Age; }; void test01() { Person* p1 = NULL; // 这是一个空指针 p1->showClassName(); p1->showPerson(); }
-
- C++空指针也可以调用成员函数的,但是也要注意有没有用到this指针
- 9.4.3.4 const修饰成员函数
- 1.常函数
- 1. 成员函数加const后我们称这个函数为常函数
- 2. 常函数内不可以修改成员属性
- 3. 长远属性声明是加关键字mutable后,在常函数中依然可以修改
- 2. 常对象
- 1. 声明对象前加const称该对象为常对象
- 2. 常对象只能调用常函数
- 示例
-
class Person { public: // this 指针的本质 是指针常量 指针的指向是不可以修改的 // 在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改 // 下一行相当于 const Person * const this; void showPerson() const // 常函数 { // this 指针可以表达为 Person* const this; // this = NULL; // 表达式错误,this指针不可以修改指针的指向 // this->m_Age = 100; // 表达式错误,常函数内不可以修改成员属性 this->m_B = 100; } void func() { } int m_Age; mutable int m_B; // 特殊变量,即使在常函数中也可以修改 }; void test01() { Person p1; p1.showPerson(); } void test02() { const Person p2; //对象前加const,变为常对象 // p2.m_Age = 100; // 表达式错误,常对象内不可以修改成员 p2.m_B = 200; // m_B是特殊值,在常对象下也可以修改 // p2.func(); // 表达式错误,常对象只能调用常函数 }
-
- 1.常函数
- 9.4.3.1 成员变量和成员函数分开存储
- 9.4.4 友元
- 9.4.4.1 友元介绍
- 比喻:客厅(public)、卧室(Private);
- 客厅所有客人都可以去,卧室私有仅仅自己能进
- 卧室也允许自己的朋友(friend)进去
- 程序中,有一些私有属性也可以让类外的特殊函数或者类访问,需要用到友元
- 友元的目的:让一个函数或者类访问另一个类的私有成员
- 友元关键字:friend
- 友元的三种实现
- 1. 全局函数做友元
- 2. 类做友元
- 3. 成员函数做友元
- 比喻:客厅(public)、卧室(Private);
- 9.4.4.2 全局函数做友元
- 9.4.4.3 类做友元
- 9.4.4.4 成员函数做友元
- 示例: 三种方式做友元
-
class Buliding { friend void goodGay(Buliding* buliding);// 方式1:全局函数做友元:goodGay全局函数是Buliding好朋友,可以访问Buliding中的私有成员 friend class GoodGay; // 方式2:类做友元 friend void BadGay::visit(); // 方式3:成员函数做友元 public: Buliding() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } public: string m_SittingRoom; // 客厅 private: string m_BedRoom; // 卧室 }; // 方式1:全局函数做友元 void goodGay(Buliding* buliding) { cout << "全局函数做友元 好朋友正在访问:" << buliding->m_SittingRoom << endl; cout << "全局函数做友元 好朋友正在访问:" << buliding->m_BedRoom << endl;// 方式1:全局函数做友元.访问私有成员 } // 方式2:类做友元 class GoodGay { public: GoodGay() { buliding = new Buliding; } void visit() { // 参观函数,访问building中的属性 cout << "类做友元 好朋友正在访问:" << buliding->m_SittingRoom << endl; cout << "类做友元 好朋友正在访问:" << buliding->m_BedRoom << endl; // 方式2:类做友元.访问私有成员 } Buliding* buliding; }; // 方式3:成员函数做友元 class BadGay { public: BadGay(); void visit();// visit1函数不可以访问BUliding中的私有成员 void visit2(); //3.成员函数做友元: visit2参观函数,访问building中的私有成员 Buliding* buliding; }; BadGay::BadGay() { buliding = new Buliding; } void BadGay::visit() { //3.成员函数做友元: visit2参观函数,访问building中的私有成员 cout << "普通函数 正在访问:" << buliding->m_SittingRoom << endl; cout << "成员函数做友元 好朋友正在访问:" << buliding->m_BedRoom << endl; // 方式2:类做友元.访问私有成员 } void BadGay::visit2() { cout << "成员函数做友元 好朋友正在访问:" << buliding->m_SittingRoom << endl; } void test01() { Buliding buliding; goodGay(&buliding); // 方式1:全局函数做友元 GoodGay goodGay; goodGay.visit(); // 方式2:类做友元 BadGay badGay; badGay.visit(); // 方式3:成员函数做友元 }
-
- 9.4.4.1 友元介绍
- 9.4.5 运算符重载
- 9.4.6 继承
- 9.4.6.1 继承的基本语法
- 语法:class 子类 : 继承方式 父类
- 示例:class A : public B;
- A类称为 子类 或者 派生类
- 子类中的成员,包含两大部分:
- 1. 一类是从基类继承过来的
- 2. 另一类是自己增加的成员
- 从基类继承过来的成员表现出其共性,新增的成员表现出其个性
- 子类中的成员,包含两大部分:
- B类称为 父类 或者 基类
- 语法:class 子类 : 继承方式 父类
- 9.4.6.2 继承方式
- 继承方式有三种:
- 1. 公共继承
- 2. 保护继承
- 3. 私有继承
- 三种继承方式展示
- 继承方式有三种:
- 9.4.6.3 继承中的对象模型
- 问题:从父类继承过来的成员,哪些属于子类对象中?
- 答案:父类中所有的非静态成员属性都会被子类继承。父类中私有成员属性是被编译器隐藏了,因此访问不到,但是依旧在子类中占有内存空间
- sizeof(子类) = sizeof(父类) + sizeof(子类新增的成员属性)
- 利用开发人员命令提示工具查看对象模型
- 1. 跳转到code所在的路径
- 2. 命令行输入具体的查看命令:
- cl /d1 reportSingleClassLayout类名 文件名
- 示例:
- cl /d1 reportSingleClassLayoutSon "01C++HelloWorld.cpp"
- cl /d1 reportSingleClassLayoutSon "01C++HelloWorld.cpp"
- 问题:从父类继承过来的成员,哪些属于子类对象中?
- 9.4.6.4 继承中构造和析构的顺序
- 子类继承父类后,当创建子类对象,也会调用父类的构造函数
- 问题:父类和子类的构造和析构顺序是谁先谁后?
- 答案:父构造 -> 子构造 -> 子析构 -> 父析构
- 9.4.6.5 继承同名成员处理方式
- 问题:当子类和父类出现同名的成员,如果通过子类对象,访问到子类或者父类中同名的数据呢?
- 访问子类同名成员:直接访问即可
- 访问父类同名成员:需要加作用域
- 如果子类中出现和父类同名的成员函数,子类的同名成员函数会将父类的同名成员函数全部隐藏
- 如果想要访问父类的同名成员函数,就需要加作用域
- 示例
- 问题:当子类和父类出现同名的成员,如果通过子类对象,访问到子类或者父类中同名的数据呢?
- 9.4.6.6 继承同名静态成员处理方式
- 问题:继承中同名静态成员在子类对象上如何访问?
- 静态和非静态出现同名时,处理方式一致
- 访问子类同名成员:直接访问即可
- 访问父类同名成员:需要加作用域
- 静态成员有特殊之处
- 静态成员可以通过类名访问,非静态成员不可以
- 示例:
- 静态和非静态出现同名时,处理方式一致
- 问题:继承中同名静态成员在子类对象上如何访问?
- 9.4.6.7 多继承语法
- C++允许一个类继承多个类
- 多继承可能会引发父类有同名成员出现,需要加作用域区分
- C++实际开发中不建议使用多继承
- 语法:class 子类:继承方式 父类1,继承方式 父类2…
- C++允许一个类继承多个类
- 9.4.6.8 菱形继承
- 概念:
- 1. 两个派生类继承同一个基类
- 2. 又有某个类同时继承这两个派生类
- 3. 这种继承称为菱形继承,或者钻石继承
- 问题:可能会出现二义性
- 解决:虚继承 virtual
- vdptr: 虚基类指针
- 示例
- 概念:
- 9.4.6.1 继承的基本语法
- 9.4.7 多态
- 9.4.7.1 多态的基本概念
- 多态分为两类:
- 1. 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
- 2. 动态多态:派生类和虚函数实现运行时多态
- 静态多态和动态多态区别:
- 1. 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 2. 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
- 动态多态的条件
- 1、有继承关系
- 2、子类重写父类的虚函数
- 重写:子类中的函数返回值类型、函数名称、参数列表与父类完全相同
- 动态多态的使用
- 1、父类指针 指向 子类的对象
- 示例:
-
class Animal { public: // 虚函数 virtual void speak() { cout << "动物在说话" << endl; } }; class Cat : public Animal { public: void speak() { cout << "小猫在说话" << endl; } }; // 执行说话的函数 // 如果不加virtual,地址早绑定,在编译阶段就确定了函数地址 // 如果想让子类的方法被调用,父类同名方法要加virtual void doSpeak(Animal &animal) { animal.speak(); } void test01() { Cat cat; doSpeak(cat); // 小猫在说话 }
-
- 多态的底层原理
- 工具查看底层时间的sizeof的大小
- 多态分为两类:
- 9.4.7.2 多态案例一:计算器
- 多态的优点:
- 1. 代码组织结构清晰
- 2.可读性强
- 3. 利于前期和后勤的扩展和维护
- 满足开发原则:对扩展开放、对修改关闭
- 多态的优点:
- 9.4.7.3 纯虚函数和抽象类
- 在多态中,通常父类的虚函数实现是毫无意义的,主要都是调用子类重写的内容
- 因此可以将虚函数改为纯虚函数
- 纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0
- 当一个类中有了纯虚函数,这个类就被称为抽象类
- 抽象类的特点:
- 1. 无法实例化对象
- 2. 子类必须重写抽象类中的纯虚函数,否则也属性抽象类
- 抽象类的特点:
- 9.4.7.4 多态案例二:制作饮品
- 9.4.7.5 虚析构和纯虚析构
- 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
- 解决方式:将父类的析构函数改成虚析构或者纯虚析构
- 虚析构和纯虚析构的共性:
- 1、可以解决父类指针释放子类对象
- 2、都需要具体的函数实现
- 虚析构和纯虚析构的区别:
- 1、如果是纯虚析构,该类属于抽象类,无法实例化对象
- 虚析构语法:virtual ~类名( ){ }
- 纯虚析构语法:
- 1、virtual ~类名( ) = 0;
- 2、类名::~类名( ){ }
- 总结
- 1、虚析构和纯虚析构是用来解决父类指针释放子类对象的问题
- 2、如果子类没有堆区数据,可以不写为虚析构和纯虚析构
- 3、拥有纯虚析构的类也属于抽象类
- 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
- 9.4.7.6 多态案例三:电脑组装
- 9.4.7.1 多态的基本概念
- 9.4.2.1 构造函数和析构函数
- C++面向对象的三大特征:封装、继承、多态