per tornare all'indice → Linguaggio C++

Programmazione imperativa

In queste pagine si utilizzerà il linguaggio C++ per tradurre gli algoritmi. Al momento della scrittura di queste parole, l'ultimo standard internazionale di riferimento è ISO/IEC 14882:2011 (a.k.a. C++11) che si può acquistare come PDF al prezzo di 200 Euro circa.

Link esterno

Il linguaggio C++ è un linguaggio generico (general purpose) orientato agli oggetti con cui si possono realizzare sia programmi ad interfaccia grafica che programmi ad interfaccia testuale. I programmi ad interfaccia grafica solitamente sono più grandi da realizzare, quindi si deve necessariamente iniziare con quelli più semplici da riga di comando.

La compilazione permette di ottenere un file eseguibile a partire da un file sorgente (testuale). Esistono numerosi compilatori per i programmi in linguaggio C++, sia liberi che proprietari.

  • gcc (GNU C Compiler) è un software libero usato per la compilazione dei programmi in linguaggio C
  • g++ è l'analogo software usato per i programmi in C++.

La compilazione consiste di due operazioni:

  1. compilazione (produzione di un file oggetto)
  2. linking (collegamento del file oggetto con altri file oggetto indispensabili)

Il modo più semplice di capire come funziona un programma compilatore è provare i seguenti esempi da riga di comando:

  • Codice per ottenere un file oggetto (file.o) a partire dal codice sorgente (file.cpp)
g++ -c file.cpp 
  • Codice per ottenere un file eseguibile (file.exe) a partire dal codice sorgente (file.cpp). Oltre alla compilazione, ora viene effettuato anche il linking con altri file oggetto disponibili o con quelli presenti nelle librerie.
g++ file.cpp -o file.exe

Per eseguire un programma ad interfaccia testuale, si dovrebbe aprire prima il prompt dei comandi e digitare il percorso e il nome del programma che si vuole eseguire.

Alcuni esempi di programmi che colorano le parole chiave sono:

Invece di usare un semplice editor di testo, esistono degli ambienti di sviluppo integrati con il compilatore. Ad esempio: codelite, qtcreator, codeblock, kdevelop

Si è visto che i linguaggi possono essere compilati o interpretati. In base al tipo di istruzioni si distinguono anche linguaggi imperativi e dichiarativi.

Un linguaggio di programmazione di tipo imperativo esprime dei comandi che devono essere eseguiti. Facendo un paragone con il linguaggio umano, per esprimere questi comandi si dovrebbe usare il modo imperativo (es: somma x+y, mangia, dormi, cammina). A questo tipo appartiene il linguaggio C++.

Un linguaggio di programmazione di tipo dichiarativo esprime dei comandi, indicando solo il risultato desiderato, senza specificare come ottenerlo (Es: cercare la presenza di un nome in un elenco). A questo tipo appartiene il linguaggio SQL, usato dai software che gestiscono database.

Per esercizio spiegare a parole cosa viene svolto da ogni programma….

01.cpp
// questo programma è stato scritto da Fabio
// contiene il codice indispensabile anche
// quando il programma non effettua nessuna operazione
 
int main ()
{
  return 0;
}

Il codice di ogni programma scritto in linguaggio C++ contiene sempre una funzione chiamata main(), che significa “funzione principale”. La sequenza di istruzioni inizia con la parentesi graffa aperta e termina con la parentesi graffa chiusa. Oltre alla funzione main() ci possono essere anche altre funzioni.

Quando viene eseguito un programma, il sistema operativo manda in esecuzione solo la funzione main() e questa si conclude con il comando return 0.

Valore restituito

Di che valore si tratta? Chi restituisce questo valore? A chi lo restituisce? La funzione main() mediante l'istruzione return restituisce al sistema operativo, come se fosse un risultato, il valore “zero”. Il valore zero (in informatica vuol dire anche “falso”) significa che la funzione main() è stata eseguita senza produrre nessun tipo di errore. In caso di errore poteva essere restituito un altro numero…

Oltre al main() ci possono essere molte altre funzioni che restituiscono un valore. Vedere valore_restituito

Note sul precedente codice

  • Il fatto che la funzione main(), alla fine, restituisca al sistema operativo un valore intero, deve essere indicato con int davanti al nome main().
  • Le istruzioni in C++ devono essere terminate con un punto e virgola (;).
  • Ogni blocco di codice che rappresenta un'unità è racchiuso tra parentesi graffe: { }
  • Le righe che iniziano con due slash (/) sono considerate commenti (una spiegazione per aiutare il lettore).

Molte delle parole scritte in un programma sono parole della lingua inglese ma hanno un significato speciale per il computer. Se per errore si sbaglia una sola lettera su una sola parola, il programma può diventare completamente incomprensibile.

Per esercizio trovare gli errori in questi programmi.

01error.cpp
// questo programma è stato scritto da Fabio
// il programma non effettua nessuna operazione
// il programma contiene degli errori da trovare
// interpretando gli errori prodotti dal compilatore
int main()
{
  retrun 0;
}
02error.cpp
// questo programma è stato scritto da Fabio
// il programma non effettua nessuna operazione
// il programma contiene degli errori da trovare
// interpretando gli errori prodotti dal compilatore
int main
{
  return 0;
}
02.cpp
// questo programma è stato scritto da Fabio
 
int main ()
{
  int somma; // definizione di una variabile locale (della funzione main)
  somma = 11+2; // il programma modifica il contenuto della memoria ma non e' visibile nessun effetto...
  return 0;
}

Le variabili sono delle zone di memoria (paragonabili a dei contenitori) dove viene memorizzato un valore numerico. Le definizioni delle variabili dicono al compilatore di riservare una zona di memoria per un certo tipo di dato. Ad esempio, l'struzione int somma;, riserva la memoria necessaria a memorizzare un numero intero.

  • Una variabile che si trova dentro il codice di una funzione è chiamata “variabile locale”, e può essere usata solo dentro questa funzione.
  • Ogni variabile ha un nome identificatore che la distingue: nella stessa funzione non ci possono essere due variabili con lo stesso nome.
  • Ogni variabile ha un tipo di dato (numerico, carattere, ecc.) e quindi occupa una certa quantità di memoria.
  • Su ogni tipo di dato si possono eseguire solo alcuni tipi di operazioni.

Il valore contenuto in una variabile può essere modificato, ma deve essere sempre dello stesso tipo. Ad esempio, tipo intero (-3, 44, …), tipo carattere alfanumerico ('z', '?', …), ecc.

  • Ogni tipo di dato (intero, carattere, ecc.) richiede una quantità di memoria diversa dall'altro. Ad esempio, per una variabile che contiene un numero intero (800) può essere necessario riservare 1 Byte, mentre, per una variabile che contiene un numero in virgola mobile (5.077e-3), può essere necessario riservare 4 Byte. La dimensione di questi tipi di dati in memoria dipende anche dal sistema operativo e dal tipo di CPU che utilizza il computer. Esempio: cpu a 32 bit, cpu a 64 bit
  • Ogni tipo di dato offre la possibilità di effettuare un certo tipo di operazioni invece di altre: il tipo carattere non può essere sommato come il tipo intero.
  • In C++, una variabile può essere di un solo tipo di dato, quindi il linguaggio è detto fortemente tipizzato. Esistono altri linguaggi, detti debolmente tipizzati, dove il tipo di dato a cui appartiene una variabile si può cambiare più facilmente.
  • Esiste la possibilità di traferire un valore di un certo tipo (ad esempio, carattere), in una variabile di un tipo diverso (ad esempio, numerico intero) ma ci sono delle regole rigidissime da rispettare… (vedere operazione di casting)

In C++ esistono tipi primitivi (built in), come int, bool, char, float, double, ma il programmatore può creare anche nuovi tipi di dati. Inoltre la libreria standard fornisce nuovi tipi di dati come string, complex, vector,…

Casting

I seguenti operatori permettono di cambiare il tipo di un dato, ad esempio da numero intero a numero con virgola mobile:

  • static_cast
  • const_cast
  • reinterpretet_cast (con puntatori)
  • dynamic_cast (con puntatori ad oggetti classe)

Inoltre, in C++ si può usare per il casting la stessa sintassi del linguaggio C (nuovotipo(variabile)) ma è sconsigliato farlo.

static_cast<tipo>(expr)

Ad esempio, date due variabili intere i e j:

  • static_cast<double>(i/j) // effettua prima la divisione intera e poi il cast 
  • static_cast<double>(i)/j // effettua la conversione di i, poi la divisione 
                             // (tra un double e un int) 

Al momento della definizione di una variabile, viene riservata una zona di memoria che conterrà il suo valore, ma cosa contiene inizialmente quella memoria? Se il programmatore lo prevede, può anche specificare il contenuto iniziale di una variabile al momento della sua definizione, come in questo esempio:

int somma = 80;       // definizione ed inizializzazione  

Nel caso in cui l'area di memoria rimanesse non inizializzata, in generale, nessuno può prevedere cosa ci sarà. Ci può essere un numero strano, dovuto alla presenza di un vecchio numero che non è stato cancellato dalla memoria. Alcuni tipi di variabili sono inizializzate automaticamente (vedere gestione della memoria

Una variabile può, durante la sua vita, contenere diversi valori. L'assegnazione effettua la memorizzazione di un nuovo valore all'interno di una variabile, sostituendolo al precedente.

x = 44;

Il valore da memorizzare si trova a destra del segno di uguale, la variabile dove il valore viene memorizzato si trova a sinistra dell'uguale. Al posto di un valore numerico costante (come 44) ci può essere anche un'espressione mista di variabili, operatori e numeri. L'ordine di esecuzione prevede che prima venga valutata l'espressione a destra (44+y*2) e poi venga fatta l'assegnazione alla variabile a sinistra (x).

x = 44+y*2;    // da eseguire da destra a sinistra del segno uguale

Per questo motivo spesso l'assegnazione viene rappresentata nei libri di testo anche con una freccia da destra a sinistra:

x ← 44

In C++ è lecito assegnare una variabile su se stessa:

x = x;         // assegnazione abbastanza inutile

Una assegnazione su una variabile può modificare se stessa (attenzione! eseguire prima l'espressione a destra del segno uguale e poi a sinistra)

x = x + 1;     // il nuovo valore di x è 45      

La precedente scrittura, ovviamente, non deve essere letta come se fosse un'equazione matematica…

Operazioni su int

Le classiche operazioni aritmetiche hanno un preciso ordine di precedenza. In questo elenco quelle più in in alto hanno precedenza maggiore, mentre quelle che si trovano sulla stessa riga hanno lo stesso ordine di precedenza:

  • negazione, incremento e decremento:
     ! ++ -- 
  • aritmetici
     / * % 
  • aritmetici
     + - 
  • confronto
     > < == <= >= != 
  • logici
     && 
  • logici
     || 
  • assegnazione
     = += *= -= /=  

Incremento

L'operatore incremento di uno (++), esiste in due versioni che hanno effetti leggermente diversi a seconda che l'operatore preceda (++x) oppure segua (x++) l'identificatore della variabile. Si veda il seguente esempio, anche se non è rigorosamente corretto nei commenti:

incremento.cpp
#include <iostream>
int main()
{
   int a=0, b=0, x=100;
   a = ++x; // prima incrementa x, poi esegue l'assegnazione
            // x vale 101, a vale 101
   std::cout << "valori di x, a: "<< x << ' ' << a << std::endl; // stampa 101 e 101
 
   b = x++; // prima esegue l'assegnazione, poi incrementa x
            // x vale 102, b vale 101
   std::cout << "valori di x, b: "<< x << ' ' << b << std::endl; // stampa 102 e 101
   return 0;
}

Questi effetti “collaterali” si hanno solo quando l'operatore incremento (o decremento) sono usati nelle assegnazioni. In un'istruzione per incrementare un contatore questo effetto è quasi indifferente, perché il valore restituito non viene usato e viene scartato. Ad esempio in:

 i++; 

La verità

In realtà, anche se i commenti nel precedente programma sembrano spiegare correttamente il risultato che si ottiene output, essi non sono corretti.

Per sapere cosa accade veramente bisogna conoscere bene il significato del termine valore restituito. Anche l'operatore x++ incrementa immediatamente la x a 102 (ancor prima di eseguire l'assegnazione). L'esecuzione dell'espressione x++ però, restituisce come valore il vecchio valore di x.

Si deve immaginare che venga prima eseguita l'espressione a destra del segno di uguale, nella seguente sequenza di operazioni:

  1. b = x++; // x vale 102
    1. l'espressione x++ viene valutata
    2. dentro x viene memorizzato 102
    3. x++ restituisce 101, come se fosse sostituito dal valore 101, come nella seguente fittizia istruzione:
  2. b = 101; // 101 è il valore restituito dopo il calcolo su x

Viceversa l'operatore ++x dopo aver effettuato l'incremento, restituisce il nuovo valore di x.

Quelli che seguono sono due esempi di output sul terminale:

03.cpp
// questo programma è stato scritto da Fabio
 
#include <iostream>
// che cosa contiene il file iostream?
 
int main ()
{
  int somma;
  somma = 11+2;
  std::cout << somma << std::endl;
  return 0;
}
04.cpp
// questo programma è stato scritto da Fabio
#include <iostream>
int main ()
{
  std::cout << 11+2 << std::endl;  // la precedente variabile somma era inutile...
  std::cout << 11/2 << std::endl;  //operazione di divisione tra numeri interi
  return 0;
}

std::cin - std::cout - std::cerr

CONSOLE INPUT, OUTPUT ed ERROR

Sono oggetti che appartengono alla libreria standard, con header:

#include <iostream>

Appartengono al namespace std. Non sono funzioni, ma oggetti (di tipo classe).

Per approfondire il significato del namespace std vedere namespace_come_contenitori_di_piu_header

operatore Extractor >>

Extractor preleva caratteri (da tastiera) e li trasforma in dati dentro una variabile. Quando si inserisce un testo, vengono ignorati eventuali spazi o tabulazioni in testa e la lettura si interrompe al primo spazio bianco, oppure alla prima tabulazione o al primo “invio”.

std::cin >> variabile;
  • è insensibile agli eventuali spazi iniziali (o tabulazioni)
  • termina la lettura al primo spazio (o invio o tabulazione)
  • la funzione getline(std::cin,varstring) legge invece anche gli spazi
  • la funzione std::cin.get(varchar) legge un solo carattere senza chiedere la pressione di invio.

operatore Insertion <<

Insertion deposita caratteri (nel terminale video) a partire dai dati della variabile

std::cout << variabile;
  • il valore speciale std::endl inserisce un newline e svuota il buffer
05.cpp
// questo programma è stato scritto da Fabio
// visualizza solo un semplice testo (sempre lo stesso)
#include <iostream>
 
int main ()
{
  std::cout << "Ciao a tutti!" << std::endl;
  return 0;
}
06errore.cpp
// questo programma è stato scritto da Fabio
// legge un dato alla volta dalla tastiera e lo visualizza 
 
#include <iostream>
 
int main ()
{
  int mioNumero1;
  std::cout << "Per favore scrivi due numeri separati dallo spazio bianco: ";
  std::cin >> mioNumero1;
  std::cout << "Hai inserito: " << mioNumero1 << std::endl;  // visualizza solo il primo numero...
 
  return 0;
}
06.cpp
// questo programma è stato scritto da Fabio
// legge un dato alla volta dalla tastiera e lo visualizza 
 
#include <iostream>
 
int main ()
{
  int mioNumero1, mioNumero2;
  std::cout << "Per favore scrivi due numeri separati dallo spazio bianco: ";
  std::cin >> mioNumero1 >> mioNumero2 ;  // legge entrambi i numeri separati dallo spazio
  std::cout << "Hai inserito: " << mioNumero1 << " e " << mioNumero2 << std::endl;  
 
  return 0;
}
07.cpp
// questo programma è stato scritto da Fabio
// visualizza anche i simboli in codifica UTF-8
 
#include <iostream>
 
int main ()
{
  std::cout << "Per favore scrivi un numero intero: ";
  int mioNumero;
  std::cin >> mioNumero;
  std::cout << "Tu hai in tasca " << mioNumero << "\u20AC?" << std::endl;
  return 0;
}
07errato.cpp
// questo programma è stato scritto da Fabio
// contiene degli errori che devono essere trovati
 
#include <string>
 
int main ()
{
  int mioNumero;
  std::cout << "Per favore scrivi un numero intero: ";
  std::cin >> mioNumero;
  std::cout << "Tu hai in tasca " << mioNumero << "\u20AC? << std::endl;
  return 0;
}

L'uso delle direttive al compilatore diverrà più chiaro nei prossimi capitoli

L'operatore divisione visto per i numeri interi funziona in modo diverso per i numeri in virgola mobile:

08.cpp
// questo programma è stato scritto da Fabio
#include <iostream>
int main ()
{
  float divisione; 
  //operazione di divisione tra numeri approssimati in virgola mobile
  divisione = 11.0/2.0;
  std::cout << divisione << std::endl;
  return 0;
}

Manipolatori permanenti

I manipolatori di output permettono di trattare il testo prima di visualizzarlo. Alcuni hanno un effetto temporaneo, molti hanno un effetto permanente (vanno annullati esplicitamente)

base.cpp
#include <iostream>
#include <iomanip>
int main()
{
    std::cout << 44 << std::endl;                                  // 44
    std::cout << std::setw(4) << 44 << std::endl;                  // ampiezza output (non permanente)
    std::cout << std::hex << 44 << std::endl;                      // 2c (base permanente) 
    std::cout << std::showbase << 44 << std::endl;                 // 0x2c 
    std::cout << std::dec << 44 << std::endl;                      // 44
    std::cout << std::setprecision(4) << 99.123465 << std::endl;   // 99.12 (precisione permanente)
    std::cout << std::fixed << 99.123465 << std::endl;             // 99.1235
    std::cout << std::scientific << 99.123465 << std::endl;        // 9.9123e+01
    return 0;
}

Errori di conversione

Per i numeri interi (positivi o negativi) la conversione tra sistema base 10 e base 2 del computer è sempre esatta e priva di errori. Dal punto di vista matematico invece possono accadere dei problemi con i numeri decimali (con virgola). vedi en.wikipedia.org

I numeri decimali vengono memorizzati in due valori, mantissa ed esponente: MANTISSA * BASEESPONENTE

Anche le calcolatrici usano questa notazione esponenziale: 1 * 10-37 o anche 1E-37

  • La prima osservazione: è impossibile rappresentare al computer numeri Irrazionali o Razionali Periodici illimitati.
  • Una seconda osservazione: alcuni numeri in base 10, anche con poche cifre decimali, potrebbero essere convertiti in numeri in base 2 con un numero illimitato di cifre dopo la virgola (e viceversa). Questo perché, in base 2, i numeri con la virgola sono limitati solo quando il denominatore è una potenza del 2. Esempio (0.1)base 10 viene approssimato (usando 24 bit) con 0.10000000149011… In fondo a questa pagina si trova lo svolgimento della conversione e il codice di un programma.

In tutti i casi appena descritti, poiché il computer ha un numero di cifre limitato, commetterà sicuramente un errore nel calcolo…

Tabella tratta da http://en.wikipedia.org/wiki/Binary_numeral_system
Converting 	        Result
0.1 	                0.
0.1 × 2 = 0.2 < 1 	0.0
0.2 × 2 = 0.4 < 1 	0.00
0.4 × 2 = 0.8 < 1 	0.000
0.8 × 2 = 1.6 ≥ 1 	0.0001
0.6 × 2 = 1.2 ≥ 1 	0.00011
0.2 × 2 = 0.4 < 1 	0.000110
0.4 × 2 = 0.8 < 1 	0.0001100
0.8 × 2 = 1.6 ≥ 1 	0.00011001
0.6 × 2 = 1.2 ≥ 1 	0.000110011
0.2 × 2 = 0.4 < 1 	0.0001100110
09.cpp
// questo programma è stato scritto da Fabio
//
// mostra che la differenza 1.1 - 1.0 non fa 0.1
// perché per esprimere 0.1 (in base 2) comporta degli errori di troncamento. 
// Gli errori diventano visibili solo quando si guardano un numero elevato di cifre
// in un sistema a 32 bit, il risultato della differenza dovrebbe essere  1.00000001490e-01
 
#include <iostream>
#include <iomanip>
int main ()
{
  float differenza;
  differenza = 1.1-1.0;
  std::cout << std::setprecision(11) << differenza << std:endl;
  return 0;
}

Confrontare i seguenti due programmi…

10.cpp
// questo programma è stato scritto da Fabio
// per calcolare la propria eta'
 
#include <iostream>
#include <string>
 
int main ()
{
  std::string mioNome;
  std::cout << "Per favore scrivi il nome: ";
  std::cin >> mioNome;
 
  int mioAnnoNascita;
  std::cout << "Per favore scrivi l'anno di nascita: ";
  std::cin >> mioAnnoNascita;
 
  std::cout << mioNome << ", quest'anno compie " 
            << 2012-mioAnnoNascita << " anni." << std::endl;
  return 0;
}
11.cpp
// questo programma è stato scritto da Fabio
// a volte è necessario creare dei valori costanti che
// il programmatore può modificare facilmente in un solo punto del codice
 
#include <iostream>
#include <string>
 
int main ()
{
  const int annoAttuale=2012; 
 
  std::string mioNome;
  std::cout << "Per favore scrivi il nome: ";
  std::cin >> mioNome;
 
  int mioAnnoNascita;
  std::cout << "Per favore scrivi l'anno di nascita: ";
  std::cin >> mioAnnoNascita;
 
  std::cout << mioNome << ", quest'anno compie " 
            << annoAttuale-mioAnnoNascita << " anni." << std::endl;
  return 0;
}

L'indentazione è una convenzione che aggiunge degli spazi bianchi davanti alle righe di un programma per facilitarne la lettura. Questi spazi bianchi davanti alle istruzioni non hanno nessun significato speciale (eccetto in Python), e quindi ogni persona potrebbe usarli in modo diverso. Lo stile di indentazione modifica anche la posizione delle parentesi graffe dei blocchi di codice.

esempio: http://en.wikipedia.org/wiki/Indent_style#Horstmann_style

Lo stile da usare è abbastanza libero, ma va usato coerentemente.

  1. Cosa significa main()?
  2. Cosa significa return?
  3. Che significa il punto e virgola?
  4. Cosa significano le parentesi graffe?
  5. Cosa significa il termine assegnazione?
  6. Che cosa significano il doppio slash //?
  7. Fare un esempio di indentazione, a che serve?
  8. Quali sono i tipi primitivi semplici del linguaggio C++?
  9. Che significa linguaggio “fortemente tipizzato”?
  10. Che significa linguaggio “compilato”?
  11. Programmi imperativi da scrivere:
    1. esercizio: chiedere due numeri e visualizzare la somma
    2. esercizio: chiedere due numeri e visualizzare la media aritmetica
    3. esercizio: chiedere un nome e un anno di nascita e poi visualizzare nome ed età
per tornare all'indice → Linguaggio C++
  • appunti3s/programmazione_imperativa.txt
  • Last modified: 2019/07/28 10:29
  • by profpro