初识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;可以任意调整长短时间。 如果需要导…

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

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

使用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; 创…

【智能算法】霸王龙优化算法(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 盈…

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

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

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;进行安装。 …

短视频矩阵源头====技术文档交付

短视频矩阵源头技术文档交付 搭建短视频矩阵系统源码需要以下步骤&#xff1a; 1. 确定系统需求和功能&#xff1a;明确系统需要支持哪些功能&#xff0c;例如短视频的上传、存储、播放、分享、评论、点赞等。 2. 选择合适的编程语言和框架&#xff1a;根据需求选择合适的编程…

存储人视角:人工智能AI + 大模型

原文来自于知乎存储专栏&#xff1a; 存储人视角&#xff1a;人工智能AI 大模型 前沿 我的角色 背景 AI 出场 效果 一个宠娃狂魔 娃喜爱并有奥特曼玩具 她的奥特曼玩具会跳舞了 娃对我的崇拜和爱又多了一分......amazing 杭州网商路艾弗森 球队需要制作LOGO 形象生动…

【办公软件word小技巧】如何一键提取word中的图片 几十张 几百张均可一键提取

在日常生活和工作中&#xff0c;我们经常会遇到需要从Word文档中提取图片的情况。无论是为了单独保存这些图片&#xff0c;还是为了在其他地方使用它们&#xff0c;一键提取Word中的图片都是一个非常实用的技能。提取Word文件中的图片并不是一件复杂的事情&#xff0c;只要掌握…

照片jpg格式小于50kb怎么弄?jpg压缩到指定大小

我们经常需要处理大量的图片&#xff0c;特别是在分享到社交媒体时&#xff0c;然而&#xff0c;图片文件的大小常常成为困扰我们的问题&#xff0c;尤其是当我们的设备存储空间有限时。有些平台甚至会需要将图片压缩到50kb大小&#xff0c;那么&#xff0c;如何有效地压缩图片…

WAF攻防-权限控制代码免杀异或运算变量覆盖混淆加密传参

知识点 1、脚本后门基础&原理 2、脚本后门查杀绕过机制 3、权限维持-覆盖&传参&加密&异或等 章节点&#xff1a; WAF绕过主要集中在信息收集&#xff0c;漏洞发现&#xff0c;漏洞利用&#xff0c;权限控制四个阶段。 代码表面层免杀-ASP&PHP&JSP&a…

电商数据接口开发|淘宝商品接口|天猫商品接口|京东商品接口|拼多多商品接口|API接口申请指南

电商数据接口开发涉及到多个电商平台&#xff0c;包括淘宝、天猫、京东和拼多多等。这些平台都提供了丰富的API接口&#xff0c;以便开发者能够获取商品信息、订单数据等&#xff0c;从而构建出各种电商应用和服务。 1.请求方式&#xff1a;HTTP POST GET &#xff08;复制薇&…

Python经典游戏:乒乓球对战(单人+双人模式)

Python108款&#xff0c;小游戏集合&#xff0c;总有一个是你想要的 中国象棋 像素鸟 五子棋 24点小游戏 贪吃蛇 扫雷 俄罗斯方块 魂斗罗 消消乐 坦克大战 外星人入侵 汤姆猫 斗地主 乒乓球 推箱子 植物大战僵尸 围棋 超级玛丽 飞机大战 迷宫 滑雪 吃豆人…等等 &#xff0…

基于springboot实现信息化在线教学平台设计【项目源码+论文说明】计算机毕业设计

基于springboot实现信息化在线教学平台设计演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了信息化在线教学平台的开发全过程。通过分析信息化在线教学平台管理的不足&#xff0c;创建了一个计算机管理信息…