一、虚函数
1、虚函数定义
在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。
虚函数的作用是实现动态绑定的,也就是说程序在运行的时候动态的的选择合适的成员函数
2、成为虚函数的条件
要成为虚函数必须满足两点:
一就是这个函数依赖于对象调用,因为虚函数就是依赖于对象调用,因为虚函数是存在于虚函数表中,有一个虚函数指针指向这个虚表,所以要调用虚函数,必须通过虚函数指针,而虚函数指针是存在于对象中的。
二就是这个函数必须可以取地址,因为我们的虚函数表中存放的是虚函数函数入口地址,如果函数不能寻址,就不能成为虚函数。
所以呢,这些函数不能成为虚函数;
1.内联函数:我们都知道内联函数只是在函数调用点将其展开,它不能产生函数符号,所以不能往虚表中存放,自然就不能成为虚函数。
2.静态函数:定义为静态函数的函数,这个函数只和类有关系,它不完全依赖于对象调用,所以也不能成为虚函数。
3.构造函数:都知道只有当调用了构造函数,这个对象才能产生,如果把构造函数写成虚函数,这时候我们的对象就没有办法生 成。更别说用对象去调用了。所以构造函数不能成为虚函数。
那些函数可以成为虚函数呢?
普通的成员方法是可以成为虚函数的
还有析构函数,因为析构函数是为了释放对象的,所以之前我们的对象已经生成,而且析构函数可以取地址,所以可以成为虚函数。
那么,什么情况下,析构函数必须定义为虚函数。
当我们定义了一个基类指针,然后在堆上new了一个派生类的对象,让这个指针指向堆上开辟的这块内存。
-
Base *p = new Derive(10);
-
delete p;
如果基类的析构函数没有写成虚函数,delete这个基类指针,就不能释放掉堆上的派生类对象。因为delete p会调用基类的析构,你觉得调用基类的析构函数会释放掉派生类的对象吗?当然是不可能的。所以我们就要把基类的析构函数写成虚函数。写成虚函数后,当delete的时候,先会去基类调用析构函数,一看基类的析构函数是虚函数,就会自动的到派生类中调用派生类的析构函数。这时候派生类的对象就能释放了。
virtual关键字:
解决继承过程中相同成员存在多份问题.使在派生类中只保留间接基类的一份成员
#include <iostream>
using namespace std;
//如果成员方法或者成员数据重名,子类会覆盖父类的
class A
{
public:
int a;
A(int a):a(a){
cout << "A:构造函数" << endl;
}
void get_A_val(){
cout << "A:&a: " << &a << endl;
}
};
class B: virtual public A
{
public:
int a;
public:
B(int a1, int a2):A(a1),a(a2){
cout << "B:构造函数" << endl;
}
void get_B_val(){
cout << "A:&a: " << &(A::a) << endl;
cout << "B:&a: " << &a << endl;
}
};
class C:virtual public A
{
public:
int a;
public:
C(int a1, int a2):A(a1),a(a2){
cout << "C:构造函数" << endl;
}
void get_C_val(){
cout << "A:&a: " << &(A::a) << endl;
cout << "C:&a: " << &a << endl;
}
};
//D间接继承两次A,所以在D对象里面实际有两个A的备份
class D:public B, public C
{
public:
int a;
public:
//D(int a1, int a2, int a3, int a4, int a5):B(a1, a2),C(a3, a4),a(a5){}
D(int a1, int a2, int a3, int a4):A(a1),B(a2, a2),C(a3, a3),a(a4){}
void get_D_val(){
get_B_val();
get_C_val();
cout << "D:&a: " << &a << endl;
}
};
int main(int argc, char const *argv[])
{
//D d(1,2,3,4,5);
D d(1,2,3,4);
d.get_D_val();
//cout << "A::a " << d.A::a << endl; //由于D对象里面有两个A的备份,所以这里的系统不知道打印哪个A的a
/*
那么如果我们想让D对象中只包含一个A的备份,那么B,C继承的时候就要继承A的虚类,然后D构造函数传参的时候
需要加上A的构造函数,这样子D里面就会包含一个A的备份
*/
cout << "A::a " << d.A::a << endl;
cout << "B::a " << d.B::a << endl;
cout << "C::a " << d.C::a << endl;
return 0;
}
二、纯虚函数
纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
纯虚函数 eg: virtual void func(形参) = 0;
1)基类中纯虚函数不需要实现,但是派生类中一定要对该函数实现
2)类中有纯虚函数,那么该类可以称之为 "抽象类"
3)抽象类不允许实例化(纯虚函数是不完整的函数,无法调用,也无法为其分配内存空间)
纯虚函数是一种特殊的虚函数,它的一般格式如下:
class <类名>
{
virtual <类型><函数名>(<参数表>)=0;
…
};
在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
三、纯虚函数与虚函数的差异
1)纯虚函数不需要实现, 虚函数需要实现
2)纯虚函数所在的类,没有该纯虚函数的实现,相当于该功能缺失, 但是 虚函数 对应的所在类,该功能是完整的.
3)纯虚函数 所在类是抽象类
#include <iostream>
using namespace std;
class A
{
public:
A(){cout << "A:构造函数" << endl;}
~A(){cout << "A:析构函数" << endl;}
virtual void test(); //虚函数
virtual void pure_test() = 0; //纯虚函数
};
void A::test(){
cout << "A:test func" << endl;
}
class B:public A
{
public:
B(){}
~B(){}
void pure_test(){cout << "B:pure test" << endl;}
};
int main(int argc, char const *argv[])
{
// A *a = new A; //含有纯虚函数的类叫抽象类,抽象类是不可以实例化的
// a->test();
//抽象类一般用于给自己继承,提供一个接口,强迫子类重新实现纯虚函数
B *b = new B;
b->pure_test();
return 0;
}