C++笔记 构造函数 析构函数 及二者关系(面向对象)

在C++面向对象编程中,构造函数和析构函数是类的两个特殊成员函数,二者相互配合、缺一不可,共同负责对象的"生命周期管理"------构造函数负责对象的初始化(创建对象时自动执行),析构函数负责对象的清理(销毁对象时自动执行),是实现类封装特性、避免内存泄漏、保证程序安全的核心机制。本文将从构造函数、析构函数的核心定义、特性、用法入手,深入解析二者的内在关系,结合实例帮大家扎实掌握这两个基础知识点。

一、构造函数(Constructor)------ 对象的"初始化器"

1. 什么是构造函数

构造函数是类中一种特殊的非静态成员函数,,无需显式声明返回值(注意:不是返回void,而是根本没有返回值类型),当创建类的对象时(实例化对象),编译器会自动调用构造函数,完成对象的初始化工作(如给成员变量赋值、分配内存等)。

核心作用:初始化对象的成员变量,为对象的使用做好准备,避免对象处于未初始化的"垃圾值"状态,保证对象的安全性和合法性。

2. 构造函数的核心特性

与类名同名,无返回值(不能写return,也不能声明返回值类型,包括void)。

自动调用:仅在创建对象时(实例化)自动执行一次,开发者无法手动调用(手动调用会编译报错)。

可重载:一个类可以有多个构造函数,只要它们的参数列表(参数个数、参数类型、参数顺序)不同,满足函数重载的规则,用于不同场景下的对象初始化。

默认构造函数:如果类中没有手动定义任何构造函数,编译器会自动生成一个"无参默认构造函数",该函数为空,不做任何初始化操作;一旦手动定义了构造函数,编译器就不会再生成默认构造函数。

可访问性:通常定义为public权限(供外部创建对象时调用),若定义为private,则无法在类外创建对象(常用于单例模式)。

3. 构造函数的分类与实例演示

根据参数列表的不同,构造函数主要分为3类:无参构造函数、有参构造函数、拷贝构造函数(基础阶段重点掌握前两类,拷贝构造后续补充)。

cpp 复制代码
#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分配的数组、指针)时,必须手动定义析构函数,释放这些内存,否则会导致内存泄漏。

cpp 复制代码
#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点关键)

  1. :构造函数在对象创建时(实例化)自动执行一次,是对象生命周期的"起点";析构函数在对象销毁时自动执行一次,是对象生命周期的"终点"。

  2. :构造函数的核心是"初始化",为对象分配资源(如动态内存、初始化成员变量);析构函数的核心是"清理",释放构造函数分配的资源(如动态内存),避免资源浪费和内存泄漏。

  3. :当多个对象同时存在时,构造函数的执行顺序是"先创建先执行",析构函数的执行顺序是"先创建后执行"(即"先进后出")。 例:创建对象stu1、stu2,构造顺序:stu1构造 → stu2构造;销毁顺序:stu2析构 → stu1析构。

  4. :没有构造函数,对象无法完成初始化,无法正常使用;没有析构函数,对象占用的资源(尤其是动态内存)无法释放,会导致内存泄漏,程序运行不稳定。

2. 关系演示(直观理解执行顺序)

cpp 复制代码
#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++代码的关键。

相关推荐
Dxy12393102162 小时前
Python如何删除文件到回收站
开发语言·python
斌味代码2 小时前
RAG 实战:用 LangChain + DeepSeek 搭建企业私有知识库问答系统
开发语言·langchain·c#
talen_hx2962 小时前
《零基础入门Spark》学习笔记 Day 07
笔记·学习·spark
OxyTheCrack2 小时前
【C++】一篇文章详解C++17新特性
c++
懷淰メ2 小时前
python3GUI---基于PyQt5+YOLOv8+DeepSort的智慧行车可视化系统(详细介绍)
开发语言·yolo·计算机视觉·pyqt·yolov8·deepsort·车距
mmz12072 小时前
贪心算法3(c++)
c++·算法·贪心算法
Cathy Bryant2 小时前
拓扑学:曲面与圆环
笔记·线性代数·矩阵·拓扑学
j_xxx404_2 小时前
蓝桥杯基础--排序模板合集II(快速,归并,桶排序)
数据结构·c++·算法·蓝桥杯·排序算法
weixin_649555672 小时前
C语言程序设计第四版(何钦铭、颜晖)第十一章指针进阶之查找子串
c语言·开发语言