Wskaźnik jest zmienną, która zawiera adres innej zmiennej. W języku C chętnie ko rzysta się ze wskaźników częściowo dlatego, że czasami jest to jedyny sposób przedstawienia algorytmu obliczenia, częściowo zaś dlatego, że ich użycie zwykle prowadzi do bardziej zwartego i efektywnego kodu niż otrzymywany innymi sposobami Wskaźniki i tablice są blisko spokrewnione; w tym rozdziale badamy to pokrewieństwo i pokazujemy, jak można z niego korzystać.
Wskaźniki wraz z instrukcją goto „cudownie” nadają się do tworzenia zupełnie niezrozumiałych programów. Dzieje się tak bez wątpienia wówczas, gdy wskaźniki są stosowane nieostrożnie. Łatwo jest bowiem utworzyć wskaźnik, który wskazuje nie wiadomo na co. Jednak przy przestrzeganiu odpowiedniej dyscypliny dzięki wskaźnikom można osiągnąć znaczną przejrzystość i prostotę algorytmów. Właśnie ten aspekt spróbujemy zilustrować.
Główna zmiana w ANSI C polega na jawnym określeniu reguł, według których można manipulować wskaźnikami, a więc w efekcie jest uprawomocnieniem tego, co dobrzy programiści od dawna stosują, a czego dobre kompilatory od dawna przestrzegają. Dodatkowo zamiast typu char * (wskaźnik do typu char) wprowadzono typ void* (wskaźnik do typu void) jako jedyny poprawny typ dla wskaźnika ogólnego.
Rozpoczniemy od pokazania prościutkiego obrazka, na którym widać, jak jest zorgfr nizowana pamięć. W typowej maszynie jest to tablica kolejno numerowanych lub au resowanych komórek pamięci; można nimi manipulować indywidualnie lub posługiwać się całymi grupami sąsiednich komórek. Ogólny stan rzeczy jest taki, że dowoln; bajt można traktować jako obiekt typu char, parę jednobajtowych komórek - jak obiekt całkowity typu short, a cztery przylegające do siebie bajty - jako obiekt ca kowity typu long. Wskaźnik jest grupą komórek (często dwie lub cztery), które mo?' pomieścić adres. A więc jeśli c jest obiektem typu char, a p jest wskaźnikiem, któr wskazuje na c, to taką sytuację możemy zilustrować następująco: r
Jednoargumentowy operator & podaje adres obiektu, zatem instrukcja
przypisuje zmiennej p adres zmiennej c; teraz zmienna p „wskazuje na” zmienną c. Operator adresu & może być stosowany tylko do obiektów zajmujących pamięć: zmiennych i elementów tablic. Nie można go stosować do wyrażeń, stałych i zmiennych register.
Jednoargumentowy operator * oznacza adresowanie pośrednie lub odwołanie pośrednie; zastosowany do wskaźnika daje zawartość obiektu wskazywanego przez ten wskaźnik. Przypuśćmy, że x i y są obiektami całkowitymi, a ip jest wskaźnikiem do obiektu typu int. Następujący sztuczny ciąg instrukcji pokazuje, jak zadeklarować wskaźnik oraz jak stosować operatory & i *:
int x = 1, y = 2, z[10];
int *ip; /* ip jest wskaźnikiem do obiektów typu int */
ip = &x;
y = *ip;
*ip = 0; ip = &z[0];
/* teraz ip wskazuje na x */
/* y ma teraz wartość 1 */
/* x ma teraz wartość 0 */
/* teraz ip wskazuje na element z[0] */
Deklaracje obiektów X, y i z znamy już od dawna. Deklaracja wskaźnika ip: int *ip;
z założenia jest mnemotechniczna; mówi ona, że wyrażenie *ip ma wartość typu int. Składnia deklaracji tej zmiennej naśladuje składnię wyrażenia, w którym zmienna może wystąpić. Takie rozumowanie stosuje się również do deklaracji funkcji. Na przykład deklaracja
mówi, że w wyrażeniu obie konstrukcje *dp i atof(s) mają wartości typu double oraz że argument funkcji atof jest wskaźnikiem do typu char.
Zapamiętaj także płynący stąd wniosek: od wskaźnika wymaga się wskazywania obiektów określonego rodzaju. (Z jednym wyjątkiem - wskaźnik do void, czyli „wskaźnik do niczego”, może przechowywać wskaźnik do obiektów dowolnego typu, ale nie można go stosować do adresowania pośredniego. Wrócimy do tego w p. 5.11.)
131