Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Info

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
Info

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

Code Block
languagecpp
linenumberstrue
#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

Code Block
languagecpp
linenumberstrue
// 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.

Code Block
languagecpp
linenumberstrue
// 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
languagebash
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
languagecpp
linenumberstrue
// 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
languagebash
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
---------------------------------------

Vlastný údajový typ

V literatúre sa pri práci so štruktúrou veľmi často stretávame s tým, že štruktúra je definovaná ako vlastný údajový typ. Toto je pomerne výhodné, nakoľko potom nie je potrebné písať struct zakaždým, keď sa deklaruje nový prvok (napr. struct Auto auto_3;) Vlastný údajový typ zadefinujeme pomocou príkazu typedef. Pokračujme s príkladom, ktorý sme používali doteraz ale tak, že si zadefinujeme vlastný údajový typ AUTO. Celý kód je zobrazený nižšie. Môžeme si všimnúť, že sa zmenil iba spôsob definície štruktúry, kedy sa pred struct napísal príkaz typedef a názov vytvoreného údajového typu sa zapísal za štruktúru. Vo funkcii main sa následne premenná auto_1R deklarovala ako údajový typ AUTO.


Vo všeobecnosti je tento spôsob práce so štruktúrou preferovaný a odporúčaný. 


Code Block
languagecpp
titleauta_1R.c
linenumberstrue
#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; 

int main()
{
  AUTO auto_1R[3];

  strcpy(auto_1R[0].Znacka,"Skoda");
  strcpy(auto_1R[0].Model,"Favorit");                    
  // Tu pokračuje funkcia main úplne rovnako ako v predchádzajúcom kóde ...

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
languagecpp
linenumberstrue
// 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
languagecpp
titleauta_dynamic.c
linenumberstrue
#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
languagebash
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
languagecpp
linenumberstrue
#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;
}