引用参数是由调用部位传入实参的地址(写在留言板上)的形参。
基本介绍
- 中文名:引用参数
- 用法种数:3
- 子程式:改变传递给引用参数
- 函式操纵:实参的本地拷贝
简介
引用参数是由调用部位传入实参的地址(写在留言板上)的形参。
在形参表中以符号“&”开始的参数即为引用参数。如果一个形参是引用参数,调用部位将把实参的地址传递给子程式。子程式可以改变传递给引用参数的任何实参,因为子程式操作的是真正的变数,而不是它的副本。
把参数声明成引用,实际上改变了预设的按值传递参数的传递机制,在按值传递时,函式操纵的是实参的本地拷贝。
常见用法
一、引用参数的三种常见用法:
1.需要改变实参的值,比如swap()。参数是引用时,函式接收的是实参的左值而不是值的拷贝。这意味着函式知道实参在记忆体中的位置,因而能够改变它的值或取它的地址。
2.向主调函式返回额外的结果。如下例:
2.向主调函式返回额外的结果。如下例:
1 | /***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; } |
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] );
// ...
}
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 ); // 按值传递
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
}
int foo( const X &xx ) {
// ...
X x2 = xx; // 拷贝值
// 当 foo_bar() 改变它的引用参数时, x2 被改变, xx 保持不变
return foo_bar( x2 ); // ok
}
三、 我们可以声明任意内置数据类型的引用参数
例如,如果程式设计师想修改指针本身,而不是指针引用的对象,那幺他可以声明一个参数,该参数是一个指针的引用,例如:下面是交 换两个指针的函式
?
12345 | void ptrswap( int *&v1, int *&v2 ) { int *tmp = v2; v2 = v1; v1 = tmp; } |
如下声明
int *&v1; //实际是为指针取了一个别名,这样就可以通过别名改变指针本身
应该从右向左读,v1 是一个引用,它引用一个指针,指针指向 int 型的对象。
int *&v1; //实际是为指针取了一个别名,这样就可以通过别名改变指针本身
应该从右向左读,v1 是一个引用,它引用一个指针,指针指向 int 型的对象。
用函式 main() 操纵函式 rswap() 我们可以如下修改代码以便交换两个指针值:
?
1234567891011121314151617181920 | #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; } |
编译并运行程式 产生下列输出
Before ptrswap(): pi: 10 pj: 20
After ptrswap(): pi: 20 pj: 10
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 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 );
}
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;
}
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;
}
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
// 使用引用参数的新实现
Matrix operator+( const Matrix &m1, const Matrix &m2 )
{
Matrix result;
// 在 result 中进行计算
return result;
}
该实现支持如下形式的 Matrix对象的加法
a + b + c