[C++]学习笔记——多态

多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定,编译阶段确定函数地址
  • 动态多态的函数地址晚绑定,运行阶段确定函数地址
#include
using namespace std;


//多态

//动物类
class Animal
{
public:
	//void speak() 早绑定
	virtual void speak()//晚绑定加virtual
	{
		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 & nimal=cat;允许父类与子类之间类型转换
{
	animal.speak();
}

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

}

//不想继承的就用Virtual 想继承的就不加

void test02()
{
	Dog dog;
	doSpeak(dog);
}
int main()
{
	test01();
	test02();

	return 0;
}


//总结
//动态多态满足条件
//1.有继承关系
//2.子类要重写父类的虚函数(覆盖)
//重写 函数返回值类型 函数名 参数列表 完全相同


//动态多态的使用
//动态多态:父类的指针或引用指向子类对象

多态原理剖析

#include
using namespace std;


class Animal
{
public:
	//如果将virtual去掉,变为非静态的成员函数,则不是属于类的对象
	//复习,只有非静态成员变量存储于类,静态函数和成员,非静态函数都不存储于类
	//virtual void speak()//晚绑定加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 & nimal=cat;允许父类与子类之间类型转换
{
	animal.speak();
}

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

//不想继承的就用Virtual 想继承的就不加

void test02()
{
	Dog dog;
	doSpeak(dog);
}


void test03()
{
	cout << "sizeof Animal:" << sizeof(Animal) << endl;
}
int main()
{
	cout << sizeof(int*)<

搞清楚原理:虚函数相当于一个指针,被继承时,如果子类写了一模一样的函数,内容就会被覆盖

[C++]学习笔记——多态_第1张图片

 (如上图所示)

vfptr--虚函数(表)指针

vftable-虚函数表

父类的指针或者引用指向子类对象时候,发生多态

多态案例1——计算器类

案例描述:分别用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展和维护

示例:

#include
#include

using namespace std;

//分别用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

//普通写法
class Calculator
{
public:

	int getResult(string oper)
	{
		if (oper =="+")
		{
			return m_Num1 + m_Num2;
		}
		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.getResult("+") << endl;
	cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult("-") << endl;
	cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult("*") << 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* abc = new AddCalculator;//父类指针指向子类对象

	abc->m_Num1 = 10;
	abc->m_Num2 = 10;

	cout << abc->m_Num1 << "+" << abc->m_Num2 <<"=" << abc->getResult() << endl;
	//用完后记得销毁
	delete abc;
	//当利用基类指针指向派生类对象,而派生类对象是使用new运算动态生成的,这是可能会产生不确定的后果
	//此时需要使用虚析构函数
	// 
	//减法运算
	abc = new SubCalculator;//abc还是父类指针,只不过刚刚把它堆区数据释放了,因为释放的是堆区,指针类型还没有变化
							//还是父类指针指向子类对象
	//数据要重新赋值
	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 = 10;
	abc->m_Num2 = 100;
	cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;

	delete abc;

}
int main()
{
	//test01();
	test02();
	return 0;
}

总结:C++开发提倡利用多态设计程序架构,因为多态优点很多

纯虚函数和抽象类

在多态中,通常父类中的虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

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

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

抽象类的特点:

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

示例:

#include
using namespace std;

//纯虚函数和抽象类
class Base
{
public:
	//纯虚函数
	//只要有一个纯虚函数,这个类称为抽象类
	//抽象类特点:
		//1.无法实例化对象
		//2.抽象类的子类,必须要重写父类中的纯虚函数,否则也属于抽象类,无法实例化对象
	virtual void func() = 0;
};

class Son :public Base
{
public:
	virtual void func()
	{
		cout << "func函数调用" << endl;
	}
};


void test01()
{
	
	//Base b;
	//new Base;//都不可以实例化对象
	//Son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象

	Base* base = new Son;

	base->func();
}


int main()
{
	test01();
	return 0;
}

多态案例2——饮品的制作

#include
using namespace std;

//案例描述
//制作饮品大致流程为煮水、冲泡、倒入杯中、加入辅料

//利用多态技术实现本案例,提供子类制作咖啡和茶叶

class AbstractDrinking
{
public:
	//煮水
	virtual void Boil() = 0;

	//冲泡
	virtual void Brew() = 0;

	//倒入杯中
	virtual void PourCup() = 0;
	//加入辅料
	virtual void PutSomething() = 0;

	//制作饮品
	void makeDrink()
	{
		Boil();
		Brew();
		PourCup();
		PutSomething();
	}
};


//制作咖啡
class Coffee :public AbstractDrinking
{
public:
	//煮水
	virtual void Boil()
	{
		cout << "煮农夫山泉" << endl;
	}
	//冲泡咖啡
	virtual void Brew()
	{
		cout << "冲泡咖啡" << endl;
	}
	//倒入杯中
	virtual void PourCup()
	{
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入糖和牛奶" << endl;
	}
};

//制作茶叶
class Tea :public AbstractDrinking
{
public:
	//煮水
	virtual void Boil()
	{
		cout << "煮矿泉水" << endl;
	}
	//冲泡咖啡
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}
	//倒入杯中
	virtual void PourCup()
	{
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入枸杞" << endl;
	}
};

//制作函数
void doWork(AbstractDrinking *abs)//AbstractDrinking *abs=new Coffee
{
	abs->makeDrink();//提供公共接口
	delete abs;//防止内存泄漏,及时释放堆区
}

void test01()
{
	//制作咖啡
	doWork(new Coffee);
	cout << "--------------------------------" << endl;
	//制作茶叶
	doWork(new Tea);
	
}
int main()
{
	test01();
	return 0;
}

案例中多态是一个接口有多种形态,由于传入对象不一样,接入同一个接口,但制作不同的饮品。

用引用也可以,引用不用手动释放内存,指针不用实例化对象,各有优劣。

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针释放时无法调用子类的析构代码

解决方式:将父类中析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要具体的函数实现

虚析构和纯虚析构区别:

  • 如果纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法

virtual ~类名( ){ }

纯虚析构语法

virtual ~类名( )=0;

#include
#include
using namespace std;

//虚析构和纯虚析构

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

};

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

	virtual void speak()
	{
		cout<<*m_Name << "小猫在说话" << endl;
	}
	string *m_Name;//做成一个指针,让小猫的名称直接出现在堆区域的数据
					//用指针去维护它
	~Cat()//对应的析构函数,释放堆区的数据
	{
		if (m_Name != NULL)
		{
			cout << "Cat析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}
};

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

int main()
{
	test01();
	return 0;
}

运行结果

[C++]学习笔记——多态_第2张图片

出现问题:因为使用父类指针指向子类对象,所以delete父类指针的时候不会走子类的代码

父类指针在析构的时候 不会调用子类中的析构函数,导致子类如果有堆区属性,会出现内存的泄露。因为New出来的Cat类创建在堆区,所以不会主动释放内存。

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

解决办法:虚析构

#include
#include
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;
	}
	string *m_Name;//做成一个指针,让小猫的名称直接出现在堆区域的数据
					//用指针去维护它
	~Cat()//对应的析构函数,释放堆区的数据
	{
		if (m_Name != NULL)
		{
			cout << "Cat析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}
};

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

int main()
{
	test01();
	return 0;
}

运行结果:

[C++]学习笔记——多态_第3张图片

注意:语法强制纯虚析构函数必须有函数实现,因为有时父类也有一些数据开辟在堆区

既要使用纯虚函数,又要释放父类在堆区中的数据,就需要使用类内纯虚函数声明,类外写实现的写法

并非所有都要用虚析构、纯虚析构,此题比较特殊,因为子类中有数据被开辟到了堆区,所以必须走子类中的析构代码,如果只用多态走不到子类析构代码,所以我们在父类中才加上虚析构,纯虚析构。

小结:

  1. 虚析构或纯虚析构就是用来通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  3. 拥有纯虚析构函数也属于抽象类

多态案例3——电脑组装

电脑主要组成部件为CPU,显卡,内存条;

将每个零件封装除抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

[C++]学习笔记——多态_第4张图片

 示例:

#include
using namespace std;

//抽象的cpu类
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* vc, Memory* mem)
	{
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}
	//提供工作函数
	void work()
	{
		m_cpu->calculate();

		m_vc->display();

		m_mem->storage();
	}


	//提供一个析构函数 释放三个电脑零件
	~Computer()
	{
		//释放零件堆区数据
		//释放CPU
		if (m_cpu != NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}
		//释放显卡
		if (m_vc != NULL)
		{
			delete m_vc;
			m_vc = NULL;
		}
		//释放CPU
		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 << "Intel的CPU开始计算了!" << endl;
	}
};

class IntelVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Intel的显卡开始显示了!" << endl;
	}
};

class IntelMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Intel的内存条开始存储了!" << endl;
	}
};


//Lenovo厂商
class LenovoCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Lenovo的CPU开始计算了!" << endl;
	}
};

class LenovoVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Lenovo的显卡开始显示了!" << endl;
	}
};

class LenovoMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Lenovo的内存条开始存储了!" << endl;
	}
};

void test01()
{
	cout << "第一台电脑开始工作:" << endl;
	//第一台电脑零件
	CPU* intelCpu = new IntelCPU;
	VideoCard* intelCard = new IntelVideoCard;
	Memory* intelMem = new IntelMemory;

	//创建第一台电脑
	Computer* computer1 = new Computer(intelCpu,intelCard,intelMem);
	computer1->work();
	delete computer1;

	cout << "-------------------------------" << endl;
	cout << "第二台电脑开始工作:" << endl;
	//第二台电脑组装
	Computer* computer2 = new Computer(new LenovoCPU,new  LenovoVideoCard,new LenovoMemory);
	computer2->work();
	delete computer2;

}
//不需要虚析构,因为子类中没有在堆区开辟
//1、此处Computer实例可以不用指针,直接实例化就行,因为Computer实例的建立和销毁都在同一个函数下,所以存放在栈区或是堆区都行。
//2、不需要虚析构,因为new Computer堆区指向的三个零件堆区已经在Computer析构函数中释放
//3、Computer属于单独一个类,它的析构不会影响其他类的析构,所以不需要虚析构
//4、虚析构是用在多态下,只调用父类的析构函数,没有调用子类的析构,从而无法释放在子类中建立的堆区内存。但此案例中并没有在子类中建立堆区内存。

int main()
{
	test01();
	return 0;
}

你可能感兴趣的:(c++,学习,笔记)