Rozdział 8. Wskaźniki, Informatyka, Technik informatyki, Programowanie, C++

[ Pobierz całość w formacie PDF ]
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
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;
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).
[ Pobierz całość w formacie PDF ]

  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • frania1320.xlx.pl
  • Tematy