@taqikema
2018-01-02T12:13:19.000000Z
字数 6684
阅读 1276
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;
// 以某种方式改变一下 ret
if (!ret.empty())
return ret; // 错误:返回局部对象的引用
else
return "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);