关于C++虚函数,对某些细节的理解不深入,可能导致我们的程序无法按预期结果运行,或是表明我们对其基本原理理解不够透彻。本文详细解答以下几个问题:实现多态,基类函数忘记写virtual会怎么样?虚函数的默认参数可以重载吗?纯虚函数真的不能有实现吗?析构函数可以是纯虚函数吗?
虚函数是什么?
- 虚函数是在基类中使用关键字virtual声明的函数,它在派生类中可以被重写,且在运行时根据对象的类型来调用相应的函数。
- 虚函数的作用是实现多态。多态是指同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。多态分为编译时多态和运行时多态,编译时多态是指函数重载,运行时多态是指虚函数。
- 虚函数动态绑定的实现原理:每个含有虚函数的类都有一个虚函数表,虚函数表中存储着虚函数的地址,当基类指针绑定了类对象后,通过类对象虚表指针指向的虚函数表找到虚函数的地址,然后调用对应的虚函数。
实现函数多态,基类中忘记写virtual会怎么样?
如果忘记在基类中写virtual关键字,那么就不会实现多态,而是静态绑定。因此以下例子中,调用的是基类的函数,而不是派生类的函数。为了避免这种情况发生,C++11中引入了override关键字,可以在派生类中对需要重写的函数进行override声明,这样如果派生类实际并没有成功重写基类的虚函数,编译器就会报错。
#include<iostream>
class Base {
public:
void func() {
std::cout << "Base func" << std::endl;
}
};
class Derived : public Base {
public:
void func(){
std::cout << "Derived func" << std::endl;
}
};
int main() {
Base *b = new Derived();
b->func();
delete b;
return 0;
}
输出:
虚函数的默认参数可以重载吗?
虚函数的默认参数不可以重载,因为虚函数的调用是在运行时确定的,而默认参数是在编译时确定的。从设计角度来说,这样做是合理的,如果虚函数的默认参数可以重载,那么在运行时,编译器就需要在运行时选择合适的默认参数,这样就会增加编译器的复杂度。因此,虚函数的默认参数不可以重载。
#include<iostream>
class Base {
public:
virtual void func(int i = 0)=0;
};
void Base::func(int i) {
std::cout << "Base func: " << i << std::endl;
}
class Derived : public Base {
public:
void func(int i = 2) {
std::cout<< "Derived func: " << i << std::endl;
}
};
int main() {
Base *b = new Derived();
b->func();
delete b;
return 0;
}
输出
纯虚函数是什么?
- 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法,否则编译失败。
- 纯虚函数的声明格式为:virtual 函数类型 函数名(参数表) = 0;,其中“=0”是纯虚函数的标志,它告诉编译系统,该虚函数没有实现。
- 含有纯虚函数的类是抽象类,抽象类是不能实例化的。
纯虚函数真的不能有实现吗?其实不然,纯虚函数是可以有自己的实现的,但是这个实现是在类外部实现的,而不是在类内部实现的。详见下面代码实例。
析构函数可以是纯虚函数吗?
我们知道,析构函数是在对象销毁时调用的,而纯虚函数是没有实现的虚函数,含有纯虚函数的类是抽象类,那么,析构函数可以是纯虚函数吗?
#include<iostream>
class Base {
public:
virtual ~Base() = 0;
};
Base::~Base() {
std::cout << "Base destructor" << std::endl;
}
class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
Base *b = new Derived();
delete b;
return 0;
}
输出:
结论:析构函数可以是纯虚函数,含有纯虚析构函数的类无法实例化。因为析构函数是在派生类析构函数调用之后才调用基类析构函数,而派生类析构函数在派生类对象销毁时才会调用,而派生类对象的销毁必须要调用基类的析构函数,因此基类析构函数必须要在类外提供定义。
如果不定义纯虚析构函数的实现,则会链接失败,报以下错误。
为什么在类外部实现就可以呢?因为含有纯虚函数的类是抽象类,抽象类是不能实例化的,但是抽象类可以有指针和引用,因此,我们可以通过抽象类的指针或引用调用纯虚函数,但是如果纯虚函数没有实现,那么就会出现问题,因此,我们需要在类外部实现纯虚函数。
纯虚函数可以被显示调用吗?
派生类的成员函数可以通过限定函数id自由调用基类在类外定义的纯虚函数。
#include<iostream>
class Base {
public:
virtual void func() = 0;
};
void Base::func() {
std::cout << "Base func" << std::endl;
}
class Derived : public Base {
public:
void func() {
Base::func();
std::cout << "Derived func" << std::endl;
}
};
int main() {
Base *b = new Derived();
b->func();
delete b;
return 0;
}
输出: