struct în C

Grupează mai multe valori de tipuri diferite într-un singur tip nou, de exemplu numele, vârsta și orașul unei persoane.

Sintaxă
struct nume {
    tip camp1;
    tip camp2;
};
struct nume v = {.camp1 = ..., .camp2 = ...};   // C99
v.camp      // acces direct
p->camp     // acces prin pointer, la fel cu (*p).camp

Ce face struct

Un struct (structură) definește un tip format din mai multe câmpuri. După definire, poți declara variabile de acest tip, le poți copia cu =, trimite funcțiilor și returna din funcții.

Câmpurile se accesează cu punct (p.varsta). Când ai un pointer la structură, folosești săgeata: ptr->varsta, care înseamnă (*ptr).varsta. Din C99 poți inițializa câmpurile după nume: {.nume = "Ana", .varsta = 30}; câmpurile nemenționate devin 0.

Compilatorul poate adăuga octeți de completare (padding) între câmpuri, ca fiecare să fie aliniat corect. De aceea sizeof unei structuri poate fi mai mare decât suma câmpurilor. Structurile mari se trimit de obicei prin pointer (const struct persoana *), ca să nu se copieze.

Exemple

Exemplul 1: O structură pentru un produs

Inițializarea cu nume de câmpuri face codul ușor de citit. Copierea cu = copiază toate câmpurile.

#include <stdio.h>

struct produs {
    char nume[30];
    double pret;
    int stoc;
};

int main(void) {
    struct produs p = {.nume = "Cafea", .pret = 24.5, .stoc = 12};
    struct produs copie = p;
    copie.stoc = 0;
    printf("%s: %.2f lei, stoc %d\n", p.nume, p.pret, p.stoc);
    printf("copia are stoc %d\n", copie.stoc);
    return 0;
}

Rezultat

Cafea: 24.50 lei, stoc 12
copia are stoc 0

Exemplul 2: Pointer la structură și operatorul ->

Funcția modifică structura originală prin pointer. Funcția de afișare primește const, deci promite că nu schimbă nimic.

#include <stdio.h>

struct cont {
    char titular[30];
    double sold;
};

void depune(struct cont *c, double suma) {
    c->sold += suma;
}

void afiseaza(const struct cont *c) {
    printf("%s are %.2f lei\n", c->titular, c->sold);
}

int main(void) {
    struct cont c = {"Ana Popescu", 150.0};
    depune(&c, 350.0);
    afiseaza(&c);
    return 0;
}

Rezultat

Ana Popescu are 500.00 lei

Exemplul 3: Tablou de structuri și padding

Structura are un char și un int (1 + 4 octeți), dar sizeof dă 8 pe acest calculator: compilatorul a adăugat 3 octeți ca int-ul să fie aliniat la 4.

#include <stdio.h>

struct nota {
    char materie;   // 'M' = matematică, 'R' = română
    int valoare;
};

int main(void) {
    struct nota note[] = {{'M', 9}, {'R', 10}, {'M', 7}};
    size_t n = sizeof note / sizeof note[0];
    int suma_m = 0, cate_m = 0;
    for (size_t i = 0; i < n; i++) {
        if (note[i].materie == 'M') {
            suma_m += note[i].valoare;
            cate_m++;
        }
    }
    printf("media la matematică: %.1f\n", (double)suma_m / cate_m);
    printf("sizeof(struct nota) = %zu\n", sizeof(struct nota));
    return 0;
}

Rezultat

media la matematică: 8.0
sizeof(struct nota) = 8

Greșeli frecvente

  • Uiți punctul și virgula de după acolada de închidere a definiției: struct p { int x; } fără ; dă erori greu de înțeles pe rândul următor.
  • Folosești . pe un pointer (ptr.camp) sau -> pe o variabilă. Punctul este pentru structuri, săgeata pentru pointeri la structuri.
  • Compari două structuri cu ==. Nu este permis; compară câmp cu câmp. Nici memcmp nu e sigur, pentru că octeții de padding pot avea orice valoare.
  • Scrii doar produs p; în loc de struct produs p;. În C, numele structurii trebuie precedat de struct, dacă nu ai definit un typedef.

Comenzi înrudite

  • typedef: Dă un nume nou, mai scurt sau mai clar, unui tip existent: o structură, un pointer sau un tip de funcție.
  • union: Ca un struct, dar toate câmpurile folosesc aceeași zonă de memorie, așa că la un moment dat păstrează o singură valoare.
  • enum: Definește un set de constante întregi cu nume, de exemplu zilele săptămânii sau stările unei comenzi.
  • Pointeri (& și *): O variabilă care păstrează adresa altei variabile; cu * citești sau modifici valoarea de la acea adresă.
  • sizeof: Operatorul care spune câți octeți ocupă un tip sau o variabilă; calculat de compilator, rezultatul are tipul size_t.
  • malloc: Cere sistemului un bloc de memorie de mărimea dată, în timpul rulării, și returnează adresa lui sau NULL dacă nu reușește.