Sei sulla pagina 1di 60

Corso Base di Programmazione in C/C++ - Lezione 1

Lezione 1 Introduzione
In questa prima lezione verranno descritti gli strumenti che si hanno a disposizione durante la scrittura del codice C++, si prender familiarit con i termini di uso comune come Funzioni, Procedure, Compilatore e Linker nonch la differenza esistente tra i primi due termini e tra gli ultimi due.

Cos il C++
Il C++ un linguaggio di programmazione, quindi un linguaggio per calcolatori o computer, (abbreviato in seguito come L. di P. o semplicemente linguaggi) ad alto livello nato dallevoluzione di un altro linguaggio di programmazione: il C. Si noti nella precedente frase luso dei termini alto livello. Il mondo della programmazione costellato di molti linguaggi ognuno con delle proprie caratteristiche e come si dice spesso: vicinanza alla macchina. Proprio questa vicinanza permette di fare una suddivisione dei L. di P. in tre gruppi (linguaggi di esempio sono riportati a fianco): 1. Basso Livello: Assembler. 2. Medio Livello: C, Basic, 3. Alto Livello: C++, JAVA, Pascal, MS Visual Basic, In realt esiste anche un altro livello composto da un solo linguaggio: il Linguaggio Macchina, composto da due soli simboli, lo zero (0) e luno (1). Ovviamente oggi nessuno cos folle da scrivere un intero programma come un lunga sequenza di 0 ed 1 ma si ricorre, appunto, ad un L. di P. di pi alto livello, dei linguaggi molto pi vicini al linguaggio umano anche se molto semplificati. Passando da un livello ad un altro pi alto vengono introdotti dei concetti che si astraggono dalla macchina e si avvicinano di pi al modo di pensare delluomo. Proprio questa astrazione ci introduce nella tipologia dei Linguaggi ad Oggetti e nella Programmazione Orientata agli Oggetti (OOP Object Oriented Programming). In seguito si studier cos effettivamente un Oggetto, per ora sufficiente sapere che un Oggetto un modo di racchiudere ed organizzare pezzi di programma in modo da poterli utilizzare in futuro anche in altri progetti. Per ora pensiamo ad un Oggetto come ad una scatola chiusa nella quale inserisco delle informazioni e prendo altre informazioni correlate a quelle inserite. Il C++ fa ampio utilizzo di questo concetto ed stato tra i primi L. di P. ad utilizzare questa astrazione ed per questo che fa parte dei linguaggi ad alto livello mentre il suo antenato, il C, fa parte del livello medio.

Differenze generali con gli altri linguaggi


Come per le diverse lingue parlate dalluomo, anche per le macchine sono stati creati diversi linguaggi ognuno dei quali utilizza un proprio vocabolario ed una propria grammatica. Un qualunque concetto pu essere scritto in tutti i linguaggi ma ci che cambia la forma. Ad esempio, traduciamo nei diversi L. di P. sopra elencati la frase: Incrementare la variabile X di 2. MACCHINA 01011010 10101011 10110111 10101011 11101011... ASSEMBLER mov ax, X add ax, 2 mov X, ax PASCAL C++ X = X + 2; X := X + 2; oppure X += 2;

Inoltre pi il livello a cui appartiene un linguaggio alto, pi indipendente dal tipo di macchina in cui viene scirtto. Ad esempio, il linguaggio macchina completamente dipendente dal tipo di processore in cui viene eseguito: Intel o AMD, Motorola, . LAssembler potrebbe differire in un diverso insieme di istruzioni, il C++ come pure JAVA invece sono completamente indipendenti dalla macchina e dal Sistema Operativo usato (PC, Apple, Linux, Windows, MAC OS,), ci che cambia il Compilatore ed il Linker.

Corso Base di Programmazione in C/C++ - Lezione 1

Il Compilatore
Il procedimento di creazione di un programma scritto in C++ o altro composto di 3 parti che utilizzano 3 strumenti diversi, tutte con un unico scopo: creare la famosa sequenza di 1 e 0 utili al microprocessore per portare a termine il proprio lavoro. Il primo strumento necessario per poter scrivere un programma lEditor di Testo nel quale il programmatore scrive le istruzioni come se fosse un testo qualunque. Quindi il primo procedimento : la scrittura delle istruzioni. Il secondo strumento il Compilatore. Questultimo un particolare tipo di programma che prende in input il file di testo scritto nella prima fase, va alla ricerca di eventuali errori sintattici e se tutto il programma corretto produce un particolare file binario chiamato File Oggetto (con estensione .obj). Se il programma non corretto il compilatore segnala la riga o le righe sbagliate e si ferma. Oggigiorno un compilatore include gia un editor di testo con la particolarit di mettere in evidenza con colori diversi le differenti strutture grammaticali del linguaggio. Il processo che utilizza il compilatore detto: Compilazione.

Il terzo ed ultimo strumento da utilizzare nel processo di creazione di un programma eseguibile il Linker. Questo un altro particolare programma che prende in input il file oggetto prodotto dal compilatore e crea il file binario comprensibile al computer e che pu essere eseguito. Esempi di file binari prodotti dal linker sono i .exe, .dll, .ocx, .bin Anche durante questa fase possono esserci degli errori ed il linker deve essere pronto a segnalarli ed a fermare il processo. Tipici errori del linker sono lincapacit di trovare le librerie esterne da includere nel programma eseguibile. In questi casi per correggere gli errori bisogna indicare al linker i percorsi corretti nei quali si trovano le librerie.

Il Linker

Funzioni e Procedure
Sia il linguaggio C che il C++ sono linguaggi composti da un cero numero di funzioni. Prima di andare avanti necessario chiarire quale differenza esiste tra una funzione ed una procedura. Entrambe sono dei pezzi di codice C/C++ ai quali si accede tramite un nome, il nome della funzione/procedura, ed alla quale possono essere passati dei valori detti parametri. La presenza di questi ultimi non obbligatoria. Per una funzione potrebbe sembrare strano ma in effetti non lo , si pensi ad esempio ad una funzione che restituisce un numero casuale. La differenza fondamentale tra una funzione ed una procedura e che la prima deve restituire un valore, mentre la seconda no. Esempi di funzioni sono: Massimo(x,y), Minimo(x,y,z), Esempi di procedure sono: CancellaSchermo(), ScriviFile(fd,x),

Corso Base di Programmazione in C/C++ - Lezione 1

Come vedremo in seguito in C/C++ possibile scrivere sia funzioni che procedure ma la distinzione solo a livello concettuale infatti una funzione termina con una riga del tipo return X; mentre una procedura pu non contenere listruzione return oppure contenere una riga del tipo return;

Corso Base di Programmazione in C/C++ - Lezione 2

Lezione 2 Composizione di un programma C/C++


In questa lezione gettiamo le basi su come deve essere strutturato un programma C/C++. Individuare le varie parti in cui logicamente diviso. La divisione logica e serve solo al programmatore a tenere ben distinte le diverse sezioni in modo da poterle rintracciare subito, al compilatore con interessa tale divisione (anzi, non ne nemmeno a conoscenza!). Vedremo anche alcune differenze sintattiche tra il C e C++.

Composizione schematica di un programma C/C++


E comune abitudine iniziare il programma C/C++ con la lista dei file esterni da includere nel proprio programma. Un file esterno pu contenere di tutto: definizione di variabili, costanti, funzioni e per il C++ anche oggetti. La sintassi usata in C/C++ per includere un file esterno la seguente: #include <file esterno> oppure #include file esterno

Ogni riga pu contenere una sola direttiva #include, ed ogni direttiva pu contenere un solo file. Il primo modo si utilizza per includere file di librerie contenuti nelle cartelle del compilatore, mentre il secondo modo usato quando si devono includere file scritti da noi stessi. Il numero di #include pu essere grande a piacere, ovviamente pi funzioni vengono incluse nel proprio programma pi il file eseguibile prodotto ed il tempo di compilazione saranno grandi. Vediamo qualche esempio: #include #include #include #include <iostream.h> <stdio.h> <stdlib.h> mio_file.h

Subito dopo la lista delle direttive #include compare la lista delle direttive #define. Tale direttiva viene usata per definire le costanti (valori che non cambiano mai). Un utilizzo avanzato di questa direttiva quello di definire con un nome anche una o pi istruzioni. 4

Corso Base di Programmazione in C/C++ - Lezione 2

La sintassi usata in C/C++ per utilizzare la direttiva #define : #define nome_della_costante valore istruzioni_su_una_riga

#define nome_insieme_di_istruzioni

Vediamo subito qualche esempio: #define PI_GRECO 3.1415926535897932384626433832795 #define EURO_in_LIRE 1936.27 #define _2_ALLA_16 2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2 #define _2_ALLA_32 2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2 \ *2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2 E pratica comune scrivere i nomi delle costanti in maiuscolo in modo da essere ben evidenti nel codice. Le #define utilizzate con istruzioni C/C++ devono estendersi su una sola riga, se si intende scrivere su pi righe bisogna terminare la riga con il carattere \ (backslash). Infine si noti come nella definizione del nome non sono ammessi gli spazi. Dopo le direttive #define vengono dichiarate le variabili globali. Non preoccupatevi se non capite cosa vogliono significare le diverse righe che seguiranno perch la discussione sulle variabili sar ripresa pi in l. Un esempio completo sulla prima parte di un programma il seguente: #include <string.h> #include <iostream.h> #include <conio.h> #define _2_ELEVATO_ALLA_17 2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2 *2 #define GETCH getch(); int i,j; char nome_e_cognome[50]; E possibile definire un numero qualunque di variabili ed eventualmente quelle che hanno lo stesso tipo possono essere scritte separate da una virgola. Il ; alla fine di ogni definizione di variabile obbligatorio. Il nome di una variabile pu essere lungo al massimo 255 caratteri, pu contenere lettere (a, ,z) maiuscole o minuscole, numeri ed il carattere _ (underscore) ma deve iniziare con una lettera oppure con il carattere _. \

Funzioni
Subito dopo le variabili vengono scritte le funzioni. Come gia detto in precedenza, una funzione una entit che prende in input un insieme di parametri (eventualmente vuoto) e ritorna un valore che possiede una certa relazione con i dati in ingresso. La sintassi da seguire la seguente: tipo_dato_ritornato Nome_della_funzione(tipo_dati1 dato1, tipo_dato2 dato2,...) { istruzioni tra cui compare return d; } Ad esempio, una funzione che esegue la quarta potenza del numero intero dato come input ha il seguente aspetto: int X_alla_4(int x) 5

Corso Base di Programmazione in C/C++ - Lezione 2

{ return x*x*x*x; } In un programma C++ prima di implementare la funzione, bisogna scrivere il prototipo. Questultimo semplicemente una riga di codice che stabilisce gli input e gli output della funzione. Esso non esegue alcuna azione diversa da quella da segnalare al compilatore quanto si deve aspettare successivamente nella definizione della funzione. Per i prototipi ci sono due regole da rispettare: 1. Il prototipo deve essere definito prima che il programma principale inizi. 2. Le definizioni delle funzioni devono essere poste dopo la fine del programma principale. Quindi il precedente esempio va corretto nel seguente modo: int X_alla_4(int x); ... //Altri prototipi, segue il Programma principale, il main(), che vedremo poi int X_alla_4(int x) { return x*x*x*x; } Per richiamare una funzione sufficiente scrivere il suo nome ed elencare tra parentesi tonde i valori da assegnare ai suoi parametri. Per lesempio di prima: y = X_alla_4(23); //y assumer il valore 279841

Commenti
E buona norma scrivere delle righe di commento nel programma. Un commento altro non che una spiegazione di cosa verr eseguito nelle prossime istruzioni o nella istruzione a sinistra del commento. Mentre state scrivendo un programma sapete cosa state facendo, ma supponete di dover riutilizzare una parte di codice gia scritto, chi vi dir cosa fanno le righe scritte o meglio, chi vi spiegher come utilizzare quella porzione di codice? Nessuno! A meno che non siete stati furbi a scrivere qualche commento. Il C++ ha due modi di scrivere i commenti: 1. Su riga singola. 2. Su pi righe. Il commento a riga singola si introduce scrivendo i caratteri // seguiti dal testo del commento che pu includere qualunque carattere. Ad esempio: return x*x*x*x; //Ritorna il numero x moltiplicato 4 volte per se stesso ;-) Il commento su pi righe invece si scrive cos: /* La funzione che segue calcola x elevato alla quarta */ int X_alla_4(int x) { return x*x*x*x; } Ovvero il commento viene iniziato con i caratteri /* e concluso con */. Questultimo tipo di commento lunico accettato dai compilatori C. 6

Corso Base di Programmazione in C/C++ - Lezione 2

Oggetti
Passiamo ora alla descrizione, tra laltro introduttiva, sugli oggetti. Un oggetto una entit che permette di racchiudere insieme varabili e funzioni detti rispettivamente Membri Dati e Funzioni Membro. Questo tipo di entit stato introdotto nella programmazione alcuni anni fa ed ha raccolto subito un enorme successo tant che sulla scia del C++ sono nati altri linguaggi ad oggetti come il JAVA, ADA e lObject Pascal evoluzione del Pascal utilizzato nel Borland Delphi. Ma cosa ha di tanto potente la programmazione ad oggetti? Pensiamo un po alla dimensione dei programmi di oggi, composti ad milioni di righe di codice, gestirle come un unico grande insieme di funzioni praticamente impossibile. La potenza della OOP risiede nella capacit degli oggetti di: 1. Ereditariet: Un oggetto, con dei propri dati e funzioni membro, pu ereditare (ovvero farli diventare propri) i dati e le funzioni membro di unaltra classe diventando cos ancora pi potente. 2. Generalizzazione: Questo concetto legato alla ereditariet. Un oggetto viene progettato in modo da poter soddisfare una certa classe di problemi. Tale classe composta da molti sotto-problemi pi specifici, un oggetto pu specializzarsi nella risoluzione di una sotto-classe del marco-problema iniziale e quindi eredita le propriet delloggetto padre in questo modo mettendosi in unottica relativa alloggetto pi specializzato, si dice che il padre generalizza il problema. 3. Incapsulamento: Lincapsulamento non deve essere visto come il semplice raggruppamento di dati e funzioni in ununica entit, quale loggetto. La potenza di questo concetto sta nella capacit di poter definire 3 tipi di incapsulamenti usabili anche contemporaneamente. I 3 modi sono: Pubblico: Allesterno delloggetto possono essere utilizzati solo i dati e le funzioni definite come pubbliche. Privato: Con questo tipo di incapsulamento si realizza lHiding dellinformazione ovvero, i dati e le funzioni membro vengono nascoste al mondo esterno. Solo le funzioni appartenenti alloggetto stesso potranno utilizzare le funzioni e i dati privati. Protetto: Questultima modalit legato alla ereditariet. Come vedremo pi avanti, anche un oggetto pu essere ereditato in modo pubblico, privato e protetto. A seconda di come viene ereditato un oggetto i dati e le funzioni incapsulati in modo protetto saranno visibili o no al figlio. 4. Polimorfismo: Questo concetto tra i pi importanti nella programmazione ad oggetti. Alcuni linguaggi di programmazione supportano gli oggetti ma non il polimorfismo: sono dei falsi linguaggi ad oggetti. Polimorfismo significa molte forme, nei linguaggi di programmazione vuol dire che ad una stessa funzione posso assegnare compiti diversi a seconda delloggetto che ha ereditato una particolare funzione detta virtuale. Questo concetto sar esaminato meglio pi avanti quando si conoscer meglio la programmazione ad oggetti.

Corso Base di Programmazione in C/C++ - Lezione 2

Differenza tra Espressione ed Istruzione


Parlando delle righe di codice C/C++ o di un altro linguaggio di programmazione molto spesso di sente parlare di espressione e di istruzione. Anche se non ancora abbiamo imparato a scrivere un programma C/C++ la distinzione tra i due termini pu essere compresa facendo degli esempi sul linguaggio umano e che potr essere poi applicato al C/C++ senza modifiche. Linguaggio umano Espressioni: La variabile A maggiore della variabile B? A maggiore di B e C uguale a D? Istruzioni: Metti in Y il valore di X pi 1 Se A maggiore di B incrementa X altrimenti decrementa X C/C++ Espressioni: A>B (A>B) && (C==D) Istruzioni: Y = X + 1; if(A>B) x++; else x--;

Funzione main()
Qualunque programma esso sia deve avere un inizio ed una fine. In C/C++ linizio (o come si dice in gergo tecnico, il punto di ingresso) dato da una particolare funzione denominata main. Vediamo subito un esempio di programma C++ vuoto ma sintatticamente corretto: main() { //Dichiarazioni delle variabili locali //istruzioni return; } //Non obbligatorio mettere questa istruzione

Il punto di uscita dal programma, ovvero lultima istruzione eseguita nellistruzione return, se presente, o nella parentesi graffa che termina la funzione main. Ci sono diversi modi di scrivere la funzione main, inoltre questultima ha anche dei parametri, ma non obbligatorio utilizzarli e quindi metterli al contrario del C che invece bisogna inserirli anche se non vengono usati. Vediamo un modo alternativo di scrittura del main: int main(int argc, char* argv[]) { //Dichiarazioni delle variabili locali //istruzioni return 0; //Adesso obbligatorio mettere questa istruzione } Questo tipo di scrittura permette di scrivere programmi che accettano un input dal prompt dei comandi della shell (DOS, UNIX,). Supponiamo che il precedente esempio faccia parte di un file chiamato maxnum.cpp, il cui file eseguibile dopo la compilazione si chiamer maxnum.exe, nella shell sar possibile scrivere: C:\miei_programmi\maxnum 2 2 8 La variabile argc contiene il numero di elementi nella riga di comando (4 nellesempio precedente), mentre laltra variabile, argv, contiene le 4 stringhe della riga di comando (maxnum, 2, -2 e 8). 8

Corso Base di Programmazione in C/C++ - Lezione 2

Riprendiamo per un attimo il discorso sulle variabili. Nellesempio precedente c il commento che accenna alle variabili locali. Una variabile si dice locale quando dichiarata allinterno di una funzione f1. La funzione f1 che dichiara delle proprie variabili locali, pu utilizzare sia quelle globali che quelle locali. Ovviamente le variabili locali di unaltra funzione f2 non sono accessibili allinterno di f1. Quanto detto vale sia per il C che per il C++ ed in generale vale per qualunque L. di P. Il valore zero restituito dallultimo esempio dovrebbe essere utilizzato dal sistema operativo o dal programma che ha mandato in esecuzione un altro processo. Di solito si usa lo zero per indicare la normale terminazione del processo ed un codice diverso da zero per le condizioni di errore.

Corso Base di Programmazione in C/C++ - Lezione 3

Lezione 3 Tipi di Dati Semplici


La lezione che stiamo per iniziare ci permetter di imparare a definire le diverse variabili di cui composto un programma. Definire una variabile significa allocare memoria sufficiente a contenere linformazione desiderata. Una variabile pu essere vista per semplicit come una scatola aperta nella quale posso inserire una sola cosa per volta. Cos come ci sono scatole di diversa forma (tipo) e dimensione, cos esistono variabili di tipo e dimensioni diverse. I tipi di dati possono essere divisi in due insiemi: Tipi semplici Tipi complessi

Tipi semplici
I tipi di dati semplici sono i mattoncini elementari con i quali costruire i tipi pi complessi i quali a loro volta permettono di creare complesse le strutture dati utilizzate nei database, nei sistemi operativi, ecc. In C/C++ i tipi semplici sono i seguenti: int: Permette di rappresentare interi positivi e negativi di 32bit (4 byte), ovvero valori compresi in un intervallo tra 2147483648 e 2147483647. Esistono alcune varianti di questo tipo di dato: o Unsigned: Valore intero senza segno a 32bit. Lintervallo dei numeri rappresentabili e compreso tra 0 e 4294967296. o Short: Permette di rappresentare interi positivi e negativi di 16bit (2 byte), ovvero valori compresi in un intervallo tra 32768 e 32767. o Unsigned Short: Valore intero senza segno a 16bit. Lintervallo dei numeri rappresentabili e compreso tra 0 e 65535. o DWORD: Uguale al tipo Intero, utilizzato nella programmazione per Windows e assembler. o WORD: Uguale al tipo Short, utilizzato nella programmazione per Windows e assembler. char: Permette di memorizzare un carattere. Ha la dimensione di 8bit (1 byte). bool: Tipo di dato utilizzato nella valutazione delle espressioni logiche. Ha la dimensione di 8bit (1 byte) e memorizza 0 per la condizione false (falso) ed 1 per true (vero). float: Tipo di dato per la memorizzazione di valori in virgola mobile a singola precisione. Ha la dimensione di 32bit (4 byte) e lintervallo dei valori compreso tra 3.4x10-38 e 3.4x1038. double: Tipo di dato per la memorizzazione di valori in virgola mobile a doppia precisione. Ha la dimensione di 64bit (8 byte) e lintervallo dei valori compreso tra 5.0x10324 e 1.7x10308. Puntatore: Un puntatore un tipo di dato che contiene lindirizzo di memoria di unaltra variabile. I computer di oggi hanno uno spazio di indirizzamento di 32bit pari a 4GB, un puntatore quindi occupa 4 byte di memoria. void: Non un vero e proprio tipo di dato, ma in C/C++ utilizzato per indicare che ad esempio la funzione non restituisce nulla oppure per indicare un puntatore generico (void *). Per questo ultimo caso, il puntatore sempre di 4 byte.

Tipi Complessi
Se qualche lettore ha gia familiarit con qualche linguaggio di programmazione si sar accorto della mancanza del tipo di dati per le stringhe. In effetti in C non esiste un tipo di dato per le stringhe, mentre in C++ stato creato loggetto String che permette di lavorare con le stringhe come con qualsiasi altro tipo di dato. Il motivo per cui in C non esiste il tipo stringa semplice: una stringa di caratteri altro non che una sequenza finita di caratteri memorizzati uno di seguito allaltro. 10

Corso Base di Programmazione in C/C++ - Lezione 3

Una sequenza possibile realizzarla con qualunque tipo di dato. Per i caratteri si parla di stringhe, mentre per gli altri tipi di parla di vettore o array. Quindi possibile realizzare array di caratteri, interi, double, ecc. La sintassi utilizzata per dichiarare una variabile di tipo array semplicissima: tipo_base_array nome_variabile[num_elem]; Come possiamo osservare, si scrive prima il tipo di base dellarray, poi si mette il nome da assegnare allarray ed infine racchiuso tra le parentesi quadre si mette il numero massimo di elementi che larray potr contenere. Il ; obbligatorio. Vediamo qualche esempio di dichiarazione di array: double Punto_3D[3]; int Top10[10]; char Nome_Cognome[60]; float *Coordinate2D[2]; //Array di 2 puntatori a float E possibile realizzare un array di array? Certamente! Anzi possibile creare array di array di array di array fino a 32 dimensioni. Un array di array detto matrice o anche array bi-dimensionale. La dichiarazione di una matrice o di un vettore multi-dimensionale in generale si fa nel seguete modo: tipo_base_matrice nome_variabile[num_elem1][num_elem2]...[num_elemN]; ovvero: double TavolaPitagorica[12][12]; Il numero di elementi di ogni dimensione non deve essere necessariamente uguale, ovvero avrei potuto anche scrivere: double TavolaPitagorica[20][10]; int Matrice[3][4]; 1 1 2 3 La tabella di sopra mostra lorganizzazione degli elementi, ovvero nella dicitura M[i][j], il numero i indica il numero di righe totali mentre il numero j rappresenta il numero di colonne. Prima di proseguire il discorso con altri tipi di dati complessi, obbligatorio anticipare il discorso sugli operatori matematici e vedere come si legge un elemento di un array o matrice anche perch questo argomento fonte di molti errori logici durante la scrittura di un programma. Per poter selezionare un elemento dellarray si deve utilizzare la scrittura seguente: nume_array[num_elem_desiderato-1] ad esempio primo = Top10[0]; La precedente riga non sbagliata! Lindicizzazione di un vettore inizia da zero (0), quindi avremo Top10[0], Top10[1], ..., Top10[9]; per un totale di 10 elementi. Lo stesso discorso si fa per gli array multi-dimensionali. Lultimo elemento della precedente matrice Matrice[2][3]. Concludiamo questa lezione con la descrizione del tipo di dato che racchiude (o pu racchiudere) tutti i tipi visti finora: la Struttura Struct (o record come viene denominata in altri linguaggi). 2 3 4

11

Corso Base di Programmazione in C/C++ - Lezione 3

struct TModello3D { double Posizione[3]; char NomeOggetto[50]; int ColoreRGB[3]; }; TModello3D Modello; TModello3D ListaModelli[1000]; TModello3D *ModelloTemporaneo; Quello appena visto un semplice esempio di struttura e dichiarazione di variabili che hanno tipo struttura. Una struttura viene dichiarata scrivendo la parola riservata struct: struct nome_struttura { //dichiarazioni variabili }; un altro modo di scrivere una variabile di tipo struttura il seguente: struct { //dichiarazioni variabili } nome_var_struttura; ovvero la variabile di tipo struttura ma questultima non ha nome e quindi non possibile dichiarare nessun altra variabile in modo da utilizzare la stessa struttura. Per poter utilizzare le variabili dichiarate allinterno di una struttura si ricorre alla cosiddetta notazione puntata. Ad esempio: Modello.Posizione[0] = 2; Modello.Posizione[1] = 0; Modello.Posizione[2] = 5; ovvero si utilizza la seguente sintassi: nome_var_Struttura.Nomecampo; se invece abbiamo il puntatore ad una variabile di tipo struttura si utilizza la notazione freccia ->: nome_var_Struttura->Nomecampo; ModelloTemporaneo->Posizione[0] = 2; ModelloTemporaneo->Posizione[1] = 0; ModelloTemporaneo->Posizione[2] = 5; Vediamo ora come si utilizza un array di strutture. Riprendiamo lesempio allinizio della pagina: TModello3D ListaModelli[1000]; il suo uso il seguente: ListaModelli[57].Posizione[2] = 3;

12

Corso Base di Programmazione in C/C++ - Lezione 4

Lezione 4 Operatori Matematici


In questa lezione conosceremo gli operatori del C/C++. Gli operatori sono delle vere e proprie funzioni che prendono dei valori in ingresso e restituiscono altri valori. Gli operatori sono divisi in 2 categorie: aritmetici e logici.

Operatori Aritmetici
Il C/C++ forniscono gli operatori matematici fondamentali: addizione (+), sottrazione (-), moltiplicazione (*), divisione (/) e modulo (%). Vediamo subito un esempio: void main() { int a = 10, b = 5, c = 3, r; r r r r r r r } = = = = = = = a+b; a-b; a*b; a/b; a/c; a%b; a%c; // // // // // // // r r r r r r r contiene contiene contiene contiene contiene contiene contiene 15 5 50 2 3 0 (10/5 = 2 con resto 0) 1 (10/3 = 3 con resto 1)

Nella riga: r = a/c; ad r viene assegnato il risultato della divisione intera 10/3, troncato allintero inferiore, cio 3. I risultati delle divisioni intere vengono troncati, non arrotondati. Loperatore di modulo restituisce il resto della divisione. E molto frequente avere la stessa variabile sia a sinistra che a destra delluguale, cio: r = r op x dove op uno degli operatori matematici, r e x sono gli operandi e il risultato viene memorizzato in r. Il C (e quindi il C++) fornisce una forma compatta di questo tipo di operazione: r op= x Vediamo un esempio: int x = 0; x += 5; x -= 3; x *= 10; x /= 2; x %= 3;

// // // // //

x x x x x

== == == == ==

5 2 20 10 1

Oltre agli operatori binari, il C mette ovviamente a disposizione anche gli operatori unari + e -, che permettono di attribuire un segno ai numeri, secondo il senso comune. Ad esempio: x = -y; ha il significato ovvio di assegnare a x lopposto di y. 13

Corso Base di Programmazione in C/C++ - Lezione 4

Operatori di incremento e decremento


Il C/C++ mette a disposizione del programmatore una forma compatta di incremento e decremento delle variabili. Nessun programmatore serio scriver mai: x += 1; ma piuttosto: x++; che significa: incrementa x di 1. Analogamente: x--; significa decrementa x di 1. Ci sono due versioni degli operatori di incremento e decremento: la versione prefissa e la versione postfissa. Nel pre-incremento loperatore ++ appare prima della variabile mentre nel post-incremento loperatore ++ appare dopo la variabile; analogamente per il decremento. Nel pre-incremento e pre-decremento: ++x; --x; loperazione effettuata prima della valutazione dellespressione risultante. Nel post-incremento e post-decremento: x++; x--; loperazione effettuata dopo la valutazione dellespressione. Per capire meglio la differenza, proviamo il seguente programma: #include <iostream> void main() { int x = 0, y = 5; cout << x: << x cout << ++x: << cout << x++: << cout << x: << x cout << y: << y cout << --y: << cout << y--: << cout << y: << y } In esecuzione si ottiene: x: x: x: x: y: y: y: y: 0 1 1 2 5 4 4 3 14

<< endl; ++x << endl; x++ << endl; << endl; << endl; --y << endl; y-- << endl; << endl;

Corso Base di Programmazione in C/C++ - Lezione 4

Per concludere, come molti hanno fatto, ci piace interpretare il nome C++ come un passo oltre il C.

Operatori ternari
Sempre nellottica della scrittura di codice compatto, esiste un operatore ternario che permette di scegliere tra due espressioni sulla base della valutazione di una terza espressione. La forma generale la seguente: espressione1 ? espressione2 : espressione3 Se lespressione1 viene valutata vera, allora il risultato dellintera espressione espressione2, altrimenti espressione3. Ad esempio: void main() { int x = 3; // se x uguale a 3, assegna 4 a d, altrimenti assegnagli 5 int d = (x==3) ? 4 : 5; }

Operatori logici
Gli operatori logici possono essere suddivisi in due gruppi: quelli normalmente usati nei confronti tra valori e quelli utilizzati per collegare i risultati di due confronti. Ecco una breve serie di esempi relativi al primo gruppo: (a (a (a (a (a (a == b) != b) < b) > b) <= b) >= b) // // // // // // VERA VERA VERA VERA VERA VERA se se se se se se a a a a a a UGUALE a b diversa da b strettamente minore di b strettamente maggiore di b minore o uguale a b maggiore o uguale a b

La sintassi di questi operatori ed il loro significato appaiono scontati, ad eccezione, forse, dell'operatore di uguaglianza "==": in effetti i progettisti del C, constatato che nella codifica dei programmi i confronti per uguaglianza sono, generalmente, circa la met degli assegnamenti, hanno deciso di distinguere i due operatori "raddoppiando" la grafia del secondo per esprimere il primo. Ne segue che a = b; assegna ad a il valore di b, mentre (a == b) esprime una condizione che vera se le due variabili sono uguali. Veniamo al secondo gruppo. Gli operatori logici normalmente usati per collegare i risultati di due o pi confronti sono due: si tratta del prodotto logico ("&&", o and) e della somma logica ("||", o or). (a < b && c == d) (a < b || c == d) !(a == b) // AND: vera se entrambe sono VERE // OR: vera se ALMENO UNA e' VERA // NOT: vera se lespressione false

E' possibile scrivere condizioni piuttosto complesse, ma vanno tenute presenti le regole di precedenza e di associativit. Ad esempio, poich tutti gli operatori del primo gruppo hanno precedenza maggiore di quelli del secondo, la (a < b && c == d) 15

Corso Base di Programmazione in C/C++ - Lezione 4

equivalente alla ((a < b) && (c == d) Nelle espressioni in cui compaiono sia "&&" che "||" va ricordato che il primo ha precedenza rispetto al secondo, perci (a < b || c == d && d > e) equivale a ((a < b) || ((c == d) && (d > e))) Se ne trae, se non altro, che in molti casi usare le parentesi, anche quando non indispensabile, sicuramente utile, dal momento che incrementa in misura notevole la leggibilit del codice e abbatte la probabilit di commettere subdoli errori logici.

Precedenza degli Operatori


Consideriamo ora una serie di assegnamenti: a = b = c = d; Il compilatore C/C++ la esegue assegnando il valore di d a c; poi il valore di c a b; infine, il valore di b ad a. Il risultato che il valore di d assegnato in cascata alle altre variabili; in pratica, che l'espressione stata valutata da destra a sinistra, cio che l'operatore di assegnamento gode di associativit da destra a sinistra. In altre parole, la precedenza si riferisce all'ordine in cui il compilatore valuta gli operatori, mentre l'associativit concerne l'ordine in cui sono valutati operatori aventi la stessa precedenza (non detto che l'ordine sia sempre da destra a sinistra). Le parentesi tonde possono essere sempre utilizzate per definire parti di espressioni da valutare prima degli operatori che si trovano all'esterno delle parentesi. Inoltre, quando vi sono parentesi tonde annidate, vale la regola che la prima parentesi chiusa incontrata si accoppia con l'ultima aperta e che vengono sempre valutate per prime le operazioni pi interne. Cos, ad esempio, l'espressione a = 5 * (a + b / (c - 2)); valutata come segue: dapprima calcolata la differenza tra c e 2, poi viene effettuata la divisione di b per tale differenza. Il risultato sommato ad a ed il valore ottenuto moltiplicato per 5. Il prodotto, infine, assegnato ad a. In assenza delle parentesi il compilatore avrebbe agito in maniera differente, infatti: a = 5 * a + b / c - 2; valutata sommando il prodotto di a e 5 al quoziente di b diviso per c; al risultato sottratto 2 ed il valore cos ottenuto viene assegnato ad a. Vale la pena di presentare l'insieme degli operatori C/C++, riassumendone in una tabella le regole di precedenza ed associativit; gli operatori sono elencati in ordine di precedenza decrescente.

Operatore () [] . ->

Descrizione chiamata di funzione indici di array appartenenza a struttura appartenenza a struttura refernziata da puntatore

Associativit da sx a dx

16

Corso Base di Programmazione in C/C++ - Lezione 4

! ~ ++ -& * (tipo) sizeof() * / % + << >> < <= > >= == != & ^ | && || ? : =, etc. ,

NOT logico complemento a uno meno unario (negazione) autoincremento autodecremento indirizzo di indirezione cast (conversione di tipo) dimensione di moltiplicazione divisione resto di divisione intera addizione sottrazione scorrimento a sinistra di bit scorrimento a destra di bit minore di minore o uguale a maggiore di maggiore o uguale a uguale a diverso da (NOT uguale a) AND su bit XOR su bit OR su bit AND logico OR logico espressione condizionale

da dx a sx

da sx a dx

da sx a dx da sx a dx da sx a dx

da sx a dx da sx a dx da sx a dx da sx a dx da sx a dx da sx a dx da dx a sx

operatori di assegnamento (semplice e da dx a sx composti) virgola (separatore di espressioni) da sx a dx

Come si vede, alcuni operatori possono assumere significati diversi. Il loro modo di agire sugli operandi quindi talvolta desumibile senza ambiguit solo conoscendo il contesto di azione, cio le specifiche espressioni in cui sono utilizzati.

17

Corso Base di Programmazione in C/C++ - Lezione 5

Lezione 5 Istruzioni di Condizione


Iniziamo, con questa lezione, ad entrare nel vivo del linguaggio C/C++. Al termine della lezione saremo in grado di far prendere al programma delle semplici decisioni deterministiche ovvero delle scelte fatte sulla base della risoluzione di una espressione logica di controllo.

Istruzione if-else
Nella precedente lezione si gia potuto osservare una esempio di istruzione decisionale: d = (x==3) ? 4 : 5; nella quale se x vale 3 allora alla variabile d viene dato il valore 4 altrimenti gli viene assegnato 5. Notare la terminologia utilizzata nella frase precedente: Se espressione allora espressione1 altrimenti espressione2. Una istruzione if-else funziona allo stesso modo, con lunica differenza che al posto delle espressione1 e 2 ci sono 2 gruppi di istruzioni. La sintassi di questa istruzione la seguente: if(espressione) { //istruzioni separate da ; } else { //istruzioni separate da ; } Schematicamente questa istruzione pu essere rappresentata nel modo che seguente e proprio per questa schematizzazione si parla spesso di ramo if e ramo else:

La valutazione della condizione tra parentesi tonde, in generale, deve tornare un valore del tipo bool che assume quindi i valori true o false. Se la condizione vera viene eseguito il primo blocco di istruzioni racchiuse tra le parentesi graffe, se la condizione e falsa vengono eseguite le istruzioni del secondo blocco. Vediamo subito qualche esempio: 18

Corso Base di Programmazione in C/C++ - Lezione 5

#include <iostream.h> int main() { int anni; cout << "Inserire let: "; //Stampa a video la stringa tra gli apici cin >> anni; //Legge da tastiera un numero e lo memorizza nella var. anni if(anni >= { cout << } else { cout << cout << } return 0; } 18) "Puoi prendere la patente B." << endl;

"Sei troppo giovane per portare lauto!!!" << endl; "Let minima e 18. " << endl;

In questo semplice esercizio viene stampato prima un messaggio, subito dopo il programma si mette in attesa di un input da tastiera e finalmente viene eseguito il controllo sulla variabile anni. Se questultima maggiore o uguale a 18 viene eseguita la stampa a video "Puoi...", altrimenti vengono eseguite le altre 2 stampe del ramo else. Quando il ramo if o quello else o entrambi contengono una sola istruzione, le parentesi graffe possono essere omesse: ... if(anni >= 18) cout << "Puoi prendere la patente B." << endl; else { cout << "Sei troppo giovane per portare lauto!!!" << endl; cout << "Let minima e 18. " << endl; } ... Inoltre il ramo else non obbligatorio. Ad esempio: #include <iostream.h> int main() { int anni; cout << " Inserire let: "; cin >> anni; if(anni >= 18) { cout << "Puoi prendere la patente B." << endl; return; } cout << "Sei troppo giovane per portare lauto!!!" << endl; cout << "Let minima e 18. " << endl; return 0; } 19

Corso Base di Programmazione in C/C++ - Lezione 5

Istruzioni if-else annidate


Sia il ramo if che il ramo else possono contenere altre istruzioni if-else, in questi casi si parla di istruzioni ifelse annidate. Valgono le stesse regole viste in precedenza, ovvero else non obbligatorio e parentesi graffe eliminabili se il ramo in considerazione contiene una sola istruzione. #include <iostream.h> #define MyAge 21 main() { int anni; cout <<"Indovina la mia et: "; cin >> anni; if(anni == MyAge) { cout << endl << "BRAVISSIMO, ai indovinato!"<< endl; } else { if(anni < MyAge) { cout << "No, sono pi vecchio." << endl; } else { cout << " No, sono pi giovane " << endl; } } return 0; }

Istruzione switch-case
Ci sono casi in cui necessario annidare molte istruzioni if-else: resto = a % b; if(resto==0) cout << "Resto nullo" << endl; else if(resto==1) cout << "Resto = uno" << endl; else if(resto==2) cout << "Resto = due" << endl; else if(resto==3) cout << "Resto = tre" << endl; ...

In simili situazioni la lettura del codice diventa anche pi complicata, specialmente se le istruzioni nei rami if ed else sono molte. Per fortuna il linguaggio C/C++ mette a disposizione una istruzione equivalente che permette di evitare di dover annidare tanti if-else, tale istruzione lo switch-case: switch(espressione) { case val1: //istruzioni break; case val2: //istruzioni break;

20

Corso Base di Programmazione in C/C++ - Lezione 5

case valn: //istruzioni break; default: //istruzioni } Lespressione tra le parentesi tonde dello switch deve avere come risultato un valore (di tipo int, float, char,) che sar poi confrontato con tutti i valori costanti valn dei diversi case. Il primo valore che risulter uguale con quello tornato dallespressione iniziale causer lesecuzione delle istruzioni che seguono i : del relativo case. Se nessuno dei case soddisfa lespressione dello switch allora saranno eseguite le istruzioni della sezione default. Questultima sezione opzionale.

Vediamo qualche esempio: switch(a % b) { case 0: cout << "Resto nullo" << endl; break; case 1: cout << "Resto uno" << endl; break; ... case 9: cout << "Resto nove" << endl; break; }

switch(nome[i]) { case a: case e: case i: case o: case u: cout << "La lettera" << i << "del nome una vocale." << endl; break; default : cout << "La lettera" << i << "del nome una consonante." << endl; } Osserviamo lultimo esempio. Se ci sono due o pi case per i quali si deve eseguire la stessa o le stesse istruzioni possibile utilizzare la forma abbreviata, ovvero mettere le istruzioni solo nellultimo case e lasciare vuoti tutti quelli precedenti. Esempio completo: #include <iostream.h> #include <stdlib.h> int main() { 21

Corso Base di Programmazione in C/C++ - Lezione 5

char choice;

cout cout cout cout cout cout

<< << << << << <<

"FLIGHT BOOKING SYSTEM" << endl << endl; "1..New York to London Heathrow" << endl; "2..New York to Vancouver" << endl; "3..New York to Sydney" << endl; "4..New York to Cape Town" << endl; "Q..Quit" << endl;

cout << endl << endl << "Enter your choice : "; cin >> choice; switch(choice) { case '1': cout << endl << "New York to break; case '2': cout << endl << "New York to break; case '3': cout << endl << "New York to break; case '4': cout << endl << "New York to break; case 'Q': case 'q': exit(0); default: cout << endl << "Bad choice!" } return 0; }

London booked"<< endl << endl; Vancouver booked" << endl << endl; Sydney booked" << endl << endl; Cape Town booked" << endl << endl;

<< endl << endl;

22

Corso Base di Programmazione in C/C++ - Lezione 6

Lezione 6 Istruzioni di ciclo


In questa lezione impareremo come far eseguire al calcolatore le stesse istruzioni un numero determinato di volte oppure fin quando non si verifica una certa condizione. In C/C++ i cicli sono divisi in due classi: incondizionati o iterativi e cicli condizionati.

Il ciclo iterativo for


Numerose operazioni di elaborazione richiedono che determinate istruzioni vengano ripetute varie volte, un procedimento noto come iterazione. Il ciclo for segue un tracciato simile alla figura che segue:

Come vedremo tra breve, nella definizione del for viene specificato il numero di volte che il ciclo deve ripetersi tramite una variabile contatore. E anche possibile creare cicli infiniti dai quali possibile uscire utilizzando listruzione break vista nellistruzione switch-case. La sintassi del ciclo for la seguente: for(valore iniziale; valore finale; incremento) { //istruzioni } Esaminiamo una alla volta le 3 espressioni tra le parentesi tonde: Valore iniziale: in realt un assegnamento del tipo i=0; oppure i=-10; ma che comunque stabilisce il valore iniziale della variabile contatore. Valore finale: il valore finale che la variabile contatore assumer viene specificato dalla valutazione di una espressione di controllo del tipo i<50; oppure i<100; ovvero la variabile contatore viene incrementata fino a che lespressione precedente diventa falsa (i==50 o i==100). Incremento: composto da una istruzione di assegnamento o incremento o ancora decremento del tipo i++; o i--; o infine i+=3; Lultima istruzione fa procedere lincremento a passi di 3. Per lassegnamento di decremento fare attenzione a che il valore iniziale sia maggiore di quello finale altrimenti si cade in un ciclo infinito!!!

Vediamo qualche esempio: #include <iostream.h> int main() 23

Corso Base di Programmazione in C/C++ - Lezione 6

{ int x; for(x = 0; x < 10; x++) { cout << x << endl; } return 0; } Questo esempio stampa a video uno sotto laltro i numero da 0 a 9. Come visto nella Lezione 5 per lif-else ad una istruzione, anche per il for vale la regola che permette di eliminare le parentesi graffe solo se il corpo del ciclo composto da una sola istruzione. for(x = 0; x < 10; x++) cout << x << endl; Vediamo un altro esempio che esegue la somma di 5 numeri immessi dallutente: #include <iostream.h> main() { int loop; int total = 0; int number = 0;

// declare and initialize variables

for(loop = 1; loop <= 5; loop++) { cout << "Enter a number : "; cin >> number; total += number; // keep running total } cout << endl << "The total is " << total; return(0; } Lesempio che segue scrive in lettere maiuscole il nome (o la stringa) inserita da tastiera: #include <iostream.h> #include <string.h> #include <ctype.h> #define MAX 20 int main() { char name[MAX]; int x; cout << "Enter a name : "; cin >> name; for (x = 0; x < strlen(name); x++) { name[x] = toupper(name[x]); } cout << endl << name << endl; return 0; } Concludiamo gli esempi con un ciclo infinito particolare: 24

Corso Base di Programmazione in C/C++ - Lezione 6

... for(;;) //Realizza il ciclo infinito { //istruzioni } ... oppure ... for(;;) { if(condizione_uscita) break; //permette di uscire dal ciclo ... } ... NOTA: Anche se non espressamente vietato dalla sintassi del linguaggio, si consiglia di evitare assolutamente di modificare la variabile contatore allinterno del ciclo perch si potrebbe incorrere in errori logici e cadere quindi in un ciclo infinito o eseguire il ciclo un numero inferiore rispetto a quando il calcolo ne richiede. Se si ha la necessit di modificare la variabile contatore, utilizzare uno dei cicli condizionati che seguono.

Ciclo condizionato while


Listruzione di ciclo while utilizzata per ripetere un gruppo di istruzioni fino a che rimane vera una espressione di controllo. Lo schema logico di questa istruzione il seguente:

La sintassi di questa istruzione la seguente: while(condizione) { //istruzioni che modificano le variabili della condizione }

25

Corso Base di Programmazione in C/C++ - Lezione 6

dove condizione una qualunque espressione (complicata quanto basta) che ha come risultato true o false. Fino a che la condizione vera le istruzioni tra le parentesi graffe vengono eseguite, in caso di falsit si esce fuori dal ciclo. Nella condizione quindi possibile inserire una espressione contenente qualunque operatore di confronto (<, <=, >, >=, ==, !=) e/o logico (&&, ||, !). Anche qui, come visto nella Lezione 5 per lif-else ad una istruzione, vale la regola che permette di eliminare le parentesi graffe solo se il corpo del ciclo composto da una sola istruzione. Vediamo qualche esempio di utilizzo. #include <iostream.h> int main() { int counter = 0; //set initial value while(counter < 10) { cout << counter << " "; //display counter++; //increment } return 0; } Il precedente listato di programma C++, stampa a video i numeri da 0 a 9. #include <iostream.h> int main() { char lettera = b; while(lettera > a && lettera < z) { cout << lettera << " "; lettera ++; } return 0; } Il precedente listato di programma C++, stampa a video le lettere minuscole dellalfabeto dalla b alla y. Anche con il ciclo while possibile creare cicli infiniti. Un tipico esempio il seguente: ... while(true) //Realizza il ciclo infinito { //istruzioni } ...

Ciclo condizionato do-while


Listruzione di ciclo do-while utilizzata per ripetere un gruppo di istruzioni mentre rimane vera una condizione di controllo. Al contrario del while, le istruzioni sono eseguite almeno una volta. Lo schema logico il seguente nel quale risulta evidente che se la condizione di controllo vera si ripetono le istruzioni altrimenti si esce dal ciclo:

26

Corso Base di Programmazione in C/C++ - Lezione 6

La sintassi simile al while: do{ //istruzioni che modificano le variabili della condizione }while(condizione); Nella condizione quindi possibile inserire una espressione contenente qualunque operatore di confronto (<, <=, >, >=, ==, !=) e/o logico (&&, ||, !). Anche qui, come visto nella Lezione 5 per lif-else ad una istruzione, vale la regola che permette di eliminare le parentesi graffe solo se il corpo del ciclo composto da una sola istruzione. Vediamo un esempio di utilizzo: #include <iostream.h> int main() { int counter = 0; //set initial value do{ cout << counter << " "; //display counter++; //increment }while(counter < 10); //test condition return 0; }

27

Corso Base di Programmazione in C/C++ - Lezione 7

Lezione 7 Funzioni
Nel mondo reale i programmi tendono ad essere molto grandi e complicati. Per gestire una simile complessit, sono disponibili un certo numero di tecniche di programmazione note come progettazione topdown. La progettazione top-down larte della decomposizione di un problema complesso in attivit di complessit ridotta e pi facilmente gestibili. Queste piccole attivit formano la base della scrittura di un insieme di moduli che possono essere collegati fra loro a formare un programma completo. In questo modo si ottengono numerosi vantaggi: codice breve, ricerca degli errori pi semplice, possibilit di lavorare in gruppo, riutilizzo del codice, librerie. Un modulo in C++ noto come funzione e consiste di un prototipo e di una definizione. Come accennato nella lezione 2, ci sono due regole che bisogna rispettare: Il prototipo deve essere definito prima che il programma principale inizi. Le definizioni devono essere poste dopo il termine del programma principale.

Dichiarazione di una funzione: il Prototipo


Come accennato il prototipo deve essere posto prima che il programma principale inizi ed composto da tre elementi (visibili nel disegno sottostante):

Input: Gli input della funzione sono racchiusi tra parentesi tonde e sono noti come parametri di input, ma in realt sono delle vere e proprie variabili locali alla funzione (vedi Lezione 2 main()) e quindi visibili solo ed esclusivamente dalla funzione in questione. Si deve sempre specificare il tipo di ogni parametro uno alla volta, ovvero non possibile utilizzare la forma abbreviata per parametri che hanno lo stesso tipo (ad es.: int i,j,k; per le funzioni non ammesso). Nome Funzione: Il nome della funzione deve essere unico e non deve coincidere con le parole chiavi del C/C++ (ad es.: for, switch,). Inoltre, per il nome vale la stessa regole delle variabili: deve iniziare con una lettera o con _ e contenere poi solo lettere, numeri o _, tutto il resto vietato. Output: Viene specificato solo il tipo di dati che la funzione deve restituire: int, float, double, char, struct e puntatori ad uno qualunque dei tipi visti. Quando si parler pi in dettaglio della OOP si vedr come una funzione pu tornare anche un oggetto.

Definizione della funzione: implementazione


Il corpo di una funzione esattamente come quello di un normale programma C/C++ ovvero del main() e pu includere proprie variabili locali ed istruzioni. Ripetiamo ancora una volta che tutte le variabili definite allinterno delle parentesi graffe del corpo della funzione sono invisibili allesterno della funzione stessa. Ora che la funzione dichiarata e implementata, come si utilizza? Loperazione semplicissima, basta scrivere il suo nome ed inserire tra le parentesi tonde i diversi valori da assegnare ai rispettivi parametri e terminare la riga con il ;. Passiamo ora ad esaminare un primo semplice esempio completo: #include <iostream.h> // Prototipo void Show(int x); 28

Corso Base di Programmazione in C/C++ - Lezione 7

int main() { int number; cout << "Enter a number: "; cin >> number; Show(number); return 0; } // Implementazione void Show(int x) { cout.setf(ios::right); //***** - vedi sotto cout << "The number is: "; cout.width(6); //***** - vedi sotto cout << x << endl; } Si noti dal definizione del prototipo, il main e limplementazione della funzione. Si provi al calcolatore il risultato ottenuto cercando anche di capire cosa eseguono le righe commentate con gli asterischi (un buon programmatore deve cimentarsi da subito a capire le istruzioni che non ha mai visto).

Passaggio per Valore e per Riferimento


I due concetti esposti in questo paragrafo sono importantissimi e permettono di risolvere diversi problemi di ottimizzazione della memoria utilizzata e velocit di esecuzione dei programmi. Quando un valore viene passato ad una funzione come parametro di input, il contenuto di quella variabile viene copiato nella variabile interna dichiarata nellintestazione della funzione. Allinterno della funzione, solo il valore copiato viene manipolato; il valore originale rimane inalterato. Lesempio precedente tipico di un passaggio di parametro per valore nel quale avrei anche potuto modificare il contenuto della variabile x senza avere influenza sulla variabile number. Il secondo importante concetto che bisogna capire perfettamente il passaggio dei parametri per Riferimento. Ricordiamo quanto detto nella lezione 3 a proposito del tipo di dato puntatore: un puntatore un tipo di dato che contiene lindirizzo di memoria di unaltra variabile.

Quando una variabile puntatore esterna viene passata come parametro di input a una funzione, il contenuto di quella variabile, costituito da un indirizzo, viene copiato nella variabile puntatore interna dichiarata nellintestazione della funzione. Poich un indirizzo punta ad una locazione di memoria allinterno del computer, questo significa che entrambe le variabili puntatore, interna ed esterna, puntano alla stessa posizione. Questarea di memoria pu essere quindi manipolata dallinterno della funzione o dal programma principale. Questa la cosiddetta memoria condivisa. Vediamo subito un esempio: #include <iostream.h> void Twice(int* x); int main() { int* number; //puntatore ad intero 29

Corso Base di Programmazione in C/C++ - Lezione 7

int Num1 = 77; number = &Num1; //number prende lindirizzo della variabile Num1 cout << *number << endl; Twice(number); cout << *number << endl; return 0; } void Twice(int* x) { *x = *x * 2; //modifica la locaz. di memoria il cui indirizzo contenuto in x }

Nel precedente esempio facciamo attenzione a due particolari metodi di scrittura che finora non abbiamo visto: &nome_var: questa scrittura va letta come indirizzo della variabile nome_var. Quindi il risultato di questa espressione un puntatore che ha lo stesso tipo della variabile nome_var. *nome_var: questa scrittura leggermente pi complicata da capire. Prima di tutto la variabile nome_var di tipo puntatore (a intero, carattere,). Lasterisco va letto come contenuto della locazione di memoria, quindi tutta lespressione si legge cos: contenuto della locazione di memoria puntata da nome_var. Nei casi in cui potrebbe crearsi confusione, possibile usare le parentesi tonde ovvero scrivere, per lesempio precedente,(*x) = (*x) * 2;.

Ricorsione
Una definizione che adoperasse il concetto stesso che intende definire sarebbe considerata circolare e dunque vuota, per le stesse ragioni per cui non accetteremmo come concludente una dimostrazione che facesse uso della tesi da stabilire. Tuttavia esistono forme di circolarit che non sono considerate vuote, ma anzi accettate come definizioni e ragionamenti validi: sono quelli in cui si ricorre allinduzione matematica. Come in generale si possono definire induttivamente insiemi, cos, in particolare, si pu fare per le funzioni:

Possiamo tuttavia calcolare i valori di una funzione definita per ricorsione interpretando la definizione implicita come una regola di calcolo. Questo quanto avviene nel caso di funzioni ricorsive in C++ (ed in ogni linguaggio che accetti la ricorsione: ad esempio il PASCAL, il C, Java). La funzione fattoriale si pu infatti implementare: int Fattoriale(int n) { if(n == 0) return 1; return n * Fattoriale (n-1); } La valutazione di Fattoriale(3) si pu descrivere nel seguente modo: poich 3 0 il valore sar 3*Fattoriale(2); il calcolo di Fattoriale(3) viene allora sospeso, per valutare Fattoriale(2), che a sua volta richiede di calcolare 2*Fattoriale(1) e dunque viene anchesso sospeso in attesa che la chiamata Fattoriale(1) ritorni un valore. Analogamente Fattoriale(1) richiede di calcolare 1*Fattoriale(0), e viene sospeso per calcolare Fattoriale(0). Finalmente largomento 0, dunque sappiamo esplicitamente che il valore di Fattoriale(0) 1. Da questo momento si riprendono una 30

Corso Base di Programmazione in C/C++ - Lezione 7

dopo laltra le computazioni delle chiamate sospese: troviamo allora che Fattoriale(1) ritorna 1; che Fattoriale(2) ritorna 2, ed infine che Fattoriale(3) ritorna 6. Come questo semplice esempio suggerisce, non agevole eseguire a mente i calcoli che una definizione ricorsiva di una funzione comporta. Al contrario abbiamo unidea molto pi chiara di cosa faccia la versione iterativa della stessa funzione fattoriale: int Fattoriale_iterativo(int n) { int fact=1; for(int i=2; i<=n; i++) fact *= i; return fact; } Concludiamo questo discorso un p articolato con una semplice frase che racchiude tutto il senso del paragrafo: una funzione C/C++ ricorsiva quando allinterno del suo corpo viene richiamata se stessa!

31

Corso Base di Programmazione in C/C++ - Lezione 8

Lezione 8 Strutture dati Complesse


Qualunque tipo di programma, semplice o complesso, poggia le sue basi su un insieme di dati di un certo tipo (tali tipi possono essere anche pi duno) organizzati secondo le proprie esigenze. Linsieme organizzato e tipizzato di dati prende nome di struttura dati. Facciamo un paragone con un esempio preso della vita quotidiana. Pensiamo ad una libreria, questultimo altro non che un grande contenitore diviso in ripiani ognuno dei quali pu contenere un numero finito di libri. Se ci serve altro spazio possiamo comprare unaltra libreria. Un libro a sua volta pu essere visto come un insieme di pagine contenenti a loro volta testo. Schematicamente abbiamo una simile situazione:

Dalla figura si evince che la macro entit Libreria composta da 4 sotto-tipi di dati ognuno dei quali con un proprio tipo. Una simile organizzazione forma la struttura dati Libreria. In questa lezione esamineremo alcune strutture dati molto comuni ed anche molto utili in quanto lorganizzazione dei dati della quasi totalit dei programmi pu essere riconducibile ad esse. Tali strutture dati sono: Lista, Lista bi-direzionale, Pila, Albero.

Allocazione dinamica della Memoria


Prima di passare in rassegna le strutture dati sopra menzionate, bisogna prima sapere come possibile chiedere al sistema operativo di darci altra memoria. Questa azione chiamata in gergo Allocazione Dinamica, allopposto c il rilascio della memoria che si chiama Deallocazione. Pensiamo allesempio della libreria. Come facciamo a sapere quante librerie ci serviranno per tutta la vita (in programmazione la vita del programma)? Non potremmo mai saperlo! Quando ne abbiamo bisogno andiamo al negozio e ne compriamo unaltra (chiediamo altra memoria per il programma al S.O.). Ma il negoziante avr sempre una libreria della grandezza richiesta (ci sar nel computer una quantit di memoria sufficiente a soddisfare la nostra richiesta)? Non detto! In casi simili dobbiamo gestire questa condizione di ERRORE. La memoria libera presente nel calcolatore viene chiamata Heap. Il modo di allocare e deallocare memoria in C e C++ diverso. Il C fa uso di funzioni di libreria mentre il C++ ha degli operatori che fanno parte direttamente nel linguaggio. Il motivo di una simile differenza sta nel fatto che il C++ deve poter allocare memoria anche per gli Oggetti e ricordiamo che un oggetto composto da dati e funzioni e quindi deve essere trattato in un certo modo. Esaminiamo prima lallocazione dinamica in C. Il prototipo della funzione di allocazione : void *malloc(unsigned size); 32

Corso Base di Programmazione in C/C++ - Lezione 8

Semplice no? La funzione prende in ingrasso un intero senza segno che rappresenta il numero di byte da allocare e restituisce un puntatore alla memoria allocata. Il puntatore restituito non ha tipo, ma come vedremo tra breve pu essere convertito in un puntatore standard. Per deallocare la memoria che non pi necessaria si utilizza invece la seguente funzione: void free(void *block); ovvero una funzione che prende in ingresso un puntatore e che non restituisce nulla. Esempio completo in C: #include #include #include #include <stdio.h> <string.h> <alloc.h> <process.h>

int main(void) { char *str; /* allocate memory for string */ if((str = (char *) malloc(10)) == NULL) { printf("Not enough memory to allocate buffer\n"); exit(1); /* terminate program if out of memory */ } /* copy "Hello" into string */ strcpy(str, "Hello"); /* display string */ printf("String is %s\n", str); /* free memory */ free(str); return 0; } Esaminiamo le parti che pi ci interessano: Conversione del puntatore void: Ci che a noi serve un puntatore a carattere, la conversione del puntatore tornato dalla malloc viene fatta in questo modo: (char *) malloc(10). Questo tipo di conversione si chiama Conversione Cast a run-time ed una delle capacit del C/C++ che li rende dei potenti linguaggi di programmazione. Deallocazione: free(str); esaminando il prototipo delle funzione necessaria una conversione cast. Siccome un puntatore void pi generale un (char *), la conversione viene fatta automaticamente dal compilatore. Puntatore nullo: un particolare valore che pu assumere un puntatore il valore nullo indicato con NULL. Tale valore deve essere controllato in modo da sapere se il contenuto della variabile puntatore valido. Passiamo al C++. Loperatore di allocazione il new, mentre quello di deallocazione il delete o delete[]. Puntatore = new tipo(num_byte); delete[] Puntatore; oppure delete Puntatore; Il precedente programma convertito in C++ diventa: #include <iostream.h> 33

Corso Base di Programmazione in C/C++ - Lezione 8

#include <stdio.h> #include <string.h> #include <process.h> int main() { char *str; //allocate memory for string if((str = new char(10)) == NULL) { cout << "Not enough memory to allocate buffer\n" << endl; exit(1); //terminate program if out of memory } //copy "Hello" into string strcpy(str, "Hello"); //display string cout << "String is " << str << endl; //free memory delete[] str; return 0; } Visto che il C++ include il C, in un programma C++ possibile usare il sistema di allocazione-deallocazione del C. E importante per rispettare la seguente regola: un puntatore allocato con loperatore new deve essere deallocato con loperatore delete, mentre un puntatore allocato con la funzione malloc deve essere deallocato con la funzione free.

Liste
Iniziamo la descrizione delle strutture dati complesse pi comuni con le liste. Queste ultime non sono altro che una concatenazione logica di una struttura, struct, di base. La rappresentazione schematica di una struttura dati a Lista la seguente:

Prima di andare avanti bisogna dare una avvertenza. Attenzione a non cancellare il Puntatore Principale. Se ci accade, tutta la struttura dati andr persa! Anzi, rimarr anche allocata inutilmente memoria nel sistema! La lista governata dalla seguente regola: Un elemento della lista viene aggiunto sempre alla fine. Ovviamente secondo le proprie esigenze sar possibile modificare la struttura e comportamento. Ad esempio una ottimizzazione nella velocit di inserimento potrebbe essere quella di mantenere un altro puntatore ausiliario che punta allultimo elemento della lista. Limportante per definire la struttura e le sue regole di utilizzo e rispettarle sempre nel programma onde evitare errori logici. Vediamo come si realizza in C/C++ la definizione della struttura dati lista: struct TLista; struct TLista { 34

Corso Base di Programmazione in C/C++ - Lezione 8

int dato1; int dato2; TLista *next; }; TLista *Lista; //Il puntatore principale della lista Esaminiamo il codice. La prima riga definisce lesistenza di una struct chiamata TLista. Siccome la definizione di una lista ricorsiva ovvero allinterno della struttura c una variabile puntatore dello stesso tipo della lista, la prima riga indica al compilatore di mantenere traccia del nome TLista e la sua definizione sar esplicitata in seguito. Subito dopo segue la struct che definisce il contenitore dei dati mantenuti da una lista. Nel record possibile inserire qualunque tipo di dato standard o complesso definito prima della struct in esame. Notare la presenza del puntatore *Lista di tipo TLista. Questo puntatore la variabile che lega logicamente uno dopo laltro i vari record della lista. Scriviamo adesso il codice C++ utile alla gestione della lista, nello specifico: inserimento di un elemento alla fine della lista, stampa del contenuto della lista e cancellazione totale della lista: #include <iostream.h> #include <conio.h> struct TLista; struct TLista { int dato1; int dato2; TLista *next; }; TLista *Lista; void AggiungiElem(TLista *L, int d1, int d2); void StampaLista(TLista *L); void CancellaLista(TLista *L); int main(int argc, char* argv[]) { Lista = NULL; Lista = new TLista; Lista->dato1 = 0; Lista->dato2 = 0; Lista->next = NULL; AggiungiElem(Lista,1,10); AggiungiElem(Lista,2,20); AggiungiElem(Lista,3,30); AggiungiElem(Lista,4,40); AggiungiElem(Lista,5,50); StampaLista(Lista); CancellaLista(Lista); getch(); return 0; } void AggiungiElem(TLista *L, int d1, int d2) { TLista *temp, *ultimo; temp = L; //esploro la lista fino alla fine. 35

Corso Base di Programmazione in C/C++ - Lezione 8

while(temp!=NULL) { ultimo = temp; temp = temp->next; } temp = new TLista; temp->dato1 = d1; temp->dato2 = d2; temp->next = NULL; ultimo->next = temp; } void StampaLista(TLista *L) { TLista *temp; temp = L; while(temp!=NULL) { cout << "(" << temp->dato1 << ", " << temp->dato2 << ")"; temp = temp->next; } } void CancellaLista(TLista *L) { TLista *temp; temp = L; while(L!=NULL) { temp = L->next; delete L; L = temp; } } La funzione AggiungiElem, aggiunge appunto un elemento alla lista. Lelemento costituito da 2 numeri interi. Il ciclo while non fa altro che esplorare la lista fino alla fine e mantenere un puntatore che al termine del ciclo punter allultimo elemento della lista, alloca la memoria per un nuovo record e lo collega alla lista, precisamente allultimo. La funzione StampaLista, tramite il ciclo while, scorre lintera lista e per ogni record stampa tra parentesi tonde i due campi del record. Lultima funzione, CancellaLista, simile alla precedente. Il ciclo while scorre tutta la lista e mantiene un puntatore ausiliario che al momento della cancellazione del record punta al record successivo nella lista.

Lista Bidirezionale
La precedente struttura dati ha un problema: la scansione pu essere fatta solo in un senso. Volendo creare una struttura dati lista che pu essere esplorata in entrambi i sensi, necessario apportare solo una piccola modifica, visibile schematicamente nella figura seguente:

Come si pu vedere, sufficiente aggiungere un altro puntatore di tipo lista che punta allelemento logicamente precedente a quello in cui ci si trova. 36

Corso Base di Programmazione in C/C++ - Lezione 8

Lesempio del precedente paragrafo pu essere trasformato in lista bidirezionale apportando pochissime modifiche: #include <iostream.h> #include <conio.h> struct TLista; struct TLista { int dato1; int dato2; TLista *next; TLista *prev; }; TLista *Lista; void AggiungiElem(TLista *L, int d1, int d2); void StampaLista(TLista *L); void CancellaLista(TLista *L); int main(int argc, char* argv[]) { Lista = NULL; Lista = new TLista; Lista->dato1 = 0; Lista->dato2 = 0; Lista->next = NULL; Lista->prev = NULL; AggiungiElem(Lista,1,10); AggiungiElem(Lista,2,20); AggiungiElem(Lista,3,30); AggiungiElem(Lista,4,40); AggiungiElem(Lista,5,50); StampaLista(Lista); CancellaLista(Lista); getch(); return 0; } void AggiungiElem(TLista *L, int d1, int d2) { TLista *temp, *ultimo; temp = L; //esploro la lista fino alla fine. while(temp!=NULL) { ultimo = temp; temp = temp->next; } temp = new TLista; temp->dato1 = d1; temp->dato2 = d2; temp->next = NULL; temp->prev = ultimo; ultimo->next = temp; } void StampaLista(TLista *L) 37

Corso Base di Programmazione in C/C++ - Lezione 8

{ TLista *temp; temp = L; while(temp!=NULL) { cout << "(" << temp->dato1 << ", " << temp->dato2 << ")"; temp = temp->next; } } void CancellaLista(TLista *L) { TLista *temp; temp = L; while(L!=NULL) { temp = L->next; delete L; L = temp; } } Le modifiche fatte sono: Aggiunta del puntatore TLista *prev; nella definizione della struttura TLista. Aggiunta della riga Lista->prev = NULL; nel main per linizializzazione del primo record. Aggiunta della riga temp->prev = ultimo; nella funzione AggiungiElem. Questa riga quella che realizza la connessione logica tra i record consecutivi della lista.

Tutto il resto rimane inalterato.

Pila
Quando si discusso sulla lista mono-direzionale, si detto che gli elementi devono essere aggiunti sempre alla fine della lista. Ma se vogliamo invece inserirli allinizio?? Ebbene, nessuno ci vieta di inserirli allinizio, in questo modo per stiamo creando unaltra struttura dati! Pi precisamente stiamo creando una struttura dati a Pila, nella quale gli elementi (i record) sono inseriti sempre in testa. Questa organizzazione dei dati anche detta LIFO dallacronimo inglese Last In, First Out ovvero il primo ad essere inserito sar lultimo ad essere tolto. Lo schema logico di una simile struttura dati identico in ogni sua parte a quello dalla lista mono-direzionale, quindi non lo ripetiamo. Ci che invece cambiano sono le operazioni di gestione. Fondamentalmente le operazioni possibili sono 2 ma nessuno vieta di aggiungerne altre di aiuto. Le operazioni principali si chiamano PUSH e POP. La prima inserisce un record in testa alla struttura dati mentre la seconda cancella la testa della pila restituendo come valore di output il contenuto della dellelemento cancellato. Riportiamo come di consueto un listato di esempio con laggiunta delle funzioni ausiliarie EMPTY che controlla se la pila vuota e CLEAR_STACK che svuota tutta la pila liberando anche la memoria: #include <iostream.h> #include <conio.h> #define DIMBUF 50 struct PILA; struct PILA { char strdir[DIMBUF]; struct PILA *next; } *Pila; void PUSH(char *s); 38

Corso Base di Programmazione in C/C++ - Lezione 8

void POP(char *s); bool EMPTY(void); void CLEAR_STACK(void); int main(int argc, char* argv[]) { char msg[DIMBUF]; PUSH("uno"); PUSH("DUE"); PUSH("tre"); PUSH("QUATTRO"); PUSH("--- fine ---"); while(!EMPTY()) { POP(msg); cout << msg << endl; } CLEAR_STACK(); getch(); return 0; } void PUSH(char *s) { struct PILA *t = new PILA; if(t==NULL) return; else { strcpy(t->strdir,s); t->next = Pila; Pila = t; } } void POP(char *s) { struct PILA *t; if(Pila!=NULL) { strcpy(s,Pila->strdir); t = Pila; Pila = Pila->next; delete t; } } bool EMPTY(void) { if(Pila==NULL) return true; return false; } void CLEAR_STACK(void) { char *buf=new char[DIMBUF]; while(!EMPTY()) POP(buf); }

39

Corso Base di Programmazione in C/C++ - Lezione 8

Lultima struttura dati che esamineremo prende il nome di struttura dati ad Albero in quanto la sua schematizzazione assomiglia ad un albero capovolto o ancora meglio ad un albero genealogico. Proprio sulla base di questultima analogia, quando si parla di struttura dati ad albero, si usano i termini padre, figlio, ecc, per i record che terminano la struttura si usa il termine foglia e in generale, un qualunque record della struttura viene indicato come Nodo. Il primo nodo della struttura prende il nome di Radice. Lo schema logico si presenta cos:

Struttura dati ad Albero

Un albero che ha solo 2 figli si chiama Albero Binario, uno con 3 figli Albero ternario,, uno con n figli si chiama Albero n-ario. Pensate un attimo, ma non vi sembra di aver gia visto da qualche parte nel vostro computer una organizzazione simile? Di esempi ce ne sono tanti, ma quello pi evidente lorganizzazione dei file e cartelle del sistema operativo nel quale i nodi sono le singole cartelle e le foglie i file veri e propri (documenti, immagini,). Altri esempi di impiego massiccio di queste strutture sono i database nei quali le righe delle tabelle sono organizzate ad albero ordinato per una maggiore velocit di ricerca rispetto ad una organizzazione sequenziale ordinata. Sugli alberi stata fatta molta ricerca e sono nati tanti libri. Per lo scopo di queste dispense sufficiente sapere come sono organizzati gli alberi, come si inserisce e cancella un nodo e come si pu testare la presenza di un cero elemento nellalbero supponendo che sia ordinato. Di seguito riportiamo il codice di un possibile esempio di utilizzo della struttura ad albero binario detto di ricerca in quanto governato dalla seguente regola: dato un nodo contenente il valore X, il figlio sinistro contiene un dato minore di X mentre il figlio destro contiene un dato maggiore di X. #include <iostream.h> #include <conio.h> struct TAlbero; struct TAlbero { int dato; TAlbero *Padre; TAlbero *FiglioDS; TAlbero *FiglioSN; }; TAlbero *Albero; void void void void InOrder(TAlbero *A); PreOrder(TAlbero *A); PostOrder(TAlbero *A); TreeInsert(TAlbero *A, int v); 40

Corso Base di Programmazione in C/C++ - Lezione 8

TAlbero TAlbero TAlbero TAlbero

*Minimo(TAlbero *A); *Successore(TAlbero *A); *DeleteNode(TAlbero *A, int key); *TreeSearch(TAlbero *A, int v);

int main(int argc, char* argv[]) { Albero=NULL; TreeInsert(Albero,5); TreeInsert(Albero,1); TreeInsert(Albero,10); TreeInsert(Albero,3); TreeInsert(Albero,6); TreeInsert(Albero,4); TreeInsert(Albero,0); TreeInsert(Albero,7); TreeInsert(Albero,2); TreeInsert(Albero,8); cout << "Radice: " << Albero->dato << endl; cout << "InOrder: "; InOrder(Albero); cout<<endl; cout << "PreOrder: "; PreOrder(Albero); cout<<endl; cout << "PostOrder: "; PostOrder(Albero); getch(); return 0; } void InOrder(TAlbero *A) //Detto anche Ordine Simmetrico { if(A!=NULL) { InOrder(A->FiglioSN); cout << A->dato << " "; InOrder(A->FiglioDS); } } void PreOrder(TAlbero *A) //Detto anche Ordine Anticipato { if(A!=NULL) { cout << A->dato << " "; PreOrder (A->FiglioSN); PreOrder (A->FiglioDS); } } void PostOrder(TAlbero *A) //Detto anche Ordine Differito { if(A!=NULL) { PostOrder (A->FiglioSN); PostOrder (A->FiglioDS); cout << A->dato << " "; } } void TreeInsert(TAlbero *A, int val) { TAlbero *x, *y, *z; 41

Corso Base di Programmazione in C/C++ - Lezione 8

z = new TAlbero; z->FiglioSN = NULL; z->FiglioDS = NULL; z->Padre = NULL; z->dato = val; y = NULL; x = A; while(x!=NULL) { y = x; if(z->dato<x->dato) x = x->FiglioSN; else x = x->FiglioDS; } z->Padre = y; if(y==NULL) Albero = z; else if(z->dato<y->dato) y->FiglioSN = z; else y->FiglioDS = z; } TAlbero *Minimo(TAlbero *A) { TAlbero *x; x = A; while(x->FiglioSN!=NULL) x = x->FiglioSN; return x; } TAlbero *Successore(TAlbero *A) { TAlbero *x,*y; x = A; if(x->FiglioDS!=NULL) return Minimo(x->FiglioDS); else { y = x->Padre; while(y!=NULL && x==y->FiglioDS) { x = y; y = y->Padre; } } return y; } TAlbero *DeleteNode(TAlbero *A, TAlbero *z) { TAlbero *x,*y; if(z->FiglioSN==NULL || z->FiglioDS==NULL) y = z; else y = Successore(z); if(z->FiglioSN!=NULL) x = y->FiglioSN; else x = y->FiglioDS; if(x!=NULL) x->Padre = y->Padre; if(y->Padre==NULL) Albero = x; else if(y==(y->Padre)->FiglioSN) (y->Padre)->FiglioSN = x; else (y->Padre)->FiglioDS = x; if(y!=z) z->dato = y->dato; return y; } TAlbero *TreeSearch(TAlbero *A, int v) { 42

Corso Base di Programmazione in C/C++ - Lezione 8

TAlbero *temp; temp = A; while(temp!=NULL && v!=temp->dato) { if(v<temp->dato) temp = temp->FiglioSN; else temp = temp->FiglioDS; } return temp; } Iniziamo la spiegazione delle funzioni. Supponiamo di avere in memoria una albero come in figura:

InOrder(): La funzione richiama ricorsivamente se stessa sul figlio sinistro del nodo attuale. Quando il nodo passato nullo la ricorsione termina, si torna allultima funzione chiamante, si stampa il contenuto del nodo e comincia la ricorsione sul figlio destro. Questo processo ricorsivo termina quando si raggiunge lultima foglia figlia destra di un certo nodo. Il risultato stampato da questa funzione : 10, 20, 30, 35, 40, 50, 70, 90, 80, 99. PreOrder(): La funzione stampa prima il contenuto del nodo e inizia il processo ricorsivo sul figlio sinistro e poi su quello destro. Il risultato stampato da questa funzione : 50, 30, 20, 10, 40, 35, 70, 80, 90, 99. PostOrder(): La funzione inizia il processo ricorsivo sul figlio sinistro, poi su quello destro ed infine stampa il contenuto del nodo. Il risultato stampato da questa funzione : 10, 20, 35, 40, 30, 90, 99, 80, 70. Minimo(): Ritorna il nodo che contiene il dato pi piccolo ovvero, in base alla regola che governa un albero binario di ricerca, ritorna la foglia pi a sinistra seguendo il disegno precedente (10).

Per esercizio provare a descrivere il comportamento delle altre funzioni.

Puntatore a Funzione
Concludiamo questa lezione con la descrizione di un particolare tipo di puntatori che non tutti i linguaggi possiedono: i puntatori a funzione. Cos come abbiamo un puntatore ad intero, carattere, cos possibile avere un puntatore il cui contenuto sar lindirizzo di memoria nel quale una certa funzione inizia. Per coloro i quali dopo questo corso vorranno imparare a programmare per Windows senza utilizzare il framework VCL o MFC si troveranno subito a dover utilizzare almeno un puntatore a funzione. Senza addentrarci negli aspetti della programmazione base di Windows, diciamo soltanto che il sistema operativo utilizzer questo puntatore in modo da poter richiamare il gestore dei messaggi che Windows manda allapplicazione. La sintassi di dichiarazione di un puntatore a funzione la seguente: tipo_restituito (*nome_funz)(parametri); 43

Corso Base di Programmazione in C/C++ - Lezione 8

dove parametri un elenco di parametri di input della funzione dichiarato come di consueto. Vediamo un esempio: ... double (*f1)(double x1); double (*f2)(double x2,double x3); ... cout << 2 elevato alla 32= << pow(2,32) << endl; f2 = pow; cout << 2 elevato alla 32= << f1(2,32) << endl; ... cout << Coseno di 0.321 radianti= << cos(0.321) << endl; f1 = cos; cout << Coseno di 0.321 radianti= << f1(0.321) << endl; ...

44

Corso Base di Programmazione in C/C++ - Lezione 9

Lezione 9 Oggetti
In passato, i programmatori scrivevano codice che manipolava i dati, i dati stessi e il codice che li manipolava venivano trattati come due elementi separati. La programmazione orientata agli oggetti (OOP), invece, tratta dati e codice come una singola entit, nota come classe. Il concetto della OOP introduce nuove parole di uso comune durante la programmazione in C++ (tutto ci che verr detto da qui in poi non sar pi applicabile al C): CLASSE ISTANZA OGGETTO INCAPSULAMENTO MEMBRO DATI FUNZIONE MEMBRO COSTRUTTORE e DISTRUTTORE La OOP un nuovo modi di pensare la programmazione. Non si deve pensare pi al codice lineare e alla manipolazione di alcuni dati esterni con una funzione. Si deve adottare limpostazione mentale che i dati e il codice sono raggruppati allinterno dello stesso corpo.

Classe: Incapsulamento
Una classe una struttura di dati che contiene tutto quanto necessario per memorizzare e manipolare i dati. Nella OOP, ogni variabile definita allinterno di una classe denominata membro dati. Le funzioni che manipolano i dati sono dette invece funzioni membro. Dovrebbe essere possibile manipolare i dati membro solo tramite le funzioni membro. Le funzioni esterne non possono accedere ad un membro dati. Questo modo di agire realizza quello che in gergo si chiama incapsulamento dellinformazione. Vediamo come si crea una classe in C++: class nome_classe { //dati e funzioni membro }; ad esempio: class shape { //dati e funzioni membro dichiarati alla solita maniera }; int main() { shape forma; //esempio di dichiarazione di una var. return 0; } Quindi la classe viene dichiarata usando la parola riservata class seguita dal nome da dare alla classe. Lincapsulamento dellinformazione utile quando non si vuole far accedere a dati critici per il corretto funzionamento del programma. Come vedremo in seguito, il divieto di accesso diretto ai dati non comunque controllato, ovvero, possibile rendere visibili allesterno dati non critici. Un ultimo concetto che bisogna avere ben chiaro in mente la distinzione tra classe ed oggetto. La prima un qualcosa di astratto che non esiste fisicamente e lunico modo per poterla vedere leggere la sua definizione (class x {...};), un oggetto fisicamente presente nella memoria di un calcolatore, ha un proprio spazio di memoria e cosa pi importante pu essere utilizzato ovvero creato, distrutto accedere ai dati e alle funzioni membro, copiato e passato come parametro di input ad una funzione oppure restituito come parametro di output da unaltra funzione. 45

Corso Base di Programmazione in C/C++ - Lezione 9

Membri Dati e Funzioni Membro


A questo punto dovrebbe essere gia ben chiaro la distinzione tra un membro dati ed una funzione membro. Ma che differenza c tra una funzione membro ed una normalissima funzione esterna? E tra un membro dati ed una variabile generale? Per entrambe le domande la risposta : Nessuna! Esiste solo una distinzione concettuale, un dato o funzione membro dichiarata allinterno di una classe ed esiste solo se viene creato un oggetto, mentre le variabili e funzioni generali esistono allesterno della classe/oggetto. Infatti estendendo lesempio precedente possiamo scrivere: class shape { public: //Vedremo dopo cosa significa double Larghezza, Altezza; double area; double CalcolaArea(double l, double h); }; Semplice no? Volendo ora scrivere limplementazione della funzione CalcolaArea(); come bisogna procedere? La sintassi da utilizzare nella definizione delle funzioni membro la seguente: tipo_restituito nome_classe::funzione_membro(parametri) { //istruzioni } Ovvero double shape::CalcolaArea(double l, double h) { Larghezza = l; Altezza = h; area = l*h; return area; } Come si utilizza una classe? Estendiamo il main visto prima. int main() { shape forma; forma.Larghezza = 10.9; forma.Altezza = 100.0; cout << forma.CalcolaArea(10.0, 12.5) << endl; return 0; } Il programma precedente stamper 125. Quindi come possiamo osservare si utilizza la stessa notazione utilizzata per le struct. Facciamo lo stesso esempio ma questa volta i puntatori: int main() { shape *forma; forma = new shape; forma->Larghezza = 10.9; forma->Altezza = 100.0; 46

Corso Base di Programmazione in C/C++ - Lezione 9

cout << forma->CalcolaArea(10.0, 12.5) << endl; delete forma; return 0; }

Visibilit esterna dei dati e funzioni


Senza addentrarci in discussioni filosofiche universitarie sulla visibilit dei dati e funzioni membro, veniamo subito al dunque! Ci sono 3 modi possibili di nascondere linformazione che si realizzano utilizzando le 3 parole riservare public, private e protected. Pi precisamente la sintassi la seguente: class nome_classe { public: //dati e funzioni membro pubbliche private: //dati e funzioni membro private protected: //dati e funzioni membro protette }; Notare i : dopo le parole chiavi. Non necessario utilizzarle tutte, inoltre lordine pu essere come pi si ritiene giusto ed infine possibile avere pi sezioni private, pubbliche o protette: class classe1 { public: //dati e funzioni membro pubbliche private: //dati e funzioni membro private public: ... }; Descriviamo ora con pi precisione il significato di questi 3 modi di proteggere linformazione.

Public
Qualunque membro dati e funzione membro dichiarata dopo la parola chiave public detta essere una funzione pubblica o dato pubblico. Essere pubblico significa che allesterno della classe si pu accedere direttamente a quella funzione o variabile. Ad esempio, lecito scrivere: class classe1 { public: int d1; char d2; char LeggiCarattere(); private: //dati e funzioni membro private }; int main() { classe1 c; c.d2 = c.LeggiCarattere(); }

47

Corso Base di Programmazione in C/C++ - Lezione 9

Private
Qualunque membro dati e funzione membro dichiarata dopo la parola chiave private detta essere una funzione privata o dato privato. Essere privato significa che allesterno della classe non possibile accedere direttamente a quella funzione o variabile. Ad esempio: class classe1 { public: int d1; char d2; char LeggiCarattere(); private: int priv1; int leggiIntero(); }; int main() { int i; classe1 c; c.d2 = c.LeggiCarattere(); i = c.leggiIntero(); //ERRORE } Il precedente programma sbagliato in quanto la funzione leggiIntero(); privata ovvero non visibile dallesterno. Una qualunque funzione dichiarata come pubblica ha comunque accesso, nella sua implementazione, ai dati e funzioni membro privati.

Protected

Lo status di dato o funzione protetta simile a quello di privato ma ha un comportamento particolare quando una classe eredita i dati e funzioni membro di unaltra classe. Per ora sospendiamo il discorso e lo riprenderemo pi in l quando parleremo della ereditariet.

Costruttore e Distruttore
Nella creazione di una classe possibile dichiarare 2 funzioni particolari che non vengono chiamate direttamente nel codice C++, queste sono il Costruttore e il Distruttore. Il costruttore ha il nome uguale a quello della classe, pu avere parametri di input e non restituisce niente e viene chiamato automaticamente quando la variabile oggetto viene creata. Lo scopo del costruttore quello di inizializzare i dati membro ed allocare eventualmente memoria dinamica. Non utilizzate mai una chiamata diretta al costruttore di una classe! Il costruttore opzionale, ovvero, non necessario che la classe abbia un costruttore. Il distruttore ha un nome uguale a quello della classe ma preceduto dal carattere tilde ~, non ha parametrici input e non restituisce niente. Viene chiamato automaticamente quando viene distrutta (o deallocata) la variabile oggetto. Lo scopo principale quello di deallocare la memoria dinamica utilizzata nella classe. Anche per il distruttore e consigliabile evitare di richiamarlo direttamente ed inoltre non necessario che sia presente. Per entrambi infine obbligatorio che siano definiti nella sezione pubblica della classe, in caso contrario il compilatore dar errore. Facciamo qualche esempio concreto class shape { public: //Vedremo dopo cosa significa shape(); //costruttore ~shape(); //distruttore 48

Corso Base di Programmazione in C/C++ - Lezione 9

double Larghezza, Altezza; double area; }; double CalcolaArea(double l, double h);

shape::shape() { cout << Variabili inizializzate << endl; Larghezza = Altezza = 0; } shape::~shape() { cout << Distruzione delloggetto << endl; } double shape::CalcolaArea(double l, double h) { Larghezza = l; Altezza = h; area = l*h; return area; } int main() { shape *forma; forma = new shape; forma->Larghezza = 10.9; forma->Altezza = 100.0; cout << forma->CalcolaArea(10.0, 12.5) << endl; delete forma; return 0; } Il programma precedente produrr il seguente output sullo schermo: Variabili inizializzate 125 Distruzione delloggetto

Ereditariet
Con lintroduzione della OOP stato introdotto un nuovo e potente metodo di sviluppo dei programmi: lereditariet. Come il nome suggerisce, con il C++ (ovvero con qualunque linguaggio ad oggetti), possibile creare una classe che eredita i dati e le funzioni membro di unaltra classe (la classe padre). Ereditare vuol dire che tutto ci che era contenuto nella classe padre diventa parte integrante della classe figlio e allesterno sembra ununica classe. Ci sono 3 modi di ereditare i dati e membri da una classe padre: pubblico, privato o protetto. Inoltre leredit pu essere multipla ovvero una classe eredita da pi classi padre. In gergo pi tecnico si parla di classe derivata e classe base. In questo modo, la creazione di pi livelli di derivazione da luogo ad un sistema gerarchico di classi. Alcuni di questi sistemi sono noti come framework VCL (di Borland) ed MFC (di Microsoft).

49

Corso Base di Programmazione in C/C++ - Lezione 9

Riprendiamo il concetto di dato o membro protetto (protected) di una classe, giunto il momento di spiegare meglio il suo significato. Qualsiasi cosa protetta si comporta come privata allinterno della propria classe. Questo significa che non possibile accedere ad essa dal mondo esterno. Gli elementi protetti possono essere comunque ereditati da una classe derivata e nella classe derivata essi si comportano come se fossero privati. Una classe derivata non pu ereditare gli elementi privati della classe base. La tabella che segue mostra come sono ereditati i dati e funzioni membri in base a come la classe base viene ereditata (colonne). Definizione del dati e membri della classe Public Pubblico Privato Privato Protected privato Privato Privato Private Non acc. dalla classe base Non acc. dalla classe base Non acc. dalla classe base

Public Protected Private

Vediamo come si scrive in C++ il codice che permette di eseguire lereditariet. class nome_classe_derivata: public nome_classe_base { //dati e funzioni membro }; class nome_classe_derivata: private nome_classe_base { //dati e funzioni membro }; class nome_classe_derivata: protected nome_classe_base { //dati e funzioni membro }; Per una eredit multipla si scrive: class nome_classe_derivata: public nome_classe_base1, nome_classe_base2 { //dati e funzioni membro }; 50

Corso Base di Programmazione in C/C++ - Lezione 9

Concludiamo il discorso sulla ereditariet descrivendo un ultimo importante concetto. Se un classe base ha un costruttore allora la classe derivata deve esporre un costruttore che abbia gli stessi parametri del costruttore della classe base pi eventuali altri parametri. Il passaggio dei parametri dal costruttore della classe derivata a quello della classe base avviene in un modo diverso dal solito: class base { public: base(int p1, int p2); ... }; class derivata : public base { public: derivata(double a, int b, int c); ... }; derivata::derivata(double a, int b, int c) : base(b, c) { ... } Vediamo un semplice esempio funzionante di ereditariet: #include <iostream.h> class shape { protected:

public:

double length; double height; double area; void CalcArea(); void ShowArea(); shape(double l = 0, double h = 0);

};

class ThreeD : public shape { protected : double depth; double volume; public: void CalcVol(); void ShowVol(); ThreeD(double z=0,double x=0,double y=0); }; ThreeD::ThreeD(double z, double x, double y):shape(x,y) { depth = z; } shape::shape(double l, double h) { length = l; height = h; } 51

Corso Base di Programmazione in C/C++ - Lezione 9

void shape::CalcArea() { area = length * height; } void shape::ShowArea() { cout << "THE AREA IS : " << area; } void ThreeD::CalcVol() { volume = depth * length * height; } void ThreeD::ShowVol() { cout << "THE VOLUME IS : " << volume << endl; } main() { double x, y, z; cout << "ENTER THE LENGTH : "; cin >> x; cout << "ENTER THE HEIGHT : "; cin >> y; cout << "ENTER THE DEPTH : "; cin >> z; ThreeD box(z,x,y); box.CalcVol(); box.ShowVol(); return 0; }

Polimorfismo
Prima di definire il concetto di Polimorfismo fondamentale che il concetto di ereditariet sia ben chiaro ed inoltre bisogna apprenderne un altro: classe astratta. La classe astratta uno strumento di progettazione che consente di definire funzionalit di base, lasciando che le funzionalit specifiche del programma vengano definite successivamente. Le classi astratte hanno le seguenti caratteristiche: Hanno almeno una funzione membro virtuale pura (che vedremo tra poco). Le classi astratte vengono usate come classe base per creare classi derivate. Ogni classe che contiene una funzione virtuale pura, non pu creare un oggetto.

Una funzione definita virtuale anteponendo alla sua definizione la parola chiave virtual. Questo significa che tutte le classi derivate condividono la stessa funzione per evitare lambiguit.

52

Corso Base di Programmazione in C/C++ - Lezione 9

Una classe derivata pu sovrascrivere la definizione di una funzione membro virtuale ridefinendo la sua funzionalit. La nuova definizione verr utilizzata in tutte le istanze degli oggetti della classe derivata. Quando una funzione viene dichiarata virtuale nella classe base, essa rimane virtuale in tutte le classi derivate. Si liberi di includere od omettere la parola virtual nella ridefinizione della funzione membro. Una funzione virtuale pura, una funzione virtuale impostata a zero e non viene specificata alcuna definizione per essa. Sembra un po strano. La sintassi la seguente: virtual tipo_restituito nome_funzione(parametri) = 0; ad esempio virtual void f1()=0; Essa non esegue nulla eccetto impedire alla classe di creare unistanza delloggetto e occupare memoria per assegnargli una definizione quando eredita una classe. A questo punto si pu dire definire il concetto di polimorfismo: Il polimorfismo la capacit di una funzione membro di avere differenti funzionalit in vari punti dellalbero gerarchico. La funzionalit utilizzata quella pi appropriata per loggetto al quale appartiene. Diamo un esempio: #include <iostream.h> #include <stdlib.h> #define TAB '\t' class SEQUENCE { protected: int back; char data[10]; public: virtual void POKE(char ch); virtual void POP(void) = 0; virtual void PEEK(void) = 0; SEQUENCE(); }; SEQUENCE::SEQUENCE() { back = 0; } void SEQUENCE::POKE(char ch) 53

Corso Base di Programmazione in C/C++ - Lezione 9

{ if(back < 9) { back++; data[back] = ch; cout << endl; } else cout << endl << "SORRY - FULL" << endl << endl; } class MyDEQUE : public SEQUENCE { public: MyDEQUE(); void POP(void); void PEEK(void); }; void MyDEQUE::POP(void) { int index; char item; if(back > 0) { cout << "LEAVE DEDUE FROM FRONT OR BACK (f/b) : "; cin >> item; if((item == 'b') || (item == 'B')) { back--; } if((item == 'f') || (item == 'F')) { for(index = 0; index < back; index++) { data[index] = data[index + 1]; } back--; } } else { cout << endl << "DEDUE IS EMPTY"; cout << endl << endl; }

void MyDEQUE::PEEK(void) { int x; if (back == 0) { cout << endl << "DEDUE IS EMPTY"; cout << endl << endl; } else { for (x = 1; x <= back; x++) { 54

Corso Base di Programmazione in C/C++ - Lezione 9

cout << data[x] << TAB; } cout << endl << endl;

MyDEQUE::MyDEQUE() : SEQUENCE() { cout << "LIST CREATED" << endl << endl; } char menu(void); main() { char ch; char poker; MyDEQUE D; while (1) { ch = menu(); switch(ch) { case '1' :

case '2' : case '3' : } case '4' :

cout << "Enter the character : "; cin >> poker; D.POKE(poker); break; D.POP(); break; D.PEEK(); break; exit(0);

} return(0);

char menu(void) { char choice; cout << "1...Join the DEQUE" << endl; cout << "2...Leave the DEQUE" << endl; cout << "3...Show the DEQUE" << endl; cout << "4...Quit the program" << endl << endl; cout << "Enter your choice : "; cin >> choice; return choice; }

55

Corso Base di Programmazione in C/C++ - Lezione 10

Lezione 10 Gestione dei File


Nelle precedenti lezioni, tutti gli input o output avvenivano tramite la tastiera e lo schermo. In questa lezione impareremo ad effettuare letture e scritture su file presenti sulla memoria di massa (hard disk). Senza addentrarci troppo su come fatto un calcolatore, i diversi tipi di dispositivi di memorizzazione e velocit di accesso, descriviamo subito quali sono le operazioni di base che bisogna fare per poter utilizzare il file. La gestione di un file, di qualunque tipo esso sia, fatta in tre passi: 1. Apertura: Questa operazione crea una associazione tra il file su disco, individuato tramite il suo percorso e nome, ed una variabile locale al programma chiamata in gergo file descriptor o Handle. In questa operazione viene anche fatta la scelta della modalit di apertura: lettura, scrittura, append (scrittura che inizia alla fine del file), binario o ASCII. 2. Lettura/Scrittura: Queste operazioni permettono leffettiva scrittura e lettura dei dati su e da file. Il contenuto da scrivere/leggere ovviamente memorizzato in una variabile locale al programma. In base al tipo di apertura del file (binario o ASCII) sar possibile leggere/scrivere dati direttamente in formato testo o binario. 3. Chiusura: Quando non pi necessario lutilizzo del file consigliabile chiudere il file. Al contrario dellapertura, questa operazione distrugge il collegamento che si era creato prima. Questa operazione non obbligatoria ma siccome il numero di file che un programma pu mantenere aperti contemporaneamente limitato, per evitare la generazione di un errore di apertura file consigliabile chiudere un file dopo il suo utilizzo.

Handle Apertura e chiusura di un file


Per capire cos un handle osserviamo il disegno sottostante:

Dal disegno si evince che un handle non altro che un puntatore ad una struttura dati interna al sistema operativo, alla quale ovviamente il programma ha accesso, contenente delle informazioni sul file aperto: dimensione, attributi, posizione attuale nel file per le operazioni di lettura, data di creazione ed ultima modifica ed infine un puntatore alla posizione fisica del file nellHDD che un programmatore non user mai. Questa struttura potrebbe essere nascosta al programmatore visibile solo parzialmente tramite una struttura intermedia fornita dal linguaggio di programmazione. Ad esempio in Visual Basic lhandle di un file un numero intero a cominciare da zero. Il modo di aprire e chiudere un file in C e C++ diverso, esaminiamoli separatamente a partire dal linguaggio C (come al solito in un programma C++ possibile utilizzare le chiamate fatte in C). Lesempio che segue include anche operazioni lettura e scrittura che vedremo in seguito, questo esempio apre 2 file, uno in lettura e laltro in scrittura. Il secondo file viene riempito con lo stesso contenuto del primo ed infine chiusi. Notare la gestione dellerrore di apertura file. #include <stdio.h> int main(void) 56

Corso Base di Programmazione in C/C++ - Lezione 10

{ FILE *in, *out; if((in = fopen("TESTFILE.DAT", "rt")) == NULL) { fprintf(stderr, "Cannot open input file.\n"); return 1; } if((out = fopen("TESTFILE.BAK", "wt")) == NULL) { fprintf(stderr, "Cannot open output file.\n"); return 1; } while(!feof(in)) fputc(fgetc(in), out); fclose(in); fclose(out); return 0; } La prima riga di codice allinterno del main dichiara 2 variabili puntatore alla struttura FILE. Viene subito aperto il file di input tramite loperazione in = fopen("TESTFILE.DAT", "rt"), nella quale viene specificato il nome del file e la modalit lettura (r) testo (t). Se loperazione fallisce la variabile in conterr NULL. La chiusura dei file fatta nelle 2 istruzioni precedenti il return: fclose(in); e fclose(out);. Una funzione interessante la feof() prensente nella condizione di test del ciclo while. Questa funzione controlla che il puntatore al file, nella struttura descritta allinizio del paragrafo, non sia arrivato alla fine del file stesso. In altre parole questa funzione ci consente di determinare quando il file termina. La lettura di un carattere dal file di input si esegue con la funzione fgetc(in) che ritorna il carattere letto, mentre la scrittura di un carattere viene fatta con fputc() la quale ha come parametri di input il carattere da scrivere e lhandle del file di output. Passiamo al C++. Un semplice esempio di gestione file il seguente: #include <fstream.h> main() { int loop = 0; int x; char filename[12] = "a:xtest.dat"; int mode = (ios::in | ios::binary); fstream fin( filename, mode ); if (!fin) cerr << "Unable to open file"; while (fin >> x) { cout << x << endl; loop++; } fin.close(); return(0); }

57

Corso Base di Programmazione in C/C++ - Lezione 10

Ricordiamo che il C++ un linguaggio ad oggetti, quindi quale migliore esempio di utilizzo degli oggetti quello di incapsulare in una classe tutta la gestione dei file! Infatti la classe che permette di gestire i file chiamata fstream e nellesempio loggetto di questo tipo e fin. Linizializzazione avviene passando al costruttore delloggetto i parametri filename e mode. In questo esempio il file aperto in lettura (ios::in) binaria (ios::binary). La chiusura del file fatta tramite lultima istruzione: fin.close(), mentre la lettura con listruzione fin >> x. Se questultima istruzione non legge alcun intero dal file ha come risultato zero e quindi si esce dal ciclo while.

File di testo e file binari


La distinzione tra un file di testo o binario utile solo al programmatore, per il calcolatore non esiste alcuna differenza. Un file di testo un file che contiene solo ed esclusivamente lettere, segni di punteggiatura e numeri. Un file di testo se aperto con un qualunque editor di testo sar possibile leggere chiaramente il suo contenuto. Attenzione: Se nel nostro programma abbiamo una variabile di tipo intero e dobbiamo scriverla in un file di testo, necessario convertire il valore intero nella sua rappresentazione stringa. Lo stesso discorso vale per i valori in virgola mobile. Se invece il file binario, ovvero un file contenente un serie di byte qualunque, loperazione di conversione non necessaria. In C le operazioni utilizzate sono:

fprintf

Il prototipo int fprintf(FILE *stream, const char *format[, argument, ...]);. ed un altro esempio di utilizzo : fprintf(stream, "%d %c %f", i, c, f); Dopo il segno % possibile mettere una stringa composta nel seguente modo: % [flags] [width] [.prec] [F|N|h|l|L] type_char Dove: [flags] : Segno dei valori numerici e pu assumere il carattere -, + o la stringa blank. [width]: Numero minimo di caratteri da utilizzare si pu scrivere %0n ad indicare che se il numero di caratteri minore di n allora la saranno stampati degli zeri. [.prec] : Precisione, ovvero il numero di caratteri da stampare dopo la virgola ad es. %.3f. [F|N|h|l|L] : Modificatore del tipo di caratteri che segue. type_char : Vedi figura seguente

58

Corso Base di Programmazione in C/C++ - Lezione 10

fscanf

Il prototipo int fscanf(FILE *stream, const char *format[, address, ...]); I parametri utilizzati sono gli stessi della fprintf. Lunica differenza alla quale bisogna fare attenzione la modalit di passaggio delle variabili che conterranno i valori letti. Questa regola vale non solo per la fscanf ma anche per la scanf. scanf(%i, &val); nella quale evidente la & davanti al nome della variabile val. Questa notazione indica che bisogna passare alla funzione lindirizzo della variabile. Per i file binari in C possibile utilizzare la funzione open il cui prototipo : int open(const char *path, int access [, unsigned mode]); nella quale la variabile access permette di impostare oltre che la modalit testo anche quella binaria. Vediamo un esempio applicativo: #include #include #include #include <string.h> <stdio.h> <fcntl.h> <io.h>

int main(void) { int handle; char msg[] = "Hello world"; if ((handle = open("TEST.$$$", O_CREAT | O_TEXT)) == -1) { perror("Error:"); 59

Corso Base di Programmazione in C/C++ - Lezione 10

return 1; } write(handle, msg, strlen(msg)); close(handle); return 0; } I possibili valori che il parametro access pu assumere sono divisi in 2 gruppi:

Read/Write Flags
O_RDONLY O_WRONLY O_RDWR Apertura in sola lettura. Apertura in sola scrittura. Apertura in lettura e scrittura.

Other Access Flags


O_NDELAY O_APPEND O_CREAT O_TRUNC O_EXCL O_BINARY O_TEXT Non usato; per compatibilit con UNIX. Se impostato, il puntatore al file punta alla fine del file. Se il file esiste, questo flag non ha effetto, se invece non esiste il file viene creato. Se il file esiste, la sua lunghezza viene impostata a zero. Usato solo con O_CREAT. Se il file esiste, viene generato un errore. Apre il file in modalit binaria. Apre il file in modalit testo.

Le operazioni di lettura e scrittura avvengono tramite le funzioni read e write. int read(int handle, void *buf, unsigned len); int write(int handle, void *buf, unsigned len) In C++ invece possibile utilizzare un unico oggetto che gestisce sia la lettura/scrittura binaria che quella testuale. Per la lettura/scrittura binaria sufficiente specificare la costante ios::binary vista nellesempio sul C++. Nel caso in cui sia necessaria la lettura/scrittura testuale basta omettere tale costante. Le operazioni di lettura e scrittura avvengono gli usuali operatori >> e <<.

60

Potrebbero piacerti anche