Qual é a diferença entre
- um parâmetro passado por referência
- um parâmetro passado por valor?
Você poderia me dar alguns exemplos, por favor?
Qual é a diferença entre
Você poderia me dar alguns exemplos, por favor?
Respostas:
Em primeiro lugar, a distinção "passar por valor vs. passar por referência", conforme definida na teoria da CS, está agora obsoleta porque a técnica originalmente definida como "passe por referência" caiu em desuso e raramente é usada agora. 1 1
Os idiomas mais recentes 2 tendem a usar um par de técnicas diferente (mas semelhante) para obter os mesmos efeitos (veja abaixo), que é a principal fonte de confusão.
Uma fonte secundária de confusão é o fato de que em "passagem por referência", "referência" tem um significado mais restrito que o termo geral "referência" (porque a frase é anterior a ela).
Agora, a definição autêntica é:
Quando um parâmetro é passado por referência , o chamador e o destinatário usam a mesma variável para o parâmetro. Se o chamado modifica a variável de parâmetro, o efeito é visível para a variável do chamador.
Quando um parâmetro é passado por valor , o responsável pela chamada e o chamado têm duas variáveis independentes com o mesmo valor. Se o chamado modifica a variável de parâmetro, o efeito não é visível para o chamador.
Os pontos a serem observados nesta definição são:
"Variável" aqui significa a própria variável (local ou global) do chamador - ou seja, se eu passar uma variável local por referência e atribuir a ela, mudarei a própria variável do chamador, não o que quer que esteja apontando se for um ponteiro .
O significado de "referência" em "passagem por referência" . A diferença com o termo geral "referência" é que essa "referência" é temporária e implícita. O que o receptor basicamente recebe é uma "variável" que é de alguma forma "a mesma" que a original. A especificidade desse efeito é irrelevante (por exemplo, a linguagem também pode expor alguns detalhes de implementação - endereços, ponteiros, desreferenciamento - tudo isso é irrelevante; se o efeito líquido é esse, é passagem por referência).
Agora, nas linguagens modernas, as variáveis tendem a ser de "tipos de referência" (outro conceito inventado depois de "passar por referência" e inspirado por ele), ou seja, os dados reais do objeto são armazenados separadamente em algum lugar (geralmente, na pilha), e somente "referências" a ele são mantidas em variáveis e passadas como parâmetros. 3
Passar essa referência se enquadra na passagem por valor, porque o valor de uma variável é tecnicamente a própria referência, não o objeto referido. No entanto, o efeito líquido no programa pode ser o mesmo que passar por valor ou passar por referência:
Como você pode ver, esse par de técnicas é quase o mesmo que o da definição, apenas com um nível de indireção: basta substituir "variável" por "objeto referenciado".
Não existe um nome definido para eles, o que leva a explicações distorcidas como "chamar por valor onde o valor é uma referência". Em 1975, Barbara Liskov sugeriu o termo " compartilhamento de chamada por objeto " (ou, às vezes, apenas "chamada por compartilhamento"), embora nunca tenha entendido bem. Além disso, nenhuma dessas frases é paralela ao par original. Não é de admirar que os termos antigos acabassem sendo reutilizados na ausência de algo melhor, levando à confusão. 4
NOTA : Por um longo tempo, esta resposta costumava dizer:
Digamos que eu queira compartilhar uma página da web com você. Se eu lhe disser o URL, estou passando por referência. Você pode usar esse URL para ver a mesma página da web que posso ver. Se essa página for alterada, ambos veremos as alterações. Se você excluir o URL, tudo o que você está fazendo é destruir sua referência a essa página - você não está excluindo a própria página.
Se eu imprimir a página e fornecer a impressão, estou passando por valor. Sua página é uma cópia desconectada do original. Você não verá nenhuma alteração subsequente e as alterações que você fizer (por exemplo, rabiscar na impressão) não aparecerão na página original. Se você destruir a impressão, na verdade você destruiu sua cópia do objeto - mas a página da Web original permanece intacta.
Isso é mais correto, exceto pelo significado mais restrito de "referência" - sendo temporário e implícito (não é necessário, mas ser explícito e / ou persistente são recursos adicionais, não faz parte da semântica de passagem por referência). , como explicado acima). Uma analogia mais próxima seria fornecer uma cópia de um documento versus convidá-lo a trabalhar no original.
1 A menos que você esteja programando no Fortran ou no Visual Basic, esse não é o comportamento padrão e, na maioria dos idiomas em uso moderno, a verdadeira chamada por referência não é possível.
2 Uma boa quantidade de idosos também apóia
3 Em vários idiomas modernos, todos os tipos são tipos de referência. Essa abordagem foi pioneira na linguagem CLU em 1975 e desde então foi adotada por muitas outras linguagens, incluindo Python e Ruby. E muitas outras linguagens usam uma abordagem híbrida, onde alguns tipos são "tipos de valor" e outros são "tipos de referência" - entre eles C #, Java e JavaScript.
4 Não há nada ruim em reciclar um termo antigo adequado por si só, mas é preciso, de alguma forma, deixar claro qual significado é usado a cada vez. Não fazer isso é exatamente o que continua causando confusão.
É uma maneira de passar argumentos para funções. Passar por referência significa que o parâmetro das funções chamadas será o mesmo que o argumento passado pelos chamadores (não o valor, mas a identidade - a própria variável). Passar por valor significa que o parâmetro das funções chamadas será uma cópia do argumento passado pelos chamadores. O valor será o mesmo, mas a identidade - a variável - é diferente. Assim, as alterações em um parâmetro feito pela função chamada em um caso alteram o argumento passado e, no outro caso, apenas alteram o valor do parâmetro na função chamada (que é apenas uma cópia). Depressa:
ref
usada no chamador e na função chamada). Jon Skeet também tem uma boa explicação sobre isso aqui .Códigos
Como minha linguagem é C ++, usarei isso aqui
// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
p = NULL;
}
// passes an integer
void call_by_value(int p) { // :2
p = 42;
}
// passes an integer by reference
void call_by_reference(int & p) { // :3
p = 42;
}
// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
*p = 10; // changes what p points to ("what p references" in java)
// only changes the value of the parameter, but *not* of
// the argument passed by the caller. thus it's pass-by-value:
p = NULL;
}
int main() {
int value = 10;
int * pointer = &value;
call_by_value(pointer); // :1
assert(pointer == &value); // pointer was copied
call_by_value(value); // :2
assert(value == 10); // value was copied
call_by_reference(value); // :3
assert(value == 42); // value was passed by reference
call_by_value_special(pointer); // :4
// pointer was copied but what pointer references was changed.
assert(value == 10 && pointer == &value);
}
E um exemplo em Java não fará mal:
class Example {
int value = 0;
// similar to :4 case in the c++ example
static void accept_reference(Example e) { // :1
e.value++; // will change the referenced object
e = null; // will only change the parameter
}
// similar to the :2 case in the c++ example
static void accept_primitive(int v) { // :2
v++; // will only change the parameter
}
public static void main(String... args) {
int value = 0;
Example ref = new Example(); // reference
// note what we pass is the reference, not the object. we can't
// pass objects. The reference is copied (pass-by-value).
accept_reference(ref); // :1
assert ref != null && ref.value == 1;
// the primitive int variable is copied
accept_primitive(value); // :2
assert value == 0;
}
}
Wikipedia
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_reference
Esse cara praticamente diz:
Muitas respostas aqui (e, em particular, a resposta mais votada) são factualmente incorretas, pois entendem mal o que "chamada por referência" realmente significa. Aqui está minha tentativa de esclarecer as coisas.
Em termos mais simples:
Em termos metafóricos:
Observe que esses dois conceitos são completamente independentes e ortogonais do conceito de tipos de referência (que em Java são todos os tipos que são subtipos de Object
e em C # todos os class
tipos) ou do conceito de tipos de ponteiro, como em C (que são semanticamente equivalentes aos "tipos de referência" do Java, simplesmente com sintaxe diferente).
A noção de tipo de referência corresponde a um URL: é ao mesmo tempo uma informação e é uma referência (um ponteiro , se você desejar) a outras informações. Você pode ter muitas cópias de um URL em locais diferentes, e eles não mudam para qual site todos eles vinculam; se o site for atualizado, todas as cópias de URL ainda levarão às informações atualizadas. Por outro lado, alterar o URL em qualquer lugar não afetará nenhuma outra cópia escrita do URL.
Observe que o C ++ possui uma noção de "referências" (por exemplo int&
) que não é como os "tipos de referência" de Java e C #, mas é como "chamada por referência". Os "tipos de referência" de Java e C # e todos os tipos em Python são como o que C e C ++ chamam de "tipos de ponteiros" (por exemplo int*
).
OK, aqui está a explicação mais longa e formal.
Para começar, quero destacar alguns trechos importantes da terminologia, para ajudar a esclarecer minha resposta e garantir que todos nós estamos nos referindo às mesmas idéias quando usamos palavras. (Na prática, acredito que a grande maioria da confusão sobre tópicos como esses decorre do uso de palavras de maneira a não comunicar completamente o significado pretendido.)
Para começar, aqui está um exemplo em alguma linguagem semelhante a C de uma declaração de função:
void foo(int param) { // line 1
param += 1;
}
E aqui está um exemplo de chamar esta função:
void bar() {
int arg = 1; // line 2
foo(arg); // line 3
}
Usando este exemplo, quero definir alguns bits importantes da terminologia:
foo
é uma função declarada na linha 1 (Java insiste em fazer todos os métodos de funções, mas o conceito é o mesmo sem perda de generalidade; C e C ++ fazem uma distinção entre declaração e definição que não abordarei aqui)param
é um parâmetro formal para foo
, também declarado na linha 1arg
é uma variável , especificamente uma variável local da função bar
, declarada e inicializada na linha 2arg
também é um argumento para uma chamada específica da foo
linha 3Existem dois conjuntos de conceitos muito importantes para distinguir aqui. O primeiro é o valor versus a variável :
bar
função acima, a seguir à linha int arg = 1;
, a expressão arg
tem o valor 1
.final
ou C # readonly
) ou profundamente imutável (por exemplo, usando C ++ const
).O outro par importante de conceitos a distinguir é parâmetro versus argumento :
Na chamada por valor , os parâmetros formais da função são variáveis recém-criadas para a chamada da função e inicializadas com os valores de seus argumentos.
Isso funciona exatamente da mesma maneira que qualquer outro tipo de variável é inicializado com valores. Por exemplo:
int arg = 1;
int another_variable = arg;
Aqui arg
e another_variable
são variáveis completamente independentes - seus valores podem mudar independentemente um do outro. No entanto, no ponto em que another_variable
é declarado, é inicializado para manter o mesmo valor que arg
mantém - o que é 1
.
Como são variáveis independentes, as alterações another_variable
não afetam arg
:
int arg = 1;
int another_variable = arg;
another_variable = 2;
assert arg == 1; // true
assert another_variable == 2; // true
É exatamente o mesmo que a relação entre arg
e param
no nosso exemplo acima, que repetirei aqui por simetria:
void foo(int param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
É exatamente como se tivéssemos escrito o código desta maneira:
// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here
Ou seja, a característica definidora do que chamada por valor significa é que o chamado ( foo
neste caso) recebe valores como argumentos, mas possui suas próprias variáveis separadas para esses valores das variáveis do chamador ( bar
neste caso).
Voltando à minha metáfora acima, se eu sou bar
e você foo
, quando eu ligo para você, entrego a você um pedaço de papel com um valor escrito. Você chama esse pedaço de papel param
. Esse valor é uma cópia do valor que escrevi no meu notebook (minhas variáveis locais), em uma variável que eu chamo arg
.
(Como um aparte: dependendo do hardware e do sistema operacional, existem várias convenções de chamada sobre como você chama uma função de outra. A convenção de chamada é como se decidíssemos se eu escreveria o valor em um pedaço do meu papel e o entregasse a você , ou se você tem um pedaço de papel em que eu o escrevo, ou se eu o escrevo na parede à nossa frente. Esse também é um assunto interessante, mas muito além do escopo desta resposta já longa.)
Na chamada por referência , os parâmetros formais da função são simplesmente novos nomes para as mesmas variáveis que o chamador fornece como argumentos.
Voltando ao nosso exemplo acima, é equivalente a:
// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here
Como param
é apenas outro nome para arg
- ou seja, eles são a mesma variável , as alterações nas quais param
são refletidas arg
. Essa é a maneira fundamental pela qual a chamada por referência difere da chamada por valor.
Pouquíssimos idiomas suportam chamadas por referência, mas o C ++ pode fazer o seguinte:
void foo(int& param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
Neste caso, param
não apenas tem o mesmo valor como arg
, ele realmente é arg
(apenas por um nome diferente) e assim bar
pode-se observar que arg
foi incrementado.
Observe que não é assim que funciona Java, JavaScript, C, Objective-C, Python ou quase qualquer outra linguagem popular. Isso significa que esses idiomas não são chamados por referência, eles são chamados por valor.
Se o que você tem é chamado por valor , mas o valor real é um tipo de referência ou ponteiro , o "valor" em si não é muito interessante (por exemplo, em C, é apenas um número inteiro de um tamanho específico da plataforma) - o que é interessante é o que esse valor aponta .
Se o que esse tipo de referência (ponteiro) aponta para é mutável , é possível um efeito interessante: você pode modificar o valor apontado e o chamador pode observar alterações no valor apontado, mesmo que o chamador não possa observar muda para o ponteiro em si.
Para emprestar a analogia do URL novamente, o fato de eu ter lhe dado uma cópia do URL para um site não é particularmente interessante se a única coisa com a qual nos preocupamos é o site, não o URL. O fato de você rabiscar sua cópia da URL não afeta minha cópia da URL não é algo que nos interessa (e, de fato, em linguagens como Java e Python, a "URL" ou o valor do tipo de referência pode não pode ser modificado, apenas a coisa apontada por ele pode).
Barbara Liskov, quando inventou a linguagem de programação CLU (que possuía essa semântica), percebeu que os termos existentes "chamada por valor" e "chamada por referência" não eram particularmente úteis para descrever a semântica dessa nova linguagem. Então, ela inventou um novo termo: chamada por compartilhamento de objetos .
Ao discutir linguagens que são tecnicamente chamadas por valor, mas onde tipos comuns em uso são tipos de referência ou ponteiro (ou seja: quase todas as linguagens de programação imperativas, orientadas a objetos ou com vários paradigmas modernos), acho muito menos confuso para simplesmente evite falar sobre chamada por valor ou chamada por referência . Atenha-se à chamada pelo compartilhamento de objetos (ou simplesmente chamada por objeto ) e ninguém ficará confuso. :-)
The first is value versus variable.
The other important pair of concepts to distinguish is parameter versus argument:
Antes de entender os 2 termos, você DEVE entender o seguinte. Todo objeto tem duas coisas que podem fazer com que ele se destaque.
Então se você diz employee.name = "John"
sabe que existem 2 coisas sobre name
. Seu valor que é "John"
e também a sua localização na memória que é um número hexadecimal talvez assim: 0x7fd5d258dd00
.
Dependendo da arquitetura da linguagem ou do tipo (classe, estrutura, etc.) do seu objeto, você estaria transferindo "John"
ou0x7fd5d258dd00
Passar "John"
é conhecido como passar por valor. Passagem 0x7fd5d258dd00
é conhecida como passagem por referência. Qualquer pessoa que esteja apontando para esse local de memória terá acesso ao valor de "John"
.
Para saber mais sobre isso, recomendo que você leia sobre o cancelamento de referência de um ponteiro e também por que escolher struct (tipo de valor) sobre a classe (tipo de referência)
Aqui está um exemplo:
#include <iostream>
void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }
int main()
{
int x = 0;
by_val(x); std::cout << x << std::endl; // prints 0
by_ref(x); std::cout << x << std::endl; // prints 2
int y = 0;
by_ref(y); std::cout << y << std::endl; // prints 2
by_val(y); std::cout << y << std::endl; // prints 2
}
y
já foi definido como 2 pela linha anterior. Por que voltaria a 0?
A maneira mais simples de fazer isso é em um arquivo do Excel. Digamos, por exemplo, que você tenha dois números, 5 e 2 nas células A1 e B1 em conformidade, e deseja encontrar a soma deles na terceira célula, digamos A2. Você pode fazer isso de duas maneiras.
Quer por passagem seus valores para a célula A2 por tipagem = 5 + 2 para essa célula. Nesse caso, se os valores das células A1 ou B1 forem alterados, a soma em A2 permanecerá a mesma.
Ou passando as "referências" das células A1 e B1 para a célula A2 , digitando = A1 + B1 . Nesse caso, se os valores das células A1 ou B1 forem alterados, a soma em A2 também será alterada.
Ao passar por ref, você está basicamente passando um ponteiro para a variável. Passe por valor, você está passando uma cópia da variável. No uso básico, isso normalmente significa que as alterações passadas por ref na variável serão vistas como o método de chamada e passadas pelo valor que não costumam ser.
Passagem por valor envia uma CÓPIA dos dados armazenados na variável especificada, passagem por referência envia um link direto para a própria variável. Portanto, se você passar uma variável por referência e depois alterar a variável dentro do bloco em que a passou, a variável original será alterada. Se você simplesmente passar por valor, a variável original não poderá ser alterada pelo bloco em que você a passou, mas você receberá uma cópia do que quer que ela contenha no momento da chamada.
Passagem por valor - A função copia a variável e trabalha com uma cópia (para que não mude nada na variável original)
Passar por referência - A função usa a variável original; se você alterar a variável na outra função, ela também muda na variável original.
Exemplo (copie e use / tente você mesmo e veja):
#include <iostream>
using namespace std;
void funct1(int a){ //pass-by-value
a = 6; //now "a" is 6 only in funct1, but not in main or anywhere else
}
void funct2(int &a){ //pass-by-reference
a = 7; //now "a" is 7 both in funct2, main and everywhere else it'll be used
}
int main()
{
int a = 5;
funct1(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 5
funct2(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 7
return 0;
}
Mantenha as coisas simples, espreitadelas. Paredes de texto podem ser um mau hábito.
Uma grande diferença entre eles é que as variáveis do tipo valor armazenam valores, portanto, especificar uma variável do tipo valor em uma chamada de método passa uma cópia do valor dessa variável para o método. As variáveis do tipo de referência armazenam referências a objetos, portanto, especificar uma variável do tipo de referência como argumento passa ao método uma cópia da referência real que se refere ao objeto. Embora a própria referência seja passada por valor, o método ainda pode usar a referência que recebe para interagir com - e possivelmente modificar - o objeto original. Da mesma forma, ao retornar informações de um método por meio de uma instrução de retorno, o método retorna uma cópia do valor armazenado em uma variável do tipo valor ou uma cópia da referência armazenada em uma variável do tipo referência. Quando uma referência é retornada, o método de chamada pode usar essa referência para interagir com o objeto referenciado. Assim,
Em c #, para passar uma variável por referência para que o método chamado possa modificar a variável, o C # fornece as palavras-chave ref e out. A aplicação da palavra-chave ref a uma declaração de parâmetro permite passar uma variável a um método por referência - o método chamado poderá modificar a variável original no chamador. A palavra-chave ref é usada para variáveis que já foram inicializadas no método de chamada. Normalmente, quando uma chamada de método contém uma variável não inicializada como argumento, o compilador gera um erro. Preceder um parâmetro com a palavra-chave out cria um parâmetro de saída. Isso indica ao compilador que o argumento será passado para o método chamado por referência e que o método chamado atribuirá um valor à variável original no chamador. Se o método não atribuir um valor ao parâmetro de saída em todos os caminhos possíveis de execução, o compilador gerará um erro. Isso também impede que o compilador gere uma mensagem de erro para uma variável não inicializada que é passada como argumento para um método. Um método pode retornar apenas um valor ao seu chamador por meio de uma declaração de retorno, mas pode retornar muitos valores especificando vários parâmetros de saída (ref e / ou out).
veja discussão e exemplos do c # aqui link text
Exemplos:
class Dog
{
public:
barkAt( const std::string& pOtherDog ); // const reference
barkAt( std::string pOtherDog ); // value
};
const &
geralmente é melhor. Você não incorre na penalidade de construção e destruição. Se a referência não for const, sua interface sugere que alterará os dados passados.
Se você não quiser alterar o valor da variável original depois de passá-la para uma função, a função deve ser construída com um parâmetro " passar por valor ".
Então a função terá APENAS o valor, mas não o endereço da variável passada na. Sem o endereço da variável, o código dentro da função não pode alterar o valor da variável como visto de fora da função.
Mas se você deseja dar à função a capacidade de alterar o valor da variável como vista de fora, é necessário usar o passe por referência . Como o valor e o endereço (referência) são passados e disponibilizados dentro da função.
passar por valor significa como passar valor para uma função usando argumentos. na passagem por valor, copiamos os dados armazenados na variável especificada e é mais lenta que a passagem por referência, porque os dados são copiados. Se fizermos alterações nos dados copiados, os dados originais não serão afetados. e em passagem por referência ou passagem por endereço, enviamos link direto para a própria variável. ou passando o ponteiro para uma variável. é mais rápido porque menos tempo é consumido
Aqui está um exemplo que demonstra as diferenças entre a passagem por valor - valor do ponteiro - referência :
void swap_by_value(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
}
void swap_by_pointer(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void swap_by_reference(int &a, int &b){
int temp;
temp = a;
a = b;
b = temp;
}
int main(void){
int arg1 = 1, arg2 = 2;
swap_by_value(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 1 2
swap_by_pointer(&arg1, &arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
arg1 = 1; //reset values
arg2 = 2;
swap_by_reference(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
}
O método "passando por referência" tem uma limitação importante . Se um parâmetro é declarado como passado por referência (portanto, é precedido pelo sinal &), o parâmetro atual correspondente deve ser uma variável .
Um parâmetro real referente ao parâmetro formal "passado por valor" pode ser uma expressão em geral, portanto, é permitido usar não apenas uma variável, mas também o resultado de uma invocação literal ou mesmo de uma função.
A função não pode colocar um valor em algo que não seja uma variável. Ele não pode atribuir um novo valor a um literal ou forçar uma expressão a alterar seu resultado.
PS: Você também pode verificar a resposta de Dylan Beattie no tópico atual que explica em palavras simples.