int ival = 1024; int &refVal = ival; // ok: refVal refers to iVal int &refVal2; // error: a reference must be initialized int &refVal3 = 10; // error: initializer must be an object
constint ival = 1024; constint &refVal = ival; // ok: both reference and object are const int &ref2 = ival; // error: non const reference to a const object
引用形参
从 C 语言背景转到 C++ 的程序员习惯通过传递指针来实现对实参的访问。在 C++ 中,使用引用形参则更安全和更自然。
##### 引用形参 #####
在 C 语言中,一个交换两个元素的函数可能用指针来实现:
1 2 3 4 5 6
voidswap(int *p1, int *p2) { int tmp = *p2; *p2 = *p1; *p1 = tmp; }
但在 C++ 中,更好的做法是使用引用形参:
1 2 3 4 5 6
voidswap(int &v1, int &v2) { int tmp = v2; v2 = v1; v1 = tmp; }
// swap values of two pointers to int voidptrswap(int *&v1, int *&v2) { int *tmp = v2; v2 = v1; v1 = tmp; }
形参
1
int *&v1
的定义应 *从右至左理解* :v1 是一个引用,与指向 int 型对象的指针相关联。也就是说,v1 只是传递进 ptrswap 函数的任意指针的别名。
相应的 main 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
intmain() { int i = 10; int j = 20; int *pi = &i; // pi points to i int *pj = &j; // pj points to j cout << "Before ptrswap():\t*pi: " << *pi << "\t*pj: " << *pj << endl; ptrswap(pi, pj); // now pi points to j; pj points to i cout << "After ptrswap():\t*pi: " << *pi << "\t*pj: " << *pj << endl; return0; }
编译并执行后,该程序产生如下结果:
1 2
Before ptrswap(): *pi: 10 *pj: 20 After ptrswap(): *pi: 20 *pj: 10
// pass iterators to the first and one past the last element to print voidprint(vector<int>::const_iterator beg, vector<int>::const_iterator end) { while (beg != end) { cout << *beg++; if (beg != end) cout << " "; // no space after last element } cout << endl; }
// ok: parameter is a reference to an array; size of array is fixed voidprintValues(int (&arr)[10]){ /* ... */ } intmain() { int i = 0, j[2] = {0, 1}; int k[10] = {0,1,2,3,4,5,6,7,8,9}; printValues(&i); // error: argument is not an array of 10 ints printValues(j); // error: argument is not an array of 10 ints printValues(k); // ok: argument is an array of 10 ints return0; }
这个版本的 printValues 函数只严格地接受含有 10 个 int 型数值的数组,这限制了哪些数组可以传递。然而,由于形参是引用,在函数体中依赖数组的大小是安全的:
1 2 3 4 5 6 7
// ok: parameter is a reference to an array; size of array is fixed voidprintValues(int (&arr)[10]) { for (size_t i = 0; i != 10; ++i) { cout << arr[i] << endl; } }
``&arr` 两边的圆括号是必需的,因为下标操作符具有更高的优先级:
1 2
f(int &arr[10]) // error: arr is an array of references f(int (&arr)[10]) // ok: arr is a reference to an array of 10 ints
voidprintValues(constint *beg, constint *end) { while (beg != end) { cout << *beg++ << endl; } } intmain() { int j[2] = {0, 1}; // ok: j is converted to pointer to 0th element in j // j + 2 refers one past the end of j printValues(j, j + 2); return0; }
printValues 中的循环很像用 vector 迭代器编写的程序。每次循环都使 beg 指针指向下一个元素,从而实现数组的遍历。当 beg 指针等于结束标记时,循环结束。结束标记就是传递给函数的第二个形参。
调用这个版本的函数需要传递两个指针:一个指向要输出的第一个元素,另一个则指向最后一个元素的下一个位置。只要正确计算指针,使它们标记一段有效的元素范围,程序就会安全。
#### 方法3:显式传递表示数组大小的形参 ####
第三种方法是将第二个形参定义为表示数组的大小,这种用法在 C 程序和标准化之前的 C++ 程序中十分普遍。
用这种方法再次重写函数 printValues,新版本及其调用如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// const int ia[] is equivalent to const int* ia // size is passed explicitly and used to control access to elements of ia voidprintValues(constint ia[], size_t size) { for (size_t i = 0; i != size; ++i) { cout << ia[i] << endl; } } intmain() { int j[] = { 0, 1 }; // int array of size 2 printValues(j, sizeof(j)/sizeof(*j)); return0; }
// return plural version of word if ctr isn't 1 stringmake_plural(size_t ctr, conststring &word, conststring &ending) { return (ctr == 1) ? word : word + ending; }
我们可以使用这样的函数来输出单词的单数或复数形式。
这个函数要么返回其形参 word 的副本,要么返回一个未命名的临时 string 对象,这个临时对象是由字符串 word 和 ending 的相加而产生的。这两种情况下,return 都在调用该函数的地方复制了返回的 string 对象。
### 返回引用类型 ###
当函数返回引用类型时,没有复制返回值。相反,返回的是对象本身。例如,考虑下面的函数,此函数返回两个 string 类型形参中较短的那个字符串的引用:
// Disaster: Function returns a reference to a local object conststring &manip(conststring& s) { string ret = s; // transform ret in some way return ret; // Wrong: Returning reference to a local object! }
这个函数会在运行时出错,因为它返回了局部对象的引用。当函数执行完毕,字符串 ret 占用的储存空间被释放,函数返回值指向了对于这个程序来说不再有效的内存空间。
引用返回左值
返回引用的函数返回一个左值。因此,这样的函数可用于任何要求使用左值的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13
char &get_val(string &str, string::size_type ix) { return str[ix]; } intmain() { strings("a value"); cout << s << endl; // prints a value get_val(s, 0) = 'A'; // changes s[0] to A
Record lookup(const Account&); // find by Account Record lookup(const Phone&); // find by Phone Record lookup(const Name&); // find by Name Record r1, r2; r1 = lookup(acct); // call version that takes an Account r2 = lookup(phone); // call version that takes a Phone
/* Program for illustration purposes only: * It is bad style for a function to define a local variable * with the same name as a global name it wants to use */ stringinit(); // the name init has global scope voidfcn() { int init = 0; // init is local and hides global init string s = init(); // error: global init is hidden }