BF - Easter Office week
Tento týden až 80% sleva na e-learning MS Office!
80 % bodů zdarma díky naší Velikonoční akci!

Lekce 4 - Typový systém podruhé: Datové typy

V předešlém cvičení Řešené úlohy k 1.-3. lekci C# .NET jsme si procvičili nabyté zkušenosti z předchozích lekcí.

Nyní se na datové typy podíváme více zblízka a vysvětlíme si, kdy jaký použít. Dnešní lekce bude hodně teoretická, ale o to více bude praktická ta příští. Na konci si vytvoříme pár jednoduchých ukázek.

C# rozeznává dva druhy datových typů, hodnotové a referenční.

Hodnotové datové typy

Proměnné hodnotového datového typu si dokážeme jednoduše představit. Může se jednat např. o číslo nebo znak. V paměti je jednoduše uložena přímo hodnota a my k této hodnotě můžeme z programu přímo přistupovat. Slovo přímo jsem tolikrát nepoužil jen náhodou. V tomto C# kurzu se budeme věnovat výhradně těmto proměnným.

Celočíselné datové typy

Podívejme se nyní na tabulku všech vestavěných celočíselných datových typů v .NET, všimněte si typu int, který již známe zminula.

Datový typ Rozsah Velikost .NET typ
sbyte -128 až 127 8 bitů System.SByte
byte 0 až 255 8 bitů System.Byte
short -32 768 až 32 767 16 bitů System.Int16
ushort 0 až 65 535 16 bitů System.UInt16
int -2 147 483 648 až 2 147 483 647 32 bitů System.Int32
uint 0 až 4 294 967 295 32 bitů System.UInt32
long -9 223 372 036 854 775 808 až 9 223 372 036 854 775 807 64 bitů System.Int64
ulong 0 až 18 446 744 073 709 551 615 64 bitů System.UInt64

Všechna ta šílené čísla z této tabulky si pamatovat nemusíte, téměř vždy vám pomůže ve Visual Studiu nástroj IntelliSense, který když budete psát ten datový typ a počkáte ukáže nad ním bublinu:

Nástroj Intellisense ve Visual Studio

Případně v dokumentaci dodávané k Visual studiu to najdete podrobněji. Do dokumentace se dostanete, když napíšete konkrétní datový typ, označíte jej a stisknete F1.

Asi vás napadá otázka, proč máme tolik možných typů pro uložení čísla. Odpověď je prostá, záleží na jeho velikosti. Čím větší číslo, tím více spotřebuje paměti. Pro věk uživatele tedy zvolíme byte, protože se jistě nedožije více, než 255 let. Představte si databázi milionu uživatelů nějakého systému, když zvolíme místo byte int, bude zabírat 4x více místa. Naopak když budeme mít funkci k výpočtu faktoriálu, stěží nám bude stačit rozsah integeru a použijeme long.

Všimněte si, že některé typy začínají na u. Jsou téměř stejné, jako jejich dvojníci bez u, jen neumožňují záporné hodnoty a tím pádem na kladnou část mohou uložit 2x vyšší hodnotu. Těmto typům se říká unsigned, klasickým signed.

.NET typ je název dané struktury v .NET knihovnách. My používáme tzv. aliasy, aby byla práce jednodušší, ve skutečnosti si C# kód:

int a = 10;

přebere jako:

System.Int32 a = 10;

My budeme samozřejmě používat aliasy, od toho tam jsou :)

Nad výběrem datového typu nemusíte moc přemýšlet a většinou se používá jednoduše int. Typ řešte pouze v případě, když jsou proměnné v nějakém poli (obecně kolekci) a je jich tedy více, potom se vyplatí zabývat se paměťovými nároky. Tabulky sem dávám spíše pro úplnost. Mezi typy samozřejmě funguje již zmíněná implicitní konverze, tedy můžeme přímo přiřadit int do proměnné typu long a podobně, aniž bychom něco konvertovali.

Desetinná čísla

U desetinných čísel je situace poněkud jednodušší, máme na výběr pouze dva datové typy. Samozřejmě se liší opět v rozsahu hodnoty, dále však ještě v přesnosti (vlastně počtu des. míst). Typ double má již dle názvu dvojnásobnou přesnost oproti float.

Datový typ Rozsah Přesnost .NET typ
float +-1.5 * 10−45 až +-3.4 * 1038 7 čísel System.Single
double +-5.0 * 10−324 až +-1.7 * 10308 15-16 čísel System.Double

Vzhledem k tomu, že desetinná čísla jsou v počítači uložena ve dvojkové soustavě, dochází k určité ztrátě přesnosti. Odchylka je sice téměř zanedbatelná, nicméně když budete programovat např. finanční systém, nepoužívejte tyto dat. typy pro uchování peněz, mohlo by dojít k malým odchylkám.

Když do floatu chceme dosadit přímo ve zdrojovém kódu, musíme použít sufix F, u double sufix D (u double ho můžeme vypustit, jelikož je výchozí desetinný typ):

float f = 3.14F;
double d = 2.72;
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Jako desetinný separátor používáme ve zdrojovém kódu vždy tečku, nehledě na to, jaké máme ve Windows regionální nastavení.

Další vestavěné datové typy

Podívejme se na další datové typy, které nám .NET nabízí:

Datový typ Rozsah Velikost/Přesnost .NET typ
char U+0000 až U+ffff 16 bitů System.Char
decimal +-1.0 * 10−28 až +-7.9 * 1028 28-29 čísel System.Decimal
bool true nebo false 8 bitů System.Boolean
char

Char nám reprezentuje jeden znak, na rozdíl od stringu, který reprezentoval celý řetězec charů. Znaky v C# píšeme do apostrofů:

char c = 'A';

Typ char patří v podstatě do celočíselných proměnných (obsahuje číselný kód znaku), ale přišlo mi logičtější uvést ho zde. Typ char nám vrací např. metoda Console.ReadKey().

decimal

Typ decimal řeší problém ukládání desetinných čísel v binární podobě, ukládá totiž číslo vnitřně podobně jako text. Používá se tedy pro uchování peněžních hodnot. Ke všem dalším matematickým operacím s des. čísly použijeme float nebo double. K zápisu decimal hodnoty opět používáme sufix, m:

decimal m = 3.14159265358979323846m;
bool

Datový typ bool nabývá dvou hodnot: true (pravda) a false (nepravda). Budeme ho používat zejména tehdy, až se dostaneme k podmínkám. Do proměnné typu bool lze uložit jak přímo hodnotu true/false, tak i logický výraz. Zkusme si jednoduchý příklad:

bool b = false;
bool vyraz = (15 > 5);
Console.WriteLine(b);
Console.WriteLine(vyraz);
Console.ReadKey();

Výstup programu:

Konzolová aplikace
False
True

Výrazy píšeme do závorek. Vidíme, že výraz nabývá hodnoty true (pravda), protože 15 je opravdu větší než 5. Od výrazů je to jen krok k podmínkám, na ně se podíváme příště.

Referenční datové typy

K referenčním typům se dostaneme až u objektově orientovaného programování, kde si také vysvětlíme zásadní rozdíly. Zatím budeme pracovat jen s tak jednoduchými typy, že rozdíl nepoznáme. Spokojíme se s tím, že referenční typy jsou složitější, než ty hodnotové. Jeden takový typ již známe, je jím string. Možná vás napadá, že string nemá nijak omezenou délku, je to tím, že s referenčními typy se v paměti pracuje jinak.

Typ string má na sobě řadu opravdu užitečných metod. Některé si teď probereme a vyzkoušíme:

String

StartsWith(), EndsWith() a Contains()

Můžeme se jednoduše zeptat, zda řetězec začíná, končí nebo zda obsahuje určitý podřetězec (substring). Podřetězcem myslíme část původního řetězce. Všechny tyto metody budou jako parametr brát samozřejmě podřetězec a vracet hodnoty typu bool (true/false). Zatím na výstup neumíme reagovat, ale pojďme si ho alespoň vypsat:

string s = "Krokonosohroch";
Console.WriteLine(s.StartsWith("krok"));
Console.WriteLine(s.EndsWith("hroch"));
Console.WriteLine(s.Contains("nos"));
Console.WriteLine(s.Contains("roh"));
Console.ReadKey();

Výstup programu:

Konzolová aplikace
False
True
True
False

Vidíme, že vše funguje podle očekávání. První výraz samozřejmě neprošel díky tomu, že řetězec ve skutečnosti začíná velkým písmenem.

ToUpper() a ToLower()

Rozlišování velkých a malých písmen může být někdy na obtíž. Mnohdy se budeme potřebovat zeptat na přítomnost podřetězce tak, aby nezáleželo na velikosti písmen. Situaci můžeme vyřešit pomocí metod ToUpper() a ToLower(), které vrací řetězec ve velkých a v malých písmenech. Uveďme si reálnější příklad než je Krokonosohroch. Budeme mít v proměnné řádek konfiguračního souboru, který psal uživatel. Jelikož se na vstupy od uživatelů nelze spolehnout, musíme se snažit eliminovat možné chyby, zde např. s velkými písmeny.

string konfig = "Fullscreen shaDows autosave";
konfig = konfig.ToLower();
Console.WriteLine("Poběží hra ve fullscreenu?");
Console.WriteLine(konfig.Contains("fullscreen"));
Console.WriteLine("Budou zapnuté stíny?");
Console.WriteLine(konfig.Contains("shadows"));
Console.WriteLine("Přeje si hráč vypnout zvuk?");
Console.WriteLine(konfig.Contains("nosound"));
Console.WriteLine("Přeje si hráč hru automaticky ukládat?");
Console.WriteLine(konfig.Contains("autosave"));
Console.ReadKey();

Výstup programu:

Konzolová aplikace
Poběží hra ve fullscreenu?
True
Budou zapnuté stíny?
True
Přeje si hráč vypnout zvuk?
False
Přeje si hráč hru automaticky ukládat?
True

Vidíme, že jsme schopni zjistit přítomnost jednotlivých slov v řetězci tak, že si nejprve řetězec převedeme celý na malá písmena (nebo na velká) a potom kontrolujeme přítomnost slova jen malými (nebo velkými) písmeny. Takhle by mimochodem mohlo opravdu vypadat jednoduché zpracování nějakého konfiguračního skriptu.

Trim(), TrimStart() a TrimEnd()

Problémem ve vstupech od uživatele může být i diakritika. C# ale naštěstí pracuje plně v UTF-8, nestane se nám tedy, že by se diakritika nějak zkomolila. Další nástrahou mohou být mezery a obecně všechny tzv. bílé znaky, které nejsou vidět, ale mohou nám uškodit. Obecně může být dobré tzv. trimovat všechny vstupy od uživatele, můžeme trimovat buď okolo celého řetězce nebo jen bílé znaky před ním a za ním. Prozradím, že při parsovacích funkcích C# trimuje zadaný řetězec automaticky, než s ním začne pracovat. Odstraněny jsou pouze neviditelné znaky okolo řetězce, např. mezery mezi slovy zůstanou. Zkuste si v následující aplikaci před číslo a za číslo zadat několik mezer:

Console.WriteLine("Zadejte číslo:");
string s = Console.ReadLine();
Console.WriteLine("Zadal jste text: " + s);
Console.WriteLine("Text po funkci trim: " + s.Trim());
int a = int.Parse(s);
Console.WriteLine("Převedl jsem zadaný text na číslo parsováním, zadal jste: " + a);
Console.ReadKey();
Replace()

Asi nejdůležitější metodou na stringu je nahrazení určité jeho části jiným textem. Jako parametry zadáme dva podřetězce, jeden co chceme nahrazovat a druhý ten, kterým to chceme nahradit. Metoda vrátí nový string, ve kterém proběhlo nahrazení. Když daný podřetězec metoda nenajde, vrátí původní řetězec. Zkusme si to:

string s = "Java je nejlepší!";
s = s.Replace("Java", "C#");
Console.WriteLine(s);
Console.ReadKey();

Výstup programu:

Konzolová aplikace
C# je nejlepší!
Format()

Format() je velmi užitečná metoda, která nám umožňuje vkládat do samotného textového řetězce zástupné značky. Ty jsou reprezentovány jako číslo ve složených závorkách, prvním číslem je 0. Jako další parametry metody následují v tomto pořadí proměnné, které se mají do textu místo značek vložit. Všimněte si, že se metoda nevolá na konkrétní proměnné (přesněji instanci, viz další lekce), ale přímo na typu string.

int a = 10;
int b = 20;
int c = a + b;
string s = string.Format("Když sečteme {0} a {1}, dostaneme {2}", a, b, c);
Console.WriteLine(s);
Console.ReadKey();

Výstup programu:

Konzolová aplikace
Když sečteme 10 a 20, dostaneme 30

Konzole sama umí přijímat text v takovémto formátu, můžeme tedy napsat:

int a = 10;
int b = 20;
int c = a + b;
Console.WriteLine("Když sečteme {0} a {1}, dostaneme {2}", a, b, c);
Console.ReadKey();

Toto je velmi užitečná a přehledná cesta, jak sestavovat řetězce, a určitě se ji vyplatí mnohdy použít místo běžné konkatenace pomocí operátoru +, pokud nebazírujeme na vysoké rychlosti.

PadLeft() a PadRight()

Jako poslední si zmíníme metody, které nám k textu naopak mezery přidají :) K čemu to je dobré? Představte si, že máme 100 proměnných a budeme je chtít uspořádat do tabulky. Text upravíme pomocí metody PadRight() s parametrem šířky sloupce, tedy např. 20 znaků. Pokud bude mít text jen 12 znaků, vypíše se před něj 8 mezer, aby měl velikost 20. Obdobně metoda PadLeft() by vypsala 8 mezer za něj. Jelikož nemáme znalosti k vytvoření takové tabulky, budeme si metody jen pamatovat a vyzkoušíme si je dále v C# kurzu.

Vlastnost Length

Poslední, ale nejdůležitější vlastnost (pozor, ne metoda) je Length, tedy délka. Vrací celé číslo, které představuje počet znaků v řetězci. Za vlastnosti nepíšeme závorky, protože nemají parametry.

Console.WriteLine("Zadejte vaše jméno:");
string jmeno = Console.ReadLine();
Console.WriteLine("Délka vašeho jména je: {0}", jmeno.Length);
Console.ReadKey();

Je toho ještě spoustu k vysvětlování a jsou další datové typy, které jsme neprobrali.

V následujícím cvičení Řešené úlohy k 4. lekci C# .NET si procvičíme nabyté zkušenosti z předchozích lekcí.


 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
125 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
Řešené úlohy k 1.-3. lekci C# .NET
Všechny články v sekci
Základní konstrukce jazyka C#
Miniatura
Následující článek
Řešené úlohy k 4. lekci C# .NET
Aktivity (22)

 

 

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

Avatar
Ondřej Fiala:27.3.2019 14:20

Pořád nechápu to PadRight a Left a Trim. Nechápu k čemu to je. Mluvim o tom kódu s trimem nahoře. Zadám číslo a stejně se nic nezmění. Když dam číslo 1 a tisíc mezer před ním, tak se to stejně zobrazí hned za : a úplně ta samá hodnota vyjde i po tom "trimování".

Odpovědět
27.3.2019 14:20
Jestli v životě najdeš cestu bez překážek, určitě nikam nevede. -Arthur C. Clarke
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Ondřej Fiala
David Čápka:27.3.2019 15:42

Když zadáš číslo, tak se nic nezmění, to máš pravdu. Je to také v článku uvedené, že parsování ty mezery trimuje samo. Ale co když zadáš text? Třeba se uživatele zeptáš na otázku ano/ne, tam nechceš řešit, že zadal mezery před/za a pak se ti to špatně porovná.

Odpovědět
27.3.2019 15:42
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Odpovídá na Ondřej Fiala
Reaktivní uživatel:27.3.2019 18:26

Ještě doplním Davida o PadLeft a PadRight. Ano, mohl bys je nahradit for cyklem, ale proč to dělat? Chceš tabulku? Při troše snahy ji uděláš z čehokoli a nemusíš psát o for víc. Chceš podtrhnout nadpis? Oddělit od sebe něco? "".PadLeft(Con­sole.WindowWid­th, '-') je tvůj dobrý kamarád (ano, je to celkem šílenost, ale proč tak krásnou příležitost nevyužít?).

Odpovědět
27.3.2019 18:26
Kdo je připraven, toho zaskočí něco jiného
Avatar
Honza
Člen
Avatar
Honza:2. února 18:45

Mám prosbu, nerozumím následujícímu:

volání na proměnné, např.:

string s = "Java je nejlepší!";
s = s.Replace("Java", "C#");

int cislo = 3;
string s = soucet.ToString();

a naproti tomu volání na datovém typu:

string s = string.Format("Když sečteme {0} a {1}, dostaneme {2}", a, b, c);
int a = int.Parse(s);

proč se některé funkce (metody) jednou volají na proměnné a jindy na datovém typu? Nejsem schopen si to pamatovat tak, že si to nabifluju, potřeboval bych to pochopit. Děkuju :-)

 
Odpovědět
2. února 18:45
Avatar
Mouser
Člen
Avatar
Odpovídá na Honza
Mouser:3. února 0:10

Protože je C# objektový jazyk, k úplnému pochopení je potřeba znalost OOP. Ale prozatím se dá říct, že metody volané na datovém typu jsou takové obecnější. Metody volané na proměnné nějak pracují přímo s tou proměnnou. Když mám proměnnou s obsahující text "Java je nejlepší", tak metoda Replace vezme přímo ten text "Java je nejlepší" z té proměnné, vymění "Java" za "C#" a výsledek vrátí. Ale někdy se stane, že potřebuješ něco udělat, a žádnou proměnnou, s kterou bys pracoval, nemáš. Třeba u té metody Format sestavuješ řetězec, a nemáš žádnou "počáteční" proměnnou, na které bys ji mohl zavolat. Celý řetězec sestavuješ až v parametru. Tak C# implementoval metodu Format tak, aby se dala zavolat na datovém typu. Podobně u té metody Parse. Chceš naparsovat text na číslo, a výsledek uložit do proměnné typu int. Ale na začátku žádnou proměnnou typu int nemáš, tu teprve vytváříš. Tak C# umožňuje volat parsování přímo na datovém typu.

Ale když jsem říkal, že metody na datovém typu jsou obecnější, tak to v tomhle případě neznamená lepší. Jak se dozvíš dál, správnější je spíš to, co jde, volat na proměnné. Na datovém typu se volají jen věci, co nejdou jinak udělat, nebo jen krkolomně.

Jinak není potřeba si nic pamatovat. Když za proměnnou napíšeš tečku, Visual Studio ti rozbalí nabídku metod, které na dané proměnné můžeš zavolat. A u datového typu to funguje stejně. Nicméně všechny v článku uváděné metody (snad až na PadLeft a PadRight) se používají natolik běžně, že časem si je stejně zapamatuješ. :-)

 
Odpovědět
3. února 0:10
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Honza
Člen
Avatar
Odpovídá na Mouser
Honza:3. února 11:39

Paráda, děkuju za odpověď, obecně rozumím, důležité z Tvého příspěvku je:

  • Metody volané na proměnné nějak pracují přímo s tou proměnnou.
  • Ale někdy se stane, že potřebuješ něco udělat, a žádnou proměnnou, s kterou bys pracoval, nemáš. (pak se volá metoda na datovém typu)
  • správnější je spíš to, co jde, volat na proměnné.

K tomu ale opět jeden dotaz

int cislo = 3;
string s = cislo.ToString();

string s = "5";
int a = int.Parse(s);

Tady bych podle Tvého vysvětlení očekával, že by bylo správnější volat něco jako int a = s.Parse(); A to proto, že proměnnou, nad kterou chci volat Parse, v tomto případě mám.

... možná si odpovím sám - důvod by mohl být ten, že by Parse nevědelo, jaký datový typ má vrátit (jestli int, float, ...)

Tak to jsem zvědav, jak se tím prokoušu. Každopádně díky

 
Odpovědět
3. února 11:39
Avatar
Mouser
Člen
Avatar
Odpovídá na Honza
Mouser:3. února 21:40

Máš talent. :-) Jen pro upřesnění, to, že když nemáš proměnnou, voláš metodu na datovém typu, neber jako pravidlo. To jsem myslel jen jako příklad, kdy má třeba volání metody na datovém typu smysl. Na datovém typu se prostě dají volat metody, které ten datový typ nabízí. A tvůrci C# tam dali takové, které zpravidla umožňují elegantnější zápis něčeho, co by se jinak dělalo blbě. Ale můžeš je brát spíš jako takové výjimky, ono jich je relativně málo.

U toho Parse sis opravdu odpověděl sám. :-) Přesně, nevědělo by se, co vracet. Teoreticky by mohly existovat metody ParseToFloat, ParseToInt apod., případně elegantněji pomocí generiky (poznáš později), ale pak by na stringové proměnné musela být hromada metod pro všechny možné návratové typy. A stejně by to nestačilo, protože si můžeš dokonce vytvořit datový typ vlastní (třeba pro komplexní číslo), a pro něj by tam pak parsovací metoda chyběla. Navíc u některých datových typů může být parsování složité - u čísel asi tolik ne, ale taky se dá parsovat třeba datum, které může být v X různých formátech (začínající rokem, začínající dnem, oddělovačem je tečka, lomítko, atd.). Je logičtější, aby zodpovědnost za správné naparsování byla na výsledném datovém typu, který ví, jak má výsledek správně fungovat.

Ale to už zabíháme zbytečně do detailů. Tomu, co teď potřebuješ, rozumíš beze zbytku, a můžeš se s klidným svědomím posunout dál. Kompletní souvislosti pochopíš u OOP. :-)

 
Odpovědět
3. února 21:40
Avatar
David Holohlavský:25. února 9:19

Skvělá práce. ;-)

 
Odpovědět
25. února 9:19
Avatar
Marek Vajčner:29. února 16:02

Jo jo ještě dva takové články a bude to chtít nějakou referenční příručku. :-D

 
Odpovědět
29. února 16:02
Avatar
Jaromír Vikukel:6. dubna 14:36

Dobrý den, zaujala mne poznámka u datových typů float a double. Není mi jasné, kde dochází k určité ztrátě přesnosti. Do binární soustavy se snad nechá konvertovat libovolné dekadické číslo? V praxi jsem se setkával s použitím datových typů float a double i ve finančních výpočtech. Znamená poznámka, že zde vždycky existovalo riziko odchylek? Dále: co znamená u popisu těchto datových typů sloupec "přesnost"? Je to počet míst za desetinnou čárkou? Díky

 
Odpovědět
6. dubna 14:36
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 110. Zobrazit vše