Jak się tu dostaliśmy
Składnia C do deklarowania punktów funkcyjnych miała na celu odzwierciedlenie użycia. Rozważ taką deklarację funkcji jak ta <math.h>
:
double round(double number);
Aby mieć zmienną punktową, możesz przypisać ją do typu bezpieczeństwa za pomocą
fp = round;
należałoby zadeklarować tę fp
zmienną punktową w ten sposób:
double (*fp)(double number);
Wystarczy więc spojrzeć na sposób użycia funkcji i zastąpić nazwę tej funkcji odwołaniem do wskaźnika, przekształcając się round
w *fp
. Potrzebny jest jednak dodatkowy zestaw parenów, co według niektórych sprawia, że jest nieco bardziej chaotyczny.
Prawdopodobnie było to łatwiejsze w oryginalnym C, który nawet nie miał podpisu funkcji, ale nie wracajmy tam, ok?
Miejsce, w którym staje się szczególnie nieprzyjemne, zastanawia się, jak zadeklarować funkcję, która albo bierze jako argument, albo zwraca wskaźnik do funkcji, albo jedno i drugie.
Jeśli miałeś funkcję:
void myhandler(int signo);
możesz przekazać go do funkcji sygnału (3) w ten sposób:
signal(SIGHUP, myhandler);
lub jeśli chcesz zachować stary moduł obsługi, to
old_handler = signal(SIGHUP, new_handler);
co jest dość łatwe. To, co jest dość łatwe - ani ładne, ani łatwe - to prawidłowe wypełnianie deklaracji.
signal(int signo, ???)
Cóż, po prostu wróć do deklaracji funkcji i zamień nazwę na odniesienie do punktu:
signal(int sendsig, void (*hisfunc)(int gotsig));
Ponieważ nie deklarujesz gotsig
, możesz łatwiej przeczytać, jeśli pominiesz:
signal(int sendsig, void (*hisfunc)(int));
Albo może nie. :(
Tyle że nie jest to wystarczająco dobre, ponieważ signal (3) zwraca również stary moduł obsługi, jak w:
old_handler = signal(SIGHUP, new_handler);
Więc teraz musisz dowiedzieć się, jak je wszystkie zadeklarować.
void (*old_handler)(int gotsig);
wystarczy dla zmiennej, do której zamierzasz przypisać. Pamiętaj, że tak naprawdę nie deklarujesz gotsig
tutaj old_handler
. To naprawdę wystarczy:
void (*old_handler)(int);
To prowadzi nas do poprawnej definicji sygnału (3):
void (*signal(int signo, void (*handler)(int)))(int);
Typedefs na ratunek
Myślę, że do tego czasu wszyscy zgodzą się, że to bałagan. Czasami lepiej nazwać swoje abstrakcje; często naprawdę. Dzięki prawu typedef
staje się to znacznie łatwiejsze do zrozumienia:
typedef void (*sig_t) (int);
Teraz twoja własna zmienna modułu obsługi staje się
sig_t old_handler, new_handler;
a twoja deklaracja sygnału (3) staje się słuszna
sig_t signal(int signo, sig_t handler);
co jest nagle zrozumiałe. Pozbycie się znaku * pozbywa się także mylących nawiasów (i mówią, że pareny zawsze ułatwiają zrozumienie - hah!). Twoje użycie jest nadal takie samo:
old_handler = signal(SIGHUP, new_handler);
ale teraz masz szansę zrozumienia dla deklaracji old_handler
, new_handler
a nawet signal
kiedy po raz pierwszy je lub potrzebę spotkać je pisać.
Wniosek
Okazuje się, że bardzo niewielu programistów C jest w stanie samodzielnie opracować prawidłowe deklaracje dla tych rzeczy bez konsultacji z materiałami referencyjnymi.
Wiem, ponieważ kiedyś mieliśmy to pytanie w naszych rozmowach kwalifikacyjnych dla osób wykonujących pracę jądra i sterownika urządzenia. :) Jasne, w ten sposób straciliśmy wielu kandydatów, którzy rozbili się i spalili na tablicy. Ale unikaliśmy również zatrudniania osób, które twierdziły, że mają wcześniejsze doświadczenie w tej dziedzinie, ale nie mogły właściwie wykonać pracy.
Ze względu na tę powszechną trudność, prawdopodobnie nie jest rozsądne, ale wręcz rozsądne, aby mieć sposób na spełnienie wszystkich tych deklaracji, które nie wymagają już od ciebie bycia programistą potrójnego alfy, siedzącym trzy sigmy powyżej średniej tylko po to, aby tego użyć. coś w rodzaju komfortu.
f :: (Int -> Int -> Int) -> Int -> Int