c++ - 类的默认成员函数

news2024/9/25 3:21:37

文章目录

    • 前言
    • 一、构造函数
    • 二、析构函数
    • 三、拷贝构造函数
    • 四、重载赋值操作符
    • 五、取地址及const取地址操作符重载


前言

默认成员函数是编译器自动生成的,也可以自己重写,自己重写之后编译器就不再生成,下面是深入了解这些成员函数

一、构造函数

1、构造函数的特征:

(1). 函数名与类名相同。
(2). 无返回值。
(3). 对象实例化时编译器自动调用对应的构造函数。
(4). 构造函数可以重载。

如:

//构造函数
class test01
{
public:
 //构造函数  - 无参构造函数   无返回值,与类名相同
 test01()
 {
	 cout << "test01()" << endl;
 }

 //构造函数的重载 - 有参构造   可以重载
 test01(int a)
 {
	 cout << "test01(int a)" << endl;
 }

private:
 int _a;

};


int main()
{
 test01 p1;		//会自动调用无参构造

 test01 p2(10);	//自动调用有参构造

 return  0;
}

在这里插入图片描述

2、需要注意的点

(1)调用无参构造是不要加(),不然就成函数声明了
如:

 //如函数 void Add()    void - 类型  Add - 函数名  () - 参数列表
 //test01 p1();	 test01  -类型 p1 - 函数名  () - 参数列表    错误使用
 test01 p1;		//正确使用

(2)无参构造与全缺省重载的构造函数
如:

// 构造函数
class test02
{
public:
 //构造函数  - 无参构造函数   无返回值,与类名相同
 test02()
 {
	 cout << "test02()" << endl;
 }

 //构造函数的重载 - 有参构造   可以重载
 test02(int a = 10)
 {
	 cout << "test02(int a)" << endl;
 }

private:
 int _a;

};


int main()
{
 test02 p1;

 return  0;
}

会出现什么情况捏?
答:出现对重载函数调用的不明确
在这里插入图片描述
(3)当我们使用编译器给我生成的默认构造函数时,那么类内的成员变量是否会被初始化呢?
对于内置类型来说:没有初始化出现随机值
对于自定义类型来说:有不需要参数的构造函数就会调用,没有就不会调用。
如:

//结构体
typedef struct N
{
//构造
	N() { cout << "struct N" << endl; }
	int i;
}N;

//联合体
union E
{
	E() { cout << "union E" << endl; }
	int i;
};

//类
 class test02
 {
 public:
	 test02()
	 {
		 cout << "test02()" << endl;
	 }
 private:
	 int _a;

 };

// 构造函数
class test03
{
public:
	

private:
	//内置类型
	int _a;
	//自定义类型
	test02 p;
	N n;
	E e;
};


int main()
{
	test03 p1;

	return  0;
}

在这里插入图片描述

3、给成员变量默认值
给成员变量默认值后如果不对其进行赋初值的话,就会使用该默认值。

//构造函数的默认值
class test02
{
public:
	test02()	//不进行任何赋值
	{
		//...
	};
	test02(int a,int b)
	{
		//...
		_a = a;
		_b = b;
	}
	void Print()
	{
		cout << _a << endl << _b << endl;
	}

private:
//给变量初始默认值
	int _a = 10;
	int _b = 10;

};

int main()
{
	test02 a;	//使用无参构造
	test02 b(20, 20);	//有参构造

	a.Print();
	b.Print();

	return 0;
}

在这里插入图片描述
4、初始化列表
(1)

//这算初始化吗?
test02(int a,int b)
	{
		_a = a;
		_b = b;
	}

答:这不是初始化,因为初始化只能初始化一次,但是在构造函数里可以进行多次的赋值,这只能算是赋初值。

(2)初始化列表格式
在构造函数后面加双引号,在双引号后面加成员变量和括号,括号里是要给成员变量初始化的值。

test03(int a, int b) :_a(a), _b(b)
{
	//,,,
}

(3)注意

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化: 引用成员变量 const成员变量 自定义类型成员(且该类没有默认构造函数时)
  3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
  4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
//初始化列表
class A
{
public:
};

class test03
{
public:

	//初始化顺序按声明顺序初始化
	test03(A a, int b,int c) :_c(c),_a(a),_b(b) 
	{
		//,,,
	}


private:
	//自定义类型
	A _a;
	//const成员
	const int _b;
	//引用
	int& _c;
};

5、作用:
完成初始化工作。
如:
初始化栈

//初始化栈
class Stack
{
public:
	Stack(int capacity = 4) //利用全缺省参数
	{
		_capacity = capacity;
		_top = 0;

		//申请空间
		_a = new int[_capacity];
	}

private:
	//数组指针
	int* _a;
	//容量
	int _capacity;
	//栈顶后一个位置
	int _top;
};

二、析构函数

1、析构函数的特征

(1) 函数名 :~ 加上类名。
(2)不能重载。
(3)没有返回值和参数。
(4)在程序结束时自动调用。

如:

class test04
{
public:
	//析构函数  无返回值无参数 不能重载 结束自动调用
	~test04()
	{
		cout << "~test04()" << endl;
	}
};

int main()
{
	test04 p;
	return  0;
}

在这里插入图片描述
2、需要注意的点

(1)使用编译器自动生成的析构函数在结束时,对于内置类型来说不做处理,因为结束后系统会自动回收,对于自定义类型来说,默认的析构函数会调用成员变量的析构函数进行对该成员变量的清理。
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

class test04
{
public:

	~test04()
	{
		cout << "~test04()" << endl;
	}
};

class test05
{
public:
	
private:
	//内置类型
	int _a;
	//自定义类型
	test04 p;
};


int main()
{
	test05 p;
	return  0;
}

在这里插入图片描述

(2)当我们使用编译器生成的析构函数时,该函数对会怎么处理捏?
对内置类型不做处理,对自定义类型会调用其析构函数。

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
};

class B 
{
public:
	//自定义类型
	A p;
	//内置类型
	int a;
};

int main()
{
	B p;
	return  0;
}

在这里插入图片描述

那么申请的资源会被清理吗?
答:是不会的,所以存在需要清理申请的资源时,析构一定要重写。

3、作用
清理申请的资源。
如果没有申请的资源的话不重写用编译器生成的析构函数也行,当我们申请了资源就必须重写析构函数来释放申请的资源了。
如栈的释放:

class Stack
{
public:
	//构造函数
	Stack(int capacity = 4) //利用全缺省参数
	{
		_capacity = capacity;
		_top = 0;

		//申请空间    有申请的空间需要在析构函数里释放
		_a = new int[_capacity];
	}

	//析构函数
	~Stack()
	{
		//释放申请的空间
		free(_a);
		_a = nullptr;

		_top = _capacity = 0;
	}

private:
	//数组指针
	int* _a;
	//容量
	int _capacity;
	//栈顶后一个位置
	int _top;
};

三、拷贝构造函数

1、拷贝构造的特征
(1)拷贝构造是构造函数的一种重载。
(2)只有一个成员,就是该类的一个引用。函数名如: test06(test06 & a)(test06是一个类)

如:

class test06
{
public:
	test06(int a,int b):_a(a),_b(b)
	{}

	//拷贝构造
	test06(test06& a)
	{
		_a = a._a;
		_b = a._b;
	}

	void Print()
	{
		cout << _a << " " << _b<<endl;
	}

private:
	int _a ;
	int _b ;
};



int main()
{
	test06 a(20,20);
	//test06 b(a); 与下面等价
	test06 b = a;

	a.Print();
	b.Print();

	return 0;
}

在这里插入图片描述

2、需要注意的点
(1)如果传的参数不是引用会发生什么?
答:会出现无尽递归。
在这里插入图片描述

(2)当使用编译器生成的默认拷贝构造函数时会怎么样?
对内置类型:进行值拷贝。
对自定义类型:调用其拷贝构造函数。

class A
{
public:
	A(int a) :_a(a)
	{

	}

	//拷贝构造
	A(A& a)
	{
		cout << "A(A& a)" << endl;
	}

	int _a;

};

class test06
{
public:
	test06(A a, int b) :_a(a), _b(b)
	{}
private:
	//自定义类型
	A _a;
	//内置类型
	int _b;
};

int main()
{
	A a(10);

	test06 b(a, 20);
	test06 c(b);

	return 0;
}

在这里插入图片描述

(3)浅拷贝和深拷贝
浅拷贝也叫值拷贝就是按照字节序的方式直接进行拷贝
如:

test06(test06 &a)
	{
		_a = a._a;
		_b = a._b;
	}

下面那样还能用浅拷贝完成吗?

class test07
{
public:
	test07(int* a):_a(a)
	{
	}
	~test07()
	{
		delete[]_a;
	}
	test07(test07& p)
	{
		_a = p._a;
	}
private:
	int* _a;
};

int main()
{
	int* a = new int[10];
	for (int i = 0; i < 10; i++)
	{
		a[i] = i;
	}
	test07 p(a);
	test07 pp(p);
	return 0;
}

答案是:不能的,因为当p._app._a指的是同一块空间,当p析构之后,p._a指向的空间就被释放了,当pp再使用pp._a时就会出现问题,以及pp析构时又会对这块空间进行析构,这样就会造成重复释放导致错误。
在这里插入图片描述

为了解决这个问题,我们使用深拷贝
如:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/eee7e22a6f9a463c97c857206c9dc85e.png

test07(test07& p)
{
	//重新开辟空间
	int* _a = new int[10];

	//拷贝
	memcpy(_a, p._a, sizeof(int)*10);
	
}

总结:
当遇到需要申请空间之类的成员变量时,需要重写拷贝构造函数并使用深拷贝,不然使用系统默认生成的也可以。

3、作用
初始化对象。

四、重载赋值操作符

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

返回类型 :const 类名 & 返回类的话就可以实现连续赋值了。
函数名:operator=
参数类型:(const & 类名),因为编译器自动会传一个隐含的this ,所以我们传一个参数就够了。

实现:

class Kind
{
public:
	//构造函数
	Kind(int a = 10,int b = 10)
	{
		_a = a;
		_b = b;
	}

	//重载 =  //只能作为成员函数  const 防止被修改
	const Kind & operator=(const Kind& p) 
	{
		this->_a = p._a;
		this->_b = p._b;

		//返回 *this 使其可以连续赋值

		return *this;	
	}
private:
	int _a;
	int _b;
};

int main()
{
	Kind p1(20, 20);
	Kind p2;
	Kind p3;
	//连续赋值
	p3 = p2 = p1;

	cout << "p1: " << p1._a << " " << p1._b << endl;
	cout << "p2: " << p2._a << " " << p2._b << endl;
	cout << "p3: " << p3._a << " " << p3._b << endl;

	return 0;
}

在这里插入图片描述

其实默认的赋值运算符重载函数就是像上写的那样进行赋值,我们对于这样的拷贝叫做浅拷贝,这样做有一个弊端就是如果遇到动态申请的空间的话就有可能发生程序崩溃,这是因为共用一块空间当另一个对象将这块空间释放之后被赋值的那个对象再使用这块空间时就会发生崩溃。

如:

class Kind
{
public:
	//构造函数
	Kind(int a = 10,int b = 10)
	{
		_a = a;
		_b = b;
		arr = new int;
		*arr = a; 
	}

	//重载 =  //只能作为成员函数  const 防止被修改
	const Kind & operator=(const Kind& p) 
	{
		this->_a = p._a;
		this->_b = p._b;
		this->arr = p.arr;

		//返回 *this 使其可以连续赋值

		return *this;	
	}

private:
	int _a;
	int _b;
	int* arr;
};

int main()
{
	Kind p1(20, 20);
	Kind p2 = p1;
	return 0;
}

当上述的p1将arr释放了,p2再使用arr就会发生崩溃。
当我们遇到这种情况时我们使用深拷贝

//深拷贝
const Kind& operator=(const Kind& p)
{
	this->_a = p._a;
	this->_b = p._b;
	
	int* tmp = new int;
	if (tmp == nullptr)
		exit(-1);

	*tmp = *p.arr;
	this->arr = tmp;

	//返回 *this 使其可以连续赋值
	return *this;
}

赋值运算符重载和拷贝构造的区别
拷贝构造是一个已经存在的类给一个刚创建的类进行初始化。
赋值运算符重载是一个已经存在的的另一个已经存在的类赋值。
如:

A a;
//只有a已经存在 使用拷贝构造
A b = a;

A c;
A d;
//c\d都是已经存在的了, 使用赋值运算符重载
c = d;

总结:
当遇到动态申请的空间时需要重写赋值运算符,如果没有用编译器自动生成的即可。

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

1、const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

如:

class test08
{
public:
	void Print() const  //等同与   const * this 
	{
		cout << _a;
	}
private:
	int _a;

};

(1)const对象可以调用非const成员函数吗?
答:不可以,属于权限放大了。

(2)非const对象可以调用const成员函数吗?

答:可以,属于权限缩小。
(3)const成员函数内可以调用其它的非const成员函数吗?
答:不可以,属于权限放大了。

(4) 非const成员函数内可以调用其它的const成员函数吗?
答:可以,属于权限缩小。

2、取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class test08
{ 
public :
	//取地址
 test08* operator&()
 {
 return this ;
 }
	// const取地址
 const test08* operator&()const
 {
 return this ;
 }
};

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

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

相关文章

#381. 四边形继承练习

太爽了 甚至还现学了叉积判断线段是否相交和求面积的方法 先给出我的代码&#xff1a; #include <iostream> #include <vector> #include <iomanip> #include <cmath>using namespace std;//下面需要补充多个类的声明及实现代码 const double EPS 1…

STM32之FreeRTOS移植

1.FreeRTOS的移植过程是将系统需要的文件和代码进行移植和裁剪&#xff0c;其移植的主要过程为&#xff1a; &#xff08;1&#xff09;官网上下载FreeRTOS源码&#xff1a;https://www.freertos.org/ &#xff08;2&#xff09;移植文件夹&#xff0c;在portable文件夹中只需…

2024年文化、历史与人文艺术与社会发展国际会议(CHHASD2024)

2024年文化、历史与人文艺术与社会发展国际会议(CHHASD2024) 会议简介 2024年国际文化、历史、人文、艺术与社会发展会议&#xff08;CHHASD2024&#xff09;将在中国武汉举行&#xff0c;主题为“文化、历史&#xff0c;人文、艺术和社会发展”。CHHASD2024汇集了来自世界各…

【C语言】指针篇-初识指针(1/5)

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 文章目录 **内存和地址(知识铺垫(了解即可))**如何理解编址**指针变量*…

故障诊断 | 基于LSTM的滚动轴承故障诊断

效果 概述 基于LSTM(长短期记忆网络)的滚动轴承故障诊断是一种利用深度学习技术来预测滚动轴承是否存在故障的方法。下面是一个基本的滚动轴承故障诊断的流程: 数据收集:首先,需要收集与滚动轴承相关的振动信号数据。这些数据可以通过传感器或振动监测系统获取。收集的数…

OSI七层网络模型 —— 筑梦之路

在信息技术领域&#xff0c;OSI七层模型是一个经典的网络通信框架&#xff0c;它将网络通信分为七个层次&#xff0c;每一层都有其独特的功能和作用。为了帮助记忆这七个层次&#xff0c;有一个巧妙的方法&#xff1a;将每个层次的英文单词首字母组合起来&#xff0c;形成了一句…

c# .net 香橙派 Orangepi GPIO高低电平、上升沿触发\下降沿触发 监听回调方法

c# .net 香橙派GPIO高低电平、上升沿触发\下降沿触发 监听回调方法 通过gpio readall 查看 gpio编码 这里用orangepi zero3 ,gpio= 70为例 当gpio 70 输入高电平时,触发回调 c# .net 代码 方法1: Nuget 包 System.Device.Gpio ,微软官方库对香橙派支持越来越好了,用得…

构建第一个ArkTS应用之@BuilderParam装饰器:引用@Builder函数

当开发者创建了自定义组件&#xff0c;并想对该组件添加特定功能时&#xff0c;例如在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法&#xff0c;将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题&#xff0c;ArkUI引入了BuilderParam装饰器&…

2024年网络安全行业全景图 | 亚信安全实力占据62领域

近日&#xff0c;安全牛《中国网络安全行业全景图》&#xff08;第十一版&#xff09;正式发布。 本次发布的全景图&#xff0c;包含16个一级安全分类&#xff0c;108个二级细分领域&#xff0c;共收录454家国内安全厂商&#xff0c;细分领域共收录2413项。亚信安全凭借在云安全…

① 学习PID--先认识有什么电机和驱动

我们在学习和使用PID的时候&#xff0c;可能会有很多电机的选择。然而不同的电机使用的PID参数是不太一样的。所以我们需要认识电机和驱动器。 1 电机有什么类型 1.1 电机的简介 电机是一种可以在电能和机械能的之间相互转换的设备&#xff0c;其中发电机是将机械能转换为电能…

虚拟机下CentOS7开启SSH连接

虚拟机下CentOS7开启SSH连接 自己在VMware中装了CentOS 6.3&#xff0c;然后主机&#xff08;或者说xshell&#xff09;与里面的虚拟机连不通&#xff0c;刚学习&#xff0c;一头雾水&#xff0c;查了半天&#xff0c;也不知道怎么弄。 在虚拟机&#xff08;Vmware Workstatio…

优思学院|2024年如何成为一名六西格玛黑带?

如果你总是觉得无论多么努力&#xff0c;职场上似乎难以有所突破&#xff0c;那么你应该知道&#xff0c;你并不是孤独的。 实际上&#xff0c;大量研究表明&#xff0c;高达90%的人对自己的工作感到不满&#xff0c;这意味着在你认识的每10人中&#xff0c;可能只有1人对其工…

小程序视频如何下载到电脑上

小程序视频如何下载到电脑上&#xff0c;很简单 1.利用Fiddler和Charles这些专业的抓包工具 2.利用录屏 3.利用专门抓取资源的工具(集成了抓取下载&#xff0c;而且对资源下载很友好) 工具我已经打包好了 下载高手链接&#xff1a;https://pan.baidu.com/s/1qJ81sNBzzzU0w…

烧结钕铁硼永磁体是如何生产的?

烧结钕铁硼永磁体是采用粉末冶金法生产的&#xff0c;从备料到成品发货一般要经过十几个工艺环节&#xff0c;在不同阶段还包括若干次检测分析。 整个生产过程是一个系统工程&#xff0c;环环相扣。一般我们将生产磁体毛坯的过程称为前道生产环节&#xff0c;将毛坯加工成最终…

Thingsboard PE 白标的使用

只有专业版支持白标功能。 使用 ThingsBoard Cloud 或安装您自己的平台实例。 一、介绍 ThingsBoard Web 界面提供了简便的操作,让您能够轻松配置您的公司或产品标识和配色方案,无需进行编码工作或重新启动服务。 系统管理员、租户和客户管理员可以根据需要自定义配色方案、…

计算机网络2——物理层2

文章目录 一、信道复用技术1、介绍2、频分复用、时分复用和统计时分复用3、波分复用4、码分复用 二、数字传输系统三、宽带接入技术1、介绍2、ADSL 技术3、光纤同轴混合网(HFC网)4、FTTx技术 一、信道复用技术 1、介绍 复用(multiplexing)是通信技术中的基本概念。计算机网络…

抖音小店开店必做的几个基础搭建,新手注意,不做店铺没流量

大家好&#xff0c;我是电商笨笨熊 新手入手做抖音小店一定不要着急选品&#xff0c;先去做店铺基础搭建&#xff0c;吸引更多流量进入店铺&#xff0c;提升店铺曝光度。 店铺搭建都没做好&#xff0c;流量来了你也接不住&#xff0c;所以今天我们就来聊聊哪些必做的搭建内容…

常见 Java 代码缺陷及规避方式

阿里妹导读 在日常开发过程中&#xff0c;我们会碰到各种各样的代码缺陷或者 Bug&#xff0c;比如 NPE、 线程安全问题、异常处理等。这篇文章总结了一些常见的问题及应对方案&#xff0c;希望能帮助到大家。 问题列表 空指针异常 NPE 或许是编程语言中最常见的问题&#xff0…

rv1103/buildroot系统中添加包如v4l2

v4l2: rv1103给出的包中已经有v4l,只需要在menuconfig中打开编译选项&#xff0c;步骤如下&#xff1a; 在luckfox的github网站中下载的源代码在~/linux/luckfox/luckfox-pico-main中目录结构如下&#xff1a; 打开编译选项 cd ./sysdrv/source/buildroot/buildroot-2023.02.…

浅谈普通人成为程序员的几个原因

成为程序员的原因可以因人而异&#xff0c;以下是一些普遍的原因&#xff1a; 兴趣和热情&#xff1a;很多人对计算机科学和编程非常感兴趣。他们喜欢探索如何使用代码来解决问题&#xff0c;并且享受编程过程中的逻辑思考和创造性。 高薪和就业机会&#xff1a;现代社会对技术…