C++:类和对象(中)---默认成员函数---运算符重载---const的含义

news2024/9/27 17:21:56

文章目录

  • 默认成员函数
  • 构造函数
  • 析构函数
  • 拷贝构造函数
  • 运算符重载
  • 赋值运算符重载
  • const的含义
  • 取地址及const取地址操作符重载

默认成员函数

首先要理解什么是默认成员函数:类在什么都不写的时,编译器会生成六个默认成员函数
用户没有显式实现,但编译器会生成的成员函数就是默认成员函数

在这里插入图片描述
下面我们对这些函数一一进行介绍

构造函数

在C语言中,无论是实现栈队列链表等各种数据结构,都避免不了要写Init初始化函数,这个函数的功能是给变量一个初始化的值,在C++中,认为C语言的这些问题有些许麻烦,于是进行了一定的优化,构造函数就是要在对象创建的时候,就把信息设置进去

构造函数是一个特殊的成员函数,名字和类名相同,创建类型对象的时候就由编译器自己自动调用,用来保证类中的每一个数据成员都有一个自己的初始值,在整个对象的生命周期中只调用一次

构造函数的特性

构造函数是特殊的成员函数,主要功能是用来初始化对象

它有下面的一些特点


  1. 函数名和类名相同
  2. 没有返回值
  3. 对象实例化的时候会自动调用对应的构造函数
  4. 构造函数可以重载
  5. 如果类中没有显式的构造函数,那么会自动生成一个无参的默认构造函数,如果用户定义了构造函数就不再生成
  6. 默认构造函数是在不写的时候会生成,且内置类型的成员不会进行处理(在C++11中的声明支持给缺省值),自定义类型的成员才会处理,回去调用这个成员的默认构造函数
  7. 无参的构造函数和全缺省的构造函数都是默认构造函数,这两个构造函数只能存在一个

既然它有这么多的特点,那么就一一举例论证它的特点

1. 函数名和类名相同
2. 没有返回值
3. 对象实例化的时候会自动调用对应的构造函数
4. 构造函数可以重载

在这里插入图片描述
在实际写代码时,尽量要写全缺省的构造函数,这样不管如何给参数都有一定的初始化值

class Date
{
public:
	Date(int year=1, int month=1, int day=1)
	{
		cout << "Date(int year, int month, int day)" << endl;
		_year = year;
		_month = month;
		_day = day;
	}

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

int main()
{
	Date d1;
	Date d2(2004);
	Date d3(2004, 6);
	Date d4(2004,6,16);
	return 0;
}

在这里插入图片描述

5. 如果类中没有显式定义构造函数,则编译器会自动生成一个无参的默认构造函数,一旦用户有显式定义就不再生成

这里也是我们接触到的第一个默认构造函数,我们用下面的代码证明它的存在性

在这里插入图片描述
下面继续验证它的后半段存在性

在这里插入图片描述
从中可以看出,当我们没有定义构造函数时,定义一个无参的对象编译器会执行默认构造函数给数据成员初始值,而现在我们定义了构造函数,那么默认构造函数不复存在,此时编译器也就不能执行默认构造函数给无参的d1初始化值,此时会报错—Date不存在默认构造函数,没有合适的构造函数可以使用

6. 默认构造函数是在不写的时候会生成,内置类型的成员不会进行处理(在C++11中的声明支持给缺省值),自定义类型的成员才会处理,回去调用这个成员的默认构造函数

在这里插入图片描述
在这里插入图片描述
简单来说,一般情况下都需要我们自己写构造函数,决定初始化方式,成员变量都是自定义类型,可以考虑不写构造函数,因为会调用自定义类型的构造函数

7. 无参的构造函数和全缺省的构造函数都是默认构造函数,这两个构造函数只能存在一个

在这里插入图片描述

后续还有初始化列表,我们后续进行讲解

析构函数

析构函数就相对简单一点

它存在的意义是什么呢?
我们实现栈顺序表链表等数据结构时,都要在堆上malloc开辟一段空间,但是是不是经常会忘记free呢?这样会造成内存泄漏的危险情况的发生

那么C++在开发的时候就想到了这个问题,因此析构函数就这样产生了,它存在的意义就是完成对象中资源的清理工作,它会在对象被销毁前自动调用

析构函数的特点如下

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。析构函数不能重载
  4. 对象生命周期结束时,C++编译自动调用析构函数。
  5. 调用顺序满足栈的顺序

在这里插入图片描述

拷贝构造函数

在实际代码运用中,我们经常会拷贝一个对象用来做其他事情,在C语言中,这个过程十分简单,把值直接全部从一个内容中拷贝到另外一个地方即可,但在C++中却不那么容易

拷贝构造函数的引入

首先要清楚堆创建后,除非通过free否则是不会被还原的,因此如果有这样的C语言代码:

void push(Stack ps)
{
	ps._a[0] = 10;
	ps._top++;
}

void test2()
{
	Stack s={0,0,0};
	StackInit(&s);
	push(s);
	printf("%d", s._a[0]);
}

这里的StackInit函数只是单纯的初始化,给栈开辟空间,而最后运行结果是10,原因就在于在push函数中,虽然是值传递,但是ps结构体中的成员_a依旧拥有改变堆内存的能力,具体可以用下面的图来表示

在这里插入图片描述
那么现在换到C++,引入类的概念后,整个就变得比C语言要复杂一点,原因如下:

首先,定义一个栈的类,并且完成一系列栈的操作

typedef int STDataType;
class Stack
{
public:
	Stack()
	{
		capacity = 4;
		a = (STDataType*)malloc(sizeof(STDataType) * capacity);
		if (a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		top = 0;
	}
	~Stack()
	{
		top = capacity = 0;
		free(a);
		a = nullptr;
	}
	void Push(STDataType x)
	{
		if (capacity == top)
		{
			capacity *= 2;
			STDataType* tmp = nullptr;
			tmp = (STDataType*)realloc(a,sizeof(STDataType) * capacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			a = tmp;
		}
		a[top] = x;
		top++;
	}
	void Pop()
	{
		top--;
	}
	STDataType Top()
	{
		return a[top];
	}
private:
	STDataType* a;
	int top;
	int capacity;
};

和C语言的实现相同,假如我们直接进行传值拷贝,具体做法如下:

void func1(Stack s)
{
   s.Push(1);
}

int main()
{
   Stack s1;
   func1(s1);
   return 0;
}

再画出和上面相仿的图

在这里插入图片描述
看似和C语言基本相同,但实际相差很大,C++会执行构造函数和析构函数,那么在进入func1的栈帧后,销毁栈帧的时候就会执行析构函数,_a所指向的空间就被销毁掉了,那么回到main函数的栈帧后,结束程序依旧要进行析构函数,此时_a已经被销毁过一次了,程序就会崩溃,无法正常运行

这其实也就说明,C++中想要直接进行对象拷贝似乎不是一件容易的事,两个对象指向同一片空间就必然会出问题,C++语法就定义了拷贝函数来解决这个问题

拷贝构造函数的特征

拷贝构造函数也是特殊的成员函数,具体表现在:

  1. 拷贝函数是构造函数的一个重载
  2. 拷贝函数的参数只有一个并且必须是类类型对象的引用,使用传值方式编译器会报错,因为涉及到了无穷递归调用
  3. 若未显式定义,编译器会生成默认的拷贝构造函数,默认拷贝构造函数会按对象按内存中的存储字节序完成拷贝,也叫做浅拷贝或值拷贝
  4. 深拷贝就涉及到上面栈在堆上空间的问题
  5. 拷贝构造函数的典型应用场景

使用已存在的对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象

下面根据拷贝构造函数的特征进行一一分析

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

这个很好解释,拷贝构造函数函数名和构造函数相同,只是函数参数不同

2. 拷贝函数的参数只有一个并且必须是类类型对象的引用,使用传值方式编译器会报错,因为涉及到了无穷递归调用

假设我们这里是这样实现拷贝构造函数:

//函数定义
Date(const Date d1)
{
	_day = d1._day;
	_month = d1._month;
	_year = d1._year;
}
//函数调用
Date d1(d2);

在这里插入图片描述
那么标准写法是如何写的呢

//函数定义
Date(const Date& d1)
{
	_day = d1._day;
	_month = d1._month;
	_year = d1._year;
}
//函数调用
void func2(Date d2)
{
	d2.Print();
}

int main()
{
	Date d1(2002, 10, 12);
	d1.Print();
	func2(d1);
	return 0;
}

3. 若未显式定义,编译器会生成默认的拷贝构造函数,默认拷贝构造函数会按对象按内存中的存储字节序完成拷贝,也叫做浅拷贝或值拷贝
4. 深拷贝就涉及到上面栈在堆上空间的问题

这里需要注意的是:

不写拷贝构造函数时,编译默认生成的拷贝构造,和之前的构造函数特性是不一样的

  1. 内置类型是值拷贝
  2. 自定义的类型是调用它的拷贝

简单来说,像Date类型的就不需要我们进行拷贝构造,但是Stack类型的就需要进行深拷贝

下面是深拷贝的拷贝构造函数

Stack(const Stack& s1)
{
	top = s1.top;
	capacity = s1.capacity;
	STDataType* tmp = (STDataType*)malloc(sizeof(STDataType) * capacity);
	if (tmp == nullptr)
	{
		perror("malloc fail");
		exit(-1);
	}
	a = tmp;
}

所谓深拷贝,就是重新在堆上开辟一个空间供构造出的栈使用,这样就避免了函数栈帧中的栈在结束时free掉了堆上的空间使得main函数崩溃的情况出现

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

运算符重载

C++在C的基础上的提升在运算符重载上也可以体现出

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

假设我们现在要比较日期谁大,那么这个场景就可以应用运算符重载

// 类体内定义运算符重载
bool operator <(const Date& d1)
{
	if (_year > d1._year)
	{
		return false;
	}
	else if (_year == d1._year && _month > d1._month)
	{
		return false;
	}
	else if (_year == d1._year && _month == d1._month && _day > d1._day)
	{
		return false;
	}
	else
	{
		return true;
	}
}

// 调用main函数
int main()
{
	int i = 10;
	int j = 20;
	int tmp1 = i < j;
	Date d1(2000, 10, 20);
	Date d2(2001, 8, 10);
	int tmp2 = d1 < d2;
	cout << tmp1 << endl;
	cout << tmp2 << endl;
}

我们转到汇编观察

在这里插入图片描述
从中也不难发现,运算符重载后调用小于实际上是调用了运算符重载函数

这样写代码的可读性大大提高

赋值运算符重载

关于拷贝构造和赋值运算符重载,你需要知道的…

1. 就运算符重载本身而言,这个函数本身是成员函数

就对上面的代码来说,假设这里执行下面的命令

d1<d2

实际上在施行的时候会转换成这样:

d1.operator<(d2)

再返回对应的值或其他形式

2. 赋值运算符重载和拷贝构造的区别

前面我们讲了拷贝构造要带引用,不带引用会递归

Date d1(const Date d2);            //错误写法
Date d1(const Date& d2);           //正确写法

那么反观运算符重载,表面上看也是这样:

bool operator <(const Date d1)
bool operator <(const Date& d1)

那上面的写法可以吗,其实是可以的,这里就需要对拷贝构造死循环有更深刻的理解

对于拷贝构造来说,它实质上是需要新建对象的,也就是说这里进行拷贝构造函数的时候要先传参再执行函数新建一个对象,而在传参的过程中就会陷入这是否也是拷贝构造的循环中,导致最后陷入死循环导致构造失败
而对于运算符重载来说,这里仅仅只是执行一个类内的成员函数,两个对象都已经创建好了,而这里传参的Date只是把参数传过来而已,如果使用的不是引用,则会创建一个形参,在内存中会有更多的消耗,经此而已,而如果使用引用就避开了创建形参这样的一个过程,相当于是减小了内存的消耗,因此这里并不一定必须传引用,传引用只是提升效率的一种方式

const的含义

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改,后续有具体实际情况再进行分析

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

这是最后一个默认成员函数,但是使用场景极少
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载

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

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

相关文章

谁能讲清楚Spark之小白入门

在这我假设大家都是小白&#xff0c;那么Spark是什么&#xff1f;你为什么搜索它&#xff1f;思考一下。 首先&#xff0c;Spark是大数据处理框架的一种&#xff0c;那么什么是大数据处理框架&#xff1f;什么是大数据&#xff1f;字面意思懂得都懂。&#xff08;如果不懂去百度…

网络安全代码合集

SQL注入 联合注入 ?id1and 11-- - ?id1order by 1-- - ?id-1union select 1,2,3-- - ?id-1union select 1,database(),3-- - ?id-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase() -- - ?id-1 union sele…

Docker啥是容器编排?

文章目录 容器编排compose案例wordpress案例swarmKubernetes特点 容器编排 Docker容器编排是一种管理和协调多个Docker容器的技术&#xff0c;旨在简化容器化应用程序的部署、扩展和管理。在现代应用开发中&#xff0c;容器化已经成为一种流行的部署方式&#xff0c;Docker是其…

A Deep Framework for Hyperspectral Image Fusion Between Different Satellites

1.摘要 最近&#xff0c;将低分辨率高光谱图像&#xff08;LR-HSI&#xff09;与不同卫星的高分辨率多光谱图像&#xff08;HR-MSI&#xff09;融合已成为提高HSI分辨率的有效方法。然而&#xff0c;由于不同的成像卫星、不同的照明条件和相邻的成像时间&#xff0c;LR-HSI和H…

15.Netty源码之EventLoop

highlight: arduino-light Netty配置主从Reactor模式 通过将NioServerSocketChannel绑定到了bossGroup。 将NioServerSocketChannel接收到请求创建的SocketChannel放入workerGroup。 将2个不同的SocketChannel绑定到2个不同的Group完成了主从 Reactor 模式。 分配NIOEventLoop的…

【Git|项目管理】Git的安装以及本地仓库的创建和配置

文章目录 1.Git简介2.安装Git2.1在Centos上安装git2.2 在ubuntu上安装git 3.创建本地仓库4.配置本地仓库 1.Git简介 Git是一个分布式版本控制系统&#xff0c;用于跟踪和管理文件的更改。它可以记录和存储代码的所有历史版本&#xff0c;并可以方便地进行分支管理、合并代码和协…

JavaScript中的this指向及绑定规则

在JavaScript中&#xff0c;this是一个特殊的关键字&#xff0c;用于表示函数执行的上下文对象&#xff0c;也就是当前函数被调用时所在的对象。由于JavaScript的函数调用方式多种多样&#xff0c;this的指向也因此而变化。本文将介绍JavaScript中this的指向及绑定规则&#xf…

【LeetCode】141.环形链表

题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&#…

M.2 接口

M.2 接口 简介 M.2模块卡口类型 常用硬盘接口 • B key&#xff1a;传输模式为PCI-E 2X或SATA&#xff0c;用于SSD或WWAN • M Key&#xff1a;传输模式为PCI-E 4X或SATA&#xff0c;传输速率达到4GB/s&#xff0c;应用于NVMe PCIe M.2 SSD等。 接口类型 常用硬盘尺寸 M.2设…

sed命令替换 M-BM- 不可见字符 解决脚本或配置文件粘贴后莫名其妙的报错

在使用shell脚本、编辑配置文件时&#xff0c;如果是直接复制&#xff0c;有时会莫名其妙报错。 使用cat -A查看文件&#xff0c;可以看到非常多的 M-BM- 不可见字符 挤占在空格位&#xff0c;正是这些字符导致脚本或配置文件的读取出错、 使用sed命令将文件内的 M-BM- 不可见…

C# IO 相关功能整合

目录 删除文件和删除文件夹 拷贝文件到另一个目录 保存Json文件和读取Json文件 写入和读取TXT文件 打开一个弹框&#xff0c;选择 文件/文件夹&#xff0c;并获取路径 获取项目的Debug目录路径 获取一个目录下的所有文件集合 获取文件全路径、目录、扩展名、文件名称 …

原生js vue react通用的递归函数

&#x1f642;博主&#xff1a;锅盖哒 &#x1f642;文章核心&#xff1a;原生js vue react通用的递归函数 目录大纲 1.递归函数的由来 2.代码逻辑 1.递归函数的由来 递归函数的由来可以追溯到数学中的递归概念和数学归纳法。 在数学中&#xff0c;递归是指通过定义基本情况和…

windows切换php版本以及composer

前提 安装php8.2 安装Php7.4 下载 nts是非线程安全的&#xff0c;这里选择线程安全的&#xff0c;选择64位 解压缩 修改系统环境变量 修改为php-7的 cmd中输入php -v查看 找到composer存放路径C:\ProgramData\ComposerSetup\bin 将三个文件复制到php目录下 重启电脑…

flink采用thrift读取tablets一个天坑

原先的配置 [INFO] StarRocksSourceBeReader [open Scan params.mem_limit 8589934592 B] [INFO] StarRocksSourceBeReader [open Scan params.query-timeout-s 600 s] [INFO] StarRocksSourceBeReader [open Scan params.keep-alive-min 100 min] [INFO] StarRocksSourceBeRea…

中间件安全-CVE漏洞复现-Docker+Websphere+Jetty

中间件-Docker Docker容器是使用沙盒机制&#xff0c;是单独的系统&#xff0c;理论上是很安全的&#xff0c;通过利用某种手段&#xff0c;再结合执行POC或EXP&#xff0c;就可以返回一个宿主机的高权限Shell&#xff0c;并拿到宿主机的root权限&#xff0c;可以直接操作宿主机…

【图论】Prim算法

一.介绍 Prim算法是一种用于解决最小生成树问题的贪心算法。最小生成树问题是指在一个连通无向图中找到一个生成树&#xff0c;使得树中所有边的权重之和最小。 Prim算法的基本思想是从一个起始顶点开始&#xff0c;逐步扩展生成树&#xff0c;直到覆盖所有顶点。具体步骤如下…

【Ansible】

目录 一、Ansible简介二、ansible 环境安装部署1、管理端安装 ansible 三、ansible 命令行模块&#xff08;重点&#xff09;1&#xff0e;command 模块2&#xff0e;shell 模块3、cron 模块4&#xff0e;user 模块5&#xff0e;group 模块6&#xff0e;copy 模块&#xff08;重…

后台管理系统中刷新业务功能的实现

实现 由于刷新业务涉及路由通信所以在store/pinia创建全局变量refresh state:()>{return{// 是否刷新refresh:false,}},在header组件中是为刷新按钮绑定点击实现并对refresh取反操作 <el-button type"default" click"refresh!refresh" icon"R…

LaTex使用技巧20:LaTex修改公式的编号和最后一行对齐

写论文发现公式编号的格式不对&#xff0c;要求是如果是多行的公式&#xff0c;公式编号和公式的最后一行对齐。 我原来使用的是{equation}环境。 \begin{equation} \begin{aligned} a&bc\\ &cd \end{aligned} \end{equation}公式的编号没有和最后一行对齐。 查了一…

No101.精选前端面试题,享受每天的挑战和学习(Promise)

文章目录 1. 解释什么是Promise&#xff0c;并简要说明它的作用和优势。2. Promise有几种状态&#xff1f;每种状态的含义是什么&#xff1f;3. 解释Promise链式调用&#xff08;chaining&#xff09;的作用和如何实现。4. 如何捕获和处理Promise链中的错误&#xff1f;5. 解释…