day7 继续努力学习

多态

多态是C++面向对象三大特性之一  三大特性是:封装 、 继承、 多态

多态分为两类

静态多态:函数重载运算符重载 属于静态多态 , 复用函数名

动态多态 : 派生类虚函数实现运行时多态

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

静态多态的函数地址早绑定 - 编译阶段确定函数地址

动态多态的函数地址晚绑定 - 运行阶段确定函数地址

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

	}
	void dospeak() {}
};
void dospeak(Animal &animal) //Animal &animal = cat;父类的指针可以指向子类
{
	animal.speak();//speak相当于多种形态,传什么是什么
}
void test01() 
{
	Cat cat;
	dospeak(cat);
}

dospeak函数中,animal.speak() 调用了 speak 方法。但是由于 speak 方法在 Animal 类中定义为非虚函数,因此在编译时就确定了调用的是 Animal 类的 speak 方法。这就是所谓的 静态绑定(或称早期绑定) 。

如果想执行让cat说话,那么这个函数地址就不能提前绑定,需要在运行阶段绑定,即地址晚绑定。

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

class Cat : public Animal 
{
public:
	//重写概念:函数返回值类型 函数名 参数列表 完全相同 子类的virtual可以不写
	void speak() 
	{
		cout << "小猫在说话" << endl;
	}
};

void dospeak(Animal &animal) //Animal &animal = cat;父类的指针可以指向子类
{
	animal.speak();//speak相当于多种形态,传什么是什么
}
void test01() 
{
	Cat cat;
	dospeak(cat);
}

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

使用: 父类的指针或者引用 指向子类的对象

现在,我们来剖析动态多态的原理。首先,父类中创建了虚函数,其内部结构包含一个虚函数指针(vfptr),该指针指向虚函数表(vftable)。虚函数表中记录着虚函数的地址,例如 &Animal::speak()。
接下来,创建一个 Cat 类。如果 Cat 没有重写父类的虚函数,那么子类的虚函数表仍然指向父类的虚函数地址,即 &Animal::speak()。当 Cat 重写了父类的虚函数时,子类的虚函数表会更新,指向子类的虚函数地址,即 &Cat::speak()。此时,父类的指针或引用指向子类对象,就会发生多态。下面附上老师上课时的演示,并且我们可以通过开发者人员 查看对象模型模型 ,也可以自己了解多态的底层含义。

纯虚函数和抽象类

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

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

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

当类中有了纯虚函数 , 这个类也称为抽象类  强制子类必须重写。

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

class Base 
{
public:
	//当类中有了纯虚函数 , 这个类也称为抽象类  强制子类必须重写
	virtual void func() = 0; //要在虚函数 的基础上
};
class Son :public Base 
{
public:
	void func() 
	{
		cout << "func函数调用" << endl;
	}

};
void test01() 
{
	//Base b;栈区
	//new Base;//堆区   抽象类无法实例化对象
	//Son s1;//子类无重写纯虚函数 也无法实例化对象
	Son s1;//子类必须要重写纯虚函数
}

在test01()中前面三行代码,第一行代码和第二行代码无法运行成功是因为 父类中含有纯虚函数,所以无法实例化对象,不管是在堆区还是栈区。第三行代码是在子类中,没有重写父类的纯虚函数,所以系统也会认为其是 抽象类,无法实例化对象。只有在子类中重写父类的虚函数,才可以实例化。

void test01() 
{
	/*Base b;栈区
	new Base;*///堆区   抽象类无法实例化对象
	//Son s1;//子类无重写纯虚函数 也无法实例化对象
	Son s1;//子类必须要重写纯虚函数
	Base* base = new Son;
	base->func();
	delete base;
	base = new Son2;//new 哪个对象就调用哪个对象的函数
	base->func();
	delete base;
}

虚析构和纯虚析构

如果一个类含有纯虚析构,该类属于抽象类无法实例化对象

虚析构语法  virtual ~类名(){}

纯虚析构语法 virtual ~类名()=0;//需要类外写函数体  Animal::~Animal(){}

class Animal 
{
public:
	Animal() 
	{
		cout << "animal的构造函数调用" << endl;
	}
	/*~Animal() 
	{
		cout << "animal的析构函数调用" << endl;
	}
	virtual void speak() = 0;*/
	//利用虚析构可以解决 父类指针释放子类对象时不干净的问题
	//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);//创建在堆区 ,堆区要在析构释放
	}
	~Cat() //会发现cat的析构函数没有调用 需要在父类的析构前面加上virtual
	{
		if (m_name != NULL) 
		{
			cout << "cat的析构函数调用" << endl;
			delete m_name;
			m_name = NULL;
		}
	}


	virtual void speak() 
	{
		cout <<*m_name<< "小猫在说话" << endl;
	}
	string *m_name;
};
void test01() 
{
	Animal* animal = new Cat("TOM");
	animal->speak();
	// 父类的指针在析构的时候不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄露
	delete animal;
}

这里为什么要用到虚析构和纯虚析构呢,是因为,在子类中存在堆区数据,需要通过析构函数来释放,但是如果没有使用到虚析构或者纯虚析构,我们在运行的时候发现,不会调用到子类的析构函数,那也就是说子类析构函数中的函数体没有执行,导致堆区内存无法释放,造成数据泄露。

总结:虚析构或纯虚析构就是用来解决通过父类指针释放子类对象。

如果子类中没有堆区数据,可以不写为虚析构或者纯虚析构。

拥有纯虚析构函数的类也属于抽象类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值