[C++面试] 基础题 11~20
11、为什么构造函数可以重载,析构函数不可以?
构造函数用于对象的创建,不同场景可能需要不同的初始化参数。
class MyClass {
public:MyClass() { /* 默认初始化 */ } // 无参构造函数MyClass(int x) { /* 用x初始化 */ } // 单参数构造函数MyClass(int x, double y) { /* 复杂初始化 */ } // 双参数构造函数
};
语法上:构造函数与类名相同且无返回值,但允许参数列表不同。
C++标准规定析构函数必须无参数且唯一。
析构函数在对象销毁时由编译器自动调用,且无参数。若允许重载,编译器无法确定应调用哪个版本。析构函数的核心职责是释放资源(如内存、文件句柄等),通常需要统一的清理逻辑。若需差异化处理,应通过成员变量状态控制,而非重载
class FileHandler {
private:bool isOpen = false;
public:~FileHandler() {if (isOpen) fclose(filePtr); // 根据状态统一处理}
};
12、 构造函数可以是虚函数吗?为什么?
对象尚未完全构造
虚函数的调用通过对象的虚指针(vptr)和虚表(vtable)实现动态绑定。
在构造函数执行时,对象的虚指针尚未初始化(仅在基类构造函数完成后才指向派生类的虚表)。
设计逻辑冲突
构造函数从基类到派生类依次执行。
若基类构造为虚函数,派生类可能会尝试调用自身版本的构造函数(此时派生类对象尚未完全构造),导致逻辑混乱。
语言规范限制
C++标准明确规定构造函数不能声明为virtual
,编译器会直接报错
13、 析构函数何时必须是虚函数?为什么?
必须声明为虚函数的场景:通过基类指针删除派生类对象时
class Base {
public:virtual ~Base() { cout << "Base destructor" << endl; } // 虚析构函数
};class Derived : public Base {
private:int* data;
public:Derived() { data = new int[10]; }~Derived() override {delete[] data; // 派生类资源清理cout << "Derived destructor" << endl;}
};// 使用基类指针创建和删除派生类对象
Base* ptr = new Derived();
delete ptr; // 若Base析构非虚,则仅调用Base::~Base(),导致Derived::data未释放
声明基类析构函数为虚函数后,delete
基类指针时会先调用派生类析构函数,再调用基类析构函数,保证资源被完整释放。
14、默认构造函数与无参构造函数
默认构造函数是指无需参数即可调用的构造函数。包括:
- 编译器自动生成的构造函数:当类中未定义任何构造函数时,编译器隐式生成一个无参构造函数(称为 “合成默认构造函数”)。
- 用户显式定义的无参构造函数:如
Data() {}
或Data() = default
。
// 若类中定义了任何构造函数(无论是否带参数),编译器不会自动生成默认构造函数。因此,以下两种情况都会导致默认构造函数缺失
struct Data1 {Data1(int x) {} // 带参构造函数 → 无默认构造函数
};struct Data2 {Data2() {} // 用户显式定义无参构造函数 → 编译器不再生成默认构造函数
};
默认构造函数是编译器自动生成或用户显式定义的无参数构造函数,或者所有参数均有默认值的构造函数(如
MyClass(int x = 0)
)。其核心特点是无需传递参数即可调用。class MyClass { public:MyClass(int x = 0, double y = 1.0) { /* 参数均有默认值 */ } }; // 此时,MyClass obj; 或 MyClass obj{}; 均可调用该构造函数
无参构造函数(Parameterless Constructor)
无参构造函数是参数列表为空的构造函数,属于默认构造函数的一种特例。
- 用户显式定义的、参数列表为空的构造函数。(如
MyClass() {}
) - 用户未定义任何构造函数时,编译器自动生成隐式无参构造函数
- 必须由用户手动编写,编译器不会自动生成(除非使用
= default
)
默认构造函数是一个更宽泛的概念,包括编译器生成的和用户定义的无参构造函数。
默认构造函数的显式声明方式
方式 1:用户自定义无参构造函数
class MyClass {
public:MyClass() { // 自定义默认构造函数// 初始化逻辑}
};
方式 2:使用= default
强制编译器生成
class MyClass {
public:MyClass() = default; // 显式要求编译器生成默认构造函数
};
当类定义了任何构造函数(包括无参构造函数)时,编译器不会自动生成默认构造函数。
可通过= default
显式请求。
class Data {
public:Data(int val) : value(val) {} // 定义带参构造函数Data() = default; // 显式生成默认构造函数
private:int value;
};
派生类的构造函数会隐式调用基类的默认构造函数。若基类无默认构造函数,派生类必须显式调用基类的其他构造函数。
class Base {
public:Base(int x) {} // 基类无默认构造函数
};class Derived : public Base {
public:Derived() : Base(0) {} // 必须显式调用Base的构造函数
};
聚合类(无用户声明构造函数、无私有 / 保护成员、无虚函数)可使用聚合初始化(花括号语法),即使没有默认构造函数。
struct Data {int x;Data(int x) : x(x) {} // 有带参构造函数,无默认构造函数
};
Data d{42}; // 合法:聚合初始化
Data d; // 非法:无默认构造函数
工程中的最佳实践:
- 若类需要默认初始化,即使为空,也显式声明
- 使用成员初始化器确保所有成员被正确初始化,避免基本类型未初始化
- 设计类接口时考虑默认构造的必要性
- 若类不允许无参初始化,显式删除默认构造函数
const
和引用成员:必须通过构造函数初始化列表初始化,隐式默认构造函数无法处理
class ConstDemo {
public:const int c;ConstDemo() : c(10) {} // 必须显式初始化
};
在C++中,每个类最多只能有一个默认构造函数,即以下两种形式不能同时存在:
- 无参构造函数(无参数)
- 全缺省参数构造函数(所有参数均有默认值)
若同时定义这两种形式,会导致编译错误
class Date {
public:// 无参构造函数Date() : _year(1), _month(1), _day(1) {} // 默认构造函数①// 全缺省参数构造函数(等价于默认构造函数②)Date(int year = 1, int month = 1, int day = 1) : _year(year), _month(month), _day(day) {}
private:int _year, _month, _day;
};int main() {Date d1; // 错误:存在多个默认构造函数return 0;
}