C-ohjelmointikieli vs. muut kielet. C vs. Java ("stereotyyppisesti") C-ohjelman korkean tason rakenne. Java-ohjelman korkean tason rakenne

Samankaltaiset tiedostot
C-ohjelmointikieli vs. muut kielet

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

Java-kielen perusteet

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

Tietueet. Tietueiden määrittely

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

Rakenteiset tietotyypit Moniulotteiset taulukot

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

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

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

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

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Osoitin ja viittaus C++:ssa

Moduli 4: Moniulotteiset taulukot & Bittioperaatiot

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

Osoittimet ja taulukot

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

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

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

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

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

Tietotyypit ja operaattorit

Java-kielen perusteet

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

5. HelloWorld-ohjelma 5.1

Lyhyt kertaus osoittimista

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

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

ITKP102 Ohjelmointi 1 (6 op)

ITKP102 Ohjelmointi 1 (6 op)

7. Näytölle tulostaminen 7.1

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

12. Javan toistorakenteet 12.1

11. Javan toistorakenteet 11.1

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

tietueet eri tyyppisiä tietoja saman muuttujan arvoiksi

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

Tietorakenteet ja algoritmit syksy Laskuharjoitus 1

Osoittimet ja taulukot

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

1. Omat operaatiot 1.1

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

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

6. Muuttujat ja Java 6.1

Taulukot. Jukka Harju, Jukka Juslin

7. Oliot ja viitteet 7.1

1. Esittelyt ja vakiot 1.1 Esittelyt (declarations) Ennen nimen, tunnuksen (identifier) käyttöä se on

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

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

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

6. Muuttujat ja Java 6.1

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

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

12. Javan toistorakenteet 12.1

Harjoitustyö: virtuaalikone

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 16.3

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

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 15.3

Tiedostot. Tiedostot. Tiedostot. Tiedostot. Tiedostot. Tiedostot

ITKP102 Ohjelmointi 1 (6 op)

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo

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

5. HelloWorld-ohjelma 5.1

Ohjelmointiharjoituksia Arduino-ympäristössä

Ohjausrakenteet. Valinta:

8. Näppäimistöltä lukeminen 8.1

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

13. Loogiset operaatiot 13.1

Olio-ohjelmointi Javalla

Sisällys. 15. Lohkot. Lohkot. Lohkot

Osoittimet. Mikä on osoitin?

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

Olio-ohjelmointi Syntaksikokoelma

Loppukurssin järjestelyt

11/20: Konepelti auki

Tietorakenteet. JAVA-OHJELMOINTI Osa 5: Tietorakenteita. Sisällys. Merkkijonot (String) Luokka String. Metodeja (public)

Luokka Murtoluku uudelleen. Kirjoitetaan luokka Murtoluku uudelleen niin, että murtolukujen sieventäminen on mahdollista.

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

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

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

Javan perusteita. Janne Käki

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

Muuttujat ja kontrolli. Ville Sundberg

13 Operaattoreiden ylimäärittelyjä

Ohjelmointi 1 Taulukot ja merkkijonot

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

Tietorakenteet ja algoritmit

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti

8. Näppäimistöltä lukeminen 8.1

C++11 lambdat: [](){} Matti Rintala

C-kurssi kevät Luennon sisältö

Luennon sisältö. C-kurssi kevät Tasokokeen kohta 1: Taulukon järjestäminen. Tasokokeen kohta 2. Tasokokeen kohta 2. Tasokokeen kohta 3

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

ITKP102 Ohjelmointi 1 (6 op)

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

Algoritmit 1. Demot Timo Männikkö

Algoritmit 1. Demot Timo Männikkö

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

Tietorakenteet (syksy 2013)

Transkriptio:

C-ohjelmointikieli vs. muut kielet C-kieli ohjelmointikielten "sukupuussa" (C++ -kielen kehittäjää Bjarne Stroustrupia mukaillen): Java-ohjelman korkean tason rakenne Ohjelma koostuu virtuaalikoneen hallinnoimista luokista. 1 C vs. Java ("stereotyyppisesti") Käytännön eroja (tyypillisesti): Java: kääntäjä tuottaa tavukoodia, jota ajetaan virtuaalikoneessa. Vanha mainoslause: "Write once, run everywhere". Voi ajaa kaikissa järjestelmissä, joissa on Java-virtuaalikone. Virtuaalikone koodin suoritus hieman hidasta? C: ohjelma käännetään suoraan konekielelle. Pitää kääntää eri järjestelmille erikseen. ANSI C käytännössä "Write once, compile everywhere"? Ongelmana kuitenkin itse koodin järjestelmäriippuvuus Optimoitu konekielikoodi koodin suoritus jokseenkin niin tehokasta, kuin mahdollista! Java: valtavan laaja yhdenmukaisesti standardoitu luokkakirjasto. Käytä valmiita apuluokkia, tee vähemmän itse. C: hyvin suppea standardikirjasto. Tee-se-itse (tai käytä ulkopuolisia apukirjastoja). 2 C-ohjelman korkean tason rakenne Ohjelma koostuu käännöksen loppuvaiheessa yhteen linkitettävistä käännösyksiköistä (yhtenäinen ohjelma). 3 4

C-ohjelman kääntämisen vaiheet C vs. Java: Hello World Java (tiedostossa "HelloWorld.java"): class HelloWorld public static void main(string [] args) System.out.print("Hei maailma!\n"); Kääntö: javac HelloWorld.java Suoritus virtuaalikoneella: java HelloWorld C (esim. tiedostossa "hello.c"): #include <stdio.h> int main(void) printf("hei maailma!\n"); return 0; Kääntö (ilman erillistä nimeä): gcc hello.c 5 C vs. Java ("stereotyyppisesti") Merkittäviä itse ohjelmointikielten eroja: Ominaisuuksia, joita C-kielessä ei ole (mutta Javassa on): Luokat. Viitemuuttujat. Automaattinen dynaamisen muistin vapautus. Poikkeukset. Ominaisuuksia, jotka C-kielessä on (mutta Javassa ei ole): Osoitinmuuttujat. Tarkka dynaamisen muistin hallinta (vapautus itse). Javan syntaksi pohjautuu C-kieleen paljon kooditason yhtäläisyyksiä: for, while, switch, if/else, return yms. rakenteet samankaltaisia. Osittain samanlaiset primitiivityypit: char, int, float ja double. Perusoperaattorit (asetus, vertailut, aritmetiikka, ). Sulkeiden käyttö, funktion parametrien välitys. Sinänsä "helppo" siirtyä Javasta C-kieleen tai päinvastoin? 6 C-ohjelman rakenteesta Ohjelman suoritus alkaa funktiosta main, jolla kaksi vaihtoehtoista muotoa: int main(void) /* Ei käytetä komentoriviparametreja. */ return kokonaisluku; /* Palautusarvo: ohjelman "tulos". */ int main(int argc, char *argv[])/* Komentoriviparametrit. */ return kokonaisluku; /* Palautusarvo: ohjelman "tulos". */ Tyypillisesti main palauttaa arvon 0, jos ohjelman ajo sujui ongelmitta. Main-funktion parametrit argc ja argv: argc: komentoriviparametrien lukumäärä. argv: komentoriviparametrit sisältävä merkkijonotaulukko. argv[0]: ohjelman kutsunimi. Suoritus:./a.out (Linux/OS X), a tai a.exe (Windows) 7 argv[i], i = 1argc-1: varsinaiset komentoriviparametrit. 8

C-ohjelman rakenteesta /* #include:lla voi lukea tiedostoon mukaan muita tiedostoja. Standardikirjaston tiedoston nimi annetaan kulmasulkeiden sisällä, oman tiedoston nimi heittomerkeissä. */ #include <math.h> #include "oma_koodi.h" /* Tiedoston runko on ns. globaali näkyvyysalue, joka koostuu globaaleista muuttujien ja funktioiden esittelyistä ja määrittelyistä. Funktioita voi määrittää vain globaaleina. Globaali määritys näkyy sen alapuoliseen tiedoston osaan. */ int x = 0; /* Globaali muuttuja. Saa alustaa vain vakiolla. */ /* Palauttaa double-arvon. Ottaa kaksi int-parametria. */ double funktio1(int a, int b) /* Funktioiden sisällä: lokaali/paikallinen näkyvyysalue. */ double y = sqrt(10); /* Paikallinen muuttuja. Ei näy ulos. */ return y; // Tämä on laiton kommentti: ANSI C ei salli //-kommentteja! int main(int argc, char *argv[]) /* Ohjelman suoritus alkaa main-funktion alusta. */ return kokonaisluku; 9 Muuttujan määrityksestä ANSI C: paikalliset muuttujat määritettävä oman lohkonsa alussa! Lohkojen muoto: ensin muuttujien määrittelyt, vasta sitten lauseet. Muuttujaa ei voi määritellä esim. for-silmukan otsakkeessa. for-silmukan silmukkamuuttuja(t) määritettävä ennen for-silmukkaa. void tee_jotain(int par1, int par2) int h = par1*par2; /* Määrittely. Alustuskin on osa sitä. */ int i = 0; /* OK: edellä vain määrittely. */ h = par1*par2; /* Sijoituslause. */ int j = 0; /* Laiton määrittely: lohkossa oli jo lause. */ for(int i = 0; i < h; ++i) /* Laiton muuttujan i määritys. */ for(i = 0; i < h; ++i) /* OK: i määritetty jo ylempänä. */ int x = 0; /* OK: x määritetään oman lohkonsa alussa. */ 11 Muuttujan määrityksestä Muuttujan määritys: johtaa muuttujan konkreettiseen luontiin. Tavallisen muuttujan määritys muotoa: [lisämääreitä] tyyppi nimi; [lisämääreitä] tyyppi nimi = alustusarvo; Erikoisrajoite: globaalin muuttujan saa alustaa vain vakioarvolla! Esim. muotoa int x = apu(); tai int y = x; oleva alustus on laiton. Ellei määrityksessä anneta alustusarvoa (eikä kyseessä pelkkä esittely): Globaali muuttuja: alustetaan arvolla 0. Paikallinen muuttuja: jätetään kokonaan alustamatta! Arvo satunnainen (mitä muuttujan muistikohdassa jo sattuu olemaan). Tyypillinen bugilähde pyri antamaan aina alustusarvo! void tee_jotain(int lkm) int h; /* Ei alustusarvoa: h jää alustamatta! */ while(h++ < lkm) /* h "satunnainen": silmukka suoritetaan */ /* tuntematon kertaa. Tämä lienee bugi. */ 10 Esittely, määrittely ja näkyvyys Jotta muuttujaan tai funktioon voi viitata, pitää sen määrittelyn (tai esittelyn) olla näkyvissä viittauskohdassa Muuttujien/funktioiden näkyvyys käännösyksikön sisällä: Globaalit ("funktioiden ulkopuoliset") määritykset: näkyvät esittely- tai määrittelykohdasta alkaen saman käännösyksikön loppuun asti. int j = 5; void tee_jotain(void) int h = apu(j); /* VIRHE: funktio apu ei vielä näkyvissä. */ int apu(int x) int i = j*x; /* OK: globaali j näkyvissä. */ return k + i; /* VIRHE: k ei vielä näkyvissä. */ int k = 100; 12

Esittely, määrittely ja näkyvyys Muuttujien/funktioiden näkyvyys saman käännösyksikön sisällä: Paikalliset ("koodilohkon, kuten funktion, sisäiset"): näkyvät esittely tai määrittelykohdasta alkaen saman lohkon loppuun asti. Huom! C sallii muuttujan kanssa samannimisen toisen muuttujan määrittelyn sisemmässä lohkossa! Sisempi muuttuja peittää ulomman pois näkyvistä. int i = 10; int x = i; /* OK: i = 10 näkyy sisempäänkin lohkoon. */ int i = 20; /* OK: Nyt i = 20 peittää arvon i = 10. */ x = i; /* OK: nyt x = 20, koska i = 20 näkyvissä. */ int j = k; /* VIRHE: k ei vielä näkyvissä. */ int k = i; /* OK: paikallinen i = 10 näkyvissä. */ int h = i+k; /* VIRHE: i ja k eivät näy lohkostaan ulos. */ 13 Muuttujan ja funktion linkittyvyys Muuttujan/funktion linkittyvyys: globaalin muuttujan/funktion nimen näkyvyys käännösyksikössä tai käännösyksiköiden välillä. Ulkoisesti linkittyvä: nimi näkyy ulos eli siihen voidaan viitata oman käännösyksikön ulkopuolelta. (myös muista käännösyksiköistä) Määritys/esittely ulkoisesti linkittyväksi: eteen lisämääre extern. Globaalit ovat oletusarvoisesti ulkoisesti linkittyviä. extern siis turha? Ei: extern estää esittelyn tulkitsemisen alustavaksi määrittelyksi. Toisessa käännösyksikössä määritellyn muuttujan esittely. koodi1.c int x = 99; extern int y; int apu(int par) int z = apu2(); x = z + y; koodi2.c int y = 5; extern int x; int apu2(void) int i = apu(x); 15 Esittely, määrittely ja näkyvyys Esittely: kertoo, että tietynlainen muuttuja/funktio on olemassa (eli määritetty jossain muualla). Muuttujan esittely mahdollista kahdella tavalla: 1) Globaali muuttuja: annetaan määrittely ilman alustusarvoa. Ns. alustava määrittely: muuttuu määrittelyksi, joka alustetaan arvolla 0, jos kääntäjä ei löydä myöhemmin muuta määrittelyä. 2) Globaali tai paikallinen muuttuja: määritys ilman alustusarvoa ja edessä lisämääre extern. (tästä kohta lisää) Funktion esittely: funktion otsake ilman runkoa. Parametrien nimiä ei ole pakko antaa (tyypit riittää). Parametrit voi jopa jättää pois (mutta älä jätä!). int i; /* Muuttujan i alustava määrittely. Voi saada arvon 0. */ extern int j; /* Muuttujan j esittely. Määrittely muualla. */ int apu(int); /* Esitellään funktio apu. */ int tee_jotain(int par) /* Funktion tee_jotain määrittely. */ return apu(par*j); /* Viittaukset OK: apu ja j on esitelty. */ /* Linkitys epäonnistuu, ellei apu ja j määritelty muualla. */ 14 Muuttujan ja funktion linkittyvyys Sisäisesti linkittyvä: nimeen voidaan viitata oman käännösyksikön sisällä eri funktioista. (eikä muista käännösyksiköistä) Käytännössä: globaali, joka ei näy käännösyksikön ulkopuolelle. Globaalin määritys sisäisesti linkittyväksi: eteen lisämääre static. Määreen merkitys globaalissa yhteydessä aivan eri kuin paikallisessa! koodi.c static int x = 99; static int apu(int x) int y = x*x; void apu2(void) int z = apu(); int i = x; koodi2.c extern int x; int apu3(void) int i = apu(5); 16

Muuttujan ja funktion linkittyvyys Ei linkittyvä: ei näkyvissä käännösyksikkötasolla (globaalisti). Nimeen voidaan viitata ainoastaan sen omassa määrittelylohkossa. Käytännössä: paikalliset muuttujat. gcc-kääntäjän käytöstä Lähdekooditiedostojen koodi1.c,, koodix.c kääntäminen ja linkitys ohjelmaksi: gcc koodi1.c koodi2.c koodix.c Linux / OS X: ohjelman nimeksi tulee oletuksena a.out. Windows: ohjelman nimeksi tulee oletuksena a.exe. koodi1.c int apu(int par) int x = 100; int apu2(int par) int y = x; koodi2.c extern int x; int apu3(void) int i = apu(x); Kääntäjän toimintaa voidaan ohjata valitsimilla. gcc tarjoaa paljon valitsimia. Esimerkiksi muutamia hyödyllisiä: -o nimi: Määritä käännöksen lopputuloksen nimi. Esim. gcc -o ohjelma koodi1.c koodi2.c koodi3.c -ansi: Käytä C90-standardia (ns. ANSI C). -pedantic: Sovella standardin sääntöjä tarkasti. -Wall: Tulosta varoituksia epäilyttävistä (vaikkakin periaatteessa laillisista) koodin osista. gcc-kääntäjän käytöstä 17 -Wextra: Tulosta varoituksia vieläkin laajemmin (monet turhia). -E: Ainoastaan esiprosessoi. Ei käännä koodia. -S: Käännä koodi symboliseksi konekieleksi ("assembly"). Tuottaa käännösyksiköistä assembly-kieliset tiedostot. -c: Käännä, mutta älä linkitä. Tuottaa käännösyksiköistä objektikooditiedostot. -g: Käännä ohjelma debuggausta varten. Kirjataan tietoa käännetyn ohjelman ja lähdekoodin suhteista (helpottaa virheiden hakua ns. debuggerin avulla). -On: Käännöksen optimointitaso n, arvo välillä 03. Mitä suurempi n on, sitä enemmän kääntäjä yrittää optimoida koodia. -O0: älä optimoi lainkaan, -O3: optimoi mahdollisimman paljon. Optimointi ei oletusarvoisesti päällä (hidastaa käännöstä). Käyttöön otettava ohjelma kannattaa kääntää optimoiden (-O3). 19 18 Esittelyt ja määrittelyt / toteutus- ja otsaketiedostot Tyypillinen jako: Käännösyksikön "julkiset" eli muidenkin yksiköiden käyttöön tarjoamat osat esitellään ns. otsaketiedostossa. Otsaketiedoston rooli: kyseisen käännösyksikön "rajapinta". Vallitseva tapa: otsaketiedoston tiedostopääte on ".h". Toteutukset varsinaisessa toteutustiedostossa. Vallitseva tapa: toteutustiedoston tiedostopääte on ".c". Jos esim. otsaketiedosto koodi.h ja toteutustiedosto koodi.c, niin: Tiedoston koodi.c tulisi sisällyttää myös itseensä otsake koodi.h. Eli käytännössä: sisältää rivi #include "koodi.h". Hyöty: kääntäjä voi havaita, jos toteutus poikkeaa rajapinnasta. Tiedoston koodi.c tarjoamia osia käyttävät käännösyksiköt: Sisällyttävät itseensä otsaketiedoston koodi.h. Käännetään/linkitetään yhdessä toteutustiedoston koodi.c kanssa. 20

Esittelyt ja määrittelyt / toteutus- ja otsaketiedostot Esimerkiksi Hello World -koodin voisi jakaa vaikkapa seuraavasti: Toteutustiedosto hello.c: viestin tulostava funktio heimaailma. #include <stdio.h> #include "hello.h" /* Sisällytetään tiedoston oma otsake. */ void heimaailma(void) printf("hei maailma!\n"); Otsaketiedosto hello.h: "rajapinta" eli funktion heimaailma esittely. void heimaailma(void); Lisäksi suoritettava ohjelma tarvitsee main-funktion. Tämä voisi olla vaikkapa tiedostossa main.c. Kääntö tapaan gcc hello.c main.c. #include "hello.h" /* Sisällytetään heimaailma:n otsake. */ int main(void) heimaailma(); /* Kutsuu hello.c:n funktiota heimaailma. */ return 0; /* Ohjelma loppui ok, palautetaan arvo 0. */ 21 C-kielen perustyypit: etumerkittömät kokonaisluvut C-kielessä on lisäksi myös etumerkittömät kokonaislukutyypit! Tallettavat vain ei-negatiivisia kokonaislukuja. Määritys: annetaan kokonaislukutyypille lisämääre unsigned. C-standardi takaa unsigned-tyypeille vähintään seuraavat lukuvälit: unsigned char: 0 2 8 1 = 0 255 unsigned short int: 0 2 16 1 = 0 65535 unsigned int: 0 2 16 1 = 0 65535 unsigned long int: 0 2 32 1 = 0 4294967295 Myös unsigned-tyypeillä on samankaltaiset vaihtoehtoiset nimet. Ei kuitenkaan voi olla samanaikaisesti sekä signed että unsigned. Esim.: unsigned short int == unsigned short Lisäksi pätee: unsigned int == unsigned 23 C-kielen perustyypit: kokonaisluvut Neljä perustyyppiä, jotka lueteltu alla tyyppiin mahtuvien arvojen suuruusjärjestyksessä: signed char short int int long int Koot voivat vaihdella järjestelmästä riippuen! C-standardi kuitenkin takaa vähintään seuraavat lukuvälit: signed char: -(2 7-1) 2 7 1 = -127 127 short int: -(2 15-1) 2 15 1 = -32767 32767 int: -(2 15-1) 2 15 1 = -32767 32767 long int: -(2 31-1) 2 31 1 = -2147483647 2147483647 Kolme viimeistä tyyppiä on mahdollista ilmaista usealla eri tavalla: short int == short == signed short == signed short int int == signed == signed int long int == long == signed long == signed long int int on C-kielen totuusarvotyyppi: 0 on epätosi ja!= 0 on tosi. Kolme liukulukutyyppiä: C-kielen perustyypit: liukuluvut 22 Suuruus/tarkkuusjärjestyksessä: float, double ja long double Myös liukulukutyyppien ominaisuudet järjestelmäriippuvaisia. C-standardi takaa vähintään seuraavat suuruusluokat ja tarkkuudet: float: Arvoalue vähintään -10 37 10 37. Ainakin 6 merkitsevän numeron tarkkuus. double: Arvoalue vähintään -10 37 10 37. Ainakin 10 merkitsevän numeron tarkkuus. long double: Arvoalue vähintään -10 37 10 37. Ainakin 10 merkitsevän numeron tarkkuus. 24

Tyyppien järjestelmäkohtaiset ominaisuudet Otsaketiedosto <limits.h> tarjoaa tietoa järjestelmäkohtaisista rajoista: Kohdejärjestelmän kokonaislukutyyppien arvojen ylärajat: char: SCHAR_MAX, UCHAR_MAX, CHAR_MAX short: SHRT_MAX, USHRT_MAX int: INT_MAX, UINT_MAX long: LONG_MAX, ULONG_MAX Tyyppien alarajat: samat kuin edellä, mutta korvaa "MAX" "MIN". Alarajoja ei ole annettu unsigned-tyypeille. Tunnetaaan muutenkin: aina 0. Otsaketiedosto <float.h> tarjoaa vastaavat tiedot liukulukutyypeille float, double ja long double: Ylärajat: FLT_MAX, DBL_MAX ja LDBL_MAX Alarajat: FLT_MIN, DBL_MIN ja LDBL_MIN 25 C-kielen taulukot Taulukon arvojen alustuminen määrityksen yhteydessä? Samat säännöt kuin yksittäisellä muuttujalla: Globaali taulukko: arvot alustuvat nollaksi. Paikallinen taulukko: arvoja ei alusteta (ovat mitä muistissa jo oli)! Taulukon määrityksen yhteydessä mahdollista antaa alustusarvoja: Annetaan aaltosuluissa, pilkuilla eroteltuina. Jos ei anna kaikkia arvoja: puuttuvat alustetaan arvolla 0. Pari esimerkkiä: int taulu[5] = 2, 7, 3, 5, 4; on taulukko, jossa esim. taulu[1] == 7. int taulu[5] = 1; on sama kuin int taulu[5] = 1, 0, 0, 0, 0; Alustusarvoja käytettäessä taulukon koon saa jättää pois: Kääntäjä päättelee: taulukon koko = alustusarvojen lukumäärä. int taulu[] = 4, 2, 9; on sama kuin int taulu[3] = 4, 2, 9; 27 C-kielen taulukot n-alkioisen taulukon määritys: muuten samoin kuin yksittäinen muuttuja, mutta muuttujan nimen perään taulukon koko hakasulkeissa: Määrityksen perusmuoto on tyyppi nimi[n];. Esim. 10-alkioinen int-taulu itaulu määritetään tapaan int itaulu[10]; Taulukon koon oltava käännösaikainen vakio! Esim. int itaulu[x]; on laiton määritys, jos x on muuttuja. Toinen tärkeä ero verrattuna Javaan: määritys johtaa samalla taulukon luontiin (sen alkiot ovat olemassa määrityksen jälkeen): int taulu[5]; /* Määritetään 5-alkioinen int-taulukko. */ int i = 0; int lkmt[uchar_max+1]; /* OK: UCHAR_MAX jne. ovat vakioita! */ for(i = 0; i < 5; ++i) taulu[i] = i*i; /* Asetus on OK: taulukko on jo luotu. */ lkmt[(unsigned char) 'a'] = 5; /* OK: mikä tahansa char-arvo */ /* etumerkittömänä on välillä 0UCHAR_MAX eli ei ylivuotoa */ 26 C-kielen taulukot Huom.! C-kielen taulukko ei tiedä kokoaan. Eräs asiaa sivuava tyypillinen tapaus: taulukko funktion parametrina. Taulukkoparametri voidaan määrittää ilman kokomääritystä. Jos tarvitaan koko: annetaan se erikseen esim. parametrina. int suurin(int koko, int taulu[])/* Kävisi myös ns. osoitin, */ int i = 0; /* muodossa int *taulu. */ int suurin = 0; for(i = 0; i < koko; ++i) suurin = (i == 0 taulu[i] > suurin)? taulu[i] : suurin; return suurin; int taulu[6] = 6, 2, 7, 4, 9, 2; void tee_jotain(void) /* printf korvaa määreen %d parametrin s arvolla (sivu 35). */ int s = suurin(6, taulu); /* Asettaa s = 9. */ printf("suurin arvo: %d", s); /* Tulostaa "Suurin arvo: 9". */ 28

C-kielen taulukot C-kielessä ei turvamekanismia taulukon indeksin laillisuuden suhteen. Laiton indeksi? Ohjelma mahdollisesti kaatuu. Riippuu tilanteesta. Tyypillinen virhe: mennään taulukon lopusta yli. (ns. "ylivuoto") void tee_jotain(void) int t[6] = 0, 1, 2, 3, 4, 5; int i = 0; for(i = 0; i <= 6; ++i) t[i] = t[i] + 2; /* Taulukon ylivuoto, kun i = 6. */ Edellä yritetään lukea ja kirjoittaa laittomaan kohtaan t[6]. Ohjelma voi kaatua, jos käyttöjärjestelmä havaitsee laittoman muistiosoituksen. Jos ohjelma ei kaadu: manipuloidaan vahingossa jotain väärää arvoa. Esim. muuttuja i voisi olla talletettuna heti taulukon t perässä! 29 C-kielen merkkijonot C-kielen merkkijono char-taulukko. Määritelmä: merkkijonon merkit kuvaava char-taulukko, jonka lopussa lisäksi ylimääräinen lopetusmerkki '\0'. Lopetusmerkki '\0': char-tyyppiä oleva kokonaislukuarvo 0. Tyypillinen bugi: unohdetaan merkkijonon lopetusmerkki! Esimerkiksi merkkijonon eli char-taulukon mjono voi määrittää ja alustaa merkkijonoksi "kevät" mm. seuraavilla tavoilla: char mjono[6] = 'k', 'e', 'v', 'ä', 't', '\0'; char mjono[6] = 'k', 'e', 'v', 'ä', 't', 0; char mjono[] = 'k', 'e', 'v', 'ä', 't', '\0'; char mjono[] = "kevät"; Huom! Myös alustus char mjono[5] = "kevät"; on sinänsä laillinen, mutta se vastaa alustusta char mjono[5] = 'k', 'e', 'v', 'ä', 't'; C-kielen merkkijonot C-merkkijono on taulukko ei varsinaisesti tiedä omaa pituuttaan. Mutta: voidaan laskea tutkimalla, milloin kohdataan lopetusmerkki '\0': int main(int argc, char *argv[]) int pit = 0; while(argv[0][pit]!= '\0') /* Oliko loppumerkki? */ ++pit; /* Ei ollut: kasvatetaan pituutta. */ printf("ohjelman kutsunimen pituus oli %d.\n", pit); return 0; Toinen esimerkki: erillinen funktio pituuden laskuun. int pituus(char *mjono) /* Kävisi myös char mjono[]. */ int pit = -1; /* Kikkailun makua kalvotilan */ while(mjono[++pit]!= '\0')/* säästämiseksi. Tämä toimii */ ; /* Käytännössä kuten ylempi. */ return pit; 31 Lopetusmerkki puuttuu tuloksena ei ole laillinen C-merkkijono! 30 C-kielen merkkijonot Tyypillinen virhe: unohdetaan lopetusmerkki. int pituus(char *mjono) /* Edellisellä sivulla ollut */ /* funktio, joka laskee */ int pit = -1; /* merkkijonon pituuden. */ while(mjono[++pit]!= '\0') ; return pit; void tee_jotain(void) char mj[] = "virhe?"; /* Luodaan merkkijono "virhe?". */ char kopio[100]; /* char-taulu, johon kopioidaan. */ int i = 0; int p = pituus(mj); /* Merkkijonon "virhe?" pituus = 6. */ for(i = 0; i < p; ++i) /* Silmukka kopioi 6 merkkiä eli */ /* lopetusmerkki jää pois! */ kopio[i] = mj[i]; p = pituus(kopio); /* Funktio pituus voi toimia väärin: */ /* se ei löydäkään lopetusmerkkiä? */ 32

printf: monipuolinen tulostusfunktio C-kielen paljon käytetty tulostusfunktio: int printf(const char *mj, ) Parametri mj: tulostettava merkkijono. printf lupaa olla muuttamatta sitä. (määreestä const sivulla 66) Palautusarvo: tulostettujen merkkien lukumäärä. Funktion printf erityisvahvuus: tulostettavan merkkijonon mj sisään voi näppärästi sisällyttää tulostettavia arvoja. Asetetaan merkkijonon mj sisään, arvojen haluttuihin tulostuskohtiin, muotoa %määre olevat tulostusmääreet. Varsinaiset arvot annetaan funktiolle printf parametreina merkkijonon mj jälkeen. (samassa järjestyksessä kuin määreet sisällytettiin) Usein käytettyjä tulostusmääreitä: Merkin tulostus: c printf("kirjain %c", 'k') tulostaa kirjain k (<stdio.h>) printf: monipuolinen tulostusfunktio int-arvon tulostus: d tai i Jos i = 100, niin printf("luku %d", i) tulostaa luku 100 printf("%d.%i.%d", 18, 3, 2013) tulostaa 18.3.2013 unsigned int-arvon tulostus: u printf("pituus %u", 180) tulostaa pituus 180 double-arvon tulostus: f printf("1/3 = %f", 1.0/3) tulostaa 1/3 = 0.333333 Merkkijonon tulostus: s Jos merkkijono mj on "maalis", printf("%skuu", mj) tulostaa maaliskuu Erikoistapaus: merkin '%' tulostus? Se annetaan muodossa "%%": printf("20%%") tulostaa 20% (<stdio.h>) printf("väl%cin", 'i') tulostaa väliin 33 printf: monipuolinen tulostusfunktio Tulostusmääreiden eteen voi antaa lisämääreitä. Säätävät (lähinnä) tulostuksen muotoilua. Tulostusmääritys voi yleisemmin ottaen olla muotoa %[valitsimia][leveys][.tarkkuus][koko]t T on tyypin ilmaiseva määre (esim. c, i, d, f tai s). Kukin edellä hakasulkeilla merkitty osa on valinnainen. Leveys: tulostettavien merkkien vähimmäismäärä. Jos arvo vie vähemmän merkkejä, lisätään eteen tyhjää. printf("luku %10d", i) tulostaa luku 100 Leveyden tilalla voi antaa '*', jolloin leveys luetaankin parametrista: printf("luku %*d", 10, i) tulostaa luku 100 (<stdio.h>) printf: monipuolinen tulostusfunktio 34 Tarkkuuden määritys annetaan muodossa.d, missä D kokonaisluku. Tulkinta riippuu tyypistä: double: tulostus D desimaalin tarkkuudella. printf("pii: %.3f", 3.14159) tulostaa pii: 3.142 int: tulostetaan vähintään D numeroa. Eteen etunollia, jos arvossa alle D numeroa. printf("james Bond %.3d", 7) tulostaa James Bond 007 Merkkijono: tulostetaan korkeintaan D merkkiä. printf("%.5s", "Hannu Hanhi") tulostaa Hannu Myös tarkkuuden D:n tilalla voi antaa '*', jolloin tarkkuus luetaankin erillisestä parametrista: printf("pii: %*.*f", 6, 2, 3.1415) tulostaa Pii: 3.14 (<stdio.h>) 35 36

printf: monipuolinen tulostusfunktio Koko: lukutyypin koon tai muunnoksen ilmaiseva lisämääre. h: tyyppimuunna saatu int-parametri short-arvoksi. Huom! printf oikeasti saa char- ja short-parametrit int-arvoina! Ns. "oletusmuunnos": int on kokonaisluvun perustyyppi. l: parametrin kokonaislukutyyppi on long. Pakko käyttää, jos parametri oikeasti on long! Miten niin pakko? Koska muuten ohjelma voi kaatua! L: parametrin liukulukutyyppi on long double. Pakko käyttää, jos parametri oikeasti on long double! Koon määrittävä lisämääre asetetaan tyyppimääreen eteen: Esimerkiksi long double -arvon x tulostus 20 merkin levyisenä ja 4 desimaalin tarkkuudella: printf("%20.4lf", x) (<stdio.h>) 37 Muuttujan elinikä, tyypin lisämääre static Globaalin muuttujan elinikä on aina "staattinen". Luodaan ja alustetaan ennen main-funktion suoritusta. Tuhotaan, kun ohjelman suoritus päättyy. Alustus sallittu ainoastaan käännösaikaisella vakioarvolla! Paikallisen muuttujan eliniän voi määrittää lisämääreellä auto tai static: auto: muuttujan elinikä on "automaattinen". Luodaan, kun ohjelman suoritus saapuu määrityksen kohdalle. Tuhotaan, kun ohjelman suoritus poistuu muuttujan lohkosta. Turha määre: paikalliset ovat joka tapauksessa oletusarvoisesti auto. static: muuttujan elinikä on "staattinen". Luodaan ja alustetaan ennen main-funktion suoritusta. Tuhotaan, kun ohjelman suoritus päättyy. Alustus ainoastaan käännöksenaikaisella vakioarvolla. Käytännössä: paikallinen muuttuja, joka säilyttää arvonsa oman lohkonsa eri suorituskertojenkin välillä. 39 C-kielen perustyypit: vakioarvon tyyppi Tavallisen kokonaislukuvakion, kuten esim. -13 tai 167242, tyypiksi määrittyy int, jos int kykenee esittämään arvon, ja muuten long int. Kokonaislukuvakion tyypin määräytymiseen voi vaikuttaa lisäämällä sen perään kirjaimen U tai/ja L. Kummastakin käy myös pieni versio u tai l. U tai u: kokonaislukuvakion tyyppi on unsigned int. L tai l: kokonaislukuvakion tyyppi on long. U tai u ja L tai l: kokonaislukuvakion tyyppi on unsigned long. Liukulukuvakion, kuten 3.14, oletusarvoinen tyyppi on aina double. Liukulukuvakion tyypin määräytymiseen voi vaikuttaa lisäämällä sen perään kirjaimen F tai L (tai f tai l). F tai f: liukulukuvakion tyyppi on float. L tai l: liukulukuvakion tyyppi on long double. printf("%lu", 10); /* Väärä määre %lu: 10 on int-arvo. */ printf("%lu", 10UL); /* Ok: 10UL on unsigned long -arvo 10. */ printf("%lf", 3.141592654L); /* Ok: 3.141592654L long double. */ printf("%ld", -3000000000L); /* Ok: -3000000000L on long int. */ 38 Muuttujan elinikä, tyypin lisämääre static int x = 100; /* x alustuu ennen mainin alkua. */ double y = sqrt(x); /* Virhe: globaali alustettava vakiolla! */ void tee_jotain(void) static int x = 0; /* x alustuu kerran: ennen mainin alkua. */ int y = 0; /* y alustuu aina kun funktio suoritetaan. */ x += 1; /* Kasvatetaan arvoa x. */ y += 1; /* Kasvatetaan arvoa y. */ printf("%d ja %d\n", x, y); /* Tulostetaan x ja y. */ int main(int argc, char *argv[]) int i = 0; for(i = 0; i < x; ++i) /* Ok, x on jo alustunut ja on 100. */ /* Funktio tee_jotain tulostaa ruudulle "1 ja 1", */ /* "2 ja 1", "3 ja 1", "4 ja 1",, "99 ja 1". Eli x */ /* ylläpitää oman arvonsa mutta y alustuu aina arvoon 0. */ tee_jotain(); return 0; 40

Tyyppialias / typedef-määrittely C-kielessä voi määrittää tyypeille aliaksia eli vaihtoehtoisia nimiä. Määritys ei luo uutta tyyppiä: ainoastaan vaihtoehtoisen tavan viitata johonkin olemassaolevaan tyyppiin. Määrityksen muoto: typedef tyyppi nimi; tyyppi voi koostua useasta sanasta. nimi on yksittäinen sana. Helppo(?) sääntö: luo halutuntyyppisen muuttujan nimi esittely ja aseta eteen avainsana typedef. typedef-määrittelyn näkyvyys: samat säännöt kuin muuttujilla. void tee_jotain(unsigned int x) /* Määritetään unsigned int -tyypille alias "etumerkiton". */ typedef unsigned int etumerkiton; etumerkiton y = x*x; /* y:n tyyppi on unsigned int. */ etumerkiton z = 0; /* Virhe: alias "etumerkiton" ei näy. */ 41 Bitit, tavut, muuttujan (tyypin) koko Digitaalisten tietokoneiden pienin perusyksikkö: 1 bitti Muistinkäsittelyn perusyksikkö: 1 tavu Muuttuja(tyypin) koko: kuinka monta tavua sen esitys vie muistissa. C-kielessä on erillinen operaattori muuttujan koon tutkimiseen: sizeof sizeof(tyyppi): tyypin tyyppi koko tavuina. Esim. sizeof(int) kertoo int-tyypin koon tavuina. sizeof x: muuttujan tai arvon x koko tavuina. Muuttujan/arvon koko == muuttujan/arvon tallettavan tyypin koko. Sulkeet sallittu: sizeof x ja sizeof(x): ilmaisevat samaa. (jos x ei tyyppi) C-standardi: Määre unsigned/signed ei vaikuta muuttujatyypin kokoon. char-tyypin koko on 1 tavu. Tyyppialias / typedef-määrittely C-standardikirjasto itsessäänkin määrittää useita tyyppialiaksia. Yksi hyvin paljon käytetty tyyppialias: size_t size_t on määritelty olemaan sizeof-operaattorin palautusarvon tyyppi. Käytännössä: jokin etumerkitön kokonaislukutyyppi. Lienee tyypillisesti (muttei välttämättä aina) unsigned long. Standardikirjastossa olisi määritys typedef unsigned long size_t; Tyyppiä size_t käytetään standardikirjastossa yleensäkin kokoa ilmaisevana etumerkittömänä tyyppinä. Esim. otsaketiedosto string.h tarjoaa merkkijonon pituuden laskuun muotoa size_t strlen(const char *mj) olevan funktion. void tee_jotain(char *mj) size_t pituus = strlen(mj); /* Lasketaan mj:n pituus. */ size_t i = 0; /* Muuttujaa i käytetään alla silmukassa. */ for(i = 0; i < pituus; ++i) /* Käydään mj:n merkit läpi. */ if(mj[i] == /* Esim. tutkitaan merkkiä mj[i]. */ 42 C-standardi: Bitit, tavut, muuttujan (tyypin) koko Ei määritä tarkkaan seuraavia asioita: Kuinka suuria muut kuin char-tyypit ovat (tavuina). Kuinka suuri char loppujen lopuksi on (bitteinä). char on yksi tavu, mutta kuinka suuri on yksi tavu? Määrittää kuitenkin: Tavun bittien lukumäärä vähintään 8. Tavu voi esittää vähintään 2 8 = 256 eri arvoa. Järjestelmän tavun koon selvitys? Tieto löytyy suoraan otsaketiedostosta <limits.h>: Vakio CHAR_BIT: tavun bittien lukumäärä. Tätä hyödyntäen: esim. int-arvossa on CHAR_BIT*sizeof(int) bittiä. sizeof(char) == sizeof(signed char) == sizeof(unsigned char) == 143 44

Kokonaislukujen bittiesitys Etumerkittömien kokonaislukujen esitys: tavallisena binäärilukuna. Esim. 8 bitistä koostuva unsigned char muuttuja tallettaa arvon 175 bittijonona 10101111: 175 = 128 + 32 + 8 + 4 + 2 + 1 = 27 + 2 5 + 2 3 + 2 2 + 2 1 + 2 0. Etumerkillisten lukujen esitys: tavallinen "arvobiteistä" koostuva binääriluku, jonka ohessa lisäksi etumerkkibitti. Etumerkkibitti = 1 luku on negatiivinen. C-standardin mukaan: Positiivisten lukujen arvobitit esitetään keskenään samalla tavalla niin etumerkillisissä kuin etumerkittömissäkin luvuissa. (= tav. binääriluku) Käytännössä esim. muuttujien signed char x = 105 ja unsigned char y = 105 bittiesitykset ovat identtisiä. Etumerkkibitin tulkinta voidaan tehdä kolmella eri tavalla: 1:n komplementti, 2:n komplementti tai etumerkki. Nykyiset järjestelmät tavallisesti: 2:n komplementti. 45 Liukuluvun tyypillinen esitys: IEEE 754 -standardi Tutkitaan esimerkkinä arvoa -0.875, jonka 32-bittinen IEEE 754 - liukulukuesitys on 1 01111110 11000000000000000000000. IEEE 754 liukuluvun esittävä bittijono koostuu, oikealta vasemmalle, kolmesta osasta: 1. Arvo-osa ("mantissa"): Arvo-osa on muotoa 1.x oleva desimaaliluku. Arvo-osan kuvaavat arvobitit kuvaavat ainoastaan desimaaliosan x. 32-bittisellä liukuluvulla on 23 arvobittiä, 64-bittisellä 52. Vasemmalta oikealle, indeksin i bitin painoarvo on 1/2 i+1. Esimerkissä arvo-osan bittijono 11000 vastaa desimaaliosaa 1/2 1 + 1/2 2 = 0.5 + 0.25 = 0.75. Etumerkillinen kokonaisluku: 2:n komplementti n-bittinen 2:n komplementtiesitys: Viimeiset (oikeanpuoleisimmat, vähiten merkitsevät) n-1 bittiä tulkitaan tavallisena binäärilukuna. Oikealta laskien indeksin i omaavan bitin arvo on 2 i. Ensimmäisen (vasemmanpuoleisin, merkitsevin) eli indeksin n-1 omaavan bitin arvo tulkitaan negatiiviseksi: Arvo ei ole 2 n-1 vaan -2 n-1. Esimerkki: 8-bittisen signed char arvon 10010101 tulkinta. -2 7 + 2 4 + 2 2 + 2 0 = -128 + 16 + 4 + 1 = -107. Jos täysin sama bittijono 10010101 tulkittaisiinkin unsigned chararvoksi? 2 7 + 2 4 + 2 2 + 2 0 = 128 + 16 + 4 + 1 = 149 Pienin ja suurin 8-bittinen signed char-arvo? Pienin 10000000 = -128, suurin 01111111 = 127. 46 Liukuluvun tyypillinen esitys: IEEE 754 -standardi -0.875: 1 01111110 11000000000000000000000 2. Eksponentti: Ilmaisee luvun suuruusluokan määrittämällä arvo-osalle kertoimen 2 eksponentti. Koodattu etumerkittömästi, mutta arvosta vähennetään korjaustermi: eksponentti = etumerkitön arvo korjaustermi. 32-bittisellä liukuluvulla on 8 eksponenttibittiä ja korjaustermi 127. 64-bittisellä liukuluvulla on 11 eksponenttibittiä ja korjaustermi 1023. Esimerkissä eksponenttibitit 01111110: etumerkitön arvo on 126. Eksponentti = 126 127 = -1 Toistaiseksi saatu tulkittua arvo 1.75 * 2-1 = 1.75/2 = 0.875. 3. Etumerkki: Ei-negatiivinen arvo: 0, negatiivinen arvo: 1 47 Huomioidaan etumerkki esimerkin lopullinen arvo on -0.875. 48

Biteistä Entä jos haluamme manipuloida suoraan muuttujan bittien arvoja? Kokonaislukumuuttujiin voidaan soveltaa bitti(vektori)operaattoreita: x & y: arvojen x ja y vastaavien bittien looginen AND. x y: arvojen x ja y vastaavien bittien looginen OR. x ^ y: arvojen x ja y vastaavien bittien looginen XOR. ~x: arvon x bittien looginen NOT kääntää bittien arvot 0 1, 1 0. x >> n: siirrä arvon x bittiesityksen bittejä n askelta oikealle. x << n: siirrä arvon x bittiesityksen bittejä n askelta vasemmalle. Esim. 8-bittisillä unsigned char arvoilla x = 74 eli 01001010 ja y = 156 eli 10011100: x & y: 01001010 x y: 01001010 x ^ y: 01001010 & 10011100 10011100 ^ 10011100 00001000 11011110 11010110 Biteistä Muuttujan x oikealta vasemmalle laskien indeksin i omaavan bitin arvon luku onnistuu esim. suorittamalla operaatio (x >> i) & 1. 1) Siirretään ensin x:n bittiesitystä i askelta oikealle, jotta indeksin i bitti siirtyy oikeanpuoleisimmaksi bitiksi (jonka painoarvo on 2 0 = 1). 2) AND-operaatio & arvon 1 kanssa, jonka kaikki muut bitit ovat 0 ja oikeanpuoleisin bitti on 1, jättää oikeanpuoleisimman bitin arvon jäljelle sellaisenaan ja asettaa kaikki muut bitit nolliksi. Lopputulos on 1, jos oikeanpuoleisin bitti on 1, ja muuten 0. Esimerkiksi bittiesityksen 01101011 omaavan unsigned char -arvon x indeksit 2 ja 6 omaavien bittien arvojen luku: x >> 2: 01101011 >> 2 x >> 6: 01101011 >> 6 00011010 00000001 & 1: & 00000001 & 1: & 00000001 00000000 tulos 0 00000001 tulos 1 ~x: ~ 01001010 x >> 3: 01001010 >> 3 x << 3: 01001010 << 3 10110101 00001001 01010000 Biteistä 49 Muuttujan x oikealta vasemmalle laskien indeksin i omaavan bitin arvon asetus 1-bitiksi onnistuu esim. asettamalla x = x (1 << i). 1) Yllä siirretään ensin arvon 1 bittiesitystä i askelta vasemmalle, jotta arvon 1 ainoa 1-bitti siirtyy indeksin i kohdalle. 2) OR-operaatio arvon x kanssa johtaa siihen, että x:n indeksin i kohdalla on 1-bitti ja kaikki muut x:n bitit säilyvät ennallaan. Esimerkiksi bittiesityksen 01101011 omaavan unsigned char -arvon x indeksien 2 ja 6 omaavien bittien asetus 1-bitiksi: 1 << 2: 00000001 << 2 1 << 6: 00000001 << 6 00000100 01000000 x: 01101011 x: 01101011 01101111 01101011 Biteistä 50 Muuttujan x oikealta vasemmalle laskien indeksin i omaavan bitin arvon asetus 0-bitiksi onnistuu esim. asettamalla x = x & ~(1 << i). 1) Yllä siirretään ensin arvon 1 bittiesitystä i askelta vasemmalle, jotta arvon 1 ainoa 1-bitti siirtyy indeksin i kohdalle. 2) Tämän jälkeen NOT-operaatio ~ kääntää kohdan 1) tuloksen bitit: lopputuloksessa indeksin i kohdalla on 0 ja muut bitit ovat 1-bittejä. 3) AND-operaatio & arvon x kanssa johtaa siihen, että indeksin kohdalla on 0-bitti ja kaikki muut x:n bitit säilyvät ennallaan. Esimerkiksi bittiesityksen 01101011 omaavan unsigned char -arvon x indeksien 2 ja 6 omaavien bittien asetus 0-bitiksi: 1 << 2: 00000001 << 2 1 << 6: 00000001 << 6 ~: ~ 00000100 ~: ~ 01000000 11111011 10111111 & x: & 01101011 & x: & 01101011 01101011 00101011 51 52

Muuttujan (ja funktionkin) osoite Muuttujat (ja merkkijonovakiot sekä funktiot) sijaitsevat muistissa. Niillä on oma (muisti)osoite. sijainnin tavun tarkkuudella ilmaiseva kokonaisluku. C-kielessä on oma operaattori osoitteen selvittämiseen: & Operaatio &nimi kertoo alkion/funktion nimi muistiosoitteen. MUISTI printf-funktiolla oma määre Osoite &par par:n arvo osoitteen tulostamiseen: p Osoite &x x:n arvo (15) koodi.c int i = 99; void apu(int par) int x = 15; printf("%p", &x); Osoite &i i:n arvo (99) Osoite &apu Funktio apu. Osoitettuun alkioon viittaaminen 53 Osoitettuun alkioon voi viitata ns. osoitusoperaattorin * avulla. Jos os on osoitin (tai osoite), niin *os viittaa sen osoittamaan alkioon. Älä sekoita osoitinmäärettä ja osoitusoperaattoria. Eroavat ulkoisesti vain käyttötapansa/käyttöpaikkansa suhteen. Osoitinmääre esiintyy vain osana tyyppimääritystä. void tee_jotain(void) double x = 5.8; /* Alla double-osoitin y määritetään osoit- */ double *y = &x; /* tamaan x:ään eli y:n arvoksi x:n osoite. */ double z = *y; /* z saa y:n osoittaman arvon eli nyt z 5.8. */ x += 1; /* Kasvatetaan x:ää yhdellä eli nyt x = 6.8. */ z = 2 * (*y); /* z saa 2* y:n osoittaman arvon eli 2*6.8. */ *y = 3.14; /* Asetetaan z:n osoittaman alkion arvoksi 3.14. */ printf("%.2f", x); /* Tulostuu x:n arvo eli "3.14". */ 55 Osoitinmuuttuja ("pointteri") C-kielessä on erillinen muuttujakategoria osoitteiden manipuloimiseen: osoitinmuuttujat (lyhyemmin: osoittimet / pointterit) Osoitinmuuttujalla on tyyppi: minkä tyyppiseen alkioon se osoittaa. Osoitinmuuttujan määritys (perustapauksessa): kuin osoitetun tyyppisen muuttujan määritys, mutta muuttujan nimen eteen osoitinmääre *. Muotoa tyyppi *osnimi; oleva määritys: osnimi on osoitin, johon voi tallettaa tyyppiä tyyppi olevan alkion (tai funktion) osoitteen. Esim. char *cp; määrittää: cp on osoitin char-alkioon eli char-osoitin. koodi.c void apu(void) int x = 15; int *y = &x; MUISTI Osoite &x x:n arvo (15) Osoite &y Osoite &apu y:n arvo (osoite &x) Funktio apu. 54 "Alkuperäisen" alkion välittäminen osoittimella C-kieli (kuten Java) välittää funktion parametrit niiden arvojen kopioina. Ns. "call-by-value". void turhaa(int x) x = 2*x; /* Muutetaan paikallista parametrimuuttujaa x. */ void tee_jotain int z = 5; turhaa(z); /* Funktio turhaa saa parametrina z:n kopion. */ /* Alkuperäinen z on yhä 5. */ Muuttujan välitys osoittimella: sallii alkuperäisen arvon muuttamisen. void tuplaa(int *x) /* Parametri: osoitin int-arvoon. */ *x = 2*(*x); /* Muutetaan parametrin osoittamaa arvoa. */ void tee_jotain int z = 5; tuplaa(&z); /* Funktiolle tuplaa parametrina z:n osoite. */ /* Nyt z on 10, koska tuplaa muutti sitä. */ 56

Osoittimet ja taulukot, osoitinaritmetiikka C-kielessä taulukkomuuttuja osoitin taulukon ensimmäiseen alkioon. Mutta: taulukkomuuttuja ei ole osoitinmuuttuja. Taulukkomuuttujan arvoa ei voi muuttaa. Taulukkomuuttujalle taulu pätee sopimus &taulu == taulu. Taulukon arvot sijaitsevat peräkkäisissä osoitteissa. Osoittimelle (tai osoitteeseen) os voi soveltaa ns. osoitinaritmetiikkaa: Osoite, joka on i askelta osoitteesta os eteenpäin: os + i. Yksittäinen askel sizeof(*os) tavua eli osoitetun alkion koko tavuina. Taulukon t alkio t[i] sijaitsee osoitteessa t + i. (eli &t[i] == t + i) koodi.c int t[3] = 6,3,7; Osoite t = &t t[0]:n arvo (6) Osoite t + 1 t[1]:n arvo (3) MUISTI Osoite t + 2 t[2]:n arvo (7) 57 Osoittimet ja taulukot, osoitinaritmetiikka Osoitin, joka ei osoita mihinkään: ns. nollaosoitin (null pointer) Arvo 0, mutta C-standardikirjastossa määritellään myös nimi NULL. int-taulukon suurimman alkion haku osoitinaritmetiikkaa ja -operaatioita käyttäen: (vrt. saman tekevä funktio sivulla 30) int suurin(int koko, int *taulu) int *nyky = NULL; /* Alustetaan nollaosoittimeksi. */ int suurin = 0; /* Alla nyky alustetaan arvoon taulu eli se osoittaa */ /* alkioon taulu[0]. Kullakin kierroksella ++nyky asettaa */ /* nyky = nyky + 1 eli nyky siirtyy osoittamaan askeleen */ /* eteenpäin taulukossa taulu. Silmukka lopetetaan koko */ /* askeleen jälkeen eli kun nyky == taulu + koko. */ for(nyky = taulu; nyky < taulu + koko; ++nyky) /* Alla tutkitaan osoittimen nyky osoittamaa arvoa *nyky. */ suurin = (nyky == taulu *nyky > suurin)? *nyky : suurin; return suurin; 59 Osoittimet ja taulukot, osoitinaritmetiikka Taulukon indeksioperaattori []: osoitinaritmetiikkalasku ja osoitus. C-kielessä merkintä t[i] tarkoittaa laskutoimitusta/operaatiota *(t+i). Indeksioperaattori laskee osoitettavan osoitteen t+i ja sitten viittaa siihen osoitusoperaattorilla *. Seuraus: alkioon t[15] voi viitata tapaan 15[t]. *(t + 15) == *(15 + t). (mutta älä viittaa!) Taulukkoparametri funktiokutsussa: funktiolle välitetään osoitinmuuttuja, joka osoittaa taulukon alkuun. Esim. int apu(int t[]); vastaa tosiasiassa muotoa int apu(int *t);. Eräs seuraus: jos funktion sisällä tehdään operaatio sizeof(t)? Tulos on sizeof(int *) eli sen alkuun osoittavan osoittimen koko. Se tavumäärä, jonka yksi kyseisen tyyppinen osoitin vie tilaa. Ei riipu millään tavalla taulukon koosta. Vakiokokoinen moniulotteinen taulukko 58 C-kielessä voi määrittää moniulotteisen taulukon ilmaisemalla ulottuvuuksien koot peräkkäisten hakasulkeiden sisällä. Esim. int taulu2d[5][10]; määrittää 5x10-kokoisen int-taulukon ja double taulu3d[7][3][5]; määrittää 7x3x5-kokoisen double-taulukon. Kuten yksiulotteisessakin tapauksessa, määritys samalla luo taulukon. int taulu2d[2][4]; /* Määritetään 2x4-kokoinen int-taulukko. */ int i = 0; int j = 0; for(i = 0; i < 2; ++i) for(j = 0; j < 4; ++j) taulu2d[i][j] = i*j; /* Asetetaan vaikkapa arvo i*j. */ Alustusarvot voi antaa kuin yksiulotteiselle taulukolle: Arvojono, joka luettelee alustusarvot "riveittäin" edeten. Alustusarvojen hierarkian voi myös esittää sisäkkäisillä aaltosulkeilla. Jos kaikille alkioille ei anneta alustusarvoa, alustamattomat nollataan. /* Alla alustetaan 3x2x3-kokoinen int-taulukko niin, että 2x3*/ /* -kokoinen alitaulu t[0] saa arvot 0,1,,5 ja loput 0. */ int t3d[3][2][3] = 0, 1, 2, 3, 4, 5; 60

Vakiokokoinen moniulotteinen taulukko Vakiokokoisin ulottuvuuksin määritetty moniulotteinen taulukko koostuu konkreettisesti peräkkäisistä taulukoista, on siis "taulukoiden taulukko". Esim. int taulu2d[5][10]; on 50 int-arvoa käsittävä yhtenäinen taulukko, jonka kukin päätason alialkio on 10-alkioinen int-taulukko. Esim. pätee sizeof(taulu2d) = 50*sizeof(int) ja sizeof(taulu2d[0]) = 10*sizeof(int). taulu2d[0][4] taulu2d[0] taulu2d[1] taulu2d[1][8] taulu2d[2] Tällainen vakiokokoinen moniulotteinen taulukko funktion parametrina: alitaulukoiden koot pitää tietää jo parametrin tyypissä. (suuri rajoite!) Sama funktio ei sovi yleisille erikokoisille taulukoille! /* Tämä käy 4-alkioisia alitaulukoita omaaville taulukoille. */ int suurin2d(int rivilkm, int taulu2d[][4]) Funktio-osoitin 61 Funktio-osoittimella voisi esimerkiksi valita halutun toimenpiteen. int pienempi(int a, int b) return (a < b); /* int-totuusarvo: päteekö a < b? */ int isompi(int a, int b) return (a > b); /* int-totuusarvo: päteekö a > b? */ /* vrt on osoitin muotoa int vrt(int, int) olevaan funktioon.*/ int haeparas(int k, int *t, int (*vrt)(int, int)) int paras = 0; int i = 0; for(i = 1; i < k; ++i) if(vrt(t[i], t[paras])) /* vrt palauttaa totuusarvon. */ paras = i; return t[paras]; int main(void) int taulu[8] = 4, 8, 2, 9, 5, 1, 0, 7; int a = haeparas(8, taulu, pienempi); /* Hakee pienimmän. */ int b = haeparas(8, taulu, isompi); /* Hakee isoimman. */ printf("min %d ja max %d\n", a, b); /* "min 0 ja max 9" */ 63 Funktio-osoitin Funktio-osoittimen määritys: kuin funktion esittely, mutta: Funktion nimen tilalla osoittimen nimi. Nimen eteen osoitinmääre * ja sulut ympärille. Esim. int (*fos)(int, int); määrittää funktio-osoittimen fos, jonka osoittama funktio palauttaa int-arvon ja ottaa kaksi int-parametria. Sulkeiden merkitys: int *fos(int, int); esittelisi funktion fos, joka palauttaa int-osoittimen. Funktio-osoittimen kautta voi tehdä funktiokutsun joko suoraan sellaisenaan tai osoitusoperaattoria * käyttäen. Esim. yllä määritellyn osoittimen fos kohdalla sekä fos(2, 3) että (*fos)(2, 3) olisi laillinen kutsutapa. Funktio-osoittimen asetus osoittamaan funktioon: voi käyttää tai olla käyttämättä osoiteoperaattoria &. Esim. sekä int (*mainos)(int, char *[]) = main että int (*mainos) (int, char *[]) = &main määrittävät osoittimen mainos, joka osoittaa main-funktioon. 62 Muuttujan tyypin lisämääreet const/volatile/register Muuttuja voidaan määrittää vakiomuuttujaksi antamalla lisämääre const. Tapaan const tyyppi muuttujan_nimi = alustusarvo; Esim. const float pii = 3.14; ANSI C: const-muuttujia ei lasketa käännösaikaisiksi vakioiksi! const int koko = 100; int taulukko[koko]; /* Virhe: koko ei käännösaikainen vakio. */ const-muuttujan arvoa ei saa muuttaa sen arvon alustuksen jälkeen. Yritys tehdä niin johtaa kääntäjän virheilmoitukseen. Entä jos yrittää muuttaa const-arvoa epäsuorasti? C-standardi ei ota kantaa, mitä tapahtuu. ("undefined behaviour") int main(void) const int a = 100; int *a_os = (int *) &a; /* a_os osoittaa vakioon a. */ a = 125; /* Kääntäjän virheilmoitus: const-arvon muutos! */ *a_os = 125; /* Tämän efekti riippuu järjestelmästä! */ /* Linux/gcc:llä alla sain a:n arvoksi 125, kun taas Mac OS */ /* X:ssä a:n arvo ei muuttunut vaan päti yhä a == 100. */ 64

Muuttujan tyypin lisämääreet const/volatile/register Sekä osoittimelle että osoitetulle alkiolle voidaan antaa määre const. const tyyppi * const os = &osoitettava; Osoitinmuuttujan os kautta ei saa kirjoittaa. Osoitinmuuttujan os arvoa ei saa muuttaa. Eräs erikoistapaus: merkkijonovakiot, kuten esimerkiksi "huhtikuu". Kääntäjän alustamia char-taulukoita, joita ei pidä yrittää muuttaa. Esim. const char *kk = "huhtikuu" tai char *kk = "huhtikuu". Kumpikin määritys saa kääntäjän alustamaan muistiin char -taulukon "huhtikuu" ja asettamaan kk:n osoittamaan kyseisen taulukon alkuun. Tyyppi const char * parempi, koska "huhtikuu" on merkkijonovakio. Esimerkki: monen merkkijonovakion taulukko. const char *mt[] = "eka", "toka", "kolmas"; Taulukon mt alkiot const char-osoittimia eli tyyppiä const char *. Kääntäjä luo muistiin merkkijonot "eka", "toka" ja "kolmas". Taulukon mt alkiot mj[0],, mj[2] alustetaan osoittamaan niihin. 65 Muuttujan tyypin lisämääreet const/volatile/register Kääntäjän eräs keino yrittää optimoida muuttujan käsittelyä: Pidetään paljon käytetyn muuttujan arvoa prosessorin sisäisessä työmuistissa (ns. rekisterissä) useiden komentojen ajan. Vähentää tarvetta muistin lukuun ja kirjoittamiseen. Potentiaalinen ongelma: entä jos jokin ulkopuolinen tekijä tällä välin muuttaa muuttujan arvoa suoraan muistissa? Muuttujan määre volatile: muuttujan arvoa tulee ylläpitää suoraan muistissa. Esim. volatile int x; estää int-muuttujan x optimoinnin rekisteriin. Muuttujan määre register: neuvo C-kääntäjää ylläpitämään muuttuja muistin sijaan suoraan prosessorin rekisterissä. Esim. register double x; Sopii hyvin laiteläheiseen optimointiin. Ei yleensä tarvita. 67 Muuttujan tyypin lisämääreet const/volatile/register const-määreen käyttö funktion osoitinparametrien kanssa: Keino luvata, että funktio ei muuta osoitettua dataa. void tee_jotain(const int *t) /* t osoitin vakioon. */ t[0] = 1; /* Virheilmoitus: yritys muuttaa const-dataa. */ Jos funktio ei lupaa näin, sille ei pitäisi antaa osoitinta vakiodataan! void apu(int *t) /* t tavallinen osoitin. */ /* Tehdään jotain (ei välttämättä muuteta t:tä). */ void tee_jotain(const int *t) /* t osoitin vakioon. */ apu(t);/* Kääntäjä voi varoittaa: annamme funktiolle apu */ /* osoittimen const-dataan, vaikka apu ei lupaa */ /* olla muuttamatta osoitettua dataa. */ Nyrkkisääntö: anna funktion osoitinparametrille const-määre, jos funktio ei muuta osoitinparametrin osoittamaa dataa. 66 Nimetyt int-vakiot enum-määrittelyn avulla C-kielessä voi määrittää nimettyjä int-vakioita ns. enum-määrittelyiden avulla, jotka ovat muotoa: enum [enum_tyypin_nimi] vakio_nimi_1 [= int-arvo], vakio_nimi_n [= int-arvo] ; Edellä hakasuluissa olevat harmaat osat ovat vapaaehtoisia. Pilkku tulee muiden paitsi viimeisen määritetyn vakion perään. Jos vakiolle ei määritä arvoa, sen arvo on edeltävän vakion arvo + 1 Jos ensimmäiselle vakiolle ei anneta arvoa: arvoksi tulee 0. enum Bool /* Annetaan enum-tyypille nimi Bool. */ false = 0, /* Määritetään vakioarvo false = 0. */ true /* Emme anna arvoa, joten true = false + 1 = 1. */ ; /* Lopusta ei saa unohtaa puolipistettä. */ 68

Nimetyt int-vakiot enum-määrittelyn avulla enum-vakioiden nimet näkyvät koodissa määrityskohdastaan eteenpäin. enum-tyyppi määrittää oman lukutyyppinsä, joka käyttäytyy kuin int. enum-tyypin nimeen viitataan muodossa enum enum_tyypin_nimi. enum Bool /* Annetaan enum-tyypille nimi Bool. */ false = 0, /* Määritetään vakioarvo false = 0. */ true /* Emme anna arvoa, joten true = false + 1 = 1. */ ; /* Lopusta ei saa unohtaa puolipistettä. */ int * hae_arvo(int *t, int koko, int a) enum Bool loytyi = false; /* enum Bool -muuttuja loytyi. */ int *loppu = t + koko; /* Taulukon t loppuraja. */ for(; t < loppu; ++t) /* Kävellään taulukkoa t läpi. */ if(*t == a) /* Löytyikö t:n nykykohdasta arvo a? */ loytyi = true; /* Kirjataan muistiin, että löytyi. */ break; /* Jos a löytyi, palautetaan osoitin siihen, muuten NULL. */ return (loytyi == true)? t : NULL; 69 C-kielen "oliotyypit": struct- ja union-tietueet C-kielessä voi määrittää useasta data-jäsenestä koostuvia tietuetyyppejä. Hieman kuin luokka ilman mitään jäsenfunktioita, perintää tms. Tällaisen ns. struct-tietueen määritys on tavallisesti muotoa: struct tietueen_nimi tyyppi_1 jäsen_1; tyyppi_n jäsen_n; ; struct-tietueen tyyppiin viitataan muodossa struct tietueen_nimi. Usein määritetään lisäksi typedef-alias ilman avainsanaa struct. struct Kurssi /* Kurssia kuvaava tietuetyyppi. */ int op; /* Jäseneen op voi tallettaa kurssin laajuuden. */ char *nimi; /* Kurssin nimeen osoittava char-osoitin. */ ; /* Puolipistettä ei saa unohtaa! */ typedef struct Kurssi Kurssi; /* typedef-alias Kurssi. */ 71 Nimetyt int-vakiot enum-määrittelyn avulla enum-vakiot ovat käännösaikaisia vakioita. Voidaan käyttää esim. taulukon koon määrityksessä. enum-tyypille määritetään usein typedef-alias ilman avainsanaa enum. Esim. typedef enum Bool Bool; Nyt tyyppiin enum Bool voidaan viitata myös pelkällä nimellä Bool. enum /* Ei anneta tälle enum-tyypille nimeä: */ koko = 100 /* määritetään vain vakioarvo koko = 100. */ ; enum Viikonloppu lauantai = 6, sunnuntai /* sunnuntai = lauantai + 1 = 7 */ ; typedef enum Viikonloppu Viikonloppu; void tee_jotain() double taulukko[koko] = 0; /* Koon 100 taulukko. */ Viikonloppu vl = lauantai; /* Tyyppinä enum Viikonloppu. */ vl += 4; /* Luvallista: enum käyttäytyy kuin int. */ printf("vl: %d", vl); /* Tulos "vl: 10", koska vl = 6+4. */ 70 C-kielen "oliotyypit": struct- ja union-tietueet struct-tietuemuuttujat määritetään samaan tapaan kuin muut muuttujat. Määritys samalla luo kyseisen muuttujan. (vrt. taulukko) Tietuemuuttujan x jäseneen n viitataan pisteoperaattorilla tapaan x.n. Tietuemuuttujien x ja y välinen asetus x = y: Muuttujan x jäseniin kopioidaan muuttujan y jäsenten arvot. Tietuemuuttujan jäsenet voi alustaa samalla tavalla kuin taulukon alkiot. Annetaan jäsenten alustusarvot, järjestyksessä, aaltosulkeissa. Jos kaikille jäsenille ei anneta alustusarvoja, loput alustetaan nollilla. Jos tietueella on const-jäsen, on tämä ainoa suora alustustapa! Kurssi k = 5, "Ote C"; /* k.op = 5, k.nimi = "Ote C". */ Kurssi k2; /* Asetetaan k2:n jäsenten arvot alla erikseen. */ Kurssi k3 = 20; /* k3.op = 20, k3.nimi = 0 (eli NULL). */ k2.op = 10; k2.nimi = "Tietorakenteet". k2 = k; /* Nyt k2.op = k.op ja k2.nimi = k.nimi. */ printf("%s %dop", k2.nimi, k2.op);/* Tulostuu "Ote C 5op". */ 72

C-kielen "oliotyypit": struct- ja union-tietueet Struct-olion x ja sen jäsenten esitys muistissa: Jos jäsen x.a määritetty ennen jäsentä x.b, pätee &x.a < &x.b. Eli jäsenet talletettu muistiin peräkkäin niiden määritysjärjestyksessä. Mutta: kääntäjä voi jättää tyhjää tilaa jäsenten väliin! Miksi? Järjestelmät voivat teknisistä syistä asettaa sääntöjä siihen, millaisiin muistiosoitteisiin minkäkin tyyppiset arvot talletetaan! Esim. int-arvo tyypillisesti 4:llä jaolliseen osoitteeseen. Seuraus: vaikka oliolla x olisi ainoastaan jäsenet a ja b, voi silti päteä sizeof(x) > sizeof(x.a) + sizeof(x.b). struct E int a; char b; int c; ; a b Kuvan tilanteessa sizeof(struct E) == 3*sizeof(int) c 73 C-kielen "oliotyypit": struct- ja union-tietueet struct-tietue ei vastaa kunnollista luokkaa: Ei voi määrittää varsinaisia jäsenfunktioita, koske ei ole käytössä "this-osoitinta" eli tietoa siitä, mitä oliota kutsu koskee. Ei ole "private"-jäseniä, jotka olisivat piilossa ulkopuolisilta. Jäsenfunktioita vastaavan toiminnallisuuden toteutus struct-tietueille: Määritetään "jäsenfunktiot" erillisinä apufunktioina, joille välitetään käsiteltävä struct-olio erillisellä osoitinparametrilla. ("this-osoitin") Esim. olion k jäsenfunktiokutsua k.metodi(x) vastaava kutsu tehdään C-kielessä tapaan metodi(k_os, x), missä k_os on osoitin olioon k. void aseta_op(kurssi *k, int op) k->op = op; /* Olion k jäsenelle op parametrin op arvo. */ /* Mahdollisesti muita Kurssi-tietueen apufunktioita? */ int main(void) Kurssi k; /* Paikallinen Kurssi-muuttuja. */ aseta_op(&k, 5); /* Kurssi-olion k jäsenen op arvoksi 5. */ aseta_nimi(&k, "OTE C"); /* Voisi olla olemassa */ C-kielen "oliotyypit": struct- ja union-tietueet Tietue-olioita käsittelevät funktiot tyypillisesti määritetään saamaan käsittelemänsä oliot osoitinparametrien välityksellä. Motiivi 1: halutaan välttää funktioparametriksi annetun olion kopiointi. Jos tietue on iso, vie kopiointi mahdollisesti turhan paljon aikaa/tilaa. Motiivi 2: halutaan tarjota funktiolle mahdollisuus muokata oliota. Osoittimen kautta pääsy alkuperäiseen olioon. Lisäksi käytännön syy: dynaamisesti luotujen olioiden käsittely. Jos os on osoitin tietuemuuttujaan, jolla on jäsen i, voi jäseneen i viitata: (1) osoitinoperaattoria * käyttäen, tapaan (*os).i, tai vaihtoehtoisesti (2) erikseen tätä varten olevalla "nuolioperaattorilla" ->, tapaan os->i. void tulosta_kurssi(kurssi *k)/* k osoitin Kurssi-olioon. */ printf("%s %dop", k->nimi, k->op); /* Nuolioperaattori. */ /* Yllä voisi vaihtoehtoisesti olla (*k).nimi ja (*k).op. */ void tulosta_kurssi2(kurssi k) /* k: Kurssi-olio kopiona. */ printf("%s %dop", k.nimi, k.op); /* Pisteoperaattori. */ 74 C-kielen "oliotyypit": struct- ja union-tietueet struct-tietue funktion palautusarvona: Vaihtoehto 1: palautus tavanomaisena arvona (ei osoittimen kautta): Kuten parametrit, myös funktioiden palautusarvot välitetään kopioina. Ei välttämättä hyvä tapa, jos tietueen koko hyvin suuri. Kurssi tee_jotain(void) Kurssi k; /* k on paikallinen automaattinen muuttuja. */ /* Tehdään jotain (esim. muokataan oliota k). */ return k; /* Palautetaan paikallisen k:n arvo (kopio). */ Vaihtoehto 2: palautus osoittimen kautta: Kysymys: palautettavan tietueen muistitilan varaus? Automaattiseen (paikalliseen) muuttujaan ei saa palauttaa osoitinta! Kurssi * tee_jotain(void) Kurssi k; /* k on paikallinen automaattinen muuttuja. */ return &k; /* Vaara: osoitin paikalliseen muuttujaan. */ Dynaaminen varaus ok, mutta vapautus jää vastaanottajan vastuulle. /* funktio aseta_nimi, joka asettaa nimi-jäsenen arvon. */ 75 Dynaaminen muistinvaraus: katso sivut 83 ja 96. 76