Передавать по ссылке или передавать по значению?

При изучении нового языка программирования одним из возможных препятствий, с которыми вы можете столкнуться, является вопрос, является ли язык по умолчанию передачей по значению или передачей по ссылке.

Итак, вот мой вопрос ко всем вам, на вашем любимом языке, как это делается на самом деле? И каковы возможные подводные камни?

Ваш любимый язык может, конечно, быть тем, с чем вы когда-либо играли: популярный, obscure, эзотерический, новый, старый...

Ответы

Ответ 1

Вот мой собственный вклад для языка программирования 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 передать параметры методу. (например, язык, подобный С++, позволяет разработчику передавать параметр по ссылке с помощью & "после типа параметра)

теперь сложная часть или, по крайней мере, часть, которая смутит большинство новых разработчиков Java: (заимствованный из javaworld)
Оригинальный автор: Тони Синтес

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 для сложного() метода, Java передает ссылки по значению как и любой другой параметр. Эта означает ссылки, переданные метод на самом деле являются копиями оригинальные ссылки. Рисунок 1 ниже показывает две ссылки, указывающие на тот же объект после того, как Java передает объект к методу.

Рисунок 1 http://www.javaworld.com/javaworld/javaqa/2000-05/images/03-qa-0512-pass2b.gif

Заключение или короткий короткий текст:

  • Java передает ему параметры по значению
  • "по значению" является только способом в java для передачи параметра методу
  • Использование методов из объекта, заданного как параметр , изменит объект как точку ссылки на исходные объекты. (если этот метод сам изменяет некоторые значения)

полезные ссылки:

Ответ 2

Вот еще одна статья для язык программирования С#

С# передает свои аргументы по значению (по умолчанию)

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 С# дает разработчику возможность передавать параметры по ссылке, это делается с помощью '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"

С# также имеет ключевое слово out, а разница между ref и out является тонкой. из msdn:

Вызывающий метод, который принимает параметр out не требуется для назначить переменной, переданной как перед параметром вызова; однако, вызываемый назначить параметр out перед возвращение.

и

В отличие от параметров refсчитаются первоначально присвоенными вызываемая. Таким образом, вызываемый не является требуется присвоить ref перед использованием. Параметры Ref передаются как в, так и из Метод.

небольшая ловушка, как и в java, позволяет передавать объекты по значению, используя их внутренние методы

заключение:

  • С# передает свои параметры по умолчанию по значению
  • но при необходимости параметры могут также передаваться по ссылке с использованием ключевого слова ref
  • внутренние методы из параметра, переданного значением , изменят объект (если этот метод сам изменяет некоторые значения)

полезные ссылки:

Ответ 3

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" и не влияет на аргумент pass-in (который теперь скрыт, так как 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'

Ответ 4

Так как я еще не видел ответа 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), но вместо этого его элементы создаются непосредственно из аргументов, переданных вызову функции. Это позволяет вам построить семантику pass-by-reference, если это потребуется. Аргументы вызова функции, которые являются равными переменными, вставляются как-есть в этот массив, а константы или результаты более сложных выражений вставляются как временные данные только для чтения.

Однако на практике это редко случается, поскольку Perl поддерживает опорные значения; то есть значения, которые относятся к другим переменным. Обычно гораздо яснее построить функцию, которая имеет очевидный побочный эффект для переменной, передавая ссылку на эту переменную. Это четкое указание читателю на call-сайте, что семантика передачи по ссылке действует.

sub incr_ref {
  my ( $ref ) = @_;
  $$ref++;
}

my $value = 1;
incr(\$value);
say "Value is now $value";

Здесь оператор \ дает ссылку почти так же, как оператор адреса & в C.

Ответ 5

Здесь хорошее объяснение здесь для .NET.

Многие люди удивляются тому, что ссылочные объекты фактически передаются по значению (как в С#, так и в Java). Это копия адреса стека. Это предотвращает изменение метода, на который указывает объект, но все же позволяет методу изменять значения объекта. В С# его можно передать ссылку по ссылке, что означает, что вы можете изменить место, на которое указывает фактический объект.

Ответ 6

Не забывайте, что есть pass by name, а передается по результату.

Передача по значению-результату аналогична прохождению по значению, с добавленным аспектом, что значение задано в исходной переменной, которая была передана в качестве параметра. Это может в какой-то мере избежать вмешательства в глобальные переменные. По-видимому, это лучше в секционированной памяти, где пропуск по ссылке может вызвать ошибку страницы (Ссылка).

Пропустить по имени означает, что значения вычисляются только тогда, когда они фактически используются, а не в начале процедуры. Алгол использовал pass-by-name, но интересным побочным эффектом является то, что очень сложно написать процедуру свопинга (Ссылка). Кроме того, выражение, переданное по имени, переоценивается каждый раз, когда к нему обращаются, что также может иметь побочные эффекты.

Ответ 7

по значению

  • медленнее, чем по ссылке, поскольку система должна скопировать параметр
  • используется только для ввода

по ссылке

  • быстрее, поскольку передается только указатель
  • используется для ввода и вывода
  • может быть очень опасным при использовании в сочетании с глобальными переменными

Ответ 8

Все, что вы говорите как пропуск по значению или пошаговая ссылка, должно быть согласованным между языками. Наиболее распространенным и последовательным определением, используемым во всех языках, является то, что с передачей по ссылке вы можете передать переменную функции "обычно" (т.е. без явного адреса или чего-то подобного), и функция может назначить (не мутировать содержимое) параметра внутри функции и будет иметь тот же эффект, что и назначение переменной в области вызова.

С этой точки зрения языки сгруппированы следующим образом; причем каждая группа имеет одну и ту же проходящую семантику. Если вы считаете, что два языка не должны помещаться в одну группу, я призываю вас придумать пример, который их отличает.

Подавляющее большинство языков, включая C, Java, Python, Ruby, JavaScript, Схема, OCaml, Стандартный ML, Перейти, Objective-C, Smalltalk и т.д., все передаются только. Передача значения указателя (некоторые языки называют его "ссылкой" ) не считается передачей по ссылке; нас беспокоит только переданная вещь, указатель, а не то, на что указывает.

Языки, такие как С++, С#, PHP, по умолчанию имеют значение pass-by-value, как указано выше, но функции могут явно объявлять параметры, которые должны быть переданы по ссылке, используя & или ref.

Perl всегда передается по ссылке; однако на практике люди почти всегда копируют значения после их получения, тем самым используя его по-разному.

Ответ 9

Относительно 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

Очевидным недостатком является то, что вам нужно каким-то образом узнать имя вашей переменной в вызываемой функции. Но этот метод может безболезненно перемещать множество данных. Вот почему, пока технически не проходят по ссылке, я называю это "в значительной степени".

Ответ 10

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"

Ответ 11

По умолчанию 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.)

У вас могут возникнуть проблемы, если вы создадите функцию, которая возвращает указатель на нестатическую переменную, созданную внутри этой функции. Возвращаемое значение следующего кода будет undefined - нет способа узнать, было ли место памяти, выделенное временной переменной, созданной в этой функции, перезаписано или нет.

float *FtoC(float temp)
{
    float c;
    c = (temp-32)*9/5;
    return &c;
}

Вы можете, однако, вернуть ссылку на статическую переменную или указатель, который был передан в списке параметров.

float *FtoC(float *temp)
{
    *temp = (*temp-32)*9/5;
    return temp;
}