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++ 多态

C++ 多态

静态联编动态联编

  1. 静态多态在编译阶段绑定地址,地址早绑定,静态联编。
  2. 动态多次在运行阶段绑定地址,地址晚绑定,动态联编。

静态多态

函数重载(函数名相同,函数列表不同),运算符重载

动态多态

  1. 先有继承关系
  2. 父类中有虚函数,子类重写父类中的虚函数
  3. 父类的指针或引用 指向子类的对象
class Animal
{
public:
	//虚函数
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}

	virtual void eat(int a )
	{
		cout << "动物在吃饭" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}

	void eat(int a)
	{
		cout << "小猫在吃饭" << endl;
	}
};

class Dog :public Animal
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};


//动态多态产生条件:
//先有继承关系
//父类中有虚函数,子类重写父类中的虚函数
//父类的指针或引用  指向子类的对象

//对于有父子关系的两个类  指针或者引用 是可以直接转换的。
//父类的指针或引用  指向子类的对象
void doSpeak(Animal & animal) //Animal & animal = cat;
{
	//如果地址早就绑定好了,地址早绑定,属于静态联编
	//如果想调用小猫说话,这个时候函数的地址就不能早就绑定好,而是在运行阶段再去绑定函数地址,属于地址晚绑定,叫动态联编
	animal.speak(); 
}

void test01()
{
	//调用doSpeak()方法,若父类中的doSpeak()方法未指定为虚方法(virtual),
	//则调用时使用的是父类中的方法,会输出:动物在说话。
	
	//若父类中的doSpeak()方法指定为虚方法(virtual),则调用时doSpeak()方法后,会使用子类的doSpeak()方法
	Cat cat;
	doSpeak(cat);

	Dog dog;
	doSpeak(dog);
}

多态原理

class Animal
{
public:
	//虚函数
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};

//此时不占字节,sizeof  Animal = 1
//加上virtual关键字后  sizeof  Animal = 4
cout << "sizeof  Animal = " << sizeof (Animal) << endl;
  1. 当父类写了虚函数后,类内部结构发生改变,多了一个vfptr
  2. vfptr 虚函数表指针 ---- > vftable 虚函数表
  3. 虚函数表内部记录着 虚函数的入口地址
  4. 当父类指针或引用指向子类对象,发生多态,调用是时候从虚函数中找函数入口地址

虚函数 关键字 virtual
利用指针的偏移调用 函数

在这里插入图片描述

Cat未发生重写时
在这里插入图片描述
Cat重写后
在这里插入图片描述

使用指针偏移方式调用虚函数

void test02()
{
	Animal * animal = new Cat;
	//默认调用
	animal->speak();
	// *(int *)animal 解引用到虚函数表中
	// *(int *)(*(int *)animal) 解引用到函数speak地址

	//调用猫说话,函数指针(void(*)()
	((void(*)()) (*(int *)(*(int *)animal))) ();


	//C/C++默认调用惯例  __cdecl
	//用下列调用时候 真实调用惯例  是 __stdcall
	//调用猫吃饭,若虚函数中存在形参,要统一调用惯例,否则程序调用会出问题
	typedef void( __stdcall *FUNPOINT)(int);
	(FUNPOINT (*((int*)*(int*)animal + 1)))(10);
}

调用惯例

  1. 主调函数和被调函数必须要有一致约定,才能正确的调用函数,这个约定我们称为调用惯例
  2. 调用惯例 包含内容: 出栈方、参数传递顺序、函数名称修饰
  3. C/C++下默认调用惯例: cdecl 从右到左 ,主调函数管理出栈
((void(*)()) (*(int *)*(int *)animal)) ();
//指定函数的 调用惯例
typedef void( __stdcall *FUNPOINT)(int);
(FUNPOINT (*((int*)*(int*)animal + 1)))(10);

纯虚函数和抽象类

  1. 语法:virtual int getResult() = 0;
  2. 如果一个类中包含了纯虚函数,那么这个类就无法实例化对象了,这个类通常我们称为 抽象类
  3. 抽象类的子类 必须要重写 父类中的纯虚函数,否则也属于抽象类
//利用多态实现计算器
class AbstractCalculator
{
public:

	//纯虚函数
	//如果一个类中包含了纯虚函数,那么这个类就无法实例化对象了,这个类通常我们称为 抽象类
	//抽象类的子类 必须要重写 父类中的纯虚函数,否则也属于抽象类
	virtual int getResult() = 0;
    
    //纯函数
	virtual int getVirtualResult()
	{
		return 0;
	}

	int m_A;
	int m_B;
};

虚析构和纯虚析构

虚析构语法:

  1. virtual ~Animal(){}
  2. 如果子类中有指向堆区的属性,那么要利用虚析构技术 ,在delete的时候 调用子类的析构函数,添加虚析构后才会调用子类的析构函数

纯虚析构语法:

  1. virtual ~Animal() = 0;
  2. Animal::~Animal(){ .. }
  3. 纯虚析构,需要有声明,也需要有实现(类内声明,类外实现)
  4. 如果一个类中 有了 纯虚析构函数,那么这个类也属于抽象类,无法实例化对象了
class Animal
{
public:
	Animal()
	{
		cout << "Animal的构造函数调用" << endl;
	}
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
	
	//如果子类中有指向堆区的属性,那么要利用虚析构技术 在delete的时候 调用子类的析构函数
	//virtual ~Animal()
	//{
	//	cout << "Animal的析构函数调用" << endl;
	//}

	//纯虚析构 需要有声明 也需要有实现
	//如果一个类中 有了 纯虚析构函数,那么这个类也属于抽象类,无法实例化对象了
	virtual ~Animal() = 0;
};

Animal::~Animal()
{
	cout << "Animal的纯虚析构函数调用" << endl;
}


class Cat :public Animal
{
public:
	Cat(const char* name)
	{
		cout << "Cat的构造函数调用" << endl;
		this->m_Name = new char[strlen(name) + 1];
		strcpy(this->m_Name, name);
	}

	virtual void speak()
	{
		cout << this->m_Name << " 小猫在说话" << endl;
	}

	~Cat()
	{
		if (this->m_Name)
		{
			cout << "Cat的析构函数调用" << endl;
			delete[] this->m_Name;
			this->m_Name = NULL;
		}
	}
	char* m_Name;
};

在这里插入图片描述

向上类型转换和向下类型转换

  1. 父转子 :向下类型转换 ,不安全,会出现地址越界
Animal * animal = new Animal;
Cat * cat  = (Cat *) animal;
  1. 子转父 向上类型转换 ,安全,仅仅是取址范围缩小
Cat * cat = new Cat;
Animal * animal = (Animal *) cat;
  1. 如果发生多态,那么转换永远都是安全的,父类子针或引用指向子类对象
Animal * animal = new Cat;
Cat * cat = (Cat * ) animal;

Animal * animal = new Cat;执行时已经开辟出了Cat所需要的内存,只是当用animal指针指向时取址范围为Animal大小,当强转回Cat时,使用原始地址范围。
在这里插入图片描述

重载、重写、重定义

重载

同一作用域的同名函数

  1. 同一个作用域下
  2. 参数个数,参数顺序,参数类型不同
  3. 和函数返回值,没有关系
  4. const也可以作为重载条件
do(const Teacher& t){}

do(Teacher& t){}

重写(覆盖)

子类重写父类中的虚函数,函数返回值、函数名、形参列表完全一致

  1. 有继承
  2. 子类重写父类的virtual函数
  3. 函数返回值,函数名字,函数参数必须和基类中的虚函数一致

重定义(隐藏)

子类重新定义父类中的同名成员函数,隐藏掉父类中同名成员函数,如果想调用加作用域

  1. 有继承
  2. 子类重新定义父类的同名成员(非virtual函数)

多态案例2 - 电脑组装案例


//纯虚函数
//CPU基类
class CPU
{
public:
	virtual void calculate() = 0;
};
//显卡基类
class VideoCard
{
public:
	virtual void display() = 0;
};
//内存基类
class Memory
{
public:
	virtual void storage() = 0;
};


//电脑类
class computer
{
public:

	computer(CPU * cpu, VideoCard * vc, Memory * mem)
	{
		cout << "电脑构造调用" << endl;
		this->m_Cpu = cpu;
		this->m_Vc = vc;
		this->m_Mem = mem;
	}

	void doWork()
	{
		this->m_Cpu->calculate();
		this->m_Vc->display();
		this->m_Mem->storage();
	}

	~computer()
	{
		cout << "电脑析构调用" << endl;
		if (this->m_Cpu)
		{
			delete this->m_Cpu;
			this->m_Cpu = NULL;
		}
		if (this->m_Vc)
		{
			delete this->m_Vc;
			this->m_Vc = NULL;
		}
		if (this->m_Mem)
		{
			delete this->m_Mem;
			this->m_Mem = NULL;
		}
	}

	CPU * m_Cpu;
	VideoCard * m_Vc;
	Memory * m_Mem;
};


//inter厂商
class intelCPU :public CPU
{
public:
	void calculate()
	{
		cout << "intelCPU开始计算了" << endl;
	}
};

class intelVideoCard :public VideoCard
{
public:
	void display()
	{
		cout << "intel 显卡开始显示了" << endl;
	}

};
class intelMemory :public Memory
{
public:
	void storage()
	{
		cout << "intel 内存条开始存储了" << endl;
	}
};


//Lenovo 厂商
class LenovoCPU :public CPU
{
public:
	void calculate()
	{
		cout << "Lenovo CPU开始计算了" << endl;
	}
};

class LenovoVideoCard :public VideoCard
{
public:
	void display()
	{
		cout << "Lenovo 显卡开始显示了" << endl;
	}

};
class LenovoMemory :public Memory
{
public:
	void storage()
	{
		cout << "Lenovo 内存条开始存储了" << endl;
	}
};


void test01()
{
	cout << "第一台电脑组成:" << endl;

	CPU * intelCpu = new intelCPU;
	VideoCard * lenovoVC = new LenovoVideoCard;
	Memory * lenovoMem = new LenovoMemory;

	computer c1(intelCpu, lenovoVC, lenovoMem);

	c1.doWork();

	cout << "第二台电脑组成:" << endl;

	CPU * intelCpu2 = new LenovoCPU;
	VideoCard * lenovoVC2 = new intelVideoCard;
	Memory * lenovoMem2 = new intelMemory;

	computer c2(intelCpu2, lenovoVC2, lenovoMem2);

	c2.doWork();


}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-06 09:26:01  更:2021-08-06 09:26:13 
 
开发: 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/9 5:04:51-

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