C++入门(2)—函数重载、引用

目录

一、函数重载

1、参数类型不同

2、参数个数不同

3、参数顺序不同 

4、 链接中如何区分函数重载

二、引用

1、规则

2、特征

3、使用场景

做参数

做返回值

4、常引用

5、传值、传引用效率比较 

6、引用和指针的区别


接上一小节C++入门(1)—命名空间、缺省参数

一、函数重载

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

 1、参数类型不同

下面代码中有两个add函数,他们的参数类型不同,当我们调用add函数时,add函数会根据传入参数的类型自动判断调用哪个add函数。

int add(int a, int b)
{
	cout << a + b << endl;
	return 0;
}

double add(double a, double b)
{
	cout << a + b << endl;
	return 0;
}
int main()
{
	add(2, 3);
	add(2.2, 3.3);
	return 0;
}

我们传入整型则调用计算整形的add函数,传入浮点型则调用计算浮点型的add函数。

 2、参数个数不同

函数根据参数个数自动匹配。 

void f(int a,int b)
{
	cout << a+b << endl;
}
void f(int a)
{
	cout << a << endl;
}
int main()
{
	f(6+6);
	f(6);
	return 0;
}

 

 3、参数顺序不同 

函数根据不同类型的顺序自动匹配。

void f(int a, char b)
{
	cout << a << b << endl;
}
void f(char b, int a)
{
	cout << b << a <<endl;
}
int main()
{
	f('a', 6);
	f(6, 'a');
	return 0;
}

 

 注意:函数重载不会变慢,因为匹配对应函数是在编译时完成的。

4、 链接中如何区分函数重载


int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}

double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}

int main()
{
	Add(1, 2);     
	Add(1.1, 2.2); 
	return 0;
}

在VS调试中转到反汇编,可以看出Add函数都是通过call Add去调用的,那么链接器怎么区分链接哪个呢?

答案:编译器会对函数名进行修饰,不同编译器对函数名的修饰规则不同,由于VS的规则太复杂,我们在linux下g++编译器中查看C++函数的汇编指令。

在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中,我们也可以发现它的修饰规则为_Z+函数名长度+函数名+参数类型首字母。

  • 通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
  • .如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。

二、引用

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

1、规则

类型& 引用变量名(对象名) = 引用实体; 

int main()
{
	int i = 0;
	int& k = i;//引用

	int j = i;

	cout << &i << endl;
	cout << &k << endl;
	cout << &j << endl;

	return 0;
}

通过三者的地址可以发现,引用只是变量的别名。 

如果我们对引用进行修改呢? 

int main()
{
	int i = 0;
	int& k = i;//引用
	int j = i;

	k++;
	j++;
	cout << i << " " << k << " " << j << endl;

	return 0;
}

根据输出结果,修改引用的值,对应引用的变量也会被修改。

 

 2、特征

  1. 引用在定义时必须初始化
    int& a; // 该条语句编译时会出错
  2. 一个变量可以有多个引用
    int& a1 = a;//给a取别名
    int& a2 = a;//给a取别名
    int& a3 = a2;//给a2(a的别名)取别名也是可以的
  3. 引用一旦引用一个实体,再不能引用其他实体
    int a = 5;
    int b = 10;
    int& ref = a; // 将ref引用绑定到a上
    ref = b; // 此时a的值为10,而不是5,ref仍然绑定到a上
    int& ref2 = b; // 错误:ref已经绑定到a上,不能再绑定到其他实体上

 我们来看下面代码:

int main()
{
	int i = 0;
	int& k = i;//引用
	int j = i;

	k++;
	j++;

	int& m = i;
	int& n = k;
	++n;

	return 0;
}
  • 定义变量 i 初始化为0,定义 k 为 i 的引用,变量 j 被赋值为 i 的值。
  • 分别对 k 和 j 的值自增。
  • 定义两个分别对变量 i 和 k 的引用,对n进行自增。

 可以看出修改引用的值也就是修改被引用的变量的值。

3、使用场景

做参数

通常在写交换两个变量的函数Swap时,一般会借助指针修改变量的值。 

void Swap(int* i, int* j)
{
	int tmp = *i;
	*i = *j;
	*j = tmp;
}
int main()
{
	int i = 0, j = 1;
	Swap(&i, &j);
	printf("i=%d,j=%d", i, j);
	return 0;
}

当我们掌握引用时,就可以借助引用来传参,函数接受两个整型引用变量 x 和 y 作为参数。

void _Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int i = 0, j = 1;
	_Swap(i, j);
	printf("i=%d,j=%d\n", i, j);
	return 0;
}

在之前学习的单链表中,为了修改指向节点结构体的指针中的结构体成员*next,我们需要使用二级指针接收指针的地址,才能修改指针指向的内容。

不过,这次我们学习了引用,使用引用在传参时可以只传参指针,无需地址。

typedef struct Node
{
	struct Node* next;
	int val;
}Node, * PNode;

void PushBack(Node*& phead, int x)
{
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (phead == nullptr)
	{
		phead = newNode;
	}
}

int main()
{
	Node* plist = NULL;
	PushBack(plist, 1);
	PushBack(plist, 2);
	PushBack(plist, 3);

	return 0;
}

我们可以看到,在PushBack函数中,使用了指针和引用结合的方法 ,具体来说,在PushBack函数中,参数phead是一个指向指针的引用,这意味着我们可以通过修改phead来修改函数外部的指针变量plist。

再来看这种传值方式。


void PushBack(PNode& phead, int x)

 这两个函数的参数类型不同,分别是PNode&和Node*&,它们的区别在于传递参数的方式不同。

  • void PushBack(PNode& phead, int x)中的PNode&是一个指向指针的引用类型,它表示一个指向Node结构体指针的引用。
  • 而void PushBack(Node*& phead, int x)中的Node*&是一个指向指针的指针类型,它表示一个指向Node结构体指针的指针。

做返回值

我们先来看看常规函数返回值赋值的过程。

int Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

实际上函数返回值 n 的值传给 ret 是借助一个临时变量实现的,临时变量通常由寄存器(一般存4/8字节)充当,如果较大,则在调用函数之前,在main函数的栈帧中提前开一块空间充当临时变量,调用Count函数时,在调用结束销毁之前把返回值拷贝给main函数的临时变量,然后main函数的临时变量再拷贝给ret。

在函数栈帧中过程如下:

 

如果n在静态区, 也需要借助临时变量将返回值拷贝给ret。

int Count()
{
	static int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

 上述全局变量 n 传值返回的情况所借助的临时变量有些多余,使用传引用返回可以很好解决。

接下来是使用引用作为返回值类型的函数,借助返回值为引用类型产生返回值n的别名,相当于传n的值,减少借助临时变量拷贝的过程,节约时间提高效率。

int& Count()
{
	static int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

 只要出了count作用域不影响变量的生命周期,就可以使用传引用返回。

使用引用返回的好处:

  • 减少拷贝
  • 调用者可以修改返回对象

使用引用返回条件:

  • 函数返回时,出了函数作用域,如果返回对象还在(还没还给系统,比如:静态作用域,全局变量,位于上一层栈帧,malloc的等等),则可以使用引用返回,如果已经还给系统,则必须使用传值返回。

下面这段代码使用了引用相关的语法来实现对数组元素的访问和修改,这种方式可以提高程序的效率并使代码更加简洁易读。 

#define N 10
typedef struct Array
{
	int a[N];
	int size;
}AY;

int& PosAt(AY& ay, int i)
{
	assert(i < N);
	return ay.a[i];
}

int main()
{
	int ret = Count();

	AY ay;
	for (int i = 0; i < N; ++i)
		PosAt(ay, i) = i*10;

	for (int i = 0; i < N; ++i)
		cout << PosAt(ay, i) << " ";

	cout << endl;
	return 0;
}

如果使用引用返回,结果是未定义的。 

 接下来看这段代码:

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	cout << "Add(1, 2) is :" << ret << endl;

	return 0;
}
  • 在这段代码中,Add函数返回了一个局部变量c的引用。然而,这是一种未定义行为,因为当函数返回时,局部变量c的生命周期就结束了,它所占用的内存空间可能被其他变量或函数使用,这意味着返回的引用可能指向一个无效的内存地址。
  • 在main函数中,我们将Add函数的返回值赋值给了ret变量。这里,ret并不是c的引用的引用,而是一个新的变量,它的值是通过复制Add函数返回的引用得到的。然而,由于Add函数返回的引用可能指向一个无效的内存地址,因此ret的值可能是不确定的。
  • 然后,我们再次调用了Add函数,但没有将返回值存储在任何变量中。这次调用可能会改变c所占用的内存空间的值,从而影响ret变量的值。
  • 最后,我们使用cout语句输出ret变量的值两次。由于ret的值可能是不确定的,因此这些输出语句可能会产生不可预测的结果。
  • 总的来说,这段代码存在问题,因为它返回了一个局部变量的引用,这是一种未定义行为。为了避免这种问题,我们应该避免返回局部变量的引用,或者使用动态分配的内存、静态变量或全局变量来存储返回值。

4、常引用

在引用的过程中:

  1. 权限可以平移
  2. 权限可以缩小
  3. 权限不可以放大
int Count()
{
	int n = 0;
	n++;
	return n;
}

int main()
{
	int a = 1;
	int& b = a;

	// 指针和引用,赋值/初始化 权限可以缩小,但是不能放大

	// 权限放大
	/*const int c = 2;
	int& d = c;

	const int* p1 = NULL;
	int* p2 = p1;*/

	// 权限保持
	const int c = 2;
	const int& d = c;

	const int* p1 = NULL;
	const int* p2 = p1;

	// 权限缩小
	int x = 1;
	const int& y = x;

	int* p3 = NULL;
	const int* p4 = p3;

    // 权限可以缩小不能放大只适用于指针和引用
    const int m = 1;
	int n = m;//正确的

    //函数传值返回,借助临时变量传值,临时变量不能修改
	const int& ret = Count();//const保持权限一致

    //类型转换都会产生临时变量
	int i = 10;
    cout << (double)i << endl;//显式类型转换
	double dd = i;//隐式类型转换
	const double& rd = i;

	return 0;
} 

5、传值、传引用效率比较 

#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
	TestRefAndValue();

	return 0;
}

传引用的速度小于1毫秒,所以显示消耗时间为0,可以看出传引用的效率非常高。

在C++中,传引用比传值效率高的原因主要有以下几点:

  1. 避免复制操作:当我们通过值传递参数时,实际上是在内存中创建了一个新的对象,然后将原对象的值复制到新对象中。这个复制操作可能非常消耗资源,特别是当对象很大时。而通过引用传递参数,我们只是传递了对象的地址,没有进行复制操作,因此效率更高。

  2. 节省内存空间:由于传引用只是传递了对象的地址,而不是复制整个对象,所以它使用的内存空间更少。

  3. 可以修改原对象:当我们需要在函数中修改原对象时,必须通过引用或指针传递参数。如果通过值传递参数,函数将操作的是原对象的一个副本,原对象不会被修改。

6、引用和指针的区别

  1. 概念:引用是一个已存在变量的别名,它们共享同一块内存空间。而指针则是一个变量的地址。

  2. 初始化:引用在定义时必须初始化,且一旦引用了一个实体,就不能再引用其他实体。相反,指针可以在任何时候指向任何同类型实体,初始化不是必须的。

  3. NULL值:不存在NULL引用,但可以有NULL指针。

  4. sizeof运算符:对引用使用sizeof运算符,结果为引用类型的大小;对指针使用sizeof运算符,结果始终是地址空间所占字节个数(例如,在32位平台下占4个字节)。

  5. 自增操作:引用自增意味着引用的实体增加1,而指针自增则意味着指针向后偏移一个类型的大小。

  6. 级别:存在多级指针,但没有多级引用。

  7. 访问实体方式:访问指针指向的实体需要显式解引用,而访问引用指向的实体时,编译器会自动处理。

  8. 安全性:相比于指针,引用在使用时相对更安全。

  9. 在底层实现上,引用实际上是有空间的,因为它是通过指针方式来实现的。但在语法概念上,引用被视为别名,没有独立的空间。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决requests库中的期限处理问题:从404到异常再到修复

在使用requests库进行网络请求时&#xff0c;用户可能会遇到一个奇怪的问题&#xff1a;当没有指定请求的期限时&#xff0c;他们得到的响应是404错误&#xff0c;但是一旦指定了请求的期限&#xff0c;就立刻遇到了一个异常&#xff0c;声称远程主机强制关闭了连接。这个问题让…

pytorch文本分类(一):文本预处理

pytorch文本分类&#xff08;一&#xff09;&#xff1a;文本预处理 本文为自己在鲸训练营答题总结&#xff0c;作业练习都在和鲸社区数据分析协作平台 ModelWhale 上。 &#x1f6a9;学习任务原链接在这里 相关数据链接&#xff1a;https://pan.baidu.com/s/1iwE3LdRv3uAkGGI…

Spring Boot使用EhCache完成一个缓存集群

在上一篇在SpringBoot中使用EhCache缓存&#xff0c;我们完成了在Spring Boot中完成了对EhCaChe的使用&#xff0c;这篇&#xff0c;我们将对EhCache的进一步了解&#xff0c;也就是搭建一个EhCache的缓存集群。 集群 在搭建一个EhCache的时候&#xff0c;我们需要先了解&…

【linux】nmon 工具使用

nmon 介绍 nmon是奈杰尔的性能监视器的缩写&#xff0c;适用于POWER、x86、x86_64、Mainframe和现在的ARM&#xff08;Raspberry Pi&#xff09;上的Linux。同样适用于nmon for AIX的工具&#xff08;与IBM的AIX一起提供&#xff09;。njmon与之类似&#xff0c;但将数据保存为…

分类预测 | Matlab实现PSO-GRU-Attention粒子群算法优化门控循环单元融合注意力机制多特征分类预测

分类预测 | Matlab实现PSO-GRU-Attention粒子群算法优化门控循环单元融合注意力机制多特征分类预测 目录 分类预测 | Matlab实现PSO-GRU-Attention粒子群算法优化门控循环单元融合注意力机制多特征分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现PSO…

前端转行可以做什么

前端开发者通常拥有很好的技术背景和解决问题的能力&#xff0c;所以有很多可能的职业选择。以下是一些可能的选择&#xff1a; 全栈开发&#xff1a;这是一个非常热门的职位&#xff0c;需要能够处理前端和后端工作。使用多种编程语言和技术来构建从数据库到用户界面的整个应…

Android14 Beta 5

Beta 5&#xff0c;这是 Android 14 Beta 计划中的最后一次计划更新。这是确保您的应用程序已准备就绪并在非 Beta 用户开始获取 Android 14 之前提供反馈的最后机会。为了使您能够在跨多种外形尺寸的设备上测试您的应用程序&#xff0c;Beta 5 适用于 Pixel Tablet 和 Pixel F…

智能制造中后期:深挖成本、提升效率的关键——标准工时

在智能制造的背景下&#xff0c;企业面临着持续的成本压力和效率提升的需求。特别是在智能制造的中后期&#xff0c;要想进一步深挖成本、提升效率&#xff0c;必须考虑标准工时这一重要因素。标准工时作为一种基础而富有价值的管理工具&#xff0c;对于建立领先的标准工时系统…

使用tesseract-ocr实现图片中的中英文字符提取

1 tesseract-ocr介绍 OCR(Optical Character Recognition)&#xff1a;光学字符识别,是指对图片文件中的文字进行分析识别&#xff0c;获取的过程。 Tesseract&#xff1a;开源的OCR识别引擎&#xff0c;初期Tesseract引擎由HP实验室研发&#xff0c;后来贡献给了开源软件业&…

【2023云栖】刘一鸣:Data+AI时代大数据平台建设的思考与发布

简介&#xff1a; 本文根据2023云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;刘一鸣 | 阿里云自研大数据产品负责人 演讲主题&#xff1a;DataAI时代大数据平台应该如何建设 今天分享的主题是DataAI时代大数据平台应该如何建设&#xf…

本地mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止

背景介绍&#xff1a; MySQL版本5.7&#xff0c;系统Win7&#xff0c;启动mysql服务时提示如下 解决方案 【会删除库中数据及mysql注册信息】&#xff1a; 1、删除原服务MySQL57 C:\Program Files\MySQL\MySQL Server 5.7\bin>mysqld --remove MySQL572、清空data 清空…

HackTheBox-Starting Point--Tier 2---Archetype

文章目录 一 Archetype测试过程1.1 打点1.2 权限获取1.3 权限提升二 题目 一 Archetype测试过程 1.1 打点 1.端口扫描 nmap -sV -sC 10.129.192.2522.枚举SMB共享 smbclient -N -L \\\\10.129.192.252\\查看backups&#xff0c;并发现 prod.dtsConfig 文件&#xff0c;在 pr…

android初集成flutter,遇到的问题

环境 studio版本&#xff1a;2022.1.1 flutter版本&#xff1a;2.8.0 电脑&#xff1a;mac flutter项目总是报错&#xff0c;编译不过 以 Resources Root 加载 记得设置dart&#xff1a;主工程和flutter项目都需要设置&#xff0c;否则不出现手机链接 下面这个样子就是好了&…

Django模型层

模型层 与数据库相关的&#xff0c;用于定义数据模型和数据库表结构。 在Django应用程序中&#xff0c;模型层是数据库和应用程序之间的接口&#xff0c;它负责处理所有与数据库相关的操作&#xff0c;例如创建、读取、更新和删除记录。Django的模型层还提供了一些高级功能 首…

【嵌入式项目应用】__单片机STM32有什么好的裸机程序架构思路推荐?

目录 前言 没设计好程序架构&#xff0c;根本做不稳定。 按照我的思维&#xff0c;我会这样去设计程序&#xff1a; 那这样的好处是什么&#xff1f; (*&#xffe3;︶&#xffe3;)创作不易&#xff01;期待你们的 点赞、收藏和评论喔。 前言 在我刚出来的时候&#x…

Linux_在命令行中以树状结构显示目录_tree

1、安装tree命令 使用tree命令&#xff0c;可以在命令行中以树状结构显示目录&#xff0c;当你想知道一个路径下文件的结构时十分方便&#xff0c;还有别的选项功能&#xff0c;下面会介绍其中的一些&#xff0c;完整的介绍Linux命令 - tree—LZL在线工具。 sudo apt updates…

python对数据的处理合集——字典、列表...

1.两个列表的数据对比 ①list2包含了list1,求出list2多余的值 #codingutf-8list1[1,3,5] list2[1,3,5,7,9,11] list[] for i in list2:if i not in list1:list.append(i) print(list)②求出两个列共同的值 ③两个列表合并 #第一种&#xff1a; list1 [1, 2, 3] list2 [4, …

pyclipper和ClipperLib操作多边型

目录 1. 等距离缩放多边形 1.1 python 1.2 c 1. 等距离缩放多边形 1.1 python 环境配置pip install opencv-python opencv-contrib-python pip install pyclipper pip install numpy import cv2 import numpy as np import pyclipperdef equidistant_zoom_contour(contour…

YOLOv8/YOLOv7/YOLOv5/YOLOv4/Faster-rcnn系列算法改进【NO.77】引入百度最新提出RT-DETR模型中AIFI模块

前言 作为当前先进的深度学习目标检测算法YOLOv8&#xff0c;已经集合了大量的trick&#xff0c;但是还是有提高和改进的空间&#xff0c;针对具体应用场景下的检测难点&#xff0c;可以不同的改进方法。此后的系列文章&#xff0c;将重点对YOLOv8的如何改进进行详细的介绍&…

openbabel包的安装

今天在安装openbabel的时候&#xff0c;使用pip / 通过源码的方式进行安装openbabel&#xff0c;但是都并没有什么用&#xff0c;直接使用&#xff1a; conda install openbabel 就openbabel3.1.1成功了