Przykład klasa biblioteczna string

Podobne dokumenty
Przykład klasa biblioteczna string

PARADYGMATY PROGRAMOWANIA Wykład 3

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

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

Konwersje. Programowanie obiektowe (2005/2006) Wykład 4

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

Programowanie obiektowe - Przykładowe zadania egzaminacyjne (2005/2006)

Część 4 życie programu

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

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

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

Przeciążanie operatorów

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

wykład II uzupełnienie notatek: dr Jerzy Białkowski Programowanie C/C++ Język C - funkcje, tablice i wskaźniki wykład II dr Jarosław Mederski Spis

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

Składnia C++ Programowanie Obiektowe Mateusz Cicheński

Projektowanie klas c.d. Projektowanie klas przykład

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

I - Microsoft Visual Studio C++

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

Szablony klas, zastosowanie szablonów w programach

Programowanie obiektowe i C++ dla matematyków

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

Podstawy Programowania

dr inż. Jarosław Forenc

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

TEMAT : KLASY DZIEDZICZENIE

Wykład :37 PP2_W9

Wzorce funkcji (szablony)

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

Język C++ wykład VIII

> C++ wskaźniki. Dane: Iwona Polak. Uniwersytet Śląski Instytut Informatyki 26 kwietnia 2017

Przeciążenie operatorów

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 26 marca kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 40

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

Wstęp do programowania obiektowego. WYKŁAD 3 Dziedziczenie Pola i funkcje statyczne Funkcje zaprzyjaźnione, this

Programowanie Obiektowe i C++

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

KLASY cz4. Dorota Pylak. destruktory składowe statyczne przeciążanie operatorów. wskaźniki

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

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

Programowanie obiektowe Wykład 3. Dariusz Wardowski. dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/21

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

Język C++ umożliwia przeciążanie operatora, tzn. zmianę jego znaczenia na potrzeby danej klasy. W tym celu definiujemy funkcję o nazwie:

Wstęp do Programowania 2

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 16 kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 27

Wstęp do Programowania 2

Automatyczne tworzenie operatora = Integer2& operator=(const Integer& prawy) {

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

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

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

// Liczy srednie w wierszach i kolumnach tablicy "dwuwymiarowej" // Elementy tablicy są generowane losowo #include <stdio.h> #include <stdlib.

Automatyczne tworzenie operatora = Integer2& operator=(const Integer& prawy) { zdefiniuje. Integer::operator=(ri);

Składnia C++ Programowanie Obiektowe Mateusz Cicheński

Wstęp do wskaźników w języku ANSI C

utworz tworzącą w pamięci dynamicznej tablicę dwuwymiarową liczb rzeczywistych, a następnie zerującą jej wszystkie elementy,

Dodatkowo klasa powinna mieć destruktor zwalniający pamięć.

Języki programowania obiektowego Nieobiektowe elementy języka C++

Wymiar musi być wyrażeniem stałym typu całkowitego, tzn. takim, które może obliczyć kompilator. Przykłady:

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

Automatyczne tworzenie operatora = Integer2& operator=(const Integer& prawy) {

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

Lab 9 Podstawy Programowania

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

Język C zajęcia nr 11. Funkcje

Operatory na rzecz typu TString

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

Operacje wejścia/wyjścia (odsłona druga) - pliki

Operator przypisania. Jest czym innym niż konstruktor kopiujący!

Szablony funkcji i szablony klas

PARADYGMATY PROGRAMOWANIA Wykład 4

1 Podstawy c++ w pigułce.

Programowanie komputerowe. Zajęcia 4

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

Konwersje napis <-> liczba Struktury, unie Scanf / printf Wskaźniki

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

Pliki wykład 2. Dorota Pylak

C++ wprowadzanie zmiennych

Języki programowania. Przetwarzanie tablic znaków. Część druga. Autorzy Tomasz Xięski Roman Simiński

Wskaźniki. nie są konieczne, ale dają językowi siłę i elastyczność są języki w których nie używa się wskaźników typ wskaźnikowy typ pochodny:

ROZDZIAŁ 2. Operatory

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

Podstawy programowania

Informacje wstępne #include <nazwa> - derektywa procesora umożliwiająca włączenie do programu pliku o podanej nazwie. Typy danych: char, signed char

Programowanie, część I

wykład IV uzupełnienie notatek: dr Jerzy Białkowski Programowanie C/C++ Język C, a C++. wykład IV dr Jarosław Mederski Spis Język C++ - wstęp

Podstawy programowania w języku C++

Programowanie w języku C++

typ y y p y z łoż o on o e n - tab a lice c e w iel e owym m ar a o r we, e stru r kt k ury

Programowanie obiektowe w C++ Wykład 12

jest mocny, skoro da się w nim wyrazić nowe pojęcia; łatwiej przenieść go na nową platformę jest mniejszy.

1 Podstawy c++ w pigułce.

Typy wyliczeniowe Konwersje napis <-> liczba Struktury, unie Scanf / printf Wskaźniki

Programowanie Obiektowo Zorientowane w języku C++ Klasy, pola, metody

Podstawy programowania w języku C i C++

Programowanie - wykład 4

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

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

Ćwiczenie 7 z Podstaw programowania. Język C++, programy pisane w nieobiektowym stylu programowania. Zofia Kruczkiewicz

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

Transkrypt:

C++ pozwala na dwie reprezentacje napisów o napisy w stylu języka C o napisy w stylu języka C++ Przykład klasa biblioteczna string Napisy w stylu języka C Napis w stylu języka C jest to ciąg znaków zakończony znakiem '\0', przechowywany w tablicy znakowej. Przykład deklarowania napisu w stylu języka C: char s[]="witam"; // tablica będzie miała rozmiar 5+1 char *s1="tak też można"; // inicjalizacja wskaźnikiem do napisu Przykład funkcji działającej na napisach: int napis_dlugosc(const char *nap) int dlugosc=0; if (nap) while (*dlugosc++) ++dlugosc; return dlugosc; Korzystanie ze standardowych funkcji na napisach w stylu C wymaga dołączenia pliku nagłówkowego: #include <cstring> (stanowi on odpowiednik pliku <string.h> języka C). Przykłady często używanych funkcji z biblioteki <cstring>: // obliczenie długości napisu int strlen(const char* ); // bez znaku końca napisu // porównanie dwóch napisów int strcmp(const char*, const char* ); int strncmp(const char*, const char*, int); // porównuj tylko n znaków // skopiowanie drugiego napisu do pierwszego char* strcpy(char*, const char*); char* strncpy(char*, const char*, int); //kopiuj tylko n znaków // dołączenie drugiego napisu do pierwszego char* strcat(char*, const char*); char* strncat(char*, const char*, int); //dołącz tylko n znaków Przypisywanie napisów: tylko za pomocą kopiowania char tekst[100]; strcpy(tekst,p) // kopiuj z p do tekst, łącznie ze znakiem końca napisu // zagrożenie: może spowodować przepełnienie tablicy tekst strncpy(tekst,p,100); // kopiuj z p do tekst 100 znaków // zagrożenie: pominięcie znaku końca napisu Inne użyteczne biblioteki: <cstdlib> (odpowiednik <stdlib.h>) - konwersje napisów do typów liczbowych, np. atoi() <cctype> (odpowiednik <ctype.h>) - klasyfikacja znaków, np. isupper() 1

Napis w stylu języka C++ to obiekt typu string: string s("witam"); Napisy w stylu języka C++ - klasa string Napis jest ciągiem znaków o zmiennej długości, nie ma znaku kończącego, tak jak w języku C, długość napisu jest przechowywana w obiekcie. Korzystanie z klasy string wymaga dołączenia pliku nagłówkowego: #include <string> i przestrzeni nazw std. Przykłady Klasa string posiada wiele konstruktorów. Pozwalają one inicjować napisy w różny sposób. Przykłady deklarowania zmiennych typu string: string s1; // konstruktor domyślny - tworzy napis pusty string s2("witam "); // napis z wartością początkową string s3="od tego zaczynamy"; // to samo co wyżej string s4(s3); // klasa posiada konstruktor kopiujący - // można inicjować jeden napis drugim string s7(10,'\n'); // napis ma 10 znaków nowego wiersza Obliczenie długości napisu - metoda length()lub size(): cout << "Napis ma " << s2.length() << " znaków\n"; cout << "Napis ma " << s2.size() << " bajtów\n"; Przypisywanie napisów string s1,s2; s1="w klasie string tak można"; s2=s1; Zachowany jest dostęp do pojedynczych znaków (pozycje napisu są numerowane od 0 do length()-1) string s("wsisiz.edu.pl"); int ile=s.length(); for (int i=0; i<ile; ++i) if (s[i]=='.') s[i]='_'; Sprawdzenie, czy napis jest pusty if (!s2.size()) // za pomocą sprawdzenia długości napisu lub if (s2.empty()) // empty() zwraca true, jeśli napis nie ma znaków Łączenie napisów (konkatenacja) jest realizowane za pomocą operatora + : string s1("adam "),s2("kowalski "),s3; s3=s1+s2; // w s3 jest napis "Adam Kowalski " Do napisu można dołączać znak: string s1("adam"),s2("kowalski"),s3; s3=s1+' '+s2; // w s3 jest napis "Adam Kowalski" 2

Napisy można porównywać z napisami if (s2 == s3) // operator == jest przeciążony Można używać operatorów ==,!=, >, <, >= i <= Napisy można wyświetlać tak, jak typy podstawowe (przeciążony operator <<): string s("adam"); cout << s << endl; Napisy można wczytywać (przeciążony operator >> oraz funkcja getline()): string s1, s2; cin >> s1; // do znaku spacji getline(cin,s2); // do znaku nowej linii getline(cin,s2,'\n'); // do znaku nowej linii Przykład: Wyświetlanie pliku tekstowego wierszami // Wersja A: nazwa pliku podana w programie #include <string> #include <iostream> #include <fstream> int main() ifstream we("plik.txt"); // Plik, którego zawartość wyświetlamy string wiersz; while(getline(we, wiersz,'\n')) cout << wiersz<<endl; // Brak końca wiersza! cin.get(); // Wersja B: nazwa pliku podawana podczas wykonywania programu #include <string> #include <iostream> #include <fstream> int main() string n_pliku; cout << "Podaj nazwe pliku: "; cin >> n_pliku; ifstream we(n_pliku.c_str()); // konwersja stringu na napis w stylu C string wiersz; while(getline(we, wiersz,'\n')) cout << wiersz; // Brak końca wiersza! cin.get(); 3

Przykład: Kopiowanie jednego pliku do drugiego wiersz po wierszu #include <string> #include <fstream> int main() ifstream we("plikwe.txt"); // Plik, którego zawartość kopiujemy ofstream wy("plikwy.txt"); // Plik do którego kopiujemy string wiersz; while(getline(we, wiersz, '\n')) // Funkcja usuwa znak nowego wiersza wy << wiersz << "\n"; // - trzeba dodać go z powrotem w pliku wy Przykład: Zliczanie wystąpień określonego słowa w pliku // Wersja A - nazwa pliku podana w programie #include <iostream> #include <fstream> #include <string> int main() ifstream f("plik.txt"); //brak spr int licznik = 0; string slowo; while (f >> slowo) ++licznik; cout << "Liczba slow = " << licznik << endl; // Wersja B - nazwa pliku podana w wierszu wywołania programu w2p3 #include <iostream> #include <string> int main() int licznik = 0; string slowo; while (cin >> slowo) ++licznik; cout << " Liczba slow = " << licznik << endl; Przykład: wyznaczenie liczby wystąpień określonego słowa w pliku. Wywołanie programu: liczslowa slowo plik.txt #include <iostream> #include <fstream> #include <string> int main(int argc, char* argv[]) if (argc < 3) cerr << "uzycie: LiczSlowa slowo plik\n"; return -1; string wzorzec(argv[1]); ifstream plik(argv[2]); long licznik = 0; string slowo; while (plik >> slowo) if (slowo == wzorzec) ++licznik; cout << '"' << wzorzec << "\" wystepuje " << licznik << " razy\n"; 4

Przykład: Funkcja sprawdza, czy w wierszu znajduje się podany napis. Jeśli napis zostanie znaleziony, zwracana jest jego pozycja w stosunku do początku wiersza, jeśli nie - zwracane jest -1. // W stylu języka C int wierszzawiera(char napis[], char wiersz[]) int j, k; if ( napis == NULL napis[0] == '\0') // napis pusty return -1; for (int i = 0 ; wiersz[i]!= '\0' ; i++) if ( wiersz[i]!= napis[0] ) // pierwsze znaki są różne continue; // zacznij od następnego znaku wiersza // porównuj pozostałe znaki // dopóki nie napotkasz pierwszych różnych lub końca napisu for ( j = i + 1, k = 1; wiersz[j]==napis[k] && napis[k]!= '\0'; j++, k++ ) if ( napis[k] == '\0') // napotkano koniec napisu return i; // znaleziono else if (wiersz[j] == '\0') // napotkano koniec wiersza return -1; // nie znaleziono return -1; // nie znaleziono // W stylu języka C++ - korzysta z klasy string int napiszawiera1(string napis, string wiersz) int j, k; if ( napis.empty()) return -1; // napis pusty int ndl=napis.size(); // długość napisu int wdl=wiersz.size(); // długość wiersza for (int i = 0 ; i<wdl ; i++) if ( wiersz[i]!= napis[0] ) // pierwsze znaki są różne continue; // zacznij od następnego znaku wiersza // porównuj pozostałe znaki // dopóki nie napotkasz pierwszego różnego lub końca napisu for ( j = i + 1, k = 1; k < ndl && wiersz[j]==napis[k] ; j++, k++ ) if ( k == ndl) // napotkano koniec napisu return i; // znaleziono else if (j == wdl) // napotkano koniec wiersza return -1; // nie znaleziono return -1; // nie znaleziono 5

Zaprzyjaźnianie Funkcja składowa danej klasy ma dostęp do wszystkich składowych prywatnych dowolnego obiektu tej samej klasy. Do składowych prywatnych jakiegoś obiektu nie ma dostępu funkcja innej klasy ani funkcja zewnętrzna (niezależna). Autor klasy może jednak zadeklarować w klasie prototypy funkcji zaprzyjaźnionych, czyli takich które mimo, że nie będą składowymi klasy będą miały dostęp do składowych prywatnych klasy. Jeśli chcemy wskazać, że funkcja jest zaprzyjaźniona, należy umieścić w definicji klasy prototyp funkcji zaprzyjaźnionej z kwalifikatorem friend. Możliwe sytuacje: o o o niezależna funkcja zewnętrzna, zaprzyjaźniona z klasą, funkcja składowa (metoda) jednej klasy, zaprzyjaźniona z inną klasą, klasy zaprzyjaźnione. Przykład 1: Pokazuje sposób definiowania funkcji zaprzyjaźnionej #include <iostream> class Punkt int x, y ; Punkt (int xx=0, int yy=0) x=xx ; y=yy ; friend bool Przyjaciel(const Punkt &, const Punkt &) ; // funkcja // zaprzyjaźniona ; // funkcja zaprzyjaźniona ma dostęp do składowych prywatnych // mimo, że nie jest funkcją składową klasy // Możemy np. sprawdzić w ten sposób, czy dwa punkty są sobie równe bool Przyjaciel (const Punkt &p, const Punkt &q) if ((p.x == q.x) && (p.y == q.y)) return true ; else return false ; int main() Punkt a(1,0), b(1), c ; if (Przyjaciel(a,b)) cout << "a rowny b \n" ; else cout << "a i b sa rozne\n" ; if (Przyjaciel(a,c)) cout << "a rowny c \n" ; else cout << "a i c sa rozne \n" ; return 0; Uwaga: w funkcji zaprzyjaźnionej nie ma wskaźnika this Trzeba przekazać dwa argumenty, bo funkcja zaprzyjaźniona nie jest funkcją składową i nie otrzymuje wskaźnika this. 6

Przykład 2: mamy klasę Wektor i klasę Macierz, dane w tych klasach są prywatne. Chcemy zdefiniować funkcję mnożenia macierzy przez wektor. Jak to zrobić? Czy możemy włączyć ją do którejś z klas, czy możemy ją zdefiniować jako funkcję zewnętrzną? Jakie inne rozwiązania są możliwe? o Wersja A: wykorzystywana jest niezależna funkcja zaprzyjaźniona #include <iostream> class Macierz ; class Wektor double v[3] ; Wektor (double v1=0, double v2=0, double v3=0) v[0] = v1 ; v[1]=v2 ; v[2]=v3 ; friend Wektor produkt(macierz, Wektor); void Drukuj () int i ; for (i=0 ; i<3 ; i++) cout << v[i] << " " ; cout << "\n" ; ; class Macierz double m[3][3]; Macierz (double t[3][3]) int i ; int j ; for (i=0 ; i<3 ; i++) for (j=0 ; j<3 ; j++) m[i][j] = t[i][j] ; friend Wektor produkt(macierz, Wektor); ; // niezależna funkcja // ta funkcja jest zaprzyjaźniona z klasą Macierz i Wektor Wektor produkt (Macierz a, Wektor x) int i, j ; double suma ; Wektor wynik ; for (i=0 ; i<3 ; i++) for (j=0, suma=0 ; j<3 ; j++) suma += a.m[i] [j] * x.v[j] ; wynik.v[i] = suma ; return wynik ; int main() Wektor w (1,2,3) ; Wektor wynik; double tb [3][3] = 1, 2, 3, 4, 5, 6, 7, 8, 9 ; Macierz a = tb ; wynik = produkt(a, w) ; wynik.drukuj () ; 7

o Wersja B: wykorzystywana jest funkcja zaprzyjaźniona będąca składową innej klasy //koniec wykł 2 #include <iostream> class Wektor ; class Macierz double m[3][3] ; Macierz (double t[3][3]) int i ; int j ; for (i=0 ; i<3 ; i++) for (j=0 ; j<3 ; j++) m[i][j] = t[i][j] ; Wektor produkt (Wektor); ; class Wektor double v[3] ; Wektor (double v1=0, double v2=0, double v3=0) v[0] = v1 ; v[1]=v2 ; v[2]=v3 ; friend Wektor Macierz::produkt (Wektor); void Drukuj() int i ; for (i=0 ; i<3 ; i++) cout << v[i] << " " ; cout << "\n" ; ; // Ta metoda jest zaprzyjaźniona z klasą Wektor Wektor Macierz::produkt (Wektor x) int i, j ; double suma ; Wektor wynik ; for (i=0 ; i<3 ; i++) for (j=0, suma=0 ; j<3 ; j++) suma += m[i] [j] * x.v[j] ; wynik.v[i] = suma ; return wynik ; main() Wektor w (1,2,3) ; Wektor wynik ; double tb [3][3] = 1, 2, 3, 4, 5, 6, 7, 8, 9 ; Macierz a = tb ; wynik = a.produkt (w) ; wynik.drukuj() ; 8

Operatory klas W C++ można definiować własne wersje operatorów przeznaczone dla argumentów będących obiektami klas. Czynność ta nazywana jest przeciążaniem operatora. Przeciążenie operatora nie powoduje utraty jego oryginalnego znaczenia, świadczy jedynie o zdefiniowaniu nowej operacji odnoszącej się do określonej klasy. Aby przeciążyć istniejący w C++ operator op, definiuje się funkcję o nazwie operatorop (można powiedzieć, że przeciążanie operatorów jest to przekształcanie operatorów w funkcje). Postać funkcji operatora: typ nazwa_klasy::operatorop(lista_argumentów) // definicja operacji względem klasy Funkcja określająca nowe działanie operatora może być zdefiniowana w postaci: o funkcji składowej klasy class Punkt int x, y ; Punkt operator +(Punkt&); ; Punkt Punkt::operator+(Punkt & b) Punkt p ; p.x = x + b.x ; p.y = y + b.y ; return p ; W main(): Punkt a(1,2), b(2,5), c ; c = a+b; // skrót dla: c = a.operator+(b); c = a+b+c ; // skrót dla: c = (a.operator+(b)).operator+(c); o niezależnej funkcji (najczęściej zaprzyjaźnionej z klasą) class Punkt int x, y ; friend Punkt operator+ (Punkt, Punkt); ; Punkt operator+(punkt a, Punkt b) Punkt p ; p.x = a.x + b.x ; p.y = a.y + b.y ; return p ; W main(): Punkt a(1,2), b(2,5), c ; c = a+b; // skrót dla: c = operator+(a,b) c = a+b+c ; // skrót dla: c = operator+(operator+(a,b),c) 9

Przeciążanie operatora + Przykład: dodawanie ułamków Chcemy uzupełnić klasę Ulamek o dodawanie ułamków. 1/2 + 1/3 = 5/6 1/6 + 2/6 = 1/2 class Ulamek int l; // licznik int m; // mianownik int nwp(int p, int q); // największy wspólny podzielnik public: Ulamek(int a=0, int b=1) // Konstruktor int q=nwp(a,b); if(b < 0) q = -q; // mianownik ma być zawsze dodatni l = a/q; m = b/q; int ZwrocLicznik()const return l; int ZwrocMian()const return m; ; Wersja 1 naiwna void Ulamek::DodajUlamki(Ulamek a, Ulamek b) l=a.l*b.m+b.l*a.m; m=a.m*b.m; int main () Ulamek f1(1,2), f2(1,3), f3, f4(1,6),f5(1,3),f6; cout << "Test dodawania ułamków\n"; cout << f1.zwroclicznik() << '/' << f1.zwrocmian() << '+' << f2.zwroclicznik() << '/' << f2.zwrocmian() << '='; f3.dodajulamki(f1,f2); cout << f3.zwroclicznik() << '/' << f3.zwrocmian()<< endl; cout << f4.zwroclicznik() << '/' << f4.zwrocmian() << '+' << f5.zwroclicznik() << '/' << f5.zwrocmian() << '='; f6.dodajulamki(f4,f5); cout << f6.zwroclicznik() << '/' << f6.zwrocmian()<< endl; return 0; Wyniki: 1/2+1/3=5/6 1/6+1/3=9/18 // Ułamek nie został skrócony 10

Wersja 2. Poprawka: uzupełnienie o skracanie ułamka powstałego w wyniku dodawania Ulamek Ulamek::DodajUlamki(Ulamek b) long rl, rm rl=l*b.m+b.l*m; rm=m*b.m; return Ulamek(rl,rm); //tutaj tworzony obiekt tymczasowy int main () Ulamek f1(1,2), f2(1,3), f3, f4(1,6),f5(1,3),f6; f3=f1.dodajulamki(f2); return 0; Wyniki: 1/2+1/3=5/6 1/6+1/3=1/2 // Ułamek został skrócony Wersja 3: Zabezpieczenie i zwiększenie wydajności: Ulamek Ulamek::DodajUlamki(const Ulamek &b) long rl, rm rl=l*b.m+b.l*m; rm=m*b.m; return Ulamek(rl,rm); 11

Wersja 4. Poprawka: uproszczenie zapisu dodawania czyli przeciążenie operatora + class Ulamek public: Ulamek operator+(const Ulamek &f); ; Ulamek Ulamek::operator+(const Ulamek &b) long rl, rm; rl=l*b.m+b.l*m; rm=m*b.m; return Ulamek(rl,rm); int main () Ulamek f1(1,2), f2(1,3), f3, f4(1,6),f5(1,3),f6; f3=f1+f2; // skrót dla f3=f1.operator+(f2) return 0; Każdy przeciążany operator otrzymuje nazwę. Nazwa składa się ze słowa operator, po którym podany jest symbol przeciążanego operatora ( tym przypadku + ). Typ zwracanej wartości Argument funkcji przeciążającej operator Ulamek operator+ (Ulamek& b) Nazwa funkcji przeciążającej operator 12

Ograniczenia przeciążania operatorów Można przeciążać tylko operatory istniejące w C++, z wyjątkiem operatorów. (wybór składowej).* (wybór składowej za pomocą wskaźnika do składowej) :: (rezolucja zasięgu)?: (wybór) sizeof (rozmiar reprezentacji zmiennej) Nie można tworzyć nowych symboli operatorów z operatorów istniejących. Nie można zmieniać definicji operatorów dla typów wbudowanych. Nie można zmieniać pierwszeństwa operatorów, liczby argumentów na których działają i łączności operatora (lewo lub prawostronnej). Nie można stosować argumentów domyślnych. Wyjątek operator wywołania: operator(). Operator może być zdefiniowany jako metoda (funkcje składowe klasy) lub jako funkcja zewnętrzna. Niektóre operatory można definiować tylko jako metody (nie mogą być również funkcjami statycznymi); należą do nich operatory = [] () -> Funkcja operatora, której pierwszym argumentem jest typ podstawowy lub obiekt innej klasy nie może być metodą, musi być zdefiniowana jako funkcja zewnętrzna. Dostęp do prywatnych składowych obiektu można wtedy zapewnić np. poprzez zaprzyjaźnienie lub za pomocą interfejsu klasy. Przykład: Fakt, że zdefiniowano dla klasy operator + oraz = nie oznacza, że zdefiniowano operator +=. Ulamek Ulamek::operator += (const Ulamek &b) // możemy wykorzystać istniejący operator // a += b => a = a + b operator + jest już zdefiniowany *this = *this + b; return *this; Przykład: Fakt, że wymuszamy to, żeby mianownik był zawsze dodatni upraszcza implementację. bool Ulamek::operator<(const Ulamek &b) // wystarczy pomnożyć ponieważ mianownik zawsze jest dodatni return l * b.m < b.l * m; 13

Przykład 1 dodawanie liczby do ułamka Metoda czy funkcja zewnętrzna? Czy przy definicji operatora + dla klasy Ulamek z poprzedniego przykładu możemy wykonać działania? Ulamek a(1,2),b(1,3),c; c = a + 2; c = 2 + b; Dla c = a + 2 potrzebujemy funkcji: class Ulamek int l; int m; public: Ulamek operator+(int n); // funkcja składowa ; Ulamek Ulamek::operator+(int n) return Ulamek(l+m*n, m); main() c=a+2; // równoważne c=a.operator+(2) Może to być funkcja składowa klasy, ponieważ pierwszym argumentem jest obiekt klasy. Dla c = 2 + a potrzebujemy funkcji: class Ulamek int l; int m; public: friend Ulamek operator+(int, Ulamek ); // funkcja // zaprzyjaźniona ; Ulamek operator+(int n, Ulamek f) return Ulamek(n*f.m+f.l, f.m); main() c=2+a; // równoważne c=operator+(2,a) Nie może to być funkcja składowa klasy, ponieważ pierwszym argumentem jest typ wbudowany int. 14

Przykład 2 drukowanie ułamka Co chcemy uzyskać? Uproszczenie drukowania ułamka czyli przeciążenie operatora << int main () Ulamek f1(1,6), f2(2,6), f3; cout << "TEST KLASY Ulamek\n"; cout << f1 << '+' << f2 << '=' << (f1+f2) << endl; return 0; Jak to zrobić? Musi to być funkcja zewnętrzna. Rozwiązanie 1: dwie funkcje: metoda zapisująca ułamek do strumienia wyjściowego, funkcja zewnętrzna wykorzystująca to void Ulamek::drukuj(ostream& wy) wy << l << '/' << m; ostream& operator<<(ostream& wy, Ulamek &f) f.drukuj(wy); return wy; Rozwiązanie 2: zaprzyjaźnienie funkcji wyprowadzającej class Ulamek public: friend ostream& operator << (ostream &wy, const Ulamek &f); ; ostream& operator<<(ostream& wy, const Ulamek &f) wy << f.l << "/" <<f.m; return wy; 15

Przeciążanie operatora [] Jest to operator dwuargumentowy, drugim argumentem jest wartość indeksu. Funkcja operatora [] nie może być funkcją zaprzyjaźnioną. Funkcja operatora [] musi zwracać referencję jeśli operator ten może występować zarówno po prawej jak i po lewej stronie operatora przypisania (musimy dobrać taki typ, aby wartość elementu po lewej stronie przypisania można było zmieniać). Funkcję operatora [] można uzupełnić o sprawdzanie przekroczenia dopuszczalnego zakresu indeksów. Przykład: o Chcemy uzyskać dla klasy wektor możliwość odwoływania się do elementów składowych obiektów tej klasy za pomocą notacji: obiekt[i]. o Musimy przeciążyć operator []. Wtedy zamiast pisać obiekt.operator[](i) można będzie pisać obiekt[i]. #include <iostream> class wektor int lelem ; int * W ; wektor (int n) W = new int [lelem=n] ; ~wektor () delete [] W ; int & operator [] (int) ; // przeciążenie operatora [] ; int & wektor::operator [] (const int i) return W[i] ; // przykład użycia int main() int i ; wektor a(3), b(3), c(3) ; for (i=0 ; i<3 ; i++) a[i] = i ; b[i] = 2*i ; for (i=0 ; i<3 ; i++) c[i] = a[i]+b[i] ; for (i=0 ; i<3 ; i++) cout << c[i] << " " ; return 0; 16

Przeciążanie operatora ++ Jest to operator jednoargumentowy. Może być zapisany w notacji przedrostkowej (++a) lub przyrostkowej (x++). Musimy przeciążyć dwa operatory. Rozróżnienie następuje poprzez deklarację dodatkowego, nieużywanego parametru: class X public: typ operator++(); // wersja przedrostkowa typ operator++(int); // wersja przyrostkowa ; Przykład: Chcemy uzyskać dla klasy Ulamek możliwość zwiększania ułamka o 1. // operator przedrostkowy przeciąża się w zwykły sposób // działanie operatora: zwiększ i pobierz Ulamek operator++() l +=m; // równoważne this->l += this->m return *this; //operator przyrostkowy wymaga wprowadzenia sztucznego argumentu int, // którego się w funkcji nie używa // działanie operatora: pobierz i zwiększ Ulamek operator++(int) l +=m; // nowa wartość return Ulamek(l-m,m); // zwróć starą wartość Chcemy uzyskać dla klasy Punkt możliwość zwiększania współrzędnych punktu o 1. // operator przedrostkowy Punkt operator++() x++; y++; // zwiększ return *this; // pobierz nową wartość //operator przyrostkowy Punkt operator++(int) Punkt p = *this; // stara wartość x++; y++; // nowa wartość return p; // zwróć starą wartość 17

Konwersje Konwersja typu ma miejsce wtedy, kiedy wspólnie korzysta się ze zmiennych lub obiektów różnych typów, na przykład w instrukcjach przypisania, w wyrażeniach, podczas przekazywania argumentów aktualnych do wywoływanej funkcji. o Konwersja typu może być: o niejawna (ang.implicite) wykonywana automatycznie przez kompilator. o jawna (ang. explicite) wykonywana na życzenie użytkownika, Konwersja niejawna wykonywana jest przez kompilator wtedy, kiedy wynika to z kontekstu: o w wyrażeniach arytmetycznych (konwersja jednego argumentu na typ drugiego argumentu, zgodnie z regułami zdefiniowanymi w języku). int i=2; double d=2.5; cout<< (i+d) <<endl; // i zostanie przekształcone do double: 2. o w instrukcji przypisania (na typ występujący po lewej stronie operatora =) int i; double d=2.5; i=d; /* d zostanie obcięte do typu int: 2 */ o w instrukcji wywołania funkcji (na typ występujący w prototypie funkcji) extern double sqrt(double); // 2 jest przekształcone do double: 2. cout << "Pierwiastek z 2 to: " << sqrt(2) << endl; o podczas zwracania wartości wyznaczonej w funkcji, której typ jest różny od typu zadeklarowanego w nagłówku funkcji double roznica(int x, int y) // wynik zostanie przekształcony do double return x-y; Konwersja jawna wykonywana jest w wyniku użycia operatora konwersji. o Jawna konwersja w stylu języka C wymaga podania typu w nawiasach: double z=6.0 int k; k=(int)z; // lub k=int(z); z=(double)k; o Stara składnia języka C++ : double z=6.0 int k; k=int(z); z=double(k); W standardzie języka C++ wprowadzono nazwane operatory rzutowania: o static_cast - konwersja typów pokrewnych, np. double do int, o dynamic_cast - konwersja kontrolowana w czasie wykonywania programu, o const_cast - konwersja usuwająca kwalifikator const, o reinterpret_cast - konwersja typów niespokrewnionych, np. wskaźnik do niespokrewnionego typu wskaźnikowego. 18

Składnia instrukcji z rzutowaniem ma postać: Przykład: operator_rzutowania < typ konwersji> (wyrażenie); double d=2.58; int i = static_cast<int>d; double d=2.58; int i = (int)d; int zaokr(float f) return static_cast<int> (f+0.5); int zaokr(float f) return int(f+0.5); Konwersje związane z obiektami Możliwe są następujące przypadki: o typ wbudowany na obiekt należy zdefiniować odpowiedni konstruktor, o obiekt na typ wbudowany - należy zdefiniować funkcję rzutowania, o obiekt jednej klasy na obiekt innej klasy - odpowiedni konstruktor lub funkcja rzutowania. 19

Konwersja typu wbudowanego na typ klasy Wszystkie konstruktory z jednym argumentem typu wbudowanego realizują konwersję tego typu na typ klasy. class Punkt int x, y ; Punkt (int xx=0, int yy=0) x = xx ; y = yy ; Punkt (const Punkt & p) x = p.x ; y = p.y ; ; void fun (Punkt p) cout << "** funkcja fun " << "\n" ; W main() void fun (Punkt) ; Punkt a(3,4) ; a = Punkt (12) ; // konwersja: jawne wywołanie konstruktora, // utworzony zostanie obiekty tymczasowy typu Punkt, // następnie zostanie przypisany obiektowi a a = 12 ; // konwersja: wywołanie niejawne konstruktora: // a = Punkt(12) fun(4) ; // konwersja: wywołanie niejawne konstruktora, // konstruktor kopiujący nie będzie używany Załóżmy, że oprócz konstruktora jednoargumentowego zdefiniowano również operator przypisania liczby typu int obiektowi klasy Punkt. Który z nich będzie użyty w przypadku instrukcji: a = 12; Konwersja z użyciem konstruktora jest wykonywana niejawnie. Można zabronić używania konstruktora do konwersji niejawnych. Służy do tego słowo kluczowe explict: class Punkt: public: explicit Punkt(int); ; W main(): a=12; // zostanie odrzucone a=punkt(3); // OK. trzeba podać jawnie Punkt a, b(2,5); a=b+5; // błąd a=b+punkt(5); // OK. podano jawnie 20

Konwersja typu klasy na typ wbudowany Tego typu konwersja wymaga zdefiniowania funkcji operatora rzutowania. Przykład: class Punkt int x,y; Aby w main() można było dokonywać konwersji: Punkt a(2,3); int n; n = int(a); // zapis jawny funkcji operatora rzutowania n = a; // zapis niejawny trzeba zdefiniować funkcję rzutowania, która jest składową klasy: operator int(); // UWAGA: w funkcji operatora konwersji // nie podaje się typu i ma postać następującą: Punkt::operator int() return x; Przebieg konwersji: wywołanie funkcji operatora rzutowania, która przekształca obiekt w zmienną typu podstawowego, następnie wykonanie zwykłego podstawienia. Przykład: niejawne wywołanie operatora rzutowania, gdy przekazywany jest obiekt do funkcji #include <iostream> class Punkt int x, y ; Punkt (int xx=0, int yy=0) // konstruktor x = xx ; y = yy ; Punkt (const Punkt & p) // konstruktor kopiujący x = p.x ; y = p.y ; operator int() // operator rzutowania return x ; ; void fun (int n) cout << "** wywolanie funkcji z argumentem : " << n << endl ; int main() Punkt a(3,4) ; fun (a) ; Która z funkcji (konstruktor kopiujący, operator rzutowania) zostanie użyta przy przekazywaniu obiektu a do funkcji? A może obydwie? 21

Przykład: niejawne wywołanie operatora rzutowania podczas obliczania wartości wyrażenia class Punkt int x, y ; Punkt (int xx=0, int yy=0) x = xx ; y = yy ; operator int() return x ; ; Co wydrukuje poniższy program? #include <iostream> int main() Punkt a(3,4), b(5,7) ; int n1, n2 ; n1 = a + 3 ; cout << "n1 = " << n1 << "\n" ; n2 = a + b ; cout << "n2 = " << n2 << "\n" ; double z1, z2 ; z1 = a + 3 ; cout << "z1 = " << z1 << "\n" ; z2 = a + b ; cout << "z2 = " << z2 << "\n" ; return 0; Jak to działa: Kompilator po napotkaniu operatora dodawania, w którym jednym z argumentów jest obiekt klasy sprawdza, czy nie ma przeciążonego operatora dodawania. Jeśli go nie znajdzie, szuka operatora rzutowania, który pozwoli mu wykonać działanie ze standardowym operatorem dodawania. Przykład: konwersja ułamka do typu double (stara i nowa składnia) Ulamek::operator double() return double(licznik)/double(mianownik); lub Ulamek::operator double() return static_cast<double>(licznik)/static_cast<double>(mianownik); Użycie: Ulamek x(22,7); if (z>3.14 && z < double(x)) Ulamek x (22,7); if (z>3.14 && z < static_cast<double>(x)) Ulamek x (22,7); if (z>3.14 && z < x) 22

Konwersja typu jednej klasy na typ innej klasy Przykład: chcemy dokonywać konwersji obiektu typu Punkt na obiekt typu liczby zespolonej. Wariant 1: można w klasie A zdefiniować operator rzutowania wykonujący konwersję A na B: #include <iostream> class zespolona ; class Punkt int x, y ; Punkt (int xx=0, int yy=0) x=xx ; y=yy ; operator zespolona () ; // konwersja Punkt --> zespolona ; class zespolona double rzeczywista, urojona ; zespolona (double r=0, double i=0) rzeczywista=r ; urojona=i ; friend Punkt::operator zespolona () ; void drukuj () cout << rzeczywista << " + " << urojona <<"i\n" ; ; Punkt::operator zespolona () zespolona r ; r.rzeczywista=x ; r.urojona=y ; return r ; main() Punkt a(2,5) ; zespolona c ; c = (zespolona) a ; c.drukuj () ; // konwersja jawna Punkt b (9,12) ; c = b ; c.drukuj () ; // konwersja niejawna 23

Wariant 2: za pomocą konstruktora; konstruktor klasy A, który otrzymuje argument klasy B wykonuje konwersję B na A.: #include <iostream> class Punkt ; class Zespolona double rzeczywista, urojona ; Zespolona (double r=0, double i=0) rzeczywista=r ; urojona=i ; Zespolona (Punkt) ; void drukuj () cout << rzeczywista << " + " << urojona << "i\n" ; ; class Punkt int x, y ; Punkt (int xx=0, int yy=0) x=xx ; y=yy ; friend Zespolona::Zespolona (Punkt) ; ; Zespolona::Zespolona (Punkt p) rzeczywista = p.x ; urojona = p.y ; main() Punkt a(3,5) ; Zespolona c (a) ; c.drukuj () ; 24

ZADANIA 1. Co różni funkcję zaprzyjaźnioną z klasą od metody klasy? 2. Czy funkcja nie będąca składową klasy musi być z nią zaprzyjaźniona, aby odwoływać się do składowych klasy? 3. Czy stosowanie funkcji zaprzyjaźnionych łamie zasadę hermetyzacji klasy? Co zyskujemy osłabiając zasadę hermetyzacji danych? 4. Czy można ograniczyć przyjaźń klasy A i B tak, aby tylko niektóre składowe klasy B były zaprzyjaźnione z klasą A oraz niektóre składowe klasy A z klasą B.? Wyjaśnij dlaczego. 5. Podaj zalety związane z używaniem obiektów string, których nie posiadają ciągi znaków języka C. 6. Operator += można zaimplementować korzystając z uprzednio zaimplementowanego operatora + dla danej klasy lub bezpośrednio, nie wykorzystując zdefiniowanego uprzednio operatora +. Jakie są plusy i minusy obydwu rozwiązań? Wskazówka: rozważ efektywność a spójność. 7. Funkcja przeciążająca operator << w celu wyświetlenia wartości obiektu najczęściej zwraca referencję do obiektu ostream. Dlaczego? 8. Dlaczego zazwyczaj przeciąża się dwa operatory indeksowania? 9. Zbuduj klasę Wektor2 do obsługi wektorów w przestrzeni dwuwymiarowej. Wektor jest reprezentowany za pomocą pary swoich składowych x i y: v = (x,y). Klasa Wektor2 powinna mieć następujące składowe: class Wektor2 private: double x,y; public: Wektor2() // konstruktor bezargumentowy Wektor2(double a, double b) // konstruktor o dwóch argumentach x=a; y=b; Wektor2 roznica(wektor2& a); // różnica dwóch wektorów double iloczyn(wektor2& a); // iloczyn skalarny dwóch wektorów bool jestprostopadly(wektor2& a); // sprawdzenie, czy wektory są // prostopadłe bool jestrownolegly(wektor2& a); // sprawdzenie, czy wektory są // równoległe bool niezero(); void wyswietl(); // wyświetlanie wektora w postaci (x,y) ; Napisz krótki program testujący opracowaną klasę. (Wskazówka: dwa niezerowe wektory są prostopadłe, jeśli ich iloczyn skalarny wynosi 0. Należy zwrócić uwagę na to, że działania są wykonywane na liczbach rzeczywistych, zatem otrzymany iloczyn należy porównać z pewną małą liczbą rzeczywistą ε - na przykład liczbą 0.00000001). 10. Zdefiniuj funkcję konwersji dla klasy Wektor2 taką, aby obiekt tej klasy mógł być konwertowany na wartość typu double będącą długością wektora. 11. Dane są cztery proste A, B, C i D na płaszczyźnie. Napisz program, który sprawdza czy są one wzajemnie do siebie prostopadłe. Wykorzystaj klasę Wektor2 z zadania 1. 12. Uzupełnij klasę Ulamek tak, aby umożliwiała wykonywanie opisanych poniżej działań na ułamkach: class Ulamek int l; // licznik int m; // mianownik int nwp(int p, int q); // największy wspólny podzielnik public: Ulamek(int a=0, int b=1) ; // Konstruktor void wyswietl(); // Wyświetl ułamek w postaci l/m // Działania arytmetyczne na ułamkach Ulamek operator+(ulamek& y); // dodawania Ulamek operator-(); // minus jednoargumentowy Ulamek operator-(ulamek& y); // odejmowanie Ulamek operator*(ulamek& y); // mnożenie 25

Ulamek operator/(ulamek& y); // dzielenie Ulamek operator+=(ulamek& y); // dodawania Ulamek operator-=(ulamek& y); // odejmowanie Ulamek operator/=(ulamek& y); // dzielenie Ulamek operator*=(ulamek& y); // mnożenie // Porównywanie ułamków bool operator==(ulamek& y) // czy równe bool operator>(ulamek& y); // czy a > b bool operator<(ulamek& y); // czy a < b bool operator>=ulamek& y); // czy a>= b bool operator<=(ulamek& y); // czy a<= b // Różne testy bool czyzero(); // licznik = 0, mianownik = 1 bool czyjeden(); // licznik = 1, mianownik = 1 bool czyliczbacalkowita(); // mianownik = 1 ; 13. Zadania z: Stephen Prata, Szkoła programowania. Język C++, wyd.v, 2006: str. 603-605, ćwiczenia programistyczne. 26