Chinaunix

标题: C++的引用的传递和返回兼批判 [打印本页]

作者: 思一克    时间: 2007-05-16 09:16
标题: C++的引用的传递和返回兼批判
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。因此使程序更不容易读。

欢迎批评和讨论。




  1. #include <stdio.h>
  2. int func0(int &i)
  3. {
  4.    i++;
  5.    return 100;
  6. }
  7. int func1(int *ip)
  8. {
  9.     (*ip) ++;
  10.    return 101;
  11. }
  12. int& func2(int i)
  13. {
  14. static int ii = 123;
  15.     return ii;
  16. }

  17. main()
  18. {
  19. int i, j;

  20.     i = 4;
  21.     j = 104;
  22.     j = func0(i);
  23.     //printf("i = %d j = %d\n", i, j);
  24.     j = func1(&i);
  25.     //printf("i = %d j = %d\n", i, j);
  26.     j = func2(9);
  27.     //printf("i = %d j = %d\n", i, j);
  28. }

  29.    .file   "t2.c"
  30.         .text
  31.         .align 2
  32. .globl _Z5func0Ri
  33.         .type   _Z5func0Ri,@function
  34. _Z5func0Ri:
  35. .LFB2:
  36.         pushl   %ebp
  37. .LCFI0:
  38.         movl    %esp, %ebp
  39. .LCFI1:
  40.         movl    8(%ebp), %eax
  41.         incl    (%eax)
  42.         movl    $100, %eax
  43.         leave
  44.         ret
  45. .LFE2:
  46. .Lfe1:
  47.         .size   _Z5func0Ri,.Lfe1-_Z5func0Ri
  48.         .align 2
  49. .globl _Z5func1Pi
  50.         .type   _Z5func1Pi,@function
  51. _Z5func1Pi:
  52. .LFB4:
  53.         pushl   %ebp
  54. .LCFI2:
  55.         movl    %esp, %ebp
  56. .LCFI3:
  57.         movl    8(%ebp), %eax
  58.         incl    (%eax)
  59.         movl    $101, %eax
  60.         leave
  61.         ret
  62. .LFE4:
  63. .Lfe2:
  64.         .size   _Z5func1Pi,.Lfe2-_Z5func1Pi
  65.         .align 2
  66. .globl _Z5func2i
  67.         .type   _Z5func2i,@function
  68. _Z5func2i:
  69. .LFB6:
  70.         pushl   %ebp
  71. .LCFI4:
  72.         movl    %esp, %ebp
  73. .LCFI5:
  74.         subl    $4, %esp
  75. .LCFI6:
  76.         movl    $123, -4(%ebp)
  77.         leal    -4(%ebp), %eax
  78.         leave
  79.         ret
  80. .LFE6:
  81. .Lfe3:
  82.         .size   _Z5func2i,.Lfe3-_Z5func2i
  83.         .align 2
  84. .globl main
  85.         .type   main,@function
  86. main:
  87. .LFB8:
  88.         pushl   %ebp
  89. .LCFI7:
  90.         movl    %esp, %ebp
  91. .LCFI8:
  92.         subl    $8, %esp
  93. .LCFI9:
  94.         andl    $-16, %esp
  95.         movl    $0, %eax
  96.         subl    %eax, %esp
  97.         movl    $4, -4(%ebp)
  98.         movl    $104, -8(%ebp)
  99.         subl    $12, %esp
  100.         leal    -4(%ebp), %eax
  101.         pushl   %eax
  102. .LCFI10:
  103.         call    _Z5func0Ri
  104.         addl    $16, %esp
  105.         movl    %eax, -8(%ebp)
  106.         subl    $12, %esp
  107.         leal    -4(%ebp), %eax
  108.         pushl   %eax
  109.         call    _Z5func1Pi
  110.         addl    $16, %esp
  111.         movl    %eax, -8(%ebp)
  112.         subl    $12, %esp
  113.         pushl   $9
  114.         call    _Z5func2i
  115.         addl    $16, %esp
  116.         movl    (%eax), %eax
  117.         movl    %eax, -8(%ebp)
  118.         movl    $0, %eax
  119.         leave
  120.         ret
  121. .LFE8:
  122. .Lfe4:
  123.         .size   main,.Lfe4-main
  124.         .ident  "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
复制代码

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


不敢苟同


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


不知所云
作者: 思一克    时间: 2007-05-16 09:43
int ii = 123;
return ii;

这2行你不知道是返回的什么,是返回的123还是一个地址。如果没有引用,返回的一定是数值。
作者: emacsnw    时间: 2007-05-16 09:45
我觉得C++的引用比指针不够灵活在两点:没有null引用和不能重新绑定。
不然的话,很多时候传递引用会比传递指针更方便一些。
作者: emacsnw    时间: 2007-05-16 09:48
原帖由 思一克 于 2007-5-15 17:43 发表
int ii = 123;
return ii;

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


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

  1. int x = 10;
  2. f(x);
  3. // what's the value of x now?
复制代码


在C里面,这个代码几乎保证 f 执行后x的值没变,而C++中则不一定。
作者: ypxing    时间: 2007-05-16 10:00
原帖由 思一克 于 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
ii是外面的变量,没有危险。

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

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

手下会给你工作。你不用管他如何做的,你知道i,j他不会影响,pi和返回是给你的结果。
作者: ypxing    时间: 2007-05-16 10:07
原帖由 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
原帖由 思一克 于 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
原帖由 思一克 于 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
原帖由 思一克 于 2007-5-16 10:07 发表
C中返回STACK变量指针的危险和引用造成的危险和模糊不是一回事。


能否给个详细说明
作者: 思一克    时间: 2007-05-16 10:21
你说的这个(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
原帖由 思一克 于 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
原帖由 思一克 于 2007-5-16 10:21 发表
你说的这个(pi)我知道。

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


版主当然会知道这个问题了
但是很多人是很迷惑的,一点也不差于对引用的迷惑
作者: 思一克    时间: 2007-05-16 10:36
我认为引用不是一个新东西。而是早期结构化程序比如FORTRAN等的一个固定的唯一的做法。
所以是一个老的传递方法。

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

至于C++中是不得已而引入的。如果不引入,C++的某些事情不好做。
作者: ypxing    时间: 2007-05-16 10:41
原帖由 思一克 于 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
是这样的:

如果没有C的传递数值和传递地址概念,而如原来语言那样一切都是引用,那么引用也没有什么不明确的问题。
不好的问题是在C程序中才有所显现。
作者: ypxing    时间: 2007-05-16 10:52
原帖由 思一克 于 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
如果很多c++的编译器都把引用当指针来实现,那么在实现上引用不是新东西(哪些c++编译器不用指针实现引用?有知道的讲一下。)。我觉得引用在c++上主要是语意上的:1、不能空,2、必须初始化,3、不能改变。有的书把他叫别名。所以如果从下往上推,就失去这些语意了。
作者: ypxing    时间: 2007-05-16 11:00
原帖由 aXe 于 2007-5-16 10:57 发表
如果很多c++的编译器都把引用当指针来实现,那么在实现上引用不是新东西(哪些c++编译器不用指针实现引用?有知道的讲一下。)。我觉得引用在c++上主要是语意上的:1、不能空,2、必须初始化,3、不能改变。有的书 ...


是的,不能根据对引用的实现来反推引用的本质
作者: missjiang    时间: 2007-05-16 11:04
原帖由 思一克 于 2007-5-16 09:16 发表
不是因为引用概念复杂
比如往前看才能确定。


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


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

  1. int x = 10;
  2. f(x);
  3. // what's the value of x now?
复制代码


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

同意以上的看法。
作者: 飞灰橙    时间: 2007-05-16 11:10
原帖由 ypxing 于 2007-5-16 10:41 发表


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

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

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


不了解一个函数,居然还敢调用它!!!真佩服楼上的两位!!
既然调用一个函数,必然知道它的函数原型,必然知道该函数的引用参数有没有加const
作者: zhujiang73    时间: 2007-05-16 11:10
原帖由 思一克 于 2007-5-16 10:36 发表
如果不引入,C++的某些事情不好做。


这就是引用的好处,不然  std::cout << " Hello World " << std::endl  都写不成了。
作者: ypxing    时间: 2007-05-16 11:17
原帖由 飞灰橙 于 2007-5-16 11:10 发表


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



这是版主提出的问题,呵呵
作者: 飞灰橙    时间: 2007-05-16 11:18
原帖由 ypxing 于 2007-5-16 11:17 发表



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


窃以为版主老大过渡思考,钻到牛角尖里去了
作者: net_robber    时间: 2007-05-16 11:26
发现一个以前没有注意到的问题

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

以前很少用这东西,呼呼
作者: 林杰杰    时间: 2007-05-16 11:43
引用就是被引用变量的别名,是同一个玩意,那他们地址相同有什么奇怪的。
就如小明的爸爸和小明妈妈的丈夫住在XX楼一样。
作者: ypxing    时间: 2007-05-16 11:44
原帖由 林杰杰 于 2007-5-16 11:43 发表
就如小明的爸爸和小明妈妈的丈夫住在XX楼一样。


不一定是同一个人呦
作者: 林杰杰    时间: 2007-05-16 11:45
原帖由 ypxing 于 2007-5-16 11:44 发表


不一定是同一个人呦

考虑没有离过婚的大多数情况。。。。。。。
作者: emacsnw    时间: 2007-05-16 11:47
原帖由 飞灰橙 于 2007-5-15 19:10 发表


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


这样说的话,加const就一定有用吗?函数实现的时候还可以const_cast后继续修改参数。
作者: 思一克    时间: 2007-05-16 11:53
没有专到牛角尖。

这样的结论应该没有人反对
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
原帖由 思一克 于 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
原帖由 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
原帖由 ypxing 于 2007-5-16 11:58 发表


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

....

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

人家的意思,在你这种情况下,是&i不会变。
参数本身不变,但是与参数相关的其它变量会不会变,这跟它没关系。
作者: 思一克    时间: 2007-05-16 12:00
int func(int *i);
i 志向的地方可能变也可能不变,但是这是明显告诉你的,那里可能被修改。
引用就不同了,没有提供任何信息。除非你找到func本身
作者: ypxing    时间: 2007-05-16 12:03
原帖由 emacsnw 于 2007-5-16 12:00 发表


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


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

所以指针也带来了模糊性
作者: 思一克    时间: 2007-05-16 12:05
”所以指针也带来了模糊性“ 这个“模糊”是不需要参考其它地方而明显告诉你的。
引用func(i),没有告诉你。除非你去看func本身。
作者: ypxing    时间: 2007-05-16 12:07
原帖由 思一克 于 2007-5-16 12:00 发表
int func(int *i);
i 志向的地方可能变也可能不变,但是这是明显告诉你的,那里可能被修改。
引用就不同了,没有提供任何信息。除非你找到func本身


安全起见,你就应该认为引用的时候是可能改变的(正如你默认指针会改变i一样)
所以,如果要想调用func时不改变i,要调用前保存i,调用后回复i
作者: connet    时间: 2007-05-16 12:08
原帖由 ypxing 于 2007-5-16 12:03 发表


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

所以指针也带来了模糊性

非也
func(int *i) 这样定义, i 一定是改变, 也许改变后的值 与先前一样。
否则就应该定义成 func(int i)
最最最基本的原则。
作者: ypxing    时间: 2007-05-16 12:10
原帖由 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
”你这种认识是制造bug的源泉“

该点引用应该远高于指针。想想所有传递都是引用的语言会是怎样的情形。
作者: ypxing    时间: 2007-05-16 12:18
请大家使用引用的优点,
避免它的缺点,
如果调用一个函数不想让参数改变,请先保存,在恢复
如果不知道函数原型和实现,请默认参数是会改变的

这也算个小原则吧
作者: ypxing    时间: 2007-05-16 12:19
原帖由 思一克 于 2007-5-16 12:12 发表
”你这种认识是制造bug的源泉“

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

不,我说的是这种认识:
即“有一些什么原则,然后想当然的认为所有人会遵守,...."
作者: 飞灰橙    时间: 2007-05-16 12:23
原帖由 emacsnw 于 2007-5-16 11:47 发表


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


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

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


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

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

  1. class CharArray
  2. {
  3. public:
  4.     ....
  5.     char& operator[](int position);        //可用于表达式中的左值
  6.     const char& operator[](int position) const;
  7. private:
  8.     char* _datas;
  9. };
复制代码


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

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

另外,引用和其所引用对象间建立了唯一的联系.而指针并没有明确指定这种关系.
作者: redac    时间: 2007-05-16 12:58
原帖由 思一克 于 2007-5-16 12:00 发表
int func(int *i);
i 志向的地方可能变也可能不变,但是这是明显告诉你的,那里可能被修改。
引用就不同了,没有提供任何信息。除非你找到func本身



确实是
作者: I/0    时间: 2007-05-16 13:01
下面的内容摘自 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:


  1. void increment(int& aa)
  2. {
  3.         aa++;
  4. }

  5. void f()
  6. {
  7.         int x = 1;
  8.         increment(x); // x = 2
  9. }
复制代码


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:


  1. int next(int p)
  2. {
  3.         return p+1;
  4. }

  5. void incr(int* p)
  6. {
  7.         (*p)++;
  8. }

  9. void g()
  10. {
  11.         int x = 1;
  12.         increment(x); // x = 2
  13.         x = next(x); // x = 3
  14.         incr(&x); // x = 4
  15. }
复制代码


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


不敢苟同

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

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

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

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

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

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

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

我个人狭隘地认为,无法读懂的程序,应该不会简单
作者: 思一克    时间: 2007-05-16 15:39
引用使得程序readable 变差,以下书中英语也是这么说的: (I/O 的引用)

”下面的内容摘自 TCPL 5.5 References“
作者: ypxing    时间: 2007-05-16 15:45
原帖由 思一克 于 2007-5-16 15:39 发表
引用使得程序readable 变差,以下书中英语也是这么说的: (I/O 的引用)

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


引用也有使readable变得好的情况,呵呵
作者: 飞灰橙    时间: 2007-05-16 15:48
原帖由 ypxing 于 2007-5-16 15:45 发表


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


引用作为参数,相比指针,
可以不需要判断 if (pointer == NULL),使程序更简洁。
作者: 思一克    时间: 2007-05-16 15:56
引用的本质就是指针。肯定有某些好处和必要性,在C++中。

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

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

如果程序一开始就是C++的,那当然不在我说的范围之内。该使用当然要用。
作者: DraculaW    时间: 2007-05-16 16:15
原帖由 思一克 于 2007-5-16 12:12 发表
”你这种认识是制造bug的源泉“

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



java不就是全都是引用么
作者: missjiang    时间: 2007-05-16 16:26
原帖由 思一克 于 2007-5-16 15:56 发表
引用的本质就是指针。肯定有某些好处和必要性,在C++中。

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

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

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

  1. 使用引用,代码较简洁
  2. void swap(int & a, int & b)
  3. {
  4.    int temp = a;
  5.    a = b;
  6.    b = temp;
  7. }

  8. 使用指针,代码不直观
  9. void swap(int * a, int * b)
  10. {
  11.    int temp = *a;
  12.    *a = *b;
  13.    *b = temp;
  14. }
复制代码

当我还是一个对地址概念不清楚的C语言初学者的时候,我觉得使用"引用"实现的swap比使用指针实现的swap的可读性要好一点。但是在熟悉了指针后,我倾向于使用指针实现swap函数,原因在于:如果用指针实现swap函数,调用swap函数的语句为swap(&a, &b),提醒程序员a、b的值有可能发生改变;而如果使用引用实现swap函数,调用swap函数的语句为swap(a, b),程序员有可能误认为a、b的值没有发生变化。
作者: missjiang    时间: 2007-05-16 16:44
C#中引入了ref关键字用来指定参数的传递方式:传址(by reference),值得注意的是,C#要求函数被调用的地方显式的使用ref关键字修饰被传递的参数,以下使用C#语言实现了Swap函数:
using System;
class Test
{
      函数定义部分申明i和j的参数传递方式为传址(by reference)
    static void Swap(ref int x, ref int y)
    {
     int temp = x;
      x = y;
          y = temp;
   }

   static void Main()
   {
     int i = 1, j = 2;
          在函数被调用的地方显式的使用ref关键字修饰被传递的参数
     Swap(ref i, ref j);
     Console.WriteLine("i = {0}, j = {1}", i, j);           
   }
}
程序结果:
i = 2, j = 1


程序员看到这条语句
Swap(ref i, ref j);

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

[ 本帖最后由 missjiang 于 2007-5-16 16:58 编辑 ]
作者: missjiang    时间: 2007-05-16 16:48
BZ提的问题还是很有意义的,BZ自己给自己加个精吧,内举不避亲,呵呵。
作者: 思一克    时间: 2007-05-16 19:27
谢谢.加精倒不用.

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

如果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
引用的本质就是指针, 不但要用,而且要坚持用,越用越好用,不用白不用,用了也白用,看不明白拉倒,反正我能看明白就行拉.
作者: nully    时间: 2007-05-17 00:08
反正我就觉得C++这个传引用很废,换句话说,鸡肋。
作者: converse    时间: 2007-05-17 00:11
想起BS说的:把C++当成一门新的语言....设计上的很多考量不是只看某一个或者几个方面就可以下结论的。
这类讨论语言细节的问题我本不该关注太多的了。
作者: unixpm    时间: 2007-05-17 01:01
Java中全是引用,因为Java中没有指针的概念,而且Java所有的对象都分布在堆上,不会存在C++中返回

栈局部变量的问题。


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

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




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



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

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

所以说学java的三个月的就可以出去找工作,c++三个月只能当玩具,
作者: 思一克    时间: 2007-05-17 09:43
TO unixpm,

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

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



  1. public void badSwap(int var1, int var2)
  2. {
  3.   int temp = var1;
  4.   var1 = var2;
  5.   var2 = temp;
  6. }
复制代码

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

栈局部变量的问题。


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

作者: unixpm    时间: 2007-05-17 12:54
标题: 回复 63楼 思一克 的帖子
呵呵,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
标题: 为什么会有人会批判C++中的引用?
两个字: 肤浅!

将引用和指针、地址混为一谈, 不可理解, 如果引用在你的眼中只与地址之类的东西相关联,那完了,你是用不好引用的,引用被你浪费了!不理解一门语言所蕴涵的文化!
在我见过的语言中,C++可以用博大精深来形容!
如果你真正理解了一门语言,你可以发现它是多么的可爱!
作者: 思一克    时间: 2007-05-17 14:08
关联也没有关系吗。

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

引用不是C++新引进的新技术。而是早期结构化语言共同的方法。
作者: ypxing    时间: 2007-05-17 14:52
原帖由 思一克 于 2007-5-17 14:08 发表
关联也没有关系吗。

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

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



不是没有关联,只是版主探索引用本质的方式有问题。
不能由它的实现来推它的本质
最后,都变成了0和1代码,版主能说它们的本质都是一样的吗?
作者: 思一克    时间: 2007-05-17 15:20
TO ypxing,

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

通过汇编,可以精确了解高级语言某功能的实现和效率,等等许多。
作者: ypxing    时间: 2007-05-17 15:24
原帖由 思一克 于 2007-5-17 15:20 发表
TO ypxing,

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

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


你说的很对,可以了解它们的实现和效率
但是不能反推它们的本质
本质东西还是需要从C++引进它们的本意和语义来考量的
作者: pakix    时间: 2007-05-17 17:22
好N的帖,留个名儿,呵呵

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

'{'
好点儿没?
作者: pakix    时间: 2007-05-17 18:14
我是一口气看完的
因为很过瘾,一直在想另一方会怎么回答
所以会有有点儿囫囵吞

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

原帖由 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
java也有传值,不全是引用(参见unixpm贴)。

FORTRAN, 编译BASIC应该没有传值,全是引用。
作者: pakix    时间: 2007-05-18 10:35
java是通过引用(更象C里的指针)处理对象(object)的,
形参和实参之间并不存在引用

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

这两个是不一样的:以引用方式传递一个参数(传引用,这种方式在java里没有)和传递一个内容为引用的参数.
作者: 思一克    时间: 2007-05-18 10:46
TO pakix,

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

“在对象的情况下是对"对象的引用"的传值,”, 如果一个OBJ非常大,如何传递?
作者: pakix    时间: 2007-05-18 10:57
引用(或者指针)不会传递对象,跟对象的大小没啥关系吧

只要是对象,在java中都是经传值的方式传进去这个对象的引用
尽量别把java中的引用这个词和C++的引用联系起来,和C中的指针联系起来,我当初就是这么混乱的
作者: 思一克    时间: 2007-05-18 10:59
”只要是对象,在java中都是经传值的方式传进去这个对象的引用“

含义就是传对象的指针?
作者: pakix    时间: 2007-05-18 11:01
差不多吧,我是这么理解的
作者: pakix    时间: 2007-05-18 11:12
原帖由 pakix 于 2007-5-17 18:14 发表
从语法上讲看到java应该会想到c
尽管人们一直在说那是对象传引用
但是事后我感觉这么说只能增加我学习过成中的混乱
直接说传值的会让我更舒服


唉太面了,语无伦次

我想表达的是:

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

c++的引用的三个限制条件在java里都不存在,这里也让我感觉它更象指针
作者: flyingzhang    时间: 2007-05-18 13:17
标题: 发大水了。。。
这个争论似乎没有那么大的必要吧?在C++里面 引用的导入貌似是为了避免直接使用指针出现的空指针 野指针问题 至少提供了一种少犯错误的手段 但它毕竟只是一种手段 谁也不能保证你不会用错 能因为自己的误用就说这种手段无意义么?
作者: ypxing    时间: 2007-05-18 15:23
原帖由 思一克 于 2007-5-18 10:59 发表
”只要是对象,在java中都是经传值的方式传进去这个对象的引用“

含义就是传对象的指针?


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

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

[ 本帖最后由 ypxing 于 2007-5-18 15:31 编辑 ]
作者: 思一克    时间: 2007-05-18 15:49
同意ypxing上面说法

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

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

C++的引用就是传地址。
作者: 思一克    时间: 2007-05-18 17:11
还有,我说的FORTRAN全是引用不正确。早期的FORTRAN好象是。后来的也使用传值了(保留传引用)。
作者: aple_smx    时间: 2007-05-19 11:36
提示: 作者被禁止或删除 内容自动屏蔽
作者: yuxh    时间: 2007-05-20 18:48
我不知道思一克在工作中是否用C++做开发的?如果用C++做过几年开发,应该不会有这样的观点吧?
站在C或汇编的角度去看C++或JAVA,有很多地方的确很难理解:这不是画蛇添足么?除了降低性能,还有什么好处呢?
但C++自有他的理由:通过封装,实现更安全、更健壮的代码,因而更有利于企业级的开发。
最可怕的代码不是纯C的,更不是纯C++的,而是C的代码却套了一个C++的壳。
用引用而不是指针的一个合理的理由是避免了内存的管理,也避免了内存的直接访问。在一个不用指针的系统中,指针越界、内存泻漏、溢出的可能性微乎其微,而这是程序不稳定的最大元凶。
我对C++代码的要求是不显式出现任何指针的使用,如果要使用指针,则必须在类里封装,使用完后立即释放或在析构函数中释放。
作者: 思一克    时间: 2007-05-21 09:40
to yuxh,

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

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

看我另外一个新帖子,你熟悉C++,看怎么能写出符合你要求的高效率的C++代码。
作者: 8pm    时间: 2007-05-21 17:49
个人看法
我觉得引用存在的目的是
1.为了方便实现运算符的重载,比如以非类成员方式重载赋值家族除 = 以外的运算符,或者运算符 [] 的返回值(否则不能用在 = 左边)等
2.c++的多态性是通过使用指针和引用实现的,因为不允许空引用,引用提供了相对安全的方法(虽然似乎大部分人都在使用基类的指针而不是引用)
作者: NewCore    时间: 2007-05-24 23:24
原帖由 思一克 于 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
TO LS,

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

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

还有,一个没有指针的语言必须要有引用。C++有指针,但不鼓励使用,否则又成C的程序了。还有其他的功能必须有引用才可以。
作者: alloney    时间: 2007-07-11 18:03
看完帖子,感觉LZ有标题党的嫌疑
作者: zhkl125    时间: 2007-07-11 18:21
提示: 作者被禁止或删除 内容自动屏蔽
作者: redhat008    时间: 2007-07-11 20:09
原帖由 flyingzhang 于 2007-5-18 13:17 发表
这个争论似乎没有那么大的必要吧?在C++里面 引用的导入貌似是为了避免直接使用指针出现的空指针 野指针问题 至少提供了一种少犯错误的手段 但它毕竟只是一种手段 谁也不能保证你不会用错 能因为自己的 ...



我觉得这个问题最好看BS怎么说....
在<<C++设计与演化>> 第三章 3.7 引用  
引入引用主要是为了支持运算符的重载....
作者: lywaml    时间: 2007-12-06 00:51
原帖由 coldwarm 于 2007-5-16 12:29 发表

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


我赞同这点。引用的主要功用是在于此的。

大家可以对比下在C++之后设计的若干高级语言提出的“引用类型”和“值类型”的概念。
作者: oyd_admin    时间: 2007-12-06 12:32
原帖由 思一克 于 2007-5-25 08:59 发表
TO LS,

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

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

还有 ...


C++最好是用来写库
不是用来写流程的。
因此最最理想和容易理解维护的程序不是纯C++的,也不是纯C的,而是C的壳,里面包着C++程序。

例如这种程序就很容易维护:
int main() {
cout<<"hello world!"<<endl;
}


相反,这种噩梦般的程序就是所谓纯C++程序:
class CMyApp {
CMyApp(){
cout<<"hello,world"<<endl;
}
};
CMyApp theApp;





欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2