@taqikema
2018-01-02T04:13:19.000000Z
字数 6684
阅读 1526
C++Primer 学习记录 函数相关

int a = fun(i, ++i); // 错误:传递进来的实参不能对其它实参有副作用!
int a = fcn(const int i); // fcn能够读取 i,但是不能修改 i的值
void fcn(const int i);void fcn(int i); // 错误,函数重定义!
尽量使用常量引用,表示该函数不会改变该形参。因为将函数定义成普通引用有以下缺点:
数组不允许拷贝,所以无法以值传递的形式传递数组参数;使用数组时通常会将其转换成指针,所以当为函数传递一个数组参数时,实际传递的是指向数组首元素的指针。数组的大小对函数的调用没有影响。
// 尽管形式不同,但三个 print函数是等价的,每个形参都是 const int*类型void print(const int *);void print(const int[]); // 此函数的意图是作用于一个数组void print(const int[10]); // 这个维度表示我们期望的输入数组有多少个元素,实际并不一定!int i = 0, j[2] = {0, 1};print(&i); // 正确,即使参数只是一个单独的 int类型print(j); // 正确
// 形参是数组的常量引用,维度是类型的一部分void print(const int (&arr) [10]);int i = 0, j[2] = {0, 1};print(&i); // 错误,实参不是含有 10个整数的数组print(j); // 错误,实参不是含有 10个整数的数组
int main(int argc, char *argv[]) {...}int main(int argc, char **argv ) {...}
在上面两个表达式中,argv是一个数组,它的元素是指向 C风格字符串的指针,而 argv又可以看成是指向首元素的指针,因此 argv就是一个二级指针,所以也就有了第二个表达式的写法。
在使用 argv的实参时,可选的实参从 argv[1]开始;argv[0]保存的是程序的名字,而非用户输入。
9. 为了编写处理不同数量实参的函数,C++11新标准提供了两种方法:所有实参类型相同,使用 initializer_list;实参类型不同,使用可变参数模板,然后实例化即可。另外,对于与C函数交互的接口程序,省略符形参(...)。可变参数符号与其它特定参数一起出现时,必须在最右边。
10.initializer_list提供了对一系列相同类型元素的轻量级存储和访问的能力,值初始化后列表中的元素永远是常量值。在拷贝或赋值时,执行的也是“类指针拷贝”,原始列表和副本共享元素。
int a = fcn(5);// 上式等价于int tmp = fcn(5); // 在调用点定义并初始化一个临时量int a = tmp; // 执行 int类型的拷贝构造函数
const string &manip(){string ret;// 以某种方式改变一下 retif (!ret.empty())return ret; // 错误:返回局部对象的引用elsereturn "Empty"; // 错误:字符串字面值还是会被转换成一个临时 string对象}
char &get_val(string &str, string::size_type ix){return str[ix];}string s("value");get_val(s, 0) = 'V'; // 将 s改成 "Value"
int (*func(int i))[10] // func函数返回指向 10个 int组成的数组的指针
using arrT = int[10];arrT* func(int i);
C++11新标准中还可以使用尾置返回类型来简化上述函数声明。下式就可以很清楚地看到 func函数返回的是一个指针,且该指针指向了含有 10个整数的数组。
auto func(int i) -> int(*)[10];
另外,如果已经有返回值类型的数组存在,可以使用 decltype关键字声明返回类型。
int arr[10] = {1,2};decltype(arr) *func(int i);
不过,需要注意,decltype的返回类型是数组类型,要想表示返回类型为指向数组的指针,必须加上一个 *符号。
int func(int i);double func(int i); // 错误,无法重载仅按返回类型重载的函数
Record lookup(Account &); // 实参为非常量对象时,优先调用此版本Record lookup(const Account &); // 实参为常量对象时,只能调用此版本
对于第二个表达式,实参为常量/非常量对象,都是可以的。但是如果两种表达式都存在,且实参为非常量对象时,会优先调用第一个非常量版本。因为第一个表达式为精确匹配,而第二个表达式则需要将非常量类型转化为常量类型。
3. 在 C++语言中,名字查找发生在类型检查之前。在内层作用域中声明的名字将会隐藏外层作用域中的同名实体。
string read();void print(const string &s);void main(){bool read = false;string s = read(); // 错误:read是一个 bool类型,而非函数void print(int);print("value"); // 错误:print(const string &s)被隐藏掉了}
// wd、def和 ht的声明必须出现在函数之外sz wd = 80;char def = ' ';sz ht();string screen(sz = ht(), sz = wd, char = def);string window = screen(); // 调用 screen(ht(), 80, ' ')
另外,用作函数默认实参的名字在函数声明所在的作用域内解析,及确定默认实参到底是用哪些名字,而这些名字的求值过程则发生在函数调用时。可以看下面这个例子。
void f2(){def = '*'; // 改变了默认实参的值sz wd = 100; // 隐藏了外层作用域的 wd,但是默认实参使用的// 是外层作用域中的 wd,所以对于函数调用没有影响!window = screen(); // 调用 screen(ht(), 80, '*')}
bool compare(int i, int j); bool (*pf)(int i, int j); pf = compare; 等价于 pf = &compare; bool b1 = pf(1, 2); 等价于 bool b2 = (*pf)(1, 2);void print(const int[10])类似,函数看起来是函数(数组)类型,但实际上却是当成指针使用。所以下面两个表达式都是可以的。
void useBigger(int i, int j, bool compare(int i, int j)); // 形参是函数类型,但会自动地转换成相应的函数指针void useBigger(int i, int j, bool (*pf)(int i, int j)); // 显式地将形参定义成函数指针
注意,对于上面两个表达式,在其之前是否已经声明了 compare和 pf,不会对其产生任何影响。因为作为形参, compare或 pf只是形参的名字,与之前已经声明的同名名字没有关系。另外,作为形参表达式,整体的意义是一个类型。所以使用类型别名可以简化代码,增强可读性。
// Func和 Func2是函数类型typedef bool Func(int i, int j);typedef decltype(compare) Func2;// FuncP和 FuncP2是函数指针类型typedef bool (*FuncP)(int i, int j);typedef decltype(compare) *FuncP2;// useBigger的等价声明void useBigger(int i, int j, Func); // 形参是函数类型,但会自动地转换成相应的函数指针void useBigger(int i, int j, FuncP2);
int (*f1(int)) (int*, int);using PE = int(*)(int*, int); PE f1(int);auto f1(int) ->int(*)(int*, int);int f(int*, int); decltype(f) *f1(int);