previous next Title Contents Index

13. C- kielen taulukoista


Mitä tässä luvussa käsitellään?

* C- kielen taulukot

* taulukoiden ja osoittimien yhteys

* C- merkkijonot (char *)

* C- merkkijonojen ja C++ merkkijonojen yhteiskäyttö

* päällekkäiset muistialueet (union) ja luetteloidut tyypit (enum)

* moniulotteiset taulukot

Syntaksi:

	Taulukon esittely:    alkiontyyppi taulukonnimi[koko_alkioina];
	Alkioon viittaaminen: taulukonnimi[alkion_indeksi]
	Muista                1. indeksi = 0
	                      viimeinen  = koko_alkiona-1 
	Silmukoissa           for (i=0; i<koko; i++) ...
	C-mjonon esittely     char jono[tarvittava_merkkimaara+1];
	2-ul.taulukon es:     alkiontyyppi taulukonnimi[rivimaara][sarakemaara];

13.1 Yksiulotteiset taulukot

C- kielessä taulukoita ei oikeastaan ole, tai ainakin ne ovat '2. luokan kansalaisia'. Lausuma tarkoittaa sitä, että taulukoista on käytettävissä vain 1. alkion osoite ja esimerkiksi taulukon sisällön sijoittaminen toiseen taulukkoon ei onnistu sijoitusoperaattorilla.

13.1.1 Taulukon määrittely

Taulukko määritellään kertomalla taulukon alkioiden tyyppi ja alkioiden lukumäärä:
	int k_pituudet[12]; 
Tällöin taulukon 1. alkion indeksi on 0 ja 12. alkion indeksi on 11.

Määrittelyllä muuttujasta k_pituudet tulee osoitin kokonaislukuun; taulukon alkuun.

13.1.2 Taulukon alkioihin viittaaminen indeksillä

Taulukon alkioon voidaan viitata alkion indeksin avulla
	k_pituudet[0]=31; /* tammikuu */
	k_pituudet[1]=28; /* helmikuu */
Vaarallista on, että kukaan ei kiellä viittaamasta
	k_pituudet[24]=31; 
vaikka moista paikkaa taulukkoon ei alunperin ole edes varattu.

Indeksiviittaus k_pituudet[2] tarkoittaa itse asiassa viittausta *(k_pituudet+2)

k_pituudet+2 --+ 
               |
k_pituudet     |
  |            v  
  |      0  1  2  3  4  5  6  7  8  9 10 11 
  |    +-----------------------------------+ 
  +--->|31|28|31|30|31|30|31|31|30|31|30|31|
       +-----------------------------------+
eli 2 paikkaa eteenpäin taulukon alusta lukien.

Taulukko voitaisiin nollata seuraavalla silmukalla:

	int i;
	...
	for (i=0; i<12; i++) k_pituudet[i]=0; 
Huomautus! Taulukoiden käsittelyssä on muistettava, että indeksi liikkuu välillä [0,YLÄRAJA[.

13.1.3 Viittaaminen osoittimella

Taulukoihin voidaan viitata myös osoittimen avulla:

c-taul\kuut.c - esimerkki osoittimista taulukkoon

	  int *tammikuu,*helmikuu,*joulukuu; 
	...
	  tammikuu  = k_pituudet;
	  helmikuu  = tammikuu+1;
	  joulukuu  = k_pituudet+11;
	  *tammikuu = 31;
	  *helmikuu = 28;
	  *joulukuu = 31;
	
helmikuu  -----+ 
tammikuu  --+  |
            |  |                joulukuu ---+  
k_pituudet  |  |                            |
  |         v  v                            v
  |         0  1  2  3  4  5  6  7  8  9 10 11 
  |       +-----------------------------------+ 
  +------>|31|28| 0| 0| 0| 0| 0| 0| 0| 0| 0|31|
          +-----------------------------------+
Taulukon nollaaminen voitaisiin hoitaa myös:
	  int i,*p;
	  for (i=0, p=k_pituudet; i<12; i++, p++) *p=0;
	/* tai */
	  for (i=0, p=k_pituudet; i<12; i++) *p++=0;
	/* tai */
	  for (p=k_pituudet; p<k_pituudet+12; p++) *p=0; 
Viimeistä esimerkkiä lukuunottamatta näissä ei kuitenkaan ole järkeä, koska myös indeksi i tarvitaan joka tapauksessa. Viimeisenkin esimerkin mukaista käyttöä kannattaa harkita: se mitä tehdään, piiloutuu osoittimien taakse!

13.1.4 Taulukon alustaminen

Taulukko voidaan alustaa (vain) esittelyn yhteydessä:
	                  /*  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12 */
	int k_pituudet[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; 

Tehtävä 13.119 Taulukon alkioiden summa

Kirjoita funktio- aliohjelma taulukon_summa, joka palauttaa taulukon alkioiden summan. Kirjoita pääohjelma, jossa aliohjelmaa kutsutaan valmiiksi alustetulla taulukolla k_pituudet ja tulostetaan vuoden päivien lukumäärä.

13.2 Merkkijonot

Merkkijonot ovat eräs ohjelmoinnin tärkeimmistä tietorakanteista. Valitettavasti tämä on lähes poikkeuksetta unohtunut ohjelmointikielten tekijöiltä. Heille riittää että kielellä VOI tehdä merkkijonotyypin. Tavallista käyttäjää kiinnostaa tietysti onko se tehty ja onko se hyvä. Usein vastaus on EI. Näin myös C- kielen kohdalla! C++:han oli jo uusimmassa ehdotuksessa välttävä merkkijonoluokka. Itse asiassa tämä oli merkkittävin tekijä miksi tätä kurssia edeltävän alkeisohjelmointikurssin kieleksi valittiin C++ puhtaan C- kielen sijaan.

13.2.1 Merkkityyppi

Yksittäinen merkki on C- kielessä tyyppiä char:
	char rek_1_merkki;
	rek_1_merkki = 'X'; 
Huomattakoon, että merkkityypin vakio kirjoitetaan yksinkertaisiin lainausmerkkeihin. Vakio on tosin kokonaislukutyyppinen, eli
	sizeof('a') == sizeof(int) ( sizeof(char) == 1 )
Merkkityyppi on käytännössä (yleensä) 8- bittinen kokonaisluku. Toteutuksesta riippuen joko etumerkillinen tai etumerkitön. Siis merkkimuuttujiin voidaan vallan hyvin sijoittaa myös lukuarvoja:
	char m;
	m = 65;
	if ( m == 'A' ) ... 
	
Lukuarvo tarkoittaa merkin (ASCII- ) koodia.

Merkkien etumerkillisyys tulee ilmi esimerkiksi Turbo- C:ssä seuraavasti:

c-taul\merkit.c - esimerkki merkkien vertailusta

	  char m1,m2,m3; 
	  m1='a';
	  m2='z';
	  m3='ä';
	  if ( m1 < m2 ) printf("%c < %c \n",m1,m2);
	  printf("%c = %d,  %c = %d \n",m1,m1,m2,m2);
	  if ( m1 < m3 ) printf("%c < %c \n",m1,m3);
	  printf("%c = %d,  %c = %d \n",m1,m1,m3,m3); 
Jälkimmäistä if tulostusta ei tapahdu. ä:n koodi on 132, joka olisi suurempi kuin a:n ASCII- koodi eli 97, mutta koska Turbo- C:ssä merkit ovat etumerkillisiä, on ä:n koodi - 124!

HUOM! Tästä syystä merkkityyppisten muuttujien käyttö taulukoiden indeksinä on vaarallista!

13.2.2 C- kielen merkkijonot

Vaikka C++:sta löytyykin hyvä merkkijonoluokka <string>, niin joudumme silti tutustumaan myös puhtaan C- kielen merkkijonoihin. Miksikö? Koska esimerkiksi sijoituksessa
	cpp_jono = "Kissa";
sijoitetaan tosiasiassa C- merkkijono C++:n merkkijonoon. Lisäksi valtava määrä valmiista aliohjelmista haluaa parametrikseen nimenomaan C- merkkijonon tai palauttaa C- merkkijonon. Itse asiassa C++:n merkkijonojen käyttö on vielä tänä päivänä (1997) varsin harvinaista.

Tämä on sääli, sillä suurin osa C:n "huonoudesta" johtuu nimenomaan aloittelijoiden (ja kokeneidenkin) käsissä VAARALLISISTA merkkijonoista. Valtava määrä UNIXeistakin löytyvistä tietoturva- aukoista perustuu väärin käytettyihin merkkijonoihin! Näytimmekin jo aikaisemmin valmiiden olioluokkien kohdalla muutamia asioita, joita EI voi tehdä C- merkkijonoilla, tai jotka näyttävät hyvältä, mutta toimivat väärin. Nyt tutustumme vähän tarkemmin ongelmien syihin ja siihen miten niitä voidaan välttää.

Vinkki

Varaa tila myös loppumerkille

C- kielen merkkijonot eivät ole mitään muuta kuin taulukoita kirjaimista. Merkkijonoista on kuitenkin sovittu, että merkkijonon lopuksi taulukossa on merkki jonka koodi on 0 (NUL, '\0'). Siis merkkijono täytyy muistaa varata yhtä alkiota isommaksi, kuin aiottu merkkien maksimimäärä.

Huomattakoon, ettei ole olemassa valmista NUL- vakiota, vaan pitää käyttää joko muotoa 0 tai '\0'.

string.h- kirjastosta löytyy iso kasa merkkijonojen käsittelyyn tarkoitettuja aliohjelmia. Esimerkiksi sijoitus

	elain = "Kissa"
ei onnistu (tai onnistuu, muttei kopioi merkkejä "Kissa" taulukkoon elain), vaan on käytettävä esimerkiksi strcpy funktiota:
	char elain[12];
	strcpy(elain,"Kissa");           /* VAARALLINEN!!! Tässä toimii!!! */
	...
	
	Seuraavassa koodit on esitetty heksana ja 
	? tarkoittaa ettei muistipaikan arvoa tunneta.
	
elain
  |      0  1  2  3  4  5  6  7  8  9 10 11 
  |    +-----------------------------------+ 
  +--->|4B|69|73|73|61|00| ?| ?| ?| ?| ?| ?|
       +-----------------------------------+
        K  i  s  s  a  NUL ?  ?  ?  ?  ?  ? 
Merkkijonon pituus voitaisiin laskea seuraavalla aliohjelmalla:

c-taul\merkit.c - esimerkki merkkijonon viemisestä parametrina

	int pituus(const char jono[])
	{
	  int i;
	  for (i=0; jono[i]; i++);
	  return i;
	}
	
	/* Tai: */
	
	int pituus(const char jono[])  /* const char [] ei char []            */
	{                              /* koska jonoa ei muuteta              */
	  char *p;
	  for (p=jono; *p; p++);
	  return p- jono;
	}
Tosin string.h:sta löytyy funktio strlen, joka tekee täsmälleen saman asian.

Funktio pituus voitaisiin aivan yhtähyvin esitellä:

	int pituus(const char *jono) 

13.2.3 Kirjaimen ja merkkijonon ero

Ohjelmasta
	{
	  char c,jono[2];
	  c = 'A';
	  strcpy(jono,"A");
	...
	}
seuraisi seuraavat muistin sisällöt (heksana):
     +--+                  +-----+ 
  c: |41|         jono---->|41|00|          
     +--+                  +-----+
Siis muuttuja c ja taulukon alkio jono[0] käyttäytyvät samalla tavoin ja ovat kumpikin muistipaikkoja jotka sisältävät yhden merkin. Vastaavasti jono on osoitin merkkijonon alkuun (merkkiin jono[0]). Tyhjä merkkijonokin veisi vähintään yhden paikan! Miksi?

13.2.4 Merkkijono, osoitin merkkiin ja vakiojono

Seuraavat esittelyt saattavat näyttää samanlaisilta
	char *p = "Kissa", jono[10] = "Kissa";
mutta näiden ero on siinä, että *p on osoitin johonkin merkkiin ja jono 10- paikkaisen merkkijonon 1. merkkiin. Osoitin p on alustettu osoittamaan vakiomerkkijonoa jossa sisältönä on "Kissa" ja 10- paikkainen merkkijono on sisällöltään alustettu arvoksi "Kissa". Osoittimen p arvoa voidaan muuttaa, osoittimen jono arvoa sen sijaan ei voida muuttaa!

Esimerkiksi seuraava selvältä näyttävä ohjelma saattaisi tuottaa yllätyksen:

c-taul\strptr.c - varoittava esimerkki vakiojonon muuttamisesta

	#include <stdio.h> 	
	#include <string.h>
	int main(void)
	{
	  char *p="IsoKissa", jono[10];
	  strcpy(p,"Koira");             /* Tämä on tyhmää!!! */	
	  strcpy(jono,"Kissa");
	
	  printf("p: %s jono: %s\n",p,jono);
	  return 0;
	}
Ohjelma saattaa (kääntäjän optioista, kääntäjästä ja laiteympäristöstä riippuen) tulostaa:
	p: Koira jono: ra
Miksikö? Koska "IsoKissa" on vakiomerkkijono, jonka alkuun osoitin p alustetaan. Tänne osoitteeseen kopioidaan teksti "Koira". Koska kääntäjän mielestä "IsoKissa" on vakiojono ja lauseessa strcpy(jono,"Kissa") tarvitaan vakiojonoa "Kissa", päättää kääntäjä antaa käännösaikana strcpy- funktiolle osoitteen "IsoKissa" - merkkijonon sisältä!
                     osoitin "Koira"-------------------------+  
                     osoitin "Kissa"-------------+           |
                     osoitin "IsoKissa"----+     |           |
                                           v     v           v
                                          +-----------------------------+ 
alustus *p="IsoKissa"               p---->|I|s|o|K|i|s|s|a|0|K|o|i|r|a|0|
                                          +-----------------------------+
                     osoitin "Kissa"-------------+                       
                                                 v
                                          +-----------------------------+ 
kopiointi strcpy(p,"Koira")         p---->|K|o|i|r|a|0|s|a|0|K|o|i|r|a|0|   
                                          +-----------------------------+                      
                       +-------------------------+ 
                       |                         v
                       |                  +-----------------------------+ 
kopiointi strcpy(jono,"Kissa")      p---->|K|o|i|r|a|0|s|a|0|K|o|i|r|a|0|   
 +---------------+                        +-----------------------------+                      
 v            
+-------------------+
|r|a|0|?|?|?|?|?|?|?|
+-------------------+ 
Edellisessä esimerkissä sijoitus
	p = jono;   /* OIKEIN */
on sallittu, muttei sijoitus
	jono = p;   /* VÄÄRIN */
Myös sijoitus
	p = "Koira"; 
olisi sallittu, mutta ei suinkaan sijoita osoitteeseen p jonoa "Koira", vaan sijoittaa osoittimen p osoittamaan vakiomerkkijonoon "Koira".

Eroa käsitellään vielä lisää sizeof - operaattorin kohdalla.

13.3 string.h

C- merkkijonojen käsittelyyn on standardin mukaan seuraavat valmiit aliohjelmat string.h:ssa
	memchr        memcmp        memcpy        memmove       memset
	strcat        strchr        strcmp        strcpy        strcspn
	strerror      strlen        strncat       strncmp       strncpy
	strpbrk       strrchr       strspn        strstr        strtok
	strcoll       strxfrm
	
Lisäksi esim. Turbo- C:ssä on:
	memccpy       memicmp        movedata     movmem        setmem
	stpcpy        strcmpi        strdup       _strerror     stricmp
	strlwr        strncmpi       strnicmp     strnset       strrev
	strset        strupr
Kirjaston nimien yksi idea on siinä, että str- alkuiset nimet tarkoittavat string- funktioita, eli taulukon käsittely lopetetaan NUL- alkioon. mem- alkuiset toimivat yleensä vastaavasti, mutta niissä välitetään parametrina taulukon pituus ja NUL- tavusta ei välitetä. strn- alkuiset funktiot toimivat kuten str- funktiot, mutta NUL- tavun lisäksi taulukon käsittely lopetetaan, kun annettu maksimimäärä merkkejä on käsitelty.

13.3.1 strcpy ja strncpy

Esimerkiksi strcpy:n käyttö on erittäin vaarallista, mikäli merkkijono jonne kopioidaan jää pienemmäksi kuin mitä kopioidaan. Tällöin kopioidaan seuraavien muistialueiden päälle. Esimerkiksi saattaisi olla:

c-taul\strcpy.c - esimerkki tilan ylityksestä

	char jono1[6],jono2[6]; 
	...
	strcpy(jono2,"Koira");
	strcpy(jono1,"Kissa on!");
	...
	printf("%s %s\n",jono1,jono2); /* - > Kissa on! on! */
	
                           +-----jono2
jono1                      v
  |      0  1  2  3  4  5  0  1  2  3  4  5 
  |    +-----------------------------------+ 
  +--->|4B|69|73|73|61|20|6F|6E|21|00|61|00|
       +-----------------------------------+
        K  i  s  s  a     o  n  !  NUL a NUL 
Edellä jono2:n pilaaminen voitaisiin estää kutsulla strncpy:
	char jono1[6],jono2[6];
	...
	strncpy(jono2,"Koira",6);
	strncpy(jono1,"Kissa on!",6);
	...
	printf("%s %s\n",jono1,jono2); /* - > Kissa Koira Koira */

                           +-----jono2
jono1                      v
  |      0  1  2  3  4  5  0  1  2  3  4  5 
  |    +-----------------------------------+ 
  +--->|4B|69|73|73|61|20|6B|6F|69|72|61|00|
       +-----------------------------------+
        K  i  s  s  a     K  o  i  r  a  NUL 
Vikana on kuitenkin edelleen se, että nyt strncpy ei laita NUL merkkiä jonon loppuun, mikäli käsittely lopetetaan maksimipituuden takia!

Usein tämä varmistetaan laittamalla NUL- merkki varmuuden vuoksi kopioinnin jälkeen:

	...
	strncpy(jono1,"Kissa on!",6);
	jono1[6- 1] = 0;
	...
                           +-----jono2
jono1                      v
  |      0  1  2  3  4  5  0  1  2  3  4  5 
  |    +-----------------------------------+ 
  +--->|4B|69|73|73|61|00|6B|6F|69|72|61|00|
       +-----------------------------------+
        K  i  s  s  a  NULK  o  i  r  a  NUL 

Tehtävä 13.120 kopioi_jono

Kirjoita aliohjelma
int kopioi_jono(char *tulos, int max_koko, const char *jono)
joka kopioi merkkijonon jono paikkaan tulos, muttei ylitä tulos- jonon maksimipituutta max_pit. tulos jono loppuu aina NUL- merkkiin. Funktio palauttaa 0 mikäli kopiointi onnistuu kokonaan ja muuten jonkin muun arvon.

13.3.2 memmove

Mikäli kopioinnin kohde ja lähde ovat päällekäin, saattaa kopiointi tuottaa yllätyksiä. Jos jonot olisivat esimerkiksi seuraavasti
                           +-----jono2
jono1                      v
  |      0  1  2  3  4  5  0  1  2  3  4  5 
  |    +-----------------------------------+ 
  +--->|4B|69|73|73|61|00|6B|61|6E|61|00|??|
       +-----------------------------------+
        K  i  s  s  a  NULk  a  n  a  NUL    
ja tavoitteena olisi siirtää Kissa merkkijonoa 3 pykälää vasemmalle sekä muuttaa kana muotoon kkana, voitaisiin kuvitella tämän tapahtuvan
	strcpy(jono1,jono1+3);
	strcpy(jono2+1,jono2); 
Yleensä käyttöjärjestelmästä riippuen ainakin toinen edellisistä sekoaa. Ainoa turvallinen tapa tehdä em. temppuja on memmove, jonka taataan toimivan vaikka lähde ja kohde olisivat osittain päällekäinkin.
	memmove(jono1,jono1+3,strlen(jono1+3)+1);
	memmove(jono2+1,jono2,strlen(jono2)+1); 

13.3.3 strcat ja strncat

Merkkijonojen liittämiseen on funktiot strcat ja strncat. Esimerkiksi:
	char jono1[10],jono2[6];
	...
	strcpy(jono1,"Kissa");
	strcpy(jono2," on!");
	
                                       +-----jono2
jono1                                  v
  |      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
  |    +-----------------------------------------------+ 
  +--->|4B|69|73|73|61|00|??|??|??|??|20|6F|6E|21|00|??|
       +-----------------------------------------------+
        K  i  s  s  a  NUL               o  n  !  NUL

strcat(jono1,jono2);

                                       +-----jono2
jono1                                  v
  |      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
  |    +-----------------------------------------------+ 
  +--->|4B|69|73|73|61|20|6F|6E|21|00|20|6F|6E|21|00|??|
       +-----------------------------------------------+
        K  i  s  s  a     o  n  !  NUL   o  n  !  NUL
Funktiossa strcat on sama vaara kuin strcpyssäkin. Vaaraa voidaan osittain välttää strncat kutsulla, muistaen kuitenkin, että pituutena annetaan lisättävien merkkien maksimimäärä (NUL mukaanlukien, joka strncatin tapauksessa laitetaan), EI tuloksen maksimikoko. Siis turvallinen muoto olisi:
	strncat(jono1,jono2,10- (strlen(jono1)+1)); 

Tehtävä 13.121 liita_jono

Kirjoita aliohjelma
int liita_jono(char *tulos, int max_koko, const char *jono)
joka liittää merkkijonon jono jonon tulos perään, muttei ylitä tulos- jonon maksimipituutta max_pit. tulos- jono loppuu aina NUL- merkkiin. Funktio palauttaa 0 mikäli liittäminen onnistuu täysin ja muuten jonkin muun arvon.

13.3.4 Tulos myös funktion nimessä

string.h - kirjaston funktioista useat ovat sellaisia, että ne palauttavat myös nimessään osoittimen tulosmerkkijonoon. Miksikö? Siksi, että tällöin merkkijonokutsuja voidaan ketjuttaa funktiomaisesti:
	char jono[30],st[40];
	...
	strcat(strcpy(st,strcat(strcpy(jono,"Kissa")," istuu"))," puussa!"); 
Esimerkissä jonot saisi arvokseen:
	jono: "Kissa istuu"
	st  : "Kissa istuu puussa!" 

Tehtävä 13.122 Ketjutus

Miksi jonot jono ja st saisivat edellä mainitut arvot?

Tehtävä 13.123 string.h

Ota selvää mitä mikäkin string.h:n aliohjelmista tekee.

13.4 Merkkijonojen lukeminen

13.4.1 Valmiit funktiot VAARALLISIA

Vinkki

ÄLÄ LUOTA

valmiisiin merkkijonofunktioihin

Merkkijonojen lukeminen on eräs C- kielen vaarallisimpia operaatioita. Seuraavan malliesimerkin takia merkkijonot on aina luettava käyttäen jotakin itse tehtyä turvallista funktiota. Funktiot scanf ja gets pitää unohtaa.

Funktiolla fgets jono voidaan lukea turvallisesti, koska fgetsille viedään parametrina jonon maksimipituus:

	fgets(jono,max_pituus,tiedosto) 
Haittana on kuitenkin se, että jonon loppuun jää '\n'- merkki. Tämä ongelma on poistettu kappaleen lopussa esitetyssä malliohjelmassa.

Seuraava ohjelma on erittäin vaarallinen:

c-taul\jonovaar.c - esimerkki tilan ylityksestä päätesyötössä

	#include <stdio.h>
	
	char jono1[5]="";
	int  i=0;
	char jono2[5]="Kana";
	
	int main(void)
	{
	  printf("Anna merkkijono>");
	  scanf("%s",jono1); 	
	  printf("|%s| %x |%s|\n",jono1,i,jono2);
	  return 0;
	}

Tehtävä 13.124 Kanahaukka

Jos edelliselle ohjelmalle syötetään "Kanahaukka", niin se tulostaa:
|Kanahaukka| 7561 |kka|
Miksi?

13.4.2 Formaatin käyttö helpottaa hieman

Kanahaukka - esimerkin tilanne voitaisiin välttää käyttämällä lukemisessa muotoja:

c-taul\jonovaa2.c - tilanylityksen välttäminen formaatin avulla

	  scanf("%4s",jono1); 
	  scanf("%4[^\n]",jono1); 
Jälkimmäisessä on %s - formaatin tilalla käytetty formaattia [merkit- jotka- sallitaan], ja sen muotoa [^merkit- joita- ei- sallita]. Näin merkkijonoon saadaan mukaan myös välilyönnit, mitä ei %s- formaatissa saada. Muotoa voidaan käyttää tilapäisissä testiohjelmissa, mikäli alla olevaa kirjastoa ei ole käytössä. Vikana on se, että kentän maksimipituus on erittäin vaikeata saada seuraamaan merkkijonolle varattua tilaa!

13.4.3 getline apuun

Onneksi edes C++:ssa on tehty toimiva metodi merkkijonojen lukemiseen.

c-taul\jonolue.cpp - esimerkki toimivasta merkkijonon lukemisesta

	#include <iostream.h>
	#include <stdio.h>
	
	char jono1[5] = "";
	int  i=0;
	char jono2[5] = "Kana";
	
	int main(void)
	{
	  cout << "Anna merkkijono>";
	  cin.getline(jono1,sizeof(jono1));
	  printf("|%s| %x |%s|\n",jono1,i,jono2);
	  return 0;
	}

13.5 Malliohjelmia merkkijonojen käsittelyyn

13.5.1 Aliohjelmia C- merkkijonojen käsittelyyn

Seuraavassa on joukko aliohjelmia merkkijonojen lukemiseen ja käsittelyyn. Niistä tehokkaimmalla - lue_jono_oletus - voidaan tulostaa näyttöön samalla hoput- teksti ja mikäli kysymykseen vastataan pelkkä [RET], käytetään oletusarvoa. Tämä aliohjelma on suunniteltu nimenomaan toimimaan kerhon jäsenrekisterin tarpeiden mukaisesti.

ali\mjonot.c - aliohjelmia merkkijonojen käsittelyyn

	 /****************************************************************************/
	/*  
	**        M J O N O T . C
	**
	** Yleisiä merkkijonojen käsittelyyn liittyviä aliohjelmia.               
	**
	** Aliohjelmat:
	**    tee_jono               - luo uuden merkkijonon jonne jono kopioidaan    
	**    kopioi_jono            - kopioi kork. annetun määrän merkkejä           
	**    liita_jono             - liittää jonon toisen perään, tulos
	**                             korkeintaan max.pituus mittainen               
	**    f_lue_jono             - lukee tiedostosta merkkijonon                  
	**    alusta_lue_jono        - alustaa merkkijonon lukemisen                  
	**    lue_jono               - lukee päätteeltä merkkijonon                   
	**    lue_jono_oletus        - lukee päätteeltä merkkijonon.
	**                             Näyttöön tulostetaan haluttu viesti ja
	**                             jonon oletusarvo, mikäli painetaan RET         
	**    lue_kokluku_oletus     - luetaan kokonaisluku, jolle käytetään
	**                             oletusarvoa mikäli heti painetaan RET          
	**    poista_alkutyhjat      - poistaa merkkijonon alussa olevat välilyönnit  
	**    poista_lopputyhjat     - poistaa merkkijonon lopussa olevat välil.      
	**    poista_2_tyhjat        - muuttaa merkkijonossa kaikki peräkkäiset
	**                             välilyönnit yhdeksi välilyönniksi              
	**    poista_tyhjat          - poistaa alusta ja lopusta kaikki sekä
	**                             muualta moninkertaiset välilyönnit             
	**    poista_alku_ja_2_tyhjat- poistaa alusta ja 2x tyhjät                    
	**    isoksi                 - muuttaa kirjaimen isoksi kirjaimeksi huomioiden
	**                             skandit                                        
	**    pieneksi               - muuttaa pieneksi huomioiden skandit            
	**    jono_isoksi            - muuttaa jonon kaikki merkit isoiksi            
	**    jono_pieneksi          - muuttaa jonon kaikki merkit pieniksi           
	**    jono_alku_isoksi       - muuttaa jonon kaikki sanojen alut isoiksi
	**                             ja kaikki muut pieniksi                        
	**    jono_1_isoksi          - muuttaa jonon 1. kirjaimen isoksi              
	**    wildmat                - vertaa onko sana == maski, missä maskissa
	**                             voi olla jokeri-merkkejä (* tai ?)             
	**    onko_samat             - ensin muutetaan jonot isoiksi ja poistetaan
	**                             tyhjät ja sitten wildmat
	**                             (eli "  Kalle " == "    k*  ")                 
	**    palanen                - ottaa merkkijonosta seuraavan erotinmerkkien
	**                             määräämän palasen                              
	**    laske_merkit           - laskee annettujen merkkien esiintymismäärän
	**                             merkkijonossa                                  
	**    paikka                 - palauttaa kirjaimen 1. indeksin merkkijonossa  
	**    tayta_valit            - täyttää syötön "A-F" muotoon "ABCDEF"          
	**    joku_jono              - vastaa kysymykseen onko "EY" joku jonoista
	**                             "EU|EY|EL"                                     
	**    joku_jono_func         - kuten edellä, mutta vertailufunktio voidaan
	**                             antaa itse, esim
	**                               joku_jono("EU","E?|USA",wildmat) => 1        
	**    jono_arvosanaksi       - muuttaa merkkijonon "7-" reaaliluvuksi 6.75    
	**    arvosana_jonoksi       - muuttaa reaaliluvun 6.75 merkkijonoki "7-"     
	**    sallituissa            - paluttaa -1 jos tutkittavan jonon kaikki
	**                             merkit ovat annetussa joukossa, muuten
	**                             1. väärän merkin indeksin                      
	**    poista_merkit          - poistaa jonosta kaikki valitut merkit          
	**    poista_alusta          - poistaa merkkejä jonon alusta                  
	**    lisaa_alkuun           - lisää merkkejä jonon alkuun                    
	**    lisaa_merkki           - lisää merkin merkkijonon valittuun kohtaan     
	**    vaihda_jonot           - vaihtaa jonossa merkit toisiksi
	**                               ^ - rivin alussa tarkoittaa, etta
	**                                   vaihto tehdaan vain rivin alusta         
	**    vaihda_merkit          - vaihtaa jonossa yksittäiset merkit
	**                             toisiksi                                       
	**    muunna_C_symbolit      - muuttaa \t, \n ja \0x61 muotoiset
	**                             C-symbolit vastaaviksi merkeiksi               
	**    jonossa_vain_merkit    - poistaa jonosta kaikki ne merkit, jotka
	**                             eivät ole valitussa joukossa                   
	*/

Tehtävä 13.125 Merkkijonojen käsittely

Valitse muutamia aliohjelmia mjonot.c aliohjelmakirjastosta ja yritä itse toteuttaa ne.

13.5.2 Aliohjelmia C++ - merkkijonojen käsittelyyn

Edellisistä aliohjelmista suurin osa on toteutettu myös käsittelemään C++ - merkkijonoja:

ali\mjonotpp.h - aliohjelmia C++ -merkkijonojen käsittelyyn

	/****************************************************************************/
	/*     M  J  O  N  O  T  P  P.  H 
	**
	**
	**   Tiedostossa on merkkien ja merkkijonojen käsittelyohjelmien yleiset
	**   aliohjelmien otsikot.  Tiedosto on lähinnä muunnostiedosto string-
	**   tyypille vastaavasta C-kirjastosta mjonot.c
	**
	**   Tekijät:          Vesa Lappalainen
	**   Tehty:            02.01.1996
	**   Muutettu          07.03.1996/vl
	**   Mitä muutettu:    lue_jono_oletus laitettu toimimaan
	**
	**  Tiedostossa on seuraavia aliohjelmia:
	**    remove(st)             - poistaa tiedoston st
	**    onko_tiedostoa(st)     - palauttaa 1 jos on tiedosto nimellä st
	**    rename(&vanha,&uusi)   - vaihtaa tiedoston nimen
	**    tarkennin_alku(&tied)  - palauttaa mistä kohti tarkennin alkaa
	**                             tiedoston nimessä
	**    poista_tarkennin(tied) - poistaa tarkentimen tiedostonnimestä
	**    laita_tarkennin(tied,tark) - laittaa tiedoston nimen tarkentimen
	**    vaihda_tarkennin(tied,tark)- vaihtaa tiedoston nimen tarkentimen
	**
	**    istream &lue_rivi(istream &is,char *s,int max_koko);
	**    istream &lue_rivi(istream &is,string &s);
	**    istream &lue_rivi(istream &is,int &i,int def=0);
	**
	**  Seuraavat ovat mjonot.c:n muunnoksia string-luokalle
	**  (- merk ei totetuttu erikseen string-luokalle
	**     tai ei tarvitse toteuttaa)
	**
	**    tee_jono               - luo uuden merkkijonon jonne jono kopioidaan
	**    kopioi_jono            - kopioi kork. annetun määrän merkkejä
	**    liita_jono             - liittää jonon toisen perään, tulos
	**                             korkeintaan max.pituus mittainen               
	** -  f_lue_jono             - lukee tiedostosta merkkijonon                  
	** -  alusta_lue_jono        - alustaa merkkijonon lukemisen                  
	** -  lue_jono               - lukee päätteeltä merkkijonon                   
	**    lue_jono_oletus        - lukee päätteeltä merkkijonon.
	**                             Näyttöön tulostetaan haluttu viesti ja
	**                             jonon oletusarvo, mikäli painetaan RET         
	** -  lue_kokluku_oletus     - luetaan kokonaisluku, jolle käytetään
	**                             oletusarvoa mikäli heti painetaan RET          
	**    poista_alkutyhjat      - poistaa merkkijonon alussa olevat välilyönnit  
	**    poista_lopputyhjat     - poistaa merkkijonon lopussa olevat välil.      
	**    poista_2_tyhjat        - muuttaa merkkijonossa kaikki peräkkäiset
	**                             välilyönnit yhdeksi välilyönniksi              
	**    poista_tyhjat          - poistaa alusta ja lopusta kaikki sekä
	**                             muualta moninkertaiset välilyönnit             
	** -  isoksi                 - muuttaa kirjaimen isoksi kirjaimeksi huomioiden
	**                             skandit                                        
	** -  pieneksi               - muuttaa pieneksi huomioiden skandit            
	**    jono_isoksi            - muuttaa jonon kaikki merkit isoiksi            
	**    jono_pieneksi          - muuttaa jonon kaikki merkit pieniksi           
	**    jono_alku_isoksi       - muuttaa jonon kaikki sanojen alut isoiksi
	**                             ja kaikki muut pieniksi                        
	**    wildmat                - vertaa onko sana == maski, missä maskissa
	**                             voi olla jokeri-merkkejä (* tai ?)             
	**    onko_samat             - ensin muutetaan jonot isoiksi ja poistetaan
	**                             tyhjät ja sitten wildmat
	**                             (eli "  Kalle " == "    k*  ")                 
	**    palanen                - ottaa merkkijonosta seuraavan erotinmerkkien
	**                             määräämän palasen                              
	**    laske_merkit           - laskee annettujen merkkien esiintymismäärän
	**                             merkkijonossa                                  
	**    paikka                 - palauttaa kirjaimen 1. indeksin merkkijonossa  
	**    tayta_valit            - täyttää syötön "A-F" muotoon "ABCDEF"          
	**    joku_jono              - vastaa kysymykseen onko "EY" joku jonoista
	**                             "EU|EY|EL"                                     
	**    jono_arvosanaksi       - muuttaa merkkijonon "7-" reaaliluvuksi 6.75
	**    arvosana_jonoksi       - muuttaa reaaliluvun 6.75 merkkijonoki "7-"
	**    sallituissa            - paluttaa -1 jos tutkittavan jonon kaikki
	**                             merkit ovat annetussa joukossa, muuten
	**                             1. väärän merkin indeksin                      
	**    poista_merkit          - poistaa jonosta kaikki valitut merkit          
	**    poista_alusta          - poistaa merkkejä jonon alusta                  
	**    lisaa_alkuun           - lisää merkkejä jonon alkuun                    
	**    lisaa_merkki           - lisää merkin merkkijonon valittuun kohtaan     
	**    vaihda_jonot           - vaihtaa jonossa merkit toisiksi                
	**    muunna_C_symbolit      - muuttaa \t, \n ja \0x61 muotoiset              
	**                             C-symbolit vastaaviksi merkeiksi
	*/

13.6 C++ - merkkijonojen ja C- merkkijonojen yhteiskäyttö

Koska suurta osaa valmiina olevista aliohjelmista ei ole tehty kuin käsittelemään pelkkiä C- merkkijonoja, tutkimme seuraavassa hieman eri merkkijonotyyppien ristiinkäyttöä. Tämä on tietenkin mahdollista vain C++ - ohjelmissa.

13.6.1 C- merkkijonon muuttaminen C++ - jonoksi

C- merkkijono muuttuu C++ - jonoksi esimerkiksi sijoituslauseessa tai muodostajassa. Tämän ansiosta voidaan myös kutsua C- merkkijonoilla kaikkia niitä C++ - aliohjelmia, jonne viedään parametrina joko string tai const string &. Miksikö? Koska kääntäjä voi tällöin luoda tilapäisen string- olion kutsun ajaksi ja alustaa tämän kutsussa olleella C- merkkijonolla:

c-taul\c2cpp.cpp - C-jonojen muuttaminen C++ -jonoiksi

	#include <iostream.h>
	#include <string>
	using namespace std;
	
	void tulosta(const string &cppS)
	{
	  cout << cppS << endl;
	}
	
	void muuta_eka(string &cppS)
	{
	  cppS[0] = 'R';
	}
	
	void muuta_toka(string *cppS)
	{
	  (*cppS)[1] = 'u';
	}
	
	int main(void)
	{
	  char cs1[10] = "Kissa";
	  string cppS1(cs1), cppS2("Koira");  // 1. tapa muuttaa
	  cout << cppS1 << " " << cppS2 << endl;
	
	  string cppS3,cppS4,cppS5;
	  cppS3 = cs1;  cppS4 = "Kana";       // 2. tapa muuttaa
	  cppS5 = 'A';                        // Jopa merkki voidaan sijoittaa
	  cout << cppS3 << " " << cppS4 << " " << cppS5 << endl;
	
	  tulosta(cppS4);     // Toimii ilman muuta!
	  tulosta(cs1);       // Muuttaa automaattisesti cs1    => tilapäinen apujono
	  tulosta("Mato");    // Muuttaa automaattisesti "Mato" => tilapäinen apujono
	
	  muuta_eka(cppS3);  tulosta(cppS3); // Tottakait toimii
	  muuta_eka(cs1);    tulosta(cs1);   // Ei virheitä, mutta varoitus ja ei toimi
	  muuta_eka("Emu");  tulosta("Emu"); // Onneksi ei toimi!  Varoitus.
	
	  muuta_toka(&cppS4); tulosta(cppS4);// Tottakait toimii
	//  muuta_toka(&cs1);   tulosta(cs1);  // Ei käänny
	
	  return 0;
	}
Ohjelman tulostus:
	Kissa Koira
	Kissa Kana A
	Kana
	Kissa
	Mato
	Rissa
	Kissa
	Emu
	Kuna
Huomattakoon, että esimerkiksi kutsusta muuta_eka(cs1); seuraa esimerkiksi varoitus:
	Warn :  c2cpp.cpp(35,17):Temporary used for parameter 'cppS' in call to   
	  'muuta_eka(string &)'
Tässä varoitetaan ohjelmoijaa siitä, että tilapäinen string- olio luodaan kutsun ajaksi, mutta tämä olio sitten häviää kutsun jälkeen ja cs1 jää muuttumatta ehkä vastoin ohjelmoijan toiveita. Tässäkin on hyvä esimerkki siitä, että varoitukset kannattaa ottaa todesta!

Kutsussa muuta_eka("Emu"); ei ole mitään järkeä, mutta sekin menee kääntäjästä läpi pelkällä varoituksella. Onneksi tulee se tilapäinen jono ja vakiomerkkijono jää muuttumatta!

Tähän asiaan osittain kuulumaton vaaranpaikka on taas osoittimissa. Jos aliohjelma muuta_toka olisikin ollut muodossa (oli nimittäin minulla ja kaatoi koneen):

	void muuta_toka(string *cppS)
	{ 	
	  cppS[1] = 'u';
	}
menisi tämä täysin ilman varoituksia kääntäjästä lävitse. Eihän tässä nimittäin ole mitään virhettä. Lauseessahan vain käsketään sijoittamaan merkkijonotaulukon cppS toiseen paikkaan merkkijono u. Kirjain u muuttuu ensin automaattisesti merkkijonoksi ja tämä taas kuten aiemmin todettiin, voidaan sijoittaa C++ merkkijonoon. Taas yksi syy lisää VAROA osoittimia (ei kieltää niiden käyttöä). Toisaalta myös varoitus siitä, etteivät automaattiset tyypinmuunnokset ehkä sittenkään ole niin ihana asia!

13.6.2 C++ - merkkijonon muuttaminen C- jonoksi

Toisensuuntainen muunnos ei yleensä onnistu. C++ - merkkijonosta kyllä saadaan tilapäinen osoitin vastaavaan VAKIO C- merkkijonoon, jota voidaan käyttää hyväksi aliohjelmakutsuissa, joiden parametrilistassa on vakiomerkkijono:

c-taul\cpp2c.cpp - C++ -merkkijonojen käyttö C-merkkijonoja vaativissa kutsuissa

	#include <iostream.h>
	#include <string>
	#include <string.h>
	using namespace std;
	
	int montako_pta(const char *s)
	{ /* oikein C:mäinen */
	  int i,n = 0;
	  for (i=0; s[i]; i++) n +=  s[i] == 'p';
	  return n;
	}
	
	void muuta_eka(char s[])
	{
	  s[0] = 'R';
	}
	
	int main(void)
	{
	  string cppS1("Kolme porsasta padassa porisee pippurikastikkeen kera!");
	
	  int p_lkm = montako_pta(cppS1.c_str());
	  cout << "Jonossa oli " << p_lkm << " kpl p-kirjaimia\n";
	
	//  muuta_eka(cppS1.c_str()); // Ei onnistu! Täytyy tehdä apumuuttujan kautta
	  char cs[100];
	  strncpy(cs,cppS1.c_str(),100); cs[99] = 0;
	  muuta_eka(cs);
	  cppS1 = cs;
	  cout << cppS1 << endl;
	
	  return 0;
	}
Muoto jono.c_str() palauttaa nimenomaan osoittimen vakiomerkkijonoon. Tämän osoittimen elinikää ei taata, korkeintaan se elää niin kauan kuin vastaava jonokin. Kuitenkin esimerkiksi edellä joka tapauksessa aliohjelmakutsujen ajan. Tilanne on hankalampi jos jonoa pitää muuttaa, koska (onneksi) kääntäjä ei hyväksi tätä osoitinta muuttuvan merkkijonon tilalle kutsussa. Tällöin ainoaksi mahdollisuudeksi jää tehdä C++- jonosta itse tilapäinen kopio C- merkkijonoksi ja käyttää tätä jonoa aliohjelman parametrina ja aliohjelman jälkeen sitten sijoitetaan tilapäinen jono takaisin alkuperäiseen.

Jos useasti pitää kutsua samaa C- aliohjelmaa C++ - merkkijonolla, kannattaa tehdä "välittäjä"funktio, joka tekee em. kopioinnin. Funktioiden nimien kuormittamisen ansiostahan tämä välittäjäfunktio voidaan tehdä samalle nimelle. Tätä tekniikkaa on käytetty mm. muutettaessa mjonot.c:n funktioita C++:ksi kirjastossa mjonotpp.h.

Tehtävä 13.126 Välittäjäfunktio

Kirjoita C++ jonon hyväksyvä funktio muuta_eka käyttäen hyväksi cpp2c.cpp:n C- funktiota muuta_eka.

13.7 Tietueet, union ja enum

struct:n tapainen varattu sana on union, jolla tehdään päällekkäin olevia muistialueita, joilla on eri tilanteessa eri käyttö. Esimerkiksi henkilötietueessa naisilla voisi olla synnyttanyt_lapsia ja miehillä armeija_kayty, jotka jakaisivat saman muistialueen (huom. esimerkki tehty -91).

c-taul\union.c - vaihtuva muistipaikan tyyppi

	#include <stdio.h>                               Matti                    
	                                                 ---------------------------
	typedef enum {                                   |          -------------  |
	  mies,                                          |nimi:     |M|a|t|t|i|0|  |
	  nainen                                         |          -------------  |
	} tSukupuoli;                                    |sukupuoli:| 0 |          |
	                                                 |          -----          |
	typedef union {                                  |tiedot:   | 1 |          |
	  int syn_lapsia;                                ---------------------------
	  int armeija_kayty;                             Maija                       
	} tTiedot;                                       ---------------------------
	                                                 |          -------------  |   
	typedef struct {                                 |nimi:     |M|a|i|j|a|0|  |  
	  char       nimi[40];                           |          -------------  | 
	  tSukupuoli sukupuoli;                          |sukupuoli:| 1 |          |   
	  tTiedot    tiedot;                             |          -----          |  
	} tHenkilo;                                      |tiedot:   | 4 |          |    
	                                                 ---------------------------    
	void tulosta_henkilo(tHenkilo *henkilo) 
	{ 
	  printf("Nimi: %- 40s\n",henkilo- >nimi);
	  switch (henkilo- >sukupuoli) {
	    case mies:
	      printf("Mies, armeija käyty: %d\n",henkilo- >tiedot.armeija_kayty);
	      break;
	    case nainen:
	      printf("Nainen, synnyttanyt lapsia: %d\n",henkilo- >tiedot.syn_lapsia);
	      break;
	    default: printf("Eunukki\n");
	  }
	}
	
	int main(void)
	{
	  tHenkilo Matti = {"Matti",mies,1}, Maija = {"Maija",nainen,4};
	
	  tulosta_henkilo(&Matti);
	  tulosta_henkilo(&Maija);
	  return 0;
	}
Tällä kurssilla emme kuitenkaan suosittele union- tyyppisten muuttujien käyttöä! Mieluummin käytämme olioita ja polymorfismia.

Edellä enum tarkoitti lueteltua tyyppiä, jolloin mies vastasi vakiota 0 ja nainen vakiota 1. Tähän palataan myöhemmin.

Tehtävä 13.127 union

Mitä union.c ohjelma tulostaa. Piirrä kuva tietueista jos union vaihdettaisiinkin structiksi.
Korvaa enum sekä sen nainen ja mies #define- vakiolla.

Tehtävä 13.128 Tietue tietueessa

Olkoon meillä seuraavat C- määritykset:
	typedef struct {
	  char nimi[50];
	  int  paino;
	  int  ika;
	} Elain_tiedot;
	
	typedef struct {
	  char laji[40];
	  Elain_tiedot elain;
	  int nro; 
	} Laji_tiedot;
	
	int main(void)
	{
	  Laji_tiedot miuku;
	  ...
	  return 0;
	}
Täydennä ... - kohtaan seuraavat "sijoitukset" muuttujalle miuku:
	  laji  <-  "Kissa"
	  nro   <-   5
	  nimi  <-  "Miuku"
	  paino <-  3
	  ika   <-  5
Piirrä kuva miuku - muuttujasta sijoitusten jälkeen.

Tehtävä 13.129 Miukusta olio

Muuta edellisen tehtävän tietueet C++ - luokiksi ja tee sijoituksien avuksi metodit, jotka hoitavat sijoitukset. Saat nyt - tottakai - käyttää myös C++:n string- luokkaa.

13.8 Moniulotteiset taulukot

Moniulotteiset taulukot voidaan C- kielessä esitellä monella eri tavalla. Käymme nopeasti lävitse eri vaihtoehtoja.

13.8.1 Kiinteä esittely

Kaikkein helpoin tapa esitellä moniulotteinen taulukko on aivan normaali esittely:
int matriisi[3][4];         
              +---------------+ 
matriisi----->|   |   |   |   |<--- &matriisi[0][3]
              +---+---+---+---|
matriisi+1 -->|   |   |   |   |
              +---+---+---+---|
matriisi+2--->|   |   |   |   |
              +---------------+
Taulukon nimi on osoitin sen 1. RIVIIN!. Mallin tapauksessa kokonaislukuvektoriin int [4].

Taulukon alkioina voi tietysti olla mikä tahansa olemassa oleva tyyppi. C- kielessä matriisi talletetaan rivilistana, eli muistissa on ensin rivin 0 alkiot ja sitten rivin 1 alkiot jne. Myös moniulotteinen taulukko voidaan alustaa esittelyn yhteydessä:

	double yks[3][3] = {
	  { 1.0, 0.0, 0.0 },
	  { 0.0, 1.0, 0.0 },
	  { 0.0, 0.0, 1.0 }
	} 

Tehtävä 13.130 Matriisit

Kirjoita seuraavat aliohjelmat, jotka saavat parametrinaan 2 3x3 matriisia ja palauttavat 3x3 matriisin:
1.
Laskee yhteen 2 matriisia.
1.
Kertoo kaksi matriisia keskenään. (Kirjoita avuksi funktio, joka kertoo matriisin rivin i toisen matriisin sarakkeella j).

13.8.2 Muuttuvat dimensiot

Kun moniulotteinen taulukko esitellään aliohjelman parametrina, pitää suurinta ulottuvuutta lukuunottamatta (tietysti senkin saa kiinnittää) kiinnittää kaikki muut ulottuvuudet. Esimerkiksi:

c-taul\matriisi.c - esimerkki vaihtuvasta rivimäärästä 2-ulotteisessa taulukossa

	#include <stdio.h>
	
	#define SARAKKEITA 3
	
	double alkioiden_summa(double mat[][SARAKKEITA], int riveja)
	{
	  int i,j; double summa=0;
	
	  for (i=0; i<riveja; i++)
	    for (j=0; j<SARAKKEITA; j++)
	      summa +=mat[i][j];
	  return summa;
	}
	
	int main(void)
	{
	  double s2,s3, mat2[2][SARAKKEITA] = { {1,2,3},{4,5,6} },
	                mat3[3][SARAKKEITA] = { {1,0,0},{0,1,0},{0,0,1} };
	
	  s2 = alkioiden_summa(mat2,2);
	  s3 = alkioiden_summa(mat3,3);
	
	  printf("Summat on %5.2lf ja %5.2lf\n",s2,s3);
	
	  return 0;
	}

13.8.3 Yksiulotteisen taulukon käyttäminen moniulotteisena

On tietysti surullista, ettei taulukon kaikkia ulottuvuuksia voida välittää parametrina. Eihän juuri koskaan ohjelmaa tehtäessä tiedetä lopullista tilan tarvetta. Tai sitten ohjelman aikana samoja aliohjelmia tarvittaisiin erikoisille taulukoille. Tietysti voitaisiin esitellä liian suuri taulukko, ja "käyttää" vain vasenta ylänurkkaa.

Toisaalta moniulotteinenkin taulukko on todellisuudessa muistissa vain 1- ulotteisena. Tästä muunnoksestahan puhuttiin jo monisteen alkuosassa. On makuasia kumpiko järjestys esimerkiksi matriisissa valitaan: sarakelista vaiko rivilista. Rivilista on C- kielen mukainen, mutta toisaalta maailma on pullollaan Fortran aliohjelmia, joissa matriisit on talletettu sarakelistana. Siis kumpikin tapa on syytä hallita.

Tehtävä 13.131 Matriisi 1- ulotteisena

Kirjoita aliohjelma tee_yksikko, jolle tuodaan parametrina neliömatriisin rivien lukumäärä ja 1- ulotteisen taulukon alkuosoite, ja joka alustaa tämän neliömatriisin yksikkömatriisiksi.

13.8.4 Taulukko taulukoista

Eräs tapa käsitellä useampiulotteisia taulukoita on määritellä ensin rivityyppi ja sitten 1- ulotteinen taulukko näitä rivityyppejä:

c-taul\mat2.c - matriisi parametrina riviosoittimen avulla

	#include <stdio.h>
	
	#define SARAKKEITA 3
	typedef double Rivi_tyyppi[SARAKKEITA];
	
	double alkioiden_summa(Rivi_tyyppi *mat, int riveja)
	{
	  int i,j; double summa=0;
	
	  for (i=0; i<riveja; i++)
	    for (j=0; j<SARAKKEITA; j++)
	      summa +=mat[i][j];
	  return summa;
	}
	
	int main(void)
	{
	  double s2,s3; 
	  Rivi_tyyppi mat2[2] = { {1,2,3},{4,5,6} },
	              mat3[3] = { {1,0,0},{0,1,0},{0,0,1} };
	
	  s2 = alkioiden_summa(mat2,2);
	  s3 = alkioiden_summa(mat3,3);
	
	  printf("Summat on %5.2lf ja %5.2lf\n",s2,s3);
	
	  return 0;
	}
Tosiasiassa tällä ei ole mitään eroa edelliseen vastaavaan ohjelmamalliin verrattuna, tämä ehkä vain paremmin kuvastaa sitä, mihin matriisin nimi on osoitin.

13.8.5 Taulukko osoittimista

Mikäli halutaan tehdä esimerkiksi 2- ulotteinen taulukko, jonka kumpikin ulottuvuus on muuttuva, mutta silti taulukkoa käytetään kuten 2- ulotteista tavallista taulukkoa, pitää käyttää taulukkoa osoittimista:
mat-+      mat[0]         +---------- mat[0][2]
    |      |              |      
    |      |              v 
    v      +--->+---------------+
  +---+   +---->| 1 | 2 | 3 | 4 |  r0
0 | o-+---+     +---------------+ 
  +---|         +---------------+
1 | o-+-------->| 5 | 6 | 7 | 8 |  r1
  +---|         +---------------+ 
2 | o-+---+     +---------------+
  +---+   +---->| 9 | 0 | 1 | 2 |  r2
                +---------------+ 

c-taul\mat3.c - matriisi osoitintaulukon avulla

	#include <stdio.h>
	
	double alkioiden_summa(double **mat, int riveja, int sarakkeita)
	{
	  int i,j; double summa=0;
	
	  for (i=0; i<riveja; i++)
	    for (j=0; j<sarakkeita; j++)
	      summa +=mat[i][j];
	  return summa;
	}
	
	int main(void)
	{
	  double s1,s2; 
	  double r0[] = {1,2,3,4}, r1[] = {5,6,7,8}, r2[] = {9,0,1,2};
	  double *mat[3];
	  mat[0] = r0; mat[1] = r1; mat[2] = r2;
	
	  s1 = alkioiden_summa(mat,2,3);
	  s2 = alkioiden_summa(mat,3,4);
	
	  printf("Summat on %5.2lf ja %5.2lf\n",s1,s2);
	
	  return 0;
	}
Esimerkissä on sama esitelläänkö aliohjelmassa:
	double alkioiden_summa(double **mat,...
	/* vai */
	double alkioiden_summa(double *mat[],...
Tässä menettelyssä on vielä se etu, ettei kaikkien rivien välttämättä tarvitsisi edes olla yhtäpitkiä. Harvassa matriisissa osa osoittimista voisi olla jopa NULL- osoittimia, mikäli rivillä ei ole alkioita (aliohjelman pitäisi tietysti tarkistaa tämä). Oikeasti rivit usein vielä luotaisiin dynaamisesti ajonaikana tarvittavan pituisina.

Tehtävä 13.132 Transpoosi

Kirjoita taulukko- osoittimia käyttäen aliohjelma, joka saa parametrinaan kaksi matriisia ja niiden dimensiot. Aliohjelma tarkistaa voiko toiseen matriisiin tehdä toisen transpoosin (vaihtaa rivit ja sarakkeet keskenään) ja tekee transpoosin jos pystyy. Onnistuminen palautetaan aliohjelman nimessä.

13.9 Komentorivin parametrit (argv)

Esimerkiksi C- kielinen pääohjelma saa käyttöjärjestelmältä tällaisen taulukon kutsussa olleista argumenteista:

c-taul\argv.c - komentorivin parametrit

	#include <stdio.h>
	
	int main(int argc, char *argv[])
	{
	  int i;
	  printf("Argumentteja on %d kappaletta:\n",argc);
	  for (i=0; i<argc; i++)
	    printf("%d: %s\n",i,argv[i]);
	  return 0;
	}
Edellä nimet tulevat
	argc = argument count
	argv = argument vector
Kun ohjelma käännettäisiin (vaikkapa nimelle argv.exe) ja ajettaisiin komentoriviltä saattaisi tulostus olla seuraavan näköinen (MS- DOS - koneessa):
	C:\KURSSIT\CPP\MONISTE\ESIM\C-TAUL>argv kissa istuu puussa[RET]
	Argumentteja on 4 kappaletta:
	0: C:\KURSSIT\CPP\MONISTE\ESIM\C-TAUL\ARGV.EXE
	1: kissa
	2: istuu
	3: puussa
	C:\KURSSIT\CPP\MONISTE\ESIM\C-TAUL>_
	                                                        
argv-+             +----------------------------------------
     |   +-------->| C | : | \ | K | U | R | S | S | I | T |
     v   |         +----------------------------------------
   +---+ |         +-----------------------+    
0  | o-+-+ +------>| k | i | s | s | a |NUL|      argc = 4
   +---|   |       +-----------------------+ 
1  | o-+---+       +-----------------------+
   +---|     +---->| i | s | t | u | u |NUL|
2  | o-+-----+     +-----------------------+    
   +---|           +---------------------------+
3  | o-+---------->| p | u | u | s | s | a |NUL|
   +---|           +---------------------------+ 
4  | o-+-+                   ^
   +---+ |  argv[3][2]-------+ 
        ---
HUOM! Myös C++- ohjelma saa saman taulukon. Siis joukon osoittimia C- merkkijonoihin, ei mitään string taulukkoa

Tehtävä 13.133 Palindromi

Kirjoita ohjelma pali, jota kutsutaan komentoriviltä seuraavasti:
	C:\OMAT\OHJELMOI\VESA>pali kissa[RET]
	kissa EI ole palindromi!
	C:\OMAT\OHJELMOI\VESA>pali saippuakauppias[RET]
	saippuakauppias ON palindromi!
	C:\OMAT\OHJELMOI\VESA>_


previous next Title Contents Index