Ciele cvičeniaCvičenie je zamerané na prácu so štruktúrovaným údajovým typom resp. štruktúrou (struct). Práca so štruktúrou sa považuje za pokročilé pracovne techniky v jazyku C. Po osvojení si učiva, študent dokáže používať štruktúrovaný údajový typ, dokáže ho definovať a inicializovať. Študent dokáže pristupovať k v štruktúre uloženým údajom a meniť ich. Pristupovať k údajom dokáže priamo alebo prostredníctvom smerníkov. Ďalej si študent po osvojení si učiva prehĺbi vedomosti a zručnosti s prácou so smerníkom a pamäťou, dokáže dynamicky alokovať pamäť pre štruktúru, resp. vlastný údajový typ.
|
Odporúčaná literatúra a dôležité odkazy[1] KPI-FEI-TUKE: Prednáška č. 3 - Structures [2] KPI-FEI-TUKE: Cvičenie č. 5 - Complex Numbers [3] Tutorialspoint: C - typedef [4] ITnetwork.cz: Lekce 13 - Struktury v jazyce C |
Štruktúra v jazyku C je údajový typ, ktorý môžeme uložiť do jednej premennej, ktorá vo svojom vnútri obsahuje niekoľko prvkov [4]. Tieto prvky na rozdiel od polí (1R, 2R...) nemusia byť rovnakého údajového typu. Štruktúra nám teda umožňuje vytvárať premenné, ktoré môžu reprezentovať entity s veľkým množstvom parametrov. Pod pojmom entita si pre potreby výkladu tohto učiva môžeme predstaviť napríklad auto. Každému je zrejmé, že keď sa bavíme o aute môžeme sa rozprávať o mnohých atribútoch (Značka, Model, Rok výroby, Palivo, Farba, ...). Všetky tieto parametre by bolo možné ukladať do separátnych premenných a následne mať systém, ktorý by jednoznačne určoval, ktorý údaj patrí k danému vozidlu. V jazyku C môžeme využiť údajový typ štruktúra, ktorú môžeme nazvať Auto a jednotlivé vozidla vozového parku budú reprezentované ako samostatné entity s vlastnými parametrami.
V nasledujúcom príklade ukážeme ako štruktúru zadefinovať a inicializovať. Existuje niekoľko spôsobov ako to urobiť. Vždy však štruktúru definujeme buď pred funkciou main alebo vo vlastnom hlavičkovom súbore. Definícia môže vyzerať nasledovne:
#include <stdio.h> #include <stdlib.h> #include <string.h> // Tu je definovaná štruktúra. Vidíme, že táto štruktúra obsahuje rôzne údajové typy (reťazce, čísla, znaky). struct Auto { char Znacka[10]; char Model[10]; int Rok; char Palivo; char Farba[10]; }; int main() { // Tu pokračuje funkcia main .... |
Vidíme, že v predošlom bloku kódu sme zatiaľ iba zadefinovali aké atribúty jednotlivé autá budú mať. Ak chceme vytvoriť entitu napr. auto_1 resp. auto_2 ... , ktorá bude mať atribúty štruktúry Auto, tak to môžeme spraviť dvoma spôsobmi. V nasledujúcom kóde sú tieto zobrazené. Pozor, takto sa robí inicializácia premennej a môže sa v kóde urobiť iba raz!
// Tu boli hlavičky a definícia štruktúry Auto int main() { // 1. spôsob - Atribúty sú uvedené v takom poradí ako je definovaná štruktúra. // V takom prípade stačí ak sú uvedené iba hodnoty týchto atribútov. struct Auto auto_1 = { "Skoda", "Favorit", 1989, 'B', "Modra"}; // 2. spôsob - Atrubúty nastavujeme v ľubovolnom poradí, vtedy používame takzvané - Designated initialisers (C99) struct Auto auto_2 = { .Znacka = "Peugeot", .Model = "307", .Farba = "Strieborna", .Rok = 2004, .Palivo = 'D', }; // Tu pokračuje funkcia main .... |
K jednotlivým prvkom štruktúry pristupujeme cez operátor bodky "." (v prípade ak nepoužívame smerníky!) Nasledujúci kód zobrazuje priradenie parametrov ďalšiemu vozidlu auto_3, ktorého parametre neboli vopred inicializované. Týmto spôsobom môžeme meniť aj atribúty skôr inicializovaných vozidiel (auto_1 a auto_2). V nasledujúcom kóde sú aj vypísané atribúty jednotlivých vozidiel.
// Tu boli hlavičky a definícia štruktúry Auto int main() { struct Auto auto_1 = { "Skoda", "Favorit", 1989, 'B', "Modra"}; struct Auto auto_2 = { .Znacka = "Peugeot", .Model = "307", .Farba = "Strieborna", .Rok = 2004, .Palivo = 'D', }; struct Auto auto_3; // Iba deklarácia, bez inicializácie. // Platí to čo už vieme zo ZAP a predošlých hodín PROG. Reťazce musíme // skopírovať cez strcpy(). strcpy(auto_3.Znacka,"Citroen"); strcpy(auto_3.Model,"Saxo"); strcpy(auto_3.Farba,"Zlta"); auto_3.Rok = 2001; auto_3.Palivo = 'B'; printf("\n---------------------------------------\n"); printf("Znacka >> %s\n", auto_1.Znacka); printf("Model >> %s\n", auto_1.Model); printf("Rok >> %d\n", auto_1.Rok); printf("Palivo >> %c\n", auto_1.Palivo); printf("Farba >> %s\n", auto_1.Farba); printf("---------------------------------------\n"); printf("Znacka >> %s\n", auto_2.Znacka); printf("Model >> %s\n", auto_2.Model); printf("Rok >> %d\n", auto_2.Rok); printf("Palivo >> %c\n", auto_2.Palivo); printf("Farba >> %s\n", auto_2.Farba); printf("---------------------------------------\n"); printf("Znacka >> %s\n", auto_3.Znacka); printf("Model >> %s\n", auto_3.Model); printf("Rok >> %d\n", auto_3.Rok); printf("Palivo >> %c\n", auto_3.Palivo); printf("Farba >> %s\n", auto_3.Farba); printf("---------------------------------------\n"); |
ab123cd@zapfei:dir$ gcc -Werror -Wall auta.c -lm -o AUTA ab123cd@zapfei:dir$ ./AUTA --------------------------------------- Znacka >> Skoda Model >> Favorit Rok >> 1989 Palivo >> B Farba >> Modra --------------------------------------- Znacka >> Peugeot Model >> 307 Rok >> 2004 Palivo >> D Farba >> Cervena --------------------------------------- Znacka >> Citroen Model >> Saxo Rok >> 2001 Palivo >> B Farba >> Zlta --------------------------------------- |
Môžeme si všimnúť, že takýto prístup k vytváraniu jednotlivých vozidiel vozového parku je dosť "neohrabaný". Vhodnejšie by bolo, ak by jednotlivé autá boli položkami 1R poľa, ktoré by využívalo štruktúru. Toto si ukážeme v nasledujúcom texte.
Ak spracovávame veľké množstvo dát (napr. databázu áut na predajni), je možné tieto mať uložené v 1R poli. Toto môžeme urobiť nasledovne:
// Tu boli hlavičky a definícia štruktúry Auto int main() { // Vidíme, že štruktúra je teraz 1R poľom, // v ktorom sú alokované tri položky. struct Auto auto_1R[3]; strcpy(auto_1R[0].Znacka,"Skoda"); strcpy(auto_1R[0].Model,"Favorit"); strcpy(auto_1R[0].Farba,"Modra"); auto_1R[0].Rok = 1989; auto_1R[0].Palivo = 'B'; strcpy(auto_1R[1].Znacka,"Peugeot"); strcpy(auto_1R[1].Model,"307"); strcpy(auto_1R[1].Farba,"Cervena"); auto_1R[1].Rok = 2004; auto_1R[1].Palivo = 'D'; strcpy(auto_1R[2].Znacka,"Citroen"); strcpy(auto_1R[2].Model,"Saxo"); strcpy(auto_1R[2].Farba,"Zlta"); auto_1R[2].Rok = 2001; auto_1R[2].Palivo = 'B'; printf("\n---------------------------------------\n"); for(int i=0; i<3; i++) { printf("Znacka >> %s\n", auto_1R[i].Znacka); printf("Model >> %s\n", auto_1R[i].Model); printf("Rok >> %d\n", auto_1R[i].Rok); printf("Palivo >> %c\n", auto_1R[i].Palivo); printf("Farba >> %s\n", auto_1R[i].Farba); printf("---------------------------------------\n"); } return 0; } |
ab123cd@zapfei:dir$ gcc -Werror -Wall auta.c -lm -o AUTA ab123cd@zapfei:dir$ ./AUTA --------------------------------------- Znacka >> Skoda Model >> Favorit Rok >> 1989 Palivo >> B Farba >> Modra --------------------------------------- Znacka >> Peugeot Model >> 307 Rok >> 2004 Palivo >> D Farba >> Cervena --------------------------------------- Znacka >> Citroen Model >> Saxo Rok >> 2001 Palivo >> B Farba >> Zlta --------------------------------------- |
Jazyk C by bez používania smerníkov stratil svoju snáď najväčšiu silu. Preto je potrebné osvojiť si prácu so smerníkom aj v prípade štruktúr. Hlavným rozdielom je to, že k prvkom štruktúry nepristupujeme cez bodku ale cez šípku "->". Pre jednoduchosť stále ostaňme pri príklade s autami, tieto sme mali uložené separátne auto_1, auto_2 ... . Povedzme, že výpis jednotlivých vozidiel chceme spraviť cez samostatnú funkciu PrintCars(). Táto funkcia bude mať vstupný parameter typu smerník na štruktúru, ktorá je údajového typu AUTO.
// Tu boli hlavičky a definícia údajového typu AUTO (to isté ako v kóde vyššie) // Funkcie, ktoré používajú údajový typ AUTO, je potrebné deklarovať až potom // ako je definovaná štruktúra! void PrintCars(const AUTO*); int main() { AUTO auto_1 = { "Skoda", "Favorit", 1989, 'B', "Modra"}; // Funkcii je potrebné zaslať adresu premennej. Použijeme '&' PrintCars(&auto_1); return 0; } // Funkcia očakáva smerník na údajový typ AUTO void PrintCars(const AUTO* auta) { // Používame '->' namiesto '.' pretože tu pracujeme s adresou danej štruktúry printf("\n---------------------------------------\n"); printf("Znacka >> %s\n", auta->Znacka); printf("Model >> %s\n", auta->Model); printf("Rok >> %d\n", auta->Rok); printf("Palivo >> %c\n", auta->Palivo); printf("Farba >> %s\n", auta->Farba); printf("---------------------------------------\n"); } |
V poslednej časti tohto tematického celku si ukážeme ako je možné dynamicky alokovať miesto z volanej funkcie. Vytvoríme jednoduchú funkciu, ktorá od používateľa z konzoly načíta atribúty vozidla. Pre jednoduchosť, jedno vozidlo už načítané je a druhé zadá používateľ. Funkcia bude s návratovou hodnotou smerník a miesto v pamäti bude alokované dynamicky v tele tejto funkcie. Výpis je zabezpečený pomocou funkcie PrintCars, ktorá má tento krát 2 vstupné parametre: smerník typu AUTO a počet aut, ktoré sa majú vypísať. Správnosť riešenia môžeme overiť pomocou nástroja Valgrind.
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char Znacka[10]; char Model[10]; int Rok; char Palivo; char Farba[10]; } AUTO; void PrintCars(const AUTO*, int); AUTO* LoadCar(); int main() { AUTO auto_1R[2]; strcpy(auto_1R[0].Znacka,"Skoda"); strcpy(auto_1R[0].Model,"Favorit"); strcpy(auto_1R[0].Farba,"Modra"); auto_1R[0].Rok = 1989; auto_1R[0].Palivo = 'B'; // Vytvormíme smerník na štruktúru AUTO a zavolá sa funkcia, // ktorá vráti smerník na štruktúru, ktorá má dynamicky // alokovanú pamäť AUTO* Temp_Car = LoadCar(); // Z daného miesta v pamäti si skopírujeme údaje // do lokalnej štruktúry. Je jasné, že takto by // sa to dalo robiť v slučke, toľko krát, koľko // by bolo potrebné! strcpy(auto_1R[1].Znacka,Temp_Car->Znacka); strcpy(auto_1R[1].Model,Temp_Car->Model); strcpy(auto_1R[1].Farba,Temp_Car->Farba); auto_1R[1].Rok = Temp_Car->Rok; auto_1R[1].Palivo = Temp_Car->Palivo; // Už nepotrebujeme túto premennú, tak uvoľníme pamäť free(Temp_Car); PrintCars(auto_1R,2); return 0; } // Funkcia pre načítanie nového auta AUTO* LoadCar() { // Dynamická alokácia AUTO* CAR = malloc(sizeof(AUTO)); printf("\nZadaj vozidlo:\nZnacka:"); scanf("%s",CAR->Znacka); printf("Model:"); scanf("%s",CAR->Model); printf("Farba:"); scanf("%s",CAR->Farba); // Všimnime si &, ktorý používame // keď načítavame jeden char resp. int! printf("Rok:"); scanf("%d",&CAR->Rok); printf("Palivo:"); getchar(); scanf("%c",&CAR->Palivo); return CAR; } // Funkcia pre vytlačenie zoznamu aut void PrintCars(const AUTO* auta, int Pocet_aut) { printf("\n---------------------------------------\n"); for(int i=0; i<Pocet_aut; i++) { // Všimnime si, že teraz nepoužívame šípky. // Je to preto lebo smerník ukázal // na začiatok 1R poľa, ktorého prvkami sú štruktúry. printf("Znacka >> %s\n", auta[i].Znacka); printf("Model >> %s\n", auta[i].Model); printf("Rok >> %d\n", auta[i].Rok); printf("Palivo >> %c\n", auta[i].Palivo); printf("Farba >> %s\n", auta[i].Farba); printf("---------------------------------------\n"); } } |
ab123cd@zapfei:dir$ gcc -Werror -Wall auta_dynamic.c -lm -o AUTA ab123cd@zapfei:dir$ valgrind ./AUTA ==245874== Memcheck, a memory error detector ==245874== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==245874== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info ==245874== Command: ./AUTA ==245874== Zadaj vozidlo: Znacka:Ford Model:Focus Farba:Modra Rok:1999 Palivo:B --------------------------------------- Znacka >> Skoda Model >> Favorit Rok >> 1989 Palivo >> B Farba >> Modra --------------------------------------- Znacka >> Ford Model >> Focus Rok >> 1999 Palivo >> B Farba >> Modra --------------------------------------- ==245874== HEAP SUMMARY: ==245874== in use at exit: 0 bytes in 0 blocks ==245874== total heap usage: 3 allocs, 3 frees, 2,084 bytes allocated ==245874== All heap blocks were freed -- no leaks are possible ==245874== For lists of detected and suppressed errors, rerun with: -s ==245874== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) |
Úloha 1.1
Vypracujte úlohy cvičenia z oficiálnej stránky predmetu.
Úloha 1.2
Upravte svoj program nasledovne:
#include <stdio.h> #include <stdlib.h> typedef struct { int real; int imag; } CMPX; void zobrazit(const CMPX*); CMPX* sucet(const CMPX*, const CMPX*); CMPX* rozdiel(const CMPX*, const CMPX*); CMPX* sucin(const CMPX*, const CMPX*); CMPX Conj(const CMPX*); float Abs(const CMPX*); // sqrt(re^2 + im^2) float Angle(CMPX); // arctg(im/re) int main() { CMPX C1 = {1, 2}; CMPX C2 = { .real = 2, .imag = 1, }; CMPX* C3; C3 = sucet(&C1, &C2); zobrazit(&C1); zobrazit(&C2); zobrazit(C3); free(C3); return 0; } void zobrazit(const CMPX* C) { printf("\n>> C = %d + %dj\n",C->real, C->imag); } CMPX* sucet(const CMPX* C1, const CMPX* C2) { CMPX* ANS = malloc( sizeof(CMPX) ); ANS->real = C1->real + C2->real; ANS->imag = C1->imag + C2->imag; return ANS; } |