Modulo 5 – La programmazione Unità 9 – Le matrici e strutture Prof. Antonio Scanu 1 Le Matrici Supponiamo di voler memorizzare le votazioni riportate dai ginnasti in un'intera gara (agli anelli, al corpo libero, alla sbarra, alle parallele, al volteggio e al cavallo con maniglie). Avremo bisogno di sei vettori da gestire in modo parallelo (sette, considerando quello dei nominativi). In queste circostanze è utile inserire i punteggi in una struttura di dati a doppia entrata. Tale struttura è caratterizzata da tante righe quanti sono i ginnasti e da tante colonne quante sono le prove da sostenere. L'incrocio tra una riga e una colonna conterrà la votazione di un ginnasta in una prova. Questa struttura prende il nome di matrice o array bidimensionale. Si definisce matrice a N righe e a M colonne, o più brevemente matrice di tipo NxM, una tabella formata con NxM elementi omogenei, disposti su N linee orizzontali (righe) ed M li nee verticali (colonne) . Gli elementi si indicano di solito con una stessa lettera munita di due indici: il primo per indicare la riga, il secondo per indicare la colonna a cui apparten gono. Gli array finora esaminati erano monodimensionali e richiedevano l'uso di un solo indice. È facile determinare il numero di dimensioni di un array osservando la sua dichiarazione: se è presente una sola coppia di parentesi quadre [], l' array è monodimensionale; due coppie di parentesi quadre [][] indicano invece un array bidimensionale e così via. Il numero massimo di dimensioni solitamente non supera 3 (array multidimensionali). Per dichiarare una matrice utilizziamo la seguente sintassi, che evidenzia come sia necessario far seguire al nome della variabile l'operatore di dichiarazione [] ripetuto due volte: < TipoElementi > < NomeArray > [ <Righe >][ < Colonne > ]; dove <Righe> e <Colonne> indicano rispettivamente il numero di righe e il numero di colonne della matrice. Per dichiarare e creare una matrice 7x4 di numeri reali e di nome Temperature scriveremo: float Temperature[7][ 4]; Per assegnare a una variabile X il valore dell'elemento di posto riga 5 e colonna 1 scriveremo: X = Temperature [5] [ 1]; 1.1 Esempio Dimensionare una matrice, caricarla, visualizzarla e creare un vettore parallelo contenente le somme delle righe della matrice. #include<iostream> #define dimcolonne 100 #define dimrighe 100 using namespace std; void dimensiona(int &n, int &m); void carica(int n, int m, int matt[dimrighe][dimcolonne]); void visualizza(int n, int m, int const matt[dimrighe][dimcolonne]); void creavettore(int n, int m, int matt[dimrighe][dimcolonne],int vett[dimrighe]); void visualizzavettore (int n,int const v[dimrighe]); int main() { int nrighe,mcolonne,matrice[dimrighe][dimcolonne],vettore[dimrighe]; dimensiona(nrighe,mcolonne); carica(nrighe,mcolonne,matrice); visualizza(nrighe,mcolonne,matrice); creavettore(nrighe,mcolonne,matrice,vettore); visualizzavettore(nrighe,vettore); return 0; } void dimensiona(int &n, int &m){ do{cout<<"inserisci il numero delle righe"; cin>>n; }while(n<=0 || n>=dimrighe); do{cout<<"inserisci il numero delle colonne"; cin>>m; }while(m<=0 || m>=dimcolonne); } void carica(int n, int m, int matt[dimrighe][dimcolonne]){ int i,j; for(i=0;i<n;i++) {for(j=0;j<m;j++) {cout<<"inserisci il valore di posizione"<<i<<j; cin>>matt[i][j]; } } } void visualizza(int n, int m, int const matt[dimrighe][dimcolonne]){ int i,j; for(i=0;i<n;i++) {for(j=0;j<m;j++) {cout<<matt[i][j]<<" "; } cout<<endl; } } void creavettore(int n, int m, int matt[dimrighe][dimcolonne],int vett[dimrighe]){ int i,j; for(i=0;i<n;i++) {for(j=0;j<m;j++) {vett[i]=vett[i]+matt[i][j]; } } } void visualizzavettore (int n,int const vett[dimrighe]){ int j; for(j=0;j<n;j++) {cout<<vett[j]<<" "; } } 2 I Record Nella realtà nasce spesso l'esigenza di dover trattare in formazioni di tipo diverso relative a un oggetto preso in esame. Ad esempio, per avere delle informazioni su una fattura è necessario ricordare il numero della fattura in questione, la data di emissione, la denominazione dell'impresa emittente e l' importo. Utilizzando le seguenti variabili semplici: è possibile risolvere il problema, ma l'utilizzo di tali variabili non indica in alcun modo che i dati in esse contenuti si riferiscono allo stesso oggetto (nel nostro caso, alla stessa fattu ra) . Un altro esempio: le variabili Titolo, Autore, CasaEditrice e DataPubb contengono informazioni relative a un medesimo libro, ma non esiste un modo che ci consenta di assicurarci di ciò. Dagli esempi esposti si evince che il nostro intento non è quello di descrivere un oggetto, bensì di trovare un metodo per descrivere una classe di oggetti aventi caratteristiche comuni, che prendono il nome di attributi. Riferendoci all'ultimo esempio, possiamo supporre che la classe di oggetti Libri sia composta da tutti i libri presenti nella biblioteca della nostra scuola e che ogni libro sia caratte rizzato dai seguenti attributi: Titolo, Autore, CasaEditrice e DataPubb. Tali valori rappresentano, perciò, gli attributi comuni a tutti i libri presenti nella biblioteca, quindi tutti gli oggetti appartenenti alla classe. Utilizzando gli array, il solo modo di rappresentare la classe di oggetti Libri è quello di servirsi di quattro vettori paralleli, perché gli attributi non sono omogenei: uno per la memorizzazione dei titoli, uno per i nominativi degli autori, uno per il nome della casa editrice e uno per la memorizzazione della data di pubblicazione. L'idea di utilizzare quattro vettori paralleli sembrerebbe valida, ma ci accorgiamo che, pur risolvendo il problema, non ci consente di avere una visione globale dei dati relativi a ciascun oggetto. Non possiamo infatti fare riferimento ai singoli attributi come insieme logico di attributi di un libro. In conclusione, quando dobbiamo trattare più informazioni non necessariamente omogenee relative a uno stesso oggetto, dobbiamo ricorrere a un altro tipo di dato strutturato: il record. Un record, o registrazione, è una struttura di dati a carattere statico composta da un insieme finito di elementi omogenei o eterogenei detti campi. I campi sono tra loro logicamen te connessi e corrispondono agli attributi. Ogni campo accoglie un valore per un attributo. Il record è un costruttore di tipi di dato predefinito. Viene messo a disposizione da molti linguaggi di programmazione non basati sul paradigma a oggetti. Un record è caratterizza to da un nome, che lo identifica e si riferisce a esso nella sua globalità. I campi che lo compongono sono caratterizzati da un nome e dal tipo di dato che possono contenere. Possono essere, indifferentemente, di tipo semplice o di tipo strutturato: un singolo campo di un record, infatti, può essere a sua volta un record oppure un array. Chiamiamo struttura di un record la definizione dei campi che compongono il record stesso. Le strutture del linguaggio C coincidono con quelli che in informatica sono comunemente definiti record. Nel seguito, dunque, utilizzeremo indifferentemente i termini "struttura" e "record". Il raggruppamento delle suddette informazioni sotto un nome comune permette di rappresentare tramite le strutture entità logiche i cui attributi sono rappresentati dalle variabili comprese nella struttura. Un esempio può essere l'aggregazione di informazioni quali Marca, Modello, Targa, Cilindrata, Potenza, che costituiscono un insieme di tipo eterogeneo ma che sono accomunate dall'essere le caratteristiche peculiari di una struttura che possiamo chiamare Automobi le. Per dichiarare una struttura si utilizza la seguente sintassi: struct < NomeStruttura > { < Ti p_oCamQ.Qj > < NomeCampo 1 >; <TipoCampoN > < NomeCampoN >; }; dove < TipoCampo > e < NomeCampo > indicano rispettivamente il tipo e il nome di ogni elemento che concorre a caratterizzare la struttura che stiamo definendo. Tali elementi si chiamano campi. Possiamo allora dire che una struttura, o meglio un particolare tipo di struttura, è dichiarata definendone il nome ed elencando i nomi e i tipi dei suoi campi. La precedente dichiarazione è considerata una dichiarazione di tipo. Per esempio, per dichiarare la struttura automobile o equivalentemente "una struttura di tipo automobile" o ancora "un tipo di struttura automobile" scriveremo: struct automobile { char marca; char modello; char targa; int cilindrata; float potenza; }; All'interno della dichiarazione: non è consentita alcuna inizializzazione dei campi; non è consentito utilizzare un nome per un campo uguale al nome della struttura; è consentito usare lo stesso nome per campi appartenenti a strutture diverse; si possono utilizzare tipi semplici, tipi aggregati o tipi definiti dall'utente. Esiste l'istruzione typedef che permette di rinominare la struttura dati per semplificare la notazione. Ad esempio se vogliamo chiamare la struttura automobile auto scriveremo alla fine della dichiarazione della struttura: typedef struct automobile auto; Per dichiarare tre variabili di tipo automobile e di nome a1,a2 e a3 scriveremo: struct automobile a1 , a2, a3; auto a1,a2,a3 /*nel caso della typedef; */ Per fare riferimento a un particolare campo di una struttura, ricorriamo alla dot notation secondo la seguente sintassi: < VariabileDiTipoStruct > . < NomeCampo > Per esempio, per assegnare a una variabile x di tipo intero il valore della cilindrata di una variabile al di tipo automobile, scriveremo: x = a1 .cilindrata; In modo analogo, per assegnare al campo cilindrata della variabile al di tipo automobile un nuovo valore, scriveremo: a1 .cilindrata = 1600; 2.1 Esempio Dopo aver definito una struttura dati che descrive le seguenti caratteristiche di un uomo: nome,altezza, peso ed età. Scrivere un programma in cui si inseriscono i dati di due uomini e si visualizzano dopo averli scambiati. #include <iostream> using namespace std; struct uomo { char nome; int eta; int altezza; float peso; }; typedef struct uomo uomo; int main(){ uomo u1,u2,aux; cout<<"inserisci nome primo uomo"; cin>>u1.nome; cout<<"inserisci eta' primo uomo"; cin>>u1.eta; cout<<"inserisci altezza primo uomo"; cin>>u1.altezza; cout<<"inserisci peso primo uomo"; cin>>u1.peso; cout<<"inserisci nome secondo uomo"; cin>>u2.nome; cout<<"inserisci eta' secondo uomo"; cin>>u2.eta; cout<<"inserisci altezza secondo uomo"; cin>>u2.altezza; cout<<"inserisci peso secondo uomo"; cin>>u2.peso; aux.altezza=u1.altezza; aux.eta=u1.eta; aux.peso=u1.peso; aux.nome=u1.nome; u1.altezza=u2.altezza; u1.eta=u2.eta; u1.peso=u2.peso; u1.nome=u2.nome; u2.altezza=aux.altezza; u2.eta=aux.eta; u2.peso=aux.peso; u2.nome=aux.nome; cout<<"dati primo uomo="; cout<<" nome "<<u1.nome; cout<<" eta' "<<u1.eta; cout<<" peso "<<u1.peso; cout<<" altezza"<<u1.altezza<<endl; cout<<"dati seconodo uomo="; cout<<" nome "<<u2.nome; cout<<" eta' "<<u2.eta; cout<<" peso "<<u2.peso; cout<<" altezza"<<u2.altezza<<endl; return 0; } 2.2 Vettori di struttura e passaggio a funzione Si possono definire anche vettori di struttura utilizzando la stessa notazione dei vettori e indicando i campi preceduti da un punto. Il passaggio di parametri è identico a quello degli altri tipi di dato. Esempio dopo aver caricato un vettore di struttura che tiene conto delle caratteristiche di un uomo (nome,eta,altezza,peso) lo visualizzi e determini e visualizzi le caratteristiche della persona più alta e la media delle età attraverso rispettivamente una procedura e una funzione. #include <iostream> #define dim 100 using namespace std; struct uomo { char nome; int eta; int altezza; float peso; }; typedef struct uomo uomo; int dimensiona(); void carica(int n,uomo u[dim]); void visualizza (int n, uomo const u[dim]); void calcolo(int n,uomo const u[dim], uomo &umax); float funmedia(int n, uomo const u[dim]); int main(){ uomo uvett[dim],uomomax; float media; int n1; n1=dimensiona(); carica(n1,uvett); visualizza(n1,uvett); calcolo(n1,uvett,uomomax); cout<<"\n dati dell' uomo con l'altezza' maggiore sono:"<<endl; cout<<" nome "<<uomomax.nome<<endl; cout<<" eta' "<<uomomax.eta<<endl; cout<<" peso "<<uomomax.peso<<endl; cout<<" altezza "<<uomomax.altezza<<endl<<endl; media=funmedia(n1,uvett); cout<<"la media dell'eta' e'"<<media<<endl; return 0; } int dimensiona(){ int n; do {cout<<"inserisci dimensione ninore di 100"; cin>>n;} while(n<=0 || n>100); return n; } void carica(int n,uomo u[dim]){ int i; for(i=0;i<n;i++){ cout<<endl<<"dati uomo numero"<<i+1<<endl; cout<<"inserisci nome uomo "; cin>>u[i].nome; do{cout<<"inserisci eta' "; cin>>u[i].eta;} while(u[i].eta<=0); do{cout<<"inserisci altezza "; cin>>u[i].altezza;} while(u[i].altezza<=0); do{cout<<"inserisci peso "; cin>>u[i].peso;} while(u[i].peso<=0); } } void visualizza (int n, uomo const u[dim]){ int i; cout<<"\n tabella dati"<<endl<<"num | nome | eta' | peso | altezza "<<endl; for(i=0;i<n;i++){ cout<<i<<"\t"<<u[i].nome<<"\t"<<u[i].eta<<"\t"<<u[i].peso<<"\t"<<u[i].altezza<<"\t"<<e ndl; } } void calcolo(int n,uomo const u[dim], uomo &umax){ int i; umax.eta=-1; umax.peso=-1; umax.altezza=-1; for(i=0;i<n;i++){ if(umax.altezza<u[i].altezza) { umax.nome=u[i].nome; umax.eta=u[i].eta; umax.peso=u[i].peso; umax.altezza=u[i].altezza; } } } float funmedia(int n, uomo const u[dim]){ int i;float acc,m; for(i=0;i<n;i++){ acc=acc+u[i].eta; } m=acc/n; return m; } 3 ESERCITAZIONE (Matrici) 1. Dimensionare,caricare e visualizzare A(5,5). Letto un intero da tastiera visualizzare la riga corrispondente a quell’intero. Eseguire un controllo sul valore inserito da tastiera. 2. Data una matrice costruire un algoritmo che crea un vettore contenete la media dei valori contenuti nelle righe ed un secondo vettore contenente la media dei valori contenuti nelle colonne. 3. Considerare una matrice di interi S di dimensione Nx2 in cui la prima colonna riporta i numeri di matricola di N studenti e la seconda colonna il voto da essi riportato nell’esame di programmazione. Ad esempio : mat voto 121 24 286 27 138 18 231 21 153 30 215 27 Scrivere un programma che legga e visualizzi la matrice e successivamente effettui le seguenti operazioni : Letto il valore di un voto compreso tra 18 e 30, visualizzare le matricole di tutti gli studenti che hanno riportato quel voto, inviando un messaggio nel caso che nessuno lo abbia riportato. Letto il valore di una matricola, verificare se lo studente con tale matricola ha sostenuto o no l’esame. Scambiare di posto nella tabella lo studente che ha la matricola più alta con quello che ha la matricola più bassa ( scambiando anche i voti ) 4. Si consideri una matrice di interi S di dimensione Nx3 n cui la prima colonna riporta i numeri di matricola di N studenti e la seconda colonna il voto da essi riportato in un esame e la terza il codice dell’esame . Ad esempio : matr. voto esame 121 24 1 286 27 2 151 25 1 286 28 1 138 24 5 Nella prima colonna, compaiono più volte le matricole degli studenti che hanno sostenuto più di un esame. Scrivere un programma che generi e visualizzi la matrice e successivamente effettui le seguenti operazioni : letto un numero di matricola, visualizzi la media dello studente che ha quella matri cola oppure un messaggio se lo studente non ha sostenuto nessun esame. Visualizzi per ogni codice di esame la media riportata dagli studenti che lo hanno sostenuto. 5. Considerare una matrice di interi S di dimensione Nx2 in cui la prima colonna riporta i numeri di matricola di N studenti e la seconda colonna il voto da essi riportato nell’esame di programmazione. Ad esempio : mat voto 121 24 286 27 138 18 231 21 153 30 215 27 Scrivere un programma che legga e visualizzi la matrice. Ordinare la tabella per matricole crescenti (scambiando anche i voti). 4 ESERCITAZIONE (Strutture) 1. Scrivere un programma che, utilizzando il tipo struttura, costruisca la seguente tabella. matricola Cognome e Nome Corso voto 121 Piras Giorgio A 24 286 Zucca Mario B 27 138 Rossi Paolo A 18 231 Pinco Pallino A 21 153 Vacca Sergio B 30 215 Serra Marcello B 27 Acquisire i dati da tastiera e visualizzare la tabella. Inserire nel precedente programma una funzione che riceve la tabella come argomento, legge il codice di un corso e visualizza tutti gli studenti di quel corso. La vi sualizzazione avviene all’interno della funzione. 2. Utilizzando una struttura, descrivere il menù di un ristorante in modo che sia utilizzabile da una persona che segue una dieta. Nella struttura sono riportati il nome del piatto (es. minestra, pastasciutta), le calorie, il tipo (0=antipasto, 1=primo, 2=secondo, 3=con-torno, 4=frutta, 5=dessert), il prezzo. Memorizzare i piatti disponibili in una tabella acquisita dal main e visualizzarla. Inserire nel programma una funzione che riceve dal main un intero tra 0 e 5 che indica un tipo di piatto e visualizza il nome del piu’ costoso piatto di quel tipo. Inserire nel programma precedente una funzione che legge i piatti scelti dal cliente (mediante la loro posizione nel menù) e rende al main il prezzo del pasto e le calorie fornite. Inserire nel programma una funzione che ordina i piatti disponibili in ordine di tipo.