C++类的默认成员函数

news2024/12/26 1:18:47

文章目录

  • 默认函数
  • 构造函数和析构函数
    • 构造函数
    • 析构函数
  • 拷贝构造函数
  • 运算符重载
  • 赋值运算符重载
  • 赋值运算符重载和构造函数

默认函数

什么是默认函数?
默认函数就是当你使用这个类对象时,这个类会自动调用的函数C++中有六个默认成员函数,并且作用各不相同,下面我们来一一进行介绍

构造函数和析构函数

什么是构造函数?构造函数是干什么的?
什么是析构函数?析构函数是干什么的?
我们以栈为例,每一次我们在使用栈的时候我们都要先定义它,并且每次在使用完栈之后还要去销毁它,为了释放空间防止内存泄漏。然而我们在实际操作时经常会忘记去定义,特别是最后的销毁。祖师爷为了防止你的遗忘,专门设计出了这两个默认函数来减小你的压力

构造函数

构造函数就是在定义对象的同时,就对成员变量进行初始化。

#include <iostream>

using namespace std;

class Date
{
public:
	//过去
	/*void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
	//现在
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

int main()
{
	//Date D1;
	//D1.Init(2002, 01, 01);
	Date D1(2002, 01, 01);
	D1.Print();
	return 0;
}

一般情况下,我们会定义一个Init函数,再定义完一个对象后,再调用Init函数对成员变量进行初始化,这样显得有些繁琐,那么可不可以选择在定义对象时就对成员变量进行初始化呢?
答案是肯定的,我们可以通过构造函数来对成员变量进行初始化。
默认构造函数,其实类中是有默认构造函数的,就是在你创建这个对象时,它就会自动调用这个函数,对成员变量进行初始化,只不过有时候它默认生成的值并不是我们想要的,所以就需要我们自己去定义。
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造(成员)函数,一旦用户显式定义编译器将不再生成。
无论何时只要类的对象被创建,就会执行构造函数。C++规定内置类型不做处理,自定义类型会去调用而它的默认构造。但是有些也会对内置类型做处理,但那只是编译器的个性化行为,C++并没有规定。
构造函数怎么去定义呢?首先构造函数的取名就与其他函数不同,它的名字与类名相同,且不需要返回值,这里的不需要返回值是指他不需要加返回类型,void也不用加。
什么时候自己写构造函数呢?
如果类成员函数中有自定义类型就要去自己写构造函数,如果类成员全是自定义类型就可以选择不写。
构造函数参数可以给缺省值,且可以重载。
还有一个问题当我们在定义对象时调用无参的构造函数,为什么不写成这样?

Date D1();

因为这样编译器很可能会把它当做返回类型为Date的函数声明。但是如果我们传的是值

Date D1(2002, 01, 01);

编译器就会识别出来它是变量的初始化而不是函数声明。
注意:区分一些概念,我们不写编译器自己生成的是默认成员函数,也属于默认构造函数,默认构造函数是一个大类包括我们自己显示写的和编译器自己生成的。
我们不传参自动调用的构造函数就是默认构造函数,必须要传参的不属于默认构造函数,属于构造函数。
默认构造函数有三种:一种是编译器自己生成的,一种是参数全缺省的,另一种是无参的构造函数,且这三个默认构造函数只能有一个。如果是自己写的构造函数,编译器就不会自动生成构造函数。

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

上面两个构造函数,符合语法规定构成重载,但是C++规定只能有一个默认构造函数,所以我们只能写其中的一个,且上面两个在函数调用的时候存在歧义,当你不写形参的时候,编译器不知道你是要调用哪一个。
构造函数可以重载,那默认构造函数与半缺省的构造函数可不可以同时写呢?

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

	Date(int year = 3, int month = 2)
	{
		_year = year;
		_month = month;
		//_day = day;
	}

这样也是不行的,半缺省与默认构造函数放在一起时也会存在调用歧义。
例如:

void Print(int x = 2, int y = 2)
{
	cout << x << " " << y << endl;
}

void Print(int x = 3, int y = 3, int z = 5)
{
	cout << x << " " << y << " " << z << endl;
}

这种虽然语法上来说也是构成重载的,但是在你传一个参数或两个参数时会有多个参数参数列表匹配,就会存在调用歧义。

void Print(int x, int y )
{
	x = 2;
	y = 3;
	cout << x << " " << y << endl;
}

void Print(int x, int y, int z )
{
	x = 5;
	y = 6;
	z = 8;
	cout << x << " " << y << " " << z << endl;
}

上面这种才是真正的符合重载,不会再冲调用歧义。
对类成员初始化方式不同时才考虑使用重载构造函数,比如对一个栈的操作你可能想初始化时只对成员变量赋值,也可能想直接插入很多数据,比如将一个数组都插入,这时才真正符合重载,就是参数类型不同。在有缺省的情况下,你再定义一个缺省函数就很有可能会造成调用歧义,除非你传的实参个数不符合其它的重载函数。
全局对象先于局部对象进行构造
局部对象按照出现的顺序进行构造,无论是否为static

析构函数

析构函数的函数名也与类名相同,但是要在函数名前加上按位取反符号‘~’,析构函数的作用是在函数调用结束之后释放空间,注意不是释放这个对象,对象是在栈上开辟的,出了作用域栈上的空间会自动释放,只有动态开辟的空间才需要用到析构函数去释放,就是在堆上开辟的空间,其它的像临时变量等都是在栈上开辟的空间它会自动释放,此外析构函数没有参数,所以析构函数不可以重载,一个类中只有一个析构函数,如果不显示的去写,编译器也会默认生成构造函数,且对内置类型不做处理,自定义类型会去调用它的构造函数。出了作用域系统会自动调用析构函数。

~Stack()
	{
		if (_array)//_array是动态开辟的空间
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

什么时候写析构函数?
析构函数释放的空间是释放堆上的空间,就是我们动态开辟的空间。
1.一般情况下,有动态开辟的空间,就需要我们显示的去写析构函数去释放空间。
2.没有动态申请资源,就不需要写析构。像对象这种临时变量,不是动态开辟的空间,是在栈上开辟的,出了作用域系统会自动清除栈上的资源。例如日期类类里面就没有动态申请的资源。
3.如果需要释放的资源都是自定义类型,就不需要写析构函数,默认生成的析构函数足够用了。例如:

class Myque
{
private:
	Stack _pushst;
	Stack _popst;
};

拷贝构造函数

在我们传参数的过程中,可能会选择值传参,值传参实际上就是对形参进行初始化,对于内置类型变量赋给另一个变量的时候就是值拷贝也称为浅拷贝不用调用拷贝构造函数,而对于自定义类型在值拷贝时,会调用拷贝构造函数。每一个类都有它的默认拷贝构造函数,但是这个默认构造函数是浅拷贝,也就是说这个拷贝它是按字节赋给新的变量,并没有开辟新的空间。如果这个自定义类型中包含指针,那么新变量和被拷贝指针变量就会指向同一个空间,如果是这样的话就会出问题,当这两个对象空间释放时,就会导致同一块空间被析构两次,同一空间是不能被析构两次的,此外如果其中一个对象值发生改变,也会导致另一个对象值发生改变。任何类型的指针都是内置类型,包括自定义类型。如果我们自己定义拷贝构造函数,就会解决上述问题,我们选择深拷贝,深拷贝就是新开辟一块空间,并将原指针指向的空间地址的值赋给新的空间,这样就完成了拷贝,指针指向的空间不同,但是值是相同的。
那么当我们用可不可以直接自定义类型作为形参?
答案是否定的,首先我们在对自定义类型进行拷贝时,调用的就是拷贝构造函数,调用它的时候又要进行传参,而在传参的过程中,就是相当于对自定义类型的形参进行初始化,而这种赋值,就又会调用拷贝构造函数,而调用拷贝构造函数就要传参,传参就要调用拷贝构造,最后会形成无限递归。
C语言当中自定义类型传参就直接是浅拷贝,而在C++当中在传参时要先调用拷贝构造,如果想避免上面的问题,还要进行深拷贝,也就是调用我们自定义的拷贝构造。
那么怎么解决自定义类型对象的拷贝呢?
上述传自定义类型时在拷贝时都会调用拷贝构造函数,那么可以选择传地址或者用引用去解决上述问题。因为自定义类型时才要拷贝构造,传指针或引用不需要,因为任何类型的指针都是内置类型,不会调用拷贝构造函数,直接把地址赋给新变量就可以,而传引用就是传这个对象的别名,引用对应的变量并没有开辟新的空间,这里重要的是这个类变量的地址和它的引用的地址是相同的,所以起引用名时不存在什么拷贝
在这里插入图片描述
引用的地址是相同的

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	Date(const Date& d) 
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,实际上是有两个参数一个是显示的被拷贝的对象的引用,另一个是隐式的this指针
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

运算符重载

运算符重载的作用:比如日期类我们想让两个日期进行相减,如果我们直接让这两个对象进行相减就会报错,因为自定义类型不是内置类型,编译器不知道他们是怎么进行运算的,所以我们要自己定义出这些符号具体的操作。运算符重载的关键字是字operator然后后面加上重载符号。

#include <iostream>

using namespace std;

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

public:
	int _year;
	int _month;
	int _day;
};

//重载操作符定义在类外
bool operator<(Date d1, Date d2)
{
	if (d1._year < d2._year)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month < d2._month)
	{
		return true;
	}
	else if (d1._year == d2._year 
	&& d1._month < d2._month 
	&& d1._day < d2._day)
	{
		return true;
	}
	return false;
}

int main()
{
	Date d1(2005, 02, 03);
	Date d2(2006, 6, 10);
	d1 < d2;
	cout << operator<(d1, d2) << endl;
	return 0;
}

因为重载操作也是一个函数所以也可以直接通过调用这个函数来对这两个对象进行比较。
注意如果是定义在类外的那么类成员就不能是私密的应是public,因为在类外不能直接访问私密的类成员
上面的重载操作符是全局函数即定义在类外的,所以显示的形参个数与比较对象的个数相同。如果重载操作符函数是定义在类内的,那么显示传参的个数就要比实际比较的对象个数少一个,因为定义在类内就会有一个隐式的参数this指针存在

#include <iostream>

using namespace std;

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

	bool operator<(Date d)//只有一个显式的参数因为此时还有一个隐式的this
	{
		if (_year < d._year)
		{
			return true;
		}
		else if (_year == d._year && _month < d._month)
		{
			return true;
		}
		else if (_year == d._year && _month < d._month && _day < d._day)
		{
			return true;
		}
		return false;
	}

public:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2005, 02, 03);
	Date d2(2006, 6, 10);
	//定义为全局的重载操作运算符时的调用
	/*d1 < d2;
	cout << operator<(d1, d2) << endl;*/
	//定义在类内时的调用
	d1 < d2;
	d1.operator<(d2);
	return 0;
}

重载运算符的参数中必须有一个是自定义类型的参数,不能全是内置类型。
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。
.* //千万别忘了这个运算符也不能重载
:: sizeof ? : . 注意以上5个运算符不能重载。

赋值运算符重载

运算符重载中有一个特殊的,就是赋值运算符重载,像大于,小于,加,减等,如果你不写那么这个自定义类型就不能进行这些运算符操作,但是赋值运算符=,如果你不写,编译器也会自动生成默认的赋值运算符重载,只不过默认生成的是按字节赋值的浅拷贝,如果自定义类型中成员变量有动态开辟的空间,那么就需要我们自己手动的写上。特别注意,其它的运算符重载既可以定义成全局的,也可以定义在类内,但是赋值运算符只能定义在类内,因为其它的运算符编译器不会自动生成,而赋值运算符编译器会在类内自动生成默认的赋值运算符重载,如果你在类外定义就会造成调用不明确,出现歧义,在类内定义了,编译器就不会自动生成。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。另外赋值运算符,我们要有返回值且选择返回它的引用,当然也可以返回值,只不过是浅拷贝,且传值返回会调用拷贝构造,而返回引用不会调用拷贝构造,且会减少空间的消耗,这个是定义在类里面的,出了作用域 this 还在,只不过是在调用时的中介this不在了,this是这个对象,对象的声明周期不是在这个类里的

对象的传值才要调用拷贝构造,内置类型传值不调用拷贝构造,任何类型的指针都是内置类型,包括自定义类型的指针,传引用就是在传别名也不会调用拷贝构造。

赋值运算符重载和构造函数

赋值运算符是作用于两个已经存在的对象,一个对象的值赋给另一个对象。而构造函数是用一个已经存在的对象去初始化另一个对象。

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

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

相关文章

2 ROS2话题通讯基础

2 ROS2话题通讯基础 2.1 ROS2话题通讯介绍2.2 ROS2常用的消息类型介绍2.2.1 std_msgs消息类型2.2.2 geometry_msgs消息类型 2.3 使用C/C创建基础消息类型的话题通讯2.3.1 创建C/C发布话题信息的功能包并配置VSCode环境2.3.2 编写ROS2发布话题节点CPP文件2.3.3 配置C/C发布话题功…

数据结构学习记录——堆的插入(堆的结构类型定义、最大堆的创建、堆的插入:堆的插入的三种情况、哨兵元素)

目录 堆的结构类型定义 最大堆的创建 堆的插入 堆的插入的三种情况 代码实现 哨兵元素 堆的结构类型定义 #define ElementType int typedef struct HNode* Heap; /* 堆的类型定义 */ struct HNode {ElementType* Data; /* 存储元素的数组 */int Size; /* 堆中…

『python爬虫』08. 数据解析之bs4解析(保姆级图文)

目录 1. 安装bs4模块find()findall() 2. 爬取信息测试总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 1. 安装bs4模块 pip install bs4 pip install-i https://pypi.tuna.tsinghua.edu.cn/simplebs4如果遇到报…

[网络原理] HTTP协议

要珍惜时间呀 文章目录 1. HTTP协议概念2. HTTP协议格式2.URL3. GET与POST方法3.1 GET方法3.2 POST方法3.3 GET与POST的区别 1. HTTP协议概念 HTTP协议是应用层协议,TCP/IP协议为传输层协议,负责传输数据.而HTTP协议相当于对传输的数据据怎样处理和使用进行说明. 每次,我们访问…

Mininet+Ryu安装教程

最近要做一个Mininet的网络环境&#xff0c;网络设备由Mininet来模拟&#xff0c;SDN控制器用Ryu来做&#xff0c;为了避免每次重新做再去翻查资料&#xff0c;我在这里系统地整理一遍 硬件需求 我在 VMWare Workstation 16 Player虚拟机上运行的Ubuntu 22.04.1 硬件需求内存…

供应链 | 需求不确定情况下的物料需求规划: 基于随机优化的研究

作者&#xff1a;Simon Thevenin, Yossiri Adulyasak, Jean-Francois Cordeau​ 引用&#xff1a;Thevenin S, Adulyasak Y, Cordeau J F. Material requirements planning under demand uncertainty using stochastic optimization[J]. Production and Operations Management,…

react的项目实战 2

入口文件引入了app这个组件 app这个组件又引入了header这个组件。 然后外面引入这个组件 进行页面的显示 它不会影响到其他页面的组件的样式。 ​​​​​​​

面试必备:接口自动化测试精选面试题大全

目录 一、 请问你是如何做接口测试的&#xff1f; 二、接口测试如何设计测试用例&#xff1f; 三、接口测试执行中需要比对数据库吗&#xff1f; 四、接口测试质量评估标准是什么&#xff1f; 五、接口产生的垃圾数据如何清理 六、其他接口要先获取接口信息&#xff0c;如…

利用wenda实现本地多模态数据的知识获取和推理

近年来&#xff0c;大型语言模型&#xff08;LLM&#xff09;技术取得了令人瞩目的进展&#xff0c;为自然语言处理领域带来了巨大的变革&#xff0c;但是大多数LLM都面临着领域适应性的问题&#xff0c;因为它们使用的数据都是公开的数据&#xff0c;在国内&#xff0c;有很多…

Day960.架构现代化-微服务 -遗留系统现代化实战

架构现代化-微服务 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于架构现代化-微服务的内容。 在自治气泡模式的基础上&#xff0c;通过事件拦截来实现数据同步&#xff0c;给气泡和遗留系统之间又加上 API 访问这个通信渠道。 这时的自治气泡就和真正的微服务差不…

Rust - 变量与数据的交互方式(move)

变量与数据的交互方式 - 移动 Rust 中的多个变量可以采用一种比较独特的方式和同一个数据进行交互&#xff0c;如下代码所示&#xff0c;将变量x的值赋给y&#xff1a; fn main() {let x 1;let y x; }我们大概可以推论出上述代码的原理&#xff1a;将1这个整数绑定给x变量&…

Mybatis读取和存储json类型的数据

目录 一、测试使用JSONObject来获取json二、设置TableName的autoResultMap为true&#xff0c;TableField的typeHandler为JacksonTypeHandler.class三、设置xml当中的resultMap四、JacksonTypeHandler讲解五、新增假如是JSONObject 不管数据库当中是以json还是longtext数据类型来…

树莓派Opencv调用摄像头(Raspberry Pi 11)

前言&#xff1a;本人初玩树莓派opencv&#xff0c;使用的是树莓派Raspberry Pi OS 11&#xff0c;系统若不一致请慎用&#xff0c;本文主要记录在树莓派上通过Opencv打开摄像头的经验。 1、系统版本 进入树莓派&#xff0c;打开终端输入以下代码&#xff08;查看系统的版本&…

“人工智能教父”从谷歌离职 称后悔发展AI,为世人敲响警钟?

在加入谷歌的第十年、深度学习迎来爆发式发展的当下&#xff0c;被誉为“人工智能教父”的Geoffrey Hinton已从谷歌离职&#xff0c;只是为了告诫人们AI已经变得很危险。 公开资料显示&#xff0c;Geoffrey Hinton在2013年加入谷歌&#xff0c;曾任副总裁&#xff0c;研究机器学…

Python每日一练:硬币的面值奇偶排序陶陶摘苹果(花样解法)

Python每日一练 文章目录 Python每日一练前言一、硬币的面值二、奇偶排序三、陶陶摘苹果总结 前言 很显然&#xff0c;Python的受众远远大于C&#xff0c;其实笔者本人对Python的理解也是远强于C的&#xff0c;C纯粹是为了假装笔者是个职业选手才随便玩玩的&#xff0c;借着十…

【是C++,不是C艹】 缺省参数 | 函数重载 | 内联函数

&#x1f49e;&#x1f49e;欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e; &#x1f449; 专栏&#xff1a;《是C&#xff0c;不是C艹》&#x1f448; 前言&#xff1a; 上期&#xff0c;我带大家给C打了招呼&#xff0c;捎带着认识了命名空间和输入输出&#xff0c;那…

SPSS如何使用基础功能?

文章目录 0.引言1.菜单栏2.工具栏 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对软件界面基础功能进行阐述。    1.菜单栏 &am…

如何向面试官正确地提问?

你好&#xff0c;我是朱显杰。今天我们来聊一聊面试时&#xff0c;如何向面试官正确提问。 我做过8年面试官&#xff0c;面试过500多人。在面试的过程中&#xff0c;我发现了一个普遍现象&#xff0c;就是大部分候选人都不知道如何向面试官正确提问。要么不问&#xff0c;白白…

RocketMq源码分析(六)--消息消费者启动流程

文章目录 一、消息消费者模式二、消费者启动流程1、 push模式1&#xff09;类关系2&#xff09;类构造器3&#xff09;启动流程 2、pull模式1&#xff09;类关系2&#xff09;类构造器3&#xff09;启动流程 一、消息消费者模式 消息消费分两种模式&#xff1a;推&#xff08;p…

【Redis】Redis缓存双写一致性之更新策略

介绍 面试题 1、只要用到缓存&#xff0c;就可能会涉及到Redis缓存与数据库双存储双写&#xff0c;只要是双写&#xff0c;就一定会有数据一致性问题&#xff0c;怎么解决一致性问题&#xff1f; 2、双写一致性&#xff0c;先动缓存redis还是数据库mysql&#xff1f;为什么&a…