Spremenljivke ustvarjamo na naslednji način:
[vrsta spremenljivke] [tip spremenljivke] ime spremenljivke = začetna vrednost;
Vrste spremenljivk je opcijska, ravno tako je opcijska začetna vrednost.
int n;
int n = 7;
Primer:
int n = 7;
int f(int m) {
for (int i = 0; i < n; i++) {
m = m * 2;
}
return m;
}
int main() {
printf("main\n");
printf("%d %d\n", n, f(n + 1));
return 0;
}
Tukaj imamo 3 spremenljivke (m
, n
in i
). Spremenljivka n
je živa ves čas izvajanja programa (pravimo, da je globalna).
Spremenljivki m
in i
pa sta živi le med izvajanjem funkcije f
(tem pravimo lokalne spremenljivke).
Poznamo več vrst spremenljivk (auto
, register
, static
, extern
):
- avtomatske spremenljivke:
auto
(lokalne spremenljivke in parametri). Spremenljivke so uničene, ko se konča izvajati funkcija v kateri smo deklarirali spremenljivko. - statične spremenljivke:
static
(globalne spremenljivke, lokalne sstatic
) To lahko uporabimo tudi znotraj funkcije, taka spremenljivka bo preživela izvajanje funkcije. Uporabimo jo lahko npr. če želimo narediti random funkcijo.
unsigned int seed = 0;
unsigned int random() {
seed = (1103515245 * seed + 12345) % (1 << 31);
return seed;
}
int main() {
for (int i = 0; i < 1000; i++) {
printf("%d %u", i, random());
}
return 0;
}
V zgornjem programu je spremenljivka seed
statična, spremenljivka i
pa avtomatska. Ravno tako bi bilo pravilno, če bi poleg njiju napisal ustrezni besedi static
oziroma auto
.
Moti nas, da je spremenljivka seed
vidna vsem v programu. Radi bi, da je dostopna samo funkciji random
. To lahko naredimo tako, da jo ustvarimo znotraj funkcije. Tako bo vidna samo tej funkciji, a se bo ob vsakem njenem izvajanju ustvarila in uničila. Da to rešimo jo spremenimo v statično. Tako dobimo naslednji program.
unsigned int random() {
static unsigned int seed = 0;
seed = (1103515245 * seed + 12345) % (1 << 31);
return seed;
}
Tako smo sedaj dosegli, da je spremenljivka seed
prisotna čez celotno izvajanje programa.
Spremenljivka seed se izvede samo ob prvem izvajanju funkcije. Inicializacija se izvede samo enkrat v programu. Tako spodnji program ne bi deloval.
unsigned int random(unsigned int init) {
static unsigned int seed = init;
seed = (1103515245 * seed + 12345) % (1 << 31);
return seed;
}
Opomba 1: a << b
se imenuje bit shifting, deluje pa tako, da zamakne binarno vrednost števila a
za b
mest v levo, na konec (na desno stran števila) pa doda ničle. Dejansko tako število a
pomnožimo z 2^b
.
Opomba 2: Če želimo z uporabo funkcije printf
izpisati nepredznačeno vrednost (unsigned) je potrebno uporabiti prinft("%u", n)
.
- Zunanje spremenljivke:
extern
(spremenljivke "od drugje")
extern int n;
To so spremenljivke v knjižnici oziroma v drugem delu programa.
- Registrske spremenljivke:
register
(lokalne z register) Take spremenljivke so vedno v pomnilniku, tako procesorju ni potrebno vedno brati s pomnilnika in je tako lahko program dosti hitrejši. Tega se danes ne uporablja več. Najverjetneje bo prevajalnik to ignoriral.
[DODAJ SLIKO]
Avtomatske spremenljivke se shranjujejo na stack, statične spremenljivke na data prostor na pomnilniku.
Napiši program, ki vrne vse možne kombinacije ničel in enic z določeno širino k
.
int k = 7;
// Gremo cez vse mozne kombinacije 7 bitov (teh je 2^k)
for (unsigned comb = 0; comb < 128; comb++) {
// Gremo cez vseh k bitov
for (int b = 0; b < 7; b++) {
if ((comb & (1 << b)) == 0)
printf("0");
else
printf("1");
}
printf("\n");
}
Opomba 1: Če napišemo samo unsigned i
je to enako kot unsigned int i
.
Opomba 2: Brez uporabe if stavka bi lahko za izpis izpisali tudi z uporabo
printf("%s", ((comb & (1 << b)) == 0) ? "0" : "1");
ali pa enako tudi kot
printf("%c", 48 + !((comb & (1 << b)) == 0));
Napiši program, ki šteje z uporabo črk a, b, c in d.
int k = 14;
for (int comb = 0; comb < (1 << 14); comb++) {
for (int b = 0; b < 7; b++) {
switch ((comb >> (2 * b)) & 3) {
case 0:
printf("a");
break;
case 1:
printf("b");
break;
case 2:
printf("c");
break;
case 3:
printf("d");
break;
}
}
printf("\n");
}
Opomba 1: Z uporabo (comb >> (2 * b)) & 3
dobimo ustrezen par bitov. (comb >> (2 * b))
nam premakne vse bite v desno, tako da sta bita, ki jih iščemo na skrajni desni. Ker je mogoče, da je levo od teh bitov še kakšen bit, ki ni ena, jih je potrebno izločiti. To storimo tako, da izvedemo operacijo and
nad rezultatom in številom 3 (3
ima binarni zapis 00...011
).
Opomba 2: Namesto uporabe switch stavka bi lahko vrednosti, ki jo dobimo prišteli ASCII kodo za a
(97) in jo izpisali kot znak.
printf("%c", (comb >> (2 * b)) & 3) + 97);
[dopiši, manjka]
Primer:
static int n = 1000;
static int *pn = &n;
Ker je n
statična spremenljivka je v razdelku data
na našem pomnilniku. Je na točno določenih (ponavadi zaporednih) 4B na pomnilniku.
Spremenljivka pn
je kazalec (pointer) na n
. Na pomnilniku pn
ravno tako zaseda 4 bajte.
| |
| |
|________|
12A0 n |__1000__| (4 bytes)
| |
|________|
1287 pn |__12A0__| (4 bytes)
| |
| |
| |
Torej imamo naslednje možnosti:
-
n
je int -
&n
je naslov n-ja -
pn
je kazalec na int -
*pn
je int na katerega kažepn
-
&pn
je naslov kazalca na int
S pomočjo ukaza sizeof
lahko dobimo število bajtov, ki jih zaseda spremenljivka. Če želimo dobiti število bajtov v spremenljivki n
in število bajtov v kazalcu pn
lahko dobimo s pomočjo spodnjega programa.
printf("%d %d\n", sizeof(n), sizeof(pn));
Vsi kazalci na nekem sistemu imajo enako širino (enako število bajtov).
Primer ko nimamo samo statičnih spremenljivk ampak avtomatske:
int main() {
int n = 1000;
int *pn = &n;
printf("%d %lu\n", n, &n);
printf("%lu %lu\n", (unsigned long int)pn, (unsigned long int)(&pn));
}
Opomba 1: Na Intel procesorjih so naslovi v unsigned long int
spremenljivkah.
Branje števil v spremenljivko.
int n;
n = 10;
printf("%d\n", n);
printf("%d\n", 2 * 5);
scanf("%d", n); // NAROBE
scanf("%d", &n); // PRAVILNO
scanf("%d", n);
je napačno, ker mi funkciji scanf
pošljemo vrednost spremenljivke 10.
Pri drugem primeru scanf("%d", &n);
pa funkciji scanf
pošljemo naslov, na katerega naj shrani prebrano vrednost.
void swap(int a, int b) {
int t = a;
a = b;
b = t;
}
spaw(i, j);
To ne deluje, ker funkcija swap
sprejme dva parametra po vrednosti. V funkciji sta torej 2 neodvisni spremenljivki, ki imata enaki imeni, a nista enaki prejšnjim. Nato ti dve spremenljivki zamenjamo in ob koncu izvajanja funkcije tudi uničimo. Tako smo dosegli, da se vrednosti nista dejansko zamenjali, temveč sta se zamenjali le novo-ustvarjeni spremenljivki v funkciji swap
.
Če pa napišemo funkcijo swap
takole, se vrednosti dejansko zamenjata.
void swap(int *a, int *b) {
int t = *a;
*a = *b;
*b = t;
}
swap(&i, &j);
V tem primeru pa mi funkciji kot parametre posredujemo lokaciji spremenljivk i
in j
na pomnilniku, znotraj metode zamenjamo podatke na pomnilniku na teh dveh lokacijah. Tako sta se zamenjali tudi dejanski vrednosti spremenljivk i
in j
.