7  Estructures de repetició

Objectius

  • Entendre què és un bucle i per què evita repetir codi.
  • Escollir entre while, do-while i for segons la situació.
  • Controlar l’execució amb break i continue.
  • Combinar bucles amb condicions per resoldre problemes.
  • Escriure bucles aniuats per treballar amb estructures de dues dimensions.
  • Generar nombres aleatoris per a jocs i simulacions.
  • Reconèixer i evitar els bucle infinits.

Fins ara els nostres programes s’executaven de dalt a baix, una instrucció rere l’altra, i com a molt triaven un camí o un altre amb un if. Però moltes tasques consisteixen a fer el mateix moltes vegades: sumar cent nombres, demanar dades fins que siguin correctes, dibuixar una graella… Copiar i enganxar la mateixa instrucció cent vegades no és una opció. Per això existeixen les estructures de repetició o bucles.

7.1 Què és un bucle?

Un bucle és una estructura que executa un bloc d’instruccions diverses vegades mentre es compleix una condició.

El bloc que es repeteix s’anomena cos del bucle, i cada repetició s’anomena iteració.

Tot bucle té tres components que has de tenir sempre al cap. Si en falta un, el bucle no funcionarà bé:

flowchart TD
    A([Inici]) --> B["1. Inicialització<br/>(preparar el comptador)"]
    B --> C{"2. Condició<br/>certa?"}
    C -->|Sí| D["Cos del bucle<br/>(què repetim)"]
    D --> E["3. Actualització<br/>(avançar cap al final)"]
    E --> C
    C -->|No| F([Fi])

  1. Inicialització: preparem una variable de control abans d’entrar (per exemple, int i = 1).
  2. Condició: una expressió booleana que es comprova abans de cada iteració.
  3. Actualització: dins del cos, canviem alguna cosa perquè, tard o d’hora, la condició deixi de ser certa.

Si oblides l’actualització, la condició sempre serà certa i el bucle no acabarà mai: és un bucle infinit.

7.2 El bucle while

El while és el bucle més senzill i directe: repeteix el cos mentre la condició sigui certa.

while (condició) {
    // Cos: instruccions que es repeteixen
}

Primer comprova la condició. Si és certa, executa el cos i torna a comprovar-la.

Si és falsa, surt del bucle sense executar el cos ni una vegada.

Exemple: comptar de l’1 al 5.

int i = 1;                                      // 1. Inicialització
while (i <= 5) {                                // 2. Condició
    System.out.println("Comptem: " + i);
    i = i + 1;                                  // 3. Actualització
}
System.out.println("Fet!");

Aquest programa escriu “Comptem: 1” fins a “Comptem: 5” i després “Fet!”.

La variable i comença a 1, i a cada volta creix en 1. Quan arriba a 6, la condició i <= 5 es torna falsa i el bucle acaba.

AlertaError típic: el bucle infinit

Si t’oblides d’actualitzar la variable de control, el bucle no acaba mai:

int i = 1;
while (i <= 5) {
    System.out.println("Comptem: " + i);
    // Ens hem deixat i = i + 1;  → i sempre val 1 i generem un bucle INFINIT!
}

El programa es queda “penjat” escrivint “Comptem: 1” per sempre.

Si això et passa, atura’l amb Ctrl + C a la consola. Comprova sempre que alguna cosa dins del cos acosta la condició cap a fals.

ConsellMini-repte

Escriu un while que escrigui els nombres parells del 2 al 20 (2, 4, 6, …).

Pista: pots avançar de dos en dos amb i = i + 2.

7.3 El bucle do-while

A vegades volem que el cos s’executi almenys una vegada, i comprovar la condició després.

Per exemple, un menú que sempre s’ha de mostrar un cop abans de decidir si el tornem a mostrar. Per a això fem servir do-while:

do {
    // Cos: s'executa com a mínim una vegada
} while (condició);   

La línia on expressem la condició acaba amb un punt i coma final.

Exemple: demanar un nombre positiu i no parar fins que l’usuari l’introdueixi bé:

Scanner sc = new Scanner(System.in);
int edat;
do {
    System.out.print("Introdueix la teva edat (positiva): ");
    edat = sc.nextInt();
} while (edat <= 0);       
System.out.println("Gràcies! Edat: " + edat);

Com que la pregunta s’ha de fer sí o sí un primer cop, do-while hi encaixa millor que while.

La diferència clau entre tots dos és quan es comprova la condició:

%%{init: {"flowchart": {"nodeSpacing": 120}}}%%
flowchart LR
    subgraph while
    W1{"Condició?"} -->|Sí| W2["Cos"]
    W2 --> W1
    W1 -->|No| W3([Fi])
    end
    subgraph do-while
    D1["Cos"] --> D2{"Condició?"}
    D2 -->|Sí| D1
    D2 -->|No| D3([Fi])
    end
    while ~~~ do-while
    classDef noBox fill:none,stroke:none;
    class while,do-while noBox;

Amb while, si la condició és falsa d’entrada, el cos no s’executa cap vegada.

Amb do-while, el cos s’executa sempre almenys una vegada.

AlertaError típic: oblidar el punt i coma

El do-while acaba amb punt i coma després del while (condició);.

Si te’l deixes, el compilador es queixarà. És l’únic bucle que porta punt i coma al final.

7.4 El bucle for

Quan sabem quantes vegades volem repetir (o tenim un comptador clar), el for és l’opció més neta.

Reuneix els tres components del bucle en una sola línia:

for (inicialització; condició; actualització) {
    // Cos
}

Exemple: el mateix comptador que hem vist abans, però amb for:

for (int i = 1; i <= 5; i++) {
    System.out.println("Comptem: " + i);
}

Es llegeix així: “comença amb i = 1; mentre i <= 5, executa el cos; i després de cada volta, fes i++”.

L’operador i++ és una manera curta d’escriure i = i + 1.


Compara les dues formes d’escriure el mateix bucle:

Aspecte while for
Els 3 components Escampats pel codi Junts a la capçalera
Quan usar-lo No saps quantes voltes faràs Saps quantes voltes o tens comptador
Àmbit de i Viu fora del bucle Viu només dins del bucle
Llegibilitat de comptadors Més verbós Més compacte


Tots dos són igual de potents: qualsevol for es pot reescriure com un while i a l’inrevés.

Tria el que faci el codi més clar.

NotaRecorreguts amb for

Usos habituals del bucle for:

// Endavant: 0, 1, 2, 3, 4
for (int i = 0; i < 5; i++) { ... }

// Enrere: 10, 9, 8, ..., 1
for (int i = 10; i >= 1; i--) { ... }

// De dos en dos: 0, 2, 4, 6, 8
for (int i = 0; i < 10; i += 2) { ... }

En programació és molt habitual començar a comptar des de 0. Ho entendràs del tot quan arribem als arrays.

AlertaError típic: el desajust d’una unitat (off-by-one)

És facilíssim equivocar-se en un < per un <=. Aquest bucle fa 6 voltes (de 0 a 5), no 5:

for (int i = 0; i <= 5; i++) { ... }   // 0,1,2,3,4,5 → 6 voltes!

Si volies exactament 5 voltes començant des de 0, la condició ha de ser i < 5.

Aquest error té nom propi perquè és dels més comuns del món: off-by-one.

ConsellMini-repte

Escriu un for que calculi la suma de tots els nombres de l’1 al 100 i la mostri per pantalla.

Pista: fes servir una variable suma inicialitzada a 0 abans del bucle.

7.5 Controlar el bucle: break i continue

A vegades cal alterar el flux normal del bucle des de dins:

  • break: surt del bucle immediatament, encara que la condició fos certa.
  • continue: salta la resta del cos i passa directament a la iteració següent.

Exemple de break. Buscar el primer múltiple de 7 més gran que 50 i parar en trobar-lo:

for (int i = 51; i <= 100; i++) {
    if (i % 7 == 0) {
        System.out.println("Trobat: " + i);
        break;   // No cal seguir buscant
    }
}

Exemple de continue. Escriure només els nombres senars de l’1 al 10:

for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) {
        continue;   // Si és parell, salta l'escriptura
    }
    System.out.println(i);   // Només s'arriba aquí si és senar
}
NotaAnalogia
  • break és sortir del gimnàs a mitja sessió i tancar la porta: s’ha acabat.
  • continue és saltar-te un exercici concret però continuar amb la rutina: passes al següent.
AlertaError típic: abusar del break

break i continue són útils, però si n’omples el codi es fa difícil de seguir.

Sovint una condició ben pensada a la capçalera del bucle és més clara que un break amagat al mig. Fes-los servir amb mesura.

7.6 Bucles aniuats

Un bucle pot contenir un altre bucle dins del seu cos: en diem bucles aniuats. El bucle de fora fa una volta, i per cada volta seva el de dins fa totes les seves. Són imprescindibles per treballar amb graelles, taules i estructures de dues dimensions. Ho necessitarem més endavant.

Exemple: les taules de multiplicar de l’1 al 3.

for (int taula = 1; taula <= 3; taula++) {          
    System.out.println("--- Taula del " + taula + " ---");
    for (int n = 1; n <= 10; n++) {                 
        System.out.println(taula + " x " + n + " = " + (taula * n));
    }
}

El bucle extern tria la taula; per a cada taula, el bucle intern recorre del multiplicand 1 al 10.

En total s’executen 3 × 10 = 30 línies de multiplicació.

ConsellMini-repte

Escriu dos bucles niats que dibuixin un triangle d’asteriscs com aquest (5 files):

*
**
***
****
*****

Pista: a la fila número i, imprimeix i asteriscs. Pots imprimir sense salt de línia amb System.out.print("*") i fer el salt amb System.out.println() al final de cada fila.

7.7 Nombres aleatoris

Per a jocs, simulacions o barrejar dades, necessitem atzar. Java ens el dona amb la classe Random.

Primer l’hem d’importar a dalt del fitxer i després en creem un objecte i li demanem nombres:

import java.util.Random;
...

Random atzar = new Random();

int daus = atzar.nextInt(6) + 1;      // Enter de 1 a 6 (com un dau)
int nota = atzar.nextInt(11);         // Enter de 0 a 10
double x = atzar.nextDouble();        // Decimal de 0.0 (inclòs) a 1.0 (exclòs)
boolean cara = atzar.nextBoolean();   // True o false (llançar moneda)

El mètode nextInt(n) retorna un enter entre 0 (inclòs) i n (exclòs).

Per obtenir un dau (1 a 6) sumem 1 al resultat de nextInt(6), que dona de 0 a 5.

Exemple complert. Joc d’endevinar un nombre. Uneix tot el que hem après (bucle, condicions, aleatoris).

import java.util.Random;
import java.util.Scanner;

public class Endevina {
    public static void main(String[] args) {
        Random atzar = new Random();
        Scanner sc = new Scanner(System.in);

        int secret = atzar.nextInt(100) + 1;   // 1 a 100
        int intent;
        int intents = 0;

        do {
            System.out.print("Endevina el nombre (1-100): ");
            intent = sc.nextInt();
            intents++;

            if (intent < secret) {
                System.out.println("Massa baix!");
            } else if (intent > secret) {
                System.out.println("Massa alt!");
            }
        } while (intent != secret);

        System.out.println("Encertat en " + intents + " intents!");
    }
}


AlertaError típic: pensar que nextInt(6) dona d’1 a 6

nextInt(6) dona 0, 1, 2, 3, 4 o 5 (mai el 6). Si vols un dau real, no oblidis el + 1.

Confondre els límits inclòs/exclòs és una font d’errors molt habitual.

ConsellMini-repte

Simula el llançament de dos daus 10 vegades i escriu, a cada tirada, els dos valors i la seva suma. Compta quantes vegades surt una suma de 7.

7.8 Quin bucle triar?

Amb tres bucles disponibles, com decideixes? Aquesta guia ràpida t’ajudarà:

flowchart TD
    A{"Saps quantes<br/>vegades es repetirà?"} -->|Sí| B["Fes servir <b>for</b>"]
    A -->|No| C{"El cos s'ha d'executar<br/>almenys una vegada?"}
    C -->|Sí| D["Fes servir <b>do-while</b>"]
    C -->|No| E["Fes servir <b>while</b>"]


No és una regla sagrada: qualsevol dels tres pot resoldre qualsevol problema. Però seguir aquest criteri farà que el teu codi sigui més natural de llegir.

7.9 Resum

  • Un bucle repeteix un bloc d’instruccions mentre es compleix una condició. Cada repetició és una iteració.
  • Tot bucle necessita inicialització, condició i actualització. Si falta l’actualització, tens un bucle infinit.
  • Comparativa ràpida de bucles:
    • while: comprova la condició abans; pot no executar-se cap vegada.
    • do-while: comprova la condició després; s’executa almenys una vegada (i porta ; al final).
    • for: reuneix els tres components a la capçalera; ideal quan tens un comptador.
  • break surt del bucle; continue salta a la iteració següent.
  • Els bucles aniuats permeten treballar amb graelles i taules.
  • La classe Random genera atzar; recorda que nextInt(n) dona de 0 a n-1.
  • Vigila el desajust d’una unitat (off-by-one) i el bucle infinit: són els errors més freqüents.

7.10 Per practicar

  1. Compte enrere. Demana un nombre n i escriu la seqüència de n fins a 0, i després “Enlaire!”.
  2. Factorial. Calcula el factorial d’un nombre (5! = 5·4·3·2·1 = 120) amb un for.
  3. Validació robusta. Amb do-while, demana una nota entre 0 i 10 i no acceptis res fora d’aquest rang.
  4. Piràmide. Amb bucles niats, dibuixa una piràmide centrada d’asteriscs d’alçada n.
  5. Endevina millorat. Amplia el joc d’endevinar per limitar-lo a 7 intents màxim, fent servir break.
  6. Pedra, paper, tisora. Fes que l’ordinador jugui a l’atzar amb Random i porti el marcador de partides al millor de 5.
  7. Nombres primers. Escriu tots els nombres primers menors que 100 (pista: un for extern per als candidats i un d’intern per comprovar divisors).