Programovanie (1) v C/C++
1-INF-127, ZS 2024/25

Úvod · Pravidlá · Prednášky · Softvér · Testovač
· Kontaktujte nás pomocou e-mailovej adresy E-prg.png (bude odpovedať ten z nás, kto má príslušnú otázku na starosti alebo kto má práve čas).
· Prosíme študentov, aby si pravidelne čítali e-maily na @uniba.sk adrese alebo aby si tieto emaily preposielali na adresu, ktorú pravidelne čítajú.


Prednáška 23: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
(Vytvorená stránka „==Oznamy== Koniec semestra: * '''Dnešná prednáška''': informácia o skúškach, detaily skúšky z programovania, opakovanie, vaše ot…“)
 
 
(25 medziľahlých úprav od 2 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
==Oznamy==
+
== Nepreberané črty jazykov C a C++ ==
Koniec semestra:
 
* '''Dnešná prednáška''': informácia o skúškach, [[Zimný semester, skúška|detaily skúšky z programovania]], opakovanie, vaše otázky
 
* '''Doplnkové cvičenia tento piatok:''' ako obvykle, bonusová rozcvička nebude
 
* '''Pondelok prednáška:''' nepreberané črty C resp. C++. Nebudeme skúšať, ale môžu sa zísť (môžete použiť aj na skúške). Dohadovanie termínu opravnej písomky.
 
* '''Budúcu stredu prednáška nebude'''
 
* '''Cvičenia budúci utorok''': už dnes po prednáške sa na testovači objavia tréningové príklady na skúšku. Za niektoré budete môcť získať bonusový bod, ak ich vyriešite do 6.1. (ako tréning sa dajú riešiť aj neskôr). V utorok na cvičeniach pribudne ešte jeden tréningový príklad za 4 body. Ak prídete na cvičenia a odovzdáte na konci aspoň rozumne rozrobenú verziu programu, získate jeden bonusový bod, aj keď ho nestihnete dokončiť.
 
<!-- ** Ak ste na cvičeniach používali vlastný počítač alebo webové prostredia, odporúčame vám vyskúšať si aspoň teraz prácu v linuxovom prostredí v učebni, aby ste vedeli, čo môžete použiť na skúške. -->
 
** Ak ste na programovaní používali webové prostredia, nainštalujte si a vyskúšajte nejaký softvér, ktorý pobeží priamo na vašom počítači.
 
** Dobrým nápadom je naučiť sa používať aj program [[Valgrind]] alebo alternatívy, ktorý vám na skúške môže pomôcť nájsť chýbajúce odalokovanie alebo chybu, pre ktorú váš program padá.
 
* '''Namiesto piatkových cvičení budúci týždeň bude predtermín skúšky'''.
 
  
<!--
+
Dnešná prednáška bude venovaná (pomerne plytkému) prehľadu rôznych čŕt jazykov C a C++, ktoré sa počas semestra nepreberali. Tento prehľad by mal poslúžiť ako pomôcka pri štúdiu existujúcich programov, resp. ako inšpirácia pre ďalšie samoštúdium.
* Pred skúškou si '''skontrolujte body''' na testovači. Prípadné reklamácie pošlite na prog@.... Na/po skúške už reklamácie bodov z cvičení a domácich úloh nebudeme riešiť. -->
 
* Ak ste namiesto úloh robili '''rýchlostné programovanie''', nezabudnite do 18.12. odovzdať zoznam príkladov vyriešených v C alebo C++ podľa pokynov na testovači.
 
  
Iné
+
Znalosť materiálu z tejto prednášky ''nebude'' vyžadovaná na skúške. Rovnako ''nie je'' odporúčané na skúške tento materiál využívať bez dôkladného samostatného oboznámenia sa s ním.  
* Do konca skúškového môžete vypĺňať študentskú '''anketu''': https://anketa.uniba.sk/fmph/ (ešte nebola spustená) Tešíme sa na konštruktívne návrhy, ktoré nám pomôžu zlepšiť predmet do budúcnosti.
 
* Ak by ste cez skúškové obdobie mali záujem o konzultácie pred opravným testom alebo pred skúškou, dajte nám vedieť.
 
  
== Sylaby predmetu ==
+
Táto prednáška nepokrýva objektovo-orientované programovanie v jazyku C++. Objektovo-orientované programovanie (v jazyku Java) bude hlavnou náplňou druhého semestra programovania.
=== Základy ===
 
  
'''Konštrukcie jazyka C'''
+
== Rozdiely medzi jazykmi C a C++ ==
* premenné typov <tt>int</tt>, <tt>double</tt>, <tt>char</tt>, <tt>bool</tt>, konverzie medzi nimi
+
 
* podmienky (<tt>if</tt>, <tt>else</tt>, <tt>switch</tt>), cykly (<tt>for</tt>, <tt>while</tt>)
+
Jazyk C vznikol okolo roku 1972 na podporu vývoja operačného systému Unix; jazyk C++ okolo roku 1985. Programy v jazyku C sú väčšinou súčasne aj korektnými programami v jazyku C++ (ide ale o isté zjednodušenie). Obidva tieto jazyky existujú vo viacerých štandardoch, v ktorých sa postupne pridávali nové črty.
* funkcie (a parametre funkcií - odovzdávanie hodnotou, referenciou, smerníkom)
+
 
 +
V priebehu semestra sme programovali v jazyku C++. Veľká časť konštrukcií, ktoré sme v programoch využívali, však pochádza už z jazyka C. Výnimkami sú však napríklad nasledujúce črty jazyka C++, ktoré v jazyku C nie sú k dispozícii:
 +
* V jazyku C nie je možné predávanie parametrov funkcií referenciou. Namiesto toho je potrebné používať smerníky.
 +
* V jazyku C nefungujú operátory <tt>new</tt> a <tt>delete</tt>. Namiesto nich treba použiť funkcie popísané nižšie.
 +
* Na používanie typu <tt>bool</tt> a konštánt <tt>true</tt> a <tt>false</tt> je potrebná knižnica <tt>stdbool</tt> (t. j. <tt>#include <stdbool.h></tt>). Zabudovaný booleovský typ má názov <tt>_Bool</tt> a nadobúda hodnoty <tt>0</tt> a <tt>1</tt>.
 +
* Štandardnými knižnicami jazyka C sú spomedzi štandardných knižníc jazyka C++ v zásade tie začínajúce písmenom <tt>c</tt>, napríklad <tt>cstdio</tt>. Namiesto <tt>#include <cstdio></tt> potom v C píšeme <tt>#include <stdio.h></tt>.
 +
** V jazyku C špeciálne nie je možné používať knižnicu <tt>iostream</tt> a v nej definované štandardné vstupno-výstupné prúdy <tt>cin</tt> a <tt>cout</tt>.
 +
* Ak máme napr. <tt>struct bod</tt>, v C++ sme premenné deklarovali cez <tt>bod b;</tt>, v jazyku C treba písať <tt>struct bod b;</tt>.
 +
* V starších verziách jazyka C nefungujú mnohé konštrukcie jazyka C++, ktoré sme bežne používali, napríklad komentáre vo forme <tt>//</tt>, deklarácie premenných inde ako na začiatku funkcie, atď.
 +
 
 +
== Nepreberané črty jazyka C (použiteľné aj v C++) ==
 +
 
 +
=== Enumerované typy ===
 +
 
 +
Enumerované typy pozostávajú z vymenovania niekoľkých hodnôt, ktoré sa stanú celočíselnými konštantami. To je občas užitočné na sprehľadnenie zdrojového kódu.
 +
 
 +
''Príklad'':
 +
<syntaxhighlight lang="C++">
 +
#include <iostream>
 +
using namespace std;
 +
 
 +
int main() {
 +
    // definicia enumerovaneho typu farba
 +
    enum farba {biela, modra, cervena, zelena, cierna}; 
 +
    // definicia premennej typu farba   
 +
    farba f = biela;                                   
 +
    f = zelena;
 +
    cout << f << endl; // vypise 3
 +
}
 +
</syntaxhighlight>
 +
 
 +
=== Zložený typ (<tt>union</tt>) ===
 +
 
 +
Zložený typ umožňuje na jednom mieste pamäte uchovávať hodnotu, ktorá môže byť viacerých dátových typov (na rozdiel od štruktúr však vždy ide o práve jednu hodnotu). Definuje sa podobne ako štruktúra, avšak s použitím kľúčového slova <tt>union</tt> namiesto <tt>struct</tt>.
 +
 
 +
''Príklad'':
 +
<syntaxhighlight lang="C++">
 +
#include <iostream>
 +
#include <cstring>
 +
using namespace std;
 +
 
 +
union zlozenyTyp {
 +
    int n;
 +
    char s[8];
 +
};
 +
 
 +
int main() {
 +
    zlozenyTyp z;
 +
    z.n = 10;
 +
    cout << z.n << endl;  // vypise 10
 +
    strcpy(z.s, "abcd");
 +
    cout << z.s << endl;  // vypise abcd
 +
    cout << z.n << endl;  // vypise nejaky nezmysel
 +
}
 +
</syntaxhighlight>
 +
 
 +
Napríklad pri aritmetických stromoch by použitie zloženého typu umožnilo ušetriť trochu pamäte tým, že by sme si v každom uzle pamätali buď jeho hodnotu, alebo operátor, pričom typ uzla by sme rozlišovali podľa toho, či sú smerníky na oboch synov rovné <tt>NULL</tt>.
 +
 
 +
===Operátory===
 +
Okrem operátorov, ktoré sme používali, existuje niekoľko ďalších, ako napríklad nasledujúce:
 +
* ''Bitové operátory'' pracujú s celým číslom ako s poľom bitov (vhodnejšie sú <tt>unsigned</tt> typy):
 +
** <tt><<</tt> a <tt>>></tt> posúvajú bity doľava a doprava, zodpovedajú násobeniu a deleniu mocninami dvojky.
 +
** <tt>&</tt> (''and'' po bitoch), | (''or'' po bitoch), ^ (''xor'' po bitoch), ~ (negácia po bitoch).
 +
* ''Ternárny operátor'' <tt>(podmienka)?(hodnota pre true):(hodnota pre false)</tt>
 +
** <tt>cout << x << " je " << ((x%2==0) ? "parne" : "neparne") << endl;</tt>
 +
* Pozor na rozdiel medzi <tt>a[i++]=0</tt> a <tt>a[++i]=0</tt>, prehľadnejšie je použiť dva príkazy: <tt>a[i]=0; i++;</tt> alebo <tt>i++; a[i] = 0;</tt>
 +
 
 +
===Cyklus <tt>do-while</tt>===
 +
 
 +
Cyklus <tt>do-while</tt> je obdobou cyklu <tt>while</tt> s vyhodnocovaním podmienky na konci iterácie. Nasledujúce dva spôsoby písania cyklu sú viac-menej ekvivalentné:
 +
<syntaxhighlight lang="C++">
 +
do {
 +
    prikazy;
 +
} while(podmienka);
 +
 
 +
while (true) {
 +
    prikazy;
 +
    if (!podmienka) break;
 +
}
 +
</syntaxhighlight>
 +
 
 +
===Makrá a konštanty===
 +
Konštantu možno zadefinovať napríklad aj takto:
 +
<syntaxhighlight lang="C++">
 +
#define MAXN 100
 +
</syntaxhighlight>
 +
Na rozdiel od konštanty s definíciou
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
void f1(int x){}                                //hodnotou
+
const int maxN = 100;
void f2(int &x){}                                //referenciou
 
void f3(int* x){}                                //smerníkom
 
void f(int a[], int n){}                        //polia bez & (ostanú zmeny)
 
void kresli(Turtle &t){}                        //korytnačky, SVGdraw a pod. s &
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
sa pri použití <tt>#define</tt> konštantná premenná <tt>MAXN</tt> reálne nevytvára (nevyhradzuje sa pre ňu pamäťové miesto); všetky výskyty <tt>MAXN</tt> sú preprocesorom kompilátora nahradené v zdrojovom kóde konštantou <tt>100</tt>.
  
'''Polia, reťazce''' (char[])
+
Okrem konštánt možno definovať aj zložitejšie makrá s parametrami:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
int A[4]={3, 6, 8, 10};
+
/* Definicia makra: */
int B[4];             
+
#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
B[0]=3; B[1]=6; B[2]=8; B[3]=10;
+
 
 +
/* Priklad pouzitia: */
 +
cout << MIN(a*a, b+5);
  
char C[100] = "pes";
+
/* Preprocesor vykona substituciu za MIN, ktorou dostane: */
char D[100] = {'p', 'e', 's', 0};
+
cout << ((a*a) < (b+5) ? (a*a) : (b+5));
 
</syntaxhighlight>
 
</syntaxhighlight>
* funkcie strlen, strcpy, strcmp, strcat
+
* Bez dostatočného množstva zátvoriek by pri použití makra <tt>MIN</tt> mohlo dôjsť k &bdquo;interakcii s okolím&rdquo;.
 +
* Vo všeobecnosti je odporúčané vyvarovať sa použitia makier.
  
'''Súbory, spracovanie vstupu'''
+
=== Delenie programu na súbory ===
* cin, cout alebo printf, scanf
+
* Väčší program chceme rozdeliť na viac súborov
* fopen, fclose, feof
+
* Chceme vytvárať a používať vlastné knižnice - skupiny funkcií s podobným účelom
* fprintf, fscanf
+
** Napríklad knižnica implementujúca funkcie pracujúce so zásobníkom
* getc, putc, ungetc, fgets, fputs
+
* Knižnicu rozdelíme na dva súbory, napr. <tt>stack.h</tt> a <tt>stack.c</tt> resp. <tt>stack.cpp</tt>
* spracovanie súboru po znakoch, po riadkoch, po číslach alebo slovách
 
  
'''Smerníky, dynamicky alokovaná pamäť, dvojrozmerné polia'''
+
V hlavičkovom súbore (header file) <tt>stack.h</tt> zadeklarujeme funkcie, ale neuvádzame ich kód, napr.:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
int i;   // „klasická“ celočíselná premenná
+
typedef int dataType;
int *p;   // ukazovateľ na celočíselnú premennú
+
struct stack {
 +
    int top;  /* pozicia vrchného prvku zásobníka */
 +
    dataType *items; /* pole prvkov */
 +
};
 +
void init(stack &s);
 +
bool isEmpty(stack &s);
 +
void push(stack &s, dataType data);
 +
dataType pop(stack &q);
 +
</syntaxhighlight>
 +
 
 +
Programy, ktoré chcú použiť stack, použijú  <tt>#include "stack.h"</tt>
 +
* Všimnite si, že v include dávame meno štandardných knižníc v <>, našich vlastných v ""
  
p = &i;        // spravne
+
V súbore <tt>stack.cpp</tt> uvedieme kód funkcií, napr.
p = &(i + 3);  // zle i+3 nie je premenna
+
<syntaxhighlight lang="C++">
p = &15;       // zle konstanta nema adresu
+
#include "stack.h"
i = *p;         // spravne ak p bol inicializovany
+
const int maxN = 100;
 +
void init(stack &s) {
 +
    s.top = -1;
 +
    s.items = new dataType[maxN];
 +
}
 +
...
 +
</syntaxhighlight>
  
int * cislo = new int;  // alokovanie jednej premennej
 
*cislo = 50;
 
..
 
delete cislo;
 
  
int a[4];
+
Pri kompilácii potom potrebujeme kompilátoru zadať mená všetkých častí programu s príponou cpp resp. c, napríklad
int *b = a;  // a,b su teraz takmer rovnocenne premenne
+
<pre>
 +
g++ -o program main.cpp stack.cpp
 +
</pre>
 +
* Práve jeden z kompilovaných zdrojových súborov musí obsahovať funkciu <tt>main</tt>.
  
int *A = new int[n]; // alokovanie 1D pola danej dlzky
+
===Zopár užitočných funkcií===
..
 
delete[] A;
 
  
int **a;      // alokovanie 2D matice
+
'''Alokácia pamäte'''
a = new int *[n];
+
* V jazyku C nie sú definované operátory <tt>new</tt> a <tt>delete</tt>, resp. <tt>new[]</tt> a <tt>delete[]</tt>.
for (int i = 0; i < n; i++) a[i] = new int[m];
+
* Pamäť sa alokuje funkciou <tt>malloc</tt>, ktorá alokuje kus pamäte s daným počtom bajtov.
..
+
** V prípade neúspechu vráti <tt>NULL</tt>.
for (int i = 0; i < n; i++) delete[] a[i];
+
** V prípade úspechu vráti smerník na <tt>void</tt>, ktorý je následne nutné pretypovať.
delete[] a;
+
* Uvoľnenie pamäte realizuje funkcia <tt>free</tt>.
 +
* Pri výpočte veľkosti potrebnej pamäte sa zvyčajne používa operátor <tt>sizeof</tt>.
 +
 
 +
<syntaxhighlight lang="C++">
 +
#include <cstdlib>  // resp. #include <stdlib.h> v C
 +
 
 +
/* vytvorime pole 100 int-ov */
 +
int *a = (int *)malloc(sizeof(int) * 100);
 +
/* odalokujeme pole a */
 +
free(a);
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Abstraktné dátové typy ===
+
'''Triedenie'''
 +
* Funkcia <tt>qsort</tt> z knižnice <tt>stdlib.h</tt>.
 +
* Dostane pole, počet jeho prvkov, veľkosť každého prvku a funkciu, ktorá porovná dva prvky.
 +
** Funkciu teda posielame ako parameter.
 +
** Táto porovnávacia funkcia dostane dva smerníky typu <tt>void *</tt> (na dva prvky poľa).
 +
** Vráti záporné číslo, ak prvý prvok je menší, nulu, ak sú rovnaké a kladné číslo, ak je prvý väčší.
 +
* Ak si napíšeme porovnávaciu funkciu, môžeme triediť prvky hocijakého typu.
 +
<syntaxhighlight lang="C++">
 +
int compare(const void *a, const void *b) {
 +
    return (*(int*)a - *(int*)b);
 +
}
 +
int a[] = {5, 3, 2, 4, 1};
 +
qsort(a, 5, sizeof(int), compare);
 +
</syntaxhighlight>
 +
* Existuje napríklad aj funkcia <tt>bsearch</tt> na binárne vyhľadávanie v utriedenom poli.
 +
===Odbočka: argumenty typu const===
 +
* V príklade vyššie máme funkciu s hlavičkou <tt>int compare(const void *a, const void *b)</tt>.
 +
* Argumentom typu <tt>const void *a</tt> programátor &bdquo;sľubuje&rdquo;, že nebude meniť obsah pamäte, na ktorú ukazuje smerník <tt>a</tt>.
 +
* Napríklad pre funkciu <tt>void f(const int *a) {*a = 5;}</tt> teda kompilátor vyhlási chybu.
 +
* Ak by sme naopak pri triedení chceli použiť funkciu s hlavičkou <tt>int compare(void *a, void *b)</tt>, kompilátor tiež vyhlási chybu, lebo <tt>qsort</tt> očakáva <tt>const void *</tt> a nie <tt>void *</tt>.
 +
* Za dobrú prax sa považuje používanie <tt>const</tt> na všetky parametre typu smerník alebo referencia, ktoré funkcia nepotrebuje meniť.
  
Abstraktný dátový typ '''dynamické pole''' (rastúce pole)
+
== Nepreberané črty jazyka C++ ==
* operácie init, add, get, set, length
+
===Generické funkcie===
 +
Občas sa zíde napísať algoritmus, ktorý by mohol pracovať na dátach rôznych typov.
 +
Napríklad triediť môžeme celé alebo desatinné čísla, reťazce, zložitejšie štruktúry s určitým kľúčom a pod.
  
Abstraktný dátový typ '''množina''' (set)
+
V C++ sa dajú písať takzvané ''generické funkcie'', ktoré možno &bdquo;parametrizovať&rdquo; podľa typu:
* operácie init, find, insert, remove
+
<syntaxhighlight lang="C++">
* implementácie pomocou
+
#include <iostream>
** neutriedeného poľa
+
using namespace std;
** utriedeného poľa
 
** spájaných zoznamov
 
** binárnych vyhľadávacích stromov
 
** hešovacej tabuľky
 
** lexikografického stromu (ak kľúč je reťazec)
 
  
<!--
+
template <typename T>
Abstraktný dátový typ '''slovník''' (asociatívne pole, map)
+
T myMax (T a, T b) {
* kľúče a ďalšie dáta
+
  return (a > b) ? a : b;
* operácie init, insert, find, remove (hľadáme podľa kľúča)
+
}
* implementácie podobné ako množina
 
-->
 
  
Abstraktné dátové typy '''rad a zásobník'''
+
int main() {
* operácie pre rad (frontu, queue): init, isEmpty, enqueue, dequeue, peek
+
    int i = 3;
* operácie pre zásobník (stack): init, isEmpty, push, pop
+
    int j = 5;
* implementácie: v poli alebo v spájanom zozname
+
    int k = myMax(i, j);
* využitie: ukladanie dát na spracovanie, odstránenie rekurzie
+
    cout << k << endl;
* kontrola zátvoriek a vyhodnocovanie výrazov pomocou zásobníka
+
}
 +
</syntaxhighlight>
 +
* O generických funkciách sa budete viac učiť budúci semester.
  
 +
===Preťaženie operátorov===
 +
Pre novovytvorené typy je možné štadardným operátorom jazyka C++ priradiť novú sémantiku pomocou tzv. ''preťaženia''.
 +
Napríklad v nasledujúcom príklade definujeme operátor <tt><</tt> na menách, ktorý najprv porovnáva podľa priezviska a následne podľa krstného mena.
  
=== Dátové štruktúry ===
 
[[Image:PROG-list.png|right|200px]]'''Spájané zoznamy'''
 
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
struct node {
+
struct meno {
     int data;
+
     char *krstne, *priezvisko;
    item* next;
 
 
};
 
};
struct linkedList {
+
 
     item* first;
+
bool operator < (const meno &x, const meno &y) {
 +
    return strcmp(x.priezvisko, y.priezvisko) < 0
 +
            || strcmp(x.priezvisko, y.priezvisko) == 0
 +
            && strcmp(x.krstne, y.krstne) < 0;
 +
}
 +
</syntaxhighlight>
 +
* Podobne môžeme zadefinovať napríklad operátory <tt>+</tt> a <tt>*</tt> pre štruktúry reprezentujúce polynómy alebo komplexné čísla...
 +
* <tt>cout << "Hello"</tt> používa preťažený operátor <tt><<</tt> pre výstupný prúd na ľavej strane a reťazec na pravej strane.
 +
 
 +
===Reťazce typu <tt>string</tt>===
 +
 
 +
* V C++ je možné okrem klasických C-čkových reťazcov použiť aj typ <tt>string</tt> z C++.
 +
* Jeho použitie je pohodlnejšie, sám si určuje potrebnú veľkosť pamäte.
 +
* Reťazce tohto typu sú objekty, do funkcií ich odovzdávame väčšinou referenciou (cez &).
 +
* K jednotlivým znakom pristupujeme pomocou <tt>[]</tt> (ako u polí) alebo pomocou metódy <tt>at</tt>.
 +
 
 +
<syntaxhighlight lang="C++">
 +
#include <string>
 +
#include <iostream>
 +
using namespace std;
 +
 
 +
int main() {
 +
    char cstr[100] = "Ahoj\n";
 +
    string str = "Ako sa mas?\n";
 +
    string str2;
 +
 
 +
    /* Do str mozno pomocou operatora =
 +
    * korektne priradit konstantne retazce,
 +
    * C-ckove retazce (polia znakov),
 +
    * aj ine premenne typu string. */
 +
    str2 = "Ahoj\n";
 +
    str2 = cstr;
 +
    str2 = str;
 +
 
 +
    /* Meranie dlzky retazca: */
 +
    cout << "Dlzka je: " << str.length() << endl;
 +
 
 +
    /* Pomocou operatora + mozeme spojit dva retazce do jedneho. */
 +
    str2 = cstr + str;
 +
    cout << str2 << endl;
 +
 
 +
    /* Funguje porovnanie pomocou ==, !=, <, ...
 +
    * (bud dvoch C++ stringov, alebo C++ stringu a C stringu) */
 +
    if (str < str2) {
 +
        cout << "Prvy je mensi" << endl;
 +
    } else if (str == str2) {
 +
        cout << "Rovnaju sa" << endl;
 +
    } else {
 +
        cout << "Druhy je mensi" << endl;
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
* Pomocou metódy <tt>c_str()</tt> možno získať z reťazca typu <tt>string</tt> premennú typu <tt>const char*</tt>.
 +
 
 +
===Dátová štruktúra <tt>vector</tt>===
 +
 
 +
* Súčasťou štandardnej knižnice jazyka C++ je viacero rôznych [http://www.cplusplus.com/reference/stl/ dátových štruktúr].
 +
* Ide pritom o generické štruktúry, ktoré môžu uchovávať dáta rôznych typov.
 +
** elegantnejšie ako naše riešenie s dataType
 +
** v jednom programe môžeme mať štruktúry uchovávajúce rôzne typy
 +
 
 +
Spomenieme dátovú štruktúru <tt>vector</tt> [http://www.cplusplus.com/reference/vector/vector/]:
 +
* Elegantnejšia verzia dynamických polí z prednášky.
 +
 
 +
Vytvorenie nového vektora
 +
<syntaxhighlight lang="C++">
 +
// vytvori pole celych cisel s nula cislami
 +
vector<int> a;     
 +
// vytvori pole 5 celych cisel, ktore nastavi na 1
 +
vector<int> a(5,1); 
 +
</syntaxhighlight>
 +
 
 +
Prístup k prvkom vektora:
 +
* Pomocou <tt>a[index]</tt> podobne ako pri poliach, nekontroluje, či je index v medziach poľa.
 +
* Funkciou <tt>a.at(index)</tt>, ktorá v prípade indexu mimo rozsahu vyhodí výnimku (program spadne) a nenarobí chaos v pamäti, ľahšie sa hľadá chyba.
 +
* V obidvoch prípadoch môžeme aj priraďovať: <tt>a[index] = value; a.at(index) = value;</tt>
 +
 
 +
* Ďalšie metódy na prácu s vektormi:
 +
** <tt>a.push_back(x)</tt> vloží hodnotu <tt>x</tt> ako nový prvok na koniec poľa, podľa potreby pritom pole realokuje a pod.
 +
** <tt>a.size()</tt> vráti počet prvkov v poli.
 +
 
 +
<syntaxhighlight lang="C++">
 +
#include <vector>
 +
#include <iostream>
 +
using namespace std;
 +
 
 +
int main() {
 +
    vector<int> a;
 +
   
 +
    while(true) { 
 +
        // nacitame postupnost nezapornych cisel
 +
        // ukoncenu -1, ulozime do vektora
 +
        int prvok;
 +
        cin >> prvok;
 +
        if (prvok < 0) {
 +
            break;
 +
        }
 +
        a.push_back(prvok);
 +
    }
 +
    // vypiseme prvky vektora
 +
    for (int i = 0; i < (int)a.size(); i++) {
 +
        cout << a[i] << endl;  // alebo a.at(i)
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
Algoritmus na '''triedenie''':
 +
* V knižnici <tt><algorithm></tt>
 +
<syntaxhighlight lang="C++">
 +
//triedime normalne pole
 +
int A[6] = {1, 4, 2, 8, 5, 7};
 +
sort(A, A + 6);
 +
 
 +
//triedime vektor
 +
vector <int> A;
 +
sort(A.begin(), A.end());
 +
 
 +
//triedime podla nasej porovnavacej funkcie, napr. podla absolutnej hodnoty
 +
struct cmp {
 +
     bool operator()(int x, int y) { return abs(x) < abs(y); }
 
};
 
};
void insertFirst(linkedList &z, int d){
+
cmp c;
     /* do zoznamu z vlozi na zaciatok novy prvok s datami d */
+
sort(A.begin(), A.end(), c);
     item* p = new item;   // vytvoríme nový prvok
+
</syntaxhighlight>
     p->data = d;         // naplníme dáta
+
 
     p->next = z.first;   // prvok bude prvým prvkom zoznamu (ukazuje na doterajší začiatok)
+
2D matica pomocou vektora:
     z.first = p;         // tento prvok je novým začiatkom
+
<syntaxhighlight lang="C++">
 +
#include <vector>
 +
#include <iostream>
 +
using namespace std;
 +
 
 +
int main() {
 +
     // nacitanie a vypis matice m x n
 +
     int m, n;
 +
     cin >> m >> n;
 +
 
 +
     vector<vector<int>> a(m, vector<int> (n, 0));
 +
 
 +
    for (int i = 0; i < (int)a.size(); i++) {
 +
        for (int j = 0; j < (int)a[i].size(); j++) {
 +
            cin >> a[i][j];
 +
        }
 +
    }
 +
     for (int i = 0; i < (int)a.size(); i++) {
 +
        for (int j = 0; j < (int)a[i].size(); j++) {
 +
            cout << " " << a[i][j];
 +
        }
 +
        cout << endl;
 +
    }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
[[Image:PROG-P21-aritm.png|thumb|right|Strom pre výraz (65 – 3*5)/(2 + 3)]]'''Binárne stromy'''
+
===Range-based for loop===
 +
* Špeciálny cyklus cez prvky vektora a pod.
 +
** nepotrebujeme zavádzať premennú pre index
 +
** podobný cyklus uvidíte podrobnejšie v Jave
 +
 
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
struct node {
+
#include <iostream>
    /* vrchol stromu  */
+
#include <vector>
     dataType data;
+
using namespace std;
    node * left;  /* lavy syn */
+
 
    node * right; /* pravy syn */
+
void vypis(const vector<int> &a) {
};
+
  // vypise vsetky prvky vektora a
 +
  for (const int &value : a) {
 +
     cout << value << ' ';
 +
  }
 +
  cout << '\n';   
 +
}
 +
 
 +
int main() {
 +
  vector<int> a;
 +
 
 +
  // do vektora a vlozi prvky 0,..,5
 +
  for(int value : {0,1,2,3,4,5}) {
 +
      a.push_back(value);
 +
  }
 +
 
 +
  vypis(a); // vypise 0 1 2 3 4 5
 +
 +
  // zvysi kazdy prvok vektora o 1
 +
  for (int &value : a) {
 +
      value++;
 +
  }
  
node * createNode(dataType data, node *left, node *right) {
+
  vypis(a); // vypise 1 2 3 4 5 6
    node *v = new node;
 
    v->data = data;
 
    v->left = left;
 
    v->right = right;
 
    return v;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
* prehľadávanie inorder, preorder, postorder
 
* použitie na uloženie aritmetických výrazov
 
  
[[Image:P22-BST.png|200px|right]]'''Binárne vyhľadávacie stromy'''
+
===Slovník, map===
* vrcholy vľavo od koreňa menší kľúč, vpravo od koreňa väčší
+
 
* insert, find, remove v čase závisiacom od hĺbky stromu
+
Štruktúra '''map''' implementuje slovník, pričom zadáme dva typy: kľúč a hodnota
 +
* väčšinou je implementovaný pomocou nejakej verzie binárnych vyhľadávacích stromov
 +
* existuje aj unordered_map, ktorý využíva hašovanie
 +
 
 +
<syntaxhighlight lang="C++">
 +
  map <string, string> zoznam;
 +
  zoznam["Jozko Mrkvicka"] = "02/12345678";
 +
  zoznam["Janko Hrasko"] = "02/87654321";
 +
  if (zoznam.count("Jozko Mrkvicka") > 0) {
 +
      cout << zoznam["Jozko Mrkvicka"] << endl;
 +
  }
 +
  // prejdenie vsetkych zaznamov v slovniku pomocou iteratora
 +
  cout << "Vsetky zaznamy:" << endl;
 +
  for(map <string, string>::iterator i = zoznam.begin();
 +
      i != zoznam.end(); i++) {
 +
      // i->first je meno, i->second je cislo
 +
      cout << i->first << " " << i->second << endl;
 +
  }
 +
</syntaxhighlight>
 +
 
 +
===Stream===
 +
 
 +
* Textové súbory môžeme načítavať alebo zapisovať podobne ako sme pracovali s konzolou cez <tt>cin</tt> a <tt>cout</tt>
 +
* Budeme využívať typy a funkcie zadefinované v knižnici <tt>fstream</tt>.
 +
* <tt>ifstream</tt> je typ súboru určený na čítanie, <tt>ofstream</tt> na zápis
  
[[Image:trie.jpg|right]]'''Lexikografické stromy'''
 
* ukladajú množinu reťazcov
 
* nie sú binárne: vrchol môže mať veľa synov
 
* insert, find, remove v čase závisiacom od dĺžky kľúča, ale nie od počtu kľúčov, ktoré už sú v strome
 
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
struct node {
+
#include <fstream>
     /* vrchol lexikografickeho stromu  */
+
#include <iomanip>
     char data; // pismeno ulozene v tomto vrchole
+
using namespace std;
     bool isWord; // je tento vrchol koncom slova?
+
 
     node* next[Abeceda]; // pole smernikov na deti   
+
int main () {
};
+
     // otvorenie súboru
 +
    ofstream fw;
 +
    fw.open ("test.txt");   
 +
    // do fw zapisujeme podobne ako na cout
 +
    fw << "10*32=" << 10*32 << endl;
 +
    // vypise 0.666667
 +
     fw << 2.0/3.0 << endl;  
 +
    // vypise 0.67
 +
     fw << setprecision(2) << 2.0/3.0 << endl;
 +
    // vypise 6.67e-01
 +
     fw << scientific << 2.0/3.0 << endl;
 +
    fw.close();
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
* Knižnica <tt>iomanip</tt> umožňuje formátovanie výstupu napríklad pomocou manipulátorov <tt>setprecision()</tt>, <tt>setfill()</tt>, <tt>setw()</tt>, <tt>scientific</tt> atď
  
'''Hešovanie'''
+
Podobne môžeme otvoriť aj súbor, z ktorého budeme čítať vstup. V príklade načítame pole celých čísel.
* hešovacia tabuľka veľkosti ''m''
 
* kľúč ''k'' premietneme nejakou funkciou na index v poli (0,...,m-1}
 
* každé políčko hešovacej tabuľky spájaný zoznam prvkov, ktoré sa tam zahešovali
 
* v ideálnom prípade sa prvky rozhodia pomerne rovnomerne, zoznamy krátke, rýchle hľadanie, vkladenie, mazanie
 
* v najhoršom prípade všetky prvky v jednom zozname, pomalé hľadanie a mazanie
 
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
int hash(int k, int m){ // veľmi jednoduchá hešovacia funkcia, v praxi väčšinou zložitejšie
+
#include <fstream>
     return abs(k) % m;
+
#include <iostream>
 +
using namespace std;
 +
const int MAX = 100;
 +
 
 +
int main () {
 +
    ifstream fr;
 +
    fr.open ("vstup.txt");
 +
 
 +
    int pocet, a[MAX];
 +
    fr >> pocet;
 +
    for (int i = 0; i < pocet; i++) {
 +
        fr >> a[i];
 +
    }
 +
    fr.close();
 +
 
 +
     for (int i = 0; i < pocet; i++) {
 +
      cout << a[i] << endl;
 +
    }
 
}
 
}
struct node {
+
</syntaxhighlight>
     int item;
+
 
     node* next;
+
Kontrola správneho otvorenia súboru a iné chyby
};
+
* Funkcia <tt>f.fail()</tt> vráti <tt>true</tt>, ak vznikla chyba (väčšinou pri otvorení neexistujúceho súboru)
 +
* Druhou možnosťou je testovať správne otvorenie pomocou testovania premennej typu <tt>ofstream</tt> alebo <tt>ifstream</tt>.
 +
** Technicky ide o preťaženie operátora konverzie na <tt>bool</tt> resp. na <tt>void*</tt>, robí negáciu funkcie <tt>fail</tt>
 +
<syntaxhighlight lang="C++">
 +
  ifstream fr1("vstup1.txt");
 +
  if(fr1.fail()) {
 +
     cout << "Cannot open vstup1.txt file.\n";
 +
     return 1;
 +
  }
  
struct set {
+
  ifstream fr2("vstup2.txt");
     node** data;
+
  if(!fr2) {
     int m;
+
     cout << "Cannot open vstup2.txt file.\n";
};
+
     return 1;
 +
  }
 +
  // tiez mozeme pouzit assert(fr2)
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Algoritmy ===
+
Testovanie konca súboru môžeme robiť pomocou funkcie <tt>eof</tt>
 +
<syntaxhighlight lang="C++">
 +
ifstream fin("vstup.txt");
 +
while(!fin.eof()) {
 +
    char c;
 +
    fin >> c;
 +
    cout << c;
 +
}
 +
</syntaxhighlight>
  
'''Rekurzia'''
+
Podobne ako pri konzole môžeme použiť funkciu <tt>getline</tt>, ktorá načíta celý riadok. Nasledovný príklad spočíta v jednotlivých riadkoch počet bodiek.
* Rekurzívne funkcie
 
* Vykresľovanie fraktálov
 
* Prehľadávanie s návratom (backtracking)
 
* Vyfarbovanie
 
* Prehľadávanie stromov
 
  
'''Triedenia'''
+
<syntaxhighlight lang="C++">
* nerekurzívne: Bubblesort, Selectionsort, Insertsort
+
#include <fstream>
* rekurzívne: Mergesort, Quicksort
+
#include <iostream>
* súvisiace algoritmy: binárne vyhľadávanie
+
using namespace std;
  
'''Matematické úlohy'''
+
int main(void){
* Euklidov algoritmus, Eratostenovo sito
+
    ifstream f("vstup.txt");;
* Práca s aritmetickými výrazmi: vyhodnocovanie postfixovej formy, prevod z infixovej do postfixovej, reprezentácia vo forme stromu
+
    string line;
 +
    while (getline(f,line)){
 +
        int pocet = 0;
 +
        for (int i = 0; i < line.length(); i++){
 +
            if (line[i] == '.') {
 +
                pocet++;
 +
            }
 +
        }
 +
        cout << pocet <<endl;
 +
    }
 +
}
 +
</syntaxhighlight>

Aktuálna revízia z 07:49, 11. december 2023

Nepreberané črty jazykov C a C++

Dnešná prednáška bude venovaná (pomerne plytkému) prehľadu rôznych čŕt jazykov C a C++, ktoré sa počas semestra nepreberali. Tento prehľad by mal poslúžiť ako pomôcka pri štúdiu existujúcich programov, resp. ako inšpirácia pre ďalšie samoštúdium.

Znalosť materiálu z tejto prednášky nebude vyžadovaná na skúške. Rovnako nie je odporúčané na skúške tento materiál využívať bez dôkladného samostatného oboznámenia sa s ním.

Táto prednáška nepokrýva objektovo-orientované programovanie v jazyku C++. Objektovo-orientované programovanie (v jazyku Java) bude hlavnou náplňou druhého semestra programovania.

Rozdiely medzi jazykmi C a C++

Jazyk C vznikol okolo roku 1972 na podporu vývoja operačného systému Unix; jazyk C++ okolo roku 1985. Programy v jazyku C sú väčšinou súčasne aj korektnými programami v jazyku C++ (ide ale o isté zjednodušenie). Obidva tieto jazyky existujú vo viacerých štandardoch, v ktorých sa postupne pridávali nové črty.

V priebehu semestra sme programovali v jazyku C++. Veľká časť konštrukcií, ktoré sme v programoch využívali, však pochádza už z jazyka C. Výnimkami sú však napríklad nasledujúce črty jazyka C++, ktoré v jazyku C nie sú k dispozícii:

  • V jazyku C nie je možné predávanie parametrov funkcií referenciou. Namiesto toho je potrebné používať smerníky.
  • V jazyku C nefungujú operátory new a delete. Namiesto nich treba použiť funkcie popísané nižšie.
  • Na používanie typu bool a konštánt true a false je potrebná knižnica stdbool (t. j. #include <stdbool.h>). Zabudovaný booleovský typ má názov _Bool a nadobúda hodnoty 0 a 1.
  • Štandardnými knižnicami jazyka C sú spomedzi štandardných knižníc jazyka C++ v zásade tie začínajúce písmenom c, napríklad cstdio. Namiesto #include <cstdio> potom v C píšeme #include <stdio.h>.
    • V jazyku C špeciálne nie je možné používať knižnicu iostream a v nej definované štandardné vstupno-výstupné prúdy cin a cout.
  • Ak máme napr. struct bod, v C++ sme premenné deklarovali cez bod b;, v jazyku C treba písať struct bod b;.
  • V starších verziách jazyka C nefungujú mnohé konštrukcie jazyka C++, ktoré sme bežne používali, napríklad komentáre vo forme //, deklarácie premenných inde ako na začiatku funkcie, atď.

Nepreberané črty jazyka C (použiteľné aj v C++)

Enumerované typy

Enumerované typy pozostávajú z vymenovania niekoľkých hodnôt, ktoré sa stanú celočíselnými konštantami. To je občas užitočné na sprehľadnenie zdrojového kódu.

Príklad:

#include <iostream>
using namespace std;

int main() {
    // definicia enumerovaneho typu farba
    enum farba {biela, modra, cervena, zelena, cierna};  
    // definicia premennej typu farba     
    farba f = biela;                                     
    f = zelena; 
    cout << f << endl; // vypise 3
}

Zložený typ (union)

Zložený typ umožňuje na jednom mieste pamäte uchovávať hodnotu, ktorá môže byť viacerých dátových typov (na rozdiel od štruktúr však vždy ide o práve jednu hodnotu). Definuje sa podobne ako štruktúra, avšak s použitím kľúčového slova union namiesto struct.

Príklad:

#include <iostream>
#include <cstring>
using namespace std;

union zlozenyTyp {
    int n;
    char s[8];
};

int main() {
    zlozenyTyp z;
    z.n = 10;
    cout << z.n << endl;  // vypise 10
    strcpy(z.s, "abcd");
    cout << z.s << endl;  // vypise abcd
    cout << z.n << endl;  // vypise nejaky nezmysel
}

Napríklad pri aritmetických stromoch by použitie zloženého typu umožnilo ušetriť trochu pamäte tým, že by sme si v každom uzle pamätali buď jeho hodnotu, alebo operátor, pričom typ uzla by sme rozlišovali podľa toho, či sú smerníky na oboch synov rovné NULL.

Operátory

Okrem operátorov, ktoré sme používali, existuje niekoľko ďalších, ako napríklad nasledujúce:

  • Bitové operátory pracujú s celým číslom ako s poľom bitov (vhodnejšie sú unsigned typy):
    • << a >> posúvajú bity doľava a doprava, zodpovedajú násobeniu a deleniu mocninami dvojky.
    • & (and po bitoch), | (or po bitoch), ^ (xor po bitoch), ~ (negácia po bitoch).
  • Ternárny operátor (podmienka)?(hodnota pre true):(hodnota pre false)
    • cout << x << " je " << ((x%2==0) ? "parne" : "neparne") << endl;
  • Pozor na rozdiel medzi a[i++]=0 a a[++i]=0, prehľadnejšie je použiť dva príkazy: a[i]=0; i++; alebo i++; a[i] = 0;

Cyklus do-while

Cyklus do-while je obdobou cyklu while s vyhodnocovaním podmienky na konci iterácie. Nasledujúce dva spôsoby písania cyklu sú viac-menej ekvivalentné:

do { 
    prikazy;
} while(podmienka);

while (true) {
    prikazy;
    if (!podmienka) break;
}

Makrá a konštanty

Konštantu možno zadefinovať napríklad aj takto:

#define MAXN 100

Na rozdiel od konštanty s definíciou

const int maxN = 100;

sa pri použití #define konštantná premenná MAXN reálne nevytvára (nevyhradzuje sa pre ňu pamäťové miesto); všetky výskyty MAXN sú preprocesorom kompilátora nahradené v zdrojovom kóde konštantou 100.

Okrem konštánt možno definovať aj zložitejšie makrá s parametrami:

/* Definicia makra: */
#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))

/* Priklad pouzitia: */
cout << MIN(a*a, b+5);

/* Preprocesor vykona substituciu za MIN, ktorou dostane: */
cout << ((a*a) < (b+5) ? (a*a) : (b+5));
  • Bez dostatočného množstva zátvoriek by pri použití makra MIN mohlo dôjsť k „interakcii s okolím”.
  • Vo všeobecnosti je odporúčané vyvarovať sa použitia makier.

Delenie programu na súbory

  • Väčší program chceme rozdeliť na viac súborov
  • Chceme vytvárať a používať vlastné knižnice - skupiny funkcií s podobným účelom
    • Napríklad knižnica implementujúca funkcie pracujúce so zásobníkom
  • Knižnicu rozdelíme na dva súbory, napr. stack.h a stack.c resp. stack.cpp

V hlavičkovom súbore (header file) stack.h zadeklarujeme funkcie, ale neuvádzame ich kód, napr.:

typedef int dataType;
struct stack {
    int top;  /* pozicia vrchného prvku zásobníka */
    dataType *items; /* pole prvkov */
};
void init(stack &s);
bool isEmpty(stack &s);
void push(stack &s, dataType data); 
dataType pop(stack &q);

Programy, ktoré chcú použiť stack, použijú #include "stack.h"

  • Všimnite si, že v include dávame meno štandardných knižníc v <>, našich vlastných v ""

V súbore stack.cpp uvedieme kód funkcií, napr.

#include "stack.h"
const int maxN = 100;
void init(stack &s) {
    s.top = -1;
    s.items = new dataType[maxN];
}
...


Pri kompilácii potom potrebujeme kompilátoru zadať mená všetkých častí programu s príponou cpp resp. c, napríklad

g++ -o program main.cpp stack.cpp
  • Práve jeden z kompilovaných zdrojových súborov musí obsahovať funkciu main.

Zopár užitočných funkcií

Alokácia pamäte

  • V jazyku C nie sú definované operátory new a delete, resp. new[] a delete[].
  • Pamäť sa alokuje funkciou malloc, ktorá alokuje kus pamäte s daným počtom bajtov.
    • V prípade neúspechu vráti NULL.
    • V prípade úspechu vráti smerník na void, ktorý je následne nutné pretypovať.
  • Uvoľnenie pamäte realizuje funkcia free.
  • Pri výpočte veľkosti potrebnej pamäte sa zvyčajne používa operátor sizeof.
#include <cstdlib>   // resp. #include <stdlib.h> v C

/* vytvorime pole 100 int-ov */
int *a = (int *)malloc(sizeof(int) * 100);
/* odalokujeme pole a */
free(a);

Triedenie

  • Funkcia qsort z knižnice stdlib.h.
  • Dostane pole, počet jeho prvkov, veľkosť každého prvku a funkciu, ktorá porovná dva prvky.
    • Funkciu teda posielame ako parameter.
    • Táto porovnávacia funkcia dostane dva smerníky typu void * (na dva prvky poľa).
    • Vráti záporné číslo, ak prvý prvok je menší, nulu, ak sú rovnaké a kladné číslo, ak je prvý väčší.
  • Ak si napíšeme porovnávaciu funkciu, môžeme triediť prvky hocijakého typu.
int compare(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}
int a[] = {5, 3, 2, 4, 1};
qsort(a, 5, sizeof(int), compare);
  • Existuje napríklad aj funkcia bsearch na binárne vyhľadávanie v utriedenom poli.

Odbočka: argumenty typu const

  • V príklade vyššie máme funkciu s hlavičkou int compare(const void *a, const void *b).
  • Argumentom typu const void *a programátor „sľubuje”, že nebude meniť obsah pamäte, na ktorú ukazuje smerník a.
  • Napríklad pre funkciu void f(const int *a) {*a = 5;} teda kompilátor vyhlási chybu.
  • Ak by sme naopak pri triedení chceli použiť funkciu s hlavičkou int compare(void *a, void *b), kompilátor tiež vyhlási chybu, lebo qsort očakáva const void * a nie void *.
  • Za dobrú prax sa považuje používanie const na všetky parametre typu smerník alebo referencia, ktoré funkcia nepotrebuje meniť.

Nepreberané črty jazyka C++

Generické funkcie

Občas sa zíde napísať algoritmus, ktorý by mohol pracovať na dátach rôznych typov. Napríklad triediť môžeme celé alebo desatinné čísla, reťazce, zložitejšie štruktúry s určitým kľúčom a pod.

V C++ sa dajú písať takzvané generické funkcie, ktoré možno „parametrizovať” podľa typu:

#include <iostream>
using namespace std;

template <typename T>
T myMax (T a, T b) {
  return (a > b) ? a : b;
}

int main() {
    int i = 3;
    int j = 5;
    int k = myMax(i, j);
    cout << k << endl;
}
  • O generických funkciách sa budete viac učiť budúci semester.

Preťaženie operátorov

Pre novovytvorené typy je možné štadardným operátorom jazyka C++ priradiť novú sémantiku pomocou tzv. preťaženia. Napríklad v nasledujúcom príklade definujeme operátor < na menách, ktorý najprv porovnáva podľa priezviska a následne podľa krstného mena.

struct meno {
    char *krstne, *priezvisko;
};

bool operator < (const meno &x, const meno &y) {
    return strcmp(x.priezvisko, y.priezvisko) < 0
            || strcmp(x.priezvisko, y.priezvisko) == 0
            && strcmp(x.krstne, y.krstne) < 0;
}
  • Podobne môžeme zadefinovať napríklad operátory + a * pre štruktúry reprezentujúce polynómy alebo komplexné čísla...
  • cout << "Hello" používa preťažený operátor << pre výstupný prúd na ľavej strane a reťazec na pravej strane.

Reťazce typu string

  • V C++ je možné okrem klasických C-čkových reťazcov použiť aj typ string z C++.
  • Jeho použitie je pohodlnejšie, sám si určuje potrebnú veľkosť pamäte.
  • Reťazce tohto typu sú objekty, do funkcií ich odovzdávame väčšinou referenciou (cez &).
  • K jednotlivým znakom pristupujeme pomocou [] (ako u polí) alebo pomocou metódy at.
#include <string>
#include <iostream>
using namespace std;

int main() {
    char cstr[100] = "Ahoj\n";
    string str = "Ako sa mas?\n";
    string str2;

    /* Do str mozno pomocou operatora = 
     * korektne priradit konstantne retazce,
     * C-ckove retazce (polia znakov), 
     * aj ine premenne typu string. */
    str2 = "Ahoj\n";
    str2 = cstr;
    str2 = str;

    /* Meranie dlzky retazca: */
    cout << "Dlzka je: " << str.length() << endl;

    /* Pomocou operatora + mozeme spojit dva retazce do jedneho. */
    str2 = cstr + str;
    cout << str2 << endl;

    /* Funguje porovnanie pomocou ==, !=, <, ...
     * (bud dvoch C++ stringov, alebo C++ stringu a C stringu) */
    if (str < str2) {
        cout << "Prvy je mensi" << endl;
    } else if (str == str2) {
        cout << "Rovnaju sa" << endl;
    } else {
        cout << "Druhy je mensi" << endl;
    }
}
  • Pomocou metódy c_str() možno získať z reťazca typu string premennú typu const char*.

Dátová štruktúra vector

  • Súčasťou štandardnej knižnice jazyka C++ je viacero rôznych dátových štruktúr.
  • Ide pritom o generické štruktúry, ktoré môžu uchovávať dáta rôznych typov.
    • elegantnejšie ako naše riešenie s dataType
    • v jednom programe môžeme mať štruktúry uchovávajúce rôzne typy

Spomenieme dátovú štruktúru vector [1]:

  • Elegantnejšia verzia dynamických polí z prednášky.

Vytvorenie nového vektora

// vytvori pole celych cisel s nula cislami
vector<int> a;       
// vytvori pole 5 celych cisel, ktore nastavi na 1
vector<int> a(5,1);

Prístup k prvkom vektora:

  • Pomocou a[index] podobne ako pri poliach, nekontroluje, či je index v medziach poľa.
  • Funkciou a.at(index), ktorá v prípade indexu mimo rozsahu vyhodí výnimku (program spadne) a nenarobí chaos v pamäti, ľahšie sa hľadá chyba.
  • V obidvoch prípadoch môžeme aj priraďovať: a[index] = value; a.at(index) = value;
  • Ďalšie metódy na prácu s vektormi:
    • a.push_back(x) vloží hodnotu x ako nový prvok na koniec poľa, podľa potreby pritom pole realokuje a pod.
    • a.size() vráti počet prvkov v poli.
#include <vector>
#include <iostream>
using namespace std;

int main() {
    vector<int> a;
    
    while(true) {  
        // nacitame postupnost nezapornych cisel 
        // ukoncenu -1, ulozime do vektora
        int prvok;
        cin >> prvok;
        if (prvok < 0) {
            break;
        }
        a.push_back(prvok);
    }
    // vypiseme prvky vektora
    for (int i = 0; i < (int)a.size(); i++) {
        cout << a[i] << endl;  // alebo a.at(i)
    }
}

Algoritmus na triedenie:

  • V knižnici <algorithm>
//triedime normalne pole
int A[6] = {1, 4, 2, 8, 5, 7};
sort(A, A + 6);

//triedime vektor
vector <int> A;
sort(A.begin(), A.end());

//triedime podla nasej porovnavacej funkcie, napr. podla absolutnej hodnoty
struct cmp {
    bool operator()(int x, int y) { return abs(x) < abs(y); }
};
cmp c;
sort(A.begin(), A.end(), c);

2D matica pomocou vektora:

#include <vector>
#include <iostream>
using namespace std;

int main() {
    // nacitanie a vypis matice m x n
    int m, n;
    cin >> m >> n;

    vector<vector<int>> a(m, vector<int> (n, 0));

    for (int i = 0; i < (int)a.size(); i++) {
        for (int j = 0; j < (int)a[i].size(); j++) {
            cin >> a[i][j];
        }
    }
    for (int i = 0; i < (int)a.size(); i++) {
        for (int j = 0; j < (int)a[i].size(); j++) {
            cout << " " << a[i][j];
        }
        cout << endl;
    }
}

Range-based for loop

  • Špeciálny cyklus cez prvky vektora a pod.
    • nepotrebujeme zavádzať premennú pre index
    • podobný cyklus uvidíte podrobnejšie v Jave
#include <iostream>
#include <vector>
using namespace std; 

void vypis(const vector<int> &a) {
  // vypise vsetky prvky vektora a
  for (const int &value : a) { 
    cout << value << ' ';
  }
  cout << '\n';  
}

int main() {
  vector<int> a;

  // do vektora a vlozi prvky 0,..,5
  for(int value : {0,1,2,3,4,5}) {
      a.push_back(value);
  }

  vypis(a);  // vypise 0 1 2 3 4 5
 
  // zvysi kazdy prvok vektora o 1
  for (int &value : a) { 
      value++;
  }

  vypis(a); // vypise 1 2 3 4 5 6
}

Slovník, map

Štruktúra map implementuje slovník, pričom zadáme dva typy: kľúč a hodnota

  • väčšinou je implementovaný pomocou nejakej verzie binárnych vyhľadávacích stromov
  • existuje aj unordered_map, ktorý využíva hašovanie
  map <string, string> zoznam;
  zoznam["Jozko Mrkvicka"] = "02/12345678";
  zoznam["Janko Hrasko"] = "02/87654321";
  if (zoznam.count("Jozko Mrkvicka") > 0) {
      cout << zoznam["Jozko Mrkvicka"] << endl;
  }
  // prejdenie vsetkych zaznamov v slovniku pomocou iteratora
  cout << "Vsetky zaznamy:" << endl;
  for(map <string, string>::iterator i = zoznam.begin();
      i != zoznam.end(); i++) {
      // i->first je meno, i->second je cislo
      cout << i->first << " " << i->second << endl;
  }

Stream

  • Textové súbory môžeme načítavať alebo zapisovať podobne ako sme pracovali s konzolou cez cin a cout
  • Budeme využívať typy a funkcie zadefinované v knižnici fstream.
  • ifstream je typ súboru určený na čítanie, ofstream na zápis
#include <fstream>
#include <iomanip>
using namespace std;

int main () {
    // otvorenie súboru
    ofstream fw;
    fw.open ("test.txt");     
    // do fw zapisujeme podobne ako na cout
    fw << "10*32=" << 10*32 << endl; 
    // vypise 0.666667
    fw << 2.0/3.0 << endl; 
    // vypise 0.67
    fw << setprecision(2) << 2.0/3.0 << endl;
    // vypise 6.67e-01
    fw << scientific << 2.0/3.0 << endl; 
    fw.close();
}
  • Knižnica iomanip umožňuje formátovanie výstupu napríklad pomocou manipulátorov setprecision(), setfill(), setw(), scientific atď

Podobne môžeme otvoriť aj súbor, z ktorého budeme čítať vstup. V príklade načítame pole celých čísel.

#include <fstream>
#include <iostream>
using namespace std;
const int MAX = 100;

int main () {
    ifstream fr;
    fr.open ("vstup.txt");

    int pocet, a[MAX];
    fr >> pocet;
    for (int i = 0; i < pocet; i++) {
        fr >> a[i];
    }
    fr.close();

    for (int i = 0; i < pocet; i++) {
      cout << a[i] << endl;
    }
}

Kontrola správneho otvorenia súboru a iné chyby

  • Funkcia f.fail() vráti true, ak vznikla chyba (väčšinou pri otvorení neexistujúceho súboru)
  • Druhou možnosťou je testovať správne otvorenie pomocou testovania premennej typu ofstream alebo ifstream.
    • Technicky ide o preťaženie operátora konverzie na bool resp. na void*, robí negáciu funkcie fail
  ifstream fr1("vstup1.txt");
  if(fr1.fail()) {
    cout << "Cannot open vstup1.txt file.\n";
    return 1;
  }

  ifstream fr2("vstup2.txt"); 
  if(!fr2) {
    cout << "Cannot open vstup2.txt file.\n";
    return 1;
  }
  // tiez mozeme pouzit assert(fr2)

Testovanie konca súboru môžeme robiť pomocou funkcie eof

ifstream fin("vstup.txt");
while(!fin.eof()) {
    char c;
    fin >> c;
    cout << c;
}

Podobne ako pri konzole môžeme použiť funkciu getline, ktorá načíta celý riadok. Nasledovný príklad spočíta v jednotlivých riadkoch počet bodiek.

#include <fstream>
#include <iostream>
using namespace std;

int main(void){
    ifstream f("vstup.txt");;
    string line;
    while (getline(f,line)){
        int pocet = 0;
        for (int i = 0; i < line.length(); i++){
            if (line[i] == '.') {
                pocet++;
            }
        }
        cout << pocet <<endl;
    }
}