IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

2. diel - Implementácia zásobníka v jazyku C

V minulej lekcii, Úvod do kolekcií a dátových štruktúr v jazyku C , sme si uviedli kolekcie a implementovali zásobník (stack).

Dnes dokončíme našu implementáciu zásobníka (LIFO).

Naša jednoduchá implementácia zásobníka z minula používala globálnej premennej header, ktorá ukazovala na vrchol zásobníka. Hovorili sme si, že to nie je úplne šťastné riešenie a že kolekciu dnes ešte vylepšíme. Najskôr ale vysvetlenie, prečo je používanie globálne premenné header nesprávne.

Problém globálne premenné

Ak by sme náš zásobník používali takto, fungoval by, ale mohli by sme v každom programe použiť len jeden zásobník. Pre pochopenie princípu kolekcia to stačilo, ale poďme to urobiť lepšie. Vrchol zásobníka si budeme ukladať lokálne a každej funkcii ho odovzdáme parametrom. Tak zásobníkov môžeme mať neobmedzené množstvo.

push()

Funkciu push() zmeníme nasledovne:

struct ITEM* push( struct ITEM **header, int new_number)
{
    struct ITEM* new_item = (struct ITEM*)malloc(sizeof(struct ITEM));
    if (new_item == NULL)
        return NULL;
    new_item->number = new_number;
    new_item->next = NULL;
    new_item->next = *header;
    *header = new_item;
    return new_item;
}

Funkciu pribudol jeden parameter, header. Tento parameter je v podstate reprezentant zásobníku, iba cez neho sa k zásobníku dostaneme. V pamäti vyzerá situácia nasledovne:

Parameter header zásobníka v jazyku C - Dátové štruktúry v jazyku C

Tie dve hviezdičky pred parametrom sú síce zvláštne, ale hovoria, že je to ukazovateľ na ukazovateľ na položku ITEM. Ak je pre vás syntaxe ukazovateľov cudzie, veľmi odporúčam prvýkrát prejsť kurz Dynamická práca s pamäťou v jazyku C.

Reťazenie ukazovateľov je tu nutné, pretože vrchol zásobníka sa vo funkciách push() a pop() mení, keď sa položka pridáva alebo odoberá. A keby sme si odovzdali iba ukazovateľ na hodnotu vrchole, nemohli by sme samotný lokálny ukazovateľ, ktorý používa používateľ kolekcie, upraviť, a ukazoval by na starú adresu.

Okrem parametra sa zmenili iba dva riadky, v ktorých pribudli dve hviezdičky.

pop()

Vo funkcii pop() zas pribudol iba jeden nový parameter, štyri hviezdičky a dve zátvorky:

struct ITEM* pop( struct ITEM **header )
{
    struct ITEM* old_item;
    if (*header == NULL)
        return NULL;
    old_item = *header;
    *header = (*header)->next;
    return old_item;
}

Ide len o to, že parameter ukazuje na ukazovateľ na vrchol zásobníka.

peek()

Podobne vo funkcii peek() je len jednoduchá úprava:

struct ITEM* peek( struct ITEM *header )
{
    return header;
}

Ono to vyzerá absurdne, že vrátime to, čo posielame, v neskorších lekciách si to vysvetlíme. Keďže funkcia peek() v zásobníku nič nemení, stačí nám len odkaz na vrchol zásobníka.

print_collection()

V print_collection() pribudne parameter, ukazovateľ na vrchol zásobníka. Podobne ako peek() funkcia nič nemení, tak stačí len odkaz na vrchol zásobníka:

void print_collection( struct ITEM* header)
{
    int i = 1;
    struct ITEM* item;
    printf("PRINT COLLECTION START:\r\n");
    item = header;
    while (item != NULL)
    {
        printf("%2d: %4d\r\n", i, item->number);
        i++;
        item = item->next;
    }
    printf("PRINT COLLECTION END.\r\n");
}

main()

Ale vo funkcii main() toho musíme zmeniť veľa. Miesto ukazovatele na vrchol zásobníka totiž musíme zadať adresu ukazovatele na vrchol zásobníka:

int main()
{
    struct ITEM* zasobnik = NULL;
    struct ITEM* polozka = NULL;
    push( &zasobnik, 11 );
    push( &zasobnik, 7 );
    push( &zasobnik, 8 );
    print_collection(zasobnik);
    polozka = pop(&zasobnik);
    if( polozka != NULL)
    {
        printf("Item from stack: %d\r\n", polozka->number);
        free(polozka);
    }
    else
    {
        printf("Stack is empty\r\n");
    }
    push(&zasobnik, -88);
    push(&zasobnik, 100);
    print_collection(zasobnik);
    free( pop(&zasobnik) );
    pop(&zasobnik);
    print_collection(zasobnik);
    free( pop(&zasobnik) );
    free(pop(&zasobnik));
    if( pop(&zasobnik) == NULL )
    {
        printf("Stack is empty\r\n");
    }
    print_collection(zasobnik);
    getchar();
    return 0;
}

Premenná zasobnik je ukazovateľ na vrchol zásobníka. Do funkcií push(), pop() a print_collection() odovzdávame adresu tohto ukazovateľa &zasobnik. Takto si môžeme vytvoriť ľubovoľné množstvo zásobníkov a pristupovať k nim.

Nové funkcie

Naši implementáciu zásobníka môžeme dokončiť ešte pridaním dvoch jednoduchých, ale užitočných funkcií.

empty()

Funkcia empty() vráti 1, ak je zásobník prázdny a 0, ak má aspoň jednu položku:

int empty(struct ITEM* header )
{
    if (header == NULL)
        return 1;
    else
        return 0;
}

length()

Funkcia length() vráti počet položiek v zásobníku:

int length(struct ITEM* header)
{
    int counter_item = 0;
    struct ITEM* item;
    item = header;
    while (item != NULL)
    {
        counter_item++;
        item = item->next;
    }
    return counter_item;
}

Funkcia prechádza od vrcholu zásobníka po jeho dno a pri každom priechode zvýši counter_item o jedna. Nakoniec funkcie counter_item vráti ako návratovú hodnotu.

Zásobník je k stiahnutiu v prílohe nižšie.

V budúcej lekcii, Implementácia fronty v jazyku C , budeme implementovať jednoduchú front v jazyku C.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 41x (2.02 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C

 

Predchádzajúci článok
Úvod do kolekcií a dátových štruktúr v jazyku C
Všetky články v sekcii
Dátové štruktúry v jazyku C
Preskočiť článok
(neodporúčame)
Implementácia fronty v jazyku C
Článok pre vás napísal Daniel Martinko
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Aktivity