我的C++奇迹之旅相遇:支持函数重载的原理

这篇具有很好参考价值的文章主要介绍了我的C++奇迹之旅相遇:支持函数重载的原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言


📝前言

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

//参数类型不同
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;
}

//参数个数不同
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}

//参数类型顺序不同
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;
}

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

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

C++通过名字查找、名字修饰、解析和链接这几个步骤,实现了函数重载的功能。名字修饰产生唯一内部名称,是支持重载的关键。但在程序运行时,仍然使用原来的外部函数名称调用,这是函数重载的一个重要特点。

什么是名字修饰:
名字修饰(Name Mangling)是C++编译器为函数、类等名称添加额外信息的过程,目的是为了区分重载重定义等名称。

名字修饰的原理
名称修饰是编译器在编译源代码时为函数、类等名称添加额外信息的过程,生成内部链接名称。该内部链接名称包含原名称以及其他信息,如参数类型、返回类型等。
这样就可以区分函数重载、重定义等情况,生成唯一的内部名称。链接器根据这些内部名称进行链接。但程序在调用时仍然使用原外部未修饰的名称

例如一下C++的函数重载:

int func(int a);
int func(double b); 

编译器可能为它们生成以下内部名称:

_Z4funci // func(int)
_Z4funcd // func(double)

这里_Z4func是原名称,后面的id表示参数类型。因此,即使两个函数的原名相同,但在编译器进行编译处理后,根据参数的类型进行标记,获得了不同的名字标识符。所以,当编译器根据内部名称的不同,就可以将他们区分开来。

当然,更细化的理解,应该是这样的:在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接

实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们可以知道编译和链接他们各自都干了不少事,首先,我们先吧一个项目分为3个文件:Stack.h,Stack.cpp ,Test.cpp

  1. Stack.h
#pragma once
#include<iostream>
using namespace std;

struct Stack
{
};

void StackInit(struct Stack* ps, int n);
  1. Stack.cpp
#include"Stack.h"

void StackInit(struct Stack* ps, int n)
{
	cout << "void StackInit(struct Stack* ps, int n)" << endl;
}
  1. Test.cpp
#include"Stack.h"

int main()
{
	struct Stack st;
	StackInit(&st, 10);
	return 0;
}

代码运行:
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
此时程序正常运行,当我把Stack.cpp里的定义去掉后,如图:
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
再次编译运行时,代码就会报错,这个错误不是编译错误,而是链接错误,编译错误通常是语法错误。
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
再看此图,我们来分析这个为什么是链接错误,可知道当Test.cpp,Stack.cpp,Stack.h这三个文件运行起来是,先进行预处理,预处理****就是把相应的头文件展开,然后宏替换,然后条件编译等等,紧接着Stack.cppStack.h会生成Stack.i文件,Stack.hTest.cpp会生成Test.i文件,也就是.c文件分别与.h文件进行生成.i文件,生成了两个.i文件然后进行编译,编译就检查语法,生成汇编代码,两个.i文件就会生成两个.s文件Stack.sTest.s,接下来就是汇编将汇编代码转成二进制机器码,此时两.s文件将会生成对应的.o文件Stack.oTest.o,这时编译已然结束,那接下来就是将这两个文件链接起来,生成可执行程序xxx.exe

我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言

了解了以上编译的大致过程,接下来,我们把Stack.cpp里的定义还原,我们拿完整的代码来解析。
我们看以下反汇编代码图,首先进去main()主函数时,
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言

可以看到函数有一堆要执行的指令函数地址,第一句指令的地址
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
当我们继续按F11进入Call这个指令时,他根据函数StackInit (0A113C5h)选择括号里的地址0A113C5h的跳转到00A113C5(注:这个地址跟0A113C5h是一样的,只是进制的表示不同),当再次运行时会继续根据函数括号的地址记性跳转
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
从这里看出:有函数的定义,才能生成函数一堆汇编指令,第一句指定的地址,才是函数的地址,先Stack.cpp->Stack.o最后Test.oStack.o链接到一起,合并到一起,才有地址,
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
结论:Test.cpp只有函数声明,把Stack.cpp的定义去掉,可以过,因为语法检查是匹配的,Test.cpp->Test.o过程中没有函数的地址,链接时,就要用StacklInit这个名字去Stack.o找他的地址

链接时:
1、直接用函数名字去查找,是否支持重载,不支持C语言
2、直接用修饰后的函数名字去查找,就可以支持重载C++

C++如此例子运行
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言

这就回到了我们最初的这个概念:这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题
注意:以上情况是分多个文件才会发生这样的情况,如果你不分这么一个文件,全部放在一个文件中,就不会有这个情况,但是实际项目通常是由多个头文件和多个源文件构成。

这是大致流程图:
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言

🌉不同编译器不同函数名修饰规则

那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。

int Add(int a, int b) 
{
	return a + b;
}
void func(int a, double b, int* p) 
{ 

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

由于Windowsvs的修饰规则过于复杂,而Linuxg++的修饰规则简单易懂,下面我们使用了g++演示了这个修饰后的名字。

  • 通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。
  • 采用C语言编译器编译后结果
    我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
    结论:linux下,采用gcc编译完成后,函数名字的修饰没有发生改变
  • 采用C++编译器编译后结果
    我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言
    结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。

🌠Windows下名字修饰规则

函数签名 修饰后名称
int func(int) ?func@@YAHH@Z
float func(float) ?func@@YAMM@z
int C :: func(int) ?func@c@@AAEHH@z
int C::C2::func(int) ?func@C2@c@@AAEHH@z
int N::func(int) ?func@N@@YAHH@z
int N::C::func(int) ?func@c@N@@AAEHH@z

我们以int N::C:func(int)这个函数签名来猜测Visual C++的名称修饰规则(当然,你只须大概了解这个修饰规则就可以了)。修饰后名字由“?”开头,接着是函数名由“@”符号结尾的函数名;后面跟着由“@”结尾的类名“C”和名称空间“N",再一个“@”表示函数的名称空间结束:第一个“A”表示函数调用类型为“..cdecl”,接着是函数的参数类型及返回值,由“@”结束,最后由“Z”结尾。可以看到函数名、参数的类型和名称空间都被加入了修饰后名称,这样编译器和链接器就可以区别同名但不同参数类型或名字空间的函数,而不会导致link 的时候函数多重定义。

对比Linux会发现,windowsvs编译器对函数名字修饰规则相对复杂难懂,但道理都是类似的,我们就不做细致的研究了。
扩展学习:C/C++函数调用约定和名字修饰规则–有兴趣好奇的同学可以看看,里面
有对vs下函数名修饰规则讲解】


🚩总结

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

感谢你的收看,如果文章有错误,可以指出,我不胜感激,让我们一起学习交流,如果文章可以给你一个小小帮助,可以给博主点一个小小的赞😘
我的C++奇迹之旅相遇:支持函数重载的原理,【C++的奇迹之旅】,c++,开发语言,函数重载的原理,linux,c语言文章来源地址https://www.toymoban.com/news/detail-852725.html

到了这里,关于我的C++奇迹之旅相遇:支持函数重载的原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 开启C++之旅(上):探索命名空间与函数特性(缺省参数和函数重载)

    之前浅显的讲解了数据结构的部分内容:数据结构专栏 那么今天我们迎来了新的起点:C++的探索之旅 在c中: 严格的编译器会直接 报错 : rand我们都知道是产生随机数的函数,现在我定义了一个全局变量rand,显然是有 命名冲突 所以c++就提供了解决方案 想必学过c的大家第一

    2024年02月01日
    浏览(39)
  • 【C++学习】C++入门 | 缺省参数 | 函数重载 | 探究C++为什么能够支持函数重载

    上一篇文章我介绍了C++该怎么学,什么是命名空间,以及C++的输入输出, 这里是传送门:http://t.csdn.cn/Oi6V8 这篇文章我们继续来学习C++的基础知识。 目录 写在前面: 1. 缺省参数 2. 函数重载 3. C++是如何支持函数重载的 写在最后: 在学习C语言的时候,如果一个函数存在参数

    2024年02月13日
    浏览(34)
  • C++ 学习 ::【基础篇:05】:C++ 函数重载认识及使用、简单介绍:C++ 支持函数重载的原因

    本系列 C++ 相关文章 仅为笔者学习笔记记录,用自己的理解记录学习!C++ 学习系列将分为三个阶段: 基础篇、STL 篇、高阶数据结构与算法篇 ,相关重点内容如下: 基础篇 : 类与对象 (涉及C++的三大特性等); STL 篇 : 学习使用 C++ 提供的 STL 相关库 ; 高阶数据结构与算

    2024年02月06日
    浏览(43)
  • C++奇迹之旅:隐含的this指针

    在 C++ 编程中,有一个特殊的指针叫做 this 指针,它在类的成员函数中扮演着重要的角色。本文将从一个简单的例子开始,逐步探讨 this 指针的概念、作用和用法。 我们先来定义一个日期类 Date 对于上述类,有这样的一个问题: Date 类中有 Init 与 Print 两个成员函数,函数体中

    2024年04月28日
    浏览(26)
  • 【C++】函数重载及实现原理

           🔥🔥 欢迎来到小林的博客!!       🛰️博客主页:✈️林 子       🛰️博客专栏:✈️ 小林C++之路       🛰️社区 :✈️ 进步学堂       🛰️欢迎关注:👍点赞🙌收藏✍️留言 函数重载就是允许出现多个同名函数,但是

    2024年02月03日
    浏览(25)
  • C++奇迹之旅:探索类对象模型内存的存储猜想

    上回我们学习了类的定义,初步了解了什么是类?类的定义,以及类的三个访问限定符: public , private , protected ,本小节将讲解类的实例化,类对象模型的猜想存储,及三种简单类的计算。 在 C++ 中,类的实例化是指创建一个类的对象。当我们定义了一个类之后,就可以根据

    2024年04月12日
    浏览(28)
  • C++奇迹之旅:从0开始实现日期时间计算器

    通过前面学完了 C++ 的默认成员函数,实践出真知,本小节我们将一起来实现一个简单上手的日期时间计算器,阿森和你一起一步一步的操作实现! 完整代码在文章末尾哦 为了代码的维护性和可观型,我们在设置三个文件头文件 Date.h ,源文件 Date.cpp , Test.cpp 我们先把头文

    2024年04月28日
    浏览(26)
  • [C++] C++入门第一篇 -- 命名空间,输入输出,缺省函数,函数重载底层原理

      目录 1、  2、命名空间 2.1 命名空间的定义 2.2 命名空间的使用方式 2.2.1 加命名空间名称及作用域限定符 2.2.2 使用using将命名空间中某个成员引入 2.2.3 使用using namespace 命名空间名称引入 3、C++输入与输出 4、缺省参数 4.1 缺省参数的概念 4.2 缺省参数分类 4.2.1 全缺省参

    2024年02月15日
    浏览(40)
  • C++ | 探究函数重载的原理:函数名修饰【基于Windows + Linux双系统】

    网上呢一直流传着这么两个说法,我国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“ 谁也赢不了! ”,后者是“ 谁也赢不了! 相信了解的读者就可以看出来这两句话的不同含义了,虽然都叫做【谁也赢不了】,但是呢因为这个 谁 所

    2024年02月21日
    浏览(27)
  • 【我的OpenGL学习进阶之旅】Assimp库支持哪些3D模型格式?

    在通过LearnOpenGL学习 OpenGL 知识的时候,有介绍如何通过Assimp来加载3D模型,并了解了Mesh网格的概念。 Assimp Mesh网格 3D模型 在 https://learnopengl-cn.github.io/03%20Model%20Loading/01%20Assimp/ 中有介绍使用 Assimp 库来加载 3D 模型 .obj 格式。 一个非常流行的模型导入库是Assimp,它是 Open Asse

    2024年02月05日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包