1、指向子对象的父类指针
这个例子遇到的问题,用虚函数就可以解决
class Father
{
public:
~Father(){cout<<"Father析构"<<endl;}
void drive(){cout<<"爸爸会开车"<<endl;}
void Occupation(){cout<<"爸爸是工人"<<endl;}
};
class Son:public Father
{
public:
~Son(){cout<<"Son析构"<<endl;}
void drive(){cout<<"儿子会骑车"<<endl;}
void Occupation(){cout<<"儿子是学生"<<endl;}
};
int main()
{
Father *f = new Son();
//这里调用的是父类的drive函数,但是指针实际是指向Son类的,却没有调用Son的drive函数??
f->drive();
//这里father对象会被释放掉,但是Son不会被释放,造成内存泄露
delete f;
return 0;
}
2、虚函数
当类中有虚函数时,编译器就会记录虚函数的地址,并且在构造对象的时候建立好对象和虚函数的联系;这样在调用虚函数的时候就可以根据父类指针指向的对象类型来调用相匹配的虚函数,因为要判断对象类型,所以这个过程有一定的资源开销,所以虚函数比非虚函数的开销大。
(1)虚函数必须在父类定义才能实现其作用,而且虚函数特性只对引用或者指针有效。
(2)虚函数必须是基类的非静态成员函数。
(3)声明函数的时候用virtual关键字,定义的时候不需要使用virtual关键字。
(4)如果子类覆盖了父类的虚函数,不管子类中的此函数有没有virtual关键字,都被视为虚函数。
class Father
{
public:
//virtual关键字说明这个函数是虚函数
virtual void drive(){cout<<"爸爸会开车"<<endl;}
};
class Son:public Father
{
public:
//子类函数覆盖父类函数,这个函数也变成了虚函数,不管有没有virtual声明
void drive(){cout<<"儿子会骑车"<<endl;}
};
int main()
{
Father *f = new Son();
//父类的drive是虚函数,所以这里会调用子类的drive函数
f->drive();
return 0;
}
3、多态性
多态性是指同一类的对象接收相同的消息,会得到不同的结果。虚函数的特性就体现了多态性。
注:C++规定必须使用基类的指针或者引用来操作同一对象,然后当收到消息时才确定指向哪个具体的对象。
class Base
{
public:
virtual void print(){cout<<"A"<<endl;}
};
class A:public Base
{
public:
void print(){cout<<"A"<<endl;}
};
class B:public Base
{
public:
void print(){cout<<"B"<<endl;}
};
class C:public Base
{
public:
void print(){cout<<"C"<<endl;}
};
int main()
{
//同一类对象base,接受相同消息print,得到不同结果A、B、C
Base *base = new A;
base->print();
base = new B;
base->print();
base = new C;
base->print();
return 0;
}
4、虚析构函数
当创建一个派生类对象的时候,会先调用基类的构造函数构造这个对象的基类部分,然后调用派生类的构造函数构造其余部分。
一般情况下,使用虚函数的时候,我们会把派生类的地址赋给基类指针,这时如果基类的析构函数是非虚析构函数,那么在执行删除操作的时候,它只会删除基类对象,子类对象不会被删除,从而造成内存泄露。
上述情况,需要把基类的析构函数声明为虚析构函数,这样就具备了多态性,在执行删除操作的时候,首先会判断基类指针指向哪个对象,然后调用这个对象的析构函数,由于派生类的析构函数会自动调用基类的析构函数,所以整个对象会被正确销毁。
注:只要基类是虚析构函数,那么派生类的析构函数也是虚析构函数,不管派生类自己有没有声明,这一点和普通的虚函数是一样的。
class Father
{
public:
//虚析构函数
virtual ~Father(){cout<<"Father析构"<<endl;}
};
class Son:public Father
{
public:
virtual ~Son(){cout<<"Son析构"<<endl;}
};
int main()
{
Father *f = new Son();
//先析构子类,再析构父类
delete f;
return 0;
}
5、在虚函数中使用成员名限定
在虚函数中使用成员名限定可以强制解除
动态联编(即虚函数的多态性)
class Father
{
public:
//虚析构函数
virtual ~Father(){cout<<"Father析构"<<endl;}
virtual void drive(){cout<<"爸爸会开车"<<endl;}
};
class Son:public Father
{
public:
~Son(){cout<<"Son析构"<<endl;}
void drive(){cout<<"儿子会骑车"<<endl;}
};
int main()
{
Father *f = new Son();
//调用子类drive函数
f->drive();
//作用域限定符强制调用父类drive函数
f->Father::drive();
delete f;
return 0;
}