IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C++】多态 -> 正文阅读

[C++知识库]【C++】多态

一、多态定义

1. 构成条件

多态:多种形式

静态的多态:函数重载,原理是编译时实现

动态的多态:

  • 必须通过基类的指针或者引用调用虚函数
  • 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(覆盖)

重写(覆盖)的含义: 当子类和父类(或者只有父类但建议都有、这里可以理解为,子类继承了父类的虚函数属性,再进行重写)中虚函数(有virtual关键字),返回值,函数名,参数一模一样。


#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;

class A
{
public:
	virtual void AB()
	{
		cout << 'A' << endl;
	}

};

class B : public A
{
public:
	virtual void AB()
	{
		cout << 'B' << endl;
	}
};

void test(A* a)
{
	a->AB();
}
int main()
{
	A a;
	B b;
	test(&a);
	test(&b);
	
	return 0;
}


image-20220422162713074


如果只是传类,不会构成多态,必须是指针和引用

构成多态,跟p的类型没有关系,传的哪个类型的对象,调用的就是这个类型的虚函数 – 跟对象有关

b类型:image-20220423150846552

a类型:(因为父类不能给子类会报错)

ab型:

image-20220423150932227


  • 不构成多态,调用就是A类型的函数 – 跟类型有关

image-20220422170104305


2. 虚函数

只能是类的非静态成员函数 + virtual关键字才是虚函数

class A
{
    virtual void test()
    {}
}

3. 虚函数重写的两个例外

协变

协变(基类与派生类虚函数返回值类型不同) :派生类重写基类虚函数时,与基类虚函数返回值类型不同。

即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

class A
{
public:
	virtual A* AB()
	{
		cout << 'A' << endl;
        return nullptr;
	}

};

class B : public A
{
public:
	virtual B* AB()
	{
		cout << 'B' << endl;
        return nullptr;
	}
};

析构函数的重写

在继承那,子类的析构函数析构时候,会最后调用父类析构函数

且编译会将两个析构函数都变成destructor()调用

在继承中它们构成隐藏,先析构子类再析构父类

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;

class A
{
public:
	virtual ~A()
	{
		cout << "~A" << endl;
	}

};

class B : public A
{
public:
	virtual ~B()
	{
		cout << "~B" << endl;
	}
};

int main()
{
	A a;
	B b;

	return 0;
}

image-20220422213901783


如果有这样一个场景的话

复习:

new = operator new + 构造函数

delete = 析构函数 + operator delete

int main()
{
	A* a = new A;
	A* b = new B;

	delete a;
	delete b;

	return 0;
}

image-20220422214207267

当delete b 的时候,如果不构成多态,只会调用父类析构函数,但new B类型确实存在,虽然A* b发生截断了,但这样不释放空间会导致内存泄漏

加上virtual 之后

image-20220422214341621


总结在创建动态申请子类对象的时候,需要重写


二、什么情况下类不能被继承

  • 当父类的构造函数是私有的时候,当子类定义时候,调用不了构造函数

【C++98解决方式:private】

class A
{
	A(int a)
		:_a(a)
	{}

	int _a;
};

class B : public A
{
};


int main()
{
	B b;
	return 0;
}

因为是private被继承不可见

image-20220423142603708


不仅仅子类,父类也是没法对象实例化,同样没法调用构造函数

class A
{
	A(int a)
		:_a(a)
	{}
public:
	static A Create(int a)
	{
		return A(a);
	}

	int _a;
};

class B : public A
{
};


int main()
{
	A a = A::Create(10);
	return 0;
}

这里是static是保证可以从类域里面使用(因为还没实例化的时候Create函数不能使用)


【C++11解决方式:final】

class A final
{
public:
	A(int a)
		:_a(a)
	{}
	int _a;
};

class B : public A
{
};

int main()
{
	B b;
	return 0;
}

image-20220423145302778


final还可以解决重写,防止被重写

class A 
{
public:
	virtual void AB() final
	{
		cout << "a" << endl;
	}

};

class B : public A
{
public : 
	virtual void AB()
	{
		cout << "b" << endl;
	}

};

int main()
{
	A a;
	B b;

	A* aa = &a;
	B* bb = &b;

	aa->AB();
	bb->AB();
	
	return 0;
}

image-20220423151031224


三、override检查重写

放在子类重写的虚函数后面,检查是否完成,没有重写报错

class A
{
public:
	void AB()
	{
		cout << "a" << endl;
	}

};

class B : public A
{
public:
	virtual void AB() override
	{
		cout << "b" << endl;
	}

};

image-20220423151434821


总结:

image-20220423152542460


四、抽象类

包含纯虚函数的类叫做抽象类(也叫接口类)

class A
{
public:
	virtual void AB() = 0;
	//virtual void AB() = 0
	//{
	//	cout << "a" << endl;
	//}
};

纯虚函数只声明不实现(但也可以写内容不过没用),因为包含纯虚函数的类不能实例化出对象


class A
{
public:
	//virtual void AB() = 0;
	virtual void AB() = 0
	{
		cout << "a" << endl;
	}
};

int main()
{
	A* a = nullptr;
	a->AB();
	return 0;
}

image-20220423163259792

崩了


派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承
一个类型,如果一般在现实世界中,没有具体的对应实物就定义成抽象类比较好

纯虚函数的类,本质上强制的子类去完成虚函数重写。
override只是在语法上检查是否完成重写。

五、多态的原理

1. 没有继承的虚函数表

  • 没有继承,单独类里面有虚函数,有虚函数表
class A
{
	virtual void f()
	{

	}
	int a;
	char i;
};

int main()
{
	A a;
	return 0;
}

image-20220423170047748

可见虚函数这里是多了个指针,而且这里虚函数表指针是放在前面

如果有两个虚函数?

image-20220423170400022

这里虚函数表就是函数指针数组

2. 单继承中虚函数表

总结:虚函数表放,重写的虚函数和自己虚函数的地址(这里的地址也不是真实的地址,而是封装过的地址(jump指令的地址,jump之后才能到函数的第一句))

虚表存储在常量区并且没有构成重写的虚函数也在虚表中

image-20220424194441023


这里代码运行成功的原因是,类成员函数存在代码区,p->ab只是把指针传给this不会出错

class A
{
public:
	void AB()
	{
		cout << "a" << endl;
	}
};

int main()
{
	A* a = nullptr;
	a->AB();
	return 0;
}

如果把virtual那个函数拿出来,会错误:错误的原因是把指针给了this,this会去找虚函数表的指针,所以空指针会出错(nullptr无法访问数组元素)

image-20220423201319384


3. 多态当中的虚函数表

  • 多态例子,多态时候,子类只有重写和继承的函数地址在监控里可以看到在虚表当中,子类其他虚函数不显示,但可以用虚表地址,访问虚表,看到虚表里面其实是有存储其他虚函数的地址
class A
{
public:
	virtual void f()
	{
		cout << "A" << endl;
	}
};

class B : public A
{
public:
	virtual void f()
	{
		cout << "B" << endl;
	}

};

void test(A& p)
{
	p.f();
}

int main()
{
	A a;
	B b;
	test(a);
	test(b);
	return 0;
}

image-20220423221546213

二者虚函数表不一样


4. 不能直接给类切片的原因

int main()
{
	A a;
	B b;
	A aa = b;

	return 0;
}

image-20220423222158440

二者地址虚表地址不同


5. 同类型的对象,虚函数表一样

就像类的函数只存一份一样

都存在公共代码区

int main()
{
	A a;
	A aa;

	return 0;
}

image-20220423222707439


6. private不限制虚函数表

尽管有private修饰子类的多态,但仍然能够调用到函数

image-20220423225511700

所以访问限定符对虚函数表没有影响,所以只要有虚表的指针,就能强制访问


7. 多继承中的虚函数

此时会有两个虚函数表,一个a表一个b表

class A
{
public:
	virtual void f()
	{
		cout << "A" << endl;
	}
	virtual void fa()
	{
		cout << "A" << endl;
	}

};

class B 
{
public:
	virtual void f()
	{
		cout << "B" << endl;
	}

	virtual void fb()
	{
		cout << "B" << endl;
	}

};

class C : public A, public B
{

};

int main()
{
	C c;
	A& a = c;
	B& b = c;
	return 0;
}

image-20220424211159362

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-26 11:24:29  更:2022-04-26 11:27:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 23:36:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码