#9 Práce s textem

Právě dnes Vám přinášíme další díl našeho kurzu programování v jazyce C. V této lekci se budeme zabývat řetězci, zejména používání vestavěných funkcí pro práci s textem, kterých není málo a proto si ukážeme jen ty nejpoužívanější nebo spíše nejužitečnější pro základní práci s řetězci.

Poprvé jsme se o řetězcích zmínili v 6. lekci, kde jsme si ukázali jejich deklaraci, vstup do programu a následné vypsání. Nyní si informace o řetězcích trochu rozšíříme.

Charakteristika řetězců

Jak jsme si již definovali v předešlé lekci (6. lekce), že řetězec je posloupnost znaků. V jazyce C bývají zapsané jako pole znaků (charů). Každý řetězec je ukončen tzv. Binární nulou. Právě díky ukončovací nule si nemusíme pamatovat velikost pole. Pole znaků procházíme pouze do výskytu ukončovací nuly. Je možné nulu posouvat na nižší úroveň v poli, čímž se řetězec zkracuje. Znaky za ukončovací nulou nejsou zpravidla dostupná, ale při pokusu o jejich vypsání se zobrazí jakési „smetí“ z paměti.

Deklarace řetězce

char string[10]; //9 znaku + ukoncovaci nula

Pokud při deklaraci rovnou přiřadíme i nějaký řetězec, velikost pole se nastaví podle počtu znaků + ukončovací nula.

char string[] = "text"; //velikost pole = 5

Inicializace řetězce

Rozdíl v inicializaci pole a řetězce:

char str1[]="abc"; //inicializace pomoci retezcove konstanty
char str2[]={'a','b','c'}; //inicializace pole znak po znaku

Druhý zápis nevloží nakonec pole ukončovací nulu. Pro správnost musíme přidat i nulu jako 4. prvek pole.

char str2[]={'a','b','c', 0};

Pokud použijeme funkci scanf pro načtení řetězce tak syntaxe vypadá následovně:

scanf("%s", string);

Víme, že u funkce scanf se k proměnné přidává operátor reference, aby fungovala správně, jelikož proměnná typu řetězec obsahuje adresu tak se tento operátor nepřidává.

Operace s řetězci

char string[] = “Lorem ipsum”;

Vypsání určitého znaku v řetězci lze bud pomocí operátoru hranaté závorky, kde se vypíše daný prvek v poli, nebo pomocí ukazatelové aritmetiky nastavíme ukazatel na konkrétní adresu a pomocí operátoru dereference vypíšeme obsah paměti na této adrese.

printf("%c\n", string[6]);
printf("%c\n", *(string+6));

Můžeme část řetězce vložit do nového, opět za pomoci ukazatele.

char* string2 = string + 6; //string2 = "ipsum"

Můžeme řetězec zkrátit vložením ukončovací nuly. Zapsáni nuly je možné dvěma způsoby, je jedno který zvolíte.

string[5] = 0;
string[5] = '\0';

Pole řetězců

Je to pole kde typ jednotlivého prvku pole je ukazatel na řetězec (char*). Nejčastější použití takového pole je pro řadu konstantních řetězců.

char* OnlineKurzy[] = {"Lekce VBA","Lekce Pascalu","Kurz C","Webdesign"};

Funkce pro práci s řetězci

Nejprve si ukážeme pár důležitých funkcí, které se nacházejí v knihovně string.h

Funkce strlen

Je to velice používaná funkce, která vrací počet znaků v řetězci. Na příkladu vidíme rozdíl mezi sizeof a strlen. V příkladu je i test ukončovací nuly, zda se opravdu v řetězci nachází.

#include <stdio.h>
#include <string.h>

int main(void)
{
    char string[] = "Lorem ipsum";
    printf("%s\n", string);
    printf("sizeof: %d\nstrlen: %d\n",sizeof(string),strlen(string));
    string[5] = '\0'; //vlozime ukoncovaci nulu doprostred retezce
    printf("%s\n", string);
    printf("sizeof: %d\nstrlen: %d\n\n",sizeof(string),strlen(string));

    printf("vypisuju ukoncovaci nulu jako znak:%c(vlozi mezeru)\n", string[11]);
    printf("vypisuju ukoncovaci nulu jako cislo:%d(vlozi cislo)\n", string[11]);

    return 0;
}

Pokud chceme nulu vypsat samostatně jako znak tak se na výpisu objeví mezera, při výpisu celého řetězce se tato mezera neobjeví, jelikož se vypisují pouze znaky před ukončovací nulou.

U funkce strlen opravdu vidíme, že řetězec má délku 11, ale funkce sizeof nám vrací číslo o 1 větší a to proto, že funkce ukazuje kolik je potřeba bytů na uchování tohoto řetězce v paměti. Víme, že na uložení jednoho znaku potřebujeme právě 1 byte a ten 12. byte navíc je právě pro ukončovací nulu.

Funkce strcpy

Slouží k překopírování jednoho řetězce do druhého.

strcpy(retezec1,retezec2);

Dle znázornění se zkopíruje retezec2 do retezce1. Důležité pravidlo pro použití této funkce je, že retezec1 musí být dostatečně velký, aby byl schopen uložit do sebe retezec2.

Funkce strcat

Tato funkce umožňuje spojit dva řetězce v jeden.

char str[256] = "Lorem";
printf("%s\n",strcat(str,"Ipsum"));

Výsledek bude vypadat: LoremIpsum.

Pravidlo pro tuto funkci je, že opět první řetězec musí mít dostatečnou velikost a to: (délka prvního řetězce) + (délka druhého řetězce) + 1 (ukončovací nula)

Funkce strcmp

Pro porovnání dvou řetězců se používá tato funkce.

strcmp(retezec1,retezec2);

Funkce vrací celočíselnou hodnotu:

  • hodnota 0 – vrací, jestliže jsou oba řetězce shodné
  • hodnota >0 – vrací, pokud první řetězec je větší než druhý
  • hodnota <0 – vrací, pokud první řetězec je menší než druhý

Výsledky jsou vyhodnoceny na základě uspořádání znaků v ASCII tabulce. Pokud tedy použijeme toto porovnání:

strcmp("A","a");

Hodnota velkých písmen je v ASCII tabulce před malými písmeny, tudíž funkce vyhodnotí výsledek na záporný.

Funkce strchr

Jedná se o vyhledávací funkci, která zjišťuje, zda řetězec obsahuje hledaný znak.

strchr(retezec,znak);

Pokud se hledaný znak v řetězci nachází potom funkce vrací hodnotu v podobě ukazatele na prvně nalezený znak, v opačném případě vrací hodnotu NULL.

Funkce strstr

Obdobná funkce jako předchozí, akorát s tím rozdílem, že funkce vyhledává, zda se v prvních řetězci nachází řetězec druhý. Pokud se takový řetězec najde, funkce vrací ukazatel na první výskyt řetězce.

Znakové funkce

V následující tabulce si ukážeme několik funkcí, které zjišťují charakteristiku znaku, všechny tyto funkce se nacházejí v knihovně ctype.h. Všechny funkce mají jeden parametr a to znak (char). Návratová hodnota pro všechny funkce je stejná, mohou nastat pouze 2 případy:

  • Funkce vrátí číslo různé od nuly pro PRAVDU
  • Funkce vrátí číslo 0 pro NEPRAVDU
Funkce Popis Znaky 1
isalnum(char) Je znak písmeno nebo číslo? A – Z, a – z, 0 – 9
isalpha(char) Je znak písmeno? A – Z, a – z
isblank(char) Je znak prádný? tabulátor (‚\t‘), mezera (‚ ‚)
iscntrl(char) Je znak řídící? znaky s ASCII hodnotu < 32 a znak s hodnoutou 127
isdigit(char) Je znak číslo? 0 – 9
isgraph(char) Má znak grafické znázornění? 0 – 9, A – Z, a – z, všechny speciální znaky
islower(char) Je znak malé písmeno? a – z
isprint(char) Je znak tisknutelný? mezera, 0 – 9, A – Z, a – z, všechny speciální znaky
ispunct(char) Je znak speciální? !“#$%&'()*+,-./:;<=>?@[\]^_`{|}~
isspace(char) Je znak „bílý“? tabulátor (‚\t‘), mezera (‚ ‚), nový řádek (‚\n‘), návrat vozíku (‚\r‘), vertikální tabulátor (‚\v‘), konec stránky (‚\f‘)
isupper(char) Je znak velké písmeno? A – Z
isxdigit(char) Je znak hexadecimalní číslo? 0 – 9, A – F, a – f

1) Tato položka zobrazuje znaky, u kterých daná funkce vyhodnotí znak na pravdivý

V knihovně ctype.h se nacházejí jěště 2 funkce, které převádějí znak buď na velké písmeno nebo naopak na malé. Ještě jednou zdůrazňuji, že převádejí znak nikoliv celé slovo (řetězec), pokud bychom chtěli použít jednu z těchto funkcí na celém řetězci, jednoduše použijeme cyklus, který projde řetězec znak po znaku a upraví jej podle potřeby.

Funkce Popis
tolower(char) převede velké písmeno na malé
toupper(char) převede malé písmeno na velké

 

K dnešní lekci to bude vše, doufám že jste se zase něco dozvěděli, můžete i články sdílet na sociálních sítích a podobně. V následujícím článku se zaměříme na soubory.

#8 Rekurze a vlastní datové typy

Tato lekce se bude zabývat rekurzivnímu volání funkcí, dále také rozsahu platnosti proměnných. Hlavním bodem tohoto článku budou vlastní datové typy.

Rekurze

Rekurze je proces kde určitý objekt, v případě jazyka C je to funkce, se využívá sebou samým. Funkce obsahuje ve svém těle volání sebe sama a při každém volání jsou upravené předávané hodnoty. Rozlišujeme dva druhy rekurze:

  1. Přímá – uvnitř kódu funkce je volání té samé funkce
  2. Nepřímá – v kódu funkce se nachází volání druhé funkce, která opět zavolá funkci první

Na příkladu si znázorníme použití rekurze a syntaxi kódu, opět použijeme nám již známy výpočet faktoriálu:

#include <stdio.h>

int factorial(int n) {
    if (n > 1) 
        return n * factorial(n - 1);
    else 
        return 1;
}

int main(void)
{
    int number = 0;

    printf("Insert number: ");
    scanf("%d", &number);

    printf("Result: %i\n", factorial(number));

    return 0;
}

Rekurze je určitý druh cyklu, tudíž musí mít ukončovací podmínku, jinak by výpočet rekurze procházel čím dál více do hloubky a mohlo by dojít i k nekonečné hloubce.

Detailnější chování výpočtu rekurze, nám prozradí debugger. Ten obsahuje zajímavou pomůcku, kde si můžeme nastavit podmínku zastavení v rekurzi, jedná se o podmíněný breakpoint.

Vlasnosti breakpointu si zobrazite pravým kliknutím myši na breakpoint a klikněte na Edit breakpoint. Do položky Condition zapíšete podmínku podle které se debugger zastaví na určitém místě.

Rekurzivnímu řešení se raději vyhněte, pokud to samozřejmě není podmínkou příkladu. Jakákoliv rekurze lze zapsat cyklem, nejčastěji while, proto raději preferujte tuto volbu. Rekurze je náročnější vzhledem k výkonu počítače. Při zavolání nové funkce se spouštějí i nové instrukce procesoru a to předání parametru, předání návratové hodnoty a také alokace a dealokace paměti na zásobníku.

Rozsah platnosti proměnné

Deklarace lokální či globální proměnné se nijak neliší. Rozdíl mezi nimi je v rozsahu platnosti v programu.

Lokální proměnná

Pro lokální proměnnou jsou 3 možnosti jak jej deklarovat, myslí se kde, v jaké částí kódu se nachází:

  1. Funkce
  2. Blok
  3. Cyklus

Pří deklaraci proměnné ve funkci, se považuje za platnou až do ukončení funkce. Jedná se pouze o proměnné, které se nacházejí v hlavičce. Pokud bychom deklarovali proměnnou pod hlavičkou, tedy někde uprostřed kódu funkce. Jednalo by se o deklaraci v bloku. Blokem považujeme část kódu, která je ohraničená složenými závorkami. Jako u funkcí i u bloku je to stejné s rozsahem platnosti proměnné, tedy proměnná deklarovaná uvnitř bloku (např.: v podmínce if) zaniká při skončení bloku. Co se týče cyklů, tak proměnná deklarovaná v „hlavičce“ cyklu (platí pouze pro cyklus for) je platná pouze při provádění daného cyklu, po skončení zaniká.

Globální proměnná

Deklarace takovéto proměnné se nachází vně jakékoliv funkce. Platnost proměnné zaniká až při ukončení celého programu.

#include <stdio.h>

int number = 50;

void myFunc()
{
    number += 100;
}

int main(void)
{
    myFunc();
    printf("%d\n", number);
    return 0;
}

Za jakousi globální proměnnou lze považovat i identifikátor vzniklý pomocí direktivy #define. Ačkoliv pojmenování proměnná není správné, jelikož je konstantní a nedá se přepisovat. Rozsah platnosti může být stejný jako u globální proměnné, za předpokladu že se v kódu neprovede direktiva #undef, která by tento identifikátor odstranila.

Překrytí identifikátorů (proměnných)

Existuje možnost překrytí či zastínění proměnné. Například globální identifikátor může být překrytý lokálním identifikátorem (např.: v cyklu), který má sejný název. Používání překrývaní proměnných je nevhodné a zhoršuje se přehlednost kódu.

Vlastní datové typy

Prozatím jsme používali pouze vestavěné datové typy, jako např.: int, double, char. Nyní si však ukážeme jak vytvářet vlastní typy, pomocí kterých lze v jazyce C vytvářet rozsáhlejší konstrukce. Definice všech vlastních typů se zapisují mimo jakoukoliv funkci. Dle konvence se zapisuje ještě před definicí první funkce programu. Existují 4 druhy klíčových slov, bez kterých se vytvoření vlastního datového typu neobejde, jsou to:

  • enum
  • struct
  • union
  • typedef

1. Enum

Nejprve se zaměříme na enum. Pomocí něj lze vyvářet výčtový typ. Jednoduše lze říct, že enum slouží k pojmenování konstant. Obecná deklarace typu enum vypadá takto (zároveň vidíme i deklaraci proměnné, která používá nově definovaný typ):

enum nazev_typu {hodnota1,hodnota2,…}
enum nazev_typu nazev_promenne;

Při deklaraci typu jsou jednotlivé hodnoty ve složených závorkách oddělené čárkou. Při deklaraci proměnné nesmíme zapomínat napsat i zde klíčové slovo enum.

#include <stdio.h>
enum cisla {nula, jedna, dva};

int main(void)
{
    enum cisla cislo;
    cislo = dva;
    //cislo = tri; //error tri neni soucasti vyctoveho typu

    printf("%d\n", cislo);
    return 0;
}

Po spuštění ukázkového programu, ihned zjistíme, že konstrukce enum je vytvořená pomocí celočíselného typu, konkrétněji je to typ int. Dle výchozího nastavení se první položka inicializuje na hodnotu 0, další položka má hodnotu 1, atd.

Tyto hodnoty je možné změnit a to tak, že např.: jedné z položek ve výčtu přiřadíme konkrétní hodnotu.

enum muj_enum {automobil, letadlo = 5, kolo};
// auto = 4, letadlo = 5, kolo = 6

Hodnoty mohou být i stejné.

enum muj_enum {automobil = 7, letadlo = 7, kolo};
// auto = 7, letadlo = 7, kolo = 8

2. Struct

Jak již z pojmenování vyplývá, budou se pomocí slova struct vytvářet datové struktury. Jedná se o množinu proměnných různého datového typu, které jsou navzájem spojené. Každá proměnná má sice své pojmenování, ale celkové spojení musí mít také svůj název.

Obecná deklarace struktury:

struct nazev_typu {
	datovy_typ1 promenna1;
	datovy_typ2 promenna2;
	…
};

Vytvoření proměnné ve funkci:

struct nazev_typu promenna;

Podle obecné definice se nám může zdát, že je to v podstatě to samé co enum, ale není to úplné pravda, struct může mít libovolný datový typ ke každé své proměnné, třeba i další struct.

Ukázkový příklad nám napoví, k čemu vlastně slouží struct.

#include <stdio.h>

struct person_t {
    char name[255];
    unsigned short age;
};

int main(void)
{
    struct person_t person1 = {"Jan", 25};
    printf("%s, %d\n",person1.name,person1.age);

    struct person_t person2 = {.age = 20};
    printf("%s, %d\n",person2.name,person2.age);
    return 0;
}

Struct můžeme použít například k evidenci osob, protože samotná osoba má více položek, které je potřeba uchovat.

V příkladu jsou použity 2 různé deklarace proměnné, které používají stejný datový typ. První přiřazení je klasické, ale u druhého je použitá tečková notace, stejné jako při výpisu jednotlivých proměnných.

Právě pomocí operátoru tečka, přistupujeme k jednotlivým položkám ve structu.

Struct je tedy vhodný na již zmíněnou evidenci osob, ale abychom nemuseli deklarovat každou osobu zvláště, je jednoduší vytvořit pole. Deklaraci pole sice známe, ale pouze pro upřesnění je zde syntaxe:

struct person_t persons[1000];
persons[485].age = 25;

Také jako obyčejnou proměnnou i celé struktury lze kopírovat.

struct person_t person1 = {"Jan", 25};
struct person_t person2;
person2 = person1;

Musíme si však dát pozor, pokud struktura obsahuje datový typ ukazatel. Kopírování totiž způsobí, že 2 proměnné budou ukazovat na stejné místo v paměti a mohlo by docházet k nesprávným výsledkům.

3. Union

Typ union je stejný jako struct, jeho deklarace je totožná, s tím rozdílem, že se zamění klíčové slovo. Union se moc nepoužívá a je vhodnější použít struct. Odlišnost mezi structem a unionem je v tom, že struct zabírá v paměti tolik místa kolik je potřeba pro uchování všech jeho položek, kdežto union zabírá pouze tolik paměti jako jeho největší položka, je tedy jasné, že je možné použít pouze jednu položku v jeden moment. Je tedy možné říct, že všechny položky unionu sdílí jedno místo v paměti.

4. Typedef

Tento nástroj neslouží k vytvoření nějaké struktury různých datových typů jako předešlé 3 případy. Typedef slouží k pojmenovávání různých datových typů, šetří se tím zejména velikost kódu a stává se tím i přehlednější.

Obecný tvar:

typedef datovy_typ nazev_typu;

Pomocí typedef je možné si zkrátit zápis některých deklarací např.:

typedef unsigned short ushort;
typedef int matrix[3][3];

Lze také využít u deklarace structu, poté při deklaraci proměnné typu struct nebude již nutné vepisovat klíčové slovo, ale zapíše se pouze jeho zkrácený zápis vytvořený pomocí typedef:

typedef struct person_t {
    char name[255];
    unsigned short age;
} person;

person person1 = {"Jan", 25};

To bude vše k dnešní lekci, snad jste se zase něco nového dozvěděli a naučili.

#7 Rozdělení zdrojového kódu

V sedmé lekci kurzu programování v jazyce C si ukážeme, jak a k čemu je dobré rozdělovat zdrojový kód do více části, než právě do jedné velké funkce.

Již v druhé lekci jsme si popsali základní prvky, které funkce může nebo musí obsahovat. Klasická funkce by měla obsahovat návratový typ a také vstupní argumenty, na základě kterých se ve funkci vyhodnotí kód a funkce nám vrací nějaký výstup.

Rozdělení kódu do více funkcí je užitečné zejména proto, že celý kód se stává o mnoho přehlednější a je také snadnější na editaci. Je vhodné pro odstraňování duplicity kódu. Pokud potřebujete použít vícekrát stejný výpočet akorát s jinými parametry, jednoduše tuto část vyjměte jako novou funkci a poté ji spusťte kolikrát bude potřeba.

Deklarace funkce

Jsou dvě možnosti jak funkci deklarovat:

  1. samotná deklarace funkce a její definice se nachází později v kódu
  2. deklarace společně s definicí

Jak tomuto rozumět? Znázorníme si vše na příkladu.

void func1(int, char, double); //deklarace, nemusi obsahovat nazvy parametru
void func2(int number) //deklarace s definici
{
    //prikazy
}

int main(void)
{
    func1(0,'f',5.0);
    func2(0);

    return 0;
}

void func1(int number, char c, double pi) //definice funkce
{
    //prikazy
}

Pokud máte program, který má pouze jednu vlastní funkci, tak použijte deklaraci společně s definicí. Pro více funkcí a komplexnější programy je pro přehlednost vhodnější použít strukturu:

  • deklarace vlastních funkcí
  • funkce main
  • definice vlastních funkcí

Argumenty a proměnné ve funkci

Proměnné deklarované ve funkci jsou lokální, tzn. že po skončení funkce zanikají. Argumenty jsou také lokální proměnné. Při zavolání funkce se tyto proměnné vytvoří a zkopíruje se do nich hodnota, která jim při zavolání byla předána. Existují 2 možnosti předání argumentů:

  1. Předání hodnotou
  2. Předání hodnotou ukazatele

Rozdíly mezi těmito způsoby si ukážeme na příkladu, použijeme oddělené proměnné pro každý způsob.

void Afunc(int a) {a = 50;} //nastavime a na 50
void Bfunc(int* b) {*b = 80;} //nastavime b na 80

int main(void)
{
    int a = 10;
    int b = 20;

    Afunc(a); //predani hodnotou
    Bfunc(&b); //predani hodnotou ukazatele

    printf("%d\n", a); //a je opet 10
    printf("%d\n", b); //b se zmenilo na 80

    return 0;
}

Ještě si ukážeme jak předat funkci celé pole proměnných, aby jej bylo možno upravovat ve funkci.

#define MAX_ARRAY 10

void write(int* array)
{
    for (int i = 0; i < MAX_ARRAY; i++)
        array[i] = i;
}

void print(const int* array)
{
    for (int i = 0; i < MAX_ARRAY; i++)
        printf("%d ", array[i]);
    printf("\n");
}

int main(void)
{
    int array[MAX_ARRAY] = {0};
    print(array);
    write(array);
    print(array);
    return 0;
}

Poznámka

Na příkladu vidíme, jakým způsobem jsou vyhodnocovány výrazy v jazyce C. Jedná se o zkrácené vyhodnocování neboli také líné. Tento způsob využívají i jiné programovací jazyky jako C# nebo Java.

int func()
{
    printf("func\n");
    return 0;
}

int main(void)
{
    if ((6 == 5) && (func() == 0)) {}
    return 0;
}

Volání funkce func se nachází v podmínce. Vidíme, že první část podmínky se vyhodnotí na false, zde líné vyhodnocování způsobí, že se druha část podmínky ani nemusí vyhodnocovat, jelikož bude celý výraz false.

K dnešní lekci o rozdělení zdrojového kódu to bude vše. V následující lekci se podíváme na využití rekurze a také na různé použití datových struktur.

#6 Vstupní a výstupní funkce

Po delší době zde máme další pokračování kurzu céčka. V této lekci se podíváme na různé možnosti jak načíst data do programu. Ukážeme si také jak data vypisovat pomocí jiných funkcí než z klasické funkce printf, o které jsme se zmínili hned ve druhé lekci našeho kurzu.

Vstupní a výstupní funkce (I/O)

Hodnota Vstup (input) Výstup (output)
znak getchar putchar
řetězec gets puts
formátovaná data scanf printf

 

Všechny tyto funkce se nacházejí v hlavičkovém souboru, který jsme používali doteď a to stdio.h. Pro vstupní funkce se hodnoty vkládají přímo do konzole a potvrdí se enterem. Jejich výzva by měla být zdůrazněna zprávou pomocí výstupní funkce (např.: „Zadejte své jméno: „). Všechny funkce v tabulce si popíšeme.

Getchar a Putchar

Obě tyto funkce pracují pouze s jedním znakem. Pokud bychom funkci getchar dali na vstup nějaké slovo o 2 a více znacích, popřípadě větu, funkce si uloží pouze první znak a zbytek zahodí. Funkce je deklarovaná takto:

int getchar(void)

Vidíme, že funkce nemá žádné vstupní parametry, ale má návratovou hodnotu.

  • úspěšné vyhodnocení vrací vstupní znak
  • neúspěšné vrací konstantu EOF (end of file), tedy hodnotu -1

Funkce getchar je deklarovaná takto:

int putchar(int c);

Funkce má jeden vstupní parametr c od slova character (znak), parametrem je hodnota která se má vypsat na standardní výstup. Putchar dokáže vypsat pouze znaky ohodnocené číslem 0 až 255 (podle ASCII tabulky). Návratová hodnota je znak, který se vypsal. Při neúspěšném výpisu vrací EOF.

char c;
c = getchar();
putchar(c); //vraci zadany znak funkci getchar
putchar(97); //vraci znak 'a'
putchar(97+256); //take vraci znak 'a'

Gets a Puts

Tyto funkce pracují s celými řetězci. Řetězec je posloupnost znaků, v céčku se zapisuje jako pole znaků. Deklarace proměnné typu řetězec se zapíše takto:

char retezec[delka_retezce];

Deklarace obou funkcí vypadají následovně:

char* gets (char*);
int puts (const char*);

Funkce gets načítá řetězec ze standardního vstupu jejím parametrem je řetězec, který musí být předem deklarovaný na určitou délku. Pokud bychom na vstup funkci dali větší řetězec než jak je deklarovaná samotná proměnná, tak se délka proměnné zvětší na požadovanou velikost. Návratová hodnota je vstupní řetězec při úspěšném provedení. Při neúspěšném načtení vrací EOF.

Funkce puts vypisuje řetězec na standardní výstup. Návratová hodnota je nezáporná hodnota při úspěšném vypsání a EOF při neúspěšném.

char string[16];
gets(string);
puts(string);

Scanf a printf

Doteď jsme mohli načíst pouze znak či řetězec, ale pokud bychom chtěli zaimplementovat vstup čísla do programu, musíme použít funkci scanf. Deklarace této funkce:

int scanf(const char *format,…);

Scanf pracuje na stejném principu jako printf, který známe z předchozích lekcí. Může načíst nekonečně mnoho hodnot. Pokud bychom chtěli načíst číslo ze standardního vstupu, kód bude vypadat následovně:

int cislo;
scanf("%d", &cislo);

Aby funkce scanf načetla číslo ze vstupu a následně jej uložila do proměnné cislo, musíme funkci předat adresu proměnné cislo v paměti, nikoliv jeji hodnotu. Pokud bychom chtěli načíst více hodnot, syntaxe bude vypadat obdobně.

int x,y,z;
scanf("%d %d %d", &x, &y, &z);

Na následujícím příkladu vidíme, co udělá formát ve výpisu znaku:

char c = '0';
printf("%c %d\n", c,c);

Pokud vypíšeme proměnnou c (znak ‚0‘) jako číslo vypíše se jeho ASCII kód.

Funkce Rand

Tato funkce je v podstatě vstupní, neboť vrací náhodně vygenerované číslo. Nachází se v hlavičkovém souboru stdlib.h, proto je nutné tento soubor přidat do programu pomocí direktivy #include. Syntaxe vypadá následovně.

nah_cislo = rand() % (horni_hranice-dolni_hranice+1) + dolni_hranice;

Pokud nevyznačíme hranice, v jakých se vygenerované číslo má vyskytovat, použije se výchozí nastavení těchto hodnot. Rozmezí je mezi 0 a 32767. Zápis je pouze takový:

number = rand(); //0 az 32767

Příklady různých rozmezí:

number = rand() % 10; //0 az 9
number = rand() % 10 + 1; //1 az 10
number = rand() % 113 + 1900; // 1900 az 2012

Pokud si program, který generuje číslo, spustíte vícekrát za sebou a přitom nezměníte syntaxi, zjistíte, že se generují pořád stejné čísla. Aby se při každém spuštění generovaly nové čísla, použijeme příkaz z knihovny time.h.

srand (time(NULL));

Celý program nyní může vypadat takto:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    srand (time(NULL));
    int n = 10;
    int horni, dolni;
    int number;

    printf("Zadej horni mez: ");
    scanf("%d", &horni);
    printf("Zadej dolni mez: ");
    scanf("%d", &dolni);

    printf("%d cisel vygenerovanych v rozmezi %d az %d (vcetne)\n",n, dolni, horni);
    for (int i = 0; i < n; i++)
    {
        number = rand() % (horni-dolni+1) + dolni;
        printf("%d\n", number);
    }

    return 0;
}

Data lze načíst také pomocí souboru, ale kompletní práci se soubory si ukážeme v některé z následujících lekcí. Ke vstupním a výstupním funkcím to bude vše. Příště se podíváme jak rozdělit zdrojový kód do více pod částí.

#5 Pole a ukazatel

V dnešním článku se podíváme na velice důležitou část v céčku, ukazatel nebo také pointer. Jak ukazatel funguje, si ukážeme hned na několika příkladech. S ukazateli se budeme potýkat i v dalších lekcích, takže se pokusím co nejlépe vysvětlit jak s nimi pracovat. V druhé části si ukážeme využití pole.

Ukazatel

Ukazatele uchovávají adresu na nějaké místo v paměti. Při deklaraci proměnné typu ukazatel se specifikuje i datový typ, tento typ je totožný s hodnotou místa paměti, na kterou ukazuje, tzn. že ukazuje na hodnotu určitého datového typu.

Deklarace ukazatele na datový typ:

int* nazev_ukazatele

Operátor reference (&)

Vrací adresu (pozici v paměti) svého parametru, následná hodnota lze přiřadit do ukazatele.

int cislo = 5;
int*pCislo = &cislo;

Konvence o pojmenovávání ukazatelů: Název proměnné typu ukazatel se skládá z písmene p (od slova pointer) a z názvu proměnné (bude začínat velkým písmenem) na kterou ukazuje.

Operátor dereference (*)

Vrací hodnotu na určitém místě paměti, adresa toho místa je uložena v argumentu.

Nyní si ukážeme jak správně vypsat hodnotu ukazatele. Použijeme deklarace, které jsou znázorněny na obrázku.

printf("%d\n", *pCislo); //vypise hodnotu promenne cislo, tedy 5
printf("%d\n", pCislo); //vypise adresu v pameti promenne cislo

Více ukazatelů může ukazovat na stejné místo v paměti. Následující zápis je správný pokud používáme předešlé deklarace.

int* pCislo2 = &cislo;

Pole

Jedná se o skupinu proměnných, které mají stejný datový typ a jsou označený stejným názvem. Pro přístup k jednotlivým prvkům v poli se využívají tzv. indexy, kterými jsou všechny prvky v poli identifikovány. V paměti je pole uloženo souvisle.

Jazyk C nehlídá meze polí, z důvodu rychlosti, proto pokud budete zapisovat mimo pole, obvykle to vede k chybě a program spadne.

Jednorozměrné pole

  • K prvkům pole se přistupuje pomocí operátoru [] (hranaté závorky)
  • Indexy prvků začínají vždy od nuly.

Obecná deklarace jednorozměrného pole:

datovy_typ nazevpole[velikost_pole]; //velikost_pole urcuje pocet prvku v poli.

Pokud bychom chtěli již při deklaraci proměnné inicializovat i jednotlivé prvky pole, použijeme tento zápis:

int array[5] = {8,6,9,-1,25};

Jestli použijeme takto plně inicializované pole, je možné vynechat hodnotu, která určuje velikost pole, velikost se automaticky zjistí z počtu inicializovaných prvků.

Pokud neinicializujeme všechny prvky v poli, ale např. pouze 2, tak se ostatní prvky nastaví na hodnotu 0. Pokud bychom chtěli mít celé pole vynulované, použijeme tento zápis:

int array[5] = {0};

Pro zápis konkrétního prvku v poli použijeme následující zápis, ostatní neinicializované hodnoty se opět nastaví na hodnotu 0.

int array[5] = {[1] = 5, [4] = 10};

Inicializace prvku pole se většinou neprovádí ihned při samotné deklaraci pole, ale později v kódu na základě vstupu ze souboru či z klávesnice. Pro procházení prvku se nejlépe použije cyklus. Aritmetika nad prvcích pole je stejná jako u obycejné proměnné.

int array[10];
for (int i = 0; i < 10; i++)
    array[i] = i; //zapis do pole

for (int i = 0; i < 10; i++)
    printf("%d\n", array[i]); //vypis prvku z pole

Vícerozměrné pole

  • Deklarace je stejná jako u jednorozměrného pole, pouze za názvem pole tolik určení velikosti, kolik chceme rozměru. Velikosti se zapisují za sebou, opět do hranatých závorek.
  • Přístup k prvkům je stejný, opět musíme uvést tolik hodnot v hranatých závorkách, kolik je rozměrů.

Deklarace dvourozměrného pole, jedná se v podstatě o matici prvků:

int array[2][3];
int array2D[2][3];
for (int i = 0; i < 2; i++)
    for (int j = 0; j < 3; j++)
        array2D[i][j] = i + j;

for (int i = 0; i < 2; i++)
    for (int j = 0; j < 3; j++)
        printf("%d\n", array2D[i][j]);

Nejčastěji z vícerozměrných polí se používá právě matice. Jednoduší je ale realizovat dvourozměrné pole jako jednorozměrné a o správný zápis do pole řešit později v programu. Deklarace je jednoduchá, stačí provést násobení mezi oběma rozměry. Přístup k prvku je o něco složitější:

array1D[radek * pocet_sloupcu + sloupec];

Převod naší matice 2 x 3 na jednorozměrné pole.

int array1D[2*3];
for (int i = 0; i < 2; i++)
    for (int j = 0; j < 3; j++)
        array1D[i * 3 + j] = i + j;

for (int i = 0; i < 2; i++)
    for (int j = 0; j < 3; j++)
        printf("%d\n", array1D[i * 3 + j]);

Pole a ukazatel

Vztah mezi ukazateli a poli je velmi blízky, totiž pole je možno realizovat pomoci ukazatele, tento způsob je rychlejší ale asi o trošku hůř pochopitelný.

    int array[10];
    for (int i = 0; i < 10; i++)
        array[i] = i;

    int* pArray = array; //pArray ukazuje na prvni prvek v poli

    printf("%d\n", *pArray); //vypise prvni prvek
    printf("%d\n", *(pArray + 5));

    pArray++; //posun na prvek nasledujici
    //*pArray++; //spatny posun prvku (warning)
    *pArray = -20; // prirazeni hodnoty do aktualniho prvku
    *(pArray + 3) = -10; // prirazeni hodnoty do aktualniho prvku + 3

    printf("Vypis pole: ");
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", array[i]); //vypis vsech prvku
    }

    pArray++;
    pArray++;
    printf("\n%d\n", pArray - array); //vypise pocet prvku mezi zacatkem pole a aktualnim prvkem

K dnešní lekci to bude vše doufám, že jsem vám dostatečně vysvětlil práci s ukazateli. Příště se podíváme na různé funkce vstupu.

#4 Řídící Struktury

V této lekci, v pořadí 4. se podíváme na řídící struktury. Patří zde podmíněné příkazy a cykly. Zařadíme zde i přepínač (příkaz switch), jeho využití je minimální. Použití jednotlivých struktur si znázorníme na příkladech.

Podmínky

Podmínka s IF

Podmíněné výrazy se tvoří pomocí klíčového slova if. Je možné použít i else ale není to nutnost.

Obecný tvar:

if (podminka)
{
	prikazy
}
else
{
	Prikazy
}

Syntaxe dovoluje zapsat podmínku i bez složených závorek, které ohraničují příkazy jak pro splněnou tak pro nesplněnou podmínku.

int vysledek = 0;

if (5 > 6)
    vysledek += 10;
    vysledek += 15;

printf("Vysledek: %d\n", vysledek);

Jak bude vypadat výpis toho programu?

Vysledek: 15

Pokud nepoužíváme složené závorky, tak ke splněné či nesplněné podmínce přiřadí pouze první příkaz. V tomto programu se podmínka nesplní. Přiřazení +10 se neprovede a else větev neexistuje. Následná operace +15 již není součásti podmíněného příkazu a je provedena.

Na následujícím příkladu vidíme použití více větví:

    
int den = 7; //predpokladejme prirazeni v rozmezi 1 až 7

if (den == 6)
    printf("je vikend\n");
else if (den == 7)
    printf("je vikend\n");
else
    printf("je pracovni den\n");

Vidíme ale, že první 2 větve mají stejný výstup je možné je spojit do jedné za použití správného operátoru:

if (den == 6 || den == 7)
    printf("je vikend\n");
else
    printf("je pracovni den\n");

Příkaz switch

Pokud máme podmínku, která obsahuje mnoho větví, je jednoduší a přehlednější použít příkaz switch.

Obecný tvar:

switch(celociselna_promenna)
{
	case konst_vyraz_1 : prikazy_1;
	case konst_vyraz_n : prikazy_n;
	default : prikazy_vychozi;
}

Následující program vypisuje slovně den v týdnu. Každý den je charakterizovaný číslem, zadán v proměnné den. Klauzule default se provede, pokud výčet neobsahuje konkrétní hodnotu, ale tento příkaz není povinný.

int den = 5;

switch(den)
{
    case 1 : printf("pondeli\n");break;
    case 2 : printf("utery\n");break;
    case 3 : printf("streda\n");break;
    case 4 : printf("ctvrtek\n");break;
    case 5 : printf("patek\n");break;
    case 6 : printf("sobota\n");break;
    case 7 : printf("nedele\n");break;
    default : printf("neznamy den\n");break;
}

Tento příklad obsahuje stejné zadání, jako příklad u podmínky s if, abyste mohli srovnat syntaxi.

int den = 5; //opet predpokladame ze prirazeni je v rozmezi 1 až 7
switch (den)
{
    case 6 :
    case 7 :
    printf("je vikend\n"); break;
    default : printf("je pracovni den\n"); break;
}

Cykly

Cyklus musí vždy obsahovat podmínku, podle které se cyklus ukončí. V těle cyklu musí být výraz, který souvisí s ukončovací podmínkou cyklu. Na základě výpočtu tohoto výrazu, se podmínka v určitém kroku nesplní a dojde k ukončení cyklu. V jazyce C existují 2 typy cyklů. Jako u podmínek i v cyklech je možné nevyužít složené závorky, ale cyklus se bude vztahovat pouze k prvnímu příkazu (posloupnost znaků oddělená nejbližším středníkem).

1. For

Obecný tvar:

for (pocatecni_hodnota;podminka;inkrementace)
{
	prikazy
}

Zde vidíme základní použití cyklu for. Program vypisuje jednotkovou matici, za pomoci 2 cyklu.

#include <stdio.h>

int main(void)
{
    unsigned char rozmer = 6; //rozmer matice

    for (int i = 0; i < rozmer; i++)
    {
        for (int j = 0; j < rozmer; j++)
        {
            if (i == j)
                printf("1 "); //vypisuje 1 na hlavni diagonale
            else
                printf("0 ");
        }
        printf("\n");
    }

    return 0;
}

Tento program vypočítává faktoriál ze zadaného čísla (proměnná cislo), je vhodné si na tomto programu vyzkoušet debugger. Pokud neumíte s debuggerem v Qt Creatoru pracovat, zde je návod k použití.

#include <stdio.h>

int main(void)
{
    unsigned int cislo = 6;
    unsigned int faktorial = 1;

    for (unsigned int i = cislo; i > 0; i--)
    {
        faktorial *= i;
    }

    printf("Vysledek: %u\n", faktorial);
    return 0;
}

Tento ukázkový příklad zobrazuje nekonečný cyklus. Ukončení běhu programu v konzoli se provede pomocí klávesové zkratky CTRL + C.

for (int i = 0; i < 1; i++)
{
    i = 0; //ridici promenna je vynulovana a podminka cyklu je s kazdou iteraci cyklu splnena
    printf("%d", i);
}

2. While

Tento cyklus může být dvojího typu, záleží jestli je podmínka cyklu na začátku nebo na konci.

Obecný tvar s podmínkou na začátku:

while (podminka)
{
	prikazy
}

Obecný tvar s podmínkou na konci:

Do
{
	Prikazy
} while (podminka);

Na tomto triviálním příkladu vidíme rozdíly mezi oběma typy cyklu while:

do
{
    printf("Cyklus while s podminkou na kocni\n");
} while(1 == 2);

while(1 == 2)
{
    printf("Cyklus while s podminkou na zacatku\n");
}

Zde můžeme srovnat syntaxe obou dvou typu cyklu (for i while)

#include <stdio.h>

int main(void)
{
    printf("Zde je vypis cyklu for\n");
    for (int i = 0; i < 3; i++) //v tomto radku vidime inicializaci pocatecni hodnoty, podminku i inkrementaci 
    {
        printf("for: %d\n", i);
    }

    printf("\nZde je vypis cyklu while\n");
    int j = 0; //zde je inicializace pocatecni hodnoty
    while (j < 3) //zde je podminka cyklu
    {
        printf("while: %d\n", j);
        j++; // zde je inkrementace
    }

    return 0;
}

Pomocné příkazy v řídících strukturách

break

S tímto příkazem jsme se již setkali u switch, kde byl použit u každého výčtu. Pokud by příkaz break nebyl pouzit ani u jednoho výčtu, příkaz switch by se nechoval jako podmíněný výraz a byl by naprosto zbytečný. Pokud upravíme příklad, který jsme si uvedli právě u příkazu switch, tak že odstraníme všechny příkazy break, můžeme pozorovat změnu ve výpisu pro všechny hodnoty 1 až 7.

Obecně příkaz break předčasně ukončuje cykly for či while. V daném programu se postoupí na příkaz, který se nachází bezprostředně za tímto cyklem. Použití si znázorníme na ukázkovém příkladu:

#include <stdio.h>

int main(void)
{
    unsigned char cislo = 250;
    for (int i = 1; i < 100; i++)
    {
        cislo++;
        printf("%d. iterace cyklu | cislo = %d\n", i, cislo);
        if (cislo == 255) //pokud dojde k preteceni datoveho typu, cyklus se zastavi
            break;
    }

    return 0;
}

continue

Tento příkaz funguje podobně jako break, s tím rozdílem, že neukončí celý cyklus, ale pouze právě prováděnou iteraci.

goto

Příkaz provede přesun na určitý řádek kódu, který je označen tzv. štítkem. Tento štítek musí mít stejný název jak pro označení řádku tak jako hodnota příkazu goto, aby příkaz fungoval správně

goto stitek
...
stitek : prikazy

Tento příkaz je možné použít i ke zkonstruování cyklu:

int i = 0;

iterace : if (i < 10)
{
    printf("%d. iterace\n",i);
    i++;
    goto iterace;
}

Všechny tyto 3 příkazy je možné nahradit, je vhodné je používat co nejméně a příkaz break používat pouze u switch.

Poznámka

Nepleťte si operátory = a == první slouží k přiřazení a druhý porovnává 2 hodnoty.

To je vše k dnešní lekci o řídících strukturách v jazyce C. Příště se podíváme na využití polí a také na ukazatele.

#3 Operátory a operandy

V další lekci programování v C se podíváme pouze na všechny možné operátory, které v jazyce C existují. Na některých z nich si ukážeme několik příkladů.

Dodatek k deklaraci proměnné

V předchozí lekci jsme se zapomněli zmínit o tom, že jméno funkce nemůže obsahovat jakékoliv slovo. V jako každém jiném programovacím jazyce i v C existují určitá rezervovaná slova, která nelze použít jako jméno pro proměnné, či názvy vlastních funkcí. Zde je výčet všech těchto rezervovaných slov:

auto break case char const
continue default do double else
enum extern float for goto
if inline int long register
return short signed sizeof static
struct switch typedef union unsigned
void volatile while _Bool _Complex

Příklad:

int return; // tento nazev nelze pouzit
int Return; // tento nazev je mozne pouzit

Tři pojmy na úvod

  • Výraz = posloupnost operandů a operátorů
  • Operand = proměnná či hodnota
  • Operátor = určuje druh operace, která bude ve výrazu provedena nad operandy

Operátory

V jazyce C existuje 15 prioritních skupin, do kterých se jednotlivé operátory řadí. Směr sdružení nebo také asociativita může být buď zleva nebo zprava. Operátory dělíme do skupin podle toho, kolik potřebují operandů.

Unární operátory

OPERÁTOR POPIS PRIORITA ASOCIATIVITA
+ hodnota operandu se nemění 2 zprava
změní hodnotu operandu na z kladné na zápornou a opačně 2 zprava
++ inkrementace 2 zprava
dekrementace 2 zprava
! logická negace 2 zprava
~ změna bitu z 0 na 1 a opačně 2 zprava
& reference 2 zprava
* dereference 2 zprava
(datovy_typ) přetypování operandu 2 zprava
sizeof počet bytů které operand zabírá v paměti 2 zprava

Na příkladu si ukážeme využití operátoru přetypování:

#include <stdio.h>

int main(void)
{
    int x = 5, y = 2;
    float vysledek;

    vysledek = x / y;
    printf("%.1f\n", vysledek); //presnost na 1 desetinne misto
    /*
    pokud delime celociselne promenne vysledek take bude
    celociselny a tim ze jej priradime do realneho typu
    tak se vypise i s desetinnou carkou ale vysledek bude
    vypocitan spatne nebo spise jinak nez jsme chteli
    */

    vysledek = (float)x / y;
    printf("%.1f\n", vysledek);

    vysledek = x / (float)y;
    printf("%.1f\n", vysledek);

    vysledek = (float)x / (float)y;
    printf("%.1f\n", vysledek);
    /*
    pokud alespon jeden operand x nebo y pretypujeme na realny tak vysledek bude spravny  
    */
    
    return 0;
}

Binární operátory

Aritmetické

OPERÁTOR POPIS PRIORITA ASOCIATIVITA
* násobení 3 zleva
/ dělení 3 zleva
% modulo (zbytek po dělení) 3 zleva
+ sčítání 4 zleva
odčítání 4 zleva

Můžeme si také ukázat, jak vlastně funguje priorita operátorů. Použijeme stejný kód jako u předešlého příkladu.

#include <stdio.h>

int main(void)
{
    int x = 5, y = 2;
    float vysledek;

    vysledek = (float)x / y;
    printf("%.1f\n", vysledek);
    /* vime ze pretypovani ma vetsi prioritu nez deleni
      takze se tento vyraz vyhodnoti spravne, pokud
      bychom ale vynutili aby se deleni provedlo nejdrive
      tak uz vysledek bude jiny
      */

    vysledek = (float)(x / y); //vynuceni priority pomoci zavorek
    printf("%.1f\n", vysledek); //chybny vysledek, vidime ze priorita operatoru je dulezita

    return 0;
}

Přiřazovací

OPERÁTOR PRIORITA ASOCIATIVITA
= 14 zprava
+= 14 zprava
-= 14 zprava
*= 14 zprava
/= 14 zprava
%= 14 zprava
<<= 14 zprava
>>= 14 zprava
&= 14 zprava
^= 14 zprava
|= 14 zprava

U těchto operátoru dojde nejprve k provedení operace, která se nachází před (=) a následně se tento výpočet přiřadí do hodnoty která je vlevo.

Příklad:

int cislo1 = 11;
int cislo2 = 2;
cislo1 %= cislo2; //zmena promennych: cislo1 = 1 a cislo2 = 2 (zustane stejne)

Porovnávací

OPERÁTOR POPIS PRIORITA ASOCIATIVITA
< menší než 6 zleva
> větší než 6 zleva
<= menší nebo rovno než 6 zleva
>= větší nebo rovno než 6 zleva
== rovnost 7 zleva
!= nerovnost 7 zleva

Logické

OPERÁTOR POPIS PRIORITA ASOCIATIVITA
<< bitový posun vlevo 5 zleva
>> bitový posun vpravo 5 zleva
& logický součet po bitech 8 zleva
^ nonekvivalence po bitech 9 zleva
| logický součin po bitech 10 zleva
&& logický součet 11 zleva
|| logický součin 12 zleva

Ternární operátory

Existuje pouze jeden operátor, který potřebuje 3 operandy a tím je Podmíněný výraz. Sdružuje zprava a priorita je 13. Zápis vypadá takto:

podminka ? hodnota1 : hodnota2

Nejprve se vyhodnotí podmínka, pokud je splněna (hodnota <> 0), výsledkem bude hodnota1, při nesplněné podmínce (hodnota = 0), výsledkem bude hodnota2.

Ostatní operátory

OPERÁTOR POPIS PRIORITA ASOCIATIVITA
() zavolání funkce 1 zleva
[] výběr hodnoty z pole 1 zleva
. výběr prvku ze struktury 1 zleva
-> výběr prvku ze struktury zadané pomocí ukazatele 1 zleva
, jednotné provedení výrazů 15 zleva

Operátor (,) čárka slouží k jednotnému provedení výrazů. Občas potřebujeme operaci kde je více výrazů, ale syntaxe dovoluje pouze jeden výraz. Operátor čárka tyto jednotlivé výrazy odděluje a v podstatě je sloučí do jednoho. Používá se například u cyklů.

Operandy

  • Proměnná – známe již z předchozí lekce
  • Konstanta – definují se pomocí direktivy preprocesoru #define nebo lze použít klíčové slovo const při deklaraci proměnné
  • Výpočet funkce – samotné zavolání funkce je možné použít jako operand, za podmínky, že funkce nemá návratový typ void

Vysvětlíme si používání konstantních operandů:

Konstanta pomocí  #define

  • obecný tvar = #define NAZEV hodnota
  • podle konvence by název konstanty měl být velkými písmeny
#include <stdio.h>
#define PI 3.14

int main(void)
{
    int polomer = 10;
    float objem;

    objem = 4.0/3.0 * PI * polomer;
    printf("Objem koule o polomeru %d je %.2f\n", polomer, objem);

    return 0;
}

Pomocí direktivy #define se dají vytvářet i makra:

#include <stdio.h>
#define PLUS(a,b) ((a)+(b))

int main(void)
{
    printf("Soucet = %d\n", PLUS(1,2));
    return 0;
}

Konstanta pomocí const

Pokud použijeme klíčové slovo const, tak jediná možnost jak proměnnou inicializovat je ihned při její deklaraci, pozdější inicializace není možná.

#include <stdio.h>

int main(void)
{
    const int cislo = 10;
    cislo = 15; //error
    printf("%d\n", cislo);
    return 0;
}

Program neprojde překladem, kvůli chybnému přiřazení do konstantní proměnné na řádku 6

main.c:6: error: assignment of read-only variable 'cislo'

To by bylo vše k této lekci o operátorech a operandech. Příště se naučíme pracovat s podmíněnými výrazy a cykly.

#2 Struktura kodu, datové typy a proměnna

V druhé lekci kurzu o programování v jazyce C se podíváme na strukturu zdrojového kódu, využívání komentářů ve zdrojovém kódu, datové typy a jak správně zapsat deklaraci proměnné a také jak se proměnná inicializuje. V bonusové lekci je popsáno jak využít Debugger v IDE Qt Creator.

Struktura kódu v C

Rozbor si ukážeme na kódu, který se vygeneruje při vytvoření nového projektu v Qt Creatoru.

Direktivy preprocesoru

V horní části (řádek #include <stdio.h>), zde se vkládají tzv. direktivy preprocesoru, každý řádek začínající „#“ je považován za direktivu preprocesoru. Existuje několik operací:

  • #define, #undef – slouží pro definování maker v jazyce C
  • #include – vkládá hlavičkové soubory (tyto soubory obsahují vestavěné funkce)
  • #if, #ifdef, #ifndef, #else, #elif, #endif – podmíněné příkazy, slouží k tomu, aby kompilátor přeložil určité části zdrojového kódu, daný právě těmito příkazy

Funkce main

Funkce main, jak už z názvu vyplývá, jedná se o hlavní funkci programu a je to funkce která se při spuštění programu zavolá jako první. Není možné ji smazat ani přejmenovat.

Obecná struktura funkce v jazyce C

navratovy_typ nazev_funkce (vstupni_parametry_funkce) 
{
	telo funkce
}
  • Popis návratových typů (resp. Datových typů) se nachází níže v tomto článku.
  • Název funkce – jednoznačný identifikátor, podle kterého je funkce volána
  • Vstupní parametry – seznam všech proměnných, se kterými se bude pracovat v dané funkci a jsou deklarována na jiném místě
  • Tělo funkce – zapsané ve složených závorkách, muže obsahovat lokální deklarace, posloupnost příkazů, které budou vyhodnoceny při zavolání funkce. Příkazy bývají zpravidla ukončeny středníkem, ale výjimku tvoří např.: složené příkazy

Rozbor na konkrétní funkci main:

  • int návratový typ – celé číslo (detailněji popsán níže)
  • název funkce – main
  • vstupní parametry – void = tento příkaz znamená, že funkce nemá žádné vstupní parametry
  • V těle funkce main najdeme 2 přikazy – printf a return

Funkce Printf

  • Funkce je deklarovaná v hlavičkovém souboru stdio.h
  • Funkce vypisuje na standardní výstup nekonečně mnoho hodnot, které mu zadáme, v tomto případě vypíše text:Hello World!, na konec textu vloží nový řádek, to značí příkaz \n

Ostatní příkazy, které jsou možné vložit do textu, jsou uvedený zpětným lomítkem (\)

Příkaz Popis
\n nový řádek
\t tabulátor
\“ uvozovky
\\ zpětné lomítko

Pokud se podíváme na deklaraci printf v hlavičkovém souboru tak zjistíme, že vypadá následovně:

int printf (const char *__format, ...)

Můžeme si všimnout, že funkce má návratový typ int, existují 2 typy výpisu:

  • Pří úspěšném = vrací počet znaků, které printf vypsal
  • Při neúspěšném = vrací zápornou hodnotu (typicky -1)

Dále vidíme, že vstupním parametrem je format a tři tečky značí, že parametrů může být nekonečně mnoho. Parametr format určuje, jakou hodnotu chcete vypsat, přesněji jaký datový typ má právě vypisovaná hodnota pomocí funkce printf. Ukážeme si to na příkladu, přikaz:

printf("Hello World!\n");

je možno zapsat také následujícím způsobem:

printf("%s","Hello World!\n");

Vidíme, že vnitřní parametry se rozdělily na 2 části oddělené čárkou, napravo jsou stále vstupní hodnoty, mohou byt zapsané i jako proměnné. Vlevo vidíme právě daný formát výstupu dané hodnoty. Počet těchto formátu se musí rovnat počtu vstupních hodnot, zde vidíme jak mužem vypsat více těchto hodnot v jednom příkazu printf:

printf("%s: %d","Cislo", 10);

Opět v první části vidíme výpis formátů, jsou zapsané všechny společně obalené do uvozovek, dále následuje vstup konkrétních hodnot, které chceme vypsat.

V této tabulce vidíte, jaké další možné formáty lze použít ve funkci printf:

specifikátor výstup příklad
d dekadické číslo se znaménkem -524
i dekadické číslo se znaménkem -524
u dekadické číslo bez znaménka 25
o oktalové číslo 76
x hexadecimální číslo (malé písmena) 3e
X hexadecimální číslo (velké písmena) 3E
f desetinné dekadické číslo 25.56
e vědecký zápis čísla (malé písmena) 3.9265e+2
E vědecký zápis čísla (velké písmena) 3.9265E+2
c znak a
s řetězec slovo

Příkaz return

Příkaz return vrací hodnotu, kterou daná funkce vypočítá, v jaké formě tato hodnota bude, určuje návratový typ funkce, v tomto případě int. Správný programátor by se měl držet určité konvence. U příkazu return ve funkci main, konvence říká:

  • rozsah hodnot, které příkaz return vrací: 0 – 255
  • return 0; = hodnota 0 se vrací, pokud chod programu byl úspěšný, jinak jakákoliv jiná hodnota v rozmezí 0 – 255, nepoužívají se záporné hodnoty

Příkaz return nemusí obsahovat každá funkce, ale musí být ve funkci main, Pokud vlastní funkce bude mít návratový typ void:

void moje_funkce (void)
{
    prikazy
}

Znamená to, že funkce nevrací žádny argument a nesmí obsahovat příkaz return, možná si někteří říkáte na co je funkce, která nevrací žádný vypočtený údaj, ale i takové funkce jsou užitečné, detailněji si takové funkce rozebereme v článku o funkcích v jazyce C.

Komentáře

Psaní komentářů je velice užitečné, kód se stává více přehledným. Pokud na daném projektu pracuje více programátoru, tak ať ostatní ví, co jste daným kódem chtěli vlastně říct, je dobré jej okomentovat. Využití je mnoho, takže se toho nebojte a určitě komentujte. Každá funkce by měla obsahovat popis (= komentář) co vlastně dělá.

Komentáře jdou zapsat dvěma způsoby:

  • Na jeden řádek
// komentář
  • Více řádkový
/* viceradkovy
komentar */

Příklad:

#include <stdio.h>
 
int main(void)
{
    printf("Hello World!\n"); //vypiste na strandardni vystup Hello World
    
    /*
    toto je vice radkovy komentar
    kod ktery je zde, prekladac preskakuje
    printf("Toto se nevypise");
    */
    
    return 0;
}

Datové typy

Celočíselné

Název rozsah velikost v paměti v Bytech
char (= signed char) -27 až 27 – 1 1
unsigned char 0 až 28 – 1 1
short int (= signed short int) -215 až 215 – 1 2
unsigned short int 0 až 216 – 1 2
int (= signed int) -231 až 231 – 1 4
unsigned int 0 až 232 – 1 4
long long int -263 až 263 – 1 8
unsigned long long int 0 až 264 – 1 8

signed = datový typ který rozlišuje kladná a záporná čísla

unsigned = tento příkaz určuje, že datový typ nepoužívá záporná čísla

  • typ short int lze zapsat jako short
  • typ int lze zapstat jako long int nebo i jako long
  • typ long long int lze zapsat jako long long
  • obdobně je to u typu, kde je použité slovo unsigned

Neceločíselné

název rozsah Počet desetinných míst velikost v paměti (v bytech)
float +/- 3.4 * 1038 8 4
double +/- 1.7 * 10308 16 8
long double +/- 1.7 * 10308 20 12

Řetězcový typ

Řetězcy se budeme zabývat v samostatné kapitole, společně s funkcemi které slouží pro práci s nimi.

Příkaz sizeof

Velikost paměti jednotlivých datových typů lze zjistit pomocí příkazu:

sizefof(datovy_typ)

Příklad:

#include <stdio.h>
int main(void)
{
    printf("%d\n", sizeof(int));
    return 0;
}

Velikost zabrané paměti se může lišit v závislosti na verzi systému, běžně int zabírá 4 B, ale na určitých operačních systémech může zabírat pouze 2 B.

Zápis proměnné

Deklarace proměnné

Obyčejná deklarace proměnné se skládá z datového typu a názvu proměnné:

int cislo;

Název proměnné může obsahovat znaky anglické abecedy a čísla a znak podtržítka (_). Jazyk C rozlišuje malá a velká písmena (= Case-sensitive)

int cislo; //promenna cislo
int Cislo; //promenna Cislo, ktera nema nic spolecneho s promenou cislo

Inicializace proměnné

Inicializace znamená přiřazení konkrétní hodnoty do proměnné. Je možné deklaraci a inicializaci proměnné provést zvláště:

int cislo;
cislo = 5;

nebo také společně na jeden řádek;

int cislo = 5;

Pokud bychom chtěli inicializovat maximalní možné číslo, které v jazyce C existuje tj. 264 – 1 kód by vypadal následovně:

#include <stdio.h>
int main(void)
{
    unsigned long long int cislo = 18446744073709551615LLU;
    printf("%llu\n", cislo);
}

U inicializace proměnné má číslo přidaný sufix LLU, jinak by kompilátor při překladu hlásil varování:

warning: integer constant is so large that it is unsigned

Shrnutí

Zde máme takový souhrn dnešní lekce a malý dodatek k funkci printf, jak vidíte na příkladu tak mezi % a formátem výstupu, jsou 2 čísla oddělené tečkou. První značí na kolikátou pozici se má vypsaná hodnota odsadit, druhé určuje přesnost výpisu (použitelné pouze u čísel s pohyblivou desetinou čárkou)

#include <stdio.h>
int main(void)
{
    int cislo = 15;
    int cislo2 = 215;
    float c_float = 0.12345678; //8
    double c_double = 0.1234567890123456; //16
    long double c_long_double = 0.12345678901234567890; //20

    printf("Velikost float: %d B\nVelikost double: %d B\nVelikost long double: %d B\n", sizeof(c_float), sizeof(c_double), sizeof(c_long_double));

    printf("%50.8f\n%50.16f\n%50.20lf\n", c_float, c_double, c_long_double);

    printf("%15d\n", cislo); //zarovna na 15. pozici zleva
    printf("%15d\n", cislo2); //zarovna na 15. pozici zleva
    printf("%15.10d\n", cislo); //zarovna na 15. pozici zleva pokud zadame presnot pro datovy typ int, nektere mezery se vyplni nulama
    
    char c1 = 'a';
    printf("%c - %d\n",c1,c1); //format c vypisuje znaky, pokud jej vypiseme jako cislo vrati znak a = 97 (podle pozice v ASCII tabulce)

    char c2 = 98;
    printf("%c - %d\n",c2,c2);

    return 0;
}

Jěště se podívejte na bonusový tutoriál, který se týká využití debuggeru ve IDE Qt Creator.

#1 Úvod do kurzu programování v jazyce C

Právě dnes na našem webu jakprogramovat.cz rozjíždíme nový kurz/tutoriál o programování v jazyce C. V tomto úvodním článku si řekneme několik základních parametrů a vlastností toho jazyka a také se podíváme blíže na vývojové prostředí, které se nejvíce hodí právě pro programování v C.

Jazyk C je jeden z nejpopulárnějších programovacích jazyků. Vznikl v roce 1972, autorem je Dennis Ritchie. Jazyk C je typický pro svou rychlost. Během vývoje vyšlo několik standardů, nejnovější je z prosince 2011 (c11). V tomto kurzu budeme používat standard z roku 1999 (nese označení c99) pro tyto výukové účely je přesně to co potřebujeme.

Charakteristika jazyka C

  • Imperativní = program se skládá z posloupnosti příkazů, které řeší danou úlohu
  • Procedurální = je možné zdrojový kód rozložit na menší části tzn. procedury, funkce
  • Staticky typovaný = typová kontrola probíhá v době překladu
  • Není objektově orientovaný
  • Překladač jazyka C je dnes už multiplatformní

Vývojové prostředí

Existuje mnoho programů, ve kterých lze programovat v jazyce C. Pro výukové účely, tedy i v tomto kurzu budeme používat vývojové prostředí QT Creator. Toto IDE umožňuje např .: zvýraznění syntaxe, lokalizace chyb, doplňování kódu a obsahuje také kontextovou nápovědu

Ostatní vhodné IDE:

  • Code::Blocks
  • Visual Studio
  • NetBeans
  • Eclipse

Programovat v C lze i za pomocí obyčejného poznámkového bloku a program následně sestavovat pomocí překladače GNU Compiler Collection (GCC). Osobně si myslím, že je vhodnější použít IDE.

Vytvoření programu v QT Creatoru

Návod na instalaci a nastavení QT Creatoru najdete ZDE.

Nyní si vytvoříme základní program v jazyce C – Hello World. Spusťte QT Creator File -> New File or Project (CTRL + N) -> Choose template: Non-Qt Project -> Plain C Application

Nový Projekt

Okno: Introduction and Project Location

Zvolíme název projektu a jeho umístění. Můžeme zaškrtnou Use as default project location pro uložení cesty pro další projekty.

Název projektu a umístění

V dalším kroku zvolíme Next.

Okno: Kit Selection

Platforma

Klikneme na Next a v dalším kroku na Finish a máme vytvořený čistý projekt jazyka C.

Po vytvoření nového projektu, máte před sebou zobrazen kód ze souboru main.c, Tento soubor se vždy vytvoří s novým projektem, je možné jej přejmenovat, pochopitelně příponu .c nesmíte měnit.

Kód našeho projektu vypadá následovně:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("Hello World!\n");
    return 0;
}

Nyní si zkusíme program spustit.  Nejprve je třeba program sestavit, vlevo dole klikneme na ikonu kladívka (CTRL + B). Žádná chyba ani varovné hlášení se nám nezobrazilo čili můžeme program spustit – zelená šipka vlevo dole (CTRL + R).

Výpis konzole:

 

Výpis konzole

Více se už do této lekce nevešlo. Pokud máte nějaký dotaz k článku, buď napište do komentář nebo mi pošlete osobní zprávu. V příští lekci se podíváme na základní prvky jazyka C (proměnné, datové typy, apod.), ukážeme si také jak se program ladí v QT Creatoru.