Merkkijonot ja C++ Antti-Juhani Kaijanaho. 5. maaliskuuta Vanhojen C++-kääntäjien käyttäjät, huomio! 2 Merkkijonojen perusteet

Koko: px
Aloita esitys sivulta:

Download "Merkkijonot ja C++ Antti-Juhani Kaijanaho. 5. maaliskuuta 2001. 1 Vanhojen C++-kääntäjien käyttäjät, huomio! 2 Merkkijonojen perusteet"

Transkriptio

1 Merkkijonot ja C++ Antti-Juhani Kaijanaho 5. maaliskuuta Vanhojen C++-kääntäjien käyttäjät, huomio! Tämä kirjoitus perustuu vuonna 1998 julkistettuun C++-kielen kansainväliseen ISO-standardiin. Vanhoilla kääntäjillä (esimerkiksi Ohjelmointi-kurssilla paljon käytetyllä Borlandin DOS-kääntäjällä) ohjeita täytyy hieman soveltaa: Kaikki std::-alkuliitteet jätetään pois (ts. std::string on vanhoilla kääntäjillä string). Uusillakin kääntäjillä tämä saadaan tehdä, jos ohjelman alkuun laitetaan taikasanat using namespace std;. -rivi pitää muuttaa muotoon #include <iostream.h>. Saattaa olla tarpeen asentaa Vesa Lappalaisen vstring-kirjasto. 2 Merkkijonojen perusteet Merkkijonoluokka std::string on konkreettinen tyyppi. Muuttujia voidaan sen pohjalta luoda täysin samalla tavalla kuin esimerkiksi double-muuttujia. Ainoa ero on se, mitä muuttuja sisältää: double sisältää yhden luvun, std::string merkkisarjan. Merkkijonoluokkaa käyttävien ohjelmien tulee sisällyttää (include) otsikkotiedosto <string>. Merkkijonomuuttuja voidaan alustaa merkkijonovakiolla: std::string tervehdys = "Hei maailma!"; std::cout << tervehdys << std::endl; Merkkijonolla on pituus, joka on jonossa olevien merkkien lukumäärä. Se on aina joko positiivinen tai nolla. Merkkijonon pituus saadaan käyttämällä metodia length: ohjelmanpätkä std::string mjono = "Lyhyt."; std::cout << mjono.length() << std::endl; tulostaa 6. Merkkijonoa voi indeksoida kuin taulukkoa ikään: mjono[0] on merkkijonon mjono ensimmäinen merkki ja mjono[mjono.length() - 1] on jonon viimeinen merkki. Merkkijonon 1

2 tervehdys 4 tervehdys.length() H e i! tervehdys[2] tervehdys[1] tervehdys[0] Kuva 1: Merkkijono-olion summittainen rakenne tervehdys[3] std::string tervehdys = "Hei!"; summittainen rakenne 1 kuvataan kuvassa 1 sivulla 2. Uloin laatikko kuvastaa itse merkkijono-oliota. Sen sisällä on pienemmissä laatikoissa kukin merkki omalla paikallaan. Vasemmanpuoleisin merkki, H, on nimeltään tervehdys[0] ja oikeanpuolimmaisin,!, on nimeltään tervehdys[3]. Lisäksi oliossa on tieto siitä, kuinka pitkä merkkijono on (tervehdys.length()), eli montako pientä laatikkoa ison laatikon sisällä on. Seuraava ohjelma laskee, montako pientä a-kirjainta lauseessa Alavilla mailla on hallan vaara on. std::string lause = "Alavilla mailla on hallan vaara."; int lkm = 0; for (int i = 0; i < lause.length(); i++) if (lause[i] == a ) ++lkm; std::cout << "Lauseessa " << lause << " on " << lkm << " pientä a-kirjainta.\n"; 3 Merkkijonon muuttaminen Merkkijonon loppuun voidaan lisätä toinen merkkijono käyttäen muualta tuttua +=-operaattoria: lauseiden std::string laulu = "Ma tu, Re"; laulu += " tu signore possente..."; jälkeen muuttujan laulu sisältönä on italialaisen oopperakappaleen Ma tu, Re tu signore possente... nimi. Tällä operaattorilla voi myös lisätä merkkijonon loppuun yksittäisiä merkkejä. Myös merkkijonon keskelle voidaan lisätä käyttämällä insert-metodia. Se ottaa kaksi parametria: indeksin muutettavaan merkkijonoon, jolla ilmaistaan, mihin kohtaan merkkijonoa lisäys tehdään; 1. Kuva on tietenkin vain hahmotelma: oikea std::string on huomattavasti monimutkaisempi. 2

3 sekä merkkijonon, joka lisätään. Metodi ei tuhoa mitään: insert jättää lisäyskohdan jälkeen tulleen tekstin paikalleen lisätyn tekstin jälkeen. Esimerkiksi erään elokuvan nimen voi korjata seuraavasti: std::string elokuva = "Viu-hah-taja"; elokuva.insert(3, "-hah"); Muuttujaan elokuva jää, kuten pitääkin, sana Viu-hah-hah-taja. Metodilla insert voidaan lisätä paitsi merkkijonoja myös yksittäisiä merkkejä. Tällöin parametreiksi on annettava indeksi, jonka kohdalle lisätään, luku, joka ilmaisee, kuinka monta kopiota merkistä lisätään, sekä merkki itse. Merkkijonosta voidaan poistaa osa metodilla erase. Se ottaa kaksi parametria: indeksin, jolla ilmaistaan ensimmäisen poistettavan merkin paikka merkkijonossa, sekä luvun, jonka verran merkkejä poistetaan. Niinpä std::string hmm = "autoksiko"; hmm.erase(4, 3); jättää muuttujan hmm arvoksi autoko. Merkkijonon osa voidaan korvata uudella merkkijonolla. Tähän käytetään metodia replace, jolle annetaan seuraavat parametrit: indeksi, josta korvattava alkaa; korvattavan osajonon pituus; sekä viimeiseksi korvaava merkkijono. Esimerkin std::string tinc = "There Is A Cabal"; tinc.replace(9, 1, "No"); jälkeen muuttuja tinc sisältää väitteen There Is No Cabal. Merkkijono voidaan myös korvata kokonaan toisella käyttämällä sijoitusoperaattoria =. 4 Uuden merkkijonon luominen Kuten edellä nähtiin, merkkijonomuuttuja voidaan alustaa merkkijonovakiolla. Muitakin tapoja alustaa merkkijono on. Merkkijonomuuttuja voidaan alustaa tyhjäksi. Tällöin merkkijonon pituus on nolla, ja kaikenlainen indeksointi on kiellettyä (kunnes merkkijonoa muutetaan). Tällainen alustus tehdään yksinkertaisesti esittelemällä kyseinen muuttuja ilman mitään erityisiä temppuja: std::string tyhjaakin_tyhjempi; // tyhjä merkkijono Jos ei haluta esitellä uutta muuttujaa, tyhjä merkkijono voidaan luoda lausekkeella std::string(); tästä voi olla hyötyä tietyissä tilanteissa (esim. aliohjelman oletusparametrin esittelyssä). Merkkijonomuuttujan esittelyssä voidaan jonolle määrätä koko. Tällöin se alustetaan sisältämään kyseinen määrä annettua merkkiä. Konstruktorin ensimmäinen parametri on haluttu lukumäärä ja toinen alustuksessa käytettävä merkki. Esimerkiksi ohjelma std::string viivat(75, - ); std::cout << viivat << std::endl; 3

4 tulostaa 75 peräkkäistä viivaa. Merkkijonomuuttuja voidaan alustaa toisen merkkijonon perusteella. Tällöin voidaan antaa myös indeksi, josta alustusarvo alkaa alustavassa jonossa, alustusarvon pituus. Merkkijonovakiolla alustettaessa voidaan antaa vain alustusarvon pituus; aluste alkaa aina alusta. std::string a = "qwertyuiop"; // a <- "qwertyuiop" std::string b(a, 6, 4); // b <- "uiop" std::string c(a, 6, 2); // c <- "ui" std::string d(c); // d <- "ui" std::string e("epäjärjestelmällistyttämättömyydellänsäkäänköhän", 14); // e <- "epäjärjestelmä" Merkkijonoja voidaan laskea yhteen eli hienosti sanoen konkatenoida. Tällöin muodostetaan uusi merkkijono, joka alkaa kuin vasen yhteenlaskettava ja joka päättyy kuin oikeanpuoleinen yhteenlaskettava. Kumpikaan merkkijono ei tässä muutu. Yhteen voidaan laskea useampikin kuin yksi merkkijono (myös merkkijonojen yhteenlasku on liitännäinen), ja seassa voi olla myös yksittäisiä merkkejä ja merkkijonovakioita. std::string h = "Hello"; std::string w = "World"; std::string hw = h + + w; Sen sijaan merkkijonovakioita ei voi laskea keskenään yhteen eikä se ole yleensä tarpeenkaan. Merkkijonosta voidaan erottaa osa omaksi merkkijonokseen. Tämä tapahtuu metodilla substr, eikä tässä alkuperäinen merkkijono muutu lainkaan. Metodille annetaan parametreina indeksi, josta haluttu osajono alkaa, sekä halutun osajonon pituus. Esimerkiksi ohjelmanpätkä std::string vakooja = "Carl Gustaf Gilbert Hamilton"; std::cout << vakooja.substr(5, 6) << std::endl; tulostaa sanan Gustaf. 5 Merkkijonosta etsiminen Merkkiä etsitään merkkijonosta metodien find ja rfind avulla. Kumpikin ottaa kaksi argumenttia: haettavan merkin sekä indeksin, josta haku aloitetaan. Indeksi voidaan jättää antamatta, jolloin etsintä kattaa koko jonon. Metodi find palauttaa pienimmän indeksin, jossa etsittävä merkki on, ja metodi rfind suurimman. Kumpikin palauttaa vakion std::string::npos (hyvin suuri (mahdollisesti negatiivinen?) luku, jota ei ole lupa käyttää indeksinä), mikäli merkki ei ole jonossa. Samat metodit find ja rfind ovat käytettävissä, kun halutaan etsiä merkkijonoa merkkijonosta. Tällöin metodit palauttavat indeksin löydetyn osajonon alkuun. Hakea voidaan lisäksi ensimmäistä sellaista indeksiä, jossa jokin annetuista merkeistä esiintyy (metodi find_first_of) tai ei esiinny (find_first_not_of), sekä vastaavaa viimeistä indeksiä (find_last_of ja find_last_not_of). Kaikki mainitut metodit ottavat parametrikseen merkkijonon, jonka sisältönä on ne merkit, joita etsitään. Ne myös ottavat vastaan toisen argumentin (mikäli se niille annetaan), joka tulkitaan indeksiksi samoin kuin metodien find ja rfind tapauksessa. Esimerkkiohjelma 4

5 void kerro_loyto(std::string::size_type n) if (n == std::string::npos) std::cout << "npos"; return; std::cout << n; std::string heinasuova = "Kokoonko kokon kokoon?"; kerro_loyto(heinasuova.find( k )); std::cout << ; kerro_loyto(heinasuova.rfind( k )); std::cout << ; kerro_loyto(heinasuova.find("koko")); std::cout << ; kerro_loyto(heinasuova.rfind("koko")); std::cout << std::endl; kerro_loyto(heinasuova.find_first_of("ko")); std::cout << ; kerro_loyto(heinasuova.find_first_not_of("ko")); std::cout << ; kerro_loyto(heinasuova.find_last_of("ko")); std::cout << ; kerro_loyto(heinasuova.find_last_not_of("ko")); std::cout << ; kerro_loyto(heinasuova.find(! )); std::cout << std::endl; tulostaa npos 6 Syöttö ja tulostus Merkkijonoja voi lukea muuttujaan käyttäen >>-operaattoria tai aliohjelmaa getline, jolle parametreiksi annetaan tiedostovirta ja merkkijonomuuttuja. Näistä jälkimmäinen on suositeltavampi: se lukee koko rivin muuttujaan, kun ensimmäinen lukee vain yhden sanan. Merkkijonot tulostetaan käyttäen <<-operaattoria. Esimerkkiohjelma std::cout << "Mikä on nimesi?\n"; std::string nimi; std::getline(std::cin, nimi); std::cout << "Terve, " << nimi << "\n"; tulostaa esimerkiksi seuraavaa: Mikä on nimesi? Antti-Juhani [RET] Terve, Antti-Juhani 5

6 7 Merkkijonot vs. primitiiviset merkkitaulukot C-kielestä C++:aan periytyi tapa käyttää primitiivisiä merkkitaulukkoja ja merkkiosoittimia merkkijonojen tilalla C-kielessä ei oikeita merkkijononja ole. Silloin tällöin on tarpeen käyttää hyväksi vanhaa koodia, joka käyttää merkkitaulukoita ja -osoittimia, ja täytyy osata vaihtaa näiden esitysmuotojen välillä. Merkkijonosta saadaan merkkitaulukko metodilla c_str, joka ei ota parametreja. Varo: metodin palauttama merkkitaulukko on olemassa vain niin kauan kuin merkkijonomuuttuja itse, ja sen arvo saattaa muuttuja, kun merkkijonomuuttujan arvo muuttuu. Merkkitaulukko käy sellaisenaan kaikkiin niihin paikkoihin, joihin kelpaa merkkijonovakio. Muulloin saattaa olla tarpeen tehdä muunnos eksplisiittisesti alustamalla merkkitaulukon avulla jokin merkkijonomuuttuja. Uusissa ohjelmissa erityisesti Ohjelmointi-kurssin harjoitustyössä on primitiivisten merkkitaulukoiden käyttöä syytä välttää. 8 Kirjallisuutta Bjarne Stroustrup: The C++ programming language. Third edition. Reading, MA: Addison-Wesley, (Suositeltavaa luettavaa muutenkin C++-kielestä kiinnostuneille.) Programming Languages C++. ISO/IEC 14882:1998. (Kallis ja vaikealukuinen C++-kielen määritelmä. Suositellaan lähinnä kielipoliiseille ja muille hörhöille;-) Copyright c 2000, 2001 Antti-Juhani Kaijanaho. All rights reserved. Tämä kirjoitelma on vapaa. Tämän kirjoitelman tekijä luopuu Tekijänoikeuslain 2 :n mukaan tekijänoikeuden hänelle tuottamasta yksinomaisesta oikeudesta määrätä teoksesta valmistamalla siitä kappaleita ja saattamalla se yleisön saataviin, muuttamattomana tai muutettuna, käännöksenä tai muunnelmana, toisessa kirjallisuus- tai taidelajissa taikka toista tekotapaa käyttäen. 6