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.

CZYM JEST TABLICA W PROGRAMOWANIU?

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 😁!

JAK WYGLĄDA TABLICA W JĘZYKU C?

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ą 🙂.

CO SIĘ STANIE, JAK TABLICA W JĘZYKU C BĘDZIE NIEZDEFINIOWANA?

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️⃣.

JAKIE SĄ SPOSOBY PRZYPISYWANIA ELEMENTÓW DO TABLICY W JĘZYKU C?

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 😉!

JAK SPRAWIĆ, ABY TABLICA W JĘZYKU C OTRZYMAŁA SAME ZERA?

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).

W JAKI SPOSÓB PRZYDZIELAĆ ELEMENTY TABLICY W JĘZYKU C 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, 67}

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" ℹ️.

JAK UZYSKAĆ DOSTĘP DO ELEMENTÓW TABLICY W JĘZYKU C?

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 💥?

CO SIĘ STANIE JAK PRZEKROCZYSZ ROZMIAR TABLICY W JĘZYKU C?

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 🔥!

JAKA JEST DOBRA PRAKTYKA PRZYDZIELANIA ROZMIARU TABLICY W JĘZYKU C?

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 ✅.

A teraz ostatnia rzecz z mojego zestawu informacji 🙂.

JAK WYZNACZYĆ ROZMIAR TABLICY W JĘZYKU C?

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 😱!!!

Aby 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 DZIAŁANIA TABLICY W JĘZYKU C

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.

Dobrze. Teraz mogę ujawnić być może przerażającą prawdę o tablicach 😳.

CZYM TAK NAPRAWDĘ JEST TABLICA W JĘZYKU C?

Tablica w języku C...to także wskaźnik 😱!!! To może zaburzyć Twój dotychczasowy punkt widzenia na tablice 😳!

Gdy 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 to zrobić 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 swego czasu potrzebowałem dodatkowych wyjaśnień i ostrzeżeń 🙂.

PODOBNE ARTYKUŁY