Tällä sivulla esitellään muutamia perus asioita c-kielisestä ohjelmoinnista.
Tutkitaan aluksi kuinka se maailmankuulu hello-world sovellus kirjoitetaan c-kielellä.
#include <stdio.h> int main(){ printf("Hello World!"); return 0; }
Ja analysoidaan edellistä koodia
#include <stdio.h>
on ns. esikääntäjän ohjauskäsky, joka kertoo että tiedosto stdio.h tarvitaan mukaan käännökseenint main() {
kertoo, että tästä alkaa main-niminen funktio, joka päättyy loppusulkuun }
. Sana int
kertoo, että main funktio palauttaa kokonaisluvunprintf("Hello World!");
kutsutaan funktiota printf ja sille annetaan argumenttina merkkijono "Hello World!"return 0
palautetaan arvo nollaEsimerkistä huomataan lisäksi, että C-kielessä jokainen lause päättyy puolipisteeseen!
Edellä tulikin monta termiä, joiden merkitys pitäisi selvittää. Eli siis termit:
(Kannattaa ensin lukea Johdannosta kappale Tietokone ohjelman toiminta )
Muuttujia tarvitaan ohjelmoinnissa aina kun halutaan tallentaa jotain arvoja, joita voi myös muuttaa myöhemmin. Jos esimerkiksi haluat tehdä ohjelman, joka kysyy käyttäjän iän ja tulostaa sen ruudulle, tarvitset muuttujan.
Muuttuja luodaan ilmoittamalla muuttujan tietotyyppi ja nimi. Tietotyyppi ilmaisee millaista tietoa haluat tallentaa. Tietotyyppi voi olla esimerkiksi:
Muuttujatyyppiä valittaessa pitää tarkistaa tietotyypin lisäksi lukualueen riittävyys ja muuttujatyypin vaatima muisti-tilantarve. Esimerkiksi kokonaislukutyyppejä on useita ja ne vievät eri määrän muistia. Muistia kannattaa yrittää säästää, mutta kannattaa varmistaa, että lukualue riittää.
Muuttujatyyppejä on esitelty mm. seuraavalla sivulla https://en.wikipedia.org/wiki/C_data_types
Muuttuja voidaan luoda esimerkiksi seuraavilla lauseilla
Muuttujan alustamisella tarkoitetaan arvon sijoittamista muuttujaan sen luonnin yhteydessä. Esimerkiksi näin:
Muuttujan näkyvyysalue tarkoittaa sitä osaa koodista, josta muuttujan voidaan viitata. Jos muuttujaan viitataan tämän näkyvyysalueen ulkopuolelta, kääntäjä antaa virheilmoituksen joka ilmoittaa "ettei muuttujaa ole".
Muuttujan näkyvyysalue on se lohko, jossa muuttuja on määritelty. Lohko tarkoittaa yleensä alkusulun {
ja loppusulun }
välistä aluetta.
Esimerkki
int main(){ int i; for (i=1; i<=5; i++) { printf("%d: Hello inside loop!\n",i); } printf("%d: Hello outside loop!\n",i); return 0; }Edellinen koodi toimii, koska muuttujan i näkyvyysalue on koko main-funktio.
Jos esimerkkiä muutetaan näin:
int main(){ for (int i=1; i<=5; i++) { printf("%d: Hello inside loop!\n",i); } printf("%d: Hello outside loop!\n",i); return 0; }Koodi ei toimi, koska muuttuja i on määritetty for-loopissa ja näin sen näkyvyysalue on vain tuo for-lohko.
Erityisesti scanf-funktiota käytettäessä on hyvä ymmärtää mitä tarkoittaa muuttujan osoite. Muuttujan osoite tarkoittaa sen muistipaikan numeroa, johon muuttujan arvo on tallennettu. Voidaan ajatella, että tietokoneen muisti on jaettu samankokoisiin lokeroihin, jotka on numeroitu. Kukin lokero on yhden tavun suuruinen eli siihen voidaan tallentaa 8 bittiä.
Muuttujan osoitteeseen viitataan c-kielessä laittamalla &-merkki muuttujan nimen eteen. Esimerkiksi muuttujan age osoite on &age.
char tyyppiselle muuttujalle varataan muistia vain yksi muistipaikka, joten muuttujan osoite on selkeästi tuon muistipaikan osoite. Sen sijaan esimerkiksi int tyyppiselle muuttujalle varataan yleensä 4 muistipaikkaa ja muuttujan osoitteella tarkoitetaan näistä ensimmäistä. Jos esimerkiksi muuttujalle age on varattu muistipaikat 101,102,103 ja 104, niin &age on 101.
Typecasting tarkoittaa muuttujatyypin vaihtoa ohjelman suorituksen aikana. Se taphtuu kirjoittamalla uusi tyyppi suluissa muuttujan nimen eteen. Tällaista tarvitaan, jos halutaan esimerkiksi laskea kokonaislukujen osamäärä, niin että tulos on liukuluku. Esimerkiksi näin:
int a, b; float result; result = (float)a / (float)b;Jos edellä ei käytetä typecasting:ia, on myös result tyypiltään int ja silloin desimaaliosa menetetään.
Kirjastossa stdio.h on määritelty funktio nimeltään printf. Sen avulla voidaan tulostaa ruudulle tekstiä ja muuttujien arvoja.
Esimerkiksi seuraava lause tulostaa lainausmerkkien sisällä olevan tekstin ruudulle. Merkit \n aiheuttavat rivinvaihdon.
printf("This is first line \n and this is second line\n");
Muuttujien arvot tulostetaan niin, että siihen kohtaan johon arvo halutaan tulostaa kirjoitetaan tulostettavan tiedon tyyppi ja lainausmerkkien jälkeen ilmoitetaan muuttujan nimi, jonka arvo tuon tyypin tilalle tulostetaan.
Esimerkiksi int tyyppisen muuttujan arvo voidaan tulostaa seuraavasti:
int age=23; printf("Jim is %d years old", age);Edellä %d ilmoittaa, että tähän kohtaan tulostetaan kokonaisluku ja pilkun jälkeen ilmoitetaan, että em. kokonaisluku on muuttujan age arvo. Esimerkki siis tulostaa ruudulle tekstin
Jim is 23 years old
Voidaan tulostaa myös useita muuttujan arvoja. Esimerkiksi näin:
int age=23; float length=181.5; printf("Jim is %d years old and his length is %f cm ", age, length);Ja edellä %f ilmoittaa, että tähän tulostetaan liukuluku. Ilman lisäohjeita, printf tulostaa melkoisen määrän desimaaleja. Jos halutaan rajata tulostettavien desimaalien määrä vaikka kahteen voidaan edellisen esimerkin tulostuslause kirjoittaa näin
printf("Jim is %d years old and his length is %.2f cm ", age, length);
Voit lukea lisätietoja printf-funktiosta sivulta https://www.tutorialspoint.com/c_standard_library/c_function_printf.htm
Kirjastossa stdio.h on määritelty myös funktio nimeltään scanf. Sen avulla voidaan lukea käyttäjän näppäimistöltä antama tieto muuttujan arvoksi. scanf-funktiolle tulee ilmoittaa
Esimerkiksi käyttäjältä kysytään ikä ja hänen antamansa tieto halutaan tallentaa kokonaislukuna muuttujan age arvoksi (eli siis age-muuttujalle varattuun muistipaikkaan). Nyt voidaan käyttää seuraavanlaista funktiokutsua:
scanf("%d", &age);
Alla olevassa esimerkissä on kokonaisuudessaan c-lähdekoodi sovellukselle, jossa kysytään käyttäjältä ikä ja se tulostetaan ruudulle
#includeint main(){ int age; printf("How old are you?\n"); scanf("%d",&age); printf("You said that you are %d years old\n", age); return 0; }
Voit lukea lisätietoja scanff-funktiosta sivulta https://www.tutorialspoint.com/c_standard_library/c_function_scanf.htm
Kommentti tarkoittaa tekstinosaa (sana, rivi, kappale), jota kääntäjä ei huomioi. Kommenttien tarkoituksena on antaa informaatiota koodia lukevalle ohjelmoijalle. Kommentit muodostetaan c-kielessä kahdella tavalla:
//
tarkoittavat, että näiden jälkeen loppuosa rivistä tulkitaan kommentiksi/*
ja */
väliin jäävä osa tulkitaan kommentiksiTähän mennessä tekstissä on jo esiintynyt funktiot main, printf ja scanf.
Funktiota voidaan sanoa myös aliohjelmaksi, sillä se on itsenäinen osa sovellusta. Edellä mainitut printf ja scanf ovat funktioita, jotka on määritetty c-ohjelmointiympäristön mukana toimitettavassa stdio-kirjastossa. Aiemmissa esimerkeissä on hyödynnetty niitä kutsumalla mainittuja funktioita.
Ohjelmoijat kirjoittavat myös itse funktioita. Niiden tarkoitus on selkeyttää koodia jakamalla sitä pienempiin kokonaisuuksiin. Ne voivat myöskin vähentää koodirivien määrää, koska samaa funtiota voidaan hyödyntää useita kertoja.
Funktio muodostuu kahdesta osasta:
Funktion palautusarvo tarkoittaa tietoa, jonka funktio lopuksi palauttaa. Voidaan esimerkiksi kirjoittaa funktio, joka suorittaa jonkin laskutoimituksen ja palauttaa tuon laskutoimituksen tuloksen. Palauttaminen määritetään käyttäen sanaa return.
Funktion parametrit tarkoittavat funktiolle annettavia lähtötietoja. Jos esimerkiksi kirjoitat funktion, joka laskee kahden kokonaisluvun summan, niin nuo kaksi kokonaislukua ovat funktion parametrit.
Esimerkki yhteenlasku-funktio voidaan kirjoittaa näin:
int sum(int a, int b) { int answer=a+b; return answer; }Edellä rivi
int sum(int a, int b)
on siis funktion otsikko. Siinä on määritetty, että
Sulkumerkkien {}
sisällä oleva osa muodostaa funktion rungon. Sen sisällä suoritetaan yhteenlasku ja lopuksi palautetaan laskun tulos.
void-palautusarvo tarkoittaa, että funktio ei palauta mitään. Esimerkiksi yhteenlaskufunktio voidaan toteuttaa niin, että se tulostaa vastauksen, eikä palauta mitään:
void second_sum(int a, int b) { int answer=a+b; printf("The sum is %d\n", answer); }Koska eo. funktio ei palauta mitään (paluuarvo on void), ei funktiossa ole return-lausetta.
Funktiota ei suoriteta, jollei sitä kutsuta. Funktion kutsu siis ilmoittaa, että tässä kohdassa halutaan suorittaa mainittu funktio.
Funktiota kutsutaan "mainitsemalla" sen nimi. Nimen perään tulee aina sulut ()
. Sulkujen sisään tulee argumentit eli siis ne arvot, jotka funktion parametreille annetaan. Jos, funktio palauttaa jotain, yleensä tämä palautusarvo tallennetaan johonkin (tai tulostetaan).
Esimerkiksi em. funktiota, joka ei palauta mitään voidaan kutsua näin:
second_sum(7,8);Jolloin funktio tulostaa lukujen 7 ja 8 summan.
Sen sijaan funktiota joka palauttaa annettujen lukujen summan voitaisiin kutsua näin:
int calc=sum(5,6);jolloin calc nimiseen muuttujaan tallennetaan lukujen 5 ja 6 summa.
Jokaisessa c-ohjelmassa tulee olla main-funktio eli pääohjelma. Muuten sovellusta ei suoritetan ollenkaan. main-funktiolle ei kirjoiteta kutsua, vaan käyttöjärjestelmä kutsuu sitä.
Nykyisessä c-standardissa suositellaan, että main-funktion palautusarvoksi määritetään int. Siksi siis funktion lopussa on lause return 0;
Mikäli main-funktiolle määritetään parametreja on kyseessä ns. komentoriviargumentit
, jotka siis annetaan komentoriviltä.
Esimerkiksi ping ohjelmaa käynnistetään komentoriviltä antamalla sille argumenttina ip-osoite tai dns-nimi näin:
ping google.com
Funktion prototyypin tarkoitus on kertoa kääntäjälle mikä on funktion palautusarvon tyyppi ja mikä on funktion parametrien lukumäärä ja niiden tyypit. Prototyypit kirjoitetaan yleensä ohjelman alkuun.
Funktion prototyypiksi kelpaa funktion otsikko eli esimerkiksi aiemmista esimerkeistä rivi int sum(int a, int b)
. Parametrien nimiä ei ole prototyypissä välttämätön mainita eli voitaisiin kirjoittaa edellinen prototyyppi myös muodossa int sum(int , int )
Aiemmissa esimerkeissä funktion prototyyppi puuttui, mutta silti kääntäjä ei antanut virheilmoituksia (tai varoituksia). Tämä johtui siitä, että funktioiden kutsut olivat koodissa myöhemmin kuin funktioiden määrittely.
Alla olevassa esimerkissä prototyyppien poisjättäminen aiheuttaa virheilmoituksen tai ainakin varoituksen.
int main(){ int sum(int a, int b); void second_sum(int a, int b); int calc=sum(5,6); printf("The sum is %d\n",calc); second_sum(7,8); return 0; } int sum(int a, int b){ int answer=a+b; return answer; } void second_sum(int a, int b){ int answer=a+b; printf("The sum is %d\n", answer); }
Header tiedostot (eli kirjastot) tunnistaa tarkentimesta .h. Ne voivat olla ohjelmoijan itsensä kirjoittamia tai kääntäjän mukana toimitettuja tiedostoja, kuten stdio.h
Header tiedostot liitetään koodiin include-lauseella esimerkiksi näin:
#include <stdio.h> #include "myfunctions.h"Nyt ensimmäinen tiedosto eli stdio.h on kääntäjän mukana toimitettu tiedosto, joten kulmasuluilla
<>
ilmoitetaan kääntäjälle, että sitä etsitään systeemihakemistosta. Sen sijaan toinen eli myfunctions.h on käyttäjän itse kirjoittama, joten lainausmerkeillä ilmoitetaan, että sitä etsitään projektihakemistosta.
Esimerkki voisi sisältää tiedostot main.c ja myfunctions.h ja myfunctions.c. Tiedoston myfunctions.h sisältö voisi olla seuraava:
int multiply(int, int );Eli siinä on funktion multiply prototyyppi. Tiedoston myfunctions.c sisältö voisi olla seuraava
#include "myfunctions.h" int multiply(int a, int b){ return a*b; }Siinä on siis funtion multiply toteutus. Ja main.c tiedoston sisältö on seuraava:
#include <stdio.h> #include "myfunctions.h" int main(){ int calc=multiply(5,6); printf("The result is %d\n",calc); return 0; }
C-kielessä ei ole varsinaista muuttujatyppiä merkkijonoja varten, vaan merkkijonoja käsitellään merkkitaulukoina. Esimerkiksi, jos halutaan tallentaa sana Hello
, voidaan se tallentaa taulukkoon, johon mahtuu 6 merkkiä. Tarvitaan 6 merkkiä, koska sanassa on 5 merkkiä ja jonon loppumerkille tarvitaan yksi tavu. Loppumerkki on \0.
Seuraavana on kaksi tapaa luoda merkkitaulukko ja alustaa se sanalla Hello
char myWord[]="Hello";
char myWord[6]={'H','e','l','l','o','\0'};
Käytettiinpä kumpaa vain edellisistä, niin on luotu merkkitaulukko, jossa on 6 alkiota ja nämä alkiot ovat: myWord[0], myWord[1], myWord[2], myWord[3], myWord[4], myWord[5].
Huomaa, että edellä alkiota myWord[6] ei ole olemassa!
Sekä printf ja scanf-funktioissa merkkijonojen formaatiksi kannattaa määritää %s.
Seuraavassa esimerkissä käyttäjältä kysytään nimi ja hänelle tulostetaan tervehdys.
int main() { char fname[30]; printf("What is your name!\n"); scanf("%s",fname); printf("Hello %s\n",fname); return 0; }Muutama huomio Esimerkistä
fname
, johon voidaan tallentaa 30 merkkiä&
(*)
(*) mitä tarkoittaa &fname
?
&fname on siis merkkitaulukon osoite ja se viittaa merkkitaulukon ensimmäisen alkion osoitteeseen. Siis &fname on sama kuin &fname[0]. Edellä siis scanf-rivi voidaan korvata myös seuraavilla
scanf-funktiolla voidaan lukea vain yksittäisiä sanoja, koska se lopettaa lukemisen välilyöntiin. Jos halutaan lukea merkkijonoja, joissa on välilyöntejä, voidaan käyttää gets-funtiota seuraavasti
gets(fname);Huom! Jos fgets saa syötteekseen merkin "\n", se lopettaa lukemisen. Ja scanf funktion jälkeen juuri tuo mainittu merkki on puskurissa. Jos, siis scanf-funktion jälkeen tulee fgets, on puskuri tyhjennettävä ennen fgets:iä lauseella fflush(stdin);
Taulukko eli taulukkomuuttuja tarkoittaa muuttujaa johon voidaan tallentaa useita samantyyppisiä arvoja. Edellä käsiteltiin jo merkkitaulukoita, mutta taulukoihin voidaan tallentaa myös kokonaislukuja tai liukulukuja.
Esimerkiksi seuraava lause luo kokonaislukutaulukon, johon voidaan tallentaa 5 kokonaislukua
int ages[5];
Taas on syytä muistaa, että ensimmäinen alkio on ages[0]
ja viimeinen on ages[4]
Pari esimerkkiä kokonaislukutaukoista:
int ages[5]={6,3,12,22,31}; printf("The age of the second person is %d\n",ages[1]);
printf("What is the age of the third person?\n"); scanf("%d",&ages[2]);
Kaksiulotteinen taulukko määritellään antamalla kaksi indeksiä eli ikäänkuin rivien ja sarakkaiden lukumäärä. Esimerkiksi seuraavassa määritellään kaksiulotteinen kokonaislukutaulukko.
int main() { int tableOfNumbers[2][3]={ {1,2,3}, {4,5,6} }; printf("The last number in the table is %d\n",tableOfNumbers[1][2]); return 0; }Ja sovellus tulostaa siis tekstin
The last number in the table is 6
Ehtorakenteet ovat tärkeä osa ohjelmointia. Sovellukset tekevät usein päätöksiä jatkotoimenpiteistä esimerkiksi sen perusteella mitä painiketta käyttäjä on painanut. Nämä päätökset perustuvat ehtorakenteiden käyttöön.
Yksinkertaisimmillaan ehtorakenne koostuu if-osasta ja toimenpiteistä, jotka suoritetaan ehdon täyttyessä. Esimerkiksi näin:
printf("Before the test\n"); if(test==5){ printf("This is the first thing to do if test=5"); printf("This is the second thing to do if test=5"); } printf("After the test\n");Edellä siis ehto täyttyy, jos muuttujan
test
arvo on yhtäsuuri kuin 5. Tällöin tulostetaan sulkujen {}
sisällä olevat lauseet. Jos muuttujan test arvo ei ole 5, molemmat em. lauseet jätetään tulostamatta.
Edellä mainittiin jo vertailuoperaattori == eli yhtäsuuruus. Vertailuoperaattorit ovat:
Boolean tyyppinen muuttuja voi saada vain arvon TRUE tai FALSE (tarvitaan kirjasto stdbool.h). Jos esimerkiksi on luotu boolean tyyppinen muuttuja nimeltä myTest seuraavasti:
#include <stdbool.h> bool myTest;Voidaan kirjoittaa if-ehto
if(myTest == TRUE)tai lyhyemmin
if(myTest)Ja kaikki seuraavat ehdot ovat identtisiä
if(myTest == FALSE)
if(myTest != TRUE)
if(!myTest)
Jos on kaksi vaihtoehtoa, ei ole järkevää kirjoittaa kahta if-lausetta näin:
if(myTest){ toimenpiteet A } if(!myTest){ toimenpiteet B }Koska edellä ensimmäisen ehdon täyttyessä joudutaan silti testaamaan toisen ehdon täyttyminen, vaikka se ei voi täyttyä, jos ensimmäinen ehto täyttyi. Parempi olisi siis käyttää rakennetta
if(myTest){ toimenpiteet A } else{ toimenpiteet B }
Jos if-else rakenteessa toimenpideosissa on vain yksi lauseke, voidaan käyttää lyhyttä versiota. Esimerkiksi seuraava if-else-rakenne
int myNumber; //myNumber saa arvon jostain ... if(myNumber > 10){ printf("Iso luku"); } else { printf("Pieni luku"); }voidaan kirjoittaa lyhyemmin näin
(myNumber > 10) ? printf("Iso luku") : printf("Pieni luku") ;
If-else-rakenne voi sisältää useita vaihtoehtoja eli else if
-osia. Esimerkiksi seuraava rakenne on mahdollinen
int main() { int test=1; if (test==1) { printf("Option A\n"); } else if(test==2){ printf("Option B\n"); } else if(test==3){ printf("Option C\n"); } else { printf("Option D\n"); } return 0; }
int main() { int test=1; switch (test) { case 1:{ printf("Option A\n"); break; } case 2:{ printf("Option B\n"); break; } case 3:{ printf("Option C\n"); break; } default:{ printf("Option D\n"); break; } } return 0; }
Huomaa, että switch-case rakenteessa ehdot eivät voi olla monimutkaisia, kuten if-else rakenteessa. if-ehdot voivat olla esimerkiksi seuraavia:
if(test==1 || test==2)Eli "jos test=1 tai test=2"
if(test>0 && test<5)Eli "jos test suurempi kuin 0 ja pienempi kuin 5"
if(test==5 && age==22)Eli "jos test=5 ja age=22"
Esimerkiksi on toteutettava seuraavanlainen luokittelurakenne c-kielellä:
if(age<13){ printf("child\n"); } else if(age<=19){ printf("teenage\n"); } else { printf("adult\n"); }Huomaa, että keskimmäistä ehtoa ei tarvitse kirjoittaa muodossa
if(age>13 && age<=19)
Toistorakenteet ovat myös tärkeä osa ohjelmointia. Useimmissa ohjelmointikielissä on kaksi toistorakennetta for-looppi ja while-looppi.
for-loopilla toteutetaan yleensä toistorakenne, jossa toistojen lukumäärä on ennalta tiedossa (esim. kysytään 5 lukua).
while-looppi taas soveltuu paremmin tilanteisiin, jossa toistetaan toimenpiteitä, niin kauan kuin ehto on voimassa (esim. kysytään lukuja, niin kaun kuin luvut ovat positiivisia).
Esimerkki for-loopista
int main() { int myNum; int sumOfNums=0; for(int x=1; x<=5; x++){ printf("Give me a number\n"); scanf("%d",&myNum); sumOfNums=sumOfNums+myNum; } printf("The sum of given numbers is %d\n",sumOfNums); return 0; }Edellä muuttujaa x käytetään rajoittamaan toistojen määrää.
sumOfNums = sumOfNums+myNum;
voitaisiin kirjoittaa lyhyemmin muodossa sumOfNums += myNum;
Esimerkki while-loopista
int main() { int myNum=1; int sumOfNums=0; while (myNum > 0) { printf("Give me a number\n"); scanf("%d",&myNum); sumOfNums+=myNum; } printf("The sum of given numbers is %d\n",sumOfNums); return 0; }
Joskus tarvitaan rakennetta, jossa on toistorakenne toistorakenteen sisällä. Seuraavassa esimerkissä on luotu kaksiulotteinen kokonaislukutaulukko, jonka alkiot halutaan tulostaa rivi kerrallaan allekkain. Tulos saadaan kahdella for-loopilla
int main() { int tableOfNumbers[3][4]={ {5,2,9,11}, {4,6,8,10}, {12,8,4,3} }; for(int row=0; row<=2; row++){ for(int column=0; column<=3; column++){ printf("%d, ",tableOfNumbers[row][column]); } printf("\n"); } return 0; }Ja edellisen tulos on siis
5, 2, 9, 11, 4, 6, 8, 10, 12, 8, 4, 3,
Operaattorien avulla voidaan manipuloida muuttujien arvoja, kuten kasvattaa tai vähentää.
Matemaattiset operaattorit ovat:
Incrementointi tarkoittaa tässä yhteydessä muuttujan arvon kasvattamista yhdellä ja decrementointi taas vähentämistä yhdellä. C-kielessä nämä ovat
Yksinkertaisin sijoitusoperaattori on =, joka siis sijoittaa merkin oikealla puolella olevan arvon(muuttuja tai vakio) merkin vasemmalla puolella olevaan muuttujaan.
Muita sijoitusoperaattoreita ovat :
Operaattori | Esimerkki | Kuvaus |
---|---|---|
= | a = b | a = b |
+= | a += b | a = a+b |
-= | a -= b | a = a-b |
*= | a *= b | a = a*b |
/= | a /= b | a = a/b |
%= | a %= b | a = a%b |
Osoitin (pointer) eli osoitinmuuttuja tarkoittaa muuttujaa, johon voidaan sijoittaa muistiosoite. Osoitin luodaan ilmoittamalla minkä tyyppisen muuttujan osoite halutaan sijoittaa ja osoittimen nimen eteen kirjoitetaan *-merkki. Esimerkiksi mypointer-niminen osoitinmuuttuuja, johon voidaan sijoittaa kokonaislukumuuttuja osoite voidaan luoda seuraavasti
int *mypointer;Tutkitaan vielä osoitinta seuraavan esimerkin avulla
int x=5; int *mypointer; mypointer=&x; printf("x:n arvo eli *mypointer on %d\n",*mypointer); printf("x:n osoite eli mypointer on %p\n",mypointer);Edellä siis lauseella
mypointer=&x;
mypointer osoittimeen sijoitettiin muuttujan x osoite (eli &x).
Osoitinta tarvitaan, jos funktion pitää muuttaa sille annettujen muuttujien arvoja. Esimerkiksi seuraavassa sovelluksessa funktiolle doubleTest1 annetaan muuttujan x arvo argumenttina. Tällöin funktio ei muokkaa alkuperäisen muuttujan x arvoa, koska funktion parametrina määritelty muuttuja x ei ole sama muuttuja kuin mainissa määritelty x.
void doubleTest1(int x); int main() { int x=5; doubleTest1(x); printf("Test1: mainissa x:n arvo=%d\n",x); return 0; } void doubleTest1(int x){ x=2*x; printf("funktiossa doubleTest1 x:n arvo=%d\n",x); }Ohjelma tulostaa seuraavaa:
funktiossa doubleTest1 x:n arvo=10 Test1: mainissa x:n arvo=5Jos halutaan, että funktio muuttaa sille argumenttina annetun muuttujan arvoa, määritetään funktion parametri osoittimeksi ja argumenttina viedään muuttujan x osoite eli seuraavasti
void doubleTest2(int *x); int main() { int x=5; doubleTest2(&x); printf("Test2: mainissa x:n arvo=%d\n",x); return 0; } void doubleTest2(int *x){ *x=2**x; printf("funktiossa doubleTest2 x:n arvo=%d\n",*x); }Jolloin ohjelma tulostaa
funktiossa doubleTest2 x:n arvo=10 Test2: mainissa x:n arvo=10
Tietue(struct) tarkoittaa rakennetta, johon voidaan sisällyttää useita muuttujia. Tietue voidaan luoda yksinkertaisesti näin:
struct Person_struct{ int pnumber; char pname[20]; };Tietueen sisällä määritettyjä muuttujia (pnumber ja pname) kutsutaan kentiksi (member). Kun tietue on luotu edellisen esimerkin tavoin, voidaan luoda tietuemuuttujia, joiden tyyppi on em. tietue seuraavasti:
struct Person_struct p1; struct Person_struct p2;Usein näkee myös seuraavan esimerkin mukaisia tietueen määrityksiä:
typedef struct Person_struct{ int pnumber; char pname[20]; } person;Sanalla typedef ilmoitetaan, että halutaan luoda vaihtoehtoinen nimi, joka tässä on siis person. Jolloin tietuemuuttujia voidaan luoda seuraavasti:
person p1; person p2;
Jos luodaan muuttuja, jonka tyyppinä on tietue, niin muuttujannimen ja kentän välissä käytetään pistettä. Jos sensijaan luodaan osoitinmuuttuja, niin erottimena käytetään nuolta. Seuraavassa esimerkissä havainnollistetaan näiden eroa.
typedef struct Person_struct{ int pnumber; char pname[20]; } person; person p1; p1.pnumber=1; strcpy(p1.pname,"Jim Morrison"); printf("Person1: name = %s number = %d \n",p1.pname, p1.pnumber); person *p2=&p1; printf("Person2: name = %s number = %d \n",p2->pname, p2->pnumber);
Myös tietueesta voidaan muodostaa taulukko. Seuraava esimerkki havainnollistaa tietuetaulukon käyttöä.
typedef struct Person_struct{ int pnumber; char pname[20]; } person; person team[3]; for(int x=0; x<=2; x++){ printf("Anna %d. numero\n",x+1); scanf("%d",&team[x].pnumber); printf("Anna %d. nimi\n",x+1); fflush(stdin); gets(team[x].pname); } printf("Joukkueen jasenet\n"); for(int x=0; x<=2; x++){ printf("Nimi: %s Numero:%d\n", team[x].pname, team[x].pnumber); }
C-kielinen sovellus voi kirjoittaa tai lukea tiedostoja. Käsiteltävä tiedosto voi olla joko tekstitiedosto tai binääritiedosto. Tässä tutkitaan vain tekstitiedoston käsittelyä.
Tiedoston käsittelyssä tarvitaan osoitin johon tallennetaan käsiteltävän tiedoston muistiosoite.
Tiedosto-osoitin voidaan määritellä seuraavasti
FILE *filepointer;Sen jälkeen funktiolla fopen-voidaan avata tiedosto. fopen-funktiolle annetaan kaksi argumenttia
FILE *filepointer; filepointer=fopen("C:/temp/test2.txt","w");
Lopuksi tiedosto suljetaan fclose-lauseella.
Tiedostosta voidaan lukea merkkejä fscanf-funktiolla. Sen syntaksi on muuten sama kuin scanf-funtkiolla, mutta ensimmäinen parametri on tiedosto osoitin. Esimerkiksi kaksi lausetta, joista ensimmäinen lukee ruudulta annetun luvun ja jälkimmäinen tiedostosta
scanf("%d", &age); fscanf(filepointer, "%d", &age);Tiedostota voidaan lukea myös fgetc ja fgets funktioilla. Niistä on esimerkkejä alla.
Tiedostoon voidaan kirjoittaa fprintf-funktiolla. Sen syntaksi on muuten sama kuin printf-funktiolla, mutta ensimmäinen parametri on tiedosto osoitin. Esimerkiksi kaksi lausetta, joista ensimmäinen tulostaa ruudulle ja jälkimmäinen tiedostoon
printf("Opiskelijan nimi on %s ja ika on %d",name, age); fprintf(filepointer,"Opiskelijan nimi on %s ja ika on %d",name, age);Tiedostoon voidaan kirjoittaa yksi merkki funktiolla fputc. Esimerkiksi näin
char x='a'; fputc(x, filepointer);
Esimerkiksi seuraava sovellus kirjoittaa tiedostoon rivin "Hello world"
int main() { FILE *filepointer; filepointer=fopen("C:/temp/test2.txt","w"); fprintf(filepointer,"Hello world\n"); fclose(filepointer); return 0; }Huomaa, että tiedostopolussa on käytetty kauttaviivaa(/) ei kenoviivaa(\). Kenoviivaa ei voida käyttää, koska sillä on esityismerkityksiä C-kielessä kuten
"C:\\temp\\test2.txt"
Seuraavassa esimerkissä luetaan tiedostoa merkki kerrallaan ja tulostetaan merkit ruudulle
#include <stdio.h> #include <stdlib.h> int main() { FILE *filepointer; char ch; filepointer=fopen("C:/temp/test2.txt","r"); if (filepointer == NULL) { perror("Error while opening the file.\n"); exit(EXIT_FAILURE); } printf("The contents of file is:\n"); while((ch = fgetc(filepointer)) != EOF) printf("%c", ch); fclose(filepointer); return 0; }Esimerkissä if-lauseella tarkistetaan, onko tiedosto olemassa. Jos ei ole niin lopetetaan, eikä yritetä jatkaa. while-loopilla merkkejä luetaan, kunnes saavutetaan tiedoston loppu.
EOF ei ole tiedoston loppumerkki, vaan funktio fgetc palauttaa EOF arvon luettuaan tiedoston loppumerkin.
Edellä siis lukemiseen käytettiin funktiota fgetc, joka lukee yhden merkin. Lukemiseen voidaan käyttää myös funktioita
Seuraavassa esimerkissä luetaan tiedostoa rivi kerrallaan ja tulostetaan sisältö ruudulle
char row[255]; while(!feof(filepointer)){ printf("%s",row); fgets(row, 255, filepointer); }Ja sama voidaan tehdä sana kerrallaan seuraavasti
char word[50]; fscanf(filepointer, "%s", &word); //luetaan ensimmäinen sana while(!feof(filepointer)){ printf("%s ",word); fscanf(filepointer, "%s", &word); }
Kun sovellus käännetään c-kääntäjän on tiedettävä kuinka paljon muistia sen tulee varata sovellukselle. Täten esimerkiksi kokonaisluku taulukon kokoa ei voida asettaa sovelluksen suorituksen aikana, jos ei käytetä dynaamista muistinvarausta.
Dynaaminen muistinvaraus voidaan tehdä mm. funktioiden malloc tai calloc avulla. Alla olevassa esimerkissä käytetään malloc-funktiota.
#include <stdio.h> #include <stdlib.h> int main() { int *array_pointer; int array_size; int sum=0; printf("Montako lukua haluat antaa?\n"); scanf("%d",&array_size); array_pointer = (int*)malloc(array_size * sizeof(int)); if(array_pointer == NULL){ printf("Taulukon luonti ei onnistu\n"); } else { for(int x=0; x<array_size; x++){ printf("Anna %d.luku\n",x+1); scanf("%d",&array_pointer[x]); sum+=array_pointer[x]; } printf("Annoit luvut\n"); for(int x=0; x<array_size; x++){ printf("%d ,",array_pointer[x]); } printf("\nAnnettujen lukujen summa=%d\n",sum); } return 0; }