Ciele cvičenia
Cvič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.
- Štruktúrovaný údajový typ - Štruktúry v jazyku C
- Definícia vlastného údajového typu
- Smerník na štruktúru
- Dynamická alokácia pamäte a štruktúra
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úrovaný údajový typ
Š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:
Definícia
#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!
Inicializácia
// 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 ---------------------------------------
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.
// 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.
#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:
- 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
#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;
}