Języki nawet gdzie masz wyraźnej manipulacji wskaźnik C jak to zawsze przekazywane przez wartość (ty może przekazać je poprzez odniesienie, ale nie jest to zachowanie domyślne).
Jaka jest z tego korzyść, dlaczego tak wiele języków jest przekazywanych przez wartości, a inne przez referencje ? (Rozumiem, że Haskell jest przekazywany przez referencję, choć nie jestem pewien).
programming-languages
language-design
Mój kod nie zawiera błędów
źródło
źródło
void acceptEntireProgrammingLanguageByValue(C++);
temp := a; a := b; b := temp;
A kiedy ona zwróci, wartościa
ib
zostaną zamienione. Nie ma takiej możliwości w C; trzeba przejść do wskazóweka
ib
iswap
rutyna musi działać na wartościach, do których prowadzą.Odpowiedzi:
Przekazywanie przez wartość jest często bezpieczniejsze niż przekazywanie przez referencję, ponieważ nie można przypadkowo zmodyfikować parametrów metody / funkcji. Dzięki temu język jest prostszy w użyciu, ponieważ nie musisz się martwić o zmienne, które podajesz funkcji. Wiesz, że nie zostaną zmienione, a często tego się spodziewasz .
Jednakże, jeśli chcesz zmodyfikować parametry, musisz mieć wyraźną operację, aby to wyjaśnić (przekazać wskaźnik). Zmusi to wszystkich twoich rozmówców do wykonania połączenia nieco inaczej (
&variable
w C), a to wyraźnie wyjaśni, że parametr zmiennej może zostać zmieniony.Teraz możesz założyć, że funkcja nie zmieni twojego parametru zmiennej, chyba że jest to wyraźnie zaznaczone (wymagając od ciebie podania wskaźnika). Jest to bezpieczniejsze i czystsze rozwiązanie niż alternatywa: załóż, że wszystko może zmienić twoje parametry, chyba że wyraźnie powiedzą, że nie mogą.
źródło
Call-by-Value i Call-by-Reference to techniki implementacji, które dawno temu były mylone z trybami przekazywania parametrów.
Na początku istniał FORTRAN. FORTRAN miał tylko call-by-referencję, ponieważ podprogramy musiały być w stanie modyfikować swoje parametry, a cykle obliczeniowe były zbyt drogie, aby umożliwić wiele trybów przekazywania parametrów, a ponadto nie było wystarczająco dużo informacji o programowaniu, gdy FORTRAN został zdefiniowany po raz pierwszy.
ALGOL wymyślił nazwę po nazwie i nazwę po wartości. Call-by-value dotyczyło rzeczy, których nie należy zmieniać (parametry wejściowe). Call-by-name było dla parametrów wyjściowych. Call-by-name okazało się poważnym crockiem, a ALGOL 68 go upuścił.
PASCAL dostarczył funkcję call-by-value i call-by-reference. Nie dało to programiście żadnego sposobu na poinformowanie kompilatora, że przekazuje duży obiekt (zwykle tablicę) przez odniesienie, aby uniknąć wysadzenia stosu parametrów, ale tego obiektu nie należy zmieniać.
PASCAL dodał wskaźniki do leksykonu projektowania języka.
C dostarczył call-by-value i symulował call-by-referencję poprzez zdefiniowanie operatora kludge w celu zwrócenia wskaźnika do dowolnego obiektu w pamięci.
Późniejsze języki kopiowały C, głównie dlatego, że projektanci nigdy nie widzieli nic innego. Prawdopodobnie dlatego tak popularna jest funkcja call-by-value.
C ++ dodał kludge na górze C kludge, aby zapewnić call-by-reference.
Teraz, jako bezpośredni rezultat call-by-value vs. call-by-reference vs. call-by-pointer-kludge, C i C ++ (programiści) mają straszne bóle głowy ze wskaźnikami const i wskaźnikami const (tylko do odczytu) przedmioty
Adzie udało się uniknąć tego koszmaru.
Ada nie ma wyraźnej funkcji call-by-value vs. call-by-reference. Ada ma raczej parametry wejściowe (które można odczytać, ale nie zapisane), parametry wyjściowe (które MUSZĄ zostać zapisane, zanim będą mogły zostać odczytane) oraz parametry wyjściowe, które można odczytać i zapisać w dowolnej kolejności. Kompilator decyduje, czy dany parametr jest przekazywany przez wartość, czy przez odniesienie: jest on przezroczysty dla programisty.
źródło
In the beginning, there was FORTRAN
.0
prefiks jest niespójny z każdym innym prefiksem innym niż podstawowy. Może to być nieco usprawiedliwione w C (i w większości kompatybilnych ze źródłami językach, np. C ++, Objective C), jeśli octal był jedyną alternatywną bazą po wprowadzeniu, ale kiedy bardziej nowoczesne języki używają obu0x
i0
od samego początku, to po prostu sprawia, że wyglądają źle wymyślony.Przekazywanie przez odniesienie pozwala na bardzo subtelne niezamierzone skutki uboczne, które są bardzo trudne do prawie niemożliwego do wykrycia, gdy zaczynają powodować niezamierzone zachowanie.
Przechodzą przez wartości, w szczególności
final
,static
czyconst
parametry wejściowe czyni całą tę klasę błędów zniknie.Niezmienne języki są jeszcze bardziej deterministyczne i łatwiejsze do uzasadnienia i zrozumienia, co się dzieje i czego oczekuje się od funkcji.
źródło
Swap
hakerskie, które nie używają zmiennej tymczasowej, choć same prawdopodobnie nie stanowią problemu, pokazują, jak trudny może być debugowanie bardziej złożonego przykładu.Istotą podziału dużych programów na małe podprogramy jest to, że możesz samodzielnie rozumować podprogramy. Przekazywanie przez odniesienie psuje tę właściwość. (Podobnie jak współdzielony stan mutable.)
W rzeczywistości C jest zawsze wartością przekazywaną, nigdy nie referencyjną. Możesz wziąć adres czegoś i przekazać ten adres, ale adres nadal będzie przekazywany przez wartość.
Istnieją dwa główne powody, dla których warto skorzystać z metody przekazywania według odwołania:
Osobiście uważam, że nr 1 jest fałszywy: prawie zawsze jest usprawiedliwieniem złego interfejsu API i / lub języka:
Równie dobrze możesz symulować wiele zwracanych wartości, umieszczając je w lekkiej strukturze danych, takiej jak krotka. Działa to szczególnie dobrze, jeśli język obsługuje dopasowanie wzorca lub wiązanie destrukcyjne. Np. Ruby:
Często nie potrzebujesz nawet wielu zwracanych wartości. Na przykład popularnym wzorcem jest to, że podprogram zwraca kod błędu, a rzeczywisty wynik jest zapisywany przez odwołanie. Zamiast tego powinieneś po prostu rzucić wyjątek, jeśli błąd jest nieoczekiwany, lub zwrócić,
Either<Exception, T>
jeśli błąd jest oczekiwany. Innym wzorcem jest zwrócenie wartości logicznej, która informuje, czy operacja się powiodła, i zwrócenie rzeczywistego wyniku przez referencję. Ponownie, jeśli błąd jest nieoczekiwany, powinieneś zamiast tego zgłosić wyjątek, jeśli błąd jest oczekiwany, np. Podczas wyszukiwania wartości w słowniku, powinieneśMaybe<T>
zamiast tego zwrócić wartość .Przekazywanie przez odniesienie może być bardziej wydajne niż przekazywanie przez wartość, ponieważ nie trzeba kopiować wartości.
Nie, Haskell nie jest referencją. Nie jest to również wartość dodana. Przekazywanie przez odniesienie i przekazywanie według wartości są ścisłymi strategiami oceny, ale Haskell nie jest ścisły.
W rzeczywistości specyfikacja Haskell nie określa żadnej konkretnej strategii oceny. Większość implementacji Hakell korzysta z kombinacji imienia i nazwiska i imienia i nazwiska (wariant imienia i nazwiska z zapamiętywaniem), ale standard tego nie nakazuje.
Zauważ, że nawet w przypadku ścisłych języków nie ma sensu rozróżnianie między odniesieniami lub wartościami dla języków funkcjonalnych, ponieważ różnicę między nimi można zaobserwować tylko po zmutowaniu odwołania. Dlatego implementator może swobodnie wybierać między nimi, bez przerywania semantyki języka.
źródło
Istnieją różne zachowania w zależności od modelu wywołującego języka, rodzaju argumentów i modeli pamięci języka.
W przypadku prostych rodzimych typów przekazywanie według wartości pozwala przekazać wartość przez rejestry. Może to być bardzo szybkie, ponieważ wartości nie trzeba ładować z pamięci ani zapisywać. Podobna optymalizacja jest również możliwa po prostu przez ponowne użycie pamięci używanej przez argument po zakończeniu go przez odbiorcę, bez ryzyka zepsucia się z kopią obiektu wywołującego. Gdyby argument był obiektem tymczasowym, prawdopodobnie zapisałbyś w ten sposób pełną kopię (C ++ 11 czyni ten rodzaj optymalizacji jeszcze bardziej oczywistym dzięki nowemu prawemu odnośnikowi i jego semantycznemu ruchowi).
W wielu językach OO (C ++ jest bardziej wyjątkiem w tym kontekście), nie można przekazać obiektu przez wartość. Jesteś zmuszony przekazać to przez odniesienie. Dzięki temu kod jest domyślnie polimorficzny i jest bardziej zgodny z pojęciem instancji właściwym dla OO. Ponadto, jeśli chcesz przekazać wartość, musisz sam wykonać kopię, potwierdzając koszt wydajności, która wygenerowała takie działanie. W takim przypadku wybierz język, który ma większe szanse na najlepszą wydajność.
W przypadku języków funkcjonalnych myślę, że podanie wartości lub referencji jest po prostu kwestią optymalizacji. Ponieważ funkcje w takim języku są czyste i wolne od skutków ubocznych, naprawdę nie ma powodu, aby kopiować wartość, z wyjątkiem prędkości. Jestem nawet całkiem pewien, że taki język często dzieli tę samą kopię obiektów o tych samych wartościach, możliwość dostępna tylko wtedy, gdy użyjesz semantycznej referencji (const). Python użył również tej sztuczki do tworzenia liczb całkowitych i wspólnych (takich jak nazwy metod i klas), wyjaśniając, dlaczego liczby całkowite i ciągi są stałymi obiektami w Pythonie. Pomaga to również ponownie zoptymalizować kod, umożliwiając na przykład porównanie wskaźnika zamiast porównania zawartości i leniwą ocenę niektórych danych wewnętrznych.
źródło
Możesz przekazać wyrażenie według wartości, to naturalne. Przekazywanie wyrażenia przez (tymczasowe) odwołanie jest ... dziwne.
źródło
Jeśli przejdziesz przez odniesienie, wtedy efektywnie zawsze będziesz pracować z wartościami globalnymi, ze wszystkimi problemami globalnymi (mianowicie zakresem i niezamierzonymi skutkami ubocznymi).
Referencje, podobnie jak globale, są czasem korzystne, ale nie powinny być twoim pierwszym wyborem.
źródło