C++深度解析教程学习笔记(3)函数的扩展
1.内联函数
1.1.常量与宏的回顾
(1)C++中的 const 常量可以替代宏常数定义,如:
const int A = ;
//等价于
#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 = ;
int b = ; //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); // int c = func(++a, b); printf("a = %d\n", a);//
printf("b = %d\n", b);//
printf("c = %d\n", c);// return ;
}
内联函数没嵌入到调用地方(仍为函数调用)
函数体被嵌入到调用的地方
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(); printf("r = %d\n", r); return ;
} __forceinline int add_inline(int n)
{
int ret = ; for (int i = ; 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 = ); //参数x的默认值为0 int main()
{
printf("%d\n", mul()); //传入默认值0
printf("%d\n", mul(-)); //传入-1
printf("%d\n", mul()); //传入2 return ;
} 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 = , int z = ); int main()
{
//第2参数y使用了默认值,则后续的z也必须使用默认值
//诸如add(1, ,3);的调用是错的。
printf("%d\n", add()); //x = 0, y = 1, z = 2 printf("%d\n", add(, )); //x = 2, y = 3, z = 2
printf("%d\n", add(, , ));//x = 3, y = 2, z = 1 return ;
} 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 = , int = ); int main()
{
printf("%d\n", func()); //
printf("%d\n", func()); //
printf("%d\n", func(, )); // return ;
} //第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()); //int (int)
printf("%d\n", func(,)); //int (int,int)
printf("%d\n", func("Hello World!")); //int (const char* s) return ;
}
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 ;
}
3.3.函数重载与函数的默认参数
(1)编译器调用重载函数的准则
①将所有同名函数作为候选者
②尝试寻找可行的候选函数(注意,下面 3 种匹配任一种后,会继续匹配下一种,所以可能出现多个匹配的结果!)
A.精确匹配实参;B 通过默认参数能够匹配实参;C 通过默认类型转换匹配实参
③匹配失败
A.最终寻找到的候选函数不唯一,则出现二义性,编译失败。
B.无法匹配所有候选者,函数未定义,编译失败
函数默认参数 VS 函数重载
#include <stdio.h> int func(int a, int b, int c = )
{
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(, ); return ;
}
函数重载用于模拟自然语言中的词汇搭配,使得 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 = ; PFUNC p = func;//编译器将根据函数指针的类型去严格匹配对应的函数
//所以会找到int func(int);其他函数则匹配不成功 c = p(); // printf("c = %d\n", c); // return ;
}
(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(, ); printf("c = %d\n", c); // return ;
}
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(, ); printf("c = %d\n", c); // return ;
}
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(, ); printf("c = %d\n", c); // return ;
}
(2)注意事项
①C++编译器不能以 C 的方式编译重载函数,即如果在 extern "C"块里有两个同名的函数里,则会编译失败。
②编译方式决定函数名被编译后的目标名。C++编译方式将函数名和参数列表编译成目标名,而 C 编译方式只将函数名作为目标名进行编译。
C++深度解析教程学习笔记(3)函数的扩展的更多相关文章
- C++深度解析教程学习笔记(6)对象的构造和销毁
1. 对象的初始化 (1)从程序设计的角度看,对象只是变量,因此: ①在栈上创建对象时,成员变量初始化为随机值 ②在堆上创建对象时,成员变量初始化为随机值 ③在静态存储区创建对象时,成员变量初始化为 ...
- C++深度解析教程学习笔记(5)面向对象
1. 面向对象基本概念 (1)面向对象的意义在于 ①将日常生活中习惯的思维方式引入程序设计中 ②将需求中的概念直观的映射到解决方案中 ③以模块为中心构建可复用的软件系统 ④提高软件产品的可维护性和可扩 ...
- C++深度解析教程学习笔记(4)C++中的新成员
1. 动态内存分配 (1)C++通过 new 关键字进行动态内存申请,是以类型为单位来申请空间大小的 (2)delete 关键字用于内存释放 ▲注意释放数组时要加[],否则只释放这个数组中的第 1 个 ...
- C++深度解析教程学习笔记(2)C++中的引用
1.C++中的引用 (1)变量名的回顾 ①变量是一段实际连续存储空间的别名,程序中通过变量来申请并命名存储空间 ②通过变量的名字可以使用存储空间.(变量的名字就是变量的值,&变量名是取地址操作 ...
- C++深度解析教程学习笔记(1)C到C++的升级
1.现代软件产品架构图 比如商场收银系统 2.C 到 C++ 的升级 2.1变量的定义 C++中所有的变量都可以在需要使用时再定义,而 C 语言中的变量都必须在作用域开始位置定义. 2.2 regis ...
- ES6学习笔记(6)----函数的扩展
参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ 函数的扩展 函数的默认值 : ES6可以为函数指定默认值 (1)指定默认值的两种方式 a.函数参 ...
- 《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现
pring框架是一个分层架构,它包含一系列的功能要素,并被分为大约20个模块,如下图所示 这些模块被总结为以下几个部分: Core Container Core Container(核心容器)包含有C ...
- 《C语言深度解剖》学习笔记之函数
第6章 函数 1.编码风格 [规则6-1]每一个函数都必须有注释 [规则6-2]每个函数定义之后以及每个文件结束之后都要加若干个空行 [规则6-3]在一个函数体内,变量定义与函数语句之间要加空行 [规 ...
- 尚硅谷韩顺平Linux教程学习笔记
目录 尚硅谷韩顺平Linux教程学习笔记 写在前面 虚拟机 Linux目录结构 远程登录Linux系统 vi和vim编辑器 关机.重启和用户登录注销 用户管理 实用指令 组管理和权限管理 定时任务调度 ...
随机推荐
- 【辅助工具】Python实现微信跳一跳
最近迷上了微信跳一跳小游戏,正好也看到知乎上有大神分享了技术贴,我也参考了好多资料,原理就是通过abd命令截取图片,python计算两个点距离,然后转化按压时间,让电脑来完成游戏.我花了很长时间才把程 ...
- 条款16:成对使用new以及delete的时候应该采取相同的形式
首先思考下面的代码: ]; ... delete stringArray; 这是最常见的错误之一了,new与delete不配对,但是仔细想想new与delete为什么一定要配对呢? 可以想到一种可能就 ...
- Android 应用程序窗体显示状态操作(requestWindowFeature()的应用)
我们在开发程序是经常会需要软件全屏显示.自定义标题(使用按钮等控件)和其他的需求,今天这一讲就是如何控制Android应用程序的窗体显示. 首先介绍一个重要方法那就是requestWindowF ...
- HAWQ取代传统数仓实践(七)——维度表技术之维度子集
有些需求不需要最细节的数据.例如更想要某个月的销售汇总,而不是某天的数据.再比如相对于全部的销售数据,可能对某些特定状态的数据更感兴趣等.此时事实数据需要关联到特定的维度,这些特定维度包含在从细节维度 ...
- Build ios app with Delphi Xe4. Lazy Social Talker ready for sale.
Yes, it is build with Delphi XE4. try it. now. What is Lazy Social Talker? Lazy Social Talker is a ...
- 程序猿之GitHub
介绍 GitHub是一个分布式的代码.文章等等管理仓库.面向开源及私有软件项目. 简单来说,Git 是一个管理你的「代码的历史记录」的工具. 开始使用(知乎总结) 账号注册 创建新工程 克隆到本地 删 ...
- 【spring源码学习】spring的IOC容器在初始化bean过程
[一]初始化IOC的bean的时候Spring会执行的一些回调方法 (1)spring bean创建的前置处理 =>ApplicationContextAwareProcessor 在创建bea ...
- call()方法和apply()方法用法总结
1. 每个函数都包含两个非继承而来的方法:call()方法和apply()方法. 2. 相同点:这两个方法的作用是一样的. 都是在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖 ...
- gradle 安装试用
1. java 环境(jdk 6 以上,最好使用8以及以上) yum install -y java-1.8.0-openjdk-devel 2. 基本配置 // path 路径 export PAT ...
- assembly 需要 unload 和 update 的时候怎么办?
我正在开发公司的业务组件平台,组件池的灵活性要求很高,业务组件都是可以立即更新和及时装配的;目前完成这些功能,有待测试.用appDomain.unload 拆卸assembly 可以,只是用起来比较麻 ...