Literatura: Pola Metody
Transkrypt
Literatura: Pola Metody
Klasy = pola + metody + specyfikatory dostępu Definicja klasy class nazwa_klasy //nagłówek klasy { W treści klasy wymienia się składowe klasy (pola //treść klasy i metody) oraz określa się sposób dostępu do nich. }; Każda definicja klasy wprowadza do programu nowy typ danych, żeby z niego skorzystać należy zdefiniować zmienną tego typu, którą nazywamy obiektem. Definicja obiektu: nazwa_klasy nazwa_obiektu; //o ile brak w klasie konstruktora lub klasa posiada konstruktor domyślny nazwa_klasy nazwa_obiektu(lista argumentów aktualnych konstruktora); W momencie definicji obiektu klasy, definicja klasy musi być znana. Wykład 1 Literatura: Bjarne Stroustrup Język C++. Jerzy Grębosz Symfonia C++. Jerzy Grębosz Pasja C++. Bruce Eckel Thinking in C++ edycja polska. Josee Liberty C++ dla każdego. Stanley B. Lippman Istota języka C++. Stanley B. Lippman Josee Lajoie Podstawy języka C++. 1 Pola Pola klasy deklaruje się tak samo jak zmienne. Pola klasy mogą być dowolnego typu (w tym też typu zdefiniowanego przez użytkownika). Jeśli Stack pierwszy; Przykład Stack *wsk_drugi=&pierwszy; Stack &ref_trzeci=pierwszy; const MAX=10; class Stack to do pola top obiektu pierwszy { możemy odwołać się (o ile jest to int table[MAX]; możliwe) w następujący sposób: int top; }; pierwszy.top wsk_drugi->top (*wsk_drugi).top 3 ref_trzeci.top Czasami wystarczy klasę zadeklarować. Deklaracja klasy to nagłówek klasy zakończony średnikiem class nazwa_klasy; Definiujemy raz, deklarować możemy wiele razy. 2 Metody Metody klasy - funkcje określające to co z obiektem reprezentującym klasę można zrobić. Metody deklaruje się w treści klasy. Deklaracja metody wygląda tak jak deklaracja zwykłej funkcji. const MAX=10; class Stack { void push (int); int pop ( ); void print ( ); int count ( ); int table[MAX]; int top; }; Jeśli Stack pierwszy; Stack *wsk_drugi=&pierwszy; Stack &ref_trzeci=pierwszy; to wywołanie metody push, o ile będzie to możliwe, dla obiektu pierwszy można wykonać następująco: pierwszy.push(10); wsk_drugi->push(10); (*wsk_drugi).push(10); ref_trzeci.push(10); 4 Specyfikatory dostępu Etykieta private poprzedza składowe prywatne (tzw. implementacja klasy), które są dostępne w metodach klasy oraz w funkcjach, metodach i klasach zaprzyjaźnionych. W sekcji prywatnej ukrywa się najbardziej wrażliwe składowe klasy, zwykle pola. Możliwość dostępu do składowych zależy od miejsca zadeklarowania składowych w klasie. Klasę można podzielić na następujące sekcje: publiczna (zaczyna się od public:), prywatna (zaczyna się od private:) chroniona (zaczyna się od protected:). Etykieta protected poprzedza składowe chronione, które są dostępne tak jak składowe prywatne, a dodatkowo są dostępne w klasach wywodzących (w klasach pochodnych) się z tej klasy. Etykiety public, private, protected można umieszczać w dowolnej kolejności, mogą też się powtarzać. Etykieta public poprzedza składowe publiczne (tzw. interfejs klasy), które są dostępne w każdym miejscu programu. Najczęściej sekcja publiczna zawiera metody określające ogólnie dostępne operacje wykonywane na obiektach klasy w programie. Zakłada się, że dopóki w definicji klasy nie wystąpi żadna z tych etykiet, to składniki przez domniemanie są poprzedzane etykietą private. 5 Dodamy do klasy Stack etykiety: Kolejność deklarowania pól i metod jest class Stack dowolna, ale pamiętać należy o { przejrzystości kodu. public: void push (int); Z samą definicją klasy nie jest związany int pop ( ); żaden obszar w pamięci z którego można void print ( ); korzystać w programie. Dopiero definicja int count ( ); obiektu klasy powoduje przydzielenie pamięci. private: int main( ) int table[MAX]; { int top; Stack pierwszy; }; pierwszy.top=10; //błąd, brak dostępu pierwszy.table[2]=23; //błąd j.w. pierwszy.print( ); return 0; 7 } 6 Mając definicję klasy Stack uda nam się skompilować poniższy program int main( ) int main( ) { { Stack pierwszy, Stack pierwszy; drugi; pierwszy.print( ); pierwszy.print( ); Stack drugi; drugi.print( ); drugi.print( ); return 0; return 0; } } Nie będziemy go mogli jednak uruchomić, gdyż brakuje definicji metody print. Każdy obiekt klasy ma własne egzemplarze pól klasy, natomiast istnieje tylko jeden egzemplarz metod klasy. Jak to dzieje się, że mimo, że mamy jeden egzemplarz metody, metoda ta pracuje na danym konkretnym obiekcie? Każda metoda posiada niejawny argument, stały wskaźnik this do 8 obiektu na rzecz którego ma pracować. Treść metody print wygląda następująco: Definicja metod klasy. Implementacja metod klasy. Metodę możemy zdefiniować za treścią klasy, w której została zadeklarowana, wtedy nazwę metody należy kwalifikować nazwą klasy do której należy. Metoda print void Stack::print( ) { if (top>0) for (int i=top-1; i>=0; i--) cout<<"[ "<<table[i]<<" ]"<<endl; else cout<<"*** stos pusty ***"<<endl; cout<<endl; } void Stack::print ( ) { if (this->top>0) for (int i=this->top-1;i>=0;i--) cout<<"[ "<<this->table[i]<<" ]"<<endl; else cout<<"*** stos pusty ***"<<endl; cout<<endl; } Wskaźnik this sprawia, że operacje przeprowadzane są na składowych tego (this) konkretnego obiektu, dla którego tę metodę wywołujemy. 9 Wskaźnik this pojawia się w definicjach metod przed każdą nazwą składowej klasy. Można go dopisywać ręcznie, ale kompilator oszczędza nasz czas i wstawia go sam. W momencie wywołania funkcji oprócz swoich argumentów metoda otrzymuje w pierwszym niejawnym argumencie wskaźnik do obiektu na rzecz którego ma pracować, jest to 10 stały wskaźnik o nazwie this. Program Metody definiować możemy też w treści klasy, o ile treść metody zawiera co najwyżej dwie linijki. int main( ) { Stack pierwszy; pierwszy.print( ); Stack drugi; drugi.print( ); return 0; } Definicja i deklaracja metody count( ) class Stack { public: inline int count ( ) {return top;} //... }; Metody definiowane w treści klasy automatycznie otrzymują atrybut inline Może działać dziwnie, gdyż pola obiektów nie zostały zainicjowane. Chcielibyśmy nadać wartość zero składowej top. W miejscu wywołania metody inline może zostać wstawiana jej treść. 11 12 W treści klasy nie można składowym nie statycznym przypisywać wartości początkowych class Stack { //... private: [C++ Error] Cannot initialize a class member here int top=0; }; Sposób znany nam ze struktur też nie jest dobrym rozwiązaniem jako, że pola które chcemy zainicjować należą do części prywatnej klasy. int main( ) { Stack pierwszy={{0},0}; //... return 0; } [C++ Error] Expression syntax 13 Dwa alternatywne sposoby definicji konstruktora. Konstruktor z definicją Konstruktor z definicją za treścią klasy: w treści klasy: class Stack class Stack { { public: public: Stack( ); //deklaracja Stack( ) {top=0;} //... //... }; }; Konstruktor Stack( ) Stack::Stack ( ) //definicja automatycznie zostanie { wywołany w liniach: top=0; Stack pierwszy; } Stack pierwszy=Stack( ); Stack *Wsk=new Stack; Stack tablica[10]; Konstruktor, który można wywołać bez argumentów nazywamy 15 konstruktorem domyślnym. Idealnie byłoby żeby w momencie definicji obiektu klasy nadać mu wartość początkową. Do tego służy specjalna metoda zwana konstruktorem. Konstruktor klasy. •Konstruktor jest specjalną metodą, której głównym zadaniem jest nadanie wartości początkowych składowym obiektu (polom) klasy. •Konstruktor nazywa się tak samo jak klasa do której należy. •Konstruktor nie zwraca żadnej wartości (nawet typu void). •Klasa może zawierać więcej niż jeden konstruktor, bądź może nie mieć ich wcale. •Konstruktor wywoływany jest automatycznie w momencie definicji obiektu klasy, o ile konstruktor został zdefiniowany w części publicznej klasy. •Konstruktor tak jak wszystkie metody klasy musi zostać zadeklarowany w treści klasy, a zdefiniowany za treścią klasy lub w treści klasy (otrzymuje wtedy atrybut inline). •Konstruktor nie może zostać wykonywany na rzecz konkretnego 14 obiektu, bo ten obiekt jeszcze nie istnieje. Po tych modyfikacjach int main( ) { Stack pierwszy; // pracuje konstruktor pierwszy.print( ); Stack drugi; // pracuje konstruktor drugi.print( ); return 0; } na ekranie zobaczymy *** stos pusty *** *** stos pusty *** 16 Nazwę konstruktora można przeciążać. class Stack { public: Stack( ); Stack::Stack(int t) //definicja za klasą Stack(int t); //deklaracja { //... top=t; }; if (t<0) top=0; else if (t>MAX) top=MAX; for (int i=0; i<top; i++) table[i]=i; } 17 Konstruktor tak jak inne metody klasy może posiadać argumenty domyślne. W naszym przypadku dwa konstruktory można zastąpić jednym, z argumentem domyślnym o wartości 0. class Stack Wykład 2 { public: Stack(int t=0); //konstruktor domyślny, deklaracja //... Stack::Stack(int t) }; { top=t; if (t<0) top=0; else if (t>MAX) top=MAX; for (int i=0; i<top; i++) table[i]=i; 19 } Automatyczne wywołanie konstruktora z jednym argumentem będzie się dokonywało w liniach: Stack drugi(3); Stack drugi=3; Stack drugi=Stack(3); Stack *wsk=new Stack(3); Stack tablica[10]={Stack(2), Stack(4)}; A program int main( ) { Stack pierwszy; pierwszy.print( ); Stack drugi(3); drugi.print( ); return 0; } Wyprodukuje następujące wyniki *** stos pusty *** [2] [1] [0] 18 Destruktor – specjalna metoda, która może "coś" zrobić tuż przed "zniszczeniem" obiektu. Na przykład, gdy: * konstruktor klasy zarezerwował pamięć operatorem new, to dobrze byłoby żeby w destruktorze ta pamięć została zwolniona za pomocą delete, * zliczamy obiekty klasy, to w destruktorze licznik powinien się zmniejszać o 1. * gdy jest mierzony czas pracy z obiektem. Destruktor nazywa się tak samo jak klasa, tylko że przed nazwą umieszczamy znak ~. Destruktor nie może zwracać żadnej wartości (nawet typu void). Destruktor nie może mieć argumentów, tak więc nie można przeciążać 20 nazwy destruktora. W przypadku klasy Stack destruktor będzie wypisywał tylko informacje na ekranie. Destruktor tak jak wszystkie metody klasy musi zostać zadeklarowany w treści klasy, a zdefiniowany za treścią klasy lub w treści klasy (tzn. może mieć atrybut inline). Destruktor obiektu jest wywoływany automatycznie, w momencie, gdy obiekt wychodzi ze swojego zasięgu, lub gdy został użyty operator delete. class Stack { public: Stack(int t=0); ~Stack( ); //destruktor, deklaracja //... }; Destruktor może zostać wywołany na rzecz obiektu. Stack::~Stack( ) //destruktor, definicja { cout<<"Destruktor stosu:"<<endl; print( ); } Zaleca się umieszczanie obok konstruktora, destruktora nawet gdy nic nie robi. 21 Natomiast po wykonaniu programu int main( ) { Na ekranie zobaczymy { Stack pierwszy; *** stos pusty *** pierwszy.print( ); Stack drugi(3); [2] drugi.print( ); [1] } [0] return 0; } Destruktor stosu: [2] [1] [0] Wtedy po wykonaniu programu int main( ) { Stack pierwszy; pierwszy.print( ); Stack drugi(3); drugi.print( ); return 0; } Na ekranie zobaczymy *** stos pusty *** [2] [1] [0] 22 23 Destruktor stosu: *** stos pusty *** 24 Przyjaciel klasy – np. funkcja, która nie jest składową klasy, ale może korzystać ze składowych prywatnych klasy z którą się przyjaźni. Deklarację przyjaźni umieszcza się w dowolnym miejscu klasy. class Stack { public: Stack(int=0); ~Stack( ); void push (int); int pop ( ); void print ( ); int count ( ) {return top;} private: int table[MAX]; int top; friend void info(Stack &); }; void info(Stack & arg) { if (arg.top==0) cout<<"Stos jest pusty"<<endl; else if (arg.top==MAX) cout<<"Stos jest pelny"<<endl; else cout<<"Na stosie zostalo: " <<(MAX-arg.top)*sizeof(int)<<" B"<<endl; } Przyjaciela klasy możemy zdefiniować w miejscu deklaracji, ale lepiej tego unikać, ponieważ przyjaciel nie jest składową klasy. Zwykle definiuje się go poza treścią klasy. 25 Wtedy po wykonaniu programu int main( ) { Stack pierwszy; pierwszy.print( ); info(pierwszy); Stack drugi(3); drugi.print( ); info(drugi); return 0; } Na ekranie zobaczymy Cały program: *** stos pusty *** Stos jest pusty [2] [1] [0] Na stosie zostalo: 28 B 26 27 #include <iostream.h> const MAX=10; class Stack { public: Stack(int t=0); ~Stack( ); void push (int); int pop ( ); void print ( ); int count( ) {return top;} private: int table[MAX]; int top; friend void info(Stack &); }; int main( ) { Stack pierwszy; pierwszy.print( ); info(pierwszy); Stack drugi(3); drugi.print( ); info(drugi); return 0; } 28 Stack::Stack(int t) { top=t; if (t<0) top=0; else if (t>MAX) top=MAX; for (int i=0; i<top;i++) table[i]=i; } void Stack::print( ) { if (top>0) for (int i=top-1;i>=0;i--) cout<<"[ "<<table[i]<<" ]"<<endl; else cout<<"*** stos pusty ***"<<endl; cout<<endl; } void Stack::push(int element) { if (top<MAX) table[top++]=element; } Stack::~Stack( ) { cout<<"Destruktor stosu:"<<endl; print( ); } 29 int Stack::pop( ) { int element=0; if (top>0) element=table[--top]; return element; } void info(Stack & arg) { if (arg.top==0) cout<<"Stos jest pusty"<<endl; else if (arg.top==MAX) cout<<"Stos jest pelny"<<endl; else cout<<"Na stosie zostalo: " <<(MAX-arg.top)*sizeof(int)<<" B"<<endl; } ///koniec 30 Konstruktor ciąg dalszy. Zajmiemy się klasą: class Pacjent { public: Pacjent (int _wiek, double _waga, double _wzrost); void wyswietl ( ); private: int wiek; double waga; double wzrost; double wspolczynnik; double oblicz ( ) { return waga/(wzrost*wzrost); } }; 31 32 Konstruktor z listą inicjalizującą (z listą inicjalizacji składowych). składowych). Lista inicjalizująca pojawia się tylko w definicji konstruktora. Rozpoczyna się od : który stawiamy po nawiasie ) i kończy w momencie otworzenia klamerki {. Definicje metod klasy Pacjent mogą wyglądać następująco: Pacjent::Pacjent (int _wiek, double _waga, double _wzrost) { wiek=_wiek; waga=_waga; wzrost=_wzrost; wspolczynnik=oblicz ( ); //ważna kolejność } Pacjent::Pacjent(int _wiek, double _waga, double _wzrost) : wiek (_wiek), waga (_waga), wzrost (_wzrost) { wspolczynnik=oblicz( ); } void Pacjent::wyswietl ( ) { cout<<"Waga="<<waga<<" kg"<<endl <<"Wzrost="<<wzrost<<" m"<<endl <<"Wspolczynnik="<<wspolczynnik; } 33 • Składowa listy inicjalizującej to nazwa pola klasy dla którego w nawiasach okrągłych podajemy wartość początkową składowej. • Składowe listy inicjalizującej oddzielamy przecinkami. • Na liście inicjalizującej składową klasy możemy inicjować tylko raz. • Kolejność składowych listy inicjalizującej jest dowolna, kompilator 34 i tak wywołuje je zgodnie z kolejnością definicji w klasie. Wykonanie konstruktora składa się z dwóch etapów: – nadanie wartości początkowych polom klasy znajdujących się na liście inicjalizującej konstruktora (inicjalizacja jawna lub niejawna). – wykonywanie treści konstruktora – faza obliczeń (przypisań). Pacjent::Pacjent (int _wiek, double _waga, double _wzrost) : wiek (_wiek), waga (_waga), wzrost (_wzrost), wspolczynnik (oblicz ( )) {} Inicjalizacja a przypisanie. Inicjalizacja jest operacją jednorazową w całym cyklu istnienia obiektu (zmiennej), wykonywaną natychmiast po przydzieleniu zmiennej potrzebnego obszaru pamięci i polega na nadaniu tej zmiennej w tym szczególnym momencie pewnej wartości początkowej. Faza przydzielania pamięci oraz inicjalizowania odbywa się zanim zmienna ta będzie mogła zostać rzeczywiście użyta. Pacjent::Pacjent (int _wiek, double _waga, double _wzrost) : wiek (_wiek), waga (_waga), wzrost ((_wzrost>3)? _wzrost/100:_wzrost), wspolczynnik (oblicz ( )) {} Wartość początkowa pojawiająca się w nawiasach może być wielkością stałą, argumentem definiowanego konstruktora, wyrażeniem, a nawet metodą klasy lub funkcją spoza klasy. 35 Przypisanie polega na zmodyfikowaniu wartości zmiennej; operacja ta, w całym okresie istnienia zmiennej, może być wykonywana dowolną ilość razy. 36 Konstruktor z listą inicjalizacji składowych zdefiniowany w treści klasy Program testujący działanie metod klasy: class Pacjent { public: Pacjent (int _wiek, double _waga, double _wzrost) : wiek(_wiek), waga(_waga), wzrost((_wzrost>3)? _wzrost/100:_wzrost), wspolczynnik(oblicz( )) { } void wyswietl ( ); int main( ) { Pacjent A(18,80,180); A.wyswietl( ); return 0; } Na ekranie zobaczymy: private: int wiek; double waga; double wzrost; double wspolczynnik; double oblicz ( ) {return waga/(wzrost*wzrost);} }; Waga=80 kg Wzrost=1.8 m Wspolczynnik=24.6914 37 Cały program: #include <iostream.h> class Pacjent { public: Pacjent (int _wiek, double _waga, double _wzrost) : wiek(_wiek), waga(_waga), wzrost((_wzrost>3)? _wzrost/100:_wzrost), wspolczynnik(oblicz( )) { } void wyswietl ( ); private: int wiek; double waga; double wzrost; double wspolczynnik; double oblicz ( ) {return waga/(wzrost*wzrost);} }; 38 int main( ) { Pacjent A(18,80,180); A.wyswietl( ); return 0; } void Pacjent::wyswietl ( ) { cout<<"Waga="<<waga<<" kg"<<endl <<"Wzrost="<<wzrost<<" m"<<endl <<"Wspolczynnik="<<wspolczynnik; } // koniec programu 39 40 W praktyce plik nagłówkowy może zawierać: Program możemy podzielić na moduły (pliki). W naszym przypadku, mogą to być następujące moduły: Pacjent.h - plik nagłówkowy z definicją klasy, Pacjent.cpp - plik z implementacją metod klasy Pacjent, test.cpp - plik z funkcją main. Deklaracje zmiennych extern int liczba; Deklaracje funkcji Definicje funkcji inline [extern] int suma(int, int); inline int suma(int a, int b) {return a+b;} const double pi=3.141593; Definicje stałych Definicje typów Tylko jeden plik w programie wielomodułowym może zawierać funkcję main. Deklaracja typu Makrodefinicje class Punkt { double x,y; }; class Punkt; #define WERSJA 2 Dyrektywy kompilacji warunkowej #ifdef _cplusplus Wyliczenia enum kolor {niebieski, czarny}; 41 #ifndef PACJENT_H #define PACJENT_H Plik Pacjent.h class Pacjent { public: Pacjent (int _wiek, double _waga, double _wzrost) : wiek(_wiek), waga(_waga), wzrost((_wzrost>3)? _wzrost/100:_wzrost), wspolczynnik(oblicz( )) { } void wyswietl ( ); private: int wiek; double waga; double wzrost; double wspolczynnik; double oblicz ( ) {return waga/(wzrost*wzrost);} }; 43 #endif 42 Dyrektywa preprocesora define (bez argumentów). #define NAZWA ciąg znaków // bez ; Każde wystąpienie w programie słowa NAZWA jest zastępowane przez ciąg znaków Przykład: #define BEGIN { #define END } W programie możemy napisać: Kompilator widzi rozwiniętą postać makrodefinicji if (a>0) BEGIN a++; b++; END if (a>0) { a++; b++; } 44 Dyrektywy kompilacji warunkowej #ifdef (#if (#if define), define), #ifndef (#if (#if !define), define), #endif Plik Pacjent.cpp #include <iostream.h> #include "Pacjent.h" #ifndef NAZWA //linie kodu #endif Jeśli NAZWA nie została zdefiniowana za pomocą dyrektywy define to wykonywane są linie kodu. #ifdef NAZWA //linie kodu #endif Jeśli NAZWA została zdefiniowana za pomocą dyrektywy define to wykonywane są linie kodu. 45 Plik test.cpp #include "Pacjent.h" int main( ) { Pacjent A(18,80,180); A.wyswietl( ); return 0; } 47 void Pacjent::wyswietl ( ) { cout<<"Waga="<<waga<<" kg"<<endl <<"Wzrost="<<wzrost<<" m"<<endl <<"Wspolczynnik="<<wspolczynnik; } 46