C

Mureakuha

Loikkaa: valikkoon, hakuun

Dennis Ritchien Ken Thompsonin B-kielestä kehittämä standardoitu ohjelmointikieli. Uusin standardi on (ISO/IEC 9899:1999) johon yleensä viitataan lyhyemmin C99.

C99-standardissa on varattuja sanoja 37, (C89: 32) mutta huomattava määrä kirjastofunktiota. Kirjastoihin on liitetty kaikki laitteista ja käyttöjärjestelmästä riippuvat toiminnot kuten syöttö ja tulostus. Samoin tietorakenteiden kuten taulukoiden ja merkkijonojen käsittely on kirjastoissa.

C soveltuu systeemiohjelmoinnista aina korkean tason ohjelmointiin. Koska kieli sallii muistinkäsittelyn osoittimilla, tyyppimuunnokset ja seka-aritmetiikan, on ohjelmoijan kuitenkin tiedettävä tarkkaan mitä tekee. Toisaalta C-ohjelmat voivat olla lähes yhtä tehokkaita ja nopeita kuin taitavan assemblerohjelmoijan tekemät. Yleisimpiä virhetilanteita on listattu tänne.

Esimerkkiohjelma:

#include <stdio.h>
 
int main(void) {
    printf("Terve maailma!\n");
    return 0;
}

Sisällysluettelo

Yleistä

Koodissa esiintyvät merkit

rivinvaihto, tabulaattori, välilyönti, numerot, kirjaimet A...Z ja a...z sekä erikoismerkit !"#%&'()*+,-./:;<=>?[\]^_{|}~. Muun muassa skandinaavisia merkkejä ei saa siis esiintyä. Merkkijonoliteraaleissa ja esikääntäjämakroissa saa esiintyä lähes kaikkia merkkejä. Koodissa olevat kommentit sijoitetaan perinteisessä C:ssä /* */ merkkien sisään, C99-standardi lisää C++-tyyliset yhden rivin //-kommentit. Merkkijonoliteraalit sijoitetaan lainausmerkkien sisään " ". Merkki- ja merkkijonovakiot sijoitetaan heittomerkkien, ' ' sisään.

Ohjelman lohkorakenne

Jokainen aaltosuluilla ympäröity lause muodostaa lohkon, jonka alussa ennen ensimmäistä lausetta voi määritellä paikallisia muuttujia. Funktioiden koodit kirjoitetaan aaltosuljelohkon väliin. Yksittäinen lause päättyy puolipisteeseen. Usean muuttujan määrittelyissä käytetään erottimena pilkkua. For-ohjausrakenteessa käytetään usein pilkkuoperaattoria erottamaan useita lausekkeita yhden lauseen sisällä.

Tunnuksista

Makrojen, funktioiden, vakioiden ja muuttujien nimet voivat sisältää kirjaimia, numeroita ja alaviivan. Ensimmäinen merkki on oltava (alaviiva tai) kirjain. Isoilla ja pienillä kirjaimilla on merkitystä.

Funktion prototyypit ja funktiokutsut

Funktion esittelyn, funktion määrittelyn ja funktiokutsun tunnistaa kaarisuluista '( )', jotka ovat funktion tunnuksen jäljessä. Kaarisulkujen sisässä on funktion parametrit/argumentit pilkkueroitteisena listana. Funktion voi olla nk. variadinen, jolloin sille voi antaa vaihtelevan määrän argumentteja. Variadisen funktion parametriluettelossa on kolme pistettä, jolloin

  • viimeinen parametri on ...
  • alussa on oltava vähintään yksi parametri
int printf(char* format,...); // funktion printf-prototyyppi (parametriluettelo)
printf("Luvut: %d,%d", 1, 2); // kutsuu funktiota antaen merkkijonon osoitteen ja luvut argumentteina 
 

Ainoastaan kutsuvalla funktiolla on tiedossa annettujen argumenttien todellinen määrä ja koko. Seuraavaan parametriin variadinen funktio pääsee käsiksi stdarg.h-otsikkotiedostojen makroja käyttäen.

Esikääntäjä

Ennen varsinaista käännöstä konekielelle C:ssä on esikäännösvaihe. Kääntäjälle on mahdollista antaa makroja, jotka alkavat #-merkillä.

'#include'-käskyllä liitetään mukaan sanan jäljessä oleva tiedosto. Jos tiedosto on kulmasuluissa, kyseessä on systeemikirjasto (tietyssä hakemistossa olevat tiedostot). Jos taas nimi on lainausmerkkien ("") sisällä, kyseessä on itse luotu tiedosto.

'#define'-käskyllä puolestaan voidaan määritellä esikääntäjämakroja.

Varatut sanat

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

C99-standardi lisää vielä: inline, restrict, _Bool, _Complex, _Imaginary.

Avainsanat ja muut varatut tunnukset tarkemmin

Tietorakenteet

Tietotyypit

Tietotyyppien koot riippuvat C -kääntäjästä ja kohdejärjestelmästä. Perustietotyyppejä ovat:

  • int (kokonaisluvut)
  • char (merkit)
  • float (yksinkertaisen tarkkuuden liukuluvut)
  • double (kaksinkertaisen tarkkuuden liukuluvut)
  • void (näennäistyyppi)

int -tyypille on käytössä lisämääreet: short (lyhyt) ja long (pitkä), jolloin jätetään usein int -sana pois. Lisäksi on määreet unsigned (etumerkitön) ja signed (etumerkillinen), joita voi käyttää char, short, int ja long kanssa. Lisäksi erikoisuutena on tyyppi 'long double'. C99 standardi määrittelee vielä long long ja unsigned long long -tyypit (inttypes.h, limits.h). Esimerkkejä muuttujamäärittelyistä:

unsigned long m, n;
short s;

Olemassa olevia tyypejä voi hyödyntää tekemällä synonyymejä typedef:llä. Esimerkiksi

typedef unsigned int paiva;

määrittelee paiva:n unsigned int synonyymiksi. typedef ei siis luo uutta tyyppiä.

Lisäksi C:ssä on mahdollisuus luoda omia kokonaislukutyyppisiä tietotyyppejä:

typedef enum { lista } nimi;
enum nimi { lista }; //Vaihtoehtoinen tapa
 
//Käyttöesimerkit
typedef enum { ma, ti, ke, to, pe, la, su } paiva;
paiva tanaan; //muuttuja tanaan on tyyppiä paiva 
 

Yllä olevia perustyyppejä voi käyttää osoittimissa ja rakenteisissa tietotyypeissä:

int taulukko[100]; //Kokonaislukutyyppinen taulukko, jossa on 100 alkiota
double taulu[4][3]; //Taulukko, jossa on 3 saraketta ja 4 riviä
 
struct tuote { //Tietue nimeltä 'tuote'
    unsigned int tunnus; //Struktuurin jäsen
    float maara; //Struktuurin jäsen
    char nimi[32]; //Struktuurin jäsen
} a; //muuttuja a on tyyppiä tuote
 
union luku { //Unioni
    int i; //Vaihtoehtoinen tietotyyppi (esitystapa)
    float f; //Vaihtoehtoinen tietotyyppi (esitystapa)
} b; //muuttuja b on tyyppiä luku 
 

Tietotyyppien kooista on kerrottu täällä.

Talletusluokat ja tyypit

  • auto: funktioiden sisäisiä muuttujia. Häviää funktion suorituksen päättyessä
  • const: vakio, jota ei pitäisi pystyä muuttamaan. Säilyy koko ohjelman ajan muistissa
  • extern: yleinen muuttuja, jonka määrittely on kaikkien funktioiden ulkopuolella
  • register: funktion sisäinen muuttuja (tai parametri), joka on tarkoitus säilyä koko olemassaolonsa ajan keskusyksikön rekisterissä
  • restrict: C99-standardissa lisätty varattu sana, jota voi käyttää vain osoittimille. Restrict kertoo kääntäjälle, että sitä muistia, johon osoitin osoittaa ei käytetä muiden osoittimien läpi. Esimerkki: char * restrict p.
  • static: muuttuja, joka on näkyvissä vain määrittelyn tehneessä funktiossa tai ohjelmatiedostossa. Säilyy koko ohjelman ajan muistissa
  • volatile: muuttujan arvo voi muuttua ohjelman ulkopuolelta; muuttujaa ei pidetä rekisterissä lainkaan.

Osoittimet, taulukot ja merkkijonot

Osoitinmuuttujan arvona voi olla jonkin muun muuttujan osoite. Osoittimeen liittyvät

  • tyyppi, johon muuttujalla voi viitata
  • osoittama muuttuja eli muistiosoite
  • muuttujan arvo eli muistiosoitteesta luettava arvo
int i, j, *pi, *pj; /*pi ja pj ovat int-tyyppisiä osoittimia.
//Määrittely ei liitä niitä vielä mihinkään muuttujaan*/
i = 5;
pi = &i; //pi:n arvoksi sijoitetaan i:n osoite
j = *pi; //j:n arvoksi tulee pi:n osoittaman muuttujan (muistiosoitteen), i:n arvo 5
*pi = 0; //nollataan pi:n osoittaman muuttujan, i:n arvo
pj = pi; //asetetaan pj osoittamaan samaa muuttujaa, i:tä
(*pi)++; //lisätään pi:n osoittaman muuttujan eli i:n arvoon 1 
 

C:ssä taulukot käyttäytyvät kuten osoittimet eli kääntäjä muuttaa taulukkoon kohdistuvan viittauksen osoitinlauseeksi. Kuitenkin poikkeavuuksia löytyy:

  • sizeof -operaattoria käytettäessä palautuu taulukon todellinen koko (paitsi funktion parametrina tai osoitinmuunnoksen jälkeen)
  • taulukon nimi eroaa osoitinmuuttujasta myös olemalla vakio (paitsi funktion parametrina)
  • & -operaattorin kanssa
  • jos taulukko on prosessorin rekisterissä, muunnon lopputulosta ei ole määritelty

Merkkijonot ovat taulukoita, joiden alkiot ovat yksittäisiä merkkejä. Merkkijonoja käsitellään kuten taulukoitakin, indeksejä tai osoittimia käyttäen. Tekstejä voi käsitellä myös kirjastofunktioiden avulla, jolloin merkkijono on päätyttävä nul -merkkiin (useimmiten '\0'). Tilanvarauksessa on siis huomioitava ylimääräinen merkki.

int taulu[5], *pi, **pi2;
//Taulukon nimi = sen ensimmäiseen alkioon osoittava osoitin. Siksi
pi = taulu;
pi = &taulu[0];
//edelliset tarkoittavat samaa.
 
pi = (void*)&taulu; //&taulu tarkoittaa osoitinta int -tyyppiseen taulukkoon
//eli sama muistiosoite pi:ssä kuin aiemmilla. Mutta seuraava:
pi2 = &pi; //Osoittimen osoitin eli eri asia kuin edelliset
pi = *pi2; //taulu -taulukon osoite
 
//Seuraavat nollaavat saman osoitteen
pi[3] = 0;
taulu[3] = 0; //Indeksit alkavat nollasta, joten tässä nollataan neljäs alkio
*(pi+3) = 0;
*(taulu+3) = 0;

Seuraavia vertailuja voi tehdä taulukonnimille ja osoittimille

  • pi == taulu verrataan osoittaako pi taulun ensimmäiseen soluun
  • pi != taulu verrataan osoittaako pi muualle kuin taulun ensimmäiseen soluun
  • pi > taulu verrataan osoittaako pi myöhempään alkioon

Seuraavia ei voi tehdä kuin osoittimille tai taulukonnimen ollessa funktion parametrina

  • pi++; asetetaan pi osoittamaan seuraavaa solua
  • pi+= 2; asetetaan pi osoittamaan kaksi alkiota eteenpäin
  • pi--; asetetaan pi osoittamaan edellistä solua
  • pi-= 2; asetetaan pi osoittamaan kaksi alkiota taaksepäin
  • pi = NULL; asetetaan pi osoittamaan tyhjään (eli pi ei osoita mihinkään). Riippuen järjestelmästä tämä voi olla nolla.

Edellisessä kääntäjä huolehtii osoittimien laskennasta - kaikki lisäykset ja vähennykset ovat sizeof(*pi)-suuruisia. Jos sizeof(int) on siis neljä tavua, pi++ kasvattaa muistiosoitetta automaattisesti neljällä tavulla.

Määrittelyssä voi olla taulukon ja osoittimen sekoitus

char *ctaulu[5] // osoite taulukkoon, jossa on viisi osoitinta merkkijonoihin (ks alla)
char *(ctaulu[5]) // ks edellinen
char (*ctaulu)[5] // osoitin taulukkoon, jossa on viisi merkkiä
int* (*(*taulu[1][2])[3])[4]
// int -tyyppinen taulukko osoittimista taulukkoon osoittimista taulukkoon osoittimista 
 

Huomaa, että C:ssä funktiolle ei voi antaa parametrina taulukkoa, ainoastaan osoittimia. Funktion f prototyyppi void f(int array[10]) on kääntäjälle täsmälleen sama kuin void f(int *array). Tästä seuraa että funktiossa f sizeof(array) == sizeof(int *), eikä sizeof(int) * 10. C99 standardin mukaan, jos taulukkoa edeltää talletusluokka static (ei toimi osoitinversiossa), taulukossa on oltava vähintään annettu määrä elementtejä (koodaaja takaa). Näin ollen parametri ei saa olla NULL. Tietoa voidaan myöhemmässä vaiheessa hyödyntää suuren taulukon ennaltalataamiseen (prefetch).

int testi(int taul[5])
{
    int* pi;
    pi = ++taul; //Voi käyttää nimeä osoittimen tapaan
    return *pi + sizeof(taul); //sizeof(taul) = sizeof(int*)
}


Funktion osoitteen voi vastaavasti tallettaa tai antaa toisen funktion parametrina kuten taulukoillakin: funktion nimi = sen osoite. Funktion osoitteita voi näin ollen myös tallettaa muuttujiin ja taulukoida.

int* mtaulu() // funktio, joka palauttaa osoittimen kokonaislukuun (tai taulukkoon)
int (*mtaulu)() // osoitin funktioon, joka palauttaa kokonaisluvun 
 

Tietueet ja unionit

Taulukon kaikkien alkioiden on oltava samaa tyyppiä, kun taas struktuurien ja unionien avulla kootaan eri tyyppiä olevia muuttujia saman nimen alle.

struct temp { int s; } x, y; //vastaa muodoltaan ja sisällöltään
int x, y; //vastaava 
 

Jos tietueen nimen jättää pois, on muuttujat annettava

struct { int s; } x, y;

Struktuuri voi olla toisen jäsen

struct luokka {
  int eka;
  struct {
    int eka;
  } toka;
} muuttuja, *p;
//Edellisiin muuttujiin voi viitata koodissa
muuttuja.eka
muuttuja.toka.eka
p->eka
p->toka.eka

Unionit ovat määrittelyiltään struktuurien kaltaisia. Unionin kaikki jäsenet ovat päällekkäin muistissa, kun taas struktuurin jäsenet ovat peräkkäin muistissa (jäsenen vaatima tilantarve riippuu molemmilla lisäksi paketoinnista). Jos yhteen unionin jäseneen sijoitetaan arvo, kaikkien muiden jäsenten tila on määrittelemätön (koodaaja täytyy tietää onko muut luettavissa vai ei). Unionin vaatima tila on aina vähintään suurimman jäsenen vaatima tila. Unioniin ja struktuuriin on mahdollista sijoittaa myös alle tavun kokoisia jäseniä, jolloin on kääntäjä-/järjestelmäkohtaista

  • mihin järjestykseen bitit sijoittuvat muistissa (yleensä noudattaa tavujärjestystä Little Endian vs. Big Endian)
  • kentän tarvitsema tilantarve (peräkkäiset bittikentät aina kuitenkin yhdistetään, jos välissä ei ole 0 -kokoista kenttää stopparina)
  • jos annetaan enemmän bittejä kuin mitä määriteltyyn tietotyyppiin mahtuu
union luku { //Voi olla myös ilman nimeä, kun muuttuja annettu
  struct {
    unsigned isokentta:31; 
    unsigned pikkukentta:1;
  } toka;
  float f; //Sijoitettu samaan kohtaan muistia
  int i; //Sijoitettu myös samaan kohtaan muistia (void*)&luku.i == (void*)&luku.f;
} m;
.
:
m.toka.pikkukentta = 1; // asetetaan bitti ykköseksi muuttamatta muiden bittien arvoja
m.f = 1.0; //Toiseen jäseneen viittaus
if(m.toka.pikkukentta) //tämän kentän arvo on nyt toteutusriippuvainen 
 

Tietueilla voi olla myös viimeisenä kenttänä määrittelemättömän levyinen jäsen, jolloin

  • tietueen koossa sizeof -operaattoria käytettäessä ei huomioida viimeisen kentän kokoa
  • koodaaja on tiedettävä tarvittava tilantarve ja tarvittaessa osattava lisätä/vähentää muistia
struct luokka {
  int eka;
  char toka[]; //määrittelemätön tilantarve (muuttuvaleveyksinen)
} *p;

Tietuetta tai unionia (koko suurempi kuin sizeof(long)) ei ole suositeltavaa antaa funktion parametrina tai paluuarvona. Jokaisella kääntäjällä on oma tapansa hoitaa siirto ja tapa voi muuttua koon muuttuessa. Funktion kutsuminen muilla kääntäjillä tai kielillä (esim. VB) tulee näin ollen hyvin vaikeaksi (vaatii syvällisen tietämyksen kääntäjän toiminnasta tai assembly -listausten lukua). Lisäksi vaadittava tilamäärä voi olla 2-3 kertainen todelliseen kokoon verrattuna. Parametrit ja lopputulos on siis parasta olla osoittimia tietueeseen tai unioniin.

Vakiot

Kokonaislukuvakiot voivat olla seuraavissa muodoissa:

  • desimaalina: 123
  • oktaalina: 0173 (edessä numero 0)
  • heksadesimaalina: 0x7B (edessä numero 0 ja kirjain x/X). Kirjaimet a...f voivat olla isolla tai pienellä
  • merkkivakiona: '{' (ASCII-koodi 123). Toisin kuin C++-kielessä, ovat myös merkkivakiot C-kielessä oletusarvoisesti tietotyyppiä int (ts. sizeof('A') == sizeof(int)).
  • leveä merkki -vakiona: L'{'
  • useampitavuiset merkkivakiot, kuten 'abcd' ovat kääntäjäkohtaisia - joillain tavut ovat kirjoitetussa järjestyksessä, toiset taas kääntävät järjestyksen

Kokonaislukuvakioiden oletustyyppi on int. Luvun perässä voi olla kirjain ilmaisemassa suurempaa tietotyyppiä:

  • 1234l/1234L long int
  • 1234u/1234U unsigned int
  • 1234ll/1234LL long long int
  • tai edellisten yhdistelmät
    • 1234ul/1234UL unsigned long int
    • 1234ull/1234ULL unsigned long long int

Liukuluvuilla merkintätapoja ovat

  • 123.45 double (C99, voi riippua kääntäjästä)
  • 123.45f/123.45F float
  • 123.45l/123.45L long double
  • 1.2345e2/1.2345E2 (double, eksponenttimuodossa)

Merkkivakio tai merkkijonoliteraali voi sisältää erikoismerkkejä:

  • \a piippaus (alert, bell, BEL)
  • \b askelpalautin (BS)
  • \f sivunvaihto (FF)
  • \n rivinvaihto
  • \r vaununpalautus (CR)
  • \t tabulaattori (TAB)
  • \v vertikaalinen tabulaattori
  • \0 nul -merkki (oktaalimuotoinen vakio)
  • \\ yksittäinen kenoviiva
  • \' heittomerkki '
  • \" lainausmerkki "
  • \x0A heksadesimaalia 0A vastaava merkki (ASCII: LF)
  • \012 oktaalilukua 012 vastaava merkki (ASCII: LF)
  • \Unnnnnnnn,\Unnnn (tai pienellä u:lla) universal character name (nnnnnnnn). ISO/IEC 10646 mukainen merkki, joka ei saa olla arvoltaan
    • < 00A0
    • 0024 ($)
    • 0040 (@)
    • 0060 (`)
    • tai välillä D800...DFFF

Operaattorit

Tyyppimuunnokset

Luvut muunnetaan automaattisesti laskutoimituksessa ja sijoituksessa suurempaan: char -> short -> int -> long (-> long long); vastaava toimii liukuluvuilla: float -> double -> long double. Kokonaisluku voidaan myös automaattisesti konvertoida liukuluvuksi.

Pienempään päin muutettaessa saatetaan tarvita cast-operaattoria eli eksplisiittistä tyyppimuunnosta: (tyyppi)luku.

int i, *pi;
char* pc;
i = (int)(8.7); //katkaisu kokonaisluvuksi 8
pi = (void*)pc; //osoittimen muutto vaatii käynnin tyypittömän kautta.
//C++:ssa vaaditaan koko ketju pi = (char*)(void*)pc. 
 

Aritmeettiset ja sijoitus

C:ssä on hyvin vähän sisällytettyjä operaattoreja laskentaan

  • + yhteenlasku
  • - vähennyslasku
  • * kertolasku
  • / jakolasku
  • % jakojäännös jakolaskussa

Loput on toteutettu kirjastofunktioilla. C:ssä voi yhdistää sijoitusoperaattoriin jonkin seuraavista

+ - * / % << >> & | ^

i -= 2; //vastaa i = i - 2
i /= j + 2; //vastaa i = i /(j + 2)
 
//Sijoitusoperaattori voi esiintyä muiden operaattorien tapaan lauseissa
printf("%d", i = 0); //Tulostaa luvun 0 ja nollaa i:n arvon 
 

Loogiset lauseet

Relaatiolauseita käytetään ehtojen muodostamiseen. Peräkkäisissä ehdoissa käytetään

  • && ja
  • || tai
  •  ! ei

operaattoreita. Relaatio-operaattoreita ovat:

  • == yhtäsuuri kuin
  •  != erisuuri kuin
  • >
  • <
  • >=
  • <=
if(i == 1 || i >= 3) //Jos i = 1 tai i suurempi tai yhtäsuuri kuin 3
if(i && i > 5) //Jos i <> 0 ja i on suurempi kuin 5 
 

Yhtäsuuruusvertailussa on suositeltavaa asettaa vakio ensin ja sitten vertailtava, jolloin vältytään ikävyyksiltä.

Seuraava menee kääntäjästä läpi kyllä ja tekeekin jotain, mutta ei todellakaan vertaile onko i kaksi vaan asettaa i:n arvoksi kaksi. Virhe voi olla vaikea huomata joissain tilanteissa.

if (i = 2) dosomething();

Monet kääntäjät varoittavat ylläolevasta rakenteesta, mikäli käännösaikaiset varoitukset ovat päällä. Esimerkiksi käytettäessä -Wall-komentoriviparametria, GCC antaa varoituksen "warning: suggest parentheses around assignment used as truth value".

Sen sijaan seuraava rivi aiheuttaa käännösvirheen:

if (2 = i) dosomething();

Lisäys ja vähennys

Operaattorit ja järjestykset ovat

++muuttuja
--muuttuja
muuttuja++
muuttuja--

++ :lla lisätään muuttujaan 1 ja -- :lla vähennetään muuttujan arvosta 1. Jos operaattori esiintyy ensin, ensin muutetaan arvo.

int i, taulu[5], *pi;
i = 0;
taulu[i++] = 1; //taulu[0] = 1, i = 1
taulu[++i] = 2; //i = 2, taulu[2] = 2
pi = taulu; //pi osoittamaan taulu[0]:n
i = *pi++; //i = 1 (taulu[0]:sta), pi osoittaa taulu[1]:n
i = *++pi; //i = 2, pi osoittaa taulu[2]:n
i = ++*pi; //i = 3, pi osoittaa taulu[2]:n 
 

Bittioperaattorit

C:ssä on useita kokonaislukutyyppien bittien muokkaamiseen ja vertailuun tarkoitettuja toimintoja

  • x >> n - bittien siirto oikealle
    • Toiminta riippuu x:n tyypistä. Jos x on etumerkillinen, siirto on aritmeettinen, ts. luvun etumerkki säilyy. Jos x on taas etumerkitön, täydennetään tulosta vasemmalta 0-biteillä.
    • Sama kuin x / 2n:llä
  • x << n - bittien siirto vasemmalle
    • Sama kuin x * 2n:llä
  • x & y - bitittäinen ja (and)
  • x | y - bitittäinen tai (or)
  • ~ x - bitittäinen komplementti eli yhden komplementti (not)
  • x ^ y - bitittäinen poissulkeva tai (exclusive-or)

Ehto-operaattori

Ehto-operaattorilause

z = a > b ? x : y ;
//on sama kuin
if (a > b)
    z = x;
else
    z = y;

sizeof -operaattori

sizeof(tietotyyppi) ja sizeof(muuttuja) palauttavat tietotyypin ja muuttujan koon tavuina (paluuarvo on tyyppiä size_t). Sitä ei voi kuitenkaan käyttää

  • funktioille
  • määrittelemättömille tyypeille
  • alle tavun kokoisille jäsenille (struktuurit ja unionit)

Taulukoille paluuarvo riippuu tapahtuuko muunnosta osoittimeksi

  • funktion parametreilla ja muilla osoitinmuunnoksilla: osoittimen koko
  • muutoin: taulukon vaatima tavumäärä

Tietueilla tavumäärä riippuu tallennustavasta (align). Unioneilla puolestaan palautuu suurimman jäsenen vaatima tila (ja mahdollisesti pakkaus).

void func(int ptaul[5], int n)
{
  int taulu[5];
  int utaul[n]; //C99 standardin määrittelemä tapa
  int *taul = (int*)malloc(100 * sizeof(int));
  int koko = sizeof(taul); //Tähän liittyen:
// koko EI ole taulukon koko vaan osoittimen koko eli 32-bittisissä järjestelmissä yleensä 4
  koko = sizeof(taulu) / sizeof(int); //Taulukon elementtien määrä 5
  koko = sizeof(ptaul + 4); //Osoittimen koko eli sizeof(int*) (+4 osalla ei ole merkitystä)
  koko = sizeof(utaul); //Suorituksenaikainen koon määrittäminen (n*sizeof(int)) 
 

Taulukon alkioiden määrän laskenta on parempi kirjoittaa muodossa: sizeof(taulu) / sizeof(taulu[0]), koska se vähentää kirjoittelun vaivaa mikäli tietotyyppi joskus muuttuisi.

Tietueihin ja unioneihin liittyvät

Pisteoperaattorilla viitataan struktuurin ja unionin jäseniin. Jos käytetään osoittimia, tavallisesti käytetään -> -operaattoria (*). -merkinnän sijaan.

struct tuote { //Tietue nimeltä 'tuote'
    unsigned int tunnus; //Struktuurin jäsen
    char nimi[12]; //Struktuurin jäsen
} a, *ptuote; //muuttuja a on tyyppiä tuote
.
:
unsigned i;
i = a.tunnus;
printf("%s", ptuote->nimi);

Pilkku-operaattori

Pilkulla voidaan erotella lausekkeita, jolloin viimeinen lause on tulos

#define OPER(a, b) (b = a * a, b + a)
int i;
i = OPER(2, i); //Lopuksi i = 6 
 

Ohjausrakenteet

return

return:lla poistutaan funktiosta. Paluuarvo muutetaan tarvittaessa funktion määrittelyssä olevaan tyyppiin. Tyhjällä lauseella (return ;) palataan funktiosta, jonka paluuarvo on void.

break

break:lla pääsee pois for-, while-, do- ja switch -rakenteen sisältä. Ohjelma jatkuu rakennetta seuraavasta lauseesta.

continue

continue aiheuttaa toistorakenteen (for, while, do) meneillään olevan kierroksen päättymisen ja seuraavan alkamisen.

goto

goto:lla ohjelman suoritus siirtyy tunnuksella merkittyyn kohtaan. goto:a pyritään välttämään ohjelmissa. Lausetta joudutaan käyttämään esim. useiden sisäkkäisten toistorakenteiden sisältä poistuttaessa.

Ehtorakenne

if - else -ehtorakenteella voidaan haarautua yhteen tai useampaan kohtaan riippuen else -osien määrästä. else:jä voi siis esiintyä useita tai ei yhtään.

if (lauseke1)
    lause1;
else if (lauseke2) {
    lause2;
}
else
    lause3;

Jos lauseke1 on tosi eli erisuuri kuin nolla, suoritetaan lause1. Muutoin määritetään lauseke2, jonka ollessa tosi, suoritetaan lause2. Muussa tapauksessa suoritetaan lause3.

switch-case

switch-case -rakennetta käytetään, kun halutaan haarautua useampaan kohtaan. Rakenne poikkeaa if-else-rakenteessa myös siinä, että jokaisen tapauksen (case) valitsimen arvo on oltava käännösaikainen vakio tai koostuttava vakioista. Rakenteessa voi olla yksi tai useampia case-osia ja tarvittaessa default-osa.

typedef enum {ma, ti, ke, to, pe, la, su} paiva;
paiva tanaan;
.
:
switch (tanaan) { //Lausekkeeen arvo ratkaistaan ja hypätään johonkin seuraavista
    printf("%d", i); //Tätä ei koskaan suoriteta. Tarpeeton
case ma: //Vakioarvo
    i = 5;
    break; //Hyppy pois rakenteesta
case ti: //Vakioarvo
    i = 3;
    break; //Hyppy pois rakenteesta
case -1: //Vakioarvo
    a = 1; //Tämän suoritus jatkuu seuraavalla kohdalla
default:
    i = 0;
}

while

while toimii kuten muissakin ohjelmointikielissä

while (lauseke)
    lauseet;

Toistoa jatketaan kunnes lauseke on epätosi, mutta ei edes aloiteta, jos lauseke on epätosi jo alussa.

for

for vastaa tietynlaista while -rakennetta, mutta on tiiviimpi

alustuslause;
while (ehtolauseke) {
    lauseet;
    lauseke2;
}
//Vastaava
for(alustuslause;ehtolauseke;lauseke2)
    lauseet;

C89-standardissa alustuslause oli oikeastaan pelkkä lauseke, C99-standardi mahdollistaa muuttujien määrittelyn alustuslauseessa.

do-while

do-while -rakenteella silmukka suoritetaan vähintään kerran tai niin kauan kun while:n ehto on tosi.

kertoma = 1;
i = 0;
do
    kertoma *= ++i;
while (i < maara);

Kirjastofunktioita

C:n kirjastofunktiot ovat pääosin 80 -luvun tuotantoa, jolloin ei ollut graafista käyttöliittymää. Siksi standardikirjastojen toiminnot kuten tekstin ja taustan värjäys ja kohdistimen siirto eivät ole standardeja. Eri kääntäjillä ja kirjastoilla voi siis haluttu toiminto jäädä toteutumatta.

Syöttö ja tulostus

Muotoiltu tulostus komentoriville tapahtuu funktiolla:

int printf (char* format,...);

ja muotoiltu lukeminen

int scanf (char* format,...);.

Parametreja voi olla useita, joten format -teksti on oltava tietyssä muodossa. Jokaisen scanf:n jälkeen on kuitenkin syytä käyttää getchar() -funktiota tyhjentämään syöte, jotta seuraava luenta onnistuisi

int c; // c:n oltava tyyppiä int, jotta vertailu EOFiin toimisi oikein!
...
while ((c = getchar()) != EOF && c != '\n');
//Tai jos tiedetään, että käyttäjä antaa EOF -merkin
while (getchar() != EOF);

getchar -funktio on toimivampi vaihtoehto merkkijonoille, joilla luettavan merkkijonon pituus ei saa ylittyä. Paras, tosin yhteensopimattomampi vaihtoehto olisi käytellä käyttöjärjestelmästä riippuvia funktioita.

EOF -arvon saaminen on järjestelmästä riippuva

  • Unix, Linux, Macintosh: lähes aina Ctrl + D
  • MS-DOS: Ctrl + Z ja päätteeksi Enter
  • Katso muut ko. järjestelmän oppaista

C-standardin mukaisesti standardisyöte-, standardituloste- ja standardivirhe-virroille voi käyttää muuttujien FILE *stdin, *stdout, *stderr avulla normaaleja tiedostoonkirjoitus- ja lukukomentoja.

I/O

Tiedostona voidaan seuraavissa käyttää stdin, stdout, stderr tai fopen:sta saatavaa osoitinta.

  • file = fopen(nimi, mode) avaa tiedoston tai luo uuden. mode on joku seuraavista:
    • "r" vain luku (tiedosto on oltava olemassa)
    • "w" vain kirjoitus
    • "a" kirjoitus tiedoston loppuun (lisäys)
    • "r+" luku ja kirjoitus (tiedosto on oltava olemassa)
    • "w+" luku ja kirjoitus. Tiedoston alkuperäinen sisältö tuhotaan
    • "a+" luku ja lisäys

ja lisäksi voi antaa edellisiin määreen b, jolloin tiedostoa käsitellään binäärimuodossa (ei '\r\n' <-> '\n' muunnoksia)

  • file = tmpfile() perustaa väliaikaistiedoston
  • fsetpos(file, *pos) siirtää tiedosto-osoittimen paikkaa pos osoittamaan paikkaan
  • fgetpos(file, *pos) palauttaa pos osoittamaan paikkaan tiedosto-osoittimen sijainnin
  • feof(file) palauttaa tiedon onko tiedosto-osoitin lopussa (=0)
  • fread(buffer, size, count, file) lukee file -tiedostosta buffer -osoittamaan paikkaan korkeintaan count -määrän tietueita, joiden koko on size, ja palauttaa luettujen tietueiden määrän (vain täydet)
  • fwrite(buffer, size, count, file) kirjoittaa file -tiedostoon buffer -osoittamasta paikasta korkeintaan count -määrän tietueita, joiden koko on size, ja palauttaa kirjoitetun määrän (vain täydet)
  • fclose(file) sulkee avatun tiedoston
  • fprintf(file, format, ...) kirjoittaa file -tiedostoon format muotoillun tekstin (kts. printf)
  • fscanf(file, format, ...) lukee file -tiedostosta format muotoillun tekstin (kts. scanf)
  • putc(merkki, file) kirjoittaa yhden merkin file -tiedostoon
  • getc(file) lukee yhden merkin file -tiedostosta
  • remove(nimi) poistaa tiedoston
  • rename(vanha, uusi) nimeää tai siirtää tiedoston

Merkkijonojen ja muistialueiden käsittely

Seuraavilla merkkijonot on päätyttävä NULL-merkkiin ('\0')

  • strlen(str) palauttaa merkkijonon str pituuden
  • strcmp(s1,s2) vertailee merkkijonojen sisältöä ja palauttaa
    • < 0 jos s1<s2
    • = 0 jos s1=s2 eli tekstit samat
    • > 0 jos s1>s2
  • strncmp(s1,s2,n) on vastaava kuin edellinen, mutta vertailee vain n merkkiä
  • strcat(dest,source) liittää dest -merkkijonon perään source -merkkijonon
  • strncat(dest,source,n) liittää dest -merkkijonon perään n merkkiä source -merkkijonosta
  • strstr(haystack,needle) etsii merkkijonoa needle merkkijonosta haystack ja palauttaa
    • NULL jos merkkijonoa ei löytynyt
    • löytyneen kohdan osoitteen
  • strchr(str,c) etsii merkkiä c merkkijonosta str. Paluuarvo kuten edellisellä
  • strcpy(dest,source) kopioi dest -kohtaan source-merkkijonon sisällön
  • strncpy(dest,source,n) kopioi kuten edellinen mutta vain n merkkiä. Mikäli merkkijonossa source on vähemmän kuin n merkkiä, täytetään dest-taulukkoa NUL-merkeillä n merkkiin asti!

Seuraavat ovat yleiskäyttöisiä eli niitä voi käyttää paitsi merkkijonojen myös muistialueiden (taulukoiden, tietueiden) käsittelyyn:

  • memcpy(dest,source,n) kopioi n merkkiä source:sta dest -osoitteeseen
  • memmove(dest,source,n) siirtää n merkkiä source:sta dest -osoitteeseen varmistaen, että siirto onnistuu (muistialueet eivät ole samat)
  • memset(dest,c,n) muuttaa dest:stä n merkkiä c:ksi
  • memchr(s,c,n) etsii merkkiä c merkkijonosta s lopettaen etsinnän, jos pituus n ylittyy

Kääntäjästä riippuen osa edellisistä voi korvautua suoraan koodilla ilman funktion kutsua (intrinsic functions), jolloin

  • ohjelma nopeutuu
  • ohjelman koko saattaa kasvaa hieman
  • virheenkorjaus muuttuu

Seuraavat korvaantuvat harvemmin: memmove, strstr, strncmp, strchr, strncpy.

Matemaattisia

  • itseisarvo: int abs(int)
  • arkuskosini: double acos(double)
  • arkussini: double asin(double)
  • arkustangentti: double atan(double)
  • pyöristys ylös lähimpään kokonaislukuun: double ceil(double)
  • kosini: double cos(double)
  • kosinihyperbeli: double cosh(double)
  • e:n potenssi: double exp(double)
  • itseisarvo: double fabs(double)
  • pyöristys alas lähimpään kokonaislukuun: double floor(double)
  • jakojäännös: double fmod(double,double)
  • hypotenuusa: double hypot(double,double)
  • itseisarvo: long labs(long)
  • luonn.logaritmi: double log(double)
  • 10-kantainen logaritmi: double log10(double)
  • luvun desimaaliosa: double modf(double,*double)
  • potenssi: double pow(double,double)
  • sini: double sin(double)
  • sinihyperbeli: double sinh(double)
  • neliöjuuri: double sqrt(double)
  • tangentti: double tan(double)
  • tangenttihyperbeli: double tanh(double)

Muita

  • malloc(n) varaa muistia n tavua ja palauttaa osoittimen muistialueen alkuun. Käytettävä lopuksi free -komentoa
  • realloc(mem, n) muuttaa varatun muistin, mem kokoa n:ksi ja palauttaa uuden osoitteen. On huomattava, että muistialue voi siis myös siirtyä. Muistialueen ensimmäiset min(n, vanhakoko) tavua ovat ennallaan. Jos n = 0, realloc voi palauttaa NULL:n tai olla palauttamatta. Jos mem = NULL, realloc toimii kuten malloc.
  • free(mem) vapauttaa varatun muistin, mem
  • qsort(mem, n, width, fcmp) lajittelee n kappaletta tietueita, joiden koko on width fcmp -funktion määräämään järjestykseen alkaen muistiosoitteesta mem. Funktio fcmp on muotoa int fcmp(void* s1, void* s2), ja palauttaa
    • < 0, kun s1 tulee ennen s2:sta
    • = 0, kun s1:n ja s2:n järjestyksellä ei ole väliä
    • > 0, kun s1 tulee s2:n jälkeen
  • rand() palauttaa satunnaisluvun (kokonaisluku, jonka maksimi on RAND_MAX). Ennen ensimmäistä käyttöä on kutsuttava funktiota srand(seed), jonka seed -luvuksi annetaan siemenluku esim. kellonaika
  • time(time_t *) palauttaa nykyisen ajan (muoto riippuu järjestelmästä)
  • difftime(time1, time0) palauttaa kahden ajan erotuksen time1 - time0
  • tm* gmtime(time_t *) muuntaa ajan muodosta time_t tietueeseen tm
  • tm* localtime(time_t *) muuntaa ajan aikavyöhykekohtaisesti muodosta time_t tietueeseen tm
  • ctime(time_t*) palauttaa ajan aikavyöhykekohtaisesti merkkijonossa
  • mktime(tm*) muuntaa tietueen tm muotoon time_t
Henkilökohtaiset työkalut