Prawo Harpera - poszukiwaną rzecz znajdziesz dopiero wtedy, gdy już zastąpisz ją inną.
Kurs C++ - Część VI. Tablice i wskaźniki

Tablica to ułożone kolejno w pamięci zmienne jednego typu. Stosowałeś już tablice nic o tym nie wiedząc. Chodzi o teksty, które przesyłałeś do strumienia. Każdy tekst to nic innego jak tablica - zmienne typu char ustawione kolejno w pamięci. Jeżeli zamiast liczb chciałbyś wczytać z klawiatury jakiś tekst, to powstaje problem: w jakiej zmiennej go przechować? Do tego służy właśnie tablica. Można w niej przechować np. ciąg znaków. Ttworzymy ją tak:

   char napis[30];

Taka deklaracja przydziela pamięć na 30 kolejnych bajtów (zmiennych typu char). Nie wiadomo, co znajdowało się wcześniej w tym miejscu pamięci. Dlatego można od razu zainicjować taką tablicę jakimiś wartościami, na przykład kolejnymi znakami napisu. Robi się to tak:

   char napis[30] = "jakiś tekst";

Takie przypisanie jest możliwe tylko raz, w deklaracji tablicy. Później już nie będzie to możliwe, o czym nieco dalej. Jak widać, nie trzeba w tablicy zapełnić wszystkich 30 znaków. Można zarezerwować pamięć dla 30 znaków, ale używać raz 10, raz 20, innym razem tylko 1.

Ile znaków może zapisać w tablicy 30-elementowej: 29. Komputer podczas wypisywania łańcucha znaków musi wiedzieć, gdzie łańcuch się kończy. Do oznaczenia końca używa bajtu o wartości 0 (\0). W C++ każdy łańcuch jest zakończony takim znakiem. Nie musisz go umieszczać w tekście, kompilator sam o to zadba. Ze względu na ten znak tablica 30-elementowa może pomieścić tylko 29 znaków - bo na 30 miejscu w tablicy będzie znak kończący.

Mamy już utworzoną i zainicjowaną tablicę. Jak się do tych wartości odwoływać? Ponieważ są ułożone w pamięci sekwencyjnie, możemy się do nich odwoływać poprzez indeksy, czyli ich numery porządkowe. Elementy tablicy są numerowane od 0. Pierwszy element ma numer 0, drugi element ma numer 1, itd.. Np. żeby wypisać na ekranie 5 element tablicy, używamy takiej instrukcji:

   cout << napis[4];

co jest równoznaczne z instrukcją:

   cout << 'ś';

bo 5 element tej tablicy to właśnie znak ś. Podobnej składni używamy, gdy chcemy zmienić zawartość wybranego elementu tablicy. Na przykład zmiana 1 znaku wygląda tak:

   napis[0] = 'J';

Wyświetl cały tekst zawarty w tablicy, a zobaczysz, że odpowiednia litera rzeczywiście się zmieniła. Instrukcja:

   cout << napis;

spowoduje teraz wyświetlenie na ekran napisu Jakiś tekst. Spróbuj teraz do 5 elementu tablicy wstawić znak \0 i zobacz, że tym razem wyświetli się tylko część napisu, bo komputer kończy wyświetlanie tekstu, gdy natrafi na znak \0.

Jednak reszta napisu dalej jest w tablicy, nigdzie nie ginie. Wystarczy, że zmienisz 5 element tablicy ponownie na znak spacji.

Podczas odwoływania się do elementów tablicy należy uważać, żeby nie odwoływać się do elementów nieistniejących. Jeśli zarezerwowałeś w pamięci miejsce dla tablicy 30-elementowej, nie możesz odwoływać się do jej 50 elementu, bo spowoduje to błąd dostępu do pamięci.

Przejdźmy teraz do wskaźników. Wskaźnik (ang. pointer) to taka zmienna, która nie przechowuje wartości, lecz jej adres w pamięci lub inaczej: wskazuje miejsce w pamięci, w którym znajduje się zmienna określonego typu. Przypuśćmy, że mamy zmienną liczba typu int. Deklaracja wskaźnika, który będzie wskazywał na ten typ zmiennej, wygląda tak:

   int *wskaz;

Gwiazdka oznacza, że wskaz nie jest zmienną typu int, lecz wskaźnikiem do takiej zmiennej. Przy czym nie jest ważne, gdzie gwiazdkę umieścisz: bliżej typu, bliżej nazwy, czy nawet pośrodku między nimi, byle tylko tam była. Nowoutworzony wskaźnik wskazuje w jakieś nieznane miejsce w pamięci. Zanim go użyjemy, należy go ustawić tak, żeby wskazywał tam gdzie chcemy, czyli na naszą zmienną liczba.

   wskaz = &liczba;

Operator & to operator adresu (zwany też operatorem referencji): zwraca adres obiektu, który stoi po jego prawej stronie. Powyższa instrukcja oznacza: do wskaźnika wskaz przypisz adres zmiennej liczba. Od tej chwili wskaz wskazuje na miejsce zmiennej liczba w pamięci.

Po takim przypisaniu obie poniższe instrukcje są równoważne (obie przypiszą do numer wartość zmiennej liczba):

   int numer = liczba;
   int numer = *wskaz;

Operator * nazywa się operatorem wyłuskania. Zwraca to, na co wskazuje wskaźnik stojący po jego prawej stronie czyli wyłuskuje z pamięci to, na co wskazuje wskaźnik. Jeżeli nie byłoby operatora wyłuskania, to do zmiennej numer trafiłby adres, na który wskazuje wskaźnik, a nie to co się pod tym adresem znajduje.

Wskaźnik możemy w każdej chwili przestawić na inne miejsce w pamięci. Przestawimy go teraz tak, by wskazywał na zmienną numer:

   wskaz = &numer;

W C++ tablica i wskaźnik to to samo: nazwa tablicy to nic innego jak wskaźnik na miejsce w pamięci, gdzie ta tablica się zaczyna. Jedyną różnicą jest to, że nazwa tablicy jest wskaźnikiem stałym i dlatego nie można takiego "wskaźnika" przestawić na inne miejsce w pamięci. Z tego powodu niepoprawny jest taki kod:

   char napis[50] = "Przykładowy tekst";
   napis = "Inny tekst";

bo oznaczałoby to przestawienie "wskaźnika" na inne miejsce w pamięci. Jeżeli zamiast tablicy posłużymy się wskaźnikiem, to taka instrukcja jest poprawna, bo zwykły wskaźnik można przestawiać. Wskaźnik również możemy zadeklarować jako stały. Robi się to tak:

   char * const napis = "To jest stały wskaźnik do tekstu";

napis działa identycznie jak nazwa tablicy - nie da się go przestawiać. Sprawiła to klauzula const. Nieraz istnieje potrzeba przestawienia wskaźnika z jednoczesnym zakazem modyfikacji obszaru pamięci, na który on wskazuje. Do tego służy wskaźnik do stałej. Różni się on od wskaźnika stałego tym, że można go przestawiać na inne miejsce pamięci. Deklarujemy go tak:

   char const * napis;

Tym razem słówko const (oznaczające stałą) znajduje się za znakiem gwiazdki. Tym razem nie zmienna, ale jej typ jest wartością stałą.

Tablica i wskaźnik mają dużo wspólnego. W C++ to co można zrobić z użyciem tablicy, można też zrobić z użyciem wskaźników i na ogół będzie to działać szybciej. Przykłady odwoływania się do poszczególnych znaków zmiennej wskaźnikowej:

   char *wskaz = "ABCDEFGHIJK";
   char znak;
   znak = *Wskaz;     //znak='A'
   znak = *(Wskaz+2); //znak='C'
   znak = Wskaz[4];   //znak='E'

Wyjaśnienia wymaga użycie nawiasów w drugim odwołaniu do zmiennej wskaz: brak nawiasów kompilator zrozumiałby jako przypisanie do zmiennej znak tego, na co wskazuje wskaz powiększonego o liczbę 2. Operator * ma wyższy priorytet niż operator + i najpierw wartość jest wyciągana z pamięci, a dopiero potem zwiększana o 2.

Można to zapisać prościej, wiedząc że wskaźnik i tablica to to samo. Przykład takiego zapisu jest w trzecim odwołaniu. Poniżej masz zestawienie równoważnych odwołań:

     *wskaz;         <=>     wskaz[0];
     *wskaz + 3;     <=>     wskaz[0] + 3;
     *(wskaz+4);     <=>     wskaz[4];

Spróbuj teraz sam napisać program, który będzie pytał użytkownika o nazwisko, a następnie wyświetli mu, na jaką literę się to nazwisko zaczyna, a na jaką kończy.

« wstecz   dalej »