【C++深度解析】6、函数重载

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、编译方式决定符号表中的函数名的最终目标名

发布了248 篇原创文章 · 获赞 115 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/happyjacob/article/details/104085912