Sei sulla pagina 1di 47

INFORMATICA

Tipi strutturati
Tipi strutturati
• I tipi considerati finora hanno la caratteristica comune di
non essere strutturati: ogni elemento è una singola
entità.

• Se il programma deve trattare collezioni di dati, anche se


sono dello stesso tipo, a ognuno deve essere associato un
identificatore.

• Supponendo di dover gestire le paghe in una ditta di


3000 dipendenti sarebbe necessario definire 3000
variabili diverse, del tipo: operaio1, operaio2, ....,
impiegato1, impiegato2, ....., ecc.

© Piero Demichelis 2
Tipi strutturati
• I linguaggi ad alto livello permettono di ovviare a questo
inconveniente con i tipi strutturati, caratterizzati sia dal
tipo dei loro componenti che dai legami strutturali tra i
componenti stessi, cioè dal metodo di strutturazione.

• Il linguaggio C, anche in questo caso, si presenta


ambivalente: permette di creare dati aggregati, senza
peraltro che questi costituiscano dei tipi nell'accezione
classica.

• Infatti l'organizzazione strutturale dei dati e le modalità di


accesso ai singoli elementi che costituiscono la struttura
non vengono nascoste all'utente, che invece può
interagire con esse in piena libertà.

© Piero Demichelis 3
Vettori
• Il vettore è una collezione di variabili tutte dello stesso
tipo (detto appunto tipo base) di lunghezza prefissata.

• Questa collezione di variabili è individuata da un unico


nome, il nome appunto del vettore.

• Ogni elemento del vettore è detto componente ed è


individuato dal nome del vettore seguito da un indice
posto tra parentesi quadre.

• L'indice può essere solo di tipo intero o enumerato e


determina la posizione dell'elemento nel vettore.

© Piero Demichelis 4
Vettori
• L'intervallo dei valori assunti dall'indice determina la
dimensione del vettore, che deve essere limitata.

• Definizione generale di vettore:

tipo_componente nome_vettore [numero_componenti];

• tipo_componente può essere un qualunque tipo semplice,


• nome_vettore è il nome da attribuire al vettore,
• numero_componenti, racchiuso tra parentesi quadre, è il
numero di elementi che costituiscono il vettore e pertanto
deve essere un intero o un'espressione costante di tipo
intero.
© Piero Demichelis 5
Vettori
• L'indice del vettore può assumere valori compresi tra 0 e
numero_componenti – 1.

• L'indice corrisponde pertanto alla posizione nel vettore


dell’elemento a cui è associato, rispetto al primo.

• Gli elementi del vettore sono memorizzati in celle di


memoria contigue (successive)!

Vettore a (di 5 elementi): a[0] a[1] a[2] a[3] a[4]

© Piero Demichelis 6
Vettori
• Esempi di definizioni di vettore:
#define NUM_MATERIE 20
char s[8];
double giornate[167];
int voti_ottenuti[NUM_MATERIE];

• La variabile s è un vettore di 8 elementi: equivale alle 8


variabili di tipo char: s[0], s[1], s[2], s[3], s[4], s[5],
s[6], s[7].
• La variabile giornate è un vettore di 167 elementi di tipo
double il cui indice può variare tra 0 e 166.
• La variabile voti_ottenuti è un vettore di 20 elementi di
tipo intero il cui indice può variare tra 0 e 19.

© Piero Demichelis 7
Inizializzazione di un vettore
• E’ possibile assegnare un valore iniziale ad un vettore al
momento della sua definizione.

• L’operazione consiste nell’indicare i valori degli elementi


del vettore separati tra loro da virgola.

• Sintassi (per un vettore di N elementi):

= {<valore_0>, <valore_1>, ..., ,<valore_N-1>};

• Esempio:
int lista[4] = { 2, 0, -1, 5 };
© Piero Demichelis 8
Inizializzazione di un vettore
• NOTA: se vengono specificati meno di N elementi,
l’inizializzazione comincia comunque a partire dal primo
valore e lascia non assegnati i rimanenti.

• Esempi:

int s[4] = {2, 0, -1}; /* s[0]=2, s[1]=0, s[2]=-1, s[3]=? */


char p[5] = {‘a’, ‘b’, ‘c’};
/* p[0]=‘a’, p[1]=‘b’, p[2]=‘c’, p[3]=?, p[4]=? */
double d[2] = {2.56}; /* d[0]=2.56, d[1]=? */

© Piero Demichelis 9
Vettori e indici
• L’indice, che definisce la posizione di un elemento di un
vettore, DEVE essere rigorosamente un intero!

• Può ovviamente anche essere un’espressione, più o meno


complessa, purché con risultato intero.

• Esempio:

double x, a[30]; /* a vettore di double */


int i, j, k;
.............
x = a[2*i+j-k]; /* espressione aritmetica per l’indice */
© Piero Demichelis 10
Vettori e cicli
• I cicli sono particolarmente utili per “scandire” un vettore.
• Utilizzo tipico: applicazione iterativa di un’operazione sugli
elementi di un vettore.
• Schema:
int data[10], ind;
.............
for (ind=0; ind<10; ind++)
{
elaborazione dell’elemento data[ind]
}

• Ad ogni ciclo è interessato l’elemento individuato


dall’indice ind.
© Piero Demichelis 11
Vettori
• Non ci sono operatori che agiscono sul vettore nel suo
complesso: non è lecito pertanto l'assegnamento di un
vettore ad un altro vettore.

• Se vett_x e vett_y sono vettori, l'istruzione:

vett_x = vett_y;

è errata anche se vett_x e vett_y sono dello stesso tipo.

• Per trasferire un vettore in un altro occorre copiare un


elemento per volta.

© Piero Demichelis 12
Vettori
• Esempio: copia il vettore vett_iniz nel vettore vett_fin

#include <stdio.h>
#define NUMDATI 5

int vett_iniz[NUMDATI] = {11, -2, -63, 4, 15};


int vett_fin[NUMDATI], indice;

main()
{
for (indice = 0; indice < NUMDATI; indice++)
vett_fin[indice] = vett_iniz[indice];
}

© Piero Demichelis 13
Vettori
• Sugli elementi del vettore agiscono gli operatori previsti
per il tipo_componente. Pertanto è lecito scrivere:

valor_fin = vett_x[m1] + vett_y[m2];

purché, naturalmente, valor_fin, il vettore vett_x e il


vettore vett_y siano dello stesso tipo.

• Il tempo necessario per accedere a un elemento di un


vettore è indipendente dal valore dell'indice: il vettore è
pertanto una

struttura ad accesso casuale

© Piero Demichelis 14
Esempio
• Leggere 10 valori da tastiera e memorizzarli in un vettore;
quindi calcolarne il minimo ed il massimo.

• Pseudocodice:
- Con un indice ind che varia tra 0 e 9:
•legge un dato e lo salva in vettdati[ind];
- Inizializzo la variabile massimo e la variabile minimo col primo
elemento del vettore vettdati[0];
- Con un indice ind che varia tra 1 e 9:
• se vettdati[ind] è più grande di massimo:
massimo vettdati[ind];
altrimenti se vettdati[ind] è più piccolo di minimo:
minimo vettdati[ind];
- Visualizza massimo e minimo

© Piero Demichelis 15
Esempio
#include <stdio.h>
#define NUMDATI 10

main()
{
int minimo, massimo, ind;
int vettdati[NUMDATI];

/* lettura dei dati */


for (ind = 0; ind < NUMDATI; ind++)
{
printf (“\nIntroduci vettdati[%d]: ", ind);
scanf ("%d", &vettdati[ind]);
}
© Piero Demichelis 16
Esempio
/* cerca il massimo e il minimo */
massimo = vettdati[0];
minimo = vettdati[0];
for (ind = 1; ind < NUMDATI; ind++)
{
if (vettdati[ind] > massimo)
massimo = vettdati[ind];
else
{
if (vettdati[ind] < minimo)
minimo = vettdati[ind];
}
}
printf (“\nIl massimo è %d e il minimo è %d\n ", massimo, minimo);
}

© Piero Demichelis 17
Esempio
• Scrivere un programma che legga un numero decimale
positivo minore di 1024 e lo converta nella corrispondente
codifica binaria.

• Analisi

Per convertire in binario puro un numero decimale occorre


eseguire una sequenza di divisioni per 2 prendendo i resti (0
oppure 1): occorre dunque un vettore per memorizzare questi
resti.

Poiché i numeri devono essere compresi tra 0 e 1023 sono


sufficienti 10 bit: il nostro vettore sarà pertanto lungo 10 elementi e
in ogni elemento memorizzeremo una cifra.

© Piero Demichelis 18
Esempio
Analisi (continua):
I resti ottenuti dalle divisioni per 2 vanno però letti al contrario,
conviene pertanto riempire il vettore a partire dall’ultimo
elemento.

Per eseguire le divisioni per due è intuitivo che conviene servirsi di


un ciclo il quale, ad ogni iterazione, calcola un nuovo bit (resto
della divisione per 2).

for o while? È pressochè indifferente usare un ciclo for o un ciclo


while: occorre però che le inizializzazioni delle variabili siano
adattate al ciclo prescelto.

Se usiamo il for avremo come “dato-guida” del ciclo l’indice del


vettore;
Se usiamo il while il “dato-guida” sarà il resto delle divisioni per 2.

© Piero Demichelis 19
Esempio (con while)
#include <stdio.h>

main()
{
int ind, numero, num;
int binario[10];

/* inizializza il vettore risultato con tutti zeri */


for (ind = 0; ind < 10; binario[ind++]=0);
/* equivale a : for (ind=0; ind<10; ind++)
binario[ind] = 0; */
printf (“\nIntroduci un numero intero positivo minore di 1024: ");
scanf ("%d", &numero);

© Piero Demichelis 20
Esempio (con while)
if ((numero >= 0) && (numero < 1024))
{
num = numero; /* num è il “dato-guida” del ciclo */
ind = 9;
while (num != 0) /* finché num è diverso da 0! */
{
binario[ind] = num % 2; /* calcola un nuovo bit */
num /= 2; /* aggiorna num per il prossimo ciclo */
ind--; /* aggiorna l’indice del vettore */
}
printf ("\nConversione del numero %d: ", numero);
for (ind=0; ind<10; ind++) /* Visualizza il vettore: */
printf ("%1d",binario[ind]); /* un bit per volta */
}
else
printf (“\nNumero non lecito!”);
}

© Piero Demichelis 21
Esempio (con for)
#include <stdio.h>

main()
{
int ind, numero, num;
int binario[10];

/* non è necessario inizializzare il vettore in quanto il ciclo for deve */


/* scrivere comunque tutti gli elementi del vettore */

printf (“\nIntroduci un numero intero positivo minore di 1024: ");


scanf ("%d", &numero);

© Piero Demichelis 22
Esempio (con for)
if ((numero >= 0) && (numero < 1024))
{
num = numero;
for (ind = 9; ind >= 0; ind--) /* con un indice che va da 9 a 0 */
{
binario[ind] = num % 2; /* calcola un nuovo bit */
num /= 2; /* aggiorna num per il prossimo ciclo */
}
printf ("\nConversione del numero %d: ", numero);
for (ind = 0; ind < 10; ind++) /* Visualizza il vettore: */
printf ("%1d",binario[ind]); /* un bit per volta! */
}
else
printf (“\nNumero non lecito!”);
}

© Piero Demichelis 23
Vettori
• Quando si definisce un vettore il compilatore riserva
un’area di memoria sufficiente per contenerlo e associa
l'indirizzo iniziale di quell'area al nome simbolico
(identificatore) da noi scelto per il vettore.

• Pertanto il nome vett_dati non è una vera e propria


variabile, ma piuttosto un puntatore : in pratica vett_dati
è l'indirizzo di memoria del primo elemento del vettore
cioè l'indirizzo di vett_dati[0].

• Ecco perché è errata l'istruzione:

voti_ottenuti = voti_semestre;
© Piero Demichelis 24
Vettori multidimensionali
• Il concetto di vettore come collezione di elementi
consecutivi, può essere esteso immaginando che gli
elementi siano a loro volta dei vettori: si ottiene così un
vettore multidimensionale o matrice.

• La definizione di matrice ricalca pienamente quella del


vettore:
tipo_comp nome [dim1] [dim2].........;

• tipo_comp può essere un qualunque tipo semplice,

• dim1, dim2, ecc.; racchiusi tra parentesi quadre,


definiscono il numero di elementi di ogni dimensione.
© Piero Demichelis 25
Vettori multidimensionali

Esempio:
matrice bidimensionale di numeri interi formata da tre
righe e 5 colonne:

int a[3][5];

a[0][0] a[1][0] a[2][0] a[3][0] a[4][0] a[0]

a a[0][1] a[1][1] a[2][1] a[3][1] a[4][1] a[1]

a[0][2] a[1][2] a[2][2] a[3][2] a[4][2] a[2]

© Piero Demichelis 26
Vettori multidimensionali
• Accesso ad un elemento:
<nome vettore> [<posizione1>] [<posizione2>].............
• Per esempio
matrix [10][20][15]
individua l'elemento di coordinate rispettivamente 10, 20 e 15 nella
matrice a 3 dimensioni matrix.

• Inizializzazione di un vettore multidimensionale:


- deve essere effettuata per righe!

int vett[3][2] = { {8,1}, /* vett[0] */


{1,9}, /* vett[1] */
{0,3} /* vett[2] */
};

© Piero Demichelis 27
Vettori multidimensionali e cicli
• Per un vettore a più dimensioni, la scansione va applicata
a tutte le dimensioni: in questo caso si devono in genere
utilizzare “cicli annidati ”.

• Esempio: elaborazione degli elementi di un vettore


bidimensionale.

int vett [3][5];



for (i = 0; i < 3; i++) { /* per ogni riga */
for (j = 0; j < 5; j++) { /* per ogni colonna */
... elaborazione su vett[i][j]
}
}

© Piero Demichelis 28
Stringhe
Vettori di caratteri: le stringhe
• Le variabili di tipo char possono contenere un solo
carattere: per trattare sequenze di caratteri come nomi
o, più in generale, testi il C prevede le stringhe.

• Differentemente dagli altri tipi di dato (intero, reale, ecc.)


per le stringhe non è sempre possibile fissare a priori le
dimensioni: la loro caratteristica peculiare è proprio la
lunghezza variabile.

• Per gestire dati di questo tipo occorrerebbe l'allocazione


dinamica della memoria, in modo da riservare tutta e solo
la memoria che serve.

© Piero Demichelis 30
Vettori di caratteri: le stringhe
• Il C prevede per le stringhe un vettore di caratteri, il
quale deve quindi avere una lunghezza massima
prefissata.

• All'interno di questo vettore, la lunghezza reale della


stringa è determinata dalla presenza di un carattere
delimitatore particolare, '\0', detto anche NULL.

• Come per i tipi base, anche per le stringhe è prevista una


notazione particolare per indicarne i valori: la sequenza di
caratteri deve essere delimitata da una coppia di doppi
apici (").

© Piero Demichelis 31
Stringhe
• Esempio:
‘C’ ‘i’ ‘a’ ‘o’ ‘!’ ‘\0’
const char s[6] = “Ciao!”; s[0] s[1] s[2] s[3] s[4] s[5]

• Il messaggio è lungo solo 5 caratteri, ma deve essere riservato un


carattere in più per il terminatore di stringa, che deve essere sempre
presente e viene forzato automaticamente dal linguaggio C.

• Il vettore può essere definito più lungo, con gli ultimi elementi
indefiniti, ma non più corto.

• NOTA: la stringa vuota non è un vettore “vuoto”!

‘\0’
char s[] = “”;
s[0]
© Piero Demichelis 32
Stringhe
• Per la definizione di una stringa si può anche utilizzare la
direttiva define:

#define MESSAGGIO “Ciao!”;

• Attenzione infine a non confondere variabili di tipo


carattere con stringhe: per esempio,

'C' rappresenta un unico carattere che è memorizzato in


un'unica cella.

"C" rappresenta invece una stringa che è memorizzata in


due celle consecutive che contengono i caratteri 'C' e '\0'.
© Piero Demichelis 33
Stringhe
• Un vettore di nomi si realizza mediante una matrice di
tipo carattere dove ogni riga (vettore) contiene un nome.
Ad esempio, per memorizzare i nomi dei giorni della
settimana, si può procedere così:

const char giorni[7][10] = {"lunedì", "martedì",


"mercoledì", "giovedì", "venerdì", "sabato", "domenica"};

• dove la dimensione dei singoli vettori (tutti i vettori!), cioè


10, è determinata sommando 1 alla lunghezza del nome
più lungo (mercoledì).

• Pertanto un vettore di stringhe è in realtà una matrice di


caratteri!
© Piero Demichelis 34
Stringhe

l u n e d ì \0 giorni[0]

m a r t e d ì \0 giorni[1]

m e r c o l e d ì \0 giorni[2]

g i o v e d ì \0 giorni[3]

v e n e r d ì \0 giorni[4]

s a b a t o \0 giorni[5]

d o m e n i c a \0 giorni[6]

© Piero Demichelis 35
I/O di stringhe
• Le stringhe possono comparire come argomento di printf e
scanf: per esse si utilizza lo specificatore di formato %s.

• In particolare la printf, quando trova nel format lo


specificatore %s, interpreta i valori contenuti nella variabile
corrispondente (che dev’essere un vettore di caratteri!) come
caratteri e li visualizza finché non trova un carattere '\0'.

• Se non è presente il carattere terminatore la printf continua


l’output oltre i confini del vettore fino a che non incontra un
'\0‘ (ovvero una cella di memoria che contiene 0!).

• Come per gli altri specificatori, anche in %s si può specificare


la lunghezza del campo e gli altri attributi di allineamento.
© Piero Demichelis 36
Esempio
• Programma per visualizzare i giorni della settimana, uno per riga,
allineati a sinistra (flag - ) in un campo di 15 caratteri.

#include <stdio.h>

const char giorni[7][10] = { "lunedì", "martedì", "mercoledì",


"giovedì", "venerdì", "sabato", "domenica"};

main()
{
int indice;

printf ("\nI giorni della settimana sono:\n");


for (indice = 0; indice < 7; indice++)
printf ("%-15s\n", giorni[indice]);
}

© Piero Demichelis 37
Lettura di stringhe
• La scanf, quando trova nel format lo specificatore %s,
attua un meccanismo di lettura simile a quello usato per i
numeri: scarta tutti gli “spazi neutri ” iniziali (spazio,
<TAB>, <CR>, ecc.), “legge” i caratteri successivi
scrivendoli in locazioni consecutive del vettore indicato e
si ferma non appena incontra un altro carattere
appartenente alla categoria degli “spazi neutri ”,
chiudendo la stringa appena generata nel vettore con il
carattere NULL.

• E’ importante quindi che nella stringa non siano presenti


spazi e che il vettore destinazione sia dimensionato
opportunamente poiché, come sempre in C, non ci sono
controlli sugli indici.

© Piero Demichelis 38
Lettura di stringhe
• Il fatto che non ci siano controlli sul numero di caratteri
introdotti, ad esempio da tastiera, può provocare danni
collaterali non trascurabili: infatti la lettura prosegue fino al
primo “spazio neutro ” in ogni caso e i caratteri letti vengono
memorizzati consecutivamente come se la stringa fosse stata
dimensionata in modo corretto anche quando è più corta di
quanto sarebbe necessario.

• I caratteri “in eccesso” e il NULL vengono comunque


memorizzati e possono pertanto andare a ricoprire aree di
memoria riservate ad altri dati sporcandoli irrimediabilmente.

• Poiché il nome della stringa è proprio l’idirizzo del vettore di


caratteri associato nella scanf non si deve usare il carattere &
prima del nome.

© Piero Demichelis 39
Esempio
• Esempio: programma per leggere i nomi (lunghi al massimo 20
caratteri) e le altezze (in cm) di 10 persone e successivamente
visualizzarli incolonnati.

#include <stdio.h>
#define NUM_NOMI 10
#define L_STRING 21

main()
{

/* Definizioni */

char nome[NUM_NOMI][L_STRING];
int altezza[NUM_NOMI];
int ind;

© Piero Demichelis 40
Esempio
printf (“\nIntroduci il nome e l'altezza di 10 persone:\n");
for (ind = 0; ind < NUM_NOMI; ind++)
{
printf (“\nPersona N. %2d: ", (ind + 1)); /* indice a partire da 1 */
scanf ("%s%d", nome[ind], &altezza[ind]);
}

printf("\n Nome Altezza");


for (ind = 0; ind < NUM_NOMI; ind++)
{
printf (“\nPersona N. %4d: %-20s %3d", (ind + 1), nome[ind],
altezza[ind]);
}
}
© Piero Demichelis 41
Confronto tra stringhe
• Poiché le stringhe sono vettori, non è lecito assegnare
una stringa ad un'altra. Pertanto il frammento di
programma che segue è errato:

char messag[16];
.....................
messag = "Errore nei dati";
.....................

• Anche il confronto tra stringhe non può essere effettuato


mediante un'unica istruzione, come invece avviene per i
singoli caratteri, ma occorre confrontare col criterio
opportuno i singoli elementi delle due stringhe.

© Piero Demichelis 42
Confronto tra stringhe: esempio
• Programma che legge da tastiera due parole (lunghe al più 20
caratteri) e verifica se sono uguali o diverse.

#include <stdio.h>
#define VERO 1
#define FALSO 0

main()
{
char parola1[21], parola2[21];
int ind, uguali;

printf (“\nIntroduci la prima parola: “);


scanf (“%s”, parola1);
printf (“\nIntroduci la seconda parola: “);
scanf (“%s”, parola2);

© Piero Demichelis 43
Confronto tra stringhe: esempio
/* verifica se sono uguali */
uguali = VERO; /* ipotizza che siano uguali */
ind = 0;

while (uguali && (ind < 20) && (parola1[ind] != ‘\0’))


{
if (parola1[ind] != parola2[ind])
uguali = FALSO;
ind++;
}
if (uguali)
printf (“\nLe parole introdotte sono uguali”);
else
printf (“\nLe parole introdotte sono diverse”);
}

© Piero Demichelis 44
Confronto tra stringhe
• Essendo i caratteri interpretati come numeri interi, è
lecito confrontarli tra loro per stabilire la precedenza
alfabetica mediante una espressione relazionale.

0 < 1 < 2 <.... < A < B < C <.....< Z ..... < a < b < c <....< z

• Poiché le stringhe sono vettori di caratteri, e quindi


composte da elementi di tipo char, è lecito stabilire
l’ordine alfabetico di due stringhe confrontandole fra loro
carattere per carattere.

• Esempio: programma che legge da tastiera due parole e


stabilisce l’ordine alfabetico.
© Piero Demichelis 45
Confronto tra stringhe: esempio
#include <stdio.h>

#define VERO 1
#define FALSO 0

main()
{
char parola1[21], parola2[21];
int ind, finito, prima1;

printf ("\nIntroduci la prima parola: ");


scanf ("%s", parola1);
printf ("\nIntroduci la seconda parola: ");
scanf ("%s", parola2);

finito = FALSO; /* segnala fine dei confronti! */


ind = 0;
© Piero Demichelis 46
Confronto tra stringhe: esempio
while (!finito && (ind < 20))
{
if (parola1[ind] == parola2[ind]) /* caratteri uguali: nessuna decisione */
ind++;
else
{
if(parola1[ind] < parola2[ind])
prima1 = VERO; /* parola1 precede parola2 */
else
prima1 = FALSO; /* parola2 precede parola1 */
finito = VERO; /* caratteri diversi: fine dei confronti */
}
}
if (prima1)
printf ("\n%s precede %s", parola1, parola2);
else
printf ("\n%s precede %s", parola2, parola1);
}
© Piero Demichelis 47