JezykC6, IIS PWSZ, Podstawy programowania
[ Pobierz całość w formacie PDF ]
5. ZMIENNE WSKAŹNIKOWE
Każda dana (typu prostego lub złożonego) zapisana w pamięci operacyjnej komputera jest
scharakteryzowana przez dwa parametry:
1)
wartość
(zapisaną w pamięci w postaci ciągu bajtów o określonej długości);
2)
adres
(wyznaczający położenie pierwszego bajtu w pamięci).
adres
wartość
Wartość danej (zawartość pamięci) zajmuje w pamięci liczbę bajtów, która zależy od typu danej
(np. wartości typu
char
zajmują pojedyncze bajty, liczba typu
int
zajmuje (w systemach linuksowych)
cztery bajty itd.). Adresy danych mają ustaloną wielkość, która zależy od typu procesora i przyjętego
trybu adresowania - w systemach linuksowych na procesorach zgodnych z procesorem Intel są to
cztery bajty.
Nazwy zmiennych używanych przez nas do tej pory były związane z wartościami zapisywanymi
w pamięci. Deklaracja:
int x
;
oznaczała, że
x
w treści programu będzie utożsamiane z zawartością pewnych czterech kolejnych
bajtów. Operacja:
x
+ + ;
w treści programu oznacza zatem zwiększenie liczby zapisanej w tych bajtach o 1.
Natomiast deklaracja:
int * a
;
oznacza, że
a
w treści programu będzie utożsamiane z adresem początkowego spośród czterech
kolejnych bajtów zawierających liczbę typu
int
.
Operator & służy do obliczania
adresu
przyporządkowanego pewnej zmiennej przez kompilator.
Jeśli zostały podane deklaracje:
int x
;
int * a
;
to w wyniku przypisania:
a
= &
x
;
wartością zmiennej
a
stanie się adres zmiennej
x
.
Operator * służy do obliczania
wartości
aktualnie zapisanej pod pewnym adresem. Przy powyższych
deklaracjach, w wyniku przypisania:
x
= *
a
;
wartością zmiennej
x
stanie się liczba typu
int
zapisana pod adresem przechowywanym w zmiennej
a
.
Uwaga
Operator & nie może być stosowany do wyrażeń. Może być stosowany tylko do zmiennych.
Operator * może być stosowany do wyrażeń (których wartość jest traktowana jako adres).
Ponieważ wszystkie wskaźniki (adresy) w danym systemie mają tę samą wielkość (niezależną od
wielkości wskazywanej danej), teoretycznie ta sama zmienna wskaźnikowa mogłaby wskazywać
dane różnych typów w pamięci, np.
int * a
;
float r
;
..............
a =
&
r
;
ŹLE !
Należy jednak przypuszczać, że takie podstawienie jest wynikiem błędu programisty. Kompilator
wykrywa tego rodzaju sytuacje i wyświetla
ostrzeżenie
(warning)
, niemniej dokonuje kompilacji
i tworzy wynikowy plik wykonywalny.
W niektórych przypadkach programista może świadomie chcieć używać zmiennych wskaźnikowych
do wskazywania w pamięci danych różnych typów i wielkości. W takim przypadku powinien
zaznaczyć to w programie wykorzystując odpowiednie
operatory rzutowania
(konwersji typu
danej). W powyższym przykładzie miałoby to postać
a =
(
int *
) &
r
;
W tym przypadku kompilator nie wyświetli ostrzeżenia (choć instrukcja wygląda na nielogiczną).
Głównym zastosowaniem zmiennych wskaźnikowych jest tworzenie
dynamicznych struktur
danych
, czyli takich struktur danych, dla których miejsce w pamięci nie jest przydzielane
statycznie
przez kompilator, ale może być przydzielane i zwalniane w trakcie wykonywania programu.
Do przydzielania porcji pamięci wykonywanemu programowi służy funkcja
malloc
, zaś do zwalniania
przydzielonych wcześniej porcji pamięci - funkcja
free
(aby móc ich używać, należy na początku
programu włączyć plik nagłówkowy <
stdlib.h >
).
Z użyciem powyższych funkcji związane jest istnienie w języku C typu wskaźnikowego „ogólnego
użytku” (
void *
), wskazującego na nieokreślony („pusty”) typ danej
void
. Typ
void
został wprowa-
dzony w języku ANSI C dla zwiększenia „składniowej elegancji” języka, głównie aby zwiększyć
czytelność deklaracji funkcji. Typ wskaźnikowy
void *
powinien zawsze być zrzutowany na typ
konkretnej zmiennej wskaźnikowej występującej w programie.
Jedną z możliwych wartości zmiennej dowolnego typu wskaźnikowego jest wskaźnik pusty NULL
(stała standardowa o wartości 0). W programach wskaźnik NULL jest używany do oznaczania
końców struktur dynamicznych („adres nieistniejący”).
[ Pobierz całość w formacie PDF ]