Sesja 6 — Funkcje: porządkowanie maszyny wirtualnej
✅ Sprawdź się z sesji 5
Cel
Nauczyć się pisać własne funkcje i użyć ich, żeby uporządkować “maszynę” z sesji 5 — wydzielić wykonywanie pojedynczej instrukcji do osobnej funkcji zamiast trzymać wszystko w main.
Teoria w pigułce
Po co dzielić kod na funkcje?
main() z sesji 5, gdy dodacie więcej operacji, robi się długi i trudny do czytania. Funkcja pozwala nazwać kawałek logiki i używać go wielokrotnie, nie kopiując kodu.
int dodaj(int a, int b) {
return a + b;
}
int main(void) {
int wynik = dodaj(3, 4);
printf("%d\n", wynik); // 7
return 0;
}Anatomia funkcji: int dodaj(int a, int b) — int na początku to typ zwracanej wartości, dodaj to nazwa, (int a, int b) to parametry (dane, które funkcja przyjmuje). return a + b; kończy działanie funkcji i przekazuje wynik z powrotem do miejsca, które ją wywołało.
Funkcje bez zwracanej wartości: void
void przywitaj(char *imie) {
printf("Czesc, %s!\n", imie);
}void oznacza “ta funkcja niczego nie zwraca” — robi coś (tu: wypisuje na ekran), ale nie oddaje wyniku, którego można by użyć w wyrażeniu.
Parametry przekazywane przez wartość — ważna pułapka
void zwieksz(int x) {
x = x + 1;
}
int main(void) {
int a = 5;
zwieksz(a);
printf("%d\n", a); // WCIAZ 5, nie 6!
}Funkcja zwieksz dostaje kopię wartości a, nie samą zmienną a. Zmiana x wewnątrz funkcji nie wpływa na oryginał. To zaskakuje niemal każdego na starcie — i jest bezpośrednim powodem, dla którego istnieją wskaźniki (sesja 11): to jedyny sposób, żeby funkcja mogła zmienić coś w miejscu, skąd została wywołana.
Zadania
Zadanie 1 — wydziel execute_instruction
Weź kod z sesji 5 (zadanie 3) i wydziel ciało switcha do osobnej funkcji:
int execute_instruction(int akumulator, int kod, int wartosc) {
switch (kod) {
case 1: return wartosc;
case 2: return akumulator + wartosc;
case 3: printf("Wynik: %d\n", akumulator); return akumulator;
case 4: return akumulator - wartosc;
case 5: return akumulator * wartosc;
default:
printf("Nieznana instrukcja: %d\n", kod);
return akumulator;
}
}W main, pętla for powinna teraz wyglądać tak:
for (int i = 0; i < liczba_instrukcji * 2; i += 2) {
akumulator = execute_instruction(akumulator, program[i], program[i + 1]);
}Zwróć uwagę: main jest teraz krótszy i czytelniejszy — mówi co się dzieje (dla każdej instrukcji, wykonaj ją), a szczegóły jak są schowane w funkcji.
Zadanie 2 — funkcja pomocnicza do wypisywania
Napisz void wypisz_program(int program[], int liczba_instrukcji), która przed wykonaniem wypisuje cały “program” w czytelnej formie (kod: X, wartosc: Y dla każdej instrukcji). Wywołaj ją w main przed pętlą wykonującą.
Zwróć uwagę na parametr int program[] — tablice, w odróżnieniu od zwykłych zmiennych z zadania o zwieksz, nie są kopiowane przy przekazaniu do funkcji (do tego wrócimy dokładniej w sesji o wskaźnikach) — funkcja operuje na tej samej tablicy, którą ma main.
Zadanie 3 — funkcja z wieloma wywołaniami
Napisz int silnia(int n), liczącą silnię (n!) przez pętlę for wewnątrz funkcji (bez rekurencji — to temat na później). Przetestuj dla kilku wartości n, w tym 0 (wynik powinien być 1).
Kryterium sukcesu
execute_instructiondziała identycznie jakswitchbezpośrednio wmainz sesji 5 — ten sam wynik, inna organizacja kodu.- Potrafisz wytłumaczyć, dlaczego zmiana parametru
int xwewnątrz funkcji nie zmienia zmiennej wmain, z której go wywołano. silnia(0)zwraca1,silnia(5)zwraca120.