目 录CONTENT

文章目录

C++构造函数与析构函数

TalentQ
2025-06-25 / 0 评论 / 0 点赞 / 22 阅读 / 0 字

引言

在C++面向对象编程中,构造函数(Constructor)和析构函数(Destructor)是类的两个特殊成员函数,它们分别负责对象的初始化和清理工作,会被系统自动调用。我们可以在构造函数中给类分配资源,在类的析构函数中释放对应的资源。如果我们不提供构造函数与析构函数,编译器会自动提供两个函数的空实现。

构造函数和析构函数是一种特殊的公有成员函数,必须定义在public中。构造函数在类定义时由系统自动调用,析构函数在类被销毁时由系统自动调用。一个类可以有多个构造函数,只能有一个析构函数,不同的构造函数之间通过参数个数和参数类型来区分。

构造函数

构造函数是一个特殊的成员函数,用于在创建对象时初始化对象的数据成员。构造函数具有以下特点:

  • 函数名与类名相同

  • 没有返回类型(包括void)

  • 在对象创建时自动调用

  • 可以重载

  • 可以有参数

无参(默认)构造函数(Default Constructor)

定义:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

作用:为对象提供默认的初始化方式。

示例:

class Person {
public:
    Person() {
        cout << "默认构造函数被调用" << endl;
    }
};

Person p; // 自动调用默认构造函数

注意:如果你没有为类定义任何构造函数,编译器会自动生成一个默认构造函数。

带参数构造函数(Parameterized Constructor)

定义:带有一个或多个参数的构造函数。

作用:允许在创建对象时,指定初始值。

示例:

class Person {
public:
    string name;
    int age;
    Person(string n, int a) : name(n), age(a) {
        cout << "带参数构造函数被调用" << endl;
    }
};

Person p("talentq", 18); // 调用带参数构造函数

拷贝构造函数(Copy Constructor)

定义:以本类对象的引用作为参数的构造函数。标准写法为:ClassName(const ClassName& other);

作用:用一个已有对象来初始化新对象,实现对象的“复制”。

示例:

class Person {
public:
    string name;
    int age;
    Person(const Person& other) : name(other.name), age(other.age) {
        cout << "拷贝构造函数被调用" << endl;
    }
};

Person p1("李四", 30);
Person p2(p1); // 调用拷贝构造函数

注意:如果没有自定义拷贝构造函数,编译器会自动生成一个“浅拷贝”版本。对于有动态资源管理的类,建议自定义“深拷贝”拷贝构造函数。

移动构造函数(Move Constructor)

定义:以右值引用(ClassName&&)作为参数的构造函数。

作用:实现资源的“移动”而不是“拷贝”,提升性能,避免不必要的资源分配和释放。

示例:

class Buffer {
public:
    int* data;
    size_t size;
    Buffer(size_t n) : data(new int[n]), size(n) {}

    // 移动构造函数
    Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr; // 资源转移
        other.size = 0;
        cout << "移动构造函数被调用" << endl;
    }
};

应用场景:适用于临时对象、返回局部对象等场合。

委托构造函数(Delegating Constructor)

定义:在一个构造函数中调用同类的另一个构造函数。

作用:减少代码重复,统一初始化逻辑。

示例:

class Person {
public:
    string name;
    int age;
    Person() : Person("未知", 0) {
        cout << "委托构造函数被调用" << endl;
    }
    Person(string n, int a) : name(n), age(a) {
        cout << "主构造函数被调用" << endl;
    }
};

转换构造函数(Conversion Constructor)

定义:只有一个参数(且不是本类类型)或者有多个参数但除了第一个外都有默认值的构造函数。

作用:允许从其他类型隐式或显式地转换为本类对象。

示例:

class Fraction {
public:
    int numerator, denominator;
    // 转换构造函数
    Fraction(int num) : numerator(num), denominator(1) {}
};

Fraction f = 5; // 隐式调用转换构造函数,将int转为Fraction

注意:为避免隐式转换带来的意外,可以加explicit关键字:explicit Fraction(int num) : numerator(num), denominator(1) {} ,阻止隐式转换。

#include <iostream>
using namespace std;

class Fraction {
public:
    int numerator;
    int denominator;
    explicit Fraction(int num) : numerator(num), denominator(1) {}
};

void printFraction(const Fraction& f) {
    cout << f.numerator << "/" << f.denominator << endl;
}

int main() {
    // Fraction f1 = 5;      // 编译错误,不能隐式转换
    Fraction f2(5);         // 正确,显示构造
    printFraction(f2);      // 正确

    // printFraction(10);    // 编译错误,不能隐式转换
    printFraction(Fraction(10)); // 正确,显示构造
    return 0;
}

析构函数

1 什么是析构函数?

析构函数(Destructor)是 C++ 类中的一种特殊成员函数,在对象生命周期结束时自动调用,用于执行清理工作,比如释放资源、关闭文件、断开网络连接等。

2 析构函数的语法与特点

析构函数的名称是在类名前加上波浪号(~),没有参数、没有返回值(连 void 都不能写)。

class ClassName {
public:
    ~ClassName();
};

析构函数具有以下特点:

  • 没有参数、返回值、返回类型,也不能重载(一个类只能有一个析构函数)

  • 可以是虚函数(用于多态场景)

  • 在对象销毁时自动调用,不需要手动调用(手动调用会导致未定义行为)

  • 如果不定义,编译器会自动生成一个默认的析构函数(“浅析构”)

3 析构函数的调用时机

1 局部对象离开作用域时

void foo() {
    MyClass obj; // foo 结束时 obj 自动析构
}

2 动态分配的对象被delete时

MyClass* p = new MyClass;
delete p; // 调用析构函数

3 程序结束时(全局对象和静态对象)

4 析构函数与多态(虚析构函数)

当用基类指针指向派生类对象时,基类析构函数强烈建议声明为 virtual,否则只会调用基类析构函数,派生类资源不会被释放,可能导致内存泄漏。

凡是要被继承的类,析构函数都强烈建议声明为 virtual。

#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base构造" << endl; }
    virtual ~Base() { cout << "Base析构" << endl; }
};

class Derived : public Base {
private:
    int* p;
public:
    Derived() : p(new int[10]) { cout << "Derived构造" << endl; }
    ~Derived() {
        delete[] p;
        cout << "Derived析构" << endl;
    }
};

int main() {
    Base* ptr = new Derived();
    delete ptr; // 会正确调用 Derived 和 Base 的析构函数
    return 0;
}

5 RAII 与析构函数

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++中非常重要的资源管理思想。

核心思想是:资源的申请和释放分别放在构造函数和析构函数中。

class FileHandler {
private:
    FILE* fp;
public:
    FileHandler(const char* filename) {
        fp = fopen(filename, "r");
    }
    ~FileHandler() {
        if (fp) fclose(fp);
    }
};

这样,FileHandler对象销毁时,文件会自动关闭,不会忘记释放资源。

C++11的“五法则”(Rule of Five)

在C++98/03中,三法则(Rule of Three)要求:如果你自定义了析构函数、拷贝构造函数、拷贝赋值运算符中的任何一个,通常都要自定义全部三个。

C++11 引入了移动语义,增加了移动构造函数和移动赋值运算符。因此,现代C++提出了五法则(Rule of Five)。

五法则(Rule of Five):如果类需要自定义析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符中的任何一个,通常都要自定义全部五个。这样可以确保资源的正确管理,避免内存泄漏、悬垂指针等问题。

现代C++(C++11及以后)推荐优先使用智能指针(如std::unique_ptr、std::shared_ptr)来自动管理资源,这样可以避免手写五法则。

五法则是C++11以后面向资源管理类的基本规范,避免资源泄漏和不安全的浅拷贝。

1 五个特殊成员函数

如果你的类管理资源(如动态内存、文件句柄、网络连接等),就要考虑如下五个函数:

  1. 析构函数(Destructor)
    ~ClassName();
    负责释放资源。

  2. 拷贝构造函数(Copy Constructor)
    ClassName(const ClassName& other);
    用于对象的“复制构造”。

  3. 拷贝赋值运算符(Copy Assignment Operator)
    ClassName& operator=(const ClassName& other);
    用于对象的“复制赋值”。

  4. 移动构造函数(Move Constructor, C++11)
    ClassName(ClassName&& other) noexcept;
    用于“窃取”资源,提高效率。

  5. 移动赋值运算符(Move Assignment Operator, C++11)
    ClassName& operator=(ClassName&& other) noexcept;
    用于“窃取”资源并赋值。

2 为什么需要“五法则”?

  • 如果你的类涉及资源管理,比如有原始指针成员变量,你自定义了其中一个函数,通常也要自定义其它四个,否则会出现浅拷贝、资源重复释放、悬垂指针等问题。

  • C++11引入移动语义,如果你没自定义,编译器可能生成默认的移动构造/赋值,可能不符合你的资源管理需求。

0

评论区