Oliot, osoittimet ja taulukot
Luokkien toteutus ja kapselointi
C++:ssa luokan määrittely ja metodien toteutukset sijoitetaan tavallisesti eri tiedostoihin. Tämä helpottaa luokan käyttöä useissa ohjelman osissa ja erottaa julkisen rajapinnan toteutuksen yksityiskohdista.
Otsikko- ja toteutustiedostot
Pääohjelma ottaa luokan määrittelyn käyttöön otsikkotiedostosta:
#include "henkilo.hh" #include <iostream> using namespace std; int main() { Henkilo kaveri("Matti", 18); cout << kaveri.hae_nimi() << endl; kaveri.tulosta(); kaveri.vieta_syntymapaivaa(19); kaveri.tulosta(); }
Otsikkotiedosto henkilo.hh ilmoittaa luokan nimen, julkiset metodit ja yksityiset jäsenmuuttujat:
#include <string> class Henkilo { public: Henkilo(const std::string& nimi, int ika); std::string hae_nimi() const; void vieta_syntymapaivaa(int uusi_ika); void tulosta() const; private: std::string nimi_; int ika_; };
Metodien toteutukset ovat tiedostossa henkilo.cpp. Toteutustiedosto ottaa ensin käyttöön vastaavan otsikon:
#include "henkilo.hh" #include <iostream> using namespace std; Henkilo::Henkilo(const string& nimi, int ika) : nimi_(nimi), ika_(ika) {} string Henkilo::hae_nimi() const { return nimi_; } void Henkilo::vieta_syntymapaivaa(int uusi_ika) { ika_ = uusi_ika; } void Henkilo::tulosta() const { cout << nimi_ << " : " << ika_ << endl; }
Kirjastojen otsakkeet kirjoitetaan yleensä kulmasulkeisiin, kuten <iostream>. Omiin tiedostoihin viitataan lainausmerkeillä, kuten "henkilo.hh". Kääntäjälle täytyy lisäksi kertoa, että henkilo.cpp kuuluu ohjelman käännettäviin lähdetiedostoihin.
Tavallisesti jokaista luokkaa kohti tehdään otsikkotiedosto ja toteutustiedosto. Otsikkotiedosto sisältää luokan määrittelyn; toteutustiedosto sisältää metodien rungot. Esimerkissä käytetään päätteitä .hh ja .cpp.
Luokan lisääminen Qt Creatorissa
Qt Creatorissa uuden luokan voi lisätä valitsemalla New File or Project, sitten C++ ja C++ Class. Työkalu luo luokalle otsikko- ja toteutustiedoston ja lisää toteutustiedoston projektin käännettäviin lähdetiedostoihin.
Projektin .pro-tiedostossa käännettävät lähdetiedostot on lueteltu tavallisesti SOURCES-kohdassa. Otsikkotiedostoja ei lisätä tähän samalla tavalla. Kurssin alussa Qt Creator voi hoitaa nämä asetukset automaattisesti.
Kellonaika-luokka: kaksi toteutusta
Seuraava esimerkki näyttää, miten luokan yksityisen toteutuksen voi vaihtaa julkista rajapintaa muuttamatta. Ensimmäisessä versiossa kellonaika tallennetaan erillisiin tunti- ja minuuttikenttiin.
Versio 1: tunnit ja minuutit erikseen
class Kellonaika { public: Kellonaika(int tunti, int minuutti); void tiktok(); void tulosta() const; private: int tunnit_; int minuutit_; };
Luokan käyttävä koodi kutsuu julkisia metodeja eikä näe jäsenmuuttujia:
#include "kellonaika.hh" int main() { Kellonaika aika(23, 59); aika.tulosta(); aika.tiktok(); aika.tulosta(); }
tiktok() siirtää aikaa minuutilla eteenpäin. Tulostus muotoillaan <iomanip>-kirjaston toiminnoilla:
#include "kellonaika.hh" #include <iostream> #include <iomanip> using namespace std; Kellonaika::Kellonaika(int tunti, int minuutti) : tunnit_(tunti), minuutit_(minuutti) {} void Kellonaika::tiktok() { ++minuutit_; if (minuutit_ >= 60) { minuutit_ = 0; ++tunnit_; } if (tunnit_ >= 24) { tunnit_ = 0; } } void Kellonaika::tulosta() const { cout << setw(2) << setfill('0') << tunnit_ << "." << setw(2) << minuutit_ << endl; }
iomanip-kirjaston tulostusmuotoiluun ei tarvitse tässä keskittyä. Tärkeää on, että metodit toimivat luokan julkisen rajapinnan kautta.
Versio 2: kuluneet minuutit
Toisessa toteutuksessa kellonaika tallennetaan yhteen jäsenmuuttujaan: minuutteina vuorokauden alusta. Julkiset metodit ja niiden kutsut pysyvät samoina.
class Kellonaika { public: Kellonaika(int tunti, int minuutti); void tiktok(); void tulosta() const; private: int kuluneet_minuutit_; int hae_tunti() const; int hae_minuutti() const; };
Kellonaika::Kellonaika(int tunti, int minuutti) : kuluneet_minuutit_(60 * tunti + minuutti) {} void Kellonaika::tiktok() { ++kuluneet_minuutit_; if (kuluneet_minuutit_ >= 24 * 60) { kuluneet_minuutit_ = 0; } } int Kellonaika::hae_tunti() const { return kuluneet_minuutit_ / 60; } int Kellonaika::hae_minuutti() const { return kuluneet_minuutit_ % 60; } void Kellonaika::tulosta() const { cout << setfill('0') << setw(2) << hae_tunti() << "." << setw(2) << hae_minuutti() << endl; }
Nyt tulosta() ei käsittele tallennustapaa suoraan, vaan pyytää tunnit ja minuutit yksityisiltä apumetodeilta. Jos tallennustapa myöhemmin muuttuu, tulostusmetodiin ei tarvitse koskea, kunhan nämä metodit säilyttävät toimintansa.
Tämä on kapseloinnin hyöty: luokan yksityinen toteutus voi muuttua, vaikka sen käyttäjien koodi ja julkinen rajapinta säilyvät ennallaan. Luokan sisällä metoditkin voivat käyttää toisiaan kutsumalla niitä ilman olion nimeä.
Luokkien käytön hyötyjä
- Yksityinen toteutus voidaan vaihtaa, kunhan julkinen rajapinta säilyy yhteensopivana.
- Rakentajat ja tilaa muuttavat metodit voivat pitää huolta siitä, ettei olio päädy virheelliseen tilaan.
- Luokat kokoavat yhteen käsitteeseen liittyvää tietoa ja toimintaa, mikä helpottaa ohjelman ymmärtämistä ja ylläpitoa.
- Hyvin suunnitellut luokat ovat uudelleenkäytettäviä ja auttavat hallitsemaan ohjelman monimutkaisuutta.
Nämä edut eivät kuulu vain luokille. Myös funktiot tarjoavat rajapinnan: kun niiden parametrit ja paluuarvot ovat selvät, toteutuksen yksityiskohtia ei tarvitse tuntea.
Lisää esimerkkejä
Kurssin esimerkeistä hakemistossa examples/05/students on lisää luokkien käyttöä. Esimerkeissä voi olla metodeja, joiden toteutusta ei vielä tarvitse ymmärtää kokonaan.