1. PRZEŁADOWANIE NAZW FUNKCJI
W poprzedniej części kursu powiedzieliśmy sobie o funkcjach. Każda miała swoją nazwę, typ zwracany oraz argumenty. Logicznym jest, że mogą występować funkcje takiego samego typu oraz z takimi samymi argumentami. Rodzi się więc pytanie: czy mogą instnieć funkcje, które będą miały takie same nazwy?
Otóż tak. Jednak obok siebie nie mogą istnieć dwie funkcje o identycznej deklaracji. Musi być coś, co je rozróżni. Tym czymś będzie inny zestaw argumentów.
Zobaczmy przykład:
int licz(int a, int b); // funkcja 1
int licz(int a); // funkcja 2
Są to deklaracje dwóch funkcji o tych samych nazwach, ale innych argumentach. Wywołujemy je przez podanie odpowiednich wartości w nawiasach, np:
licz(25, 28); // wywolanie funkcji 1
licz(25); // wywolanie funkcji 2
Kompilator nie zaprotestuje, gdyż zorientuje się na podstawie argumentów, której funkcji ma użyć. Jeszcze jeden przykład:
#include <iostream.h>
float licz(int a, int b); // funkcja dzielaca 2 liczby
int licz(int a); // funkcja podnoszaca liczbe do kwadratu
main()
{
cout << licz(28, 25) << endl;
cout << licz(25) << endl;
}
float licz(int a, int b)
{
return a/b;
}
int licz(int a)
{
return a*a;
}
Tak na marginesie: mimo że typy zwracane tych funkcji są różne, nie mogłoby to pomóc w wybraniu odpowiedniej do wykonania - cout może bowiem wyświetlać zarówno dane typu float, jak i int.
Warto wspomnieć tutaj jeszcze o argumentach domniemanych. Jak pamiętamy, pomagały nam one ograniczać ilość wprowadzanych wartości, nie redukując przy tym liczby argumentów - mogliśmy uruchamiać tę samą funkcję na różne sposoby. Tutaj sytuacja trochę się komplikuje - funkcja z jednym "zwykłym" argumentem np. typu char może zostać uruchomiona dokładnie tak, jak na przykład funkcja z dwoma argumentami - jednym zwyczajnym i jednym domniemanym. Wystąpienie takiej sytuacji w programie jest błędem. Przykładowo:
char funkcja(int a = 28, int b = 25, int c); // dwa argumenty domniemane
float funkcja(int liczba);
Obie zdeklarowane funkcje mogłyby zostać wywołane w ten sam sposób - funkcja(wartosc_int). Przedstawiony zapis jest więc błędem.
Kolejny przykład:
int funkcja(int a = 28);
char funkcja();
Tutaj również widzimy błąd - każda z przedstawionych funkcji może zostać wywołana bez argumentów, np. cout << funkcja();
Do zapamiętania z rozdziału:
- możemy nadawać funkcjom identyczne nazwy, pod warunkiem, że będą się różniły zestawem argumentów
- wartości argumentów domniemanych nie muszą być podawane przy wywołaniu funkcji
Zadanie:
Zmodyfikuj nasz program z poprzedniej lekcji (a jest to naprawdę bardzo proste), aby korzystał z dwóch funkcji o takiej samej nazwie: ruch.
2. TABLICE
Bardzo często musimy skorzystać z większej ilości danych tego samego typu. Aby zapisać na przykład 25 znaków, musielibyśmy stworzyć 25 osobnych zmiennych. W praktyce oznacza to jednak nadmierne zużycie czasu i klawiatury - powinniśmy bowiem skorzystać z tablic.
Tablice w programowaniu to nic innego, jak zbiór zmiennych tego samego typu. Zmienne te nie mają swoich osobnych nazw - każda jest po prostu kolejnym elementem. Definicja tablicy wygląda tak: typ_danych nazwa[ilosc_elementow]
Do elementu tablicy odnosimy się przez podanie odpowiedniego indeksu: nazwa[index].
Przykładowo aby zapisać znak 'M' do 25. elementu tablicy znakowej o nazwie litery używamy instrukcji: litery[24] = 'M';
Należy podkreślić tutaj istotną rzecz - indeksowanie tablic w C++ zaczynamy od 0, a nie od 1, dlatego pierwszym elementem tablicy będzie 0, a nie 1. Wartości elementom tablicy można nadawać już w czasie inicjalizacji, np.:
int liczby[10] = {25, 28}; // dwa pierwsze elementy mają wartość 25 i 28
Korzystanie z tablic bardzo ułatwiają pętle. Zobaczmy na przykład jak do stuelementowej tablicy zapisać 100 kolejnych naturalnych liczb parzystych:
int liczby[100], licznik, licznik2; // definiujemy tablice oraz pomocnicze zmienne
for(licznik = 0; licznik <= 99; licznik++) // petla for
{
licznik2 += 2; // inkrementujemy licznik2 o 2
liczby[licznik] = licznik2; /* wpisujemy w element tablicy o numerze licznik kolejna liczbe parzysta */
}
Powiedzmy sobie coś na temat łańcuchów znaków. Możemy zapisywać je w tablicy o elementach typu char. Przykładowo:
char imie[20]; // podajemy max. dlugosc
cin >> imie;
cout << "Masz na imie: " << imie;
Należy pamiętać, że w ten sposób można wyświetlać zawartość jedynie tablic znakowych. Oczywiście można się też odnosić do pojedynczych elementów takiej tablicy, np.
cout << "Druga litera Twojego imienia to: " << imie[1];
Możliwości tablic nie zamykają się jednak tylko na zapisywaniu zmiennych w uporządkowanym szeregu. Możemy bowiem tworzyć tablice o większej liczbie wymiarów, np. 2, 3, 4 czy nawet więcej. Tablice takie definiujemy dodając kolejny wymiar, np.
int tablica[10][10];
W tak zdefiniowanej tablicy możemy zapisać 100 elementów (10*10). Do elementu takiej tablicy odnosimy się przez podanie wszystkich "współrzędnych", tzn. np.
tablica[2][8] = 25;
Mówiąc bardziej obrazowo: pierwszym wymiarem może być linijka tekstu w książce, drugim cała strona, trzecim książka, czwartym półka z książkami, piątym biblioteka etc.
Tablice mogą być również przesyłane do funkcji jako argumenty. Należy tu jednak uważać: jako argument nie są przesyłane wszystkie wartości zapisane w tablicy, a jedynie adres w pamięci jej zerowego elementu. Już wyjaśniam o co chodzi. Wysyłanie wielu (czasami tysięcy) zmiennych do funkcji znacznie spowalniałoby pracę programu. Dlatego, jeśli chcemy potraktować tablicę jako argument, podajemy jedynie jej nazwę, która jest jednocześnie adresem jej zerowego elementu (jest to po prostu referencja, o której mówiliśmy o niej w poprzedniej części kursu - przypomnij sobie).
Przykładowo program sumujący elementy tablicy:
#include <iostream.h>
int suma(int liczby[]); // prototyp funkcji pobierajacej tablice
main()
{
int tablica[5] = {25, 28, 729, 95, 314};
cout << suma(tablica); // jako argument podajemy jedynie nazwe tablicy
}
int suma(int liczby[]) // definicja funkcji pobierajacej tablice
{
int licznik, wynik = 0;
for(licznik=0; licznik<=4; licznik++)
wynik += liczby[licznik]; // sumujemy wszystkie elementy
return wynik; // zwracamy wynik
}
Definiując funkcję nie musimy podawać ilości elementów w tablicy - i tak bowiem pobierze ona jedynie adres zerowego elementu. Sytuacja taka implikuje fakt, iż funkcja nie wykonuje kopii argumentu - pracuje na "oryginale" tablicy, więc wszystkie dokonane zmiany będą dostrzegalne również poza funkcją.
Napiszmy teraz program, który losuje 100 liczb z zakresu 0 ÷ 99, a następnie sprawdza ile razy wystąpiła liczba wprowadzona przez użytkownika (nie jest może zbyt ciekawy, ale dostarczy nam trochę nowych wiadomości). Funkcja odpowiadająca za losowanie wywołujemy następująco: rand() % liczba_maksymalna+1, np. rand() % 10 dokona (pseudo)losowania liczby z zakresu 0 ÷ 9.
#include <iostream.h>
#include <stdlib.h>
#include <time.h>
void wylosuj(int tablica[]); // prototyp funkcji wylosuj
int policz(int tablica[], int n);
main()
{
int tablica[100], n, licznik;
wylosuj(tablica); // wywolanie funkcji wylosuj
cout << "Podaj liczbe: ";
cin >> n;
cout << "W tym losowaniu liczba ta wystapila " << policz(tablica, n) << " razy";
}
void wylosuj(int tablica[]) // definicja funkcji wylosuj
{
int licznik;
srand(time(NULL)); // rozpoczecie procesu losowania
for(licznik = 0; licznik <= 100; licznik++)
tablica[licznik] = rand() % 100; // do elementu tablicy wpisujemy liczbe od 0 do 99
}
int policz(int tablica[], int n)
{
int licznik, ile_razy = 0;
for(licznik = 0; licznik <= 100; licznik++)
if(tablica[licznik] == n) // jesli element tablicy jest wpisana liczba
ile_razy++; // inkrementacja zmiennej ile_razy
return ile_razy; // zwracamy ile_razy
}
Wyjaśnijmy sobie teraz co i jak. Po pierwsze dodaliśmy 2 nowe pliki nagłówkowe, z których nigdy wczesniej nie korzystaliśmy - time.h oraz stdlib.h. W pierwszym z nich zawarta jest definicja funkcji time() - uruchomiona z argumentem NULL zwraca ilość sekund, które upłynęły od północy 1 stycznia 1970 roku. Zapewne zapytasz: a po co nam takie dziwadło? Otóż jest ona nam potrzebna do zainicjowania losowania (można oczywiście się bez niej obejść, ale tak jest chyba ciekawiej...). Chodzi mniej więcej o to, aby funkcję, która inicjuje losowanie (srand()) musi uruchamiać się za każdym razem z innym parametrem. Jeśli uruchamialibyśmy ją cały czas z jednym argumentem (np. 28), to program przy każdym uruchomieniu losowałby te same liczby. Dzięki funkcji time(NULL) mamy pewność, że przy każdym użyciu minęła od 1 stycznia 1970 inna ilość sekund...
Definicje funkcji srand() i rand() znajdują się w drugim pliku - stdlib.h.
Do zapamiętania z rozdziału:
- tablice definiujemy przez podanie typu, nazwy i w nawiasie kwadratowym liczby elementów, np. int liczby[25]
- indeksowanie tablic rozpoczynamy od 0
- tablice mogą być wielowymiarowe - rozmiar kolejnych wymiarów podajemy w kolejnych nawiasach, np. int liczby[25][28]
- do konkretnego elementu tablicy odnosimy się przez podanie jej nazwy i indeksu, np. liczby[25] = 28;
- funkcja zwracająca losową liczbę to rand() (musi być poprzedzona inicjującą funkcją srand()) - ich definicje znajdują się w pliku stdlib.h
Zadanie:
Napisz program, ktory w dwuwymiarowej tablicy liczb całkowitych przeprowadzi dwa losowania - Multi Lotka (20 liczb z 80) oraz Dużego Lotka (5 z 49). Następnie poprosi użytkownika o wprowadzenie własnych typów i sprawdzi, czy udało mu się coś "trafić".
DODATEK O NOWYCH TYPACH DANYCH
Poznaliśmy już podstawowe typy danych (int, float, char), gdyż inne nie były nam jeszcze potrzebne. Należy jednak wiedzieć, że od istnieje wiele ich wariacji, których użycie może często (zwłaszcza przy intensywnym wykorzystywaniu pamięci RAM) znacznie zoptymalizować nasz program. Typ podstawowy możemy modyfikować przez dodawanie odpowiednich słów kluczowych: signed - ze znakiem, unsigned - bez znaku (liczba dodatnia), long - liczba z większego przedziału, short - liczba z mniejszego przedziału (w praktyce jednak słowo short używane jest raczej niezbyt często).
Przykładowo dla liczb całkowitych:
- unsigned int - liczba całkowita dodatnia z zakresu 0 ÷ 65535
- signed int - liczba całkowita z zakresu -32768 ÷ 32767
- long signed int - liczba całkowita z zakresu -2.147.483.648 ÷ 2.147.483.647
- long unsigned int - liczba całkowita z zakresu 0 ÷ 4.294.967.295
Warto zwrócić tu uwagę na jedną ciekawą rzecz - typ int, którego używaliśmy do tej pory jest w rzeczywistości typem signed int (obydwa zapisy są jak najbardziej poprawne).
Z kolei dla liczb rzeczywistych "podtypy" wyglądać będą następująco:
- double - liczba rzeczywista z zakresu 1.7*10-308 ÷ 1.7*10308
- long double - liczba rzeczywista z zakresu 3.4*10-4932 ÷ 1.1*104932
Oprócz tych wariacji możemy korzystać jeszcze oczywiście z podstawowego float (liczba rzeczywista z zakresu 3.4*10-38 ÷ 3.4*1038).
Powiedzmy sobie coś jeszcze na temat typu wyliczeniowego enum. Jest to osobny typ dla liczb całkowitych służący raczej nie do przechowywania liczb, ale do przechowywania informacji za pomocą liczb. Definiuje się go następująco: enum nazwa_typu {lista wyliczeniowa};
Na przykład:
enum ruch
{
zmienny = 4,
prostoliniowy = 25,
po_okregu = 28
};
Musimy jeszcze stworzyć zmienną typu ruchy:
ruch wybor; // definicja zmiennej naszego typu o nazwie wybor
Do zmiennej tej mozemy podstawic jedynie to, co zostało określone na liście wyliczeniowej, np. ruch = prostoliniowy lub ruch = zmienny. Jest to bardzo ważne, gdyż nie możemy do tego typu zmiennej wpisywać nic innego, np. ruch = 28 - będzie to zwyczajny błąd.
Jeśli jednak chcielibyśmy korzystać z "normalnej" numeracji na liście wyliczeniowej (tzn. 0, 1, 2...), to wystarczy, że podamy tylko nazwy elementów, a kolejne wartości zostaną przypisane przez domniemanie, np.
enum ruch
{
zmienny, // wartosc 0
prostoliniowy, // wartosc 1
po_okregu = 25, // wartosc 25
inny // wartosc 26 - poprzedni element listy + 1
};
We wszystkich językach programowania istnieje jeszcze taki twór jak stałe. Są to elementy, które nie zmieniają swej wartości przez cały czas trwania programu. Można je stworzyć na wiele sposobów, nam wystarczą stałe tworzone przy pomocy słowa const, np. const int liczba = 25. Zmienna liczba w tym wypadku przez cały czas trwania programu będzie miała wartość 25. Próba zapisania do niej czegokolwiek (np. liczba = 28) jest niedozwolona.
W następnej części kursu będziemy zajmować się wskaźnikami.