Prowadzący: mgr inż. Elżbieta Majka Programowanie strukturalne i obiektowe Semestr 2 Prowadzący: mgr inż. Elżbieta Majka
Funkcja Każda funkcja posiada trzy własności: zwraca dane (lub nie(void) jeśli tego nie chcemy); posiada swoją nazwę; może posiadać dowolną ilość parametrów wejściowych (lub może nie mieć żadnego(void), jeśli tego nie chcemy). Jedynym wyjątkiem co do wartości zwracanego typu jest brak możliwości zwrócenia danych w postaci tablicy!
Ogólna budowa funkcji zwracany_typ_danych nazwa_funkcji(parametry_funkcji) { //blok funkcji → co funkcja będzie robiła return(wartosc_zwracana); } Wszystkie zadeklarowane zmienne wewnątrz funkcji lub w parametrach funkcji są widoczne tylko i wyłącznie w obrębie funkcji(zmienne funkcji są dla niej prywatne). Ich modyfikacja nie wpływa na wartości zmiennych poza obrębem funkcji. Parametry funkcji możesz traktować jak zwykłe zmienne wewnątrz funkcji, które różnią się tylko tym, że mają przypisaną wartość podaną podczas wywołania funkcji.
Co ważnego powinniśmy wiedzieć o funkcjach Każda funkcja musi mieć unikatową nazwę w obrębie całego programu (są odstępstwa ale o tym później); Parametry funkcji oddzielone są przecinkami; Słowo kluczowe void informuje kompilator, że funkcja nie zwraca żadnych danych. Słowo kluczowe void użyte w miejscu definiowania parametrów funkcji informuje kompilator, że funkcja nie przyjmuje żadnych parametrów; Parametrom funkcji można przypisywać domyślne wartości, które zostaną użyte wtedy gdy podczas jej wywołania parametr nie zostanie uzupełniony; Funkcja może zwracać wszystkie dopuszczalne typy – liczby całkowite, zmiennoprzecinkowe, wskaźniki, struktury i obiekty; Za pomocą parametrów możemy przekazywać również tablice.
Deklaracja i wywoływanie funkcji Prototyp funkcji int Funkcja1(int, char); Funkcja2(int, char); void Funkcja3(int, char); int Funkcja4(void); int Funkcja5(); Funkcja int x=9,y=6,wynik; wynik = suma(x,y); Argumenty aktualne typ_zwracanej_wartości nazwa_funkcji(lista argumentów formalnych); int f1(int a, char b); Pisząc deklarację funkcji możemy dodatkowo pisać nazwy dla parametrów. Są one jednak ignorowane przez kompilator i pełnią tylko i wyłącznie rolę informacyjną dla użytkownika. Zamiast bloku głównego na końcu deklaracji funkcji jest średnik.
Funkcje - powtórzenie Funkcja Funkcja #include<iostream.h> int suma(int,int);//prototyp void main(){ int x=9,y=6,wynik; wynik = suma(x,y); cout<<wynik; } //definicja funkcji int suma(int a,int b){ int w = a+b; return w; Funkcja #include<iostream.h> //definicja funkcji jest jej //prototypem int suma(int a,int b){ int w = a+b; return w; } void main(){ int x=9,y=6,wynik; wynik = suma(x,y); cout<<wynik;
Funkcje – przekazywanie argumentów Przez wartość: int x=2,y=3; int suma(int,int); … int w = suma(x,y); Przez adres: int x=2,y=3; int suma(int *,int); int w = suma(&x,y);
Funkcje – przekazywanie argumentów Przez referencję int x=2,y=3; int &z=a; int suma(int &,int); … int w = suma(z,y); Argumenty tablicowe int x=2,y[5]; int suma(int, int *); //int suma (int, int []);
Biblioteki
Poznajemy dyrektywę #include na nowo Wraz z rozwojem każdego programu kodu przybywa, a poruszanie się po nim staje się coraz bardziej uciążliwe ze względu na jego długość. Staramy się grupować tematycznie większość funkcji w programie, jednak i to niewiele daje, gdy przychodzi pracować z kodem, który ma co najmniej 1000 wierszy. Z pomocą przychodzi tu dyrektywa #include. Język C++ to zbiór logicznych zasad umożliwiających programowanie. Same zasady umożliwiają zarządzać danymi, jednak nie są one wystarczające do tego, aby wykorzystywać w łatwy sposób możliwości sprzętowe komputera. Ponieważ język C++ umożliwia łatwe organizowanie danych, to oczywistym też jest, że równie potrzebnym elementem jest interfejs, umożliwiający prezentację danych oraz interfejs reagujący na wszystkie urządzenia, jakie posiadamy w komputerze (np. mysz, klawiaturę, kartę graficzną, kartę sieciową itd.). Zadaniem dyrektywy #include jest umożliwienie łatwego wykorzystywania zasobów sprzętowych komputera, poprzez dołączanie plików nagłówkowych bibliotek, które są odpowiedzialne za komunikację z różnymi urządzeniami. Innymi słowy za każdym razem, gdy korzystamy z dyrektywy #include, dołączamy do swojego programu interfejs umożliwiający łatwy dostęp do wybranych zasobów komputera. Za pomocą tego polecenia można w łatwy sposób pisać serwery TCP/UDP, używać OpenGL'a, czy też innych modułów umożliwiających łatwy dostęp do sprzętowych zasobów komputera. Oprócz dołączania istniejących bibliotek, dyrektywa pozwala dołączać własne biblioteki. Dzięki tej własności możemy zorganizować swój kod źródłowy lepiej, a wszelkie modyfikacje kodu staną się szybsze i łatwiejsze.
Ustawianie ścieżki do pliku nagłówkowego Do tej pory gdy chcieliśmy dołączyć bibliotekę do programu, stosowaliśmy tylko i wyłącznie zapis #include <ścieżka do pliku>. Użycie ostrych nawiasów <...> informuje kompilator, aby przeszukiwał domyślne ścieżki, w których znajdują się pliki nagłówkowe. Jeśli kompilator nie odnajdzie żądanego pliku nagłówkowego, kompilator zwróci błąd informując, że pliku o podanej nazwie nie znaleziono. Drugą metodą na dołączanie plików nagłówkowych jest wykorzystanie zapisu #include "ścieżka do pliku". Zapis z użyciem podwójnych apostrofów informuje kompilator, że plik nagłówkowy ma być poszukiwany tylko i wyłącznie względem aktualnego katalogu, w którym znajduje się nasz projekt.
Rozszerzenia plików i ich znaczenie Pliki *.h (dla C) i *.hpp (dla C++), są nazywane plikami nagłówkowymi (ang. header files). Każdy plik nagłówkowy powinien zawierać tylko i wyłącznie interfejs. Przez słowo interfejs rozumiemy: deklaracje typów deklaracje funkcji deklaracje struktur deklaracje klas deklarację ewentualnych zmiennych globalnych Dodatkowo dołączamy do niego niezbędne pliki nagłówkowe, jakie będą wykorzystywane przez daną bibliotekę. W pliku nagłówkowym nie umieszczamy natomiast bloków funkcji. Można powiedzieć po prostu, że w pliku nagłówkowym umieszczamy wszystko oprócz bloków funkcji.
Rozszerzenia plików i ich znaczenie Pliki *.c i *.cpp nazywamy plikami źródłowymi. Jak nietrudno domyślić się, *.c oznacza standard użytego języka C, natomiast *.cpp standard użytego języka C++. W plikach z takim rozszerzeniem umieszczamy tylko i wyłącznie definicje funkcji, czyli nazwę funkcji razem z jej ciałem (czyli blokiem funkcji).
Budowa pliku nagłówkowego *.h *.hpp Istnieje co najmniej kilka wersji budowy plików nagłówkowych dla języka C i C++. Niektóre z nich nie działają jednak pod wszystkimi kompilatorami #ifndef nazwaPliku_hpp #define nazwaPliku_hpp /* tutaj piszesz cały interfejs */ #endif
Budowa pliku nagłówkowego *.h *.hpp Opis dyrektyw preprocesora
Budowa pliku nagłówkowego *.h *.hpp Zaprezentowany przykład czytamy następująco: #ifndef dowolna_nazwa // jeśli nie istnieje dowolna_nazwa, wykonuj blok #define dowolna_nazwa //utwórz zmienną o nazwie dowolna_nazwa #endif //koniec bloku warunkowego Wykorzystując instrukcje preprocesora zabezpieczamy bibliotekę przed wielokrotnym dołączaniem tego samego kodu do własnego programu. Jeśli go nie użyjemy, a dołączymy tą samą bibliotekę co najmniej dwukrotnie w programie (nawet w różnych plikach), otrzymamy błąd kompilacji nawet jeśli wszystko będzie poprawnie napisane. Nazwy zmiennych, które definiujemy za pomocą preprocesora muszą być unikatowe podczas kompilacji projektu dla każdego używanego pliku, tak więc w każdym pliku musi się znajdować inna nazwa zmiennej preprocesora. Najpopularniejszą metodą zapewnienia sobie unikatowych nazw zmiennych, jest używanie nazwy pliku dla zmiennej preprocesora. Nie jest to jednak obowiązek i mamy tu praktycznie pełną dowolność. Nazwy zmiennych preprocesora mają takie same kryteria dla nazewnictwa jak zmienne języka C++.
Budowa pliku źródłowego *.c *.cpp Plik źródłowy ma bardzo prostą budowę. Jedyne co musimy zrobić to dołączyć plik nagłówkowy pliku, który opisuje interfejs jaki znajduje się w tym pliku. #include "nazwaPliku.hpp" /* tutaj piszesz definicje funkcji */ Jeśli będziemy kompilowali plik *.cpp i nie będzie w nim żadnych błędów, otrzymamy następujący błąd kompilacji: [Linker error] undefined reference to `WinMain@16‘ ld returned 1 exit status Komunikat ten informuje, że w programie nie ma 'ciała' programu, czyli głównej funkcji programu. Ponieważ pliki, które utworzyliśmy nie mają być programem, tylko źródłem dołączanym do programu, który będzie zawierał funkcję main(), wskazane jest aby ten plik nie zawierał funkcji o którą 'doczepił się' kompilator. Dołączając natomiast plik nagłówkowy (*.hpp) do programu głównego za pomocą dyrektywy #include, uzyskamy w pełni sprawny kod, który się kompiluje (pod warunkiem, że nie ma w nim innych błędów).
Szybki przykład //Plik: main.cpp #include <iostream> #include <conio.h> #include "nazwaPliku.hpp" using namespace std; int main() { cout<<"Wynik dodawania to: "<<dodajLiczby(10,15)<<endl; getch(); return(0); } ------------------------------- ------------------------------------ //Plik: nazwaPliku.hpp #ifndef nazwaPliku_hpp #define nazwaPliku_hpp int dodajLiczby(int a,int b); #endif //Plik: nazwaPliku.cpp #include "nazwaPliku.hpp" int dodajLiczby(int a,int b) { return(a+b); }