刚学 C++ 的朋友,大概率都会被类和对象里的各种 “函数” 绕晕:构造函数、析构函数、拷贝构造、赋值重载…… 这些到底是干嘛的?为什么系统非要自动生成?自己写和不写有什么区别?

        这篇文章我用最通俗的大白话 + 超多代码示例 + 生活类比,把 C++ 类中6 大核心成员函数一次性讲透,不管是新手入门、面试备考,还是复习巩固,看完都能彻底吃透。

全文无晦涩术语,循序渐进,建议收藏慢慢看!

一、先搞懂:什么是 C++ 的 “6 大核心成员函数”?

只要你定义了一个类(class),哪怕里面一行代码都不写,C++ 编译器也会自动给你生成 6 个函数,它们是对象生命周期的 “管家”,负责对象的创建、复制、赋值、销毁

这 6 个函数就是:

  1. 构造函数 —— 对象出生时调用
  2. 析构函数 —— 对象死亡时调用
  3. 拷贝构造函数 —— 用一个对象复制出新对象
  4. 拷贝赋值运算符重载 —— 两个已存在对象互相赋值
  5. 移动构造函数 (C++11) —— 高效 “偷” 资源创建新对象
  6. 移动赋值运算符重载 (C++11) —— 高效 “偷” 资源赋值

二、构造函数:对象的 “出生仪式”

1. 通俗理解

类比:新生儿出生时的初始化(起名字、办户口、分配房间)

  • 对象被创建时,自动调用
  • 作用:给对象的成员变量初始化,避免野值、空指针、未初始化的 bug
  • 没有返回值,连void都不用写
  • 函数名必须和类名完全一样

2. 语法规则

class 类名{
public:
    // 构造函数
    类名(参数列表){
        // 初始化代码
    }
};

3. 代码示例:无参构造 + 有参构造

#include <iostream>
#include <string>
using namespace std;

// 定义一个人类
class Person {
public:
    // 1. 无参构造函数(默认构造)
    Person() {
        m_name = "无名氏";
        m_age = 0;
        cout << "无参构造调用:对象出生啦!" << endl;
    }

    // 2. 有参构造函数
    Person(string name, int age) {
        m_name = name;
        m_age = age;
        cout << "有参构造调用:对象出生啦!" << endl;
    }

    // 打印信息
    void showInfo() {
        cout << "姓名:" << m_name << " 年龄:" << m_age << endl;
    }

private:
    string m_name;
    int m_age;
};

int main() {
    // 创建对象1:调用无参构造
    Person p1;
    p1.showInfo();

    // 创建对象2:调用有参构造
    Person p2("张三", 20);
    p2.showInfo();

    return 0;
}

4. 关键知识点

  1. 一个类可以有多个构造函数(重载)
  2. 如果你不写构造函数,编译器会自动生成一个空的无参构造
  3. 一旦你自己写了构造函数,编译器就不会再自动生成默认构造了
  4. 构造函数不能被手动调用,只能在创建对象时自动执行

三、析构函数:对象的 “告别仪式”

1. 通俗理解

类比:人去世后的善后工作(注销户口、清理遗产、归还借的东西)

  • 对象销毁(生命周期结束)时,自动调用
  • 作用:释放对象占用的资源(内存、文件、网络连接、锁等)
  • 函数名:~类名无参数、无返回值
  • 一个类只能有一个析构函数,不能重载

2. 语法规则

~类名(){
    // 释放资源代码
}

3. 为什么必须手写析构?(重点!)

如果你的类里没有动态分配内存(new/malloc),不写析构也没事;但如果用了new必须手写析构用delete释放,否则会造成内存泄漏

4. 代码示例:带动态内存的析构

#include <iostream>
#include <cstring>
using namespace std;

class Student {
public:
    // 构造函数:用new分配内存
    Student(const char* name) {
        // 动态分配内存
        m_name = new char[strlen(name) + 1];
        strcpy(m_name, name);
        cout << "构造函数:分配内存成功" << endl;
    }

    // 析构函数:释放new的内存
    ~Student() {
        if (m_name != NULL) {
            // 必须释放!
            delete[] m_name;
            m_name = NULL;
            cout << "析构函数:内存已释放" << endl;
        }
    }

    void show() {
        cout << "姓名:" << m_name << endl;
    }

private:
    // 指针成员,指向堆内存
    char* m_name;
};

int main() {
    {
        // 进入作用域,创建对象
        Student s("小明");
        s.show();
    } 
    // 离开作用域,对象销毁,自动调用析构

    cout << "主函数结束" << endl;
    return 0;
}

5. 关键知识点

  1. 先构造的对象后析构,后构造的对象先析构(栈的规则:先进后出)
  2. 不写析构,编译器自动生成空析构
  3. 只要类里有指针 / 动态资源,必须手写析构!

四、拷贝构造函数:对象的 “克隆术”

1. 通俗理解

类比:用一张身份证复印件,复制出一张一模一样的新身份证

  • 已经存在的对象,创建一个同类型的新对象
  • 属于构造函数的一种,创建对象时自动调用
  • 参数必须是引用(一般加 const)

2. 语法规则

类名(const 类名& 其他对象){
    // 拷贝成员变量
}

3. 什么时候会调用拷贝构造?

3 种最常见场景:

  1. 用旧对象初始化新对象
  2. 对象作为函数值传递参数
  3. 函数返回值类型的对象

4. 浅拷贝 vs 深拷贝(面试必考!)

这是新手最容易踩坑的地方!

  • 浅拷贝(默认拷贝构造):只拷贝变量的值,指针只拷贝地址→ 两个对象指向同一块内存,析构时会重复释放内存,程序崩溃!
  • 深拷贝(自己手写):重新分配内存,再拷贝内容→ 两个对象独立,安全无冲突

5. 代码示例:深拷贝拷贝构造

#include <iostream>
#include <cstring>
using namespace std;

class Person {
public:
    Person(const char* name) {
        m_name = new char[strlen(name) + 1];
        strcpy(m_name, name);
    }

    // 手写拷贝构造:深拷贝
    Person(const Person& p) {
        // 1. 分配新内存
        m_name = new char[strlen(p.m_name) + 1];
        // 2. 拷贝内容
        strcpy(m_name, p.m_name);
        cout << "拷贝构造调用:克隆成功" << endl;
    }

    ~Person() {
        if (m_name) {
            delete[] m_name;
            m_name = NULL;
        }
    }

    void show() {
        cout << "姓名:" << m_name << endl;
    }

private:
    char* m_name;
};

int main() {
    Person p1("李四");
    // 调用拷贝构造
    Person p2 = p1;

    p1.show();
    p2.show();
    return 0;
}

6. 关键结论

只要类里有指针成员,必须手写拷贝构造(深拷贝)!否则一定会出现重复释放内存的崩溃 bug。

五、拷贝赋值运算符重载:对象的 “互相赋值”

1. 通俗理解

类比:把 A 笔记本里的所有内容,完整复制到 B 笔记本里(A、B 都已经存在)

  • 两个已经创建好的对象之间用=赋值
  • 和拷贝构造的区别:
    • 拷贝构造:创建新对象
    • 赋值重载:给旧对象赋值

2. 语法规则

类名& operator=(const 类名& 其他对象){
    // 赋值逻辑
}

3. 必须注意 3 个细节

  1. 检查自赋值(自己 = 自己)
  2. 先释放当前对象的旧内存
  3. 返回*this,支持链式赋值(a=b=c)

4. 代码示例:赋值重载

// 在上面Person类中添加赋值重载
Person& operator=(const Person& p) {
    // 1. 防止自赋值
    if (this == &p) {
        return *this;
    }

    // 2. 释放当前对象原有内存
    if (m_name) {
        delete[] m_name;
        m_name = NULL;
    }

    // 3. 深拷贝
    m_name = new char[strlen(p.m_name) + 1];
    strcpy(m_name, p.m_name);

    cout << "赋值重载调用" << endl;
    // 4. 返回自身,支持链式赋值
    return *this;
}

使用:

int main(){
    Person p1("张三");
    Person p2("李四");
    // 赋值重载
    p2 = p1;
    // 链式赋值
    Person p3 = p2 = p1;
    return 0;
}

5. 核心规则

拷贝构造和赋值重载,要么都写,要么都不写!(有指针就必须都手写深拷贝)

六、移动构造 & 移动赋值:C++11 的 “偷家神器”

1. 通俗理解

前面的拷贝是 “复制”,慢;移动是 “搬家”—— 直接把别人的资源抢过来,不复制,速度极快

类比:

  • 拷贝:把一本书手抄一遍,费时费力
  • 移动:直接把书拿过来,一秒完成

2. 适用场景

临时对象(即将销毁的对象)、函数返回值、std::move转移对象

3. 移动构造语法

类名(类名&& 其他对象){
    // 偷走别人的资源
    成员 = 其他对象.成员;
    // 把别人的指针置空,防止被析构
    其他对象.成员 = NULL;
}

4. 移动赋值语法

类名& operator=(类名&& 其他对象){
    // 偷资源 + 置空源对象
}

5. 优势

  • 不分配新内存
  • 不拷贝数据
  • 性能提升巨大(尤其字符串、容器、大对象)

七、终极总结:6 大函数一张表搞定

函数名称 调用时机 作用 必须手写场景
构造函数 创建对象时 初始化成员变量 需要自定义初始化
析构函数 对象销毁时 释放资源 有 new / 指针 / 文件 / 网络连接
拷贝构造函数 用对象创建新对象 克隆对象 类中有指针(深拷贝)
拷贝赋值运算符重载 两个对象 = 赋值 对象赋值 类中有指针(深拷贝)
移动构造函数 (C++11) 临时对象创建新对象 高效偷取资源 优化性能、大对象
移动赋值运算符重载 (C++11) 临时对象赋值给对象 高效偷取资源 优化性能、大对象

更多推荐