面试题答案
一键面试构造函数在继承体系下声明形式变化及要点
- 声明形式变化:
- 派生类构造函数在声明时,需要在参数列表后通过冒号
:
调用基类构造函数。例如,若有基类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; };
- 派生类构造函数先调用基类构造函数来初始化从基类继承的成员,然后再初始化自己新增的成员。
- 派生类构造函数在声明时,需要在参数列表后通过冒号
- 要点:
- 若基类没有默认构造函数,派生类构造函数必须显式调用基类的合适构造函数,否则会编译错误。
- 构造函数调用顺序是从基类到派生类。如果有多层继承,先调用最顶层基类的构造函数,依次向下,最后调用派生类自身的构造函数。
普通函数在继承体系下声明形式变化及要点
- 声明形式变化:
- 普通函数在派生类中可以重新定义(覆盖,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; } };
- 也可以定义新的普通函数,与基类函数没有重写关系。
- 普通函数在派生类中可以重新定义(覆盖,override)基类的虚函数。若基类函数声明为
- 要点:
- 对于虚函数重写,函数签名(参数列表和返回类型)必须与基类虚函数完全一致(协变返回类型除外,即派生类虚函数返回类型可以是基类虚函数返回类型的派生类型)。
- 若基类函数不是
virtual
,派生类定义同名函数只是隐藏基类函数,而不是重写,通过基类指针或引用调用时,依然调用基类的函数。
构造函数的初始化列表与在函数体中赋值的区别及适用场景
- 区别:
- 初始化顺序:
- 使用初始化列表时,成员变量按照它们在类中声明的顺序进行初始化,而不是按照初始化列表中出现的顺序。
- 在函数体中赋值,成员变量首先进行默认初始化(如果有默认构造函数),然后在函数体中进行赋值操作。
- 效率:
- 初始化列表效率更高,因为它避免了默认初始化和后续赋值的额外开销,尤其是对于没有默认构造函数或构造开销较大的类型。例如对于
std::string
类型,如果使用函数体赋值,会先默认构造一个临时对象,然后再赋值,而初始化列表直接构造。
- 初始化列表效率更高,因为它避免了默认初始化和后续赋值的额外开销,尤其是对于没有默认构造函数或构造开销较大的类型。例如对于
- 适用类型:
- 对于常量成员(
const
)和引用成员,必须使用初始化列表进行初始化,因为这些成员一旦初始化后就不能被赋值。
- 对于常量成员(
- 初始化顺序:
- 适用场景:
- 初始化列表:适用于所有成员变量的初始化,特别是对于
const
成员、引用成员以及构造开销较大的类型(如自定义类对象),能提高效率并满足特定成员的初始化要求。 - 函数体中赋值:适用于一些简单类型(如
int
、char
等),在这种情况下初始化列表和函数体赋值的效率差异不明显,且代码可能更简洁易读。但总体而言,尽量优先使用初始化列表。
- 初始化列表:适用于所有成员变量的初始化,特别是对于