User Tools

Site Tools


dydaktyka:cprog:2016:arrays

This is an old revision of the document!


Tablice

Tablica to ciąg wartości tego samego typu (np. liczb) przechowywanych obok siebie.

Tablice jednowymiarowe

Deklarowanie

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

Definiowanie

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 elementów

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`

Jak wyświetlać tablicę?

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

Zadania

Zadanie SUM1D

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.

Zadanie MAX1D

Napisz program, który wyznaczy największą wartość w jednowymiarowej tablicy liczb całkowitych.

Jak "elastycznie" określać rozmiar tablicy?

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.

Dlaczego nie należy wychodzić poza zakres tablicy?

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.

Operator "sizeof"

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

Przydatne operatory arytmetyczne

Arytmetyczne operatory przypisania

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

Inkrementacja i dekrementacja

Operatory inkrementacji (++) i dekrementacji (--) powodują odpowiednio zwiększenie bądź zmniejszenie wartości zmiennej o 1.

Są one dostępne w dwóch trybach:

  • przedrostkowym (pre-incrementation) – najpierw następuje zwiększenie wartości zmiennej, potem dalsze operacje na zwiększonej wartości; np. ++i
  • przyrostkowym (post-incrementation) – najpierw wykonywane są operacje (na pierwotnej wartości), a dopiero potem następuje zwiększenie wartości zmiennej; np. 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.

"Co za dużo to niezdrowo!"

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:

  • Nie stosuj operatora inkrementacji lub dekrementacji do zmiennej, która jest częścią więcej niż jednego argumentu funkcji.
  • Nie stosuj operatora inkrementacji lub dekrementacji do zmiennej, która w wyrażeniu pojawia się więcej niż jeden raz.

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.

Zadania podsumowujące

Zadanie RAND-ARR

(poziom trudności: )

Napisz program, który uzupełni tablicę typu int (o rozmiarze 10) losowymi wartościami z przedziału $\langle 0, 99 \rangle$.

Zadanie REV

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

dydaktyka/cprog/2016/arrays.1481009198.txt.gz · Last modified: 2020/03/25 11:46 (external edit)