Języki programowania



Podobne dokumenty
Programowanie obiektowe C++

Wykład I. Programowanie II - semestr II Kierunek Informatyka. dr inż. Janusz Słupik. Wydział Matematyki Stosowanej Politechniki Śląskiej

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

Wykład 5: Klasy cz. 3

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

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

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

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

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

Wykład 1. Program przedmiotu. Programowanie Obiektowe (język C++) Literatura. Program przedmiotu c.d.:

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

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

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

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

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

Wykład 8: klasy cz. 4

Język C++ Różnice między C a C++

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

Wstęp do programowania

Język C++ zajęcia nr 2

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

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

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

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

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

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

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

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

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

Wyjątki (exceptions)

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

Zmienne, stałe i operatory

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

Wykład 1. Program przedmiotu. Programowanie (język C++) Literatura. Program przedmiotu c.d.:

TEMAT : KLASY DZIEDZICZENIE

Część 4 życie programu

3. Instrukcje warunkowe

1 Podstawy c++ w pigułce.

Paostwowa Wyższa Szkoła Zawodowa w Płocku Dariusz Wardowski

Wstęp do programowania. Wykład 1

Przesłanianie nazw, przestrzenie nazw

Programowanie w C++ Wykład 14. Katarzyna Grzelak. 3 czerwca K.Grzelak (Wykład 14) Programowanie w C++ 1 / 27

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

Pytania sprawdzające wiedzę z programowania C++

Programowanie obiektowe C++

Podstawy Programowania Obiektowego

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

Wstęp do informatyki- wykład 11 Funkcje

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

Techniki programowania INP001002Wl rok akademicki 2017/18 semestr letni. Wykład 4. Karol Tarnowski A-1 p.

I - Microsoft Visual Studio C++

PARADYGMATY PROGRAMOWANIA Wykład 3

Wstęp do Programowania 2

Programowanie obiektowe C++

Projektowanie klas c.d. Projektowanie klas przykład

Wstęp do informatyki- wykład 7

1 Wskaźniki i zmienne dynamiczne, instrukcja przed zajęciami

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

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

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

Wstęp do informatyki- wykład 9 Funkcje

1 Podstawy c++ w pigułce.

Podstawy Programowania

Lab 9 Podstawy Programowania

Szablony funkcji i szablony klas

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

Programowanie C++ Wykład 2 - podstawy języka C++ dr inż. Jakub Możaryn. Warszawa, Instytut Automatyki i Robotyki

Obsługa wyjątków. Język C++ WW12

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

PARADYGMATY PROGRAMOWANIA Wykład 2

Tablice (jedno i wielowymiarowe), łańcuchy znaków

Programowanie w C++ Wykład 8. Katarzyna Grzelak. 7 maja K.Grzelak (Wykład 8) Programowanie w C++ 1 / 31

Wstęp do programowania

Przekazywanie argumentów wskaźniki

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

1 P roste e t ypy p d a d n a ych c - c ąg ą g d a d l a szy 2 T y T py p z ł z o ł żo ż ne e d a d n a ych c : T BLICE

Języki programowania

Język ludzki kod maszynowy

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

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.

Zajęcia nr 2 Programowanie strukturalne. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

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

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

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

Funkcje przeciążone, konstruktory kopiujące, argumenty domyślne

Podstawy Informatyki. Kompilacja. Historia. Metalurgia, I rok. Kompilatory C++ Pierwszy program. Dyrektywy preprocesora. Darmowe:

Programowanie w języku C++

Wstęp do programowania obiektowego. Przekazywanie parametrów do funkcji w C++ Metody i funkcje operatorowe Strumienie: standardowe, plikowe, napisowe

Przeciążanie operatorów

Wstęp do Programowania, laboratorium 02

4. Funkcje. Przykłady

Języki C i C++ Wykład: 2. Wstęp Instrukcje sterujące. dr Artur Bartoszewski - Języki C i C++, sem. 1I- WYKŁAD

Do czego służą klasy?

Programowanie w C++ Wykład 1. Katarzyna Grzelak. 26 luty K.Grzelak (Wykład 1) Programowanie w C++ 1 / 28

Podstawy Informatyki. Metalurgia, I rok. Wykład 6 Krótki kurs C++

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

Programowanie - wykład 4

Programowanie w C++ Wykład 6. Katarzyna Grzelak. kwiecień K.Grzelak (Wykład 6) Programowanie w C++ 1 / 40

Programowanie Komputerów

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

Transkrypt:

Języki programowania Nowoczesne techniki programowania Wykład 4 Witold Dyrka witold.dyrka@pwr.wroc.pl 16-30/11/2011

Prawa autorskie Slajdy do wykładu powstały w oparciu o slajdy Bjarne Stroustrupa do kursu Foundations of Engineering II (C++) prowadzonego w Texas A&M University http://www.stroustrup.com/programming

Materiały Literatura Bjarne Stroustrup. Programowanie: Teoria i praktyka z wykorzystaniem C++. Helion (2010) Jerzy Grębosz. Symfonia C++ standard. Edition 2000 (2008) Dowolny podręcznik języka C++ w standardzie ISO 98 www.cplusplus.com - The C++ Reference Network (j.ang.) Środowisko programistyczne Microsoft Visual C++ (rekomendowane) Dowolne środowisko korzystające z GCC

Program wykładów 1. Pierwszy program. Obiekty, typy i wartości 2. Wykonywanie obliczeń. Błędy 3. Pisanie i wykańczanie programu 4. Techniki pisania funkcji i klas 5. Strumienie wejścia/wyjścia 6. Wskaźniki i tablice 7. Systematyka technik i języków programowania

Zagadnienia (1) O szczegółach technicznych języka Deklaracje Definicje Nagłówki i preprocesor Zakres Funkcje Deklaracje i definicje Argumenty Przekazanie argumentu przez wartość, referencję i stałą referencję Przestrzenie nazw Instrukcje using

Czy musimy zajmować się szczegółami technicznymi języka? Niestety tak! Język programowania jest językiem obcym Musimy poznać jego gramatykę i słownictwo Dlaczego? Programy muszą być precyzyjnie i kompletnie określone Jak pamiętamy, komputer jest bardzo głupią (choć bardzo szybką) maszyną Nie zgadnie co naprawdę chcesz powiedzieć (i nie powinien próbować) A więc to my musimy znać zasady No, przynajmniej niektóre z nich (cały standard C++11 liczy 1338 stron:-) Jednak pamiętajmy, że Uczymy się programowania Tworzymy programy lub systemy Język programowania jest tylko narzędziem

Szczegóły techniczne Nie roztrząsaj mało istotnych drobnych zagadnień składniowych i znaczeniowych Zwykle jest więcej niż jeden sposób by coś napisać Tak jak w języku polskim czy angielskim Większość pojęć dotyczących projektowania i programowania jest uniwersalna To czego nauczysz się używając C++ wykorzystasz w wielu innych językach Co prawda, szczegóły techniczne języka są specyficzne dla niego Ale wiele szczegółów C++, którymi zajmujemy się teraz ma odpowiedniki w C, Javie, C# itp.

Deklaracje Deklaracja wprowadza nazwę do zakresu Deklaracja określa typ nazwanego obiektu Czasami deklaracja zawiera inicjator Każda nazwa musi być zadeklarowana zanim będzie użyta w programie C++ Przykłady: int a = 7; // deklaracja zmiennej całkowitoliczbowej a const double cd = 8.7; double sqrt(double); vector<token> v; // deklaracja stałej zmiennoprzecinkowej // podwójnej precyzji o nazwie cd // deklaracja funkcji o nazwie sqrt pobierającej // zmiennoprzecinkowy argument // i zwracającej zmiennoprzecinkowy wynik // deklaracja zmiennej v będącej wektorem zmiennych // typu Token

Definicje Deklaracja, która w pełni określa deklarowany obiekt nazywa się definicją Przykłady definicji int a = 7; // definicja zmiennej całkowitoliczbowej a o wartości początkowej 7 vector<double> v; // definicja pustego wektora liczb zmiennoprzecinkowych double sqrt(double) { }; // definicja funkcji: ciało funkcji w miejscu struct Point { int x; int y; }; // definicja struktury Point o składowych x i y Przykłady deklaracji, które nie są definicjami Tylko informują jak używać obiektów double sqrt(double); struct Point; extern int a; // ciało klasy nieokreślone // składowe struktury nieokreślone // słowo kluczowe extern oznacza, że nie jest to definicja // extern jest archaiczne, rzadko się przydaje

Deklaracje a definicje Nie możesz zdefiniować czegoś dwa razy definicja mówi, czy coś jest: int a; // definicja int a; // błąd: podwójna definicja double sqrt(double d) { } // definicja double sqrt(double d) { } // błąd: podwójna definicja Deklarować możesz wielokrotnie deklaracja mówi, jak coś może być użyte; int a = 7; // definicja jest także deklaracją extern int a; // deklaracja, że gdzieś została zdefiniowana zmienna a typu int double sqrt(double d) { } // definicja funkcji jest także deklaracją double sqrt(double); // deklaracja funkcji, czyli jej prototyp albo interfejs

Po co osobno deklarować? Żeby odnieść się do czego, wystarczy sama deklaracja Często chcemy zdefiniować to gdzie indziej dalej w pliku w innym pliku najlepiej napisanym przez kogoś innego Deklaracja określa interfejs, czyli sposób użycia Twojego kodu bibliotek tu jest klucz: nie możemy ani chcemy pisać sami wszystkiego W większych programach deklaracje umieszcza się w plikach nagłówkowych, aby ułatwić dostęp do nich

Nagłówki Plik nagłówkowy ( nagłówek, ang. header) zawiera deklaracje komponentów programu np. funkcji, typów, klasy, stałych, zmiennych deklaracje określają interfejs, czyli sposób użycia Nagłówek daje dostęp do funkcji, typów itp., które chcesz użyć w programie są one zdefiniowane w swoich plikach źródłowych często jako część biblioteki przeważnie nie interesuje Cię jak są napisane To rozwiązanie wspiera technikę programowania nazwaną abstrakcją nie musisz znać szczegółów implementacji funkcji takiej jak cout, aby jej używać. Kiedy umieszczasz dyrektywę preprocesora: #include <iostream> deklaracje zawarte w bibliotece iostream są dołączane do programu

Nagłówki (2) Implementacja Tokenu token.cpp: Nagłówek token.h: // deklaracje: class Token { }; class Token_stream { Token get(); }; Program użytkownika use.cpp: #include "token.h" //definicje: Token Token_stream::get() { /* */ } #include "token.h" Token t = ts.get(); Plik nagłówkowy (tutaj, token.h) określa interfejs pomiędzy kodem użytkownika (use.cpp) a kodem implementacji komponentu (token.cpp, przeważnie w bibliotece) Ta sama deklaracja #include w obu plikach.cpp (pliku z implementacją komponentu Token oraz pliku używającym komponent Token) ułatwia zachowanie spójności

Nagłówki (3) Implementacja Tokenu token.cpp: Nagłówek token.h: // deklaracje: class Token { }; class Token_stream { Token get(); }; Program użytkownika use.cpp: #include "token.h" //definicje: Token Token_stream::get() { /* */ } #include "token.h" Token t = ts.get(); Uwaga! Zapis #include ''token.h'' oznacza, że kompilator szuka nagłówka biblioteki w tym samym katalogu co nasz plik, który dołącza bibliotekę. Zapis #include <iostream> oznacza, że kompilator szuka nagłówka biblioteki na ścieżce przeszukiwania kompilatora Jeśli biblioteka jest częścią biblioteki standardowej, pomijamy rozszerzenie.h

Zakres Zakres jest fragmentem tekstu programu, np. Zakres globalny Zakres klasy (wewnątrz klasy) Zakres lokalny (blok kodu pomiędzy nawiasami { i }) Zakres instrukcji (np. wnętrze instrukcji for) Nazwa zadeklarowana w danym zakresie jest widoczna wewnątrz tego zakresu w zakresach zagnieżdżonych (podrzędnych) w tym zakresie ale dopiero po deklaracji Zakres zapewnia lokalność nazw Zabezpiecza przed kolizją nazw zmiennych i funkcji używanych w różnych częściach programu To ważne: prawdziwe programy składają się z tysięcy komponentów Lokalność jest dobra nazwy powinny być tak lokalne jak to tylko możliwe

Zakres przykład #include <cmath> // dołącz funkcje max and abs // tu nie ma r, i, oraz v class My_vector { vector<int> v; // v jest w zakresie klasy My_vector public: int largest() // funkcja largest jest w zakresie klasy My_vector { int r = 0; // r jest w zakresie lokalnym funkcji largest() for (int i = 0; i<v.size(); ++i) // i jest w zakresie instrukcji for r = max(r,abs(v[i])); } // tutaj nie ma już i return r; // tutaj nie ma już r }; // tutaj nie ma już v

Zagnieżdżanie zakresów int x; int y; // zmienna globalna unikaj, jeśli tylko możesz // inna zmienna globalna int f() { int x; // zmienna lokalna (teraz mamy dwa x) x = 7; // podstawienie lokalnej zmiennej x, a nie globalnej x { int x = y; // kolejna lokalna x zainicjowana globalną y // (teraz mamy trzy x) x++; // zwiększamy lokalną x w tym zakresie } } // Uwaga! Unikaj takiego skomplikowanego zagnieżdżania zakresów i przesłaniania zmiennych pisz proste programy!

Funkcje Ogólna postać: typ_zwracany nazwa (argumenty); typ_zwracany nazwa (argumenty) ciało_funkcji // deklaracja // definicja Na przykład: double f(int a, double d) { return a*d; } Argumenty funkcji nazywa się także parametrami By funkcja nie zwracała niczego, podaj void jako typ zwracany void zwieksz_moc(int poziom); Ciało funkcji to blok lub blok try, np. { /* kod programu */ } // blok try { /* kod programu */ } // blok try catch(exception& e) { /* kod obsługi wyjątków */ } // Funkcje reprezentują/implementują przetwarzanie/obliczenia

Przekazywanie parametrów przez wartość // przekazywanie przez wartość wysyłamy funkcji f kopię wartości argumentu int f(int a) { a = a+1; return a; } a: 0 int main() { } xx: int xx = 0; cout << f(xx) << endl; // wypisze 1 cout << xx << endl; // wypisze 0; f() nie zmieniła xx int yy = 7; cout << f(yy) << endl; // wypisze 8; f() nie zmieniła yy cout << yy << endl; // wypisze 7 0 kopia wartości

Przekazywanie parametrów przez referencję (czyli odniesienie) // przekazywanie przez referencję przekazujemy funkcji f referencję do argumentu int f(int& a) { a = a+1; return a; } int main() { } a odnosi się do xx xx: 0 int xx = 0; cout << f(xx) << endl; // wypisze 1 cout << xx << endl; // wypisze 1; f() zmieniła wartość xx int yy = 7; cout << f(yy) << endl; // wypisze 8; f() zmieniła wartość yy cout << yy << endl; // wypisze 8 a:

Przekazywanie przez referencję Niebezpieczne, prowadzi do trudnych do znalezienia błędów kiedy zapomnimy, które argumenty mogą się zmienić int zwieksz1(int a) { return a+1; } void zwieksz2(int& a) { ++a; } int x = 7; x = zwieksz1(x); // to jest jasne zwiekszylismy x zwieksz2(x); // to jest niejasny sposób na zrobienie tego samego Po co więc przekazywać parametry przez referencję? Czasem jest to przydatne, np. przy zmienianiu kilku wartości naraz przy pracy z kontenerami (np. wektor) bardzo przydatne jest przekazywanie przez stałą referencję

Przekazywanie parametrów przez stałą referencję void f(int a, int& r, const int& cr) { ++a; ++r; ++cr; } // błąd: cr jest stałe (const) void g(int a, int& r, const int& cr) { ++a; ++r; int x = cr; ++x; } // ok int main() { int x = 0; int y = 0; int z = 0; g(x,y,z); // x==0; y==1; z==0 g(1,2,3); // błąd: argument referencyjny r musi odnosić się do zmiennej g(1,y,3); // ok: ponieważ cr jest stałą (const) możemy przekazać literał // literału nie można modyfikować z definicji jest stałą } // stałe referencje są bardzo przydatne do przekazywania dużych obiektów (oszczędzamy na czasie wykonywania kopii)

Referencje Referencja jest pojęciem ogólnym służy nie tylko do przekazywania parametrów funkcji int i = 7; int& r = i; r = 9; // i staje się 9 const int& cr = i; // cr = 7; // błąd: cr traktuje obiekt, do którego się odnosi, jak stałą i = 8; cout << cr << endl; // wypisuje wartość i (tj. 8) Referencję można traktować jako przezwisko obiektu Nie da się przestawić raz zainicjowanej referencji na inny obiekt zapis: int a=1,b=2; int& r = a; r = b byłby niejednoznaczny (sprawdź jak to działa?)

Wskazówki jak przekazywać argumenty funkcji przez wartość bardzo małe obiekty (np. typy wbudowane: char, int, double) przez stałą referencję duże obiekty przez referencję tylko jeśli musisz lepiej zwrócić wynik i zmodyfikować obiekt niż modyfikować obiekt przekazując referencję int zwieksz1(int a) { return a+1; } void zwieksz2(int& a) { ++a; } int x = 7; x = zwieksz1(x); // to jest jasne zwiekszylismy x zwieksz2(x); // to jest niejasny sposób na zrobienie tego samego

Kolizja nazw Piszemy program korzystając z klas opracowanych przez dwoje programistów Jacka i Agatkę: class Glob { /* */ }; class Widget { /* */ }; // w nagłówku Jacka jacek.h // także w jacek.h class Blob { /* */ }; class Widget { /* */ }; // w nagłówku Agatki agatka.h // także w agatka.h #include "jacek.h"; // to jest nasz kod, dołączamy nagłówki Jacka i Agatki #include "agatka.h"; // void my_func(widget p) // ups! błąd: wielokrotna definicja klasy Widget { // }

Kolizja nazw (2) Kompilator nie poradzi sobie z wielokrotnie zdefiniowanymi komponentami Takie konflikty nazw zdarzają się gdy korzystamy z wielu nagłówków Możemy ich uniknąć stosując przestrzenie nazw: namespace Jacek { class Glob { /* */ }; class Widget { /* */ }; } // w nagłówku Jacka jacek.h #include "jacek.h"; #include "agatka.h"; // to jest nasz kod // dołączamy nagłówki Jacka i Agatki void my_func(jacek::widget p) // ok! Widget Jacka nie pomyli się już { // z innymi Widgetami // }

Przestrzeń nazw Przestrzeń nazw to nazwany zakres Używamy składni :: aby określić, którą przestrzeń nazw mamy na myśli Na przykład cout jest w przestrzeni nazw std, dlatego piszemy: std::cout << "Proszę coś napisać \n";

Deklaracje i dyrektywy using Ponieważ pisanie std::cout jest niewygodne można użyć deklaracji using using std::cout; // czytaj: kiedy piszę cout, chodzi mi o std::cout cout << ''Proszę coś napisać: \n // To zadziała, chodzi o std:cout cin >> x; // Ale to już nie, bo cin nie jest w zakresie Dlatego wygodne może być zastosowanie dyrektywy using using namespace std; // czytaj: jeśli w bieżącym zakresie nie znajdziesz // deklaracji nazwy, szukaj jej w przestrzeni nazw std cout << ''Proszę coś napisać: \n // To zadziała, chodzi o std:cout cin >> x; // To też, chodzi o std:cin

Zagadnienia (2) Klasy Interfejs i implementacja Konstruktory Funkcje składowe Wyliczenia Przeładowywanie operatorów

Klasy Klasa bezpośrednio reprezentuje pojęcie w programie Jeśli o czymś można myśleć jako o oddzielnym bycie, to prawdopodobnie może to być klasą lub obiektem klasy Np. wektor, macierz, strumień wejściowy, łańcuch znakowy, szybka transformata furiera, kontroler zaworu, ramię robota, sterownik urządzenia, obraz na ekranie, okno dialogowe, wykres, okno, odczyt temperatur, zegar Klasa jest typem zdefiniowanym przez użytkownika, który określa jak tworzyć i używać obiekty tego typu Klasy po raz pierwszy wprowadzono w języku Simula67

Składowe klasy i dostęp do nich class X { // ta klasa nazywa się X // dane składowe (przechowują informacje) // funkcje składowe (robią coś z danymi składowymi) }; class X { public: int m; // dana składowa int mf(int v) { int old = m; m=v; return old; } // funkcja składowa }; X var; var.m = 7; int x = var.mf(9); // zmienna var typu X // dostęp do danej składowej m zmiennej var // wywołanie funkcji składowej mf() dla zmiennej var

Interfejs i implementacja klasy class X { // klasa o nazwie X public: // funkcje // typy // publiczne składowe klasy interfejs użytkownika // (dostępne dla wszystkich) // dane (najczęściej lepiej, żeby były prywatne) private: }; // funkcje // typy // dane // priwatne składowe klasy szczegóły implementacyjne // (dostępne tylko dla składowych klasy)

Struktury i klasy Składowe klasy są domyślnie prywatne, czyli zapis: class X { int mf(); // }; oznacza tyle co: class X { private: int mf(); // }; Więc: X x; // zmienna x typu X int y = x.mf(); // błąd kompilacji: mf jest prywatna (niedostępna)

Struktury i klasy (2) Składowe struktury są domyślnie publiczne, czyli zapis: struct X { int m; // }; oznacza tyle co: class X { public: int m; // }; Rodzi się pytanie po co ukrywać składowe?

Po co komu prywatne składowe? Aby stworzyć przejrzysty interfejs klasy Dane i zagmatwane funkcje można ukryć Aby kontrolować niezmienniki Dzięki ograniczonemu zbiorowi funkcji z dostępem Aby ułatwić debugowanie Zawężenie grona podejrzanych Aby umożliwić zmianę reprezentacji (danych składowych) Wystarczy zmienić ograniczony zbiór funkcji W przypadku składowej publicznej nie można nigdy wiedzieć, kto ją używa

Struktura tylko dane (jak w języku C) // najprostsza wersja Date (tylko dane) struct Date { }; int y,m,d; // rok, miesiąc, dzień my_birthday: y m d Date: Date my_birthday; // zmienna typu Date (obiekt) my_birthday.y = 12; my_birthday.m = 30; my_birthday.d = 1950; // Ups! (nie ma dnia 1950 w miesiącu 30) // Błąd nie został zgłoszony, ale dalej w programie // będziemy mieli problemy

Struktura tylko dane (jak w języku C) podejście 2 // prosta wersja Date (dodaliśmy kilka funkcji pomocnicznych) struct Date { }; int y,m,d; // rok, miesiąc, dzień my_birthday: y Date: m d Date my_birthday; // zmienna typu Date (obiekt) // funkcje pomocnicze: void init_day(date& dd, int y, int m, int d); void add_day(date&, int n); // // sprawdź poprawność daty i inicjalizuj // zwiększ obiekt Date o n dni init_day(my_birthday, 12, 30, 1950); // błąd czasu wykonania: // nie ma dnia 1950 w miesiącu 30 Świetnie, ale nie ma gwarancji, że użytkownik struktury użyje funkcji init_day() po utworzeniu zmiennej typu Date.

Struktura w stylu C++ my_birthday: y Date: m // prosta Date // gwarantuje inicjalizację przy użyciu konstruktora d // zapewnia pewną wygodę notacyjną struct Date { int y,m,d; // rok, miesiąc, dzień Date(int y, int m, int d); // konstruktor: sprawdza poprawność daty i inicjuje void add_day(int n); // zwiększa datę o n dni }; 1950 // Date my_birthday; // błąd: my_birthday nie zainicjowane (I dobrze!) Date my_birthday(12, 30, 1950); // ups! Błąd czasu wykonania Date my_day(1950, 12, 30); // ok my_day.add_day(2); // 1 stycznia 1951 my_day.m = 14; // grrrhh! (teraz my_day jest niepoprawną datą) Świetnie, poprawna inicjacja jest zagwarantowana. Niestety, publiczny dostęp do danych składowych Date grozi zepsuciem daty 12 30

Klasa (z kontrolą dostępu) m 12 // prosta Date (z kontrolą dostępu) class Date { d 30 int y,m,d; // rok, miesiąc, dzień public: Date(int y, int m, int d); // konstruktor: sprawdza poprawność daty i inicjuje }; // funkcje dostępowe: void add_day(int n); // zwiększa datę o n dni int month() { return m; } int day() { return d; } int year() { return y; } // Date my_birthday(1950, 12, 30); cout << my_birthday.month() << endl; my_birthday.m = 14; my_birthday: y Date: 1950 // ok // możemy odczytać miesiąc // błąd: składowa Date::m jest prywatna

Niezmienniki Pojęcie poprawnej daty jest szczególnym przypadkiem pojęcia poprawnej wartości Zasada: zagwarantować poprawne wartości dla projektowanego typu (poprawny stan obiektu) Inaczej musielibyśmy cały czas sprawdzać poprawność danych lub liczyć, że zrobi to użytkownik klasy Regułę opisującą poprawną wartość nazywa się niezmiennikiem W przypadku daty jest to wyjątkowo trudne (28 luty, rok przestępny itd.) Jeśli nie można znaleźć dobrego niezmiennika pracujesz na samych czystych danych Użyj struktury Pierwszą opcją jest jednak zawsze szukanie niezmienników!

Prosty konstruktor klasy // prosta Date (niektórzy wolą interfejs klasy na początku dlaczego?) d 30 class Date { public: Date(int y, int m, int d); // konstruktor: sprawdza poprawność daty i inicjuje void add_day(int n); int month(); // private: int y,m,d; }; // rok, miesiąc, dzień Date::Date(int yy, int mm, int dd) :y(yy), m(mm), d(dd) { /* */ }; // zwiększa datę o n dni my_birthday: y Date: m // definicja konstruktora klasy // inicjacja składowych 1950 12 void Date::add_day(int n) { /* */ }; // definicja funkcji

Konstruktor z kontrolą poprawności // prosta Date (co zrobimy z nieprawidłową datą) class Date { public: class Invalid { }; // użyta jako rakieta sygnalizacyjna // przy obsłudze wyjątków Date(int y, int m, int d); // sprawdza poprawność daty i inicjuje // private: int y,m,d; // rok, miesiąc, dzień bool check(int y, int m, int d); // czy (y,m,d) jest poprawną datą? }; Date:: Date(int yy, int mm, int dd) : y(yy), m(mm), d(dd) // inicjuje dane składowe { if (!check(y,m,d)) throw Invalid(); // sprawdza poprawność daty }

Wyliczenia Wyliczenie enum (ang. enumaration) jest typem użytkownika określającym zestaw wartości (elementy wyliczenia) Na przykład: enum Month { jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec }; Month m = feb; m = 7; // błąd: nie można przypisać int do Month int n = m; // ok: możemy dostać wartość liczbową Month Month mm = Month(7); // konwersja typu int na Month (nie sprawdzana)

Zastosowanie wyliczeń Prosta lista stałych enum { red, green }; int a = red; enum { red, blue, purple }; // enum { } nie definiuje zakresu // red jest dostępne tutaj // błąd: red jest już zdefiniowany Typ z listą stałych enum Day { sun, mon, tue, /* */ }; // domyślnie: sun==0, mon==1, tue==2 enum Color { red=100, green=1, blue=10, /* */ }; Day m1 = sun; Day m2 = red; Day m3 = 7; int i = m1; // błąd: red nie jest wartością Day // błąd: 7 nie jest wartością Day // ok: element wyliczenia jest konwertowany do // swojej wartości, i==0

Klasa z wyliczeniem // prosta Date (używa typu Month) class Date { public: my_birthday: y enum Month { jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec }; Date(int y, Month m, int d); // sprawdza poprawność daty i inicjuje // private: int y; // rok Month m; int d; // dzień }; Date: m d 1950 12 30 Date my_birthday(1950, 30, Date::dec); Date my_birthday(1950, Date::dec, 30); // błąd: 2. argument nie jest miesiącem // ok enum pomaga kontrolować poprawność // zwróć też uwagę na zakres: Date::dec

Składniki stałe klasy (const) class Date { public: // int day() const { return d; } void add_day(int n); // }; // stały składnik: nie może modyfikować // nie-stały składnik: może modyfikować Date d(2000, Date::jan, 20); const Date cd(2001, Date::feb, 21); // zmienna d typu Date // stała cd typu Date cout << d.day() << " " << cd.day() << endl; // ok d.add_day(1); // ok cd.add_day(1); // błąd: cd jest const // Tylko stałe funkcje składowe są dopuszczane // do działania na stałych obiektach klasy

Składniki stałe klasy (2) // Date d(2004, Date::jan, 7); const Date d2(2004, Date::feb, 28); d2 = d; // błąd: d2 jest const d2.add(1); // błąd: d2 jest const d = d2; // w porządku d.add(1); // w porządku // zmienna // stała d2.f(); // Taka operacja powinna zostać wykonana wtedy i tylko wtedy, gdy // funkcja składowa f() nie modyfikuje danych składowych obiektu d2 // Jak to zapewnić? (zakładając, że tego właśnie chcemy)

Składniki stałe klasy (3) // Rozróżnij pomiędzy funkcjami, które mogą modyfikować obiekty // I tymi, które nie mogą zwanymi stałymi funkcjami składowymi class Date { public: // int day() const; // pobierz dzień (w zasadzie jego kopię) // void add_day(int n); // przesuń datę o n do przodu // }; const Date dx(2008, Month::nov, 4); int d = dx.day(); // w porządku dx.add_day(4); // błąd: nie można modyfikować stałych danych

Dobry interfejs klasy Minimalny Tak mały jak to możliwe Kompletny I nie mniejszy Bezpieczny dla typów (ang. type-safe) Uwaga na kolejność argumentów Poprawnie określać stałe składniki klasy (ang. const-correct)

Minimalny zestaw składowych Najbardziej podstawowe operacje Konstruktor domyślny (domyślnie: nie robi nic lub brak domyślnego konstruktora jeśli zadeklarowano inny konstruktor Konstruktor kopiujący (domyślnie: kopiuje składowe) Operator przypisania (domyślnie: kopiuje składowe) Destruktor (domyślnie: nie robi nic) Na przykład Date d; Date d2 = d; d = d2; // błąd: brak domyślnego konstruktora (zadeklarowaliśmy inny!) // ok: inicjacja kopią (kopiuje elementy domyślne) // ok: przypisanie kopii (kopiuje elementy domyślne)

Interfejs klasy i funkcje pomocnicze Chcemy minimalny interfejs klasy (zbiór funkcji pomocnicznych), bo to: Ułatwia zrozumienie klasy Upraszcza debugowanie Upraszcza pielęgnację Potrzebujemy funkcji pomocniczych, operujących na obiektach klasy, ale zdefiniowanych poza klasą, np. == (równość),!= (nierówność) next_weekday(), next_sunday()

Funkcje pomocnicze Date next_sunday(const Date& d) { // ma dostęp do obiektu d poprzez d.day(), d.month(), oraz d.year() // tworzy nowy obiekt typu Date który zwraca } Date next_weekday(const Date& d) { /* */ } bool operator==(const Date& a, const Date& b) { return a.year()==b.year() && a.month()==b.month() && a.day()==b.day(); } bool operator!=(const Date& a, const Date& b) { return!(a==b); }

Przeładowanie operatorów Można zdefiniować (czyli przeładować) prawie wszystkie operatory dla operandów typu użytkownika (klasa, wyliczenie), np. enum Month { jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec }; Month operator++(month& m) { } m = (m==dec)? jan : Month(m+1); return m; // operator inkrementacji prefiksowej // zawijanie Month m = nov; ++m; // m zmienia się na dec ++m; // m zmienia się na jan

Zasady przeładowania operatorów Można definiować działania tylko istniejących operatorów np. + - * / % [] () ^! & < <= > >= Trzeba zachować konwencjonalną liczbę operandów np., nie ma unarnego (jednoargumentowego) <= (mniejsze-równe) albo binarnego (dwuargumentowego)! (negacja) Każdy przeładowany operator musi mieć przynajmniej jeden operand typu użytkownika int operator+(int,int); // błąd: nie można przeładować wbudowanego+ Wielomian operator+(const Wielomian&, const Wielomian &); // ok Dobre rady: nadawaj operatorom tylko konwencjonalne znaczenie + powinnien być dodawaniem, * - mnożeniem, [] - dostępem, a () wywołaniem przeładowuj tylko jeśli to naprawdę potrzebne

A za dwa tygodnie... Strumienie wejścia/wyjścia