C-kieli‎ > ‎

Perusteita

C-kielen perusteita





Yleistä

Miltei kaikissa vähänkin monimutkaisimmissa laitteissa on toimintaa ohjaava logiikka, joka päättelee miten laitteen tulee toimia eri tilanteissa. Tätä logiikkaa sanotaan ohjelmaksi. Se on perättäisten ja haarautuvien toimintojen sarja, joka määrää laitteen käyttäytymisen. Laitteiden ohjaus voidaan totetuttaa näennäisesti monella eri tavalla, mutta taustalla on aina jonkin asteinen tietokone.  Kun tietokone ei näy käyttäjälle puhutaan sulautetusta ohjauksesta.

Ohjelma jakaantuu tyypillisesti pääohjelmaan ja funktioihin (pääohjelmakin on funktio). Ohjelma alkaa aina pääohjelman alusta ja kun pääohjelma päättyy, päättyy myös koko ohjelma. Pääohjelmasta kutsutaan aliohjelmia eli funktioita. Tyypillisesti aliohjelmat tekevät kaiken työn ja pääohjelma pitää ohjelman käynnissä. Yksittäinen aliohjelma ratkaisee jonkin probleeman. Aliohjelman tulisi olla kooltaan kohtuullisen lyhyt, jotta se olisi ymmäretettävissä.
                            
Ohjelmakokonaisuutta kutsutaan prosessiksi. Yksinkertaisimmillaan tietokoneessa on yksi prosessi, joka ohjaa esimerkiksi auton ABS-jarruja. Useimmiten tietokoneessa on kuitenkin useita prosesseja yhtä aikaa käynnissä. Esimerkiksi käyttöjärjestelmä sisältää monta erillistä ohjelmaa eli prosessia. Periaatteellisia lähestymistapoja ohjelmointiin on kaksi, joista ensimmäinen on proseduraalinen ja toinen olioita käyttävä tapa. Proseduraalisessa, eli toiminnollisessa tavassa tieto ja funktiot on eroteltu toisistaan ja ohjelman suunnittelu lähtee tehtävistä, joita ohjelman tulee tehdä. Olio-ohjelmoinnissa suunnitellaan ensin olioille mallit eli luokat. Olio on toiminnot ja tieto yhdessä. Luokkaa voisi verrata vaikkapa piparkakkumuottiin ja piparkakkua piparkakkumuotin luomaan olioon.

Samoin ihmisen DNA:ta voidaan ajatella luokkana, ja ihmistä DNA:n määrittelemänä oliona. Suurissa ohjelmissa olioperustainen lähestymistapa on yleistynyt viime vuosina ehkä siksi, että sillä on helppo mallintaa monimutkaisia tapahtumaohjattuja ongelmia.

Ohjelmoinnin tarve on viime vuosina kasvanut räjähdysmäisesti. Syynä tähän on osaltaan pc:n tuleminen jokaisen työkaluksi sekä toisaalta sulautettujen järjestelmien yleistyminen esimerkiksi kodinkoneissa, autoissa jne. Koneautomaatiossa vielä kymmenen vuotta siten riitti ohjelmoitavan logiikan ohjelmoinititaito. Tänään logiikkaohjelmat siirtyvät yhä useammin osaksi tietokoneohjausta ja liittyvät hyvinkin monimutkaiseen tietojärjestelmään. Tämänkaltaisten sovellusten käyttöönotto ja ohjelmointi vaativat hyvää käyttöjärjestelmä- ja ohjelmointiosaamista.


 Yksinkertaisia esimerkkejä motivaation herättämiseen


Yksinkertaisen C-ohjelman tekeminen on suhteellisen helppoa. Alla olevan ohjelman voi tehdä Windows ympäristössä esimerkiksi VisualC:llä. 

#include <stdio.h> //sisällytetään ohjelmaan standardi input output tiedostossa esitellyt funktiot esim. printf
#include <conio.h> //

int main()//pääohjelma alkaa tästä
{
    printf("Eka ohjelma");//tulostettan tekstiä ruudulle
    getch();//odotetaan näppäimen painallusta
    return 1;//palautetaan ohjelmasta arvo 1
}//pääohjelma loppuu


Toisessa ohjelmassa kommunikoidaan jo käyttäjän kanssa.

#include <stdio.h>
#include <conio.h>

int main()
{
    char JokinMerkki;//esitellään muuttuja, joka voi tallettaa näppäimisöltä annettavan merkin
    printf("anna jokin merkki: ");
    JokinMerkki=getch();//kysytään merkki näppäimistöltä ja talletetaan se muuttujaan
    printf("Antamasi merkki oli %c",JokinMerkki );//tulostetaan annettu merkki %c:n osoittamaan kohtaan
    getch();
    return 1;
}


Kolmannessa esimerkissä käytämme jo kahta aliohjelmaa ja julkista muuttujaa.

#include <stdio.h>
#include <conio.h>

char JokinMerkki;//esitellään muuttuja, nyt julkisena, jotta kaikki funktiot voivat käyttää sitä

void KysyMerkki()
{
    printf("anna jokin merkki: ");
    JokinMerkki=getch();//kysytään merkki näppäimistöltä ja talletetaan se muuttujaan
}

void TulostaMerkki()
{
        printf("Antamasi merkki oli %c",JokinMerkki );//tulostetaan annettu merkki %c:n osoittamaan kohtaan
}

int main()
{
    KysyMerkki();
    TulostaMerkki();   
    getch();
    return 1;
}



Neljännessä esimerkissä esittelemme muuttujan paikallisena, jolloin muuttuja näkyy vain sen funktion sisällä, jossa se on esitelty. Lisäksi opettelemme return lauseen  käyttöä sekä arvoparametrien lähetystä funktiokutsun mukana.

#include <stdio.h>
#include <conio.h>

char KysyMerkki()//funktion tyyppi on nyt char ja funktio paluttaa nyt kutsujalle näppäimistöltä annetun merkin
{
    printf("anna jokin merkki: ");
    return getch();//kysytään merkki näppäimistöltä ja palutetaan se funktiosta return lauseella
}

void TulostaMerkki(char Merkki)
{
        printf("Antamasi merkki oli %c",Merkki );//tulostetaan annettu merkki %c:n osoittamaan kohtaan
}

int main()
{
    char JokinMerkki;//esitellään muuttuja, nyt paikallisena, nyt muutuja näkyy vain tässä funktiossa
    JokinMerkki=KysyMerkki();//funktio palauttaa arvonsa muuttujaan JokinMerkki
    TulostaMerkki(JokinMerkki);//Funktio saa parametrinaan JokinMerkki muuttujan arvon   
    getch();
    return 1;
}

Toivottavasti edellä olevat esimerkit auttoivat herättämään kipinän opetella lisää C-kieltä.

C-kielen varatut sanat

C-kielessä on varattuja sanoja, joita voi käyttää ainoastaan niiden omaan tarkoitukseen.

auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while

Näihin termeihin palataan tekstissä myöhemmin.



Muuttujat


Muuttujat tallettavat tiedot, joita ohjelma käsittelee. Tieto voi olla lukuja ja merkkejä, tieto voi olla esitetty yksittäinä arvoina, vektoreina tai tulukkomuodossa. Muuttujat ovat siis tietovarastoja, tietovarastot ovat erilaiselle tiedolle erikokoisia. Siksi C-kielessä tiedolla on aina tyyppi, tyyppi kertoo muuttujan tarvitseman muistin määrän. Tietokoneen muistia kuvataan tavuilla. Tavun pituus on kahdeksan bittiä. Kahdeksalla bitillä voidaan kuvata luvut 0...255 tai -128...+127. Esimerkiksi ASCII-taulukossa, joka sisältää tietokoneen esittämät merkit, jokainen kirjoitettava merkki edustaa lukua 0...255. Täten yksi merkki varaa tietokoneelta muistia yhden tavun. (UNI-koodissa merkkit esitetään  kahdella tavulla, jollin voidaan esittää 65535, jolloin miltei kaikki maailman merkit saadaan esitettyä ilman erillisiä kooditaulukoita)

C-kielessä muuttujilla voi olla seuraava tyyppi:
    •  char       = merkki
    •  int          = kokonaisluku
    •  float       = reaaliluku
    •  double   = kaksoistarkkuuden reaaliluku
    •  void       = tavuosoitin, (alussa voidaan ajatella, että tämä ei tarkoita mitään)

Muuttujan tyyppi kertoo sen, millaista tietoa muuttujaan voi tallentaa, esimerkiksi char OmaMuuttuja voi tallettaa yhden merkin.

Eli muuttujaan OmaMuuttuja voidaan sijoittaa yksi merkki, esimerkiksi,

OmaMuuttuja = ’a’; //huom! yksittäiset merkit laitetaan heittomerkkien väliin.

Toinen esimerkki.

int iLuku; // kokonaislukumuuttuja, joka voi tallettaa 16-bittisen etumerkillisen luvun 

Muuttuja voidaan haluttaessa myös alustaa esittelyn yhteydessä, esim.

int Luku=5;
char Merkki='a';

Muuttujalle voidaan antaa lisämääreitä:
   •  signed = etumerkillinen
   •  unsigned = etumerkitön
   •  long = pitkä
   •  short = lyhyt
  
Esim.

unsigned int Luku = 65535; //Muuttuja voi olla ainoastaan positiivinen kokonaisluku

Lisämääreillä tarkennetaan tyyppiä. Lisämääreiden merkitys on kääntäjäkohtaista ja siksi ne on syytä tarkastaa ko. kääntäjän käsikirjoista.

Muuttujan nimeäminen


Muuttuja kannattaa nimetä selkeällä nimellä, joka kertoo muuttujan käyttötarkoituksen. Huom! isot ja pienet kirjaimet tarkoittavat eri asiaa.
Puhdas C-kielen esittelytapa on seuraava:

    int auton_nopeus;

Muuttujan eri sanat voidaan erotella  toisistaan esimerkiksi alaviivalla.

C++:ssa käytetään tyypillisesti tapaa, jossa muuttujassa olevat sanat aloitetaan isoilla kirjaimilla seuraavasti:

    int AutonNopeus;

Monet ohjelmoitsijat liittävät muuttujan eteen kirjaimen kuvaamaan muuttujan tyyppiä seuraavasti:

    int iAutonNopeus;

Tyypistä otetaan yksi kirjain muuttujan eteen muistuttamaan ohjelmoitsijaa muuttujalle annetusta tyypistä. (tätä sanotaan unkarilaiseksi notaatioksi)
Edellä mainitut muuttujien nimeystavat eivät vaikuta millään tapaa muuttujan käyttöön tai ohjelman toimintaan.

Muuttujan näkyvyysalue


Muuttuja voi olla käytettävissä ainoastaan yhdessä funktiossa (paikallinen), yhdessä tiedostossa tai koko ohjelmassa. Riippuen siitä missä kohtaa ohjelmaa muuttuja esitellään, sitä voidaan käyttää ohjelmassa eri tavoin.

Yhteinen eli public muuttuja


Yhteinen, globaali, public  muuttuja esitellään kaikkien funktioiden ulkopuolella ja se "näkyy" eli se on käytettävissä kaikissa funktioissa, jotka ovat samassa tiedostossa kuin muuttuja. Yhteistä muuttujaa on hyvin helppo käyttää, koska sen arvoon päästään käsiksi kaikista funktioista. Kun ohjelma kasvaa suureksi, siinä voi olla tuhansia muuttujia, jolloin yhteisten muuttujien käyttö tulee hankalaksi ja ohjelmasta tulee altis virheille.

Paikallinen eli local muuttuja


Paikallinen muuttuja esitellään funktion sisällä ja se on käytettävissä ainoastaan siinä funktiossa, jossa se on määritelty. Alkuarvokseen paikallinen muuttuja saa satunnaisarvon, siksi paikalliset muuttujat monesti alustetaan johonkin alkuarvoon esittelyn yhteydessä.

Paikallinen muuttuja katoaa (ja siten kadottaa arvonsa), kun funktio päättyy.

Paikallinen staattinen muuttuja


Jos halutaan paikallisen muuttujan muistavan arvonsa siitä, kun funktio suoritettiin edellisen kerran, täytyy muuttuja esitellä staattisena esim. seuraavasti:

    static int Laskuri;

Staattinen muuttuja saa automaattisesti alkuarvokseen nollan, joten sitä ei alusteta esittelyn yhteydessä. Staatisena esitellyllä muuttujalla voidaan esim. laskea, kuinka monta kertaa ko. funktiossa on käyty.

Yhteinen staattinen muuttuja


Haluttaessa rajata yhteisen muuttujan näkyvyys ainoastaan siihen tiedostoon, jossa se on esitelty, täytyy yhteinen muuttuja esitellä staattisena esim. seuraavasti:

    static long Summa;

Edellä oleva määrittely kannatta tehdä esim. silloin kun samaa ohjelmistoprojektia tekee useita henkilöitä ja halutaan estää samanimisten eri tiedostoissa olevien muuttujien vaikutus toisiinsa. Nämäkin muuttujat täytyy tietysti esitellä funktioiden ulkopuolella.



Funktio


Funktio on itsenäinen ohjelman osa, joka tekee jonkin halutun tehtävän, se voi esimerkiksi  laskea jonkin laskutoimituksen. Seuraavassa esimerkissä funktio laskee kartion tilavuuden, funktion alussa esitellään ne muuttujat (tietovarastot), joita ohjelma tarvitsee. Ohjelmassa on käytetty float määrittelyä muuttujan nimen edessä, float = reaaliluku (luku, jossa on desimaaliosa). Funktion täydellinen nimi koostuu kolmesta asiasta, paluuarvon tyyppistä, funktion nimestä ja parametreista.
 
Esim.
         void KartionTilavuus()
         {
          float Sade, Korkeus, Tilavuus; //esitellään muuttujat
          system(“cls”);//putsataan ruutu
          printf(“Lasken kartion tilavuuden”); //annetaan ohjeita käyttäjälle
          printf(“Anna pohjan säde: ”);
          scanf(“%f“,&Sade); //kysytään tietoa käyttäjältä
          printf(“Anna korkeus: “);
          scanf(“%f“,&Korkeus);
          Tilavuus=3.14159 * Sade*Sade*Korkeus/3; //lasketaan ja sijoitetaan
          printf(“\Tilavuus on %5.2f”,Tilavuus);
         }

Edellinen ohjelma laskee kartion tilavuuden ja tulostaa tiedot ruudulle, muttei pysty välittämään tietoa muille funktioille. Muuttujat Sade, Korkeus ja Tilavuus ovat tämän funktion
muuttujia eli tiedon säilömispaikkoja.

Funktion tehtävät
Funktioihin sisältyvät ohjelmassa tarvittavat toiminnot. Funktioon voidaan sen käynnistyessä viedä tietoa ja se voi päätyttyään palauttaa kutsujalleen tietoa. Tieto viedään parametrien avulla ja palautetaan return-käskyn perässä olevalla muuttujalla. Tietoa voidaan palauttaa joskus myös parametreissa.


        void main()//pääohjelma
        {
             KartionTilavuus(); //funktion kutsu
             getch();//jäädään odottamaan, että käyttäjä painaa jotain nappulaa
        }

Pääohjelmasta tai mistä tahansa funktiosta toinen funktio voidaan kutsua sen nimellä + () merkeillä.


Funktion parametrit


Funktioon voidaan sen käynnistyessä viedä tietoa ja se voi päätyttyä palauttaa kutsujalleen tietoa. Tieto viedään parametrien avulla ja palautetaan return-käskyn perässä olevalla muuttujalla. Tietoa voidaan palauttaa joskus myös parametreissa. Funktion muoto Funktion täydellinen nimi koostuu kolmesta asiasta, paluuarvon tyyppistä, funktion nimestä ja parametreista.


void TulostaMerkki(char Merkki) //funktion parametrina char tyyppinen muuttuja
{
        printf("Antamasi merkki oli %c",Merkki );//tulostetaan annettu merkki %c:n osoittamaan kohtaan
}

int main()
{
    char JokinMerkki='a';//esitellään muuttuja, nyt paikallisena, nyt muutuja näkyy vain tässä funktiossa
    TulostaMerkki(JokinMerkki); //Funktio saa parametrinaan JokinMerkki muuttujan arvon   
    getch();
    return 1;
}

Toinen esimerkki

       void LaskeYhteen(int Eka, int Toka)
       {
              int Summa=0;
              Summa=Eka + Toka;
              printf("\nsumma on %d", Summa); 
       }

        void main()//pääohjelma
        {
             int First=5, Second=6;
             LaskeYhteen(First,Second); //funktion kutsu, kutsussa ja itse funktiossa muuttujien nimet vovat olla erilaisia
             getch();//jäädään odottamaan, että käyttäjä painaa jotain nappulaa, ei toimi Linuxissa
        }

Edellä oleva funktio ottaa parametreikseen muuttujat Eka ja Toka eli Eka ja Toka vievät tietoa tälle funktiolle. Funktion laskema tulos näytetään ruudulla, muuta tämä funktio ei palauta mitään tietoa kutsujalle.


Funktion paluuarvo


Funktio voi päätyttyään palauttaa kutsujalleen tietoa. Tieto palautetaan return-käskyn perässä olevalla muuttujalla. Funktiossa voi olla useita return lauseita sopivissa kohdissa. Funktion nimen edessä oleva tyyppi määrää paluuarvon tyypin ja siksi return lauseen perässä olevan muuttujan tyypin pitää olla sama.

#include <stdio.h>
#include <conio.h>

char KysyMerkki()//funktion tyyppi on nyt char ja funktio paluttaa nyt kutsujalle näppäimistöltä annetun merkin
{
    printf("anna jokin merkki: ");
    return getch();//kysytään merkki näppäimistöltä ja palutetaan se funktiosta return lauseella
}

void TulostaMerkki(char Merkki) //funktion parametrina char tyyppinen muuttuja
{
        printf("Antamasi merkki oli %c",Merkki );//tulostetaan annettu merkki %c:n osoittamaan kohtaan
}

int main()
{
    char JokinMerkki;//esitellään muuttuja, nyt paikallisena, nyt muutuja näkyy vain tässä funktiossa
    JokinMerkki=KysyMerkki();//funktio palauttaa arvonsa muuttujaan JokinMerkki
    TulostaMerkki(JokinMerkki);//Funktio saa parametrinaan JokinMerkki muuttujan arvon   
    getch();
    return 1;
}


Toinen esimerkki

       int LaskeYhteen(int Eka, int Toka)
       {
              int Summa=0;
              Summa=Eka + Toka;
              return Summa; 
       }

        void main()//pääohjelma
        {
             int First=4, Second=9,Sum=0;
             Sum=LaskeYhteen(First,Second); //Sum nappaa paluuarvon = merkillä
             printf("\nsumma on %d", Sum); 
             getch();//jäädään odottamaan, että käyttäjä painaa jotain nappulaa
        }

Paluuarvona ei saa palauttaa esimerkiksi osoitteita paikallisiin muuttujiin, koska ne häviävät funktion päättymisen jälkeen. 



Funktio ja osoiteparametrit


Kun funktion pitää muuttaa muuttujan arvoa kutsuvassa ohjelmassa, pitää muuttujasta välittää sen osoite eikä muuttujan arvoa. 

       void LaskeYhteen(int Eka, int Toka, int *Summa)
       {
              *Summa = Eka  + Toka; //* summan edessä aiheuttaa sen ettei osiotteen arvoa muuteta, vaan osoittimen osoittaman muistipaikan sisältö
       }

        void main()//pääohjelma
        {
             int First=5, Second=8,Sum=0;
             LaskeYhteen(First, Second, &Sum); //välitetään yhteenlaskettavat arvoparametreina ja summa osoiteparametrina, jotta summa saadan muutettua nollasta haluttuun arvoon kutsuttavassa funktiossa
             printf("\nsumma on %d", Sum); 
             getch();
        }

Edellä kuvattu menetelmä vaatii jo hieman huolellisuutta. Koska funktion parametrit esitellään nyt muuttujina. Aina kun tyypin ja muuttujan nimen edessä on * merkki, tämä tarkoittaa, että muuttuja on osoite. Taas * pelkän muuttujan edessä merkitsee  siirtymistä muuttujan osoitteesta muuttujan sisältöön.  * voi tiestysti myös tarkoittaa pelkkää kertomista, onneksi kääntäjä osaa päätellä tämän.



Funktion esittely


Funktio esitellään ennen sen käyttöä. Funktion esittelyä sanotaan myös funktion prototyypiksi. Esittely tapahtuu seuraavasti:

        int LaskeYhteen(int Eka, int Toka);
tai
        int LaskeYhteen(int, int);

ja

       void LaskeYhteen(int Eka, int Toka, int *Summa);
tai
       void LaskeYhteen(int, int, int*);


Funktion esittelyn tarkoituksena on kertoa kääntäjälle (kääntäjä kääntää ohjelmoijan koodin koneen ymmärtämään muotoon) etukäteen millaisia funktioita ohjelmassa on, lisäksi kerrotaan miten funktiota voidaan kutsua, jotta kääntäjä voi tarkastaa, menikö kutsu oikein. Jos kutsu oli virheellinen, antaa kääntäjä siitä virheilmoituksen. Funktioiden esittelyt laitetaan ohjelman alkuun tai erillisiin otsikkotiedostoihin. Kuten edellä nähdään funktioiden parametrien nimiä ei välttämättä tarvita esittelyssä. Funktiot voidaan jättää myös esittelemättä, jos ne on lähdekoodissa ennen pääohjelmaa ja siinä järjestyksessä, että kutsuttava on lähdekoodissa ennen kutsujaa. Tätä tapaa ei kuitenkaan sositella kuin joissain sulautettujen laitteiden ohjelmoinnissa.



Merkkijono funktion parametrina


Funktio ei voi palauttaa merkkijonoja eikä taulukoita, vaan näissä tapuksissa palautetaan aina osoite. Lisäksi osoitteen täyttyy olla kutsuvan funktion varaama, koska kutsuttavan funktion muuttujavaraukset häviävät funktoista palataessa.

        void AnnaNimesi(char OmaNimi[]);
tai
       void AnnaNimesi(char*); //tästä voisi päätellä että kyseessä onkin osoite

       void AnnaNimesi(char OmaNimi[]) //itse asiassa tässä välitetään osoite merskkijonoon
       {
              printf("Anna nimesi: ");
              gets(OmaNimi); // tässäkin välitetään merkkijonon osoite
       }

        void main()//pääohjelma
        {
             char Nimi[100];
             AnnaNimesi(Nimi);//ei tarvita &-merkkiä, koska Nimi on osoite merkkitaulukkoon Nimi[100]
             printf("\nnimi on %s", Nimi); 
             getch();
        }

   Huomaa, että kutsuttava funktio EI määrää merkkijonon pituutta, ainoastaan kutsuja muuttujan esittelyssä!!!



 

Vektori funktion parametrina


Vektori on yksiuloitteinen taulukko. Vektori voi sisältää alkioinaan kaikkia C:n tyyppejä.  Funktio ei voi palauttaa, vaan ainoastaan osoitteen vektorin. Lisäksi osoitteen täyttyy olla kutsuvan funktion varaama, koska kutsuttavan funktion muuttujavaraukset häviävät funktoista palataessa.

void Tayta(int* taulu);//esitellä funktio, joka ottaa parametrikseen osoitteen kokonaislukuun tai kokonaislukuvektoriin

int main()
{
    int Taulu[50];
    Tayta(Taulu);
    getch();
    return 1;
}

void Tayta(int* Vektori)
{
   
    int koko=sizeof(Vektori); //selvitetään vektorin koko tavuina
    int IntinKoko=sizeof(int); //selvitetään int muuttujankoko tavuina
    int JasentenMaara=koko/IntinKoko; //lasketaan vektorin alkioiden määrä
    for(int i=0;i<JasentenMaara;i++)Vektori[i]=5; // tehdään miltei mahdoton sijoitusoperaatio
}



Taulukko funktion parametrina


Taulukkossa on enemmän kuin yksi ulottuvuus. Taulukko voi sisältää alkioinaan kaikkia C:n tyyppejä.  Funktio ei voi palauttaa koko taulukkoa, vaan ainoastaan osoitteen. Lisäksi osoitteen täyttyy olla kutsuvan funktion varaama, koska kutsuttavan funktion muuttujavaraukset häviävät funktiosta palattaessa. Ensimmäisen kokoparametrin voi halutessaan jättää kutsusta pois kts. alla


void Tayta(int taulu[][10],int);

int main()
{
    int rivit=5;
    int Taulu[5][10];
    Tayta(Taulu,rivit);
    return 1;
}

void Tayta(int Taulu[][10],int rivit)
{
    int i,j;
    int sarakkeenkoko=sizeof(*Taulu); //selvitetään vektorin koko tavuina
    int IntinKoko=sizeof(int); //selvitetään int muuttujankoko tavuina
    int JasenetRivilla=sarakkeenkoko/IntinKoko; //lasketaan vektorin alkioiden määrä
    for(j=0;j<rivit; j++)
        for(i=0;i<JasenetRivilla;i++)
            Taulu[j][i]=5;// tehdään miltei mahdoton sijoitusoperaatio
}




Matemaattisia operaattoreita


Perusoperaattorit


Suurin osa kaikista matemaattisista toiminnoista on ohjelmakirjastoissa. C-kieleen kuuluvat kuitenkin aritmeettiset perusoperaattorit, jotka ovat seuraavat:
    • - vähennyslasku esim. x = a-3,
    • - unaari vähennys x-=3 (x:stä otetaan pois 3)
    • + yhteenlasku, esim x=b+2
    • + unaari summaus x+=5 (X:ään lisätään 5)
    • * kertolasku
    • / jakolasku
    • % jakolaskun jakojäännös
    • -- dekrementointi eli vähennys yhdellä, esim x-- (x:stä pois 1)
    • ++ inkrementointi eli lisäys yhdellä

Inkrementointi ja dekrementointi merkit voidaan laittaa muuttujan eteen tai sen jälkeen. Jos lisäys on for- tai while silmukan sisällä, tehdään ennen olevilla merkeillä testaus ennen silmukkaa ja ja jälkeen olevilla merkeillä silmukan suorituksen jälkeen. x=x+1; on sama kuin ++x;jax=x-1; on sama kuin x--;
+ ja – operaattoreita voidaan käyttää unaarioperaattoreina seuraavaan tapaan:

Luku+=5; vastaa samaa kuin Luku=Luku+5;



Bittioperaattorit


Yksi tärkeimmistä C-kielen ominaisuuksista on kyky käsitellä tavussa tai sanassa olevia bittejä yksitellen. C-kielen on suunniteltu ottavan assembly-kielen paikan useimmissa yhteyksissä ja siksi sillä on kyettävä tekemään myös hyvin koneläheistä ohjelmaa. Bitti-operaattoreilla testataan, asetetaan, siirretään bittejä tavuissa tai sanoissa, jotka ovat joko char- tai int-tyyppiä tai niiden variantteja:
  • & ja 
  • | tai
  • ^ ehdoton tai
  • >> siirto oikealle
  • << siirto vasemmalle


Bittioperaatiot kahdeksanbittisellä etumerkittömällä luvulla

TRUE true on muu kuin nolla, FALSE false  on nolla

Ja, And = & ,   Tai, Or =  | ,     Xor =   ^ ,  Negaatio = ~   ,    Shifting left,  Siirto vasemmalle  =  <<  ,    Shifting right,  Siirto oikealle =  >>

JA, AND, operaattori = &  ,  esimerkkejä 8 bittisilla luvuilla, b luvun perässä tarkoittaa sanaa binaari
1. esimerkki,  a = 255 & 15  =  1111 1111b & 0000 1111b  =  0000 1111b = 15  = 0xE  = 017
2. esimerkki   a = 255 & 1    =  1111 1111b & 0000 0001b  =  0000 0001b = 1    = 0x1   = 01
3. esimerkki   a = 2 & 1        =  0000 0010b & 0000 0001b  =  0000 0000b = 0    = 0x1   = 00
4. esimerkki   a = 3 & 1        =  0000 0011b & 0000 0001b  =  0000 0001b = 1
5. esimerkki   a = 3 & 2        =  0000 0011b & 0000 0010b  =  0000 0010b = 2

TAI , OR, operaattori |
1. esimerkki,  a = 0 | 1          =  0000 0000b | 0000 0001b  = 0000 0001b  = 1
2. esimerkki,  a = 2 | 1          =  0000 0010b | 0000 0001b  = 0000 0011b  = 3
3. esimerkki,  a = 4 | 8          =  0000 0100b | 0000 1000b  = 0000 1100b  = 12  =0 xC = 014
4. esimerkki,  a = 4 | 12        =  0000 0100b | 0000 1100b  = 0000 1100b  = 12

Ehdoton TAI , XOR, operaattori ^
1. esimerkki,  a = 0 ^ 1          =  0000 0000b ^ 0000 0001b  = 0000 0001b  = 1
2. esimerkki,  a = 1 ^ 1          =  0000 0001b ^ 0000 0001b  = 0000 0000b  = 0
3. esimerkki,  a = 2 ^ 1          =  0000 0010b ^ 0000 0001b  = 0000 0011b  = 3
4. esimerkki,  a = 3^ 1          =   0000 0011b ^ 0000 0001b  = 0000 0000b  = 2
5. esimerkki,  a = 4 ^ 8          =  0000 0100b ^ 0000 1000b  = 0000 1100b  = 12  =0 xC = 014
6. esimerkki,  a = 4 ^ 12        =  0000 0100b ^ 0000 1100b  = 0000 1000b  = 8

Negaatio, operaattori ~,  Negaatio-operaattori kääntää kaikki muuttujan bitit päinvastaiseksi 
1.esimerkki,  a = 0  = 0000 0000;  operaatio a = ~a; nyt a= 1111 1111; lopputulos a = 255 = 0xFF
2.esimerkki   a = 1  = 0000 0001;  operaatio a =~a; nyt a=  1111 1110; lopputulos a = 254 =  0xFE
3.esimerkki   a = 12= 0000 1100;  operaatio a = ~a; nyt a= 1111 0011; lopputulos a = 243 = 0xF3
4.esimerkki   a = 15= 0000 1111;  operaatio a = ~a; nyt a= 1111 0000; lopputulos a = 240 = 0xF0

Siirto vasemmalle  Shifting left, operaattori <<
1. esimerkki  a=1=     0000 0001,  operaatio a = a<<1 ; nyt a= 0000 0010 ; lopputulos a = 2
2. esimerkki  a=128= 1000 0000,  operaatio a = a<<1 ; nyt a= 0000 0000 ; lopputulos a = 0
3. esimerkki  a=0 =    0000 0000,  operaatio a = a<<1 ; nyt a= 0000 0000 ; lopputulos a = 0

Siirto oikealle,  Shifting right, operaattori >>
1. esimerkki  a=1=     0000 0001,  operaatio a = a>>1 ; nyt a= 0000 0000 ; lopputulos a = 0
2. esimerkki  a=128= 1000 0000,  operaatio a = a>>1 ; nyt a= 0100 0000 ; lopputulos a = 64=0x70
3. esimerkki  a=0 =    0000 0000,  operaatio a = a>>1 ; nyt a= 0000 0000 ; lopputulos a = 0



Ehtorakenteet


Loogisilla- ja vertailuoperaatioilla testataan muuttujan tai muuttujien arvoja. Ehto saa arvokseen tosi tai epätosi.

Esim.

if (a<b) printf ("a oli pienempi kuin b");
else printf ("a oli yhtäsuuri tai suurempi kuin b");

Testi voi olla myös kolmiosainen.

if (a<b) printf ("a oli pienempi kuin b");
else if (a==b) printf ("a oli yhtäsuuri kuin b");
else printf ("a suurempi kuin b");
   



Vertailu loogisten operaatioiden avulla


Loogisilla operaatioilla saadaan informaatio siitä onko muuttuja nolla tai ei, lisäksi voidaan testata muuttujien yhtä- ja erisuuruutta.

1. muuttuja erisuuri kuin nolla

if(a) printf("a ei ole nolla");
else  printf("a on nolla");


2. muuttuja on nolla

if(!a) printf("a on nolla");
else  printf("a ei ole nolla");

3. muuttuja a tai muuttuja b on tosi (erisuuri kuin nolla), myös molemmat voivat olla tosia.

if(a||b) printf("muuttuja a tai muuttuja b on tosi (erisuuri kuin nolla), myös molemmat voivat olla tosia");
else  printf("molemmat muuttujat ovat epätosia");

4. muuttuja a ja muuttuja b on tosi.

if(a&&b) printf("muuttuja a ja muuttuja b on tosi (erisuuri kuin nolla)");
else  printf("molemmat muuttujat ovat epätosia");




Vertailuoperaattorit

Operaattoreilla verrataan muuttujia keskenään.


1. muuttujat a ja b ovat yhtäsuuria.

if(a==b) printf("a ja b ovat yhtäsuuria");
else  printf("a ja b ovat erisuuria");

2. muuttujat a ja b ovat erisuuria.

if(a!=b) printf("a ja b ovat erisuuria");
else  printf("a ja b ovat yhtäsuuria");

3. muuttuja a on pienempi kuin b.

if(a<b) printf("a ja b ovat erisuuria");
else  printf("a ja b ovat yhtäsuuria");

4. muuttuja a on suurempi kuin b.

if(a>b) printf("muuttuja a on suurempi kuin b");
else  printf("muuttuja a on suurempi kuin b");

5. muuttuja a on pienempi tai yhtäsuuri kuin b.

if(a<=b) printf("muuttuja a on pienempi tai yhtäsuuri kuin b");
else  printf("muuttuja a on pienempi tai yhtäsuuri kuin b");

6. muuttujat a on suurempi tai yhtäsuuri kuin b.

if(a>=b) printf("muuttujat a on suurempi tai yhtäsuuri kuin b");
else  printf("muuttujat a on suurempi tai yhtäsuuri kuin b");

 if  valinta voidaan tehdä monen vaihtoehdon välillä:

       if(ehto1) lauseke1;
       else if (ehto2) lauseke2;
       else if (ehto3) lauseke3;
       else lauseke4; //tämä toteutetaan jos mikään ehto ei ole tosi

Lauseke1 voi koostua yhdestä käskystä tai {} sulkujen sisällä olevasta ohjelmalohkosta  (sulkulauseke voi olla myös tyhjä).
Alistettuja if-lauseita käytetään usein tehokkaan ohjelmoinnin yhteydessä:

         if(i)
       {
                if(j) lauseke1;
                if(k) lauseke2;
                else lauseke3;
       }
       else lauseke4;

Toistorakenteet


C-kielessä on kolme toistorakenetta for, do while ja while for-toistorakenne on ehkä selväpiirteisin, for-sanan jälkeen tulee kaikki silmukan toimintaan
liittyvät määrittelyt.

         for(alkuarvo; silmukassa olo ehto ; lisäys toistokertojen välillä)
         {
                 toiminnot silmukan sisällä;
         }

Esim. Seuraavassa for-silmukassa tulostetaan i:n arvo 0...9

         int i;
         for(i=0;i<10;i++)
         {
                printf("\ni = %d",i);
         }

do-while- tostorakennetta käytetään silloin, kun silmukkaan täytyy mennä kerran, vaikka silmukassa oloehto ei olisikaan tosi. do-while-rakennetta sanotaan loppuehtoiseksi while-silmukaksi

         do
        {
                toiminnot silmukan sisällä;
        }while(silmukassa olo ehto);

Esimerkki
        do
       {
            printf("\ni = %d",i);
            i++;
       }while(i<10);


        while(silmukassa olo ehto)
      {
             toiminnot silmukan sisällä;
      }

Esimerkki
        while(i<10)
      {
             printf("\ni = %d",i);
             i++;
      }

Seuraavassa on esimerkki ikuisen silmukan ja if sekä else- lauseiden käytöstä. Ikuista silmukkaa tarvitaan hyvin usein ohjelmassa, kun ennalta ei tiedetä koska ohjelmasta tulisi poistua. Tyypillisesti ikuisesta silmukasta poistutaan break tai return käskyillä.

        /*Ohjelma, joka tulostaa millä välillä annettu luku on.
         Lukuvälit ovat <10, 10-100 ja >100.
         Käytetään ohjelmassa if else lauseita
        */
        #include<stdio.h>
        #include<conio.h>
        void main()
        {
             int Luku;
             while(1)//"ikuinen silmukka"
             {
                     printf("\nAnna jokin luku: ");
                     scanf("%d",&Luku);
                     if(Luku < 10) printf("Luku on < 10");
                     else if(Luku >= 10 && Luku <= 100)
                     printf("Luku on 10...100");
                     else printf("Luku on > 100");
                     if(Luku==500) break;/*poistutaan silmukasta, kun käyttäjä antaa luvun 500*/
             }
        }


Haarautumisrakenteet switch - case - default

switch on eräänlainen kytkin, jonka avulla haaraudutaan oikeaan case-haaraan, kun switchin jälkeen suluissa oleva lauseke saa jonkin case-lauseen jälkeisen arvon. Jos mikään ei matchaa, suoritetaan dafaultin jälkeinen lauseke.

       switch(lauseke)
      {
             case arvo_1: lausekkeita; break;
             case arvo_2: lausekkeita; break;
                     ...
             case arvo_n: lausekkeita; break;
             default: lausekkeita; break;
      }

Esimerkki

      void main();
      void Valikko(void);//piirretään käyttäjälle ohjeet
      void Funktio1(void);
      void Funktio2(void);

      void main()
       {
        unsigned int Valitsin = 0;
        while (1)
         {
               valikko();
               Valitsin=getch();
               switch (Valitsin)
               {
                case '0': return;
                case '1': Funktio1(); break;
                case '2': Funktio2(); break;
                default : break;
               }
         }
       }

       void Funktio1()
       {
               system("cls");
               printf("\n Funktio 1 PAINA JOTAIN");
               getch();
       }

       void Funktio2()
       {
               system("cls");
               printf("\n Funktio 2 PAINA JOTAIN");
               getch();
       }

       void valikko()
       {
               system("cls");
               printf("\n0........Lopetus");
               printf("\n1........Funktio 1");
               printf("\n2........Funktio 2");
       }


Vektori

Vektori on yksiuloitteinen taulukko.  Vektorissa kaikkien jsenten tyyppi on sama. Vektori esitellään esimerkiksi seuraavasti:

    int lukuja[100];

tai

    int lukuja[100]={0}; // nyt kaikille alkioille annetaan alkuarvoksi 0

Vektorin alkioiden tyyppi voi olla mikä tahansa C:n tyypeistä.

    char Merkkijoono[100]; // merkkijonoon voidaan laittaa 99 merkkiä ja lopetusmerkki

tai

    char Merkkijono[]="sdfadfasd";//nyt varataan merkkien määrä +1 nollamerkkiä varten joka lopettaa merkkijonon


Taulukkomuuttujat

C-kieli käsittelee taulukoita indeksien avulla. Indeksointi alkaa nollasta. C-kieli ei sisällä taulukon ylivuodon tarkastusta vaan se on ohjelmoitsijan tehtävä. Taulukon kaikki alkiot ovat samaa tyyppiä keskenään. Taulukko esitellään seuraavasti:

        int OmaTaulukko[5][4];

Voidaan ymmärtää että ensimmäinen on rivi-indeksi ja toinen on sarakeindeksi. Huomaa, että ensimmäinen indeksi on välillä 0...4 ja toinen 0...3. Taulukon haluttuun paikkaan päästään indeksien avulla seuraavasti:

        int Arvo;
        Arvo= OmaTaulukko[1][2];

Pelkkä taulukon nimi on osoitin taulukon alkuun. Eli kaksi seuraavaa tapaa osoittaa samaan alkioon:

        Arvo=*OmaTaulukko;
tai
        Arvo= OmaTaulukko[0][0];

Haluttaessa osoittaa toista taulukon alkiota, voidaan se tehdä seuraavasti:

      int *Osoitin;
      Osoitin= OmaTaulukko;
      Osoitin++;//lisätään osoitetta yhdellä
      Arvo=*Osoitin;

Osoitin ”juoksee” taulukon läpi rivi kerrallaan.

Merkkijonot

Merkkijono on vektori eli yksiulotteinen taulukko. C-kielessä ei merkkijonoille ole valmista muuttujaa, vaan merkkijonot on esiteltävä vektoreina seuraavasti:

         char Sukunimi[100];

Merkkijono on tyyppiä char ja merkkijonon perään laitetaan hakasulkuihin merkkien suurin sallittu määrä +1. Viimeinen merkkijonon merkki on varattu lopetusmerkille \0, jonka ascii arvo on nolla, siitä kääntäjä tietää, mihin merkkijono loppuu. Merkkijonon esittelyssä muistista varattu alue ei tyhjene vaan sinne jää aikaisempi sisältö. Edellä olevassa esimerkissä on varattu tila kymmenelle merkille ja varattuun tilaan on kopioitu kirjaimet abcdef sekä loppumerkki, loput merkit muistissa aikaisemmin ollutta tietoa.

Merkkijono voidaan alustaa esittelyn yhteydessä seuraavasti, esim.

         char Sukunimi[100]="Jokunen";
tai
         char Sukunimi[]="Jokunen";

Viimeisessä vaihtoehdossa merkkijonon maksimipituudeksi tulee 7+1. Kun merkkijono alustetaan esittelyn yhteydessä ilman hakasulkujen välissä olevaa lukua, kääntäjä laskee merkkijonon pituuden automaattisesti.



Merkkien ja merkkijonojen käsittelyfunktoita


Merkkojonoille pitää olla varattu riittävästi tilaa, koska ohjelma ei tarkasta mahtuuko kopioitava merkkijono merkkijonomuuttujaan. Merkkijonolle pitää varata merkkien vaatima tila plus yksi ylimääräinen merkki, jotta lopetusmerkki mahtuu mukaan.

getch
getch-funktiolla kysytään näppäimistöltä yksi merkki.
         char Merkki;
       Merkki=getch();
       printf(”merkki on %c ja sen ASCII-arvo on %d”,Merkki,Merkki);

Miksi edellinen tulostuslause toimii, koska kaikki merkit talletetaan merkkejä vastaavina ASCII-arvoina koneen muistiin ja ne voidaan tulostaa em. esimerkin mukaisesti merkkeinä  tai vastaavina ASCII-arvoina.

getch-funktolla saa myös ohjelman toiminnan pysätymään, kunnes käyttäjä painaa jotain näppäintä.

gets
gets-funktiolla kysytään näppäimistöltä merkkijono.
Esim.
         char Merkkeja[100];
       gets(Merkkeja);
       printf(”Kirjoitit %s”,Merkkeja);

gets-funktoissa on se ongelma, ettei se rajaa käyttäjän antamien merkkien määrää ja siten käyttäjä voi ”kaataa” koneen syöttämällä merkkejä muuttujaan enemmän kuin siihen mahtuu.

strcpy

strcpy-funktiolla kopioidaan merkkijonomuuttuja toiseen merkkijonomuuttujaan. Merkkojonolle pitää olla varattu riittävästi tilaa, koska ohjelma ei tarkasta mahtuuko kopioitava merkkijono merkkijonomuuttujaan. Merkkijonolle pitää varata yksi ylimääräinen merkki tilaa, jotta lopetusmerkki mahtuu mukaan.

         char jono1[100];
       char jono2[100] = ”asdfrt” ;
       strcpy(jono1,jono2);

Merkkiono jono2 kopioituu muuttujaan jono1.

strcat

strcat-funktiolla kopioidaan merkkijonomuuttuja toisen merkkijonomuuttujan jatkoksi.

        char jono1[100];
        char jono2[100] = ”asdfrt” ;
        strcpy(jono1,jono2);

Merkkiono jono2 kopioituu muuttujaan jono1 siellä olevan tiedon perään .

strncpy

strncpy-funktiolla kopioidaan merkkijonomuuttujasta määrätty määrä merkkejä toiseen
merkkijonomuuttujaan.
         char jono1[100];
       char jono2[100] = ”asdfrt” ;
       strcpy(jono1,jono2,5);
Merkkijonosta jono2 kopioituu 5 merkkiä muuttujaan jono1 .

strncat

strcat-funktiolla kopioidaan merkkijonomuuttujasta määrätty määrä merkkejä merkkijonomuuttujan jatkoksi.
         char jono1[100];
       char jono2[100] = ”asdfrt” ;
       strncat(jono1,jono2,5);

Merkkijonosta jono2 kopioituu 5 merkkiä muuttujan jono1 siellä olevan tiedon perään.


Tietue, struct


Tietue (struct) on erilaisten tietojen joukko, joka on yhdistetty yhden nimen alle. Tietueita käytetään tietokantojen yhteydessä, kun samaan tietojoukkoon täytyy tallettaa erityyppistä tietoa. Seuraavassa on esimerkki tietuemäärittelystä työntekijälle.
 
        struct TyontekijaTiedot
       {
             char Sukunimi[50];
             char Etunimi[50];
             char Sotu[15];
             int Palkka;
       };

TyontekijaTiedot tietuerakenne voidaan ottaa käyttöön seuraavasti:

          struct TyontekijaTiedot Tyontekija;

Nyt voidaan antaa työntekijälle nimi

          strcpy(Tyontekija.Sukunimi,”Jokunen”);

ja palkka

          Tyontekija.Palkka=15000;

Edellä olevassa tavassa on se ongelma, miten esitellä 1000 työntekijää? Seuraavassa kappaleessa on osittainen vastaus tähän.


Tietuetaulukot


Tietuetaulukon avulla voidaan esitellä monta tietuetta sisältävä tietokokonaisuus seuraavasti:

       struct Varastohyllyt
       {
             char Nimike[50];
             int Kolliluku;
       };
       void main(void)
       {
               int Paikka=0;
               struct Varastohyllyt Hylly[1000];
               strcpy(Hylly[Paikka].Nimike,”Kossu”);
               Hylly[Paikka].Kolliluku=10;
       }

Esitetty taulukkomäärittely soveltuu tapauksiin, joissa etukäteen tiedetään, kuinka monta
jäsentä taulukkoon kuuluu. Osoittimien avulla voidaan käsitellä tietuetaulukoita joustavam-
min, palaamme niihin myöhemmin.


Union

union on vaihtoehtorakenne, jolla voidaan vaihtoehtoisesti tallettaa tiettyyn tietueen kohtaan

vaihtoehtoisen tyyppistä tietoa,esim:
         union Luku// esitellään union:
      {
        int iArvo; //
        double dArvo; //
      };
         void JokuFunktio()
      {
        struct Luku Eka,Toka;
        int iLuku=5;
        double dLuku=10.45;
        Eka.iArvo=iLuku;
        Toka.dArvo=dLuku;
      ...
      }.

Union tietueen osana


Sellaisissa tapaksissa, joissa samaan tietueen kenttään halutaan tallettaa erityyppistä tietoa,
unionia käytetään tietueen osana.

      struct TyontekTiedot
      {
              char sSukunimi[50];
              char sEtunimi[50];
              union ID
              {
                      char sSotu[15];
                      int HenkiloID;
              };
              int Palkka;
      }






Osoitinmuuttujat ja muuttujan osoite


Muuttuja voi sisältää arvon tai osoitteen. Edellä kuvatut muuttujat sisälsivät kukin yhden arvon. Kun tietoa täytyy välittää funktioille suuri määrä yhdellä kertaa tai, kun tiedonkulun on oltava kaksisuuntaista, täytyy tieto välittää osoitteen avulla. Esim. haluttaessa välittää taulukko toiselle funktiolle, annetaan tyypillisesti kutsuttavalle funktiolle tiedoksi paikka, missä kohdassa muistia taulukko sijaitsee, eikä kaikkia taulukon alkioita erikseen. Esim. kaikki merkkijonot välitetään funktioilta toiselle osoitteiden avulla. Osoitinmuuttuja esitellään seuraavasti:

          int* OsoitinKokonaisLukuun;
          float* OsoitinReaalilukuun;
          char* OsoitinMerkkiinTaiMerkkijonoon;
          double* OsoitinPitkaanReaalilukuun;

Tähti tyypin ja muuttujan nimen välissä kertoo, että kyseessä on osoitin. p kirjainta käytetään usein muuttujan edessä ilmoittamaan tätä (p = pointer = osoitin). Edellä esitellyillä osoitinmuuttujilla voidaan nyt osoittaa vastaavan tyypin muuttujiin. Osoittimen esittelyn jälkeen, osoitin on ns. ”villi” osoitin (wild pointer), koska se ei osoita vielä mihinkään. Tällaisen osoittimen käyttö on erittäin vaarallista. Monesti osoitin laitetaan osoittamaan ”ei minnekään” seuraavalla tavalla:

         int* pLuku=NULL;

Edellä esitetty esittely takaa sen ettei ko. osoitinta voi käyttää ennen kuin se on alustettu osoittamaan jotain muuttujaa esim. seuraavasti:

       int Luku;//esitellään muuttuja nimeltään Luku
       pLuku=&Luku;//Sijoitetaan muuttujan Luku osoite osoiteeseen pLuku

&-merkillä haetaan esitellyn muuttujan osoite.

Osoitetta vastaava muuttuja


Monessa tapauksessa tiedetään, missä tieto on eli tiedetään tiedon osoite. Esim. kaikki tietokoneeseen liitetyt laitteet ovat tietyissä osoitteissa. Osoitettavastaava muuttuja saadaan *

merkillä. esim.

        char Merkki;
       char* pSarjaportti=0x0238;//sarjaportti on liitetty osoitteeseen 0x238
       Merkki= *pSarjaportti;

Esim.
        int Eka=5;
       int *pLuku=NULL;
       int OmaLuku=0;
       pLuku = &Eka;//pLuku osoittamaan OmaLuku muuttujaa
       OmaLuku = *pLuku;//pLuku osoitteen osoittaman muistipaikan sisältö OmaLuku muuttujalle

Edellä olevassa esimerkissä muuttujaan Eka sijoitetaan arvo viisi, pLuku laitetaan osoittama-
an muuttujaa Eka. Seuraavaksi haetaan pLukua vastaava muuttuja *:llä ja sijoitetaan se mu-
uttujaan OmaLuku


Osoitin osoittaa muuttujan paikkaa muistissa. Osoitinmuuttuja on erityinen muuttuja, joka
sisältää tiettyyn tyyppiin määritellyn osoittimen. Osoittimilla on C-kielessä kolme tärkeää tehtävää: niillä voidaan osoittaa nopeasti vektorin tai taulukon elementteihin, ne mahdollistavat kaksisuuntaisen tiedon siirron funktioiden välillä ja kolmantena ne tukevat linkattuja listoja ja dynaamisia tietorakenteita.

Osoittimien oikea ymmärtäminen on erittäin tärkeää tehokkaan C-ohjelmoinnin kannalta. Osoittimet ovat yksi C:n vahvimmista, mutta myös vaarallisimmista piirteistä. Alustamaton osoitin saattaa aiheuttaa koko ohjelman romahtamisen ja mikä pahinta osoittimien aiheuttamia virheitä on erittäin vaikea löytää, koska ohjelman antamat virheilmoitukset ko. tapauksessa osoittavat tyypillisesti väärään paikkaan. Lisäksi ohjelma saattaa toimia pitkiäkin aikoja näennäisen virheettömästi.

         int Luku;
       int* pLuku;
       int** ppLuku;

   Osoitteen osoite           Osoite                    Muuttuja
   &&Luku                         &Luku                   Luku
   &pLuku                         pLuku                    *pLuku
   ppLuku                         *ppLuku                 **ppLuku

Yllä oleva taulukko esittää, kuinka voidaan siirtyä muuttujasta sen osoitteeseen ja päin vas-
toin.

Tiedonvälitys osoitteiden avulla


Esimerkki tiedonvälityksestä osoittimen avulla.

       //esittelyt
       void JokuFunktio(int*);
       void main();

       void main()
       {
       int Luku=5;
                     JokuFunktio(&Luku);
                     printf("Luvun arvo on %d",Luku);
                     getch();
       }
         void JokuFunktio(int* pMuuttuja)
       {
               *pMuuttuja = *pMuuttuja * 2;
       }

Tähdellä on kolme merkitystä:
* hakee osoitetta vastaavan muuttujan
* esittelee osoitinmuuttujan
* toimii myös kertomerkkinä

Osoittimen määrittäminen vektorille


Osoitin vektorille on vektorin nimi ilman indeksiä.
Esim.
        int p*;       /*kokonaislukuosoitin*/
      int testi[20]; /*20:n kokonailuvun vektori*/
      p = testi;     /*kokonaislukuvektorin ensimmäisen elementin osoitin sijoitetaan osoittimen p arvoksi*/

Edellenen käsky voidaan toteuttaa myös & merkillä
        testi = &testi[0];

Osoitinaritmetiikkaa


Osoittimiin voidaan kohdistaa ainoastaan kaksi laskutoimitusta, yhteen- ja vähennyslasku.
Laskenta on turvallisinta ++ ja -- operaattoreita käyttäen, jotta osoitin osoittaa aina muuttujan
alkuun.
        int *p;
        float *f;
        int lukuja[10];
        float flukuja[10];
        p=lukuja;//laitetaan kokonaislukuosoitin osoittamaan vektorin alkuun
        f=flukuja;
        p++;//siirrytään seuraavaan alkioon
        f++;

p:n arvo nousee laskutoimituksessa kahdella ja f:n arvo kahdeksalla(riippu ympäristöstä, tarkasta sizeof funktiolla lukualueiden koko), koska kokonaisluku on kaksi tavua ja liukuluku kahdeksan tavua pitkä.


Osoittimien vertailu


Osoittimia voidaan vertailla keskenään esim.

        if (p<q) printf("p osoittaa pienempään muistiosoitteeseen kuin q);

Osoitinvertailuja joudutaan käyttämään erityisesti tapauksissa, joissa tarvitaan pinoa. Tyypillisiä sovellutuksia ovat kääntäjät, tulkit ja taulukkolaskentaohjelmat. (eli ei tavallista)

Osoittimet ja vektorit


Osoittimien ja vektoreiden välillä on läheinen yhteys esim.
        char strMerkkiJono[80], *psrtMerkkiJono;
        psrtMerkkiJono = strMerkkiJono;
p1 asetettiin osoittamaan ensimmäistä vektorin elementtiä. Kun halutaan osoittaa neljättä el-
ementtiä, voidaan kirjoittaa:
        str[3]
tai
        *(p1+3)
Vektoreita voidaan osoittaa osoitinaritmetiikalla tai vektorin indeksien avulla. Osoitin arit-
metiikka on ohjelman kannalta nopeampi tapa.



void osoitin


void-tyyppinen osoitin on ns. geneerinen osoitin. Geneerinen osoitin voi osoittaa minkä tahansa tyypin muuttujaan. Esimerkiksi seuraavat ohjelmalauseet ovat sallittuja.

         char MerkkiJono[80];
         void* OsoitinMihintahansa=NULL;
         OsoitinMihintahansa=Merkkijono;

Geneerisen osoittimen inkrementointi ja degrementoinit muuttavat osoittimen asemaa yhden tavun verran. Geneerisiä osoittimia käytetään tyypillisesti laiteläheisessä ohjelmoinnissa.



 


Komentoriviargumentit

Komentoriviargumenteilla ohjataan käynnitettävän ohjelman toimintaa. Ohjelmalle voidaan antaa parametreja sen toiminnan muuttamiseksi.
    main(int argc, char** argv)
char** argv kertoo, että osoittimen osoite merkkiin, joka on merkkijono. Joskus edellinen kirjoitetaan myös char *argv[].  

Esimerkiksi ohjelma.exe voidaan käynnistää seuraavasti:

	ohjelma.exe -i 2 -g -x 3 4
jolloin ohjelma ottaa vastaan seuraavat arvot
	argc = 7

argv[0] = "ohjelma.exe"

argv[1] = "-i"

argv[2] = "2"

argv[3] = "-g"

argv[4] = "-x"

argv[5] = "3"

argv[6] = "4"
Kaikki argumentit ovat merkkijonoja.

#include < stdio.h>

main(int argc, char** argv)

{

int i;

printf("argc = %d\n", argc);

for (i = 0; i < argc; i++) printf("argv[%d] = \"%s\"\n", i, argv[i]);

}

Yleisesti käytetyt standardikirjastot

Kaikki C-kielen tarvitsemat funktiot on sijoitettu eri tiedostoihin, joiden pääte on .lib, kun eri kirjastofunktioita tarvitaan ohjelmassa, täytyy niiden otsikkotiedostot sijoittaa lähdekoodin alkuun käskyllä #include esimerkiksi seuraavasti   #include<stdio.h>
  • stdio.h

    Tärkein I/O- kirjasto
    • Esimerkkejä: 
      • stdin, stdout ja stderr ovat tiedostopointtereita näppäimistöltä lukemiseen sekä tekstiin ja virheilmoituksien tulostusta varten 
      • fgets merkkijonon lukemista varten
      • printf tulostusta varten
      •  perror virheilmoituksen tulostusta varten
  • stdlib.h

    • Yleiskirjasto
      • abs itseisarvo luvusta

      • atoi kokonaisluku merkkijonosta

  • string.h

    • Merkkijonon käsittelyyn
      • strlen mittaa merkkijonon pituuden

  • types.h

    • Kirjainten käsittelyyn
    • ei välttämättä tajua skandeja
      • isdigit testaa, onko kirjain numero

  • errno.h

    • Virhekoodeja
  • math.h

    • Matemaattisia funktioita
      • pow potenssi

      • sqrt neliöjuuri

  • time.h

    • Aikaan liittyviä funktioita
  • limits.h

    • Ympärisrökohtaisia vakioita, mm. muuttujien maksimi- ja minimikoot

Muistin varaukset keosta (heap)


Ohjelma varaa käyttöönsä muistia, jossa muuttujat ja ohjelma voivat toimia. Muuttujille varattu muisti jakaantuu kahteen osaan, pinoon (stack) ja kekoon (heap). Pinoon talletetaan paikalliset muuttujat sekä fuktiokutsujen parametrit ja funktioiden paluuosoitteet. Pinoa ohjaa pino-osoitin joka näyttää pinon päällimmäiseen tietoon. Pino toimii automaattisesti, eikä ohjelmoitsija ohjaa sitä ohjelman sisältä. Keko on toinen tietovarasto, johon säilötään yhteiset muuttujat sekä sieltä voidaan varata muistia dynaamisesti muutujien käyttöön. Ongelmana on se , että kaikki osoitukset kekoon täytyy tehdä osoitteiden avulla ja kaikki muisti on varattava ennen käyttöä. Lisäksi ohjelman on muistettava kaikki varauksensa, jottei muistia hukata. Käsiteltäessä pitkiä  merkkijonoja tai tietueita, kannataa niille varata erikseen muistialueet keosta. Otsikkotiedosto malloc.h sisältää muistin varaukseen eli allokointiin usbeita funktioita esim.:

    •  alloc
    •  calloc
    •  malloc
    •  free

Seuraavana on esimerkki _fmalloc allokointi-funktion käytöstä, joka varaa muistia toisesta 64 kb muistilohkosta.

Funktio varaa osoittimelle pTuotteet tilaa VuoronTiedot tietueen verran.

       if((pTuotteet = _fmalloc( 1 * sizeof (struct VuoronTiedot)))==NULL)
       {
                       printf("Allokointivirhe !!!");
                       exit(1);
       }

Funktio varaa osoittimelle OsSiirtoVektori tilaa 100 merkkiä.

       if(OsSiirtovektori = _fmalloc( 100 * sizeof(char))==NULL)
       {
              printf("Allokointivirhe !!!");
              exit(1);
       }


scanf

scanf-funktiolla voidaan syöttää tietoa ohjelman muuttujille scanf("tyypin muotoilu",muuttujan osoite,[ ]);

tyypin muotoilu
   • %c char=merkki
   • %s char[]=merkkijono
   • %d int=kokonaisluku
   • %i int =kokonaisluku
   • %f float=reaaliluku
   • %l long=pitkä kokonaisluku
   • %lf double=kaksoistarkkuuden reaaliluku
   • %u unsigned=etumerkitön

Huom! scanf ottaa aina parametrikseen muuttujan osoitteen ja siksi scanf funktion kanssa kannattaa olla hyvin varovainen, jos ohjelman käyttäjä antaa esimerkiksi lukua kysyttäessä kirjaimen, todennäköisesti ohjelma "kaatuu".

       int Luku;
       printf(”Anna jokin luku”);
       scanf(”%d”,&Luku);
       char Merkkeja[100];
       printf(”Anna merkkejä. max 99”);
       scanf(”%s”,Merkkeja);

Merkkeja muuttujan eteen ei laiteta &-merkkiä, koska se on jo osoite merkkijonoon. (Taulukon nimi on aina osoitin taulukon alkuun)

printf


printf-funktio on C:n perustulostuskäsky, sitä käytetään seuraavasti:

       char Juttu[]=”tulostan lukuja” ;
       int Luku=5;
       float Reaaliluku=45.7;
       printf(”\nJuttuja %s numeroita %d Reaalilukuja %f”,Juttu,Luku,Reaaliluku);

printf ja tulostuksen muotoilu
Koodimerkitys
\b siirto vasemmalle (back space)
\f arkin poisto (form feed)
\n uusi rivi (line feed)
\r telan palautus (carriage return)
\t vaakatabulaattori (horisontal tab)
\" lainausmerkki (double quote)
\' heittomerkki (single quote)
\0 null (null)
\\ vasemmalle vino viiva (Backslash)
\v pystysuora tapulointi (vertical tab)
\a hälytys (alert)
\N oktaalivakio (octal constant)
\xN heksades.vakio (hexadesimal constant)

Kaikki tulostusmuotoilut laitetaan lainausmerkien sisälle \-merkistä printf-käsky tietää, että kyseessä on muotoilu.

Seuraavassa on esimerkki, jossa on käytetty printf-käskyn eri muotoja sekä tyypinmuunnoksia jne.

         /*Ohjelma, joka tulostaa antamasi reaaliluvun
           kokonaislukuna, heksalukuna sekä oktaalilukuna. */
         #include<stdio.h>
         #include<conio.h>
         #include<stdlib.h>
         void main(void)
         {
              double Reaaliluku;
              int Kokonaisluku,Laskuri=0;
              char Merkkeja[100];
              char Merkki;
              //silmukka, jossa ollaan niin kauan kuin käyttäjä syöttää luvun 555
              while(Kokonaisluku != 555)
              {
                     system("cls");//putsataan ruutu
                     printf("Anna reaaliluku: ");
                     do
                     {//tieto näppikseltä
                       Merkki=getche();
                       //laitetaan Merkki, Merkkeja vektoriin
                       Merkkeja[Laskuri]=Merkki;
                       Laskuri=Laskuri+1;//lisätään paikkaa vektorissa
                     }while(Laskuri<8 && Merkki!=13);
                     //päätetään merkkijono
                     Merkkeja[Laskuri]=0;
                     Laskuri=0;
                     //muutetaan merkkijono reaaliluvuksi
                     Reaaliluku = atof(Merkkeja);
                     printf("\nReaaliluku on %.3f ",Reaaliluku);
                     //Muutetaan reaaliluku kokonaisluvuksi
                     Kokonaisluku=(int) Reaaliluku;
                     printf("\nKokonaisluku on %d ",Kokonaisluku);
                     //Tulostus 16-kantaisena lukuna
                     printf("\nHeksaluku on %x ",Kokonaisluku);
                     //Tulostus 8-kantaisena lukuna
                      printf("\nOktaaliluku on %o ",Kokonaisluku);
                      printf("\nPaina Enter");
                      getch();
               }
         }

Tiedon talletus ja haku levyltä


Ohjelman käsittelemä tieto halutaan usein tallettaa levylle uutta käyttöä varten. Tieto voidaan tallentaa teksti tai binäärimuodossa.

Tekstitiedostojen käsittely

Tekstitiedostot ovat ns. perättäissaantitiedostoja, joita on kohtuullisen helppo käsitellä tiedosto-osoittimen (FILE* pTiedosto) avulla, lisäksi tarvitaan käskyt tiedoston avaukseen (open) ja sulku (close) sekä tiedostoon kirjoitukseen (fprintf) ja lukuun (fgets).
 
Tiedosto-osoitin esitellään seuraavasti:

          FILE* pTiedosto;

open käskyllä tiedosto-osoitin saadaan osoittamaan luettavaa tai kirjoitettavaa paikkaa tiedostossa
          pTiedosto = fopen("a:tiedot.txt","a");

Ensimmäinen parametri open- funktiossa on avattavan tiedoston nimi.

Toinen parametri määrää, mitä tiedostolle tehdään:

r = luku

w = kirjoitus tiedoston alusta

a = lisäys

Tekstitiedostoon kirjoitus


        KirjoitusTiedostoon()
        {
                       FILE* fi;
                      if ((fi = fopen("a:tiedot.txt","a")) !=NULL) //tiedoston avaus
                      {
                      fprintf(fi,"%06s","Minä");
                      fprintf(fi," %06s","olen");
                      fprintf(fi," %06s"," Teemu ");
                      fprintf(fi," %12s"," Tietäväinen ");
                      fprintf(fi," %15s\n"," tiedän kaiken");
                      fprintf(fi," %15s"," tietokoneista");
                      fprintf(fi," %05s"," !!!!\n");
                      fclose(fi);
                      }
                      else
                      {
                      printf ("Tiedostoa ei saa avatua ");
                      printf("\n virhe PAINA ENTER");
                      getch();
                      }
        }

Tekstitiedoston luku levyltä muuttujiin


void LukuLevylta()
{
     char teksti[100];
     FILE*fi;
     if ((fi = fopen("a:tiedot.txt","r")) !=NULL) //tiedoston avaus
     {
        while (fgets (teksti, 100,fi)) printf(teksti);
         if (feof (fi)) printf("\n Tiedosto luettu\n");
         fclose (fi);
     }
     else
    {
        printf ("Tiedostoa ei saa avatua!! Tarkasta onko levy täynnä!!");
        printf("\n virhe PAINA ENTER");
        getch();
    }
}


Binääritiedostojen käsittely


         void BinTiedostonKirjoitus()
         {
                       FILE*fp;
                       char juttu[100];
                       strcpy(juttu,"tämä on testi");
                       //tiedoston avaus
                       if((fp=fopen("C:testi.bin","wb")) == NULL)
                       {
                       printf(" tiedostoa ei voi avata ");
                       return;
                       }
                       fwrite(juttu,sizeof(char),len(juttu),fp);
                       fclose(fp);
                       printf("talletin PAINA ENTER");
                       getch();
         }

           void BinTiedostonLuku()
         {
                       FILE *fp;
                       char juttu[100];
                       if((fp=fopen("a:testi.bin","rb")) == NULL) return;
                       if(fread(juttu,sizeof(char),100,fp)!=1)
                {
                       fclose(fp);
                       return;
                       }
                       printf("\n Binääritiedoston lukuvirhe ");
         }


Yhdessä käytettävät funktiot
	FILE *fi = fopen(nimi,"rt") - fscanf(fi,...) - fgets(...,fi) - feof(fi) - 

fclose(fi)

FILE *fo = fopen(nimi,"wt") - fprintf(fo,...) - fclose(fo)



Vakiot ja muualla määrättävät muuttujat



Sellaiset tiedot, joita ei haluta muuttaa ohjelman suorituksen aikana, kannattaa määritellä vakioiksi esim. pii on todennäköisesti koko ohjelman ajan sama.

C:ssä käytetään menesti define = määrää lausetta vakioiden määrittelyyn
       
        #define PII 3.14

edellämainitulla tiedolla ei kuitenkaan ole tyyppiä ja siksi varsinkin C++:ssa käytetään useasti uudempaa määrittelyä

        const int Pii=3.14;

const avainsana kertoo että Pii onkin nyt vakio tyyppiä int.

Kun tietoa luetaan esim. joltain laiteportilta, voidaan tehdä seuraavasti:

        const volatile unsigned char *port=0x30;

Ohjelma lukee portilta, jonka heksadesimaalinen osoite on 0x30, arvon, jonka ohjelma ymmärtää vakiona ja siten se kykenee ainoastaan käyttämään ko. muuttujan arvoa muttei muuttamaan sitä.



Seuraavassa esikääntälle ohjeet siitä mitä ohjelman osia otetaan mukaan käännökseen

#define LINUX

#ifdef LINUX

tämä mukaan jos sanan LINUX määrätty

#else

tämä mukaan jos ei sana LINUX määrätty

#endif //tämä aina päättää ifdef tai ifndef alueen

muita käskyjä on esimerkiksi #ifndef = !ifdef  = jos ei määritelty
Comments