C/Taulukot

Wikikirjasto
< C
Loikkaa: valikkoon, hakuun

Tämä luku kertoo C:n taulukoista.

Yksiulotteiset[muokkaa]

Taulukko on muuttuja, johon on mahdollista tallentaa joukko arvoja. Yksiulotteinen taulukko luodaan samalla tavalla kuin normaali muuttuja, mutta sen perään laitetaan hakasulkuihin alkioiden lukumäärä. Seuraava esimerkki luo taulukon johon voi tallentaa kymmenen kokonaislukua.

int taulukko[10];

Taulukon alkioiden alkioiden numerointi alkaa nollasta, ei ykkösestä. 10-alkioisen taulukon ensimmäinen alkio on siis 0 ja viimeinen alkio 9. Yllä oleva taulukko näyttäisi siis tältä.

Tyhjä 10-alkioinen taulukko

Taulukkoja voi alustaa eri tavoin. Yksi tapa on esitellä taulukko yllä olevalla tavalla ja täyttää jokainen alkio yksi kerrallaan. Arvojen sijoittaminen taulukoiden alkioihin tapahtuu samalla tavalla kuin tavallisiin muuttujiin. Sinun täytyy vain kirjoittaa hakasulkuihin muokattavan alkion järjestysnumero eli indeksi.

int taulukko[3];
taulukko[0] = 1;
taulukko[1] = 2;
taulukko[2] = 3;

Jos alkioita on monta, tällainen arvojen sijoittaminen on kuitenkin työlästä kirjoittaa. On olemassa toinen tapa alustaa taulukoita, jonka avulla voit alustaa monta alkiota kerralla laittamalla arvot aaltosulkujen sisään pilkuilla eroteltuina. Mikäli aaltosulkeisiin ei kirjoiteta kaikkia arvoja, loput alustetaan nolliksi. Jos aaltosulkuihin laitetaan ainoastaan nolla, kaikki alkiot alustetaan nolliksi.

/* alustetaan alkioihin eri arvot */
int numeroita[3] = { 1, 2, 3 };

/* alustetaan kaikki alkiot nolliksi */
int numeroita[5] = { 0 };

C-kielessä taulukot ja pointterit ovat hyvin läheistä sukua keskenään ja itse asiassa taulukon indeksointiin käytettävä lauseke numeroita[3] tekee aivan saman asian kuin *(numeroita + 3) ja itse asiassa myös saman kuin *(3 + numeroita) eli 3[numeroita]. Tällaisten muotojen käyttöä ei voida pitää hyvänä ohjelmointityylinä, mutta taulukon pointteriluonne on hyvä tuntea. Edelliset lausekkeet toimivat, koska taulukko (muuttuja) numeroita muuntuu implisiittisesti int*-tyyppiseksi pointteriksi, joka osoittaa taulukon ensimmäiseen alkioon. Siis numeroita on sama kuin &numeroita[0] eli &*(numeroita + 0).

Pointteriluonne mahdollistaa myös taulukoiden välittämisen funktioiden argumentteina:

#include <stdio.h>

void print(int* array, size_t size) {
    // Tämä antaa pointterin koon - taulukon koko ei välity pointterin mukana
    printf("sizeof(array) = %d\n", sizeof(array));
    printf("%d != %d\n", sizeof(array) / sizeof(array[0]), size);
    // Tulostetaan taulukon sisältö
    for (size_t i = 0; i < size; ++i) printf("%d\n", array[i]);
}

void modify(int* array) { array[2] = -1234; }

int main(void) {
    int numbers[] = { 4, 8, 15, 16, 23, 42 };
    // Huom: sizeof(numbers) antaa taulukon koko sisällön koon tavuina ja
    // sizeof(numbers[0]) yhden alkion koon, joten size on alkioiden lukumäärä
    int size = sizeof(numbers) / sizeof(numbers[0]);
    modify(numbers);
    print(numbers, size);
}

Tällaisessa tilanteessa monelle ohjelmoijalle tulee kantapään kautta vastaan se, että pointterille sizeof antaa pointterin koon (yleensä 4 tai 8), mutta taulukolle taasen sen sisältämän tiedon koon. Tämä on hämäävää, koska C:ssä taulukko muuntuu implisiittisesti pointteriksi, melkein vahingossa, kuten esim. tässä funktiokutsun yhteydessä.

Myös malloc-funktiolla varattua muistia tai yleensäkin mitä tahansa pointteria voi käyttää taulukkona, koska indeksointi on todellisuudessa pelkkää pointteriaritmetiikkaa. Tätä tarvitaan jos halutaan esim. varata taulukko funktiossa ja antaa se paluuarvona, jolloin staattisesti varattu taulukko vapautettaisiin automaattisesti funktion päättyessä.

Moniulotteiset[muokkaa]

3x4-kokoinen kaksiulotteinen taulukko, lukuarvoilla täytettynä.

Moniulotteiset taulukot ovat taulukoita, joita indeksoidaan useammalla kuin yhdellä muuttujalla, esim. array[15][12][i] (kolmiulotteinen taulukko). Niitä on C:ssä kahta päätyyppiä: staattisia ja dynaamisia, joista jälkimmäisiä kutsutaan myös pointteritaulukoiksi, koska ne on toteutettu pointterein.

Staattiset moniulotteiset taulukot[muokkaa]

Staattisessa kaksiulotteisessa taulukossa array[3][4] on 12 alkiota, jotka on sisäisesti tallennettu 12-alkioiseen 1-ulotteiseen taulukkoon, mutta kääntäjä antaa indeksoida taulukkoa ikään kuin se olisi kaksiulotteinen. Kirjoittaessasi array[i][j], hakee kääntäjä taulukostaan alkion [i * 4 + j]. Huomaa, että tässä kääntäjän oli tiedettävä taulukon taulukon koko toisen dimension suhteen. Vastaavasti kolmiulotteisella staattisella taulukolla taulukko[3][4][5], viitattaessa soluun taulukko[i][j][k], indeksi lasketaan [i * 4 * 5 + j * 5 + k] ja niin edelleen. Huomionarvoista tässä on, että taulukon käyttämiseksi kääntäjän täytyy aina tietää sen kaikki dimensiot, ensimmäistä lukuun ottamatta.

Staattiset moniulotteiset taulukot esitellään samoin kuin yksiulotteisetkin ja ne voi myös alustaa vastaavasti (jos ei alusta, ovat taulukon sisältämät arvot satunnaisia). Taulukkoa ei kuitenkaan yksiulotteisten tapaan voi helposti välittää funktiolle, koska funktion määrittelyssä tulee taulukon koon (paitsi ensimmäisen indeksin) olla määritelty jo lähdekoodissa, kuten tässä:

#include <stdio.h>

void print(int taulukko[][4]) {
    for (size_t i = 0; i < 3; ++i) {
        for (size_t j = 0; j < 4; ++j) printf("%d ", taulukko[i][j]);
        putchar('\n');
    }
}

int main(void) {
   int taulukko[3][4] = { { 11, 12, 13, 14 }, { 21, 22, 23, 24 }, { 31, 32, 33, 34 } };
   print(taulukko);
}

Käytännössä moniulotteista staattista taulukkoa ei kannata koskaan välittää muualle. Jos tarvetta taulukon välittämiseen on, ovat yksiulotteiset taulukot (joita voi kertolaskun avulla käyttää moniulotteisena) ja dynaamiset taulukot parempia vaihtoehtoja.

Dynaamiset moniulotteiset taulukot[muokkaa]

Pointteritaulukko on huomattavasti staattista taulukkoa joustavampi ratkaisu, mutta myös merkittävästi hankalampi luoda. Taulukko ei myöskään tuhoudu automaattisesti lohkon päätteeksi, vaan se täytyy itse vapauttaa. Staattisesta moniulotteisesta taulukosta poiketen, pointteritaulukko voidaan helposti välittää funktion argumenttina, tietämättä sen kokoa ennalta. Seuraavassa esimerkki täysin dynaamisesta moniulotteisesta taulukosta:

#include <stdio.h>
#include <stdlib.h>

void print(int** array, size_t dim1, size_t dim2) {
    for (size_t i = 0; i < dim1; ++i) {
        for (size_t j = 0; j < dim2; ++j) printf("%d ", array[i][j]);
        putchar('\n');
    }
}

int** array_construct(size_t dim1, size_t dim2) {
    // Varataan ulommaisin taulukko (ensimmäinen indeksi), sisältää pointtereita sisätaulukoihin
    int** array = malloc(dim1 * sizeof(int*));
    for (size_t i = 0; i < dim1; ++i) {
        // Varataan sisätaulukko (toinen indeksi), sisältää numeroita
        array[i] = malloc(dim2 * sizeof(int));
        // Alustetaan taulukon solut vastaavilla arvoilla kuin staattisella taulukolla edellä
        for (size_t j = 0; j < dim2; ++j) array[i][j] = (i + 1) * 10 + j + 1;
    }
}

void array_destruct(int** array) {
    for (size_t i = 0; i < 3; ++i) free(array[i]);
    free(array);
}

int main(void) {
    int** array = array_construct(3, 4);
    // ...
    print(array, 3, 4);
    // Kerrotaan oikeassa alakulmassa olevan alkion arvo sadalla:
    array[2][3] *= 100;
    // ...
    // Vapautetaan taulukolle varattu muisti
    array_destruct(array);
    array = NULL; // Jottei vahingossa käytettäisi jo vapautettua muistia
    // ...
}

Edellä olevassa esimerkkikoodissa mallocin paluuarvoja ei tarkistettu, mutta oikeassa koodissa pitää aina mallocin kutsumisen jälkeen tarkistaa palauttiko kutsu NULL:n ja tällöin suorittaa vaadittavat siivoustoimenpiteet (esim. jo varatun muistin vapautus) ennen funktiosta poistumista.

Täysin dynaamisessa pointteritaulukossa rivien ei tarvitse olla yhtä pitkiä, eli loopin sisällä oltaisiin voitu tehdä malloc((i + 1) * sizeof(int)), jolloin ensimmäisellä rivillä olisi yksi alkio, toisella kaksi, jne. Rivien kokoa voi muuttaa (realloc) ilman että muulle taulukossa olevalle tiedolle tarvitsee tehdä mitään ja myös rivien lisäys ja poisto hoituu kevyesti rivipointtereita siirtelemällä (sisällä olevaa tietoa ei tarvitse siirtää esim. lisättäessä uusi rivi taulukon alkuun).

Monesti kuitenkin riittää myös kevyempi "puolidynaaminen" pointteritaulukko, jossa tieto on tallennettu isoon yksiulotteiseen taulukkoon (voi olla esim. staattinen moniulotteinen taulukko), mutta sitä indeksoidaan pointteritaulukon avulla. Seuraavassa esimerkki sellaisen luomisesta:

int static_array[3 * 4] = { 11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34 };
int* array[3]; // Pointteritaulukko
for (size_t i = 0; i < 3; ++i) array[i] = static_array + i * 4 + j;
print(array, 3, 4);

Taulukko static_array olisi yhtä hyvin voinut olla staattinen kaksiulotteinen taulukko, koska sellaisen sisäinen rakenne on identtinen yksiulotteisen taulukon kanssa. Pointteritaulukko taasen on tällä kertaa toteutettu pinosta varattuna, eikä mallocilla varattuna, joten sitä ei tarvitse erikseen vapauttaa funktion päättyessä. Kuitenkin taulukko voidaan print-funktiolle välittää samoin kuin mikä tahansa pointteritaulukko (huomaa implisiittinen tyyppimuunnos int* array[3]:sta int**:ksi funktiokutsun yhteydessä).

C-ohjelmointikieli

EtusivuHistoriaTyökalut


Yksinkertainen C-kielinen ohjelmaMuuttujatAritmeettiset operaatiotKommentitOhjausrakenteetFunktiotOsoittimetDynaaminen muistinvarausTaulukotMerkkijonotTietueet


C-kielen varatut sanatStandardikirjastoLiitteet