• Co je proměnná v c. Datové typy a operace v jazyce C. výrazy

    Typy dat. Program v procedurálních jazycích, do kterého C patří, je popis operací s veličinami různé typy. Typ definuje množinu hodnot, které může hodnota nabývat, a množinu operací, kterých se může účastnit.

    V jazyce C jsou typy spojeny s názvy (identifikátory) hodnot, tedy s proměnnými. Proměnná v C je spojena s umístěním paměti. Typ proměnné určuje velikost buňky, způsob kódování jejího obsahu a povolené transformace hodnoty této proměnné. Všechny proměnné musí být před použitím deklarovány. Každá proměnná musí být deklarována pouze jednou.

    Popis se skládá ze specifikátoru typu následovaného seznamem proměnných. Proměnné v seznamu jsou odděleny čárkami. Na konci popisu je umístěn středník.

    Příklady popisu:

    char a,b; /* Proměnné aab mají typ

    char */intx; /* Proměnná x - zadejte int

    */ symbol znaku; /" Jsou popsány proměnné sym typu char;

    */ int pocet.num; /* počet a počet typu int */

    Proměnným lze v rámci jejich deklarací přiřadit počáteční hodnoty. Pokud za názvem proměnné následuje rovnítko a konstanta, pak tato konstanta slouží jako inicializátor.

    Příklady: char backch = "\0";

    Zvažte základní typy v jazyce C.

    int - celé číslo ("celé číslo"). Hodnoty tohoto typu jsou celá čísla z nějakého omezeného rozsahu (obvykle - 32768 až 32767). Rozsah je určen velikostí buňky pro daný typ a je specifický pro daný stroj. Kromě toho existují pomocná slova, která lze použít s typem int: short int („krátké celé číslo“ – „krátké celé číslo“), bez znaménka (“celé celé číslo bez znaménka“ – „celé číslo bez znaménka“), dlouhé int („dlouhé celé číslo“ “), které zmenšují nebo naopak rozšiřují rozsah zobrazení čísel.

    char- postava ("postava"). Platná hodnota pro tento typ je jeden znak (neplést s textem!). Symbol je psán v apostrofech.

    Příklady:"x" 2 "?"

    Znak zabírá jeden bajt v paměti počítače. Ve skutečnosti není uložen znak, ale číslo - kód znaku (od 0 do 255). Ve speciálních tabulkách kódování jsou uvedeny všechny platné znaky a jim odpovídající kódy.

    V jazyce C je povoleno používat typ char jako číselný typ, tedy provádět operace s kódem znaku, a to při použití specifikátoru typu integer v závorkách - (int).

    float - skutečný (plovoucí desetinná čárka). Hodnoty tohoto typu jsou čísla, ale na rozdíl od char a int nemusí být nutně celá čísla.

    12,87 -316,12 -3,345e5 12,345e-15

    double - reálná čísla s dvojnásobnou přesností. Tento typ je podobný typu float, ale má mnohem větší rozsah hodnot (například pro programovací systém Borland-C od 1.7E-308 do 1.7E+308 namísto rozsahu od 3.4E-38 do 3.4E+38 pro plovoucí typ). Zvýšení rozsahu a přesnosti zobrazení čísel však vede ke snížení rychlosti provádění programu a plýtvání paměť s náhodným přístupem počítač.


    Všimněte si, že v tomto seznamu chybí typ řetězce. V C neexistuje žádný speciální typ, který lze použít k popisu řetězců. Místo toho jsou řetězce reprezentovány jako pole prvků char. To znamená, že znaky v řetězci budou umístěny v sousedních paměťových buňkách.

    Je třeba poznamenat, že posledním prvkem pole je znak \0. Toto je "nulový znak" a používá se v C k označení konce řetězce. Znak null není číslice 0; netiskne se a v tabulce kódů ASCII má číslo 0. Přítomnost znaku null znamená, že počet buněk v poli by měl být. alespoň o jeden více, než je počet znaků, které se mají uložit do paměti.

    Uveďme si příklad použití řetězců.

    Program 84

    #zahrnout hlavní()

    scanf("%s",string) ;

    printf("%s",string);

    Tento příklad popisuje pole 31 paměťových míst, z nichž 30 může obsahovat jeden znakový prvek. Zadává se při volání funkce scanf("%s",string);. Při zadávání pole znaků chybí znak „&“.

    Ukazatele. Ukazatel - nějaké symbolické znázornění adresy paměťové buňky přidělené pro proměnnou.

    Například &name je ukazatel na proměnnou name;

    Zde & je operace získání adresy. Skutečná adresa je číslo a symbolická reprezentace adresy a jména je konstanta ukazatele.

    Jazyk C má také proměnné ukazatele. Stejně jako hodnota typ proměnné char je znak a hodnota proměnné typu int je celé číslo, hodnota proměnné typu pointer je adresa nějaké hodnoty.

    Pokud dáme ukazateli název ptr, můžeme napsat následující příkaz:

    ptr = /* přiřadí adresu jména k ptr */

    V tomto případě říkáme, že prt je "ukazatel na" jméno. Rozdíl mezi dvěma zápisy ptr a &name je ten, že prt je proměnná, zatímco &name je konstanta. V případě potřeby můžete změnit proměnnou ptr na jiný objekt:

    ptr= /* ptr ukazuje na bah, nikoli na jméno */

    Nyní je hodnota proměnné prt adresou proměnné bah. Předpokládejme, že víme, že proměnná ptr obsahuje odkaz na proměnnou bah. Pro přístup k hodnotě této proměnné pak můžete použít operaci „nepřímé adresování“ * :

    val = *ptr; /* určí hodnotu, na kterou ukazuje ptr */ Poslední dva příkazy dohromady jsou ekvivalentní následujícímu:

    Takže když znamení & následuje název proměnné, výsledkem operace je adresa zadané proměnné; &nurse udává adresu proměnné sestra; když za * následuje ukazatel na proměnnou, výsledkem operace je hodnota umístěná v paměťovém místě na zadané adrese.

    Příklad: zdravotní sestřička = 22;

    ptr= /* ukazatel na sestru */

    Výsledkem je přiřazení hodnoty 22 proměnné val.

    Nestačí říci, že nějaká proměnná je ukazatel. Kromě toho musíte říci, na jaký typ proměnné se tento ukazatel vztahuje. Důvodem je, že proměnné různých typů berou jiné číslo paměťové buňky, zatímco některé operace související s ukazateli vyžadují, abyste znali množství přidělené paměti.

    Příklady správný popis ukazatele: int *pi; char*pc;

    Specifikace typu určuje typ proměnné, na kterou odkazuje ukazatel, a symbol * definuje samotnou proměnnou jako ukazatel. Popis typu int *pi; říká, že pi je ukazatel a že *pi je hodnota typu int.

    Jazyk C poskytuje možnost definovat názvy datových typů. Jakýkoli datový typ lze pojmenovat pomocí definice typedef a tento název lze použít později při popisu objektů.

    Formát: typedef<старый тип> <новый тип> Příklad: typedef long LARGE; /* je definován velký typ, který je ekvivalentní dlouhému */

    Definice typedef nezavádí žádné nové typy, pouze přidává nový název pro již stávající typ. Takto deklarované proměnné mají přesně stejné vlastnosti jako proměnné deklarované explicitně. Přejmenování typů se používá k zavedení smysluplných nebo zkrácených názvů typů, což zlepšuje srozumitelnost programů, a ke zlepšení přenositelnosti programů (názvy stejného datového typu se mohou na různých počítačích lišit).

    Operace. Jazyk C se vyznačuje širokou škálou operací (více než 40). Zde uvažujeme pouze ty hlavní, tabulku. 3.3.

    Aritmetické operace. Obsahují

    Sčítání (+),

    Odečítání (binární) (-),

    Násobení (*),

    divize (/),

    Zbytek z celočíselného dělení (%),

    Odečítání (unární) (-) .

    V jazyce C je akceptováno pravidlo: pokud jsou dělenec a dělitel typu int, pak se dělení provede celé, to znamená, že se zlomková část výsledku zahodí.

    Jako obvykle se ve výrazech před sčítáním a odčítáním provádějí operace násobení, dělení a zbytku. Závorky se používají ke změně pořadí akcí.

    Program 85

    #zahrnout

    5 = -3 + 4 * 5 - 6; printf("%d\n",s);

    s = -3 + 4 % 5 - 6; printf("%d\n",s);

    s = -3 x 4 % - 6/5; printf("%d\n",s);

    s= (7 + 6) % 5/2; printf("%d\n",s);

    Výsledek spuštění programu: 11 1 0 1

    Tabulka 3.3 Seniorita a pořadí provádění operací

    Základy jazyka

    Kód programu a data, se kterými program manipuluje, se zapisují do paměti počítače jako sekvence bitů. Bit je nejmenší prvek paměti počítače, schopný uložit buď 0 nebo 1. Zap fyzické úrovni to odpovídá elektrické napětí, o kterém je známo, že buď existuje, nebo ne. Při pohledu na obsah paměti počítače uvidíme něco jako:
    ...

    Je velmi obtížné porozumět takové sekvenci, ale někdy potřebujeme manipulovat s takovými nestrukturovanými daty (obvykle to vzniká při programování ovladačů hardwarových zařízení). C++ poskytuje sadu operací pro práci s bitovými daty. (Budeme o tom mluvit v kapitole 4.)
    Posloupnosti bitů je zpravidla uložena nějaká struktura, která bity seskupuje bajtů A slova. Bajt obsahuje 8 bitů a slovo obsahuje 4 bajty nebo 32 bitů. Definice slova se však může v různých operačních systémech lišit. Nyní začíná přechod na 64bitové systémy a systémy s 16bitovými slovy jsou v poslední době běžné. Přestože je velikost bajtu ve velké většině systémů stejná, budeme tyto hodnoty stále označovat jako specifické pro stroj.

    Nyní můžeme mluvit například o bajtu na adrese 1040 nebo o slovu na adrese 1024 a říci, že bajt na adrese 1032 se nerovná bajtu na adrese 1040.
    Nevíme však, co který bajt, jakékoli strojové slovo představuje. Jak porozumět významu určitých 8 bitů? Abychom mohli jednoznačně interpretovat význam tohoto bajtu (nebo slova, či jiné sady bitů), musíme znát typ dat reprezentovaných tímto bajtem.
    C++ poskytuje sadu vestavěných datových typů: znak, celé číslo, reálné číslo a sadu složených a rozšířených typů: řetězce, pole, komplexní čísla. Navíc pro akce s těmito daty existuje základní sada operace: srovnávací, aritmetické a jiné operace. Existují také přechody, smyčky, podmíněné příkazy. Tyto prvky jazyka C++ tvoří sadu stavebních bloků, ze kterých můžete sestavit systém libovolné složitosti. Prvním krokem k zvládnutí C++ je naučit se tyto základní prvky, čemuž je věnována část II této knihy.
    Kapitola 3 poskytuje přehled vestavěných a rozšířených typů a mechanismů, pomocí kterých můžete vytvářet nové typy. V zásadě jde samozřejmě o mechanismus tříd představený v sekci 2.3. Kapitola 4 se zabývá výrazy, vestavěnými operátory a jejich prioritou a převody typů. Kapitola 5 hovoří o jazykových pokynech. Nakonec kapitola 6 představuje standardní knihovnu C++ a typy kontejnerů, vektor a asociativní pole.

    3. Datové typy C++

    Tato kapitola poskytuje přehled vložené nebo základní, datové typy jazyka C++. Začíná to definicí literály, jako je 3,14159 nebo pí, a potom pojem variabilní nebo objekt, což musí být jeden z datových typů. Zbytek kapitoly je věnován Detailní popis každý vestavěný typ. Také uvádí odvozené datové typy pro řetězce a pole poskytované standardní knihovnou C++. Přestože tyto typy nejsou elementární, jsou velmi důležité pro psaní skutečných programů v C++ a chceme s nimi čtenáře seznámit co nejdříve. Takové datové typy budeme nazývat expanze Základní typy C++.

    3.1. Doslovy

    C++ má sadu vestavěných datových typů pro reprezentaci celých a reálných čísel, symbolů a také datový typ „pole znaků“, který se používá k ukládání znakové řetězce. Typ char se používá k ukládání jednotlivých znaků a malých celých čísel. Zabírá jeden strojový bajt. krátké typy, int a long představují celá čísla. Tyto typy se liší pouze v rozsahu hodnot, které mohou čísla nabývat, a konkrétní velikosti uvedených typů jsou závislé na implementaci. Obvykle short je polovina strojového slova, int je jedno slovo, long je jedno nebo dvě slova. Na 32bitových systémech mají int a long obecně stejnou velikost.

    Typy float, double a long double jsou pro čísla s plovoucí desetinnou čárkou a liší se přesností zobrazení (počet platných číslic) a rozsahem. Typ float (jednoduchá přesnost) trvá obvykle jedno strojové slovo, double (dvojitá přesnost) dvě a dlouhá dvojitá (rozšířená přesnost) tři.
    char, short, int a long dohromady tvoří celočíselné typy, což zase může být ikonický(podepsáno) a nepodepsaný(nepodepsaný). U typů se znaménkem se k uložení znaménka používá bit zcela vlevo (0 je plus, 1 je mínus) a zbývající bity obsahují hodnotu. U typů bez znaménka jsou pro hodnotu použity všechny bity. 8bitový znak se znaménkem může představovat hodnoty od -128 do 127 a znak bez znaménka může představovat hodnoty od 0 do 255.

    Když je v programu nalezeno číslo, například 1, zavolá se toto číslo doslovný nebo doslovná konstanta. Konstanta, protože nemůžeme změnit její hodnotu, a literál, protože se její hodnota objevuje v textu programu. Literál je neadresovatelná hodnota: ačkoli je samozřejmě ve skutečnosti uložen v paměti stroje, neexistuje způsob, jak zjistit jeho adresu. Každý literál má specifický typ. Tedy 0 je typu int, 3.14159 je typu double.

    Literály typu Integer lze zapsat v desítkové, osmičkové a šestnáctkové soustavě. Takto vypadá číslo 20 jako desítkový, osmičkový a šestnáctkový literál:

    20 // desetinné
    024 // osmičková
    0x14 // hex

    Pokud literál začíná 0, je považován za osmičkový, pokud začíná 0x nebo 0X, je považován za hexadecimální. Obvyklý zápis je považován za desetinné číslo.
    Ve výchozím nastavení jsou všechny celočíselné literály podepsané int. Celočíselný literál můžete explicitně definovat jako typ long tím, že na konec čísla připojíte písmeno L (používá se jak velké L, tak malé l, ale kvůli čitelnosti by se nemělo používat malá písmena: je snadné je zaměnit s

    1). U (nebo u) na konci definuje literál jako unsigned int a dvě písmena UL nebo LU jako unsigned long. Například:

    128u 1024UL 1L 8Lu

    Literály reprezentující reálná čísla lze psát buď s desetinnou čárkou, nebo ve vědeckém (exponenciálním) zápisu. Standardně jsou typu double. Chcete-li explicitně určit typ float, musíte použít příponu F nebo f a pro dlouhé dvojité - L nebo l, ale pouze v případě zápisu s desetinnou čárkou. Například:

    3.14159F 0/1f 12.345L 0.0 3el 1.0E-3E 2.1.0L

    Slova true a false jsou literály typu bool.
    Reprezentovatelné doslovné znakové konstanty se zapisují jako znaky v jednoduchých uvozovkách. Například:

    "a" "2" "," " " (mezera)

    Speciální znaky (tabulátor, návrat vozíku) se zapisují jako sekvence escape. Jsou definovány následující sekvence (začínají znakem zpětného lomítka):

    Nový řádek \n vodorovný tabulátor \t backspace \b svislý tabulátor \v návrat vozíku \r posun listu \f zvonek \a zpětné lomítko \\ otázka \? jednoduché uvozovky \" dvojité uvozovky \"

    escape sekvence obecný pohled je ve tvaru \ooo, kde ooo je jedna až tři osmičkové číslice. Toto číslo je kód znaku. Pomocí ASCII kódu můžeme napsat následující literály:

    \7 (volání) \14 (nový řádek) \0 (null) \062("2")

    Znakový literál může mít předponu L (například L"a"), což znamená speciální typ wchar_t, dvoubajtový znakový typ, který se používá k ukládání znaků národních abeced, pokud je nelze reprezentovat obvyklým typem znaku. , jako jsou čínská nebo japonská písmena.
    Řetězcový literál je řetězec znaků uzavřený do dvojitých uvozovek. Takový literál může zahrnovat několik řádků, v takovém případě je na konec řádku umístěno zpětné lomítko. Speciální znaky mohou být reprezentovány vlastními únikovými sekvencemi. Zde jsou příklady řetězcových literálů:

    "" (prázdný řetězec) "a" "\nCC\options\tsoubor.\n" "víceřádkový \ řetězcový literál signalizuje \ pokračování zpětným lomítkem"

    Řetězcový literál je ve skutečnosti pole znakové konstanty, kde podle konvence jazyků C a C++ je posledním prvkem vždy speciální znak s kódem 0 (\0).
    Literál "A" určuje jeden znak A a řetězcový literál "A" určuje pole dvou prvků: "A" a \0 (prázdný znak).
    Protože existuje typ wchar_t, existují literály tohoto typu, označované jako v případě jednotlivých znaků předponou L:

    L "široký řetězcový doslov"

    Řetězcový literál typu wchar_t je pole znaků stejného typu ukončené nulou.
    Pokud se v testu programu objeví dva nebo více řetězcových literálů (typu char nebo wchar_t) za sebou, kompilátor je zřetězí do jednoho řetězce. Například následující text

    "dva" "nějaké"

    vytvoří pole osmi znaků – dvojka a koncový znak null. Výsledek zřetězení řetězců jiný typ neurčitý. Pokud napíšete:

    // to není dobrý nápad "dva" L"nějaké"

    pak na některém počítači bude výsledkem nějaký smysluplný řetězec a na jiném to může dopadnout úplně jinak. Programy, které využívají implementační funkce konkrétního kompilátoru resp operační systém, nejsou přenosné. Důrazně nedoporučujeme používat takové struktury.

    Cvičení 3.1

    Vysvětlete rozdíl v definicích následujících literálů:

    (a) "a", L"a", "a", L"a" (b) 10, 10u, 10L, 10uL, 012, 0*C (c) 3,14, 3,14f, 3,14L

    Cvičení 3.2

    Jaké jsou chyby v níže uvedených příkladech?

    (a) "Kdo jde s F\144rgus?\014" (b) 3,14e1L (c) "dva" L"některé" (d) 1024f (e) 3,14UL (f) "víceřádkový komentář"

    3.2. Proměnné

    Představte si, že řešíme problém zvýšení 2 na 10. Píšeme:

    #zahrnout
    int main() (
    // první řešení
    cout<< "2 raised to the power of 10: ";
    cout<< 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
    cout<< endl;
    návrat 0;
    }

    Problém je vyřešen, i když jsme museli opakovaně kontrolovat, zda se opravdu 10x opakuje doslovná 2. Při psaní této dlouhé sekvence dvojek jsme neudělali chybu a program vrátil správný výsledek - 1024.
    Ale nyní jsme byli požádáni, abychom zvýšili 2 na 17 a pak na 23. Je extrémně nepohodlné pokaždé upravovat text programu! A co je ještě horší, je velmi snadné udělat chybu tím, že napíšete dvojku navíc nebo ji vynecháte... Co když ale potřebujete vytisknout tabulku mocnin dvou od 0 do 15? Opakujte 16krát dva řádky, které mají společný tvar:

    Cout<< "2 в степени X\t"; cout << 2 * ... * 2;

    kde X se postupně zvyšuje o 1 a místo tečky se dosadí požadovaný počet literálů?

    Ano, práci jsme splnili. Je nepravděpodobné, že by se zákazník ponořil do detailů a byl spokojen s výsledkem. V reálném životě tento přístup často funguje, navíc má své opodstatnění: problém není zdaleka vyřešen tím nejelegantnějším způsobem, ale v požadovaném časovém horizontu. Hledání krásnější a kompetentnější možnosti může být nepraktickou ztrátou času.

    V tomto případě „metoda hrubé síly“ dává správnou odpověď, ale jak nepříjemné a nudné je řešit problém tímto způsobem! Víme přesně, jaké kroky podniknout, ale samotné kroky jsou jednoduché a monotónní.

    Zapojení složitějších mechanismů pro stejný úkol zpravidla výrazně prodlužuje dobu přípravné fáze. Navíc platí, že čím složitější mechanismy jsou použity, tím větší je pravděpodobnost chyb. Ale i přes nevyhnutelné chyby a špatné tahy může použití „špičkových technologií“ přinést výhody v rychlosti vývoje, nemluvě o tom, že tyto technologie značně rozšiřují naše možnosti. A - co je zajímavé! Samotný rozhodovací proces se může stát atraktivním.
    Vraťme se k našemu příkladu a zkusme jeho realizaci „technologicky vylepšit“. Pojmenovaný objekt můžeme použít k uložení hodnoty mocniny, na kterou chceme zvýšit naše číslo. Také místo opakující se sekvence literálů můžeme použít operátor smyčky. Bude to vypadat následovně:

    #zahrnout
    int main()
    {
    // objekty typu int
    int hodnota = 2;
    int pow = 10;
    cout<< value << " в степени "
    << pow << ": \t";
    int res = 1;
    // operátor smyčky:
    // opakování výpočtu res
    // dokud cnt nebude větší než pow
    for (int cnt=1; cnt<= pow; ++cnt)
    res = res * hodnota;
    cout<< res << endl;
    }

    value, pow, res a cnt jsou proměnné, které umožňují ukládat, upravovat a načítat hodnoty. Příkaz cyklu for opakuje řetězec výpočtu výsledku pow times.
    Nepochybně jsme vytvořili mnohem flexibilnější program. Stále to však není funkce. Chcete-li získat skutečnou funkci, kterou lze použít v jakémkoli programu k výpočtu stupně čísla, musíte vybrat obecnou část výpočtů a nastavit konkrétní hodnoty jako parametry.

    int pow(int val, int exp) ( for (int res = 1; exp > 0; --exp) res = res * val; return res; )

    Nyní získat jakýkoli stupeň požadovaného čísla nebude obtížné. Zde je návod, jak je implementován náš poslední úkol - vytisknout tabulku mocnin dvou od 0 do 15:

    #zahrnout extern int pow(int,int); int main() ( int val = 2; int exp = 15;
    cout<< "Степени 2\n";
    for (int cnt=0; cnt<= exp; ++cnt)
    cout<< cnt << ": "
    << pow(val, cnt) << endl;
    návrat 0;
    }

    Naše funkce pow() samozřejmě stále není dostatečně obecná a dostatečně spolehlivá. Neumí pracovat s reálnými čísly, nesprávně zvyšuje čísla na zápornou mocninu - vždy vrací 1. Výsledek zvýšení velkého čísla na velkou mocninu se nemusí vejít do proměnné int a pak se vrátí nějaká náhodná chybná hodnota. Vidíte, jak obtížné může být psát funkce, které mají být široce používány? Je mnohem obtížnější než implementovat specifický algoritmus zaměřený na řešení konkrétního problému.

    3.2.1. Co je proměnná

    Variabilní nebo objekt- toto je pojmenovaná oblast paměti, ke které máme přístup z programu; můžete tam dát hodnoty a pak je načíst. Každá proměnná C++ má specifický typ, který charakterizuje velikost a umístění této oblasti paměti, rozsah hodnot, které může uložit, a sadu operací použitelných pro tuto proměnnou. Zde je příklad definování pěti objektů různých typů:

    int počet_studentů; dvojnásobný plat; bool on_loan; řetězce adresa_ulice; oddělovač znaků;

    Proměnná, stejně jako literál, má specifický typ a ukládá svou hodnotu do nějaké oblasti paměti. Adresovatelnost- to doslovu chybí. S proměnnou jsou spojeny dvě hodnoty:

    • samotná hodnota nebo r-hodnota (z načtené hodnoty - hodnota pro čtení), která je uložena v této oblasti paměti a je vlastní jak proměnné, tak literálu;
    • hodnota adresy oblasti paměti spojené s proměnnou, neboli l-hodnota (z hodnoty umístění - hodnota umístění) - místo, kde je uložena hodnota r; patří pouze k objektu.

    Ve výrazu

    Ch = ch - "0";

    proměnná ch je jak vlevo, tak vpravo od přiřazovacího symbolu. Vpravo je čtená hodnota (ch a znakový literál "0"): data spojená s proměnnou se čtou z odpovídající oblasti paměti. Vlevo je hodnota umístění: výsledek odečítání je umístěn do oblasti paměti spojené s proměnnou ch. Obecně platí, že levý operand operátoru přiřazení musí být l-hodnota. Nemůžeme napsat následující výrazy:

    // chyby kompilace: hodnoty nalevo nejsou l-hodnoty // chyba: doslovný není l-hodnota 0 = 1; // chyba: aritmetický výraz není l-hodnota plat + plat * 0,10 = nový_plat;

    Operátor definice proměnné pro ni alokuje paměť. Protože k objektu je přidružena pouze jedna paměťová oblast, může se takový operátor v programu vyskytnout pouze jednou. Pokud proměnná definovaná v jednom zdrojovém souboru musí být použita v jiném, nastanou problémy. Například:

    // soubor module0.C // definuje objekt fileName řetězec fileName; // ... nastaví název_souboru na hodnotu
    // soubor module1.C
    // používá objekt název_souboru
    // bohužel se to nezkompiluje:
    // název_souboru není definován v module1.C
    ifstream vstupní_soubor(název_souboru);

    C++ vyžaduje, aby byl objekt znám před prvním přístupem. To je způsobeno nutností zajistit správné použití objektu podle jeho typu. V našem příkladu modul1.C způsobí chybu kompilace, protože v něm není definován název_souboru. Abychom se této chybě vyhnuli, musíme kompilátoru říci o již definované proměnné fileName. To se provádí pomocí příkazu deklarace proměnné:

    // file module1.C // používá objekt fileName // fileName je deklarován, tj. program obdrží
    // informace o tomto objektu bez jeho sekundární definice
    externí řetězec název_souboru; ifstream vstupní_soubor(název_souboru)

    Deklarace proměnné říká kompilátoru, že objekt s daným jménem a daného typu je definován někde v programu. Když je proměnná deklarována, není alokována žádná paměť. (Klíčové slovo extern je popsáno v části 8.2.)
    Program může obsahovat libovolný počet deklarací téže proměnné, ale může být definován pouze jednou. Takové deklarace jsou pohodlně umístěny v hlavičkových souborech, včetně jejich modulů, které je vyžadují. Můžeme tak ukládat informace o objektech na jednom místě a poskytnout pohodlí při jejich úpravě v případě potřeby. (O hlavičkových souborech si povíme více v sekci 8.2.)

    3.2.2. Název proměnné

    název proměnné, popř identifikátor, může sestávat z latinských písmen, číslic a znaku podtržítka. Velká a malá písmena v názvech se liší. Jazyk C++ neomezuje délku identifikátoru, ale použití příliš dlouhých názvů jako gosh_this_is_an_impossically_name_to_type je nepohodlné.
    Některá slova jsou klíčová slova v C++ a nelze je použít jako identifikátory; Tabulka 3.1 poskytuje jejich úplný seznam.

    Tabulka 3.1. Klíčová slova C++

    asm auto bool přestávka pouzdro
    chytit char třída konst const_cast
    pokračovat výchozí vymazat dělat dvojnásobek
    dynamic_cast jiný enum explicitní vývozní
    externí Nepravdivé plovák pro přítel
    jít do -li v souladu int dlouho
    proměnlivý jmenný prostor Nový operátor soukromé
    chráněný veřejnost Registrovat reinterpret_cast vrátit se
    krátký podepsaný velikost statický static_cast
    strukturovat přepínač šablona tento házet
    typedef skutečný Snaž se typid typové jméno
    svaz neplatné spojení použitím virtuální prázdnota

    Aby byl text vašeho programu srozumitelnější, doporučujeme dodržovat obecně uznávané konvence pro pojmenování objektů:

    • název proměnné se obvykle píše malými písmeny, například index (pro srovnání: Index je název typu a INDEX je konstanta definovaná pomocí direktivy preprocesoru #define);
    • identifikátor musí mít nějaký význam, vysvětlující účel objektu v programu, například: datum narození nebo plat;

    pokud se takové jméno skládá z několika slov, jako například datum_narození, je obvyklé oddělovat slova buď podtržítkem (datum_narození), nebo každé následující slovo psát velkým písmenem (datum narození). Bylo pozorováno, že programátoři zvyklí na objektově orientovaný přístup dávají přednost psaní velkých písmen ve slovech, zatímco ti, kteří mají_napsáno_C, často používají znak podtržítka. Která z těchto dvou metod je lepší, je věcí vkusu.

    3.2.3. Definice objektu

    Ve svém nejjednodušším případě se operátor definice objektu skládá z specifikátor typu A název objektu a končí středníkem. Například:

    dvojnásobný plat; dvojnásobná mzda; int měsíc; int den; int rok; dlouhá vzdálenost bez znaménka;

    V jednom příkazu můžete definovat více objektů stejného typu. V tomto případě jsou jejich jména uvedena oddělená čárkami:

    Dvojitý plat, mzda; int měsíc, den, rok; dlouhá vzdálenost bez znaménka;

    Pouhé definování proměnné nenastaví její počáteční hodnotu. Pokud je objekt definován jako globální, specifikace C++ zaručuje, že bude inicializován na hodnotu null. Pokud je proměnná lokální nebo dynamicky alokovaná (pomocí operátoru new), její počáteční hodnota není definována, to znamená, že může obsahovat nějakou náhodnou hodnotu.
    Používání proměnných, jako je tato, je velmi častá chyba a je také těžké ji odhalit. Doporučuje se explicitně specifikovat počáteční hodnotu objektu, alespoň v případech, kdy není známo, zda se objekt může inicializovat sám. Mechanismus třídy zavádí koncept výchozího konstruktoru, který se používá k přiřazení výchozích hodnot. (O tom jsme již mluvili v sekci 2.3. O výchozích konstruktorech budeme pokračovat o něco později v sekcích 3.11 a 3.15, kde budeme diskutovat o řetězcích a komplexních třídách ze standardní knihovny.)

    int main() ( // neinicializovaný lokální objekt int ival;
    // objekt typu string je inicializován
    // výchozí konstruktor
    provázkový projekt;
    // ...
    }

    Počáteční hodnotu lze zadat přímo v příkazu definice proměnné. V C++ jsou povoleny dvě formy inicializace proměnné – explicitní, pomocí operátoru přiřazení:

    Intival = 1024; string project = "Fantasia 2000";

    a implicitní, s počáteční hodnotou uvedenou v závorkách:

    Intival(1024); string project("Fantasia 2000");

    Obě možnosti jsou ekvivalentní a nastavují počáteční hodnoty pro celočíselnou proměnnou ival na 1024 a pro řetězec projektu na "Fantasia 2000".
    Explicitní inicializaci lze také použít při definování proměnných jako seznamu:

    Dvojnásobný plat = 9999,99, mzda = plat + 0,01; int měsíc = 08; den=07, rok=1955;

    Proměnná se stává viditelnou (a platnou v programu) jakmile je definována, takže bychom mohli inicializovat mzdovou proměnnou součtem nově definované mzdové proměnné s nějakou konstantou. Takže definice je:

    // správné, ale nesmyslné int bizarre = bizarre;

    je syntakticky platná, i když nesmyslná.
    Vestavěné datové typy mají speciální syntaxi pro nastavení hodnoty null:

    // ival je nastaveno na 0 a dval je nastaveno na 0.0 int ival = int(); double dval = double();

    V následující definici:

    // int() se aplikuje na každý z 10 prvků vektoru< int >ivec(10);

    každý z deseti prvků vektoru je inicializován pomocí int(). (O třídě vector jsme již mluvili v sekci 2.8. Více o tom viz sekce 3.10 a kapitola 6.)
    Proměnná může být inicializována výrazem libovolné složitosti, včetně volání funkcí. Například:

    #zahrnout #zahrnout
    dvojnásobná cena = 109,99, sleva = 0,16;
    double sale_price(cena * sleva);
    string pet("vrásky"); extern int get_value(); intval = get_value();
    unsigned abs_val = abs(val);

    abs() je standardní funkce, která vrací absolutní hodnotu parametru.
    get_value() je nějaká uživatelsky definovaná funkce, která vrací celočíselnou hodnotu.

    Cvičení 3.3

    Která z následujících definic proměnných obsahuje syntaktické chyby?

    (a) int car = 1024, auto = 2048; (b) int ival = ival; (c) int ival(int()); d) dvojnásobný plat = mzda = 9999,99; (e) cin >> int vstupní_hodnota;

    Cvičení 3.4

    Vysvětlete rozdíl mezi l-hodnotou a r-hodnotou. Dát příklad.

    Cvičení 3.5

    Najděte rozdíly v použití proměnných jméno a student v prvním a druhém řádku každého příkladu:

    a) název externího řetězce; stringname("cvičení 3.5a"); b) externí vektor studenti; vektor studenti;

    Cvičení 3.6

    Jaké názvy objektů nejsou v C++ povoleny? Změňte je tak, aby byly syntakticky správné:

    (a) int double = 3,14159; (b) vektor< int >_; (c) jmenný prostor řetězce; d) záchyt struny-22; (e) char 1_or_2 = "1"; (f) float Float = 3,14f;

    Cvičení 3.7

    Jaký je rozdíl mezi následujícími definicemi globálních a lokálních proměnných?

    Řetězec globální_třída; int global_int; int main() (
    int local_int;
    řetězec místní_třída; //...
    }

    3.3. Ukazatele

    Ukazatele a dynamické přidělování paměti byly stručně představeny v sekci 2.2. Ukazatel je objekt, který obsahuje adresu jiného objektu a umožňuje vám nepřímo manipulovat s tímto objektem. Ukazatele se obvykle používají k práci s dynamicky vytvářenými objekty, ke konstrukci souvisejících datových struktur, jako jsou propojené seznamy a hierarchické stromy, ak předávání velkých objektů – polí a objektů tříd – jako parametrů funkcí.
    Každý ukazatel je spojen s nějakým typem dat a jejich vnitřní reprezentace nezávisí na interním typu: velikost paměti obsazené objektem typu ukazatele a rozsah hodnot jsou pro ně stejné. Rozdíl je v tom, jak kompilátor zachází s adresovaným objektem. Ukazatele na různé typy mohou mít stejnou hodnotu, ale umístění v paměti, kde jsou umístěny odpovídající typy, se může lišit:

    • ukazatel na int obsahující hodnotu adresy 1000 je směrován do oblasti paměti 1000-1003 (na 32bitovém systému);
    • ukazatel na double obsahující hodnotu adresy 1000 je směrován do oblasti paměti 1000-1007 (na 32bitovém systému).

    Zde jsou nějaké příklady:

    int *ip1, *ip2; komplex *cp; řetězec*pstring; vektor *pvec; double *dp;

    Ukazatel je označen hvězdičkou před jménem. Při definování proměnných seznamu musí před každým ukazatelem předcházet hvězdička (viz výše: ip1 a ip2). V níže uvedeném příkladu je lp ukazatel na objekt typu long a lp2 je objekt typu long:

    Dlouhé *lp, lp2;

    V následujícím případě je fp interpretován jako objekt float a fp2 je ukazatel na něj:

    Float fp, *fp2;

    Operátor dereference (*) lze od názvu oddělit mezerami a dokonce přímo sousedit s klíčovým slovem typu. Proto jsou následující definice syntakticky správné a zcela ekvivalentní:

    //varování: ps2 není ukazatel na řetězec! řetězec* ps, ps2;

    Dá se předpokládat, že ps i ps2 jsou ukazatele, ačkoli ukazatel je pouze prvním z nich.
    Pokud je hodnota ukazatele 0, pak neobsahuje žádnou adresu objektu.
    Nechť je zadána proměnná typu int:

    Intival = 1024;

    Následují příklady definování a použití ukazatelů na int pi a pi2:

    //pi inicializováno na nulovou adresu int *pi = 0;
    // pi2 inicializován s adresou ival
    int *pi2 =
    // správně: pi a pi2 obsahují adresu ival
    pi = pi2;
    // pi2 obsahuje adresu nula
    pi2 = 0;

    Ukazateli nelze přiřadit hodnotu, která není adresou:

    // chyba: pi nemůže být int pi = ival

    Podobně nemůžete přiřadit ukazatel jednoho typu k hodnotě, která je adresou objektu jiného typu. Pokud jsou definovány následující proměnné:

    dvojitý dval; double *ps =

    pak oba níže uvedené výrazy přiřazení způsobí chybu kompilace:

    // chyby kompilace // neplatné přiřazení datového typu: int*<== double* pi = pd pi = &dval;

    Nejde o to, že proměnná pi nemůže obsahovat adresu dval objektu - adresy objektů různých typů mají stejnou délku. Takové operace míchání adres jsou záměrně zakázány, protože interpretace objektů kompilátorem závisí na typu ukazatele na ně.
    Samozřejmě jsou chvíle, kdy nás zajímá hodnota samotné adresy, a ne objekt, na který ukazuje (řekněme, že chceme tuto adresu porovnat s nějakou jinou). K vyřešení takových situací je zaveden speciální ukazatel void, který může ukazovat na jakýkoli datový typ a následující výrazy budou správné:

    // správně: void* může obsahovat // adresy libovolného typu void *pv = pi; pv=pd;

    Typ objektu, na který ukazuje void*, není znám a s tímto objektem nemůžeme manipulovat. Jediné, co můžeme s takovým ukazatelem udělat, je přiřadit jeho hodnotu jinému ukazateli nebo jej porovnat s nějakou adresovou hodnotou. (Více si povíme o ukazateli prázdnoty v části 4.14.)
    Abyste mohli odkazovat na objekt, který má svou adresu, musíte použít operaci dereference neboli nepřímé adresování, označené hvězdičkou (*). S následujícími definicemi proměnných:

    int ival = 1024;, ival2 = 2048; int*pi =

    // nepřímé přiřazení ival k ival2 *pi = ival2;
    // nepřímé použití ival jako rvalue a lvalue
    *pi = abs(*pi); // ival = abs(ival);
    *pi = *pi + 1; // ival = ival + 1;

    Když aplikujeme operaci adresy (&) na objekt typu int, dostaneme výsledek typu int*
    int*pi =
    Pokud je stejná operace aplikována na objekt typu int* (ukazatel na int), dostaneme ukazatel na ukazatel na int, tzn. int**. int** je adresa objektu, který obsahuje adresu objektu typu int. Dereferencováním ppi získáme objekt typu int* obsahující adresu ival. Chcete-li získat samotný objekt ival, musí být operace dereference ppi použita dvakrát.

    int **ppi = π int *pi2 = *ppi;
    cout<< "Значение ival\n" << "явное значение: " << ival << "\n"
    << "косвенная адресация: " << *pi << "\n"
    << "дважды косвенная адресация: " << **ppi << "\n"

    Ukazatele lze použít v aritmetických výrazech. Všimněte si následujícího příkladu, kde dva výrazy provádějí zcela odlišné akce:

    Int i, j, k; int *pi = // i = i + 2
    *pi = *pi + 2; // zvětší adresu obsaženou v pi o 2
    pi = pi + 2;

    K ukazateli můžete přidat celočíselnou hodnotu, můžete od ní také odečítat. Přidání 1 k ukazateli zvýší jeho hodnotu o velikost paměťové oblasti přidělené objektu odpovídajícího typu. Pokud char zabere 1 bajt, int zabere 4 a double zabere 8, pak přidání 2 k ukazatelům na char, int a double zvýší jejich hodnotu o 2, 8 a 16. Jak to lze interpretovat? Pokud jsou objekty stejného typu umístěny v paměti jeden po druhém, pak zvýšení ukazatele o 1 způsobí, že bude ukazovat na další objekt. Proto se při zpracování pole nejčastěji používá ukazatelová aritmetika; v ostatních případech jsou stěží ospravedlnitelné.
    Takto vypadá typický příklad použití aritmetiky adres při iteraci prvků pole pomocí iterátoru:

    intia; int *iter = int *iter_end =
    while (iter != iter_end) (
    udělej_něco_s_hodnotou(*iter);
    ++iter;
    }

    Cvičení 3.8

    Jsou uvedeny definice proměnných:

    int ival = 1024, ival2 = 2048; int *pi1 = &ival, *pi2 = &ival2, **pi3 = 0;

    Co se stane, když se provedou následující operace přiřazení? Jsou v těchto příkladech chyby?

    (a) ival = *pi3; (e) pi = *pi3; (b) *pi2 = *pi3; (f) ival = *pi1; (c) ival = pi2; (g) pi = ival; (d) pi2 = *pi1; (h) pi3 =

    Cvičení 3.9

    Manipulace s ukazateli je jedním z nejdůležitějších aspektů C a C++, ale je snadné dělat chyby. Například kód

    Pi = pí = pí + 1024;

    téměř jistě způsobí, že pi ukáže na náhodnou oblast paměti. Co dělá tento operátor přiřazení a v jakém případě nebude mít za následek chybu?

    Cvičení 3.10

    Tento program obsahuje chybu související s nesprávným použitím ukazatelů:

    int foobar(int *pi) ( *pi = 1024; return *pi; )
    int main() (
    int*pi2 = 0;
    int ival = foobar(pi2);
    návrat 0;
    }

    v čem je chyba? Jak to mohu opravit?

    Cvičení 3.11

    Chyby z předchozích dvou cvičení se projeví a vedou k fatálním následkům kvůli nedostatečnému ověření hodnot ukazatelů v C++ za běhu. Proč si myslíte, že taková kontrola nebyla provedena? Můžete nabídnout nějaké obecné pokyny pro bezpečnější manipulaci s ukazatelem?

    3.4. Typy řetězců

    C++ podporuje dva typy řetězců, vestavěný typ zděděný z C a třídu string ze standardní knihovny C++. Třída string poskytuje mnohem více funkcí, a proto je její použití pohodlnější, ale v praxi často nastávají situace, kdy potřebujete použít vestavěný typ nebo dobře pochopit, jak funguje. (Jedním příkladem by mohla být analýza voleb příkazového řádku předávaných funkci main(). Tomu se budeme věnovat v kapitole 7.)

    3.4.1. vestavěný typ řetězce

    Jak již bylo zmíněno, vestavěný typ řetězce předaný do C ++ zděděný z C. Řetězec znaků je uložen v paměti jako pole a přistupuje se k němu pomocí ukazatele typu char*. Standardní knihovna C poskytuje sadu funkcí pro manipulaci s řetězci. Například:

    // vrátí délku řetězce int strlen(const char*);
    // porovná dva řetězce
    int strcmp(const char*, const char*);
    // zkopíruje jeden řetězec do druhého
    char* strcpy(char*, const char*);

    Standardní knihovna C je součástí knihovny C++. Abychom jej mohli použít, musíme zahrnout hlavičkový soubor:

    #zahrnout

    Ukazatel na char, kterým k řetězci přistupujeme, ukazuje na pole znaků odpovídající řetězci. I když napíšeme řetězec doslovně jako

    Const char *st = "Cena láhve vína\n";

    kompilátor vloží všechny znaky v řetězci do pole a poté přiřadí st adresu prvního prvku v poli. Jak lze pracovat s řetězcem pomocí takového ukazatele?
    Obvykle se aritmetika adresy používá k výčtu znaků řetězce. Protože řetězec vždy končí znakem null, můžete ukazatel zvyšovat o 1, dokud nebude další znak null. Například:

    Zatímco (*st++) ( ... )

    st je dereferencováno a výsledná hodnota je zkontrolována na pravdivost. Jakákoli nenulová hodnota je považována za pravdivou, a proto cyklus končí, když je dosaženo znaku s kódem 0. Operace inkrementace ++ přidá 1 k ukazateli st a tím jej posune na další znak.
    Zde je návod, jak by mohla vypadat implementace funkce, která vrací délku řetězce. Všimněte si, že protože ukazatel může obsahovat hodnotu null (neukazuje na nic), měl by být zkontrolován před operací dereference:

    int string_length(const char *st) ( int cnt = 0; if (st) while (*st++) ++cnt; return cnt; )

    Řetězec vestavěného typu lze považovat za prázdný ve dvou případech: pokud má ukazatel na řetězec hodnotu null (pak nemáme vůbec žádný řetězec), nebo pokud ukazuje na pole sestávající z jednoho znaku null (tj. řetězec, který neobsahuje jediný významný znak).

    // pc1 neadresuje žádné pole znaků char *pc1 = 0; // adresy pc2 null znak const char *pc2 = "";

    Pro začínajícího programátora je používání řetězců vestavěného typu plné chyb kvůli příliš nízké úrovni implementace a nemožnosti obejít se bez aritmetiky adres. Níže si ukážeme některé typické chyby začátečníků. Úkol je jednoduchý: vypočítat délku řetězce. První verze je nesprávná. Opravte ji.

    #zahrnout const char *st = "Cena láhve vína\n"; int main() (
    intlen = 0;
    while (st++) ++len; cout<< len << ": " << st;
    návrat 0;
    }

    V této verzi není st ukazatel dereferencován. Není to tedy znak, na který ukazuje st, který je shodný s 0, ale samotný ukazatel. Protože tento ukazatel měl původně nenulovou hodnotu (adresu řádku), nikdy se nestane nulou a smyčka bude probíhat neomezeně dlouho.
    Ve druhé verzi programu je tato chyba odstraněna. Program skončí úspěšně, ale výsledek je nesprávný. Kde jsme tentokrát špatně?

    #zahrnout const char *st = "Cena láhve vína\n"; int main()
    {
    intlen = 0;
    while (*st++) ++len; cout<< len << ": " << st << endl;
    návrat 0;
    }

    Chyba je v tom, že po skončení cyklu ukazatel st neadresuje původní znakový literál, ale znak umístěný v paměti za ukončovací nulou tohoto literálu. Na tomto místě může být cokoli a výstupem programu bude náhodná sekvence znaků.
    Můžete zkusit opravit tuto chybu:

    St = st - len; cout<< len << ": " << st;

    Nyní náš program produkuje něco smysluplného, ​​ale ne úplně. Odpověď vypadá takto:

    18: ena láhev vína

    Zapomněli jsme vzít v úvahu, že do vypočítané délky nebyl započítán ukončovací znak null. st by mělo být posunuto o délku řetězce plus 1. Zde je konečně správné tvrzení:

    St \u003d st - len - 1;

    a zde je správný výsledek:

    18: Cena láhve vína

    Nemůžeme však říci, že by náš program vypadal elegantně. Operátor

    St \u003d st - len - 1;

    přidáno k opravě chyby v rané fázi návrhu programu – přímé zvýšení ukazatele st. Toto prohlášení nezapadá do logiky programu a kód je nyní obtížně srozumitelný. Opravy tohoto druhu se často označují jako záplaty – něco, co je navrženo k zaplnění díry v existujícím programu. Mnohem lepším řešením by bylo přehodnotit logiku. Jednou z možností v našem případě by bylo definovat druhý ukazatel inicializovaný hodnotou st:

    Const char *p = st;

    Nyní lze p použít v délkové smyčce a ponechat hodnotu st nezměněnou:

    Zatímco (*p++)

    3.4.2. třída strun

    Jak jsme právě viděli, použití vestavěného typu řetězce je náchylné k chybám a není příliš pohodlné, protože je implementováno na příliš nízké úrovni. Proto je zcela běžné vyvinout vlastní třídu nebo třídy reprezentující typ řetězce - téměř každá společnost, oddělení nebo individuální projekt měl svou vlastní implementaci řetězce. Co mohu říci, totéž jsme udělali v předchozích dvou vydáních této knihy! To způsobilo problémy s kompatibilitou a přenositelností programů. Implementace třídy standardních řetězců pomocí standardní knihovny C++ měla tento vynález jízdních kol ukončit.
    Zkusme specifikovat minimální sadu operací, které by třída string měla mít:

    • inicializace polem znaků (řetězec vestavěného typu) nebo jiným objektem typu string. Vestavěný typ nemá druhou schopnost;
    • kopírování jednoho řádku do druhého. Pro vestavěný typ musíte použít funkci strcpy();
    • přístup k jednotlivým znakům řetězce pro čtení a zápis. Ve vestavěném poli se to provádí pomocí operace dolního indexu nebo nepřímého adresování;
    • srovnání dvou řetězců pro rovnost. Pro vestavěný typ použijte funkci strcmp();
    • zřetězení dvou řetězců, čímž získáte výsledek buď jako třetí řetězec, nebo místo jednoho z původních. Pro vestavěný typ se používá funkce strcat(), ale abyste získali výsledek na novém řádku, musíte postupně použít funkce strcpy() a strcat();
    • výpočet délky řetězce. Délku řetězce vestavěného typu můžete zjistit pomocí funkce strlen();
    • schopnost zjistit, zda je řetězec prázdný. U vestavěných řetězců je pro tento účel nutné zkontrolovat dvě podmínky: char str = 0; //... if (! str || ! *str) return;

    Třída řetězců standardní knihovny C++ implementuje všechny tyto operace (a mnohem více, jak uvidíme v kapitole 6). V této části se naučíme používat základní operace této třídy.
    Abyste mohli používat objekty třídy string, musíte zahrnout příslušný soubor záhlaví:

    #zahrnout

    Zde je příklad řetězce z předchozí části, reprezentovaného objektem typu string a inicializovaným znakovým řetězcem:

    #zahrnout string st("Cena lahve vina\n");

    Délku řetězce vrací členská funkce size() (délka nezahrnuje ukončující znak null).

    Cout<< "Длина " << st << ": " << st.size() << " символов, включая символ новой строки\n";

    Druhá forma definice řetězce určuje prázdný řetězec:

    Řetězec st2; // prázdný řádek

    Jak poznáme, že je řetězec prázdný? Jeho délku samozřejmě můžete porovnat s 0:

    if (! st.size()) // správně: prázdné

    Existuje však také speciální metoda empty(), která vrací hodnotu true pro prázdný řetězec a false pro neprázdný řetězec:

    if (st.empty()) // správně: prázdný

    Třetí forma konstruktoru inicializuje objekt typu string s jiným objektem stejného typu:

    Řetězec st3(st);

    Řetězec st3 je inicializován řetězcem st. Jak se můžeme ujistit, že se tyto řetězce shodují? Použijme porovnávací operátor (==):

    If (st == st3) // inicializace úspěšná

    Jak zkopírovat jeden řádek do druhého? S obvyklým operátorem přiřazení:

    st2 = st3; // zkopírujte st3 do st2

    Ke zřetězení řetězců použijte operátor sčítání (+) nebo operátor přiřazení sčítání (+=). Nechť jsou uvedeny dva řádky:

    String s1("ahoj, "); řetězec s2("svět\n");

    Můžeme získat třetí řetězec sestávající ze zřetězení prvních dvou, tedy:

    Řetězec s3 = s1 + s2;

    Pokud chceme přidat s2 na konec s1, musíme napsat:

    SI + = s2;

    Operace sčítání může zřetězit objekty třídy string nejen mezi sebou, ale také s řetězci vestavěného typu. Výše uvedený příklad můžete přepsat tak, že speciální znaky a interpunkční znaménka jsou reprezentovány vestavěným typem a významná slova jsou objekty řetězce třídy:

    Const char *pc = ","; řetězec s1("ahoj"); řetězec s2("svět");
    řetězec s3 = s1 + pc + s2 + "\n";

    Takové výrazy fungují, protože kompilátor ví, jak automaticky převést objekty vestavěného typu na objekty typu string. Je také možné jednoduše přiřadit vestavěný řetězec k objektu typu string:

    Řetězec s1; const char *pc = "pole znaků"; s1=pc; // Že jo

    Zpětná transformace však nefunguje. Pokus o provedení následující vestavěné inicializace řetězce typu způsobí chybu kompilace:

    Char*str = s1; // chyba kompilace

    Chcete-li provést tento převod, musíte explicitně zavolat členskou funkci s poněkud zvláštním názvem c_str():

    Char *str = s1.c_str(); // téměř správně

    Funkce c_str() vrací ukazatel na pole znaků obsahující řetězec objektu string, jako by tomu bylo u vestavěného typu řetězce.
    Výše uvedený příklad inicializace ukazatele char *str stále není zcela správný. c_str() vrací ukazatel na pole const, aby se zabránilo přímé úpravě obsahu objektu prostřednictvím tohoto ukazatele, který má typ

    Const char *

    (O klíčovém slově const si povíme v další části.) Správná inicializace vypadá takto:

    Const char *str = s1.c_str(); // Že jo

    K jednotlivým znakům řetězcového objektu, stejně jako k vestavěnému typu, lze přistupovat pomocí operace indexu. Zde je například fragment kódu, který nahradí všechny tečky podtržítky:

    Stringstr("fa.disney.com"); int velikost = str.size(); for (int ix = 0; ix< size; ++ix) if (str[ ix ] == ".")
    str[ix] = "_";

    To je vše, co jsme teď chtěli říct o smyčcové třídě. Ve skutečnosti má tato třída mnohem zajímavější vlastnosti a schopnosti. Řekněme, že předchozí příklad je také implementován voláním jediné funkce replace():

    Replace(str.begin(), str.end(), ".", "_");

    nahradit() je jedním ze zobecněných algoritmů, které jsme představili v sekci 2.8 a podrobně se o něm budeme věnovat v kapitole 12. Tato funkce iteruje v rozsahu od begin() do end() , které vrací ukazatele na začátek a konec string a nahradí elementy rovné jeho třetímu parametru čtvrtým.

    Cvičení 3.12

    Hledejte chyby v následujících prohlášeních:

    (a) char ch = "Dlouhá a klikatá cesta"; (b) int ival = (c) char *pc = (d) string st(&ch); (e) pc = 0; (i) pc = "0";
    (f) st = pc; (j) st =
    (g) ch = pc; (k) ch = *pc;
    (h) pc = st; (l) *pc = ival;

    Cvičení 3.13

    Vysvětlete rozdíl v chování následujících příkazů cyklu:

    Zatímco (st++) ++cnt;
    zatímco (*st++)
    ++cnt;

    Cvičení 3.14

    Jsou uvedeny dva sémanticky ekvivalentní programy. První používá vestavěný typ řetězce, druhý používá třídu string:

    // ***** Implementace pomocí C řetězců ***** #include #zahrnout
    int main()
    {
    int chyby = 0;
    const char *pc = "velmi dlouhý doslovný řetězec"; for (int ix = 0; ix< 1000000; ++ix)
    {
    int len ​​​​= strlen(pc);
    char *pc2 = nový char[ len + 1 ];
    strcpy(pc2, pc);
    if (strcmp(pc2, pc))
    ++chyby; smazat pc2;
    }
    cout<< "C-строки: "
    << errors << " ошибок.\n";
    ) // ***** Implementace pomocí třídy string ***** #include
    #zahrnout
    int main()
    {
    int chyby = 0;
    string str("velmi dlouhý doslovný řetězec"); for (int ix = 0; ix< 1000000; ++ix)
    {
    int len ​​​​= str.size();
    řetězec str2 = str;
    if (str != str2)
    }
    cout<< "класс string: "
    << errors << " ошибок.\n;
    }

    Co tyto programy dělají?
    Ukazuje se, že druhá implementace je dvakrát rychlejší než první. Čekal jste takový výsledek? jak to vysvětlíš?

    Cvičení 3.15

    Je něco, co byste mohli zlepšit nebo přidat do sady operací s třídou string v poslední sekci? Vysvětlete své návrhy

    3.5. konst specifikátor

    Vezměme si následující příklad kódu:

    For(int index = 0; index< 512; ++index) ... ;

    Při používání literálu 512 jsou dva problémy. Prvním je snadnost vnímání textu programu. Proč by horní mez proměnné smyčky měla být přesně 512? Co se za touto hodnotou skrývá? Vypadá náhodně...
    Druhý problém se týká snadnosti úprav a údržby kódu. Předpokládejme, že program se skládá z 10 000 řádků a doslovných 512 se vyskytuje ve 4 % z nich. Řekněme, že v 80 % případů by mělo být číslo 512 změněno na 1024. Dokážete si představit pracnost takové práce a množství chyb, kterých se lze dopustit opravou špatné hodnoty?
    Oba tyto problémy jsou vyřešeny současně: potřebujeme vytvořit objekt s hodnotou 512. Když mu dáme smysluplný název, například bufSize, uděláme program mnohem srozumitelnějším: je jasné, co přesně je smyčka proměnná se porovnává s.

    Index< bufSize

    V tomto případě změna velikosti bufSize nevyžaduje prohlížení 400 řádků kódu pro úpravu 320 z nich. Jak moc se snižuje pravděpodobnost chyb za cenu přidání pouze jednoho objektu! Nyní je hodnota 512 lokalizované.

    bufSize = 512; // velikost vstupní vyrovnávací paměti // ... for (int index = 0; index< bufSize; ++index)
    // ...

    Zůstává jeden malý problém: proměnná bufSize je zde l-hodnota, kterou lze v programu náhodně změnit, což povede k těžko dohledatelné chybě. Zde je jedna z nejčastějších chyb - použití operátoru přiřazení (=) místo porovnání (==):

    // náhodná změna hodnoty bufSize if (bufSize = 1) // ...

    V důsledku provedení tohoto kódu bude hodnota bufSize rovna 1, což může vést ke zcela nepředvídatelnému chování programu. Chyby tohoto druhu se většinou velmi obtížně odhalují, protože prostě nejsou vidět.
    Tento problém řeší použití specifikátoru const. Prohlášením objektu jako

    Const int bufSize = 512; // velikost vstupní vyrovnávací paměti

    změníme proměnnou na konstantu s hodnotou 512, jejíž hodnotu nelze změnit: takové pokusy zastaví kompilátor: nesprávné použití operátoru přiřazení místo porovnání, jako ve výše uvedeném příkladu, způsobí chybu při kompilaci.

    // chyba: pokus o přiřazení hodnoty konstantě if (bufSize = 0) ...

    Protože konstantě nelze přiřadit hodnotu, musí být inicializována v místě její definice. Definování konstanty bez její inicializace také způsobí chybu kompilace:

    Const double pí; // chyba: neinicializovaná konstanta

    Const double min.Mzda = 9,60; // Že jo? chyba?
    double *ptr =

    Měl by kompilátor povolit toto přiřazení? Vzhledem k tomu, že minWage je konstanta, nelze jí přiřadit hodnotu. Na druhou stranu nám nic nebrání psát:

    *ptr+= 1,40; // změna objektu minWage!

    Překladač zpravidla není schopen chránit před použitím ukazatelů a nebude schopen signalizovat chybu, pokud budou použity tímto způsobem. To vyžaduje příliš mnoho analýzy logiky programu. Překladač proto jednoduše zakáže přidělovat adresy konstant běžným ukazatelům.
    No, jsme ochuzeni o možnost používat ukazatele na konstanty? Ne. K tomu existují ukazatele deklarované se specifikátorem const:

    Const double *cptr;

    kde cptr je ukazatel na objekt typu const double. Jemnost spočívá v tom, že ukazatel sám o sobě není konstanta, což znamená, že můžeme měnit jeho hodnotu. Například:

    Const double *pc = 0; const double minMage = 9,60; // správně: nelze změnit minWage s pc
    pc = double dval = 3,14; // správně: nelze změnit minWage s pc
    // ačkoli dval není konstanta
    pc = // správně dval = 3,14159; //Že jo
    *pc = 3,14159; // chyba

    Adresa objektu const je přiřazena pouze ukazateli na konstantu. Takovému ukazateli však lze také přiřadit adresu obyčejné proměnné:

    PC =

    Konstantní ukazatel neumožňuje upravit objekt, který adresuje, nepřímým adresováním. Ačkoli dval není ve výše uvedeném příkladu konstanta, kompilátor nedovolí změnit dval přes pc. (Opět proto, že není schopen určit, jakou adresu by mohl mít ukazatel v kterémkoli bodě provádění programu.)
    V reálných programech se jako formální parametry funkcí nejčastěji používají ukazatele na konstanty. Jejich použití zaručuje, že objekt předaný funkci jako skutečný argument nebude touto funkcí modifikován. Například:

    // V reálných programech se jako formální parametry funkcí // nejčastěji používají ukazatele na konstanty int strcmp(const char *str1, const char *str2);

    (O ukazatelích na konstanty si povíme více v kapitole 7, když mluvíme o funkcích.)
    Existují také konstantní ukazatele. (Všimněte si rozdílu mezi konstantním ukazatelem a ukazatelem na konstantu!). Konstantní ukazatel může adresovat jak konstantu, tak proměnnou. Například:

    Inter errNumb = 0; int *const currErr =

    CurErr je zde konstantní ukazatel na non-const objekt. To znamená, že k němu nemůžeme přiřadit adresu jiného objektu, ačkoli samotný objekt lze upravit. Zde je návod, jak lze použít ukazatel curErr:

    Dělej něco(); if (*curErr) (
    errorHandler();
    *curErr = 0; // správně: resetujte hodnotu errNumb
    }

    Pokus o přiřazení hodnoty konstantnímu ukazateli způsobí chybu kompilace:

    CurErr = // chyba

    Konstantní ukazatel na konstantu je spojením dvou uvažovaných případů.

    Const double pi = 3,14159; const double *const pi_ptr = π

    V programu nelze změnit hodnotu objektu, na který ukazuje pi_ptr, ani hodnotu samotného ukazatele.

    Cvičení 3.16

    Vysvětlete význam následujících pěti definic. Je některý z nich špatný?

    (a) int i; (d) int *const cpi; (b) const int ic; (e) const int *const cpic; (c) const int *pic;

    Cvičení 3.17

    Které z následujících definic jsou správné? Proč?

    (a) int i = -1; (b) const int ic = i; (c) const int *pic = (d) int *const cpi = (e) const int *const cpic =

    Cvičení 3.18

    Pomocí definic z předchozího cvičení identifikujte správné operátory přiřazení. Vysvětlit.

    (a) i = ic; (d) pic = cpic; (b) pic = (i) cpic = (c) cpi = pic; (f) ic = *cpic;

    3.6. Typ reference

    Referenční typ, někdy nazývaný alias, se používá k přidělení dalšího názvu objektu. Odkaz vám umožňuje nepřímo manipulovat s objektem, stejně jako to dělá ukazatel. Tato nepřímá manipulace však nevyžaduje speciální syntaxi potřebnou pro ukazatele. Obvykle se jako formální parametry funkcí používají odkazy. V této části se podíváme na použití objektů referenčního typu samostatně.
    Typ odkazu je indikován uvedením operátoru adresy (&) před názvem proměnné. Odkaz musí být inicializován. Například:

    Intival = 1024; // správně: refVal - odkaz na ival int &refVal = ival; // chyba: odkaz musí být inicializován na int

    Intival = 1024; // chyba: refVal je typu int, ne int* int &refVal = int *pi = // správně: ptrVal je odkaz na ukazatel int *&ptrVal2 = pi;

    Jakmile definujete referenci, nemůžete ji změnit, aby fungovala s jiným objektem (proto musí být reference inicializována v místě její definice). V následujícím příkladu příkaz přiřazení nemění hodnotu refVal, nová hodnota je přiřazena proměnné ival, té, kterou refVal adresuje.

    int min_val = 0; // ival získá hodnotu min_val, // not refVal změní hodnotu na min_val refVal = min_val;

    RefVal += 2; přidá 2 k ival, proměnné, na kterou odkazuje refVal. Podobně int ii = refVal; nastaví ii na aktuální hodnotu ival, int *pi = inicializuje pi s adresou ival.

    // jsou definovány dva objekty typu int int ival = 1024, ival2 = 2048; // definována jedna reference a jeden objekt int &rval = ival, rval2 = ival2; // definován jeden objekt, jeden ukazatel a jedna reference
    int inal3 = 1024, *pi = ival3, &ri = ival3; // definovány dvě reference int &rval3 = ival3, &rval4 = ival2;

    Konstantní odkaz může být inicializován objektem jiného typu (pokud samozřejmě existuje možnost převodu jednoho typu na jiný), stejně jako neadresnou hodnotou, jako je doslovná konstanta. Například:

    dvojitý dval = 3,14159; // true pouze pro odkazy const
    const int &ir = 1024;
    const int &ir2 = dval;
    const double &dr = dval + 1,0;

    Pokud bychom specifikátor const nespecifikovali, všechny tři referenční definice by způsobily chybu kompilace. Důvod, proč překladač takové definice nepřeskočí, však není jasný. Zkusme na to přijít.
    U literálů je to víceméně jasné: neměli bychom mít možnost nepřímo měnit hodnotu literálu pomocí ukazatelů nebo odkazů. Co se týče objektů jiného typu, překladač převede původní objekt na nějaký pomocný. Pokud například napíšeme:

    Double dval = 1024; const int &ri = dval;

    pak to kompilátor převede na něco takového:

    int temp = dval; const int &ri = temp;

    Pokud bychom mohli přiřadit novou hodnotu ri referenci, ve skutečnosti bychom nezměnili dval, ale temp. Hodnota dval by zůstala stejná, což je pro programátora naprosto nesrozumitelné. Proto kompilátor takové akce zakazuje a jediný způsob, jak inicializovat odkaz s objektem jiného typu, je deklarovat jej jako const.
    Zde je další příklad odkazu, který je napoprvé obtížně pochopitelný. Chceme definovat odkaz na adresu objektu const, ale naše první možnost způsobí chybu kompilace:

    Const int ival = 1024; // chyba: je potřeba konstantní odkaz
    int *&pi_ref =

    Pokus o vyřešení problému přidáním specifikátoru const se také nezdaří:

    Const int ival = 1024; // stále chyba const int *&pi_ref =

    Jaký je důvod? Při pozorném čtení definice uvidíme, že pi_ref je odkaz na konstantní ukazatel na objekt typu int. A potřebujeme non-const ukazatel na const objekt, takže následující záznam bude správný:

    Const int ival = 1024; // Že jo
    int *const &piref=

    Mezi referencí a ukazatelem jsou dva hlavní rozdíly. Nejprve musí být odkaz inicializován v místě jeho definice. Za druhé, jakákoli změna v odkazu netransformuje odkaz, ale objekt, na který odkazuje. Podívejme se na příklady. Pokud napíšeme:

    int*pi = 0;

    ukazatel pi inicializujeme na nulu, což znamená, že pi neukazuje na žádný objekt. Zároveň rekord

    const int &ri = 0;
    znamená něco takového:
    int temp = 0;
    const int &ri = temp;

    Pokud jde o operaci přiřazení, v následujícím příkladu:

    int ival = 1024, ival2 = 2048; int *pi = &ival, *pi2 = pi = pi2;

    proměnná ival, na kterou ukazuje pi, zůstane nezměněna a pi získá hodnotu adresy ival2. Pi i pi2 stále ukazují na stejný objekt ival2.
    Pokud pracujeme s odkazy:

    Int &ri = ival, &ri2 = ival2; ri = ri2;

    // příklad použití odkazů // Hodnota je vrácena v parametru next_value
    bool get_next_value(int &next_value); // přetížený Matrix operator+(const Matrix&, const Matrix&);

    Intival; while (get_next_value(ival)) ...

    int &next_value = ival;

    (Použití odkazů jako formálních parametrů funkcí je podrobněji popsáno v kapitole 7.)

    Cvičení 3.19

    Jsou v těchto definicích nějaké chyby? Vysvětlit. Jak byste je opravil?

    (a) int ival = 1,01; (b) int &rvall = 1,01; (c) int &rval2 = ival; (d) int &rval3 = (e) int *pi = (f) int &rval4 = pi; (g) int &rval5 = pi*; (h) int &*prval1 = pi; (i) const int &ival2 = 1; (j) const int &*prval2 =

    Cvičení 3.20

    Je některé z následujících zadání chybné (s použitím definic z předchozího cvičení)?

    (a) rval1 = 3,14159; (b) prval1 = prval2; (c) prval2 = rval1; (d) *prval2 = ival2;

    Cvičení 3.21

    Hledejte chyby v následujících pokynech:

    (a) int ival = 0; const int *pi = 0; const int &ri = 0; (b) pí =
    ri =
    pí =

    3.7. typ bool

    Objekt typu bool může nabývat jedné ze dvou hodnot: true a false. Například:

    // inicializace řetězce string search_word = get_word(); // inicializace nalezené proměnné
    bool nalezen = false; řetězec další_slovo; while (cin >> next_word)
    if (další_slovo == hledané_slovo)
    nalezeno = pravda;
    // ... // zkratka: if (found == true)
    pokud (nalezen)
    cout<< "ok, мы нашли слово\n";
    jinak cout<< "нет, наше слово не встретилось.\n";

    Přestože bool je jedním z celočíselných typů, nelze jej deklarovat jako podepsaný, nepodepsaný, krátký nebo dlouhý, takže výše uvedená definice je chybná:

    // nalezena chyba short bool = false;

    Objekty typu bool jsou implicitně převedeny na typ int. Hodnota true se stane 1 a false se stane 0. Například:

    bool nalezen = false; int počet_výskytů = 0; zatímco (/* mumlá */)
    {
    nalezeno = hledat (/* něco */); // nalezená hodnota se převede na 0 nebo 1
    počet_výskytů += nalezeno; )

    Stejným způsobem lze hodnoty celočíselných typů a ukazatelů převést na boolovské hodnoty. V tomto případě je 0 interpretováno jako nepravda a vše ostatní jako pravdivé:

    // vrátí počet výskytů extern int find(const string&); bool nalezen = false; if (found = find("rosebud")) // správně: found == true // vrací ukazatel na prvek
    extern int* find(int hodnota); if (found = find(1024)) // správně: found == true

    3.8. Výčty

    Často je nutné definovat proměnnou, která nabývá hodnot z určité množiny. Řekněme, že soubor je otevřen v kterémkoli ze tří režimů: pro čtení, pro zápis, pro připojení.
    Pro označení těchto režimů lze samozřejmě definovat tři konstanty:

    Const int input = 1; const int output = 2; const int append = 3;

    a použijte tyto konstanty:

    bool open_file(řetězec název_souboru, int otevřený_režim); //...
    open_file("Phoenix_and_the_Crane", append);

    Toto řešení je přijatelné, ale ne zcela přijatelné, protože nemůžeme zaručit, že argument předaný funkci open_file() je pouze 1, 2 nebo 3.
    Použití výčtového typu tento problém řeší. Když píšeme:

    Enum open_modes( input = 1, output, append );

    definujeme nový typ open_modes. Platné hodnoty pro objekt tohoto typu jsou omezeny na 1, 2 a 3, přičemž každá ze zadaných hodnot má mnemotechnický název. Název tohoto nového typu můžeme použít k definování objektu tohoto typu i typu formálních parametrů funkce:

    Void open_file(řetězec název_souboru, otevřené_režimy om);

    vstup, výstup a připojení jsou výčtové prvky. Sada prvků výčtu určuje platnou sadu hodnot pro objekt daného typu. Proměnná typu open_modes (v našem příkladu) je inicializována jednou z těchto hodnot, lze jí také přiřadit kteroukoli z nich. Například:

    Open_file("Fénix a jeřáb", append);

    Pokus o přiřazení hodnoty proměnné tohoto typu, která se liší od jednoho z prvků výčtu (nebo ji předá jako parametr funkci), způsobí chybu kompilace. I když se pokusíme předat celočíselnou hodnotu odpovídající jednomu z prvků výčtu, stále dostaneme chybu:

    // chyba: 1 není člen výčtu open_modes open_file("Jonah", 1);

    Existuje způsob, jak definovat proměnnou typu open_modes, přiřadit jí hodnotu jednoho z prvků výčtu a předat ji jako parametr funkci:

    open_modes om=vstup; // ...om = append; open_file("TailTell", om);

    Názvy takových prvků však není možné získat. Pokud napíšeme výstupní příkaz:

    Cout<< input << " " << om << endl;

    stále dostáváme:

    Tento problém je vyřešen definováním pole řetězců, ve kterém bude prvek s indexem rovným hodnotě prvku enum obsahovat svůj název. S takovým polem můžeme napsat:

    Cout<< open_modes_table[ input ] << " " << open_modes_table[ om ] << endl Будет выведено: input append

    Také nemůžete iterovat přes všechny hodnoty výčtu:

    // není podporováno pro (open_modes iter = input; iter != append; ++inter) // ...

    Klíčové slovo enum se používá k definování výčtu a názvy prvků jsou uvedeny ve složených závorkách oddělených čárkami. Ve výchozím nastavení je první 0, další 1 a tak dále. Operátor přiřazení může toto pravidlo změnit. V tomto případě bude každý další prvek bez explicitně zadané hodnoty o 1 více než prvek před ním v seznamu. V našem příkladu jsme explicitně zadali hodnotu 1 pro vstup a výstup a připojení budou 2 a 3. Zde je další příklad:

    // tvar == 0, koule == 1, válec == 2, mnohoúhelník == 3 enum Forms( share, spere, cylinder, polygon );

    Celočíselné hodnoty odpovídající různým prvkům stejného výčtu se nemusí lišit. Například:

    // bod2d == 2, bod2w == 3, bod3d == 3, bod3w == 4 enum Body ( bod2d=2, bod2w, bod3d=3, bod3w=4 );

    Objekt, jehož typ je enum, lze definovat, použít ve výrazech a předat funkci jako argument. Takový objekt je pouze inicializován hodnotou jednoho z prvků výčtu a je mu přiřazena pouze tato hodnota, buď explicitně, nebo jako hodnota jiného objektu stejného typu. Nelze k němu přiřadit ani celočíselné hodnoty odpovídající platným prvkům výčtu:

    Void mumble() ( Body pt3d = bod3d; // správně: pt2d == 3 // chyba: pt3w je inicializováno na typ int Body pt3w = 3; // chyba: mnohoúhelník není ve výčtu bodů pt3w = mnohoúhelník; // správně : oba objekty typu Body pt3w = pt3d; )

    V aritmetických výrazech však lze výčet automaticky převést na typ int. Například:

    const int velikost_pole = 1024; // správně: pt2w se převede na int
    int chunk_size = velikost_pole * pt2w;

    3.9. Zadejte "pole"

    Pole jsme se již dotkli v sekci 2.1. Pole je množina prvků stejného typu, ke kterým se přistupuje pomocí indexu – pořadového čísla prvku v poli. Například:

    Intival;

    definuje ival jako proměnnou typu int a instrukce

    int ia[10];

    určuje pole deseti objektů int. U každého z těchto objektů, popř prvky pole, lze získat přístup pomocí operace převzetí indexu:

    Ival = ia[2];

    přiřadí proměnné ival hodnotu prvku pole ia s indexem 2. Podobně

    la[7] = ival;

    nastaví prvek na indexu 7 na ival.

    Definice pole se skládá ze specifikátoru typu, názvu pole a velikosti. Velikost určuje počet prvků pole (alespoň 1) a je uzavřena v hranatých závorkách. Velikost pole musí být známa již v době kompilace, a proto musí být konstantním výrazem, i když to nemusí být doslovný. Zde jsou příklady správných a nesprávných definic pole:

    extern int get_size(); // konstanty buf_size a max_files
    const int buf_size = 512, max_files = 20;
    intstaff_size = 27; // správně: konstantní char input_buffer[ buf_size ]; // správně: konstantní výraz: 20 - 3 char *fileTable[ max_files-3 ]; // chyba: nejde o konstantní dvojnásobné platy[ velikost_personálu ]; // chyba: není konstantní výraz int test_scores[ get_size() ];

    Objekty buf_size a max_files jsou konstanty, takže definice pole input_buffer a fileTable jsou správné. Ale staff_size je proměnná (i když inicializovaná na konstantu 27), takže platy jsou nezákonné. (Kompilátor nemůže najít hodnotu proměnné staff_size v době, kdy je definováno pole platů.)
    Výraz max_files-3 lze vyhodnotit v době kompilace, takže definice pole fileTable je syntakticky správná.
    Číslování prvků začíná od 0, takže pro pole 10 prvků není správný rozsah indexu 1 - 10, ale 0 - 9. Zde je příklad iterace přes všechny prvky pole:

    int main() ( const int velikost_pole = 10; int ia[ velikost_pole ]; for (int ix = 0; ix< array_size; ++ ix)
    ia[ ix] = ix;
    }

    Při definování pole jej můžete explicitně inicializovat uvedením hodnot jeho prvků ve složených závorkách, oddělených čárkami:

    const int velikost_pole = 3; int ia[velikost_matice] = (0, 1, 2);

    Pokud explicitně zadáme seznam hodnot, pak nemůžeme specifikovat velikost pole: kompilátor si počet prvků vypočítá sám:

    // pole velikosti 3 int ia = ( 0, 1, 2 );

    Když jsou explicitně zadány jak velikost, tak seznam hodnot, existují tři možnosti. Když se velikost a počet hodnot shodují, je vše zřejmé. Pokud je seznam hodnot kratší než zadaná velikost, zbývající prvky pole se inicializují na nulu. Pokud je v seznamu více hodnot, kompilátor zobrazí chybovou zprávu:

    // ia ==> ( 0, 1, 2, 0, 0 ) const int velikost_pole = 5; int ia[velikost_matice] = (0, 1, 2);

    Pole znaků lze inicializovat nejen pomocí seznamu hodnot znaků ve složených závorkách, ale také pomocí řetězcového literálu. Mezi těmito metodami však existují určité rozdíly. Řekněme

    Const char cal = ("C", "+", "+" ); const char cal2 = "C++";

    Dimenze pole ca1 je 3, dimenze pole ca2 je 4 (v řetězcových literálech je zohledněn ukončovací znak null). Následující definice způsobí chybu kompilace:

    // chyba: řetězec "Daniel" se skládá ze 7 prvků const char ch3[ 6 ] = "Daniel";

    Pole nelze přiřadit hodnotu jiného pole a inicializace jednoho pole jiným je také nezákonná. Rovněž není povoleno používat pole odkazů. Zde jsou příklady správného a nesprávného použití polí:

    const int velikost_pole = 3; int ix, jx, kx; // správně: pole ukazatelů typu int* int *iar = ( &ix, &jx, &kx ); // chyba: referenční pole jsou neplatná int &iar = ( ix, jx, kx ); int main()
    {
    int ia3( array_size ]; // správně
    // chyba: vestavěná pole nelze zkopírovat
    ia3 = ia;
    návrat 0;
    }

    Chcete-li zkopírovat jedno pole do druhého, musíte to udělat pro každý prvek samostatně:

    const int velikost_pole = 7; int ia1 = (0, 1, 2, 3, 4, 5, 6); int main() (
    int ia3[velikost_pole]; for (int ix = 0; ix< array_size; ++ix)
    ia2[ ix] = ia1[ ix]; návrat 0;
    }

    Index pole může být jakýkoli výraz, který dává výsledek typu celé číslo. Například:

    int someVal, get_index(); ia2[ get_index() ] = someVal;

    Zdůrazňujeme, že jazyk C++ neposkytuje kontrolu nad indexy polí, a to ani v době kompilace, ani v době běhu. Programátor sám musí zajistit, aby index nepřesahoval hranice pole. Chyby indexu jsou poměrně časté. Bohužel není tak těžké najít příklady programů, které se zkompilují a dokonce i spustí, ale přesto obsahují fatální chyby, které dříve nebo později vedou k havárii.

    Cvičení 3.22

    Které z následujících definic pole obsahují chyby? Vysvětlit.

    (a) int ia[velikost_buf]; (d) int ia[ 2 * 7 - 14] (b) int ia[ get_size() ]; (e) char st[ 11 ] = "základní"; (c) int ia[4*7-14];

    Cvičení 3.23

    Následující fragment kódu by měl inicializovat každý prvek pole hodnotou indexu. Najděte chyby, které jste udělali:

    int main() ( const int velikost_pole = 10; int ia[ velikost_pole ]; for (int ix = 1; ix<= array_size; ++ix)
    ia[ia] = ix; //...
    }

    3.9.1. Vícerozměrná pole

    V C++ je možné použít vícerozměrná pole, která musí být deklarována s pravou hranicí každé dimenze v samostatných hranatých závorkách. Zde je definice dvourozměrného pole:

    Int ia[ 4][3];

    První hodnota (4) udává počet řádků, druhá (3) počet sloupců. Objekt ia je definován jako pole čtyř řetězců, každý se třemi prvky. Vícerozměrná pole lze také inicializovat:

    Int ia[4][3] = ((0, 1,2), (3, 4, 5), (6, 7, 8), (9, 10, 11));

    Vnitřní složené závorky, které rozdělují seznam hodnot na řádky, jsou volitelné a používají se zpravidla pro čitelnost kódu. Následující inicializace je úplně stejná jako v předchozím příkladu, i když méně jasná:

    int ia = (0,1,2,3,4,5,6,7,8,9,10,11);

    Následující definice inicializuje pouze první prvky každého řádku. Zbývající prvky budou nulové:

    Int ia[4][3] = ((0), (3), (6), (9));

    Pokud vynecháte vnitřní kudrnatá rovnátka, je výsledek úplně jiný. Všechny tři prvky prvního řádku a první prvek druhého obdrží zadanou hodnotu a zbytek bude implicitně inicializován na 0.

    Int ia[4][3] = (0, 3, 6, 9);

    Při přístupu k prvkům vícerozměrného pole musíte použít indexy pro každý rozměr (jsou uzavřeny v hranatých závorkách). Takto vypadá inicializace dvourozměrného pole pomocí vnořených smyček:

    int main() ( const int rowSize = 4; const int colSize = 3; int ia[ rowSize ][ colSize ]; for (int = 0; i< rowSize; ++i)
    for (int j = 0; j< colSize; ++j)
    ia[i][j] = i + j j;
    }

    Design

    Ia[1, 2]

    je platný z hlediska syntaxe C++, ale neznamená to, co by nezkušený programátor očekával. V žádném případě se nejedná o deklaraci dvojrozměrného pole 1 x 2. Agregát v hranatých závorkách je seznam výrazů oddělených čárkami, jejichž výsledkem bude konečná hodnota 2 (viz operátor „čárka“ v části 4.2 ). Proto je deklarace ia ekvivalentní ia. To je další příležitost udělat chybu.

    3.9.2. Vztah mezi poli a ukazateli

    Pokud máme definici pole:

    Int ia = (0, 1, 1, 2, 3, 5, 8, 13, 21);

    co potom znamená jednoduché uvedení jeho jména v programu?

    Použití identifikátoru pole v programu je ekvivalentní zadání adresy jeho prvního prvku:

    Podobně existují dva způsoby přístupu k hodnotě prvního prvku pole:

    // oba výrazy vrátí první prvek *ia; IA;

    Abychom převzali adresu druhého prvku pole, napsali bychom:

    Jak jsme již zmínili, výraz

    také poskytuje adresu druhého prvku pole. Jeho hodnota je nám tedy dána následujícími dvěma způsoby:

    *(ia+1); IA;

    Všimněte si rozdílu ve výrazech:

    *ia+1 a *(ia+1);

    Operace dereference má vyšší prioritou než operace sčítání (další informace o prioritě operátorů naleznete v části 4.13). Proto první výraz nejprve dereferencuje proměnnou ia a získá první prvek pole a poté k němu přidá 1. Druhý výraz dodá hodnotu druhého prvku.

    Pole můžete iterovat pomocí indexu, jak jsme to udělali v předchozí části, nebo pomocí ukazatelů. Například:

    #zahrnout int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); int *pbegin = ia; int *pend = ia + 9; while (pbegin != pend) ( cout<< *pbegin <<; ++pbegin; } }

    Ukazatel pbegin je inicializován adresou prvního prvku pole. Každá iterace smyčkou zvýší tento ukazatel o 1, což znamená, že se přesune na další prvek. Jak zjistit, kde se ubytovat? V našem příkladu jsme definovali druhý pend pointer a inicializovali jej s adresou následující za posledním prvkem pole ia. Jakmile se hodnota pbegin rovná pend, víme, že pole je u konce. Přepišme tento program tak, aby začátek a konec pole byly předány jako parametry nějaké obecné funkci, která dokáže vytisknout pole libovolné velikosti:

    #zahrnout void ia_print(int *pbegin, int *pend) (
    while (pbegin != pend) (
    cout<< *pbegin << " ";
    ++pbegin;
    }
    ) int main()
    {
    int ia = (0, 1, 1, 2, 3, 5, 8, 13, 21);
    ia_print(ia, ia + 9);
    }

    Naše funkce se stala univerzálnější, nicméně umí pracovat pouze s poli int. Existuje způsob, jak toto omezení také odstranit: převést danou funkci na šablonu (šablony byly krátce představeny v sekci 2.5):

    #zahrnout šablona void print(elemType *pbegin, elemType *pend) ( while (pbegin != pend) ( cout<< *pbegin << " "; ++pbegin; } }

    Nyní můžeme zavolat naši funkci print() k tisku polí libovolného typu:

    int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); double da = ( 3,14, 6,28, 12,56, 25,12 ); string sa = ( "prasátko", " eyore", "pú" ); print(ia, ia+9);
    print(da,da+4);
    tisknout(sa, sa+3);
    }

    Psali jsme zobecněný funkce. Standardní knihovna poskytuje sadu generických algoritmů (již jsme to zmínili v sekci 3.4) implementovaných tímto způsobem. Parametry takových funkcí jsou ukazatele na začátek a konec pole, se kterým provádějí určité akce. Zde je například uvedeno, jak vypadají volání obecného algoritmu řazení:

    #zahrnout int main() ( int ia = ( 107, 28, 3, 47, 104, 76 ); string sa = ( "prasátko", "eeyore", "pú" ); sort(ia, ia+6);
    sort(sa, sa+3);
    };

    (Zobecněné algoritmy podrobně probereme v kapitole 12; příklady jejich použití budou uvedeny v příloze.)
    Standardní knihovna C++ obsahuje sadu tříd, které zapouzdřují použití kontejnerů a ukazatelů. (Probrali jsme to v části 2.8.) V další části se podíváme na standardní vektor typu kontejneru, což je objektově orientovaná implementace pole.

    3.10. vektorová třída

    Použití třídy vector (viz část 2.8) je alternativou k používání vestavěných polí. Tato třída poskytuje mnohem více funkcí, takže její použití je vhodnější. Existují však situace, kdy se pole vestavěného typu nelze obejít. Jednou z těchto situací je zpracování parametrů příkazového řádku předávaných programu, o kterém pojednáme v části 7.8. Třída vector, stejně jako třída string, je součástí standardní knihovny C++.
    Chcete-li použít vektor, musíte zahrnout soubor záhlaví:

    #zahrnout

    Existují dva zcela odlišné přístupy k použití vektoru, nazvěme je idiom pole a idiom STL. V prvním případě se objekt třídy vector používá stejným způsobem jako pole vestavěného typu. Vektor dané dimenze je definován:

    vektor< int >ivec(10);

    což je stejné jako definování pole vestavěného typu:

    int ia[10];

    Pro přístup k jednotlivým prvkům vektoru se používá operace převzetí indexu:

    Void simp1e_examp1e() ( const int e1em_size = 10; vektor< int >ivec(e1em_velikost); int ia[ e1em_velikost ]; for (int ix = 0; ix< e1em_size; ++ix)
    ia[ ix ] = ivec[ ix ]; //...
    }

    Velikost vektoru můžeme zjistit pomocí funkce size() a zkontrolovat, zda je vektor prázdný, pomocí funkce empty(). Například:

    Void print_vector(vektor ivec) ( if (ivec.empty()) return; for (int ix=0; ix< ivec.size(); ++ix)
    cout<< ivec[ ix ] << " ";
    }

    Vektorové prvky jsou inicializovány s výchozími hodnotami. Pro číselné typy a ukazatele je tato hodnota 0. Pokud objekty třídy fungují jako prvky, pak je jejich iniciátor určen výchozím konstruktorem (viz oddíl 2.3). Iniciátor však může být také specifikován explicitně pomocí formuláře:

    vektor< int >ivec(10, -1);

    Všech deset prvků vektoru se bude rovnat -1.
    Pole vestavěného typu lze explicitně inicializovat pomocí seznamu:

    int ia[6] = (-2, -1, 0, 1, 2, 1024);

    Pro objekt třídy vector není podobná akce možná. Takový objekt však lze inicializovat pomocí pole vestavěného typu:

    // 6 prvků ia se zkopíruje do vektoru ivec< int >ivec(ia, ia+6);

    Do vektorového konstruktoru ivec jsou předány dva ukazatele - ukazatel na začátek pole ia a na prvek následující za posledním. Jako seznam počátečních hodnot je přípustné zadat ne celé pole, ale některé z jeho rozsahu:

    // Kopírují se 3 prvky: ia, ia, ia vector< int >ivec(&ia[2], &ia[5]);

    Dalším rozdílem mezi vektorem a polem vestavěného typu je schopnost inicializovat jeden objekt typu vector s jiným a ke kopírování objektů použít operátor přiřazení. Například:

    vektor< string >svec; void init_and_assign() ( // jeden vektor je inicializován jiným vektorem< string >user_names(svec); // ... // jeden vektor je zkopírován do druhého
    svec = uživatelská_jména;
    }

    Když už mluvíme o idiomu STL, máme na mysli velmi odlišný přístup k použití vektoru. Místo okamžitého nastavení správné velikosti definujeme prázdný vektor:

    vektor< string >text;

    Pak do něj pomocí různých funkcí přidáváme prvky. Například funkce push_back() vloží prvek na konec vektoru. Zde je část kódu, která čte sekvenci řádků ze standardního vstupu a přidává je do vektoru:

    řetězec; while (cin >> slovo) ( text.push_back(word); // ... )

    I když můžeme použít operaci vzít index k iteraci prvků vektoru:

    Cout<< "считаны слова: \n"; for (int ix =0; ix < text.size(); ++ix) cout << text[ ix ] << " "; cout << endl;

    typičtější v tomto idiomu by bylo použití iterátorů:

    Cout<< "считаны слова: \n"; for (vector::iterator it = text.begin(); it != text.end(); ++it) cout<< *it << " "; cout << endl;

    Iterátor je standardní třída knihovny, která je ve skutečnosti ukazatelem na prvek pole.
    Výraz

    dereferencuje iterátor a dává samotný vektorový prvek. Návod

    Posune ukazatel na další prvek. Není třeba tyto dva přístupy míchat. Při definování prázdného vektoru postupujte podle idiomu STL:

    vektor ivec;

    Byla by chyba napsat:

    Zatím nemáme jediný prvek vektoru ivec; počet prvků se zjistí pomocí funkce size().

    Můžete udělat i opačnou chybu. Pokud jsme definovali vektor nějaké velikosti, například:

    vektor ia(10);

    Pak vkládání prvků zvětší jeho velikost přidáním nových prvků k existujícím. Ačkoli se to zdá zřejmé, začínající programátor by mohl snadno napsat:

    Const int size = 7; int ia[velikost] = (0, 1, 1, 2, 3, 5, 8); vektor< int >ivec(velikost); for (int ix = 0; ix< size; ++ix) ivec.push_back(ia[ ix ]);

    To znamenalo inicializaci vektoru ivec s hodnotami prvků ia, místo kterých jsme dostali vektor ivec o velikosti 14.
    Podle idiomu STL můžete prvky vektoru nejen přidávat, ale také odebírat. (To vše podrobně a s příklady probereme v kapitole 6.)

    Cvičení 3.24

    Jsou v následujících definicích chyby?
    int ia[7] = (0, 1, 1, 2, 3, 5, 8);

    (a) vektor< vector< int >>ivec;
    (b) vektor< int >ivec = (0, 1, 1, 2, 3, 5, 8);
    (c) vektor< int >ivec(ia, ia+7);
    (d) vektor< string >svec = ivec;
    (e)vektor< string >svec(10, string("null"));

    Cvičení 3.25

    Implementujte následující funkci:
    bool is_equal(const int*ia, int ia_size,
    konstantní vektor &ivec);
    Funkce is_equal() porovnává dva kontejnery prvek po prvku. V případě různých velikostí nádob se „ocas“ delší nezohledňuje. Je jasné, že pokud jsou všechny porovnávané prvky stejné, funkce vrací true, pokud se alespoň jeden liší, false. K iteraci prvků použijte iterátor. Napište funkci main(), která volá is_equal().

    3.11. komplexní třída

    Třída komplexních čísel je další třídou ze standardní knihovny. Jako obvykle, abyste jej mohli použít, musíte zahrnout soubor záhlaví:

    #zahrnout

    Komplexní číslo má dvě části – reálnou a imaginární. Imaginární část je druhá odmocnina záporného čísla. Komplexní číslo se obvykle zapisuje jako

    kde 2 je skutečná část a 3i je imaginární část. Zde jsou příklady komplexních definic objektů:

    // čisté imaginární číslo: 0 + 7-i komplex< double >purei(0,7); // imaginární část je 0:3 + komplex Oi< float >real1_num(3); // skutečná i imaginární část jsou 0: 0 + 0-i komplexní< long double >nula; // inicializace jednoho komplexního čísla jiným komplexem< double >purei2(purei);

    Protože komplex, stejně jako vektor, je šablona, ​​můžeme ji vytvořit pomocí float, double a long double, jako ve výše uvedených příkladech. Můžete také definovat řadu složitých prvků:

    Komplex< double >konjugovat[2] = (komplex< double >(2, 3), komplexní< double >(2, -3) };

    Komplex< double >*ptr = komplexní< double >&ref = *ptr;

    3.12. směrnice typedef

    Direktiva typedef umožňuje zadat synonymum pro vestavěný nebo uživatelem definovaný datový typ. Například:

    Typedef dvojnásobné mzdy; typedef vektor vec_int; typedef vec_int test_scores; typedef bool docházka; typedef int *Pint;

    Názvy definované direktivou typedef lze použít stejným způsobem jako specifikátory typu:

    // double hourly, weekly; mzda hodinová, týdenní; // vektor vecl(10);
    vec_int vecl(10); // vektor test0(velikost_c1ass); const int c1ass_size = 34; test_scores test0(c1ass_size); // vektor< bool >účast; vektor< in_attendance >docházka(c1ass_size); // int *tabulka[ 10 ]; Půllitr [ 10 ];

    Tato direktiva začíná klíčovým slovem typedef následovaným specifikátorem typu a končí identifikátorem, který se stane aliasem pro zadaný typ.
    K čemu slouží názvy definované direktivou typedef? Použitím mnemotechnických názvů pro datové typy můžete usnadnit pochopení svého programu. Je také obvyklé používat taková jména pro složité, jinak těžko srozumitelné komplexní typy (viz příklad v části 3.14), k deklaraci ukazatelů na funkce a členské funkce třídy (viz část 13.6).
    Následuje příklad otázky, na kterou téměř každý odpoví špatně. Chyba je způsobena nesprávným pochopením direktivy typedef jako jednoduché textové substituce makra. Definice je dána:

    Typedef char *cstring;

    Jaký je typ proměnné cstr v následující deklaraci:

    extern const cstring cstr;

    Odpověď, která se zdá zřejmá, je:

    Const char *cstr

    To však není pravda. Specifikátor const odkazuje na cstr, takže správnou odpovědí je ukazatel const na char:

    Char *const cstr;

    3.13. těkavý specifikátor

    Objekt je deklarován jako nestálý (nestabilní, asynchronně proměnlivý), pokud lze jeho hodnotu změnit, aniž by si toho kompilátor všiml, jako je například proměnná aktualizována systémovými hodinami. Tento specifikátor říká kompilátoru, aby neoptimalizoval kód pro práci s daným objektem.
    Těkavý specifikátor se používá jako specifikátor const:

    Volatile int disp1ay_register; volatile Task *curr_task; volatile int ixa[ max_velikost ]; nestálá obrazovka bitmap_buf;

    display_register je nestabilní objekt typu int. curr_task je ukazatel na nestabilní objekt Task. ixa je nestabilní pole celých čísel a každý prvek takového pole je považován za nestabilní. bitmap_buf je nestálý objekt třídy Screen, každý jeho datový člen je také považován za nestálý.
    Jediným účelem použití nestálého specifikátoru je sdělit kompilátoru, že nemůže určit, kdo a jak může změnit hodnotu daného objektu. Proto by kompilátor neměl optimalizovat kód, který tento objekt používá.

    3.14. párová třída

    Třída pair standardní knihovny C++ nám umožňuje definovat pár hodnot s jedním objektem, pokud mezi nimi existuje nějaký sémantický vztah. Tyto hodnoty mohou být stejného nebo různého typu. Chcete-li použít tuto třídu, musíte zahrnout soubor záhlaví:

    #zahrnout

    Například návod

    Pár< string, string >autor("James", "Joyce");

    vytvoří autorský objekt typu pair, který se skládá ze dvou řetězcových hodnot.
    Jednotlivé části páru lze získat pomocí prvního a druhého členu:

    String firstBook; if (Joyce.first == "James" &&
    Joyce.second == "Joyce")
    první kniha = "Stephen Hero";

    Pokud potřebujete definovat několik objektů stejného typu této třídy, je vhodné použít direktivu typedef:

    Typedef pár< string, string >Autoři; Autoři proust("marcel", "proust"); Autoři joyce("James", "Joyce"); Autoři musil("robert", "musi1");

    Zde je další příklad použití páru. První hodnota obsahuje jméno nějakého objektu, druhá je ukazatel na prvek tabulky odpovídající tomuto objektu.

    Class EntrySlot; extern EntrySlot* 1ook_up(string); typedef pár< string, EntrySlot* >SymbolEntry; SymbolEntry current_entry("autor", 1ook_up("author"));
    // ... if (EntrySlot *it = 1ook_up("editor")) (
    current_entry.first = "editor";
    aktuální_vstup.sekunda = it;
    }

    (Vrátíme se k diskusi o párové třídě v kapitole 6 o typech kontejnerů a generických algoritmech v kapitole 12.)

    3.15. Typy tříd

    Mechanismus třídy umožňuje vytvářet nové datové typy; zavedlo výše uvedené typy řetězců, vektorů, komplexů a párů. V kapitole 2 jsme hovořili o konceptech a mechanismech, které podporují objektově a objektově orientovaný přístup, a jako příklad jsme použili implementaci třídy Array. Zde na základě objektového přístupu vytvoříme jednoduchou třídu String, jejíž implementace pomůže pochopit zejména přetěžování operátorů – o tom jsme hovořili v části 2.3. (Třídy jsou podrobně popsány v kapitolách 13, 14 a 15). Uvedli jsme stručný popis třídy, abychom uvedli zajímavější příklady. Čtenář, který se teprve začíná učit C++, může tuto část přeskočit a počkat na systematičtější popis tříd v dalších kapitolách.)
    Naše třída String musí podporovat inicializaci s objektem třídy String, řetězcovým literálem a vestavěným typem řetězce a také operaci přiřazování hodnot těchto typů. Používáme k tomu konstruktory tříd a přetížený operátor přiřazení. Přístup k jednotlivým znakům řetězce bude implementován jako přetížený operátor indexového odběru. Dále potřebujeme: funkci size() pro získání informace o délce řetězce; operace porovnávání objektů typu String a objektu String s řetězcem vestavěného typu; stejně jako I/O operace našeho objektu. Nakonec implementujeme možnost přístupu k interní reprezentaci našeho řetězce jako vestavěný typový řetězec.
    Definice třídy začíná klíčovým slovem class následovaným identifikátorem, názvem třídy nebo typu. Obecně se třída skládá z částí, kterým předcházejí slova public (otevřená) a private (uzavřená). Otevřená sekce obvykle obsahuje sadu operací podporovaných třídou, nazývaných metody nebo členské funkce třídy. Tyto členské funkce definují veřejné rozhraní třídy, jinými slovy, sadu akcí, které lze provádět s objekty této třídy. Soukromá sekce obvykle zahrnuje datové členy, které poskytují interní implementaci. V našem případě mezi interní členy patří _string - ukazatel na char, stejně jako _size typu int. _size bude ukládat informace o délce řetězce a _string je dynamicky alokované pole znaků. Takto vypadá definice třídy:

    #zahrnout třída String; istream& operátor>>(istream&, String&);
    stream a operátor<<(ostream&, const String&); class String {
    veřejnost:
    // sada konstruktorů
    // pro automatickou inicializaci
    // Stringstrl; // tětiva()
    // String str2("doslovný"); // String(const char*);
    // String str3(str2); // String(const String&); tětiva();
    string(const char*);
    String(const String&); // destruktor
    ~řetězec(); // operátory přiřazení
    // strl = str2
    // str3 = "řetězcový literál" String& operator=(const String&);
    String& operator=(const char*); // operátory rovnosti
    // strl == str2;
    // str3 == "řetězcový literál"; bool operator==(const String&);
    bool operator==(const char*); // přetížení přístupového operátora indexem
    // strl[ 0 ] = str2[ 0 ]; char& operator(int); // přístup ke členům třídy
    int size() ( return _size; )
    char* c_str() ( return _string; ) private:
    int_size;
    char*_string;
    }

    Třída String má tři konstruktory. Jak je uvedeno v části 2.3, mechanismus přetížení umožňuje definovat více implementací funkcí se stejným názvem, pokud se všechny liší v počtu a/nebo typech svých parametrů. První konstruktér

    Je to výchozí konstruktor, protože nevyžaduje explicitní počáteční hodnotu. Když píšeme:

    Pro str1 se takový konstruktor nazývá.
    Dva zbývající konstruktory mají každý jeden parametr. Ano, pro

    String str2("řetězec znaků");

    Zavolá se konstruktor

    string(const char*);

    řetězec str3(str2);

    Konstruktér

    String(const String&);

    Typ volaného konstruktoru je určen typem skutečného argumentu. Poslední konstruktor, String(const String&), se nazývá kopírovací konstruktor, protože inicializuje objekt s kopií jiného objektu.
    Pokud napíšete:

    stringstr4(1024);

    To způsobí chybu kompilace, protože neexistuje žádný konstruktor s parametrem typu int.
    Deklarace přetíženého operátora má následující formát:

    operátor návratového_typu op(seznam_parametrů);

    Kde operátor je klíčové slovo a op je jeden z předdefinovaných operátorů: +, =, == atd. (Přesnou definici syntaxe naleznete v kapitole 15.) Zde je deklarace přetíženého operátoru dolního indexu:

    Char& operator(int);

    Tento operátor má jeden parametr typu int a vrací odkaz na char. Přetížený operátor může být sám přetížen, pokud se seznamy parametrů jednotlivých instancí liší. Pro naši třídu String vytvoříme každý dva různé operátory přiřazení a rovnosti.
    Chcete-li volat členskou funkci, použijte operátory přístupu členů tečka (.) nebo šipka (->). Řekněme, že máme deklarace objektů typu String:

    String object("Danny");
    String *ptr = new String("Anna");
    Stringarray;
    Takto vypadá volání funkce size() pro tyto objekty:
    vektor velikosti(3);

    // přístup členů pro objekty (.); // objekty mají 5 velikostí[ 0 ] = object.size(); // přístup členů pro ukazatele (->)
    // ptr má velikost 4
    velikosti[ 1 ] = ptr->velikost(); // přístup členů (.)
    // pole má velikost 0
    size[ 2 ] = array.size();

    Vrací 5, 4 a 0 v tomto pořadí.
    Přetížené operátory jsou aplikovány na objekt stejným způsobem jako normální operátory:

    String name("Yadie"); Stringname2("Yodie"); // bool operátor==(const String&)
    if (jméno == jméno2)
    vrátit se;
    jiný
    // String& operator=(const String&)
    jmeno=jméno2;

    Deklarace členské funkce musí být uvnitř definice třídy a definice funkce může být uvnitř nebo vně definice třídy. (Velikost() i c_str() jsou definovány uvnitř třídy.) Pokud je funkce definována mimo třídu, musíme mimo jiné určit, do které třídy patří. V tomto případě je definice funkce umístěna do zdrojového souboru, řekněme String.C, a definice samotné třídy je umístěna do hlavičkového souboru (v našem příkladu String.h), který by měl být zahrnut ve zdrojovém souboru:

    // obsah zdrojového souboru: String.C // včetně definice třídy String
    #include "String.h" // zahrnout definici funkce strcmp().
    #zahrnout
    bool // návratový typ
    String:: // třída, do které funkce patří
    operator== // název funkce: operátor rovnosti
    (const String &rhs) // seznam parametrů
    {
    if (_velikost != rhs._size)
    vrátit false;
    return strcmp(_stringq, rhs._string) ?
    nepravda: pravda;
    }

    Připomeňme, že strcmp() je standardní funkce knihovny C. Porovnává dva vestavěné řetězce typu, přičemž vrací 0, pokud jsou řetězce stejné, a nenulové, pokud ne. Podmíněný operátor (?:) testuje hodnotu před otazníkem. Pokud je to pravda, vrátí se hodnota výrazu nalevo od dvojtečky, jinak je napravo od dvojtečky. V našem příkladu je hodnota výrazu nepravdivá, pokud strcmp() vrátila nenulovou hodnotu, a pravdivá, pokud je null. (Podmíněný operátor je uveden v části 4.7.)
    Porovnávací operace se používá poměrně často, funkce, která ji implementuje, se ukázala jako malá, proto je užitečné tuto funkci deklarovat jako zabudovanou (inline). Kompilátor místo volání nahradí text funkce, takže na takové volání nestráví žádný čas. (Vestavěné funkce jsou popsány v části 7.6.) Členská funkce definovaná v rámci třídy je ve výchozím nastavení vestavěná. Pokud je definováno mimo třídu, k jeho deklaraci jako inline musíte použít klíčové slovo inline:

    Inline bool String::operator==(const String &rhs) ( // ditto )

    Definice vložené funkce musí být v hlavičkovém souboru obsahujícím definici třídy. Předefinováním operátoru == jako vestavěného operátoru musíme přesunout samotný text funkce ze souboru String.C do souboru String.h.
    Následuje implementace operace porovnávání objektu String s vestavěným typem řetězce:

    Inline bool String::operator==(const char *s) ( return strcmp(_string, s) ? false: true; )

    Název konstruktoru je stejný jako název třídy. Nepovažuje se za návratovou hodnotu, takže návratovou hodnotu nemusíte zadávat ani v její definici, ani v jejím těle. Konstruktorů může být více. Jako každá jiná funkce mohou být deklarovány jako inline.

    #zahrnout // výchozí vložený konstruktor String::String()
    {
    _size=0;
    _string=0;
    ) inline String::String(const char *str) ( if (! str) ( _size = 0; _string = 0; ) else ( _size = str1en(str); strcpy(_string, str); ) // kopírovat konstruktor
    inline String::String(const String &rhs)
    {
    velikost = rhs._size;
    if(!rhs._string)
    _string=0;
    jiný(
    _string = nový znak[_velikost + 1];
    } }

    Vzhledem k tomu, že jsme dynamicky alokovali paměť pomocí operátoru new, musíme ji uvolnit voláním smazání, když již nepotřebujeme objekt String. K tomuto účelu slouží další speciální členská funkce, destruktor, který je automaticky volán pro objekt, když tento objekt přestane existovat. (Viz kapitola 7 o životnosti objektu.) Název destruktoru je tvořen znakem vlnovky (~) a názvem třídy. Zde je definice destruktoru třídy String. Zde voláme operaci odstranění, abychom uvolnili paměť alokovanou v konstruktoru:

    Vložený řetězec: :~String() ( smazat _string; )

    Oba přetížené operátory přiřazení používají speciální klíčové slovo this.
    Když píšeme:

    String name("orville"), name2("wilbur");
    name = "Orville Wright";
    toto je ukazatel adresující objekt name1 v těle funkce operace přiřazení.
    to vždy ukazuje na objekt třídy, přes který je funkce volána. Li
    ptr->velikost();
    obj[1024];

    Pak uvnitř size() bude jeho hodnota adresa uložená v ptr. Uvnitř operace get index obsahuje adresu obj. Dereferencováním tohoto (pomocí *this) získáme samotný objekt. (Tento ukazatel je podrobně popsán v části 13.4.)

    Inline String& String::operator=(const char *s) ( if (! s) ( _size = 0; delete _string; _string = 0; ) else ( _size = str1en(s); delete _string; _string = new char[ _size + 1 ]; strcpy(_string, s); ) return *toto; )

    Při implementaci operace přiřazení dochází poměrně často k jedné chybě: zapomínají zkontrolovat, zda kopírovaný objekt není stejný, do kterého se kopíruje. Tuto kontrolu provedeme pomocí stejného ukazatele:

    Inline String& String::operator=(const String &rhs) ( // ve výrazu // name = *pointer_to_string // toto je jméno1, // rhs - *pointer_to_string. if (toto != &rhs) (

    Zde je úplný text operace pro přiřazení objektu stejného typu k objektu String:

    Inline String& String::operator=(const String &rhs) ( if (toto != &rhs) ( smazat _string; _size = rhs._size; if (! rhs._string)
    _string=0;
    jiný(
    _string = nový znak[_velikost + 1];
    strcpy(_string, rhs._string);
    }
    }
    vrátit *toto;
    }

    Operace dolního indexu je téměř stejná jako implementace pro pole, které jsme vytvořili v sekci 2.3:

    #zahrnout vložený znak&
    String::operator (prvek int)
    {
    tvrdit(elem >= 0 && el< _size);
    return _string[elem];
    }

    Vstupní a výstupní operátory jsou implementovány jako samostatné funkce, nikoli členové třídy. (Důvody probereme v části 15.2. Části 20.4 a 20.5 pojednávají o přetížení vstupních a výstupních operátorů knihovny iostream.) Náš vstupní operátor nemůže číst více než 4095 znaků. setw() je předdefinovaný manipulátor, který čte daný počet znaků mínus 1 ze vstupního toku, čímž zajišťuje, že nepřetečeme náš interní inBuf buffer. (Kapitola 20 podrobně pojednává o manipulátoru setw().) Chcete-li použít manipulátory, musíte zahrnout příslušný soubor záhlaví:

    #zahrnout inline istream& operator>>(istream &io, String &s) ( // umělý limit: 4096 znaků const int 1imit_string_size = 4096; char inBuf[ limit_string_size ]; // setw() je součástí knihovny iostream // omezuje blok čtení size to 1imit_string_size -l io >> setw(1imit_string_size) >> inBuf; s = mBuf; // String::operator=(const char*); return io; )

    Výstupní příkaz potřebuje přístup k interní reprezentaci řetězce. Od operátora<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при реализации операции вывода:

    Inline ostream & operátor<<(ostream& os, const String &s) { return os << s.c_str(); }

    Následuje příklad programu, který používá třídu String. Tento program bere slova ze vstupního proudu a počítá jejich celkový počet, stejně jako počet slov „the“ a „it“ a registruje samohlásky, se kterými se setká.

    #zahrnout #include "String.h" int main() ( int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, theCnt = 0, itCnt = 0, wdCnt = 0, notVowel = 0; / / Slova "The" a "It"
    // zkontrolujeme operátorem==(const char*)
    String but, the("the"), it("it"); // operátor>>(ostream&, String&)
    while (cin >> buf) (
    ++wdCnt; // operátor<<(ostream&, const String&)
    cout<< buf << " "; if (wdCnt % 12 == 0)
    cout<< endl; // String::operator==(const String&) and
    // String::operator==(const char*);
    if (buf == the | | buf == "The")
    ++theCnt;
    jiný
    if (buf == to || buf == "To")
    ++itCnt; // vyvolá String::s-ize()
    for (int ix = 0; ix< buf.sizeO; ++ix)
    {
    // vyvolá řetězec:: operátor(int)
    switch(buf[ix])
    {
    případ "a": případ "A": ++aCnt; přestávka;
    případ "e": případ "E": ++eCnt; přestávka;
    případ "i": případ "I": ++iCnt; přestávka;
    případ "o": případ "0": ++oCnt; přestávka;
    případ "u": případ "U": ++uCnt; přestávka;
    výchozí: ++notVowe1; přestávka;
    }
    }
    ) // operátor<<(ostream&, const String&)
    cout<< "\n\n"
    << "Слов: " << wdCnt << "\n\n"
    << "the/The: " << theCnt << "\n"
    << "it/It: " << itCnt << "\n\n"
    << "согласных: " < << "a: " << aCnt << "\n"
    << "e: " << eCnt << "\n"
    << "i: " << ICnt << "\n"
    << "o: " << oCnt << "\n"
    << "u: " << uCnt << endl;
    }

    Pojďme si program vyzkoušet: nabídneme mu odstavec z dětského příběhu, který napsal jeden z autorů této knihy (s tímto příběhem se setkáme v 6. kapitole). Zde je výstup z programu:

    Alice Emma má dlouhé rozpuštěné zrzavé vlasy. Její tatínek říká, že když jí vítr fouká do vlasů, vypadají skoro jako živé, jako ohnivý pták v letu. Krásný ohnivý pták, říká jí, kouzelný, ale nezkrotný. "Tati, šup, nic takového neexistuje," říká mu a zároveň chce, aby jí řekl víc. Plaše se zeptá: "Myslím, tati, je tam?" Slova: 65
    the/The: 2
    to/to: 1
    souhlásky: 190
    a:22
    e:30
    já:24
    asi: 10
    ty:7

    Cvičení 3.26

    V našich implementacích konstruktorů a operátorů přiřazení se hodně opakuje. Zvažte přesunutí opakujícího se kódu do samostatné soukromé členské funkce, jak jsme to udělali v části 2.3. Ujistěte se, že nová možnost funguje.

    Cvičení 3.27

    Upravte testovací program tak, aby počítal i souhlásky b, d, f, s, t.

    Cvičení 3.28

    Napište členskou funkci, která počítá počet výskytů znaku v řetězci pomocí následující deklarace:

    Class String ( public: // ... int count(char ch) const; // ... );

    Cvičení 3.29

    Implementujte operátor zřetězení řetězců (+) tak, aby zřetězil dva řetězce a vrátil výsledek v novém objektu String. Zde je deklarace funkce:

    Class String ( public: // ... Operátor řetězce+(const String &rhs) const; // ... );

    Datový typ v programování je kolekce dvou sad: sada hodnot a sada operací, které na ně lze použít. Například nezáporný celočíselný datový typ, který se skládá z konečné množiny přirozených čísel, lze použít na operace sčítání (+), násobení (*), dělení celého čísla (/), zbytek (%) a odčítání (-).

    Programovací jazyk má obvykle sadu primitivních datových typů – typů poskytovaných programovacím jazykem jako základní vestavěná jednotka. V C++ nazývá tvůrce jazyka takové typy základními typy. Základní typy v C++ jsou:

    • boolean (bool);
    • znak (např. char);
    • celé číslo (např. int);
    • plovoucí desetinná čárka (např. plovoucí);
    • výčty (definované programátorem);
    • prázdnota

    Kromě výše uvedeného jsou vytvořeny následující typy:

    • ukazatele (např. int*);
    • pole (např. char);
    • odkaz (např. double&);
    • jiné struktury.

    Přejděme ke konceptu literálu (např. 1, 2.4F, 25e-4, ‚a‘ atd.): literál je záznam ve zdrojovém kódu programu, který představuje pevnou hodnotu. Jinými slovy, literál je jednoduše reprezentace objektu (hodnoty) nějakého typu v kódu programu. C++ má schopnost zapisovat celočíselné hodnoty, hodnoty s pohyblivou řádovou čárkou, znakové, booleovské, řetězcové hodnoty.

    Literál typu celé číslo lze zapsat jako:

    • 10. číselná soustava. Například 1205;
    • 8. číselná soustava ve formátu 0 + číslo. Například 0142;
    • 16. číselná soustava ve formátu 0x + číslo. Například 0x2F .

    24, 030, 0x18 jsou všechny položky stejného čísla v různých číselných soustavách.
    Pro zápis čísel s plovoucí desetinnou čárkou se používá tečkový zápis: 0,1, 0,5, 4. - buď v
    exponenciální zápis - 25e-100. V takovém záznamu by neměla být žádná mezera.

    Název, se kterým můžeme spojit hodnoty zapsané literály, se nazývá proměnná. Proměnná je pojmenované nebo jinak adresovatelné místo v paměti, jehož adresu lze použít pro přístup k datům. Tato data jsou během provádění programu určitým způsobem zapsána, přepsána a vymazána v paměti. Proměnná umožňuje kdykoli přistupovat k datům a v případě potřeby je měnit. Data, která lze načíst z názvu proměnné, se nazývají hodnota proměnné.
    Aby bylo možné v programu použít proměnnou, musí být deklarována a v případě potřeby může být definována (= inicializována). Deklarace proměnné v textu programu nutně obsahuje 2 části: základní typ a deklarátor. Specifikátor a inicializátor jsou volitelné části:

    const int příklad = 3; // zde const - specifikátor // int - základní typ // příklad - název proměnné // = 3 - inicializátor.

    Název proměnné je posloupnost znaků z písmen latinské abecedy (malá a velká), čísel a/nebo podtržítek, ale první znak nemůže být číslice. Název proměnné by měl být zvolen tak, aby bylo vždy snadné uhodnout, co ukládá, například „monthPayment“. Abstraktně i v praxi budeme pro pravidla pro zápis proměnných používat notaci CamelCase. Název proměnné se nemůže shodovat se slovy vyhrazenými v jazyce, příklady takových slov: if, while, function, goto, switch atd.

    Deklarátor může kromě názvu proměnné obsahovat další znaky:

    • * - ukazatel; před jménem
    • *const - konstantní ukazatel; před jménem
    • & - odkaz; před jménem
    • - pole; za jménem;
    • () - funkce; za jménem.

    Inicializátor umožňuje definovat hodnotu proměnné bezprostředně po deklaraci. Inicializátor začíná literálem rovná se (=) a poté probíhá proces nastavení hodnoty proměnné. Obecně řečeno, rovnítko v C++ označuje operaci přiřazení; lze jej použít k nastavení a změně hodnoty proměnné. U různých typů se může lišit.

    Specifikátor určuje další atributy jiné než typ. Specifikátor const uvedený v příkladu umožňuje zakázat následné změny hodnoty proměnné. Takové neměnné proměnné se nazývají konstantní nebo konstantní.

    Deklarování konstanty bez inicializace nebude fungovat z logických důvodů:

    Const int EMPTY_CONST; // chyba, konstantní proměnná není inicializována const int PŘÍKLAD = 2; // konstanta s hodnotou 2 EXAMPLE = 3; // chyba, pokuste se přiřadit hodnotu konstantní proměnné

    Pro pojmenování konstant je zvykem používat pouze velká písmena, slova oddělující znakem podtržítka.

    Základní datové typy v C++

    Při rozboru každého typu musí čtenář pamatovat na definování datového typu.

    1. Typ celého čísla (char, short(int), int, long(int), long long)

    Z názvu je snadné pochopit, že množina hodnot se skládá z celých čísel. Také sada hodnot každého z uvedených typů může být podepsána (podepsána) nebo nepodepsána (nepodepsána). Počet prvků obsažených v sadě závisí na velikosti paměti, která se používá k uložení hodnoty daného typu. Například pro proměnnou typu char je přidělen 1 bajt paměti, takže celkový počet prvků bude:

    • 2 8N = 2 8 * 1 = 256, kde N je množství paměti v bajtech pro uložení hodnoty

    V takovém případě jsou rozsahy dostupných celých čísel následující:

    • - pro nepodepsané char
    • [-128..127] - pro podepsané char

    Ve výchozím nastavení je proměnná celočíselného typu považována za podepsanou. Aby bylo v kódu uvedeno, že proměnná má být bez znaménka, je základnímu typu vlevo přiřazen znaménko se znaménkem, tzn. nepodepsaný:

    dlouhé hodnoty bez znaménka; // definuje celočíselný (dlouhý) typ bez znaménka.

    Uvedené typy se liší pouze velikostí paměti potřebné pro uložení. Protože jazyk C++ je spíše strojový jazykový standard, zaručuje pouze následující podmínku:

    • 1 = velikost znaku ≤ krátká velikost ≤ velikost int ≤ dlouhá velikost.

    Obvykle jsou velikosti typů následující: char - 1, krátký - 2, int - 4, dlouhý - 8, dlouhý dlouhý - 8 bajtů.

    Můžete provádět aritmetické operace s hodnotami typu celé číslo: +, -, *, /, %; srovnávací operace: ==, !=,<=, <, >, >=; bitové operace: &, |, xor,<<, >>.
    Většina operací, jako je sčítání, násobení, odčítání a porovnávání, je snadno pochopitelná. Někdy po provedení aritmetických operací může být výsledek mimo rozsah hodnot; v tomto případě program ohlásí chybu.
    Celočíselné dělení (/) najde celočíselnou část dělení jednoho celého čísla druhým. Například:

    • 6 / 4 = 1;
    • 2 / 5 = 0;
    • 8 / 2 = 4.

    Symbol procenta (%) označuje operaci určení zbytku dělení dvou celých čísel:

    • 6 % 4 = 2;
    • 10 % 3 = 1.

    Obtížněji pochopitelné operace jsou bitově: & (AND), | (OR), xor (exkluzivní OR),<< (побитовый сдвиг влево), >> (bitový posun doprava).

    Bitové operace AND, OR a XOR aplikují odpovídající logickou operaci na každý bit informace:

    • 1 10 = 01 2
    • 3 10 = 11 2
    • 1 10 & 3 10 = 01 2 & 11 2 = 01 2
    • 1 10 | 3 10 = 01 2 | 11 2 = 11 2
    • 1 10 x nebo 3 10 = 01 2 x nebo 11 2 = 10 2

    Při zpracování obrazu se pro barvu používají 3 kanály: červený, modrý a zelený - plus průhlednost, které jsou uloženy v proměnné typu int, protože každý kanál má rozsah hodnot od 0 do 255. V hexadecimální soustavě je hodnota zapsána následovně: 0x180013FF; pak hodnota 18 16 odpovídá červenému kanálu, 00 16 - modrý, 13 16 - zelený, FF - alfa kanál (průhlednost). Pro výběr určitého kanálu z takového celého čísla použijte tzv. maska, kde pozice, které nás zajímají, jsou F 16 nebo 1 2 . To znamená, že pro zvýraznění hodnoty modrého kanálu musíte použít masku, tzn. bitově A:

    int blue_channel = 0x180013FF & 0x00FF0000;

    Poté se přijatá hodnota posune doprava o požadovaný počet bitů.

    Bitový posun posune doleva nebo doprava tolik bitů čísla, kolik je uvedeno na pravé straně operace. Například číslo 39 pro typ char v binárním tvaru je zapsáno takto: 00100111. Potom:

    Binární znak Příklad = 39; // 00100111 výsledek znaku = binaryExample<< 2; // сдвигаем 2 бита влево, результат: 10011100

    Pokud je proměnná typu unsigned, pak výsledkem bude číslo 156, pro proměnnou se znaménkem je rovno -100. Všimněte si, že pro typy celých čísel se znaménkem je jednotka v horním řádu bitové reprezentace znamením, že číslo je záporné. V tomto případě hodnota v binárním tvaru sestávající ze všech jedniček odpovídá -1; pokud je 1 pouze na nejvýznamnější číslici a zbývající číslice jsou nuly, pak má takové číslo minimální hodnotu pro určitý typ hodnoty: pro char je to -128.

    2. Typ s pohyblivou řádovou čárkou (float, double (float))

    Sada hodnot s pohyblivou řádovou čárkou je podmnožinou reálná čísla, ale ne každé reálné číslo může být reprezentováno binárně, což někdy vede k hloupým chybám:

    plovoucí hodnota = 0,2; hodnota == 0,2; // chyba, hodnota zde nebude rovna 0,2.

    Při práci s proměnnými s plovoucí desetinnou čárkou by programátor neměl používat operaci rovnosti nebo nerovnosti, místo toho obvykle používá test k dosažení určitého intervalu:

    Hodnota - 0,2< 1e-6; // ok, подбирать интервал тоже нужно осторожно

    Kromě operací porovnávání podporuje typ s plovoucí desetinnou čárkou 4 aritmetické operace, které jsou plně v souladu s matematickými operacemi s reálnými čísly.

    3. Booleovský (logický) typ (bool)

    Skládá se pouze ze dvou hodnot: true (true) a false (false). Pro práci s proměnnými tohoto typu se používají logické operace: ! (NE), == (rovnost), != (nerovnost), && (logické AND), || (logické NEBO). Výsledek každé operace lze nalézt v odpovídající pravdivostní tabulce. Například:

    X Y XOR0 0 0 0 1 1 1 0 1 1 1 0

    4. Typ znaku (char, wchar_t)

    Typ char není pouze celočíselný typ (obvykle se takový typ nazývá byte), ale také typ znaku, který ukládá číslo znaku z tabulky jako znak ASCII. Například kód 0x41 odpovídá znaku 'A' a 0x71 - 't'.

    Někdy je nutné použít znaky, které nejsou v tabulce ASCII pevně dané, a proto vyžadují uložení více než 1 bajt. Existuje pro ně široký znak (wchar_t).

    5.1. Pole

    Pole umožňují uložit sekvenční sadu prvků stejného typu. Pole je uloženo v paměti v souvislém bloku, takže pole nelze deklarovat bez určení jeho velikosti. Chcete-li pole deklarovat, za název proměnné napište hranaté závorky () označující její velikost. Například:

    int myArray; // Pole 5 prvků typu integer

    Pro inicializaci pole jsou hodnoty uvedeny ve složených závorkách. Tímto způsobem můžete inicializovat pouze v době deklarace proměnné. Mimochodem, v tomto případě není nutné specifikovat velikost pole:

    int odds = (1, 3, 7, 9, 11); // Pole je inicializováno 5 hodnotami

    Chcete-li získat přístup ke konkrétní hodnotě v poli (prvku pole), použijte operaci přístupu k indexu () s číslem prvku (čísla začínají od 0). Například:

    šance; // přístup k prvnímu prvku pole. Vrátí hodnotu 1 odds; // přístup ke třetímu prvku. Vrátí hodnotu 7 odds = 13; // Přiřadí novou hodnotu 5. prvku pole odds; // chyba přístupu

    5.3. Struny

    K zápisu řetězce programátoři používají myšlenku, že řetězec je po sobě jdoucí řada (pole) znaků. K identifikaci konce řádku použijte zvláštní charakter konec řádku: '\0'. Tyto speciální znaky sestávající ze zpětného lomítka a identifikačního znaku se nazývají řídicí nebo únikové znaky. Stále existuje například '\n' - začátek nového řádku, '\t' - tabelování. Pro zaznamenání zpětného lomítka na řádek se používá escapování - před samotný znak se vloží další lomítko: '\'. Escapování se také používá k psaní uvozovek.

    Vytvořme řetězcovou proměnnou:

    Char textExample = ('T', 'e', ​​​​'s', 't', '\0'); // je zapsán řetězec "Test".

    Pro inicializaci řetězce existuje zjednodušený zápis:

    Znak textExample = "Test"; // Poslední znak není zapsán, ale velikost je stále 5

    Aniž bychom zacházeli do podrobností, zde je další užitečný datový typ - řetězec. Struny
    tohoto typu můžete například přidat:

    Řetězec hello = "Ahoj,"; stringname = "Max!"; řetězec ahoj_jméno = ahoj + jméno; // Získejte řetězec "Ahoj, Max!"

    6. Odkaz

    Int a = 2; // proměnná "a" ukazuje na hodnotu 2 int &b = a; // proměnná "b" ukazuje na stejné místo jako "a" b = 4; // změnou hodnoty b změní programátor hodnotu a. Nyní a = 4 int &c = 4; // chyba, nemůžete to udělat, protože referenci nelze přiřadit hodnotu

    7. Ukazatel

    Pro práci s tímto typem dat je nutné si uvědomit, že množinou hodnot tohoto typu jsou adresy paměťových buněk, odkud data začínají. Ukazatel také podporuje operace sčítání (+), odčítání (-) a dereferencování (*).

    Adresy 0x0 znamená, že ukazatel je prázdný, tzn. neukazuje na žádná data. Tato adresa má svůj vlastní literál - NULL:

    Int *nullPtr = NULL; // nulový ukazatel

    Umožňuje sčítání a odečítání adresy s celým číslem nebo jinou adresou
    pohybovat po paměti dostupné programu.

    Operace získávání dat začínajících na adrese uložené v ukazateli se nazývá dereferencování (*). Program načte požadovaný počet paměťových buněk a vrátí hodnotu uloženou v paměti.

    Int hodnotaInMemory = 2; // nastaví proměnnou celočíselného typu int *somePtr = // zkopíruje adresu proměnné, zde & - vrátí adresu proměnné somePtr; // adresa paměťové buňky, například 0x2F *somePtr; // hodnota je uložena ve 4 buňkách: 0x2F, 0x30, 0x31 a 0x32

    U ukazatelů není k dispozici operace přiřazení, která je syntakticky stejná jako operace kopírování. Jinými slovy, můžete zkopírovat adresu jiného ukazatele nebo adresu proměnné, ale nemůžete sami určit hodnotu adresy.

    Samotný ukazatel je uložen v paměti, stejně jako hodnoty proměnných jiných typů, a zabírá 4 bajty, takže můžete vytvořit ukazatel na ukazatel.

    8. Převody

    Výčty jsou jediným základním typem definovaným programátorem. Celkově je výčet uspořádaná sada pojmenovaných celočíselných konstant, přičemž název výčtu je základní typ.

    Enumcolor (ČERVENÁ, MODRÁ, ZELENÁ);

    Standardně ČERVENÁ = 0, MODRÁ = 1, ZELENÁ = 2. Hodnoty lze tedy vzájemně porovnávat, tzn. ČERVENÉ< BLUE < GREEN. Программист при объявлении перечисления может самостоятельно задать значения каждой из констант:

    Enum access(READ=1, WRITE=2, EXEC=4);

    Často je vhodné používat výčty, jejichž hodnoty jsou mocninou dvou, protože v binární reprezentaci se číslo, které je mocninou 2, bude skládat z 1. jednotky a nul. Například:

    8 10 = 00001000 2

    Výsledek sčítání těchto čísel vždy jasně ukazuje, která čísla byla přidána:

    37 10 = 00100101 2 = 00000001 2 + 00000100 2 + 00100000 2 = 1 10 + 4 10 + 32 10

    Neplatné

    Syntakticky je typ void jedním ze základních typů, ale lze jej použít pouze jako součást složitějších typů, protože objekty typu void neexistují. Obvykle se tento typ používá k informování, že funkce nemá návratovou hodnotu, nebo jako základní typ ukazatele na objekty nedefinovaných typů:

    prázdný objekt; // chyba, nejsou zde žádné objekty typu void void // chyba, nejsou zde žádné odkazy na void void *ptr; // ok, ulož ukazatel na neznámý typ

    Často budeme používat void specificky k označení, že funkce nevrací žádnou hodnotu. Ukazatel typu void se zpracuje, když programátor převezme plnou odpovědnost za integritu paměti a správné přetypování.

    Obsazení

    Často je nutné přetypovat hodnotu proměnné jednoho typu na jiný. V případě, kdy je množina hodnot původního typu podmnožinou většího typu (například int je podmnožinou long a long je double), může kompilátor implicitně ( implicitně) změnit typ hodnoty.

    int celé číslo = 2; plovoucí plovoucí = celé číslo; // plovoucí = 2,0

    Typová konverze bude provedena se ztrátou informace, takže z čísla s plovoucí desetinnou čárkou zůstane pouze celočíselná část, zlomková část se ztratí.

    Existuje možnost explicitní (explicitní) konverze typu, za tímto účelem vlevo od proměnné nebo nějaké hodnoty původního typu v závorce napište typ, na který bude přetypováno:

    hodnota int = (int) 2,5;

    Unární a binární operace

    Operace, které jsme provedli dříve, se nazývají binární: vlevo a vpravo od symbolu operace jsou hodnoty nebo proměnné, například 2 + 3. Kromě binárních operací používají programovací jazyky také unární operace které se týkají proměnných. Mohou být buď nalevo, nebo napravo od proměnné, několik takových operací se již setkalo - operace dereference (*) a převzetí adresy proměnné (&) jsou unární. Operátory "++" a "-" zvyšují a snižují hodnotu celočíselné proměnné o 1 a mohou být zapsány vlevo nebo vpravo od proměnné.

    C++ také používá zkrácený zápis pro binární operace, když levá a pravá strana výrazu obsahují stejnou proměnnou, tzn. nějaká operace se provede s hodnotou proměnné a výsledek operace se uloží do stejné proměnné:

    A+= 2; // stejné jako a = a + 2; b/= 5; // stejné jako b = b / 5; c &= 3; // stejné jako c = c & 3;

    Datové typy v C jsou třídou dat, jejichž hodnoty mají podobné vlastnosti. Typ definuje vnitřní reprezentaci dat v paměti. Nejzákladnější datové typy jsou boolean, integer, floating point, string, pointer.

    Při dynamickém psaní je proměnná přidružena k typu v době inicializace. Ukazuje se, že proměnná v různých částech kódu může mít různé typy. Dynamické psaní podporují Java Script, Python, Ruby, PHP.

    Statické psaní je opakem dynamického psaní. Když je proměnná deklarována, obdrží typ, který se v budoucnu nezmění. Jazyky C a C++ jsou přesně takové. Tato metoda je nejvhodnější pro psaní složitého kódu a mnoho chyb je eliminováno ve fázi kompilace.

    Jazyky se neformálně dělí na silně a slabě typizované. Silné psaní znamená, že kompilátor vyvolá chybu, pokud se očekávané a skutečné typy neshodují.

    x = 1 + "2"; //chyba - k číslu nelze přidat znak

    Příklad slabého psaní.

    Kontrolu typové konzistence provádí systém typové bezpečnosti. K chybě při psaní dochází například při pokusu o použití čísla jako funkce. Existují jazyky bez typu. Na rozdíl od psaných vám umožňují provádět libovolnou operaci s každým objektem.

    Třídy paměti

    Proměnné, bez ohledu na svůj typ, mají svůj rozsah a životnost.

    Třídy paměti:

    • auto;
    • statický;
    • externí;
    • Registrovat.

    Všechny proměnné v C jsou ve výchozím nastavení lokální. Lze je použít pouze uvnitř funkce nebo bloku. Když funkce skončí, jejich hodnota je zničena.

    Statická proměnná je také lokální, ale mimo svůj blok může mít jinou hodnotu a mezi voláními funkcí je zachována.

    Externí proměnná je globální. Je k dispozici v jakékoli části kódu a dokonce i v jiném souboru.

    Specifikátory datových typů v C mohou být vynechány v následujících případech:

    1. Všechny proměnné uvnitř bloku nejsou proměnnými; pokud se tedy předpokládá použití této konkrétní třídy úložiště, pak není specifikátor auto specifikován.
    2. Všechny funkce deklarované mimo blok nebo funkci jsou ve výchozím nastavení globální, takže externí specifikátor je volitelný.

    Chcete-li zadat jednoduché typy, jsou určeny specifikátory int, char, float nebo double. Proměnné lze nahradit modifikátory unsigned (unsigned), signed (signed), short, long, long long.

    Ve výchozím nastavení jsou všechna čísla podepsána, takže mohou být pouze v rozsahu kladných čísel. Chcete-li definovat proměnnou typu char jako podepsanou, napište podepsaný char. Dlouhé, dlouhé dlouhé a krátké označují, kolik paměti je přiděleno pro úložiště. Největší je dlouhý dlouhý, nejmenší je krátký.

    Char je nejmenší datový typ v C. Pro ukládání hodnot je přidělen pouze 1 bajt paměti. Proměnné typu charakter jsou obvykle přiřazeny znaky, méně často číslice. Hodnoty znaků jsou uzavřeny v uvozovkách.

    Typ int ukládá celá čísla, jeho velikost není definována – zabírá až 4 bajty paměti v závislosti na architektuře počítače.

    Explicitní převod proměnné bez znaménka je dán takto:

    Implicitní vypadá takto:

    Float a double definují čísla s tečkou. Floaty jsou reprezentovány jako -2,3 nebo 3,34. Double se používá pro větší přesnost - za oddělovačem desetinných míst je uvedeno více číslic. Tento typ zabírá více místa v paměti než float.

    Void má prázdnou hodnotu. Definuje funkce, které nic nevrací. Tento specifikátor určuje prázdnou hodnotu v argumentech metody. Ukazatele, které mohou mít jakýkoli datový typ, jsou také definovány jako neplatné.

    Booleovský typ

    Používá se při stavových testech a smyčkách. Má pouze dva významy:

    • skutečný;
    • lhát.

    Booleovské hodnoty lze převést na hodnotu int. True se rovná jedné, nepravda se rovná nule. Konverze typu je poskytována pouze mezi bool a int, jinak kompilátor vygeneruje chybu.

    if (x) ( //Chyba: "Nelze implicitně převést typ 'int' na 'bool""

    if (x != 0) // Způsob C#

    Řetězce a pole

    Pole jsou komplexní datové typy v C. JS nepracuje s řetězci stejným způsobem jako Javascript nebo Ruby. V C jsou všechny řetězce pole prvků znakové hodnoty. Řetězce končí nulovým bajtem “

    Datový typ je popis rozsahu hodnot, které může proměnná zadaného typu nabývat. Každý datový typ se vyznačuje:
    1. počet obsazených bajtů (velikost)
    2. rozsah hodnot, které může proměnná tohoto typu nabývat.

    Všechny datové typy lze rozdělit do následujících typů:
    1. jednoduché (skalární) a komplexní (vektorové) typy;
    2. základní (systémové) a uživatelské (definované uživatelem).
    V jazyce C se základní typový systém skládá ze čtyř datových typů:
    1. symbolický,
    2. celé číslo,
    3. skutečná jednoduchá přesnost,
    4. skutečná dvojitá přesnost.

    Podrobný popis datových typů v jazyce C

    typ Typ postavy celočíselný typ Jediný přesný skutečný typ Skutečný typ s dvojitou přesností
    popis char int plovák dvojnásobek
    velikost 1 bajt (8 bitů) 4 bajty (32 bitů) 4 bajty (32 bitů)
    23 bitů - mantisa;
    8 bitů - pořadí;
    1 bit - znaménko.
    8 bajtů (64 bitů)
    52 bitů - mantisa;
    11 bitů - pořadí;
    1 bit - znaménko.
    rozsah hodnot -128 ... 127 2147483648 ... 2147483647 ±3,4E±38
    Přesnost až na 7 desetinných míst
    ±1,7E±308
    Přesnost až na 17 desetinných míst

    Jazyk C poskytuje dva typy modifikátorů datových typů:
    1. modifikátory znaménka: podepsané a nepodepsané.
    2. modifikátory velikosti: krátké a dlouhé.
    Modifikátory typu jsou podrobněji popsány v tabulce:

    Komplexní čísla v SI

    Komplexní čísla jsou zavedena ve standardu C99.
    float _Complex
    dvojitý _Komplex
    dlouhý dvojitý _Komplex
    Všechno to štěstí je v knihovně komplexní.h :)

    Minimální a maximální hodnoty všech základních datových typů jazyka SI jsou popsány v knihovnách: limity.h - obsahuje rozsahy celočíselných hodnot, float.h - obsahuje rozsahy reálných hodnot.

    Booleovský datový typ v SI

    Standard C89:

    booleovský typ - int
    0 - nepravda (nepravda);
    ne 0 - pravda (pravda). To znamená, že jako takový se nevytvoří logický typ, ale místo toho se použije int.
    Standard C99:
    Booleovský typ - _Bool
    Klíčová slova: bool true false
    A to je štěstí v knihovně stdbool.h

    Prohlášení Operátoři

    Proměnná je pojmenovaná paměťová oblast počítače určená k ukládání hodnot určitého typu s libovolnou metodou přístupu: čtení a zápis. Název proměnné je platný identifikátor jazyka C, který se dříve nepoužíval k odkazování na jiné proměnné, typy, členy výčtu nebo názvy funkcí. Operátor deklarace proměnné má následující syntaxi: type name1[,name2[,...]]; Příklady:
    int a, b, c;
    dvojité x, y;
    charch;
    Existují některá nevyřčená pravidla, tj. provedení, které je v dobré formě, ale není to nutné:
    1. každá deklarace proměnných nového typu začíná na novém řádku;
    2. z názvu proměnné by mělo být jasné, proč to je a co se v ní bude ukládat (i když někdy kvůli takovým informativním názvům rychlost psaní kódu klesá, protože někteří se nechají unést a proměnné volají celé věty);
    3. platí tedy pravidlo: název proměnné by neměl být příliš dlouhý;
    4. po deklaraci proměnné je velmi žádoucí v komentářích označit, proč tomu tak je;
    5. je nutné oddělit názvy proměnných mezerami.
    Operátor deklarace proměnné s inicializací má následující syntaxi: typ jméno1[=hodnota1][, název2[=hodnota2][,...]]; Příklady:
    int a=26, b=032, c=0xlA;
    double x=2,5e2,y=0x1.ffe-3;
    charch='Z';

    SI konstanty

    V jazyce C existují tři typy konstant:
    1. celé číslo,
    2. skutečný,
    3. symbolický.
    Celočíselné konstanty
    1. Desetinná konstanta je označena desetinným číslem v obvyklém tvaru.
    2. Osmičková konstanta je označena číslem začínajícím číslicí nula a obsahujícím číslice 0...7.
    3. Hexadecimální konstanta je označena celým číslem s předponou 0x nebo 0X, obsahujícím čísla 0...9 a písmena latinské abecedy a...f, A...F.
    Reálné konstanty se zapisují v desítkové nebo šestnáctkové soustavě. Pozice čárky je označena tečkou, exponent je uveden za latinským písmenem e (nebo E). Znakovým konstantám předchází znak \, jedná se o tzv. "escapování" . V jazyce C jsou speciální znaky:
    „\“ – jednoduchá citace,
    „\““ – dvojitá uvozovka,
    ‚\\‘ – zpětné lomítko,
    '\?' - otazník,
    „\a“ – zvukový signál,
    „\b“ – smazání znaku,
    „\f“ – rolování stránky,
    „\n“ – odřádkování,
    „\r“ – návrat vozíku na začátek řádku,
    ‚\t‘ – vodorovný tabulátor,
    '\v' - svislá záložka.

    V C můžete také vytvářet proměnné, které mají konstantní hodnotu (jejich hodnotu nelze měnit). Deklarace takových "proměnných" má následující syntaxi: const typ jméno1=hodnota1[,jméno2=hodnota2[,...]]; Příklady:
    const unsigned int x=80, y=25;
    const double pi=3,1415;

    Operátor pro vytváření datových typů v SI

    Operátor typedef se používá k vytváření vlastních datových typů, syntaxe pro jeho použití je: typedef název_starého_typu název_nové_typu; Příklad:
    typedef unsigned intword;
    V SI lze podle standardu definovat typ téměř kdekoli v programu (tj. neexistuje přesně definovaný blok pro definování datových typů) Funkce pro určení velikosti typu, nebo proměnné libovolného typu: sizeof , vrací počet bajtů obsazených v paměti. Příklad:
    sizeof(int) //vrátí 4
    sizeof(char) //výsledek 1
    sizeof(double) // vrátí 8