C++从入门到精通 第九章(继承和多态)【下】

这篇具有很好参考价值的文章主要介绍了C++从入门到精通 第九章(继承和多态)【下】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

五、虚函数与多态性

1、多态性的概念

(1)一个面向对象的系统常常要求一组具有相同基本语义的方法能在同一接口下为不同的对象服务,这就是多态性。

(2)在C++中,多态性可分为编译时的多态性(静态多态)和运行时的多态性(动态多态),编译时的多态性是通过函数重载和模板体现的,运行时的多态性是通过虚函数体现的。

(3)静态多态和动态多态的区别:

①静态多态的函数地址早绑定——编译阶段确定函数地址。

②动态多态的函数地址晚绑定——运行阶段确定函数地址。

2、虚函数

(1)在非静态成员函数声明的前面加上virtual修饰符,即把该函数声明为虚函数

(2)在派生类中可以重写从基类继承下来的虚函数,从而提供该函数的适用于派生类的专门版本,如果不重写虚函数,那么继承下来的虚函数仍然保持其在基类中的定义,即派生类和基类使用同一函数版本。(除少数特殊情况外,在派生类中重写虚函数时,函数名、形参和返回值类型必须保持不变)

(3)虚函数在派生类中被重写后,重写的函数仍然是虚函数,可以在其派生类中再次被重写对于虚函数的重写函数,无论是否使用virtual修饰符都是虚函数(建议加上,避免遗忘它是虚函数)。

(4)对虚函数的调用有非多态调用和多态调用两种方式:

①非多态调用是指不借助指针或引用的直接调用,它建立在静态绑定机制基础之上,不具备多态特征)。

多态调用是指借助指向基类的指针或引用的调用,一个基类指针(或引用)可以指向它的派生类对象,而且通过这样的指针(或引用)调用虚函数时,调用的是该指针(或引用)实际所指向的对象所在类的那个重写版本

(5)基类中的实函数也可以在派生类中改写,但改写的函数仍然是实函数,调用实函数时,通过基类指针(或引用)所调用的也只能是基类的函数版本,无法调用到派生类中的改写函数。(换句话说,对实函数的任何形式的调用都是非多态的)

(6)举例:

①例1:

#include<iostream>
using namespace std;

class Animal
{
public:
	int m_Age;
	/*void speak()
	{
		cout << "动物在说话" << endl;
	}*/
	virtual void speak()          //虚函数
	{
		cout << "动物在说话" << endl;
	}
};
class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
class Dog :public Animal
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};

//地址早绑定 在编译阶段确定函数地址
//如果想让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段绑定(地址晚绑定)
void doSpeak(Animal &animal)   //Animal & animal = cat/dog;父类的指针或者引用执行与子类对象
{
	animal.speak();   //动态多态满足条件:有继承关系;子类重写父类的虚函数
	//重写时,函数的返回值类型、函数名、参数列表都要完全相同
}

void test01()
{
	Cat cat;
	doSpeak(cat);    
	Dog dog;
	doSpeak(dog);
}

int main() {

	test01();

	system("pause");

	return 0;
}

②例2:

#include<iostream>
using namespace std;

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};
class Cat :public Animal
{
public:
	/*void speak()
	{
		cout << "小猫在说话" << endl;
	}*/
};
class Dog :public Animal
{
public:
	virtual void speak()
	{
		cout << "小狗在说话" << endl;
	}
};

void doSpeak(Animal &animal)   
{
	animal.speak();   
}

void test01()
{
	Cat cat;
	doSpeak(cat);
	Dog dog;
	doSpeak(dog);
}
void test02()
{
	cout << "sizeof Animal=" << sizeof(Animal) << endl;   //虚函数带指针vfptr,所以对象占4个字节
	cout << "sizeof Animal=" << sizeof(Cat) << endl;
	cout << "sizeof Animal=" << sizeof(Dog) << endl;
}

int main() {

	test01();
	test02();

	system("pause");

	return 0;
}

③例3:

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

class Calculator         //普通写法
{
public:
	int m_Num1;
	int m_Num2;
	int getResult(string oper)
	{
		if (oper == "+")
		{
			return m_Num1 + m_Num2;
		}
		else if (oper == "-")
		{
			return m_Num1 - m_Num2;
		}
		else if (oper == "*")
		{
			return m_Num1 * m_Num2;
		}
	}
};
class AbstractCalculator    //多态技术
{
public:
	int m_Num1;
	int m_Num2;
	virtual int getResult()
	{
		return 0;
	}
};
class AddCalculator :public AbstractCalculator
{
	int getResult()
	{
		return m_Num1 + m_Num2;
	}
};
class SubCalculator :public AbstractCalculator
{
	int getResult()
	{
		return m_Num1 - m_Num2;
	}
};
class MulCalculator :public AbstractCalculator
{
	int getResult()
	{
		return m_Num1 * m_Num2;
	}
};
//开闭原则:对扩展进行开发,对修改进行关闭
void test01()
{
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;
	cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
	cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult("-") << endl;
	cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult("*") << endl;
}
void test02()
{
	AbstractCalculator* abc = new AddCalculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;
	cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
	delete abc;   //用完计算器后记得销毁
	abc = new SubCalculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;
	cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
	delete abc;   //用完计算机后记得销毁
	abc = new MulCalculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;
	cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
}

int main() {

	test01();
	test02();

	system("pause");

	return 0;
}

3、纯虚函数和抽象类

(1)为了将一个虚函数声明为纯虚函数,需要在虚函数原型的语句结束符“;”之前加上“=0”。

(2)拥有纯虚函数的类称为抽象类,抽象类不能用来定义对象如果一个抽象类的派生类没有重写来自其基类的某个纯虚函数,则该函数在派生类中仍然是纯虚函数,这就使得该派生类也称为抽象类,也就是说,一个派生类可以把重写纯虚函数的任务进一步转交给它自己的派生类。

(3)可以在将一个函数声明为纯虚函数的同时为该函数提供实现版本,一个函数是否为纯虚函数,取决于其原型的尾部是否为“=0;”,与实现版本的有无没有关系。纯虚函数的实现版本通常是不完善的版本,但包含了一些共有操作供各个派生类在重写函数中调用。拥有实现版本的纯虚函数仍然有赖于派生类提供重写版本。

(4)派生类在重写一个纯虚函数时可以继续将之声明为纯虚函数。另外,纯虚函数不得声明为内联函数。

(5)举例:

①例1:

#include<iostream>
using namespace std;

class Base  
{
public:
	int m_Num1;
	int m_Num2;
	virtual int func() = 0;    //纯虚函数
};
class Son :public Base
{
public:
	int func()          //子类必须重写父类中的纯虚函数,否则无法实例化对象
	{
		cout << "func()函数调用" << endl;
	}
};

void test01()
{
	//Base b;      对于抽象类,无法实例化对象,即使是在堆区开辟也不行
	Base * base = new Son;
	base->func();
}

int main() {

	test01();

	system("pause");

	return 0;
}

②例2:

#include<iostream>
using namespace std;

class AbstractDrinking
{
public:
	virtual void zhushui() = 0;
	virtual void chongpao() = 0;
	virtual void daorubeizhong() = 0;
	virtual void jiarufuliao() = 0;
	void makeDrink()
	{
		zhushui();
		chongpao();
		daorubeizhong();
		jiarufuliao();
	}
};
class Coffee :public AbstractDrinking
{
public:
	void zhushui()
	{
		cout << "煮水" << endl;
	}
	virtual void chongpao()         //这里virtual 加不加都行
	{
		cout << "冲泡咖啡" << endl;
	}
	void daorubeizhong()
	{
		cout << "倒入杯中" << endl;
	}
	void jiarufuliao()
	{
		cout << "加糖和牛奶" << endl;
	}
};
class Tea :public AbstractDrinking
{
public:
	void zhushui()          
	{
		cout << "煮水" << endl;
	}
	void chongpao()
	{
		cout << "冲泡茶叶" << endl;
	}
	void daorubeizhong()
	{
		cout << "倒入杯中" << endl;
	}
	void jiarufuliao()
	{
		cout << "加柠檬" << endl;
	}
};
void doWork(AbstractDrinking * abs)
{
	abs->makeDrink() ;
	delete abs;  //做完drink后,释放new在堆区开辟出的数据
}

void test01()
{
	doWork(new Coffee);
	cout << endl;
	doWork(new Tea);
}

int main() {

	test01();

	system("pause");

	return 0;
}

4、虚析构函数和纯虚析构函数

(1)析构函数也可通过virtual修饰而声明为虚函数。

(2)只要虚基类的析构函数声明为虚函数,由它派生的所有派生类的析构函数也一定是虚函数。

(3)在使用多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,为了解决这个问题,需要将父类中的析构函数改为虚析构函数或者纯虚析构函数

①虚析构函数或纯虚析构函数可达到通过父类指针释放子类对象的目的。

②如果子类中没有堆区数据,可以不将析构函数写为虚析构函数或纯虚析构函数。

③拥有纯虚析构函数(不是虚析构函数)的类也属于抽象类,无法实例化对象。

(4)虚析构函数和纯虚析构函数的语法:

virtual ~<类名>( )     //虚析构函数的定义(类内)

{

        <虚析构函数体>

}

virtual ~<类名>( ) = 0;  //纯虚析构函数的声明(类内)

<类名>::~<类名>( )    //纯虚析构函数的定义(类外)

{

        <纯虚析构函数体>

}

(5)举例:

①例1:

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

class Animal
{
public:
	virtual void speak() = 0;   //纯虚函数
	Animal()
	{
		cout << "Animal构造函数调用" << endl;
	}
	virtual ~Animal()   //父类指针在析构的时候不会调用子类中的析构函数,子类可能会发生内存泄漏,所以要给它改成虚析构
	{
		cout << "Animal析构函数调用" << endl;
	}
	//virtual ~Animal() = 0;    纯虚析构(需要有声明以及具体实现)
};
/*Animal::~Animal()     
{
    cout << "Animal纯虚析构函数调用" << endl;
}*/
class Cat :public Animal
{
public:
	void speak() 
	{
		cout << *m_Name << "小猫在说话" << endl;
	}
	Cat(string name)
	{
		cout << "Cat构造函数调用" << endl;
		m_Name = new string(name);
	}
	~Cat()
	{
		if (m_Name != NULL)
		{
			cout << "Cat析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}
	string *m_Name;
};

void test01()
{
	Animal * animal = new Cat("Tom");
	animal->speak();
	delete animal;      
}

int main() {

	test01();

	system("pause");

	return 0;
}

②例2:文章来源地址https://www.toymoban.com/news/detail-832076.html

#include<iostream>
using namespace std;

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 * card, Memory * memory)
	{
		m_cpu = cpu;
		m_card = card;
		m_memory = memory;
	}
	void work()
	{
		m_cpu->calculate();
		m_card->display();
		m_memory->storage();
	}
	~Computer()         //提供析构函数释放3个电脑零件
	{
		if (m_cpu != NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}
		if (m_card != NULL)
		{
			delete m_card;
			m_card = NULL;
		}
		if (m_memory != NULL)
		{
			delete m_memory;
			m_memory = NULL;
		}
	}
private:
	CPU * m_cpu;
	VideoCard * m_card;
	Memory * m_memory;
};
class IntelCPU :public CPU
{
public:
	void calculate()
	{
		cout << "Inter的CPU开始计算了" << endl;
	}
};
class IntelVideoCard :public VideoCard
{
public:
	void display()
	{
		cout << "Inter的显卡开始显示了" << endl;
	}
};
class IntelMemory :public Memory
{
public:
	void storage()
	{
		cout << "Inter的内存条开始存储了" << endl;
	}
};
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()
{
	//第一台电脑零件
	CPU * intelCPU = new IntelCPU;
	VideoCard * intelCard = new IntelVideoCard;
	Memory * intelMemory = new IntelMemory;
	//第一台电脑
	Computer * c1 = new Computer(intelCPU, intelCard, intelMemory);
	c1->work();
	delete c1;
	cout << endl;
	//第二台电脑
	Computer * c2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
	c2->work();
	delete c2;
	cout << endl;
	//第三台电脑
	Computer * c3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);
	c3->work();
	delete c3;
	cout << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

到了这里,关于C++从入门到精通 第九章(继承和多态)【下】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • c、c++、java、python、js对比【面向对象、过程;解释、编译语言;封装、继承、多态】

    目录 内存管理、适用 区别 C 手动内存管理:C语言没有内置的安全检查机制,容易出现内存泄漏、缓冲区溢出等安全问题。 适用于系统级编程 C++ 手动内存管理:C++需要程序员手动管理内存,包括分配和释放内存,这可能导致内存泄漏和指针错误。 适用于游戏引擎和系统级编

    2024年02月08日
    浏览(76)
  • 第九章 SpringBoot 自动配置原理 入门

    @SpringBootApplication -- @SpringBootConfiguration -- @EnableAutoConfiguration -- @ComponentScan 1.1 @SpringBootConfiguration @Configuration。代表当前是一个配置类 1.2 @ComponentScan 指定扫描哪些,Spring注解; 1.3 @EnableAutoConfiguration @EnableAutoConfiguration -- @AutoConfigurationPackage -- @Import(AutoConfigurationImportSelector.clas

    2024年02月13日
    浏览(43)
  • (EasyX入门与实战)第九章 鼠标操作

     基础代码:         功能:移动画点,左键画方块,右键退出。  输出:   进阶编程: 1.jpg: 2.jpg:         给鼠标添加标志,按键做出响应。 输出:         小鸟会根跟随鼠标的位置移动。  

    2024年02月11日
    浏览(43)
  • 『C语言初阶』第九章 -结构体

    🔥 博客主页 : 小羊失眠啦. 🔖 系列专栏 : C语言 🌥️ 每日语录 : 相信自己,比谁都棒。 ❤️ 感谢大家点赞👍收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 今天小羊又来给铁汁们分享关

    2024年02月12日
    浏览(41)
  • Go语言精修(尚硅谷笔记)第九章

    map是key-value数据结构,又称为字段或者关联数组。类似其它编程语言的集合,在编程中是经常使用到 基本语法 key可以是什么类型 golang中的map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针,channel, 还可以是只包含前面几个类型的 接口, 结构体, 数组 通常 key 为 int 、

    2023年04月08日
    浏览(31)
  • C++ Primer (第五版)-第九章 顺序容器

    如何选择合适的容器 迭代器 容器类型成员 列表初始化 赋值和Swap 容器的大小 关系运算符 9.3.1向顺序容器添加元素 访问元素 删除元素 改变容器大小 ### 容器操作可能使迭代器失效 9.5.2、改变string其他的方法 9.5.3 string搜索操作

    2023年04月17日
    浏览(48)
  • 《Opencv3编程入门》学习笔记—第九章

    记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。 一、图像直方图概述 1、作用:   在每个兴趣点设置一个有相近特征的直方图所构成的标签,通过标记帧与帧之间显著的边缘、颜色、角度等特征的统计变化,来检测视频中场景的变化。 2、概念:

    2024年02月11日
    浏览(48)
  • WPF入门到跪下 第九章 MVVM-行为处理

    命令是指特定指令、有明确的执行内容且具有一定的强制性。 命令VS事件 命令与控件的事件看起来有类似的地方,但是两者是不同的。 控件的事件触发函数是在对应窗体的后台代码中进行定义的,仔细查阅窗体的后台代码,能发现这是一个部分类,也就是编译过后后台代码

    2024年02月02日
    浏览(40)
  • WPF入门到跪下 第九章 MVVM-跨模块交互

    在实际开发过程中,经常会遇到多个窗口对象,随之而来的就是对应的多个ViewModel对象,这些对象在一定条件下会发生相互访问的情况,例如VM与不同窗口交互、VM与不同VM交互,这些不同模块对象之间的交互,就是跨模块交互。 MVVM模式下跨模块交互解决方案 面对跨模块交互

    2024年02月02日
    浏览(35)
  • WPF入门到跪下 第九章 MVVM-基本数据处理

    MVVM是Model-View-ViewModel的缩写。mvvm是一种设计思想。Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI展现出来,ViewModel是一个同步View和Model的对象。 在MVVM架构下,View和Model之间没有直接的联系,它们通过Vie

    2024年01月21日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包