Jaka jest różnica między przekazywaniem przez odniesienie a przekazywaniem według wartości?

Odpowiedzi:

1079

Przede wszystkim rozróżnienie „przekazywanie przez wartość vs. przekazywanie przez odniesienie” zdefiniowane w teorii CS jest obecnie nieaktualne, ponieważ technika pierwotnie zdefiniowana jako „przekazywanie przez odniesienie” od tego czasu nie sprzyjała i jest rzadko używana. 1

Nowsze języki 2 zwykle używają innej (ale podobnej) pary technik, aby osiągnąć te same efekty (patrz poniżej), co jest głównym źródłem zamieszania.

Drugim źródłem nieporozumień jest fakt, że w „przekazywaniu przez odniesienie” „odniesienie” ma węższe znaczenie niż ogólny termin „odniesienie” (ponieważ fraza poprzedza je).


Teraz autentyczna definicja to:

  • Gdy parametr jest przekazywany przez referencję , wywołujący i odbierający używają tej samej zmiennej dla parametru. Jeśli odbiorca modyfikuje zmienną parametru, efekt jest widoczny dla zmiennej dzwoniącego.

  • Gdy parametr jest przekazywany przez wartość , wywołujący i odbierający mają dwie niezależne zmienne o tej samej wartości. Jeśli odbiorca modyfikuje zmienną parametru, efekt nie jest widoczny dla dzwoniącego.

W tej definicji należy zwrócić uwagę na:

  • „Zmienna” oznacza tutaj samą zmienną wywołującą (lokalną lub globalną) - tzn. Jeśli przekażę zmienną lokalną przez odniesienie i przypiszę jej, zmienię samą zmienną wywołującą, a nie np. Cokolwiek, na co wskazuje, jeśli jest wskaźnikiem .

    • Jest to obecnie uważane za złą praktykę (jako domniemana zależność). Jako takie, praktycznie wszystkie nowsze języki są wyłącznie lub prawie wyłącznie wartościami dodanymi. Przekazywanie przez odwołanie jest teraz używane głównie w postaci „argumentów wyjściowych / wejściowych” w językach, w których funkcja nie może zwrócić więcej niż jednej wartości.
  • Znaczenie „odniesienia” w „przekazać przez odniesienie” . Różnica w stosunku do ogólnego terminu „odniesienie” polega na tym, że to „odniesienie” jest tymczasowe i dorozumiane. To, co w zasadzie otrzymuje callee, to „zmienna”, która jest w pewnym sensie „taka sama” jak pierwotna. To, jak konkretnie osiąga się ten efekt, jest nieistotne (np. Język może również ujawniać niektóre szczegóły implementacji - adresy, wskaźniki, dereferencje - to wszystko jest nieistotne; jeśli efekt netto jest taki, to jest to przejście przez odniesienie).


Teraz, w nowoczesnych językach, zmienne są zwykle „typami referencyjnymi” (inna koncepcja wymyślona później niż „przekazana przez referencję” i zainspirowana przez nią), tj. Rzeczywiste dane obiektowe są gdzieś przechowywane osobno (zwykle na stercie), i tylko „odniesienia” do niego są zawsze przechowywane w zmiennych i przekazywane jako parametry. 3)

Przekazanie takiego odniesienia mieści się w wartości przekazywanej, ponieważ wartość zmiennej jest technicznie samym odwołaniem, a nie obiektem, do którego się odnosi. Jednak efekt netto w programie może być taki sam, jak wartość przekazywana lub wartość referencyjna:

  • Jeśli referencja zostanie pobrana ze zmiennej wywołującej i przekazana jako argument, będzie to miało taki sam efekt jak przekazywanie przez referencję: jeśli odnośny obiekt zostanie zmutowany w odbierającym, wywołujący zobaczy zmianę.
    • Jednak jeśli zmienna zawierająca to odniesienie zostanie ponownie przypisana, przestanie wskazywać na ten obiekt, więc wszelkie dalsze operacje na tej zmiennej wpłyną na wszystko, na co wskazuje teraz.
  • Aby uzyskać taki sam efekt, jak wartość przekazywana, w pewnym momencie tworzona jest kopia obiektu. Opcje obejmują:
    • Dzwoniący może po prostu wykonać kopię prywatną przed połączeniem i zamiast tego podać adresatowi odwołanie.
    • W niektórych językach niektóre typy obiektów są „niezmienne”: każda operacja na nich, która wydaje się zmieniać wartość, w rzeczywistości tworzy zupełnie nowy obiekt bez wpływu na oryginalny. Tak więc przekazanie obiektu takiego typu jako argumentu zawsze skutkuje przekazywaniem wartości: kopia dla odbiorcy zostanie wykonana automatycznie, jeśli i kiedy będzie wymagała zmiany, i obiekt dzwoniącego nigdy nie zostanie zmieniony.
      • W językach funkcjonalnych wszystkie obiekty są niezmienne.

Jak widać, ta para technik jest prawie taka sama jak w definicji, tylko z poziomem pośrednim: wystarczy zastąpić „zmienną” „obiektem odniesienia”.

Nie ma dla nich żadnej uzgodnionej nazwy, co prowadzi do zniekształconych wyjaśnień, takich jak „wywołaj według wartości, gdzie wartość jest odniesieniem”. W 1975 r. Barbara Liskov zasugerowała termin „ współdzielenie obiektu przez obiekt ” (lub czasem po prostu „współdzielenie obiektu przez telefon”), chociaż nigdy się nie przyjął. Ponadto żadne z tych wyrażeń nie jest zbieżne z oryginalną parą. Nic dziwnego, że stare terminy zostały użyte ponownie w nieobecności czegoś lepszego, co doprowadziło do zamieszania. 4


UWAGA : Przez długi czas ta odpowiedź brzmiała:

Powiedz, że chcę udostępnić Ci stronę internetową. Jeśli podam ci adres URL, przekazuję przez referencję. Możesz użyć tego adresu URL, aby zobaczyć tę samą stronę internetową, którą widzę. Jeśli ta strona zostanie zmieniona, oboje zobaczymy zmiany. Jeśli usuniesz adres URL, wszystko, co robisz, to niszczenie odwołania do tej strony - nie usuwasz samej strony.

Jeśli wydrukuję stronę i dam ci wydruk, przekażę wartość. Twoja strona jest rozłączoną kopią oryginału. Nie zobaczysz żadnych kolejnych zmian, a wszelkie zmiany, które wprowadzisz (np. Bazgroły na wydruku) nie pojawią się na oryginalnej stronie. Jeśli zniszczysz wydruk, faktycznie zniszczyłeś kopię obiektu - ale oryginalna strona internetowa pozostaje nienaruszona.

Jest to w większości poprawne, z wyjątkiem węższego znaczenia „odniesienia” - jest ono zarówno tymczasowe, jak i niejawne (nie musi, ale jest wyraźne i / lub trwałe są dodatkowymi cechami, a nie częścią semantycznego przejścia przez odniesienie , jak wyjaśniono powyżej). Bliższą analogią byłoby przekazanie kopii dokumentu w porównaniu z zaproszeniem do pracy nad oryginałem.


1 O ile nie programujesz w Fortran lub Visual Basic, nie jest to zachowanie domyślne, aw większości języków we współczesnym użyciu prawdziwe wywołanie referencyjne nie jest nawet możliwe.

2 Wspiera go także spora liczba starszych

3 W kilku współczesnych językach wszystkie typy są typami referencyjnymi. To podejście zostało wprowadzone przez CLU w 1975 roku i od tego czasu zostało przyjęte przez wiele innych języków, w tym Python i Ruby. Wiele innych języków stosuje podejście hybrydowe, w którym niektóre typy są „typami wartości”, a inne są „typami referencyjnymi” - wśród nich są C #, Java i JavaScript.

4 Nie ma nic złego w recyklingu starego, odpowiedniego terminu jako takiego , ale trzeba jakoś wyjaśnić, które znaczenie jest używane za każdym razem. Nie robienie tego jest dokładnie tym, co powoduje zamieszanie.

ivan_pozdeev
źródło
Osobiście użyłbym terminów „nowe” lub „pośrednie” pass-by-value / pass-by-reference dla nowych technik.
ivan_pozdeev
Podana przez ciebie „autentyczna” definicja nie jest definicją podaną w prawie każdym wstępnym kursie programowania. Google, co jest przekazywane przez odniesienie i nie dostaniesz tej odpowiedzi. Podana przez ciebie autentyczna definicja niewłaściwie używa odwołania do słowa, ponieważ kiedy podążasz za tą definicją, używasz aliasu, a nie odwołania: masz dwie zmienne, które są w rzeczywistości tą samą zmienną, to jest alias, a nie odwołanie. Twoja autentyczna definicja powoduje masowe zamieszanie bez powodu. Wystarczy powiedzieć, że podanie przez odniesienie oznacza przekazanie adresu. Ma to sens i pozwoliłoby uniknąć tego bezcelowego zamieszania.
YungGun,
@YungGun 1) Proszę podać link do „definicji podanej na prawie każdym kursie wstępnego programowania”. Zauważ też, że ma to być jasne w dzisiejszych realiach, a nie w rzeczywistości sprzed dekady czy trzech lat, kiedy napisano jakiś kurs CS. 2) „Adres” nie może być użyty w definicji, ponieważ celowo abstrahuje od możliwych implementacji. Np. Niektóre języki (Fortran) nie mają wskaźników; różnią się także tym, czy ujawniają użytkownikowi nieprzetworzony adres (VB nie); nie musi to być również nieprzetworzony adres pamięci, wystarczyłoby cokolwiek, co pozwoliłoby połączyć się ze zmienną.
ivan_pozdeev
@ivan_podeev brak linku przepraszam. Mówię „prawie każdy kurs wprowadzający”, ponieważ osobiście poszedłem na studia i też wziąłem bootcampy programistyczne, które mnie tego nauczyły. Kursy te były nowoczesne (mniej niż 5 lat temu). „Surowy adres” jest synonimem „wskaźnika” ... Być może technicznie jest poprawny (według niektórych wybranych linków), ale używany język jest niepraktyczny i mylący dla większości programistów. Jeśli chcesz moje pełne przemyślenia na ten temat, napisałem 3500
słowowy
@YungGun „za długo, nie czytałem”. Rzut oka pokazuje dokładnie zamieszanie nakreślone w odpowiedzi. Przekazywanie przez odniesienie jest abstrakcyjną techniką agnostyczną dla wdrożenia. Nie ma znaczenia, co dokładnie jest przekazywane pod maską, ma znaczenie, jaki ma wpływ na program.
ivan_pozdeev
150

Jest to sposób przekazywania argumentów do funkcji. Przekazywanie przez referencję oznacza, że ​​parametr wywoływanych funkcji będzie taki sam, jak argument przekazanego argumentu (nie wartość, ale tożsamość - sama zmienna). Przekaż przez wartość oznacza, że ​​parametr wywoływanych funkcji będzie kopią przekazanego argumentu wywołujących. Wartość będzie taka sama, ale tożsamość - zmienna - jest inna. Zatem zmiany parametru dokonanego przez wywoływaną funkcję w jednym przypadku zmieniają przekazany argument, aw drugim przypadku po prostu zmieniają wartość parametru w wywoływanej funkcji (która jest tylko kopią). W pośpiechu:

  • Java obsługuje tylko przekazywanie według wartości. Zawsze kopiuje argumenty, nawet jeśli podczas kopiowania odwołania do obiektu, parametr w wywoływanej funkcji będzie wskazywał na ten sam obiekt, a zmiany w tym obiekcie będą widoczne w obiekcie wywołującym. Ponieważ może to być mylące, oto, co mówi o tym Jon Skeet.
  • C # obsługuje przekazywanie przez wartość i przekazywanie przez referencję (słowo kluczowe refużywane w funkcji wywołującej i wywoływanej). Jon Skeet posiada również piękny wyjaśnienie tego tutaj .
  • C ++ obsługuje przekazywanie według wartości i przekazywanie przez referencję (typ parametru odwołania używany w wywoływanej funkcji). Wyjaśnienie tego znajduje się poniżej.

Kody

Ponieważ moim językiem jest C ++, użyję go tutaj

// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
    p = NULL;
}

// passes an integer
void call_by_value(int p) { // :2
    p = 42;
}

// passes an integer by reference
void call_by_reference(int & p) { // :3
    p = 42;
}

// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
    *p = 10; // changes what p points to ("what p references" in java)
    // only changes the value of the parameter, but *not* of 
    // the argument passed by the caller. thus it's pass-by-value:
    p = NULL;
}

int main() {
    int value = 10;
    int * pointer = &value;

    call_by_value(pointer); // :1
    assert(pointer == &value); // pointer was copied

    call_by_value(value); // :2
    assert(value == 10); // value was copied

    call_by_reference(value); // :3
    assert(value == 42); // value was passed by reference

    call_by_value_special(pointer); // :4
    // pointer was copied but what pointer references was changed.
    assert(value == 10 && pointer == &value);
}

A przykład w Javie nie zaszkodzi:

class Example {
    int value = 0;

    // similar to :4 case in the c++ example
    static void accept_reference(Example e) { // :1
        e.value++; // will change the referenced object
        e = null; // will only change the parameter
    }

    // similar to the :2 case in the c++ example
    static void accept_primitive(int v) { // :2
        v++; // will only change the parameter
    }        

    public static void main(String... args) {
        int value = 0;
        Example ref = new Example(); // reference

        // note what we pass is the reference, not the object. we can't 
        // pass objects. The reference is copied (pass-by-value).
        accept_reference(ref); // :1
        assert ref != null && ref.value == 1;

        // the primitive int variable is copied
        accept_primitive(value); // :2
        assert value == 0;
    }
}

Wikipedia

http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value

http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_reference

Ten facet prawie to przybija:

http://javadude.com/articles/passbyvalue.htm

Johannes Schaub - litb
źródło
9
dlaczego głosowanie negatywne? jeśli coś jest nie tak lub prowadzi do nieporozumień, proszę zostawić komentarz.
Johannes Schaub - litb
1
Nie był to mój głos, ale w drobnej kwestii (wiesz, takiej, jaką podjęto w debatach prezydenckich) powiedziałbym, że to bardziej „taktyka” niż „strategia”.
harpo
28
+1 za kompletność. Nie przejmuj się głosami negatywnymi - ludzie robią to z dziwnych powodów. W opinii o pytaniu o kalkulatory wszyscy byli oceniani przez faceta, który nie sądził, że programiści powinni używać kalkulatorów! W każdym razie myślałem, że twoja odpowiedź była bardzo dobra.
Mark Brittingham,
1
Linki do wyjaśnień Skeeta są zepsute.
Programista zorientowany na pieniądze
Linki do wyjaśnień Skeeta są nadal zepsute.
Rokit,
85

Wiele odpowiedzi tutaj (a zwłaszcza najbardziej wysoko ocenionych) jest niepoprawnych pod względem faktycznym, ponieważ źle rozumieją, co tak naprawdę oznacza „wywołanie przez odniesienie”. Oto moja próba uporządkowania spraw.

TL; DR

Mówiąc najprościej:

  • call by value oznacza, że ​​przekazujesz wartości jako argumenty funkcji
  • Wywołanie przez odniesienie oznacza, że ​​przekazujesz zmienne jako argumenty funkcji

W kategoriach metaforycznych:

  • Call by value polega na tym, że zapisuję coś na kartce papieru i wręczam ci . Może to adres URL, może pełna kopia Wojny i pokoju. Bez względu na to, co to jest, jest na kawałku papieru, który ci dałem, a więc teraz to skutecznie twój kawałek papieru . Możesz teraz bazgroić na tym kawałku papieru lub użyć tego kawałka papieru, aby znaleźć coś innego i bawić się nim, cokolwiek.
  • Zadzwoń przez referencję, kiedy dam ci mój notatnik, w którym jest coś zapisanego . Możesz nabazgrać w moim zeszycie (może chcę, żebyś to zrobił, a może nie), a potem trzymam swój zeszyt, bez względu na to, co tam umieścisz. Ponadto, jeśli to, co napisałem ty lub ja, zawiera informacje o tym, jak znaleźć coś gdzie indziej, ty lub ja możemy udać się tam i pogrzebać te informacje.

Co nie znaczy „call by value” i „call by reference”

Zauważ, że obie te koncepcje są całkowicie niezależne i ortogonalne od koncepcji typów referencyjnych (która w Javie jest wszystkimi typami, które są podtypami Object, a w C # wszystkie classtypy), lub koncepcji typów wskaźników jak w C (które są semantycznie równoważne na „typy referencyjne” Javy, po prostu z inną składnią).

Pojęcie typu odwołania odpowiada adresowi URL: zarówno on sam, jak i informacja, i jest odniesieniem ( wskaźnikiem , jeśli wolisz) do innych informacji. Możesz mieć wiele kopii adresu URL w różnych miejscach i nie zmieniają one witryny, do której prowadzą wszystkie linki; jeśli witryna zostanie zaktualizowana, każda kopia adresu URL będzie nadal prowadzić do zaktualizowanych informacji. I odwrotnie, zmiana adresu URL w jednym miejscu nie wpłynie na żadną inną pisemną kopię adresu URL.

Zauważ, że C ++ ma pojęcie „referencje” (np. int&), Które nie jest podobne do „typów referencji” Java i C #, ale jest jak „wywołanie przez referencję”. „Typy referencyjne” Java i C # oraz wszystkie typy w Pythonie przypominają to, co C i C ++ nazywają „typami wskaźników” (np int*.).


OK, oto dłuższe i bardziej formalne wyjaśnienie.

Terminologia

Na początek chcę podkreślić kilka ważnych fragmentów terminologii, aby pomóc wyjaśnić moją odpowiedź i upewnić się, że wszyscy odwołujemy się do tych samych pomysłów, gdy używamy słów. (W praktyce uważam, że zdecydowana większość nieporozumień związanych z takimi tematami wynika z używania słów w sposób, który nie do końca przekazuje zamierzone znaczenie).

Na początek przykład w deklaracji funkcji w języku C podobnym do C:

void foo(int param) {  // line 1
  param += 1;
}

A oto przykład wywołania tej funkcji:

void bar() {
  int arg = 1;  // line 2
  foo(arg);     // line 3
}

Korzystając z tego przykładu, chcę zdefiniować kilka ważnych fragmentów terminologii:

  • foojest funkcją zadeklarowaną w wierszu 1 (Java nalega na stworzenie metod wszystkich funkcji, ale koncepcja jest taka sama bez utraty ogólności; C i C ++ wprowadzają rozróżnienie między deklaracją a definicją, do której nie będę tutaj wchodził)
  • paramto parametr formalny do foozadeklarował również na linii 1
  • argjest zmienną , w szczególności lokalną zmienną funkcji bar, zadeklarowaną i zainicjowaną w wierszu 2
  • argjest również argumentem do konkretnego wywołania z foona linii 3

Istnieją dwa bardzo ważne zestawy pojęć do rozróżnienia. Pierwszy to wartość w porównaniu ze zmienną :

  • Wartość jest wynikiem oceny wyrażenie w języku. Na przykład w barpowyższej funkcji po wierszu int arg = 1;wyrażenie argma wartość 1 .
  • Zmienna jest pojemnik dla wartości . Zmienna może być zmienna (jest to domyślna opcja w większości języków podobnych do C), tylko do odczytu (np. Zadeklarowana przy użyciu Javy finallub C # readonly) lub głęboko niezmienna (np. Przy użyciu C ++ const).

Inną ważną parą pojęć do rozróżnienia jest parametr kontra argument :

  • Parametr (zwany również parametr formalny ) jest zmienna , które muszą być dostarczone przez dzwoniącego podczas wywoływania funkcji.
  • Argumentem jest wartość , która jest dostarczana przez wywołującego funkcję do spełnienia konkretną formalny parametr tej funkcji

Zadzwoń według wartości

W wywołaniu według wartości formalne parametry funkcji są zmiennymi, które są nowo tworzone dla wywołania funkcji i które są inicjowane wartościami ich argumentów.

Działa to dokładnie tak samo, jak wszelkie inne rodzaje zmiennych są inicjowane wartościami. Na przykład:

int arg = 1;
int another_variable = arg;

Tutaj argi another_variablesą całkowicie niezależnymi zmiennymi - ich wartości mogą się zmieniać niezależnie od siebie. Jednak w punkcie, w którym another_variablejest zadeklarowane, jest on inicjowany, aby przechowywał tę samą wartość, która argposiada - czyli jest 1.

Ponieważ są to zmienne niezależne, zmiany, another_variablektóre nie mają wpływu na arg:

int arg = 1;
int another_variable = arg;
another_variable = 2;

assert arg == 1; // true
assert another_variable == 2; // true

Jest to dokładnie to samo, co związek między argi paramw naszym powyższym przykładzie, który powtórzę tutaj dla symetrii:

void foo(int param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

Jest tak, jakbyśmy napisali kod w ten sposób:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here

Oznacza to, że cechą charakterystyczną tego, co oznacza wywołanie przez wartość , jest to, że odbiorca ( foow tym przypadku) otrzymuje wartości jako argumenty, ale ma własne oddzielne zmienne dla tych wartości ze zmiennych wywołującego ( barw tym przypadku).

Wracając do mojej metafory powyżej, jeśli jestem bar, a ty foo, gdy wzywam was, oddam ci kawałek papieru z wartością na nim napisane. Nazywasz ten kawałek papieru param. Ta wartość jest kopią wartości, którą zapisałem w zeszycie (moje zmienne lokalne) w zmiennej, którą wywołuję arg.

(Nawiasem mówiąc : w zależności od sprzętu i systemu operacyjnego istnieją różne konwencje wywoływania dotyczące sposobu wywoływania jednej funkcji z drugiej. Konwencja wywoływania przypomina naszą decyzję, czy zapisać wartość na kartce papieru, a następnie przekazać ją tobie , lub jeśli masz kawałek papieru, na którym piszę, lub jeśli piszę go na ścianie przed nami obojgiem. To także interesujący temat, ale daleko poza zakresem tej i tak już długiej odpowiedzi.)

Zadzwoń przez referencję

W wywołaniu przez odniesienie parametry formalne funkcji są po prostu nowymi nazwami dla tych samych zmiennych, które wywołujący podaje jako argumenty.

Wracając do naszego przykładu powyżej, jest to równoważne z:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here

Ponieważ paramjest to po prostu inna nazwa arg- to znaczy, że są tą samą zmienną , w której zmiany paramsą odzwierciedlone arg. Jest to podstawowy sposób, w jaki połączenie przez odniesienie różni się od połączenia według wartości.

Bardzo niewiele języków obsługuje wywołanie przez odniesienie, ale C ++ może to zrobić w następujący sposób:

void foo(int& param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

W tym przypadku, paramnie tylko mają taką samą wartość , jak argto faktycznie jest arg (tylko pod inną nazwą), a więc barmożna zauważyć, że argzostała zwiększona.

Zauważ, że nie działa to w żaden sposób z Java, JavaScript, C, Objective-C, Python ani prawie żadnego innego popularnego języka. Oznacza to, że te języki nie są wywoływane przez odniesienie, są wywoływane według wartości.

Dodatek: połączenie przez udostępnianie obiektów

Jeśli masz wywołanie według wartości , ale rzeczywista wartość jest typem referencyjnym lub wskaźnikiem , to sama „wartość” nie jest zbyt interesująca (np. W C jest to liczba całkowita o rozmiarze specyficznym dla platformy) - co to jest ciekawe jest to, na co wskazuje ta wartość .

Jeśli to, na co wskazuje ten typ odniesienia (czyli wskaźnik), jest zmienne, możliwy jest interesujący efekt: możesz zmodyfikować wartość wskazywaną, a dzwoniący może obserwować zmiany wartości wskazywanej, nawet jeśli dzwoniący nie może obserwować zmiany w samym wskaźniku.

Aby ponownie pożyczyć analogię adresu URL, fakt, że dałem ci kopię adresu URL do strony internetowej, nie jest szczególnie interesujący, jeśli chodzi o stronę, a nie o adres URL. Fakt, że zapisujesz swoją kopię adresu URL, nie ma wpływu na moją kopię adresu URL, nie jest to dla nas ważne (w rzeczywistości w językach takich jak Java i Python „URL” lub wartość typu odwołania może wcale nie będzie modyfikowany, tylko to, na co wskaże, może).

Barbara Liskov, gdy wynalazła język programowania CLU (który miał taką semantykę), zdała sobie sprawę, że istniejące terminy „call by value” i „call by reference” nie były szczególnie przydatne do opisu semantyki tego nowego języka. Wymyśliła więc nowy termin: połączenie przez udostępnianie obiektów .

Podczas omawiania języków, które technicznie nazywane są wartością, ale tam, gdzie często używane są typy referencyjne lub wskaźnikowe (to znaczy: prawie każdy współczesny język programowania imperatywny, obiektowy lub oparty na wielu paradygmatach), uważam, że jest to mniej mylące po prostu unikaj mówienia o połączeniu według wartości lub połączeniu przez odniesienie . Trzymaj się połączenia przez udostępnianie obiektu (lub po prostu zadzwoń przez obiekt ) i nikt się nie pomyli. :-)

Daniel Pryden
źródło
Wyjaśniono lepiej: Istnieją dwa bardzo ważne zestawy pojęć do rozróżnienia. The first is value versus variable. The other important pair of concepts to distinguish is parameter versus argument:
SK Venkat,
2
Doskonała odpowiedź. Wydaje mi się, że dodałbym, że nie trzeba tworzyć nowego magazynu na zasadzie odniesienia. Nazwa parametru odnosi się do oryginalnej pamięci (pamięci) .Dziękujemy
drlolly
1
Najlepsza odpowiedź IMO
Rafael Eyng
59

Przed zrozumieniem 2 warunków MUSISZ zrozumieć następujące. Każdy obiekt ma 2 rzeczy, dzięki którym można go rozróżnić.

  • Jego wartość.
  • Jego adres

Więc jeśli powiesz employee.name = "John"

wiedz, że są 2 rzeczy name. Jej wartość, która jest "John"i również jego położenie w pamięci, który jest jakiś numer szesnastkowy może tak: 0x7fd5d258dd00.

W zależności od architektury języka lub typu (klasy, struktury itp.) Twojego obiektu, możesz przenieść "John"lub0x7fd5d258dd00

Przekazywanie "John"jest znane jako przekazywanie według wartości. Przekazywanie 0x7fd5d258dd00jest znane jako przekazywanie przez odniesienie. Każdy, kto wskazuje na tę lokalizację pamięci, będzie miał dostęp do wartości "John".

Aby uzyskać więcej informacji na ten temat, polecam przeczytać o dereferencjowaniu wskaźnika i dowiedzieć się, dlaczego wybrać struct (typ wartości) zamiast klasy (typ odwołania)

miód
źródło
3
To jest to, czego szukałem, właściwie należy szukać tej koncepcji, a nie tylko wyjaśnienia, kciuki w górę, stary.
Haisum Usman
Java jest zawsze przekazywana przez wartość. Przekazywanie odwołań do obiektów w języku Java jest uważane za przekazywanie według wartości. Jest to sprzeczne z Twoim stwierdzeniem „Przekazywanie 0x7fd5d258dd00 jest znane jako przekazywanie przez referencję”.
chetan raina
53

Oto przykład:

#include <iostream>

void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }

int main()
{
    int x = 0;
    by_val(x); std::cout << x << std::endl;  // prints 0
    by_ref(x); std::cout << x << std::endl;  // prints 2

    int y = 0;
    by_ref(y); std::cout << y << std::endl;  // prints 2
    by_val(y); std::cout << y << std::endl;  // prints 2
}
pyon
źródło
1
Myślę, że jest jeden problem, ponieważ ostatnia linia powinna wypisać 0 zamiast 2. Uprzejmie powiedz mi, jeśli czegoś brakuje.
Taimoor Changaiz
@TaimoorChangaiz; Która „ostatnia linia”? Nawiasem mówiąc, jeśli możesz używać IRC, przejdź do programowania ## na Freenode. O wiele łatwiej byłoby tam wyjaśnić. Mój nick to „pyon”.
pyon
1
@ EduardoLeón by_val (y); std :: cout << y << std :: endl; // drukuje 2
Taimoor Changaiz
5
@TaimoorChangaiz: Dlaczego nie wydrukuje 2? yzostał już ustawiony na 2 w poprzednim wierszu. Dlaczego miałoby wrócić do 0?
pyon
@ EduardoLeón my bad. tak masz rację. Dzięki za korektę
Taimoor Changaiz
28

Najprostszym sposobem na uzyskanie tego jest plik Excel. Powiedzmy na przykład, że masz dwie liczby, 5 i 2 odpowiednio w komórkach A1 i B1, i chcesz znaleźć ich sumę w trzeciej komórce, powiedzmy A2. Możesz to zrobić na dwa sposoby.

  • Albo przekazując ich wartości do komórki A2 , wpisując = 5 + 2 w tej komórce. W takim przypadku, jeśli zmienią się wartości komórek A1 lub B1, suma w A2 pozostaje taka sama.

  • Lub przekazując „referencje” komórek A1 i B1 do komórki A2 , wpisując = A1 + B1 . W takim przypadku, jeśli zmienią się wartości komórek A1 lub B1, zmieni się również suma w A2.

Niż Skourtan
źródło
To najprostszy i najlepszy przykład spośród wszystkich innych odpowiedzi.
Amit Ray
18

Przechodząc przez ref, zasadniczo przekazujesz wskaźnik do zmiennej. Przekaż według wartości przekazujesz kopię zmiennej. W podstawowym użyciu zwykle oznacza to, że zmiany pass by ref w zmiennej będą widoczne jako metoda wywołująca i pass by value, których nie chcą.

Craig
źródło
12

Przekaż przez wartość wysyła KOPIA danych przechowywanych w określonej zmiennej, przekazanie przez odniesienie wysyła bezpośredni link do samej zmiennej. Więc jeśli przekażesz zmienną przez odniesienie, a następnie zmienisz zmienną w bloku, do którego ją przekazałeś, zmienna oryginalna zostanie zmieniona. Jeśli po prostu przekażesz wartość, pierwotna zmienna nie będzie mogła zostać zmieniona przez blok, w którym ją przekazałeś, ale dostaniesz kopię tego, co zawierała w momencie wywołania.

MetaGuru
źródło
7

Przekaż według wartości - funkcja kopiuje zmienną i działa z kopią (więc nie zmienia niczego w oryginalnej zmiennej)

Przekaż przez referencję - funkcja używa oryginalnej zmiennej, jeśli zmienisz zmienną w innej funkcji, zmieni się ona również w oryginalnej zmiennej.

Przykład (skopiuj i użyj / wypróbuj to sam i zobacz):

#include <iostream>

using namespace std;

void funct1(int a){ //pass-by-value
    a = 6; //now "a" is 6 only in funct1, but not in main or anywhere else
}
void funct2(int &a){ //pass-by-reference
    a = 7; //now "a" is 7 both in funct2, main and everywhere else it'll be used
}

int main()
{
    int a = 5;

    funct1(a);
    cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 5
    funct2(a);
    cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 7

    return 0;
}

Prosto, zerkaj. Ściany tekstu mogą być złym nawykiem.

użytkownik326964
źródło
Jest to naprawdę pomocne w zrozumieniu, czy wartość parametru została zmieniona, czy nie, dziękuję!
Kevin Zhao
5

Główną różnicą między nimi jest to, że zmienne typu wartości przechowują wartości, więc określenie zmiennej typu wartości w wywołaniu metody przekazuje kopię wartości tej zmiennej do metody. Zmienne typu odniesienia przechowują odniesienia do obiektów, więc określenie zmiennej typu odwołania jako argumentu przekazuje metodzie kopię faktycznego odwołania, które odnosi się do obiektu. Mimo że sama referencja jest przekazywana przez wartość, metoda może nadal używać referencji, którą otrzymuje, do interakcji z oryginalnym obiektem - i ewentualnie do modyfikacji. Podobnie, gdy zwracane są informacje z metody za pomocą instrukcji return, metoda zwraca kopię wartości przechowywanej w zmiennej typu wartości lub kopię odwołania przechowywaną w zmiennej typu odwołania. Po zwróceniu odwołania metoda wywołująca może użyć tego odwołania do interakcji z obiektem, do którego się odwołuje. Więc,

W c #, aby przekazać zmienną przez referencję, tak zwana metoda może modyfikować zmienną, C # zapewnia słowa kluczowe ref i out. Zastosowanie słowa kluczowego ref do deklaracji parametru pozwala przekazać zmienną do metody przez referencję - wywoływana metoda będzie mogła zmodyfikować oryginalną zmienną w wywołującym. Słowo kluczowe ref służy do zmiennych, które zostały już zainicjowane w metodzie wywoływania. Zwykle, gdy wywołanie metody zawiera niezainicjowaną zmienną jako argument, kompilator generuje błąd. Poprzedzenie parametru bez słowa kluczowego tworzy parametr wyjściowy. Wskazuje to kompilatorowi, że argument zostanie przekazany do wywoływanej metody przez odniesienie i że wywoływana metoda przypisze wartość oryginalnej zmiennej w wywołującym. Jeśli metoda nie przypisuje wartości do parametru wyjściowego w każdej możliwej ścieżce wykonania, kompilator generuje błąd. Zapobiega to również generowaniu przez kompilator komunikatu o błędzie dla niezainicjowanej zmiennej, która jest przekazywana jako argument do metody. Metoda może zwrócić tylko jedną wartość do swojego obiektu wywołującego za pomocą instrukcji return, ale może zwrócić wiele wartości poprzez określenie wielu parametrów wyjściowych (ref i / lub out).

patrz dyskusja c # i przykłady tutaj link tekst

Tina Endresen
źródło
3

Przykłady:

class Dog 
{ 
public:
    barkAt( const std::string& pOtherDog ); // const reference
    barkAt( std::string pOtherDog ); // value
};

const &jest ogólnie najlepszy. Nie ponosisz kary za budowę i zniszczenie. Jeśli odniesienie nie jest stałe, interfejs sugeruje, że zmieni przekazane dane.

Deduplikator
źródło
2

Krótko mówiąc, wartość przekazywana to CO TO jest, a wartość referencyjna to GDZIE to jest.

Jeśli twoja wartość to VAR1 = „Happy Guy!”, Zobaczysz tylko „Happy Guy!”. Jeśli VAR1 zmieni się na „Happy Gal!”, Nie będziesz tego wiedział. Jeśli zostanie przekazany przez odniesienie, a VAR1 zmieni się, to zrobisz.

Potwór
źródło
2

Jeśli nie chcesz zmieniać wartości oryginalnej zmiennej po przekazaniu jej do funkcji, funkcja powinna zostać zbudowana z parametrem „ pass by value ”.

Wtedy funkcja będzie miała TYLKO wartość, ale nie adres przekazywanej zmiennej. Bez adresu zmiennej kod wewnątrz funkcji nie może zmienić wartości zmiennej widzianej z zewnątrz funkcji.

Ale jeśli chcesz dać tej funkcji możliwość zmiany wartości zmiennej widzianej z zewnątrz, musisz użyć pass by reference . Ponieważ zarówno wartość, jak i adres (odniesienie) są przekazywane i dostępne wewnątrz funkcji.

Stanley
źródło
1

pass by value oznacza, jak przekazać wartość do funkcji przy użyciu argumentów. przekazując wartość kopiujemy dane przechowywane w określonej przez nas zmiennej i jest ona wolniejsza niż przekazywanie przez referencję, ponieważ dane są kopiowane. z dokonanych zmian w skopiowanych danych nie wpłynie to na oryginalne dane. W drodze referencji lub adresu wysyłamy bezpośredni link do samej zmiennej. lub przekazanie wskaźnika do zmiennej. jest to szybsze, ponieważ zużywa mniej czasu

abhinisha thakur
źródło
0

Oto przykład, który pokazuje różnice między przekazywaniem wartości - wartością wskaźnika - referencją :

void swap_by_value(int a, int b){
    int temp;

    temp = a;
    a = b;
    b = temp;
}   
void swap_by_pointer(int *a, int *b){
    int temp;

    temp = *a;
    *a = *b;
    *b = temp;
}    
void swap_by_reference(int &a, int &b){
    int temp;

    temp = a;
    a = b;
    b = temp;
}

int main(void){
    int arg1 = 1, arg2 = 2;

    swap_by_value(arg1, arg2);
    cout << arg1 << " " << arg2 << endl;    //prints 1 2

    swap_by_pointer(&arg1, &arg2);
    cout << arg1 << " " << arg2 << endl;    //prints 2 1

    arg1 = 1;                               //reset values
    arg2 = 2;
    swap_by_reference(arg1, arg2);
    cout << arg1 << " " << arg2 << endl;    //prints 2 1
}

Metoda „przejścia przez odniesienie” ma ważne ograniczenie . Jeśli parametr jest zadeklarowany jako przekazany przez referencję (więc jest poprzedzony znakiem &), odpowiadający mu parametr rzeczywisty musi być zmienną .

Rzeczywisty parametr odnoszący się do parametru formalnego „przekazany przez wartość” może być ogólnie wyrażeniem , więc można używać nie tylko zmiennej, ale również wyniku wywołania funkcji dosłownej lub nawet funkcji.

Funkcja nie może umieścić wartości w czymś innym niż zmienna. Nie może przypisać nowej wartości do literału ani zmusić wyrażenia do zmiany wyniku.

PS: Możesz także sprawdzić odpowiedź Dylana Beattie w bieżącym wątku, który wyjaśnia to prostymi słowami.

BugShotGG
źródło
Stwierdzasz „jeśli parametr jest zadeklarowany [jako odniesienie], odpowiadający mu rzeczywisty parametr musi być zmienną”, ale ogólnie nie jest to prawdą. Jeśli odwołanie jest powiązane z tymczasowym (takim jak wartość zwracana przez funkcję), jego czas życia jest przedłużany, aby pasował do odwołania. Zobacz tutaj, aby uzyskać szczegółowe informacje.
Chris Hunt