Jeśli C nie obsługuje przekazywania zmiennej przez referencję, dlaczego to działa?
#include <stdio.h>
void f(int *j) {
(*j)++;
}
int main() {
int i = 20;
int *p = &i;
f(p);
printf("i = %d\n", i);
return 0;
}
Wynik:
$ gcc -std=c99 test.c
$ a.exe
i = 21
&
) przed wywołaniem funkcji i jawnie wyrejestrować ją (z*
) w funkcji.f(&i);
jest implementacją przekazywania przez referencję, która nie istnieje wyłącznie w C.Odpowiedzi:
Ponieważ przekazujesz wartość wskaźnika do metody, a następnie odsuwasz ją, aby uzyskać wskazaną liczbę całkowitą.
źródło
func
:func(&A);
przekazałoby wskaźnik do funkcji A bez kopiowania czegokolwiek. Jest to wartość przekazywana, ale ta wartość jest referencją, więc „przechodzisz przez referencję” zmiennej A. Kopiowanie nie jest wymagane. Można powiedzieć, że jest to odnośnik.To nie jest przekazywanie przez odniesienie, to jest przekazywanie przez wartość, jak twierdzili inni.
Reguła jest następująca:
Spróbujmy zobaczyć różnice między parametrami skalarnymi i wskaźnikowymi funkcji.
Zmienne skalarne
Ten krótki program pokazuje wartość przekazywaną za pomocą zmiennej skalarnej.
param
jest nazywany parametrem formalnym, avariable
przy wywołaniu funkcji nazywany jest parametrem faktycznym. Uwaga: zwiększenieparam
funkcji nie zmienia sięvariable
.Wynik to
Złudzenie przejścia przez odniesienie
Nieznacznie zmieniamy fragment kodu.
param
jest teraz wskaźnikiem.Wynik to
To sprawia, że uważasz, że parametr został przekazany przez referencję. Nie było. Został przekazany przez wartość, przy czym wartość parametru jest adresem. Zwiększono wartość typu int i jest to efekt uboczny, który każe nam myśleć, że było to wywołanie funkcji pass-by-reference.
Wskaźniki - przekazywane przez wartość
Jak możemy pokazać / udowodnić ten fakt? Cóż, może możemy wypróbować pierwszy przykład zmiennych skalarnych, ale zamiast skalara używamy adresów (wskaźników). Zobaczmy, czy to może pomóc.
W rezultacie oba adresy będą sobie równe (nie martw się o dokładną wartość).
Przykładowy wynik:
Moim zdaniem dowodzi to wyraźnie, że wskaźniki są przekazywane według wartości. W przeciwnym razie
ptr
byłobyNULL
po wywołaniu funkcji.źródło
Źródło: www-cs-students.stanford.edu
źródło
Ponieważ w powyższym kodzie nie ma przekazu referencyjnego. Używanie wskaźników (takich jak
void func(int* p)
) to przekazywanie według adresu. To jest przejście przez odniesienie w C ++ (nie będzie działać w C):źródło
Twój przykład działa, ponieważ przekazujesz adres swojej zmiennej do funkcji, która manipuluje jej wartością za pomocą operatora dereferencji .
Chociaż C nie obsługuje referencyjnych typów danych , nadal możesz symulować przekazywanie przez referencję, jawnie przekazując wartości wskaźnika, jak w twoim przykładzie.
Referencyjny typ danych C ++ jest mniej wydajny, ale uważany za bezpieczniejszy niż typ wskaźnika odziedziczony z C. To byłby twój przykład przystosowany do użycia referencji C ++ :
źródło
Podajesz wskaźnik (lokalizację adresu) według wartości .
To tak, jakby powiedzieć „oto miejsce z danymi, które chcę, abyś zaktualizował”.
źródło
p jest zmienną wskaźnikową. Jego wartością jest adres i. Kiedy wywołujesz f, przekazujesz wartość p, która jest adresem i.
źródło
Brak referencji w C, ale p „odnosi się” do i, a ty przekazujesz p według wartości.
źródło
W C wszystko jest przekazywane według wartości. Zastosowanie wskaźników daje nam złudzenie, że mijamy przez odniesienie, ponieważ zmienia się wartość zmiennej. Jeśli jednak wydrukujesz adres zmiennej wskaźnikowej, zobaczysz, że nie ma to wpływu. Kopia o wartości adresu jest przekazywana do funkcji. Poniżej znajduje się fragment tego ilustrujący.
źródło
Ponieważ przekazujesz wskaźnik (adres pamięci) do zmiennej p do funkcji f. Innymi słowy, przekazujesz wskaźnik, a nie odniesienie.
źródło
Krótka odpowiedź: Tak, C implementuje przekazywanie parametrów przez referencję za pomocą wskaźników.
Podczas wdrażania przekazywania parametrów projektanci języków programowania stosują trzy różne strategie (lub modele semantyczne): przesyłają dane do podprogramu, odbierają dane z podprogramu lub wykonują oba te zadania. Modele te są powszechnie znane jako odpowiednio w trybie, trybie wyjściowym i trybie wejściowym.
Projektanci języków opracowali kilka modeli w celu wdrożenia tych trzech podstawowych strategii przekazywania parametrów:
Przekazywanie przez wartość (semantyka trybu) Przekazywanie przez wynik (semantyka trybu wyjścia) Przekazywanie przez wartość-wynik (semantyka trybu wejścia) Przekazywanie przez odniesienie (semantyka trybu wprowadzania) Przekazywanie według nazwy (tryb wprowadzania semantyka)
Przekazywanie przez referencję to druga technika przekazywania parametrów w trybie wyjściowym. Zamiast kopiować dane tam iz powrotem między główną procedurą a podprogramem, system wykonawczy wysyła ścieżkę bezpośredniego dostępu do danych dla podprogramu. W tej strategii podprogram ma bezpośredni dostęp do danych, skutecznie współdzieląc dane z główną procedurą. Główną zaletą tej techniki jest to, że jest absolutnie wydajna pod względem czasu i przestrzeni, ponieważ nie ma potrzeby powielania przestrzeni i nie ma operacji kopiowania danych.
Implementacja przekazywania parametrów w C: C implementuje semantykę pass-by-value, a także pass-by-reference (tryb inout), używając wskaźników jako parametrów. Wskaźnik jest wysyłany do podprogramu i żadne rzeczywiste dane w ogóle nie są kopiowane. Ponieważ jednak wskaźnik jest ścieżką dostępu do danych głównej procedury, podprogram może zmieniać dane w głównej procedurze. C przyjął tę metodę z ALGOL68.
Implementacja przekazywania parametrów w C ++: C ++ implementuje również semantykę pass-by-reference (tryb inout) przy użyciu wskaźników, a także specjalnego rodzaju wskaźnika, zwanego typem odniesienia. Wskaźniki typu odniesienia są domyślnie wyłuskiwane w podprogramie, ale ich semantyka jest również przekazywana przez odniesienie.
Zatem kluczową koncepcją jest to, że przekazywanie przez odniesienie implementuje ścieżkę dostępu do danych zamiast kopiowania danych do podprogramu. Ścieżkami dostępu do danych mogą być jawnie wskaźniki dereferencyjne lub wskaźniki auto dereferencyjne (typ odniesienia).
Aby uzyskać więcej informacji, zapoznaj się z książką Koncepcje języków programowania autorstwa Roberta Sebesta, 10 wydanie, rozdział 9.
źródło
Nie przekazujesz int przez referencję, przekazujesz wskaźnik do int przez wartość. Inna składnia, to samo znaczenie.
źródło
void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }
pomocąint main(){ int value=0; func(&value); printf("%i\n",value); return 0; }
, drukuje 111 zamiast 500. Jeśli przekazujesz przez referencję, powinien wypisać 500. C nie obsługuje przekazywania parametru przez referencję.ptr = &newvalue
zostanie niedozwolony. Bez względu na różnicę, myślę, że wskazujesz, że „to samo znaczenie” nie jest do końca prawdziwe, ponieważ masz również dodatkową funkcjonalność w C (możliwość ponownego przypisania samego „odniesienia”).ptr=&newvalue
jeśli jest przekazywana przez referencję. Zamiast tego piszemyptr=newvalue
Oto przykład w C ++:void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }
Stanie się wartość parametru przekazanego do func ()500
.param = new Class()
wewnątrz funkcji nie miałaby wpływu na obiekt wywołujący, jeśli zostanie przekazany przez wartość (wskaźnik). Jeśliparam
zostanie przekazany przez odniesienie, zmiany będą widoczne dla dzwoniącego.W C, aby przekazać referencję, używasz operatora adresu,
&
który powinien być użyty przeciwko zmiennej, ale w twoim przypadku, ponieważ użyłeś zmiennej wskaźnikap
, nie musisz poprzedzać jej operatorem adresu. To byłoby prawdziwe, jeśli stosowany&i
jako parametr:f(&i)
.Możesz również dodać to, aby wyłuskać
p
i zobaczyć, jak pasuje ta wartośći
:źródło
wskaźniki i referencje to dwie różne kwestie.
Kilka rzeczy, o których nie wspomniałem.
Wskaźnik to adres czegoś. Wskaźnik można przechowywać i kopiować jak każdą inną zmienną. Ma więc rozmiar.
Odniesienie należy postrzegać jako ALIAS czegoś. Nie ma rozmiaru i nie można go przechowywać. MUSI odnosić się do czegoś, tj. nie może być zerowy ani zmieniony. Czasami kompilator musi przechowywać referencję jako wskaźnik, ale jest to szczegół implementacji.
W przypadku odniesień nie występują problemy ze wskaźnikami, takie jak obsługa własności, sprawdzanie wartości zerowej, usuwanie odnośników przy użyciu.
źródło
„Przekaż przez referencję” (przy użyciu wskaźników) było w C od samego początku. Jak myślisz, dlaczego tak nie jest?
źródło
j
( nie*j
) wf()
nie ma wpływu nai
wmain()
.Myślę, że C faktycznie obsługuje przekazywanie przez odniesienie.
Większość języków wymaga cukru syntaktycznego, aby przekazywał się przez odniesienie zamiast wartości. (Na przykład C ++ wymaga & w deklaracji parametru).
C wymaga również cukru syntaktycznego. Jest * w deklaracji typu parametru i argumencie. Więc * i & jest składnia C przekazywana przez referencję.
Można teraz argumentować, że rzeczywiste przekazywanie przez referencję powinno wymagać tylko składni w deklaracji parametru, a nie po stronie argumentu.
Ale teraz jest C #, który robi wsparcia poprzez przepuszczanie odniesienia i wymaga cukier syntaktyczny na obie parametrów i argumentów stron.
Argument, że C nie przepuszcza by-ref powoduje, że elementy składniowe wyrażające go wykazują podstawową implementację techniczną wcale nie jest argumentem, ponieważ dotyczy to mniej więcej wszystkich implementacji.
Jedynym pozostałym argumentem jest to, że przekazywanie przez ref w C nie jest cechą monolityczną, ale łączy dwie istniejące cechy. (Weź argument argumentu przez &, spodziewaj się, że odniesienie zostanie wpisane przez *.) Na przykład C # wymaga dwóch elementów składniowych, ale nie można ich używać bez siebie.
Jest to oczywiście niebezpieczny argument, ponieważ wiele innych funkcji w językach składa się z innych funkcji. (jak obsługa napisów w C ++)
źródło
To, co robisz, to przekazywanie wartości, a nie przekazywanie referencji. Ponieważ wysyłasz wartość zmiennej „p” do funkcji „f” (głównie jako f (p);)
Będzie wyglądał ten sam program w C z przekazywaniem przez referencję (!!! ten program daje 2 błędy, ponieważ przekazywanie przez odwołanie nie jest obsługiwane w C)
Wynik:-
źródło