Rok akademicki 2016/2017, Wykład nr 4 2/45 Plan wykładu nr 4 Informatyka 2 Politechnika Białostocka - Wydział Elektryczny Elektrotechnika, semestr III, studia stacjonarne I stopnia Rok akademicki 2016/2017 Programowanie obiektowe w języku C++ definicja klasy, składniki klasy: dane i funkcje prawa dostępu do składników klasy definiowanie funkcji składowych wewnątrz i poza klasą wskaźnik this klasy a pliki nagłówkowe funkcje zaprzyjaźnione z klasą konstruktor (domyślny, kopiujący) i destruktor dynamiczny przydział pamięci w C++ Wykład nr 4 (09.11.2016) Rok akademicki 2016/2017, Wykład nr 4 3/45 Rok akademicki 2016/2017, Wykład nr 4 4/45 Definicja klasy Składniki klasy - dane dane (dane składowe, pola, składniki) - oznaczają to samo co pola w strukturach zmienne typu nazwa_typu nazywa się obiektami utworzenie obiektu wymaga, podobnie jak przy deklaracji innych zmiennych, podania nazwy typu i nazwy obiektu: nazwa_typu x; - deklaracja obiektu x klasy nazwa_typu nazwa_typu *y; - deklaracja wskaźnika y do obiektów typu nazwa_typu ; do danych w klasie odwołujemy się w taki sam sposób jak do pól struktury: obiekt.dana wskaźnik->dana osoba x; x.wiek = 15; osoba *y; y = &x; mówimy: y->wiek = 20; x jest obiektem klasy osoba y jest wskaźnikiem do obiektu klasy osoba
Rok akademicki 2016/2017, Wykład nr 4 5/45 Rok akademicki 2016/2017, Wykład nr 4 6/45 Składniki klasy - funkcje Składniki klasy - funkcje funkcje (funkcje składowe, metody) - są to funkcje operujące na danych składowych klasy funkcje (funkcje składowe, metody) - są to funkcje operujące na danych składowych klasy void zapisz(char *i, char *n, int w); ; mówimy: wywołanie funkcji zapisz na rzecz obiektu x klasy osoba void zapisz(char *i, char *n, int w); ; do funkcji w klasie odwołujemy się w taki sam sposób jak do jej danych: obiekt.funkcja(argumenty) wskaźnik->funkcja(argumenty) osoba x; x.zapisz("jan","nowak",30); osoba *y = &x; y->zapisz("adam","nowak",25); deklaracje danych i funkcji mogą być umieszczane w klasie w dowolnej kolejności niezależnie od miejsca zdefiniowania składnika wewnątrz klasy - składnik znany jest w całej definicji klasy Rok akademicki 2016/2017, Wykład nr 4 7/45 Rok akademicki 2016/2017, Wykład nr 4 8/45 Prawa dostępu do składników klasy private (prywatne) oznacza, że funkcje i dane klasy dostępne są tylko z wnętrza klasy dla danych oznacza to, że tylko funkcje będące składnikami klasy (oraz funkcje zaprzyjaźnione) mogą te dane odczytywać lub do nich coś zapisywać dla funkcji oznacza to, że mogą one zostać wywołane tylko przez inne funkcje składowe tej klasy (oraz funkcje zaprzyjaźnione) public (publiczne) komponenty publiczne są ogólnie dostępne, można się do nich odwoływać z wnętrza klasy lub spoza klasy protected (zabezpieczone) dostęp jest taki sam jak dla private, ale dodatkowo są one dostępne dla klas wywodzących się od tej klasy (dziedziczenie) Prawa dostępu do składników klasy etykiety private, public, protected można umieszczać w dowolnej kolejności, mogą one powtarzać się int ocena; void zapisz(char *i, char *n); float wzrost; float waga; void drukuj(); ; domyślnie (bez podania praw dostępu) wszystkie składowe są prywatne funkcje składowe klasy mają dostęp do wszystkich jej danych i funkcji (niezależnie od praw dostępu)
Rok akademicki 2016/2017, Wykład nr 4 9/45 Rok akademicki 2016/2017, Wykład nr 4 10/45 Definiowanie funkcji składowych klasy Definiowanie funkcji składowych wewnątrz klasy class nazwa typ funkcja(parametry) kod ; class nazwa typ funkcja(parametry); ; typ nazwa::funkcja(parametry) kod wewnątrz klasy deklaracja i definicja funkcji poza klasą deklaracja funkcji definicja funkcji void zapisz(char *i, char *n, int w) void drukuj(void) ; funkcja zdefiniowana wewnątrz klasy jest funkcją inline podczas kompilacji, w miejscu wywołania funkcji, wstawiany jest jej kod ciało funkcji wewnątrz klasy nie powinno mieć więcej niż dwie/trzy linijki kodu częste wywołania długich funkcji mogą prowadzić do dużego wzrostu wielkości pliku wynikowego Rok akademicki 2016/2017, Wykład nr 4 11/45 Rok akademicki 2016/2017, Wykład nr 4 12/45 Definiowanie funkcji składowych poza klasą void zapisz(char *i, char *n, int w); void drukuj(void); ; deklaracje funkcji void osoba::zapisz(char *i, char *n, int w) :: - operator zakresu, pokazuje do jakiej klasy należy funkcja void osoba::drukuj(void) Przykład nr 1 (1/2) #include <iostream> #include <cstring> using namespace std; void zapisz(char *i, char *n, int w); void drukuj(); ; void osoba::zapisz(char *i, char *n, int w)
Rok akademicki 2016/2017, Wykład nr 4 13/45 Rok akademicki 2016/2017, Wykład nr 4 14/45 Przykład nr 1 (2/2) Wskaźnik this void osoba::drukuj() Jan Kowalski 30 Anna Nowak 25 funkcje wywoływane są zawsze na rzecz konkretnego obiektu do wnętrza funkcji przekazywany jest niejawnie wskaźnik do tego obiektu - tym adresem funkcja inicjalizuje swój wskaźnik zwany this w rzeczywistości funkcja drukuj(): osoba os1, os2; os1.zapisz("jan","kowalski",30); os2.zapisz("anna","nowak",25); void osoba::drukuj() os1.drukuj(); os2.drukuj(); ma następującą postać: return 0; void osoba::drukuj() cout << this->imie << " " << this->nazwisko; cout << " " << this->wiek << endl; Rok akademicki 2016/2017, Wykład nr 4 15/45 Rok akademicki 2016/2017, Wykład nr 4 16/45 Obiekty w pamięci komputera Statyczne składniki klasy definicja klasy nie definiuje obiektu, a więc nie przydziela pamięci void zapisz(char *i,char *n,int w); void drukuj(); ; osoba os1, os2;... definiując kilka obiektów danej klasy w pamięci przydzielane jest miejsce dla wszystkich danych, natomiast funkcje są w pamięci tylko jeden raz w definicji klasy nie można inicjować danych static int semestr; ; int osoba::semestr = 3; osoba::semestr = 4; osoba os1, os2; os1.semestr = 5;... statyczne dane składowe klasy są wspólne dla wszystkich obiektów danej klasy pamięć na składnik jest przydzielana jednokrotnie, nawet jeśli nie ma żadnego obiektu tej klasy każdy składnik statyczny musi być ponownie zdefiniowany w zakresie globalnym Zastosowanie: licznik obiektów danej klasy licznik wywołań funkcji składowej klasy przez wszystkie jej obiekty zdefiniowanie składnika o takiej samej wartości dla wszystkich obiektów klasy
Rok akademicki 2016/2017, Wykład nr 4 17/45 Rok akademicki 2016/2017, Wykład nr 4 18/45 Modyfikator const Modyfikator volatile ; void zapisz(char *i,char *n,int w); void drukuj() const; void osoba::drukuj() const modyfikator const zapewnia, że funkcja nie będzie mogła zmieniać wartości danych składowych klasy wskaźnik this przekazywany do takiej funkcji traktowany jest jako stały modyfikator const umieszcza się w deklaracji i definicji funkcji modyfikator volatile (ang. ulotny) oznacza, że obiekt tak określony może zmienić się w sposób niezauważalny dla kompilatora volatile float temperatura; każde odwołanie do tego obiektu powinno prowadzić do odczytania komórek pamięci przydzielonych temu obiektowi kompilator nie powinien wykonywać żadnych optymalizacji związanych z tym obiektem modyfikator volatile może pojawić się także w nagłówku funkcji int function() volatile; funkcja taka może być wywołana tylko na rzecz obiektu zadeklarowanego jako volatile Rok akademicki 2016/2017, Wykład nr 4 19/45 Rok akademicki 2016/2017, Wykład nr 4 20/45 Klasy a pliki nagłówkowe Klasy a pliki nagłówkowe zazwyczaj definicje klas umieszcza się w plikach nagłówkowych *.h, natomiast definicje funkcji składowych w plikach *.cpp pliki *.h dołącza się do plików *.cpp dyrektywą #include "nazwa.h" w plikach nagłówkowych nie mogą występować definicje funkcji, chyba, że są one umieszczone bezpośrednio w klasie osoba.h #include <iostream> #include <cstring> using namespace std; void zapisz(char *i, char *n, int w); void drukuj(); ;
Rok akademicki 2016/2017, Wykład nr 4 21/45 Rok akademicki 2016/2017, Wykład nr 4 22/45 Klasy a pliki nagłówkowe Klasy a pliki nagłówkowe w pliku osoba.cpp umieszczone są definicje funkcji składowych klasy osoba klasę osoba wykorzystujemy w pliku main.cpp osoba.cpp #include "osoba.h" void osoba::zapisz(char *i, char *n, int w) void osoba::drukuj() main.cpp #include "osoba.h" osoba os1, os2; os1.zapisz("jan","kowalski",30); os2.zapisz("anna","nowak",25); os1.drukuj(); os2.drukuj(); return 0; Rok akademicki 2016/2017, Wykład nr 4 23/45 Rok akademicki 2016/2017, Wykład nr 4 24/45 Klasy a pliki nagłówkowe Funkcje zaprzyjaźnione z klasą w celu uniknięcia wielokrotnego dołączania tego samego pliku nagłówkowego stosuje się dyrektywy kompilacji warunkowej: funkcja zaprzyjaźniona z klasą to funkcja, która nie będąc składnikiem klasy ma dostęp do wszystkich (także prywatnych) składników klasy osoba.h #ifndef OSOBA_H_ #define OSOBA_H_ #include <iostream> #include <cstring> using namespace std;... ; #endif void zapisz(char *i,char *n,int w); friend void funkcja(osoba Nowak); ; void funkcja(osoba Nowak) Nowak.wiek = 20; funkcja może przyjaźnić się z więcej niż jedną klasą nie ma znaczenia, w którym miejscu w klasie pojawia się deklaracja przyjaźni (sekcja private, protected, public) funkcja zaprzyjaźniona może być funkcją składową innej klasy przyjaźń nie jest dziedziczona
Rok akademicki 2016/2017, Wykład nr 4 25/45 Rok akademicki 2016/2017, Wykład nr 4 26/45 Konstruktor Konstruktor osoba(char *i, char *n, int w); void drukuj(void); ; osoba::osoba(char *i,char *n, int w) konstruktor służy do nadania wartości początkowych obiektowi i dynamicznego przydzielenia pamięci wywoływany jest bezpośrednio po utworzeniu obiektu nazwa konstruktora jest taka sama jak nazwa klasy dla konstruktora nie określamy typu zwracanej wartości (nie może tam wystąpić nawet void) może mieć kilka wariantów czyli różną liczbę parametrów (przeładowanie/przeciążenie) osoba(char *i, char *n, int w); void drukuj(void); ; osoba::osoba(char *i,char *n,int w) bez konstruktora: osoba os1; os1.zapisz("jan","kos",30); z konstruktorem: osoba os1("jan","kos",30); lub osoba os1=osoba("jan","kos",30); Rok akademicki 2016/2017, Wykład nr 4 27/45 Rok akademicki 2016/2017, Wykład nr 4 28/45 Przeładowanie nazw funkcji Przeładowanie nazw funkcji przeładowanie nazw funkcji polega na tym, że w danym zakresie ważności jest więcej niż jedna funkcja o tej samej nazwie: int oblicz(int x); int oblicz(int x, int y); int oblicz(int x, double y); w rzeczywistości przy przeładowaniu kompilator zmienia nazwy funkcji int oblicz(int x); oblicz_fi int oblicz(int x, int y); oblicz_fii int oblicz(int x, double y); oblicz_fid to, która funkcja zostanie w danym przypadku uaktywniona, zależy od liczby i typu argumentów przy przeładowaniu ważna jest tylko odmienność listy argumentów, natomiast typ zwracany przez funkcję nie jest brany pod uwagę int oblicz(int x); float oblicz(int x); // błąd!!!
Rok akademicki 2016/2017, Wykład nr 4 29/45 Rok akademicki 2016/2017, Wykład nr 4 30/45 Konstruktor domniemany Konstruktor kopiujący char imie[20], nazwisko[30]; osoba(char *i, char *n, int w); osoba(); ; osoba::osoba(char *i,char *n,int w) osoba::osoba() strcpy(imie,""); strcpy(nazwisko,""); wiek = 0; jest to konstruktor, który można wywołać bez żadnego argumentu osoba os1; lub osoba os1 = osoba(); jeśli w klasie nie ma takiego konstruktora, to kompilator sam go wygeneruje kompilator generuje konstruktor domniemany tylko wtedy, gdy w programie nie ma zdefiniowanego żadnego innego konstruktora konstruktor kopiujący służy do skonstruowania obiektu, który jest kopią innego, już istniejącego obiektu tej klasy: klasa::klasa(klasa &); klasa::klasa(const klasa &); konstruktor kopiujący nie jest obowiązkowy, jeśli go nie zdefiniujemy wtedy kompilator wygeneruje go sobie sam (kopiowanie składnik po składniku ) konstruktor kopiujący możemy sami wywołać, np. klasa obiekt1; klasa obiekt2 = obiekt1; Rok akademicki 2016/2017, Wykład nr 4 31/45 Rok akademicki 2016/2017, Wykład nr 4 32/45 Konstruktor kopiujący Destruktor konstruktor kopiujący może być wywołany niejawnie: podczas przesyłania argumentów do funkcji - jeśli argumentem funkcji jest obiekt klasy, a przesyłanie odbywa się przez wartość podczas, gdy funkcja jako swój wynik, zwraca (przez wartość) obiekt danej klasy konstruktor kopiujący jest niezbędny, gdy daną składową w klasie jest wskaźnik osoba(char *i, char *n, int w); ~osoba(void); void drukuj(void); ; osoba::~osoba()... destruktor jest wywoływany bezpośrednio przed zniszczeniem obiektu (destruktor można wywołać jawnie - nie spowoduje on jednak usunięcia obiektu) jego nazwa jest taka sama jak nazwa klasy, ale przed jego nazwą umieszcza się znak ~ zadaniem destruktora jest posprzątanie po obiekcie, np. zwolnienie pamięci dla destruktora nie określamy typu zwracanej wartości
Rok akademicki 2016/2017, Wykład nr 4 33/45 Rok akademicki 2016/2017, Wykład nr 4 34/45 Przykład nr 2 - konstruktor i destruktor (1/2) Przykład nr 2 - konstruktor i destruktor (2/2) #include <iostream> #include <cstring> #pragma warning (disable:4996) using namespace std; class test char name[15]; test(char *n) strcpy(name,n); cout << "Konstruktor obiektu: " << name << endl; ~test() cout << "Destruktor obiektu: " << name << endl; ; test t1("t1"), t2("t2"); system("pause"); test t3("t3"); system("pause"); system("pause"); return 0; Konstruktor obiektu: t1 Konstruktor obiektu: t2 Aby kontynuować, naciśnij... Konstruktor obiektu: t3 Aby kontynuować, naciśnij... Destruktor obiektu: t3 Aby kontynuować, naciśnij... Destruktor obiektu: t2 Destruktor obiektu: t1 Aby kontynuować, naciśnij... Rok akademicki 2016/2017, Wykład nr 4 35/45 Rok akademicki 2016/2017, Wykład nr 4 36/45 Lista inicjalizacyjna konstruktora Dynamiczny przydział pamięci w języku C++ class abc int a, b, c; abc(int aa, int bb, int cc); ; abc::abc(int aa, int bb, int cc): a(aa), b(bb) c = cc; do dynamicznego przydziału pamięci w języku C++ służy operator new operator ten alokuje obszar pamięci niezbędny dla przechowywania obiektu podanego typu i zwraca wskaźnik na początek tego obszaru int *ptr; ptr = new int; jeśli alokacja pamięci nie jest możliwa, to zwracana jest wartość NULL specyfikuje jak należy zainicjować niestatyczne składniki klasy a(aa) - składnik a należy zainicjować wartością wyrażenia w nawiasie (aa) przydział pamięci na tablicę n elementów typu int: lista inicjalizacyjna pojawia się tylko przy definicji konstruktora kolejność umieszczania elementów na liście inicjalizacyjnej nie ma znaczenia int *ptr; ptr = new int[n]; jako rozmiar tablicy n można podać dowolne wyrażenie
Rok akademicki 2016/2017, Wykład nr 4 37/45 Rok akademicki 2016/2017, Wykład nr 4 38/45 Dynamiczny przydział pamięci w języku C++ Dynamiczny przydział pamięci w języku C++ pamięć przydzielona operatorem new istnieje do zakończenia programu lub do chwili zwolnienia tej pamięci za pomocą operatora delete delete ptr; gdzie ptr jest wskaźnikiem wskazującym na obiekt stworzony przez new usuwając tablicę, trzeba podać, że chodzi o tablicę, a nie o pojedynczy element: delete [] ptr; gdzie ptr jest wskaźnikiem zwróconym przez operator new podczas tworzenia dynamicznej tablicy ; osoba os, *ptr_os; ptr_os = new osoba; os.wiek = 25; ptr_os->wiek = 25; delete ptr_os; operator new można zastosować do dynamicznego tworzenia obiektu w trakcie wykonywania programu pamięć utworzonego obiektu należy zwolnić za pomocą operatora delete Rok akademicki 2016/2017, Wykład nr 4 39/45 Rok akademicki 2016/2017, Wykład nr 4 40/45 Przykład nr 3 - konstruktor i destruktor (1/3) Przykład nr 3 - konstruktor i destruktor (2/3) #include <iostream> #include <cstring> #pragma warning (disable:4996) using namespace std; char *imie; char *nazwisko; osoba(char *i, char *n, int w); ~osoba(); void drukuj(); ; osoba::osoba(char *i, char *n, int w) imie = new char[strlen(i)+1]; nazwisko = new char[strlen(n)+1]; osoba::~osoba() delete [] imie; delete [] nazwisko; void osoba::drukuj()
Rok akademicki 2016/2017, Wykład nr 4 41/45 Rok akademicki 2016/2017, Wykład nr 4 42/45 Przykład nr 3 - konstruktor i destruktor (3/3) Przykład nr 4 - konstruktor i destruktor (1/3) osoba os1("jan","kos",30); os1.drukuj(); Jan Kos 30 w kolejnym przykładzie zdefiniujemy funkcję drukuj() jako funkcję zaprzyjaźnioną z klasą w definicji klasy: friend void drukuj(osoba ktos); definicja funkcji: void drukuj(osoba ktos) cout << ktos.imie << " " << ktos.nazwisko; cout << " " << ktos.wiek << endl; #include <iostream> #include <cstring> #pragma warning (disable:4996) using namespace std; char *imie; char *nazwisko; osoba(char *i, char *n, int w); osoba(const osoba &os); ~osoba(); friend void drukuj(osoba ktos); ; konstruktor kopiujący wywołanie funkcji: drukuj(os1); Rok akademicki 2016/2017, Wykład nr 4 43/45 Rok akademicki 2016/2017, Wykład nr 4 44/45 Przykład nr 4 - konstruktor i destruktor (2/3) osoba::osoba(char *i, char *n, int w) imie = new char[strlen(i)+1]; nazwisko = new char[strlen(n)+1]; osoba::osoba(const osoba &os) imie = new char[strlen(os.imie)+1]; nazwisko = new char[strlen(os.nazwisko)+1]; strcpy(imie,os.imie); strcpy(nazwisko,os.nazwisko); wiek = os.wiek; konstruktor kopiujący Przykład nr 4 - konstruktor i destruktor (3/3) osoba::~osoba() delete [] imie; delete [] nazwisko; void drukuj(osoba ktos) cout << ktos.imie << " " << ktos.nazwisko; cout << " " << ktos.wiek << endl; osoba os1("jan","kos",30); drukuj(os1); Jan Kos 30
Rok akademicki 2016/2017, Wykład nr 4 45/45 Koniec wykładu nr 4 Dziękuję za uwagę! (Następny wykład: 23.11.2016)