构造函数是C++中用于初始化类对象的特殊成员函数。它的核心价值在于将对象的创建和初始化过程合二为一,彻底改变了C语言中"先分配内存再手动初始化"的繁琐模式。想象一下建筑工地:构造函数就像一支专业的施工队,在打好地基(分配内存)后自动完成房屋的主体建造(初始化),而不是只给你一块空地让你自己从头开始。
构造函数的关键特性体现在几个方面:
在C++中,默认构造函数实际上有三种合法形式,但三者不能共存:
cpp复制class Widget {
// 编译器自动生成默认构造函数
};
cpp复制class Widget {
public:
Widget() { /*...*/ } // 手动定义无参版本
};
cpp复制class Widget {
public:
Widget(int x = 0, int y = 0) { /*...*/ } // 所有参数都有默认值
};
重要提示:无参构造和全缺省构造虽然语法上构成重载,但实际调用时会产生歧义。编译器无法区分
Widget()和Widget(0,0)的调用意图,因此实践中应当只选择其中一种形式。
构造函数对不同类型的成员变量处理方式截然不同:
| 成员类型 | 默认构造处理方式 | 注意事项 |
|---|---|---|
| 内置类型 | 不保证初始化(取决于编译器实现) | 建议手动初始化避免未定义行为 |
| 自定义类型 | 调用该类型的默认构造函数 | 若无默认构造需用初始化列表 |
典型问题示例:
cpp复制class Inner {
public:
Inner(int x) {} // 非默认构造
};
class Outer {
Inner obj; // 错误!Inner缺少默认构造
int num; // 可能未初始化
};
解决方法是通过初始化列表显式初始化:
cpp复制class Outer {
public:
Outer() : obj(0), num(0) {} // 初始化列表
private:
Inner obj;
int num;
};
cpp复制class Stack {
public:
Stack(int capacity = 4) // 带默认值的参数
: _top(0), _capacity(capacity) {
_data = new int[capacity];
if(!_data) throw std::bad_alloc();
}
private:
int* _data;
size_t _top;
size_t _capacity;
};
关键技巧:
cpp复制class Date {
public:
// 无参版本设置默认日期
Date() : _year(1970), _month(1), _day(1) {}
// 带参版本允许自定义日期
Date(int y, int m, int d) {
if(!isValid(y,m,d)) throw InvalidDate();
_year = y;
_month = m;
_day = d;
}
// 带缺省参数的版本
Date(int y=2000, int m=1) : _year(y), _month(m), _day(1) {}
private:
bool isValid(int y, int m, int d) { /*...*/ }
int _year, _month, _day;
};
拷贝构造函数用于通过已有对象创建新对象,其典型特征为:
cpp复制class String {
public:
String(const String& other)
: _size(other._size) {
_data = new char[_size + 1];
std::copy(other._data, other._data + _size + 1, _data);
}
private:
char* _data;
size_t _size;
};
现代C++引入移动语义,通过转移资源所有权而非复制来提升效率:
cpp复制class String {
public:
String(String&& other) noexcept
: _data(other._data), _size(other._size) {
other._data = nullptr; // 置空源对象
other._size = 0;
}
};
使用场景:
赋值运算符需要处理自赋值情况并返回引用:
cpp复制String& operator=(const String& rhs) {
if(this != &rhs) { // 自赋值检查
delete[] _data;
_size = rhs._size;
_data = new char[_size + 1];
std::copy(rhs._data, rhs._data + _size + 1, _data);
}
return *this;
}
对于管理资源的类,通常需要定义以下五个特殊成员函数:
cpp复制class FileHandle {
public:
~FileHandle() { if(_handle) fclose(_handle); }
FileHandle(const FileHandle&) = delete; // 禁止拷贝
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&& other) noexcept
: _handle(other._handle) {
other._handle = nullptr;
}
FileHandle& operator=(FileHandle&& rhs) noexcept {
if(this != &rhs) {
if(_handle) fclose(_handle);
_handle = rhs._handle;
rhs._handle = nullptr;
}
return *this;
}
private:
FILE* _handle;
};
C++11后可用=default和=delete简化特殊成员函数的声明:
cpp复制class Widget {
public:
Widget() = default; // 使用编译器生成版本
~Widget() = default;
Widget(const Widget&) = delete; // 禁止拷贝
Widget& operator=(const Widget&) = delete;
Widget(Widget&&) = default; // 允许移动
Widget& operator=(Widget&&) = default;
};
构造函数执行失败时,已构造的成员会被自动销毁:
cpp复制class ResourceHolder {
public:
ResourceHolder()
: res1(new Resource), // 可能抛出异常
res2(new Resource) { // 如果这里抛出,res1会被自动释放
// ...
}
private:
std::unique_ptr<Resource> res1;
std::unique_ptr<Resource> res2;
};
最佳实践:
派生类构造时成员的初始化顺序:
cpp复制class Base {
public:
Base() { cout << "Base\n"; }
};
class Member {
public:
Member() { cout << "Member\n"; }
};
class Derived : public Base {
Member m;
public:
Derived() { cout << "Derived\n"; }
};
// 输出顺序:Base → Member → Derived
移动操作后源对象应处于有效但未定义状态:
cpp复制vector<int> createData() {
vector<int> temp(1000000);
// ...填充数据...
return temp; // 触发移动构造
}
void process() {
auto data = createData(); // 高效移动而非复制
// 此时temp已被移空但仍是合法状态
}
关键原则: