6.1 函数基础
通过调用运算符来执行函数。函数的调用完成两项工作:用实参初始化函数的形参;将控制权转移给被调用函数。
当遇到一条 return 语句时函数结束执行过程。return 也完成两项工作:返回 return 语句中的值;将控制权从被调用函数转移回主调函数。
形参和实参
形参出现在函数定义的地方,实参出现在函数调用的地方。实参是形参的初始值,实参的类型必须与对应的形参类型相匹配。
任意两个形参都不能同名,而且函数最外层作用域中的局部变量也不能使用与函数形参一样的名字(外层局部变量被内部形参隐藏)。
6.1.1 局部对象
对象的生命周期是程序执行过程中该对象存在的一段时间。
形参和函数体内部定义的变量统称为局部变量。局部变量会隐藏在外层作用域中同名的其他所有声明中。
只存在于块执行期间的对象称为自动对象。
局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁。内置类型的未初始化局部变量产生未定义的值,内置类型的局部静态变量初始化(值初始化)为 0。
6.2 参数传递
形参的类型决定了形参和实参交互的方式。当形参是引用类型时,其对应的实参被引用传递或函数被传引用调用;当实参的值被拷贝给形参时,实参被值传递或函数被传值调用。
6.2.1 传值参数
初始化一个非引用类型的变量时,初始值被拷贝给变量,对变量的改动不会影响初始值。
6.2.2 传引用参数
通过使用引用形参,允许函数改变一个或多个实参的值。如果函数无需改变引用形参的值,最好将其声明为常量引用。
使用引用形参返回额外信息
一个函数只能返回一个值,引用形参为一次返回多个结果提供了途径。如下例 find_char 函数,返回在 string 对象中某个指定字符第一次出现的位置,同时,返回该字符出现的总次数。
//返回 s 中 c 第一次出现的位置索引
//引用形参 occurs 负责统计 c 出现的总次数
string::size_type find_char(const string &s, char c, string::size_type &occurs){
auto ret = s.size(); //第一次出现的位置
occurs = 0;
for(decltype(ret) i = 0; i != s.size(); ++i){
if(s[i] == c){
if(ret == s.size())
ret = i; //记录 c 第一次出现的位置
++occurs;
}
}
return ret; //隐式返回出现次数 occurs
}
6.2.3 const 形参和实参
顶层 const 作用于对象本身,用实参初始化形参时会忽略掉顶层 const。即形参有顶层 const 时,传递常量对象或非常量对象均可。
Note:不能用字面值初始化非常量引用,但可以用字面值初始化常量引用。尽量使用常量引用。
6.2.4 数组形参
数组有两个特殊性质:不允许拷贝数组;使用数组时通常会将其转换成指针。
管理数组实参的第一种方法是要求数组本身包含一个结束标记。如 C 风格字符串,其存储在字符数组中,并且在最后一个字符后面跟着一个空字符。
void print(const char *cp){
if(cp)
while(*cp)
cout << *cp++ << endl;
}
第二种方法是传递指向数组首元素和尾后元素的指针。
void print(const int *beg, const int *end){
while(beg != end)
cout << *beg++ << endl;
}
//int mian()...
int j[2] = {0, 1};
print(begin(j), end(j)); //#include <iterator>
第三种方法是专门定义一个表示数组大小的形参。
void print(const char ia[], size_t size){
for(size_t i = 0; i != size; ++i)
cout << ia[i] << endl;
}
//int main()...
int j[2] = {0, 1};
print(j, end(j) - begin(j));
当函数不需要对数组元素执行写操作的时候,数组形参应该是指向 const 的指针。
数组引用形参
//arr 是具有10个整数的整型数组的引用
void print(int (&arr)[10]){
for(auto elem : arr)
cout << elem << endl;
}
传递多维数组
多维数组其实是数组的数组,将多维数组传递给函数时,真正传递的是指向数组首元素的指针。由于处理数组的数组,首元素本身就是一个数组,指针是一个指向数组的指针。
//指向含有 10 个整数的数组的指针
void print(int (*matrix)[10], int rowSize){/* ... */}
void print(int matrix[][10], int rowSize){/* ... */}
6.2.5 main:处理命令行选项
int main(int argc, char *argv[]){...}
int main(int argc, char **argv){...}
第二个形参 argv 是一个数组,它的元素是指向 C 风格字符串的指针;第一个形参 argc 表示数组中字符串的数量。当实参传递给 main 函数之后,argv 的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为 0 。
Note:使用 argv 中的实参时,可选的实参从 argv[1] 开始,argv[0] 保存程序的名字。
6.2.6 含有可变形参的函数
initializer_list 形参
函数的实参数量未知且全部实参的类型相同,可以使用 initializer_list 类型的形参。initializer_list 对象中的元素永远是常量值,无法改变其对象中元素的值。
initializer_list<string> ls;
initializer_list<int> li;
向 initializer_list 形参中传递一个值的序列,必须把序列放在一堆花括号内。
//#include <initializer_list>
void err_msg(initializer_list<string> ls){
for(auto beg = ls.begin(); beg != ls.end(); ++beg)
cout << *beg << " ";
cout << endl;
}
//int main()...
string exp, act;
if(exp != act)
err_msg({"function", exp, act});
else
err_msg({"function", "ok"});
省略符形参
省略符形参只能出现在形参列表的最后一个位置。
void foo(parm, ...);
void foo(...);
6.3 返回类型和 return 语句
return 语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。
6.3.1 无返回值函数
没有返回值的 return 语句只能用在返回类型是 void 的函数中。void 函数最后一条语句后会隐式执行 return。
void 类型函数也能使用 return 返回 expression 的形式,此时的 expression 必须是另一个返回 void 的函数。
6.3.2 有返回值函数
只要函数的返回类型不是 void ,则该函数体内的每条 return 语句必须返回一个值。返回值的类型必须与函数的返回类型相同,或者能隐式转换成函数的返回类型。
Note:在含有 return 语句的循环后面应该也有一条 return 语句。
返回局部对象的引用或局部对象的指针都是错误的。
调用运算符的优先级与点运算符和箭头运算符相同,且符合左结合律。如果函数返回指针、引用或类的对象,可以使用函数调用的结果访问结果对象的成员。
const string &shorterString(const string &s1, const string &s2){
return s1.size() <= s2.size() ? s1 : s2;
}
auto sz = shorterString(s1, s2).size();
引用返回左值
调用一个返回引用的函数得到左值,其他返回类型得到右值。能为返回值为非常量引用的函数的结果赋值。
列表初始化返回值
函数可以返回花括号包围的值的列表。
6.3.3 返回函数指针
因为数组不能被拷贝,所以函数不能返回数组。函数可以返回数组的指针或引用,最直接的方法是使用引用别名。
typedef int arr[10]; //arr 是一个类型别名,它表示的类型是含有 10 个整数的数组
using arr = int[10]; //arr 的等价声明
arr *func(int i); //func 返回一个指向含有 10 个整数的数组的指针
声明一个返回数组指针的函数
不使用类型别名时,声明一个返回数组指针的函数形式如下。
//type (*function(parameter_list))[dimension]
int arr[10]; //arr 是含有 10 个整数的数组
int *p1[10]; //p1 是含有 10 个指针的数组
int (*p2)[10]; //p2 是一个指针,指向含有 10 个整数的数组
int (*func(int i))[10];
- func(int i) 表示调用 func 函数时需要一个 int 类型的实参。
- (*func(int i)) 表示可以对函数调用的结果执行解引用操作。
- (*func(int i))[10] 表示解引用 func 的调用将得到一个大小为 10 的数组。
- int (*func(int i))[10] 表示数组中的元素是 int 类型。
尾置返回类型
尾置返回类型跟在形参列表后面并以一个 -> 符号开头。
auto func(int i) -> int(*)[10]; //返回一个指向含有 10 个整数的数组的指针
6.4 函数重载
如果同一作用域内的几个函数名字相同但形参列表不同,称之为重载函数。编译器根据实参的类型确定应该调用哪一个函数。mian 函数不能重载。
- 重载的函数应该在形参数量或形参类型上有所不同。
- 不允许两个函数除了返回类型外其他所有要素相同。
重载和 const 形参
拥有顶层 const 的形参无法和没有顶层 const 的形参区分开。如果形参是某种类型的指针或引用,通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的 const 是底层的。
Record lookup(Account &);
Record lookup(const Account &);
传递一个非常量对象或者一个指向非常量对象的指针时,编译器会优先选择非常量版本的函数。
const_cast 和重载
const_cast 在重载函数的情景中最有用。const_cast 将底层 const 对象转换成对应的非常量类型,或者执行相反的转换。
调用重载的函数
函数匹配是指把函数调用与一组重载函数中的某一个关联起来的过程,也称为重载确定。调用重载函数时有三种可能的结果:
- 找到与实参最佳匹配的函数。
- 找不到任何一个函数与调用的实参匹配,编译器发出无匹配错误信息。
- 有多个函数可以匹配,但每个函数都不是最佳匹配。此时发生错误,即二义性调用。
6.4.1 重载与作用域
如果在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。
6.5 特殊用途语言特性
6.5.1 默认实参
形参在函数的多次调用中被赋予相同的值,将这个反复出现的值称为默认实参。
typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
string window;
window = screen();
window = screen(36, 128);
window = screen(36, 128, '#');
默认实参作为形参的初始值出现在形参列表中。一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。在给定的作用域中,一个形参只能被赋予一次默认实参。
局部变量不能作为默认实参。除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参。
6.5.2 内联函数和 constexpr 函数
内联函数 (inline) 可避免函数调用的开销。用于优化规模较小、流程直接、频繁调用的函数。
constexpr 函数用于常量表达式,返回类型和所有形参的类型都是字面值类型,而且函数体中必须有且仅有一条 return 语句(不一定返回常量表达式)。constexpr 函数被隐式指定为内联函数。
6.6 函数匹配
第一步:选定本次调用对应的重载函数集。集合中的函数称为候选函数。
第二步:考察本次调用提供的实参,从函数中选出能被这组实参调用的函数。新选出的函数称为可行函数。
第三步:从可行函数中选择与本次调用最匹配的函数。
Note:调用重载函数时应尽量避免强制类型转换。
6.7 函数指针
函数指针指向的是函数而非对象。声明一个指向函数的指针,只需用指针替换函数名即可。
bool lengthCompare(const string &, const string &);
bool (*pf)(const string &, const string &);
把函数名作为一个值使用时,该函数自动地转换成指针。
pf = lengthCompare;
pf = &lengthCompare; //等价表达
返回指向函数的指针
声明一个返回函数指针的函数,最简单的办法是使用类型别名。
using PF = int (*)(int *, int);
Note:将 decltype 作用于某个函数时,它返回函数类型而非指针类型,需要显示加上 * 以表明返回指针。
string::size_type lengthCompare(const string &, const string &);
decltype(lengthCompare) *getFunc(const string &);
练习题
来源:CSDN
作者:markscientist
链接:https://blog.csdn.net/markscientist/article/details/104677334