CSES - C++:n perusteet

C++ ja Java perustuvat molemmat C-kieleen. Tämä helpottaa paljon C++:n oppimista, jos osaa jo ennestään Javaa.

Ensimmäinen ohjelma

Tässä on ensimmäinen C++-ohjelmamme (testi.cpp):

#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        if (i%2 == 0) cout << i << " ";
    }
    cout << "\n";
}

Ohjelma kirjoitetaan main-funktion sisään. Tämä ohjelma kysyy käyttäjältä luvun n ja tulostaa sitten parilliset luvut väliltä 1 \dots n.

Voit kääntää ja suorittaa ohjelman seuraavasti Linuxissa:

$ g++ testi.cpp -o testi -O2
$ ./testi
20
2 4 6 8 10 12 14 16 18 20

Merkintä -o antaa käännetyn tiedoston nimen. Merkintä -O2 tarkoittaa, että käännös tehdään optimoidusti, ja se tehostaa usein huomattavasti ohjelmaa.

Muuttujat

C++:n tärkeimmät muuttujatyypit ovat:

  • char: merkki
  • int: kokonaisluku (yleensä 32-bittinen)
  • long long: kokonaisluku (yleensä 64-bittinen)
  • double: liukuluku (yleensä 64-bittinen)
  • bool: totuusarvo (true tai false)
  • string: merkkijono

Muuttujia käytetään samalla tavalla kuin Javassa.

Syöte ja tuloste

C++:ssä voi lukea syötettä cin-virrasta ja tulostaa tekstiä cout-virtaan. Nämä ovat käytettävissä, kun ohjelman alussa on rivi #include <iostream>.

Syötteen luvussa muuttujan tyyppi vaikuttaa lukemiseen. Esimerkiksi seuraava ohjelma lukee ensin kaksi kokonaislukua ja sitten yhden merkkijonon:

int a, b;
cin >> a >> b;
string s;
cin >> s;

Syötteessä olevien lukujen ja merkkijonojen välissä voi olla vapaasti tyhjää tilaa (välilyöntejä ja rivinvaihtoja), ja nämä jätetään huomiotta.

Tekstin tulostamisessa rivinvaihdon saa merkinnän \n avulla. Esimerkiksi seuraava koodi tulostaa kolme riviä tekstiä:

cout << "apina\nbanaani\ncembalo\n";

Ehdot ja silmukat

Ehdot (if) ja silmukat (for, while) toimivat samalla tavalla kuin Javassa.

Merkkijonot

Merkkijonon tyyppi on string. Esimerkiksi seuraava koodi tulostaa merkkijonon ja sen pituuden:

string s = "apina";
cout << s << "\n"; // apina
cout << s.size() << "\n"; // 5

Merkkijonon merkkeihin pääsee käsiksi []-syntaksin avulla. Merkkejä voi myös muuttaa (toisin kuin Javassa).

string s = "apina";
cout << s[0] << "\n"; // a
s[3] = 'l';
cout << s << "\n"; // apila

Merkkijonon loppuun lisääminen on nopeaa (toisin kuin Javassa). Esimerkiksi seuraava koodi muodostaa merkkijonon, jossa on miljoona a:ta:

string s;
for (int i = 1; i <= 1000000; i++) {
    s += "a";
}

Taulukot

Taulukot toimivat samalla tavalla kuin Javassa, mutta määrittely eroaa hieman.

Seuraava koodi luo taulukon, jossa on 100 alkiota. Jokaisen alkion arvo on aluksi 0.

int a[100] = {0};
a[5] = 123;

Seuraava koodi luo kaksiulotteisen taulukon:

int b[5][5] = {0};
b[2][3] = 123;

Funktiot

C++:n funktiot toimivat lähes samalla tavalla kuin Javan metodit. Kuitenkin syntaksi on tiiviimpi (ei tarvitse kirjoittaa esim. static):

#include <iostream>

using namespace std;

void testi() {
    cout << "Moikka!\n";
}

int summa(int a, int b) {
    return a+b;
}

int main() {
    testi();
    cout << summa(1,2) << "\n"; // 3
}

Koodin alussa määritellyt muuttujat ovat kaikkien funktioiden käytössä:

#include <iostream>

using namespace std;

int x;

void testi() {
    x++;
}

int main() {
    x = 3;
    testi();
    cout << x << "\n"; // 4
}

Muistinhallinta

Muuttujan tallennustapa riippuu siitä, onko se määritelty ohjelman alussa vain funktion sisällä. Ohjelman alussa määritelty muuttuja tallennetaan kekomuistiin (heap) ja sen arvoksi tulee automaattisesti 0. Funktion sisällä määritelty muuttuja tallennetaan pinomuistiin (stack) ja sen arvo voi olla mitä tahansa muistissa olevaa tietoa.

Esimerkiksi seuraava ohjelma voi tulostaa minkä tahansa luvun:

int main() {
    int x;
    cout << x << "\n"; // ???
}

Kuitenkin muuttujan arvon voi antaa heti määrittelyssä:

int main() {
    int x = 5;
    cout << x << "\n"; // 5
}

Seuraava ohjelma puolestaan tulostaa aina luvun 0:

int x;

int main() {
    cout << x << "\n"; // 0
}

Taulukot tallennetaan muistiin vastaavasti riippuen niiden määrittelypaikasta. Tällä kurssilla hyvä tapa on määritellä taulukot aina ohjelman alussa, jolloin jokaisen alkion alkuarvoksi tulee 0 ja taulukko menee kekomuistiin, johon mahtuu suurikin taulukko (pinomuistin koko on oletuksena melko pieni).

Huomaa myös, että koska tehtävissä annetaan suurin syötteen koko, taulukon voi määritellä suoraan niin suureksi, jolloin se toimii myös pienemmillä syötteillä. Esimerkiksi jos 1 \le n \le 10^6, taulukon voi määritellä näin:

int x[1000001];

int main() {
    //
}

C++:n sudenkuopat

C++ on hyvin tehokas kieli, mutta tämän vastapainona se olettaa, että ohjelmoija tietää, mitä tekee. Jos koodissa on virhe, tästä ei välttämättä tule mitään ilmoitusta vaan koodi vain toimii oudosti.

Esimerkiksi seuraavassa koodissa on bugi:

int a[5];
a[5] = 123;

Koodi luo taulukon, jossa on kohdat 0 \dots 4, mutta se tallentaa luvun kohtaan 5. C++ ei ilmoita tästä vaan tallentaa luvun taulukon jälkeiseen kohtaan muistissa, jossa voi olla esimerkiksi toiselle muuttujalle varattua tilaa.

Monia bugeja voi kuitenkin löytää antamalla lisäasetuksia käännöksessä. Tässä on joitakin hyödyllisiä asetuksia:

g++ -Wall -Wextra -Wshadow -fsanitize=undefined testi.cpp -o testi -O2

Merkinnät muotoa -W antavat varoituksia käännöksen aikana, jos koodissa on jotain epäilyttävää. Merkintä -fsanitize=undefined puolestaan huomaa esim. taulukon ulkopuolelle viittaamisen ohjelman suorituksen aikana. Tämä merkintä kuitenkin hidastaa ohjelman suoritusta.

Lisämateriaali

Tie koodariksi -sivustolla on C++-ohjelmointia käsittelevä materiaali, johon pääset tästä. Tämä materiaali sopii hyvin tämän kurssin tehtävien tueksi.