1 初识重载
函数重载指的是用同一个函数名定义不同的函数,当函数名和不同的参数搭配时函数的含义不同。
编程实验:函数重载初探
// 6-1.c
#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);
}
int main(int argc, char *argv[])
{
printf("%d\n", func(3));
printf("%d\n", func(3, 4));
printf("%d\n", func("Hello"));
return 0;
}
编译运行:
$ g++ 6-1.c -o 6-1
$ ./6-1
3
7
5
可以看出函数名和不同的参数搭配时函数的含义不同,调用的函数不同。实现同一函数名定义不同函数。
1.1 重载条件
函数重载至少满足下面的一个条件:
- 参数个数不同
- 参数类型不同
- 参数顺序不同
下面两个函数的参数顺序不同,所以是重载函数。
1.2 避免默认参数碰到函数重载
当默认参数碰到函数重载会发生什么呢?我们通过实验来说明:
直接看代码,有两个 func() 函数,第一个 func() 函数有三个 int 型参数,第三个参有默认值。第二个 func() 函数有两个 int 型参数。
// 6-2.c
#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;
}
编译结果如下:
$ g++ 6-2.c -o 6-2
6-2.c: In function ‘int main()’:
6-2.c:13:22: error: call of overloaded ‘func(int, int)’ is ambiguous
int c = func(1, 2);
^
6-2.c:3:5: note: candidate: int func(int, int, int)
int func(int a, int b, int c = 0)
^~~~
6-2.c:7:5: note: candidate: int func(int, int)
int func(int a, int b)
^~~~
编译器报错,说 func() 函数调用不明确,两个函数都满足调用条件,编译器不知道调用谁,直接报错。
1.3 编译器调用重载函数的准则
- 将所有的同名函数作为候选者
- 尝试寻找可行的候选函数
- 精确匹配实参
- 通过默认参数能够匹配实参
- 通过默认类型转换匹配实参
- 匹配失败
- 最终寻找到的候选函数不唯一,有二义性
- 无法匹配候选者
2 函数重载的本质
函数重载是函数名和参数列表决定的:
- 重载函数在本质上是相互独立的不同函数,其函数类型不同
- 函数返回值不能作为函数重载的依据
// 6-3.c
#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;
}
上面的代码将两个重载函数的地址打印出来。
$ g++ 6-3.c -o 6-3
$ ./6-3
0x560c03a8c64a
0x560c03a8c65e
可以看出两个重载函数的地址完全不同,说明这是两个独立的函数。
3 重载与指针
将重载函数名赋值给函数指针时:
- 严格匹配候选者的函数类型与函数指针的函数类型,也就是数需要匹配参数个数,参数类型,参数顺序和返回值类型
直接看代码,下面的函数指针将保存哪个函数的地址呢?
// 6-4.c
#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 argc, char* argv[])
{
int c = 0;
PFUNC p = func;
c = p(1);
printf("c = %d\n", c);
return 0;
}
定义函数指针 PFUNC,参数类型 int,返回值为 int,则将 PFUNC 指向 函数 func(),将保存那个函数的地址呢?
编译运行:
$ g++ 6-4.c -o 6-4
$ ./6-4
c = 1
可以看出函数指针保存的是函数 int func(int x); 的地址。
如果将代码 6-4.c 的第 16 行改为 typedef void(*PFUNC)(int a); 或者 typedef double(*PFUNC)(int a); 重新编译
编译器报错,没有匹配的函数与函数指针相对应。
typedef void(*PFUNC)(int a); 的函数类型为 void(int, int)
typedef double(*PFUNC)(int a); 的函数类型为 double(int, int)
这两个函数类型与所有的 func() 函数都不匹配。
注意:
- 函数重载必然发生在同一作用域中
- 编译器需要用参数列表或函数类型进行函数选择
- 无法直接通过函数名得到重载函数的入口地址
4 C++ 和 C 相互调用
- C++ 编译器能够兼容 C 语言的编译方式
- C++ 编译器优先使用 C++ 编译的方式
- extern 关键字能强制让 C++ 编译器进行 C 方式的编译
extern "C"
{
// do C-style compilation here
}
编程实验:C++调用 C 函数
// add.c
#include "add.h"
int add(int a, int b)
{
return a + b;
}
// add.h
int add(int a, int b);
首先将上面的代码用 gcc 编译成汇编器处理之后,链接器处理之前的 .o 文件,生成 add.o 文件。
$ 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;
}
在 main.cpp 中调用 add.o 中的 add 函数,编译如下:
$ g++ main.cpp add.o -o main
/tmp/cccdww4k.o:在函数‘main’中:
main.cpp:(.text+0x13):对‘add(int, int)’未定义的引用
collect2: error: ld returned 1 exit status
编译器报错,说 add 函数没定义,这是因为 add.o 是以 C 语言的规则编译的,以 C++ 规则找不到编译后的文件名。所以在 C++ 编译器中编译 main.cpp 中的 #include “add.h” 应该以 C 语言的规则进行编译。
解决方案:
但是,如何保证一段 C 代码只会以 C 的方式被编译?
- __cplusplus 是 C++ 编译器内置的标准宏定义,用于确保 C 代码以统一的 C 方式被编译成目标文件
将 main.cpp 更改如下:
// main.cpp
#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;
}
编译运行:
$ g++ main.cpp add.o -o main
$ ./main
c = 3
这样,无论是 C 编译器还是 C++ 编译器,都可以运行。
注意事项:
- C++ 编译器不能以 C 的方式编译重载函数,C 中没有重载
- C++编译方式将函数名和参数列表编译器目标文件,C 编译方式只将函数名作为目标名进行编译‘
5 小结
1、重载使用一个函数定义不同的函数
2、重载本质为相互独立的不同的函数
3、C++ 通过函数名和函数参数确定函数调用
4、extren 关键字能够实现 C 和 C++ 的相互调用
5、编译方式决定符号表中的函数名的最终目标名