// agrupar dades de tipus diferents

Estructures en C

Fins ara cada variable guardava una sola dada d'un sol tipus. Una struct n'agrupa diverses sota un sol nom — el primer pas cap a modelar coses del món real.

struct agrupar dades relacionades

Declaració i ús

struct Persona {
    char nom[20];
    int edat;
};

struct Persona p1 = {"Jordi", 16};

printf("%s té %d anys\n", p1.nom, p1.edat);

El bloc struct Persona { ... } no crea cap variable: defineix una plantilla. p1 és una variable d'aquest nou tipus, i s'hi accedeix amb el punt: p1.nom, p1.edat.

Una estructura com a paràmetre

Una struct es passa per valor, igual que un int: la funció en rep una còpia completa.

void aniversari(struct Persona p) {
    p.edat = p.edat + 1;
    printf("Dins: %s té %d anys\n", p.nom, p.edat);
}

Com que p és una còpia, incrementar p.edat dins la funció no toca la variable original — exactament el mateix comportament que vas veure amb incrementa(int n) al capítol de funcions. Ho veuràs en detall a l'execució pas a pas.

typedef estalviar-se escriure "struct" sempre

typedef struct {
    char nom[20];
    int edat;
} Persona;

Persona p1 = {"Jordi", 16};   // ja no cal escriure "struct Persona"

typedef crea un àlies per a un tipus. És purament una comoditat d'escriptura: el comportament és idèntic.

union compartir el mateix espai de memòria

Una union s'escriu igual que una struct, però tots els seus membres comparteixen la mateixa adreça: només n'hi pot haver un de vàlid a la vegada.

union Dada {
    int enter;
    float decimal;
};

union Dada d;
d.enter = 5;
printf("%d\n", d.enter);     // 5

d.decimal = 3.14;
printf("%.2f\n", d.decimal); // 3.14
// a partir d'aquí, d.enter ja no té cap sentit: s'ha sobreescrit

Una struct Dada amb els mateixos membres ocuparia l'espai d'un int més el d'un float; una union Dada només ocupa l'espai del més gran dels dos, perquè es reparteixen el mateix lloc. S'utilitzen quan saps que només et cal un valor o l'altre, mai els dos alhora.

Memòria dinàmica

Fins ara, la mida de qualsevol vector calia saber-la en compilar (#define MIDA 10). Amb malloc pots reservar memòria en temps d'execució, amb la mida que calgui en aquell moment.

Exemple

#include <stdlib.h>

int *v;
int n = 5;

v = malloc(n * sizeof(int));
if (v == NULL) {
    printf("Error reservant memòria\n");
    return 1;
}

// ... fer servir v[0], v[1], ... com un vector normal ...

free(v);

malloc(bytes) reserva aquest nombre de bytes i retorna un apuntador al principi; sizeof(int) et diu quants bytes ocupa un int en aquesta màquina, perquè mai cal donar-ho per fet. Si no hi ha prou memòria lliure, malloc retorna NULL — per això cal comprovar-ho sempre.

Molt important: tota memòria reservada amb malloc s'ha d'alliberar amb free quan ja no es fa servir. Oblidar-ho és una fuita de memòria (memory leak): el programa va consumint cada cop més memòria sense raó.

bonus Apuntadors a funció

Igual que pots tenir un apuntador a un int, pots tenir-ne un a una funció — útil per passar "quin codi executar" com si fos una dada més.

#include <stdlib.h>

int compara(const void *a, const void *b) {
    return *(int *)a - *(int *)b;
}

int v[5] = {5, 2, 4, 1, 3};
qsort(v, 5, sizeof(int), compara);
// v ara és {1, 2, 3, 4, 5}

qsort (de <stdlib.h>) ja porta un algorisme d'ordenació fet — l'únic que li falta saber és com comparar dos elements, i per això li passes compara com a argument: una funció que rep dos apuntadors genèrics i diu quin dels dos és més petit.

Mira les estructures i la memòria dinàmica pas a pas

A la segona variant, fixa't en el contrast amb incrementa(int n): una estructura també es copia en passar-la a una funció.

Estructures

Memòria dinàmica

Codi

Variables

NomValor

Vector (quan n'hi hagi)

Terminal

Pas 1 / 1

Problemes per resoldre

Sense solució ni comprovació automàtica: escriu el programa, compila'l (gcc programa.c -o programa) i prova'l amb els exemples. Les pistes són plegades perquè les obris només si les necessites.

1. Vector d'estructures

Defineix struct Llibre { char titol[30]; double preu; }; i declara un vector de 3 llibres amb valors escrits al codi. Mostra quin és el més car.

El més car és: El Petit Príncep (18.50)
Pistes

struct Llibre llibres[3] = { {"...", 12.0}, {"...", 18.5}, {"...", 9.0} };

Recorre el vector amb un bucle, igual que faries amb un vector d'int, comparant llibres[i].preu amb el candidat actual.

2. Agenda amb memòria dinàmica

Demana quantes persones vols desar, reserva amb malloc un vector d'aquesta mida de struct Persona (nom i edat), demana'n les dades i mostra la mitjana d'edat. No t'oblidis de free al final.

Quantes persones? 2
Nom 1: Jordi
Edat 1: 16
Nom 2: Anna
Edat 2: 20
Mitjana d'edat: 18.00
Pistes

struct Persona *agenda = malloc(n * sizeof(struct Persona)); — igual que amb un vector d'int, només que ara cada "cella" és una estructura sencera.

Pots accedir a agenda[i].nom i agenda[i].edat exactament com si agenda fos un vector normal de structs.

3. Comprovar una unió

Declara union Dada { int enter; float decimal; };. Assigna primer un enter i mostra'l; després assigna-hi un decimal i mostra'l. Finalment, torna a mostrar el membre enter i comenta (en un comentari al codi) per què el resultat ja no té sentit.

Com a enter: 7
Com a decimal: 3.14
Com a enter (ja no és vàlid): -1057904406
Pistes

No cal fer res de especial perquè surti un valor "estrany" al final: simplement torna a llegir d.enter després d'haver escrit a d.decimal. El número exacte que surti no importa — l'important és veure que ja no és el 7 que hi vas posar.

Exercicis d'autoavaluació

Aquests es generen amb valors nous a l'atzar i tenen solució comprovable.

—

Quina serà la sortida per terminal d'aquest programa?