Biagio Ianero
Il C a 360°
Breve manuale per la programmazione in C
Matematicamente.it
1
Biagio Ianero – Il C a 360°
Biagio Ianero
Il C a 360°
© 2013 Biagio Ianero
Questo materiale è rilasciato sotto licenza CC BY-SA
Per le parti dell’opera di pubblico dominio, tale condizione non è in alcun modo modificata dalla licenza.
Alcune tabelle provengono da Wikipedia
Foto di copertina: The C on the Clicff by Barry Solow
http://www.flickr.com/photos/baslow/5620208 2
Indice
1. Introduzione
2. Il linguaggio C
- Librerie
- File di codice
- Variabili e tipi di dati
- Operatori aritmetici
- Operatori di confronto
- Operatori logici
- Operatori bit a bit
- Funzioni
- Scope
- Commenti
3. Hello world!
4. Puntatori e array
- Puntatori
- Array
- Array multidimensionali
5. Stringhe
- Caratteri
- Stringhe
6. Ancora funzioni
- Parametri delle funzioni
- I parametri di main()
- printf()
- scanf()
- L’input diventa output
- exit()
- rand() e srand()
- La ricorsione
- Puntatori a funzione
7. Cicli e condizioni
- If, else if, else
- Switch
- While
- Do While
- For
- Goto
8. Preprocessore
- Include
- Define
- If, elif, else, endif
- ifdef, ifundef, endif
- Macro
- Pragma
9. Gestione della memoria
- sizeof()
- type-casting
- malloc()
- calloc()
- realloc()
- free()
Biagio Ianero – Il C a 360°
10. Nuovi tipi di dati
- Struct
- Union
- Enum
- Typedef
- Campi di bit
11. Lavorare con i file
- FILE
- fopen()
- fclose()
- File di testo
- Esempio conclusivo
12. Standard C Library
13. Un giochino funzionante
14. Ringraziamenti 4
Introduzione
Ciao caro lettore, mi presento, mi chiamo Biagio Ianero e ti ringrazio di aver scelto questo libro per imparare il linguaggio C.
In questa guida verranno trattati tutti gli aspetti del linguaggio per una comprensione a 360°.
Ci saranno anche degli esempi pratici per capire meglio ciò che viene spiegato.
Elenco qui di seguito alcune convenzioni che userò nella scrittura:
Il codice sarà scritto con questo tipo di Font;
–
– Il codice delle applicazioni di esempio funzionanti verrà scritto “a colori” per una più facile
–
Per qualsiasi problema riguardante la comprensione della teoria, del codice od opinioni sul testo in generale contattami all’indirizzo:
biagio.ianero@alice.it
Ti auguro una buona lettura ed una facile comprensione...
5
Biagio Ianero – Il C a 360°
Il linguaggio C
Il linguaggio C, diretto successore dell’assembly, è un linguaggio “di basso livello” perché permette di svolgere operazioni di I/O molto basilari e con estrema semplicità, non a caso è molto usato per la scrittura di driver e kernel di sistemi operativi. È un linguaggio di tipo procedurale, il che significa che usa una logica “a blocchi”, detti comunemente funzioni. Questi blocchi di codice hanno un nome e sono delimitati da appositi simboli che spiegheremo meglio nei capitoli successivi.
Il C è un linguaggio di tipo Case-Sensitive, quindi “ciao” sarà diverso da “CIAO” e diverso da “cIaO”.
Inoltre è un linguaggio Cross-Platform, cioè significa che può essere usato per programmare su qualsiasi
S.O.
Cercherò di essere il più generale possibile senza fare esempi riguardanti una piattaforma specifica.
Per quanto mi riguarda lo preferisco a qualunque altro linguaggio di programmazione e spero sia lo stesso per voi.
Iniziamo con alcuni concetti di base prima di addentrarci nella scrittura di codice vera e propria.
Librerie
Facciamo un esempio: vogliamo creare una macchina, ma da dove iniziamo?
Chi ci fornisce gli attrezzi? Dove prendiamo il motore? E le gomme?
Dovremmo fare tutto noi da zero, ma è per questo motivo che ci vengono in soccorso le librerie.
Nel nostro esempio la macchina è l’applicazione che vogliamo scrivere, ma per scriverla abbiamo bisogno di funzioni basilari scritte già da altri programmatori da “includere” nel nostro codice, altrimenti se dovessimo riscrivere ogni volta tutto da zero, il lavoro dell’app vero e proprio rappresenterebbe lo 0,00000001%.
File di codice e compilazione
Dove scriviamo il nostro codice C? La risposta è in un editor di testo più rudimentale possibile!
Questo codice sarà salvato con estensione .c al posto del classico .txt ed infine compilato.
La compilazione è un processo tramite il quale dal file.c ricaviamo il file eseguibile: la nostra applicazione vera e propria.
Ci sono varie fasi per portare a termine questo processo che sono del tutto trasparenti al programmatore grazie ai moderni compilatori che si trovano tranquillamente in rete.
La nostra applicazione sarà composta da più file.c, ognuno con un compito specifico, che saranno poi uniti (sempre in fase di compilazione...) per formare un solo file eseguibile.
Un file.c può utilizzare una funzione scritta in un altro file.c, semplicemente importandolo.
Viene quindi da pensare ad una situazione di questo genere:
6
C’è però un problema: i file.c sono i file in cui viene scritto il codice vero e proprio, dove viene fatto il cosiddetto “lavoro sporco”, sarebbe quindi molto difficile trovare una determinata funzione in un file pieno di istruzioni di quel tipo.
Per rendere la cosa molto più semplice e pulita il C usa un altro tipo di file, chiamato file di header avente estensione .h.
Quindi se esiste un file chiamato A.c, esisterà anche un file che avrà nome A.h, nel quale saranno solo dichiarate tutte le funzioni implementate in A.c.
Tornando all’esempio della macchina, in un file chiamato Movimento.h troveremo le dichiarazioni delle funzioni “sterza”, “accelera” e “frena”, mentre nel file Movimento.c troveremo tutte le istruzioni necessarie per l’implementazione della sterzata, dell’accelerazione e della frenata della nostra automobile.
L’accoppiata file.c/file.h forma un modulo di codice sorgente.
Come vediamo è il file header ad essere importato, il file grazie al quale riceviamo tutte le informazioni sul file.c dello stesso modulo.
Vedremo più avanti la procedura corretta per l’importazione dei file header.
Variabili e tipi di dati
Durante la scrittura del nostro codice avremo bisogno di trattare con tipi di dati diversi in base alle nostre esigenze (numeri interi, decimali, ecc...).
Come sappiamo un bit può essere rappresentato da “0” o da “1” e mettendo insieme più bit si ottengono rappresentazioni di numeri più grandi di 0 e di 1.
Più bit mettiamo insieme, più grande è il limite numerico che riusciamo a rappresentare.
Dove si raggruppano questi bit? È qui che introduciamo il concetto di variabile.
In C qualsiasi tipo di dato è una variabile.
Essa non è nient’altro che un “agglomerato” di bit che ci rappresenta un determinato valore.
I tipi di dati primitivi che il C ci mette a disposizione sono i seguenti:
- Variabile di tipo “int”, rappresenta un numero intero e, a seconda della macchina sulla quale si compila il codice, può contenere 16 o 32 bit;
- Variabile di tipo “float”, rappresenta un numero decimale ed è formata da 32 bit;
- Variabile di tipo “double”, rappresenta un numero decimale ed è formata da 64 bit;
- Variabile di tipo “char”, rappresenta un carattere e contiene 8 bit;
Capiamo quindi che una variabile può rappresentare al suo interno un totale di numeri pari a 2 (numeri rappresentabili in un bit) elevato al numero di bit che la formano.
La variabile che contiene meno bit è il char poiché è formata da un solo byte (1 byte = 8 bit).
7
Biagio Ianero – Il C a 360°
I più attenti avranno già capito che il char, oltre che per rappresentare caratteri letterali, può essere anche utilizzato come “piccolo int”; esso infatti potenzialmente può rappresentare 28 numeri, decisamente inferiore alla variabile “int”, ma comunque utilizzabile.
Ovviamente queste variabili comprendono anche numeri negativi, ad esempio l’int va da -2147483648 a +2147483648.
Se volessimo solo rappresentare numeri dopo lo zero, quindi raddoppiare il limite numerico positivo ed azzerare il limite numerico negativo possiamo inserire la parola chiave “unsigned” prima di “int”.
A questo punto la variabile “unsigned int” potrà contenere numeri da 0 a 4294967296 (cioè 2147483648 * 2).
Ci sono inoltre parole chiave che possono “allungare” o “accorciare” il campo di bit contenuto nella variabile: esse sono “long” e “short”.
Troviamo infine altre due parole chiave che sono “void” e “NULL”: la prima sta per “non-tipo” di dato, la seconda per “nessun riferimento in memoria”.
Non si possono creare variabili di tipo void, ma questo tipo di dato viene usato per creare puntatori generici e per non ritornare nessun valore da una funzione (esamineremo nello specifico questi due aspetti più avanti). I nomi delle variabili non possono contenere spazi!
Operatori aritmetici
Il C mette a disposizione gli operatori aritmetici basilari per compiere operazioni matematiche.
Essi sono:
- “+”, simbolo che ci permette di eseguire l’addizione tra l’elemento a destra del simbolo e quello alla sua sinistra;
- “-”, simbolo che ci permette di eseguire la sottrazione tra l’elemento a destra del simbolo e quello alla sua sinistra;
- “*”, simbolo che ci permette di eseguire la moltiplicazione tra l’elemento a destra del simbolo e quello alla sua sinistra;
- “/”, simbolo che ci permette di eseguire la divisione tra l’elemento a destra del simbolo e quello alla sua sinistra;
- “%”, simbolo che ci permette di ottenere il resto di una divisione intera tra l’elemento a destra del simbolo e quello alla sua sinistra;
Il simbolo “%” non esegue la percentuale!!!
Abbiamo poi anche altri operatori che io definisco “di comodo” perché servono semplicemente ad incrementare (aumentare di 1) o decrementare (diminuire di 1) il valore di una variabile.
Ad esempio data la variabile “var” la scrittura:
var++ ++var
oppure
incrementano di 1 la variabile var, mentre:
var-- --var
oppure
decrementano di 1 la variabile var. 8
Cosa cambia se ++ o -- si trovano prima o dopo del nome della variabile?
Cerchiamo di capirlo con un esempio:
Poniamo per semplicità che “var” valga 10.
Allora...
5 * var++
corrisponde a (5 * var) + 1 e il risultato sarà quindi 51.
Mentre...
5 * ++var
corrisponde a 5 * (var + 1) e il risultato sarà quindi 55.
Operatori di confronto
Gli operatori di confronto sono un’altra categoria di operatori che possiamo usare per mettere a confronto (chi l’avrebbe mai detto eh?) due elementi.
Semplicemente sono:
- “==”, operatore di uguaglianza (diverso da “=” !!!) che ritorna 1 (VERO) se le due variabili messe a confronto sono uguali, altrimenti ritorna il valore 0 (FALSO);
- “!=”, operatore di disuguaglianza che ritorna 1 (VERO) se le due variabili messe a confronto NON sono uguali, altrimenti ritorna il valore 0 (FALSO);
- “>”, ritorna 1 (VERO) se la variabile a sinistra è maggiore di quella di destra, altrimenti ritorna il valore 0 (FALSO);
- “<”, ritorna 1 (VERO) se la variabile a sinistra è minore di quella di destra, altrimenti ritorna il valore 0 (FALSO);
- “>=”, ritorna 1 (VERO) se la variabile a sinistra è maggiore o uguale a quella di destra, altrimenti ritorna il valore 0 (FALSO);
- “<=”, ritorna 1 (VERO) se la variabile a sinistra è minore o uguale a quella di destra, altrimenti ritorna il valore 0 (FALSO);
“=” non è la stessa cosa di “==”: il primo assegna il valore dell’elemento di destra all’elemento a sinistra, il secondo invece effettua solo un confronto ma non modifica il valore degli elementi.
Operatori logici
Gli operatori logici più utilizzati in C sono “AND”, “OR” e “NOT”:
- “&&”, è l’AND e restituisce 1 (VERO) solo se entrambe le espressioni messe a confronto sono vere, altrimenti 0 (FALSO);
- “||”, è l’OR e restituisce 1 (VERO) se almeno una delle espressioni messe a confronto è vera, altrimenti 0 (FALSO);
- “!”, è il NOT ed inverte il valore di ritorno di un’espressione: se essa ritorna 1 (VERO), il risultato sarà 0 (FALSO) e viceversa. 9
Biagio Ianero – Il C a 360°
Possiamo quindi riassumere in questo modo...
A B A AND B
0 0 0
0 1 0
1 0 0
1 1 1
A B A OR B
0 0 0
0 1 1
1 0 1
1 1 1
A NOT A
0 1
1 0
Operatori bit a bit
Altro tipo di operatori sono quelli che lavorano sui singoli bit.
Essi non sono molto usati nella programmazione in “User-Space”, ma comunque vengono elencati per completezza.
Essi sono:
- “&”, and su bit, il risultato è 1 solo se entrambi i bit sono 1;
- “|”, or su bit, il risultato è 1 se almeno un bit vale 1;
- “^”, xor su bit, il risultato è 1 se solo uno dei due bit vale 1;
- “~”, not su bit, inverte ogni singolo bit;
- “>>”, shift a destra di bit, sposta a destra i bit di un dato elemento di “n” posizioni senza riporto;
- “<<”, shift a sinistra di bit, sposta a sinistra i bit di un dato elemento di “n” posizioni senza riporto.
Per capire meglio la cosa facciamo un piccolo esempio...
Prediamo il numero 7 e il numero 9, che in binario sono rispettivamente “0111” e “1001”.
Usiamo tutti gli operatori bit a bit su questi due numeri secondo le regole elencate sopra.
0111 & 0111 | 0111 ^ ~ 0111 >> 0111 << 0111
1001 = 1001 = 1001 = ______ ______ ______
______ ______ ______
0001 1111 1110 1000 0011 1110
10
Come potrete notare gli ultimi tre operatori operano su un solo valore e non su una coppia di valori, come invece fanno i primi tre.
Funzioni
Dove raggruppiamo tutte le istruzioni per il corretto funzionamento della nostra applicazione?
Sarebbe semplicemente folle scrivere tutte le istruzioni una dietro l’altra: non ci orienteremmo più tra tutto il codice nel giro di venti minuti.
Ecco perché il C si scrive suddividendolo in funzioni, dette anche sotto-programmi o sotto-routine.
Una funzione è semplicemente un “blocco” di codice delimitato da due parentesi graffe (all’inizio una aperta e alla fine una chiusa) e contrassegnato da un nome che lo identifica.
Ecco un esempio di funzione:
void funzione ()
{
}
Il primo elemento della funzione è il valore che ritorna a chi la chiama, o meglio invoca, in questo caso “void”, quindi la funzione non ritorna nessun valore.
Queste funzioni posso avere anche dei parametri che vanno racchiusi tra le due parentesi tonde dopo il nome della funzione...
void funzione (int numero)
{
}
In questo nuovo esempio ho aggiunto un parametro alla funzione: una variabile di tipo int.
A questo punto vi sarete fatti questa domanda: Dove e come invoco una funzione?
Abbiamo detto che una funzione è un blocco di codice che svolge determinate operazioni, quindi la risposta al dove è: dipende dalla logica che usate nella costruzione della vostra applicazione! (ovviamente tra poco faremo degli esempi pratici...).
La risposta al come è molto semplice: la funzione si invoca scrivendo il suo nome seguito dalle due parentesi tonde.
Un esempio:
funzione();
In questo esempio non stiamo passando nessun parametro alla funzione perché non c’è niente all’interno delle parentesi tonde.
Ecco un esempio in cui viene passato un intero alla funzione:
funzione( 4 ); 11
Biagio Ianero – Il C a 360°
Non so se avete notato il “punto e virgola”...
In C qualsiasi istruzione deve terminare con il “;”, è una regola di sintassi.
L’invocazione di una funzione è un’istruzione? Sì, quindi deve terminare con un punto e virgola, così come tutte le altre istruzioni (dichiarazioni di variabili, assegnazioni, ecc...).
È molto importante distinguere questi tre momenti:
- Dichiarazione di una funzione, è il momento in cui viene semplicemente detto al compilatore “attento che nel mio codice userò una funzione che si chiama <nome_della_funzione> più avanti! Quando la incontrerai non impressionarti!”
- Invocazione di una funzione, è il momento in cui la funzione viene chiamata, cioè viene detto al compilatore “esegui tutte le istruzioni della funzione <nome_della_funzione>!”;
- Implementazione di una funzione, è il momento in cui la funzione viene “scritta”, quando apriamo e chiudiamo le parentesi graffe e scriviamo il vero e proprio lavoro sporco all’interno di esse!
Facciamo un esempio per ogni situazione...
Dichiarazione
void funzione (void);
Invocazione
void funzione();
Implementazione
void funzione (void)
{ ......tutte le istruzioni......
}
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Il linguaggio C
Il linguaggio C, diretto successore dell'assembly, è un linguaggio "di basso livello" perché permette di svolgere operazioni di I/O molto basilari e con estrema semplicità, non a caso è molto usato per la scrittura di driver e kernel di sistemi operativi. È un linguaggio di tipo procedurale, il che significa che usa una logica "a blocchi", detti comunemente funzioni. Questi blocchi di codice hanno un nome e sono delimitati da appositi simboli.
Il C è un linguaggio di tipo Case-Sensitive, quindi "ciao" sarà diverso da "CIAO" e diverso da "cIaO". Inoltre è un linguaggio Cross-Platform, cioè significa che può essere usato per programmare su qualsiasi S.O. Cercherò di essere il più generale possibile senza fare esempi riguardanti una piattaforma specifica. Per quanto mi riguarda lo preferisco a qualunque altro linguaggio di programmazione e spero sia lo stesso per voi. Iniziamo con alcuni concetti di base prima di addentrarci nella scrittura di codice vera e propria.
B. Ianero, Il C a 360°
Breve manuale di programmazione in C, pp. 65, pdf (0,9MB)