| Info |
|---|
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, si š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.
|
| Info |
|---|
Odporúčaná literatúra a dôležité odkazy[1] KPI-FEI-TUKE: Prednáška č. 3 - Structures [2] KPI-FEI-TUKE: Cvičenie č. 6 5 - Complex Numbers [3] Tutorialspoint: C - typedef [4] ITnetwork.cz: Lekce 13 - Struktury v jazyce C |
Štruktúrovaný údajový typ
Štruktúra v jazyku C je údajový typ, ktorý môže môžeme uložiť do jednej premennej ale tento , ktorá vo svojom vnútri obsahuje niekoľko prvkov [4]. Tieto prvky ale 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, ...). Áno, všetky Všetky tieto parametre by bolo možné ukladať do separátnych premenných a následne mať systém, ktorý by jednoznačne determinovalurčoval, ktorý údaj patrí k danému vozidlu (niečo ako sql databáza). V jazyku C -čku ale pre prácu môžeme využiť údajový typ štruktúra, ktorú môžeme nazvať AUTO Auto a jednotlivé vozidla vozového parku budú reprezentované ako samostatné entity s vlastnými parametrami.
V nasledujúcom príklade si 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:
Definícia
| Code Block | ||||
|---|---|---|---|---|
| ||||
#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. Autoauto_1 resp. Autoauto_2 ... , ktorá bude mať atribúty štruktúry Auto, tak to môžeme spraviť viacerími 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!
Inicializácia
| Code Block | ||||
|---|---|---|---|---|
| ||||
// Tu boli hlavickovyhlavič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.
| Code Block | ||||
|---|---|---|---|---|
| ||||
// 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"); |
| Code Block | ||
|---|---|---|
| ||
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:
| Code Block | ||||
|---|---|---|---|---|
| ||||
// 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; } |
| Code Block | ||
|---|---|---|
| ||
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
---------------------------------------
|
Smerník na štruktúru
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.
| Code Block | ||||
|---|---|---|---|---|
| ||||
// 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");
} |
Dynamická alokácia pamäte a štruktúra
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.
| Code Block | ||||||
|---|---|---|---|---|---|---|
| ||||||
#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");
}
} |
| Code Block | ||
|---|---|---|
| ||
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:
- Funkcie, ktoré treba dokončiť sú deklarované v kóde nižšie.
- Vaše riešenia overte pomocou nástroja Valgrind.
- Štruktúra bude váš vlastný údajový typ CMPX
| Code Block | ||||
|---|---|---|---|---|
| ||||
#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;
} |