【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初始化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.mfbz.cn/a/730190.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nvidia Isaac Sim组装机器人和添加传感器 入门教程 2024(5)

Nvidia Isaac Sim 入门教程 2024 版权信息 Copyright 2023-2024 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyright law. …

AI穿戴设备是未来手机的终结者?中国AI商业化的未来预测

AI技术的发展正处于商业化应用的关键阶段&#xff0c;而中国在互联网时代已凭借商业化应用逆袭。AI算法大模型虽强大&#xff0c;但真正普惠民众需与设备深度结合。穿戴式智能设备就成为了新战场&#xff0c;AI算法与穿戴设备结合能释放更大工作效率。私人助理AI将成趋势&#…

如何使用k8s安装nexus3呢

百度云盘地址 链接&#xff1a;https://pan.baidu.com/s/1YN1qc2RvzTU3Ba6L_zCTdg?pwd5z1i 提取码&#xff1a;5z1i 下载后上传到本地服务器 docker load -i nexus3 创建 nexus-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata:name: nexus3-deployment spec…

我的Mac疯了!居然可以生成这样的奇葩AI图片!

在当今人工智能领域&#xff0c;midjourney无疑是生成图片的王者&#xff0c;但是苦于付费才能使用&#xff0c;今天我就给大家分享一下midjourney平替stable diffusion&#xff0c;实现本地生成不逊色于midjourney的图片 效果图 先上一个我自己生成的效果(就是在我的Mac上用C…

DPDK与传统收发报文的区别

1.去除中断 传统的收发报文方式都必须采用硬中断来做通讯&#xff0c;每次硬中断大约消耗100微秒&#xff0c;这还不算因为终止上下文所带来的Cache Miss。 DPDK采用轮询模式驱动(PMD)。 PMD由用户空间的特定的驱动程序提供的API组成&#xff0c;用于对设备和它们相应的…

暴雨讲堂|通往AGI的必由之路—AI agent是什么?

在三月份英伟达的新品发布会上&#xff0c;黄仁勋反复提及一个词汇— Generalist Embodied Agent&#xff0c;意为“通用具身智能体”&#xff0c;给观众留下了深刻的印象。其实具身智能指的是不同形态的拥有主动感知交互能力的机器人。其实&#xff0c;业界对它还有一个更为熟…

[Vulnhub] Troll FTP匿名登录+定时任务权限提升

信息收集 IP AddressPorts Opening192.168.8.104TCP:21,22,80 $ nmap -sC -sV 192.168.8.104 -p- --min-rate 1000 Nmap scan report for 192.168.8.104 (192.168.8.104) Host is up (0.0042s latency). Not shown: 65532 closed tcp ports (conn-refused) PORT STATE SER…

php基础语法_面向对象

PHP php代码标记 多种标记来区分php脚本 ASP标记&#xff1a;<% php代码 %> 短标记&#xff1a; 脚本标记: 标准标记&#xff08;常用&#xff09;&#xff1a; 简写风格&#xff1a; ASP风格&#xff1a;<% php代码 %> 注意&#xff1a;简写风格和ASP风格…

VisualBox 虚拟机 Ubunut 18.04 在大显示器上黑屏的问题

在小屏幕上显示没有问题&#xff0c;但是移动到大显示器上就黑屏了&#xff0c;并且不能铺满&#xff0c;如下所示 如果我希望它铺满整个屏幕&#xff0c;如何解决呢&#xff1f; 下面是解决方法&#xff1a; 虚拟机底部这个按钮&#xff0c;右键 产生菜单&#xff0c;按这个选…

在寻找电子名片在线制作免费生成?5个软件帮助你快速制作电子名片

在寻找电子名片在线制作免费生成&#xff1f;5个软件帮助你快速制作电子名片 当你需要快速制作电子名片时&#xff0c;有几款免费在线工具可以帮助你实现这个目标。这些工具提供了丰富的设计模板和元素&#xff0c;让你可以轻松地创建个性化、专业水平的电子名片。 1.一键logo…

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

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

【网络安全学习】漏洞扫描:-04- ZAP漏洞扫描工具

**ZAP(Zed Attack Proxy)**是一款由OWASP组织开发的免费且开源的安全测试工具。 ZAP支持认证、AJAX爬取、自动化扫描、强制浏览和动态SSL证书等功能。 1️⃣ 安装zap工具 现在的kali版本不一定会预装zap&#xff0c;我们可以自行安装&#xff0c;安装也十分简单。 apt-get …

构建个人文件上传服务:Python Flask实现上传和下载完整指南

介绍 在本教程中&#xff0c;我们将学习如何使用Python Flask框架将文件上传到服务器&#xff0c;并使用SQLite数据库来跟踪上传的文件。我们将提供后端代码和一个示例项目的Git链接&#xff0c;以便您可以轻松地跟随本教程。 准备工作 首先&#xff0c;您需要安装Python和F…

若依 ruoyi 排序 顺序 倒序 的实现

1. table标签新增排序相关属性 // :default-sort"defaultSort" 指定默认排序 // sort-change"handleSortChange" 指定排序点击事件 :default-sort"defaultSort" sort-change"handleSortChange" 2. 列上新增排序相关配置 自定义查询语…

API低代码平台介绍6-数据库记录删除功能

数据库记录删除功能 在前续文章中我们介绍了如何插入和修改数据库记录&#xff0c;本篇文章会沿用之前的测试数据&#xff0c;介绍如何使用ADI平台定义一个删除目标数据库记录的接口&#xff0c;包括 单主键单表删除、复合主键单表删除、多表删除&#xff08;整合前两者&#x…

【Android14 ShellTransitions】(六)SyncGroup完成

这一节的内容在WMCore中&#xff0c;回想我们的场景&#xff0c;是在Launcher启动某一个App&#xff0c;那么参与动画的就是该App对应Task&#xff08;OPEN&#xff09;&#xff0c;以及Launcher App对应的Task&#xff08;TO_BACK&#xff09;。在确定了动画的参与者后&#x…

Python武器库开发-武器库篇之Redis未授权漏洞扫描器(五十七)

Python武器库开发-武器库篇之Redis未授权漏洞扫描器(五十七) Redis未授权访问漏洞简介以及危害 Redis是一个开源的内存数据库&#xff0c;具有高性能和可扩展性。然而&#xff0c;由于配置不当或者默认设置&#xff0c;Redis服务器可能会存在未授权访问的漏洞。 未授权访问漏…

用Vite基于Vue3+ts+DataV+ECharts开发数据可视化大屏,即能快速开发又能保证屏幕适配

数据可视化大屏 基于 Vue3、Typescript、DataV、ECharts5 框架的大数据可视化&#xff08;大屏展示&#xff09;开发。此项目vue3实现界面&#xff0c;采用新版动态屏幕适配方案&#xff0c;全局渲染组件封装&#xff0c;支持数据动态刷新渲染、内部DataV、ECharts图表都支持自…

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

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

Python用于解析 XML 数据之untangle使用详解

概要 在处理 XML 数据时,解析和提取数据是一个常见的需求。虽然 Python 提供了多种处理 XML 的库,如 xml.etree.ElementTree 和 lxml,但它们通常需要编写较多的代码来解析和处理 XML 数据。untangle 库是一个轻量级的 Python 库,它提供了一种简单而直观的方式来解析 XML 数…