多态

1、基本概念

多态是C++面向对象的三大特点之一。

多态分为两类: 静态多态: 函数重载和运算符属于静态多态,服用函数名; 动态多态: 派生类和虚函数实现运行时多态。

静态多态和动态多态区别: 静态多态的函数地址早绑定 - 编译阶段确定函数地址。 动态多态的函数地址晚绑定 - 运行阶段确定函数地址。

#include <iostream>
using namespace std;

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

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

//执行说话的函数
void doSpeak(Animal& animal) //Animal& animal = cat;
{
	animal.speak();
}

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

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

上面的代码并不能达到预期的效果,它打印的并不是“小猫在说话”,而是“动物在说话”。 原因就是地址早绑定,在编译阶段确定函数地址。

如果想要打印“猫在说话”,需要使用动态多态。

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

添加关键字“virtual”,将函数变为“虚函数”,实现动态多态。

动态多态的满足条件:

1、有继承关系;

2、子类重写父类的虚函数;

动态多态如何使用呢? 总结下来,就是父类的指针或者引用,执行子对象。

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

那么,动态多态的底层是怎样实现的呢?

首先我们删除虚函数关键字“virtual”,看一看Animal类占了多大内存。

void test02()
{
	cout << "sizeof Animal = " << sizeof(Animal) << endl;
}

打印的结果是 1,它是一个空类。

那加上“virtual”关键字后有什么变化呢? 结果变成了 4。 也就是说类的内部发生了变化。

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

当写了这样一段代码后,Animal类的内部会有一个大小为4个字节的指针,名叫vfptr,也叫虚函数指针。 这个指针指向虚函数表vftable,表的内部记录一个虚函数的地址 &Animal::speak。

class Cat : public Animal
{
public:

};

对于小猫的类,如果不发生重写,仅仅只是继承,那Cat的内部结构中继承一份虚指针,指向子类的虚函数表&Animal::speak。

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

当发生重写后,子类中的虚函数表会被覆盖,替换成子类的虚函数地址&Cat::speak。

所以,多态的原理就是父类的指针和引用指向子类对象。

可以使用开发人员命令工具验证。

案例1:计算器类

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

//分别利用普通写法和多态技术实现计算器
//普通写法
class Calculator
{
public:

	int gerResult(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;
		}
	}

	int m_Num1; //操作数1
	int m_Num2; //操作数2
};

void test01()
{
	//创建计算器对象
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;

	cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.gerResult("+") << endl;
	cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.gerResult("-") << endl;
	cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.gerResult("*") << endl;
}

//如果要改进计算器,要修改源码。
//在实际开发中,提倡 开闭原则:对扩展进行开放,对修改进行关闭。

//利用多态实现计算器
//实现计算器的抽象类
class abstractCalculator
{
public:
	virtual int getResult()
	{
		return 0;
	}

	int m_Num1;
	int m_Num2;
};

//加法计算器类
class addCalculator : public abstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 + m_Num2;
	}
};

//减法计算器类
class subCalculator : public abstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 - m_Num2;
	}
};

//乘法计算器类
class mulCalculator : public abstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 * m_Num2;
	}
};

void test02()
{
	//多态使用条件:父类的指针或引用指向子类对象
	//加法计算器
	abstractCalculator* ptr = new addCalculator;
	ptr->m_Num1 = 100;
	ptr->m_Num2 = 100;

	cout << ptr->m_Num1 << "+" << ptr->m_Num2 << "=" << ptr->getResult() << endl;

	//用完后销毁
	delete ptr;

	//减法
	ptr = new subCalculator;
	ptr->m_Num1 = 100;
	ptr->m_Num2 = 100;
	cout << ptr->m_Num1 << "-" << ptr->m_Num2 << "=" << ptr->getResult() << endl;
	delete ptr;

	//乘法
	ptr = new mulCalculator;
	ptr->m_Num1 = 100;
	ptr->m_Num2 = 100;
	cout << ptr->m_Num1 << "*" << ptr->m_Num2 << "=" << ptr->getResult() << endl;
	delete ptr;
}

//使用多态后代码量变大了,
//但是,组织结构清晰;可读性强;对于前期和后期的扩展以及维护性高;

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

2、纯虚函数和抽象类

在上面的代码中,父类中的虚函数下的代码块是没有任何意义的,主要是在调用子类中的重写内容。 因此,可以将虚函数改为纯虚函数。

纯虚函数语法:virtual 返回值类型 函数名 (参数类型) = 0;

当类中有了纯虚函数,这个类称为抽象类。

抽象类特点: 无法实例化对象; 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

#include <iostream>
using namespace std;

class Base
{
public:
	//纯虚函数
	virtual void func() = 0;
};
//子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
class Son : public Base
{
public:
	virtual void func()
	{
		cout << "func函数调用" << endl;
	}
};

void test01()
{
	/*抽象类无法实例化对象
	Base b;
	new Base;*/
	Base* base = new Son;
	base->func();

}

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

案例二:制作饮品

#include <iostream>
using namespace std;

//抽象类
class drink
{
public:
	//煮水
	virtual void boil() = 0;
	//洗茶具
	virtual void wash() = 0;
	//放茶叶
	virtual void put() = 0;
	//冲泡
	virtual void brew() = 0;
	//静置
	virtual void standing() = 0;

	void makeDrink()
	{
		boil();
		wash();
		put();
		brew();
		standing();
	}
};

//制作过程
class Tea : public drink
{
public:
	//煮水
	virtual void boil()
	{
		cout << "煮山泉" << endl;
	}
	//洗茶具
	virtual void wash()
	{
		cout << "清洗茶具" << endl;
	}
	//放茶叶
	virtual void put()
	{
		cout << "放茶叶" << endl;
	}
	//冲泡
	virtual void brew()
	{
		cout << "冲泡" << endl;
	}
	//静置
	virtual void standing()
	{
		cout << "静置等待" << endl;
	}
};

void doWork(drink* ptr)
{
	ptr->makeDrink();
	delete ptr;
}

void test1()
{
	cout << "---泡茶---" << endl;
	//泡茶
	doWork(new Tea);
}

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

3、虚析构和纯虚析构

在多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,堆区的数据会因此造成数据的泄露。 所以,要将父类中的析构函数改为虚析构或纯虚析构。

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

class Animal
{
public:

	Animal()
	{
		cout << "Animal构造函数调用" << endl;
	}

	/*virtual ~Animal()
	{
		cout << "Animal析构函数调用" << endl;
	}*/
	//纯虚析构,需要声明也需要实现
	//有了纯虚析构后, 这个类属于抽象类,无法实例化对象
	virtual ~Animal() = 0;

	virtual void speak() = 0;
};

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

class Cat : public Animal
{
public:
	Cat(string name)
	{
		cout << "cat的构造函数调用" << endl;
		m_Name = new string(name);
	}

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

	~Cat()
	{
		if (m_Name != NULL)
		{
			cout << "cat的析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}

	string* m_Name;
};

void test1()
{
	Animal* ptr = new Cat("Tom");
	ptr->speak();
	//父类指针析构的时候不会调用子类的析构函数,堆区的数据会因此造成数据的泄露。
	delete ptr;
}

int main()
{
	test1();
	system("pause");
	return 0;
}
全部评论

相关推荐

11-24 00:11
已编辑
广东工业大学 算法工程师
避雷深圳&nbsp;&nbsp;yidao,试用期&nbsp;6&nbsp;个月。好嘛,试用期还没结束,就直接告诉你尽快找下一家吧,我谢谢您嘞
牛客75408465号:笑死,直属领导和 hr 口径都没统一,各自说了一些离谱的被裁理由,你们能不能认真一点呀,哈哈哈哈哈😅😅😅
点赞 评论 收藏
分享
1 2 评论
分享
牛客网
牛客企业服务