首页 > 学技术 > 技术网文 > C/C++ > 正文

[精彩] C++的引用的传递和返回兼批判


来源 chinaunix.net kuqin整理

C++的引用的传递和返回兼批判

C中只有传值(PV)和传地址(PP)。有些语言仅仅有传地址,没有传值。好象FORTRAN就是。
实际上从汇编开始理解,传递(包括参数和返回)要么是PV要么是PP。那么引用是什么东西?

引用是C++不得以产生的概念。如果没有C的PV,如FORTRAN那样全是引用(一种PP),也就不会出现引用的概念。

下面的小程序和对应的汇编可以说明引用本质是什么。

从func0和func1的调用可以看出引用本质就是地址传递(PP)。2个函数调用的汇编一样的。
func2返回引用,实际是返回地址,也是地址传递(PP)

那么引用有好处吗?我个人意见,不多。除非万不得已非用不可,建议千万不要使用。
使用了,后果是让简单的程序无法读懂(不是因为引用概念复杂),尤其是分模块的情况下。
比如:
int i = 1;
return i;
的本意很清楚,可以有引用的情况下,这2行的意义是不确定的。比如往前看才能确定。
function(i); 也是一样,本来清楚的东西变为不确定。

实际上引用就是PP,但是一种用编译器做了伪装的PP。因此使程序更不容易读。

欢迎批评和讨论。



[CODE]
#include <stdio.h>
int func0(int &i)
{
   i++;
   return 100;
}
int func1(int *ip)
{
    (*ip) ++;
   return 101;
}
int& func2(int i)
{
static int ii = 123;
    return ii;
}

main()
{
int i, j;

    i = 4;
    j = 104;
    j = func0(i);
    //printf("i = %d j = %d\n", i, j);
    j = func1(&i);
    //printf("i = %d j = %d\n", i, j);
    j = func2(9);
    //printf("i = %d j = %d\n", i, j);
}

   .file   "t2.c"
        .text
        .align 2
.globl _Z5func0Ri
        .type   _Z5func0Ri,@function
_Z5func0Ri:
.LFB2:
        pushl   %ebp
.LCFI0:
        movl    %esp, %ebp
.LCFI1:
        movl    8(%ebp), %eax
        incl    (%eax)
        movl    $100, %eax
        leave
        ret
.LFE2:
.Lfe1:
        .size   _Z5func0Ri,.Lfe1-_Z5func0Ri
        .align 2
.globl _Z5func1Pi
        .type   _Z5func1Pi,@function
_Z5func1Pi:
.LFB4:
        pushl   %ebp
.LCFI2:
        movl    %esp, %ebp
.LCFI3:
        movl    8(%ebp), %eax
        incl    (%eax)
        movl    $101, %eax
        leave
        ret
.LFE4:
.Lfe2:
        .size   _Z5func1Pi,.Lfe2-_Z5func1Pi
        .align 2
.globl _Z5func2i
        .type   _Z5func2i,@function
_Z5func2i:
.LFB6:
        pushl   %ebp
.LCFI4:
        movl    %esp, %ebp
.LCFI5:
        subl    $4, %esp
.LCFI6:
        movl    $123, -4(%ebp)
        leal    -4(%ebp), %eax
        leave
        ret
.LFE6:
.Lfe3:
        .size   _Z5func2i,.Lfe3-_Z5func2i
        .align 2
.globl main
        .type   main,@function
main:
.LFB8:
        pushl   %ebp
.LCFI7:
        movl    %esp, %ebp
.LCFI8:
        subl    $8, %esp
.LCFI9:
        andl    $-16, %esp
        movl    $0, %eax
        subl    %eax, %esp
        movl    $4, -4(%ebp)
        movl    $104, -8(%ebp)
        subl    $12, %esp
        leal    -4(%ebp), %eax
        pushl   %eax
.LCFI10:
        call    _Z5func0Ri
        addl    $16, %esp
        movl    %eax, -8(%ebp)
        subl    $12, %esp
        leal    -4(%ebp), %eax
        pushl   %eax
        call    _Z5func1Pi
        addl    $16, %esp
        movl    %eax, -8(%ebp)
        subl    $12, %esp
        pushl   $9
        call    _Z5func2i
        addl    $16, %esp
        movl    (%eax), %eax
        movl    %eax, -8(%ebp)
        movl    $0, %eax
        leave
        ret
.LFE8:
.Lfe4:
        .size   main,.Lfe4-main
        .ident  "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
[/CODE]



 ypxing 回复于:2007-05-16 09:37:11

引用:原帖由 思一克 于 2007-5-16 09:16 发表
那么引用有好处吗?我个人意见,不多。除非万不得已非用不可,建议千万不要使用。 



不敢苟同

引用: 
比如:
int i = 1;
return i;
的本意很清楚,可以有引用的情况下,这2行的意义是不确定的。比如往前看才能确定



不知所云


 思一克 回复于:2007-05-16 09:43:58

int ii = 123;
return ii;

这2行你不知道是返回的什么,是返回的123还是一个地址。如果没有引用,返回的一定是数值。


 emacsnw 回复于:2007-05-16 09:45:08

我觉得C++的引用比指针不够灵活在两点:没有null引用和不能重新绑定。
不然的话,很多时候传递引用会比传递指针更方便一些。


 emacsnw 回复于:2007-05-16 09:48:11

引用:原帖由 思一克 于 2007-5-15 17:43 发表
int ii = 123;
return ii;

这2行你不知道是返回的什么,是返回的123还是一个地址。如果没有引用,返回的一定是数值。 



支持。允许参数是引用还有一个问题就是隐藏了函数可能产生的side effect。

int x = 10;
f(x);
// what's the value of x now?


在C里面,这个代码几乎保证 f 执行后x的值没变,而C++中则不一定。


 ypxing 回复于:2007-05-16 10:00:32

引用:原帖由 思一克 于 2007-5-16 09:16 发表
下面的小程序和对应的汇编可以说明引用本质是什么。

从func0和func1的调用可以看出引用本质就是地址传递(PP)。2个函数调用的汇编一样的。
func2返回引用,实际是返回地址,也是地址传递(PP)



不能用引用的底层汇编实现来反推引用的本质, 
在汇编的层次上,地址的使用太平常了。

引用:
int ii = 123;
return ii;

这2行你不知道是返回的什么,是返回的123还是一个地址。如果没有引用,返回的一定是数值。



意义很明确,返回的是数值,只是这样的用法很危险而已。

int ii = 123;
return &ii;

这两行在C语言中同样很危险


 思一克 回复于:2007-05-16 10:07:14

ii是外面的变量,没有危险。

C中返回STACK变量指针的危险和引用造成的危险和模糊不是一回事。

C的函数规则可让你给手下一个函数定义,比如
int  yourwork(int i, int j, int *pi)

手下会给你工作。你不用管他如何做的,你知道i,j他不会影响,pi和返回是给你的结果。


 ypxing 回复于:2007-05-16 10:07:32

引用:原帖由 emacsnw 于 2007-5-16 09:48 发表


支持。允许参数是引用还有一个问题就是隐藏了函数可能产生的side effect。



指针的滥用产生的side effect一点也不少


引用:
int x = 10;
f(x);
// what's the value of x now?
在C里面,这个代码几乎保证 f 执行后x的值没变,而C++中则不一定。 



那是因为你的使用方法不对


 zlbruce 回复于:2007-05-16 10:10:39

引用:原帖由 思一克 于 2007-5-16 10:07 发表

C的函数规则可让你给手下一个函数定义,比如
int  yourwork(int i, int j, int *pi)

手下会给你工作。你不用管他如何做的,你知道i,j他不会影响,pi和返回是给你的结果。


C++的函数规则可让你给手下一个函数定义,比如
int  yourwork(int i, int j, int &pi)

手下会给你工作。你不用管他如何做的,你知道i,j他不会影响,pi和返回是给你的结果。


 ypxing 回复于:2007-05-16 10:12:43

引用:原帖由 思一克 于 2007-5-16 10:07 发表
C的函数规则可让你给手下一个函数定义,比如
int  yourwork(int i, int j, int *pi)

手下会给你工作。你不用管他如何做的,你知道i,j他不会影响,pi和返回是给你的结果。



版主这个理解就不对,*pi而不是pi是返回的结果,pi也只是一个普通变量而已,它的值也不会变
这样可以看出指针给你造成了多大的影响。

要使i,j不变,你可以明确的将它们定义成const,不要要编译器猜你想干什么

[ 本帖最后由 ypxing 于 2007-5-16 10:15 编辑 ]


 ypxing 回复于:2007-05-16 10:14:42

引用:原帖由 思一克 于 2007-5-16 10:07 发表
C中返回STACK变量指针的危险和引用造成的危险和模糊不是一回事。 



能否给个详细说明


 思一克 回复于:2007-05-16 10:21:11

你说的这个(pi)我知道。

pi是返回的结果,我的意思是pi指的int住址是返回的结果。

这么说吧:

你调用了一个其他人编写的函数func(i, j);
你一定明白func不会对i,j造成改变。即使你不知道函数的原形。

但有引用的时候,完全不是这样了。

引用的使用使得程序的模块化结构变的不好。如同FORTRAN的模块化不如C好一样。因为一切都会变。



引用:原帖由 ypxing 于 2007-5-16 10:12 发表


版主这个理解就不对,*pi而不是pi是返回的结果,pi也只是一个普通变量而已,它的值也不会变
这样可以看出指针给你造成了多大的影响。

要使i,j不变,你可以明确的将它们定义成const,不要要编译器猜你想干什么 




 ypxing 回复于:2007-05-16 10:31:01

引用:原帖由 思一克 于 2007-5-16 10:21 发表
你说的这个(pi)我知道。

pi是返回的结果,我的意思是pi指的int住址是返回的结果。

这么说吧:

你调用了一个其他人编写的函数func(i, j);
你一定明白func不会对i,j造成改变。即使你不知道函数的原形。
 ... 



你的意思我很明白
一个新概念的出现有它的优点也有它的缺点,但是版主不能只看它带来的负面效应,
而且这个负面效应简直不值一提(因为你在调用一个函数后不想让i,j改变的话,更明确的做法是在调用前将i和j保存起来).

指针的滥用同样造成了很多的混乱,减少指针的使用可以在一定程度上改变这种状况.

很多bug,不是因为语言不好用,而是因为程序员的习惯不好造成的.

[ 本帖最后由 ypxing 于 2007-5-16 10:32 编辑 ]


 ypxing 回复于:2007-05-16 10:33:40

引用:原帖由 思一克 于 2007-5-16 10:21 发表
你说的这个(pi)我知道。

pi是返回的结果,我的意思是pi指的int住址是返回的结果。 



版主当然会知道这个问题了
但是很多人是很迷惑的,一点也不差于对引用的迷惑


 思一克 回复于:2007-05-16 10:36:18

我认为引用不是一个新东西。而是早期结构化程序比如FORTRAN等的一个固定的唯一的做法。
所以是一个老的传递方法。

而早期结构化程序不如C好的一个比较重要的因素就是引用传递的问题导致的一切都可以变。

至于C++中是不得已而引入的。如果不引入,C++的某些事情不好做。


 ypxing 回复于:2007-05-16 10:41:14

引用:原帖由 思一克 于 2007-5-16 10:36 发表
我认为引用不是一个新东西。而是早期结构化程序比如FORTRAN等的一个固定的唯一的做法。
所以是一个老的传递方法。

而早期结构化程序不如C好的一个比较重要的因素就是引用传递的问题导致的一切都可以变。

至 ... 



个人认为引用的意义还是很明确的

一个程序员在不了解一个函数时对它进行调用,
如果不想让参数改变,调用前就应该对参数进行保存.

不用引用,只用指针的话,比如void f(int *p)
你只知道p不变,而根本不知道*p会不会变
同样会造成你所说的一些问题
int i;
f(&i); /*调用后你也不知道i变不变,要想i不变,只能事先保存它,因为f不是你定义的,你不能改变它,
           只能使用它*/

[ 本帖最后由 ypxing 于 2007-5-16 10:50 编辑 ]


 思一克 回复于:2007-05-16 10:44:17

是这样的:

如果没有C的传递数值和传递地址概念,而如原来语言那样一切都是引用,那么引用也没有什么不明确的问题。
不好的问题是在C程序中才有所显现。


 ypxing 回复于:2007-05-16 10:52:22

引用:原帖由 思一克 于 2007-5-16 10:44 发表
是这样的:

如果没有C的传递数值和传递地址概念,而如原来语言那样一切都是引用,那么引用也没有什么不明确的问题。
不好的问题是在C程序中才有所显现。 



一个程序员在不了解一个函数时对它进行调用,
如果不想让参数改变,调用前就应该对参数进行保存.

不用引用,只用指针的话,比如void f(int *p)
你只知道p不变,而根本不知道*p会不会变
同样会造成你所说的一些问题
int i;
f(&i); /*调用后你也不知道i变不变,要想i不变,只能事先保存它,因为f不是你定义的,你不是很了解它,你不能改变它,
           只能使用它*/

[ 本帖最后由 ypxing 于 2007-5-16 10:56 编辑 ]


 aXe 回复于:2007-05-16 10:57:11

如果很多c++的编译器都把引用当指针来实现,那么在实现上引用不是新东西(哪些c++编译器不用指针实现引用?有知道的讲一下。)。我觉得引用在c++上主要是语意上的:1、不能空,2、必须初始化,3、不能改变。有的书把他叫别名。所以如果从下往上推,就失去这些语意了。


 ypxing 回复于:2007-05-16 11:00:06

引用:原帖由 aXe 于 2007-5-16 10:57 发表
如果很多c++的编译器都把引用当指针来实现,那么在实现上引用不是新东西(哪些c++编译器不用指针实现引用?有知道的讲一下。)。我觉得引用在c++上主要是语意上的:1、不能空,2、必须初始化,3、不能改变。有的书 ... 



是的,不能根据对引用的实现来反推引用的本质


 missjiang 回复于:2007-05-16 11:04:47

引用:原帖由 思一克 于 2007-5-16 09:16 发表
不是因为引用概念复杂
比如往前看才能确定。 




引用:原帖由 emacsnw 于 2007-5-16 09:48 发表


支持。允许参数是引用还有一个问题就是隐藏了函数可能产生的side effect。

int x = 10;
f(x);
// what's the value of x now?


在C里面,这个代码几乎保证 f 执行后x的值没变,而C++中则不一定


同意以上的看法。


 飞灰橙 回复于:2007-05-16 11:10:03

引用:原帖由 ypxing 于 2007-5-16 10:41 发表


个人认为引用的意义还是很明确的

一个程序员在不了解一个函数时对它进行调用,
如果不想让参数改变,调用前就应该对参数进行保存.

不用引用,只用指针的话,比如void f(int *p)
你只知道p不变,而根本 ... 



不了解一个函数,居然还敢调用它!!!真佩服楼上的两位!!
既然调用一个函数,必然知道它的函数原型,必然知道该函数的引用参数有没有加const


 zhujiang73 回复于:2007-05-16 11:10:08

引用:原帖由 思一克 于 2007-5-16 10:36 发表
如果不引入,C++的某些事情不好做。 



这就是引用的好处,不然  std::cout << " Hello World " << std::endl  都写不成了。


 ypxing 回复于:2007-05-16 11:17:09

引用:原帖由 飞灰橙 于 2007-5-16 11:10 发表


不了解一个函数,居然还敢调用它!!!真佩服楼上的两位!!
既然调用一个函数,必然知道它的函数原型,必然知道该函数的引用参数有没有加const 




这是版主提出的问题,呵呵


 飞灰橙 回复于:2007-05-16 11:18:37

引用:原帖由 ypxing 于 2007-5-16 11:17 发表



这是版主提出的问题,呵呵 



窃以为版主老大过渡思考,钻到牛角尖里去了


 net_robber 回复于:2007-05-16 11:26:42

发现一个以前没有注意到的问题

引用在C中不可用,这是C++中的东西,呵呵

以前很少用这东西,呼呼


 林杰杰 回复于:2007-05-16 11:43:09

引用就是被引用变量的别名,是同一个玩意,那他们地址相同有什么奇怪的。
就如小明的爸爸和小明妈妈的丈夫住在XX楼一样。


 ypxing 回复于:2007-05-16 11:44:35

引用:原帖由 林杰杰 于 2007-5-16 11:43 发表
就如小明的爸爸和小明妈妈的丈夫住在XX楼一样。 



不一定是同一个人呦


 林杰杰 回复于:2007-05-16 11:45:46

引用:原帖由 ypxing 于 2007-5-16 11:44 发表


不一定是同一个人呦 


考虑没有离过婚的大多数情况。。。。。。。


 emacsnw 回复于:2007-05-16 11:47:16

引用:原帖由 飞灰橙 于 2007-5-15 19:10 发表


不了解一个函数,居然还敢调用它!!!真佩服楼上的两位!!
既然调用一个函数,必然知道它的函数原型,必然知道该函数的引用参数有没有加const 



这样说的话,加const就一定有用吗?函数实现的时候还可以const_cast后继续修改参数。


 思一克 回复于:2007-05-16 11:53:15

没有专到牛角尖。

这样的结论应该没有人反对
0)引用传递方式不是新事物,而是最早期的机构化语言所采用的
1)早期的一切传递都是引用的结构化语言是结构不好的。因为变量不太分局部和全局。而且不能将函数认为是输入输出的黑盒子。任何参数都会被调用者修改。
3)C的一个特点就是“低级”,和汇编有着清晰简单的对应。你读C程序,看见一个函数调用i = func(j, k),你不需要再找func的原来函数到底是什么就可以通过黑盒子方法实验出func的功能,或继续网下读。引用的引入使这一点不行了。
4)如果所有传递都是引用,和FORTRAN一样了

我说能不用尽量不用。必要时当然还要用的。


引用:原帖由 net_robber 于 2007-5-16 11:26 发表
发现一个以前没有注意到的问题

引用在C中不可用,这是C++中的东西,呵呵

以前很少用这东西,呼呼 




 ypxing 回复于:2007-05-16 11:58:12

引用:原帖由 思一克 于 2007-5-16 11:53 发表
你读C程序,看见一个函数调用i = func(j, k),你不需要再找func的原来函数到底是什么就可以通过黑盒子方法实验出func的功能,或继续网下读。引用的引入使这一点不行了。



反对,例如
int func(int *i)
{
  ...
}

....

int i;
func(&i);
/* i 的地址是没有变,i 变没变呢?不知道.*/

[ 本帖最后由 ypxing 于 2007-5-16 12:00 编辑 ]


 emacsnw 回复于:2007-05-16 12:00:04

引用:原帖由 ypxing 于 2007-5-15 19:58 发表


反对,例如
int func(int *i)
{
  ...
}

....

int i;
func(&i);
/*i的地址是没有变,i变没变呢?不知道*/ 



看到 func(&i) 难道还会认为 i 在 f 里面不会改变?


 林杰杰 回复于:2007-05-16 12:00:28

引用:原帖由 ypxing 于 2007-5-16 11:58 发表


反对,例如
int func(int *i)
{
  ...
}

....

int i;
func(&i);
/*i的地址是没有变,i变没变呢?不知道*/ 


人家的意思,在你这种情况下,是&i不会变。
参数本身不变,但是与参数相关的其它变量会不会变,这跟它没关系。


 思一克 回复于:2007-05-16 12:00:55

int func(int *i);
i 志向的地方可能变也可能不变,但是这是明显告诉你的,那里可能被修改。
引用就不同了,没有提供任何信息。除非你找到func本身


 ypxing 回复于:2007-05-16 12:03:38

引用:原帖由 emacsnw 于 2007-5-16 12:00 发表


看到 func(&i) 难道还会认为 i 在 f 里面不会改变? 



不看func(int *i)的定义是不能确定的,i可能变,也可能不变

所以指针也带来了模糊性


 思一克 回复于:2007-05-16 12:05:37

”所以指针也带来了模糊性“ 这个“模糊”是不需要参考其它地方而明显告诉你的。
引用func(i),没有告诉你。除非你去看func本身。


 ypxing 回复于:2007-05-16 12:07:00

引用:原帖由 思一克 于 2007-5-16 12:00 发表
int func(int *i);
i 志向的地方可能变也可能不变,但是这是明显告诉你的,那里可能被修改。
引用就不同了,没有提供任何信息。除非你找到func本身 



安全起见,你就应该认为引用的时候是可能改变的(正如你默认指针会改变i一样)
所以,如果要想调用func时不改变i,要调用前保存i,调用后回复i


 connet 回复于:2007-05-16 12:08:27

引用:原帖由 ypxing 于 2007-5-16 12:03 发表


不看func(int *i)的定义是不能确定的,i可能变,也可能不变

所以指针也带来了模糊性 


非也
func(int *i) 这样定义, i 一定是改变, 也许改变后的值 与先前一样。
否则就应该定义成 func(int i)
最最最基本的原则。


 ypxing 回复于:2007-05-16 12:10:38

引用:原帖由 connet 于 2007-5-16 12:08 发表

非也
func(int *i) 这样定义, i 一定是改变, 也许改变后的值 与先前一样。
否则就应该定义成 func(int i)
最最最基本的原则。 



把“可能”当“必然”
你这种认识是制造bug的源泉

"最最最基本的原则"?
那么有引用时候,大家也可以加几条原则呀
让大家不要想当然的使用

[ 本帖最后由 ypxing 于 2007-5-16 12:12 编辑 ]


 思一克 回复于:2007-05-16 12:12:42

”你这种认识是制造bug的源泉“

该点引用应该远高于指针。想想所有传递都是引用的语言会是怎样的情形。


 ypxing 回复于:2007-05-16 12:18:14

请大家使用引用的优点,
避免它的缺点,
如果调用一个函数不想让参数改变,请先保存,在恢复
如果不知道函数原型和实现,请默认参数是会改变的

这也算个小原则吧


 ypxing 回复于:2007-05-16 12:19:36

引用:原帖由 思一克 于 2007-5-16 12:12 发表
”你这种认识是制造bug的源泉“

该点引用应该远高于指针。想想所有传递都是引用的语言会是怎样的情形。 


不,我说的是这种认识:
即“有一些什么原则,然后想当然的认为所有人会遵守,...."


 飞灰橙 回复于:2007-05-16 12:23:13

引用:原帖由 emacsnw 于 2007-5-16 11:47 发表


这样说的话,加const就一定有用吗?函数实现的时候还可以const_cast后继续修改参数。 



是的,如果加上const_cast确实可以修改原本定义成const &的参数,
事无绝对,这里依靠程序员的良心和代码规范。
c/c++世界里,很多规则都可以人为打破的,不奇怪。对于故意打破规则捣乱的人,应该严惩不怠。


 coldwarm 回复于:2007-05-16 12:29:11


1)早期的一切传递都是引用的结构化语言是结构不好的。因为变量不太分局部和全局。而且不能将函数认为是输入输出的黑盒子。任何参数都会被调用者修改。


所以在某些公司的编程规范里,当你的函数不修改参数时,在参数列表中要加入const修饰来显式指定参数的不可变性.

引用在c++中引入,最初是为了解决诸如下标运算符重载等问题.比如

class CharArray
{
public:
    ....
    char& operator[](int position);        //可用于表达式中的左值
    const char& operator[](int position) const;
private:
    char* _datas;
};


不这么做的话,就不得不定义成员函数来完成类似功能.

另一个原因是考虑到性能,避免传值调用所引入的拷贝构造函数调用等开销.

另外,引用和其所引用对象间建立了唯一的联系.而指针并没有明确指定这种关系.


 redac 回复于:2007-05-16 12:58:47

引用:原帖由 思一克 于 2007-5-16 12:00 发表
int func(int *i);
i 志向的地方可能变也可能不变,但是这是明显告诉你的,那里可能被修改。
引用就不同了,没有提供任何信息。除非你找到func本身 




确实是


 I/0 回复于:2007-05-16 13:01:45

下面的内容摘自 TCPL 5.5 References

A reference can be used to specify a function argument so that the function can change the value of an object passed to it. For example:


void increment(int& aa)
{
aa++;
}

void f()
{
int x = 1;
increment(x); // x = 2
}


The semantics of argument passing are defined to be those of initialization, so when called, increment's argument aa became another name for x. To keep a program readable, it is often best to avoid functions that modify their arguments. Instead, you can return a value from the function explicitly or require a pointer argument:


int next(int p)
{
return p+1;
}

void incr(int* p)
{
(*p)++;
}

void g()
{
int x = 1;
increment(x); // x = 2
x = next(x); // x = 3
incr(&x); // x = 4
}


The increment(x) notation doesn't give a clue to the reader that x's value is being modified, the way x = next(x) and incr(&x) does. Consequently "plain" reference arguments should be used only where the name of the function gives a strong hint that the reference argument is modified.

[ 本帖最后由 I/0 于 2007-5-16 13:02 编辑 ]


 思一克 回复于:2007-05-16 14:21:04

“A reference can be used to specify a function argument so that the function can change the value of an object passed to it. For example:



”To keep a program readable, it is often best to avoid functions that modify their arguments. Instead, you can return a value from the function explicitly or require a pointer argument:“

这里也是在建议,为让程序可读性高,最好避免使用引用,而代之以用函数返回数值或用指针显式地改变参数。


 antonym55 回复于:2007-05-16 15:34:52

引用:原帖由 思一克 于 2007-5-16 09:16 发表
那么引用有好处吗?我个人意见,不多。除非万不得已非用不可,建议千万不要使用。
使用了,后果是让简单的程序无法读懂(不是因为引用概念复杂),尤其是分模块的情况下。
 



不敢苟同

楼主说的后果不是很严重嘛,因此我还是会继续使用refrence

那有不看函数原型就直接使用的?

实现同样的功能,不同的人写的函数原型就不一样,比如写个文件拷贝函数,

有人喜欢把 “源” 作为第2个参数,“目标”作为第1个参数,

也有人喜欢把 “源” 作为第1个参数,“目标”作为第2个参数.

如果用错了,只能说是有勇无谋;

另外,"后果是让简单的程序无法读懂" 这句话有点别扭,

我个人狭隘地认为,无法读懂的程序,应该不会简单


 思一克 回复于:2007-05-16 15:39:46

引用使得程序readable 变差,以下书中英语也是这么说的: (I/O 的引用)

”下面的内容摘自 TCPL 5.5 References“


 ypxing 回复于:2007-05-16 15:45:40

引用:原帖由 思一克 于 2007-5-16 15:39 发表
引用使得程序readable 变差,以下书中英语也是这么说的: (I/O 的引用)

”下面的内容摘自 TCPL 5.5 References“ 



引用也有使readable变得好的情况,呵呵


 飞灰橙 回复于:2007-05-16 15:48:36

引用:原帖由 ypxing 于 2007-5-16 15:45 发表


引用也有使readable变得好的情况,呵呵 



引用作为参数,相比指针,
可以不需要判断 if (pointer == NULL),使程序更简洁。


 思一克 回复于:2007-05-16 15:56:27

引用的本质就是指针。肯定有某些好处和必要性,在C++中。

但是2个坏处是影响最大的
1)程序可读性差。
2)模块结构差。

我的意思是要注意这2个坏处。还有要注意的是,本来是C程序,千万不要为了C++,用G++编译,再使用上象引用这样的扩展。如果那样,程序只能变的更糟。

如果程序一开始就是C++的,那当然不在我说的范围之内。该使用当然要用。


 DraculaW 回复于:2007-05-16 16:15:50

引用:原帖由 思一克 于 2007-5-16 12:12 发表
”你这种认识是制造bug的源泉“

该点引用应该远高于指针。想想所有传递都是引用的语言会是怎样的情形。 




java不就是全都是引用么


 missjiang 回复于:2007-05-16 16:26:16

引用:原帖由 思一克 于 2007-5-16 15:56 发表
引用的本质就是指针。肯定有某些好处和必要性,在C++中。

但是2个坏处是影响最大的
[color=red]1)程序可读性差。[/color]
2)模块结构差。

我的意思是要注意这2个坏处。还有要注意的是,本来是C程序,千万不要为了C++,用G+ ... 


我们可以使用指针、引用、宏三种方式实现交换两个数的功能,如下所示:

使用引用,代码较简洁
void swap(int & a, int & b)
{
   int temp = a;
   a = b;
   b = temp;
}

使用指针,代码不直观
void swap(int * a, int * b)
{
   int temp = *a;
   *a = *b;
   *b = temp;
}

当我还是一个对地址概念不清楚的C语言初学者的时候,我觉得使用"引用"实现的swap比使用指针实现的swap的可读性要好一点。但是在熟悉了指针后,我倾向于使用指针实现swap函数,原因在于:如果用指针实现swap函数,调用swap函数的语句为swap(&a, &b),提醒程序员a、b的值有可能发生改变;而如果使用引用实现swap函数,调用swap函数的语句为swap(a, b),程序员有可能误认为a、b的值没有发生变化。


 missjiang 回复于:2007-05-16 16:44:39

C#中引入了ref关键字用来指定参数的传递方式:传址(by reference),值得注意的是,[color=green]C#要求函数被调用的地方显式的使用ref关键字修饰被传递的参数[/color],以下使用C#语言实现了Swap函数:
引用:
using System;
class Test
{
      [color=green]函数定义部分申明i和j的参数传递方式为传址(by reference)[/color]
    static void Swap([color=red]ref[/color] int x, [color=red]ref[/color] int y)
    {
     int temp = x;
      x = y;
          y = temp;
   }

   static void Main()
   {
     int i = 1, j = 2;
          [color=green]在函数被调用的地方显式的使用ref关键字修饰被传递的参数[/color]
     Swap([color=blue]ref[/color] i, [color=blue]ref[/color] j);
     Console.WriteLine("i = {0}, j = {1}", i, j);           
   }

程序结果:
i = 2, j = 1



程序员看到这条语句
引用:Swap([color=blue]ref[/color] i, [color=blue]ref[/color] j);

就可以意识到i和j的值可能发生变化。

[ 本帖最后由 missjiang 于 2007-5-16 16:58 编辑 ]


 missjiang 回复于:2007-05-16 16:48:13

BZ提的问题还是很有意义的,BZ自己给自己加个精吧,内举不避亲,呵呵。


 思一克 回复于:2007-05-16 19:27:33

谢谢.加精倒不用.

我出此贴目的主要目的不是为了批判引用,而是看引用的本质:

如果func(TYPE &i)定义了,那么
func(i);
本质上传递(放到STACK上的)给func的是i的地址,而不是i的数值本身.虽然写的是i

如果int &func()定义了,那么
i = func();
本质上func返回(通过DX,AX回送的)的是一个指针. 而i = 将该指针的内容取出给i.


从编译后的汇编层面上,引用和指针一样. 


所以可以说引用是编译器对指针进行的伪装或叫封装. 这一伪装,使一些学C的人因为有了指针数值概念而感有些疑惑. 

如果本贴可以解释这封装,就已经达到目的.

感谢以上的批评,质疑和评价.





引用:原帖由 missjiang 于 2007-5-16 16:48 发表
BZ提的问题还是很有意义的,BZ自己给自己加个精吧,内举不避亲,呵呵。 




 醉卧水云间 回复于:2007-05-16 22:25:16

引用的本质就是指针, 不但要用,而且要坚持用,越用越好用,不用白不用,用了也白用,看不明白拉倒,反正我能看明白就行拉.


 nully 回复于:2007-05-17 00:08:30

反正我就觉得C++这个传引用很废,换句话说,鸡肋。


 converse 回复于:2007-05-17 00:11:08

想起BS说的:把C++当成一门新的语言....设计上的很多考量不是只看某一个或者几个方面就可以下结论的。
这类讨论语言细节的问题我本不该关注太多的了。


 unixpm 回复于:2007-05-17 01:01:48

Java中全是引用,因为Java中没有指针的概念,而且Java所有的对象都分布在堆上,不会存在C++中返回

栈局部变量的问题。


引用的好处就是在于"别名"这个特点,必须初始化, 而且只能绑定一次,不需要对引用进行NULL判断,

这些都是对于指针的改进。




指针产生的许多问题,比如指针初始化,野指针,使用空指针等都是许多问题的根源。



其实Java很聪明,所有对象都放在堆上,对象只能用句柄(即引用)进行访问,实在是省掉了C++中许多的

问题。当然,因为java有垃圾回收机制的保证。

所以说学java的三个月的就可以出去找工作,c++三个月只能当玩具,


 思一克 回复于:2007-05-17 09:43:20

TO unixpm,

以下JAVA程序的var1, var2是传递引用还是数值?

因为我不懂JAVA,你能否告诉我如何编译(LINUX),我实验一下。



public void badSwap(int var1, int var2)
{
  int temp = var1;
  var1 = var2;
  var2 = temp;
}


引用:原帖由 unixpm 于 2007-5-17 01:01 发表
Java中全是引用,因为Java中没有指针的概念,而且Java所有的对象都分布在堆上,不会存在C++中返回

栈局部变量的问题。


引用的好处就是在于"别名"这个特点,必须初始化, 而且只能绑定一次,不需 ... 




 unixpm 回复于:2007-05-17 12:54:54

呵呵,Java里面的引用只能针对class类型,对于int等原始类型还是值表示的。

你上面那个例子当然是传值的啦。

这里还要补充一点,Java中的引用跟C++中的还是有区别的,其实在Java中一般叫句柄, 句柄其实也是一种类
型(个人认为)

看这个例子

Java Code:

public class Untitled1
{
    public static void main(String[] args)
    {
        String b = "123";

        modify(b);

        System.out.println(b);
    }

    public static void modify(String a)
    {
        a = "789";
    }
}

打印结果: 123

C++ Code :
#include <cstdlib>
#include <iostream>
#include <assert.h>
#include <string.h>
#include <vector>

void test(std::string& a)
{
     a = "789";
}

int main(int argc, char *argv[])
{   
    std::string b = "123";
    
    test(b);
    
    std::cout<<b<<std::endl;
    
    system("PAUSE");
    return EXIT_SUCCESS;
}

打印结果: 789


找个jdk, 先javac  xxx.java 生成 xxx.class文件

             然后java xxx.class就可以运行了


 wxPhoenix 回复于:2007-05-17 13:58:17

两个字: 肤浅!

将引用和指针、地址混为一谈, 不可理解, 如果引用在你的眼中只与地址之类的东西相关联,那完了,你是用不好引用的,引用被你浪费了!不理解一门语言所蕴涵的文化!
在我见过的语言中,C++可以用博大精深来形容!
如果你真正理解了一门语言,你可以发现它是多么的可爱!


 思一克 回复于:2007-05-17 14:08:38

关联也没有关系吗。

任何东西人们都有探究其本质和如何实现的倾向。通过这种探究(包括关联,比较,区别等)来深入了解和掌握。

引用不是C++新引进的新技术。而是早期结构化语言共同的方法。


 ypxing 回复于:2007-05-17 14:52:26

引用:原帖由 思一克 于 2007-5-17 14:08 发表
关联也没有关系吗。

任何东西人们都有探究其本质和如何实现的倾向。通过这种探究(包括关联,比较,区别等)来深入了解和掌握。

引用不是C++新引进的新技术。而是早期结构化语言共同的方法。 




不是没有关联,只是版主探索引用本质的方式有问题。
不能由它的实现来推它的本质
最后,都变成了0和1代码,版主能说它们的本质都是一样的吗?


 思一克 回复于:2007-05-17 15:06:58

高级语言的实现最终可以使它们区别开来的的最高层次的结果就是汇编。通过汇编看他们的实现和本质。

不恰当的比喻
要找对象看美女的“最终可以使它们区别开来的的最高层次的结果”是身材和相貌。研究到细胞级别是没有意义的,也是耽误事情的。

引用:原帖由 ypxing 于 2007-5-17 14:52 发表

不是没有关联,只是版主探索引用本质的方式有问题。
不能由它的实现来推它的本质
最后,都变成了0和1代码,版主能说它们的本质都是一样的吗? 




 ypxing 回复于:2007-05-17 15:17:18

引用:原帖由 思一克 于 2007-5-17 15:06 发表
高级语言的实现最终可以使它们区别开来的的最高层次的结果就是汇编。通过汇编看他们的实现和本质。

不恰当的比喻
要找对象看美女的“最终可以使它们区别开来的的最高层次的结果”是身材和相貌。研究到细胞级别 ... 



版主忽略了高级语言与汇编的区别,高级语言的引进是牺牲效率,但更便于理解和编写
如果只从汇编的角度去考量高级语言的一些特性,则有失偏颇

引用:高级语言的实现最终可以使它们区别开来的的最高层次的结果就是汇编。


这种说法也是很有问题的,起码不能说是最高层次


 思一克 回复于:2007-05-17 15:20:25

TO ypxing,

比如对与编译的语言的运行程序,你用PASCAL,FORTRAN或C写的,看反汇编可以区别他们。

通过汇编,可以精确了解高级语言某功能的实现和效率,等等许多。


 ypxing 回复于:2007-05-17 15:22:01

引用:原帖由 思一克 于 2007-5-17 15:06 发表
高级语言的实现最终可以使它们区别开来的的最高层次的结果就是汇编。通过汇编看他们的实现和本质。

不恰当的比喻
要找对象看美女的“最终可以使它们区别开来的的最高层次的结果”是身材和相貌。研究到细胞级别 ... 



是很不恰当
当有些技术出来以后(比如克隆),以前的认识可能就有问题了,
”身材和相貌“就可能不能区别了,更别说是什么最高层次了,


 ypxing 回复于:2007-05-17 15:24:21

引用:原帖由 思一克 于 2007-5-17 15:20 发表
TO ypxing,

比如对与编译的语言的运行程序,你用PASCAL,FORTRAN或C写的,看反汇编可以区别他们。

通过汇编,可以精确了解高级语言某功能的实现和效率,等等许多。 



你说的很对,可以了解它们的实现和效率
但是不能反推它们的本质
本质东西还是需要从C++引进它们的本意和语义来考量的


 pakix 回复于:2007-05-17 17:22:27

好N的帖,留个名儿,呵呵

感觉争论双方心里都有点气
不利于交留和学习
心里还堵的跟我一起深呼吸:
大口吸气,吸----------------------------------------------------------------------------------
大品呼气,呼----------------------------------------------------------------------------------

'{'
好点儿没?


 pakix 回复于:2007-05-17 18:14:18

我是一口气看完的
因为很过瘾,一直在想另一方会怎么回答
所以会有有点儿囫囵吞

我在下面这个地方起了一个反复

引用:原帖由 ypxing 于 2007-5-16 10:41 发表

个人认为引用的意义还是很明确的

一个程序员在不了解一个函数时对它进行调用,
如果不想让参数改变,调用前就应该对参数进行保存.

不用引用,只用指针的话,比如void f(int *p)
你只知道p不变,而根本不知道*p会不会变
同样会造成你所说的一些问题
int i;
f(&i); /*调用后你也不知道i变不变,要想i不变,只能事先保存它,因为f不是你定义的,你不能改变它,
           只能使用它*/

[ 本帖最后由 ypxing 于 2007-5-16 10:50 编辑 ]



这里给我的第一印象是ypxing占了上风,
心想版主会怎么回答,
这一想才发现ypxing的例子好像并不太能证明什么,更像一个陷阱,呵呵
ypxing前后的话都挺还挺中肯的,就是跟这个例子有关让我不太舒服,
而且好像就是从这帖后开始有点儿较针儿,呵呵


由于没有心理准备,这句话让我乐了半天
引用:原帖由 emacsnw 于 2007-5-16 11:47 发表
这样说的话,加const就一定有用吗?函数实现的时候还可以const_cast后继续修改参数。 



引用:原帖由 飞灰橙 于 2007-5-16 11:10 发表
不了解一个函数,居然还敢调用它!!!真佩服楼上的两位!!
既然调用一个函数,必然知道它的函数原型,必然知道该函数的引用参数有没有加const 


如果是读代码的话,
要是每个函数都要看原形,那也太痛苦了
我希望尽量少看,不想增加这种负担

引用:原帖由 DraculaW 于 2007-5-16 16:15 发表
java不就是全都是引用么


从语法上讲看到java应该会想到c
尽管人们一直在说那是对象传引用
但是事后我感觉这么说只能增加我学习过成中的混乱
直接说传值的会让我更舒服

只是旁观者的感受,仅供参考
有错误请指正,
有脾气请跟我一起深呼吸
大口吸气,吸----------------------------------------------------------------------------------
大品呼气,呼----------------------------------------------------------------------------------


 思一克 回复于:2007-05-18 09:08:41

java也有传值,不全是引用(参见unixpm贴)。

FORTRAN, 编译BASIC应该没有传值,全是引用。


 pakix 回复于:2007-05-18 10:35:10

java是通过引用(更象C里的指针)处理对象(object)的,
形参和实参之间并不存在引用

java里的函数参数一定是传值,不是引用
不管是基本数据类型还是对象
在对象的情况下是对"对象的引用"的传值,
在子函数里怎么改这个引用都不会影响实参,
但参数本身是引用,所以对象有可能会变,并且函数外面是可见的(这个就更象C里的指针了)

这两个是不一样的:以引用方式传递一个参数(传引用,这种方式在java里没有)和传递一个内容为引用的参数.


 思一克 回复于:2007-05-18 10:46:44

TO pakix,

JAVA的我不明白,但你说的和unixpm不一样。

“在对象的情况下是对"对象的引用"的传值,”, 如果一个OBJ非常大,如何传递?


 pakix 回复于:2007-05-18 10:57:35

引用(或者指针)不会传递对象,跟对象的大小没啥关系吧

只要是对象,在java中都是经传值的方式传进去这个对象的引用
尽量别把java中的引用这个词和C++的引用联系起来,和C中的指针联系起来,我当初就是这么混乱的


 思一克 回复于:2007-05-18 10:59:38

”只要是对象,在java中都是经传值的方式传进去这个对象的引用“

含义就是传对象的指针?


 pakix 回复于:2007-05-18 11:01:15

差不多吧,我是这么理解的


 pakix 回复于:2007-05-18 11:12:06

引用:原帖由 pakix 于 2007-5-17 18:14 发表
从语法上讲看到java应该会想到c
尽管人们一直在说那是对象传引用
但是事后我感觉这么说只能增加我学习过成中的混乱
直接说[color=Red]传值[/color]的会让我更舒服



唉太面了,语无伦次

我想表达的是:

Object o;
o被称为对对象的引用
但是事后我感觉这么说只能增加我学习过成中的混乱
直接说指针会让我更舒服

c++的引用的三个限制条件在java里都不存在,这里也让我感觉它更象指针


 flyingzhang 回复于:2007-05-18 13:17:33

这个争论似乎没有那么大的必要吧?在C++里面 引用的导入貌似是为了避免直接使用指针出现的空指针 野指针问题 至少提供了一种少犯错误的手段 但它毕竟只是一种手段 谁也不能保证你不会用错 能因为自己的误用就说这种手段无意义么?


 ypxing 回复于:2007-05-18 15:23:42

引用:原帖由 思一克 于 2007-5-18 10:59 发表
”只要是对象,在java中都是经传值的方式传进去这个对象的引用“

含义就是传对象的指针? 



理论上,Java, C只有一种传参方式,那就是传值
只是传值的内涵不一样而已
比如C中
void func(int * a);这个函数
本质上,指针也是一个变量而已
这个函数用传值的方式传递了指针这个值,
只是这个值比较特殊,它可以访问别的对象,从而改变别的对象的值.

其实不需要把指针看的很神秘,它也是一个普通变量,只是它可以间接访问别的变量而已

[ 本帖最后由 ypxing 于 2007-5-18 15:31 编辑 ]


 思一克 回复于:2007-05-18 15:49:42

同意ypxing上面说法

C中都是传值(PV)。传地址(指针)也是显式地传地址的值。
那么和PV对应的是传地址(PA或PP)。

PA的意思是你写在参数的的是个变量v, 比如func(v), 被传递的根本不是v本身,而是v的地址。这种东西才叫PA。
C中没有这样,C中写的传什么就是什么。

C++的引用就是传地址。


 思一克 回复于:2007-05-18 17:11:09

还有,我说的FORTRAN全是引用不正确。早期的FORTRAN好象是。后来的也使用传值了(保留传引用)。


 aple_smx 回复于:2007-05-19 11:36:18

同意BZ的建议,能不使用最好别使用引用。
BTW
int a = 10;
func0(int& a)
{
      ....
      int b = func1(a);
}
func1(int& b)
{
      ......
      b = 3000;
}

在这种情况下想要在func1中改变a的值,是不是func0,func1参数都要定义成引用。

我在开发中调试别人的代码遇到func0使用引用,func1没用导致a的值不变。


 yuxh 回复于:2007-05-20 18:48:23

我不知道思一克在工作中是否用C++做开发的?如果用C++做过几年开发,应该不会有这样的观点吧?
站在C或汇编的角度去看C++或JAVA,有很多地方的确很难理解:这不是画蛇添足么?除了降低性能,还有什么好处呢?
但C++自有他的理由:通过封装,实现更安全、更健壮的代码,因而更有利于企业级的开发。
最可怕的代码不是纯C的,更不是纯C++的,而是C的代码却套了一个C++的壳。
用引用而不是指针的一个合理的理由是避免了内存的管理,也避免了内存的直接访问。在一个不用指针的系统中,指针越界、内存泻漏、溢出的可能性微乎其微,而这是程序不稳定的最大元凶。
我对C++代码的要求是不显式出现任何指针的使用,如果要使用指针,则必须在类里封装,使用完后立即释放或在析构函数中释放。


 思一克 回复于:2007-05-21 09:40:19

to yuxh,

我没有用C++作过开发。主要用C。因此我难免有用C眼光看C++的偏颇之处。
同意你的观点,千万不要混合C与C++。一个C程序,偏偏来上个引用传递,完全是自找麻烦。

我不用C++,主要是考虑代码效率C++比C差,因为系统软件效率是第一位的。

看我另外一个新帖子,你熟悉C++,看怎么能写出符合你要求的高效率的C++代码。


 8pm 回复于:2007-05-21 17:49:07

个人看法
我觉得引用存在的目的是
1.为了方便实现运算符的重载,比如以非类成员方式重载赋值家族除 = 以外的运算符,或者运算符 [] 的返回值(否则不能用在 = 左边)等
2.c++的多态性是通过使用指针和引用实现的,因为不允许空引用,引用提供了相对安全的方法(虽然似乎大部分人都在使用基类的指针而不是引用)


 NewCore 回复于:2007-05-24 23:24:40

引用:原帖由 思一克 于 2007-5-21 09:40 发表
to yuxh,

我没有用C++作过开发。主要用C。因此我难免有用C眼光看C++的偏颇之处。
同意你的观点,千万不要混合C与C++。一个C程序,偏偏来上个引用传递,完全是自找麻烦。

我不用C++,主要是考虑代码效率C++ ... 



LZ既然都提到了“系统软件”中的限制,那么当然该清楚,应用范围不同,自然不能以同一标准来要求。我不知道你对C++的疑问来自于C++本身,还是“C中的C++”?


 思一克 回复于:2007-05-25 08:59:08

TO LS,

我主要批判的是“C中的C++”。因为这成为一个问题。有不少人开发不直接C++,而是C/C++混合,本来用C的程序非利用C++的东西。

如果一切按C++规范编C++程序,我就没有资格和道理来批判了。

还有,一个没有指针的语言必须要有引用。C++有指针,但不鼓励使用,否则又成C的程序了。还有其他的功能必须有引用才可以。




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=936360
转载请注明作者名及原文出处



收藏本页到: