Merkkijonot C-kielessä merkkijono on taulukko, jonka alkiot ovat char -tyyppiä. Taulukon viimeiseksi merkiksi tulee merkki '\0', joka ilmaisee merkkijonon loppumisen. Merkkijono määritellään kuten muutkin taulukot, mutta tilaa on varattava yksi ylimääräinen paikka lopetusmerkille: char mjono [9]; /* tallettaa max. 8 kpl merkkejä! */ 341
Merkkijono voidaan alustaa merkkijonovakiolla, jolloin kääntäjä laskee taulukon koon ja tallettaa merkkijonon merkit taulukkoon: char mjono [] ="Eka arvo"; 0 1 2 3 4 5 6 7 8 E k a a r v o \0 Merkkijonon lopetusmerkiksi lisätään automaattisesti '\0' eli NUL-merkki, jonka avulla tunnistetaan jonon loppu. Lopetusmerkillekin on varattava tilaa! 342
Omissa funktioissa koodaajan tulee huolehtia NUL-merkin lisäämisen merkkijonon loppuun. Jos alustus tehdään seuraavasti: char mjono [8] ="Eka arvo"; 0 1 2 3 4 5 6 7 E k a a r v o Tässä tilanteessa NUL-merkki talletetaan taulukon ulkopuolelle, mahdollisesti jonkin toisen muuttujan sisällöksi. 343
n:n kokoisessa merkkijonotaulukossa voidaan esittää merkkijonoja, joiden pituus vaihtelee välillä 0... i -1, koska tilaa on varattava myös lopetusmerkille. NUL-merkin jälkeisillä alkioiden arvoilla ei ole merkitystä, merkkien ei tarvitse täyttää koko taulukkoa. char merkkijono[10] = "ABCDEF"; 0 1 2 3 4 5 6 7 8 9 'A' 'B' 'C' 'D' 'E' 'F' '\0' 344
Merkkivakiot ja merkkijonovakiot eroavat toisistaan siten, että merkkivakiot kirjoitetaan heittomerkkien väliin ja merkkijonovakiot kirjoitetaan lainausmerkkien väliin. Merkkijonojen ja yksittäisen merkin esitys tietokoneen muistissa on erilainen. 1. char merkki = "a"; 2. char merkkijono[10] = {'a'}; Esimerkkirivillä 1 yritetään tallettaa kahta merkkiä char - tyyppiseen muuttujaan, sillä merkkijono "a" koostuu kahdesta merkistä 'a' ja '\0'. Esimerkkirivillä 2 taulukko merkkijono[10] ei esitä merkkijonoa, koska taulukossa ei ole merkkijono lopetusmerkkiä '\0'! 345
Merkkijonotaulukko toteutetaan kaksiulotteisella char -taulukolla. char merkkijonoja [10][31]; Taulukon ensimmäinen indeksi 10 kertoo montako merkkijonoa taulukkoon voi tallettaa ja toinen indeksi 31 ilmaisee, että rivin merkkijonon pituus voi olla enintään 30 merkkiä, jotta lopetusmerkki mahtuu mukaan. 346
Merkkijonotaulukko voidaan määritellä ja alustaa seuraavasti. char kuukaudet[12][10] = {"Tammikuu", "Helmikuu", "Maaliskuu", "Huhtikuu", "Toukokuu", "Kesäkuu", "Heinäkuu", "Elokuu", "Syyskuu", "Lokakuu", "Marraskuu", "Joulukuu"}; 347
Taulukko näyttäisi tällaiselta: T a m m i k u u \0 H e l m i k u u \0 M a a l i s k u u \0 H h t i k u u \0 T o u k o k u u \0 K e s ä k u u \0 H e i n ä k u u \0 E l o k u u \0 S y y s k u u \0 L o k a k u u \0 M a r r a s k u u \0 J o u l u k u u \0 348
Merkkijonojen käsittely Merkkijonojen käsittelyyn printf - ja scanf -funktioiden avulla on oma kentänmäärittely %s. printf -funktiolla tulostettaessa voidaan määrittää myös tulostuskentän leveys, kirjoittamalla numero % ja s merkkien väliin %10s. Jos tulostettava merkkijono on lyhyempi kuin kentän leveys tasataan tulostus tulostuskentän oikeaan reunaan. Jos halutaan tasata tulostuskentän vasempaan reunaan käytetään kentän leveyden määrityksen edessä - merkkiä %-10s. 349
Jos tulostettava teksti on pitempi kuin kentänleveysmäärityksessä on määritelty, kentänleveysmääritys menettää merkityksensä. #include <stdio.h> int main(void){ char nimi1[21] = "Kalle"; char nimi2[21] = "Uuno Välkky"; /* tulostetaan merkkijono "nimi1" selitysteksteineen nimen sisältö tasattuna tulostuskentän oikeaan reunaan */ printf("nimi1 sisältää \"%15s\" nimen\n", nimi1); 350
} /* tulostetaan merkkijono "nimi1" selitysteksteineen nimen sisältö tasattuna tulostuskentän vasempaan reunaan */ printf("nimi1 sisältää \"%-15s\" nimen\n", nimi1); /* tulostetaan merkkijono "nimi2" siten että tulostuskentän leveys on liian lyhyt */ printf("nimi2 sisältää \"%5s\" nimen\n", nimi2); printf("\n\n"); return 0; 351
Funktiolla scanf voidaan lukea koko merkkijono, jolloin parametrina välitetään merkkijonomuuttujan ensimmäisen alkion osoite joka on sama kuin merkkijonomuuttujan nimi. 352
#include <stdio.h> int main(void){ } char nimi1[21]; printf("syötä merkkijono >"); scanf("%s", nimi1); /* huomaa ei &-merkkiä nimi1:n edessä */ printf("\nsyötit merkkijonon \"%s\"\n\n", nimi1); return 0; scanf lukee merkkijonon taulukkoon nimi1 ja lisää automaattisesti lopetusmerkin merkkijonon perään. 353
Funktiolla scanf ei voi lukea merkkijonoja, jotka sisältävät välilyöntejä. Funktio scanf lukee merkkijonomuuttujaan vain välilyöntimerkkiin asti olevat merkit ja jättää loput merkit näppäimistöpuskuriin. Tämä saattaa joskus aiheuttaa ohjelmassa kummallista käytöstä. 354
#include <stdio.h> int main(void){ } char nimi1[21]; char nimi2[21]; printf("syötä 1. merkkijono >"); scanf("%s", nimi1); printf("\n\nsyötä 2. merkkijono >"); scanf("%s", nimi2); printf("\n\nsyötit 1. merkkijonoksi \"%s\"\n\n", nimi1); printf("\nsyötit 2. merkkijonoksi \"%s\"\n\n", nimi2); return 0; 355
356
Funktion scanf sijasta voidaan käyttää funktiota fgets, joka löytyy kirjastosta string.h. Funktiolle täytyy antaa kolme parametria: 1.Merkkijonomuuttujan nimi, jonne fgets tallettaa lukemansa merkkijonon 2.Merkkijonomuuttujan maksimikoko 3.Tiedosto, josta luetaan. Jos luetaan näppäimistöltä, nimeksi laitetaan stdin. 357
fgets lukee merkkejä kunnes luetaan enter tai on luettu merkkijonon koko -1 merkkiä. Jos merkkijonomuuttujan koko on suurempi kuin luetun merkkijonon koko talletetaan enterin painallus myös merkkijonomuuttujan sisällöksi ja vasta sen jälkeen tulee merkkijonon loppumerkki. Jos syötetty merkkijono on saman mittainen kuin merkkijonomuuttujan koko-1, merkkijonoon ei talleteta enteriä, vaan se jää näppäimistöpuskuriin stdin. Tämä saattaa jälleen aiheuttaa ohjemassa outoa toimintaa. Esimerkkiohjelma fgets - funktion käytöstä, jossa on huomioitu edellä mainitut tilanteet. 358
#include <stdio.h> #include <string.h> #define JONOPIT 80 void luepois(void); /* näppäimistöpuskuin tyhjentäjä */ int main ( void ) { char nimi[jonopit]; char pois; /* luetaan enintään 79 merkkiä (JONOPIT-1) */ printf("anna nimesi >"); fgets( nimi, JONOPIT, stdin ); 359
/* jos syötetty merkkijono oli lyhyempi kuin mitä nimimuuttujaan mahtuu, on '\n' eli enter/return */ if( nimi[strlen(nimi)-1] == \n ) nimi[strlen(nimi)-1] = \0 ; /* loppumerkki oikeaan paikkaan */ else luepois(); /* tyhjentää lukupuskurin */ printf(" OK, nimesi on %s\n", nimi); } return ( 0 ); void luepois(void){ while( getc(stdin)!= '\n'); } 360
Merkkijonoja voidaan tulostaa myös funktiolla puts char nimi[] = "Matti"; puts(nimi); /* tulostaa merkkijonon nimi sisällön */ 361
Ohjelmissa täytyy ottaa aina huomioon se, että luettava merkkijono mahtuu sille varattuun tilaan. Jos luettava merkkijono on pitempi kuin sille varattu tila, tallettuvat loput merkit mahdollisesti jonkin toisen muuttujan muistialueelle (puskuriylivuoto). Merkkijonojen käsittelyssä usein tarvittava apufunktio on strlen, joka palauttaa merkkijonoon kuuluvien merkkien määrän, kuitenkaan jonon loppumerkkiä '\0' ei lasketa pituuteen mukaan! 362
#include <stdio.h> #include <string.h> int main ( void ) { char nimi[] = "Matti"; int pituus; } pituus = strlen(nimi); printf("merkkijonon \"%s\" pituus on %d\n\n", nimi, pituus); return 0; 363
364
Merkkijonojen käsittelyfunktioita Merkkijonojen käsittelyfunktioita löytyy kirjastosta string.h Merkkijonojen kopiointi Merkkijonoja ei voi kopioida sijoitusoperaattoria = käyttämällä. Sijoitusta voidaan käyttää vain alustuksessa merkkijonon määrittelyn yhteydessä, ohjelman muissa osissa merkkijonojen sijoitukset on tehtävä erillisen kopiointifunktion strcpy avulla. strcpy ( kopio, kopioitava ); 365
jossa kopio ja kopioitava ovat kahden ennalta määritellyn merkkijonomuuttujan nimet Funktio kopioi jonon kopioitava sisällön jonon kopio sisällöksi NUL-merkki ('\0') mukaanlukien olettaen, että jonossa kopio on riittävästi tilaa. Funktiolla strncpy kopioidaan myös merkkijono toiseen merkkijonoon, se saa kolmanneksi parametriksi kopioitavien merkkien lukumäärän. strncpy ( kopio, kopioitava, lukumäärä ); HUOMIO: Funktio ei lisää merkkijonon lopetusmerkkiä! 366
char mjono1[10]; char mjono2[] = "abcdefg"; /*Kopioidaan 3 ensimmäistä merkkiä mjono2:sta mjono1:een */ strncpy ( mjono1, mjono2, 3 ); mjono[3] = '\0'; /* lisätään NUL-merkki */ Tilanteen huomioimatta jättäminen aiheuttaa ohjelmassa outoa käyttäytymistä, jonka selvittäminen voi olla aloittelijalle työlästä. 367
Merkkijonon loppuun voidaan lisätä toinen merkkijono funktiolla strcat. #include <stdio.h> #include <string.h> int main ( void ) { char nimi[25] = "C-ohjelmointi"; char k[] = " -kurssi"; printf("\nnimi-muuttuja: %s\n", nimi); strcat( nimi, k ); printf("\nnimi-muuttuja lisäämisen jälkeen: %s\n\n", nimi); } return 0; 368
369
Merkkijonojen vertailu tehdään funktiolla strcmp. strcmp (mjono1, mjono2); Funktio tarkistaa edeltääkö mjono1 mjono2:ta aakkosjärjestyksessä. Vertailua tehdään merkki merkiltä kunnes ero löytyy tai merkkijonot loppuvat. Jos eroa ei löydy palauttaa funktio arvon 0 eli merkkijonot ovat samanlaiset. Jos mjono1 edeltää mjono2:ta, palauttaa funktio negatiivisen kokonaisluvun (mjono1 < mjono2). Jos mjono2 edeltää mjono1:tä funktio palauttaa positiivisen kokonaisluvun (mjono1 > mjono2). 370
Funktio strcmp vertaa oikein vain merkkijonoja, jotka eivät sisällä skandeja (å, Å, ä, Ä, ö, Ö). Esimerkkiohjelma ilmoittaa saamiensa nimien aakkosjärjestyksen: #include <stdio.h> #include <string.h> void luepois( void ); int main ( void ) { char nimi1[ 21 ]; char nimi2[ 21 ]; printf("anna nimi ( nimi saa olla max 20 merkkiä): "); fgets ( nimi1, 21, stdin ); 371
if( nimi1[strlen(nimi1)-1] == \n ) nimi1[strlen(nimi1)-1] = \0 ; else luepois(); /* tyhjentää lukupuskurin */ printf("anna nimi ( nimi saa olla max 20 merkkiä): "); fgets ( nimi2, 21, stdin ); if( nimi[strlen(nimi2)-1] == \n ) nimi2[strlen(nimi2)-1] = \0 ; else luepois(); /* tyhjentää lukupuskurin */ 372
if( strcmp ( nimi1, nimi2) == 0 ) { printf("nimet ovat samat"); }else{ if(strcmp ( nimi1, nimi2) < 0 ) { printf("%s on aakkosissa aikaisemmin kuin %s\n", nimi1, nimi2); }else{ printf("%s on aakkosissa aikaisemmin kuin %s\n", nimi2, nimi1); } } } /* main */ void luepois( void ){ while( getc(stdin)!= '\n' ); } 373
Merkkijonon voi pilkkoa osamerkkijonoksi jonkin erotinmerkin tai erotinmerkkijonon avulla käyttämällä funktiota strtok. char * strtok(merkkijono, erotinmerkkijono); Esimerkiksi päiväyksenn lukeminen muodossa "9.9.2010". 374
#include <stdio.h> #include <string.h> void luepois( void ); int main ( void ) { char paivays[13]; char * osamerkkijono; char erotinmerkkijno[] = "."; char selitystekstit[3][11] ={"Paiva: ","Kuukausi: ", "Vuosi: "}; int i; /* valitsee selitystekstin */ printf("anna paivays muodossa \"paiva.kuukausi.vuosi\" > "); fgets ( paivays, 13, stdin ); 375
if( paivays[strlen(paivays)-1] == '\n') paivays[strlen(paivays)-1] = '\0'; else luepois(); /* tyhjentää lukupuskurin */ osamerkkijono = strtok( paivays, erotinmerkkijno); i = 0; while( NULL!= osamerkkijono ){ printf("%10s %s\n", selitystekstit[i], osamerkkijono); /* strtok "muistaa" merkkijonon, NULL kertoo että jatka entisen merkkijono käsittelyä */ osamerkkijono = strtok( NULL, erotinmerkkijno); i = i+1; } } /* main */ 376
void luepois( void ){ while( getc(stdin)!= '\n' ); } 377