【C++】类的六个默认成员函数

文章目录

  • 类的六个默认成员函数
  • 一、构造函数
  • 二、析构函数
  • 三、拷贝构造函数
  • 四、赋值运算符重载
  • 五、const成员
  • 六、取地址及const取地址操作符重载

类的六个默认成员函数

如果一个类中什么成员都没有,称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数

在这里插入图片描述
前面四个默认成员函数很重要,后面两个很少自己实现。

默认成员函数:用户没有显式实现,编译器会自己生成的成员函数称为默认成员函数。

一、构造函数

构造函数的概念

构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有
一个合适的初始值,并且在对象整个生命周期内只调用一次。

构造函数的主要任务并不是开空间创建对象,而是初始化对象

构造函数的特性

1.函数名与类名相同。
2.无返回值。
3.对象实例化时编译器自动调用对应的构造函数。
4.构造函数可以重载,在创建对象时根据传递实参来判断调用哪一个构造函数。


在这里插入图片描述
在这里插入图片描述

注意:通过无参构造函数创建对象时,对象后面不能跟括号,否则就成了函数声明。

5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数
,一旦用户显式定义,编译器将不再生成。


在这里插入图片描述

这里显示定义了一个带参的构造函数,编译器将不会生成无参的默认构造函数,由于不存在无参构造函数,所以这样调用是错误的。

一般情况下,我们可以写成全缺省的构造函数,这样既可以无参调用,也可以带参调用构造函数。

6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。因为都可以无参调用,不能同时存在,否则调用会存在歧义。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

class Date
{
public://全缺省构造函数 可以认为是默认构造函数Date(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main()
{Date d1;//可以无参调用Date d2(2024, 6, 1);//也可以带参调用return 0;
}

如果我们不写构造函数,使用编译器默认生成的构造函数,会发现打印结果是随机数。

在这里插入图片描述

不实现构造函数的情况下,编译器会生成默认的构造函数。d1对象调用了编译器生成的默认构造函数,但是它的_year、_month、_day,依旧是随机值。也就说在这里编译器生成的默认构造函数似乎并没有什么用?

这是因为,C++把类型分成内置类型(基本类型)和自定义类型。
内置类型就是语言提供的数据类型,如int、char、指针等基本类型。
自定义类型就是struct、class、union等自己定义的类型。

7.我们不写,编译器默认生成的构造函数,对内置类型不做处理,对于自定义类型会去调用它的默认构造函数。

在这里插入图片描述可以发现,创建一个A类的对象,会自动调用B的默认构造函数(自己定义的无参构造函数也认为是默认构造函数)。

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

在这里插入图片描述

那我们什么时候自己写构造函数,什么时候不写,使用编译器默认生成的呢?

1.一般情况下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成的。如果内置类型成员在定义时已初始化且初始化符合我们的要求,这种情况可以不写。
2.全部是自定义类型成员,可以考虑让编译器自己生成。

怎么判断是不是默认构造函数呢?

可以不传参调用的都可以看做是默认构造函数。 无参构造函数、全缺省构造函数、我们不写编译器自己生成的都叫做默认构造函数,都无需传参。没有缺省值需要传参调用的不是默认构造。

二、析构函数

析构函数的概念

与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

析构函数的特性

1.析构函数名是在类名前加上字符 ~
2.无参数无返回值类型。
3.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4.对象生命周期结束时,C++编译系统会自动调用析构函数。

在这里插入图片描述

比如我们写一个顺序栈。

typedef int STDateType;
class Stack
{
public://给缺省值的默认构造函数 完成初始化工作Stack(int capacity = 4){_a = (STDateType*)malloc(sizeof(STDateType) * capacity);if (nullptr == _a){perror("malloc");return;}_capacity = capacity;_top = 0;}void Push(STDateType x){// CheckCapacity();_a[_top++] = x;}//...//析构函数完成销毁工作~Stack(){if (nullptr != _a)//空指针不能free{free(_a);_a = nullptr;_capacity = _top = 0;}}
private:STDateType* _a;int _top;int _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(10);return 0;
}

是不是比C语言的好写并且好用,不需要我们手动去调用初始化函数和销毁函数,构造函数和析构函数会自动调用。

5.系统自动生成的默认析构函数对内置类型不做处理,自定义类型会去调用它的析构函数。

注意

1.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如日期Date类;有资源申请时,析构函数一定要写,否则会造成资源泄漏,比如Stack类。
2.析构函数不能重载!

三、拷贝构造函数

拷贝构造函数的概念

只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

拷贝构造函数与构造函数的名字相同,二者构成重载关系;拷贝构造是用已存在的类对象来初始化新对象

拷贝构造函数的特性

1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式引发无穷递归调用。

//拷贝构造函数
Date(const Date& d)//引用类型
{_year = d._year;_month = d._month;_day = d._day;
}
int main()
{Date d1(2024, 6, 10);Date d2(d1);//调用拷贝构造,用d1初始化d2return 0;
}

3.若未显式定义,编译器会生成默认的拷贝构造函数。默认拷贝构造对于内置类型按照字节拷贝;对于自定义类型则调用它的拷贝构造函数。(这点与构造、析构函数一样)
4.编译器生成的默认拷贝构造函数只能完成浅拷贝也就是按照字节拷贝,涉及到资源申请时需要我们自己写深拷贝。

浅拷贝:通过默认的拷贝构造函数构造的对象,按字节完成拷贝,这种拷贝被称为浅拷贝或者值拷贝

对于日期类这种内置类型的成员,我们不需要显示定义拷贝构造,使用编译器默认生成的就可以。
在这里插入图片描述

但是涉及到动态开辟空间时,如果还使用浅拷贝,就会出现问题。

在这里插入图片描述

浅拷贝是按字节进行拷贝,所以s1和s2的指针内容是一样的,即指向同一块地址。所以其中一个修改,另一个也会修改。生命周期结束时,s2先析构,s1再析构,一块空间释放两次,就会出错。

因此,对于需要动态申请资源的类,我们就需要自己写深拷贝。

class Stack
{
public://构造函数Stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror("malloc");return;}_top = 0;_capacity = capacity;}//深拷贝构造Stack(const Stack& st){_a = (int*)malloc(sizeof(int) * st._capacity);//重新开辟空间if (nullptr == _a){perror("malloc");return;}memcpy(_a, st._a, sizeof(int) * st._top);//内容拷贝过来_capacity = st._capacity;_top = st._top;}//析构函数~Stack(){if (nullptr != _a){free(_a);_a = nullptr;_capacity = _top = 0;}}
private:int* _a;int _top;int _capacity;
};int main()
{Stack s1;Stack s2(s1);//调用拷贝构造return 0;
}

拷贝构造函数的调用场景
1.使用已存在的对象创建新对象

Date d1(2024, 6, 10);//构造函数
Date d2(d1);//拷贝构造函数

2.函数参数类型为类的类型对象(传值调用,实参拷贝给形参)

在这里插入图片描述

所以我们自定义拷贝构造函数只能传引用,不可以传值,否则会引发无穷递归调用。因为如果拷贝构造的参数类型是类类型的对象(传值),则又会调用拷贝构造即调用自身,于是就会无限递归循环调用,程序会出错。

3.函数返回值类型为类类型对象(传值返回)

在这里插入图片描述

注意:不能返回局部对象的引用!
之前通过C++入门篇,我们知道,引用做返回值时是不可以返回局部对象的

在这里插入图片描述
可以看到结果是随机数,其实返回的结果可能是随机数也可能是正确结果。
因为是局部变量,函数结束,栈帧销毁,如果没有清理栈帧,那么返回结果侥幸是正确的;如果清理了栈帧,那么结果就是随机值。

函数参数是类类型的引用或者返回类型是类类型的引用时,是不会调用拷贝构造的,因此效率也比较高。为了提高程序效率,一般对象传参时,尽量使用引用类型;返回时根据实际场景,能用引用尽量使用引用。

四、赋值运算符重载

首先,什么是运算符重载?
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数。

函数名:关键字operator后面加需要重载的运算符符号
运算符重载定义:返回值类型 operator操作符(参数列表)

例如,对于日期类,我们如何进行日期的大小比较呢?
先写一个判断日期是否相等的运算符重载函数。

bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}

判断是否相等的操作符==是双目运算符,所以参数列表有两个参数。因为传值传参会调用拷贝构造,降低效率,所以用传引用传参;因为不改变实参,所以最好加上const,提高代码健壮性。

但是我们如果重载成全局的,就需要类成员变量是公有属性,因为类外是无法访问私有成员的。但是这样无法保证封装性。

在这里插入图片描述
重载为全局函数,则调用方法如下
在这里插入图片描述

除了上述方法(不建议,封装性很差),我们嗨可以用友元来解决(下篇内容),或者重载为类的成员函数

在这里插入图片描述
可不能直接这些写,别忘了,每个成员函数都有一个隐藏的this指针,为第一个参数,也就是说,上述函数写法其实有三个参数。所以显示参数只需要写一个。正确写法
在这里插入图片描述
重载运算符的调用方法
在这里插入图片描述

以上就是运算符重载的使用方法和注意事项,我们再来写一下日期类的比较<、<=等运算符来练习一下。

bool operator<(const Date& d)
{if (_year == d._year){if (_month == d._month)return _day < d._day;elsereturn _month < d._month;}else{return _year < d._year;}
}

写<=的时候就可以调用前面写的<和==运算符,同理后面的>、>=、!=等都可以这样实现,自己可以练习实现一下。

bool operator<=(const Date& d)
{return *this < d || *this == d;
}

运算符重载的注意事项:

1.不能创建新的操作符:比如operator@
2.重载操作符必须有一个类类型参数(内置类型不需要操作符重载)
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5.>* :: sizeof ?: .这5个运算符不能重载。

赋值运算符重载

赋值运算符重载用于自定义类型,在两个及以上已存在的对象之间进行赋值。

Date d1(2024, 6, 1);
Date d2(2000, 1, 1);
d1 = d2;
Date d3, d4;
d4 = d3 = d1;//连续赋值

重载后应实现原本赋值运算符应实现的功能,赋值以及连续赋值等。所以赋值运算符重载是带有返回值的(支持连续赋值),返回引用和传引用可以提高返回效率,因为不用调用拷贝构造(拷贝构造调用的场景)。赋值前可以进行判断优化,相同就不用继续赋值。下面是日期类的赋值运算符重载。

Date& operator=(const Date& d)//传引用提高传参效率
{if (&d != this)//判断优化{_year = d._year;_month = d._month;_day = d._day;return *this;}
}

前面提到过,引用返回不可以返回临时对象,而*this就是对象本身,作用域在定义的域(此处定义在main()函数中)。

赋值运算符和拷贝构造的区别
赋值运算符:两个已经存在的对象之间进行赋值;

Date d1(2024, 6, 1);
Date d2(2024, 6, 1);
d2 = d1;
//或者
Date d1(2024, 6, 1);
Date d2, d3;
d2 = d3 = d1;//d2,d3是已存在的对象

拷贝构造:用一个已经存在的对象初始化新对象;

Date d1(2024, 6, 1);
Date d2(d1);
//或者
Date d1(2024, 6, 1);
Date d2 = d1;//d2是新创建对象

搞明白底层逻辑,就很容易区分了。
在这里插入图片描述

赋值运算符重载也是类的默认成员函数,我们不写,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。(浅拷贝)

和前面的拷贝构造一样,对于内置类型例如日期类这种,我们不需要显示定义赋值运算符重载,使用编译器默认生成的就可以了。但是涉及到资源申请的比如栈这种,浅拷贝就不可以了,需要我们自己显示定义赋值运算符重载完成深拷贝。(参考拷贝构造)

赋值运算符只能重载成类的成员函数不能重载成全局函数。

因为赋值运算符如果不显式实现,编译器会生成一个默认的。此时再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中默认生成的起冲突,所以赋值运算符重载只能是类的成员函数。

五、const成员

在介绍最后两个默认成员函数之前,我们先了解下什么是const成员。
const修饰的类成员函数称为const成员函数,const实际修饰的是隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

如果我们定义一个const的类对象d1,那么这个const对象还可以使用我们之前定义的函数吗?

在这里插入图片描述不可以,之前在C++入门篇中提过,权限不可以放大,只能平移或者缩小。上述Print()函数中有一个隐藏的Date*类型的this指针,而d1是const对象,所以传的是const Date*类型的this指针,所以权限会放大,因此报错。

cosnt成员函数只需要在原本成员函数后面加上cons即可,这样const对象就可以正常调用。

在这里插入图片描述

const对象不可以调用非const成员函数(权限放大
const成员函数不可以调用其他非成员函数(权限放大
非const对象可以调用const成员函数(权限缩小
非const成员函数可以调用其他非const成员函数(权限缩小

总结: 不修改成员变量的成员函数,最好都加上const修饰成const成员函数,这样const对象和非const对象都可以调用。

六、取地址及const取地址操作符重载

这两个默认成员函数基本上很少需要我们自己去实现,一般不需要重载,使用编译器默认生成的即可,除非想让别人通过取地址操作符获取到指定的内容

class Date
{
public:Date* operator&()//取地址操作符重载{return this;}const Date* operator&()const//const取地址操作符重载{return this;}
private:int _year;int _month;int _day;
};

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

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

相关文章

CRMEB 多店商品详情页装修说明

一、功能介绍 商家可调整商品详情各板块样式&#xff0c;可根据不同的需求开启或关闭单独的板块 二、操作流程 装修 > 商品详情 三、功能说明 1、商品信息 可控制商品详情页面商品信息的显示与隐藏 2、会员信息&#xff0c;排行榜 控制商品详情页面会员信息及排行榜的…

SRM供应商管理系统是什么?企业为何都非常重视供应商管理?

企业与供应商之间的关系就像一张错综复杂的网&#xff0c;维系着生产的顺利进行。但是&#xff0c;这张网也时常让企业感到焦虑和困扰。例如&#xff0c; 如何准确地评估供应商的信誉和实力&#xff1f; 如何高效地与众多供应商沟通&#xff0c;确保信息的及时传递和反馈&…

qemu 安装ubuntu22.04虚拟机 -纯命令行-可ssh-带网络-编译安装 linux kernel-编译安装 kernel module

1&#xff0c;预备系统盘数据 1.1 下载光盘 注意需要 liver-server $ wget https://releases.ubuntu.com/22.04.4/ubuntu-22.04.4-live-server-amd64.iso 1.2 挂载并拷贝 $ sudo mkdir /mnt/iso_ubuntu-22.04.4-live-server-amd64 $ sudo mount ubuntu-22.04.4-live-ser…

基于DPU的云原生裸金属服务快速部署及存储解决方案

1. 背景介绍 1.1. 业务背景 在云原生技术迅速发展的当下&#xff0c;容器技术因其轻量级、可移植性和快速部署的特性而成为应用部署的主流选择&#xff0c;但裸金属服务器依然有其独特的价值和应用场景&#xff0c;是云原生架构中不可或缺的一部分。 裸金属服务器是一种高级…

主机游戏也可以上云桌面玩了?

最近steam夏季促销活动也快到了&#xff0c;对于很多钟情于主机游戏的小伙伴们&#xff0c;是不是也在摩拳擦掌了&#xff1f; 但有时候现实想愉快地玩到自己想玩的游戏实在是太难了&#xff01; 当你一直关注的新游戏终于上线Steam时&#xff0c;你的钱包是这样的… 而游戏的…

前端新手小白的Vue3入坑指南

昨天有同学说想暑假在家学一学Vue3&#xff0c;问我有没有什么好的文档&#xff0c;我给他找了一些&#xff0c;然后顺带着&#xff0c;自己也写一篇吧&#xff0c;希望可以给新手小白们一些指引&#xff0c;Vue3欢迎你。 目录 1 项目安装 1.1 初始化项目 1.2 安装初始化依…

Vscode中的行尾序列CRLF/LF不兼容问题

最近开发的的时候&#xff0c;打开项目文件经常会出现爆红错误提示信息&#xff0c;显示如下图&#xff1a; 这东西太烦人了&#xff0c;毕竟谁都不希望在遍地都是爆红的代码里写东西&#xff0c;就像能解决这个问题&#xff0c;根据提示可以知道这是vscode中使用的prettier插件…

11、鸿蒙学习—UDID获取方法

一、手机的UDID获取方法如下&#xff1a; 1、打开“设置 > 关于手机”&#xff0c;多次点击版本号&#xff0c;打开开发者模式。 2、打开“设置 > 系统和更新”&#xff0c;在最下方找到“开发人员选项”&#xff0c;打开“USB调试”开关。 3、使用PC连接手机后&#…

【Spine学习10】之 创建新骨骼时,自动绑定图片和插槽的快捷方式

两天没更新了。 遇到一些难解的难题 用的版本是破解版 不知道为啥现在的教程非常地快 明明有些细节很重要还略过讲 所以创建骨骼这里 基本创建是都会 可是骨骼一多 实际工作中的重命名也太麻烦了 。 这就需要学习快捷创建方式&#xff1a; <将对应图片自动绑定到新骨骼上并…

Android 大话binder通信 (上)

戳蓝字“牛晓伟”关注我哦&#xff01; 用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章 本文摘要 用故事的方式把binder通信的整个过程都描述出来&#xff0c;binder通信都经历了哪些节点&#xff0c;在这些节点上的数据有哪些变化&#xff0c;同时还对binder通…

▶《强化学习的数学原理》(2024春)_西湖大学赵世钰 Ch2 贝尔曼公式

PPT 截取有用信息。 课程网站做习题。总体 MOOC 过一遍 1、学堂在线 视频 习题 2、相应章节 过电子书 复习 GitHub界面链接 3、总体 MOOC 过一遍 学堂在线 课程页面链接 中国大学MOOC 课程页面链接 B 站 视频链接 PPT和书籍下载网址&#xff1a; 【github链接】 文章目录 计算…

泽众云真机-平台即将升级支持华为机型HarmonyOS NEXT系统

具小编了解&#xff0c;泽众云真机即将升级支持华为机型HarmonyOS NEXT系统。有些人可能对HarmonyOS NEXT系统了解不多。 之前我们有个银行项目&#xff0c;客户要求测试华为HarmonyOS NEXT系统环境&#xff0c;当时我们云真机尚未有该系统的机型&#xff0c;然后技术人员向华为…

护眼必看!台灯怎么选对眼睛好的方法

家长们是否和我一样发现孩子时常作出眯眼的行为&#xff01;那就要小心了&#xff01;最近我注意到家中的孩子开始表现出眯眼的习惯。经过仔细观察后发现&#xff0c;这可能与她长时间晚上熬夜写作业导致的光线不足有关。随着孩子学习负担的增加&#xff0c;我作为家长开始担心…

ubuntu下同时安装和使用不同版本的库 librealsense

apt 安装的最新版本在/usr 源码安装的旧版本在/usr/local set(realsense2_DIR /usr/local/) find_package(realsense2 2.50.0 REQUIRED) message( "\n\n ${realsense2_INCLUDE_DIR} ${realsense2_VERSION} RealSense SDK 2.0 is FINDINGING, please install it from…

英伟达和IBM搞事情!主攻“量子计算+AI”

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨娴睿/慕一 排版丨沛贤 深度好文&#xff1a;2000字丨8分钟阅读 Ismael Faro是一位计算机工程师&#xff0c;自2015年以来&#xff0c;他就成为开发IBM量子软件生态系统的重要人物。从2016…

【Linux】rouyiVue 项目部署全过程(含MySQL,Nginx等中间件部署)

查看nginx 进程命令 ps aux | grep server_name 1. 安装MySQL 1.1 下载压缩包 官网下载 1.2 解压 上传并解压好放在指定位置 创建soft文件夹 mkdir /soft上传文件&#xff0c;在该目录下再创建一个mysql文件夹&#xff0c;将安装包解压到新文件夹中 mkdir /soft/mysql-…

【odoo】常用的字符转义:“>“,“<“,““,“/“等

概要 字符转义是指在编写代码或处理文本数据时&#xff0c;将特殊字符转换为另一种形式&#xff0c;以便在特定的上下文中正确解析和处理这些字符。 内容 特殊字符描述XML转义表示法&和符号&amp;<小于符号<>大于符号>"双引号&quot;单引号&ap…

CentOS7 部署安装ClickHouse

一、什么是ClickHouse ClickHouse 是俄罗斯的Yandex于2016年开源的列式存储数据库&#xff08;DBMS&#xff09;&#xff0c;使用C语言编写&#xff0c;主要用于在线分析处理查询&#xff08;OLAP&#xff09;&#xff0c;能够使用SQL查询实时生成分析数据报告。 OLAP场景需要…

Laravel - excel 导入数据

在Laravel中&#xff0c;可以使用maatwebsite/excel这个库来处理Excel文件的导入。 1.用命令行窗口打开项目根目录&#xff0c;使用 Composer 安装 maatwebsite/excel composer require maatwebsite/excel --ignore-platform-reqs 在你的config/app.php文件中注册服务提供者&…

何在 Vue3 中使用 Cytoscape 创建交互式网络图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 Vue.js 中加载 Cytoscape.js 的技术实现 应用场景 Cytoscape.js 是一个用于创建交互式网络的可视化库。在生物信息学、社会网络分析和药物发现等领域中得到了广泛应用。 基本功能 本代码片段演示了如何在 V…