Ero sivun ”C” versioiden välillä

Poistettu sisältö Lisätty sisältö
Tigru (keskustelu | muokkaukset)
Tigru (keskustelu | muokkaukset)
→‎Taulukot: lohkaisen
Rivi 31:
* [[/Dynaaminen muistinvaraus/]]
 
==* [[/Taulukot ==/]]
 
=== Yksiulotteiset ===
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ä.
 
[[Kuva:Array1.svg|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 <tt>numeroita[3]</tt> tekee aivan saman asian kuin <tt>*(numeroita + 3)</tt> ja itse asiassa myös saman kuin <tt>*(3 + numeroita)</tt> eli <tt>3[numeroita]</tt>. Tällaisten muotojen käyttöä ei voida pitää hyvänä ohjelmointityylinä, mutta taulukon pointteriluonne on hyvä tuntea. Edelliset lausekkeet toimivat, koska taulukko (muuttuja) <tt>numeroita</tt> muuntuu implisiittisesti <tt>int*</tt>-tyyppiseksi pointteriksi, joka osoittaa taulukon ensimmäiseen alkioon. Siis <tt>numeroita</tt> on sama kuin <tt>&numeroita[0]</tt> eli <tt>&*(numeroita + 0)</tt>.
 
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 <tt>malloc</tt>-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 ===
 
[[Kuva:Array3x4.svg|3x4-kokoinen kaksiulotteinen taulukko, lukuarvoilla täytettynä.]]
 
Moniulotteiset taulukot ovat taulukoita, joita indeksoidaan useammalla kuin yhdellä muuttujalla, esim. <tt>array[15][12][i]</tt> (kolmiulotteinen taulukko). Niitä on C:ssä kahta päätyyppiä: <strong>staattisia</strong> ja <strong>dynaamisia</strong>, joista jälkimmäisiä kutsutaan myös <strong>pointteritaulukoiksi</strong>, koska ne on toteutettu pointterein.
 
==== Staattiset moniulotteiset taulukot ====
 
Staattisessa kaksiulotteisessa taulukossa <tt>array[3][4]</tt> 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 <tt>array[i][j]</tt>, hakee kääntäjä taulukostaan alkion <tt>[i * 4 + j]</tt>. Huomaa, että tässä kääntäjän oli tiedettävä taulukon taulukon koko toisen dimension suhteen. Vastaavasti kolmiulotteisella staattisella taulukolla <tt>taulukko[3][4][5]</tt>, viitattaessa soluun <tt>taulukko[i][j][k]</tt>, indeksi lasketaan <tt>[i * 4 * 5 + j * 5 + k]</tt> 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 ====
 
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 <tt>NULL</tt>: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ä <tt>malloc((i + 1) * sizeof(int))</tt>, jolloin ensimmäisellä rivillä olisi yksi alkio, toisella kaksi, jne. Rivien kokoa voi muuttaa (<tt>realloc</tt>) 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 <tt>static_array</tt> 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 <tt>print</tt>-funktiolle välittää samoin kuin mikä tahansa pointteritaulukko (huomaa implisiittinen tyyppimuunnos <tt>int* array[3]</tt>:sta <tt>int**</tt>:ksi funktiokutsun yhteydessä).
 
== Merkkijonot ==
Noudettu kohteesta ”https://fi.wikibooks.org/wiki/C