Zapraszam na kolejną część tłumaczenia notacji wskaźnikowej. Wczoraj wieczorem była tablica jednowymiarowa, a teraz pokażę jak wygląda notacja wskaźnikowa do tablicy dwuwymiarowej. Wszyscy Ci, co pominęli pierwszą część, zapraszam tutaj. Jeśli myśleliście, że ostatni artykuł przyprawia o zawrót głowy, tutaj dopiero zacznie się wyrywanie włosów! Nie żartuję!

NOTACJA WSKAŹNIKOWA TABLICY DWUWYMIAROWEJ TO JESZCZE TRUDNIEJSZE ZAGADNIENIE

Wskaźniki są uważane przez wielu programistów za jeden z najtrudniejszych tematów dotyczących języków C i C++. Nie jestem innego zdania. Nawet po gruntownym przerobieniu całego rozdziału z książki na temat samych wskaźników, wciąż się gubię przy drobnych szczegółach. A one są istotne! W tym temacie są wyjątkowo istotne! Przechodząc do tematu przypomnijmy jaka jest spełniona zależność dla tablicy 1D:

numbers = &numbers[0]

Teraz wyobrażamy sobie taką oto tablicę o tej samej nazwie:

int numbers[2][3];

Pierwsze pytanie na początek. Jak myślicie, co będzie po drugiej stronie równania przy podaniu samej nazwy, co? Pomyślcie chwilę zanim zerkniecie poniżej:

numbers = &numbers[0]

Tablica 2D to inaczej mówiąc, tablica tablic. Przechodząc na taki kąt widzenia powinno być lepiej w kwestii zrozumienia. Notacja wskaźnikowa dla tablicy dwuwymiarowej przy samej nazwie tablicy jest adresem do pierwszej tablicy! A ta tablica też posiada kilka argumentów. Idziemy dalej. Załóżmy, że chcemy "wyłuskać" wartość pierwszego argumentu z pierwszej tablicy. Czy za pomocą samego operatora dereferencji uda się nam to zrobić? Nie sądzę:

*numbers = &numbers[0][0]

Dereferencja sama w sobie spowoduje jedynie dostanie się do adresu "podtablicy", czyli drugiego wymiaru tablicy 2D. Gdybyśmy chcieli dostać się do rzeczywistej wartości, musimy wykonać "podwójną dereferencję". Tak, można coś takiego zrobić. Przyjmując, że pierwszy argument pierwszej tablicy wynosi 18:

**numbers = numbers[0][0] = 18

Dopiero w ten sposób uzyskujemy dostęp do danych. Przejdźmy teraz do przesunięć. Uprzedzam, że tutaj naprawdę mogą być duże kłopoty w rozumieniu. Załóżmy, że chcemy "przesunąć się" o jeden argument (nie podtablicę!). Jak będzie wyglądać notacja wskaźnikowa? Ano tak:

*numbers + 1 = &numbers[0][1]

Dla porównania, zerknijmy jeszcze jak wygląda wyrażenie "objęte" w nawias oraz z dodatkowym składnikiem na końcu:

*(numbers + 1) = &numbers[1][0]

*(numbers + 1) + 1 = &numbers[1][1]

Wynika stąd, że przesuwanie adresu po drugim wymiarze jest realizowane bez użycia nawiasów z powodu pierwszeństwa dereferencji (czyli występuje najpierw konwersja na adres pierwszego argumentu (&numbers[0][0])), a dopiero potem występuje przesunięcie o jeden argument. Przy wprowadzeniu nawiasów jest DOKŁADNIE ODWROTNIE. Najpierw następuje przesunięcie adresu o jeden cały rząd, a dopiero potem jest wyłuskiwany adres. Teraz rozumiecie, czemu notacja wskaźnikowa dla tablicy dwuwymiarowej potrafi namieszać w głowie...

A jak wygląda przedostawanie się do wartości powiedzmy, pierwszy rząd, argument pierwszy? Trzymajcie się mocno swoich krzeseł:

*(*(numbers + 1) + 1) = numbers[1][1]

Zgadnijcie która z liczb odpowiada za jaki wymiar, a dopiero potem spójrzcie na kolejny zapis:

*(*(numbers + 2) + 1) = numbers[2][1]

Z moich notatek i obserwacji wynika, że wzór na to jest następujący:

*(*(numbers + i) + j) = numbers[i][j]

Tak więc jeżeli macie problem z powyższymi przykładami, może warto zacząć od kilkukrotnych podstawień pod ten wzór? Decyzję zostawiam już Wam.


Kończę już ten temat i mam nadzieję, że uda się dzięki temu szybciej to zrozumieć i opanować oraz że nigdzie się nie pomyliłem. Pamiętajcie, C i C++ nie sprawdzą Wam czy przesuwacie się jeszcze po tablicy, czy już poza nią!