C++多态基础

本文详细介绍了C++中的多态性,包括静态多态和动态多态的定义、区别以及实现方式。通过计算器和动物说话的例子展示了多态的应用,并强调了多态在代码组织、可读性和维护性上的优势。同时,讲解了虚函数、纯虚函数、抽象类、虚析构与纯虚析构的概念,以及它们在多态中的作用。最后,通过电脑组装的案例进一步阐述了多态的实际运用。
摘要由CSDN通过智能技术生成

什么是多态

所谓多态就是一种事务的多种表现状态,比如说一条狗,通过它的毛色区分可以是黄狗,黑狗,白狗等等,这就是狗的多态。

那么在C++代码中怎么体现多态呢。

多态在C++中的表现

多态分为静态多态和动态多态

  1. 静态多态: 函数重载 和 运算符重载都属于 静态多态,复用函数名
  2. 动态多态: 派生类和虚函数实现动态多态(也就是父类指针或引用指向子类实例化对象)

两者的区别

  1. 静态多态 函数的地址 早 绑定,编译时期就已经确定了函数地址。
  2. 动态多态 函数的地址 晚 绑定,运行时确定函数地址

动态多态的满足条件

  1. 有继承关系。
  2. 子类重写父类的虚函数(重写时virtual关键字可写可不写)

动态多态的使用

  1. 父类的指针或引用指向子类对象

多态的优点

  1. 代码组织结构清晰。
  2. 可读性强。
  3. 利于前期和后期的扩展以及维护。

下面通过代码举几个例子,体会一下多态

多态的代码实现

分别使用普通写法和多态实现计算器

class Calculator
{ // 普通写法
public:
	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;
		}
	}

	int m_Num1;
	int m_Num2;
};

这种写法 但凡需要新增功能,那就需要修改源码
违反了 开闭原则(对扩展进行开放,对修改进行关闭)
这非常不利于代码的开发和维护

// 多态实现
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 ExCalculator : public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 - m_Num2;
	}
};
// 乘法计算器类
class DoubleCalculator : public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 * m_Num2;
	}
};

1、多态组织接口非常清晰:假设乘法出错了,后续只需要看乘法的子类,别的代码不需要关心
2、可读性更强
3、后期扩展性和可维护性高:扩展时不需要动源码
4、符合开闭原则

多态的实现

执行说话的函数
如果想要猫说话就要进行晚绑定,
具体做法就是将Animal的speak方法声明为虚函数

// 多态
class Animal
{
public:
	//虚函数,使得该函数晚绑定地址,也就是将静态多态转换为动态多态
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};
class Cat : public Animal
{
public:
	void speak()
	{
		cout << "喵喵喵···" << endl;
	}
};
class Dog : public Animal
{
public:
	void speak()
	{
		cout << "汪汪汪···" << endl;
	}
};

虚函数与纯虚函数

虚函数在父类中一般来说是毫无意义的,都是调用子类重写的虚函数
所以就可以将父类中的虚函数改为纯虚函数
纯虚函数的语法 : virtual void function() = 0;

抽象类

  • 当类中有了纯虚函数,这个类就是抽象类

特点:

  • 1、无法实例化对象
  • 2、子类必须重写抽象类中 所有的 纯虚函数,否则也属于抽象类
class Base
{ // 此时这个类就叫做抽象类
public:
	// 纯虚函数
	// 只要有一个纯虚函数,那么这个类就是抽象类
	virtual void func01() = 0;
	virtual void func02() = 0;
};

那么抽象类改怎么继承呢

class Son01 : public Base
{ // 此时这个类也是抽象类,因为没有重写父类的抽象类
public:
};
class Son02 : public Base
{ // 此时这个类也是抽象类,因为没有将父类的所有纯虚函数重写
public:
	void func01()
	{}
};
class Son03 : public Base
{ // 此时这个类是一个具体类,可以实例化对象
public:
	void func01()
	{}
	void func02()
	{}
};

可以看到,子类必须重写父类所有的纯虚函数才能实例化对象

虚析构与纯虚析构

先来看下面一段代码,Animal类与上面的一样

class Animal
{
public:
	Animal()
	{
		cout << "Animal 构造 " << endl;
	}
	virtual void speak() = 0;
	~Animal()
	{
		cout << "Animal 纯虚析构 " << endl;
	}
};

class Cat : public Animal
{
public:
	Cat(string name) {
		m_Name = new string(name); // 堆区没有释放
	}
	virtual void speak()
	{
		cout << *m_Name << "  miaomiaomiao~~~" << endl;
	}

	~Cat() // 析构函数 释放堆区
	{
		cout << "cat 析构" << endl;
		delete m_Name;
		m_Name = NULL;
	}

	string* m_Name;

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

int main()
{
	test01();

	return 0;
}

你会发现子类对象生命周期到了之后并没有调用子类的析构函数,而是直接调用了父类的析构,如果子类的析构不能被调用将会有内存泄漏的风险

原因分析:子类的实例化对象是由父类指针操作的,而父类指针是找不到子类的析构的。
父类的指针在析构的时候不会调用子类的析构,导致子类如果有堆区属性会出现内存泄漏

如何改进呢?
我们只需要将父类中的析构虚拟化即可,这里有几个注意点

  1. 虚析构或纯虚析构不是必须写的,如果子类没有开辟堆区空间,那就可以不写
  2. 虚析构是有函数体的,没有函数体只有声明会报错。
  3. 纯虚析构,这时如果不写析构的函数体会报错
    两中解决办法,可以在外部实现纯虚析构,也可以直接在 = 0后面写函数体只是要去掉纯虚函数的封号
virtual~Animal() // 虚析构是有函数体的
	{
		cout << "Animal 虚析构 " << endl;
	}
virtual ~Animal() = 0; // 纯虚析构,这时如果不写析构的函数体会报错
	// 两中解决办法,可以在外部实现纯虚析构,也可以直接在 = 0后面写函数体
	// 只是要去掉纯虚函数的封号
	virtual ~Animal() = 0
	{
		cout << "Animal 纯虚析构 " << endl;
	}

总结

多态在使用时,如果子类有在堆区开辟空间,那么父类指针在释放时无法调用子类的析构

  • 解决方式: 将父类中的析构函数改为虚析构或纯虚析构
  • 虚析构和纯虚析构的共性:
  • 1、可以解决父类指针释放子类对象
  • 2、都需要有具体的函数实现
  • 虚析构与纯虚析构的区别
  • 1、如果是纯虚析构,那么这个类即使没有其他纯虚函数 也属于抽象类
  • 2、若为虚析构,这个类没有纯虚函数 则不属于 抽象类,可以实例化对象
  • 构造函数是不允许使用virtual关键字修饰的

下面通过一个案例来加深对多态的理解

// cpu抽象类
class CPU
{
public:
	virtual void calculate() = 0;
};
// 内存条抽象类
class Memory
{
public:
	virtual void storage() = 0;
};
// 抽象显示类
class VideoCard
{
public:
	virtual void display() = 0;
};

// 电脑
class Computer
{
public:
	Computer(CPU* cpu, VideoCard* vc, Memory* mem)
	{
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}

	// 提供工作的函数
	void work()
	{
		// 让零件工作
		m_cpu->calculate();
		m_vc->display();
		m_mem->storage();
	}

	~Computer()
	{
		if (m_cpu != NULL) delete m_cpu, m_cpu = NULL;
		if (m_vc != NULL) delete m_vc, m_vc = NULL;
		if (m_mem != NULL) delete m_mem, m_mem = NULL;
	}

private:
	CPU* m_cpu; // cpu的零件指针
	VideoCard* m_vc; // 显卡零件指针
	Memory* m_mem; // 内存条零件指针
};

// 具体厂商
// Intel 厂商
class IntelCPU : public CPU
{
public:
	virtual void calculate()
	{
		cout << "IntelCPU开始计算了" << endl;
	}
};
class IntelVideo : public VideoCard
{
public:
	virtual void display()
	{
		cout << "IntelCPU开始显示了" << endl;
	}
};
class IntelMem : public Memory
{
public:
	virtual void storage()
	{
		cout << "IntelCPU开始存储了" << endl;
	}
};

// Lenovo 厂商
class LenovoCPU : public CPU
{
public:
	virtual void calculate()
	{
		cout << "LenovoCPU开始计算了" << endl;
	}
};
class LenovoVideo : public VideoCard
{
public:
	virtual void display()
	{
		cout << "LenovoCPU开始显示了" << endl;
	}
};
class LenovoMem : public Memory
{
public:
	virtual void storage()
	{
		cout << "LenovoCPU开始存储了" << endl;
	}
};

void test01()
{
	// 第一台电脑
	CPU* intelcpu = new IntelCPU(); // 需要析构释放
	VideoCard* vedeoCard = new IntelVideo();
	Memory* intelmem = new IntelMem();

	// 创建第一台电脑
	Computer* computer1 = new Computer(intelcpu, vedeoCard, intelmem);
	computer1->work();
	delete computer1;

	cout << "------------------------" << endl;
	// 创建第二台电脑
	Computer* computer2 = new Computer(new LenovoCPU, new LenovoVideo, new LenovoMem);
	computer2->work();
	delete computer2;

	cout << "------------------------" << endl;
	// 创建第三台电脑
	// 把它创建在栈区,因为我Computer类里析构时进行了delete操作,所以下面代码就会报错。
	/*LenovoCPU c;
	LenovoVideo v;
	LenovoMem m;
	CPU& cpu = c;
	VideoCard& video = v;
	Memory& mem = m;
	Computer* computer3 = new Computer(&cpu, &video, &mem);
	computer3->work();
	delete computer3;*/
}

int main()
{
	test01();

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值