C/C++: introduzione completa
Linguaggi C e C++
C è un linguaggio di programmazione compilato nato nel 1972, inventato da Dennis Ritchie e Brian Kerningham come linguaggio con cui scrivere applicazioni per Unix (inventato da Ken Tompson e lo stesso Ritchie negli stessi anni). Il compilatore C è oggi presente nella pressochè totalità dei sistemi operativi della pressochè totalità dei computer esistenti. E’ utilizzato principalmente per realizzare sistemi operativi (il kernel di Linux è interamente 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, tanto che essa è utilizzata in modo quasi identico dalla maggior parte dei linguaggi di programmazione più diffusi al mondo (come Java, C#, Javascript 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 della memoria. Il linguaggio C, grazie alla sua sintassi, la gestione diretta della memoria, la mancanza di strutture complesse come classi e generici, e la gestione del multithreading, si rende particolarmente adatto a scopo didattico, in quanto permette di comprendere appieno il modo in cui un processo è strutturato in memoria, il modo in cui sono create e gestite variabili semplici e complesse, ed il modo in cui viene gestita la concorrenza.
Una volta imparato il linguaggio permette allo studente di informatica di avere una base di competenza per poter apprendere altri linguaggi più ad alto livello da un punto di vista privilegiato di chi ha avuto modo di avere una visione d’insieme.
C++ è un linguaggio di programmazione nato nel 1983 da Bjourne Stroustrup, presso i laboratori Bell della AT&T. C++ è una estensione del linguaggio C, quindi ogni codice scritto in C compila anche col compilatore C++, tanto che oggi la quasi totalità dei compilatori C compila anche 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 e ambiente
L’Integrated Development Environment è l’ambiente integrato dove avviene lo sviluppo. L’IDE qui 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++.
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;
}
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
Struttura generale di un programma C++
Un programma C++ è costituito da una o più funzioni. Una funzione è un sottoprogramma definito da un nome, un tipo di dato restituito e dei parametri tra parentesi.
E’ 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. Nel codice di esempio viene inclusa la libreria <iostream> che permette di utilizzare l’istruzione cout, che consente di stampare su schermo. Gli header sono quindi, a compile time, le interfacce di programmazione spiegate nel primo paragrafo. A build time saranno sostituite dalle librerie vere e proprie ed incluse nell’eseguibile.
Può essere poi (facoltativamente) previsto un namespace. Quando definiamo un namespace creiamo una regione del programma dove possiamo creare funzioni e variabili che possono essere identificati dall’esterno (del namespace) solo tramite il prefisso del namespace. Un namespace può essere usato anche in più file sorgente e serve per organizzare sorgenti in gruppi ben definiti ed evitare quindi confusione in programmi medio-grandi. Quando vogliamo usare i simboli di un namespace possiamo usare l’istruzione using namespace name
dove name è il nome del namespace. Nell’esempio viene utilizzato il namespace std, che contiene l’istruzione cout. Vedremo i namespace più in dettaglio fra poco.
Qui l’elenco completo di cosa possiamo inserire in un programma C++:
– direttive del compilatore (precedute da #)
– commenti (preceduti da // oppure /* … */ se su più righe)
– istruzioni using
– istruzioni namespace
– dichiarazione di variabili globali (esterne e visibili dalle funzioni)
– funzione main (obbligatoria)
– funzioni utente (facoltative)
– istruzioni (interne alle funzioni)
Tipi di dato e identificatori.
I nomi di variabile sono chiamati identificatori. Un identificatore valido deve cominciare con una maiuscola, minuscola o carattere _, seguito da maiuscole, minuscole, numeri o ancora _. 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, onde ricordare a cosa servono. L’uso di identificatori molto brevi, come x, y, i, ecc. può essere comunque utile in variabili ad uso circoscritto e con visibilità locale.
I tipi di dato base fondamentali del C++ sono: int, float (virgola mobile), double (virgola mobile doppia parola), long (intero doppia parola), bool, char. Ne sono presenti molti altri (vedere documentazione ufficiale).
Si ricorda che la parola è nel computer la dimensione (in bit o byte) del tipo di dato, che dipende dall’architettura della CPU (es. 16, 32, 64 bit, ecc.).
Quindi con una parola di 32 bit, un intero potrà contenere valori da -232 a + 232-1.
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:
{
int i=0;
int j=i+2;
}
Le variabili dichiarate in un blocco non sono visibili anche all’esterno del blocco.
Per estensione le variabili dichiarate in un blocco di funzione sono visibili nella funzione ma non all’esterno.
Una variabile dichiarata in un blocco con lo stesso nome di una variabile dichiarata esternamente, la va a sostituire (come simbolo) all’interno del blocco.
In conclusione, le variabili possono essere globali se dichiarate fuori da una funzione, locali se dichiarate in una funzione (visibili quindi solo in quella funzione) o blocco (visibili solo nel blocco).
Namespace
In C++ sono poi previsti i namespace. I namespace sono un modo per raggruppare nomi di variabili in un contenitore, anche in sorgenti differenti. Questa necessità nasce dal fatto che un programma C++ può essere costituito da molti sorgenti, realizzati anche da differenti sviluppatori: diventa probabile quindi un conflitto di nomi tra variabili col rischio quindi di riusare lo stesso nome di variabile senza volerlo. Il namespace permette di identificare univocamente una variabile rinchiudendola in un contenitore, ovvero il namespace, anche se ha lo stesso nome locale di un’altra variabile.
Questo rende possibile usare lo stesso nome di variabile (anche globale) in due namespace differenti.
Per usare un nome di un altro namespace occorre prevedere il prefisso:
nome_spazio::nome_variabile
Ad esempio usando la libreria iostream è possibile usare i simboli cin e cout del namespace std:
std::cin >> n;
In alternativa si può anteporre la direttiva using per indicare esplicitamente che si usa un determinato namespace:
using namespace std;
In questo modo non si deve usare il prefisso.
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.
Espressioni logiche
Queste espressioni servono per confrontare due o più valori.
Operatore | Significato |
---|---|
== | Uguaglianza |
!= | Diversità |
> | Maggiore |
< | Minore |
>= | Maggiore uguale |
<= | Minore uguale |
Si applicano a valori numerici Hanno come risultato un bool quindi possono avere valore TRUE o FALSE e vengono chiamati anche CONDIZIONI.
Esempio:
a == b
a < 3
k == 4
c <= 6
Operatori booleani
Operatore | Significato |
---|---|
! | Not logico |
&& | And logico |
Permettono di verificare più condizioni insieme (quindi connettono variabili di tipo bool).
Il risultato è di tipo bool quindi TRUE o FALSE.
Espressioni:
le espressioni sono una qualsiasi combinazione di operatori di confronto e booleani. Esse producono un bool quindi TRUE o FALSE
Esempio:
a == b || c == b : vera se a è uguale a b OPPURE c è uguale a b
z < 4 && z > 1 : vera se z è compreso tra 1 e 4 esclusi
(a == 6 || a == 8) && ( b < c) : vera se a è 6 oppure 8 E INOLTRE se b è inferiore a c.
Occorre fare attenzione alle regole di precedenza:
- Prima vengono le parentesi
- Poi vengono gli operatori di confronto
- Infine gli operatori booleani
- Tra i booleani l’ordine è NOT (!), AND (&&), OR (||)
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;
A runtime, quando viene eseguita questa istruzione, il programma lascia il controllo all’utente, che da tastiera può inserire un valore. Questo viene passato (operatore >>) 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, indipendente dal sistema operativo, 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;
}