Oliot ja luokat

 

Oliot ja luokat

Tervetuloa t�m�n oppaan ehdottomasti t�rkeimm�n osion pariin. Kuten ehk� oletkin jo huomannut, rakastan suuresti k�yt�nn�n tilanteiden pohtimista C++:n avulla. Ja niit� on tulossa viel� paljon lis��, joten ei auta kuin tottua. Nyt menn��n taas: Koska vartiointifirma Pamppu ja sateenvarjo oy:lle tekem�si ohjelma toimi loistavasti ja moitteettomasti, p��tt�v�t he tilata sinulta toisenkin ohjelman. Ohjelman pit�isi yll�pit�� heid�n asiakasluetteloaan. Jokaisella asiakkaalla on tietoina nimi, osoite, sovittu kuukausimaksu jne..

Kuinka t�llainen tietom��r� pit�isi j�rjest��? No, hommahan on helppo. Tehd��n taulukko nimi[ASIAKKAIDEN_LKM], maksu[ASIAKKAIDEN_LKM] jne.. N�in se onnistuisi ihan perusominaisuuksien avulla. Mutta mites suu pannaan, kun noin puolet asiakkaista haluaa y�vartioinnin ja toinen puoli ei? Kaiken kukkuraksi y�vartijoiden m��r� vaihtelee. Yksi ratkaisu on tehd� taulukko yovartijat[ASIAKKAIDEN_LKM] ja olla v�litt�m�tt� siit�, ett� osa alkioista on tyhji� ja turhia. Jos ei viel� hymy hyydy, lis��mme soppaan sen, ett� ohjelman pit�isi pysty� lis��m��n ja poistamaan asiakkaita lennossa. Ohjelmaa py�ritt�� vanha 386-kone, jossa on v�h�n muistia ja tehoa. Niinp� muistia tai koneen prosessoriaikaa ei saisi hukata ollenkaan.

Kuulen jo kuinka huudatte edellisen esimerkin ikeen alla: "Me haluamme olioita ja luokkia! Tahtoo!" Hyv� on, nyt niit� tulee. Pient� jaarittelua kuitenkin viel�.

Lapsikin huomaa, ett� edellisen esimerkkiongelman ratkaisu taulukoilla ontuu. Asiakas olisi parempi pit�� omana kokonaisuutenaan, ei vain yhten� indeksin� moniin taulukoihin. Pelkill� taulukoilla ja isolla kasalla purkkavirititelmi� edellisen tapauksen voi kyll� hoitaa muistia ja tehoa haaskaamatta, mutta tulos on sellainen ohjelmanv�nkyr�, jota kukaan ei vapaaehtoisesti kyll� ala en�� kehitt�m��n. Niinp� k�sittelemmekin nyt luokat (class) ja oliot (object). T�m� on nyt vihdoinkin sit� C++:aa itse��n. C++ on oliopohjainen kieli, joten ilman olioita ei pitk�lle p�tki. Toki C++:lla voi ohjelmoida ilmankin olioita, mutta harvemmin syyt� sellaiseen on..

Ajatellaan nyt n�in p�in, ett� kaikkia asiakkaan tietoja ei laiteta taulukkoon, vaan ne s�il�t��n asiakkaaseen, ja sitten tehd��n asiakkaista taulukko. Me tarvitsemme asiakasolioita. Koska kaikki asiakkaat ovat samanlaisia, sit� y�vahtijuttua lukuunottamatta, ne ovat samoja olioita. Niill� on samat ominaisuudet, mutta eri tiedot. Samanlaiset oliot kuuluvat samaan luokkaan. Nyt otamme ja m��rittelemme luokan Asiakas.

class Asiakas
{
public:
  char* nimi;
  char* osoite;
  int kuukausiMaksu;
};  // huomaa ; -merkki lohkon lopussa!!!

Ensin on varattu sanan class. Sitten seuraa luokan nimi. Sitten alkaa lohko, jossa luokka m��ritell��n. public tarkoittaa, ett� alla olevat muuttujat ovat kaikkien k�yt�ss�. TSM. Sen j�lkeen homma on tuttua, m��rittelemme vain Asiakkaaseen liittyv�t muuttujat. Lohko loppuu ja sitten tulee taas penteleen t�rke� kohta, nimitt�in puolipiste lohkon lopussa. Muista se! Lohkojen loppuun ei muuten tule C++:ssa puolipistett�, mutta luokan m��rittely on (mielest�ni typer�) poikkeus.

Kun haluamme luoda asiakkaan, k�y se n�in (kunhan luokka on ensin m��ritelty):

Asiakas erkinKumikorjaamo;

Luomme olion erkinKumikorjaamo. Asiakas on itseasiassa kuin mik� tahansa muuttujatyyppi. Voi tehd� Asiakas-taulukoita ja muiden luokkien j�senen� voi olla Asiakas-olioita. Palataksemme esimerkkiin, teemme n�in:

Asiakas asiakkaat[ASIAKKAIDEN_LKM];

Luomme siis taulukon asiakkaat, joka sis�lt�� ASIAKKAIDEN_LKM (vakio) kappaletta Asiakas-luokan olioita. Olion sis�lt�mi� muuttujia k�ytet��n piste-operaattorin avulla. Siis olionNimi.muuttuja.

Asiakas kalevinKukkakauppa;
kalevinKukkakauppa.kuukausiMaksu = 150; // Kalevi maksaa 150 mk kuukaudessa

On t�rke�t� hahmottaa luokan ja olion ero. Luokka on kuin muotti. Se kertoo mit� ominaisuuksia olioilla on. Olio on taas yksi sill� muotilla tehty piparkakku. Sill� on luokan ominaisuudet, siis muuttujat, mutta niiss� omat arvonsa.

T�m� ei kuitenkaan ratkaise esimerkkimme kahta ongelmaa, y�vahtiasian eroavaisuutta ja asiakkaiden alati muuttuvaa lukum��r��. N�ihin asioihin tarvitaan kuitenkin osoittimia, dynaamista muistinhallintaa ja perint��. Ennen niit� sinun pit�� kuitenkin oppia luokkien perusteet, joten niit� ensin.

Luokka on muutakin kuin pelkk� kasa muuttujia. Luokkaan saa nimitt�in j�senfunktioita eli metodeja. Niiden kautta luokan olioita k�ytet��n. Seuraa lastenmielinen esimerkkiohjelma:

#include<iostream.h>

class Veturi
{
public:
  int nopeus;
  void Puhu();
};

void Veturi::Puhu()
{
  cout  << "Tuut! Tuut! Hurjastelen " << nopeus << " km/h nopeutta!";
}

int main()
{
  Veturi villeVeturi;
  villeVeturi.nopeus = 50;
  villeVeturi.Puhu();
  
  return EXIT_SUCCESS;
}

Olio on tiedot ja toiminnallisuus

Keksitk� mit��n muuta tapaa toteuttaa Veturia matkiva ohjelma? On nimitt�in olemassa toinenkin vaihtoehto... Etk�? Eritt�in hyv�, koska se toinen vaihtoehto on v��r� ja tuo yll� esitetty oikea. Mietip�s mill� tavalla alla oleva muunnelma Veturista on huonompi:

#include<iostream.h>

class Veturi
{
public:
  int nopeus;
};

void Puhu(Veturi veturi)
{
  cout  << "Tuut! Tuut! Hurjastelen " << veturi.nopeus << " km/h nopeutta!";
}

int main()
{
  Veturi villeVeturi;
  villeVeturi.nopeus = 50;
  Puhu(villeVeturi);
  
  return EXIT_SUCCESS;
}

Varmasti mieleesi tulee ainakin, ett� j�lkimm�inen vaihtoehto on ep�looginen. Kun vertaamme puhumista, on villeVeturi.Puhu() eritt�in looginen, sen voisi k��nt�� selkokieliseksi k�skyksi: "Ville Veturi! Puhu!". Puhu(villeVeturi) taas voisi vastata suomen kielen lausetta: "Suorita puheoperaatio Ville Veturilla". Kuulostaa v�h�n KELA:n lomakkeen t�ytt�ohjeilta...

Kuten huomaat, kun ohjelman metodit (j�senfunktiot) sijoitetaan samaan luokkaan ohjelman tietojen kanssa, saavutetaan kaksi etua: ohjelma vaikuttaa loogiselta eli on sellainen kuin terveell� maalaisj�rjell� sen kuvittelisi olevan, ja sen lis�ksi ohjelman l�hdekoodi on selke�mp��. Minusta ainakin veturin antaminen Puhu()-funktioille on hankalaa verrattuna luokan sis�ll� toteutettuun puhumiseen, jossa vastaavaa hankaluutta ei tarvitse tehd� - ollaanhan jo valmiiksi oikean olion "sis�ll�" (tuolle "sis�ll� olemiselle" on my�s teknisempi m��ritelm�, mihin palaamme my�hemmin). Ja nytp� olemmekin tunkeutuneet C++-ohjelmoinnin, ja olio-ohjelmoinnin yleens�, kaikkein pyhimp��n. Olio-ohjelmointi on sit�, ett� tiedot (muuttujat) ja toiminnallisuus (metodit) ovat yhdess�. Kaikki mit� t�m�n j�lkeen kerron ei ole l�hellek��n yht� t�rke�� kuin tuo edellinen havainto. 

OLIO = TIEDOT + TOIMINNALLISUUS

Haluaisin viel� lis�t�, ett� tiedon ja toiminnallisuuden yhdist�minen ei ole pelk�st��n loogista, vaan johtaa parempiin ohjelmiin. Kun luokkaan on paketoitu sek� tiedot ett� toiminnot, syntyy "musta laatikko". Luokan k�ytt�j�n ei tarvitse v�litt�� siit� miten luokka itseasiassa toimii, kunhan se toimii oikein. Ohjelmoidessa voi hyvinkin tuntua, ett� p��n sis�ll� on menossa italialaisen talonyhti�n kokous, siis kaaos. Juuri t�t� varten olio-ohjelmoinnissa koitetaan piilottaa kaikki turha, joka muuten kuluttaisi ohjelmoijan kallista mutta v�h�ist� (?) aivokapasiteettia.

Tiedon kapselointi

Tiedon kapselointi. Armottomana ihmisen� iskin sinua suoraan p�in kasvoja yhdell� hyvin t�rke�ll� oliopohjaisen ajattelun termill�. Viel� uudestaan: tiedon kapselointi. Idea on, ett� jokaiseen k�sitteeseen liittyv�t tiedot on kapseloitu siihen k�sitteeseen (olioon), eiv�tk� muut p��se niit� sorkkimaan. Tiedon kapselointi! Edellisess� esimerkiss� tietoa ei oltu kapseloitu, vaan se roikkui villin vapaana kuin Kemppaisen kolli kes�housuissaan.

Jos mietit edellista esimerkki�, niin huomaat ett� kuka tahansa p��see sorkkimaan luokan muuttujia. Mit� kapselointia ja suojaamista se on muka olevinaan? Nyt konkari vet�� takataskustaan taika-avaimen, nimitt�in varatun sanan private. private on publicin kaveri. Kun public m��rittelee julkisia j�senmuuttujia ja funktioita, niin private m��rittelee yksityisi�. Julkiset muodostavat olion k�ytt�liittym�n, tavan jolla muut ohjelman osat kommunikoivat olion kanssa. Yksityiset osat muodostavat luokan sis�isen toiminnan. Ne ovat muuttujia ja funktioita, joita luokka tarvitsee omaan sis�iseen toimintaansa ja joita ulkopuoliset eiv�t p��se sotkemaan. Yksityiseksi m��ritelty� tietoa voivat k�ytt�� vain luokan omat metodit, j�senmuuttujat. Luokan j�senet ovat oletuksena yksityisi�.

Esittelemme nyt lis�� h�yrykoneiden kauhuja.

#include<iostream.h>

const int MAX_NOPEUS = 180;

class Veturi
{
public:
  void AsetaNopeus(int n);
  void Kiihdyta();
  void Jarruta();
  int LueMittari(); // nk. saantifunktio
private:
  int nopeus;
};

void Veturi::AsetaNopeus(int n)
{ 
  nopeus = n;
}

void Veturi::Kiihdyta()
{
  nopeus += 20;
  if (nopeus > MAX_NOPEUS) nopeus = MAX_NOPEUS;
}

void Veturi::Jarruta()
{
  nopeus -= 20;
  if (nopeus < 0) nopeus = 0;
}

int Veturi::LueMittari()
{
  return nopeus;
}

int main()
{
  Veturi villeVeturi;
  villeVeturi.AsetaNopeus(0);

  villeVeturi.Kiihdyta();
  villeVeturi.Kiihdyta();
  cout << "Nopeutta " << villeVeturi.LueMittari() << " km/h!" << endl;

  villeVeturi.Jarruta();
  cout << "Nopeutta " << villeVeturi.LueMittari() << " km/h!" << endl;

  // villeVeturi.nopeus = 50;
  
  return EXIT_SUCCESS;
}

Veturi osaa muuttaa nopeuttaan ja ilmoittaa sen. Nopeus asetetaan nollaksi olion luonnin yhteydess�. Ensin veturi kiihdytet��n 40 km/h nopeuteen ja nopeus tulostetaan. Sitten jarrutetaan ja nopeus ilmoitetaan uudestaan. Huomaa viimeisell� rivill� oleva viittaus yksityiseen muuttujaan. Jos sit� ei olisi kommentoitu, se ei olisi mennyt k��nt�j�st� l�pi. Yksityinen tieto ei ole ulkopuolisten k�ytett�viss�.

Nopeus on varma muuttuja. Jarruta() ja Kaasuta() metodit valvovat tehokkaasti nopeuden k�ytt��. Jos pelk��t, ett� muuttujien k�ytt� erillisten funktioiden kautta hidastaa ohjelmaa, niin h�ps�n p�ps�n. Hyv� k��nt�j� optimoi saantifunktiot olemattomiin. Huomaa kohta: k��nt�j� optimoi. Samalla k��nt�j� tarkistaa tietotyypit. Eli k��nt�j� tekee enemm�n ty�t�, mutta lopputulos (konekielinen ohjelma) on sama. Metodin AsetaNopeus(int) avulla nopeuden voi kuitenkin sotkea pahanp�iv�isesti. Ihannetilanne olisi semmoinen, jossa luokan k�ytt�j� ei pystyisi sotkemaan ollenkaan luokan j�senmuuttujien arvoja. Kun kirjoittaa v�h�n pitempi� ja monimutkaisempia ohjelmia, jaksaa t�llaist� ominaisuutta kyll� arvostaa... Niinp� pit�isi olla joku kikka, jolla nopeus voitaisiin alustaa olion luonnin yhteydess� ja AsetaNopeus(int) metodi tulisi tarpeettomaksi... n�en horisontissa h��m�tt�v�n aasinsillan...

 

Muodostin- ja tuhoajafunktiot

Usein oliota luodessa t�ytyy tehd� jotain toimia, kuten varata muistia luokan tiedoille. Ei kannata j�tt�� luokan k�ytt�j�n vastuulle kutsua homman hoitavaa funktiota, vaan k�ytt�� luokkaan liittyv�� muodostinfunktiota (constructor). Kun luokka luodaan, luo k��nt�j� sille automaattisesti muodostinfunktion - joka ei tee mit��n. Jos haluamme laittaa jotain toimia luokan tyyppisen olion luonnin yhteyteen, voimme korvata muodostinfuktion omallamme. Muodostinfunktion nimi on luokan nimi ja sill� ei ole paluuarvoa. Muodostinfunktio pit�� tietenkin m��ritell� julkiseksi.

#include<iostream.h>

class Auto
{
public:
  Auto();	// muodostin
};

Auto::Auto()
{
  cout << "Muodostinta kutsuttu";
}

int main()
{
  Auto lada;
  
  return EXIT_SUCCESS;
}

Muodostinfunktiolle voi my�s v�litt�� parametrej� ja siit� voi kirjoittaa monta erilaista versiota eri parametreill� - polymorfismi siis toimii t�ss�kin suhteessa. Muodostinfunktion parina toimii tuhoajafunktio (destructor). Sit� kutsutaan kun olio tuhotaan. Sen m��rittely on muuten vastaava, mutta funktion nimi alkaa ~ (tilde, mato)-merkill�. Jos m��rittelet oman muodostinfunktion, niin k��nt�j� j�tt�� oman tyhj�n muodostimensa m��rittelem�tt�. Siis jos m��rittelet yhden int-parametrin ottavan muodostimen, ei luokkaa voida en�� luoda ilman parametrej�, koska tyhj� parametriton muodostin j�� m��rittelem�tt�.

Rakennamme nyt luokan, jota voidaan k�ytt�� hyvin yksinkertaiseen tiedon kryptaamiseen, siis salaamiseen. Siin� on taulukollinen unsigned char muuttujia. Taulukko varataan dynaamisesti (dynaamisesta varauksesta kerron tarkemmin sitten my�hemmin), sen koko annetaan oliota luodessa. Jos kokoa ei anneta, k�ytet��n oletuskokoa. Tietenkin varattu muisti vapautetaan kun olio lakkaa olemasta. Itse kryptausosa on mukana l�hinn� aidon tunnelman saavuttamiseksi.

const int OLETUS_KOKO = 256;

class KryptattuTaulukko
{
public:
  KryptattuTaulukko();
  KryptattuTaulukko(int koko);
  ~KryptattuTaulukko();

  void Koodaa();

  unsigned char *taulukko;
  int taulukonKoko;
};

KryptattuTaulukko::KryptattuTaulukko()
{
  taulukko = new unsigned char[OLETUS_KOKO]; // varataan muisti
  taulukonKoko = OLETUS_KOKO;
}

KryptattuTaulukko::KryptattuTaulukko(int koko)
{
  taulukko = new unsigned char[koko]; // varataan muisti
  taulukonKoko = koko;
}

KryptattuTaulukko::~KryptattuTaulukko()
{
  delete [] taulukko; // vapautetaan muisti
}

void KryptattuTaulukko::Koodaa()
{
  for (int i=0; i < taulukonKoko; i++)
    taulukko[i] = 255 ^ taulukko[i]; // py�r�ytet��n bitit ymp�ri
}

Pari puutetta viel� yll� olevan ohjelman rakenteessa on, mutta ne eiv�t ole niin oleellisi� - viel�.

Muodostinfunktiossa muuttujien alustus voidaan hoitaa my�s itse funktion rungon ulkopuolella, t�h�n tyyliin:

KryptattuTaulukko::KryptattuTaulukko() : taulukonKoko(OLETUS_KOKO)
{
	taulukko = new unsigned char[OLETUS_KOKO];
}

Siis muodostinfunktion nimen j�lkeen kaksoispiste, sitten muuttujien alustus. Muuttujan arvo annetaan suluissa, muuttujan nimen per�ss�. T�m� tapa on suositeltavampi kuin muodostinfunktion rungossa muuttujien alustaminen. Yll� oleva funktio on karsea esimerkki muodostinfunktiosta. Koska muodostinfunktio ei palauta arvoa, se ei voi viesti� onnistumisesta mitenk��n. Ja koska se varaa muistia, voi ongelmiakin tulla. Muodostinfunktiossa ei saa tehd� mit��n, mik� voi ep�onnistua (muistin varaus, tiedostojen avaus jne...). Joten luokan kehittely jatkuu, kunhan vaan ved�mme operaattorit ensin k�ytt��n.

 

Operaattoreiden ylikuormittaminen

Mik�li et ole viel� tutustunut String-luokkaan, niin kertoilenpa siit� t�ss� aluksi. Merkkijonojen ja String-luokan k�ytt� on tarkemmin esitelty standardikirjastojen yhteydess�. String luokka on kuin yhdistetty luokka ja taulukko. Toisaalta sill� on muodostin- ja tuhoajafunktiot, mutta toisaalta sit� voi k�ytt�� indeksointioperaattorilla kuin tavallista taulukkoa. Siis String-luokan olioita voi k�ytt�� n�in: merkkijono[3]. T�m� palauttaa merkkijonon nelj�nnen merkin. Vaikka String onkin k��nt�j�n mukana tuleva, ei siihen kuitenkaan liity mit��n maagisia, kuolevaisille saavuttamattomia temppuja. Pelk�st��n operaattoreiden ylikuormittamista, mik� kyll� tavallisille kuolevaisille voi vaikuttaa mystisten voimien ty�lt� - mutta ei C++-velholle!

Kun k��nt�j�n pit�� k�sitell� +-operaattori kahden int-luvun yhteydess�, ei se ole mik��n ongelma. Kai nyt jokainen pari lukua osaa yhteen ynn�t�. Mutta ent� jos k��nt�j�� k�sket��n ynn��m��n kaksi itse ohjelmoidun Tyontekija-luokan oliota? K��nt�j� ei tunne luokan ominaisuuksia, joten ainoa heti mieleen tuleva ratkaisu olisi laskea olioiden muistiosoitteet yhteen. Ratkaisu onkin mahdollinen, mutta j�rjeton. Muistiosoitteella ei ole mit��n tekemist� luokan toiminnan kanssa ja tulos osoittaisi mihin sattuu. Parempi vaihtoehto olisi vaikka laskea yhteen ty�ntekij�iden palkat. N�in �lyk�st� toimintaa emme kuitenkaan C++-k��nt�j�lt� voi odottaa, vaan meid�n pit�� se itse ohjelmoida.

Ensimm�inen ratkaisu olisi tehd� Tyontekija-luokkaan metodi Lisaa(Tyontekija&). Lisays tehtaisiin nain:

Tyontekija jamppa;
Tyontekija jomppa;
palkatYhteensa = jomppa.Lisaa(jamppa);

Tuo ei kuitenkaan ole hirve�n havainnollinen tapa. Me nimitt�in voimme ohjelmoida Tyontekija-luokkaan +-operaattorin, siis ylikuormittaa +-operaattorin. C++ antaa mahdollisuuden ylikuormittaa seuraavat operaattorit:

+  -  *  /  %  ^  &  |  ~  !  =  <  >  +=  -=  *=  /=  %=  ^=  &=  |=  
<<  >>  >>=  <<=  ==  !=  <=  >=  &&  ||  ++  --  ->*  ,  ->  []  ()  
new  delete

Ylikuormitettu operaattori on funktio, joka saa operaattorista riippuvat parametrit. Se siis toimii kuin Lisays(Tyontekija&) -funktio toimisi aikaisemmassa esimerkiss�. Operaattorifunktiota voidaan kuitenkin kutsua niin kuin operaatoreita k�ytet��n. Funktio m��ritell��n operator-sanalla. Sen per��n kirjoitetaan haluttu operaattori, siis vaikka operator+. Jotta operaattorit ++olio ja olio++ voitaisiin erottaa toisistaan, m��ritell��n j�lkimm�isempi (postfix-muoto)

int operator(int) {}

Siis se saa int-tyyppisen haamuparametrin, parametrin joka ei sis�ll� mit��n. T�ss�p� sit� on sitten ylikuormitusta kullekin.

#include <iostream.h>

class Tyontekija
{
public:
    void AsetaPalkka(int p) { palkka = p; }
    int AnnaPalkka() { return palkka; }
    int operator+(Tyontekija& toinen) { return palkka + toinen.AnnaPalkka(); }

private:    
    int palkka;
};

int main()
{
    Tyontekija jamppa; jamppa.AsetaPalkka(8000);
    Tyontekija jomppa; jomppa.AsetaPalkka(12000);


    cout << "Kaveruksille pit�� maksaa yhteens� " << jomppa + jamppa << endl;
    
    return EXIT_SUCCESS;
}

Nyt voimme kirjoittaa kryptatusta taulukosta hienon luokan. Sit� voi k�ytt�� kuin taulukkoa, mutta se on kuitenkin luokka - siis sijoituksien oikeellisuus voidaan tarkistaa, muutamia hienouksia mainitakseni.


#include 

const int OLETUS_KOKO = 256;

class KryptattuTaulukko
{
public:
	KryptattuTaulukko(int koko = OLETUS_KOKO); // oletusparametri
	~KryptattuTaulukko();
	
	int Alusta(); // todellinen muodostinfunktio
	void Koodaa();
	
	int AnnaKoko() { return taulukonKoko; }
	int OnkoKoodattu() { return onKoodattu; }
	int OnkoAlustettu() { return onAlustettu; }
	
	int& operator[](unsigned kohta); // voidaan sijoittaa kuin taulukkoon
	int operator[](unsigned kohta) const; // voidaan lukea kuin taulukkoa
	void operator=(const int* arvot);// voidaan sijoittaa taulukollinen arvoja
	
private:
	int onAlustettu;
	int onKoodattu;
	int *taulukko;
	int taulukonKoko;
        int virhe; // virheelliset viittaukset ohjataan t�nne
};

KryptattuTaulukko::KryptattuTaulukko(int koko) : onAlustettu(0), onKoodattu(0),
						taulukonKoko(koko), virhe(0)
{
	// tyhj� muodostimen runko
}

int KryptattuTaulukko::Alusta()
{
	taulukko = new int[taulukonKoko];
	
	if (taulukko == 0) return 0;
	
	onAlustettu = 1;
	return 1;
}

KryptattuTaulukko::~KryptattuTaulukko()
{
	if (onAlustettu) delete [] taulukko;
}

int& KryptattuTaulukko::operator[](unsigned kohta) 
{
	if (onAlustettu) {
		if (kohta < taulukonKoko) return taulukko[kohta]; // laillinen sijoitus
		else return virhe; // ylivuoto
	}
	else return virhe; // vakion nolla palauttaminen ei olisi mahdollista, koska se ei ole olemassa kuin metodissa
}

int KryptattuTaulukko::operator[](unsigned kohta) const // unsigned est�� laittomat negatiiviset indeksit
{
	if (onAlustettu) {
		if (kohta < taulukonKoko) return taulukko[kohta];
		else return 0; 
	}
	else return 0; // ei tarvita viitattavaa kohdetta, joten nolla k�y
}

void KryptattuTaulukko::operator=(const int* arvot)
{
	// vaarallista, const int* arvot eiv�t v�ltt�m�tt� osoita taulukonKoko kokoiseen muistialueeseen...
	if (onAlustettu) for (int a = 0; a < taulukonKoko; a++) taulukko[a] = arvot[a];

}

void KryptattuTaulukko::Koodaa()
{
	for (int i=0; i < taulukonKoko; i++)
		taulukko[i] = 255 ^ taulukko[i]; // py�r�ytet��n bitit ymp�ri
	
	if (onKoodattu) onKoodattu = 0; // vaihdetaan koodauksen tila
	else onKoodattu = 1;
}



int main()
{
	int alkupTaulu[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
	KryptattuTaulukko taulu(10);
	
	taulu[5] = 5; // ei tee mit��n, koska ei alustettu
	
	if (!taulu.Alusta()) return EXIT_FAILURE; // taulukon luonti ep�onnistui
	taulu = alkupTaulu; // kopioidaan alkupTaulu taulu:n
	
	taulu[-1] = 5; // kumpikin laittomia sijoituksia, eiv�t tee mit��n
	taulu[100] = 5;
	
	taulu.Koodaa();
	cout << "Koodattu taulu: ";
	for (int a = 0; a < 10; a++) cout << taulu[a] << " ";
	taulu.Koodaa();
	
	cout << endl << "Koodaamaton taulu: ";
	for (int b = 0; b < 10; b++) cout << taulu[b] << " ";
	cout << endl << endl;
	
	cout << "Ensimm�inen alkio on nyt " << taulu[0] << endl;
	taulu[0] = 7;
	cout << "Sijoituksen j�lkeen se on " << taulu[0] << endl << endl;
	
	cout << "Tilamuuttujat: koodaus:" <

 

coutin todellinen luonne

Itse olen joskus kummastellut: "C++:ss� on olemassa muuttujia, funktioita, luokkia ja olioita. Mik� ihmeen vetkutin se cout sitten on?" Vaikka ihan ensimm�isest� ohjelmasta asti olet k�ytt�nyt cini� ja coutia, niin silti niiden olemus lienee hieman ep�selv�.

cin ja cout ovat olioita. Ne ovat iostream.h:ssa (tai sen alaisessa otsikkotiedostossa) valmiiksi m��riteltyj� olioita. Niist� l�ytyy uudelleenm��riteltyn� << tai >> operaattori. Siksi siis kun k�skemme coutia tulostamaan - siis kutsumme sen j�senfunktiota - ei toimenpide vaikuta milt��n funktiokutsulta, vaikka se sit� onkin. Sellaisia peijooneita ne operaattorit ovat, varsinkin uudelleenm��ritellyt. << -operaattorin k�ytt�minen kutsuu todellisuudessa operator<<(...) -funktiota, jonka me alla olevassa esimerkiss� esiinmerkitsemme:

#include <iostream.h>

int main()
{
	int luku = 5;
	
	cout << "Ik�ni on " << luku << endl;
	cout.operator<<("Ik�ni on ").operator<<(luku).operator<<(endl);
	
	return EXIT_SUCCESS;
}

Esimerkin kahden tulostuslauseen tulostama teksti on sama, k�yt�mme funktiokutsun mit� muotoa tahansa. Monien per�kk�isten funktiokutsujen ketjuttaminen onnistuu sen vuoksi, ett� operator<<(...) palauttaa viittauksen coutiin. Siis alku:

cout.operator<<("Ik�ni on ") 

... muuttuu funktiokutsun j�lkeen lausekkeeksi...

cout

.. ja sen per��n voi lyk�t� uutta funktiokutsua ihan rauhassa. Kuten ehk� arvasitkin, n�m� pohdinnot ovat kuin hyv� ranskanleip�: ne toimivat my�s toisesta p��st� pureskeltuna - siis sama idea p�tee ciniin, operaattorin nuolet ovat vain toiseen suuntaan.

 

Osoitin j�senfunktioon

Piti sitten t�llekin asialle oma kappale kyh�t�. No, nyt on komia paragraafi, niin pit�� sitten jotakin yritt�� t�h�n rustata.

void (Luokka::*pFunktio)(int, char);

Voi kehveli, tuohon se nyt lipsahti. Mahtaako sit� en�� mit��n sanoa? Siis * merkkaamaan osoitinta, luokka ja n�kyvyysoperaattori (siis :: ) alkuun. Ja asteriski eli * ennen funktion nime�, ei koko rimpsun alkuun, koska luokalla ei olla osoittelemassa mihink��n.

Takaisin