Programowanie w C
Tablice i wskaźniki
Grupa L-16
1. Wskaźniki
1.1. Czym są wskaźniki
Wskaźniki (ang. pointers) są jednym z najważniejszych zagadnień do poznania podczas nauki języków C, ale są również głównym źródłem zagrożeń. Ponieważ mogą zwykle wskazywać na dowolny obszar pamięci, prowadzić to może do niepożądanych efektów. Nawet odpowiednio używane wskaźniki wskazujące na bezpieczne miejsca, mogą zostać przypadkiem przeniesione na miejsca niebezpieczne przez użycie nieodpowiedniej arytmetyki wskaźników; pamięć na którą wskazują może być zwolniona i użyta już na coś innego (zwisający wskaźnik); mogą być niezainicjalizowane (dziki wskaźnik), lub mogą mieć bezpośrednio przypisaną wartość poprzez rzutowanie, unię, lub inny uszkodzony wskaźnik. Ogólnie C pozwala na swobodną manipulację i konwersję typów wskaźników, chociaż kompilatory zwykle dostarczają opcje różnego poziomu ich kontroli. Inne języki niwelują problemy ze wskaźnikami poprzez użycie bardziej ograniczonych typów referencji.
Wskaźnik to zmienna (zwana zmienną wskaźnikową), która zawiera adres pierwszej komórki pamięci, w której przechowywana jest inna zmienna. Jeśli zmienna zajmuje więcej niż jedną komórkę pamięci to wskaźnik wskazuje na pierwszą z tych komórek. Dla każdego wskaźnika określany jest jego typ. Wskaźnik typu int wskazuje na zmienną typu int. Dzięki temu, kompilator wie ile komórek w pamięci zajmuje zmienna rozpoczynająca się w komórce, na którą wskazuje wskaźnik.
1.2. Operatory związane ze wskaźnikami
Nierozłącznie ze wskaźnikami związane są dwa operatory. Są to operator wyłuskania, który jest zapisywany jako gwiazdka (*) oraz operator pobrania adresu - &. Oba te operatory są operatorami przedrostkowymi (zapisuje się je przed zmienną) oraz jednoargumentowymi.
1.2.1. Operator pobrania adresu - &
Operator pobrania adresu zwraca adres (numer komórki pamięci) zmiennej, która jest jego argumentem. Przykład:`
Do zmiennej p przypisana zostaje liczba będąca adresem zmiennej x.
1.2.2. Operator wyłuskania - *
Operator wyłuskania pozwala na odwołanie się do zawartości komórki pamięci lub komórek pamięci rozpoczynających się od podanego jako argument adresu (zapisanego na przykład w innej zmiennej). Przykład:
Do zmiennej umieszczonej pod adresem p dodawane jest siedem.
1.3. Tworzenie wskaźnika
Właściwie proces tworzenia wskaźnika już opisałem w przykładach użycia operatorów wyłuskania i pobrania adresu, ale zacznijmy od początku. Przypomnijmy sobie wcześniejszy schemat:
· Tworzymy jakąś zmienną np. typu integer (zakładamy, że zmienna została umieszczona w komórkach od 0x2 do 0x5. Na razie jej wartością jest poprzednia wartość tych komórek, czyli jakieś nic nieznaczące dane.)
· Następnie tworzymy wskaźnik na zmienną typu integeger (Typ wskaźnika jest równocześnie typem zmiennej, na którą wskazuje. W deklaracji wskaźnika należy użyć gwiazdki, po to by dać do zrozumienia kompilatorowi, że jest to zmienna wskaźnikowa. Załóżmy, że zmienna p została umieszczona w komórce pamięci 0x8 (rozmiar tej zmiennej nie jest ważny))
· Przypisujemy adres zmiennej abc do wskaźnika p. (Podczas tworzenia zmiennej wskaźnikowej można od razu przypisać jej jakiś adres)
· Gwiazdka w tym przypadku nie pełni funkcji operatora ale informatora. Informuje kompilator, że tworzona zmienna jest zmienną wskaźnikową, a nie wyłuskuje adresu.
2. Tablice
2.1. Czym są tablice
Tablica to zbiór określonej liczby obiektów powiązanych w pewien logiczny związek. Wszystkie takie obiekty muszą być tego samego rodzaju. Nie można w tablicy umieścić kilku różnych zmiennych. Tablice są również ściśle związane ze wskaźnikami. Nazwa tablicy (sama nazwa, bez indeksu) jest wskaźnikiem do jej pierwszego elementu.
2.2. Deklaracja tablicy
Tablice deklaruje się w taki sam sposób jak zmienne. Nadaje się im typ, następnie określa się ich nazwę, oraz rozmiar (w kwadratowych nawiasach). Jeśli nasza tablica ma być wielowymiarowa, deklarujemy kilka rozmiarów.
2.3. Użycie tablicy
Podczas używania tablicy musimy pamiętać że indeksowanie w języku C zaczyna się od 0, a ostatni element to n-1, gdzie n to zadeklarowana wielkość tablicy. Np.
Odwoływanie do tablic, oraz ich wypełnianie zazwyczaj wykonuje się w pętlach, aby ułatwić sobie indeksowanie.
Musimy także pamiętać o pewnym utrudnieniu w języku C, ponieważ nie jest sprawdzana on zgodność indeksu z wymiarami tablicy. np. odwołanie: macierz[ 1 ][ 2 ], przy wymiarach naszej tablicy 5x2 zwróci w rzeczywistości wartość pierwszego elementu z trzeciego wiersza tzn. macierz[ 2 ][ 0 ].
0 , 0
0 , 1
1 , 0
1 , 1
1 , 2
2 , 0
2 , 1
3 , 0
3 , 1
4 , 0
4 , 1
reprezentacja tej macierzy ¯ w pamięci komputera
Wartość dla każdego elementu tablicy możemy przypisać w chwili deklaracji tablicy tak jak ma to miejsce w przypadku zwykłych zmiennych.
Luki424