函数重载(七)

        今天我们来看下函数重载,那么什么是函数重载呢?我们先来看看自然语言中的上下文,比如:洗字。在不同的场景便会有不同的意义,比如:洗衣服、洗车、洗脸、洗马桶、洗脑。每个洗字搭配的词汇的意义都不一样,这便有了重载(overload)的概念。

        重载是指同一个标识符在不同的上下文有不同的意义,重载在自然语言中是随处可见的,那么在程序设计中是否也有重载呢?C++ 是一门面向对象的语言,当然要支持函数重载了。C++ 中的函数重载表现为:a> 用同一个函数名定义不同的函数;b> 当函数名和不同的参数搭配时函数的含义不同

        下来我们以代码为例进行分析

#include <stdio.h>
#include <string.h>

int func(int a)
{
    return a;
}

int func(int a, int b)
{
    return a + b;
}

int func(const char* s)
{
    return strlen(s);
}

int main()
{
    printf("%d\n", func(1));
    printf("%d\n", func(1, 2));
    printf("%d\n", func("hello"));
    
    return 0;
}

        我们看到用同一个函数名定义了 3 个函数,在 C 语言中这样是不行的。我们看看在 C++ 中是否支持呢?

图片.png

        我们看到编译通过并且也完成函数的功能了。那么怎样就能称之为函数重载呢?它至少得满足下面的一个条件:a> 参数个数不同;b> 参数类型不同;c> 参数顺序不同;那么下面的函数可以称之为函数重载吗?

int func(int a, const char* s)
{
    return a;
}

int func(const char* s, int a)
{
    return strlen(s);
}

        它们显然是函数重载了,因为它们满足上面的第 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()
{
    printf("%d\n", func(1, 2));
    
    return 0;
}

        我们看到定义了两个 func 函数,第一个的最后一个是默认参数,这样的函数可以称之为函数重载吗?编译器究竟是否知道它该调用那个函数呢?我们看看编译结果

图片.png

        我们看到编译报错了,它说这个函数重载是模糊的,它不知道该调用那个函数。虽然 C++ 相对于 C 添加了很多特性,显然这个就是一个不好的特性,在后续的高级语言(如 Java、C#等)中都取消这些相互矛盾的特性。所以这样的函数重载是不可取的,那么编译器调用重载函数时有哪些准则呢?A、将所有的同名函数作为候选者;B、尝试寻找可行的候选函数:a> 精确匹配实参。  b> 通过默认参数能够匹配实参 。 c> 通过默认类型转换匹配实参;C、匹配失败:a> 最终寻找到的候选函数不唯一,则出现二义性,编译失败。 b> 无法匹配所有候选者,函数未定义,编译失败

        在函数重载时应注意的事项:a> 重载函数在本质上是相互独立的不同函数;b> 重载函数的函数类型不同;c> 函数返回值不嫩作为函数重载的依据;函数重载是由函数名和参数列表决定的!!!

扫描二维码关注公众号,回复: 117844 查看本文章

        下来我们通过一份示例代码来看看函数重载的本质

#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;
}

        我们在之前学过函数名其实就是函数的入口地址,我们通过函数的类型来打印它的地址。我们来看看编译结果

图片.png

        我们看到打印的是不同的地址,也就是说这两个重载函数是不同的。我们再在 C 语言编译器中编译试下,看看结果是什么

图片.png

        我们看到它报错了,说 add 函数已经定义了。在 C 语言中,只要函数名相同,它便认为这是同一个函数。我们再来看看重载与指针,还是以代码为例进行分析

#include <stdio.h>
#include <string.h>

int func(int a)
{
    return a;
}

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;
}

        我们在第 19 行定义了一个函数指针 PFUNC,那么我们在第 25 行将它指向 func 函数,这时它会知道自己指向的是哪个函数吗?我们来看看编译结果

图片.png

        我们看到编译通过,并完美运行。很明显它指向的是第一个 func 函数,因为它的类型为 int(int);所以它匹配到了第一个 func 函数。

        当函数重载遇上函数指针,将重载函数名赋值给函数指针时:a> 根据重载规则挑选与函数指针参数列表一致的候选者;b> 严格匹配候选者的函数类型与函数指针的函数类型。注意:1、函数重载必然发生在同一个作用域中;2、编译器需要用参数列表或函数类型进行函数选择;3、无法直接通过函数名得到重载函数的入口地址

        在实际的工程中 C++ 和 C 代码间的相互调用时不可避免的,C++ 编译器能够兼容 C 语言的编译方式。但 C++ 编译器会优先使用 C++ 编译的方式,extern 关键字能够强制让 C++ 编译器进行 C 方式的编译。下来我们来看个示例代码


add.h 源码

int add(int a, int b);


add.c 源码

#include "add.h"

int add(int a, int b)
{
    return a + b;
}


test.cpp 源码

#include <stdio.h>

extern "C"
{
#include "add.h"
}

int main(int argc, char *argv[])
{
    int c = add(1, 2);
    
    printf("c = %d\n", c);
    
    return 0;
}

        我们先将 add.c 编译成 add.o 文件,再编译 test.cpp 文件,看看结果是否如我们所想的那样

图片.png

        我们看到结果已经实现了。我们如果将 test.cpp 里面的代码复制到 test.c 里面,以 C 的方式编译,能否通过呢?

图片.png

        我们发现它不认识 extern  "C",那么问题来了,我们如何保证一段 C 代码只会以 C 的方式被编译呢?__cplusplus 是 C++ 编译器内置的标准宏定义,我们可以利用这个宏来确保 C 代码以统一的 C 方式被编译成目标文件。我们将上面 test.cpp 中的 extern  "C" 这一段代码改成下面这样,我们再次试试看呢

#ifdef __cplusplus
extern "C" {
#endif

#include "add.h"

#ifdef __cplusplus
}
#endif

        这样便保证了在 C++ 编译器中以 C 的方式进行编译,如果是在 C 编译器中,extern "C" 便不起作用。我们编译下看看结果

图片.png

        我们看到在 C 编译器下也能正常编译。在这块有两个注意事项:A、C++ 编译器不能以 C 的方式编译重载函数;B、编译方式决定函数名被编译后的目标名:a> C++ 编译方式将函数名和参数列表编译成目标名。 b> C 编译方式只将函数名作为目标名进行编译。

        通过对函数重载的学习,总结如下:1、函数重载是 C++ 中引入的概念,函数重载用于模拟自然语言中的词汇搭配;2、函数重载使得 C++ 具有更丰富的语义表达能力,它的本质为相互独立的不同函数;3、C++ 中通过函数名和函数参数确定函数调用;4、函数重载是 C++ 对 C 的一个重要升级;5、函数重载通过函数参数列表区分不同的同名函数;6、extern 关键字能够实现 C 和 C++ 的相互调用;7、编译方式决定符号表中的函数名的最终目标名。


        欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083

猜你喜欢

转载自blog.51cto.com/12810168/2113276
今日推荐