C++重载机制


  C++实现函数重载的技术手段是函数符号改名,所以我们可以通过分析编译器的函数符号改名机制来验证C++函数重载规则。

(1)如何查看编译器改名后的函数名

  在VS编译器下,我们可以从符号表中看编译器如何改名的。符号表是在.map文件里,在vs里默认不显示符号表文件。要想显示出来,这样设置:属性—>配置属性—>链接器—–>调试—>生成映射文件—>选择是。

//.cpp
int test() {
	return 0;
}

int main() {
}
// 编译后,在exe文件下打开.map文件可以看到:
 0002:00000710       ?test@@YAHX@Z               00411710 f   test.obj
 0002:00000740       _main                      00411740 f   test.obj
(tips:从这里还可以看到main函数之前前后还做了啥,这里就不展开了)

  从上面可以看到test()被改名成?test@@YAHX@Z(这里要注意,这里是VS下的改名规则,不同平台可能不一样,还有当然对.cpp进行编译,如果对.c进行编译,会生成如下:)

// .c
0002:00000670       _main                      00411670 f   test.obj
0002:000006a0       _test                      004116a0 f   test.obj
// 可以看到C编译的时候,只在函数名前加_,所以不能重载。

  现在来解释**?test@@YAHX@Z**的含义:

  • ? 表示名称开始,后面是函数名
  • @@YA 表示参数表开始,后面紧跟返回值和参数列表。这里的YA是__cdecl,而对于__stdcall是@@YG
  • H表示返回值,X表示函数参数。(H表示int, X表示void,N表示double,M表示float, F表示short等
  • Z表示结束

(2)重载的好处

  • C中没有重载,所以要对不同的函数取不同的名字,可能会造成名称污染

  • 类的构造函数跟类名相同,如果没有重载机制,实例化不同对象比较麻烦

  • 操作符重载,比如string +

(3)重载的决定因素

  从上面改名规则可以看到,编译器根据函数的名称、返回值类型、参数类型、个数来进行重命名。

但是:函数返回值并不是重载的因素, 其原因在于函数返回值可以被忽略。

  对于参数个数不同、类型不同一般很好理解,这里对有个特殊:

  • 常函数const可以作为重载条件,因为常对象只能调用常函数
  • 重载的概念要在同一个作用域内
  • 参数为const* 和 const &形式是可以成为重载因素的,而单单const参数是无法形成重载决定因素的。
// const

int test(int i)         // ?test@@YAHH@Z

int test(const int i)   // ?test@@YAHH@Z

// const*

int test(int* i)         // ?test@@YAHPAH@Z

int test(const int* i)   // ?test@@YAHPBH@Z

int test(int* const i)   // ?test@@YAHQAH@Z

// const&

int test(int& i)        // ?test@@YAHAAH@Z

int test(const int& i)  // ?test@@YAHABH@Z

(4) 重载优先级

  • 第一步:创建候选函数列表,其中包含有与被调函数名称相同的函数与模板函数。

  • 第二步:使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数。

  • 第三步:确定是否有最佳可行的函数。如果有,则使用,重载要求最佳可行函数至少有一个参数的优先级是大于其他函数的,否则会产生二义性。

    • 确定最佳函数,只考虑其特征标,而不考虑返回类型。确定最佳函数,匹配特征标要依次经过以下判断(1)完全匹配(常规函数优于模板;允许无关紧要的转换)

      (2)提升匹配(如char和short自动转换为int)

      (3)标准转换(int转换为char,long转换为double)

      (4)用户自定义的转换(如类声明中定义的转换)

      (5)允许无关紧要的转换,这些转换包括引用,指针与实体之间,数组与指针之间,函数与函数指针之间,const与非const等等。

    int test(float i) {
    	return 0;
    }
    
    int test(double i)return 0;
    }
    
    int main() {
    	test(1);
    }
    
    // error,这里产生二义性
    

(5) extern “C”

  如果用C语言编译器编程生成1.c和目标文件1.o,然后再cpp中调用2.c中的函数,cpp生成2.cpp和2.o,则1.o和2.o链接时会发生错误。虽然1.c和2.cpp可以各自编译成功,但是链接一起的时候需要调用函数符号,而1.c中的函数符号不同,如果要用extern “C”。告诉编译器调用C中函数时,不要用C++的符号命名机制,而是要用C语言命名机制。

#ifdef __cplusplus
extern "C" {
#endif
	int test() {
		return 0;
	}
#ifdef __cplusplus
}
#endif
// 然后test就不会进行改名,在.map文件里是这样的
 0002:000006a0       _test                      004116a0 f   test.obj
参考资料

(1)https://blog.youkuaiyun.com/gogogo_sky/article/details/72807123
(2)https://www.cnblogs.com/invisible2/p/6204492.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值