Capitolo precedente. Indice del capitolo. Indice analitico globale. Capitolo successivo.

Capitolo 1

Introduzione generale

1.1 Il C

Il linguaggio di programmazione C è nato nei Bell Laboratories, ad opera di Dennis Ritchie, come sostitutivo dell'assembler per la scrittura di sistemi operativi e riflette molte delle esigenze di chi deve operare a "basso livello", come la possibilità di operare sugli indirizzi e sui bit. Le prime implementazioni del C sono del 1972 ed è stato utilizzato per scrivere il sistema operativo Unix. Seppur nato per sviluppare software di base, il linguaggio offre tutte le caratteristiche di ogni linguaggio imperativo ad alto livello, come il Pascal. Infatti dispone dei dati tipizzati, dispone di istruzioni di controllo sofisticate, mette a disposizione operatori semplici e potenti. e permette di costruire programmi modulari. La definizione precisa del linguaggio è avvenuta nel 1978 con la pubblicazione del libro The C Programming Language ad opera di Brian W. Kernighan e di Dennis M. Ritchie. La nuova definizione del linguaggio C è del 1988 e riflette ciò che ha stabilito l'Istituto Nazionale Americano per gli Standard (ANSI) ed è quella che verrà presa in esame in questo libro.

1.2 Sintassi

Un programma in C è una sequenza di caratteri che rispettano le regole definite dalla sintassi. Gli identificatori sono sequenze di caratteri, maiuscoli o minuscoli, e di numeri. È possibile utilizzare anche il carattere _ chiamato "underscore". È importante sottolineare come, a differenza del Pascal, esiste differenza tra carattere maiuscolo e minuscolo negli identificatori. Ad esempio l'identificatore Alfa è differente da alfa. Le parole riservate in C sono molto poche e verranno presentate man mano che si introdurrà il linguaggio. I commenti in un programma sono sequenze di caratteri racchiuse tra i simboli /* e */. Ad esempio

/* Questo è un commento */
Oppure
/*

Il commento può

essere scritto su

più righe

*/

1.3 Variabili e costanti.

Come in Pascal, le variabili devono essere dichiarate prima del loro utilizzo. La dichiarazione avviene indicando il nome della variabile e il tipo. La prima informazione viene utilizzata per riferirsi alla veriabile stessa nel corso del programma mentre la seconda serve al traduttore ( il compilatore) per riservare lo spazio di memoria e per determinare il tipo di operazioni possibili.

I tipi di dato presenti in C sono:

char short int int long int
unsigned char unsigned short int unsigned int unsigned long int
float double --- ---

In questa tabella compaiono alcune delle parole riservate che non possono essere utilizzate come identificatori. Il loro significatoè il seguente:

short int (abbreviato in short ): si indica il tipo intero con rappresentazione in complemento a due a 16 bit; una costante di tipo short è un numero seguito dalla lettera s,come ad esempio 12s;

long int (abbreviato in long ): si indica il tipo intero con rappresentazione in complemento a due a 32 bit; una costante di tipo long è un numero seguito dalla lettera l, come ad esempio 12L;

int: si indica il tipo intero in complemento a due che, a seconda della versione C utilizzata, può essere a 16 o a 32 bit. È il tipo dato più utilizzato. Una costante di tipo int è una sequenza di numeri che non deve però superare le capacità rappresentative del tipo int, ad esempio -12;

unsigned: si indica un dato senza segno;

char : è il carattere, rappresentato in genere in ascii. Una costante di tipo char è un qualsiasi carattere racchiuso da apici, ad esempio 'a'; le costamti di tipo char possono essere dichiarate anche in base alla rappresentazione ottale premettendo al valore il carattere "backslash". Ad esempio '\7' rappresenta il bell mentre '\0' il null;

unsigned char si considerano tutte le 256 combinazioni possibili, da 0 a 255.

double : è un numero rappresentato in virgola mobile; una costante di tipo double è un numero dove compare il carattere <punto> , come ad esempio 12.3; tutte le costanti dove compare il punto, o scritte in notazione scientifica ( 1e3 ) sono di tipo double; in genere il compilatore utilizza più spazio per le variabili di tipo double rispetto alle variabili di tipo float , per cui la precisione di calcolo risulta superiore.

Alcuni esempi di dichiarazione sono

  int i,j,k;
  char a;
  unsigned long ui;
  float f

Una variabile può essere dichiarata e contemporaneamente inizializzata con un particolare valore:

  int a=3, b=5;
  char c='c';

1.4 Operazioni.

1.4.1 Operazioni aritmetiche binarie.

Gli operatori aritmetici binari sono:

+ addizione
- sottrazione
* moltiplicazione
/ divisione
% resto della divisione tra interi

Una espressione è una sequenza di costanti,variabili, operatori e chiamate di funzioni che in genere determina un risultato. Alcuni esempi sono

  3
  a + b
  3 * x
  2+sin( 3*x)
  5 % 2

Il risultato di un'espressione dipende dal tipo dellevariabili che compaiono nell'espressione stessa. Se x e y sono di tipo int allora x+y è ancora di tipo int. Ma se x e y sono di tipo short o di tipo char, prima della somma vengono implicitamente convertitti in int e quindi il risultato sarà di tipo int. I valori di x e y non vengono comunque modificati ma viene effettuata una copia temporanea con cui effettuare i calcoli.

Quando in un'espressione compaiono variabili di tipo diverso allora le cose cambiano. La conversione implicita avviene seguendo le seguenti regole:

1. tutte le variabili di tipo char o short vengono trasformate in tipo int. Le variabili di tipo unsigned char o unsigned short vengono trasormate in unsigned int.

2. se dopo il primo passo l'espressione contiene dati di tipo differente, tutti i dati vengono convertiti in base alla seguente gerarchia

int unsigned long unsigned long float double

Questa operazione di modifica dei dati viene chiamata conversione implicita.

1.4.2 Assegnazione.

L'operazione comune a tutti i tipi di variabile visti inprecedenza è l' assegnazione , indicata con =. Con l'assegnazione si assegna ad una variabile il risultato di un'espressione. L'assegnazione in C è a tutti gli effetti un'operazione che produce un risultato: il risultato è il dato che viene trasferito nella variabile. Ad esempio con

  a=3;

si inizializza la variabile a con 3; il risultatodell'operazione è 3. In questo modo sono possibili assegnazioni multiple come la seguente

  a=b=c=d=3;

In questo caso le variabili a, b, c sono inizializzate tutte con 3. La valutazione delle operazioni da svolgere avviene da destra a sinistra. La prima operazione è l'assegnazione d=3 che produce come risultato 3; tale risultato viene assegnato alla variabile c, producendo nuovamente come risultato 3, che viene assegnato alla variabile b, e così via.

Esempi. Sia

   int a, b, c;

   double x, y;

Con

   a=5; b=2;

si assegna ad a il valore 5, b il valore 2;

con

   c=a/b;

si assegna a c il valore 2. Lo stesso risultato si ha se si scrive

   x=a / b;

Se si scrive

   x = 2.1 + 5 / 2;

il risultato è 4.1. Se si scrive

   x = 2 .1 + 5.0 / 2

il risultato è 4.6

1.4.3 Operazioni di incremento e di decremeto.

Le operazioni di incremento e decremento possono essere realizzate attraverso due operatori unari: ++ e -- . Hanno la stessa precedenza e possono essere applicati solo a variabili.

Sia

   int a;

Le operazioni

   a++;

e

   ++a

sono equivalenti all'operazione

   a = a + 1;

In situazioni semplici scrivere a++ o ++a è indifferernte. Le cose cambiano quando il risultato di tale operazione è utilizzato all'interno di un'altra espressione. Ad esempio sia

   int a, b=0, c=0;

Se si scrive

   a = b++ + c++;

avremo a = 0, b = 1, c = 1.

Se si scrive

   a = ++b + ++c;

avremo a=2, b=1, c=1. Questo perchè con l'operatore ++ premesso alla variabile si richiede di incrementarne il contenuto prima di utilizzarla mentre con la variabile premessa all'operatore ++ si richiede di utilizzarne il contenuto e solo dopo incrementarla.

1.4.4 Operatori logici.

Gli operatori logici sono utilizzati nelle espressioni e forniscono come risultato un int di valore 0 per falso e 1 per vero. Gli operatori sono:

operatori relazionali

< minore

> maggiore

<= minore uguale

>= maggiore uguale

operatori di uguaglianza == uguale

!= diverso

operatori logici ! negazione

&& e

|| o

L'unico operatore unario è la negazione (!); tutti glialtri sono binari.

Esempi.

Sia

char c='w';

int i=0, j=1, k=2; m=1;

double x=0.001, y=0.002;

Per illustrare le precedenze realizziamo la seguente tabella

Espressione

Espressione equivalente

Risultato

'b'+1<c

('b'+1)<c

1

x<x+y

x<(x+y)

1

3<k<5

(3<k)<5

1

i!=j

!(i==j)

1

!!5

!(!5)

1

!5-3

(!5)-3

-3

x/!y

x/(!y)

errore

i && j && k

(i && j) && k

0

x && i || k

(x && i) || k

1

Anche se in C è possibile comporre espressioniutilizzando sia operatori logici che aritmetici, non necessariamente si deveoperare così, soprattutto se si è alla prima esperienza con il linguaggio.

1.5 Istruzioni.

1.5.1 Istruzione composta.

L'istruzione composta è una sequenza di istruzioni racchiusa tra due parentesi grafe. Alcuni esempi di istruzioni composte sono:
{
   a = 1;
   b = 2;
}
oppure
{
   a = 1;
   b = 2;
   {
      c = 3;
      d = 4;
   }
}

Le istruzioni sono separate le une dalle altre dal carattere puntovirgola ( ; ).

1.5.2 Istruzione vuota.

La si utilizza quando la sintassi richiede un'istruzione ma non è richiesta alcuna azione. L'istruzione vuota è rappresentata da un singolo ; (puntovirgola).

1.5.3 Istruzione condizionale.

istruzione if: ha la forma

   if ( espressione )

      istruzione;

dove istruzione può anche essere un'istruzione composta.

Se il risultato di espressione è un valore diverso da 0 allora viene eseguita istruzione.

istuzione if - else: ha la forma

   if ( espressione )

      istruzione1;

   else

      istuzione2;

dove sia istruzone1 che istruzione2 possono anche essere istruzioni composte.

Se espressione ha come risultato un valore diverso da 0 viene eseguta l'istuzione 1 altrimenti viene eseguita l'istruzione 2.

Esempi.

1. Assegnare a min il valore minore tra a e b.

if( a<=b ) min=a;

else min=b;

2. Determinare il maggiore tra tre numeri.

   if(a>b ) max=a;

   else max=b;

   if( max< c ) max=c;

3. Dati due numeri max e min ordinarli.

   if( max<min ) { tmp=max; max=min; min=tmp }

4. Istruzione probabilmente inutile:

   if( a<b );

1.5.4 Istruzioni di ciclo.

Ci sono tre istruzioni di ciclo:

istruzione while : ha la forma

   while( espressione )

      istruzione;

dove istruzione può essere un'istruzione composta.

Mentre espressione è diversa da zero viene eseguita istruzione.

Esempi.

1. Eseguire una sequenza di azioni 5 volte;

   int n=5;

   while( n-- >0 ) {

      /* esegui azioni */

   }

oppure

   int n=5;

   while( n-- ) {

      /* esegui azioni */

   }

2. Scrivere un ciclo infinito.

   while( 1 )

      /* azione */

istruzione do : ha la forma

   do

      istruzione

   while( espressione );

dove istruzione può essere un'istruzione composta. È una variante dell'istruzione while dove il test di controllo viene eseguito dopo le istruzioni. Le istruzioni vengono ripetute se l'espressione è diversa da zero.

Spesso per semplificare la lettura del codice, anche se l'istruzione è singola si scrive:

   do {

      istruzione;

   } while( espressione );

Esempio: ripetere un ciclo 5 volte.

   int n=5;

   do {

      /* istruzioni */

   } while( --n );

istruzione for : ha la forma

   for( espressione1; espressione2; espressione3 )

      istruzione;

dove istruzione può essere anche l'istruzione composta.

Questo ciclo è equivalenteal seguente:

   espressione1;

   while( espressione2 ) {

      istruzioni;

      espressione3;

   }

Espressione1 rappresenta l'operazione che deve essere effettuata prima del ciclo; espressione2 è il controllo che viene effettuato per decidere se eseguire le istruzioni del ciclo: se il risultato è diverso da zero il ciclo viene eseguito. Espressione3 rappresenta le operazioni che vengono eseguite dopo l'escuzione di istruzioni.

Ad esempio per ripetere 5 volte un'istruzione si scrive:

   for(i=1; i<=5; i++)

      /* istruzioni del ciclo */

In un ciclo for tutte e tre le espressioni possono anche essere l'istruzione vuota. Con un ciclo for possiamo simulare anche un ciclo while nel seguente modo:

   for( ; espressione; )

Per avere una ciclo infinito si scrive

   for(; 1 ;)

Esempi.

1. Ripetere un ciclo n volte.

   for( i=0; i<n; i++) {

      istruzioni;

   }

oppure

   for(; --n; ) {

      istruzioni;

   }

2. Sommare i numeri compresi tra m ed n, con m minore di n.

   somma=0;

   for( i=m; i<=n; i++) somma=somma+i;

1.6 L'operatore virgola.

L'operatore virgola è tra tutti gli operatori in C quello che ha precedenza minore. È un operatore binario che serve per separare espressioni che verranno eseguite da sinistra a destra. Il risultato dell'operatore virgola è del tipo dell'ultima operazione. Viene spesso utilizzato in altre istruzioni. Ad esempio per sommare i numeri compresi tra m ed n possiamo scrivere:

   for( somma=0, i=m; i<=n; somma=somma+i, i++);

1.7 L'operatore indirizzo.

In alcune operazioni potrebbe essere necessario conoscere non il contenuto di una variabile ma dove si trova, il suo indirizzo. In C esiste un operatore molto importante e che non compare nella maggior parte degli altri linguaggi: l'operatore &. È un operatore unario che, applicato ad una variabile restituisce l'indirizzo della variabile stessa.

Esempio.

   int a;

&a restituisce l'indirizzo della variabile a.

1.8 Operazioni di ingresso/uscita.

Il linguggio C non è dotato di istruzioni di ingresso/uscita dei dati. In un linguaggio di programmazione non sono necessarie perchè è sempre possibile sfruttare il sistema operativo , cioè quell'insieme di programmi e di funzioni che permettono ad ogni elaboratore di funzionare e che necessariamente mette sempre a disposizione, tra le altre cose, delle funzioni per l'ingresso/uscita dei dati dai dispositivi dell'elaboratore.

Per semplificare la vita ai programmatori vengono però fornite, insieme al linguaggio di programmazione, una serie di estensioni, diverse per oni sistema operativo, che aggiungono al linguaggio stesso nuove possibilità. Ritorneremo più avanti su questi concetti, chiarendoli meglio ed approfondentoli. Per ora ci accontentiamo di introdurre due funzioni, comuni a tutte le implementazioni del linguaggio C, che permettono l'ingresso e l'uscita dei dati: scanf() e printf().

L'introduzione che faremo non sarà per ora completa e precisa ma ci permetterà di incominciare a scrivere i primi programmi in C.

1.8.1 printf.

La sintassi di printf è la seguente:

   printf("formato", elenco variabili);

dove con formato si deve indicare il tipo di dato da stampare. Ad esempio

   printf("Il risultato è %d", area);

Il carattere '%' nella stringa di formato indica che ciò che segue indicherà il tipo del dato da stampare.

d indica un int. Ogni altro carattere che compare nella stringa di formato viene visualizzato così come incontrato.

1.8.2 scanf.

La sintassi di scanf è la seguente:

scanf("formato", indirizzo delle variabili dove inserire i dato);

dove con formato si indica il tipo di dato da leggere.

Nell'operazione di scanf non si deve indicare la variabile in cui inserire i dati ma l'indirizzo della variabile.

Alcuni possibili caratteri di formato sono:

carattere di conversione significato esempio
d int -12
u unsigned int 43
l long

123456

e floating con esponente -3.0034e5
f floating senza esponente 234.004
c char c

Esempi.

1. Sia

   int a;

   double b;

   char c;

Con

   scanf("%d %e %c", &a, &b, &c);

Si leggono tre valori, il primo intero, il secondo double, il terzo char e si inseriscono in a, b, c.

2. Sempre con la definizione di prima con

   printf("Intero: %d \nDouble: %e \nChar: %c\n", a,b,c);

viene visualizzato il contenuto di a, b, c su tre linee diverse con un commento. L'indicazione per cambiare linea è data dal carattere \n.

3.  int i=6;

   printf("%d", i=7);

viene stampato il valore 7. Perchè?

1.9 Il programma.

Dobbiamo ancora affrontare il problema di come si combinano le istruzioni e le dichiarazioni delle variabili per realizzare un programma in C. In prima approssimazione diciamo che un programma in C è un insieme di dichiarazioni e di istruzioni racchiuso tra due parentesi grafe( istruzione composta ) e preceduto dalla parola chiave main(). Ogni variabile deve essere dichiarata prima dell'utilizzo. La dichiarazione avviene all'interno di una istruzione composta.

Nelle prossime pagine definiremo meglio il significato di main. Per ora ci accontenteremo di questa definizione che ci permette di incominciare a scrivere i primi programmi in C.

Esempi di programmi in C sono:

1. Calcolare la potenza n-esima di un numero.

2. Dati n numeri contare i pari e dispari.

3. Contare le vocali e le consonanti

4. Dati n numeri determinare il massimo

5. Determinare l'errore relativo.

1.10 Scelta multipla.

istruzione switch ha la forma:

   switch( espressione ) {

   case costante_1 : istruzione_1

   case costante_2 : istruzione_2

   case ...  : ......

   case costante_n : istruzione_n

   default : istruzioni

   }

è un'istruzione di scelta plurima; viene controllato se il valore di espressione è uguale ad una serie di costanti intere e si eseguono le istruzioni a partire da quell'etichetta; le costanti vengono infatti utilizzate come etichette. Se il valore di espressione non corrisponde a nessuna costante vengono eseguite le istruzioni a partire dall'etichetta default .

6. scrivere un programma che conti le diverse vocali.

In questo esempio compare l'istruzione break ; questa istruzione provoca l'uscita dall'istruzione switch non appena viene eseguita una istruzione associata ad una costante case. In caso contrario sarebbero incrementati tutti i contatori a partire dalla costante trovata.

1.11 Operazioni con i BIT

In C ci sono 6 operatori che permettono di manipolare i bit delle variabili di tipo char, short, int, long sia signed che unsigned. Tali operatori sono:

& AND tra bit
| OR tra bit
^ XOR tra bit
<< shift a sinistra
>> shift a destra
~ complemento ad 1

Bisogna distingure tra gli operatori logici e gli operatori sui bit. Per esempio se x =1, y=2 allora

x && y ha come risultato 1, poichè sia x che y sono diversi da 0
x & y ha come risultato 0

<< e >> producono lo shift rispettivamente a sinistra e a destra, introducendo degli 0. Ad esempio:

L'operatore ~ provoca il complemento ad 1 del valore . Significa che tutti i bit a 1 vengono posti a 0 e viceversa.

1.12 Operazioni di assegnazione

La maggior parte degli operatori binari ha un corrispondente operatore di assegnazione, che si ottiene aggiungendo il simbolo = dietro all'operatore. Ad esempio

i += 3 significa incrementare i di 3;
i *= 2 significa moltiplicare i per 2;
i <<3 significa effettuare lo shift a sinistra di 3;
In generale oper=, dove oper è

+ - * / % << >> & ^ |

è un operatore di assegnazione. E' importante notare che

espr1 oper= espr2
è equivalente a:
espr1 = (espr1) op (espr2);
Questo significa che ad esempio:
a *= b +1;
significa:
a = a * (b + 1);
e non
a = a * b + 1;
ÿ