Omat tietotyypit. Mikä on olio?



Samankaltaiset tiedostot
Osoitin ja viittaus C++:ssa

Luokat. Luokat ja olio-ohjelmointi

Virtuaalifunktiot ja polymorfismi

Periytyminen. Luokat ja olio-ohjelmointi

Tietueet. Tietueiden määrittely

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Taulukot. Jukka Harju, Jukka Juslin

Ohjelmointi 2. Jussi Pohjolainen. TAMK» Tieto- ja viestintäteknologia , Jussi Pohjolainen TAMPEREEN AMMATTIKORKEAKOULU

C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa.

Operaattoreiden uudelleenmäärittely

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Luokan operaatiot. Osoittimet ja viittaukset luokan olioihin

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)

12 Mallit (Templates)

Osoittimet. Mikä on osoitin?

Ohjelmointi 1 Taulukot ja merkkijonot

Java-kielen perusteet

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat:

T Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

Mallit standardi mallikirjasto parametroitu tyyppi

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

Ohjelman virheet ja poikkeusten käsittely

13 Operaattoreiden ylimäärittelyjä

Kääntäjän virheilmoituksia

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Ohjelmoinnin perusteet Y Python

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

Kirjoita oma versio funktioista strcpy ja strcat, jotka saavat parametrinaan kaksi merkkiosoitinta.

Alkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5)

4. Luokan testaus ja käyttö olion kautta 4.1

ITKP102 Ohjelmointi 1 (6 op)

Merkkijono määritellään kuten muutkin taulukot, mutta tilaa on varattava yksi ylimääräinen paikka lopetusmerkille:

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin perusteet Y Python

Harjoitustyö: virtuaalikone

Java-kielen perusteet

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. X Poikkeusten käsittelystä

Ohjelmoinnin perusteet Y Python

Taulukot. Taulukon määrittely ja käyttö. Taulukko metodin parametrina. Taulukon sisällön kopiointi toiseen taulukkoon. Taulukon lajittelu

Kerta 2. Kerta 2 Kerta 3 Kerta 4 Kerta Toteuta Pythonilla seuraava ohjelma:

tietueet eri tyyppisiä tietoja saman muuttujan arvoiksi

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Lyhyt kertaus osoittimista

Pythonin alkeet Syksy 2010 Pythonin perusteet: Ohjelmointi, skriptaus ja Python

Ohjelmoinnin perusteet Y Python

Esimerkkiprojekti. Mallivastauksen löydät Wroxin www-sivuilta. Kenttä Tyyppi Max.pituus Rajoitukset/Kommentit

Olio-ohjelmoinnissa luokat voidaan järjestää siten, että ne pystyvät jakamaan yhteisiä tietoja ja aliohjelmia.

Ohjelmointiharjoituksia Arduino-ympäristössä

Ohjelmoinnin perusteet Y Python

T Olio-ohjelmointi Osa 3: Luokka, muodostin ja hajotin, this-osoitin Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

Java kahdessa tunnissa. Jyry Suvilehto

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä

2. Olio-ohjelmoinista lyhyesti 2.1

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen

Muuttujien roolit Kiintoarvo cin >> r;

Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes)

5.6. C-kielen perusteet, osa 6/8, Taulukko , pva, kuvat jma

C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi

ITKP102 Ohjelmointi 1 (6 op)

Tietotyypit ja operaattorit

Rakenteiset tietotyypit Moniulotteiset taulukot

Ohjelmoinnin perusteet Y Python

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

Sisältö. 2. Taulukot. Yleistä. Yleistä

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

Geneeriset luokat. C++ - perusteet Java-osaajille luento 6/7: Template, tyyppi-informaatio, nimiavaruudet. Geneerisen luokan käyttö.

C++ rautaisannos. Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout:

Ohjelmoinnin jatkokurssi, kurssikoe

3.1 Mitä tarkoittaan heredoc? Milloin sitä kannattaa käyttää? Kirjoita esimerkki sen käyttämisestä.

Tietojen syöttäminen ohjelmalle. Tietojen syöttäminen ohjelmalle Scanner-luokan avulla

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

SQL-perusteet, SELECT-, INSERT-, CREATE-lauseet

Tehtävä 1. TL5302 Olio-ohjelmointi Koe Malliratkaisuja. Tässä sekä a)- että b)-kohdan toimiva ratkaisu:

Ohjelmoinnin peruskurssi Y1

Sisältö. 22. Taulukot. Yleistä. Yleistä

Javan perusteita. Janne Käki

ITKP102 Ohjelmointi 1 (6 op)

7. Oliot ja viitteet 7.1

Tähtitieteen käytännön menetelmiä Kevät 2009 Luento 4: Ohjelmointi, skriptaus ja Python

15. Ohjelmoinnin tekniikkaa 15.1

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

Ohjelmointi funktioiden avulla

Ohjelmoinnin peruskurssi Y1

15. Ohjelmoinnin tekniikkaa 15.1

Olio-ohjelmointi Javalla

3. Muuttujat ja operaatiot 3.1

Johdatus Ohjelmointiin

Ohjelmoinnin perusteet Y Python

Osa. Listaus 2.1. HELLO.CPP esittelee C++ -ohjelman osat. 14: #include <iostream.h> 15: 16: int main() 17: {

INSIDE C++ Ohjelmoijan käsikirja. Ivor Horton WROX PRESS

815338A Ohjelmointikielten periaatteet

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

Metodien tekeminen Javalla

Transkriptio:

Omat tietotyypit 11 Omat tietotyypit C++:n suuri vahvuus on sen oliopohjaisuudessa. Siihen liittyy runsaasti asiaa ja kulutammekin seuraavat viisi lukua tässä aiheessa. Tässä ja seuraavassa luvussa käsittelemme olio-ohjelmoinnin perustana olevaa asiaa - omien tietotyyppien määrittelyä. Mutta oliopohjaisuuteen liittyy paljon muutakin kuin vain uusien tyyppien lisääminen. Olio-ohjelmointi tarjoaa tehokkaan lähestymistavan, joka on perusteiltaan aivan erilainen kuin olemme tähän saakka nähneet. Luvuissa 13-16 käsittelemme yksityiskohtaisesti kaikki C++:n tekniikat, joita saatat tarvita luodessasi omia tietotyyppejäsi. Tässä luvussa esittelemme joitakin olioiden perusteita ja joitakin niiden yksinkertaisia käyttömahdollisuuksia. Tämä antaa perustan seuraavalle luvulle, jossa käsittelemme olio-ohjelmoinnin periaatteita tarkemmin. Seuraavassa luvussa käsittelemme lisäksi luokkia ja niiden määrittelyä. Tämän luvun aiheita ovat: Mikä on olio Mikä on struktuuri ja miten niitä esitellään Miten struktuuriolioita määritellään ja käytetään Miten struktuuriolion jäseniä käsitellään Mikä on unioni Mikä on olio? C++-kieli hyödyntää olio-ohjelmoinnin etuja ja tässä luvussa aloitamme siihen liittyvien käsitteiden läpikäynnin. Aluksi meidän pitää käsitellä kaiken perustana olevat rakennusosat. Eli, mikä olio tarkalleen ottaen on? Tavallaan tämä on vaikea kysymys vastattavaksi - juuri siksi, että olio voi olla ihan mitä sinä itse haluat. Itse asiassa, olemme (tavallaan) käyttäneet olioita ohjelmissamme jo kirjan alusta saakka. 413

C++ Ohjelmoijan käsikirja Olio on tietotyypin ilmentymä. Oletetaan, että teemme seuraavanlaisen muuttujan määrittelyn: int markat = 0;! Tässä olemme tehneet tyypin int yhden ilmentymän. Olemme antaneet tälle ilmentymälle nimen, markat. Voimme sanoa, että markat on int-tyyppinen olio. Tämä on riittävän totta tässä kohtaa. Tarkka ohjelmoija voisi kuitenkin väittää, että tarkalleen ottaen näin ei ole: C++ erottaa käyttäjän määrittelemien tietotyyppien (kutsutaan olioiksi) ja perustietotyyppien (kutsutaan vain ilmentymiksi) ilmentymät toisistaan. Oliolla on arvo - kokonaisluvulla markat on arvo 0. Operaatiot, joita voimme oliolle (ja olion kanssa) suorittaa, on tarkasti määritelty. Esimerkiksi oliolla markat (int-tyyppinen) on joukko sallittuja operaatioita - kuten aritmeettiset operaatiot, esimerkiksi + ja -, ja vertailuoperaattorit, esimerkiksi <, ==. On olemassa myös operaatioita, jotka eivät ole sallitut tietylle oliolle, koska operaatiota ei ole määritelty kyseiselle oliolle. Tarkastellaan jälleen esimerkkioliotamme markat. Et voi esimerkiksi lisätä oliota markat merkkijonoon etkä voi ottaa siitä alimerkkijonoa. Tämä siitä syystä, että näitä operaatioita ei ole määritelty int-tyyppiselle oliolle. Kuten olemme huomanneet, C++ sisältää joukon standardeja perustietotyyppejä, mutta on selvää, että ohjelmointi olisi huomattavasti rajoittuneempaa, jos emme voisi määritellä omia tietotyyppejämme. Joten voimme C++:ssa tämän tehdä - voimme määritellä omia tietotyyppejämme ja niille sallitut operaatiot. Määrittelemiäsi tyyppejä kutsutaan yleensä käyttäjän määrittelemiksi tietotyypeiksi, mutta nimestä ei seuraa mitään rajoituksia niille. Omat tietotyyppisi voivat olla niin monimutkaisia kuin vain sovelluksesi tarvitsee ja kuten tulemme huomaamaan, niille voidaan soveltaa aivan samoja operaattoreita kuin perustietotyypeillekin. Luokka on käyttäjän määrittelemä tietotyyppi ja yleensä määrittelemme luokkia avainsanan class avulla. Älä sekoita luokkia typedef-komennon käyttöön eikä lueteltuihin tyyppeihin. typedef-komento ei luo uutta tyyppiä, vaan se luo peitenimen jo olemassa olevalle tyypille. Jokainen lueteltu tyyppi on oma tyyppinsä, mutta ne eivät ole luokkia. Luokka on aivan uusi tyyppi, jolla on ominaisuuksiensa lisäksi omat operaatiot, jotka olet sille määritellyt. 414 Tutustuessamme luokkiin, käsittelemme kahta muuta tapaa luoda omia tyyppejä. Ne ovat unionit ja struktuurit; näiden luonnissa käytetään avainsanoja union ja struct. Teknisesti ne ovat myös luokkia; tarkoitamme kuitenkin luokalla tyyppiä, joka on luotu class-avainsanalla ja näitä kahta kutsumme unioniksi ja struktuuriksi. Struktuuri on varsin samanlainen kuin luokka, joten struktuurien perusteet kannattaa käydä läpi ennen unionien käsittelyä. Näiden jälkeen käsittelemme luokkia tarkemmin seuraavissa kahdessa luvussa.

C++:n struktuurit Omat tietotyypit Historiallisesti ajatellen, struktuuria käytettiin jo C-kielessä - se oli vain eri tyyppiä olevien tietoalkioiden yhdistelmä. Voit esimerkiksi yhdistää char*-tyyppisen muuttujan nimi, inttyyppisen muuttujan ika ja char-tyyppisen muuttujan sukupuoli yhdeksi struktuuriksi henkilo. C++:ssa struktuuria käytetään yleensä samaan tehtävään. Tulemme kuitenkin huomaamaan, että C++:n struktuuri pystyy huomattavasti parempaan kuin C:n struktuuri. C++-kielen kehityksessä luokan perustana oli C:n struktuuri, jota sitten laajennettiin. C++:n struktuurit saivat myös nämä uudet ominaisuudet. Itse asiassa luokka voidaan toiminnallisesti korvata C++:n struktuurilla. Miksi meidän sitten pitää kuluttaa aikaa niinkin vanhentuneen asian kuin struktuurien käsittelyyn? No ei se nyt niin vanhentunut ole ja samaan tapaan kuin Mount Everest vuorikiipeilijälle, struktuuri on osa C++-kieltä. Ja vieläkin tärkeämpi seikka, joka tukee struktuurien läpikäyntiä on se, että sitä käytetään vielä runsaasti. Esimerkiksi Microsoft Windows -käyttöjärjestelmä on täysin riippuvainen struktuureista - joten jos kirjoitat ohjelman tähän ympäristöön, sinun tulee todellakin tuntea struktuureiden perusteet sekä miten niitä määritellään ja käytetään. Lisäksi struktuurien käsittely ei mene hukkaan, sillä niiden ominaisuudet ovat samat kuin luokillakin. Huomaa myös seuraava seikka: C++:ssa struktuuri on luokka. Kuten tulemme seuraavissa kahdessa luvussa huomaamaan, ainut ero struktuurin ja luokan välillä on tietyssä oletusarvoisessa käyttäytymisessä, joka johtuu struktuurien ja luokkien erilaisista toteutustavoista C++:ssa. Struktuurit Tähän saakka tässä kirjassa käsittelemämme muuttujat ja tietotyypit ovat muodostuneet yhdestä oliosta - esimerkiksi numerosta, merkistä tai merkkijonosta. Elävässä elämässä kaikki on kuitenkin huomattavasti monimutkaisempaa kuin näillä perustietotyypeillä voidaan esittää. Jotta voimme kuvata elävän elämän asioita, meidän tulee pystyä määrittelemään useita arvoja, jotka ovat usein erityyppisiä numeroita. Tästä ajatuksesta struktuurit juontavat juurensa. Ajatellaan esimerkiksi niinkin yksinkertaista asiaa kuin kirjan kuvaamiseksi tarvittavaa tietoa. Mieleen tulee aluksi kirjan nimi, tekijä, kustantaja, julkaisupäivä, sivujen lukumäärä, hinta ja ISBN-numero - lisäksi saattaa olla useita muitakin tietoja. Tunnistaaksesi kirjan ohjelmassasi, voisit määritellä erilliset muuttujat kullekin edellä olleelle tiedoille. Selvästi parempi ratkaisu olisi kuitenkin yhden tietotyypin käyttö - jota kutsumme kirjaksi. Tämä tyyppi sisältää kaikki nämä ominaisuudet. Tämä on juuri se, mitä voit struktuurien avulla tehdä. Struktuuri on tietotyyppi, joka määrittelee haluamasi tyyppisen olion. 415

C++ Ohjelmoijan käsikirja Struktuurin määrittely Käsitellään kirja-tyyppiä lisää. Oletetaan, että haluamme ottaa kirja-tyyppiin mukaan vain kirjan nimen, tekijän, kustantajan ja julkaisuvuoden. Voimme esitellä struktuurin, joka sisältää nämä tiedot: struct Kirja char nimi[80]; char tekija[80]; char kustantaja[80]; int vuosi; Tämä esittely ei määrittele yhtään muuttujaa; se määrittelee vain uuden tyypin kirja. Kääntäjä käyttää tätä pohjana luodessaan uusia kirja-tyyppisiä olioita. Avainsana struct esittelee, että kirja on struktuuri. Alkiot, jotka muodostavat kirja-tyyppisen olion, on lueteltu aaltosulkeissa. Huomaa, että jokaisen alkion määrittely päättyy puolipisteeseen ja struktuurin päättävän aaltosulkeen perään tulee myöskin puolipiste - eli se on juuri samanlainen kuin tavallinenkin esittely. Nimi Tekija Kustantaja Vuosi Kirja-tyyppinen olio The Selfish Gene Richard Dawkins Oxford University Press 1989 Alkioita nimi, tekijä, kustantaja ja vuosi, jotka on kirjoitettu aaltosulkeiden sisään, kutsutaan yleensä struktuurin kirja jäsenmuuttujiksi (tai yleisemmin jäseniksi). Jokainen kirja-tyypin muuttuja sisältää jäsenet nimi, tekijä, kustantaja ja vuosi. Struktuurityyppisen olion tallettamiseen tarvittava muistimäärä on kaikkien alkioiden viemä muistitila yhteensä. Esimerkiksi kirja-tyyppinen olio vie tilaa niin paljon, että siihen mahtuu kolme merkkitaulukkoa ja kokonaisluku. Voimme käyttää luvussa kolme käsittelemäämme sizeof()-operaattoria tietyn tyyppisen olion viemän muistimäärän selville saamiseksi. (Seuraavassa luvussa käsittelemme tätä lisää.) 416 Struktuurin alkiot voivat olla minkä tyyppisiä tahansa, poislukien määriteltävänä oleva tyyppi. Eli esimerkiksi tyypin kirja määrittely ei voi sisältää tyyppiä kirja. Mitä tapahtuisikaan, jos näin voisimme tehdä: sinun tulisi alustaa sen sisältämä kirja-tyyppinen alkio, joka puolestaan sisältää jäsenen kirja ja niin edelleen. Itse asiassa uloimman muuttujan koko sizeof()-operaattorilla laskettuna olisi ääretön! Tyypin kirja määrittely voi kuitenkin sisältää osoittimen tyyppiin kirja ; tämä on hyödyllinen ominaisuus, kuten pian huomaamme.

Struktuuriolion esittely Omat tietotyypit Kun nyt olemme määritelleet struktuurin kirja, katsotaan seuraavaksi kuinka voimme (ja kuinka emme voi) määritellä sen tyyppisen muuttujan. Voimme määritellä kirja-tyyppisen muuttujan aivan samaan tapaan kuin minkä tahansa muunkin tyyppisen muuttujan: Kirja paperikantinen; //Määritellään kirja-tyyppinen muuttuja Tämä määrittelee muuttujan paperikantinen, johon voimme nyt tallettaa tietoa kirjasta. Vaihtoehtoisesti voit käyttää avainsanaa struct struktuurityyppisen muuttujan määrittelyssä. Olisimme siis voineet määritellä muuttujan paperikantinen seuraavasti: struct Kirja paperikantinen; //Määritellään kirja-tyyppinen muuttuja Tämä on kuitenkin C-kielen perua ja sitä ei yleensä käytetä. Seuraavat kolme lausetta määrittelevät kolme muuttujaa: Kirja romaani; Kirja* pmatkaopas; Kirja kieliopas[10]; Muuttuja romaani on tyyppiä Kirja, pmatkaopas on tyyppiä osoitin tyyppiin kirja ja kieliopas on 10 alkioinen Kirja-tyyppinen taulukko. Voit myös määritellä useita Kirja-tyyppisiä muuttujia yhdessä lauseessa, vaikka se ei olekaan suositeltavaa. Edellisten lauseiden sijaan voisit kirjoittaa: Kirja romaani, *pmatkaopas, kieliopas[10]; Aivan samaan tapaan kuin perustietotyyppien kohdallakin, tämä lause on herkempi virheille ja koodi ei ole niin selkeää. Kolme lausetta sisältävä määrittely on suositeltava tapa. Voit myös määritellä muuttujan samalla, kun määrittelet struktuurityypin: struct Kirja char nimi[80]; char tekija[80]; char kustantaja[80]; int vuosi; } sanakirja, kielioppi; Tämä lause määrittelee tyypin Kirja ja sen jälkeen määrittelee sen tyyppiset muuttujat sanakirja ja kielioppi. On kuitenkin hyvä ottaa heti alkujaan tavaksi kirjoittaa erilliset lauseet tyypin määrittelylle ja jokaiselle muuttujan määrittelylle. Tavallisesti tyypin esittelyt sijoitetaan otsikkotiedostoon, joka sitten sisällytetään #include-komennolla.cpp-tiedostoosi aina, kun esittelyä tarvitaan. Seuraavaksi käsittelemme, miten tietoa sijoitetaan Kirja-tyyppisen muuttujan jäseniin. 417

C++ Ohjelmoijan käsikirja Struktuurimuuttujan alustus Ensimmäinen tapa sijoittaa tietoa struktuurimuuttujien jäseniin on määritellä jäsenmuuttujien alkuarvot muuttujan määrittelyn yhteydessä. Oletetaan, että haluamme alustaa muuttujan romaani tietyn kirjan tiedoilla. Voimme esitellä olion romaani ja alustaa sen jäsenmuuttujat seuraavalla lauseella: Kirja romaani = "Feet of Clay", // Nimen alkuarvo "Terry Pratchett", // Tekijän alkuarvo "Victor Gollanz", // Julkaisijan alkuarvo 1996 // Vuoden alkuarvo Alkuarvot kirjoitetaan aaltosulkeiden sisään pilkuilla eroteltuina, varsin samaan tapaan kuin taulukon alkioille annetaan alkuarvot. Alkuarvojen järjestyksen tulee luonnollisesti vastata struktuurin jäsenten järjestystä. Tämä lause alustaa olion romaani kaikki jäsenet vastaavilla arvoilla. Nimi romaani viittaa nyt tiettyyn olioon, eli kirjaan, jonka sen jäsenmuuttujat määrittelevät. Struktuurityyppiä, joka voidaan alustaa aaltosulkeiden sisällä olevalla arvoluettelolla, kutsutaan yhdisteeksi - tämä yksinkertaisesti tarkoittaa, että struktuurin tyyppi muodostuu kielessä olevista tietotyypeistä tai taulukoista tai toisista yhdisteistä. Huomaa, että emme voisi alustaa kirja-oliota tällä tavalla, jos sen jäsenet olisivat luokkatyyppiä, kuten string. Syy on siinä, että luokkatyyppiset oliot voidaan luoda vain kutsumalla erityistä funktiota, muodostinfunktiota. Tämä tarkoittaa, että struktuuri ei olisi yhdiste. Käsittelemme muodostinfunktioita lisää seuraavassa luvussa. Jos kirjoitat alkuarvoluetteloon vähemmän arvoja kuin struktuurilla on jäseniä, jäsenmuuttujat, joilla ei ole alkuarvoa määritelty, alustetaan arvolla 0. Voit myös alustaa taulukollisen struktuurimuuttujia, samaan tapaan kuin alustimme moniulotteisia taulukoitakin. Kirjoitat vain kunkin taulukon alkioiden alkuarvot aaltosulkeiden sisään ja erotat nämä alkuarvoluettelot toisistaan pilkuilla. Voimme esitellä ja alustaa taulukon lauseella: Kirja romaanit[] = "Our Game", "John Le Carre", "Hodder & Stoughton", 1995 }, "Trying to Save Piggy Sneed", "John Irving", "Bloomsbury", 1993 }, "Illywhacker", "Peter Carey", "Faber & Faber ", 1985 } Tämä esittely luo ja alustaa Kirja-tyyppisen taulukon romaanit, jossa on kolme alkiota. 418

Struktuurimuuttujan jäsenten käsittely Omat tietotyypit Päästäksesi käsiksi struktuurimuuttujan yksittäiseen jäseneen, voit käyttää pisteoperaattoria. Viitataksesi tiettyyn jäseneen, kirjoitat ensin struktuurimuuttujan nimen, jonka perään kirjoitat pisteen ja haluamasi jäsenen nimen. Voimme sijoittaa arvon 1988 struktuurimuuttujan romaani jäseneen vuosi seuraavalla lauseella: romaani.vuosi = 1988; Struktuurimuuttujan tietyntyyppisiä jäseniä voidaan käyttää laskennassa aivan samaan tapaan kuin käyttäisit muitakin saman tyyppisiä muuttujia. Jos haluamme kasvattaa jäsenen vuosi arvoa kahdella, voimme kirjoittaa: romaani.vuosi += 2; Struktuurityyppisen taulukon alkioiden käsittely on varsin suoraviivaista. Voimme laskea taulukon romaanit ensimmäisen ja viimeisen kirjan julkaisupäivämäärien erot lauseella: int vali = romaanit[0].vuosi - romaanit[2].vuosi; Tämä laskee taulukon ensimmäisen ja viimeisen alkion julkaisupäivämäärien erot. Kokeile itse - Struktuurien käyttö Tarvitsemme nyt yksinkertaisen mutta todellisen asian, jonka kuvaamiseen tarvitaan enemmän kuin yksi arvo, joka sitten mallinnetaan struktuurina. Oletetaan, että haluamme tehdä ohjelman, joka käsittelee erikokoisia laatikoita. Ne voivat olla karkkilaatikoita, kenkälaatikoita tai mitä tahansa laatikoita, jotka ovat nelikulmaisia. Käytämme kolmea arvoa, jotka määrittelevät laatikon - pituus, syvyys ja korkeus. Jäsenmuuttujat vastaavat laatikon fyysisiä mittoja. Voimme esitellä alla olevan kaavion mukaisen struktuurin, joka esittää laatikkoa: Korkeus Pituus Syvyys // Laatikon struktuuri struct Laatikko double pituus; double syvyys; double korkeus; 419

C++ Ohjelmoijan käsikirja Käytämme laatikko-struktuuriamme esimerkissä, jossa luomme muutamia laatikoita. Määrittelemme ja käytämme myös globaalia funktiota, joka laskee laatikon tilavuuden. // Esimerkki 11.1 - Laatikkostruktuurin käyttö #include <iostream> using namespace std; // Laatikon struktuuri struct Laatikko double pituus; double syvyys; double korkeus; // Laatikon tilavuuden laskevan funktion prototyyppi double tilavuus(const Laatikko& altk); int main() Laatikko ensimmainenltk = 80.0, 50.0, 40.0 // Lasketaan laatikon tilavuus double ensltktilavuus = tilavuus(ensimmainenltk); cout << endl; cout << "Ensimmäisen Laatikon koko on " << ensimmainenltk.pituus << " x " << ensimmainenltk.syvyys << " x " << ensimmainenltk.korkeus << endl; cout << "Ensimmäisen Laatikon tilavuus on " << ensltktilavuus << endl; Laatikko toinenltk = ensimmainenltk; // Samaksi kuin 1. laatikko } // Kasvatetaan toisen laatikon kokoa 10% toinenltk.pituus *= 1.1; toinenltk.syvyys *= 1.1; toinenltk.korkeus *= 1.1; cout << "Toisen laatikon koko on " << toinenltk.pituus << " by " << toinenltk.syvyys << " by " << toinenltk.korkeus << endl; cout << "Toisen laatikon tilavuus on " << tilavuus(toinenltk) << endl; cout << "Koon kasvattaminen 10% kasvatti tilavuutta " << static_cast<long> ((tilavuus(toinenltk)-ensltktilavuus)*100.0/ensltktilavuus) << "%" << endl; return 0; 420

Omat tietotyypit // Funktio, joka laskee laatikon tilavuuden double tilavuus(const Laatikko& altk) return altk.pituus * altk.syvyys * altk.korkeus; } Jos käännät ja suoritat tämän ohjelman, sen pitäisi tulostaa: Ensimmäisen laatikon koko on 80 x 50 x 40 Ensimmäisen Laatikon tilavuus on 160000 Toisen laatikon koko on 88 x 55 x 44 Toisen Laatikon tilavuus on 212960 Koon kasvattaminen 10% kasvatti tilavuutta 33%! Kuinka se toimii Tässä ohjelmassa struktuurin määrittely tapahtuu globaaliin näkyvyysalueeseen. Yleisesti ottaen struktuurin määrittely on voimassa siinä näkyvyysalueessa, jossa se määritellään. Jos struktuurin Laatikko määrittely olisi ollut funktion main() rungossa, funktio tilavuus() (joka on myöskin määritelty globaalissa näkyvyysalueessa) ei pystyisi tunnistamaan parametrinsä tyyppiä Laatikko&. Kun sijoitamme Laatikko-struktuurin määrittelyn globaaliin näkyvyysalueeseen, voimme esitellä Laatikko-tyyppisiä muuttujia kaikkialla.cpp-tiedostossamme. Useammasta.cpp-tiedostosta muodostuvassa ohjelmassa tyyppien määrittelyt sijoitetaan yleensä.h-tiedostoon, joka sitten sisällytetään kaikkiin tyyppiä tarvitseviin.cpp-tiedostoihin. Esittelemme funktion tilavuus() seuraavalla prototyypillä: double tilavuus(const Laatikko& altk); Parametri on const-tyyppinen viittaus Laatikko-olioon. Parametrin määrittely viittaukseksi tarkoittaa sitä, että alkuperäistä oliota ei kopioida. Tämä on tärkeää monimutkaisten struktuurityyppien kohdalla (ja myöskin luokkien kohdalla, kuten näemme seuraavassa luvussa), koska monimutkaisten patametrien kopiointi heikentää ohjelman tehokkuutta. Teemme parametristä const-tyyppisen, koska funktion ei ole tarkoitus muuttaa parametrin alkuperäistä arvoa mitenkään - se vain käyttää jäsenmuuttujia. main()-funktiossa esittelemme ja alustamme Laatikko-tyyppisen olion lauseella: Laatikko ensimmainenltk = 80.0, 50.0, 40.0 Aaltosulkeiden sisällä olevilla arvoilla alustetaan vastaavat struktuurin jäsenet, eli pituudeksi tulee 80, syvyydeksi 50 ja korkeudeksi 40. Luonnollisestikin on tärkeää kirjoittaa alkuarvot oikeassa järjestyksessä. Laatikon ensimmainenltk tilavuus lasketaan seuraavalla lauseella: double ensltktilavuus = tilavuus(ensimmainenltk); Laskettu tilavuus talletetaan muuttujaan ensltktilavuus, koska tarvitsemme sitä ohjelmassa myöhemmin uudelleen. Palaamme funktion tilavuus() toimintaan hetken kuluttua. 421

C++ Ohjelmoijan käsikirja main()-funktion seuraava lause tulostaa laatikon ensimmainenltk koon: cout << "Ensimmäisen Laatikon koko on " << ensimmainenltk.pituus << " x " << ensimmainenltk.syvyys << " x " << ensimmainenltk.korkeus << endl; Tässä käsittelemme jokaista jäsentä yksinkertaisesti kirjoittamalla ensin olion nimen ja sen perään pisteoperaattorin ja jäsenen nimen. Tätä muotoa käytämme sitten tulostuslauseessa tavalliseen tapaan. Seuraava lause tulostaa laatikon tilavuuden, jonka laskimme jo aikaisemmin: cout << "Ensimmäisen Laatikon tilavuus on " << ensltktilavuus << endl; Seuraavaksi luomme uuden laatikon. Aluksi se on sama kuin ensimmainenltk, mutta muutamme sitä hetken päästä. Luonnin suoritamme lauseella: Laatikko toinenltk = ensimmainenltk; // Samaksi kuin 1. laatikko Tämä luo uuden Laatikko-tyyppisen olion, toinenltk, ja asettaa sen jokaisen jäsenen arvon vastaavaksi kuin on oliossa ensimmainenltk. Kääntäjä tekee tämän tavutavulta -kopiointina. Kasvatamme toinenltk-olion kutakin mittaa seuraavilla lauseilla: toinenltk.pituus *= 1.1; toinenltk.syvyys *= 1.1; toinenltk.korkeus *= 1.1; Käytämme kolmea erillistä lausetta kertomaan kunkin jäsenen arvolla 1.1. Tämä näyttää varsin monimutkaiselta vai mitä? Miksemme voi vain kirjoittaa: toinenltk *= 1.1; //VÄÄRIN!!! Ei toimi!!!! Kääntäjällä on ongelma tässä kohtaa, koska toinenltk on Laatikko-tyyppinen olio - ja kääntäjä ei tiedä, miten kertolaskun tulee toimia Laatikko-tyyppisen olion kohdalla. Kolmesta eri lauseesta koostuva muutos toimii, koska jäsenmuuttujat ovat kaikki double-tyyppisiä ja kääntäjä tietää mitä tarkoitamme, jos kerromme double-tyypin luvulla 1.1. Tavallisesti operaattori, jota käytetään käyttäjän määrittelemien tietotyyppien yhteydessä, täytyy olla ohjelmoijan määrittelemä. Ainoastaan sijoitusoperaattorilla - kun sitä käytetään sijoittamaan toinen käyttäjän määrittelemän tyyppinen olio toiseen, samantyyppiseen olioon - on oletusmäärittely. Tämä sijoitus suoritetaan sijoittamalla arvot jäsen jäseneltä. Seuraavassa luvussa käsittelemme, miten omille tietotyypeille tehdään operaatioita. Kun olion toinenltk jäseniä on kasvatettu 10%, tulostamme koon ja tilavuuden samaan tapaan kuin ensimmäisen laatikonkin kohdalla. Lopuksi laskemme tilavuuden muutoksen lauseella: 422

Omat tietotyypit cout << "Koon kasvattaminen 10% kasvatti tilavuutta " << static_cast<long> ((tilavuus(toinenltk)-ensltktilavuus)*100.0/ensltktilavuus) << "%" << endl; Muunnamme prosenttiarvon eksplisiittisesti tyypiksi long, jotta tulostamme prosentit kokonaisina. Ennen kuin siirrymme eteenpäin, katsotaan hieman, miten funktio tilavuus() toimii. Sen määrittely on seuraava: double tilavuus(const Laatikko& altk) return altk.pituus * altk.syvyys * altk.korkeus; } Laatikko-tyyppinen olio välitetään funktiolle aivan samaan tapaan kuin minkä tahansa muunkin tyyppinen muuttuja. Funktiossa viittaamme alkuperäiseen muuttujaan nimellä altk. Laskemme tilavuuden kertomalla jäsenet keskenään. Jotta pääsemme käsiksi jäseniin, meidän tulee käyttää olion nimeä ja pisteoperaattoria. Tämän yhdistelmän avulla tunnistetaan funktiolle välittämäsi olio. Tuloksena oleva tilavuus palautetaan funktion paluuarvona. Struktuurin jäsenfunktiot Esimerkissä 11.1 struktuuri Laatikko oli yksinkertaisesti tietotyyppien yhdistelmä, joka sisälsi ainoastaan kolme jäsenmuuttujaa. Tämä on struktuureiden yleinen käyttötapa - monet ohjelmoijat suosivat struktuuria ainoastaan tietoalkioiden kokoelmana. Vastakohtana heillä on luokat, jotka voivat (kuten pian huomaamme) sisältää myös toiminnallisuutta. Olemme kuitenkin jo maininneet, että C++:n struktuuri on luokka, joten myös C++:n struktuuri tukee toiminnallisuutta. Se, että tietotyyppi voi sisältää toiminnallisuutta, on meille ideana uusi. Voimme havainnollistaa ideaa katsomalla esimerkkiä 11.1 uudelleen ja muuttamalla tietotyyppiä Laatikko. Kuten olet jo saattanut huomatakin, esimerkin 11.1 funktio tilavuus() on riippuvainen vain Laatikko-tyyppisistä olioista. Muussa yhteydessä sillä ei ole mitään käyttöä. Tästä syystä funktio kannattaa integroida tyyppiin, eikä antaa sen olla vapaana globaalissa nimiavaruudessa. Voimme määritellä funktion tilavuus() tyypin Laatikko-määrittelyn sisällä, jolloin siitä tulee Laatikko-tyyppisten olioiden osa. Funktio on tyypin Laatikko jäsen. Seuraavassa näytetään, miltä funktion tilavuus() määrittely näyttää, kun se määritellään struktuurin jäsenenä: struct Laatikko double pituus; double syvyys; double korkeus; // Funktio, joka laskee laatikon tilavuuden double tilavuus() 423

C++ Ohjelmoijan käsikirja } return pituus * syvyys * korkeus; Funktio on nyt kiinteä osa tyyppiä ja voit käyttää sitä vain Laatikko-tyyppisten olioiden yhteydessä. Kun kutsut funktiota, se yhdistetään automaattisesti nykyiseen Laatikko-olioon. Tällä on kaksi seurausta: ensinnäkin, kun kutsut funktiota, sille ei tarvitse välittää parametrejä. Toiseksi, return-lauseessa ei tarvita olion nimeä. Kutsuaksesi tätä funktiota tietyn olion kohdalla, käytät pisteoperaattoria samaan tapaan kuin jäsenmuuttujienkin kohdalla. Esimerkiksi seuraavassa esitellään Laatikko-tyyppinen olio ensimmainenltk ja lasketaan sen tilavuus: Laatikko ensimmainenltk = 80.0, 50.0, 40.0 double ensltktilavuus = ensimmainenltk.tilavuus(); Tämä kutsuu funktiota tilavuus() oliolle ensimmainenltk, jonka tilavuus lasketaan ja tulos talletetaan muuttujaan ensltktilavuus. Laskennassa käytettävät jäsenmuuttujat pituus, syvyys ja korkeus ovat olion ensimmainenltk jäsenmuuttujat. Jos kutsut funktiota jonkin toisen Laatikko-tyyppisen olion kohdalla, käytetään tällöin tämän olion jäsenmuuttujia. Jos esimerkiksi haluat tulostaa Laatikko-tyyppisen olion toinenltk tilavuuden, voit kirjoittaa seuraavaa: cout << "Toisen laatikon tilavuus on " << toinenltk.tilavuus() << endl; Voit halutessasi muuttaa esimerkkiä 11.1 siten, että siinä käytetään jäsenfunktiota tilavuus(). Ohjelman tulostus on kuitenkin aivan sama. Funktion liittäminen tyyppiin muuttaa funktion luonteen täysin. Jokainen Laatikko-tyyppinen olio pystyy nyt laskemaan oman tilavuutensa. tilavuus()-funktio on voimassa vain Laatikkoolioille ja jos sinulla ei ole yhtään Laatikko-oliota, et voi käyttää tilavuus()-funktiota lainkaan. (Voit kirjoittaa toisen tilavuus()-funktion globaaliin näkyvyysalueeseen. Tässä ei ole sekaannuksen vaaraa, koska toista voidaan kutsua vain käyttämällä Laatikko-oliota ja toista voidaan käyttää vain käyttämällä pelkkää funktion nimeä.) Jäsenfunktion määrittelyn sijoittaminen Edellä olleessa esimerkissä jäsenfunktion määrittely oli sijoitettu itse struktuurin määrittelyyn. Voit kuitenkin sijoittaa jäsenfunktion esittelyn struktuurin määrittelyyn ja määritellä jäsenfunktion muualla. Tällöin struktuurin määrittely näyttäisi seuraavalta: struct Laatikko double pituus; double syvyys; double korkeus; 424 // Funktio, joka laskee laatikon tilavuuden double tilavuus()

Omat tietotyypit Tässä funktion määrittely on irrallaan struktuurin määrittelystä. Funktion määrittelyssä meidän tulee kertoa kääntäjälle, että määriteltävä funktio on struktuurin Laatikko jäsenfunktio. Tämä tapahtuu käyttämällä näkysyysalue-operaattoria :: samaan tapaan kuin nimiavaruuksienkin kohdalla. Funktion määrittely olisi seuraava: double Laatikko::tilavuus() return pituus * syvyys * korkeus; } Laatikko määrittelee tässä (yhdessä funktion nimen kanssa), että funktion määrittely kuuluu struktuuriin Laatikko. Jäsenmuuttujia voidaan funktion rungossa silti käyttää ilman struktuurin nimeä. Kun järjestelet struktuuriesi ja luokkiesi jäsenfunktioiden määrittelyjä, tämä tapa on suositeltava. Havainnollistamme tätä esimerkissä 11.2 ja käytämme tätä tapaa myös luokkien yhteydessä seuraavassa luvussa. Kuten sanoimme, jokaisella Laatikko-tyyppisellä oliolla on oma tilavuus()-funktio. Funktion koodi on muistissa kuitenkin vain kerran, riippumatta siitä, montako Laatikko-tyyppistä oliota on olemassa. Ne käyttävät kaikki samaa kopiota funktiosta, joten muistia ei haaskata. Tästä seuraakin kysymys: miten tietyn olion jäsenmuuttujat sidotaan funktion rungossa oleviin jäsenmuuttujan nimiin, kun funktio suoritetaan? Palaamme tähän kysymykseen seuraavassa luvussa, kun pääsemme luokkien yksityiskohtiin. Osoittimien käyttö struktuurien yhteydessä Kuten olemme jo sanoneet, voit luoda osoittimen struktuurityyppiseen muuttujaan tai itse asiassa mihin tahansa käyttäjän määrittelemään tyyppiin. Jos esimerkiksi haluat määritellä osoittimen Laatikko-tyyppiin, voit käyttää seuraavaa lausetta: Laatikko* pltk = 0; //Määritellään null-osoitin Tämä osoitin voi tallettaa Laatikko-tyyppisen olion osoitteen. Olemme asettaneet sen arvoksi tässä nollan. Oletetaan, että olemme jo aikaisemmin määritelleet Laatikko-tyyppisen olion altk, voimme sijoittaa tämän muuttujan osoitteen osoittimeen normaaliin tapaan: pltk = &altk; //Sijoitetaan osoite osoittimeen Osoitin pltk sisältää nyt olion altk osoitteen. Jäsenmuuttujien käsittelyn osoittimien avulla Oletetaan, että määrittelemme Laatikko-olion seuraavalla lauseella: Laatikko Ltk = 80.0, 50.0, 40.0 425

C++ Ohjelmoijan käsikirja Voimme nyt esitellä osoittimen Laatikko-olioon ja alustaa sen olion Ltk osoitteella: Laatikko* pltk = &Ltk; Voimme käyttää osoitinta pltk sen osoittaman olion jäsenmuuttujien käsittelyssä. Tämä on kaksiosainen tapahtuma: meidän tulee käyttää osoittimen nimeä yhdessä *-operaattorin kanssa sekä pilkku-operaattoria ja jäsenmuuttujan nimeä. Voimme esimerkiksi kasvattaa olion pltk jäsenmuuttujaa korkeus lauseella: (*pltk).korkeus += 10.0; //Kasvatetaan korkeutta Tämän lauseen suorituksen jälkeen jäsenmuuttujan korkeus arvo on 60.0 ja muut jäsenmuuttujat säilyvät tietysti muuttumattomina. Huomaa, että sulkeet ovat tässä lauseessa välttämättömät, koska pilkku-operaattori on suoritusjärjestyksessä *-operaattorin edellä. Ilman sulkeita kääntäjä tulkitsisi lauseen muodossa: *(pltk.korkeus) += 10.0; Tämä yrittäisi käyttää osoitinta pltk struktuurina, joten lause ei kääntyisi. Jäseneen osoitus -operaattori Edellä ollut kaksiosainen tapahtuma suoritetaan C++-ohjelmissa varsin usein. * ja.- operaattorit näyttävät kuitenkin hieman kömpelöltä; siinä tarvitsee käyttää sulkeita eikä se suoraan kerro, mitä yritämme tehdä. Kaikista näistä syistä johtuen, C++-kieleen kuuluu erityinen operaattori, jäseneen osoitusoperaattori, jonka avulla edellä mainittu kaksiosainen tapahtuma voidaan kirjoittaa selkeämmin. Jäseneen osoitus -operaattoria käytetään erityisesti käyttäjän määrittelemien tyyppien jäsenmuuttujien käsittelyssä osoittimen avulla. Seuraavan, varsin kömpelön lauseen (*pltk).korkeus += 10.0; //Kasvatetaan korkeutta voimme kirjoittaa käyttämällä jäseneen osoitus -operaattoria seuraavasti: pltk->korkeus += 10.0; //Kasvatetaan korkeutta Tämä operaattori näyttää nuolelta ja muodostuu miinus-merkistä ja suurempi kuin -merkistä. Se kertoo huomattavasti selvemmin mistä on kyse, vai mitä? Tätä operaattoria käytetään samoin myös luokkien kohdalla, joten käytämme sitä runsaasti koko kirjan loppuosassa. Tietysti voit halutessasi käyttää piste-operaattoria jäsenmuuttujien käsittelyssä osoittimen avulla. Katsotaan nyt olioita ja osoittimia esimerkin avulla. 426

Omat tietotyypit Kokeile itse - Olio-osoittimien käyttö Tässä on esimerkin koodi: // Esimerkki 11.2 Olio-osoittimien käyttö #include <iostream> using namespace std; struct Laatikko double pituus; double syvyys; double korkeus; double tilavuus(); // Funktio, joka laskee laatikon tilavuuden int main() Laatikko altk = 10, 20, 30 Laatikko* pltk = &altk; // Talletetaan altk:n osoite cout << endl << "Laatikon altk tilavuus on " << pltk->tilavuus() << endl; } Laatikko* pdynltk = new Laatikko; // Luodaan laatikko vapaasta muistista pdynltk->korkeus = pltk->korkeus + 5.0; pdynltk->pituus = pltk->pituus - 3.0; pdynltk->syvyys = pltk->syvyys - 2.0; cout << "Vapaan muistin laatikon tilavuus on " << pdynltk->tilavuus() << endl; delete pdynltk; return 0; // Laatikon jäsenfunktio, joka laskee tilavuuden double Laatikko::tilavuus() return pituus * syvyys * korkeus; } Ohjelman tulostus on seuraava: Laatikon altk tilavuus on 6000 Vapaan muistin laatikon tilavuus on 4410 Kuinka se toimii Ennen kuin käsittelemme osoittimien toimintaa, huomaa, että struktuurin Laatikko määrittely sisältää vain funktion tilavuus() esittelyn. Funktion määrittely on ohjelman lopussa. Näkyvyysalueoperaattori :: kertoo, että määrittely kuuluu struktuuriin Laatikko. Olion altk luonnin ja alustuksen jälkeen talletamme sen osoitteen osoittimeen pltk: Laatikko* pltk = &altk; // Talletetaan altk:n osoite 427

C++ Ohjelmoijan käsikirja Voimme nyt käsitellä altk:n jäseniä epäsuorasti osoittimen pltk avulla. Seuraavassa lauseessa käytämme altk:n tilavuus()-funktiota: cout << endl << "Laatikon altk tilavuus on " << pltk->tilavuus() << endl; Seuraavaksi luomme Laatikko-olion dynaamisesti ja talletamme sen osoitteen toiseen osoittimeen: Laatikko* pdynltk = new Laatikko; // Luodaan laatikko vapaasta muistista new-operaattorin palauttama osoite talletetaan osoittimeen pdynltk. Emme ole vielä alustaneet tämän uuden Laatikko-olion jäsenmuuttujia, joten ne sisältävät roskaa. Sijoitamme jäsenmuuttujiin arvot olion altk jäsenmuuttujista: pdynltk->korkeus = pltk->korkeus + 5.0; pdynltk->pituus = pltk->pituus - 3.0; pdynltk->syvyys = pltk->syvyys - 2.0; Jokaisessa näistä lauseista jäsenmuuttujiin viitataan osoittimella, jäseneen osoitus -operaattorilla ja jäsenmuuttujan nimellä. Nyt, kun meillä on järkevät arvot jäsenmuuttujissa, voimme laskea tilavuuden: cout << "Vapaan muistin laatikon tilavuus on " << pdynltk->tilavuus() << endl; Lopuksi vapautamme Laatikko-olion viemän muistitilan vapaaseen muistiin: delete pdynltk; Kuten huomaat, jäseneen osoitus -operaattorin käyttö on varsin selkeää ja helppolukuista. Olio-osoittimien käyttötilanteet Osoittimet luokkatyyppiseen olioon ovat erittäin tärkeät ja tässä kappaleessa mainitsemme kolme tilannetta, jossa ne ovat erityisen käyttökelpoiset. Ensimmäinen on tilanne, jonka näimme esimerkissä 11.2. Sinun täytyy käyttää osoitinta, kun luot (tai käytät) olion vapaasta muistista. Ohjelmiesi täytyy usein pystyä käsittelemään eri määrää olioita. Tämä voidaan tehdä parhaiten olioiden dynaamisen luonnin avulla. Toinen käyttötilanne on linkitetyt listat. Kuten aikaisemmin mainitsimme, struktuurin jäsenmuuttujat eivät voi sisältää saman struktuurityypin tyyppiä - eli emme voi esimerkiksi luoda Kirja-struktuuria, joka sisältäisi Kirja-tyyppisen jäsenmuuttujan. Struktuuri voi kuitenkin sisältää osoittimen struktuurityyppiin ja laajennettuna voidaan sanoa, että struktuurin määrittely voi sisältää osoittimen samaan struktuurityyppiin. Tälle on useita käyttökohteita; yksi on mahdollisuus luoda tehokkaita mekanismeja, joilla voidaan tallettaa olioiden kokoelmia, kuten linkitetyt listat. 428

Omat tietotyypit ListaOlio LO1 Jäsenet: Laatikko altk = ltk1 ListaOlio* pseur= &LO2 ListaOlio LO2 Jäsenet: Laatikko altk = ltk2 ListaOlio* pseur= &LO3 ListaOlio LO3 Jäsenet: Laatikko altk = ltk3 PSeur = 0 Jokaisella listan oliolla on jäsen, pseur, joka on tyyppiä osotitin tyyppiin ListaOlio, joka sisältää seuraavan olion osoitteen. Tämä jäsen on null listan viimeisessä oliossa. Ei enää seuraavaa Linkitetty lista, jossa kolme ListaOlio-tyyppistä oliota Kaavio selventää, miten joukko saman tyyppisiä olioita voidaan ketjuttaa: jokaisessa oliossa on osoitintyyppinen jäsen, joka sisältää seuraavan olion osoitteen. Aina, kun tiedät ketjun ensimmäisen olion osoitteen, pääset kaikkiin muihinkin seuraamalla ketjua. Käsittelemme tätä lisää luokkien yhteydessä luvussa 13. Ehkä kaikkein tärkein olio-osoittimien käyttö on niiden tärkeä rooli polymorfismin toteuttamisessa. Polymorfismi on olio-ohjelmoinnin perusosa, jota käsittelemme tarkasti luvussa 16. Unionit Unioni on tietotyyppi, jonka avulla voit käyttää samaa muistilohkoa tallettamaan eri tyyppisiä arvoja ohjelman suorituksen eri aikoina. Unionin muuttujia kutsutaan unionin jäseniksi. Unionia voidaan käyttää kolmessa eri tilanteessa. Ensinnäkin, voit käyttää samaa muistilohkoa tallettamaan eri muuttujia (ehkä eri tyyppisiäkin) ohjelman eri osissa. Idea unionien takana on alkujaan muistin käytön tehostaminen, koska muisti oli tuolloin rajallista ja erittäin kallista. Näin ei kuitenkaan onneksi enää ole, ja tällaisesta järjestelystä mahdollisesti seuraavien virheiden riski on suuri. Tästä syystä tämäntyyppinen unionien käyttö ei ole suositeltavaa - saat saman aikaan käyttämällä dynaamista muistinvarausta. Toinen tilanne, josta seuraa suuressa mittakaavassa muistin säästöä, liittyy taulukoihin. Oletetaan, että tarvitset jossain tilanteessa suurta taulukkoa, mutta et ennen suoritusta tiedä, minkä tyyppisiä taulukon alkioiden tulisi olla - sen määrää syötetty tieto. Unionin avulla sinulla voi olla useita taulukoita, joiden jäsenten tyypit ovat eri, mutta jokainen taulukko vie saman muistilohkon (sen sijaan, että jokainen taulukko olisi omalla muistialueellaan). Käännösaikana olet kattanut kaikki mahdollisuudet; ja suoritusaikana valitaan sitten sopiva syötettyjen tietojen tyypin perusteella. Käyttämättömät taulukot eivät vie lisätilaa. Suosittelen, ettet käytä unionia tällaisessakaan tilanteessa, koska sama saadaan aikaan käyttämällä muutamaa erityyppistä osoitinta ja dynaamista muistinvarausta. 429

C++ Ohjelmoijan käsikirja Kolmanneksi, saatat haluta käyttää unionia, kun haluat tulkita saman datan kahdella eri tavalla. Oletetaan, että sinulla on long-tyyppinen muuttuja, jonka haluat tulkita neljäksi char-tyyppiseksi kirjaimeksi ( jotta voit kirjoittaa sen tiedostoon binääriarvona). Unionin avulla voit käsitellä long-tyyppistä arvoa kokonaislukuna laskutoimituksissa ja neljän tavun lohkona, kun kirjoitat sen tiedostoon. Tämän tekemiseen on muitakin vaihtoehtoja, kuten huomaamme, kun käsittelemme tiedostojen syöttöä ja tulostusta. Vaihtoehtoisesti voit käyttää unionia toisinkin päin - eli siten, että merkkijonoa käytetään kokonaislukujoukkona esimerkiksi avaimissa. Unionin avulla nämä molemmat tilanteet voidaan tehdä helposti. Älä kuitenkaan ymmärrä tätä viimeistä tilannetta väärin. Vaikka voit käsitellä samaa dataa erityyppisten muuttujien avulla, mitään tyypinmuunnoksia ei suoriteta. Samat bitit vain tulkitaan eri tavalla, mitään tyypintarkistuksia ei tehdä - ja useissa tapauksissa tämä johtaa katastrofiin. Yleisesti ottaen unionin jäsen ei voi olla luokka-tyyppinen, ellei se ole yhdiste - eli olio, joka voidaan alustaa alkuarvoluettelolla. Erityisesti merkkijono-olio ei voi olla unionin jäsen. Unionin jäsenet ovat lähes aina perustietotyyppiä olevia muuttujia. Katsotaan nyt, miten esittelemme unionin. Unionien esittely Kuten jo mainittiin, unioni esitellään avainsanalla union. Katsotaan esimerkin avulla, kuinka se toimii: union JaaDL //Jaetaan muisti double ja long -tyyppien kesken double darvo; long larvo; Tämä esittelee unionin JaaDL, jonka avulla long- ja double-tyyppiset muuttujat vievät saman määrän muistia. Tämä lause on samankaltainen kuin luokan esittely, koska emme ole vielä määritelleet unionin ilmentymää, joten mitään muuttujaa ei vielä tässä vaiheessa ole. Voimme nyt esitellä unionin JaaDL ilmentymä omaunioni: JaaDL omaunioni; Olisimme voineet määritellä unionin omaunioni myös unionin määrittelylauseessa: union JaaDL //Jaetaan muisti double ja long -tyyppien kesken double darvo; long larvo; } omaunioni; 430 Jos haluamme viitata unionin jäseneen, käytämme pisteoperaattoria yhdessä unionin nimen kanssa - aivan samaan tapaan kuin luokankin jäsenten käsittelyssä. Eli voimme sijoittaa longtyyppiseen jäseneen larvo arvon 100 seuraavalla lauseella:

Omat tietotyypit omaunioni.larvo = 100; //Unionin jäsenen käsittely! Kun käytämme unionia tallettamaan samalle muistialueelle eri typpisiä arvoja, törmäämme ongelmaan. Unionien toiminnasta johtuen täytyy olla mahdollista määritellä, mikä jäsenmuuttujien arvoista on nykyinen arvo. Tämä tehdään tavallisesti lisämuuttujan avulla. Tämä lisämuuttuja toimii sijoitetun arvon indikaattorina. Unionissa voi tarvittaessa olla useampiakin kuin kaksi jäsentä. Voimme määritellä uuden unionin, jossa sama muistialue jaetaan useamman muuttujan kesken. Unioni tarvitsee tilaa suurimman jäsenensä mukaan. Oletetaan, että esittelemme unionin: union JaaDLF double darvo; long larvo; float farvo; JaaDLF uilm = 1.5 Minun tietokoneessani uilm vie tilaa 8 tavua. Tämä havainnollistetaan viereisessä kaaviossa. Uilm 8 tavua LArvo Esimerkissä esittelimme ensin unionin JaaDLF. Tämän jälkeen esittelimme unionin ilmentymän uilm, jolle annoimme alkuarvon 1.5. FArvo DArvo Kolme muuttujaa sisältävä unioni jakaa saman muistialueen Huomaa tapa, jolla alustimme unionin alkuarvon: JaaDLF uilm = 1.5 Kun alustat unionin esittelylauseessa, alustetaan aina ensimmäinen jäsen. Tässä tapauksessa arvo 1.5 sijoitetaan jäseneen darvo. Jos haluat alustaa jäsenen farvo, täytyy sinun kirjoittaa erillinen lause esittelyä ja sijoitusta varten: JaaDLF uilm; uilm.farvo = 1.5; 431

C++ Ohjelmoijan käsikirja Nimettömät unionit Voit esitellä unionin ilman unionin tyypin nimeä, jolloin unionin ilmentymä määritellään automaattisesti. Voimme esimerkiksi määritellä unionin seuraavasti: union char* parvo; double darvo; long larvo; Tämä esittelee nimettömän unionin ja myöskin määrittelee unionin ilmentymän, jolla ei ole nimeä. Nyt unionien jäseniä voidaan käsitellä pelkillä jäsenten nimillä. Tämä voi joskus olla järkevää, mutta ole tarkkana, ettet sekoita unionin jäseniä tavallisiin muuttujiin. Tämän unionin jäsenet jakavat saman muistialueen. Katsotaan tarkemmin, kuinka tämä nimetön unioni toimii. Käsitelläksesi double-tyyppistä jäsentä, voisit kirjoittaa lauseen: darvo = 99.5; //Nimettömän unionin jäsenen käsittely Kuten huomaat, missään ei kerrota, että tässä käytetty muuttuja darvo on unionin jäsen. Jos haluat käyttää nimettömiä unioneja, kannattanee käyttää nimeämissääntöjä, jotka kertovat, että kyseessä on unionin jäsen. Huomaa, että jos esittelet unionin ilman nimeä, mutta esittelet samassa lauseessa sen tyyppisen olion, unioni ei ole enää nimetön. Esimerkiksi: union char* parvo; double darvo; long larvo; } uarvo; Nyt et voi viitata jäseniin pelkillä jäsenten nimillä, koska nimetöntä unionia ei ole olemassa. Sinun tulee käyttää uarvo-ilmentymää, eli voit kirjoittaa: uarvo.darvo = 10.0; Et voi käyttää pelkkää jäsentä: darvo = 10.0; //Virhe!! Nimetöntä unionia ei ole!! 432 Monimutkaisemmat struktuurit Tähän saakka käsittelemämme struktuurit ovat olleet varsin yksinkertaisia. Olemme käyttäneet vain perustietotyyppejä struktuurien jäseninä. Struktuurien jäsenet voivat kuitenkin olla muuntyyppisiäkin, kuten unioneita ja muita struktuureja. Katsotaan esimerkkiä.

Omat tietotyypit Oletetaan, että meidän on pakko säästää muistia ja käyttää jaettua muistia useille muuttujille aina kun mahdollista. Voisimme määritellä unionin, jolla erityyppiset muuttujat sijoitetaan samaan muistialueeseen: union uarvot double ddata; float fdata; long larvo; int idata; Nyt uarvot-tyyppiseen muuttujaan voidaan tallettaa tyyppiä double, float, long tai int oleva arvo, mutta vain yksi kerrallaan. Tämän käyttö on helppoa, voimme esimerkiksi kirjoittaa: uarvot arvo; arvo.ddata = 25.0; Tämä tallettaa liukulukuarvon jäseneen ddata. Hieman myöhemmin voisimme koodissa kirjoittaa: arvo.ldata = 5; arvo.ldata++; Tämä poistaa liukulukuarvon ja tallettaa arvon 5 jäseneen ldata. Tämä saa kuitenkin hieman hiukset pystyyn. Yksi pieni virhe - kuten jos oletettaisiin, että uarvo-tyyppi sisältää longtyyppisen arvon, kun siihen voidaan tallettaa double-tyyppinen arvo - ja katastrofi on valmis. Jos meidän välttämättä täytyy tehdä tällaisia asioita, tulisi meillä olla edes jonkinlainen mahdollisuus tyypin tarkistukseen. Voimme käyttää lueteltua tyyppiä, jos haluamme tarkistaa talletettavan arvon tyypin: enum Tyyppi Double, Float, Long, Int Nyt voimme esitellä struktuurin, jossa on kaksi tietotyyppiä. Ensimmäinen on unioni, johon voidaan sijoittaa tyyppiä double, float, long tai int oleva arvo. Toinen tyyppi on lueteltua tyyppiä Tyyppi. Eli struktuuri näyttää seuraavalta: struct JaaData union uarvot double ddata; float fdata; long larvo; int idata; } Tyyppi tyyppi; //Nimetön unioni //Lueteltua tyyppiä Tyyppi oleva muuttuja Huomaa, että struktuurin JaaData jäsenunioni on nimetön. Näin voimme viitata unionin jäseniin struktuurista JaaData ilman unionin nimeä. Voimme esitellä JaaData-tyyppisen muuttujan seuraavasti: JaaData arvo = 25.0, Double //Alustetaan ddata ja Tyyppi 433

C++ Ohjelmoijan käsikirja Tämä alustaa jäsenen ddata arvoksi 25.0, koska ddata on unionin ensimmäinen jäsen. (Koska alustamme esittelylauseesta, voimme alustaa vain jäsenen ddata, koska se on ainut jäsen, jonka kääntäjä antaa alustaa.) Alkuarvoluettelon toinen arvo on muuttujan tyyppi arvo. Voit valita vain yhden luetellun tyypin Tyyppi arvoista. Huomaa, että voit jättää tyypin alkuarvon asettamatta ja tulos olisi silti oikein, koska oletusarvo 0 vastaa tyyppiä Double: JaaData arvo = 25.0 //Alustetaan ddata = 25.0 ja tyyppi = Double Tämä toimii ainoastaan siitä syystä, että Double esiintyy ensimmäisenä luetellun tyypin Tyyppi esittelyssä. Voimme myöhemmin muuttaa muuttujan arvo sisältöä lauseilla: arvo.ldata = 10; arvo.tyyppi = Long; Nyt, jos aina muistamme asettaa myöskin arvon tyypin arvoa muuttaessamme, voimme testata talletetun arvon tyypin. Näin voimme varmistua, että käsittelemme arvoa oikein. Esimerkiksi: if(arvo.tyyppi == Long) arvo.ldata++; Tämän esimerkin tarkoitus oli vain näyttää, miten erilaiset tiedot voidaan koota yhteen struktuurin avulla. Tässä varsin teoreettisessa tapauksessa ei tällaisen tekniikan käyttö olisi kuitenkaan järkevää, koska käytämme vain hyvin pientä muistialuetta - tyypillisesti 8 tavua tyypille double. Käytämme enemmän muistia joka kerta, kun tarkistamme tyypin kuin säästämme sitä jakamalla muistin eri tyypeille! Struktuurit struktuurin jäsenenä Olemme jo nähneet unionin struktuurin jäsenenä. Kuten olemme jo aikaisemmin maininneet, struktuuri voi olla myös struktuurityypin jäsen (nämä kaksi struktuuria eivät kuitenkaan voi olla samat). Oletetaan, että haluamme tyypin edustavan henkilöä. Haluamme tallettaa joitakin henkilöiden tietoja, kuten nimi, osoite, puhelinnumero, syntymäaika - no lopetetaan nyt tähän. Tarkastellaan nyt vähän, miten nämä tiedot talletetaan. Esimerkiksi puhelinnumero voitaisiin tallettaa kokonaislukuna. On todennäköisempää, että haluat suuntanumeron erilleen varsinaisesta numerosta. Haluat ehkä tallettaa myös maanumeron, jos haluat tallettaa ulkomailla asuvia henkilöitä. Huomaamme, että puhelinnumeron tallettaminen vaatii jo struktuurin. Samaan tapaan lähes kaikki Henkilo-struktuurin jäsenet tulevat olemaan struktuureita. Katsotaan mitä typistetty versio näyttäisi. 434

Omat tietotyypit Voimme määritellä Henkilo-struktuurin seuraavasti: struct Henkilo Nimi nimi; Pvm syntaika; Puhelin puhnro; Tyyppi Henkilo koostuu vain kolmesta jäsenestä, mutta jokainen niistä on struktuuri. Nimen tallettava tyyppi voisi olla seuraavanlainen: struct Nimi char etunimi[80]; char sukunimi[80]; } Tässä olisi tietysti parempi käyttää jäsenenä String-oliota, mutta Nimi-olion alustamiseen vaadittaisiin tällöin muodostinfunktiota. Käsittelemme muodostinfunktiota seuraavassa luvussa; pysytään nyt vielä vanhoissa null-merkkiin päättyvissä merkkijonoissa. Pvm-struktuurissa voimme tallettaa päivämäärän kolmena kokonaislukuna, jotka vastaavat päivää, kuukautta ja vuotta: struct Pvm int paiva; int kuukausi; int vuosi; Puhelin-struktuuriin haluat varmastikin tallettaa numeron merkkijonona, mikä helpottaisi numeroon soittamista. Meidän tarkoituksiime riittää kuitenkin suuntanumeron ja numeron tallettaminen kokonaislukuna: struct Puhelin int suuntanro; int numero; Tyyppiä Henkilo oleva muuttuja esitellään samaan tapaan kuin aikaisemminkin tässä luvussa esittelemämme struktuurioliot: Henkilo han; 435

C++ Ohjelmoijan käsikirja Voimme alustaa Henkilo-tyyppisen muuttujan esittelyn yhteydessä käyttämällä alkuarvoluetteloa: Henkilo han = "Letitia", "Gruntfuttock" }, // Alustaa jäsenen Nimi 1, 4, 1965 }, // Alustaa jäsenen Pvm 212, 5551234 } // Alustaa jäsenen Puhelin Alkuarvoluettelon arvot järjestetään samaan tapaan kuin moniulotteisen taulukonkin kohdalla. Kunkin struktuurityyppisen jäsenen alkuarvoluettelo on aaltosulkeiden sisällä ja kukin näistä luetteloista on erotettu toisistaan pilkuilla. Jokaisen struktuurityyppisen jäsenen alkuarvoluettelon ympärille ei ole pakko kirjoittaa aaltosulkeita, jos jätät ne pois, alkuarvot sijoitetaan yksinkertaisesti järjestyksessä. Tällöin virheiden mahdollisuus on huomattavasti suurempi. Henkilo-tyyppisellä muuttujalla voi tehdä vain rajoitettuja asioita. Voit sijoittaa Henkilo-tyyppisen muuttujan jäsenten arvot toiseen saman tyyppiseen muuttujaan sijoituslauseella: Henkilo nayttelija; nayttelija = han; //kopioidaan muuttujan han jäsenet Tässä tapauksessa muuttujan han jäsenet (ja niiden jäsenet) kopioidaan vastaaviin muuttujan nayttelija jäseniin. Jäsenen arvoon voit viitata tietysti jäseneen osoitus -operaattorilla. Voit esimerkiksi tulostaa Henkilo-tyyppisen muuttujan han nimen seuraavalla lauseella: cout << han.nimi.etunimi << << han.nimi.sukunimi << endl; Nimen tulostaminen tällä tavalla on hieman monimutkaisen näköistä, joten tällaista varten voidaan kirjoittaa jäsenfunktio. Kokeillaan tätä esimerkin avulla. Kokeile itse - Henkilo-esimerkki Henkilo-struktuurin muodostavat struktuurit voivat sisältää jäsenfunktioita. Voimme lisätä niihin kuhunkin jäsenfunktion, joka tulostaa niiden nimen. Seuraavassa on Nimi-struktuuri: struct Nimi char etunimi[80]; char sukunimi[80]; // Tulostetaan nimi void nayta() cout << etunimi << " " << sukunimi; } 436

Omat tietotyypit Voimme tehdä saman Pvm-struktuurin kohdalla: struct Pvm int paiva; int kuukausi; int vuosi; // Tulostetaan päivämäärä void nayta() cout << paiva << "/" << kuukausi << "/" << vuosi; } Sekä myöskin Puhelin-struktuuriin: struct Puhelin int suuntanro; int numero; // Tulostetaan puhelinnumero void nayta() cout << suuntanro << " " << numero; } Nyt voimme käyttää näitä struktuureihin lisäämiämme funktioita struktuurin Henkilo vastaavassa jäsenfunktiossa: struct Henkilo Nimi nimi; Pvm syntaika; Puhelin numero; // Tulostetaan henkilo void nayta() cout << endl; nimi.nayta(); cout << endl; cout << "Syntynyt: "; syntaika.nayta(); cout << endl; cout << "Puhelinnumero: "; numero.nayta(); cout << endl; } // Lasketaan ikä annettuun päivää saakka int ika(pvm& pvm) if(pvm.vuosi <= syntaika.vuosi) return 0; 437

C++ Ohjelmoijan käsikirja int vuodet = pvm.vuosi - syntaika.vuosi; if((pvm.kuukausi>syntaika.kuukausi) (pvm.kuukausi == syntaika.kuukausi && pvm.paiva>= syntaika.paiva)) return vuodet; else return --vuodet; } Henkilo-olion tiedot tulostavan funktion nayta() lisäksi olemme lisänneet jäsenfunktion ika(), joka laskee iän parametrinä saamaansa päivämäärään saakka. Voit kirjoittaa kaikki neljä struktuurimäärittelyä otsikkotiedostoon Henkilo.h: // Henkilo.h Henkilo-struktuurin ja siihen liittyvien struktuurien määrittelyt #ifndef HENKILO_H #define HENKILO_H #include <iostream> using namespace std; // Nimi-struktuurin määrittely // Pvm-struktuurin määrittely // Puhelin-struktuurin määrittely // Henkilo-struktuurin määrittely #endif Kaikkien niiden struktuurien määrittely, joihin viitataan struktuurissa Henkilo, tulee olla tiedostossa ennen Henkilo-struktuurin määrittelyä. Voimme nyt käyttää näitä pienessä ohjelmassa: // Esimerkki 11.3 Henkilo-struktuurin käyttö #include <iostream> using namespace std; #include "henkilo.h" int main() Henkilo han = "Letitia", "Gruntfuttock" }, // Alustaa jäsenen Nimi 1, 4, 1965 }, // Alustaa jäsenen Pvm 212, 5551234 } // Alustaa jäsenen Puhelin } Henkilo nayttelija; nayttelija = han; //kopioidaan muuttujan han jäsenet han.nayta(); Pvm tanaan = 15, 2, 1999 cout << endl << "Tänään on "; tanaan.nayta(); cout << endl; cout << "Tänään " << nayttelija.nimi.etunimi << " on " << nayttelija.ika(tanaan) << " vuotias." << endl; return 0; 438