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; }
内联函数没嵌入到调用地方(仍为函数调用)
函数体被嵌入到调用的地方
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 编译方式只将函数名作为目标名进行编译。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++深度解析教程学习笔记(3)函数的扩展 - Python技术站