C++类和对象 中(六大默认成员函数)

news2024/12/23 10:13:46

 前言

        紧接着上一篇文章,接下来我们来认识下类的六大默认成员函数,如下图。之所以叫他默认成员函数,是因为即使我们不写,编译器会默认帮我们写,但只要我们自己显示的写了,编译器就不会帮我们生成对应的成员函数。 类似于我们脱贫后就不会再有低保一样。

        接下来我们一个一个接着看吧!

构造函数

为什么要有构造函数

       在讲构造函数之前,我们在介绍下上篇文章的日期类,实现如下。

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

	void Print()
	{
		printf("%d-%d-%d\n", _year, _month, _day);
	}

private:
	int _year;
	char t;
	int _month;
	int _day;
};

        我们要创建一个日期函数并且打印也十分容易,如下代码。

int main()
{

	Date t;

	t.Init(2024, 4, 11);
	t.Print();

	return 0;
}

        但是我们这里是正常的调用初始化函数,假如我们忘记了初始化,程序任然可以正常的运行,但结果是随机值。如下代码结果。(也许有读者观察到VS随机值打印几乎都是-858993460,这是VS编译器做的初始化,甚至有的编译器初始化为0,每个编译器处理不同,我们在这里就同一称之为随机值)

        我们在某些时候可能会忘记给相应的类初始化,造成不可预料的后果,初始化又是个令人容易忘记的事情,那么有什么办法可以解决么?

        方法很简单,我们身边就有一个永久记忆装置,一但给他制定相应的规则,他会严格执行,丝毫不差的执行安排的任务。------------显而易见这个东西就是计算机,我们可以把繁琐的任务甩给他,专注于开发。

构造函数的定义

        构造函数给人一种开辟空间的感觉,但其实它的作用是初始化而不是开辟空间。我们可以把它当作一个特殊的函数,他的语法如下。

        1.要求函数名要和类名相同

        2.没有返回值

        3.对象实例化的时候编译器自动调用

        4.可以重载

构造函数的使用

         我们的本贾尼博士通过苦思冥想帮我们创造了方便的工具,于是像上面的日期类便可以改写为如下实现。这样我们每次创建对象的时候,对应构造函数就会自动的调用,帮助我们减小记忆的负担。下面我们看个示例。

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

	void Print()
	{
		printf("%d-%d-%d\n", _year, _month, _day);
	}

private:
	int _year;
	char t;
	int _month;
	int _day;
};
int main()
{

	Date t;

	t.Print();

	return 0;
}

       但上面的代码是错误的,我们没有显示的写构造函数的时候,编译器会帮我们生成一个默认的构造函数,这个函数没有参数,如下。这个函数对于内置类型(如int double float等)不做任何的处理,对于自定义类型(class,struct)调用其构造函数,一直往下的递归下去,我们明白所谓的自定义类型就是一些内置类型的集合,所以可以认为默认的构造函数调用到可能什么都没干,除非有自定义构造函数。

Date( )
{
		
}

        而一但我们自己定义了构造函数,编译器就不会帮我们在创建构造函数了,于是我们上面的Date t;相当于调用了无参的构造函数。但我们只定义了一个Date( int year, int month, int day)函数,函数参数匹配不上,自然就报错了。

        对于这个问题我们有两个解决方案,第一个是重载一个无参数的构造函数,第二个就是用缺省参数,我们用的最多的就是缺省参数。改动后如下。

Date( int year=1, int month=1, int day=1)
{
	_day = day;
	_month = month;
	_year = year;
}

        因为构造函数可以重载,我们甚至可以写出如下的构造函数。当然这个在日期类函数中没有什么实际的意义,大家可以根据实际需要重构构造函数。

Date(float t)
{
	cout << "恭喜你发现隐藏" << endl;
}

        我们可以写些代码测试上面的代码是否正确。通过下图我们可以发现,程序的运行如我们所料,缺省参数起了作用。

        也许有细心的读者疑问,t1的创建不可以写为如下的形式么?符合我们以往对于无参函数的认知,但构造函数是个特殊的函数,创建类的对象是个特殊的过程。

Date t1();

        如果我们抛弃上面日期类的认识,单纯的看这一段代码,他会是什么意思。我们分析这几个符号,首先是一个类型,然后是一个标识符,最后是()即函数调用符。这不就是个函数声明语句么?但从我们刚学的构造函数的角度看似乎又是创建变量,并且调用无参构造函数。

        这就产生了歧义,在计算机中凡是有歧义的都是错误的程序,计算机程序必须是明确的,这与我们的自然语言不同,程序作为一种形式语言有严厉的规则。于是摆在眼前的问题就是解决Date t1();歧义的问题,如果我们是本贾尼博士,我相信大多人会保留函数声明的做法,改变无参构造的调用。原因无他:成本小,如果改变函数声明的做法,又要改造函数,又要增加记忆,无参不带括号显得十分合理。

析构函数

        有创造就一定有毁灭,这个世界充满二异面。析构函数与构造函数是一对函数,析构函数在类的销毁时调用。

为什么要有析构函数

        假设我们现在有如下的函数调用。

void test()
{
	Date t;
	int c = 0;
}

        当这个test函数调用完之后,随着test函数栈帧的释放,Date,c位置的内存被释放,似乎没有什么问题,但有些情况就十分特殊。

        假设我们此时给日期类增加一个指针用于存储特定日期的信息,将代码稍微改写一下。

class Date
{
public:
	Date( int year=1, int month=1, int day=1)
	{
		_day = day;
		_month = month;
		_year = year;
		t = (int*)malloc(4 * sizeof(int));
	}
	
	void Print()
	{
		printf("%d-%d-%d\n", _year, _month, _day);
	}

private:
	int _year;
	int _month;
	int _day;
	int* t;
};

        此时我们在运行test函数就会发现内存泄露了。当然解决方法十分简单,我们只需要在test函数结束前加上free即可,如下代码。

void test()
{
	Date t;
	int c = 0;
    free(t.t);
}

        但这个比初始化更容易忘记,于是我们的本贾尼博士为了解决这个烦恼,引入了析构函数的概念,在对象销毁的时候调用。

析构函数的定义

        析构函数的定义和构造函数的定义十分的相似。他的语法如下。

        1.函数名字必须为 ~类名,相当于在构造函数名字前加了个~(取反)

        2.函数没有返回类型,没有形参(其实有个隐藏的this指针,可以看上篇文章)

        3.在对象销毁时自动的调用。

        4.不能重载

    析构函数没有形参自然也就无法重载了。我们使用析构函数就可以完成堆区内存的释放了。

        同构造函数一样,如果我们没有自己定义析构函数,编译器会帮我们自动的生成默认析构函数,此时对于内置类型不做处理,对于自定义类型调用他的析构函数。此时我们要明白指针也是内置类型,他不会帮我们释放指针所指的空间。

析构函数的使用

        析构函数的使用也十分简单,例如上述改装的日期类函数便可以写如下的析构函数。

~Date()
{
	free(t);
}

        这样我们就可以在对象销毁的时候自动释放堆区的空间,而不需要我们手动的释放了。

        由此可见本贾尼博士对于C的优化十分戳中痛点,让编译器帮我们处理许多的问题,简化我们自己的操作。

拷贝构造函数

引入

        听名字就可以判断出他是一种特殊的构造函数,重点在于拷贝。对于单个数据我们可能手动的赋值,但对于多个数据就有了拷贝,复制操作。同样在初始化的操作中我们可能想要创造两个完全相同的日期类对象,便可以采用下述代码。

        两次采用相同的初始化,但我们可以采用接下来要讲的拷贝构造函数,简化代码。

int main()
{
	Date t1(2024, 4, 18);
	Date t2(2024, 4, 18);

	t1.Print();
	t2.Print();

	return 0;
}

拷贝构造函数定义

        拷贝构造函数他属于特殊的构造函数,当然要满足构造函数的要求,名字为类名,没有返回值,在创建对象的时候自动调用,他还有个特殊规定,形参必须是类的引用,这就可以与普通的构造函数进行重载,将上面日期类重载拷贝构造函数如下。

Date(Date& d)
{
	_day = d._day;
	_month = d._month;
	_year = d._year;
	t = (int*)malloc(4 * sizeof(int));
	memcpy(t, d.t, 4 * sizeof(int));
}

  拷贝构造函数的使用    

        于是上面的代码我们就可以简写为如下代码,调用拷贝构造函数创建两个相同的日期类。

int main()
{
	Date t1(2024, 4, 18);
	Date t2(t1);

	t1.Print();
	t2.Print();

	return 0;
}

        相信大家看到了上面在处理指针指向的数组时单独开辟了一段空间,我们不可以直接写t=d.t么?答案是显而易见的,不可以。如下图。

        我们俗称为浅拷贝,只是将原来的数据按字节的拷贝而不做任何的处理,对于用指针开辟的数组就会造成多个对象指向同一块内存空间,从而造成多次释放同一块内存。如下图

        而我们的默认拷贝构造函数就是按字节拷贝,如果对于指针指向数组这种情况不做任何处理,就会报错。所以我们有时需要自己写拷贝构造函数进行特殊处理。

        其次我们来谈一个问题,既然是拷贝一个对象,我们可以把形参Date&改为Date么?对于现在的编译器而言,不允许存在形参只为为Date的构造函数,他会报出如下错误。

        为什么呢?我们在调用函数的时候会先传递参数,如果参数的类型为Date&,实际上我们会把当前对象的地址传递过去(为什么引用传地址可以看这篇博客,在这里就不过多赘述了从C到C++过渡知识 下(深入理解引用与指针的关系)-CSDN博客)我们传递地址直接对其变量取地址传递就可以了。

        但如果形参为Date会怎么样?我们会先开辟一份空间,分配给Date实例化后的对象d,然后调用d的拷贝构造函数,d的拷贝构造函数又会开辟一份空间给实例化对象,然后调用其实例化对象的拷贝构造函数,陷入一个死循环,故我们不可以将形参写为Date。

        拷贝构造函数的使用还有一种方式,就是在创建对象的时候用=,可能祖师爷觉得=符合直观的感觉就加上了这条规定,使用如下。

        但一定记住,只有在创建变量的使用用=是拷贝构造函数,在创建后用=就是赋值重载函数!也就是我们接下来要讲的函数。

赋值重载函数

      引入

        回顾我们引入拷贝构造函数的原因,为了简单的让两个对象的相同,其实比起在初始化的时候让两个对象相同,我们更多的情况是在定义完变量后让他与其他对象相同,赋值重载函数就是对操作符=进行重载,使两个类的对象可以像内置类型一样,简单的复制。

        这样便可以提高代码的可读性,相较于函数而言,使用=更加符合人的习惯。

赋值重载函数的定义

       赋值重载函数是对操作符=进行重载,所以要求和操作符重载的要求一样。但为了高效使用我们通常定义如下模式。

	Date& operator=(const Date& d)
	{
		_day = d._day;
		_month = d._month;
		_year = d._year;
		t = (int*)malloc(4 * sizeof(int));
		memcpy(t, d.t, 4 * sizeof(int));
		return *this;
	}

                参数类型写为const Date& d,如果写为Date d又要去调用Date的构造函数,那么这样就会白白的浪费内存,而传引用就可以大大提高效率。其次我们在调用=运算符的时候不会修改等号右边的值,尽管我们可以在函数的内部实现,但不要将运算符偏离原来的意思,写出防御性代码。因此我们就可以将参数写为const Date& d形式,及提高效率,又预防不测。

        最后我们为什么要将函数的返回值写为Date&呢?我们看如下代码。

        我们可能出现连续赋值的情况这个时候就需要我们返回Date&,为什么不写Date原因如上。

        最后我们再来区分以下赋值重载函数和拷贝构造函数。读者可看如下代码。

int main()
{
	Date t1(2024, 4, 18);
	Date t2=t1;
	Date t3(t2);


	t3 = t2 ;
	t1.Print();
	t2.Print();


	return 0;
}

        Date t2=t1;  Date t3(t2);这两个调用的使拷贝构造函数,而t3 = t2 ;调用的是赋值重载函数。

        如果=两边的对象都是创建好的对象,分配过了空间,那么调用的就是赋值重载函数,如果=左边是刚创建变量,就是拷贝构造函数。

        其实在vs中还有种技巧判断,VS中图片如下。

        我们可以发现,调用拷贝构造函数的=是黑色,而调用赋值重载函数的=被标为了特殊颜色,这也可以作为判断的一点技巧。

取地址运算符重载

        这个成员函数我们自定义的不多,一般用编译器帮我们默认生成的就可以了.他的实现如下

Date* operator&()
 {
     return this ;
 }

const成员函数

        但有些时候我们对const成员取地址,但const 成员不可修改,形参就要写为const,最终代码如下。

const Date* operator&()const
 {
     return this ;
 }

        第一个const是用来修饰返回值的,第二个const是修饰隐藏的this指针的,上述的两个函数又可以构成重载。一般我们用编译器生成的就可以了。

        当我们需要对隐藏的tihis指针修饰常量的时候在最后加上const就可以了。这看起来有些奇怪,但也是无奈之举,为了减小复杂度将参数this隐藏,而又要修饰this只能在创造规则了。

总结

        到此我们就认识完了类的六大成员函数,如果文章又错误欢迎在评论区指正。由上述可得类的六大默认成员函数,最重要的是前四个,而前四个中构造函数又是十分重要的。下一篇文章我将与大家共同实现一个日期类,帮我们巩固知识,更好的理解类和对象。、

        喜欢的点点关注和赞吧!!!

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

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

相关文章

基于小程序实现的精准扶贫数据收集系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;ssm 【…

Canal 同步mysql 到es 日期格式报错解决

第一步&#xff1a;下载源码alibaba/canal: 阿里巴巴 MySQL binlog 增量订阅&消费组件 (github.com) 第二步&#xff1a;编辑源码&#xff08;client-adapter下面的clinet-adapter.escore)&#xff1a; com.alibaba.otter.canal.client.adapter.es.core.support.ESSyncUt…

C++修炼之路之反向迭代器和非模板参数,模板特化,分离编译

目录 前言 一&#xff1a;反向迭代器 二&#xff1a;非类型模板参数 三&#xff1a;模板的特化 四&#xff1a;模板的分离编译 五&#xff1a;模板的优点与缺点 接下来的日子会顺顺利利&#xff0c;万事胜意&#xff0c;生活明朗-----------林辞忧 前言 在vector&am…

【网站项目】自习室预约系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

C#下Httpclient post请求获取令牌

1.postman测试ok 2.C#代码 public static async Task<string> testGetToken(string URL, string param){string responseBody "eee";//using (var clientHandler new HttpClientHandler()){ var handler new HttpClientHandler();handler.ServerCertificat…

如何在 VM 虚拟机中安装 OpenEuler 操作系统保姆级教程(附链接)

一、VMware Workstation 虚拟机 若没有安装虚拟机的可以参考下篇文章进行安装&#xff1a; 博客链接https://eclecticism.blog.csdn.net/article/details/135713915 二、OpenEuler 镜像 点击链接前往官网 官网 选择第一个即可 三、安装 OpenEuler 打开虚拟机安装 Ctrl …

Kali Linux扩容(使用图形化界面)

因为今天在拉取vulhub中的镜像的时候报错空间不够&#xff0c;因为最开始只给了20GB的空间&#xff0c;所以现在需要扩容了&#xff0c;结合了一下网上的找到了简便的解决方法 1.首先虚拟机设置->磁盘->扩展 小插曲&#xff1a;在对虚拟机磁盘进行扩容以后&#xff0c;…

美女视频素材无水印哪里找?四个顶级资源站点

寻找高质量的美女视频素材无水印对于制作引人注目的视频内容至关重要。如果你正困惑于“美女视频素材无水印哪里找”&#xff0c;以下是四个提供优秀无水印美女视频素材的网站&#xff0c;可以满足你的各种创作需求。 蛙学网&#xff1a;多样化的美女视频素材 首先推荐的是蛙…

若依 ruoyi-vue el-select 多选框 全选 反选 全不选 查询功能

参考文章vueel-select下拉实现&#xff1a;全选、反选、清空功能 如图&#xff0c;优化代码&#xff0c;支持若依字典 import multipleSelect from /components/MultipleSelect/index.vuecomponents: { multipleSelect },<el-row><el-form-item label"分管领域…

关于 AssertionError: Torch not compiled with CUDA enabled 问题

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

安全开发实战(4)--whois与子域名爆破

目录 安全开发专栏 前言 whois查询 子域名 子域名爆破 1.4 whois查询 方式1: 方式2: 1.5 子域名查询 方式1:子域名爆破 1.5.1 One 1.5.2 Two 方式2:其他方式 总结 安全开发专栏 安全开发实战​​http://t.csdnimg.cn/25N7H 前言 whois查询 Whois 查询是一种用…

《四月女友》定档5月18日 佐藤健、长泽雅美演绎唯美爱情

由川村元气担任编剧&#xff0c;山田智和导演&#xff0c;佐藤健、长泽雅美、森七菜主演的唯美爱情电影《四月女友》今日正式宣布定档5月18日&#xff0c;并发布了“相恋”版预告和“相拥”版海报。预告中&#xff0c;优美宁静的风景令人心生向往&#xff0c;藤代俊&#xff08…

LeetCode701:二叉搜索树中的插入操作

题目描述 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和要插入树中的值 value &#xff0c;将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 &#xff0c;新值和原始二叉搜索树中的任意节点值都不同。 代码 递归法 class Solution { public…

【网络编程】Web服务器shttpd源码剖析——线程池调度

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的网络编程系列之web服务器shttpd源码剖析——线程池调度&#xff0c;在这篇文章中&#xff0c;你将会学习到在Linux内核中如何创建一个自己的并发服务器shttpd&#xff0c;并且我会给出源码进行剖析&#xff0c;以及手绘…

Nodejs 第六十四章(SSO单点登录)

单点登录 单点登录&#xff08;Single Sign-On&#xff0c;简称SSO&#xff09;是一种身份认证和访问控制的机制&#xff0c;允许用户使用一组凭据&#xff08;如用户名和密码&#xff09;登录到多个应用程序或系统&#xff0c;而无需为每个应用程序单独提供凭据 SSO的主要优…

EI、Scopus检索 | 2024年第四届机械、航空航天与汽车工程国际会议

会议简介 Brief Introduction 2024年第四届机械、航空航天与汽车工程国际会议&#xff08;CMAAE 2024&#xff09; 会议时间&#xff1a;2024年11月8 -10日 召开地点&#xff1a;中国北京 大会官网&#xff1a;www.cmaae.org 航空航天是当今世界最具挑战性和广泛带动性的高技术…

HCIP的学习(10)

OSPF不规则区域划分 区域划分 非骨干与骨干区域直接相连骨干区域唯一 限制规则&#xff1a; 非骨干区域之间不允许直接相互发布区域间路由信息OSPF区域水平分割&#xff1a;从非骨干区域收到的路由信息&#xff0c;ABR设备能接收到不能使用&#xff08;从某区域传出的路由&…

Tomcat漏洞利用工具-TomcatVuln

检测漏洞清单 CVE-2017-12615 PUT文件上传漏洞 tomcat-pass-getshell 弱认证部署war包 弱口令爆破 CVE-2020-1938 Tomcat 文件读取/包含项目地址 https://github.com/errors11/TomcatVuln TomcatVuln put文件上传 ajp协议漏洞 默认读取web.xml文件&#xff0c;漏洞利用…

使用yolov5训练自己的目标检测模型

使用yolov5训练自己的目标检测模型 使用yolov5训练自己的目标检测模型1. 项目的克隆2. 项目代码结构3. 环境的安装和依赖的安装4. 数据集和预训练权重的准备4.1利用labelimg标注数据和数据的准备4.1.1 **labelimg介绍:**4.1. 2 labelimg的安装 4.2 使用labelimg4.2.1 数据准备4…

【EI、CPCI稳定检索】2024年现代化教育、知识和信息管理国际学术会议(ICMEKIM 2024)

2024 International Conference on Modern Education, Knowledge and Information Management (ICMEKIM 2024) ●会议简介 2024年现代教育、知识与信息管理国际学术会议将聚焦于教育的最新趋势和信息管理技术的创新发展。本次会议将汇集全球教育专家、学者及行业领袖&#xf…