INDEX

Programowanie w C

Wskaźniki


Co to jest ten tajemniczy wskaźnik (pointer)? Najkrócej mówiąc jest to obiekt identyfikujący jakiś adres w pamięci komputera. Ale po kolei, wyświetlmy na ekranie plik wsk01.c (wsk01.cpp) - posłuży on nam do dalszych rozważań. Pierwsza niespodzianka to dwie deklaracje rozpoczynające się od gwiazdki(star, asterisk), tuż za zmienną index. To właśnie są definicje wskaźników.

Jak odczytywać definicje wskaźników

Znak * w definicji obiektu informuje nas (i kompilator), że mamy do czynienia ze wskaźnikiem. Słowo kluczowe int określa pole zastosowania wskaźnika - ten wskaźnik może wskazywać zmienne typu int, czyli służy on do przechowywania adresu jakiegoś obiektu całkowitego (mieszczącego się w danym zakresie int). Zdefiniowaliśmy zatem zmienną typu całkowitego oraz dwa wskaźniki mogące pokazywać na obiekty typu całkowitego.

Treścią wskaźnika jest informacja o tym, gdzie wskazywany obiekt się znajduje, a nie co się w nim znajduje.

Warto nadmienić, iż nazwa wskaźnika nie zawiera gwiazdki; w naszym przypadku zdefiniowaliśmy dwa wskaźniki wsk1 i wsk2, a nie: *wsk1 i *wsk2.

Pod zmienną index podstawiamy wybraną wartość 39. W następnej linii pojawia się przypisanie w nowej postaci:

    wsk1 = &index;
Po lewej jego stronie znajduje się nazwa wskaźnika wsk1, a po prawej nazwa zmiennej poprzedzona znakiem & (ampersand) - jest to jednoargumentowy operator pobrania adresu. By mówić cokolwiek o wskaźnikach musimy w tym miejscu zapamiętać dwie bardzo ważne zasady:
  1. Nazwa zmiennej poprzedzona znakiem & określa jej adres. Pamiętajmy, że powyższa deklaracja zmiennej index jest jednocześnie definicją - tj. nie tylko określa typ zmiennej, ale rezerwuje na nią miejsce w pamięci. To miejsce ma własny adres, którego wartość zależy od typu komputera. Wyższość języka C (i C++) pozwala nam osiągnąć ten adres właśnie poprzez umieszczenie znaku & przed nazwą zmiennej.
  2. Nazwa wskaźnika poprzedzona gwiazdką odnosi się do wartości zmiennej, na którą pokazuje ten wskaźnik.

Wskaźnik to obiekt, który też ma adres. To w nim gromadzona jest informacja o adresie wskazywanej zmiennej. Aby uzyskać ten adres wystarczy wpisać taką linijkę kodu:

    printf("%d", &nazwa_wskaznika);

Kontynuując analizę programu rozumiemy już, że w linii 10 przypisaliśmy wskaźnikowi wsk1 adres zmiennej index. Od tej pory ten wskaźnik identyfikuje tę zmienną. Możemy manipulować wartością zmiennej używając albo nazwy zmiennej, albo wskaźnika.

W linii 12 modyfikujemy wartość zmiennej index używając do tego celu wskaźnika. Umieszczając gwiazdkę przed nazwą wskaźnika informujemy kompilator, że chcemy dobrać się do wartości ukrytej pod adresem, na który on pokazuje. Innymi słowy odsyłamy kompilator do jakiegoś miejsca w pamięci, które przechowuje informacje jakiegoś typu (w naszym przypadku liczbę całkowitą typu int). Wszędzie tam, gdzie poprawne jest użycie nazwy zmiennej index, poprawne jest również użycie wskaźnikowego zapisu *wsk1. Jest to prawdziwe póki wsk1 odwołuje się do zmiennej index.

Wymienność obu wspomnianych wyżej zapisów bierze się stąd, że oba wyrażenia, tj. index i *wsk1 są l-wartościami (l-value), bowiem mogą stać po lewej stronie operatora przypisania (znaku =). Każda l-wartość jest też r-wartością (r-value), może stać po prawej stronie operatora przypisania, ale nie na odwrót.

Jednak wskaźnik to nie drogowskaz, który zawsze wskazuje na to samo miejsce. Wskaźniki możemy przestawiać. Co to dokładnie znaczy? Wskaźnik określonego typu może wskazywać na różne obiekty (oczywiście nie w tym samym czasie), pod warunkiem że są to obiekty zgodne z zadeklarowanym typem wskaźnika. Wskaźnikiem do int nie można pokazywać na float czy char. Taka próba ustawienia wskaźnika wywoła błąd podczas kompilacji. Jeśli nie wiemy jakiego typu obiekt ma identyfikować wskaźnik, wyjściem z sytuacji jest deklaracja:

    void *p;
Czytamy ją: p jest wskaźnikiem do pokazywania na obiekt nieznanego typu.

Wskaźnik służący do pokazywania na obiekty jednego typu nie nadaje się do pokazywania na obiekty innego typu.

Program z jednym wskaźnikiem byłby nudny, więc mamy drugi wskaźnik o nazwie wsk2. Dopóki wsk2 nie został zainicjalizowany adresem konkretnego obiektu zawierał śmieci; taki nieustawiony wskaźnik nazywamy dzikim wskaźnikiem (wild pointer). Dzikie wskaźniki są jak nieoswojone zwierzęta - nie można ich kontrolować. A nieustawiony przez nas wskaźnik i tak zawsze na coś przypadkowego pokazuje. Próba użycia dzikiego wskaźnika, a zwłaszcza instrukcja podobna do tej:

    *dziki_wskaznik = 100;
może mieć opłakane skutki, bowiem próbujemy tutaj wpisać wartość 100 w przypadkową komórkę pamięci, w której mogą już być zapisane jakieś inne dane naszego programu.

W linii 13 wskaźnikowi wsk2 przypisany zostaje adres, na który pokazuje wsk1.

    wsk2 = wsk1;
Zauważ, że nie adres wskaźnika, lecz adres miejsca, które ten wskaźnik identyfikuje. A identyfikuje on zmienną index, więc od teraz wsk2 również pokazuje na tę zmienną. Możemy teraz odczytać wartość zmiennej index poprzez jej nazwę bezpośrednio, jak i przez *wsk1, czy *wsk2; wszystkie trzy zapisy są identyczne w znaczeniu. Ilustruje to funkcja printf w linii 15.

Zaraz, zaraz, to właściwie ile jest zmiennych? Tak naprawdę jest tylko jedna "zwykła" zmienna (taka, która przechowuje wartości, a nie adresy). Dwa wskaźniki pokazują na jedną zmienną. Zauważmy jak w linii 16 zmiennej index zostaje przypisana wartość 13. Następnie funkcja printf powoduje wyświetlenie tej 13 trzy razy. Została zmieniona tylko jedna zmienna, nie trzy.

Obszary zastosowania wskaźników

Istnieją cztery główne obszary zastosowania wskaźników:

  1. Praca z tablicami .
  2. Zastosowanie wskaźników w argumentach funkcji (C++).
  3. Dostęp do specjalnych komórek pamięci.
  4. Rezerwacja obszarów pamięci (C++).

Więcej wskaźników...

INDEX