C++-ohjelmoinnin aloittaminen
Muuttujat ja tyypitys
Muuttujat ja tyypitys (V: 4 min)
C++:ssa muuttujien käsittelemiseen liittyy enemmän yksityiskohtia kuin Pythonissa. Ei kuitenkaan masennuta, vaikka alkuun pääseminen saattaakin tuntua hitaalta.
Python vs. C++: muuttujat ja syötteet
Vertaillaan seuraavia ohjelmia, jotka toimivat samalla tavalla:
Python
def main(): nimi = input("Syötä nimesi: ") ika = int(input("Syötä ikäsi: ")) print("Hauska tavata", nimi, ika, "v.") print(50 - ika, "vuoden päästä olet 50-vuotias.") main()
C++
#include <iostream> #include <string> using namespace std; int main() { string nimi = ""; cout << "Syota nimesi: "; getline(cin, nimi); int ika = 0; cout << "Syota ikasi: "; cin >> ika; cout << "Hauska tavata " << nimi << " " << ika << " v." << endl; cout << 50 - ika << " vuoden paasta olet 50-vuotias." << endl; }
Huomioita ja johtopäätelmiä
- C++-ohjelma on usein pidempi kuin vastaava Python-ohjelma. Yksi esimerkki ei tätä yksin osoita, mutta myös kokemukset tukevat tätä havaintoa.
- Pythonissa muuttuja otetaan käyttöön antamalla sille arvo sijoitusoperaatiolla (
=). Python ei ole tarkka siitä, minkä tyyppistä tietoa muuttujaan talletetaan, joten muuttujan sisältämän arvon tyyppi voi vaihdella suorituksen aikana. - C++:ssa muuttuja täytyy esitellä ennen käyttöä. Esittely kertoo kääntäjälle muuttujan nimen ja sen, minkä tyyppistä tietoa siihen voidaan tallentaa.
- Pythonissa muuttujien ja funktioiden nimissä voidaan käyttää skandinaavisia kirjaimia, mutta C++:ssa nimet koostuvat ASCII-kirjaimista, numeroista ja alaviivoista (
_). Nimen ensimmäisen merkin täytyy olla kirjain. Siksi esimerkin Python-muuttujaikäkirjoitetaan C++:ssaika. - Skandinaavisia kirjaimia voi käyttää C++:n merkkijonoissa, mutta joissakin tilanteissa niistä voi aiheutua ongelmia. Siksi niiden välttäminen on usein hyvä käytäntö.
- C++:n
string-tyyppi on kirjastotyyppi. Merkkijonojen käyttö edellyttää#include <string>-riviä ohjelman alussa. - Toisin kuin Pythonin
print-funktio, C++:ncoutei lisää automaattisesti välilyöntejä tulostettavien arvojen väliin eikä rivinvaihtoa tulostuksen loppuun. Rivinvaihdon saa lisäämällä esimerkiksiendl. cinjacoutovat tietovirtoja: niiden avulla käsitellään näppäimistöltä luettavaa syötettä ja näytölle tulostettavaa tekstiä.- Merkkijonoja luetaan
cin-tietovirrastagetline-funktiolla. Syöttöoperaattorilla>>luetaan muuttujalle sopivan tyyppinen arvo. - Pythonin
input-funktio ja C++:ngetline-funktio toimivat eri tavoin. Pythonininputottaa parametrina tulostettavan kehotteen ja palauttaa luetun merkkijonon. C++:ngetlineottaa parametreina tietovirran (kurssin alussa yleensäcin) ja muuttujan, johon syöte luetaan.getline-funktion paluuarvoa ei sijoiteta merkkijonomuuttujaan. - Syöttöoperaattori
>>ohittaa tyhjät merkit, myös rivinvaihdot. Se lukee syötettä, kunnes vastaan tulee näkyvä merkki. Kokeile tätä syöttämällä ikää kysyttäessä ensin muutama rivinvaihto ja vasta sitten luku. Tämä eroaa Pythonininput-funktiosta, joka lukee syötteen rivi kerrallaan.
Näillä pääsemme alkuun. Myöhemmin perehdymme tietovirtoihin tarkemmin ja opettelemme tarkistamaan syötteiden oikeellisuuden, esimerkiksi tilanteessa, jossa käyttäjä syöttää >>-operaattorilla vääräntyyppisen arvon.
Muuttujat C++:ssa
C++:ssa muuttuja on määriteltävä (tai ainakin esiteltävä) ennen käyttöä. Määrittely koostuu esittelystä ja alustuksesta. Esittelyssä ilmoitetaan muuttujan tietotyyppi ja nimi; alustuksessa annetaan muuttujalle alkuarvo.
- muuttujan tietotyyppi eli siihen tallennettavan tiedon tyyppi
- muuttujan nimi
- alkuarvon antaminen eli muuttujan alustaminen
Tietotyyppi ja nimi ovat pakollisia; alkuarvon antaminen on vapaaehtoista mutta suositeltavaa. Esimerkkejä muuttujien määrittelystä:
// Määrittely ilman alustusta int ika; double lampotila; string nimi; // Määrittely alustuksen kanssa int ika = 21; double lampotila = 16.7; string nimi = "Teppo";
Esitellyn mutta alustamattoman muuttujan arvo on määrittelemätön, kunnes se asetetaan esimerkiksi sijoitusoperaatiolla = tai syöttöoperaatiolla cin >>:
int ika; double lampotila; // Tässä kohdassa molempien muuttujien arvo on määrittelemätön ika = 21; cin >> lampotila; // Nyt molemmilla muuttujilla on arvo
Varoitus
Alusta muuttujat määrittelyn yhteydessä aina, kun se on mahdollista. Alustamattomat muuttujat voivat aiheuttaa vaikeasti jäljitettäviä virheitä.
C++ valvoo muuttujan tietotyyppiä. Arvo muunnetaan tarvittaessa muuttujan tyypin mukaiseksi, jos tällainen muunnos on sallittu; kaikkia arvoja ei voi sijoittaa kaikkiin muuttujiin.
Video: Miksi muuttujat kannattaa alustaa? (4 min)
C++-kääntäjä vaatii, että muuttuja esitellään. Alustaminen eli alkuarvon antaminen ei ole pakollista, mutta se on hyvän ohjelmointitavan mukaista. Alustamatta jättämisellä voi olla yllättäviä vaikutuksia.
Muuttujan näkyvyysalue
Lohkon sisällä määritelty paikallinen muuttuja on käytettävissä määrittelykohdastaan lohkon loppuun saakka. Lohko rajataan aaltosulkeilla { }:
int main() { int osallistujia = 0; while (osallistujia < 100) { int vapaita_paikkoja = 100 - osallistujia; ... rivejä poistettu ... // Tässä on viimeinen kohta, jossa vapaita_paikkoja on käytettävissä. } ... rivejä poistettu ... // Tässä on viimeinen kohta, jossa osallistujia on käytettävissä. }
Tietotyyppejä
Yksinkertaisissa C++-ohjelmissa käytetään usein tyyppejä int, double, bool ja string. C++:n double vastaa suunnilleen Pythonin float-tyyppiä eli liukulukua.
C++:ssa on lisäksi char-tietotyyppi, jolla käsitellään yksittäisiä merkkejä. Sen koko on vähintään yksi tavu, mutta se ei sellaisenaan vastaa kaikkien Unicode-merkkien käsittelyä. Esimerkiksi:
char sukupuoli = 'N';
Literaalit ja vakiot
Literaali on ohjelmakoodissa esiintyvä nimetön vakioarvo. Esimerkkejä ovat 42, 1.4142, 'x', "teksti" ja true.
Pythonissa yksittäiselle merkille ei ole erillistä tietotyyppiä, vaan sitä käsitellään yhden merkin pituisena merkkijonona. Pythonissa merkkijonoliteraalin voi kirjoittaa joko yksi- tai kaksinkertaisiin lainausmerkkeihin. C++:ssa merkkijonoliteraali kirjoitetaan kaksinkertaisiin lainausmerkkeihin, kuten "abc", ja char-literaali yksinkertaisiin lainausmerkkeihin, kuten 'x'. Siksi 'X' on merkki ja "X" yhden merkin merkkijono. "X"-merkkijonoa ei voi sijoittaa char-muuttujaan.
C++:ssa vakion arvoa ei voi muuttaa alustuksen jälkeen. Vakio määritellään lisäämällä const muuttujan tyypin eteen:
const double PII = 3.14; ... rivejä poistettu ... // Vakion muuttaminen aiheuttaa virheen PII = 3.141592653589793; // VIRHE
Vakiot sopivat sellaisten arvojen nimeämiseen, joiden ei ole tarkoitus muuttua. Luonnonvakiot, kuten pii, ovat selkeitä esimerkkejä:
const int LOTTOPALLOJA = 7;
Vakio täytyy alustaa määrittelyn yhteydessä, koska sen arvoa ei voi myöhemmin muuttaa. Vakiot nimetään usein isoilla kirjaimilla, ja moniosaisen nimen sanat erotetaan alaviivalla. Kuvaavat nimet helpottavat ohjelmakoodin ymmärtämistä.
Dynaaminen ja staattinen tyypitys
Miten seuraava Python-ohjelma käyttäytyy?
def main(): print("Alku") arvo1 = 5.3 arvo2 = 7.1 print(arvo1 + arvo2) arvo2 = "Hei" # Vanhan muuttujan uudelleenkäyttö print("Keskiväli") print(arvo1 + arvo2) print("Loppu") main()
Ohjelma toimii normaalisti riville 8 asti, jolloin suoritus päättyy virheeseen:
Alku
12.399999999999999
Keskiväli
Traceback (most recent call last):
File "koodit/osa-01-04.py", line 11, in <module>
main()
File "koodit/osa-01-04.py", line 8, in main
print(arvo1 + arvo2)
TypeError: unsupported operand type(s) for +:
'float' and 'str'
Python käyttää dynaamista tyypitystä: arvon tyypin sopivuus tarkistetaan, kun ohjelma yrittää käyttää arvoa.
- Sama muuttuja voi viitata erityyppisiin arvoihin ohjelman suorituksen aikana.
- Ohjelma voi toimia normaalisti siihen asti, kunnes yritetään käsitellä yhteen sopimattomia arvoja.
Dynaaminen tyypitys on yleistä tulkattavissa ohjelmointikielissä.
Vastaava C++-ohjelma näyttäisi tältä:
#include <iostream> #include <string> using namespace std; int main() { cout << "Alku" << endl; double arvo1 = 5.3; double arvo2 = 7.1; cout << arvo1 + arvo2 << endl; string arvo3 = "Hei"; // Uusi muuttuja cout << "Keskivali" << endl; cout << arvo1 + arvo3 << endl; cout << "Loppu" << endl; }
Jos tätä ohjelmaa yritetään kääntää, kääntäjä antaa virheilmoituksen (ilmoitusta on yksinkertaistettu):
main.cpp:13: error: no match for 'operator+'
cout << arvo1 + arvo3 << endl;
C++ käyttää staattista tyypitystä. Kääntäjä tarkistaa, ovatko ohjelmassa käytetyt operaatiot sallittuja. Tyyppivirheen vuoksi ohjelmaa ei käännetä eikä sitä voi suorittaa.
- Muuttujat on määriteltävä ennen käyttöä.
- Muuttujilla on kiinteä tietotyyppi, ja niihin voi sijoittaa vain kyseiseen tyyppiin sopivia arvoja.
- Tyyppivirheet havaitaan käännöksen aikana.
Staattinen tyypitys on yleistä käännettävissä ohjelmointikielissä. C++-esimerkissä määritellään uusi merkkijonomuuttuja arvo3, koska merkkijonoa ei voi sijoittaa liukulukumuuttujaan arvo2, vaikka Pythonissa muuttujalle voi antaa myöhemmin erityyppisen arvon.
Dynaamisen tyypityksen etuna on, että ohjelmoijan ei tarvitse määritellä jokaista muuttujaa etukäteen ja sama muuttuja voi viitata eri tyyppisiin arvoihin. Haittapuolena ohjelmaan voi jäädä virheitä, jotka tulevat esiin vasta suorituksen aikana. Lisäksi saman muuttujan käyttäminen eri tarkoituksiin voi vaikeuttaa koodin ymmärtämistä.
Tyyppimuunnos
Tyyppimuunnoksessa arvo muunnetaan tai tulkitaan toisen tyypin mukaisesti. Muunnos voi olla implisiittinen eli automaattinen tai eksplisiittinen eli ohjelmoijan pyytämä.
Kääntäjä tekee joitakin implisiittisiä tyyppimuunnoksia automaattisesti. Esimerkiksi kokonaisluku voidaan muuntaa liukuluvuksi:
double d = 3;
Kokonaisluvun voi muuntaa myös merkiksi:
char merkki = 113; // Tavallisessa ASCII-koodauksessa merkki on 'q'
Implisiittinen muunnos on mahdollinen yhteensopivien tyyppien välillä. Esimerkiksi etumerkillisten ja etumerkittömien kokonaislukujen vertailu voi kuitenkin olla ongelmallista, jolloin kääntäjä antaa varoituksen:
int i = 0; unsigned int ui = 0; ... if (ui == i) { // Tästä voi tulla varoitus ... }
Jos ohjelmoija on varma, että etumerkillinen kokonaisluku halutaan muuntaa etumerkittömäksi, muunnoksen voi kirjoittaa eksplisiittisesti operaattorilla static_cast:
if (ui == static_cast<unsigned int>(i)) { ... }
Seuraavassa esimerkissä merkki muunnetaan kokonaisluvuksi, jotta sen koodiarvo voidaan tulostaa:
cout << "Syota jokin merkki: "; char merkki = ' '; cin >> merkki; int merkin_koodiarvo = static_cast<int>(merkki); cout << "Merkin " << merkki << " koodiarvo on " << merkin_koodiarvo << endl;