C++之多态(纯虚函数、抽象类、虚析构、纯虚析构)

多态的基本概念

多态分两类

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

静态多态和动态多态的区别

静态多态的函数地址 早绑定,编译阶段确定函数地址
动态多态的函数地址 晚绑定,运行阶段确定函数地址

动态多态的满足条件
1、有继承关系
2、子类重写父类的虚函数

动态多态的使用
父类的指针或者引用,执行子类对象
多态的优点

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

#include
using namespace std;
class Animal
{
public:
	//虚函数  动态多态
	virtual void speak()
	{
		cout << "shuohua" << endl;
	}

	//Animal类内部结构:vfptr:虚函数(表)指针   指向一个虚函数表(vftable)
	//虚函数表的内部记录一个虚函数的地址(&Animal::speak)
	//而子类也会继承这个指针,指向子类的虚函数表,
    //当子类重写了父类的虚函数,那么子类内部的虚函数表会替换(覆盖)成子类的函数地址(&Cat::speak)

	//当父类的指针或引用指向子类对象时,发生多态
};

class Cat :public Animal
{
public:
	//重写:函数返回值类型 函数名 参数列表 完全相同
	virtual void speak()
	{
		cout << "cat 说话" << endl;
	}
};
class Dog :public Animal
{
	virtual void speak()
	{
		cout << "Dog 说话" << endl;
	}
};
//地址早绑定,在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Animal &animal)//c++允许父子类的相互转换
{
	animal.speak();
}

void test02()
{
	cout << "size of Animal  = " << sizeof(Animal1) << endl;//加virtual后 4字节   无virtual,相当于空类 1字节
}
void test01()
{
	Cat cat;
	doSpeak(cat);
	Dog dog;
	doSpeak(dog);
}
int main()
{
	test01();
	//test02();
	system("pause");
	return 0;
}

//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,那么动态联编
案例

#include
using namespace std;
//普通实现
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 AddCllculator :public AbstractCalculator
{
public:
     int getResult()
	 { 
		return m_Num1 + m_Num2;
	 }
};
class SubCllculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 - m_Num2;
	}
};
class MulCllculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 * m_Num2;
	}
};
void work(AbstractCalculator& calculator)
{
	cout << calculator.getResult() << endl;
}

void test01()
{
	//用指针指向子类对象
	AbstractCalculator* cal = new AddCllculator;
	cal->m_Num1 = 10;
	cal->m_Num2 = 10;
	cout << cal->getResult() << endl;
	delete cal;	//记得销毁

	//用引用指向子类对象
	AddCllculator add;
	add.m_Num1 = 100;
	add.m_Num2 = 100;
	work(add);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此,可将虚函数改为纯虚函数
纯虚函数的语法: virtual 返回值类型 函数名 (参数列表) = 0;

抽象类:当类中有了(只要有一个)纯虚函数,这个类也称为抽象类

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

#include
using namespace std;
//抽象类
class Base
{
public:
	virtual void func() = 0;//子类必须要重写
};
class Son1 :public Base
{
public:
	virtual void func()
	{
		cout << "Son1 func函数调用" << endl;
	}
};
class Son2 :public Base
{
public:
	virtual void func()
	{
		cout << "Son2 func函数调用" << endl;
	}
};

void test01()
{
	//Base b = new Base 抽象类无法实例化
	
	Son1 s;//子类必须重写父类的纯虚函数,否则也无法实例化
	s.func();

	Base* base1 = new Son1;
	base1->func();
	Base* base2 = new Son2;
	base2->func();//这就是多态的体现,通过创建对象的不同调用不同的接口
}
int main()
{
	test01();
	system("pause");
	return 0;
}

虚析构和纯虚析构

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

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

虚析构和纯虚析构共性:

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

虚构函数和纯虚析构的区别:如果是纯虚析构,该类属于抽象类,无法实例化。
纯虚函数在类内声明,类外实现。

#include
using namespace std;
#include
class Animal
{
public:
	Animal()
	{
		cout << "Animal 构造函数调用" << endl;
	}
	virtual void speak() = 0;
	//利用虚析构可以解决 父类指针释放子类对象不干净的问题
	//virtual ~Animal()
	//{
	//	cout << "Animal 析构函数调用" << endl;
	//}
	//纯虚析构
	virtual ~Animal() = 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();
	system("pause");
	return 0;
}

只有在子类中有堆区数据时,才需要用到虚析构和纯虚析构

你可能感兴趣的:(笔记,理解交流,多态,抽象类,指针,c++)