Wstep do Programowania 2
Transkrypt
Wstep do Programowania 2
Wstep ˛ do Programowania 2 dr Bożena Woźna-Szcześniak [email protected] Akademia im. Jana Długosza Wykład 8 Przykład realizowany na wykładzie Klasy StringBad i String. Wstepne ˛ pliki załaczone ˛ jako źródła. Przykład z ksiażki: ˛ Stephen Prata. Jezyk ˛ C++. Szkoła programowania. Wydanie V. StringBad - interfejs #include <iostream> #ifndef STRNGBAD_H_ #define STRNGBAD_H_ class StringBad { private: char * str; // wskaznik ciagu int len; // dlugosc ciagu static int num_strings; // liczba obiektow public: StringBad(const char * s); // konstruktor StringBad(); // konstruktor domyslny ~StringBad(); // destruktor // funkcja zaprzyjazniona friend std::ostream & operator<<(std::ostream & os, const StringBad & st); }; #endif Uwagi do przykładu Składowa statyczna, np. static int num_strings;, ma specjalna˛ własność: program tworzy tylko jedna˛ jej kopie, ˛ niezależnie od tego ile obiektów danej klasy zostało powołanych do życie. Wniosek: składowa statyczna jest współdzielona przez wszytkie obiekty klasy. Klasa StringBad - implementacja #include <cstring> #include "strngbad.h" using std::cout; // inicjalizacja statycznej skladowej klasy int StringBad::num_strings = 0; // metody klasy // konstruuje obiekt StringBad na bazie ciagu jezyka C StringBad::StringBad(const char * s) { len = std::strlen(s); // ustalenie rozmiaru str = new char[len + 1]; // przydzial odpowiedniej ilosci pamieci std::strcpy(str, s); // inicjalizacja wskaxnika num_strings++; // uaktualnienie licznika obiektów cout << num_strings << ": \"" << str << "\" - obiekt utworzony.\n"; // komunikat diagnostyczny } Uwagi do przykładu Składowej statycznej nie można inicjalizować wewnatrz ˛ deklaracji klasy, gdyż deklaracja ta zawiera jedynie opis sposobu alokacji pamieci, ˛ jednak samej alokacji nie wykonuje. Alokacja i inicjalizacja pamieci ˛ dokonuje sie˛ w momencie utworzenia obiektu danej klasy. Pola statyczne inicjalizowane sa˛ niezależnie za pomoca˛ instrukcji znajdujacej ˛ sie˛ poza deklaracja˛ klasy - powodem jest fakt, że składowe statyczne nie sa˛ przechowywane jako cz˛eść obiektu. Klasa StringBad - implementacja StringBad::StringBad() // konstruktor domyslny { len = 4; str = new char[4]; std::strcpy(str, "C++"); // ciag domyslny num_strings++; cout << num_strings << ": \"" << str << "\" - obiekt domyslny utworzony.\n"; // komunikat diagnostyczny } StringBad::~StringBad() // niezbedny teraz destruktor { cout << "\"" << str << "\" - obiekt usuniety, ";// diagnostyka --num_strings; // koniecznosc cout << "sa jeszcze " << num_strings << ".\n"; // diagnostyka delete [] str; // koniecznosc } std::ostream & operator<<(std::ostream & os, const StringBad & st) { os << st.str; return os; } Klasa StringBad - test dzialania operatorow new i delete #include <iostream> using std::cout; #include "strngbad.h" void callme1(StringBad &); // obiekt przekazywany przez referencje void callme2(StringBad); // obiekt przekazywany przez wartosc int main() { using std::endl; StringBad headline1("Niech zyje bal !"); StringBad headline2("Ferie tuz tuz ..."); StringBad sports("Narciarstwo"); cout << "Z ostatniej chwili: " << headline1 << endl; cout << "Temat dnia: " << headline2 << endl; cout << "Wiadomosci sportowe: " << sports << endl; callme1(headline1); cout << "Z ostatniej chwili: " << headline1 << endl; callme2(headline2); cout << "Temat dnia: " << headline2 << endl; cout << "Inicjalizacja obiektu ciagu innym obiektem:\n"; StringBad sailor = sports; cout << "Z kraju: " << sailor << endl; cout << "Przypisanie obiektu do innego obiektu:\n"; StringBad knot; knot = headline1; cout << "Ze swiata: " << knot << endl; cout << "Koniec main()\n"; return 0; } Klasa StringBad - test dzialania operatorow new i delete #include <iostream> using std::cout; #include "strngbad.h" void callme1(StringBad &); // obiekt przekazywany przez referencje void callme2(StringBad); // obiekt przekazywany przez wartosc int main() { ... } void callme1(StringBad & rsb) { cout << "Obiekt ciagu przekazany przez referencje:\n"; cout << " \"" << rsb << "\"\n"; } void callme2(StringBad sb) { cout << "Obiekt ciagu przekazany przez wartosæ:\n"; cout << " \"" << sb << "\"\n"; } Klasa StringBad - test dzialania operatorow new i delete bws@bws:~/wyklady/WstepDoProg2/wyklad8$ ./a.out 1: "Niech zyje bal !" - obiekt utworzony. 2: "Ferie tuz tuz ..." - obiekt utworzony. 3: "Narciarstwo" - obiekt utworzony. Z ostatniej chwili: Niech zyje bal ! Temat dnia: Ferie tuz tuz ... Wiadomosci sportowe: Narciarstwo Obiekt ciagu przekazany przez referencje: "Niech zyje bal !" Z ostatniej chwili: Niech zyje bal ! Obiekt ciagu przekazany przez wartosc: "Ferie tuz tuz ..." "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 2. Temat dnia: Inicjalizacja obiektu ciagu innym obiektem: Z kraju: Narciarstwo Przypisanie obiektu do innego obiektu: 3: "C++" - obiekt domyslny utworzony. Ze swiata: Niech zyje bal ! Koniec main() "Niech zyje bal !" - obiekt usuniety, sa jeszcze 2. "Narciarstwo" - obiekt usuniety, sa jeszcze 1. "" - obiekt usuniety, sa jeszcze 0. *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000000ffe050 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x78a96)[0x7f33cc61ca96] ./a.out[0x400e9f] ...... ======= Memory map: ======== 00602000-00603000 rw-p 00002000 08:07 1574202 Gdzie jest problem ???? #include <iostream> using std::cout; #include "strngbad.h" void callme1(StringBad &); // obiekt przekazywany przez referencje void callme2(StringBad); // obiekt przekazywany przez wartosc int main() { using std::endl; StringBad headline1("Niech zyje bal !"); StringBad headline2("Ferie tuz tuz ..."); StringBad sports("Narciarstwo"); cout << "Z ostatniej chwili: " << headline1 << endl; cout << "Temat dnia: " << headline2 << endl; cout << "Wiadomosci sportowe: " << sports << endl; //.... return 0; } Wykonanie – problemu brak ws@bws:~/WstepDoProg2/wyklad8$ ./a.out 1: "Niech zyje bal !" - obiekt utworzony. 2: "Ferie tuz tuz ..." - obiekt utworzony. 3: "Narciarstwo" - obiekt utworzony. Z ostatniej chwili: Niech zyje bal ! Temat dnia: Ferie tuz tuz ... Wiadomosci sportowe: Narciarstwo "Narciarstwo" - obiekt usuniety, sa jeszcze 2. "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 1. "Niech zyje bal !" - obiekt usuniety, sa jeszcze 0. bws@bws:~/WstepDoProg2/wyklad8$ Gdzie jest problem ???? #include <iostream> using std::cout; #include "strngbad.h" void callme1(StringBad &); // obiekt przekazywany przez referencje void callme2(StringBad); // obiekt przekazywany przez wartosc int main() { using std::endl; StringBad headline1("Niech zyje bal !"); StringBad headline2("Ferie tuz tuz ..."); StringBad sports("Narciarstwo"); cout << "Z ostatniej chwili: " << headline1 << endl; cout << "Temat dnia: " << headline2 << endl; cout << "Wiadomosci sportowe: " << sports << endl; callme1(headline1); cout << "Z ostatniej chwili: " << headline1 << endl; //.... return 0; } Wykonanie – problemu brak ws@bws:~/WstepDoProg2/wyklad8$ ./a.out 1: "Niech zyje bal !" - obiekt utworzony. 2: "Ferie tuz tuz ..." - obiekt utworzony. 3: "Narciarstwo" - obiekt utworzony. Z ostatniej chwili: Niech zyje bal ! Temat dnia: Ferie tuz tuz ... Wiadomosci sportowe: Narciarstwo Obiekt ciagu przekazany przez referencje: "Niech zyje bal !" Z ostatniej chwili: Niech zyje bal ! "Narciarstwo" - obiekt usuniety, sa jeszcze 2. "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 1. "Niech zyje bal !" - obiekt usuniety, sa jeszcze 0. bws@bws:~/WstepDoProg2/wyklad8$ Gdzie jest problem ???? #include <iostream> using std::cout; #include "strngbad.h" void callme1(StringBad &); // obiekt przekazywany przez referencje void callme2(StringBad); // obiekt przekazywany przez wartosc int main() { using std::endl; StringBad headline1("Niech zyje bal !"); StringBad headline2("Ferie tuz tuz ..."); StringBad sports("Narciarstwo"); cout << "Z ostatniej chwili: " << headline1 << endl; cout << "Temat dnia: " << headline2 << endl; cout << "Wiadomosci sportowe: " << sports << endl; callme1(headline1); cout << "Z ostatniej chwili: " << headline1 << endl; callme2(headline2); cout << "Temat dnia: " << headline2 << endl //.... return 0; } Wykonanie – problemu nr 1 1: "Niech zyje bal !" - obiekt utworzony. 2: "Ferie tuz tuz ..." - obiekt utworzony. 3: "Narciarstwo" - obiekt utworzony. Z ostatniej chwili: Niech zyje bal ! Temat dnia: Ferie tuz tuz ... Wiadomosci sportowe: Narciarstwo Obiekt ciagu przekazany przez referencje: "Niech zyje bal !" Z ostatniej chwili: Niech zyje bal ! Obiekt ciagu przekazany przez wartosc: "Ferie tuz tuz ..." "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 2. Temat dnia: "Narciarstwo" - obiekt usuniety, sa jeszcze 1. "" - obiekt usuniety, sa jeszcze 0. "Niech zyje bal !" - obiekt usuniety, sa jeszcze -1. Problem nr 1 Z jakiegos powodu przekazanie obiektu headline2 w argumencie funkcji callme2(headline2); powoduje wywołanie destruktora. Przekazanie przez wartość powinno zabezpieczać przed modyfikacja˛ orginału, ale w naszym przypadku tak nie jest ! Skad ˛ powyższe problemy: z automatycznego definiowania niejwnych funkcji składowych, których zachowanie nie jest zgodne z założeniami jakie według programisty - powinna spełniać klasa. Dokładniej: Konstruktor kopiujacy ˛ i Operator przypisania. Domyślny konstruktor kopiujacy ˛ nie zadbał o pole statyczne i wykonał tzw. kopiowanie płytkie. Domyślny operator przypisania również wykonał kopiowanie płytkie. Konstruktor kopiujacy ˛ Postać: NazwaKlasy (const NazwaKlasy &); Zadaniem konstruktora kopiujacego ˛ jest kopiowanie obiektu istniejacego ˛ do obiektu nowo tworzonego. Konstrukora kopiujacego ˛ używa sie˛ w czasie inicjalizacji, ale nie używa sie˛ podczas zwykłego przypisania. Przykłady użycia: Dane jest: StringBad A(”Niech zyje bal !”); StringBad StringBad StringBad StringBad B(A); B=A; B=StringBad(A); *B= new StringBad(A); Cze˛ ściowe rozwiazanie ˛ problemu #include <iostream> #ifndef STRNGBAD_H_ #define STRNGBAD_H_ class StringBad { private: char * str; // wskaznik ciagu int len; // dlugosc ciagu static int num_strings; // liczba obiektow public: StringBad(const char * s); // konstruktor StringBad(); // konstruktor domyslny StringBad(const StringBad &); // konstruktor kopiujacy ~StringBad(); // destruktor // funkcja zaprzyjazniona friend std::ostream & operator<<(std::ostream & os, const StringBad & st); }; #endif Cze˛ ściowe rozwiazanie ˛ problemu ... StringBad::StringBad(const StringBad & st) { num_strings++; // aktualizacja skladowej statycznej len = st.len; // ta sama dlugosc ciagu str = new char [len + 1]; // przydzial pamieci std::strcpy(str, st.str); // skopiowanie ciagu } ... Wykonanie – Cze˛ ściowe rozwiazanie ˛ problemu bws@bws:~/WstepDoProg2/wyklad8$ ./a.out 1: "Niech zyje bal !" - obiekt utworzony. 2: "Ferie tuz tuz ..." - obiekt utworzony. 3: "Narciarstwo" - obiekt utworzony. Z ostatniej chwili: Niech zyje bal ! Temat dnia: Ferie tuz tuz ... Wiadomosci sportowe: Narciarstwo Obiekt ciagu przekazany przez referencje: "Niech zyje bal !" Z ostatniej chwili: Niech zyje bal ! Obiekt ciagu przekazany przez wartosc: "Ferie tuz tuz ..." "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 3. Temat dnia: Ferie tuz tuz ... "Narciarstwo" - obiekt usuniety, sa jeszcze 2. "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 1. "Niech zyje bal !" - obiekt usuniety, sa jeszcze 0. bws@bws:~/WstepDoProg2/wyklad8$ Konstruktor kopiujacy ˛ pozwala na rozwiazanie kolejnego problemu #include <iostream> using std::cout; #include "strngbad.h" void callme1(StringBad &); // obiekt przekazywany przez referencje void callme2(StringBad); // obiekt przekazywany przez wartosc int main() { using std::endl; StringBad headline1("Niech zyje bal !"); StringBad headline2("Ferie tuz tuz ..."); StringBad sports("Narciarstwo"); cout << "Z ostatniej chwili: " << headline1 << endl; cout << "Temat dnia: " << headline2 << endl; cout << "Wiadomosci sportowe: " << sports << endl; callme1(headline1); cout << "Z ostatniej chwili: " << headline1 << endl; callme2(headline2); cout << "Temat dnia: " << headline2 << endl cout << "Inicjalizacja obiektu ciagu innym obiektem:\n"; StringBad sailor = sports; cout << "Z kraju: " << sailor << endl; //.... return 0; } Wszystko OK! bws@bws:~/WstepDoProg2/wyklad8$ ./a.out 1: "Niech zyje bal !" - obiekt utworzony. 2: "Ferie tuz tuz ..." - obiekt utworzony. 3: "Narciarstwo" - obiekt utworzony. Z ostatniej chwili: Niech zyje bal ! Temat dnia: Ferie tuz tuz ... Wiadomosci sportowe: Narciarstwo Obiekt ciagu przekazany przez referencje: "Niech zyje bal !" Z ostatniej chwili: Niech zyje bal ! Obiekt ciagu przekazany przez wartosc: "Ferie tuz tuz ..." "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 3. Temat dnia: Ferie tuz tuz ... Inicjalizacja obiektu ciagu innym obiektem: Z kraju: Narciarstwo "Narciarstwo" - obiekt usuniety, sa jeszcze 3. "Narciarstwo" - obiekt usuniety, sa jeszcze 2. "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 1. "Niech zyje bal !" - obiekt usuniety, sa jeszcze 0. bws@bws:~/WstepDoProg2/wyklad8$ Konstruktor kopiujacy ˛ to jednak nie wszystko ... #include <iostream> using std::cout; #include "strngbad.h" void callme1(StringBad &); // obiekt przekazywany przez referencje void callme2(StringBad); // obiekt przekazywany przez wartosc int main() { using std::endl; StringBad headline1("Niech zyje bal !"); StringBad headline2("Ferie tuz tuz ..."); StringBad sports("Narciarstwo"); cout << "Z ostatniej chwili: " << headline1 << endl; cout << "Temat dnia: " << headline2 << endl; cout << "Wiadomosci sportowe: " << sports << endl; callme1(headline1); cout << "Z ostatniej chwili: " << headline1 << endl; callme2(headline2); cout << "Temat dnia: " << headline2 << endl cout << "Inicjalizacja obiektu ciagu innym obiektem:\n"; StringBad sailor = sports; cout << "Z kraju: " << sailor << endl; cout << "Przypisanie obiektu do innego obiektu:\n"; StringBad knot; knot = headline1; cout << "Ze swiata: " << knot << endl; cout << "Koniec main()\n"; return 0; } Gdzie jest problem ? bws@bws:~/WstepDoProg2/wyklad8$ ./a.out 1: "Niech zyje bal !" - obiekt utworzony. 2: "Ferie tuz tuz ..." - obiekt utworzony. 3: "Narciarstwo" - obiekt utworzony. Z ostatniej chwili: Niech zyje bal ! Temat dnia: Ferie tuz tuz ... Wiadomosci sportowe: Narciarstwo Obiekt ciagu przekazany przez referencje: "Niech zyje bal !" Z ostatniej chwili: Niech zyje bal ! Obiekt ciagu przekazany przez wartosc: "Ferie tuz tuz ..." "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 3. Temat dnia: Ferie tuz tuz ... Inicjalizacja obiektu ciagu innym obiektem: Z kraju: Narciarstwo Przypisanie obiektu do innego obiektu: 5: "C++" - obiekt domyslny utworzony. Ze swiata: Niech zyje bal ! Koniec main() "Niech zyje bal !" - obiekt usuniety, sa jeszcze 4. "Narciarstwo" - obiekt usuniety, sa jeszcze 3. "Narciarstwo" - obiekt usuniety, sa jeszcze 2. "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 1. "" - obiekt usuniety, sa jeszcze 0. Gdzie jest problem ? Instrukcja: knot = headline1; Dlaczego: wywołanie domyślnego operatora przypisania i zwiazane ˛ z tym Kopiowanie płytkie. Efekt: - obiekt usuniety, sa jeszcze 0., czyli pusty łańcuch zwiazany z obiektem headline2. Rozwiazanie ˛ Przedefiniować własny operator przypisania. Pamietaj ˛ może on być tylko przedefionowywany jako funkcja składowa ! Pamietaj ˛ aby zwracać referencje do obiektu. W przciwnym wypadku nie bedzie ˛ można wykonywać instrukcji typu: A=B=C; StringBad & StringBad::operator=(const StringBad & st) { if (this == &st) return *this; delete [] str; len = st.len; str = new char[len + 1]; std::strcpy(str, st.str); return *this; } Potwierdzenie poprawności: bws@bws:~/mysvn/wyklady/2011-2012/Matematyka/WstepDoProg2/wyklad8$ ./a.out 1: "Niech zyje bal !" - obiekt utworzony. 2: "Ferie tuz tuz ..." - obiekt utworzony. 3: "Narciarstwo" - obiekt utworzony. Z ostatniej chwili: Niech zyje bal ! Temat dnia: Ferie tuz tuz ... Wiadomosci sportowe: Narciarstwo Obiekt ciagu przekazany przez referencje: "Niech zyje bal !" Z ostatniej chwili: Niech zyje bal ! Obiekt ciagu przekazany przez wartosc: "Ferie tuz tuz ..." "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 3. Temat dnia: Ferie tuz tuz ... Inicjalizacja obiektu ciagu innym obiektem: Z kraju: Narciarstwo Przypisanie obiektu do innego obiektu: 5: "C++" - obiekt domyslny utworzony. Ze swiata: Niech zyje bal ! Koniec main() "Niech zyje bal !" - obiekt usuniety, sa jeszcze 4. "Narciarstwo" - obiekt usuniety, sa jeszcze 3. "Narciarstwo" - obiekt usuniety, sa jeszcze 2. "Ferie tuz tuz ..." - obiekt usuniety, sa jeszcze 1. "Niech zyje bal !" - obiekt usuniety, sa jeszcze 0. Udoskonalona klasa String - interfejs #include <iostream> using namespace std; #ifndef STRING1_H_ #define STRING1_H_ class String { private: char * str; // wskaznik ciagu int len; // dlugosc ciagu static int num_strings; // liczba obiektów klasy static const int CINLIM = 80; // limit dlugosci ciagu na wejsciu public: String(const char * s); // konstruktor String(); // konstruktor domyslny String(const String &); // konstruktor kopiujacy ~String(); // destruktor int length () const { return len; } String & operator=(const String &); // metody przeciazajace operatory String & operator=(const char *); char & operator[](int i); const char & operator[](int i) const; // funkcje zaprzyjaznione przeciazajace operatory friend bool operator<(const String &st, const String &st2); friend bool operator>(const String &st1, const String &st2); friend bool operator==(const String &st, const String &st2); friend ostream & operator<<(ostream & os, const String & st); friend istream & operator>>(istream & is, String & st); static int HowMany(); // metoda statyczna }; #endif Udoskonalona klasa String - definicja #include <cstring> #include "string1.h" using std::cin; using std::cout; // inicjalizacja statycznej skladowej klasy int String::num_strings = 0; // metoda statyczna int String::HowMany() { return num_strings;} String::String(const char * s) { // konstruuje obiekt String z ciagu C len = std::strlen(s); // ustawienie dlugosci ciagu str = new char[len + 1]; // przydzial pamieci std::strcpy(str, s); // inicjalizacja wskaznika ciagu num_strings++; // aktualizacja licznika obiektow } Udoskonalona klasa String - definicja String::String() // konstruktor domyslny { len = 4; str = new char[1]; str[0] = ’\0’; // domyslny ciag obiektow klasy num_strings++; } String::String(const String & st) { num_strings++; // aktualizacja skladowej statycznej len = st.len; // ta sama dlugosc ciagu str = new char [len + 1]; // przydzial pamieci std::strcpy(str, st.str); // skopiowanie ciagu } String::~String() // destruktor (niezbedny) { --num_strings; // koniecznie delete [] str; // koniecznie } Udoskonalona klasa String - definicja // przypisywanie obiektu klasy String //do innego obiektu tej klasy String & String::operator=(const String & st) { if (this == &st) return *this; delete [] str; len = st.len; str = new char[len + 1]; std::strcpy(str, st.str); return *this; } // przypisywanie ciagu C do obiektu klasy String String & String::operator=(const char * s) { delete [] str; len = std::strlen(s); str = new char[len + 1]; std::strcpy(str, s); return *this; } Udoskonalona klasa String - definicja // pelny dostep do znaków ciagu (dla obiektów zwyklych) char & String::operator[](int i){ return str[i]; } // dostep (do odczytu) do znaków ciagu (dla obiektów const) const char & String::operator[](int i) const {return str[i];} // zaprzyjaznione funkcje przeciazajace operatory bool operator<(const String &st1, const String &st2) { return (std::strcmp(st1.str, st2.str) < 0); } bool operator>(const String &st1, const String &st2) { return st2.str < st1.str; } bool operator==(const String &st1, const String &st2) { return (std::strcmp(st1.str, st2.str) == 0); } Udoskonalona klasa String - definicja // wyprowadzenie ciagu na wyjscie ostream & operator<<(ostream & os, const String & st) { os << st.str; return os; } // wczytywanie ciagu z wejscia (uproszczone) istream & operator>>(istream & is, String & st) { char temp[String::CINLIM]; is.get(temp, String::CINLIM); if (is) st = temp; while (is && is.get() != ’\n’) continue; return is; }