* 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
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];
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.
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[.
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!
/* 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};
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:
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!
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:
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)
{ 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?
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:
#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: raMiksikö? 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.
memchr memcmp memcpy memmove memset strcat strchr strcmp strcpy strcspn strerror strlen strncat strncmp strncpy strpbrk strrchr strspn strstr strtok strcoll strxfrmLisäksi esim. Turbo- C:ssä on:
memccpy memicmp movedata movmem setmem stpcpy strcmpi strdup _strerror stricmp strlwr strncmpi strnicmp strnset strrev strset struprKirjaston 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.
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 NULEdellä 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 NULVikana 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
+-----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 NULja 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);
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 ! NULFunktiossa 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));
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!"
Ä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:
#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; }
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!
#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; }
/****************************************************************************/ /* ** 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 */
/****************************************************************************/ /* 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 */
#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 KunaHuomattakoon, 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!
#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.
#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.
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; }
laji <- "Kissa" nro <- 5 nimi <- "Miuku" paino <- 3 ika <- 5
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 } }
#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; }
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.
#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.
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 +---------------+
#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.
#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 vectorKun 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
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>_