接上一篇文章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》
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出