IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 9 - Statika v C++

V předešlém cvičení, Řešené úlohy k 6. až 8. lekci OOP v C++, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

V dnešním C++ tutoriálu se budeme věnovat pojmu statika. Až doposud jsme byli zvyklí, že data (stav) nese instance. Atributy, které jsme definovali, tedy patřily instanci a byly pro každou instanci jedinečné (každý bojovník měl své životy). OOP však umožňuje definovat atributy a metody na samotné třídě. Těmto prvkům říkáme statické (někdy třídní) a jsou společné pro všechny instance.

POZOR! Dnešní lekce vám ukáže statiku, tedy postupy, které v podstatě narušují objektový model. OOP je obsahuje jen pro speciální případy a obecně platí, že vše jde napsat bez statiky. Vždy musíme pečlivě zvážit, zda statiku opravdu nutně potřebujeme. Obecně bych doporučoval statiku vůbec nepoužívat, pokud si nejste naprosto jisti, co děláte. Podobně jako globální proměnné, je statika v objektovém programování něco, co umožňuje psát špatný kód a porušovat dobré praktiky. Dnes si ji spíše vysvětlíme, abyste pochopili určité metody a třídy, které ji používají. Znalosti použijte s rozvahou, na světe bude potom méně zla.

Statické (třídní) atributy

Jako statické můžeme označit různé prvky. Začněme u atributů. Jak jsem se již v úvodu zmínil, statické prvky patří třídě, nikoli instanci. Data v nich uložená tedy můžeme číst bez ohledu na to, zda nějaká instance existuje. V podstatě můžeme říci, že statické atributy jsou společné pro všechny instance třídy, ale není to přesné, protože s instancemi doopravdy vůbec nesouvisí. Například bychom vyžadovali, aby délka jména byla alespoň 4 znaky. Tato konstanta by neměla být vložena přímo v kódu, ale měli bychom si ji někde uchovat (pokud bychom ji chtěli později změnit). Uchováme ji právě jako statický atribut. Syntaxe je stejná jako u atributu, pouze před něj přidáme klíčové slovo static.

Hrac.h

class Hrac
{
private:
    Bojovnik bojovnik;
    string jmeno;
public:
    static int MIN_DELKA_JMENA;
    Hrac(string jmeno, Kostka &kostka);
    string getJmeno();
    Bojovnik& getBojovnik();
};

Hrac.cpp

int Hrac::MIN_DELKA_JMENA = 4;

Hrac::Hrac(string jmeno, Kostka &kostka) : bojovnik(100, 8, 5, kostka)
{
    if (jmeno.length() < MIN_DELKA_JMENA)
        exit(1);
    this->jmeno = jmeno;
}

Všimněte si rozdělení deklarace a inicializace. Deklarace je v souboru Hrac.h a obsahuje klíčové slovo static. Inicializace musí být v .cpp souboru a již slovo static nepoužívá. Nyní máme statický atribut, který ale jde měnit. Protože se jedná o konstantu, měli bychom ji jako konstantu označit. Pro konstanty musí být deklarace a inicializace v .h souboru (část v Hrac.cpp smažeme).

Hrac.h

class Hrac
{
private:
    Bojovnik bojovnik;
    string jmeno;
public:
    static const int MIN_DELKA_JMENA = 4;
    Hrac(string jmeno, Kostka &kostka);
    string getJmeno();
    Bojovnik& getBojovnik();
};

Protože se často jedná o zdroj chyb, rychlé zopakování: konstantní statické atributy musí být inicializované v .h souborech, zatímco pouze statické atributy musí být inicializované v .cpp souborech.

Samotné jméno statického atributu (stejně jako u klasických atributů) můžeme použít pouze v metodách třídy (MIN_DELKA_JMENA). Pokud bychom konstantu chtěli použít například ve funkci main(), musíme ji specifikovat i s názvem třídy (Hrac::MIN_DELKA_JMENA).

Pozn.: Stejný zápis bychom mohli použít i uvnitř třídy Hrac, záleží na programátorovi.

cout << "Min delka jmena: " << Hrac::MIN_DELKA_JMENA << endl;

Statické metody

Statické metody se volají na třídě. Jedná se zejména o pomocné metody, které potřebujeme často používat a nevyplatí se nám tvořit instanci. Ukažme si opět reálný příklad. Při vytváření arény se v konstruktoru ptáme na jména. To je špatný přístup - konstruktor by měl sloužit jen a pouze k vytvoření instance. Jakákoliv další logika (jako například získávání vstupů) je nežádoucí. Musím na to upozornit, protože jsem se několikrát setkal s porušením tohoto pravidla. Například konstruktor spouštěl samotný algoritmus, který třída reprezentovala. To je z hlediska OOP i obecných praktik naprosto špatně. Konstruktor má pouze vytvořit instanci, nic víc. Jak opravíme naši Arénu? Nejdříve změníme konstruktor tak, aby pouze přijímal pole jmen a jejich počet. A samozřejmě dodáme statickou metodu, která se zeptá na jména a arénu pro nás vytvoří.

Nejdříve upravíme hlavičkový soubor Arena.h - musíme změnit parametry konstruktoru a přidat deklaraci statické metody.

Arena.h

#ifndef __ARENA_H_
#define __ARENA_H_
#include "Hrac.h"
#include "Kostka.h"

class Arena
{
private:
    Hrac** hraci;
    int pocet_hracu;
    int tah;
    bool existujeVitaz();
    int pocetZivych();
public:
    Arena(string* jmena, int pocet_hracu, Kostka &kostka); // upraveno
    ~Arena();
    void vypis();
    void vypisViteze();
    void zapas();
    static Arena* vytvorArenu(int pocet_hracu, Kostka &kostka); // statická metoda
};
#endif

Nyní musíme změnit implementaci konstruktoru a naimplementovat naši statickou metodu:

Arena.cpp

// ...další implementace
Arena::Arena(string* jmena, int pocet_hracu, Kostka &kostka) : tah(1)
{
    this->pocet_hracu = pocet_hracu;
    this->hraci = new Hrac*[pocet_hracu];
    for (int i = 0; i < pocet_hracu; i++)
        this->hraci[i] = new Hrac(jmena[i], kostka);

}

Arena* Arena::vytvorArenu(int pocet_hracu, Kostka &kostka)
{
    string* jmena = new string[pocet_hracu]; // vytvoříme si pole pro jména
    for (int i = 0; i < pocet_hracu; i++)
    {
        cout << "Zadejte jmeno hrace: ";
        cin >> jmena[i];
    }
    Arena* arena = new Arena(jmena, pocet_hracu, kostka);
    delete[] jmena; // toto pole nesmíme zapomenout smazat
    return arena;
}

A to je vše :). Zbývá již jen upravit soubor main.cpp, aby používal naši novou statickou metodu:

main.cpp

#include <iostream>
#include "Kostka.h"
#include "Arena.h"
using namespace std;


int main()
{
    Kostka kostka;
    Arena* arena = Arena::vytvorArenu(3, kostka); // volání statické metody
    arena->zapas();
    arena->vypis();
    arena->vypisViteze();
    cin.get(); cin.get();
    return 0;
}

Stejně jako u statického atributu, přistupujeme k metodě pomocí názvu třídy, dvou dvojteček a názvu metody.

Oprava kostky

Nyní, když už umíme pracovat se statickými atributy, můžeme opravit naši třídu Kostka. Že nemáme co opravovat? Zkuste si spustit následující kód:

int main()
{
    Kostka kostka1;
    for (int i = 0; i < 10; i++)
        cout << kostka1.hod() << " ";
    cout << endl;
    Kostka kostka2;
    for (int i = 0; i < 10; i++)
        cout << kostka2.hod() << " ";
    cout << endl;
    return 0;
}
#ifndef __KOSTKA_H__
#define __KOSTKA_H__
using namespace std;

class Kostka
{
private:
    int pocet_sten;
public:
    Kostka();
    Kostka(int pocet_sten);
    int hod();
    int getPocetSten();
};
#endif
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "Kostka.h"

using namespace std;
Kostka::Kostka() : Kostka(6)
{
}

Kostka::Kostka(int pocet_sten)
{
    this->pocet_sten = pocet_sten;
    srand((unsigned int)time(NULL));
}

int Kostka::hod()
{
    return rand() % this->pocet_sten + 1;
}

int Kostka::getPocetSten()
{
    return this->pocet_sten;
}

Uvidíte, že obě kostky naházely stejné hodnoty. Jak je to možné? V konstruktoru kostky jsme změnili počáteční bod generátoru (viz. lekce o konstruktorech v C++). Ale hodnota funkce time(), kterou používáme pro inicializaci, se mění pouze jednou za sekundu. Při vytvoření druhé kostky se tedy znovu zavolal konstruktor a znovu se nastavil počáteční bod generátoru na stejnou hodnotu, jako tomu bylo u první kostky. Jak z toho ven? Pro celou aplikaci nám stačí, když inicializace proběhne pouze jednou. Vytvoříme si tedy (privátní) statický atribut, který nám bude říkat, jestli jsme inicializaci provedli. Na základě této proměnné budeme inicializovat pouze jednou.

// Kostka.h
class Kostka
{
private:
    int pocet_sten;
    static bool inicializovano;
public:
    Kostka();
    Kostka(int pocet_sten);
    int hod();
    int getPocetSten();
};
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "Kostka.h"
bool Kostka::inicializovano = false;

Kostka::Kostka(int pocet_sten)
{
    this->pocet_sten = pocet_sten;
    if (!Kostka::inicializovano)  // nebo if(!inicializovano)
    {
        srand((unsigned int)time(NULL));
        inicializovano = true; // nebo Kostka::inicializovano = true;
    }
}

Kostka::Kostka() : Kostka(6)
{
}

int Kostka::hod()
{
    return rand() % this->pocet_sten + 1;
}

int Kostka::getPocetSten()
{
    return this->pocet_sten;
}
#include <iostream>
#include "Kostka.h"
using namespace std;
int main()
{
    Kostka kostka1;
    for (int i = 0; i < 10; i++)
        cout << kostka1.hod() << " ";
    cout << endl;
    Kostka kostka2;
    for (int i = 0; i < 10; i++)
        cout << kostka2.hod() << " ";
    cout << endl;
    return 0;
}

Nyní už program funguje správně. Tím máme statiku ukončenou.

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


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 56x (9.76 kB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

Předchozí článek
Řešené úlohy k 6. až 8. lekci OOP v C++
Všechny články v sekci
Objektově orientované programování v C++
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 9. lekci OOP v C++
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
25 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity