Programowanie w Języku C 2 Laboratorium 9 C++ - klasy, metody i pola Opracował: mgr inż. Leszek Ciopiński Wstęp teoretyczny Sposób kompilacji Sposób kompilowania programu napisanego w języku C++ jest podobny do metody kompilowania programu w języku C. Podstawową różnicą jest jednak wykorzystywany kompilator. Zamiast polecenia gcc należy użyć g++. g++ -o test test.cpp W zależności od użytej konwencji dopuszcza się stosowanie plików o rozszerzeniach.c i.cpp. Podstawowe cechy języka C++ Słowem wstępu Jeśli użyjemy dowolnej wyszukiwarki internetowej, to powinniśmy bez problemu znaleźć prosty, szablonowy kod napisany w C++, który wyświetla napis Hello World!, w tłumaczeniu na polski Witaj Świecie!. Spójrzmy na niego: using namespace std; int main () cout << "Hello World!" << endl; Zaleca się używanie znaku nowej linii (\n) zamiast manipulatora wyjścia "endl". Chyba że jest to uzasadnione: endl wymusza opróżnienie bufora, ale na przykład przy wielokrotnym zapisie na dysk może to obciążyć jego pracę. Osoby, które już znają C, na pewno się domyślą, co mniej więcej się dzieje w tym kodzie. Najpierw, pokrótce omówimy, co ten program właściwie robi. Za pomocą dołączyliśmy plik nagłówkowy do obslugi strumieni I/O, dzięki czemu możemy wypisywać dane na ekran (ściślej: na standardowe wyjście). Dodatkowo istnieje plik nagłówkowy iostream.h. Jest to jednak nagłówek niestandardowy, pomagający zachować Programowanie w Języku C2 Laboratorium strona: 1 z 10
wsteczną zgodność. int main( )... służy zdefiniowaniu funkcji głównej, która jest zawsze uruchomiana podczas startu naszego programu. Wyrażenie cout << umożliwia nam wypisywanie pewnych informacji. W naszym przypadku wypisaliśmy napis Hello World!, a następnie przedłużyliśmy to polecenie za pomocą operatora <<, i użyliśmy endl, który m.in. dodaje znak nowej linii. Za pomocą return 0 informujemy system, że program może zakończyć działanie bez zgłaszania błędów. Na koniec zostawiliśmy linię z kodem using namespace std. Aby wyjaśnić jej znaczenie, musimy omówić czym są przestrzenie nazw. Przestrzenie nazw Podczas pracy nad dużymi projektami, w których używa się wielu bibliotek z licznymi deklaracjami, możemy w końcu natknąć się na problem konfliktu nazw - gdy kilka obiektów, typów czy funkcji ma tę samą nazwę. Rozwiązaniem może być np. zamknięcie nazw w "zakresach", w celu oddzielenia ich. Z pomocą przychodzi nam mechanizm przestrzeni nazw. Przestrzeń nazw jest zatem zbiorem obiektów, która ogranicza dostęp do nich - oprócz nazwy obiektu niezbędne jest też wspomnienie, z której przestrzeni nazw chcemy go użyć, obchodząc tym samym problem konfliktu nazw. Spójrzmy na kolejny program, zmienioną wersję poprzedniego: int main () std::cout << "Hello World!" << std::endl; Widzimy tu wyrażenie std:: pojawiające się przed cout i endl. Zapis ten oznacza, że wspomniane obiekty chcemy zaczerpnąć z przestrzeni std, a przy okazji nie obchodzi nas, czy są jakieś inne obiekty o takich nazwach. Jeśli jednak pominiemy wzmiankę o std::, pojawi się informacja o błędzie. W przestrzeni nazw std znajdziemy mnóstwo, a wręcz cały arsenał różnych narzędzi, począwszy od pewnych bardzo przydatnych funkcji, np. sortowania, wyszukiwania, a kończywszy na tak zwanych pojemnikach (kolekcjach), które pozwalają nam w łatwy sposób przechowywać pewne wartości. Oczywiście, aby mieć dostęp do tych narzędzi, musimy dołączyć odpowiedni plik nagłówkowy, używając do tego dyrektywy #include. Przykład pierwszy ze wstępu pokazał nam, że nie musimy za każdym razem odwoływać się do przestrzeni nazw, kiedy chcemy użyć znajdujących się w niej rzeczy. Używając using namespace PrzestrzenNazw, podpowiadamy kompilatorowi, w którym miejscu może szukać używanych przez nas obiektów i funkcji, abyśmy mogli swobodnie używać wszystkiego co się znajduje w danej przestrzeni nazw, tzn. bez dodatkowej wzmianki jak np. std::. Oczywiście nie musimy naraz "udostępniać" wszystkiego, co jest w danej przestrzeni nazw, możemy wykorzystać także pewne wybrane elementy. Używamy do tego operacji using PrzestrzenNazw::element. Zobaczmy przykład użycia tej operacji: using std::endl; Programowanie w Języku C2 Laboratorium strona: 2 z 10
int main () std::cout << "Hello World!" << endl; Za pomocą using std::endl poinformowaliśmy kompilator, że będziemy mogli używać w kodzie endl i będziemy mieli na myśli właśnie to pochodzące z przestrzeni std. Nie wykonaliśmy tej operacji na elemencie cout (nie wstawiliśmy instrukcji using std::cout), więc musieliśmy go dalej poprzedzić nazwą przestrzeni. Źródło: http://pl.wikibooks.org/wiki/c%2b%2b/przestrzenie_nazw [dostęp: 21 listopada 2011] C++/Czym jest obiekt > C++ > Czym jest obiekt» < C++ Aby odpowiedzie na pytanie zadane w temacie, zadajmy sobie inne: Co nazywamy obiektem w w iecie rzeczywistym? Otó wszystko mo e by obiektem! Drzewa, zwierz ta, miasta, auta, ludzie... W programowaniu równie obiektem mo e by dowolny twór, o jakim pomy limy. Tworz c " wiat programu" mo na stworzy obiekt, którego u ycie b dzie bardziej "namacalne" od szeregu parametrów, porozrzucanych w ró nych zmiennych. To ró ni programowanie strukturalne od programowania obiektowego. Projekt i twór klasa i obiekt Zanim stworzymy jaki obiekt, trzeba ustali czym ten obiekt b dzie. W zale no ci od tego, czy chcemy stworzy wirtualny samochód, czy samolot, nale y okre li dwie rzeczy: jakie w a ciwo cib dzie mia ten obiekt, jakie b dzie mia metody dzia ania. W zwi zku z tym, przed stworzeniem jakiegokolwiek obiektu nale y przedstawi kompilatorowi jego projekt(wzorzec), czyli okre li jego klas. Klasa to byt programistyczny okre laj cy jakie w a ciwo ci i metody b d mia y obiekty, które zostan utworzone na jej podstawie. Jednak sam projekt nie sprawi jeszcze, e dostaniemy obiekty (to tak jakby po narysowaniu projektu domu chcie zamieszka na kartce papieru:-)). Trzeba jeszcze obiekt utworzy, co oznacza po prostu deklaracj obiektu na podstawie pewnej klasy: NazwaKlasy MojObiekt; Wygl da to jak deklaracja zwyk ej zmiennej i tak jest w istocie w C++ tworz c klas definiuje si nowy typ danych. Podobnie jak w przypadku zmiennych, mo na utworzy wiele obiektów danej klasy. [edytuj] Definicja klasy Ogólny szablon definiowania klas w C++ wygl da nast puj co: class NaszaNazwaKlasy... // pola i metody sk adowe klasy Programowanie w Języku C2 Laboratorium strona: 3 z 10
; Po s owie kluczowym class nast puje nazwa naszej klasy (prawid a jej nazywania s takie same jak dla zmiennych).w nawiasach klamrowych umieszcza si definicje sk adowych klasy: pól i metod okre laj c dla nich specyfikatory dost pu.nale y pami ta o r edniku za klamerk zamykaj c definicj klasy. Oto przyk adowa definicja klasy: class NazwaKlasy public: //pola i metody s publicznie dost pne //definiowanie pól int poleint; float polefloat; //deklarowanie metod int Metoda1(); void Metoda2(); ; //pami taj o r edniku! [edytuj] U ycie klasy Sama definicja klasy nie wystarczy, aby uzyska dost p do jej sk adowych. Nale y stworzy obiekt. Mo na przyj, e obiekt to zmienna typu klasowego. Deklaracja obiektu: NazwaKlasy Obiekt; Dost p do pól i metod uzyskuje si operatorem (.): Obiekt.poleInt = 0;//przypisanie warto ci polom Obiekt.poleFloat = 9.04; Obiekt.Metoda1();//wywo anie metody obiektu W przypadku deklaracji wska nika do obiektu: NazwaKlasy *ObiektWsk = new NazwaKlasy; Analogicznie jak w przypadku wska ników na struktury operatorem dost pu do pola/metody klasy poprzez wska nik do obiektu staje si ->: ObiektWsk->poleInt = 0; //przypisanie warto ci polom ObiektWsk->poleFloat = 9.04; ObiektWsk->Metoda1(); //wywo anie metody obiektu Nale y pami ta o zniszczeniu obiektu przed zako czeniem dzia ania programu (lub kiedy nie jest nam ju potrzebny): delete ObiektWsk; [edytuj] Przyk ad Stwórzmy klas kostki do gry: class Kostka public: unsigned int wartosc; unsigned int maks; void Losuj(); ; Po definicji klasy, zdefiniujmy jeszcze metod Losuj() zadeklarowan w tej klasie: void Kostka::Losuj() wartosc = rand()%maks + 1; Programowanie w Języku C2 Laboratorium strona: 4 z 10
Warto zwróci uwag w jaki sposób si to robi. Nazw metody dowolnej klasy jest NazwaKlasy::NazwaMetody. Poza tym aby uzyska dost p do pól klasy, w której istnieje dana metoda nie stosuje si operatora wy uskania. Po tym mo na napisa reszt programu: int main() Kostka kostkaszescienna; //utworzenie obiektu kostkaszescienna.maks = 6; //okre lenie maksymalnej ilosci oczek kostkaszescienna.losuj(); //losowanie cout << "Wylosowano:" << kostkaszescienna.wartosc << endl;//wypisanie wyniku [edytuj] Autorekursja Wska nik this umo liwia jawne odwo anie si zarówno do atrybutów, jak i metod klasy. Poni szy program wymusza u ycie wska nika this, gdy nazwa pola jest taka sama jak nazwa argumentu metody wczytaj: class KlasaThis int liczba; public: void wczytaj(int liczba) this->liczba=liczba; void wypisz() cout << liczba <<endl; ; int main() KlasaThis ObiektThis; ObiektThis.wczytaj(11); ObiektThis.wypisz(); [edytuj] Kontrola dost pu Istniej trzy specyfikatory dost pu do sk adowych klasy: private (prywatny) - dost pne tylko z wn trza danej klasy i klas/funkcji zaprzyja nionych. protected (chroniony) - dost pne z wn trza danej klasy, klas/funkcji zaprzyja nionych i klas pochodnych. public (publiczny) - dost pne dla ka dego. Je li sekwencja deklaracji sk adowych klasy nie jest poprzedzona a dnym z powy szych specyfikatorów, to domy lnym specyfikatorem (dla kompilatora) b dzie private. Dzi ki specyfikatorom dost pu inni programi ci maj u atwione korzystanie z utworzonej przez nas klasy, gdy metody i pola, których nie powinni modyfikowa, bo mog oby to spowodowa niepoprawne dzia anie obiektu, s oznaczone jako private lub protected i nie mog z nich korzysta. Funkcje, które zapewniaj pe n funkcjonalno klasy oznaczone s jako public i tylko do nich ma dost p u ytkownik klasy (do protected równie, ale z ograniczeniami). Oto zmodyfikowany przyk ad z kostk, który zobrazuje cele kontroli dost pu: class Kostka Programowanie w Języku C2 Laboratorium strona: 5 z 10
public : void Losuj(); void Wypisz(); int DajWartosc(); void ZmienIloscScian(unsigned argmax); protected: unsigned wartosc; unsigned max; ; int Kostka::DajWartosc() return this->wartosc; void Kostka::ZmienIloscScian(unsigned argmax) if(argmax> 20) max = 20; else max = argmax; Zmodyfikowana klasa zezwala tylko na kostki maksymalnie dwudziesto cienne. R czne modyfikacje zmiennej max s zabronione, mo na tego dokona jedynie poprzez funkcj ZmienIloscScian, która zapobiega przydzieleniu wi kszej ilo ci c ianek ni 20. Prywatny jest te atrybut warto. Przecie nie chcemy aby by a ona ustawiona inaczej ni przez losowanie! Dlatego mo emy udost pni jej warto do odczytu poprzez metod DajWartosc(), ale modyfikowana mo e by tylko na skutek dzia ania metody Losuj(). Źródło: http://pl.wikibooks.org/wiki/c%2b%2b/czym_jest_obiekt [dostęp:21 listopada 2011] Zarządzanie pamięcią > C++ > Zarządzanie pamięcią» < C++ W języku C++ do alokowania pamięci służy operator new a do zwalniania - delete. W C++ można również stosować funkcje malloc i free, jednak należy być ostrożnym. Najczęstszym błędem jest mieszanie operatorów new i delete z funkcjami malloc i free, np. zwalnianie pamięci zaalokowanej przez new przy pomocy free. Rozważmy prosty przykład. Załóżmy, że chcemy stworzyć wektor 10 liczb typu całkowitego. Możemy to zrobić na dwa sposoby. W stylu znanym z języka C: int *wektor = (int*) malloc (sizeof(int)*10); free (wektor); Albo w stylu C++: int *wektor = new int[10]; delete [] wektor; Od razu widać, że drugi zapis jest łatwiejszy i przyjemniejszy w użyciu. To jest podstawowa zaleta operatora new - krótszy zapis. Wystarczy wiedzieć jakiego typu ma być obiekt, który chcemy powołać do życia, nie martwiąc się o rozmiar alokowanego bloku pamięci. Za pomocą operatora new można również tworzyć tablice wielowymiarowe: int **wektory = new int *[5]; for (int i = 0; i < 5; ++i) Programowanie w Języku C2 Laboratorium strona: 6 z 10
wektory[i] = new int [10]; W ten sposób stworzono tablicę dwuwymiarową którą statycznie zadeklarowalibyśmy jako: int wektory[5][10]; Jenak w przeciwieństwie do int wektory[5][10], która jest tablicą dwuwymiarową, nasze int **wektory jest tablicą tablic i może być rozrzucone po całej pamięci. Ilość elementów poszczególnych wymiarów nie musi być jednakowa. Można np zadeklarować tablicę taką: int **wektory = new int *[2]; wektory[0] = new int [5]; wektory[1] = new int; Przy takiej deklaracji pierwszy wiersz ma 5 elementów (tablica) a drugi to jeden element. Deklaracja tablic o większej ilości wymiarów przebiega podobnie: int ***wektory; // deklarujemy tablicę 3-wymiarową wektory = new int **[5]; // pierwszy wymiar wektory[0] = new int *[10]; // pierwszy element pierwszego wymiaru wektory[1] = new int *[3]; // drugi element pierwszego wymiaru... wektory[0][0] = new int [3] // wymiar I = 0 -> wymiar II = 1 -> 3 elementy(tablica) wektory[0][1] = new int [5] // wymiar I = 0 -> wymiar II = 3 -> 5 elementów(tablica) wektory[1][0] = new int; // wymiar I = 1 -> wymiar II = 2 -> 1 element... Stosując ten sposób, ogólnie można deklarować tablice n-wymiarowe bez większego problemu. Usuwanie tablic wielowymiarowych przebiega podobnie jak jednowymiarowych, z tą różnicą, że usuwanie zaczynamy od "najgłębszego" wymiaru: delete wektory[0][0]; // kasujemy pojedynczą zmienną delete [] wektory[0][1]; delete [] wektory[1][0]; // II wymiar delete [] wektory[0]; delete [] wektory[1]; // I wymiar delete [] wektory; Zwrócić uwagę trzeba na dwie rzeczy: delete [] używamy dla zmiennych tablicowych, a delete dla pojedynczych zmiennych Kolejność zwalniania wymiarów jest odwrotna niż ich tworzenia Drugą zaletą jest fakt, że przy okazji alokacji pamięci możemy wywołać odpowiedni konstruktor inicjując wartości zmiennych obiektu, np. Test *test = new Test(1,2); zakładając, że obiekt Test posiada dwie zmienne typu całkowitego i zdefiniowany konstruktor Test(int,int). Kolejną korzyścią jest możliwość przeciążania. Jednak to już jest temat na inny rozdział. Źródło: http://pl.wikibooks.org/wiki/c%2b%2b/zarządzanie_pamięcią [dostęp: 21 listopada 2011] Programowanie w Języku C2 Laboratorium strona: 7 z 10
C++/Strumienie > C++ > Strumienie» < C++ Spis treści [ukryj] 1 Czym są strumienie? 2 Strumienie "konsoli" 3 Strumienie plikowe 4 Strumienie napisów [edytuj]czym są strumienie? Najprościej mówiąc jest to ciąg bajtów o nieokreślonej długości. Działanie strumieni zademonstruje na przykładzie kranu, za pomocą którego nalewamy wodę do szklanki. Gdy nasz program chce wyświetlić coś na ekranie (lub zapisać coś do pliku) to musi on "przesłać" wodę do kranu. Szklanka (ekran) ma ograniczoną pojemność i pierw trzeba ją opróżnić by przyjąć kolejne porcje. Nie przejmuj się, jeśli nie zrozumiałeś powyższej analogii, wszystko przyjdzie z czasem. Zarządzać strumieniami możemy tak samo, jak w języku C, za pomocą struktur typu FILE i poleceń fopen() i fclose(), lecz daje to małe możliwości, o czym się przekonamy podczas nauki programowania obiektowego. Dlatego w C++ utworzono dużo wygodniejszy mechanizm, z którego już skorzystaliśmy. Wyróżniamy trzy rodzaje strumieni: [edytuj]strumienie "konsoli" Zapewne każdy uważny czytelnik wie już, jak pobierać oraz wyświetlać dane na ekranie konsoli. Dla przypomnienia napiszę. Do wczytywania danych ze strumienia wejścia służy operator >>, a wysyłania danych do strumienia wyjścia służy operator <<. Jednak metody, które do tej pory poznałeś nie zawsze spełnią twoje oczekiwania. Jak myślisz, co wyświetli poniższy program? #include <string> int main () std::string x; std::cout << "Podaj swoje imie i nazwisko: "; std::cin >> x; std::cout << x << std::endl; Prawdopodobnie Cię rozczaruję - wyświetli tylko i wyłącznie imię! Operator >> "wyciąga" pojedyncze słowo oddzielone białymi znakami oraz zapisuje je do zmiennej x. Musimy stworzyć kolejną zmienną typu string i zapisać w niej nazwisko i użyć kaskadowej operacji wstawiania danych do strumienia. Wystarczy dokonać kilka modyfikacji tego programu: zmienić linijkę: std::string x; na: std::string a, b; zmienić linijkę: std::cin >> x; na std::cin >> a >> b; zmienić linijkę: std::cout << x << std::endl; Programowanie w Języku C2 Laboratorium strona: 8 z 10
na std::cout << a << ' ' << b << std::endl; Obiekty tego typu dziedziczą po klasie ostream dla strumieni wyjścia i istream dla wejścia. Plik nagłówkowy iostream sprawia, że mamy od początku otwarte 3 strumienie: std::cin - standardowe wejście std::cout - standardowe wyjście std::cerr - gdy coś złego się stanie (wyjście) [edytuj]strumienie plikowe Program w C++ zapisujący dane do pliku graficznego Za pomocą strumieni możemy czytać i zapisywać do plików: #include <fstream> #include <string> int main() std::string a; std::cout << "Nacisnij Enter aby zakonczyc zapis.\n"; std::ofstream f ("log.txt"); std::cin >> a; if (f.good()) f << a; f.close(); Program zapisuje łańcuch znaków do pliku. Pobiera go do momentu naciśnięcia Enter. [edytuj]strumienie napisów Wyróżniamy jeszcze jeden rodzaj strumieni - stringstream. Dzięki niemu jesteśmy w stanie operować na napisach tak, jak na zwykłym strumieniu. Wyobraźmy sobie sytuację, gdy musimy zamienić liczbę całkowitą na napis. Język C umożliwiał nam dokonywanie takich operacji za pomocą funkcji sprintf() bądź niestandardowej funkcji itoa(). Jednak zaprezentowane poniżej rozwiązanie jest o wiele czytelniejsze. #include <sstream> int main () long x; // Zmienna do przechowania liczby std::string napis; // Zmienna do przechowania napisu std::stringstream ss; // Strumień do napisów std::cout << "Podaj dowolna liczbe calkowita: "; std::cin >> x; Programowanie w Języku C2 Laboratorium strona: 9 z 10
ss << x; // Do strumienia 'wysyłamy' podaną liczbę napis = ss.str(); // Zamieniamy zawartość strumienia na napis std::cout << "Dlugosc napisu wynosi " << napis.size() << " znakow." << std::endl; Źródło: http://pl.wikibooks.org/wiki/c%2b%2b/strumienie [dostęp: 21 listopada 2011] Dostęp do bibliotek języka C Język C++ jest w pełni zgodny wstecz z językiem C. Możliwe jest również używanie bibliotek tego języka. Nie zaleca się jednak używania nazw nagłówków z rozszerzeniem.h. W zamian zalecane jest użycie nagłówka bez rozszerzenia i z przedrostkiem c. Przykłady: C #include <stdio.h> #include <time.h> #include <stdlib.h> C++ #include <cstdio> #include <ctime> #include <cstlib> Zadania Napisz przykład klasy, która będzie reprezentowała obiekt ze świata rzeczywistego i posiadała następujące cechy: 1. posiadała co najmniej 5 pól różnych typów, w tym co najmniej 2 nie widoczne na zewnątrz (1punkt) 2. metodę wypisującą na ekran całą zawartość klasy przy pomocy strumieni (1 punkt) 3. konstruktor ustawiający wartości początkowe pól klasy (1 punkt) 4. napisz metodę ustawiającą jedno z pól ukrytych przypisując mu wartość podaną jako parametr metody, pod warunkiem, że należy do zakresu od 0 do 100. Jeśli warunek ten nie jest spełniony, to wartość pola nie powinna ulec zmianie, a metoda powinna zwrócić wartość false. (1 punkt) 5. obiekt tworzonej klasy należy utworzyć statycznie, a następnie utworzyć kolejny obiekt dynamicznie. (1 punkt) Programowanie w Języku C2 Laboratorium strona: 10 z 10