整理日: 2015年3月18日程序员
引用(reference)和指针(pointer)是学C++过程当中最使人头疼的问题,经常不知道何时用哪一个合适,又经常弄混。找到Dan Saks的这篇文章,讲的很清楚,强烈推荐,因此翻译一下供你们参考。安全
如下译自Dan Saks的文章 References vs. Pointers函数
了解引用reference与指针pointer到底有什么不一样能够帮助你决定何时该用reference,何时该用pointer。翻译
在C++ 中,reference在不少方面与指针(pointer)具备一样的能力。虽然多数C++程序员对于什么时候使用reference什么时候使用pointer 都会有一些直觉,但总仍是会有些时候搞不清楚。若是你想要创建一个关于使用reference使用的清晰有理的概念, 又有必要了解到底reference和pointer有什么不一样。指针
深层含义
与pointer 相似,一个reference是一个对象(object),能够用来间接指向另外一个对象。一个reference的声明与pointer的声明的实质语法结构是相同的。不一样的是,声明pointer的时候使用星号操做符 * , 而声明reference的时候使用地址操做符 & 。 例如,咱们有:code
int i = 3;
则有:对象
int *pi = &i;
声明 pi 为一个指针类型的对象,而且是一个”指向int整型的指针”,它的初始值为对象i的地址。而另外一方面:接口
int &ri = i;
声明 ri为一个reference类型的对象,而且也是一个指向整型的reference,它指向的是i。 咱们能够看到pointer和reference的声明有显著的不一样,但这并非决定什么时候使用哪个的根据。决定的真正依据是当它们被用在表达式中时其显 示的不一样决定了使用哪个合适io
Pointer 和reference的最大不一样是:pointer必须使用一个星号操做符 * 来去掉reference (英文叫作dereference,我不知道这里怎样翻译这个词合适,姑且就叫“去参考”吧)而reference不须要任何操做符来去参考。 例如, 有了上面例子中的定义, 间接表达式 *pi 将 pi 去参考为指向i。相反, 表达式ri-不须要任何操做符-自动将ri去参考为指向i。所以, 使用指针p,咱们须要用赋值语句:编译
*p = 4;
将i的值变为4; 而使用reference ri,咱们只须要直接写:
ri = 4;
就能够一样将i的值变为4 。
这个显示的不一样在当你为函数的参数类型和返回值类型选择是使用pointer仍是reference的时候就会显著起来,尤为是对于重载操做符的函数。
下面使用一个针对列举类型(enumeration)的++操做符例子来讲明上面这点。在C++中, 内置的++操做符对列举类型无效,例如, 对下面定义:
enum day{ Sunday, Monday, … }; day x;
表达式 ++x 不能编译。若是想让它经过编译,必需要定义一个名为operator++的函数,接受day为参数,而且调用 ++x 必须改变x的值。所以, 仅声明一个函数 operator++ , 以类型day为参数, 以下:
day operator++(day d);
并不可以获得想要得效果。 这个函数经过值传递参数(pass by value),这就意味着函数内看到的是参数的一个拷贝,而不是参数自己。为了使函数可以改变其操做数(operand)的值,它必须经过指针或reference来传递其操做数。
经过指针传递参数(passing by pointer),函数定义以下:
day operator++(day d);
它经过将增长后的值存储到*d里面来使函数改变日期(day)的值。可是,这样你就必须使用像表达式++&x这样来调用这个操做符,这看起来不太对劲儿。
正确的方法是定义operator++以reference为参数类型,以下:
day &operator++(day &d) { d = (day)(d + 1); return d; }
使用这个函数, 表达式 ++x 才有正确的显示以及正确的操做。
Passing by reference不只仅是写operator++较好的方法,而是惟一的方法。 C++在这里并无给咱们选择的余地。 像下面的声明:
day operator++(day d);
是不能 经过编译的。每一个重载的操做符函数必须或者是一个类的成员, 或者使用类型T、 T & 或 T const & 为参数类型,这里T是一个类(class)或列举(enumeration)类型。 也就是说,每个重载操做符必须以类或列举类型为参数类型。指针,即便是指向一个类或列举类型对象的指针,也不能够用。C++ 不容许在重载操做符时从新定义内置操做符的含义,包括指针类型。所以,咱们不能够定义:
int operator++(int i); // 错误
由于它试图对int从新定义操做符 ++ 的含义。 咱们也不能够定义:
int operator++(int i); // 错误
由于它试图对 int * 从新定义操做符 ++ 的含义。
References vs. const pointers
C++ 中不容许定义”const reference”, 由于一个reference天生就是const。也就是说,一旦将一个reference绑定到一个对象,就没法再将它从新绑定到另外一个不一样的对象。在声 明一个reference以后没有写法能够将它从新绑定到另一个对象。例如:
int &ri = i;
将 ri 绑定到 i 。而后下面的赋值:
ri = j;
并非把 ri 绑定到 j ,而是将 j 中的值赋给 ri 指向的对象,也就是赋给 i 。
简而言之,一个pointer在它的有生之年能够指向许多不一样的对象,而一个reference只可以指向一个对象。有些人认为这才是 reference和 pointer最大的不一样。我并不同意。也许这是reference与pointer的一点不一样, 但并非reference和const pointer的不一样。在强调一遍,一旦一个reference与一个对象绑定,就不能再将它改指向另外的东西。既然不能再绑定reference以后再 改变, 一个reference就必须在一出生就被绑定。不然这个reference就永远不能被绑定到任何东西,也就毫无用处了。
上一段的讨论也一样彻底适用于常量指针(const pointer)。(注意,我这里说的是常量指针(const pointer), 而不是指向常量的指针 “pointers to const”。) 例如,一个reference声明必须同时带有一个初始化赋值,以下所示:
void f() { int &r = i; // … }
省略这个初始化赋值将产生一个编译错误:
void f() { int &r; //错误 // … }
一个常量指针的声明也一样必须带有一个初始化赋值,以下所示:
void f() { int *const p = &i; // … }
省略这个初始化赋值一样会出错:
void f(){ int *const p; // 错误 // … }
在我看来, 不可以对reference二次绑定做为reference与pointer的不一样。并不比常量指针和很是量指针的不一样更为显著。
Null references
除了显示的不一样,常量指针与reference还有一点很是不一样,那就是,一个有效的reference必须指向一个对象;而一个指针不须要。一个指针,即便是一个常量指针, 均可以有空值。 一个空指针不指向任何东西。
这点不一样就暗示当你想要确信一个参数必须指向一个对象的时候,应该使用reference做为参数类型。 例如,交换函数(swap function),它接受两个int参数,并将两个参数的数值对调,以下所示:
int i, j; swap(i, j);
将本来在 i 中的值放到 j 中, 并将本来在 j 中的值放到 i 中。咱们能够这样写这个函数:
void swap(int *v1, int *v2) { int temp = *v1; *v1 = *v2; *v2 = temp; }
这种定义下,函数要像这样被调用: swap(&i, &j);
这个接口暗示其中一个或两个参数都有可能为空(null)。而这个暗示是误导的。例如,调用
swap(&i, NULL);
的后果极可能是不愉快的。
而像下面这样定义reference为参数:
void swap(int &v1, int &v2) { int temp = v1; v1 = v2; v2 = temp; }
清晰的代表了调用swap应该提供两个对象,它们的值将被交换。 而且这样定义的另外一个好处是,在调用这个函数的时候,不须要使用那些&符号,看起来更顺眼:
swap(i, j);
更安全?
有些人认为既然reference不可以为空,那么它应该比指针更安全。 我认为reference可能要安全一点,但不会安全不少。虽然一个有效的reference不能为空,可是无效的能够呀。实际上,在不少状况下程序有可 能产生无效的reference,而不仅是空的reference。 例如,你能够定义一个reference,使它绑定到一个指针指向的对象,以下所示:
int *p; // … int &r = *p;
若是指针p在reference定义时恰好为空,则这个reference为空。 从技术上来讲,这个错误并不在于将reference绑定到一个空值,而是在于对一个空指针去参考。 对一个空指针去参考产生了一个不肯定的操做,也就意味着不少事均可能发生,并且大部分都不是什么好事。颇有可能当程序将reference r 绑定到p (p所指向的对象)的时候,p实际上没有被去参考,甚至程序只是将p的值拷贝给实现r的指针。而程序将会继续执行下去直到错误在后面的运行中更为明显的表 现出来,产生不可预知的危害。
下面的函数展现了另一种产生无效reference的方法:
int &f() { int i; // … return i; }
这个函数返回一个指向本地变量 i 的reference。然而当函数返回时,本地变量 i 的存储空间也就消失了。所以这个函数实际返回了一个指向被回收了的空间的reference。这个操做与返回一个指向本地变量的指针的后果相同。有些编译 器能够在编译时发现这个错误,但也颇有可能不会发现。
我喜欢reference,也有很好的理由使用它们代替pointer。但若是你指望使用reference来使你的程序健壮性显著加强,那么你多半会失望的。