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)函数的扩展的更多相关文章

  1. C++深度解析教程学习笔记(6)对象的构造和销毁

    1. 对象的初始化 (1)从程序设计的角度看,对象只是变量,因此: ①在栈上创建对象时,成员变量初始化为随机值 ②在堆上创建对象时,成员变量初始化为随机值 ③在静态存储区创建对象时,成员变量初始化为 ...

  2. C++深度解析教程学习笔记(5)面向对象

    1. 面向对象基本概念 (1)面向对象的意义在于 ①将日常生活中习惯的思维方式引入程序设计中 ②将需求中的概念直观的映射到解决方案中 ③以模块为中心构建可复用的软件系统 ④提高软件产品的可维护性和可扩 ...

  3. C++深度解析教程学习笔记(4)C++中的新成员

    1. 动态内存分配 (1)C++通过 new 关键字进行动态内存申请,是以类型为单位来申请空间大小的 (2)delete 关键字用于内存释放 ▲注意释放数组时要加[],否则只释放这个数组中的第 1 个 ...

  4. C++深度解析教程学习笔记(2)C++中的引用

    1.C++中的引用 (1)变量名的回顾 ①变量是一段实际连续存储空间的别名,程序中通过变量来申请并命名存储空间 ②通过变量的名字可以使用存储空间.(变量的名字就是变量的值,&变量名是取地址操作 ...

  5. C++深度解析教程学习笔记(1)C到C++的升级

    1.现代软件产品架构图 比如商场收银系统 2.C 到 C++ 的升级 2.1变量的定义 C++中所有的变量都可以在需要使用时再定义,而 C 语言中的变量都必须在作用域开始位置定义. 2.2 regis ...

  6. ES6学习笔记(6)----函数的扩展

    参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ 函数的扩展 函数的默认值 : ES6可以为函数指定默认值 (1)指定默认值的两种方式 a.函数参 ...

  7. 《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现

    pring框架是一个分层架构,它包含一系列的功能要素,并被分为大约20个模块,如下图所示 这些模块被总结为以下几个部分: Core Container Core Container(核心容器)包含有C ...

  8. 《C语言深度解剖》学习笔记之函数

    第6章 函数 1.编码风格 [规则6-1]每一个函数都必须有注释 [规则6-2]每个函数定义之后以及每个文件结束之后都要加若干个空行 [规则6-3]在一个函数体内,变量定义与函数语句之间要加空行 [规 ...

  9. 尚硅谷韩顺平Linux教程学习笔记

    目录 尚硅谷韩顺平Linux教程学习笔记 写在前面 虚拟机 Linux目录结构 远程登录Linux系统 vi和vim编辑器 关机.重启和用户登录注销 用户管理 实用指令 组管理和权限管理 定时任务调度 ...

随机推荐

  1. mybatis 使用接口增删改查和两表一对一关联查询

    导包 总配置文件 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration ...

  2. auto_ptr, unique_ptr, shared_ptr and weak_ptr智能指针讲解

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  3. beego配置文件

    关于App配置: #App配置 for Api AppName = ApiService RunMode = dev RouterCaseSensitive = true ServerName = A ...

  4. 【MFC】vs2013_MFC使用文件之15.mfc 按钮CBitmapButton的使用

    本文是基于对话框的 博文基于 无幻 的博文为基础写的 http://blog.csdn.net/akof1314/article/details/4951836 笔者使用mfc撑死2个星期,不过这是有 ...

  5. LeetCode Split Array into Consecutive Subsequences

    原题链接在这里:https://leetcode.com/problems/split-array-into-consecutive-subsequences/description/ 题目: You ...

  6. [C/C++]宽字符与控制台程序

    转自:http://www.cnblogs.com/zplutor/archive/2010/11/27/1889227.html 在我刚开始学C/C++的时候,字符类型使用的都是char.接触Win ...

  7. LA3218 Find the Border

    题意 PDF 分析 虽然只找外轮廓,但是时间复杂度不比PSLG优秀,所以可以当做联系PSLG的题做. PSLG框架 找出所有交点 交点按序连边 把边按极角序排序 逆时针找圈 然后何以会顺时针找出无限区 ...

  8. win10 svn server安装过程中到starting service时失败

    当安装到Start Service阶段后,将会出错并会弹出一个对话框,提示是否要retry. (此时SVN服务软件已经安装完毕,仅仅是无法通过证书验证,无法启动服务),如果此时选择对话框中的cance ...

  9. 三个Telnet链接类----来自网络

    声明:下面的三个类都是从网上找到的,具体出处不详,哪个类的好坏性能优劣自己把握,可参考使用.拒绝使用商业用途,如产生版权纠纷和本人无关. 一:Telnet链接网络设备,在网上看到C#Telnet连接网 ...

  10. Python学习系列(二)(基础知识)

    Python基础语法 Python学习系列(一)(基础入门) 对于任何一门语言的学习,学语法是最枯燥无味的,但又不得不学,基础概念较繁琐,本文将不多涉及概念解释,用例子进行相关解析,适当与C语言对比, ...