天天看点

C/C++面试题的知识点(13)

(1)

一个对象访问普通成员函数和虚函数哪个更快?

答:访问普通成员函数较快。原因是普通成员函数的地址,在编译阶段已经确定,访问时直接寻址;而虚函数在调用时,首先需要找到虚函数表指针,然后在虚函数表中寻找虚函数地址,这个过程花费时间导致访问比普通函数速度慢一些。

(2)

在什么情况下,析构函数需要是虚函数?

当我们delete一个动态分配的对象的指针时,将执行析构函数。如果该指针指向继承体系中的某个类型,则有可能出现指针的静态类型与被删除对象的动态类型不符的情况。例如:

class Base{
    ...
    virtual ~Base() = default;
};
class Derived : public Base{
    ... 
};
Base *p = new Derived;
delete p;
           

这样的话,编译器必须清楚应该执行子类的析构函数。通过将父类的析构函数定义为虚函数就可以保证执行正确的析构函数版本。

继承关系对基类拷贝控制的最直接的影响是基类通常应该定义一个虚析构函数,这样就可以动态分配继承体系中的对象了。

(3)

内联函数、构造函数、静态成员函数可以是虚函数吗?

虚函数在运行时确定绑定的对象的动态类型。

内联函数在编译阶段展开,为了减少函数调用时的代价。inline关键字作为提示符告诉编译器此函数作为内联函数希望在编译阶段展开,但是,编译器并不一定要展开。所以可以声明为虚函数。亲测可以。

class Base {
public:
    ...
    virtual inline void fun() { cout << "base::hello" << endl; }
};

class Derived :public Base {
public:
    ...
    void fun() { cout << "derived::hello" << endl; }
};

Derived d;
d.fun();
           

构造函数不能是虚函数,因为调用虚函数需要虚函数表指针,执行构造函数之前是没有虚函数表指针的。

静态成员函数以类为单位,与具体对象无关,无法有自己的虚函数表指针。虚函数是面向对象的动态绑定的。

(4)

构造函数中可以调用虚函数吗?

语法上可以,但没有实际意义。不能起到多态效果,失去了虚函数的意义。

生成一个子类对象,会先调用父类的构造函数,然后调用子类的构造函数。和普通函数没区别。

(5)

C++中虚继承的作用及底层原理?
class A {
public:
    int a;
};

class B :public A {

};

class C :public A {

};

class D :public B, public C {

};

D d;
d.a;    //error, 不明确
           

D类对象中有俩a副本,调用时编译器有些迷糊。“菱形继承”问题:从不同路径继承来的同名的数据成员,在内存中有不同的拷贝,造成数据不一致问题。

解决:虚继承,将共同基类设置为虚基类。

class A {
public:
    int a;
};

class B :public virtual A {

};

class C :public virtual A {

};

class D :public B, public C {

};

ok!
           

底层实现与编译器有关。通常可使用虚基类指针实现,各对象中仅保存一份父类的对象,多继承时通过虚基类指针引用该公共对象,避免菱形继承问题中的二义性问题。

虚继承:在继承定义中包含了virtual关键字的继承关系;

虚基类:在虚继承体系中通过virtual继承而来的基类,我们说A称之为B和C的虚基类,不说A是个虚基类。

虚拟继承很少用到,主要是C++中多重继承不被推荐,也并不常用。一旦离开多重继承,虚拟继承失去存在必要。