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.编译器调用重载函数的准则

编译器调用重载函数的准则:

  • 将所有同名函数作为候选者
  • 尝试寻找可行的候选函数
    1. 精确匹配实参
    2. 通过默认参数能够匹配实参
    3. 通过默认类型转换匹配实参
  • 匹配失败
    1. 最终寻找到的候选函数不唯一,则出现二义性,编译失败。
    2. 无法匹配所有候选者,函数未定义,编译失败。

函数重载的注意事项:

  • 重载函数在本质上是相互独立的不同函数
  • 重载函数的函数类型不同
  • 函数返回值不能作为函数重载的依据

函数重载是由函数名和参数列表决定的!

函数重载的本质就是定义了相互独立不相同的函数。(下面将证明(重头戏!!!))





(如何证明两个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.重载与指针

下面的函数指针将保存哪个函数的地址?

函数重载遇上函数指针——将重载函数名赋值给函数指针时:

  1. 根据重载规则挑选与函数指针参数列表一致的候选者
  2. 严格匹配候选者的函数类型与函数指针的函数类型

观察下面代码:

#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的方式编译重载函数

  • 编译方式决定函数名被编译后的目标名

    1. C++编译方式将函数名和参数列表编译成目标名
    2. 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):函数重载分析的更多相关文章

  1. 你不知道的JavaScript--Item6 var预解析与函数声明提升(hoist )

    1.var 变量预编译 JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对"先声明.后使用"的规则很熟悉, ...

  2. Jieba分词包(一)——解析主函数cut

    1. 解析主函数cut Jieba分词包的主函数在jieba文件夹下的__init__.py中,在这个py文件中有个cut的函数,这个就是控制着整个jieba分词包的主函数.    cut函数的定义如 ...

  3. JavaScipt 源码解析 回调函数

    函数是第一类对象,这是javascript中的一个重要的概念,意味着函数可以像对象一样按照第一类管理被使用,所以在javascript中的函数: 能"存储"在变量中,能作为函数的实 ...

  4. (63)Wangdao.com第十天_预处理、预解析_函数 上下文对象、参数列表对象

    预解析.预处理 1. 在全局代码执行之前,js 引擎 就会创建一个栈来存储管理所有的 执行上下文对象 2. 在 全局执行上下文 window 确定以后,进行压栈 3. 在 函数执行上下文对象 确定以后 ...

  5. 解析main函数的命令行参数

    原创文章,转载请正确注明本文原始URL及作者. 介绍 写C/C++程序,我们常常需要把main函数的参数作为选项来传递.在linux中,解析选项有专门的函数可以用. int getopt(int ar ...

  6. 深入解析array_merge函数的用法 php (转)

    array_merge是我们用来合并数组使用最多的函数: 下面就来深入解析array_merge的用法: 第四点是个坑需注意: 1:如果数组中有相同的字符串键名: 则该键名后面的值覆盖前面的值: 如果 ...

  7. 用函数式编程思维解析anagrams函数

    //函数式编程思维分析 这个排列函数 const anagrams = str => { if (str.length <= 2) return str.length === 2 ? [s ...

  8. 函数与函数式编程(生成器 && 列表解析 && map函数 && filter函数)-(四)

    在学习python的过程中,无意中看到了函数式编程.在了解的过程中,明白了函数与函数式的区别,函数式编程的几种方式. 函数定义:函数是逻辑结构化和过程化的一种编程方法. 过程定义:过程就是简单特殊没有 ...

  9. django url配置-反向解析-视图函数-HttpRequest对象-HttpResponse对象-cookies-session-redis缓存session

    """ --视图概述:-- 作用:视图接受WEB请求,并响应WEB请求 本质:视图就是一个python中的函数 响应: 1.网页: 一.重定向 二.错误视图 400,50 ...

随机推荐

  1. 【洛谷P2016】战略游戏

    题面 题解 树形\(dp\)(最大独立集) 设\(f_{i,0/1}\)表示\(dp\)到第\(i\)个点,在这个点放了(没放)士兵的最小花费 直接转移即可. 代码 #include<cstdi ...

  2. JS基础,相亲,逻辑训练

    简单逻辑 <script> var a = prompt("有房么?"); // if(a == "有") { alert("结婚吧&qu ...

  3. selenium 结合 docker 构建分布式测试环境 (初学者视角)

    前言:随着自动化测试越学越深,深深觉得有太多的东西需要总结. 1.记录下学习中遇到的坑,当做学习笔记.2.有前人路过看到文章中比较落后的做法,请务必一定要指教.(因为是初学者视角,很多东西只是走通而已 ...

  4. Saving James Bond - Easy Version (MOOC)

    06-图2 Saving James Bond - Easy Version (25 分) This time let us consider the situation in the movie & ...

  5. Elasticsearch.Net 异常:[match] query doesn't support multiple fields, found [field] and [query]

    用Elasticsearch.Net检索数据,报异常: )); ElasticLowLevelClient client = new ElasticLowLevelClient(settings); ...

  6. 数据库mysql的常规操作

    1. 什么是数据库? 数据库(Database)是按照数据结构来组织.存储和管理数据的建立在计算机存储设备上的仓库. 简单来说是本身可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据进 ...

  7. How to pass an Amazon account review

    Have you ever sold products on Amazon? How about sold so much within the first week that amazon deci ...

  8. 使用 MPI for Python 并行化遗传算法

    前言 本文中作者使用MPI的Python接口mpi4py来将自己的遗传算法框架GAFT进行多进程并行加速.并对加速效果进行了简单测试. 项目链接: GitHub: https://github.com ...

  9. [linux] reboot和shutdown-r的区别

    google看看: 先搜英文的资料 http://askubuntu.com/questions/441969/what-is-the-difference-between-reboot-and-sh ...

  10. 复利计算器4.0之再遇JUnit

    复利计算器4.0之再遇JUnit 前言    虽然之前的复利计算器版本已经尝试过使用JUnit单元测试,但由于没有系统性地学习过JUnit的使用,用得并不好,主要问题表现在测试的场景太少,并没有达到测 ...