This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
dydaktyka:cprog:2016:arrays [2016/01/30 08:19] 127.0.0.1 edycja zewnętrzna |
— (current) | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Tablice ====== | ||
- | Tablica to ciąg wartości tego samego typu (np. liczb) przechowywanych obok siebie. | ||
- | |||
- | ===== Tablice jednowymiarowe ===== | ||
- | |||
- | |||
- | ==== Deklarowanie ==== | ||
- | |||
- | Uogólniona deklaracja tablicy: | ||
- | <code> | ||
- | typ_danych nazwa_tablicy[liczba_elementow]; | ||
- | </code> | ||
- | przy czym dostępne typy elementów są identyczne, jak dla "zwykłych" zmiennych (''int'', ''float'' itd.) | ||
- | |||
- | Przykład: | ||
- | <code c> | ||
- | int tab[8]; // tablica osmiu wartosci typu calkowitego | ||
- | </code> | ||
- | |||
- | ==== Definiowanie ==== | ||
- | |||
- | Definicja tablicy polega na przypisaniu do tablicy podanej w nawiasach klamrowych listy wartości kolejnych elementów (rozdzielonych przecinkami). | ||
- | |||
- | Przykład: | ||
- | <code c> | ||
- | int tab[4] = {3, 2, 5, 1}; | ||
- | </code> | ||
- | |||
- | 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: | ||
- | <code c> | ||
- | int tab[] = {1, 2, 3, 4}; | ||
- | int elem = tab[2]; // przypisz zmiennej `elem` wartosc TRZECIEGO elementu tablicy `tab` | ||
- | </code> | ||
- | |||
- | ==== 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 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!). | ||
- | ===== 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: | ||
- | <code c> | ||
- | int n = 3; | ||
- | int tab[n]; // blad | ||
- | </code> | ||
- | <code c> | ||
- | const int n = 3; | ||
- | int tab[n]; // blad | ||
- | </code> | ||
- | |||
- | :!: 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: | ||
- | <code c> | ||
- | #define N 3 | ||
- | </code> | ||
- | |||
- | Preprocesor to program uruchamiany przed właściwą kompilacją (stąd jego nazwa: __pre__procesor), 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 | ||
- | <code c> | ||
- | #define N 3 | ||
- | ... | ||
- | int tab[N]; | ||
- | </code> | ||
- | wygląda następująco (to widzi kompilator): | ||
- | <code c> | ||
- | int tab[3]; | ||
- | </code> | ||
- | |||
- | 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: | ||
- | <code c> | ||
- | #define R 3 | ||
- | #define C 3 | ||
- | </code> | ||
- | (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: | ||
- | |||
- | <code c> | ||
- | #include <stdio.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int tab[3] = {1, 2, 3}; | ||
- | |||
- | printf("%d\n", tab[-1]); | ||
- | printf("%d\n", tab[3]); | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | 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. | ||
- | ===== Tablice wielowymiarowe ===== | ||
- | |||
- | ==== Deklarowanie ==== | ||
- | |||
- | Uogólniona deklaracja tablicy wielowymiarowej: | ||
- | <code> | ||
- | typ_danych nazwa_tablicy[rozmiar_1][rozmiar_2]...[rozmiar_n]; | ||
- | </code> | ||
- | |||
- | na przykładzie dwuwymiarowym: | ||
- | <code> | ||
- | typ_danych nazwa_tablicy[ilosc_wierszy][ilosc_kolumn]; | ||
- | </code> | ||
- | |||
- | Przykład: | ||
- | <code c> | ||
- | int tab[8][3]; // tablica o osmiu wierszach i trzech kolumnach, wartosci typu calkowitego | ||
- | </code> | ||
- | |||
- | Komputer przechowuje w pamięci taką tablicę dwuwymiarową liniowo, jako kilka następujących po sobie tablic jednowymiarowych. | ||
- | |||
- | ==== Definiowanie ==== | ||
- | |||
- | Tablicę wielowymiarową można zdefiniować na dwa sposoby: | ||
- | * używając "zagnieżdżonych" klamer (pierwszy zestaw -> piewszy rząd, drugi zestaw -> drugi rząd itd.) | ||
- | * stosując "płaską" definicją -- wszystkie wartości znajdują się w jednych klamrach, przypisywane są do kolejnych elementów wierszami (najpierw całkowicie zapełniany jest pierwszy wiersz, potem drugi itd.) | ||
- | |||
- | Przykład sposobu z "zagnieżdżaniem" klamer: | ||
- | <code c> | ||
- | int tab[2][3] = { | ||
- | {3, 2, 6}, | ||
- | {5, 1, 9} | ||
- | }; | ||
- | </code> | ||
- | |||
- | Przykład sposobu "płaskiego": | ||
- | <code c> | ||
- | int tab[2][3] = {3, 2, 6, 5, 1, 9}; // zdecydowanie mniej czytelne, | ||
- | // w miare mozliwosci unikac! | ||
- | </code> | ||
- | |||
- | |||
- | ==== Dostęp do elementów ==== | ||
- | |||
- | 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: | ||
- | <code c> | ||
- | int tab[3][2]; | ||
- | tab[2][0] = 5; // przypisz wartosc elementowi w 3. wierszu w 1. kolumnie | ||
- | </code> | ||
- | |||
- | Poniższy rysunek pomoże Ci załapać tę zasadę:\\ | ||
- | {{:dydaktyka:cprog:2015:2d-array_indexing.png?nolink|}} | ||
- | |||
- | ==== Zadania ==== | ||
- | |||
- | === Zadanie SUM2D === | ||
- | |||
- | 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.// | ||
- | ===== Tablice bezwymiarowe ===== | ||
- | |||
- | 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 | ||
- | <code c>int tab[] = {1,2,3}; | ||
- | </code> | ||
- | 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: | ||
- | <code c> | ||
- | int tab[][2] = {{1,2}, {3,4}, {5,6}}; // OK | ||
- | int tab[][] = {{1,2}, {3,4}}; // blad | ||
- | </code> | ||
- | |||
- | ===== 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: | ||
- | <code c> | ||
- | int s = sizeof(char); // wartosc `s` to 1, bo tyle wynosi rozmiar typu 'char' | ||
- | int s2 = sizeof(s); // wartosc `s2` to (prawdopodobnie) 4 | ||
- | </code> | ||
- | |||
- | Operator ten można wykorzystać, aby otrzymać ilość elementów tablicy: | ||
- | <code c> | ||
- | int tab[5]; | ||
- | int ilosc_elementow = sizeof(tab) / sizeof(int); | ||
- | </code> | ||
- | ===== Łańcuchy znaków ===== | ||
- | |||
- | 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"%%'': \\ | ||
- | {{:dydaktyka:cprog:2015:string.png?nolink|}} | ||
- | |||
- | :!: 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ą. | ||
- | |||
- | |||
- | ==== Wyświetlanie i wczytywanie ==== | ||
- | |||
- | Do wyświetlania łańcuchów znaków służy specyfikator konwersji ''%s'': | ||
- | <code c> | ||
- | printf("Miasto: %s\n", "Krakow"); | ||
- | </code> | ||
- | |||
- | Do wczytywania łańcuchów znaków najwygodniej wykorzystać funkcję [[http://www.cplusplus.com/reference/cstdio/gets/?kw=gets|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. | ||
- | <code c> | ||
- | char imie[15]; | ||
- | printf("Podaj swoje imie: "); | ||
- | gets(imie); | ||
- | </code> | ||
- | |||
- | Oczywiście możesz też użyć funkcji ''scanf()'' ze specyfikatorem konwersji ''%s'': | ||
- | <code c> | ||
- | 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 | ||
- | </code> | ||
- | |||
- | :!: Pamiętaj, żeby wczytywać znaki do wystarczająco dużej tablicy (aby zmieściły się wszystkie znaki i jeszcze znak zerowy)! | ||
- | |||
- | |||
- | ==== Przydatne funkcje ==== | ||
- | |||
- | 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 [[http://www.cplusplus.com/reference/cstring/strlen/?kw=strlen|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): | ||
- | <code c> | ||
- | #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; | ||
- | } | ||
- | </code> | ||
- | |||
- | Aby określić typ znaku (np. czy dany znak jest cyfrą) warto skorzystać z biblioteki [[http://www.cplusplus.com/reference/cctype/|ctype.h]]. | ||
- | |||
- | ==== Zadania ==== | ||
- | |||
- | === Zadanie IDL === | ||
- | |||
- | 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: | ||
- | <code c> | ||
- | Podaj imie i nazwisko: Jan Nowak | ||
- | |||
- | Jan Nowak | ||
- | 3 5 | ||
- | </code> | ||
- | |||
- | //Wskazówka: Przechowuj imię i nazwisko w osobnych tablicach.// | ||
- | |||
- | ===== 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: | ||
- | <code c> | ||
- | 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 | ||
- | </code> | ||
- | |||
- | ==== 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: | ||
- | <code c> | ||
- | 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); | ||
- | </code> | ||
- | |||
- | :!: Zwróć uwagę na to, że np. ''i+1'' to nie to samo co ''i++'': | ||
- | <code c> | ||
- | 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 | ||
- | </code> | ||
- | |||
- | 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 | ||
- | <code c> | ||
- | int n = 3; | ||
- | int y = n++ + n++; | ||
- | </code> | ||
- | ''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 ===== | ||
- | |||
- | ==== Fotoszop ==== | ||
- | |||
- | ([size=10]poziom trudności:[/size] {{stars>2/4}} / {{stars>3/4}}) | ||
- | |||
- | Wykonaj zadania w ramach polecenia [[dydaktyka:cprog:2015:photoshop#cwiczenia|Rozmycie obrazu]]. | ||
- | |||
- | ==== Zadanie REV ==== | ||
- | |||
- | ([size=10]poziom trudności:[/size] {{stars>2/4}}) | ||
- | |||
- | 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!// |