1.非引用形参
普通的非引用类型的参数通过复制对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。
2.指针形参
函数的形参可以是指针,此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。事实上被复制的指针只影响对指针的赋值。
如果保护指针指向的值,则形参需定义为指向 const 对象的指针:
void use_ptr(const int *p) { // use_ptr may read but not write to *p }
3.Limitations of Copying Arguments
复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:
-
当需要在函数中修改实参的值时。
-
当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过大。
-
当没有办法实现对象的复制时。
5.利用 const 引用避免复制
在向函数传递大型对象时,需要使用引用形参,这是引用形参适用的另一种情况。虽然复制实参对于内置数据类型的对象或者规模较小的类类型对象来说没有什么问题,但是对于大部分的类类型或者大型数组,它的效率(通常)太低了;此外,我们将在第十三章学习到,某些类类型是无法复制的。使用引用形参,函数可以直接访问实参对象,而无须复制它。
编写一个比较两个 string 对象长度的函数作为例子。这个函数需要访问每个 string 对象的 size,但不必修改这些对象。由于 string 对象可能相当长,所以我们希望避免复制操作。使用 const 引用就可避免复制:
// compare the length of two strings bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); }
其每一个形参都是 const string 类型的引用。因为形参是引用,所以不复制实参。又因为形参是 const 引用,所以 isShorter 函数不能使用该引用来修改实参。
如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为 const 引用。
应该将不需要修改的引用形参定义为 const 引用。普通的非 const 引用形参在使用时不太灵活。这样的形参既不能用 const 对象初始化,也不能用字面值或产生右值的表达式实参初始化。
6.vector 和其他容器类型的形参
通常,函数不应该有 vector 或其他标准库容器类型的形参。调用含有普通的非引用 vector 形参的函数将会复制 vector 的每一个元素。从避免复制 vector 的角度出发,应考虑将形参声明为引用类型。然而,事实上,C++ 程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器:
// pass iterators to the first and one past the last element to print void print(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; }
7.数组形参
(1)如果要编写一个函数,输出 int 型数组的内容,可用下面三种方式指定数组形参:
// three equivalent definitions of printValues void printValues(int*) { } void printValues(int[]) { } void printValues(int[10]) { }
注意:编译器忽略为任何数组形参指定的长度。
和其他类型一样,数组形参可声明为数组的引用。如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配:
// ok: parameter is a reference to an array; size of array is fixed void printValues(int (&arr)[10]) { } int main() { 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 return 0; }
这个版本的 printValues 函数只严格地接受含有 10 个 int 型数值的数组,这限制了哪些数组可以传递。然而,由于形参是引用,在函数体中依赖数组的大小是安全的:
&arr 两边的圆括号是必需的,因为下标操作符具有更高的优先级:
(3)对数组标界的处理
- 第一种方法是在数组本身放置一个标记来检测数组的结束。
- 第二种方法是传递指向数组第一个和最后一个元素的下一个位置的指针。
- 第三种方法是将第二个形参定义为表示数组的大小,这种用法在 C 程序和标准化之前的 C++ 程序中十分普遍。