一、基础概念题
- 什么是虚函数?作用是什么?
定义:用virtual关键字声明的成员函数,允许子类重写(override)以实现多态。
作用:
实现运行时多态(动态绑定),通过基类指针/引用调用子类重写的函数。
支持接口抽象(如纯虚函数定义抽象类)。
示例:
cpp
class Base {
public:
virtual void show() { cout << “Base” << endl; }
};
class Derived : public Base {
public:
void show() override { cout << “Derived” << endl; } // C++11推荐用override
};
Base* obj = new Derived();
obj->show(); // 输出"Derived"(动态绑定) - 虚函数与普通函数的区别?
特性 虚函数 普通函数
绑定时机 运行时(动态绑定) 编译时(静态绑定)
性能开销 稍高(需查虚函数表) 无额外开销
是否可重写 必须用override(C++11后) 不可重写(除非隐藏)
构造函数/析构函数 析构函数通常应为虚函数 无特殊要求
二、实现原理题 - 虚函数是如何实现的?
虚函数表(vtable):
每个包含虚函数的类有一个虚函数表,存储虚函数地址。
对象内存中包含一个指向虚函数表的指针(vptr)。
调用虚函数时,通过vptr找到虚函数表,再根据索引调用对应函数。
示例:
cpp
class Base {
public:
virtual void f() {}
virtual void g() {}
};
// Base的虚函数表布局:
// [ &Base::f, &Base::g ] - 为什么构造函数不能是虚函数?
原因:
对象构造时,vptr尚未初始化(子类构造前需先构造基类部分)。
虚函数依赖vptr,若构造函数为虚函数,无法确定调用哪个类的实现。
替代方案:使用工厂模式或静态成员函数创建对象。 - 为什么析构函数通常要声明为虚函数?
问题:若基类指针指向子类对象,删除时若析构函数非虚,仅调用基类析构函数,导致子类资源泄漏。
示例:
cpp
class Base {
public:
~Base() { cout << “Base destructor” << endl; } // 非虚析构函数
};
class Derived : public Base {
public:
~Derived() { cout << “Derived destructor” << endl; }
};
Base* obj = new Derived();
delete obj; // 仅调用Base::~Base(),Derived部分未释放
解决:将基类析构函数声明为virtual,确保调用链正确。
三、应用场景题 - 何时需要使用虚函数?
场景:
需要通过基类接口操作不同子类对象(如插件系统、图形渲染)。
实现策略模式或模板方法模式。
示例:
cpp
class Shape {
public:
virtual double area() = 0; // 纯虚函数(抽象类)
};
class Circle : public Shape {
public:
double area() override { return 3.14 * radius * radius; }
}; - 虚函数能否是内联函数?
答案:可以声明为内联,但编译器通常忽略内联请求(动态绑定需运行时确定函数地址)。
例外:若通过对象(而非指针/引用)调用虚函数,且编译器能确定具体类型,可能内联。 - 虚函数能否是静态函数?
答案:不能。静态函数属于类而非对象,无this指针,无法支持多态。
四、陷阱与高级题 - 虚函数调用被“截断”的情况?
问题:若子类未重写虚函数,仍调用基类实现。
示例:
cpp
class Base {
public:
virtual void foo() { cout << “Base::foo” << endl; }
};
class Derived : public Base {
// 未重写foo()
};
Derived d;
d.foo(); // 调用Base::foo(无截断)
Base* b = &d;
b->foo(); // 仍调用Base::foo(若Derived未重写) - 虚函数与final关键字(C++11)
作用:禁止子类重写虚函数。
示例:
cpp
class Base {
public:
virtual void foo() final {} // 禁止子类重写
};
class Derived : public Base {
public:
void foo() override {} // 编译错误:尝试重写final函数
}; - 虚函数与多继承的虚函数表
问题:多继承时,对象可能包含多个vptr(每个基类一个),虚函数调用需正确选择虚函数表。
示例:
cpp
class Base1 {
public:
virtual void f() {}
};
class Base2 {
public:
virtual void f() {}
};
class Derived : public Base1, public Base2 {
public:
void f() override { /* 重写哪个Base的f? */ }
};
解决:使用作用域运算符明确指定重写哪个基类的虚函数(如Base1::f())。
五、综合代码题 - 以下代码的输出是什么?为什么?
cpp
class Base {
public:
virtual void func() { cout << “Base” << endl; }
};
class Derived : public Base {
public:
void func() override { cout << “Derived” << endl; }
};
int main() {
Base* b = new Derived();
b->func(); // 输出?
delete b;
return 0;
}
答案:输出Derived。
原因:func()是虚函数,通过基类指针调用时动态绑定到子类实现。 - 如何避免虚函数调用的性能开销?
方法:
使用CRTP(奇异递归模板模式)实现静态多态。
在性能关键路径中,若类型可确定,直接调用具体类函数(绕过虚函数表)。
示例(CRTP):
cpp
template
class Base {
public:
void interface() {
static_cast<T*>(this)->implementation(); // 静态绑定
}
};
class Derived : public Base {
public:
void implementation() { cout << “Derived” << endl; }
};
总结
核心考点:虚函数表机制、多态实现、析构函数虚化、override/final关键字。
准备建议:
手动绘制虚函数表示意图。
编写代码验证虚函数调用行为。
理解虚函数与性能、内存布局的关系。

1083

被折叠的 条评论
为什么被折叠?



