970 likes | 1.1k Views
Introduzione al linguaggio C++ 5 lezioni Lunedì, Giovedì ore 12.00-13.30 Alessandro Lonardo alessandro.lonardo@roma1.infn.it Davide Rossetti davide.rossetti@roma1.infn.it Pagina WEB http://apegate.roma1.infn.it/ ~lonardo Testi base: -Bjarne Stroustrup, Il Linguaggio C++,
E N D
Introduzione al linguaggio C++ 5 lezioni Lunedì, Giovedì ore 12.00-13.30 Alessandro Lonardo alessandro.lonardo@roma1.infn.it Davide Rossetti davide.rossetti@roma1.infn.it Pagina WEB http://apegate.roma1.infn.it/~lonardo Testi base: -Bjarne Stroustrup, Il Linguaggio C++, Addison-Wesley. -Brian W. Kernighan, Dennis M. Ritchie, Il Linguaggio C, Jackson libri.
Programma del corso • Lezione 1 • Paradigmi di programmazione • Dichiarazioni • Tipi • Costanti • Lezione 2 • Operatori • Istruzioni • Funzioni • Header File • Il Preprocessore • Le librerie • Lezione 3 • Classi • Interfacce ed implementazioni • Caratteristiche delle classi • Costruttori e distruttori • Lezione 4 • Classi derivate • Classi astratte • Ereditarietà multipla • Controllo dell’accesso • Memoria dinamica
Lezione 5 • Overload di Operatori • Template • La Standard Template Library • argomenti non trattati a lezione
Paradigmi di Programmazione Programmazione Procedurale Si definiscano le procedure desiderate; Si utilizzino gli algoritmi migliori. -Programmatore concentrato sull’algoritmo -Supporto fornito dai linguaggi: funzioni, procedure. (Fortran, Pascal, C...) -Il programma viene suddiviso in funzioni, ogni funzione realizza un algoritmo o una parte di esso. Es. double sqrt(double arg) { //codice per il calcolo della radice quadrata } void some_function() { double root2 = sqrt(2.0); //... }
Programmazione Modulare Modulo = Dati + Procedure; Si decida quali sono i moduli necessari; Si suddivida il programma in modo che i dati siano nascosti nei Moduli. -Dati nascosti: nomi delle variabili, delle costanti e dei tipi sono resi locali al modulo. -Il linguaggio C consente l’impiego di questo paradigma attraverso Il concetto di unità di compilazione. Es. File stack.h: //dichiarazione della interfaccia per //il modulo stack di caratteri void push(char); char pop(); const int stack_size = 100;
File stack.cc (implementazione de modulo): #include “stack.h” //usa l’interfaccia stack //static significa: simbolo locale //a questo modulo (file) static char v[stack_size]; //lo stack viene inizializzato vuoto static char* p = v; void push(char c) { //implementazione } char pop() { //implementazione }
Uso del modulo stack di caratteri: File bubu.cc: #include “stack.h” //usa il modulo stack void some_function() { push(‘y’); char c = pop(); assert(c == ‘y’); } -in questo file non si ha accesso alla struttura interna dello stack, è possibile utilizzare lo stack solo per mezzo delle funzioni esposte nell’interfaccia del modulo. -Il linguaggio C++ estende il supporto del C alla programmazione modulare attraverso l’uso delle classi.
Astrazione dei dati Si decida quali tipi si desiderano; si fornisca un insieme completo di operazioni per ogni tipo. -estende il concetto di modulo al caso in cui siano necessari più oggetti di un certo tipo (come avrei fatto a dichiarare 2 stack?) -I linguaggi che supportano la programmazione modulare permettono l’astrazione dei dati. -Linguaggi come Ada, Java, C++... supportano il paradigma della astrazione dei dati. File complex.h: //dichiarazione del tipo numero complesso class Complex { double re, im; public: Complex(double r, double i) {re = r; im = i;} Complex(double r) {re = r; im = 0;} friend complex operator+(Complex, Complex); friend Complex operator-(Complex, Complex); friend Complex operator-(Complex);//unario friend Complex operator*(Complex, Complex); friend Complex operator/(Complex, Complex); };
File complex.cc: //implementazione del tipo complex //... Complex operator+(Complex a1, Complex a2) { return Complex(a1.re+a2.re, a1.im+a2.im); } //... File bubu.cc: //uso del tipo complex void some_function() { Complex a(2.0, 1.0), b(3.14), i(0.0, 1.0), c; c = a*i+b; //... } -L’uso del tipo complex definito dall’utente è del tutto analogo a quello dei tipi predefiniti. -Il tipo di dato astratto è una scatola nera. Il suo comportamento non può essere cambiato, se non ridefinendo il tipo. Questa è una limitazione significativa.
Esempio: un sistema grafico che gestisce cerchi, triangoli e quadrati. -Esistono i seguenti tipi astratti: class Point {//...}; class Color {//...}; enum Kind {circle, triangle, square}; //rappresentazione di una forma class Shape { Point center; Color col; Kind k; public: point where() {return center;} void move(point to) {center = to; draw(); } void draw(); void rotate(int); }; -k è un “campo tipo” utile alle funzioni per determinare il tipo di forma su cui si lavora: void Shape::draw() { switch(k) { case circle: //disegna un cerchio case triangle: //disegna un triangolo case square: //disegna un quadrato } }
-Problemi: • draw() (come le altre operazioni) deve conoscere tuttti i tipi • di forme su cui si lavora. Se si introduce una nuova forma il • codice di draw() dovrà essere modificato. • 2. Non è possibile aggiungere nel sistema la gestione di una • nuova forma se non si ha accesso al codice sorgente di ogni • operazione. • 3. Ogni modifica espone il sistema alla introduzione di bug su • codice già sviluppato. • -La sorgente di tutti questi problemi è la mancata espressione della • distinzione tra le proprietà generali di una forma(ha un colore, • ha una posizione, si può disegnare...) e le proprietà di una • forma particolare (il cerchio ha un raggio, si disegna come un • cerchio (!), ...). • -L’espressione di questa distinzione in modo utile per la scrittura del • codice rappresenta la • Programmazione Orientata agli Oggetti • -Il supporto che il linguaggio C++ offre a questo paradigma è il • meccanismo della ereditarietà.
-Il concetto di forma più generale: class Shape { Point center; Color col; //è sparito il fastidioso Kind public: point where() {return center;} void move(point to) {center = to; draw(); } virtual void draw(); //ora è virtual virtual void rotate(int); //ora è virtual }; -“virtual”: può essere ridefinito in una classe derivata -Una forma particolare: class Circle : public Shape { int radius; public: void draw() {//disegna un cerchio!}; void rotate(int) {}//facile implementazione }; -La classe Circle è derivata (sottoclasse) dalla classe Shape. -La classe Shape è di base (superclasse) per la classe Circle.
Esempio di uso: funzione che prende un vettore di size forme e le ruota di angle gradi. void rotate_all(Shape v[], int size, int angle) { int i = 0; while (i<size) { v[i].rotate(angle); i = i+1; } } -l’elemento v[i]è in principio una forma qualsiasi, l’operazione di rotazione sarà quella che gli compete. -Nella fase di progettazione del software è necessario individuare la massima quantità di elementi in comune tra i tipi del sistema e rappresentare queste similitudini utilizzando classi di base comuni. Paradigma di programmazione orientata agli oggetti Si determini quali classi si desiderano; Si fornisca un insieme completo delle operazioni di ogni classe; Si espliciti ciò che hanno in comune per mezzo della ereditarietà
Dichiarazioni -Identificatore C++: sequenza di lettere e cifre, il primo carattere deve essere una lettera (o “underscore”, ‘_’). Non si possono usare keyword. Case sensitive. Buoni identificatori: Hello hello _bubu_ ApeMaia un_identificatore_molto_lungo var1 var2 Non sono accettati: 1var $pippo for lunghezza.massima -Dichiarazione di un identificatore: Prima dell’uso di qualsiasi identificatore bisogna specificare il Suo tipo: Char c; int count = 1; char* name = “ciccio”; Const double pi=3.1415926535897932385 float minus(float arg) { return -arg; } -Queste sono anche definizioni di identificatori: definiscono l’entità alla quale il nome si riferisce. Per le variabiliè la quantità di memoria allocata, per le funzioni la loro implementazione, per le costanti il loro valore.
-le seguenti sono solo dichiarazioni: extern float sqrt(float arg); extern int err_num; struct user; -Una dichiarazione ha effetto in generale in un sottoinsieme del programma (visibilità). int x; // x globale, visibile in tutto il pr. void f() { int x; //x locale, nasconde x globale x = 1; { int x; //locale, nasconde la prec. Locale x = 2; } x = 3; } int* p = &x;
Oggetto : zona di memoria lvalue: espressione che fa riferimento ad un oggetto -Un oggetto viene creato all’atto della sua definizione e distrutto quando non è più visibile (anche i locali definiti static) int a = 1; void f() { int b = 1; //inizializzato ad ogni chiamata static int c = a; //ini. una sola volta cout << “ a = “ << a++ << “ b = “ << b++ << “ c = “ << c++ << endl; } int main() { while ( a < 4 ) f(); } Output: a = 1 b = 1 c = 1 a = 2 b = 1 c = 2 a = 3 b = 1 c = 3
Tipi il tipo specifica le operazioni che si possono compire sul dato e la loro semantica Tipi fondamentali: void Tipi interi: bool char short int int long int Tipi floating point (reali): float double long double Tipi interi senza segno, valori logici, vettori di bit: unsigned char unsigned short int unsigned int unsigned long int Per esplicitare i tipi interi con segno: signed char signed short int signed int signed long int -se il tipo è omesso si assume int
-tipi interi e floating point diversi, diversa occupazione di memoria, velocità di esecuzione... -Il linguaggio definisce solo queste restrizioni: 1==sizeof(char)<=sizeof(short)<=sizeof(int)<=sizeof(long) sizeof(float)<=sizeof(double)<=sizeof(long double) sizeof(I)==sizeof(signed I) == sizeof(unsigned I) Ad esempio, architettura IA32: bool 8 bit char 8 bit short 16 bit int 32 bit long int 32 bit float 32 bit double 64 bit long double 80 bit
Conversione tra i tipi -implicita: in generale si possono mescolare liberamente variabili di tipo diverso in una espressione.(non è un bello stile...) int i = 2; float f, g = 2.0; f = i * g - 4; -esplicita: float r = (float) 2; //cast float r = float(2); -promozioni Tipi derivati -definiti a partire da quelli base o user-defined per mezzo degli operatori di dichiarazione: * puntatore & reference [] array Esempio: int* pi; //tipo = puntatore ad int double& d; //tipo = reference a double float v[10]; //tipo = vettore di 10 float
-Un altro modo di introdurre un tipo derivato è la definizione di una struttura: struct Point { int x; int y; }; Point a,b,c;
Puntatori puntatore: variabile che contiene l'indirizzo di un'altra variabile -In C++ i puntatori hanno un tipo associato (eccezione void *). c char c = 'y'; char* p; p = &c; char c2 = *p; 'y' p c 'y' null c p &c 'y' c p c2 &c 'y' 'y' &c: indirizzo di c. *p: dereferenziazione di p, accesso all'oggetto puntato. -avrei potuto scrivere: char *p = &'y'?
no! ottengo: non-lvalue in unary '&'. Naturalmente posso dichiarare un puntatore a puntatore: p c2 c char** pp = &p; &c 'y' 'y' pp &p **pp = 'z'; p c c2 &c 'z' 'y' pp &p cout << c << '\t' << c2 << endl; in output: z y
-se p è un puntatore di tipo T* allora *p può comparire ovunque ci si aspetti un oggetto di tipo T: int i = 4; int* pi = &i; int* pi2; *pi = *pi + 1; //i=5 pi2 = pi; *pi2 = i * 2; //i=10 Quanto vale i? -void*: è il tipo che corrisponde ai puntatori generici, qualsiasi puntatore può essere convertito a void* e poi riconvertito nel suo tipo originale senza perdita di informazione. Questo tipo è utilissimo come parametro di funzioni.
-In realtà esistono anche puntatori a funzione int (*funp) (int, int); è la dichiarazione di un puntatore di nome funp ad una funzione che accetta due parametri di tipo int e restituendo un tipo int come risultato. La dereferenziazione di funp restituisce una funzione. Esempio: //restituisce il massimo tra arg1 e arg2 int max(int arg1, int arg2) {//...} //restituisce il minimo tra arg1 e arg2 int min(int arg1, arg2) {//...} int (*funp) (int, int); int i = 1, j = 2, k, l; funp = &max; k = (*funp)(i, j); funp = &min; l = (*funp)(i, j); Quanto valgono k ed l?
array tipo T, T[size] è un vettore di size elementi di tipo T. -L'indice è compreso tra 0 e size-1. -size deve essere una costante intera; alcune implementazioni del compilatore (ad es. GNU) permettono l'uso di variabili o espressioni intere. float v[3]; //v[0], v[1], v[2] int m[2][3];//2 vettori di 3 interi char* vpc[10];//vettore di 10 punt. a char -Inizializzazione: //v[] ha 6 elementi int v[] = {137, -12, 53, 12943, 21, -20}; float vf[] = {12.2, 0.1, -22.1}; double id[3][3] ={ {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0} }; char vc[] = {'c', 'i', 'a', 'o', '\0'};
-solo per i vettori di char si può utilizzare una notazione più comoda: char vocali[] = "aeiou"; //in questo caso il carattere di fine stringa //viene aggiunto automaticamente -boundary checking: no! Il compilatore non controlla la correttezza degli indici degli elementi di array. Si può facilmente ottenere un errore. Puntatori ed array -Il nome di un array può anche essere usato come puntatore al suo primo elemento: int v[10]; int* pi = v; *pi = 0;//equivale a v[0] = 0 pi++; //ora pi punta a v[1] *pi = 1;//v[1] = 1 pi--; //ora pi punta a v[0] pi = pi +5 ; //ora pi punta a v[5] int offset = pi - v; //numero di el. tra i 2 p. aritmetica dei puntatori +, -, ++, --. Da usare con grande cautela, è facile puntare ad aree di memoria sbagliate uscita dal programma con errore, il famigerato segmentation fault
Strutture • Meccanismo per introdurre tipi di dato costituiti da un insieme di • elementi di tipi (anche) diversi. • struct Particle { • double p[3]; • double v[3]; • int charge; • }; • -si possono dichiarare variabili di questo nuovo tipo: • Particle p1, p2, p3; • -si può accedere ai campi del tipo usando l'operatore . • p1.p[0] = p1.p[1] = p1.p[2] = 0.0; • p1.v[0] = p1.v[1] = p1.v[2] = 0.0; • p1.charge = -1; • //poi vedremo che risulta più comodo • //utilizzare il costruttore • //... • p2 = p1; • //...
-il nome del tipo risulta utilizzabile anche nella definizione del tipo stesso: struct Link { Link* prev; Link* succ; }; -ma ciò non significa che si possono dichiarare oggetti del nuovo tipo durante la sua dichiarazione! struct NewType { NewType x; //ERRORE IN COMPILAZIONE //... }; -Come si gestiscono i riferimenti incrociati durante le dichiarazioni? Meccanismo della forward declaration.
Esempio: struct List; //dichiarazione non definizione struct Link { Link* prev; Link* succ; List* member_of; }; struct List { Link* head; }; -In generale il nome della struct può essere utilizzato prima della sua definizione quando non è necessario conoscere la sua dimensione. struct Astruct; void f(Astruct); //no problem Astruct a; //Errore! f(a); //Errore!
typedef • -introduce un nuovo nome per un tipo • -comodo per costruire convenzioni proprie: • typedef double Mass; • typedef double Distance; • Mass m1, m2, m3; • Distance d1, d2, d3; • -esempi abbastanza comuni: • typedef unsigned char uchar; • typedef unsigned short ushort; • typedef unsigned int uint; • -è utile per abbreviare tipi complicati (come i puntatori a funzione): • typedef void (*calc_func)(float); • calc_func func_table[10]; • è certamente più espressivo di: • void (* func_table[10])(float);
reference nome alternativo di un oggetto -tipo T, T& significa riferimento a T. int i = 1; int& r = i; //r ed i si rif. allo stesso int int x = r; // x = 1 r = 2; // i = 2 -Una reference deve essere sempre inizializzato (a cosa riferirebbe?) -Inizializzazione reference != assegnamento di variabile -Gli operatori applicati ad una reference non agiscono su di essa, ma sull'oggetto a cui si riferisce: int ii = 0; int& rr = ii; rr++; //è ii che viene incrementato, non rr -una reference non può essere modificata dopo l'inizializzazione. -Come vedremo sono utili come parametri di funzioni e nella definizione degli operatori definiti dall'utente.
costanti senza nome -costanti intere: decimali 0 137 12 3 1 ottali 0 064 0237 esadecimali 0x0 0x3 0x7fff 0xfefe -suffissi U, L, LL: void f(int); void f(unsigned int); void f(long int); void f(long long int); f(3); f(3U); f(3L); f(3LL); -costanti floating point: 0.0 1.37 2. 1.3e10 1.6e-15 -costanti carattere (ASCII, EBCDIC, UNICODE...) 'a' '2' '\n' '\t' si può, ma è meglio evitare (portabilità del codice): '\137' '\x05f' 95 codice ASCII di '_'
costanti con nome la keyword const premessa alla dichiarazione di un oggetto lo rende una costante invece di una variabile (deve essere inizializzato): const int bu = 20; bu++; //ERRORE const char* pippo = "abcde"; -Chi è costante il puntatore o l'oggetto puntato? pippo[2] = 'z'; //ERRORE pippo = "ciccio"; ovvero ho dichiarato un puntatore a costante. -Per rendere costante il puntatore si usa l'operatore *const char *const bubu = "yogi"; bubu[3] = 'a'; bubu = "napo"; //ERRORE ovvero ho dichiarato un puntatore costante. -infine: const char *const cp = "fred"; -notare che non si può: const int x = 10; int* pi = &x; //ERRORE, potrei modificare x const int* pic = &x; //no problem -vantaggi per il compilatore usando const (e ovviamente per l'utente).
enum -un nome simbolico per ogni costante: enum { PICCOLO, MEDIO, GRANDE }; equivale a: const int PICCOLO = 0; const int MEDIO = 1; const int GRANDE = 2; -è possibile assegnare un nome, facendo diventare l'enum un nuovo tipo: enum Verdure { RAPE, BROCCOLI, CIPOLLE }; //... Verdure cose_da_comprare; cose_da_comprare = RAPE; int j = BROCCOLI; Verdure da_preparare = 2; //ERRORE!! Verdure da_preparare = Verdure(2); //OK
-in realtà gli enumeratori si possono inizializzare a piacere: enum Colors { red = 2, green, blue = green + 1, grey = blue * 2 }; //... cout << grey << ' ' << blue << ' ' << green << ' ' << red << endl;; Cosa ottengo in uscita? enum <---> switch
union -definisce piu` modi di vedere lo stesso oggetto: // nell’ipotesi sizeof(int)==4 union MultipleAccess { int word_value; unsigned short halfword_values[2]; unsigned char byte_values[4]; }; -come si usa ? MultipleAccess value; value.word_value = 0xA3458543; //cosi` accedo ai bytes: unsigned char first_byte = value.byte_values[0]; //cosi` alle parole di 16 bit (half word): unsigned short second_halfword = value.halfword_values[1]; cout << hex << value.word_value << ' ' << (int) first_byte << ' ' << second_halfword << endl; -cosa ottengo in uscita?
word = a3458543 byte[0] = 43 halfword[1] = a345 -E' utilissimo quando si abbia necessita di risparmiare memoria (lo stesso spazio occupa oggetti diversi in momenti diversi): enum EntryType { STRING, INT}; union EntryValue { char* string_val; int int_val; }; struct Entry { char* name; EntryType type; EntryValue value; }; //... Entry a[10]; a[0].name = "Pippo"; a[0].type = STRING; a[0].value.string_val = "Amico di Topolino"; a[1].name = "Targa di Paperino"; a[1].type = INT; a[1].value.int_val = 313;
campi di bit -modo per inserire oggetti di dimensioni ridotte in una sola word (economizzando lo spazio occupato). struct { unsigned int sign : 1; unsigned int exponent: 8; unsigned int fraction0: 7; unsigned int fraction1: 16; } number; -i campi si comportano come degli interi (di dimensione ridotta) -Tutti i dettagli (come avviene l'allocazione dei campi in memoria...) dipendono dalla macchina. -Tipo di dato con cui è facile scrivere codice non portabile
operatori • -aritmetici (tipi interi e floating point): • + - * / • % resto della div. int (modulo) • ++ -- pre e post incremento/decremento • - + unari • -esempio: • int i, j, inc_i, j_inc; • i = j = 3; • inc_i = ++i; • j_inc = j++; • cout << i << '\t' << j << '\t' • << inc_i << '\t' << j_inc << endl; • ottengo: • 4 4 4 3
-relazionali: > >= < <= == != -logici: && AND || OR ! NOT le espressioni formate con questi operatori vengono valutate da sin. a destra, bloccandosi non appena si determina il risultato. Attenzione! int ciao() { cout << "Ciao" << endl; return 1; } //... int i = 10; unsigned booleano = (i == 10) || (ciao() == 1); Verremo salutati?
-bit a bit (tipi interi), utili per lavorare con vettori di bit: & AND | OR ^ XOR << shift a sinistra >> shift a destra (logico/aritmetico) ~ complemento ad uno -mascherare (azzerare) insiemi di bit: AND n = n & 0xF0 //11110000 -accendere insiemi di bit: OR n = n | 0x1; //dispari -moltiplicare per potenze di 2 (x = y * 2 z) x = y << z; -dividere per potenze di 2 (x = y / 2 z) x = y >> z; - mascherare il bit meno significativo: n = n & (~0x1);
-assegnamento (semplice e composto): = assegnamento *= /= %= += -= <<= >>= &= |= ^= es: a *= 2; ---> a = a * 2; -vari: . selezione elemento object.member -> selezione elemento pointer->member es: struct Color { int r,g,b; }; //... Color c; Color* pc = &c; //... c.r = pc->r;
[] indicizzazione pointer[expr] • () chiamata di funzione expr(expr_list) • () costruzione valore type(expr_list) • & indirizzo di &lvalue • * dereferenziazione *expr • new crea un oggetto new type • delete distrugge un oggetto delete pointer • sizeof dimensioni del tipo sizeof type • sizeof dimensioni oggetto sizeof expr • :: scope resolution class_name::member • ?: espressione condiz. expr?expr:expr • , virgola expr, expr
associatività • -unari e assegnamento associativi a destra: • a = b = c ---> a = ( b = c ) • *p++ ---> *(p++) //non (*p)++ • -tutti gli altri sono associativi a sinistra • precedenza degli operatori: manuale di riferimento! • -esiste la forma funzionale di quasi tutti gli operatori visti: • double n1 = 1.33; • double n2 = .3E-2; • double result; • result = operator+(n1,n2); • -e` come se il compilatore avesse predefinite e utilizzato le • funzioni speciali: • double operator +(const double &d1, • const double &d2); • idem per: • int operator <(const int &n1, const int &n2); • int operator ~(const int &n1); • int operator >>=(const int &n1, • const int &n2); • -nessuno usa questa forma, di solito, ma servono per l’overloading • nei tipi definiti dall'utente
costrutti if-else permette di esprimere una decisione if(espressione) istruzione_1 else istruzione_2 -per istruzione si intende anche un blocco di istruzioni (sequenza di dichiarazioni ed istruzioni tra parentesi graffe), che a sua volta può contenere altri blocchi... es. if( a > b ) max = a; else max = b;
switch-case permette di operare delle scelte multiple controllando se una espressione assume un certo valore in un insieme di costanti intere switch (espressione) { case const_expr1 : istruzioni case const_expr2 : istruzioni ... default : istruzioni } -Risulta conveniente (e migliora la leggibilità del codice) usare degli enum come valori possibili per i case.
es. enum Animale {CANE, GATTO, TOPO}; Animale bu; //... switch(bu) { case CANE: cout << “BAU!" << endl; break; case GATTO: cout << “MIAO!” << endl; break; case TOPO: cout << “SQUIT!” << endl; break; default: cout << “Un minollo?” << endl; break; }
while permette di eseguire iterativamente una istruzione (o blocco) while (espressione) istruzione espressione viene valutata, se il suo valore != 0 allora viene eseguita istruzione ed espressione viene valutata di nuovo. Il ciclo si interrompe quando espressione diventa falsa (uguale a 0). -istruzione a seconda del valore di espressione puo` anche non esser mai eseguita. es. while(i == 0 && j < 100) { //... if (ww) break; //esci dal while if (kk) continue;//riparti dalla iterazione succ. v1[j] = v2[j] + v3[j++];//attenzione }
do-while • controlla la condizione di uscita al termine • di ogni iterazione • do • istruzione • while (espressione); • -e` eseguito almeno una volta : • int k = 0; • ... • do • { • … • k++; • } while(k < 100);
for struttura iterativa alternativa allo while for (espr1; espr2; espr3) istruzione equivale a: espr1; while (espr2) { istruzione espr3; } -in molti casi è piu' comodo da usare for( solo_la_prima_volta; all_inizio_di_ogni_ciclo; alla_fine_di_ogni_ciclo) { // in qualunque momento posso: // uscire dal ciclo conbreak // oppure andare direttamente // alla iterazione succ. con continue }