初识C++ · 类和对象(中)(1)

目录

1 类的6个默认成员函数

2 构造函数

3 析构函数

3 拷贝构造函数


1 类的6个默认成员函数

class Date
{
public:private:};

这是一个空类,试问里面有什么?
可能你会觉得奇怪,明明是一个空类,却问里面有什么。其实一点也不奇怪,这就像文件操作章节,系统默认有三个流一样,标准输出流(stdout),标准输入流(stdin),标准错误流(stderr),类里面系统是有默认的函数的,一共有6个默认函数。

默认函数是指用户没有显式实现,系统会自己生成的函数,下面依次介绍。


2 构造函数

class Date
{
public:void Init(int year = 2020,int month = 1,int day = 17){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};

当我们写了一个日期类之后,我们想要对它进行初始化,我们通常都会写一个函数叫做Init()函数,用来初始化里面的成员变量,这是一般写法。

那么有疑问了,我们介绍的不是构造函数吗,为什么会涉及到构造函数?
这是因为构造函数就是专门用来作为初始化函数的,至于为什么取名为构造函数呢?咱也不知道,咱也不敢问。

构造函数应遵行一下几个点:

1 函数名和类名应相同,并且没有返回值

class Date
{
public:Date(){_year = 2020;_month = 1;_day = 17;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();return 0;
}

里面的Date()函数就是构造函数,因为没有返回值,所以不用加void,只有默认成员函数如果没有返回值就可以不用加上void,其他函数就不可以,可以用print函数试验一下。

2 类实例化的时候编译器自动调用构造函数

这里就这里结合调试:

是会自动跳到构造函数的,留个疑问,如果我们没有显式写默认构造函数会怎么样呢?

3 构造函数支持函数重载

这里就复习一下函数重载的概念,函数名相同,函数的参数不同,包括类型不同,个数不同,顺序不同,就构成函数重载:

class Date
{
public:Date(){_year = 2020;_month = 1;_day = 17;}Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(2024,4,10);d1.Print();d2.Print();return 0;
}

构造函数可以有多个,只要支持函数重载就行,并且不存在调用歧义

class Date
{
public:Date(){_year = 2020;_month = 1;_day = 17;}Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

这种代码就会存在调用歧义,两个函数都构成构造函数的函数重载,但是调用的时候会出现问题,传参的时候如果是无参,则两个函数都行,就会存在调用歧义,所以编译器就会报错。

使用构造函数的时候一般有无参调用和带参调用:

Date d1;
Date d2(2024,4,10);

两种调用方式都可以,取决于带不带参数,都是没有问题的。

4 如果用户没有显示调用构造函数,编译器就会调用默认的构造函数,一旦用户显示定义构造函数,系统就不会生成默认构造函数。
 

class Date
{
public:Date(int x){_year = 2020;_month = 1;_day = 17;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();return 0;
}

这里定义了一个默认构造函数,系统就不会默认生成构造函数,所以这里编译器会报错,说没有合适的默认构造函数,主要就是因为我们已经显式定义了默认构造函数。 

5 构造函数只会对自定义类型进行初始化,C++标准没有规定对内置类型要有所处理,初始化自定义类型的时候会调用该自定义类型自己的构造函数

这个点可能有点绕,我们分开来看,一是没有规定对内置类型有所处理, 如下:

class Date
{
public:private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();return 0;
}

如上,打印出来都是些随机值,说明编译器对这三个内置类型没有进行处理,但是不乏有些编译器会将它们初始化为0,这也不用惊讶,因为对内置类型没有规定要处理,所以可处理可不处理,取决于编译器心情咯。

那么什么是调用自定义类型的构造函数呢?

class Time
{
public:Time(){_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
private:int _year;int _month;int _day;Time _t;
};

当我们进行调试的时候,我们会发现编译器会自动进入到Time类的构造函数,随即初始化Time类的三个内置类型为0,但是如果Time类中我们没有显式定义构造函数呢?

那么就会:

那么Time类的内置类型的成员都会是随机值,有点类似无限套娃,只要我们没有显式定义构造函数,就会被定义为随机值,是不是看起来很鸡肋?

先不着急,C++11的标准中为了给内置成员初始化,添加了一个补丁,即可以在声明的时候给上缺省值:

class Date
{
public:void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
private:int _year = 1;int _month = 1;int _day = 1;Time _t;
};
int main()
{Date d1;d1.Print();return 0;
}

打印出来的时候即都是1,这就补上了不给内置成员初始化的缺陷。

那么构造函数是不是很鸡肋没有用处呢?

实际上并不是,如下:

class Stack
{
public:private:int* arr;int _size;int _capacity;
};class MyQueue
{
public:private:Stack _st1;Stack _st2;
};

在两个栈实现队列的时候,当我们调用MyQueue的时候,调用到MyQueue的构造函数的时候,我们不需要对队列进行初始化,因为使用的是栈,所以在栈里面初始化,队列类里面就不需要了,这个时候就不需要在Queue里面显式构造函数了。

默认构造函数有三种,无参构造函数,全缺省构造函数,系统自动生成的默认构造函数

总结来说就是不需要传参的构造函数就是默认构造函数,而且默认构造函数只能有一个,不然存在调用歧义的问题。


3 析构函数

构造函数是用来初始化的,那么析构函数就是用来做类似销毁的工作的,但是不是对对象本身进行销毁,对象本身是局部变量,局部变量进行销毁是编译器完成的,析构函数是用来进行对象中的资源清理的。

析构函数应遵循如下特点:
函数名是类型前面加个~,没有返回值没有参数

class Date
{
public:~Date(){_year = 0;_month = 0;_day = 0;}
private:int _year;int _month;int _day;
};

析构函数不能函数重载,如果用户显式定义了析构函数,系统就不会默认生成析构函数

当代码执行到这一步的时候,系统就会开始执行析构函数的代码,下一步语句就会跳转到~Date函数执行代码清理工作,因为析构函数没有参数,所以不支持函数重载,即只能有一个析构函数。

对象的声明周期结束的时候编译器会自己调用析构函数

也就是上图了,因为声明周期一结束,就会自己调用析构函数,如果没有显式定义析构函数的话,就会调用系统自己生成的析构函数。

当我们调用系统给的析构函数的时候就会发现:

内置类型并没有进行处理,这就是析构函数和构造函数相同的点:
对于内置类型没有要求要进行处理,处理自定义类型的时候会调用自定义类型自己的析构函数

class Time
{
public:~Time(){_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;};
class Date
{
public:private:int _year;int _month;int _day;Time _t;
};

同构造函数一样。

那么总结起来也是,比如碰到两个栈实现一个队列的时候,就可以不用写析构函数,其他情况用户都是要显式定义析构函数的。

在类中,如果没有资源申请,那么就可以不用写析构函数,如果有资源申请,那么一定要写析构函数,不然就会导致内存泄露.

内存泄露是一件很恐怖的事,因为它不会报错,内存一点点的泄露,最后程序崩溃了,然后重启一下程序发现又好了,如此往复,就会导致用户的体验很不好

class Stack
{
public:Stack(int capacity = 4){int* tem = (int*)malloc(sizeof(int) * capacity);if (tem == nullptr){perror("malloc fail!");exit(1);}arr = tem;_capacity = capacity;_size = 0;}~Stack(){free(arr);arr = nullptr;_capacity = _size = 0;}
private:int* arr;int _size;int _capacity;
};

像这种在堆上申请了空间的,就一定要写析构函数,不然就会导致内存泄露。


3 拷贝构造函数

拷贝构造函数,拷贝就是复制,像双胞胎一样,复制了许多特征,拷贝构造函数就是用来复制对象的,应遵行如下特点:
拷贝构造函数是构造函数的一个重载形式
既然是构造函数的重载形式,那么拷贝构造函数的函数名也应该是类名,当然,也是没有返回值的。

拷贝构造函数的参数只有一个,是类类型的引用,如果采用传值调用就会触发无限递归,程序就会崩溃
这个点的信息量有点大,我们一个一个解释

第一个,函数参数只有一个引用类型的参数,使用的时候如下:

class Date
{
public:Date(int year,int month,int day){_year = year;_month = month;_day = day;}Date(Date& dd){_year = dd._year;_month = dd._month;_day = dd._day;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2020, 1, 17);d1.Print();Date d2(d1);d2.Print();Date d3 = d1;d3.Print();return 0;
}

其中参数是Date& dd的就是拷贝构造函数,拷贝构造函数一共有两种拷贝方法:
一是Date d2 = d1,二是Date d3(d1),两种方式都可以的,最后打印出来的结果都是2020-1-17。

那么,为什么使用传值调用就会触发无限递归呢?
这是因为在传值调用的时候,形参也是一个对象,对象之间的赋值都会涉及到拷贝构造函数的调用,我们结合以下代码:

class Date
{
public:Date(int year,int month,int day){_year = year;_month = month;_day = day;}Date(const Date& dd){_year = dd._year;_month = dd._month;_day = dd._day;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
private:int _year;int _month;int _day;
};
void Func(Date pd)
{cout << "Date pd" << endl;
}
int main()
{Date d1(2020, 1, 17);Func(d1);return 0;
}

当代码段执行到Func的时候,语句就会先跳到拷贝构造函数,赋值完了才会进入到函数Func里面,这时候我们监视形参pd,就会发现pd已经赋值了d1的数值。

也就是说,传值调用的时候,就会自动跳到拷贝函数,那么如果拷贝构造函数也是传值调用的话呢?就会造成拷贝构造函数的形参调用拷贝构造函数的形参,一直循环往复,从而导致了无限递归。

这就是为什么拷贝构造函数的参数必须是引用类型了,但是我们拷贝构造的时候,因为是引用类型,我们不希望引用类型被修改,所以常加一个const进行修饰。

如果用户没有显式定义拷贝构造函数,系统会默认生成拷贝构造函数,拷贝构造函数按字节序进行拷贝,这种拷贝被叫做浅拷贝,与之对应的是深拷贝

默认成员函数都有个特点,如果用户没有显式定义函数,系统都会默认生成该函数。

那么,什么是浅拷贝呢?对于日期类,无非就是赋值,我们不必太过在乎,但是对于Stack这种,我们就需要注意一下了,先看代码:

class Stack
{
public:Stack(int capacity = 4){int* tem = (int*)malloc(sizeof(int) * capacity);if (tem == nullptr){perror("malloc fail!");exit(1);}arr = tem;_capacity = capacity;_size = 0;}~Stack(){free(arr);arr = nullptr;_capacity = _size = 0;}
private:int* arr;int _size;int _capacity;
};
int main()
{Stack s1;Stack s2(s1);return 0;
}

对于Stack这种有资源申请的类,我们拷贝构造之后,生成解决方案的时候是成功的,但是当我们

运行程序的时候就会报错:

报错位置是在空指针那里,那么我们可以把重心放在空指针这里,既然是空指针报错,是我们越界访问了吗?还是说我们free了两次空指针?

看这个:

在拷贝构造完成之后,发现s1 和 s1的arr指向的空间居然是一样的:

因为拷贝构造函数内置类型是按照字节序拷贝的,所以拷贝的时候就会出现两个指针指向空间是同一个的情况,那么在析构函数,释放空间的时候,就会free掉空间两次,所以会报错。

浅拷贝对应的就是深拷贝,所以解决方法就是深拷贝,对于这种有空间申请的类,我们进行拷贝构造的时候都要深拷贝,不然析构的时候就会出现问题:

	Stack(const Stack& ss){arr = (int*)malloc(sizeof(int) * ss._capacity);if (arr == nullptr){perror("malloc fail!");return;}memcpy(arr, ss.arr, sizeof(int) * ss._size);_size = ss._size;_capacity = ss._capacity;}

深度拷贝构造无非就是两个指针指向不同的空间,但是里面的数据是一样的,那么拷贝数据我们就可以用到memcpy,然后自己开辟一块空间给s2,最后赋值相关的数据就可以了,这样就不会报错了。

总结:

如果是日期类的拷贝构造,是没有必要进行深拷贝的,用系统默认生成的拷贝构造函数就行

拷贝构造函数报错常常因为析构函数,所以一般情况下拷贝构造函数不用写的话,析构函数也不用写

如果内置成员都是自定义类型,如MyQueue,没有指向资源,默认的拷贝构造函数就可以。

如果内部资源有申请的话,如Stack类,就需要用户自己显式定义拷贝构造函数,防止空间多次释放


感谢阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/825356.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据融合概念解析:特征融合与特征交互

特征融合与特征交互的区别 我是目录 特征融合与特征交互的区别前言三者关系三者定义特性融合(Feature Fusion):特征拼接(Feature Concatenation):特征交互(Feature Interaction): 特征融合和特征交互关键的不同点数据处理目的应用 总结 前言 遥感系列第14篇。遥感图像处理方向…

js解密心得,记录一次抓包vue解密过程

背景 有个抓包结果被加密了 1、寻找入口&#xff0c;打断点 先正常请求一次&#xff0c;找到需要的请求接口。 寻找入口&#xff0c;需要重点关注几个关键字&#xff1a;new Promise 、new XMLHttpRequest、onreadystatechange、.interceptors.response.use、.interceptors.r…

c++的学习之路:25、map与set

摘要 本文中说一下map与set的使用 目录 摘要 一、关联式容器 二、键值对 三、map 1、map的介绍 2、map的使用 1、map的模板参数说明&#xff1a; 2、map的构造 3、map的迭代器 4、map的容量与元素访问 5、map中元素的修改 6、代码使用 ​编辑 三、总结 四、se…

PMM2 MySQL监控管理工具

目录 1. PMM介绍 2. 安装PMM服务端 2.1 安装docker 2.1.1 下载docker 2.1.2 上传docker包 2.1.3 启动守护进程 2.1.4 查看docker状态 2.2 安装PMM 2.2.1 下载镜像 2.2.2 load镜像 2.2.3 查看镜像 2.2.4 创建容器 2.2.5 运行镜像 2.2.6 验证PMM服务器 2.2.7 删除…

外包干了16天,技术倒退明显

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01; 而我已经在一个企业干了四年的功能…

【剪映专业版】09定格、倒放、镜像、旋转、裁剪

视频课程&#xff1a;B站有知公开课【剪映电脑版教程】 分别为定格、倒放、镜像、旋转、裁剪 定格 时间指示器移动到需要定格的地方&#xff0c;点击定格&#xff0c;自动生成一张图片&#xff0c;时长为3秒。 定格出来的画面&#xff0c;可以任意调整长短时间。 如果需要导…

与128题类似的2007题 O(n)做法

2007. 从双倍数组中还原原数组 class Solution:def findOriginalArray(self, changed: List[int]) -> List[int]:cnt Counter(changed)# 单独处理 0cnt0 cnt.pop(0, 0)if cnt0 % 2:return []ans [0] * (cnt0 // 2)for x in cnt:# 如果 x/2 在 cnt 中&#xff0c;则跳过if…

MySQL高级(性能分析-查看执行频次、慢查询日志)

目录 1、SQL性能分析 1.1、SQL执行频率 1.2、慢查询日志 1、SQL性能分析 1.1、SQL执行频率 MySQL 客户端连接成功后&#xff0c;通过 show [ session | global ] status 命令可以提供服务器状态信息。通过如下指令&#xff0c;可以查看当前数据库的 insert、update、delete、…

【JS】转换多个json结构的字符串为数组

文章目录 背景&#xff1a;今天遇到接口返回的 EventStream 结构的数据&#xff0c;由于http 流式传输时&#xff0c;可能会分段&#xff0c;所以导致本该每次返回一段json数据结构的字符串&#xff0c;变成了多个json数据结构的字符串拼接在了一起。 例如&#xff1a; {a: 1}…

使用vite从头搭建一个vue3项目(四)使用axios封装request.js文件,并使用proxy解决跨域问题

目录 一、创建request.js文件二、创建axios实例三、创建请求、响应拦截器四、使用 request.js&#xff0c;测试接口&#xff1a;https://api.uomg.com/api/rand.qinghua1、调取接口代码书写2、注意&#xff08;跨域问题&#xff09; axios 的二次封装有三个要点&#xff1a; 创…

孩子成绩差怎么办?建议家长看一看

孩子的教育问题&#xff0c;永远是家长最关心的一件事&#xff0c;望子成龙&#xff0c;望女成凤&#xff0c;是每一个家庭、每一个家长的向往和追求。在以分数定输赢的大环境下&#xff0c;学习成绩很大程度上决定了一个孩子的未来。孩子学习成绩差&#xff0c;怎么办&#xf…

金三银四面试题(二十一):代理模式知多少?

代理模式在软件开发中是一个非常重要的设计模式之一。它提供了一种灵活的方式来控制和管理对象的访问&#xff0c;并且可以在访问对象前后执行额外的操作。 什么是代理模式 代理模式是一种结构型设计模式&#xff0c;其目的是为其他对象提供一种代理以控制对这个对象的访问。…

【智能算法】霸王龙优化算法(TROA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2023年&#xff0c;VSDM Sahu等人受到霸王龙狩猎行为启发&#xff0c;提出了霸王龙优化算法&#xff08;Tyrannosaurus Optimization Algorithm, TROA&#xff09;。 2.算法原理 2.1算法思想 TR…

【系统分析师】系统规划

文章目录 1、项目的机会选择2、可行性分析3、成本效益分析3.1 基本概念3.2 盈亏临界分析3.3 净现值分析3.4 投资回收期 截图&#xff1a;希赛讲义 视频&#xff1a;B站 系统规划对应的是 立项 阶段 1、项目的机会选择 2、可行性分析 3、成本效益分析 3.1 基本概念 例题 3.2 盈…

大厂Redis高频面试题及参考答案(持续更新)

描述一下Redis的基本工作原理。 Redis(Remote Dictionary Server)是一个开源的,基于内存的高性能键值对数据库。它的基本工作原理可以分为以下几个方面: 内存存储:Redis将所有数据存储在内存中,这使得数据的读写速度非常快,可以支持每秒数十万次的读写操作。 数据持久化…

Linux应用开发-进程间通信-管道

文章目录 管道特点匿名管道匿名管道的创建实现步骤 有名管道Linux系统命令实现C语言实现写入读取 管道 管道中的数据只能单向流动&#xff0c;也就是半双工通信&#xff0c;如果想实现相互通信&#xff08;全双工通信&#xff09;&#xff0c;我们需要创建两个管道。 向管道中…

安装Selenium和WebDriver

幻灯片4&#xff1a;安装Selenium和WebDriver 安装Python环境 步骤一&#xff1a;下载Python安装包 访问Python官方网站&#xff08;https://www.python.org/downloads/&#xff09;&#xff0c;根据您的操作系统选择对应的Python安装包进行下载。请确保下载最新稳定版本的P…

边缘计算智能分析网关V4地面垃圾AI检测算法介绍及场景应用

在传统的卫生监管场景中&#xff0c;无法及时发现地面遗留的垃圾&#xff0c;通过人工巡逻的方式需要大量的人力、物力和时间&#xff0c;而且效率不高&#xff0c;并存在一定的滞后性&#xff0c;而采用地面垃圾AI检测算法则可以大大提高监管效率。 TSINGSEE青犀AI智能分析网…

macos、windows 思维导图 xmind pro

思维导图单机 Pro版&#xff1a; mac: XMind Pro 2024 for Mac v24.03.04745 直接拖动安装使用 windows: XMind思维导图 2024 V24.01.14362 安装教程&#xff1a;https://www.jb51.net/softs/876712.html https://www.jb51.net/softs/879963.html#downintro2

windows10安装Tensorflow-gpu 2.10.0

windows10安装Tensorflow-gpu 2.10.0 本文主要目的是 从0开始演示 在windows10 平台安装Tensorflow-gpu 2.10.0。 Tensorflow-gpu 2.10.0 之后的版本&#xff0c;不再支持这样的安装方式&#xff0c;如果有需要&#xff0c;请参考wsl安装ubuntu的方式&#xff0c;进行安装。 …