面试题答案
一键面试1. 延迟初始化
- 原理:在真正需要使用某个成员变量时才进行初始化,而不是在对象构造时就初始化所有成员变量。
- 优点:
- 减少不必要的构造开销,尤其是对于那些在对象生命周期中可能不会用到的成员变量。
- 避免初始化顺序问题,因为成员变量在使用前才初始化,不存在先后顺序冲突。
- 缺点:
- 代码逻辑相对复杂,每次使用成员变量时都需要检查是否已初始化。
- 增加运行时开销,每次检查是否初始化会有额外的时间成本。
- 代码示例:
class ComplexClass {
private:
// 延迟初始化的成员变量
std::unique_ptr<SomeExpensiveObject> expensiveObj;
public:
ComplexClass() = default;
SomeExpensiveObject& getExpensiveObj() {
if (!expensiveObj) {
expensiveObj = std::make_unique<SomeExpensiveObject>();
}
return *expensiveObj;
}
};
2. 依赖注入
- 原理:通过构造函数或成员函数将成员变量所需的依赖传递进来,而不是在类内部自己构造依赖对象。
- 优点:
- 提高代码的可测试性,因为可以方便地传入模拟对象进行测试。
- 清晰地表明类对外部依赖的需求,增强代码的可读性。
- 避免了复杂的初始化顺序问题,因为依赖对象在外部构造好后传入。
- 缺点:
- 增加了调用者的复杂性,调用者需要负责创建和管理依赖对象。
- 如果依赖关系复杂,构造函数可能会变得冗长。
- 代码示例:
class Dependency {
public:
Dependency() = default;
// 相关操作
};
class ComplexClass {
private:
Dependency dep;
public:
ComplexClass(Dependency d) : dep(std::move(d)) {}
};
3. 静态初始化
- 原理:对于静态成员变量,在程序启动时就进行初始化,且初始化顺序是确定的(按照定义顺序)。
- 优点:
- 简单直接,不需要额外的逻辑来管理初始化顺序。
- 保证在使用前已经初始化。
- 缺点:
- 不能延迟初始化,所有静态成员变量在程序启动时就占用资源。
- 如果静态成员变量初始化开销大,可能会影响程序启动速度。
- 代码示例:
class ComplexClass {
private:
static std::vector<int> staticVec;
public:
ComplexClass() = default;
};
std::vector<int> ComplexClass::staticVec = {1, 2, 3};
4. 初始化列表
- 原理:在构造函数的初始化列表中初始化成员变量,这样可以按照成员变量在类中声明的顺序进行初始化。
- 优点:
- 避免了先默认构造再赋值的额外开销(对于复杂对象)。
- 明确初始化顺序,减少初始化顺序相关的错误。
- 缺点:
- 如果成员变量较多,初始化列表可能会很长,影响代码可读性。
- 代码示例:
class MemberA {
public:
MemberA() = default;
};
class MemberB {
public:
MemberB() = default;
};
class ComplexClass {
private:
MemberA a;
MemberB b;
public:
ComplexClass() : a(), b() {}
};
在实际应用中,需要根据具体的业务需求、性能要求和代码结构综合选择合适的初始化策略。比如,如果某个成员变量初始化开销大且可能不会用到,延迟初始化是个好选择;如果需要提高代码的可测试性和明确依赖关系,依赖注入更合适;对于简单的初始化且需要确定顺序,初始化列表是不错的方式;而对于需要在程序启动时就准备好的全局资源,静态初始化比较合适。