【C++】类和对象(中篇)—— 默认成员函数,const成员函数,运算符重载

news2024/9/22 2:09:20

前言

类和对象没有技巧,只有多加练习,多多尝试自己完成代码,例如各种运算符的重载,或是实现一个自己的日期类



目录

一、类的六个默认成员函数

二、构造函数

2.1 概念

2.2 特点

2.3 默认无参的构造函数

三、析构函数

3.1 概念

3.2 特性

3.3 默认析构函数

四、拷贝构造函数

4.1 概念

4.2 特性

五、对象复制

六、const成员


一、类的六个默认成员函数

二、构造函数

先看一下我们之前如何初始化一个类的成员变量:

#include<iostream>
using namespace std;

class student
{
private:
	string _id;
	string _name;
	int _age;
public:
	void Inset(string id, string name, int age)
	{
		_id = id, _name = name, _age = age;
	}
	void print()
	{
		cout << _id << endl << _name << endl << _age << endl;
	}
};

int main()
{
	student a;
	student b;
	a.Inset("001", "大黄", 19);
	b.Inset("002", "小黄", 18);
	return 0;
}

我们调用了一个成员函数来实现初始化,那么有没有一种方式使我们可以直接在定义的时候初始化呢? C++ 提供了一种构造函数来实现。

2.1 概念

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

2.2 特点

1. 构造函数不为对象创建空间,而是对对象进行初始化;

2. 构造函数的函数名与类名相同;

3. 构造函数无返回值;

4. 对象实例化时,编译器会自动调用对应的构造函数;

5. 构造函数可以重载。

例如:

#include<iostream>
using namespace std;

class student
{
private:
	string _id;
	string _name;
	int _age;
    int k = 10;
public:
	student(string id = "", string name = "", int age = 18)
	{
		_id = id, _name = name, _age = age;
	}
	student(student& x)
	{
		_id = x._id;
		_name = x._name;
		_age = x._age;
	}
	void print()
	{
		cout << _id << endl << _name << endl << _age << endl << k << endl;
	}
};

int main()
{
	student a("001", "小黄", 19);
	student b;
    student c(a);
    // student print();
	a.print();
	b.print();
    c.print();
	return 0;
}

 我们可以看到,我们可以在定义时直接初始化,也可以通过类似于函数传参的方式,直接对类进行初始化,还可以定义其函数重载实现不同的传参用来初始化,同时我们也可以通过缺省参数的方式对每一个类都初始化,但是需要注意的是,当全缺省时,初始化类的后面不能加括号,否则万一恰好存在一个和新创建的类的名字相同的另一个函数呢?那不就可以理解为另一个函数的调用了吗?正如上图中被注释的一行,这在大多数编译器下是不可以运行的,会给出一个报错。

2.3 默认无参的构造函数

如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,而如果用户显式定义,则编译器将不再生成。但是如果我们不写构造函数的话就会发现,似乎编译器生成的默认构造函数并没有什么作用,成员变量还是随机值,那么这个默认无参的构造函数似乎就完全没有作用欸?

实际上,C++ 把成员变量分为内置类型(基本类型)和自定义类型。内置类型就是语言自带的变量类型,如 int / char / double …,自定义类型就是我们使用 class / struct / union … 自己定义的类型。编译器生成的默认构造函数只会对自定义类型初始化,而不管内置类型

例如:

#include<iostream>
using namespace std;

class A
{
private:
	int a;
public:
	A() { a = 10; }
	void print() { cout << a << endl; }
};

class B
{
private:
	int age;
	A x;
public:
	void print()
	{
		x.print();
		cout << age << endl;
	}
};

int main()
{
	B x;
	x.print();
	return 0;
}

 也许还有人会觉得奇怪,类 A 中还是自己写的构造函数呀?那不是还是需要自己写构造函数吗?但是,思考一下,如果没有类 B 的默认构造函数,那么也就不会调用类 A 的构造函数,x 中的变量 a 也就不会初始化。因此可见,默认构造函数函数还是很有必要的。

PS:无参的构造函数,全缺省构造函数,编译器自动生成的默认构造函数,都可以被称为默认构造函数哦~

三、析构函数

3.1 概念

有构造函数初始化,那么对应的也会有析构函数来进行资源清理。注意!是资源清理,不是销毁对象本身!对象的销毁是由编译器完成的,析构函数是在对象销毁的时候自动调用的函数,用于对象中资源的清理工作。例如对堆区空间的释放等。

3.2 特性

1. 析构函数的函数名是在类名前加上字符 ~;

2. 析构函数无参数无返回值类型;

3. 一个类只能有一个析构函数,若用户未显式定义,编译器会自动生成默认的析构函数,即析构函数不可重载;

4. 在对象生命周期结束时,由编译器自动调用析构函数。

例如:

#include<iostream>
using namespace std;

class A
{
private:
	int* arr;
	char m;
	int  n;
public:
	A()
	{
		arr = (int*)malloc(4);
		m = 'x';
		n = 99;
	}
	~A()
	{
		if (arr != nullptr)
			free(arr);
		arr = nullptr;
		m = n = 0;
		cout << "析构完成" << endl;
	}
};

int main()
{
	A x;
	return 0;
}

如图便是一个析构函数,主要作用时将 A.arr 申请的堆空间释放掉了。

3.3 默认析构函数

和构造函数一样,当用户未显式地定义类的析构函数的时候,编译器会自动生成一个默认的析构函数,同样,它也是仅对自定义类型起作用。

例如:

#include<iostream>
using namespace std;

class A
{
private:
	int* arr;
public:
	A()
	{
		arr = (int*)malloc(4);
	}
	~A()
	{
		if (arr != nullptr)
			free(arr);
		arr = nullptr;
		cout << "析构完成" << endl;
	}
};

class B
{
private:
	int nums = 99;
	A p;
};

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

可以看到,虽然我们没有写类 B 的析构函数,但是它的成员变量中有类 A ,还是会调用类 A 的析构函数,因此可以推断出系统自动生成了一个析构函数。由此可见当我们定义的类中没有申请资源的时候,可以不写析构函数,而出现了申请资源的情况,则一定要写,否则会造成内存泄漏!

四、拷贝构造函数

假设我们需要初始化一个类,但是已经存在一个类,我们希望两者是一样的,那么能不能把这个类直接传参过去,实现拷贝呢?

答案是肯定的,这就是拷贝构造函数。

4.1 概念

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已存在的类的类型对象创建新对象时由编译器自动调用。本质上是构造函数的一种重载。

4.2 特性

1. 拷贝构造函数的参数有且仅能有一个,通常用该类的引用进行传参,不能使用传值调用,否则系统将报错;

2. 若未显示定义拷贝构造函数,则编译器会生成默认的拷贝构造函数,默认拷贝构造函数将函数成员变量按字节序完成拷贝,这种方式为浅拷贝(值拷贝)

3. 在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

例如:

#include<iostream>
using namespace std;

class A
{
private:
	int* arr;
public:
	A()
	{
		arr = (int*)malloc(4);
	}
	A(A& a) 
	{
		arr = (int*)malloc(4);
		cout << "拷贝构造" << endl;
	}
	~A()
	{
		if (arr != nullptr)
			free(arr);
		arr = nullptr;
		cout << "析构完成" << endl;
	}
};
int main()
{
	A x;
	A y(x);
	return 0;
}

当我们在主函数里面新创建一个类A时,用已经有的 x 对新的 y 进行构造,需要特别注意的是,在类 A 的拷贝构造函数中只能用引用传参

如果是普通的形参,那么在之前的 C 语言的学习中我们已经知道在调用拷贝构造函数的时候,形参会新创建一个,也就是说此处会新创建一个类 A,而这个类 A 的构造方式又是拷贝构造,然后再次调用拷贝构造,那么又需要新的形参,从而导致了死循环,因此只能采用引用传参的方式。

五、对象复制

对象复制从某种程度上来说和拷贝构造函数有一定的相似,不过对象复制是两个已有的对象之间进行赋值,这个时候就会用到运算符重载,什么叫运算符重载呢?简单来说,即对于自定义的一个类,系统不知道怎么调用一些运算法,例如两个日期相减,我们都知道可以得到一个数字,但是系统没有日期类的定义,因此也就会导致两个自定义的日期类不能想减。

1.通常情况下,我们也是采用引用传参的方式进行构造,这样做的目的是为了节省形参需要的空间。

2.函数的返回值采用类的引用,这样做的目的是实现连续赋值,例如 x = y = z

3.赋值运算符只能重载为成员函数,这是系统规定的(原因在于,系统会自动生成一个默认的赋值运算符重载)

4.在赋值运算符的重载中也需要注意“浅拷贝”带给的危害

#include<iostream>
using namespace std;

class A
{
private:
	int* arr;
public:
	A()
	{
		arr = (int*)malloc(4);
	}
	A(A& a) 
	{
		arr = (int*)malloc(4);
		cout << "拷贝构造" << endl;
	}
	~A()
	{
		if (arr != nullptr)
			free(arr);
		arr = nullptr;
		cout << "析构完成" << endl;
	}
	A& operator = (const A& p) {
		if (this == &p)
			return *this;
		*arr = *(p.arr);
		cout << "对象复制" << endl;
	}
};
int main()
{
	A x;
	A y = x;
	y = x;
	return 0;
}

六、const成员

我们常常会看到在一个类的成员函数的参数列表之后,多出一个 const,这个 const 修饰的是当前调用函数的对象,也就是 this 指针。简单来说,就是表示这次调用函数时,调用函数的对象的内容不能被修改。通常用于代码的安全性,防止误操作修改了对象。

#include<iostream>
using namespace std;

class A
{
private:
	int* arr;
public:
	A()
	{
		arr = (int*)malloc(4);
	}
	A(A& a) 
	{
		arr = (int*)malloc(4);
		cout << "拷贝构造" << endl;
	}
	~A()
	{
		if (arr != nullptr)
			free(arr);
		arr = nullptr;
		cout << "析构完成" << endl;
	}
	A& operator == (const A& p)  const
    {
		// *arr = *(p.arr); 不能对this.arr修改
		cout << "对象比较" << endl;
	}
};
int main()
{
	A x;
	A y = x;
	y = x;
	return 0;
}

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

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

相关文章

ORB305与CISCO路由器构建L2TP over IPSec VPN操作手册

1、网络拓扑在思科路由器与ORB305之间建立一个安全隧道&#xff0c;对客户路由器端设备子网&#xff0c;与思科路由器端服务器子网之间的数据流进行安全保护&#xff0c;组网拓扑图如图所示。 2、思科路由器端配置指导(此处以多数客户使用专线上网形式为例)Cisco&#xff08;AR…

90年三本程序员,8年5跳,年薪4万变92万……

很多时候&#xff0c;虽然跳槽可能带来降薪的结果&#xff0c;但依然有很多人认为跳槽可以涨薪。近日&#xff0c;看到一则帖子。 发帖的楼主表示&#xff0c;自己8年5跳&#xff0c;年薪4万到92万&#xff0c;现在环沪上海各一套房&#xff0c;再干5年码农&#xff0c;就可以…

2022年NOC大赛创客智慧编程赛道图形化scratch初赛题,包含答案解析

目录 一、单选题 二、多选题 三、判断题 下载打印文档做题: 一、单选题

项目干系人是什么?如何有效管理项目干系人?

项目干系人是指对项目具有利益关系或影响力的个人、团体或组织。他们可能会对项目的目标、范围、进度、成本、质量等方面产生影响&#xff0c;因此&#xff0c;有效地管理项目干系人是项目管理成功的关键之一。 一、干系人识别和分类 项目经理应该首先识别和分类所有与项目有关…

STM32模数转换器(ADC)

1.ADC的简要 我们首先说一下ADC的转换过程&#xff0c;然后说一下原理&#xff0c;当然如果嫌啰嗦可以直接跳过。 ADC是英文Analog-to-Digital Converter缩写&#xff0c;翻译过来就是模数转换器&#xff0c;是指将连续变化的模拟信号转换为离散的数字信号的器件。A/D转换的作…

DiffusionDet源码阅读(1)

本文仅仅适用于已经通读过全文的小伙伴 本文代码节选自 mmdet 中的 DiffusionDet 代码&#xff0c;目前该代码还处于 Development 阶段&#xff0c;所以我博客里写的代码和之后的稳定版本可能稍有不同&#xff0c;不过不用担心&#xff0c;我们只看最关键的部分 DDPM中扩散部…

mybatis中大数据量foreach插入效率对比

1.controller代码 RequestMapping("/testInsert")public String testInsert(Integer sum){testService.testInsert(sum);return "发送成功";}2.service代码 Overridepublic void testInsert(Integer sum) {long start System.currentTimeMillis();List<…

LightGBM面试题

1.偏差 vs 方差? 偏差是指由有所采样得到的大小为m的训练数据集&#xff0c;训练出的所有模型的输出的平均值和真实模型输出之间的偏差。 通常是由对学习算法做了错误的假设导致的描述模型输出结果的期望与样本真实结果的差距。分类器表达能力有限导致的系统性错误&#xff0c…

基于AT89C52单片机的温度检测报警设计

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87708680?spm1001.2014.3001.5503 源码获取 主要内容&#xff1a; 本系统的设计主要是了解了单片机微型计算机&#xff0c;根据现实生活的需要以及已掌握的理论知识…

Linux应用程序开发:静态库与动态库的制作及使用

目录 一、库的简介二、静态库与动态库的简介三、静态库制作与调用案例四、动态库制作与调用案例 一、库的简介 库是一种可执行的二进制文件&#xff0c;是编译好的代码。使用库可以提高开发效率。而Linux库的种类可分为动态库和静态库。 二、静态库与动态库的简介 1、静态库&a…

第十七届中国CFO大会圆满举办 用友蝉联中国CFO首选智能财务厂商!

4月21日&#xff0c;由财政部指导、《新理财》杂志社主办、用友等单位协办的「数字财务 智能引领」第十七届中国CFO大会在北京圆满举办&#xff01;业内专家、权威学者以及众多来自央国企等知名大型企业的财务领路人荟聚一堂&#xff0c;共襄中国CFO领域的顶尖盛会&#xff0c;…

数字化转型中的石头和沙子问题

作者介绍 朱金衡&#xff0c;西门子Mendix 高级技术咨询顾问及架构师&#xff0c;Mendix Certified 中级培训讲师以及TOGAF Certified 企业架构师。作为专家服务架构师提供咨询服务&#xff0c;如方案设计、开发辅导、故障排除、应用程序审查等&#xff0c;同时创造了许多专门…

【算法】从x的n次方看递归时间复杂度计算

从x的n次方看递归时间复杂度计算 1.循环 这个问题&#xff0c;最简单的办法是用循环 int pow1(int x,int n) {int result 1;for(int i0;i<n;i){result*x;}return result; }如上算法的时间复杂度为O(N)&#xff0c;但还是不够理想。这时尝试使用递归算法 2.递归1 int po…

交换机的电口和光口到底是个啥东东,做网络的这个常识得懂!

在计算机网络中&#xff0c;交换机是一个非常重要的设备&#xff0c;它可以将来自不同设备的数据包进行转发和交换。交换机通常具有多个接口&#xff0c;其中包括光口和电口。在本文中&#xff0c;我们将详细讨论交换机的光口和电口的概念以及它们的不同之处。 电口 电口是交换…

应届生的天坑,悔不该进那外包啊.....

关于计算机专业应届生毕业之后会遇到的就业问题&#xff0c;网上已经有许多的套路&#xff0c;实际上许多人在选择专业的时候并没有考虑到之后的就业方向&#xff0c;甚至于自己所学的专业面向的工作岗位都不是特别清楚。计算机专业毕业大概率是要做程序员的&#xff0c;而目前…

RichTextBox控件详解

RichTextBox和TextBox的区别 从外观来看 multiline设置为true or false区别 textbox RichTextBox 先看截图 属性 AcceptsTab AutoWordSelection BulletIndent DetectUrls Dock EnableAutoDragDrop HideSelection Lines ScrollBars WordWrap SelectionIndent and SelectionC…

FE_TA不知道的CSS 换行系列【1】white-space

在W3C官方描述中&#xff0c;white-space主要有以下两个作用&#xff1a; 是否进行空格合并&#xff0c;以及控制空格合并的方式&#xff1b;是否在soft wrap opportunities&#xff08;文本中可进行换行的断点位置&#xff09;处进行文本换行。 从字面意思来看white-space即…

从github下载项目并进行环境配置

文章目录 1 设置虚拟环境2 git clone 链接地址3 环境配置 1 设置虚拟环境 利用pycharm打开项目&#xff1a;File->Open配置对应的虚拟环境&#xff1a;File->Setting->Project->Python解释器&#xff0c;然后选择对应的虚拟环境如果没有提前设置虚拟环境&#xf…

私人工具集6——使用C# 创建一个简单的restful风格的WebAPI

创建一个简单的WebApi 工具&#xff1a;VS2022 创建新项目 打开VS2022,创建新项目&#xff0c;可以搜索API作为关键字。 为项目取个名字 创建的应用程序&#xff0c;选择WebAPI&#xff0c;注意&#xff0c;右侧的信息默认即可&#xff0c;不要随意选择。 点击创建&#xff…

高可用消息服务消息一致、可靠性、链路稳定性核心关注点

面临的问题 初期业务主要的场景是直播间的群聊消息以及一小部分的单聊消息。由于是教育场景&#xff0c;所以业务在划分聊天室的时候是以班级为单位进行划分的&#xff0c;假设每个聊天室的人数为500人。 问题一&#xff1a;用户的维护 直播场景的群聊与微信等常见的群聊在用…