02 introduzione al C, variabili, espressioni, cast, indentazione.pptx

Transcript

02 introduzione al C, variabili, espressioni, cast, indentazione.pptx
27/11/14 Introduzione al linguaggio C
Qualche nota storica
¨ 
¨ 
¨ 
¨ 
¨ 
C sviluppato da Dennis M. Ritchie agli AT&T Bell Labs fra il
1969 e il 1973
Nato come linguaggio di programmazione per lo sviluppo
di Unix e per sopperire alle limitazioni del linguaggio “B”
Ritchie e Brian Kernighan nel 1978 pubblicano il libro "The
C Programming Language". Il dialetto descritto nel libro è
noto come K&R-Style
Grazie alla portabilità, il linguaggio iniziò subito ad essere
apprezzato. Ma è mancata una versione standard del
linguaggio fino ANSI 1989 (ANSI-C)
Ultime revisioni dello Standard risalgono al 1999 e al 2011
e sono dette Standard C99 e Standard C11
1 27/11/14 Linguaggio C
http://spectrum.ieee.org/static/interactive-the-top-programming-languages
Linguaggio C
¨ 
Software che forse avete usato e che sono scritti in C:
¤  Java
virtual machine
¤  Linux (ma una piccola parte è in assembly)
¤  Kernel di Mac OS X
¤  Windows (in C e in C++)
¤  Oracle DBMS (in C e in C++)
¤  Firmware dei router Cisco
2 27/11/14 Caratteristiche di base
Linguaggio imperativo, procedurale
¨  Linguaggio compilato, ovvero, il codice sorgente
viene tradotto interamente da un compilatore in
codice eseguibile
¨ 
Variabili
3 27/11/14 variabili
¨ 
¨ 
¨ 
¨ 
Per ogni variabile esistono due operazioni
fondamentali:
Dichiarazione: il compilatore deve sapere quanto
spazio riservare per la variabile e come interpretare i
bit contenuti in questa area di memoria
Assegnamento (modifica) di un valore: calcolo di un
nuovo valore e modifica del valore corrente di una
variabile. Il valore contenuto in una variabile è l’ultimo
assegnatole e resta inalterato finché un successivo
assegnamento non modifica il valore stesso
Una variabile può essere usata (in un’espressione, per
modificarne il valore) solo dopo essere stata dichiarata
variabili
¨ 
Dichiarazione:
int fattoriale;
¨ 
Assegnamento:
fattoriale = 1;
¨ 
Dichiarazione e inizializzazione:
int fattoriale = 1;
¨ 
¨ 
Il valore assegnato deve essere coerente con il tipo della
variabile
Il C, come altri linguaggi di programmazione, è fortemente
tipato, cioè per ogni espressione utilizzata nel programma il
compilatore deve sempre essere in grado di determinarne il
tipo
4 27/11/14 Dichiarazione di costanti
¨ 
Sintassi
const <tipo> <identificatore> = <espr>;
Es.:
const int N = 100;
const float PIGRECO = 3.1415F;
const char SIM = 'A';
¨ 
¨ 
Le costanti sono variabili alle quali non è permesso
cambiare valore: non possono cioè trovarsi a sinistra
dell’operatore di assegnamento
Convenzione: il nome di costante si scrive interamente
con caratteri maiuscoli
Dichiarazione di costanti simboliche
¨ 
Per definire una costante simbolica si scrive:
#define <nome> <valore>
Es.:
#define PIGR 3.1415
#define MSG "ciao mondo!"
¨ 
¨ 
Convenzione: come le costanti, devono essere scritte interamente in
maiuscolo
Differenza tra #define e const:
¤  Una costante definita con const occupa memoria come una
variabile, ma non può essere modificata
¤  Una costante definita con #define non occupa memoria: il
preprocessore del C, prima di compilare il codice, sostituisce ogni
simbolo con il suo corrispondente valore (come se fosse un
“sostituisci tutti” in un editor di testo).
5 27/11/14 Variabili
¨ 
¨ 
¨ 
Una variabile non può essere “vuota”: a una variabile è
sempre associata una locazione di memoria e quindi un
valore, che corrisponde al valore dei bit contenuti nella
zona di memoria
Se, ad esempio, il compilatore associa a una variabile
la locazione di memoria con indirizzo 2341, allora la
variabile assumerà come valore il valore “casuale” dei
bit all’indirizzo 2341 (ad esempio 42)
Se non provvedete voi, inizialmente la variabile
fattoriale conterrà quindi “spazzatura”
Regola d’oro in C:
Inizializzare sempre le variabili!
Variabili
¨ 
Ogni variabile è caratterizzata da:
¤  Nome:
nome simbolico con cui si fa riferimento alla
variabile all’interno di un programma
¤  Tipo: definisce l'insieme dei valori che la variabile può
assumere
¤  Indirizzo: identifica un'area di memoria che mantiene il
valore corrente della variabile
¤  Valore: valore corrente della variabile
¨ 
Ad esempio:
¤  nome:
fattoriale
¤  tipo: int
¤  indirizzo: 2341
¤  valore: 42
6 27/11/14 Tipi primitivi
¨ 
Un tipo di dato è caratterizzato da:
¤  la
rappresentazione in memoria dei dati di quel
particolare tipo (es. quanti bit si usano)
¤  l’insieme di operazioni ammissibili su dati di quel tipo
¨ 
A cosa servono i tipi di dati?
¤  Per
permettere di effettuare controlli statici (cioè al
momento della compilazione e non in fase di
esecuzione) sulla correttezza sintattica del programma
e quindi prevenire errori di programmazione
Tipi primitivi
Tipi primitivi del C: char, int, short, long, float, double
¨  I tipi char, int, short, long contengono numeri interi, float e
double numeri in virgola mobile
¨  Specificatori opzionali: signed, unsigned
¨ 
7 27/11/14 Tipi primitivi
¨ 
¨ 
Lo standard C, che è pensato per una grande varietà di architetture
diverse, non specifica esattamente quanto siano grandi i tipi primitivi
Per sapere quanti byte occupa un determinato tipo su una macchina
specifica, si usa a tempo di esecuzione l’operatore sizeof. Ad es.
printf("%d", sizeof(int));
Tipo
Dimensione
char
di solito 1 byte
short
di solito 2 byte
int
di solito 4 byte
long
di solito 8 byte
float
Singola precisione (di solito 4 byte)
double
Precisione doppia (di solito 8 byte)
Tipi interi
¨ 
¨ 
¨ 
¨ 
¨ 
¨ 
I tipi int, short e long permettono di rappresentare
numeri interi positivi e negativi su un numero di bit che
dipende dalla macchina
Le operazioni possibili su variabili di tipo intero sono le
operazioni aritmetiche +, -, *, / e l’operatore %
(modulo)
Attenzione: la divisione tra due interi è un intero
Es.: 16/5 dà come risultato 3
Per avere il resto di questa divisione occorre utilizzare
l’operatore % (modulo)
16%5 dà come risultato 1
8 27/11/14 Tipi interi
¨ 
¨ 
¨ 
¨ 
¨ 
Si può specificare se si rappresentano interi con segno o
senza segno: signed int e unsigned int
Per default i tipi int hanno segno (cioè scrivere
semplicemente int è equivalente a scrivere signed int)
una variabile unsigned è normalmente rappresentata con
lo stesso numero di byte della variabile signed, tuttavia i
valori memorizzati sono privi di segno (e quindi rappresenta
esclusivamente numeri positivi)
il numero di bit usato per rappresentare un int varia tra le
diverse architetture. Lo standard specifica soltanto che
vengano usati almeno 16 bit
Nel file header limits.h sono indicati con costanti
simboliche il valore minimo e massimo che possono assumere
le variabili di un determinato tipo (ad es. INT_MIN,
INT_MAX, UINT_MAX)
Tipo char
Rappresenta un numero; di solito occupa 8 bit
¨  Prevede quindi come operatori quelli aritmetici
¨  Usato di solito per contenere il codice ASCII di un
carattere
¨  Nelle operazioni, bisogna inoltre considerare che le
tabelle ASCII dei caratteri occidentali rispettano il
seguente ordinamento:
'0'<'1'<'2'<...<'9'<...
<'A'<'B'<'C'<...<'Z'<...
<'a'<'b'<'c'<...<'z'
¨ 
9 27/11/14 Tipi interi
Tipo
Dimensione Intervallo rappresentabile
in byte su
x86 a 64
bit
signed char
1
-128 ... +127
unsigned char
1
0 ... +255
signed short
2
-32768 ... +32 767
unsigned short
2
0 ... +65 535
signed int
4
-2 147 483 648 ... +2 147 483 647
unsigned int
4
0 ... +4 294 967 295
signed long
8
-9 223 372 036 854 775 808 ...
+9 223 372 036 854 775 807 (9 miliardi di miliardi)
unsigned long
8
0 ... +18 446 744 073 709 551 615 (18 miliardi di
miliardi)
Tipi interi
Si consideri il seguente programma su una macchina x86 a 64 bit
#include <stdio.h>
#define BIG 2000000000 //2 miliardi
int main(void) {
int a, b = BIG, c = BIG;
a = b + c;
printf("%d\n", a);
}
¨ 
¨ 
¨ 
Nonostante i due addendi siano rappresentabili con int, la loro somma
è superiore al valore massimo rappresentabile da un int. Si ha
overflow
Il compilatore e il runtime del C non segnalano errori e l’esecuzione
prosegue con risultati scorretti (in questo caso -294 967 296)
È compito del programmatore assicurarsi che i valori non escano dagli
intervalli appropriati
10 27/11/14 Tipi float e double
Rappresentano (a precisione singola i float e a
precisione doppia i double) i numeri reali
¨  In realtà sono solo una approssimazione (per
precisione e intervallo dei valori rappresentabili)
dei numeri reali
¨  Le operazioni aritmetiche sui numeri in virgola
mobile non sono necessariamente esatte ma sono
approssimate
¨  Le operazioni possibili sui tipi float e double sono le
operazioni aritmetiche
¨ 
Espressioni
11 27/11/14 Espressioni numeriche
¨ 
Come abbiamo già visto:
¤  4 è un’espressione il cui valore corrisponde alla costante
stessa
¤  x è una variabile, ma è anche un’espressione il cui valore
corrisponde al valore assunto dalla variabile
¤  (x+y)*3-z è un’espressione aritmetica in cui valgono le
normali precedenze tra operatori
Espressioni con operatori di confronto
¨ 
¨ 
Uguaglianza
espr1 == espr2 è un’espressione che assume il valore:
0 quando la valutazione di espr1 restituisce un valore diverso dalla
valutazione di espr2
1 altrimenti.
Es: int x=5, y=7;
l’espressione x == y vale 0 (falso)
mentre x+2 == y vale 1 (vero)
Disuguaglianza
espr1 != espr2 è un’espressione che vale 1 quando espr1 è diverso da
espr2, 0 altrimenti
Es.: int x=5, y=7;
x != y vale 1 (vero)
x+2 != y vale 0 (falso)
12 27/11/14 Espressioni con operatori di confronto
¨ 
¨ 
Maggiore (minore)
espr1 > espr2 vale 1 (vero) quando espr1 è maggiore di espr2,
0 (falso) altrimenti [analogo ma opposto per il minore]
Es.: int x=5, y=7;
x > y è falsa (0)
x+2 > y è ancora falsa (0)
x+5 > y è vera (1)
Maggiore o uguale (minore o uguale)
espr1 >= espr2 vale 1 quando espr1 è maggiore o uguale di espr2, 0
viceversa.
Es.: int x=5, y=7;
x >= y è falsa (0)
x+2 >= y è vera (1)
x+5 >= y è vera (1)
Espressioni con operatori di confronto
¨ 
Negazione
!(x==y) è vera quando x è diverso da y
!(x>=3) è vera quando x è minore di 3, ovvero quando l’espressione
x>=3 è falsa
!(!x) doppia negazione afferma: l’espressione è vera quando è vera x
13 27/11/14 Valori logici
¨ 
¨ 
¨ 
In C non esiste un tipo booleano. Quando si valuta
un’espressione logica (ad esempio in una istruzione if), si
assume che sia falso un valore uguale a 0 e vero qualsiasi
valore diverso da 0
Quindi in un’espressione logica scrivere “diverso da zero” è
opzionale
if (trovato) …
equivale a
if (trovato != 0) …
È comunque meglio scrivere !=0 se facilita la comprensione
del codice
L’espressione di assegnamento
¨ 
¨ 
Anche l’assegnamento è un’espressione
x = espr
è un’espressione il cui valore corrisponde al valore
dell’espressione espr.
In quanto espressione potrebbe essere usata per discriminare una
condizione vera da una condizione falsa, infatti:
x = 0 è un’espressione falsa perché vale 0
x = 8 è un’espressione vera perché vale 8, diverso da 0
ATTENZIONE! Questa caratteristica è una potenziale fonte
d’errore, spesso difficile da scoprire:
if (eta=18) {…}
Una disattenzione di questo tipo non causa alcun tipo di errore di
compilazione, ma il comportamento del codice non è quello atteso
14 27/11/14 Espressioni complesse
¨ 
¨ 
¨ 
¨ 
Due espressioni possono essere congiunte per mezzo
dell’operatore && (AND logico)
Se x rappresenta l’età di un individuo, diremo che è minorenne se:
x >= 0 && x < 18
L’espressione è vera quando valgono entrambe le condizioni su x
Due espressioni possono essere disgiunte per mezzo
dell’operatore || (OR logico)
Se x rappresenta il peso di un individuo adulto, diremo che non è
normale se:
x < 45 || x > 180
L’espressione è vera quando vale almeno una delle condizioni su x
Cast
Cambiamenti di tipo impliciti ed espliciti
15 27/11/14 Esempio
Scriviamo un programma che converte temperature
Fahrenheit in temperature Celsius
¨  La formula è
cels = (fahr - 32) * 5 * 1/9
¨  Sfruttando la proprietà associativa del prodotto
possiamo scrivere la formula in due modi
equivalenti:
cels = ((fahr - 32) * 5) / 9
cels = (fahr - 32) * (5 / 9)
¨ 
Esempio
#include <stdio.h>
int main(void) {
/* stampa la tabella Fahrenheit-Celsius per fahr=0,
40, ..., 320 */
int fahr, cels1, cels2, inf, sup, incr;
inf = 0; /* estremo inferiore della tabella */
sup = 320; /* estremo superiore della tabella */
incr = 40; /* incremento */
fahr = inf;
printf("fahr\tcels1\tcels2\n");
while (fahr <= sup) {
cels1 = ((fahr - 32) * 5) / 9;
cels2 = (fahr – 32) * (5/9);
printf("%d\t%d\t%d\n", fahr, cels1, cels2);
fahr = fahr + incr;
}
return 0;
}
16 27/11/14 Esempio
¨ 
L’output del programma è:
fahr
cels1 cels2
0
-17
0
40
4
0
80
26
0
120
48
0
160
71
0
200
93
0
240
115
0
280
137
0
320
160
0
Osservazione
Nel nostro programma che differenza c’è tra:
cels1 = ((fahr - 32) * 5) / 9;
e
cels2 = (fahr - 32) * (5 / 9);
17 27/11/14 Osservazione
¨ 
Supponiamo fahr = 120 e valutiamo le due espressioni
cels1 = ((fahr - 32) * 5) / 9;
cels1 = ((120 - 32) * 5) / 9;
cels1 = (88 * 5) / 9;
cels1 = 440 / 9;
cels1 = 48;
cels2 = (fahr - 32) * (5 / 9);
cels2 = (120 - 32) * (5 / 9);
cels2 = 88 * (5 / 9);
cels2 = 88 * 0;
cels2 = 0;
Overloading
In C (come in molti altri linguaggi) operazioni primitive (+,-,*,/,%) sono
definite per tipi omogenei (operandi tutti int, o tutti float, o tutti double,
…)
¨  Overloading di significati: lo stesso simbolo (/) è utilizzato in contesti
diversi e può dare risultati diversi.
int x, y;
/* divisione tra interi, il risultato è
ancora un valore di tipo intero*/
Se x=10 e y=4;
¨ 
x/y vale 2
double x, y;
Se x=10 e y=4.0;
x/y vale 2.5
/* divisione tra float, il
risultato è di tipo float*/
/* divisione tra double, il
risultato è ancora di tipo
double*/
float x, y;
Se x=10.0f e y=4.0f;
x/y vale 2.5
18 27/11/14 Espressioni con tipi non omogenei
Se gli operatori di base sono definiti tra tipi
primitivi omogenei, cosa accade quando si scrive
un’espressione non omogenea?
int x = 5;
double y = 2.0;
Qual è il tipo dell'espressione x/y? Viene eseguita la
divisione tra reali o quella tra interi?
¨ 
Conversioni di tipo implicite
¨ 
¨ 
In C sono possibili operazioni solo tra operandi omogenei, quindi il tipo
degli operandi è temporaneamente forzato in modo che l'espressione
x/y diventi omogenea
Il linguaggio promuove automaticamente, quando necessario, i tipi “più
piccoli” in tipi “più grandi” seguendo la gerarchia
char < short < int < long < float < double
¨ 
¨ 
¨ 
Cioè un valore di tipo char può essere “visto” come un valore di tipo
short oppure int oppure long ecc.
Analogamente un valore di tipo int può essere “visto” come di tipo
long, float o double
Nel nostro esempio, il valore assunto dalla variabile x viene promosso
al tipo double. In questo modo l'espressione x/y diventa omogenea
di tipo double e la divisione diventa in virgola mobile
19 27/11/14 Esempio
int x = 5;
char y = 7;
double r = 5.0;
double k =(x+y)/ r;
¨ 
Passo 1: (x+y)
¤ 
¤ 
¤ 
¨ 
y viene convertito da char all’intero corrispondente
viene effettuata la somma tra interi
risultato intero tmp = 12
Passo 2 (tmp / r)
¤ 
¤ 
¤ 
tmp viene convertito nel double corrispondente (12.0)
viene applicata la divisione tra double
risultato double k=2.4
Compatibilità nell’assegnamento
¨ 
¨ 
¨ 
In generale, sono implicite le conversioni di tipo che non provocano perdita di
informazione
Tuttavia, le espressioni che possono provocare perdita di informazioni non sono
illegali: il compilatore genera un warning. Per “tacitare” il warning si può fare un
cast esplicito
Esempio
int
f =
i =
f =
¨ 
i=5; float f=2.71F; double d=3.1415;
f+i; /* int convertito in float */
d/f; /* double convertito in int! */
d; /* arrotondamento o troncamento */
Al momento della compilazione verrà visualizzato il messaggio:
Possible warning: conversion may lose significant digits
che avvisa che l’assegnamento di un valore float a una variabile intera può
causare la perdita di informazioni e che, di per sé, non è un errore
20 27/11/14 Casting
¨ 
¨ 
In qualunque espressione è possibile forzare una particolare
conversione utilizzando l’operatore di cast
Sintassi
( <tipo> ) <espressione>
¨ 
Esempi
int i=5; double x=7.77; double y=7.1;
i = (int) sqrt(384); //sqrt(384)==19.595917
x = y*y;
i = (int) x % (int) y;
Esempio
¨ 
¨ 
Ricordate. Come il valore 9 rappresenta una costante intera, il valore 9.0 rappresenta
una costante in virgola mobile, quindi la divisione i/9 è diversa dalla divisione
i/9.0. Infatti la prima è intera e la seconda è in virgola mobile
Supponiamo di volere effettuare la divisione in virgola mobile nell’espressione
double cels3 = ((fahr - 32) * 5) / 9;
Non basta dichiarare cels3 di tipo double
Infatti la divisione sarebbe pur sempre quella intera e solo successivamente il risultato
verrebbe promosso a double:
Con fahr = 120, l’espressione
cels3 = ((fahr - 32) * 5) / 9;
assume il valore 48, che verrebbe promosso al tipo double e cioè al valore 48.0
¨ 
¨ 
Perché la divisione sia in virgola mobile occorre forzare uno dei due operandi a essere
di tipo float o double
Esistono diversi modi per ottenere questo risultato.
21 27/11/14 Esempio
¨ 
Un primo modo è quello di usare costanti in virgola mobile invece che intere
cels3 = ((fahr - 32) * 5) / 9.0;
¨ 
Un secondo modo è quello di forzare il tipo di un’espressione con un cast
cels3 = (double)((fahr - 32) * 5) / 9;
Il cast forza al tipo double il valore dell’espressione
((fahr - 32) * 5)
Sempre con fahr = 120 l’espressione ((fahr - 32) * 5) assume il
valore 440, il quale viene forzato al tipo double, quindi 440.0, e
successivamente viene valutata l'espressione 440.0/9
Per quanto detto prima, i tipi dei due operandi vengono resi omogenei: 9 viene
promosso a double e si ha 440.0/9.0 → 48.888… divisione in virgola
mobile!
Cast: osservazione sulle precedenze
¨ 
Poiché l’operatore di cast ha precedenza rispetto agli
operatori aritmetici, nell’espressione
(double)((fahr - 32) * 5) / 9
solo ((fahr - 32) * 5) è soggetta al cast
¨ 
Attenzione! Nella seguente espressione la divisione non è in
virgola mobile: perché?
(double)(((fahr - 32) * 5) / 9)
22 27/11/14 Overflow
Si riconsideri il programma su una macchina x86 a 64 bit
#include <stdio.h>
#define BIG 2000000000 //2 miliardi
int main(void) {
int b = BIG, c = BIG;
long a;
a = b + c;
printf("%ld\n", a);
return 0;
}
¨ 
Rispetto alla versione vista in precedenza, abbiamo cambiato il tipo
della variabile a da int a long. In questo caso si ha ancora
overflow? Perché?
Overflow
¨ 
¨ 
Si ha overflow anche in questo caso: la somma è tra tipi int e dà un
risultato int errato perché in overflow. Solo dopo avviene la
promozione a long
Una possibile soluzione consiste nel forzare la somma tra long:
#include <stdio.h>
#define BIG 2000000000 //2 miliardi
int main(void) {
int b = BIG, c = BIG;
long a;
a = b + (long) c;
printf("%ld\n", a);
return 0;
}
23 27/11/14 Conversioni nei cast
Quando si effettua un cast da un tipo a un altro (o
quando si assegna un valore di un tipo a una
variabile di un altro tipo), viene effettuata una
conversione del valore
¨  Se la conversione avviene tra tipi per i quali ci può
essere perdita di informazione, il compilatore può
emettere un warning (per abilitare questo warning
su gcc si può usare il flag –Wconversion)
¨  Se la conversione è voluta, si può “tacitare” il
warning rendendo il cast esplicito
¨ 
Conversioni nei cast
Es. (su architettura x86 a 64 bit)
int main(void) {
int i1, i2 = 12;
long l=3000000000; //3 miliardi
double d1, d2 = 7.0, d3 = 2.275;
i1 = d2; //warning: implicit conversion turns floatingpoint number into integer e assegna 7
i1
i1
d1
i1
=
=
=
=
(int) d2; //assegna 7
(int) d3; //assegna 2
i2; //assegna 12.0
l; //warning: implicit conversion loses integer
precision e assegna -1294967296
i1 = (int) l; //assegna -1294967296
return 0;
}
24 27/11/14 Note sulla scrittura di codice
Leggibilità del codice sorgente
Un aspetto importante nella scrittura dei programmi
consiste non solo nella correttezza e nell’efficienza
del codice, ma anche nella sua leggibilità
¨  Un codice sorgente facilmente leggibile è più facile
da capire, permette di coglierne la struttura
velocemente ed è più semplice da modificare in un
secondo tempo, quando si fa la manutenzione del
codice
¨  Quindi è importante usare nomi di variabile che
rispecchino il loro uso e indentare il codice
¨ 
25 27/11/14 L’indentazione del codice sorgente
¨ 
¨ 
¨ 
¨ 
¨ 
¨ 
Indentare il codice sorgente significa fare iniziare una
linea a una certa distanza dal margine
Ha lo scopo di mettere in evidenza blocchi di codice
Si fa iniziare la linea con un certo numero di spazi (di
solito multiplo di 3 o di 4) o di TAB
L’indentazione viene ignorata dal compilatore C: è utile
esclusivamente agli esseri umani che metteranno mano
al sorgente del programma
Va aumentato il livello di indentazione per il corpo
delle funzioni, dei cicli, delle istruzioni if else
Il codice va indentato mentre si scrive il codice e non in
un secondo momento
L’indentazione del codice sorgente
Si confronti la leggibilità di queste due versioni (il compilatore C genera il medesimo eseguibile):
#include <stdio.h>
#define SIZE 1000
#define DASTAMPARE 1
#define DANONSTAMPARE 0
#include <stdio.h>
#define SIZE 1000
#define DASTAMPARE 1
#define DANONSTAMPARE 0
int main(void) {
unsigned int array[SIZE];
unsigned int i, j;
int main(void) {
unsigned int array[SIZE];
unsigned int i, j;
}
for (i = 0; i < SIZE; i++)
array[i] = DASTAMPARE;
for (i = 0; i < SIZE; i++)
array[i] = DASTAMPARE;
for (i = 2; i < SIZE; i++)
if (array[i] == DASTAMPARE)
for (j = i + 1; j < SIZE; j++)
if (j % i == 0)
array[j] = DANONSTAMPARE;
for (i = 2; i < SIZE; i++)
if (array[i] == DASTAMPARE)
for (j = i + 1; j < SIZE; j++)
if (j % i == 0)
array[j] = DANONSTAMPARE;
for (i = 2; i < SIZE; i++)
if (array[i] == DASTAMPARE)
printf( "%u\n", i);
for (i = 2; i < SIZE; i++)
if (array[i] == DASTAMPARE)
printf( "%u\n", i);
}
26 27/11/14 Utilizzo di costanti
¨ 
¨ 
Nel codice sorgente bisogna cercare di non utilizzare valori numerici
ma utilizzare delle costanti simboliche.
In questo modo si hanno due vantaggi:
¤  Se fosse necessario cambiarne il valore, sarebbe più facile farlo
perché si evita di modificare il codice sorgente in più punti
¤  È più facile comprendere il loro scopo
Utilizzo di costanti
Si confrontino queste due versioni
#include <stdio.h>
#define SIZE 1000
#define DASTAMPARE 1
#define DANONSTAMPARE 0
int main(void) {
unsigned int array[SIZE];
unsigned int i, j;
#include <stdio.h>
int main(void) {
unsigned int array[1000];
unsigned int i, j;
for (i = 0; i < 1000; i++)
array[i] = 1;
for (i = 0; i < SIZE; i++)
array[i] = DASTAMPARE;
for (i = 2; i < 1000; i++)
if (array[i] == 1)
for (j = i + 1; j < 1000; j++)
if (j % i == 0)
array[j] = 0;
for (i = 2; i < SIZE; i++)
if (array[i] == DASTAMPARE)
for (j = i + 1; j < SIZE; j++)
if (j % i == 0)
array[j] = DANONSTAMPARE;
for (i = 2; i < SIZE; i++)
if (array[i] == DASTAMPARE)
printf("%u\n", i);
for (i = 2; i < 1000; i++)
if (array[i] == 1)
printf("%u\n", i);
}
}
27 27/11/14 Compilazione
¨ 
¨ 
¨ 
¨ 
gcc produce due tipi principali di messaggio: warning ed errori
I warning sono avvertimenti che indicano possibili problemi nel codice
ma non pregiudicano la compilazione
Non tutti i warning vengono visualizzati, quindi è preferibile utilizzare
il flag -Wall: gcc -Wall sorgente.c -o eseguibile
Per un warning, gcc riporta il nome del file sorgente, il numero di
linea e colonna in cui è stato rilevato il warning e un messaggio
Ad es., se nel file sorgente.c alla linea 5 ho un’istruzione if (a=0), gcc
stampa il messaggio:
sorgente:5:8: warning: using the result of an
assignment as a condition without parentheses
¨ 
È buona norma correggere il sorgente in modo da eliminare tutti i
problemi segnalati dai warning
Compilazione
¨ 
¨ 
¨ 
Gli errori, invece, interrompono la compilazione e l’eseguibile non
verrà generato
Anche per gli errori, gcc riporta il nome del file sorgente, il numero di
linea e colonna in cui è stato rilevato il problema e un messaggio
Può succedere che l’errore non sia nella linea segnalata ma nelle
linee precedenti
28