Pergunta

Ao aprender uma nova linguagem de programação, um dos possíveis obstáculos que você pode encontrar é a questão de saber se a linguagem é, por padrão, passagem por valor ou passagem por referência.

Então aqui está minha pergunta para todos vocês, em seu idioma favorito, como isso é realmente feito?E quais são os possíveis armadilhas?

Seu idioma favorito pode, é claro, ser qualquer coisa com a qual você já tenha brincado: popular, obscurecer, esotérico, novo, velho...

Nenhuma solução correta

Outras dicas

Aqui está minha própria contribuição para o Linguagem de programação Java.

primeiro algum código:

public void swap(int x, int y)
{
  int tmp = x;
  x = y;
  y = tmp;
}

chamar este método resultará no seguinte:

int pi = 3;
int everything = 42;

swap(pi, everything);

System.out.println("pi: " + pi);
System.out.println("everything: " + everything);

"Output:
pi: 3
everything: 42"

mesmo usando objetos 'reais' mostrará um resultado semelhante:

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"

portanto, fica claro que Java passa seus parâmetros por valor, como o valor para pi e tudo e a Objetos MeuObj não são trocados.esteja ciente de que "por valor" é o único caminho em java para passar parâmetros para um método.(por exemplo, uma linguagem como c++ permite ao desenvolvedor passar um parâmetro por referência usando '&'após o tipo do parâmetro)

agora o parte complicada, ou pelo menos a parte que irá confundir a maioria dos novos desenvolvedores Java:(emprestado de mundo java)
Autor original:Tony Sintes

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"

complicado altera com sucesso o valor de pnt1!Isto implicaria que os objetos são passados ​​por referência, mas não é o caso!Uma afirmação correta seria: o Referências de objeto são passados ​​por valor.

mais de Tony Sintes:

O método altera com sucesso o valor do PNT1, mesmo que seja aprovado pelo valor;No entanto, uma troca de PNT1 e PNT2 falha!Esta é a principal fonte de confusão.No método main (), pnt1 e pnt2 nada mais são do que referências de objetos.Quando você passa PNT1 e PNT2 para o método Tricky (), Java passa as referências por valor como qualquer outro parâmetro.Isso significa que as referências passadas ao método são na verdade cópias das referências originais.A Figura 1 abaixo mostra duas referências apontando para o mesmo objeto depois que Java passa um objeto para um método.

figure 1
(fonte: javaworld.com)

Conclusão ou uma longa história:

  • Java passa parâmetros por valor
  • "por valor" é o único caminho em java para passar um parâmetro para um método
  • usando métodos do objeto dado como parâmetro irá alterar o objeto, pois as referências apontam para os objetos originais.(se o próprio método alterar alguns valores)

Links Úteis:

Aqui está outro artigo para o linguagem de programação c#

c# passa seus argumentos por valor (por padrão)

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

chamar esta versão de swap não terá resultado:

string x = "foo";
string y = "bar";
swap(x, y);

"output: 
x: foo
y: bar"

no entanto, diferente de java c# faz dê ao desenvolvedor a oportunidade de passar parâmetros por referência, isso é feito usando a palavra-chave 'ref' antes do tipo do parâmetro:

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

esta troca vai altere o valor do parâmetro referenciado:

string x = "foo";
string y = "bar";
swap(x, y);

"output: 
x: bar
y: foo"

c# também tem um palavra-chave out, e a diferença entre ref e out é sutil.do msdn:

O chamador de um método que recebe um parâmetro de saída não é obrigado a atribuir à variável passada como o parâmetro out antes da chamada;no entanto, o chamado é necessário para atribuir ao parâmetro OUT antes de retornar.

e

Em contraste parâmetros de referência são considerado inicialmente atribuído pelo callee.Como tal, o chamador é Não é necessário atribuir ao REFparâmetro antes de usar.Os parâmetros REF são passados ​​para dentro e fora de um método.

uma pequena armadilha é, como em java, que objetos passados ​​por valor ainda podem ser alterados usando seus métodos internos

conclusão:

  • c# passa seus parâmetros, por padrão, por valor
  • mas quando necessário, parâmetros também podem ser passados por referência usando a palavra-chave ref
  • métodos internos de um parâmetro passado por valor irá alterar o objeto (se o próprio método alterar alguns valores)

Links Úteis:

Pitão usa passagem por valor, mas como todos esses valores são referências de objeto, o efeito líquido é algo semelhante à passagem por referência.No entanto, os programadores Python pensam mais sobre se um tipo de objeto é mutável ou imutável.Objetos mutáveis ​​podem ser alterados no local (por exemplo, dicionários, listas, objetos definidos pelo usuário), enquanto objetos imutáveis ​​não podem (por exemplo, inteiros, strings, tuplas).

O exemplo a seguir mostra uma função que recebe dois argumentos, uma string imutável e uma lista mutável.

>>> 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 linha a = "Red" apenas cria um nome local, a, para o valor da string "Red" e não tem efeito no argumento passado (que agora está oculto, como a deve referir-se ao nome local a partir de então).A atribuição não é uma operação local, independentemente de o argumento ser mutável ou imutável.

O b parâmetro é uma referência a um objeto de lista mutável, e o .append() método executa uma extensão no local da lista, acrescentando o novo "Blue" valor da sequência.

(Como os objetos string são imutáveis, eles não possuem nenhum método que suporte modificações no local.)

Assim que a função retornar, a reatribuição de a não teve qualquer efeito, enquanto a prorrogação b mostra claramente a semântica de chamada no estilo passagem por referência.

Como mencionado anteriormente, mesmo que o argumento a favor a é um tipo mutável, a reatribuição dentro da função não é uma operação no local e, portanto, não haveria alteração no valor do argumento passado:

>>> a = ["Purple", "Violet"]
>>> do_something(a, b)
>>> print a, b
['Purple', 'Violet'] ['Black', 'Burgundy', 'Blue', 'Blue']

Se você não quisesse que sua lista fosse modificada pela função chamada, você usaria o tipo de tupla imutável (identificado pelos parênteses na forma literal, em vez de colchetes), que não suporta o no local .append() método:

>>> 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'

Como ainda não vi uma resposta em Perl, pensei em escrever uma.

Nos bastidores, Perl funciona efetivamente como passagem por referência.Variáveis ​​como argumentos de chamada de função são passadas referencialmente, constantes são passadas como valores somente leitura e resultados de expressões são passados ​​como temporários.As expressões usuais para construir listas de argumentos por atribuição de lista a partir de @_, ou pela shift tendem a esconder isso do usuário, dando a aparência de passagem por valor:

sub incr {
  my ( $x ) = @_;
  $x++;
}

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

Isto irá imprimir Value is now 1 porque o $x++ incrementou a variável lexical declarada dentro do incr() função, em vez da variável passada.Esse estilo de passagem por valor geralmente é o que se deseja na maioria das vezes, pois funções que modificam seus argumentos são raras em Perl e o estilo deve ser evitado.

Contudo, se por alguma razão este comportamento for especificamente desejado, ele poderá ser alcançado operando diretamente em elementos do @_ array, porque eles serão apelidos para variáveis ​​passadas para a função.

sub incr {
  $_[0]++;
}

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

Desta vez ele irá imprimir Value is now 2, porque o $_[0]++ expressão incrementou o real $value variável.A maneira como isso funciona é que, sob o capô @_ não é um array real como a maioria dos outros arrays (como seria obtido por my @array), mas em vez disso seus elementos são construídos diretamente a partir dos argumentos passados ​​para uma chamada de função.Isso permite construir semântica de passagem por referência, se isso for necessário.Argumentos de chamada de função que são variáveis ​​simples são inseridos como estão nesta matriz, e constantes ou resultados de expressões mais complexas são inseridos como temporários somente leitura.

No entanto, é extremamente raro fazer isto na prática, porque Perl suporta valores de referência;isto é, valores que se referem a outras variáveis.Normalmente é muito mais claro construir uma função que tenha um efeito colateral óbvio em uma variável, passando uma referência a essa variável.Esta é uma indicação clara para o leitor no callsite de que a semântica de passagem por referência está em vigor.

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

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

Aqui o \ operador produz uma referência da mesma maneira que o & endereço do operador em C.

Há um boa explicação aqui para .NET.

Muitas pessoas ficam surpresas com o fato de os objetos de referência serem realmente passados ​​​​por valor (em C# e Java).É uma cópia de um endereço de pilha.Isso evita que um método altere para onde o objeto realmente aponta, mas ainda permite que um método altere os valores do objeto.Em C# é possível passar uma referência por referência, o que significa que você pode alterar para onde um objeto real aponta.

Não esqueça que também existe passar pelo nome, e passar por valor-resultado.

A passagem por valor-resultado é semelhante à passagem por valor, com a vantagem adicional de que o valor é definido na variável original que foi passada como parâmetro.Pode, até certo ponto, evitar a interferência com variáveis ​​globais.Aparentemente é melhor em memória particionada, onde uma passagem por referência pode causar uma falha de página (Referência).

Passar por nome significa que os valores só são calculados quando são realmente utilizados, e não no início do procedimento.Algol usou passagem por nome, mas um efeito colateral interessante é que é muito difícil escrever um procedimento de troca (Referência).Além disso, a expressão passada por nome é reavaliada cada vez que é acessada, o que também pode trazer efeitos colaterais.

por valor

  • é mais lento que por referência, pois o sistema precisa copiar o parâmetro
  • usado apenas para entrada

por referência

  • mais rápido já que apenas um ponteiro é passado
  • usado para entrada e saída
  • pode ser muito perigoso se usado em conjunto com variáveis ​​globais

Tudo o que você diz como passagem por valor ou passagem por referência deve ser consistente entre os idiomas.A definição mais comum e consistente usada entre linguagens é que com passagem por referência, você pode passar uma variável para uma função "normalmente" (ou seja,sem explicitamente tomar endereço ou algo parecido), e a função pode atribuir a (não alterar o conteúdo) do parâmetro dentro da função e terá o mesmo efeito que atribuir à variável no escopo de chamada.

Nesta visão, os idiomas são agrupados da seguinte forma;cada grupo tendo a mesma semântica de passagem.Se você acha que duas línguas não devem ser colocadas no mesmo grupo, desafio você a dar um exemplo que as diferencie.

A grande maioria das línguas, incluindo C, Java, Pitão, Rubi, JavaScript, Esquema, OCaml, ML padrão, Ir, Objetivo-C, Conversa fiada, etc.são todos apenas passagem por valor.Passar um valor de ponteiro (algumas linguagens chamam isso de "referência") não conta como passagem por referência;estamos preocupados apenas com a coisa passada, o ponteiro, e não com a coisa apontada.

Idiomas como C++, C#, PHP são por padrão passados ​​por valor como as linguagens acima, mas as funções podem declarar explicitamente parâmetros como passados ​​por referência, usando & ou ref.

Perl é sempre passado por referência;entretanto, na prática as pessoas quase sempre copiam os valores depois de obtê-los, usando-os assim de forma passada por valor.

Relativo J., embora exista apenas, AFAIK, passagem por valor, existe uma forma de passagem por referência que permite movimentar muitos dados.Você simplesmente passa algo conhecido como localidade para um verbo (ou função).Pode ser uma instância de uma classe ou apenas um contêiner genérico.

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

A desvantagem óbvia é que você precisa saber de alguma forma o nome da sua variável na função chamada.Mas essa técnica pode mover muitos dados sem problemas.É por isso que, embora tecnicamente não passe por referência, chamo de "praticamente isso".

PHP também é passado por valor.

<?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";

Saídas:

a b

Porém no PHP4 os objetos eram tratados como primitivos.Que significa:

<?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"

Por padrão, ANSI/ISO C usa qualquer um deles - depende de como você declara sua função e seus parâmetros.

Se você declarar seus parâmetros de função como ponteiros, a função será passada por referência, e se você declarar seus parâmetros de função como variáveis ​​que não são ponteiros, a função será passada por valor.

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.)

Você pode ter problemas se criar uma função que retorne um ponteiro para uma variável não estática que foi criada dentro dessa função.O valor retornado do código a seguir seria indefinido – não há como saber se o espaço de memória alocado para a variável temporária criada na função foi substituído ou não.

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

Você poderia, entretanto, retornar uma referência a uma variável estática ou um ponteiro que foi passado na lista de parâmetros.

float *FtoC(float *temp)
{
    *temp = (*temp-32)*9/5;
    return temp;
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top