深度解析C++中函数重载与引用

news2024/9/22 21:29:41

🌈个人主页:Yui_
🌈Linux专栏:Linux
🌈C语言笔记专栏:C语言笔记
🌈数据结构专栏:数据结构
🌈C++专栏:C++

文章目录

  • 1. 函数重载
    • 1.1 函数重载概念
    • 1.2 C++支持函数重载的原理--名字修饰(name Mangling)
  • 2. 引用
    • 2.1 引用的概念
    • 2.2 引用的特性
    • 2.3 常引用
    • 2.4 使用场景
    • 2.5 传值、传引用效率比较
    • 2.6 引用和指针的区别

1. 函数重载

在中文语境中有些词语它就是一词多义的,人们通过上下文来判断词语的意思,即该词被重载了。
就比如说:以前有个笑话,我们国家有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是足球。前者"谁也赢不了"后者"谁也赢不了"。

1.1 函数重载概念

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

#include <iostream>
using namespace std;
//1.参数类型不同
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;
}
//2.参数个数不同
void test()
{
	cout<<"test()"<<endl;
}
void test(int a)
{
	cout<<"test(int a)"<<endl;
}
//3.参数类型顺序不同
void test2(int a,char b)
{
	cout<<"test2(int a,char b)"<<endl;
}
void test2(char b,int a)
{
	cout<<"test2(char b,int a)"<<endl;
}
int main()
{
	add(1,2);
	add(1.1,2.2);
	
	test();
	test(100);
	
	test2(10,'a');
	test2('a',10);
	
	return 0;
}

1.2 C++支持函数重载的原理–名字修饰(name Mangling)

为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
编译链接
编译链接

  1. 实际项目中通常是由多个头文件和多个源文件构成的,而通过C语言阶段学习的编译链接,我们可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?
  2. 所以链接阶段就是专门处理这种问题的,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。
  3. 那么链接时,面对Add函数,链接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。
  4. 由于windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使用了g++演示这个修饰后的名字。
  5. 通过下面我们可以看到gcc的函数修饰后名字不变。而g++的函数修饰后变成【__Z+函数长度+函数名+类型首字母】
  • 采用C语言编译器编译后的结果:
//file.c中的代码
#include <stdio.h>
int Add(int a,int b)
{
    return a+b;
}
void test(int a,double b,int*p)
{
   //... 
}
int main()
{
    Add(10,20);
    test(1,2,0);
    return 0;
}

演示

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

  • 采用C++编译器编译后结果:
    采用C++编译器编译后结果:

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

  • windows下名字修饰规则
    windows下名字修饰规则

总结:对比Linux会发现,Windows下vs编译器对函数名字修饰规则相对复杂难懂,但道理类似。
6. 通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分地,只要参数不同,修饰出来地名字就不一样,那么就可以支持重载了。
7. 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。

2. 引用

2.1 引用的概念

引用不是新定义一个变量,而是给已存在变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
就像外号一样,尽管名字不同但人都是一个人。
语法:

类型& 引用变量名(对象名) = 引用实体
#include <cstdio>

void test()
{
	int a = 10;
	int& ra = a;//定义引用类型
	printf("%p\n", &a);
	printf("%p\n", &ra);
}

int main()
{
	test();
	return 0;
}
//打印结果:
/*
009EFDE0
009EFDE0
*/

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

2.2 引用的特性

  1. 引用在定义时必须初始化。
  2. 一个变量可以有多个引用。
  3. 引用一旦引用一个实体,就不能再引用其他实体。
void test()
{
	int a = 10;
	//int& ra;//引用在定义时必须初始化,否则报错
	int& ra = a;
	int& rra = a;
	printf("%p\n", &a);
	printf("%p\n", &ra);
	printf("%p\n", &rra);
}
//打印结果:
/*
00AFF9A0
00AFF9A0
00AFF9A0
*/

2.3 常引用

void testconstref()
{
	const int a = 10;
	//int& ra = a;//该语句编译时会出错,a为常量
	const int& ra = a;
	//int& b = 10;//该语句编译时会出错,10为常量
	const int& b = 10;
	double d = 3.14;
	//int& rd = d;该语句编译时会出错,类型不同
	const int& rd = d;
}

2.4 使用场景

  1. 做参数
void Swap(int& left,int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
  1. 做返回值
int& Count()
{
	static int n = 0;
	n++;
	//...
	return n;
}

观察下来代码,会输出什么结果

#include <iostream>
using namespace std;
int& add(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int& ret = add(2, 3);
	add(4, 5);
	cout << "add(2,3) is :" << ret << endl;
	return 0;
}
//打印结果
/*
add(2,3) is :9
*/

函数运行时,系统需要给函数开辟独立的栈空间,用来保存函数的形参,局部变量以及一些寄存器信息等。
函数运行结束后,该函数对应的栈空间就被系统回收了。
空间被回收指该栈空间暂时不能使用,但是内存还在,比如:上课申请教室,上完课之后教师归还给学校,但是教室本身还在,不能说归还后,教室就消失了。

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

2.5 传值、传引用效率比较

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

演示:效率对比,值和引用作为参数类型的性能对比

#include <iostream>
#include <ctime>
using namespace std;
struct  A
{
	int a[10000];
};
void TestFunc(A a)
{

}
void TestFunc2(A& a)
{

}
int main()
{
	A a;
	//以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
	{
		TestFunc(a);
	}
	size_t end1 = clock();

	//以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
	{
		TestFunc2(a);
	}
	size_t end2 = clock();
	//分别计算两个函数运行结束后的时间
	cout << "Testfunc(A) time:" << end1 - begin1 << endl;
	cout << "Testfunc2(A&) time:" << end2 - begin2 << endl;
	return 0;

}
//打印结果        
/*
Testfunc(A) time:12
Testfunc2(A&) time:0
(单位毫秒)
*/

演示:值和引用的作为返回类型的性能对比

#include <iostream>
#include <ctime>
using namespace std;
struct  A
{
	int a[10000];
};
struct A a;
//值返回
A TestFunc()
{
	return a;
}
//引用返回
A& TestFunc2()
{
	return a;
}
int main()
{
	//以值作为函数的返回类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
	{
		TestFunc();
	}
	size_t end1 = clock();

	//以引用作为函数的返回类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
	{
		TestFunc2();
	}
	size_t end2 = clock();
	//分别计算两个函数运行结束后的时间
	cout << "Testfunc(A) time:" << end1 - begin1 << endl;
	cout << "Testfunc2(A&) time:" << end2 - begin2 << endl;
	return 0;
}
//打印结果
/*
Testfunc(A) time:25
Testfunc2(A&) time:1
*/

结论:通过上述的代码可以清楚的发现,传值和传引用在作为传参以及返回值类型上效率相差很大。

2.6 引用和指针的区别

在语法层面上呢,引用就是一个别名,没有独立空间,和其被引用体共用一块空间。

#include <iostream>
using namespace std;
int main()
{
	int val = 100;
	int& rval = val;
	cout<<"&val = "<<&val<<endl;
	cout<<"&rval = "<<&rval<<endl;
	return 0;
}
//打印结果
/*
&val = 003AFB98
&rval = 003AFB98
*/

但是呢,在底层方面实际是由空间的,因为引用是按照指针方式来实现的。

#include <iostream>
using namespace std;
int main()
{
	int a = 10;
	int& ra = a;
	ra = 20;
	int* pa = &a;
	*pa = 20
	return 0;
}

反汇编:
反汇编

可以看到操作是类似的。
引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针就没有要求。
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以再任何时候指向任何一个同类型实体。
  4. 没有NULL引用,但是又NULL指针
  5. 再sizeof中含义不同:引用结果为引用类型的大小,但是指针始终是地址空间所占字节数个(根据所在平台确定,如32位平台占4个字节)
  6. 引用自加即引用的实体加1,指针自加即指针向后偏移一个类型的大小。
  7. 由多级指针,但是没有多级引用。
  8. 访问实体方式不同,指针需要显示解引用,引用编译器自己处理。
  9. 引用比指针使用起来相对安全。

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

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

相关文章

前端Flex布局常见的几个问题

1. 如何设置Flex布局的排列方向&#xff1f; 要设置Flex布局的排列方向&#xff0c;你可以使用CSS的flex-direction属性。该属性有四个值可以选择&#xff1a;row&#xff08;默认值&#xff0c;水平排列&#xff09;、row-reverse&#xff08;反向水平排列&#xff09;、colu…

零信任赋予安全牙齿,AI促使它更锋利

距离上次写关于安全的文字已经过去了很久很久&#xff0c;久到上次看到的AI还停留在TTS、ASR等最初的语音交互搜索类似的各种智能音箱以及通过关键字匹配的基于知识库的聊天的机器人。之后的几年各种视觉识别遍地开花&#xff0c;AI四小龙在人脸识别上成熟应用&#xff0c;再然…

Java:数字验证

Java中的有效数字验证&#xff1a;从混乱到清晰 引言问题背景初始尝试&#xff1a;一段令人困惑的代码解决方案&#xff1a;简化与重构 结语 引言 在计算机科学领域&#xff0c;确保输入数据的有效性至关重要。 尤其在处理用户提交的数据或解析文本文件时&#xff0c;确认数据…

第二证券:大洗牌!头部券商营收、净利集体下滑

前十券商营收团体下滑&#xff0c;银河证券跌幅最小 新股IPO数量锐减129家至44家&#xff0c;国内证券市场股票基金交易量日均规划 同比下降 6.83%……关于证券公司而言&#xff0c;本年上半年可谓多重要素叠加冲击&#xff0c;成果下滑难以避免。于大多数证券公司而言&#x…

金融行业选择数据安全交换系统三定律,你遵从了没?

金融行业对我国的重要性不言而喻&#xff0c;它作为国民经济的血脉&#xff0c;是国家核心竞争力的重要组成部分。金融行业是数据密集型行业&#xff0c;数据是金融行业的重要资产。数据在金融行业内部及内外部流动时&#xff0c;其价值才得以发挥&#xff0c;金融行业的业务以…

在麒麟系统安装php7.4中遇到的问题

一、安装过程 下载相关php安装包&#xff0c;进行解压&#xff0c;进入安装包&#xff0c;进行编译安装&#xff0c;编译 ./configure --prefix/soft/php74 --with-config-file-path/soft/php74/etc --with-libdir/usr/lib64 --with-freetype/usr/include/freetype2/free…

Zynq7000系列FPGA中的DDRC纠错码(ECC)

仅在半总线宽度&#xff08;16位&#xff09;数据宽度配置中提供可选的ECC支持。这种配置下&#xff0c;外部DRAM DDR设备需要26位&#xff0c;其中16位用于数据&#xff0c;10位用于ECC。每个数据字节使用独立的5位ECC字段&#xff0c;这种模式提供了单错误纠正和双错误检测的…

【C++】vector迭代器失效问题

本文是对vector迭代器失效问题的分析&#xff0c;需要对vector有一定了解&#xff0c;若还不了解的可以看这篇文章进行学习&#xff1a;【C】容器vector常用接口详解-CSDN博客 目录 一.什么是迭代器失效&#xff1f; 二.迭代器失效的典型案例 1.引起底层空间改变 2.指定位置…

Docker 在 Windows 上的使用指南

Docker 在 Windows 上的使用指南 Docker 是一种强大的容器化平台&#xff0c;广泛应用于开发和生产环境。本文将介绍如何在 Windows 系统上使用 Docker&#xff0c;包括容器的启动、常见问题的排查&#xff0c;以及网络问题的解决方法。 1. Docker 安装与配置 在开始使用 Do…

大模型书单指南:如何快速找到最适合你的那一本书?别找了就是这本!

大模型的书这么多&#xff0c;该怎么选呢&#xff1f; 本期书单就来教大家怎么快速地从众多大模型书中选到你想要的那一本&#xff01; 大模型基础 大模型入门不可错过的一本书&#xff0c;就是这本大模型界的经典畅销书**《大规模语言模型》**&#xff01;系统性强&#xf…

如何从 Mac 上清空的垃圾箱中恢复误删除的文件

在 Mac 上删除的文件将被移至垃圾箱并保留 30 天&#xff0c;然后才会被永久删除。但是&#xff0c;许多 Mac 用户可能会意外清空垃圾箱而没有意识到其中包含了重要文件。本指南包含从清空的垃圾箱中恢复 Mac 上已删除文件的所有有效方法。 当您意识到自己意外清空了 Mac 上的…

C语言基础(三十一)

1、线性搜索&#xff1a; #include "date.h" #include <stdio.h> #include <stdlib.h> #include <time.h> // 希尔排序 void shellSort(int arr[], int n) { for (int gap n / 2; gap > 0; gap / 2) { for (int i gap; i < n; i…

vue学习记录十四:路由(router)学习(五):Vue Router基本原理实现第二种方法

vue学习记录十四&#xff1a;路由&#xff08;router&#xff09;学习&#xff08;四&#xff09;&#xff1a;Vue Router基本原理实现二 一、vueRouter目录结构二、模拟vueRouter目录结构三、index.js文件实现四、install.js文件实现五、创建组件六、解析路由规则七、match实现…

浅谈人工智能之基于anaconda的AutoGen Studio环境搭建

浅谈人工智能之基于anaconda的AutoGen Studio环境搭建 AutoGen Studio 是一个基于 AutoGen 框架的图形用户界面&#xff08;GUI&#xff09;工具。它使开发人员能够更轻松地创建和管理多智能体应用&#xff0c;而无需编写代码。AutoGen Studio 提供了拖放式界面和各种预构建模…

绿色革命:‘众店‘如何重塑零售业并引领消费新潮流

在数字化浪潮的推动下&#xff0c;传统零售业正面临深刻的变革。在这一过程中&#xff0c;绿色积分系统作为一种创新的消费模式&#xff0c;逐渐成为市场的新宠。 一、"众店"平台的迅猛发展 "众店"平台仅用两年时间就实现了跨越式发展&#xff0c;交易额突…

JS基础【双重for循环的实现与应用、打印九九乘法表】

&#x1f680; 个人简介&#xff1a;某大型国企资深软件开发工程师&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主、华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码喽的自我修养&#x1f970; &#…

springboot+MySQL流浪猫狗疾病预约救治系统-计算机毕设 附源码 13676

springboot流浪猫狗疾病预约救治系统 摘 要 现如今在中国&#xff0c;随着人民生活质量的逐渐提高&#xff0c;以及人民群众消费能力的日渐增长&#xff0c;各种各样的家养小动物&#xff0c;已经逐渐成为人类越来越亲密的生活伴侣。并且&#xff0c;现如今社会竞争及其激烈&am…

钢铁百科:A633D交货状态、A633D对应牌号、A633D力学性能解析

A633D钢板是一种美标低合金高强度结构钢板&#xff0c;也被称为A633GrD钢板&#xff0c;其执行标准为ASTM/A633M。这种钢板以其高屈服强度、良好的韧性、抗腐蚀性和耐疲劳性而著称&#xff0c;广泛应用于建筑、桥梁、船舶、汽车、铁路、重型机械及矿山机械等多个领域。 一、A6…

【CSS】如何写渐变色文字并且有打光效果

效果如上&#xff0c;其实核心除了渐变色文字的设置 background: linear-gradient(270deg, #d2a742 94%, #f6e2a7 25%, #d5ab4a 48%, #f6e2a7 82%, #d1a641 4%);color: #e8bb2c;background-clip: text;color: transparent;还有就是打光效果&#xff0c;原理其实就是两块遮罩&am…

微信小程序知识点(一)

1.条件判断&#xff1a; wx:if&#xff0c;wx:elif&#xff0c;wx:else 和Hidden的区别 wx:if等是动态实现组件的&#xff0c;符合条件&#xff0c;页面上就新增一个组件&#xff0c;不符合&#xff0c;就会在也页面上加载&#xff0c;而Hidden只是控制页面的组件的显示与否&…