Programovací jazyk C
Úplný přehled od vývojového prostředí po ukazatele a knihovny
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í, 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 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:
gcc -o mujprogram main.c
Přepínač -o říká, jak má výsledný spustitelný soubor být pojmenován.
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.
Datové typy – Definice vs. inicializace
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é 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á:
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 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:
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."
Přetypování (Type Casting)
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).
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í:
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:
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í provádíme pomocí syntaxe (typ)výraz:
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:
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.
Standardní vstupy a výstupy
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 (z knihovny stdio.h) slouží k formátovanému výpisu na stdout. Její základní syntaxe je:
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:
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 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 &):
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:
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:
char jmeno[50]; fgets(jmeno, 50, stdin);
Ří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.
Základní větvení je podmíněné: „Pokud platí podmínka, proveď toto, jinak proveď tamto." Syntaxe:
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:
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í
= a == je jednou z nejčastějších chyb začátečníků v C.Pro situace, kdy chceme porovnat jednu proměnnou s mnoha různými hodnotami, je elegantnější switch:
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 je nejčastěji používaný cyklus, zejména tam, kde předem víme, kolikrát chceme iterovat:
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 opakuje tělo dokud platí podmínka. Podmínka se testuje před každým průchodem:
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.
Podobný while, ale podmínka se testuje až po prvním průchodu – tělo cyklu se tedy provede vždy alespoň jednou:
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 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.
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); }
Práce s polem
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].
Definice a inicializace jednorozměrného pole:
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:
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ů.
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.Typický průchod polem pomocí cyklu for:
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 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í:
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:
printf("%d\n", matice[1][2]); // druhý řádek, třetí sloupec: hodnota 6
Průchod dvourozměrným polem vyžaduje vnořené cykly:
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ů.
Funkce
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ě
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 funkce obsahuje: návratový typ, název, parametry a tělo s příkazem return.
// 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."
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.
#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; }
Funkce nemusí mít žádné parametry:
void pozdrav() { printf("Dobrý den!\n"); }
Nebo může mít libovolný počet parametrů:
double mocnina(double zaklad, int exponent) { double vysledek = 1.0; for (int i = 0; i < exponent; i++) { vysledek *= zaklad; } return vysledek; }
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:
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.
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:
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.
Struktury a ukazatele
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:
struct Student { char jmeno[50]; int vek; double prumer; };
Vytvoření proměnné tohoto typu a přístup k členům:
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:
typedef struct { char jmeno[50]; int vek; double prumer; } Student; // Nyní můžeme psát jen: Student s1;
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 &:
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:
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 *):
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[i] je totéž co *(pole + i). Název pole je ukazatel na první prvek, a pole + i je aritmetika ukazatelů – posun o i prvků.Knihovny
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.
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):
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.
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ě.
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.