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:

Incrementi e decrementi sono cosi' comuni che una versione ancora piu' succinta e' definita.

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