1.问题的引入
考虑下面不适宜复制实参的例子,该函数希望交换两个实参的值:
<span style="font-size:18px;">void swap(int v1, int v2)
{
int temp = v2;
v2 = v1;
v1 = temp;
}
int main()
{
int i = 10;
int j = 20;
cout << "Before swap():\ti: "<< i << "\tj: " << j << endl;
swap(i, j);
cout << "After swap():\ti: "<< i << "\tj: " << j << endl;
return 0;
}</span>
编译并执行程序,产生如下输出结果:
Before swap(): i: 10 j: 20
After swap(): i: 10 j: 20
这个例子期望改变实参本身的值。但是对于上述的函数定义,swap无法影响实参本身。执行swap时,只交换了实参的局部副本,而传递swap的实参并没有修改。
为了使swap函数以期望的方式进行工作,交换实参的值,需要将形参定义为引用类型:
<span style="font-size:18px;">// ok: swap acts on references to its arguments
void swap(int &v1, int &v2)
{
int tmp = v2;
v2 = v1;
v1 = tmp;
}</span>
与所有的引用一样,引用形参直接关联所绑定的实参,他是实参的一个别名,而不是这些对象所谓的副本。定义引用时,必须用与该引用绑定的对象初始化该引用。引用形参完全以相同的方式进行工作。每次调用该函数时,引用形参被创建并与相应的实参关联。
此时,调用该函数时,swap(i,j);
形参v1只是对象i的另一个名字,而v2则是对象j的另外一个名字。对v1的任何修改实际上也是对i的修改。所以此时可以完成两个值的交换任务。
从C语言背景转到C++的我们习惯通过传递指针来实现对实参的访问。在C++中,使用引用形参更安全和更自然。
2.使用引用形参返回额外的信息
通过对上例的讨论,可以理解如何利用引用形参让函数修改实参的值。引用形参的另一种用法是向主调函数返回额外的结果。函数只能返回单个值,但有些时候,函数有不止一个的内容需要返回。例如,定义一个 find_val 函数。在一个整型 vector 对象的元素中搜索某个特定值。如果找到满足要求的元素,则返回指向该元素的迭代器;否则返回一个迭代器,指向该 vector 对象的 end 操作返回的元素。此外,如果该值出现了不止一次,我们还希望函数可以返回其出现的次数。在这种情况下,返回的迭代器应该指向具有要寻找的值的第一个元素。如何定义既返回一个迭代器又返回出现次数的函数?可以定义一种包含一个迭代器和一个计数器的新类型。而更简便的解决方案是给 find_val 传递一个额外的引用实参,用于返回出现次数的统计结果:
<span style="font-size:18px;">// returns an iterator that refers to the first occurrence of value
// the reference parameter occurs containn a second return value
vector<int>::const_iterator find_val(
vector<int>::const_iterator beg,
vector<int>::const_iterator end,
<span style="color:#cc0000;"><strong> int value</strong></span>, // the value we want
<strong><span style="color:#ff0000;">vector<int>::size_type &occurs</span></strong>) // number of times it occurs
{
// res_iter will hold first occurrence, if any
vector<int>::const_iterator res_iter = end;
<span style="color:#ff0000;"><strong>occurs = 0;</strong></span> // set occurrence count parameter ///引用,即别名,我们可以在主函数体中查到
for ( ; beg != end; ++beg)
if (*beg == value) {// remember first occurrence of value
if (res_iter == end)
{ res_iter = beg;}
++occurs; // increment occurrence count
}
return res_iter; // count returned implicitly in occurs
}</span>
it = find_val(ivec.begin(), ivec.end(), 42, ctr);
调用 find_val 时,需传递四个实参:一对标志 vector 对象中要搜索的元素范围的迭代器,所查找的值,以及用于存储出现次数的size_type 类型对象。假设 ivec 是 vector<int>类型的对象,it 是一个适当类型的迭代器,而 ctr 则是 size_type 类型的变量。
调用后,ctr 的值将是 42 出现的次数,如果 42 在 ivec 中出现了,则 it将指向其第一次出现的位置;否则,it 的值为 ivec.end(),而 ctr 则为 0。
3. 利用const引用避免复制(const int&)
在向函数传递大型对象时,需要使用引用形参,这是引用形参适用的另一种情况。虽然复制实参对于内置数据类型的对象或者规模较小的类类型对象来说没有什么问题,但是对于大部分的类类型或者大型数组,它的效率太低了;此外,也会注意某些类类型是无法复制的。使用引用形参,函数可以直接访问实参对象,而无须复制它。
举个例子:<span style="font-size:18px;">// compare the length of two strings
bool isShorter(<strong><span style="color:#ff0000;">const string&</span></strong> s1, <span style="color:#ff0000;"><strong>const string&</strong></span> s2)
{
return s1.size() < s2.size();
}</span>
注意:如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为const引用。4.更灵活的指向const的引用
<span style="font-size:18px;">// function takes a non-const reference parameter
int incr(int &val)
{
return ++val;
}
int main()
{
short v1 = 0;
const int v2 = 42;
int v3 = incr(v1); // error: v1 is not an int
v3 = incr(v2); // error: v2 is const
<strong><span style="color:#ff0000;">v3 = incr(0); // error: literals are not lvalues</span></strong>
v3 = incr(v1 + v2); // error: addition doesn't yield an lvalue
int v4 = incr(v3); // ok: v3 is a non const object type int
}</span>
问题的关键在于:非const引用形参只能与完全同类型的非const对象关联!!!<span style="font-size:18px;">// returns index of first occurrence of c in s or s.size() if c isn't in s
// Note: s doesn't change, so it should be a reference to const
string::size_type find_char(<span style="color:#ff0000;"><strong>string &s</strong></span>, char c)
{
string::size_type i = 0;
while (i != s.size() && s[i] != c)
++i; // not found, look at next character
return i;
}</span>
if (find_char("Hello World", 'o')) // ...
虽然字符串字面值可以转换为string对象,但是上述的调用仍然会导致编译失败。尽管函数并没有修改这个形参的值,但是这样的定义带来的问题是不能通过字符串字面值调用函数。
应该说,更鼓励将不加以修改的实参定义为const引用,因为将形参定义为非const引用,将毫不必要地限制了这些函数的使用(比如字面值字符串查找某一元素)!
注意:应当将不需要修改的引用形参定义为const引用。普通的非const引用形参在使用时非常不灵活。非const形参既不允许const对象初始化,也不能用字面值或产生右值的表达式实参初始化,大大地限制了函数功能。
5.传递指向指针的引用
假设想编写一个与前面交换两个整数的 swap 类似的函数,实现两个指针的交换。已知需用 * 定义指针,用 & 定义引用。现在,问题在于如何将这两个操作符结合起来以获得指向指针的引用。这里给出一个例子:
<span style="font-size:18px;">// swap values of two pointers to int
void ptrswap(int* &v1, int* &v2)
{
int *tmp = v2;
v2 = v1;
v1 = tmp;
}</span>
形参: int* &v1;应该从右至左理解:v1是一个引用(别名),与指向int型对象的指针相关联。也就是说,v1只是传递金ptrswap函数的任意指针的别名。
<span style="font-size:18px;">int main()
{
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;
return 0;
}</span>
解释:形参实参关系可以理解为 int* &v1 (pi); 实际上,传进来了两个指针,最终完成的是指针的交换!
即指针的值被交换了。在调用 ptrswap 时,pi 指向 i,而 pj 则指向 j。在 ptrswap 函数中, 指针被交换, 使得调用 ptrswap 结束后, pi 指向了原来 pj所指向的对象。换句话说,现在 pi 指向 j,而 pj 则指向了 i。