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

 前言

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

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

构造函数

为什么要有构造函数

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

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.mzph.cn/news/825586.shtml

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

相关文章

第十五届蓝桥杯复盘python大学A组——试题C 数字诗意

思路 数字可以分为 有诗意的数字可以写成 (ij)(j-i1)/2 &#xff08; i、j都是正整数 &#xff09; ij 、j-i1 的奇偶性不同&#xff08;因为i、j都是正整数&#xff09; 因此&#xff0c; 如果一个数是奇数就一定有诗意 eg.312 ,523,734,945… 原因&#xff1a;根据上述分…

114 接口中幂等性的保证

前言 同样是 面试问题 如何确保接口的 幂等性 幂等是一个 较为抽象的概念, 多次重复访问, 不会导致业务逻辑的异常 这里从增删改查, 几个方面列一下 一般来说, 我们核心需要关注的就是 新增 和 更新 对于 增加元素, 首先针对唯一约束进行校验, 然后再处理新增的相关业…

Day09 React———— 第九天

ReactRoter 一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候&#xff0c;path 对应的组件会在页面中进行渲染 基础用法 import { createBrowserRouter, RouterProvider } from "react-router-dom"; const router createBrowserRoute…

AI讲师人工智能讲师大模型培训讲师叶梓:突破大型语言模型推理效率的创新方法

大型语言模型&#xff08;LLM&#xff09;在自然语言处理&#xff08;NLP&#xff09;任务中展现出了前所未有的能力&#xff0c;但它们对计算资源的巨大需求限制了其在资源受限环境中的应用。SparQ Attention算法提出了一种创新的方法&#xff0c;通过减少注意力机制中的内存带…

探索Java世界中的七大排序算法(上)

文章目录 排序的概念直接插入排序希尔排序( 缩小增量排序)选择排序堆排序冒泡排序 在计算机科学中&#xff0c;排序算法是一类重要的算法&#xff0c;它们用于将一组元素按照一定的顺序进行排列。在Java编程中&#xff0c;我们经常需要对数组或集合进行排序操作。本文将介绍Jav…

驱动云创建保存自己的环境

驱动云创建保存自己的环境 制作镜像方法一方法二报错 上一篇link介绍了如何在驱动云上部署llama2以及驱动云在训练大模型的方便之处。也说到了可以直接使用驱动云现有的环境&#xff0c;免得自己配置环境。 但是有的时候免不了自己想要安装一些包。 驱动云的环境是这样的&…

电视音频中应用的音频放大器

电视机声音的产生原理是将电视信号转化为声音&#xff0c;然后通过扬声器将声音播放出来。当我们打开电视并选择频道时&#xff0c;电视机首先从天线或有线电视信号中获取声音信号。声音信号经过放大器放大之后&#xff0c;就能够通过扬声器发出声音。电视机声音的产生原理和音…

react中子父组件互相传值

在react中父子组件互相传值&#xff0c;除了使用类似于redux这样状态管理的工具&#xff0c;怎么实现&#xff1f;&#xff1f; 父传子(简单)父:子: 子传父(较麻烦)父&#xff1a;子&#xff1a; 父传子(简单) 父: 子: 子传父(较麻烦) 父&#xff1a; 子&#xff1a;

elementui中文官网

Element - The worlds most popular Vue UI frameworkElement&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库https://element.eleme.cn/#/zh-CN/

一个 .net 8 + Azure 登录 + Ant Design Blazor 的基本后台框架

一个 .net 8 Azure 登录 Ant Design Blazor 的基本后台框架 主界面使用了 Ant Design Blazor 项目模板搭建 后台技术是 .net 8 Blazor run at server 模式 登录方式使用 Azure 实现了菜单导航和路由 此外实现了读取和修改本地Json文件的功能&#xff0c;不是必须的&#x…

[MySQL数据库] 索引与事务

1. 索引 1.1 概念 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针.可以对表中的一列或多列创建索引,并指定索引的类型&#xff0c;各类索引有各自的数据结构实现. 1.2 作用 数据库中的表、数据、索引之间的关系&#xff0c;类似于书架上的图书、书籍…

【力扣】148. 排序链表

148. 排序链表 题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,…

深度学习架构(CNN、RNN、GAN、Transformers、编码器-解码器架构)的友好介绍。

一、说明 本博客旨在对涉及卷积神经网络 &#xff08;CNN&#xff09;、递归神经网络 &#xff08;RNN&#xff09;、生成对抗网络 &#xff08;GAN&#xff09;、转换器和编码器-解码器架构的深度学习架构进行友好介绍。让我们开始吧&#xff01;&#xff01; 二、卷积神经网络…

【观察】容器化部署“再简化”,云原生体验“再升级”

自2013年云原生概念被提出以来&#xff0c;云原生技术和架构在过去十多年得到了迅速的发展&#xff0c;并对数字基础设施、应用架构和应用构建模式带来了深刻的变革。根据IDC预测&#xff0c;到2024年&#xff0c;新增的生产级云原生应用在新应用的占比将从2020年的10%增加到60…

Java学习-详述main方法、可变参数、数组的工具类、二维数组

详述main方法 【1】main方法&#xff1a;程序的入口&#xff0c;在同一个类中&#xff0c;如果有多个方法&#xff0c;那么虚拟机就会识别main方法&#xff0c;从这个方法作为程序的入口 【2】main方法格式严格要求&#xff1a; public static void main(String[] args){} p…

线性代数---行列式的性质

1. 行列式的行与列(按原顺序)互换

SpringCloud +UniApp技术开发saas模式的智慧工地云平台源码,支持可视化大屏端、手机端、平板端、PC端

基于微服务架构JavaSpring Cloud UniApp MySql技术开发saas模式的一套智慧工地云平台源码&#xff0c;支持多端展示&#xff1a;可视化大屏端、手机端、平板端、PC端。 智慧工地平台支持项目级、公司级、集团级多级权限划分&#xff0c;可根据企业的组织架构进行项目权限、功能…

编程入门(四)【计算机网络基础(由一根网线连接两个电脑开始)】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 前言两个电脑如何互连呢&#xff1f;集线器、交换机与路由器总结 前言 当你有…

【opencv】dnn示例-speech_recognition.cpp 使用DNN模块结合音频信号处理技术实现的英文语音识别...

模型下载地址&#xff1a; https://drive.google.com/drive/folders/1wLtxyao4ItAg8tt4Sb63zt6qXzhcQoR6 终端输出&#xff1a;&#xff08;audio6.mp3 、audio10.mp3&#xff09; [ERROR:00.002] global cap_ffmpeg_impl.hpp:1112 open VIDEOIO/FFMPEG: unsupported parameter…

华为手机p70即将上市,国内手机市场或迎来新局面?

4月15日&#xff0c;华为官宣手机品牌全新升级&#xff0c;p系列品牌升级为Pura。华为P70系列手机预计将于2024年第一季度末发布&#xff0c;而网友也纷纷表示期待p70在拍照、性能上的全新突破。 网友们对华为P70系列的热情高涨&#xff0c;也印证了国内高端手机市场的潜力巨大…