Pobierz prezentację
Pobieranie prezentacji. Proszę czekać
OpublikowałKarol Madej Został zmieniony 8 lat temu
1
K URS JĘZYKA C++ – WYKŁAD 7 (8.04.2015) Konwersje
2
S PIS TREŚCI Tradycyjne operatory rzutowania Konstruktory konwertujące Operatory konwersji Rzutowanie static_cast Rzutowanie const_cast Rzutowanie reinterpret_cast Rzutowanie dynamic_cast RTTI – operator typeid() Automatyczne określanie typu ( auto ) Wydobycie typu wyrażenia ( decltype )
3
T RADYCYJNE OPERATORY RZUTOWANIA 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 konstruktorowa Operacja jawnej konwersji typów jest niebezpieczna i należy ją stosować bardzo ostrożnie (tylko w razie konieczności). Zaleca się używać konstruktorowej formy przy rzutowaniu tradycyjnym.
4
T RADYCYJNE OPERATORY RZUTOWANIA 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.71828182845904523; int x = (int)e; // wymagana konwersja double y = 2*x+1; // konwersja niejawna
5
K ONSTRUKTORY KONWERTUJĄCE Konstruktor konwertujący to konstruktor bez deklaratora explicit, który można wywołać z jednym parametrem: K::K (typ x) {/*…*/} // typ!=K Konstruktorów konwertujących może być wiele w jednej klasie. Deklarator explicit zabrania używać konstruktora konwertującego niejawnie.
6
K ONSTRUKTORY KONWERTUJĄCE 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 (7.8); zespolona f(9.0,0.9);
7
O PERATORY KONWERSJI 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. Przy operatorach konwersji można użyć słowa kluczowego explicit aby uniknąć konwersji niejawnej.
8
O PERATOR STATIC _ CAST Operator rzutowania static_cast ma następującą postać: static_cast (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).
9
R ZUTOWANIE CONST _ CAST Operator rzutowania const_cast ma następującą postać: const_cast (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).
10
R ZUTOWANIE REINTERPRET _ CAST Operator rzutowania reinterpret_cast ma następującą postać: reinterpret_cast (wyrażenie) przy czym typ powinno być wskaźnikiem, referencją lub typem porządkowym (znaki, liczby całkowite, typ boolowski, wykliczenia). 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.
11
R ZUTOWANIE DYNAMIC _ CAST Operator rzutowania dynamic_cast ma następującą postać: dynamic_cast (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 (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 (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 ).
12
RTTI Operator typeid() zwraca referencję do obiektu opisującego typ wyrażenia w nawiasach (można też podać nazwę typu). Klasa type_info zdefiniowana w 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 *.
13
A UTOMATYCZNE OKREŚLANIE TYPU W definicji zmiennej z jawnym inicjowaniem można użyć słowa kluczowego auto – można w ten sposób utworzyć zmienną o typie takim, jak typ inicjującego wyrażenia. Przykład 1: auto jakasZmienna = L"To jest tekst"; Typ jakasZmienna jest programiście łatwiej napisać słowo auto niż const wchar_t * (taki jak dla literału tekstowego). Przykład 2: auto innaZmienna = boost::bind(&Funkcja, _2, _1, Obiekt); Typem innaZmienna może być cokolwiek zwracanego przez pewną funkcję szablonową pod boost::bind dla danych argumentów, typ ten jest łatwy do określenia przez kompilator, natomiast dla użytkownika jest to trudne.
14
A UTOMATYCZNE OKREŚLANIE TYPU Prztkład 3: Typ auto jest przydatny przy ograniczaniu rozwlekłości kodu. Zamiast pisać: for (vector ::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr) … Programista może użyć krótszego zapisu: for (auto itr = myvec.begin(); itr != myvec.end(); ++itr) …
15
W YDOBYCIE TYPU WYRAŻENIA Operator decltype pozwala na uzyskanie typu wyrażenia. Jego głównym przeznaczeniem tego operatora jest programowanie uogólnione, w którym często trudno, jeśli w ogóle jest to możliwe, określić typy zależne od parametrów szablonu. Typ określony za pomocą operatora decltype zgadza się z typem obiektu lub funkcji zadeklarowanym w kodzie źródłowym. Podobnie jak w przypadku operatora sizeof, operand decltype nie jest wykonywany.
16
W YDOBYCIE TYPU WYRAŻENIA Przykłady: const int& foo(); int i; struct A { double x; }; const A *a = new A(); decltype(i) x2; // typ to int decltype(foo()) x1 = i; // typ to const int& decltype(a->x) x3; // typ to double decltype((a->x)) x4; // typ to const double& Wyrażenie w nawiasie (a->x) nie jest ani id-wyrażenie ani dostępem do członków klasy, a stąd nie oznacza nazwanego obiektu. Ponieważ to wyrażenie jest l-wartością, jego wydedukowany typ jest referencją do typu wyrażenia, czyli const double&.
18
K URS JĘZYKA C++ – WYKŁAD 7 (8.04.2015) Przestrzenie nazw
19
S PIS TREŚCI Czym jest przestrzeń nazw Definicja przestrzeni nazw Deklaracja użycia Dyrektywa użycia Anonimowe przestrzenie nazw Poszukiwanie nazw w przestrzeniach Aliasy przestrzeni nazw Komponowanie i wybór w kontekście tworzenia nowej przestrzeni nazw Przestrzenie nazw są otwarte Przestrzeń nazw std
20
C ZYM JEST PRZESTRZEŃ NAZW 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.
21
D EFINICJA PRZESTRZENI NAZW 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.
22
D EFINICJA PRZESTRZENI NAZW Przykład przestrzeni nazw: namespace wybory { int min2 (int, int); int min3 (int, int, int); } int wybory::min2 (int a, int b) { return a<b ? a : b; } int wybory::min3 (int a, int b, int c) { return min2(min2(a,b),c); } int min4 (int a, int, b, int c, int d) { return wybory::min2( wybory::min2(a,b), wybory::min2(c,d)); }
23
D EKLARACJA UŻYCIA 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.
24
D YREKTYWA UŻYCIA 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!
25
A NONIMOWE PRZESTRZENIE NAZW 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ń.
26
P OSZUKIWANIE NAZW W PRZESTRZENIACH 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.
27
A LIASY PRZESTRZENI NAZW 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"; ATT::Napis nn = "y"; Nadużywanie aliasów może prowadzić do nieporozumień!
28
K OMPONOWANIE I WYBÓR 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.
29
K OMPONOWANIE I WYBÓR 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 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.
30
K OMPONOWANIE I WYBÓR 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 &vs) { //... my_fct(vs[5]); //... }
31
K OMPONOWANIE I WYBÓR 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.
32
K OMPONOWANIE I WYBÓR Łą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.
33
P RZESTRZENIE NAZW SĄ OTWARTE 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 { /*…*/ }
34
P RZESTRZEŃ NAZW STD 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 i, itp).
35
Z AGNIEŻDŻONE PRZESTRZENIE NAZW I KLASY ZAGNIEŻDŻONE Wewnątrz przestrzeni naw można zdefiniowań inną przestrzeń. Klasa tworzy lokalną przestrzeń nazw – domyślną dla składowych w tej klasie. W definicji klasy można umieścić definicję innego typu: klasy, struktury, wyliczenia czy też typu nazwanego za pomocą instrukcji typedef.
36
K URS JĘZYKA C++ – WYKŁAD 7 (8.04.2015) STL
37
S PIS TREŚCI Pojęcie szablonu Para wartości pair<> Wektor danych vector<>
38
S ZABLONY Szablon funkcji lub klasy to konstrukcja językowa, która pozwala sparametryzować definicję funkcji lub klasy typem danych, na którym ta funkcja czy klasa działa. Prawie cała biblioteka standardowa STL jest oparta na szablonach.
39
K LASA PAIR <> W pliku nagłówkowym zdefiniowano szablon klasy pair, który służy do przechowywania pary wartości. Para std::pair<> to struktura z dwoma polami first i second dowolnych typów podawanych jako parametry wzorca. Przykład: pair pi = new pair (”pi”,3.141593); W szablonie klasy std::pair<> zdefiniowano oprócz konstruktora z dwiema wartościami inicjalizującymi pola first i second także konstruktor kopiujący i domyślny.
40
K LASA PAIR <> Typy występujące w parze mogą być wydedukowane, gdy para jest tworzona za pomocą funkcji szablonowej make_pair(). Przykład: pair e = make_pair(string(”e”),2.718282); Dla szablonu klasy std::pair<> zdefiniowano nieskładowe operatory relacji porównujących == i < (porównywane są pierwsze pola, a gdy te są równe porównywane są drugie pola). Funkcja składowa oraz zewnętrzna funkcja szablonowa swap<>() zamienia zawartości dwóch par.
41
K LASA VECTOR <> W pliku nagłówkowym zdefiniowano szablon klasy vector, który służy do przechowywania dowolnej liczby wartości określonego typu. Kolekcja std::vector<> przechowuje swoje elementy w tablicy dynamicznej – dodawanie (metoda push_back() ) i usuwanie (metoda pop_back() ) elementów na końcu tablicy działa bardzo szybko. Przykład: vector coll; for (int i=1; i<=10; ++i) coll.push_back(i*i); for (int i=0; i<coll.size(); ++i) cout << coll[i] << ’ ’; cout << endl;
42
K LASA VECTOR <> W kolekcji std::vector<> istnieją metody do wstawiania elementu na początku wektora push_front() i usuwania elementu z początku pop_front(). Dostęp do elementów w wektorze jest realizowany za pomocą operatora indeksowania [] albo za pomocą metody at() ; odczytanie wartości pierwszego elementu w wektorze można zrobić za pomocą metody front() a ostatniego za pomocą back(). Metoda empty() mówi czy wektor jest pusty a metoda size() zwraca liczbę wszystkich elementów w wektorze. Metoda clear() usuwa wszystkie elementy z wektora.
Podobne prezentacje
© 2024 SlidePlayer.pl Inc.
All rights reserved.