La CPU riconosce un insieme di operazioni elementari per l'acquisizione, la memorizzazione, l'elaborazione dei dati e l'emissione dei risultati, quale ad esempio l'istruzione:
memorizza il valore 100 nella locazione di memoria avente indirizzo 12345
La programmazione a questo livello di dettaglio è molto scomoda: costringe il programmatore a gestire gli spazi di memoria e a esprimere un'operazione descrivendola nei termini che la CPU riconosce.
La programmazione in linguaggio C++ permette al programmatore di
esprimere sinteticamente un comando. Sarà il compilatore a farsi carico di tradurlo nelle istruzioni che la CPU è in grado di eseguire (sinteticità);
apportare modifiche e miglioramenti al programma agevolando l'individuazione delle istruzioni che devono essere revisionate (Manutenzione);
Rendere il programma eseguibile da qualsiasi CPU (Portabilità).
Rispetto ad altri linguaggi di programmazione il C++ consente di utilizzare funzioni di basso livello allo scopo di giungere ad un programma ottimizzato sia in termini di spazio occupato sia in termini di tempo di esecuzione
Il linguaggio C possiede poche istruzioni, ma molti operatori e molte funzioni.
Alcuni degli operatori corrispondono direttamente a un'istruzione macchina. Inoltre sono ammesse espressioni che permettono al compilatore di produrre un codice ottimizzato.
Esempi:
gli operatori di autoincremento e autodecremento hanno la corrispondente istruzione assembler: INC e DEC.
In un comune linguaggio di programmazione per incrementare una variabile bisogna usare un espressione come:
i = i + 1,
che il compilatore potrebbe tradurre con una sequenza di istruzioni come le seguenti:
MOV AX, valore della variabile i
ADD AX, 1
MOV variabile i, AX
Mentre in linguaggio C il compilatore traduce direttamente:
INC variabile i
In questo modo il programma è più compatto, ma anche più veloce. Infatti l'istruzione INC può essere lunga anche solo un byte (se l'operando si trova in un registro) e viene eseguita con il minimo numero di cicli di clock.
Tra le espressioni che il compilatore riconosce e sfrutta per ottimizzare il codice vi è
l'assegnazione multipla.
Normalmente l'inizializzazione di variabili comporta un lungo elenco di assegnazioni del tipo:
a = 0
b = 0
c = 0
In linguaggio C, poichè l'operatore di assegnazione si comporta come
una funzione, cioè restituisce il risultato dell'espressione a secondo membro, allora sono ammesse
espressioni della forma:
a = b = c = 0
Anche in questo caso il compilatore produce una traduzione più compatta, della forma:
XOR AX, AX
MOV variabile a, AX
MOV variabile b, AX
MOV variabile c, AX
In ciascuna istruzione un operando è un registro contenente il valore da assegnare alle variabili, quindi le istruzioni sono lunghe al massimo 2 byte. In un altro linguaggio di programmazione, invece, il compilatore avrebbe prodotto istruzioni contenenti ciascuna lo stesso valore dell'operando, e quindi lunghe anche 3 byte.
Dopo la fase di analisi del problema, l'individuazione dell'algoritmo risolutivo e la definizione delle strutture dati, il programmatore deve codificare il procedimento che risolve il problema in un linguaggio di programmazione. La produzione del programma, a questo punto, avviene in tre passi.
Il programma viene scritto nel linguaggio di programmazione tramite un text editor. (programma sorgente)
Il programma sorgente viene dato in input al compilatore che produce in output il programma oggetto.
Il programma oggetto è la traduzione in linguaggio macchina del programma sorgente. Si deve considerare che la traduzione non consiste nel trasformare ciascuna istruzione dal linguaggio C al linguaggio macchina, perchè un'istruzione in linguaggio C esprime un'operazione complessa che potrebbe essere descritta mediante molte istruzioni in linguaggio macchina. Inoltre Il programmatore fa uso di istruzioni che non appartengono al linguaggio, ma sono disponibili in una libreria di funzioni (ad esempio le operazioni matematiche). Il terzo passo (collegamento o Linking) consiste nel collegare tutti i riferimenti che il compilatore non ha tradotto alle funzioni di libreria per produrre il programma eseguibile.
il compilatore, per svolgere la traduzione, compie un'analisi sintattica delle istruzioni (operazione di Parsing), che consiste nel riconoscere gli elementi che fanno parte dell'istruzione (codice operativo e operandi)
Alcuni linguaggi di programmazione (ad esempio il basic) sono detti interpretati perchè ciascuna istruzione del programma in formato sorgente viene tradotta e subito eseguita. Addirittura se il flusso del programma conduce nuovamente su un'istruzione già incontrata, l'interprete la traduce di nuovo, prima di eseguirla. Si nota che in un linguaggio interpretato la fase di esecuzione del programma dura molto di più a causa della continua traduzione delle istruzioni. In un linguaggio compilato, invece, la traduzione viene fatta una sola volta.
Il linguaggio C++ si basa sul linguaggio C per realizzare la programmazione orientata agli oggetti (OOP).
Si ricordi che il linguaggio C++ è case sensitive (distingue tra maiuscole e minuscole): la variabile unaVariabile è diversa dalla variabile unavariabile.
Avviare Dev Cpp e aprire un nuovo progetto di tipo "Console Application". Scrivere il seguente programma:
// il programma che saluta
#include <cstdlib>
#include <iostream>
int main(int argc, char *argv[]) {
std::cout << "Hello World\n";
system("PAUSE");
return EXIT_SUCCESS;
}
Gli elementi che vengono riconosciuti dal compilatore sono:
tipo | Descrizione | Esempi |
Keywords | Termini che hanno un significato per il compilatore | int, double, for |
Identificatori | nomi, che non appartengono al linguaggio, scelti dal programmatore | argc, argv, x, unaFunzione |
Costanti (Literals) | Valore specificato nell'istruzione | "Hello world!", 24.3, 0, ‘c’ |
Operatori | segni per indicare operazioni matematiche o logiche | +, -, &&, %, << |
Spazi | segni usati nel text editor che vengono ignorati dal compilatore | Spazi, tabulazioni, vai a capo |
Linea 1: // il programma che saluta
i due slash // indicano che da quel punto inizia un commento che il compilatore deve ignorare. Si tratta,
cioè di un promemoria o di un'annotazione per il programmatore. Se si vuole inserire un commento che
occupa solo una parte della riga, il testo deve esere delimitato tra i segni /* e */
(esempio:
x = 1 + /* aggiungo uno */ 1;).
Un commento di tale forma può occupare anche più righe
Le linee che iniziano con il simbolo # sono direttive al compilatore, #include avverte il compilatore che se, durante il processo di traduzione, trova un elemnto che non fa parte del linguaggio, deve cercare la sua definizione nel file specificato. In questo caso la libreria iostream contiene le funzioni per l'input e l'output.
int main() {...} un programma C++ è costituito da molte funzioni. L'esecuzione inizia dalla funzione chiamata main. Le parentesi graffe delimitano un Blocco di istruzioni.
cout <<: Viene inviata una stringa di caratteri sullo schermo.
Namespaces: gli identificatori che vengono definiti in C++ hanno una regione di visibilità
chiamata namespace. Quando si vuole accedere ad un identificatore definito in una regione di visibilità
bisogna specificarlo usando l'operatore :: (Scope Resolution).
La prima istruzione, all'interno del main, specifica al compilatore che l'identificatore cout
deve essere cercato nello spazio dei nomi standard. In alternativa, e in una maniera più chiara, si
può aggiungere la seguente linea dopo le direttive:
using namespace std;
Questa linea dice al compilatore che deve cercare nello spazio std tutti gli identificatori che non sono
stati definiti dal programmatore. In questo caso non è più necessario anteporre std::
davanti a cout.
Stringhe: La sequenza di caratteri "Hello World" è una stringa, in questo caso è una stringa costante.
Sequenze di Escape: il carattere "n" preceduto dal segno backslash (\n = new line), all'interno di una stringa rappresenta il comando per portare il cursore di stampa sulla riga successiva. Si tratta di una sequenza di escape ovvero una combinazione di caratteri che rappresentano un codice che non deve essere stampato ma deve comandare il posizionamento della testina di stampa. Di seguito viene fornito un elenco di sequenze di escape:
Sequenza di Escape | Azione |
\a | Campanello (bip prodotto dall'altoparlante sulla scheda) |
\b | Cancella il carattere a sinistra del cursore (Backspace) |
\f | Emissione del foglio di stampa (Formfeed) |
\n | Passa alla linea successiva (Newline) |
\r | Porta il cursore all'inizio della riga (carriage Return) |
\t | Porta il cursore all'inizio del campo successivo. La riga di stampa è suddivisa in colonne larghe 8 caratteri. In questo modo quando un dato da stampare viene preceduto dal carattere di tabulazione si ottiene un'incolonnamento dei dati. (Tabulazione) |
\\ | Quando all'interno della stringa bisogna specificare un carattere backslash (ad esempio per specificare il percorso di un file) si devono usare due caratteri Backslash |
\‘ | Apice |
\" | Doppio apice |
\x (con x intero) | viene rappresentato il carattere il cui codice Ascii è x |
return EXIT_SUCCESS; indica che il programma restituisce il controllo al sistema operativo fornendo un codice che ha il significato di "terminazione corretta".
Notare che, per indicare al compilatore la fine di un'istruzione e l'inizio della successiva, ogni istruzione è terminata con il punto e virgola. Infatti un'istruzione potrebbe occupare più linee. Oppure su una sola linea si possono inserire più istruzioni
un programma contiene:
direttive al compilatore,
Una direttiva fornisce indicazioni al compilatore relative alla modalità di traduzione del programma.
dichiarazioni,
una dichiarazione rappresenta la richiesta di riservare uno spazio di memoria
istruzioni,
un'istruzione descrive un'azione da compiere.
Sia in una dichiarazione, sia in un'istruzione si possono formare delle espressioni usando gli operatori matematici:
Il tipo di un dato è l'intervallo di valori che il dato può assumere e viene quindi espresso mediante il numero di bit che vengono riservati per rappresentare il valore.
Tipo | Descrizione | Nr. bit | Range |
char | Un solo carattere. Indicato tra singoli apici (‘a‘, ‘3‘). | 1 byte | signed: -128 to 127 unsigned: 0 to 255 |
int | intero. | 4 bytes | signed: -2147483648 to 2147483647 unsigned: 0 to 4294967295 |
bool | Boolean (true/false). Può assumere solo i valori true e false. | 1 byte | true (1) - false (0). |
double | numero in "Doppia" precisione. | 8 bytes | ±1.7e ±308 ( 15 digits) |
Note:
un intero con segno può rappresentare i numeri negativi, mentre un numero intero senza segno può rappresentare numeri positivi.
ci sono tre tipi di interi: short, int, e long. Anche i numeri reali possono essere di tre tipi: float, double, e long double
Il range non è standardizzato: il numero di bit usato per rappresentare un tipo di dato dipende dalla dimensione dei registri della CPU. Come regola le operazioni devono essere compiute su dati dello stesso tipo.
Il risultato di un'operazione è un valore dello stesso tipo degli operandi. Quindi ad esempio 3/2 fornisce 1, perchè i due operandi sono interi. Per ottenere il risultato corretto bisogna esprimere i due operandi in notazione reale: 3.0/2.0;
Una stringa di caratteri viene dichiarata di tipo char *.
Una variabile è il nome assegnato ad una locazione di memoria destinata a contenere un dato. Per esempio per fare dei calcoli con il valore 6 si memorizza il valore nella variabile x:
#include <iostream>
using namespace std;
int main () {
int x;
x = 6;
cout << x / 3 << ' ' << x * 2;
return 0;
}
Si noti che si può stampare una sequenza di valori concatenandoli con il simbolo <<
Gli identificatori possono contenere lettere, numeri e caratteri di sottolineatura, ma non devono cominciare con un
numero.
La linea int x è la dichiarazione della variabile x. Con essa si chiede al compilatore di riservare
uno spazio sufficientemente grande per rappresentare un valore intero e di riferisi ad esso con il nome x.
La linea x = 6 è un'operazione di assegnazione: viene valutata l'espressione a secondo membro e il
risultato viene scritto nella locazione identificata con x
Le due linee potrebbero essere sostituite da:
int x = 4 + 2;
In questo caso l'assegnazione è detta inizializzazione.
Il programma precedente svolge sempre le stesse operazioni sullo stesso valore, In generale il programma deve svolgere le stesse operazioni ma sui dati forniti in input.
#include <iostream>
using namespace std;
int main () {
int x;
cin >> x;
cout << x / 3 << ' ' << x * 2;
return 0;
}
Il doppio segno maggiore o minore nelle istruzioni di I/O viene detto operatore di scorrimento e il suo verso concorda con la direzione del trasferimento: dalla tastiera >> alla memoria (Input), dalla memoria << allo schermo (Output).
Gli errori possono essere segnalati dal compilatore quando non è stata rispettata la sintassi o possono essere riscontrati nei risultati diversi da quelli attesi. In questo caso il compilatore non è in grado di fornire assistenza perchè non riesce a fare l'analisi semantica delle istruzioni. Il programmatore deve adottare opportune tecniche di collaudo per verificare che il programma fornisca sempre risultati corretti per qualsiasi combinazione di dati di input venga data. In presenza di risultati sbagliati il programmatore deve poi riconoscere le istruzioni che provocano il comportamento imprevisto e correggerle.
Il linguaggio C++ possiede gli stessi operatori e le stesse regole di scrittura delle dichiarazioni del linguaggio C. Qualsiasi cosa che si può fare in C può essere fatta in C++. Le caratteristiche che introduce il linguaggio C++ rappresentano un nuovo stile di programmazione che rispetta i principi della programmazione orientata agli oggetti (OOP: Object Oriented Programming).
Il termine stream, in informatica, ha il significato di flusso continuo e ordinato di dati e rappresenta il modello di un generatore di informazioni (byte) che li fa scorrere lungo un canale verso il ricevitore, dove vengono raccolti nel loro ordine di arrivo.
Osservando la versione C++ del programma che stampa "Hello World", chi conosce le operazioni di stampa in C, può notare che questo programma non include stdio.h ma include iostream.h, e non chiama printf per stampare una stringa, ma usa la variabile non dichiarata denominata cout, inoltre si vede anche l'operatore di scorrimento a sinistra e la stringa da mostrare.
Per mostrare un messaggio sullo schermo:
La stringa Hello, wor1d\n viene inviata sullo schermo. l'operatore << (in C++) è chiamato operatore di scorrimento. È orientato dall'elemento che deve essere inviato (la stringa) al destinatario che lo deve acquisire (lo schermo).
Se anzichè stampare una stringa si volesse stampare un intero, in C si dovrebbe usare printf con il destrittore di formato relativo alla variabile da stampare:
In C++ non è necessario specificare il formato:
#include <iostream.h>
void main () {
int Numero = 123;
cout << Numero;
}
Il programma stampa 123.
Si può inviare qualsiasi tipo di dato predefinito sullo stream di output, non occorre specificare il formato. Lo stream cout riconosce il tipo della variabile e la stampa correttamente. Nell'esempio seguente si mostra come stampare una stringa, un intero e un carattere usando una sola istruzione.
void main () {
int Numero = 123;
cout << "valore di numero: " << Numero << endl;
}
Questo programma invia tre diversi tipi di variabili a cout: una stringa, la variabile intera Numero e il carattere endl per chiudere il messaggio con un vai a capo. Il programma stampa:
Valore di Numero: 123.
Notare che i diversi tipi sono separati dall'operatore <<.
Applicare il Formato all'Output
Si supponga di volere stampare il valore contenuto in una variabile intera secondo il codice esadecimale. Il linguaggio C++ associa dei manipolatori allo stream di output. Questi cambiano il formato di rappresentazione di default per gli interi. I nomi dei manipolatori sono dec, oct e hex. L'esempio che segue mostra come stampare un intero nella codifica ottale ed esadecimale:
void main () {
int Numero = 123;
cout << dec << Numero << ' '
<< oct << Numero << ' '
<< hex << Numero << endl;
}
Nelle istruzioni di stampa ognuno dei manipolatori (dec, oct e hex) converte il valore contenuto nella variabile intera Numero nelle 3 differenti rappresentazioni. Questo programma stampa:
123 173 7b
Ognuno dei valori stampati corrisponde allo stesso numero 123 decimale.
Per leggere dati immessi tramite la tastiera il linguaggio C++ mette a disposizione cin. Nel prossimo esempio si mostra come usare cin per leggere un dato intero da tastiera:
void main () {
int Numero;
cout << "Scrivi un numero: ";
cin >> Numero;
cout << endl << "il numero inserito e': " << Numero << endl;
}
In questo esempio si chiede di inserire un valore numerico da tastiera e cin lo trasferisce nella variabile Numero. L'istruzione di stampa mostra il valore contenuto nella variabile Numero usando cout.
Si può usare cin per leggere qualsiasi altro tipo di dato. L'esempio seguente mostra come leggere una stringa da tastiera:
void main () {
int nome[20];
cout << "Scrivi un nome: ";
cin >> nome;
cout << endl << "il nome inserito e': " << nome << endl;
}
L'array di caratteri, in cui memorizzare la stringa, è lungo solo 20 caratteri. Se si scrive un nome di lunghezza maggiore potrebbe verificarsi un errore, La funzione get risolve questo problema (consultare la guida alla libreria iostream). Per adesso si assuma che non verranno inseriti più di 20 caratteri.
Nota - Si ricordi che le funzioni di input e di output del C, scanf e printf non appartengono al linguaggio, ma sono funzioni di libreria. Analogamente gli stream cin e cout non appartengono al linguaggio C++, ma sono definite nella libreria iostream.h. Inoltre il significato degli operatori << e >> dipende dal contesto in cui vengono usati. Quando vengono usati con cout e cin consentono di scrivere o di leggere dati.
In C++ è ammesso indicare i commenti usando sia la notazione del C, racchiudendo il commento tra la coppia di caratteri /* di inizio commento e la coppia di caratteri */ di fine commento, che con la notazione a doppio slash (//). Questa, però interpreta come commento tutto ciò che segue, fino alla fine della riga.
Una funzione può essere dichiarata prima di essere definita.
La dichiarazione specifica l'identificatore usato come nome della funzione, il tipo del valore di ritorno e il tipo
dei parametri. La dichiarazione di funzione, che precede il suo uso è chiamata prototipo di funzione.
Questo consente al compilatore di verificare se le chiamate di funzione rispettano il tipo dei parametri.
L'esempio che segue usa un prototipo di funzione per stampare i saluti:
// programma con il prototipo di funzione
void main () {
mostra("Hello word");
}
void mostra(char *s) {
cout << s;
}
Il prototipo di funzione non è necessario se la funzione viene definita prima di essere usata perchè la definizione agisce anche come prototipo. Il prototipo è necessario se la funzione viene definita in un file separato.