LABORATORUL 11

Tipuri de date definite de utilizator

Durată: 2 ore Limbaj: C / C++ Precedent: Laboratorul 10 PDF îndrumar EN English version

Tipurile predefinite nu sunt suficiente când datele sunt complexe și trebuie structurate după criterii proprii. C/C++ permite programatorului să își definească tipuri noi, adaptate exact cerințelor aplicației.

1Obiectivele lucrării

struct S { char a; int b; char c; }; pe o masina cu int de 4 octetiaumpluturabcumplutura01234567891011indicii octetilor in interiorul structuriisizeof(S) = 12, nu 6compilatorul insereaza umplutura ca fiecare camp sa inceapa la o adresa aliniata
Fig. - Alinierea campurilor intr-o structura. Octetii de umplutura nu contin date, dar intra in sizeof - motiv pentru care ordinea de declarare a campurilor schimba dimensiunea structurii.
  • Redenumirea tipurilor cu typedef pentru un cod mai lizibil
  • Definirea și utilizarea enumerărilor
  • Construirea structurilor și accesarea membrilor
  • Înțelegerea alinierii în memorie și a octeților de umplutură
  • Folosirea câmpurilor de biți și a uniunilor

2Privire de ansamblu

MecanismCe faceCând se folosește
typedefredenumește un tip existentsimplificarea declarațiilor complicate
enumdefinește un set de constante întregi cu numestări, opțiuni, coduri de eroare
structgrupează date de tipuri diferite sub un numeînregistrări: student, produs, punct
câmpuri de bițimembri alocați la nivel de bitregiștri hardware, indicatori compacți
unionmai mulți membri care împart aceeași zonăeconomie de memorie, reinterpretarea datelor

În plus față de C, limbajul C++ oferă definirea de clase - tipuri care respectă principiile programării orientate pe obiecte (tratate în Laboratorul 13).

3Declarația typedef

exemple
typedef unsigned char  byte;        // byte devine sinonim cu unsigned char
typedef unsigned int   uint;
typedef int            Vector[10];  // Vector este "tablou de 10 intregi"
typedef int          (*Operatie)(int, int);   // pointer la functie

byte     mask = 0xF0;
uint     contor = 0;
Vector   v;                         // echivalent cu int v[10];
Operatie op;                        // echivalent cu int (*op)(int,int);
Utilitatea realăDeclarația int (*tabel[5])(int, int); este greu de citit. Cu typedef devine Operatie tabel[5]; - aceeași semnificație, dar inteligibilă dintr-o privire.

4Enumerarea

sintaxă
enum <id_tip_enum> { id_elem <= const>, ... } <lista_id_var>;

enum Culoare { ROSU, VERDE, ALBASTRU };        // 0, 1, 2 implicit
enum Cod     { OK = 0, EROARE = -1, ATENTIE = 10, GRAV };  // GRAV devine 11

enum boolean { false, true };                  // false=0, true=1
typedef enum { false, true } boolean;          // declaratie echivalenta
RegulăDetaliu
Valoarea implicităprimul element este 0, fiecare următor crește cu 1
Valori explicitese pot atribui; elementele următoare continuă de la acea valoare
Unicitatea numeloridentificatorii trebuie să fie distincți în domeniul lor
Tipul de bazăeste compatibil cu int
De ce sunt utileComparați if (stare == 2) cu if (stare == CONECTAT). A doua formă se explică singură și, la modificarea valorilor numerice, codul rămâne corect fără nicio schimbare.

5Structuri

declarare și utilizare
struct Student {
    char   nume[50];
    int    varsta;
    float  medie;
};                                  // atentie: punct si virgula obligatoriu

struct Student s1;                  // in C se repeta cuvantul struct
Student s2;                         // in C++ nu este necesar

typedef struct {                    // varianta cu typedef, uzuala in C
    char  nume[50];
    int   varsta;
    float medie;
} Student_t;

Student_t s3 = {"Popescu Ion", 20, 9.15};   // initializare la declarare

s3.varsta = 21;                     // acces prin punct
Student_t *p = &s3;
p->medie = 9.40;                    // acces prin sageata, cand avem pointer
OperațieSintaxăObservații
Acces la membrus.membrupentru variabile obișnuite
Acces prin pointerp->membruechivalent cu (*p).membru
Atribuires2 = s1;permisă - se copiază toți membrii
Comparares1 == s2interzisă - trebuie comparat membru cu membru
Structurile nu se pot compara directOperatorul == nu este definit pentru structuri, tocmai din cauza octeților de umplutură, al căror conținut este nedefinit. Două structuri cu aceleași valori pot avea reprezentări binare diferite.

6Simulator: alinierea în memorie

Compilatorul inserează octeți de umplutură pentru ca fiecare membru să înceapă la o adresă potrivită. Comparați cele două structuri de mai jos: conțin exact aceiași membri, dar în ordine diferită - și ocupă spațiu diferit.

struct { char c; int x; char d; } - ordine nefavorabilă
struct { int x; char c; char d; } - ordine optimizată
Regula practicăDeclarați membrii în ordine descrescătoare a dimensiunii (double, apoi int, apoi short, apoi char). Într-un tablou de un milion de structuri, economia devine semnificativă.

7Câmpuri de biți

Membrii unei structuri pot fi alocați la nivel de bit, nu de octet. Se specifică numărul de biți după numele membrului:

exemplu - descrierea unui registru
struct RegistruControl {
    unsigned int activ      : 1;    // 1 bit  -> valori 0 sau 1
    unsigned int mod        : 3;    // 3 biti -> valori 0..7
    unsigned int prioritate : 4;    // 4 biti -> valori 0..15
    unsigned int rezervat   : 8;    // 8 biti nefolositi
};                                  // total 16 biti, adica 2 octeti

struct RegistruControl reg = {1, 5, 12, 0};
AvantajDezavantaj
Economie considerabilă de memorieaccesul este mai lent (necesită deplasări și măști)
Corespondență directă cu regiștrii hardwareordinea biților depinde de compilator
Cod mai lizibil decât măștile manualenu se poate lua adresa unui câmp cu &

8Uniuni

O uniune arată ca o structură, dar toți membrii împart aceeași zonă de memorie. Dimensiunea uniunii este cea a celui mai mare membru, iar la un moment dat este valid un singur membru.

struct vs. union
struct S { int a; float b; char c; };   // sizeof = 12 (4 + 4 + 1 + padding)
union  U { int a; float b; char c; };   // sizeof = 4  (cel mai mare membru)

union U u;
u.a = 65;                    // scriem ca intreg
printf("%c\n", u.c);         // citim ca si caracter: 'A'
                             // ...pentru ca este ACEEASI zona de memorie
Doar un membru este valid la un moment datScrierea unui membru distruge valoarea celorlalți. Uniunea nu reține care membru a fost scris ultima dată - este responsabilitatea programatorului, de regulă printr-un câmp suplimentar de tip enum.
Utilizare tipicăUniunea combinată cu un indicator de tip formează o „variantă": o valoare care poate fi întreg, real sau șir, dar nu simultan.

9Cod sursă

studenti.c - structuri și tablouri de structuri
#include <stdio.h>
#include <string.h>

typedef struct {
    char  nume[50];
    int   varsta;
    float medie;
} Student;

void afiseaza(const Student *s)      // primim adresa, nu copie
{
    printf("%-20s %3d ani   media %.2f\n", s->nume, s->varsta, s->medie);
}

int main(void)
{
    Student grupa[3] = {
        {"Popescu Ion",     20, 9.15f},
        {"Ionescu Maria",   21, 8.70f},
        {"Georgescu Radu",  20, 9.60f}
    };

    printf("Lista grupei:\n");
    for (int i = 0; i < 3; i++) afiseaza(&grupa[i]);

    // calculul mediei generale
    float suma = 0;
    for (int i = 0; i < 3; i++) suma += grupa[i].medie;
    printf("\nMedia generala: %.3f\n", suma / 3);

    // cautarea studentului cu media maxima
    int poz = 0;
    for (int i = 1; i < 3; i++)
        if (grupa[i].medie > grupa[poz].medie) poz = i;
    printf("Cea mai mare medie: %s\n", grupa[poz].nume);

    printf("\nsizeof(Student) = %zu octeti\n", sizeof(Student));
    return 0;
}
enum_stare.c - mașină de stări cu enumerare
#include <stdio.h>

typedef enum {
    OPRIT,          // 0
    PORNIRE,        // 1
    FUNCTIONARE,    // 2
    EROARE = 99
} Stare;

const char* numeStare(Stare s)
{
    switch (s) {
        case OPRIT:       return "OPRIT";
        case PORNIRE:     return "PORNIRE";
        case FUNCTIONARE: return "FUNCTIONARE";
        case EROARE:      return "EROARE";
        default:          return "NECUNOSCUT";
    }
}

int main(void)
{
    Stare s = OPRIT;

    printf("Stare initiala: %s (%d)\n", numeStare(s), s);

    s = PORNIRE;      printf("-> %s (%d)\n", numeStare(s), s);
    s = FUNCTIONARE;  printf("-> %s (%d)\n", numeStare(s), s);
    s = EROARE;       printf("-> %s (%d)\n", numeStare(s), s);

    return 0;
}
aliniere.c - verificarea experimentală a padding-ului
#include <stdio.h>
#include <stddef.h>

struct Nefavorabil { char c; int x; char d; };
struct Optimizat   { int x; char c; char d; };

int main(void)
{
    printf("Nefavorabil: sizeof = %zu\n", sizeof(struct Nefavorabil));
    printf("  offset c = %zu\n", offsetof(struct Nefavorabil, c));
    printf("  offset x = %zu\n", offsetof(struct Nefavorabil, x));
    printf("  offset d = %zu\n", offsetof(struct Nefavorabil, d));

    printf("\nOptimizat:   sizeof = %zu\n", sizeof(struct Optimizat));
    printf("  offset x = %zu\n", offsetof(struct Optimizat, x));
    printf("  offset c = %zu\n", offsetof(struct Optimizat, c));
    printf("  offset d = %zu\n", offsetof(struct Optimizat, d));

    printf("\nAceiasi membri, spatiu diferit!\n");
    return 0;
}
uniune.c - reinterpretarea aceleiași zone
#include <stdio.h>

union Convertor {
    unsigned int  intreg;
    unsigned char octeti[4];
};

int main(void)
{
    union Convertor c;
    c.intreg = 0x12345678;

    printf("Valoare: 0x%X\n", c.intreg);
    printf("Octeti in memorie: ");
    for (int i = 0; i < 4; i++) printf("%02X ", c.octeti[i]);
    printf("\n");

    // pe procesoarele Intel/AMD (little-endian) se afiseaza 78 56 34 12
    if (c.octeti[0] == 0x78) printf("Sistem little-endian\n");
    else                     printf("Sistem big-endian\n");

    printf("\nsizeof(union) = %zu octeti\n", sizeof(union Convertor));
    return 0;
}

10Atelier de cod

Structurile grupează date de tipuri diferite sub un singur nume. În panoul din dreapta, o structură apare cu toate câmpurile ei, iar adresa arată unde începe în memorie.

Un tablou de structuri
#include <stdio.h>
#include <string.h>

typedef struct {
    char   nume[20];
    int    varsta;
    double medie;
} Student;

int main(void)
{
    Student grupa[3];
    int i;
    double sumaMedii = 0;

    strcpy(grupa[0].nume, "Popescu");  grupa[0].varsta = 19; grupa[0].medie = 9.15;
    strcpy(grupa[1].nume, "Ionescu");  grupa[1].varsta = 20; grupa[1].medie = 7.40;
    strcpy(grupa[2].nume, "Vasilescu"); grupa[2].varsta = 19; grupa[2].medie = 8.80;

    printf("%-12s %6s %7s\n", "NUME", "VARSTA", "MEDIA");
    for (i = 0; i < 3; i++) {
        printf("%-12s %6d %7.2f\n", grupa[i].nume, grupa[i].varsta, grupa[i].medie);
        sumaMedii += grupa[i].medie;
    }
    printf("\nMedia grupei: %.2f\n", sumaMedii / 3);
    printf("sizeof(Student) = %d octeti\n", (int)sizeof(Student));
    return 0;
}
Exercițiu - enum și structuri
#include <stdio.h>

typedef enum { OPRIT, PORNIT, EROARE } Stare;

typedef struct {
    int   id;
    Stare stare;
} Dispozitiv;

const char* numeStare(Stare s)
{
    /* Completati: intoarceti "oprit", "pornit" sau "eroare",
       in functie de valoarea primita. Folositi switch. */
    return "?";
}

int main(void)
{
    Dispozitiv d[3] = { {1, PORNIT}, {2, OPRIT}, {3, EROARE} };
    int i;

    for (i = 0; i < 3; i++)
        printf("dispozitivul %d: %s\n", d[i].id, numeStare(d[i].stare));
    return 0;
}

11Sarcini de lucru

  • Definiți o structură Produs (cod, denumire, preț, stoc) și un tablou de produse.
  • Scrieți funcții pentru afișarea listei, calculul valorii totale a stocului și căutarea după cod.
  • Sortați tabloul de structuri după preț, folosind sortarea prin interschimbare.
  • Verificați cu sizeof și offsetof unde apar octeții de umplutură.
  • Reordonați membrii structurii și măsurați economia de spațiu obținută.
  • Definiți o enumerare pentru zilele săptămânii și afișați numele zilei dintr-un număr.
  • Construiți o structură cu câmpuri de biți care descrie un registru de control pe 8 biți.
  • Folosiți o uniune pentru a determina dacă sistemul este little-endian sau big-endian.

12Aplicație de aprofundare

ExtindereImplementați o mică bază de date de studenți, cu structuri alocate dinamic: adăugare, ștergere, căutare după nume, sortare după medie și salvarea listei în fișier. Adăugați un membru de tip enum pentru statutul studentului (buget, taxă, exmatriculat) și un câmp de biți care reține situația la cele opt examene. Comparați dimensiunea structurii înainte și după optimizarea ordinii membrilor.

13Întrebări de verificare

14Resurse