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

优点
(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;
}

猜你喜欢

转载自blog.csdn.net/newson92/article/details/112701640