Lekce 7 - Pole v jazyce C
V předešlém cvičení, Řešené úlohy k 6. lekci Céčka, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Dnes si v tutoriálu představíme datovou strukturu pole a vyzkoušíme si, co všechno umí.
Pole
Představte si, že si chcete uložit nějaké údaje o více prvcích.
Např. chcete v paměti uchovávat 10
čísel, políčka
šachovnice nebo jména 50ti
uživatelů. Asi vám dojde, že v
programování bude nějaká lepší cesta, než začít bušit proměnné
uzivatel1
, uzivatel2
... až uzivatel50
.
Nehledě na to, že jich může být třeba 1000. A jak by se v tom potom
hledalo? Brrr, takle ne
Pokud potřebujeme uchovávat větší množství proměnných
stejného typu, tento problém nám řeší pole. Můžeme si ho
představit jako řadu přihrádek, kde v každé máme uložený jeden prvek.
Přihrádky jsou očíslované tzv. indexy, první má index 0
.
(Na obrázku je vidět pole osmi čísel)
Programovací jazyky se velmi liší v tom, jak s polem pracují. V nižších kompilovaných jazycích, kterým je právě i jazyk C, se musí specifikovat pevná velikost pole ve zdrojovém kódu, která již za běhu nelze měnit. Do pole tedy není možné přidávat další přihrádky a proto musíme myslet na to, aby nám vždy stačilo. Jazyk C nám dále umožňuje vytvořit tzv. dynamicky alokované pole nebo využívat např. spojových seznamů, abychom tento problém obešli. Jedná se však o poměrně pokročilou problematiku, ke které se dostaneme později. Naopak některé interpretované jazyky umožňují nejen deklarovat pole s libovolnou velikostí, ale dokonce tuto velikost na již existujícím poli měnit (např. PHP).
Pro hromadnou manipulaci s prvky pole se používají cykly.
Pole deklarujeme jako běžnou proměnnou, pouze za její název uvedeme hranaté závorky s počtem prvků:
int pole[10];
Slovo pole
je samozřejmě název naší proměnné. Nyní máme
v proměnné pole
pole o velikosti deseti typů int
.
Jelikož jsme pole teprve založili a operační systém nám pro něj
přidělil nějakou paměť, kterou mohla předtím používat jiná aplikace,
nemůžeme se spolehnout na to, že jsou v poli samé nuly. Stejně dobře v
něm zatím mohou být libovolná náhodná čísla.
K prvkům pole poté přistupujeme opět přes hranatou závorku, pojďme na
první index (tedy index 0
) uložit číslo 1
.
int pole[10]; pole[0] = 1;
Plnit pole takto ručně by bylo příliš pracné, použijeme cyklus a
naplníme si pole čísly od 1
do 10
. K naplnění
použijeme for
cyklus:
int pole[10]; int i; for (i = 0; i < 10; i++) { pole[i] = i + 1; }
Pozn.: i + 1
do pole ukládáme proto, že i
jde od nuly a my chceme do pole uložit čísla od 1
.
Abychom pole vypsali, můžeme za předchozí kód připsat:
{C_CONSOLE}
int pole[10];
int i;
for (i = 0; i < 10; i++)
{
pole[i] = i + 1;
}
for (i = 0; i < 10; i++)
{
printf("%d ", pole[i]);
}
{/C_CONSOLE}
Výsledek:
Konzolová aplikace
1 2 3 4 5 6 7 8 9 10
Pole samozřejmě můžeme naplnit ručně a to i bez toho, abychom dosazovali postupně do každého indexu. Použijeme k tomu složených závorek a prvky oddělujeme čárkou:
int cisla[] = {15, 8, 3, 10, 9, 2, 2};
Všimněte si, že nemusíme udávat velikost pole, překladač si ji odvodí z počtu prvků ve výčtu.
Pole často slouží k ukládání mezivýsledků, které se potom dále v programu používají. Když něco potřebujeme 10x, tak to nebudeme 10x počítat, ale spočítáme to jednou a uložíme do pole, odtud poté výsledek jen načteme.
Konstanty
Jelikož musíme uvést velikost pole ve zdrojovém kódu a tuto velikost
obvykle používáme na několika místech, hodilo by se ji mít někde
uloženou. Není nic horšího, než rozmyslet si, že chceme pole místo
15
prvků velké jen 10
a zapomenout někde v dlouhém
kódu nějakou patnáctku přepsat, např. v cyklu, který pole vypisuje. Proto
se velikost polí často ukládá do tzv. konstant.
Konstanta je hodnota libovolného datového typu, kterou nelze změnit.
Můžeme ji chápat jako proměnnou, ze které lze pouze číst. Využívá se
pro případy, kdy chceme definovat v programu nějaké hodnoty, které se sice
za jeho běhu nemění, ale my programátoři bychom je občas mohli chtít v
kódu změnit v rámci nějaké úpravy. Konstanty definujeme pomocí příkazu
#define
, jejich názvy je zvykem zapisovat
VELKYMI_PISMENY
. Hodnotu uvedeme pouze za mezeru nebo tabulátor,
není zde rovná se. Definice uvádíme těsně za příkazy
#include
. Uveďme si kompletní zdrojový kód programu výše tak,
aby pro velikost pole využíval konstantu:
#include <stdio.h>
#include <stdlib.h>
#define POCET 10
int main(int argc, char** argv) {
// Vytvoření pole
int pole[POCET];
// Naplnění pole
int i;
for (i = 0; i < POCET; i++)
{
pole[i] = i + 1;
}
// Výpis pole
for (i = 0; i < POCET; i++)
{
printf("%d ", pole[i]);
}
return (EXIT_SUCCESS);
}
Pozn.: Příkazy začínající #
nejsou příkazy
překladače, ale tzv. preprocesoru. To je program, který zpracovává
zdrojový kód jako první a vkládá do něj určité úseky kódu, aby to měl
překladač následně jednodušší. Preprocesor v našem případě do souboru
s programem vloží definice funkcí ze systémových knihoven
stdio.h
a stdlib.h
a dále nahradí každý výskyt
POCET
za hodnotu 10
. Přesněji je konstanta tzv.
makrem a preprocesor toho umí ještě mnohem více. Pro naše účely to však
nyní opomeneme.
Meze pole
Pozor! Jazyk C žádným způsobem nehlídá, zda se pohybujeme v mezích pole. Je tomu tak kvůli rychlosti. Můžeme tedy např. uložit data na 15. prvek, i když jich má pole pouze 10. Pole je v paměti uloženo jako blok bajtů a céčko počítá podle indexu adresu, na kterou prvek zapíše. Můžeme tedy chybně zapsat na příliš vysoký index a do paměti, která nám nepatří. Tímto způsobem si můžeme nabourat nějaká jiná data v naší aplikaci, která s polem vůbec nesouvisí, ale náhodou byla uložena v paměti za ním. Tyto chyby se obecně velmi těžko hledají a je dobré snažit se jim vyvarovat.
Pole s délkou, kterou určíme až za běhu aplikace
Standard C99 umožňuje deklarovat tzv. VLA (Variable Length Array), pole s délkou, kterou zadáme až za běhu programu. Zafunguje tedy i takovýto kód:
int velikost; printf("Zadej velikost pole a já ho vytvořím: "); scanf("%d", &velikost); int pole[velikost];
Ačkoli je to právě platící standard, je možné, že kód nebude fungovat na všech překladačích. Podobné funkcionality lze docílit také pomocí dynamických polí, se kterými se seznámíme v následující sekci seriálu. Stále platí, že jakmile pole jednou vytvoříme, nemůžeme jeho velikost změnit.
Velikost pole
Pole, které jsme vytvořili, je tzv. staticky alokované. U tohoto typu pole můžeme získat jeho velikost i tímto způsobem:
{C_CONSOLE}
int pole[10]; // Založíme si pole velikosti 10
printf("Velikost pole je: %d", sizeof(pole)/sizeof(int));
{/C_CONSOLE}
Jednoduše zjistíme kolik bajtů zabírá celé pole a toto číslo vydělíme velikostí datového typu jedné položky. Tím získáme počet položek.
Výsledek:
Konzolová aplikace
Velikost pole je: 10
U dynamických polí a textových řetězců tento způsob použít nelze, spíše si na něj nezvykejte.
Seřazení pole
Velmi často se nám stane, že pole prvků potřebujeme seřadit, např.
budeme chtít nalézt nejnižší/nejvyšší plat, počet bodů, náklady,
počet kusů a podobně. Ačkoli je tento typ úlohy s oblibou zadáván
studentům jako algoritmus k procvičení, standardní knihovna jazyka C
poskytuje za tímto účelem funkci qsort()
. Její volání je
poněkud složitější, takže si ho popíšeme spíše intuitivně. Vůbec nic
nám ovšem nebrání, abychom funkci používali již nyní, detailně kód
pochopíme až dále v seriálu.
#include <stdio.h>
#include <stdlib.h>
int porovnej(const void * a, const void * b)
{
return (*(int*)a - *(int*)b);
}
int main(int argc, char** argv)
{
int cisla[] = {15, 8, 3, 10, 9, 2, 2};
qsort(cisla, 7, sizeof(int), porovnej);
int i;
for (i = 0; i < 7; i++)
{
printf("%d ", cisla[i]);
}
return (EXIT_SUCCESS);
}
Kromě funkce main()
zde máme navíc porovnávací funkci,
která definuje jak porovnat 2 prvky v poli. Funkce qsort()
totiž
vnitřně funguje tak, že porovnává vždy 2 prvky mezi sebou a díky tomu ve
finále pole seřadí. Syntaxe hvězdiček si nemusíte všímat, jde o to, že
funkce vrací výraz a - b
, který je kladný pokud je
a > b
, nulový pokud a = b
a záporný pokud
a < b
. Podle této hodnoty qsort()
poté
porovnává. Pokud bychom chtěli pole řadit naopak (sestupně), zadali bychom
zde b - a
.
Samotné vytvoření pole by mělo být jasné. Při volání funkce
qsort()
je třeba uvést kromě pole, které má seřadit, také
počet jeho prvků, velikost jednoho prvku v bajtech a právě porovnávací
funkci. Odteď jsou prvky v poli seřazené a my si je pro kontrolu vypíšeme
pomocí cyklu for
.
Výsledek:
Konzolová aplikace
2 2 3 8 9 10 15
To by pro dnešek stačilo, můžete si s polem hrát.
V následujícím cvičení, Řešené úlohy k 7. lekci Céčka, si procvičíme nabyté zkušenosti z předchozích lekcí.