C wykad 8 25 04 2013 Rzutowanie i

  • Slides: 32
Download presentation
C++ wykład 8 (25. 04. 2013) Rzutowanie i konwersje Przestrzenie nazw

C++ wykład 8 (25. 04. 2013) Rzutowanie i konwersje Przestrzenie nazw

Wskaźniki do składowych l Przykład zwykłych wskaźników: class K { public: int skl; //…

Wskaźniki do składowych l Przykład zwykłych wskaźników: class K { public: int skl; //… }; //… K *wskob; K ob, tab[10]; //… wskob = &ob; int x = wskob->skl; wskon = &tab[4];

Wskaźniki do składowych l l Definicja wskaźnika do składowych w klasie: TYP klasa: :

Wskaźniki do składowych l l Definicja wskaźnika do składowych w klasie: TYP klasa: : *wsk; gdzie TYP jest typem składnika w klasie klasa Pobranie adresu składowej w klasie: &klasa: : składowa Aby odnieść się do wskazywanego pola w obiekcie stosujemy operator. * lub ->*: ob_ref. *wsk wskaźnik->*wsk Wskaźnikami do składowych możemy pokazywać na pola i na metody w klasie.

Wskaźniki do składowych l Przykład wskaźników do składowych: class K { public: int r,

Wskaźniki do składowych l Przykład wskaźników do składowych: class K { public: int r, s; int f (int); //… }; //… int K: : *wskint = &K: : r; int (K: : *wskfun) (int) = &K: : f; //… K a; K *p = &a; wskint = &K: : s; int x = p->*wskint; int y = (a. *wskfun)(3);

Tradycyjne operatory rzutowania l l Tradycyjne operatory rzutowania jawnie przekształcają typ danych. Tradycyjne operatory

Tradycyjne operatory rzutowania l l Tradycyjne operatory rzutowania jawnie przekształcają typ danych. Tradycyjne operatory konwersji mogą przyjmować dwie formy: (typ)wyrażenie typ(wyrażenie) Przykłady: (int)3. 1415926 // forma rzutowania double(7*11+5) // forma funkcyjna Operacja jawnej konwersji typów jest niebezpieczna i należy ją stosować bardzo ostrożnie (tylko w razie konieczności). Zaleca się używać funkcyjnej formy przy rzutowaniu tradycyjnym.

Tradycyjne operatory rzutowania l l Kompilator umie przekształcać na siebie wszystkie typy podstawowe. Operator

Tradycyjne operatory rzutowania l l Kompilator umie przekształcać na siebie wszystkie typy podstawowe. Operator rzutowania eliminuje ostrzeżenia kompilatora przy przekształcaniu typów podstawowych. Kompilator nie będzie generował ostrzeżeń w przypadku konwersji na typach podstawowych, w których mamy do czynienia z promocją (konwersje niejawne). Przykłady: const double e = 2. 7182845904523; int x = (int)e; // wymagana konwersja double y = 2*x+1; // konwersja niejawna

Konstruktory konwertujące l Konstruktor konwertujący to konstruktor bez deklaratora explicit, który można wywołać z

Konstruktory konwertujące l Konstruktor konwertujący to konstruktor bez deklaratora explicit, który można wywołać z jednym parametrem: K: : K (typ x) {/*…*/} // typ!=K l Konstruktorów konwertujących może być wiele w jednej klasie. Deklarator explicit zabrania używać konstruktora konwertującego niejawnie. l

Konstruktory konwertujące l Przykład konstruktora konwertującego i jego niejawnego użycia: class zespolona { double

Konstruktory konwertujące l Przykład konstruktora konwertującego i jego niejawnego użycia: class zespolona { double re, im; public: zespolona (double r=0, double i=0); // … }; // … zespolona a; zespolona b = zespolona(1. 2); // jawna konwersja zespolona c = 3. 4; // niejawna konwersja zespolona d = (zespolona)5. 6; // rzutowanie zespolona e = static_cast<zespolona>(7. 8); zespolona f(9. 0, 0. 9);

Operatory konwersji l l l Operator konwersji ma następującą postać: operator typ (); Operator

Operatory konwersji l l l Operator konwersji ma następującą postać: operator typ (); Operator konwersji ma pustą listę argumentów i nie ma określonego typu wyniku (typ wyniku jest określony poprzez nazwę tego operatora). Operator konwersji musi być funkcją składową w klasie. Operator konwersji jest dziedziczony. Operator konwersji może być wirtualny. Operatorów konwersji może być wiele w jednej klasie.

Operator static_cast l l l Operator rzutowania static_cast ma następującą postać: static_cast<typ>(wyrażenie) Rzutowanie to

Operator static_cast l l l Operator rzutowania static_cast ma następującą postać: static_cast<typ>(wyrażenie) Rzutowanie to działa tak jak rzutowanie tradycyjne – jeśli jest zdefiniowana operacja rzutowania to zostanie ona wykonana. Rzutowania static_cast używa się do konwersji typów pokrewnych (zmiana typu wskaźnikowego w tej samej hierarchii klas, wyliczenia do typu całkowitego, typu zmiennopozycyjnego do całkowitego, itp).

Rzutowanie const_cast l l Operator rzutowania const_cast ma następującą postać: const_cast<typ>(wyrażenie) przy czym typ

Rzutowanie const_cast l l Operator rzutowania const_cast ma następującą postać: const_cast<typ>(wyrażenie) przy czym typ powinno być wskaźnikiem, referencją lub wskaźnikiem do składowej. Rzutowanie to pozwala dodać albo zlikwidować deklarator const lub volatile w typie wyrażenia (ale nie pozwala zmienić typu głównego).

Rzutowanie reinterpret_cast l Operator rzutowania reinterpret_cast ma następującą postać: reinterpret_cast<typ>(wyrażenie) l Rzutowanie to ma

Rzutowanie reinterpret_cast l Operator rzutowania reinterpret_cast ma następującą postać: reinterpret_cast<typ>(wyrażenie) l Rzutowanie to ma zmienić interpretację typu wyrażenia (kompilator nie sprawdza sensu tego rzutowania). Operator rzutowania reinterpret_cast tworzy wartość nowego typu, który ma ten sam wzorzec bitowy co podane wyrażenie. Rzutowanie to nie gwarantuje przenośności. l l

Rzutowanie dynamic_cast l l Operator rzutowania dynamic_cast ma następującą postać: dynamic_cast<typ>(wyrażenie) przy czym wyrażenie

Rzutowanie dynamic_cast l l Operator rzutowania dynamic_cast ma następującą postać: dynamic_cast<typ>(wyrażenie) przy czym wyrażenie powinno być wskaźnikiem lub referencją do typu polimorficznego. Rzutowanie to wykonuje się w trakcie działania programu. dynamic_cast<T*>(p) zwraca wskaźnik typu T* gdy obiekt wskazywany przez p jest typu T lub ma unikatową klasę bazową typu T (w przeciwnym przypadku zwraca 0). dynamic_cast<T&>(r) zwraca referencję typu T& gdy obiekt wskazywany przez r jest typu T lub ma unikatową klasę bazową typu T (w przeciwnym przypadku rzuca wyjątek bad_cast).

RTTI l l Operator typeid() zwraca referencję do obiektu opisującego typ wyrażenia w nawiasach

RTTI l l Operator typeid() zwraca referencję do obiektu opisującego typ wyrażenia w nawiasach (można też podać nazwę typu). Klasa type_info zdefiniowana w <typeinfo> służy do opisu typu danych lub wyrażeń. W klasie type_info są zdefiniowane operatory == i != do porównywania informacji o typie. W klasie type_info jest zdefiniowana metoda name() dostarczająca nazwę typu w postaci const char *.

Przestrzenie nazw l l Przestrzeń nazw to obszar, w którym umieszcza się różne deklaracje

Przestrzenie nazw l l Przestrzeń nazw to obszar, w którym umieszcza się różne deklaracje i definicje. Przestrzeń nazw definiuje zasięg, w którym dane nazwy będą obowiązywać i będą dostępne. Przestrzenie nazw rozwiązują problem kolizji nazw. Przestrzenie nazw wspierają modularność kodu.

Definicja przestrzeni nazw l l l Przestrzeń nazw tworzymy za pomocą słowa kluczowego namespace,

Definicja przestrzeni nazw l l l Przestrzeń nazw tworzymy za pomocą słowa kluczowego namespace, ograniczając zawartość klamrami: namespace przestrzeń { // deklaracje i definicje } Aby odnieść się do typu, funkcji albo obiektu umieszczonego w przestrzeni nazw musimy stosować kwalifikator zakresu przestrzeń: : poza tą przestrzenią. Funkcja main() musi być globalna, aby środowisko uruchomieniowe rozpoznało ją jako funkcję specjalną. Do nazw globalnych odnosimy się za pomocą pustego kwalifikatora zakresu : : , na przykład : : wspolczynnik. Jeśli w przestrzeni nazw zdefiniujemy klasę to do składowej statycznej w takiej klasie odnosimy się kwalifikując najpierw nazwą przestrzeni a potem nazwą klasy przestrzeń: : klasa: : składowa.

Definicja przestrzeni nazw l Przykład przestrzeni nazw: namespace wybory { int min 2 (int,

Definicja przestrzeni nazw l Przykład przestrzeni nazw: namespace wybory { int min 2 (int, int); int min 3 (int, int); } int wybory: : min 2 (int a, int b) { return a<b ? a : b; } int wybory: : min 3 (int a , int b , int c) { return min 2(a, b), c); } int min 4 (int a, int, b, int c, int d) { return wybory: : min 2(a, b), wybory: : min 2(c, d)); }

Deklaracja użycia l l l Deklaracja użycia wprowadza lokalny synonim nazwy z innej przestrzeni

Deklaracja użycia l l l Deklaracja użycia wprowadza lokalny synonim nazwy z innej przestrzeni nazw (wskazanej nazwy można wówczas używać bez kwalifikowania jej nazwą przestrzeni). Deklaracja użycia using ma postać: using przestrzeń: : symbol; Deklaracja użycia obowiązuje do końca bloku, w którym wystąpiła. Deklaracje użycia stosujemy w celu poprawienia czytelności kodu. Deklaracje użycia należy stosować tak lokalnie, jak to jest możliwe. Jeśli większość funkcji w danej przestrzeni nazw korzysta z jakiejś nazwy z innej przestrzeni, to deklaracje użycia można włączyć do przestrzeni nazw.

Dyrektywa użycia l l l Dyrektywa użycia udostępnia wszystkie nazwy z określonej przestrzeni nazw.

Dyrektywa użycia l l l Dyrektywa użycia udostępnia wszystkie nazwy z określonej przestrzeni nazw. Dyrektywa użycia using namespace ma postać: using namespace przestrzeń; Dyrektywy użycia stosuje się najczęściej w funkcjach, w których korzysta się z wielu symboli z innej niż ta funkcja przestrzeni nazw. Globalne dyrektywy użycia są stosowane do transformacji kodu i nie powinno się ich stosować do innych celów. Globalne dyrektywy użycia w pojedynczych jednostkach translacji (w plikach. cpp) są dopuszczalne w programach testowych czy w przykładach, ale w produkcyjnym kodzie jest to niestosowne i jest uważane za błąd. Globalnych dyrektyw użycia nie wolno stosować w plikach nagłówkowych!

Anonimowe przestrzenie nazw l l l Anonimową przestrzeń nazw tworzymy za pomocą słowa kluczowego

Anonimowe przestrzenie nazw l l l Anonimową przestrzeń nazw tworzymy za pomocą słowa kluczowego namespace bez nazwy, ograniczając zawartość klamrami: namespace { // deklaracje i definicje } Anonimowa przestrzeń nazw zastępuje użycie deklaratora static przy nazwie globalnej – dostęp do nazw zdefiniowanych w przestrzeni anonimowej jest ograniczony do bieżącego pliku. Dostęp do anonimowej przestrzeni nazw jest możliwy dzięki niejawnej dyrektywie użycia. namespace $$$ { // deklaracje i definicje } using namespace $$$; W anonimowej przestrzeni nazw $$$ jest unikatową nazwą w zasięgu, w którym jest zdefiniowana ta przestrzeń.

Przeszukiwanie nazw l l l Gdy definiujemy funkcję z jakiejś przestrzeni nazw (przed nazwą

Przeszukiwanie nazw l l l Gdy definiujemy funkcję z jakiejś przestrzeni nazw (przed nazwą definiowanej właśnie funkcji stoi kwalifikator przestrzeni) to w jej wnętrzu dostępne są wszystkie nazwy z tej przestrzeni. Funkcja z argumentem typu T jest najczęściej zdefiniowana w tej samej przestrzeni nazw co T. Jeżeli więc nie można znaleźć funkcji w kontekście, w którym się jej używa, to szuka się jej w przestrzeniach nazw jej argumentów. Jeżeli funkcję wywołuje metoda klasy K, to pierwszeństwo przed funkcjami znalezionymi przez typy argumentów mają metody z klasy K i jej klas bazowych.

Aliasy przestrzeni nazw l l Jeżeli użytkownicy nadają przestrzeniom nazw krótkie nazwy, to mogą

Aliasy przestrzeni nazw l l Jeżeli użytkownicy nadają przestrzeniom nazw krótkie nazwy, to mogą one spowodować konflikt. Długie nazwy są niewygodne w użyciu. Dylemat ten można rozwiązać za pomocą krótkiego aliasu dla długiej nazwy przestrzeni nazw. Aliasy dla przestrzeni nazw tworzymy za pomocą słowa kluczowego namespace z dwiema nazwami namespace krótka = długa_nazwa_przestrzeni; Przykład: namespace American_Telephone_and_Telegraph { // tutaj zdefiniowano Napis } namespace ATT = American_Telephone_and_Telegraph; American_Telephone_and_Telegraph: : Napis n = "x"; AAT: : Napis nn = "y"; Nadużywanie aliasów może prowadzić do nieporozumień!

Komponowanie i wybór l l Interfejsy projektuje się po to, by zminimalizować zależności pomiędzy

Komponowanie i wybór l l Interfejsy projektuje się po to, by zminimalizować zależności pomiędzy różnymi częściami programu. Minimalne interfejsy prowadzą do systemów łatwiejszych do zrozumienia, w których lepiej ukrywa się dane i implementację, łatwiej się je modyfikuje oraz szybciej kompiluje. Eleganckim narzędziem do konstruowania interfejsów są przestrzenie nazw.

Komponowanie i wybór l l Gdy chcemy utworzyć interfejs z istniejących już interfejsów to

Komponowanie i wybór l l Gdy chcemy utworzyć interfejs z istniejących już interfejsów to stosujemy komponowanie przestrzeni nazw za pomocą dyrektyw użycia, na przykład: namespace His_string { class String { /*. . . */ }; String operator+ (const String&, const String&); String operator+ (const String&, const char*); void fill (char) ; //. . . } namespace Her_vector { template<class T> class Vector { /*. . . */ }; //. . . } namespace My_lib { using namespace His_string; using namespace Her_vector; void my_fct(String&) ; } Dyrektywa użycia wprowadza do zasięgu wszystkie deklarację z podanej przestrzeni nazw.

Komponowanie i wybór l Teraz przy pisaniu programu można posługiwać się My_lib: void f

Komponowanie i wybór l Teraz przy pisaniu programu można posługiwać się My_lib: void f () { My_lib: : String s = "Byron"; // znajduje My_lib: : His_string: : String //. . . } using namespace My_lib; void g (Vector<String> &vs) { //. . . my_fct(vs[5]); //. . . }

Komponowanie i wybór l l Gdy chcemy utworzyć interfejs i dołożyć do niego kilka

Komponowanie i wybór l l Gdy chcemy utworzyć interfejs i dołożyć do niego kilka nazw z innych interfejsów to stosujemy wybór za pomocą deklaracji użycia, na przykład: namespace My_string { using His_string: : String; using His_string: : operator+; // … } Deklaracja użycia wprowadza do zasięgu każdą deklarację o podanej nazwie. Pojedyncza deklaracja użycia może wprowadzić każdy wariant funkcji przeciążonej.

Komponowanie i wybór l l l Łączenie komponowania (za pomocą dyrektyw użycia) z wyborem

Komponowanie i wybór l l l Łączenie komponowania (za pomocą dyrektyw użycia) z wyborem (za pomocą deklaracji użycia) zapewnia elastyczność potrzebną w praktyce. Z użyciem tych mechanizmów możemy zapewnić dostęp do wielu udogodnień, a zarazem rozwiązać problem konfliktu nazw i niejednoznaczności wynikających z komponowania. Nazwy zadeklarowane jawnie w przestrzeni nazw (łącznie z nazwami wprowadzonymi za pomocą deklaracji użycia) mają pierwszeństwo przed nazwami wprowadzonymi za pomocą dyrektyw użycia. Nazwę w nowej przestrzeni nazw można zmienić za pomocą instrukcji typedef lub poprzez dziedziczenie.

Przestrzenie nazw są otwarte l l Przestrzeń nazw jest otwarta, co oznacza, że można

Przestrzenie nazw są otwarte l l Przestrzeń nazw jest otwarta, co oznacza, że można do niej dodawać nowe pojęcia w kilku deklaracjach (być może rozmieszczonych w różnych plikach), na przykład: namespace NS { int f (); // NS ma nową składową f() } namespace NS { int g (); // teraz NS ma dwie składowe f() i g() } Definiując wcześniej zadeklarowaną składową w przestrzeni nazw, bezpieczniej jest użyć operatora zakresu niż ponownie otwierać przestrzeń (kompilator nie wykryje literówek w nazwie składowej), na przykład: namespace NS { int h (); } int NS: : hhh () // błąd - zamiast h napisano hhh { /*…*/ }

Przestrzeń nazw std l l l W języku C++ wszystkie nazwy z biblioteki standardowej

Przestrzeń nazw std l l l W języku C++ wszystkie nazwy z biblioteki standardowej są umieszczone w przestrzeni nazw std. W języku C tradycyjnie używa się plików nagłówkowych i wszystkie nazwy w nich deklarowane są w przestrzeni globalnej (dostępne bez żadnych kwalifikacji). Aby zapewnić możliwość kompilowania przez kompilatory C++ programów napisanych w C przyjęto, że jeśli użyjemy tradycyjnej (pochodzącej z C) nazwy pliku nagłówkowego, to odpowiedni plik jest włączany i zadeklarowane w nim nazwy są dodawane do globalnej przestrzeni nazw. Jeśli natomiast ten sam plik nagłówkowy włączymy pod nową nazwą, to nazwy w nim deklarowane są dodawane do przestrzeni nazw std. Przyjęto przy tym konwencję, że pliki nagłówkowe z C nazwie nazwa. h są w C++ nazywane cnazwa (pary plików <math. h> i <cmath>, itp).

Nowości z C++11 – polecenie utworzenia domyślnych metod l l Polecenie to realizujemy za

Nowości z C++11 – polecenie utworzenia domyślnych metod l l Polecenie to realizujemy za pomocą specyfikatora =default za nagłówkiem metody. W przypadku domyślnego konstruktora, mogłaby być przydatna możliwość jawnego przekazania kompilatorowi polecenia utworzenia go (kompilator nie stworzy konstruktora domyślnego, jeśli obiekt posiada dowolne konstruktory): struct Some. Type { // domyślny konstruktor jest jawnie określony Some. Type() = default; Some. Type(Other. Type value); // … };

Nowości z C++11 – polecenie blokowania domyślnych metod l l Polecenie to realizujemy za

Nowości z C++11 – polecenie blokowania domyślnych metod l l Polecenie to realizujemy za pomocą specyfikatora =delete za nagłówkiem metody. Generowanie pewnych metod przez kompilator może być jawnie zablokowane: struct Non. Copyable { Non. Copyable & operator= (const Non. Copyable&) = delete; Non. Copyable () = default; // … }; struct Non. Newable { void *operator new(std: : size_t) = delete; // … };

Nowości z C++11 – polecenie blokowania domyślnych metod l Specyfikator =delete może być użyty

Nowości z C++11 – polecenie blokowania domyślnych metod l Specyfikator =delete może być użyty do zablokowania wywołania dowolnej metody, co może być użyte do zablokowania wywołania metody z określonymi parametrami: struct No. Double { void f(int i); void f(double) = delete; // … }; Próba wywołania f() z argumentem typu double będzie odrzucona przez kompilator (kompilator nie wykona niejawnej konwersji do int).