第六章
函数基础
- 函数执行的第一步是隐式的定义并初始化他的形参。
- 如果局部静态变量没有显式的初始值,他将执行值初始化,内置类型的局部静态变量初始化为零。
- 在头文件中进行函数声明,含有函数声明的头文件应该被包含到定义函数的源文件中。
- 使用引用避免拷贝,效率高,而且某些类类型不支持拷贝操作。如果函数无需修改引用形参的值,声明为常量引用。
温习----指针引用与const
int i = 42;
const int *cp = &i; //正确:但是cp不能改变i
const int &r = i; // 正确:但是r不能改变i
const int &r2 = 42; //正确, 常量引用可以引用常量
int *p = cp; //错误: p的类型和cp的类型不匹配
int &r3 = r; //错误: r3的类型和r的类型不匹配
int &r4 = 42; //错误:不能用字面值初始化一-个非常量引用
尽量使用常量引用。
数组形参
-
为函数传递一个数组时,实际上传递的是指向数组元素的指针。
-
当函数不需要对数组元素执行写操作的时候,数组形参应该是指向const的指针。只有当函数确买要改变元素值的时候,才把形参定义成指向非常量的指针。
温习
-
int *matrix[10]; //10个指针构成的数组 int (*matrix)[10]; //指向含有10个整数的数组的指针
-
返回局部对象的引用是错误的。同样,返回指向局部对象的指针也是错误的。一旦函数完成,局部对象被释放,指钍将指向一个不存在的对象。
函数返回值
数组不能拷贝,所以函数不能返回数组。
声明一个返回数组指针的函数
//Type ( *function (parameter_ list) ) [dimension]
int (*func(int i))[10]; //函数返回指向int数组的指针
- func(int i) 表示调用func函数时需要一个int类型的实参。
- (*func(inti)) 意味着我们可以对函数调用的结果执行解引用操作。
- (*func(int i)) [10]表示解引用func的调用将得到一个大小是10的数组。
- int (*func(int i)) [10]表示数组中的元素是int类型。
使用尾置返回类型
// func 接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*) [10] ;
函数重载
-
不允许两个函数除了返回类型外其他所有的要素都相同。
-
一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来
Record lookup (Phone);
Record lookup (const Phone);//重复声明了Record lookup (Phone)
Record lookup (Phone*);
Record lookup (Phone* const);//重复声明了Record lookup (Phone*)
- 如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载
//对于接受引用或指针的函数来说,对象是常量还是非常量对应的形参不同
//定义了4个独立的重载函数
Record lookup (Account&); // 函数作用于Account 的引用
Record lookup (const Account&); // 新函数,作用于常量引用
Record lookup (Account*); //新函数,作用于指向Account的指针
Record lookup (const Account*); //新函数,作用于指向常量的指针
- const_cast:在下面这个版本的函数中,首先将它的实参强制转换成对const 的引用,然后调用了shorterString函数的const版本。const版本返回对const string 的引用,这个引用事实上绑定在了某个初始的非常量实参上。因此,我们可以再将其转换回一个普通的string&, 这显然是安全的。
string &shorterString(string &s1, string &s2){
auto &r = shorterString(const_cast<const string&>(s1),
const_cast<const string&> (s2) );
return const_cast<string&>(r);
}//发问:这样的意义是不重写一遍代码?
默认实参
- 一旦某个形参被赋予了默认值,则其后面的所有形参都必须有默认值。
- 只能省略尾部的默认实参,从右到左看。
- 默认实参可以多次声明,但不能修改一个已经存在的默认值。
string screen(SZ, SZ, char ='');
string screen(SZ, SZ, char = '*'); //错误:重复声明
string screen(sz = 24, sz = 80, char); //正确:添加默认实参
内联函数和constexpr函数
inline : 内联函数可避免函数调用的额外开销,但是只是像编译器发出的一个请求,编译器可以选择忽略它。
constexpr函数(constexpr function): 指能用于常量表达式的函数。
-
约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句。
-
为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数。
-
允许constexpr函数的返回值并非一个常量。
-
//如果arg是常量表达式,则scale(arg)也是常量表达式. constexpr size_t scale (size_t cnt) { return new_sz() * cnt; } //当scale的实参是常量表达式时,它的返回值也是常量表达式,反之则不然: int arr[scale(2)]; //正确: scale(2)是常量表达式 int i = 2; // i不是常量表达式 int arr[scale(i)]; //错误: scale(i)不是常量表达式
-
函数匹配
-
函数每个实参的匹配都不劣于其他可行函数需要的匹配。
-
至少有一个实参的匹配优于其他可行函数提供的匹配。
如果在检查了所有实参之后没有任何一个函数脱颖而出,则该调用是错误的。编译器将报告二义性调用的信息。
调用重载函数时应尽量避免强制类型转换。如果在实际应用中确实需要强制类型转换,则说明我们设计的形参集合不合理。
函数指针
声明函数指针
要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可。
// pf指向一个函数, 该函数的参数是两个const string 的引用,返回值是bool类型
bool (*pf)(const string &, const string &); //未初始化
使用函数指针
函数名作为值使用时,该函数自动转换为指针。
pf = func; //pf指向名为func的函数
pf = &func; //等价的赋值语句:取地址符是可选的
直接使用指向函数的指针调用该函数,无须提前解引用指针,但指向不同函数类型的指针间不存在转换规则。
bool b1 = pf("hel1o", "goodbye"); //调用func函数
bool b2 = (*pf)("hello", "goodbye"); //一个等价的调用
bool b3 = func("hello","goodbye"); // 另一个等价的调用
重载函数的指针
void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int) = ff; // pf1指向ff (unsigned)
//编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数中的某一个精确匹配
void (*pf2)(int) = ff; //错误:没有任何一个ff与该形参列表匹配
double (*pf3)(int*) = ff; //错误: ff和pf3的返回类型不匹配
typedef decltype(lengthCompare) *FuncP2; //指向函数的指针
decltype的结果是函数类型,所以只有在结果前面加上*才能得到指针。
返回指向函数的指针
-
使用类型别名,最简单。
using F = int (int*, int); // F是函数类型,不是指针 using PF = int (*)(int*, int); // PF是指针类型 PF f1 (int); //正确:PF是指向函数的指针,f1返回指向函数的指针 F f1 (int); //错误: E是函数类型f1不能返回一个函数 F *f1 (int); //正确:显式地指定返回类型是指向函数的指针
-
尾置返回
int (*f1(int))(int*, int); auto f1(int) -> int (*)(int*, int); //尾置返回
由内而外解析:首先f1带有形参列表, 是一个函数,(类比)直接调用一个函数就是使用其返回值,前面加了’*’,说明返回的是一个指针。进一步观察,返回的指针也带有参数列表,所以这个指针指向的是一个函数,且函数的返回类型是int。