C/Tablice: Różnice pomiędzy wersjami

Z Wikibooks, biblioteki wolnych podręczników.
< C
Usunięta treść Dodana treść
m Wycofano edycje użytkownika 91.201.19.30 (dyskusja). Autor przywróconej wersji to Adam majewski.
Znacznik: Wycofanie zmian
m Update syntaxhighlight tags - remove use of deprecated <source> tags
Linia 35: Linia 35:
Przykład:
Przykład:


<source lang=c>
<syntaxhighlight lang=c>
/*
/*
http://joequery.me/code/snprintf-c/
http://joequery.me/code/snprintf-c/
Linia 102: Linia 102:
}
}


</syntaxhighlight>
</source>


===Tablice wielowymiarowe===
===Tablice wielowymiarowe===
Linia 108: Linia 108:
Rozważmy teraz konieczność przechowania w pamięci komputera całej macierzy o wymiarach 10 x 10. Można by tego dokonać tworząc 10 osobnych tablic jednowymiarowych, reprezentujących poszczególne wiersze macierzy. Jednak język C dostarcza nam dużo wygodniejszej metody, która w dodatku jest bardzo łatwa w użyciu. Są to '''tablice wielowymiarowe''', lub inaczej "tablice tablic".
Rozważmy teraz konieczność przechowania w pamięci komputera całej macierzy o wymiarach 10 x 10. Można by tego dokonać tworząc 10 osobnych tablic jednowymiarowych, reprezentujących poszczególne wiersze macierzy. Jednak język C dostarcza nam dużo wygodniejszej metody, która w dodatku jest bardzo łatwa w użyciu. Są to '''tablice wielowymiarowe''', lub inaczej "tablice tablic".
Tablice wielowymiarowe definiujemy podając przy zmiennej kilka wymiarów, np.:
Tablice wielowymiarowe definiujemy podając przy zmiennej kilka wymiarów, np.:
<source lang="c">
<syntaxhighlight lang="c">
float macierz[10][10];
float macierz[10][10];
</syntaxhighlight>
</source>
Tak samo wygląda dostęp do poszczególnych elementów tablicy:
Tak samo wygląda dostęp do poszczególnych elementów tablicy:
<source lang="c">
<syntaxhighlight lang="c">
macierz[2][3] = 1.2;
macierz[2][3] = 1.2;
</syntaxhighlight>
</source>
Jak widać ten sposób jest dużo wygodniejszy (i zapewne dużo bardziej "naturalny") niż deklarowanie 10 osobnych tablic jednowymiarowych.
Jak widać ten sposób jest dużo wygodniejszy (i zapewne dużo bardziej "naturalny") niż deklarowanie 10 osobnych tablic jednowymiarowych.
Aby zainicjować tablicę wielowymiarową należy zastosować zagłębianie klamr, np.:
Aby zainicjować tablicę wielowymiarową należy zastosować zagłębianie klamr, np.:
<source lang="c">
<syntaxhighlight lang="c">
float macierz[3][4] = {
float macierz[3][4] = {
{ 1.6, 4.5, 2.4, 5.6 }, /* pierwszy wiersz */
{ 1.6, 4.5, 2.4, 5.6 }, /* pierwszy wiersz */
Linia 123: Linia 123:
{ 8.8, 7.5, 4.3, 8.6 } /* trzeci wiersz */
{ 8.8, 7.5, 4.3, 8.6 } /* trzeci wiersz */
};
};
</syntaxhighlight>
</source>
Dodatkowo, pierwszego wymiaru nie musimy określać (podobnie jak dla tablic jednowymiarowych) i wówczas kompilator sam ustali odpowiednią wielkość, np.:
Dodatkowo, pierwszego wymiaru nie musimy określać (podobnie jak dla tablic jednowymiarowych) i wówczas kompilator sam ustali odpowiednią wielkość, np.:
<source lang="c">
<syntaxhighlight lang="c">
float macierz[][4] = {
float macierz[][4] = {
{ 1.6, 4.5, 2.4, 5.6 }, /* pierwszy wiersz */
{ 1.6, 4.5, 2.4, 5.6 }, /* pierwszy wiersz */
Linia 132: Linia 132:
{ 6.3, 2.7, 5.7, 2.7 } /* czwarty wiersz */
{ 6.3, 2.7, 5.7, 2.7 } /* czwarty wiersz */
};
};
</source>
</syntaxhighlight>
Innym, bardziej elastycznym sposobem deklarowania tablic wielowymiarowych, jest użycie wskaźników. Opisane to zostało w następnym [[C/Wskaźniki#Tablice wielowymiarowe|rozdziale]].
Innym, bardziej elastycznym sposobem deklarowania tablic wielowymiarowych, jest użycie wskaźników. Opisane to zostało w następnym [[C/Wskaźniki#Tablice wielowymiarowe|rozdziale]].


Linia 146: Linia 146:


Przykładowy program :
Przykładowy program :
<source lang=c>
<syntaxhighlight lang=c>
/*
/*
http://stackoverflow.com/questions/2151084/map-a-2d-array-onto-a-1d-array-c/2151113
http://stackoverflow.com/questions/2151084/map-a-2d-array-onto-a-1d-array-c/2151113
Linia 169: Linia 169:
}
}
}
}
</syntaxhighlight>
</source>




Linia 182: Linia 182:
===Sposoby deklaracji tablic===
===Sposoby deklaracji tablic===
Tablicę deklaruje się w następujący sposób:
Tablicę deklaruje się w następujący sposób:
<source lang="c">
<syntaxhighlight lang="c">
typ nazwa_tablicy[rozmiar];
typ nazwa_tablicy[rozmiar];
</syntaxhighlight>
</source>
gdzie rozmiar oznacza ile zmiennych danego typu możemy zmieścić w tablicy. Zatem aby np. zadeklarować tablicę, mieszczącą 20 liczb całkowitych możemy napisać tak:
gdzie rozmiar oznacza ile zmiennych danego typu możemy zmieścić w tablicy. Zatem aby np. zadeklarować tablicę, mieszczącą 20 liczb całkowitych możemy napisać tak:
<source lang="c">
<syntaxhighlight lang="c">
int tablica[20];
int tablica[20];
</syntaxhighlight>
</source>
Podobnie jak przy deklaracji zmiennych, także tablicy możemy nadać wartości początkowe przy jej deklaracji. Odbywa się to przez umieszczenie wartości kolejnych elementów oddzielonych przecinkami wewnątrz nawiasów klamrowych:
Podobnie jak przy deklaracji zmiennych, także tablicy możemy nadać wartości początkowe przy jej deklaracji. Odbywa się to przez umieszczenie wartości kolejnych elementów oddzielonych przecinkami wewnątrz nawiasów klamrowych:
<source lang="c">
<syntaxhighlight lang="c">
int tablica[3] = {0,1,2};
int tablica[3] = {0,1,2};
</syntaxhighlight>
</source>


Niekoniecznie trzeba podawać rozmiar tablicy, np.:
Niekoniecznie trzeba podawać rozmiar tablicy, np.:
<source lang="c">
<syntaxhighlight lang="c">
int tablica[] = {1, 2, 3, 4, 5};
int tablica[] = {1, 2, 3, 4, 5};
</syntaxhighlight>
</source>
W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku - 5 elementów).
W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku - 5 elementów).


Rozpatrzmy następujący kod:
Rozpatrzmy następujący kod:
<source lang="c">
<syntaxhighlight lang="c">
#include <stdio.h>
#include <stdio.h>
#define ROZMIAR 3
#define ROZMIAR 3
Linia 215: Linia 215:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>
Wynik:
Wynik:


Linia 233: Linia 233:


Spróbujmy przedstawić to na działającym przykładzie. Przeanalizuj następujący kod:
Spróbujmy przedstawić to na działającym przykładzie. Przeanalizuj następujący kod:
<source lang="c">
<syntaxhighlight lang="c">
int tablica[5] = {0};
int tablica[5] = {0};
int i = 0;
int i = 0;
Linia 241: Linia 241:
printf ("tablica[%d]=%d\n", i, tablica[i]);
printf ("tablica[%d]=%d\n", i, tablica[i]);
}
}
</syntaxhighlight>
</source>
Jak widać, na początku deklarujemy 5-elementową tablicę, którą od razu zerujemy. Następnie pod trzeci i czwarty element (liczone począwszy od 0) podstawiamy liczby 3 i 7. Pętla ma za zadanie wyprowadzić wynik naszych działań.
Jak widać, na początku deklarujemy 5-elementową tablicę, którą od razu zerujemy. Następnie pod trzeci i czwarty element (liczone począwszy od 0) podstawiamy liczby 3 i 7. Pętla ma za zadanie wyprowadzić wynik naszych działań.


Linia 253: Linia 253:


Wystarczy pomylić się o jedno miejsce (tzw. błąd [[w:off by one|off by one]]) by spowodować, że działanie programu zostanie nagle przerwane przez system operacyjny ( [[C/Błędy#B.C5.82.C4.99dy_przy_uruchamianiu|błąd przy uruchamianiu]] ) :
Wystarczy pomylić się o jedno miejsce (tzw. błąd [[w:off by one|off by one]]) by spowodować, że działanie programu zostanie nagle przerwane przez system operacyjny ( [[C/Błędy#B.C5.82.C4.99dy_przy_uruchamianiu|błąd przy uruchamianiu]] ) :
<source lang="c">
<syntaxhighlight lang="c">
int foo[100];
int foo[100];
int i;
int i;
Linia 261: Linia 261:


/* program powinien zakończyć się błędem */
/* program powinien zakończyć się błędem */
</syntaxhighlight>
</source>





Wersja z 00:15, 20 kwi 2020

W rozdziale Zmienne w C dowiedziałeś się, jak przechowywać pojedyncze liczby oraz znaki. Czasami zdarza się jednak, że potrzebujemy przechować kilka, kilkanaście albo i więcej zmiennych jednego typu. Nie tworzymy wtedy np. dwudziestu osobnych zmiennych. W takich przypadkach z pomocą przychodzi nam tablica.

Tablica to ciąg zmiennych jednego typu. Ciąg taki posiada jedną nazwę a do jego poszczególnych elementów odnosimy się przez numer (indeks).

tablica 10-elementowa



Wstęp

Typy tablic

Podział wg sposobu definiowania:

  • statyczne
  • dynamiczne
  • tablice, których rozmiar jest definiowany przez zmienną ( ang. Variable-length array)[1]
  • tablice, których rozmiar jest niezdefiniowany. Tablice te są elementem struktury. Angielska nazwa tych tablic : Flexible array member[2]


Podział wg typu elementu

  • tablice znaków
  • tablice liczb całkowitych


Podział wg rozmiaru

  • jednowymiarowe
  • wielowymiarowe

Tablice znaków

Tablice znaków, tj. typu char oraz unsigned char, posiadają dwie ogólnie przyjęte nazwy, zależnie od ich przeznaczenia:

  • bufory - gdy wykorzystujemy je do przechowywania ogólnie pojętych danych, gdy traktujemy je jako po prostu "ciągi bajtów" (typ char ma rozmiar 1 bajta, więc jest elastyczny do przechowywania np. danych wczytanych z pliku przed ich przetworzeniem).
  • napisy - gdy zawarte w nich dane traktujemy jako ciągi liter; jest im poświęcony osobny rozdział Napisy.

Przykład:

/*
 http://joequery.me/code/snprintf-c/
 
 
  gcc a.c -Wall
 ./a.out
 
012345678
hello th\0
turtle\078
2222\05678

*/ 
#include<stdio.h>
#define BUFSIZE 9




void init_buf(char *buf, size_t size){
    int i;
    for(i=0; i<size; i++){
        buf[i] = i + '0'; // int to char conversion
    }
}

void print_buf(char *buf){
    int i;
    char c;
    for(i=0; i<BUFSIZE; i++){
        c = buf[i];
        if(c == '\0'){
            printf("\\0");

        }
        else{
            printf("%c", buf[i]);
        }
    }
    printf("\n");
}


int main(){
    char buf[BUFSIZE];
    init_buf(buf, BUFSIZE);
    print_buf(buf);

    // hello there! == 12 characters, > BUFSIZE
    init_buf(buf, BUFSIZE);
    snprintf(buf, BUFSIZE, "hello there!");
    print_buf(buf);

    // turtle == 6 charaters, < BUFSIZE
    init_buf(buf, BUFSIZE);
    snprintf(buf, BUFSIZE, "turtle");
    print_buf(buf);

    // 2222220 == 7 charaters, > 5
    init_buf(buf, BUFSIZE);
    snprintf(buf, 5, "%d", 222222 * 10);
    print_buf(buf);

    return 0;
}

Tablice wielowymiarowe

tablica dwuwymiarowa (5x5)

Rozważmy teraz konieczność przechowania w pamięci komputera całej macierzy o wymiarach 10 x 10. Można by tego dokonać tworząc 10 osobnych tablic jednowymiarowych, reprezentujących poszczególne wiersze macierzy. Jednak język C dostarcza nam dużo wygodniejszej metody, która w dodatku jest bardzo łatwa w użyciu. Są to tablice wielowymiarowe, lub inaczej "tablice tablic". Tablice wielowymiarowe definiujemy podając przy zmiennej kilka wymiarów, np.:

 float macierz[10][10];

Tak samo wygląda dostęp do poszczególnych elementów tablicy:

 macierz[2][3] = 1.2;

Jak widać ten sposób jest dużo wygodniejszy (i zapewne dużo bardziej "naturalny") niż deklarowanie 10 osobnych tablic jednowymiarowych. Aby zainicjować tablicę wielowymiarową należy zastosować zagłębianie klamr, np.:

 float macierz[3][4] = {
   { 1.6, 4.5, 2.4, 5.6 },  /* pierwszy wiersz */
   { 5.7, 4.3, 3.6, 4.3 },  /* drugi wiersz */
   { 8.8, 7.5, 4.3, 8.6 }   /* trzeci wiersz */
 };

Dodatkowo, pierwszego wymiaru nie musimy określać (podobnie jak dla tablic jednowymiarowych) i wówczas kompilator sam ustali odpowiednią wielkość, np.:

 float macierz[][4] = {
   { 1.6, 4.5, 2.4, 5.6 },  /* pierwszy wiersz */
   { 5.7, 4.3, 3.6, 4.3 },  /* drugi wiersz */
   { 8.8, 7.5, 4.3, 8.6 },  /* trzeci wiersz */
   { 6.3, 2.7, 5.7, 2.7 }  /* czwarty wiersz */
 };

Innym, bardziej elastycznym sposobem deklarowania tablic wielowymiarowych, jest użycie wskaźników. Opisane to zostało w następnym rozdziale.


Kolejność głównych wierszy

Kolejność głównych wierszy ( ang. Row Major Order = ROM [3])

W C tablica wielowymiarowa A[n][m] :

  • jest przechowywana wierszami[4] :
  • numeracja indeksów rozpoczyna się od zera
 A[0][0], A[0][1], ..., A[0][m-1], A[1][0], A[1][1],..., A[n-1][m-1] 

Przykładowy program :

/*
 http://stackoverflow.com/questions/2151084/map-a-2d-array-onto-a-1d-array-c/2151113

*/
  #include <stdio.h>

   int main(int argc, char **argv) {
   int i, j, k;
   int arr[5][3];
   int *arr2 = (int*)arr;

       for (k=0; k<15; k++) {
          arr2[k] = k;
          printf("arr[%d] = %2d\n", k, arr2[k]);
       }

       for (i=0; i<5; i++) {
         for (j=0; j< 3; j++) {
            printf("arr2[%d][%d] = %2d\n", i, j ,arr[i][j]);
         }
       } 
    }


Działania na tablicach

Sposoby deklaracji tablic

Tablicę deklaruje się w następujący sposób:

 typ nazwa_tablicy[rozmiar];

gdzie rozmiar oznacza ile zmiennych danego typu możemy zmieścić w tablicy. Zatem aby np. zadeklarować tablicę, mieszczącą 20 liczb całkowitych możemy napisać tak:

 int tablica[20];

Podobnie jak przy deklaracji zmiennych, także tablicy możemy nadać wartości początkowe przy jej deklaracji. Odbywa się to przez umieszczenie wartości kolejnych elementów oddzielonych przecinkami wewnątrz nawiasów klamrowych:

 int tablica[3] = {0,1,2};

Niekoniecznie trzeba podawać rozmiar tablicy, np.:

 int tablica[] = {1, 2, 3, 4, 5};

W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku - 5 elementów).

Rozpatrzmy następujący kod:

 #include <stdio.h>
 #define ROZMIAR 3
 int main()
 {
   int tab[ROZMIAR] = {3,6,8};
   int i;
   puts ("Druk tablicy tab:");
 
   for (i=0; i<ROZMIAR; ++i) {
     printf ("Element numer %d = %d\n", i, tab[i]);
   }
   return 0;
 }

Wynik:

Druk tablicy tab:
Element numer 0 = 3
Element numer 1 = 6
Element numer 2 = 8

Jak widać, wszystko się zgadza.

W powyżej zamieszczonym przykładzie użyliśmy stałej do podania rozmiaru tablicy. Jest to o tyle pożądany zwyczaj, że w razie potrzeby zmiany rozmiaru tablicy, zmieniana jest tylko wartość w jednej linijce kodu przy #define, w innym przypadku musielibyśmy szukać wszystkich wystąpień rozmiaru rozsianych po kodzie całego programu.

Odczyt/zapis wartości do tablicy

Tablicami posługujemy się tak samo jak zwykłymi zmiennymi. Różnica polega jedynie na podawaniu indeksu tablicy. Określa on, z którego elementu (wartości) chcemy skorzystać spośród wszystkich umieszczonych w tablicy. Numeracja indeksów rozpoczyna się od zera, co oznacza, że pierwszy element tablicy ma indeks równy 0, drugi 1, trzeci 2, itd.

Spróbujmy przedstawić to na działającym przykładzie. Przeanalizuj następujący kod:

 int tablica[5] = {0};
 int i = 0;
 tablica[2] = 3;
 tablica[3] = 7;
 for (i=0;i!=5;++i) {
   printf ("tablica[%d]=%d\n", i, tablica[i]);
 }

Jak widać, na początku deklarujemy 5-elementową tablicę, którą od razu zerujemy. Następnie pod trzeci i czwarty element (liczone począwszy od 0) podstawiamy liczby 3 i 7. Pętla ma za zadanie wyprowadzić wynik naszych działań.

Tablica może być również zmieniana w obrębie funkcji

Ograniczenia tablic

Pomimo swej wygody tablice statyczne mają ograniczony, z góry zdefiniowany rozmiar, którego nie można zmienić w trakcie działania programu. Dlatego też w niektórych zastosowaniach tablice statyczne zostały wyparte tablicami dynamicznymi, których rozmiar może być określony w trakcie działania programu. Zagadnienie to zostało opisane w następnym rozdziale.

Wystarczy pomylić się o jedno miejsce (tzw. błąd off by one) by spowodować, że działanie programu zostanie nagle przerwane przez system operacyjny ( błąd przy uruchamianiu ) :

 int foo[100];
 int i;
 
 for (i=0; i<=100; i+=1) /* powinno być i<100 */
   foo[i] = 0;

 /* program powinien zakończyć się błędem */


Rozwiązanie:

  • cppcheck sprawdza zakres (Bounds checking )

Zobacz również

Przypisy

  1. Variable-length_array w ang. wikipedii
  2. Flexible_array_member w ang. wikipedii
  3. Row-major_order w ang wikipedii
  4. Metody obliczeniowe w nauce i technice - laboratorium informatyka II rok, Katarzyna Zając
  5. W zasadzie kompilatory mają możliwość dodania takiego sprawdzania, ale nie robi się tego, gdyż znacznie spowolniłoby to działanie programu. Takie postępowanie jest jednak pożądane w okresie testowania programu.