为什么C++至今仍让资深开发者感到畏惧?
C++是一种赋予您对内存完全控制权的语言,但前提是您别忘了加分号,或者不小心在构造函数中调用了虚函数。C++面试以其严苛著称:您可能会被问到位域、虚表机制以及std::move的细节。如果您正在准备技术面试,这篇文章就是您的检查清单。我们将剖析最棘手的主题,这些几乎肯定会在面试中出现,并展示如何回答才能避免失败。
在教育门户网站PythonLib上,我们经常讨论如何从Python过渡到C++,反之亦然。但今天,我们只谈纯C++。没有任何妥协。
1. 虚函数和VTable:如何避免陷阱
问题:“在基类构造函数中调用虚函数会发生什么?”——这是经典问题。70%的候选人回答错误。让我们来剖析一下。
为什么这很危险?
当您在基类构造函数中调用虚函数时,派生类对象尚未创建。此时虚表(VTable)指向基类的方法,而非派生类的方法。因此,多态性无法工作。
#include <iostream>
class Base {public: Base() { callFoo(); } // 在构造函数中调用 virtual void foo() { std::cout << "Base::foo"; } void callFoo() { foo(); }};
class Derived : public Base {public: Derived() {} virtual void foo() override { std::cout << "Derived::foo"; }};
int main() { Derived d; // 输出:Base::foo,而非Derived::foo return 0;}结论:永远不要在构造函数和析构函数中调用虚函数。如果面试官问到,请自信地解释VTable机制。
2. 智能指针:RAII与内存管理
任何严肃的C++面试都离不开这个主题。关于std::unique_ptr、std::shared_ptr和std::weak_ptr的问题是基础。但经常也会问到循环引用和std::enable_shared_from_this。
如何避免shared_ptr导致的内存泄漏?
假设有两个对象通过shared_ptr相互引用。引用计数永远不会归零——内存会泄漏。解决方案:其中一个指针应为weak_ptr。
#include <memory>#include <iostream>
struct Node { std::shared_ptr<Node> next; std::weak_ptr<Node> prev; // 弱引用,避免循环 ~Node() { std::cout << "Node destroyed"; }};
int main() { auto a = std::make_shared<Node>(); auto b = std::make_shared<Node>(); a->next = b; b->prev = a; // weak_ptr不会增加引用计数 // 离开作用域时,两个节点都会被正确销毁 return 0;}在面试中,务必提及RAII(资源获取即初始化)概念——这是C++的哲学,它将资源的生命周期与对象的生命周期绑定。
3. 移动语义与std::move:魔法还是陷阱?
典型问题:“std::move做什么?”回答“移动对象”是错误的。std::move只是将类型转换为右值引用。真正的移动发生在移动构造函数或移动赋值运算符中。
面试示例
#include <string>#include <vector>#include <iostream>
class MyString { std::string data;public: MyString(const std::string& s) : data(s) {} // 移动构造函数 MyString(MyString&& other) noexcept : data(std::move(other.data)) { std::cout << "Move constructor c