• C++深度解析教程学习笔记(3)函数的扩展


    1.内联函数

    1.1.常量与宏的回顾

    (1)C++中的 const 常量可以替代宏常数定义,如:

    const int A = 3;
    //等价于
    #define A 3

    (2)C++中是否有解决方案,可以用来替代宏代码片段呢?

    1.2.内联函数的定义

    (1)C++编译器可以将一个函数进行内联编译,被 C++编译器内联编译的函数叫内联函数。

    (2)C++中使用 inline 关键字声明内联函数。如

    inline int func(int a, int b)
    {
         return a < b ? a : b;
    }

    (3)内联函数声明时 inline 关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求。(在 vs2013 下,inline 放在声明或定义前均可以)

    1.3.内联函数的特点

    (1)C++编译器直接将内联函数的函数体插入到函数调用的地方

    (2)内联函数没有普通函数调用时的额外开销(压栈、跳转、返回)

    (3)C++中推荐使用内联函数替代宏代码片段。

    (4)C++编译器也不一定满足函数的内联请求。

    #include <stdio.h>
    
    #define FUNC(a, b) ((a) < (b) ? (a) : (b))
    
    //MSVC下:要让inline、__forceinline生效必须得做如下的设置:
    //①在“项目”→“配置属性”→“C / C++” →“优化”→“内联函数扩展”中选择“只适用于__inline(/ Ob1)”
    //②在“配置属性”→“C / C++” →“所有选项”→“调试信息格式”中选择“程序数据库( / Zi)”
    
    //VS2013下,inline可放在声明前或也可放在定义前。或两者前都加
    inline int func(int a, int b) 
    {
        return a < b ? a : b;
    }
    
    int main()
    {
        int a = 1;
        int b = 3;
    
        //int c = FUNC(++a, b);//相当于(++a)<(b)?:(++a):(b);
    
        //printf("a = %d
    ", a); //3
        //printf("b = %d
    ", b); //3
        //printf("c = %d
    ", c); //3
    
        int c = func(++a, b);
    
        printf("a = %d
    ", a);//2
        printf("b = %d
    ", b);//3
        printf("c = %d
    ", c);//2
    
        return 0;
    }

    内联函数没嵌入到调用地方(仍为函数调用)

    函数体被嵌入到调用的地方

    1.4.内联函数与宏的不同

     

    内联函数

    处理方式

    由预处理器处理,只是进行简单的文本替换

    由编译器处理,会将函数体嵌入到调用的地方。但内联请求也可能被编译器拒绝

    类型检查

    不做类型检查

    具有普通函数的特征,会进行参数和返回类型的检查。

    副作用

    1.5.现代C++编译器对内联函数的优化

    (1)现代 C++编译器能够进行编译优化,一些函数即没有 inline 声明,也可能被内联编译。

    (2)一些现代的 C++编译器提供了扩展语法,可用下列列关键字替代 inline 来对函数进行强制内联,如:

    ①g++:__atrribute__((always_inline))   ②MSVC:__forceinline

    (3)MSVC 下:要让 inline、__forceinline 生效必须得做如下的设置:

    ①在“项目”→“配置属性”→“C/C++” →“优化”→“内联函数扩展”中选择“只适用于__inline(/Ob1)”

    ②在“配置属性”→“C/C++” →“所有选项”→“调试信息格式”中选择“程序数据库(/Zi)”

    #include <stdio.h>
    
    //MSVC2013下:在函数声明或定义前加inline或__forceinline都可以
    //同时,这两个的表现行为几乎一模一样。只不过__forceinline是MS
    //下的,而inline是标准C++的,可移植性更高。
    
    //__forceinline
    //__attribute__((always_inline))
    //inline
    int add_inline(int n);
    
    int main()
    {
        int r = add_inline(10);
    
        printf("r = %d
    ", r);
    
        return 0;
    }
    
    __forceinline int add_inline(int n)
    {
        int ret = 0;
    
        for (int i = 0; i < n; i++)
        {
            ret += i;
        }
    
        return ret;
    }

    1.6. C++中 inline 内联编译的限制

    (1)含有递归调用的函数不能设置为 inline

    (2)使用了复杂流程控制语句:循环语句和 switch 语句,无法设置为 inline(说明:如上述实例,在 VS2013 下,循环语句是可以被内联的)

    (3)函数体不能过于庞大

    (4)不能对函数进行取址操作

    (5)函数内联声明必须在调用语句之前.

    2.函数参数的扩展

    2.1.函数参数默认值

    (1)C++中可以在函数声明时为参数提供一个默认值(注意是声明,不能在定义中提供)

    (2)当函数调用时没有提供参数的值,则使用默认值

    默认参数值

    #include <stdio.h>
    
    //默认值只能在函数声明时提供
    int mul(int x = 0); //参数x的默认值为0
    
    int main()
    {
        printf("%d
    ", mul());   //传入默认值0
        printf("%d
    ", mul(-1)); //传入-1
        printf("%d
    ", mul(2)); //传入2
    
        return 0;   
    }
    
    int mul(int x)    //定义中,不能提供默认值,编译器会报错
    {
        return x * x;
    }

    (3)函数参数默认值的规则

    ①声明时,默认值必须从右向左提供

    ②函数调用时,如果使用了默认值,则后续参数必须使用默认值。

    #include <stdio.h>
    
    //默认参数必须从右向左提供,诸如
    //int add(int x = 0,int y = 1,int z)是错误的
    int add(int x, int y = 1, int z = 2);
    
    int main()
    {
        //第2参数y使用了默认值,则后续的z也必须使用默认值
        //诸如add(1, ,3);的调用是错的。
        printf("%d
    ", add(0));      //x = 0, y = 1, z = 2
    
        printf("%d
    ", add(2, 3));   //x = 2, y = 3, z = 2
        printf("%d
    ", add(3, 2, 1));//x = 3, y = 2, z = 1
    
        return 0;   
    }
    
    int add(int x, int y, int z)
    {
        return x + y + z;
    }

    2.2.函数占位参数

    (1)占位参数只有参数类型声明,而没有参数名声明,如:int func(int x,int)

    (2)一般情况下,在函数体内部无法使用占位参数

    (3)占位参数的意义

    ①占位参数与默认参数结合起来使用

    ②兼容 C 语言程序中可能出现的不规范写法

    C++中支持占位参数,用于兼容 C 语言中的不规范写法

    占位参数与默认参数值

    #include <stdio.h>
    
    //在C中int func()是可以接受任意参数的,所以在后来的调用中可能
    //出现func(1)、func(2, 3)等不同的调用,而这样的代码在C++中是
    //错误的,所以为了兼容C语言这种不规范的写法,可以给func提供两个
    //占用参数如func(int = 0,int = 0),则前面的两种调用就合法了,
    //这样花很少的代价,就可以让C的代码可以在C++中使用。让人感觉仿
    //佛C++也可以像C语言一样,接受任意个参数了!
    
    //占位参数,且默认值为0
    int func(int x = 0, int = 0);
    
    int main()
    {
        printf("%d
    ", func());     //0
        printf("%d
    ", func(1));    //1
        printf("%d
    ", func(2, 3)); //2
    
        return 0;   
    }
    
    //第2个参数为占位参数(没函数名),因此在函数内部也就无法使用
    //这个参数,只起到占位的作用
    int func(int x, int)
    {
        return x;
    }

    3.函数重载

    3.1.函数重载(overload)的概念

    (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);
    }
    
    int main()
    {
        printf("%d
    ", func(3));              //int (int)
        printf("%d
    ", func(4,5));            //int (int,int)
        printf("%d
    ", func("Hello World!")); //int (const char* s)
    
        return 0;   
    }

    3.2.函数重载

    (1)重载的条件:必须至少满足下面的一个条件

    ①参数个数不同

    ②参数类型不同

    ③参数顺序不同

    (2)函数重载的注意事项

    ①重载函数在本质上是相互独立的不同函数。

    ②重载函数的函数类型不同

    ③函数的返回值不能作为函数重载的依据

    ④函数重载是由函数名和参数列表共同决定的。

    函数重载的本质

    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
    ", add);//因为函数的重载,在编译的结果中找不到这样的函数名
    
        //以下两个printf显示出来,重载函数的本质是相互独立的两个函数,其函数地址
        //是不同的。
    
        printf("%p
    ",(int (*)(int, int))add);    //在add前面加上类型,编译器就会
                                                  //就根据重载函数的命名规则找到
                                                  //被编译后的真正的函数名
    
        printf("%p
    ",(int (*)(int, int,int))add);//在add前面加上类型,编译器就会
                                                  //就根据重载函数的命名规则找到
                                                  //被编译后的真正的函数名
        return 0;   
    }

    3.3.函数重载与函数的默认参数

    (1)编译器调用重载函数的准则

    ①将所有同名函数作为候选者

    ②尝试寻找可行的候选函数(注意,下面 3 种匹配任一种后,会继续匹配下一种,所以可能出现多个匹配的结果!)

    A.精确匹配实参;B 通过默认参数能够匹配实参;C 通过默认类型转换匹配实参

    ③匹配失败

    A.最终寻找到的候选函数不唯一,则出现二义性,编译失败。

    B.无法匹配所有候选者,函数未定义,编译失败

    函数默认参数 VS 函数重载

    #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()
    {
        //根据匹配原则:通过函数名找到两个候选函数
        //并尝试先通过精确匹配会找到func(int,int)
        //但这时并不会停止匹配,而是会尝试用默认参数去匹配
        //所以会找到另一个func,即func(int,int,int = 0),因此
        //出现了二义性,编译器直接报错。
        int c = func(1, 2);
    
        return 0;   
    }

    函数重载用于模拟自然语言中的词汇搭配,使得 C++具有更丰富的语义表达能力。函数重载的本质为相互独立的不同函数,C++中通过函数名和函数参数确定函数调用。

    3.4.重载函数与函数指针

    (1)将重载函数名赋值给函数指针时

    ①根据重载规则挑选与函数指针参数列表一致的候选者

    ②严格匹配候选者的函数类型与函数指针的函数类型(所谓严格匹配,即函数参数及返回值都匹配)

    函数重载 VS 函数指针

    #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;//编译器将根据函数指针的类型去严格匹配对应的函数
                        //所以会找到int func(int);其他函数则匹配不成功
    
        c = p(1); //
    
        printf("c = %d
    ", c); //1
    
        return 0;   
    }

    (2)注意事项

    ①函数重载必然发生在同一个作用域中(如,同一个类或同一命名空间中)

    ②编译器需要用参数列表或函数类型进行函数选择

    ③无法直接通过函数名得到重载函数的入口地址(因为编译结束后,C++会根据重载函数命名的规则重命名各个函数,而原来的函数名实际上是找不到的)

    3.5.C和C++相互调用

    1)实际工作中 C++和 C 代码相互调用是不可避免的

    (2)C++编译器能够兼容 C 语言的编译方式

    (3)C++编译器会优先使用 C++编译的方式

    (4)extern 关键字能强制 C++编译器进行 C 方式的编译

    C++调用 C 函数

    //add.h
    int add(int a, int b);
    //add.c
    #include "add.h"
    
    //该文件的编译,得到目标文件add.o
    //gcc -c add.c
    
    int add(int a, int b)
    {
        return a + b;
    }
    //main.cpp
    #include <stdio.h>
    
    //该文件的编译
    //g++ main.cpp add.o
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    //C++中以C的方式编译:将add的函数名就是目标名
    #include "add.h"
    
    #ifdef __cplusplus
    }
    #endif
    
    int main()
    {
        int c = add(1, 2);
    
        printf("c = %d
    ", c); //3
    
        return 0;   
    }

    3.6.让C/C++代码只以C的方式编译

    (1)C++内置的标准宏:__cplusplus,可以确保 C 代码以统一的 C 方式编译

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    ......; //C/C++代码,将以C的方式编译
    
    #ifdef __cplusplus
    }
    #endif

    C 调用 C++函数(其中的 C++函数己经被按 C 方式编译)

    //add.h
    //该文件的编译,得到目标文件add.o
    //g++ -c add.c
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    //C++中以C的方式编译:add的函数名就是目标名
    int add(int a, int b);
    
    #ifdef __cplusplus
    }
    #endif
    //add.cpp
    #include "add.h"
    
    //该文件的编译,得到目标文件add.o
    //g++ -c add.c
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    //C++中以C的方式编译:add的函数名就是目标名
    int add(int a, int b)
    {
        return a + b;
    }
    
    #ifdef __cplusplus
    }
    #endif
    //main.c
    #include <stdio.h>
    #include "add.h"
      //编译方式:
      //gcc main.c add.o
    int main()
    {
        int c = add(1, 2);
    
        printf("c = %d
    ", c); //3
    
        return 0;   
    }

    C 调用 C++函数(其中的 C++函数是 C++方式编译)

    ①假设别人提供了编译好的 cpp 的头文件和.o 目标文件,但其中的函数是以 C++方式编译的,很明显函数名是用 C++方式命名的。我们的 C 文件里不方便使用这个的函数名。

    ②解决的方案是:做一个 C++的封装层,对其中的函数进行一个封装,然后再用extern "c"编译这些封装层中的函数,最后就可以在 C 文件中使用了。

    //add.h
    int add(int a, int b);
    //add.cpp
    #include "add.h"
    
    //编译命令:g++ -c add.cpp
    
    int add(int a, int b)
    {
        return a + b;
    }

    我们的封装层

    //addEx.h
    int addEx(int a, int b);
    //addEx.cpp
    #include "add.h"
    
    //编译命令:
    //g++ -c addEx.cpp
    
    extern "C" int addEx(int a,int b)
    {
        return add(a, b);
    }
    //main.c
    #include <stdio.h>
    #include "addEx.h"
    //编译命令:
    //gcc main.c addEx.0 add.o
    
    int main()
    {
        int c = addEx(1, 2);
    
        printf("c = %d
    ", c); //3
    
        return 0;   
    }

    (2)注意事项

    ①C++编译器不能以 C 的方式编译重载函数,即如果在 extern "C"块里有两个同名的函数里,则会编译失败。

    ②编译方式决定函数名被编译后的目标名。C++编译方式将函数名和参数列表编译成目标名,而 C 编译方式只将函数名作为目标名进行编译。


     
  • 相关阅读:
    笔记
    软件工程第二次作业
    现代软件工程 第一周作业
    2019春季学期期末总结
    2019第十四周作业
    2019第十二周左右
    2019第十一周作业
    2019第十周作业
    2019第九周作业
    2019第八周作业
  • 原文地址:https://www.cnblogs.com/CoderTian/p/7745766.html
Copyright © 2020-2023  润新知