Java týden Body zdarma
Využij podzimních slev a získej od nás až 40 % bodů zdarma! Více zde
Pouze tento týden sleva až 80 % na Java e-learning!

Lekce 7 - Pole v jazyce C

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Cykly v Céčku, jsme si ukázali cykly. 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.

Struktura pole

(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ří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.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Abychom pole vypsali, můžeme za předchozí kód připsat:

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

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

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:

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. 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 příští lekci, Textové řetězce v jazyce C, si konečně uvedeme jak pracovat s textovými řetězci a naučíme se ukládat text do proměnných :-)


 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
14 hlasů
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 sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Předchozí článek
Cykly v Céčku
Všechny články v sekci
Základní konstrukce jazyka C
Miniatura
Následující článek
Cvičení k 7. lekci Céčka
Aktivity (11)

 

 

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

Avatar
Patrik Pastor:23. srpna 22:32

to jsi mi ale stale nedal zadnou odpoved. Rekl jsi mi syntax, ale na to jsem se.prave Neptal (jeste jsem to zduraznoval). Me zajima JAK VNITRNE pointer, ktery ma ulozenou adresu promenne, se dostane k hodnote teto adresy (protoze pointer si drzi JENOM adresu, ale nedrzi si uz hodnotu na teto adrese) - to je na co se ptam, jak probiha -jeste jednou VNITRNE, v pameti - dereference. To znamena jak se pointer ktery si drzi adresu promenne, dostane na hodnotu teto adresy (kterou uz si ale NEdrzi).

 
Odpovědět 23. srpna 22:32
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:23. srpna 22:42

Je to uvedené v posledním odstavci mého příspěvku. Pointer se nedostává k hodnotě uložené na adrese, kterou uchovává. K tomu se dostává operační systém. Překladač přeloží program do počítačem srozumitelného a čitelného tvaru, po spuštění programu jsou instrukce čteny operačním systémem a ten si přistupuje k daným datům. Když zná adresu, může se na tuto adresu podívat a hodnotu přečíst a vrátit.

Odpovědět 23. srpna 22:42
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:23. srpna 22:47

takze pointer sice ulehcuje misto, tim, ze se pouze odkazuje, ale na ukor prekladace - ktery musi jeste prelozit z adresy hodnotu. Tak jaka je potom vyhoda pointeru, kdyz prekladac stejne udala to co bych udelal.ja bez pointeru - zkopiroval promennou, misto abych se na ni referencoval. vysledne mnozstvi strojovych instrukci je stejne - bud to skopiruju (je to narocne) - ale jinak to udal prekladac - takze to vyjde na stejno

 
Odpovědět 23. srpna 22:47
Avatar
Patrik Valkovič
Šéfredaktor
Avatar
Odpovídá na Patrik Pastor
Patrik Valkovič:23. srpna 22:52

Tvlj příspěvek na StackOverflow byl míněný dobře, jen vůbec nedává smysl.
Ukazatel je ve skutečnosti pouze adresa - číslo. Tj. teoreticky můžeš napsat int* x = 0x4F3C4F18 (ta koncovka u nedává smysl). Poté, když dereferencuješ takový ukazatel, říkáš tím "dej mi hodnotu na adrese 0x4F3C4F18". Takhle si to lze představit zjednodušeně.
Problém 1 - velikost ukazatele se může lišit mezi platformama, někde může mít 32 bitů, někde 64 a někde jen 24. Nemůžeš tedy do proměnné uložit adresu, to nedává smysl.
Problém 2 - operační systém si paměť hlídá a když mu sáhneš někam, kam to nemáš povoleno, tak program násilně ukončí (viz Terminated). Dnešní operační systémy aplikují tzv. stránkování (memory paging), takže pokud chceš vědět, jak to funguje pod kapotou, nastuduj si tohle.

Odpovědět 23. srpna 22:52
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
Odpovídá na Patrik Valkovič
Patrik Pastor:23. srpna 22:56

jop diky. Docela zajimave tema uz sem se na to dival na wiki - memory paging , ale je to jeste celkem hard.
me byc treba zajimalo, ale jestli je mozne i toto zmenit - jak si OS managuje pamet. V cem jsou instrukce napsany a jestli i toto je mozne naprogramovat. Je to napsane v asm? nebo uz rovnou v binarce?... jsem na ubuntu =, tak by snad byla moznost jak se podivat i do techto systemovych souboru se zdrojaky :D

 
Odpovědět 23. srpna 22:56
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Patrik Valkovič
Šéfredaktor
Avatar
Odpovídá na Patrik Pastor
Patrik Valkovič:23. srpna 23:05

Ne, způsob práce s paměti z hlediska operačního systému nezměníš. Ono je to koneckonců pro programátora skryté.
Jinak se zdá, že je to psáno v C - https://github.com/…/mm/memory.c

Odpovědět 23. srpna 23:05
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
Odpovídá na Patrik Valkovič
Patrik Pastor:23. srpna 23:08

kdyz je to skryte, tak proc to dali na github. Jak skryte? pokud je to na harddisku, musi byt prece zpusob jak se k tomu dostat

 
Odpovědět 23. srpna 23:08
Avatar
Patrik Valkovič
Šéfredaktor
Avatar
Odpovídá na Patrik Pastor
Patrik Valkovič:23. srpna 23:12

To, jak operační systém nakládá z pamětí, nezměníš z programu a ani tě to nezajímá, protože operační systém ti prostě vrátí adresu, kterou můžeš používat. Kam adresa ukazuje tě jako programátora nezajímá. Jestli operační systém používá segmentaci, stránkování nebo hardwarové adresy tě taky nezajímá - z programu je to pro tebe skryté a ty jen využíváš operační systém.
Změnit by to šlo - kdyby jsi upravil kód co jsem ti poslal, překompiloval Linux kernel a spustil ten. Na harddisku máš binární kód, ne zdrojáky.

Odpovědět 23. srpna 23:12
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
Odpovídá na Patrik Valkovič
Patrik Pastor:23. srpna 23:16

kdy to slo prekompilovat, taky asi uz byl na to nejaky malware ne? ktery bi zmenil kernel sve obeti se svym vlastni malcodem (pokud by obet mela kernel a ne win)

 
Odpovědět 23. srpna 23:16
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:23. srpna 23:42

Výhoda pointeru? Když pracuješ s polem jako parametr funkce, předáváš ve skutečnosti ukazatel na pole, když pracuješ se soubory, vytváříš na něj ukazatel, když předáváš strukturu pomocí ukazatele, předáváš pouze její adresu, pokud pro předání struktury nepoužiješ ukazatel, předáváš ji jako celek. Což bude podstatně pomalejší. Veškerá práce s objekty, totéž co u struktury. Přístup k prvkům pole pomocí ukazatele je efektivnější nežli indexace pole. Když chceš měnit hodnoty argumentů ve funkci, musíš předávat ukazatel na ně. Když chceš zjistit kolik prvků je mezi dvěma prvky pole, použiješ ukazatele (rozdíl pointerů). Pole a ukazatele, to je jedna z nejsilnějších věcí v C. Ukazatelová aritmetika. Když chceš vracet vícero hodnot z funkce používáš ukazatele. Funkce mají svoje adresy, pro vyvolání funkcí opět můžeš využít ukazatele. Chceš-li aby tvé aplikace byly efektivnější, pracuj s ukazateli. Ukazatelé Ti poskytují obrovskou sílu, ale nesmíš dovolit aby ovládly oni tebe, jinak za to draze zaplatíš.

Odpovědět 23. srpna 23:42
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
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 34. Zobrazit vše