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编辑器 关机.重启和用户登录注销 用户管理 实用指令 组管理和权限管理 定时任务调度 ...
随机推荐
- mybatis 使用接口增删改查和两表一对一关联查询
导包 总配置文件 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration ...
- auto_ptr, unique_ptr, shared_ptr and weak_ptr智能指针讲解
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...
- beego配置文件
关于App配置: #App配置 for Api AppName = ApiService RunMode = dev RouterCaseSensitive = true ServerName = A ...
- 【MFC】vs2013_MFC使用文件之15.mfc 按钮CBitmapButton的使用
本文是基于对话框的 博文基于 无幻 的博文为基础写的 http://blog.csdn.net/akof1314/article/details/4951836 笔者使用mfc撑死2个星期,不过这是有 ...
- LeetCode Split Array into Consecutive Subsequences
原题链接在这里:https://leetcode.com/problems/split-array-into-consecutive-subsequences/description/ 题目: You ...
- [C/C++]宽字符与控制台程序
转自:http://www.cnblogs.com/zplutor/archive/2010/11/27/1889227.html 在我刚开始学C/C++的时候,字符类型使用的都是char.接触Win ...
- LA3218 Find the Border
题意 PDF 分析 虽然只找外轮廓,但是时间复杂度不比PSLG优秀,所以可以当做联系PSLG的题做. PSLG框架 找出所有交点 交点按序连边 把边按极角序排序 逆时针找圈 然后何以会顺时针找出无限区 ...
- win10 svn server安装过程中到starting service时失败
当安装到Start Service阶段后,将会出错并会弹出一个对话框,提示是否要retry. (此时SVN服务软件已经安装完毕,仅仅是无法通过证书验证,无法启动服务),如果此时选择对话框中的cance ...
- 三个Telnet链接类----来自网络
声明:下面的三个类都是从网上找到的,具体出处不详,哪个类的好坏性能优劣自己把握,可参考使用.拒绝使用商业用途,如产生版权纠纷和本人无关. 一:Telnet链接网络设备,在网上看到C#Telnet连接网 ...
- Python学习系列(二)(基础知识)
Python基础语法 Python学习系列(一)(基础入门) 对于任何一门语言的学习,学语法是最枯燥无味的,但又不得不学,基础概念较繁琐,本文将不多涉及概念解释,用例子进行相关解析,适当与C语言对比, ...