C++解析(7):函数重载分析
0.目录
1.重载的概念
2.C++中的函数重载
3.函数默认参数遇上函数重载
4.编译器调用重载函数的准则
5.重载与指针
6.C++和C相互调用
7.小结
1.重载的概念
自然语言中的上下文——你知道下面词汇中“洗”字的含义吗?
结论:
- 能和“洗”字搭配的词汇有很多
- “洗”字和不同的词汇搭配有不同的含义
重载的概念:
重载(Overload)——同一个标识符在不同的上下文有不同的意义
如:
重载在自然语言中是随处可见的。那么程序设计中是否也有重载呢?
2.C++中的函数重载
函数重载(Function Overload):
- 用同一个函数名定义不同的函数
- 当函数名和不同的参数搭配时函数的含义不同
函数重载至少满足下面的一个条件:
- 参数个数不同
- 参数类型不同
- 参数顺序不同
下面的两个函数可以构成重载函数吗?
3.函数默认参数遇上函数重载
对比以下两个程序:
#include <stdio.h>
int func(int a, int b, int c = 0)
{
return a * b * c;
}
int func(int a, int b)
{
return a + b;
}
int main()
{
return 0;
}
#include <stdio.h>
int func(int a, int b, int c = 0)
{
return a * b * c;
}
int func(int a, int b)
{
return a + b;
}
int main()
{
int c = func(1, 2);
return 0;
}
第一个程序能编译通过,第二个程序不能编译通过,报错信息如下:
[root@bogon Desktop]# g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:16: error: call of overloaded ‘func(int, int)’ is ambiguous
test.cpp:4: note: candidates are: int func(int, int, int)
test.cpp:9: note: int func(int, int)
大概意思是编译器说我也不知道调用哪个,所以直接报错了。(造成了二义性,因为确实可能连你自己也不知道想要调用哪一个。)
C++引入了太多的特性进入这门语言,然而一些特性之间又是相冲突的。这就是我们遇到的第一个相冲突的地方。在Java中就会发现,默认值是不允许的。
4.编译器调用重载函数的准则
编译器调用重载函数的准则:
- 将所有同名函数作为候选者
- 尝试寻找可行的候选函数
- 精确匹配实参
- 通过默认参数能够匹配实参
- 通过默认类型转换匹配实参
- 匹配失败
- 最终寻找到的候选函数不唯一,则出现二义性,编译失败。
- 无法匹配所有候选者,函数未定义,编译失败。
函数重载的注意事项:
- 重载函数在本质上是相互独立的不同函数
- 重载函数的函数类型不同
- 函数返回值不能作为函数重载的依据
函数重载是由函数名和参数列表决定的!
函数重载的本质就是定义了相互独立不相同的函数。(下面将证明(重头戏!!!))
(如何证明两个add函数不同呢?)
见证奇迹的时刻——运行结果为:
00DF100A
00DF100F
(地址不同,证明函数入口地址不同,反向证明两个add是不同的函数。)
(编译器是怎么看待这两个add函数的呢?)
编译器产生的中间结果:Test.obj
(使用的编译器是vs2010,进入对应的工程文件夹,有一个Debug文件夹,打开后找到Test.obj,这就是编译Test.cpp后产生的中间文件)
(打开vs自带的命令行工具,输入dumpbin)
dumpbin这个命令是VC所提供的。
这里有很多选项,我们只需要关注符号表。
符号表就是编译器在编译的过程当中根据源代码所生成的一张表,这张表里面有程序里面的各个函数名变量名等等。
输入dumpbin /symbols (Test.obj的路径):
仔细观察发现,标记的那一行就是我们第一个有两个参数的add函数被编译过后生成的符号表当中的一项。
仔细观察这里还有一个有三个参数的add函数编译过后产生的符号表里的一项。
这就相当于注释的东西,编译器编译这两个函数之后所得到的标识符就是?add@@YAHHH@Z和?add@@YAHHHH@Z,在我们看来的add是函数名,但是编译器编译过后它就不认为add是函数名了。对比发现,两个标识符不相同,这就意味着编译器在编译这两个函数的时候已经分开对待了。编译器看到的东西和我们看到的不一样,我们所看到的这两个函数的名字是一样的,然而编译器看到的这两个函数的名字是不同的。因为编译器觉得这是两个不同的函数,所以编译出的最终结果也就是可执行程序当中,这两个add入口地址不同也就很容易理解了。
5.重载与指针
下面的函数指针将保存哪个函数的地址?
函数重载遇上函数指针——将重载函数名赋值给函数指针时:
- 根据重载规则挑选与函数指针参数列表一致的候选者
- 严格匹配候选者的函数类型与函数指针的函数类型
观察下面代码:
#include <stdio.h>
#include <string.h>
int func(int x)
{
return x;
}
int func(int a, int b)
{
return a + b;
}
int func(const char* s)
{
return strlen(s);
}
typedef int(*PFUNC)(int a);
int main()
{
int c = 0;
PFUNC p = func;
c = p(1);
printf("c = %d\n", c);
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
c = 1
(将typedef int…处换成typedef void…或者是typedef double…都会报错,证明了是严格匹配。)
注意:
- 函数重载必然发生在同一个作用域中
- 编译器需要用参数列表或函数类型进行函数选择
- 无法直接通过函数名得到重载函数的入口地址
只靠函数名的话因为存在二义性所以无法直接通过函数名得到重载函数的入口地址!此时可以依靠函数指针来获取函数入口地址:
#include <stdio.h>
int add(int a, int b) // int(int, int)
{
return a + b;
}
int add(int a, int b, int c) // int(int, int, int)
{
return a + b + c;
}
int main()
{
printf("%p\n", (int(*)(int, int))add);
printf("%p\n", (int(*)(int, int, int))add);
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
0x4005a4
0x4005b9
6.C++和C相互调用
- 实际工程中C++和C代码相互调用是不可避免的
- C++编译器能够兼容C语言的编译方式
- C++编译器会优先使用C++编译的方式
- extern关键字能强制让C++编译器进行C方式的编译
(C++编译C程序不是直接可以通过吗?确实,直接编译源码可以通过。但是,在工程中,有一些C代码已经不是源码了,已经被编译成了目标文件。相当于我们在C工程里面会用到一些第三方的库,然而这些第三方的库是用C语言编写并编译的,而且这些第三方的库还是要钱的。既然花钱买了,那我们选择了C++后就不用了吗?肯定不可能,所以必须要让C++代码也能够去使用C语言编写的库,那这会不会有问题呢?)
对于以下两段代码:
// add.c
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
// add.h
int add(int a, int b);
命令行执行代码(生成add.o):
[root@bogon Desktop]# gcc -c add.c -o add.o
此时:
// main.cpp
#include <stdio.h>
#include "add.h"
int main()
{
int c = add(1, 2);
printf("c = %d\n", c);
return 0;
}
此程序用C++编译器编译出错:
[root@bogon Desktop]# g++ main.cpp
/tmp/ccwHtgIL.o: In function `main':
main.cpp:(.text+0x13): undefined reference to `add(int, int)'
collect2: ld returned 1 exit status
编译器说add函数未定义。真的没定义吗?
命令行中输入
[root@bogon Desktop]# nm add.o
0000000000000000 T add
发现:符号表中确实有add函数。
那为什么编译说没有呢?因为,这里的add.o是C编译的,C++和C它们的编译方式是不同的。然而C++编译器肯定能兼容C语言的编译方式,但是C++编译器会优先使用C++编译的方式!
使用以下代码:
// main.cpp
#include <stdio.h>
extern "C"
{
#include "add.h"
}
int main()
{
int c = add(1, 2);
printf("c = %d\n", c);
return 0;
}
这样就告诉了C++编译器,所包含的这个头文件是C代码,必须用C的方式来编译它。
运行结果为:
[root@bogon Desktop]# g++ main.cpp add.o
[root@bogon Desktop]# ./a.out
c = 3
问题:如何保证一段C代码只会以C的方式被编译?
extern “C”是C++中的代码,C语言中没有。
所以没有办法保证这段程序既能被C++编译器编译通过,也能被C编译器编译通过。
解决方案:
__cplusplus
是C++编译器内置的标准宏定义__cplusplus
的意义——确保C代码以统一的C方式被编译成目标文件
这个程序就可以被C和C++编译器都编译通过:
// main.c
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "add.h"
#ifdef __cplusplus
}
#endif
int main()
{
int c = add(1, 2);
printf("c = %d\n", c);
return 0;
}
运行结果为:
[root@bogon Desktop]# gcc main.c add.o
[root@bogon Desktop]# ./a.out
c = 3
[root@bogon Desktop]# g++ main.c add.o
[root@bogon Desktop]# ./a.out
c = 3
#ifdef __cplusplus
用来判断是不是C++编译器
注意事项:
C++编译器不能以C的方式编译重载函数
编译方式决定函数名被编译后的目标名
- C++编译方式将函数名和参数列表编译成目标名
- C编译方式只将函数名作为目标名进行编译
对于这个程序:
// test.cpp
int add(int a, int b) // int(int, int)
{
return a + b;
}
int add(int a, int b, int c) // int(int, int, int)
{
return a + b + c;
}
命令行输入:
[root@bogon Desktop]# g++ -c test.cpp -o test.oo
[root@bogon Desktop]# nm test.oo
0000000000000000 T _Z3addii
0000000000000015 T _Z3addiii
U __gxx_personality_v0
大家看到addii和addiii比较一下。ii和iii代表了参数。i就是int。
所以:
// test.cpp
extern "C"
{
int add(int a, int b) // int(int, int)
{
return a + b;
}
int add(int a, int b, int c) // int(int, int, int)
{
return a + b + c;
}
}
命令行输入:
[root@bogon Desktop]# g++ -c test.cpp -o test.oo
test.cpp: In function ‘int add(int, int, int)’:
test.cpp:10: error: declaration of C function ‘int add(int, int, int)’ conflicts with
test.cpp:5: error: previous declaration ‘int add(int, int)’ here
编译器报错!!!
编译报错说这两个函数通过C方式编译过后得到的目标名相冲突!!!
7.小结
- 函数重载是C++中引入的概念
- 函数重载用于模拟自然语言中的词汇搭配
- 函数重载使得C++具有更丰富的语义表达能力
- 函数重载的本质为相互独立的不同函数
- C++中通过函数名和函数参数确定函数调用
- 函数重载是C++对C的一个重要升级
- 函数重载通过函数参数列表区分不同的同名函数
- extern关键字能够实现C和C++的相互调用
- 编译方式决定符号表中的函数名的最终目标名
C++解析(7):函数重载分析的更多相关文章
- 你不知道的JavaScript--Item6 var预解析与函数声明提升(hoist )
1.var 变量预编译 JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对"先声明.后使用"的规则很熟悉, ...
- Jieba分词包(一)——解析主函数cut
1. 解析主函数cut Jieba分词包的主函数在jieba文件夹下的__init__.py中,在这个py文件中有个cut的函数,这个就是控制着整个jieba分词包的主函数. cut函数的定义如 ...
- JavaScipt 源码解析 回调函数
函数是第一类对象,这是javascript中的一个重要的概念,意味着函数可以像对象一样按照第一类管理被使用,所以在javascript中的函数: 能"存储"在变量中,能作为函数的实 ...
- (63)Wangdao.com第十天_预处理、预解析_函数 上下文对象、参数列表对象
预解析.预处理 1. 在全局代码执行之前,js 引擎 就会创建一个栈来存储管理所有的 执行上下文对象 2. 在 全局执行上下文 window 确定以后,进行压栈 3. 在 函数执行上下文对象 确定以后 ...
- 解析main函数的命令行参数
原创文章,转载请正确注明本文原始URL及作者. 介绍 写C/C++程序,我们常常需要把main函数的参数作为选项来传递.在linux中,解析选项有专门的函数可以用. int getopt(int ar ...
- 深入解析array_merge函数的用法 php (转)
array_merge是我们用来合并数组使用最多的函数: 下面就来深入解析array_merge的用法: 第四点是个坑需注意: 1:如果数组中有相同的字符串键名: 则该键名后面的值覆盖前面的值: 如果 ...
- 用函数式编程思维解析anagrams函数
//函数式编程思维分析 这个排列函数 const anagrams = str => { if (str.length <= 2) return str.length === 2 ? [s ...
- 函数与函数式编程(生成器 && 列表解析 && map函数 && filter函数)-(四)
在学习python的过程中,无意中看到了函数式编程.在了解的过程中,明白了函数与函数式的区别,函数式编程的几种方式. 函数定义:函数是逻辑结构化和过程化的一种编程方法. 过程定义:过程就是简单特殊没有 ...
- django url配置-反向解析-视图函数-HttpRequest对象-HttpResponse对象-cookies-session-redis缓存session
""" --视图概述:-- 作用:视图接受WEB请求,并响应WEB请求 本质:视图就是一个python中的函数 响应: 1.网页: 一.重定向 二.错误视图 400,50 ...
随机推荐
- VS2010 不显示 最近使用的项目 解决办法
昨天重装了VS2010,然后开了项目看了下今天早上再打开发现起始页近使用项目列表是空白的,每次打开项目都要去到指定目录去找解决方案才能打开,感觉很麻烦,在网上找了下解决方案,解决步骤下:菜单 —— 运 ...
- vmware因为软件出过一次复制的错误导致不能复制到主机的解决方法
只需要把vmware的虚拟机进程全部结束掉,然后重置(先设置不勾选复制等,然后保存后在勾选上并保存)一次虚拟机隔离设置(需要在关闭虚拟机的情况下设置,否则就是灰色不允许操作),然后再开启虚拟机,就能正 ...
- hdu2061 Treasure the new start, freshmen!(暴力简单题)
Treasure the new start, freshmen! Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/3276 ...
- 当git遇上中文乱码
git有个比较奇怪的问题,当目录或者文件名中出现了中文的时候,在执行git status 的时候,会返回一串unicode码,这段unicode码就读不懂了,必须解决. git status显示uni ...
- 使用calendar日历插件实现动态展示会议信息
效果图如下,标红色为有会议安排,并跳转详细会议信息页面. html页面 <%@ page contentType="text/html;charset=UTF-8"%> ...
- 《Learning scikit-learn Machine Learning in Python》chapter1
前言 由于实验原因,准备入坑 python 机器学习,而 python 机器学习常用的包就是 scikit-learn ,准备先了解一下这个工具.在这里搜了有 scikit-learn 关键字的书,找 ...
- [C++] Solve "No source available for main()" error when debugging on Eclipse
In Mac, the issue image: 1. A existing cmake project on disk 2. import this project into Eclipse. 3 ...
- LeetCode 206. Reverse Linked List(C++)
题目: Reverse a singly linked list. Example: Input: 1->2->3->4->5->NULL Output: 5->4 ...
- Beta周王者荣耀交流协会第六次会议
1.立会照片 成员王超,高远博,冉华,王磊,王玉玲,任思佳,袁玥全部到齐. master:袁玥 2. 时间跨度 2017年11月15日 19:00 — 19:10 ,总计10分钟. 3. 地点 一食堂 ...
- 《Linux内核与分析》第六周
20135130王川东 1.操作系统的三大管理功能包括:进程管理,内存管理,文件系统. 2. Linux内核通过唯一的进程标识PID来区别每个进程.为了管理进程,内核必须对每个进程进行清晰的描述,进程 ...