在C面向对象编程中构造函数和析构函数是类的两个特殊成员函数二者相互配合、缺一不可共同负责对象的“生命周期管理”——构造函数负责对象的初始化创建对象时自动执行析构函数负责对象的清理销毁对象时自动执行是实现类封装特性、避免内存泄漏、保证程序安全的核心机制。本文将从构造函数、析构函数的核心定义、特性、用法入手深入解析二者的内在关系结合实例帮大家扎实掌握这两个基础知识点。一、构造函数Constructor—— 对象的“初始化器”1. 什么是构造函数构造函数是类中一种特殊的非静态成员函数无需显式声明返回值注意不是返回void而是根本没有返回值类型当创建类的对象时实例化对象编译器会自动调用构造函数完成对象的初始化工作如给成员变量赋值、分配内存等。核心作用初始化对象的成员变量为对象的使用做好准备避免对象处于未初始化的“垃圾值”状态保证对象的安全性和合法性。2. 构造函数的核心特性与类名同名无返回值不能写return也不能声明返回值类型包括void。自动调用仅在创建对象时实例化自动执行一次开发者无法手动调用手动调用会编译报错。可重载一个类可以有多个构造函数只要它们的参数列表参数个数、参数类型、参数顺序不同满足函数重载的规则用于不同场景下的对象初始化。默认构造函数如果类中没有手动定义任何构造函数编译器会自动生成一个“无参默认构造函数”该函数为空不做任何初始化操作一旦手动定义了构造函数编译器就不会再生成默认构造函数。可访问性通常定义为public权限供外部创建对象时调用若定义为private则无法在类外创建对象常用于单例模式。3. 构造函数的分类与实例演示根据参数列表的不同构造函数主要分为3类无参构造函数、有参构造函数、拷贝构造函数基础阶段重点掌握前两类拷贝构造后续补充。#include iostream #include string using namespace std; class Student { private: string name; int age; int studentId; public: // 1. 无参构造函数默认构造函数手动定义 Student() { // 初始化成员变量避免垃圾值 name 未知; age 0; studentId 0; cout 无参构造函数调用对象初始化完成 endl; } // 2. 有参构造函数重载用于指定初始化值 Student(string n, int a, int id) { name n; age a; studentId id; cout 有参构造函数调用对象初始化完成 endl; } // 成员函数显示对象信息 void showInfo() { cout 学号 studentId 姓名 name 年龄 age endl; } }; int main() { // 调用无参构造函数创建对象注意无参构造不能加()否则会被识别为函数声明 Student stu1; stu1.showInfo(); // 调用有参构造函数创建对象两种写法均可 Student stu2(张三, 18, 2024001); Student stu3 Student(李四, 19, 2024002); stu2.showInfo(); stu3.showInfo(); return 0; } // 运行结果 // 无参构造函数调用对象初始化完成 // 学号0姓名未知年龄0 // 有参构造函数调用对象初始化完成 // 学号2024001姓名张三年龄18 // 有参构造函数调用对象初始化完成 // 学号2024002姓名李四年龄19说明上述代码中手动定义了无参和有参构造函数编译器不再生成默认无参构造创建对象时根据是否传入参数自动匹配对应的构造函数完成成员变量的初始化。注意无参构造创建对象时不能写成Student stu1();这会被编译器识别为“声明一个返回值为Student类型的无参函数”而非创建对象。4. 构造函数的注意事项构造函数不能是静态成员函数静态函数属于类无this指针无法初始化对象的非静态成员。若手动定义了有参构造函数又需要使用无参构造创建对象必须手动定义无参构造函数否则编译器不生成会编译报错。构造函数可以初始化所有成员变量包括private权限的成员无需通过setter接口。二、析构函数Destructor—— 对象的“清理工”1. 什么是析构函数析构函数也是类中一种特殊的非静态成员函数波浪线类名同样无需显式声明返回值当对象的生命周期结束时如对象出作用域、用delete删除动态对象编译器会自动调用析构函数完成对象的清理工作如释放动态分配的内存、关闭文件等。核心作用清理对象占用的资源避免内存泄漏尤其是当对象中包含动态分配的内存如new关键字分配的空间时析构函数是释放这些资源的唯一途径。2. 析构函数的核心特性名称为“~类名”无返回值也不能有任何参数因此析构函数一个类只能有一个析构函数。自动调用仅在对象生命周期结束时自动执行一次开发者无法手动调用手动调用无意义且可能导致重复清理。默认析构函数如果类中没有手动定义析构函数编译器会自动生成一个默认析构函数该函数为空仅负责销毁对象本身不做额外的资源清理若对象有动态分配的内存默认析构函数无法释放会导致内存泄漏。可访问性通常定义为public权限若定义为private对象生命周期结束时无法自动调用会导致资源泄漏。3. 析构函数的实例演示重点动态内存清理当对象中包含动态分配的内存如用new分配的数组、指针时必须手动定义析构函数释放这些内存否则会导致内存泄漏。#include iostream #include string using namespace std; class Student { private: string name; int* score; // 动态分配的分数指针需要手动释放 public: // 有参构造函数动态分配内存 Student(string n, int s) { name n; score new int(s); // 动态分配int类型空间存储分数s cout 有参构造函数调用对象初始化动态内存分配完成 endl; } // 手动定义析构函数释放动态分配的内存 ~Student() { delete score; // 释放score指向的动态内存 score nullptr; // 避免野指针将指针置空 cout 析构函数调用对象清理动态内存释放完成 endl; } // 显示对象信息 void showInfo() { cout 姓名 name 分数 *score endl; } }; int main() { // 创建对象栈上对象出main函数作用域后自动销毁 Student stu(王五, 95); stu.showInfo(); // 创建动态对象堆上对象需手动用delete删除否则不会自动销毁 Student* stu2 new Student(赵六, 88); stu2-showInfo(); delete stu2; // 手动删除动态对象触发析构函数 cout main函数执行结束 endl; return 0; } // 运行结果 // 有参构造函数调用对象初始化动态内存分配完成 // 姓名王五分数95 // 有参构造函数调用对象初始化动态内存分配完成 // 姓名赵六分数88 // 析构函数调用对象清理动态内存释放完成 // main函数执行结束 // 析构函数调用对象清理动态内存释放完成说明上述代码中score是动态分配的指针构造函数中用new分配内存析构函数中用delete释放内存避免内存泄漏栈上的对象stu在main函数执行结束、出作用域时自动调用析构函数堆上的对象stu2必须手动用delete删除才能触发析构函数否则会导致动态内存无法释放造成内存泄漏。4. 析构函数的注意事项析构函数不能有参数因此不能重载一个类只能有一个析构函数。若对象中没有动态分配的资源可不用手动定义析构函数编译器生成的默认析构函数足够使用。析构函数的执行顺序与构造函数相反先创建的对象后销毁即后调用析构函数。动态创建的对象new创建必须用delete手动删除否则析构函数不会被调用导致资源泄漏。三、构造函数与析构函数的核心关系构造函数和析构函数是“成对出现、相互配合”的关系二者共同管理对象的生命周期如同“对象的出生与死亡”——构造函数负责“出生初始化”析构函数负责“死亡清理”二者的执行时机、作用互补缺一不可。1. 核心关系总结4点关键构造函数在对象创建时实例化自动执行一次是对象生命周期的“起点”析构函数在对象销毁时自动执行一次是对象生命周期的“终点”。构造函数的核心是“初始化”为对象分配资源如动态内存、初始化成员变量析构函数的核心是“清理”释放构造函数分配的资源如动态内存避免资源浪费和内存泄漏。当多个对象同时存在时构造函数的执行顺序是“先创建先执行”析构函数的执行顺序是“先创建后执行”即“先进后出”。 例创建对象stu1、stu2构造顺序stu1构造 → stu2构造销毁顺序stu2析构 → stu1析构。没有构造函数对象无法完成初始化无法正常使用没有析构函数对象占用的资源尤其是动态内存无法释放会导致内存泄漏程序运行不稳定。2. 关系演示直观理解执行顺序#include iostream using namespace std; class Test { private: int id; public: // 构造函数记录对象id Test(int i) { id i; cout 构造函数调用对象 id 创建 endl; } // 析构函数记录对象id ~Test() { cout 析构函数调用对象 id 销毁 endl; } }; int main() { cout 进入main函数创建对象 endl; Test t1(1); // 第一个对象先构造 Test t2(2); // 第二个对象后构造 cout main函数核心逻辑执行完毕 endl; return 0; } // 运行结果 // 进入main函数创建对象 // 构造函数调用对象1创建 // 构造函数调用对象2创建 // main函数核心逻辑执行完毕 // 析构函数调用对象2销毁 // 析构函数调用对象1销毁说明从运行结果可以清晰看出构造顺序是t1→t2先创建先构造析构顺序是t2→t1先创建后析构完美体现了二者“构造与析构顺序相反”的核心关系。3. 常见误区规避错误误区1认为析构函数不重要不手动定义。—— 若对象有动态分配的资源new、malloc不定义析构函数会导致内存泄漏长期运行会导致程序崩溃。误区2手动调用构造函数或析构函数。—— 二者均由编译器自动调用手动调用无意义甚至会导致重复初始化或重复清理如重复delete动态内存。误区3认为构造函数和析构函数可以重载。—— 构造函数可重载多参数析构函数不能重载无参数。误区4动态创建的对象不手动delete。—— new创建的对象必须用delete删除否则析构函数不会执行资源无法释放。四、总结扎根核心梳理逻辑1. 构造函数与类同名无返回值可重载对象创建时自动执行负责初始化成员变量、分配资源是对象的“初始化器”。2. 析构函数名称为~类名无返回值无参数不能重载对象销毁时自动执行负责释放资源、清理对象是对象的“清理工”。3. 二者关系成对出现、作用互补、执行顺序相反共同管理对象的生命周期是C面向对象编程中保证对象安全、避免内存泄漏的核心机制。掌握构造函数和析构函数的用法及关系是后续学习拷贝构造、动态对象、继承中的构造/析构调用等知识点的基础也是编写安全、高效C代码的关键。
C++笔记 构造函数 析构函数 及二者关系(面向对象)
发布时间:2026/5/23 18:52:51
在C面向对象编程中构造函数和析构函数是类的两个特殊成员函数二者相互配合、缺一不可共同负责对象的“生命周期管理”——构造函数负责对象的初始化创建对象时自动执行析构函数负责对象的清理销毁对象时自动执行是实现类封装特性、避免内存泄漏、保证程序安全的核心机制。本文将从构造函数、析构函数的核心定义、特性、用法入手深入解析二者的内在关系结合实例帮大家扎实掌握这两个基础知识点。一、构造函数Constructor—— 对象的“初始化器”1. 什么是构造函数构造函数是类中一种特殊的非静态成员函数无需显式声明返回值注意不是返回void而是根本没有返回值类型当创建类的对象时实例化对象编译器会自动调用构造函数完成对象的初始化工作如给成员变量赋值、分配内存等。核心作用初始化对象的成员变量为对象的使用做好准备避免对象处于未初始化的“垃圾值”状态保证对象的安全性和合法性。2. 构造函数的核心特性与类名同名无返回值不能写return也不能声明返回值类型包括void。自动调用仅在创建对象时实例化自动执行一次开发者无法手动调用手动调用会编译报错。可重载一个类可以有多个构造函数只要它们的参数列表参数个数、参数类型、参数顺序不同满足函数重载的规则用于不同场景下的对象初始化。默认构造函数如果类中没有手动定义任何构造函数编译器会自动生成一个“无参默认构造函数”该函数为空不做任何初始化操作一旦手动定义了构造函数编译器就不会再生成默认构造函数。可访问性通常定义为public权限供外部创建对象时调用若定义为private则无法在类外创建对象常用于单例模式。3. 构造函数的分类与实例演示根据参数列表的不同构造函数主要分为3类无参构造函数、有参构造函数、拷贝构造函数基础阶段重点掌握前两类拷贝构造后续补充。#include iostream #include string using namespace std; class Student { private: string name; int age; int studentId; public: // 1. 无参构造函数默认构造函数手动定义 Student() { // 初始化成员变量避免垃圾值 name 未知; age 0; studentId 0; cout 无参构造函数调用对象初始化完成 endl; } // 2. 有参构造函数重载用于指定初始化值 Student(string n, int a, int id) { name n; age a; studentId id; cout 有参构造函数调用对象初始化完成 endl; } // 成员函数显示对象信息 void showInfo() { cout 学号 studentId 姓名 name 年龄 age endl; } }; int main() { // 调用无参构造函数创建对象注意无参构造不能加()否则会被识别为函数声明 Student stu1; stu1.showInfo(); // 调用有参构造函数创建对象两种写法均可 Student stu2(张三, 18, 2024001); Student stu3 Student(李四, 19, 2024002); stu2.showInfo(); stu3.showInfo(); return 0; } // 运行结果 // 无参构造函数调用对象初始化完成 // 学号0姓名未知年龄0 // 有参构造函数调用对象初始化完成 // 学号2024001姓名张三年龄18 // 有参构造函数调用对象初始化完成 // 学号2024002姓名李四年龄19说明上述代码中手动定义了无参和有参构造函数编译器不再生成默认无参构造创建对象时根据是否传入参数自动匹配对应的构造函数完成成员变量的初始化。注意无参构造创建对象时不能写成Student stu1();这会被编译器识别为“声明一个返回值为Student类型的无参函数”而非创建对象。4. 构造函数的注意事项构造函数不能是静态成员函数静态函数属于类无this指针无法初始化对象的非静态成员。若手动定义了有参构造函数又需要使用无参构造创建对象必须手动定义无参构造函数否则编译器不生成会编译报错。构造函数可以初始化所有成员变量包括private权限的成员无需通过setter接口。二、析构函数Destructor—— 对象的“清理工”1. 什么是析构函数析构函数也是类中一种特殊的非静态成员函数波浪线类名同样无需显式声明返回值当对象的生命周期结束时如对象出作用域、用delete删除动态对象编译器会自动调用析构函数完成对象的清理工作如释放动态分配的内存、关闭文件等。核心作用清理对象占用的资源避免内存泄漏尤其是当对象中包含动态分配的内存如new关键字分配的空间时析构函数是释放这些资源的唯一途径。2. 析构函数的核心特性名称为“~类名”无返回值也不能有任何参数因此析构函数一个类只能有一个析构函数。自动调用仅在对象生命周期结束时自动执行一次开发者无法手动调用手动调用无意义且可能导致重复清理。默认析构函数如果类中没有手动定义析构函数编译器会自动生成一个默认析构函数该函数为空仅负责销毁对象本身不做额外的资源清理若对象有动态分配的内存默认析构函数无法释放会导致内存泄漏。可访问性通常定义为public权限若定义为private对象生命周期结束时无法自动调用会导致资源泄漏。3. 析构函数的实例演示重点动态内存清理当对象中包含动态分配的内存如用new分配的数组、指针时必须手动定义析构函数释放这些内存否则会导致内存泄漏。#include iostream #include string using namespace std; class Student { private: string name; int* score; // 动态分配的分数指针需要手动释放 public: // 有参构造函数动态分配内存 Student(string n, int s) { name n; score new int(s); // 动态分配int类型空间存储分数s cout 有参构造函数调用对象初始化动态内存分配完成 endl; } // 手动定义析构函数释放动态分配的内存 ~Student() { delete score; // 释放score指向的动态内存 score nullptr; // 避免野指针将指针置空 cout 析构函数调用对象清理动态内存释放完成 endl; } // 显示对象信息 void showInfo() { cout 姓名 name 分数 *score endl; } }; int main() { // 创建对象栈上对象出main函数作用域后自动销毁 Student stu(王五, 95); stu.showInfo(); // 创建动态对象堆上对象需手动用delete删除否则不会自动销毁 Student* stu2 new Student(赵六, 88); stu2-showInfo(); delete stu2; // 手动删除动态对象触发析构函数 cout main函数执行结束 endl; return 0; } // 运行结果 // 有参构造函数调用对象初始化动态内存分配完成 // 姓名王五分数95 // 有参构造函数调用对象初始化动态内存分配完成 // 姓名赵六分数88 // 析构函数调用对象清理动态内存释放完成 // main函数执行结束 // 析构函数调用对象清理动态内存释放完成说明上述代码中score是动态分配的指针构造函数中用new分配内存析构函数中用delete释放内存避免内存泄漏栈上的对象stu在main函数执行结束、出作用域时自动调用析构函数堆上的对象stu2必须手动用delete删除才能触发析构函数否则会导致动态内存无法释放造成内存泄漏。4. 析构函数的注意事项析构函数不能有参数因此不能重载一个类只能有一个析构函数。若对象中没有动态分配的资源可不用手动定义析构函数编译器生成的默认析构函数足够使用。析构函数的执行顺序与构造函数相反先创建的对象后销毁即后调用析构函数。动态创建的对象new创建必须用delete手动删除否则析构函数不会被调用导致资源泄漏。三、构造函数与析构函数的核心关系构造函数和析构函数是“成对出现、相互配合”的关系二者共同管理对象的生命周期如同“对象的出生与死亡”——构造函数负责“出生初始化”析构函数负责“死亡清理”二者的执行时机、作用互补缺一不可。1. 核心关系总结4点关键构造函数在对象创建时实例化自动执行一次是对象生命周期的“起点”析构函数在对象销毁时自动执行一次是对象生命周期的“终点”。构造函数的核心是“初始化”为对象分配资源如动态内存、初始化成员变量析构函数的核心是“清理”释放构造函数分配的资源如动态内存避免资源浪费和内存泄漏。当多个对象同时存在时构造函数的执行顺序是“先创建先执行”析构函数的执行顺序是“先创建后执行”即“先进后出”。 例创建对象stu1、stu2构造顺序stu1构造 → stu2构造销毁顺序stu2析构 → stu1析构。没有构造函数对象无法完成初始化无法正常使用没有析构函数对象占用的资源尤其是动态内存无法释放会导致内存泄漏程序运行不稳定。2. 关系演示直观理解执行顺序#include iostream using namespace std; class Test { private: int id; public: // 构造函数记录对象id Test(int i) { id i; cout 构造函数调用对象 id 创建 endl; } // 析构函数记录对象id ~Test() { cout 析构函数调用对象 id 销毁 endl; } }; int main() { cout 进入main函数创建对象 endl; Test t1(1); // 第一个对象先构造 Test t2(2); // 第二个对象后构造 cout main函数核心逻辑执行完毕 endl; return 0; } // 运行结果 // 进入main函数创建对象 // 构造函数调用对象1创建 // 构造函数调用对象2创建 // main函数核心逻辑执行完毕 // 析构函数调用对象2销毁 // 析构函数调用对象1销毁说明从运行结果可以清晰看出构造顺序是t1→t2先创建先构造析构顺序是t2→t1先创建后析构完美体现了二者“构造与析构顺序相反”的核心关系。3. 常见误区规避错误误区1认为析构函数不重要不手动定义。—— 若对象有动态分配的资源new、malloc不定义析构函数会导致内存泄漏长期运行会导致程序崩溃。误区2手动调用构造函数或析构函数。—— 二者均由编译器自动调用手动调用无意义甚至会导致重复初始化或重复清理如重复delete动态内存。误区3认为构造函数和析构函数可以重载。—— 构造函数可重载多参数析构函数不能重载无参数。误区4动态创建的对象不手动delete。—— new创建的对象必须用delete删除否则析构函数不会执行资源无法释放。四、总结扎根核心梳理逻辑1. 构造函数与类同名无返回值可重载对象创建时自动执行负责初始化成员变量、分配资源是对象的“初始化器”。2. 析构函数名称为~类名无返回值无参数不能重载对象销毁时自动执行负责释放资源、清理对象是对象的“清理工”。3. 二者关系成对出现、作用互补、执行顺序相反共同管理对象的生命周期是C面向对象编程中保证对象安全、避免内存泄漏的核心机制。掌握构造函数和析构函数的用法及关系是后续学习拷贝构造、动态对象、继承中的构造/析构调用等知识点的基础也是编写安全、高效C代码的关键。