Qual è la differenza tra passaggio per riferimento e passaggio per valore?


Risposte:


1080

Innanzitutto, la distinzione "passa per valore vs passa per riferimento", come definita nella teoria CS, è ormai obsoleta perché la tecnica originariamente definita come "passa per riferimento" è caduta da un favore all'altro e raramente viene ora utilizzata. 1

Le lingue più recenti 2 tendono ad usare una coppia diversa (ma simile) di tecniche per ottenere gli stessi effetti (vedi sotto) che è la principale fonte di confusione.

Una fonte secondaria di confusione è il fatto che in "passa per riferimento", "riferimento" ha un significato più ristretto rispetto al termine generale "riferimento" (perché la frase lo precede).


Ora, la definizione autentica è:

  • Quando un parametro viene passato per riferimento , il chiamante e il chiamante usano la stessa variabile per il parametro. Se la chiamata modifica la variabile del parametro, l'effetto è visibile alla variabile del chiamante.

  • Quando un parametro viene passato per valore , il chiamante e il chiamante hanno due variabili indipendenti con lo stesso valore. Se la chiamata modifica la variabile del parametro, l'effetto non è visibile al chiamante.

Le cose da notare in questa definizione sono:

  • "Variabile" qui significa la variabile del chiamante (locale o globale) stessa - cioè se passo una variabile locale per riferimento e la assegno, cambierò la variabile del chiamante stessa, non ad es. Qualunque cosa stia indicando se è un puntatore .

    • Questa è ora considerata una cattiva pratica (come una dipendenza implicita). Come tale, praticamente tutte le lingue più recenti sono esclusivamente o quasi esclusivamente pass-by-value. Il pass-by-reference viene ora utilizzato principalmente sotto forma di "argomenti output / inout" in linguaggi in cui una funzione non può restituire più di un valore.
  • Il significato di "riferimento" in "passa per riferimento" . La differenza con il termine "riferimento" generale è che questo "riferimento" è temporaneo e implicito. Ciò che la chiamata in pratica ottiene è una "variabile" che è in qualche modo "la stessa" di quella originale. Il modo specifico in cui si ottiene questo effetto è irrilevante (ad esempio il linguaggio può anche rivelare alcuni dettagli di implementazione - indirizzi, puntatori, dereferenziazione - tutto ciò è irrilevante; se l'effetto netto è questo, è pass-by-reference).


Ora, nei linguaggi moderni, le variabili tendono ad essere di "tipi di riferimento" (un altro concetto inventato più tardi di "passa per riferimento" e ispirato da esso), vale a dire che i dati dell'oggetto reale vengono memorizzati separatamente da qualche parte (di solito, sull'heap), e solo i "riferimenti" ad esso vengono mai mantenuti in variabili e passati come parametri. 3

Il passaggio di un riferimento di questo tipo rientra nel valore di passaggio perché il valore di una variabile è tecnicamente il riferimento stesso, non l'oggetto indicato. Tuttavia, l'effetto netto sul programma può essere lo stesso del valore per passaggio o del riferimento per passaggio:

  • Se un riferimento viene appena preso dalla variabile di un chiamante e passato come argomento, questo ha lo stesso effetto del riferimento pass-by: se l'oggetto indicato viene mutato nel chiamante, il chiamante vedrà il cambiamento.
    • Tuttavia, se una variabile che contiene questo riferimento viene riassegnata, smetterà di puntare a quell'oggetto, quindi qualsiasi ulteriore operazione su questa variabile influenzerà invece qualunque cosa stia puntando ora.
  • Per avere lo stesso effetto del pass-by-value, ad un certo punto viene creata una copia dell'oggetto. Le opzioni includono:
    • Il chiamante può semplicemente fare una copia privata prima della chiamata e dare al chiamante un riferimento a quello.
    • In alcune lingue, alcuni tipi di oggetti sono "immutabili": qualsiasi operazione su di essi che sembra alterare il valore crea effettivamente un oggetto completamente nuovo senza influire su quello originale. Quindi, passare un oggetto di un tipo come un argomento ha sempre l'effetto del valore pass-by: una copia per la chiamata verrà fatta automaticamente se e quando avrà bisogno di una modifica e l'oggetto del chiamante non sarà mai influenzato.
      • Nei linguaggi funzionali, tutti gli oggetti sono immutabili.

Come puoi vedere, questa coppia di tecniche è quasi uguale a quella nella definizione, solo con un livello di riferimento indiretto: basta sostituire "variabile" con "oggetto referenziato".

Non esiste un nome concordato per loro, il che porta a spiegazioni contorte come "chiama per valore dove il valore è un riferimento". Nel 1975, Barbara Liskov ha suggerito il termine " condivisione per oggetto " (o talvolta solo "condivisione per condivisione"), anche se non ha mai preso piede. Inoltre, nessuna di queste frasi traccia un parallelo con la coppia originale. Non sorprende che i vecchi termini finissero per essere riutilizzati in assenza di qualcosa di meglio, portando alla confusione. 4


NOTA : per molto tempo, questa risposta ha usato per dire:

Di 'che voglio condividere una pagina web con te. Se ti dico l'URL, sto passando per riferimento. Puoi usare quell'URL per vedere la stessa pagina web che posso vedere. Se quella pagina viene modificata, entrambi vediamo le modifiche. Se elimini l'URL, tutto ciò che stai facendo è distruggere il tuo riferimento a quella pagina: non stai eliminando la pagina stessa.

Se stampo la pagina e vi do la stampa, sto passando per valore. La tua pagina è una copia disconnessa dell'originale. Non vedrai alcuna modifica successiva e tutte le modifiche apportate (ad es. Scarabocchi sulla stampa) non verranno visualizzate nella pagina originale. Se distruggi la stampa, hai effettivamente distrutto la tua copia dell'oggetto, ma la pagina Web originale rimane intatta.

Questo è per lo più corretto tranne il significato più stretto di "riferimento" - essendo sia temporaneo che implicito (non deve, ma essere esplicito e / o persistente sono caratteristiche aggiuntive, non una parte del semantico pass-by-reference , come spiegato sopra). Un'analogia più stretta ti darebbe una copia di un documento rispetto all'invito a lavorare sull'originale.


1 A meno che non si stia programmando in Fortran o Visual Basic, non è il comportamento predefinito e nella maggior parte dei linguaggi in uso moderno, la vera chiamata per riferimento non è nemmeno possibile.

2 Anche una buona parte di quelli più anziani lo supporta

3 In diverse lingue moderne, tutti i tipi sono tipi di riferimento. Questo approccio è stato introdotto dalla lingua CLU nel 1975 e da allora è stato adottato da molte altre lingue, tra cui Python e Ruby. E molte altre lingue usano un approccio ibrido, in cui alcuni tipi sono "tipi di valore" e altri sono "tipi di riferimento", tra cui C #, Java e JavaScript.

4 Non c'è niente di male nel riciclare un vecchio termine adatto di per sé, ma bisogna in qualche modo chiarire quale significato viene usato ogni volta. Non farlo è esattamente ciò che continua a causare confusione.


Personalmente userei i termini "nuovo" o "indiretto" pass-by-value / pass-by-reference per le nuove tecniche.
Ivan_pozdeev,

La definizione "autentica" fornita non è la definizione fornita in quasi tutti i corsi di programmazione introduttiva. Google è ciò che passa per riferimento e non otterrai quella risposta. La definizione autentica che fornisci è un uso improprio della parola riferimento, poiché quando segui quella definizione stai usando un alias non un riferimento: hai due variabili che sono in realtà la stessa variabile, che è un alias e non un riferimento. La tua definizione autentica causa confusione di massa senza motivo. Basta dire passare per riferimento significa passare l'indirizzo. Ha senso e eviterebbe questa inutile confusione.
YungGun,

@YungGun 1) Fornire un collegamento a una "definizione fornita in quasi tutti i corsi di programmazione introduttiva". Si noti inoltre che questo mira ad essere chiaro nelle realtà odierne, non nelle realtà di un decennio o tre fa quando è stato scritto un corso di CS. 2) "Indirizzo" non può essere utilizzato nella definizione perché si sottrae deliberatamente da possibili implementazioni. Ad esempio alcune lingue (Fortran) non hanno puntatori; differiscono anche se espongono l'indirizzo non elaborato all'utente (VB no); inoltre non deve essere un indirizzo di memoria non elaborata, farebbe qualsiasi cosa che consenta di collegarsi alla variabile.
ivan_pozdeev,

@ivan_podeev nessun link mi dispiace. Dico "quasi tutti i corsi introduttivi" perché personalmente, sono andato al college e ho preso anche programmi di bootcamp che mi hanno insegnato questo. Questi corsi erano moderni (meno di 5 anni fa). Un "indirizzo non elaborato" è sinonimo di "puntatore" ... Potresti essere tecnicamente corretto (secondo alcuni link scelti da Cherry) ma la lingua che stai usando è poco pratica e confusa per la maggior parte dei programmatori. Se vuoi i miei pensieri pieni su di esso, ho scritto un post sul blog di 3500 parole: medium.com/@isaaccway228/…
YungGun

@YungGun "troppo lungo, non ho letto". Uno sguardo mostra esattamente le confusioni delineate nella risposta. Passare per riferimento è una tecnica astratta agnostica all'implementazione. Non importa che cosa passi esattamente sotto il cofano, importa quale sia l'effetto sul programma.
ivan_pozdeev,

150

È un modo per passare argomenti alle funzioni. Passare per riferimento significa che il parametro delle funzioni chiamate sarà lo stesso dell'argomento passato dei chiamanti (non il valore, ma l'identità - la variabile stessa). Passa per valore significa che il parametro delle funzioni chiamate sarà una copia dell'argomento passato dei chiamanti. Il valore sarà lo stesso, ma l'identità - la variabile - è diversa. Pertanto, la modifica di un parametro fatto dalla funzione chiamata in un caso cambia l'argomento passato e nell'altro caso cambia semplicemente il valore del parametro nella funzione chiamata (che è solo una copia). In fretta:

  • Java supporta solo il passaggio per valore. Copia sempre gli argomenti, anche se quando si copia un riferimento a un oggetto, il parametro nella funzione chiamata punterà allo stesso oggetto e le modifiche a tale oggetto verranno visualizzate nel chiamante. Dal momento che questo può essere fonte di confusione, ecco cosa ha da dire Jon Skeet a riguardo.
  • I supporti C # passano per valore e passano per riferimento (parola chiave refutilizzata dal chiamante e chiamata funzione). Jon Skeet ha anche una bella spiegazione di questo qui .
  • Il C ++ supporta il passaggio per valore e il passaggio per riferimento (tipo di parametro di riferimento utilizzato nella funzione chiamata). Di seguito troverai una spiegazione di questo.

codici

Dal momento che il mio linguaggio è C ++, lo userò qui

// 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 un esempio in Java non farà male:

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

Questo ragazzo praticamente lo inchioda:

http://javadude.com/articles/passbyvalue.htm


9
perché il downvote? se qualcosa non va o porta a malintesi, si prega di lasciare un commento.
Johannes Schaub - litb

1
Non è stato il mio voto, ma su un punto minore (sai, il tipo preso nei dibattiti presidenziali) direi che è più una "tattica" che una "strategia".
Harpo,

28
+1 per completezza. Non preoccuparti dei voti negativi, le persone lo fanno per strane ragioni. In una domanda di opinione sui calcolatori, tutti sono stati sottoposti a downgrade da un ragazzo che non pensava che i programmatori avrebbero dovuto usare i calcolatori! Comunque, ho pensato che la tua risposta fosse molto buona.
Mark Brittingham,

1
I collegamenti alle spiegazioni di Skeet sono interrotti.
Programmatore orientato al denaro il

I collegamenti alle spiegazioni di Skeet sono ancora interrotti.
Rokit,

85

Molte risposte qui (e in particolare la risposta più votata) sono in realtà errate, poiché fraintendono il significato reale di "chiamare per riferimento". Ecco il mio tentativo di chiarire le cose.

TL; DR

In termini più semplici:

  • call by value significa che si passano valori come argomenti di funzione
  • chiamare per riferimento significa che si passano variabili come argomenti di funzione

In termini metaforici:

  • Chiama per valore è dove scrivo qualcosa su un pezzo di carta e te lo porgo . Forse è un URL, forse è una copia completa di War and Peace. Qualunque cosa sia, è su un pezzo di carta che ti ho dato, e quindi ora è effettivamente il tuo pezzo di carta . Ora sei libero di scarabocchiare su quel pezzo di carta o usare quel pezzo di carta per trovare qualcosa da qualche altra parte e giocherellare con esso, qualunque cosa.
  • La chiamata per riferimento è quando ti do il mio taccuino che contiene qualcosa scritto . Puoi scarabocchiare sul mio quaderno (forse lo voglio, forse no), e poi tengo il mio quaderno, con qualsiasi scarabocchio che hai messo lì. Inoltre, se ciò che io o te abbiamo scritto ci sono informazioni su come trovare qualcosa da qualche altra parte, sia io che te possiamo andare lì e giocherellare con quelle informazioni.

Che cosa "chiamata per valore" e "chiamata per riferimento" non significano

Si noti che entrambi questi concetti sono completamente indipendenti e ortogonali al concetto di tipi di riferimento (che in Java sono tutti i tipi che sono sottotipi Objecte in tutti i classtipi C # ) o al concetto di tipi di puntatori come in C (che sono semanticamente equivalenti ai "tipi di riferimento" di Java, semplicemente con sintassi diversa).

La nozione di tipo di riferimento corrisponde a un URL: è sia essa stessa un'informazione, sia un riferimento (un puntatore , se vuoi) ad altre informazioni. Puoi avere molte copie di un URL in luoghi diversi e non cambiano il sito Web a cui si collegano tutti; se il sito Web viene aggiornato, ogni copia dell'URL porterà comunque alle informazioni aggiornate. Al contrario, la modifica dell'URL in qualsiasi luogo non influirà su qualsiasi altra copia scritta dell'URL.

Si noti che C ++ ha una nozione di "riferimenti" (ad esempio int&) che non è come i "tipi di riferimento" di Java e C #, ma è come "chiamata per riferimento". I "tipi di riferimento" di Java e C #, e tutti i tipi in Python, sono come quelli che C e C ++ chiamano "tipi di puntatore" (ad es int*.).


OK, ecco la spiegazione più lunga e più formale.

Terminologia

Per cominciare, desidero evidenziare alcuni aspetti importanti della terminologia, per aiutare a chiarire la mia risposta e per garantire che tutti ci riferiamo alle stesse idee quando usiamo le parole. (In pratica, credo che la stragrande maggioranza della confusione su argomenti come questi derivi dall'uso delle parole in modi che non comunicano pienamente il significato previsto).

Per iniziare, ecco un esempio in un linguaggio di tipo C di una dichiarazione di funzione:

void foo(int param) {  // line 1
  param += 1;
}

Ed ecco un esempio di chiamata a questa funzione:

void bar() {
  int arg = 1;  // line 2
  foo(arg);     // line 3
}

Utilizzando questo esempio, desidero definire alcuni aspetti importanti della terminologia:

  • fooè una funzione dichiarata sulla linea 1 (Java insiste nel rendere tutti i metodi delle funzioni, ma il concetto è lo stesso senza perdita di generalità; C e C ++ fanno una distinzione tra dichiarazione e definizione in cui non entrerò qui)
  • paramè un parametro formale per foo, ha dichiarato anche sulla linea 1
  • argè una variabile , in particolare una variabile locale della funzione bar, dichiarata e inizializzata sulla riga 2
  • argè anche un argomento per una specifica invocazione della fooriga 3

Ci sono due serie di concetti molto importanti da distinguere qui. Il primo è valore contro variabile :

  • Un valore è il risultato della valutazione di un'espressione nella lingua. Ad esempio, nella barfunzione sopra, dopo la riga int arg = 1;, l'espressione argha il valore 1 .
  • Una variabile è un contenitore per valori . Una variabile può essere mutabile (questo è il valore predefinito nella maggior parte dei linguaggi simili a C), di sola lettura (ad es. Dichiarata usando Java finalo C # readonly) o profondamente immutabile (ad es. Usando C ++ const).

L'altra importante coppia di concetti da distinguere è parametro contro argomento :

  • Un parametro (chiamato anche parametro formale ) è una variabile che deve essere fornita dal chiamante quando chiama una funzione.
  • Un argomento è un valore fornito dal chiamante di una funzione per soddisfare un parametro formale specifico di quella funzione

Chiama per valore

In call by value , i parametri formali della funzione sono variabili che sono state appena create per l'invocazione della funzione e che sono inizializzate con i valori dei loro argomenti.

Funziona esattamente nello stesso modo in cui qualsiasi altro tipo di variabile viene inizializzato con valori. Per esempio:

int arg = 1;
int another_variable = arg;

Qui arge another_variablesono variabili completamente indipendenti - i loro valori possono cambiare indipendentemente l'uno dall'altro. Tuttavia, nel punto in cui another_variableviene dichiarato, viene inizializzato per contenere lo stesso valore che argdetiene, ovvero 1.

Poiché sono variabili indipendenti, le modifiche another_variablenon influiscono su arg:

int arg = 1;
int another_variable = arg;
another_variable = 2;

assert arg == 1; // true
assert another_variable == 2; // true

Questo è esattamente lo stesso della relazione tra arge paramnel nostro esempio sopra, che ripeterò qui per simmetria:

void foo(int param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

È esattamente come se avessimo scritto il codice in questo modo:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here

Cioè, la caratteristica che definisce cosa significa chiamare per valore è che la chiamata ( fooin questo caso) riceve valori come argomenti, ma ha le sue variabili separate per tali valori dalle variabili del chiamante ( barin questo caso).

Tornando alla mia metafora sopra, se io bare te foo, quando ti chiamo, ti passo un pezzo di carta con un valore scritto sopra. Tu chiami quel pezzo di carta param. Quel valore è una copia del valore che ho scritto sul mio taccuino (le mie variabili locali), in una variabile che chiamo arg.

(A parte: a seconda dell'hardware e del sistema operativo, ci sono varie convenzioni di chiamata su come chiamate una funzione da un'altra. La convenzione di chiamata è come noi che decidiamo se scrivo il valore su un pezzo del mio foglio e poi ve lo passiamo , o se hai un pezzo di carta su cui lo scrivo, o se lo scrivo sul muro di fronte a entrambi. Anche questo è un argomento interessante, ma ben oltre lo scopo di questa risposta già lunga.)

Chiama per riferimento

Nella chiamata per riferimento , i parametri formali della funzione sono semplicemente nuovi nomi per le stesse variabili che il chiamante fornisce come argomenti.

Tornando al nostro esempio sopra, è 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

Poiché paramè solo un altro nome per arg- cioè, sono la stessa variabile , in cui paramsi riflettono le modifiche arg. Questo è il modo fondamentale in cui la chiamata per riferimento differisce dalla chiamata per valore.

Pochissime lingue supportano la chiamata per riferimento, ma C ++ può farlo in questo modo:

void foo(int& param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

In questo caso, paramnon ha solo lo stesso valore di arg, in realtà lo è arg (solo con un nome diverso) e quindi barpuò osservare che argè stato incrementato.

Si noti che questo non è il modo in cui oggi funzionano Java, JavaScript, C, Objective-C, Python o quasi qualsiasi altro linguaggio popolare. Ciò significa che quelle lingue non sono chiamate per riferimento, sono chiamate per valore.

Addendum: chiama per condivisione oggetto

Se quello che hai è chiamare per valore , ma il valore effettivo è un tipo di riferimento o un tipo di puntatore , il "valore" in sé non è molto interessante (ad esempio in C è solo un numero intero di una dimensione specifica della piattaforma) - che cos'è interessante è ciò a cui punta quel valore .

Se ciò a cui quel tipo di riferimento (ovvero puntatore) punta è mutabile, allora è possibile un effetto interessante: è possibile modificare il valore puntato e il chiamante può osservare le modifiche al valore puntato, anche se il chiamante non può osservare cambia al puntatore stesso.

Per prendere nuovamente in prestito l'analogia dell'URL, il fatto che ti abbia dato una copia dell'URL a un sito Web non è particolarmente interessante se la cosa che ci interessa sia il sito Web, non l'URL. Il fatto che tu scarabocchi sulla tua copia dell'URL non influisca sulla mia copia dell'URL non è una cosa di cui ci preoccupiamo (e in effetti, in lingue come Java e Python, "URL", o valore del tipo di riferimento, può non sarà affatto modificato, solo la cosa indicata da esso può).

Barbara Liskov, quando inventò il linguaggio di programmazione CLU (che aveva queste semantiche), si rese conto che i termini esistenti "chiamata per valore" e "chiamata per riferimento" non erano particolarmente utili per descrivere la semantica di questo nuovo linguaggio. Quindi ha inventato un nuovo termine: chiamare per condivisione degli oggetti .

Quando parlo di linguaggi tecnicamente chiamati per valore, ma dove tipi comuni in uso sono tipi di riferimento o puntatore (vale a dire: quasi tutti i moderni linguaggi di programmazione imperativi, orientati agli oggetti o multi-paradigma), trovo che sia molto meno confuso evita semplicemente di parlare di chiamata per valore o di chiamata per riferimento . Attenersi alla chiamata per condivisione degli oggetti (o semplicemente chiamare per oggetto ) e nessuno sarà confuso. :-)


Spiegato meglio: ci sono due serie di concetti molto importanti da distinguere qui. The first is value versus variable. The other important pair of concepts to distinguish is parameter versus argument:
SK Venkat,

2
Risposta eccellente. Penso che aggiungerei che nessun nuovo spazio di archiviazione deve essere creato come riferimento. Il nome del parametro fa riferimento alla memoria originale (memoria). Grazie
drlolly

1
Migliore risposta IMO
Rafael Eyng

59

Prima di comprendere i 2 termini, DEVI comprendere quanto segue. Ogni oggetto ha 2 cose che possono farlo distinguere.

  • Il suo valore.
  • Il suo indirizzo.

Quindi se dici employee.name = "John"

sappi che ci sono 2 cose in merito name. Il suo valore, che è "John"anche la sua posizione nella memoria, che è un numero esadecimale forse in questo modo: 0x7fd5d258dd00.

A seconda dell'architettura del linguaggio o del tipo (classe, struttura, ecc.) Del tuo oggetto, verrai trasferito "John"o0x7fd5d258dd00

Il passaggio "John"è noto come passaggio per valore. Il passaggio 0x7fd5d258dd00è noto come passaggio per riferimento. Chiunque stia puntando a questa posizione di memoria avrà accesso al valore di "John".

Per ulteriori informazioni, ti consiglio di leggere sulla dereferenziazione di un puntatore e anche perché scegliere struct (tipo di valore) piuttosto che classe (tipo di riferimento)


3
Quello che stavo cercando, in realtà si dovrebbe cercare il concetto non solo la spiegazione, pollice in alto fratello.
Haisum Usman,

Java è sempre passato per valore. Il passaggio di riferimenti ad oggetti in Java è considerato passaggio per valore. Ciò contraddice la tua affermazione "Passing 0x7fd5d258dd00 è noto come passaggio per riferimento".
Chetan Raina,

53

Ecco un esempio:

#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
}

1
Penso che ci sia un problema in quanto l'ultima riga dovrebbe stampare 0 invece di 2. Per favore, dimmi se mi manca qualcosa.
Taimoor Changaiz,

@TaimoorChangaiz; Quale "ultima riga"? A proposito, se puoi usare IRC, per favore, vai alla programmazione ## su Freenode. Sarebbe molto più semplice spiegare le cose lì. Il mio nick c'è "pyon".
pyon,

1
@ EduardoLeón by_val (y); std :: cout << y << std :: endl; // stampa 2
Taimoor Changaiz,

5
@TaimoorChangaiz: Perché non dovrebbe stampare 2? yè già stato impostato su 2 dalla riga precedente. Perché dovrebbe tornare a 0?
pyon

@ EduardoLeón mio male. si hai ragione. Grazie per la correzione
Taimoor Changaiz,

28

Il modo più semplice per ottenere questo è su un file Excel. Diciamo ad esempio che hai due numeri, 5 e 2 nelle celle A1 e B1 di conseguenza, e vuoi trovare la loro somma in una terza cella, diciamo A2. Puoi farlo in due modi.

  • Sia per superamento dei relativi valori di cella A2 digitando = 5 + 2 in questa cella. In questo caso, se i valori delle celle A1 o B1 cambiano, la somma in A2 rimane invariata.

  • O passando i "riferimenti" delle celle A1 e B1 alla cella A2 digitando = A1 + B1 . In questo caso, se i valori delle celle A1 o B1 cambiano, anche la somma in A2 cambia.


Questo è l'esempio più semplice e migliore tra tutte le altre risposte.
Amit Ray,

18

Quando si passa per ref, si passa sostanzialmente a un puntatore alla variabile. Passa per valore che stai passando una copia della variabile. Nell'uso di base questo normalmente significa passare per ref le modifiche alla variabile saranno il metodo chiamante e passeranno per valore che non vogliono.


12

Passa per valore invia una COPIA dei dati memorizzati nella variabile specificata, passa per riferimento invia un collegamento diretto alla variabile stessa. Pertanto, se si passa una variabile per riferimento e quindi si modifica la variabile all'interno del blocco in cui è stata passata, la variabile originale verrà modificata. Se passi semplicemente per valore, la variabile originale non sarà in grado di essere modificata dal blocco in cui l'hai passata ma otterrai una copia di tutto ciò che conteneva al momento della chiamata.


7

Passa per valore: la funzione copia la variabile e funziona con una copia (quindi non cambia nulla nella variabile originale)

Passa per riferimento - La funzione utilizza la variabile originale, se si cambia la variabile nell'altra funzione, cambia anche nella variabile originale.

Esempio (copia e usa / prova questo da solo e vedi):

#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;
}

Mantenerlo semplice, fa capolino. Le pareti del testo possono essere una cattiva abitudine.


Questo è davvero utile per capire se il valore del parametro è stato modificato o no, grazie!
Kevin Zhao,

5

Una grande differenza tra loro è che le variabili di tipo valore memorizzano valori, quindi specificando una variabile di tipo valore in una chiamata di metodo si passa una copia del valore di quella variabile al metodo Le variabili del tipo di riferimento memorizzano i riferimenti agli oggetti, pertanto la specifica di una variabile del tipo di riferimento come argomento passa al metodo una copia del riferimento effettivo che fa riferimento all'oggetto. Anche se il riferimento stesso viene passato per valore, il metodo può comunque utilizzare il riferimento che riceve per interagire con - e possibilmente modificare - l'oggetto originale. Allo stesso modo, quando si restituiscono informazioni da un metodo tramite un'istruzione return, il metodo restituisce una copia del valore memorizzato in una variabile di tipo valore o una copia del riferimento memorizzato in una variabile di tipo riferimento. Quando viene restituito un riferimento, il metodo chiamante può utilizzare quel riferimento per interagire con l'oggetto referenziato. Così,

In c #, per passare una variabile per riferimento in modo che il metodo chiamato possa modificare la variabile, C # fornisce le parole chiave ref e out. L'applicazione della parola chiave ref a una dichiarazione di parametro consente di passare una variabile a un metodo per riferimento: il metodo chiamato sarà in grado di modificare la variabile originale nel chiamante. La parola chiave ref viene utilizzata per le variabili che sono già state inizializzate nel metodo chiamante. Normalmente, quando una chiamata al metodo contiene una variabile non inizializzata come argomento, il compilatore genera un errore. Precedendo un parametro con la parola chiave out crea un parametro di output. Ciò indica al compilatore che l'argomento verrà passato nel metodo chiamato per riferimento e che il metodo chiamato assegnerà un valore alla variabile originale nel chiamante. Se il metodo non assegna un valore al parametro di output in ogni possibile percorso di esecuzione, il compilatore genera un errore. Ciò impedisce inoltre al compilatore di generare un messaggio di errore per una variabile non inizializzata che viene passata come argomento a un metodo. Un metodo può restituire un solo valore al suo chiamante tramite un'istruzione return, ma può restituire molti valori specificando più parametri di output (ref e / o out).

vedi discussione c # ed esempi qui link text


3

Esempi:

class Dog 
{ 
public:
    barkAt( const std::string& pOtherDog ); // const reference
    barkAt( std::string pOtherDog ); // value
};

const &è generalmente il migliore. Non incorrere nella pena di costruzione e distruzione. Se il riferimento non è costante, l'interfaccia suggerisce che cambierà i dati passati.


2

In breve, Passed by value is WHAT is it e passato per riferimento is WHERE is it.

Se il tuo valore è VAR1 = "Happy Guy!", Vedrai solo "Happy Guy!". Se VAR1 cambia in "Happy Gal!", Non lo saprai. Se viene passato per riferimento e VAR1 cambia, lo farai.


2

Se non si desidera modificare il valore della variabile originale dopo averla passata in una funzione, la funzione deve essere costruita con un parametro " passa per valore ".

Quindi la funzione avrà SOLO il valore ma non l'indirizzo della variabile passata. Senza l'indirizzo della variabile, il codice all'interno della funzione non può modificare il valore della variabile visto dall'esterno della funzione.

Ma se si desidera dare alla funzione la possibilità di modificare il valore della variabile vista dall'esterno, è necessario utilizzare il passaggio per riferimento . Poiché sia ​​il valore che l'indirizzo (riferimento) vengono passati e disponibili all'interno della funzione.


1

passare per valore significa come passare il valore a una funzione usando argomenti. in passaggio per valore copiamo i dati memorizzati nella variabile specificata ed è più lento del passaggio per riferimento poiché i dati vengono copiati. di apportiamo modifiche ai dati copiati, i dati originali non sono interessati. e passiamo per riferimento o passiamo per indirizzo inviamo il collegamento diretto alla variabile stessa. o passando il puntatore a una variabile. è più veloce perché si consuma meno tempo


0

Ecco un esempio che dimostra le differenze tra passa per valore - valore puntatore - riferimento :

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
}

Il metodo "passaggio per riferimento" ha un limite importante . Se un parametro viene dichiarato passato come riferimento (quindi è preceduto dal segno &) il suo parametro effettivo corrispondente deve essere una variabile .

Un parametro reale che si riferisce al parametro formale "passato per valore" può essere un'espressione in generale, quindi è consentito utilizzare non solo una variabile ma anche il risultato di una chiamata letterale o persino di una funzione.

La funzione non è in grado di inserire un valore in qualcosa di diverso da una variabile. Non può assegnare un nuovo valore a un valore letterale o forzare un'espressione a modificarne il risultato.

PS: Puoi anche controllare la risposta di Dylan Beattie nel thread corrente che lo spiega in parole semplici.


Si afferma "se un parametro viene dichiarato [come riferimento] il suo parametro effettivo corrispondente deve essere una variabile", ma ciò non è vero in generale. Se un riferimento è associato a un temporaneo (come il valore restituito di una funzione), la sua durata viene estesa per corrispondere al riferimento. Vedi qui per i dettagli.
Chris Hunt,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.