当前位置: 首页 > news >正文

类和对象(构造函数和析构函数)

类和对象(构造函数和析构函数)

文章目录

  • 类和对象(构造函数和析构函数)
    • 构造函数
      • 构造函数的特征
        • 1. 函数名与类名相同
        • 2. 没有返回类型
        • 3. 自动调用
        • 4. 可以重载
        • 5. 爱吃吃,不爱吃就不吃
          • 1. 基本数据类型成员(内置类型成员)
          • 2. 类类型成员(自定义类型成员)
          • 3. 数组类型成员
          • 4. 静态成员变量
          • 总结
          • 解决
      • 构造函数的类别
        • 按照谁写的分:
        • 按类型分:
          • 默认构造函数:
          • 非默认构造函数
    • 析构函数
      • 析构函数的作用
        • 1. 释放动态分配的内存
        • 2. 关闭文件或释放其他资源
      • 基本语法
      • 析构函数的特点
      • 默认析构函数
      • 析构函数和拷贝构造函数、移动构造函数的关系

构造函数

这个东西是啥呢? 这个东西就是C++祖师爷用C语言用恼了整出来的。这里的构造函数对应的就是C语言中的初始化函数。

之前咱们写栈昂,队列昂,这些东西都是要初始化一下才可以开始接着用的。因为一些数据结构里面有一些指针,你不初始化一下,直接用的后果就是很容易越界访问,或者访问到不该访问的东西,那不就报错了咯。

但是每次用之前都要初始化一下,就很麻烦,而且容易忘记。所以祖师爷呢就为了方便,写C++的时候,整出来了构造函数。构造函数是自动调用的,不用再去想有没有初始化了。

在这里插入图片描述

构造函数是一个特殊的函数,这个函数呢有点像是C++祖师爷钦定的一位大臣,辅佐我们,我们就是皇帝,这位大臣呢和别的小官(普通函数)不太一样。

构造函数的特征

1. 函数名与类名相同

构造函数的名称和它所属的类的名称一致。这样编译器才能识别它是构造函数,进而在创建对象时自动调用。示例如下:

class MyClass {
public:// 构造函数,函数名与类名相同MyClass() {// 初始化代码}
};
2. 没有返回类型

构造函数没有返回类型,甚至连void也不用。这是它与普通成员函数的显著区别。示例如下:

class MyClass {
public:// 构造函数,没有返回类型MyClass() {// 初始化代码}
};
3. 自动调用

在创建类的对象时,构造函数会被自动调用,从而完成对象的初始化工作。示例如下:

在这里插入图片描述

4. 可以重载

构造函数能够进行重载,也就是可以定义多个同名但参数列表不同的构造函数。这样就能根据不同的参数来选择合适的构造函数进行对象的初始化。示例如下:

class MyClass {
public:// 无参构造函数MyClass() {std::cout << "无参构造函数被调用" << std::endl;}// 带参数的构造函数MyClass(int value) {std::cout << "带参数的构造函数被调用,值为: " << value << std::endl;}
};int main() {MyClass obj1; // 调用无参构造函数MyClass obj2(10); // 调用带参数的构造函数return 0;
}

在这里插入图片描述

看到没.

5. 爱吃吃,不爱吃就不吃

诶,你不写,编译器会自动生成一个构造函数给你。这是“默认构造函数”的一种。也就是说,就算你忘记写构造函数,编译器也会帮你写,算是很负责任的了。不过它只管送饭,送的什么饭,它不管了。也就是说,编译器会送💩过来。

其实吧,这个编译器默认生成的构造函数用处不大,几乎很少类能用到。它这个自动生成的默认构造函数会怎么做呢?

1. 基本数据类型成员(内置类型成员)

对于基本数据类型(如intdoublechar等)的成员变量,默认构造函数不会对它们进行初始化,这些成员变量的值是未定义的(即包含垃圾值)。

#include <iostream>class MyClass {
public:int num;double d;
};int main() {MyClass obj;std::cout << "num: " << obj.num << ", d: " << obj.d << std::endl;return 0;
}

在上述代码中,obj.numobj.d的值是未定义的,输出结果是不确定的。

2. 类类型成员(自定义类型成员)

如果类包含其他类类型的成员,默认构造函数会调用这些成员对象的默认构造函数来初始化它们。

#include <iostream>class AnotherClass {
public:AnotherClass() {std::cout << "AnotherClass 默认构造函数被调用" << std::endl;}
};class MyClass {
public:AnotherClass member;
};int main() {MyClass obj;return 0;
}

在这个例子中,当创建MyClass对象obj时,默认构造函数会调用AnotherClass的默认构造函数来初始化member成员。

3. 数组类型成员

对于数组类型的成员,如果是基本数据类型数组,元素的值是未定义的;如果是类类型数组,会调用每个元素的默认构造函数进行初始化。

#include <iostream>class ElementClass {
public:ElementClass() {std::cout << "ElementClass 默认构造函数被调用" << std::endl;}
};class MyClass {
public:int intArray[3];ElementClass elementArray[2];
};int main() {MyClass obj;return 0;
}

在这个代码中,intArray的元素值是未定义的,而elementArray中的每个元素都会调用ElementClass的默认构造函数进行初始化。

4. 静态成员变量

静态成员变量不会由默认构造函数初始化,它们在类定义外进行初始化。

#include <iostream>class MyClass {
public:static int staticNum;
};int MyClass::staticNum = 10; // 静态成员变量的初始化int main() {MyClass obj;std::cout << "staticNum: " << MyClass::staticNum << std::endl;return 0;
}

在这个例子中,staticNum的初始化是在类定义外完成的,与默认构造函数无关。

总结

编译器生成的默认构造函数在初始化类时表现较为 “消极”,对于基本数据类型成员不做初始化,对于类类型成员则调用其默认构造函数进行初始化。而且不同编译器,做这件事情的时候还有有所区别,所以在实际编程中,为了确保对象的成员变量有合理的初始值,通常需要显式定义构造函数。

解决

这个只能算是一个小补救吧,这个自动生成的默认构造函数,不是不会对内置类型(int,char,int*)等初始化嘛,那么有个办法就是类里面声明成员变量的时候给个缺省值,这样编译器自动生成的默认构造函数就会用这个缺省值进行初始化。

在这里插入图片描述
在这里插入图片描述

那么我们什么时候可以不写构造函数,就用编译器帮我们生成的默认构造函数呢?

  1. 第一种就是“是否初始化”不会影响你结果的,你可以直接使用编译器默认生成的。
  2. 第二种就是你类里面的成员变量全是自定义类型变量,因为自定义类型变量会去调用它的默认构造函数嘛。
//也就是这种类型的
#include <iostream>class AnotherClass {
public:AnotherClass() {std::cout << "AnotherClass 默认构造函数被调用" << std::endl;}
};class MyClass {
public:AnotherClass member;
};int main() {MyClass obj;return 0;
}
  1. 第三种呢就是你在类里面声明成员变量的时候给了缺省值的,这种时候你不写构造函数用编译器的也是可以的。不过呢,这样就无法传参数初始化了,有些时候就会比较麻烦。

所以呢,绝大多数情况下,我们最好都根据具体问题,具体的情境写一个构造函数出来。不然出问题了,还是很麻烦的。

构造函数的类别

按照谁写的分:
  1. 编译器写的。当你没有写构造函数的时候,编译器就会帮你写一个构造函数,这个构造函数的类型呢是默认构造函数,默认构造函数有几种,这是其中一种。
  2. 你写的。只要你写了,编译器就不会写了。明白吧。
按类型分:
默认构造函数:
  1. 第一种默认构造函数,就是编译器写的构造函数。编译器写的构造函数是不需要我们做任何事情的,也不需要传参
  2. 第二种默认构造函数,我们自己写的构造函数,但是我们写了一个什么构造函数呢?写了一个没有参数的构造函数,也就是“无参构造函数”,也叫默认构造函数
  3. 第三种默认构造函数,肯定也是我们自己写的了,我们写了一个有参数的构造函数,但是是什么情况呢?我们写了一个全缺省的构造函数。也就是说可以不传参数就能用的构造函数。

总的来说,只要可以不传参数就能使用的构造函数就是默认构造函数。这3种默认构造函数不能同时存在,只能存在一个,否则编译器调用的时候就会有歧义,就会疑惑:我到底用哪个呢?明白吧。

但是其实是什么呢?其实是2种默认构造函数不能同时存在。为啥这么说?因为只要我们写了,编译器就不会写了,那么第一种默认构造就没有了,明白嘛?所以一般是后面两种我们自己写的时候要注意不能同时存在后面两种默认构造函数,明白嘛?就像下面那幅图那样:

在这里插入图片描述

非默认构造函数

那么要传参数的构造函数就不是默认构造函数了嘛,因为构造函数可以重载嘛,那么你就可以这样写:

#include <iostream>using std::cout;
using std::endl;class Date
{
public://够成函数重载Date(){_year = 1;_month = 1;_day = 1;}Date(int year , int month , int day ){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main() {Date d1;//你不需要传参数初始化,你就用你写的默认构造函数嘛Date d2(10, 10, 10);//需要传参数,就用你写的那个带参数的构造函数嘛return 0;
}

不过呢其实用不着这样,一般都是直接写一个全缺省的默认构造函数就够用了。不传参数的时候使用缺省值,传参数的时候就用传过来的参数嘛,对吧。

#include <iostream>using std::cout;
using std::endl;class Date
{
public://全缺省的默认构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main() {Date d1;Date d2(10, 10, 10);return 0;
}

这里顺带着讲一个问题:

在这里插入图片描述

析构函数

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

这部分的难点呢在于理解啥是完成对象中资源的清理工作。我们先弄懂析构函数最主要的作用是什么。

大家先看看这个代码:

#include <iostream>using std::cout;
using std::endl;class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main() {Date d1;Date d2(10, 10, 10);return 0;
}

你们觉得时间类,需要析构函数做些啥嘛?很明显其实根本不需要析构函数做什么,因为时间类里面的成员变量年份,月份,日,这些东西,你没必要去管它的。我们没必要去说每个对象用完之后把这些东西全部设置为0 。没必要。因为你设置为0和是随机值没啥区别昂,你又不用了,就算用,你后面再用的时候构造函数也会重新初始化一遍,是吧。所以析构函数在这里发挥不出真正的作用。

析构函数的作用

1. 释放动态分配的内存

当使用 new 运算符动态分配内存时(或者malloc动态开辟的空间),需要使用 delete 运算符(或free运算符)来释放内存。析构函数能够确保在对象销毁时释放这些动态分配的内存,防止内存泄漏。

#include <iostream>class MyArray {
private:int* data;int size;
public:MyArray(int s) {size = s;data = new int[size];std::cout << "Array created" << std::endl;}~MyArray() {delete[] data;std::cout << "Array destroyed" << std::endl;}
};int main() {MyArray arr(5); // 调用构造函数创建数组return 0; // 离开作用域,调用析构函数释放数组内存
}
2. 关闭文件或释放其他资源

如果对象在其生命周期内打开了文件或者获取了其他系统资源,那么在对象销毁时,析构函数可以确保这些资源被正确释放。

#include <iostream>
#include <fstream>class FileHandler {
private:std::ofstream file;
public:FileHandler(const char* filename) {file.open(filename);std::cout << "File opened" << std::endl;}~FileHandler() {if (file.is_open()) {file.close();std::cout << "File closed" << std::endl;}}
};int main() {FileHandler fh("test.txt"); // 调用构造函数打开文件return 0; // 离开作用域,调用析构函数关闭文件
}

这以上两个才是析构函数真正的作用。

在这里插入图片描述

应该说的挺清楚的了。

在 C++ 里,析构函数是一种特殊的成员函数,它会在对象的生命周期结束时自动调用,其主要作用是进行资源清理工作,比如释放动态分配的内存、关闭文件、释放网络连接等。

基本语法

析构函数的名称和类名相同,不过前面要加一个波浪号 ~,并且析构函数没有返回类型,也不接受任何参数。以下是一个简单示例:

#include <iostream>class MyClass {
public:// 构造函数MyClass() {std::cout << "Constructor called" << std::endl;}// 析构函数~MyClass() {std::cout << "Destructor called" << std::endl;}
};int main() {MyClass obj; // 调用构造函数return 0; // 离开作用域,调用析构函数
}

在这个示例中,当 obj 对象离开 main 函数的作用域时,析构函数就会被自动调用。

在这里插入图片描述

析构函数的特点

  • 自动调用:在对象的生命周期结束时,析构函数会被自动调用,无需手动调用。
  • 不能重载:析构函数没有参数,因此不能像普通成员函数那样进行重载。
  • 不返回值:析构函数没有返回类型,也不能指定返回值。

默认析构函数

这里和默认构造函数很像,如果你不写析构函数,编译器会帮你写,编译器写的析构函数,就是默认析构函数,也只有一个默认析构函数,因为不能重载嘛。那么它的行为呢和编译器自动生成的构造函数比较相像:

  1. 内置类型成员不做处理
  2. 自定义类型成员会去调用它的析构函数

//以下这部分现阶段可以先不管。

析构函数和拷贝构造函数、移动构造函数的关系

如果类中包含动态分配的资源,那么在编写拷贝构造函数和移动构造函数时,需要特别注意资源的管理,避免出现资源泄漏或者悬空指针的问题。例如,在拷贝构造函数中可能需要进行深拷贝,而在移动构造函数中则需要进行资源的转移。

总之,析构函数在 C++ 中是非常重要的,它能够确保对象在销毁时正确地释放所占用的资源,从而避免资源泄漏和其他潜在的问题。

相关文章:

  • 修改RK3568 UBUNTU开机画面
  • Python实现异步编程的重要方式【协程(Coroutine)函数】(内含详细案例)
  • win11中wsl在自定义位置安装ubuntu20.04 + ROS Noetic
  • 将视频生成视频二维码步骤
  • Python协程详解:从基础到实战
  • 技巧-多数元素
  • 软件开发过程通常包含多个阶段,结合 AI 应用,可规划出以下 Markdown 文件名称的资料来记录各阶段内容
  • 深度强化学习的AI智能体实战:从训练到部署全流程解析
  • 码上云端·实战征文|无需邀请码,OpenManus深度测评
  • Python中的 for 与 迭代器
  • 第14章:MCP服务端项目开发实战:多模态信息处理
  • 每日算法-250424
  • 黑客密码:解锁互联网提问的智慧密码
  • 解决NSMutableData appendData性能开销太大的问题
  • Linux命令行基础入门详解
  • 09前端项目----分页功能
  • 通过监督微调(SFT)提升AI Agent效果的完整指南
  • 2025年3月电子学会青少年机器人技术(五级)等级考试试卷-实际操作
  • 小刚说C语言刷题——1317正多边形每个内角的度数?
  • 项目班——0419——chrono时间库
  • 中宣部版权管理局:微短剧出海面临版权交易不畅、海外维权较难等难题
  • 魏晓栋已任上海崇明区委常委、组织部部长
  • 恒瑞医药一季度营收72亿元,净利增超36%:授权交易推动利润增长
  • 天问三号计划2028年前后发射实施,开放20千克质量资源
  • 聚焦“共赢蓝色未来” “海洋命运共同体”上海论坛举行
  • 经济日报刊文:如何破除“内卷式”竞争