User Tools

Site Tools


dydaktyka:cprog:2016:arrays-adv

This is an old revision of the document!


~~NOTRANS~~

Tablice – zaawansowane

Tablice wielowymiarowe

Deklarowanie

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.

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:

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!

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:

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ę:

Zadania

Zadanie SUM2D-1

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.

Zadanie SUM2D-2

Zadeklaruj tablicę dwuwymiarową $A_{N \times M}$. Przypisz do każdego z jej elementów losową wartość z przedziału $\langle 0, \rangle 9$. Wyświetl tablicę zawierającą wylosowane elementy. Oblicz i wyświetl sumę elementów w każdym rzędzie oraz w każdej kolumnie tej tablicy.

Tablice bezwymiarowe

Różnica w tworzeniu tablic “bezwymiarowych” w przypadku tablicy o wymiarze większym niż 1 jest następująca:

:!: Kompilator jest w stanie obliczyć tylko jeden wymiar tablicy – pierwszy od lewej. Pozostałe wymiary muszą zostać określone przez programistę:

int tab[][2] = {{1,2}, {3,4}, {5,6}};  // OK
int tab[][] = {{1,2}, {3,4}};  // blad

Jak "elastycznie" określać rozmiar tablicy? – Derektywa #define

Jak wspomniano w poprzednim laboratorium o tablicach, 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

Taki mechanizm jest przydatny właśnie podczas tworzenia wielowymiarowych tablic, szczególnie w przypadku gdy jeden z wymiarów ma być określony automatycznie.

:!: Stosuj stałe symboliczne (bądź ilość elementów obliczoną z użyciem sizeof) wszędzie tam, gdzie normalnie powinien występować rozmiar tablicy (tj. dany jej wymiar) – w deklaracjach, w pętlach for itd.

Ł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":

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

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

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

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:

Podaj imie i nazwisko: Jan Nowak
 
Jan Nowak
3 5

Wskazówka: Przechowuj imię i nazwisko w osobnych tablicach.

Zadanie PAL

Napisz program, który sprawdzi czy dany łańcuch znaków jest palindromem.

Na przykład kajak jest palindromem, natomiast rower już nie ;-)

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