1 ISTITUTO DI ISTRUZIONE SUPERIORE “ANGIOJ” LE STRUTTURE DATI PARTE 2: RECORD Prof. G. Ciaschetti Abbiamo visto nella prima dispensa sulle strutture dati che una struttura dati è una collezione di dati in memoria ai quali è possibile dare un unico nome. Abbiamo studiato finora gli array, strutture dati omogenee e lineari, dove per omogenee si intende il fatto che i dati che esse contengono sono tutti dello stesso tipo (o tutti interi, o tutte stringhe, o tutti reali, ecc.), mentre per lineari si intende che i dati della collezione sono memorizzati in modo contiguo in RAM, uno dietro l’altro. Oltre agli array, esistono strutture dati non omogenee: i record. Un record (registrazione, in italiano) è la struttura base per la creazione degli archivi, ed è una struttura dati che serve per memorizzare tutti i dati relativi a un determinato individuo dell’archivio. Degli archivi ci occuperemo nella prossima unità didattica. Facciamo, per ora, qualche esempio per capire meglio: In una biblioteca, gli individui sono i diversi libri e avremo, pertanto, un diverso record per ogni diverso libro, per poter memorizzare, ad esempio, il suo titolo, l’autore, l’anno di pubblicazione, e la casa editrice. record libro Nell’anagrafe del comune di Carbonia, gli individui sono i cittadini e ogni record memorizzerà, per ogni cittadino, ad esempio, il nome, il cognome, la data di nascita, il domicilio. record cittadino In un canile municipale, gli individui sono i diversi cani ospitati, e per ognuno di essi si avrà un record che memorizzerà il nome, la razza, l’età. record cane 2 I dati che compongono un record si chiamano campi, e ognuno di essi ha un nome e un tipo. Nell’esempio precedente della biblioteca, il record libro ha il campo titolo di tipo stringa, il campo autore di tipo stringa, il campo anno di tipo intero, e il campo editore di tipo stringa. Nell’esempio del canile, il record cane ha il campo nome di tipo stringa, il campo razza di tipo stringa, il campo eta di tipo intero. Quanti campi occorre mettere per ogni record? Vedremo, nella prossima unità didattica, quando parleremo di archivi, che occorre fare un giusto compromesso tra il numero di informazioni che vogliamo memorizzare per ogni individuo, e lo spazio occupato in memoria. Come criterio generale, per il momento, possiamo adottare il seguente: rappresentare solo le informazioni utili ai nostri scopi. Ad esempio, nella biblioteca, è inutile memorizzare per ogni libro il numero delle pagine, in quanto questo dato non serve in nessuna delle operazioni che si dovranno fare (prestito, restituzione, ecc.), e comporterà solo uno spreco di memoria. In linguaggio C, i record si chiamano strutture (structure, in inglese), e devono essere definiti prima di poterli utilizzare. Definizione di un record in C Per definire un record, in linguaggio C, occorre scrivere: struct nome { tipo1 campo1; tipo2 campo2; … tipoN campoN; }; La parola chiave struct del linguaggio C informa il compilatore che si sta definendo un nuovo tipo di dati, che è un record, che si chiama nome e che contiene i campi campo1, campo2, …, campoN, rispettivamente di tipo tipo1, tipo2, …, tipoN. Il nuovo tipo di dato così definito sarà il tipo struct nome. ATTENZIONE: una definizione di record non è una dichiarazione di variabile, cioè non riserva spazio in memoria! Piuttosto, è una definizione di tipo di dato. Abbiamo, cioè, oltre ai tipi di dato predefiniti del C (int, float, char, ecc.), un nuovo tipo di dato che si chiama struct nome. Questo significa che per poter effettivamente andare a memorizzare dati in un record, occorre avere spazio in memoria, e cioè dichiarare variabili del nuovo tipo: solo la dichiarazione di variabili riserva spazio in memoria! Riprendiamo i nostri esempi, e andiamo a definire i record libro, cittadino e cane. struct libro { char char int char }; struct cittadino titolo[50]; autore[20]; anno; editore[20]; 3 { char char int char nome[20]; cognome[20]; anno_di_nascita; domicilio[50]; }; struct cane { char char int }; nome[20]; razza[20]; eta; Nota: per semplicità di esposizione, nel campo data di nascita del record cittadino abbiamo scelto di rappresentare solo l’anno, perché l’informazione completa è in effetti composta da tre dati: anno, mese, giorno. Per fare le cose in modo più preciso, avremmo potuto utilizzare un campo di tipo array di tre interi, oppure definire tre diversi campi. ESERCIZIO: pensare a un qualsiasi archivio, e definire la struttura dei suoi record. Utilizzo del tipo di dato record Come abbiamo detto, la definizione di un record non ci permette ancora di memorizzare dei dati al suo interno, piuttosto, definisce un nuovo tipo di dato. Per poter memorizzare informazioni in un record, abbiamo bisogno di una variabile del nuovo tipo. Esattamente come dichiariamo variabili di ogni altro tipo di dato in C: tipo nome; come ad esempio: int N; char nome[20]; possiamo dichiarare una variabile di tipo record scrivendo struct nome_struct nome_variabile; Ad esempio, facendo riferimento al record libro definito precedentemente, dichiariamo una variabile di tipo struct libro che si chiama un_libro: struct libro un_libro; L’effetto della dichiarazione della variabile un_libro di tipo struct libro è quello di riservare memoria in RAM, come si vede dalla seguente figura: 4 Per fare un altro esempio, facendo riferimento al record cane definito precedentemente, dichiariamo due variabili che si chiamano primo_cane e secondo_cane: struct cane primo_cane, secondo_cane; Programmando, può risultare abbastanza noioso dover ripetere la parola struct ogni qualvolta si vuole utilizzare il nuovo tipo di dato. Questo avviene nelle dichiarazioni di variabili, ma anche quando si vuole passare un record come parametro a una funzione (ricordate? Ogni parametro formale va definito con il suo tipo!). Per ovviare a questo inconveniente, il linguaggio C permette di effettuare una definizione esplicita di tipo, con la quale è possibile assegnare un nuovo nome al nuovo tipo appena definito, nel seguente modo: typedef struct nome nuovo_nome; Ad esempio, possiamo dare il nuovo nome precedentemente book al tipo di dato struct libro definito typedef struct libro book; A questo punto, il nuovo tipo di dato record che abbiamo definito si chiama book. Per dichiarare una variabile del nuovo tipo, supponiamo di nome un_altro_libro, basta scrivere book un_altro_libro; senza dover ripetere la parola struct. Come accedere ai campi di un record in memoria? Per lavorare con i record occorre poter accedere ai suoi dati interni, cioè i suoi campi. Per far questo, si utilizza l’operatore . (punto), nel seguente modo: nome_del_record . nome_del_campo Ad esempio, se vogliamo caricare in memoria nella variabile un_libro i dati di un particolare libro, dovremo scrivere: printf(“inserisci il titolo del libro: ”); scanf(“%s”, un_libro.titolo); printf(“inserisci l’autore del libro: ”); scanf(“%s”, un_libro.autore); printf(“inserisci l’anno di pubblicazione del libro: ”); scanf(“%d”, &un_libro.anno); printf(“inserisci la casa editrice: ”); scanf(“%s”, un_libro.editore); Oppure, se vogliamo controllare se il titolo del libro memorizzato è lo stesso di un titolo chiesto in input, dopo aver dichiarato una variabile dello stesso tipo del campo titolo 5 char titolo_da_cercare[50]; dovremo scrivere, ricordando di usare la strcmp per il confronto tra due stringhe: printf(“quale titolo cerchi? ”); scanf(“%s”, titolo_da_cercare); if (!strcmp(titolo_da_cercare, un_libro.titolo)) printf(“e’ il libro che stavi cercando!”); Facciamo un altro esempio: supponiamo di avere in RAM un record un_cane del tipo struct cane definita in un esempio precedente (anche in questo caso avremmo potuto utilizzare una typedef per non dover ripetere la parola chiave struct), e di voler visualizzare il suo campo eta. Basterà scrivere la seguente istruzione: printf(“%d”, un_cane.eta); Per concludere questa trattazione sui record, diciamo che i campi di un record possono essere di qualsiasi tipo, tra tutti quelli che abbiamo visto. Quindi, potremmo avere un intero, un reale, un carattere, una stringa (vettore di caratteri), un array, o anche un altro record. Attenzione alle notazioni da utilizzare per accedere ai campi, quando si utilizzano strutture dati e non dati semplici: facciamo qualche esempio: struct cittadino { char char int char }; /* definizione del tipo record */ nome[20]; cognome[20]; data_di_nascita[3]; domicilio[50]; /* l’array contiene tre interi: giorno, mese, anno, rispettivamente in posizione 0, 1, e 2 */ typedef struct cittadino citizen; /* definizione esplicita di tipo */ citizen c; /* dichiarazione della variabile c di tipo record */ printf(“nome: %s”, c.nome); /* visualizza il nome del cittadino */ printf(“giorno di nascita: %d”, c.data_di_nascita[0]); /* visualizza il giorno di nascita */ printf(“mese di nascita: %d”, c.data_di_nascita[1]); /* visualizza il mese di nascita */ printf(“anno di nascita: %d”, c.data_di_nascita[2]); /* visualizza l’anno di nascita */ Assegnamento multiplo A differenza degli array, i record permettono l’assegnamento multiplo, ossia, è possibile assegnare un record a un altro record, assegnando in una sola volta tutti i dati in esso contenuto. Facciamo un esempio: sia dato il seguente record struct libro { char titolo[50]; 6 char int char autore[20]; anno; editore[20]; }; typedef struct libro book; book b1, b2; Supponiamo di aver già caricato i campi del libro b1, con i dati b1.titolo = “L’idiota” b1.autore = “Dostoevskij” b1.anno = “1869” b1.editore = “Einaudi” Possiamo, con una sola istruzione, assegnare il record b1 al record b2 b2 = b1; e ritrovarci gli stessi valori nei campi del record b2. Si noti che questo non è possibile con gli array. Se si vuole assegnare un vettore a un altro vettore, ad esempio, occorre invece assegnare uno per uno ogni elemento! Cioè, se abbiamo int vet1[10],vet2[10]; e vogliamo assegnare al vettore v2 il vettore v1, è sbagliato scrivere vet2 = vet1 mentre bisogna eseguire un ciclo per assegnare, uno per uno, tutti gli elementi for(i=0; i<10; i++) vet2[i] = vet1[i];