« Funkcje kolejne aspekty, lekcja »
Przekazywanie struktur do funkcji oraz funkcji przez wskaźnik. Ponadto omówiono zagadnienie rekurencji. (lekcja)
Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Zarejestruj się!
Autor: 'Dante'
Kurs C++

Funkcje kolejne aspekty

[lekcja] Przekazywanie struktur do funkcji oraz funkcji przez wskaźnik. Ponadto omówiono zagadnienie rekurencji.

Funkcje z strukturami

Zobaczmy jak za pomocą funkcji przekazać struktury, jest to proces znacznie prostszy niż wcześniej prezentowane przekazywanie tabel.
C/C++
// Funkcje i struktury-------
#include <iostream>
#include <cmath> //nowa biblioteka
//definicja struktur ---------
struct BokiTrojkata
{
    double bokA;
    double bokB;
    double bokC;
    bool czy_prawda;
};
struct WynikiTrojkata
{
    double Wynik1;
    double Wynik2;
    double Wynik3;
    bool czy_prawda;
};
//definicje funkcji-----------
WynikiTrojkata TwierdzeniePitagorasa( BokiTrojkata pobiezboki );
void WyswietlWyniki( const WynikiTrojkata wynik );
//funkcja główna -------------
int main()
{
    using std::cout;
    using std::cin;
    //tworzenie obiektów struktur
    BokiTrojkata wprowadzDane;
    WynikiTrojkata wyswietl;
   
    cout << "Wprowadz dana bokow a, b i c: ";
    //sprytny sposób wprowadzania danych
    while( cin >> wprowadzDane.bokA >> wprowadzDane.bokB >> wprowadzDane.bokC )
    {
        wyswietl = TwierdzeniePitagorasa( wprowadzDane );
        WyswietlWyniki( wyswietl );
        cout << "\nPodaj ponownie boki lub 'k' by wyjsc.\n";
    }
    return 0;
}
//funkcja obliczająca kwadrat poszczególnych boków A, B, C
WynikiTrojkata TwierdzeniePitagorasa( BokiTrojkata pobierzboki )
{
    // pow służy do potęgowania liczb
    using std::pow;
    //tworzy strukturę by móc ją zwrócić przez return
    WynikiTrojkata odpowiedz;
   
    if(( pow( pobierzboki.bokA, 2 ) + pow( pobierzboki.bokB, 2 ) ) == pow( pobierzboki.bokC, 2 ) )
    {
        odpowiedz.Wynik1 =( pow( pobierzboki.bokA, 2 ) );
        odpowiedz.Wynik2 =( pow( pobierzboki.bokB, 2 ) );
        odpowiedz.Wynik3 =( pow( pobierzboki.bokC, 2 ) );
        odpowiedz.czy_prawda = true;
    } else {
        odpowiedz.czy_prawda = false;
        /*   odpowiedz.Wynik1 = (pow(pobierzboki.bokA, 2));
                odpowiedz.Wynik2 = (pow(pobierzboki.bokB, 2));
                odpowiedz.Wynik3 = (pow(pobierzboki.bokC, 2));*/
    }
    return odpowiedz; //zwraca strukturę
}
void WyswietlWyniki( const WynikiTrojkata wynik )
{
    using std::cout;
   
    if( wynik.czy_prawda )
    {
        cout << "\nTo sa boki trojkata, a dodatkowo trojkata prostokatnego!\n"
        << "Udalo sie to ustalic dzieki twierdzeniu pitagorasa.\n"
        << "Bok a * a = " << wynik.Wynik1
        << "\tBok b * b = " << wynik.Wynik2
        << "\tBok c * c = " << wynik.Wynik3
        << "\nTwierdzenie pitagorasa to: (a*a) + (b*b) = (c*c)\n"
        << "\n";
    } else
         cout << "\nPodane boki nie tworza trojkata prostokatnego(lub zadnego innego)\n\n";
   
}
Mamy tutaj dwie funkcje, pierwsza WynikiTrojkata TwierdzeniePitagorasa(BokiTrojkata pobiezboki); pobiera strukturę BokiTrojkata, a zwraca drugą strukturę TwierdzeniePitagorasa. By można był tego dokonać w ciele funkcji musimy utworzyć obiekt zwracanej struktury(czyli WynikiTrojkata odpowiedz;). W ten sposób możemy to co funkcja zwraca przypisać do innego obiektu struktury WynikiTrojkata(czyli wyswietl).
Do obliczeń użyliśmy pow z biblioteki cmath(z C math.h), pobiera on dwie liczby zmiennoprzecinkowe. Składnia to pow(x, y) co czytamy x do potęgi y, tj. xy. Na koniec używamy funkcji do wyświetlenia wyników, która powinna być czytelna dla Ciebie voidWyswietlWyniki(const WynikiTrojkata wynik);.
Ciekawym elementem jest zastosowanie cin w pętli while (cin >> wprowadzDane.bokA >> wprowadzDane.bokB >> wprowadzDane.bokC).Gdy podamy dane w odpowiedniej kolejności jak na zdjęciu i potwierdzimy Enterem.[center]
[/center]
To strumień przekaże, każdą z liczb do odpowiedniej zmiennej, zgodniej z kolejnością zawartą w kodzie. Oczywiście dane muszą być oddzielone spacją od siebie. Przedstawione liczby spełniają twierdzenie Pitagorasa, które zostało zastosowane w przykładzie.

Funkcje z strukturami II

Zobaczmy jeszcze raz ten sam przykład, jednak wykorzystujący inne rozwiązanie w pracy ze strukturami w funkcjach.
C/C++
// Funkcje i struktury-------
#include <iostream>
#include <cmath> //nowa biblioteka
//definicja struktur ---------
struct BokiTrojkata
{
    double bokA;
    double bokB;
    double bokC;
    bool czy_prawda;
};
struct WynikiTrojkata
{
    double Wynik1;
    double Wynik2;
    double Wynik3;
    bool czy_prawda;
};
//definicje funkcji-----------
void TwierdzeniePitagorasa( const BokiTrojkata * pobiezboki, WynikiTrojkata * wynik );
void WyswietlWyniki( const WynikiTrojkata * wynik );
//funkcja główna -------------
int main()
{
    using std::cout;
    using std::cin;
    //tworzenie obiektów struktur
    BokiTrojkata wprowadzDane;
    WynikiTrojkata wyswietl;
   
    cout << "Wprowadz dana bokow a, b i c: ";
    //sprytny sposób wprowadzania danych
    while( cin >> wprowadzDane.bokA >> wprowadzDane.bokB >> wprowadzDane.bokC )
    {
        TwierdzeniePitagorasa( & wprowadzDane, & wyswietl );
        WyswietlWyniki( & wyswietl );
        cout << "\nPodaj ponownie boki lub 'k' by wyjsc.\n";
    }
    return 0;
}
//funkcja obliczająca kwadrat poszczególnych boków A, B, C
void TwierdzeniePitagorasa( const BokiTrojkata * wprowadzDane, WynikiTrojkata * wynik )
{
    // pow służy do potęgowania liczb
    using std::pow;
   
    if(( pow( wprowadzDane->bokA, 2 ) + pow( wprowadzDane->bokB, 2 ) ) == pow( wprowadzDane->bokC, 2 ) )
    {
        wynik->Wynik1 =( pow( wprowadzDane->bokA, 2 ) );
        wynik->Wynik2 =( pow( wprowadzDane->bokB, 2 ) );
        wynik->Wynik3 =( pow( wprowadzDane->bokC, 2 ) );
        wynik->czy_prawda = true;
    } else
         wynik->czy_prawda = false;
   
}
void WyswietlWyniki( const WynikiTrojkata * wynik )
{
    using std::cout;
   
    if( wynik->czy_prawda )
    {
        cout << "\nTo sa boki trojkata, a dodatkowo trojkata prostokatnego!\n"
        << "Udalo sie to ustalic dzieki twierdzeniu pitagorasa.\n"
        << "Bok a * a = " << wynik->Wynik1
        << "\tBok b * b = " << wynik->Wynik2
        << "\tBok c * c = " << wynik->Wynik3
        << "\nTwierdzenie pitagorasa to: (a*a) + (b*b) = (c*c)\n"
        << "\n";
    } else
         cout << "\nPodane boki nie tworza trojkata prostokatnego(lub zadnego innego)\n\n";
   
}
Pierwszą różnica między tymi programami jest to, iż w pierwszym operowaliśmy na strukturach, a w tym operujemy na adresach struktur i znów z pomocą przychodzi wskaźnik. Dzięki takiemu postępowaniu uzyskujemy mniejsze zużycie pamięci. Ponieważ nie musimy tworzyć dodatkowych obiektów, by poprawnie obsługiwać struktury. Pracujemy tylko na dwóch obiektach(zamiast 5). Program wykonuje dokładnie to samo, dodatkową różnicą jest sposób operowania z danymi. Ponieważ pracujemy na wskaźnikach używamy innej formy zapisu przy dostępie do danych obiektu struktury. Zamiast wynik.Wynik1(kropka oznacza operator bezpośredni), używamy wynik->Wynik1(strzałka to tzw. operator pośredni).
Ostatnią rzeczą jaką musieliśmy zmodyfikować to funkcję zwracającą WynikiTrojkata TwierdzeniePitagorasa(BokiTrojkata pobierzboki) zmodyfikowaliśmy na void TwierdzeniePitagorasa(const BokiTrojkata *wprowadzDane, WynikiTrojkata *wynik). Funkcja jednak działa tak samo, z tą różnicą, że nie zwraca danych jak poprzednio, tylko od razu dzięki pobraniu adresu(wskaźnika) drugiej struktury przypisuje(inaczej zwraca) jej odpowiednie wartości danych dla tego przykładu(czyli wyniki działania).
Z punktu widzenia użytkowników, aplikacja działa tak samo. Z punktu widzenia programisty są to dwie odrębne techniki tworzenia aplikacji.

Rekurencja

Jak każdy szanujący się język programowania, C++ daje Ci możliwość tworzenia rekurencji. Rekurencją nazywamy wywoływanie funkcji wewnątrz jej definicji. Nieumiejętne posługiwanie się rekurencją może w bardzo łatwy sposób zawiesić Twój program. Wystarczy, że dasz zły warunek wyjścia z rekurencji i wywoływanie będzie zapętlone w nieskończoność. Nieskończoność prędzej czy później w przypadku rekurencji nastanie, ponieważ zasoby pamięciowe komputera są ograniczone. Poniżej zamieszczam funkcję, która liczy silnię i jest napisana rekurencyjnie.
C/C++
long long Silnia( long long n )
{
    if( n <= 1 ) return( 1 ); else return( Silnia( n - 1 ) * n );
}

Inny przykład - rekurencja


Zobaczmy jak wygląda zadanie z kursu 8.2, przerobione na funkcję rekurencyjną.
C/C++
//Odliczanie inne spojrzenie przykład z kursu 8.2
#include <conio.h>
#include <iostream>
void Odliczanie( int i );

int main()
{
    Odliczanie( 10 );
    getch();
    return 0;
}
void Odliczanie( int i )
{
    using std::cout;
    cout << "Rakieta startuje za "
    << i << " sek.\n";
    if( i > 0 )
         Odliczanie( i - 1 );
   
    cout << i << ". Start zgodny z planem\n";
}
Przykład jest bardzo prosty, jednak bardzo dobrze pokazuje jak można manipulować rekurencją do osiągnięcia wybranych celów. By nie rozpisywać się za bardzo nad opisaniem przykładu, będę go opisywać dla funkcji Odliczanie(3). Funkcja wywoła się 4 raz dla Odliczanie(3), Odliczanie(2), Odliczanie(1), Odliczanie(0). Ponieważ dla pierwszych 3 wywołań funkcji warunek if jest prawdziwy, dlatego otrzymamy:
C/C++
/*
Rakieta startuje za 3 sek.
Rakieta startuje za 2 sek.
Rakieta startuje za 1 sek.
*/
Następnie Odliczanie(0) powoduje, iż otrzymujemy
C/C++
/*Rakieta startuje za 0 sek.
0. Start zgodny z planem*/
Czyli na początku komunikaty wyświetlają się w kolejności od 1 do 4, by następnie wyświetlać się w kolejności odwrotnej od 4 do 1. By potwierdzić moje słowa można sobie zmodyfikować kod wstawiając w odpowiednie miejsce getch():
C/C++
getch();
if( i > 0 )
     Odliczanie( i - 1 );

cout << i << ". Start zgodny z planem\n";
getch();
By dobitnie potwierdzić tą tezę zobacz jaki adres ma wartość i modyfikując kod funkcji:
C/C++
void Odliczanie( int i )
{
    using std::cout;
    cout << "Rakieta startuje za "
    << i << " sek.  Natomiast adres i to: "
    << & i << "\n";
    if( i > 0 )
         Odliczanie( i - 1 );
   
    cout << i << ". Start zgodny z planem."
    << " Natomiast adres i to: "
    << & i << "\n";
}
Co nie pozostawia wątpliwości, jak działa rekurencja w tym przypadku. Adres i jest zmienny w zależności od wartości.

Kolejny przykład - rekurencja


Kolejna funkcja, która pokazuje zasadę rekurencji. W bloku głównym main wywołujemy funkcję Silnia(n), która wywołuje samą siebie:
C/C++
//Silnia, jako przykład funkcji rekurencyjnej
#include <conio.h>
#include <iostream>

long long Silnia( long long n );
int main()
{
    using std::cout;
    int n = 3, silnia;
    silnia = Silnia( n );
    /*wywołanie funkcji dla n=3
       obliczenie funkcji dla n=3*/
    cout << "\nSilnia (" << n << ") = " << silnia;
    getch();
    return 0;
}

long long Silnia( long long n )
{
    using std::cout;
    int k;
    cout << "wejsce do funkcji dla n = " << n << "\n";
    /* kolejno
         wejsce do funkcji dla n=3
         wejsce do funkcji dla n=2
         wejsce do funkcji dla n=1*/
    if( n <= 1 ) {
        k = 1;
        cout << "return k = " << k << "\n";
        //obliczenie funkcji (k) dla n=1
        return k;
    }
    else {
        k = n * Silnia( n - 1 );
        /*kolejno wykonywane są:
             wywołanie funkcji dla n=2
             wywołanie funkcji dla n=1
             (funkcja dwukrotnie wywołała samą siebie)
             obliczenie funkcji dla n=2
             obliczenie funkcji dla n=3
             (funkcja dwukrotnie obliczyła k)*/
        cout << "return k = " << k << "\n";
        return k;
    }
}

Przekazywanie funkcji przez wskaźnik

C/C++
//Przekazywanie funkcji przez wskaźnik
#include <conio.h>
#include <iostream>
#include <cmath>
float Kwadrat( float );
float Szescian( float );
/*deklaracja wskaźnika funkcji
float (*wskaznik) (float)
musi być zgodna z definicja
funkcji którą ma wskazywać*/
void WyswietlWynik( float, float( * wskaznik )( float ) );

int main()
{
    using std::cout;
    using std::cin;
    char znak;
    float bok;
   
    cout << "Czego pole oblczamy K - kwadrat, S - szescianu, wybierz?\n";
    cin.get( znak ).get();
   
    if( znak == 'K' ) {
        cout << "\nPodaj bok kwadrata\n";
        cin >> bok;
        WyswietlWynik( bok, Kwadrat );
        //WyswietlWynik(bok, *Kwadrat);
    } else if( znak == 'S' ) {
        cout << "\nPodaj bok szescianu\n";
        cin >> bok;
        WyswietlWynik( bok, Szescian );
    } else
         cout << "\nPodano bledny parametr!\n";
   
    getch();
    return 0;
}
float Kwadrat( float bok ) {
    using std::pow;
    return pow( bok, 2 );
}
float Szescian( float bok ) {
    using std::pow;
    return 6 * pow( bok, 2 );
}
void WyswietlWynik( float bok, float( * wskaznik )( float ) ) {
    using std::cout;
    cout << "Wynik pole = "
    <<( * wskaznik )( bok );
}
Jak widać nie jest to trudna sprawa, należy pamiętać tylko by definicja funkcji i deklaracja wskaźnika miały tą samą postać. Czyli
C/C++
//deklaracja funkcji
float Szescian( float );
//definicja wskaźnika na tą funkcję
float( * wskaznik )( float );
Kompilator w razie błędu wychwyci niezgodności.
Jeśli dobrze przyjrzałeś się przykładowi, to pewnie poeksperymentowałeś zza komentowaną linijką kodu. To pewnie zauważyłeś, że robi ona dokładnie to samo co jej pierwowzór,
C/C++
WyswietlWynik( bok, Kwadrat );
//WyswietlWynik(bok, *Kwadrat);
Oznacza to, iż zapis Kwadrat i *Kwadrat, są sobie równoważne. By otrzymać adres funkcji wystarczy podać jej samą nazwę(Kwadrat), a wskaźnik na tą funkcję(*Kwadrat) to przecież także jej adres.

Kurs XVII i XVIII - podsumowanie

To nie jest wszystko co można napisać o funkcjach. Są one składowymi wszystkich programów, które są budowane w C++.  Jest to kolejny z tematów, który lepiej dobrze mieć opanowany, ponieważ na pewno się przyda. Jeżeli dobrze opanowałeś tematykę z kursów to właśnie zostałeś początkującym programistą, który potrafi budować programy proceduralne. Kolejne kilka kursów jeszcze będzie dotykać podstaw C++. Będą stanowić one uzupełnienie poznanych wiadomości i poszerzą poznaną wiedzę. Jeżeli będziesz konsekwentnie poznawał kolejne zagadnienia to będziesz miał jeszcze styczność z zagadnieniami związanymi z funkcjami.

Ćwiczenia

1. Napisz program obliczający średnią Twoich ocen ze świadectwa(indeksu). Program ma pobierać tak długo liczby, aż podasz liczbę zero. Program ma posiadać funkcję sprawdzającą czy podany dane są liczbami, czyli ma zapobiegać uszkodzenia strumienia wejściowego.
 
2. Napisz program jak wyżej, jednak stwórz strukturę, która będzie przechowywać oceny dla poszczególnych podawanych przedmiotów np.
C/C++
struct oceny {
    przedmiot1;
    przedmiot2;
    .
    .
}
Oczywiście podczas podawania ocen ma być wyświetlana informacja, dla jakiego przedmiotu jest wprowadzana dana. Możesz jako ostatnią pozycję w strukturze utworzyć zmienną średnia, która będzie przechowywać obliczoną średnią dla ocen z przedmiotów.
 
3. Używając rekurencji dla funkcji napisz program obliczającą silnie dla liczby. By przypomnieć silnia dla liczby 3 to inaczej działanie 1 * 2 * 3, dla liczby 6 to 1 * 2 * 3 * 4 * 5 * 6. Trzeba jednak pamiętać, że silnia 0! równa się 1!!!
 
4.* Zaprojektuj kalkulator, który będzie wykonywać wszystkie działania matematyczne jakie znasz. Dodatkowo ma mieć możliwość obliczenia pola i obwodów wszystkich znanych ci figur geometrycznych. Dla lepszej wizualizacji, proponuje stworzyć ciekawy interfejs oparty o bibliotekę DDT. Wszystkie operacje podczas działanie programu mają być zapisane w pliku. Czyli jeżeli użytkownik włączył program zrobił dodawanie i obliczył pole jakiejś figury i zakończył program, to wszystko co wykonał ma być zapisane w pliku o nazwie jaką sam wymyślisz. Dobrze by było by zapisane dane to te, które użytkownik widzi na ekranie podczas obsług programu. Wykorzystaj wszystkie poznane zagadnienia by napisać spójny i przyjazdy dla użytkownika program.
 
5. Napisz program, który uzyska ten sam wynik co przykład z 6.3 Tablice wielowymiarowe. Użyj funkcji, która będzie obsługiwać tabele w programie. Najpierw je wypełnij a potem wyświetl.
Poprzedni dokumentNastępny dokument
Obsługa plikówDynamiczne zarządzanie pamięcią new i delete