Basi di programmazione C++
Introduzione alla programmazione C/C++
Appunti di programmazione C e C++ su GNU/Linux
Parte prima
- Introduzione
- Liberatoria
- Come pensa il computer
- La compilazione
- Concetto di funzione
- Variabili
- Input e output: concetto di flusso
- Espressioni e operatori
Introduzione
Il Calcolatore è una macchina programmabile generica: significa che,
partendo da un piccolo insieme finito di istruzioni, il computer
può essere istruito per eseguire i più svariati compiti.
Iniziare a programmare un computer richiede, come minimo, due operazioni fondamentali:
- Analisi del compito da svolgere e progettazione di un algoritmo: un algoritmo è l'insieme ordinato, finito e completo di istruzioni che devono essere eseguite per portare a termine il compito. Un algoritmo è quindi una procedura non ambigua che il computer può eseguire ciecamente per svolgere un determinato compito.
- Stesura dell'algoritmo: una volta definito l'algoritmo che la macchina deve eseguire per completare il suo compito, le singole operazioni devono essere tradotte in un linguaggio che la macchina possa comprendere per eseguirle.
Questa guida si occuperà di spiegare i fondamenti della programmazione procedurale in C e a oggetti in C++, integrando le lezioni con degli esempi concreti. In questo modo, sarà sicuramente soddisfatto il secondo punto fondamentale della programmazione, e, attraverso gli esempi, sarà data una discreta conoscenza del primo, fermo restando che l'unico modo per comprenderlo a fondo è l'esperienza.
Liberatoria
Nella vita, svolgere un compito in maniera completa, esauriente e perfetta è difficile, per non dire pressocché impossibile. I lettori vogliano perdonare l'autore di questa guida per eventuali errori, omissioni o imprecisioni; nel caso in cui vengano rilevati, per favore segnalateli tramite la pagina di contatto e, sia io che voi, saremo persone migliori. Si noti che alcuni aspetti sono stati comunque volutamente ignorati: questa guida non è stata scritta da Stroustrup per Stroustrup.
Come pensa il computer
Premesse:
- Il calcolatore è una macchina, e, in quanto tale, se non è guasta, esegue perfettamente, ma senza un criterio morale, tutte le istruzioni che le vengono impartite.
- Il calcolatore opera sempre con istruzioni e dati binari, cioè una serie di bit. Un bit è l'unità di misura minima dell'informazione: un dato di 1 bit può assumere solo due valori, convenzionalmente 1 e 0 (detti anche High e Low). Un multiplo del bit è il byte, che è composto da 8 bit. Rappresentare un'informazione (un numero) con diversi sistemi di notazione non è argomento di questa guida, ma è utile conoscerne i fondamenti, che possono essere approfonditi su Wikipedia.
Il cervello elettronico del calcolatore è il processore, il componente pensante che si occupa di eseguire le istruzioni. Un processore è in grado di eseguire solo qualche decina di istruzioni elementari in formato binario. Ecco un esempio di un programma che somma i numeri contenuti nelle celle di memoria 0x60 e 0x61 e salva il risultato in 0x62 (per ragioni di complessità, queste istruzioni sono per un Motorola 6809 e non per un comune processore x86):
10110110 00000000 01100000 10111011 00000000 01100001 10110111 00000000 01100010
Come si vede, scrivere un programma in formato binario è alquanto ostico: i primi programmatori, quindi, hanno ben pensato di non scrivere le istruzioni direttamente in formato binario, ma di utilizzare la base 16 o base esadecimale, in modo da poter rappresentare in maniera più leggibile i valori binari. Il programma precedente diventa così:
0xB6 0x00 0x60 0xBB 0x00 0x61 0xB7 0x00 0x62
Ciò tuttavia non è bastato: per semplificare maggiormente il compito, è stato inventato l'assembly, un primitivo linguaggio in cui, ad un insieme semanticamente correlato di istruzioni in linguaggio macchina, viene associata una sigla mnemonica che ricorda la sua funzione. Il precedente programma diventa:
LDA $0060 # carica il valore contenuto in 0x0060 nel registro A ADDA $0061 # aggiunge il valore contenuto in 0x0061 al registro A STA $0062 # salva il valore del registro A nella cella 0x0062
L'assembly, come il linguaggio macchina, è un linguaggio di basso livello, cioè che lavora direttamente a contatto con l'hardware, le parti intime del computer, e ne ricalca fedelmente il comportamento.
Poiché ogni processore possiede il suo set o insieme di istruzioni,
diverso da quello di ogni altro processore, per poter funzionare su computer
diversi, ogni programma dovrebbe essere scritto più volte.
Inoltre, utilizzando un linguaggio di basso livello, molte operazioni
apparentemente immediate, devono essere scomposte in molti sottoprogrammi
(un esempio banale è il ciclo for
, che consente di ripetere
un'operazione per un determinato numero di volte, e che in assembly richiede
un'attenta pianificazione delle condizioni e dei salti).
Per ovviare a questo problema, furono quindi inventati i linguaggi di alto livello, che permettono di astrarre l'hardware e le strutture logiche, e di creare programmi che, in seguito ad una traduzione in linguaggio macchina, possono essere utilizzati su molte diverse architetture.
È importante notare che un programma in linguaggio di alto livello, per essere eseguito dalla macchina, deve essere comunque tradotto in linguaggio macchina, cioè in quei famosi codici binari con 0 e 1 di cui parlavamo all'inizio: per svolgere questo compito, esistono i compilatori, programmi atti a questo scopo.
Si osservi quindi com'è facile sommare due numeri utilizzando un linguaggio ad alto livello, lasciando al compilatore il compito di scrivere il codice macchina, di trovare le celle di memoria per contenere i dati, eccetera...
c = a + b;
La serie di istruzioni di alto livello, facilmente leggibili da un essere umano, costituisce il codice sorgente del programma, mentre la sua versione binaria, in linguaggio macchina, costituisce l'eseguibile vero e proprio.
Compilazione
Abbiamo visto i numerosi vantaggi della programmazione di alto livello, e sappiamo che il programma, per poter essere eseguito dal computer, deve essere tradotto nel corrispondente linguaggio macchina.
- Talvolta, come avviene in Python o in PHP, questa traduzione avviene al volo: esiste infatti un interprete che legge le istruzioni, le traduce e le esegue immediatamente. Ad una nuova esecuzione del programma, questo viene nuovamente letto, tradotto ed eseguito. In questo caso, si parla di linguaggi interpretati. Altri esempi di linguaggi interpretati sono il Javascript o il vecchio BASIC.
- Spesso, invece, come avviene con C e C++, la traduzione in linguaggio macchina viene effettuata una sola volta da un programma terzo, detto compilatore. Quando si cambia qualcosa nel programma, però, esso deve essere compilato nuovamente. In questo caso, quindi, si parla di linguaggi compilati. Altri esempi di linguaggi compilati sono il Pascal e il Visual Basic.
Entrambi i tipi di linguaggi hanno vantaggi e svantaggi: i programmi scritti con linguaggi compilati sono più veloci dei loro cugini interpretati, ma devono essere compilati ogni volta che il codice sorgente viene cambiato. Così, mentre l'esecuzione dei primi è più performante, la modifica dei secondi è più immediata. Inoltre, mentre un programma interpretato può essere eseguito facilmente su qualunque tipo di computer o di sistema operativo, a patto che vi sia un interprete, quello compilato deve essere compilato per ogni piattaforma dove si desidera eseguirlo. Infine, mentre un programma interpretato può essere facilmente letto da chiunque lo desideri, utilizzando un linguaggio compilato non si è obbligati a fornire il codice sorgente del programma, rendendo così possibile la sua esecuzione, ma impedendone di fatto il miglioramento, la ricerca degli errori e il controllo.
La mia prima compilazione
Ogni compilatore e ogni ambiente di sviluppo (insieme di strumenti atti a semplificare la scrittura e la produzione di programmi) possiede una procedura per compilare i programmi nella maniera che, chi l'ha prodotto, ritiene sia più efficiente.
Per coloro che sono alle prime armi, consigliamo di installare l'ambiente
di sviluppo Code::Blocks,
disponibile su GNU/Linux, Microsoft Windows e Apple Mac OS X,
facendo attenzione ad installare anche il compilatore GNU (gcc o MinGW,
a seconda del sistema operativo).
Per chi invece fosse un po' più audace, consigliamo di utilizzare il metodo
tradizionale a riga di comando, che inizialmente può essere più ostico,
ma, dopotutto, non si può aspirare a programmare se ci si rifiuta di
aprire una riga di comando.
Chi utilizza Code::Blocks, si preoccuperà di far riferimento alla sua documentazione; chi invece vuole seguire la retta via, potrà trovare le prime indispensabili informazioni qui di seguito.
Programma di esempio, da copiare e incollare nel proprio editor di testo
preferito, ad esempio Geany, X Code, Notepad++ o l'editor di Code::Blocks.
Salvare questo file in un percorso noto, con estensione .c
,
ad esempio /home/glgprograms/sorgenti/hello.c
oppure
"C:\Documents and Settings\GLG Programs\sorgenti\hello.c"
.
#include <stdio.h> int main(int argc, char** argv) { printf("Hello World!\n"); return 0; }
Questo è il nostro codice sorgente. Apriamo un terminale e rechiamoci
nella directory che lo contiene (utilizzando cd
). Se non
si è pratici di questi passaggi, è praticamente obbligatorio prenderci
confidenza leggendo i fondamenti di una qualsiasi guida sul terminale
Linux/UNIX
(questa
non è troppo aggiornata, ma è validissima) o sul terminale di Windows.
Compiliamo:
gcc hello.c
Questo comando invoca il compilatore gcc (GNU Compiler Collection) che:
-
analizza il file
hello.c
, alla ricerca di eventuali errori di sintassi (per i quali la compilazione non avverrà) e potenziali situazioni di errore (per le quali saremo avvertiti); - compila il sorgente, ossia lo traduce in codice macchina;
- collega (linka) l'eseguibile alle librerie necessarie per il suo funzionamento;
-
produce un file
a.out
, che è il nostro eseguibile vero e proprio.
Avviamo il nostro eseguibile a.out
e verifichiamo che funzioni:
./a.out
Se tutto è andato bene, verrà stampato sul terminale
Hello world!
Complimenti: la procedura è andata a buon fine e il vostro primo programma è stato compilato ed eseguito. Questa è la procedura standard che seguiamo per compilare i programmi e gli esempi di questa guida. Chiunque è comunque liberissimo di utilizzare altre procedure, magari con gli strumenti integrati nel proprio ambiente di sviluppo, ma, se qualcosa non funziona, non venite a chiedere informazioni a noi.
Concetto di funzione
Il programma minimale
Poiché il modo migliore per capire la teoria è sperimentare nella pratica,
iniziamo con il più piccolo programma che può essere scritto, e analizziamolo
riga per riga, in modo da avere già da subito un quadro, seppur vago,
di ciò che stiamo per affrontare. Si tenga conto che gli argomenti che,
per ragioni di chiarezza, è necessario accennare in questo momento,
verranno tuttavia spiegati approfonditamente più avanti; se non si
comprende a fondo quanto scritto in questa prima parte, ciò non dovrebbe
rappresentare un problema.
Qualcuno potrebbe asserire che imparare a programmare iniziando con il
concetto di funzione è fuori luogo; dimostreremo che non è così.
int main(int argc, char** argv) { return 0; }
I computer possono eseguire un piccolo insieme limitato di operazioni, che, combinate nella giusta maniera, permettono di realizzare operazioni più complesse. È molto utile poter raccogliere un insieme di istruzioni già pronte sotto un unico nome, in modo da non doverle riscrivere tutte ogni volta che abbiamo bisogno di svolgere un determinato compito. Il paragone con la ricetta di cucina è perfetto: se in una ricetta troviamo scritto che dobbiamo sbattere le uova, sappiamo già che dobbiamo prendere una scodella, aprire le uova, metterle dentro, prendere una forchetta e agitarle finché l'albume e il tuorlo si mescolano e diventano di una consistenza più viscosa. Quindi, con sbattere le uova abbiamo riassunto in poche parole una serie di operazioni.
In programmazione, una funzione è una raccolta di operazioni.
Tutti i programmi devono avere almeno una funzione contenente
operazioni. Si tratta della funzione principale, o funzione main.
Ad ogni funzione corrisponde un blocco di istruzioni,
delimitato dalle parentesi graffe { e };
Il return 0;
finale indica che la nostra
funzione main termina e, in particolare, ritorna zero,
ma questo lo vedremo più avanti.
Per riassumere quanto detto finora, introduciamo dei commenti all'interno del nostro programma, in maniera da spiegare esattamente cosa fa. I commenti in C sono delimitati da i caratteri /* e */, e, poiché vengono completamente ignorati dal computer, è possibile scriverci ciò che preferiamo.
int main(int argc, char** argv) /* funzione principale del nostro programma */ { /* apro il blocco della funzione principale */ return 0; /* la funzione termina */ } /* chiudo il blocco della funzione principale */
Se provassimo a eseguire questo programma in un terminale, non vedremmo niente: infatti, l'unica cosa che fa, è uscire dal programma stesso.
Hello World!
Quasi sempre, alcune funzioni di base vengono fornite direttamente dal
sistema operativo o dall'ambiente di sviluppo in cui stiamo lavorando,
in raccolte di funzioni, altrimenti dette librerie.
Una di queste librerie è stdio.h
,
nome che sta per standard input/output,
che contiene le funzioni minime per interagire con l'utente, cioè
per interfacciarsi con lo schermo e con la tastiera.
Ampliamo il nostro programma:
#include <stdio.h> int main(int argc, char** argv) { printf("Hello world!"); return 0; }
Con #include <stdio.h>
,
dichiariamo alla macchina che abbiamo intenzione di utilizzare delle
funzioni che si trovano nella libreria stdio.h
.
Con printf("Hello world!");
chiamiamo
la funzione printf
(contenuta in stdio.h
),
che esegue le istruzioni necessarie
a scrivere sullo schermo ciò che noi le passiamo come argomento
tra parentesi semplici ( e ), cioè
Hello world!
Se eseguiamo questo programma in un terminale, verrà quindi mostrata
la scritta Hello world!
.
Variabili
Un'altra definizione di computer è una macchina che prende dei dati in ingresso, li elabora, e successivamente restituisce dei risultati. Le informazioni necessarie a calcolare il risultato durante l'esecuzione del programma, sono memorizzate in variabili. Ogni variabile ha un nome o identificatore, che la identifica nel programma, e un tipo, ovvero il tipo di valori che può assumere.
Il nome di una variabile può contenere lettere e underscore,
ma non può contenere spazi, né numeri come primo carattere, né lettere accentate.
Esistono alcuni tipi fondamentali di variabili:
ad esempio, una variabile che contiene la mia età in anni può assumere valori
naturali, come 0, 1, 2, ecc..., mentre una variabile che contiene la mia
temperatura corporea può assumere valori reali, come 36.0, 36.5, 37.0 ecc...
Per usare una variabile è necessario definirla,
o, in maniera più comune ma meno precisa, dichiararla: questa operazione
serve per poter riservare nella memoria uno spazio adeguato a contenere
il dato che intendiamo memorizzare.
Vediamo alcuni esempi di definizione di variabili:
int numero2; /* numero intero, come 0, 2, -7 o 128923 */ float temperatura = 36.7; /* numero reale (in realtà approssimato a razionale) */ double piGreca = 3.1415269; /* numero reale con approssimazione minore */ bool opzione_attiva = true; /* valore booleano (due stati, cioè vero oppure falso) */ char lettera = 'a'; /* un carattere qualsiasi */
Si noti che, in alcuni casi, è stata anche effettuata un'operazione di assegnamento, cioè è stato assegnato un valore iniziale alle variabili. Prima di leggere un dato da una variabile (vedremo tra poco come fare) è buona norma inizializzarla con un valore noto: infatti, una variabile appena definita contiene il valore precedentemente assegnato alle celle di memoria in cui si trova, valore che è praticamente impossibile prevedere a priori.
Ogni variabile occupa uno spazio ben preciso, e di conseguenza potrà assumere
un intervallo di valori prefissato.
Il tipo fondamentale intero può essere modificato attraverso dei qualificatori,
in maniera che il programmatore possa controllare qualitativamente quanta
memoria occuperà il dato, scegliendo in base allo scopo a cui è destinata
la variabile.
short numero1; /* numero intero piccolo, come -5, 0 o 131 */ unsigned anni; /* numero naturale positivo, come 0, 2, 41 ma non -7 */ long distanza; /* numero intero anche molto grande, come -17179869143 o 17179869209 */
Qui di seguito è riportata una tabella di riferimento,
con lo spazio occupato in memoria e gli estremi degli intervalli
rappresentabili su un comune processore a 32-bit.
Si noti che questa
tabella è solo indicativa, in quanto i parametri dipendono da molti fattori,
come l'architettura del processore o il sistema operativo.
Mentre la memoria abbonda sui comuni computer e permette di trascurare inizialmente
questo aspetto, sui microcontrollori bisogna essere parsimoniosi
nell'uso di variabili che occupano molta memoria, in quanto questa è
estremamente ridotta.
tipo | memoria occupata byte (bit) | intervallo di rappresentazione |
int | 4 (32) | [-2147483648; +2147483647] |
short | 2 (16) | [-32768; +32767] |
unsigned | 4 (32) | [0; +4294967296] |
float | 4 (32) | //calcolare// |
double | 8 (64) | //calcolare// |
bool | 1 (8) | { true, false } |
char | 1 (8) | [-128; +127] |
In C esiste un operatore (vedremo gli operatori nel dettaglio successivamente), chiamato sizeof, che consente di conoscere lo spazio che occupa una variabile in memoria in byte. Nel codice, si presenta esteticamente come una comune funzione.
int intero; /* si osservi che sizeof accetta sia nomi di variabili che tipi */ sizeof(intero); /* 4 */ sizeof(long); /* 4 sui processori a 32-bit, 8 sui processori a 64-bit */ sizeof(short); /* 2 */
La codifica ASCII e il char
Giunti a questo punto, è opportuno chiarire la natura del tipo char. In un computer, tutta l'informazione non è altro che una serie di numeri: perciò, per rappresentare le lettere e i simboli utilizzati comunemente nella lingua scritta, è stato necessario stabilire una convenzione per associare ogni carattere rappresentabile ad un numero.
Questa convezione di codifica è chiamata codifica ASCII, che sta per American Standard Code for Information Interchange. Essa è una codifica a 7 bit, cioè può codificare un massimo di 27 = 128 differenti caratteri. Si noti che questa codifica viene utilizzata solamente per lo scambio di dati con altre periferiche (tastiere, stampanti...) e non è utilizzata per l'elaborazione diretta dell'informazione (ad esempio, per eseguire operazioni matematiche sui numeri).
Lavorare con la tabella tabella ASCII può sembrare estremamente dispendioso, ma tutto diventa più facile dopo aver fatto alcune considerazioni:
- Le cifre numeriche e l'alfabeto latino si trovano ordinati secondo l'ordine convenzionale.
- Le corrispondenze possono essere divise in quattro macroblocchi da 32 caratteri ciascuno:
- Da 0 a 32 si hanno i caratteri di controllo non stampabili (come a capo, cancella, tabulazione, ecc...)
- Da 32 a 64 ci sono i simboli stampabili più comuni, a partire dallo spazio e compresi i numeri (che iniziano a 48)
- A 65 iniziano le lettere maiuscole; le lettere minuscole iniziano a 97, cioè esattamente al macroblocco successivo, 32 posizioni più avanti. Tra i valori rimanenti, sono inseriti altri simboli di uso comune, come i vari tipi di parentesi
Poiché negli elaboratori non è possibile rappresentare agevolmente valori più piccoli di 1 byte (ossia 8 bit), un char ha dimensione 1 byte, e può quindi rappresentare un totale di 256 caratteri. I 128 caratteri aggiuntivi rappresentano un insieme ASCII esteso, con lettere accentate e altri simboli, e che dipende dal locale della singola macchina (esistono code pages estese americane, italiane, nordiche, ecc...). Oggi non si deve più fare riferimento a tale standard, bensì all'UNICODE (o un suo sottoinsieme, come UTF-8), che consente di utilizzare un formato di codifica unico e comune in tutto il mondo, comprendente fino a 65536 differenti caratteri. I primi 128 sono gli stessi della codifica ASCII.
Input e Output
Giunti a questo punto, ci si domanderà come si può stampare sullo schermo il dato contenuto in una variabile, oppure come leggerlo da tastiera per poterlo elaborare. Cerchiamo di dare una risposta esauriente.
In un computer, i dati possono essere scritti su o letti da un flusso: un flusso è un'astrazione che può rappresentare lo schermo, la tastiera, un file, ma anche una stampante o un modem. I flussi possono essere di sola uscita (output), cioè i dati possono fluire solo dal nostro programma verso il flusso, di sola entrata (input), dove i dati possono solo essere letti per poterli inserire nel nostro programma, e, infine, di entrata e uscita (input/output), dove la comunicazione può avvenire in entrambi i sensi.
La maggior parte dei buoni sistemi operativi fornisce tre flussi distinti:
- stdin: cioè standard input, ossia il flusso di ingresso che contiene i dati che vengono digitati sulla tastiera;
- stdout: cioè standard output, ossia il flusso di uscita che viene mostrato sullo schermo;
- stderr: cioè standard error, ossia il flusso di uscita che viene sempre mostrato sullo schermo, ma sul quale devono essere scritti gli errori, in modo da poterli separare dal flusso normale quando necessario.
Si tenga presente che alcuni sistemi inoperativi basati esclusivamente su interfaccia grafica, non consentono di accedere direttamente ai flussi, e anzi, talvolta non viene nemmeno fornito lo standard error.
Output
Per scrivere dati su stdout e stderr, cioè sullo schermo,
si utilizza la funzione printf()
. È possibile passare
a printf
più argomenti, indicando quali dati si vogliono
stampare e le variabili che li contengono. Ad esempio, per stampare un intero:
int a = 7; printf("La variabile 'a' vale %d", a);
Analizziamo i due argomenti passati alla funzione:
"La variabile 'a' vale %d"
è una stringa, cioè un insieme di char o caratteri di qualsiasi tipo (numeri, lettere, simboli), delimitati dalle virgolette doppie ". Vedremo le stringhe successivamente nel dettaglio.
I caratteri di questa stringa vengono interpretati daprintf
in maniera speciale: la funzione infatti si aspetta una variabile come argomento aggiuntivo per ogni segno di percentuale % della stringa. Tale variabile deve essere convertibile nel tipo printf specificato a destra di %, il quale verrà sostituito dal suo valore.a
è la variabile che desideriamo stampare.
Pertanto, per chiarire il lungo discorso teorico per il quale un esempio vale più di mille parole, mostriamo l'output del suddetto codice, che sarà:
La variabile 'a' vale 7
Allo stesso modo, è possibile stampare più variabili, anche di diverso tipo,
purché queste vengano passate a printf
in un ordine corrispondente
a quello in cui si presentano i %.
int settimana = 7; float pi = 3.1415; char lettera = 'A'; printf("Una settimana ha %d giorni, Pi Greca vale %f e la prima lettera dell'alfabeto è %c.", settimana, pi, lettera);
Il cui output sarà:
Una settimana ha 7 giorni, Pi Greca vale 3.1415 e la prima lettera dell'alfabeto è A.
Qui che segue una tabella riassuntiva dei tipi printf più comuni:
Escape | Tipo |
%d | Numero intero decimale |
%f | Numero in virgola mobile a precisione semplice (float) |
%lf | Numero in virgola mobile a doppia precisione (double) |
%c | Carattere char |
%h | Numero intero in notazione esadecimale |
%o | Numero intero in notazione ottale |
%u | Numero intero senza segno |
%s | Stringa |
È molto importante notare che a printf
può essere passata
una variabile di qualsiasi tipo, purché questa possa essere convertita
nel corrispondente tipo printf prima di essere stampata.
double pi = 3.1415; char lettera = 'A'; printf("%d %h", pi, lettera); /* stampa 3 40 */
All'interno della stringa per printf
, è inoltre possibile
inserire altre sequenze di escape, cioè sequenze di caratteri
annunciate da un carattere speciale, che non vengono stampate letteralmente
ma modificano il comportamento della funzione.
Attraverso il carattere di escape \ (backslash, o barra rovesciata),
è possibile stampare alcuni caratteri di controllo; ad esempio, per
andare a capo, si usa \n.
printf("Questo testo si trova sulla prima riga\nmentre questo sulla seconda");
Questo testo si trova sulla prima riga mentre questo sulla seconda
Tra i caratteri di escape più comunemente usati, possiamo trovare i seguenti:
Escape | Effetto |
\n | va a capo |
\t | salta alla tabulazione successiva |
\a | suona lo speaker (su molti portatili e alcuni fissi non è più in dotazione) |
\b | cancella l'ultimo carattere |
Input
Un programma il cui unico scopo è stampare non serve a molto. I programmi
sono fatti per prendere dei dati in ingresso, risolvere un problema attraverso
un algoritmo noto, e poi restituirne il risultato.
È possibile leggere i dati dalla tastiera e immetterli in delle
variabili per poterli elaborare tramite la funzione scanf()
.
scanf("format", address list...);
La funzione scanf()
si comporta in maniera simile a printf()
:
- Il suo primo argomento è una stringa di sequenze di escape, che indicano il tipo di dato da leggere, esattamente nella stessa maniera di printf();
- i suoi argomenti successivi sono gli indirizzi di memoria delle variabili in cui salvare i dati letti, dati nell'ordine specificato dalle sequenze di controllo.
/* Programma che legge un intero da tastiera e lo restituisce a video */ #include <stdio.h> int main(int argc, char** argv) { int a; printf("Inserisci numero intero: "); scanf("%d", &a); /* Lettura di un intero decimale nella variabile 'a'*/ printf("Numero inserito: %d\n", a); return 0; }
Questo semplice programma, alla linea 8 legge un numero intero decimale e lo salva nell'indirizzo di memoria della variabile a. Per specificare l'indirizzo di memoria di una variabile, è necessario preporre l'operatore & all'identificatore della variabile. Prima di proseguire, può essere utile compilare questo programma, verificarne il funzionamento e, una volta capito, estenderlo. Qui di seguito, è esposta una piccola modifica che è possibile fare.
/* Calcolatrice per somme e differenze */ #include <stdio.h> int main(int argc, char** argv) { double a, b, c; printf("Inserisci due numeri: "); scanf("%lf%lf", &a, &b); c = a + b; printf("A + B = %lf\n", c); return 0; }
Si noti che alla linea 8 è stata utilizzata un'espressione banale per realizzare la somma di due numeri. Se si ha un poco di dimestichezza con la matematica, è facile capire le espressioni più semplici; tuttavia, analizzeremo dettagliatamente il loro comportamento nella programmazione nel prossimo capitolo.
Espressioni e introduzione agli operatori
Un operatore è una funzione che viene eseguita su uno o più operandi e restituisce un risultato. Una serie di operatori e operandi dà luogo a un'espressione. Un esempio vale più di mille parole:
a + b
Questa è una espressione. Essa contiene un operatore (+) e i suoi due operandi
(a b). Quello che fa è evidente: l'operatore + somma i suoi due operandi
a
e b
, e restituisce il risultato. Si noti che
tale risultato, per poter essere conosciuto, dovrebbe essere salvato
in qualche variabile con un operatore di assegnamento, ad esempio:
c = a + b;
In C (e C++) esistono molti operatori, con delle proprietà specifiche:
- Arietà: cioè il numero di operandi su cui lavorano (da uno a tre);
- Precedenza: cioè, in caso di espressioni formate da più operatori, quali di questi devono essere valutati prima; la precedenza può essere modificata utilizzando delle parentesi tonde ( e )
- Associatività: cioè l'ordine in cui si valuta un'espressione costituita da operatori con la stessa precedenza;
Per esempio, gli operatori di moltiplicazione e divisione hanno arietà 2, hanno precedenza su somma e differenza e, se compaiono in fila in un'espressione, vengono valutati da sinistra a destra.
/* Operatori aritmetici -- si utilizzano come i corrispondenti simboli matematici */ + somma - differenza * moltiplicazione / divisione % modulo o resto /* Operatori di valutazione -- valutano le relazioni tra i loro operandi */ < minore <= minore o uguale > maggiore >= maggiore o uguale == uguale != diverso /* Operatore di assegnamento */ = /* Operatori Logici -- corrispondono alle operazioni booleane */ || OR o somma logica && AND o prodotto logico ! NOT o negazione /* Comodi operatori unari */ ++ incremento di 1 -- decremento di 1 & indirizzo in memoria di... (Attenzione: vedremo che può avere anche un altro uso) sizeof dimensione di...
Esistono molti altri operatori che, per le loro caratteristiche, meritano dei capitoli a parte. Nel frattempo, quelli esposti, sono più che sufficienti per iniziare davvero a programmare. Seguono alcuni esempi di espressioni e di valutazioni.
23 + 2 /* vale 25 */ a = 4 /* assegna ad a il valore 4 e restituisce l'indirizzo di a */ a == 4 /* valuta se a è uguale a 4, perciò restituisce vero (non zero) */ a < 2 /* restituisce falso (zero) */ ++a /* incrementa a di 1 e ritorna 5 */ 5 + 2 * 3 /* vale 11 */ (5 + 2) * 3 /* vale 21 */
Operatori logici
Meritano un paragrafo a sé le condizioni, che sono delle espressioni a tutti gli effetti, con l'unica differenza che possono avere solo due risultati: vero oppure falso. In ambito di programmazione si usano per connettere insieme più valutazioni, costruendo espressioni del tipo "a è maggiore di 3 ma minore di 7?".
Se applichiamo gli operatori logici a espressioni che ritornano valori non booleani viene implicitamente effettuata una conversione, dove per convenzione è attribuito falso a 0, mentre vero a un qualunque altro numero diverso da 0.
#include <stdio.h> int main() { int a = 5, b = 3; printf ("%i\n", (a > 8) && (a < 6)); /* Stampa 0 (falso) */ printf ("%i\n", (a > 3) && (a < 7)); /* Stampa 1 (vero) */ printf ("%i\n", (a > 7) || (b < 5)); /* Stampa 1 (vero) */ printf ("%i\n", (a > 7) || (b < 2)); /* Stampa 0 (falso) */ printf ("%i\n", a && (!b)); /* Stampa 0 (falso) */ /* In questo caso a e b sono convertiti entrambi a vero, e b viene negato, diventando falso */ }