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

Programowanie obiektowe i C++ dla matematyków

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

Programowanie - wykład 4

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

Wstęp do Programowania 2

11.6 Klasa do obsługi liczb wymiernych

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

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

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Czym są właściwości. Poprawne projektowanie klas

Rozdział 4 KLASY, OBIEKTY, METODY

TEMAT : KLASY DZIEDZICZENIE

Podstawy Programowania Obiektowego

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

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

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Struktury Struktura polami struct struct struct struct

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

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

Wstęp do programowania

4. Funkcje. Przykłady

C-struktury wykład. Dorota Pylak

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

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

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

Szablony funkcji i klas (templates)

Wyjątki (exceptions)

Programowanie obiektowe i C++ dla matematyków

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

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

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

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

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

Wstęp do Programowania potok funkcyjny

Różne właściwości. Różne właściwości. Różne właściwości. C++ - klasy. C++ - klasy C++ - KLASY

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

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Ćwiczenie 3 stos Laboratorium Metod i Języków Programowania

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

Zadanie 2: Arytmetyka symboli

Wykład 4: Klasy i Metody

Programowanie Obiektowo Zorientowane w języku c++ Przestrzenie nazw

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.

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

Programowanie obiektowe

Laboratorium 1 - Programowanie proceduralne i obiektowe

2.4 Dziedziczenie. 2.4 Dziedziczenie Przykłady programowania w C - kurs podstawowy

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

Operator przypisania. Jest czym innym niż konstruktor kopiujący!

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.

Wstęp do informatyki- wykład 11 Funkcje

Programowanie Obiektowe i C++

Wstęp do informatyki- wykład 9 Funkcje

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

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

Wyszukiwanie w BST Minimalny i maksymalny klucz. Wyszukiwanie w BST Minimalny klucz. Wyszukiwanie w BST - minimalny klucz Wersja rekurencyjna

Do czego służą klasy?

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

Abstrakcyjne struktury danych w praktyce

C-struktury wykład. Dorota Pylak

1 Wskaźniki. 1.1 Główne zastosowania wskaźników

1 Podstawy c++ w pigułce.

Przekazywanie argumentów wskaźniki

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

Metody Metody, parametry, zwracanie wartości

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

Programowanie komputerowe. Zajęcia 1

Programowanie komputerowe. Zajęcia 2

. Podstawy Programowania 2. Drzewa bst - część druga. Arkadiusz Chrobot. 12 maja 2019

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

Klasy Obiekty Dziedziczenie i zaawansowane cechy Objective-C

#include <iostream> using namespace std; void ela(int); int main( ); { Funkcja 3. return 0; }

Podstawy języka C++ Maciej Trzebiński. Praktyki studenckie na LHC IFJ PAN. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. M. Trzebiński C++ 1/16

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

Wstęp do programowania obiektowego. Wykład 2

Listy powiązane zorientowane obiektowo

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

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

Mechanizm dziedziczenia

Programowanie obiektowe, wykład nr 6. Klasy i obiekty

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

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

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

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

Programowanie obiektowe

Wieczorowe Studia Licencjackie Wrocław, Wykład nr 6 (w oparciu o notatki K. Lorysia, z modyfikacjami) Sito Eratostenesa

Przesłanianie nazw, przestrzenie nazw

Algorytmy i język C++

Podstawy Informatyki. Inżynieria Ciepła, I rok. Wykład 10 Kurs C++

Programowanie obiektowe - zadania

Pytania sprawdzające wiedzę z programowania C++

Programowanie obiektowe

void Pobierz(Student &a); void Wypisz(Student a); void Ustaw_zaliczenia(Student t[],int r); void Wypisz_najlepszych(Student t[],int r, float prog);

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

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Laboratorium 1. Wprowadzenie, środowisko programistyczne, pierwsze programy

Podstawy programowania. Wykład 7 Tablice wielowymiarowe, SOA, AOS, itp. Krzysztof Banaś Podstawy programowania 1

Dokumentacja do API Javy.

Transkrypt:

Programowanie obiektowe i C++ dla matematyków Bartosz Szreder szreder (at) mimuw... 25 X 2011 1 Funkcje składowe struktur i klas Jeśli wcześniej mieliśmy typ ulamek, to mogliśmy dla nich zdefiniować funkcję skroc, która przyjmuje ulamek, oblicza największy wspólny dzielnik licznika i mianownika (np. korzystając z pomocniczej funkcji nwd), dzieli licznik i mianownik przez tak otrzymaną liczbę i zwraca wynikowy ułamek. Całość mogłaby wyglądać na przykład tak: 001 #include <iostream> 003 using namespace std; 004 005 struct ulamek { 006 int licznik, mianownik; 007 }; 008 009 //założenie: a >= b 010 //jeśli tak nie jest, to wołamy nwd z odwróconymi argumentami 011 int nwd(int a, int b) 012 { 013 if (a < b) 014 return nwd(b, a); 015 016 int c; 017 while (b!= 0) { 018 c = a % b; 019 a = b; 020 b = c; 021 } 022 023 return a; 024 } 025 1

026 ulamek skroc(ulamek x) 027 { 028 int d = nwd(x.licznik, x.mianownik); 029 x.licznik /= d; 030 x.mianownik /= d; 031 return x; 032 } 033 034 int main() 035 { 036 ulamek a; 037 a.licznik = 27; 038 a.mianownik = 6; 039 a = skroc(a); 040 cout << a.licznik << " / " << a.mianownik << "\n"; 041 return 0; 042 } Funkcję skroc będziemy najprawdopodobniej używać do przypisania na ten sam ułamek, dla którego ją wywołujemy, tzn. można się spodziewać, że prawie zawsze będziemy działać na zasadzie x = skroc(x), a już niekoniecznie y = skroc(x). Nietrudno zauważyć, że w takim użytkowaniu łatwo się pomylić: a to przypiszemy wynik funkcji skroc na niewłaściwą zmienną, a to zapomnimy o przypisaniu w ogóle... Kompilator oczywiście nie zwróci żadnego błędu, bo niby czemu miałby? Wszak działamy cały czas poprawnie z punktu widzenia języka programowania. Zdefiniujemy zatem skracanie jako funkcję wewnątrz struktury (wtedy taką funkcję nazywamy metodą określonej struktury). Nasza metoda skroc będzie działała na polach struktury, dla której ją wywołujemy: 001 #include <iostream> 003 using namespace std; 004 005 int nwd(int a, int b) 006 { 007 if (a < b) 008 return nwd(b, a); 009 010 int c; 011 while (b!= 0) { 012 c = a % b; 013 a = b; 014 b = c; 015 } 016 017 return a; 018 } 019 020 struct ulamek { 2

021 int licznik, mianownik; 022 023 void skroc() 024 { 025 int d = nwd(licznik, mianownik); 026 licznik /= d; 027 mianownik /= d; 028 } 029 }; 030 031 int main() 032 { 033 ulamek a; 034 a.licznik = 27; 035 a.mianownik = 6; 036 a.skroc(); 037 cout << a.licznik << " / " << a.mianownik << "\n"; 038 return 0; 039 } Dla zaprezentowania składni języka C++ pokazany dalej został też drugi sposób definiowania metod na przykładzie kodu obliczającego NWD. Funkcję nwd zapiszemy jako metodę struktury ulamek. Jednakże w opisie ułamka zawrzemy jedynie samą deklarację metody nwd, a jej definicję (kod) napiszemy w innym miejscu. Takie podejście może pomóc w redukcji bałaganiarstwa i oddzielaniu interfejsu od implementacji (o czym więcej poniżej). W szczególności niedługo nauczymy się kompilować programy zapisane w więcej niż jednym pliku źródłowym oraz rozgraniczenia pomiędzy zawartością plików nagłówkowych (dołączanych do kodu programu dyrektywami #include) a plików z implementacją bytów deklarowanych w tychże nagłówkach. W poniższym przykładzie warto zwrócić uwagę na sposób definiowania metod struktur, tzn. przedrostek ulamek:: przed nazwą metody. Składnia ta pojawiła się już przy okazji używania bytów zawartych w przestrzeni nazw biblioteki standardowej std. 001 #include <iostream> 003 using namespace std; 004 005 struct ulamek { 006 int licznik, mianownik; 007 008 void skroc() 009 { 010 int d = nwd(licznik, mianownik); 011 licznik /= d; 012 mianownik /= d; 013 } 014 015 int nwd(int, int); 016 }; 017 3

018 int ulamek::nwd(int a, int b) 019 { 020 if (a < b) 021 return nwd(b, a); 022 023 int c; 024 while (b!= 0) { 025 c = a % b; 026 a = b; 027 b = c; 028 } 029 030 return a; 031 } 032 033 034 int main() 035 { 036 ulamek a; 037 a.licznik = 27; 038 a.mianownik = 6; 039 a.skroc(); 040 cout << a.licznik << " / " << a.mianownik << "\n"; 041 return 0; 042 } 2 Moralizowanie na śniadanie Wspomniane wcześniej zostało, że należy rozgraniczać implementację od interfejsu, ale co właściwie przez to rozumiemy? Spójrzmy na kod, który piszemy jak na pewną biblioteczkę, którą udostępnimy dalszym użytkownikom, tj. programistom do stosowania w ich programach. W szczególności często sami jesteśmy swoimi użytkownikami dzielimy programy na pewne podmoduły, np. obliczeń ułamkowych, które wykorzystujemy w różnych częściach większego projektu. Interfejs w takim wypadku jest jak najbardziej zminimalizowanym i uproszczonym zbiorem pól składowych i metod, które chcemy udostępnić użytkownikom. Wszelkie szczegóły implementacyjne należy ukryć. Jedną z korzyści oddzielania implementacji od interfejsu jest swoboda w zmienianiu implementacji bez naruszania fragmentów kodu, które wołają metody interfejsu. Jest to jeden z przejawów programowania warstwowego, tzn. takiego konstruowania projektów programistycznych, w których są one podzielone na pewne poziomy, z których każdy komunikuje się jedynie z poziomem nadrzędnym (o ile jest jakiś) i podrzędnym (o ile jest jakiś). Dzięki takiemu podejściu programy zyskują na modularności możemy wprowadzać zmiany i poprawki w wyizolowanych fragmentach projektu, co zwykle wspomaga rozumienie kodu i wpływa korzystnie na zmniejszenie liczby wprowadzanych błędów. Najbardziej spektakularnym przykładem działania zasady warstwowości w praktyce jest stos protokołów sieciowych, przenoszących komunikację w Internecie (http://en.wikipedia.org/wiki/tcp/ip_model). W przypadku wcześniej wspomnianych ułamków użytkownik nie powinien samemu wołać NWD dla licznika i mianownika, a jedynie korzystać z metody skroc, która w razie potrzeby policzy największy wspólny dzielnik w dowolny sposób. Wolność w zmianie implementacji bez ruszania interfejsu przejawia się w tym przypadku 4

na możliwości wpisania kodu obliczającego NWD bezpośrednio w kod metody skroc i wyeliminowanie ze struktury ulamek nadmiarowej metody. Dobrze zachowujące się programy (tzn. wołające skroc, ale nie nwd) działałyby jakby nigdy nic się nie stało, natomiast programy wołające (umownie) wewnętrzną metodę nwd przestałyby się kompilować. Sam sposób użycia metody skroc także uprościliśmy, aby zredukować potencjalne miejsca popełnienia błędu. Możemy zatem uznać metodę skroc za część interfejsu, a metodę nwd za część implementacji. Pola licznik i mianownik są pewnego rodzaju szarą strefą, do kwestii której jeszcze wrócimy. Przykład z ułamkami jest zmontowany nieco na siłę. Spróbujmy zatem pokazać jakiś bardziej rzeczywisty przykład, mianowicie wcześniejszy zbiór funkcji operujących na wskaźnikach do węzłów BST zbierzemy w jedną strukturę, implementującą w scentralizowany sposób funkcjonalność BST. Motywacja dla upraszczania interfejsu stanie się dużo bardziej widoczna. 2.1 Przykład: BST jako pełna struktura danych Przykładowy program zamieszczony poniżej wprowadza pewną nowość, mianowicie definiowanie typów zagnieżdżonych. Typ BST_node definiujemy jako składową typu BST, czyniąc go tym samym widocznym tylko wewnątrz metod struktury BST. Oczywiście wprowadza to konieczność prefiksowania nazwy typu przedrostkiem BST:: w niektórych kontekstach, co dokładnie widać w przykładzie. Jest to jeden z przejawów oddzielania interfejsu od implementacji: typ BST_node traktujemy jako właściwy i użyteczny tylko dla struktury BST, więc określamy go jako typ składowy zamiast globalny. Nie zaśmiecamy w ten sposób globalnej przestrzeni nazw typami właściwymi tylko dla pewnych ściśle określonych fragmentów projektu. Ułatwia to też rozumienie kodu: od razu widać, że tym typem nie musimy się przejmować nigdzie poza metodami własnymi struktury BST. 001 #include <cstddef> 003 struct BST { 004 struct BST_node { 005 int key; 006 BST_node *left, *right; 007 }; 008 009 BST_node *root; 010 011 BST_node * BST_search(BST_node *, int); 012 BST_node * BST_insert(BST_node *, int); 013 }; 014 015 BST::BST_node * BST::BST_search(BST_node *node, int key) 016 { 017 if (node == NULL) 018 return NULL; 019 if (node->key == key) 020 return node; 021 if (node->key > key) 022 return BST_search(node->left, key); 023 else 024 return BST_search(node->right, key); 5

025 } 026 027 028 BST::BST_node * BST::BST_insert(BST_node *node, int key) 029 { 030 if (node == NULL) { 031 BST_node *temp = new BST_node; 032 temp->key = key; 033 temp->left = temp->right = NULL; 034 return temp; 035 } 036 037 if (key < node->key) 038 node->left = BST_insert(node->left, key); 039 else 040 node->right = BST_insert(node->right, key); 041 return node; 042 } 043 044 int main() 045 { 046 BST tree; 047 tree.root = NULL; 048 049 tree.root = tree.bst_insert(tree.root, 10); 050 tree.root = tree.bst_insert(tree.root, 8); 051 052 return 0; 053 } Wywołania BST_insert są analogiczne do pokazanego na poprzednich zajęciach: pierwszym argumentem jest korzeń drzewa, drugim wstawiany klucz. Wynik wywołania zapisujemy na korzeń drzewa. To bardzo dużo rzeczy, o których trzeba pamiętać, a których pominięcie lub podanie gdzieś niewłaściwej zmiennej w przypisaniu albo pierwszym argumencie (literówki zdarzają się... ) będzie przyczyną poważnych i potencjalnie trudnych do wyśledzenia błędów. Metoda BST_insert to nie jest dobra metoda interfejsu. Naprawdę Dobrą TM metodą interfejsu będzie taka, która przyjmuje jeden argument: klucz do wstawienia. Analogicznie dobrą metodą interfejsu dla operacji wyszukiwania będzie metoda, która przyjmuje jeden argument (oczywiście klucz) i zwraca wartość logiczną (prawda albo fałsz) zamiast obrzydliwego wskaźnika, noszącego znamiona bycia częścią wewnętrznej implementacji. W poniższym przykładzie pokazano lepszy interfejs, zaimplementowany w postaci krótkich metod opakowujących odpowiednie wywołania metod wewnętrznych. Dla czytelności pominięto implementację BST_insert i BST_search (są identyczne jak w poprzednim przykładzie). 001 #include <cstddef> 003 struct BST { 004 struct BST_node { 005 int key; 6

006 BST_node *left, *right; 007 }; 008 009 BST_node *root; 010 011 BST_node * BST_search(BST_node *, int); 012 BST_node * BST_insert(BST_node *, int); 013 bool search(int); 014 void insert(int); 015 }; 016 017 bool BST::search(int key) 018 { 019 return BST_search(root, key)!= NULL; 020 } 021 022 void BST::insert(int key) 023 { 024 root = BST_insert(root, key); 025 } 026 027 int main() 028 { 029 BST tree; 030 tree.root = NULL; 031 032 //teraz wygląda dużo lepiej 033 tree.insert(10); 034 tree.insert(8); 035 036 return 0; 037 } 3 Pola publiczne i prywatne Powyższa implementacja BST jest już całkiem porządna. Jedyny problem jaki pozostaje polega na samym istnieniu metod (np. BST_insert), które są niebezpieczne w użyciu. Chcielibyśmy w jakiś sposób zabronić użytkownikowi wołania takiej metody, żeby nie strzelił sobie w stopę. Język C++ zawiera wsparcie dla podziału struktur na część prywatną (implementację) i publiczną (interfejs) 1. Każde pole prywatne (zmienna albo metoda) jest dostępne wyłącznie z poziomu metod struktury. Próba wywołania metody prywatnej albo dokonania zapisu/odczytu zmiennej prywatnej kończy się błędem kompilacji. Składnię użycia pokazuje poniższy program (z pominięciem implementacji metod pozostaje ona bez zmian względem wcześniejszego programu). 001 #include <cstddef> 1 Są też części chronione (protected), ale o nich innym razem. 7

003 struct BST { 004 private: 005 struct BST_node { 006 int key; 007 BST_node *left, *right; 008 }; 009 010 BST_node *root; 011 012 BST_node * BST_search(BST_node *, int); 013 BST_node * BST_insert(BST_node *, int); 014 015 public: 016 bool search(int); 017 void insert(int); 018 }; 019 020 021 BST tree; 022 023 int main() 024 { 025 tree.insert(10); 026 tree.insert(8); 027 028 return 0; 029 } W tak określonej strukturze BST możemy jedynie wołać z zewnątrz metody insert i search. Pozostałe metody oraz zmienna root są dostępne jedynie z innych metod struktury. Zauważmy, że powoduje to pewien problem przy inicjowaniu drzewa wcześniej mogliśmy dobrać się do pola root i ustawić je na NULL. Teraz nie jest to możliwe, więc deklarujemy zmienną typu BST globalnie, bo wtedy wszystkie jej pola są wyzerowane. Ćwiczenie: napisać metodę publiczną BST::init(), której jedynym zadaniem będzie ustawienie root na NULL. Ćwiczenie: napisać metodę publiczną BST::destroy(), która ma usunąć całe drzewo (zwalniając pamięć po każdym zaalokowanym węźle żadnych wycieków!), a następnie ustawia root na NULL (np. przez wywołanie init), w efekcie odtwarzając puste drzewo. Podpowiedź: wystarczy lekko zmodyfikować kod wypisujący wszystkie klucze drzewa w kolejności leksykograficznej. 3.1 Słowa kluczowe struct i class Do definiowania własnych typów złożonych można użyć słowa kluczowego class. Jedyna różnica w C++ pomiędzy struct i class jest taka, że w pierwszym przypadku wszystkie składowe typu są domyślnie publiczne, natomiast w drugim są prywatne. 8