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;
}
};
//C#页面
class C# : public BasePage
{
public:
void contnt()
{
cout<<C#学习<<endl;
}
}
//python页面
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;
}

小技巧

  1. 利用开发人员命令提示工具查看查看对象模型
  2. 跳转盘符D;
  3. 跳转文件路径cd 具体路径
  4. 查看文件命名
  5. 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);//不能写成 s.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()
{
//1.通过对象访问数据
Son s;
cout<<"m_a="<<s.m_a<<endl;//默认访问子类
cout<<"m_a="<<s.Base::m_a<<endl;//添加作用域后访问父类成员
//2.通过类名访问数据
cout<<"m_a="<<Son::m_a<<endl;
cout<<"m_a="<<Base::m_a<<endl;
//第一个::通过类名的方法访问
//第二个::代表访问父类的作用域
cout<<"m_a="<<Son::Base::m_a<<endl;
}
//同名成员函数处理
void test02()
{
//1.通过对象访问数据
Son s;
s.func();//默认子类
s,Base::func();
//2.通过类名访问数据
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;//16
cout<<"Base1::m_a= "<<s.Base1::m_a<<endl;
cout<<"Base2::m_a= "<<s.Base2::m_a<<endl;
}
//同名成员函数处理
void test02()
{
//1.通过对象访问数据
Son s;
s.func();//默认子类
s,Base::func();
//2.通过类名访问数据
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;
}
//加上 virtual 基类变成虚基类 继承变成虚继承
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; //28
cout<<st.Tuo::m_age<<endl; //28
cout<<st.m_age<<endl; //28
}
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;
};
//执行说话的函数
//地址早绑定,在编译阶段就确定了函数地址
//因此指向了animal里的函数
void doSpeak(Animal &animal)//Animal &animal = sheep
{
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;
};
//执行说话的函数
//地址早绑定,在编译阶段就确定了函数地址
//因此指向了animal里的函数
void doSpeak(Animal &animal)//Animal &animal = sheep
{
animal.spaek();
}
void test01()
{
Sheep sheep;
dospeak(sheep);
}
int main()
{
test01;
return 0;
}

答案就变成了 羊在叫。

满足条件:

1.有继承关系

2.子类重写父类的虚函数

使用方法:

通过对父类的引用 来执行子类对象

重写:

函数的返回值类型 函数名 参数列表 完全一致称为重写