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

language-agnostic oop parameters pass-by-reference pass-by-value

16085 просмотра

11 ответа

11220 Репутация автора

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

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

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

Автор: sven Источник Размещён: 05.08.2008 08:56

Ответы (11)


31 плюса

11220 Репутация автора

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

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 передать параметр методу
  • использование методов из объекта, указанного в качестве параметра , изменит объект, так как ссылки указывают на исходные объекты. (если этот метод сам изменяет некоторые значения)

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

Автор: sven Размещён: 05.08.2008 08:56

4 плюса

9461 Репутация автора

по значению

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

по ссылке

  • быстрее, так как передается только указатель
  • используется для ввода и вывода
  • может быть очень опасным, если используется вместе с глобальными переменными
Автор: DShook Размещён: 05.08.2008 09:10

5 плюса

27436 Репутация автора

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

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

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

Автор: Matthew Schinckel Размещён: 05.08.2008 10:00

20 плюса

11220 Репутация автора

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

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

Автор: sven Размещён: 05.08.2008 04:40

6 плюса

17396 Репутация автора

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

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

Автор: Karl Seguin Размещён: 06.08.2008 10:43

2 плюса

11231 Репутация автора

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"
Автор: grom Размещён: 11.08.2008 02:21

19 плюса

15257 Репутация автора

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'
Автор: yukondude Размещён: 23.08.2008 04:50

3 плюса

11490 Репутация автора

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

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

Автор: MPelletier Размещён: 21.11.2009 05:14

-1 плюса

1378 Репутация автора

По умолчанию 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;
}
Автор: oosterwal Размещён: 13.01.2011 08:48

7 плюса

7291 Репутация автора

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

Автор: LeoNerd Размещён: 13.04.2012 03:33

4 плюса

94918 Репутация автора

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

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

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

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

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

Автор: newacct Размещён: 13.04.2012 08:00
Вопросы из категории :
32x32