学习一门新的编程语言时,您可能遇到的障碍之一是该语言是否默认为 按值传递或按引用传递.

这是我用你们最喜欢的语言向你们所有人提出的问题: 如何 真的完成了吗?以及哪些是 可能的陷阱?

当然,您最喜欢的语言可以是您曾经使用过的任何语言: 受欢迎的, 朦胧, 深奥的, 新的, 老的...

没有正确的解决方案

其他提示

这是我自己的贡献 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传递了它的参数 按价值, ,作为值 圆周率一切MyObj 对象 没有被交换。请注意,“按价值”是 唯一办法 在java中将参数传递给方法。(例如,像 c++ 这样的语言允许开发人员使用 ' 通过引用传递参数&' 在参数类型之后)

现在 棘手的部分, ,或者至少是会让大多数新的 java 开发人员感到困惑的部分:(借自 爪哇世界)
原作者:托尼·辛特斯

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的值!这意味着对象是通过引用传递的,但事实并非如此!正确的说法是: 对象引用 是按值传递的。

托尼·辛特斯的更多内容:

该方法成功地改变了PNT1的值,即使它通过值通过。但是,PNT1和PNT2的交换失败了!这是混乱的主要来源。在主()方法中,PNT1和PNT2不过是对象引用。当您将PNT1和PNT2传递到Tricky()方法时,Java像其他任何参数一样按值传递引用。这意味着传递给该方法的引用实际上是原始引用的副本。下图1显示了两个引用指向同一对象后,Java将对象传递给方法。

figure 1
(来源: javaworld.com)

结论 或者长话短说:

  • Java向其传递参数 按价值
  • “按价值” 是个 唯一办法 在java中将参数传递给方法
  • 使用 来自对象的方法 作为参数给出 会改变 作为引用的对象指向原始对象。(如果该方法本身改变了一些值)

有用的链接:

这是另一篇文章 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#也有一个 输出关键字, ,并且 ref 和 out 之间的区别是微妙的。来自msdn:

方法的调用者,该方法采用 输出参数 不需要将传递的变量分配为呼叫之前的OUT参数;然而,被调用者是 需要在返回之前分配给OUT参数。

相比之下 参考参数视为最初分配 由Callee。因此,被调用者是 不需要分配给参考使用前的参数。REF参数被传递到方法中。

一个小陷阱是,就像在java中一样 按值传递的对象仍然可以使用其内部方法进行更改

结论:

  • c#默认传递它的参数, 按价值
  • 但需要时也可以传递参数 引用 使用 ref 关键字
  • 来自按值传递的参数的内部方法 会改变 对象(如果该方法本身改变了一些值)

有用的链接:

Python 使用按值传递,但由于所有这些值都是对象引用,因此最终效果类似于按引用传递。然而,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 倾向于向用户隐藏这一点,给出按值传递的外观:

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 中的地址运算符

有一个 这里有很好的解释 对于.NET。

很多人都惊讶于引用对象实际上是按值传递的(在 C# 和 Java 中)。它是堆栈地址的副本。这可以防止方法更改对象实际指向的位置,但仍然允许方法更改对象的值。在 C# 中,可以通过引用传递引用,这意味着您可以更改实际对象指向的位置。

别忘了还有 通过名字, , 和 按值-结果传递.

按值结果传递与按值传递类似,但附加的方面是在作为参数传递的原始变量中设置值。它可以在一定程度上避免对全局变量的干扰。它在分区内存中显然更好,其中通过引用传递可能会导致页面错误(参考).

按名称传递意味着这些值仅在实际使用时计算,而不是在过程开始时计算。Algol 使用了 pass-by-name,但一个有趣的副作用是编写交换过程非常困难(参考)。此外,每次访问按名称传递的表达式时都会重新计算,这也会产生副作用。

按价值

  • 比通过引用慢,因为系统必须复制参数
  • 仅用于输入

引用

  • 更快,因为只传递了一个指针
  • 用于输入 输出
  • 如果与全局变量一起使用可能非常危险

无论您所说的按值传递还是按引用传递,都必须在不同语言中保持一致。跨语言使用的最常见和一致的定义是,通过引用传递,您可以“正常”将变量传递给函数(即没有明确获取地址或类似的东西),并且该函数可以 分配给 (不改变其内容)函数内部的参数,它将与分配给调用范围中的变量具有相同的效果。

从这个角度来看,语言分为以下几类:每个组具有相同的传递语义。如果您认为两种语言不应该放在同一组中,我挑战您想出一个区分它们的例子。

绝大多数语言包括 C, 爪哇, Python, 红宝石, JavaScript, 方案, 奥卡米尔, 标准机器学习, , Objective-C, 短暂聊天, , ETC。都是 仅按值传递. 。传递指针值(某些语言称其为“引用”)不算作引用传递;我们只关心传递的东西,指针,而不是指向的东西。

语言如 C++, C#, PHP 默认情况下,像上面的语言一样按值传递,但函数可以显式声明参数为按引用传递,使用 & 或者 ref.

珀尔 始终是按引用传递;然而,在实践中,人们几乎总是在获取值后复制它,从而以传递值的方式使用它。

关于 J, ,据我所知,虽然只有按值传递,但有一种按引用传递的形式可以移动大量数据。您只需将称为语言环境的东西传递给动词(或函数)即可。它可以是类的实例,也可以只是通用容器。

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;
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top