5 WSKAŹNIKI I TABLICE
Między nazwą tablicy a wskaźnikiem jest jednak istotna różnica, o której należy pamiętać. Wskaźnik jest zmienną, więc operacje pa=a czy pa-H- są dozwolone. Natomiast nazwa tablicy nie jest zmienną, zatem konstrukcje w rodzaju a=pa i a++ są niedozwolone.
Gdy argumentem funkcji jest nazwa tablicy, wówczas funkcja otrzymuje położenie początkowego elementu tej tablicy. Wewnątrz wywołanej funkcji temu argumentowi odpowiada zwykła zmienna lokalna. Zatem parametr będący nazwą tablicy jest w istocie wskaźnikiem, czyli zmienną zawierającą adres. Korzystając z tego, możemy napisać inną wersję funkcji strlen obliczającej długość tekstu.
/* strlen: podaj długość tekstu s */ int strlen(char *s)
{
int n;
tor (n = 0; *s != ’\0’; s++) •
n-H-; return n;
1 jM jh
Zwiększanie s jest całkowicie poprawne, s jest bowiem zmienną wskaźnikową. Operacja s++ nie ma żadnego wpływu na ciąg znaków w miejscu wywołania funkcji strlen, zwiększa jedynie jej prywatną kopię wskaźnika. Z tego wynika, że wywołania mające postać
strlen(”ahoj, przygodo”); /* stała napisowa */ strlen(array); /* char array[100]; */
strlen(ptr); /* char *ptr; */
działają poprawnie.
Następujące definicje parametrów formalnych funkcji: chars[];
i
są równoważne; my dajemy pierwszeństwo tej ostatniej, gdyż wyraźniej mówi o tytf-że parametr jest wskaźnikiem. Jeśli do funkcji jest przekazywana nazwa tablicy,10 - w zależności od tego, co jest wygodniejsze - w funkcji tej można przyjąć, że ja*0 argument otrzymuje ona albo nazwę tablicy, albo wskaźnik i odpowiednio tym argu
5.4 ARYTMETYKA NA ADRESACH I |
| powered by 1 |
mentem manipulować. Można nawet jednocześnie stosować oba rodzaje e |
I Misio! |
0 ile wydaje się to stosowne i przejrzyste.
Funkcji wolno przekazać część tablicy, podając wskaźnik do początku podtablicy. Na przykład, jeżeli a jest tablicą, to w obu przypadkach:
f(&a[2])
1
f (a+2)
funkcji f zostanie przekazany adres podtablicy, która zaczyna się od elementu a[2|. Wewnątrz funkcji f deklaracja parametru formalnego może mieć postać
f (int arr[ ]) (...)
lub
f(int *arr) (...)
Z punktu widzenia funkcji f to, że jej argument wskazuje w rzeczywistości na fragment większej tablicy, nie ma żadnego znaczenia.
Można także odwoływać się do elementów tablicy wstecz, tj. p[-1], p[—2] itd., jeśli jest rzeczą pewną, że te elementy istnieją. Taki zapis jest składniowo poprawny i odnosi się do obiektów, które bezpośrednio poprzedzają obiekt p[0]. Oczywiście odwoływanie się do obiektów, które leżą poza granicami tablicy, jest nielegalne.
Jeżeli p jest wskaźnikiem do pewnego elementu tablicy, to po zwiększeniu p++ wskazuje on na następny element, a po zwiększeniu p+=i - na element oddalony o i pozycji od aktualnie wskazywanego. Te i podobne konstrukcje są najprostszymi oraz najczęściej stosowanymi formami arytmetyki na wskaźnikach lub adresach.
Stosowana w języku C arytmetyka na adresach jest spójna i regularna. Związek między wskaźnikami, tablicami i arytmetyką na adresach stanowi jedną z najważniejszych cech świadczących o mocy języka. Zilustrujemy to na przykładzie prymitywnego programu dystrybutora pamięci. Tworzą go dwa podprogramy. Pierwszy, o nazwie alloc(n), udostępnia wskaźnik p do obszaru pamięci zajmującego n kolejnych pozycji znakowych; obszar ten można w funkcji wywołującej alloc wykorzystać do przechowywania znaków. Drugi podprogram, o nazwie afree(p), zwalnia uprzednio przydzieloną pamięć tak, aby mogła być ponownie wykorzystana. Ten mechanizm przydziału pamięci jest „prymitywny”, ponieważ wywołania afree muszą następować w odwrotnej kolejności do wywołań alloc. Oznacza to, że pamięć obsługiwana przez
139