小米二面
在牛客网上的C++面筋题。
1. 虚函数和纯虚函数的区别
因为写代码时不能在一开始就确定被调用的是基类的函数,还是哪个派生类的成员函数,所以C++通过虚函数实现多态,即在基类中用virtual声明,父类可以引用子类对象(如Human* phuman1 = new Men;父类类型指针phuman指向子类对象),子类成员函数可以重写父类方法(函数)。
虚函数的核心理念就是通过基类访问派生类定义的函数:如在父类中用virtual声明某成员函数为虚函数,父类指针既能调用父类,也能调用子类中的同名同参成员函数。如下代码:
Human* phuman = new Men(); //男人喜欢吃米饭,调用的是子类Men类的eat函数 puhuman -> eat(); //人类吃各种粮食,调用的是Human类的eat函数 phuman->Human::eat(); delete phamn;
当存在继承关系时,父类的析构函数应当是虚函数(即虚析构函数),将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数;
如果父类析构函数没设置为虚函数,则delete指向子类对象的父类指针时,不会执行子类的析构函数释放内存,造成内存泄漏。
定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。纯虚函数没有函数体,同时在基类声明时需要在函数原型后加上=0。如上所示:
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。所以出现【抽象类】(含有纯虚函数的类)就规定不能实例化为对象。
纯虚函数是为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。
【纯虚函数的实现原理】
纯虚函数也一定是某个类的成员函数,含有纯虚函数的类叫做抽象类。在C++中,抽象类无法实例化对象。如果我们定义了Shape这样的类,那么,Shape类当中,因为有虚函数和纯虚函数,所以它一定有一个虚函数表,也就一定有一个虚函数表指针。在虚函数表当中,如果是纯虚函数,那么虚函数表中的函数指针值为0;如果是普通的虚函数,那就肯定是一个有意义的值。
一个虚函数的最简单的栗子:
#include <iostream> using namespace std; class A{ public: virtual void foo() { cout<<"A::foo() is called"<<endl; } }; class B:public A { public: void foo() { cout<<"B::foo() is called"<<endl; } }; int main(void) { A *a = new B(); a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的! system("pause"); return 0; }
2. 构造函数能不能是虚函数
不能。。C++在编译期间,就能确定你要创建的对象的具体类型,而这个具体类型包含了什么,继承了什么在编译期间也是明确的,即构建一个对象,必须知道具体的类型信息,构造什么也都是明确的,根本没必要存在虚构造函数。虚函数的存在是因为编译期间没法确定具体调用对象,才会有虚函数,虚函数表这么个东西。
解决方案:Bjarne建议用factory pattern,也就是为每一个要构建的类型再创建一个对应的factory,把问题放到factory的make方法中去解决。这也是C++中的通用解决方案。