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

类和对象(中上)

ʕ • ᴥ • ʔ

づ♡ど

 🎉 欢迎点赞支持🎉

个人主页:励志不掉头发的内向程序员;

专栏主页:C++语言;


文章目录

前言

一、类的默认成员函数

二、构造函数

三、析构函数

总结


前言

我们上一章节已经大致的了解了类和对象的基本框架和写法,这一章节我们就来了解了解它里面的几个非常重要的函数,这些函数对于类和对象以及我们C++以后的学习至关重要,也决定了我们是否能够学好C++这一门富有创造性的语言。接下来我们就一起来进行我们愉快的学习吧。


一、类的默认成员函数

默认成员函数就是我们用户没有显示的写出来,但是我们的编译器会自动生成的函数。我们的一个类在不写的情况下默认会自动生成以下的6个默认成员函数,在这6个中最重要的就是前4个,最后2个不是很重要,我们了解即可。我们要得知道这两个方面才算是学明白了默认成员函数:

1、我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求

2、编译器默认生成的函数的函数不满足我们的需求,我们需要自己实现时,该如何自己实现

二、构造函数

首先是我们的构造函数,它是一个特殊的成员函数,它的主要任务并非是开辟空间创建对象,而是我们的对象实例化时初始化对象。构造函数其实就相当于取代了我们之前的Init函数的功能,而且构造函数是自动调用的,完美替代了Init功能。

我们来看看构造函数的特点:

1、构造函数的函数名与类名相同

2、构造函数没有返回值,甚至连void都不用写(C++规定如此,不必纠结)

3、对象实例化时系统会自动调用对应的构造函数

4、构造函数可以重载

5、如果类中没有显示的定义构造函数,那么C++编译器会自动生成一个无参的默认构造函数,一旦用户定义编译器就不会再自动生成了。

6、无参构造函数、全缺省构造函数以及我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。我们的无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义(前面C++基础的函数重载就有说过)。由此可以得知只要不传实参就可以调用的构造就叫做默认构造

7、我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。如果是自定义类型的成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量没有写默认构造函数,那么编译器就会报错。

注:C++把我们的类型分为内置类型和自定义类型两大类,像我们int/double/long long等就是语言提供的原生数据类型,自定义类型就是我们使用class/struct等关键字自己定义类型

我们可以看到,构造函数的规则还是蛮多的,我们来一条一条看看吧。

class Date
{
public:// 1. 无参构造函数Date(){_year = 1;_month = 1;_day = 1;}// 2. 带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}// 3. 全缺省构造函数//Date(int year = 1, int month = 1, int day = 1)//{//	_year = year;//	_month = month;//	_day = day;//}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};

我们从上面的代码可以看出,我们的构造函数和我们的类名是完全一样的,同时它是没有任何的返回值的,也不用把返回值写出来,这就是我们的构造函数的基本写法。我们的第一个无参构造函数,我们在函数体中进行初始化,由于没有参数我们就只能给他们一个固定值。第二个就是带参的构造函数,这个函数就可以在创建时给予它自己想要的初始化的内容。同时它又是第一个无参的构造的函数重载,就变成了如果我们在外界没有给他赋值就是默认都初始化为1,如果给它们赋值了就默认初始化为我们想要的值。我们第三个全缺省默认构造函数的效果就和它们两个融合在一起的一样,但是它不能和第一个同时出现。

int main()
{// 想要调用无参的构造函数时不需要加(),C++规定的// 原因是如果加上(),可能和函数的声明区分不开Date d1;d1.Print();Date d2(2025, 7, 15);d2.Print();return 0;
}

我们可以运行看看效果。

我们可以看到,我们没有像之前一样调用初始化函数,但是也达到了我们的目的,这就是我们的构造函数比原来用来初始化的函数的优点,它是系统自动调用的,完全不用我们来操心。

这就是前面4点的主要内容,接下来来看看剩下的3点。

我们在没有写构造函数时系统会自动生成一个无参的默认构造函数,这是因为系统不知道到底我们有哪些成员变量需要初始化,哪些不需要,所以索性就直接创建一个无参的默认构造。

我们可以看到如果让系统自己生成一个默认构造函数是十分不靠谱的,因为C++没有规定到底要初始化成什么样子,所以这些初始化到底是什么全看不同的编译器行为,所以我们在写类时一定要写构造函数。

对于我们自定义类型的变量我们如果不写构造函数只靠系统提供的默认构造函数的话就会在编译器层面报错。

只要满足上面的前4点就是一个构造函数了,但是想要是默认构造函数就得在满足前4点的情况下又多些限制,那就是我们可以不用传实参就可以调用的构造函数就是默认构造函数。我们有的时候认为默认构造函数就是系统自动生成的函数,其实有这种想法是因为它的名字导致的,向我们刚才写的第1个无参的构造函数和第3个全缺省的构造函数都是可以不用传参就可以调用的函数,这个和我们系统在没有写构造函数时自动生成的函数全都是默认构造函数。

class Date
{
public:// 无参构造函数也是默认构造函数// 不用传实参,因为没有形参Date(){_year = 1;_month = 1;_day = 1;}
};
class Date
{
public:// 全缺省构造函数也是默认构造函数,// 不用传实参,全全缺省参数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
};
class Date
{
public:// 不写构造,系统自动生成的构造函数// 也是默认构造函数
};

这3种默认构造函数存在一种,不能同时存在,一般写全缺省默认构造函数即可。

我们构造函数大部分情况下都得自己写,我们不能指望编译器个体行为,因为到时候如果换了编译器没有自己初始化的行为那我们就相当于写了一个bug。

三、析构函数

析构函数与构造函数的功能完全相反,C++规定对象在销毁时会自动调用我们的析构函数,完成对象中资源清理释放的工作。析构函数的功能类就相当于我们之前写的Destroy的功能,如果一个类没有析构函数,其实就是没有资源需要释放,可以理解为这个类全是内置类型而完全没有自定义类型的。因为内置类型在函数结束后系统会自动销毁,我们没有必要去写一个析构去销毁它。

我们来看看析构函数的特点:

1、析构函数名就是在类名称前加上字符~

2、无参数无返回值(和构造函数类似,也不需要加void等返回值)

3、一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数

4、对象的生命周期结束时,系统会自动调用析构函数

5、跟构造函数类似,我们不写编译器会自动生成,自动生成的析构函数对内置类型不做处理,自定义类型成员会调用他的析构函数

6、要注意我们显示写析构函数,对于自定义类型成员也会调用他的析构函数,也就是说自定义类型成员无论什么情况都会自动调用析构函数

typedef int STDateType;
class Stack
{
public:// 我们这里创建了自定义的类型,所以一定要初始化// 否则编译器会报错。Stack(int n = 4){_a = (STDateType*)malloc(sizeof(STDateType) * n);if (_a == nullptr){perror("malloc申请失败");}_capacity = n;_top = 0;}// 我们有自定义的类型,一定要析构// 否则会内存泄露。~Stack(){free(_a);_a = nullptr;// 内置类型析不析构都无所谓,系统本来函数结束都会自动析构_capacity = _top = 0;}private:STDateType* _a;size_t _capacity;size_t _top;
};

我们可以看到,其实我们的析构函数和我们的构造函数长得差不多,析构函数和我们的构造函数和构造函数不同的是,我们构造函数不管成员变量是内置类型还是自定义类型都是最好自己写而不是依靠系统默认构造函数的,但是析构函数如果成员变量没有自定义类型其实就无所谓,可写可不写,因为系统帮你自动完成析构就已经足够了。

当我们函数调用完毕时

我们编译器就自动进入析构函数中将我们的空间释放掉了。

当我们如果定义了多个类时,函数调用结束时先析构谁呢?

int main()
{Stack s1;Stack s2;return 0;
}

我们要记住,后定义的先析构。

s2先析构了

s1在s2析构后才析构。

C++与C语言在这里相比就是不用害怕忘记调用Destroy函数而导致内存泄漏了,因为系统会自动帮你释放,这就会大大降低我们因为忘记调用而导致的内存泄露问题。这就是构造和析构函数的价值。


总结

本章节讲述了我们类的6个默认成员函数中的前2个,还有4个我们放到下一章节来讲解,这几章节比较重要,希望大家认真吸收。加油。

🎇坚持到这里已经很厉害啦,辛苦啦🎇

ʕ • ᴥ • ʔ

づ♡ど

http://www.dtcms.com/a/325976.html

相关文章:

  • 计算机网络---DNS(域名系统)
  • Go 语言中的切片排序:从原理到实践玩转 sort 包
  • 【第四章:大模型(LLM)】05.LLM实战: 实现GPT2-(6)贪婪编码,temperature及tok原理及实现
  • 云服务器部署SSM项目
  • 逻辑备份恢复工具gs_dump/gs_restore
  • Apache Ignite分片线程池深度解析
  • app,h5,微信,携带传递参数的两种方法getCurrentPages()
  • LAMP/LNMP示例
  • Unknown collation: ‘utf8mb4_0900_ai_ci‘
  • thymeleaf 日期格式化显示
  • 基于 ZooKeeper 的分布式锁实现原理是什么?
  • Vue 利用el-table和el-pagination组件,简简单单实现表格前端分页
  • 【数据库】如何使用一款轻量级数据库SqlSugar进行批量更新,以及查看最终的Sql操作语句
  • QT_QUICK_BACKEND 环境变量详解(AI生成)
  • Linux中配置DNS
  • 在 Rocky Linux 9.2 上使用 dnf 安装 Docker 全流程详解
  • 高并发场景下抢单业务解决方案实现(乐观锁 + 分布式锁)
  • Python洛谷做题31:P5726 【深基4.习9】打分
  • A2O MAY确认发行新曲《B.B.B (Bigger Badder Better)》 8月13日强势回归!
  • window显示驱动开发—多平面覆盖硬件要求
  • 深度解析三大HTTP客户端(Fetch API、Axios 和 Alova)——优劣与选择策略
  • JavaScript let的使用
  • 【网络运维】Linux:常见 Web 服务器
  • Vuex和Pina的区别
  • 利用coze搭建智能体和应用的区别
  • SQL复杂查询
  • ListNode* dummy = new ListNode();什么意思
  • 视觉相机偏移补偿
  • 5G NR 非地面网络 (NTN) 5G、太空和统一网络
  • 5G NR 非地面网络 (NTN)