Proměnné
Aby programy mohly řešit nějaký úkol, tak si téměř vždy musí umět něco zapamatovat. K tomu slouží tzv. proměnné (variables). Proměnné nám umožňují pracovat s pamětí počítače (RAM) intuitivním způsobem - část paměti si pojmenujeme nějakým jménem a dále se na ni tímto jménem odkazujeme. Do proměnné poté můžeme uložit nějakou hodnotu, čímž si ji počítač "zapamatuje". Tuto hodnotu můžeme později v programu přečíst anebo ji změnit.
Příklady použití proměnných:
- Ve webové aplikaci si číselná proměnná pamatuje počet návštěvníků. Při zobrazení stránky se hodnota proměnné zvýší o 1.
- Ve hře si číselná proměnná pamatuje počet životů hráčovy postavy. Pokud dojde k zásahu postavy nepřítelem, tak se počet životů sníží o zranění (damage) nepřítelovy zbraně. Pokud hráč sebere lékárníčku, tak se počet jeho životů opět zvýší.
- V terminálu si proměnná reprezentující znaky pamatuje text, který byl zadán na klávesnici.
Definice
Proměnné jsou jedním z nejzákladnějších a nejčastěji používaných stavebních kamenů většiny programů, během semestru se s nimi budeme setkávat neustále. Není tak náhodou, že jedním z nejzákladnějších příkazů v C je právě vytvoření proměnné. Tím řekneme počítači, aby vyčlenil (tzv. naalokoval) místo v paměti, které si v programu nějak pojmenujeme a dále se na něho pomocí jeho jména můžeme odkazovat1.
1O tom, jak přesně tato alokace paměti probíhá, se dozvíte později v sekci o práci s pamětí.
Takto vypadá příkaz definice (vytvoření) proměnné s názvem vek
s datovým typem int
:
int vek;
Jakmile proměnnou nadefinujeme, tak z ní můžeme buď číst anebo zapisovat paměť, kterou tato proměnná
reprezentuje, pomocí jejího názvu (zde vek
).
Platnost
Proměnná je platná (lze ji používat) vždy od místa (řádku) definice do konce bloku, ve kterém byla
nadefinována. Bloky jsou kusy kódu ohraničené složenými závorkami ({
a }
):
int main() {
// zde není platné ani `a`, ani `b`
int a;
// zde je platné pouze `a`
{
// zde je platné pouze `a`
int b;
// zde je platné `a` i `b`
} // zde končí platnost proměnné `b`
// zde je platné pouze `a`
return 0;
} // zde končí platnost proměnné `a`
Všimněte si, že bloky lze vnořovat (lze vytvořit blok v bloku), a proměnné jsou platné i ve vnořených blocích. Oblast kódu, ve které je proměnná validní, se nazývá (variable) scope.
Datový typ
int
před názvem proměnné udává její datový typ, o kterém pojednává následující kapitola.
Prozatím si řekněme, že int
je zkratka pro integer
, tedy celé číslo. Tím říkáme programu, že má
tuto proměnnou (resp. paměť, kterou proměnná reprezentuje) interpretovat jako celé číslo se znaménkem.
Inicializace
Do proměnné bychom měli při jejím vytvoření rovnou uložit nějaký výraz, který musí být stejného datového typu jako je typ proměnné:
int a = 10;
int b = 10 + 15;
Obecná syntaxe pro definici proměnné je
<datový typ> <název>;
popřípadě
<datový typ> <název> = <výraz>;
pokud použijeme inicializaci.
Všimněte si, že na konci definice proměnné vždy musí následovat středník (;). Opomenutí středníku na konci příkazu je velmi častá chyba, která často končí těžko srozumitelnými chybovými hláškami při překladu. Dávejte si tak na středníky pozor, obzvláště ze začátku.
Vždy inicializujte proměnné!
Je opravdu důležité do proměnné vždy při její definici přiřadit nějakou úvodní hodnotu. Pokud to neuděláme, tak její hodnota bude nedefinovaná (undefined). Čtení hodnoty takovéto nedefinované proměnné způsobuje nedefinované chování (undefined behaviour, UB)2 programu. Pokud k tomu dojde, tak si překladač s vaším programem může udělat, co se mu zachce, a váš program se poté může chovat nepředvídatelně.
2Situace, které můžou způsobit nedefinované chování, budou dále v textu označené pomocí ikony 💣.
Proto vždy dávejte proměnným iniciální hodnotu!
Čtení
Pokud v programu použijeme název platné proměnné, tak vytvoříme výraz, který se vyhodnotí jako její současná hodnota:
#include <stdio.h>
int main() {
int a = 5;
int b = a; // hodnota `b` je 5
int c = b + a + 1; // hodnota `c` je 11
printf("a = %d, b krat 2 = %d, c = %d", a, b * 2, c);
return 0;
}
Proměnnou (resp. její název) tak lze použít kdekoliv, kde je očekáván výraz (pokud sedí datové typy).
Pro výpis hodnot proměnných na výstup programu můžete použít printf
.
Hodnoty proměnných můžete zkoumat také krokováním pomocí debuggeru.
Zápis
Pokud by proměnná měla pouze svou původní hodnotu, tak by nebyla moc užitečná. Hodnoty proměnných naštěstí jde měnit. Můžeme k tomu použít výraz přiřazení (assignment):
#include <stdio.h>
int main() {
int a = 5; // hodnota `a` je 5
printf("%d\n", a);
a = 8; // hodnota `a` je nyní 8
printf("%d\n", a);
return 0;
}
Obecná syntaxe pro přiřazení do proměnné je
<název proměnné> = <výraz>
Opět musí platit, že výraz musí být stejného typu3, jako je proměnná, do které přiřazujeme. Na konci
řádku také nesmí chybět středník. Přiřazení je příklad výrazu, který má vedlejší efekt.
Abychom z něj udělali příkaz, musíme za něj dát středník ;
.
3C umožňuje automatické (tzv. implicitní) konverze mezi některými datovými typy, takže typ výrazu nemusí být nutně vždy stejný. Tyto konverze se nicméně často chovají neintuitivně a překladač vás před nimi obvykle nijak nevaruje, i když vrátí výsledek, který nedává smysl. Snažte se tak ze začátku opravdu vždy používat odpovídající typy. Více se dozvíte v sekci o datových typech.
Jak přiřazení funguje? Počítač se podívá, na jaké adrese v paměti daná proměnná leží, a zapíše do paměti hodnotu výrazu, který do proměnné zapisujeme, čímž změní její hodnotu v paměti. Z toho vyplývá, že dává smysl zapisovat hodnoty pouze do něčeho, co má adresu v paměti4. Například příkaz
5 = 8;
nedává smysl.5
je výraz, číselná hodnota, která nemá žádnou adresu v paměti, nemůžeme tak do ní nic zapsat. Stejně tak jako nedává smysl říctČíslo 5 odteď bude mít hodnotu 8
.4Zatím známe pouze proměnné, později si však ukážeme další možnosti, jak vytvořit "něco, co má adresu v paměti", a co tak půjde použít na levé straně výrazu přiřazení
=
.
Kde vytvářet proměnné?
Proměnnou vždy vytvářejte (deklarujte) až na místě v programu, kde ji opravdu budete poprvé potřebovat. Bude pak mnohem
jasnější, k čemu se proměnná využívá, kde opravdu začíná její platnost, a kde naopak ještě není potřeba.
Pokud byste všechny proměnné vytvořili na začátku funkce (bloku kódu, např. main
)5, tak nebude zřejmé, k čemu vlastně
jednotlivé proměnné jsou, a může se vám jednoduššeji stát, že proměnnou omylem použijete v kusu kódu, se kterým nesouvisí.
5Pokud už jste se s jazykem C dříve setkali, možná jste byli přesvědčeni, že musíte všechny proměnné deklarovat
již na začátku každé funkce. Vězte, že tomu tak není již zhruba 25 let, od standardu C99
:)
Definice více proměnných najednou
Pokud potřebujete vytvořit více proměnných stejného datového typu, můžete použít více názvů
oddělených čárkou za datovým typem proměnné. Takto například lze vytvořit tři celočíselné proměnné
s názvy x
, y
a z
:
int x = 1, y = 2, z = 3;
Doporučujeme však tento způsob tvorby více proměnných spíše nepoužívat, aby byl kód přehlednější.
Cvičení 🏋
- Zkuste napsat program, který vytvoří několik proměnných, přečte a změní jejich hodnoty
a pak je vypíše na výstup programu (k výpisu využijte
printf
, který jsme si již ukázali dříve). - Použijte debugger, abyste se interaktivně za běhu programu podívali, jaké jsou hodnoty jednotlivých proměnných a jak se měni v čase po provedení přiřazení.
Více úloh naleznete zde.
Kvíz 🤔
-
Co vypíše následující program?
#include <stdio.h> int main() { int a = 5; printf("a\n"); return 0; }
Odpověď
Program vypíše znak
a
, jelikož vše uvnitř uvozovek se bere jako text. Aby program vypsal hodnotu proměnnéa
, museli bychom použít např. příkazprintf("a=%d\n", a);
. -
Co vypíše následující program?
#include <stdio.h> int main() { int a = 5; printf("%d\n", a); a = 8; return 0; }
Odpověď
Program vypíše znak
5
, protože v době, kdy proměnnou vypisujeme, tak je její hodnota5
. Po vypsání proměnné sice její hodnotu změníme na8
, ale poté už ji nevypíšeme a program skončí. -
Co vypíše následující program?
#include <stdio.h> int main() { int a = 5; a + 1; printf("%d\n", a); return 0; }
Odpověď
Program vypíše znak
5
. Provedeme sice výraza + 1
, který se vyhodnotí jako6
, ale výsledek tohoto výrazu se "zahodí", nijak tedy neovlivní další chování programu. Abychom změnili hodnotu proměnnéa
, museli bychom výsledek tohoto výrazu zpět do proměnné uložit:a = a + 1;
. Vyzkoušejte si to. -
Co vypíše následující program?
#include <stdio.h> int main() { int a = 5; int b = a; a = 8; printf("%d\n", a); printf("%d\n", b); return 0; }
Odpověď
Program vypíše:
8 5
Při definici proměnné
b
jsme ji inicializovali hodnotou proměnnéa
. Výraza
se tedy vyhodnotil jako hodnota5
, která byla uložena do proměnnéb
. Dále však už spolu proměnné nesouvisí, změna hodnoty proměnnéa
tedy nijak neovlivní hodnotu uloženou v proměnnéb
. -
Co vypíše následující program?
#include <stdio.h> int main() { printf("%d\n", a); int a = 5; return 0; }
Odpověď
Překlad programu skončí s chybou (
use of undeclared identifier 'a'
), protože se snažíme číst hodnotu proměnné, která na daném řádku zatím nebyla nadefinována. Proměnnoua
můžeme začít používat až poté, co ji nadefinujeme, tj. za řádkemint a = 5;
. -
Co vypíše následující program?
#include <stdio.h> int main() { a = 5; printf("%d\n", a); return 0; }
Odpověď
Překlad programu skončí s chybou (
use of undeclared identifier 'a'
), protože se snažíme zapsat výraz5
do proměnné, která neexistuje. Před prvním použitím proměnné ji vždy nejprve musíme nadefinovat:int a = 5;
. -
Co vypíše následující program?
#include <stdio.h> int main() { int a = 1; int b = a = 5; printf("%d\n", a); printf("%d\n", b); return 0; }
Odpověď
Program vypíše:
5 5
Výraz přiřazení (
<promenna> = <vyraz>
) se vyhodnotí jako přiřazená hodnota (<vyraz>
), a takto vyhodnocený výraz lze dále v programu použít a např. přiřadit do jiné proměnné. Přiřazení se vyhodnotí následovně:int b = a = 5; // int b = 5;
Nicméně jak asi sami uznáte, takovýto zápis je dosti zmatečný a nemusí být na první pohled jasné, jak se takovýto výraz vyhodnotí. Proto výsledek výrazu přiřazení raději dále nepoužívejte a přiřazení vždy používejte na samostatném řádku se středníkem.
-
Co vypíše následující program?
#include <stdio.h> int main() { int a = 1; 5 = a + 1; printf("%d\n", a); return 0; }
Odpověď
Překlad programu skončí s chybou
expression is not assignable
. Snažíme se zde uložit hodnotu výrazua + 1
na nějaké místo v paměti, ale5
žádné takové místo neoznačuje,5
je prostě číselný literál s hodnotou5
, který nemůžeme přepsat či změnit. -
Co vypíše následující program?
#include <stdio.h> int main() { int a; printf("%d\n", a + 1); return 0; }
Odpověď
Tento program obsahuje nedefinované chování 💣, protože čteme hodnotu proměnné, která nebyla inicializována, a její hodnota je tedy nedefinovaná. Nelze tak určit, co tento program provede, překladač jej může přeložit na totální nesmysl. Takovýto program je špatně a nemá smysl zkoumat, co provede, je potřeba jej nejprve opravit tak, že proměnnou
a
nainicializujeme. -
Co vypíše následující program?
#include <stdio.h> int main() { int a = a + 1; printf("%d\n", a); return 0; }
Odpověď
Tento program obsahuje nedefinované chování 💣, stejně jako předchozí ukázka. Při inicializaci proměnné
a
používáme její hodnotu, která ale v té době není definovaná. Je to jako kdybychom napsaliint a; a = a + 1;
-
Co vypíše následující program?
#include <stdio.h> int main() { printf("cislo: %d\n"); return 0; }
Odpověď
Tento program obsahuje nedefinované chování 💣. Pokud při použití příkazu
printf
v textu mezi uvozovkami použijeme zástupný znak (%d
), musíme za každý takovýto použitý znak předat této funkci také nějaký celočíselný výraz. V opačném případě bude chování programu nedefinované.