在学习新的编程语言时,您可能遇到的一个可能的障碍是,默认情况下,该语言是按值传递还是按引用传递 。
所以这是我对你们所有人的问题,用你最喜欢的语言,它是如何实际完成的? 什么是可能的陷阱 ?
在学习新的编程语言时,您可能遇到的一个可能的障碍是,默认情况下,该语言是按值传递还是按引用传递 。
所以这是我对你们所有人的问题,用你最喜欢的语言,它是如何实际完成的? 什么是可能的陷阱 ?
这是我对Java编程语言的贡献。
首先是一些代码:
public void swap(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
调用此方法将导致:
int pi = 3;
int everything = 42;
swap(pi, everything);
System.out.println("pi: " + pi);
System.out.println("everything: " + everything);
"Output:
pi: 3
everything: 42"
即使使用'真实'对象也会显示类似的结果:
public class MyObj {
private String msg;
private int number;
//getters and setters
public String getMsg() {
return this.msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getNumber() {
return this.number;
}
public void setNumber(int number) {
this.number = number;
}
//constructor
public MyObj(String msg, int number) {
setMsg(msg);
setNumber(number);
}
}
public static void swap(MyObj x, MyObj y)
{
MyObj tmp = x;
x = y;
y = tmp;
}
public static void main(String args[]) {
MyObj x = new MyObj("Hello world", 1);
MyObj y = new MyObj("Goodbye Cruel World", -1);
swap(x, y);
System.out.println(x.getMsg() + " -- "+ x.getNumber());
System.out.println(y.getMsg() + " -- "+ y.getNumber());
}
"Output:
Hello world -- 1
Goodbye Cruel World -- -1"
因此很明显,Java通过值传递其参数,因为pi的值和所有内容以及MyObj对象都没有交换。 请注意,“按值”是java将参数传递给方法的唯一方法。 (例如,像c ++这样的语言允许开发人员在参数类型之后使用' & '通过引用传递参数)
现在是棘手的部分 ,或者至少会混淆大多数新Java开发人员的部分:(借用javaworld )
原作者:Tony Sintes
public void tricky(Point arg1, Point arg2)
{
arg1.x = 100;
arg1.y = 100;
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
}
public static void main(String [] args)
{
Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);
System.out.println("X: " + pnt1.x + " Y: " +pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);
System.out.println(" ");
tricky(pnt1,pnt2);
System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);
}
"Output
X: 0 Y: 0
X: 0 Y: 0
X: 100 Y: 100
X: 0 Y: 0"
棘手成功改变了pnt1的价值! 这意味着对象是通过引用传递的,事实并非如此! 正确的陈述是: Object引用按值传递。
更多来自Tony Sintes:
该方法成功地改变了pnt1的值,即使它是按值传递的; 但是,pnt1和pnt2的交换失败了! 这是混乱的主要原因。 在main()方法中,pnt1和pnt2只不过是对象引用。 当您将pnt1和pnt2传递给tricky()方法时,Java会像任何其他参数一样按值传递引用。 这意味着传递给方法的引用实际上是原始引用的副本。 下面的图1显示了Java将对象传递给方法后指向同一对象的两个引用。
(来源: javaworld.com )
结论或长话短说:
有用的链接:
这是c#编程语言的另一篇文章
c# 按值传递其参数(默认情况下)
private void swap(string a, string b) {
string tmp = a;
a = b;
b = tmp;
}
因此调用此版本的swap将没有结果:
string x = "foo";
string y = "bar";
swap(x, y);
"output:
x: foo
y: bar"
但是, 与java不同, c# 确实为开发人员提供了通过引用传递参数的机会,这是通过在参数类型之前使用'ref'关键字来完成的:
private void swap(ref string a, ref string b) {
string tmp = a;
a = b;
b = tmp;
}
此交换将更改引用参数的值:
string x = "foo";
string y = "bar";
swap(x, y);
"output:
x: bar
y: foo"
c#也有一个out关键字 ,ref和out之间的区别是微妙的。 来自msdn:
取出参数的方法的调用者不需要分配给在调用之前作为out参数传递的变量; 但是,被调用者需要在返回之前分配out参数。
和
相比之下, ref参数被认为是被调用者最初分配的。 因此,被调用者在使用之前不需要分配给ref参数。 Ref参数传入和传出方法。
像java一样,一个小的缺陷是仍然可以使用内部方法更改通过值传递的对象
结论:
有用的链接:
Python使用pass-by-value,但由于所有这些值都是对象引用,因此净效果类似于pass-by-reference。 但是,Python程序员更多地考虑对象类型是可变的还是不可变的 。 可变对象可以就地更改(例如,字典,列表,用户定义的对象),而不可变对象不能(例如,整数,字符串,元组)。
以下示例显示了一个传递两个参数的函数,一个不可变字符串和一个可变列表。
>>> def do_something(a, b):
... a = "Red"
... b.append("Blue")
...
>>> a = "Yellow"
>>> b = ["Black", "Burgundy"]
>>> do_something(a, b)
>>> print a, b
Yellow ['Black', 'Burgundy', 'Blue']
该行a = "Red"
只是创建一个本地名称, a
,对于字符串值"Red"
和对传入的参数没有影响(现在隐藏,作为a
必须是指从以本地名称再打开) 。 无论参数是可变的还是不可变的,赋值都不是就地操作。
b
参数是对可变列表对象的引用,而.append()
方法执行列表的就地扩展,增加新的"Blue"
字符串值。
(因为字符串对象是不可变的,所以它们没有任何支持就地修改的方法。)
一旦函数返回,重新分配a
已经没有效果,同时延长b
清楚地表明传址引用样式调用的语义。
如前所述,即使a
的参数是一个可变类型,函数内的重新赋值也不是就地操作,因此传递的参数值不会改变:
>>> a = ["Purple", "Violet"]
>>> do_something(a, b)
>>> print a, b
['Purple', 'Violet'] ['Black', 'Burgundy', 'Blue', 'Blue']
如果您不希望被调用函数修改列表,则应使用不可变元组类型(由字面形式的括号标识,而不是方括号),这不支持就地.append()
方法:
>>> a = "Yellow"
>>> b = ("Black", "Burgundy")
>>> do_something(a, b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in do_something
AttributeError: 'tuple' object has no attribute 'append'
由于我还没有看过Perl的答案,我以为我会写一个。
在引擎盖下,Perl可以有效地作为传递引用。 作为函数调用参数的变量是引用传递的,常量作为只读值传递,表达式的结果作为临时值传递。 通过@_
列表赋值构建参数列表或通过shift
来构造参数列表的惯用惯用法倾向于将其隐藏在用户之外,从而给出了pass-by-value的外观:
sub incr {
my ( $x ) = @_;
$x++;
}
my $value = 1;
incr($value);
say "Value is now $value";
这将打印Value is now 1
因为$x++
增加了在incr()
函数中声明的词法变量,而不是传入的变量。这种按值传递的样式通常是大多数时候想要的,作为函数修改他们的参数在Perl中很少见,应该避免使用样式。
但是,如果由于某种原因特别需要这种行为,可以通过直接操作@_
数组的元素来实现,因为它们将是传递给函数的变量的别名。
sub incr {
$_[0]++;
}
my $value = 1;
incr($value);
say "Value is now $value";
这次它将打印Value is now 2
,因为$_[0]++
表达式增加了实际的$value
变量。 它的工作方式是引擎盖@_
不是像大多数其他数组一样的真实数组(例如my @array
可以获得),而是它的元素直接由传递给函数调用的参数构建。 如果需要,这允许您构造传递引用语义。 作为普通变量的函数调用参数按原样插入到此数组中,并将更复杂表达式的常量或结果作为只读临时值插入。
然而,在实践中这样做非常罕见,因为Perl支持参考值; 也就是说,引用其他变量的值。 通常,通过传入对该变量的引用来构造对变量具有明显副作用的函数更为清晰。 这对于呼叫站点的读者来说是一个明确的指示,即传递引用语义是有效的。
sub incr_ref {
my ( $ref ) = @_;
$$ref++;
}
my $value = 1;
incr(\$value);
say "Value is now $value";
这里的\\
运算符产生的引用&
C中的&
address-of运算符非常相似。
这里有一个很好的解释 .NET。
很多人都很惊讶参考对象实际上是通过值传递的(在C#和Java中)。 它是堆栈地址的副本。 这可以防止方法更改对象实际指向的位置,但仍允许方法更改对象的值。 在C#中,可以通过引用传递引用,这意味着您可以更改实际对象指向的位置。
无论你说什么作为传值或传递引用必须在各种语言中保持一致。 跨语言使用的最常见和一致的定义是,通过引用传递,您可以“正常”将变量传递给函数(即,不显式地获取地址或类似的东西),并且函数可以分配给 (不是变异的)函数内部参数的内容与调用作用域中的变量具有相同的效果。
从这个角度来看,语言分组如下; 每个组具有相同的传递语义。 如果您认为不应将两种语言放在同一组中,我会向您提出一个区分它们的示例。
绝大多数语言,包括C , Java , Python , Ruby , JavaScript , Scheme , OCaml , Standard ML , Go , Objective-C , Smalltalk等,都只是按值传递 。 传递指针值(某些语言称之为“引用”)不算作引用传递; 我们只关心传递的东西,指针,而不是指向的东西。
像C ++ , C# , PHP这样的语言默认是像上面的语言一样传递值,但是函数可以使用&
或ref
显式地声明参数是pass-by-reference。
Perl总是通过引用传递; 然而,在实践中,人们几乎总是在获得它之后复制它们,从而以值传递的方式使用它。
按价值
引用
关于J ,虽然只有AFAIK,通过值传递,但有一种通过引用传递的形式,它可以移动大量数据。 您只需将已知为语言环境的内容传递给动词(或函数)。 它可以是类的实例,也可以只是通用容器。
spaceused=: [: 7!:5 <
exectime =: 6!:2
big_chunk_of_data =. i. 1000 1000 100
passbyvalue =: 3 : 0
$ y
''
)
locale =. cocreate''
big_chunk_of_data__locale =. big_chunk_of_data
passbyreference =: 3 : 0
l =. y
$ big_chunk_of_data__l
''
)
exectime 'passbyvalue big_chunk_of_data'
0.00205586720663967
exectime 'passbyreference locale'
8.57957102144893e_6
明显的缺点是您需要在被调用函数中以某种方式知道变量的名称。 但是这种技术可以轻松地移动大量数据。 这就是为什么,虽然技术上没有通过引用传递,但我称之为“几乎就是这样”。
PHP也是通过值传递的。
<?php
class Holder {
private $value;
public function __construct($value) {
$this->value = $value;
}
public function getValue() {
return $this->value;
}
}
function swap($x, $y) {
$tmp = $x;
$x = $y;
$y = $tmp;
}
$a = new Holder('a');
$b = new Holder('b');
swap($a, $b);
echo $a->getValue() . ", " . $b->getValue() . "\n";
输出:
a b
但是在PHP4中,对象被视为原始对象。 意思是:
<?php
$myData = new Holder('this should be replaced');
function replaceWithGreeting($holder) {
$myData->setValue('hello');
}
replaceWithGreeting($myData);
echo $myData->getValue(); // Prints out "this should be replaced"
默认情况下,ANSI / ISO C使用 - 它取决于您声明函数及其参数的方式。
如果将函数参数声明为指针,则函数将通过引用传递,如果将函数参数声明为非指针变量,则函数将按值传递。
void swap(int *x, int *y); //< Declared as pass-by-reference.
void swap(int x, int y); //< Declared as pass-by-value (and probably doesn't do anything useful.)
如果创建一个返回指向该函数内创建的非静态变量的指针的函数,则可能会遇到问题。 以下代码的返回值将是未定义的 - 无法知道分配给函数中创建的临时变量的内存空间是否被覆盖。
float *FtoC(float temp)
{
float c;
c = (temp-32)*9/5;
return &c;
}
但是,您可以返回对静态变量或参数列表中传递的指针的引用。
float *FtoC(float *temp)
{
*temp = (*temp-32)*9/5;
return temp;
}