Tematem w tym artykule jest niejaka notacja wskaźnikowa w języku C - następna nietypowa część składni jakiej nie znajdziesz w językach Java, C#, JavaScript, Python itp. Zagadnienie stanowi kolejną część rozdziału dotyczącego tablic (stąd zalecane jest przeczytanie wprowadzenia 😉). Jak wstęp grzecznie przeczytany, to zapraszam 😄!
NOTACJA WSKAŹNIKOWA W JĘZYKU C JAKO DRUGI SPOSÓB ODWOŁYWANIA SIĘ DO WARTOŚCI ELEMENTÓW W TABLICY
W sumie, napisałem już w nagłówku o co chodzi 😁. Notację tablicową poznaliśmy wcześniej, a teraz przybliżę Ci zapis z użyciem wskaźnika - notację wskaźnikową 💥! Gdy masz tablicę, to jeżeli zapoznałeś(-aś) się z artykułem o jej drugim "wcieleniu", to wiesz, że możemy uzyskiwać dostęp do jej elementów na 2 sposoby 👇:
- poprzez indeksy (nawiasy kwadratowe),
- poprzez wskaźniki ("poruszanie się" według bajtów tzw. "arytmetyka wskaźników").
CZYM SIĘ RÓŻNI NOTACJA WSKAŹNIKOWA OD TABLICOWEJ W JĘZYKU C?
Zdecydowana większość obecnie wykorzystywanych języków programowania obsługuje wszystkim znany zapis "dostawania się" do konkretnego argumentu tablicy za pomocą nawiasów kwadratowych (sposób nr 1) 👇:
int number = numbers[i];Notacja wskaźnikowa w języku C pozwala na przemieszczanie się po elementach w drugi możliwy sposób, łącząc arytmetykę wskaźników z operatorem "wyłuskania" (dereferencji) 2️⃣:
int number = *(numbers + i);To daje ten sam efekt - pobranie aktualnej wartości i-tego elementu tablicy 🌟. Wyjaśniam poszczególne części wyrażenia.
Zacznijmy od porównania obu poznanych notacji odwołujących się do pierwszego elementu.
JAK UZYSKIWAĆ DOSTĘP DO ELEMENTÓW TABLICY PRZY UŻYCIU NOTACJI WSKAŹNIKOWEJ?
W notacji tablicowej, piszemy w następujący sposób 👇:
numbers[0]Chcąc dostać się do pierwszego elementu poprzez zapis wskaźnikowy, korzystamy z operatora "wyłuskania" (dereferencji), a ponieważ sama nazwa zmiennej tablicowej to zarazem adres pierwszego elementu, to można to sprowadzić do prostej postaci 😎:
*numbersTo jest odniesienie się do pierwszego elementu. Po podstawieniu np. do funkcji "printf":
printf("%d\n", *numbers);ujrzymy taką samą liczbę, jaka została przypisana w definicji dla pierwszego elementu.
Zawsze możesz przypisać "wyłuskaną" wartość do zmiennej:
int valueOfFirstElement = *numbers;i przypisać już bezpośrednio tam, gdzie chcesz:
printf("%d\n", valueOfFirstElement);Idziemy dalej - jak przechodzić do następnych argumentów 🤔?
JAK "PRZESUWAĆ SIĘ" PO KOLEJNYCH ELEMENTACH TABLICY Z UŻYCIEM NOTACJI WSKAŹNIKOWEJ?
Stosujemy arytmetykę wskaźników, czyli "przesunięcie się" o określoną liczbę elementów względem początku tablicy. Przypuśćmy, że chcemy uzyskać adres do drugiego elementu. Co to oznacza dla prawej strony równania?
numbers + 1 = &numbers[1]Czyli "przesunęliśmy" adres o jeden argument do przodu (konkretnie, to o 4 bajty typu "int") i w ten sposób uzyskaliśmy adres drugiego argumentu tablicy pod indeksem 1 (przypominam, że w tablicy, argumenty są liczone od zera!).
Musisz też mieć świadomość, że tak jak w przypadku notacji tablicowej, tutaj również jesteśmy narażeni na "wyjście" poza tablicę i jeżeli "przesuniemy" adres za daleko, to wywołasz niezdefiniowane zachowanie programu 💥!
JAK "WYŁUSKAĆ" WARTOŚĆ ELEMENTU TABLICY BEZPOŚREDNIO NA "PRZESUNIĘTYM" ADRESIE?
W sytuacji, gdy chcesz pobrać wartość i-tego elementu z tablicy bezpośrednio (bez przypisywania adresu do zmiennej), musisz skorzystać z tego samego wyrażenia w postaci dodania do adresu "przesunięcia" w postaci liczby całkowitej ⏩:
*(numbers + 1)To spowoduje przedostanie się do wartości drugiego elementu w kolejności 2️⃣.
Zwróć uwagę na jeden szczegół - nawiasy! Jeżeli chcesz "wyłuskać" wartość od razu "na miejscu", to musisz umieścić nawiasy 🛑! Operator dereferencji ma pierwszeństwo przed dodawaniem/odejmowaniem! Napisanie w taki sposób:
*numbers + 1spowoduje pobranie wartości elementu i zwiększenie jej o jeden ❌! Gdy chcemy pobrać wartość i-tego elementu, to koniecznie otocz dodawanie nawiasami! W ten sposób wymusisz oczekiwaną kolejność wykonywania operacji (najpierw "przesunięcie" adresu, potem pobranie wartości) 👍.
![]() |
Notacja wskaźnikowa w języku C to drugi sposób uzyskiwania dostępu do elementów danej tablicy (obok notacji tablicowej).
Ten fragment artykułu traktuj jako ciekawostkę, niż konieczną do opanowania naukę 😉. Tutaj pokazuję jak posługiwać się notacją wskaźnikową w przypadku tablic dwuwymiarowych 💣!
JAK PORUSZAĆ SIĘ PO TABLICY DWUWYMIAROWEJ Z UŻYCIEM NOTACJI WSKAŹNIKOWEJ?
Załóżmy, że od tej pory, nasza tablica przyjmuje dwa wymiary, zamiast jednego (jest prostokątna) 👇:
int numbers[2][3];Pierwsze pytanie: co będzie po drugiej stronie równania przy podaniu samej nazwy?
Pomyśl chwilę, zanim zerkniesz niżej 😏:
numbers = &numbers[0]Adres do pierwszej tablicy 🤯???
Tablica dwuwymiarowa, to inaczej, tablica tablic. Przechodząc na taki tok myślenia, powinno Ci być lepiej to zrozumieć 🙂. Dla tablicy dwuwymiarowej, początek tablicy wskazuje na pierwszą "podtablicę"! A ta "podtablica" też posiada własne elementy 😲! Przechodząc na jeszcze inne spojrzenie - najpierw przedostajesz się do wiersza tablicy, który sam w sobie składa się z pewnej liczby wartości ℹ️.
JAK "WYŁUSKAĆ" WARTOŚĆ ELEMENTU TABLICY DWUWYMIAROWEJ BEZPOŚREDNIO NA "PRZESUNIĘTYM" ADRESIE?
Kolejna rzecz, to jak "wyłuskać" wartość pierwszego argumentu z pierwszej tablicy 🤔. Czy za pomocą samego operatora dereferencji uda się nam to zrobić? Nie 😁:
*numbers = &numbers[0][0]Pojedynczy operator spowoduje tylko dostanie się do adresu elementu pierwszego wiersza w pierwszej kolumnie. Jeżeli chcemy dostać się do wartości, musimy wykonać "podwójną dereferencję" 😱:
**numbers = numbers[0][0]Tłumacząc po kolei 😉:
- najpierw "stawiamy" nazwę tablicy - otrzymujemy adres wskazujący na pierwszy wiersz,
- potem wykonujemy pierwszy raz "wyłuskanie" - otrzymujemy adres wskazujący na element w pierwszym wierszu, w pierwszej kolumnie,
- na końcu ponownie wykonujemy "wyłuskanie" - otrzymujemy wartość elementu w pierwszym wierszu, w pierwszej kolumnie.
Dopiero wtedy uzyskujemy dostęp do danych 😅. Z nadzieją, że udało mi się to w miarę prosto wyjaśnić, przejdźmy dalej.
Zostało nam jeszcze wyjaśnienie poruszania się po tablicy dwuwymiarowej. Uprzedzam, że tutaj naprawdę mogą być duże kłopoty w rozumieniu 💥!
JAK "PRZESUWAĆ SIĘ" PO ELEMENTACH TABLICY DWUWYMIAROWEJ Z UŻYCIEM NOTACJI WSKAŹNIKOWEJ?
Na początek dajmy na to, że chcemy "przesunąć się" o jedną kolumnę (nie element!). Jak wtedy będzie wyglądać notacja wskaźnikowa? Ano tak 👇:
*numbers + 1 = &numbers[0][1]Wykonujemy pojedynczą dereferencję na tablicy celem przedostania się do pierwszego wiersza, a następnie zwiększamy wartość adresu o 1 - w ten sposób otrzymujemy adres do pierwszego wiersza, drugiej kolumny.
Teraz zobacz jaki wpływ ma "objęcie" wyrażenia w nawias i dodatkowy składnik 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 najpierw występuje konwersja na adres pierwszego argumentu:
*(numbers + 1) = &numbers[1][0]a dopiero potem występuje przesunięcie o jeden argument:
&numbers[1][0] + 1 = &numbers[1][1];A jak wygląda przedostawanie się do wartości np. pierwszy rząd, pierwsza kolumna 🤔? Trzymaj się mocno:
*(*(numbers + 1) + 1) = numbers[1][1]Postawię obok siebie wyrażenia odnoszące się do tego samego elementu i przestawię odstępy, aby było Ci łatwiej dostrzec różnicę 👍:
*(numbers + 1) + 1 = &numbers[1][1]
*(*(numbers + 1) + 1) = numbers[1][1]Zgadnij która z liczb odpowiada za jaki wymiar 😄:
*(*(numbers + 2) + 1) = numbers[2][1]Z powyższych zapisów wynika, że występuje taki wzór dla i-tego wiersza i j-tej kolumny:
*(*(numbers + i) + j) = numbers[i][j]Teraz widzisz czemu notacja wskaźnikowa dla tablicy dwuwymiarowej potrafi namieszać w głowie 🤯! Zdaję sobie sprawę, że może to być "małą masakrą" 😅, zwłaszcza gdy widzisz to po raz pierwszy 😊!
Koniec 🏁. Rozumiem, że ten temat może powodować ból głowy, jako że wygląda na niepotrzebne "obchodzenie" wygodnego zapisu z nawiasami kwadratowymi 🙂.
