Passa per riferimento o passa per valore?

Quando apprendi un nuovo linguaggio di programmazione, uno dei possibili ostacoli che potresti incontrare è la domanda se la lingua sia, per impostazione predefinita, pass-by-value o pass-by-reference .

Quindi, ecco la mia domanda a tutti voi, nella vostra lingua preferita, come si fa effettivamente? E quali sono le possibili insidie ?

La tua lingua preferita può, ovviamente, essere qualsiasi cosa tu abbia mai giocato: popolare , oscuro , esoterico , nuovo , vecchio …

Ecco il mio contributo per il linguaggio di programmazione Java .

prima un po ‘di codice:

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

chiamare questo metodo comporterà questo:

 int pi = 3; int everything = 42; swap(pi, everything); System.out.println("pi: " + pi); System.out.println("everything: " + everything); "Output: pi: 3 everything: 42" 

anche usando oggetti “reali” mostrerà un risultato simile:

 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" 

quindi è chiaro che Java passa i suoi parametri in base al valore , in quanto il valore per pi e tutto e gli oggetti MyObj non vengono scambiati. essere consapevoli che “per valore” è l’ unico modo in Java per passare i parametri a un metodo. (ad esempio un linguaggio come c ++ consente allo sviluppatore di passare un parametro per riferimento usando ‘ & ‘ dopo il tipo del parametro)

ora la parte difficile , o almeno la parte che confonderà la maggior parte dei nuovi sviluppatori java: (preso a prestito da javaworld )
Autore originale: 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" 

difficile con successo cambia il valore di pnt1! Ciò implicherebbe che gli oggetti vengano passati per riferimento, non è questo il caso! Un’istruzione corretta sarebbe: i riferimenti dell’object vengono passati per valore.

più da Tony Sintes:

Il metodo modifica con successo il valore di pnt1, anche se è passato per valore; tuttavia, uno scambio di pnt1 e pnt2 fallisce! Questa è la principale fonte di confusione. Nel metodo main (), pnt1 e pnt2 non sono altro che riferimenti a oggetti. Quando passi pnt1 e pnt2 al metodo tricky (), Java passa i riferimenti per valore proprio come qualsiasi altro parametro. Ciò significa che i riferimenti passati al metodo sono in realtà copie dei riferimenti originali. La Figura 1 di seguito mostra due riferimenti che puntano allo stesso object dopo che Java ha passato un object a un metodo.

figura 1 http://sofit.miximages.com/parameters/03-qa-0512-pass2b.gif

Conclusione o breve:

  • Java passa i parametri in base al valore
  • “by value” è l’ unico modo in java per passare un parametro ad un metodo
  • l’utilizzo di metodi dall’object fornito come parametro modificherà l’object come i riferimenti puntano agli oggetti originali. (se quel metodo stesso altera alcuni valori)

link utili:

Ecco un altro articolo per il linguaggio di programmazione c #

c # passa i suoi argomenti per valore (di default)

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

chiamare questa versione di swap non avrà quindi alcun risultato:

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

tuttavia, a differenza di java c # , offre allo sviluppatore l’opportunità di passare i parametri per riferimento , ciò viene fatto utilizzando la parola chiave ‘ref’ prima del tipo del parametro:

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

questo scambio cambierà il valore del parametro di riferimento:

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

c # ha anche una parola chiave out , e la differenza tra ref e out è sottile. da msdn:

Il chiamante di un metodo che accetta un parametro out non è necessario da assegnare alla variabile passata come parametro out prima della chiamata; tuttavia, il chiamato deve assegnarsi al parametro out prima di tornare.

e

In contrasto i parametri di riferimento sono considerati inizialmente assegnati dal destinatario. Pertanto, non è necessario che il callee assegni il parametro ref prima dell’uso. I parametri di riferimento vengono passati sia dentro che fuori da un metodo.

una piccola trappola è, come in Java, che gli oggetti passati per valore possono ancora essere modificati usando i loro metodi interni

conclusione:

  • c # passa i suoi parametri, per impostazione predefinita, per valore
  • ma quando necessario i parametri possono anche essere passati per riferimento usando la parola chiave ref
  • i metodi interni da un parametro passato per valore alterano l’object (se quel metodo stesso altera alcuni valori)

link utili:

Python usa il pass-by-value, ma poiché tutti questi valori sono riferimenti a oggetti, l’effetto netto è qualcosa di simile al pass-by-reference. Tuttavia, i programmatori Python pensano di più se un tipo di object è mutabile o immutabile . Gli oggetti mutabili possono essere modificati sul posto (ad es. Dizionari, elenchi, oggetti definiti dall’utente), mentre gli oggetti immutabili non possono (ad esempio, numeri interi, stringhe, tuple).

L’esempio seguente mostra una funzione che ha passato due argomenti, una stringa immutabile e una lista mutabile.

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

La riga a = "Red" crea semplicemente un nome locale, a , per il valore di stringa "Red" e non ha alcun effetto sull’argomento passato (che ora è nascosto, poiché deve riferirsi al nome locale da allora in poi) . L’assegnazione non è un’operazione sul posto, indipendentemente dal fatto che l’argomento sia mutevole o immutabile.

Il parametro b è un riferimento a un object elenco mutabile e il metodo .append() esegue un’estensione sul posto dell’elenco, virando sul nuovo valore di stringa "Blue" .

(Poiché gli oggetti stringa sono immutabili, non hanno metodi che supportano le modifiche sul posto.)

Una volta che la funzione ritorna, la riassegnazione di a ha avuto alcun effetto, mentre l’estensione di b mostra chiaramente la semantica delle chiamate di stile pass-by-reference.

Come accennato in precedenza, anche se l’argomento per a è un tipo mutevole, la riassegnazione all’interno della funzione non è un’operazione in-place, e quindi non ci sarebbe alcuna modifica al valore dell’argomento passato:

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

Se non vuoi che la tua lista sia modificata dalla funzione chiamata, dovresti usare il tipo di tupla immutabile (identificato dalle parentesi nella forma letterale, piuttosto che tra parentesi quadre), che non supporta il posto .append() metodo:

 >>> a = "Yellow" >>> b = ("Black", "Burgundy") >>> do_something(a, b) Traceback (most recent call last): File "", line 1, in  File "", line 3, in do_something AttributeError: 'tuple' object has no attribute 'append' 

Dal momento che non ho ancora visto una risposta in Perl, ho pensato di scriverne una.

Sotto il cofano, Perl funziona efficacemente come pass-per-riferimento. Le variabili come argomenti di chiamata di funzione vengono passate in modo referenziale, le costanti vengono passate come valori di sola lettura e i risultati delle espressioni vengono passati come temporanei. I soliti idiomi per build liste di argomenti per assegnazione di lista da @_ , o per shift tendono a nasconderlo all’utente, dando l’impressione di pass-by-value:

 sub incr { my ( $x ) = @_; $x++; } my $value = 1; incr($value); say "Value is now $value"; 

Questo Value is now 1 Stampa Value is now 1 perché $x++ ha incrementato la variabile lessicale dichiarata all’interno della funzione incr() , piuttosto che la variabile passata. Questo stile pass-by-value è solitamente ciò che è desiderato la maggior parte del tempo, come funzioni che modifica i loro argomenti sono rari in Perl, e lo stile dovrebbe essere evitato.

Tuttavia, se per qualche ragione questo comportamento è specificamente desiderato, può essere ottenuto operando direttamente sugli elementi dell’array @_ , perché saranno alias per le variabili passate nella funzione.

 sub incr { $_[0]++; } my $value = 1; incr($value); say "Value is now $value"; 

Questa volta stamperà il Value is now 2 , perché l’espressione $_[0]++ incrementato la variabile di $value effettiva. Il modo in cui funziona è che sotto la cappa @_ non è un array reale come la maggior parte degli altri array (come sarebbe ottenuto dal my @array ), ma invece i suoi elementi sono costruiti direttamente dagli argomenti passati a una chiamata di funzione. Ciò consente di build semantica pass-by-reference se ciò fosse necessario. Gli argomenti di chiamata di funzione che sono semplici variabili vengono inseriti così come sono in questo array e le costanti oi risultati di espressioni più complesse vengono inseriti come temporanei di sola lettura.

È tuttavia estremamente raro farlo in pratica, perché Perl supporta i valori di riferimento; cioè valori che si riferiscono ad altre variabili. Normalmente è molto più chiaro build una funzione che abbia un evidente effetto collaterale su una variabile, passando un riferimento a quella variabile. Questa è una chiara indicazione al lettore del callsite, che la semantica pass-by-reference è in vigore.

 sub incr_ref { my ( $ref ) = @_; $$ref++; } my $value = 1; incr(\$value); say "Value is now $value"; 

Qui l’operatore \ produce un riferimento più o meno allo stesso modo dell’operatore & address-of in C.

C’è una buona spiegazione qui per .NET.

Un sacco di persone sono sorprese che gli oggetti di riferimento siano effettivamente passati per valore (sia in C # che in Java). È una copia di un indirizzo stack. Ciò impedisce a un metodo di cambiare dove l’object punta effettivamente, ma consente comunque a un metodo di cambiare i valori dell’object. In C # è ansible passare un riferimento per riferimento, il che significa che è ansible modificare dove punta un object reale.

Non dimenticare che c’è anche un passaggio per nome e passare per valore-risultato .

Passa per valore-risultato è simile al passaggio per valore, con l’aspetto aggiunto che il valore è impostato nella variabile originale passata come parametro. Può, in una certa misura, evitare l’interferenza con le variabili globali. È apparentemente migliore nella memoria partizionata, in cui un passaggio per riferimento potrebbe causare un errore di pagina ( riferimento ).

Passare per nome significa che i valori vengono calcolati solo quando vengono effettivamente utilizzati, piuttosto che all’inizio della procedura. Algol ha usato pass-by-name, ma un effetto collaterale interessante è che è molto difficile scrivere una procedura di swap ( Reference ). Inoltre, l’espressione passata per nome viene rivalutata ogni volta che si accede, che può anche avere effetti collaterali.

in base al valore

  • è più lento del riferimento poiché il sistema deve copiare il parametro
  • utilizzato solo per l’input

come riferimento

  • più veloce poiché viene passato solo un puntatore
  • utilizzato per input e output
  • può essere molto pericoloso se usato in combinazione con variabili globali

Qualunque cosa tu dica come pass-by-value o pass-by-reference deve essere coerente tra le lingue. La definizione più comune e coerente utilizzata in tutte le lingue è che con il pass-per-riferimento, è ansible passare una variabile a una funzione “normalmente” (cioè senza prendere esplicitamente l’indirizzo o qualcosa del genere) e la funzione può assegnarla (non mutare il contenuto di) il parametro all’interno della funzione e avrà lo stesso effetto dell’assegnazione alla variabile nell’ambito della chiamata.

Da questa vista, le lingue sono raggruppate come segue; ogni gruppo ha la stessa semantica passante. Se pensi che due lingue non debbano essere inserite nello stesso gruppo, ti sfido a proporre un esempio che le contraddistingue.

La stragrande maggioranza dei linguaggi inclusi C , Java , Python , Ruby , JavaScript , Scheme , OCaml , Standard ML , Go , Objective-C , Smalltalk , ecc. Sono tutti solo per il valore pass-by . Passare il valore di un puntatore (alcuni linguaggi lo chiamano “riferimento”) non conta come passaggio per riferimento; ci interessa solo la cosa passata, il puntatore, non la cosa puntata.

Linguaggi come C ++ , C # , PHP sono per impostazione predefinita come le lingue precedenti, ma le funzioni possono dichiarare esplicitamente i parametri come pass-by-reference, usando & o ref .

Perl è sempre un riferimento pass-by; tuttavia, in pratica, le persone copiano quasi sempre i valori dopo averli acquisiti, utilizzandoli quindi in modalità pass-by-value.

Riguardo a J , mentre c’è solo, AFAIK, passando per valore, c’è una forma di passaggio per riferimento che abilita lo spostamento di molti dati. Basta passare qualcosa noto come locale a un verbo (o funzione). Può essere un’istanza di una class o solo un contenitore generico.

 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 

L'ovvio svantaggio è che è necessario conoscere il nome della variabile in qualche modo nella funzione chiamata. Ma questa tecnica può spostare un sacco di dati senza dolore. Ecco perché, sebbene tecnicamente non passi per riferimento, lo chiamo "più o meno così".

Anche il PHP passa per valore.

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

Uscite:

 ab 

Tuttavia in PHP4 gli oggetti sono stati trattati come primitivi . Che 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" 

Per impostazione predefinita, ANSI / ISO C utilizza entrambi – dipende da come si dichiara la propria funzione e i suoi parametri.

Se si dichiarano i parametri della funzione come puntatori, la funzione sarà pass-by-reference e se si dichiarano i parametri della funzione come variabili non puntatore, la funzione sarà pass-by-value.

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

È ansible incontrare problemi se si crea una funzione che restituisce un puntatore a una variabile non statica creata all'interno di tale funzione. Il valore restituito del codice seguente non è definito: non è ansible sapere se lo spazio di memoria allocato alla variabile temporanea creata nella funzione è stato sovrascritto o meno.

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

Tuttavia, è ansible restituire un riferimento a una variabile statica oa un puntatore che è stato passato nell'elenco dei parametri.

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