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
- Redenumirea tipurilor cu
typedefpentru 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
| Mecanism | Ce face | Când se folosește |
|---|---|---|
typedef | redenumește un tip existent | simplificarea declarațiilor complicate |
enum | definește un set de constante întregi cu nume | stări, opțiuni, coduri de eroare |
struct | grupează date de tipuri diferite sub un nume | înregistrări: student, produs, punct |
| câmpuri de biți | membri alocați la nivel de bit | regiștri hardware, indicatori compacți |
union | mai 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
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);
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
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 explicite | se pot atribui; elementele următoare continuă de la acea valoare |
| Unicitatea numelor | identificatorii trebuie să fie distincți în domeniul lor |
| Tipul de bază | este compatibil cu int |
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
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ție | Sintaxă | Observații |
|---|---|---|
| Acces la membru | s.membru | pentru variabile obișnuite |
| Acces prin pointer | p->membru | echivalent cu (*p).membru |
| Atribuire | s2 = s1; | permisă - se copiază toți membrii |
| Comparare | s1 == s2 | interzisă - trebuie comparat membru cu membru |
== 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.
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:
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};
| Avantaj | Dezavantaj |
|---|---|
| Economie considerabilă de memorie | accesul este mai lent (necesită deplasări și măști) |
| Corespondență directă cu regiștrii hardware | ordinea biților depinde de compilator |
| Cod mai lizibil decât măștile manuale | nu 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 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
enum.9Cod sursă
#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;
}
#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;
}
#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;
}
#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.
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șioffsetofunde 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
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.