Syntaksista ja kääntämisestä¶
Attention
Tässä kohdassa (ja muuallakin materiaalin alkupäässä) Python- ja C++-ohjelmia vertaillaan toisiinsa. Python on valittu vertailukieleksi, koska useimmat opiskelijat osaavat Pythonia Ohjelmointi 1:n pohjalta. Jos et kuitenkaan osaa Pythonia, vertailukohdissa riittää kiinnittää huomiota C++-osuuksiin.
Tässä osiossa katsotaan ensin, miltä pieni C++-ohjelma näyttää ja minkälaisia yksityiskohtia siihen liittyy. Vertaillaan seuraavia Python- ja C++-ohjelmia, jotka ovat toiminnaltaan täysin identtisiä:
#
# Versio 1: Pythonilla
#
def main():
print("Hello world!")
print("Mitä kuuluu?")
main()
//
// Versio 2: C++:lla
//
#include <iostream>
using namespace std;
int main() {
cout << "Hello world!" << endl;
cout << "Mita kuuluu?"
<< endl;
}
Tutkitaan eroja ja yhtäläisyyksiä ohjelmien välillä.
C++-ohjelma käynnistyy aina automaattisesti
main
-funktiosta.Pythonissa pääohjelmafunktio nimettiin
main
:ksi vain “synergiasyistä”, koska se on monissa muissakin ohjelmointikielissä nimetty niin. Python-kieli itsessään ei tällaista nimeämiskäytäntöä vaadi, mutta C++ vaatii.Suurin osa C++-käskyistä päättyy puolipisteeseen. Valitettavasti tämä sääntö ei kuitenkaan ole niin kattava, että puolipistettä voisi tunkea aivan joka paikkaan. Tämä on merkittävä virheiden ja harmin lähde, kun aloittelevalle C++-ohjelmoijalle ei vielä ole täysin avautunut, minne puolipiste kuuluu ja minne ei.
Koska käskyt päättyvät useimmiten puolipisteeseen, sallii C++ käskyjen jakamisen usealle riville ilman erityisnotaatiota. Python vaati yleensä katkaistun rivin loppuun
\
-merkin osoittamaan, että käsky jatkuu seuraavalla rivillä.Yhteenkuuluvat ohjelman osat (lohkot) merkitään aaltosulkeiden
{ }
sisään, kun Pythonissa ne esitettiin sisennyksen avulla. Tämä ei kuitenkaan tarkoita sitä, että C++-ohjelma kannattaisi jättää sisentämättä, sillä sisennykset kuitenkin auttavat ohjelmakoodin rakenteen hahmottamista.Kommenttimerkki C++-ohjelmassa on merkkiyhdistelmä
//
. Pythonissa kommentit ilmaistiin#
-merkillä.C++:ssa kieli itsessään ei sisällä tulostuskäskyä, vaan tulostaminen on toteutettu kirjaston avulla. Näytölle tulostaminen ja näppäimistöltä lukeminen vaativat
iostream
-kirjaston käyttöönoton:#include <iostream>
Käytetty
#include <...>
-rakenne on C++:n vastine Pythoninimport
-käskylle. Kun halutaan käyttää johonkin toiseen tiedostoon toteutettua ohjelmakoodia, käytetääninclude
-rakennetta. Jos halutaan, että kääntäjä etsii sisällytettävää tiedostoa sieltä, minne C++:n kirjastot on talletettu, kirjoitetaan kirjaston nimi kulmasulkeiden sisään. Jos halutaan etsiä itse toteutettuja tiedostoja, kirjoitetaan tiedoston nimi lainausmerkkien (""
) sisään.Merkillä
#
alkavat rivit ovat C++:ssa esiprosessorin direktiivejä. Esiprosessori on kääntäjän osa, joka valmistelee ohjelmakooditiedostoja ennen varsinaista käännöstä. Esiprosessori tekee oikeastaan vain tekstikorvausta, eli#include
-direktiivin käytännössä kopioi nimetyn tiedoston - tässä tapauksessaiostream
-kirjaston - sisällöninclude
-direktiivin paikalle.Kun
#include <iostream>
on kirjoitettu ohjelmakooditiedoston alkuun, ohjelmasta voidaan tulostaa tietoa näytölleiostream
-kirjastosta löytyväncout
-käskyn ja tulostusoperaattorin<<
avulla. Yhdelläcout
-käskyllä voi tulostaa niin monta tietoalkiota kuin haluaa, kunhan muistaa liittää jokaisen eteen tulostusoperaattorin<<
. Joscout
:iin tulostaaendl
:n, kursori siirtyy seuraavan rivin alkuun.C++-koodissa on vielä yksi käskyrivi, jota Python-versiossa ei näennäisesti ole:
using namespace std;
Tämä rivi mahdollistaa sen, että
iostream
-kirjastosta tarvittuja nimiäcout
jaendl
voidaan käyttää sellaisenaan, ilman että niiden paikalle olisi aina kirjoitettavastd::cout
jastd::endl
.Pythonissa on aivan sama mekanismi, joka vaan ei tullut esimerkissä vastaan, koska siinä ei käytetty kirjastoja. Jos Python-ohjelmassa halutaan käyttää kirjastoja, voidaan kirjoittaa:
import math ... math.sqrt(2.0)
Tarvitaan siis kirjaston nimi etuliitteenä, kun halutaan kirjaston tarjoama palvelu käyttöön. Tämä voidaan välttää kirjoittamalla:
from math import * ... sqrt(2.0)
Käskyllä
using namespace
siis mahdollistetaan tunnisteiden käyttäminen jostain nimetystä nimiavaruudesta ilman nimiavaruuden määrittävää etuliitettä. Toisaalta etuliitteetkin voivat olla käteviä. Nimiavaruudet ikään kuin määrittävät joukon tunnisteita ja piilottavat tunnisteet näkymästä nimiavaruuden ulkopuolella, jolloin ohjelmakoodissa voidaan hyvin käyttää samaa tunnistetta useassa paikassa, kunhan ne vain on määritetty eri nimiavaruuksiin, jolloin ei ole epäselvyyttä siitä mitä tunnistetta yritetään käyttää. Esimerkiksi tunnistenimi
voisi opintorekisteriohjelmassa olla käytössä sekä opiskelijan tiedoissa että opintojakson tiedoissa. Kun molemmat näistä olisi toteutettu omissa nimiavaruuksissaan, tunnisteisiin päästäisiin käsiksi esim.Opiskelija::nimi
jaOpintojakso::nimi
.Tässä vaiheessa kurssia nimiavaruuksista ei tarvitse sen kummemmin murehtia, vaan riittää hoksata, että jos tunniste kuuluu johonkin muuhun kuin globaaliin nimiavaruuteen, se ei löydy, mikäli nimiavaruutta ei ole kirjoitettu koodiin näkyviin joko
using namespace
-käskyllä tai kirjoittamalla nimiavaruuden näkyviin tunnisteen eteen tähän tapaan:nimiavaruus::tunniste
Attention
Mitä suurempia ohjelmia toteutat, sitä tärkeämpää on huolehtia, ettei ohjelmassa ole käytössä “turhia” nimiä. Jos käytössä on useita suuria kirjastoja kaikkine nimineen, alkaa käytettyjen tunnisteiden määrä olla niin suuri, että se voi vaikeuttaa uusien tunnisteiden keksimistä.
Siksi on usein parempi vaihtoehto kirjoittaa nimiavaruudet
tunnisteiden eteen tähän tapaan: std::cout
ja std::endl
kuin
sisällyttää koko nimiavaruus using namespace std;
-käskyllä ja
kirjoittaa pelkästään cout
ja endl
, olkoonkin, että
jälkimmäinen muoto on yksinkertaisempi kirjoittaa.
Ilman using namespace
-käskyä edellä ollut ohjelma näyttäisikin
seuraavalta:
//
// Versio 3: Ohjelmointityylillisesti paremmin C++:lla
//
#include <iostream>
int main() {
std::cout << "Hello world!" << std::endl;
std::cout << "Mita kuuluu?"
<< std::endl;
}
Vaikka yllä oleva koodi onkin tyylillisesti parempi,
tulee helposti käytettyä versiota, jossa lukee using namespace std
,
sillä Qt generoi tämän rivin automaattisesti.
Tulkkaus vs. kääntäminen¶
Python on tulkattava ohjelmointikieli ja C++ on käännettävä ohjelmointikieli.
Tulkattavan ohjelmointikielen ohjelmia suoritettaessa jokaisella suorituskerralla tarvitaan erillinen apuohjelma, jota kutsutaan kyseisen ohjelmointikielen tulkiksi (esim. Python-tulkki). Jos tulkkiohjelma poistetaan tai se tuhoutuu, kyseisellä ohjelmointikielellä kirjoitettuja ohjelmia ei voida enää suorittaa ennen kuin tulkki asennetaan uudelleen. Tulkin tehtävänä on muuttaa lähdekoodin käskyjä konekielelle sitä mukaa kun ohjelman suoritus etenee, jotta tietokoneen keskusyksikkö (prosessori) pystyy suorittamaan ne.
Käännettävällä ohjelmointikielellä toteutettujen ohjelmien suorittaminen eroaa tulkattavasta ohjelmasta siinä, että ohjelma täytyy ennen suorittamista kokonaisuudessaan kääntää eli muuntaa konekieliseksi koodiksi. Kääntämiseen tarvitaan apuohjelma, jota kutsutaan ohjelmointikielen kääntäjäksi (esim. C++-kääntäjä).
Käännöksen yhteydessä kääntäjä suorittaa koko lähdekooditiedostolle
suuren määrän virhetarkistuksia ja tuottaa siitä kokonaisen
konekielisen ohjelman, joka yleensä talletetaan kovalevylle erilliseen
tiedostoon (esim. Windows:issa .exe
-päätteiseen tiedostoon). Kun
käännösprosessi on valmis, kääntäjää ei enää tarvita, koska
kovalevylle talletettua konekielistä ohjelmaa voidaan suorittaa yhä
uudelleen ilman kääntäjäohjelman apua. Toki, jos lähdekoodiin tehdään
muutoksia, se on käännettävä uudelleen konekieliseksi tiedostoksi,
jolloin kääntäjää tarvitaan uudelleen.
On olemassa monia ohjelmointikieliä, jotka sijoittuvat jonnekin tulkattavien ja käännettävien kielien välimaastoon. Tällaisilla kielillä kirjoitetut ohjelmat voidaan kääntää nk. välikoodiksi, jota sitten voidaan suorittaa nopeasti ja vähemmillä virhetarkasteluilla.
Jos haluamme olla aivan täsmällisiä, käännös on (ainakin) kaksivaiheinen. Ensin ohjelma käännetään niin sanotuksi objektitiedostoksi. Tässä muodossa kirjoitettu ohjelma on jo konekoodia, mutta sitä ei voi vielä suorittaa tietokoneella. Syy on se, että lopulliseen ohjelmaan tarvitaan osia myös kääntäjän kirjastosta, jonka takia ohjelma pitää vielä linkittää.
Linkityksessä ohjelman erikseen käännetyt osat liitetään yhteen ja niistä muodostetaan yksi ajettava (executable) ohjelma. Kääntäjät yleensä kätkevät ohjelmoijalta linkitysvaiheen, mutta suurissa ohjelmistossa käännös on tehtävä kahtena vaiheena, jotta koko ohjelmaa ei tarvitsisi kääntää jokaisen pienen muutoksen jälkeen. Hyöty on melkoinen, kun ohjelmiston koko ylittää 100000 tai miljoona riviä, mutta havaittavissa se on jo paljon pienemmilläkin ohjelmilla.