Salta al contingut
Llegat fiber

Contenidors, memòria i iteradors

El vector guarda els elements seguits a la memòria; les estructures d'aquest tema no. Per això no s'hi pot accedir per posició, i t'hi mous amb un iterador.

Conceptes clau

  • Estructura lineal: [a1,…,an][a_1, \dots, a_n]
  • Vector: un sol bloc contigu a la memòria
  • Llista: nodes repartits per la memòria
  • Iterador: begin(), end(), *it i ++it

Per què?

Fins ara hem estat utilitzant els vectors.

En aquest tema aprendràs altres estructures d’aquest estil: piles, cues, llistes, conjunts i diccionaris.
Totes són contenidors: objectes que en guarden d’altres.
I totes són genèriques: les fas servir amb el tipus d’element que vulguis, entre angles (stack<int>, list<string>…), com el vector.

Estructures lineals

Una estructura lineal guarda els elements en fila, l’un darrere l’altre:

[a1,a2,…,an][a_1, a_2, \dots, a_n]

Cada element (menys l’últim) té un següent, i cada element (menys el primer) té un anterior.

El vector és una estructura lineal, i la majoria de les d’aquest tema també.
La diferència és com es guarden a la memòria.

Com es guarden a la memòria

La memòria de l’ordinador és una fila molt llarga de caselles.
Cada casella té un número, la seva adreça, que diu on és.
Les adreces s’escriuen en hexadecimal, amb 0x al davant.

0x1000x1010x1020x1030x1040x1050x1060x107
La memòria: una fila de caselles, cadascuna amb la seva adreça a sota.

1. El vector: tot seguit

Un vector guarda tots els elements en un sol bloc, l’un just a continuació de l’altre.
Es diu que el vector és contigu a la memòria.

0x10030x10180x10210x10350x1040x1050x106vv: un sol bloc
El vector v = [3, 8, 1, 5] a la memòria: els quatre elements van seguits, de l'adreça 0x101 a la 0x104.

Com que van seguits, el vector sap on és cada element sense buscar-lo: v[i] és ii caselles després de v[0].

  • v[2] és a la casella 0x101+2=0x103\texttt{0x101} + 2 = \texttt{0x103}. Per trobar v[1000] tampoc no cal passar pels d’abans: és una suma, i ja hi és.

Alerta: el bloc no pot tenir forats.
Si insereixes o esborres un element enmig, tots els de després s’han de desplaçar.

3071821354s'han desplaçat
Inserir el 7 a la posició 1 de [3, 8, 1, 5]: el 8, l'1 i el 5 s'han de moure una casella a la dreta.

2. Les estructures d’aquest tema: en nodes

Les estructures d’aquest tema no van en un sol bloc.

A la llista, per exemple, cada element es guarda en un tros a part, que es diu node, allà on hi hagi lloc a la memòria.
Cada node guarda l’element i l’adreça del node següent (i la de l’anterior).
Així la fila es manté, encara que els nodes estiguin repartits per tota la memòria.

0x10030x1010x1020x10310x1040x10580x1060x10750x108primer
La llista [3, 8, 1, 5] a la memòria: cada node és on hi havia lloc, i cada fletxa és l'adreça del següent que guarda el node.

Com que el lloc on és cada node no diu res, la llista es dibuixa en ordre, seguint les fletxes:

3815
La mateixa llista dibuixada en ordre. El node ratllat és el final: no té cap dada, però existeix a la memòria.

Això té dues conseqüències, al revés que el vector:

  • Inserir o esborrar enmig és ràpid: només canvien les fletxes dels nodes del costat. No es mou cap altre element.
37815
Inserir el 7 després del 3: es crea un node nou i només canvien les fletxes del 3 i del 8.
  • Anar a la posició kk és lent: no hi ha cap suma que digui on és el node kk. Has de començar pel primer i seguir les fletxes, kk vegades.
3815
Per arribar a l'1, a la posició 2, cal començar pel 3 i seguir dues fletxes. Per a la posició 1000, en caldrien 1000.

Alerta: aquesta diferència es nota molt quan hi ha molts elements.
En un vector d’un milió d’elements, inserir al principi vol dir desplaçar-ne un milió; en una llista, canviar dues fletxes.
En una llista d’un milió d’elements, arribar a l’últim vol dir seguir un milió de fletxes; en un vector, fer una suma.

Per això una llista no té l[k].
Per moure-t’hi necessites una altra cosa: un iterador.

Iteradors

Un iterador és un objecte que assenyala un element d’un contenidor, com si hi posessis el dit a sobre.
Des d’aquell element, el pots fer avançar al següent o retrocedir a l’anterior, seguint les fletxes.

3815endit
L'iterador it assenyala el 8. La caixa ratllada és el final, end(): no té cap dada, però existeix a la memòria.

Cada contenidor té el seu tipus d’iterador, que s’escriu amb el nom del contenidor al davant:

list<int>::iterator it;

Aquestes són les operacions bàsiques:

Operació Què fa
l.begin() Retorna un iterador que assenyala el primer element.
l.end() Retorna un iterador que assenyala el final: la posició just després de l’últim.
No té cap element: no hi pots fer *it.
*it L’element que assenyala it.
++it Fa avançar it al següent.
--it Fa retrocedir it a l’anterior.
it = l.begin()3815endit++it3815enditquan it == l.end()3815endit
Recórrer una llista: comences a begin() i avances amb ++it fins que arribes a end().

Per recórrer tota la llista, comences a begin() i avances fins que arribes a end():

list<int> l = {3, 8, 1, 5};
for (list<int>::iterator it = l.begin(); it != l.end(); ++it) {
    cout << *it << ' ';     // escriu 3 8 1 5
}
  • El bucle s’atura quan it arriba al final, i per això mai no fa *it a end().

Ho veuràs amb calma a Llistes i iteradors.

Els contenidors de la STL

La biblioteca estàndard de C++ (la STL) ja porta aquests contenidors fets.
A PRO2 faràs servir sobretot aquests:

Contenidor #include Què guarda Capítol
vector<T> <vector> Elements en posicions numerades PRO1
stack<T> <stack> Una pila Piles
queue<T> <queue> Una cua Cues
list<T> <list> Una llista amb iteradors Llistes i iteradors
map<K, V> <map> Parells clau-valor ordenats per clau Diccionaris
set<T> <set> Un conjunt ordenat sense repetits Diccionaris

T és el tipus dels elements: el que poses entre angles.

Tens el resum de totes les operacions al chuletari.

Exercicis

ExerciciVector o llista?

Digues si és més ràpid amb un vector o amb una list de molts elements, i per què:

  1. Escriure l’element de la posició del mig.
  2. Afegir un element al principi.
  3. Esborrar tots els elements parells, recorrent l’estructura una vegada.
Mostra la solució
  1. Vector. És contigu: la posició del mig es troba amb una suma. A la llista cal seguir les fletxes des del primer fins al mig.
  2. Llista. Es crea un node i s’enganxa davant del primer. Al vector, tots els elements s’han de desplaçar una casella.
  3. Llista. Cada esborrat només canvia les fletxes dels nodes del costat. Al vector, cada esborrat desplaça tots els elements de després.

ExerciciQuè escriu?

Digues què escriu aquest codi:

list<int> l = {4, 9, 2, 7};
list<int>::iterator it = l.begin();
++it;
++it;
cout << *it << ' ';
--it;
cout << *it << ' ';
Mostra la solució

Escriu 2 9.
it comença al 4; després de dos ++it, assenyala el 2.
Amb --it torna enrere, al 9.