Lisää perustietotyypeistä



Samankaltaiset tiedostot
Perustietotyypit ja laskutoimitukset

Tietotyypit ja operaattorit

Kappale 20: Kantaluvut

Osoitin ja viittaus C++:ssa

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Java-kielen perusteet

Tietueet. Tietueiden määrittely

Osoittimet. Mikä on osoitin?

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

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

Java-kielen perusteet

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

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

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

811120P Diskreetit rakenteet

Virtuaalifunktiot ja polymorfismi

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

Moduli 4: Moniulotteiset taulukot & Bittioperaatiot

Datatähti 2019 loppu

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

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

VIII. Osa. Liitteet. Liitteet Suoritusjärjestys Varatut sanat Binääri- ja heksamuoto

Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö C-ohjelmassa

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

Valinnat ja päätökset

Harjoitus 3 (viikko 39)

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

System.out.printf("%d / %d = %.2f%n", ekaluku, tokaluku, osamaara);

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

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

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

Avaa ohjelma ja tarvittaessa Tiedosto -> Uusi kilpailutiedosto

3. Muuttujat ja operaatiot 3.1

Python-ohjelmointi Harjoitus 2

Sisällys. 6. Muuttujat ja Java. Muuttujien nimeäminen. Muuttujien nimeäminen. Muuttujien nimeäminen. Muuttujan tyypin määritys. Javan tietotyypit:

Binäärioperaatiot Tiedostot ja I/O

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

Algoritmit 1. Demot Timo Männikkö

Ohjelmointi 1 Taulukot ja merkkijonot

1. luento. Ohjelmointi (C) T0004 Syksy luento. 1. luento. 1. luento. 1. luento. kurssin sisältö ja tavoitteet työmuodot.

7. Näytölle tulostaminen 7.1

Harjoitustyö: virtuaalikone

Muuttujien roolit Kiintoarvo cin >> r;

Sisällys. 6. Muuttujat ja Java. Muuttujien nimeäminen. Muuttujien nimeäminen. salinovi tai syntymapaiva

6. Muuttujat ja Java 6.1

System.out.printf("%d / %d = %.2f%n", ekaluku, tokaluku, osamaara);

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.

KAAVAT. Sisällysluettelo

12 Mallit (Templates)

Ohjelmoinnin peruskurssi Y1

Kääntäjän virheilmoituksia

Ohjelmoinnin peruskurssi Y1

Sisällys. 6. Muuttujat ja Java. Muuttujien nimeäminen. Muuttujien nimeäminen. salinovi tai syntymapaiva

6. Muuttujat ja Java 6.1

Sisällys. 12. Näppäimistöltä lukeminen. Yleistä. Yleistä

Javan perusteet. Ohjelman tehtävät: tietojen syöttö, lukeminen prosessointi, halutun informaation tulostaminen tulostus tiedon varastointi

OHJ-1010 Tietotekniikan perusteet 4 op Syksy 2012

Taulukkolaskentaa selkokielellä EXCEL

PERUSLASKUJA. Kirjoita muuten sama, mutta ota välilyönti 4:n jälkeen 3/4 +5^2

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

Ohjelman virheet ja poikkeusten käsittely

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

Harjoitus 5. Esimerkki ohjelman toiminnasta: Lausekielinen ohjelmointi I Kesä 2018 Avoin yliopisto 1 / 5

Ohjelmoinnin perusteet Y Python

Taulukot. Jukka Harju, Jukka Juslin

Operaattoreiden uudelleenmäärittely

Harjoitus 5 (viikko 41)

ITKP102 Ohjelmointi 1 (6 op)

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

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

13. Loogiset operaatiot 13.1

Mallit standardi mallikirjasto parametroitu tyyppi

Luku 8. Aluekyselyt. 8.1 Summataulukko

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

Lukujärjestelmät. Digitaalitekniikan matematiikka Luku 9 Sivu 3 (26) Lukujärjestelmät ja lukujen esittäminen Fe

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

Ohjelmoinnin perusteet Y Python

Luennon sisältö Tyypit int, char, float, double signed, unsigned short, long Vakiot const Rakenteet if, for, while, switch, do-while Syöttö ja tulostu

SISÄLLYS - DIGITAALITEKNIIKKA

Tietotekniikan valintakoe

Taulukkolaskennan perusteet Taulukkolaskentaohjelmat

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

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

Yhtälönratkaisu oppilaan materiaali

Binäärioperaatiot Tiedostot ja I/O

Ohjelmoijan binaarialgebra ja heksaluvut

Toinen harjoitustyö. ASCII-grafiikkaa

2.3 Virheitä muunnosten käytössä

Ohjelmoinnin perusteet Y Python

Condes. Quick Start opas. Suunnistuksen ratamestariohjelmisto. Versio 7. Quick Start - opas Condes 7. olfellows 1.

Ohjelmoinnin perusteet Y Python

Taulukkolaskennan perusteet Taulukkolaskentaohjelmat

plot(f(x), x=-5..5, y= )

13 Operaattoreiden ylimäärittelyjä

Ohjeet Google kalenteriin. Kirjaudu palveluun saamillasi tunnuksilla

Digitaalitekniikan matematiikka Luku 10 Sivu 1 (14) Lukujärjestelmämuunnokset. 2 s s

C-ohjelma. C-ohjelma. C-ohjelma. C-ohjelma. C-ohjelma. C-ohjelma. Operaatioiden suoritusjärjestys

Matematiikan tukikurssi, kurssikerta 3

8.1 Murtoluvun määritelmä - murtoluvulla tarkoitetaan aina osaa (osia) jostakin kokonaisuudesta

Transkriptio:

Perustietotyypit ja laskutoimitukset 3 Lisää perustietotyypeistä Tässä luvussa käsittelemme lisää jo edellisessä luvussa käsittelemiämme perustietotyyppejä. Näemme, kuinka eri tyyppiset muuttujat vaikuttavat toisiinsa monimutkaisemmissa tilanteissa. Käsittelemme myöskin joitakin uusia C++:n ominaisuuksia sekä joitakin tapoja, joilla näitä ominaisuuksia käytetään. Tässä luvussa käsittelemme: Kuinka lasketaan lausekkeet, joissa on useita eri tietotyyppejä Miten arvo muunnetaan perustietotyypistä toiseksi perustietotyypiksi Mitä ovat bittioperaatiot ja kuinka niitä käytetään Kuinka määritellään uusi tyyppi, jonka arvot on rajoitettu tietylle arvovälille Kuinka olemassa oleville tietotyypeille annetaan toinen nimi Kuinka kauan muuttuja säilyy muistissa ja mikä sen määrää Mikä on muuttujan näkyvyysalue ja mikä sen määrää Eri tietotyyppejä sisältävät lausekkeet Kuten jo saatatkin tietää, tietokoneesi pystyy suorittamaan laskutoimituksia vain kahden saman tyyppisen arvon välillä. Se voi laskea yhteen kaksi kokonaislukua tai kaksi samanlaista liukulukua, mutta se ei pysty suoraan lisäämään kokonaislukuun liukulukua. Esimerkiksi lauseketta 2 + 7.5 se ei pysty laskemaan tällaisenaan. Ainoa tapa, jolla tämä laskutoimitus voidaan suorittaa, on muuntaa toinen arvoista saman tyyppiseksi kuin toinenkin on - tyypillisesti kokonaisluku muunnetaan vastaavaksi liukuluvuksi, joten lauseke laskettaisiin kuten 2.0 + 7.5. Tämä pätee kaikkiin C++:n lausekkeisiin, joissa on useita eri tyyppejä. Jokaisen binäärioperaation kummankin operandin tulee olla samaa tyyppiä; jos ne ovat eri tyyppiä, toinen pitää muuntaa toista vastaavaksi. Katsotaanpa seuraavia lauseita: int arvo1 = 10; long arvo2 = 25L; float arvo3 = 30.0f; double tulos = arvo1 + arvo2 + arvo3; 71

C++ Ohjelmoijan käsikirja Arvo tulos lasketaan kolmen erityyppisen muuttujan summana. Jokaisen laskutoimituksen kohdalla toinen operandeista muunnetaan toista operandia vastaavaksi ennen kuin laskutoimitus voidaan suorittaa. Tehtävä muunnos ja mikä operandi muunnetaan määrätään tiettyjen sääntöjen mukaan. Näitä sääntöjä tarkistetaan peräkkäin, kunnes löytyy sopiva kyseiseen tilanteeseen. Edellä oleva lause suoritetaan itse asiassa seuraavissa vaiheissa: arvo1 + arvo2 lasketaan muuntamalla arvo arvo1 long-tyyppiseksi ennen laskutoimitusta. Tuloksena on myös long-tyyppinen arvo, eli arvo lasketaan 10L + 25L = 35L. Seuraava operaatio on 35L + arvo3. Edellisen laskutoimituksen tulos 35L muunnetaan liukuluvuksi ennen kuin se lasketaan yhteen muuttujan arvo3 kanssa. Tulos on tyyppiä float, eli arvo lasketaan 35.0f + 30.0f = 65.0f. Lopuksi tulos muunnetaan double-tyyppiseksi ja talletetaan muuttujaan tulos. Eri tyyppejä sisältävien lausekkeiden muunnossääntöjä käytetään vain, jos binäärisen operaattorin operandit ovat erit. Nämä säännöt on seuraavassa lueteltu siinä järjestyksessä kuin ne tarkistetaan: 1. Jos toinen operandeista on tyyppiä long double, toinenkin muunnetaan long double -tyyppiseksi. 2. Jos toinen operandeista on tyyppiä double, toinenkin muunnetaan double -tyyppiseksi. 3. Jos toinen operandeista on tyyppiä float, toinenkin muunnetaan float-tyyppiseksi. 4. Operandi, joka on tyyppiä char, signed char, unsigned char, short tai unsigned short muunnetaan int-tyyppiseksi, jos tyyppi int pystyy esittämään kaikki operandin alkuperäisen tyypin arvot. Muutoin operandi muunnetaan unsigned int-tyyppiseksi. 5. Lueteltu tietotyyppi muunnetaan ensin tyypiksi int, unsigned int, long tai unsigned long sen mukaan mikä näistä tyypeistä pystyy esittämään kaikki luetellut arvot. 6. Jos toinen operandeista on tyyppiä unsigned long, toinenkin muunnetaan unsigned long-tyyppiseksi. 7. Jos toinen operandi on tyyppiä long ja toinen tyyppiä unsigned int, unsigned int - tyyppinen muunnetaan long-tyyppiseksi, jos tyyppi long pystyy esittämään kaikki unsigned int-tyypin arvot. Muutoin molemmat operandit muunnetaan unsigned long -tyyppisiksi. 8. Jos toinen operandeista on tyyppiä long, toinenkin muunnetaan long-tyyppiseksi. 72 Et ole vielä tavannut lueteltuja tyyppejä, mutta käsittelemme niitä hiukan myöhemmin tässä luvussa. Ne esiintyvät tässä jo nyt, jotta säännöt olisivat täydelliset. Tämä kaikki näyttää varsin monimutkaiselta, mutta se ei ole sitä. Osa monimutkaisuudesta tulee siitä, että kokonaislukutyyppien arvovälit riippuvat laitteistoympäristöstä ja sääntöjen tulee ottaa tämä huomioon. Kääntäjä tarkistaa säännöt ylläolevassa järjestyksessä, kunnes löytyy käyttökelpoinen sääntö. Jos operandit ovat samaa tyyppiä säännön soveltamisen jälkeen, itse operaatio suoritetaan. Jos näin ei ole, etsitään toinen sääntö. Perusidea on varsin yksinkertainen. Jos kaksi operandia on eri, pienemmän arvovälin omaavan tyypin operandi muunnetaan. Muodolliset säännöt voidaankin yksinkertaistaa:

Perustietotyypit ja laskutoimitukset 1. Jos operaatiossa on kaksi eri liukulukutyyppiä, pienemmän tarkkuuden operandi muunnetaan. 2. Jos operaatiossa on kokonaisluku ja liukuluku, kokonaisluku muunnetaan liukuluvuksi. 3. Jos operaatiossa on erilaisia kokonaislukutyyppejä, pienimmän arvovälin operandi muunnetaan. 4. Jos operaatiossa on lueteltuja tyyppejä, ne muunnetaan sopiviksi kokonaislukutyypeiksi. Termillä muunto tarkoitetaan automaattista muuntoa tyypistä toiseksi. Voit myös eksplisiittisesti muuntaa tyypin toiseksi. Tähän palaamme hetken päästä. Sijoitukset ja eri tietotyypit Jos sijoituslausekkeen oikealla puolella olevan lausekkeen tyyppi on eri kuin vasemmalla puolella olevan muuttujan, lausekkeen laskettu arvo muunnetaan automaattisesti muuttujaa vastaavaksi tyypiksi ennen sijoitusta. Monissa tilanteissa voit menettää tässä kohtaa tietoa. Oletetaan, että olemme määritelleet liukuluvun seuraavasti: double juuri = 1.732; Jos nyt kirjoitamme lauseen: int arvo = juuri; Muuttujan juuri arvon muuntaminen int-tyyppiseksi saa aikaan sen, että muuttujaan arvo sijoitetaan arvo 1. Tyyppiä int oleva muuttuja pystyy tallettamaan vain kokonaisia lukuja, joten muuttujaan juuri talletetun arvon desimaaliosaa ei huomioida muunnoksessa. Voit jopa hävittää tietoa erityyppisten kokonaislukujen sijoituksissa: long laskuri = 60000; short arvo = laskuri; Jos tyyppi short on kahden tavun kokoinen ja long nelitavuinen, muuttujan laskuri arvo ei mahdu muuttujaan arvo ja tuloksena on väärä arvo. Monet kääntäjät huomaavat tällaiset tilanteet ja näyttävät varoituksen, mutta älä luota, että näin tapahtuisi aina. Välttääksesi tämäntyyppiset ongelmat, vältä sijoittamasta erityyppistä tietoa toiseen, pienemmän arvovälin muuttujaan. Jos et voi välttää tällaista sijoitusta, voit määritellä muunnoksen eksplisiittisesti, jolloin samalla ilmoitat, että todella haluat näin tehdä. Eksplisiittiset tyypinmuunnokset Kääntäjä huolehtii automaattisesti tyypinmuunnoksista perustietotyyppien kohdalla, mutta voit myös eksplisiittisen tyypinmuunnoksen avulla pakottaa muunnoksen tietotyypistä toiseen. Kun haluat muuntaa lausekkeen arvon toiseksi tyypiksi, kirjoitat tyypinmuunnoksen muodossa: static_cast<tyyppi, joksi muunnetaan>(lauseke) 73

C++ Ohjelmoijan käsikirja Avainsana static_cast kertoo, että tyypinmuunnos tarkastetaan staattisesti - eli ohjelman käännösvaiheessa. Myöhemmin, kun käsittelemme luokkia, tapaamme dynaamisia tyypinmuunnoksia, jossa muunnos tarkastetaan dynaamisesti - eli ohjelman suorituksen aikana. Eksplisiittisessä tyypinmuunnoksessa lausekkeen lauseke arvo muunnetaan kulmasulkeiden välissä olevan tyypin mukaiseksi. Lauseke voi olla hyvinkin monimutkainen. Seuraavassa käytetään eksplisiittistä tyypinmuunnosta static_cast<>(): double arvo1 = 10.5; double arvo2 = 15.5; int kokonaisluku = static_cast<int>(arvo1) + static_cast<int>(arvo2); Muuttujan kokonaisluku alkuarvo lasketaan muuttujien arvo1 ja arvo2 kokonaislukuosien summana, joten ne muunnetaan eksplisiittisesti int-tyyppisiksi. Muuttujan kokonaisluku alkuarvoksi tulee täten arvo 25. Tyypinmuunnokset eivät vaikuta muuttujien arvo1 ja arvo2 arvoihin, ne säilyvät arvoissa 10.5 ja 15.5. Tyypinmuunnosten tuottamat arvot 10 ja 15 talletetaan väliaikaisesti laskutoimitusta varten, minkä jälkeen ne hävitetään. Vaikka molemmissa tyypinmuunnoksissa menetetään tietoa, kääntäjä olettaa aina, että eksplisiittistä tyypinmuunnosta käyttäessäsi tiedät, mitä olet tekemässä. Aikaisemmassa esimerkissä, jossa käsittelimme eri tyyppien sijoitusta, voit eksplisiittisen tyypinmuunnoksen avulla tehdä selväksi, että tiedät tyypinmuunnoksen tarpeellisuuden: int arvo = static_cast<int>(juuri); Yleisesti ottaen, eksplisiittisten tyypinmuunnosten tarve on melko pieni, varsinkin perustietotyyppien kohdalla. Jos joudut käyttämään ohjelmassasi runsaasti eksplisiittisiä tyypinmuunnoksia, on se yleensä merkki siitä, että sinun tulisi valita muuttujille sopivammat tyypit. On kuitenkin olemassa tilanteita, joissa näin täytyy tehdä, joten katsotaan esimerkkiä tällaisesta tilanteesta. Kokeile itse - Eksplisiittinen tyypinmuunnos Oletetaan, että meidän tarvitsee muuntaa pituus jaardeina (desimaaliosalla) jaardeiksi, jaloiksi ja tuumiksi (kokonaislukuina). Seuraavassa on esimerkki tästä: // Esimerkki 3.1 - Eksplisiittinen tyypinmuunnos #include <iostream> using namespace std; int main() { const long jalkoja_jaardissa = 3; const long tuumia_jalassa = 12; double jaardeja = 0.0; long jaardit = 0; long jalat = 0; long tuumat = 0; // Pituus jaardeina (desimaaliosineen) // Kokonaiset jaardit // Kokonaiset jalat // Kokonaiset tuumat 74

} cout << "Syötä pituus jaardeina (desimaaliosineen): "; cin >> jaardeja; Perustietotyypit ja laskutoimitukset // Lasketaan pituus jaardeina, jalkoina ja tuumina jaardit = static_cast<long>(jaardeja); jalat = static_cast<long>((jaardeja - jaardit) * jalkoja_jaardissa); tuumat = static_cast<long>(jaardeja * jalkoja_jaardissa * tuumia_jalassa) % tuumia_jalassa; << jaardeja << " jaardia on muunnettu: " << jaardit << " jaardia " << jalat << " jalkaa " << tuumat << " tuumaa."; ; return 0; Ohjelman tyypillinen tulostus on: Syötä pituus jaardeina (desimaaliosineen): 2.75 2.75 jaardia on muunnettu: 2 jaardia 2 jalkaa 3 tuumaa. Kuinka se toimii Ensimmäisillä kahdella funktion main() lauseella esitellään kaksi muunnosarvoa: const long jalkoja_jaardissa = 3; const long tuumia_jalassa = 12; Esittelemme nämä muuttujat vakioiksi const-määreellä, koska emme halua niitä muutettavan ohjelmassa ja tyyppinä käytämme long-tyyppiä yhteensopivuuden takia. Vaikka tyyppi short olisikin ollut riittävä näille arvoille, sen käyttö olisi itse asiassa kasvattanut (eikä pienentänyt) ohjelman kokoa. Tämä siitä syystä, että tällöin ohjelmassa tulisi käyttää automaattista tyypinmuunnosta. Seuraavat neljä esittelyä määrittelevät laskennassa tarvittavat muuttujat: double jaardeja = 0.0; long jaardit = 0; long jalat = 0; long tuumat = 0; // Pituus jaardeina (desimaaliosineen) // Kokonaiset jaardit // Kokonaiset jalat // Kokonaiset tuumat Seuraavaksi pyydämme käyttäjältä syötettä ja luemme syötteen näppäimistöltä: cout << "Syötä pituus jaardeina (desimaaliosineen): "; cin >> jaardeja; Seuraava lause laskee jaardien määrän kokonaisina eksplisiittisen tyypinmuunnoksen avulla: jaardit = static_cast<long>(jaardeja); 75

C++ Ohjelmoijan käsikirja Tyypinmuunnos muuntaa tyypiksi long ja hävittää desimaaliosat ja tallettaa kokonaislukutuloksen muuttujaan jaardit. Jos et käyttäisi eksplisiittistä tyypinmuunnosta tässä kohdassa, jotkut kääntäjät kääntäisivät ohjelman ilman varoitusta lisätystä automaattisesta tyypinmuunnoksesta. Tässä muunnoksessa on kuitenkin suuri mahdollisuus tiedon häviämiselle ja tällaisissa tapauksissa sinun tulee aina käyttää eksplisiittistä tyypinmuunnosta kertomaan, että todella haluat niin tehdä. Jos jättäisit sen pois, ei olisi selvää, että olet ymmärtänyt muunnoksen tarpeellisuuden ja mahdollisen tiedon häviämisen. Kokonaisten jalkojen määrän laskemme lauseella: jalat = static_cast<long>((jaardeja - jaardit) * jalkoja_jaardissa); Haluamme niiden kokonaisten jalkojen määrän, jotka eivät ole kokonaisissa jaardeissa, joten vähennämme muuttujan jaardeja arvosta jaardit. Kääntäjä huolehtii muuttujan jaardit automaattisesta muunnoksesta double-tyyppiseksi ja vähennyslaskun tulos on myös tyyppiä long. Muuttujan jalkoja_jaardissa arvo muunnetaan sitten automaattisesti double-tyyppiseksi kertolaskua varten. Lopuksi tulos muunnetaan eksplisiittisen tyypinmuunnoksen avulla tyypistä double tyyppiin long. Laskennan viimeisenä vaiheena on laskea jäljellä oleva pituus kokonaisissa tuumissa: tuumat = static_cast<long>(jaardeja * jalkoja_jaardissa * tuumia_jalassa) % tuumia_jalassa; Tämä tapahtuu laskemalla koko alkuperäinen pituus tuumina, muuntamalla se eksplisiittisesti long-tyyppiseksi ja jakamalla tulos tuumien määrällä jalassa. Lopuksi tulostamme tulokset: << jaardeja << " jaardia on muunnettu: " << jaardit << " jaardia " << jalat << " jalkaa " << tuumat << " tuumaa."; Vanhantyyliset tyypinmuunnokset Ennen kuin eksplisiittinen tyypinmuunnos static_cast<>() (sekä muut vastaavat: const_cast<>(), dynamic_cast<>() ja reinterpret<>(), joita käsittelemme myöhemmin) esiteltiin C++:ssa, tyypin muuntaminen eksplisiittisesti toiseksi kirjoitettiin seuraavasti: (tyyppi, joksi muunnetaan)lauseke Lausekkeen tulos muunnetaan tässä sulkeiden sisällä olevaksi tyypiksi. Esimerkiksi edellisen esimerkkimme tuumien laskenta tapahtuisi seuraavasti: tuumat = (long)(jaardeja * jalkoja_jaardissa * tuumia_jalassa) % tuumia_jalassa; 76

Perustietotyypit ja laskutoimitukset Itse asiassa käytössä on neljä erilaista eksplisiittistä tyypinmuunnosta ja vanhantyylinen kattaa ne kaikki. Tästä syystä ohjelma, jossa käytetään vanhantyylistä tyypinmuunnosta, on herkempi virheille - aina ei ole selvää, mitä tarkoitetaan, eikä tulos ole aina haluttu. Vanhantyylistä tyypinmuunnosta käytetään kuitenkin vielä runsaasti (se on yhä osa kieltä), mutta suosittelemme uusien käyttämistä koodissasi. Tyyppien tiedot Tyypin tarvitsemaa muistimäärää tavuissa ei ole määritelty C++:n standardissa; kääntäjä määrää sen. Joskus saattaa olla hyödyllistä saada selville tietyn tyypin tarvitsema muistimäärä. Tämän voit saada selville kääntäjäsi ohjekirjoista, mutta saat sen selville myös sizeof() -operaattorilla. sizeof() on unaarinen operaattori, joten se tarvitsee vain yhden operandin. Se palauttaa yhden kokonaislukuarvon, joka on halutun muuttujan tai tyypin viemä muistimäärä. Palautettava arvo on itse asiassa tyypin char kerrannaisina, mutta koska tyypin char koko on 1 tavu, palautettava arvo on operandin tarvitsema tila tavuina. Jos haluat saada selville tyypin tyyppi viemän muistimäärän, käytät lauseketta sizeof(tyyppi). Näin ollen tyypin int koon saat selville lauseella: << Tyypin int koko on << sizeof(int); // Tulostetaan tyypin int koko Lauseke sizeof(int) palauttaa kaiken int-tyyppiseksi esitellyn koon. Tällä tavalla saat selville minkä tahansa tietotyypin koon. Tyypin long double koon saat selville lauseella: << Tyypin long double koko on << sizeof(long double); // Tulostetaan tyypin long double koko Voit käyttää sizeof() -operaattoria tietyn muuttujan tai jopa lausekkeenkin kanssa. Tällaisessa tapauksessa lausekkeen ei tarvitse olla sulkeiden sisällä, mutta voit käyttää niitä halutessasi. Seuraavassa tulostetaan muuttujan numero viemä muistimäärä: long numero = 999999999; << Muuttujan numero koko on << sizeof numero; // Tulostetaan muuttujan numero koko Voit käsitellä sizeof() -operaattorin palauttamaa arvoa kokonaislukuna, mutta itse asiassa se palauttaa size_t-tyyppisen arvon. Tämä tyyppi on määritelty standardiotsikkotiedostossa cstddef tavallisesti unsigned int -tyyppiseksi. Kysytkin varmasti mielessäsi: Miksi uusi tyypin nimi tässä kohdassa? Miksei voida käyttää suoraan tyyppiä unsigned int? Tämä johtuu siitä, että halutaan mahdollistaa joustavuus. Operaattori sizeof() palauttaa aina size_t-tyyppisen arvon, jonka kääntäjäsi voi hyvinkin olla määritellyt unsigned int -tyyppiseksi, mutta näin ei välttämättä tarvitse olla. C++ -kääntäjän tekijöille voisi mahdollisesti olla 77

C++ Ohjelmoijan käsikirja järkevämpää määritellä tyyppi size_t jonkin muun tyyppiseksi, ja he voisivat myös tehdä niin. Se ei vaikuttaisi sinun koodiisi, koska oletat tuloksen olevan vain tyyppiä size_t. Käsittelemme synonyymin määrittelyä olemassa olevalle tyypille myöhemmin tässä luvussa. Kokeile itse - Tietotyyppien koko Seuraavassa esimerkissä tulostetaan kaikkien jo käsittelemiemme tyyppien koot: // Esimerkki 3.2 - Tietotyyppien koko #include <iostream> using namespace std; int main() { // Tulostetaan kokonaislukutyyppien koot << "Tyypin char koko on " << sizeof(char); << "Tyypin short koko on " << sizeof(short); << "Tyypin int koko on " << sizeof(int); << "Tyypin long koko on " << sizeof(long); } // Tulostetaan liukulukutyyppien koot << "Tyypin float koko on " << sizeof(float); << "Tyypin double koko on " << sizeof(double); << "Tyypin long double koko on " << sizeof(long double); ; return 0; Ohjelman tyypillinen tulostus on: Tyypin char koko on 1 Tyypin short koko on 2 Tyypin int koko on 4 Tyypin long koko on 4 Tyypin float koko on 4 78

Perustietotyypit ja laskutoimitukset Tyypin double koko on 8 Tyypin long double koko on 8 Voisit yhtä hyvin tässä esimerkissä käyttää sizeof() -operaattoria muuttujien ja lausekkeiden kanssa. Tietotyyppien raja-arvot Joskus saattaa olla tilanne, että haluat tietää tietystä tietotyypistä muutakin kuin pelkän koon. Saatat esimerkiksi haluta tietää tyypin arvojen ylä- ja alarajan. Standardi otsikkotiedosto limits sisältää tällaisen tiedon kaikista perustietotyypeistä. Tietoja voidaan käyttää kullekin tyypille määritellyn luokan avulla ja koska emme ole vielä käsitelleet luokkia, tämän toiminta saattaa olla hieman epäselvä tässä ja nyt. Näytämme kuitenkin, miten saat tämän tiedon, mutta emme käsittele yksityiskohtia ennen kuin käsittelemme luokkia luvussa 12. Katsotaan esimerkkiä. Tulostaaksesi double-tyyppiseen muuttujaan talletettavan suurimman mahdollisen arvon, voit kirjoittaa: << Tyypin double maksimiarvo on << numeric_limits<double>::max(); Lauseke numeric_limits<double>::max() tuottaa haluamamme tuloksen. Kirjoittamalla kulmasulkeiden sisään muita tietotyyppejä, saat selville niiden maksimiarvot. Voit myöskin korvata max():n min():llä, jolloin saat minimiarvon. Saat selville myös monia muita tietoja eri tyypeistä. Esimerkiksi binääristen numeroiden lukumäärän saat selville lausekkeella: numeric_limits<tyypin_nimi>::digits Tässä kirjoitat haluamasi tyypin tyypin_nimi kulmasulkeiden sisään. Liukulukujen kohdalla tuloksena on binääristen numeroiden lukumäärä mantissassa. Etumerkillisten kokonaislukutyyppien kohdalla tuloksena on binääristen numeroiden lukumäärä merkkibitti poislukien. Havainnollistamme edellä mainittuja asioita seuraavassa esimerkissä, joka tulostaa numeeristen tietotyyppien maksimi- ja minimiarvot. Kokeile itse - Maksimi- ja minimiarvot Seuraavassa on ohjelman koodi: // Esimerkki 3.3 - Maksimi- ja minimiarvot #include <limits> #include <iostream> using namespace std; 79

C++ Ohjelmoijan käsikirja int main() { << "Tyypin short arvoväli on " << numeric_limits<short>::min() << " - " << numeric_limits<short>::max(); << "Tyypin int arvoväli on " << numeric_limits<int>::min() << " - " << numeric_limits<int>::max(); << "Tyypin long arvoväli on " << numeric_limits<long>::min() << " - " << numeric_limits<long>::max(); << "Tyypin float arvoväli on " << numeric_limits<float>::min() << " - " << numeric_limits<float>::max(); << "Tyypin double arvoväli on " << numeric_limits<double>::min() << " - " << numeric_limits<double>::max(); << "Tyypin long double arvoväli on " << numeric_limits<long double>::min() << " - " << numeric_limits<long double>::max(); ; return 0; } Ohjelman tyypillinen tulostus on: Tyypin short arvoväli on -32768-32767 Tyypin int arvoväli on -2147483648-2147483647 Tyypin long arvoväli on -2147483648-2147483647 Tyypin float arvoväli on 1.17549e-038-3.40282e+038 Tyypin double arvoväli on 2.22507e-308-1.79769e+308 Tyypin long double arvoväli on 2.22507e-308-1.79769e+308 Otsikkotiedostossa limits määritellään myöskin raja-arvojen symbolit. Esimerkiksi vakio INT_MAX tarkoittaa tyypin int maksimiarvoa ja SCHAR_MIN tarkoittaa tyypin signed char minimiarvoa. Nämä ovat kuitenkin vain symboleja, jotka korvataan koodissasi vastaavilla numeroilla. Esimerkkiohjelmassamme haetuilla arvoilla on tyyppi ja siksi kääntäjä tarkastaa niiden tyypin, kun käytät niitä. Symbolit ovat olemassa historiallisista syistä ja edellä esitetty tapa on suositeltavampi tapa saada nämä tiedot selville. 80

Bittioperaattorit Perustietotyypit ja laskutoimitukset Kuten nimikin jo kertoo, bittioperaattoreilla voit käsitellä kokonaislukumuuttujaa bittitasolla. Voit käyttää niitä millaisen kokonaisluvun kanssa tahansa, niin signed, unsigned kuin char - muotoistenkin kanssa. Useimmiten niitä käytetään kuitenkin unsigned-muotoisten kokonaislukujen kanssa. Tavallinen käyttötarkoitus näille operaattoreille on, kun haluat käyttää kokonaislukumuuttujan yksittäisiä bittejä. Esimerkkinä olisi liput, joilla tarkoitetaan binäärisiä indikaattoreita. Voit käyttää bittiä aina, kun tarvitaan vain kahta arvoa: on tai off, mies tai nainen, tosi tai epätosi. Voit myöskin käyttää bittioperaattoreita, kun muuttujaan on talletettu useampia tietoalkioita. Oletetaan, että sinun täytyy pitää kirjaa fonteista. Haluat tallettaa tietoa kunkin fontin tyylistä ja koosta sekä onko fontti lihavoitu tai kursivoitu. Voit pakata kaiken tämän yhteen kaksitavuiseen kokonaislukumuuttujaan: Tyyli 6 Ei käytetä Pistekoko 12 0 0 0 0 0 1 1 0 1 0 0 1 1 0 0 Kursivoitu Ei lihavoitu Bittien käyttö fontin tietoina Voit käyttää yhtä bittiä pitämään kirjaa, onko fontti kursivoitu, ja toista bittiä, onko se lihavoitu. Yhden tavun avulla voit tallettaa 256 eri tyyliä. Viidellä lisäbitillä voit tallettaa fontin pistekoon aina arvoon 32 saakka. Bittioperaattoreilla pääset käsiksi ja voit muuttaa kokonaisluvun yksittäistä bittiä tai bittiryhmää helposti. Bittien siirto-operaattori Bittien siirto-operaattori siirtää kokonaislukumuuttujan bittejä halutun määrän biteissä oikealle tai vasemmalle. Operaattori >> siirtää bittejä oikealle ja operaattori << vasemmalle. Bitit, jotka valuvat yli jommasta kummasta päästä, häviävät. Katsotaan esimerkkiä, jossa oletamme tyypin int olevan kahden tavun mittainen, jotta esimerkki pysyy yksinkertaisena. Määrittelemme ja alustamme muuttujan numero lauseella: unsigned int numero = 16387U; Kuten näimme edellisessä luvussa, etumerkittömien literaalivakioiden yhteydessä tulisi käyttää liitteitä U tai u. Voimme siirtää tämän muuttujan sisältöä sekä tallettaa tuloksen lauseella: unsigned int tulos = numero << 2; //Vasemmalle kaksi bittiä 81

C++ Ohjelmoijan käsikirja Siirto-operaattorin vasemmanpuoleinen operandi on arvo, jota siirretään ja oikeanpuoleinen operandi on lukumäärä, montako bittiä siirretään. Seuraava kuva selventää tätä operaatiota: 16,387 on binäärisenä: 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 Lisätään nollia oikealta Vasemmalle 2: Nämä kaksi bittiä häviää 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 = 12 Lisätään nollia vasemmalta 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 Oikealle 2: 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 Nämä kaksi bittiä häviää = 4096 Kuten huomaat, kun siirrämme luvun 16387 bittejä kaksi bittiä vasemmalle, tuloksena on luku 12. Eniten merkitsevän bitin menettäminen vaikuttaa hyvin suuresti arvoon. Siirtääksemme oikealle, kirjoitamme: tulos = numero >> 2: //Oikealle kaksi bittiä Tämä siirtää arvoa 16387 oikealle kaksi bittiä ja tuloksena on luku 4096. Siirtäminen kaksi bittiä oikealle tarkoittaa itse asiassa jakamista luvulla 4. Niin kauan, kun bittejä ei menetetä, siirtäminen vasemmalle n bittiä on sama asia kuin kertominen n kertaa luvulla 2. Toisin sanoen se vastaa kertomista luvulla 2 n. Vastaavasti siirtäminen oikealle n bittiä vastaa jakamista luvulla 2 n. Mutta huomaa: kuten näimme muuttujan numero vasemmalle siirtämisessä, jos merkitseviä bittejä menetetään, tuloksena on arvo, jota et varmastikaan odottanut. Tämä ei kuitenkaan eroa mitenkään oikeasta kertolaskusta. Jos kerrot kaksitavuisen luvun neljällä, saat saman tuloksen, joten siirto vasemmalle ja kertominen ovat yhä vastaavia. Tarkkuudessa tulee ongelmia, koska kertolaskun tulos on kaksitavuisen kokonaisluvun arvoalueen ulkopuolella. Jos haluat muuttaa alkuperäistä arvoa, voit käyttää op=-operaattoria. Tässä tapauksessa käyttäisimme >>= tai <<= -operaattoria: numero >>= 2; //Oikealle kaksi bittiä 82

Perustietotyypit ja laskutoimitukset Tämä on sama kuin: numero = numero >> 2; //Oikealle kaksi bittiä Saatat ajatella, että näiden siirto-operaattoreiden ja tulostuksessa jo käyttämiemme >> ja <<operaattoreiden välillä tapahtuisi sekaannuksia. Kääntäjä pystyy kuitenkin yleensä erottamaan tarkoituksen. Jos se ei pysty, se antaa varoituksen. Sinun tulee kuitenkin olla varovainen. Jos esimerkiksi haluat tulostaa muuttujan numero, kun olet ensin siirtänyt sen arvoa vasemmalle kaksi bittiä, voit kirjoittaa: cout << (number << 2); Tässä sulkeet ovat välttämättömät. Ilman niitä kääntäjä tulkitsee siirto-operaattorin virran tulostus -operaattoriksi, eli tuloksena ei ole sitä mitä olit ajatellut. Etumerkillisten kokonaislukujen bittisiirto Voit käyttää bittisiirto-operaattoreita sekä etumerkillisten että etumerkittömien kokonaislukujen yhteydessä. Siirto oikealle -operaattorin toiminta saattaa kuitenkin vaihdella eri järjestelmissä etumerkillisten kokonaislukujen yhteydessä; toiminta riippuu kääntäjäsi toteutuksesta. Joissakin tapauksissa oikealle siirrossa lisätään nollia vapautuviin kohtiin ja joissain tapauksissa merkkibitti siirtyy oikealle, eli vapautuviin kohtiin lisätään ykkösiä. Syy miksi merkkibitti siirtyy oikealle on se, että säilytetään yhteensopivuus oikealle siirron ja jakolaskun välillä. Voimme havainnollistaa tätä char-tyyppisellä muuttujalla. Oletetaan, että määrittelemme char-tyyppisen muuttujan arvo ja asetamme siihen desimaalisen alkuarvon -104: signed char arvo = -104; Tämän binäärinen arvo on 10011000. Seuraavalla lauseella siirrämme sitä kaksi bittiä oikealle: arvo >>= 2; //Tulos: 11100110 Binäärinen tulos näytetään kommentissa. Kaksi nollaa häviää oikeasta reunasta ja koska merkkibitti on 1, vasemmalta lisättään ykkösiä. Tuloksen desimaaliarvo on -26, mikä on sama kuin jos olisimme jakaneet luvulla 4. Näinhän pitikin olla. unsigned-tyyppisillä kokonaisluvuilla merkkibittiä lisätään vasemmalta päin. Kuten mainittu, mitä todella tapahtuu kokonaislukujen bittien oikealle siirrossa, riippuu kääntäjästä, joten älä luota, että tämä toimii tällä tavalla. Koska käytät näitä operaattoreita pääasiassa bittitasolla työskennellessäsi - eli kun bittien eheys on tärkeää - sinun tulee aina käyttää unsigned kokonaislukuja. 83

C++ Ohjelmoijan käsikirja Bittitason loogiset operaattorit Käytössä on neljä bittioperaattoria, joilla voidaan muokata kokonaisluvun bittejä: Op Kuvaus ~ Tämä on bittitason komplementtioperaattori. Tämä on unaarinen operaattori, joka kääntää operandinsa bitit vastakkaisiksi, eli ykkösestä tulee nolla ja päinvastoin. & Tämä on bittitason AND-operaattori, joka suorittaa AND-operaation operandiensa biteille. Jos kummankin samassa kohdassa oleva bitti on 1, tulos on 1, muutoin tulos on 0. ^ Tämä on bittitason poissulkeva OR-operaattori, joka suorittaa poissulkevan OR - operaation operandiensa biteille. Jos samassa kohdassa olevat bitit ovat erit (eli jos toinen on 0 ja toinen 1), tulos on 1. Jos samassa kohdassa olevat bitit ovat samat, tulos on 0. Tämä on bittitason OR-operaattori, joka suorittaa OR-operaation operandiensa biteille. Jos jompi kumpi samassa kohdassa olevista biteistä on 1, tulos on 1. Jos molemmat ovat 0, tulos on 0. Operaattorit lueteltiin edellä suoritusjärjestyksessä, eli bittitason komplementtioperaattori suoritetaan näistä ensimmäisenä ja bittitason OR viimeisenä. Kuten näet täydellisestä operaattoreiden suoritusjärjestystaulukosta liitteestä D, siirto-operaattorit << ja >> ovat suoritusjärjestyksessä samalla tasolla ja ne ovat ~-operaattorin alapuolella, mutta &-operaattorin yläpuolella. Jos et ole käyttänyt tällaisia operaattoreita aikaisemmin, kysyt varmastikin: Erittäin mielenkiintoista, mutta mihin näitä käytetään? Katsotaan, saammeko niille jotain järkevää käyttöä. Bittitason AND-operaattori Tavallisesti käyttäisit bittitason AND-operaattoria valitessasi kokonaisluvun tietyn bitin tai bittiryhmän. Jotta näemme mitä tämä tarkoittaa, käytämme uudelleen tämän osan alussa ollutta esimerkkiä, jossa käytettiin kokonaislukua tallettamaan fontin tietoja. Oletetaan, että määrittelimme ja alustimme muuttujan sisällöksi 12 pisteen, kursivoidun, tyylin 6 fontin - eli itse asiassa juuri saman kuin käytimme esimerkkikuvassammekin. Binäärimuodossa tyyli on 00000110, kursivoitu-bitti on 1, lihavoitu-bitti on 0 ja koko on 01100. Kun vielä muistamme, että muuttujassa on yksi bitti, jota ei käytetä, muuttuja fontti tulee alustaa binääriarvolla 0000 0110 0100 1100. Koska neljän bitin ryhmä vastaa yhtä heksadesimaalista numeroa, helpoin tapa määritellä alkuarvo on käyttää heksadesimaalista muotoa: unsigned int fontti = 0x064C; // Tyyli 6, kursivoitu, 12 pist. 84! Kun bittejä asetetaan tällä tavalla, heksadesimaalinen esitysmuoto on poikkeuksetta parempi tapa kuin desimaalisten arvojen käyttö.

Perustietotyypit ja laskutoimitukset Käyttääksemme fontin kokoa, meidän tulee saada se selville muuttujasta fontti; tämä voidaan tehdä bittitason AND-operaattorilla. Koska bittitason AND-operaattorin tuloksena on 1 vain silloin kun molemmat bitit ovat ykkösiä, voimme määritellä arvon, joka valitsee fontin koon, kun suoritamme bittitason AND-operaation muuttujalle fontti. Tämä tapahtuu yksinkertaisesti määrittelemällä arvo, jossa on ykkösiä niissä kohdissa, joista olemme kiinnostuneita ja nollia muissa kohdissa. Tällaista arvoa kutsutaan maskiksi. Tällainen maski voidaan määritellä lauseella: unsigned int maski_koko = 0x1F; //Maski on 0000 0000 0001 1111 Viisi vähiten merkitsevää bittiä määrittelee fontin koon, joten ne asetetaan maskissa ykkösiksi. Muut bitit ovat 0, joten niitä ei oteta huomioon. Voimme nyt irrottaa muuttujan fontti koon lauseella: unsigned int koko = fontti & maski_koko; Kun samassa kohdassa olevat bitit ovat molemmat 1, tulosbitti on 1. Muutoin tulosbitti on 0. Arvot muodostuvat seuraavalla tavalla: fontti 0000 0110 0100 1100 maski_koko 0000 0000 0001 1111 fontti & maski_koko 0000 0000 0000 1100 Bittien jakamisella neljän bitin ryhmiin ei ole varsinaista merkitystä; se vain helpottaa näkemään montako bittiä luvussa on. Kuten näet maskin tarkoituksena on erottaa viisi oikeanpuolimmaista bittiä, jotka kuvaavat pistekokoa. Voimme käyttää samaa menetelmää fontin tyylin erottamiseen, mutta tällöin tarvitsemme myöskin siirto-operaattoria siirtämään fontin tyyliä oikealle. Voimme määritellä maskin, jolla valitaan kahdeksan vasemmanpuoleisinta bittiä: unsigned int maski_tyyli = 0XFF00; //Maski on 1111 1111 0000 0000 Fontin tyyli valitaan lauseella: unsigned int tyyli = (fontti & maski_tyyli) >> 8; Lauseen vaikutus on seuraava: fontti 0000 0110 0100 1100 maski_tyyli 1111 1111 0000 0000 fontti & maski_tyyli 0000 0110 0000 0000 (fontti & maski_tyyli) >> 8 0000 0000 0000 0110 Yhtä helposti voit irrottaa kursivointia ja lihavointia ilmaisevat bitit. Tarvitset luonnollisestikin tavan, jolla voit testata tulosbitin arvon. Tämän opimme seuraavassa luvussa. Toinen käyttötapa bittitason AND-operaattorille on bittien muuttaminen nollaksi. Edellä näimme, että jos bitti on maskissa 0, tulosbittinäkin on 0. Muuttaaksesi esimerkiksi kursivointibitin nollaksi, suoritat bittitason AND-operaation maskilla, jossa kursivoitu-bitti on 0 ja kaikki muut bitit 1. Katsomme, miten tämä tehdään, kun käsittelemme bittitason ORoperaattoria. 85

C++ Ohjelmoijan käsikirja Bittitason OR Bittitason poissulkevan OR-operaattorin avulla voit asettaa yksittäisiä bittejä tai bittiryhmiä. Jos jatkamme esimerkkimuuttujamme fontti käsittelyä, on varmasti loogista, että haluamme asettaa kursivointi- ja lihavointibitit tarvittaessa ykkösiksi. Voimme muodostaa maskin näiden bittien valitsemiseksi: unsigned int kursivoitu = 0X40U; unsigned int lihavoitu = 0X20U; //7. bitti oikealta //6. bitti oikealta Nyt voimme asettaa lihavoinnin päälle lauseella: fontti = lihavoitu; //Asetetaan lihavointi Bitit muodostuvat tässä seuraavasti: fontti 0000 0110 0100 1100 lihavoitu 0000 0000 0010 0000 fontti lihavoitu 0000 0110 0110 1100 Nyt muuttuja fontti määrittelee, että fontti on lihavoitu sekä kursivoitu. Huomaa, että tämä operaatio asettaa bitin arvoksi ykkösen riippumatta alkuperäisestä arvosta. Jos bitti oli jo ennen 1, se säilyy ykkösenä. Voit myös asettaa useita bittejä yhtä aikaa usean maskin avulla. Seuraava lause asettaa sekä lihavoitu- että kursivoitu-bitin: fontti = lihavoitu kursivoitu;//asetetaan lihav. ja kursivointi Tässä yhteydessä on helppo mennä lankaan valitsemalla väärä operaattori. Koska ajattelemme mielessämme: Aseta kursivointi ja lihavointi, houkutuksena on käyttää &-operaattoria, mikä olisi väärin. Käyttämällä &-operaattoria kahdelle maskille olisi tuloksena arvo, jossa kaikki bitit ovat nollia, eli et muuttaisi mitään varsinaisessa muuttujassa. Kuten mainitsimme edellisen osan lopussa, voimme käyttää &-operaattoria asettamaan bittien arvoksi 0. Tarvitset vain maskin, jossa on 0 sen bitin kohdalla, jonka haluat asettaa nollaksi ja 1 muissa kohdissa. Miten sitten määritellään tällainen maski? Jos haluat määritellä sen eksplisiittisesti, sinun täytyy tietää, montako bittiä muuttujassa on - ei niinkään hyvä tapa, jos haluat ohjelmasi olevan siirrettävän. Voit kuitenkin saada maskin selville käyttämällä bittitason komplementtioperaattoria maskille, jolla asettaisit kyseisen bitin ykköseksi. Voimme ratkaista maskin, jolla lihavointi poistetaan, lihavoitu-maskista itsestään: lihavoitu 0000 0000 0010 0000 ~lihavoitu 1111 1111 1101 1111 Komplementtioperaattori saa aikaan sen, että alkuperäisen muuttujan kaikki bitit käännetään. Kuten huomaat, tämä muodostaa haluamamme tuloksen riippumatta siitä, onko tyyppi unsigned int kaksitavuinen vai nelitavuinen. Bittitason komplementtioperaattoria kutsutaan joskus NOT-operaattoriksi, koska alkuperäinen bitti muuttuu aina vastakkaiseksi. 86

Perustietotyypit ja laskutoimitukset Näin ollen voimme poistaa lihavoinnin seuraavalla lauseella: fontti &= ~lihavoitu; //Poistetaan lihavointi Voit myös asettaa useamman bitin yhdellä kertaa nollaksi yhdistämällä monta maskia &- operaattorilla: fontti &= ~lihavoitu & ~kursivoitu; //Poistetaan lihav. ja kursivointi Tämä asettaa muuttujan fontti lihavoinnin ja kursivoinnin pois päältä. Huomaa, että tässä ei tarvita sulkeita, koska ~-operaattori on suoritusjärjestyksessä ennen &-operaattoria. Bittitason poissulkeva OR Bittitason poissulkevaa OR-operaattoria käytetään huomattavasti harvemmin kun & ja - operaattoreita ja sen käytöstä ei ole monia esimerkkejä. Eräs tärkeä käyttökohta on grafiikan ohjelmoinnissa. Yksi tapa muodostaa vaikutelma, että kuvio liikkuu näytöllä, on piirtää kuvio, poistaa se ja piirtää se uuteen kohtaan. Tämä pitää toistaa hyvin nopeasti, jos haluat pehmeän animaation. Kuvion poistaminen on tässä kriittisessä osassa. Et tietenkään halua tyhjentää koko näyttöä ja piirtää sitä uudelleen. Se olisi liian hidasta ja näyttö vilkkuisi. Ideana on, että poistat vain kuvion tai kuviot, joita olet siirtämässä. Tämä voidaan tehdä ns. poissulkevalla OR -tilalla. Piirtäminen poissulkevassa OR -tilassa Piirretöään alkuperäisellä värillä Piirretään taustan värillä uudelleen ja piirretään uuteen kohtaan Piirretään taustan värillä uudelleen ja piirretään uuteen kohtaan Piirretään taustan värillä uudelleen ja piirretään uuteen kohtaan Piirretään taustan värillä uudelleen ja piirretään uuteen kohtaan Piirretään taustan värillä uudelleen ja piirretään uuteen kohtaan Poissulkeva OR -tila perustuu ideaan, että kun olet piirtänyt kuvion näytölle tietyllä värillä, se häviää näytöltä, jos piirrät sen uudelleen taustan värillä. Kun piirrät kuvion näytölle poissulkevassa OR -tilassa, väri vuorottelee automaattisesti kuvion värin ja taustan värin välillä aina kun piirrät kuvion. Tämä saavutetaan käyttämällä bittitason poissulkevaa OR -operaattoria, jolla värit muutetaan automaattisesti ja nopeasti. Eli jos käytät poissulkevaa OR -operaattoria kahdelle arvolle ja sen jälkeen käytät uudelleen poissulkevaa OR -operaattoria jommalle kummalle alkuperäisistä arvoista äskeisen tuloksella, saat toisen alkuperäisistä arvoista. Kuulostaa monimutkaiselta, joten katsotaan, kuinka se toimii esimerkin avulla. 87

C++ Ohjelmoijan käsikirja Oletetaan, että haluamme vaihdella piirtovärin (käytämme punaista) ja taustavärin (valkoinen) välillä. Väri ilmaistaan usein kolmella 8-bittisellä arvolla, jotka vastaavat punaisen, sinisen ja vihreän voimakkuuksia. Muuttamalla punaisen, sinisen ja vihreän suhteita, voit muodostaa noin 16 miljoonaa eri väriä valkoisesta mustaan. Kirkas punainen on 0xFF0000, jossa punaisen arvo on maksimissa ja muut kaksi väriä ovat nollassa. Samaan tapaan vihreä olisi 0xFF00 ja sininen 0xFF. Valkoisessa on kaikki värit maksimissa, eli se on 0xFFFFFF. Voimme nyt määritellä muuttujat, jotka esittävät punaista ja valkoista: unsigned long punainen = 0XFF0000UL; unsigned long valkoinen = 0XFFFFFFUL; //Punainen //Valkoinen Seuraavaksi teemme maskin, jolla muutamme värin valkoisen ja punaisen välillä sekä asetamme piirtovärin muuttujan alkuarvoksi punaisen: unsigned long maski = punainen ^ valkoinen; unsigned long piirtovari = punainen; //Värien vaihdon maski //Piirtoväri Muuttuja maski alustetaan haluamistamme väreistä bittitason poissulkevan OR -operaattorin avulla: punainen 1111 1111 0000 0000 0000 0000 valkoinen 1111 1111 1111 1111 1111 1111 maski (punainen ^ valkoinen) 0000 0000 1111 1111 1111 1111 Jos käytämme poissulkevaa OR -operaattoria maskin ja punaisen välillä, saamme valkoisen ja päinvastoin. Näin ollen, kun olemme piirtäneet kuvion piirtovari-värillä, voimme vaihtaa värin lauseella: piirtovari ^= maski; //Vaihdetaan piirtoväri Tämän vaikutus on seuraava: piitovari 1111 1111 0000 0000 0000 0000 maski 0000 0000 1111 1111 1111 1111 piirtovari ^ maski 1111 1111 1111 1111 1111 1111 Eli olemme muuttaneet värin punaisesta valkoiseen. Suorittamalla saman lauseen uudelleen vaihtuu väri takaisin punaiseksi: piirtovari ^= maski; //Vaihdetaan piirtoväri Tämän vaikutus on seuraava: 88 piitovari 1111 1111 1111 1111 1111 1111 maski 0000 0000 1111 1111 1111 1111 piirtovari ^ maski 1111 1111 0000 0000 0000 0000 Kuten huomaat, piirtovari on jälleen punainen. Tämä tekniikka toimii minkä kahden värin kohdalla tahansa, vaikka itse asiassa sillä ei ole mitään tekemistä itse värien kanssa: voit käyttää sitä minkä tahansa kahden kokonaislukuarvon välillä siirtymiseen.

Perustietotyypit ja laskutoimitukset Kokeile itse - Bittitason operaattorit Seuraava esimerkki selventää bittioperaattoreiden käyttöä. Havainnollistamme myöskin poissulkevan OR -operaattorin käyttöä kahden arvon välillä vaihtelemiseen sekä miten maskeja käytetään yksittäisten bittien valitsemiseen ja asettamiseen: // Esimerkki 3.4 - Bittitason operaattorit #include <iostream> #include <iomanip> using namespace std; int main() { unsigned long punainen = 0XFF0000UL; unsigned long valkoinen = 0XFFFFFFUL; cout << hex; cout.fill('0'); // Punainen // Valkoinen // Heksadesimaalinen tulostus // Asetetaan tulostuksen täyttömerkki cout << "\nkokeillaan bittitason AND ja OR -operaattoreita."; cout << "\nalkuarvo punainen = " << setw(8) << punainen; cout << "\nkomplementti ~punainen = " << setw(8) << ~punainen; cout << "\nalkuarvo valkoinen = " << setw(8) << valkoinen; cout << "\nkomplementti ~valkoinen = " << setw(8) << ~valkoinen; cout << "\nand punainen & valkoinen = " << setw(8) << (punainen & valkoinen); cout << "\nor punainen valkoinen = " << setw(8) << (punainen valkoinen); cout << "\n\nperäkkäisiä poissulkevia OR-operaatioita."; unsigned long maski = punainen ^ valkoinen; cout << "\nmaski = punainen ^ valkoinen = " << setw(8) << maski; cout << "\nmaski ^ punainen = " << setw(8) << (maski ^ punainen); cout << "\nmaski ^ valkoinen = " << setw(8) << (maski ^ valkoinen); unsigned long liput = 0xFF; // Liput-muuttuja unsigned long bit1maski = 0x1; // Valitsee bitin 1 unsigned long bit6maski = 0x20; // Valitsee bitin 6 unsigned long bit20maski = 0x80000; // Valitsee bitin 20 cout << "\n\nvalitaan tai asetetaan maskien avulla tietty lippu."; cout << "\nvalitaan 1. bitti lipuista: " << setw(8) << (liput & bit1maski); cout << "\nvalitaan 6. bitti lipuista: " << setw(8) << (liput & bit6maski); cout << "\nasetetaan lippujen 6. bitti: " << setw(8) << (liput &= ~bit6maski); cout << "\nasetetaan lippujen 20. bitti: " << setw(8) << (liput = bit20maski); ; return 0; } 89

C++ Ohjelmoijan käsikirja Ohjelma tulostaa seuraavaa: Kokeillaan bittitason AND ja OR -operaattoreita. Alkuarvo punainen = 00ff0000 Komplementti ~punainen = ff00ffff Alkuarvo valkoinen = 00ffffff Komplementti ~valkoinen = ff000000 AND punainen & valkoinen = 00ff0000 OR punainen valkoinen = 00ffffff Peräkkäisiä poissulkevia OR-operaatioita. maski = punainen ^ valkoinen = 0000ffff maski ^ punainen = 00ffffff maski ^ valkoinen = 00ff0000 Valitaan tai asetetaan maskien avulla tietty lippu. Valitaan 1. bitti lipuista: 00000001 Valitaan 6. bitti lipuista: 00000020 Asetetaan lippujen 6. bitti: 000000df Asetetaan lippujen 20. bitti: 000800df Kuinka se toimii Tarvitsemme #include-esikäsittelijäkomentoa sisällyttämään iomanip-otsikkotiedoston, koska ohjelmamme käyttää muokkausfunktioita kontrolloimaan tulostusta. Aluksi määrittelemme kaksi kokonaislukumuuttujaa, jotka sisältävät arvot väreille, joita käytämme bittioperaatioissamme: unsigned long punainen = 0XFF0000UL; unsigned long valkoinen = 0XFFFFFFUL; // Punainen // Valkoinen Haluamme tulostaa tiedot heksadesimaalisina. Tämän määrittelemme lauseella: cout << hex; // Heksadesimaalinen tulostus Tässä hex asettaa tulostuksen siten, että kokonaislukuarvot tulostetaan heksadesimaalisina. Huomaa, että tämä on modaalinen - kaikki sitä seuraavat kokonaislukujen tulostukset tulostuvat heksadesimaalisina. Eli hex tarvitsee lähettää cout-tulostusvirtaan vain kerran. Jos haluat, voit palauttaa tulostuksen desimaaliseksi: cout << dec; //Desimaalinen tulostus Tässä dec asettaa tulostuksen siten, että kokonaislukuarvot tulostuvat oletusarvoisessa desimaalisessa muodossa. Huomaa, että tulostuksen muuttaminen heksadesimaaliseksi vaikuttaa vain kokonaislukuarvojen tulostamiseen. Liukuluvut tulostuvat edelleen normaalissa desimaalimuodossa. Asioiden selkeyttämiseksi tulostamme kokonainaisluvut alkunollilla. Tämä asetetaan seuraavalla lauseella: cout.fill('0'); // Asetetaan tulostuksen täyttömerkki 90

Perustietotyypit ja laskutoimitukset Tässä fill() on funktio, joka asettaa täyttömerkiksi sulkeiden sisällä olevan merkin. Tämä on myöskin modaalinen, joten kaikki seuraavat kokonaislukujen tulostukset käyttävät tätä täyttömerkkiä tarvittaessa. Tämä vaikuttaa sekä desimaalisiin että heksadesimaalisiin tulostuksiin. Jos olisit halunnut täytteeksi * -merkit, olisit kirjoittanut: cout.fill( * ); // Asetetaan tulostuksen täyttömerkki Jos haluat täyttömerkin takaisin oletusarvoonsa, kirjoita sulkeiden sisään välilyönti: cout.fil( ); // Asetetaan tulostuksen täyttömerkki Älä sijoita funktion fill() kutsua tulostuslauseeseen. Jos teet näin, edellinen täyttömerkki tulostetaan, koska funktio fill() palauttaa sen. Tämä selvenee, kun käsittelemme funktioita luvussa 8. Punaisen ja sen komplementin arvot tulostetaan lauseilla: cout << "\nalkuarvo punainen = " << setw(8) << punainen; cout << "\nkomplementti ~punainen = " << setw(8) << ~punainen; Käytämme setw()-muokkausfunktiota, jolla asetamme tulostuksen kentän leveyden arvoksi 8. Jos varmistamme, että kaikki tulostetut arvot ovat saman levyisissä kentissä, niiden vertaileminen on helpompaa. Kentän leveyden asettaminen ei ole modaalinen; se vaikuttaa vain seuraavaan tulostuslauseeseen. Punaisen ja valkoisen tulostuksesta näet, että ~-operaattori tekee sen minkä odotimmekin: kääntää operandinsa bitit. Vertailemme muuttujia punainen ja valkoinen bittitason AND ja OR -operaattoreilla: cout << "\nand punainen & valkoinen = " << setw(8) << (punainen & valkoinen); cout << "\nor punainen valkoinen = " << setw(8) << (punainen valkoinen); Huomaa sulkeet tulostuslausekkeessa. Ne ovat tarpeelliset, koska << on suoritusjärjestyksessä operaattoreiden & ja edellä. Ilman sulkeita lauseet eivät käänny. Tulostuksesta huomaat, että näin todella tapahtuu. Seuraavaksi teemme maskin, jonka avulla vaihtelemme punaisen ja valkoisen välillä. Tähän käytämme poissulkevaa OR-operaattoria: unsigned long maski = punainen ^ valkoinen; Jos tutkit maskin tulostusta, huomaat, että poissulkevan OR-operaattorin tuloksen bitin arvo on 1, kun bitit ovat erit ja 0, kun ne ovat samat. Käyttämällä maskia jomman kumman värin kanssa, saamme toisen väreistä: cout << "\nmaski ^ punainen = " << setw(8) << (maski ^ punainen); cout << "\nmaski ^ valkoinen = " << setw(8) << (maski ^ valkoinen); 91

C++ Ohjelmoijan käsikirja Viimeiset lauseet havainnollistavat, miten valitaan yksittäinen bitti lippubittien joukosta. Maskissa, joka valitsee halutun bitin, kyseisen bitin tulee olla 1 ja kaikissa muissa kohdissa 0. Näin ollen maskit, joilla valitaan 32-bittisestä long-muuttujasta bitit 1, 6 ja 20, ovat seuraavanlaiset: unsigned long bit1maski = 0x1; // Valitsee bitin 1 unsigned long bit6maski = 0x20; // Valitsee bitin 6 unsigned long bit20maski = 0x80000; // Valitsee bitin 20 Valitaksemme bitin muuttujasta liput, käytämme bittitason AND-operaattoria halutun maskin kanssa: cout << "\nvalitaan 6. bitti lipuista: " << setw(8) << (liput & bit6maski); Asettaaksesi bitin nollaksi, sinun tulee käyttää bittitason AND-operaattoria maskin kanssa, jossa on 0 bitin kohdalla, jonka haluat asettaa nollaksi ja 1 muualla. Tällainen maski saadaan aikaan helposti komplementtioperaattorilla maskista, jossa halutun bitin arvo on 1. Maski bit6maski on juuri tällainen maski. Lause, joka asettaa nollaksi bitin 6 ja tulostaa tuloksen, on: cout << "\nasetetaan lippujen 6. bitti: " << setw(8) << (liput &= ~bit6maski); Jos bitti 6 oli jo nolla, se tietysti säilyy sellaisena. Asettaaksesi bitin arvoksi ykkösen, käytät bittitason OR-operaattoria maskilla, jossa haluamasi bitin arvo on 1: cout << "\nasetetaan lippujen 20. bitti: " << setw(8) << (liput = bit20maski); Tämä asettaa muuttujan liput bitin 20 arvoksi ykkösen ja tulostaa tuloksen. Edelleen, jos bitin arvo oli jo 1, se säilyy sellaisena. Lisää muokkausfunktioista Olemme tähän mennessä tutustuneet viiteen modaaliseen tulostuksen muokkausfunktioon, jotka määritellään otsikkotiedostossa iostream: scientific, fixed, dec, hex ja oct. Nyt listaamme kaikki muutkin tällaiset funktiot. Älä välitä viimeisissä kahdessa mainittuja bool-arvoja - käsittelemme niitä seuraavassa luvussa. Muokkausf. Toiminta dec Muotoilee kokonaisluvut 10-kantaisina (desimaalisina). Oletuksena. hex Muotoilee kokonaisluvut 16-kantaisina (heksadesimaalisina). oct Muotoilee kokonaisluvut 8-kantaisina (oktaalisina). left Tasaa arvot tulostuskentän vasempaan reunaan ja täyttää oikealta täyttömerkillä. Oletustäyttömerkkinä on välilyönti. 92

Perustietotyypit ja laskutoimitukset Muokkausf. Toiminta right fixed scientific Tasaa arvot tulostuskentän oikeaan reunaan ja täyttää vasemmalta täyttömerkillä. Tämä on oletustasaus. Tulostaa liukuluvut fixed-muodossa, eli ilman eksponenttia. Tulostaa liukuluvut tieteellisessä muodossa, eli mantissan ja eksponentin avulla. Oletusarvoisesti liukuluvut tulostetaan joko fixed tai scientific, riippuen tulostettavasta arvosta. showpoint Tulostaa desimaalipisteen ja liukuluvun loppunollat. noshowpoint Vastakohtainen edelliselle muokkausfunktiolle. Tämä on oletusarvona. showbase Tulostaa oktaaliluvun eteen 0 ja heksadesimaalisen eteen 0x tai 0X. noshowbase Tulostaa oktaaliset ja heksadesimaaliset luvut ilman etuliitettä. Oletusarvona. showpos Tulostaa plus-merkin (+) positiivisille arvoille. noshowpos Positiivisille arvoille ei tulosteta plus-merkkiä. Tämä on oletusarvona. uppercase Tulostaa isot merkit A - F heksadesimaalinumeroina, kun kokonaisluvut tulostetaan heksadesimaalisina ja 0X jos showbase on asetettu. Tulostaa E eksponentille, jos arvot tulostetaan tieteellisessä muodossa. nouppercase Tulostaa pienet merkit edellä olleille. Tämä on oletusarvona. boolalpha Tulostaa bool-arvot sanoilla true ja false. noboolalpha Tulostaa bool-arvot lukuina 1 ja 0. Jos haluat asetta samalla kertaa useamman näistä tiloista, voit tehdä sen tulostamalla useamman muokkausfunktion tulostusvirtaan. Jos esimerkiksi haluat tulostaa kokonaisluvut heksadesimaalisina vasemmalta tasattuun tulostuskenttään, voit kirjoittaa: cout << hex << left << arvo; Tämä tulostaa muuttujan arvo (sekä kaikki seuraavat kokonaisluvut, kunnes nämä tulostusasetukset muutetaan) vasemmalle tasattuna heksadesimaalisena lukuna. Luetellut tietotyypit Joskus tarvitaan muuttujia, joihin voidaan sijoittaa vain tiettyjä, nimettyjä arvoja, kuten viikonpäivät tai kuukaudet. Tällaiseen tarkoitukseen on C++:ssa luetellut tietotyypit. Kun käytät lueteltua tietotyyppiä, teet itse asiassa uuden tietotyypin. Katsotaan esimerkkiä käyttämällä juuri mainittuja viikonpäiviä. Voimme määritellä tämän seuraavasti: enum Viikonpaiva { Maanantai, Tiistai, Keskiviikko, Torstai, Perjantai, Lauantai, Sunnuntai }; 93

C++ Ohjelmoijan käsikirja Tämä määrittelee luetellun tietotyypin Viikonpaiva, jonka tyyppiset muuttujat voivat saada vain aaltosulkeiden välissä määriteltyjä arvoja. Jos yrität asettaa Viikonpaiva-tyyppiseen muuttujaan arvoja, jotka eivät ole lueteltujen arvojen joukossa, saat virheilmoituksen. Itse asiassa kukin viikonpäivä määritellään automaattisesti kokonaislukuarvoksi. Luettelon ensimmäinen nimi, Maanantai, saa arvon 0, Tiistai saa arvon 1 ja Sunnuntai saa lopulta arvon 6. Voimme määritellä muuttujan tanaan luetellun tyypin Viikonpaiva tyyppiseksi lauseella: Viikonpaiva tanaan = Tiistai; Tyyppiä Viikonpaiva käytetään aivan samaan tapaan kuin perustietotyyppejäkin. Tämä määrittely myöskin alustaa muuttujan tanaan arvoksi Tiistai. Jos tulostat muuttujan tanaan arvon, tulostuu luku 1. Oletusarvoisesti luetellun arvon kokonaislukuarvo on edellisen luetellun arvon kokonaislukuarvo + 1 ja ensimmäisen kokonaislukuarvo on 0. Jos haluat, että numerointi alkaa esimerkiksi arvosta 1, voit tehdä sen seuraavasti (kokonaislukuarvot ovat 1-7): enum Viikonpaiva { Maanantai = 1, Tiistai, Keskiviikko, Torstai, Perjantai, Lauantai, Sunnuntai }; Kaikilla tunnisteilla ei tarvitse olla eri arvoa. Voit määritellä tunnisteille Maanantai ja Ma arvon 1 lauseella: enum Viikonpaiva { Maanantai = 1, Ma = 1, Tiistai, Keskiviikko, Torstai, Perjantai, Lauantai, Sunnuntai }; Tämä mahdollistaa viikon ensimmäistä päivää kutsuttavan joko tunnisteella Maanantai tai Ma. Muuttuja eilen, joka on määritelty Viikonpaiva-tyyppiseksi, voidaan asettaa lauseella: eilen = Ma; Voit myöskin määritellä tunnisteen luettelossa aiemmin olleen tunnisteen perusteella. Sijoitetaan kaikki jo tästä aiheesta käsittelemämme samaan lauseeseen: enum Viikonpaiva { Maanantai, Ma = Maanantai, Tiistai = Maanantai + 2, Ti = Tiistai, Keskiviikko = Tiistai + 2, Ke = Keskiviikko, Torstai = Keskiviikko + 2, To = Torstai, Perjantai = Torstai + 2, Pe = Perjantai, Lauantai = Perjantai + 2, La = Lauantai, Sunnuntai = Lauantai + 2, Su = Sunnuntai }; Nyt Viikonpaiva-tyyppisillä muuttujilla voi olla arvo Maanantai - Sunnuntai ja Ma - Su, joita vastaavat kokonaislukuarvot ovat 0, 2, 4, 6, 8, 10 ja 12. Halutessasi voit myöskin antaa eksplisiittisesti arvon kaikille tunnisteille. Voimme esimerkiksi määritellä luetellun tietotyypin: 94