7. díl - Pole v jazyce C

C++ Základní konstrukce C Pole v jazyce C

Minule jsme si v našem seriálu o programování ukázali cykly v jazyce C. Dnes si 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.

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říhrá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];

Pole je samozřejmě název naší proměnné. Nyní máme v proměnné pole pole o velikosti deseti 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:

for (i = 0; i < 10; i++)
{
        printf("%d ", pole[i]);
}

Výsledek:

Naplnění a výpis pole v programovacím jazyce C

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 15ti 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ého efektu 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:

int pole[10]; // Založíme si pole velikosti 10
printf("Velikost pole je: %d", sizeof(pole)/sizeof(int));

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:

Zjištění velikosti pole v jazyce C

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 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. 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:

Seřazení pole v jazyce C

To by pro dnešek stačilo, můžete si s polem hrát. Příště si konečně uvedeme jak pracovat s textovými řetězci a naučíme se ukládat text do proměnných :-)


 

  Aktivity (2)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (6 hlasů) :
4.666674.666674.666674.666674.66667


 


Miniatura
Předchozí článek
Cvičení k 6. lekci Céčka
Miniatura
Všechny články v sekci
Programování v jazyce C - Základy
Miniatura
Následující článek
Cvičení k 7. lekci Céčka

 

 

Komentáře
Zobrazit starší komentáře (3)

Avatar
Michal
Člen
Avatar
Michal :

Dobrý den,

Jen bych chtěl upozornit na malou drobnost :) .

int pole[10];
int i = 0;
for (i = 0; i < 10; i++)
{
pole[i] = i + 1;
}
Na začátku je napsané int i = 0; v tom případě v cyklu, může být jen (i; i < 10; i++), anebo smazat int i = 0 a do cyklu napsat (int i = 0; i < 10; i++).

Jinak povedený a poučný článek :).

Editováno 27.10.2014 13:52
 
Odpovědět 27.10.2014 13:51
Avatar
Odpovídá na Michal
Michal Žůrek (misaz):
(int i = 0; i < 10; i++)

funguje jenom v C99.

Odpovědět 27.10.2014 13:53
Nesnáším {}, proto se jim vyhýbám.
Avatar
Libor Šimo (libcosenior):

Veľmi pekne a stručne si to napísal. ;)

Editováno 28.10.2014 11:38
Odpovědět 28.10.2014 11:38
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Michal
David Čápka:

Díky, to mi tam uteklo, opraveno.

Odpovědět 28.10.2014 12:06
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
lastp
Redaktor
Avatar
lastp:

Nově vytvořené pole může na začátku obsahovat libovolná náhodná data, ale nikdy to nejsou data z jiné aplikace. Když vám operační systém přidělí paměť, kterou předtím používala jiná aplikace, pak ji vždy nejdříve vynuluje.

 
Odpovědět 29.10.2014 18:49
Avatar
ra3sk
Člen
Avatar
ra3sk:

Funguje define aj s textom?

 
Odpovědět 25.10.2015 13:29
Avatar
Martin Halada:

Dá se to pole seřadit ještě jiným způsobem?

Odpovědět 21.12.2015 21:46
Počítač je zařízení sloužící k řešení problémů, které by bez něj vůbec nevznikly.
Avatar
JohnLuther
Člen
Avatar
JohnLuther:

>Syntaxe hvězdiček si nemusíte všímat, jde o to, že funkce vrací výraz a - b, který je záporný pokud je a > b, nulový pokud a = b a kladný pokud a < b. Podle této hodnoty qsort() poté porovnává. Pokud bychom chtěli pole řadit naopak (sestupně), zadali bychom zde b - a.
Nemá to byť celé naopak ?

 
Odpovědět 6. ledna 14:29
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovědět 1. července 16:52
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
DarkCoder
Člen
Avatar
Odpovídá na David Čápka
DarkCoder:

Pěkný článek, má úroveň. Je v něm vidět způsob práce s poli, učí dobrým návykům a ukazuje i na místa na co si dát pozor (deklarace, inicializace, načtení, výpis, přiřazení, použití). Vyzdvihl bych zejména dvě místa a to odstavec Konstanty a Meze pole. Chybí snad jen informace o způsobu kopírování pole do jiného pole. Ale jinak článek obsahuje vše nejdůležitější pro práci jednorozměrného pole s čísly. Dobrá práce!

Editováno 6. října 12:44
 
Odpovědět 6. října 12:43
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 13. Zobrazit vše