C++多态详解

C++多态详解

文章目录

  • C++多态详解
    • 1.什么是多态?(What)
      • 1.1概念
      • 1.2 多态的条件:
      • 1.3 什么是虚函数?
    • 2.怎么实现多态?(How)
    • 3.重写(覆盖)/重载/隐藏(重定义)区别
      • 3.1区别
      • 3.2协变
    • 4.多态具体在哪里? (Where)
    • 5.细谈虚表
    • 6.补充
      • 6.1 抽象类
      • 6.2 绑定
      • 6.3 构造析构函数与虚函数的关系
      • 6.4 访问权限与顺序问题

1.什么是多态?(What)

1.1概念

​ 多态(Polymorphism):通常是指同一个行为面对不同对象,呈现出不同的状态和结果。

举个例子: 乘坐公交买票时,成人买票是全价,学生买票是半价,而老人买票是免费的

而对应到计算机里面:多态则由不同继承关系的类对象,去调用同一函数,而产生不同行为,那么具体怎么触发多态机制呢?

1.2 多态的条件:

​ 1.构成虚函数的重写(同函数名-同参数-同返回值(协变除外) )

​ 2.有父类的指针*或者引用& 的调用

C++多态详解_第1张图片

1.3 什么是虚函数?

​ ——虚函数就是被virtual关键字修饰的函数。

C++多态详解_第2张图片

​ ——虚函数就是为重写而生 ---------> 重写就是为了多态而生


2.怎么实现多态?(How)

​ ——通过重写虚函数

class Man{					//父类
	public:	virtual void Func()	//同名
};
class Kid :public Man{		//子类继承
	public: virtual void Func()	//同名
};

——如上面形式称:子类虚函数重写了父类虚函数。

注意: 当父类虚函数加上了virtual,子类在重写相同虚函数的时候virtual也可以不加,同样构成多态。


3.重写(覆盖)/重载/隐藏(重定义)区别

3.1区别

在了解虚函数的重写后我们发现,无论是重写还是隐藏还是重载,都是对相同函数名函数的一种重定义,那么这三者区别又在哪里呢?

C++多态详解_第3张图片

3.2协变

上面提到了协变:是一种特殊的重写,虽然不满足重写:返回值相同的定义,但依然是满足重写规则的,可以理解为重写的特例。

class Man{
		public:
		virtual Man* f() {return new A;}	//返回值是Man*类型
};
class Kid: public Man{
	public:
		virtual Kid* f() {return new B;} 	//返回值不同,是Kid* 类型
};

4.多态具体在哪里? (Where)

​ 多态通过虚函数实现,那么虚函数在哪里呢?虚函数的指针放在虚函数表里!(以下简称虚表),虚表中存放着虚函数的指针。两者在物理存储上都本质放在代码段(常量区)中!

回顾一道经典面试题:问sizeof(Base)?

class Base{
	virtual void func1();
	int _b=1;
	char a =1;
};		

最终答案为sizeof(Base) = 12; 那是因为除了a和b之外的内存对齐情况外,还得算上创建虚函数的虚函数表大小。而虚表本质是一个存虚函数指针的指针数组!

在这里插入图片描述

图中**_vfptr**就是虚表指针

5.细谈虚表

我们接下来写两个简单的继承类做个实验

class Base {
public:
	virtual void func1() { cout << "Base::func1()" << endl; }
	virtual void func2() { cout << "Base::func2()" << endl; }
	void func3() { cout << "Base::func3()" << endl; }	//普通函数以示区分

};

class Derive:public Base {
public:
	virtual void func1() { cout << "Derive::func1()" << endl; }
	
};

int main(){	//创建两个对象
	Base b;
	Derive d;
	return 0;
}

调试结果如下:

C++多态详解_第4张图片

通过调试我们可以发现:

1.派生类对象d也有一个虚表指针,一部分是继承下来的成员,一部分是自己的成员

2.对象b和对象d的虚表是不一样的,在这里我们发现func1在对象d中完成了重写所以d中虚表是derive::func1,所以虚函数重写也叫覆盖,重写是语法层面的叫法,覆盖是底层的叫法。

3.虚函数本质是一个存放着虚函数指针的指针数组,一般会在结尾放一个nullptr。

4.对象中存的都是虚表指针! 不是虚表本身,虚表和虚函数都是放在代码段(常量区)上的。

5.虚表什么时候生成? ——编译阶段

6.对象中的虚表指针在哪里初始化? ——构造函数的初始化列表


6.补充

6.1 抽象类

​ 什么是抽象类? **包含纯虚函数的类就叫抽象类。**纯虚函数是将虚函数初始化为0,即加上” =0“的虚函数

virtual void Derive() = 0; //纯虚函数

抽象类不能实例化出对象,抽象类的子类也不能实例化,子类要实例化必须重写虚函数。

6.2 绑定

静态绑定(前期绑定):又称静态多态指在程序编译期间确定程序行为的过程。

动态绑定(后期绑定):又称动态多态指在程序运行期间,根据拿到的类型确定行为

6.3 构造析构函数与虚函数的关系

一般构造函数不能作虚函数,为什么?

​ ——比如若父类A作构造函数,在通过父类指针调用子类函数A* ptr = new B()时,会调用B类构造,但是B并未实例化,所以编译器会报错。

一般析构函数常作虚函数,为什么?

​ ——析构函数作虚函数的好处:会在调用父类A析构的同时,会顺便也析构子类B,这样一次性都能释放是我们所期望的。

6.4 访问权限与顺序问题

​ 1.父类指针可以指向父类或者子类的对象。

Person* ptr1 = new Person;	//父类指针指向父类对象
Person* ptr2 = new Student;	//父类指针指向子类对象

2.子类对象可以传给父类对象,但父类对象不能传给子类对象。因为儿子是继承父亲的,父亲有的儿子一定有,而儿子有的父亲不一定有。

C++多态详解_第5张图片

3.父类指针访问子类虚函数时候是调用的子类方法!

Person* ptr2 = new Student;	//先调用的是子类Student的构造函数

你可能感兴趣的:(C++Learning,c++,开发语言)