3.6继承
3.6.1继承的基本语法
class A:pubilc B; //A称为 子类 或 派生类 //B称为 父类 或 基类
3.6.2继承方式
无论是那种继承方式,都不能访问父类中访问权限为private的成员,公共权限和保护权限均可访问;继承后,子类:
- 公共继承:访问权限保持不变
- 保护继承:访问权限均变为 pretected
- 私有继承:访问权限均变为 private
3.6.3继承中的对象模型
继承过程中所有成员都会继承下去,但是私有成员会被编译器隐藏
class Base { int m_A; int m_B; int m_C; }; class Son :public Base { int m_D; }; int main() { Son s1; cout <<"sizeof(s1)=" << sizeof(s1) << endl; }
class默认的成员属性为private,根据sizeof 的计算结果也能推测出子类会继承父类的所有成员,但是private权限的成员会被编译器隐藏
3.6.4继承中构造和析构的顺序
在子类继承父类后,当创建子类对象时,会先创建一个父类的对象,也会调用父类的构造函数,销毁时也会调用父类的析构函数
构造:先构造父类,再构造子类
析构:先析构子类,再析构父类
class Base { public: Base() { cout << "Base的构造函数" << endl; } ~Base() { cout << "Son的析构函数" << endl; } }; class Son :public Base { public: Son() { cout << "Son的构造函数" << endl; } ~Son() { cout << "Son的析构函数" << endl; } }; int main() { Son s1; return 0; }
3.6.5继承中同名成员的处理
当子类成员和父类成员出现同名的情况时,直接调用 调用的是子类的成员,如果要调用父类的成员,需要加作用域;
如果成员函数同名,则子类的同名成员会隐藏掉父类中的所有同名成员函数,即使成员函数的参数不同,直接调用都只会调用子类的成员函数
class Base { public: int m_A; }; class Son :public Base { public: int m_A; Son(int a,int b) { m_A = a; Base::m_A = b; } }; int main() { Son s1(13, 14); cout << s1.m_A << s1.Base::m_A << endl; s1.m_A = 5; s1.Base::m_A = 20; cout << s1.m_A << s1.Base::m_A << endl; }
拓展:继承中同名静态成员的处理方式
静态成员和非静态成员出现同名,处理方式一致
- 通过对象访问
访问子类同名成员直接访问即可
访问父类同名成员需要加作用域
- 通过类名访问
class Base { public: static int m_A; }; int Base::m_A = 0; class Son : public Base { public: static int m_A; }; int Son::m_A = 1; int main() { Son s1; cout << "1.通过对象访问" << endl; cout << "Base下的 m_A=" << s1.Base::m_A << endl; cout << "Son下的 m_A=" << s1.m_A << endl; cout << "2.通过类名访问" << endl; cout << "Base下的 m_A=" << Base::m_A << endl; cout << "Son下的 m_A=" << Son::m_A << endl; //第一个::代表以类名形式访问,第二个::代表访问父类作用域下 cout << "Base下的 m_A=" << Son::Base::m_A << endl; }
3.6.6多继承语法
C++允许—个类继承多个类
语法:class 子类︰继承方式 父类1,继承方式 父类2..
多继承可能会引发父类中有同名成员出现,需要加作用域区分,实际开发不建议使用多继承
3.6.7 菱形继承
概念:两个派生类继承同一个基类,又有某个类(简称孙类)同时继承者两个派生类,这种继承被称为菱形继承,或者钻石继承
这个孙类继承了父类的数据两份,但这份数据只需要一份就行;
解决这个问题可以利用 虚继承 解决:在继承之前加上 关键字 virtual 变为 虚继承,
class Animal { public: int m_Age; }; class Sheep:virtual public Animal{}; class Tuo:virtual public Animal{}; //Animal类称为 虚基类 class SheepTuo :public Sheep, public Tuo{}; //当Sheep和Tuo虚继承后,SheepTuo继承的并不是两份数据而是两个指针 //这两个指针通过偏移量找到唯一的数据 int main() { //可以通过在作用域的方式解决SheepTuo中m_Age的二义性; SheepTuo s1; s1.Sheep::m_Age = 18; s1.Tuo::m_Age = 28; cout << "s1.Sheep::m_Age=" << s1.Sheep::m_Age << endl; cout << "s1.Tuo::m_Age=" << s1.Tuo::m_Age << endl; cout << "s1.m_Age=" << s1.m_Age << endl; }
3.7多态
3.7.1多态的基本语法
基本概念:多态是C++面向对象三大特性之一
分类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
区别:
- 静态多态的函数地址早绑定–编译阶段确定函数地址
- 动态多态的函数地址晚绑定–运行阶段确定函数地址
class Animal { public: //函数前面加上virtual关键字,变成虚函数 //编译器在编译时就不能确定函数调用了 virtual void speak()//虚函数 { cout << "动物在说话" << endl; } }; class Cat :public Animal { public: void speak() { cout << "小猫在说话" << endl; } }; class Dog :public Animal { public: void speak() { cout << "小狗在说话" << endl; } }; //执行说话的函数 void doSpeak(Animal& animal) { animal.speak();//地址早绑定,在编译阶段确定函数地址 //如果要让输出根据输入的不同而变化,需要地址晚绑定 //动态多态满足条件 //1.有继承关系 2.子类重写父类的虚函数 //重写:函数返回类型 函数名 参数列表 完全相同 } int main() { Cat cat; doSpeak(cat);//C++中允许父类子类之间的类型转换 //Animal &animal=cat; Dog dog; doSpeak (dog); }
总结:
多态满足条件:
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件:
- 父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
3.7.2纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0 ;
当类中有了纯虚函数,这个类也称为抽象类,其特点为:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
3.7.3虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象:无法通过类名直接创建对象,但可创建该类的指针代码事例:
class Animal { public: Animal() { cout << "Animal 构造函数调用" << endl; } ~Animal() { cout << "Animal 析构函数调用" << endl; } virtual void speak() = 0;//纯虚函数 }; class Cat :public Animal { public: string* m_Name; Cat(string name) { cout << "Cat 构造函数调用" << endl; m_Name = new string (name); } ~Cat() { if (m_Name != NULL) { cout << "Cat 析构函数调用" << endl; delete m_Name; m_Name = NULL; } } void speak() { cout << *m_Name << "小猫在说话" << endl; } }; int main() { Animal* animal = new Cat("Tom"); animal->speak(); //父类指针在析构时 不会调用 子类中的析构函数,导致子类中如果有堆区数据,会发生内存泄漏 delete animal; }
父类指针在析构时 不会调用 子类中的析构函数,导致子类中如果有堆区数据,会发生内存泄漏
解决办法是在 父类的析构函数前加上 virtual,称为虚析构
virtual ~Animal() { cout << "Animal 析构函数调用" << endl; }
纯虚析构:类内声明,类外实现。
class Animal { public: virtual ~Animal() = 0; }; Animal::~Animal() { cout << "Animal 析构函数调用" << endl; }
总结:
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类