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

 写在前面:

  1. 本系列专栏主要介绍C++的相关知识,思路以下面的参考链接教程为主,大部分笔记也出自该教程,笔者的原创部分主要在示例代码的注释部分。
  2. 除了参考下面的链接教程以外,笔者还参考了其它的一些C++教材(比如计算机二级教材和C语言教材),笔者认为重要的部分大多都会用粗体标注(未被标注出的部分可能全是重点,可根据相关部分的示例代码量和注释量判断,或者根据实际经验判断)。
  3. 由于C++基本继承了C语言的所有内容,建议读者先阅读C语言系列的专栏,有一些重点是仅在C语言系列专栏中有介绍的(比如二级指针、预处理等)。
  4. 如有错漏欢迎指出。

参考教程:黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili

五、虚函数与多态性

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:

#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;
}
  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevalin爱灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值