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

news2024/10/7 8:23:55

文章目录

  • 类的六个默认成员函数
  • 一、构造函数
  • 二、析构函数
  • 三、拷贝构造函数
  • 四、赋值运算符重载
  • 五、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初始化d2
	return 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;
		else
			return _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.coloradmin.cn/o/1846977.html

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

相关文章

【windows】字体安装手册

windows字体安装手册 1 下载字体文件 百度搜索XXX字体ttf文件进行下载 附&#xff1a;宋体gb2312下载地址&#xff1a; https://www.downza.cn/soft/7780.html 2 字体安装 1.搜索字体 2.将下载的ttf文件拖拽添加 3.关闭办公软件重新打开后&#xff0c;outlook、word、…

html做一个分组散点图图的软件

在HTML中创建一个分组散点图&#xff0c;可以结合JavaScript库如D3.js或Plotly.js来实现。这些库提供了强大的数据可视化功能&#xff0c;易于集成和使用。下面是一个使用Plotly.js创建分组散点图的示例&#xff1a; 要添加文件上传功能&#xff0c;可以让用户上传包含数据的文…

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

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

如何保护表格部分内容不被修改!

你是否还在为辛苦做的表格&#xff0c;收回来的表格总是被人随意修改&#xff0c;不是表头变了样&#xff0c;就是设置好的格式、公式都乱了&#xff0c;苦恼、崩溃..... 别急&#xff0c;今天刘小生教你一招解决烦恼&#xff01; 0.案例效果 【案例】现收集员工个人信息表&a…

canal 服务安装

简介&#xff1a;Canal 是阿里巴巴开源的一个基于 MySQL 数据库增量日志解析的中间件&#xff0c;用于提供准实时的数据同步功能。 准备工作 1.修改配置文件 ,需要先开启 Binlog 写入功能&#xff0c;配置 binlog-format 为 ROW 模式&#xff0c;my.cnf 中配置如下&#xf…

【目标检测】图解 DETR 系统框图

简略版本 Backbone&#xff1a;CNN backbone 学习图像的 2D 特征Positional Encoding&#xff1a;将 2D 特征展平&#xff0c;并对其使用位置编码&#xff08;positional encoding&#xff09;Encoder&#xff1a;经过 Transformer 的 encoderDecoder&#xff1a;encoder 的输出…

重磅来袭,表格数据显示的快捷方式和注意事项!

今天分享数据表格常见的两个问题&#xff0c;如何“快速去除小数点”和“快速显示万位数”&#xff0c;及日常工作计算常见问题&#xff0c;今天刘小生跟大家一起排雷&#xff01; 1. 快速去除小数点 问题&#xff1a;经数据计算后出现小数位数&#xff0c;想要在整体数据展示…

Rust这5个自动验证工具,你知道几个?

自动验证是一种有助于检查程序是否满足某些属性的技术&#xff0c;例如内存安全性和避免在运行时错误。此外&#xff0c;自动验证工具使你能够验证并发代码的正确性&#xff0c;这很难手工测试。 自动验证对Rust特别重要&#xff0c;因为它可以帮助确保正确使用unsafe的代码。…

NTFS和exFAT哪个性能好 U盘格式化NTFS好还是exFAT好 mac不能读取移动硬盘怎么解决

文件系统的选择对存储设备的性能和兼容性有着重要影响。而NTFS和EXFAT作为两种常见的文件系统&#xff0c;它们各有特点&#xff0c;适用于不同的使用场景。我们将深入探讨NTFS和EXFAT的区别&#xff0c;帮助大家选择最适合自己需求的文件系统。 NTFS&#xff1a;稳定与性能的平…

代理网络基础设施 101:增强安全性、速度和可扩展性

编辑代理网络在现代网络架构中发挥着重要作用&#xff0c;充当管理和重新路由数据流的中介。它们处理的数据可以是各种类型&#xff0c;包括搜索查询和潜在的敏感客户信息&#xff0c;这凸显了它们在数据安全方面的作用。 然而&#xff0c;代理的好处不仅限于安全性。它们为用…

Apifox 中如何处理加密或编码过的响应数据?

接口返回的响应数据有时是经过编码或加密处理的&#xff0c;要转换成可读的明文&#xff0c;可以使用 Apifox 内置的 JS 类库、或者通过调用外部编程语言 &#xff08;如 Python、JavaScript 等&#xff09; 来进行处理。 例如&#xff0c;一个经过 Base64 编码的数据可以通过…

【开源项目】智慧北京案例~超经典实景三维数字孪生智慧城市CIM/BIM数字孪生可视化项目——开源工程及源码!

飞渡科技数字孪生北京管理平台&#xff0c; 依托实景数字孪生底座&#xff0c;以城市感知网络为硬件基础&#xff0c;以城市大数据为核心资源&#xff0c;以数字孪生、云计算、人工智能为关键技术&#xff0c;实现城市产业规划、资产安全管理、城市能耗监控等一体化空间融合。 …

我国光伏连接器市场规模逐渐扩大 国产化率不断提升

我国光伏连接器市场规模逐渐扩大 国产化率不断提升 光伏连接器是在光伏系统中用于连接控制器、汇流箱、逆变器等各组件的设备。光伏连接器是光伏系统的“生命线”&#xff0c;可确保有效的能量传输&#xff0c;并尽量减少系统中的任何功率损失。光伏连接器具有防水、防尘、电压…

The First项目报告:解读互链操作协议LayerZero

随着 DeFi 项目的兴起&#xff0c;跨链互操作性成为区块链领域的热门话题&#xff0c;在众多的跨链平台中&#xff0c;Layer Zero 凭借其创新技术和设计备受关注&#xff0c;近期Layer Zero发布代币空投方案&#xff0c;引发社区热议&#xff0c;随着其代币上线The First平台&a…

Vue69-路由基本使用

一、需求 二、开发步骤 2-1、路由的安装 vue-router3才能在vue2中使用&#xff01;现在默认是vue-router4版本&#xff0c;要在vue3中使用&#xff01;所以&#xff0c;安装的时候要指定版本。 2-2、在main.js中引入和使用路由 2-3、创建router文件夹 一般在vue中用了vue-ro…

SpringBoot引入外部依赖包

将需要引入的文件放置到与src同级别的目录下 如上&#xff0c;在src的同级&#xff0c;新建了一个lib目录&#xff0c;将jar包放置其中 在POM文件下&#xff0c;加入如下配置 <dependency><groupId>com.aliyun</groupId><artifactId>com.aliyun.filed…

电脑蓝屏修复|你的设备遇到问题,需要重启。我们只收集某些错误信息,然后为你重新启动。100% 完成 终止代码: 0xc000021a

问题描述 今天莫名其妙电脑打不开了&#xff0c;一直如上图所示蓝屏&#xff0c;重启也不行 问了某电脑店的客服&#xff0c;说修复需要50元&#xff0c;真黑啊&#xff0c;果断自己搜方法&#xff0c;怒省50大洋hh 修复方法 重启电脑三次&#xff0c;第三次触发电脑的自动修…

ShuffleNet系列论文阅读笔记(ShuffleNetV1和ShuffleNetV2)

目录 ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices摘要Approach—方法Channel Shuffle for Group Convolutions—用于分组卷积的通道重排ShuffleNet Unit—ShuffleNet单元Network Architecture—网络体系结构 总结 ShuffleNet V2: Pra…

mybatis查询PostgreSQL报错:无法确定参数 $1 的数据类型

错误信息 ### Cause: org.postgresql.util.PSQLException: 错误: 无法确定参数 $1 的数据类型 ; bad SQL grammar []; nested exception is org.postgresql.util.PSQLException: 错误: 无法确定参数 $1 的数据类型] with root cause org.postgresql.util.PSQLException: 错误: …