函数基础
- 函数定义:返回类型、函数名、0个或多个形参组成的列表、函数体。
- 函数执行:调用运算符。
- 调用运算符作用于函数或指向函数的指针。
- 圆括号内为一个用逗号隔开的实参列表,用于对形参进行初始化。
- 调用表达式的类型即为函数的返回类型。
- 函数调用过程中,完成两项工作:
- 用实参初始化对应的形参。
- 将程序控制权转移给被调用函数。
- 形参与实参:
- 实参的求值顺序未被规定。
- 初始化过程中,实参应与形参类型关联。
- 函数的形参列表:
- 形参列表可为空。
- 任意两个形参不可同名,且不能与函数最外层作用域中的局部变量同名。
- 形参可以不命名,用来表示该形参在函数中不被使用,但此操作不影响实参个数。
- 函数的返回值:
- 可为void。
- 不可以是数组或函数,但可以是指向数组和指向函数的指针。
- 局部静态对象:在程序的执行路径第一次经过对象定义语句时初始化,直到程序终止时被销毁。
- 在类型声明前使用static关键字。
- 局部静态变量没有显式的初始值时,初始化为0。
- 函数声明:可以声明多次,但只能定义一次。
参数传递
- 调用类型:根据形参是否为引用分为引用传递(传引用调用)和值传递(传值调用)。
- 参数类型:
- 传值参数:初始值被拷贝给变量,此时对变量的改动不会影响初始值。
- 指针形参:可以通过指针间接访问其所指的对象。
- 传引用参数:形参与实参绑定,函数可以通过改变形参来间接改变实参。
- 使用引用,避免拷贝,更加高效。(无需修改参数时,使用常量引用)
- 使用引用参数可以返回额外信息。
- const类型的形参与实参:实参初始化形参时会忽略顶层const限制。
//需要注意:
void func(const int i); //func能够读取i,但不能向i写值。
void func(int i); //由于顶层const被忽略,此处属于重复定义。
- 数组形参:为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
- 为函数提供数组尺寸信息的方法:
- 使用标记指定数组长度:C风格字符串中的结束符。
- 使用标准库规范:传递指向数组首元素和尾后元素的指针(begin、end函数)。
- 显示传递一个表示数组大小的形参:size_t类型。
- 数组引用形参:形参可以是数组的引用。
- 多维数组:数组第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略。
- 含有可变数目形参的函数:如果实参类型相同,可传递名为initializer_list的标准库类型;如果实参类型不同,则使用可变参数模板(后续介绍)。
- initializer_list可提供的操作:
initializer_list<T> lst; //默认初始化,T类型元素的空列表。
initializer_list<T> lst{a, b, c...}; //lst元素数量与初始值一样多,lst的元素是对应初始值的副本,列表中元素为const。
lst2(lst);
lst2 = lst; //拷贝或赋值一个initializer_list对象不会拷贝列表中的元素,拷贝后,原始列表和副本共享元素。
lst.size(); //列表中的元素数量。
lst.begin(); //返回指向lst首元素的指针。
lst.end(); //返回指向lst尾后元素的指针。
//Example
void error_msg(initializer_list<string> il);
//调用形式
if(expected != actual)
error_msg({"functionX", expected, actual});
else
error_msg({"functionX", "Okay"});
返回类型和return语句
- return语句形式:
- return ;
- return expression ;
- 无返回值函数:无返回值的return语句只能用在返回类型是void的函数中。
- 无返回值的函数不要求一定return语句,函数会隐式执行。
- 使用有返回值的return语句时,要求expression必须是另一个返回值为void的函数,强行令void函数返回其他类型的表达式将引发编译错误。
- 有返回值函数:
- 非void的函数必须返回一个值。
- 未返回值便结束函数执行将引发未定义行为。
- 返回的之用于初始化调用点的一个临时量,该临时量即为函数调用的结果。
- 不要返回局部对象的引用或指针。
- 返回引用的函数将返回左值,但只有返回非常量引用时才可对其赋值。
- 函数可以返回花括号包围的值的列表。
- 返回值为int的main()函数可以没有return语句,编译器将隐式插入
return 0;
。
- main()函数返回0值表示执行成功,其他值表示执行失败。
- 递归:函数调用自身。
- 返回数组指针方法:
- 使用类型别名。
-
Type (*function(parameter_list))[dimension]
。
- 使用尾置返回类型:
auto function(parameter_list) -> Type(*)[dimension]
- 已知返回的指针将指向哪一个数组时,使用decltype。
int odd[] = {1, 3, 5, 7, 9};
int even[] = {2, 4, 6, 8, 10};
decltype(odd) *arrPtr(int i) //decltype并不会将数组类型转化为对应的指针,所以声明中需要加*符号。
{
return (i % 2) ? &odd : &even;
}
函数重载
- 重载函数的形参数量、形参类型应有所不同,返回值类型不同不能作为重载函数。
- 函数重载时,形参的顶层const属性会被忽略,但底层const属性不会被忽略。
- const_cast在函数重载中的应用:
//比较两个string的长度,返回较短者的引用
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
//要求在实参不是常量时,得到普通引用
string &shorterString(string &s1, string &s2)
{
auto &r = shorterString(const_cast<const string&>(s1), const_cast<string &>(s2));
return const_cast<string &>(r);
}
- 调用重载函数时的可能结果:最佳匹配,无匹配,二义性调用。
特殊用途语言特性
- 默认实参:
- 一旦某个形参被赋予默认值,其后出现的所有形参均应该有默认值(合理设置形参顺序)。
- 给定作用域内,在函数的多次声明中,一个形参只能被赋予一次默认实参。
- 局部变量不能作为默认实参。
string screen(sz, sz, char = ' ');
string screen(sz, sz, char = '*'); //False
string screen(sz = 24, sz = 80, char); //True
- 内联函数:返回类型前加上inline关键字,声明为内联函数,减少调用开销。
- 适用于规模较小、流程直接、频繁调用的函数。
- 内联只是向编译器发送的一个请求,编译器可以忽略此请求。
- constexpr函数:
- 函数的返回类型及所有形参的类型均应为字面值类型。
- 函数体中有且只有一条return语句。
- 编译器将constexpr函数隐式指定为内联函数。
- 允许constexpr函数的返回值为非常量。
- 内联函数和constexpr函数一般放在头文件内。
- 调试帮助:
- assert预处理宏:
assert(expr)
,expr为0时,assert输出信息并终止程序执行,expr非0时,不执行操作。
- NDEBUG预处理变量:如果定义了NDEBUG,则assert不执行任何操作。
-
_ _func_ _
:当前调试的函数名字。
-
_ _FILE_ _
:存放文件名的字符串字面值。
-
_ _LINE_ _
:存放当前行号的整型字面值。
-
_ _TIME_ _
:存放文件编译时间的字符串字面值。
-
_ _DATE_ _
:存放文件编译日期的字符串字面值。
函数匹配
//Example
void f(); //(1)
void f(int); //(2)
void f(int, int); //(3)
void f(double, double = 3.14); //(4)
- 确定候选函数和可行函数:
- 候选函数:重载函数集,需与被调用函数同名且其声明在调用点可见。
- 可行函数:可被调用这组实参调用的函数,要求形参数量与本次调用提供的实参数量相同,且类型相关。
- 寻找最佳匹配:
- 实参类型与形参类型越接近,匹配越好。
- 含有多个形参的函数最佳匹配需要满足该函数的每个实参的匹配都不劣于其它可行函数需要的匹配,且至少有一个实参的匹配优于其它可行函数提供的匹配。
- 调用错误:无匹配函数、二义性调用。
- 实参类型转换等级:
- 精确匹配,包括:
- 实参与形参类型相同。
- 实参从数组、函数类型转化为对应的指针类型。
- 向实参增删顶层const。
- 通过const转换实现的匹配。
- 通过类型提升实现的匹配。
- 通过算数类型转换或指针转换实现的匹配。
- 通过类类型转换实现的匹配。
函数指针
- 形式:
returnType (*function)(parameter_list)
- 使用函数指针:
- 当函数名作为一个值使用时,函数自动转化为指针(即取地址符可选)。
- 可以直接通过指针调用函数,无需解引用指针。
- 指向不同类型的函数的指针之间无转换规则。
- 函数指针可赋值
0
或nullptr
。
- 重载函数指针:指针类型必须与重载函数中的某一个精确匹配。
- 函数指针形参:可以使用类型别名或decltype简化代码。
void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &));
//等价声明
void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &));
//Func、Func2是函数类型
typedef bool Func(const string &, const string &);
typedef decltype(lengthCompare) Func2;
//FuncP、FuncP2是指向函数的指针
typedef bool (*FuncP)(const string &, const string &);
typedef decltype(lengthCompare) *FuncP2;
//useBigger的等价声明
void useBigger(const string &s1, const string &s2, Func);
void useBigger(const string &s1, const string &s2, Func2);
void useBigger(const string &s1, const string &s2, FuncP);
void useBigger(const string &s1, const string &s2, FuncP2);
- 返回指向函数的指针:
//直接定义
int (*f(int))(int *, int);
//类型别名
using F = int(int *, int);
using PF = int(*)(int *, int);
PF f(int);
F *f(int);
//尾置返回
auto f(int) -> int (*)(int *, int);
- 使用auto和decltype可简化代码书写。