一. 简介
Day02,主要学习函数的类别及特点
二. 主要内容
1. 内联函数
内联函数是指用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。
a. 定义方式
使用关键字inline进行修饰函数。如下所示
inline int Add_int(int a, int b)
{
return a + b;
}
b. 运行原理
通过其概念可知,在实际问题中,在调用内联函数时并不会进行真正的调用,而是在调用处直接展开函数体,减少开栈清栈开销,需要注意的是内联函数的函数体执行开销需小于开栈清栈开销,否则不必定义为内联函数。
具体使用情况如下所示
inline int Add_int(int a, int b)
{
return a + b;
}
int main()
{
int c = 0;
c = Add_int(10, 56); // 此时会直接展开函数体,不会调用,即汇编层面不会call
cout << c << endl;
return 0;
}
在调用Add_int()函数时,在汇编层面不会执行call指令,而是直接将形参值相加并返回赋值。
2. 缺省函数
缺省函数是指在定义函数时为形参指定缺省值(即默认值)
a. 定义方式
在定义缺省函数时,需注意从右至左依次给出缺省值,具体如下所示
// 可全为缺省参数
void fun_1(int a = 1, int b = 2, int c = 3,int d = 4)
{
cout << a << b << c << d << endl;
}
// 部分为缺省参数,此情况符合要求
void fun_2(int a, int b = 2, int c = 3,int d = 4)
{
cout << a << b << c << d << endl;
}
// 部分为缺省参数,此情况不符合要求,无法编译通过
void fun_3(int a = 1, int b, int c = 3,int d = 4)
{
cout << a << b << c << d << endl;
}
b. 使用方式
调用缺省参数时,需从左至右依次给出实参,不可跳跃给出,如下所示
void fun(int a = 1, int b = 2, int c = 3,int d = 4)
{
cout << a << b << c << d << endl;
}
int main()
{
fun(1, 2, 3, 4);
fun(1, 2, 3);
fun(1, 2);
fun(1);
fun();
// fun(1, , 3, 4); // 此调用方式错误
return 0;
}
c. 注意事项
在多文件项目中,在声明中给出缺省值,在定义中不要给出缺省值;
另外缺省值可以为常量,也可以是其他函数返回值;
仅在C++中支持缺省函数
3. 函数重载
Tips:仅在C++中支持函数重载,C语言中不支持函数重载。
在C++中可以为两个或两个以上的函数提供相同的函数名称,只要参数类型不同,或者参数类型相同而参数个数不同,即为函数重载。
在C/C++函数在内部(编译和链接)通过修饰名识别,修饰名是指编译器在编译函数定义或者原型时生成的字符串。在C语言的编译过程中,对于函数编译时,仅在函数名前添加"_"作为修饰名,而在C++编译过程中,在函数编译时,修饰名格式比较复杂,会根据形参列表进行生成,可参考C++标准。也可强制名称修饰方式,即修改默认的修饰名生成方式。
a. 定义方式
如下函数my_max()分别为整形、字符型、double类型的两个参数取最大值。
// 整形最大值
int my_max(int a, int b)
{
return a > b ? a : b;
}
// 字符型最大值
char my_max(char a, char b)
{
return a > b ? a : b;
}
// double类型最大值
double my_max(double a, double b)
{
return a > b ? a : b;
}
在实际情况中,情况可能更为复杂,需根据实际情况进行定义
b. 调用方式
在调用时,需与形参列表相对应,程序会调用相应的重载函数
int main()
{
my_max(12, 23); // 调用整形重载
my_max('a', 'u'); // 调用字符型重载
my_max(12.23, 23.21); // 调用double重载
return 0;
}
c. 注意事项
1. 如果两个函数的参数表相同,但返回值不同,会被标记为编译错误:函数的重复声明。即定义重载函数时,要注意同一形参列表的函数有且仅有一个。
2. 参数表的比较过程与形参名无关。
3. 参数表是否给出缺省值与重载无关,即两个相同参数表中,一个给出缺省值,另外一个未给出缺省值,也会被标记为编译错误:函数的重复声明。
4. typedef的影响,typedef作用是为已有的参数类型创建新名称(马甲),即
typedef unsigned int u_int
unsigned int a;
u_int b;
// a和b是同一种类型的变量,即unsigned int
5. const和volatile的影响,形参表中若对同一类型的参数一个添加const或volatile修饰符,也不会构成函数重载,即在重载函数声明及定义时,若形参是按值传递方式定义,不考虑const和volatile修饰符;若形参为指针或引用的传递,此时const修饰符会影响函数重载,即构成函数重载,但要注意在声明与定义引用类型形参时,需避免普通形参的函数重载,此类情况会造成编译错误,即在声明与定义第二种重载方式时,避免第一种函数重载。
// void print(int a) {}
void print(int &b) {}
void print(const int& c) {}
但在实际情况中,虽然第二、三种函数重载方式共存时不会造成编译错误,但在实际调用时,第三种重载方式可兼容第二种函数重载,即即使不声明与定义第二种重载方式,仅有第三种重载方式,也可实现第一、二种函数调用的功能,总结为const修饰符的向下兼容性。
6. 重载函数调用的二义性,如下所示
void func(int a)
{
}
void func(int a, int b = 10)
{
}
int main()
{
// func(1); // error
func(1, 2);
return 0;
}
如上代码中,在主函数中的func(1)调用时,会产生二义性,即程序无法确定匹配一个参数的函数还是带缺省值的函数,即会编译错误。因此在函数重载的声明与定义时,尽可能的避免缺省值的添加。
7. 尽可能的避免相同形参个数,但不同类型的顺序不同方式的函数重载声明与定义,即
void print(int a, char ch)
{
}
void print(char ch, int a)
{
}
d. 重载解析步骤
1. 确定函数调用考虑的重载函数的集合,确定函数调用中实参表的属性;
2. 从重载函数集合中选择函数,该函数可以在(给出实参个数和类型)的情况下可以调用函数;
3. 选择并调用最匹配的和函数。
4. 函数模板
为了代码重用,代码就必须是通用的;通用的代码就必须不受数据类型的限制。因此将数据类型改为一个设计参数。这种类型的程序设计成为参数化程序设计。软件模板由模板template构造。包括函数模板function template和类模板class template。
函数模板可以用来创建一个通用函数的功能,以支持多种不同形参,简化重载函数的设计。
a. 定义方式
tempalte<模板参数表>
返回类型 函数名(形式参数表)
{
// 函数体
}
其中模板参数表不能为空,尖括号中不能为空,参数可以有多个,用逗号分开。
对上述重载函数的模板设计,如下所示
template <class T>
T my_max(T a, T b)
{
return a > b ? a : b;
}
b. 调用方式
在调用时,仅需成功匹配形参个数,即可调用该模板。
int main()
{
int x = my_max(12, 23);
char ch = my_max('a', 'x');
double dx = my_max(12.23, 34.56);
return 0;
}
在调用时,系统会根据实参类型自动生成相应的函数(比如在第一次调用时,会自动生成代码:typedef int T),从而进行调用。
c. 注意事项
在实际调用时,实参应为相同类型的参数,或者在调用时如下
int x = my_max<int>(12, 'b');
即在函数名后添加"<类型名>"强制以该类型生成函数。