C nie jest takie trudne: void (* (* f []) ()) ()


188

Właśnie widziałem dziś zdjęcie i myślę, że doceniłbym wyjaśnienia. Oto zdjęcie:

jakiś kod c

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:

ciekawe wyjaśnienie

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.


9
Jak wyjaśnienie może być prostsze? Obejmuje wszystkie aspekty definicji fw / kilka słów dla każdego kluczowego punktu.
Scott Hunter,

30
Może C jest trudne? To faktycznie deklaruje fjako tablicę wskaźników do funkcji, które mogłyby przyjąć dowolny argument .. gdyby tak było void (*(*f[])(void))(void);, to tak, byłyby to funkcje, które nie przyjmowałyby żadnych argumentów ...
txtechhelp

18
W praktyce nie koduj tak niejasnego kodu. Użyj typedef do podpisów
Basile Starynkevitch

4
każda deklaracja dotycząca wskaźników funkcji może być trudna. To nie znaczy, że normalne C lub C ++ jest trudne w ten sam sposób. Inne języki rozwiązują to na różne sposoby, w tym brak wskaźników funkcji, co w niektórych przypadkach może być znaczącym pominięciem
Kate Gregory

20
Jeśli zmrużysz oczy, wygląda to na LISP.
user2023861,

Odpowiedzi:


117

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 :

Istnieją trzy proste kroki do naśladowania:

  1. Zaczynając od nieznanego elementu, poruszaj się po spirali / zgodnie z ruchem wskazówek zegara; podczas zliczania następujących elementów zastąp je odpowiednimi angielskimi stwierdzeniami:

    [X]lub []
    => Array X size of ... lub Array undefined size of ...

    (type1, type2)
    => funkcja przekazująca typ 1 i zwracająca typ 2 ...

    *
    => wskaźnik (i) do ...

  2. Rób to dalej w kierunku spiralnym / zgodnie z ruchem wskazówek zegara, aż wszystkie żetony zostaną pokryte.

  3. Zawsze najpierw rozwiązuj wszystko w nawiasach!

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

void (*(*f[])())()

generuje:

zadeklaruj f jako tablicę wskaźnika do funkcji zwracającej wskaźnik do funkcji zwracającej void

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 jak int **(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).


5
Chciałbym, żeby cdecl.org było lepsze
Grady Player

8
Nie ma „reguły spirali” ... ”int *** foo [] [] []” definiuje tablicę tablic tablic wskaźników do wskaźników do wskaźników. „Spirala” pochodzi tylko z faktu, że ta deklaracja zdarzyła się grupować rzeczy w nawiasach w sposób, który spowodował ich naprzemienność. To wszystko po prawej, a potem w lewo, w obrębie każdego zestawu nawiasów.
Random832

1
@ Random832 Istnieje „reguła spiralna”, która obejmuje właśnie wspomniany przypadek, tzn. Mówi o tym, jak radzić sobie z nawiasami / tablicami itp. Oczywiście nie jest to standardowa reguła C, ale dobry mnemonik do wymyślania, jak sobie radzić ze skomplikowanymi deklaracjami. IMHO, jest to niezwykle przydatne i oszczędza, gdy masz kłopoty lub gdy cdecl.org nie może przeanalizować deklaracji. Oczywiście nie należy nadużywać takich deklaracji, ale dobrze jest wiedzieć, w jaki sposób są one analizowane.
vsoftco

5
@vsoftco Ale to nie jest „poruszanie się w spiralę / zgodnie z ruchem wskazówek zegara”, jeśli odwrócisz się tylko po dotarciu do nawiasów.
Random832

2
ouah, powinieneś wspomnieć, że zasada spirali nie jest uniwersalna .
haccks

105

Rodzaj „spirali” wypada z następujących reguł pierwszeństwa:

T *a[]    -- a is an array of pointer to T
T (*a)[]  -- a is a pointer to an array of T
T *f()    -- f is a function returning a pointer to T
T (*f)()  -- f is a pointer to a function returning T

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ę ai fmogą być bardziej skomplikowanymi wyrażeniami niż tylko identyfikatory; in T (*a)[N], amoż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:

         f              -- f
         f[]            -- is an array
        *f[]            -- of pointers  ([] has higher precedence than *)
       (*f[])()         -- to functions
      *(*f[])()         -- returning pointers
     (*(*f[])())()      -- to functions
void (*(*f[])())();     -- returning void

signalFunkcja w bibliotece standardowej jest prawdopodobnie okaz typu dla tego rodzaju szaleństwa:

       signal                                       -- signal
       signal(                          )           -- is a function with parameters
       signal(    sig,                  )           --    sig
       signal(int sig,                  )           --    which is an int and
       signal(int sig,        func      )           --    func
       signal(int sig,       *func      )           --    which is a pointer
       signal(int sig,      (*func)(int))           --    to a function taking an int                                           
       signal(int sig, void (*func)(int))           --    returning void
      *signal(int sig, void (*func)(int))           -- returning a pointer
     (*signal(int sig, void (*func)(int)))(int)     -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int);    -- and returning void

W tym momencie większość ludzi mówi „użyj typedefs”, co z pewnością jest opcją:

typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);

innerfunc *f[N];

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).


7
Dziękujemy za podanie przykładu „sygnału”, pokazującego, że tego rodzaju rzeczy pojawiają się na wolności.
Justsalt,

To świetny przykład.
Casey

Podobało mi się twoje fdrzewo spowalniania, wyjaśniające pierwszeństwo ... z jakiegoś powodu zawsze dostaję kopa w sztuce ASCII, szczególnie jeśli chodzi o wyjaśnianie rzeczy :)
txtechhelp

1
zakładając, że żadna funkcja nie przyjmuje argumentów : wtedy musisz użyć voidw nawiasie funkcji, w przeciwnym razie może przyjąć dowolne argumenty.
haccks

1
@hckck: dla deklaracji tak; Mówiłem o wywołaniu funkcji.
John Bode

57

W C deklaracja odzwierciedla użycie - tak to jest zdefiniowane w standardzie. Deklaracja:

void (*(*f[])())()

Jest twierdzeniem, że wyrażenie (*(*f[i])())()daje wynik typu void. Co znaczy:

  • f musi być tablicą, ponieważ można ją indeksować:

    f[i]
  • Elementy fmuszą być wskaźnikami, ponieważ możesz je wyrejestrować:

    *f[i]
  • Wskaźniki te muszą być wskaźnikami do funkcji bez argumentów, ponieważ można je wywoływać:

    (*f[i])()
  • Wyniki tych funkcji muszą być również wskaźnikami, ponieważ można je wyrejestrować:

    *(*f[i])()
  • Wskaźniki te muszą być również wskaźnikami do funkcji bez argumentów, ponieważ można je wywoływać:

    (*(*f[i])())()
  • Wskaźniki funkcji muszą zwrócić void

„Reguła spirali” jest po prostu mnemonikiem, który zapewnia inny sposób rozumienia tego samego.


3
Świetny sposób patrzenia na to, czego nigdy wcześniej nie widziałem. +1
tbodt

4
Miły. Patrząc w ten sposób, to naprawdę jest proste . Właściwie raczej łatwiej niż coś takiego vector< function<function<void()>()>* > f, zwłaszcza jeśli dodasz std::s. (Ale cóż, ten przykład jest wymyślony ... nawet f :: [IORef (IO (IO ()))]wygląda dziwnie.)
lewo około

1
@TimoDenk: Deklaracja a[x]wskazuje, że wyrażenie a[i]jest ważne, kiedy i >= 0 && i < x. Natomiast a[]pozostawia rozmiar nieokreślony, a zatem jest identyczny z *a: wskazuje, że wyrażenie a[i](lub równoważnie *(a + i)) jest poprawne dla pewnego zakresu i.
Jon Purdy,

4
To zdecydowanie najłatwiejszy sposób na myślenie o typach C, dzięki za to
Alex Ozer

4
Uwielbiam to! O wiele łatwiejsze do uzasadnienia niż głupie spirale. (*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.
Lynn

32

Czy to „duchowe czytanie” jest czymś ważnym?

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ę

int *a[10];

Stosowanie reguły:

int *a[10];      "a is"  
     ^  

int *a[10];      "a is an array"  
      ^^^^ 

int *a[10];      "a is an array of pointers"
    ^

int *a[10];      "a is an array of pointers to `int`".  
^^^      

Rozszyfrujmy złożoną deklarację jak

void ( *(*f[]) () ) ();  

stosując powyższe zasady:

void ( *(*f[]) () ) ();        "f is"  
          ^  

void ( *(*f[]) () ) ();        "f is an array"  
           ^^ 

void ( *(*f[]) () ) ();        "f is an array of pointers" 
         ^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function"   
               ^^     

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer"
       ^   

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function" 
                    ^^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function returning `void`"  
^^^^

Oto GIF pokazujący, jak idziesz (kliknij obraz, aby powiększyć):

wprowadź opis zdjęcia tutaj


Wspomniane tu zasady pochodzą z książki C Programowanie nowoczesnego podejścia autorstwa KN KING .


Jest to podobne do podejścia standardu, tj. „Deklaracja odzwierciedla użycie”. W tym miejscu chciałbym jednak zapytać o coś innego: czy sugerujesz książkę KN Kinga? Widzę wiele fajnych recenzji na temat książki.
Motun

1
Tak. Proponuję tę książkę. Zacząłem programować od tej książki. Dobre teksty i problemy.
haccks

Czy możesz podać przykład niezrozumienia deklaracji przez cdecl? Myślałem, że cdecl używał tych samych reguł parsowania co kompilatory i, o ile wiem, zawsze działa.
Fabio mówi Przywróć Monikę

@FabioTurati; Funkcja nie może zwrócić tablic lub funkcji. char (x())[5]powinien spowodować błąd składni, ale cdecl parsuje go jako: deklaruje xjako funkcję zwracając tablicę 5 zchar .
haccks,

12

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.


Cóż, w „podejściu spiralnym” idziesz tak daleko, jak to możliwe, a potem tak daleko w lewo, jak to możliwe, itp. Ale często jest to błędnie wyjaśnione ...
Lynn

7

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.


3
Niemniej jednak ważne jest, aby wiedzieć, jak to parsować, nawet jeśli tylko wiesz, jak parsować typedef!
inetknght

1
@ inetknght, sposób, w jaki to robisz za pomocą typedefs, polega na tym, aby były one wystarczająco proste, aby nie było potrzeby analizowania.
Siergiej

2
Ludzie, którzy zadają tego typu pytania podczas wywiadów, robią to tylko po to, by pogłaskać swoje ego.
Casey

1
@JohnBode, a zrobiłbyś sobie przysługę, wpisując wartość zwracaną funkcji.
Siergiej

1
@JohnBode, uważam, że to kwestia osobistego wyboru, nie warta debaty. Widzę twoje preferencje, wciąż mam swoje.
Siergiej

7

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


1
To słowo nie wskazuje wewnątrz, jak w zagnieżdżonym przez parens lub w pojedynczym wierszu. Opisuje wzór „węża”, z linią LTR, po której następuje linia RTL.
Potatoswatter

5

Jeśli chodzi o przydatność tego, podczas pracy z kodem powłoki często widzisz tę konstrukcję:

int (*ret)() = (int(*)())code;
ret();

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.


5

Deklaracja

void (*(*f[])())()

to po prostu niejasny sposób powiedzenia

Function f[]

z

typedef void (*ResultFunction)();

typedef ResultFunction (*Function)();

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.


4

Metoda opisana przez Bruce'a Eckela okazała się pomocna i łatwa do naśladowania:

Definiowanie wskaźnika funkcji

Aby zdefiniować wskaźnik do funkcji, która nie ma argumentów ani wartości zwracanej, mówisz:

void (*funcPtr)();

Kiedy patrzysz na taką złożoną definicję, najlepszym sposobem na atak jest rozpoczęcie od środka i wypracowanie wyjścia.„Rozpoczęcie w środku” oznacza rozpoczęcie od nazwy zmiennej, którą jest funcPtr. „Wypracowanie wyjścia” oznacza patrzenie w prawo na najbliższy przedmiot (w tym przypadku nic; prawy nawias nie pozwala się zatrzymać), a następnie patrzenie w lewo (wskaźnik oznaczony gwiazdką), a następnie patrzenie w prawo ( pusta lista argumentów wskazująca funkcję, która nie przyjmuje żadnych argumentów), a następnie patrząc w lewo (void, co oznacza, że ​​funkcja nie ma wartości zwracanej). Ten ruch prawo-lewo-prawo działa z większością deklaracji.

Aby przejrzeć, „zacznij od środka” („funcPtr to ...”), idź w prawo (nic tam nie ma - prawy nawias zatrzyma cię), idź w lewo i znajdź „*” („ ... wskaźnik do ... ”), przejdź w prawo i znajdź pustą listę argumentów („ ... funkcja, która nie przyjmuje argumentów ... ”), przejdź w lewo i znajdź pustkę („ funcPtr jest wskaźnik do funkcji, która nie przyjmuje argumentów i zwraca void ”).

Możesz się zastanawiać, dlaczego * funcPtr wymaga nawiasów. Jeśli ich nie użyjesz, kompilator zobaczy:

void *funcPtr();

Będziesz deklarował funkcję (która zwraca void *) zamiast definiować zmienną. Możesz myśleć o kompilatorze jako przechodzącym przez ten sam proces, który robisz, kiedy odkryjesz, czym powinna być deklaracja lub definicja. Potrzebuje tych nawiasów, aby „zderzyć się z”, więc wraca w lewo i znajduje „*”, zamiast kontynuować w prawo i znajdować pustą listę argumentów.

Skomplikowane deklaracje i definicje

Nawiasem mówiąc, gdy zorientujesz się, jak działa składnia deklaracji C i C ++, możesz utworzyć znacznie bardziej skomplikowane elementy. Na przykład:

//: C03:ComplicatedDefinitions.cpp

/* 1. */     void * (*(*fp1)(int))[10];

/* 2. */     float (*(*fp2)(int,int,float))(int);

/* 3. */     typedef double (*(*(*fp3)())[10])();
             fp3 a;

/* 4. */     int (*(*f4())[10])();


int main() {} ///:~ 

Przejdź każdy z nich i skorzystaj z prowadnicy po prawej i lewej stronie, aby to rozgryźć. Liczba 1 mówi „fp1 jest wskaźnikiem funkcji, która pobiera argument liczby całkowitej i zwraca wskaźnik do tablicy 10 pustych wskaźników”.

Liczba 2 mówi: „fp2 jest wskaźnikiem funkcji, która pobiera trzy argumenty (int, int i float) i zwraca wskaźnik do funkcji, która przyjmuje argument liczby całkowitej i zwraca liczbę zmiennoprzecinkową”.

Jeśli tworzysz wiele skomplikowanych definicji, możesz użyć typedef. Liczba 3 pokazuje, w jaki sposób typedef oszczędza wpisywanie skomplikowanego opisu za każdym razem. Mówi: „Fp3 to wskaźnik do funkcji, która nie przyjmuje argumentów i zwraca wskaźnik do tablicy 10 wskaźników do funkcji, które nie przyjmują argumentów i zwracają podwójną liczbę”. Potem mówi „a jest jednym z tych typów fp3”. Typedef jest ogólnie przydatny do tworzenia skomplikowanych opisów z prostych.

Liczba 4 jest deklaracją funkcji zamiast definicji zmiennej. Mówi: „f4 jest funkcją, która zwraca wskaźnik do tablicy 10 wskaźników do funkcji zwracających liczby całkowite”.

Rzadko będziesz potrzebować tak skomplikowanych deklaracji i definicji jak te. Jeśli jednak przejdziesz ćwiczenie polegające na ich rozszyfrowaniu, nie będziesz nawet lekko zaniepokojony tymi nieco skomplikowanymi, które możesz spotkać w prawdziwym życiu.

Zaczerpnięte z: Myślenie w C ++ Tom 1, drugie wydanie, rozdział 3, sekcja „Adresy funkcji” Bruce'a Eckela.


4

Zapamiętaj te zasady dla deklaracji C
I pierwszeństwo nigdy nie będzie wątpliwości:
zacznij od sufiksu, kontynuuj z prefiksem,
i przeczytaj oba zestawy od wewnątrz, na zewnątrz.
- ja, połowa lat 80

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:

if if if = then then then = else else else = if then ...

2
Instrukcja PL / I była IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIFi jest analizowana jako if (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF).
Cole Johnson

Myślę , że była wersja, która posunęła się o krok dalej, używając warunkowego wyrażenia IF / THEN / ELSE (odpowiednik C? :), który wprowadził trzeci zestaw do miksu ... ale minęło kilka dekad i być może zależało od konkretnego dialektu języka. Chodzi o to, że każdy język ma co najmniej jedną formę patologiczną.
keshlam

4

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:

char *ar[10][10];

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:

char *(ar[10][10]);

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” :)


3
  • unieważnić (*(*f[]) ()) ()

Rozwiązywanie void>>

  • (*(*f[]) ()) () = nieważne

Resoiving ()>>

  • (* (*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 [ ]>>

  • f = tablica (wskaźnik do (funkcja zwracająca (wskaźnik do (funkcja zwracająca (void)))))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.