C++--类和对象(二)

news2025/1/10 6:02:19

类和对象的基础定义可参看:C++--类和对象(一)-CSDN博客

本篇讲述类和对象里相当重要的几个成员函数

目录

类的默认成员函数:

1.构造函数

2.析构函数

3.拷贝构造函数

(1)无限递归调用拷贝构造

(2)浅拷贝和深拷贝的区别

4.赋值运算符重载

(1)运算符重载(operator)

(2)赋值运算符重载(operator=)


类的默认成员函数:

1.默认的成员函数是指在类中没有主动编写的,编译器会自动生成的函数

2.编译器默认生成的成员函数不显式实现

一个类中在我们不写的情况下会默认生成6个成员函数,这里只讲前四个,取地址重载函数除非有特殊情况一般不会主动写,毕竟C++有引用这个好东西

1.构造函数

构造函数它的名字叫做构造,但它并不负责对于对象的空间开辟,对象在实例化的时候空间就开好了,而它的作用是对象实例化时初始化对象(局部对象在栈帧创建时,空间就开好了)。构造函数的本质是要替代stack(栈)中写的初始化Init函数的功能,且构造函数会自动调用就很完美的替代了它

class stack
{
public:
    //stack(int n)    //需要传参的构造函数
    //stack()         //无参构造函数

    stack(int n=4)    //全缺省构造函数
    {
        arr=(int*)malloc(sizeof(int)*n);    
        top=0;
        capacity=n;
    }
private:
    int* arr;
    size_t top;
    size_t capacity;
};

构造函数的特点:

1.函数名和类名一致

2.它没有返回值(不要写void,C++规定这样的不用想太多)

3.在对象实例化的时候会自动调用构造函数

4.构造函数时可以重载的

5.在类中没有显式定义构造函数,那么C++编译器会自己生成一个无参的默认构造函数,一旦显式定义构造函数那么编译器就不会生成

6.无参构造函数,全缺省构造函数,及编译器默认生成的构造函数,都叫作默认构造函数。他们都有一个共同点那就是都可以无传参调用,虽然无参构造和全缺省构造可以构成函数重载,但是调用时会出现歧义的情况(你都显式定义了,编译器默认生成的就肯定不可能存在了),所以这三个函数只能存在其中一个。

7.默认生成的构造函数,它对内置类型成员变量的初始化没有要求,也就是说它是不确定的,有可能他会给你初始化成0,也有可能不作为(随机值)。而对于自定义类型成员变量,要求调用这个成员自己的默认构造(如果没有,那么就报错)

2.析构函数

析构函数对应的是stack(栈)中的Destroy功能。它不是对对象本身的销毁,对象的销毁会在生命周期结束时销毁栈帧所以不用管,而它主要是对对象中资源的清理和释放工作,它也会在函数结束时自动调用

class stack
{
public:
    stack(int n=4)    //全缺省构造函数
    {
        arr=(int*)malloc(sizeof(int)*n);    
        top=0;
        capacity=n;
    }
    ~stack()    //析构函数
    {
        free(arr);   
        capacity=top=0;
    }
private:
    int* arr;
    size_t top;
    size_t capacity;
};

析构函数的特点:

1.名字是在类名的前面加上 ~

2.无参数,无返回(和构造一样可以不写void

3.一个类只能有一个析构,不显式实现的话编译器自己会生成默认的析构函数

4.只有在对象生命周期结束时,会自动调用析构函数

5.默认生成的析构对内置类型成员不做处理,但自定义类型自动会调用他自己的析构

6.我们显式实现析构函数时自定义类型在析构函数中,会自动调用他自己的析构,也就是不管啥情况都会自动调用自己的析构函数

7.类中如果没有申请资源的情况下或者默认生成的析构函数足以解决问题,析构函数可以不写;但是有申请资源就一定要写析构函数,否则会出现内存泄漏,像stack(栈)就需要显式实现析构函数

8.一个局部域内多个对象,后定义的先析构

小知识:可以通过有没有存在析构函数显式实例化来判断一个类有没有存在申请资源,有基本上就是有存在申请资源的情况,没有就是没有申请资源

3.拷贝构造函数

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,那么此构造函数也叫作拷贝构造,他是一个很特殊的构造函数

拷贝构造的特点:

1.拷贝构造是构造函数的一个重载

2.拷贝构造函数的参数只有一个且必须是类的引用,使用传值方式会导致无限循环导致编译器报错,因为在逻辑上会引发无限递归调用

3.C++规定了自定义类型对象如果进行拷贝行为必须调用拷贝构造函数,所以这里自定义类型传参或者传值返回都会调用拷贝构造

4.没有显式实现拷贝构造,编译器会自动生成一个默认的拷贝构造,自动生成的拷贝构造对内置类型成员变量会进行浅拷贝(一个字节一个字节的拷贝),自定义类型则是调他自己的拷贝构造

5.函数传值返回会产生一个临时对象调用拷贝构造;传值引用返回,那么返回的就是这个对象的别名,但是函数局部域的对象出了域就会销毁,所以使用引用返回应该保证对象在出了局部域还存在,否则会出现空引用的情况(空引用类似野指针都是指向一块未知的空间),应尽力避免此情况

(1)无限递归调用拷贝构造

这里是个人理解来讲述为什么传值会导致无限递归调用拷贝构造:

也就是:传值->是拷贝行为->触发拷贝构造->拷贝构造需要传值->传值->是拷贝行为->...

这就在语法逻辑上出现了无限递归调用拷贝造,而使用引用就直接使用那个对象自然就不会触发无限拷贝构造

注意:记得给引用加上const避免修改被拷贝对象(能修改在逻辑上那就是倒反天罡了)

(2)浅拷贝和深拷贝的区别

上面就讲过浅拷贝是一个字节一个字节进行拷贝,这种就非常适合没有申请资源的类,而有申请资源的话就不好使了,下面为有申请空间的对象使用浅拷贝的错误演示:

这里是使用默认的拷贝构造(浅拷贝),可以看到在对b进行拷贝构造时a的栈里是1,2,3所以b的内容应该是1,2,3,但是我在拷贝构造后对a栈里的值3进行了修改,这时错误就体现出来了,对a进行出栈是4,2,1没问题,但b不应该啊,b不应该是3,2,1为什么也变成了4,2,1呢?

通常申请空间都会有一个指针指向那块空间,浅拷贝是一个一个字节进行拷贝,把指针指向的地址拷给新对象的指针会怎么样就不用多说了吧

这就会导致两个对象里的指针会同时指向一块空间

这里就引入了第二个问题:

两个对象的指针都指向一块空间,当生命周期结束时调用析构函数时会析构两次会发生什么呢?这就和对一个已经释放空间后的指针再访问,就会导致程序崩溃

解决问题的方法就是使用深拷贝

深拷贝就是开辟新空间,将被拷贝的对象空间里的资源拷过来,这时再指向新空间就可以了

	stack(const stack& st)        //深拷贝
	{
		int* arr = new int[st._top];    //这里就当作malloc一样的效果就好了
		memcpy(arr, st._acc, sizeof(int) * st._top);    //拷贝内容

		_acc = arr;       //指向开辟出来的空间
		_top = st._top;
		_capacity = st._capacity;
	}
    int main()
    {
        stack a;
        stack b(a);//这是拷贝构造
        stack b=a; //这也是拷贝构造 可按自己习惯使用    
        return 0;
    }

4.赋值运算符重载

(1)运算符重载(operator)

C++语言允许我们在类中可以重新定义运算符的定义,可以随自己的想法重新写一个+或者+=,使用运算符重载要求类类型或者枚举类型的参数

1.C++中类类型对象在使用运算符的时候,必须转换成调用对应的运算符重载,若没有对应的运算符重载那么就会报错

2.运算符重载函数是一个有独特名字的函数,它的名字由operator关键字和想重新定义的运算符构成,它和其他函数一样具有返回值和参数列表及函数体

//作为类成员函数声明
返回类型 operator+=(const int& x);//重载一个+=

3.重载运算符的参数个数与该运算符的作用对象一样多,一元运算符有一个参数,二元就有两个,多个参数的运算符传参,如二元运算符左侧运算对象对应第一个参数,右侧对应第二个

4.如果一个重载运算符函数是成员函数,第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个

5.运算符重载后优先级,优先级和结合性与内置类型的运算符一致

6.不能使用语法中没有的符号重新定义新的操作符

7.作用域操作符(::)   sizeof   ?:  (.)   .*  注意这个五个操作符不能重载  

8.重载操作符至少有一个类类型对象不能通过运算符重载改变内置类型对象的含义

9.重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,不好区分。 C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分

class add
{
public:
	add(int x = 0)        //构造函数
	{
		_x = x;
	}
	add& operator+=(int y)
	{
		_x += y;
		return *this;
	}
	add& operator++()//前置++
	{
		*this += 1;
		return *this;
	}
	add operator++(int)//后置++
	{
		add a = *this;
		*this += 1;
		return a;
	}
	void print()
	{
		cout << _x<<endl;
	}
private:
	int _x;
};
int main()
{
	add a(1);
	add b(a);

	(a++).print();
	a.print();
	cout << endl;

	(++b).print();
	return 0;
}

        

10.重载<<和>>的时候需要注意,他们作为成员函数时第一个参数位置会默认被*this占走,导致出现<<cout,不符合使用的习惯和操作性,所以应该重载为全局函数,这样就可以将ostream(cout)/istream(cin)放到第一个形参的位置上了,而第二个位置为类类型对象

注意:这里的重载无法访问对象中私有权限的成员,如果需要访问私有成员需要将函数设为那个类的友元函数(friend)

//流输出
ostream& operator<<(ostream& out, const date& da);
//流输入
istream& operator>>(istream& in, date& da);

(2)赋值运算符重载(operator=)

赋值运算符重载是一个默认成员函数,用于两个已经实例化对象之间的拷贝赋值,这里千万不要和拷贝构造弄混,拷贝构造是使用一个实例化对象去创建初始化一个对象,注意两者的区分

赋值运算符需要注意的点:

1.赋值运算符重载规定上必须重载成成员函数,且参数最好使用const修饰的引用,因为成员函数属于类,而传值传参需要拷贝数据必然会触发拷贝构造导致降低效率

2.它具有返回值,也建议使用引用返回,可以提升效率,有返回值的目的是为了支持连续赋值

3.不显式实现时编译器也会默认生成一个赋值运算符重载函数,它的行为和默认构造函数在一定程度上类似,且对内置成员会进行浅拷贝,对自定义类型则会调用它自己的拷贝构造

4.如果类成员变量都是内置类型的话,使用编译器自己默认生成的赋值预算符重载就足以满足需求,但是有申请资源的话就一定要自己手写了,默认生成的重载默认是浅拷贝,道理和拷贝构造函数同理

stack& operator=(stack& st)
{
	int* newstack = new int[st._top];
	memcpy(newstack, st._acc, sizeof(int)*st.size());
	delete[]_acc;    //删除旧空间

	_acc = newstack;
	_top = st._top;
	_capacity = st._capacity;
	return *this;
}
//拷贝构造
stack(const stack& st)
{
	int* arr = new int[st._top];
	memcpy(arr, st._acc, sizeof(int) * st._top);

	_acc = arr;
	_top = st._top;
	_capacity = st._capacity;
}

下面是简单实现一个栈 :

class stack
{
public:
	stack(int n = 4)
	{
		_acc = new int[n];
		_top = 0;
		_capacity = n;
	}
	void reverse(size_t n)
	{
		if (n > _capacity)
		{
			int* newstack = new int[n];
			memcpy(newstack, _acc, sizeof(int) * _top);

			delete[]_acc;
			_acc = newstack;
			_capacity = n;
		}
	}
	void push_back(const int& x)
	{
		if (_top == _capacity)
		{
			reverse(_capacity * 2);
		}
		_acc[_top++] = x;
	}
	void pop_back()
	{
		assert(!empty());
		_top--;
	}
	int& stacktop()
	{
		assert(!empty());
		return _acc[_top-1];
	}
	bool empty()
	{
		return _top == 0;
	}
	size_t size()
	{
		return _top;
	}
	int& operator[](size_t pos)
	{
		assert(!empty());
		return _acc[pos];
	}
	//赋值运算符重载
	stack& operator=(stack& st)
	{
		int* newstack = new int[st._top];
		memcpy(newstack, st._acc, sizeof(int)*st.size());
		delete[]_acc;
		_acc = newstack;
		_top = st._top;
		_capacity = st._capacity;
		return *this;
	}
	//拷贝构造
	stack(const stack& st)
	{
		int* arr = new int[st._top];
		memcpy(arr, st._acc, sizeof(int) * st._top);

		_acc = arr;
		_top = st._top;
		_capacity = st._capacity;
	}
private:
	int* _acc=nullptr;
	size_t _top=0;
	size_t _capacity=0;
};

本篇文章就到这里了,如果能对你产生帮助就行,感谢你的阅读

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

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

相关文章

在Ubuntu中重装Vscode(没有Edit Configurations(JSON)以及有错误但不标红波浪线怎么办?)

在学习时需要将vscode删除重装&#xff0c;市面上很多方法都不能删干净&#xff0c;删除之后拓展都还在。因此下面的方法可以彻底删除。注意&#xff0c;我安装时使用的是snap方法。 如果你的VScode没有Edit Configurations(JSON)&#xff0c;以及有错误但不标红波浪线的话&…

基于QT实现的TCP连接的网络通信(客户端)

上篇介绍了QT实现网络通信的服务器端&#xff0c;还没看服务器的朋友们先去上篇了解&#xff0c;这篇我来实现一下客户端的实现。 首先还是新建一个项目 选择mainwindow类 在通信前将.pro文件的第一行代码中追加network 窗口搭建 在mainwindow.ui中完成一下窗口的搭建 首先在…

序列建模之循环和递归网络 - 双向RNN篇

序言 在序列建模的广阔领域中&#xff0c;循环神经网络&#xff08; RNN \text{RNN} RNN&#xff09;以其独特的循环结构&#xff0c;在处理序列数据方面展现出了强大的能力。然而&#xff0c;传统的单向 RNN \text{RNN} RNN在处理某些复杂任务时&#xff0c;如自然语言处理中…

基于51单片机的士出租车计价器proteus仿真

地址&#xff1a;https://pan.baidu.com/s/1-GOrUrYlyGZFfkiiO6i5yg 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectron…

「Unity3D」TextMeshPro-Text(UI)无法拖放到TextMeshPro的属性面板上

继承MonoBehaviour&#xff0c;然后定义public TextMeshPro textPro&#xff0c;属性面板上就会有TextMeshPro的拖放槽&#xff08;slot&#xff09;&#xff0c;以配置含有TextMeshPro的组件对象&#xff08;GameObject&#xff09;。 但此时会发现&#xff0c;含有TextMeshPr…

李沐老师动手深度学习pytorch版本的读取fashion_mnist数据并用AlexNet模型训练,其中修改为利用本地的数据集训练

李沐老师的d2l.load_data_fashion_mnist里面没有root参数&#xff0c;所以只会下载&#xff0c;不能利用本地的fashion_mnist数据。所以我使用torchvision 的datasets里面FashionMNIST方法&#xff0c;又由于李沐老师此处是利用AlexNet模型来训练fashion_mnist数据&#xff0c;…

xiaomi pad 6PRO 小米平板6 pro hyperOS降级 澎湃os 降级MIUI 14 教程 免解锁BL 降级,168小时解锁绑定

小米平板 6 Pro 机型代号 &#xff1a;liuqin 降级MIUI 14 小米澎湃 OS 正式版 澎湃OS安卓发布日期卡刷包线刷包OS1.0.7.0.UMYCNXM14.02024-07-13miui_LIUQIN_OS1.0.7.0.UMYCNXM_d618a5c980_14.0.zipliuqin_images_OS1.0.7.0.UMYCNXM_20240705.0000.00_14.0_cn_8cbf5920be.…

Android14之解决编译libaaudio.so报错问题(二百二十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列…

OceanBase内存管理小窍门

本文来自OceanBase热心用户的实践分享。 本文主要是对OceanBase内存管理的实用技巧分享&#xff0c;而并非直接深入OceanBase的代码层面进行阐述。​​​​​​​ 阅读本文章你将了解&#xff1a; 重载运算符new 与malloc在返回值上区别&#xff1f;在ceph 双向链表新用法&am…

[240815] GPT-4o 系统安全卡 | AMD 完成对 Silo AI 的收购,加速 AI 模型在硬件上的开发和部署

目录 GPT-4o 系统安全卡AMD 完成对 Silo AI 的收购&#xff0c;加速 AI 模型在硬件上的开发和部署 GPT-4o 系统安全卡 一、概述 GPT-4o 是一个多模态模型&#xff0c;可以处理文本、音频、图像和视频输入&#xff0c;并生成文本、音频和图像输出。本文概述了 GPT-4o 发布前的…

Azure OpenAI Swagger Validation Failure with APIM

题意&#xff1a;Azure OpenAI Swagger 验证失败与 APIM 问题背景&#xff1a; Im converting the Swagger for Azure OpenAI API Version 2023-07-01-preview from json to yaml 我正在将 Azure OpenAI API 版本 2023-07-01-preview 的 Swagger 从 JSON 转换为 YAML。 My S…

快速排序算法详解及Python实现

目录 引言 快速排序算法步骤 快速排序的Python实现 性能分析 注意事项 引言 快速排序&#xff08;Quick Sort&#xff09;是一种高效的排序算法&#xff0c;由C. A. R. Hoare在1960年提出。它的基本思想是&#xff1a;通过一趟排序将待排序的数据分割成独立的两部分&…

数维原动:如何应对亿级规模的数据分析挑战|OceanBase案例

本文作者&#xff1a;贾万利&#xff0c;北京数维原动教育科技 业务背景 作为国内专注于高端教育评估的机构&#xff0c;北京数维原动教育科技有限公司&#xff0c;其核心业务聚焦于中小学学校的诊断、评估与提升工作。自2009年起&#xff0c;公司便踏上了自主研发之路。经过十…

Visual Studio VS 插件之 ReSharper

集成在VS2022上的ReSharper暂无找到汉化方式&#xff0c;如果有大神可以汉化&#xff0c;请指导下。 首先ReSharper 是IDE 下的插件 主要是基于C# 语句优化的这么一个插件。 使用ReSharper可以使开发效率大大提高&#xff0c;但是也是比较吃电脑的配置。所以说如果配置低的小…

移动开发(一):使用.NET MAUI开发第一个安卓APP

目录 一、.NET MAUI官方介绍 二、开发工具安装 三、创建项目 四、Windows Machine模式运行 五、安卓虚拟机方式运行 六、总结 对于工作多年的C#程序员来说,近来想尝试开发一款安卓APP,考虑了很久最终选择使用.NET MAUI这个微软官方的框架来尝试体验开发安卓APP,毕竟是…

C/C++开发---全篇

1、统筹 学习目标&#xff1a; C/C、python精通。 就业匹配方向&#xff1a;专精一个领域&#xff0c;延长职业生涯。 &#xff08;1&#xff09;适配行业&#xff1b; &#xff08;2&#xff09;量化&#xff1b; &#xff08;3&#xff09;安全&#xff1b; &#xff08;4&…

嵌入式系统可靠性设计案例分析

目录 案例 【题目】 【问题 1】(共 9 分) 【问题 2】(共 8 分) 【问题 3】(共 8 分) 【问题 1】解析 【问题 2】解析 【问题 3】解析 相关知识 案例 阅读以下关于嵌入式系统可靠性设计方面的描述&#xff0c;回答问题 1 至问题 3。 【题目】 某宇航公司长期从事宇航装…

H7-TOOL混合脱机烧录以及1拖4不同的通道烧录不同的程序操作说明(2024-08-07)

【应用场景】 原本TOOL的1拖4是用于同时烧录相同程序给目标板&#xff0c;但有时候一个板子上有多个不同的MCU&#xff0c; 客户希望仅通过一个TOOL就可以完成对板子上多个MCU的烧录&#xff0c;也就是1拖4不同的通道烧录不同的程序&#xff0c;此贴为此制作。 【实验目标】…

Android+Jacoco+code-diff全量、增量覆盖率生成实战

背景 主要是记录下Android项目使用jacoco生成代码覆盖率的实战流程&#xff0c;目前已完成全量覆盖方案&#xff0c;仅使用jacoco就能实现&#xff1b; 由于我们的Android端是使用Java和kotlin语言,目前增量的方案code-diff仅针对Java代码&#xff0c;卡在kotlin文件的分析&am…

java 面试 PDF 资料整理

“尊贵的求知者&#xff0c;作者特此献上精心编纂的Java面试宝典PDF&#xff0c;这份资料凝聚了无数面试精华与实战经验&#xff0c;是通往Java技术殿堂的钥匙。若您渴望在Java编程的求职之路上稳健前行&#xff0c;只需轻轻一点&#xff0c;完成这象征支持与认可的一键三联&am…