C不支持函数重载,C++支持

代码演示

main.c

 #include<stdio.h>

 void Max(int a, int b)
{
printf("%d ", a > b ? a : b) ;
} void Max(double a, double b)
{
printf("%f ", a > b ? a : b);
} int main()
{
Max(,);
Max(1.0, 2.0);
return ;
}

由于C不支持重载,上述代码编译时报错 错误 C2084 函数“void Max(int,int)”已有主体

main.cpp

 #include<stdio.h>

 void Max(int a, int b)
{
printf("%d ", a > b ? a : b) ;
} void Max(double a, double b)
{
printf("%f ", a > b ? a : b);
} int main()
{
Max(,);
Max(1.0, 2.0);
return ;
}

正常编译执行。

代码是一样的,仅仅是改个文件后缀,相当于从C编译器换成C++编译器,差异竟如此之大。代码里面我们定义2个Max函数,Max这个函数名字是给我们程序员看的,并不是编译器看的。编译器看代函数的视角决定了是否支持重,编译器也是以这种视角来区分不同函数的。下面介绍编译器识别函数名字规则。

C/C++名字修饰约定

以下截图在VC6.0下生成

C编译器名字修饰约定

C的名字修饰约定比较简单粗暴,他不考虑返回值以及参数,只看函数名。然后,不管是啥函数名,在编译器眼中统一是_函数名。所以main.c那个代码编译报错原因就很显而易见了,编译器发现两个_Max函数,于是报错 XXX函数已有主体。

C++编译器名字修饰约定

C++名字约定不仅要考虑函数名字,还要考虑参数,返回值。具体见名字约定

名字约定

修饰名(Decoration name) 

“C”或者“C++”函数在内部(编译和链接)通过修饰名识别。

修饰名是编译器在编译函数定义或者原型时生成的字符串。

有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出“C++”重载函数、构造函数、析构函数,又如在汇编代码里调用“C””或“C++”函数等。

修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。

名字修饰约定随调用约定和编译种类(C或C++)的不同而变化

修饰名随编译种类和调用约定的不同而不同,下面分别说明。

C编译时函数名修饰约定规则:

__stdcall

约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。

__cdecl

约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。

__fastcall

约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。

它们均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。

C++编译时函数名修饰约定规则:

__stdcall

①以“?”标识函数名的开始,后跟函数名

②函数名后面以“@@YG”标识参数表的开始,后跟参数表

③参数表以代号表示

X--void ,

D--char,

E--unsigned char,

F--short,

H--int,

I--unsigned int,

J--long,

K--unsigned long,

M--float,

N--double,

_N--bool,

....

PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;

参数表的第一项为该函数的返回值类型

其后依次为参数的数据类型,指针标识在其所指数据类型前

参数表后以“@Z”标识整个名字的结束

如果该函数无参数,则以“Z”标识结束。

其格式为“?functionname@@YA*****@Z”或“?functionname@@YG*XZ”,例如

 __cdecl

规则同上面的_stdcall调用约定,只是参数表的开始标识为“@@YA”。

__fastcall

规则同上面的_stdcall调用约定,只是参数表的开始标识为“@@YI”。

VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用.

CB在输出函数声明时使用4种修饰符号

//__cdecl

cb的默认值,它会在输出函数名前加_,并保留此函数名不变,参数按照从右到左的顺序依次传递给栈,也可以写成_cdecl和cdecl形式。

//__fastcall

她修饰的函数的参数将尽肯呢感地使用寄存器来处理,其函数名前加@,参数按照从左到右的顺序压栈;

//__pascal

它说明的函数名使用Pascal格式的命名约定。这时函数名全部大写。参数按照从左到右的顺序压栈;

//__stdcall

使用标准约定的函数名。函数名不会改变。使用__stdcall修饰时。参数按照由右到左的顺序压栈,也可以是_stdcall;

extern “C”

在C++代码中经常看到在函数之前加上extern "C",代码如下

 #include<stdio.h>

 extern "C" void Max(int a, int b)
{
printf("%d ", a > b ? a : b);
} void Max(double a, double b)
{
printf("%f ", a > b ? a : b);
} int main()
{
Max(, );
Max(1.0, 2.0);
return ;
}

这种做法的本质是让C++编译器不采用C++方式编译函数,而是采用C的方式编译。这样导致编译器看待函数名称是不同的

上面代码是可以编译过的,如果给另一个Max函数也使用extern "C"则无法编译通过,道理也很显然,此时代码按照C变异角度看有2个_Max函数,因此编译不过。

 #include<stdio.h>

 extern "C" void Max(int a, int b)
{
printf("%d ", a > b ? a : b);
} extern "C" void Max(double a, double b)
{
printf("%f ", a > b ? a : b);
} int main()
{
Max(, );
Max(1.0, 2.0);
return ;
}

重载为什么不能只靠返回值的不同而进行重载

函数只有返回值不同不可以构成重载,代码如下,假设以__cdecl方式调用

#include<stdio.h>

//?Max@@YAHHH@Z
void Max(int a, int b)
{
printf("%d ", a > b ? a : b);
} //?Max@@YANHH@Z
void Max(int a, int b)
{
printf("%f ", a > b ? a : b);
} int main()
{
Max(, );
Max(1.0, 2.0);
return ;
}

我们在调用函数时并没有写函数返回值,形如(没有这么写的,这么写是错的)

int Max(1, 2);
double Max(1.0, 2.0);

这样就可以依据返回值区分不同函数调用。但现实情况是函数调用依据函数名和参数,调用时是不清楚返回值的。如果函数名,参数都一样,编译器就懵逼了

C++——重载原理分析的更多相关文章

  1. 消息队列NetMQ 原理分析1-Context和ZObject

    前言 介绍 NetMQ是ZeroMQ的C#移植版本,它是对标准socket接口的扩展.它提供了一种异步消息队列,多消息模式,消息过滤(订阅),对多种传输协议的无缝访问. 当前有2个版本正在维护,版本3 ...

  2. Tomcat源码分析——请求原理分析(下)

    前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在& ...

  3. ZT自老罗的博客 Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析

    Android系统的智能指针(轻量级指针.强指针和弱指针)的实现原理分析 分类: Android 2011-09-23 00:59 31568人阅读 评论(42) 收藏 举报 androidclass ...

  4. java string 细节原理分析(2016.5)

    看到了以前2016.5月学习java写的笔记,这里放在一起. String实现的细节原理分析 一.jdk源码中String 的实现 public final class String implemen ...

  5. 「必知必会」最细致的 ArrayList 原理分析

      从今天开始也正式开 JDK 原理分析的坑了,其实写源码分析的目的不再是像以前一样搞懂原理,更重要的是看看他们编码风格更进一步体会到他们的设计思想.看源码前先自己实现一个再比对也许会有不一样的收获! ...

  6. Guava Cache 原理分析与最佳实践

    前言 目前大部分互联网架构 Cache 已经成为了必可不少的一环.常用的方案有大家熟知的 NoSQL 数据库(Redis.Memcached),也有大量的进程内缓存比如 EhCache .Guava ...

  7. Handler系列之原理分析

    上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...

  8. Java NIO使用及原理分析(1-4)(转)

    转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...

  9. 原子类java.util.concurrent.atomic.*原理分析

    原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...

随机推荐

  1. C#中Invoke的用法(转)

    invoke和begininvoke 区别 一直对invoke和begininvoke的使用和概念比较混乱,这两天看了些资料,对这两个的用法和原理有了些新的认识和理解. 首先说下,invoke和beg ...

  2. POJ 3903 Stock Exchange 最长上升子序列入门题

    题目链接:http://poj.org/problem?id=3903 最长上升子序列入门题. 算法时间复杂度 O(n*logn) . 代码: #include <iostream> #i ...

  3. selenium3+Python3+sublime text3自动化登录

    前言: 对于初学者来说,python自带的IDLE,精简又方便,不过一个好的编辑器能让python编码变得更方便,更加优美些. 不过呢,也可以自己去下载其他更好用的代码编辑器,在这推荐: PyChar ...

  4. k8s 使本地集群支持 LoadBalancer 服务

    k8s 使本地集群支持 LoadBalancer 服务 为了使本地集群支持 LoadBalancer 服务,可以参考以下两种实现方案: keepalived-cloud-provider metalL ...

  5. docker pull 提示错误的username or password

    安装完docker后,使用cli docker pull images 时,提示用户名密码错误 解决方法 使用docker ID 不要使用 Email 登陆. https://github.com/d ...

  6. WXS----数据类型

  7. ROS安装,配置

    ROS最好安装在Ubuntu系统,因为ROS目前在其他的系统中都是试验性的 ! <Learning ROS for Robotics Programming-- second Edition&g ...

  8. jenkins publish .net core application to linux server in docker

    上一个Demo进行了单独的Jenkins远程部署, 本Demo将使用流行的Jenkins+Git+Docker进行持续部署. 准备Linux服务器 和上一篇Demo一样, 在Azure创建一台Cent ...

  9. LeetCode 142. 环形链表 II(Linked List Cycle II)

    142. 环形链表 II 142. Linked List Cycle II 题目描述 给定一个链表,返回链表开始入环的第一个节点.如果链表无环,则返回 null. 为了表示给定链表中的环,我们使用整 ...

  10. SVN增加访问用户

    1.在Linux中进入SVN配置文件目录. 2.authz是设置权限的,只读还是可读可写,passwd是增加访问用户的. vim passwd; vim authz;