文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

【C++心愿便利店】No.2---函数重载、引用

2023-09-22 18:27

关注

文章目录


前言

在这里插入图片描述

👧个人主页:@小沈YO.
😚小编介绍:欢迎来到我的乱七八糟小星球🌝
📋专栏:C++ 心愿便利店
🔑本章内容:函数重载、引用
记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


提示:以下是本篇文章正文内容,下面案例可供参考

🌟一、函数重载

函数重载

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!"

🌏1.1.函数重载概念

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

1. 参数类型不同
#includeusing namespace std;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(){cout << Add(1, 2) << endl;cout << Add(1.1, 2.2) << endl;return 0;}
类型不同:一个整形一个浮点型但是函数名相同C++会自动匹配类型C却不能 2. 参数个数不同
#includeusing namespace std;void f(){cout << "f()" << endl;}void f(int a){cout << "f(int a)" << endl;}int main(){f();f(10);return 0;}
3. 参数类型顺序不同
#includeusing namespace std;void f(int a, char b){cout << "f(int a,char b)" << endl;}void f(char b, int a){cout << "f(char b, int a)" << endl;}int main(){f(10, 'a');f('a', 10);return 0;}
注意:是参数类型的顺序不同不是形参的名字不同
#includeusing namespace std;void f(int a, char b){cout << "f(int a,char b)" << endl;}void f(int b, char a){cout << "f(int b, char a)" << endl;}

请添加图片描述

注意:返回值不同不能构成重载
#includeusing namespace std;void f(char a, int b){cout << "f(int a,char b)" << endl;}int f(char a, int b){cout << "f(int a, char b)" << endl;}int main(){return 0;}

请添加图片描述

4. 重载与缺省参数的碰撞擦出的火花
#includeusing namespace std;//构成函数重载void func(int a){cout << "void func(int a)" << endl;}void func(int a, int b = 1){cout << "void func(int a,int b)" << endl;}int main(){func(1,2);//调用存在歧义func(10);return 0;}

重载和缺省参数碰撞是可以构成重载的(参数个数不同),但是会出现调用歧义(当调用func(1,2)是不会出现问题的,但是调用func(10),编译器就不知道调用哪个因为两个都符合调用)

🌏1.2.C++支持函数重载的原理 – 名字修饰

为什么C++支持函数重载,而C语言不支持函数重载呢

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接,最终形成一个可执行程序

1. 对于C语言示例:

对于C语言来说,当经过预处理、编译、汇编、链接

//#include//using namespace std;void func(int i, double d){//cout << "void func(int i, double d)" << endl;}void func(double d, int i){//cout << "double func(double d, int i)" << endl;}int main(){func(1, 5.2);    func(5.2, 1);return 0;}
效果演示:

请添加图片描述

2. 对于C++示例:
#includeusing namespace std;void func(int i, double d){//cout << "void func(int i, double d)" << endl;}void func(double d, int i){//cout << "double func(double d, int i)" << endl;}int main(){func(1, 5.2);    func(5.2, 1);return 0;}
效果演示:

在这里插入图片描述

名字修饰:

先提前说明一下,这部分不懂得可以看C语言—程序环境和预处理(底层原理万字详解)

请添加图片描述

实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?

1. 采用C语言编译器编译后结果:

结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变

2. 采用C++编译器编译后结果:

请添加图片描述

结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参
数类型信息添加到修改后的名字中

函数名修饰规则带入返回值,返回值不能构成重载
#includeusing namespace std;int func(double d, int i){cout << "void func(int i, double d)" << endl;return 0;}void func(double d, int i){cout << "double func(double d, int i)" << endl;}int main(){func(1.1, 1);func(1, 1.1);return 0;}

请添加图片描述

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

🌟二、引用

🌏2.1.引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:孙悟空,唐僧称为"悟空",江湖上人称"齐天大圣"。
类型& 引用变量名(对象名) = 引用实体;

示例:
void TestRef(){int a = 10;int& ra = a;//<====定义引用类型printf("%p\n", &a);printf("%p\n", &ra);}
效果演示:

请添加图片描述

注意:引用类型必须和引用实体是同种类型的

🌏2.2.引用特性

  • 引用在定义时必须初始化
  • 一个变量可以有多个引用
  • 引用一旦引用一个实体,再不能引用其他实体
void TestRef(){1. 引用在定义时必须初始化int a = 10;// int& ra; // 该条语句编译时会出错2. 一个变量可以有多个引用int& ra = a;//给a取别名int& rra = a;//给a取别名int& raa = ra;//给b(a的别名)取别名也是可以的3. 引用一旦引用一个实体,再不能引用其他实体(C++的引用不可以改变指向但是Java可以)int x = 1;b = x; //这里是赋值而不是把b变成x的别名}

在这里插入图片描述

🌏2.3.常引用

void TestConstRef(){权限的放大:就相当于带上金箍圈的孙悟空(const)摘下了金箍圈变得肆无忌惮const int a = 10;//int& ra = a; // 该语句编译时会出错,a为常量 --- 权限的放大在这里去掉了const也就是去掉了金箍圈const int& ra = a;--- 权限的平移:带上金箍圈无论是孙悟空还是齐天大圣它都有限制不会肆无忌惮权限的缩小:本来是大闹天宫的齐天大圣被戴上了金箍圈(const)就有了限制int x=10;const int& y=x;double d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d;在C/C++中有规定:发生类型转换,会产生一个临时变量,例如上面这一小段代码const int& rd = d,转换时会有一个int类型的临时变量,临时变量再给rd,但是临时变量具有常性int& rd = d这里就是权限的放大不能通过编译,所以这种是对的const int& rd = d// int& b = 10; // 该语句编译时会出错,b为常量 - 不能变成常量对象的别名const int& b = 10;--- 权限平移}
注意:

在引用的过程中:

  1. 权限可以平移
  2. 权限可以缩小
  3. 权限不可以放大
示例:
#includeusing namespace std;int func(){int a = 0;return a;}int main(){const int& ret = func();return 0;}

请添加图片描述

🌏2.4.使用场景

1. 做参数/交换两个数值:
#includeusing namespace std;void Swap(int& left, int& right){int tmp = left;left = right;right = tmp;}int main(){int i = 3, j = 6;Swap(i, j);cout << i << endl;cout << j << endl;return 0;}
效果演示:

请添加图片描述

2. 做返回值: 1. 传值返回:不是把n返回给ret,n在Count函数栈帧里面,函数调用结束栈帧也就销毁了

实际原理:是会生成一个临时变量(可能寄存器充当也可能其他方式),n会在返回值之前拷贝给临时变量,临时变量不会在Count函数的栈帧,一般是在寄存器或者上一层函数的栈帧

#includeusing namespace std;int Count(){int n = 0;n++;// ...return n;}int main(){int ret = Count();return 0;}
2. 传引用返回第一种:返回的是n的别名/n的引用

会出现类似野指针的危险:n都销毁了,在访问n的别名
问:n都销毁了还能访问它的别名吗?
答:可以,因为空间销毁并不是这块空间就没了而是被系统回收,就像酒店里的房间,退房后房间不会消失而是被回收你的入住权租给别人或者空着,而野指针就是退房后你还偷偷藏了房间的钥匙,然后偷偷跑进房间。但是这里不是野指针,返回n的别名是不合法的

#includeusing namespace std;int& Count(){int n = 0;n++;// ...return n;}int main(){int ret = Count();cout << ret << endl;cout << ret << endl;return 0;}

请添加图片描述

效果演示:

程序的结果有两种可能:1和随机值------>调用函数返回n的别名,当int ret=Count(),函数Count()栈帧已经销毁了,再去访问这块空间就会出现两种可能性第一种是1拷贝给ret,还有一种可能是随机值(取决于栈帧销毁后空间是否会被置成随机值取决于编译系统)请添加图片描述

3. 传引用返回第二种:

这种代表ret也是n的别名,第一次访问打印ret是1,第二次就变成了随机值为什么
知识点补充:cout是一个函数调用,(调用函数先传参)第一次先传参取到的还是1然后进行函数调用,Count函数的栈帧销毁,第一次函数调用占用的还是那块空间只不过可能比之前Count函数栈帧大或者小此时函数调用覆盖这块空间而ret还是这块空间的别名所以取到的就是一个随机值,但也不一定是随机值,当Count函数栈帧很大n在下面,就不会被覆盖

#includeusing namespace std;int& Count(){int n = 0;n++;// ...return n;}int main(){int& ret = Count();cout << ret << endl;cout << ret << endl;return 0;}
效果演示:

请添加图片描述

4. 例题及构图解析:
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;    return 0;}

在这里插入图片描述

3. 注意:

注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

🌏2.5.传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低

1. 值和引用作为函数参数的性能比较:
#includeusing namespace std;#include 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;}
效果演示:

请添加图片描述

2. 值和引用的作为返回值类型的性能比较:
#includeusing namespace std;#include struct A { int a[10000]; };A a; //值返回A TestFunc1() { return a; } //引用返回A& TestFunc2() { return a; }void TestReturnByRefOrValue(){ //以值作为函数的返回值类型size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc1();size_t end1 = clock(); //以引用作为函数的返回值类型size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc2();size_t end2 = clock(); //计算两个函数运算完成之后的时间cout << "TestFunc1 time:" << end1 - begin1 << endl;cout << "TestFunc2 time:" << end2 - begin2 << endl;}int main(){TestReturnByRefOrValue();return 0;}
效果演示:

请添加图片描述

通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大

3. 总结:

传引用传参(任何时候都可以用)

  1. 提高效率
  2. 输出型参数(形参的修改,影响的实参)

传引用返回(出了函数作用域对象还在才可以用)

  1. 提高效率
  2. 修改返回对象(末尾彩蛋处有体现)

🌏2.6.引用和指针的区别

1. 在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间:
#includeusing namespace std;int main(){int a = 10;int& ra = a;cout << "&a = " << &a << endl;cout << "&ra = " << &ra << endl;return 0;}

在这里插入图片描述

2. 在底层实现上实际是有空间的,因为引用是按照指针方式来实现的:
#includeusing namespace std;int main(){int a = 0;int* p1 = &a;int& ref = a;++(*p1);++ref;return 0;}

请添加图片描述

引用和指针的不同点:

引用和指针的不同点:

🌟三、末尾彩蛋(带你回溯时空联想之前)

1. C设计顺序表部分接口
#include#include#includestruct SeqList{int a[10];int size;};//读取第i个位置的接口int SLAT(struct SeqList* ps, int i){assert (i <ps->size) ;return ps->a [i];}//修改第i个位置的接口void SLModify(struct SeqList* ps, int i, int x){assert(i < ps->size);ps->a[i] = x;}int main(){return 0;}
2. C++设计顺序表部分接口
#include#include#includeusing namespace std;struct SeqList{int a[10];int size;};int& SLAT(struct SeqList& ps, int i){assert(i < ps.size);return (ps.a[i]);}int main(){struct SeqList s;s.size = 3;SLAT(s, 0) = 10;SLAT(s, 1) = 20;SLAT(s, 2) = 30;cout << SLAT(s, 0) << endl;cout << SLAT(s, 1) << endl;cout << SLAT(s, 2) << endl;return 0;}
效果演示:

请添加图片描述

  1. 读取i位置:减少了拷贝,返回此时位置的别名
  2. 修改i位置:数组中第i个位置的值出了作用域肯定还在,因为结构体在外面不在函数的栈帧里面所以存在,出了作用域不会销毁,得到它的别名后,通过赋值加加等就会修改
  3. 如果不用引用返回的就是它的临时拷贝打印是没有问题的修改却是不可以的因为临时对象具有常性不能修改

来源地址:https://blog.csdn.net/ljq_up/article/details/132102267

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯