Po co funkcje
Wszystko do tej pory mieszkało w main. To działa, dopóki program robi jedną rzecz, a potem pojawiają się naraz trzy problemy: ten sam blok kodu trafia w trzy miejsca, main rośnie ponad to, co da się ogarnąć w głowie, i nie ma jak nazwać, do czego służy dany kawałek kodu.
Funkcja rozwiązuje wszystkie trzy. Nadaje blokowi kodu nazwę, własne zmienne lokalne, listę wejść i jedną wartość do oddania.
square jest napisana raz i wywołana trzy razy, w tym raz na własnym wyniku. Funkcje wywołujesz już od dawna: printf jest funkcją, main też.
Budowa funkcji
returnType name(parameterList) {
// ciało
return value;
}
Cztery części:
- Typ zwracany: typ wartości, którą funkcja oddaje.
int,double,char, typ wskaźnikowy albovoiddla "niczego". - Nazwa: podlega tym samym regułom co nazwa zmiennej i powinna mówić, co funkcja robi albo wytwarza.
- Lista parametrów: wejścia, każde z własnym typem, oddzielone przecinkami.
(void)oznacza, że funkcja nic nie przyjmuje. - Ciało: instrukcje w nawiasach klamrowych, zakończone
return, chyba że typ zwracany tovoid.
Zauważ, że pi() nadal potrzebuje pustych nawiasów w miejscu wywołania: to one sprawiają, że jest to wywołanie, a nie samo wspomnienie nazwy. A printBanner(20); to osobna instrukcja, bo nie daje żadnej wartości do użycia.
Dla funkcji bez parametrów pisz (void), a nie (). W C te zapisy znaczą co innego, a () jest luźniejszy; więcej o tym za chwilę.
Wywołanie i powrót
Wywołanie przenosi sterowanie do funkcji, wykonuje jej ciało i wraca z wartością, która zastępuje wywołanie w otaczającym wyrażeniu.
return robi naraz dwie rzeczy: dostarcza wartość i od razu kończy funkcję. Nic, co stoi po return na tej samej ścieżce, się nie wykona, i dlatego styl wczesnego powrotu w larger działa: else nie jest potrzebne, bo dojście do ostatniej linii już oznacza, że a > b był fałszem.
Funkcja może mieć kilka instrukcji return. W jednym wywołaniu wykona tylko jedną z nich.
Funkcje void
Funkcja, która istnieje dla swojego efektu, a nie wartości, ma typ zwracany void: wypisywanie, zmiana czegoś przez wskaźnik, rysowanie.
Wewnątrz funkcji void samo return; kończy ją wcześniej. return someValue; jest tam błędem kompilacji, podobnie jak użycie wywołania w wyrażeniu: int x = printTable(7, 5); się nie skompiluje, i to system typów robi swoje.
Podział na funkcje: o to naprawdę chodzi
Funkcje nie służą głównie unikaniu powtórzeń. Służą zamianie jednej długiej procedury na kilka nazwanych kroków, tak żeby main czytało się jak opis programu, a nie jego implementacja.
main to teraz cztery linie wyjścia i jedno obliczenie. Każda funkcja pomocnicza robi jedną rzecz, ma nazwę, która mówi, co to za rzecz, i można ją przetestować osobno. Zwróć uwagę, że długość tablicy wędruje jako drugi parametr: po przekazaniu tablicy do funkcji sztuczka z sizeof już tam nie działa, bo tablica degraduje się do wskaźnika.
Trzy praktyczne zasady podziału pracy na funkcje:
- Jedno zadanie na funkcję. Jeśli nazwa potrzebuje "i", to pewnie są dwie funkcje.
- Bierz to, czego potrzebujesz, zwracaj to, co wytwarzasz. Funkcja, która czyta stan globalny, jest trudniejsza do zrozumienia i nie da się jej przetestować w izolacji.
- Na tyle krótka, żeby objąć ją wzrokiem. Nie ma magicznej liczby linii, ale funkcja, która nie mieści się na ekranie, zwykle ukrywa w sobie mniejszą funkcję.
Funkcje wywołujące funkcje
Funkcja może wywołać każdą funkcję, która została już zadeklarowana, w tym te napisane przez ciebie:
main wywołuje report, która wywołuje sumEvens, która w pętli wywołuje isEven. Każde wywołanie dostaje własny, świeży zestaw zmiennych lokalnych, ułożony na stosie nad zmiennymi wywołującego, i porzucany po powrocie.
Definicje powyżej stoją w kolejności zależności, isEven przed sumEvens przed report, bo C wymaga, żeby funkcja była zadeklarowana przed wywołaniem. Wywołaj funkcję, która pojawia się niżej w pliku, a kompilator zgłosi niejawną deklarację. Ogólnym rozwiązaniem jest prototyp na początku pliku, który pozwala ułożyć definicje w dowolnej kolejności. Funkcja wywołująca samą siebie też jest poprawna i przydatna; to rekurencja.
main też jest funkcją
main to zwykła funkcja, którą środowisko uruchomieniowe wywołuje za ciebie. Jej typ zwracany int to kod wyjścia programu: 0 oznacza sukces, wartość niezerowa porażkę, a odczytują go skrypty powłoki i narzędzia budowania.
int main(void) {
/* ... */
return 0;
}
Od C99 dojście do końca main bez return niejawnie zwraca 0. To specjalna reguła, która dotyczy tylko main. Każda inna funkcja nie-void, która kończy się bez zwrócenia wartości, daje niezdefiniowane zachowanie, gdy wywołujący użyje wyniku.
Druga standardowa forma to int main(int argc, char *argv[]), która przyjmuje argumenty wiersza poleceń. Obie są poprawne; void main() nie jest, cokolwiek mówią stare tutoriale.
Częste błędy
- Brak
returnw funkcji nie-void. Wywołujący odczyta wtedy śmieciową wartość.gcc -Wallostrzega. - Deklarowanie w ciele zmiennej o tej samej nazwie co parametr. Przesłania ona parametr, a przypisanie, które miało trafić do wejścia, trafia donikąd.
- Oczekiwanie, że funkcja zmieni swoje argumenty. C przekazuje wszystko przez wartość, więc funkcja dostaje kopie.
void reset(int x) { x = 0; }niczego nie zmienia w miejscu wywołania; to temat następnej strony. - Pominięcie nawiasów w wywołaniu.
printBanner;to poprawne wyrażenie, które oblicza adres funkcji i go porzuca. Kompiluje się, nic nie robi, a-Wallto zgłasza. - Pisanie
()zamiast(void). Poprawne, ale w starszych dialektach C wyłącza sprawdzanie argumentów.
Najczęściej zadawane pytania
Jak zadeklarować funkcję w C?
Napisz typ zwracany, nazwę i listę parametrów w nawiasach, a potem ciało w nawiasach klamrowych: int add(int a, int b) { return a + b; }. Funkcja, która nic nie przyjmuje, używa (void), a taka, która nic nie zwraca, ma typ zwracany void.
Co robi return w funkcji w C?
Natychmiast kończy funkcję i oddaje wartość wywołującemu. Typ tej wartości musi pasować do zadeklarowanego typu zwracanego funkcji. W funkcji void możesz napisać samo return;, żeby wyjść wcześniej, albo je pominąć i pozwolić funkcji skończyć się na zamykającym nawiasie klamrowym.
Dlaczego main w C zwraca int?
Wartość zwracana przez main to kod wyjścia programu, który może odczytać system operacyjny i skrypty powłoki. return 0; oznacza sukces, a każda wartość niezerowa oznacza porażkę. Od C99 dojście do końca main bez instrukcji return niejawnie zwraca 0.
Czy funkcja w C może zwrócić więcej niż jedną wartość?
Nie bezpośrednio: return zwraca dokładnie jedną wartość. Typowe obejścia to zwrócenie struktury z kilkoma polami albo przekazanie wskaźników jako parametrów, przez które funkcja zapisze dodatkowe wyniki.