tiedosto - oamkjjauhiai/opetus/lk1/c5.pdf · 2007-03-26 · 93 2. tiedoston avaaminen tiedosto...
Post on 13-Aug-2020
9 Views
Preview:
TRANSCRIPT
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.
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.
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);
}
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.
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
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.
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.
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.
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,
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ä
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);
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
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);
}
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
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ä.
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.
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
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.
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]);
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;
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 /.
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
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ä
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
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.
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
stumpf@gtenmc.gtetele.com (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
117
top related