转 c++引用参数深入分析

来源:互联网 发布:js获取元素的父节点 编辑:程序博客网 时间:2024/06/04 01:23

转自:http://www.cnblogs.com/charley_yang/archive/2010/12/14/1905161.html

把参数声明成引用,实际上改变了缺省的按值传递参数的传递机制,在按值传递时,函数操纵的是实参的本地拷贝。
              

一、引用参数的三种常见用法:


1.需要改变实参的值,比如swap()。参数是引用时,函数接收的是实参的左值而不是值的拷贝。这意味着函数知道实参在内存中的位置,因而能够改变它的值或取它的地址。 
2.向主调函数返回额外的结果。如下例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<font size="4" face="宋体">/*********************************************************
* Description:  测试引用传递的第二种使用方式
* Author:charley
* DateTime:2010-12-13 23:00
* Compile Environment:win7 32 位 +vs2008
 
***********************************************************/
 
#include <iostream>
#include <vector>
using namespace std;
 
// 引用参数 'occurs' 可以含有第二个返回值
vector<int>::const_iterator look_up( const vector<int> &vec,
                                     int value,// 值在 vector 中吗?
                                     int &occurs ) // 多少次?
{
     // res_iter 被初始化为最后一个元素的下一位置 
    vector<int>::const_iterator res_iter = vec.end();
    occurs = 0;
    for ( vector<int>::const_iterator iter = vec.begin(); iter != vec.end(); ++iter )
    {
        //判断是否存在value
        if ( *iter == value )
        {
            //只有第一次找到value时,该项才成立;
            //找到第二个value时res_iter已经被下一步赋值了,该项不成立
            //达到在多次出现value的情况下,指向第一次出现的iterator 被返回
            if ( res_iter == vec.end() )
                res_iter = iter;
            ++occurs;
        }
    }
 
    //如果找不到该value值,返问一个指向vector 最后一个元素下一位置的iterator
    return res_iter;
}
 
/**
* 调用look_up方法
*/
int main_test6(){
    vector<int> vInts;
    int iVal;
    cout<<"请输入数字,按Ctrl+Z结束:"<<endl;
    while(cin>>iVal)
        vInts.push_back(iVal);
 
    if(vInts.size()==0){
        cout<<"没有元素。"<<endl;
        return -1;
    }
 
    cout<<"您输入的结果如下:"<<endl;
    for(vector<int>::const_iterator iter = vInts.begin();iter!=vInts.end();iter++)
        cout<<*iter<<"\t";
    cout<<endl;
     
    int occurs = 0;
    //查找该容器中是否含有值2。
    vector<int>::const_iterator resIter = look_up(vInts,2,occurs);
 
    if(!occurs)
    {
        cout<<"容器中不含2。"<<endl;
    }
    else
    {
        cout<<"容器中2出现了:"<<occurs<<"次,*iterator为:"<<*resIter<<endl;
    }
    return 0;
}
 
</font>

3.向函数传递大型的类对象。例如:

class Huge { public: double stuff[1000]; }; 
extern int calc( const Huge & ); 
int main() { 
Huge table[ 1000 ]; 
 
// ... 初始化 table 
int sum = 0; 
for ( int ix=0; ix < 1000; ++ix ) 
  // 函数 calc() 将指向 Huge 类型的数组元素指定为实参 
  sum += calc( table[ix] ); 
 
// ... 
}

二、如果引用参数不希望在被调用的函数内部被修改,那么把参数声明为 const 型的引用是个不错的办法。

如下例:

class X; 
extern int foo_bar( X& ); 
 
int foo( const X& xx ) { 
// 错误: const 传递给非 const 
return foo_bar( xx ); 

为使该程序通过编译 我们改变 foo_bar()的参数的类型 以下两种声明都是可以接受的  
extern int foo_bar( const X& ); 
extern int foo_bar( X ); 
// 按值传递

 

或者可以传递一个 xx 的拷贝做实参 允许 foo_bar()改变它  
int foo( const X &xx ) { 
// ... 
X x2 = xx; // 拷贝值 
 
// 当 foo_bar() 改变它的引用参数时, x2 被改变, xx 保持不变 
return foo_bar( x2 ); // ok 
}

三、 我们可以声明任意内置数据类型的引用参数

例如,如果程序员想修改指针本身,而不是指针引用的对象,那么他可以声明一个参数,该参数是一个指针的引用,例如:下面是交 换两个指针的函数  

?
1
2
3
4
5
6
<font size="4" face="宋体">void ptrswap(int *&v1,int *&v2 ) {
 int *tmp = v2;
 v2 = v1;
 v1 = tmp;
}
</font>

如下声明  
int *&v1;   //实际是为指针取了一个别名,这样就可以通过别名改变指针本身
应该从右向左读,v1 是一个引用,它引用一个指针,指针指向 int 型的对象。

用函数 main() 操纵函数 rswap() 我们可以如下修改代码以便交换两个指针值:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<font size="4" face="宋体">#include <iostream>
  
void ptrswap(int *&v1,int *&v2 );
int main() {
 int i = 10;
 int j = 20;
  
 int *pi = &i;
 int *pj = &j;
  
 cout << "Before ptrswap():\tpi: "
  << *pi << "\tpj: " << *pj << endl;
 
//参数为指针的别名(即指针的引用,那么传递的实参就是指针本身,道理和传递普通变量一样的)
 ptrswap( pi, pj );
  
 cout << "After ptrswap():\tpi: "
  << *pi << "\tpj: " << *pj << endl;
 return 0;
}
 
 
</font>

编译并运行程序 产生下列输出  
Before ptrswap(): pi: 10 pj: 20 
After ptrswap(): pi: 20 pj: 10

四、引用参数还是指针参数

这2种参数都能够改变实参的值,也可以有效的传递大型类对象,怎么样决定参数该声明成哪种呢?

根本区别是引用必须被初始化为指向一个对象,一旦初始化了,它就不能再指向其他对象;指针可以指向一系列不同的对象也可以什么都不指向 。

因为指针可能指向一个对象或没有任何对象,所以函数在确定指针实际指向一个有效的对象之前不能安全地解引用(dereference) 一个指针,例如:  
class X; 
void manip( X *px ) 

// 在解引用指针之前确信它非 0 
if ( px != 0 ) 
 
// 解引用指针 
}


另一方面,对于引用参数,函数不需要保证它指向一个对象。引用必须指向一个对象,甚至在我们不希望这样时也是如此,例如 : 
class Type { }; 
void operate( const Type& p1, const Type& p2 ); 
int main() { 
Type obj1; 
// 设置 obj1 为某个值 
// 错误: 引用参数的实参不能为 0 
Type obj2 = operate( obj1, 0 ); 
}


如果一个参数可能在函数中指向不同的对象,或者这个参数可能不指向任何对象,则必须使用指针参数 。

 

引用参数的一个重要用法是:它允许我们在有效地实现重载操作符的同时,还能保证用法的直观性。

以Matrix 对象的加法重载操作符来举例:

Matrix // 加法返回一个 Matrix 对象 
operator+( // 重载操作符的名字 
Matrix m1, // 操作符左操作数的类型 
Matrix m2 // 操作符右操作数的类型 


Matrix result; 
// do the computation in result 
return result; 
}

可以这样使用:

Matrix  a,b,c;  a = b+c; a+b+c;

但是operator+是按值传递,效率较低,如果我们改成指针参数

Matrix operator+( Matrix *m1, Matrix *m2 )


Matrix result; 
 
// 在 result 中计算 
return result; 
}

虽然在获得了效率,但在使用时将出现这样的代码:a = &b +&c  和&( &a+&b)+&c;显然并不友好。

下面是 Matrix类的重载加法操作符的修订版本  
// 使用引用参数的新实现 
Matrix operator+( const Matrix &m1, const Matrix &m2 ) 

Matrix result; 
// 在 result 中进行计算 
return result; 

该实现支持如下形式的 Matrix对象的加法  
a + b + c