文章目录
1 内联函数(inline function)
1.1 内联函数的引出:宏函数的缺陷
宏函数/预处理宏:将函数体代码较少且频繁执行的计算写成宏(宏函数不是函数),可提高执行效率;宏可以避免函数调用的开销,宏在程序预处理时展开。
优点:以空间换时间,消耗内存空间以节省普通函数入栈与出栈的时间开销。
缺点:
(1)C和C++中,宏与函数调用类似,但容易隐藏不易发现的错误(需添加括号,保证运算表达式的完整性,否则容易出现优先级问题),且某些情况下与预期效果不相符;
(2)C++中,预处理器不允许访问类的成员,即预处理宏不能作为类的成员函数。
示例:宏函数的缺陷
#include <iostream>
using namespace std;
/* 宏函数的缺陷1:需添加括号,保证运算表达式的准确性 */
#define MYADD1(a, b) a + b
#define MYADD2(a, b) ((a) + (b))
void func1() {
int res1 = MYADD1(10, 20) * 2; //预处理时展开为:10 + 20 * 2 → 50
cout << "res1 = " << res1 << endl; //实际结果:50 //预期结果:60
int res2 = MYADD2(10, 20) * 2; //预处理时展开为:((10) + (20)) * 2 → 60
cout << "res2 = " << res2 << endl; //实际结果:60 //预期结果:60
}
/* 宏函数的缺陷2:某些情况下,即使添加括号,结果仍与预期不符 */
#define MYCOMPARE(a, b) ( ( (a) < (b) ) ? (a) : (b) )
void func2() {
int a = 10;
int b = 20;
int res = MYCOMPARE(++a, b); //预处理时展开为:(((++a)<(b))?(++a):(b)) 包含两次前置自增操作
cout << "res = " << res << endl; //实际结果:12 //预期结果:11
}
int main() {
func1();
func2();
return 0;
}
1.2 内联函数
内联函数:为保持预处理宏的效率、提升安全性,同时,在类中可以像普通成员函数访问,C++引入内联函数(内联函数本身是函数)。内联函数具有普通函数的所有行为,区别在于会在适当的时机像预定义宏一样展开(不是在预处理阶段展开),无需函数调用的开销。
注:C++中,建议使用内联函数替代宏。
优点:
(1)保持宏函数的高执行效率,以空间换时间,无函数调用时入栈与出栈的时间开销;
(2)同普通函数,可进行函数参数、返回值类型的安全检查;
(3)可作为类的成员函数。
用法:在普通函数(非成员函数)的返回类型前使用inline
关键字,且函数声明与函数定义需同时包含inline
关键字,否则编译器仍视为普通函数。
示例:
//头文件——函数声明
inline int func(int num);
//源文件——函数定义
inline int func(int num){
return num;
}
注1:若头文件和源文件中使用内联函数,则头文件的函数声明与源文件的函数定义,均需包含
inline
关键字。
注2:在类中定义的函数,均为内联函数,编译器默认加上inline
关键字。
注3:C++编译器编译内联函数时的限制情况(不将函数以内联方式编译):
①不能存在任何形式的循环语句;
②不能存在过多的条件判断语句;
③函数体代码不能过长;
④不能对函数进行取址操作(如递归函数)。
总结:内联函数仅仅是对编译器的建议,但编译器并不一定会采纳该建议;编译器有可能会将未声明为内联函数的函数进行内联编译。内联编译是编译器的行为,优秀的编译器会将简短且频繁的函数进行内联编译,通常无需用户主动声明为内联函数。
2 函数默认参数
C++中,函数形参列表中的形参可以有默认值。
语法:返回值类型 函数名(参数 = 默认值, ...) {...}
注1:若函数声明或函数定义中存在默认参数,则函数调用时实参个数大于等于非默认参数的个数。(若形参列表均设置默认值,则函数调用时可不传入实参。)
注2:若形参列表中某个形参设置了默认值,则从该默认参数位置起的所有形参,均需设置默认值,否则编译器报错:默认实参不在形参列表的结尾
。
注3:函数声明或函数定义中,只能有一处包含默认参数。若函数声明包含默认参数,则函数定义不能包含默认参数,否则编译器报错:重定义默认参数
(存在二义性)。
示例:
/* 函数调用时,实参个数大于等于未默认参数的个数 */
//如func1(1)、func(1,3)、func(1,3,5)均合法
int func1(int a, int b = 2, int c = 3) {
return a + b + c;
}
/* 若形参列表中某个形参设置了默认值,则从该默认参数位置起的所有形参,均需设置默认值 */
//int func2(int a, int b = 2, int c, int d); //错误:默认实参不在形参列表的结尾
int func2(int a, int b = 2, int c = 3, int d = 4); //正确
int func2(int a, int b = 2, int c = 3, int d = 4) {
return a + b + c + d;
}
/* 函数声明或函数定义中,只能有一处包含默认参数 */
/*
//错误示例:重定义默认参数(存在二义性)
int func3(int a = 10, int b = 10); //函数声明
int func3(int a = 10, int b = 10) { //函数定义
return a + b;
}
*/
/* 若形参列表中某个形参设置了默认值,则从该默认参数位置起的所有形参,均需设置默认值 */
int main() {
func1(1); //6 //1+2+3
func2(0); //9 //0+2+3+4
return 0;
}
3 函数占位参数
C++函数的形参列表可包含占位参数,用作占位,调用函数时必须使用相同数据类型的数据填充占位参数的位置。
语法:形参只写数据类型,而不写形参名,即返回值类型 函数名(数据类型){...}
注:占位参数可使用默认值(默认参数)。
示例:
void func(int a, int) {
cout << "test" << endl;
}
/* 占位参数可使用默认值(默认参数) */
void func2(int a, int = 10) {
cout << "test" << endl;
}
int main() {
//函数调用时必须使用相同数据类型的数据填充占位参数的位置
func(1, 2);
//占位参数可使用默认值(默认参数)
func2(1);
return 0;
}
4 函数重载
4.1 函数重载概述
作用:函数名相同,可提高复用性。
函数重载的条件:
(1)同一作用域中;
(2)函数名相同;
(3)函数形参列表不同(参数类型、个数或顺序不同)。
注1:函数重载只与函数名和形参列表相关,与函数返回类型无关。若仅改变函数返回类型,则编译器报错:
无法重载仅按返回类型区分的函数
。
注2:C++函数重载与Java方法重载(Overload)类似,与Java方法重写(Override)无关。Java方法重载(Overload):在同一个类中,方法名相同,形参列表不同;与返回值类型无关。
Java方法重写(Override):子类中出现与父类方法声明完全相同的方法(包括方法名、形参及返回类型),也称为方法覆盖、方法复写。
示例:
#include <iostream>
using namespace std;
void func(int a) {
cout << "func(int a)" << endl;
}
//参数个数不同
void func(int a, int b) {
cout << "func(int a, int b)" << endl;
}
//参数类型不同
void func(int a, double b) {
cout << "func(int a, double b)" << endl;
}
//参数顺序不同
void func(double a, int b) {
cout << "func(double a, int b)" << endl;
}
/* 函数重载与函数返回类型无关 */
//若仅改变函数返回类型,则编译器报错:无法重载仅按返回类型区分的函数
/*
int func(int a) {
cout << "func(int a)" << endl;
}
*/
int main() {
func(1);
func(1, 2); //参数个数不同
func(1, 3.14); //参数类型不同
func(3.14, 1); //参数顺序不同
return 0;
}
4.2 函数重载的注意事项
(1)形参列表中引用可作为函数重载的条件,且非常量引用数据类型 &引用名
和常量引用const 数据类型 &引用名
之间可互相重载。
注1:实参为变量,优先调用形参为非常量引用(无const)的函数。
注2:实参为常量或字面量时,只会调用形参为常量引用(含const)的函数。
示例:
#include <iostream>
using namespace std;
/* 形参列表中,引用可作为函数重载的条件 */
//非常量引用:函数调用时,实参可传入变量(不可传入常量或字面量)
void func(int &a) {
cout << "func(int &a)" << endl;
}
//常量引用:函数调用时,实参可传入常量或字面量
void func(const int &a) {
cout << "func(const int &a)" << endl;
}
int main() {
/* 1.实参为变量,优先调用形参为非常量引用(无const)的函数 */
//int &a = var; 合法
//const int &a = var; 合法,但会限定引用为常量引用(只读状态)
int var = 1;
func(var); //输出结果:func(int &a)
/* 2.实参为常量或字面量时,只会调用形参为常量引用(含const)的函数 */
//int &a = 3; 不合法
//const int &a = 3; 合法(相当于创建临时变量int temp = 3; const int &a = temp;)
const int c_var = 2;
func(c_var); //输出结果:func(const int &a)
func(3); //输出结果:func(const int &a)
return 0;
}
(2)函数重载时,需尽量避免使用默认参数,否则可能存在二义性,编译器报错:有多个重载函数 func 实例与参数列表匹配
或对重载函数的调用不明确
。
示例:
#include <iostream>
using namespace std;
/* 函数重载时,需尽量避免使用默认参数,否则可能存在二义性 */
void func(int a) {
cout << "func(int a)" << endl;
}
//使用默认参数
void func(int a, int b = 2) {
cout << "func(int a, int b = 2)" << endl;
}
int main() {
//错误示例
//func(1); //报错:有多个重载函数`func`实例与参数列表匹配;对重载函数的调用不明确
return 0;
}
4.3 函数重载的实现原理
为实现函数重载,编译器内部会根据形参列表的不同修饰不同的重载函数名。
不同编译器对重载函数可能产生不同的内部函数名,例如:
void func() → _func
void func(int x) → _func_int
void func(int x, double y) → _func_int_double
5 extern “C”(C++代码调用C语言代码)
作用:在C++中调用C语言的源文件,即在C++代码中使用extern "C"
,兼容地调用C语言代码。
应用场景:C++语言支持函数重载,C语言不支持函数重载。C++函数重载时,C++编译器内部会将函数名修饰为指定格式,并按照修饰后的函数名查找函数,因此,无法正常链接C语言程序中的函数,编译器报错:无法解析的外部命令
(链接阶段出错,无法找到函数的实现体)。使用extern "C"
,以C语言方式进行链接。
注:C++编译函数时会经过一次加工,即C++的函数重载,而C编译没有特殊处理。
解决方案:
(1)在调用C语言代码函数的C++源文件(.cpp文件)中,对所调用的函数使用extern "C"
加函数声明,如:extern "C" void func();
注:该方式的缺点:若需要调用的C语言代码函数过多时,则需多次使用
extern "C"
语句。【不建议使用】
示例:
/* C语言头文件test.h */
#include <stdio.h>
void function();
/* C语言源文件test.c */
#include "test.h"
void function() {
printf("Hello C!");
}
/* C++源文件main.cpp */
#include <iostream>
using namespace std;
//使用extern C加函数声明后,不能再包含头文件,否则提示:链接规范不兼容
//#include "test.h"
//兼容地调用C语言程序中的函数
extern "C" void function();
int main() {
function();
return 0;
}
(2)在被调用的C语言头文件(.h文件)中的首尾,使用条件编译语句(#ifdef
和#endif
语句)插入extern "C" {}
语句(共插入6行代码)。
#ifdef __cplusplus
extern "C"{
#endif
/*
C语言头文件的正常内容
*/
#ifdef __cplusplus
}
#endif
注:当C++编译器编译源文件时,会自带
__cplusplus
宏,表示某个文件采用C++编译方式。 对于一个*.cpp源文件,编译时会自动定义__cplusplus
宏;若需要兼容C语言代码,则应使用extern "C"
, 表示对代码采用C编译方式。
示例:
/* C语言头文件test.h */
//在C语言头文件中,使用条件编译插入extern "C"{}语句
#ifdef __cplusplus
extern "C"{
#endif
//C语言头文件的正常内容
#include <stdio.h>
void function();
#ifdef __cplusplus
}
#endif
/* C语言源文件test.c */
#include "test.h"
void function() {
printf("Hello C!");
}
/* C++源文件main.cpp */
#include <iostream>
using namespace std;
//在C语言头文件中使用条件编译插入extern "C"{}语句时,.cpp源程序需包含C语言头文件
#include "test.h"
int main() {
function();
return 0;
}