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

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

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

相关文章

Python 如何实现适配器设计模式?什么是适配器(Adapter)设计模式?

什么是适配器设计模式&#xff1f; 适配器&#xff08;Adapter&#xff09;设计模式是一种结构型设计模式&#xff0c;它允许接口不兼容的类之间进行合作。适配器模式充当两个不兼容接口之间的桥梁&#xff0c;使得它们可以一起工作&#xff0c;而无需修改它们的源代码。 主要…

C++ 基础

准备工具Vscode或者Clion或者Dev C或者Vs studio 和 MSYS2 是C跨平台的重要工具链. 基础一 准备工作安装MSYS2软件 创建文件 一、基本介绍1.1C源文件1.2 代码注释1.3变量与常量1.3.1变量1.3.2 常量1.3.3 二者的区别&#xff1a; 1.4 关键字和标识符 二、数据类型2.1 基本数据类…

C/C++ 实现获取硬盘序列号

获取硬盘的序列号、型号和固件版本号&#xff0c;此类功能通常用于做硬盘绑定或硬件验证操作&#xff0c;通过使用Windows API的DeviceIoControl函数与物理硬盘驱动程序进行通信&#xff0c;发送ATA命令来获取硬盘的信息。 以下是该程序的主要功能和流程&#xff1a; 定义常量…

2023版Idea创建JavaWeb时,右键new没有Servlet快捷键选项

问题&#xff1a;右键时&#xff0c;没有创建servlet的快捷键&#xff0c;如下图&#xff1a; 解决方法&#xff1a; 1.打开idea&#xff0c;点击File>settings(设置)&#xff0c;进入settings页面&#xff0c;如下 从上图中的Files选项中没看到有servlet选项&#xff0c;…

正则表达式入门教程

一、本文目标 让你明白正则表达式是什么&#xff0c;并对它有一些基本的了解&#xff0c;让你可以在自己的程序或网页里使用它。 二、如何使用本教程 文本格式约定&#xff1a;专业术语 元字符/语法格式 正则表达式 正则表达式中的一部分(用于分析) 对其进行匹配的源字符串 …

线程锁的应用与示例代码

为了解决这个问题&#xff0c;可以使用线程锁来确保在提取zip文件中的每个文件时&#xff0c;同一时间只有一个线程可以访问文件。这样可以避免多个线程同时访问和写入文件&#xff0c;从而解决race condition的问题。以下是修改后的示例代码&#xff1a; python import reque…

提升pip速度!设置pip全局镜像源,速度飞起!

文章目录 💢 问题 💢💯 解决方案 💯🐾 镜像源🐾 镜像全局配置🍄 Windows系统🍄 Linux和macOS系统🍄 添加环境变量的方式💢 问题 💢 由于“某些网络限制”原因,我们在使用pip安装python模块的时候速度会比较慢,这个时候我们就需要用到一些镜像源,本文将…

R语言提取文字(字符串)中的内容--正则式(2)

科学研究中有时候咱们收集到的数据很乱&#xff0c;不能马上进行分析&#xff0c;如SEER数据&#xff0c;用过都知道&#xff0c;咱们需要对数据进行清洗&#xff0c;从数据中提取咱们需要的东西&#xff0c;才能进行分析&#xff0c;这时候有个有用的东西叫正则式&#xff0c;…

2023年05月 Python(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 有列表L=[‘UK’,‘china’,‘lili’,“张三”],print(L[-2])的结果是?( ) A: UK B: ‘lili’,‘张三’ C: lili D: ‘UK’,‘china’,‘lili’ 答案:C 列表元素定位 第2题 …

最新宝塔反代openai官方API开发接口详细搭建教程,解决502 Bad Gateway问题

一、前言 宝塔反代openai官方API接口详细教程&#xff0c;实现国内使用ChatGPT502 Bad Gateway问题解决&#xff0c; 此方法最简单快捷&#xff0c;没有复杂步骤&#xff0c;不容易出错&#xff0c;即最简单&#xff0c;零代码、零部署的方法。 二、实现前提 一台海外服务器…

正版软件|Soundop 专业音频编辑器,实现无缝的音频制作工作流程

关于Soundop Soundop 音频编辑器 直观而专业的音频编辑软件&#xff0c;用于录制、编辑、混合和掌握音频内容。 Soundop 是一款适用于 Windows 的专业音频编辑器&#xff0c;可在具有高级功能的直观灵活的工作区中录制、编辑和掌握音频并混音轨道。音频文件编辑器支持波形和频谱…

在Python中使用sqlite3进行数据持久化操作

目录 引言 一、安装sqlite3模块 二、创建数据库连接 三、创建游标对象 四、执行SQL命令 五、提交更改 六、关闭连接 七、使用参数化查询 八、使用ORM进行数据操作 九、备份和恢复数据库 十、处理大量数据 十一、优化查询性能 十二、处理并发访问 十三、处理数据持…

【C++高阶(二)】熟悉STL中的map和set --了解KV模型和pair结构

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; map和set 1. 前言2. map和set介绍3. pair结构介…

一起学docker系列之二深入理解Docker:基本概念、工作原理与架构

目录 前言1 Docker的基本概念2 Docker的基本组成3 docker工作原理4 docker架构5 Docker详细工作过程结语 前言 在当今的软件开发和部署中&#xff0c;Docker已经成为一种不可或缺的工具。它简化了应用程序的打包、交付和运行&#xff0c;同时提供了强大的隔离性和可移植性。本…

Java学习笔记(七)——面向对象编程(中级)

一、IDEA &#xff08;一&#xff09;常用的快捷键 &#xff08;二&#xff09;模版/自定义模版 二、包 &#xff08;一&#xff09;包的命名 &#xff08;二&#xff09;常用的包 &#xff08;三&#xff09;如何引入&#xff08;导入&#xff09;包 &#xff08;四&am…

「Verilog学习笔记」用优先编码器①实现键盘编码电路

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 分析 用此编码器实现键盘的编码电路。 注意&#xff1a;编码器的输出是低电平有效&#xff0c;而键盘编码电路输出的是正常的8421BCD码&#xff0c;是高电平有效。因此将编…

ElasticSearch 安装(单机版本)

文章目录 ElasticSearch 安装&#xff08;单机版本&#xff09;环境配置下载安装包调整系统参数安装启动并验证 ElasticSearch 安装&#xff08;单机版本&#xff09; 此文档演示 ElasticSearch 的单机版本在 CentOS 7 环境下的安装方式以及相关的配置。 环境配置 Linux 主机一…

OpenAI暂停ChatGPT Plus新用户注册;迷宫与图神经网络

&#x1f989; AI新闻 &#x1f680; OpenAI暂停ChatGPT Plus新用户注册&#xff0c;考虑用户体验 摘要&#xff1a;OpenAI决定暂停ChatGPT Plus新用户注册&#xff0c;以应对开发日后使用量激增带来的压力&#xff0c;确保每个人都能享受良好的体验。根据调查机构Writerbudd…

下载huggingface预训练模型到本地并调用

写在前面 在大模型横行的时代&#xff0c;无法在服务器上连接外网的研究僧真的是太苦逼了&#xff0c;每次想尝试类似于CLIP&#xff0c;BLIP之类的大模型都会得到“requests.exceptions.ConnectionError: (MaxRetryError("HTTPSConnectionPool(host‘huggingface.co’, …

Datawhale智能汽车AI挑战赛

1.赛题解析 赛题地址&#xff1a;https://tianchi.aliyun.com/competition/entrance/532155 任务&#xff1a; 输入&#xff1a;元宇宙仿真平台生成的前视摄像头虚拟视频数据&#xff08;8-10秒左右&#xff09;&#xff1b;输出&#xff1a;对视频中的信息进行综合理解&…