后室:恐惧免安装中文版
2.06G · 2025-09-28
构造函数是C++面向对象编程的基石,理解它们对于编写健壮、高效的代码至关重要。
构造函数是一个特殊的成员函数,它在创建对象时自动调用,用于初始化对象的内存状态。它的名称与类名完全相同,并且没有返回类型(连void
都没有)。
public
(除非有特殊设计,如单例模式)现在,让我们深入探讨各类构造函数。
默认构造函数是不需要任何参数就能调用的构造函数。
两种形式:
int
, double
, 指针)的成员变量不进行初始化(值是未定义的),对类类型的成员变量则调用其自身的默认构造函数。示例代码:
class MyClass {
public:
int data;
std::string name;
// 用户定义的默认构造函数(无参)
MyClass() {
data = 0; // 显式初始化
name = "Unknown";
}
// 或者使用成员初始化列表的更优写法
// MyClass() : data(0), name("Unknown") {}
};
int main() {
MyClass obj1; // 调用默认构造函数
MyClass obj2{}; // C++11 列表初始化,也调用默认构造函数
MyClass* obj3 = new MyClass; // 动态分配,也调用默认构造函数
return 0;
}
关键点:
一旦你定义了任何构造函数,编译器将不再自动生成默认构造函数。
如果你需要一个默认构造函数但又已经定义了其他构造函数,可以使用 = default
来显式要求编译器生成。
class MyClass {
public:
MyClass(int x) { ... } // 参数化构造函数
MyClass() = default; // 显式要求编译器生成默认构造函数
};
参数化构造函数接受一个或多个参数,用于在创建对象时提供初始值。
示例代码:
class Date {
private:
int day, month, year;
public:
// 参数化构造函数
Date(int d, int m, int y) : day(d), month(m), year(y) { // 使用成员初始化列表
// 可以在这里添加验证逻辑,例如检查日期是否合法
}
void display() {
std::cout << day << "/" << month << "/" << year << std::endl;
}
};
int main() {
Date today(26, 10, 2023); // 调用参数化构造函数
Date birthday{10, 5, 1990}; // C++11 统一初始化语法,推荐使用
today.display(); // 输出:26/10/2023
// Date errorDate; // 错误!因为我们已经定义了构造函数,编译器不再生成默认构造函数。
return 0;
}
成员初始化列表:
: day(d), month(m), year(y)
。这是成员初始化列表。day = d;
)。对于常量成员(const
)和引用成员(&
),必须使用初始化列表。拷贝构造函数用于用一个已存在的对象来初始化一个新对象。它接受一个对本类类型的常量引用作为参数。
形式: ClassName(const ClassName& other)
何时被调用?
MyClass obj1; MyClass obj2 = obj1;
或 MyClass obj2(obj1);
示例代码:
class StringWrapper {
private:
char* m_data;
size_t m_size;
public:
// 参数化构造函数
StringWrapper(const char* str) {
m_size = strlen(str);
m_data = new char[m_size + 1]; // 动态分配内存
strcpy(m_data, str);
}
// 1. 拷贝构造函数(深拷贝)
StringWrapper(const StringWrapper& other) : m_size(other.m_size) {
std::cout << "拷贝构造函数被调用!" << std::endl;
m_data = new char[m_size + 1];
strcpy(m_data, other.m_data);
}
// 析构函数
~StringWrapper() {
delete[] m_data;
}
void print() {
std::cout << m_data << std::endl;
}
};
int main() {
StringWrapper str1("Hello");
StringWrapper str2 = str1; // 调用拷贝构造函数
str1.print(); // Hello
str2.print(); // Hello
return 0;
}
// 析构时不会出错,因为str1和str2拥有各自独立的内存(深拷贝)。
深浅拷贝问题:
char* m_data
),必须自定义拷贝构造函数实现深拷贝。否则,两个对象的指针会指向同一块内存,导致双重释放(double free)的运行时错误。移动构造函数是C++11为支持移动语义而引入的,它用于将资源(如动态内存)从一个即将销毁的临时对象“移动”到新对象中,从而避免不必要的深拷贝,提升性能。
形式: ClassName(ClassName&& other) noexcept
(noexcept
很重要,标准库容器在重新分配内存时会使用移动构造函数,它要求该操作不抛异常)。
示例代码:
class StringWrapper {
// ... 其他成员同上 ...
// 2. 移动构造函数
StringWrapper(StringWrapper&& other) noexcept : m_data(nullptr), m_size(0) {
std::cout << "移动构造函数被调用!" << std::endl;
// "窃取" 临时对象的资源
m_data = other.m_data;
m_size = other.m_size;
// 将临时对象置于有效但可析构的状态
other.m_data = nullptr;
other.m_size = 0;
}
};
StringWrapper createString() {
return StringWrapper("Temporary String"); // 这是一个右值
}
int main() {
StringWrapper str3 = createString(); // 这里会优先调用移动构造函数(如果存在)
str3.print(); // Temporary String
// 如果没有移动构造函数,则会调用拷贝构造函数,性能较低。
return 0;
}
关键点:
ClassName&&
)。nullptr
)。委托构造函数允许一个构造函数调用同一个类的另一个构造函数,以避免代码重复。
示例代码:
class Employee {
private:
int m_id;
std::string m_name;
std::string m_department;
public:
// 目标构造函数
Employee(int id, const std::string& name, const std::string& dept)
: m_id(id), m_name(name), m_department(dept) {
std::cout << "三参数构造函数" << std::endl;
}
// 委托构造函数:委托给上面的三参数构造函数
Employee(int id, const std::string& name) : Employee(id, name, "Unassigned") {
std::cout << "委托构造函数" << std::endl;
}
// 默认构造函数也可以委托
Employee() : Employee(0, "Unknown", "Unassigned") {}
};
任何只接受一个参数的构造函数(除了拷贝/移动构造函数),都定义了一种从参数类型到该类类型的隐式转换规则。
示例代码:
class MyNumber {
private:
int value;
public:
// 转换构造函数:从 int 到 MyNumber
MyNumber(int v) : value(v) {}
void display() {
std::cout << "Value: " << value << std::endl;
}
};
void printNumber(MyNumber num) {
num.display();
}
int main() {
MyNumber num = 42; // 隐式转换:int 42 被转换为 MyNumber 对象
printNumber(100); // 隐式转换:int 100 被转换为 MyNumber 对象
return 0;
}
防止隐式转换:
隐式转换有时会带来意想不到的错误。可以使用 explicit
关键字来禁止它。
class MyNumber {
public:
explicit MyNumber(int v) : value(v) {} // 显式构造函数
};
// MyNumber num = 42; // 错误!转换是显式的,无法进行隐式转换。
MyNumber num(42); // 正确!直接初始化。
MyNumber num2 = MyNumber(42); // 正确!显式转换。
printNumber(MyNumber(100)); // 正确!必须显式转换。
// printNumber(100); // 错误!
构造函数类型 | 语法示例 | 主要用途 |
---|---|---|
默认构造函数 | MyClass(); | 创建对象时不提供初始化值 |
参数化构造函数 | MyClass(int a, string s); | 创建对象时提供初始化值 |
拷贝构造函数 | MyClass(const MyClass& other); | 用一个已存在对象初始化新对象(深拷贝) |
移动构造函数 | MyClass(MyClass&& other) noexcept; | 从临时对象“转移”资源,提升性能 |
委托构造函数 | MyClass() : MyClass(0, "") {} | 在一个构造函数中调用另一个,避免重复代码 |
转换构造函数 | MyClass(int x); | 定义从参数类型到类类型的隐式转换(可用explicit 禁用) |
理解并正确使用这些构造函数,是编写出正确、高效、易于维护的C++代码的关键。对于资源管理类(如智能指针、容器),三/五法则(需要自定义拷贝构造/赋值、移动构造/赋值、析构函数中的一个,通常需要自定义全部)是重要的指导原则。