C-kielen alkeisopas
C-kielen perusteet

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

  • Ensimmäinen rivi #include <stdio.h> on ns. esikääntäjän ohjauskäsky, joka kertoo että tiedosto stdio.h tarvitaan mukaan käännökseen
  • Rivi int main() { kertoo, että tästä alkaa main-niminen funktio, joka päättyy loppusulkuun }. Sana int kertoo, että main funktio palauttaa kokonaisluvun
  • Rivillä printf("Hello World!"); kutsutaan funktiota printf ja sille annetaan argumenttina merkkijono "Hello World!"
  • Rivillä return 0 palautetaan arvo nolla

Esimerkistä huomataan lisäksi, että C-kielessä jokainen lause päättyy puolipisteeseen!

Edellä tulikin monta termiä, joiden merkitys pitäisi selvittää. Eli siis termit:

  • funktio
  • funktion kutsu
  • funktion argumentti
  • funktion palautus
  • lause

Muuttujat

(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:

  • int jolloin voit tallentaa kokonaisluvun
  • char jolloin voit tallentaa merkin (kirjain tai numero tai erikoismerkki)
  • float jolloin voit tallentaa desimaaliluvun

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

Muuttujan luonti ja alustaminen

Muuttuja voidaan luoda esimerkiksi seuraavilla lauseilla

  • int age;
  • float weight;
  • double area;

Muuttujan alustamisella tarkoitetaan arvon sijoittamista muuttujaan sen luonnin yhteydessä. Esimerkiksi näin:

  • int age=22;

Muuttujan näkyvyysalue

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.

Muuttujan osoite

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

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.

printf

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

scanf

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

  • luettavan tiedon tietotyyppi
  • muistipaikan osoite, johon tieto tallennetaan

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

#include 
int 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

Kommentit

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:

  • Merkit // tarkoittavat, että näiden jälkeen loppuosa rivistä tulkitaan kommentiksi
  • Merkkien /* ja */ väliin jäävä osa tulkitaan kommentiksi

Funktiot

Tä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.

Funktion määrittäminen

Funktio muodostuu kahdesta osasta:

  • funktion otsikko
  • funktion runko
Funktion otsikossa määritetään:
  • funktion palautusarvon tyyppi
  • funktion nimi
  • funktiolle annettavat parametrit ja niiden tyypit

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ä
  • funktio palauttaa int-tyyppisen arvon
  • funktion nimi on sum
  • funktiolla on parametrit a ja b, molemmat int-tyyppiä

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.

Funktion kutsu

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.

main-funktio

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 prototyyppi

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

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;
}

Merkkijonot

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!

Merkkijonon lukeminen ja tulostaminen

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ä
  • Esimerkissä on luotu merkkitaulukko nimeltään fname, johon voidaan tallentaa 30 merkkiä
  • scanf-funktiossa ei merkkitaulukon edessä tarvita 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("%s",&fname);
  • scanf("%s",&fname[0]);

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);

Taulukot

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

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

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.

if-ehto

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.

Vertailuoperaattorit

Edellä mainittiin jo vertailuoperaattori == eli yhtäsuuruus. Vertailuoperaattorit ovat:

  • == yhtäsuuri
  • > suurempi kuin
  • < pienempi kuin
  • >= suurempi tai yhtäsuuri kuin
  • <= pienempi tai yhtäsuuri kuin
  • != eri suuri kuin

Boolean tyypin vertailu

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)

if-else

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
}

if-else lyhytversio

Jos if-else rakenteessa toimenpideosissa on vain yksi lauseke, voidaan käyttää lyhyttä versiota. Esimerkiksi seuraava if-else-rakenne

int result;
bool test=true;

if(test){
    result=10;
}
else {
    result=5;
}
voidaan kirjoittaa lyhyemmin näin
int result;
bool test=true;
    
result = (test) ?  10: 5;

if ...else if else ...

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;
}

switch-case
Edellinen esimerkki voidaan toteuttaa myös switch-case-rakenteella seuraavasti:
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"

Luokittelu if else -rakenteessa

Esimerkiksi on toteutettava seuraavanlainen luokittelurakenne c-kielellä:

  • Jos ikä on alle 13 tulostetaan "child"
  • Jos ikä on 13-19 tulostetaan "teenage"
  • Jos ikä on yli 19 tulostetaan "adult"
Voidaan käyttää seuraavaa c-koodia:
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

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ää.
  • Lausekkeella x=1 annetaan x:lle alkuarvoksi yksi
  • Lausekkeella x<=5 määritetään, että toimenpiteitä suoritetaan niin kauan kuin x on pienempi tai yhtäsuuri kuin 5
  • Lausekkeella x++ ilmoitetaan, että jokaisen toiston jälkeen muuttujan x arvoa kasvatetaan yhdellä
Toimenpideosassa lauseke 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;
}

Sisäkkäiset silmukat

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,

Operaattorit

Operaattorien avulla voidaan manipuloida muuttujien arvoja, kuten kasvattaa tai vähentää.

Matemaattiset operaattorit

Matemaattiset operaattorit ovat:

  • + yhteenlaskuoperaattori
  • - vähennyslaskuoperaattori
  • * kertolaskuoperaattori
  • / jakolaskuoperaattori
  • % jakojäännösoperaattori

Incrementointi- ja Decrementointi-operaattorit

Incrementointi tarkoittaa tässä yhteydessä muuttujan arvon kasvattamista yhdellä ja decrementointi taas vähentämistä yhdellä. C-kielessä nämä ovat

  • ++ incrementointi
  • -- decrementointi

Sijoitusoperaattorit

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

Osoittimet

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).

Osoittimen tarpeellisuus

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=5
Jos 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

Tietueet

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);

Tietue taulukko

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);
}

Tiedostot

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 Ja fopen-funktion palauttama arvo sijoitetaan luotuun tiedosto-osoittimeen seuraavasti
FILE *filepointer;
filepointer=fopen("C:/temp/test2.txt","w");

Lopuksi tiedosto suljetaan fclose-lauseella.

Tiedostosta lukeminen

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 kirjoittaminen

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
  • \n rivinvaihtomerkki
  • \t tabulointimerkki
Jos halutaan käyttää kenoviivaa, niitä tarvitaan kaksi eli "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

  • fscanf, joka lukee merkkijonoa kunnes löytyy välilyönti
  • fgets, joka lukee merkkijonoa, kunnes löytyy rivinvaihtomerkki
Eli siis fscanf-funktiolla voidaan lukea tiedostoa sana kerrallaan ja fgets-funktiolla rivi kerrallaan.

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);
}  

Dynaaminen muistinvaraus

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;
}



Toggle Menu