r08-06.pdf

(464 KB) Pobierz
Szablon dla tlumaczy
Rozdział 8.
Wskaźniki
Jedną z najbardziej przydatnych dla programisty C++ rzeczy jest możliwość bezpośredniego
manipulowania pamięcią za pomocą wskaźników.
Z tego rozdziału dowiesz się:
• czym są wskaźniki,
• jak deklarować wskaźniki i używać ich,
• czym jest sterta i w jaki sposób można manipulować pamięcią.
Wskaźniki stanowią podwójne wyzwanie dla osoby uczącej się języka C++: po pierwsze, mogą
być niezrozumiałe, a po drugie, na początku może nie być jasne, do czego mogą się przydać. W
tym rozdziale krok po kroku wyjaśnimy działanie wskaźników. Aby w pełni zrozumieć potrzebę
ich używania, musisz zapoznać się z zawartością kolejnych rozdziałów.
Czym jest wskaźnik?
Wskaźnik (ang. pointer ) jest zmienną, przechowującą adres pamięci. To wszystko. Jeśli rozumiesz
to proste stwierdzenie, wiesz już wszystko o wskaźnikach. Jeszcze raz: wskaźnik jest zmienną
przechowującą adres pamięci.
Kilka słów na temat pamięci
Aby zrozumieć, do czego służą wskaźniki, musisz wiedzieć kilka rzeczy o pamięci komputera.
Pamięć jest podzielona na kolejno numerowane lokalizacje. Każda zmienna umieszczona jest w
danym miejscu pamięci, jednoznacznie określonym przez tzw. adres pamięci. Rysunek 8.1
przedstawia schemat miejsca przechowywania zmiennej typu unsigned long o nazwie theAge
(wiek).
Rys. 8.1. Schemat przechowywania zmiennej theAge
6144286.001.png
Użycie operatora adresu (&)
W każdym komputerze pamięć jest adresowana w inny sposób, za pomocą różnych, złożonych
schematów. Zwykle programista nie musi znać konkretnego adresu danej zmiennej, tymi
szczegółami zajmuje się kompilator. Jeśli jednak chcesz uzyskać tę informację, możesz użyć
operatora adresu ( & ), który zwraca adres obiektu znajdującego się w pamięci. Jego wykorzystanie
przedstawiono na listingu 8.1.
Listing 8.1. Przykład użycia operatora adresu
0: // Listing 8.1 Demonstruje operator adresu
1: // oraz adresy zmiennych lokalnych
2:
3: #include <iostream>
4:
5: int main()
6: {
7: using namespace std;
8: unsigned short shortVar=5;
9: unsigned long longVar=65535;
10: long sVar = -65535;
11:
12: cout << "shortVar:\t" << shortVar;
13: cout << "\tAdres zmiennej shortVar:\t";
14: cout << &shortVar << "\n";
15:
16: cout << "longVar:\t" << longVar;
17: cout << "\tAdres zmiennej longVar:\t" ;
18: cout << &longVar << "\n";
19:
20: cout << "sVar:\t\t" << sVar;
21: cout << "\tAdres zmiennej sVar:\t" ;
22: cout << &sVar << "\n";
23:
24: return 0;
6144286.002.png
25: }
Wynik
shortVar: 5 Adres zmiennej shortVar:
0012FF7C
longVar: 65535 Adres zmiennej longVar:
0012FF78
sVar: -65535 Adres zmiennej sVar:
0012FF74
Analiza
Tworzone i inicjalizowane są trzy zmienne: typu unsigned short w linii 8., typu unsigned
long w linii 9. oraz long w linii 10. Ich wartości i adresy są wypisywane w liniach od 12 do 16.
Adresy zmiennych uzyskiwane są za pomocą operatora adresu ( & ).
Wartością zmiennej shortVar (krótka zmienna) jest 5 (tak jak można było oczekiwać). W moim
komputerze Pentium (32-bitowym) ta zmienna ma adres 0012FF7C . Adres zależy od komputera i
może być nieco inny przy każdym uruchomieniu programu. W twoim komputerze adresy tych
zmiennych także mogą się różnić.
Deklarując typ zmiennej, informujesz kompilator, ile miejsca w pamięci powinien dla niej
zarezerwować, jednak adres jest przydzielany zmiennej automatycznie. Na przykład długie ( long )
zmienne całkowite zajmują zwykle cztery bajty, co oznacza, że zmienna posiada adres dla czterech
bajtów pamięci.
Zwróć uwagę, że twój kompilator, podobnie jak mój, może nalegać na to, by zmienne
otrzymywały adresy będące wielokrotnością 4 (tj. zmienna longVar otrzymuje adres położony
cztery bajty za zmienną shortVar , mimo iż zmienna shortVar potrzebuje tylko dwóch bajtów!)
Przechowywanie adresu we wskaźniku
Każda zmienna posiada adres. Możesz umieścić go we wskaźniku nawet bez znajomości adresu
danej zmiennej.
Przypuśćmy na przykład, że zmienna howOld jest całkowita. Aby zadeklarować wskaźnik o
nazwie pAge , mogący zawierać adres tej zmiennej, możesz napisać:
int *pAge = 0;
Spowoduje to zadeklarowanie zmiennej pAge jako wskaźnika do typu int . Innymi słowy,
zmienna pAge jest zadeklarowana jako przechowująca adresy wartości całkowitych.
Zwróć uwagę, że pAge jest zmienną. Gdy deklarujesz zmienną całkowitą (typu int ), kompilator
rezerwuje tyle pamięci, ile jest potrzebne do przechowania wartości całkowitej. Gdy deklarujesz
zmienną wskaźnikową taką jak pAge , kompilator rezerwuje ilość pamięci wystarczającą do
przechowania adresu (w większości komputerów zajmuje on cztery bajty). pAge jest po prostu
kolejnym typem zmiennej.
Puste i błędne wskaźniki
W tym przykładzie wskaźnik pAge jest inicjalizowany wartością zero. Wskaźnik, którego
wartością jest zero, jest nazywany wskaźnikiem pustym (ang. null pointer ). Podczas tworzenia
wskaźników, powinny być one zainicjalizowane jakąś wartością. Jeśli nie wiesz, jaką wartość
przypisać wskaźnikowi, przypisz mu wartość 0. Wskaźnik, który nie jest zainicjalizowany, jest
nazywany wskaźnikiem błędnym (ang. wild pointer ). Błędne wskaźniki są bardzo niebezpieczne.
UWAGA Pamiętaj o zasadzie bezpiecznego programowania: inicjalizuj swoje wskaźniki!
Musisz jawnie przypisać wskaźnikowi adres zmiennej howOld . Poniższy przykład pokazuje, jak to
zrobić:
unsigned short int howOld = 50; // tworzymy zmienną
unsigned short int * pAge = 0; // tworzymy wskaźnik
pAge = &howOld; // umieszczamy adres zmiennej hOld w zmiennej pAge
Pierwsza linia tworzy zmienną — howOld typu unsigned short int — oraz inicjalizuje ją
wartością 50 . Druga linia deklaruje zmienną pAge jako wskaźnik do typu unsigned short int
i ustawia ją na zero. To, że zmienna pAge jest wskaźnikiem, można poznać po gwiazdce ( * )
umieszczonej pomiędzy typem zmiennej a jej nazwą.
Trzecia, ostatnia linia, przypisuje wskaźnikowi pAge adres zmiennej howOld . Przypisywanie
adresu można poznać po użyciu operatora adresu ( & ). Gdyby operator adresu został pominięty,
wskaźnikowi pAge zostałaby przypisana wartość zmiennej howOld . Oczywiście, wartość ta
mogłaby być poprawnym adresem.
Teraz wskaźnik pAge zawiera adres zmiennej howOld . Ta zmienna ma wartość 50 . Można
uzyskać ten rezultat wykonując o jeden krok mniej, na przykład:
unsigned short int howOld = 50; // tworzymy zmienną
unsigned short int * pAge = &howOld; // tworzymy wskaźnik do howOld
pAge jest wskaźnikiem, zawierającym teraz adres zmiennej howOld . Używając wskaźnika pAge ,
możesz sprawdzić wartość zmiennej howOld , która w tym przypadku wynosi 50 . Dostęp do
zmiennej howOld poprzez wskaźnik pAge jest nazywany dostępem pośrednim (dostęp do niej
rzeczywiście odbywa się poprzez ten wskaźnik). Z dalszej części rozdziału dowiesz się, jak w ten
sposób odwoływać się do wartości zmiennej.
Dostęp pośredni oznacza dostęp do zmiennej o adresie przechowywanym we wskaźniku. Użycie
wskaźników stanowi pośredni sposób uzyskania wartości przechowywanej pod danym adresem.
UWAGA W przypadku zwykłej zmiennej, jej typ informuje kompilator, ile potrzebuje pamięci do
przechowania jej wartości. W przypadku wskaźników sytuacja wygląda inaczej: każdy wskaźnik
zajmuje cztery bajty. Typ wskaźnika informuje kompilator, ile potrzeba miejsca w pamięci do
przechowania obiektu, którego adres zawiera wskaźnik!
W deklaracji
unsigned shot int * pAge = 0; // tworzymy wskaźnik
zmienna pAge jest zadeklarowana jako wskaźnik do typu unsigned short int . Mówi ona
kompilatorowi, że wskaźnik ten (który do przechowania adresu wymaga czterech bajtów) będzie
przechowywał adres obiektu typu unsigned short int , który zajmuje dwa bajty.
Nazwy wskaźników
Podobnie jak inne zmienne, wskaźniki mogą mieć dowolne nazwy. Wielu programistów
przestrzega konwencji, w której nazwy wszystkich wskaźników poprzedza się literką p
( pointer ), np. pAge czy pNumber .
Operator wyłuskania
Operator wyłuskania ( * ) jest zwany także operatorem dostępu pośredniego albo dereferencją.
Podczas wyłuskiwania wskaźnika otrzymywana jest wartość wskazywana przez adres zawarty w
tym wskaźniku.
Zwykłe zmienne zapewniają bezpośredni dostęp do swoich wartości. Gdy tworzysz nową zmienną
typu unsigned short int o nazwie yourAge i chcesz jej przypisać wartość zmiennej howOld ,
możesz napisać:
unsigned short int yourAge;
yourAge = howOld;
Wskaźnik umożliwia pośredni dostęp do wartości zmiennej, której adres zawiera. Aby przypisać
wartość zmiennej howOld do zmiennej yourAge za pomocą wskaźnika pAge , powinieneś
napisać:
unsigned short int yourAge;
yourAge = *pAge;
Operator wyłuskania ( * ) znajdujący się przed zmienną pAge oznacza „wartość przechowywana
pod adresem.” To przypisanie można potraktować jako: „Weź wartość przechowywaną pod
adresem zawartym w pAge i przypisz ją do zmiennej yourAge ”.
UWAGAW przypadku wskaźników gwiazdka ( * ) może posiadać dwa znaczenia (może
symbolizować część deklaracji wskaźnika albo operator wyłuskania).
Zgłoś jeśli naruszono regulamin