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

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

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

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

Здесь уже есть ответ, который объясняет ситуацию в PHP5.
Mat 5.08.2008 09:02:31
Ух ты - на этот вопрос есть ссылка на бета-сервер. Я думаю, что вы исправите ссылку.
Andrew Grimm 10.06.2010 04:06:19
11 ОТВЕТОВ

Вот мой собственный вклад в язык программирования 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 )
Оригинальный автор: Тони Синтес

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"

Tricky успешно меняет значение pnt1! Это будет означать, что объекты передаются по ссылке, это не так! Правильное утверждение будет следующим: ссылки на объекты передаются по значению.

больше от Тони Синтеса:

Метод успешно изменяет значение pnt1, даже если оно передается по значению; однако обмен pnt1 и pnt2 завершается неудачно! Это основной источник путаницы. В методе main () pnt1 и pnt2 являются не более чем ссылками на объекты. Когда вы передаете pnt1 и pnt2 методу tricky (), Java передает ссылки по значению, как и любой другой параметр. Это означает, что ссылки, переданные методу, на самом деле являются копиями исходных ссылок. На рисунке 1 ниже показаны две ссылки, указывающие на один и тот же объект после того, как Java передает объект методу.

фигура 1
(источник: javaworld.com )

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

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

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

31
16.02.2019 10:00:58

по значению

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

по ссылке

  • быстрее, поскольку передается только указатель
  • используется для ввода и вывода
  • может быть очень опасным, если используется вместе с глобальными переменными
4
5.08.2008 09:10:58
Не совсем отвечает на вопрос, но +1 за изложенные факты.
MPelletier 21.11.2009 16:49:49

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

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

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

5
5.08.2008 10:00:46

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

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

private void swap(string a, string b) {
  string tmp = a;
  a = b;
  b = tmp;
}

вызов этой версии свопа, таким образом, не даст результата:

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 передаются как в метод, так и из него.

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

заключение:

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

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

20
23.05.2017 12:17:05

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

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

6
6.08.2008 22:43:29

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"
2
11.08.2008 02:21:42

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ясно показывает семантику вызова стиля передачи по ссылке.

Как упоминалось ранее, даже если аргумент for 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'
19
23.08.2008 17:53:47
Из того, что я прочитал в кратком обзоре аргументов Python, проходящих обсуждения в Интернете, большинство людей Python не знают, что означает передача по ссылке. Python определенно передается по значению. Неизменность ценностей является отдельной проблемой. Кроме того, есть люди, которые путаются с привязками словаря и не понимают, что привязка символа к ссылке на значение в словаре - это то же самое, что и переменная, содержащая ссылку на значение. Передача по ссылке - это когда вы передаете ссылку на переменную, а не значение; или на языке символов, где вы передаете изменяемую привязку имени.
Barry Kelly 13.02.2010 06:02:37

Что касается 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

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

3
21.11.2009 17:14:18

По умолчанию 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;
}
-1
13.01.2011 20:48:40
-1. Это не верно. C всегда передается по значению. Если вы объявите параметр как a int, a intбудет передан по значению, если вы объявите параметр как a float, a floatбудет передан по значению, а если вы объявите параметр как указатель, указатель будет передан по значению , но там никогда не будет проходить по ссылке.
Jörg W Mittag 25.02.2012 04:11:07
@ JörgWMittag: В ANSI / ISO C указатели являются ссылками.
oosterwal 6.04.2012 19:34:28
Просто неправильно. Параметры в обеих функциях подкачки являются значениями. Разница в типе. Первая сигнатура определяет два параметра указателя на int, вторая определяет параметры типа int.
Aluan Haddad 24.04.2016 14:19:42

Поскольку я еще не видел 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.

7
13.04.2012 15:33:07

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

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

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

Такие языки, как C ++ , C # , PHP по умолчанию передаются по значению, как и языки выше, но функции могут явно объявлять параметры для передачи по ссылке, используя &или ref.

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

4
13.04.2012 20:00:14
C не должен входить в ту же группу, что и Java, и тому подобное, потому что в C можно взять адрес переменной и передать ее функции. Это позволяет вызываемой функции изменять значение переменной. То есть можно сделать обход по ссылке в C.
fishinear 2.03.2014 15:54:10
@fishinear: Нет. Это передача по значению. Это копирование переданного значения (указатель).
newacct 3.03.2014 03:35:26
@fishinear: Нет. Передача по значению и передача по ссылке - это семантические концепции, имеющие дело со структурой синтаксиса. Это не имеет ничего общего с «концептуальным». В C или Objective-C нет передачи по ссылке.
newacct 3.03.2014 11:07:08
@fishinear: Ваш "концептуальный" не очень хорошо определен. На самом деле «концептуальный проход по ссылке» можно сделать на любом языке. На Java легко. Просто используйте массив из 1 элемента вместо всех переменных. Чтобы прочитать переменную, получите доступ к элементу 0. Чтобы записать в переменную, запишите в элемент 0. А когда вы «передаете по ссылке», просто передайте массив.
newacct 4.03.2014 02:35:01
@fishinear: Опять же, вы не «передаёте это как параметр». Ввод &не является «технической деталью» - это самая важная деталь. Передача по ссылке - это очень технический термин, касающийся синтаксиса и семантики. Что-то будет только «передачей по ссылке», если вы передадите переменную напрямую, без каких-либо дополнительных действий с ней. Если вы не хотите быть строгими в этих вещах, вам не следует использовать эти термины. Технически НЕТ переходов по ссылкам на C. Это хорошо известно и не оспаривается. Просто ищите по StackOverflow.
newacct 5.03.2014 02:56:57