继承和多态

# 继承和多态

C++三大特性:封装,继承,多态

封装:通过访问权限的控制(private,protect,public) 将成员变量变得不可见,

继承:一个类继承另外一个类的成员变量和成员函数

被继承的类:父类(基类)

继承的类:子类(派生类)

继承方式:

1、public:公有继承 public->public protected->protected private->private

1
class Student:public Person    //其中public代表继承权限

(1)父类的私有成员可以被继承,但是不可见

(2)子类可以调用父类的变量进行初始化,但这个函数只能在初始化列表中调用

1
2
3
using CStrRef = const std::string &;
public:
Student(CStrRef name, int age, CStrRef ID,int score):Person(name,age,ID),score(score){}

(3)父类先构造,子类先析构

(4)继承中的名词遮蔽:父类与子类定义同名函数,子类对象只能调用的是子类函数,如果非要使用,要用域解析符

1
s.Person::show();

2、proteced:保护继承 public->protected protected->protected private->private

1
class Student:protected Person

3、private:私有继承 public->private protected->private private->private

1
class Student:private Person

一般都用public,private几乎不使用

开闭原则:对修改关闭,对扩展开发

4、多继承

1
class Derived:public Base1,public Base2     //以 , 隔开继承两个父类

父类构造顺序和构造函数调用顺序无关,和继承顺序有关

二义性问题:当两个父类都定义一个同名变量时,子类调用会出现定义不明确的问题

解决菱形继承的二义性问题:虚继承 virtual

1
class Base1:virtual public ULtraBase

虚继承指针->虚继承表(虚继承的类的变量相对于本类的偏移量)

5、组合

将要使用的类的对象,塞入当前类中

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
class Base1
{
public:
Base1(int a):a(a){}
void show()
{
std::cout << "a:" << a;
}
protected:
int a;
};
class Base2
{
public:
Base2(int b):b(b){}
void show()
{
std::cout << "b:" << b;
}
protected:
int b;
};
class Derived
{
public:
Derived(int a,int b):b1(a),b2(b){}
void show()
{
b1.show();
b2.show();
}
private:
Base1 b1; //在需要使用其他类的类中创建需要使用的类的对象
Base2 b2;
};

多态:

相同的代码,不同的表现形式

目的:提高代码的可扩用性

静多态:在编译期进程函数的地址绑定(早绑定) C多态:函数指针(回调) C++多态:函数重载

动多态:在运行期才真正确定函数的地址(迟绑定) C++:虚函数virtual

虚函数指针:虚函数表

1
virtual void show()

重写:子类函数和基类函数完全相同(返回值,函数名,参数列表)

只有重写的情况下,才能构成多态

重载,重写,名词遮蔽的概念一定要分清。

构成多态的要素:

1、基类指针指向子类对象

2、基类要有虚函数

3、子类重写父类虚函数

纯虚函数:

定函数的格式

抽象类:具有纯虚函数的类,不允许实例化对象

1
2
3
4
5
6
//具有纯虚函数的类:抽象类:不允许实例化对象
class Sort
{
public:
virtual void sort(int *a, int size) = 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
class Base
{
public:
virtual void show()
{
std::cout << "Base" << std::endl;
}
protected:
int a;
};
class Derived1:public Base
{

public:
void show()
{
std::cout << "Derived1" << std::endl;
}
};
class Derived2:public Base
{

public:
void show()
{
std::cout << "Derived2" << std::endl;
}
};
int main()
{
//向上转型
// Derived *d=new Derived;//基类指针,指向子类对象
// Base *b = d;
// b->show();
// d->show();
Base *a[2];
a[0] = new Derived1;
a[1] = new Derived2;
//多态
for (int i = 0; i < 2;i++)
{
a[i]->show();

}
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
class Base
{
public:
Base()
{
std::cout << "父类构造\n";
a = new int;

}
virtual ~Base() //虚析构函数
{
std::cout << "父类析构\n";
delete a;
}

private:
int *a;
};
class Derived:public Base
{
public:
Derived():Base()
{
std::cout << "子类构造\n";
b = new int;
}
~Derived()
{
std::cout << "子类析构\n";
delete b;
}

private:
int *b;
};
int main()
{
Base *d = new Derived;
delete d;
return 0;
}

类的内存空间分配

不同的对象应该具有不同的内存地址,空类占一个字节。

静态变量不占类型,函数不占,虚函数会有一个虚函数表占8个字节

基类和子类之间不能拼接空间

创建虚函数指针之后,编译器会自己进行优化内存,会使基类和子类的空间以及可以拼接的空间进行拼接

虚函数表是一个类一张表,虚函数指针指向表吗,表里可以储存多个虚函数

g++编译器内存存储顺序:1、有虚继承、虚函数:虚继承指针->子类变量->虚函数指针->基类变量

? 2、有虚函数,无虚继承:虚函数指针->基类变量->子类变量