Po obejrzeniu (i zadaniu!) Tylu pytań podobnych do
Co
int (*f)(int (*a)[5])
znaczy w C?
a nawet widząc, że stworzyli program, który ma pomóc ludziom zrozumieć składnię C, nie mogę przestać się zastanawiać:
Dlaczego składnia C została zaprojektowana w ten sposób?
Na przykład, gdybym projektował wskaźniki, tłumaczyłbym „wskaźnik na 10-elementową tablicę wskaźników” na
int*[10]* p;
i nie
int* (*p)[10];
co wydaje mi się, że większość ludzi się zgodzi, jest o wiele mniej proste.
Zastanawiam się więc, dlaczego ta nieintuicyjna składnia? Czy był jakiś konkretny problem, który rozwiązuje składnia (być może dwuznaczność?), Którego nie jestem świadomy?
cdecl
Polecenie jest bardzo przydatny do dekodowania złożone zgłoszenia C. Istnieje również interfejs sieciowy na cdecl.org .Odpowiedzi:
Rozumiem jego historię, ponieważ opiera się ona na dwóch głównych punktach ...
Po pierwsze, autorzy języków woleli, aby składnia była skoncentrowana na zmiennych niż na typach. Oznacza to, że chcieli, aby programista spojrzał na deklarację i pomyślał: „jeśli napiszę wyrażenie
*func(arg)
, to spowoduje, żeint
; jeśli napiszę*arg[N]
, będę mieć liczbę zmiennoprzecinkową” zamiast „func
musi ” być wskaźnikiem funkcji biorącej to i zwracanie tego ”.Wejście C Wikipedia twierdzi, że:
... powołując się na p122 K & R2, które, niestety, nie muszę ręcznie szukać rozszerzonej oferty dla ciebie.
Po drugie, naprawdę bardzo trudno jest znaleźć składnię deklaracji, która jest spójna, gdy mamy do czynienia z dowolnymi poziomami pośrednimi. Twój przykład może zadziałać dobrze w wyrażaniu typu, który wymyśliłeś tam od razu, ale czy skaluje się do funkcji przyjmującej wskaźnik do tablicy tych typów i zwracającej jakiś inny ohydny bałagan? (Może tak, ale sprawdziłeś? Czy możesz to udowodnić? ).
Pamiętaj, że część sukcesu C wynika z faktu, że kompilatory zostały napisane dla wielu różnych platform, dlatego lepiej byłoby zignorować pewien stopień czytelności, aby ułatwić kompilatorom pisanie.
Powiedziawszy to, nie jestem ekspertem od gramatyki języka ani pisania kompilatorów. Ale wiem wystarczająco dużo, aby wiedzieć, że jest wiele do zrobienia;)
źródło
Wiele osobliwości języka C można wytłumaczyć sposobem działania komputerów podczas jego projektowania. Ilość pamięci była bardzo ograniczona, dlatego bardzo ważne było zminimalizowanie rozmiaru samych plików kodu źródłowego . Praktyka programowania w latach 70. i 80. polegała na upewnieniu się, że kod źródłowy zawiera jak najmniej znaków, a najlepiej, że nie zawiera nadmiernych komentarzy do kodu źródłowego.
Jest to dziś absurdalne, z praktycznie nieograniczoną przestrzenią dyskową na dyskach twardych. Ale jest to część powodu, dla którego C ma tak dziwną składnię w ogóle.
W odniesieniu do wskaźników tablicowych twoim drugim przykładem powinno być
int (*p)[10];
(tak, składnia jest bardzo myląca). Być może przeczytałbym to jako „wskaźnik na tablicę dziesięciu” ... co ma sens. Gdyby nie nawias, kompilator interpretowałby go jako tablicę dziesięciu wskaźników, co nadałoby deklaracji zupełnie inne znaczenie.Ponieważ zarówno wskaźniki tablicowe, jak i funkcyjne mają dość niejasną składnię w C, rozsądnym rozwiązaniem jest wpisanie dziwnego charakteru. Być może tak:
Niejasny przykład:
Niejasny, równoważny przykład:
Sprawy mogą stać się jeszcze bardziej niejasne, jeśli mamy do czynienia z tablicami wskaźników funkcji. Lub najbardziej niejasny ze wszystkich: funkcje zwracające wskaźniki funkcji (mało użyteczne). Jeśli nie używasz typedefs do takich rzeczy, szybko oszalejesz.
źródło
To całkiem proste:
int *p
oznacza, że*p
jest to int;int a[5]
oznacza, żea[i]
jest int.Oznacza, że
*f
jest to funkcja,*a
jest tablicą pięciu liczb całkowitych, podobnief
jak funkcja pobierająca wskaźnik do tablicy pięciu liczb całkowitych i zwracająca liczbę całkowitą. Jednak w C nie jest przydatne przekazywanie wskaźnika do tablicy.Deklaracje C bardzo rzadko komplikują sprawę.
Możesz także wyjaśnić za pomocą typedefs:
źródło
typedef
,const
,volatile
oraz zdolność do zainicjowania rzeczy wewnątrz deklaracji. Wiele irytujących dwuznaczności składni deklaracji (np. Czyint const *p, *q;
powinno się wiązaćconst
z typem, czy z deklaracją) nie mogło powstać w języku pierwotnie zaprojektowanym. Chciałbym, aby język dodał dwukropek między typem a deklaracją, ale zezwolił na jego pominięcie podczas używania wbudowanych typów „słowa zastrzeżonego” bez kwalifikatorów. Znaczenieint: const *p,*q;
iint const *: p,*q;
byłoby jasne.Myślę, że musisz rozważyć * [] jako operatory dołączone do zmiennej. * jest zapisywane przed zmienną, [] po.
Przeczytajmy wyrażenie typu
Najbardziej wewnętrznym elementem jest zatem p, zmienna
oznacza: p jest zmienną.
Przed zmienną występuje *, * operator jest zawsze umieszczany przed wyrażeniem, do którego się odnosi, dlatego
oznacza: zmienna p jest wskaźnikiem. Bez () operator [] po prawej stronie miałby wyższy priorytet, tj
zostanie sparsowany jako
Następnym krokiem jest []: ponieważ nie ma dalszego (), [] ma zatem wyższy priorytet niż zewnętrzny *
oznacza: (zmienna p jest wskaźnikiem) do tablicy. Następnie mamy drugi *:
oznacza: ((zmienna p jest wskaźnikiem) do tablicy) wskaźników
Wreszcie masz operator int (nazwa typu), który ma najniższy priorytet:
oznacza: (((zmienna p jest wskaźnikiem) do tablicy) wskaźników) na liczbę całkowitą.
Cały system oparty jest więc na wyrażeniach typu z operatorami, a każdy operator ma własne reguły pierwszeństwa. Pozwala to zdefiniować bardzo złożone typy.
źródło
Nie jest to takie trudne, kiedy zaczynasz myśleć, a C nigdy nie był łatwym językiem. I
int*[10]* p
naprawdę nie jest łatwiejsze niżint* (*p)[10]
I w jakim typie k byłbyint*[10]* p, k;
źródło
pointer to int
iint
nie są nawet tego samego typu, dlatego należy je zadeklarować osobno. Kropka. Posłuchaj mężczyzny. Nie bez powodu ma 18 tys. Przedstawicieli.