Podczas programowania, na 100% zajdzie potrzeba utworzenia sobie kontenera do przechowywania wielu zmiennych w jednym miejscu 📦. Przejdę teraz do ukazania czym jest tablica w języku C, po co się ją stosuje i w jakich okolicznościach. Kolejny ważny temat, który trzeba dobrze znać i nie ma żadnych odwołań ✋!

TABLICA W JĘZYKU C PRZYDATNA DO "SKŁADOWANIA" DANYCH W JEDNYM MIEJSCU!

Teorię na temat tablic możesz przeczytać w odrębnym artykule z ogólniejszej kategorii "Programowanie", jako że trzeba było uwzględnić inne języki programowania i jakoś wydzielić od siebie informacje ogólne od konkretnych zapisów w danym języku 🙂. Tutaj skupimy się wyłącznie na praktycznej części, czyli występującej jedynie w języku C ℹ️.

Muszę wziąć pod uwagę fakt, że trafi się ktoś, kto mnie nie posłucha i nie przejdzie do tamtego odnośnika 😄, więc ujmę jednym zdaniem o co chodzi.

DEFINICJA "NA SZYBKO"

Tablica to statyczna struktura danych do przechowywania wielu elementów jednego wybranego typu o uprzednio zdefiniowanym rozmiarze ℹ️. Dla osób, które potrzebują "obrazkowego" porównania, to jest taka "półka" o skończonej szerokości, na której można "postawić" wiele danych tego samego typu.

Tablica charakteryzuje się ekspresowym dostępem do każdego z elementów poprzez tzw. "indeks", czyli liczbę całkowitą od 0 do N - 1. Natomiast ma 2 istotne ograniczenia, których musisz być świadom(a) 👇:

  1. elementy mogą być tylko jednego typu,
  2. rozmiar musi być ustalony z góry i jest on potem niezmienny do momentu utworzenia całkowicie nowej tablicy.

Po resztę informacji, zapraszam do wyżej załączonego odnośnika 😁!

CO TRZEBA WIEDZIEĆ?

Na początek, przyjrzyjmy się prostemu zapisowi w kodzie źródłowym (na razie sama instrukcja inicjująca) 👇:

int numbers[5];

Jest podobnie jak przy deklaracji zmiennej z jednym drobnym "wystającym" wyjątkiem zaraz po nazwie: nawiasy kwadratowe 💥! W środku nich określamy, ile elementów ma posiadać w sobie dana tablica ℹ️. I jak pisałem, to jest definitywne - tego się nie da zmienić ⚠️! Jeżeli zachodzi potrzeba "zmiennych" rozmiarów podczas działania programu, wtedy musisz skorzystać z listy dynamicznej 💣.

Pamiętaj, że w przeciwieństwie do innych języków, w C i C++ nawiasy kwadratowe mają miejsce po nazwie tablicy, a nie po typie danych ⚠️!

Teraz przybliżę Ci różne cenne informacje związane z tablicą 🙂.

POSTAĆ DOMYŚLNA

Gdy zostawimy tablicę w takiej postaci:

int numbers[5];

to elementy będą przydzielone jako "śmieciowe" (byle jakie) ℹ️. Nie będą reprezentować zer, jak mogło Ci to przyjść do głowy, tylko kompletnie urwane z kontekstu liczby. To pierwsza rzecz jaką warto wiedzieć 1️⃣.

PRZYDZIELANIE ELEMENTÓW

Po utworzeniu tablicy, masz możliwość przyporządkowania odpowiednich elementów według własnych potrzeb. Możesz to zrobić na 2 sposoby 👇:

  1. podczas deklaracji tablicy,
  2. w dowolnej późniejszej instrukcji, używając operatora przypisania.

Sposób nr 1, to określenie elementów już "na dzień dobry", w tej samej instrukcji, w której tworzysz nową tablicę. Do postaci ukazanej powyżej, wystarczy dorzucić znak przypisania i klamerki:

int numbers[5] = {-89, 13, 3, 5, 67};

Co się stanie, gdy liczba podanych elementów będzie niezgodna z określonym rozmiarem 🤔? Zależy od scenariusza 📜.

Jeżeli podasz mniejszą liczbę argumentów (synonim elementów w tablicy), to reszta otrzyma wartość 0 (zero) ℹ️. Jeżeli podasz większą, to tu się robi bardziej niebezpiecznie: podczas kompilacji pojawi się ostrzeżenie, że przekroczyłeś(-aś) liczbę podanych elementów w miejscu definicji ⚠️! Nie będzie konsekwencji podczas działania programu, jednak kompilator zwróci na to uwagę - nie myśl sobie, że ucieknie mu to z pola widzenia 😉!

PRZYPISANIE SAMYCH ZER

Jeżeli zależy Ci na przypisaniu każdemu elementowi zera, to warto znać taką sztuczkę 👇:

int numbers[5] = {0};

Spowoduje to to, co napisałem u góry - każda liczba ściśle określonych elementów mniejsza od zakresu tablicy, spowoduje przypisanie reszcie elementów wartości 0 (zero).

PRZYDZIELANIE WEDŁUG NIESTANDARDOWEJ KOLEJNOŚCI

Od standardu C99, możesz sięgnąć po bardziej nietypową konstrukcję polegającą na ustawianiu elementów na konkretnych indeksach tablicy aniżeli od pierwszego, do ostatniego 👇:

int numbers[5] = {[4] = -89, [1] = 13, [0] = 3, [2] = 5, [3] = 67};

Kolejność może być dowolna - możemy na przykład, przypisać wartość ostatniemu indeksowi albo zacząć od środka, albo trochę tak i trochę tak. To sprawi, że podane liczby uszeregują się w takiej kolejności:

{3, 13, 5, 67, -89}

Powtarzam: "odliczanie" elementów zawsze zaczynamy od zera ‼️!

Nie musimy określać miejsca dla każdego elementu. Możemy dla każdego z nich opuścić przedrostek w postaci nawiasów kwadratowych i znaku przypisania:

int numbers[5] = {[4] = -89, [1] = 13, 3, 5, 67};

I tu uwaga na pułapkę ⚠️ !!! Tablica w języku C wg standardu C99 działa tak, że podając indeks przy jednym argumencie, kolejne wartości bez tego przedrostka są przypisywane po kolei następnym argumentom. Zatem, otrzymamy taki oto zestaw elementów:

{0, 13, 3, 5, -89}

Pierwszy argument ([0]) przyjmie wartość zero, bo w środku klamerek nie ma żadnego przypisania do tego miejsca. Drugi element posiada jawnie przypisaną liczbę 13, natomiast zobacz co się dzieje dalej!

Brak jawnych przyporządkowań sprawia, że następne elementy są wstawiane jeden po drugim. Stąd, liczba 3 "trafi" na trzecie miejsce, liczba 5 na czwarte, a 67, na piąte 💡.

Jeżeli indeksy "nachodzą" na siebie tzn. przypiszesz do tego samego miejsca jakiś element wiele razy 👇:

int numbers[5] = {[4] = -89, [1] = 13, 3, 5, [4] = 67};

to brana jest pod uwagę ostatnia wartość, więc to liczba 67 pojawi się na ostatnim (piątym) miejscu 🚀. Bądź z tym ostrożny(-a), bo takie podejście może spowodować wyjście poza podany zakres (więcej o tym za chwilę) ⚠️. Wówczas kompilator powinien wyświetlić ostrzeżenie typu "excess elements in array initializer", co w wolnym tłumaczeniu oznacza: "wyszedłeś/wyszłaś poza ramy tablicy" ℹ️.

"PORUSZANIE SIĘ" PO TABLICY

Mając tablicę, możesz "sięgnąć" do jej elementów przy użyciu notacji tablicowej, jako jeden z dwóch sposobów (drugi to notacja wskaźnikowa opisana w odrębnym artykule ℹ️). Indeks to nic innego, jak liczba całkowita określająca liczbę porządkową danego elementu według kolejności przypisania ℹ️. Gdy mamy na przykład, taką tablicę 👇:

int numbers[5] = {-89, 13, 3, 5, 67};

to elementy zostaną przyporządkowane dla indeksów od 0 do 4 (nie od 1 do 5 ⚠️!). Pierwszy element zawsze zaczyna się na pozycji zerowej i zapamiętaj dobrze tę istotną rzecz ❗! To nie żaden wymysł autora - to wynika z prawdziwej "postaci" tablicy, o czym piszę pod sam koniec artykułu ⬇️.

Dysponując tą wiedzą, celem odwołania się do któregoś z elementów, stosujesz następujący zapis:

numbers[0]

To oznacza przedostanie się do pierwszego elementu 1️⃣. Tak dostaniesz się do drugiego w kolejności 2️⃣:

numbers[1]

i tak dalej. Elementy są dla Ciebie dostępne, zarówno do odczytu np. przez funkcję "printf":

printf("%d\n", numbers[0]);

jak i modyfikacji:

numbers[0] = 30;

Możesz swobodnie to podstawiać do wyrażeń, funkcji itd.

Z uwagi na swoją strukturę, która wymaga podania konkretnej liczby, osobiście odradzam programowania w stylu "sięgania" po konkretne wartości poza pętlą (jedyne wyjątki, to sięganie do pierwszego bądź ostatniego w kolejności). Jeżeli Twoja implementacja będzie polegać na sięganiu po coś "w połowie", to jak najszybciej powinieneś/powinnaś to zmienić - to jest bardzo zła praktyka, bo to jest poleganie na założeniu, że elementy będą zawsze w tej samej kolejności ⚠️. A co, gdy będą w innej 💥?

PATRZ JAK DALEKO SIĘ ODWOŁUJESZ!

Dodatkowa przestroga, której musisz być świadomy(-a) ⚠️. Tablica w języku C nie osłania Cię w żadnej mierze przed "wyjściem" poza jej rozmiar. W praktyce oznacza to to, że jeżeli masz tablicę 5-elementową 👇:

int numbers[5] = {-89, 13, 3, 5, 67};

to możesz postawić takie wyrażenie:

numbers[5] = 100;

dopuszczając się niezdefiniowanego zachowania 🚨! Zobacz, że indeks nr 5, to szósty element ⚠️. Natomiast rozmiar wynosi 5! Czyli wychodzimy poza ramy naszej tablicy 😳!

Warto wiedzieć, że "poza" tablicą nadal występują różne wartości, tylko po prostu należą do innych segmentów niewiadomego pochodzenia i zastosowania, którymi zajmuje się system operacyjny ℹ️. Z tego względu, nie powinno się odwoływać do pamięci spoza tablicę, a co gorsza, modyfikować. Nie wiadomo co się wtedy stanie, a może nawet dojść do awarii programu przez naruszenie ochrony pamięci🔥!

STAŁA DLA PRZYDZIELANIA ROZMIARU

Punkt będący raczej dobrą radą, niż informacją. Jeżeli kojarzysz materiał dotyczący dyrektywy "#define" i słowa "const", to dobrym pomysłem jest zdefiniowanie stałej dla rozmiaru tablicy jaką sobie stworzysz 👇:

#define ARRAY_SIZE 5

Wtedy w kodzie, wpisujesz sobie w nawiasy kwadratowe, nazwę stałej:

int numbers[ARRAY_SIZE] = {-89, 13, 3, 5, 67};

co jest bardzo pomocne przy iterowaniu po tablicy z użyciem pętli (więcej informacji w dalszej części), bo ta sama wartość będzie występować w 2 różnych miejscach w kodzie ✅.

POBIERANIE ROZMIARU TABLICY

Ostatnia rzecz z mojego zestawu informacji. Mogłeś(-aś) przyzwyczaić się do gotowych metod typu "length" czy "size" wyznaczających liczbę elementów w tablicy. W języku C tego nie ma 😱!!!

Żeby uzyskać coś takiego, musimy to sobie sami wyznaczyć przy użyciu bardzo niekonwencjonalnego wyrażenia 👇:

int numbersArrayLength = sizeof(numbers) / sizeof(numbers[0]);

"sizeof" zwraca liczbę całkowitą oznaczającą liczbę bajtów pamięci zajętej przez podaną zmienną ℹ️. Wszystkie powyższe przykłady opierają się na tablicy składającej się z liczb całkowitych ("int"). Zakres liczby całkowitej (a co za tym idzie, liczba bajtów), zależy od architektury systemu i kompilatora. W chwili pisania tego artykułu, na moim komputerze miałem wynik 4 bajtów, więc dokładnie tyle pamięci zajmuje jeden element.

Ten fragment:

sizeof(numbers)

oznacza całkowitą zajętość pamięci w bajtach przez tablicę, a ten fragment:

sizeof(numbers[0])

oznacza zajętość pamięci w bajtach pojedynczego elementu. W wyniku dzielenia otrzymamy wartość 5, czyli tyle, ile wynosi rozmiar zdefiniowanej tablicy ☑️. Teraz wyjaśniam jak to się dzieje ✒️.

Jeżeli przyjmiemy, że liczba bajtów przypadająca na jeden element wynosi 4, to wyrażenie:

sizeof(numbers)

zwróci 20, ponieważ występuje 5 komórek po 4 bajty każda. Wzór gwarantuje niezawodność ✅, ponieważ obie wartości zawsze "skalują się" odpowiednio do architektury i kompilatora.

To Ci się przyda w sytuacji, gdy chcesz wyznaczyć rozmiar jakiejś tablicy dużo później, bez definiowania stałej.

PRZYKŁAD KODU ŹRÓDŁOWEGO

Czas na prosty przykład, jak zwykle 😄! Poniższy kod źródłowy podsumowuje wszystkie wyżej opisane podpunkty i przedstawia iterowanie po tablicy zawierającej 5 liczb całkowitych z użyciem pętli "for" 👇:

#include <stdio.h>

#define ARRAY_SIZE 5

int main(void)
{
	int numbers[ARRAY_SIZE] = {-89, 13, 3, 5, 67};

	for (int i = 0; i < ARRAY_SIZE; ++i)
	{
		printf("Liczba %d = %d\n", i + 1, numbers[i]);
	}

	return 0;
}

Na samym początku funkcji "main" tworzona jest tablica. Zabezpieczamy się przed wyjściem poza jej zakres poprzez zdefiniowanie stałej używając dyrektywy "#define". W środku pętli, wypisujemy sobie wszystkie elementy jeden po drugim, używając funkcji "printf". Poza samą wartością elementu kryjącego się pod danym indeksem, wypisujemy dodatkowo sam indeks zwiększony o 1, abyśmy mieli podgląd na liczbę porządkową 👍.

Tablica w języku C

Tablica w języku C to struktura danych zdolna do przechowywania wielu wartości tego samego typu.

TABLICA JAKO DRUGA "TWARZ" WSKAŹNIKA!

Dobrze. Teraz mogę ujawnić być może przerażającą prawdę o tablicach 😳. Tablica w języku C...to także wskaźnik 😱!!! To może zaburzyć Twój dotychczasowy punkt widzenia na tablice!

Kiedy masz tablicę 👇:

int numbers[5] = {-89, 13, 3, 5, 67};

to poza notacją tablicową, możesz odwoływać się do jej elementów w jeszcze jeden sposób - przez notację wskaźnikowąGdy chcemy przedostać się do pierwszego elementu tablicy, to możemy w ten sposób:

numbers[0]

bądź w ten sposób:

*numbers

Korzystanie z notacji wskaźnikowej wygląda zupełnie inaczej od indeksowej i polega na odwoływaniu się do elementów tablicy poprzez operator "wyłuskania" (dereferencji) oraz określeniu o ile elementów chcemy się "przesunąć" względem jej "początku" ℹ️.

Aby nie łączyć ze sobą dwóch różnych zagadnień, dalsza część tego wątku znajduje się w odrębnym artykule.


To wszystko co miałem do przekazania 😄. Gdybym skupił się jedynie na samych podstawach, to pewnie wyszłaby jedna trzecia całego materiału. Jednak zawsze wolę uzupełnić wątek o własne porady i spostrzeżenia, abyś nie był(a) w tej samej sytuacji, co ja, gdy ja potrzebowałem dodatkowych wyjaśnień i ostrzeżeń 🙂.

PODOBNE ARTYKUŁY