Linguaggio C++ Fondamenti Un primo semplice esempio: Calcolo dello spazio di frenata di un’automobile
1. Analisi del problema Si desidera determinare lo spazio (in metri) necessario ad arrestare un’automobile viaggiante ad una data velocità (in km/h), ipotizzando la decelerazione costante ed uguale a 9,2 m/s2
2. Scrittura dell’algoritmo Una prima rappresentazione in pseudocodice potrebbe essere la seguente: { Richiedi la velocità iniziale in km/h Calcola lo spazio di frenata Comunica il risultato in metri }
Secondo il metodo d’analisi TOP-DOWN, ogni fase può essere considerata un sottoproblema e pertanto elaborata ulteriormente. Mentre la prima e l’ultima fase sono in una forma sufficientemente semplice, per scomporre la fase Calcola lo spazio di frenata è necessario conoscere le leggi della cinematica relativa ai moti uniformemente decelerati
s è lo spazio necessario all’arresto (in metri) Il modello matematico (formula) che descrive il fenomeno del moto decelerato è il seguente: dove: s è lo spazio necessario all’arresto (in metri) vi è la velocità iniziale (in m/s) d è la decelerazione (9,2 m/s2)
Nel nostro caso, per poter applicare la formula precedente è necessario convertire la velocità iniziale da km/h a m/s. Chiamando viKmh la velocità in km/h e con vi quella in m/s si ha:
In conclusione, la fase Calcola lo spazio di frenata può essere vista come: { Conversione della velocità da km/h a m/s Calcolo dello spazio }
e, in ulteriore dettaglio: Calcola lo spazio di frenata { }
L’algoritmo complessivo rimane comunque molto semplice, la sola struttura di controllo utilizzata è la SEQUENZA. Si vedrà adesso come si passa dall’algoritmo (in questo caso in pseudocodice) al programma in linguaggio C++
3. Scrittura del programma Ogni programma C++ deve sempre avere una (ed una sola) funzione main dalla quale inizia l’esecuzione del programma: int main() { ... ... }
Per funzione s’intende un insieme di dati ed istruzioni, delimitato da una parentesi graffa aperta { e da una parentesi graffa chiusa }, cui è associato un nome e che ritorna un valore. Si noti la notevole somiglianza con la struttura utilizzata nel linguaggio di progetto per definire un algoritmo. La parola int che precede il nome della funzione indica che il valore ritornato è un intero.
La traduzione del primo enunciato del nostro algoritmo: Richiedi la velocità iniziale in km/h sottintende, implicitamente due fasi distinte: deve essere presentato sullo schermo un messaggio esplicativo affinché l’utente sia a conoscenza di quale dato richiede il programma in quel momento. Ad esempio: Scrivi la velocita' iniziale [km/h]:
2. Il dato deve essere effettivamente acquisito e memorizzato al fine di poterlo utilizzare in un momento successivo. Il linguaggio C++ non possiede alcuna istruzione per la comunicazione e l’acquisizione dei dati ma, insieme al compilatore, vengono solitamente fornite un insieme di oggetti e funzioni che consentono di supplire a questa mancanza. Questo insieme prende il nome di Libreria Standard
In particolare, l’oggetto cout (console output) consente di interagire con l’unità di output standard ovvero il video, e l’oggetto cin (console input) con l’unità di input standard, la tastiera. Scrivendo il seguente programma: #include <iostream> using namespace std; int main() { cout << "Scrivi la velocita' iniziale [km/h]: "; } al momento dell’esecuzione sullo schermo comparirà il messaggio scritto tra virgolette
Le prime due righe del listato sono necessarie per interpretare e manipolare correttamente la successiva cout cout rappresenta, simbolicamente, la destinazione standard dell’output (video) verso la quale è diretto il flusso (stream) delle informazioni presenti a destra del simbolo << ovvero dell’operatore di inserzione Il carattere punto e virgola (;) ha lo scopo di indicare al compilatore la fine dell’istruzione. Attenzione a non dimenticarlo altrimenti il compilatore interpreterà i caratteri successivi come appartenenti alla stessa istruzione, con risultati spesso imprevedibili
A questo punto è necessario che il programma possa acquisire e memorizzare il valore della velocità dell’automobile. In generale, per acquisizione s’intende l’operazione mediante la quale un dato proveniente da un’unità d’ingresso (in questo caso la tastiera) è memorizzato in una variabile
Per variabile s’intende un'area circoscritta della memoria RAM del calcolatore nella quale può essere conservato un valore. Per semplicità, e per potervi far riferimento quando occorre, ad ogni variabile è associato un nome (identificatore)
In questo caso, ad esempio, la variabile che contiene la velocità iniziale dell’automobile, espressa in km/h, potrebbe essere chiamata arbitrariamente viKmh. Nulla vieterebbe di chiamarla con un altro nome, ad esempio x, ma è consigliato l’uso di un nome mnemonico poiché permette di ricordare più facilmente il dato che rappresenta
L’identificatore di una variabile non può essere composto da caratteri qualsiasi ma deve essere una sequenza di una o più lettere dell’alfabeto inglese (maiuscole o minuscole), cifre e caratteri di sottolineatura (underscore: _). Inoltre non può iniziare con una cifra. Ad esempio sono corretti: somma_numeri _Area3 viKmh
Mentre non sono permessi identificatori come: viKm/h (il carattere / non è consentito) vi Kmh (lo spazio non è consentito) vi-Kmh (il carattere - non è consentito) 2vi (non può iniziare con una cifra) Il C++ distingue inoltre le lettere maiuscole dalle minuscole (è case sensitive): identificatori con lo stesso nome ma di formato differente sono considerati nomi di variabili diverse
Lo Standard prevede inoltre che le parole che iniziano con un doppio underscore __ siano riservate alle varie implementazioni del compilatore. Un identificatore non può avere lo stesso nome e formato di una parola chiave (keyword) del linguaggio. Questi identificatori predefiniti, infatti, hanno un significato speciale e non devono essere confusi col nome attribuito alle variabili
Le keyword del linguaggio C++ sono le seguenti: auto bad_cast bad_typeid bool break case catch char class const const_cast continue default delete dynamic_cast do double else enum explicit extern false float for friend goto if inline int main long mutable namespace new operator private protected public register reinterpret_cast return short signed sizeof static static_cast struct switch typedef template this throw true try typeid typename union unsigned using virtual void volatile wchar_t while
L’insieme dei valori memorizzabili in una variabile dipende in stretta misura dal modo in cui viene rappresentata internamente e dal numero di byte utilizzati. A tale proposito sono previsti dei tipi fondamentali di variabili, ciascuno dei quali consente di memorizzare dati appartenenti a insiemi diversi (interi, reali e caratteri)
Nella tabella che segue è indicata la gamma dei valori e l’insieme di appartenenza di alcuni tipi fondamentali: Tipo Gamma di valori Insieme char -128 ÷ 127 Caratteri int -2 147 483 648 ÷ 2 147 483 647 Interi double -1,7976931410308 1,7976931410308 Reali
Per comunicare al compilatore la scelta del nome e del tipo da utilizzare per una data variabile è necessario una dichiarazione che, nella sua forma più semplice, ha la seguente forma: tipo identificatore;
Ad esempio, nel nostro caso, si potrebbe chiamare la velocità iniziale dell’automobile viKmh e, poiché è un valore appartenente all’insieme dei numeri reali, associarle il tipo double: double viKmh;
Le variabili vanno dichiarate prima di utilizzarle e le dichiarazioni possono comparire ovunque. In questo esempio viKmh è dichiarata prima della funzione main: #include <iostream> using namespace std; double viKmh; int main() { ... }
A questo punto si può acquisire il valore della velocità fornito dall’utente mediante la tastiera e memorizzarlo nella variabile viKmh. Per farlo è necessario fare ricorso ad un oggetto della Libreria Standard, cin. L’oggetto cin è la sorgente simbolica dei dati che provengono dall’ingresso standard (la tastiera). L’operatore di estrazione >> indica la direzione del loro flusso
Il primo enunciato dell’algoritmo, Richiedi velocità iniziale dell’automobile in km/h, potrà quindi essere tradotto nel seguente programma: #include <iostream> using namespace std; double viKmh; int main() { cout << "Scrivi la velocita' iniziale [km/h]: "; cin >> viKmh; }
Calcola lo spazio di frenata { Si può adesso passare alla fase Calcola lo spazio di frenata di cui riportiamo per comodità l’algoritmo: Calcola lo spazio di frenata { }
In C++ può essere scritta nel seguente modo: La prima espressione: In C++ può essere scritta nel seguente modo: vi = viKmh / 3.6 dove vi è un’altra variabile che sarà opportunamente dichiarata di tipo double
Il formalismo è molto simile a quello delle normali espressioni aritmetiche. Nel linguaggio C++ anche la modalità di scrittura, così come i criteri adottati nella valutazione delle espressioni, hanno origine dall’aritmetica tradizionale
L’importanza delle espressioni nel linguaggio C++ è rappresentata dal fatto che una qualsiasi espressione terminata dal carattere punto e virgola ; costituisce un’istruzione. L’azione che ne deriva consiste semplicemente nella valutazione dell’espressione
In generale, un’espressione è una combinazione qualsiasi di operandi e operatori che dà origine a un valore. Un operando è uno dei valori (costante o variabile) che viene manipolato nell’espressione. Poiché un operando possiede un valore, ne segue che anche gli operandi sono espressioni
Sono tutte espressioni in quanto ognuna di esse rappresenta un valore Quindi: viKmh viKmh / 3.6 3.6 Sono tutte espressioni in quanto ognuna di esse rappresenta un valore
Gli operatori sono simboli (sia singoli caratteri che loro combinazioni) che specificano come devono essere manipolati gli operandi dell’espressione. Ad esempio, nel rapporto: viKmh / 3.6 viKmh e 3.6 sono gli operandi ed il carattere / (slash) costituisce l’operatore di divisione
come consueto, sarà prima eseguita la divisione e poi l’addizione La valutazione di un’espressione avviene eseguendo le operazioni indicate dagli operatori sui loro operandi secondo le regole di precedenza degli operatori, di norma uguali a quelle dell’aritmetica tradizionale. Ad esempio, nell’espressione: 4 + 10 / 2 come consueto, sarà prima eseguita la divisione e poi l’addizione
Il segno = usato nell’espressione: vi = viKmh / 3.6 invece, ha lo stesso significato del segno solitamente impiegato negli algoritmi per rappresentare l’operazione di assegnazione. Mediante questo operatore il valore a destra dell’operatore = (r-value) è memorizzato (assegnato) nell’operando di sinistra (l-value); quest’ultimo, pertanto deve riferirsi a una locazione di memoria modificabile
È quindi lecito scrivere: x = 10 se x è il nome di una variabile, mentre sicuramente non avrebbe nessun senso il contrario: 10 = x perché 10 è un valore costante e non può essere modificato
È bene osservare che l’operatore di assegnazione non ha lo stesso significato del segno uguale usato in aritmetica. Pertanto, espressioni del tipo: x = y e y = x con x diverso da y, non sono assolutamente equivalenti: nella prima il valore di y è memorizzato nella variabile x; nella seconda, invece, sarà il valore di x ad essere assegnato a y
Analogamente, mentre in aritmetica non è corretto scrivere: x = x + 5 in C++ è perfettamente lecito e assume il significato di: somma alla variabile x il numero 5 e memorizzane il risultato nella variabile x
Nell’espressione: vi = viKmh / 3.6 compaiono quindi tre operandi e due operatori, quello di assegnazione e quello di divisione. Poiché quest’ultimo ha priorità maggiore del primo verrà subito effettuata la divisione tra viKmh e 3.6 e, successivamente, il risultato sarà memorizzato nella variabile vi
Si vedrà, adesso, come tradurre la seconda espressione dell’algoritmo Calcola lo spazio di frenata: In C++ l’operatore di moltiplicazione si indica con l’asterisco *. Non esiste, invece, nessun operatore per l’elevazione al quadrato. Pertanto, la suddetta espressione si scriverà: s = 1 / 2 * vi * vi / 9.2
Nonostante sembri corretta, il valore di questa espressione sarà, tuttavia, sempre zero. Il perché di questa singolarità sta nell’espressione: 1 / 2 Per valutare correttamente un’espressione, il compilatore deve conoscere il tipo di ogni operando coinvolto:
vengono automaticamente assimilate a tipi int; nel caso di variabili, il tipo è quello attribuitogli al momento della loro dichiarazione; nel caso di operandi costanti, invece, il tipo viene automaticamente assegnato in base al loro valore e come è stato specificato. Ad esempio le costanti: 10234 -567 89 vengono automaticamente assimilate a tipi int;
mentre le costanti: 4567898586 -567.4 89.0 vengono considerate numeri reali: la prima eccede il massimo valore per una variabile di tipo int, la seconda è chiaramente un valore reale mentre la terza, benché possa essere correttamente rappresentata da una variabile di tipo int, essendo stata specificata con una cifra decimale (anche se zero), viene considerata un valore reale
In base a queste considerazioni, nell’espressione 1/2 le costanti 1 e 2 sono quindi interpretate come operandi di tipo int. L’operatore binario / effettua la divisione del primo operando per il secondo. Se entrambi gli operandi sono interi mentre il risultato della divisione non lo è, il suo valore viene approssimato secondo le seguenti regole:
Se entrambi gli operandi sono positivi o di segno discorde, il risultato sarà troncato, cioè non verranno considerate le cifre decimali. 2. Se entrambi gli operandi sono negativi, l’approssimazione del risultato (troncamento o arrotondamento) dipende dal compilatore utilizzato
Quindi, per le considerazioni precedentemente fatte, il rapporto: 1 / 2 viene considerato come una divisione tra interi positivi ed il risultato, ottenuto da quello reale (0,5) per troncamento vale, pertanto, zero
Il modo più semplice per ovviare a questo inconveniente è quello di trasformare una o entrambe le costanti coinvolte in costanti reali, ad esempio: 1 / 2.0 Se gli operandi coinvolti in un’espressione sono differenti, molti operatori effettuano automaticamente delle conversioni per portarli ad un tipo comune
Solitamente, durante questa operazione, l’operando di tipo inferiore viene convertito in quello di tipo superiore (che può rappresentare dei valori più alti). Ad esempio nel rapporto precedente sono coinvolti un operando di tipo intero (1) ed uno reale (2.0). Essendo di tipo differente, prima della divisione, il primo (di tipo inferiore) verrà automaticamente convertito nello stesso tipo dell’altro, (di tipo superiore). Il risultato della divisione sarà ancora del tipo comune, in questo caso un reale
È possibile adesso aggiornare il programma che assume una forma quasi definitiva: #include <iostream> using namespace std; double viKmh; double vi; double s; int main() { cout << "Scrivi la velocita' iniziale [km/h]: "; cin >> viKmh; vi = viKmh / 3.6; s = (1 / 2.0) * (vi * vi) / 9.2; ... }
Per rendere più leggibile il programma, anziché impiegare dei valori costanti è possibile usare delle loro rappresentazioni simboliche. Ad esempio, in questo problema la costante 9.2 rappresenta la decelerazione. La formula che la contiene sarebbe più chiara se al posto di 9.2 comparisse la lettera d o la parola decelerazione
const double decelerazione = 9.2; La dichiarazione di una costante simbolica è analoga a quella di una variabile con la differenza che è preceduta dalla keyword const. Poiché dopo la dichiarazione non è possibile modificare una costante, il suo valore deve essergli attribuito in quell’occasione: const double decelerazione = 9.2;
Da questo momento in poi, al posto del valore 9 Da questo momento in poi, al posto del valore 9.2 è possibile impiegare il nome simbolico decelerazione. Eventuali espressioni del programma che tentano di alterare il valore di decelerazione saranno segnalate come errori in fase di compilazione perché non è possibile modificare il valore di oggetti dichiarati costanti: decelerazione = 5.6;
A questo punto, manca solo la traduzione dell’algoritmo Comunica il risultato in metri. Può essere fatta utilizzando ancora l’oggetto cout che consente di presentare sullo schermo dati e messaggi combinati insieme. Se il valore memorizzato nella variabile s è 48.5, scrivendo: cout << "Lo spazio per il suo arresto e' di " << s << " metri" << endl;
sullo schermo apparirà: Lo spazio per il suo arresto e' di 48.5 metri endl, presente al termine dell’istruzione, è un manipolatore di cout e serve a terminare la linea introducendo un ritorno a capo. In questo caso, eventuali altre operazioni di input/output sul videoterminale inizieranno nella riga successiva
Infine, ecco il listato completo del programma: #include <iostream> using namespace std; double viKmh; double vi; double s; const double decelerazione = 9.2; int main() { cout << "Scrivi la velocita' iniziale [km/h]: "; cin >> viKmh; vi = viKmh / 3.6; s = (1 / 2.0) * (vi * vi) / decelerazione; cout << "Lo spazio per il suo arresto e' di " << s << " metri" << endl; }
Benché questo programma risulti sostanzialmente corretto, al fine di aumentarne la leggibilità da parte di altri programmatori (ed anche di noi stessi, dopo un certo periodo di tempo), è bene dotarlo di appositi commenti che lo documentino in modo appropriato
double s; // spazio di frenata in metri In un programma C++ il modo più semplice per inserire un commento è di farlo precedere dal doppio slash //. Tutto quello che viene scritto da quel punto fino al termine della riga è considerato un commento: double s; // spazio di frenata in metri
Se il commento è molto lungo ed occupa più righe (ad esempio per illustrare una parte del programma), è conveniente impiegare una sintassi alternativa dove il commento inizia con la sequenza di caratteri /* e termina con la medesima sequenza, ma invertita, */: /*Questo programma calcola lo spazio (in metri) necessario ad arrestare un’automobile viaggiante ad una data velocità (indicata in km/h) supponendo che la decelerazione sia costante.*/
Vi = viKmh / /* fattore di conversione */ 3.6; All’interno di un commento è ammesso qualunque carattere (anche //) con l’esclusione della combinazione */ che determina la fine del commento stesso. Diversamente dal commento che inizia col doppio slash, la sintassi precedente consente di introdurre un commento anche all’interno di un’espressione, ad esempio: Vi = viKmh / /* fattore di conversione */ 3.6;
I commenti non influenzano né la dimensione né la velocità del programma eseguibile. È bene quindi, utilizzarli frequentemente al fine di rendere più leggibile un programma. Spesso in un programma ben scritto, i caratteri utilizzati per i commenti sono addirittura maggiori di quelli utilizzati per le istruzioni
Affinché il programma appena scritto possa essere eseguito, è necessario prima tradurlo in una forma comprensibile al calcolatore. Questo compito può essere svolto da appositi programmi che fanno parte del cosiddetto software di base. I passi da seguire possono essere schematizzati dal diagramma di flusso che segue:
Esempio: programma per il calcolo dello spazio di frenata Programma sorgente Compilatore .obj Editor .lib Linker File sorgente Esempio: frenata.cpp Librerie File eseguibile Esempio: frenata.exe Programma eseguibile
I programmi descritti possono differire a seconda delle implementazioni. Esistono anche delle versioni che racchiudono, nello stesso ambiente di sviluppo, le funzionalità di tutti questi strumenti. Esempi tipici sono gli ambienti IDE (Integrated Development Enviroment) Microsoft Visual C++ .NET, Dev C++ e Borland C++ Builder