C++知识点51——虚函数与纯虚函数(下)

接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109957146

10.练习

示例

class base
{
public:
	base(){cout<<__func__<<endl;}
	~base(){cout<<__func__<<endl;}
	virtual void vfunc() {cout<<__func__<<"in base2"<<endl;}
};

class derive:public base
{
public:
	derive(){cout<<__func__<<endl;}
	~derive(){cout<<__func__<<endl;}
	void vfunc(int a) { cout<<__func__<<"in derive"<<endl;}
	virtual void vfunc2() {cout<<__func__<<"in derive"<<endl;}
};

class derive2:public derive
{
public:
	derive2(){cout<<__func__<<endl;}
	~derive2(){cout<<__func__<<endl;}
	void vfunc() {cout<<__func__<<"in derive2"<<endl;}
	void vfunc(int a) { cout<<__func__<<"in derive2"<<endl;}
	void vfunc2() {cout<<__func__<<"in derive2"<<endl;}
};

int main(int argc, char const *argv[])
{
	base b1;
	derive d1;
	derive2 d2;
	base *pb1=&b1;
	base *pb2=&d1;
	base *pb3=&d2;
	pb1->vfunc();
	pb2->vfunc();
	pb3->vfunc();

	derive *pd1=&d1;
	derive2 *pd2=&d2;
//	bp2->vfunc2();
	pd1->vfunc2();
	pd2->vfunc2();

	base *pb4=&d2;
	//pb4->vfunc(10);
 	pd1->vfunc(10);
	pd2->vfunc(10);
	return 0;
}

上面一个有三个类base,derive,derive2,基类base中只定义了虚函数vfunc,base的子类derive中,定义了vfunc和vfunc2,但是vfunc和基类中的vfunc的形参列表不同,且子类中的vfunc没有override关键字修饰,所以这两个vfunc没有任何关系,而derive的子类derive2中重写了vfunc,且参数和base中的vfunc一致,所以,main中的前三个调用会中的前两个会调用base中的vfunc,而第三个调用会调用derive2中的vfunc

而vfunc2没有在基类base中定义,又因为只能通过静态类型解析能调用的成员,所以不能通过基类的指针调用vfunc2,所以中间三个的第一个调用是错误的,而vfunc2是个虚函数,所以通过中间三个的后两个调用会分别动态调用derive和derive2中的vfunc2

同样,因为基类中没有定义带参数的vfunc,所以不能通过基类的指针调用带参数的vfunc,所以最后三个的第一个调用是错误的,而带参数的vfunc不是虚函数,所以是静态调用,所以最后两个会静态调用derive和derive2中的带参数的vfunc

执行结果如下

上面两个错误的调用也说明,如果基类中没有定义对应的虚函数,只在子类中定义对应的虚函数,那么即使通过基类的指针指向子类,也无法调用子类中的虚函数,无法使用虚机制,因为基类的静态类型无法在基类中找到要调用的成员

 

11.基类的析构函数通常应该是virtual的

示例

class base3
{
public:
	base3(){cout<<__func__<<endl;}
	virtual ~base3(){cout<<__func__<<endl;}
};

class derive3:public base3
{
public:
	derive3(){cout<<__func__<<endl;}
	~derive3(){cout<<__func__<<endl;}
};

int main(int argc, char const *argv[])
{
	base3 *bp=new derive3();
	delete bp;
	return 0;
}

上述代码的输出结果非常正常,但是如果要把base3中的析构函数的virtual关键字去掉,就会变得不正常

从输出结果上看,不仅弹出了了一个警告(销毁含有非虚析构函数的多态类将导致不确定性为),而且还没有调用子类的析构函数,有可能在子类对象析构的时候导致内存泄漏

原因:

1.因为继承的关系,静态类型和动态类型很有可能不一致。此时,delete基类指针时需要正确调用对应版本的析构函数,如果动态类型是基类,那么只调用基类的析构函数即可,此时基类的析构函数是不是虚的无所谓。

2.如果基类指针指向一个动态分配的子类对象,此时,如果基类的析构函数非虚,那么delete 基类指针时,不会触发虚机制,就只会释放子类对象中的基类的部分,导致子类对象销毁不彻底,子类中的成员无法回收,出现内存泄露。如果基类的析构函数是virtual的,那么在delete基类指针销毁子类对象时,会触发虚机制,会先动态调用子类的析构函数,而子类部分销毁后,根据派生类中析构函数的调用顺序,会接着调用基类的析构函数。这样,当基类指针指向一个动态分配的子类对象时,就可以正确的释放动态分配的子类对象。

 

12.在构造函数和析构函数中调用虚函数

因为一个子类对象在创建时,先创建基类部分,然后再创建子类部分,如果在基类的构造函数中调用虚函数,此时子类的特有部分还没有构建,因此虽然会动态调用虚函数,但是调用的是基类中的虚函数,达不到多态的效果

同理,一个子类对象在销毁时,先销毁子类部分,调用子类的析构函数,然后再销毁基类部分,调用基类的析构函数,如果在基类的析构函数中调用虚函数,此时子类的特有部分已经被销毁,因此虽然会动态调用虚函数,但是调用的是基类中的虚函数,也达不到多态的效果

示例

class base3
{
public:
	base3(){cout<<__func__<<endl;vfunc2();}
	virtual ~base3(){cout<<__func__<<endl;vfunc2();}
	virtual void vfunc2() {cout<<__func__<<"in base2"<<endl;}
};

class derive3:public base3
{
public:
	derive3(){cout<<__func__<<endl;}
	~derive3(){cout<<__func__<<endl;}
	void vfunc2() {cout<<__func__<<"in derive3"<<endl;}
};

int main(int argc, char const *argv[])
{
	base3 *bp=new derive3();
	delete bp;
	return 0;
}

 

二、纯虚函数与抽象类

1.纯虚函数在虚函数声明处的最后加个=0就可以将一个虚函数变为纯虚函数,纯虚函数通常不用(但是可以)定义,如果定义的话,需要将纯虚函数定义在类的外部。

2.含有纯虚函数的类就是抽象类。因为含有纯虚函数,所以不能直接定义纯虚类的对象,只能定义纯虚类的指针或者引用,然后指向一个继承该类的子类对象

示例

class animal
{
public:
	animal():legs(2), wing(false){cout<<__func__<<endl;}
	virtual ~animal(){cout<<__func__<<endl;}
	virtual void eat()=0;
	virtual void drink()=0;
	virtual void howl()=0;

	int legs;
	bool wing;
};

void animal::eat()
{
	cout<<__func__<<endl;
}

class duck:public animal
{
public:
	duck():animal(){cout<<__func__<<endl;}
	~duck(){cout<<__func__<<endl;}
    void eat(){cout<<__func__<<"in duck"<<endl;}
	void drink(){cout<<__func__<<"in duck"<<endl;}
	void howl(){cout<<__func__<<"in duck"<<endl;}
};

int main(int argc, char const *argv[])
{
	duck d;
	animal &ra=d;
	ra.howl();
	return 0;
}

和虚函数一样,纯虚函数也能实现多态,但是子类通常应该(可以不)实现自定义版本的纯虚函数,否则继承纯虚类后,如果有些纯虚函数没有重新实现,继承到子类里依然是纯虚函数,子类依然是个纯虚类

 

3.有了虚函数为啥还要纯虚函数

虚函数能实现多态,纯虚函数也能实现多态,那么纯虚函数的作用体现在哪里呢?

除了多态,还有个很重要的原因就是很多情况下,基类本身生成对象是不合情理的。例如,形状作为一个基类可以派生出三角形,正方形,梯形等,但形状本身生成对象明显不合常理。所以纯虚类一般用来描述定义比较抽象的概念,比如动物,形状,灯,树木等等并定义一堆纯虚函数(接口),然后再由一些表示具体概念的类来继承该抽象类进行具体实现。

 

参考

《C++ Primer》

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值