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

голоса
48

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

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

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

Задан 05/08/2008 в 09:56
источник пользователем
На других языках...                            


11 ответов

голоса
31

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

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

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

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

фигура 1 http://www.javaworld.com/javaworld/javaqa/2000-05/images/03-qa-0512-pass2b.gif

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

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

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

Ответил 05/08/2008 в 09:56
источник пользователем

голоса
20

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

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 # также имеет выход ключевого слова , а разница между рефами и из тонок. из MSDN:

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

а также

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

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

вывод:

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

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

Ответил 05/08/2008 в 17:40
источник пользователем

голоса
19

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'
Ответил 23/08/2008 в 17:50
источник пользователем

голоса
7

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

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

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

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

Ответил 13/04/2012 в 16:33
источник пользователем

голоса
6

Там очень хорошее объяснение здесь для .NET.

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

Ответил 06/08/2008 в 23:43
источник пользователем

голоса
5

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

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

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

Ответил 05/08/2008 в 11:00
источник пользователем

голоса
4

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

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

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

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

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

Ответил 13/04/2012 в 21:00
источник пользователем

голоса
4

по значению

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

по ссылке

  • быстрее, так как только указатель передается
  • используется для ввода и вывода
  • может быть очень опасно, если они используются в сочетании с глобальными переменными
Ответил 05/08/2008 в 10:10
источник пользователем

голоса
3

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

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

Ответил 21/11/2009 в 18:14
источник пользователем

голоса
2

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/08/2008 в 03:21
источник пользователем

голоса
-1

По умолчанию, 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;
}
Ответил 13/01/2011 в 21:48
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more