Operator dot ( .
) służy do uzyskiwania dostępu do elementu struktury, podczas gdy operator strzałki ( ->
) w C służy do uzyskiwania dostępu do elementu struktury, do którego odwołuje się dany wskaźnik.
Sam wskaźnik nie ma żadnych elementów, do których można uzyskać dostęp za pomocą operatora kropki (w rzeczywistości jest to tylko liczba opisująca lokalizację w pamięci wirtualnej, więc nie ma żadnych elementów). Tak więc nie byłoby dwuznaczności, gdybyśmy po prostu zdefiniowali operator kropki, aby automatycznie wyłuskać wskaźnik, jeśli jest on używany na wskaźniku (informacja znana kompilatorowi w czasie kompilacji).
Dlaczego więc twórcy języków postanowili skomplikować sprawę, dodając ten pozornie niepotrzebny operator? Jaka jest ważna decyzja dotycząca projektu?
źródło
.
operator ma wyższy priorytet niż*
operator? Jeśli nie, moglibyśmy mieć * ptr.member i var.member.Odpowiedzi:
Zinterpretuję twoje pytanie jako dwa pytania: 1) dlaczego w
->
ogóle istnieje, i 2) dlaczego.
nie wyłacza automatycznie wskaźnika. Odpowiedzi na oba pytania mają historyczne korzenie.Dlaczego w
->
ogóle istnieje?W jednej z pierwszych wersji języka C (który będę określał jako CRM dla „ C Reference Manual ”, który pojawił się wraz z 6. edycją Unix w maju 1975 r.), Operator
->
miał bardzo ekskluzywne znaczenie, nie był synonimem*
i.
kombinacjąJęzyk C opisany przez CRM pod wieloma względami bardzo różnił się od współczesnego C. W CRM struct członkowie zaimplementowali globalną koncepcję przesunięcia bajtów , którą można dodać do dowolnej wartości adresu bez ograniczeń typu. To znaczy wszystkie nazwiska wszystkich członków struktury miały niezależne znaczenie globalne (i dlatego musiały być unikalne). Na przykład możesz zadeklarować
a nazwa
a
oznaczałaby przesunięcie 0, a nazwab
oznaczałaby przesunięcie 2 (przy założeniu, żeint
rozmiar 2 nie ma wypełnienia). Język wymagał, aby wszyscy członkowie wszystkich struktur w jednostce tłumaczenia posiadali unikalne nazwy lub oznaczali tę samą wartość przesunięcia. Np. W tej samej jednostce tłumaczeniowej, którą można dodatkowo zadeklarowaći to by było OK, ponieważ nazwa
a
konsekwentnie oznaczałaby offset 0. Ale ta dodatkowa deklaracjabyłby formalnie nieważny, ponieważ próbował „przedefiniować”
a
jako przesunięcie 2 ib
jako przesunięcie 0.I tu pojawia się
->
operator. Ponieważ nazwa każdego członka struktury ma swoje własne samowystarczalne znaczenie globalne, język obsługiwał takie wyrażenia jak tePierwszy przydział został zinterpretowany przez kompilator jako „adres odbioru
5
, dodać przesunięcie2
do niego i przypisać42
doint
wartości uzyskanej w adresie”. Czyli powyżej byłoby przypisać42
doint
wartości w adresie7
. Zauważ, że to użycie->
nie dbało o rodzaj wyrażenia po lewej stronie. Lewa strona została zinterpretowana jako adres numeryczny wartości (może to być wskaźnik lub liczba całkowita).Tego rodzaju oszustwo nie było możliwe przy użyciu
*
i.
kombinacji. Nie mogłeś zrobićponieważ
*i
jest już niepoprawnym wyrażeniem.*
Operatora, ponieważ jest oddzielone od.
nakłada bardziej rygorystyczne wymogi pisania na jej argumentu. Aby zapewnić możliwość obejścia tego ograniczenia, CRM wprowadził->
operatora, który jest niezależny od rodzaju operandu po lewej stronie.Jak zauważył Keith w komentarzach, ta różnica między kombinacją
->
a*
+.
jest tym, co CRM nazywa „rozluźnieniem wymogu” w 7.1.8: Z wyjątkiem rozluźnienia wymoguE1
typu wskaźnikowego, wyrażenieE1−>MOS
jest dokładnie równoważne z(*E1).MOS
Później w K&R C wiele funkcji pierwotnie opisanych w CRM zostało znacznie przerobionych. Idea „struktury członka jako globalnego identyfikatora przesunięcia” została całkowicie usunięta. A funkcjonalność
->
operatora stała się w pełni identyczna z funkcjonalnością*
i.
kombinacją.Dlaczego nie można
.
wyrejestrować wskaźnika automatycznie?Ponownie, w wersji CRM języka lewy argument z
.
operatorem musiała być lwartość . To był jedyny wymóg nałożony na ten operand (i to go odróżniało->
, jak wyjaśniono powyżej). Zauważ, że CRM nie wymagał, aby lewy operand.
miał typ struktury. Wymagało tylko, aby była to wartość, każda wartość. Oznacza to, że w CRM w wersji C można napisać taki kodW tym przypadku kompilator będzie pisać
55
do oint
wartości umieszczonej w bajcie offsetu 2 w ciągłym bloku pamięci znanego jakoc
, choć typstruct T
nie miał pole o nazwieb
. Kompilator nie przejmuje sięc
w ogóle faktycznym typem . Chodziło tylko o to, żebyc
była to wartość: jakiś zapisywalny blok pamięci.Teraz zauważ, że jeśli to zrobiłeś
kod będzie uznane za ważne (ponieważ
s
jest również lwartością) oraz kompilator po prostu próba zapisu danych do wskaźnikas
samego , co bajt przesunięcie 2. Oczywiście, takie rzeczy mogą łatwo spowodować przekroczenie pamięci, ale język nie zajmował się takimi sprawami.Tj. W tej wersji języka twój pomysł na przeciążanie operatora
.
dla typów wskaźników nie zadziałałby: operator.
miał już bardzo konkretne znaczenie, gdy był używany ze wskaźnikami (ze wskaźnikami wartości lub w ogóle wartościami). To była bardzo dziwna funkcjonalność, bez wątpienia. Ale to było wtedy.Oczywiście, ta dziwna funkcjonalność nie jest bardzo silnym powodem, aby nie wprowadzać przeciążonego
.
operatora wskaźników (jak sugerowałeś) w przerobionej wersji C - K&R C. Ale tego nie zrobiono. Być może w tym czasie był napisany jakiś starszy kod w CRM w wersji C, który musiał być obsługiwany.(Adres do 1975 C Instrukcja referencyjnych nie może być stabilna. Innym kopii, ewentualnie z pewnymi subtelnych różnic jest tutaj ).
źródło
*i
była to wartość domyślnego typu (int?) Pod adresem 5? Wtedy (* i) .b działałoby w ten sam sposób.struct stat
) Poprzedza swoje pola (npst_mode
.).Oprócz przyczyn historycznych (dobrych i już zgłoszonych) istnieje również mały problem z pierwszeństwem operatorów: operator kropki ma wyższy priorytet niż operator gwiazdy, więc jeśli masz strukturę zawierającą wskaźnik do struktury zawierającą wskaźnik do struktury ... Te dwa są równoważne:
Ale drugi jest wyraźnie bardziej czytelny. Operator strzałek ma najwyższy priorytet (podobnie jak kropka) i kojarzy od lewej do prawej. Myślę, że jest to bardziej zrozumiałe niż użycie operatora kropki zarówno dla wskaźników do struct i struct, ponieważ znamy typ z wyrażenia bez konieczności patrzenia na deklarację, która może nawet znajdować się w innym pliku.
źródło
a.b.c.d
jako(*(*(*a).b).c).d
, co czyni->
operatora bezużytecznym. Tak więc wersja OP (a.b.c.d
) jest równie czytelna (w porównaniu doa->b->c->d
). Dlatego twoja odpowiedź nie odpowiada na pytanie PO.a.b.c.d
ia->b->c->d
jako dwie bardzo różne rzeczy: Pierwsza to dostęp do pojedynczej pamięci do zagnieżdżonego pod-obiektu (w tym przypadku jest tylko jeden obiekt pamięci ), drugi to trzy dostępy do pamięci, ścigające wskaźniki przez cztery prawdopodobne różne obiekty. To ogromna różnica w układzie pamięci i uważam, że C ma rację, bardzo wyraźnie rozróżniając te dwa przypadki.C wykonuje również dobrą robotę, nie czyniąc niczego niejednoznacznym.
Oczywiście kropka może być przeciążona, co oznacza obie rzeczy, ale strzałka upewnia programistę, że działa na wskaźniku, tak jak wtedy, gdy kompilator nie pozwala mieszać dwóch niekompatybilnych typów.
źródło