C++大成之路:构造函数和析构函数

📅 2026/7/6 15:14:47
C++大成之路:构造函数和析构函数
C中的构造函数和析构函数核心原理、种类与实战应用在C面向对象编程中构造函数和析构函数是类的核心成员函数相互配合负责对象的“出生”与“消亡”是实现对象初始化、资源管理的关键机制。构造函数负责在对象创建时初始化成员变量、分配资源析构函数则负责在对象生命周期结束时释放资源、清理内存避免内存泄漏、资源浪费等问题。目录一构造函数对象的“初始化器”构造函数的使用注意事项二析构函数对象的“清理器”析构函数的使用注意事项三、构造函数与析构函数的调用顺序四、总结一构造函数对象的“初始化器”构造函数是一种特殊的成员函数其名称与类名完全一致无返回值无需写void在创建类的对象时自动被调用无法手动直接调用。其核心功能是初始化类的成员变量包括普通成员变量、静态成员变量部分场景以及分配对象所需的资源如动态内存、文件句柄等。构造函数的核心特性① 与类名同名无返回值② 自动调用对象创建时触发③ 支持重载C中的构造函数主要分为5类默认构造函数、带参构造函数、拷贝构造函数、移动构造函数、委托构造函数。1.默认构造函数无参的“默认初始化”默认构造函数是最基础的构造函数无参数、无实现或默认实现分为两种情况一是开发者未手动定义任何构造函数时编译器自动生成的默认构造函数二是开发者手动定义的无参构造函数可自定义实现。核心作用当创建对象时未传入任何参数自动调用默认构造函数对成员变量进行默认初始化内置类型变量初始化为随机值自定义类型变量调用其自身的默认构造函数。使用场景无需手动传入参数仅需创建一个“默认状态”的对象适用于成员变量无需复杂初始化、有默认值的场景。代码示例cpp#include iostreamusing namespace std;//定义一个学生类class Student {private:string name; // 自定义类型成员int age; // 内置类型成员float score; // 内置类型成员public:Student() {name 未知姓名;age 0;score 0.0f;cout 默认构造函数被调用对象初始化完成 endl;}void showInfo() {cout 姓名 name 年龄 age 成绩 score endl;}};int main() {Student s1;s1.showInfo();return 0;}注意若手动定义了任何构造函数如带参构造、拷贝构造编译器将不再自动生成默认构造函数此时若需要无参创建对象必须手动定义默认构造函数否则会编译报错。2.带参构造函数自定义“个性化初始化”带参构造函数是最常用的构造函数有参数列表开发者可根据需求传入参数对成员变量进行个性化初始化解决默认构造函数无法灵活初始化的问题。核心作用创建对象时通过传入参数为成员变量赋值使对象创建后就具备特定的状态适用于成员变量需要根据不同场景初始化不同值的场景。使用场景创建对象时需要指定初始值如学生对象指定姓名、年龄汽车对象指定品牌、价格等。代码示例cpp#include iostream#include stringusing namespace std;class Car {private:string brand; //汽车品牌float price; // 汽车价格int year; // 生产年份public:Car(string b, float p, int y) {brand b;price p;year y;cout 带参构造函数被调用对象初始化完成 endl;}Car(string b, float p) {brand b;price p;year 2024;cout 重载带参构造函数被调用对象初始化完成 endl;}void showCarInfo() {cout 品牌 brand 价格 price 年份 year endl;}};int main() {Car c1(奔驰, 50.0f, 2023);c1.showCarInfo();Car c2(宝马, 45.0f);c2.showCarInfo();return 0;}注意带参构造函数支持重载只要参数列表参数个数、参数类型、参数顺序不同即可定义多个带参构造函数满足不同的初始化场景调用时编译器会根据传入的参数自动匹配对应的构造函数。3.拷贝构造函数对象的“复制器”拷贝构造函数是一种特殊的带参构造函数参数为当前类的const引用const ClassName obj其核心作用是用一个已存在的对象创建一个完全相同的新对象复制对象的所有成员变量值。核心作用实现对象的“深拷贝”或“浅拷贝”当需要创建一个与已有对象完全一致的新对象时使用如对象作为函数参数传递、函数返回值为对象、主动复制对象等场景。使用场景① 对象作为函数参数值传递② 函数返回值为对象值返回③ 用已有对象初始化新对象如ClassName obj2 obj1。注意若未手动定义拷贝构造函数编译器会自动生成默认拷贝构造函数实现“浅拷贝”仅复制成员变量的值若成员变量为指针仅复制指针地址不复制指针指向的内容若成员变量包含动态内存如new分配的内存浅拷贝会导致内存泄漏此时必须手动定义拷贝构造函数实现深拷贝。代码示例浅拷贝与深拷贝对比cpp#include iostream#include string.husing namespace std;class StringDemo {private:char* str; //动态内存分配的字符串指针int len; // 字符串长度public:// 带参构造函数分配动态内存StringDemo(const char* s) {len strlen(s);str new char[len 1]; // 分配内存1存储\0strcpy(str, s);cout 带参构造函数被调用分配内存 endl;}// 3. 手动定义拷贝构造函数深拷贝StringDemo(const StringDemo other) {len other.len;// 深拷贝重新分配内存复制指针指向的内容str new char[len 1];strcpy(str, other.str);cout 拷贝构造函数被调用深拷贝完成 endl;}// 析构函数释放动态内存后续讲解~StringDemo() {delete[] str; // 释放数组内存cout 析构函数被调用内存释放 endl;}void showStr() {cout 字符串 str 长度 len endl;}};// 函数参数为对象值传递会调用拷贝构造函数void printString(StringDemo obj) {obj.showStr();}int main() {StringDemo s1(Hello C);s1.showStr(); // 输出字符串Hello C长度7// 用s1初始化s2调用拷贝构造函数深拷贝StringDemo s2 s1;s2.showStr(); // 输出字符串Hello C长度7// 对象作为函数参数调用拷贝构造函数printString(s1);return 0;}说明上述代码中拷贝构造函数实现了深拷贝为新对象重新分配了动态内存避免了两个对象共用同一块内存、析构时重复释放的问题若删除手动定义的拷贝构造函数编译器自动生成的浅拷贝会导致s1和s2的str指针指向同一块内存程序结束时析构函数会重复释放该内存引发程序崩溃。4.移动构造函数对象的“高效转移器”移动构造函数是C11新增的构造函数参数为当前类的右值引用ClassName obj其核心作用是“转移”已有对象的资源如动态内存、文件句柄而非复制资源从而提高程序效率避免不必要的内存拷贝。核心作用当对象是“临时对象”如函数返回的临时对象、匿名对象时移动构造函数会直接接管临时对象的资源无需重新分配内存大幅提升性能适用于需要频繁传递对象、且对象包含大量动态资源的场景。使用场景① 函数返回值为对象临时对象② 用匿名对象初始化新对象③ 调用std::move()将左值对象转为右值触发移动构造。注意移动构造函数会“窃取”临时对象的资源转移后原临时对象会被销毁但其资源已被新对象接管原对象不再拥有资源避免重复释放。代码示例cpp#include iostream#include string.husing namespace std;class MyArray {private:int* arr; //动态分配的数组指针int size; // 数组大小public:// 带参构造函数分配动态数组MyArray(int s) {size s;arr new int[size];// 初始化数组为0for (int i 0; i size; i) {arr[i] 0;}cout 带参构造函数被调用分配数组内存 endl;}// 4. 移动构造函数C11新增右值引用参数MyArray(MyArray other) {// 转移资源直接接管other的arr指针和sizearr other.arr;size other.size;// 将other的指针置空避免析构时重复释放other.arr nullptr;other.size 0;cout 移动构造函数被调用资源转移完成 endl;}// 析构函数释放动态数组~MyArray() {if (arr ! nullptr) {delete[] arr;arr nullptr;}cout 析构函数被调用数组内存释放 endl;}void showArray() {if (arr nullptr) {cout 数组为空 endl;return;}cout 数组元素;for (int i 0; i size; i) {cout arr[i] ;}cout endl;}};// 函数返回值为对象返回临时对象触发移动构造MyArray createArray(int size) {MyArray temp(size); // 创建临时对象return temp; // 返回临时对象触发移动构造}int main() {// 用函数返回的临时对象初始化arr1触发移动构造MyArray arr1 createArray(5);arr1.showArray(); // 输出数组元素0 0 0 0 0// 用匿名对象初始化arr2触发移动构造MyArray arr2 MyArray(3);arr2.showArray(); // 输出数组元素0 0 0return 0;}说明上述代码中createArray函数返回的临时对象通过移动构造函数将资源转移给arr1无需重新分配数组内存效率远高于拷贝构造函数转移后临时对象的arr指针被置空析构时不会释放资源避免了内存泄漏。5.委托构造函数构造函数的“复用器”委托构造函数是C11新增的特性其核心作用是让一个构造函数调用另一个构造函数实现构造函数代码的复用避免重复编写相同的初始化逻辑。核心作用当多个构造函数有相同的初始化逻辑时无需重复编写可让其中一个构造函数委托构造函数调用另一个构造函数目标构造函数简化代码提高可维护性。使用场景多个构造函数存在相同的初始化逻辑如默认构造、带参构造都需要初始化某个成员变量可通过委托构造复用代码。代码示例cpp#include iostream#include stringusing namespace std;class Person {private:string name;int age;string gender;public://目标构造函数带3个参数包含完整的初始化逻辑Person(string n, int a, string g) {name n;age a;gender g;cout 目标构造函数被调用 endl;}// 5. 委托构造函数1无参委托目标构造函数传入默认值Person() : Person(未知, 0, 未知) {cout 委托构造函数无参被调用 endl;}// 委托构造函数2带2个参数委托目标构造函数传入默认性别Person(string n, int a) : Person(n, a, 未知) {cout 委托构造函数带2参被调用 endl;}void showPersonInfo() {cout 姓名 name 年龄 age 性别 gender endl;}};int main() {// 调用无参委托构造函数间接调用目标构造函数Person p1;p1.showPersonInfo(); // 输出姓名未知年龄0性别未知// 调用带2参委托构造函数间接调用目标构造函数Person p2(张三, 20);p2.showPersonInfo(); // 输出姓名张三年龄20性别未知// 直接调用目标构造函数Person p3(李四, 22, 男);p3.showPersonInfo(); // 输出姓名李四年龄22性别男return 0;}说明上述代码中无参构造函数和带2参构造函数都是委托构造函数它们通过“初始化列表”调用带3参的目标构造函数复用了目标构造函数的初始化逻辑避免了重复编写name、age、gender的赋值代码简化了代码结构。构造函数的使用注意事项1. 构造函数无返回值无需写void即使没有任何实现也不能省略函数体或用default表示默认实现2. 构造函数可重载但参数列表必须不同个数、类型、顺序否则会编译报错3. 若成员变量为const或引用类型必须在构造函数的初始化列表中初始化不能在函数体中赋值4. 拷贝构造函数的参数必须是const引用避免无限递归调用若为值传递会再次调用拷贝构造函数5. 移动构造函数的参数必须是右值引用且转移资源后需将原对象的资源指针置空避免重复释放。二析构函数对象的“清理器”析构函数也是类的特殊成员函数名称为“~类名”无返回值、无参数无法重载一个类只能有一个析构函数在对象生命周期结束时自动被调用如对象超出作用域、delete删除动态对象核心作用是释放对象占用的资源如动态内存、文件句柄、网络连接等避免内存泄漏。析构函数的核心特性① 名称为~类名无返回值、无参数② 自动调用对象销毁时触发③ 无法重载一个类只有一个析构函数析构函数的核心功能是“清理资源”主要用于以下场景1. 释放动态内存若类的成员变量包含new、new[]分配的动态内存必须在析构函数中用delete、delete[]释放否则会导致内存泄漏2. 关闭资源若对象打开了文件、网络连接、数据库连接等需在析构函数中关闭这些资源避免资源浪费3. 清理其他资源如释放互斥锁、注销注册的回调函数等。结合之前的动态内存场景编写析构函数的完整示例重点展示资源释放的逻辑cpp#include iostream#include stringusing namespace std;class FileHandler {private:FILE* file; //文件指针需要手动关闭string fileName;public:// 带参构造函数打开文件FileHandler(string name) {fileName name;// 打开文件只读模式file fopen(name.c_str(), r);if (file ! nullptr) {cout 文件 fileName 打开成功 endl;} else {cout 文件 fileName 打开失败 endl;}}// 析构函数关闭文件释放资源~FileHandler() {if (file ! nullptr) {fclose(file); // 关闭文件file nullptr; // 置空指针避免野指针cout 文件 fileName 关闭成功资源释放 endl;}}// 读取文件内容简单示例void readFile() {if (file nullptr) {return;}char buf[1024] {0};fread(buf, 1, sizeof(buf), file);cout 文件内容 buf endl;}};int main() {// 创建对象打开文件FileHandler fh(test.txt);fh.readFile();// 当main函数结束对象fh超出作用域自动调用析构函数关闭文件return 0;}说明上述代码中FileHandler类的构造函数打开文件析构函数在对象销毁时关闭文件确保文件资源被正确释放避免文件句柄泄露若未定义析构函数编译器自动生成的默认析构函数不会关闭文件会导致文件资源无法释放。析构函数的使用注意事项1. 析构函数无参数、无返回值名称必须是~类名不能重载2. 若类包含动态内存new、new[]必须手动定义析构函数释放动态内存否则会导致内存泄漏3. 析构函数的调用顺序与构造函数相反先创建的对象后销毁后创建的对象先销毁如先创建obj1再创建obj2销毁时先调用obj2的析构函数再调用obj1的析构函数4. 动态分配的对象new创建必须用delete删除否则析构函数不会被调用会导致资源泄漏5. 继承场景中若基类有析构函数建议将基类析构函数定义为虚函数virtual确保派生类对象销毁时先调用派生类析构函数再调用基类析构函数避免资源泄漏后续继承相关内容会详细讲解。三、构造函数与析构函数的调用顺序构造函数和析构函数的调用顺序是固定的遵循“先构造、后析构”“先创建、后销毁”的原则具体分为两种场景1. 单个对象创建对象时调用构造函数对象销毁时调用析构函数顺序为“构造→析构”2. 多个对象构造顺序为对象创建的顺序析构顺序与构造顺序相反如obj1先创建obj2后创建析构时obj2先析构obj1后析构。代码示例多个对象的调用顺序cpp#include iostreamusing namespace std;class Test {private:int id;public:Test(int i) {id i;cout 构造函数对象 id 创建 endl;}~Test() {cout 析构函数对象 id 销毁 endl;}};int main() {Test t1(1); // 第一个创建构造函数调用Test t2(2); // 第二个创建构造函数调用Test t3(3); // 第三个创建构造函数调用// 函数结束对象销毁析构顺序与构造顺序相反return 0;}输出结果构造函数对象1创建构造函数对象2创建构造函数对象3创建析构函数对象3销毁析构函数对象2销毁析构函数对象1销毁从输出结果可以清晰看到多个对象的构造顺序与创建顺序一致析构顺序与构造顺序相反这是C规定的固定调用逻辑。四、总结构造函数和析构函数是C面向对象编程的基础二者共同负责对象的生命周期管理构造函数负责对象的初始化和资源分配确保对象创建后处于合法状态析构函数负责对象的资源清理和销毁避免内存泄漏和资源浪费。