Kurs: Kurs wstępu do programowania KONKURS

Lekcja: Funkcje

Część programistyczna: Funkcje

W tej lekcji wprowadzamy kolejny nowy element języka C++: funkcje. Funkcje pomagają nadawać programom dobrą strukturę, a ponadto pozwalają na wielokrotne wykorzystywanie tego samego kodu w różnych miejscach tego samego programu, a także później w innych programach.

Przykłady 1-4: Podstawowa wiedza o funkcjach w C++


Zobacz tekst nagrania

Przykłady 5-7: Więcej o parametrach i wynikach funkcji


Zobacz tekst nagrania

Uwaga: Wielu funkcji nie trzeba za każdym razem pisać w swoich programach, gdyż są one udostępnione w języku C++. Aby użyć funkcji min albo swap, wystarczy dołożyć na początku programu deklarację:

#include <algorithm>
Aby użyć funkcji abs, obliczającej wartość bezwzględną z liczby, trzeba przy początku programu umieścić:

#include <cstdlib>
Ważną rzeczą dla programisty jest znajomość podstawowych funkcji dostępnych w języku programowania, którego używa. Nie musi on ich pamiętać dokładnie, ale dobrze, by choć z grubsza pamiętał, że coś takiego było. Wtedy może zawsze znaleźć informacje na temat konkretnej funkcji w Internecie.

Zasięg zmiennej, zmienne lokalne i globalne

W jednym programie może występować wiele różnych funkcji. Przykład poniżej.

#include <iostream>
using namespace std;

int suma(int t[], int n) {
    int wyn = 0;
    for (int i = 0; i < n; i++)
        wyn += t[i];
    return wyn;
}

int min(int t[], int n) {
    int wyn = t[0];
    for (int i = 1; i < n; i++)
        if (t[i] < wyn)
            wyn = t[i];
    return wyn;
}

int main() {
    int n;
    cin >> n;
    int t[n];
    for (int i = 0; i < n; i++)
        cin >> t[i];
    cout << suma(t, n) << endl
         << min(t, n)  << endl;
}
Gdzieś na początku kursu mówiliśmy, że zmienną o danej nazwie można zadeklarować w programie co najwyżej raz. Jednak wyjątkiem od tej zasady jest to, gdy zmienne mają różny zasięg. Zasięgiem zmiennej nazywamy fragment programu między dwoma nawiasami klamrowymi, w którym ta zmienna jest zadeklarowana. Nie może być dwóch zmiennych o tej samej nazwie, np. \(i\), zadeklarowanych w tym samym zasięgu. W skrajnym przypadku druga zmienna może być położona w zasięgu bardziej wewnętrznym od pierwszej, jednak jest to bardzo niewskazane ze względu na możliwość pomyłki (choć program skompiluje się poprawnie).

Możemy jednak zupełnie nieszkodliwie inną zmienną o takiej samej nazwie umieścić w innym miejscu programu. Przykład mamy powyżej: zmienna \(wyn\) jest deklarowana dwukrotnie, a zmienna \(i\), identyfikator tablicy \(t\) oraz zmienna/parametr \(n\) są deklarowane aż trzykrotnie, a mimo wszystko program kompiluje się i działa poprawnie! Innymi słowy, wszystkie te zmienne, mimo tych samych nazw, są różne. Nazwy funkcji mogą się powtarzać, ale tylko wtedy, gdy funkcje mają istotnie różne nagłówki (np. min z dwóch liczb i min w tablicy).

Zmienne, które są zadeklarowane w ramach jakiejś funkcji, nazywamy zmiennymi lokalnymi. Ich przeciwieństwem są zmienne globalne, które są dostępne dla wielu funkcji. Wszystkie dotychczas występujące zmienne w kursie były zmiennymi lokalnymi. Zmienne globalne deklaruje się na zewnątrz wszystkich funkcji. Zmiennej globalnej można używać we wszystkich funkcjach umieszczonych po miejscu jej deklaracji.

Zmienne globalne mają oczywiste zalety i – być może mniej oczywiste, ale jednak poważne – wady. Pierwszą zaletą jest to, że danej zmiennej nie trzeba wielokrotnie deklarować i można jej używać w ramach wielu funkcji. Tak więc jeśli kilka funkcji używa tych samych danych, można je umieścić jako zmienne globalne i wtedy nie trzeba ich ciągle przekazywać jako argumenty funkcji. Drugą zaletą jest to, że zmienne globalne są automatycznie wyzerowane w C++, więc nie trzeba ich inicjować! Podstawową wadą zmiennych globalnych jest to, że czynią program mniej czytelnym i zwiększają ryzyko popełnienia błędu. Łatwo przy pisaniu jednej funkcji zapomnieć, że dana zmienna jest także modyfikowana w innej funkcji. Poza tym cała idea funkcji opiera się na tym, żeby podzielić program na niezależne fragmenty, które w idealnej sytuacji można także wykorzystać w innych programach. Stosowanie zmiennych globalnych istotnie zmniejsza szansę na takie ponowne wykorzystanie kodu.

Czasem jednak można zdecydować się na wykorzystanie zmiennych globalnych. Najczęstszy bodaj przykład to umieszczenie w zmiennych globalnych danych wejściowych i dużych struktur danych (np. tablic) używanych w wielu funkcjach. Pozwala to uniknąć ciągłego przekazywania parametrów między funkcjami. Tablicę będącą zmienną globalną trzeba deklarować ze stałym rozmiarem. Dobrze jest wtedy umieścić ten rozmiar jako stałą (słówko const), wtedy w przypadku użycia np. wielu tablic o tym samym rozmiarze zmniejszamy ryzyko błędu wpisania o jedno zero za mało. Zgodnie z powszechną konwencją nazwy stałych pisze się całe wielkimi literami. Przykład tego wszystkiego znajduje się poniżej.

#include <iostream>
using namespace std;

const int MAX_N = 1000000;

int t[MAX_N];
int n;

int suma() {
    int wyn = 0;
    for (int i = 0; i < n; i++)
        wyn += t[i];
    return wyn;
}

int min() {
    int wyn = t[0];
    for (int i = 1; i < n; i++)
        if (t[i] < wyn)
            wyn = t[i];
    return wyn;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> t[i];
    cout << suma() << endl
         << min()  << endl;
}

Komentarz: Najstarszy algorytm świata

Jednym z najstarszych, nietrywialnych algorytmów używanych do dzisiaj jest algorytm Euklidesa, służący do obliczania największego wspólnego dzielnika dwóch dodatnich liczb całkowitych. Największym wspólnym dzielnikiem dodatnich liczb \(a\), \(b\) nazywamy największą liczbę całkowitą \(d\), która dzieli bez reszty zarówno liczbę \(a\), jak i liczbę \(b\). Największy wspólny dzielnik liczb \(a\), \(b\) będziemy oznaczali przez \(NWD(a,b)\).

Przykłady
\(NWD(5,20) = 5\), \(NWD(42,24) = 6\), \(NWD(1,30) = 1\), \(NWD(33,20) = 1\).

Liczby, których największy wspólny dzielnik wynosi 1, nazywamy względnie pierwszymi.

Naszym celem będzie teraz napisanie funkcji, która dla dodatnich i całkowitych parametrów \(a\), \(b\) oblicza ich największy wspólny dzielnik.


Zobacz tekst nagrania

Część techniczna: C++ czy ++C, czyli sztuczki programistyczne

W tej części technicznej opiszemy zagadnienie, którego znajomość nie jest konieczna przy pisaniu programów, jednak poszerza naszą wiedzę o języku C++. Główna część tej lekcji była z konieczności dosyć obszerna, więc jeśli jesteś już zmęczony, nie musisz się w nią teraz zbyt dokładnie wczytywać.

Rozwiń część techniczną

Zadania

Oto kolejna partia zadań do samodzielnego rozwiązania. W naszym kursie Wstępu do programowania zaszliśmy już naprawdę daleko. Świadczyć może o tym fakt, że zadanie z gwiazdką wybrane do tej lekcji jest takie samo jak jedno z zadań w równolegle trwającym w MAIN 2 kursie podstaw algorytmiki!