При изучении нового языка программирования одним из возможных препятствий, с которыми вы можете столкнуться, является вопрос, является ли язык по умолчанию передачей по значению или передачей по ссылке .
Итак, вот мой вопрос ко всем вам, на вашем любимом языке, как это на самом деле делается? И каковы возможные подводные камни ?
Конечно, вашим любимым языком может быть все, с чем вы когда-либо играли: популярный , непонятный , эзотерический , новый , старый ...
Вот мой собственный вклад в язык программирования 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 передает объект методу.
(источник: javaworld.com )
Заключение или короткая история:
- Java передает его параметры по значению
- «по значению» это единственный способ в Java передать параметр методу
- использование методов из объекта, заданного в качестве параметра , изменит объект, так как ссылки указывают на исходные объекты. (если этот метод сам изменяет некоторые значения)
Полезные ссылки:
по значению
- медленнее, чем по ссылке, так как система должна скопировать параметр
- используется только для ввода
по ссылке
- быстрее, поскольку передается только указатель
- используется для ввода и вывода
- может быть очень опасным, если используется вместе с глобальными переменными
Не забывайте, что есть также передача по имени и передача по значению-результату .
Передача по значению-результату аналогична передаче по значению с дополнительным аспектом, заключающимся в том, что значение установлено в исходной переменной, которая была передана в качестве параметра. Это может в некоторой степени избежать вмешательства в глобальные переменные. Очевидно, лучше в многораздельной памяти, где передача по ссылке может вызвать сбой страницы ( Ссылка ).
Передача по имени означает, что значения рассчитываются только тогда, когда они фактически используются, а не в начале процедуры. Алгол использовал передачу по имени, но интересным побочным эффектом является то, что очень трудно написать процедуру обмена ( Ссылка ). Кроме того, выражение, переданное по имени, переоценивается при каждом обращении к нему, что также может иметь побочные эффекты.
Вот еще одна статья для языка программирования 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
- внутренние методы из параметра, переданного по значению , изменят объект (если этот метод сам изменяет некоторые значения)
Полезные ссылки:
Здесь есть хорошее объяснение .NET.
Многие удивляются, что ссылочные объекты фактически передаются по значению (как в C #, так и в Java). Это копия стекового адреса. Это препятствует тому, чтобы метод изменил, куда фактически указывает объект, но все еще позволяет методу изменять значения объекта. В C # возможно передавать ссылку по ссылке, что означает, что вы можете изменить место, на которое указывает реальный объект.
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"
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'
Что касается 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
Очевидным недостатком является то, что вам нужно каким-то образом знать имя вашей переменной в вызываемой функции. Но эта техника может перемещать много данных безболезненно. Вот почему, хотя технически это не относится к ссылкам, я называю это "довольно много".
По умолчанию 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;
}
int
, a int
будет передан по значению, если вы объявите параметр как a float
, a float
будет передан по значению, а если вы объявите параметр как указатель, указатель будет передан по значению , но там никогда не будет проходить по ссылке. Поскольку я еще не видел 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.
Все, что вы говорите как передача по значению или передача по ссылке, должно быть одинаковым для всех языков. Наиболее распространенное и непротиворечивое определение, используемое в разных языках, заключается в том, что с помощью передачи по ссылке вы можете передать переменную в функцию «обычно» (т. Е. Без явного получения адреса или чего-либо подобного), и функцию можно назначить (не изменять). содержимое) параметра внутри функции, и он будет иметь тот же эффект, что и присвоение переменной в вызывающей области.
С этой точки зрения языки сгруппированы следующим образом; каждая группа имеет одинаковую семантику прохождения. Если вы считаете, что два языка не следует относить к одной группе, я призываю вас привести пример, который их отличает.
Подавляющее большинство языков, включая C , Java , Python , Ruby , JavaScript , Scheme , OCaml , Standard ML , Go , Objective-C , Smalltalk и т. Д., Являются только передаваемыми по значению . Передача значения указателя (некоторые языки называют его «ссылкой») не считается передачей по ссылке; нас интересует только то, что передано, указатель, а не то, на что он указывает.
Такие языки, как C ++ , C # , PHP по умолчанию передаются по значению, как и языки выше, но функции могут явно объявлять параметры для передачи по ссылке, используя &
или ref
.
Perl всегда передается по ссылке; однако на практике люди почти всегда копируют значения после их получения, таким образом используя их в порядке передачи по значению.
&
не является «технической деталью» - это самая важная деталь. Передача по ссылке - это очень технический термин, касающийся синтаксиса и семантики. Что-то будет только «передачей по ссылке», если вы передадите переменную напрямую, без каких-либо дополнительных действий с ней. Если вы не хотите быть строгими в этих вещах, вам не следует использовать эти термины. Технически НЕТ переходов по ссылкам на C. Это хорошо известно и не оспаривается. Просто ищите по StackOverflow.