Perché C ++ e Java usano entrambi la nozione di "riferimento" ma non nello stesso senso?


26

In C ++ un argomento di riferimento a una funzione consente alla funzione di fare riferimento a qualcos'altro:

int replacement = 23;

void changeNumberReference(int& reference) {
    reference = replacement;
}

int main() {
    int i = 1;
    std::cout << "i=" << i << "\n"; // i = 1;
    changeNumberReference(i);
    std::cout << "i=" << i << "\n"; // i = 23;
}

Analogamente, un argomento di riferimento costante a una funzione genererà un errore di tempo di compilazione se proviamo a modificare il riferimento:

void changeNumberReference(const int& reference) {
    reference = replacement; // compile-time error: assignment of read-only reference 'reference'
}

Ora, con Java, i documenti dicono che argomenti di funzioni di tipi non primitivi sono riferimenti. Esempio dai documenti ufficiali:

public void moveCircle(Circle circle, int deltaX, int deltaY) {
    // code to move origin of circle to x+deltaX, y+deltaY
    circle.setX(circle.getX() + deltaX);
    circle.setY(circle.getY() + deltaY);

    // code to assign a new reference to circle
    circle = new Circle(0, 0);
}

A Circle viene quindi assegnato un riferimento a un nuovo oggetto Circle con x = y = 0. Questa riassegnazione non ha permanenza, tuttavia, poiché il riferimento è stato passato per valore e non può essere modificato.

Per me questo non assomiglia affatto a riferimenti C ++. Non assomiglia ai normali riferimenti C ++ perché non puoi farne riferimento a qualcos'altro e non assomiglia ai riferimenti const C ++ perché in Java, il codice che cambierebbe (ma in realtà non lo fa) il riferimento non genera una compilazione errore di tempo.

Questo è più simile nel comportamento ai puntatori C ++. Puoi usarlo per cambiare i valori degli oggetti appuntiti, ma non puoi cambiare il valore del puntatore stesso in una funzione. Inoltre, come con i puntatori C ++ (ma non con riferimenti C ++), in Java è possibile passare "null" come valore per tale argomento.

Quindi la mia domanda è: perché Java usa la nozione di "riferimento"? Resta inteso che non assomigliano ai riferimenti C ++? Oppure assomigliano davvero ai riferimenti C ++ e mi manca qualcosa?


10
Nota che nel tuo primo esempio C ++, non stai facendo riferimento "punta a qualcos'altro". Invece, stai cambiando il valore della variabile a cui punta il riferimento. Questo è concettualmente simile alla mutazione dello stato dell'oggetto a cui si fa riferimento con riferimento Java,
Xion

@Xion sì, ora che ho letto il tuo commento, vedo cosa intendi e sono d'accordo con il 98% di quello che dici :) Il problema in Java è che devi avere accesso a qualsiasi stato dell'oggetto per poter concettualmente ottenere ciò che fa C ++ semplicemente permettendoti di impostare il riferimento al valore di qualche altro riferimento.
Drago Shivan,

Risposte:


48

Perché? Perché, sebbene una terminologia coerente sia generalmente buona per l'intera professione, i progettisti di lingue non rispettano sempre l'uso del linguaggio di altri progettisti di lingue, in particolare se tali altre lingue sono percepite come concorrenti.

Ma in realtà, nessuno dei due riferimenti a "riferimento" è stata un'ottima scelta. I "riferimenti" in C ++ sono semplicemente un costrutto di linguaggio per introdurre esplicitamente alias (nomi alternativi esattamente per la stessa entità). Le cose sarebbero state molto più chiare se avessero semplicemente chiamato la nuova funzione "alias" in primo luogo. Tuttavia, a quel tempo la grande difficoltà era far capire a tutti la differenza tra puntatori (che richiedono dereferenziazione) e riferimenti (che non lo fanno), quindi l'importante era che si chiamasse qualcosa di diverso da "puntatore", e non così in particolare quale termine utilizzare.

Java non ha puntatori, e ne è orgoglioso, quindi usare "pointer" come termine non era un'opzione. Tuttavia, i "riferimenti" che ha si comportano un po 'come fanno i puntatori di C ++ quando li passi in giro - la grande differenza è che non puoi fare le operazioni di basso livello più cattive (casting, aggiunta ...) su di loro , ma si traducono esattamente nella stessa semantica quando si passa intorno a handle a entità identiche rispetto a entità che risultano semplicemente uguali. Sfortunatamente, il termine "puntatore" porta così tante associazioni negative di basso livello che è improbabile che vengano mai accettate dalla comunità Java.

Il risultato è che entrambe le lingue usano lo stesso termine vago per due cose piuttosto diverse, entrambe le quali potrebbero trarre profitto da un nome più specifico, ma nessuna delle due è probabile che venga sostituita presto. Anche il linguaggio naturale a volte può essere frustrante!


7
Grazie, pensare ai riferimenti C ++ come "alias" (per lo stesso valore / istanza) e riferimenti Java come "puntatori senza le operazioni di basso livello più cattive (casting, aggiunta ...)" mi chiarisce davvero le cose.
Drago Shivan

14
Java non ha puntatori, e ne è orgoglioso, quindi usare "pointer" come termine non era un'opzione. Che dire NullPointerExceptiono il fatto che JLS usi il termine "puntatore"?
Tobias Brandt,

7
@TobiasBrandt: NullPointerExceptionnon è un puntatore, ma è un'eccezione che indica che, a un livello inferiore, è stato passato / annullato un puntatore NULL. L'utilizzo Pointercome parte di un'eccezione, se non altro, aggiunge all'immagine cattiva che hanno. JLS deve menzionare i puntatori, perché la VM Java è stata principalmente scritta in un linguaggio che li utilizza. Non puoi descrivere accuratamente le specifiche della lingua senza descrivere come funzionano gli interni
Elias Van Ootegem

3
@TobiasBrandt: Java utilizza puntatori ovunque; gli oggetti heap sono referenziati attraverso qualcosa che per tutti gli scopi pratici è un puntatore. È solo che Java non espone al programmatore alcuna operazione sui tipi di puntatore.
John Bode,

2
Preferisco il termine "ID oggetto" per riferimenti Java / .NET. A livello di bit, tali cose possono in genere essere implementate come qualcosa che assomiglia a un puntatore, ma nulla lo richiede. L'importante è che un ID oggetto contenga qualsiasi tipo di informazione di cui il framework / vm ha bisogno per identificare un oggetto.
supercat

9

Un riferimento è una cosa che si riferisce a un'altra cosa. Varie lingue hanno attribuito un significato più specifico alla parola "riferimento", di solito a "qualcosa come un puntatore senza tutti gli aspetti negativi". C ++ attribuisce un significato specifico, così come Java o Perl.

In C ++, i riferimenti sono più simili agli alias (che possono essere implementati tramite un puntatore). Ciò consente di passare per riferimento o argomenti fuori .

In Java, i riferimenti sono puntatori, tranne per il fatto che questo non è un concetto reificato del linguaggio: tutti gli oggetti sono riferimenti, i tipi primitivi come i numeri no. Non vogliono dire "puntatore" perché non c'è aritmetica del puntatore e non ci sono puntatori reificati in Java, ma vogliono chiarire che quando si passa un Objectcome argomento, l'oggetto non viene copiato . Anche questo non è un passaggio per riferimento , ma qualcosa di più simile al passaggio tramite condivisione .


6

Risale in gran parte all'Algol 68 e in parte a una reazione contro il modo in cui C definisce i puntatori.

Algol 68 ha definito un concetto chiamato riferimento. Era praticamente lo stesso di (per un esempio) un puntatore in Pascal. Era una cella che conteneva NIL o l'indirizzo di un'altra cella di un determinato tipo. È possibile assegnare un riferimento, quindi un riferimento può fare riferimento a una cella alla volta e una cella diversa dopo essere stata riassegnata. Lo ha fatto non , tuttavia, il supporto qualsiasi cosa analoga a C o C ++ aritmetica dei puntatori.

Almeno come Algol 68 ha definito le cose, tuttavia, i riferimenti erano concetti abbastanza chiari che erano abbastanza goffi da usare nella pratica. La maggior parte delle definizioni variabili erano in realtà dei riferimenti, quindi hanno definito una notazione abbreviata per impedirle di sfuggire completamente di mano, ma comunque più che un uso banale potrebbe diventare goffo abbastanza rapidamente comunque.

Ad esempio, una dichiarazione simile è INT j := 7;stata davvero trattata dal compilatore come una dichiarazione simile REF INT j = NEW LOC INT := 7. Quindi, quello che hai dichiarato in Algol 68 era normalmente un riferimento, che è stato quindi inizializzato per fare riferimento a qualcosa che è stato allocato sull'heap e che (facoltativamente) è stato inizializzato per contenere un valore specificato. A differenza di Java, tuttavia, hanno almeno cercato di farti mantenere una sintassi sana per quello invece di avere costantemente cose come foo bar = new foo();o cercare di dire ai fibs di "i nostri puntatori non sono puntatori".

Pascal e la maggior parte dei suoi discendenti (sia diretti che ... spirituali) ribattezzarono il concetto di riferimento di Algol 68 in "puntatore" ma mantennero il concetto stesso essenzialmente lo stesso: un puntatore era una variabile che conteneva nilo l'indirizzo di qualcosa che hai assegnato sull'heap (ovvero, almeno come originariamente definito da Jensen e Wirth, non c'era un operatore "indirizzo-di", quindi non c'era modo per un puntatore di fare riferimento a una variabile normalmente definita). Pur essendo "puntatori", non era supportata alcuna aritmetica dei puntatori.

C e C ++ hanno aggiunto alcuni colpi di scena a questo. In primo luogo, fare avere un operatore di indirizzo, in modo da un puntatore può riferirsi non solo a qualcosa allocato sul mucchio, ma per qualsiasi variabile, indipendentemente da come è allocato. In secondo luogo, definiscono l'aritmetica sui puntatori e definiscono gli indici di array come fondamentalmente solo una notazione abbreviata per l'aritmetica del puntatore, quindi l'aritmetica del puntatore è onnipresente (quasi inevitabile) nella maggior parte dei C e C ++.

Quando fu inventato Java, apparentemente Sun pensò che "Java non ha puntatori" era un messaggio di marketing più semplice e più pulito di: "quasi tutto in Java è un puntatore, ma questi puntatori sono principalmente come Pascal anziché C". Dato che avevano deciso che "pointer" non era un termine accettabile, avevano bisogno di qualcos'altro, e invece hanno raccolto "riferimento", anche se i loro riferimenti erano sottilmente (e in alcuni casi non così sottilmente) diversi dai riferimenti Algol 68.

Sebbene risultasse in qualche modo diverso, il C ++ era praticamente bloccato dallo stesso problema: la parola "puntatore" era già conosciuta e compresa, quindi avevano bisogno di una parola diversa per questa diversa cosa che stavano aggiungendo che si riferiva a qualcos'altro, ma per il resto era abbastanza un po 'diverso da ciò che la gente intendeva "puntatore". Quindi, anche se è notevolmente diverso da un riferimento Algol 68, hanno riutilizzato anche il termine "riferimento".


Ciò che rende particolarmente doloroso il riferimento in Algol 68 è la dereferenziazione automatica. È qualcosa di conveniente purché limitato a un livello. Ma il fatto che potesse essere fatto più volte in combinazione con lo zucchero sintattico che nascondeva alcuni dei riferimenti agli occhi inconsapevoli lo rendeva confuso.
Programmatore il

0

La nozione di "tipo di riferimento" è generica, può esprimere sia un puntatore che un riferimento (in termini di C ++), anziché "tipo di valore". I riferimenti di Java sono in realtà puntatori senza sovraccarico sintattico di ->un *dereferenziamento. Se guardi l'implementazione di JVM in C ++, sono davvero dei puntatori nudi. Inoltre, C # ha una nozione di riferimenti simili a quelli di Java, ma ha anche puntatori e qualificatori 'ref' sui parametri delle funzioni, permettendo di passare tipi di valore per riferimento ed evitare di copiare proprio come &in C ++.


in che modo questa risposta è diversa / migliore rispetto alle risposte precedenti qui?
moscerino del
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.