cześć, piszę mały tutorial na temat funkcji w C dla znajomych z polibudy. czy powinienem zamieścić jeszcze coś, żeby pomóc im zrozumieć funkcje?
Podstawy programowania
Programowanie to pisanie instrukcji dla procesorów. Procesory wykonują instrukcje linijka po linijce, z góry do dołu.
Wyrażenia
Wyrażenia w językach programowania to wszystko co zwraca wartość. Mogą to być wyrażenia matematyczne, lub wywołania funkcji. Funkcje, które zwracają void
też są wyrażeniami, ale używanie wartości wyrażenia ich wywołania jest błędem i uniemożliwi kompilacje programu w językach C i pochodnych.
Funkcje (podejście matematyczne)
Żeby zrozumieć funkcje trzeba podejść do nich z dwóch stron: należy zrozumieć matematyczne pojęcie funkcji oraz sposób, w jaki wykonywane są przez procesor komputera.
Ze strony matematycznej funkcja to przyporządkowanie pewnym argumentom wartości. Wywołując funkcję z pewnymi argumentami otrzymamy pewną wartość. W matematyce wywoływanie funkcji z danymi argumentami dowolną ilość razy zawsze da ten sam wynik. Funkcje, którę będziemy pisać nie są matematyczne i nie obowiązuje ich ta zasada. Funkcje, które mimo tego trzymają się tej reguły nazywamy czystymi.
Wyrażenie wywołania czystej funkcji jest równe wynikowi tej funkcji, czyli wyrażenie 2 + 2;
jest równe wyrażeniu dodaj(2, 2);
i jest równe wyrażeniu 4;
. Przy definicji funkcji dodaj:
int dodaj(int a, int b) { return a + b; }
// funkcja czysta bo do ustalenia wyniku korzysta tylko z argumentów
Czyste funkcje pozwalają nie myśleć o sobie jak o podprogramie, który wykonuje polecenia, które trzeba znać bo mogą mieć konsekwencje w dalszym działaniu programu. Dzięki temu, że funkcja ta nie może mieć wpływu na stan programu można myśleć o jej wywołaniu jak o statycznym wyrażeniu. W matematyce, mówiąc o sinusie z 30 stopni nie myślimy o procedurze, jaką należałoby przeprowadzić, tylko o wyniku - liczbie 0,5. Dlatego, mówiąc o wyrażeniu dodaj(2, 2)
nie należy myśleć o procedurze dodawania wykonywanej podczas wywołania funkcji, lecz o jej wyniku jak o wartości stałej - liczbie 4. Matematyczne wywołanie funkcji to jak przeczytanie wyniku dla odpowiednich argumentów z wydrukowanej tabeli. Wynik będzie zawsze taki sam dla takich samych argumentów i przeczytanie go z tabeli nie będzie miało żadnych skutków ubocznych jak zmiana jakiejś innej wartości w tabeli.
Stos
Zrozumienie funkcji od strony sprzętowej wymaga rozumienia pojęcia stosu. Stos jako struktura danych pozwala na dodawanie danych i zdejmowanie ich. Ważna jest kolejność zdejmowania. Zdjąć ze stosu można tylko dane umieszczone na nim najpóźniej (znajdujące się na jego szczycie). Żeby usunąć podstawę stosu trzeba po kolei zdejmować wszystkie wartości umieszczone na stosie po umieszczeniu podstawy.
Sprzętowo, stos to pewna ilość zaalokowanej pamięci RAM. Każdy proces uruchomiony w komputerze dostaje swój stos. W procesorze komputera znajduje się rejestr przechowywujący liczbę całkowitą - adres bitu pamięci ram, który jest w danym momencie szczytem stosu; nazywa się go wskaźnikiem stosu.
Wywołanie funkcji (sprzętowo)
Sprzętowa implementacja funkcji jest mniej abstrakcyjna i można podzielić ją na kilka etapów
-
Przesunięcie wskaźnika stosu, tak aby zmieściły sie pod nim wartości wszystkich argumentów funkcji i jej zmiennych lokalnych
-
Wykonanie operacji z definicji funkcji
-
Opcjonalne przepisanie wyrażenia po słowie kluczowym
return
w odpowiednie miejsce w pamięci -
Przesunięcie wskaźnika stosu w miejsce, w którym był przed wykonaniem kroku 1.
Zasięg zmiennych
W językach programowania zasięg zmiennych to części kodu, w których można korzystać z określonych zmiennych. Zmienne globalne mają zasięg w każdym miejscu programu. Zmienne lokalne są w zasięgu tylko w bloku kodu, w którym zostały zdefiniowane. W języku C++ bloki kodu są określone klamrami. Przy rozpoczęciu bloku kodu na stos zostaje włożony nowy segment, który będzie przechowywał zmienne w tym bloku. Po wyjściu programu z bloku kodu, wskaźnik stosu zostaje cofnięty co powoduje usunięcie wszystkich zmiennych w opuszczonym bloku.
Zmienne zdefiniowane w bloku kodu znajdującym się w innym bloku mają dostęp do zmiennych ze wszystkich nadrzędnych bloków.
int a;
void func() {
int b;
{
int c;
{
int d;
cout << a + b + c + d << endl; // Prawidłowe wyrażenie
} // <- tu zmienna d zostaje skasowana
} // <- tu zmienna c zostaje skasowana
} // <- tu zmienna b zostaje skasowana