Lezione 3: Tipi e Iteratizione
Soluzione di Esercizi
Esercizio 2.1: Scrivere un programma che legge tre numeri e li stampa in ordine crescente.
#include <stdio.h>
// legge tre numeri e li stampa in ordine crescente
int main() {
int a, b, c;
printf("Digitare tre numeri: ");
scanf("%d %d %d", &a, &b, &c);
if (a <= b) {
if (a <= c) {
if (b <= c) printf("%d %d %d\n", a, b, c);
else printf("%d %d %d\n", a, c, b);
} else printf("%d %d %d\n", c, a, b);
} else {
if (b <= c) {
if (a <= c) printf("%d %d %d\n", b, a, c);
else printf("%d %d %d\n", b, c, a);
} else printf("%d %d %d\n", c, b, a);
}
}
Esercizio 2.2: Scrivere un programma che legge due date nel formato g/m/a
(ad es. 17/4/2009
), le confronta e stampa se la prima data è anteriore, posteriore o uguale alla seconda. Ad esempio, se le date sono 12/5/2009
e 10/7/2009
, il programma deve stampare la prima data è anteriore alla seconda.
#include <stdio.h>
// legge due date nel formato g/m/a e le confronta
int main() {
int g1, m1, a1;
int g2, m2, a2;
printf("inserire le date nel formato g/m/a (ad es. 17/3/2009)\n");
printf("prima data: ");
scanf("%d/%d/%d", &g1, &m1, &a1);
printf("seconda data: ");
scanf("%d/%d/%d", &g2, &m2, &a2);
int risultato; //1: posteriore; -1: anteriore; 0: uguale
if (a1 > a2) risultato = 1;
else if (a1 < a2) risultato = -1;
else {
if (m1 > m2) risultato = 1;
else if (m1 < m2) risultato = -1;
else {
if (g1 > g2) risultato = 1;
else if (g1 < g2) risultato = -1;
else risultato = 0;
}
}
printf("La prima data è ");
if (risultato > 0) printf("posteriore");
else if (risultato < 0) printf("anteriore");
else printf("uguale");
printf("alla seconda\n");
}
Operatori Logici
Varie condizioni di possono combinare usando operatori logici con le sintassi seguenti:
<operatore_logico> <condizione>
<condizione> <operatore_logico> <condizione>
Il C definisce gli operatori logici and &&
, or ||
e not !
di cui tabuliamo i valori di seguito:
a |
b |
and: a && b |
or: a || b |
not: ! a |
---|---|---|---|---|
true |
true |
true |
true |
false |
true |
false |
false |
true |
false |
false |
true |
false |
true |
true |
false |
false |
false |
false |
true |
Esempi
Come esempio, possiamo risolvere i due esercizi precedenti usando i nuovi operatori.
Esercizio 2.1: Scrivere un programma che legge tre numeri e li stampa in ordine crescente.
#include <stdio.h>
// legge tre numeri e li stampa in ordine crescente
int main() {
int a, b, c;
printf("Digitare tre numeri: ");
scanf("%d %d %d", &a, &b, &c);
if (a <= b && b <= c) printf("%d %d %d\n", a, b, c);
else if (a <= b && a <= c) printf("%d %d %d\n", a, c, b);
else if (a <= b) printf("%d %d %d\n", c, a, b);
else if (b <= c && a <= c) printf("%d %d %d\n", b, a, c);
else if (b <= c) printf("%d %d %d\n", b, c, a);
else printf("%d %d %d\n", c, b, a);
}
Esercizio 2.2: Scrivere un programma che legge due date nel formato g/m/a
(ad es. 17/4/2009
), le confronta e stampa se la prima data è anteriore, posteriore o uguale alla seconda. Ad esempio, se le date sono 12/5/2009
e 10/7/2009
, il programma deve stampare la prima data è anteriore alla seconda.
#include <stdio.h>
// legge due date nel formato g/m/a e le confronta
int main() {
int g1, m1, a1;
int g2, m2, a2;
printf("inserire le date nel formato g/m/a (ad es. 17/3/2009)\n");
printf("prima data: ");
scanf("%d/%d/%d", &g1, &m1, &a1);
printf("seconda data: ");
scanf("%d/%d/%d", &g2, &m2, &a2);
printf("La prima data è ");
if (a1 > a2 || (a1 == a2 && m1 > m2) ||
(a1 == a2 && m1 == m2 && g1 > g2))
printf("posteriore");
else if (a1 == a2 && m1 == m2 && g1 == g2)
printf("uguale");
else
printf("anteriore");
printf("alla seconda\n");
}
Istruzioni Iterative
In quasi tutti i programmi che producono output interessante, avremo la necessita' di ripetere una istruzione un numero variabile di volte. Per far cio', si utilizzano istruzioni iterative. La prima istruzione di questo tipo e' il ciclo for.
for(<inizializzazione>; <condizione>; <incremento>) {
<istruzioni>
}
Nel ciclo for
, le <instruzioni>
sono ripetute continuamente finche' <conditione>
e' vera. In genere, si controlla l'esecuzione del ciclo usando variabili addizionali, detta contatori, che vengono definiti in <inizializzazione>
e modificati in <incremento>
.
Esempi
Ad esempio, ecco un programma che legge un intero n
e stampa n
volte "repetita iuvant".
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
for(int i = 0; i < n; i = i + 1) {
printf("repetita iuvant\n");
}
}
Operatori Aritmetici e di Assegnamento
Negli esempi precedenti abbiamo visto istruzioni del tipo i = i + 1
, che cambiamo il valore di una variabile applicando operazioni aritmetiche sul valore della variabile stesso. Queste operazioni sono cosi' comuni che il C definisce delle versione abbreviate di queste operazioni. Per gli operatori aritmetici e logici, si definiscono le operazioni di assegnamento
<variable> <operatore_assegnamento> <expressione>;
con i seguenti operatori:
- somma:
x += y
, equivalente ax = x + y
- differenza:
x -= y
, equivalente ax = x - y
- prodotto:
x *= y
, equivalente ax = x * y
- quoziente:
x /= y
, equivalente ax = x / y
- modulo:
x %= y
, equivalente ax = x % y
- and:
a &&= b
, equivalente aa = a && b
- or:
a ||= b
, equivalente aa = a || b
Incrementi e decrementi sono cosi' comuni che una versione ancora piu' succinta e' definita.
- incremento:
x ++
, equivalente ax = x + 1
- decremento:
x --
, equivalente ax = x - 1
Esempi
Programma che prende in input un intero n
e stampa le prime n
potenze di 2:
#include <stdio.h>
//stampa le prime n potenze di 2
int main() {
long long p = 1;
int n;
printf("Numero di potenze: ");
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
p *= 2;
printf("2 alla %d fa %lld\n", i, p);
}
}
Programma che legge n
, un carattere c
e stampa un quadrato di n
righe e n
colonne riempito con il carattere c
:
#include <stdio.h>
//stampa un quadrato di caratteri di dimensione n
int main() {
int n;
char ch;
printf("Dimensione quadrato: ");
scanf("%d", &n);
printf("Carattere: ");
scanf(" %c", &ch); //lo spazio prima di %c cattura i whitespace
for (int r = 0; r < n; r++) {
for (int c = 0 ; c < n ; c++)
printf("%c", ch);
printf("\n");
}
}
Tipi Primitivi
In C definisce definisce vari tipi primitivi, che chiamiamo cosi' perche' sono definiti nel linguaggio stesso. Vedremo nelle lezioni successive che l'utente puo' definire i propri tipi.
In C (e in tutti gli altri linguaggi di programmazione), i numeri non hanno precisione infinita. In C ogni variabile occupa un quantita' fissa di memoria specificata dal tipo di dati. Ad esempio una variabile di tipo int
occupa 4 bytes, equivalenti a 32 bits. Dato che ogni bit puo' avere solo valore 0 o 1, una variable che occupa 4 bytes puo' rappresentare solo 232 valori, cioe' 4,294,967,296 di valori diversi. Se utilizziamo la meta' di questi valori per numeri positivi e meta' per numeri negativi,possiamo rappresentare tutti i numeri interi tra -2,000,000,000 e 2,000,000,000 (incluso 0). Piu' memoria dedichiamo ad ogni variabile, piu' valori diversi possiamo rappresentare. Allo stesso tempo, la memoria dei calcolatori e' finita e vogliamo risparmiarla. In genere, scegliamo un compromesso tra i due.
Il C offre vari tipi per rappresentare numeri interi. In genere, useremo il tipo int
.
tipo | bits | bytes | min | max | printf/scanf |
---|---|---|---|---|---|
char |
8 | 1 | -127 | 127 | "%hhd" |
short |
16 | 2 | -32.767 | 32.767 | "%hd" |
int |
32 | 4 | -2.147.483.647 | 2.147.483.647 | "%d" |
long |
32 | 4 | -2.147.483.647 | 2.147.483.647 | "%ld" |
long long |
64 | 8 | -263 | 263 | "%lld" |
Se non abbiamo la necessita' di rappresentare numeri negativi, possiamo dedicare tutti i bit del tipo a numeri positivi, per un modesto incremento di precisione.
tipo | bits | bytes | min | max | printf/scanf |
---|---|---|---|---|---|
unsigned char |
8 | 1 | 0 | 255 | "%hhu" |
unsigned short |
16 | 2 | 0 | 65.535 | "%hu" |
unsigned int |
32 | 4 | 0 | 4.294.967.295 | "%u" |
unsigned long |
32 | 4 | 0 | 4.294.967.295 | "%lu" |
unsigned long long |
64 | 8 | 0 | 264-1 | "%llu" |
Per rappresentare numeri frazionari, il calcolatore usa la rappresentazione in virgola mobile (floating point), in cui parte dei bits sono dedicati a rappresentare il numero frazionale, la mantissa, e parte dei bits sono usati per rappresentare un exponente. I numeri in virgola mobile sono espressi come m ∙ be dove m e' la mantissa, b la base e e l'esponente. Nella loro rappresentazione al calcolatore, i numeri in virgola mobile hanno precisione limitata sia nella mantissa (numero di cifre decimali) che nell'esponente (minimo a massimo esponente), come indicato sotto per i tipi definiti in C.
tipo | bits | bytes | prec. | min e. | max e. | printf/scanf |
---|---|---|---|---|---|---|
float |
32 | 4 | 6 | -37 | 38 | "%f" |
double |
64 | 8 | 15 | -307 | 308 | "%lf" |
long double |
128 | 16 | 18 | -4931 | 4932 | "%Lf" |
Il tipo carattere char
puo' essere usato anche per rappresentare i caratteri dell'alfabeto inglese. Per farlo, assegnamo ad ogni valore tra 0 e 127 un carattere. Questa assegnazione e' chiamata codice ASCII. In printf
e scanf
usiamo in codice diverso per indicare l'interpretazione ASCII del tipo char
.
tipo | bits | bytes | min | max | printf/scanf |
---|---|---|---|---|---|
char (ASCII) |
8 | 1 | 0 | 127 | "%c" |
La Tabella dei caratteri ASCII, ottenuta con man ascii
on Os X, e' riassunta a seguito.
0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel
8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si
16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb
24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us
32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 '
40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 /
48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7
56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ?
64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G
72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O
80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W
88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _
96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g
104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o
112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w
120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del
Per rappresentare caratteri in altre lingue, dagli accenti europei ai simboli asiatici, si usa il codice Unicode. Il C non supporta il codice Unicode dirattamente, quindi non lo useremo in questo corso.
Promozione di Tipi
Come abbiamo visto in precedenza, il C ha vari tipi predefiniti per rappresentare sia interi che numeri in virgola mobile. Operazioni aritmetiche tra variabili dello stesso tipo sono ben definite. Quando una operazione ha operandi di tipo diverso, il C converte automaticamente il tipo più piccolo a quello più grande. Formalmente diciamo che avviene una promozione da un tipo all'altro. Ad esempio, se si somma una variabile short
and una int
, la variabile short
e' convertita ad int
automaticamente dal linguaggio.
La promozione automatica può avvenire in espressioni che combinano due tipi interi, o due tipi in virgola mobile, o un tipo intero ed una in virgola mobile. In quest'ultimo caso il tipo intero verra' convertito in virgola mobile (con possibile perdita di precisione).
Operatore di Divisione
Nota che l'operatore di divisione ha significato diverso per tipo numerico diverso. Quando si applica ad operandi interi, l'operatore di divisione va interpretato con divisione intera. Mentre quando e' applicato ad operandi in virgola mobile, va inteso come divisione. Ad esempio 3/2 == 1
(divisione intera) mentre 3.0/2.0 == 1.5
(divisione in virgola mobile). Questo puo' causare confusione ed errori di programmazione quando e' combinato con la promozione; ad esempio 3/2.0 == 1.5
.
Esercizi
Esercizio 3.1 Scrivere un programma che prende in input n
numeri in virgola mobile (float
) e stampa la media di tali numeri.
Esercizio 3.2 Scrivere un programma che legge un carattere c
e un intero n
e stampa un triangolo di altezza n
fatto con caratteri c
. Ad esempio, se n = 5
e c = 'a'
allora il programma stampa:
a
aa
aaa
aaaa
aaaaa