Programowanie obiektowe i C++ dla matematyków



Podobne dokumenty
Programowanie obiektowe i C++ dla matematyków

Programowanie obiektowe i C++ dla matematyków

Programowanie obiektowe i C++ dla matematyków

Programowanie obiektowe i C++ dla matematyków

Wykład 5: Klasy cz. 3

Wstęp do Programowania 2

Programowanie w C++ Wykład 11. Katarzyna Grzelak. 13 maja K.Grzelak (Wykład 11) Programowanie w C++ 1 / 30

W2 Wprowadzenie do klas C++ Klasa najważniejsze pojęcie C++. To jest mechanizm do tworzenia obiektów. Deklaracje klasy :

Programowanie w C++ Wykład 12. Katarzyna Grzelak. 28 maja K.Grzelak (Wykład 12) Programowanie w C++ 1 / 27

Wykład 8: klasy cz. 4

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

TEMAT : KLASY DZIEDZICZENIE

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy INNE SPOSOBY INICJALIZACJI SKŁADOWYCH OBIEKTU

wykład IV uzupełnienie notatek: dr Jerzy Białkowski Programowanie C/C++ Język C, a C++. wykład IV dr Jarosław Mederski Spis Język C++ - wstęp

KLASA UCZEN Uczen imię, nazwisko, średnia konstruktor konstruktor Ustaw Wyswietl Lepszy Promowany

KLASA UCZEN Uczen imię, nazwisko, średnia konstruktor konstruktor Ustaw Wyswietl Lepszy Promowany

PROE wykład 2 operacje na wskaźnikach. dr inż. Jacek Naruniec

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

C-struktury wykład. Dorota Pylak

Wskaźnik może wskazywać na jakąś zmienną, strukturę, tablicę a nawet funkcję. Oto podstawowe operatory niezbędne do operowania wskaźnikami:

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

Podstawy Programowania Obiektowego

public: // interfejs private: // implementacja // składowe klasy protected: // póki nie będziemy dziedziczyć, // to pole nas nie interesuje

PROE wykład 3 klasa string, przeciążanie funkcji, operatory. dr inż. Jacek Naruniec

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

Programowanie w języku C++

Klasa jest nowym typem danych zdefiniowanym przez użytkownika. Najprostsza klasa jest po prostu strukturą, np

1. Które składowe klasa posiada zawsze, niezależnie od tego czy je zdefiniujemy, czy nie?

C-struktury wykład. Dorota Pylak

Programowanie obiektowe i C++ dla matematyków

Programowanie 2. Język C++. Wykład 3.

Przekazywanie argumentów wskaźniki

Programowanie współbieżne Wykład 8 Podstawy programowania obiektowego. Iwona Kochaoska

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Wstęp do programowania

Mechanizm dziedziczenia

Wprowadzenie w dziedziczenie. Klasa D dziedziczy klasę B: Klasa B klasa bazowa (base class), klasa D klasa pochodna (derived class).

Składnia C++ Programowanie Obiektowe Mateusz Cicheński

Języki i techniki programowania Ćwiczenia 2

Część XVII C++ Funkcje. Funkcja bezargumentowa Najprostszym przypadkiem funkcji jest jej wersja bezargumentowa. Spójrzmy na przykład.

Język C++ wykład VII. uzupełnienie notatek: dr Jerzy Białkowski. Programowanie C/C++ Język C++ wykład VII. dr Jarosław Mederski. Spis.

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Konstruktor kopiujacy

referencje Wykład 2. Programowanie (język C++) Referencje (1) int Num = 50; zdefiniowano zmienną Num (typu int) nadając jej wartość początkową 50.

Programowanie obiektowe w języku C++ dr inż. Jarosław Forenc

Algorytmy i złożoności. Wykład 3. Listy jednokierunkowe

Podstawowe elementy proceduralne w C++ Program i wyjście. Zmienne i arytmetyka. Wskaźniki i tablice. Testy i pętle. Funkcje.

Podstawy programowania skrót z wykładów:

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

Programowanie obiektowe w C++ Wykład 12

Podstawy informatyki. Elektrotechnika I rok. Język C++ Operacje na danych - wskaźniki Instrukcja do ćwiczenia

C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy INNE SPOSOBY INICJALIZACJI SKŁADOWYCH OBIEKTU

C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy WSKAŹNIKI KLASOWE

Laboratorium nr 10. Temat: Funkcje cz.2.

PARADYGMATY PROGRAMOWANIA Wykład 3

Wstęp do programowania

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 5

Programowanie, część I

Podstawy algorytmiki i programowania - wykład 4 C-struktury

Kompilacja javac prog.java powoduje wyprodukowanie kilku plików o rozszerzeniu.class, m.in. Main.class wykonanie: java Main

Wstęp do informatyki- wykład 12 Funkcje (przekazywanie parametrów przez wartość i zmienną)

Zaawansowane programowanie w języku C++ Klasy w C++

Programowanie - wykład 4

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

> C++ dynamiczna alokacja/rezerwacja/przydział pamięci. Dane: Iwona Polak. Uniwersytet Śląski Instytut Informatyki

Instytut Mechaniki i Inżynierii Obliczeniowej Wydział Mechaniczny Technologiczny Politechnika Śląska

KLASY cz4. Dorota Pylak. destruktory składowe statyczne przeciążanie operatorów. wskaźniki

Plik klasy. h deklaracje klas

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Wstęp do programowania obiektowego. WYKŁAD 3 Dziedziczenie Pola i funkcje statyczne Funkcje zaprzyjaźnione, this

Szablony klas, zastosowanie szablonów w programach

Programowanie obiektowe

ZASADY PROGRAMOWANIA KOMPUTERÓW

Algorytmy i język C++

Programowanie obiektowe - Przykładowe zadania egzaminacyjne (2005/2006)

Język C++ wykład VI. uzupełnienie notatek: dr Jerzy Białkowski. Programowanie C/C++ Język C++ wykład VI. dr Jarosław Mederski.

Przeciążanie operatorów

Do czego służą klasy?

ISO/ANSI C - funkcje. Funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje

Podstawy Programowania Obiektowego

Programowanie w C++ Wykład 8. Katarzyna Grzelak. 15 kwietnia K.Grzelak (Wykład 8) Programowanie w C++ 1 / 33

Wstęp do informatyki- wykład 11 Funkcje

Język C++ zajęcia nr 2

TEMAT : KLASY POLIMORFIZM

Program 14. #include <iostream> #include <ctime> using namespace std;

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

Listy powiązane zorientowane obiektowo

Języki i paradygmaty programowania Wykład 2. Dariusz Wardowski. dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/18

Programowanie w C++ Wykład 9. Katarzyna Grzelak. 14 maja K.Grzelak (Wykład 9) Programowanie w C++ 1 / 30

C++ Przeładowanie operatorów i wzorce w klasach

Wstęp do informatyki- wykład 9 Funkcje

Zadanie 2: Arytmetyka symboli

// Potrzebne do memset oraz memcpy, czyli kopiowania bloków

Laboratorium nr 12. Temat: Struktury, klasy. Zakres laboratorium:

Programowanie obiektowe Wykład 3. Dariusz Wardowski. dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/21

Lista dwukierunkowa - przykład implementacji destruktorów

Wykład 4: Klasy i Metody

Wskaźniki w C. Anna Gogolińska

Transkrypt:

Programowanie obiektowe i C++ dla matematyków Bartosz Szreder szreder (at) mimuw... 8 XI 2 1 Sposoby przekazywania argumentów Powiedzmy, że chcemy napisać funkcję, która zamieni miejscami wartość dwóch zmiennych całkowitoliczbowych. Pierwsze podejście mogłoby wyglądać na przykład tak: 001 #include <iostream> 003 using namespace std; 004 005 void zamien(int a, int b) 006 { 007 int c = a; 008 a = b; 009 b = c; 010 } 012 int main() 013 { 014 int x = 2, y = 5; 015 zamien(x, y); 016 cout << "x = " << x << "\n"; 017 cout << "y = " << y << "\n"; 018 return 0; 019 } To oczywiście nie zadziała. Tak zaimplementowana funkcja zamien otrzymuje dwie liczby całkowite, które są kopiami argumentów przekazanych w miejscu wywołania. Wywołanie zamien(x, y) spowoduje utworzenie nowych zmiennych x i y, których wartość zostanie zamieniona miejscami, ale nie wpłynie to na wartość zmiennych x i y. Poradzić sobie można z tym na dwa sposoby różne składniowo sposoby, które pod maską działają analogicznie. Powyższy sposób przekazywnia argumentów nazywamy przekazywaniem przez wartość. 1

1.1 Przekazywanie przez referencję i wskaźnik Chcemy do funkcji zamien przekazać rzeczywiste adresy zmiennych x i y, a nie jedynie kopie tych zmiennych. Możemy to zrobić przez tzw. referencje (oznaczane symbolem &), które oznaczają odwołanie do tego samego adresu w pamięci pod inną nazwą: 001 #include <iostream> 003 using namespace std; 004 005 void zamien(int &a, int &b) 006 { 007 int c = a; 008 a = b; 009 b = c; 010 } 012 int main() 013 { 014 int x = 2, y = 5; 015 zamien(x, y); 016 cout << "x = " << x << "\n"; 017 cout << "y = " << y << "\n"; 018 return 0; 019 } Identyczna w działaniu wersja używająca wskaźników w sposób jawny może wyglądać tak: 001 #include <iostream> 003 using namespace std; 004 005 void zamien(int *a, int *b) 006 { 007 int c = *a; 008 *a = *b; 009 *b = c; 010 } 012 int main() 013 { 014 int x = 2, y = 5; 015 zamien(&x, &y); 016 cout << "x = " << x << "\n"; 017 cout << "y = " << y << "\n"; 018 return 0; 019 } 2

Druga metoda jest zwykle nazywana przekazywaniem przez wskaźnik, chociaż można się też spotkać z nazywaniem jej przekazywaniem przez referencję, co może nieco zamieszać. Z używaniem referencji należy uważać, bowiem ich działanie jest ciche, w przeciwieństwie do wskaźników, których użycie wymaga jawnego pobrania adresu przez &. Tzn. mając jakąś zmienną v, której adres jawnie przekazujemy do pewnej funkcji widzimy od razu, że v może ulec zmianie. Natomiast przekazując v jak przez wartość nie wiemy, czy w sygnaturze argumentu funkcji nie czai się referencja, która znienacka coś nam w zmiennej v zmodyfikuje (hint: znaczące nazwy funkcji pomagają). Tym niemniej referencje różnią się od wskaźników w kilku miejscach, które sprawiają, że wskaźniki są bardziej potężne, ale też bardziej niebezpieczne i trzeba ich silniej pilnować. O tych różnicach i ich zastosowaniach jeszcze będziemy mówić. Przekazywanie przez referencję jest też o tyle miłe, że uniknięcie kopiowania argumentu może zaoszczędzić komputerowi trochę pracy. Trzeba jednakże pamiętać, że referencja nie jest tak zupełnie darmowa i przekazywanie argumentów w taki sposób jest opłacalne (tzn. mając na uwadze tylko i wyłącznie rozważania optymalizacyjne) dopiero, gdy typy przekazywanych parametrów są odpowiednio duże. Jeśli chcemy zaoszczędzić na kopiowaniu i przekazać do funkcji wskaźnik albo referencję do zmiennej, co do której nie chcemy, aby była modyfikowana (np. przez pomyłkę albo literówkę), to możemy w sygnaturze funkcji dodać przed typem zmiennej słowo const. W ten sposób naruszenia przekazywanych zmiennych będą raportowane w czasie kompilacji. 001 struct ulamek { int licznik, mianownik; 003 }; 004 005 void zmien_licznik(const ulamek *a, ulamek *b) 006 { 007 a->licznik += 2; //błąd 008 b->licznik += 3; //OK 009 } 010 void zmien_mianownik(ulamek &a, const ulamek &b) 012 { 013 a.mianownik = 5; //OK 014 b.mianownik = 8; //błąd 015 } 2 Konstruktory i destruktory Pod koniec poprzedniego pliku z notatkami poruszony został problem zainicjowania prywatnego pola root w zmiennej typu BST. Jako ćwiczenie należało zaimplementować publiczne metody init oraz destroy, których odpowiedzialnością było inicjowanie i usuwanie BST. Implementacja tych metod może wyglądać następująco: 001 #include <cstddef> 003 struct BST { 004 private: 005 struct BST_node { 3

006 int key; 007 BST_node *left, *right; 008 }; 009 010 BST_node *root; 012 BST_node * BST_search(BST_node *, int); 013 BST_node * BST_insert(BST_node *, int); 014 void BST_destroy(BST_node *); 015 016 public: 017 bool search(int); 018 void insert(int); 019 void init(); 020 void destroy(); 021 }; 022 023 void BST::init() 024 { 025 root = NULL; 026 } 027 028 void BST::BST_destroy(BST_node *node) 029 { 030 if (node == NULL) 031 return; 032 BST_destroy(node->left); 033 BST_destroy(node->right); 034 delete node; 035 } 036 037 void BST::destroy() 038 { 039 BST_destroy(root); 040 init(); 041 } 042 043 int main() 044 { 045 BST tree; 046 tree.init(); 047 048 tree.insert(10); 049 tree.insert(8); 050 051 tree.destroy(); 4

052 053 return 0; 054 } Jak widać, metodę init należy wywołać przed pierwszym użyciem jakiejkolwiek innej metody ze struktury BST, a z kolei destroy należy zawsze wywołać przed zniknięciem zmiennej tegoż typu z pola widzenia. Czyli jeśli przykładowo weźmiemy zmienną typu BST jako lokalną zmienną w jakiejś funkcji, to na początku należy wywołać metodę init, a przed wyjściem z funkcji destroy. W przeciwnym wypadku nie zwolnimy pamięci przydzielonej dla ewentualnych węzłów takiego lokalnego drzewa i pamięć po prostu wycieknie. Już po rozważaniach z poprzedniego laboratorium widać, że to nie jest Dobry TM interfejs, bo z całą pewnością ktoś kiedyś zapomni zainicjować na początku albo posprzątać na końcu. Problem z niesprzątaniem już został przedstawiony (wyciek pamięci). Problem z nieinicjowaniem najpewniej zakończy się bardzo szybko i brutalnie, bo każda operacja na drzewie dotyka w jakiś sposób wskaźnika root, więc bez zainicjowania go polecimy po losowej pamięci. Miło byłoby mieć mechanizm autoinicjujący naszą strukturę danych w momencie deklaracji i autoniszczący, gdy znika z pola widzenia. Przez znikanie z pola widzenia rozumiemy wyjście z bloku kodu, w którym zadeklarowaliśmy naszą zmienną. Dzieje się to albo w toku zwykłego osiągnięcia klamry zamykającej blok, albo jakiegoś gwałtownego wyjścia z bloku, w rodzaju użycia return albo break. Do takiego inicjowania i niszczenia służą specjalne metody zwane konstruktorami i destruktorami. Sposób ich definicji polega na określeniu metod o takiej samej nazwie, jak definiowany typ. Przy czym destruktory poprzedzone są dodatkowo znakiem tyldy (przykład poniżej). Zarówno konstruktory jak i destruktory nie zwracają niczego przed ich deklaracją nie pojawia się nawet słowo void, które do tej pory oznaczało funkcje nie zwracające żadnego argumentu. Konstruktory można przeciążać jak zwykłe funkcje, co także zostało zaprezentowane na przykładzie poniżej. 001 #include <cstddef> 003 struct BST { 004 private: 005 struct BST_node { 006 int key; 007 BST_node *left, *right; 008 }; 009 010 BST_node *root; 012 BST_node * BST_search(BST_node *, int); 013 BST_node * BST_insert(BST_node *, int); 014 void BST_destroy(BST_node *); 015 016 public: 017 bool search(int); 018 void insert(int); 019 020 //konstruktory 021 BST(); 022 BST(int); 5

023 BST(int *, int); 024 025 //destruktor 026 BST(); 027 }; 028 029 BST::BST_node * BST::BST_search(BST_node *node, int key) 030 { 031 if (node == NULL) 032 return NULL; 033 if (node->key == key) 034 return node; 035 if (node->key > key) 036 return BST_search(node->left, key); 037 else 038 return BST_search(node->right, key); 039 } 040 041 bool BST::search(int key) 042 { 043 return BST_search(root, key)!= NULL; 044 } 045 046 BST::BST_node * BST::BST_insert(BST_node *node, int key) 047 { 048 if (node == NULL) { 049 BST_node *temp = new BST_node; 050 temp->key = key; 051 temp->left = temp->right = NULL; 052 return temp; 053 } 054 055 if (key < node->key) 056 node->left = BST_insert(node->left, key); 057 else 058 node->right = BST_insert(node->right, key); 059 return node; 060 } 061 062 void BST::insert(int key) 063 { 064 root = BST_insert(root, key); 065 } 066 067 void BST::BST_destroy(BST_node *node) 068 { 6

069 if (node == NULL) 070 return; 071 072 BST_destroy(node->left); 073 BST_destroy(node->right); 074 delete node; 075 } 076 077 //konstruktor domyślny, generujący drzewo puste 078 BST::BST() 079 { 080 root = NULL; 081 } 082 083 //konstruktor przyjmujący inicjalny węzeł do wstawienia 084 BST::BST(int x) 085 { 086 root = NULL; 087 insert(x); 088 } 089 090 //konstruktor przyjmujący tablicę elementów do wstawienia 091 BST::BST(int *tab, int len) 092 { 093 root = NULL; 094 for (int i = 0; i < len; ++i) 095 insert(tab[i]); 096 } 097 098 BST:: BST() 099 { 100 BST_destroy(root); 101 } 102 103 int main() 104 { 105 //domyślny konstruktor 106 BST tree_zero_1; 107 BST tree_zero_2(); 108 BST tree_zero_3 = BST(); 109 110 //konstruktor jednoargumentowy 111 BST tree_one_1(42); 112 BST tree_one_2 = BST(42); 113 114 //losowe wartości testowe 7

115 int keys[5] = {7, 42, 29, 13, 5}; 116 117 //konstruktor dwuargumentowy 118 //przy okazji pokazane są różne sposoby przekazania listy argumentów 119 BST tree_two_1(keys, 5); 120 BST tree_two_2 = BST(&keys[0], 5); 121 BST tree_two_3 = BST(keys + 3, 2); //tutaj pchamy tylko keys[3] i keys[4] 122 BST tree_two_4(&keys[1], 3); //a tutaj keys[1], keys[2] i keys[3] 123 124 //przy new także podajemy konstruktor 125 BST *ptr_zero = new BST; 126 BST *ptr_one = new BST(42); 127 BST *ptr_two = new BST(keys, 5); 128 129 //UWAGA 130 //pamięć zalokowaną new trzeba tak czy inaczej zwolnić samemu 131 //wywołanie delete na "BST *" powoduje wywołanie destruktora BST:: BST() 132 //ale trzeba pamiętać, że same wskaźniki się nie zerują 133 //i trzeba je wyzerować samodzielnie 134 //jeśli chcemy z nich później korzystać 135 delete ptr_zero; 136 ptr_zero = NULL; 137 138 delete ptr_one; 139 ptr_one = NULL; 140 141 delete ptr_two; 142 ptr_two = NULL; 143 144 return 0; 145 } //po wyskoczeniu poza tę klamrę (return zaraz powyżej) 146 //wszystkie zmienne typu "BST" się usuną poprzez swoje destruktory Powyższy kod zawiera pełną implementację BST wraz z przykładowym użyciem różnych konstruktorów. Ćwiczeniem pozostaje wypełnienie luk w funkcjonalności, w rodzaju metody służącej do usuwania elementów z drzewa, wypisywania całej jego zawartości w kolejności leksykograficznej czy znajdowania k-tego leksykograficznie elementu (po odpowiednim wzbogaceniu drzewa). Uwaga na boku: jeśli dla własnego typu danych nie zdefiniujemy żadnego konstruktora, to używany jest zeroargumentowy tzw. konstruktor domyślny, który działa w taki sposób, że dla każdego pola składowego swojej struktury wywołuje jego konstruktor domyślny. Analogiczne rozumowanie dotyczy domyślnych destruktorów dla pól składowych. Jeśli mimo wszystko zdefiniujemy jakiś własny konstruktor/destruktor, ale pominiemy w jego implementacji któreś z pól klasy, to dla takich pól także używane będą domyślne konstruktory/destruktory w trakcie konstruowania/niszczenia klasy głównej. Ćwiczenie (łatwe i wartościowe jednocześnie): zdefiniować konstruktor dla typu BST_node, żeby proces tworzenia nowego wierzchołka w funkcji BST_insert dało się skrócić do jednej linijki w rodzaju return new 8

BST_node(key). 9