Další šance dokončit svůj projekt a získat ceny v hodnotě 10.000 Kč! Pokračování úspěšné letní soutěže - ITnetwork winter

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

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

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. 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 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 (16)

 

 

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

Avatar
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
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
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."
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
0
Člen
Avatar
0:20. října 8:55

Nevíte někdo co dělám špatně??

 
Odpovědět
20. října 8:55
Avatar
Odpovídá na 0
Reaktivní uživatel:20. října 10:42

Kolikrát ještě to budeš psát? Viz lekci 3.

Odpovědět
20. října 10:42
Kdo je připraven, toho zaskočí něco jiného
Avatar
0
Člen
Avatar
Odpovídá na Reaktivní uživatel
0:20. října 18:02

Rád bych to nepsal ale potřeboval jsem odpověď

 
Odpovědět
20. října 18:02
Avatar
Odpovídá na 0
Honza Černoch:24. listopadu 18:37

zkus tam dat:
int b;
b = a * 2;
printf(''%d", b)

 
Odpovědět
24. listopadu 18:37
Avatar
Odpovídá na Honza Černoch
Honza Černoch:24. listopadu 18:39

printf(''%d", b);

 
Odpovědět
24. listopadu 18:39
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 39. Zobrazit vše