Tie koodariksi

C++-ohjelmointi

Luku 10: Merkit ja merkkijonot

Käsitteitä

Merkki (character) on yksittäinen symboli, kuten numero, kirjain tai välimerkki. Merkki kirjoitetaan heittomerkkien sisään (esimerkiksi 'a').

Merkkijono (string) on jono peräkkäin olevia merkkejä ja vastaa tekstiä. Merkkijonon merkit kirjoitetaan lainausmerkkien sisään (esimerkiksi "apina").

Merkit ja koodit

C++:ssa tietotyyppi char on yleensä 8-bittinen tyyppi, joka vastaa yhtä merkkiä. Esimerkiksi seuraava ohjelma määrittelee merkkimuuttujan ja tulostaa sen:
char x = 'a';
cout << x << "\n"; // a
Jokaisella merkillä on kaksi luonnetta: merkkiesitys ja merkkikoodi. Merkkiesitys tarkoittaa, miltä merkki näyttää, ja merkkikoodi on merkkiä vastaava kokonaisluku.

Merkin merkkikoodin voi tulostaa muuttamalla tyypiksi int. Esimerkiksi merkin 'a' koodi on 97, minkä voi havaita seuraavasti:

cout << (int)'a' << "\n"; // 97
Vastaavasti merkkikoodia vastaavan merkin voi tulostaa muuttamalla tyypiksi char:
cout << (char)97 << "\n"; // a
Merkkiesitys on itse asiassa vaihtoehtoinen tapa antaa kokonaisluku koodissa. Erona on, että char-arvosta tulostetaan merkkiesitys ja int-arvosta tulostetaan merkkikoodi. Seuraava koodi havainnollistaa asiaa:
int x = 'a'-5;
cout << x << "\n"; // 92
Koodissa 'a' tarkoittaa lukua 97, joten muuttujan x arvoksi tulee 97–5 = 92.

Merkistö

Merkistö (character set) määrittää, mitä merkkejä on käytettävissä ja mitkä ovat niiden merkkikoodit. Merkistöt vaihtelevat eri järjestelmissä, mutta yleensä perustana on ASCII-merkistö, jonka tavalliset merkit ovat:
32048@64P80`96p112
!33149A65Q81a97q113
"34250B66R82b98r114
#35351C67S83c99s115
$36452D68T84d100t116
%37553E69U85e101u117
&38654F70V86f102v118
'39755G71W87g103w119
(40856H72X88h104x120
)41957I73Y89i105y121
*42:58J74Z90j106z122
+43;59K75[91k107{123
,44<60L76\92l108|124
-45=61M77]93m109}125
.46>62N78^94n110~126
/47?63O79_95o111
Ennen koodia 32 merkistössä on erikoismerkkejä, kuten rivinvaihto '\n', jonka koodi on 10.

Kätevä ominaisuus ASCII-merkistössä on, että numerot 0–9 ovat peräkkäin, samoin kirjaimet a–z ja A–Z. Tämän ansiosta voi tehdä vaikkapa seuraavan koodin:

for (char x = 'a'; x <= 'z'; x++) {
    cout << x;
}
cout << "\n";
Koodin tulostus on seuraava:
abcdefghijklmnopqrstuvwxyz

Merkkijonot

C++:n merkkijonotyyppi on string. Seuraava koodi luo ja tulostaa merkkijonon:
string s = "apina";
cout << s << "\n"; // apina
Voimme myös luoda merkkijonon antamalla pituuden ja toistettavan merkin:
string s(5,'a');
cout << s << "\n"; // aaaaa
Merkkijonoja voi yhdistää toisiinsa +-operaattorin avulla:
string a = "esi";
string b = "merkki";
cout << a+b << "\n"; // esimerkki
Funktio size antaa merkkien määrän:
string s = "apina";
cout << s.size() << "\n"; // 5

Merkkijono taulukkona

Merkkijonoa voi ajatella taulukkona, jonka jokainen alkio on merkki. Kuten taulukossa, merkit on numeroitu nollasta alkaen ja niihin viitataan []-merkinnällä.

string s = "apina";
cout << s[0] << "\n"; // a
cout << s[1] << "\n"; // p
Merkkijonon sisältöä voi myös muuttaa:
string s = "apina";
s[3] = 'l';
cout << s << "\n"; // apila
Seuraava koodi käy puolestaan läpi merkkijonon merkit ja tulostaa niiden koodit:
string s = "apina";
for (int i = 0; i < s.size(); i++) {
    cout << i << " " << s[i] << " " << (int)s[i] << "\n";
}
Koodin tulostus on seuraava:
0 a 97
1 p 112
2 i 105
3 n 110
4 a 97

Muita funktioita

Funktio substr(k) erottaa merkkijonon loppuosan kohdasta k alkaen. Funktio substr(k,p) puolestaan erottaa p merkkiä kohdasta k alkaen. Jos merkkijono loppuu kesken, merkkejä tulee vähemmän.
string s = "cembalo";
cout << s.substr(2) << "\n"; // mbalo
cout << s.substr(2,3) << "\n"; // mba
cout << s.substr(2,10) << "\n"; // mbalo
Funktio find(x) etsii ensimmäisen kohdan, josta alkaa osajono x. Funktio find(x,k) etsii puolestaan ensimmäisen tällaisen kohdan, joka on k tai suurempi.
string s = "banaani";
cout << s.find("naa") << "\n"; // 2
cout << s.find("an") << "\n"; // 1
cout << s.find("an",2) << "\n"; // 4
Jos haettua kohtaa ei ole, funktio palauttaa arvon npos, joka vastaa lukua -1.
string s = "banaani";
if (s.find("ap") == -1) {
    // ei löytynyt
}

Merkkijonot ja luvut

Funktio to_string muuttaa luvun merkkijonoksi:
int x = 123;
string s = to_string(x);
Toiseen suuntaan muunnoksessa funktion nimi riippuu halutusta lukutyypistä. Esimerkiksi funktiot stoi ja stod muuttavat merkkijonon sisällön int- ja double-arvoksi.
string s = "123";
int a = stoi(s);
double b = stod(s);

Entä ääkköset?

Aiemmin esitetty ASCII-merkistö ei sisällä ääkkösiä eikä monia muitakaan merkkejä. Mitä jos haluaa kuitenkin käyttää näitä merkkejä ohjelmassa?

Vaikeutena on, että näiden merkkien sisäiseen esittämiseen on monia eri tapoja ja asia riippuu järjestelmästä. Nykyään yleinen tapa on UTF-8, jossa ASCII-merkistön ulkopuoliset merkit muodostuvat sisäisesti useammasta merkistä. Seuraava koodi havainnollistaa asiaa:

string s = "pöllö";
cout << s << "\n";
cout << s.size() << "\n";
for (int i = 0; i < s.size(); i++) {
    cout << i << " " << s[i] << " " << (int)s[i] << "\n";
}
Koska merkki ö ei kuulu ASCII-merkistöön, se tallennetaan kahtena merkkinä, minkä vuoksi merkkijonon pituus on 7 eikä 5. Jos merkkejä koettaa tulostaa, ö:n sijasta tulee kaksi erikoismerkkiä, joita ei voi tulostaa.
pöllö
7
0 p 112
1 � -61
2 � -74
3 l 108
4 l 108
5 � -61
6 � -74