MST

星途 面试题库

面试题:C++ 构造函数与普通函数声明特性深入分析

假设存在一个复杂类体系,有基类和多个派生类。请分析构造函数和普通函数在继承体系下声明形式的变化及需要注意的要点,同时说明构造函数的初始化列表与在函数体中赋值这两种初始化成员变量方式的区别及适用场景。
21.3万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

构造函数在继承体系下声明形式变化及要点

  1. 声明形式变化
    • 派生类构造函数在声明时,需要在参数列表后通过冒号 : 调用基类构造函数。例如,若有基类 Base 和派生类 Derived
    class Base {
    public:
        Base(int a) : m_a(a) {}
    private:
        int m_a;
    };
    
    class Derived : public Base {
    public:
        Derived(int a, int b) : Base(a), m_b(b) {}
    private:
        int m_b;
    };
    
    • 派生类构造函数先调用基类构造函数来初始化从基类继承的成员,然后再初始化自己新增的成员。
  2. 要点
    • 若基类没有默认构造函数,派生类构造函数必须显式调用基类的合适构造函数,否则会编译错误。
    • 构造函数调用顺序是从基类到派生类。如果有多层继承,先调用最顶层基类的构造函数,依次向下,最后调用派生类自身的构造函数。

普通函数在继承体系下声明形式变化及要点

  1. 声明形式变化
    • 普通函数在派生类中可以重新定义(覆盖,override)基类的虚函数。若基类函数声明为 virtual,派生类函数在重写时需使用 override 关键字(C++11 及以后)以显式表明重写意图。例如:
    class Base {
    public:
        virtual void func() { std::cout << "Base::func" << std::endl; }
    };
    
    class Derived : public Base {
    public:
        void func() override { std::cout << "Derived::func" << std::endl; }
    };
    
    • 也可以定义新的普通函数,与基类函数没有重写关系。
  2. 要点
    • 对于虚函数重写,函数签名(参数列表和返回类型)必须与基类虚函数完全一致(协变返回类型除外,即派生类虚函数返回类型可以是基类虚函数返回类型的派生类型)。
    • 若基类函数不是 virtual,派生类定义同名函数只是隐藏基类函数,而不是重写,通过基类指针或引用调用时,依然调用基类的函数。

构造函数的初始化列表与在函数体中赋值的区别及适用场景

  1. 区别
    • 初始化顺序
      • 使用初始化列表时,成员变量按照它们在类中声明的顺序进行初始化,而不是按照初始化列表中出现的顺序。
      • 在函数体中赋值,成员变量首先进行默认初始化(如果有默认构造函数),然后在函数体中进行赋值操作。
    • 效率
      • 初始化列表效率更高,因为它避免了默认初始化和后续赋值的额外开销,尤其是对于没有默认构造函数或构造开销较大的类型。例如对于 std::string 类型,如果使用函数体赋值,会先默认构造一个临时对象,然后再赋值,而初始化列表直接构造。
    • 适用类型
      • 对于常量成员(const)和引用成员,必须使用初始化列表进行初始化,因为这些成员一旦初始化后就不能被赋值。
  2. 适用场景
    • 初始化列表:适用于所有成员变量的初始化,特别是对于 const 成员、引用成员以及构造开销较大的类型(如自定义类对象),能提高效率并满足特定成员的初始化要求。
    • 函数体中赋值:适用于一些简单类型(如 intchar 等),在这种情况下初始化列表和函数体赋值的效率差异不明显,且代码可能更简洁易读。但总体而言,尽量优先使用初始化列表。