Funkcje z strukturami
Zobaczmy jak za pomocą funkcji przekazać struktury, jest to proces znacznie prostszy niż wcześniej prezentowane przekazywanie tabel.
#include <iostream>
#include <cmath>
struct BokiTrojkata
{
double bokA;
double bokB;
double bokC;
bool czy_prawda;
};
struct WynikiTrojkata
{
double Wynik1;
double Wynik2;
double Wynik3;
bool czy_prawda;
};
WynikiTrojkata TwierdzeniePitagorasa( BokiTrojkata pobiezboki );
void WyswietlWyniki( const WynikiTrojkata wynik );
int main()
{
using std::cout;
using std::cin;
BokiTrojkata wprowadzDane;
WynikiTrojkata wyswietl;
cout << "Wprowadz dana bokow a, b i c: ";
while( cin >> wprowadzDane.bokA >> wprowadzDane.bokB >> wprowadzDane.bokC )
{
wyswietl = TwierdzeniePitagorasa( wprowadzDane );
WyswietlWyniki( wyswietl );
cout << "\nPodaj ponownie boki lub 'k' by wyjsc.\n";
}
return 0;
}
WynikiTrojkata TwierdzeniePitagorasa( BokiTrojkata pobierzboki )
{
using std::pow;
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;
}
return odpowiedz;
}
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.
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.
#include <iostream>
#include <cmath>
struct BokiTrojkata
{
double bokA;
double bokB;
double bokC;
bool czy_prawda;
};
struct WynikiTrojkata
{
double Wynik1;
double Wynik2;
double Wynik3;
bool czy_prawda;
};
void TwierdzeniePitagorasa( const BokiTrojkata * pobiezboki, WynikiTrojkata * wynik );
void WyswietlWyniki( const WynikiTrojkata * wynik );
int main()
{
using std::cout;
using std::cin;
BokiTrojkata wprowadzDane;
WynikiTrojkata wyswietl;
cout << "Wprowadz dana bokow a, b i c: ";
while( cin >> wprowadzDane.bokA >> wprowadzDane.bokB >> wprowadzDane.bokC )
{
TwierdzeniePitagorasa( & wprowadzDane, & wyswietl );
WyswietlWyniki( & wyswietl );
cout << "\nPodaj ponownie boki lub 'k' by wyjsc.\n";
}
return 0;
}
void TwierdzeniePitagorasa( const BokiTrojkata * wprowadzDane, WynikiTrojkata * wynik )
{
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.
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ą.
#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:
Następnie Odliczanie(0) powoduje, iż otrzymujemy
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():
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:
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:
#include <conio.h>
#include <iostream>
long long Silnia( long long n );
int main()
{
using std::cout;
int n = 3, silnia;
silnia = Silnia( n );
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";
if( n <= 1 ) {
k = 1;
cout << "return k = " << k << "\n";
return k;
}
else {
k = n * Silnia( n - 1 );
cout << "return k = " << k << "\n";
return k;
}
}
Przekazywanie funkcji przez wskaźnik
#include <conio.h>
#include <iostream>
#include <cmath>
float Kwadrat( float );
float Szescian( float );
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 );
} 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
float Szescian( float );
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,
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.
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.