9. seminář - Celková koncepce programu

TEACHINGZPC2

Návratová hodnota funkce main

Již víme, že main je funkce kterou volá operační systém po spuštění programu. Návratová hodnota typu int je určena pro operační systém který je touto hodnotou informován o případné chybě. Pokud funkce main vrátí 0, tak to znamená že v programu nenastala žádná chyba. Jíné hodnoty reprezentují kódy jednotlivých chyb.

Parametry funkce main

Funkce main může mít žádný, nebo dva formální parametry. Jmenují se argc (argument count) a argv (argument vector) a definují se následovně:


    int main(int argc, char* argv[]);

Tyto argumenty předá operační systém, my je můžeme specifikovat v příkazové řádce:

    
   ./program.exe -c 10 "text s mezerou"

Hodnota argc bude rovna 4 – počet předaných argumentů včetně názvu programu. V argv bude uloženo:

    
    argv[0] = "./program.exe"; 
    argv[1] = "-c";
    argv[2] = "10";
    argv[3] = "text s mezerou";

Na prvním místě je uložen název programu. Ostatní argumenty jsou odděleny mezerou. Pokud chceme mezeru jako součást argumentu, přidáme uvozovky.

Čísla jako argumenty

Pozor na to, že i čísla se předávají jako řetězec. Pokud chceme použít číslo opravdu jako číslo, je nutné ho zkonvertovat pomocí funkcí atoi ("array to integer") a atof ("array to float"). Jsou v knihovně stdlib.h. Deklarace funkcí:


    double atof (const char* str);
    int atoi (const char* str);
    
    // Příklad 
    int number = atoi("245") + atoi("55"); // 300
    double double_number = atof("245.47") - atof("0.47"); // 245

Koncepce programu

Zatím jsem vytvářeli jeden program v jednom souboru, což je nepraktické. V praxi dělíme různé části programu do samostatných souborů. V podstatě už to znáte: v knihovně stdio jsou funkce pro vstup/výstup, v string jsou funkce pro práci s řetězci atp.

Otázka zní, jakým způsobem můžeme vytvářet podobné knihovny?

Rozdělení do více souborů

Někoho by mohlo napadnout rozdělit funkce do dvou soborů main.c a functions.c a pak tento soubor pomocí již známého #include vložíme do souboru main.c.

functions.c

    int sum(int a, int b) {
        return a + b;
    }

main.c

    #include <stdio.h>
    // Obsah souboru functions.c by se vložil na místo řádku s
    //  #include "functions.c"
    #include "functions.c"
    
    int main() {
        printf("2 + 3 = %i\n", sum(2, 3));
        return 0;
    }

Soubor by tedy vypadal následovně:


    #include <stdio.h>
    int sum(int a, int b) {
        return a + b;
    }
    
    int main() {
        printf("2 + 3 = %i\n", sum(2, 3));
        return 0;
    }

Tohle je ale špatné řešení! Chceme docítil odděleného překladu – abychom byli schopni upravit soubor functions.c, aniž bychom museli nutně přeložit soubor main.c.

Hlavičkový soubor

Místo vkládání souboru se zdrojovým kódem používáme hlavičkové .h soubory. Vytvoříme soubor functions.h, do kterého vložíme deklarace funkcí, které jsou v souboru functions.c, které chceme použít:

functions.h

    int sum(int, int);

Pomocí hlavičkového souboru říkáme překladači, jaké funkce mohou být použity ale jejích definice budou dodáný "pozdějí". Naše souboty tedy vypadají následovně:

functions.c

     int sum(int a, int b) {
        return a + b;
    }

functions.h

    int sum(int, int);

main.c

    #include <stdio.h>
    #include "functions.h"
    
    int main() {
        printf("2 + 3 = %i\n", sum(2, 3));
        return 0;
    }

Jak zabránit vícenásobnému vložení

Může se stát, že dva různé soubory vkládají jednu a tutéž knihovnu, což je obecně nežádoucí. K zabránění druhého vložení můžeme použít trik s #ifndef v hlavičkovém souboru. Můžeme modifikovat soubor functions.h:


    #ifndef FUNCTIONS_H
        #define FUNCTIONS_H
        int sum(int, int);
    #endif

Oddělený překlad pomocí objektových souborů

Na obrázku níže je znázorněno jak probíhá překlad. Ze souborů se zdrojovými kódy .c se vytvoří objektový soubor .obj A tyto objektové soubory jsou sloučeny do jednoho spustitelného souboru. Pokud uděláme změnu v souboru functions.c, tak pak jen stačí zkompilovat functions.c a functions.h do souboru fucntions.obj a poté zavolat Linker, aby sloučil nově zkompilovanou část programu a původní.

Source code compilation

Oddělený překlad v příkazovém řádku

Oddělený překlad provádí většina vývojových prostředí automaticky. Podíváme se tedy na oddělený překlad pomocí příkazové řádky a překladače gcc.


    gcc -c main.c
    gcc -c functions.c
    gcc main.o functions.o -o program
    ./program
    2 + 3 = 5 

Poznámky na konec

  • V hlavičkovém souboru nemusí být pomocné funkce a obecně funkce, které nechceme sdílet.
  • V hlavičkovém souboru mohou být i další věci – typy definované přes typedef, které chceme veřejně sdílet, konstanty apod.
  • Pokud např. fractions.c pracuje s typem Fraction a my tento typ chceme použít i v souboru main.c, musí být typ Fraction definován už v fractions.h.
  • Samotný soubor fractions.h může vložit (pomocí #include) svůj hlavičkový soubor.

Úkoly

Napište v jazyku C program pro výpočet objemu a povrchu válce, pravidelného trojbokého, čtyřbokého a šestibokého hranolu. Parametry výpočtu by mělo být možné předávat programu při spuštění z příkazové řádky. Zdrojový kód programu by měl být rozdělen do 2 modulů. Modul hlavní funkce (soubor main.c) bude zajišťovat zpracování a případně načtení chybějící parametrů výpočtu, budou z něj volány funkce zajišťující vlastní výpočet a vypisovány vypočítané hodnoty na obrazovku. Druhý modul (soubory geom.h a geom.c) pak bude zajišťovat veškeré požadované výpočty.

Příklad použití:


    geometry.exe 0 1.2 2.4 (OS Windows)

    ./geometry 0 1.2 2.4 (OS Linux)
    
    // Výstup
    Valec s vyskou 1.2 a polomerem podstavy 2.4 ma povrch 54.2592 a objem 21.7037. 

V jazyce C implementuje strukturu pro zásobník včetné funkcí pro operace se zásobníkem. Implementaci zásobníku vložte do samostatného souboru a použite ji v souboru main.c, kde navíc implementujete funkci check_brackets s následující hlavičkou:

    int check_brackets(char *math_expression)

Tato funkce zkontroluje, zda v matematickém výrazu jsou správně použity závorky. Pokud ano tak funkce vrací 1 jinak 0.

Příklad


    check_brackets("[(1 + 3) * (1 + (2 + 4))]") // -> return 1
    check_brackets("[(1 + 3)] * (1 + (2 + 4})]") // -> return 0