🌺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影响)
}

深浅拷贝问题(详见第九篇):

深拷贝

独立

独立

对象1

内存块1

对象2

内存块2

浅拷贝

共享

共享

对象1

同一块内存

对象2

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构造函数    (派生类构造函数体)

调用顺序总结

开始构造对象

1. 虚基类构造函数
(按继承顺序,深度优先)

2. 直接基类构造函数
(按声明顺序)

3. 成员对象构造函数
(按声明顺序)

4. 构造函数体执行


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🌺点点关注,收藏不迷路🌺

Logo

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

更多推荐