C++函数进阶(内联函数、默认参数、占位参数、函数重载、extern “C“)


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++中,建议使用内联函数替代宏。

特点
①内联函数的函数体内容,被拷贝至内联函数调用处
②无需执行进入函数的步骤,直接执行函数体
③相较于宏,内联函数存在类型检查,具有函数特性;
④编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数;
⑤类内定义的成员函数,除虚函数外,均被自动隐式地内联
类内声明、类外定义的成员函数,需使用inline关键字显式地内联


优点
(1)保持宏函数的高执行效率,以空间换时间,无函数调用时入栈与出栈的时间开销。内联函数类似宏函数,在被调用处进行代码展开,省去参数压栈、栈帧开辟与回收、结果返回等开销。
(2)同普通函数,代码展开时会进行函数参数、返回值类型的安全检查自动类型转换
(3)类中声明并定义的成员函数,被自动转化为内联函数,可访问类的成员变量。
(4)内联函数在运行时可调试,宏定义不可以。

缺点
(1)导致代码膨胀。内联通过在拷贝函数体代码,消除函数调用开销。
若函数体代码的执行时间大于函数调用的开销,则效率提升不明显;
内联函数的所有调用处均会复制代码,使程序总代码量增大,消耗内存空间。
(2)内联函数无法随函数库一同升级,且内联函数的改变需重新编译,而非内联函数可直接链接。
(3)编译器决定函数是否内联,程序员不可控。


用法:在普通函数(非成员函数)的返回类型前使用inline关键字,且函数声明与函数定义需同时包含inline关键字,否则编译器仍视为普通函数。

示例

//头文件——函数声明
inline int func(int num);

//源文件——函数定义
inline int func(int num){
	return num;
}

注1:若头文件和源文件中使用内联函数,则头文件的函数声明源文件的函数定义,均需包含inline关键字。
注2:在类中定义的函数,均为内联函数,编译器默认加上inline关键字。
注3:C++编译器编译内联函数时的限制情况(不将函数以内联方式编译):
①不能存在任何形式的循环语句;(否则,执行函数体代码的开销远大于函数调用开销,内联意义不大)
②不能存在过多的条件判断语句;
③函数体代码不能过长;(每一处内联函数的调用均拷贝内联函数的定义,导致可执行代码增多,消耗内存
④不能对函数进行取址操作(如递归函数)。

总结
内联函数仅仅是对编译器的建议/请求,但编译器并不一定会采纳该内联建议/请求。
内联编译是编译器的行为,优秀的编译器会根据函数体的定义,自动取消不值得的内联,或自动内联无inline请求的函数(将未定义为内联函数的函数进行内联编译)。
编译器通常将简短且频繁的函数(内联候选函数)进行内联编译,通常无需用户主动定义为内联函数


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;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值