Merkkijonot ja merkit

Pythonissa merkkijonotietotyyppi (str) kuuluu muuttumattomien (immutable) tietotyyppien joukkoon. Tämä tarkoittaa sitä, että merkkijonotyyppistä arvoa ei voi muuttaa:

def main():
    teksti = "abcdefg"
    print(teksti[3])  # Tulostuu: d
    teksti[3] = "X"   # VIRHE!
    print(teksti)

main()

Tämä ongelma vältettiin sillä, että aina kun merkkijonon sisältämää tekstiä haluttiin muokata, luotiin uusi merkkijonotyyppinen arvo, johon muutos oli huomioitu, ja nimettiin uusi arvo samalla nimellä kuin alkuperäinen:

def main():
    teksti = "abcdefg"
    print(teksti[3])  # Tulostuu: d
    teksti = teksti[:3] + "X" + teksti[4:]
    print(teksti)     # Tulostuu: abcXefg

main()

C++:ssa jo olemassa olevaa merkkijonoakin voi muuttaa (paitsi jos se on const string -tyyppinen vakio):

#include <iostream>
#include <string>   // Huomaa string-kirjasto

using namespace std;

int main() {
     string teksti = "abcdefg";
     cout << teksti.at(3) << endl;  // Tulostuu: d
     teksti.at(3) = 'X';            // Huomaa char-tyyppi
     cout << teksti << endl;        // Tulostuu: abcXefg
}

Sekä Pythonissa että C++:ssa merkkijonon merkkien indeksointi alkaa nollasta.

C++:ssa merkkijonon yksittäiset merkit ovat char-tyyppisiä, eli indeksointioperaatio teksti.at(indeksi) tuottaa aina char-tyyppisen arvon, tai jos indeksointioperaatio on sijoituksen kohteena, sijoitettavan arvon on oltava char.

Merkkijonoihin kohdistuvat nimetyt operaatiot ovat C++:ssa metodeja kuten Pythonissakin:

#include <iostream>
#include <string>

using namespace std;

int main() {
     string teksti = "One string to rule them all...";
     cout << teksti.length() << endl;
     cout << teksti.substr(4, 14) << endl;
     teksti.replace(4, 6, "thing");
     cout << teksti << endl;
}

Jotkut merkkijonoja käsittelevät operaatiot tuottavat paluuarvonaan lukuarvoja, esimerkiksi:

teksti.length();     // Merkkien lukumäärä
teksti.find("abc");  // Mistä kohdasta löytyy ensimmäinen "abc"?

Jos näitä merkkijonoihin liittyviä lukuarvoja (merkkien määrä, indeksit) haluaa tallettaa muuttujaan, sen tyypin on oltava string::size_type:

#include <iostream>
#include <string>

using namespace std;

int main() {
     string nimi = "";
     string::size_type apu = 0;

     cout << "Syötä nimesi: ";
     getline(cin, nimi);

     apu = nimi.length();
     cout << "Nimessa " << apu << " kirjainta" << endl;

     apu = nimi.find("nen");
     if ( apu == string::npos ) {
          cout << "Nimessa ei kirjainyhdistelmaa \"nen\"." << endl;
     } else {
          cout << "Yhdistelma \"nen\" loytyi kohdasta " << apu << endl;
     }
}

Huomaa, kuinka find palauttaa arvon string::npos, jos etsittyä merkkiyhdistelmää ei löydy.

Hyödyllisiä merkkijono-operaatioita

Metodit

Metodeja kutsutaan notaatiolla muuttuja.metodi(parametrit).

  • string::size_type length()

    Paluuarvo kertoo, kuinka monta merkkiä merkkijonossa on.

    string::size_type pituus = 0;
    pituus = teksti.length();
    
  • char at(indeksi)

    Palauttaa merkkijonon indeksinnen merkin. Jos at-funktion kutsu on sijoituksen kohteena, korvaa indeksinnen merkin.

    char merkki;
    merkki = teksti.at(5);
    teksti.at(2) = 'k';  // Huomaa char-tyyppi
    
  • string erase(indeksi)

    string erase(indeksi, pituus)

    Jos kutsussa on vain yksi parametri, tuhoaa merkkijonosta kaikki merkit kohdasta indeksi alkaen. Jos kutsussa myös parametri pituus, tuhoaa niin monta merkkiä eteenpäin.

    teksti.erase(4);
    teksti.erase(2, 5);
    
  • string::size_type find(etsittävä)

    string::size_type find(etsittävä, indeksi)

    Etsii merkkijonon alusta (tai kohdasta indeksi) alkaen ensimmäisen vastaantulevan etsittava-merkkijonon sijaintikohdan ja palauttaa kyseisen kohdan indeksin. Jos etsittavaa ei löydy, paluuarvo on vakio string::npos.

    string::size_type kohta = 0;
    kohta = teksti.find("abc", 5);
    if ( kohta == string::npos ) {
        // Ei löytynyt "abc":tä indeksistä 5 eteenpäin
    } else {
        // "abc" löytyi.
    }
    
  • string::size_type rfind(etsittävä)

    string::size_type rfind(etsittävä, indeksi)

    Kuten funktio find edellä, mutta etsii merkkijonon lopusta alkua kohti.

  • string substr(indeksi)

    string substr(indeksi, pituus)

    Palauttaa merkkijonon osan. Jos kutsussa on vain yksi parametri, paluuarvo on osamerkkijono kohdasta indeksi merkkijonon loppuun saakka. Jos annetaan myös parametri pituus, palautuu sen ilmoittama määrä merkkejä kohdasta indeksi eteenpäin.

    string teksti = "abcdefg";
    string tulos;
    tulos = teksti.substr(3);     // "defg"
    tulos = teksti.substr(4, 2);  // "ef"
    
  • string insert(indeksi, lisays)

    Lisätään kohdan indeksi eteen teksti lisays.

    string teksti = "abcd";
    teksti.insert(2, "xy");  // "abxycd"
    
  • string replace(indeksi, pituus, korvaaja)

    Korvataan merkkijonossa merkistä indeksi alkaen pituus merkkiä tekstillä korvaaja.

    string teksti = "ABCDEF";
    teksti.replace(1, 3, "xy");  // "AxyEF"
    

Operaattorit

  • teksti1 == teksti2

    teksti1 != teksti2

    teksti1 < teksti2

    teksti1 > teksti2

    teksti1 <= teksti2

    teksti1 >= teksti2

    Vertailuoperaattoreilla voi vertailla merkkijonojen suhdetta toisiinsa. Operaattorit, joissa on mukana pienempi- tai suurempi kuin -operaatio, vertailevat nk. leksikaalijärjestystä, joka on merkkijonojen tapauksessa lähes sama asia kuin aakkosjärjestys.

  • teksti1 + teksti2

    Liimataan (katenoidaan) kaksi merkkijonoa yhdeksi.

    string testi1 = "Qt";
    string testi2 = "Creator";
    string tulos;
    tulos = testi1 + " " + testi2;  // "Qt Creator"
    
  • merkkijono += lisays

    Liimaa (katenoi) merkkijonon perään merkkijonon tai merkin lisays.

    string tulos = "Qt";
    tulos += ' ';        // "Qt "
    tulos += "Creator";  // "Qt Creator"
    

Funktiot

  • getline(virta, rivi)

    Luetaan tiedostosta tai näppäimistölä (cin) yksi rivi tekstiä ja talletetaan se merkkijonoviiteparametriin rivi.

  • int stoi(teksti)

    Muutetaan parametri teksti vastaavaksi kokonaisluvuksi.

    string numeerinen_teksti = "123";
    int luku;
    luku = stoi(numeerinen_teksti);  // 123
    
  • double stod(teksti)

    Vastaava kuin stoi edellä, mutta muuttaa merkkijonon reaaliluvuksi.

    string numeerinen_teksti = "123.456";
    double luku;
    luku = stod(numeerinen_teksti);  // 123.456
    

Edellisten stoi- ja stod-funktioiden ongelmana on se, että ne eivät tunnista virheellistä syötettä, jos sen alku näyttää oikean tyyppiseltä luvulta. Esimerkiksi "123abc" kelpaa stoi-funktiolle ja funktio palauttaa 123. Virhettä ei voi tähän mennessä opituilla tiedoilla huomata helposti.

Merkit

Pythonissa merkkejä (kirjaimia) käsiteltiin kuten yhden merkin mittaisia merkkijonoa.

C++:n merkkityyppi char on oikeastaan kokonaislukutyyppi. Tätä tyyppiä oleva muuttuja sisältää ASCII-arvon:

cout << "Syötä jokin merkki: ";
char merkki = ' ';
cin >> merkki;
int merkin_ascii_arvo = static_cast< int >( merkki );
cout << "Merkin " << merkki << " ASCII-arvo on " << merkin_ascii_arvo << endl;

Se, että merkki on oikeastaan kokonaisluku, mahdollistaa esimerkiksi merkkimuuttujille suoritettavat laskutoimitukset:

for( char kirjain = 'a'; kirjain < 'z'; ++kirjain ){
    cout << kirjain;
}
cout << endl;

Tämä voi olla hyödyllistä, jos jostain syystä haluat käydä ohjelmassa läpi kaikki kirjaimet aakkosjärjestyksessä.

Kirjasto cctype

C++:ssa voi käyttää kirjastoa cctype (muista include), joka sisältää mm. seuraavat funktiot:

  • islower(merkki) tarkastaa, onko kyseessä pieni kirjain (paluuarvo bool)
  • isupper(merkki) tarkastaa, onko kyseessä iso kirjain
  • isdigit(merkki) tarkastaa, onko kyseessä numeromerkki
  • tolower(merkki) muuttaa ison kirjaimen vastaavaksi pieneksi kirjaimeksi
  • toupper(merkki) muuttaa pienen kirjaimen vastaavaksi isoksi kirjaimeksi.

Lisää cctype-kirjaston funktioita löydät lukuisista C++-kirjastoreferensseistä Internetistä.