Oliot, osoittimet ja taulukot
Rajapinnoista ja olio-ohjelmoinnista
Ohjelmoinnissa rajapinta määrittelee, millä tavoin ohjelman osaa voi käyttää. Toteutuksen yksityiskohdat pidetään piilossa, ja toimintoihin päästään käsiksi ennalta määriteltyjen operaatioiden kautta. Käyttäjän ei tarvitse tietää, miten osa on toteutettu, kunhan hän tuntee sen tarjoamat palvelut.
Esimerkiksi C++:n string-tyypin sisäistä toteutusta ei tarvitse tuntea. Merkkijonoa voi silti käsitellä sen tarjoamilla funktioilla ja operaattoreilla. Nämä muodostavat tyypin julkisen rajapinnan, eräänlaisen käyttöliittymän, joka kertoo mitä merkkijonolle voi tehdä. Mitä voisi tarkoittaa yksityinen rajapinta?
Rajapinnat ovat keskeisiä olio-ohjelmoinnissa. C++:ssa luokan avulla voidaan määritellä, mitkä toiminnot ovat julkisia ja mitkä toteutuksen osat pidetään yksityisinä. Oliot käyttävät toistensa palveluita julkisten rajapintojen kautta.
Luokat ja oliot
Pythonissa luokka ja sen olio voidaan määritellä esimerkiksi näin:
class Henkilö: def __init__(self, nimi, ikä): self.__nimi = nimi self.__ikä = ikä def hae_nimi(self): return self.__nimi def vietä_syntymäpäivää(self, monesko): self.__ikä = monesko def tulosta(self): print(self.__nimi, ":", self.__ikä) kaveri = Henkilö("Matti", 18) print(kaveri.hae_nimi()) kaveri.tulosta() kaveri.vietä_syntymäpäivää(19) kaveri.tulosta()
Luokka on tietotyyppi; olio on kyseisen luokan tyyppinen arvo tai muuttuja. Oliota kutsutaan myös luokan ilmentymäksi (instance).
C++:ssa sama rakenne voidaan toteuttaa luokalla, jossa julkinen rajapinta ja yksityiset jäsenmuuttujat erotetaan toisistaan:
#include <iostream> #include <string> using namespace std; class Henkilo { public: Henkilo(const string& nimi, int ika); string hae_nimi() const; void vieta_syntymapaivaa(int uusi_ika); void tulosta() const; private: string nimi_; int ika_; }; // Luokan määrittelyn jälkeen tarvitaan puolipiste. int main() { Henkilo kaveri("Matti", 18); cout << kaveri.hae_nimi() << endl; kaveri.tulosta(); kaveri.vieta_syntymapaivaa(19); kaveri.tulosta(); } 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; }
public-osassa määritellään ne metodit eli jäsenfunktiot, joita luokan käyttäjä voi kutsua. Ne muodostavat luokan julkisen rajapinnan. private-osassa oleviin jäsenmuuttujiin eli attribuutteihin ei pääse suoraan käsiksi luokan ulkopuolelta.
Jos luokan käyttäjän pitää voida lukea tai muuttaa yksityistä tietoa, luokan rajapintaan lisätään siihen sopiva metodi. Metodien rungossa jäsenmuuttujiin viitataan suoraan; kullakin oliolla on omat jäsenmuuttujansa.
Rakentajan nimenä on luokan nimi, eikä sille ilmoiteta paluutyyppiä. Se suoritetaan automaattisesti, kun olio luodaan. Esimerkin rakentajan alustuslista alkaa kaksoispisteellä ja alustaa jäsenmuuttujat ennen rakentajan rungon suorittamista.
Nimi välitetään rakentajalle vakioviitteenä, jotta merkkijonoa ei tarvitse kopioida. Jäsenmuuttuja nimi_ on silti tavallinen merkkijono: olio tallentaa oman nimensä, eikä sen jäsenmuuttuja ole vain viite rakentajan parametrin paikalliseen arvoon.
Metodit, rakentaja ja purkaja
Rakentajan lisäksi C++:ssa on erityisasemassa purkaja. Purkajan nimi on tilde ja luokan nimi, esimerkiksi ~Henkilo. Sillä ei ole parametreja, ja se suoritetaan automaattisesti olion elinkaaren päättyessä. Paikallisen olion elinkaari päättyy näkyvyysalueen lopussa.
Jäsenfunktion parametrien jälkeen oleva const kertoo, ettei metodi muuta olion tilaa tämän metodin kautta. Esimerkiksi hae_nimi() ja tulosta() ovat vakiojäsenfunktioita. Syntymäpäivämetodi muuttaa ikää, joten se ei ole const.
Tavallisen metodin kutsu tehdään muodossa olio.metodi(parametrit). Rakentajaa ei kutsuta erikseen, vaan se suoritetaan olion luonnin yhteydessä. Jos luokalla on purkaja, se suoritetaan olion elinkaaren lopussa.
Luokan tehtävä on muodostaa abstraktio: käyttäjä käsittelee käsitettä sen tarjoaman rajapinnan kautta ilman tarvetta tietää, miten yksityinen toteutus toimii.
Operaattorimetodit
Luokalle voidaan määritellä operaattorimetodeja, jos jokin tuttu operaattori sopii luontevasti sen toimintaan. Esimerkiksi murtolukujen yhtäsuuruutta voi verrata operator==-metodilla. Seuraava vertailu olettaa, että murtoluvut tallennetaan aina supistetussa muodossa:
bool Murtoluku::operator==(const Murtoluku& toinen) const { return osoittaja_ == toinen.osoittaja_ && nimittaja_ == toinen.nimittaja_; }
Tämän jälkeen murtolukuja voi verrata tavallisella yhtäsuuruusoperaattorilla:
Murtoluku m1(2, 3); Murtoluku m2(3, 4); if (m1 == m2) { // Murtoluvut ovat yhtä suuret. }
Operaattorin merkityksen kannattaa vastata käyttäjän odotusta. Esimerkiksi ==-operaattorin tulee ilmaista arvojen yhtäsuuruutta.