C++核心机制深度解析(一):构造函数分类与作用详解
·
C++核心机制深度解析(一):构造函数分类与作用详解
|
🌺The Begin🌺点点关注,收藏不迷路🌺
|
从默认构造到移动构造,一文读懂C++对象初始化的六种方式
前言
构造函数是C++中最重要的成员函数之一,它负责对象的初始化工作。理解各种构造函数的区别和使用场景,是掌握C++对象模型的第一步。本文将详细讲解C++中的六种构造函数及其作用。
1. 什么是构造函数?
构造函数是一种特殊的成员函数,在创建对象时自动调用,用于初始化对象的状态。它的特点包括:
- 函数名与类名相同
- 没有返回值(不能写void)
- 可以重载(可以有多个参数列表不同的构造函数)
- 自动调用(对象创建时编译器自动调用)
class Student {
public:
// 构造函数的典型形式
Student() { // 无返回值,函数名与类名相同
cout << "构造函数被调用" << endl;
}
};
2. 六种构造函数详解
2.1 默认构造函数
定义:不带任何参数的构造函数,或所有参数都有默认值的构造函数。
作用:创建对象的默认状态,进行基本的初始化。
class Student {
private:
string name;
int age;
bool isEnrolled;
public:
// 方式一:无参构造函数
Student() {
name = "Unknown";
age = 0;
isEnrolled = false;
cout << "默认构造函数被调用" << endl;
}
// 方式二:所有参数都有默认值(也是默认构造)
// Student(string n = "Unknown", int a = 0) : name(n), age(a) {}
void display() {
cout << "Name: " << name << ", Age: " << age
<< ", Enrolled: " << isEnrolled << endl;
}
};
// 使用方式
Student s1; // 调用默认构造函数
Student* s2 = new Student(); // 动态分配也调用默认构造
编译器自动生成默认构造函数的条件:
- 类中没有定义任何构造函数
- 所有成员变量都有类内初始值(C++11)
class Simple {
int x = 10; // 类内初始值
string s = "hello";
}; // 编译器会自动生成默认构造函数
2.2 带参构造函数
定义:接受一个或多个参数的构造函数。
作用:使用指定的值初始化对象,灵活控制对象的初始状态。
class Student {
private:
string name;
int age;
string studentId;
public:
// 带参构造函数
Student(const string& n, int a, const string& id) {
name = n;
age = a;
studentId = id;
cout << "带参构造函数被调用: " << name << endl;
}
void display() {
cout << "ID: " << studentId << ", Name: " << name
<< ", Age: " << age << endl;
}
};
// 使用方式
Student s1("张三", 20, "2024001"); // 直接初始化
Student s2 = Student("李四", 21, "2024002"); // 拷贝初始化
Student* s3 = new Student("王五", 19, "2024003"); // 动态分配
初始化列表 vs 函数体内赋值:
class Person {
private:
const int id; // const成员必须使用初始化列表
string& nameRef; // 引用成员必须使用初始化列表
int age;
public:
// 推荐:使用初始化列表
Person(int i, string& n, int a) : id(i), nameRef(n), age(a) {
// 初始化列表执行完毕后,才进入函数体
cout << "初始化列表效率更高" << endl;
}
// 不推荐:函数体内赋值
/* Person(int i, string& n, int a) {
id = i; // 错误!const成员不能赋值
nameRef = n; // 引用必须在初始化列表中初始化
age = a;
} */
};
2.3 拷贝构造函数
定义:使用同类型对象初始化新对象的构造函数,参数为当前类的const引用。
作用:通过已存在的对象创建新对象,控制对象的拷贝行为。
class StringBuffer {
private:
char* data;
int length;
public:
// 普通构造函数
StringBuffer(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
cout << "普通构造: " << data << endl;
}
// 拷贝构造函数
StringBuffer(const StringBuffer& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
cout << "拷贝构造: " << data << endl;
}
~StringBuffer() {
delete[] data;
cout << "析构: " << (data ? data : "nullptr") << endl;
}
void display() {
cout << "String: " << data << ", Length: " << length << endl;
}
};
// 拷贝构造函数的调用时机
StringBuffer s1("Hello"); // 普通构造
StringBuffer s2(s1); // ① 使用另一个对象初始化
StringBuffer s3 = s1; // ② 使用=初始化(不是赋值)
StringBuffer s4 = StringBuffer(s1); // ③ 临时对象拷贝
void func(StringBuffer s) { // ④ 值传递参数
s.display();
}
func(s1); // 调用拷贝构造函数
StringBuffer func2() { // ⑤ 按值返回
StringBuffer temp("World");
return temp; // 可能调用拷贝构造(受RVO影响)
}
深浅拷贝问题(详见第九篇):
2.4 移动构造函数(C++11)
定义:使用右值引用参数(T&&)的构造函数,转移资源所有权而非拷贝。
作用:避免临时对象的深拷贝开销,大幅提升性能。
class Buffer {
private:
int* data;
size_t size;
public:
// 普通构造
Buffer(size_t sz) : size(sz), data(new int[sz]) {
cout << "构造 " << size << " 个元素的缓冲区" << endl;
}
// 拷贝构造(深拷贝)
Buffer(const Buffer& other) : size(other.size) {
data = new int[size];
copy(other.data, other.data + size, data);
cout << "拷贝构造,深拷贝 " << size << " 个元素" << endl;
}
// 移动构造(资源转移)
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // 置空源对象
other.size = 0;
cout << "移动构造,O(1)资源转移" << endl;
}
~Buffer() {
delete[] data;
cout << "析构缓冲区" << endl;
}
};
// 使用场景
vector<Buffer> vec;
Buffer b1(1000);
vec.push_back(move(b1)); // 调用移动构造,b1不再拥有资源
Buffer createBuffer() {
return Buffer(500); // 返回临时对象,自动调用移动构造
}
Buffer b2 = createBuffer(); // 移动构造(RVO可能优化掉)
移动构造必须标记为noexcept的原因:
- 标准容器在重新分配时,如果移动构造是
noexcept的,会使用移动而非拷贝 - 异常安全保证:移动操作不应抛出异常
2.5 转换构造函数
定义:单个参数的构造函数(其他参数有默认值)允许隐式类型转换。
作用:实现从参数类型到类类型的隐式转换。
class Integer {
private:
int value;
public:
// 转换构造函数:允许从int转换到Integer
Integer(int n) : value(n) {
cout << "转换构造: " << n << endl;
}
// explicit关键字禁止隐式转换
// explicit Integer(int n) : value(n) {}
int getValue() const { return value; }
};
class Complex {
private:
double real, imag;
public:
// 多个参数,但第二个有默认值 - 也是转换构造
Complex(double r, double i = 0.0) : real(r), imag(i) {
cout << "Complex构造: " << real << " + " << imag << "i" << endl;
}
};
// 隐式转换示例
Integer a = 42; // 隐式调用Integer(42)
Integer b = 100; // 同样隐式转换
void printInteger(Integer obj) {
cout << "Value: " << obj.getValue() << endl;
}
printInteger(123); // 隐式转换:123 -> Integer(123)
// 隐式转换的危险场景
Integer c = 3.14; // 编译通过!3.14转为int 3,然后转为Integer
Complex d = 5.5; // 隐式调用Complex(5.5, 0.0)
// 使用explicit禁止隐式转换后
// Integer a = 42; // 错误!不能隐式转换
// printInteger(123); // 错误!
Integer e = Integer(42); // 必须显式调用
Integer f = static_cast<Integer>(100); // 显式转换
渲染错误: Mermaid 渲染失败: Parse error on line 6: ...-.->|要求显式| E[Integer(42)] -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
2.6 委托构造函数(C++11)
定义:在构造函数的初始化列表中调用同一类的另一个构造函数。
作用:减少代码重复,让构造函数之间复用初始化逻辑。
class Employee {
private:
string name;
int id;
double salary;
string department;
public:
// 主构造函数:包含完整初始化逻辑
Employee(const string& n, int i, double s, const string& dept)
: name(n), id(i), salary(s), department(dept) {
cout << "完整初始化: " << name << " [ID:" << id << "]" << endl;
validate(); // 公共验证逻辑
}
// 委托给主构造函数
Employee(const string& n, int i)
: Employee(n, i, 5000.0, "General") { // 委托调用
cout << "委托构造:使用默认薪资和部门" << endl;
}
// 再委托一层
Employee(const string& n)
: Employee(n, generateId(), 5000.0, "General") {
cout << "委托构造:只提供姓名" << endl;
}
// 拷贝构造委托(注意避免循环)
Employee(const Employee& other)
: Employee(other.name, other.id, other.salary, other.department) {
cout << "拷贝构造委托" << endl;
}
private:
static int idCounter;
static int generateId() { return ++idCounter; }
void validate() {
if (salary < 0) salary = 0;
if (department.empty()) department = "Unknown";
}
};
// 使用示例
Employee e1("张三"); // 最简形式
Employee e2("李四", 1001); // 提供姓名和ID
Employee e3("王五", 1002, 8000.0, "IT"); // 完整形式
Employee e4(e3); // 拷贝构造
委托构造的注意事项:
渲染错误: Mermaid 渲染失败: Parse error on line 7: ...--> F[错误示例: Employee(): name(""), Employ -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
// 错误示例
class Wrong {
int x, y;
public:
Wrong(int a) : x(a), y(0) {}
// 错误!不能同时有初始化列表和委托
// Wrong() : x(0) , Wrong(0) {}
// 正确:全委托
Wrong() : Wrong(0) {}
// 错误:循环委托
// Wrong(int a, int b) : Wrong(a) { y = b; }
// 应改为:Wrong(int a, int b) : Wrong(a) { this->y = b; }
};
3. 构造函数调用顺序
class Base {
public:
Base() { cout << "Base构造函数" << endl; }
Base(int x) { cout << "Base带参构造: " << x << endl; }
};
class Member {
public:
Member() { cout << "Member默认构造" << endl; }
Member(int x) { cout << "Member带参构造: " << x << endl; }
};
class Derived : public Base {
private:
Member m1;
Member m2;
public:
Derived() : m2(2), Base(100) { // 初始化列表顺序不影响调用顺序
cout << "Derived构造函数" << endl;
}
};
Derived d;
// 输出顺序:
// 1. Base带参构造: 100 (基类构造函数)
// 2. Member默认构造 (m1,按声明顺序)
// 3. Member带参构造: 2 (m2,按声明顺序)
// 4. Derived构造函数 (派生类构造函数体)
调用顺序总结:
4. 特殊场景:删除构造函数
// 禁止拷贝
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // 删除拷贝构造
NonCopyable& operator=(const NonCopyable&) = delete;
};
// 只能移动的类型
class MoveOnly {
public:
MoveOnly() = default;
MoveOnly(const MoveOnly&) = delete;
MoveOnly& operator=(const MoveOnly&) = delete;
MoveOnly(MoveOnly&&) = default; // 默认移动构造
MoveOnly& operator=(MoveOnly&&) = default;
};
// 禁止在堆上创建
class NoHeap {
~NoHeap() = delete; // 析构私有
public:
NoHeap() = default;
void destroy() { delete this; } // 特殊释放方法
};
5. 总结对比表
| 构造函数类型 | 语法特征 | 主要用途 | 调用时机 | 能否为虚 |
|---|---|---|---|---|
| 默认构造 | ClassName() |
默认状态初始化 | 创建无参对象 | ❌ |
| 带参构造 | ClassName(T1, T2...) |
自定义初始化 | 传入参数创建 | ❌ |
| 拷贝构造 | ClassName(const ClassName&) |
对象复制 | 基于现有对象创建 | ❌ |
| 移动构造 | ClassName(ClassName&&) |
资源转移 | 基于临时对象创建 | ❌ |
| 转换构造 | ClassName(T) 单参数 |
类型转换 | 隐式/显式转换 | ❌ |
| 委托构造 | : ClassName(...) |
代码复用 | 构造函数间调用 | ❌ |
6. 常见面试题
Q1: 什么情况下会调用拷贝构造函数?
// 四种情况
Student s1("Tom");
Student s2(s1); // ① 直接初始化
Student s3 = s1; // ② 拷贝初始化
void func(Student s) {} // ③ 值传递
Student func() { // ④ 返回值(可能被RVO优化)
Student temp;
return temp;
}
Q2: 深拷贝和浅拷贝的区别?(详见第九篇)
Q3: 为什么拷贝构造的参数必须是引用?
// 错误:值传递会导致无限递归
class X {
public:
X(const X x) { // 编译错误!
// 因为传值需要拷贝,拷贝又需要传值...
}
};
// 正确:使用const引用
class X {
public:
X(const X& x) { // 正确,避免递归
// 初始化逻辑
}
};
如果觉得有帮助,欢迎点赞、收藏、评论交流!

|
🌺The End🌺点点关注,收藏不迷路🌺
|
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)