Adreces de memòria i apuntadors
Cada variable viu en algun lloc concret de la memòria del programa: la seva adreça. Un apuntador és una variable que, en lloc de guardar un número o un caràcter, guarda una adreça.
Els dos símbols nous
| Símbol | Significat |
|---|---|
&x | "l'adreça de x" — on viu x a la memòria |
*p | "el valor al qual apunta p" — el contingut d'aquella adreça |
Exemple
int x = 42;
int *p; // p és un apuntador a int
p = &x; // p ara guarda l'adreça de x
printf("%d\n", x); // 42
printf("%d\n", *p); // 42 — el mateix, a través de p
int *p; declara p com "un apuntador a un int": p no guardarà mai directament un 42, sinó l'adreça d'algun int que el val 42. *p (desreferenciar) et porta fins a aquell valor.
Per fi: una funció que intercanvia de debò
Recordes incrementa(int n) del capítol de funcions? Rebia una còpia i mai tocava la variable original. Amb apuntadors, una funció pot rebre l'adreça d'una variable i modificar-la de debò.
Exemple
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 5;
int y = 8;
swap(&x, &y);
printf("x=%d y=%d\n", x, y);
return 0;
}
La sortida és x=8 y=5: s'han intercanviat de debò. swap no rep els valors 5 i 8, sinó les adreces de x i y; *a = *b no canvia el paràmetre a, canvia el que hi ha a l'adreça on apunta a — és a dir, x mateixa.
Fixa't en el patró: igual que scanf("%d", &edat) necessitava & perquè havia d'escriure un valor a la variable del cridant, qualsevol funció que vulgui modificar una variable de qui la crida necessita la seva adreça.
Vectors com a paràmetre
A diferència d'un int, un vector mai es copia quan el passes a una funció: la funció rep accés directe a les mateixes cel·les de l'original.
Exemple
int suma_vector(int v[], int mida) {
int i, suma = 0;
for (i = 0; i < mida; i++) {
suma = suma + v[i];
}
return suma;
}
int v[] com a paràmetre no crea una còpia del vector: per sota, el que realment es passa és l'adreça de la primera cel·la (per això sol caldre passar també mida: el vector, un cop dins la funció, ja no "sap" quants elements té). Com que no hi ha còpia, si la funció escriu a v[i], modifica el vector original — això és exactament el que farà l'ordenació.
Ordenació: el mètode de la bombolla
Amb vectors com a paràmetre, ja podem escriure una funció que ordeni un vector de debò, modificant-lo des de dins.
La idea
Es recorre el vector comparant parelles d'elements consecutius; si estan desordenats, s'intercanvien. Repetint prou voltes, els elements més grans van "pujant" cap al final, com bombolles.
Exemple
void bombolla(int v[], int mida) {
int i, j, temp;
for (i = 0; i < mida - 1; i++) {
for (j = 0; j < mida - 1 - i; j++) {
if (v[j] > v[j + 1]) {
temp = v[j];
v[j] = v[j + 1];
v[j + 1] = temp;
}
}
}
}
El bucle de fora (i) compta les passades; el de dins (j) recorre el vector comparant v[j] amb v[j+1]. Cada passada deixa un element més "col·locat" al final, per això el bucle de dins es va escurçant (mida - 1 - i). L'intercanvi és exactament el swap de més amunt, fet a mà sobre dues cel·les.
Mira el programa executar-se pas a pas
Fixa't en com el vector del panell "Vector" va canviant quan la funció hi escriu — la prova que no treballa amb cap còpia.
Apuntadors
Vectors i funcions
Codi
Variables
| Nom | Valor |
|---|
Vector
Terminal
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. Intercanviar dos decimals
Escriu una funció swap_double(double *a, double *b) que intercanviï els valors de dues variables double. Demana dos nombres decimals, mostra'ls, intercanvia'ls i torna'ls a mostrar.
Primer: 2.5 Segon: 7.1 Abans: 2.50 7.10 Després: 7.10 2.50
Pistes
És exactament el swap de la teoria, canviant int per double a tot arreu (paràmetres, temp...).
Crida-la com swap_double(&a, &b); — no t'oblidis del &.
2. El màxim d'un vector
Escriu una funció maxim(int v[], int mida) que retorni el valor més gran del vector. Prova-la amb un vector d'uns quants enters escrits directament al codi (no cal demanar-los a l'usuari).
El màxim és: 9
Pistes
Comença suposant que el màxim és v[0], i recorre la resta del vector comparant cada element amb el candidat actual.
Aquí no cal modificar el vector: n'hi ha prou de llegir-lo, així que la funció el pot rebre i prou, sense necessitat de return cap altra cosa que el màxim trobat.
3. Ordenació per selecció
Escriu una funció seleccio(int v[], int mida) que ordeni el vector amb un algorisme diferent de la bombolla: a cada passada, busca l'element més petit de la part encara no ordenada i l'intercanvia amb el primer d'aquesta part.
Abans: 5 2 4 1 Després: 1 2 4 5
Pistes
Bucle de fora i de 0 a mida-2: a cada volta, busca l'índex del mínim entre v[i] i v[mida-1] (un bucle de dins, com a l'exercici 2, però guardant l'índex, no el valor).
Un cop trobat l'índex del mínim, intercanvia'l amb v[i] — igual que a la bombolla, fes-ho amb una variable temp.
4. Invertir un vector
Escriu una funció invertir(int v[], int mida) que giri l'ordre dels elements del vector (el primer passa a ser l'últim, i així), modificant-lo directament — sense crear-ne cap altre.
Abans: 1 2 4 5 Després: 5 4 2 1
Pistes
Intercanvia l'element 0 amb el darrer, l'1 amb el penúltim, i així, parant-te a la meitat (si segueixes després de creuar-te, desfaries la feina).
Amb dos índexs esquerra (comença a 0) i dreta (comença a mida-1) que s'acosten: intercanvia v[esquerra] amb v[dreta] i avança'ls fins que esquerra >= dreta.
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?