Właśnie widziałem dziś zdjęcie i myślę, że doceniłbym wyjaśnienia. Oto zdjęcie:
Uznałem to za mylące i zastanawiałem się, czy takie kody są kiedykolwiek praktyczne. Przejrzałem zdjęcie i znalazłem inne zdjęcie w tym wpisie reddit, a oto to zdjęcie:
Czy to „duchowe czytanie” jest czymś ważnym? Czy tak analizują kompilatory C?
Byłoby wspaniale, gdyby istniały prostsze wyjaśnienia tego dziwnego kodu.
Czy wszystkie kody mogą być przydatne? Jeśli tak, to gdzie i kiedy?
Jest pytanie o „regułę spiralną”, ale nie pytam tylko o to, w jaki sposób jest stosowana lub jak wyrażenia są odczytywane z tą regułą. Podważam również użycie takich wyrażeń i zasadność reguły spiralnej. W związku z tym kilka miłych odpowiedzi jest już opublikowanych.
f
jako tablicę wskaźników do funkcji, które mogłyby przyjąć dowolny argument .. gdyby tak byłovoid (*(*f[])(void))(void);
, to tak, byłyby to funkcje, które nie przyjmowałyby żadnych argumentów ...Odpowiedzi:
Istnieje reguła o nazwie „ Reguła zgodna z ruchem wskazówek zegara / reguła spiralna”, która pomaga znaleźć znaczenie złożonej deklaracji.
Od c-faq :
Przykłady można sprawdzić powyżej.
Pamiętaj również, że aby Ci pomóc, istnieje również strona internetowa o nazwie:
http://www.cdecl.org
Możesz wprowadzić deklarację C, która nada jej angielskie znaczenie. Dla
generuje:
EDYTOWAĆ:
Jak wskazano w komentarzach Random832 , reguła spiralna nie odnosi się do tablicy tablic i doprowadzi do błędnego wyniku (większości) tych deklaracji. Na przykład
int **x[1][2];
reguła spiralna ignoruje fakt, który[]
ma wyższy priorytet niż*
.Znajdując się przed tablicą tablic, można najpierw dodać wyraźne nawiasy przed zastosowaniem reguły spirali. Na przykład:
int **x[1][2];
jest taki sam jakint **(x[1][2]);
(również poprawny C) ze względu na pierwszeństwo, a następnie reguła spirali poprawnie odczytuje go jako „x jest tablicą 1 tablicy 2 od wskaźnika do wskaźnika do int”, co jest poprawną angielską deklaracją.Zauważ, że ten problem został również omówiony w tej odpowiedzi przez Jamesa Kanze (wskazanego przez haccks w komentarzach).
źródło
Rodzaj „spirali” wypada z następujących reguł pierwszeństwa:
Operatory indeksu dolnego
[]
i wywołania funkcji()
mają wyższy priorytet niż jedność*
, więc*f()
jest analizowany jako*(f())
i*a[]
analizowany jako*(a[])
.Jeśli więc chcesz wskaźnik do tablicy lub wskaźnik do funkcji, musisz jawnie pogrupować go
*
za pomocą identyfikatora, jak w(*a)[]
lub(*f)()
.Wtedy zdajesz sobie z tego sprawę
a
if
mogą być bardziej skomplikowanymi wyrażeniami niż tylko identyfikatory; inT (*a)[N]
,a
może być prostym identyfikatorem lub może być wywołaniem funkcji takim jak(*f())[N]
(a
->f()
), lub może być tablicą podobną do(*p[M])[N]
, (a
->p[M]
), lub może być tablicą wskaźników do funkcji takich jak(*(*p[M])())[N]
(a
->(*p[M])()
), itp.Byłoby miło, gdyby operator pośredni
*
był postfiksem zamiast jedności, co uczyniłoby deklaracje nieco łatwiejszymi do odczytania od lewej do prawej (void f[]*()*();
zdecydowanie lepiej niżvoid (*(*f[])())()
), ale tak nie jest.Gdy natrafisz na taką owłosioną deklarację, zacznij od znalezienia identyfikatora znajdującego się najdalej z lewej strony i zastosuj powyższe reguły pierwszeństwa, rekurencyjnie stosując je do dowolnych parametrów funkcji:
signal
Funkcja w bibliotece standardowej jest prawdopodobnie okaz typu dla tego rodzaju szaleństwa:W tym momencie większość ludzi mówi „użyj typedefs”, co z pewnością jest opcją:
Ale...
Jak byś użył
f
wyrażenia? Wiesz, że to tablica wskaźników, ale jak go użyć do wykonania poprawnej funkcji? Musisz przejść przez typedefs i rozwikłać poprawną składnię. Natomiast wersja „naga” jest dość efektowna, ale mówi dokładnie, jak używaćf
w wyrażeniu (tzn.(*(*f[i])())();
Zakładając, że żadna funkcja nie przyjmuje argumentów).źródło
f
drzewo spowalniania, wyjaśniające pierwszeństwo ... z jakiegoś powodu zawsze dostaję kopa w sztuce ASCII, szczególnie jeśli chodzi o wyjaśnianie rzeczy :)void
w nawiasie funkcji, w przeciwnym razie może przyjąć dowolne argumenty.W C deklaracja odzwierciedla użycie - tak to jest zdefiniowane w standardzie. Deklaracja:
Jest twierdzeniem, że wyrażenie
(*(*f[i])())()
daje wynik typuvoid
. Co znaczy:f
musi być tablicą, ponieważ można ją indeksować:Elementy
f
muszą być wskaźnikami, ponieważ możesz je wyrejestrować:Wskaźniki te muszą być wskaźnikami do funkcji bez argumentów, ponieważ można je wywoływać:
Wyniki tych funkcji muszą być również wskaźnikami, ponieważ można je wyrejestrować:
Wskaźniki te muszą być również wskaźnikami do funkcji bez argumentów, ponieważ można je wywoływać:
Wskaźniki funkcji muszą zwrócić
void
„Reguła spirali” jest po prostu mnemonikiem, który zapewnia inny sposób rozumienia tego samego.
źródło
vector< function<function<void()>()>* > f
, zwłaszcza jeśli dodaszstd::
s. (Ale cóż, ten przykład jest wymyślony ... nawetf :: [IORef (IO (IO ()))]
wygląda dziwnie.)a[x]
wskazuje, że wyrażeniea[i]
jest ważne, kiedyi >= 0 && i < x
. Natomiasta[]
pozostawia rozmiar nieokreślony, a zatem jest identyczny z*a
: wskazuje, że wyrażeniea[i]
(lub równoważnie*(a + i)
) jest poprawne dla pewnego zakresui
.(*f[])()
jest typem, który można indeksować, a następnie wyrejestrowywać, a następnie wywoływać, więc jest to tablica wskaźników do funkcji.Zastosowanie reguły spirali lub użycie cdecl nie zawsze jest poprawne. Oba zawodzą w niektórych przypadkach. Reguła spiralna działa w wielu przypadkach, ale nie jest uniwersalna .
Aby rozszyfrować złożone deklaracje, pamiętaj o tych dwóch prostych zasadach:
Zawsze czytaj deklaracje od wewnątrz : Zacznij od najbardziej wewnętrznego, jeśli w ogóle, nawiasu. Znajdź deklarowany identyfikator i stamtąd zacznij odszyfrować deklarację.
Gdy jest wybór, zawsze faworyzuj
[]
i()
przeskakuj*
: jeśli*
poprzedza identyfikator i[]
podąża za nim, identyfikator reprezentuje tablicę, a nie wskaźnik. Podobnie, jeśli*
poprzedza identyfikator i()
następuje po nim, identyfikator reprezentuje funkcję, a nie wskaźnik. (Nawiasów zawsze można użyć do zastąpienia zwykłego priorytetu nad[]
i()
ponad*
.)Ta zasada faktycznie obejmuje zygzakowanie z jednej strony identyfikatora na drugą.
Teraz odszyfrowuję prostą deklarację
Stosowanie reguły:
Rozszyfrujmy złożoną deklarację jak
stosując powyższe zasady:
Oto GIF pokazujący, jak idziesz (kliknij obraz, aby powiększyć):
Wspomniane tu zasady pochodzą z książki C Programowanie nowoczesnego podejścia autorstwa KN KING .
źródło
char (x())[5]
powinien spowodować błąd składni, ale cdecl parsuje go jako: deklarujex
jako funkcję zwracając tablicę 5 zchar
.To tylko „spirala”, ponieważ w tej deklaracji znajduje się tylko jeden operator z każdej strony w obrębie każdego poziomu nawiasów. Twierdzenie, że postępujesz „spiralnie”, ogólnie sugerowałoby, że na przemian tablice i wskaźniki znajdują się w deklaracji,
int ***foo[][][]
podczas gdy w rzeczywistości wszystkie poziomy tablic znajdują się przed dowolnym poziomem wskaźnika.źródło
Wątpię, aby takie konstrukcje mogły znaleźć zastosowanie w prawdziwym życiu. Nie cierpię ich nawet jako pytań do wywiadu dla zwykłych programistów (prawdopodobnie OK dla autorów kompilatorów). zamiast tego należy użyć typedefs.
źródło
Jako przypadkowe fakty o ciekawostkach, może być zabawne wiedzieć, że w języku angielskim istnieje prawdziwe słowo opisujące sposób odczytywania deklaracji C: Boustrophedonically , to znaczy na przemian od prawej do lewej i od lewej do prawej.
Odniesienie: Van der Linden, 1994 - strona 76
źródło
Jeśli chodzi o przydatność tego, podczas pracy z kodem powłoki często widzisz tę konstrukcję:
Chociaż nie jest tak skomplikowany pod względem składniowym, ten konkretny wzór pojawia się bardzo często.
Bardziej kompletny przykład w tym pytaniu SO.
Tak więc, chociaż użyteczność na oryginalnym obrazie jest wątpliwa (sugerowałbym, że każdy kod produkcyjny powinien być drastycznie uproszczony), istnieją pewne konstrukcje składniowe, które pojawiają się dość często.
źródło
Deklaracja
to po prostu niejasny sposób powiedzenia
z
W praktyce potrzebne będą bardziej opisowe nazwy zamiast ResultFunction i Function . Jeśli to możliwe, określiłbym również listy parametrów jako
void
.źródło
Metoda opisana przez Bruce'a Eckela okazała się pomocna i łatwa do naśladowania:
Zaczerpnięte z: Myślenie w C ++ Tom 1, drugie wydanie, rozdział 3, sekcja „Adresy funkcji” Bruce'a Eckela.
źródło
Oczywiście z wyjątkiem zmodyfikowanych w nawiasach. I zauważ, że składnia do ich deklarowania dokładnie odzwierciedla składnię do użycia tej zmiennej do uzyskania wystąpienia klasy podstawowej.
Poważnie, nie jest trudno nauczyć się robić na pierwszy rzut oka; musisz po prostu poświęcić trochę czasu na ćwiczenie umiejętności. Jeśli zamierzasz zachować lub dostosować kod C napisany przez innych ludzi, zdecydowanie warto zainwestować ten czas. To także zabawna sztuczka na imprezę, która przeraża innych programistów, którzy się jej nie nauczyli.
W przypadku własnego kodu: jak zawsze fakt, że coś można zapisać jako jednowierszowe, nie oznacza, że tak powinno być, chyba że jest to niezwykle powszechny wzorzec, który stał się standardowym idiomem (np. Pętla ciąg-kopia) . Ty i ci, którzy podążą za tobą, będziesz znacznie szczęśliwszy, jeśli będziesz budować złożone typy z warstwowych typów maszynowych i dereferencji krok po kroku, zamiast polegać na swojej zdolności do generowania i analizowania ich „za jednym razem”. Wydajność będzie równie dobra, a czytelność kodu i łatwość konserwacji będą znacznie lepsze.
Mogło być gorzej, wiesz. Było legalne oświadczenie PL / I, które zaczynało się od czegoś takiego:
źródło
IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIF
i jest analizowana jakoif (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF)
.Zdarzyło mi się być oryginalnym autorem reguły spirali, którą napisałem tak wiele lat temu (kiedy miałem dużo włosów :) i byłem zaszczycony, kiedy został dodany do cfaq.
Napisałem zasadę spirali, aby ułatwić moim studentom i kolegom czytanie deklaracji C „w głowie”; tzn. bez konieczności używania narzędzi programowych, takich jak cdecl.org itp. Nigdy nie miałem zamiaru deklarować, że reguła spiralna jest kanonicznym sposobem analizowania wyrażeń typu C. Jestem jednak zachwycony widząc, że reguła pomogła dosłownie tysiącom studentów i praktyków programowania C na przestrzeni lat!
Dla przypomnienia
Wiele razy „poprawnie” zidentyfikowano na wielu stronach, w tym przez Linusa Torvaldsa (kogoś, kogo bardzo szanuję), że zdarzają się sytuacje, w których moja spirala „załamuje się”. Najczęstszym jest:
Jak zauważyli inni w tym wątku, regułę można zaktualizować, aby powiedzieć, że gdy napotkasz tablice, po prostu zużyj wszystkie indeksy, tak jakby były zapisane:
Teraz, stosując zasadę spirali, otrzymam:
„ar jest dwuwymiarową tablicą wskaźników 10 do char”
Mam nadzieję, że reguła spiralna będzie nadal przydatna w nauce języka C!
PS:
Uwielbiam obraz „C nie jest twardy” :)
źródło
(*(*f[]) ()) ()
Rozwiązywanie
void
>>(*(*f[]) ())
() = nieważneResoiving
()
>>(*f[]) ()
) = funkcja zwracana (void)Rozwiązywanie
*
>>(*f[])
() = wskaźnik do (funkcja zwraca (void))Rozwiązywanie
()
>>f[]
) = funkcja zwraca (wskaźnik do (funkcja zwraca (void)))Rozwiązywanie
*
>>f
[] = wskaźnik do (funkcja zwracająca (wskaźnik do (funkcja zwracająca (void))))Rozwiązywanie
[ ]
>>źródło