一、函数的默认参数
在 C 语言 中,函数的参数必须由实参传递。而在 C++ 中,允许给函数的参数提供默认值。
1.1 默认值的规则
1. 调用函数时,传递了实参,就使用实参的值。
2. 没有传递实参时,使用默认值。
1.2 注意事项
1. 默认参数必须遵循靠右原则,即从左往右连续的参数可以有默认值,靠左的参数不能有默认值,否则会引发歧义。
2. 默认参数只能在声明处提供,不能同时在定义处重复默认参数。
1.3 示例代码
#include <iostream>
#include <string>
using namespace std;
void func_1(int val_1 = 10)
{
cout << val_1 << endl;
}
void func_2(int val_1 ,int val_2 = 10);
int main(int argc, char const *argv[])
{
func_2(10);
return 0;
}
void func_2(int val_1 ,int val_2)
{
cout << val_1 << endl;
}
运行结果如下:
二、函数哑元
2.1 占位参数
占位参数是在函数形参列表中出现,但没有具体使用的参数。
它在某些情况下可以作为一种占位符,使函数的调用更加灵活。
1. 在定义函数时,可以为某个或多个参数指定类型,但不需要给出参数名。
2. 占位参数仅用于占位,没有其他实际意义,但调用时必须填补占位参数。
2.2 语法
返回值类型 函数名 (数据类型) {}
使用场景
当我们进行自增、自减运算符 重载 会用到哑元进行占位置
2.3 示例代码
#include <iostream>
#include <string>
using namespace std;
void Func(int val ,int )
{
cout << val << endl;
}
int main(int argc, char const *argv[])
{
Func(10 ,10);
Func(10 ,10);
return 0;
}
运行结果如下:
三、函数重载
3.1 函数重载的概念
当封装功能相同、用法相同的函数时,只是因为参数的不同,就需要定义多个版本的函数。为了避免函数名重复导致的不便,C++ 提供了函数重载功能。
3.1.1 函数重载定义
允许定义多个同名的函数,但参数列表必须不同。
3.1.2 函数重载作用
函数名可以相同,提高代码的复用性,简化调用。
3.2. 函数重载的要求
3.2.1 同一个作用域下,不能出现二义性
3.2.2 函数名相同,形参列表必须不同。 类型不同、个数不同、顺序不同
3.2.3 函数重载对返回值没有要求。
注意: 函数的返回值不可以作为函数重载的条件。
3.3 函数重载的原理
在调用函数时,编译器会根据实参的类型和个数自动选择匹配的重载函数。
编译器原理:编译器在编译过程中,会根据每个函数的参数类型和个数生成唯一的函数名称。这一过程称为名字改编(Name Mangling)。虽然源代码中的函数名相同,但在底层,编译器通过名字改编机制确保每个重载函数都有不同的符号名称。
通过查看汇编代码的方式理解原理
可以通过生成汇编代码来查看重载函数的原理。使用如下步骤:
gcc -E xxx.cpp -o xxx.i # 预处理阶段,生成预处理文件
gcc -S xxx.i -o xxx.s # 生成汇编代码文件
在生成的汇编文件中,可以通过搜索关键字 globl
来获取重载函数在汇编代码中的名字。例如:
_Z6printi // 对应 print(int)
_Z6printd // 对应 print(double)
_Z6printid // 对应 print(int, double
这些经过名字改编后的符号表示编译器根据参数类型生成了唯一的函数名,确保不同重载函数在底层不会冲突。
3.4 函数重载的示例
3.4.1 代码示例一
#include <iostream>
#include <string>
using namespace std;
void Swap_Func(int & val_1 ,int & val_2)
{
int temp =val_2;
val_2 = val_1;
val_1 =temp;
}
void Swap_Func(double & val_1, double & val_2)
{
double temp = val_2;
val_2 = val_1;
val_1 = temp ;
}
int main(int argc, char const *argv[])
{
int val_int_1 = 50 ;
int val_int_2 = 70 ;
double val_double_1 =50.123;
double val_double_2 =70.123;
Swap_Func(val_int_1 ,val_int_2);
Swap_Func(val_double_1 ,val_double_2);
cout << val_int_1 << " " << val_int_2 << endl;
cout << val_double_1 << " " <<val_double_2 << endl;
return 0;
}
运行结果如下:
3.4.2 代码示例二
#include <iostream>
#include <string>
using namespace std;
void Swap_Func(int & val_1 ,int &val_2)
{
int temp = val_2;
val_2 = val_1 ;
val_1 = temp;
}
void Swap_Func(int & val_1 ,int &val_2 ,int val =10)
{
int temp = val_2;
val_2 = val_1 ;
val_1 = temp;
}
void Func(const int &val)
{
cout << "我是一号"<< val << endl;
}
void Func(int & val)
{
cout << "我是二号" << val << endl;
}
int main(int argc, char const *argv[])
{
int val_int_1 = 50;
int val_int_2 = 70;
Swap_Func(val_int_1 ,val_int_2 ,70);
Func(70);
return 0;
}
当修改Func()函数传参的内容,输出的结果会不一样
Func(70);
Func(val_int_1);
运行结果如下:
3.5. 函数重载的注意事项
3.5.1 引用作为重载条件
在 C++ 中,引用可以作为函数重载的条件。通过区分左值引用和 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() {
int a = 10;
func(a); // 调用非 const 左值引用的版本
func(10); // 调用 const 引用的版本(右值可以绑定到 const 引用)
return 0;
}
运行结果如下:
3.5.2 函数重载碰到函数默认参数
当函数重载遇到默认参数时,可能会产生二义性。编译器在调用函数时可能无法决定应该调用带默认参数的重载版本,还是另一个参数匹配的重载版本,因此会报错。
#include <iostream>
using namespace std;
void func2(int a, int b = 10) {
cout << "func2(int a, int b = 10) 调用" << endl; // 带默认参数的函数
}
void func2(int a) {
cout << "func2(int a) 调用" << endl; // 无默认参数的函数
}
int main() {
// func2(10); // 这行代码会产生二义性
func2(10, 20); // 直接传递两个参数,明确调用两个参数的版本
return 0;
}
运行成功的结果如下:
四、内联函数
内联函数通过直接展开代码,减少函数调用的跳转时间,提高执行速度,但可能会让程序变大。
4.1 定义方法
在函数前加上 inline
关键字。
4.2 要求
- 内联函数必须写在头文件(.h)。
- 函数体要尽量简短,太大的函数不适合做内联。
具体是否调用处展开,由编译器决定,我们决定不了
4.3 例子
// func.h
inline int my_add(int a, int b) {
return a + b;
}
**拓展** 宏定义与内联函数的区别
宏定义:
- 预处理阶段:宏定义是在预处理阶段完成文本替换。它并不是函数,编译器不会检查参数类型,只是简单的文本替换。
- 潜在问题:宏定义中的参数会被多次替换,可能导致副作用,如
++a
在宏中会执行两次。内联函数:
- 编译阶段:内联函数是在编译阶段处理的,编译器会根据内联函数的调用展开代码,类似于宏,但仍然保留了函数的类型检查和语法规则。
- 优势:内联函数不会像宏那样出现参数重复执行的问题,它的参数只会计算一次。