Dlaczego zarówno C ++, jak i Java używają pojęcia „referencja”, ale nie w tym samym znaczeniu?

26

W C ++ argument odwołania do funkcji pozwala funkcji na wywołanie odwołania do czegoś innego:

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

Analogicznie, stały argument odwołania do funkcji wyrzuci błąd czasu kompilacji, jeśli spróbujemy zmienić odwołanie:

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

Teraz, w Javie, doktorzy mówią, że argumenty funkcji typów innych niż pierwotne są referencjami. Przykład z oficjalnych dokumentów:

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

Następnie okrągowi przypisywane jest odwołanie do nowego obiektu Koło za pomocą x = y = 0. To ponowne przypisanie nie ma jednak trwałości, ponieważ odwołanie zostało przekazane przez wartość i nie może się zmienić.

Dla mnie to wcale nie wygląda jak odwołania do C ++. Nie przypomina zwykłych referencji w C ++, ponieważ nie można sprawić, by odnosiły się do czegoś innego, i nie przypomina referencji w stałej C ++, ponieważ w Javie kod, który zmieniłby (ale tak naprawdę nie) referencję nie rzuca kompilacji błąd czasu.

To zachowanie jest bardziej podobne do wskaźników C ++. Możesz go użyć do zmiany wartości wskazanych obiektów, ale nie możesz zmienić samej wartości wskaźnika w funkcji. Ponadto, podobnie jak w przypadku wskaźników C ++ (ale nie w przypadku odniesień do C ++), w Javie można przekazać „null” jako wartość takiego argumentu.

Moje pytanie brzmi: dlaczego Java używa pojęcia „referencja”? Czy należy rozumieć, że nie przypominają one referencji do C ++? A może rzeczywiście przypominają odniesienia do C ++ i czegoś mi brakuje?

Shivan Dragon
źródło
10
Zauważ, że w pierwszym przykładzie C ++ nie podajesz odniesienia „wskaż coś innego”. Zamiast tego zmieniasz wartość zmiennej, do której odnoszą się punkty odniesienia. Jest to koncepcyjnie podobne do mutowania stanu obiektu, do którego odwołuje się odwołanie Java,
Xion
@ Xion tak, teraz, kiedy przeczytałem twój komentarz, rozumiem, co masz na myśli i zgadzam się z 98% tego, co mówisz :) Problem w Javie polega na tym, że musisz mieć dostęp do dowolnego stanu obiektu, aby koncepcyjnie osiągnąć to, co robi C ++, pozwalając ustawić referencję na wartość innej referencji.
Shivan Dragon

Odpowiedzi:

48

Czemu? Ponieważ chociaż spójna terminologia jest ogólnie dobra dla całego zawodu, projektanci języków nie zawsze szanują użycie języka przez innych projektantów języków, szczególnie jeśli te inne języki są postrzegane jako konkurenci.

Ale tak naprawdę żadne użycie „odniesienia” nie było bardzo dobrym wyborem. „Odwołania” w C ++ są po prostu konstrukcją językową do jawnego wprowadzania aliasów (alternatywnych nazw dla dokładnie tego samego bytu). Sprawa byłaby o wiele wyraźniejsza, ponieważ po prostu nazwali nową funkcję „aliasami”. Jednak w tym czasie dużą trudnością było sprawienie, aby wszyscy zrozumieli różnicę między wskaźnikami (które wymagają dereferencji) a referencjami (które tego nie robią), więc ważne było to, że nazywano to czymś innym niż „wskaźnik”, a nie tak konkretnie jakiego terminu użyć.

Java nie ma wskaźników i jest z tego dumna, więc użycie „wskaźnika” jako terminu nie było opcją. Jednak „referencje”, które ma, zachowują się trochę tak, jak wskaźniki C ++, kiedy je przekazujesz - duża różnica polega na tym, że nie możesz wykonywać na nich przykrych operacji na niskim poziomie (rzutowanie, dodawanie ...) , ale skutkują dokładnie taką samą semantyką, gdy przekazujesz uchwyty jednostkom identycznym vs. jednostkom, które po prostu są równe. Niestety termin „wskaźnik” zawiera tak wiele negatywnych skojarzeń niskiego poziomu, że jest mało prawdopodobne, aby społeczność Java go zaakceptowała.

W rezultacie oba języki używają tego samego niejasnego terminu dla dwóch raczej różnych rzeczy, z których obie mogą zyskać na bardziej konkretnej nazwie, ale żadna z nich prawdopodobnie nie zostanie zastąpiona w najbliższym czasie. Język naturalny może być czasem frustrujący!

Kilian Foth
źródło
7
Dzięki, myślenie o referencjach C ++ jako „aliasach” (dla tej samej wartości / instancji) i referencjach Java jako „wskaźnikach bez nieprzyjemnych operacji na niskim poziomie (rzutowanie, dodawanie ...)” naprawdę dla mnie wyjaśnia.
Shivan Dragon
14
Java nie ma wskaźników i jest z tego dumna, więc użycie „wskaźnika” jako terminu nie było opcją. A co NullPointerExceptionz faktem, że JLS używa terminu „wskaźnik”?
Tobias Brandt
7
@TobiasBrandt: NullPointerExceptionnie jest wskaźnikiem, ale jest wyjątkiem wskazującym, że na niższym poziomie wskaźnik NULL został przekazany / wyrejestrowany. Używanie Pointerjako część wyjątku, jeśli w ogóle, dodaje do nieprzyjemnego obrazu, jaki mają. JLS musi wspomnieć o wskaźnikach, ponieważ maszyna wirtualna Java została napisana głównie w języku, który ich używa. Nie można dokładnie opisać specyfikacji języka bez opisania, jak działają elementy wewnętrzne
Elias Van Ootegem
3
@TobiasBrandt: Java używa wskaźników w dowolnym miejscu; obiekty sterty są przywoływane przez coś, co dla wszystkich praktycznych celów jest wskaźnikiem. Po prostu Java nie ujawnia programistom żadnych operacji na typach wskaźników.
John Bode
2
Wolę termin „identyfikator obiektu” dla odniesień do Java / .NET. Na poziomie bitowym takie rzeczy mogą zwykle być implementowane jako coś przypominającego wskaźnik, ale nic nie wymaga tego. Ważne jest to, że identyfikator obiektu zawiera wszelkie informacje, których struktura / vm potrzebuje do zidentyfikowania obiektu.
supercat
9

Odwołanie to rzecz, która odnosi się do innej rzeczy. Różne języki przypisały bardziej szczegółowe znaczenie słowu „odniesienie”, zwykle „coś w rodzaju wskaźnika bez wszystkich złych aspektów”. C ++ przypisuje określone znaczenie, podobnie jak Java lub Perl.

W C ++ odwołania są bardziej podobne do aliasów (które można zaimplementować za pomocą wskaźnika). Umożliwia to przekazywanie argumentów referencyjnych lub wyjściowych .

W Javie referencje są wskaźnikami, z tym wyjątkiem, że nie jest to poprawiona koncepcja języka: wszystkie obiekty są referencjami, pierwotne typy, takie jak liczby, nie są. Nie chcą mówić „wskaźnik”, ponieważ nie ma arytmetyki wskaźnika i nie ma zweryfikowanych wskaźników w Javie, ale chcą wyjaśnić, że kiedy przekazujesz Objectargument jako argument, obiekt nie jest kopiowany . To również nie jest przekazywanie przez odniesienie , ale coś więcej jak przekazywanie przez udostępnianie .

amon
źródło
6

W dużej mierze wraca do Algolu 68, a częściowo do reakcji na sposób, w jaki C definiuje wskaźniki.

Algol 68 zdefiniował pojęcie zwane odniesieniem. To było prawie tak samo jak (na przykład) wskaźnik w Pascalu. Była to komórka zawierająca NIL lub adres innej komórki określonego typu. Możesz przypisać odwołanie, aby odwołanie mogło odnosić się do jednej komórki na raz i innej komórki po ponownym przypisaniu. To nie nie , jednak wsparcie cokolwiek analogiczne do C lub C ++ wskaźnik arytmetycznych.

Przynajmniej tak, jak zdefiniował Algol 68, odniesienia były jednak dość czystą koncepcją, która była dość niezdarna w praktyce. Większość definicji zmiennych faktycznie zawierała odniesienia, więc zdefiniowały one skrót, aby nie wymknął się spod kontroli, ale użycie trywialne może i tak stać się dość niezdarne.

Na przykład deklaracja podobna INT j := 7;była naprawdę traktowana przez kompilator jako deklaracja podobna REF INT j = NEW LOC INT := 7. Tak więc to, co zadeklarowałeś w Algolu 68, było zwykle odwołaniem, które zostało następnie zainicjowane, aby odwoływało się do czegoś, co zostało przydzielone na stercie, i które (opcjonalnie) zostało zainicjowane, aby zawierało pewną określoną wartość. Jednak w przeciwieństwie do Javy starali się przynajmniej zachować rozsądną składnię, zamiast ciągle mieć rzeczy takie jak foo bar = new foo();lub próbować powiedzieć fibs o „nasze wskaźniki nie są wskaźnikami”.

Pascal i większość jego potomków (zarówno bezpośrednich, jak i ... duchowych) zmieniła nazwę pojęcia referencyjnego Algol 68 na „wskaźnik”, ale sama koncepcja pozostała zasadniczo taka sama: wskaźnik był zmienną, która zawierała albo niladres czegoś, który przydzieliłeś na stercie (tzn. przynajmniej tak jak pierwotnie zdefiniowali Jensen i Wirth, nie było operatora „adres”, więc wskaźnik nie mógł odwoływać się do normalnie zdefiniowanej zmiennej). Pomimo tego, że są „wskaźnikami”, nie była obsługiwana żadna arytmetyka wskaźników.

C i C ++ dodały do ​​tego kilka zwrotów akcji. Po pierwsze, zrobić mieć adres-operatora, a więc wskaźnik może odnosić się nie tylko do czegoś przydzielonej na stercie, ale do każdej zmiennej, niezależnie od tego, jak jest przydzielona. Po drugie, definiują arytmetykę wskaźników i definiują indeksy tablic jako po prostu skrótowy zapis arytmetyki wskaźnika, więc arytmetyka wskaźnika jest wszechobecna (obok nieuniknionego) w większości C i C ++.

Kiedy wynaleziono Java, Sun najwyraźniej myślał, że „Java nie ma wskaźników” to prostszy, czystszy przekaz marketingowy niż: „prawie wszystko w Javie jest wskaźnikiem, ale te wskaźniki są w większości jak Pascal zamiast C”. Ponieważ zdecydowali, że „wskaźnik” nie jest akceptowalnym terminem, potrzebowali czegoś innego, a zamiast tego pogłębili „referencje”, mimo że ich referencje były subtelnie (aw niektórych przypadkach nie tak subtelnie) różne od referencji Algolu 68.

Chociaż wyszło to nieco inaczej, C ++ utknął z mniej więcej tym samym problemem: słowo „wskaźnik” było już znane i zrozumiałe, więc potrzebowali innego słowa dla tej dodawanej przez siebie rzeczy, która odnosiła się do czegoś innego, ale poza tym była całkiem inna nieco różni się od tego, co ludzie rozumieli jako „wskaźnik”. Tak więc, mimo że wyraźnie różni się również od odniesienia Algol 68, ponownie użyli również terminu „odniesienie”.

Jerry Coffin
źródło
Odwołanie się w Algolu 68 do szczególnie bolesnych to automatyczne dereferencje. To jest wygodne, o ile ogranicza się do jednego poziomu. Ale fakt, że można to zrobić kilka razy w połączeniu z cukrem syntaktycznym, który ukrywał niektóre odniesienia do nieświadomych oczu, sprawił, że było to mylące.
AProgrammer
0

Pojęcie „typu odniesienia” jest ogólne, może wyrażać zarówno wskaźnik, jak i odniesienie (w kategoriach C ++), w przeciwieństwie do „typu wartości”. Referencje Java są naprawdę wskaźniki bez składniowej napowietrznej ->o *dereferencji. Jeśli spojrzysz na implementację JVM w C ++, są to naprawdę proste wskaźniki. Ponadto C # ma pojęcie referencji podobnych do Javy, ale ma również wskaźniki i kwalifikatory „ref” parametrów funkcji, pozwalając na przekazywanie typów wartości przez referencje i unikanie kopiowania, tak jak &w C ++.

Herc
źródło
czym ta odpowiedź różni się / jest lepsza od wcześniejszych odpowiedzi tutaj?
komara