ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

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

1. 引言

在 C++ 面向对象编程中,构造函数和析构函数是类生命周期管理的核心机制。构造函数负责对象的初始化,析构函数负责资源的清理与释放。理解并正确使用这两个特殊成员函数,是写出健壮、无内存泄漏 C++ 程序的基础。

2. 构造函数

构造函数是一种特殊的成员函数,在对象创建时自动调用,用于初始化对象的成员变量、分配资源或执行必要的准备工作。构造函数的名字与类名相同,且没有返回值类型。

2.1 默认构造函数

默认构造函数是不需要参数即可调用的构造函数。如果类中没有显式定义任何构造函数,编译器会自动生成一个默认构造函数,对内置类型成员的初始化行为是不确定的,对类类型成员调用其默认构造函数,此时自定义类型无默认构造函数时就会报错,所以当没有默认构造函数时,成员变量会走它定义的地方->初始化列表(后续讲解)。无参构造函数,全缺省函数以及编译器自动生成的构造函数都是默认构造函数。

class Student { public: Student() { name = "未知"; age = 0; } private: std::string name; int age; }; int main() { Student s1; //这里的初始化不要在对象后面加上(),会分不清到底是实例化对象还是函数声明 return 0; }

2.2 带参构造函数

带参构造函数允许在创建对象时传入参数,从而以不同的初始值构造对象。带参构造函数可以配合默认参数使用,也可以使用初始化列表来高效初始化成员。

class Student { public: Student(const std::string& n, int a) : name(n) , age(a) //初始化列表,后续讲解 {} private: std::string name; int age; };

2.3 拷贝构造函数

拷贝构造函数用于以同类型对象为蓝本创建新对象,其第一个参数必须是同类对象的引用且其他变量均有默认值。当以值传递对象、返回对象或以已有对象初始化新对象时,拷贝构造函数会被调用。

拷贝构造函数的特点:
1.它是构造函数的一个重载,必须通过引用传递接收同类型对象,以防止编译器陷入无限递归;允许存在额外参数,但必须有默认值。

2.在C++中,自定义类型进行值传递或值返回时,均会自动调用该函数。

3.若不进行显式调用,编译器会自动生成默认版本。该版本对内置类型执行逐字节的浅拷贝,对自定义类型的成员则会递归调用其自身的拷贝构造函数。

4.当类直接管理外部资源(如指针成员)时,默认生成的浅拷贝不满足需求,需手动实现深拷贝;若类成员全为内置类型或自定义类型的聚合(如队列包含栈对象),通常无需手动实现。此外,若类已显式定义了析构函数来释放资源,通常也必须手动实现拷贝构造,以免重复释放内存。

5.值返回会产生临时对象(会触发拷贝);传引用返回虽能减少拷贝,但严禁返回已销毁的局部对象引用,以防产生野引用。

3. 析构函数

析构函数在对象生命周期结束时自动调用,用于释放对象占用的资源,如动态内存、文件句柄、网络连接等。析构函数的名字是类名前加波浪号,没有参数,也没有返回值,且一个类只能有一个析构函数。和构造函数相似的是,内置类型不进行处理,自定义类型会自动调用它自己的析构函数,需要注意的是,当一个类中并没有什么资源需要去释放时,此时就使用编译器自己生成的析构函数就够用,但是要是比如在类中向堆等需要自己手动来释放的资源,一定要显式实现一个析构函数以避免内存泄漏。

class Buffer { public: ~Buffer() { delete[] data; } private: char* data; };

3.1 析构顺序

对象的析构顺序与构造顺序相反:先构造的后析构,后构造的先析构。对于成员对象,析构顺序与声明顺序相反;对于继承体系,先析构派生类,再析构基类。

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

4. 构造函数与析构函数的调用时机

构造函数在对象创建时调用,析构函数在对象销毁时调用。对象销毁的时机包括:局部对象离开作用域、动态对象被 delete(delete关键字是C++中后续用来申请空间new对应释放内存空间的关键字)、程序结束时全局对象销毁等。

void func() { Student s; // 构造 Student* p = new Student(); // 构造 delete p; // 析构 } // s 在此处析构

5.C++中栈的实现

#include<iostream> using namespace std; typedef int STDataType; class Stack { public: Stack(int n = 4) { a = (STDataType*)malloc(sizeof(STDataType) * n); if (a == nullptr) { perror("malloc failed!!!"); return; } top = 0; capacity = n; } Stack(const Stack& d) { a = (STDataType*)malloc(sizeof(STDataType) * d.capacity); if (a == nullptr) { perror("malloc failed!!!"); return; } memcpy(this->a, d.a, sizeof(STDataType)*d.top); top = d.top; capacity = d.capacity; } void StackPush(STDataType x) { //考虑扩容条件 if (top == capacity) { size_t newcapacity = capacity * 2; STDataType* temp = (STDataType*)realloc(a, sizeof(STDataType) * newcapacity); if (nullptr == temp) { perror("realloc failed!!!"); return; } a = temp; capacity = newcapacity; } a[top++] = x; } ~Stack() { free(a); a = nullptr; top = capacity = 0; } void STPrint() { while (top) { cout << a[--top] << endl; } } private: STDataType* a; size_t top; size_t capacity; }; int main() { Stack s1; s1.StackPush(1); s1.StackPush(2); s1.STPrint(); return 0; }

6. 总结

构造函数和析构函数是 C++ 类设计的基石。构造函数负责对象的正确初始化,析构函数负责资源的彻底释放。掌握默认构造、带参构造、拷贝构造、移动构造以及虚析构等关键概念,并遵循资源管理的规范,才能写出安全、高效的 C++ 代码。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进