• Non ci sono risultati.

INFORMATICATipi strutturati

N/A
N/A
Protected

Academic year: 2021

Condividi "INFORMATICATipi strutturati"

Copied!
40
0
0

Testo completo

(1)

INFORMATICA

Tipi strutturati

int M[2][2];

(2)

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.

(3)

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

(4)

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.

(5)

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.

(6)

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]

(7)

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.

(8)

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

(9)

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]=? */

(10)

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

(11)

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.

(12)

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.

(13)

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

}

(14)

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

(15)

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

(16)

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

}

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

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

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.

(18)

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

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

(19)

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

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

}

(20)

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;

(21)

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.

(22)

Vettori multidimensionali

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

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

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

a

int a[3][5];

a[0]

a[1]

a[2]

Esempio:

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

Sostituire ai pallini neri i corrispondenti indici di posizione di riga e di colonna

(23)

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

};

(24)

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]

} }

(25)

Stringhe

(26)

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.

(27)

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

(28)

Stringhe

• Esempio:

const char s[6] = “Ciao!”;

• 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”!

char s[] = “”;

‘C’ ‘i’ ‘a’ ‘o’ ‘!’ ‘\0’

s[0] s[1] s[2] s[3] s[4] s[5]

‘\0’

‘\0’

(29)

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

(30)

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!

(31)

Stringhe

m a r t e d ì \0

l u n e d ì

m e r c o l e d ì \0

g i o v e d ì \0

v e n e r d ì \0

s a b a t o \0

giorni[0]

giorni[1]

giorni[2]

giorni[3]

giorni[4]

giorni[5]

\0

(32)

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.

(33)

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

}

(34)

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.

(35)

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.

(36)

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;

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

} }

(37)

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.

(38)

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

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

(39)

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.

(40)

Esempio: programma che legge da tastiera due parole e stabilisce l’ordine alfabetico.

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

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

Riferimenti

Documenti correlati

I compilatori C moderni permettono di scrivere codice come quello nel riquadro, in cui una matrice (o un vettore) viene creata con un numero variabile di elementi, cio` e dipendente

(array di char o costante tra “..”), un carattere o ad un’altra variabile di tipo stringa. string nome1,

int indexOf(String str, int fromIndex) : Returns the index within this string of the first occurrence of the specified substring, starting at the specified index. boolean isEmpty()

I Quando si dichiara una funzione che prende come parametro un array multi-dimensionale, solo la prima dimensione pu` o essere lasciata non specificata..

Valutazione di codice con

Prima di chiamare la free sull'array chiamarla su ogni

strncpy() Copia i primi n caratteri di una stringa in un’altra stringa strcat() Concatena due stringhe. strncat() Concatena i primi n caratteri di una stringa ad un’altra

¨  Nell’input di stringhe, la scanf scarta automaticamente i white space iniziali e continua a leggere fino a quando incontra un white space (quindi non si può usare la scanf