「C++」 函数
函数声明
函数在调用前应先声明或定义
声明给出了函数原型 ( Function Prototype ),即函数名、返回值类型、参数列表
1 |
|
- 原型中的参数名是可选的,只需指出类型
函数定义
1 |
|
返回值类型为 void 的函数无返回值,可以没有 return 语句,或者使用
return;
形参和实参
形参 ( parameter ): 函数定义中参数列表中的参数,是函数中的局部变量,对外不可见
实参 ( argument ): 函数调用时实际给函数传入的参数
为什么要使用函数原型
假设不使用函数原型,而是直接使用函数定义,当一个源文件中的一组函数存在互相调用关系时,就必然导致至少一个函数在调用前未定义。例如:
1
2
3
4
5
6函数1(){
if( 条件a ) 调用函数2; // 错误,函数二调用前未定义
}
函数2(){
if( 条件b ) 调用函数1;
}
对于单个源文件的程序,通常将函数声明放在 main 函数之前,函数定义放在 main 函数之后。对于大型的程序,则要考虑分文件编写
函数的分文件编写
创建后缀名为.h的头文件
在头文件中包含函数需要包含的头文件
在头文件中写函数的声明
创建后缀名为.cpp的源文件
在源文件中包含对应的头文件
在源文件中写函数的定义
函数调用
传值调用、传址调用、引用调用
引用注意事项:
引用在定义时必须初始化
int& b = a;
引用初始化后,不能改变
区别:
传值
实参和形参是两个不同的地址空间
传址
实参是变量的地址,形参是一个指针,函数内可以通过指针修改其所指的值
1
2
3void fun(int* a){...;}
fun(&b);将数组名传递给函数实际传递该数组首元素的地址,函数定义中形参
char s[]
和char *s
等价传递多维数组时,需要指明除第一维之外其他维的长度
传引用
形参是实参的别名,对形参的操作就是对实参的操作
1
2
3void fun(int& a){...;}
fun(b);
引用的本质: 常指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//发现是引用,转换为 int* const ref = &a;
void func(int& ref){
ref = 100; // ref是引用,转换成 *ref = 100;
}
int main(){
int a = 10;
//转换为 int* const ref = &a;
int& ref = a;
ref = 20; // 转换为 *ref = 20;
func(a);
return 0;
}
返回引用
注意: 不要返回局部变量的引用
1
2
3
4
5
6
7
8int& fun(){
/*以下是错误的
int a = 10;
return a;
*/
static int a = 10; //静态变量
return a;
}函数的调用可以作为左值
1
2
3
4
5
6
7
8
9
10
11int vals[] = {1,2,3,4,5};
int& setValues(int i){
return vals[i]; // 返回第i个元素的引用
}
int main() {
// 返回引用的函数可以做左值
setValues(1) = 7;
setValues(3) = 9; //vals: 1,7,3,9,5
}
常量引用
用来修饰形参,防止误操作
1 |
|
1 |
|
函数递归
待更新…
函数指针
函数的二进制代码存放在代码段,函数的地址是它在内存中的起始地址。如果把函数的地址作为参数传递给函数,就可以在函数中灵活的调用其他函数
函数指针就是一个指向函数首地址的指针
声明函数指针
声明函数时,要提供函数的类型,即返回值和参数列表
1 |
|
参数列表只需指明类型,不用写变量名
例如:int Max(int a, int b);
的函数指针: int (*pf)(int, int);
void printData(int (*pf)(int, int), int a, int b);
的函数指针: void (*pD)(int (*)(int, int), int, int);
使用自动类型推导创建函数指针:
1 |
|
给函数指针赋值
函数名就是函数的地址,用函数名给函数指针赋值
1 |
|
通过函数指针调用函数
两种方式调用
pf(1);
(*pf)(1);
回调函数
回调函数:Callback 即 call then back. 回调函数是指通过参数将函数传递到其它代码的,某一块可执行代码的引用。
例一:
1 |
|
例二:多线程
1 |
|
typedef 和函数指针
1 |
|
如何理解:把 typedef
后面内容中的 PFUNC
替换成 pf
, 然后整体拿到等号左边
万能指针充当函数指针
- 万能指针:空类型的指针
void*
- 万能指针能操作任何类型,在使用前必须做强制类型转换
1 |
|
万能指针做函数指针的例子:
1 |
|
自动类型推导
auto
- auto 可用于推导变量的类型,使用 auto 声明的变量必须初始化
- auto的自动类型推导发生在编译期,不会造成运行效率的降低
1 |
|
注:
- auto 可以和
&
,const
结合使用 - auto 不能用于推导数组类型
从 C++ 14 开始,auto 可以用于推导函数的返回类型
1 |
|
从 C++ 20 开始,auto 可以用于函数形参
1 |
|
decltype
待更新…
Lambda 表达式
使用 lambda 表达式可以就地封装短小的功能闭包,使代码简洁,增强可读性
Lambda 的类型是无名的非联合非聚合类类型,称为闭包类型(closure type)。这个类是具有 operator()
的类,使用方式类似于函数调用
语法:
[ capture ]( params ) -> ret_type { body };
- 返回值类型可省略,由编译器推导
- 如果要在其他地方使用,可以使用
function
存储 Lambda 表达式,例如std::function<int(int)> func = [](int x){return ++x;};
或者auto func = [](int x){return ++x;};
- 捕获列表:
[=]
表示按值捕获所有变量[n]
表示按值捕获变量n[&]
表示按引用捕获所有变量[&n]
表示按引用捕获 n- C++ 14 起允许在捕获列表中定义新的变量,不写类型,类型由编译器自动推导
捕获列表为空的 lambda 表达式会生成转换函数,可以隐式转换为函数指针类型
1 |
|
lambda 表达式的 operator()
默认是 const 的,这意味着按值捕获的变量在其中不可被修改。
若在声明时使用 mutable 关键字,则 operator()
是非 const 的,按值捕获的变量可以在 lambda 表达式内部被修改,但不会影响外界变量
1 |
|
按引用捕获的变量可以被修改,相当于修改外界变量
例如:
1 |
|
相当于:
1 |
|
例1:
1 |
|
例2:使用std::sort
( 定义于头文件 <algorithm>
) 对数组元素进行排序
1 |
|
泛型 Lambda
从 C++ 14 开始,Lambda 函数的形参可以使用 auto 来产生泛型
1 |
|
内联函数 (inline function)
内联函数与宏(macro) 的比较:
宏的缺点1:容易出错
1
2
3
4
#define MYADD(x,y) ((x)+(y))
// 若 x+y 处不加括号,那么会先计算乘法,ret = 10 + 20 * 20
int ret = MYADD(10,20) * 20;即使加了足够多的括号,有些情况依然与预期效果不符
1
2
3
4
5#define MYCOMPARE(a,b) (((a)<(b))?(a):(b))
int a = 10, b = 20;
//宏函数展开时,有两次++a
int ret = MYCOMPARE(++a,b); //ret的值为12宏的缺点2:不可调试
内联函数的优点:
- 将函数代码在调用处展开,避免了函数调用的开销,以空间换时间
- 可调试:定义为内联的函数在程序的 Debug 版本中不会内联,像普通函数一样便于调试
说明符:inline
(加在函数定义之前)
1 |
|
通常对较短的函数使用内联,可在函数声明处直接写函数的定义
在类内部定义的函数自动成为内联函数
内联函数应在头文件中定义。编译器在调用点内联展开函数的代码时,必须能够找到内联函数的定义才行,因此头文件中仅有函数声明是不够的。
如果头文件仅有声明,那么在每个使用到该内联函数的源文件中,都要对该内联函数进行定义,而且每个源文件里的定义必须完全相同。
在函数定义前加 inline 仅仅是给编译器的一个优先内联的建议,编译器不一定会接受这种建议,而没有声明为内联函数的,编译器也可能自动将此函数做内联编译。一个好的编译器会内联小的、简单的函数
默认参数 (default argument)
函数的默认参数通常设置在函数原型中
int add(int, int = 0, int = 0)
默认参数的值必须是常数值或常量
在参数列表中,带默认参数的形参必须放在后面,即假如某个参数有默认值,那么其后所有参数都必须有默认值
在函数调用时,实参只能从右往左省略,避免出现二义性
函数的默认参数应在函数名称最早出现时设置,如果不使用函数原型,那么也可以在函数定义的函数头中设置
1 |
|
占位参数
1 |
|
1 |
|
函数重载 (overload)
作用: 函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同,或者个数不同,或者顺序不同
注意: 函数的返回值不可以作为函数重载的条件
注意事项:
引用作为重载的条件
1
2
3
4
5
6
7
8
9
10
11void func(int& a){
cout << "func(int& a)调用" << endl;
}
void func(const int& a){
cout << "func(const int& a)调用" << endl;
}
int main(){
int a = 10;
func(a); // 优先调用无const: func(int& a)
func(10); // 调用有const: func(const int& a)
}函数重载碰到默认参数
应避免出现二义性的情况
1
2
3
4
5
6
7
8
9
10
11int add(int a){
return a;
}
int add(int a, int b = 0){
return a + b;
}
int main(){
cout << add(1); // 错误,有多个匹配,函数调用有歧义 ( ambiguous )
cout << add(1,2); // 调用第二个版本
}