27
91 Tiedosto Monessa tilanteessa olisi hyvä pystyä tallentamaan ohjelman suorituksen aikana syntyvää tietoa pysyvämmin. Nythän kaikki katoaa kun ohjelman suoritus lopetetaan. Tietoja on mahdollista tallentaa tiedostoon. Tiedosto on yhteenkuuluvien tietojen joukko, joka on tallennettu jollekin pitkäaikaistallennukseen pystyvälle medialle, kuten kiintolevylle, CD-levylle tai DVD-levylle. UNIX-järjestelmässä kaikki oheislaitteet ja järjestelmän osat näkyvät ohjelmoijalle tiedostoina. Tiedostot ovat joko binääritiedostoja tai tekstitiedostoja. Tekstitiedostot Tekstitiedosto sisältää ASCII-merkkejä, joista muodostuu peräkkäisiä rivejä, jotka päättyvät rivinvaihtomerkkiin '\n' (newline). Esimerkkinä tekstitiedostosta on lähdekielinen C-ohjelma, HTML-tiedosto jne. Koska tekstitiedostossa on pelkkiä ASCII-merkkejä, kaikki luvut tallennetaan niihin merkkeinä. Esimerkiksi kokonaisluku 345 varaa tekstitiedostosta tilaa kolme merkkiä. Binääritiedostot Binääritiedostot voivat sisältää käytännössä mitä tahansa tietoa: tekstiä, ääntä kuvaa. Binääritiedoston sisältöä ei voida tulkita ASCII-järjestelmän avulla. Esimerkkejä binääritiedostoista ovat C-ohjelmasta käännetty EXE-tiedosto, word- tiedosto, jpg-kuva jne.

Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

91

Tiedosto Monessa tilanteessa olisi hyvä pystyä tallentamaan ohjelman suorituksen aikana

syntyvää tietoa pysyvämmin. Nythän kaikki katoaa kun ohjelman suoritus

lopetetaan.

Tietoja on mahdollista tallentaa tiedostoon. Tiedosto on yhteenkuuluvien tietojen

joukko, joka on tallennettu jollekin pitkäaikaistallennukseen pystyvälle medialle,

kuten kiintolevylle, CD-levylle tai DVD-levylle. UNIX-järjestelmässä kaikki

oheislaitteet ja järjestelmän osat näkyvät ohjelmoijalle tiedostoina.

Tiedostot ovat joko binääritiedostoja tai tekstitiedostoja.

Tekstitiedostot

Tekstitiedosto sisältää ASCII-merkkejä, joista muodostuu peräkkäisiä rivejä, jotka

päättyvät rivinvaihtomerkkiin '\n' (newline). Esimerkkinä tekstitiedostosta on

lähdekielinen C-ohjelma, HTML-tiedosto jne. Koska tekstitiedostossa on pelkkiä

ASCII-merkkejä, kaikki luvut tallennetaan niihin merkkeinä. Esimerkiksi

kokonaisluku 345 varaa tekstitiedostosta tilaa kolme merkkiä.

Binääritiedostot

Binääritiedostot voivat sisältää käytännössä mitä tahansa tietoa: tekstiä, ääntä

kuvaa. Binääritiedoston sisältöä ei voida tulkita ASCII-järjestelmän avulla.

Esimerkkejä binääritiedostoista ovat C-ohjelmasta käännetty EXE-tiedosto, word-

tiedosto, jpg-kuva jne.

Page 2: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

92

C-ohjelmien muuttujat voidaan sellaisinaan tallentaa binääritiedostoon, jolloin

esimerkiksi kokonaisluku 345 varaa binääritiedostosta tilaa kokonaislukutyypin

vaatiman tilanvarauksen verran.

Tekstitiedostoja pystyy usein käsittelemään binääritiedostoina, mutta

binääritiedostoa ei pysty käsittelemään tekstitiedostona. Tämä johtuu siitä, että

tekstitiedostojen yhteydessä suoritetaan automaattisia merkkimuunnoksia.

Esimerkiksi, kun tekstitiedostosta tulostetaan kuvaruudulle newline-merkki '\n',

se muutetaan kahdeksi ASCII-merkiksi: carriage-return, CR (kursori rivin alkuun)

ja line-feed, LF (kursori yksi rivi alaspäin). Binääritiedostoille ei suoriteta missään

vaiheessa mitään muunnoksia.

Tiedostojen käsittelyn perustoiminnot

1. Tiedoston määrittely

Tiedosto määritellään FILE-tietotyypin avulla. FILE on tietue, joka sisältää

tiedostonhallintaan liittyviä asioita.

Tiedosto määritellään käytännössä määrittelemällä osoitin FILE-tietueeseen:

FILE *tiedosto;

Tiedosto-osoitin tiedosto on tämän määrittelyn jälkeen jonkin tietovirran (eli

tiedoston) nimi. Sitä käytetään kaikissa tiedosto-operaatioissa viittaamaan

tiedostoon. On huomattava, että tässä vaiheessa tiedosto ei vielä viittaa

mihinkään fyysiseen tiedostoon.

Page 3: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

93

2. Tiedoston avaaminen

Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää

käytännössä yhteen tiedosto-osoittimen ja fyysisen levytiedoston. Avaamiseen

käytetään funktiota fopen().

tiedosto = fopen ("C:\\NIMET.TXT", "r");

fopen() saa kaksi merkkijonoparametria, jotka ovat tiedoston fyysinen nimi

ja tiedoston avaustapa.

Avaustapa ilmoittaa sen, missä tarkoituksessa tiedostoa käytetään.

Kolme avaustapaa ovat:

• ”r” = tiedostoa luetaan (read)

• ”w” = tiedostoon kirjoitetaan (write)

• ”a” = tietojen lisääminen tiedoston loppuun (append).

Avaustavan yhteydessä voidaan määritellä, käsitelläänkö tiedostoa teksti- vai

binääritiedostona. Oletuksena on tekstitiedosto. Jos halutaan avata tiedosto

binäärimuodossa, lisätään lainausmerkkien sisään toiseksi parametriksi b-kirjain,

siis ”rb”, ”wb” tai ”ab”.

Oheisessa esimerkissä avataan tekstitiedosto kirjoittamista varten ja kirjoitetaan

sinne teksti

#include <stdio.h>

void main(void)

{

FILE *tiedostomuuttuja = fopen("teletapit.txt", "w");

fprintf(tiedostomuuttuja, "teletapit nukkumaan !\n");

fclose(tiedostomuuttuja);

}

Page 4: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

94

Funktio fprintf() toimii samantyylisesti kuin printf(), mutta tulostus

tapahtuu tiedostoon, ei näytölle. Jos tiedoston avaaminen onnistuu, fopen()

palauttaa osoittimen kyseiseen tiedostoon, muuten se palauttaa arvon NULL.

C-ohjelman pääohjelma main() on itse asiassa samanlainen funktio kuin kaikki

muutkin funktiot, eli se pystyy palauttamaan arvon käyttöjärjestelmään, josta se

käynnistettiin. Siksi oikea tapa määritellä main() on käyttää void:n sijasta sille

paluuarvoa int1.

Virhetilanteessa yleinen tapa on, että ohjelman suoritus loppuu siihen ja

käyttöjärjestelmälle palautetaan negatiivinen kokonaisluku. Jos ohjelman suoritus

menee loppuun ongelmitta, palautetaan nolla tai positiivinen luku. Ohjelman

suoritus loppuu ja paluuarvo palautetaan return-lauseella aivan samoin kuin

mistä tahansa funktiosta palatessa:

#include <stdio.h>

int main(void)

{

FILE *avaus;

avaus = fopen("oulu.txt", "w");

if(avaus == NULL)

{

printf("Tiedoston avauksessa on tapahtunut\

virhe!");

return(-1);

}

fprintf(avaus, "Paskakaupunni");

fclose(avaus);

return(0);

}

1 Modernit C-kääntäjät suorastaan vaativat paluuarvon, toisin kuin paleoliittiselta kaudelta

periytyvä Borland 4.5. Uusissa kääntäjissä määrittely void main(void) aiheuttaa

virheilmoituksen. Kääntäjät yleensä lisäävät return(0):n automaattisesti main():n loppuun, jos sitä ei siellä ole.

Page 5: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

95

Tiedosto oulu.txt tallentuu hakemistoon, joka on C-kääntäjän oletushakemisto

ajon aikana. Tiedosto näkyy tiedostolistauksessa ja sen sisältöä voi tutkia jollain

tekstieditorilla, vaikkapa Notepadilla:

Katsotaan seuraavaksi, miten oulu.txt-tiedostosta luetaan tekstiä:

#include <stdio.h>

void main(void)

{

FILE *avaus;

char puskuri[20]="";

avaus = fopen("oulu.txt", "r");

if(avaus == NULL)

{

printf("Tiedoston avauksessa on tapahtunut virhe!");

return(-1);

}

fgets(puskuri,sizeof(puskuri),avaus);

printf("%s",puskuri);

fclose(avaus);

return(0);

}

Nyt tiedot luetaan ensin 20 alkion mittaiseen merkkijonopuskuriin fgets()-

funktiolla. fgets() tarvitsee syötteekseen 3 parametria:

• Puskurin nimi

Page 6: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

96

• Puskurin koko, joka voidaan määrittää sizeof-operaattorilla.

• Tiedosto-osoittimen

fgets() on turvallisempi vaihtoehto tavalliselle gets()-funktiolle, koska sille

voidaan määritellä luettavien merkkien maksimimäärä. Sitä voidaan käyttää

lukemaan merkkijonoja myös näppäimistöltä tiedoston sijaan, jolloin tiedoston

sijalla parametrilistassa on stdin.2 fgets jättää rivinvaihtomerkin ’\n’

merkkijonon loppuun toisin kuin gets (huom, ’\n’ on eri asia kuin merkkijonon

lopetusmerkki; molemmat funktiot jättävät loppuun loppumerkin ’\0’).

Tiedoston sulkeminen

Kun tiedoston käsittely lopetetaan, se pitää sulkea funktiolla fclose(), jota

käytetään seuraavasti:

fclose(tiedosto);

missä tiedosto on fopen()-funktion palauttama osoitin. Tavallinen aloittelijan

tekemä virhe on unohtaa sulkea tiedosto, ja sitten ihmetellä että miksi mitään ei

tallennu mihinkään ☺

Harj69

Tee ohjelma, jonka avulla tallennat oman nimesi tiedostoon nimi.txt. Harj70 Tee ohjelma, jonka avulla luet oman nimesi tiedostosta, jonka nimi annetaan

ohjelman suorituksen aikana merkkijonomuuttujan avulla.

2 Jo ensimmäinen tunnettu Internet-mato vuodelta 1988, nk. Morris Internet Worm hyödynsi gets-funktion

merkkijonopuskurin ylivuotoa.

Page 7: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

97

Harj71 Tee ohjelma, joka tulostaa numerot 1-100 tabulaattorilla erotettuna 10 lukua/rivi

tiedostoon luvut.txt

Harj72

Muokkaa harjoitusta 71, niin että se käy hakemassa luvut tiedostosta ja tulostaa

ne viisi kappaletta rivilleen ohjelmaikkunaan.

Sijaintiosoitin

Kun tiedostoa luetaan tai sinne kirjoitetaan, tiedostojärjestelmän sisäinen

muuttuja, sijaintiosoitin (sijaintipointteri), osoittaa aina seuraavan

käsittelykohdan.

Kun tiedosto avataan, sijaintiosoitin siirtyy automaattisesti tiedoston alkuun.

Kun tiedostosta luetaan, sijaintiosoitin siirtyy (jälleen automaattisesti) siihen

kohtaan, mihin lukeminen päättyi. Tätä voidaan toistaa, jolloin on kysymys

tiedoston peräkkäiskäsittelystä. Peräkkäiskäsittelyssä seuraava lukemistapahtuma

aloittaa lukemisen siitä, mihin edellinen jäi.

Vastaavasti, kun kirjoitetaan, sijaintipointteri etenee kirjoituksen myötä eteenpäin

ja osoittaa aina seuraavan kirjoituskohdan.

Page 8: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

98

Esimerkki: Sanakirjaohjelma

Tehdään seuraavaksi sanakirjaohjelmasta tiedoston avulla toteutettu versio.

Sanat on talletettu tekstitiedostoon sanakirja.txt, jonka sisältö näyttää

seuraavalta.

NICE TO KNOW …

Tiedosto-osoittimen manipulointiin on C-kielessä tarjolla seuraavia standardikirjaston

funktioita:

• int fseek ( FILE * stream, long int offset, int

origin );

o Siirtää tiedosto-osoittimen nykyisestä paikasta origin offset tavua (toimii

varmasti oikein vain binääritiedostoille)

• long int ftell ( FILE * stream );

o Palauttaa tiedosto-osoittimen paikan tavuina tiedoston alusta (toimii varmasti

oikein vain binääritiedostoille)

• void rewind ( FILE * rewind );

o Siirtää osoittimen tiedoston alkuun

• int fgetpos ( FILE * stream, fpos_t * position );

o Palauttaa osoittimen fpos_t nimiseen stdio.h:ssa määriteltyyn tietueeseen,

jonka avulla saadaan tiedosto-osoittimen paikka selville. Tietuetta fpos_t

käytetään syötteenä funktiolle:

• int fsetpos ( FILE * stream, const fpos_t * pos );

o Siirtää osoittimen paikkaan pos.

Page 9: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

99

Kysytään käyttäjältä sana englanniksi ja ohjelma tulostaa vastaavan sanan

suomeksi lukemalla se tiedostosta

#include <stdio.h>

#include <string.h>

int main(void)

{

FILE *sanakirjatiedosto;

char suomi[20],englanti[20],puskuri[40],sana[20];

printf("Anna sana englanniksi\n");

gets(sana);

sanakirjatiedosto = fopen("sanakirja.txt", "r");

if(sanakirjatiedosto == NULL)

{

perror("Tiedoston avauksessa on tapahtunut virhe!");

return(-1);

}

while (!feof(sanakirjatiedosto))

{

fgets(puskuri,sizeof(puskuri),sanakirjatiedosto);

sscanf(puskuri,"%s %s",&englanti,&suomi);

if (strcmp(englanti,sana)==0)

{

printf("suomeksi %s\n",suomi);

break;

}

}

fclose(sanakirjatiedosto);

return(0);

}

Ohjelman toiminta lyhyesti:

• Määritellään 4 kpl merkkijonomuuttujia, joista muuttujaan sana

tallennetaan käyttäjän syöttämä sana, jota sanakirjasta etsitään.

• Yritetään avata sanakirja.txt-tiedosto. Jos avaus ei onnistu, perror()-

funktio, joka on erityinen virheilmoitusten tulostukseen tarkoitettu funktio,

Page 10: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

100

tulostaa tiedon siitä, mikä oli vialla. Ohjelmasta poistutaan ja palautetaan

arvo -1 Jos avaus onnistuu, niin:

• While-silmukassa luetaan tiedostosta tekstiä rivi kerrallaan niin kauan kuin

tiedosto ei ole lopussa. Tiedoston loppuminen tarkastetaan feof()-

funktiolla (EOF = End Of File).

• fgets() suorittaa varsinaisen tekstin lukemisen merkkijonomuuttujaan

puskuri.

• Puskurista luetaan kaksi merkkijonoa muuttujiin englanti ja suomi

sscanf()-funktiolla.

• Verrataan löydettyä englanninkielistä sanaa muuttujaan sana. Jos ovat

samat, tulostetaan vastaava suomenkielinen sana.

Harj73

Eräs mittauslaite on tuottanut mittausdataa tekstitiedostoon. Insinöörioppilas

S.A. Tiaiselle on annettu tehtäväksi kirjoittaa C-ohjelma, joka analysoi tätä dataa.

Tiaiselle on kerrottu, tiedostossa on aina yhdellä rivillä juokseva numero sekä

kaksoistarkkuuden liukuluku. Auta insinöörioppilasta tekemällä ohjelma, joka

tulostaa tiedostossa olevat luvut, niiden lukumäärän, keskiarvon sekä suurimman

ja pienimmän arvon. Data löytyy täältä

http://www.oamk.fi/~jjauhiai/opetus/LK1/pohjakoodit/data.txt

Esimerkki: Tietuetaulukon tallentaminen tekstitiedostoon funktiossa

Tässä esimerkissä määritellään yksinkertaisin mahdollinen tietue, joka sisältää

yhden kokonaisluvun. Ohjelmassa on funktiot kysy(), tulosta() ja

tallenna(). Funktioille viedään osoitin tietuetaulukkoon sekä tietuetaulukon

alkioiden määrä, joka kysytään pääohjelmassa. Lisäksi kysytään tiedoston nimi,

johon tallennetaan ja viedään tallenna()-funktiolle merkkijonotaulukkona. Tässä

Page 11: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

101

esimerkissä on toinen tapa kirjoittaa funktioita. Jos funktioiden määrittelyosat

kirjoitetaan ennen main()-funktiota, ei erillisiä prototyyppeja tarvita.

#include <stdio.h>

struct tietue {

int arvo;

};

void kysy(struct tietue *x,int maara)

{

int i;

for (i=0;i<maara;i++)

{

printf("Luku ?\n");

scanf("%d",&x->arvo);

x++;

}

}

void tulosta(struct tietue *x,int maara)

{

int i;

for (i=0;i<maara;i++)

{

printf("%d\n",x->arvo);

x++;

}

}

int tallenna(struct tietue *x,char *tiedosto,int n)

{

int i;

FILE *stream;

stream=fopen(tiedosto,"w");

if (stream == NULL)

{

perror("Virhe : ");

return(-1);

}

for (i=0;i<n;i++)

{

fprintf(stream,"%d\n",x->arvo);

x++;

}

fclose(stream);

Page 12: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

102

return(0);

}

int main(void)

{

tietue luvut[5];

int lkm;

char tiedostonimi[30];

printf("Mihin tallennetaan ?\n");

gets(tiedostonimi);

printf("Montako lukua ?\n");

scanf("%d",&lkm);

kysy(luvut,lkm);

tulosta(luvut,lkm);

tallenna(luvut,tiedostonimi,lkm);

return(0);

}

Harj74

Tee Harjoituksen 67 opiskelijarekisteriohjelmasta versio, joka tallettaa syötetyt

tiedot tiedostoon. Kirjoita tallennus omaksi funktiokseen.

Harj75

Lisää edelliseen harjoitustehtävään funktio, joka lukee tiedostosta

opiskelijarekisterissä olevat tiedot ja tulostaa ne näytölle.

Harj76

Tee edellisen harjoitustehtävän ohjelmaan tekstipohjainen valikko, jossa on

seuraavat toiminnot:

o Lisää opiskelija rekisteriin

o Hae rekisterissä olevat opiskelijat

o Tulosta opiskelijarekisterin sisältö

o Tallenna opiskelijarekisterin sisältö

o Lopeta

Page 13: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

103

Binääritiedostoon kirjoittaminen ja lukeminen, hex-editori

Binääritiedoston sisältöä ei voi tutkia tekstieditorilla. On kuitenkin erityisiä

ohjelmia, ns. hex-editoreita, joiden avulla voidaan tutkia binääritiedostojen

sisältöä. Suurempia datamääriä tallennettaessa binääriformaatti on

huomattavasti tehokkaampi tapa tallentaa tietoja kuin tekstiformaatti.

Binääritiedosto vie vähemmän levytilaa ja sekä sen lukeminen että sinne

kirjoittaminen on nopeampaa.

Ohjelma tallentaa 1000 kokonaislukua 0 – 999 binääritiedostoon. Erona

tekstitiedostoon kirjoittamiseen on, että luvut kirjoitetaan yhdellä kertaa

fwrite-funktiolla.

#include <stdio.h>

const int N=1000;

int main(void)

{

FILE *avaus;

avaus = fopen("numeroita.bin", "wb");

int i,taulukko[N];

if(avaus == NULL)

{

printf("Tiedoston avauksessa on tapahtunut virhe!");

return(-1);

}

for (i=0;i<N;i++)

{

taulukko[i]=i;

}

fwrite(taulukko,sizeof(int),\

sizeof(taulukko)/sizeof(int),avaus);

fclose(avaus);

return(0);

}

Page 14: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

104

Ohjelmasta kannattaa huomata, että tiedot kirjoitetaan levylle for-silmukan

jälkeen kertalinttuulla. Funktiolla fwrite() annetaan parametrina:

o Tulostettava data, tässä osoitin kokonaislukutaulukon alkuun

o Tallennettavan dataelementin koko tavuina. Yleensä on turvallisinta varata

elementille tilaa kyseisen tietotyypin maksimikoon verran. Maksimikoko

voidaan selvittää sizeof()-funktion avulla. Eli tässä esimerkissä

sizeof(int) laskee kokonaisluvun tavujen lukumäärän tässä

järjestelmässä (4). Sillä varmistetaan, että suurin mahdollinen

kokonaisluku pystytään tallentamaan.

o Kolmas parametri ilmoittaa tallennettavien alkioiden lukumäärän. Jotta

lukumäärä tulee varmasti oikein kaikissa tapauksissa, on tämä luku syytä

laskea kaavalla sizeof(taulukko)/sizeof(int) (tässä 4000/4 =

1000).

o Viimeisenä annetaan tiedosto-osoitin.

Jos käytettäisiin tekstiformaattia, pitäisi jokainen luku kirjoittaa erikseen silmukan

sisällä fprintf:llä, joka on huomattavan paljon hitaampaa.

Jos numeroita.bin-tiedoston avaa Notepadilla, tulostus näyttää seuraavalta:

Tämä ei selvästikään näytä siltä, että tiedostossa olisi tallennettuna 1000

kokonaislukua. Jos sen sijaan käytetään hex-editoria, joka ei kuulu Window$in

Page 15: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

105

vakiovarustukseen, mutta joita löytyy netistä, näyttää tiedoston sisältö

seuraavalta:

Yksi tavu vastaa yhtä kahden numeron kokoista heksalukua keskimmäisessä

kentässä (00 00 00 00 01 00 00 00 …). Ohjelmassa oli määritelty, että jokainen

luku tallennetaan 4 tavulla. Siten jokaista tallennettua lukua vastaa neljän

heksaluvun ryhmä:

00 00 00 00 = 0

01 00 00 00 = 1

02 00 00 00 = 2

03 00 00 00 = 3

Jne..

Luvut on esitetty ns. ”big endian”-muodossa, jossa eniten merkitsevä tavu on

viimeisenä.

Esimerkki: Binääritiedoston lukeminen

Edellä talletettu tiedosto voidaan lukea fread()-funktiolla. Parametrit ovat

samat kuin fwrite():llä.

Page 16: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

106

#include <stdio.h>

const int N=1000;

int main(void)

{

FILE *avaus;

avaus = fopen("numeroita.bin", "rb");

int i,taulukko[1000]={};

if(avaus == NULL)

{

printf("Tiedoston avauksessa on tapahtunut virhe!");

return (-1);

}

fread(taulukko,sizeof(int),\

sizeof(taulukko)/sizeof(int),avaus);

for (i=0;i<1000;i++)

printf("%d ",taulukko[i]);

fclose(avaus);

return(0);

}

Esimerkki: Tyypillisen mittauslaitteen dataformaatin tuottaminen ja

lukeminen

Monet lääketieteelliset mittauslaitteet (esim. EEG tai EKG-laite) tallentavat

mittaustuloksensa yleensä binääritiedostoon, vaikka XML-formaatti (joka on

tekstiformaatti) onkin viime aikoina yleistynyt. Alan insinöörin olisi varmaankin

hyvä tietää, miten datan saa luettua omaan ohjelmaan jatkokäsittelyä varten.

Tiedosto koostuu tyypillisesti kahdesta osasta:

o Header sisältää mittauksen tiedot, kuten potilaan nimen ja tunnisteen

(ID), mittauspäivämäärän ja laitteen parametreja, sekä paljon muuta,

laitekohtaista tietoa.

o Data sisältää itse mittausdatan jossain muodossa, yleensä

kokonaislukuina.

Page 17: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

107

Tiedoston rakenne on valitettavasti monesti laite- tai valmistajakohtainen, joskin

tiedostoformaatteja on viime vuosin pyritty standardoimaan 3. Tiedoston tarkka

rakenne on selvitettävä. Yleensä laitevalmistajat antavat tämän tiedon

kysyttäessä.

Seuraavassa ohjelmassa kirjoitetaan yksinkertainen itse keksitty mittaustiedosto

ja luetaan se toisessa ohjelmassa.

#include <stdio.h>

int main(void)

{

FILE *avaus;

char patname[30]="Pelle Peloton";

char date[10]="23.3.2007";

char id[10]="abc666";

int data[1000]={}; //Dataksi pelkkiä nollia

avaus = fopen("testi.bin", "wb");

int i,taulukko[1000]={};

if(avaus == NULL)

{

perror("Tiedoston avauksessa on tapahtunut virhe!");

return (-1);

}

fwrite(patname,sizeof(char),sizeof(patname)/\

sizeof(char),avaus);

fwrite(date,sizeof(char),sizeof(date)/\

sizeof(char),avaus);

fwrite(id,sizeof(char),sizeof(id)/sizeof(char),avaus);

fwrite(data,sizeof(int),sizeof(data)/\

sizeof(int),avaus);

fclose(avaus);

return(0);

}

Tiedostoa luettaessa on tietotyyppien vastattava tavun tarkkuudella toisiaan:

3 Eräs biosignaalien standardi on EDF, eli European Data Format, josta löytyy lisätietoa oheisesta linkistä::

http://www.edfplus.info/specs/index.html

Page 18: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

108

#include <stdio.h>

int main(void)

{

FILE *avaus;

char patname[30];

char date[10];

char id[10];

int data[1000];

avaus = fopen("testi.bin", "rb");

int i,taulukko[1000]={};

if(avaus == NULL)

{

perror("Tiedoston avauksessa on tapahtunut virhe!");

return (-1);

}

fread(patname,sizeof(char),sizeof(patname)/\

sizeof(char),avaus);

fread(date,sizeof(char),sizeof(date)/\

sizeof(char),avaus);

fread(id,sizeof(char),sizeof(id)/sizeof(char),avaus);

fread(data,sizeof(int),sizeof(data)/sizeof(int),avaus);

printf("%s\n%s\n%s\n",patname,date,id);

for (i=0;i<1000;i++)

printf("%d ",data[i]);

fclose(avaus);

return(0);

}

Harj77 Tee ohjelma, joka tallentaa luvut 0 - 999 binääritiedostoon, lukee ne sieltä ja

tulostaa näytölle.

Harj78

Tallenna vapaasti valittava teksti binäärimuodossa tiedostoon ja lähetä se

kaverille sellaisten tietojen kanssa että hän pystyy sen avaamaan.

Harj79

Muuta harjoituksen 76 opiskelijarekisteriä niin, että tiedot tallentuvat

binääritiedostoon.

Page 19: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

109

Komentoriviargumentit

Main-funktion sielunelämään liittyy vielä yksi tähän mennessä käsittelemätön

juttu. Joku on ehkä joskus käyttänyt tietokonetta MS-DOS-aikakaudella ennen

Windowsia, tai käyttänyt Unix- tai Linux-käyttöjärjestelmiä. Näissä järjestelmissä

ohjelmia voidaan käynnistää ns. komentotulkista (command interpreter). Itse

asiassa Window$:n ikonin klilkkaaminen on sama asia kuin että vastaava

komento kirjoitetaan komentotulkkiin.

C-kieltä on käytetty paljon esimerkiksi käyttöjärjestelmien ohjelmointiin. Niinpä

varsinkin Unixin ja C-kielen kehitys liittyvät läheisesti toisiinsa.

C-kielen main()-funktion täydellinen muoto on int main(int argc,char *argv[])

Toisin sanoen main():lle voidaan välittää tietoa käyttöjärjestelmästä:

o argc = ”argument count”, eli kuinka monta parametria main():lle

syötetään

o argv = osoitin merkkijonotaulukkoon, joka sisältää itse

komentoriviparametrit. Ensimmäinen komentoriviargumentti argv[0] on

itse suoritettavan komennon nimi.

#include <stdio.h>

int main (int argc, char *argv[])

{

int count;

printf ("1. argumentti %s\n",argv[0]);

Page 20: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

110

if (argc > 1)

{

printf("Muut argumentit:\n");

for (count = 1; count < argc; count++)

{

printf("argv[%d] = %s\n", count, argv[count]);

}

}

else

{

printf("Komennolla ei ollut argumentteja.\n");

}

return 0;

}

Toisessa esimerkissä tehdään yhteenlaskuohjelma summa(), jota kutsutaan

komentoriviltä:

#include <stdio.h>

#include <stdlib.h>

int main (int argc, char *argv[])

{

int n;

double eka=0,toka=0;

Page 21: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

111

if (argc!=4)

{

printf("Virhe !\n");

return(-1);

}

eka=atof(argv[1]);

toka=atof(argv[3]);

if (eka == 0 || toka == 0)

{

printf("Virhe");

return(-1);

}

printf("%f\n",eka+toka);

return 0;

}

Komentoriviltä suoritettavaksi tarkoitettu käännetään ”build”-komennolla, ei

normaalilla compile/run-systeemillä. Build tekee suoritettavan exe-tiedoston, joka

ajetaan Window$in Command Promptissa (Komentokehotteessa).

Harj80

Tee komentoriviltä suoritettava nelilaskin, jolle annetaan kaksi lukua ja niiden

välissä merkki +, -, * tai /.

Page 22: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

112

Ohjelman jakaminen useampaan tiedostoon

Opintojakson viimeisenä asiana käsitellään C-ohjelman jakamista useaan

tiedostoon. Tämä on käytännössä välttämätöntä, kun ohjelman koko kasvaa

suureksi. Ohjelmaan on helppo lisätä uusia ominaisuuksia ja se pysyy näin

helpommin hallittavana. Suurissa ohjelmissa saattaa olla satoja C-kielisiä

tiedostoja ja satojatuhansia rivejä koodia. Lienee selvää, ettei kukaan pystyisi

sellaista ohjelmaa ymmärtämään, jos koko koodi olisi yhdessä pötkössä.

Tässä käytetään esimerkkinä Dev-C++ ympäristöä. Muissa Windows-kääntäjissä

pitäisi löytyä suurin piirtein vastaavat toiminnot, joskin mahdollisesti hieman eri

nimisinä. Unix-ympäristössä operoidaan ns. makefile:n avulla, jonne listataan

käännettävät tiedostot ja se suoritetaan make-nimisen ohjelman avulla.

Ohjelman jakaminen aloitetaan avaamalla uusi projektitiedosto:

File -> New -> Project

Page 23: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

113

Vaihdetaan Project options:ssa vaihtoehdoksi ”C Project” ja tyypiksi

”Console Application”.

Seuraavassa esimerkissä tehdään merkkijonon merkkien laskentaohjelma, joka

on jaettu kolmeen tiedostoon:

o main.c sisältää varsinaisen pääohjelman

o laskeMerkit.h on käyttäjän itse määrittelemä header- eli

otsikkotiedosto, joka sisältää tässä esimerkissä funktion laskeMerkit()

prototyypin.

o laskeMerkit.c sisältää varsinaisen funktion laskeMerkit() rungon eli

määrittelyosan.

Nämä tiedostot voidaan lisätä projektiin File -> New source file - valikon

kautta.

Dev-C++:n käyttöliittymä näyttää nyt tältä. Eli Project2:n alle on lisätty kolme

edellä mainittua tiedostoa. Kuvassa näkyy pääohjelman sisältö. Huomaa, että

Page 24: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

114

oma otsikkotiedosto ”laskeMerkit.h” on sisällytettävä otsikkotiedostoihin.

Sitä ei merkitä hakasulkuihin, vaan lainausmerkkeihin.

Tiedosto laskeMerkit.h sisältää vain yhden rivin:

int laskeMerkit(char *);

Ja tiedosto laskeMerkit.c seuraavan koodin:

int laskeMerkit(char *s)

{

int i=0;

while (s[i]!='\0')

i++;

return i;

}

Yhteen omaan otsikkotiedostoon voidaan kirjoittaa useamman funktion

prototyypit. Jokainen funktio on kuitenkin kirjoittaa omiin tiedostoihinsa,

jolloin koodin ylläpidettävyys säilyy hyvänä.

Harj81

Page 25: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

115

Muuta nelilaskinohjelmaa siten, että kukin laskutoimitus on kirjoitettu omaan

tiedostoonsa. Kaikkien neljän funktion prototyypit on kirjoitettu yhteen

otsikkotiedostoon.

Harj82

Lisää edellä esitettyyn esimerkkiohjelmaan omaan tiedostoonsa funktio

int laskeKirjain(char *merkkijono,char haettava_merkki);

joka laskee syötetystä merkkijonosta, kuinka monta kertaa valittu kirjain

esiintyy.

Page 26: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

116

Johdatus ohjelmointiin-opintojakso päättyy tähän.

Syksyllä jatketaan Olio-ohjelmoinnilla. Kaikkia kielen

piirteitä ei vielä tässäkään ajassa pystytty kattamaan,

mutta tärkeimmät perusasiat kuitenkin. Onnea viimeiseen

välikokeeseen ja aurinkoista kesää. Jukka & Johanna.

Ode to C

[email protected] (Jon S. Stumpf)

GTE Telecom Inc., Bothell, WA

(computer, chuckle)

0x0d2C

May your signals all trap

May your references be bounded

All memory aligned

Floats to ints rounded

Remember ...

Non-zero is true

++ adds one

Arrays start with zero

and, NULL is for none

For octal, use zero

0x means hex

= will set

== means test

use -> for a pointer

a dot if its not

? : is confusing

use them a lot

a.out is your program

there's no U in foobar

and, char (*(*x())[])() is

a function returning a pointer

to an array of pointers to

functions returning char

Page 27: Tiedosto - Oamkjjauhiai/opetus/LK1/C5.pdf · 2007-03-26 · 93 2. Tiedoston avaaminen Tiedosto joudutaan avaamaan ennen kuin sitä voidaan käyttää. Avaaminen liittää käytännössä

117