C++学习之多态

基本概念

多态分为两类
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
静态多态的函数地址早绑定-编译阶段确定函数地址
动态多态的函数地址晚绑定-运行阶段确定函数地址

基本语法

C++中允许父类和子类之间的类型转换,父类的引用可以直接指向子类的对象

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

class Animal
{
public:
	void speak()
	{
		cout << "动物在说话。" << endl;
	}
};
class Cat:public Animal
{
public:
	void speak()
	{
		cout << "猫在说话。" << endl;
	}
};
//地址早绑定,在编译阶段确定函数地址
void doSpeak(Animal& animal)
{
	animal.speak();
}

void test()
{
	Cat cat;
	doSpeak(cat);
}

int main()
{
	test();
	system("pause");
	return 0;
}

在这里插入图片描述
如果想执行子类的函数,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定,在父类的同名函数前加virtual关键字。

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话。" << endl;
	}
};
class Cat:public Animal
{
public:
	void speak()
	{
		cout << "猫在说话。" << endl;
	}
};
void doSpeak(Animal& animal)
{
	animal.speak();
}

在这里插入图片描述
动态多态满足条件
1、有继承关系
2、子类重写父类的虚函数
重写不是重载,重载是函数名相同,参数不一样,重写是函数返回值、函数名、形参列表中的所有内容相同
动态多态使用,父类的指针或者引用,执行子类对象

virtual的原理

Animal类中不写virtual时,大小为1个字节,因为函数不属于类,Animal相当于个空类,编译器为了确定该空类的信息,规定大小为1个字节。
使用virtual后,会在类中生成一个虚函数(表)指针(vfptr),指针占4个字节,该指针指向虚函数表(vftable),表内部记录虚函数的地址,就是Animal::speak()函数的地址,&Animal::speak()。
当子类Cat中没有出现函数重写,即

class Cat:public Animal
{
public:

};

继承后会继承父类的东西,子类中也会有一个虚函数指针,指向子类的虚函数表,记录的还是&Animal::speak(),检查类的内存情况:

1>class Animal	size(8):
1>	+---
1> 0	| {vfptr}
1>	+---
1>Animal::$vftable@:
1>	| &Animal_meta
1>	|  0
1> 0	| &Animal::speak
1>Animal::speak this adjustor: 0
1>class std::is_error_code_enum<char const *>	size(1):
1>	+---
1> 0	| +--- (base class std::integral_constant<bool,0>)
1>	| +---
1>	+---
1>class Cat	size(8):
1>	+---
1> 0	| +--- (base class Animal)
1> 0	| | {vfptr}
1>	| +---
1>	+---
1>Cat::$vftable@:
1>	| &Cat_meta
1>	|  0
1> 0	| &Animal::speak
1>class std::ostreambuf_iterator<wchar_t,struct std::char_traits<wchar_t> >	size(16):
1>	+---
1> 0	| _Failed
1>  	| <alignment member> (size=7)
1> 8	| _Strbuf
1>	+---

当子类出现函数重写后,子类中的虚函数表内部会被替换为子类的虚函数地址,&Cat::speak(),当父类的指针或者引用指向子类对象时,发生多态

Animal& animal = cat;
	animal.speak();

会从cat的虚函数表中找这个函数,检查类的内存布局:

1>class Animal	size(8):
1>	+---
1> 0	| {vfptr}
1>	+---
1>Animal::$vftable@:
1>	| &Animal_meta
1>	|  0
1> 0	| &Animal::speak
1>Animal::speak this adjustor: 0
1>class std::is_error_code_enum<char const *>	size(1):
1>	+---
1> 0	| +--- (base class std::integral_constant<bool,0>)
1>	| +---
1>	+---
1>class Cat	size(8):
1>	+---
1> 0	| +--- (base class Animal)
1> 0	| | {vfptr}
1>	| +---
1>	+---
1>Cat::$vftable@:
1>	| &Cat_meta
1>	|  0
1> 0	| &Cat::speak
1>Cat::speak this adjustor: 0

案例

多态的优点:代码组织结构清晰、可读性强、利于前期和后期的扩展和维护。
写一个计算器类
常规写法:

class Calculator
{
public:
	int m_num1;
	int m_num2;
public:
	float GetSymbol(string symbol)
	{
		if(symbol == "+")
		{
			return m_num1 + m_num2;
		}
		else if(symbol == "-")
		{
			return m_num1 - m_num2;
		}
		else if(symbol == "*")
		{
			return m_num1 * m_num2;
		}
	}
};
void test()
{
	Calculator c1;
	c1.m_num1 = 10; 
	c1.m_num2 = 2;
	cout<< c1.m_num1  <<"+" <<c1.m_num2 <<"="<<c1.GetSymbol("+") << endl;
	cout << c1.m_num1 << "-" << c1.m_num2 << "=" << c1.GetSymbol("-") << endl;
	cout << c1.m_num1 << "*" << c1.m_num2 << "=" << c1.GetSymbol("*") << endl;
}

如果对已经写的计算器进行扩展,比如增加运算,除法、乘方之类的,就得在类中的函数中进行修改。
多态写法

class AbstractCalculator
{
public:
	int m_Num1;
	int m_Num2;
	virtual int getResult()
	{
		return 0;
	}
};

class ADDCalculator : public AbstractCalculator
{
	virtual int getResult()
	{
		return m_Num1+m_Num2;
	}
};
class SUBCalculator : public AbstractCalculator
{
	virtual int getResult()
	{
		return m_Num1 - m_Num2;
	}
};
class MULCalculator : public AbstractCalculator
{
	virtual int getResult()
	{
		return m_Num1 * m_Num2;
	}
};
void test()
{
	AbstractCalculator* abc = new ADDCalculator;//父类的指针指向子类
	//new出一个子类,返回地址
	abc->m_Num1 = 10;
	abc->m_Num2 = 2;
	cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
	//new之后要删除
	delete abc;
	abc = new SUBCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 2;
	cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
	delete abc;
}

纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)=0,当类中有了纯虚函数,这个类也称为抽象类。
抽象类的特点:
无法实例化对象(也不能在堆区new出来),子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

案例2 制作饮品

class AbstractDrink
{
public:
	virtual void step1() = 0;
	virtual void step2() = 0;
	virtual void step3() = 0;
	void makeDrink()
	{
		step1();
		step2();
		step3();
	}
};

class Coffee : public AbstractDrink
{
public:
	virtual void step1()
	{
		cout << "第一步,倒入咖啡" << endl;
	}
	virtual void step2()
	{
		cout << "第二步,煮咖啡" << endl;
	}
	virtual void step3()
	{
		cout << "第三步,添加糖和牛奶" << endl;
	}
};

class Tea : public AbstractDrink
{
public:
	virtual void step1()
	{
		cout << "第一步,倒入茶" << endl;
	}
	virtual void step2()
	{
		cout << "第二步,煮茶" << endl;
	}
	virtual void step3()
	{
		cout << "第三步,添加柠檬" << endl;
	}
};
void doDrink(AbstractDrink * abc)
{
	abc->makeDrink();
	delete abc;
}
void test()
{
	doDrink(new Coffee);
	cout << "-------------" << endl;
	doDrink(new Tea);
}

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,
解决方法:将父类中的析构函数改为虚析构或纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针无法释放子类对象,都需要有具体的函数实现
虚析构和纯虚析构的区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象。
虚析构语法:virtual ~类名() {}
纯虚析构语法:virtual ~类名()=0;
如果子类中没有堆区数据,可以不写虚析构或纯析构

案例

class Animal
{
public:
	Animal()
	{
		cout << "Animal构造函数的调用" << endl;
	}
	~Animal()
	{
		cout << "Animal析构函数的调用" << endl;
	}
	virtual void doSpeak() = 0;
};

class Cat :public Animal
{
public:
	string * m_name;
	virtual void doSpeak()
	{
		cout << *m_name << "小猫在叫" << endl;
	}
	Cat(string name)
	{
		cout << "Cat构造函数的调用" << endl;
		m_name = new string(name);
	}
	~Cat()
	{
		cout << "Cat析构函数的调用" << endl;
		if(m_name != NULL)
		{
			delete m_name;
			m_name = NULL;
		}
	}
};
void test()
{
	Animal* abc = new Cat("Tom");
	abc->doSpeak();
	delete abc;
}

输出为:在这里插入图片描述
构造时先构造父类后构造子类,按情况析构时先析构子类再析构父类,但在这子类没有调用析构函数,
在delete Animal,后析构父类指针时不会调用子类中的析构函数,导致子类如果有堆区的数据,就会出现内存泄漏。

解决方法:虚析构

class Animal
{
public:
	Animal()
	{
		cout << "Animal构造函数的调用" << endl;
	}
	virtual ~Animal()
	{
		cout << "Animal析构函数的调用" << endl;
	}
	virtual void doSpeak() = 0;
};

在这里插入图片描述

解决方法:纯虚析构

和纯虚函数不一样,纯虚函数仅需要声明即可,而纯虚析构既需要声明,也需要实现。

class Animal
{
public:
	Animal()
	{
		cout << "Animal构造函数的调用" << endl;
	}
	virtual ~Animal() = 0;//只写这个不对,假如父类中也有堆区的数据
	//父类的析构就也需要具体实现,所有仅有声明是不够的
	virtual void doSpeak() = 0;
};
Animal:: ~Animal()
{
	cout << "Aniaml析构函数的调用" << endl;
}

在这里插入图片描述

案例,电脑组装类

//具体零件基类
class AbstractCPU
{
public:
	virtual void Calculate() = 0;
};
class AbstractVideoCard
{
public:
	virtual void Display() = 0;
};
class AbstractMemoryModule
{
public:
	virtual void Storage() = 0;
};


//不同厂商生产的零件子类
class IntelCPU :public AbstractCPU
{
public:
	virtual void Calculate()
	{
		cout << "Intel厂家的CPU工作" << endl;
	}
};
class IntelVideoCard :public AbstractVideoCard
{
public:
	virtual void Display()
	{
		cout << "Intel厂家的显卡工作" << endl;
	}
};
class IntelMemoryModule :public AbstractMemoryModule
{
public:
	virtual void  Storage()
	{
		cout << "Intel厂家的内存条工作" << endl;
	}
};

class LenovoCPU :public AbstractCPU
{
public:
	virtual void Calculate()
	{
		cout << "Lenovo厂家的CPU工作" << endl;
	}
};
class LenovoVideoCard :public AbstractVideoCard
{
public:
	virtual void Display()
	{
		cout << "Lenovo厂家的显卡工作" << endl;
	}
};
class LenovoMemoryModule :public AbstractMemoryModule
{
public:
	virtual void  Storage()
	{
		cout << "Lenovo厂家的内存条工作" << endl;
	}
};


class Computer
{
public:
	Computer(AbstractCPU* cpu, AbstractVideoCard* videoCard, AbstractMemoryModule* memoryModule)
	{
		m_cpu = cpu;
		m_memoryModule = memoryModule;
		m_videoCard = videoCard;
	}
	void doWork()
	{
		m_cpu->Calculate();
		m_videoCard->Display();
		m_memoryModule->Storage();
	}
	~Computer()
	{
		if (m_cpu != NULL)
		{
			cout << "delete m_cpu" << endl;
			delete m_cpu;
			m_cpu = NULL;
		}
		if (m_videoCard != NULL)
		{
			cout << "delete m_videoCard" << endl;
			delete m_videoCard;
			m_videoCard = NULL;
		}
		if (m_memoryModule != NULL)
		{
			cout << "delete m_memoryModule" << endl;
			delete m_memoryModule;
			m_memoryModule = NULL;
		}
	}
private:
	//抽象类不能实例化,但是可以定义一个指向该类的指针
	AbstractCPU* m_cpu;
	AbstractVideoCard* m_videoCard;
	AbstractMemoryModule* m_memoryModule;
};

void test()
{
	Computer  c1= Computer(new IntelCPU,new IntelVideoCard,new IntelMemoryModule);
	c1.doWork();
	cout << "------------------"<< endl ;
	Computer c2 = Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemoryModule);
	c2.doWork();
}

在这里插入图片描述
test函数中Computer也可以这样创建

void test()
{
	Computer  *c1=  new Computer(new IntelCPU,new IntelVideoCard,new IntelMemoryModule);
	c1->doWork();
	delete c1;

	cout << "------------------"<< endl ;
	Computer * c2 =  new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemoryModule);
	c2->doWork();
	delete c2;
}

在这里插入图片描述

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值