PROE wykład 3 klasa string, przeciążanie funkcji, operatory dr inż. Jacek Naruniec
Przypomnienie z ostatnich wykładów Konstruktory/destruktory i kolejność ich wywołania w złożonej klasie. Referencja Obiekty w ramach obiektów.
Klasa string W C++ zamiast ciągów znaków char[] zwykle używa się klasy string (include <string>). Klasa znajduje się w przestrzeni nazw std, więc dodajemy using namespace std.
Klasa string Najczęstsze formy inicjalizacji: Dodawanie napisów (strcat w char[]):
Klasa string Porównywanie napisów (strcmp w char[]):
Klasa string Wyszukiwanie napisu w napisie
Klasa string Długość napisu: s.length() Ciąg znaków z obiektu klasy string: Wiele innych funkcji (ask Google)
Klasa string Zalety string nad char[] to: Nie martwimy się, że wyjdziemy poza zakres wskaźnika. Mamy jeden obiekt zamiast ciągu znaków. Mamy bezpośrednio dostęp do listy możliwych funkcji klasy. Uproszczenie operacji (np. porównywanie). Zalety char[] nad string to: printf, sprintf, fprintf, scanf, sscanf, fscanf mnóstwo możliwości odczytu i zapisu z/do konsoli/pliku/ciągu Wybór string/char[] zależy często od aplikacji, ale wskazane jest używanie string. Z obiektu string można pobrać ciąg znakowy (s.data(), s.c_str())
Pola statyczne Pole statyczne jest wspólne dla każdej instancji (każdego stworzonego obiektu) danej klasy. Tutaj przykład wykorzystania do zliczania ilości punktów:
Pola statyczne Zawsze musimy nadać początkową wartość pola statycznego: bez inicjalizacji:
Pola statyczne Takie bezpośrednie odwołanie się do klasy możliwe tylko do pól (i funkcji) statycznych! Nie można wywołać Punkt::x = 0 (bo którego obiektu to będzie dotyczyć?)
Przeciążanie funkcji W standardowym C parametry funkcji są jednoznacznie określone: W C++ wybór odpowiedniej funkcji odbywa się w czasie kompilacji:
Przeciążanie funkcji Co jeśli jest wiele funkcji odpowiadających parametrom? Np. funkcje sqrt mamy dla float, double i long double: Musimy wymusić wywołanie konkretnej funkcji:
Operatory i przeciążanie operatorów W przypadku standardowych typów zmiennych nie ma problemu z następującymi operacjami: Ale co w przypadku klasy Punkt? W C++ będziemy sami określić jakie działanie ma mieć konkretny operator (+, -, <<, >> ++, ).
Operator == Przeciążanie operatora:
Operator == Powoduje wywołanie operatora == dla obiektu Z LEWEJ strony tu jest referencja na p2 Zawsze wywołuje się funkcja dla obiektu z lewej strony, czyli p1 to this. to są pola p1
Operator == Bardziej złożony obiekt: Co oznacza równość węży? - sami to ustalamy, np.: 1. Równość węży oznacza równość długości. 2. Głęboka równość: warunek 1 + wszystkie elementy muszą być równe
Operator == Równość 1: Równość 2:
Operator > W przypadku klasy Punkt operator > jest także niejednoznaczny (co znaczy punkt większy/mniejszy?) I (suma zmiennych): II (odległość od (0,0) ):
Operator > W przypadku klasy Waz:
Operator = Operator = klasy Punkt (taki zresztą stworzy się domyślnie, nawet jeśli go nie napiszemy): Ok, ale co jeśli wywołamy Operatory równorzędne wywoływane są od strony prawej do lewej: To wywoła się jako pierwsze. Co w tym miejscu zostanie? - to, co zwraca operator = czyli void, czyli w sumie nic. a to się oczywiście nie skompiluje
Operator = Rozwiązanie: Operator = powinien zwracać ten sam obiekt na rzecz którego został wywołany (nie jego kopię, tylko referencję!): Będzie to równoważne z: Innymi słowy aby operator skonstruowany był poprawnie, bardzo istotny jest zwracany typ!
Operator = Klasa Waz (płytka i głęboka kopia) Płytka kopia: Efekt: podwójne kasowanie pamięci + wyciek
Operator = Kopia głęboka: teraz ok
Operator + Waz:
Operator + Waz: nasz ulubiony błąd
Operator + Ten obiekt zostanie usunięty w momencie wyjścia z kontekstu, czyli tutaj Dalej przekazujemy referencję, czyli ADRES zmiennej którą właśnie usunięto! W rezultacie tu otrzymujemy obiekt który usunęliśmy.
Operator + Rozwiązanie: przekażmy kopię obiektu a nie obiekt, który zaraz zostanie usunięty: W rezultacie tu otrzymujemy KOPIĘ obiektu suma. Tworzony jest zazwyczaj poprzez konstruktor kopiujący, który MUSI być tu zdefiniowany (dlaczego?)!
Operator + Dla punktu: Efekt działania dokładnie ten sam (może poza szybkością), oczywiście ostatni najładniejszy. W przypadku braku dynamicznej alokacji pamięci możemy polegać na automatycznie wygenerowanym konstruktorze kopiującym.
Operator << Operatory strumieniowe to operatory przyjmujące strumień jako parametr (np. cout, cin itp.) Strumień cout jest typu ostream ( o, czyli output wyjście) Strumień cin jest typu istream ( i, czyli input wejście) Można by bezpośrednio zaimplementować go w klasie: Jest jeden problem chcemy wywołać cout << obiekt, a przecież w operatorach this jest zawsze po lewej stronie, czyli zgodnie z powyższą definicją operatora mielibyśmy:
Operator << Powinniśmy odwrócić logikę zaimplementować funkcję ostream& cout::operator<<(punkt &p). Ponieważ nie mamy dostępu do składowych klasy cout, operator można zdefiniować poprzez funkcję poza klasami: argument z lewej argument z prawej Funkcja nie jest zdefiniowana w żadnej klasie ( zewnętrzna )! Pozwala na wywołanie cout << p1.
Operator << Waz: Proszę zauważyć: nie ma tu cout <<, tylko s <<, co oznacza, że będziemy mogli korzystać z dowolnego strumienia (np. plikowego - fstream). Ponieważ operator korzysta z pól prywatnych klasy Waz (dlugosc_weza, kierunek_weza) musi być zaprzyjaźniony z klasą wąż.
Operator << Zaprzyjaźnienie oznacza, że funkcja będzie mogła korzystać nawet z prywatnych pól klasy Waz, czego zawsze unikamy.
Inne operatory: Operator rzutowania ((int)waz) Operator dostępu do elementu tablicy (waz[i]) Operator +=, operator ++ Można definiować praktycznie dowolne operatory tak aby działały w pożądany dla nas sposób.
Operatory Sposób działania operatorów jest umowny. Umowne są także typy zwracane przez operatory. Dla swoich potrzeb można je dowolnie zmieniać, ale na projektach przedmiotu trzymamy się umownych zwracanych i przyjmowanych typów danych. Polecam przeczytać bardzo dobry opis operatorów: http://nauka-programowania.pl/o2.pdf
Operacje na plikach Pliki traktujemy tak samo jak strumienie cout, cin. Tworzymy obiekt strumienia plikowego wejściowego (ofstream), wyjściowego (ifstream) lub ogólnego (fstream).
Operacje na plikach
Operacje na plikach Na stronie prosty przykład z plikiem i okienkiem dialogowym (Windows)
Przestrzenie nazw Używane zwykle aby: Uniknąć konfliktu nazw w różnych bibliotekach. Zwiększyć przejrzystość kodu.
Przestrzenie nazw Jeśli takie same przestrzenie istnieją w różnych miejscach kodu łączą się
Przestrzenie nazw
Przestrzenie nazw Jeśli wpiszemy: Oznacza to, że bez podania nazwy przestrzeni domyślnie korzystamy z PrzestrzenGienka, czyli:
Przestrzenie nazw Stąd nasze using namespace std; sprawia, że korzystamy z przestrzeni nazw std domyślnie. Nie musimy wtedy pisać std::cout tylko po prostu cout Tak samo string i std::string i inne Można mieć włączone wiele domyślnych przestrzeni nazw, ale wtedy nazwy elementów nie mogą się pokrywać, czyli: