Programski jezik C

Podatkovni tipi

Ko pišemo program se pojavi želja, da določene vrednosti nekam shranimo oziroma jih nekje hranimo. V programu lahko za začasno hrambo vrednosti uporabimo pomnilnik do katerega dostopamo preko spremenljivk, a jih moramo pred uporabo naznaniti (najaviti) in jim pri tem določiti tip. Se pravi, se moramo odločiti, ali bomo v spremenljivki hranili znake, cela števila, realna števila, …

Spremenljivke

Spremenljivke so namenjene začasni, kratkotrajni hrambi podatkov. Vsaka spremenljivka ima tip, ime in vrednost.

Tipi spremenljivk (data types)

V programskem jeziku C so podprti naslednji (imenovani tudi primitivni) tipi spremenljivk.

Tipa char in int sta celoštevilska, torej lahko hranita cela števila določenega razpona (zaloge vrednosti). Tudi tipa float in oba double sta številska, vendar sta namenjena za shranjevanje realnih števil, torej števil z decimalno vejico, ki so največkrat, za doseganje točnosti, hranjena v obliki s plavajočo vejico. Tip void je poseben tip, s katerim povemo, da nas tip spremenljivke ne zanima, vendar ima zato spremenljivka omejeno rabo oziroma posebno rabo, ki jo bomo srečali v nadaljevanju.

Številskemu tipu int je možno dodati dolžinsko predpono short ali long. Tudi tipu double je možno dodati dolžinsko predpono long. S predponami izrazimo željo, naj spremenljivka obsežnega tipa long hrani večji razpon vrednosti od spremenljivke brez dolžinske predpone in naj spremenljivka tipa short int hrani ožji razpon vrednosti od spremenljivke tipa int.

Namesto long int zadošča pisati le long. Enako velja za short int. Saj je privzeti tip spremenljivke int.

Poleg tega poznamo še predponi za predznačenost: signed in unsigned za tipa char in int, s katerima povemo, ali spremenljivka lahko hrani tudi negativna števila. Najnižja vrednost, ki jo lahko hrani nepredznačena (unsigned) spremenljivka je 0.

Če hočemo uporabiti predznačeno spremenljivko (signed), nam te besede ni potrebno pisati, saj je privzeta vrsta spremenljivk predznačena in zato namesto signed int zadošča pisati le int. Pri določanju podatkovnega tipa spremenljivke, moramo vnaprej predviditi, kakšne vrednosti se bodo pojavljale v dotični spremenljivki in nato uporabiti primeren tip.

Tudi namesto unsigned int lahko pišemo le unsigned.

Zaloga vrednosti

V programskem jeziku C zgoraj navedeni tipi spremenljivk nimajo točno predpisanih velikosti, zato ne moremo vedeti, ali bo lahko spremenljivka hranila določeno vrednost ali ne. Ve se, da je širina spremenljivke tipa short int vsaj 2 zloga in širina spremenljivka tipa long int vsaj 4 zloge.

Te zagate se lahko rešimo z uporabo knjižnice stdint.h (iz standarda C99), ki nam postreže z naslednjimi celoštevilskimi tipi.

Tipi nam že iz samega imena povedo, da lahko shranijo 8, 16, 32 ali 64-bitno (predznačeno) celo število. Tipi, ki vsebujejo besedo fast, lahko hranijo tudi večja števila, medtem ko tipi z besedo least predstavljajo najmanjše tipe, ki lahko še hranijo cela števila željenega razpona.

Standard C99 je prinesel tudi tip long long, ki lahko hrani še večja števila od tipa long, logični tip bool ter tipe float complex, double complex, long double complex, ki lahko hrani kompleksna števila.

Čeprav se zdi uporaba tipov odveč, nas lahko že prevajalnik obvesti, da bo šlo nekaj narobe. V nadaljevanju bomo uporabljali le tipe iz standarda C90.

Operator sizeof

Kot je bilo že omenjeno tipi spremenljivk (iz C90) nimajo vnaprej določenih širin (porabe prostora v pomnilniku), vendar obstaja način, kako do teh vrednosti priti. Operator sizeof nam vrne število zlogov, ki jo spremenljivka porabi zase, medtem ko knjižnica limits.h vsebuje meje zaloge vrednosti.

Naslednji program izpiše število zlogov in zalogo vrednosti spremenljivk tipa char.

#include <limits.h>
#include <stdio.h>

int main()
{
  
  printf("Tip char zavzema %i zlog.\n", sizeof(char));
  printf("Tip char lahko hrani vrednosti od %i do %i.\n", CHAR_MIN, CHAR_MAX);

  return 0; 
}

V večini primerov bo tip char zavzel 1 zlog in hranil vrednosti od -128 do 127.

Izračun se izvrši ob prevajanju in vrednosti spremenljivk se ne spremenijo. sizeof(a++) vpliva na a povsem enako kot sizeof(a). Operator vrača podatkovni tip size_t, ki je na 32-bitnih sistemih pseudonim za unsigned int in na 64-bitnih za unsigned long.

Konstante

Konstante si zaslužijo svoje poglavje, saj so tudi te določenega tipa. Ker je privzeti tip spremenljivke int, je takšen tudi privzet tip konstante.

Na primer, 1 je konstanta v desetiškem sistemu tipa int. Če za njo pišemo L ali l, na primer 1L, je konstanta tipa long. Če za njo dodamo še U ali u, dobi konstanta predpono za nepredznačenost unsigned.

Če vrednost spremenljivke preseže izbrani tip, se najprej uporabi tip unsigned, nato pa podatkovni tip preide v long.

Če v zapisu uporabimo piko (ta ima vlogo decimalne vejice), na primer 1.0, bo ta tipa double. Če želimo float, za številom dodamo F ali f.

Številski sistemi

Konstante lahko zapišemo tudi v drugih številskih sistemih in ne samo v desetiškem. Poglejmo si nekaj primerov.

/* desetiški številski sistem */
157;

/* osmiški številski sistem */
0545;

/* dvojiški (binarni) številski sistem, podpora odvisna od prevajalnika */
0b10011101;

/* šestnajstiški številski sistem */
0x3D;

Uporaba šestnajstiškega sistema se naznani z znakoma 0x pred naštevanjem števk, uporabo osmiškega z vodilno ničlo, in uporabo dvojiškega sistema z znakoma 0b. Dvojiški sistem ni del standarda C, vendar ga gcc podpira.

Poznamu tudi znakovne konstante (character literal) oblike ' '. Ti predstavljajo številsko (ASCII) vrednost črke znotraj enojnega narekovaja. Na primer 'a' je enakovredno 97. V znakovne konstante lahko zapisujemo tudi šestnajstiško '\xa0' ali osmiško '\010'.

Naznanitev spremenljivk

Pred uporabo spremenljivke moramo njeno uporabo naznaniti; jo deklariati. Spremenljivke naznanimo na začetku funkcije, v kateri jo bomo uporabili, ali na začetku datoteke, če gre za javno spremenljivko, ki jo želimo uporabiti v več funkcijah. V standardih C99 in C11 jih lahko naznanimo tudi sredi kode.

Če hočemo uporabiti spremenljivko z imenom kolicina tipa int, njeno uporabo naznanimo na sledeči način.

int kolicina;

Spremenljivki lahko ob naznanitvi predpišemo tudi začetno vrednost, tedaj jo tudi definiramo.

int kolicina = 4;

Zgornji stavek naznani spremenljivko z imenom kolicina tipa int z začetno vrednostjo 4. Enakovredni temu sta naslednji dve vrstici. V prvi spremenljivko naznanimo in v drugi vrstici ji pripišemo vrednost.

int kolicina;
kolicina = 4;

Koda za naznanitev spremenljivke in koda za naznanitev spremenljivke, ki ji želimo predpisati začetno vrednost X, imata tako naslednji možni obliki.

tip_spremenljivke ime_spremenljivke;
tip_spremenljivke ime_spremenljivke = X;

Poglejmo si še nekaj primerov za večjo jasnost razumevanja.

long hruske;
char jabolka = -102;
int slive = 12402;
float cesnje = 3.491;
char zaboj, kosara_a2;

V zadnji vrstici smo naznanili dve spremenenljivki tipa char v eni vrstici.

Imena spremenljivk

Imena spremenljivk lahko vsebujejo velike ali male črke angleške abecede (brez šumnikov), številke, vendar ne te na prvem mestu, ter podčrtaj. Imena spremenljivk naj ne bodo predolga ali prekratka in naj izražajo namen spremenljivke. Imena so skoraj poljubna, izogniti se je potrebno le prepovedanim besedam. C je občutliv na velike ina male črke in zato loči med imenoma aa in Aa.

Jezik nam dopušča, da naznanimo več spremenljivk enakega tipa naenkrat (peta vrstica). Če bomo v istem bloku naznanili več spremenljivk z enakim imenom, bo nas na to opozoril prevajalnik.

V jeziku C se imena spremenljivk pretežno piše v t.i. snake case, kar pomeni samo male črke, povezane s podčrtajem. Primer: stevilo_hrusk

Seznam prepovedanih besed – ključnih besed – v C90

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

Seznam dodatno prepovedanih besed – dodanih ključnih besed – v C99

inline, restrict, _Bool, _Complex, _Imaginary

Shranjevanje vrednosti

V spremenljivke shranjujemo vrednosti oziroma jim prirejamo vrednosti s priredilnim stavkom na sledeči način.

hruske = 3;

Seznami

Enake tipe spremenljivk lahko združimo tako, da ustvarimo seznam oziroma polje (array), katerega dolžino moramo določiti ob naznanitvi, kasnejše krajšanje ali podaljševanje seznama ni mogoče. V standardu C99 in novejšem je dolžina seznama lahko tudi spremenljivka. Splošna oblika naznanitev seznama je sledeča.

tip_spremenljivke ime_seznama_kot_spremenljivka[velikost];

Naslednji primer bo neznanil prazen seznam dolžine 3 in tipa int. Ker je dolžina seznama enaka 3, se pod spremenljivko v pomnilniku hrani 3 vrednosti enakega tipa.

int seznam[3];

Tudi elementom seznama lahko predpišemo začetne vrednosti.

int kvadrati[4] = { 1, 4, 9, 16 };

Če predpišemo začetne vrednosti, lahko dolžino seznama izpustimo, saj jo je mogoče določiti iz števila podanih začetnih vrednosti.

int kvadrati[] = { 1, 4, 9, 16 };

Takšno navajanje elementov z uporabo zavitih oklepajev deluje samo pri naznanitvi spremenljivke. Kasneje, v programu, je potrebno uporabiti odmike.

Do elementov seznama dostopamo tako, da podamo mesto elementa, do katerega želimo dostopati. Ker prvemu elementu pripada mesto 0, je bolje temu reči odmik od začetka. Prvi element oziroma spremenljivka zgornjega seznama kvadrati je tako kvadrati[0]. Lahko ji preberemo ali ji določimo vrednost kot kaže primer.

/* Spremenljivko kvadrati poznamo od zgoraj. */
int i;
i = kvadrati[2];	/* Spremenljivka i ima sedaj vrednost 9. */
kvadrati[3] = 20;	/* Seznam kvadrati ima sedaj vrednosti { 1, 4, 9, 20 }. */

Tisti, ki so že kdaj programirali, so opazili, da med zgornjimi tipi ni tipa, ki bi prestavljal besedilo oziroma tip, ki ga drugi programski jeziki imenujejo string. Rešitev je uporaba seznama, katerega sestavljajo spremenljivke tipa char, kar bomo imenovali niz znakov.

Nizi znakov

char niz[10];

Z zgornjo vrstico smo naznanili spremenljivko z imenom niz za hranjenje besed. Ker dolžina tako hranjenih nizev ni nikjer zapisana, moramo za to poskrbeti sami; bodisi z ločeno spremenljivko bodisi z uporabo končnega ničelnega zloga (NUL – null character), z vrednostjo 0, s čimer se označi zaključek niza znakov. To tudi pomeni, da za hranjenje nekega besedila potrebujemo za en znak daljši seznam od dolžine besedila.

Če hočemo nizu niz določiti začetno vrednost, lahko to naredimo na naslednja dva načina.

char niz[10] = "Oblak";
char niz[10] = { 'O', 'b', 'l', 'a', 'k', 0 };

Izraz oblike "  " je konstanta, ki se imenuje string literal. V njih končnega – ničelnega zloga ni potrebno navajati, saj ga za nas doda prevajalnik.

Celotnega seznama ni mogoče prirediti drugemu seznamu. Uporabiti je potrebno zanko ali funkcijo, ki drugemu seznamu priredi element po element.

int i[3], j[3];
i = j;	/* To ne bo delovalo. */

Nadzorni znaki

Nadzorni znaki so posebni znaki, s katerimi nadzorujemo izhodno napravo, na primer tiskalnik. Za zapis nadzornega znaka se uporabi ubežno zaporedje (escape sequence), ki se naznani z znakom \, kateremu sledi znak, ki predstavlja vrsto nadzornega znaka.

Uporaba nizov

Če poskusimo nekje v programu uporabiti naslednji priredilni stavek, se bo prevajalnik pritožil.

char ime[6];
ime = "Oblak";

Rešitev obstaja in si jo bomo pogledali v naslednjih poglavjih, do tedaj pa lahko v nizu spreminjamo ali nastavljamo le posamezno črko. Do teh dostopamo – tako kot do elementov seznama – z odmikom. Torej, če bi želeli zamenjati prvo črko besedila v spremenljivki z imenom ime tako, da ji bi priredili vrednost 'A', bi to zapisali sledeče.

ime[0] = 'A';

Kot je razvidno, tudi pri nizih odmike pišemo v oglatih oklepajih za imenom spremenljivke.

Seznami seznamov

Tvorimo lahko tudi seznam seznamov, kot kaže primer.

int xy[3][3];
char pari[3][2] = { {'a', 'b'}, {'A', 'B'}, {'1', '2'} };
short matrika[2][2] = {{1, 2}, {3, 4}};

V drugi vrstici smo naznanili seznam iz treh elementov, ki so prav tako seznami dveh elemenov tipa char. Primer v tretji vrstici je podoben primeru v drugi vrstici, le da smo uporabili drugi tip. Takšen način je uporaben za shranjevanje elementov pretežno polne pravokotne matrike.

Lastnosti spremenljivk

Vsem spremenljivkam je moč nastaviti dodatne predpone, s katerimi določimo njihove lastnosti. Predpone dodamo pri naznanitvi spremenljivke pred ime podatkovnega tipa.

Če nočemo, da se spremenljivkam njihova vrednost spreminja, ji pri naznanitvi dodamo predpono const, s katero prevajalniku povemo, naj javi napako, če smo v kodi želeli spremenljivko spremeniti. Takšen način lahko uporabimo za „konstante“ oziroma nespremenjive spremenljivke, katerim moramo določiti začetno vrednost ob naznanitvi, saj jih kasneje ne moremo spreminjati.

const float PI = 3.14;

Namesto takšnih konstant je veliko bolje uporabiti makro #define.

Poleg const poznamo še predponi volatile, ki prevajalniku pove, da se lahko vrednost spremenljivke nepričakovano spremeni, ter register, ki prevajalniku pove naj spremenljivko za hitrejši dostop do vrednosti hrani v registru procesorja.

S predpono static prevajalniku povemo, naj spremenljivko postavi na kopico. Tedaj spremenljivka naznanjena znotraj funkcije ohranja svojo vrednost med posameznimi klici funkcije, v kateri je. Spremenljivki lahko nastavimo tudi začetno vrednost, vendar bo imela to vrednost le v prvem klicu funkcije. V drugem klicu pa bo imela vrednost, katero je imela po zaključku prvega klica. Če začetne vrednosti takšni spremenljivki ne nastavimo, bo njena vrednost enaka 0.

static unsigned int stevec = 0;

Pseudonimi

Zamudno pisanje enih in istih predpon ali tipov si lahko poenostavimo z uporabo pseudonimov, ki jih upeljemo s stavkom typedef, kot kažeta primera. Pred zaključkom stavka, s podpičjem, se navede novo ime (pseudonim), ki mora ustrezati pogojem za poimenovanje spremenljivk – vsaj biti brez presledkov.

typedef unsigned char uc;	/* Namesto unsigned char lahko v nadaljevanju pišemo le uc. */
uc i, j = 6;	 /* Spremenljivki i in j sta tipa unsigned char. Spr. i ima vrednost 6. */
typedef unsigned int uint;

Pri programiranju vgradnih naprav v standardu C99 zelo pogosto vidimo okrajšave u32, s katerimi si prihranimo pisanje.

typedef uint32_t u32;