
01. Caratteristiche del linguaggio C
02. Storia del linguaggio C
03. Primo approccio
04. Compilazione di un programma C
05. Struttura di un programma C
06. Variabili
06.01. Stampa ed input di variabili
07. Operatori
07.01. Operatori aritmetici
07.02. Operatori di confronto
07.03. Operatori logici
07.04. Operatori di basso livello
07.04.01. Operatori di bitwise
07.04.02. Bit Fields
07.05. Ordine di precedenza degli operatori
08. Strutture di controllo
08.01. If
08.02. Operatore "?"
08.03. Switch
08.04. For
08.05. While
08.06. Do-While
08.07. Break e Continue
09. Arrays
09.01. Array singoli e multidimensionali
09.02. Stringhe
10. Funzioni
10.01. Funzioni "void"
10.02. Funzioni ed array
10.03. Prototipi di funzioni
11. Ulteriori tipi di dati
11.01. Strutture
11.02. Unioni
11.03. Type-casting
11.04. Enumerated Types
11.05. Variabili statiche
12. Errori comuni in C
12.01. Assegnazione (=) al posto di confronto (==)
12.02. Passaggio dell'indirizzo di puntatori
12.03. Mancanza di () per una funzione
12.04. Indici di array
12.05. Array di caratteri e puntatori
12.06. C e' case-sensitive
12.07. ";" chiude ogni istruzione
13. Puntatori
13.01. Cos'e' un puntatore
13.02. Puntatori e funzioni
13.03. Puntatori e array
13.04. Array di puntatori
13.05. Array multidimensionali e puntatori
13.06. Inizializzazione statica degli array di puntatori
13.07. Puntatori e strutture
13.08. Le "trappole" piu' comuni dei puntatori
13.08.01. Non assegnare un puntatore ad un indirizzo di
memoria prima di utilizzarlo
13.08.02. Assegnazione indiretta illegale
14. Allocazione dinamica della memoria
14.01. Malloc
14.02. Linked Lists
15. Input ed output
15.01. Streams
15.01.01. Streams predefinite
15.01.01.01. Redirezione
15.02. Funzioni comuni di I/O
15.03. Formattazione di I/O
15.03.01. Printf
15.04. Scanf
15.05. Files
15.05.01. Lettura e scrittura su files
15.06. Sprintf ed Sscanf
15.07. Input dalla linea di comando
15.08. I/O di basso livello
16. Il preprocessore C
16.01. #define
16.02. #undef
16.03. #include
16.04. #if - Inclusione condizionale
17. Scrittura di grossi programmi
17.01. File header
17.02. Variabili esterne e funzioni
17.02.01. Scopo delle variabili esterne
17.03. L'utility Make
17.04. Programmazione di Make
17.05. Creazione di un makefile
17.06. Macro di Make
17.07. Esecuzione di Make
18. UNIX e il C
18.01. Vantaggi di usare UNIX con il C
18.02. Utilizzo delle chiamate di sistema UNIX e delle funzioni
di libreria
18.03. Trattamento di file e directory
18.03.01. Funzioni di trattamento delle directory
18.03.02. Routine di trattamento dei file
18.03.03. errno
18.04. Controllo e gestione dei processi
18.04.01. Esecuzione di comandi UNIX da C
18.04.01.01. execl()
18.04.01.02. fork()
18.04.01.03. wait()
18.04.01.04. exit()
18.04.02. Utilizzo di pipe in un programma C
18.04.02.01. popen() - Piping formattato
18.04.02.02. pipe() - Piping di basso livello
18.04.03. Interruzioni e segnali
18.04.03.01. Invio di segnali - kill()
18.04.03.02. Ricezione di segnali - signal()
18.05. Times Up!!
19. Opzioni comuni del compilatore C
19.01. Opzioni di compilazione
20. Funzioni della libreria standard C
20.01. Manipolazione dei buffer
20.02. Classificazione dei caratteri e conversione
20.03. Conversione dei dati
20.04. Manipolazione delle directory
20.05. Manipolazione dei file
20.06. Input e Output
20.06.01. Stream I/O
20.06.02. I/O di basso livello
20.07. Matematica
20.08. Allocazione di memoria
20.09. Controllo dei processi
20.10. Ricerca e ordinamento
20.11. Manipolazione di stringhe
20.12. Time
==============================================================================
------------------------------------------------------------------------------
01. Caratteristiche del linguaggio C
------------------------------------------------------------------------------
Qui di seguito verranno elencate brevemente alcune delle caratteristiche del
C che definiscono il linguaggio stesso e che hanno contribuito alla
popolarita' che ha raggiunto come linguaggio di programmazione:
- dimensioni ridotte
- utilizzo frequente di chiamate a funzioni
- loose typing (a differenza del Pascal)
- linguaggio strutturato
- programmazione a basso livello facilmente disponibile
- implementazione dei puntatori (ampio uso di puntatori per memoria,
vettori, strutture e funzioni)
Il C e' ora diventato un linguaggio professionale ampiamente utilizzato per
varie ragioni:
- ha strutture di alto livello
- puo' maneggiare attivita' di basso livello
- produce programmi efficienti
- puo' essere compilato su un'ampia gamma di computers
Il suo principale inconveniente e' quello di avere un metodo scadente per
l'identificazione di errori, che puo' escluderne l'utilizzo ai principianti.
Comunque con un minimo di diligenza si puo' risolvere elegantemente questo
problema, in quanto si possono violare le regole del C non appena si sono
imparate (non molti linguaggi lo permettono). Nel caso in cui venga fatto
correttamente e con attenzione, questo porta a sfruttare le potenzialita'
della programmazione C.
Lo standard per i programmi C in origine era dato dalle caratteristiche
messe a punto da Brian Kernighan. Al fine di rendere il linguaggio piu'
accettabile a livello internazionale, venne messo a punto uno standard
internazionale chiamato ANSI C (American National Standards Institute).
------------------------------------------------------------------------------
02. Storia del linguaggio C
------------------------------------------------------------------------------
Le pietre miliari nel corso dell'evoluzione del C come linguaggio sono
elencate di seguito:
- UNIX developed c. 1969 - DEC PDP-7 Assembly Language
- BCPL - un OS facilmente accessibile che fornisce potenti strumenti di
sviluppo prodotti a partire da BCPL. Si tratta di un assemblatore noioso,
lungo ed incline agli errori
- Un nuovo linguaggio "B" come secondo tentativo c. 1970
- Un linguaggio "C" totalmente nuovo come successore di "B" c. 1971
- Dal 1973 UNIX OS, quasi totalmente scritto in C
------------------------------------------------------------------------------
03. Primo approccio
------------------------------------------------------------------------------
Un minimo programma in C e':
main()
{
}
che corrisponde a un programma in Pascal:
program minimum;
begin
end
Ogni programma C deve contenere una e una sola funzione main().
Per ogni parentesi graffa aperta (che corrisponde al begin in pascal)
deve essercene una chiusa (che corrisponde all'end in pascal).
I commenti possono essere posti ovunque utilizzando /* (inizio commento)
e */ (fine commento), ma non si puo' inserire un commento in un altro.
Ad esempio:
/* Esempio di programma in C */
main()
{
/* Un ulteriore commento */ ESATTO
/* Commento /* Ancora un commento */ */ ERRATO
}
Il seguente esempio e' un programma che produce l'output sullo
schermo della frase "Hello World":
main()
{
printf("Hello World n");
exit(0);
}
L'istruzione "printf" e' una funzione C che visualizza cio' che gli
viene passato come argomento.
Per creare un file contenente uno dei precedenti programmi si puo'
utilizzare un qualsiasi text editor disponibile sulla macchina (vi, emacs,
xedit, ...).
Il nome del file deve avere l'estensione ".c", cioe' chiamarsi, ad esempio,
prog.c. Il contenuto, ovviamente, deve rispettare la sintassi C; per quanto
riguarda gli esempi sopra riportati, potrebbero iniziare con una riga del
tipo
/* Esempio ... */ (anche con una linea vuota che la precede)
e terminare con la linea
} /* Fine del programma */ (anche con una linea vuota che la segue)
------------------------------------------------------------------------------
04. Compilazione di un programma C
------------------------------------------------------------------------------
Per compilare il programma si utilizza il comando cc seguito dal nome del
programma C sorgente, dove "cc" e' il nome del compilatore C.
Ad esempio:
cc prog.c
Se il compilatore trova errori (in genere syntax error, come errori di
battitura, errori di sintassi delle parole chiave o ";" omessi), questi
vengono identificati e visualizzati; in caso contrario, viene creato il
file eseguibile a.out. Il compilatore non identifica eventuali errori di
logica del programma, quindi potrebbero essere eseguite delle operazioni
errate, ed e' compito dell'utente trovarle (anche con l'ausilio di appositi
programmi di debugging).
In fase di compilazione possono essere specificate anche ulteriori opzioni:
la piu' utilizzata e' "-o nome-file", che crea l'eseguibile con il nome
nome-file invece di a.out, ma ne esistono altre come ad esempio "-c"
(opzione senza argomenti, per la soppressione di link).
Altra opzione possibile e' "-g", con cui e' necessario compilare per poter
utilizzare il debugger "dbx".
Ad esempio:
cc prog.c -o prog (oppure cc -o prog.c prog)
cc -c prog.c -o prog
cc -g prog.c -o prog
Per far eseguire il programma e' sufficiente scrivere il nome
dell'eseguibile creato (e' ovvio che il file eseguibile deve avere i
permessi per l'esecuzione, solitamente assegnati automaticamente in fase di
compilazione):
si avranno visualizzati sullo schermo gli eventuali risultati.
Nel momento dell'esecuzione e' possibile osservare ed identificare eventuali
errori di run-time, come ad esempio le divisioni per zero; in tal caso
l'esecuzione termina irregolarmente e viene generato un file core con lo
stato del programma in esecuzione al momento del verificarsi dell'errore.
Se il programma in esecuzione non rilascia errori ma produce output errati,
e' evidente che contiene errori logici; questi andranno corretti editando
il programma sorgente, questo dovra' essere ricompilato e si potra' lanciare
nuovamente l'esecuzione.
La compilazione del programma C avviene attraverso le seguenti fasi:
- un preprocessore che accetta il codice sorgente come input
ed e' resposabile della:
- rimozione di commenti
- interpretazione di speciali direttive per il preprocessore
denotate da "#".
Ad esempio:
#include - include il contenuto di un determinato file
(solitamente chiamato header, con suffisso ".h").
#include - standard library maths file.
#include - standard library I/O file
#define - definisce un nome simbolico o una costante
(sostituzione di una macro).
#define MAX_ARRAY_SIZE 100
- il compilatore C che traduce il codice sorgente ricevuto dal
preprocessore in codice assembly.
- l' assembler che crea il codice oggetto (in UNIX i file con il suffisso
.o sono i file in codice oggetto, che corrispondono ai file .obj in
MSDOS).
- il link editor che combina le funzioni definite in altri file sorgenti
o definite in librerie, con la funzione main() per creare il file
eseguibile.
Infatti molte delle funzioni presenti in altri linguaggi non sono incluse
nel C (ad esempio, funzioni di I/O, di manipolazione di stringhe o
matematiche), ma il C fornisce tali funzionalita' attraverso un ricco
insieme di librerie di funzioni. Molte applicazioni C includono librerie
standard di funzioni per coprire le utilita' mancanti.
In questa fase vengono anche ricostruiti i riferimenti alle variabili
esterne utilizzate nei sorgenti C.
------------------------------------------------------------------------------
05. Struttura di un programma C
------------------------------------------------------------------------------
Un programma C ha in linea di principio la seguente forma:
Comandi per il preprocessore
Definizione di tipi
Prototipi di funzioni (dichiarazione dei tipi delle funzioni e delle
variabili passate alle funzioni)
Variabili
Funzioni
Vediamo l'esempio di un programma:
main()
{
printf("I like Cn");
exit(0);
}
Note:
- Il C richiede un punto e virgola alla fine di ogni statement.
- printf() e' una funzione standard richiamata da main.
- n significa una nuova linea (a capo).
- exit() e' anch'essa una funzione standard che fa terminare il programma
(qui non sarebbe necessaria in quanto e' l'ultima linea di main e il
programma terminerebbe comunque).
------------------------------------------------------------------------------
06. Variabili
------------------------------------------------------------------------------
Il C ha i seguenti tipi di dati:
Tipo Size (byte)
char 1
unsigned char 1
short int 2
unsigned short int 2
(long) int 4
float 4
double 8
Sui sistemi UNIX tutte le variabili dichiarate "int" sono considerate
"long int", mentre "short int" deve essere dichiarato esplicitamente.
E' importante notare che in C non esiste un tipo di variabile booleano,
quindi si possono utilizzare variabili "char", "int" o meglio "unsigned
char". "unsigned" puo' essere utilizzato con tutti i tipi "char" e "int".
Per dichiarare una varibile si scrive:
var_tipo elenco-variabili-separate-da-virgole ;
Le variabili globali si definiscono al di sopra della funzione main(),
nel seguente modo:
short number,sum;
int bignumber,bigsum;
char letter;
main()
{
...
}
E' possibile preinizializzare una variabile utilizzando = (operatore di
assegnazione).
Ad esempio:
int i,j,k=1;
float x=2.6,y;
char a;
Vediamo due esempi di inizializzazione di variabili che si equivalgono,
senza pero dimenticare che il metodo utilizzato nel primo esempio
risulta piu' efficiente:
Esempio 1: float sum=0.0;
int bigsum=0;
char letter='A';
main()
{
...
}
Esempio2: float sum;
int bigsum;
char letter;
main()
{
sum=0.0;
bigsum=0;
letter='A';
...
}
E' possibile effettuare assegnazioni multiple purche' le variabili
siano dello stesso tipo.
Ad esempio:
int somma;
char letter="A";
main()
{
int a,b,c=3;
a=b=c;
}
dove l'istruzione a=b=c (con c=3) corrisponde ad a=3, b=3 e c=3, ma
anche in questo caso risulta piu' efficiente il primo metodo.
Si possono definire nuovi propri tipi di variabili utilizzando "typedef"
(questo risulta utile quando si creano strutture complesse di dati).
Come esempio di utilizzo semplice consideriamo come sia possibile creare
i due nuovi tipi di variabile "real" e "letter", che potranno successiva-
mente essere utilizzati alla stessa maniera dei tipi predefiniti del C.
Ad esempio:
typedef float real;
typedef char letter;
variabili dichiarate:
real sum=0.0;
letter nextletter;
------------------------------------------------------------------------------
06.01. Stampa ed input di variabili
------------------------------------------------------------------------------
Il C sfrutta l'output formattato.
Per stampare il contenuto di una variabile si utilizza la funzione printf().
Bisogna pero' specificare il formato della variabile utilizzando il
carattere speciale di formattazione "%" seguito dal carattere che definisce
un certo formato per una variabile:
%c - char
%d - int
%f - float
Ad esempio: printf("%c%d%f",letter,somma,z);
Nota: l'istruzione di formattazione e' racchiusa tra "", e le variabili
vengono esposte di seguito; assicurarsi che l'ordine dei formati ed
il tipo di dato delle variabili coincidano.
Sempre a proposito della funzione "printf", vediamo il seguente esempio di
una istruzione di stampa:
printf(".n.1n..2n...3n");
per la quale l'output sara':
.
.1
..2
...3
scanf() e' la funzione per l'input di valori a strutture di dati.
Il suo formato e' simile a quello di printf():
scanf("%c%d%f",&ch,&i,&x);
Nota: "&" si riferisce all'indirizzo della variabile, e va sempre messo
davanti ai nomi di variabili in acquisizione; il motivo verra'
spiegato nel paragrafo dei "puntatori".
------------------------------------------------------------------------------
07. Operatori
------------------------------------------------------------------------------
------------------------------------------------------------------------------
07.01. Operatori aritmetici
------------------------------------------------------------------------------
Come gia' accennato, le assegnazioni in C vengono effettuate utilizzando
"=". Oltre agli operatori arimetici standard +,-,*,/ e all'operatore %
(modulo) per gli interi, in C si hanno anche gli operatori incremento ++
e decremento --, che possono essere preposti o posposti all'argomento. Se
sono preposti il valore e' calcolato prima che l'espessione sia valutata,
mentre se sono posposti il valore viene calcolato dopo la valutazione della
espressione.
Ad esempio:
int x,z=2;
1) x=(++z)-1;
A questo punto x=2 e z=3
int x,z=2;
2) x=(z++)-1;
A questo punto x=1 e z=3
Riportiamo un ulteriore esempio:
int x,y,w;
main()
{
x=((++z)-(w--))%100;
}
che equivale alle seguenti istruzioni:
int x,y,w;
main()
{
z++;
x=(z-w)%100;
w--;
}
E' importante sottolineare che un'istruzione del tipo
x++
e' piu' veloce della corrispondente
x=x+1
L'operatore "%" (modulo) puo' essere utilizzato solamente con le variabili
di tipo integer; la divisione "/" e' utilizzata sia per gli integer che
per i float.
A proposito della divisione riportiamo un altro esempio:
z=3/2
dove z avra' valore 1, anche se e' stato dichiarato come float
(di regola, se entrambi gli argomenti della divisione sono integer,
allora verra' effettuata una divisione integer);
per avere un risultato corretto sara' necessario scrivere:
z=3.0/2 oppure
z=3/2.0 o, ancora meglio,
z=3.0/2.0
Inoltre esiste una forma contratta per espressioni del tipo
expr1 = expr1 op expr2
(ad esempio: i=i+2 oppure x=x*(y+3))
che diventano:
expr1 op = expr2
Per cui i=i+2 puo' essere scritta nel modo contratto come i+=2
od x=x*(y+3) diventare x*=y+3.
Nota: l'espressione x*=y+3 corrisponde a x=x*(y+3) e non a x=x*y+3.
------------------------------------------------------------------------------
07.02. Operatori di confronto
------------------------------------------------------------------------------
Per testare l'ugualianza si usa "==" mentre per la disugualianza "!=".
Ci sono poi gli operatori "<" (minore), ">" (maggiore), "<=" (minore o
uguale), ">=" (maggiore o uguale).
NB. if (i==j) ... esegue il contenuto dell'if se i e' uguale a j, ma
if (i=j) ... e' ancora sintatticamente esatto ma effettua l'assegnazione
del valore di j ad i e procede se j e' diverso da zero in quanto
viene interpretato il valore TRUE (e' come scrivere if i ... con i
diverso da zero). In questo caso si tratterebbe di una "assegnazione di
valore", una caratteristica chiave del C.
------------------------------------------------------------------------------
07.03. Operatori logici
------------------------------------------------------------------------------
Gli operatori logici, solitamente utilizzati con le istruzioni condizionali
che vedremo piu' avanti, sono "&&" per AND logico e "||" per OR logico.
Nota: "&" e "|" hanno un significato diverso, poiche' sono bitwise AND e OR.
------------------------------------------------------------------------------
07.04. Operatori di basso livello
------------------------------------------------------------------------------
Nel capitolo relativo ai puntatori si vedra' come questi permettano il
controllo delle operazioni di memoria di basso livello.
Molti programmi (in particolare le applicazioni di gestione del sistema)
devono realmente operare a basso livello, poiche' lavorano su bytes
individuali.
E' importante notare che la combinazione di puntatori e di operatori
bit-level rendono il C utilizzabile per molte applicazioni a basso livello
e possono quasi sempre sostituire il codice assembly (ricordiamo che
solamente circa il 10% di UNIX e' un codice assembly, mentre il resto e' C).
------------------------------------------------------------------------------
07.04.01. Operatori di bitwise
------------------------------------------------------------------------------
Gli operatori di bitwise (che operano sui singoli bit) sono i seguenti:
"&" AND
"|" OR
"^" XOR
"~" Complemento a 1 (0=>1, 1=>0)
"<<" shift a sinistra
">>" shift a destra
Nota: fare attenzione, come gia' detto in precedenza, a non confondere
& con && (& e' "bitwise and", mentre && e' "logical and"); la stessa
cosa vale per | e ||.
"~" e' un operatore unario, cioe' opera su un solo argomento indicato a
destra dell'operatore.
Gli operatori di shift eseguono un appropriato shift dall'operatore indicato
a destra a quello indicato a sinistra. L'operatore destro deve essere
positivo. I bits liberati vengono riempiti con zero (cioe' non si tratta di
una rotazione, con recupero sul lato opposto dei bit shiftati).
Ad esempio: z<<2 shifta i bit in z di due posti verso sinistra
cosi', se z=00000010 (binario) o 2 (decimale)
allora, z>>=2 => z=00000000 (binario) o 0 (decimale)
inoltre, z<<=2 => z=00001000 (binario) o 8 (decimale)
Quindi, uno shift a sinistra e' equivalente ad una moltiplicazione per 2;
similmente, uno shift a destra equivale ad una divisione per 2.
Nota: l'operazione di shift e' molto piu' veloce della reale moltiplicazione
(*) o divisione (/); cosi', se occorrono veloci moltiplicazioni o
divisioni per 2 si puo' utilizzare lo shift.
Per illustrare le molteplici caratteristiche degli operatori di bitwise,
riportiamo una funzione (bitcount) che somma 2 bit settati ad 1 un un numero
ad 8 bit (unsigned char) passato come argomento alla funzione:
int bitcount(unsigned char x)
{int count;
for (count=0; x!=0; x>>=1);
if (x&01)
count++;
return count;
}
Questa funzione mostra molti punti del programma C:
- il loop "for" non viene usato per semplici operazioni di somma
- x>>=1 => x=x>>1
- il loop "for" shifta ripetutamente a destra x, finche' x diventa 0
- il controllo "if" utilizza la valutazione dell'espressione x &01
- x &01 controlla il primo bit di x, ed esegue count++ se questo e' 1
------------------------------------------------------------------------------
07.04.02. Bit Fields
------------------------------------------------------------------------------
I Bit Fields permettono il raggruppamento dei dati in una struttura. Questa
tecnica viene usata soprattutto quando la gestione della memoria o la
memorizzazione dei dati sono uno meta molto ambita.
Tipici esempi sono costituiti da:
- raggruppamento di parecchi oggetti in una parola macchinai (i flag di un
bit possono essere compattati); ad esempio, la tabella dei simboli
nell'ambito dei compilatori;
- lettura di formati di file esterni (formati di file non standard possono
essere importati); ad esempio, gli interi di 9 bit.
Il C permette di fare questo in una definizione di struttura, mettendo
":lunghezza-bit" dopo la variabile stessa, e cioe':
struct packed-struct {
unsigned int f1:1;
unsigned int f2:1;
unsigned int f3:1;
unsigned int f4:1;
unsigned int type:4;
unsigned int funny_int:9;
} pack;
Qui la struttura packed-struct contiene 6 elementi: 4 flag da 1 bit (f1,...
f4) e funny_int da 9 bit.
Il C automaticamente raggruppa assieme i campi di bit elencati nell'esempio
appena riportato.
Solitamente si accede ai membri della struttura nel seguente modo:
pack.type = 7;
Notiamo che:
- solamente "n" bit di basso livello possono essere assegnati ad un numero
di "n" bit. Cosi' il campo "type" non puo' assumere valori maggiori di 15
(4 bits long);
- i bit fields vengono sempre convertiti al tipo intero prima di eseguirvi
delle operazioni;
- e' permesso "mescolare" tipi normali con bit fields;
- la definizione di "unsigned" e' importante, per assicurarsi che per i
flags non venga usato nessun bit per il segno.
------------------------------------------------------------------------------
07.05. Ordine di precedenza degli operatori
------------------------------------------------------------------------------
E' necessario fare attenzione al significato di un'espressione come
a + b * c
dove potremmo volere sia l'effetto di
(a + b) * c
sia quello di
a + (b * c)
Tutti gli operatori hanno una propria priorita', e gli operatori ad alta
priorita' sono valutati prima di quelli a bassa priorita'.
Gli operatori con la stessa priorita' sono valutati da sinistra a destra;
Cosi'
a - b - c
e' valutato
(a - b) - c
come ci si puo' aspettare.
L'ordine di priorita', dalla piu' alta alla piu' bassa, degli operatori
in C e':
()[]->.
!~-*& sizeof cast ++ --
(these are rigth -> left)
*/%
+-
< <= >= >
== !=
&
^
|
&&
||
?: (right -> left)
= += -= (right -> left)
,(comma)
Quindi:
"a < 10 &&2 * b < c"
e' interpretato come:
"(a < 10) &&((2 * b) < c)".
ed anche:
a=
b=
spokes / spokes_per_wheel
+ spares;
e' valutato come:
a=
(b=
(spokes / spokes_per_wheel)
+ spares
);
------------------------------------------------------------------------------
08. Strutture di controllo
------------------------------------------------------------------------------
Quelli che seguono sono i vari metodi con cui il C puo' controllare il
flusso logico di un programma. A parte alcune minime differenze sintattiche,
queste istruzioni sono simili a quelle che si possono trovare negli altri
linguaggi.
Come abbiamo visto, in C esistono le seguenti operazioni logiche:
==,!=,||,&&.
Un altro operatore e' il not "!" unario (ha un solo argomento).
Questi operatori sono utilizzati congiuntamente alle istruzioni di seguito
riportate.
------------------------------------------------------------------------------
08.01. If
------------------------------------------------------------------------------
L'istruzione "if" ha le stesse funzioni degli altri linguaggi. Puo' avere
tre forme di base:
if (expression)
statement
if (expression)
statement1
else
statement2
if (expression1)
statement1
else if (expression2)
statement2
else
statement3
Ad esempio:
int x,y,z;
main()
{
int w;
...
if (x<0)
{
z=w;
...
}
else
{
z=y;
...
}
}
------------------------------------------------------------------------------
08.02. Operatore "?"
------------------------------------------------------------------------------
L' operatore ? (ternary condition) e' la forma piu' efficente per esprimere
semplici if statements. Ha la seguente forma:
expression1 ? expression2 : exprssion3
che equivale a:
if expression1 then expression2 else expression3
Ad esempio:
z=(a>b) ? a : b
cioe'
if (a>b)
z=a;
else
z=b;
assegna a z il massimo tra a e b.
------------------------------------------------------------------------------
08.03. Switch
------------------------------------------------------------------------------
Permette scelte multiple tra un insieme di items.
La sua forma generale e':
switch (expression) {
case item1:
statement1;
break;
case item2:
statement2;
break;
.
.
.
case itemn:
statementn;
break;
case default:
statement;
break;
}
Il valore degli item deve essere una costante (le variabili non sono
permesse).
Il break serve per terminare lo switch dopo l'esecuzione di una scelta,
altrimenti verra' valutato anche il caso successivo (questo, a differenza
di molti altri linguaggi).
E' possibile anche avere un'istruzione nulla, includendo solamente un ";"
oppure lasciando fallire l'istruzione di switch omettendo qualsiasi frase
(come nell'esempio di seguito).
Il caso "default" e' facoltativo e raggruppa tutti gli altri casi.
Ad esempio:
switch (letter) {
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
numerovocali++;
break;
case " ":
numerospazi++;
break;
default:
numerocostanti++;
break;
}
In questo caso se letter e' una vocale ('A','E','I','O','U') viene
incrementato il valore della varibile numerovocali, se e' uno spazio (" ")
si incrementa numerospazi e altrimenti (se nessuno dei casi precedenti e'
vero) viene eseguita la condizione di default e quindi viene incrementato
numerocostanti.
------------------------------------------------------------------------------
08.04. For
------------------------------------------------------------------------------
L'istruzione C "for" ha la seguente forma:
for (expression1; expression2; expression3)
statement;
{or block of statements}
dove expression1 inizializza, expression2 e' il test di termine e
expression3 e' il modificatore (che puo' anche essere piu' di un semplice
incremento).
Nota: fondamentalmente il C tratta le istruzioni "for" come i cicli di
tipo "while".
Ad esempio:
int x;
main()
{
for (i=0;i<3;i++)
printf("x=%dn",x);
}
che genera come output sullo schermo:
x=0
x=1
x=2
Gli esempi che seguono sono tre forme valide delle istruzioni "for" in C:
for (x=0;((x<3)&&(x>9));x++)
for (x=0,y=4;((x<3)&&(y>9));x++,y+=2)
in cui si puo' notare che le espressioni multiple possono essere separate
da una ",";
for (x=0,y=4,z=4000;z;z/=10)
in cui si puo' notare che il loop continua l'iterazione fino a quanto z
diventa 0.
------------------------------------------------------------------------------
08.05. While
------------------------------------------------------------------------------
L'istruzione "while" ha la seguente forma:
while (expression)
statement;
Ad esempio:
int x=3;
main()
{
while (x>0)
{
printf("x=%dn",x);
x--;
}
}
che genera come output sullo schermo:
x=3
x=2
x=1
While puo' accettare non solo condizioni ma anche espressioni, per cui
risultano corrette le seguenti istruzioni:
while (x-);
while (x=x+1);
while (x+=5);
Utilizzando questo tipo di espressioni, solo quando il risultato di x--,
x=x+1 oppure x+=5 ha valore 0 la condizione di while fallisce e si esce
dal loop.
E' possibile avere anche complete operazioni di esecuzione nelle espressioni
"while":
while (i++<10)
che incrementa i fino a raggiungere il valore 10;
while ((ch=getchar())!'q')
putchar(ch);
che usa le funzioni getchar() e putchar() delle librerie standard, che
rispettivamente leggono un carattere dalla tastiera e scrivono un
determinato carattere sullo schermo. Il loop while continua a leggere dalla
tastiera e a visualizzare sullo schermo il carattere digitato, fino a quando
non venga battuto il carattere "q".
------------------------------------------------------------------------------
08.06. Do-While
------------------------------------------------------------------------------
L'istruzione C "do-while" ha la seguente forma:
do
statement;
while (expression);
(e' simile al Pascal repeat ... until, eccetto il fatto che l'espressione
dell'istruzione do-while e' vera)
Ad esempio:
int x=3;
main()
{
do { /* le graffe sono superflue, visto */
printf("x=%dn",x-); /* che racchiudono solamente una */
} /* istruzione */
while (x>0);
}
Il cui output e':
x=3
x=2
x=1
Nota: l'operatore finale "x-" indica che viene usato il valore corrente
di x mentre stampa, e poi viene decrementato x.
------------------------------------------------------------------------------
08.07. Break e Continue
------------------------------------------------------------------------------
Il C fornisce due comandi per controllare i loop:
break - esce da un loop o da uno switch
continue - salta una iterazione del loop
Consideriamo il seguente esempio, dove leggiamo un valore integer e lo
elaboriamo in accordo con le seguenti condizioni. Se il valore che abbiamo
letto e' negativo, dovremo stampare un messaggio di errore ed abbandonare
il loop. Se il valore letto e' maggiore di 100, dovremo ignorarlo e
continuare con il successivo valore in input. Se il valore e' 0, dovremo
terminare il loop.
/* Viene letto un valore intero ed elaborato purche'
sia maggiore di 0 e minore di 100 */
while (scanf("%d".&value) == 1 && value !=0) {
if (value<0) {
printf("Valore non ammesson");
break; /* Abbandona il loop */
}
if (value>100) {
printf("Valore non ammesson");
continue; /Torna nuovamente all'inizio del loop */
}
/*Elabora il valore letto*/
/*che e' sicuramente tra 0 e 100 */
.
.
.
}
------------------------------------------------------------------------------
09. Arrays
------------------------------------------------------------------------------
------------------------------------------------------------------------------
09.01. Array singoli e multidimensionali
------------------------------------------------------------------------------
Un esempio di definizione di un array in C e' :
int elenco_numeri[50];
e si accede agli elementi dell'array nel seguente modo:
terzo_numero= elenco_numeri[2];
elenco_numeri[5]=100;
NB. In C gli Array subscripts iniziano da 0 e finiscono alla dimensione
dell'array meno uno. Nell'esempio precedente il range e' 0-49, cioe'
elenco_numeri e' un array di 50 elementi e si ha:
elenco_numeri[0],elenco_numeri[1],....elenco_numeri[49].
Questa e' una grossa differenza fra il C e gli altri linguaggi e
richiede un po' di pratica per raggiungere "la giusta disposizione
d'animo".
Array multidimensionali sono cosi definiti:
int tabella_numeri[50][50] => per due dimensioni
int big_D[20][30][10][40] => per piu' di due dimensioni
e si accede agli elementi nel seguente modo:
numero=tabella_numeri[5][32];
tabella_numeri[1][23]=100;
------------------------------------------------------------------------------
09.02. Stringhe
------------------------------------------------------------------------------
In C le stringhe sono definite come array di caratteri. Ad esempio, la
seguente istruzione definisce una stringa di 50 caratteri:
char name[50];
Il C non ha pero' un sistema maneggevole per costruire le stringhe, cosi
le seguenti assegnazioni non sono valide:
char firstname[50], lastname[50], fullname[50];
firstname = "Mario" /* illegale */
lastname = "Rossi" /* illegale */
fullname = "Sig."+firstname+lastname /* illegale */
Esiste pero' una libreria di routines per il trattamento delle stringhe
("< string.h >").
Per maneggiare le stringhe si possono usare puntatori ad array di char
(come vedremo piu' avanti).
Per stampare una stringa si usa printf() con lo speciale carattere di
controllo %s:
printf("%s",nome);
Nota: e' sufficiente avere il nome della stringa.
Al fine di permettere l'utilizzo di stringhe con lunghezza variabile,
il carattere
|