Programovací jazyk C Komplexní výklad od základů
Kapitola 1

Vývojové prostředí, compiler a linker

Než se vůbec začneme učit psát kód, je potřeba pochopit, co se vlastně děje od chvíle, kdy napíšeme první řádek programu, až do momentu, kdy ho spustíme. Celý tento proces se skládá z několika fází a nástrojů, které na sebe navazují.

Vývojové prostředí (IDE)

Vývojové prostředí, anglicky IDE (Integrated Development Environment), je program, ve kterém píšeme zdrojový kód. Může to být jednoduchý textový editor (jako Notepad++ nebo VS Code) nebo komplexní prostředí jako Code::Blocks, CLion nebo Visual Studio. IDE nám usnadňuje práci tím, že zvýrazňuje syntaxi (různé části kódu se zobrazují různými barvami), nabízí automatické doplňování kódu, upozorňuje nás na chyby ještě před kompilací a umožňuje spouštět a ladit (debugovat) program přímo z prostředí.

Důležité je si uvědomit, že IDE samo o sobě není to, co z našeho kódu vytváří spustitelný program. To dělá kompilátor a linker, které IDE pouze volá za nás.

Kompilátor (Compiler)

Kompilátor je program, jehož úkolem je přeložit náš zdrojový kód napsaný v jazyce C (soubory s příponou .c) do strojového kódu, kterému rozumí procesor počítače. Lidský mozek (a procesor) fungují na zcela odlišných úrovních abstrakce – my chceme psát printf("Hello, world!"), ale procesor rozumí jen binárním instrukcím jako 10110000 01100001. Kompilátor je překladatel mezi těmito dvěma světy.

Kompilace probíhá v několika vnitřních krocích:

Preprocesing je první fáze, při které se zpracovávají direktivy začínající znakem #, jako #include nebo #define. Například #include <stdio.h> způsobí, že preprocesor doslova vloží obsah souboru stdio.h na místo tohoto řádku. #define PI 3.14159 způsobí, že preprocesor nahradí každý výskyt textu PI v kódu hodnotou 3.14159. Výsledkem preprocesingu je rozšířený zdrojový soubor, který ještě stále obsahuje kód v jazyce C, ale bez direktiv.

Vlastní kompilace přeloží tento rozšířený zdrojový kód do assembleru nebo přímo do objektového kódu. Kompilátor přitom kontroluje syntaktické chyby (špatně napsané příkazy), typové chyby a další problémy. Výsledkem jsou tzv. objektové soubory s příponou .o nebo .obj.

Nejrozšířenějším kompilátorem pro C je GCC (GNU Compiler Collection), který se používá na Linuxu a Macu, a MSVC (Microsoft Visual C++) na Windows. Základní použití GCC vypadá takto:

bash
gcc -o mujprogram main.c

Přepínač -o říká, jak má výsledný spustitelný soubor být pojmenován.

Linker

Linker je nástroj, který přichází po kompilátoru. Vezme všechny objektové soubory (náš kód mohl být rozdělen do více .c souborů, každý byl zkompilován zvlášť) a spojí je dohromady do jednoho spustitelného souboru. Zároveň přidá kód ze systémových knihoven, které náš program používá (například kód funkce printf z knihovny libc).

Proč je linker oddělený od kompilátoru? Protože to umožňuje velmi elegantní věc: velké projekty se skládají z tisíců souborů. Pokud změníme jeden soubor, stačí zkompilovat jen ten jeden soubor a pak znovu prolinkovat celý projekt. Nemusíme kompilovat vše od začátku, což šetří obrovské množství času.

Výsledkem linkování je spustitelný soubor (.exe na Windows, bez přípony na Linuxu), který lze přímo spustit.

Kapitola 2

Datové typy – Definice vs. inicializace

Co je datový typ a proč existuje?

Počítač uchovává veškerá data jako jedničky a nuly v paměti. Datový typ říká kompilátoru dvě zásadní věci: kolik paměti (bajtů) má pro danou proměnnou vyhradit, a jak má tuto sekvenci bitů interpretovat. Číslo 65 uložené jako int je číslo 65, ale to samé číslo interpretované jako char je znak A (ASCII kód 65). Bez datových typů bychom nevěděli, jak data v paměti vykládat.

Základní datové typy v jazyce C jsou:

int – celé číslo, typicky 4 bajty (32 bitů), rozsah přibližně ±2 miliardy. Používáme pro počítání, indexování, celé hodnoty obecně.

char – jeden znak, 1 bajt (8 bitů). Interně je to celé číslo 0–255 (nebo -128 až 127), ale interpretujeme ho jako znak podle ASCII tabulky.

float – desetinné číslo s jednoduchou přesností, 4 bajty. Přibližně 7 platných číslic.

double – desetinné číslo s dvojitou přesností, 8 bajtů. Přibližně 15 platných číslic. Ve vědeckých výpočtech preferujeme double před float.

void – speciální typ znamenající „žádný typ", používá se u funkcí, které nic nevrací, nebo u generických ukazatelů.

Existují také modifikátory: unsigned (bez znaménka, jen kladná čísla), short (kratší int), long a long long (delší int).

Definice proměnné

Definice proměnné je okamžik, kdy kompilátoru říkáme: „Vyhraď v paměti místo pro proměnnou tohoto jména a tohoto typu." Syntaxe je jednoduchá:

C
int vek;
double teplota;
char pismeno;

Po definici proměnná existuje v paměti, ale její hodnota je nedefinovaná – obsahuje tzv. „garbage value", tedy náhodnou hodnotu, která se nacházela na dané paměťové adrese před námi. Toto je velmi důležité pochopit, protože čtení nedefinované proměnné je časté zdroj záhadných chyb. V C (na rozdíl od některých jiných jazyků) kompilátor proměnné automaticky neinicializuje nulou.

Inicializace proměnné

Inicializace je přiřazení první (počáteční) hodnoty proměnné. Může se provést buď současně s definicí, nebo až později:

C
int vek = 25; // definice a inicializace najednou
double teplota; // pouze definice
teplota = 36.6; // inicializace (přiřazení) až zde

Dobrá praxe v C je inicializovat proměnné vždy při definici, nebo ihned na začátku jejich životnosti. Takový kód je čistší a bezpečnější.

Je důležité rozumět rozdílu mezi deklarací a definicí: deklarace pouze oznamuje existenci proměnné (nebo funkce) kompilátoru, ale nevyhrazuje paměť. Definice paměť skutečně vyhrazuje. V praxi je pro lokální proměnné většinou deklarace a definice totéž. Rozdíl se projevuje například u klíčového slova extern, kdy v jednom souboru proměnnou definujeme a v druhém ji jen deklarujeme pomocí extern int vek;, čímž říkáme: „Tato proměnná existuje někde jinde, jen ji tady chci používat."

Kapitola 3

Přetypování (Type Casting)

Proč přetypování existuje?

Jazyk C je silně typovaný jazyk, což znamená, že operace mezi různými datovými typy nejsou vždy povoleny nebo přinášejí nečekané výsledky. Přetypování (casting) je mechanismus, kterým výslovně říkáme kompilátoru: „Vím, co dělám – považuj tuto hodnotu za jiný datový typ."

Existují dva druhy přetypování: implicitní (automatické, provádí ho kompilátor) a explicitní (provádíme my sami pomocí syntaxe).

Implicitní přetypování

Kompilátor automaticky převádí typy v situacích, kdy je to „bezpečné", tedy kdy nepřijdeme o informaci. Například pokud sečteme int a double, kompilátor automaticky převede int na double a provede výpočet s dvojnásobnou přesností:

C
int a = 5;
double b = 2.5;
double vysledek = a + b; // a je automaticky převedeno na 5.0

Tato pravidla se nazývají „integer promotion" a „usual arithmetic conversions". Obecná hierarchie je: char → int → long → float → double. Při operaci se oba operandy převedou na „vyšší" z obou typů.

Nebezpečný případ implicitního přetypování nastane, když výsledek přiřadíme do „nižšího" typu. Kompilátor to provede bez varování (nebo jen s varováním), ale přijdeme o data:

C
double pi = 3.14159;
int cele_cislo = pi; // cele_cislo bude 3, desetinná část se ztratí!

Toto je tzv. zúžující konverze (narrowing conversion) a je potenciálně nebezpečná.

Explicitní přetypování

Explicitní přetypování provádíme pomocí syntaxe (typ)výraz:

C
int a = 7;
int b = 2;
double vysledek = (double)a / b; // výsledek je 3.5

Bez přetypování by a / b bylo celočíselné dělení a výsledek by byl 3 (zbytek se zahodí). Přetypováním a na double vnutíme kompilátoru, aby použil dělení s desetinnými čísly. Toto je naprosto typický a velmi častý příklad použití přetypování v praxi.

Dalším příkladem je práce s char jako číslem:

C
char c = 'A';
int ascii_hodnota = (int)c; // ascii_hodnota = 65

Přetypování je mocný nástroj, ale je třeba ho používat uvědoměle. Přetypování nezmění hodnotu v paměti – pouze říká kompilátoru, jak ji má interpretovat nebo převést.

Kapitola 4

Standardní vstupy a výstupy

Princip vstupu a výstupu

Každý program potřebuje komunikovat s okolím – přijímat data od uživatele a zobrazovat výsledky. V jazyce C se vstupy a výstupy realizují prostřednictvím tzv. datových proudů (streams). Standardně existují tři:

stdin – standardní vstup, typicky klávesnice

stdout – standardní výstup, typicky obrazovka (terminál)

stderr – standardní chybový výstup, také obrazovka, ale odděleně od stdout

Tyto proudy jsou abstrakcí – program neví (ani nepotřebuje vědět), zda čte z klávesnice nebo ze souboru. Tato abstrakce je velmi elegantní a výkonná.

Funkce printf – výstup

Funkce printf (z knihovny stdio.h) slouží k formátovanému výpisu na stdout. Její základní syntaxe je:

C
printf(formátovací_řetězec, hodnota1, hodnota2, ...);

Formátovací řetězec je text, do kterého vkládáme formátovací specifikátory – zástupné znaky, které říkají, jak vypsat danou hodnotu:

%d nebo %i – celé číslo (int)
%f – desetinné číslo (float nebo double)
%c – znak (char)
%s – řetězec znaků
%lf – double (v novějších standardech %f funguje i pro double)
%p – ukazatel (adresa v paměti)
%x – celé číslo v hexadecimální soustavě

Příklady:

C
int vek = 25;
double vyska = 180.5;
printf("Věk: %d, Výška: %.1f cm\n", vek, vyska);
// Výstup: Věk: 25, Výška: 180.5 cm

Znak \n je tzv. escape sekvence reprezentující nový řádek. Dalšími jsou \t (tabulátor), \\ (zpětné lomítko) nebo \" (uvozovka uvnitř řetězce).

Číslo za tečkou u %f (např. %.2f) určuje počet desetinných míst. %5d znamená, že číslo bude zarovnáno na šířku 5 znaků.

Funkce scanf – vstup

Funkce scanf slouží ke čtení formátovaného vstupu ze stdin. Syntaxe je podobná printf, ale s důležitým rozdílem – předáváme adresy proměnných (pomocí operátoru &):

C
int vek;
printf("Zadejte věk: ");
scanf("%d", &vek);

Proč předáváme adresu? Protože scanf potřebuje vědět, kam v paměti má uloženou hodnotu zapsat. Pokud bychom předali jen vek, předali bychom hodnotu (která je nedefinovaná), ne místo v paměti. Adresa &vek říká: „Zapiš přečtené číslo na tuto adresu v paměti." Toto je fundamentální princip jazyka C a přímý úvod do problematiky ukazatelů.

Funkce scanf vrací počet úspěšně přečtených hodnot, což lze použít pro kontrolu chyb:

C
if (scanf("%d", &vek) != 1) {
    printf("Chyba při čtení vstupu!\n");
}

Pro čtení celého řádku textu (včetně mezer) se scanf nehodí – lepší je použít fgets:

C
char jmeno[50];
fgets(jmeno, 50, stdin);
Kapitola 5

Řídicí struktury

Řídicí struktury určují tok programu – v jakém pořadí se příkazy vykonávají. Bez nich by program vždy provedl instrukce shora dolů bez jakékoli flexibility.

Větvení – if / else

Základní větvení je podmíněné: „Pokud platí podmínka, proveď toto, jinak proveď tamto." Syntaxe:

C
if (podminka) {
    // kód vykonaný pokud je podmínka pravdivá
} else if (jina_podminka) {
    // kód vykonaný pokud platí jiná podmínka
} else {
    // kód vykonaný jinak
}

V jazyce C neexistuje typ bool (pravda/nepravda) v základní verzi – každé číslo různé od nuly je chápáno jako true (pravda) a nula jako false (nepravda). Tedy if (5) je pravdivé, if (0) nepravdivé. Toto je důležité vědět, protože to může vést k nečekaným situacím:

C
int x = 5;
if (x = 3)  // POZOR! Toto je přiřazení, ne porovnání! Výsledek je vždy pravdivý (3 != 0)
if (x == 3) // Toto je správné porovnání
Záměna = a == je jednou z nejčastějších chyb začátečníků v C.
switch – case

Pro situace, kdy chceme porovnat jednu proměnnou s mnoha různými hodnotami, je elegantnější switch:

C
int den = 3;
switch (den) {
    case 1:
        printf("Pondělí\n");
        break;
    case 2:
        printf("Úterý\n");
        break;
    case 3:
        printf("Středa\n");
        break;
    default:
        printf("Jiný den\n");
        break;
}

Klíčové slovo break je zásadní – bez něj by program pokračoval dál do dalšího case (tzv. fall-through chování). To je buď záměrné (pokud chceme, aby více case sdílelo stejný kód), nebo velmi zákeřná chyba. Větev default se vykoná, pokud žádný case neodpovídá.

switch funguje pouze s celočíselnými hodnotami (int, char) – nelze ho použít přímo s float, double nebo řetězci.

Cyklus for

Cyklus for je nejčastěji používaný cyklus, zejména tam, kde předem víme, kolikrát chceme iterovat:

C
for (inicializace; podmínka; aktualizace) {
    // tělo cyklu
}

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

Cyklus funguje takto: nejprve se provede inicializace (int i = 0), poté se zkontroluje podmínka (i < 10), pokud je pravdivá, provede se tělo cyklu, poté aktualizace (i++), a celý proces se opakuje. i++ je zkratka pro i = i + 1.

Proměnná i se jmenuje iterátor nebo řídící proměnná a slouží k počítání průchodů cyklem. Indexování v C začíná od nuly, nikoli od jedničky – tato konvence platí pro pole, ukazatele a cykly obecně.

Cyklus while

Cyklus while opakuje tělo dokud platí podmínka. Podmínka se testuje před každým průchodem:

C
int n = 1;
while (n <= 100) {
    printf("%d\n", n);
    n++;
}

while je vhodný, když předem nevíme, kolikrát se má cyklus opakovat – závisí to na vstupu nebo nějaké dynamické podmínce.

Cyklus do – while

Podobný while, ale podmínka se testuje až po prvním průchodu – tělo cyklu se tedy provede vždy alespoň jednou:

C
int cislo;
do {
    printf("Zadejte kladné číslo: ");
    scanf("%d", &cislo);
} while (cislo <= 0);

Toto je ideální pro validaci vstupu: chceme se uživatele zeptat alespoň jednou, a opakujeme dokud nedá správnou odpověď.

break a continue

break okamžitě ukončí celý cyklus a přejde na kód za ním. continue přeskočí zbytek aktuálního průchodu tělem a přejde na další iteraci.

C
for (int i = 0; i < 10; i++) {
    if (i == 5) continue; // přeskočí číslo 5
    if (i == 8) break;    // zastaví se na 8
    printf("%d\n", i);
}
Kapitola 6

Práce s polem

Co je pole a proč ho potřebujeme?

Pole (array) je lineárně uspořádaná kolekce prvků stejného datového typu uložená v paměti jako souvislý blok. Představ si pole jako řadu šuplíků vedle sebe – každý šuplík má stejnou velikost a všechny jsou hned vedle sebe.

Bez polí bychom museli pro 100 studentů vytvořit 100 samostatných proměnných: student1, student2, ..., student100. Pole nám umožňuje toto zapsat elegantně: int studenti[100].

Jednorozměrné pole

Definice a inicializace jednorozměrného pole:

C
int cisla[5];                         // pole 5 celých čísel (neinicializované)
int cisla[5] = {10, 20, 30, 40, 50}; // pole s předdefinovanými hodnotami
int cisla[] = {10, 20, 30, 40, 50};  // kompilátor sám zjistí velikost (5)

Přístup k prvkům pole se provádí pomocí indexu v hranatých závorkách. Index je vždy celé číslo a začíná od nuly:

C
printf("%d\n", cisla[0]); // první prvek: 10
printf("%d\n", cisla[4]); // pátý (poslední) prvek: 50
cisla[2] = 99;             // změna třetího prvku

Proč indexujeme od nuly? Toto je přímý důsledek toho, jak fungují ukazatele v C. Index je ve skutečnosti offset (posun) od začátku pole v paměti. Prvek na „začátku" má posun 0, druhý prvek má posun 1 atd. Tento princip bude jasnější u ukazatelů.

Přetečení pole (buffer overflow) je kriticky nebezpečná chyba: pokud přistoupíme k cisla[5] (šestý prvek v poli o 5 prvcích), přistupujeme mimo vyhrazenou paměť. Kompilátor toto nezabrání – C nám v tomto dává plnou svobodu, ale i plnou zodpovědnost.
Načtení, výpis a manipulace s prvky

Typický průchod polem pomocí cyklu for:

C
int pole[5] = {3, 1, 4, 1, 5};
int n = 5;

// Výpis všech prvků
for (int i = 0; i < n; i++) {
    printf("pole[%d] = %d\n", i, pole[i]);
}

// Načtení od uživatele
for (int i = 0; i < n; i++) {
    printf("Zadejte prvek %d: ", i);
    scanf("%d", &pole[i]);
}

// Součet všech prvků
int soucet = 0;
for (int i = 0; i < n; i++) {
    soucet += pole[i]; // zkratka pro soucet = soucet + pole[i]
}
printf("Součet: %d\n", soucet);
Dvourozměrné pole

Dvourozměrné pole si lze představit jako tabulku (matici) s řádky a sloupci. V paměti je stále uloženo jako jeden souvislý blok, ale kompilátor pro nás zajistí správné adresování:

C
int matice[3][4]; // 3 řádky, 4 sloupce

int matice[2][3] = {
    {1, 2, 3}, // první řádek
    {4, 5, 6}  // druhý řádek
};

Přístup k prvkům pomocí dvou indexů – první je index řádku, druhý index sloupce:

C
printf("%d\n", matice[1][2]); // druhý řádek, třetí sloupec: hodnota 6

Průchod dvourozměrným polem vyžaduje vnořené cykly:

C
int radky = 2, sloupce = 3;
for (int i = 0; i < radky; i++) {
    for (int j = 0; j < sloupce; j++) {
        printf("%d ", matice[i][j]);
    }
    printf("\n"); // nový řádek po každém řádku matice
}

V paměti je matice[3][4] uložena jako 12 po sobě jdoucích int hodnot. matice[i][j] je na pozici i * pocet_sloupcu + j od začátku pole. Toto je důvod, proč musíme při předávání dvourozměrného pole funkci vždy specifikovat počet sloupců.

Kapitola 7

Funkce

Proč funkce vůbec existují?

Funkce jsou základním nástrojem pro strukturování programu. Bez funkcí by byl každý program jedním obrovským blokem kódu, kde bychom stále dokola opakovali stejné části. Funkce nám umožňují:

1. Opakovaně použít kus kódu bez jeho duplikace
2. Rozdělit složitý problém na menší, lépe pochopitelné části
3. Skrýt detaily implementace za jednoduchým rozhraním (abstrakce)
4. Testovat každou část programu izolovaně

Popis a použití funkcí

Funkci si představ jako samostatnou „mini-továrnu": dostane vstupní materiál (parametry), provede nějakou operaci, a vrátí výsledek (návratová hodnota). Nebo nemusí vrátit nic (typ void).

Definice, tělo a prototyp

Definice funkce obsahuje: návratový typ, název, parametry a tělo s příkazem return.

C
// Funkce, která spočítá součet dvou čísel a vrátí výsledek
int secti(int a, int b) {
    int vysledek = a + b;
    return vysledek;
}

Prototyp funkce (také deklarace funkce) je deklarace bez těla, ukončená středníkem. Říká kompilátoru: „Tato funkce existuje, má tyto parametry a tento návratový typ – definici najdeš jinde."

C
int secti(int a, int b); // prototyp

Proč prototyp potřebujeme? V C se kód zpracovává shora dolů. Pokud main() volá funkci secti, ale definice secti je až za main(), kompilátor by si stěžoval. Prototyp na začátku souboru tento problém vyřeší – kompilátoru sdělíme existenci funkce předem, definici dodáme až níže.

C
#include <stdio.h>
int secti(int a, int b); // prototyp nahoře

int main() {
    int vysledek = secti(3, 5);
    printf("Výsledek: %d\n", vysledek);
    return 0;
}

int secti(int a, int b) { // definice níže
    return a + b;
}
Bez parametrů a s parametry

Funkce nemusí mít žádné parametry:

C
void pozdrav() {
    printf("Dobrý den!\n");
}

Nebo může mít libovolný počet parametrů:

C
double mocnina(double zaklad, int exponent) {
    double vysledek = 1.0;
    for (int i = 0; i < exponent; i++) {
        vysledek *= zaklad;
    }
    return vysledek;
}
Předávání parametrů hodnotou (pass by value)

V jazyce C se parametry standardně předávají hodnotou – funkce dostane kopii hodnoty, nikoli originál. Změna kopie uvnitř funkce neovlivní původní proměnnou:

C
void zdvoj(int x) {
    x = x * 2; // mění jen lokální kopii
}

int main() {
    int a = 5;
    zdvoj(a);
    printf("%d\n", a); // vypíše 5, ne 10!
    return 0;
}

Pokud chceme, aby funkce mohla měnit původní proměnnou, musíme předat ukazatel na ni (pass by pointer) – to je úzce spjato s kapitolou o ukazatelích.

Rekurze

Funkce může volat sama sebe – to se nazývá rekurze. Je to elegantní způsob řešení problémů, které lze přirozeně rozdělit na menší problémy stejného typu:

C
int faktorial(int n) {
    if (n <= 1) return 1; // základní případ (base case)
    return n * faktorial(n - 1); // rekurzivní volání
}

Každá rekurzivní funkce musí mít základní případ, který zastaví rekurzi – jinak by se funkce volala donekonečna.

Kapitola 8

Struktury a ukazatele

Struktury (struct)

Struktura umožňuje seskupit více různých datových typů pod jeden název. Zatímco pole obsahuje prvky stejného typu, struktura může kombinovat různé typy:

C
struct Student {
    char   jmeno[50];
    int    vek;
    double prumer;
};

Vytvoření proměnné tohoto typu a přístup k členům:

C
struct Student s1;
s1.vek = 21;
s1.prumer = 1.5;
// nebo inicializace najednou:
struct Student s2 = {"Jan Novák", 21, 1.5};
printf("Jméno: %s, Věk: %d\n", s2.jmeno, s2.vek);

Pro usnadnění se používá typedef, který vytvoří alias pro typ:

C
typedef struct {
    char   jmeno[50];
    int    vek;
    double prumer;
} Student;

// Nyní můžeme psát jen:
Student s1;
Ukazatele (Pointers)

Ukazatel je proměnná, která obsahuje adresu jiné proměnné v paměti. Toto je jeden z nejvýkonnějších a zároveň nejtěžších konceptů jazyka C.

Každá proměnná v programu je uložena v paměti na nějaké adrese. Adresu proměnné zjistíme operátorem &:

C
int x = 42;
printf("Hodnota x: %d\n", x);
printf("Adresa x: %p\n", &x); // vypíše např. 0x7fff5fbff5ac

Ukazatel deklarujeme pomocí * před názvem:

C
int *p; // p je ukazatel na int
p = &x; // p nyní obsahuje adresu proměnné x

Pro přístup k hodnotě, na kterou ukazatel ukazuje, používáme dereferenci (opět operátor *):

C
printf("%d\n", *p); // vypíše 42 (hodnotu na adrese, kde je x)
*p = 100;           // změní hodnotu x na 100 prostřednictvím ukazatele!
printf("%d\n", x);  // vypíše 100

Proč jsou ukazatele tak důležité? Protože umožňují: předávat funkci odkaz na proměnnou, takže ji funkce může skutečně změnit; pracovat s dynamicky alokovanou pamětí (malloc, free); efektivně pracovat s poli (název pole je ve skutečnosti ukazatel na jeho první prvek); implementovat složité datové struktury jako spojové seznamy, stromy atd.

Pole a ukazatele jsou v C úzce propojeny: pole[i] je totéž co *(pole + i). Název pole je ukazatel na první prvek, a pole + i je aritmetika ukazatelů – posun o i prvků.
Kapitola 9

Knihovny

Co je knihovna?

Knihovna je kolekce předem napsaných a zkompilovaných funkcí, které můžeme v našem programu používat. Nemusíme vymýšlet kolo pokaždé znovu – pro tisíce běžných úkolů existují hotové, otestované funkce. Standardní knihovny jazyka C jsou součástí každého prostředí a jsou definovány standardem jazyka. Připojujeme je pomocí direktivy #include.

Nejdůležitější standardní knihovny

stdio.h (Standard Input/Output) – Funkce pro vstup a výstup: printf, scanf, fgets, fopen, fclose, fprintf atd. Tato knihovna je nepostradatelná v prakticky každém programu.

stdlib.h (Standard Library) – Různé utility: malloc a free (dynamická alokace paměti), exit (ukončení programu), atoi a atof (převod řetězce na číslo), rand a srand (pseudonáhodná čísla), abs (absolutní hodnota).

string.h – Funkce pro práci s řetězci (pole znaků): strlen (délka řetězce), strcpy (kopírování řetězce), strcmp (porovnání řetězců), strcat (spojení řetězců), strchr (hledání znaku v řetězci).

math.h – Matematické funkce: sqrt (odmocnina), pow (mocnina), sin, cos, tan, log, ceil, floor, fabs (absolutní hodnota pro double). Při linkování je třeba přidat -lm (link math library):

bash
gcc program.c -o program -lm

ctype.h – Funkce pro práci se znaky: isdigit (je to číslice?), isalpha (je to písmeno?), isupper, islower, toupper, tolower.

time.h – Funkce pro práci s časem a datem: time, clock, difftime, localtime.

Jak knihovny fungují?

Soubor .h (header file, hlavičkový soubor) obsahuje pouze deklarace funkcí a typů – říká kompilátoru, že tyto funkce existují a jaké mají parametry a návratový typ. Skutečný kód (definice) je v předkompilovaných binárních souborech (.a, .so na Linuxu nebo .lib, .dll na Windows), které linker připojí k našemu programu.

Tím je C architektura velmi čistá: odděluje rozhraní (co funkce dělá) od implementace (jak to dělá). Toto oddělení je základním principem softwarového inženýrství obecně.

Shrnutí

Jazyk C je nízkoúrovňový, ale extrémně mocný jazyk, který nám dává přímou kontrolu nad pamětí a hardwarem. Každý z výše popsaných konceptů na sobě navzájem staví – datové typy jsou základem, funkce organizují kód, pole umožňují hromadnou práci s daty, ukazatele propojují vše dohromady a dávají programátorovi plnou kontrolu nad tím, co se děje v paměti počítače. Pochopení těchto principů není jen o zvládnutí jazyka C – je to pochopení toho, jak počítač skutečně funguje.

🏠