C++中引用(Reference)与指针(Pointer)的使用对比

了解引用reference与指针pointer到底有什么不同可以帮助你决定什么时候该用reference,什么时候该用pointer。

在C++ 中,reference在很多方面与指针(pointer)具有同样的能力。虽然多数C++程序员对于何时使用reference何时使用pointer 都会有一些直觉,但总还是会有些时候搞不清楚。如果你想要建立一个关于使用reference使用的清晰有理的概念, 又有必要了解到底reference和pointer有什么不同。
深层含义

与pointer 类似,一个reference是一个对象(object),可以用来间接指向另一个对象。一个reference的声明与pointer的声明的实质语法结构是相同的。不同的是,声明pointer的时候使用星号操作符 * , 而声明reference的时候使用地址操作符 & 。 例如,我们有:
int i = 3;
则有:
int *pi = &i;
声明 pi 为一个指针类型的对象,并且是一个”指向int整型的指针”,它的初始值为对象i的地址。而另一方面:
int &ri = i;
声明 ri为一个reference类型的对象,并且也是一个指向整型的reference,它指向的是i。 我们可以看到pointer和reference的声明有显著的不同,但这并不是决定何时使用哪一个的根据。决定的真正依据是当它们被用在表达式中时其显示的不同决定了使用哪一个合适

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——char * const pStr;), 而不是指向常量的指针 “pointers to const——const char * pStr”。) 例如,一个reference定义时必须同时带有一个初始化赋值,如下所示:

void f()
    {
    int &r = i;
    …
    }

省略这个初始化赋值将产生一个编译错误:

    void f()
    {
    int &r; //错误
    …
    }

一个常量指针的声明也同样必须带有一个初始化赋值,如下所示:

    void f()
    {
    int * const p = &i;
    …
    }

省略这个初始化赋值同样会出错:

void f(){
int * const p; // 错误
 int * const p = NULL; // 正确,可以指向空指针。
…
}

可见, 不能够对reference二次绑定和常量指针相似。

四 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应该提供两个对象,而不能提供NULL空值,它们的值将被交换。 并且这样定义的另一个好处是,在调用这个函数的时候,不需要使用那些&符号,看起来更顺眼:
swap(i, j);

五 reference更安全?

有些人认为既然reference不能够为空,那么它应该比指针更安全。 我认为reference可能要安全一点,但不会安全很多。虽然一个有效的reference不能为空,但是无效的可以呀。实际上,在很多情况下程序有可 能产生无效的reference,而不只是空的reference。 例如,你可以定义一个reference,使它绑定到一个指针指向的对象,如下所示:

    int *p;
    …
    int &r = *p;

如果指针*p在reference定义时刚好为空,则这个reference为空。 从技术上来说,这个错误并不在于将reference绑定到一个空值,而是在于对一个空指针解除引用——*p。 对一个空指针解除引用产生了一个不确定的操作,也就意味着很多事都可能发生,而且大部分都不是什么好事。很有可能当程序将reference r 绑定到*p (p所指向的对象)的时候,p实际上没有被解除引用,甚至程序只是将p的值拷贝给实现r的指针。而程序将会继续执行下去直到错误在后面的运行中更为明显的表现出来,产生不可预知的危害。

下面的函数展示了另外一种产生无效reference的方法:

    int & f()
    {
    int i;
    …
    return i;
    }

这个函数返回一个指向本地变量 i 的reference。然而当函数返回时,本地变量 i 的存储空间也就消失了。因此这个函数实际返回了一个指向被回收了的空间的reference。这个操作与返回一个指向本地变量的指针的后果相同。有些编译器可以在编译时发现这个错误,但也很有可能不会发现。

注意:不能在将一个reference作为函数返回类型时,却return函数内部创建的临时变量。这样会导致在程序运行结束后,临时变量内存被释放,则reference指向的内容不见了,下次使用该reference时将发生错误。

我喜欢reference,也有很好的理由使用它们代替pointer。但如果你期望使用reference来使你的程序健壮性显著增强,那么你多半会失望的。

转载自程序员实验室对Dan Saks的文章 References vs. Pointers的翻译。
译文地址:http://www.prglab.com/blog/p/28
原文地址:http://www.embedded.com/story/OEG20010311S0024

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,282评论 0 6
  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,423评论 3 44
  • 人都是自私的动物吧 不然怎会有大难临头各自飞 亏你还能睡得安稳 酒精可以麻痹神经 让你误以为自己是快乐的 只是当酒...
    允思429阅读 135评论 0 1
  • 是Colbie Caillat的一首歌曲,以一个女孩的视角写女孩经历的转变。一开始女孩化妆,盘发,保持苗条,尝试着...
    LunaLithree阅读 411评论 0 3
  • 我的目标很简单,就是把宇宙整个明白,它为何如此,它为何存在。 ...
    安娜人生物语阅读 2,427评论 5 8