C/C++: introduzione
Linguaggi C e C++
C è un linguaggio di programmazione compilato nato nel 1972, inventato nei laboratori Bell di AT&T da Dennis Ritchie e Brian Kerningham come linguaggio con cui scrivere applicazioni per il sistema operativo Unix (inventato sempre in quel periodo dallo stesso Ritchie insieme a Ken Tompson). Il linguaggio C è oggi presente nella pressochè totalità dei sistemi operativi della pressochè totalità dei computer esistenti.
E’ utilizzato principalmente per realizzare sistemi operativi (il kernel cuore del sistema Linux è scritto in C), librerie di natura scientifica, robotica, intelligenza artificiale, calcolo numerico, videogiochi e sistemi real time. Il set di istruzioni e la sintassi del C è semplice da imparare, ed ha avuto un tale successo che è utilizzato in modo quasi identico dalla maggior parte dei linguaggi di programmazione più diffusi al mondo (come Java, C#, Javascript, PHP e parzialmente Python).
Il C inoltre consente una gestione a basso livello della memoria, grazie all’utilizzo dei puntatori e ad istruzioni di allocazione e deallocazione dinamica della memoria. Per questo si rende particolarmente adatto a scopo didattico, in quanto permette di comprendere appieno il modo in cui funziona un programma, come accede alla memoria e come gestisce il flusso di esecuzione e la concorrenza.
Una volta imparato il linguaggio permette allo studente di informatica di avere una competenza di base per poter apprendere più facilmente altri linguaggi di programmazione più ad alto livello.
C++ è un linguaggio di programmazione nato nel 1983 da Bjourne Stroustrup, sempre presso i laboratori Bell della AT&T come evoluzione del linguaggio C.
C++ è una estensione del linguaggio C, quindi ogni codice scritto in C funziona anche in C++.
C++ introduce nuove funzioni ad alto livello al C, come la programmazione ad oggetti, i template, i namespace. Inoltre estende la libreria standard con nuove funzioni (come gli stream).
In questa trattazione svilupperemo in C++, anche se la maggior parte delle funzionalità che useremo saranno quelle già presenti nel C. Del C++ prenderemo in particolare il concetto di namespace e soprattutto le funzioni cin/cout, più semplici da imparare dei corrispettivi C (printf e scanf). Il sistema operativo di riferimento sarà Linux.

IDE, compilatore ed ambiente
Il C/C++ è un linguaggio compilato. Dopo che il programmatore ha scritto il codice del programma (detto sorgente), questo viene processato da un compilatore che traduce il codice in linguaggio macchina (detto oggetto), e poi aggiunge (tramite un componente chiamato linker) il codice macchina di librerie esterne che permettono di accedere alle funzioni del sistema operativo (come leggere o scrivere a schermo) o a funzioni utili già pronte (come quelle matematiche). Il risultato è il programma eseguibile. Ogni sistema operativo/hardware ha un compilatore specifico che tiene conto delle caratteristiche hardware (le istruzioni della CPU) e software (il sistema operativo).
Lo sviluppo del codice avviene di norma tramite un Integrated Development Environment, un ambiente dove è possibile sia editare il codice che compilarlo.
Un IDE molto utilizzato è Visual Studio Code, che prevede tramite plugin l’esecuzione con C++ e Linux. VS Code prevede una interfaccia per scrivere codice, visualizzare più sorgenti differenti, anche contemporaneamente, offre integrato un terminale a linea di comando (con cui è possibile interagire con le applicazioni in esecuzione), e infine, offre strumenti per il debugging.
Il debugger è uno strumento che permette di eseguire il codice passo passo, oppure fermare l’esecuzione in una linea di codice (apponendo uno specifico breakpoint), e ispezionare ad ogni interruzione il contenuto delle variabili utilizzate. VSCode si integra col debugger di C++.
Queste funzioni, in modo limitato, sono disponibili anche in molti IDE online, accessibili via web senza installare nulla, come ad esempio OnlineGDB.
Primo programma
Scrivere nell’IDE il seguente programma e salvarlo come helloworld.cpp
#include <iostream>
using namespace std;
int main() {
cout << "Hello World" << endl;
return 0;
}Con VS Code compilare il codice eseguendo:
> g++ helloworld.cpp
Il codice sarà validato, compilato e linkato. Al termine verrà generato un file a.out nella stessa cartella del sorgente. Il compilatore se non ci sono errori non darà nessun output.
Eseguire l’applicazione:
> ./a.out
Hello World
Con OnlineGDB è sufficiente premere su “Run” ed il codice verrà eseguito.
Struttura generale di un programma C++
Un programma C/C++ è costituito da una o più funzioni: queste sono sottoprogrammi definiti da un nome ed un valore di ritorno.
in C/C++ è obbligatorio inserire almeno la funzione main. Questa è, infatti, il punto di ingresso nel programma, ovvero la prima funzione che viene eseguita quando eseguiamo il programma.
Un programma può contenere istruzioni speciali precedute dal carattere #: sono le direttive del compilatore. Un esempio è #include: si tratta di una direttiva che richiede di includere l’header (cioè l’intestazione) di una libreria esterna. Una libreria è un modulo esterno del programma che è stato prodotto da terzi e che contiene funzioni utili per il programma.
La libreria <iostream>
Nel codice di esempio viene inclusa la libreria <iostream> che permette di utilizzare le istruzioni cin e cout, che consentono di leggere da tastiera e di stampare su schermo, ed è necessaria praticamente in ogni programma.
Namespace
E’ inoltre di norma previsto un namespace. Proviamo a capire a cosa serve.
Quando scriviamo un programma possiamo creare funzioni e variabili che identifichiamo con un nome. Non possiamo però usare due volte lo stesso nome: il compilatore va in errore. Il problema si pone quando cominciamo ad includere librerie esterne, c’è il concreto rischio di collisioni di nomi. Per questo sono stati inventati i namespace: essi creano delle aree del programma distinte che permettono di evitare sovrapposizioni.
Per usare un nome di un altro namespace occorre prevedere il prefisso:
nome_namespace::nome_variabile
Ad esempio usando la libreria iostream è possibile usare i simboli cin e cout del namespace std:
std::cin >> n;
std:cout << n;
E’ un po’ come con le persone: il nome della variabile corrisponde alla persona, il namespace è il “cognome” e quindi possono essere chiamate senza ambiguità.
In alternativa si può anteporre la direttiva using per indicare esplicitamente che si usa un determinato namespace:
using namespace std;Che useremo in tutti i nostri programmi per comodità.
Istruzioni di un programma
Qui l’elenco completo di cosa possiamo inserire in un programma C++:
– direttive del pre compilatore (precedute da #), possono essere #include ma anche #define, che vedremo più avanti
– commenti (preceduti da // oppure /* … */ se su più righe)
– istruzioni using namespace
– dichiarazione di variabili globali (esterne e visibili dalle funzioni), le vedremo più avanti
– funzione main (obbligatoria)
– funzioni utente (facoltative)
– istruzioni (interne alle funzioni)
Identificatori e tipi di dato
I nomi di variabile sono chiamati identificatori. Un identificatore valido deve cominciare con:
- una maiuscola oppure
- una minuscola oppure
- carattere _ (detto underscore)
seguito da qualsiasi carattere alfanumerico, ad esempio sono nomi validi a, value, _element, p1, ecc. Gli identificatori sono case sensitive. Non sono consentiti identificatori con nomi riservati con parole chiave del linguaggio (es. if, for, ecc.).
E’ una buona prassi usare come prima lettera una minuscola per nomi di variabile e di funzioni, ed usare nomi “parlanti” per le variabili, per ricordare a cosa servono. L’uso di identificatori molto brevi, come x, y, i, ecc. può essere comunque utile in variabili ad uso circoscritto.
Le variabili così create sono associate ad un tipo di dato che possono memorizzare. I tipi di dato base fondamentali del C++ sono: int, float (virgola mobile), double (virgola mobile doppio), long (intero doppio), bool, char. Ne sono presenti molti altri che useremo in situazioni particolari.
In C++ ogni variabile va dichiara indicando prima il tipo.
Sintassi
La sintassi di C++ prevede le seguenti regole:
– ogni istruzione deve terminare con un ;
int i = 0;Sono consentiti spazi nelle dichiarazioni di variabile, sono invece obbligatori nelle definizioni di tipo e nell’uso di parole chiave riservate.
– una istruzione può essere sostituita da un blocco dichiarato con parentesi {}
{
int i=0;
int j=i+2;
}Le variabili dichiarate in un blocco non sono visibili anche all’esterno del blocco, ricordarsi sempre di questa regola.
Una variabile dichiarata in un blocco e che ha lo stesso nome di una variabile dichiarata esternamente, la va a sostituire (come simbolo) all’interno del blocco. La variabile esterna non sparisce, ma diventa non raggiungibile nel blocco:
a = 3;
{
a = 0; // la a esterna non si vede più.
}
// qui si vede di nuovo la a esternaLe variabili possono essere:
- globali se dichiarate fuori da una funzione, nel corpo principale del programma
- locali se dichiarate in un blocco (visibili solo nel blocco).
Espressioni
Una espressione è una struttura codificata di dati e operatori che viene valutata a runtime.
Essa può essere oggetto di assegnazione o parametro di funzione. L’espressione viene sempre valutata prima del suo utilizzo (ovvero stampa o assegnazione).
Espressioni principali di C++:
– literal: valori numerici o stringhe assoluti. es 1, False, “Ciao”.
– espressioni aritmetiche. Es. “5+4”, “90/2”. Possono essere applicati operatori matematici (+,-,/,*,%, ecc.).
– espressioni stringa: Es. “ciao” + ” Mario”. Possono essere applicati operatori come + (concatenazione).
– espressioni logiche booleane. Es. 5 > 3. Possono essere solo True o False. Possono essere applicati operatori booleani (<, <=, >, >=, !=, ==, !== e !==).
– operatori unari. Es. c++ (incrementa di 1 il valore di c)
– null : indica il valore vuoto.
In C++ la conversione implicita di tipo è possibile solo quando si assegna un valore literal ad una variabile. Esempio: int c = 3;
In tutti gli altri casi la conversione deve essere esplicita.
int b = (int) 2.2.
Input e output
Per l’input e l’output si usano cin e cout. Questi due simboli rappresentano gli “stream” ovvero dei flussi, rispettivamente in ingresso e uscita.
cin: con questo simbolo richiediamo esplicitamente dal flusso di input l’inserimento di un valore tramite operatore >>. Esempio:
int n;
cin >> n;Quando viene eseguita questa istruzione, il programma lascia il controllo all’utente, che da tastiera può inserire un valore. Questo viene passato (>>) a n che ne assume il valore. Occorre fare attenzione: il flusso di cin si interrompe quando l’utente preme invio o spazio. Se preme invio il controllo viene rilasciato al programma, se invece viene premuto spazio cin smette di inviare ma non viene rilasciato il controllo. E’ comunque necessario premere invio per ridare il controllo al programma.
Per inserire una linea intera si usa l’istruzione cin.getline() che si vedrà successivamente.
cout: con questo simbolo inviamo al flusso di output una espressione o più espressioni, tramite operatore <<
Esempio:
cout << “Hello world”;
int n = 4;
cout << “Il valore è: ” << n;
cout << “Ciao” << endl;endl è una espressione speciale che indica il carattere di fine linea.
E’ possibile concatenare più espressioni con l’operatore >>.
Per quanto riguarda i numeri con la virgola (tipo di dato float o double) è possibile indicare la precisione (cioè il numero di cifre) con l’espressione setprecision(x) che indica il numero di cifre. Bisogna però ricordarsi di includere anche la libreria <iomanip>
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double pi = 3.14159;
cout << setprecision(2) << pi << endl; // 3.14
cout << setprecision(5) << pi << endl; // 3.1416
cout << setprecision(7) << pi << endl; // 3.1415900
return 0;
}
