Přidej si svou IT školu do profilu a najdi spolužáky zde na síti :)

13. díl - Šablony

C a C++ C++ Pokročilé konstrukce v C++ Šablony

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

Někdy můžeme mít algoritmus, který je tak univerzální, že mu nevadí různé datové typy. Jedním z takových algoritmů může být dynamické pole, které jsme si ukázali v minulé lekci. Bez ohledu na to, jaké typy ukládáme, můžeme je všechny uložit do pole a v ideálním případě by práce s nimi měla být stejná. Dalším příkladem by mohl být algoritmus pro výměnu dvou proměnných. Dříve jsme v seriálu měli funkci swap, která přesně to dělala, ale pouze pro celá čísla. Opět se jedná o univerzální algoritmus, který můžeme použít pro každý typ.

Definice šablony

Pokud chceme vytvořit šablonu, použijeme klíčové slovo template a poté do ostrých závorek (tj. < a >) napíšeme zástupný symbol pro typ, před který přidáme klíčové slovo typename. Dále již pokračujeme známou deklarací funkce. Například pro funkci swap by syntaxe vypadala následovně:

template<typename T>
void swap(int &a, int &b)
{
     int tmp = a;
     a = b;
     b = a;
}

Prozatím jsme ve funkci nechali typy int. To za chvíli napravíme. Zápis "typename T" vlastně určuje parametr šablony - T. T zároveň zastupuje typ (který v tuto chvíli může být libovolný), který můžeme v následující deklaraci (v následující funkci) použít. Přepíšeme tedy naši funkci, aby byla univerzální.

template<typename T>
void Swap(T &a, T &b)
{
     T tmp = a;
     a = b;
     b = tmp;
}

Všimněte si, že jsme všechny výskyty int nahradili za T. Pokud šablonu budeme chtít použít, musíme jí v parametru nastavit typ, pro který se má vytvořit. Kompilátor poté na pozadí vygeneruje funkci, ve které nahradí všechny výskyty T námi specifikovaým typem.

Použití šablony

Parametr do šablony předáme pomocí ostrých závorek za názvem volané funkce. Nejlépe asi poslouží ukázka:

     int cislo1 = 10;
     int cislo2 = 5;
     cout << "Cislo1: " << cislo1 << ", cislo2: " << cislo2 << endl;
     Swap<int>(cislo1, cislo2);
     cout << "Cislo1: " << cislo1 << ", cislo2: " << cislo2 << endl;
     char znak1 = 'a';
     char znak2 = 'z';
     cout << "Znak1: " << znak1 << ", znak2: " << znak2 << endl;
     Swap<char>(znak1, znak2);
     cout << "Znak1: " << znak1 << ", znak2: " << znak2 << endl;
Konzolová aplikace
Cislo1: 10, cislo2: 5
Cislo1: 5, cislo2: 10
Znak1: a, znak2: z
Znak1: z, znak2: a

Vidíme, že nezávisle na typu se hodnoty prohodili. Ve většině případů si kompilátor umí parametr šablony vydedukovat, a tak jej nemusíme specifikovat. Explicitně nastavovat typ je nutné pouze v případech, kdy není typ jednoznačně určen (například z parametrů).

     int cislo1 = 10;
     int cislo2 = 5;
     cout << "Cislo1: " << cislo1 << ", cislo2: " << cislo2 << endl;
     Swap(cislo1, cislo2); //bude fungovat, typ se vydedukuje z parametrů
     cout << "Cislo1: " << cislo1 << ", cislo2: " << cislo2 << endl;

Jak ylo řečeno, kompilátor na pozadí vygeneruje funkci, která již bude obsahovat reálně typy. Pokud chceme do parametru funkce zadat typ, který není shodný s parametrem šablony, musí existovat konverze. Například následující kód se nezkompiluje, protože nelze implicitně převést double na float.

     float cislo1 = 10.5;
     double cislo2 = 5.4;
     cout << "Cislo1: " << cislo1 << ", cislo2: " << cislo2 << endl;
     Swap<float>(cislo1, cislo2); //nebude fungovat, nelze double převést na float
     cout << "Cislo1: " << cislo1 << ", cislo2: " << cislo2 << endl;

Univerzální dynamické pole

Nyní si již napíšeme naše dynamické pole, které bude univerzální pro všechny typy. Před funkci přidáme template<typename T> a všechny výskyty int nahradíme univerzálním T. Všimněte si, že se za typ T změnila i návratová hodnota. Pouze počet prvků a kapacitu pole musíme ponechat jako celé čísla, protože tyto hodnoty nezávisle na typu pole fyzicky znamenají čísla.

template<typename T>
T* pridej_prvek(T prvek, T* pole, int &pocet_prvku, int &kapacita)
{
     //vytvarime nove pole
     if(pole == NULL)
     {
          T* vytvoreno = new T[10];
          kapacita = 10;
          vytvoreno[0]=prvek;
          pocet_prvku=1;
          return vytvoreno;
     }
     //pole je jiz plne - musime ho zvetsit
     if(kapacita == pocet_prvku)
     {
          T* vytvoreno = new T[kapacita*2]; // vytvoření nového pole
          kapacita = kapacita * 2;
          for(int i=0;i<pocet_prvku;i++)
               vytvoreno[i]=pole[i]; //zkopírování původního pole
          delete [] pole;
          vytvoreno[pocet_prvku]=prvek;
          pocet_prvku++;
          return vytvoreno;
     }
     //je dostatek mista
     pole[pocet_prvku]=prvek;
     pocet_prvku++;
     return pole;
}

Použití je velice snadné a díky automatické dedukci typů si ani nevšimneme, že používáme nějakou šablonu.

     int* pole_cisel = NULL;
     int pocet_cisel, kapacita_cisel;
     char* pole_znaku = NULL;
     int pocet_znaku, kapacita_znaku;
     for(char i='a'; i<'z'; i++)
          pole_znaku = pridej_prvek(i, pole_znaku, pocet_znaku, kapacita_znaku);
     for(int i=0; i<25; i++)
          pole_cisel = pridej_prvek(i * 3, pole_cisel, pocet_cisel, kapacita_cisel);
     cout << "Cisla: ";
     for(int i=0; i<pocet_cisel; i++)
           cout << pole_cisel[i] << " ";
     cout << endl << "Znaky: ";
     for(int i=0; i<pocet_znaku; i++)
          cout << pole_znaku[i] << " ";
Konzolová aplikace
Cisla: 0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72
Znaky: a b c d e f g h i j k l m n o p q r s t u v w x y

Parametr specifického typu

Jako parametr si také můžeme vyžádat specifický typ, například číslo. Šablona poté bude přijímat jako parametr hodnotu. Například u našeho dynamického pole bychom chtěli mít možnost nastavit, jak velké pole se bude vytvářet při inicializaci. Do šablony přidáme další parametr, který bude typu int a bude udávat velikost nově vytvořeného pole.

template<typename T, int VELIKOST>
T* pridej_prvek(T prvek, T* pole, int &pocet_prvku, int &kapacita)
{
     //vytvarime nove pole
     if(pole == NULL)
     {
          T* vytvoreno = new T[VELIKOST];
          kapacita = VELIKOST;
          vytvoreno[0]=prvek;
          pocet_prvku=1;
          return vytvoreno;
     }
//... zbytek implementace
int* pole_cisel = NULL;
int pocet_cisel, kapacita_cisel;
pole_cisel = pridej_prvek<int, 13>(0, pole_cisel, pocet_cisel, kapacita_cisel);
cout << "Velikost pole: " << kapacita_cisel << endl;

Má to jednu nevýhodu. Nyní musíme psát všechny parametry šablony, protože kompilátor nedokáže automaticky vydedukovat hodnotu druhého parametru. Můžeme ovšem nastavit výchozí hodnotu a tím tento problém vyřešit. Do parametrů šablony jsem ještě přidal hodnotu, která určuje zvětšení pole, pokud se pole naplní.

template<typename T, int VELIKOST = 8, int ZVETSENI = 2>
T* pridej_prvek(T prvek, T* pole, int &pocet_prvku, int &kapacita)
{
     //vytvarime nove pole
     if(pole == NULL)
     {
          T* vytvoreno = new T[VELIKOST];
          kapacita = VELIKOST;
          vytvoreno[0]=prvek;
          pocet_prvku=1;
          return vytvoreno;
     }
     //pole je jiz plne - musime ho zvetsit
     if(kapacita == pocet_prvku)
     {
          T* vytvoreno = new T[kapacita * ZVETSENI];
          kapacita = kapacita * ZVETSENI;
          for(int i=0;i<pocet_prvku;i++)
               vytvoreno[i]=pole[i]; //zkopírování původního pole
          delete [] pole;
          vytvoreno[pocet_prvku]=prvek;
          pocet_prvku++;
          return vytvoreno;
     }
     //je dostatek mista
     pole[pocet_prvku]=prvek;
     pocet_prvku++;
     return pole;
}

int main(){
     int* pole_cisel = NULL;
     int pocet_cisel, kapacita_cisel;
     //výhozí hodnoty
     pole_cisel = pridej_prvek<int>(0, pole_cisel, pocet_cisel, kapacita_cisel);
     cout << "Kapacita<int>: " << kapacita_cisel << ", pocet<int>: " << pocet_cisel << endl;
     for(int i=0; i<8;i++)
         pole_cisel = pridej_prvek<int>(i*2, pole_cisel, pocet_cisel, kapacita_cisel);
     cout << "Kapacita<int>: " << kapacita_cisel << ", pocet<int>: " << pocet_cisel << endl;
     //uvolnění paměti
     delete [] pole_cisel; pole_cisel = NULL;
     //výchozí velikost pole bude 10
     pole_cisel = pridej_prvek<int, 10>(0, pole_cisel, pocet_cisel, kapacita_cisel);
     cout << "Kapacita<int, 10>: " << kapacita_cisel << ", pocet<int, 10>: " << pocet_cisel << endl;
     //zvětšení pole zůstane na výchozích 1.5
     for(int i=0; i<10;i++)
          pole_cisel = pridej_prvek<int, 10>(i*2, pole_cisel, pocet_cisel, kapacita_cisel);
     cout << "Kapacita<int, 10>: " << kapacita_cisel << ", pocet<int, 10>: " << pocet_cisel << endl;
     //nyní se pole zvětší 4x
     for(int i=0; i<10;i++)
          pole_cisel = pridej_prvek<int, 10, 4>(i*3, pole_cisel, pocet_cisel, kapacita_cisel);
     cout << "Kapacita<int, 10, 4>: " << kapacita_cisel << ", pocet<int, 10, 4>: " << pocet_cisel << endl;
     return 0;
}

Pozn.: Výchozí hodnoty parametrů šablon jsou podporovány až ve standardu C++11, nemusí tedy fungovat u všech kompilátorů nebo musí být nutné zapnout standard C++11.

Konzolová aplikace
Kapacita<int>: 8, pocet<int>: 1
Kapacita<int>: 16, pocet<int>: 9
Kapacita<int, 10>: 10, pocet<int, 10>: 1
Kapacita<int, 10>: 20, pocet<int, 10>: 11
Kapacita<int, 10, 4>: 80, pocet<int, 10, 4>: 21

To by bylo pro tuto lekci vše. Příště ještě u šablon zůstaneme a řekneme si o pokročilejších technikách, které nám šablony nabízejí.


 

 

Článek pro vás napsal patrik.valkovic
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Miniatura
Předchozí článek
Dynamická alokace polí
Miniatura
Všechny články v sekci
Pokročilé konstrukce C++
Miniatura
Následující článek
Šablony - pokračování
Aktivity (2)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!