Maturitní otázka č. 9: datové typy

Jednoduché datové typy - popište, co je to jednoduchý datový typ a uveďte příklady v některých programovacích jazycích

V některých jazycích není úplně jasné, co je to jednoduchý datový typ. Proto začneme jazykem C, ve kterém to jasné je.

Naprosto základním a nejdůležitějším datovým typem jsou celá čísla (integer nebo krátce int). S jejich použitím se dá vyjádřit cokoliv, co v počítači můžeme potřebovat. Používají se pro vyjádření pravdivosti výroků: výsledkem například porovnávání (x <= 4) je číslo 0, pokud výrok neplatí (tj. hodnota x je větší než 4), a jakékoli jiné číslo, pokud výrok platí. Pro uložení této hodnoty se používá číslo nejmenšího rozsahu, se kterým můžeme pracovat, tedy osmibitový bajt.

Jeden bajt se také dá použít pro uložení obvyklých písmen: každému písmenu prostě přiřadíme číselnou hodnotu. Naprosto nejčastěji k tomu použijeme tabulku ASCII. Rozsah bajtu 0 až 255 je pro abecedu i s interpunkcí dost nadměrečný, ve skutečnosti se celá vejde do spodní poloviny (0 až 127). Horní polovinu poté můžeme použít pro kódování národních písmen podle nějakého dalšího systému (pro češtinu: ISO-8859-2, CP-1250). Za zmínku stojí systém kódování Unicode UTF-8, který ale pro neobvyklé znaky používá více bajtů (dva až šest). Většina novějších programovacích jazyků pracuje s Unicode přirozeně, ale jazyk C nikoli a česká písmena se zde opravdu chovají jako několik znaků za sebou.

Samozřejmě někdy chceme ukládat prostě celá čísla. Musíme si vybrat rozsah hodnot, který půjde do příslušné proměnné uložit. Obyčejný celočíselný typ (v jazyce C označovaný jako int) na 32bitovém počítači umožňuje uložit rozsah -(2^31) až (2^31)-1. Různé další varianty nabízejí rozsah hodnot menší nebo větší, podle potřeby se znaménkem nebo jen kladné číslo.

Významné je použití čísel jako ukazatelů (pointerů), tedy k adresování paměti. V paměti 32, resp. 64bitového počítače je každý bajt označený číslem velikosti 32, resp. 64 bitů. Zde není vhodné místo rozebírat, jakým způsobem jsou ukazatele užitečné; chápejme je prostě tak, že označují místo v paměti, kde máme uložena nějaká data. Důležitý je potom typ těchto samotných dat - něco jiného je ukazatel na znak, na 32bitové číslo atp.

Celá čísla můžeme používat jako výčtový datový typ. Když chceme například vést databázi v zelinářství, rozhodneme se, že číslem 0 budeme označovat mrkev, číslem 1 květák, číslem 2 ředkvičky atd., podle naší libovůle. Jazyk C nám pro ten účel nabízí speciální typ enum, díky kterému můžeme rovnou přiřadit číslům slovní hodnoty pro lepší čitelnost. Místo čísla 1 potom při dosazování do našeho výčtového typu pak bude stačit napsat KVETAK (či něco jiného, jak si sami zvolíme).

Počítač umí pracovat také s čísly s plovoucí čárkou (floating-point). Práce s nimi je zabudovaná přímo do procesoru. Mohli bychom je jinak také vyjádřit pomocí dvou celých čísel, ale bylo by to mnohem pomalejší. V rámci maturitní otázky 5 se tomuto tématu budeme věnovat podrobněji.

V mnoha jiných jazycích je těžké říct, které datové typy můžeme ještě považovat za jednoduché. Pro určitý náhled můžeme tvrdit, že jednoduché typy jsou všechny, které jsou v jazyce zabudované, a složené vytváříme až my, když poskládáme několik jednoduchých typů do jedné proměnné. Například se potom dá v jazycích, jako je Ruby, Python, PHP apod. považovat i řetězec znaků za jednoduchý datový typ - třeba proto, že nemáme jiný způsob, jak uložit jeden znak. Prakticky vzato jsou ale v takovýchto jazycích všechny datové typy rovnocenné: mohli bychom tedy dokonce tvrdit, že všechny jsou jednoduché.

Ve vámi zvoleném jazyce uveďte operátory nad jednoduchými datovými typy a zmiňte se o pořadí jejich vyhodnocování

Jako první se vybaví operátory aritmetické, tj. sčítání, odečítání, násobení, dělení a počítání zbytku po dělení (operace modulo, obvykle se používá znak %). Operátory jsou ale výrazně obecnější záležitost.

Z určitého úhlu pohledu jsou operátory totéž co funkce, jen zapsané jiným způsobem. Obvykle se setkáváme s operátory binárními (mezi ně patří ty aritmetické), což by odpovídalo funkci, která bere dva parametry. Dále existují operátory unární (berou jediný parametr) a některé jazyky mají dokonce jeden ternární operátor (bere tři parametry). Významné je povšimnout si, že všechny operátory také navrací nějakou hodnotu. U aritmetických je jasné, že navrací číslo vhodného typu, ale v případě některých jiných to může být překvapivé.

Dále známe operátory porovnávací: < <= > >= == !=. Ty navrací pravdivostní hodnotu, což je v některých jazycích číslo a v jiných speciální datový typ boolean.

V obdobných situacích se používají operátory logické, které přijímají pravdivostní parametry a navrací pravdivostní výsledek. První je && neboli and, což odpovídá spojení "a zároveň", v matematice konjunkce - oba parametry musí být pravdivé, aby byl výsledek pravda. Další obvyklý je || neboli or, což odpovída spojce "nebo", v matematice disjunkce - alespoň jeden z parametrů musí být pravdivý. Důležitý je unární operátor inverze: ! neboli not, který navrací opačnou hodnotu (pravdu, pokud je parametr nepravdivý a naopak). Poslední k nim může patřit onen zvláštní ternární operátor ?: - ten ale přijímá a navrací i jiné než pravdivostní typy. Jako ukázkové použití se vezměme print ((x==5) ? "Pět" : "Něco jiného"). Zde přijímá jeden pravdivostní parametr (výsledek výrazu x==5) a dva řetězce "Pět" a "Něco jiného". Funguje právě tak, že pokud je výraz před otazníkem pravdivý, navrátí hodnotu před dvojtečkou, jinak navrátí hodnotu za dvojtečkou. Je totožný, jako když napíšeme:

  if (x==5)
    print "Pět";
  else
    print "Něco jiného";

S tím jediným rozdílem, že ve většině případů je pro čtenáře méně přehledný.

Logické operátory and, not a tento zvláštní ternární mají společnou vlastnost - líné vyhodnocování. V některých případech je výsledek zřejmý už po přečtení prvního parametru a operátor je líný, takže zbylý parametr ani nevyhodnotí. Pokud je to přímo hodnota (jako v naší ukázce), nemá to význam, ale někdy může být parametrem také nějaká funkce, a v tom případě se tato funkce ani nezavolá. Například v jazyce PHP se často setkáváme s příkazy typu connect() or die(); - to znamená, pokud se funkci connect nepodaří připojit k databázi, navrátí hodnotu nepravda a jedině v tom případě se vyhodnotí funkce die; ta pak způsobí okamžité ukončení skriptu, podle potřeby s hlášením o chybě. Podobné triky lze dělat s operátorem and a s ternárním operátorem.

Méně významnou skupinou jsou operátory bitové. Ty jsou podobné logickým, ale pracují s parametry ve dvojkové soustavě bit po bitu. Bitovou obdobou operátoru && je operátor &, který navrací bitový součin zadaných čísel. Operátor | je bitovou obdobou logického ||, navíc přibývá ještě operátor ^, který odpovídá výlučné disjunkci, tedy "jeden nebo druhý, ale ne obojí". Jejich funkci nejsnáze vysvětlí ukázková tabulka; zde A=100 a B=87:

VýrazBitový zápis
A01100100
B01010111
A&B01000100
A|B01110111
A^B00110011

K bitovým operátorům patří ještě unární inverze, psáno znakem tilda (~, též vlnovka), která je bitovou obdobou operátoru not. Patří sem také dva posunové operátory, ty jsou ale hodně odlišné: jako druhý parametr přijímají vždy číslo a provádějí posuv bitů v prvním parametru doleva nebo doprava o tuto vzdálenost. Chybějící místa se doplní nulami, přebývající se zahodí. Tyto operátory jsou názorně značené << pro posuv doleva a >> pro posuv doprava. Například 101101 << 3 = 101101000; všimněme si, že výsledek je přenásobený osmi, tedy dvěma na třetí. Násobení mocninami dvojky je v mnoha situacích užitečné a hodí se, že jej počítač umí provádět rychle.

Velmi důležitý je dosazovací operátor = (v Pascalu podobných jazycích psáno := a například v jazyce R psáno jako <-). V jeho případě právě překvapí, že navrací hodnotu: v téměř všech jazycích právě tu hodnotu, kterou jsme dosadili. Opět se tedy často v souvislosti s databázemi můžeme setkat s konstrukcemi jako:

  while (row = fetch_row()) {
    print(row)
    ...
  }

Uvedený kód získává pomocí funkce fetch_row vždy další řádek z databáze a ukládá ho do proměnné row, aby jej poté mohl vypsat. Funkci fetch_row sice dokážeme zavolat kdykoli, ale ve chvíli, kdy projdeme všechny řádky v databázi, nám navrátí hodnotu nepravda (či nějakou její obdobu) a cyklus v tu chvíli skončí. Dokud funkce navrací data, cyklus běží, protože neprázdná data se automaticky považují za pravdivou hodnotu. V souvislosti s tímto operátorem tvoří výjimku jazyk Python, kde jsou podobné konstrukce zakázané a dosazovací operátor nic nenavrací.

Toto není úplný výčet všech operátorů. V některých jazycích je jich mnohem, mnohem více.

Operátory mají určité pořadí vyhodnocování. Nejlépe na příkladu: 5 + 3 * 7 nejdříve zpracuje operátor násobení a teprve potom sčítání. Násobení přichází v pořadí vyhodnocování o něco dříve. Všechny ostatní operátory mají také své místo, může se to ale lišit jazyk od jazyka. Přehledné tabulky se dají jistě na Webu najít, například pro jazyk C je jedna na Wikipedii. Pokud si člověk není jistý, který operátor se vyhodnotí dříve, měl by raději výraz pořádně uzávorkovat. Je pravděpodobné, že kdokoli bude číst váš kód, narazí na stejný problém; použitím závorky mu ušetříte práci s otevíráním webového prohlížeče.

Vysvětlete význam složených datových typů v jazycích, které je mají

Složený datový typ v sobě, stručně řečeno, obsahuje více hodnot jednoduchých typů. V jazyce C jsou to pole a struktura. Do určité míry obojí slouží opravdu stejnému účelu: pohromadě si zaznamenáme více hodnot. Rozdíl je v tom, že ve struktuře má každá z těchto hodnot svůj název a musíme k nim přistupovat výrazem jako mojeStruktura.nazevHodnoty, zatímco v poli mají všechny stejný typ a stačí tedy říct, kolikátá hodnota nás zajímá. Pozor - například mojePole[10] nenavrací (ve většině jazyků, kromě Pascalu) desátou hodnotu, ale jedenáctou - pole je číslované od nuly. Hodnoty ve struktrurách ani v polích nejsou při spuštění programu nijak upravené a jestli tedy chceme, aby tam byly například nuly, musíme je ručně nastavit.

Pole dvanácti bajtů v jazyce C vytvoříme příkazem char mojePole[12]; - na první bajt přistoupíme jako mojePole[0], na poslední mojePole[11]. Když zkusíme pracovat s hodnotou daleko za okrajem pole (například mojePole[-10000]), program nějakým ošklivým způsobem spadne. Aby programátor nemusel být tak pečlivý, některé jiné jazyky to kontrolují a vyhlásí slušnou chybovou hlášku, pokud s polem zacházíme špatně. Často se potom také setkáváme s poli proměnlivé délky, do kterých můžeme podle potřeby přidávat a ze kterých můžeme odebírat hodnoty - programovací jazyk se o to nějak postará. V Ruby a Pythonu je to typ list.

Strukturu s jedním bajtem a jedním číslem s plovoucí čárkou vytvoříme v C příkazem struct mojeStruktura {char nazevHodnoty; float plovouciCarka};. Je to podobné typu record v Pascalu. Mnohé jiné jazyky struktury vůbec nemají - tentýž účel můžou splnit objekty. Objekty jsou složené datové typy podobné strukturám, které můžou obsahovat také funkce. Jsou základním kamenem objektového programování, kterému se věnuje otázka 22. Pokud s nimi zacházíme jako se strukturou, můžou nám dobře posloužit, aniž bychom se museli do objektového programování pouštět. Ve většině jazyků jsou, v C a Pascalu nikoli.