Programování v C++, 2. cvičení 1 1 Fakulta jaderná a fyzikálně inženýrská České vysoké učení technické v Praze Zimní semestr 2018/2019
Přehled 1 Operátory new a delete 2 3
Operátory new a delete minule procvičených pojmů opakování základních pojmů příkazy datové typy práce s funkcemi seznámení s IDE QtCreator jednoduché příklady Eukleidův algoritmus směnárníkova úloha
Přehled Operátory new a delete 1 Operátory new a delete 2 3
Zadání úlohy Operátory new a delete Zadání Napište program, ve kterém uživatel za běhu zadá počet prvků pole. Poté rezervujte potřebnou pamět a pole naplňte náhodnými celými čísly. Napište funkci pro nalezení nejmenšího prvku pole a pole vypište na standardní výpis. Náhodná čísla funkce rand: vrací náhodné celé číslo mezi 0 a RAND_MAX funkce srand: inicializace generátoru náhodných čísel aktuální čas jako semínko (funkce time)
Ukazatel Operátory new a delete proměnná, jejíž hodnota představuje adresu v paměti ukazatel na data ukazatel na funkci ukazatel zadán identifikátorem a doménovým typem příklad: int *ui; deklaruje ui jako ukazatel na typ int operátor &: získání adresy proměnné příklad: mějme deklaraci int i;, pak adresu proměnné i do ukazatele ui získáme příkazem ui = &i; operátor *: přístup k paměti, na kterou ukazatel ukazuje (dereference) operátor má vyšší prioritu než aritmetické operátory, lze tedy psát *ui = *ui + 1; ekvivalentní zápisu i = i + 1;
Práce s ukazateli Operátory new a delete ukazatel nikam neukazuje na žádnou platnou adresu v C++ hodnota nullptr (dříve 0), v C předdefinovaná konstanta NULL automatická konverze na logickou hodnotu ukazatel lze použít kdekoliv, kde se očekává logická hodnota ukazatel nikam se konvertuje na false nenulový ukazatel se konvertuje na true ukazatel bez doménového typu: void *v lze mu přiřadit hodnotu libovolného ukazatele: v = &i; pro opačné přiřazení je nutné použít přetypování: int *ui = (int *)v; nelze dereferencovat
Ukazatelová aritmetika Operátory new a delete 1 porovnání ukazatelů u1, u2 stejného typu u1, u2 jsou si rovny, pokud ukazují na stejnou adresu u1 < u2, jestliže u1 ukazuje na nižší adresu než u2 2 ukazatel + celé číslo k ukazateli u na typ T lze přičíst celé číslo n u+n zvětší adresu, na kterou u ukazuje o n*velikost typu T (lze zjistit operátorem sizeof) lze použít operátory ++ a -- 3 rozdíl ukazatelů rozdílem ukazatelů téhož typu, které ukazují na různé prvky stejného pole, je číslo udávající rozdíl indexů prvků, na které ukazují dané ukazatele
Ukazatele a pole Operátory new a delete mějme pole: int pole[100]; int *upole = pole; uloží do ukazatele upole adresu prvního prvku pole, tj. pole[0] z pointerové aritmetiky plyne: pole[j] == *(upole+j) 1 for(int *upole = pole; upole < &pole[n]; upole++) 2 *upole = 0; 3 //efektivnejsi nez "klasicky" zapis 4 for(int i = 0; i < n; i++) 5 pole[i] = 0;
Operátory new a delete : dynamické proměnné nevznikají deklarací, ale jsou alokovány a rušeny práce prostřednictvím ukazatelů alokace proměnné: operátor new operátor vyhradí v paměti místo pro proměnnou požadovaného typu a vrátí ukazatel na toto místo alokace jedné proměnné: int *ui = new int; alokace pole: int *upole = new int[10]; zrušení proměnné operátor delete delete ui; zrušení pole: delete [] pole; uvolní pamět, na kterou ukazuje ui, ale ui nevynuluje detekce úniků paměti - například nástoj memcheck: valgrind --tool=memcheck --leak-check=full soubor
Řešení Operátory new a delete hledání minima v poli: 1 int minimum(int *p, unsigned int n){ 2 int min = p[0]; 3 for(unsigned int i = 1; i < n; i++){ 4 if(min > p[i]){ min = p[i];} 5 } 6 return min; 7 } naplnění pole 1 void napln(int *p, unsigned int n){ 2 srand(time(nullptr)); 3 for(unsigned int i = 1; i < n; i++){ 4 p[i] = rand() % 100; //nahodne cislo mezi 0 a 99 5 } 6 }
Přehled 1 Operátory new a delete 2 3
Motivace dynamická datová struktura počet prvků není potřeba znát v době překladu prvky jsou vytvářeny za běhu podle potřeby prostřednictvím operátoru new...... a rušeny prostřednictvím operátoru delete prvky téhož typu, obecně nemusí být v paměti za sebou prvky umístěny v haldě seznam z hlediska složitosti rychlé vkládání nového prvku na požadovanou pozici (u pole pomalé ) pomalý přístup k požadovanému prvku (u pole rychlý ) velmi často používaná datová struktura součástí standardních knihoven (C++, Java,... )
Terminologie Typy seznamů: jednosměrně zřetězený seznam jednosměrně zřetězený seznam se zarážkou obousměrně zřetězený seznam se zarážkou Struktura prvku: vlastní data ukazatel na další prvek Klasifikace prvků: hlava (angl. head) první prvek seznamu, představuje výchozí bod pro práci se seznamem ohon (angl. tail) prvky seznamu za hlavou zarážka poslední prvek, ukazatel na následující prvek obsahuje nullptr
Zadání Zadání příkladu Napište jednosměrně zřetězený seznam se zarážkou. Implementujte následující operace: vytvoření a zrušení seznamu tisk obsahu seznamu přidání položky na začátek seznamu na konec seznamu za zadaný prvek odstranění položky nalezení položky s požadovanými daty test prázdnosti seznamu
Přehled 1 Operátory new a delete 2 3
Struktury skupina proměnných různých typů, se kterou se zachází jako s celkem obdoba typu záznam (record) z Pascalu Deklarace struktury struct identifikátor {deklarace_složek} seznam_deklarátorů složkou může být proměnná libovolného typu seznam deklarátorů jsou proměnné, pole, ukazatele deklarované struktury
Prvek obsahuje: 1 užitečná data 2 ukazatel na další prvek Struktura prvku: 1 struct Prvek{ 2 Data data; 3 Prvek *dalsi; 4 }; : 1 struct Seznam{ 2 Prvek *hlava; 3 Prvek *zarazka; 4 bool vytvoren = false; 5 };
Přehled 1 Operátory new a delete 2 3
Vytvoření a zrušení seznamu vytvoření prázdného seznamu: 1 vytvoření nového prvku 2 nastavení složky dalsi tohoto prvku na nullptr 3 uložení adresy tohoto prvku do hlavy seznamu 4 uložení adresy hlavy do zarážky seznamu zrušení seznamu 1 vymazání všech prvků seznamu 2 vymazání zarážky
Vytvoření a zrušení seznamu 1 void vytvorseznam(seznam *us){ 2 us->hlava = new Prvek; 3 us->zarazka = us->hlava; 4 us->hlava->dalsi = nullptr; 5 us->vytvoren = true; 6 } 7 8 void zrusseznam(seznam *us){ 9 vycistiseznam(us); 10 delete us->hlava; 11 us->hlava = nullptr; 12 us->vytvoren = false; 13 }
Vymazání všech prvků seznamu Dokud seznam není prázdný: 1 do pomocného ukazatele ulož adresu hlavy 2 hlavu posuň na druhý prvek 3 původní hlavu odstraň (pomocí operátoru delete) 1 void smazprvni(seznam *us){ 2 //1. poznamenej si adresu hlavy 3 Prvek *prvni = us->hlava; 4 //2. posun hlavu na druhy prvek 5 us->hlava = prvni->dalsi; 6 //3. odstran puvodni prvni prvek 7 delete prvni; 8 }
Vymazání všech prvků seznamu Dokud seznam není prázdný: 1 smaž první prvek 1 bool jeprazdny(seznam *us){ 2 return (us->hlava == us->zarazka); 3 } 4 5 void vycistiseznam(seznam *us){ 6 while(!jeprazdny(us)) 7 smazprvni(us); 8 }
Tisk obsahu seznamu na standardní výstup získej ukazatel na hlavu seznamu dokud se s tímto ukazatelem nedostaneš k zarážce, opakuj: 1 vytiskni obsah prvku, na který ukazuje pomocný ukazatel 2 pomocný ukazatel posuň na následující prvek seznamu 1 void vypisseznam(seznam *us){ 2 Prvek *up = us->hlava; //vyjdi od hlavy 3 while(up!=us->zarazka){ 4 std::cout << up->data << "\n"; 5 up = up->dalsi; 6 } 7 }
Přidání nového prvku na začátek seznamu 1 vytvoř nový prvek 2 jako následníka tohoto prvku nastav hlavu seznamu 3 do hlavy seznamu ulož ukazatel na nově vytvořený prvek na konec seznamu 1 užitečná data ulož do zarážky 2 vytvoř nový prvek, do jeho položky dalsi ulož nullptr 3 do položky dalsi zarážky ulož ukazatel na nově vytvořený prvek 4 z nově vytvořeného prvku vytvoř zarážku
Přidání nového prvku na konec a na začátek seznamu 1 void vloznazacatek(seznam *us, Data co){ 2 Prvek *novy = new Prvek; 3 novy->data = co; 4 novy->dalsi = us->hlava; 5 us->hlava = novy; 6 } 7 8 void vloznakonec(seznam *us, Data co){ 9 us->zarazka->data = co; 10 us->zarazka->dalsi = new Prvek; 11 us->zarazka = us->zarazka->dalsi; 12 us->zarazka->dalsi = nullptr; 13 }
Podmíněný výraz 1 if(up == us->zarazka) 2 return nullptr; 3 else 4 return up; použití podmíněného výrazu?: syntaxe: C? E1 : E2 je-li hodnota C převedena na true, je výsledek E1, jinak je výsledkem podmíněného výrazu E2 1 return (up == us->zarazka? nullptr : up);
Hledání prvku se zadanými daty 1 hledaná data ulož do zarážky 2 vyjdi od hlavy, seznam procházej prvek po prvku od hlavy 3 procházení zastav při nalezení hledaných dat 4 otestuj, zda byla data nalezena až v zarážce 1 Prvek* najdi(seznam *us, Data co){ 2 Prvek *up = us->hlava; 3 up->zarazka->data = co; 4 while(up!=us->zarazka){ 5 up = up->dalsi; 6 } 7 //pouziti podmineneho vyrazu 8 return (up == us->zarazka? nullptr : up); 9 }
Procvičené pojmy práce se strukturami práce s textovými řetězci práce s operátory new a delete spojový seznam vytvoření a zrušení seznamu přidání prvku na začátek seznamu odstranění prvku ze začátku seznamu odstranění všech prvků tisk seznamu na obrazovku hledání prvku s požadovanými daty