Programowanie obiektowe C++Programowanie zorientowane obiektowo
Wykład 0
Witold [email protected]
26/09/2011
Prawa autorskie itp.
Wiele slajdów do tego wykładu powstało w oparciu o:● slajdy Bjarne Stroustrupa
do kursu Foundations of Engineering II (C++) prowadzonego w Texas A&M Universityhttp://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 programowania zorientowanego obiektowo
w języku 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
Plan na dziś● Bezpieczeństwo typów● Kontener vector● Obsługa błędów
Bezpieczeństwo typówZasada bezpieczeństwa typów:● Każdy obiekt będzie użyty tylko zgodnie z jego typem
● Zmienna będzie użyta dopiero po inicjacji● Tylko operacje zdefiniowane dla zadeklarowanego typu zmiennej będą użyte● Każda operacja zdefiniowana dla zmiennej nadaje jej prawidłową wartość
Ideał statycznej kontroli typów● Program naruszający bezpieczeństwo typów nie zostanie
skompilowany● Kompilator zgłasza każde naruszenie
Ideał dynamicznej kontroli typów● Jeśli program narusza bezpieczeństwo typów, zostanie to wykryte
w czasie uruchomienia● Istnieje kod, który wykrywa każde naruszenie nie znalezione przez kompilator
Bezpieczeństwo typów w C++C++ nie zapewnia (pełnej) statycznej kontroli typów● Żaden powszechnie używany język nie jest w pełni bezpieczny
● Pełna statyczna kontrola typów mogłaby za bardzo ograniczać programistę
C++ nie zapewnia (pełnej) dynamicznej kontroli typów● Istnieją inne języki, które ją zapewniają
● Pełna dynamiczna kontrola typów mogłaby za bardzo ograniczać programistę● Pełna dynamiczna kontrola typów często generuje większy i wolniejszy kod
// Uwaga: C++ nie zabrania próby umieszczenia dużej wartości// w małej zmiennej (chociaż kompilator może ostrzec przed tym).
int main(){
int a = 20000;char c = a;int b = c;if (a != b) // != oznacza relację nierówności
cout << "ups!: " << a << "!=" << b << '\n';else
cout << "Świetnie! Mamy duże znaki\n";}
Spróbuj, jaka będzie wartość b na Twoim komputerze
20000a:
???c:
Naruszenie bezpieczeństwa typówniejawne zawężanie (ang. implicit narrowing)
// Uwaga: C++ pozwala użyć niezainicjowanej zmiennej. // Jednak kompilator przeważnie ostrzega przed tym.
int main(){
int x; // x otrzymuje “losową” wartość początkowąchar c; // c otrzymuje“losową” wartość początkowądouble d; // d otrzymuje “losową” wartość początkową
// – nie każda zawartość komórki pamięci stanowi poprawną// wartość zmiennoprzecinkową
double dd = d; // potencjalny błąd: niektóre implementacje // nie kopiują niepoprawnych wartości zmiennoprzecinkowych
cout << " x: " << x << " c: " << c << " d: " << d << '\n';}
Zawsze inicjuj swoje zmienne. Wyjątek: zmienne, których wartości są wprowadzane z zewnątrz.Uwaga: tryb debugowania może inicjować zmienne
Naruszenie bezpieczeństwa typów(niezainicjowana zmienna)
Plan na dziś● Bezpieczeństwo typów● Kontener vector● Obsługa błędów
Vector – kontener danych● W zasadzie wszystkie interesujące obliczenia dotyczą zbiorów danych.● Do zapisu kolekcji danych może posłużyć klasa kontenerowa vector
// wczytywanie pomiarów temperaturyint main(){
vector<double> temps; // deklaracja zmiennej temps, która jest wektorem typu double// przechowującej listę temperatur (takich jak 32.4)
double temp; // zmienna przechowująca pojedyńczy pomiarwhile (cin>>temp) // cin wczytuje wartość i zapisuje ją w temp
temps.push_back(temp); // zapisanie temperatury w wektorze// … inne operacje …
}// wyrażenie cin>>temp będzie spełnione (wartość true) dopóki na wejściu cin// nie pojawi się coś czego nie można uznać za double: np. wyraz „koniec”
1 4 2 3 5
5v:
elementy v:
v[0] v[1] v[2] v[3] v[4]
size()
Wektor● Vector jest typem danych zdefiniowanym w bibliotece
standardowej C++● vector<T> przechowuje sekwencję wartości typu T● Można to wyobrazić sobie tak:
● Niech wektor o nazwie v posiada 5 elementów: {1,4,2,3,5}:
v:
Uzupełnianie wektora danymivector<int> v;vector<int> v; //// tworzy pusty wektortworzy pusty wektor
v.push_back(1);v.push_back(1); // // dodaje element o wartości dodaje element o wartości 11
v.push_back(4);v.push_back(4); // // dodaje element o wartości dodaje element o wartości 44//// na końcu ( na końcu (the backthe back))
v.push_back(3);v.push_back(3); // // dodaje element o wartości dodaje element o wartości 33 //// na końcu na końcu 3 341
2 41
1 1
0
vv[0] v[1] v[2][0] v[1] v[2]
v.push_back(3);zmienna (obiekt) metoda (funkcja)
Używanie wektorów// liczenie średniej i mediany temperatur:int main(){vector<double> temps; // temperatury w stopniach Celsjuszadouble temp;while (cin>>temp) temps.push_back(temp); // wczytuje wektor temperaturdouble sum = 0;for (int i = 0; i< temps.size(); ++i) sum += temps[i]; // suma temperatur cout << "Średnia temperatury: " << sum/temps.size() << endl; sort(temps.begin(),temps.end()); cout << "Mediana temperatury: " << temps[temps.size()/2] << endl;}
Ideał● Powinniśmy móc w dowolny sposób wykorzystać
podstawowe elementy języka i podstawowe biblioteki ● Sprawdźmy czy jest tak z wczytywaniem i sortowaniem
wektorów?
Przykład – lista słów/* Lista slow – kod samego sedna programu:
wczytuje listę łańcuchów do wektora łańcuchów,sortuje w kolejności alfabetyczneji drukuje posortowany wektor.
*/vector<string> words;string s;while (cin>>s && s != "quit") // && oznacza AND (iloczyn logiczny) words.push_back(s); // wczytywanie
sort(words.begin(), words.end()); // sortowaniefor (int i=0; i<words.size(); ++i) cout<<words[i]<< "\n"; // drukowanie
Przykład – usuwanie powtórzeń// Program usuwa powtórzone wyrazy
vector<string> words;
string s;
while (cin>>s && s!= "quit") words.push_back(s);
sort(words.begin(), words.end());
for (int i=1; i<words.size(); ++i)
if(words[i-1]==words[i])
“pozbądź się i-tego wyrazu (words[i])” // (pseudokod)
for (int i=0; i<words.size(); ++i) cout<<words[i]<< "\n";
// Usunąć powtórzone wyrazy można na wiele sposobów, niektóre całkiem zagmatwane.
// Celem dobrego programisty jest wybrać proste i jasne rozwiązanie
// biorąc pod uwagę takie ograniczenia jak czas kodowania, czas działania, pamięć.
Usuwanie wyrazów – rozwiązanie// Usuwa powtórzone wyrazy poprzez kopiowanie tylko wyrazów unikalnych
vector<string> words;
string s;
while (cin>>s && s!= "quit") words.push_back(s);
sort(words.begin(), words.end());
vector<string>w2; // wektor przechowujący wyrazy unikalne
if (0<words.size()) { // rób tylko jeśli wektor words nie pusty
w2.push_back(words[0]); // wstaw pierwszy element do w2
for (int i=1; i<words.size(); ++i)
if(words[i-1]!=words[i]) // jeśli dwa kolejne wyrazy w words różne
w2.push_back(words[i]); // dodaje nowy wyraz do w2
}
cout<< "znaleziono " << words.size()-w2.size() << " powtórzeń\n";
for (int i=0; i<w2.size(); ++i) cout << w2[i] << "\n";
Plan na dziś● Bezpieczeństwo typów● Kontener vector● Obsługa błędów
Błędy w programie● Podstawowym celem programisty jest
poprawność kodu
● “ … uświadomiłem sobie, że od teraz dużą część mojego życia spędzę szukając i poprawiając moje własne błędy.” Maurice Wilkes
● “Przypuszczam, że unikanie, znajdowanie i poprawianie błędów stanowi 95% lub więcej wysiłku w poważnym tworzeniu oprogramowania.” Bjarne Stroustrup
Błędy w programie● Źródła błędów● Rodzaje błędów● Sprawdzanie argumentów funkcji
● Raportowanie błędów● Wykrywanie błędów● Wyjątki
● Debugowanie (odrobaczanie)● Testowanie
Źródła błędów● Słaba specyfikacja
(80% dużych projektów wysypuje się przez złą specyfikację)
● Niekompletny program● Nieoczekiwane argumenty funkcji
(np. funkcja sqrt() nie przyjmie wartości -1)
● Niespodziewane dane wejściowe(np. uszkodzony plik z danymi)
● Niespodziewany stan● Błędy logiczne
Rodzaje błędów● Błędy kompilacji
● błędy typów● błędy składni
● Błędy konsolidacji● Błędy czasu wykonania (ang. run-time)
● wykryte przez system („program się wysypał”)● wykryte przez biblioteki (wyjątki)● wykryte przez kod użytkownika
● Błędy logiczne● wykryte przez programistę/testera
Oczekiwania
Każdy program1)Powinien wytworzyć pożądany wynik dla każdego
poprawnego wejścia
2)Powinien zwrócić sensowny komunikat o błędzie dla niepoprawnych danych wejściowych
Twój program3)Nie musi przejmować się błędami sprzętu
4)Nie musi przejmować się błędami systemu5)Może zakończyć działanie po znalezieniu błędu
Błędy argumentów funkcji
Kompilator sprawdza liczbę i typy argumentów funkcji:
int pole(int wysokosc, int szerokosc){ return wysokosc*szerokosc;}
int x1 = pole(7); // błąd kompilacji: zła liczba argumentówint x2 = pole("siedem", 2); // błąd kompilacji: argument 1 ma zły typint x3 = pole(7, 10); // okint x5 = pole(7.5, 10); // działa, ale: 7.5 zostanie obcięte do 7;
// większość kompilatorów zgłosi ostrzeżenieint x = pole(10, -7); // typy się zgadzają, ale wartość -7 nie ma sensu!!!
Obsługa błędówint x = pole(10, -7); // typy się zgadzają, ale wartość -7 nie ma sensu!!!
● Tym błędem musi zająć się programista● Dwie strategie sprawdzania poprawności argumentów
● sprawdza wywołujący funkcję+ wie jak obsłużyć błąd- musi znać specyfikację funkcji, dłuższy i nieczytelny kod
● sprawdza funkcja wywoływana+ prostszy, bardziej elegancki kod- nie wie co zrobić z błędem
● Pomysł: funkcja zwraca informacje o błędzie, a wywołujący decyduje co z tym zrobić
● Zwracanie wartości błędu int pole(int wysokosc, int szerokosc) // zwraca -1 jeśli błędne dane
{
if(wysokosc <=0 || szerokosc <= 0) return -1;
return wysokosc*szerokosc;
}● Wywołujący musi (może?) sprawdzić wynik i podjąć działanie
int z = pole(x,y);
if (z<0) {
cerr<<"Problem z obliczaniem pola";
return;
// …● Kolejny problem: jaką wartość błędu może zwracać funkcja max()?
Raportowanie błedów (1)
Raportowanie błędów (2)● Ustawianie znacznika błędu:
int errno = 0;
int pole(int wysokosc, int szerokosc) {
if(wysokosc <=0 || szerokosc <= 0) errno = 7;
return wysokosc*szerokosc;
}
● Wywołujący musi (może?) sprawdzić wynik i podjąć działanieint z = pole(x,y);
if (errno==7) {
cerr<<"Problem z obliczaniem pola";
return;
// …
● Te same problemy co poprzednio – nie używaj!!
● Zgłaszanie wyjątków:class Zle_pole { }; // Tak w C++ definiujemy typ użytkownika, tutaj – typ błędu
int pole(int wysokosc, int szerokosc) {
if (wysokosc <=0 || szerokosc <= 0) throw Zle_pole(); // wyrzucamy obiekt typu
return wysokosc*szerokosc; // Zle_pole
}
● Wywołujący próbuje czy we fragmencie kodu nie zgłoszono wyjątku, chwyta go i decyduje co zrobićtry {
int z = pole(x,y); // jeżeli pole() nie zgłosiło wyjątku
} // wykonuje przypisanie i idzie dalej
catch(Zle pole) { // jeżeli pole() zgłosiło wyjątek Zle_pole(), obsługuje go:
cerr << "Ups! Problem z obliczaniem pola";}
Raportowanie błedów (3)
Wyjątki - idea
www.sidzina.net.pl
msw-pttk.org.pl
artofmanliness.com
Program, czyli grupa funkcji przybywana dzikie i niebezpieczne tereny. Jedna z nich wyrusza na rekonesans
Wtem, napotyka problem:
Reszta „chwyta”, że żółta flara oznacza misia i podejmuje dalsze działania
Sygnalizuje go rzucając wyjątek:
Dlaczego wyjątki?● Obsługa wyjątków jest uniwersalna
● Nie można o nich zapomnieć: wystarczy, że umieścisz całość programu w bloku try … catch, a program zakończy działanie, jeśli wyjątek pojawi się i nie zostanie obsłużony
● Prawie każdy typ błędu można zgłosić wyjątkiem
● Jednak to wciąż programista musi zastanowić się co zrobić z każdym wyjątkiem
Wyjątki – błąd „poza zakresem”int main()try { vector<int> v(10); // wektor 10-ciu int-ów for (int i = 0; i<v.size(); ++i) v.at(i) = i; // ustawiamy wartości
// at(i) = [i] + wyjątek zakresu for (int i = 0; i<=10; ++i) // drukujemy 10 wartości (???) cout << "v[" << i << "] == " << v.at(i) << endl;} catch (out_of_range&) { // wyjątek out_of_range cerr << "ups – któryś indeks wektora poza zakresem\n";} catch (…) { // wszystkie inne wyjątki cerr << "ups – jakiś błąd\n";}
Odrobaczanie
Na proces debugowania składa się:1) Zauważenie, że coś działa inaczej niż oczekiwano2) Sprawdzenie co tak na prawdę się dzieje3) Poprawienie
A jak tego nie robić?dopóki (program nie sprawia wrażenia działającego) {
zaglądam tu i tam, szukając czegoś co wygląda dziwnie;
zmieniam to, aby wyglądało “lepiej”;
}
Krok 1: zadbaj o strukturę programu
Jeżeli masz znaleźć błąd– program musi być łatwy do czytania1) Komentuj – wyjaśniaj swoje pomysły2) Używaj znaczących nazw3) Rób wcięcia – trzymaj się jednego stylu4) Podziel kod na małe funkcje - unikaj funkcji dłuższych
niż strona kodu5) Jeśli to możliwe, unikaj skomplikowanego kodu, np.
zagnieżdżonych pętli i instrukcji warunkowych
6) Używaj funkcji bibliotecznych – są sprawdzone
Krok 2: skompiluj program
Jeśli program się nie kompiluje - sprawdź:1) Czy zakończyłeś literały łańcuchowe ('') i znakowe (')?
2) Czy zakończyłeś każdy blok ({ })3) Czy zamknąłeś wszystkie nawiasy?4) Czy każda nazwy została zadeklarowana?
5) Czy każda nazwa została zadeklarowana przed użyciem?6) Czy zakończyłeś każdą instrukcję wyrażeniową
średnikiem?
Krok 3: Prześledź program● Podejdź do tego jakbyś był komputerem
● Czy dane na wyjściu spełniają Twoje oczekiwania?● Warto „wyrzucić” wartości paru zmiennych na wyjście błędu:
cerr << "x == " << x << ", y == " << y << '\n';● Uważaj – musisz zobaczyć to co programu rzeczywiście
robi, nie to co uważasz, że powinien robić:for (int i=0; 0<month.size(); ++i) { // błąd!for( int i = 0; i<=max; ++j) { // podwójny błąd!
● Gdzie szukać błędu?
Krok 3: Gdzie szukać błędu?● Wprowadź „testy poczytalności” (ang. sanity checks),
if (liczba_elementow<0)
throw runtime_error("ujemna liczba elementow");if (maksymalny_sensowny<liczba_elementow)
throw runtime_error("nieoczekiwanie wielka liczba elementow");if (x<y) throw runtime_error("niemozliwosc: x<y");
● Napisz je tak, aby nie trzeba było ich usuwać z kodu w momencie, gdy program będzie wyglądał na ukończony
Krok 3: Gdzie szukać błędu?Warunki początkowe i końcowe
class Zly_argument { };
class Zle_pole { };
int pole(int wysokosc, int szerokosc) {
if (wysokosc <=0 || szerokosc <= 0) throw Zly_argument();int p = wysokosc*szerokosc;if (p<=0) throw Zle_pole();
return p;
}
Zasady● Zawsze przemyśl warunki początkowe i końcowe● Zapisz je przynajmniej w postaci komentarza● Sprawdzaj, gdy szukasz błędu● Kiedy w przykładzie warunek początkowy będzie spełniony, a końcowy nie?
Krok 3: Gdzie szukać błędu? Sprawdź przypadki brzegowe
● Czy zainicjowałeś każdą zmienną sensowną wartością?● Czy funkcja dostała prawidłowe argumenty?● Czy funkcja zwróciła prawidłową wartość?● Czy poprawnie obsłużyłeś pierwszy i ostatni element?
(np. pętli)● Czy poprawnie obsłużyłeś przypadki puste?
(brak elementów, brak wejścia)● Czy poprawnie otworzyłeś plik?● Czy rzeczywiście wczytałeś wejście i zapisałeś wyjście?
Kilka ogólnych zasad● “Jeżeli nie możesz znaleźć błędu, szukasz w złym
miejscu”● Nie dowierzaj swojemu przekonianiu, że wiesz gdzie jest
błąd● Nie zgaduj, kieruj się danymi na wyjściu:
– Szukaj od miejsca, gdzie wiesz, że jest okCo się dzieje dalej, dlaczego?
– Szukaj przed miejscem, gdzie dostałeś zły wynikCo mogło się wydarzyć?
● Znalezłeś robaka? ● Zastanów się, czy naprawienie go rozwiązuje cały problem● Często „szybka poprawka” wprowadza nowe błędy
● Jeśli programista mówi: „znalazłem ostatni błąd”, to żartuje;-)
Dziś najważniejsze było to, że...● Każdy obiekt używamy tylko zgodnie z jego typem
● Nie wymyślamy koła!● wykorzystujemy bibliotekę standardową,
– np. typ vector zamiast tablicy
● Raportujemy błędy przy użyciu mechanizmu wyjątków
● Jeśli programista mówi: „znalazłem ostatni błąd”, to żartuje;-)
A za 2 tygodnie...
...zajmiemy się klasami:
● Klasy● Implementacja i interfejs● Konstruktory● Funkcje składowe● Składowe stałe● Składowe statyczne
● Typ wyliczeniowy
Top Related