Kirjoittaja: Metabolix (2009).
Tietokone ei ymmärrä käsittelemänsä tiedon merkitystä. Se osaa vain tehdä määrättyjä, yksinkertaisia laskutoimituksia binaarimuodossa – ykkösinä ja nollina – esitetylle datalle 1). Onneksi kääntäjään on kuitenkin ohjelmoitu paljon valmiita toimituksia erilaisille tietotyypeille kuten erilaisille kokonaisluvuille ja liukuluvuille. Jotta tämä kaikki toimisi, jokaisella vakiolla ja muuttujalla tai yleensäkin arvolla on C++-kielessä tarkkaan määrätty tyyppi, jonka perusteella kääntäjä pystyy muuttamaan esimerkiksi kertolaskun sopivanlaisiksi konekielen käskyiksi.
C++:n tietotyyppejä ovat erilaiset kokonaisluvut ja desimaalilukuja vastaavat liukuluvut. Näistä voidaan muodostaa taulukoita ja omia tietotyyppejä, joista kerrotaan opassarjan 4. osassa.
Tässä oppaassa on paljon pikkutarkkaa tietoa. Kaikkia taulukoita ei tarvitse osata ulkoa, mutta tietotyyppien suuruusjärjestys ja niihin tallennettavan tiedon laatu on syytä tuntea. Nippelitiedon pariin voi palata myöhemmin, kun rajat tulevat omissa ohjelmissa vastaan.
Yksi yleisimmistä muuttujatyypeistä on nimeltään int
(engl. integer, kokonaisluku). Muut tavalliset kokonaislukutyypit (engl. integral types tai integer types) pienimmästä suurimpaan ovat char
, short
ja long
. Kaikkien näiden tyyppien eteen voi lisätä määreen unsigned
, etumerkitön, jolloin ei voi käyttää negatiivisia lukuja mutta lukualue positiivisella puolella kaksinkertaistuu 2). Nimensä mukaisesti char
-tyyppiä käytetään yksittäisten merkkien tallentamiseen. Merkki tallennetaan muuttujaan numerokoodina, yleensä ASCII-arvona, mutta koodiin sen voi kirjoittaa yksinkertaisissa lainausmerkeissä ('a'
).
Eri tyypit voivat erilaisissa järjestelmissä olla eri kokoisia. "Tavallisella" 32-bittisellä PC:llä ja nykyisillä käyttöjärjestelmillä ja kääntäjillä pätevät seuraavan taulukon tiedot.
nimi | alaraja | yläraja | koko bitteinä | koko tavuina |
---|---|---|---|---|
char | -27 = -128 | 27 - 1 = 127 | 8 | 1 |
unsigned char | 0 | 28 - 1 = 255 | 8 | 1 |
short | -215 = -32768 | 215 - 1 = 32767 | 16 | 2 |
unsigned short | 0 | 216 - 1 = 65535 | 16 | 2 |
long int | -231 = -2147483648 | 231 - 1 = 2147483647 | 32 | 4 |
unsigned long unsigned int | 0 | 232 - 1 = 4294967195 | 32 | 4 |
Taulukosta voidaan todeta, että int
ja long
ovat saman kokoiset. Uudemmissa, 64-bittisissä järjestelmissä long
-tyyppi voi olla vielä isompi:
nimi | alaraja | yläraja | koko bitteinä | koko tavuina |
---|---|---|---|---|
long | -263 = -9223372036854775808 | 263 - 1 = 9223372036854775807 | 64 | 8 |
unsigned long | 0 | 264 - 1 = 18446744073709551615 | 64 | 8 |
Toisinaan voi selkeyden vuoksi käyttää myös määrettä signed
(etumerkillinen), mutta tämä ei vaikuta mihinkään. Lisäksi short
- ja long
-tyyppien kanssa voi käyttää sanaa int
. Tyyppi int short signed
on siis sama asia kuin short
, ja sanat voivat olla missä järjestyksessä tahansa.
Totuusarvot ovat kokonaislukujen erikoistapaus. Ne tallennetaan tyyppiin bool
, jolla on vain kaksi mahdollista arvoa: true
(tosi) ja false
(epätosi). Teknisistä syistä tallennustilaa käytetään kuitenkin kokonainen tavu, vaikka vain yksi bitti on tarpeen. Totuusarvot ovat tärkeitä seuraavan oppaan ehdoissa.
Liukuluvut (engl. floating-point numbers) ovat C++:n vastine arkimaailman desimaaliluvuille. Liukulukutyypit ovat pienimmästä suurimpaan float
, double
ja long double
, ja yleisimmin näistä käytetään kahta ensimmäistä. Teknisesti liukuluvut tallennetaan tavallisesti kolmessa osassa: etumerkkinä (m, +1 tai -1), eksponenttina (e) ja kertoimena (k). Todellinen luku saadaan näistä kaavalla m·k·2e. Ohjelmoijan ei kuitenkaan tarvitse yleensä vaivata päätään tällä.
Koska tallentaminen tehdään binaarimuodossa ja määrättyyn tilaan, lukujen tarkkuudella ja lukualueella on luonnollisesti rajoituksia. Eri tyyppien rajoitukset ovat yleensä suunnilleen seuraavat:
nimi | pienin arvo (x > 0) | suurin arvo | merkitseviä numeroita | koko tavuina | bittimäärät (m, e, k) |
---|---|---|---|---|---|
float | 1,5 * 10-45 | 3,4 * 1038 | 7-8 | 4 | 1, 8, 23 (22) |
double | 5,0 * 10-324 | 1,7 * 10308 | 16-17 | 8 | 1, 11, 52 (53) |
long double | 3,6 * 10-4951 | 5,9 * 104931 | 18-19 | 12 (käytössä 10) | 1, 15, 64 |
Liukuluvun arvo voi olla myös +0, -0, ∞ (+INF), -∞ (-INF) tai NaN (Not a Number, ei luku). Esimerkiksi nollalla jakaminen tuottaa äärettömän, ja jos nolla jaetaan nollalla, tulee arvoksi NaN.
Rajallisen tarkkuuden vuoksi liukuluvuilla laskemiseen liittyy pyöristysvirheitä, minkä vuoksi niitä ei pidä noin vain käyttää erityisen tarkkoihin asioihin kuten rahasummien käsittelyyn.
Tavalliset int-tyyppiset kokonaisluvut kirjoitetaan tuttuun tapaan: 0, 1, 2, +345, -678. Liukuluvut ovat double-tyyppisiä, ja desimaalierotin on piste: 3.14, +23.439, -1.0.
Joskus luvusta täytyy saada tietyn tyyppinen: esimerkiksi suuret kokonaisluvut eivät sovi int-tietotyyppiin vaan vaativat long-tietotyypin. Tällöin luvun tyyppi ilmoitetaan luvun perässä olevilla kirjaimilla.
tyyppi | pääte | esimerkki |
---|---|---|
int | 10 | |
long | l (L) | 10l |
unsigned | u (U) | 10u |
unsigned long | ul (UL, lu, LU) | 10ul |
float | f (F) | 1.3f (ei kuitenkaan 1f) |
double | 1.3 | |
long double | l (L) | 1.3l |
Yleensä luvut kirjoitetaan kymmenjärjestelmässä. Kokonaisluvut on kuitenkin mahdollista kirjoittaa myös oktaali- tai heksadesimaalimuodoissa eli kahdeksan- ja kuusitoistakantaisissa lukujärjestelmissä. Oktaaliluku alkaa nollasta, eli esimerkiksi 0237 on oktaaliluku ja vastaa kymmenjärjestelmän lukua 159. Heksadesimaaliluvut sen sijaan alkavat merkinnästä 0x ja sisältävät myös kirjaimia a-f tai A-F. Esimerkiksi luku 0xAEafD on kelvollinen heksadesimaaliluku. Näiden merkintätapojen suurin etu on, että yksittäiset numeromerkit osuvat paremmin yksiin bittien kanssa: yksi oktaalinumero on tasan kolmen bitin ja yksi heksadesimaalinumero neljän bitin levyinen, joten esimerkiksi 0x112 vastaa binaarilukua 100010010 (0001 0001 0010).
Liukulukuihin liittyy myös eräs vaihtoehtoinen merkintätapa, nimittäin kymmenpotenssimuoto. Tämän merkitsemiseen käytetään luvun lopussa e-kirjainta, jota seuraa vielä kymmenen eksponentti. Esimerkiksi 1.3e-5 tarkoittaa lukua 1,3·10-5 eli 0,000013. Tämä merkintätapa selkeyttää hyvin suuria tai pieniä lukuja.
Merkit eli char-tyyppiset arvot kirjoitetaan yksinkertaisiin lainausmerkkeihin. Esimerkiksi 'a'
on kelvollinen merkki, ja sen lukuarvo on 97. Kirjoitettu merkki ja lukuarvo ovat muuten keskenään vaihdannaiset, paitsi merkin tietotyyppi on char ja lukuarvon tietotyyppi on int.
Pidemmät tekstit kirjoitetaan lainausmerkkeihin ("hei"
), kuten jo ensimmäisessä C++-ohjelmassa nähtiin. Tekstien käyttö muuhun kuin tulostamiseen käsitellään kuitenkin vasta myöhemmässä oppaassa.
Muuttujat ovat tapa säilyttää tietoa. Muuttujalla on aina jokin tietotyyppi ja nimi. Lisäksi muuttuja sisältää aina jonkin arvon.
Muuttujat täytyy määritellä ennen käyttöä. Määrittely alkaa tietotyypistä, esimerkiksi int
. Tämän jälkeen tulee muuttujan nimi, vaikkapa luku
. Määrittely päättyy puolipisteeseen. Kokonaisuus voisi siis näyttää tältä:
int luku;
Tämän jälkeen muuttuja on valmis käytettäväksi. Siihen voidaan sijoittaa jokin arvo yhtäsuuruusmerkillä. Alkuarvon voi halutessaan antaa jo määrittelyn yhteydessä. Jos arvoa ei anneta, muuttujan arvo voi olla aluksi mitä vain, mitä tietokoneen muistissa sattuu ennestään olemaan.
// Seuraavan muuttujan alkuarvo on 10. int luku = 10; // Muuttujaan voi sijoittaa uuden arvon =-operaattorilla. Tämä arvo siis korvaa // aiemman arvon. Alkuarvoa lukuun ottamatta sijoitusten täytyy tapahtua aina // funktion sisällä. (Funktioista kerrotaan lisää opassarjan 3. osassa.) luku = 15;
Seuraavassa esimerkkiohjelmassa määritellään muuttuja luku
, luetaan siihen arvo std::cin
-olion >>
-operaattorin avulla ja tulostetaan arvo takaisin käyttäjälle:
#include <iostream> int main() { int luku; std::cout << "Anna jokin kokonaisluku: " << std::endl; std::cin >> luku; std::cout << "Annoit luvun " << luku << ". Kiitos." << std::endl; }
Muuttujia voi määritellä monta kerralla. Tällöin muuttujien nimet annetaan pilkulla erotettuina tyypin jälkeen. Kullekin muuttujalle voi asettaa oman alkuarvon. Tässä on vielä muutama erilainen muuttuja alkuarvoineen:
// Merkin '1' ASCII-arvo on 49. Ne toimivat char-muuttujan kanssa samalla tavalla. char merkki = '1', merkki2 = 49; // Luvun perään voi merkitä tietotyypin, mutta se ei vaikuta muuttujan tyyppiin. unsigned long luku = 123, pitka_luku = 123456789ul; // Liukuluvuissa käytetään desimaalipistettä, ja kymmenpotenssimuodossa on e-kirjain. float liukuluku = 24.6, lampotila = -13.4f, suuri_luku = 12.4e+50; double tarkempi_liukuluku = 123.4567890; // Totuusarvo voi olla true tai false. bool totuus = true; // Tälle muuttujalle ei aseteta alkuarvoa, joten arvoksi voi tulla mitä tahansa. signed short int a;
Muuttujan tyyppi ei voi vaihtua, vaan määrittely on ehdoton. Siispä esimerkiksi liukuluvun desimaalit katoavat, kun se tallennetaan kokonaislukumuuttujaan:
int koko = 3.7; // koko = 3; desimaalit pudotetaan pois!
C++ sallii muuttujien nimeämisessä pienet ja suuret kirjaimet, numerot ja alaviivan, toisin ilmaistuna merkit A–Z, a–z, 0–9 ja _. Nimi ei kuitenkaan saa alkaa numerolla. Nämä samat nimeämissäännöt koskevat myös kaikkia muita C++-ohjelmissa esiintyviä nimiä, erityisesti siis myöhemmin käsiteltäviä funktioita ja omia tietotyyppejä.
Vakiot ovat lähes täysin samanlaisia kuin muuttujat. Niissä on vain yksi ero: niiden arvoja ei voi muuttaa enää määrittelyn jälkeen. Vakioiksi kannattaa määritellä kaikki se, minkä ei ole tarkoitus muuttua ohjelman aikana. Määrittelynsä puolesta vakio eroaa muuttujasta siinä, että määrittely alkaa sanasta const
(engl. constant, vakio). Tässä on muutamia hyödyllisiä vakioita:
const double pii = 3.14159265358979323846; const int sivuston_perustamisvuosi = 2002;
Jotta muuttujista ja vakioista olisi jotain iloa, niillä täytyy voida laskea. Luvuilla ovatkin käytössä tavalliset laskutoimitukset eli yhteen- ja vähennyslasku sekä kerto- ja jakolasku. Laskujärjestyskin toimii odotusten mukaan, eli ensin suoritetaan kerto- ja jakolaskut ja vasta sitten yhteen- ja vähennyslaskut. Järjestykseen voi vaikuttaa lisäämällä lausekkeeseen sulkuja.
const double pii = 3.14159265358979323846; double pallon_sade, pallon_ala, pallon_tilavuus; pallon_sade = 11.0; pallon_ala = 4.0 * pii * pallon_sade * pallon_sade; pallon_tilavuus = pallon_ala * pallon_sade / 3;
Kokonaisluvuilla laskettaessa jakolaskun tuloksena on vain kokonaisosa; esimerkiksi 14 / 3 = 4. Yli jäävän osan saa %-operaattorilla, 14 % 3 = 2.
const int ruudukon_leveys = 10, ruudukon_korkeus = 20; int laatikon_leveys = 3, laatikon_korkeus = 7; // Jakolaskusta selviää, montako laatikkoa ruudukkoon mahtuu. std::cout << ruudukon_leveys << 'x' << ruudukon_korkeus << "-ruudukkoon mahtuu " << ((ruudukon_leveys / laatikon_leveys) * (ruudukon_korkeus / laatikon_korkeus)) << " laatikkoa, jos laatikot ovat " << laatikon_leveys << 'x' << laatikon_korkeus << " ruudun kokoisia eikä niitä käännetä." << std::endl; // Ylimääräiset ruudut voi laskea jakojäännöksellä eli %-operaattorilla. std::cout << "Sivulle jää " << (ruudukon_leveys % laatikon_leveys) << " ja ylös tai alas " << (ruudukon_korkeus % laatikon_korkeus) << " tyhjää ruutua." << std::endl;
Lausekkeiden avulla edellisestä esimerkkiohjelmasta saadaankin paljon jännittävämpi. Nyt voidaan vaikkapa pyytää käyttäjältä ympyrän säde ja laskea siitä ympyrän pinta-ala. Luvun desimaalierottimena täytyy käyttää pistettä.
#include <iostream> int main() { const double pii = 3.14159265358979323846; float r, A; std::cout << "Anna ympyrän säde: " << std::endl; // Luetaan luku, esim. 2 tai 4.5. std::cin >> r; // Lasketaan pinta-ala ja sijoitetaan se muuttujaan A. A = (pii * r * r); std::cout << "säde = " << r << ", ala = " << A << std::endl; }
Seuraavassa taulukossa ovat C++-kielen yleisimmät laskuoperaattorit laskujärjestyksen mukaan lajiteltuina. Näistä normaalit matemaattiset operaatiot (+
, -
, *
, /
, %
), vertailut (<
, <=
, >
, >=
, ==
, !=
) ja loogiset operaatiot (&&
, ||
, !
, ^
) lienevät merkitykseltään selviä ja helppoja oppia, ja nämä ovatkin operaatioista tärkeimmät. Antti Laaksosen kirjoittamissa matematiikkaoppaissa kerrotaan lisää jakojäännöksestä (%
) ja bittioperaatioista (&
, |
, ~
, ^
, <<
, >>
) ja joistakin niiden sovelluksista. Bittioperaattorit eivät ole tässä vaiheessa tärkeitä, ja niiden esimerkitkin on poimittu suoraan todellisista ohjelmista, joihin on vielä pitkä matka.
operaattori (vaihtoehto) | merkitys | tyypit | esimerkki |
---|---|---|---|
! (not) | looginen ei | bool | !totta |
~ (compl) | binaari-ei eli komplementti | kokonaisluvut | ~r_maski |
* | kertolasku | kaikki luvut | 2.2 * 2.4 |
/ | jakolasku | kaikki luvut | 4.0 / 3.0 |
% | jakojäännös | kokonaisluvut | 4 % 3 |
+ | yhteenlasku | kaikki luvut | 1 + 2 |
- | vähennyslasku; (vastaluku) | kaikki luvut | 2 - 3 |
>> | bittisiirto oikealle | kokonaisluvut | bitit >> 3 |
<< | bittisiirto vasemmalle | kokonaisluvut | bitit << 3 |
< | pienempi kuin | kaikki luvut; tuloksena bool | 2 < 3 |
<= | pienempi tai yhtäsuuri kuin | kaikki luvut; tuloksena bool | 4 <= 4 |
> | suurempi kuin | kaikki luvut; tuloksena bool | 2 > 1 |
>= | suurempi tai yhtäsuuri kuin | kaikki luvut; tuloksena bool | 5 >= 4 |
== | yhtäsuuri kuin | kaikki luvut; tuloksena bool | 1 == 1 |
!= | erisuuri kuin | kaikki luvut; tuloksena bool | 1 != 2 |
& (bitand) | binaari-ja | kokonaisluvut | rgba_variarvo & r_maski |
^ (xor) | joko–tai eli vain toinen | kokonaisluvut, bool | tee ^ kahvi |
| (bitor) | binaari-tai | kokonaisluvut | rgba_variarvo | a_maski |
&& (and) | looginen ja | bool | totuus && toinen_totuus |
|| (or) | looginen tai | bool | valhe || totuus |
Näiden lisäksi ++x
ja x++
tarkoittavat muuttujan x arvon kasvattamista yhdellä ja --x
ja x--
vastaavasti vähentämistä. Merkinnöissä on pieni ero, joka selitetään seuraavassa koodissa. Sijoitusoperaattorista =
on myös versiot +=
, -=
, *=
, /=
ja %=
sekä &=
, |=
, ^=
, <<=
ja >>=
, jotka sijoituksen sijaan suorittavat ensin kyseisen laskutoimituksen ja sijoittavatkin vasta tuloksen muuttujaan.
int A = 0, B = 2, C = 4; // Nostetaan C:n arvoa yhdellä (4 => 5). // D = 4, koska C++ tuottaa lausekkeeseen vanhan arvon. int D = C++; // Nostetaan C:n arvoa toisella (5 => 6). // E = 6, koska ++C tuottaa lausekkeeseen korotetun arvon. int E = ++C; // Seuraavat lausekkeet toimivat samalla tavalla. A *= B + C; A = A * (B + C);
Operaattorit *
ja &
esiintyvät myöhemmin myös viittausten ja osoitinten yhteydessä.
Niille, joita esitetyn taulukon epätarkkuus haittaa, on tarjolla tarkempi taulukko C++ Reference -sivustolla.
Jos laskutoimitukseen osallistuu erityyppisiä lukuja, ne muutetaan siihen muotoon, jossa tietoa katsotaan häviävän vähemmän. Jos siis kerrotaan keskenään short
-muuttuja ja long
-muuttuja, vastauksen tyyppi on long
, koska sillä on laajempi lukualue. Vastaavasti kokonaisluvun ja liukuluvun laskutoimituksissa tuloksen tyyppi on liukuluku, koska tällöin mahdolliset desimaalit eivät häviä ja lukualuekin on laajempi.
int jako1 = 5 / 3; // 1, lasketaan kokonaisluvuilla. float jako2 = 5 / 3; // 1, vaikka tulos lopuksi muuttuukin float-tyyppiseksi. float jako3 = 5.0 / 3; // 1.666..., nyt laskussa on mukana liukuluku. int jako4 = 5.0 / 3; // 1, koska tulos (1.666...) sijoitetaan int-muuttujaan.
Joskus on tarpeen muuttaa luvun tyyppiä kesken laskulausekkeen. Merkinnät (tyyppi) muuttuja
ja tyyppi(muuttuja)
muuttavat muuttujan arvon määrättyyn tyyppiin. Itse muuttuja ei muutu miksikään, vaan kyseessä on yhtä irrallinen laskutoimitus kuin vaikkapa yhteenlasku.
int kolme = 3, viisi = 5; double kokojako = viisi / kolme; // 1 double liukujako = (double) viisi / kolme; // 5.0 / 3 == 1.666... double liukujako2 = viisi / double(kolme); // 5 / 3.0 == 1.666...
Mikä tahansa perustietotyyppiä oleva arvo voidaan muuttaa bool
-tyyppiseksi. Tällöin nolla on epätosi ja mikä tahansa muu arvo tosi. Totuusarvoksi siis tulee tieto, onko muuttuja erisuuri kuin nolla. Tätä muunnosta hyödynnetään usein ehtojen yhteydessä. Toiseen suuntaan muunnos toimii vastaavasti niin, että false
tuottaa luvun nolla ja true
luvun yksi.
Tietotyyppien kokoja voi tutkia sizeof
-operaattorilla, joka kertoo parametrinsa tietotyypin koon tavuina. Tämän oppaan taulukoiden kokotiedotkin on osittain hankittu sen avulla.
#include <iostream> int main() { int a = 100; // Parametriksi kelpaa muuttuja, std::cout << "sizeof(a) == " << sizeof(a) << std::endl; // tyyppi std::cout << "sizeof(double) == " << sizeof(double) << std::endl; // tai lauseke std::cout << "sizeof(a * 1.2) == " << sizeof(a * 1.2) << std::endl; }
Eikös se kuitenkin suomen kielessä ole binääri eikä binaari?
TsaTsaTsaa kirjoitti:
Eikös se kuitenkin suomen kielessä ole binääri eikä binaari?
Kyllä se on kumpi tahansa. Tämä ei kuitenkaan ole ehkä oppaan tärkein anti. :)
Tämä selvä. Aina sitä oppii jotain uutta.
Yksi (ehkä vähän tyhmä) kysymys.
std::cout tai std::cin
Mitä tuo std:: tarkoittaa tuossa vai onko tuo vaan sen olion nimi kokonaisuudessaan?
Oppaaseen olisi voinut lisätä, että riviä:
std::cout << "Anna ympyrän säde: " << std::endl;
ainakaan code blocks ei hyväksy, sillä ensinnäkin windowsilla ääkköset, kuten 'ä' pitää korvata asciitunnuksella, '\x84' ja toisekseen tässä tapauksessa kyseiseen asciikoodaukseen tulee mukaan 'säde' (s\x84de) sanaa edeltävät 'd' ja 'e', jotka pitää tietenkin erotella:
std::cout << "Anna ympyr\x84n s\x84""de: " << std::endl;
Kyllä Code::Blocks (ts. MinGW:n GCC-kääntäjä) hyväksyy sen, koodissa sinänsä ei ole mitään vikaa. Tulostus vain on virheellinen, koska Windowsin komentorivi käyttää eri merkistöä kuin editori.
Ääkkösillä ei ole ASCII-koodeja; ASCII käsittää vain 128 koodia. Esittämäsi koodi \x84 on cp850-merkistön mukainen; cp1252-merkistössä (jota editori luultavasti oletuksena käyttää) ä-kirjain on \xe4, ja merkin Unicode-koodi on samaten \u00e4.
Ohjelmaa ei kannata tehdä niin, että ääkköset toimivat vain yhdessä paikassa, mutta valitettavasti Windows on tässä asiassa niin takapajuinen, että käytännössä yleisesti toimivan ohjelman teko on melko vaikeaa.
Merkistöistä kerrotaan jonkin verran yleistä Python-oppaan liitteessä.
Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.