Wstęp do Programowania 2 dr Bożena Woźna-Szcześniak bwozna@gmail.com Akademia im. Jana Długosza Wykład 8
Przykład realizowany na wykładzie Klasy StringBad i String. Wstępne pliki załaczone jako źródła. Przykład z ksi ażki: Stephen Prata. Język 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 kopię, 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 pamięci, jednak samej alokacji nie wykonuje. Alokacja i inicjalizacja pamięci dokonuje się w momencie utworzenia obiektu danej klasy. Pola statyczne inicjalizowane sa niezależnie za pomoca instrukcji znajdujacej się poza deklaracja klasy - powodem jest fakt, że składowe statyczne nie sa przechowywane jako część 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 kopiujący 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 się w czasie inicjalizacji, ale nie używa się podczas zwykłego przypisania. Przykłady użycia: Dane jest: StringBad A( Niech zyje bal! ); StringBad B(A); StringBad B=A; StringBad B=StringBad(A); StringBad *B= new StringBad(A);
Częś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
Częś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 Częś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. Pamiętaj może on być tylko przedefionowywany jako funkcja składowa! Pamiętaj aby zwracać referencje do obiektu. W przciwnym wypadku nie będzie 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;