C++的继承和多态
继承
基本语法
继承是面向对象的三大特征之一
有些类与类之间存在特殊的关系,例如界门纲目科属种
他们都是下级有上级的共性,还有自己的特性
而通过继承可以减少重复代码
基本语法
1
| class 子类(派生类) : 继承方式 父类(基类)
|
子类的成员中包含两大部分
一类是从父类继承过来的,一类是自己增加的成员
从基类继承过来的表现其共性,而新增的成员体现了其个性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include <iostream>
class BasePage { public: void head() { cot<<1<<2<<3<<endl; } void foot() { cout<<1<<2<<3<<endl; } void left() { cout<<etc<<endl; } };
class C# : public BasePage { public: void contnt() { cout<<C#学习<<endl; } }
class Python : public BasePage { public: void contnt() { cout<<Python学习<<endl; } } void test01() { cout<<"C#页面"<<endl; C# c1; c1.head(); c1.foot(); c1.left(); c1.content(); cout<<"Python页面"<<endl; Python py; py.head(); py.foot(); py.left(); py.content(); } using namespace std; int main() { return 0; }
|
继承方式
继承方式一共有三种
- 公共继承:父类中的公共权限在子类中依然是公共权限,保护权限依然是保护权限,但父类的私有权限不可访问。
- 保护继承:父类中的公共权限在子类中改变为保护权限,保护权限依然是保护权限,但父类的私有权限不可访问。
- 私有继承:父类中的公共权限在子类中改变为私有权限,保护权限改变为私有权限,但父类的私有权限不可访问。
继承中的对象模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <iostream> using namespace std; class Base { public: int m_a; protected: int m_b; private: int m_c; }; class son :public Base { public: int m_d; } void test01() { cout<<"size of son ="<<sizeof(son); } int mian() { return 0; }
|
小技巧
- 利用开发人员命令提示工具查看查看对象模型
- 跳转盘符
D;
- 跳转文件路径
cd 具体路径
- 查看文件命名
cd /dl reportSingleClassLayout类名 文件名
继承中的构造和析构的顺序
子类继承父类后,当创建子类对象时,也会调用父类对象
Question:父类和子类的构造和析构顺序是谁先谁后呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <iostream> using namespace std; class Base { public: Base() { cout<<"Base的构造函数"<<endl; } ~Base() { cout<<"Base的析构函数"<<endl; } }; class Son :public Base { public: Son() { cout<<"Son的构造函数"<<endl; } ~Son() { cout<<"Son的析构函数"<<endl; } } void test01() { Son s; } int mian() { test01(); return 0; }
|
结论
继承中构造和析构的顺序如下
先构造父类再构造子类,先析构子类再析构父亲
继承同名成员处理方式
- 访问子类同名成员 直接访问(也就是说程序默认访问的是子类的同名成员)
- 访问父类同名成员 加作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #include <iostream> using namespace std; class Base { public: Base() { m_a=100; } void func() { cout<<"Base=func"<<endl; } void func(int a) { cout<<"Base=func(int a)"<<endl; } int m_a; }; class Son :public Base { public: Son() { m_a=200; } void func() { cout<<"Son=func"<<endl; } int m_a; }
void test01() { Son s; cout<<"m_a="<<s.m_a<<endl; cout<<"m_a="<<s.Base::m_a<<endl; }
void test02() { Son s; s.func(); s,Base::func(); s.Base::func(100); } int mian() { test01(); test02(); return 0; }
|
值得注意的是
- 如果子类中出现和父类同名的成员函数,子类的同名函数会隐藏掉父类中所有的同名函数(有参,无参)
- 而如果想要访问到父类中被隐藏的同名成员函数,需要加作用域
继承同名静态成员的处理方式
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <iostream> using namespace std; class Base { public: static int m_a; static void func() { cout<<"Base=func"<<endl; } void func(int a) { cout<<"Base=func(int a)"<<endl; } int m_a; }; int Base::m_a= 100; class Son :public Base { public: static int m_a; static void func() { cout<<"Base=func"<<endl; } void func(int a) } int Son::m_a= 200;
void test01() { Son s; cout<<"m_a="<<s.m_a<<endl; cout<<"m_a="<<s.Base::m_a<<endl; cout<<"m_a="<<Son::m_a<<endl; cout<<"m_a="<<Base::m_a<<endl; cout<<"m_a="<<Son::Base::m_a<<endl; }
void test02() { Son s; s.func(); s,Base::func(); Son::func(); Son::Base::func(); } int mian() { test01(); test02(); return 0; }
|
特点与上节一致,会进行隐藏父类的同名静态成员函数。
同名静态成员和同名非静态成员的处理方式一样,只不过有两种访问的方式(通过对象和通过类名)
多继承和语法
基本语法
class 子类 :继承方式 父类1,继承方式父类2....
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议使用多继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <iostream> using namespace std; class Base1 { public: Base1() { m_a =100; } int m_a; }; class Base2 { public: Base2() { m_b =100; } int m_b; }; class Son :public Base1, public Base2 { public: Son() { m_c=100; m_d=100; } }
void test01() { Son s; cout<<"sizeof Son = "<<sizeof(s)<<endl; cout<<"Base1::m_a= "<<s.Base1::m_a<<endl; cout<<"Base2::m_a= "<<s.Base2::m_a<<endl; }
void test02() { Son s; s.func(); s,Base::func(); Son::func(); Son::Base::func(); } int mian() { test01(); test02(); return 0; }
|
菱形继承
概念:
两个子类继承与同一个父类,
又有某个类同时继承着两个子类
这种继承被称为菱形继承,或者钻石继承
但是这种继承通常会产生二义性的问题
因此为了解决
我们通常会利用虚继承来解决上述问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream> using namespace std; class Animal { pubilc: int m_age; }
class Sheep : virtual public Animal { }; class Tuo virtual public Animal { };
class SheepTuo:public Sheep,public Tuo; void test01() { SheepTuo st; st.Sheep::m_age=18; st.TUo::m_age =23; cout<<st.Sheep::m_age<<endl; cout<<st.Tuo::m_age<<endl; cout<<st.m_age<<endl; } int main() { return 0; }
|
多态
基本概念
多态分为两类
- 静态多态:函数重载 和 运算符重载 属于静态多态,服用函数名
- 动态多态:子类和虚函数实现运行时多态
静态多态和动态多态的区别:
- 静态多态的函数地址早绑定 - 编译阶段
- 动态多态的函数地址晚绑定 - 运行阶段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <iostream> using namespace std; class Animal { pubilc: void speak() { cout<<"动物在说话"<<endl; } } class Sheep : public Animal { public: cout<<"羊在叫"<<endl; };
void doSpeak(Animal &animal) { animal.spaek(); } void test01() { Sheep sheep; dospeak(sheep); } int main() { test01; return 0; }
|
结果是
动物在说话,原因是地址早绑定,在编译阶段就确定了函数地址。
解决方法: 加上虚函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream> using namespace std; class Animal { pubilc: virtual void speak() { cout<<"动物在说话"<<endl; } } class Sheep : public Animal { public: cout<<"羊在叫"<<endl; };
void doSpeak(Animal &animal) { animal.spaek(); } void test01() { Sheep sheep; dospeak(sheep); } int main() { test01; return 0; }
|
答案就变成了 羊在叫。
满足条件:
1.有继承关系
2.子类重写父类的虚函数
使用方法:
通过对父类的引用 来执行子类对象
重写:
函数的返回值类型 函数名 参数列表 完全一致称为重写