1 Il linguaggio C 1/243 Il linguaggio C Corso di Programmazione di Sistema a.a. 2005/06 Versione 0.8 – preparata dall’ing. E. Mancini sulla base dei lucidi del prof. Ghini, Univ. Di Bologna Il linguaggio C 2/243 Riferimenti • Brian W. Kernighan, Dennis M. Ritchie, "Linguaggio C", ed. Pearson Education Italia • Webpage del corso: web.ing.unisannio.it/villano/ps0506
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
1
Il linguaggio C 1/243
Il linguaggio C
Corso di Programmazione di Sistema
a.a. 2005/06
Versione 0.8 – preparata dall’ing. E. Mancini sulla base dei lucidi del prof. Ghini, Univ. Di Bologna
Il linguaggio C 2/243
Riferimenti
• Brian W. Kernighan, Dennis M. Ritchie, "Linguaggio C",
ed. Pearson Education Italia
• Webpage del corso: web.ing.unisannio.it/villano/ps0506
2
Il linguaggio C 3/243
Caratteristiche del linguaggio C
• Utilizzo frequente di chiamate a funzioni.
• Debole controllo sui tipi di dato: a differenza del Pascal, il C permette di operare con assegnamenti e confronti su dati di tipo diverso, in qualche caso solo mediante un type cast(conversione di tipo) esplicito.
• Linguaggio strutturato. Il C prevede costrutti per il controllo di flusso, quali raggruppamenti di istruzioni, blocchi decisionali (if-else), selezione di alternative (switch), cicli con condizione di terminazione posta all'inizio (while, for) o posta alla fine (do) e uscita anticipata dal ciclo (break).
Il linguaggio C 4/243
Caratteristiche del linguaggio C
• programmazione a basso livello facilmente disponibile
• implementazione dei puntatori: l’uso di puntatori per memoria, vettori, strutture e funzioni rende facile commettere errori.
• portabilità sulla maggior parte delle architetture.
• disponibilità di librerie standard.
3
Il linguaggio C 5/243
Il primo programma in linguaggio C
Un programma minimo in C e':
main()
{
}
che corrisponde a un programma in Java:
class PrimoProg {
public static void main(String[] args) {
}
}
Il linguaggio C 6/243
Caratteristiche del linguaggio C
• Lo standard per i programmi C in origine era basato sulla
prima edizione del testo di Kernighan e Ritchie (K&R).
• Al fine di rendere il linguaggio più accettabile a livello
internazionale, venne messo a punto uno standard
internazionale chiamato ANSI C (American National
Standards Institute). Questo standard è quello descritto nella
seconda edizione del libro (ANSI/ISO/IEC 9899:1990)
• Standard internazionale corrente (non Americano!) per il C:
ISO/IEC 9899:1999 (noto come C99)
4
Il linguaggio C 7/243
Caratteristiche di ogni programma C
• Ogni programma C deve contenere una e una sola funzione main(), che rappresenta il programma principale ed il
punto di inizio dell'esecuzione del programma.
• La parentesi graffa aperta { indica l’inizio di un blocco di
istruzioni mentre la parentesi graffa chiusa } ne indica la
fine esattamente come in Java, e corrispondono al begin -
end del Pascal.
• Per ogni parentesi graffa aperta { deve essercene una
chiusa }.
Il linguaggio C 8/243
Caratteristiche di ogni programma C
• I commenti possono essere posti ovunque. L'inizio del commento è dato dalla coppia /*
• La fine del commento è indicata da */
• Non si può inserire un commento in un altro (vietato
l’annidamento dei commenti).
• Molti compilatori ammettono anche l’uso dei commenti in stile C++ con la doppia barra //, presenti anche in Java.
5
Il linguaggio C 9/243
Esempi
Ad esempio:
/* Esempio di programma con problemi nei commenti */
main()
{
/* Un commento */ ESATTO
/* Commento /* Ancora un commento */ QUI ERRORE */
}
Il linguaggio C 10/243
Esempi
Il seguente esempio e' un programma che produce l'output sullo schermo della frase "Hello World":
#include <stdio.h>
int main()
{
printf("Hello World \n");
exit(0);
}
6
Il linguaggio C 11/243
In C, occorre un punto e virgola ; dopo ogni istruzione.
L'istruzione printf(...) chiama la funzione printf che
visualizza ciò che gli viene passato come argomento (\nindica l'andata a capo), e ricorda la System.out.println() di Java e la write del Pascal.
L'istruzione exit(0) chiama la funzione exit che termina il
programma e restituisce al sistema operativo il codice 0.
Caratteristiche di ogni programma C
Il linguaggio C 12/243
In C non esiste la distinzione che esiste in Pascal tra funzionie procedure, e neppure esistono metodi come in Java. In C sono tutte funzioni, che possono restituire un qualche risultatooppure no.
Le chiamate ad ogni funzione in C si effettuano chiamando il nome della funzione seguita dalle parentesi tonde, aperta e chiusa, all'interno delle quali vengono passati i parametri necessari.
Anche se la funzione non richiede nessun parametro, il suo nome deve essere seguito dalle parentesi tonde aperta e chiusa.
Caratteristiche di ogni programma C
7
Il linguaggio C 13/243
Sviluppo di un Programma in linguaggio C
Il linguaggio C 14/243
Creare il programma
Create un file contenete il programma completo. Per creare il file
potete usare ogni editor ordinario con il quale abbiate familiarità
(vi, kedit, nedit).
Il nome di file deve per convenzione terminare con “.c”, (es.
myprog.c o progtest.c).
I contenuti devono essere conformi alla sintassi C.
8
Il linguaggio C 15/243
Compilazione
• Ci sono molti compilatori C disponibili. cc è il compilatore di default nei sistemi Unix. Il compilatore GNU C gcc è popolare e disponibile per molte piattaforme. Gli utenti di PC potrebbero avere familiarità col compilatore Borland bcc. Per i più audaci: Intel C compiler per Linux icc (http://developer.intel.com)
• Ci sono anche compilatori equivalenti C++ che di solito si chiamano CC (notate il maiuscolo). Per esempio, CC e GNU GCC. Il compilatore GNU si chiama anche g++ .
• Esistono altri compilatori C/C++ meno comuni.
• Tutti i compilatori citati sopra operano essenzialmente nello stesso modo e hanno molte opzioni command line in comune.
Il linguaggio C 16/243
Compilazione
La migliore fonte di informazioni su ogni compilatore è il suo manuale in linea: e.g. man cc.
Per compilare in vostro programma semplicemente invocate il comando cc seguito dal nome del programma (C) che volete
compilare.
E’ possibile specificare anche alcune opzioni di compilazione.
Quindi il comando di compilazione di base è: cc program.c
dove program.c è il nome del file.
9
Il linguaggio C 17/243
Compilazione
Se ci sono errori ovvi nel programma (di battitura, scrittura
sbagliata di una delle keywords o omissione di un punto e virgola),
il compilatore li troverà e li riporterà.
Ci possono essere ovviamente errori logici che il compilatore non
può scoprire. Potreste stare chiedendo al computer di fare
operazioni sbagliate.
Quando il compilatore ha “digerito” con successo il vostro
programma, la versione compilata (eseguibile) è lasciata in un file
chiamato a.out o, se viene usata l’opzione -o, nel file citato dopo il
-o.
Il linguaggio C 18/243
Compilazione
E’ più conveniente usare un -o e il nome del file nella
compilazione come in
cc -o program program.c
che mette il programma compilato nel file program (o in un
qualsiasi file citato nel nome che segue l’argomento “-o”) invece
di metterlo nel file a.out .
10
Il linguaggio C 19/243
Esecuzione
Il passo successivo è di eseguire il programma compilato. Per
lanciare un eseguibile in UNIX, basta semplicemente digitare il
nome del file che lo contiene, in questo caso program (o a.out),
preceduto dalla working directory corrente (./program, ./a.out).
Questo manda in esecuzione il programma, stampando i risultati
sullo schermo. A questo punto ci possono essere errori run-time,
come divisioni per zero, o può divenire evidente che il programma
ha prodotto un output non corretto.
In tal caso, occorre editare il sorgente del programma, ricompilarlo
e eseguirlo di nuovo.
Il linguaggio C 20/243
Il modello di Compilazione per il C
Gli step essenziali della
compilazione per un
programma sviluppato in C
sono rappresentati nel
seguente schema:
11
Il linguaggio C 21/243
Il Preprocessore
Il Preprocessore accetta codice sorgente in ingresso e ha la
responsabilità di
rimuovere i commenti,
interpretare preprocessor directives, indicate dal simbolo #.
Il linguaggio C 22/243
Il PreprocessorePer esempio
#include – include i contenuti del file nominato.
I file inclusi sono tipicamente header files.
#include <math.h> -- standard library maths file.
#include <stdio.h> -- standard library I/O file.
#define – definisce un nome simbolico o costante.
Macro substitution. #define MAX_ARRAY_SIZE 100
12
Il linguaggio C 23/243
Il Preprocessore
2) elimina i commenti contenuti nel sorgente.
3) genera così un nuovo file senza commenti e senza più necessità di
effettuare sostituzioni, pronto per essere processato dal compilatore.
/* file header h*/
extern mt, ni;
extern double f;
/* file prova.c */
#include "header.h"
/* size vettore */
#define SIZE 10
int vettore[SIZE];
/* file prova.E */
extern int i;
extern double f;
int vettore[10];
Preprocessore
Il linguaggio C 24/243
Il compilatore e l’assemblatore
Il compilatore C traduce codice sorgente in codice assembly. Il
codice sorgente è quello prodotto dal preprocessore.
L’assembler crea codice oggetto. In un sistema UNIX si possono
vedere file con un suffisso .o (.OBJ in MSDOS/WIN) che indica
file di codice oggetto.
13
Il linguaggio C 25/243
Il linkage editor (linker)
Se un file sorgente fa riferimento a funzioni di libreria o a
funzioni definite in altri file sorgente il linkage editor combina
queste funzioni (col main()) per creare un file eseguibile.
Anche i riferimenti a variabili esterne sono risolti in questa fase.
Il linguaggio C 26/243
Alcune opzioni del compilatore
-c
Sopprime il processo di linking e produce un file .o per
ogni file sorgente listato. Questi possono essere
successivamente linkati sempre col comando cc:
cc file1.o file2.o ...... -o executable
14
Il linguaggio C 27/243
Alcune opzioni del compilatore
-llibrary
Linkare con librerie oggetto. Questa opzione deve seguire
gli argomenti di tipo file sorgente. Le librerie oggetto sono
archiviate e possono essere standard, di terze parti o create
dall’utente. Probabilmente la libreria più usata è la math
library (libm.a). Occorre linkare questa libreria esplicitamente
se si vogliono usare le funzioni matematiche (non dimenticare
anche di #include l’header file <math.h> ), ad esempio:
cc calc.c -o calc -lm
Il linguaggio C 28/243
Alcune opzioni del compilatore
-Ldirectory
Aggiunge directory alla lista di directory contenente
object-library routines. Il linker cerca sempre le librerie
standard e di sistema in /lib and /usr/lib. Se si vogliono linkare
librerie che sono state create o installate dall’utente (a meno
che questi non abbia i privilegi necessari ad installare le
librerie in /usr/lib) è necessario specificare la directory dove i
file sono memorizzati, ad esempio:
cc prog.c -L/home/myname/mylibs mylib.a
15
Il linguaggio C 29/243
Alcune opzioni del compilatore-Ipathname
Aggiunge pathname alla lista di directory in cui cercare
#include files con filename relativi (che non cominciano con
slash /). Per default, il preprocessore prima cerca #include files
nella directory contenente il file sorgente, poi nelle in
directories indicate con le opzioni -I options (se presenti), e
finalmente, in /usr/include. Quindi per includere header files
memorizzati in /home/myname/myheaders occorre:
cc prog.c -I/home/myname/myheaders
Nota: I system library header files sono memorizzati in una
posizione speciale (/usr/include) e non sono influenzati dall’uso
dell’opzione -I. System header files e user header files vengono trattati in maniera lievemente differente.
Il linguaggio C 30/243
Alcune opzioni del compilatore
-g
invoca l’opzione di debugging. Questa dice al compilatore di
produrre informazioni addizionali sulla tabella dei simboli che
vengono usate da una varietà di tool di debugging.
-D
definisce simboli o come identificatori (-Didentifer) o come
valori (-Dsymbol=value) in una maniera del tutto simile al
comando del preprocessore #define .
16
Il linguaggio C 31/243
Le librerie
Il C è un linguaggio estremamente piccolo: molte delle funzioni
disponibili in altri linguaggi non sono presenti in C (e.g. niente
I/O, funzioni per gestione stringhe o matematiche).
A che serve il C allora?
Il C fornisce funzionalità mediante un ricco insieme di librerie di
funzioni.
Come risultato la maggior parte delle implementazioni C
includono librerie standard di funzioni per molte funzionalità ( I/O
etc.). Dal punto di vista pratico queste possono essere considerate
come facenti parte del C. Ma possono variare da macchina a
macchina (Borland C vs. UNIX C).
Il linguaggio C 32/243
Le librerie
Il programmatore può anche sviluppare le sue librerie di funzioni o
anche usare librerie di terze parti special purpose (e.g. NAG,
PHIGS).
Tutte le librerie (eccetto quelle di standard I/O) devono essereesplicitamente linkate con le opzioni del compilatore -l e,
possibilmente, -L, descritte prima.
Il sistema UNIX dispone di un ampio numero di funzioni di
libreria C. Alcune di queste implementano operazioni usate di
frequente, mentre altre sono di utilizzo specialistico.
17
Il linguaggio C 33/243
Le librerie
Non Reinventare la Ruota: E’ saggio per il
programmatore vedere se esiste una funzione di libreria
che esegua un certo compito prima di scriverne una
propria versione. Questo riduce il tempo di sviluppo del
programma. Le funzioni di libreria sono state testate,
quindi è più probabile che siano corrette rispetto a
qualsiasi funzione che possa essere scritta per
l’occasione. Questo riduce anche i tempi di debugging
del programma.
Il linguaggio C 34/243
Le librerie
Il manuale UNIX ha un entry per ciascuna funzione disponibile.
La documentazione delle funzioni è memorizzata nella sezione 3
del manuale, e molte utili system calls sono contenute nella
sezione 2. Se conoscete già il nome della funzione cercata, si può
leggere la pagina digitando (ad es., per sqrt):
man 3 sqrt
Se non conoscete il nome della funzione, una lista completa e’
inclusa nella pagina introduttiva della sezione 3 del manuale. Per
leggerla, digitate
man 3 intro
Ci sono circa 700 funzioni descritte qui. Questo numero tende a crescere ad ogni upgrade del sistema.
18
Il linguaggio C 35/243
Le librerie
Su ogni pagina del manuale, la sezione SYNOPSIS include
informazioni sull’uso della funzione. E.g.: #include <time.h>
char *ctime(time_t *clock)
Questo significa che occorre includere #include <time.h>
nel file sorgente prima di chiamare ctime. E che la funzione ctime
ha un puntatore al tipo time_t come argomento, e ritorna una
stringa (char *). time_t probabilmente viene definita nella stessa
pagina del manuale.
La sezione DESCRIPTION dà poi una breve descrizione di quello
che fa la funzione. Per esempio:
ctime() converte un long integer, puntato da clock, in una stringa di
26 caratteri del tipo prodotto da asctime().
Il linguaggio C 36/243
Struttura di un programma C
Un programma C ha in linea di principio la
seguente forma:
•Direttive per il preprocessore
•Definizione di tipi
•Prototipi di funzioni, con dichiarazione
dei tipi delle funzioni e delle variabili
passate alle funzioni)
•Dichiarazione delle Variabili Globali
•Dichiarazione Funzioni, dove ogni
dichiarazione di una funzione ha la forma:
tipo NomeFunzione(Parametri) { … }
•Dichiarazione variabili locali
•Istruzioni C
#include <stdio.h>
typedef struct point {
int x; int y;
};
int f1(void);
void f2(int i, double g);
int sum;
int main(void) {
int j;
double g=0.0;
for(j=0;j<2;j++)
f2(j,g);
return(2);
}
void f2(int i, double g)
{
sum = sum + g*i;
}
19
Il linguaggio C 37/243
Il preprocessore C
• Il preprocessore C modifica un codice sorgente prima
di passarlo al compilatore.
• Viene prevalentemente adoperato per includere filesdirettamente dentro altri files (#include), o per
definire costanti (#define).
• Può anche essere usato per creare inlined code usando
macro espanse al compile time e per prevenire che del
codice sia compilato più volte.
Il linguaggio C 38/243
Direttive del preprocessore
• Ci sono essenzialmente tre utilizzi del preprocessore: direttive, constanti e macro.
• Le direttive sono comandi che dicono al preprocessore di tralasciare parte di un file, di includere un altro file, o di definire una constante o una macro. Le direttive cominciano sempre con uno sharp sign (#) e, per leggibilità, dovrebbero essere
collocate alla sinistra della pagina.
• Tipicamente, constanti e macro sono scritte in ALL CAPS per indicare chiaramente cosa sono.
20
Il linguaggio C 39/243
Direttive del preprocessore
• Header Files
La direttiva #include dice al preprocessore
di prendere il testo di un file e di piazzarlo
all’interno del file corrente.
Tipicamente, questi statements sono collocati
in testa al programma - da cui il nome di
“header file” per i file inclusi in tal modo.
Il linguaggio C 40/243
Costanti#define [identifier name] [value]
Ogni volta che [identifier name] compare nel file, sarà rimpiazzato da [value]. Si noti che tutto quello che segue [identifier name] sarà
parte del rimpiazzo. Questo può portare a strani risultati: per es., è una cattiva idea usare commenti in C++ style in linee #define:
#define PI 3.14 // This is pi
x = PI + 1; // oops!
Nella linea precedente, PI sarà rimpiazzata da “3.14 // This is pi”, che commenterà il “ + 1;”,
causando un syntax error difficile da trovare.
21
Il linguaggio C 41/243
Costanti
Se dovete definire una costante in termini di una espressione matematica, è saggio includere l’intero valore in parentesi:
#define PI_PLUS_ONE (3.14 + 1)
Così facendo, si evita che l’ordine di valutazione delle espressioni distrugga il significato della costante:
x = PI_PLUS_ONE * 5;
Senza parentesi, la precedente sarebbe convertita in
x = 3.14 + 1 * 5;
che avrebbe portato al valutare 1 * 5 prima dell’addizione, e non dopo!!
Il linguaggio C 42/243
Definizioni vuote
E’ anche possibile scrivere semplicemente #define [indentifier name]
che definisce [identifier name] senza
assegnargli un valore.
Questo può servire insieme con un altro set di
direttive che consentono la compilazione
condizionale.
22
Il linguaggio C 43/243
Compilazione condizionale
• C’è un insieme di opzioni che può esser usato per
determinare se il preprocessore rimuoverà linee di
codice prima di passare il file al compilatore:
#if, #elif, #ifdef, e #ifndef.
• Un blocco #if o #if/#elif/#else o un blocco
#ifdef o #ifndef deve essere terminato da un
#endif.
• La direttiva #if prende un argomento numerico che
viene valutato true se è non-zero. Se il suo argomento
è false, allora il codice fino all’ #else, #elif, o
#endif di chiusura sarà escluso.
Il linguaggio C 44/243
Compilazione condizionale
Commenting out Code
• La compilazione condizione è particolarmente utile per
“comment out” un blocco di codice che contiene commenti
multi-line (che non possono essere innestati).
#if 0
/* comment ...
*/
code
/* comment */
#endif
23
Il linguaggio C 45/243
Compilazione condizionale
Evitare di includere file più volte (idempotenza)
• Un altro problema comune è che un header file è richiesto in più altri header files che poi sono inclusi in un source code file, con il risultato che variables, structs, classes e functionsappaiono definite più volte (una per ogni volta che l’headerfile viene incluso).
• Usando la direttiva #ifndef, si può includere un blocco di
testo solo se una particolare espressione è non definita; poi, nell’header file, si può definire l’espressione
#ifndef _FILE_NAME_H_
#define _FILE_NAME_H_
/* code */
#endif // #ifndef _FILE_NAME_H_
Il linguaggio C 46/243
Macro
• L’altro utilizzo prevalente del preprocessore è per definire macro. Il vantaggio di una macro è che può essere type-neutral (ma questo a volte è uno svantaggio!), e che viene inlined direttamente nel codice, cosicché non c’è nessun overhead per la chiamata di funzioni. (In C++, è possibile fare entrambe le cose usando templated functions e la keyword inline.)
• Una definizione di macro ha di solito la forma:
#define MACRO_NAME(arg1, arg2, ...) [code to expand to]
• Per es.:
#define INCREMENT(x) x++
24
Il linguaggio C 47/243
Macro e funzioniLe macro somigliano a chiamate di funzioni, ma ci sono almeno
un paio di trucchi da tenere presente:
Il testo esatto di una macro viene inserito nella macro
#define MULT(x, y) x * y
usando int z = MULT(3 + 2, 4 + 2)
z non assumerà il valore 30!!!
Espandendo la macro MULT:int z = 3 + 2 * 4 + 2;
2 * 4 viene valutato prima, e z assume il valore 13!
Per evitare tutto questo, bisogna forzare la valutazione degli argomenti prima del resto del corpo della macro. Questo può essere ottenuto usando le parentesi nella definizione di macro:#define MULT(x, y) (x) * (y)
ora MULT(3+2, 4+2) viene espanso in (3+2) * (4+2)
Il linguaggio C 48/243
Macro e funzioni
E’ anche di solito una buona idea includere il codice della macro
in parentesi se ci si aspetti che ritorni un valore. Altrimenti, ci
sono problemi simili a quelli per la def. di costanti.
Per es., la seguente macro, che aggiunge 5 all’argomento, ha
problemi quando viene inserita in uno statement più grande:
#define ADD_FIVE(a) (a) + 5
int x = ADD_FIVE(3) * 3; // this expands to
(3) + 5 * 3, so 5 * 3 is evaluated first //
Ora x è 18, non 24!
La soluzione è racchiudere l’intera macro in parentesi
#define ADD_FIVE(a) ((a) + 5) int x =
ADD_FIVE(3) * 3;
25
Il linguaggio C 49/243
Macro e funzioniD’altra parte, una macro multilinea usata per i suoi side effects e non per calcolare un valore, va racchiusa tra parentesi graffe in maniera tale da poterla utilizzare anche dopo uno statement if
#define SWAP(a, b) a ^= b; b ^= a; a ^= b;
int x = 10; int y = 5;
SWAP(x, y); // works OK
// What happens now?
if(x < 0) SWAP(x, y);
Nel secondo esempio, solo il primo statement, a ^= b, è gestito dal condizionale: gli altri due statements vengono eseguiti sempre!
Usando le parentesi graffe, la cosa funziona:
#define SWAP(a, b) {a ^= b; b ^= a; a ^= b;}
Per inciso, gli argomenti non sono in parentesi perché non saranno mai espressioni!!!
Il linguaggio C 50/243
Macro e funzioni
Altri problemi
Il problema più irritante con le macro è evitare di passare alle macro argomenti con "side effects".
Side effect: ogni espressione che fa qualcosa oltre a valutare un valore. (es.: ++x valuta x+1, ma incrementa anche x).Le macro non valutano gli argomenti, ma si limitano ad inserirlinel testo.
#define MAX(a, b) ((a) < (b) ? (b) : (a))
int x = 5, y = 10;
int z = MAX(x++, y++);
diventa:
int x = (x++ < y++ ? y++ : x++)
E y++ viene valutato due volte (y assumerà il valore 12 anziché11)
26
Il linguaggio C 51/243
Macro multilinea
Usando "\" Per indicare la continuazione di una linea, è possibile scrivere le macro su più righi per renderle più leggibili.
#define SWAP(a, b) { \
a ^= b; \
b ^= a; \
a ^= b; \
}
Non serve uno slash alla fine dell’ultima riga (lo slash dice al preprocessore che la macro continua alla linea successiva, non che la linea è la continuazione della linea precedente).
Il linguaggio C 52/243
Identificatori
Gli identificatori per il C possono essere usati per indicare variabili,
funzioni, etichette e dati di tipo definito dall'utente.
Un identificatore deve essere costituito da uno o più caratteri. Il
primo carattere di un identificatore (quello più a sinistra) deve essere
una lettera o una sottolineatura (underscore _ ). I caratteri
successivi al primo possono essere numeri o lettere o sottolineature.
Non sono ammessi caratteri di punteggiatura o altro, che hanno
significati speciali.
Identificatori ammessi: Num _Num num n_um
Identificatori NON ammessi: num$! num;pippo 1Num
27
Il linguaggio C 53/243
Identificatori
Il C, come in Java ma a differenza del Pascal, è case-sensitive, ovvero
considera caratteri minuscoli e caratteri maiuscoli come differenti.
Quindi l'identificatore "NUMERO" è diverso dall'identificatore
"numero" e da "Numero".
Non sono ammessi caratteri di punteggiatura o altro, che hanno
significati speciali.
Ogni identificatore, usato sia per identificare variabili sia per indicare
funzioni, deve essere diverso dalle parole riservate utilizzate per il
linguaggio C, e deve essere diverso anche dai nomi di variabili o
funzioni delle librerie utilizzate durante la fase di linking.
Il linguaggio C 54/243
Tipi di dati semplici
Il C mette a disposizione i seguenti dati di tipo semplice, la dimensioni di
alcuni di essi è dipendente dall’architettura:
void---0void
doubleextended1.7 * 10±308-1.7 * 10 ± 3088double
floatreal3.2 * 10±38-3.2 * 10 ± 384float
--232 – 104unsigned
long int
intlongint231-1-2314long int
--216 – 102unsigned
short int
shortinteger32768-327682short int
--25501unsigned
char
bytechar128-1271char
JavaPascalMax.Min.Dim.Tipo
28
Il linguaggio C 55/243
Tipi di dati semplici
Sui sistemi UNIX tutte le variabili dichiarate "int" sono
considerate “long int", mentre "short int" deve essere
dichiarato esplicitamente. In alcune architetture, inoltre, il dato
di tipo int è costituito da un numero maggiore di byte.
E' importante notare che in C non esiste un tipo di variabile
booleano. Come variabili di tipo booleano si possono utilizzare
variabili "char", "int", "long int" sia signed che unsigned.
Il linguaggio C 56/243
Tipi di dati sempliciCiascuno di questi dati, quando viene valutato dal punto di vista
booleano, è valutato FALSO quando assume valore 0 (ZERO),
se invece assume un valore diverso da zero è valuato come
VERO.
Per esempio, la seguente istruzione condizionale
if(-1)
i=1;
esegue l'assegnamento perchè il valore -1 è considerato VERO.
Il tipo void rappresenta un tipo di dato indefinito, e ha due
funzioni: serve ad indicare che una funzione non restituisce
nessun valore, e serve per definire un puntatore che punta ad un
dato generico.
29
Il linguaggio C 57/243
Variabili
Tutte le variabili devono essere dichiarate prima di essere usate. La
dichiarazione delle variabili è così fatta:
tipo ElencoVariabili;
dove “tipo” è uno dei tipi di dati ammessi dal C, e ElencoVariabili
è composto da uno o più identificatori validi separati da una virgola.
In questo modo ogni identificatore che compare in ElencoVariabili diventa una variabile di tipo “tipo”.
Il linguaggio C 58/243
Variabili
/* i è una variabile di tipo int. */
int i;
/* l1 ed l2 sono long int */
long int l1, l2;
/* f, g, x, y sono variabili in virgola
mobile */
float f, g, x, y;
30
Il linguaggio C 59/243
Variabili
Le variabili assumono caratteristiche diverse, in particolare
caratteristiche di visibilità (scope) da parte delle funzioni, in
dipendenza della posizione in cui avviene la dichiarazione.
A seconda della posizione in cui avviene la dichiarazione, si
distinguono tre tipi di variabili:
•Variabili Locali.
•Parametri Formali.
•Variabili Globali.
Il linguaggio C 60/243
Variabili localiDefiniamo “Blocco di Istruzioni” una sequenza di istruzioni C
racchiusa tra una parentesi graffa aperta ed una parentesi graffa
chiusa.
Il corpo di una funzione (il codice C che implementa una funzione) è
un caso particolare di Blocco.
Esempi di Blocchi:
Corpo di Funzione
int funcA (double f) {
int j; /* corretto */
printf("corpo funz.")
int K; /* ERRORE */
}
Interno di un ciclo forfor (i=0; i<10; i++)
{
int j;
printf("ciclo")
}
func(j);
/* ERRORE qui j NON
e' visibile */
Ovunque, usando il trucco
aperta-chiusa { ... }printf("codice C");
{
int j;
printf(“ciao")
}
31
Il linguaggio C 61/243
Variabili locali
Una variabile locale può essere dichiarata dentro un qualunque
blocco, ma in questo caso sempre e solo all'inizio del blocco, cioè
mai dopo che nel blocco sia stata scritta un'istruzione diversa da una
dichiarazione), ed in tal caso:
• la variabile verrà detta locale al blocco,
• potrà essere acceduta solo dall'interno del blocco stesso, cioè
non è visibile fuori dal blocco,
• avrà un ciclo di vita che inizierà nel momento in cui il controllo
entra nel blocco, e terminerà nel momento in cui il controllo esce
dal blocco.
Il linguaggio C 62/243
Variabili locali
Le variabili locali sono caricate sullo stack quando il controllo entra
nel blocco considerato, e vengono eliminate quando il controllo esce
dal blocco in cui sono state dichiarate.
Quando una variabile è dichiarata nel corpo di una funzione, è locale
alla funzione, e assomiglia alle variabili locali del Pascal o di Java.
32
Il linguaggio C 63/243
Variabili come parametri formali
Sono le variabili che definiscono, nell'implementazione di una
funzione, i parametri passati come argomenti alla funzione.
Sono esattamente equivalenti ai parametri formali delle funzioni
o procedure del Pascal o dei metodi Java.
Per default i dati di tipo semplice sono passati per valore, come in
Pascal. Invece i dati di tipo matrice sono passati per puntatore (la
modalità var del pascal).
int func( float f , int i ) {
printf ("param: f=%f i=%d\n,f,i);
}
Il linguaggio C 64/243
Variabili come parametri formali
Come per le variabili locali, anche i parametri formali potranno
essere acceduti solo dall'interno della funzione in cui sono stati
dichiarati, avranno un ciclo di vita che inizierà nel momento in
cui il controllo entra nella funzione stessa, e terminerà nel
momento in cui il controllo esce dal blocco.
I parametri formali vengono caricati sullo stack quando il
controllo entra nel blocco considerato, e vengono eliminati
quando il controllo esce dal blocco in cui sono state dichiarate. Se
il Parametro è passato per puntatore, è il puntatore ad essere
caricato sullo stack.
33
Il linguaggio C 65/243
Variabili globali e specificatore extern
Le variabili Globali sono quelle variabili che sono dichiarate
fuori da tutte le funzioni, in una posizione qualsiasi del file.
Una tale variabile allora verrà detta globale, perchè
• potrà essere acceduta da tutte le funzioni che stanno nello
stesso file ma sempre dopo la dichiarazione della variabile
stessa,
• potrà essere acceduta da tutte le funzioni che stanno in altri
file in cui esiste una dichiarazione extern per la stessa
variabile, ma sempre dopo la dichiarazione extern della
variabile stessa,
• avrà durata pari alla durata in esecuzione del programma.
Il linguaggio C 66/243
Variabili globali e specificatore extern
Per default, una variabile globale NomeVariabile è visibile da
tutti i moduli in cui esiste una dichiarazione di variabile extern
di NomeVariabile, ovvero una dichiarazione siffatta:
extern tipo NomeVariabile;
che è la solita dichiarazione di variabile preceduta però dalla parola extern.
34
Il linguaggio C 67/243
Variabili globali e specificatore extern
extern tipo NomeVariabile;
Una tale dichiarazione dice al compilatore che:
1.nel modulo in cui la dichiarazione extern è presente, la variabile NomeVariabile non esiste, ma esiste in
qualche altro modulo,
2. il modulo con la dichiarazione extern è autorizzato ad usare
la variabile, e quindi il compilatore non si deve preoccupare
se non la trova in questo file, perchè la variabile esiste da
qualche altra parte.
3. sarà il Linker a cercare in tutti i moduli fino a trovare il
modulo in cui esiste la dichiarazione senza extern per la
variabile NomeVariabile.
Il linguaggio C 68/243
Variabili globali e specificatore extern
La variabile NomeVariabile viene fisicamente collocata
solo nel modulo in cui compare la dichiarazione senza
extern, (che deve essere uno solo altrimenti il Linker non sa
quale scegliere) e precisamente nel punto in cui compare la
dichiarazione. Nei moduli con la dichiarazione extern invece
rimane solo un riferimento per il linker.
35
Il linguaggio C 69/243
Variabili globali e specificatore static
Se vogliamo che una certa variabile globale NomeVariabile,
collocata in un certo file, non sia accedibile da nessun altro
modulo, dobbiamo modificare la sua dichiarazione in quel modulo, facendola precedere dalla keyword static ottenendo
una dichiarazione di questo tipo.
static tipo NomeVariabile;
In tal modo, quella variabile potrà ancora essere acceduta dalle
funzioni nel suo modulo, ma da nessun altro modulo.
Il linguaggio C 70/243
Problemi con variabili globali
Esempio, Problemi con variabili globali, all'interno dello stesso
file in cui le variabili globali sono definite
#include <stdio.h>
int K=2; /* variabile globale visibile da tutte le funzioni */
int main(void) {
int i=34;
printf("i = %d \n", i ); /* stampa i cioè 34, corretto */
int J=0; /* ERRORE, dichiarazione dopo istr. */
printf("K = %d \n", K );
printf("g = %f \n", g );
funzione1();
exit(0);
}
double g=13;
void funzione1(void) {
printf("g = %f \n", g ); /* stampa g, cioè 13, corretto */
printf("i = %d \n", i ); /* NON VEDE i, ERRORE */
}
36
Il linguaggio C 71/243
Problemi tipici in programmi con più moduli
Il nostro programma è costituito da due moduli, var.c e main.c.
main.c contiene il main del programma, ed alcune funzioni, tra cui
la funzione f , che accetta come parametro formale un intero e lo
stampa. var.c contiene alcune variabili intere, alcune (A) globali,
altre (C) globali ma statiche e quindi visibili solo dentro il modulo
struct B { int idB; char campo1[8]; char campo2[10]; };
struct C { int idC; char val1[12]; char val2[25]; char val3[20] };
union ABC {
struct A a;
struct B b;
struct C c;
} abc = {IS_A, “field1”, “field2”, “field3”}
Il compilatore alloca area sufficente a contenere la più grande delle strutture contenute nella union. I
campi sono acceduti col nome della struttura contenuta, seguita dal punto come nelle struct e dal
nome del campo.
es: if( abc.a.idA == IS_B )
printf("%s", abc.b.campo2);
83
Il linguaggio C 165/243
PuntatoriI puntatori sono il segreto della potenza e la flessibilità del C, perchè:
- sono l'unico modo per effettuare alcune operazioni;
- servono a produrre codici sorgenti compatti ed efficienti, anche se a volte difficili da
leggere.
In compenso, la maggior parte degli errori che i programmatori commettono in linguaggio
C sono legati ai puntatori.
Il C utilizza molto i puntatori in maniera esplicita con:
- vettori;
- strutture;
- funzioni.
In C ogni variabile è caratterizzata da due valori:
- un indirizzo della locazione di memoria in cui sta la variabile,
- ed il valore contenuto in quella locazione di memoria, che è il valore della variabile.
Il linguaggio C 166/243
PuntatoriUn puntatore e' un tipo di dato, è una variabile che contiene l'indirizzo in memoria di
un'altra variabile, cioè un numero che indica in quale cella di memoria comincia la variabile
puntata.
Si possono avere puntatori a qualsiasi tipo di variabile.
La dichiarazione di un puntatore include il tipo dell'oggetto a cui il puntatore punta; questo
serve al compilatore che deve accedere alla locazione di memoria puntata dal puntatore per
sapere cosa troverà, in particolare per sapere le dimensioni della variabile puntata dal
puntatore.
Per dichiarare un puntatore p ad una variabile di tipo tipo, l'istruzione e':
tipo *p ;
L' operatore & fornisce l'indirizzo di una variabile, perciò l'istruzione p = & c scrive
nella variabile p l'indirizzo della variabile c, ovvero:
tipo c, *p; dichiaro una var c di tipo tipo ed un puntatore p a tipo
p = &c ; assegno a p l'indirizzo di c
84
Il linguaggio C 167/243
Puntatori
L'operatore * viene detto operatore di indirezione o deriferimento. Quando
una variabile di tipo puntatore è preceduta dall'operatore *, indica che stiamo
accedendo all'oggetto puntato dal puntatore.
Quindi con *p indichiamo la variabile puntata dal puntatore.
int c, *p; dichiaro una variabile c di tipo int ed un puntatore p a int.
p = &c; assegno a p l’indirizzo di c
c= 5; assegno a c il valore 5
printf("%d\n", *p); stampo il valore puntato dal puntatore p
viene stampato 5
Il linguaggio C 168/243
PuntatoriConsideriamo gli effetti delle seguenti istruzioni:
int *pointer; /* dichiara pointer come un puntatore a int */
int x=1,y=2;
(1) pointer = &x; /* assegna a pointer l'indirizzo di x */
(2) y = *pointer; /* assegna a y il contenuto dell'int puntato da pointer, x */
(3) x = pointer; /* assegna ad x l'indirizzo contenuto in pointer, serve cast
perchè pointer a int è diverso da int */
(4) *pointer=3; /* assegna alla variabile puntata da pointer il valore 3 */
Vediamo cosa succede in memoria. Supponiamo che la variabile x si trovi nella
locazione di memoria 100, y nella 200 e pointer nella 1000.
•L'istruzione (1) fa sì che pointer punti alla locazione di memoria 100 (quella di x), cioè
che pointer contenga il valore 100.
•La (2) fa sì che y assuma valore 1 (il valore di x).
•La (3) fa sì che x assuma valore 100 (cioe' il valore di pointer).
•La (4) fa sì che il valore del contenuto di pointer sia 3 (quindi x=3).
85
Il linguaggio C 169/243
Puntatori
Quindi con i puntatori possiamo considerare tre possibili valori:
• pointer: contenuto o valore della variabile pointer (indirizzo della
locazione di memoria a cui punta)
• &pointer: indirizzo fisico della locazione di memoria del puntatore
• *pointer: contenuto della locazione di memoria a cui punta
NB. Quando un puntatore viene dichiarato non punta a nulla, o meglio poichè
il contenuto di una cella di memoria è casuale fino a che non viene
inizializzata ad un valore noto, il puntatore punta ad una locazione di
memoria casuale, che potrebbe non essere accessibile dal processo. Cosi':
int *ip;
*ip=100;
scrive il valore 100 in una locazione qualsiasi: grave errore.
Il linguaggio C 170/243
PuntatoriL'utilizzo corretto e' il seguente; prima di scrivere un valore nella locazione di memoria
puntata dal puntatore ci assicuriamo che tale locazione di memoria appartenga al nostro
programma. Ciò e possibile in due modi:
1) il primo modo consiste nell'assegnare al puntatore l'indirizzo di una variabile del
nostro programma, quindi scriveremo il valore nella variabile puntata.
int *ip; int x; ip=&x;
*ip=100; scrivo 100 in x
2) il secondo modo consiste nel farci riservare dal sistema operativo una porzione di
memoria, salvare l'indirizzo di questa porzione di memoria nel nostro puntatore (ovvero far
puntare il puntatore a quell'area di memoria) in modo che i successivi riferimenti alla
locazione di memoria puntata dal puntatore lavorino su questa area di memoria che ci è
stata riservata.
Esiste una funzione di libreria standard malloc(), equivalente all’istruzione new di Java
e C++, che permette l'allocazione dinamica della memoria; e' definita come:
void *malloc ( int number_of_bytes).
Ad es.: int *p;
p= (int *) malloc(sizeof(int)); assegna a p spazio per un int.
86
Il linguaggio C 171/243
Aritmetica degli indirizzi
Si possono fare operazioni aritmetiche intere con i puntatori,
ottenendo come risultato di far avanzare o riportare indietro il
puntatore nella memoria, cioè di farlo puntare ad una locazione
di memoria diversa. Ovvero con i puntatori è possibile
utilizzare due operatori aritmetici + e - , ed ovviamente anche
++ e --.
Il risultato numerico di un'operazione aritmetica su un
puntatore è diverso a seconda del tipo di puntatore, o meglio a
seconda delle dimensioni del tipo di dato a cui il puntatore
punta. Questo perchè il compilatore interpreta diversamente
la stessa istruzione p++ a seconda del tipo di dato, in modo
da ottenere il comportamento seguente:
Il linguaggio C 172/243
Aritmetica degli indirizzi• Sommare un'unità ad un puntatore significa spostare in avanti in
memoria il puntatore di un numero di byte corrispondenti alle dimensioni del dato puntato dal puntatore.
• Ovvero se p è un puntatore di tipo puntatore a char, char *p; poichè il char ha dimensione 1, l'istruzione p++ aumenta effettivamente di un'unita il valore del puntatore p, che punterà al successivo byte.
• Invece se p è un puntatore di tipo puntatore a short int, short int *p; poichè lo short int ha dimensione 2 byte, l'istruzione p++ aumenteràeffettivamente di 2 il valore del puntatore p, che punterà allo short intsuccessivo a quello attuale.
87
Il linguaggio C 173/243
Aritmetica degli indirizzi
In definitiva, ogni volta che un puntatore viene
incrementato passa a puntare alla variabile successiva che
appartiene al suo tipo base, mentre un decremento lo fa
puntare alla variabile precedente. Quindi incrementi e
decrementi di puntatori a char fanno avanzare o
indietreggiare i puntatori a passi di un byte, mentre
incrementi e decrementi di puntatori a dati di dimensione
K fanno avanzare o indietreggiare i puntatori a passi di K
bytes. Il caso dei puntatori a void void *p viene trattato
come il caso dei puntatori a char, cioè incrementato o
decrementato a passi di un byte.
Il linguaggio C 174/243
Aritmetica degli indirizzi
Sono possibili non solo operazioni di incremento e decremento
(++ e --) ma anche somma e sottrazione di dati interi (char, int,
long int) che comunque vengono effettuati sempre secondo le
modalità di incremento decremento a passi di dimensioni pari alla
dimensione del dato puntato dal puntatore.
es: long int *p;
p = p + 9; oppure p +=9;
queste istruzioni fanno avanzare il puntatore p di 9*sizeof(long
int)=9*4=36 byte.
88
Il linguaggio C 175/243
Aritmetica degli indirizzi
long int *p;
char i;
i = 9;
p = p +i; oppure p +=i;
Anche queste istruzioni fanno avanzare il puntatore p di i*sizeof(long
int)=9*4=36 byte.
Non sono consentite altre operazioni oltre all'addizione e sottrazione di interi.
Non è possibile sommare sottrarre moltiplicare o dividere tra loro dei
puntatori.
Ad es,
int *ptr, *ptr1;
ptr = ptr + ptr1;
da' errore in compilazione di tipo "invalid operands to binary".
Il linguaggio C 176/243
Confronto tra puntatori
Si possono effettuare confronti tra puntatori, per verificare ad es. quale tra due
puntatori punta ad una locazione di memoria precedente, oppure se due
puntatori puntano ad una stessa locazione di memoria.
Ad es. sono valide istruzioni del tipo:
int x, y;
int *p, *q;
p = &x;
q = &y;
if(p<q)
printf("minore");
89
Il linguaggio C 177/243
Allocazione dinamica della memoria
I programmi C suddividono la memoria in 4 aree distinte: codice,
dati globali, stack e heap. Lo heap è un'area di memoria libera che
viene gestita da funzioni di allocazione dinamica del C quali lamalloc() e la free().
malloc() alloca la memoria (chiede al s.o. di riservare una area
di memoria) e restituisce un puntatore a void che punta all'inizio
di quest'area di memoria allocata. Se non è disponibile sufficiente
memoria restituisce NULL, (l'equivalente a null di Java) ad
indicare che l'allocazione non è stata effettuata. NULL equivale al
valore zero, quindi è possibile testare il risultato della malloc() in
Riassumendo, i puntatori sono delle variabili che contengono un
indirizzo in memoria, e si dice che il puntatore "punta a
quell'indirizzo". Il puntatore può essere di tipo "puntatore ad un
tipo" oppure di tipo generico "puntatore a void". Il puntatore
consente di accedere alla memoria a cui punta mediante
l'operatore [].
Se p è un puntatore ad un certo tipo (tipo *p;) e contiene un certo
valore addr, ovvero punta ad un certo indirizzo addr, l'espressione
p[k] accede all'area di memoria che parte dal byte
addr+k*sizeof(tipo) ed ha dimensione sizeof(tipo), trattandola
come se fosse una variabile di tipo tipo.
Il linguaggio C 180/243
Puntatori e array monodimensionali
• Nel caso di un puntatore a void, viene considerata 1 la dimensione del dato puntato, cioè il puntatore punta ad un byte.
• Anche per i vettori (gli array monodimensionali di dati di tipo tipo) l'accesso ai dati avviene secondo queste modalità vet[k], perchè in C, il nome di unarray è TRATTATO dal compilatore come un puntatore COSTANTE alla prima locazione di memoria dell'array stesso . (costante significa che non posso assegnare qualcosa al nome del vettore ma solo alle sue posizioni)
91
Il linguaggio C 181/243
Puntatori e array monodimensionali
A differenza dei vettori però, l'area di memoria a cui il puntatore punta non
viene allocata staticamente come per i vettori a partire dalla loro definizione
(che contiene le dimensioni del vettore stesso), ma può essere allocata
dinamicamente mediante alcune funzioni che richiedono al sistema operativo
di riservare memoria e restituire un indirizzo a quell'area di memoria allocata,
oppure può non essere allocata affatto.
Comunque la differenza principale tra puntatori e vettori e' che i puntatori
sono variabili che contengono un indirizzo, e questo contenuto (indirizzo)
può essere cambiato per puntare ad un'area di memoria diversa.
Invece per i vettori, anche se il loro nome è trattato come un puntatore (ma
costante) all'inizio del vettore stesso, non esiste una variabile (una cella di
memoria) in cui è mantenuto l'indirizzo della prima locazione del vettore,
e quindi questo indirizzo non può essere modificato, è costante. Ad es: il
compilatore da' errore qui:
int vet[100]; vet++; vet =10;
Il linguaggio C 182/243
Puntatori e array monodimensionali
A partire da queste considerazioni sottolineamo che definite due
variabili, un vettore int vet[100] ed un puntatore int *p mentre è
possibile fare riferimento all'indirizzo di un puntatore &p
ottenendo un indirizzo che punta alla locazione di p, cioè che indica
a che indirizzo sta la variabile p, nel caso dei vettori l'indirizzo del
vettore &vet è l'indirizzo che punta all'inizio del vettore stesso,
ovvero è l'indirizzo del primo elemento del vettore stesso. Ovvero le
seguenti espressioni indicano tutte la stessa cosa, l'inizio del vettore: vet, &(vet[0]), &vet
char str[100];
char *p;
p = str;
92
Il linguaggio C 183/243
Puntatori e array monodimensionali
con questo assegnamento viene assegnato al puntatore p l'indirizzo della prima locazione di memoria del vettore. Da questo momento in avanti potremo accedere ai dati del vettore sia tramite str, sia tramite p esattamente negli stessi modi, o tramite l'indicizzazione tra parentesi quadre, o tramite l'aritmetica dei puntatori.
str[10], *(str+10), p[10], *(p+10) sono tutti modi uguali per accedere alla 11-esima posizione del vettore str puntato anche da p.
es: str[10] = 'A';
printf( "str[10]=%c\n", str[10] );
printf( "str[10]=%c\n", p[10] );
printf( " str[10]=%c\n", *(p+10) );
Il linguaggio C 184/243
Un esempio di uso di puntatori: copia di una
stringa in un'altra.
Vediamo un esempio di applicazione dei puntatori, la copia di una stringa,
confrontandola con un'implementazione che non usa puntatori.
char sorgente[]="STRINGA";
char destinazione[100];
char *src, *dest;
src=sorgente;
dest=destinazione;
while( *(dest++) = *(src++) );
char sorgente[]="STRINGA";
char destinazione[100];
int i=0;
for (i=0; ; i++){
destinazione[i]=sorgente[i];
if( ! destinazione[i] )
break;
}
93
Il linguaggio C 185/243
Puntatori a strutture
E' possibile utilizzare puntatori che puntano a dati di tipo strutturato
come le struct. La definizione di questo tipo di puntatore ricalca
esattamente la definizione generale, cioè prevede di definire il tipo
di dato, e poi di definire il puntatore a quel tipo di dato.
Es.
struct PUNTO {char nome[50]; int x; int y; int z; }; /*
def. tipo */
struct PUNTO *ptrpunto; /* dichiaraz. puntatore a var.
strutturata */
struct PUNTO punto; /* dichiaraz. variabile di tipo
strutturato */
ptrpunto = &punto; /* assegno l'indirizzo della var.
punto */
Il linguaggio C 186/243
Puntatori a strutture
E' necessario per semplicita' definire un operatore che permetta di accedere ai campi della struttura direttamente dal puntatore.In C esiste l'operatore -> che permette l'accesso ad un campo della struttura puntata.
Nell'esempio, ptrpunto->x = 102; accede in scrittura al campo x della struttura di tipo PUNTO puntata da ptrpunto. Quindi l'operatore -> equivale ad usare in maniera combinata l'operatore * facendolo precedere al nome del puntatore per accedere al dato strutturato, seguito dall'operatore . per accedere al campo di interesse. Nell'esempio, l'istruzione ptrpunto->x = 102;
equivale all'istruzione:
(*ptrpunto).x = 102;
94
Il linguaggio C 187/243
Puntatori in struttureOra che abbiamo introdotto i puntatori, vediamo perchè è stato reso possibile utilizzare
per le struct quella sintassi particolare, con il nome della struct prima della sua
descrizione.
Vogliamo definire una struttura dati che serva come nodo di una lista semplice, una
struttura che contenga cioe' un intero (il dato della lista) ed un puntatore all'elemento
successivo della lista.
Il problema è come definire un puntatore ad un tipo di dato mentre ancora il tipo
di dato non è stato definito. Vediamo due esempi alternativi di come procedere, in un
caso utilizzando la typedef, nell'altro no.
struct nodolista {
int id;
struct nodolista *next;
};
struct nodolista nodo;
typedef struct nodolista {
int id;
struct nodolista *next;
} NODOLISTA;
NODOLISTA nodo;
Il linguaggio C 188/243
Array multidimensionaliUn array multidimensionale è un array, i cui elementi sono a loro volta degli array, i cui
elementi ecc.. fino ad esaurire le dimensioni volute.
Facciamo riferimento per semplicità al caso degli array bidimensionali, le matrici.
Le matrici in C sono locazioni di memoria contigue che contengono i dati
memorizzati per righe (come in pascal, mentre il fortran memorizza per colonne).
Quindi la matrice, anche se viene pensata come un oggetto a due dimensioni del tipo ad
es. (3 righe per 4 colonne), cioè come un vettore di righe, ciascuna delle quali è un
vettore,
è in realtà un oggetto disposto linearmente in memoria, riga dopo riga, come un vettore
95
Il linguaggio C 189/243
Array multidimensionaliIndicizzando righe e colonne a partire da 0, se NR è il numero di righe, ed NC è il
numero di colonne, allora l'elemento in posizione (n,c) della matrice sta fisicamente
nella posizione r*NC+c del vettore.
La matrice (es. di int) viene quindi dichiarata come un vettore di NR elementi di tipo
vettore di NC interi, ovvero come segue:
#define NR 3
#define NC 4
int matrice[NR][NC];
Nella dichiarazione la dimensione delle matrici deve essere una costante, perchè il
compilatore per effettuare l'accesso all'elemento (r,c) della matrice ovvero all'elemento
r*NC+c (a partire da dove inizia la matrice) deve conoscere la dimensione delle righe
cioè il numero NC di colonne.
L'accesso al dato in posizione r,c della matrice viene effettuato mediante l'operatore [ ]
già usato per i vettori.
Ad es. matrice[1][3]=7;
scrive il valore 7 nell'elemento che sta nella seconda riga in quarta colonna.
Il linguaggio C 190/243
Array multidimensionaliAnalogamente ai vettori, il nome della matrice rappresenta il puntatore al primo
elemento della matrice, cioè alla prima riga.
All'atto della dichiarazione è possibile inizializzare le matrici come di seguito
indicato:
int matrice [2][3] = {
{ 1,2,3 } ,
{ 4,5,6 }
};
L'inizializzazione viene fatta scrivendo tra parentesi graffe i valore di ciascun
elemento, separati da virgole. Come si vede, essendo la matrice un vettore di righe,
ciascuna riga viene inizializzata in modo analogo, scrivendo tra parentesi graffe i
valori di ciascun elemento separati da virgole.
96
Il linguaggio C 191/243
Vettori di puntatoriVogliamo costruire dinamicamente un dato M che sia un array di n puntatori ad int,
per poi passare ad allocare, per ciascuno dei puntatori ad int dell'array, spazio sufficiente
ad m int, ottenendo in definitiva un array di vettori di interi.
Il linguaggio C 192/243
Vettori di puntatori
Se gli elementi dell'array dinamico M sono puntatori ad interi, il nostro
puntatore M sarà un puntatore di puntatore ad int, cioè sarà un int* *M;
L'elemento intero che sta nella posizione c-esima dell'r-esimo vettore di
interi verrà acceduta mediante un'espressione: M[r][c]
i = M[r][c]
In effetti questa struttura dati è spesso usata come matrice in C, quando
non sono note a priori le dimensioni della matrice da realizzare, e si
preferisce non sovradimensionarla con un'allocazione statica.
97
Il linguaggio C 193/243
Vettori di puntatori/* allocazione della struttura dati */
int n,m r,c;
int* *M;
/* valori ad n ed m assegnati run-time */
n = ...;
m = ...;
if ( ! (M = malloc( n * sizeof(int*) )) )
exit(1);
for ( r=0; r<n; r++)
if ( ! (M[r] = malloc( m * sizeof(int) )) )
exit(1);
else
for ( c=0; c<m; c++)
M[r][c] = valore
Il vettore di puntatori qui visto viene utilizzato in un caso particolare, in cui tutti i vettori
allocati hanno stessa dimensione. In generale invece è possibile per ogni puntatore
allocare un vettore di dimensioni diverse.
Il linguaggio C 194/243
Inizializzazione statica di un vettore di
puntatori
E' inoltre possibile costruire un dato M che sia un array di n puntatori, non
solo dinamicamente, ma anche staticamente al momento della dichiarazione.
La seguente istruzione crea ed inizializza un array di puntatori a char, cioè un
array di stringhe di lunghezza diversa (nomi è un vettore di puntatori a char).
char *nomi [] = {
"paola",
"marco",
"giovanna"
};
98
Il linguaggio C 195/243
Funzioni
In C non esiste la distinzione che esiste in Pascal tra funzioni e procedure, non esistono le classi e
i metodi di Java, in C sono tutte funzioni, che possono restituire un qualche risultato oppure no,
nel qual caso restituiscono void.
Le chiamate ad ogni funzione in C si effettuano chiamando il nome della funzione seguita dalle
parentesi tonde, aperta e chiusa, all'interno delle quali vengono passati i parametri necessari.
Anche se la funzione non richiede nessun argomento, nella chiamata il suo nome deve essere
seguito dalle parentesi tonde aperta e chiusa. Il main stesso è una funzione.
La forma generale della definizione di una funzione e':