language-agnostic - passing - pass parameter by reference




Passa per riferimento o passa per valore? (8)

Quando apprendi un nuovo linguaggio di programmazione, uno dei possibili blocchi stradali che potresti incontrare è la domanda se il linguaggio è, per impostazione predefinita, valore per passaggio o passaggio per riferimento .

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

La tua lingua preferita può, ovviamente, essere qualsiasi cosa tu abbia mai giocato: popular , obscure , esoteric , new , old ...


C'è una buona spiegazione qui per .NET.

Molte persone sono sorprese dal fatto che gli oggetti di riferimento vengano effettivamente passati per valore (sia in C # che in Java). È una copia di un indirizzo dello stack. Ciò impedisce a un metodo di cambiare la posizione effettiva dell'oggetto, ma consente comunque a un metodo di modificare i valori dell'oggetto. In C # è possibile passare un riferimento per riferimento, il che significa che è possibile modificare il punto a cui punta un oggetto reale.


Dato che non ho ancora visto una risposta Perl, ho pensato di scriverne una.

Sotto il cofano, Perl funziona efficacemente come riferimento pass-by. 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 costruire elenchi di argomenti in base all'assegnazione di @_ da @_ o per shift tendono a nasconderli all'utente, dando l'apparenza del valore pass-by:

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

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

Questo stamperà Il 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 è di solito ciò che si desidera il più delle volte, come funzioni che modificano i loro argomenti sono rari in Perl e lo stile dovrebbe essere evitato.

Tuttavia, se per qualche motivo questo comportamento è specificamente desiderato, può essere ottenuto operando direttamente su elementi dell'array @_ , poiché saranno alias per le variabili passate nella funzione.

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

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

Questa volta stamperà Value is now 2 , poiché l'espressione $_[0]++ incrementato la variabile $value effettiva. Il modo in cui funziona è che sotto il cofano @_ non c'è 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 costruire una semantica pass-by-reference se ciò fosse necessario. Gli argomenti delle chiamate di funzione che sono semplici variabili vengono inseriti così come sono in questo array e le costanti o i risultati di espressioni più complesse vengono inseriti come temporanei di sola lettura.

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

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

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

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


Ecco un altro articolo per il linguaggio di programmazione c #

c # passa gli argomenti per valore (per impostazione predefinita)

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 , questo viene fatto usando la parola chiave 'ref' prima del tipo di parametro:

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

questo scambio modificherà 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 è tenuto ad assegnare alla variabile passata come parametro out prima della chiamata; tuttavia, è necessario assegnare alla chiamata il parametro out prima di ritornare.

e

Al contrario, i parametri di riferimento sono considerati inizialmente assegnati dalla call. Di conseguenza, non è necessario che la chiamata venga assegnata al parametro ref prima dell'uso. I parametri di riferimento vengono passati sia all'interno che all'esterno di un metodo.

una piccola trappola è, come in Java, che gli oggetti passati per valore possono ancora essere cambiati 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 modificheranno l'oggetto (se quel metodo stesso altera alcuni valori)

link utili:


Non dimenticare che esiste anche il passaggio per nome e per valore-risultato .

Passa per valore-risultato è simile a passa per valore, con l'aspetto aggiunto che il valore è impostato nella variabile originale che è stata passata come parametro. Può, in una certa misura, evitare interferenze con le variabili globali. Apparentemente è meglio nella memoria partizionata, dove un passaggio per riferimento potrebbe causare un errore di pagina ( Reference ).

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


Per impostazione predefinita, ANSI / ISO C utilizza entrambi: dipende da come dichiarate la vostra 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.)

È possibile riscontrare 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 seguente codice sarebbe indefinito: non è possibile 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, è possibile restituire un riferimento a una variabile statica o un puntatore che è stato passato nell'elenco dei parametri.

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

Per quanto riguarda J , mentre esiste solo AFAIK, passando per valore, esiste una forma di passaggio per riferimento che consente di spostare molti dati. Devi semplicemente passare qualcosa noto come locale a un verbo (o funzione). Può essere un'istanza di una classe 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 molti dati indolore. Ecco perché, sebbene tecnicamente non passi per riferimento, lo chiamo "praticamente quello".


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

L'esempio seguente mostra una funzione a cui sono passati due argomenti, una stringa immutabile e un elenco 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, come deve fare riferimento al nome locale da quel momento in poi) . L'assegnazione non è un'operazione sul posto, indipendentemente dal fatto che l'argomento sia mutabile o immutabile.

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

(Poiché gli oggetti stringa sono immutabili, non hanno alcun metodo che supporti 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 della chiamata di stile pass-by-reference.

Come accennato in precedenza, anche se l'argomento per a è un tipo mutabile, la riassegnazione all'interno della funzione non è un'operazione sul posto 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 desideri che il tuo elenco venga modificato dalla funzione chiamata, utilizzeresti invece il tipo di tupla immutabile (identificato dalle parentesi nella forma letterale, piuttosto che dalle parentesi quadre), che non supporta il posto .append() metodo:

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

per valore

  • è più lento rispetto al riferimento poiché il sistema deve copiare il parametro
  • usato solo per input

come riferimento

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




pass-by-value