il linguaggio c - people.ding.unisannio.it

138
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

Upload: others

Post on 07-Dec-2021

5 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Il linguaggio C - people.ding.unisannio.it

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

Page 2: Il linguaggio C - people.ding.unisannio.it

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.

Page 3: Il linguaggio C - people.ding.unisannio.it

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)

Page 4: Il linguaggio C - people.ding.unisannio.it

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.

Page 5: Il linguaggio C - people.ding.unisannio.it

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);

}

Page 6: Il linguaggio C - people.ding.unisannio.it

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

Page 7: Il linguaggio C - people.ding.unisannio.it

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.

Page 8: Il linguaggio C - people.ding.unisannio.it

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.

Page 9: Il linguaggio C - people.ding.unisannio.it

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 .

Page 10: Il linguaggio C - people.ding.unisannio.it

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:

Page 11: Il linguaggio C - people.ding.unisannio.it

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

Page 12: Il linguaggio C - people.ding.unisannio.it

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.

Page 13: Il linguaggio C - people.ding.unisannio.it

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

Page 14: Il linguaggio C - people.ding.unisannio.it

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

Page 15: Il linguaggio C - people.ding.unisannio.it

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 .

Page 16: Il linguaggio C - people.ding.unisannio.it

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.

Page 17: Il linguaggio C - people.ding.unisannio.it

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.

Page 18: Il linguaggio C - people.ding.unisannio.it

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;

}

Page 19: Il linguaggio C - people.ding.unisannio.it

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.

Page 20: Il linguaggio C - people.ding.unisannio.it

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.

Page 21: Il linguaggio C - people.ding.unisannio.it

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.

Page 22: Il linguaggio C - people.ding.unisannio.it

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

Page 23: Il linguaggio C - people.ding.unisannio.it

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++

Page 24: Il linguaggio C - people.ding.unisannio.it

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;

Page 25: Il linguaggio C - people.ding.unisannio.it

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)

Page 26: Il linguaggio C - people.ding.unisannio.it

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

Page 27: Il linguaggio C - people.ding.unisannio.it

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

Page 28: Il linguaggio C - people.ding.unisannio.it

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.

Page 29: Il linguaggio C - people.ding.unisannio.it

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;

Page 30: Il linguaggio C - people.ding.unisannio.it

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")

}

Page 31: Il linguaggio C - people.ding.unisannio.it

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.

Page 32: Il linguaggio C - people.ding.unisannio.it

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.

Page 33: Il linguaggio C - people.ding.unisannio.it

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.

Page 34: Il linguaggio C - people.ding.unisannio.it

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.

Page 35: Il linguaggio C - people.ding.unisannio.it

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 */

}

Page 36: Il linguaggio C - people.ding.unisannio.it

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

var.c.

/* file var.c */

int A=1;

static int C;

#include <stdio.h> /* file main.c */

extern int A; extern int C;

void f(int c){ printf("c=%d\n",c); }

void main(void) {

f(A); /*corretto */

f(B); /*error C2065: 'B' : undeclared identifier*/

f(C); /*error LNK2001:unresolved external symbol _C*/

}

Il linguaggio C 72/243

Problemi tipici in programmi con più

moduli

Il modulo main.c contiene due errori, perchè:

1) con l'istruzione f(C) main tenta di accedere alla variabile C

che non può vedere perchè è protetta dallo specificatore static

che la rende visibile solo dentro var.c.

Il compilatore non si accorge dell'errore perchè main.c ha

una dichiarazione extern per C, e il compilatore si fida e fa

finta che C esista e sia accedibile in un qualche altro modulo.

Il linker invece, che deve far tornare i conti, non riesce a

rintracciare una variabile C accedibile, e segnala l'errore

indicando un "error LNK2001: unresolved external symbol"

perchè non trova C.

Page 37: Il linguaggio C - people.ding.unisannio.it

37

Il linguaggio C 73/243

Problemi tipici in programmi con più

moduli

2) con l'istruzione f(B) main tenta di accedere alla variabile B

che non è definita nel modulo main.c, nemmeno da una

dichiarazione extern.

il compilatore si accorge dell'errore e lo segnala con il

messaggio "error C2065: 'B' : undeclared identifier".

Il linguaggio C 74/243

Lo specificatore static

Lo specificatore static, applicato ad una variabile locale ordina al

compilatore di collocare la variabile non più nello stack all'atto della chiamata

alla funzione, ma in una locazione di memoria permanente (per tutta la durata

del programma), come se fosse una variabile globale.

A differenza delle variabile globali, la variabile locale static sarà visibile solo

all'interno del blocco in cui è stata dichiarata.

L'effetto è che la variabile locale static:

• viene inizializzata una sola volta, la prima volta che la funzione viene

chiamata.

• mantiene il valore assunto anche dopo che il controllo è uscito dalla

funzione, e fino a che non viene di nuovo chiamata la stessa funzione.

Page 38: Il linguaggio C - people.ding.unisannio.it

38

Il linguaggio C 75/243

Lo specificatore static

Vediamo un esempio di utilizzo, per contare il numero delle volte

che una data funzione viene eseguita.

#include <stdio.h> /* file contaf.c */

void f(void) {

/* viene inizializzato solo una volta */static int contatore=0;

contatore = contatore + 1;

printf("contatore =%d\n", contatore);

}

void main() { /* per vedere cosa succede in f*/

int i;

for( i=0; i<100; i++ )

f();

}

Il linguaggio C 76/243

Istruzioni di assegnamento

L'operatore di assegnamento in C, come in Java, è =, mentre in

Pascal è := .

Quindi l'istruzione di assegnamento diventa:

NomeVariabile = espressione;

dove espressione può essere semplice come una singola

costante, o essere una combinazione di variabili, operatori, funzioni e costanti. NomeVariabile deve essere una variabile, e mai una

funzione.

Page 39: Il linguaggio C - people.ding.unisannio.it

39

Il linguaggio C 77/243

Istruzioni di assegnamento

Esempi:

int func( float f); /* prototipo */

int i , j;

i = 0; corretta

i = func(3.8); corretta

j = i ; corretta

func( 3.8 ) = 100; NON AMMESSA, errore

in compilazione

Il linguaggio C 78/243

AssegnamentoL'assegnamento in C produce un risultato, che è il valore

assegnato alla variabile a sinistra dell'uguale, ed è del tipo della

variabile. Tale risultato può essere utilizzato:

•per un ulteriore assegnamento, tenendolo alla destra di un uguale,

valgono perciò istruzioni composite del tipo:

int i, j , k ;

k = j = i = 10;

che assegnano alla variabile a sinistra il valore assegnato alla

variabile subito a destra, cioè si assegna prima il valore 10 ad i,

poi dato che i vale 10 si assegna il valore 10 a j, poi visto che j

vale 10 si assegna 10 a k. Il vantaggio è un'esecuzione più veloce

delle istruzioni di assegnamento separate, perchè il dato da

assegnare è già caricato sui registri.

Page 40: Il linguaggio C - people.ding.unisannio.it

40

Il linguaggio C 79/243

Assegnamento

•come espressione ad esempio come condizione in un if,

eventualmente tenendolo dentro parentesi per evitare confusioni

if ( i=10 )

func(9);

i=10 vale 10 che è diverso da zero e vale VERO

Il linguaggio C 80/243

Conversioni di tipo negli assegnamenti

void main(void) {

int i,j; double g,x;

x = i = g = 10.3;

printf("x=%f i=%d g=%f\n",x, i, g );

}

Tale programma stampa la stringa: x=10.0 i=10 g=10.3

Page 41: Il linguaggio C - people.ding.unisannio.it

41

Il linguaggio C 81/243

Conversioni di tipo negli assegnamenti

void main(void) {

int i,j; double g,x;

i = x = g = 10.3;

printf("x=%f i=%d g=%f\n",x, i, g);

}

Tale programma stampa la stringa: x=10.3 i=10 g=10.3

Il linguaggio C 82/243

Conversioni di tipo negli assegnamenti

Valutiamo cosa succede nell'assegnamento multiplo del primo

programma.

L'istruzione di assegnamento g=10.3; assegna un valore floating

point 10.3 alla variabile floating point g, e 10.3 è il risultato

dell'assegnamento più a destra.

Tale risultato (10.3) viene assegnato ad i che però è una variabile

intera, e quindi viene persa la parte decimale e ad i viene assegnato

il valore 10, che è il risultato dell'assegnamento ad i.

Page 42: Il linguaggio C - people.ding.unisannio.it

42

Il linguaggio C 83/243

Conversioni di tipo negli assegnamenti

Questo risultato (10) viene quindi assegnato a x che lo memorizza

come floating point 10.0 , che è il risultato dell'assegnamento ad x,

ed è anche il risultato finale di tutto l'assegnamento multiplo.

Nell'assegnamento multiplo del programma a destra, invece,

l'assegnamento ad x viene fatto dandogli il valore ottenuto

dall'assegnamento di 10.3 a g che è floating point, e quindi

memorizza 10.3.

N.B. In entrambi i casi il compilatore si accorge che c'e' una

perdita di dati nel passaggio da double a int e avverte il

programmatore con un warning di questo tipo:

warning: '=' : conversion from 'double ' to 'int ', possible loss of

data

Il linguaggio C 84/243

Conversioni di tipo nelle istruzioni di

assegnamento

Il problema delle conversioni di tipo esiste quando l'assegnamento

coinvolge variabili (o una variabile ed una costante) di tipo

diverso. La regola di conversione che vale in C è la seguente:

Il C converte il valore alla destra del segno di uguale (il risultato

della valutazione dell'espressione) nel tipo del lato sinistro (la

variabile destinazione dell'assegnamento).

Page 43: Il linguaggio C - people.ding.unisannio.it

43

Il linguaggio C 85/243

Conversioni di tipo nelle istruzioni di

assegnamento

Tipo

Destinazione

Tipo

Espressione

Possibile Perdita

di Informazione

Informazione

Conservata

unsigned char char se valore è > 127 7 bit meno significativi

char short int byte + significativo byte - significativo

char long int (4 byte) 3 byte + significativi byte - significativo

short int long int 2 byte + significativi byte - significativo

short int float, double almeno la parte frazionaria

la parte intera se minore del valore

massimo per short int, un valore

senza senso altrimenti

float double arrotondamento del risultatorisultato arrotondato alla

precisione del float

Il linguaggio C 86/243

Conversioni

Vediamo un esempio, conversione di uno short int in un char:

short int i; char ch; ch = i ;

byte più significativo byte meno significativo

short int i

char ch

Page 44: Il linguaggio C - people.ding.unisannio.it

44

Il linguaggio C 87/243

Inizializzazione delle variabili

All'atto della dichiarazione di una variabile, è possibile assegnare un

valore alla variabile stessa, mediante un'estensione della

dichiarazione così fatta:

Tipo NomeVariabile = costante;

Questa inizializzazione si traduce in due fatti diversi a seconda della

collocazione della variabile che stiamo dichiarando:

• Se la variabile è una variabile globale, il valore viene assegnato

alla variabile prima di cominciare l'esecuzione del programma,

Il linguaggio C 88/243

Inizializzazione delle variabili

Se la variabile è una variabile locale l'inizializzazione viene

effettuata nella posizione dello stack in cui viene posizionata la

variabile locale quando si entra nel blocco a cui appartiene, e

questa inizializzazione viene ripetuta ogni volta che si entra in

quel blocco. Fa eccezione il caso in cui la variabile locale sia

preceduta dallo specificatore static, nel qual caso la variabile non

viene messa sullo stack ma in una zona dati, e l'inizializzazione

viene effettuata una ed una sola volta, nel momento in cui il

controllo entra per la prima volta nella funzione in cui la variabile

è stata dichiarata.

• Se la variabile è un parametro formale di una funzione, non

può essere inizializzata.

Page 45: Il linguaggio C - people.ding.unisannio.it

45

Il linguaggio C 89/243

Inizializzazione delle variabili

Esempi:

int i = 0; i inizializzato a zero

double f = 13.7; f inizializzato a 13.7

int j=2, k, m=3; j inizializzato a zero, k non inizializzato,

m inizializzato a 3

#define NUM 10

int n=NUM; n inizializzato a 10

int p=0xFF; p inizializzato a 255 (costante esadecimale)

int q=011; q inizializzato a 9 (costante ottale)

Il linguaggio C 90/243

CostantiLe costanti sono entità che non possono essere modificate durante l'esecuzione del

programma, ma mantengono invariato il loro valore per tutta la durata del programma.

Le costanti possono essere:

1. scritte direttamente nel programma scrivendo il valore che interessa. Ad es.

i = 1; f = 137.12; k = 0xFF;

2. indicate mediante un simbolo tramite una direttiva al preprocessore #define nel qual

caso il preprocessore, sostituendo al simbolo il valore, ci riporta al caso precedente,

Ad es. #define SIZE 10

int i = SIZE;

viene tradotto dal preprocessore in

int i = 10;

3. indicate mediante un simbolo (tramite la keyword const) che viene inizializzato al

valore che interessa e mantenuto in una locazione di memoria come una variabile, ma

che non può essere modificato.

Page 46: Il linguaggio C - people.ding.unisannio.it

46

Il linguaggio C 91/243

Costanti

La dichiarazione di un dato di tipo costante deve essere così:

const Tipo NomeVariabile = costante;

oppure

Tipo const NomeVariabile = costante;

Ad es. const int i = 10;

Dichiarare una costante mediante la const impone al compilatore di associare il simbolo

ad una certa locazione di memoria separata dal resto dei dati, e di mantenerne in questa

locazione il valore assegnato in via di inizializzazione.

Comunque, in tutti i 3 casi, resta sempre il problema di come scrivere un certo valore

costante (numerico intero, numerico in virgola mobile, stringa) all'interno in una certa

istruzione. Il formato varia al variare del tipo di dato.

Esiste inoltre un caso particolare di uso delle costanti che sarà descritto a proposito delle

funzioni e dei loro parametri formali, e che serve a dichiarare che un parametro formale

non è modificabile.

Il linguaggio C 92/243

Costanti numeriche intere

Le costanti numeriche intere: sono espresse da numeri senza

componente frazionaria, possono essere preceduti

eventualmente da un segno (+ o -), e possono essere espresse in

notazione decimale (base 10), esadecimale (base 16) o ottale

(base 8).

• In notazione decimale il numero è espresso nel modo consueto,

in notazione esadecimale il numero deve essere preceduta dal

segno 0x, in notazione ottale il numero deve essere preceduta da

uno 0.

• Per indicare il tipo di dato con cui rappresentare la costante, si

utilizzano dei caratteri da porre immediatamente dopo i caratteri

numerici.

Page 47: Il linguaggio C - people.ding.unisannio.it

47

Il linguaggio C 93/243

Costanti numeriche intere

• Per default le costanti intere sono considerate int. Se la costante

intera è troppo grande per essere contenuto in un int, viene

considerato long.

• Una costante unsigned int deve essere seguita dal carattere u o U.

es: 41234U

• Una costante long deve essere seguita dal carattere L o l.

es: 49761234L

• Una costante unsigned long deve essere seguita dai caratteri UL o

ul.

Il linguaggio C 94/243

Esempi:

Valore Tipo DECIMALE ESADEC. OTTALE

0 int 0 0x0 00

1 int 1 0x1 01

8 int 8 0x8 010

-8 int -8 -0x8 -010

17 int 17 0x11 021

-30 int -30 -0xFD 036

100000 long int100000L

100000l

186A0L

186A0l

-303240L

-3032040l

-100000 long int-100000L

-100000l

-186A0L

-186A0l

-303240L

-3032040l

Page 48: Il linguaggio C - people.ding.unisannio.it

48

Il linguaggio C 95/243

Costanti numeriche in virgola mobile

Le costanti numeriche in virgola mobile sono scritte in rappresentazione decimale nel

modo consueto, ovvero: un eventuale segno, la componente intera, il punto decimale

ed eventualmente la componente decimale.

es: -10.4 37.235f 7. .001

oppure in formato esponenziale, cioè nella forma Xe±M con il significato di

X*10±M, dove X è una componente floating point rappresentata come prima

indicato, M è un intero

es: -1.04e+1 0.37235e+2L 0.7e+1 1.e-2

Inoltre, il tipo di dato usato per rappresentare la costante sarà:

•il tipo double se non viene specificato un suffisso alla costante.

•il tipo float se invece viene aggiunto un suffisso f o F ,

•il tipo long double a 16 byte se viene aggiunto un suffisso l o L.

Il linguaggio C 96/243

Costanti di tipo char

I caratteri, cioè i tipi char (signed e unsigned) servono a rappresentare un

insieme di simboli quali i simboli della tastiera a A b B 1 2 ed alcuni caratteri

cosiddetti "di controllo" quali Enter, Tab, BackSpace, NewLine.

Lo standard ASCII associa un carattere ai valori da 0 a 127, mentre per i

caratteri da 128 a 255 l'associazione varia a seconda del computer.

I caratteri di controllo sono i primi 32 della tabella ASCII, cui seguono i

caratteri stampabili, cioè visualizzabili da un comune editor. Qui di seguito un

estratto dalla tabella ASCII.

Il tipo char del C è più simile al tipo byte di Java che non al tipo char.

Carattere: ‘0’ ‘1’ ‘A’ ‘B’ ‘a’ ‘b’

Codice ASCII: 48 49 65 66 97 98

Page 49: Il linguaggio C - people.ding.unisannio.it

49

Il linguaggio C 97/243

Variabili di tipo carattere

Una variabile di tipo carattere quindi memorizza un certo valore, da

0 a 255, che corrisponde ad un certo carattere. In un assegnamento

ad una variabile di tipo carattere, potremo assegnare o il codice

numerico o il carattere rappresentato da quel codice numerico.

Una costante di tipo carattere quindi potrà essere rappresentata o

come valore numerico da 0 a 255, oppure come simbolo

corrispondente a quel codice, ad es. ad un certo char potremo

assegnare o il carattere '0' o il valore numerico 48.

Il linguaggio C 98/243

Variabili di tipo carattere

La rappresentazione numerica delle costanti di tipo carattere è

fatta mediante la rappresentazione decimale del codice numerico.

Ad es., assegnando ad un char il valore 97 gli assegniamo il

carattere 'a'.

La rappresentazione simbolica delle costanti di tipo carattere non

soffre dei problemi dovuti alla differenze tra le codifiche dei

caratteri, perchè scrive nel codice direttamente il simbolo che si

vuole ottenere, e non una sua rappresentazione numerica.

Page 50: Il linguaggio C - people.ding.unisannio.it

50

Il linguaggio C 99/243

Variabili di tipo carattere

D'altro canto è necessario delimitare la rappresentazione simbolica di un

certo carattere separandola dagli altri elementi del linguaggio C. A questo

scopo la rappresentazione delle costanti di tipo carattere mediante i

simboli viene fatta scrivendo il simbolo all'interno di due apici singoli.

ad es. 'L' indica il carattere che rappresenta il simbolo L.

Però, poiché non tutti i caratteri sono visualizzabili da un editor (ad es. i

caratteri di controllo) deve essere previsto un modo simbolico per indicare

un carattere che non può essere visualizzato. A tal scopo sono definite le

cosiddette "sequenze di escape", ovvero dei simboli stampabili che quando

sono precedute dal carattere \ vengono interpretate dal C come un carattere

diverso.

Ad es. la sequenza di escape '\n' indica il carattere new line cioè l'andata a

capo di una riga di testo. Le sequenze di escape vanno racchiuse tra apici

singoli come i simboli dei caratteri.

Il linguaggio C 100/243

Sequenze di escape

Le sequenze di escape vengono introdotte anche per poter indicare

simbolicamente alcuni caratteri particolari ( ' " ) che hanno un significato speciale

nel linguaggio C

Le principali sequenze di escape disponibili in C sono le seguenti:

\a suono

\b BackSpace

\n New Line, andata a capo

\r carriage return

\t il Tab orizzontale

\\ il BackSlash, che è il delimitatore delle sequenze di escape

\' il carattere apice singolo ' che in C è delimitatore di carattere

\” il carattere doppio apice " che in C è delimitatore di stringa

Notare l'analogia tra l'uso delle sequenze di escape \\ \' \" in C e l'uso della coppia

di apici singoli '' dentro la write del Pascal o la println di Java per stampare un

solo apice singolo '.

Page 51: Il linguaggio C - people.ding.unisannio.it

51

Il linguaggio C 101/243

Sequenze di escapeInfine è possibile rappresentare i caratteri ancora in forma numerica mediante rappresentazione ottale

o esadecimale, scrivendo all'interno della coppia di apici singoli:

- un BackSlash seguito dalla rappresentazione ottale del numero, oppure

- un BackSlash seguito da una x seguita dalla rappresentazione esadecimale.

rappr. ottale '\OOO‘ fino a tre caratteri ottali (0:7), (max \377)

rappr. esadecimale '\xhh‘ fino a due (hh) caratteri esadec. (0:9,A:F)

es, sono equivalenti i seguenti assegnamenti, nella stessa colonna:char ch;

ch='A'; ch=' " '; ch=' \' '; ch='\\';

ch=65; ch=34; ch=39; ch=92;

ch='\x41'; ch='\x22'; ch='\x27'; ch='\x5C';

ch='\101'; ch='\42'; ch='\47'; ch='\134';

ch='\042'; ch='\047'; ch='\'; /*errore*/

ch='\"'; ch='\047';

non ammesso ch='\0042'; ch=' ' '; /*errore*/

ch='\400‘ errore,4 ottali

Il linguaggio C 102/243

Costanti di tipo stringa

In C la stringa vera e propria non esiste, si usa come stringa un vettore di n+1 char , in

cui al carattere in ultima posizione è assegnato il valore 0, ovvero il carattere nullo '\0'.

Questo carattere particolare rappresenta il delimitatore finale della stringa, ovvero

l'indicazione che la stringa è finita.

‘s’ ‘t’ ‘r’ ‘i’ ‘n’ ‘g’ ‘\0’

Quindi, una stringa in C internamente è rappresentata da un vettore di caratteri terminati

da un carattere nullo. La lunghezza è nota solo dopo avere trovato il carattere 0 in fondo.

Una costante di tipo stringa, ovvero una costante di tipo vettore di caratteri viene scritta

come una sequenza di caratteri racchiusa tra 2 doppi apici, che non fanno parte della

stringa.

Ciascun carattere nella stringa può essere rappresentato simbolicamente o per mezzo di

una sequenza di escape o in forma esadecimale o ottale.

Page 52: Il linguaggio C - people.ding.unisannio.it

52

Il linguaggio C 103/243

Costanti di tipo stringa

Esempi di costanti stringa sono:

"questA stringa è giusta" 'è' e' un caratt. non ASCII

"quest\x41 stringa e' giusta" ' ' ' è delimitatore di carattere, non di

stringa, quindi non c'e' confusione

"quest\x41 stringa e\47 giusta" ' ' ' scritto in ottale con 2 cifre(occhio)

"quest\101 stringa e\047 giusta" ' ' ' scritto in ottale a 3 cifre

"questA stringa " è sbagliata" ERRORE, '"' è delimitatore di stringa

"questA stringa \" è stata corretta“

"aa12bb" è uguale a "aa\0612bb" ma è diverso da "aa\612bb"

NB: la const a destra dà errore in compilazione, "numeric constant too

large"

Il linguaggio C 104/243

Operatori in C

Il linguaggio C mette a disposizione degli operatori, ovvero dei simboli speciali che

indicano al compilatore di generare codice per far eseguire delle manipolazioni logiche,

matematiche o di indirizzamento sulle variabili che costituiscono gli operandi

dell'operatore.

Le funzionalità degli operatori in C sono in linea di massima le stesse di tutti i linguaggi

di programmazione evoluti, ad es. il Pascal.

Il C si distingue perchè mette a disposizione alcuni operatori particolari che servono ad

effettuare velocemente alcune operazioni semplici.

Le operazioni realizzate da questi operatori sono molto veloci perchè la semplicità delle

funzionalità rende possibile implementare l'operazione in una maniera che sfrutta appieno

la potenzialità dei registri del calcolatore, limitando il numero degli accessi alla memoria,

e lavorando il più possibile coi registri.

Prima di vedere alcuni operatori, è bene introdurre una caratteristica molto particolare del

C, il Type Casting, ovvero la modifica del tipo di dato.

Page 53: Il linguaggio C - people.ding.unisannio.it

53

Il linguaggio C 105/243

Type Casting

Molto spesso il risultato di un'operazione dipende dal tipo di dato

che è coinvolto nell'operazione. Ad es. la divisione tra interi ha un

risultato diverso dalla divisione tra floating point, anche partendo

dagli stessi dati iniziali. 5.0/2.0=2.5 5/2=2

Questo perchè a seconda del tipo di dati coinvolti nelle operazioni,

le operazioni sono svolte in modo diverso, o utilizzando registri o

porzioni di registri diversi.

Una caratteristica peculiare del C è che permette durante

l'esecuzione di un'operazione, o durante il passaggio di

parametri ad una funzione all'atto della chiamata, di modificare

il tipo del o dei dati coinvolti nell'operazione.

Il linguaggio C 106/243

Type Casting

Ciò non significa affatto che la variabile (o la costante, o il

risultato di un'espressione) interessata cambia le sue

caratteristiche (dimensioni, proprietà, ecc..) di tipo.

Significa invece che se la variabile deve essere utilizzata in dei

calcoli, viene copiata in un registro sufficentemente grande per

contenere il nuovo tipo di dato e per svolgere le operazioni di

conversione, ed il valore del registro caricato viene convertito

secondo le caratteristiche del nuovo tipo di dato.

Solo dopo questa conversione, l'operazione viene effettuata,

secondo le regole proprie del nuovo tipo di dato, ottenendo

quindi come risultato un valore coerente con il nuovo tipo.

Page 54: Il linguaggio C - people.ding.unisannio.it

54

Il linguaggio C 107/243

Type Casting

La conversione di tipo type-casting in qualche caso viene effettuata

implicitamente dal compilatore C, ma può anche essere forzata dal

programmatore mediante un operatore unario detto cast, che opera in questa

maniera.

( nome_nuovo_tipo ) espressione

dove "nome_nuovo_tipo" è un tipo di dato primitivo o definito dall'utente, ed

"espressione" può essere una variabile, una costante o un'espressione complessa.

es. (double) i;

"Espressione" viene risolta fino ad arrivare ad un risultato (di un suo certo

tipo), poi il risultato viene convertito nel tipo "nome_nuovo_tipo" mediante

opportune operazioni più o meno complesse a seconda della conversione.

A questo punto abbiamo come risultato della conversione un dato di tipo

"nome_nuovo_tipo" con un certo valore, che può essere utilizzato secondo

le regole definite per "nome_nuovo_tipo".

Il linguaggio C 108/243

Type casting: esempio

Vediamo subito un esempio di cosa succede:

Un cast quindi equivale ad assegnare l"espressione" ad una

variabile del nuovo tipo specificato, che viene poi utilizzata al

posto dell'intera espressione.

int i=5, j=2;

double f;

f=i/j;

A questo punto f vale 2

int i=5, j=2;

double f;

f=(double)i/(double)j;

A questo punto f vale 2.5

Page 55: Il linguaggio C - people.ding.unisannio.it

55

Il linguaggio C 109/243

Type casting

L'esempio riportato mostra un caso in cui il type cast modifica fortemente

il risultato di un'operazione, che comunque avrebbe potuto essere

effettuata, pur con risultati diversi, senza type cast.

Il casting viene però utilizzato spesso anche nel passaggio di parametri a

funzioni, qualora il programmatore abbia necessità di passare alla

funzione chiamata un parametro formalmente diverso da quello richiesto

dalla funzione. Ad esempio qualora si debba passare alla funzione un

puntatore ad una struttura, di tipo diverso da quella che la funzione si

aspetta. Il compilatore dovrebbe segnalare l'errore. Mediante un type cast

del parametro passato all'atto della chiamata (di cui il programmatore si

deve assumere la responsabilità relativamente alla correttezza) si può

“convincere” il compilatore della correttezza formale dell'operazione.

Il linguaggio C 110/243

Type casting

Abbiamo finora parlato di type-casting forzato, ovvero imposto

dall'utente.

In realtà il C effettua automaticamente delle conversioni

implicite di tipo, in particolare quando effettua operazioni

matematiche tra operandi di tipo diverso.

Il casting viene effettuato automaticamente dal

compilatore C quando due operandi di un'operazione

binaria sono tra loro diversi. In tal caso l'operando di

tipo più piccolo viene convertito nel tipo più grande,

senza perdita di informazioni.

Page 56: Il linguaggio C - people.ding.unisannio.it

56

Il linguaggio C 111/243

Type casting

Quindi, dati due operandi di tipo diverso, il cast automatico si ha

secondo queste regole:

Tipo 1° operando Tipo 2° operando Tipo risultato

long double double long double

double float double

float (long) int float

double (long) int double

int char , short int

long int (short) int long int

Il linguaggio C 112/243

Type casting

Attenzione, il casting automatico può dare delle false sicurezze.

Nell'esempio seguente, prima viene eseguita la divisione (tra due interi, quindi

nessuna conversione) e c'è perdita di informazione, e solo in un secondo tempo c‘è

l'assegnamento al float.

int i=5, j=2;

double f;

f = (double) (i /j); /* f diventa 2.0 */

N.B. come abbiamo già visto, anche nell'assegnamento il C effettua conversioni di

tipo automatiche, convertendo il valore del lato destro nel tipo del lato sinistro,

eventualmente perdendo informazioni quando si passa da un tipo ad un tipo più

piccolo

Page 57: Il linguaggio C - people.ding.unisannio.it

57

Il linguaggio C 113/243

Operatori aritmetici

Gli operatori aritmetici + - * / % sono operatori presenti in tutti i linguaggi di

programmazione. Sono operatori binari (servono due operandi):

Somma + x+y valore del tipo più grande

Differenza - x-y valore del tipo più grande

Cambio segno - -x stesso tipo

Prodotto * x*y valore del tipo più grande

Divisione / x/y valore float tra float o valore int tra int

Resto % n%m resto modulo m solo tra interi

Il linguaggio C 114/243

Operatori di incremento e decremento

Gli operatori incremento ++ e decremento -- sono operatori unari (serve un

unico operando): aggiungono (tolgono) l'unità (1) all'operando.

Equivalgono ad assegnare alla stessa variabile il proprio valore incrementato

(decrementato) di uno, ma lavorano velocemente perchè usano (bene) registri.

x++ x=x+1

x-- x=x-1

Attenzione, sono previste due modalità di esecuzione, qualora l'incremento sia

parte di un'istruzione più complessa.

x++; prima usa x nell'istruzione, poi lo incrementa

++x; prima incrementa x, poi lo usa nell'istruzione

Page 58: Il linguaggio C - people.ding.unisannio.it

58

Il linguaggio C 115/243

Operatori di incremento e decremento

Le due istruzioni di incremento (decremento) isolate hanno lo stesso effetto. Il risultato

cambia quando vengono usate in istruzioni meno semplici.

es. int n, m=0; int n, m=0;

n = m++; n = ++m;

n vale 0, m vale 1 n vale 1, m vale 1

Priorità: ++ -- massima

- (cambio segno)

* / %

+ - minima

A pari priorità si valuta a partire da sinistra, a meno dell’uso di parentesi.

Il linguaggio C 116/243

Operatori relazionali e logici

Ricordiamo che in C il valore booleano Falso equivale a 0, e che

Vero equivale a diverso da 0. Gli operatori logici (equivalenti ad

AND OR NOT del Pascal) e gli operatori relazionali (minore

maggiore uguale diverso ecc..) restituiscono 0 quando il risultato

è falso e 1 quando il risultato è vero.

Operatori Logici Operatori Relazionali

AND && Maggiore >

OR || Maggiore o uguale >=

NOT ! Minore <

Minore o uguale <=

Uguale == (doppio simbolo =)

Diverso !=

Page 59: Il linguaggio C - people.ding.unisannio.it

59

Il linguaggio C 117/243

Operatori relazionali e logici

La priorità degli operatori relazionali e logici è minore della priorità degli

operatori aritmetici, quindi in un'espressione complessa prima vengono valutate

le operazioni aritmetiche, e solo in un secondo momento gli operatori relazionali.

Ad es., l'espressione

x <= 5+y

viene valutata prima effettuando la somma tra 5 ed y, ed il risultato viene

confrontato con x. Ciò equivale alla valutazione di questa espressione

(x <= (5+y))

Il linguaggio C 118/243

Operatori relazionali e logici

E' buona norma abituarsi ad usare parentesi tonde e spaziature in

abbondanza per delimitare visivamente almeno le espressioni più semplici

che entreranno poi in relazione tramite operatori logici.

Si commettono di solito meno errori di programmazione guidando la

valutazione delle espressioni tramite alcune parentesi tonde poste ad hoc,

piuttosto che non affidandosi esclusivamente alla precedenza degli

operatori.

- Le espressioni sono valutate da sinistra a destra.

- Le priorità all'interno del gruppo degli operatori logici e relazionali sono le

seguenti:

! priorità massima (valutato per primo)

> >= < <=

== !=

&&

||

Page 60: Il linguaggio C - people.ding.unisannio.it

60

Il linguaggio C 119/243

Operatori relazionali e logici

Ad es. valutiamo l'espressione qui di seguito:

10>5 &&!(10<9) || 3<=4 10>5 vale 1

!(10<9) vale 1

otteniamo: 1 && 1 || 3<=4 1 && 1 vale 1

otteniamo: 1 || 3<=4 3<=4 vale 1

otteniamo: 1 || 1 1 || 1 vale 1

Il risultato finale dell'espressione è 1.

Attenzione ad evitare di usare espressioni scritte così. Utilizzate le parentesi tonde. La stessa

espressione, scritta in forma più leggibile, è riportata qui sotto:

( (10>5) && (!(10<9)) ) || (3<=4)

La sequenza delle parentesi impone l'ordine di valutazione degli operatori, ed evidenzia il criterio

logico con cui si deve essere formata l'espressione.

Il linguaggio C 120/243

Valutazione abbreviata di espressioni

connesse da operatori logici

Consideriamo una espressione formata da espressioni connesse da operatori logici && e || come ad esempio ESPR1 && ESPR2 || ESPR3

Tali espressioni sono valutate da sinistra verso destra, quindi, ricordando che l'operatore

AND logico && ha priorità maggiore dell'operatore OR logico, l'espressione nel suo

complesso può essere interpretato come segue:

( ESPR1 && ESPR2 ) || ESPR3

nel senso che dovrebbe essere valutata prima l'espressione ( ESPR1 && ESPR2 ) ed in un

secondo tempo l'espressione ESPR3, per poi mettere in OR logico i due risultati.

Ciò è abbastanza vero, ma in realtà il compilatore C è sufficientemente furbo da

generare il codice corrispondente alla valutazione di questa espressione, in modo tale

da interrompere la valutazione dell'espressione non appena determina la VERITA' o

la FALSITA' dell'INTERA espressione.

Lo stesso concetto vale per porzioni di espressioni, la cui valutazione viene interrotta

quando viene determinata la Verità o Falsità della porzione di espressione.

Page 61: Il linguaggio C - people.ding.unisannio.it

61

Il linguaggio C 121/243

Valutazione abbreviata di espressioni

connesse da operatori logici

Vediamo come il compilatore C organizza la valutazione di:

( ESPR1 && ESPR2 ) || ESPR3

viene valutata ESPR1

se ESPR1 è falsa (0)

• NON VIENE VALUTATA ESPR2 perchè in ogni caso l'espressione ESPR1 &&

ESPR2 sarà falsa

• viene valutata ESPR3

• se ESPR3 è falsa (0) l'espressione totale vale falso (0)

• se invece ESPR3 è vera (!=0) l'espressione totale vale vero.

• in ogni caso non abbiamo valutato ESPR2 perchè era inutile

Il linguaggio C 122/243

Valutazione abbreviata di espressioni

connesse da operatori logicise ESPR1 è vera

•VIENE VALUTATA ESPR2

•se ESPR2 è vera allora l'espressione ESPR1 && ESPR2 sarà vera, ed anche

l'espressione intera ESPR1 && ESPR2 || ESPR3 sarà vera, indipendentemente dal

valore di ESPR3, quindi ESPR3 non viene valutata, ed il risultato finale è vero.

•se ESPR2 è falsa allora l'espressione ESPR1 && ESPR2 sarà falsa, e deve essere

valutata ESPR3 per conoscere il risultato finale.

•viene valutata ESPR3

•se ESPR3 è vera il risultato finale è vero

•se ESPR3 è falsa il risultato finale è falso

Questo modo di valutare le espressioni, velocizza le operazioni, perchè non esegue

operazioni inutili, ma deve essere attentamente valutato, se all'interno delle espressioni

sono presenti istruzioni o funzioni che generano qualche effetto secondario, perchè tali

funzioni potrebbero essere eseguite oppure no a seconda del risultato booleano delle varie

porzioni dell'espressione.

Page 62: Il linguaggio C - people.ding.unisannio.it

62

Il linguaggio C 123/243

Valutazione abbreviata di espressioni

connesse da operatori logiciSupponiamo di avere un vettore di interi, con SIZE elementi, vogliamo stampare tutti gli elementi del

vettore o fino all'ultimo o fino a che non incontriamo un elemento che vale 17, nel qual caso non

vogliamo stampare 17 e vogliamo interrompere la stampa.

Possiamo sfruttare la valutazione abbreviata per scrivere un codice cosi fatto:

#define SIZE 10;

int Vet[SIZE];

int i;

.... scrittura degli n elementi del vettore Vet

i=0;

while ( (i<SIZE) && (Vet[i]!=17) )

printf( "Vet[%d]=%d\n", i , Vet[i++] );

l'espressione Vet[i]!=17 viene valutata solo espressione (i<SIZE).

Ciò consente di evitare di accedere alla se è stata valutata vera la prima variabile Vet in una posizione

maggiore o uguale della posizione numero SIZE, perchè si cadrebbe fuori dal vettore e si potrebbe

causare un eccezione di sistema del tipo segmentation fault che causa l'interruzione traumatica

del programma.

Il linguaggio C 124/243

Valutazione di espressioni

Uno degli errori più comuni per chi comincia a programmare in C, consiste

nell'ostinarsi a voler scrivere codice "stretto" e poco commentato.

E' sempre un errore, perchè:

1. il codice va modificato nel tempo, ed il codice scritto in forma compatta

è più difficile da capire, anche per chi l'ha scritto.

2. l'aggiunta di parentesi tonde e spazi per rendere più visibile e

comprensibile il codice non diminuisce le prestazioni.

Page 63: Il linguaggio C - people.ding.unisannio.it

63

Il linguaggio C 125/243

Operatori di assegnamento ed

espressioni aritmetiche

Il C mette a disposizione alcuni operatori particolari, che coniugano un'operazione aritmetica

all'operazione di assegnamento.

Somma e Assegnamento x += y equivale a x = x+y

Differenza e Assegnamento x -= y equivale a x = x-y

Prodotto e Assegnamento x *= y equivale a x = x*y

Divisione e Assegnamento x /= y equivale a x = x/y

Resto e Assegnamento x %= y equivale a x = x%y

Istruzioni C del seguente tipo (dove op è un'operazione aritmetica)

espressione_1 op= espressione_2

equivalgono all'istruzione

espressione_1 = espressione_1 op espressione_2

in cui l'espressione espressione_1 viene valutata due volte.

Il linguaggio C 126/243

Operatori di assegnamento ed

espressioni aritmetiche

Questi operatori composti servono a velocizzare il codice, perchè evitano di valutare

ripetutamente una stessa espressione, e in qualche caso riescono a mantenere i valori

valutati nei registri, quindi minimizzano l'accesso alla memoria.

Ad es. se noi avessimo un vettore di interi Vet, e volessimo aggiungere un 3 all'elemento

in posizione i+7*k, dovremmo scrivere (l'accesso alle posizioni di un vettore si effettua

mediante l'uso di parentesi quadre) un'istruzione del tipo:

Vet[i+7*k] = Vet[i+7*k] +2;

dovendo così valutare due volte l'espressione i+7*k

Invece con l'istruzione

Vet[i+7*k] += 2;

l'espressione i+7*k viene valutata una volta sola.

Page 64: Il linguaggio C - people.ding.unisannio.it

64

Il linguaggio C 127/243

Operatore sizeof()

Il C mette a disposizione un operatore unario, che restituisce la

dimensione della variabile o dello specificatore di tipo passato in

input. Serve a conoscere le dimensioni di alcuni tipi di dati, che

potrebbero cambiare al variare dell'architettura su cui il programma

deve girare, come ad esempio cambiano le dimensioni degli interi

int.

L'operatore sizeof prende in input o una variabile o un identificatore

di tipo, e restituisce la dimensione in byte del dato. Il dato può

anche essere un dato definito dall'utente, non solo un tipo di dato

primitivo.

Il linguaggio C 128/243

Operatore sizeof()

Es:

int I;

printf("dimensione di I: %d \n", sizeof(I) );

/* stampa 2 in Windows */ /* stampa 4 in Linux */

printf("dimensione del float: %d \n",

sizeof(float) ); /* stampa 4 */

L'operatore sizeof è molto importante per la portabilità del codice, da

un'architettura ad un'altra.

Particolarità dell'operatore unario sizeof è che viene valutato non

durante l'esecuzione del programma, ma al momento della

compilazione

Page 65: Il linguaggio C - people.ding.unisannio.it

65

Il linguaggio C 129/243

Strutture di controllo del flussoIn un linguaggio, le strutture di controllo del flusso specificano l'ordine secondo il quale le

operazioni devono essere effettuate. Abbiamo già visto informalmente alcune strutture, ora

completeremo e preciseremo la descrizione.

Istruzioni e Blocchi di Istruzioni

Ogni istruzione in C deve essere terminata da un punto e virgola ; Un'espressione qualsiasi

(come x=0, i++, 1&&23<13==7>>5) oppure una chiamata ad una funzione (come ad

es. printf(...)) diventa un'istruzione quando seguita da un punto e virgola. Il valore restituito

da queste espressioni è il risultato della valutazione dell'espressione considerata.

Un'istruzione costituita dal solo punto e virgola è un'istruzione nulla, che non ha effetto.

Un Blocco di Istruzioni è una sequenza di istruzioni C racchiusa tra una parentesi graffa

aperta ed una parentesi graffa chiusa. Un blocco dal punto di vista dei costrutti linguistici

(else while ecc..) va considerato come un'unica istruzione. Il corpo stesso di una funzione è

un blocco di istruzioni. All'interno di un blocco è possibile dichiarare delle variabili locali.

L'unica altra differenza tra un'istruzione singola ed un blocco è che dopo un blocco non deve

essere inserito il punto e virgola. Se viene messo viene considerato un'altra istruzione, nulla.

Il linguaggio C 130/243

Istruzione if-elseL'istruzione if puo' avere due forme di base:

if (espressione) if (espressione)

istruzione istruzione1

else

istruzione2

Viene valutata l'espressione "espressione", e se risulta vera (cioè diversa da zero)

viene eseguita l'istruzione "istruzione". In caso contrario, se esiste la keyword else

viene eseguita l'istruzione "istruzione2".

N.B. 1: con "istruzione" deve intendersi o singola istruzione o blocco di istruzioni.

N.B. 2: quando viene valutata l'espressione dentro parentesi tonde nel costrutto if, viene

effettuato il controllo se il risultato della valutazione è uguale o diverso da zero, cioè se è

falso o vero. Quindi scrivere if(espressione) oppure if(espressione!=0) è la

stessa cosa, ma il primo tipo di scrittura viene codificato dal compilatore in modo più

veloce, perchè non è richiesto di caricare un registro con la costante inserita nel

programma (lo zero) per il confronto, ma si ricorre ad una istruzione assembler di jump

condizionato dall'essere il risultato della valutazione (già contenuto in un registro) diverso

o uguale a zero. (Alcuni compilatori ottimizzano situazioni di questo tipo).

Page 66: Il linguaggio C - people.ding.unisannio.it

66

Il linguaggio C 131/243

Istruzione if-else

N.B. 3: Nel caso di costrutti if-else annidati, una keyword else è relativa al più vicino

degli if che lo precedono che manchi dell'else, ovviamente sempre che sia rispettata la

sintassi dell'if-else.

questo codice: significa: che è diverso da:

if (x) if (x){ if (x){

if(y) if(y) if(y)

istr1 istr1 istr1

else else }

istr2 istr2 else

} istr2

Il linguaggio C 132/243

Istruzione if-elseIl costrutto if-else-if è fatto nel modo seguente

if (espressione1)

istruzione1

else if (espressione2)

istruzione2

else if (espressione3)

istruzione3

else

istruzione

e consente di effettuare una scelta multipla. Le espressioni 1, 2, ..ecc. vengono valutate

nell'ordine in cui si presentano. La prima espressione che si rivela vera fa eseguire la

corrispondente istruzione, e fa uscire il controllo dalla struttura di controllo if-else-if.

L'ultimo else (che può anche non esserci) serve nel caso in cui nessuna delle precedenti

condizioni si realizza.

Page 67: Il linguaggio C - people.ding.unisannio.it

67

Il linguaggio C 133/243

Istruzione if-elseEcco un esempio, in cui si discrimina secondo il valore di una variabile x,

considerando 4 intervalli (-infinito,0) , [0,10) , [10,100) , [100 , +infinito)

double x=

if (x<0.)

istruzione1

else if (x<10.)

istruzione2

else if (x<100.)

istruzione3

else

istruzione

La struttura annidata if-else-if, per quanto molto potente, ha lo svantaggio di non

essere particolarmente veloce, perchè se la prima espressione che risulta vera è molto

annidata, prima di poterla verificare è necessario valutare tutte le espressioni

precedenti.

Il linguaggio C 134/243

Istruzione switch

L'istruzione switch è un'altra struttura di scelta plurima, presente

anche in Java ed analoga al case del Pascal, che controlla se

un'espressione assume un valore intero in un insieme di

COSTANTI intere, e fa eseguire una serie di istruzioni in

corrispondenza del valore intero verificato.

Una limitazione dello switch è che i valori ammessi come possibili

scelte (possibili casi previsti) devono essere delle costanti, e non

possono essere delle espressioni. Questo perchè il costrutto switch

vuole implementare una scelta multipla molto più veloce del

costrutto if-else-if, ma per fare questo deve limitare le possibili

scelte, definendole non in modo run-time mediante un'espressione,

ma mediante delle costanti, che il compilatore utilizzerà per scrivere

in codice macchina una sequenza di jump condizionati.

Page 68: Il linguaggio C - people.ding.unisannio.it

68

Il linguaggio C 135/243

Istruzione switch

La struttura dello switch è :

switch (espressione) {

case espressione_costante1:

sequenza di

istruzioni1;

break;

case espressione_costante2:

sequenza di

istruzioni2;

break;

.... altri case ...

default:

sequenza di istruzioni;

break;

}

Un esempio di switch semplice

#define NUM 3

int j;

switch( j ) {

case 1:

case 2+NUM:

sequenza_di_istruzioniA

break;

case -34:

case -34<<2:

sequenza_di_istruzioniB

break;

default

sequenza_di_istruzioniC

}

Il linguaggio C 136/243

Istruzione switch

Ogni possibile caso deve essere etichettato mediante uno (o più) costrutti case "Espressione_costante": dove

Espressione_costante è un'espressione formata solo da

costanti e operatori, ma non da variabili o funzioni.

Possono esserci più etichette per una stessa sequenza di

istruzioni, come nel precedente esempio. Le diverse case

rappresentano delle entry point nel costrutto switch.

Se l'espressione valutata nello switch assume uno dei valori

indicati nelle espressioni costanti, l'esecuzione passa alla

sequenza di istruzioni che seguono la keyword case per

quell'espressione costante.

Page 69: Il linguaggio C - people.ding.unisannio.it

69

Il linguaggio C 137/243

Istruzione switch

L'esecuzione procede fino a che non si incontra la keywordbreak, o si oltrepassa la parentesi graffa aperta che termina la

struttura switch, ed allora il controllo esce dalla struttura di

controllo di flusso switch.

Il caso etichettato come default può essere presente o no. Se è

presente assume il significato di "in tutti gli altri casi" cioè viene

eseguito se l'espressione valutata nello switch non ha assunto

nessuno dei valori indicati da una delle espressioni costanti di un

qualche case.

N.B. la keyword break non è strettamente indispensabile. Se non

è presente viene eseguita sequenzialmente ogni istruzione a

partire dal case che è stato raggiunto.

Il linguaggio C 138/243

Ciclo while

Il loop di tipo while è analogo al while del pascal, e consente di

eseguire un loop condizionale. La struttura del while è la

seguente:

while (espressione)

istruzione

dove "espressione" è una espressione che deve essere valutata

ogni volta prima di eseguire "istruzione". Se la valutazione di

"espressione" da' come risultato vero allora l'istruzione viene

eseguita, altrimenti no, e si esce dal ciclo.

Page 70: Il linguaggio C - people.ding.unisannio.it

70

Il linguaggio C 139/243

Ciclo whilePoichè la valutazione della condizione è effettuata prima delle

istruzioni che costituiscono il loop, il loop stesso può essere eseguito

zero volte oppure un numero finito di volte oppure ancora può

realizzare un loop infinito.

Esempio, stampa dei caratteri di una stringa

char str[]="stringa";

int j=0;

while( str[j] )

printf("%c", str[j++]);

Esempio, loop infinito: while(1) { .... }

Il linguaggio C 140/243

Ciclo do-while

Il loop di tipo while è analogo al repeat-until del pascal, e consente di eseguire un loop

condizionale da 1 ad infinite volte. La struttura del do-while è la seguente:do {

istruzioni

} while (espressione);

dove "espressione" è una espressione che deve essere valutata ogni volta dopo avere

eseguito "istruzioni". Se la valutazione di "espressione" da' come risultato vero il loop

viene ripetuto, cioè si riesegue "istruzioni", altrimenti si esce dal ciclo.

Poichè la valutazione della condizione è effettuata dopo le istruzioni che costituiscono

il loop, il loop stesso può essere eseguito da una volta a infinite volte.

es:int num;

do {

scanf("%d", &num);

} while(num>100);

Page 71: Il linguaggio C - people.ding.unisannio.it

71

Il linguaggio C 141/243

Ciclo forIl loop di tipo for è analogo al for del pascal, ma è più potente. La forma generale

del ciclo for è fatta così:

for ( inizializzazione ; condizione ; incremento)

istruzione

le tre componenti di un ciclo for sono delle espressioni, ovvero possono essere

degli assegnamenti o delle chiamate a funzione.

L'espressione "inizializzazione" viene eseguita una sola volta, nel

momento in cui il controllo viene affidato al costrutto for. Serve per impostare le

variabili e quanto necessario per cominciare il loop. Questa "espressione"

può anche non essere presente, ed allora dopo la parentesi tonda aperta viene

subito il punto e virgola.

L'espressione "condizione" viene valutata (eseguita) ogni volta prima di

eseguire le istruzioni del loop, ed è quella che stabilisce se il ciclo deve

continuare ad essere eseguito ancora una volta, oppure si deve uscire dal costrutto

for. Se "condizione" è vera si esegue ancora "istruzione", se invece è falsa

(0) si esce dal ciclo for passando alla istruzione successiva del programma.

Il linguaggio C 142/243

Ciclo for

Anche l'espressione "condizione" può non essere presente, nel qual caso lo

spazio tra i due punti e virgola dentro il costrutto for rimane vuoto, ed il

compilatore assume vero come risultato della valutazione della (assente)

condizione di proseguimento del ciclo for, e quindi continua ad eseguire le

istruzioni dentro al loop.

L'espressione "incremento" viene eseguita alla fine di ogni ciclo, per

modificare ad es. le variabili che stabiliscono l'incremento o decremento delle

variabili contatore che stabiliscono il numero di cicli del loop. Questa

"espressione" può anche non essere presente, ed allora dopo il secondo punto e

virgola del for viene subito la parentesi tonda chiusa.

Quindi un ciclo fatto così: for( ; ; ) { .. } realizza un loop infinito.

N.B. poichè la condizione di continuazione del loop viene valutata (eseguita)

prima di ogni ciclo, il loop for permette anche di non eseguire nemmeno una volta

il ciclo.

Page 72: Il linguaggio C - people.ding.unisannio.it

72

Il linguaggio C 143/243

Ciclo forCiascuna delle tre espressioni del for, cioè inizializzazione, condizione e

incremento può contenere più istruzioni, che dovranno essere separate da

virgole. Vediamone un esempio nel caso in cui si vogliano utilizzare due

variabili di controllo./* con due variabili di controllo */

void main(void) {

int x ,y;

for ( x=0, y=0; x+y<100; x++ , y+=3 )

printf ("%d ", x+y);

}

Il ciclo for può anche non contenere un corpo di istruzioni vero e proprio, che

può essere limitato ad un'istruzione vuota, il punto e virgola./* lunghezza di una stringa null-terminata*/

int len;

char stringa[] = "mia stringa";

for( len=0; stringa[len]; len++) ;

Il linguaggio C 144/243

Ciclo for: esempi/* con incremento */

void main(void) {

int x;

for ( x=1; x<100; x++ )

printf ("%d ",

x);

}

/* loop infinito */

int x;

for ( ;; )

printf ("?");

/* con decremento */

void main(void) {

int x;

for ( x=100; x>0; x-- )

printf ("%d ",

x);

}

/* con espressioni costituite

da funzioni */

for ( funzA();funzB();funzC())

Page 73: Il linguaggio C - people.ding.unisannio.it

73

Il linguaggio C 145/243

Istruzione break: uscita da un ciclo

L'istruzione break (che è stata vista già a proposito della struttura switch) serve

ad imporre l'uscita da un loop di tipo for, while, do-while, oppure l'uscita dallo

switch, e a far riprendere il controllo di flusso immediatamente dopo la fine del

loop (o switch) da cui si esce.

void main(void) {

int x;

for ( x=1; ; x++ ) {

if ( x==5)

break; /* esce dal ciclo for */

}

printf ("dopo break ricomincia qui ");

}

Il linguaggio C 146/243

Istruzione break: uscita da un cicloNel caso di cicli annidati, con l'istruzione break si esce solo dal ciclo piu interno, entro il

quale si trova la chiamata alla istruzione break. es.:

void main(void) {

int x;

for ( y=1; ; y++ ) {

for ( x=1; ; x++ ) {

if ( x==5)

break; /* esce dal ciclo for */

}

printf ("dopo break ricomincia qui%d ", x);

}

}

Ovviamente, in caso di switch dentro un ciclo, un break dentro lo switch fa uscire solo

dallo switch.

Page 74: Il linguaggio C - people.ding.unisannio.it

74

Il linguaggio C 147/243

Istruzione continue:

ricominciare il ciclo

L'istruzione continue, a differenza della break si applica ai cicli ma non

allo switch. Come la break interrompe l'esecuzione di un ciclo for while o do-

while, ma anzichè uscire dal ciclo definitivamente fa eseguire la successiva

iterazione.

Nel caso di ciclo while o do-while una continue posta nel corpo del ciclo fa

saltare alla espressione di controllo, che viene valutata, e se risulta vera viene

eseguito ancora il loop, altrimenti si esce dal loop.

Nel caso del ciclo for invece, un'istruzione continue fa eseguire l'espressione che

effettua l'incremento, poi viene effettuata la valutazione della condizione di

continuazione del ciclo for, e se risulta vera viene eseguito il ciclo, altrimenti si

esce dal for.

N.B. L'istruzione continue è pochissimo usata.

Il linguaggio C 148/243

Istruzione continue:

ricominciare il cicloAnche per la continue come per la break, in caso di cicli annidati, si interrompe solo il ciclo più

interno in cui è avvenuta la chiamata alla continue.

void main(void) {

int x;

for ( y=1; ; y++ )

for ( x=1; ; x++ ) {

if ( x==5)

continue;

/* si effettua l'incremento di x, x diventa 6,

si verifica la condizione, risulta vera

perchè assente si riprende dal for interno

(quello della x) con x=6 */

}

}

Page 75: Il linguaggio C - people.ding.unisannio.it

75

Il linguaggio C 149/243

Istruzione goto: salto

L'istruzione goto, consente di

passare il controllo dal punto in

cui si effettua la goto ad una

locazione del programma

individuata da una label

(un'etichetta) seguita dai due

punti, collocata in una qualche

posizione del codice. Può essere

utile per uscire da più livelli di

annidamento di cicli, cosa che la

break e la continue non riescono a

fare. Va usato con attenzione.

void main(void) {

int x;

for ( y=1; ; y++ )

for ( x=1; ; x++ ) {

for ( z=10; z<1000; z++ ) {

...

if ( x==5)

goto uscita;

...

}

}

uscita:;

...

}

Il linguaggio C 150/243

Istruzione goto: salto

Altro esempio di stranezza consentita dal goto:

void main(void) {

int x=13;

goto dentro;

for ( x=1; ; y++ ) {

dentro:

printf("atterraggio: x=%d\n", x );

}

}

Page 76: Il linguaggio C - people.ding.unisannio.it

76

Il linguaggio C 151/243

Istruzione goto

Il goto non permette di passare da una funzione ad un'altra. Ecco

un' esempio di cosa non è permesso.

int funzione1(void){

arrivo: ;

...

}

void funzione2(int i){

goto arrivo; /* errore in

compilazione */

...

}

Il linguaggio C 152/243

Array a una dimensioneUn array è un insieme di variabili dello stesso tipo cui è possibile accedere mediante un

nome comune e referenziare uno specifico elemento tramite un indice.

Il C alloca gli elementi di un array in posizioni adiacenti, associando l'indirizzo più basso al

primo elemento, e il più alto all'ultimo. Gli array ad una dimensione vengono dichiarati come

segue:

Tipo NomeVariabile[ dimensione_costante ] ;

dove Tipo è il tipo degli elementi del vettore, dimensione è una costante che indica quanti

elementi deve contenere l'array.

La dichiarazione serve al compilatore per riservare in memoria spazio sufficente al vettore.

Lo spazio occupato dal vettore sarà:

numero_byte = sizeof(Tipo) * dimensione_costante ;

esempi:

int vettore[10]; un vettore di 10 interi

char stringa[100]; un vettore di 100 caratteri, cioè potenzialmente una

stringa per 99 caratteri più il carattere terminatore '\0

Page 77: Il linguaggio C - people.ding.unisannio.it

77

Il linguaggio C 153/243

Array a una dimensione

Il C indicizza gli elementi a partire da 0, quindi possiamo accedere agli elementi con gli

indici da 0 a dimensione_costante-1.

L'accesso si effettua mediante il nome dell'array seguito dalle parentesi quadre in cui viene

racchiuso l'indice dell'elemento cercato.

vettore[3] accede al quarto elemento del vettore "vettore"

stringa[0] accede al primo carattere del vettore "stringa"

Non si puo' assegnare qualcosa al vettore intero, ma solo alle diverse posizioni del vettore

(non ha senso scrivere vettore=19; o vettore=stringa).

Inizializzazione di un array: int vet [3] = { 1,2,3 } ;

Un esempio di programma con un semplice vettore.

void main(void) {

char str[100]; int i;

for ( i=0; i<100; i++)

str[i] = (char) i;

}

Il linguaggio C 154/243

Il problema dello sconfinamento

Il linguaggio C non effettua controlli per verificare che non si acceda a posizioni

fuori dal vettore.

Cioè è possibile (ma è un errore gravissimo anche se comune) accedere ad una

locazione di memoria fuori dal vettore, con vari effetti possibili:

•Se si accede in lettura al vettore fuori da esso può accadere, a seconda del

sistema operativo:

•nessun problema apparante, solo errore logico

•segmentation fault.

•se invece si accede in scrittura al vettore ci saranno danni maggiori, perchè oltre

ai due problemi già citati si sovrascriverà una locazione di memoria che potrebbe

contenere dati essenziali al programma o peggio ancora al sistema operativo.

Oppure si modificherà il valore delle variabili dichiarate immediatamente sopra o

sotto al vettore.

Page 78: Il linguaggio C - people.ding.unisannio.it

78

Il linguaggio C 155/243

Esempio di errore di sconfinamento

void main(void) {

int prima=0;

int vet[10];

int dopo=0;

printf("INIZIO: prima=%d dopo=%d\n",

prima, dopo );

for ( i=-1; i<=10; i++) {

vet[i] = 99;

printf("i=%d prima=%d dopo=%d\n",

i, prima, dopo );

}

}

Il linguaggio C 156/243

Istruzione typedef

Il C permette di definire esplicitamente nomi nuovi per i tipi di dati, tramite la parola

chiave typedef. L'uso di typedef consente di rendere il codice più leggibile.

Il formato dell'istruzione typedef è il seguente:

typedef tipo nuovo_nome_tipo;

in questo modo assegnamo al tipo tipo il nuovo nome nuovo_nome_tipo.

Da questo momento in avanti potremo riferirci al tipo di dato tipo sia con il nome tipo sia

con il nome nuovo_nome_tipo.

Es

typedef int intero;

/* ora posso usare intero come tipo invece di int */

intero i;

/* definisce una variabile i di tipo int. */

Page 79: Il linguaggio C - people.ding.unisannio.it

79

Il linguaggio C 157/243

Tipi di dati complessi

Il C mette a disposizione i seguenti dati di tipo complesso:

• array monodimensionali, in parte già visti,

• strutture, dette struct,

• unioni, dette union,

• puntatori,

• array multidimensionali,

Ci sarebbe da discutere sul fatto che i puntatori siano un dato di tipo complesso,

in quanto in realtà gli indirizzi rappresentano uno dei dati di base per la

programmazione a basso livello (assembler) per tutte le moderne architetture dei

calcolatori. Col termine "dato complesso" intendiamo perciò indicare dati il cui

uso richiede attenzione.

Il linguaggio C 158/243

Strutture (struct)

Vediamo come è definita un tipo di dato struct chiamato luogo, che vuole rappresentare

un punto geografico individuato da tre coordinate intere x, y, z ed un nome rappresentato

da una stringa di 30 caratteri:

Vediamo come si definisce il tipo di dato "luogo":

struct luogo {

char nome[30];

int x;

int y;

int z;

};

Una volta definito il tipo di dato, possiamo dichiarare una variabile di quel tipo,

premettendo però la keyword struct:

struct luogo monte_bianco;

Page 80: Il linguaggio C - people.ding.unisannio.it

80

Il linguaggio C 159/243

Strutture (struct)

Una sintassi diversa permette di dichiarare in una sola istruzione il tipo di dato struct

luogo ed alcune variabili di quel tipo.

struct luogo {

char nome[30]; int x;

int y; int z;

} monte_bianco , monte_rosa;

In questo caso, qualora solo queste variabili debbano essere dichiarate, ovvero qualora

non ci sia altrove necessità di conoscere il tipo di dato "luogo", l'identificatore "luogo "

può essere omesso, e si avrà:

struct {

char nome[30]; int x;

int y; int z;

} monte_bianco , monte_rosa;

Il linguaggio C 160/243

Strutture (struct)Anche con i dati di tipo struct possiamo ricorrere alla typedef per definire nuovi nomi per i

tipi di dato. Ad es:

typedef struct {

char nome[30];

int x;

int y;

int z;

} LUOGO;

In questo modo abbiamo definito un nome che può essere utilizzato per dichiarare delle

variabili di tipo LUOGO in tutto e per tutto uguale al precedente tipo di dato luogo, che però

in questa dichiarazione non compare. Ora potremo dichiarare variabili omettendo la

keyword struct.

LUOGO milano.

Page 81: Il linguaggio C - people.ding.unisannio.it

81

Il linguaggio C 161/243

Strutture (struct)E' possibile comunque mantenere i due identificatori:

typedef struct luogo {

char nome[30];

int x; int y; int z;

} LUOGO;

e definire due variabili di tipo uguale pure se con nomi diversi, ed il compilatore li tratta

come dati di un solo tipo, permettendo ad es. gli assegnamenti.

struct luogo Cesena;

LUOGO Cesenatico.

Cesena = Cesenatico;

Per accedere ai membri (o campi) di una struttura struct, il C fornisce l'operatore punto "." .

Ad esempio: Cesena.nome accede alla stringa nome del dato Cesena di tipo luogo.

Il linguaggio C 162/243

Inizializzazione delle struct

Una struttura struct puo' essere pre-inizializzata al momento della dichiarazione, mettendo

le costanti che inizializzano uno per uno tutti i campi della struct tra parentesi graffe.

struct luogo rimini = { "RIMINI" , 100, 139 , 10};

E’ possibile anche annidare nell'esempio qui di seguito le struct ed inizializzarle assieme,

come:#include <stdio.h>

void main() {

struct luogo {

char nome[30];

int x, y, z;

};

struct coppia_di_luoghi {

struct luogo luogo1;

struct luogo luogo2;

};

struct coppia_di_luoghi Rimini_Milano = {

{ "RIMINI" , 100, 139 , 10} , { "MILANO" , 80, 170 , 50 }

};

printf("luogo1=%s\n" , Rimini_Milano.luogo1.nome); /* stampa

"RIMINI“ */

}

Page 82: Il linguaggio C - people.ding.unisannio.it

82

Il linguaggio C 163/243

Unioni: union

Un'union e' una variabile che può tenere (in momenti diversi)

oggetti di diversa dimensione e tipo, che sono quindi posizionati

sulla stessa area di memoria, cioè sovrapposti.

Spesso i diversi oggetti sono delle struct, ed hanno tutte un campo,

eventualmente con un nome diverso da struct a struct, ma collocato

nella stessa posizione in memoria, in cui viene mantenuta una

costante che identifica quale struttura è contenuta nell'union in quel

momento.

In questo modo, ad es, si può passare dati diversi ad una stessa

funzione, e la funzione leggendo questo campo di identificazione

della struttura, capisce che genere di struttura è contenuta in quel

momento nella union, e può accedere correttamente ai dati della

union.

Il linguaggio C 164/243

Unioni: esempiostruct A {int idA; char field1[10]; char field2[20]; char field3[10] };

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);

Page 83: Il linguaggio C - people.ding.unisannio.it

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

Page 84: Il linguaggio C - people.ding.unisannio.it

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).

Page 85: Il linguaggio C - people.ding.unisannio.it

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.

Page 86: Il linguaggio C - people.ding.unisannio.it

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.

Page 87: Il linguaggio C - people.ding.unisannio.it

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.

Page 88: Il linguaggio C - people.ding.unisannio.it

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");

Page 89: Il linguaggio C - people.ding.unisannio.it

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

questo modo:

Il linguaggio C 178/243

Allocazione dinamica della memoria

int *p;

p = (int*) malloc( 100*sizeof(int) ); /* alloca 100 interi */

if( !p ) {

printf("errore: allocazione impossibile");

exit(1);

} else {

.... uso la memoria

free(p); /* rilascia la memoria allocata v*/

}

Page 90: Il linguaggio C - people.ding.unisannio.it

90

Il linguaggio C 179/243

Puntatori e array monodimensionali

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)

Page 91: Il linguaggio C - people.ding.unisannio.it

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;

Page 92: Il linguaggio C - people.ding.unisannio.it

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;

}

Page 93: Il linguaggio C - people.ding.unisannio.it

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;

Page 94: Il linguaggio C - people.ding.unisannio.it

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

Page 95: Il linguaggio C - people.ding.unisannio.it

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.

Page 96: Il linguaggio C - people.ding.unisannio.it

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.

Page 97: Il linguaggio C - people.ding.unisannio.it

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"

};

Page 98: Il linguaggio C - people.ding.unisannio.it

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':

tipo_restituito nome_funzione (paramdef1, paramdef2,

...) {

dichiarazione variabili locali ii

istruzioni

}

Dove

•paramdef1, paramdef2, ecc. sono la definizione degli argomenti da passare alla

funzione all'atto della chiamata (ad es. int i).

•tipo_restituito è il tipo del dato che viene restituito come risultato dalla funzione.

Il linguaggio C 196/243

Funzioni

Se manca la definizione del tipo di dato restituito dalla funzione

("tipo_restituito"), il C assume che il risultato della funzione e' di tipo int.

La funzione restituisce il risultato mediante un'istruzione detta return(), che,

analogamente a quanto avviene in Java ha la forma:

return espressione;

o

return (espressione);.

La return termina la funzione e restituisce il controllo al chiamante. Se la

funzione non deve restituire nessun valore, si dichiara il tipo_restituito

come void, e non si esegue la return o la si esegue senza passare alcun argomento

( es.: return;).

Page 99: Il linguaggio C - people.ding.unisannio.it

99

Il linguaggio C 197/243

Funzioni: esempio

Es. funzione che somma due valori di tipo int e restituisce un int:int somma(int a, int b) {

int sum;

sum = a+b;

return(sum);

}

La chiamata della funzione viene fatta così:

void main(void) {

int A=23;

int B=-31;

int risultato;

risultato = somma(A,B);

printf("somma= %d\n", risultato);

}

Il linguaggio C 198/243

Funzioni: esempio

Es. funzione che non restituisce alcun valore: void somma(int a, int b) {

int sum;

sum = a+b;

printf("somma= %d\n", sum); /* non serve la return */

}

La chiamata della funzione viene fatta così: void main(void) {

int A = 23;

int B = -31;

somma(A,B);

}

Page 100: Il linguaggio C - people.ding.unisannio.it

100

Il linguaggio C 199/243

Passaggio degli argomenti: dati semplici

I dati di tipo semplice (char, int, long float, double e puntatori ), che

vengono passati come argomenti ad una funzione, vengono passati per valore

e non per indirizzo.

Ciò significa che nel momento della esecuzione della funzione, il valore

degli argomenti viene copiato sullo stack, e la funzione usa (ed

eventualmente modifica) questa copia degli argomenti. In tal modo il dato

originale rimane invariato dopo che la funzione ha restituito il controllo al

chiamante.

Se invece si vuole che un argomento di tipo semplice passato alla funzione

conservi le modifiche apportate dalla funzione durante l'esecuzione della

funzione, l'argomento deve essere passato per indirizzo, cioè alla funzione

deve essere passato l'indirizzo della variabile, in modo che la funzione (tramite

l'indirizzo) modifica la variabile originale.

Il linguaggio C 200/243

Passaggio degli argomenti: dati semplici

Es. funzione che somma due valori di tipo int e restituisce un int:

void azzera_variabile( int *ptr_a ) {

*ptr_a = 0;

}

La chiamata della funzione viene fatta così:void main(void) {

int A=23;

azzera_variabile ( &A );

/* viene passato l'indirizzo */

}

Page 101: Il linguaggio C - people.ding.unisannio.it

101

Il linguaggio C 201/243

Passaggio degli argomenti: array

I dati di tipo array ( vettori, matrici, array di dimensioni maggiori), che vengono

passati come argomenti ad una funzione, in C vengono passati per indirizzo, nel

senso che "passando l'array per nome si passa l'indirizzo in cui comincia

l'array".

Ciò significa che quando, all'atto della chiamata, passiamo ad una funzione il

nome di un vettore, passiamo l'indirizzo del primo elemento del vettore, e non

tutti i dati del vettore ( i 100 interi dell' esempio)

void main(void) {

int vet[100];

modifica_vet (vet , 100 );

}

di conseguenza la definizione della funzione dovrà contenere come argomento

formale il puntatore al tipo di dati del vettore (un puntatore ad int nell'esempio)

Il linguaggio C 202/243

Passaggio degli argomenti: array

void modifica_vet( int *v , int size ) {

v[0] = 137; /* modifica conservata fuori

dalla funzione */

v = NULL; /* annullo l'indirizzo in v, ma

questa

modifica NON viene mantenuta fuori

dalla funzione */

}

In tal modo se il vettore passato come argomento viene modificato dalla funzione,

cioè se la funzione modifica il contenuto degli elementi del vettore, queste

modifiche sono permanenti, cioè restano nel vettore anche dopo che la

funzione è terminata.

Page 102: Il linguaggio C - people.ding.unisannio.it

102

Il linguaggio C 203/243

Vettori

Quando l'argomento passato è un vettore, nella definizione della funzione l'argomento

può essere indicato o come un puntatore oppure in un modo alternativo, come segue:

void modifica_vet( int v[] , int size ) {

v[0] = 137; /* modifica conservata fuori

dalla funzione */

v = NULL; /* annullo l'indirizzo in v, ma questa

modifica NON viene mantenuta fuori

dalla funzione */

}

Questa notazione int v[] vuole indicare che v è un vettore di interi di dimensione

sconosciuta (non si sa di quanti interi è composto il vettore).

Comunque le due notazioni int *v e int v[] sono assolutamente equivalenti,

entrambi i parametri vengono trattati come puntatori.

Il linguaggio C 204/243

MatriciAnalogamente ai vettori, quando, all'atto della chiamata, passiamo ad una

funzione il nome di una matrice, passiamo l'indirizzo del primo elemento

della matrice, quindi le modifiche sui dati della matrice vengono conservate

dopo l'uscita dalla funzione.

void main(void) {

int mat[10][20];

modifica_mat ( mat );

}

void modifica_mat( int m[][20] ) {

m[1][0] = 137;/* modifica conservata

fuori dalla funz. */

m = NULL; /* questa modifica NON viene mantenuta */

}

Analogamente ai vettori, la definizione della funzione dovrà contenere un puntatore

ad array di 20 (= num. colonne) interi. Notare: si passa la dimensione delle righe, per

calcolare l'indice r*NC+c.

Page 103: Il linguaggio C - people.ding.unisannio.it

103

Il linguaggio C 205/243

Passaggio degli argomenti: strutture

I dati di tipo struct possono venire passati alle funzioni sia per valore sia per puntatore,

esplicitamente.struct point {

int x;

}

/* copia i valori di p1 nella struttura puntata da pp2 */

void copia_e_modifica_struct( struct point p1, struct point *pp2 ) {

pp2-> x = p1.x + 10; /* modifiche permanenti */

pp2-> y = p1.y + 10;

}

void main(void) {

struct point p1 = { 14 , 27 };

struct point p2;

copia_e_modifica_struct( p1, & p2 );

}

Se la struttura da passare è molto grande, è bene passarla per puntatore, per non

sovraccaricare lo stack, soprattutto quando le chiamate sono molto annidate.

Il linguaggio C 206/243

Passaggio di parametri: confronto C-Java

Java manipola gli oggetti per riferimento, e tutte le variabili che si riferiscono a

oggetti sono riferimenti (handle).

Java, però, effettua il passaggio di parametri esclusivamente per valore.

public void badSwap(int var1, int var2) {

int temp = var1;

var1 = var2;

var2 = temp;

}

Quando la funzione termina, il valore dei parametri var1 e var2 non sono

cambiati, dal punto di vista del chiamante. Ciò resta valido anche cambiando il

tipo da int a Object.

Page 104: Il linguaggio C - people.ding.unisannio.it

104

Il linguaggio C 207/243

Scambio di parametri in C

void swap(int x, int y){int temp;temp = x;x = y;y = temp;

}

main() {int a = 3;int b = 5;swap(a,b);printf(“%d

%d\n”,a,b);}

� Qual è l’output del programma ?

� 3 5

� Ciò accade perché la funzione swap agisce solo su una copia delle variabili a e b

� Per far sì che la funzione agisca sulle variabili stesse e non su delle copie occorre passare gli indirizzi delle variabili

Il linguaggio C 208/243

Scambio di parametri in C

void swap(int *x_ptr, int *y_ptr) {int temp;temp = *x_ptr;*x_ptr = *y_ptr;*y_ptr = temp;

}

main() {int a = 3;int b = 5;swap(&a,&b);printf(“%d %d\n”,a,b);

}

Page 105: Il linguaggio C - people.ding.unisannio.it

105

Il linguaggio C 209/243

Tipi di dato restituiti dalle funzioni

Le funzioni possono restituire:

•dati di tipo semplice, come char, int, long, float, double, puntatori a void, o

puntatori a qualche tipo di dato,

•ma anche strutture (struct) o puntatori a struct.

All'atto della chiamata, il valore restituito da una funzione:

- può essere utilizzato come espressione booleana:

double somma( double f, double g);

if ( somma(a,b) > 100.3 )

può essere utilizzato come membro di destra in un'istruzione di assegnamento,

f = somma(a,b);

oppure può non essere considerato affatto.

somma(a,b);

Il linguaggio C 210/243

Tipi di dato restituiti dalle funzioni

Vediamo un esempio di restituzione di una struct.

struct point { int x; int y; };

struct point crea_point( int x, int y ) {

struct point p;

p.x = x;

p.y = y;

return p ;

}

void main(void) {

struct point p1;

int x=21 , y = -10987;

p1 = crea_point( x, y );

printf ( "p1.x=%d p1.y=%d \n" , p1.x, p1.y );

}

Page 106: Il linguaggio C - people.ding.unisannio.it

106

Il linguaggio C 211/243

Considerazioni sui puntatori restituiti

dalle funzioni

Una funzione può restituire un puntatore ad un qualche tipo di dato, ma la

correttezza nell'uso di questo puntatore si può fare, dipende da come lo spazio in

memoria è allocato. Cioè:

Esempio corretto: p è allocato dinamicamente quindi, anche se p è una

variabile locale, lo spazio allocato sopravvive alla terminazione della funzione

int *alloca_vettore( int size ) {

int *p;

p = malloc(size*sizeof(int));

return p ;

}

void main(void) {

int *v;

v = alloca_vettore ( 10 ) ;

... usa v ....

}

Il linguaggio C 212/243

Considerazioni sui puntatori restituiti

dalle funzioni

Esempio sbagliato: p è una variabile locale, lo spazio allocato non sopravvive alla

terminazione della funzione

int *alloca_vettore( int size ) {

int p[1000];

return p ;

}

Esempio corretto: vet_globale è una variabile globale, quindi sopravvive alla

terminazione della funzione

void main(void) {

int *v;

v = alloca_vettore ( 10 ) ;

}

int vet_globale[10000];

int *spazio_pre_allocato( void ) {

return vet_globale ;

}

void main(void) {

int *v;

v = spazio_pre_allocato();

... usa v ....

}

Page 107: Il linguaggio C - people.ding.unisannio.it

107

Il linguaggio C 213/243

Il Prototipo delle funzioniIl prototipo o dichiarazione della funzione è un'istruzione che ripete l'intestazione della

funzione, senza il codice.

es:

double somma( double a, double b) ; dichiarazione

void main( void) {

double A=10 , B=29, C;

C = somma(A,B); chiamata

}

double somma( double a, double b) { definizione

return a+b ;

}

Il linguaggio C 214/243

Il Prototipo delle funzioni

Il prototipo serve a due scopi:

1) dare visibilità alla funzione quando il luogo in cui è chiamata precede il

luogo in cui è definita (se stiamo sullo stesso file), altrimenti il compilatore

non sa cos'è il simbolo nome della funzione. Se il compilatore trova un

simbolo nuovo seguito da parentesi tonde, lo tratta come una funzione che

restituisce un valore intero.

2) informare il compilatore su come deve trattare il dato restituito dalla

funzione, e su come passare i dati agli argomenti della funzione. Quindi il

prototipo e la definizione della funzione devono essere coerenti, altrimenti il

main (nell'esempio) tratterebbe i dati scambiati in modo diverso da come la

funzione somma si aspetta, generando errori. Il compilatore si accorge di

un'inconsistenza tra prototipo e definizione della funzione solo se entrambe

stanno su uno stesso modulo. Se stanno su due moduli diversi il compilatore

non se ne accorge e non ci avvisa.

Page 108: Il linguaggio C - people.ding.unisannio.it

108

Il linguaggio C 215/243

Ordine di valutazione degli argomenti

passati alle funzioni.

Il compilatore C opera in modo che le espressioni passate come argomenti alle

funzioni sono prima valutate, ed il risultato della valutazione viene scritto sullo

stack per essere disponibile al codice che implementa la funzione stessa.

Sorgono due problemi:

- in che ordine vengono valutate le espressioni?

-in che ordine vengono scritti sullo stack i risultati delle valutazioni ?

La risposta alle due domande è la stessa:

vengono prima valutate (e il risultato scritto sullo stack) le espressioni

passate come ultimo argomento della funzione (le più a destra), e poi via via

quelle più a sinistra.

Il linguaggio C 216/243

void stampa3( int a, int b, int c){

printf("a=%d b=%d c=%d\n", a, b, c);

printf("&a=%p &b=%p &c=%p\n”,&a,&b,&c);

}

void main( void) {

int x=0;

stampa3 (x++, x++, x++);

}

L'output del programma sarà del tipo:

a=2 b=1 c=0 (c valutato per primo)

&a=9003:0FF8 &b=9003:0FFA &c=9003:0FFC (c primo su stack) che indica come il terzo

argomento (ultimo, cioè più a destra) venga valutato (e scritto sullo stack) per primo, e poi gli

argomenti via via più a sinistra.

Ordine di valutazione degli argomenti

passati alle funzioni.

Page 109: Il linguaggio C - people.ding.unisannio.it

109

Il linguaggio C 217/243

Puntatori a funzione

In C è possibile utilizzare dei puntatori a funzioni, ovvero delle variabili a cui possono

essere assegnati gli indirizzi in cui risiedono le funzioni, e tramite questi puntatori a

funzione, le funzioni puntate possono essere chiamate all'esecuzione.

Confrontiamo la dichiarazione di una funzione:

tipo_restituito nome_funzione (paramdef1, paramdef2, ...)

con la dichiarazione di un puntatore a funzione:

tipo_restituito ( * nome_ptr_a_funz ) (paramdef1, paramdef2, ...)

dove:

paramdef1, paramdef2, ecc. sono la definizione degli argomenti da passare alla

funzione all'atto della chiamata (ad es.: int i).

tipo_restituito è il tipo del dato che viene restituito come risultato dalla funzione.

Ad esempio la seguente dichiarazione definisce un puntatore a funzione che punta a

funzioni le quali prendono come argomenti due double, e restituiscono un double (void).

double (*ptrf) ( double g, double f);

Il linguaggio C 218/243

Puntatori a funzione

Il C tratta i nomi delle funzioni come se fossero dei puntatori alle funzioni

stesse.

Quindi, quando vogliamo assegnare ad un puntatore a funzione l'indirizzo di una

certa funzione dobbiamo effettuare un operazione di assegnamento del nome

della funzione al nome del puntatore a funzione

Se ad es. consideriamo la funzione dell'esempio precedente:

double somma( double a, double b);

allora potremo assegnare la funzione somma al puntatore ptrf così:

ptrf = somma;

Page 110: Il linguaggio C - people.ding.unisannio.it

110

Il linguaggio C 219/243

Puntatori a funzione: esecuzioneAnalogamente, l'esecuzione di una funzione mediante un puntatore che la punta,viene

effettuata con una chiamata in cui compare il nome del puntatore come se fosse il

nome della funzione, seguito ovviamente dai necessari parametri. Per es. riprendiamo

l'esempio della somma:

double somma( double a, double b); /* dichiarazione

*/

void main( void) {

double A=10 , B=29, C;

double (*ptrf) ( double g, double f);

ptrf = somma;

C = ptrf (A,B); /* chiamata alla funz. somma */

}

double somma( double a, double b) { /* definizione */

return a+b ;

}

Spesso è complicato definire il tipo di dato puntatori a funzione, ed ancora di più

definire funzioni che prendono in input argomenti di tipo puntatori a funzione. In queste

situazioni è sempre bene ricorrere alla typedef per creare un tipo di dato

puntatore a funzione per funzioni che ci servono, ed utilizzare questo tipo di dato nelle

altre definizioni.

Il linguaggio C 220/243

Usare la typedef per rendere leggibile il

codice C

Esempio:

esiste in ambiente unix una funzione detta signal che puo' servire ad associare una

funzione al verificarsi di un evento. Ad esempio può servire a far eseguire una funzione

allo scadere di un timer.

Il prototipo della funzione, contenuto in signal.h, e' il seguente:

#include <signal.h>

void (*signal(int signum, void (*handler)(int) )

)(int);

NON E' SUBITO CHIARISSIMO COSA SIA QUESTA ROBA !!!

Il significato è che

1) la funzione signal vuole come parametri un intero signum, ed un puntatore handler

ad una funzione che restituisce un void e che vuole come parametro un intero.

2) la funzione signal restituisce un puntatore ad una funzione che restituisce un void e

che vuole come parametro un intero.

Page 111: Il linguaggio C - people.ding.unisannio.it

111

Il linguaggio C 221/243

Usare la typedef per rendere leggibile il

codice C

Converrebbe definire un tipo di dato come il puntatore a funzione richiesta:

typedef void (*tipo_funzione) (int);

ed utilizzarlo per definire la signal così:

tipo_funzione signal(int signum, tipo_funzione handler);

Nel man della signal e' presente questo commento:

If you're confused by the prototype at the top of this manpage, it may help to see it

separated out thus:

typedef void (*handler_type)(int);

handler_type signal(int signum, handler_type handler);

Il linguaggio C 222/243

Funzioni con numero di argomenti variabile

In C e' possibile definire funzioni aventi un numero di argomenti

variabile, cioe' funzioni che in chiamate diverse possono avere un

numero di argomenti diverso, ma ne debbono avere sempre

almeno uno. Ne è un esempio la printf();

Si utilizza a questo scopo una struttura va_list definita nel file

stdarg.h. Vediamo un esempio di funzione che riceve in input

n argomenti, di cui il primo e' un intero che contiene il numero di

argomenti seguenti, e gli altri sono delle stringhe, cioe' dei

puntatori a char. La funzione deve solo stampare tutti gli

argomenti passati.

Page 112: Il linguaggio C - people.ding.unisannio.it

112

Il linguaggio C 223/243

Funzioni con numero di argomenti variabile

void print_lista_argomenti(int narg, ...) {

va_list va;

int i; char *ptrchar;

/* va_start inizializza va_list all'argomento, tra passati a

print_lista_argomenti, che segue l'argomento narg indicato come

secondo argomento nella va_start, cioè inizializza la lista al primo

degli argomenti variabili */

va_start(va,narg);

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

ptrchar=va_arg(va,char*); /*ptrchar punta alla stringa

passata*/

printf("%d %s\n", i, ptrchar);

}

va_end(va);

}

la funzione potra' essere chiamata in una di questi modi:

print_lista_argomenti(0);

print_lista_argomenti(4,"primo","secondo","terzo","quarto");

Il linguaggio C 224/243

Funzioni con numero di argomenti variabile

Vediamo un esempio complicato ma utile di uso della lista di argomenti variabili. Stampa gli

argomenti passati, anche se sono di tipo diverso. Usa un primo parametro come formato per

sapere cosa viene passato nei successivi argomenti, indicando con s una stringa, con d un

intero, con c un carattere.

#include <stdio.h>

#include <stdarg.h>

void stampa_argomenti(char *fmt, ...) {

va_list ap;

int d;

char c, *p, *s;

va_start(ap, fmt);

while (*fmt)

switch(*fmt++) {

case 's': /* string */

s = va_arg(ap, char *);

printf("string %s\n", s);

break;

Page 113: Il linguaggio C - people.ding.unisannio.it

113

Il linguaggio C 225/243

Funzioni con numero di argomenti variabile

case 'd': /* int */

d = va_arg(ap, int);

printf("int %d\n", d); break;

case 'c': /* char */

c = va_arg(ap, char);

printf("char %c\n", c); break;

}

va_end(ap);

}

void main (void) {

stampa_argomenti ( "sdc" , "stringa" , (int) 10 ,

(char)'h' ); stampa_argomenti ("s" , "stringa2");

}

Il linguaggio C 226/243

INPUT ed OUTPUT

Le funzioni della libreria standard per il C per l'I/O sono definite nell'header

file stdio.h . (input / output standard)

Quando un programma C entra in esecuzione l'ambiente del sistema operativo

si incarica di aprire 3 files di I/O, ovvero 3 flussi di dati, e di fornire i rispettivi

puntatori di tipo FILE * globali.

La struttura chiamata FILE contiene le informazioni fondamentali sul file,

quali il modo di apertura del file, il nome del file, l'indirizzo di un buffer in cui

sono conservati i dati letti dal file e la posizione corrente nel buffer. L'utente

non deve conoscere questi dettagli, ma deve memorizzare in una variabile di

tipo puntatore a FILE (FILE*) l'indirizzo di questa struttura per utilizzarla

nelle letture o scritture su file.

Page 114: Il linguaggio C - people.ding.unisannio.it

114

Il linguaggio C 227/243

INPUT ed OUTPUT

I flussi di dati standard sono:

STANDARD INPUT serve per l'input normale (per default da tastiera), e ha come

puntatore a FILE la variabile globale stdin. Dallo standard input prendono i dati le

funzioni getchar, gets e scanf.

STANDARD OUTPUT serve per l'output normale (per default su schermo), e ha

come puntatore a FILE la variabile globale stdout. Sullo standard output scrivono i loro

dati le funzioni putc, printf e puts

STANDARD ERROR serve per l'output che serve a comunicare messaggi di errore

all'utente (per default anche questo su schermo), e ha come puntatore a FILE la

variabile globale stderr.

stdin, stdout e stderr sono delle costanti globali contenute in stdio.h,. e non

possono essere modificate.

Questi flussi possono pero' essere ridirezionati in modo da scrivere su file su disco,

oppure di leggere da file su disco.

Il linguaggio C 228/243

INPUT

- Il sistema prevede che l'input sia organizzato a linee, ciascuna terminata

da un carattere new line (\n), solitamente fornite dall'utente tramite la

tastiera, ma a volte anche fornite via file.

- L'utente da tastiera può digitare i caratteri e cancellarli, ma tali caratteri non

vengono passati all'applicazione fino a che l'utente non preme

<RETURN> nel qual caso i dati presenti sul buffer di tastiera vengono inseriti

in un vettore di caratteri, e in fondo viene aggiunto un carattere NEW LINE

(individuato da \n ). - NOTARE che l'utente non ha a disposizione i

caratteri fino a che non viene premuto RETURN, dopodiché i dati, in forma

di linea di testo terminante con un NEW_LINE sono pronti per essere letti

dall'applicazione.

La lettura dei dati può essere effettuata dallo standard input un carattere

alla volta oppure una linea alla volta.

Page 115: Il linguaggio C - people.ding.unisannio.it

115

Il linguaggio C 229/243

INPUT

La lettura carattere per carattere viene effettuata mediante la funzione:

int getchar(void);

che restituisce il successivo carattere in input in forma di codice ASCII, cioe' restituisce

ad es. 65 per 'A', 66 per 'B', ecc. 97 per 'a'. 98 per 'b', oppure restituisce EOF quando

incontra la fine del flusso di input, che viene rappresentata come la fine del file di

input.

Tale funzione e' bloccante nel senso che quando una linea di caratteri

precedentemente data in input e' finita, rimane in attesa che dallo standard input arrivi

una nuova linea di dati.

La lettura di una intera linea viene effettuata mediante la funzione:

char *gets( char *dest);

che scrive in dest la linea e restituisce un puntatore a dest se tutto va bene, restituisce

NULL altrimenti.

Il linguaggio C 230/243

Fine dell'INPUT

Se l'utente preme contemporaneamente la coppia di tasti (CTRL+d in UNIX, CTRL+z

in DOS seguito prima o poi da RETURN), in un certo senso segnala all'applicazione

che il flusso di dati e' terminato. Nella linea di testo viene aggiunto un carattere EOF

(che di solito e' rappresentato dal valore -1) ma che e' bene sempre confrontare con la

costante EOF (definita in stdio.h), ed il flusso di input viene chiuso, cioè da quel

momento in avanti ogni successiva chiamata a getchar restituirà sempre EOF.

ESEMPIO di lettura dallo standard input

#include <stdio.h>

void main(void) {

int ch;

while( (ch=getchar()) != EOF ) {

/* uso il carattere, ad es lo incremento di 1 e lo

stampo*/

ch ++;

putchar(ch);

}

}

Page 116: Il linguaggio C - people.ding.unisannio.it

116

Il linguaggio C 231/243

Ridirezionamento di input e output

L'utente puo' utilizzare lo standard input dare input non solo da tastiera ma anche da

file, ridirezionando un file sullo standard input nel seguente modo, al momento

dell'esecuzione del programma:

program < file_input

Analogamente l'output di un programma puo' essere ridirezionato su un file, su cui sara'

scritto tutto l'output del programma invece che su video

program > file_output

e le due cose possono essere fatte assieme

program < file_input > file_output

In UNIX si può ridirezionare assieme standard output e standard error:

program >& file_error_and_output

Il linguaggio C 232/243

Output non formattato

La scrittura dei dati sullo standard output (video) puo' essere effettuata un

carattere alla volta mediante la funzione:

int putchar(int ch);

che restituisce il carattere scritto ch se va tutto bene, altrimenti restituisce EOF

in caso di errore.

Page 117: Il linguaggio C - people.ding.unisannio.it

117

Il linguaggio C 233/243

Output formattatoL'output può essere effettuato anche in maniera formattata, e non solo un carattere alla volta.

La funzione printf serve proprio a stampare un output a blocchi sullo standard output secondo un

formato specificato, traducendo le variabili in caratteri, cioè printf stampa sullo standard output

gli argomenti arg1, arg2, ... secondo le specifiche contenute nel format.

int printf(char *format, ... );

meno formalmente

int printf(char *format, arg1, arg2, arg 3, ...);

il format e' una stringa di caratteri null-terminata, in cui compaiono o dei semplici caratteri che

verranno stampati così come sono oppure degli specificatori di formato nella forma %qualcosa.

"%d" stampa un intero

"%10d" stampa un intero e un minimo di 10 caratteri almeno in cui l'intero e' a destra

"%-10d" stampa un intero e un minimo di 10 caratteri almeno in cui l'intero e' a sinistra

"%f" stampa un float

"%lf" stampa un double

"%10f" stampa un float e un minimo di 10 caratteri almeno in cui il float e' a destra

Il linguaggio C 234/243

Output formattato"%-10f" stampa un float e un minimo di 10 caratteri almeno in cui il float e' a sinistra

"%10.5f" stampa un float e un minimo di 10 caratteri con al massimo 5 cifre dopo il punto

decimale

"%s" stampa tutti i caratteri di una stringa (cioe' un vettore di caratteri con un \0 in fondo)

fino a che incontra lo 0 finale "%10s" stampa almeno 10 caratteri di una stringa,

con la stringa a destra

"%15.10s" stampa almeno 15 caratteri, prendendone al massimo 10 dalla stringa, con la stringa a

destra

"%c" stampa un singolo carattere NB. per stampare un % devo scrivere %%

NB alcuni caratteri di controllo possono essere scritti mediante la loro sequenza di escape

NEW LINE (a capo linea) \n

TABULAZIONE \t

BACKSPACE \b torna indietro di un carattere ma non lo cancella

se però scrivo qualcosa gli passo sopra

Page 118: Il linguaggio C - people.ding.unisannio.it

118

Il linguaggio C 235/243

Output

NB poiché le stringhe vengono delimitate da due doppi apici " per stampare un doppio

apice devo scriverlo preceduto da un BACKSLASH cioè devo scrivere \"

Analogamente, poiché la \ serve a indicare una sequenza di escape, per stampare una \

devo scriverne due. (vedi nomi file nella fopen).

esempio:

int i=16;

double g=107.13987626;

char *str="pippo";

printf("%d) %10.3lf \"%s\"\n",i,g,str);

da' come risultato

16) 107.139 "pippo"

Il linguaggio C 236/243

Input formattato da stdin

Per estrarre dal flusso dello standard input i dati e' spesso utilizzata la funzione scanf.

La scanf cioe' prende l'input dallo standard input (aspetta un RETURN) e cerca di

estrarre i dati che sono specificati nel suo primo agomento

int scanf(char *format, ....);

int scanf(char *format, &arg1, &arg2, &arg 3, ... );

I dati estratti vengono scritti nelle variabili i cui indirizzi sono passati come argomenti

successivi al primo nella scanf.

NB. L'errore classico e' passare come argomento la variabile e non il suo indirizzo.

Page 119: Il linguaggio C - people.ding.unisannio.it

119

Il linguaggio C 237/243

Input formattato da stdin

La stringa di formato può contenere sia dei semplici caratteri, che allora devono

corrispondere ai caratteri che vengono digitati in input, sia degli specificatori di

conversione nella forma “%qualcosa” che indicano come devono essere interpretati i

caratteri che costituiscono il flusso di input.

%c viene letto un singolo carattere e copiato singolarmente nel primo byte

indicato dal puntatore

%10c vengono letti 10 caratteri e copiati nei primi 10 byte indicati dal puntatore

%s viene letta la stringa e scritta interamente a partire dal puntatore passato, e in

fondo viene aggiunto un carattere '\0' di terminazione

%d viene letto un intero

%f viene letto un float

%lf viene letto un double

Il linguaggio C 238/243

Input formattato da stdinLa scanf termina quando esaurisce la sua stringa di formato o quando verifica una

inconsistenza tra l'input e le specifiche del formato.

La scanf restituisce il numero di elementi trovati. Se non è stato scritto nessun elemento

possono esserci due motivi diversi: o lo standard input era stato chiuso e allora la scanf

restituisce EOF, oppure lo standard input era ancora aperto ma l'input non era consistente con

le richieste del formato e c'è stato errore nella conversione ed allora viene restituito 0.

Es. se in input l'utente scrive: "punti: 14 9.1 7 21" mediante la sequenza di istruzioni

int i,j,k,result;

double g;

result=scanf("punti: %d %lf %d %d", &i , &g , &j , &k);

if(result==EOF)

printf("FINE FILE INPUT\n");

else if(result<4)

printf("ERRORE, solo %d dati in input\n",result);

else

printf("OK: i=%d g=%lf j=%d k=%d\n",i,g,j,k);

Page 120: Il linguaggio C - people.ding.unisannio.it

120

Il linguaggio C 239/243

Input formattato da stdin

ottiene a video:

OK: i==14 g==9.1 j==7 k==21

NB la stringa di formato viene scandita dalla scanf, e se nel formato c'è un

blank (uno spazio bianco) l'input può contenere più caratteri di spaziatura

blank tab newline ecc. che non vengono considerati (cioè vengono considerati

come un unico carattere blank).

Il linguaggio C 240/243

Accesso ai files

Problema: Leggere i dati da un file, e scrivere dei dati su un altro file.

Supponiamo di avere un file c:\users\input.txt di testo formato da linee

costituite da due double x e y (ovvio in forma di caratteri ascii).

139.2 29.1

-13.1 1009.0

.... ....

Vogliamo leggere tutte le coppie e scrivere su un file di testo

c:\users\output.txt le sole coppie in cui x>0

Page 121: Il linguaggio C - people.ding.unisannio.it

121

Il linguaggio C 241/243

Accesso ai files

int main(void) {

FILE *finput, *foutput; double x,y;

int result;

finput=fopen("c:\\input.txt","rt");

if ( finput==NULL ) {

printf("errore: impossibile aprire file di

input\n");

exit(1);

}

foutput=fopen("c:\\output.txt","wt");

if ( foutput==NULL ) {

printf("errore: impossibile aprire file di

output\n");

exit(1);

}

Il linguaggio C 242/243

Accesso ai files/* fino a che non sono alla fine del file di input

*/

while( (result=fscanf(finput,"%lf %lf\n", &x, &y))

!= EOF ) {

if(result != 2) {

/* ho letto meno campi di quelli

richiesti */

printf("errore in lettura\n");

exit(1);

}

if( x > 0.0 )

fprintf(foutput,"%f %f\n",x,y);

}

fclose(finput);

fclose(foutput);

return(0);

}

Page 122: Il linguaggio C - people.ding.unisannio.it

122

Il linguaggio C 243/243

Accesso ai filesNB. prima di essere letto o scritto un file deve essere aperto mediante la funzione fopen

FILE *fopen( char *nomefile, char *modo);

il primo parametro specifica il nome del file ed eventualmente il percorso per raggiungerlo come

ad es: c:\\pippo.txt. NOTARE la necessità d'usare la doppia BACKSLASH \\ posto di una

sola.

il secondo parametro specifica il modo di apertura del file, che sara' diverso a seconda dell'uso

che si vuole fare del file. Il modo puo' essere uno dei seguenti

"r" apertura in sola lettura, per leggere, posiziona all'inizio del file. "w" apertura in sola scrittura,

crea il file se non esiste, lo cancella e si posiziona all'inizio se gia' esiste.

"a" apertura in scrittura per aggiungere dati alla fine del file, se il file esiste gia' si posiziona alla

fine del file per poter aggiungere dati senza cancellare quelli presenti

A questi modi si puo' aggiungere un "+" ottenendo "r+" "w+" "a+" per permettere

l'aggiornamento in lettura e scrittura. La posizione in cui ci si colloca e' quella dovuta a "r" o "w"

o "a"

A questi modi si puo'' aggiungere un "t" per indicare che si apre il file come un file di testo, ed

allora saranno utilizzabili le primitive di lettura e scrittura di testo fgets, fputs, oppure un "b" ad

indicare che il file verra' aperto come un file binario. Per default il modo di apertura é di tipo testo

"t".

Il linguaggio C 244/243

Accesso ai filesLa funzione fopen restituisce un PUNTATORE a FILE detto file pointer che punta ad una

struttura chiamata FILE che contiene le informazioni fondamentali sul file, quali il modo di

apertura del file, il nome del file, l'indirizzo di un buffer in cui sono conservati i dati letti dal file e

la posizione corrente nel buffer.

L'utente non deve conoscere questi dettagli, ma deve memorizzare in una variabile questo valore

restituito ed utilizzarlo in tutte le altre letture o scritture su file.

Quando il file e' stato aperto, possiamo leggerlo e/o scriverlo a seconda del modo di apertura

utilizzando le seguenti funzioni di libreria.

int getc(FILE *f);

int putc(int c, FILE *f);

int fscanf(FILE *finput, char *format, ... );

int fprintf(FILE *foutput, char *format, ... );

char *fgets(char *dest, int nchar, FILE *finput);

Page 123: Il linguaggio C - people.ding.unisannio.it

123

Il linguaggio C 245/243

I/O da file

Quando il file non serve più, deve essere chiuso con la chiamata alla funzione di

libreria

int fclose( FILE *f);

che restituisce 0 in caso vada tutto bene, 1 in caso di errore.

int getc(FILE *f);

legge il prossimo carattere dal file f, restituisce il carattere oppure EOF in caso di errore

o di fine file.

int putc(int c, FILE *f);

scrive il carattere c sulla posizione corrente del file. restituisce il carattere scritto

oppure EOF in caso di errore.

Il linguaggio C 246/243

I/O da file

int fscanf(FILE *finput, char *format, ... );

e' analoga alla scanf ma prende input dal file finput invece che dallo standard input restituisce il

numero di campi letti oppure EOF se la posizione corrente nel file e' a fine file

int fprintf(FILE *foutput, char *format, ... );

e' analoga alla printf ma scrive sul file invece che sullo standard output restituisce il numero di

byre scritti

La lettura scrittura da un file puo' essere effettuata anche linea per linea usando le seguenti

funzioni:

char *fgets(char *dest, int nchar, FILE *finput);

legge dal file finput una linea di input (compreso il NEWLINE \n) fino ad un massimo di nchar-

1 caratteri e la scrive nel vettore dest. Aggiunge dopo l'ultimo carattere un '\0' per terminare la

stringa. Restituisce un puntatore alla stringa in cui ha scritto oppure NULL in caso di errore

int *fput(char *string, FILE *foutput);

scrive sul file foutput una stringa ( con o senza il NEWLINE \n ) restituisce 0 se va tutto bene,

EOF in caso di errore.

Page 124: Il linguaggio C - people.ding.unisannio.it

124

Il linguaggio C 247/243

La gestione dell’errore

Esiste una variabile globale intera, definita nell'header errno.h che viene

settata nel caso in cui una chiamata di sistema non possa eseguire

correttamente il suo compito. Tale variabile allora indica il tipo di errore

avvenuto.

#include <errno.h>

extern int errno;

Tale variabile può essere letta e scritta come ogni altra variabile.

In particolare il valore di questa variabile serve come indice per una variabile

di sistema che è un vettore di stringhe. Queste stringhe contengono dei

messaggi di errore caratteristici dell'errore avvenuto.

#include <stdio.h>

const char *sys_errlist[];

Il linguaggio C 248/243

La gestione dell’erroreNel caso una funzione di sistema ci avvisi di un errore possiamo farci

visualizzare a video (sullo standard error) notifica l'errore, utilizzando la

funzione

void perror(const char *str);

La stringa puntata da str viene visualizzata prima del messaggio di errore. Se

l'ultima chiamata di sistema non ha causato errno, perchè il suo valore non è

definito.#include <stdio.h>

#include <errno.h>

void apri_file(void) {

FILE *finput;

finput=fopen(“/home/user/input.txt","rt");

if ( finput==NULL ) {

perror("errore in funzione apri_file”);

exit(1);

}

}

Page 125: Il linguaggio C - people.ding.unisannio.it

125

Il linguaggio C 249/243

La gestione dell’errore

errno è una variabile globale definita dalla libreria.

Se la si legge senza che si sia verificato un errore il risultato è indefinito, la

stess cosa succederà per la funzione perror.

Prima di poter utilizzarle, quindi è necessario verificare che si sia verificato un

errore:finput=fopen(“/home/user/input.txt","rt");

if ( finput==NULL ) {

perror("errore in funzione apri_file”);

exit(1);

}

Il linguaggio C 250/243

I/O da file

Quando un file precedentemente aperto non serve più, deve essere chiuso con la chiamata alla

funzione di libreria

int fclose( FILE *f);

che restituisce 0 in caso vada tutto bene, 1 in caso di errore.

Poichè l'I/O con le funzioni viste è bufferizzato, potrebbero essere rimaste ancora delle operazioni

di scrittura da completare, cioè qualche byte scritto sul file in precedenza potrebbe essere ancora

in un buffer temporaneo, e non essere stato ancora fisicamente scritto sul file. Con la chiamata alla

fclose si effettuano definitivamente eventuali operazioni di scrittura rimaste in sospeso.

La funzione

int fflush( FILE *f);

effettua esplicitamente lo svuotamento dei buffer facendo completare le operazioni rimaste in

sospeso riguardanti lo stream di output indicato da f.

Serve ad assicurarci che ad un certo istante le operazioni precedenti siano state effettuate. Può

servire ad esempio dopo alcune printf per far effettivamente scrivere i caratteri sul video. In

qualche caso infatti tale operazione potrebbe essere dilazionata. fflush() restituisce 0 in caso vada

tutto bene, EOF in caso di errore.

Page 126: Il linguaggio C - people.ding.unisannio.it

126

Il linguaggio C 251/243

I/O da file

La funzione

int feof( FILE *f);

restituisce un valore diverso da 0 se la posizione corrente del file ha raggiunto la fine

del file. Restituisce 0 se non siamo alla fine del file.

La funzione

int ferror( FILE *f);

restituisce un valore diverso da 0 se lo stream ha verificato un qualche tipo di errore.

Il linguaggio C 252/243

I/O in blocchiINPUT / OUTPUT di BLOCCHI di byte da file

ANSI C mette a disposizione anche delle primitive che permettono di leggere/scrivere su un file un

blocco di byte di una dimensione specificata. I prototipi di queste finzioni sono anch'essi contenuti

nell'header stdio.h, e tali funzioni sono:

size_t fread(void *ptr, size_t size, size_t number, FILE *finput);

cerca di leggere dal file finput un blocco di byte formato da number blocchi ciascuno di size byte. I

dati letti sono scritti nell'area dati puntata da ptr, che deve ovviamente essere correttamente allocata,

cioè di dimensioni sufficienti. fread restituisce il numero di blocchi letti, e non il numero di byte letti.

Se all'inizio della lettura si incontra la fine del file la funzione fread restituisce 0, perché non riesce a

leggere neanche un blocco. Però anche in caso di un qualche tipo di errore fread restituisce 0. Quindi

se fread restituisce zero è necessario utilizzare le funzioni ferror o feof per capire se è accaduto un

errore o se si è raggiunta la fine del file.

size_t fwrite(const void *ptr, size_t size, size_t num, FILE*fout);

cerca di scrivere sul file foutput un blocco di byte formato da number blocchi ciascuno di size byte,

copiandoli dall'area dati puntata da ptr. fwrite restituisce il numero di blocchi scritti, e non il numero

di byte. Come per la fread in caso di un qualche tipo di errore fwrite() restituisce 0.

Page 127: Il linguaggio C - people.ding.unisannio.it

127

Il linguaggio C 253/243

Riga di comandoL’ANSI C mette a disposizione il modo di passare al programma dei parametri nel

momento in cui questo viene lanciato. Partendo ad esempio da una shell di comandi di

Windows NT (una finestra simil DOS per intenderci) possiamo far seguire al nome del

programma una serie di caratteri separati da spazi,

che verranno copiati e passati sotto forma di stringhe in un vettore di puntatori a char

passati come argomento al main.

Il main dovrà essere a tal scopo definito come nell'esempio seguente, dove:

- argc indica il numero di parametri passati più uno (il nome del programma).

- argv è un vettore di stringhe che contiene la riga di comando, in prima posizione il

nome del programma lanciato, nelle seguenti i parametri passati Il programma stampa il

numero dei parametri passati a riga di comando compreso il nome del programma, e

poi li stampa ad uno ad uno

Il linguaggio C 254/243

Riga di comando

#include <stdio.h>

int main(int argc, char *argv[]) {

int i;

printf("argc=%d { ",argc); for ( i=0; i<argc; i++)

printf("%d:%s ", i, argv[i]); printf("}\n");

}

Se il programma si chiama param, può essere eseguito "lanciandolo" dalla shell di comando

mediante una delle seguenti linee di comando, ottenendo come output:

linea di comando | output

|

param calv hobbes 3.0 40 | argc=5 { 0:par 1:cal 2:hobbes 3:3.0 4:40}

param | argc=1 { 0:param }

param calvin | argc=2 { 0:param 1:calvin }

Page 128: Il linguaggio C - people.ding.unisannio.it

128

Il linguaggio C 255/243

I/O di basso livello

Abbiamo in precedenza visto che è possibile effettuare I/O da file, utilizzando primitive

di tipo fopen, fread fwrite, fscanf, fgets. Tali operazioni sono però limitate ai file.

Quello che si vorrebbe avere a disposizione sono delle primitive che possono agire

indifferentemente su file o su dispositivi di I/O di tipo diverso, quali interfacce di

rete, interfacce seriali ecc. Si vorrebbe cioè poter utilizzare una stessa primitiva per

accedere a entità di tipo diverso. Esistono a tale scopo delle funzioni di librerie

cosiddette per l'I/O di basso livello, che permettono di operare su diversi device.

Vengono ad esempio utilizzate per scrivere applicazioni per comunicazioni via rete.

Ogni dispositivo per I/O, verrà aperto con una funzione detta open che restituisce un

intero detto "descrittore di file". Questo numero intero rappresenta un indice per una

struttura dati che descrive le modalità con cui operare sul dispositivo. Tale descrittore

verrà utilizzato in tutte le chiamate per l'effettuazione di I/O.

Poiché queste istruzioni sono di basso livello, non esiste più distinzione tra file di testo

e file binari. Tutti i file sono visti come concatenazione di byte.

Il linguaggio C 256/243

I/O di basso livelloUn esempio d'uso di queste primitive è qui di seguito riportato.

/*leggo i byte di un file binario. */

#include <stdio.h>

#include <fcntl.h>

main(int argc,char **argv) {

int fd;

int bytes_read;

char byte;

if((fd=open(argv[1],O_RDONLY))==-1)

exit(1); /*errore,file non aperto*/

while ( (bytes_read=read(fd,&byte,1))>0 ) { /* leggo un

byte */

.... uso il byte letto

}

if (bytes_read==-1)

exit(1); /* errore in lettura file */

close(fd);

}

Page 129: Il linguaggio C - people.ding.unisannio.it

129

Il linguaggio C 257/243

I/O#include <stdio.h>

#include <fcntl.h>

Per aprire un file si usa la funzione:

int open(char *filename, int flag, int perms)

che ritorna un file descriptor con valore >=0, oppure -1 se l'operazione fallisce.

Il parametro flag controlla l'accesso al file ed è una combinazione (un OR bit a bit) ha i seguenti

predefiniti valori definiti nel file fcntl.h: O_APPEND, O_CREAT, O_EXCL, O_RDONLY,

O_RDWR, O_WRONLY ecc., con i seguenti significati:

O_APPEND apre per aggiungere dati in fondo al file, e posiziona il puntatore alla posizione

corrente alla fine del file O CREAT crea un file nuovo se non esiste

O_EXCL viene usato con O_CREAT, se il file già esiste causa errore O_RDONLY apre e

consente solo la lettura, si posiziona all'inizio del file O RDWR apre e consente sia lettura che

scrittura si posiziona all'inizio

O_WRONLY apre e consente solo la scrittura

O_TRUNC se il file esiste viene troncato, riparte da lunghezza zero

Il parametro "perms" specifica i permessi da assegnare al file quando questo viene creato, in caso

contrario viene ignorato.

Il linguaggio C 258/243

I/O

Per creare un file si puo' anche usare la funzione:

int creat(char *filename, int perms)

Per chiudere un file si usa:

int close(int fd)

Per leggere/scrivere uno specificato numero di bytes da/su un file immagazzinati in una

locazione di memoria specificata da "buffer" si utilizzano:

int read(int fd, char *buffer, unsigned length)

int write(int fd, char *buffer, unsigned length)

Queste due funzioni ritornano il numero di byte letti/scritti o -1 se falliscono.

Page 130: Il linguaggio C - people.ding.unisannio.it

130

Il linguaggio C 259/243

I/O ad accesso casuale

Un file può essere acceduto in lettura o scrittura anche in una maniera diversa da

quella sequenziale, cioè è possibile spostarsi in un file aperto ovvero spostare il

cosiddetto puntatore alla posizione corrente nel file.

Questo puntatore è un intero e indica il prossimo byte da leggere o scrivere. Il

primo byte del file è indicato da 0, il secondo da 1 e così via.

E possibile spostare la posizione corrente utilizzando una primitiva C:

int fseek(FILE *stream, long offset, int ptrname);

fseek stabilisce la nuova posizione del prossimo byte da leggere o scrivere. La

nuova posizione viene indicata da due parametri: una base indicata da ptrname

che puo' essere o l'inizio del file (SEEK_SET) o la posizione corrente

(SEEK_CUR) o la fine del file (SEEK_END), ed uno scostamento cioè la

distanza dalla base espressa in numero di byte, che è il parametro offset.

La funzione restituisce -1 in caso di errore, 0 se tutto OK.

Il linguaggio C 260/243

I/O ad accesso casuale

/* lettura del byte in posizione 135 nel file */

int c;

if ( ! fp=fopen("prova.dat","rt") )

exit(1);

if ( (fseek(fp, (long)135, SEEK_SET)) <0 )

exit(2); /* errore */

c = getc(fp);

Page 131: Il linguaggio C - people.ding.unisannio.it

131

Il linguaggio C 261/243

Operatori sui bit

Il C mette a disposizione degli operatori che lavorano su numeri di tipo intero (char, int,

long int) manipolando il dato a livello di singolo bit. Non si applicano ad operandi

floating point o a dati di tipo strutturato.

& AND (bit a bit)

| OR ''

^ OR ESCLUSIVO (XOR)

~ NOT (complemento ad uno)

>> SHIFT (SCORRIMENTO) a DESTRA

<< SHIFT (SCORRIMENTO) a SINISTRA

Il linguaggio C 262/243

Operatori sui bit

L'operatore AND & effettua un and bit a bit tra due operandi, ovvero setta ad 1 un bit in una certa

posizione quando entrambi i bit in quella posizione nei due operandi valgono 1, altrimenti lo setta a 0.

unsigned char op1, op2, op3;

op1=63; 0 0 1 1 1 1 1 1

op2=240; 1 1 1 1 0 0 0 0

op3 = op1 & op2; 0 0 1 1 0 0 0 0

op3 assume valore 48

L'operatore OR | effettua un or bit a bit tra due operandi, ovvero setta ad 1 un bit in una certa posizione

quando almeno uno dei bit in quella posizione dei due operandi vale 1 altrimenti lo setta a 0.

unsigned char op1, op2, op3;

op1=60; 0 0 1 1 1 1 0 0

op2=240; 1 1 1 1 0 0 0 0

op3 = op1 | op2; 1 1 1 1 1 1 0 0

op3 assume valore 252

Page 132: Il linguaggio C - people.ding.unisannio.it

132

Il linguaggio C 263/243

Operatori sui bit

L'operatore XOR ^ effettua un or esclusivo bit a bit tra due operandi, ovvero setta ad 1 un bit in

una certa posizione quando i bit in quella posizione nei due operandi sono diversi, altrimenti lo

setta a 0.

unsigned char op1, op2, op3;

op1=60; 0 0 1 1 1 1 0 0

op2=240; 1 1 1 1 0 0 0 0

op3 = op1 ^ op2; 1 1 0 0 1 1 0 0

op3 assume valore 204

Questo operatore presenta una tabella di verità (bit a bit) siffatta:

op1 op2 op1^op2

0 0 0

1 0 1

0 1 1

1 1 0

op3 assume valore 195

Il linguaggio C 264/243

Operatori sui bit

L'operatore NOT ~ è un operatore unario che inverte lo stato di ogni bit, cioè

setta ad 1 i bit che valgono 0, e viceversa setta a 0 i bit che valgono 1.

op1=60; 0 0 1 1 1 1 0 0

op3 = ~op1; 1 1 0 0 0 0 1 1

Page 133: Il linguaggio C - people.ding.unisannio.it

133

Il linguaggio C 265/243

Operatori sui bit

Gli operatori di scorrimento (SHIFT) sono operatori unari che realizzano lo

spostamento a sinistra o a destra dei bit di una variabile di tipo intero, mettendo a 0 i

bit che entrano, cioè non rientrano da sinistra i bit usciti da destra. La forma generale

dell'istruzione di SHIFT A DESTRA è del tipo:

variabile_intera >> numero_posizioni

Significa che i bit di variabile_intera vengono tutti spostati a destra di

numero_posizioni.

op1=61; 0 0 1 1 1 1 0 1

op3 = op1>>1; 0 0 0 1 1 1 1 0

op3 assume valore 30 (in neretto il bit entrante)

Si noti come l'operatore corrisponda ad una divisione intera per multipli di 2. Shiftare a

destra di 1 significa dividere per 2, shiftare di 2 è dividere per 4.

op3 = op1>>3; 0 0 0 0 0 1 1 1

op3 assume valore 7 (in neretto i bits entranti)

Il linguaggio C 266/243

Operatori sui bit

La forma generale dell'istruzione di SHIFT A SINISTRA è del tipo:

variabile_intera << numero_posizioni

Significa che i bit di variabile_intera vengono tutti spostati a sinistra di

numero_posizioni. I bits entranti da destra sono posti a 0.

op1=61; 0 0 1 1 1 1 0 1

op3 = op1<<1; 0 1 1 1 1 0 1 0

op3 assume valore 122 (in neretto il bit entrante)

Notare come questo operatore corrisponda al risultato di una moltiplicazione intera

per un multiplo di due finchè a sinistra non esce qualche bit ad 1. “Shiftare” a

sinistra di 1 significa moltiplicare per 2

op3 = op1<<3; 1 1 1 0 1 0 0 0

op3 assume valore 232 (in neretto i bit

entranti)

Page 134: Il linguaggio C - people.ding.unisannio.it

134

Il linguaggio C 267/243

Operatori compositi sui bit

Il C mette a disposizione anche operatori che coniugano un'operazione bit a bit

all'operazione di assegnamento.

AND bit a bit e Assegnamento x &= y equivale a x = x&y

OR bit a bit e Assegnamento x |= y equivale a x = x|y

XOR bit a bit e Assegnamento x ^= y equivale a x = x^y

Shift a Destra e Assegnamento x >>= y equivale a x = x>>y

Shift a Sinistra e Assegnamento x <<= y equivale a x = x<<y

Il linguaggio C 268/243

Alcune funzioni della libreria C standardManipolazione dei buffer

#include <memory.h>

void *memchr (void *s, int c, size_t n) - Cerca un carattere c nei primi

n caratteri di un buffer puntato da s. Restituisce un puntatore al primo carattere c trovato,

o NULL se non lo trova.

int memcmp (void *s1, void *s2, size_t n) - Paragona i primi n byte di

due buffers s1 ed s2. Restituisce un risultato minore di zero se s1<s2, uguale a zero se

s1=s2, maggiore di zero se s1>s2.

void *memcpy (void *dest, void *src, size_t n) - Copia n byte di un

buffer in un altro. Causa problemi se le due aree sono sovrapposte.

void *memmove (void *dest, void *src, size_t n) - Copia n byte di

un buffer in un altro. Funziona correttamente anche se le due aree sono sovrapposte.

Restituisce un puntatore alla destinazione.

void *memset (void *s, int c, size_t n) - Setta tutti i bytes di un buffer

ad un dato carattere. Restituisce un puntatore al buffer s.

Page 135: Il linguaggio C - people.ding.unisannio.it

135

Il linguaggio C 269/243

Classificazione di caratteri#include <ctype.h>

int isalnum(int c) - Vero se "c" e' alfanumerico, '0'...'9' 'a'...'z' 'A'...'Z'. int isalpha(int c) -

Vero se "c" e' una lettera dell'alfabeto. int iscntrl(int c) - Vero se "c" e' un carattere di controllo, i

primi 31.

int isdigit(int c) - Vero se "c" e' un numero decimale.

int islower(int c) - Vero se "c" e' una lettera minuscola.

int isprint(int c) - Vero se "c" e' un carattere stampabile.

int ispunct (int c) - Vero se "c" e' un carattere di punteggiatura.

int isspace(int c) - Vero se "c" e' un carattere spazio cioè tab o blanck o newline o

carriage return..

int isupper(int c) - Vero se "c" e' una lettera maiuscola.

int isxdigit(int c) - Vero se "c" e' un numero esadecimale.

int toascii(int c) - Converte "c" in ASCII, settando a zero il bit piu‘ significativo.

tolower(int c) - Converte "c" in minuscolo.

int toupper(int c) - Converte "c" in maiuscolo.

Il linguaggio C 270/243

Conversione dei dati

#include <stdlib.h>

double atof(char *string) -Converte una stringa in un valore floating point.

int atoi(char *string) - Converte una stringa in un valore integer.

int atol(char *string) - Converte una stringa in un valore long integer.

char *itoa(int value, char *string, int radix) - Converte un valore integer in una

stringa utilizzando il "radix" come base per la conversione (es 10 per conversione di numeri in base 10).

Le seguenti funzioni effeftuano un maggiore controllo sull'errore:

char *ltoa(long value, char *string, int radix) - Converte un valore long integer

in una stringa in un dato "radix".

double strtod(char *string, char *endptr) - Converte una stringa in un valore in

floating point.

long strtol(char *string, char *endptr, int radix) - Converte una stringa in un

valore long integer utilizzando un dato "radix".

unsigned long strtoul(char *string, char *endptr, int radix) - Converte

una stringa in un valore long senza segno.

Page 136: Il linguaggio C - people.ding.unisannio.it

136

Il linguaggio C 271/243

Alcune funzioni matematiche#include <math.h>

nota le funzioni matematiche vengono definite nella libreria libm.a, per includerla è

necessario effettuare il link con l’opzione –lm.

int abs (int n) - valore assoluto di un intero.

double acos(double x) - arcocoseno di x.

double atan(double x) - arcotangente

double ceil(double x) - il piu piccolo intero maggiore o uguale a x.

double cos(double x) - coseno di angolo x in raduanti

double exp(double x) - esponenziale

double log(double x) - logaritmo naturale

void randomize(void) - inizializza il generatore di numeri casuali.

double fabs (double x ) - valore assoluto di un double

void srand(unsigned seed) - inizializza il generatore di numeri casuali.

int random(int max_num) - genera un numero casuale tra 0 e max_num.

double pow (double x, double y) - potenza, x elevato alla y.

Il linguaggio C 272/243

Allocazione di Memoria

#include <malloc.h>

void *calloc(size_t num elems, size_t elem_size) -

Alloca un vettore ed inizializza tutti gli elementi a zero.

void free(void *mem address) - Libera un blocco di memoria.

void *malloc(size_t num bytes) - Alloca un blocco di memoria.

Page 137: Il linguaggio C - people.ding.unisannio.it

137

Il linguaggio C 273/243

Funzioni su stringhe

#include <string.h>

int strlen(char *string) - Determina la lunghezza di una stringa.

int strcmp(char *string1, char *string2) - Confronta string1 e string2 per determinare

l'ordine alfabetico.

char *strcpy(char *string1, char *string2)- Copia string2 in stringl.

char *strncat(char *string1, char *string2, size_t n) - Aggiunge "n“ caratteri di string2 in string1.

int strncmp(char *string1, char *string2, size_t n) - Confronta i primi "n“ caratteri di due

stringhe.

char *strncpy(char *string1, char *string2, size_t n) - Copia i primi "n“ caratteri di string2 in

string1.

int sprintf(char *string, char *format_string, args) Scrive output formattato su una stringa,

comportamento analogo alla printf, ma scrive su una stringa.

int sscanf(char *buffer, char *format_string, args) Legge input formattato da una "string",

comportamento analogo alla scanf, ma prende l'input da una stringa

Il linguaggio C 274/243

Funzioni per la cattura del tempo#include <time.h>

time_t time(time_t *t);

restituisce il tempo trascorso a partire dal 1 gennaio del 1979, misurato in secondi. Se il puntatore

passato come argomento e' diverso da NULL, scrive lo stesso valore nella locazione di memoria

puntata dal puntatore. In caso di errore restituisce -1 e setta errno in modo appropriato.

int gettimeofday(struct timeval *tv, struct timezone *tz);

l'argomento di tipo timezone e' obsoleto, e viene istanziato a NULL. La struttura di tipo timeval e'

così definita:

struct timeval {

long tv_sec; /* seconds */

long tv_usec; /* microseconds */

};

La funzione scrive nella struttura puntata da tv il valore del clock corrente, ed e' quindi piu' precisa

della time che mette a disposizione solo un valore in secondi. Restituisce 0 se tutto OK, altrimenti -

1 se si verifica qualche errore.

Page 138: Il linguaggio C - people.ding.unisannio.it

138

Il linguaggio C 275/243

Funzioni per la cattura del tempoclock_t clock(void);

restituisce il tempo di CPU utilizzato dal processo chiamante, a partire dalla prima volta

che è stata chiamata la clock(). Il tempo restituito è la somma del tempo utilizzato dal

processo chiamante e dai processi di sistema nell'esecuzione delle chiamate di sistema

effettuate dal processo chiamante. Il valore restituito è convenzionalmente il numero di

clock eseguiti. Per ottenere il tempo in secondi deve essere diviso per la costante

CLOCKS_PER_SEC.

clock_t start, end;

start = clock();

istruzioni del programma ...

end = clock();

printf("tempo consumato in secondi: %lf\n",

(double)(end-start) / (double)CLOCKS_PER_SEC

);