This is an old revision of the document!
Tablica to ciąg wartości tego samego typu (np. liczb) przechowywanych obok siebie.
Uogólniona deklaracja tablicy:
typ_danych nazwa_tablicy[liczba_elementow];
przy czym dostępne typy elementów są identyczne, jak dla “zwykłych” zmiennych (int
, float
itd.)
Przykład:
int tab[8]; // tablica osmiu wartosci typu calkowitego
Definicja tablicy polega na przypisaniu do tablicy podanej w nawiasach klamrowych listy wartości kolejnych elementów (rozdzielonych przecinkami).
Przykład:
int tab[4] = {3, 2, 5, 1};
W przypadku podania zbyt małej liczby wartości do inicjalizacji, pozostałe elementy tablicy otrzymują wartość 0.
Dostęp do poszczególnych elementów tablicy można uzyskać za pomocą indeksu elementu, będącego zawsze liczbą naturalną, umieszczonego w nawiasach kwadratowych.
W języku C indeksowanie zaczyna się od zera – czyli aby uzyskać wartość pierwszego elementu piszemy
tab[0]
!
Przykład:
int tab[] = {1, 2, 3, 4}; int elem = tab[2]; // przypisz zmiennej `elem` wartosc TRZECIEGO elementu tablicy `tab`
Stwórz jednowymiarową tablicę wartości zmiennoprzecinkowych zainicjalizowaną wybranymi przez siebie wartościami, a następnie oblicz sumę jej elementów.
Wskazówka: Skorzystaj z pętli for
.
Napisz program, który wyznaczy największą wartość w jednowymiarowej tablicy liczb całkowitych.
Cóż… Niestety, język C nie pozwala na wyświetlanie tablicy jedną instrukcję.
Tablicę trzeba wyświetlać element po elemencie.
Podobnie w przypadku, gdy to użytkownik ma podawać wartości elementów tablicy, trzeba to zrobić element po elemencie (najlepiej – w pętli!).
Standard ANSI C nie pozwala na używanie zmiennych bądź stałych do określania rozmiaru tablicy podczas deklaracji:
int n = 3; int tab[n]; // blad
const int n = 3; int tab[n]; // blad
Oznacza to więc, że nie ma możliwości utworzenia (statycznie) tablicy o rozmiarze zadanym przez użytkownika! – Nie pobieraj od użytkownika rozmiaru tablicy.
Można to zrobić korzystając z dynamicznej alokacji pamięci, ale to temat na jedno z ostatnich laboratoriów…
Z pomocą przychodzą stałe symboliczne.
Stałą symboliczną tworzy się z użyciem derektywy preprocesora #define
, na przykład:
#define N 3
Preprocesor to program uruchamiany przed właściwą kompilacją (stąd jego nazwa: preprocesor), a zajmuje się on m.in. dołączaniem plików nagłówkowych (poprzez znaną Ci derektywę #include
). Derektywa #define
mówi “zamień w tekście programu ciąg znaków po lewej stronie na ciąg znaków po prawej stronie”. W tym przypadku – ciąg znaków N
na ciąg znaków 3
.
Zatem po tym, jak preprocesor skończy swoją robotę, kod
#define N 3 ... int tab[N];
wygląda następująco (to widzi kompilator):
int tab[3];
Preprocesor działa więc momentami jak cenzor, którzy przed dostarczeniem listu do adresata zmienia to i owo – coś wykreśla, coś dodaje…
Derektywy #define
umieszcza się na początku kodu programu, zaraz pod derektywami #include
.
Można tworzyć wiele stałych symbolicznych – każdą w osobnej linii programu:
#define R 3 #define C 3
(to będzie przydatne przy tworzeniu tablic wielowymiarowych)
Stosuj stałe symboliczne wszędzie tam, gdzie normalnie powinien występować rozmiar tablicy (tj. dany jej wymiar) – w deklaracjach, w pętlach
for
itd.
W poniższym przykładzie mimo, że tablica składa się z trzech elementów (a więc poprawne indeksy to 0, 1 i 2), możemy odwołać się do elementów spoza zakresu – ani kompilator nie zgłosi błędów, ani (w tym przypadku) nie wystąpi błąd podczas wykonania programu:
#include <stdio.h> int main() { int tab[3] = {1, 2, 3}; printf("%d\n", tab[-1]); printf("%d\n", tab[3]); return 0; }
Tyle, że… wartości tab[-1]
i tab[3]
zawierają “śmieci”. W dodatku ta pamięć nie należy już do zmiennej tab
.
W ogólnym przypadku odwoływanie się do nie-swojej pamięci może spowodować błąd działania programu.
Uogólniona deklaracja tablicy wielowymiarowej:
typ_danych nazwa_tablicy[rozmiar_1][rozmiar_2]...[rozmiar_n];
na przykładzie dwuwymiarowym:
typ_danych nazwa_tablicy[ilosc_wierszy][ilosc_kolumn];
Przykład:
int tab[8][3]; // tablica o osmiu wierszach i trzech kolumnach, wartosci typu calkowitego
Komputer przechowuje w pamięci taką tablicę dwuwymiarową liniowo, jako kilka następujących po sobie tablic jednowymiarowych.
Tablicę wielowymiarową można zdefiniować na dwa sposoby:
Przykład sposobu z “zagnieżdżaniem” klamer:
int tab[2][3] = { {3, 2, 6}, {5, 1, 9} };
Przykład sposobu “płaskiego”:
int tab[2][3] = {3, 2, 6, 5, 1, 9}; // zdecydowanie mniej czytelne, // w miare mozliwosci unikac!
Podobnie jak w przypadku tablicy jednowymiarowej, dostęp uzyskuje się przez podanie w nawiasach kwadratowych numerów pozycji dla kolejnych wymiarów.
Dla przypadku dwuwymiarowego, podaje się najpierw numer wiersza, a potem numer kolumny.
Przykład:
int tab[3][2]; tab[2][0] = 5; // przypisz wartosc elementowi w 3. wierszu w 1. kolumnie
Poniższy rysunek pomoże Ci załapać tę zasadę:
Zadeklaruj tablicę dwuwymiarową $A_{N \times M}$. Przypisz do każdego z jej elementów $a(i,j)$ wartość $i + 2 \cdot j$, gdzie $i$ – numer wiersza, a $j$ – numer kolumny. Oblicz sumę elementów leżących w co drugim wierszu, zaczynając od pierwszego wiersza.
Wskazówka 1: Użyj zagnieżdżonych pętli for
zarówno do inicjalizacji elementów tablicy, jak i do obliczenia sumy elementów.
Wskazówka 2: Zastosuj osobny zestaw pętli dla inicjalizacji i osobny dla sumowania.
Kompilator C może automatycznie obliczyć rozmiar tablicy, jeśli tablica zostanie zadeklarowana jako bezwymiarowa (czyli programista nie poda ilości elementów tablicy).
Jeśli kompilator napotka, przykładowo, zapis
int tab[] = {1,2,3};
to utworzy tablicę na tyle dużą, by pomieścić wszystkie te elementy.
Kompilator jest w stanie obliczyć tylko jeden wymiar tablicy – pierwszy od lewej. Pozostałe wymiary muszą zostać podane:
int tab[][2] = {{1,2}, {3,4}, {5,6}}; // OK int tab[][] = {{1,2}, {3,4}}; // blad
Operator sizeof
zwraca rozmiar operandu (w bajtach). Używa się go podobnie do wywołania funkcji, przy czym argumentem może być nazwa zmiennej lub typ danych:
int s = sizeof(char); // wartosc `s` to 1, bo tyle wynosi rozmiar typu 'char' int s2 = sizeof(s); // wartosc `s2` to (prawdopodobnie) 4
Operator ten można wykorzystać, aby otrzymać ilość elementów tablicy:
int tab[5]; int ilosc_elementow = sizeof(tab) / sizeof(int);
Język C nie posiada specjalnego typu łańcuchowego. Zamiast tego, łańcuchy przechowywane są w tablicach elementów typu char
. Kolejne znaki, z których składa się łańcuch, znajdują się w kolejnych komórkach pamięci, a na ich końcu znajduje się znak zerowy (null character).
Znak zerowy nie jest cyfrą zero, tylko znakiem niedrukowanym o kodzie ASCII równym 0. Łańcuchy w języku C są przechowywane razem z tym znakiem. Oznacza to, że tablica musi mieć długość przynajmniej o jedną komórkę większą niż długość zapisanego w niej łańcucha.
Literał znaku zerowego to '\0'
.
Przykład – łańcuch znaków "Hello"
:
Znaki cudzysłowu nie są częścią łańcucha. Informują one jedynie kompilator, że to, co znajduje się pomiędzy nimi, jest łańcuchem – podobnie jak apostrofy sygnalizują stałą znakową.
Do wyświetlania łańcuchów znaków służy specyfikator konwersji %s
:
printf("Miasto: %s\n", "Krakow");
Do wczytywania łańcuchów znaków najwygodniej wykorzystać funkcję gets(), która odczytuje znaki do momentu napotkania znaku końca linii (ENTER) i zapisuje je jako łańcuch w tablicy znaków – sam znak końca linii nie jest kopiowany. Funkcja automatycznie dołącza do wczytanych znaków znak zerowy, tworząc tym samym łańcuch znaków.
char imie[15]; printf("Podaj swoje imie: "); gets(imie);
Oczywiście możesz też użyć funkcji scanf()
ze specyfikatorem konwersji %s
:
char imie[15]; printf("Podaj swoje imie: "); scanf("%s", imie); // `imie` to tablica, a nazwa tablicy to rownoczesnie ADRES jej // pierwszego elementu - nie trzeba zatem dodawac '&' przed nazwa tablicy
Pamiętaj, żeby wczytywać znaki do wystarczająco dużej tablicy (aby zmieściły się wszystkie znaki i jeszcze znak zerowy)!
Do badania długości łańcucha znaków – czyli ilości znaków faktycznie tworzących łańcuch, z pominięciem znaku zerowego – służy funkcja strlen().
Zwróć uwagę, że sizeof
i strlen
zwracają różne wartości dla tablicy przechowującej łańcuch znaków, gdyż łańcuch może nie zajmować całej tablicy (uruchom poniższy program):
#include <stdio.h> #include <stdlib.h> #include <string.h> int main () { char str[10] = "ABC"; // lancuch zajmuje 4 elementy tablicy printf("sizeof = %d , strlen = %d\n", sizeof(str), strlen(str)); return 0; }
Aby określić typ znaku (np. czy dany znak jest cyfrą) warto skorzystać z biblioteki ctype.h.
Napisz program, który pobiera imię i nazwisko użytkownika (używając jednego wywołania funkcji scanf()
), a następnie wyświetla: w pierwszym wierszu imię i nazwisko, a w drugim liczbę liter w imieniu oraz liczbę liter w nazwisku.
Na przykład:
Podaj imie i nazwisko: Jan Nowak Jan Nowak 3 5
Wskazówka: Przechowuj imię i nazwisko w osobnych tablicach.
Język C udostępnia arytmetyczne operatory przypisania <op>=
(gdzie <op>
oznacza operator arytmetyczny: +
, -
, *
, /
bądź %
): instrukcja x <op>= y;
jest równoważna x = x <op> (y);
Przykład:
int a = 5; int b = 2; a *= 3; // nowa wartosc `a` to 5 * 3 = 15 b *= 3 + 1; // nowa wartosc `b` to 2 * (3 + 1) = 8
Operatory inkrementacji (++
) i dekrementacji (--
) powodują odpowiednio zwiększenie bądź zmniejszenie wartości zmiennej o 1.
Są one dostępne w dwóch trybach:
++i
i++
Przykład:
int a1 = 3; int a2 = 3; int b_pre = 2 * ++a1; //po wykonaniu instrukcji: b_pre = 6, a1 = 3 int b_post = 2 * a2++; //po wykonaniu instrukcji: b_post = 4, a2 = 3 printf("a = %d, b_pre = %d, b_post = %d\n", b_pre, b_post);
Zwróć uwagę na to, że np.
i+1
to nie to samo co i++
:
int tab[3] = {1, 2, 3}; int i = 0; printf("%d\n", tab[i++]); // wypisz i-ty element ORAZ (nastepnie) zwieksz `i` o 1 // teraz `i` wynosi 1 printf("%d\n", tab[i+1]); // wypisz (i+1)-wszy element (ale nie zmnieniaj wartosci `i`) // `i` nadal wynosi 1
Operatory inkrementacji i dekrementacji działają tylko dla zmiennych (a ściślej: dla modyfikowalnych l-wartości). Oznacza to, że instrukcja int a = 6++;
jest błędna.
Stosuj powyższe operatory ostrożnie – zwiększają one zwięzłość kodu, lecz łatwo popełnić błąd w zapisie.
Nie łącz zbyt wielu operatorów w jednym wyrażeniu, nie rób “optymalizacji” długości kodu kosztem czytelności – w ten sposób unikniesz wielu błędów. W szczególności:
Na przykład po wykonaniu kodu
int n = 3; int y = n++ + n++;
y
może mieć albo wartość 6
– jeśli kompilator dwukrotnie użyje starej wartości – albo wartość 7
(3 + 4
), jeśli zwiększenie nastąpi przed obliczeniem sumy. Standard języka C nie narzuca jednego słusznego sposobu obliczania takiego wyrażenia.
(poziom trudności: /
)
Wykonaj zadania w ramach polecenia Rozmycie obrazu.
(poziom trudności: )
Napisz program, który odwróci kolejność elementów tablicy “w miejscu”, czyli bez deklarowania tablicy pomocniczej.
Przykład: {1, 4, 3}
→ {3, 4, 1}
.
Wskazówka: Stosowanie “zwykłej” zmiennej pomocniczej jest dozwolone!