[ Pobierz całość w formacie PDF ]
zacznie on traktować jakiś typ danych jako zupełnie inny, nawet całkowicie niezwiązany z
tym oryginalnym!
Istotnie - za pośrednictwem wskaznika typu void* możliwe jest dosłownie
zinterpretowanie ciągu bitów jako dowolnego typu zmiennych. Dzieje się tak dlatego,
że podczas rzutowania nie jest dokonywane żadne sprawdzenie faktycznej poprawności
typów. static_cast nie działa tak jak dynamic_cast i nie kontroluje sensowności oraz
celowości rzutowania.
Zakres stosowalności dynamic_cast jest zaś, jak pamiętamy, ograniczony tylko do typów
polimorficznych. Skalarne typy podstawowe z pewnościa nimi nie są, dlatego nie możemy
do nich używać tego typu rzutowania.
Potencjalnie więc dostajemy do ręki brzytwę, którą można się niezle pokaleczyć. W
określonych sytuacjach potrzebne jest jednak takie dosłowne potraktowanie pewnego
Wskazniki 291
rodzaju danych jako zupełnego innego. Pośrednictwo typu void* w niskopoziomowych
konwersjach między wskaznikami staje się wtedy kłopotliwe.
Z tego powodu (a także z potrzeby całkowitego zastąpienia rzutowania w stylu C)
wprowadzono do C++ kolejny operator rzutowania - reinterpret_cast. Potrafi on
rzutować dowolny typ wskaznikowy na dowolny inny typ wskaznikowy i nie tylko.
Konwersje przy użyciu tego operatora prawie zawsze nie są więc bezpieczne i powinny
być stosowane wyłącznie wtedy, gdy zależy nam na mechanicznej zmianie (bit po
bicie) jednego typu danych w inny.
Jeżeli chodzi o przykłady, to chyba jedynym bezpiecznym zastosowaniem
reinterpret_cast jest zapisanie adresu pamięci ze wskaznika do zwykłej zmiennej
liczbowej:
int* pnWskaznik;
unsigned uAdres = reinterpret_cast(pnWskaznik);
W innych przypadkach stosowanie tego operatora powinno być wyjątkowo ostrożne i
oszczędne.
Kompletnych informacji o reinterpret_cast dostarcza oczywiście MSDN. Jest tam także
ciekawy artykuł, wyjaśniający dogłębnie różnice między tym operatorem, a zwykłym
rzutowaniem static_cast.
Istnieje jeszcze jeden, czwarty operator rzutowania const_cast. Jego zastosowanie jest
bardzo wąskie i ogranicza się do usuwania modyfikatora const z opatrzonych nim typów
danych. Można więc użyć go, aby zmienić stały wskaznik lub wskaznik do stałej w
zwykły.
Bliższe informacje na temat tego operatora można naturalnie znalezć we wiadomym
zródle :)
Wskazniki i tablice
Tradycyjnie wskazników używa się do operacji na tablicach. Celowo piszę tu tradycyjnie ,
gdyż prawie wszystkie te operacje można wykonać także bez użycia wskazników, więc
korzystanie z nich w C++ nie jest tak popularne jak w jego generacyjnym poprzedniku.
Ponieważ jednak czasem będziemy zmuszeni korzystać z kodu wywodzącego się z czasów
C (na przykład z Windows API), wiedza o zastosowaniu wskazników w stosunku do tablic
może być przydatna. Obejmuje ona także zagadnienia łańcuchów znaków w stylu C,
którym poświęcimy osobny paragraf.
Już słyszę głosy oburzenia: Przecież miałeś zajmować się nauczaniem C++, a nie
wywlekaniem jego różnic w stosunku do swego poprzednika! . Rzeczywiście, to prawda.
Wskazniki są to dziedziną języka, która najczęściej zmusza nas do podróży w przeszłość.
Wbrew pozorom nie jest to jednak przeszłość zbyt odległa, skoro z powodzeniem wpływa
na terazniejszość. Z właściwości wskazników i tablic będziesz bowiem korzystał znacznie
częściej niż sporadycznie.
Tablice jednowymiarowe w pamięci
Swego czasu powiedzieliśmy sobie, że tablice są zespołem wielu zmiennych opatrzonych
tą samą nazwą i identyfikowanych poprzez indeksy. Symbolicznie przedstawialiśmy na
diagramach tablice jednowymiarowe jako równy rząd prostokątów, wyobrażających
kolejne elementy.
To nie był wcale przypadek. Tablice takie mają bowiem ważną cechę:
Kolejne elementy tablicy jednowymiarowej są ułożone obok siebie, w ciągłym
obszarze pamięci.
Podstawy programowania
292
Nie są więc porozrzucane po całej dostępnej pamięci (czyli pofragmentowane), ale
grzecznie zgrupowane w jeden pakiet.
Schemat 34. Ułożenie tablicy jednowymiarowej w pamięci operacyjnej
Dzięki temu kompilator nie musi sobie przechowywać adresów każdego z elementów
tablicy, aby programista mógł się do nich odwoływać. Wystarczy tylko jeden: adres
początku tablicy, jej zerowego elementu.
W kodzie można go łatwo uzyskać w ten sposób:
// tablica i wskaznik
int aTablica[5];
int* pnTablica;
// pobranie wskaznika na zerowy element tablicy
pnTablica = &aTablica[0];
Napisałem, że jest to także adres początku samej tablicy, czyli w gruncie rzeczy wartość
kluczowa dla całego agregatu. Dlatego reprezentuje go również nazwa tablicy:
[ Pobierz całość w formacie PDF ]