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\n", a); //3
    //printf("b = %d\n", b); //3
    //printf("c = %d\n", c); //3

    int c = func(++a, b);

    printf("a = %d\n", a);//2
    printf("b = %d\n", b);//3
    printf("c = %d\n", c);//2

    return 0;
}

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

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

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

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

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\n", 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\n", mul());   //传入默认值0
    printf("%d\n", mul(-1)); //传入-1
    printf("%d\n", 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\n", add(0));      //x = 0, y = 1, z = 2

    printf("%d\n", add(2, 3));   //x = 2, y = 3, z = 2
    printf("%d\n", 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\n", func());     //0
    printf("%d\n", func(1));    //1
    printf("%d\n", 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\n", func(3));              //int (int)
    printf("%d\n", func(4,5));            //int (int,int)
    printf("%d\n", 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\n", add);//因为函数的重载,在编译的结果中找不到这样的函数名

    //以下两个printf显示出来,重载函数的本质是相互独立的两个函数,其函数地址
    //是不同的。

    printf("%p\n",(int (*)(int, int))add);    //在add前面加上类型,编译器就会
                                              //就根据重载函数的命名规则找到
                                              //被编译后的真正的函数名

    printf("%p\n",(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\n", 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\n", 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\n", 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\n", c); //3

    return 0;   
}

(2)注意事项

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

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