240 likes | 393 Views
CORSO DI PROGRAMMAZIONE II Lezione 4 I FILE BINARI prof. E. Burattini a.a. 2009-2010. Esempi d’uso di file binari . Scriviamo un semplice programma che calcola la lunghezza di un file di qualsiasi natura. I commenti all’interno del codice forniscono ulteriori delucidazioni.
E N D
CORSO DI PROGRAMMAZIONE II Lezione 4 I FILE BINARI prof. E. Burattini a.a. 2009-2010
Esempi d’uso di file binari. Scriviamo un semplice programma che calcola la lunghezza di un file di qualsiasi natura. I commenti all’interno del codice forniscono ulteriori delucidazioni. // se il file non esiste il programma termina if (!file) { cerr<<"Non si puo' aprire il file"<<endl; system("pause"); return -1; } //il puntatore di lettura va alla fine del file file.seekg(0,ios::end); // legge la posizione del puntatore di lettura lun=file.tellg(); // chiude il file file.close(); cout<<"Lunghezza "<<Nomefile<< " ="<<lun<<endl; system("pause"); return 0; } #include <iostream> #include <cstdlib> #include <fstream> using namespace std; int main () { char Nomefile[80]; double lun; //il file è di sola lettura ifstream file; cout << "Nome File="; //se il file non è nella stessa directory fornire il path completo cin >> Nomefile; //file binario aperto in input file.open(Nomefile,ios::in|ios::binary);
Ci sono ancora due metodi che ci consentono di scrivere e leggere sui file binari. I loro prototipi sono write ( (char*) indirizzo-di-blocco-memoria, int grandezza-blocco) read ( (char*) indirizzo-di-blocco-memoria, int grandezza-blocco) Questi due metodi, abbastanza simili, gestiscono soltanto blocchi di memoria che, attraverso il casting, vengono trasformati in caratteri; il carattere * dopo il char, sta ad indicare che è necessario fornire soltanto l’indirizzo del primo carattere come primo parametro e la lunghezza di tutto il blocco come secondo parametro. .
Ad esempio per costruire un file di date, bisogna operare in questo modo: • definire un file di tipo fstream di nome • fstream datafile; • aprire il file datafile sia in input che in output ed in formato binario : • datafile.open(NomeFileFisico,ios::in|ios::out|ios::binary); • determinare la lunghezza del record Tpdata con l’istruzione sizeof: • lunghezza=sizeof(Tpdata); • definire almeno una variabile di tipo Tpdata: • Tpdata data1; • scrivere i dati posizionandosi nel punto voluto con l’istruzione seekp(……)e dare il comando: • write( (char*) &data1, lunghezza); • leggere i dati posizionandosi nel punto voluto con l’istruzione • seekg(……) • dare il comando: • read( (char*) &data1, lunghezza); • Se il file è di tipo ofstream si può utilizzare soltanto il metodo • write(……………), • se è di tipo ifstream si può solo leggere con il metodo • read(…………).
Esempio Riprendiamo l’esempio della lezione 2. In questo caso il programma deve gestire un insieme di persone conservando i dati sul disco; esso, inoltre, deve consentire l’inserimento dei dati, la ricerca di un dato conoscendo il cognome di una persona e la stampa su video di tutte le persone inserite o a scelta ordinate per data di nascita. Le function int MenuScelta(); void Inserimento(Tpersona&, char&); void Stampa(const Tpersona&); restano inalterate. L’unica function a cui dovremo apportare delle modifiche è la functionRicerca. .
Altre modifiche riguarderanno il main: dobbiamo introdurre la dichiarazione di un file binario ed una costante intera che rappresenti la lunghezza del record. La prima volta che viene richiamato il programma, non trovando alcun file fisico, il programma va in errore; nella gestione dell’errore esso crea il file e, dopo, termina. Tutte le esecuzioni successive non forniranno alcun errore di apertura del file. Riportiamo il programma principale senza le funzioni; successivamente discuteremo la nuova versione della funzione Ricerca. .
. // PROTOTIPI int MenuScelta(); void Inserimento(Tpersona&,char&); void Stampa(const Tpersona&); bool Ricerca(char[],fstream&, Tpersona&, int,const int); double dataNum(Tpdata x); void ordinaBuble(Tpersona [] , int n); void scambia (double&, double&);
Il main inizia con le dichiarazioni delle variabili persona1 di tipo Tpersona, corrente, che rappresenta l’indice corrente dell’array, NumPers, che rappresenta il numero attuale di persone memorizzate. scelta denota l’operazione da eseguire volta per volta, ch e cognome2 sono le variabili di input delle function Inserimento e Ricerca.
Il main contiene un ciclo do… while (il ciclo deve essere eseguito almeno una volta) al cui interno appare l’istruzione switch. caso 1 corrisponde all’inserimento dati; se l’utente preme ‘s’ i dati della persona vengono aggiunti all’array Persone e la variabile NumPers incrementata di uno; caso 2 si assegna il cognome da ricercare e si richiama la funzione Ricerca: se il valore restituito di corrente è maggiore di -1, si stampano tutti i dati, altrimenti si avverte l’utente che il dato non è stato trovato; caso 3 si esegue un ciclo for per stampare tutte le persone memorizzate. caso 4 si esegue un ciclo for per stampare tutte le persone ordinate per data di nascita.
La funzione MenuScelta deve scrivere sul monitor tutte le opzioni disponibili dando all’utente la possibilità di inserire soltanto un intero compreso tra 1 e 5. La funzione, dopo aver controllato che il valore rispetti tali limiti, deve restituirlo al programma chiamante. int MenuScelta() { int sc; do { cout <<" GESTIONE PERSONE "<< endl; cout <<" \n"; cout <<"1 - INSERIMENTO"<<endl; cout <<"2 - RICERCA"<<endl; cout <<"3 - STAMPA TUTTI"<<endl; cout <<"4 - STAMPA DATI ORDINATI x NASCITA"<<endl; cout <<"5 - FINE"<<endl; cout <<" Scelta="; cin >>sc; } while (sc<1 || sc>5); return sc; }
La procedura Inserimento restituisce la struttura persona in pers1 e la variabile carattere ch che può contenere il carattere ‘s’ o ‘n’. Le istruzioni della procedura si limitano ad acquisire i dati da tastiera. void Inserimento (Tpersona& pers1, char& ch) { cout << " INSERIMENTO PERSONE "<<endl; cout << "Cognome ="; cin >> pers1.cognome; cout << "Nome ="; cin >>pers1.nome; cout << "Data di nascita GG MM AAAA ="; cin >> pers1.nascita.giorno>>pers1.nascita.mese >>pers1.nascita.anno; cout <<"Luogo di nascita:"; cin >>pers1.luogo; cout<<"Salva (s/n)="; cin>>ch; }
La procedura Stampa visualizza sul monitor la struttura persona contenuta in pers1 con la clausola const ; essa ha lo scopo di non consentire la variazione dei dati membri della struttura. void Stampa(const Tpersona& pers1) { cout << "Cognome :"<< pers1.cognome<< " Nome:"<<pers1.nome<<endl; cout << "Luogo ="<<pers1.luogo << " Data di nascita :"; cout<<pers1.nascita.giorno<<'/'<<pers1.nascita.mese<<'/'<<pers1.nascita.anno; cout<<endl; }
#include <iostream> #include <fstream> using namespace std; struct Tpdata { int giorno; int mese; int anno; }; struct Tpersona { char cognome[30]; char nome[20]; Tpdata nascita; char luogo[20]; }; Tpersona Persone[30]; const char Nomefile[]="persone.dat"; // PROTOTIPI int MenuScelta(); void Inserimento(Tpersona&,char&); void Stampa(const Tpersona&); bool Ricerca(char[],fstream&, Tpersona&, int,const int); .
int main () { Tpersona persona1; int corrente; int NumPers; int scelta; char ch, cognome2[30]; const int Lrec=sizeof(Tpersona); // lunghezza della struttura Tpersona fstream filepers; // definizione ed apertura alla fine del file filepers.open(Nomefile,ios::in|ios::out|ios::binary|ios::ate); // apre il file in lettura o scrittura, modalità binaria, // posizionandosi alla fine //se il file non esiste lo crea con l'istruzione seguente if (!filepers) { filepers.open(Nomefile,ios::out|ios::binary|ios::trunc); // apre il file in modalità binaria, // posizionandosi in testa e cancellando quanto già c'è return 1; } NumPers=filepers.tellg()/Lrec; //calcola il numero di persone contenute nel file tellg()/ritorna un intero che rappresenta la posizione del puntatore di lettura
do { scelta=MenuScelta(); cout<<"Numero Persone = "<<NumPers<<endl; switch (scelta) { case 1: Inserimento (persona1,ch); if (ch=='s') { filepers.seekp(0,ios::end); //si posiziona per la scrittura alla fine del file filepers.write((char*) &persona1, Lrec); //scrive sul file NumPers++; //incrementa di uno il numero di persone inserite nel file } break; case 2: cout<<"\n Cognome da ricercare="; cin>>cognome2; if (Ricerca(cognome2, filepers, persona1, NumPers, Lrec)) Stampa(persona1); else cout<<"\n Dati non trovati"<<endl; break; posiziona il puntatore di scrittura nella posizione pos
La funzione, Ricerca, ricerca i dati di una persona. Il cognome inserito da tastiera viene inviato alla funzione che, attraverso una ricerca lineare, restituisce vero se la persona è stata trovata, falso nel caso in cui quel cognome non esiste nel file. bool Ricerca(char cognome2[], fstream &file, Tpersona &s1, int Num, const int Lr) { int i=0; file.seekg(0, ios::beg);//posiziona lettura testa file bool trovato=false; while (i<Num && !trovato) { file.read((char*) &s1, Lr); //legge da file un singolo record if (strcmp(s1.cognome,cognome2)==0) // confronta stringhe { trovato=true; } else i++; } return trovato; }
case 3: cout << "\n\n STAMPA DATI "<<endl; filepers.seekg(0,ios::beg); //si posizione in testa al file for (int i=0; i<NumPers; i++) { filepers.read((char*) &persona1, Lrec);//legge i dati di un record Stampa(persona1); } break; case 4: cout << "\n\n STAMPA DATI ORDINATI x NASCITA"<<endl; filepers.seekg(0,ios::beg); for (int i=0; i<NumPers; i++) { filepers.read((char*) &persona1, Lrec); //legge i dati di un record Persone[i]=persona1; } ordinaBubble(Persone, NumPers); for(corrente=0;corrente<NumPers;corrente++) Stampa(Persone[corrente]); break; } } while (scelta>0 && scelta<5); filepers.close(); return 0;
void ordinaBubble (Tpersona vet[], const int N) { int j, k; Tpdata nasc1,nasc2; for (k=0; k<N-1; k++) for (j=N-2; j>=k; j--) { nasc1=vet[j].nascita; nasc2=vet[j+1].nascita; if ( dataNum(nasc1) > dataNum(nasc2) ) scambia (vet[j],vet[j+1]); } } La procedura ordinaBubble ordina i dati che preventivamente sono stati inseriti in un array vet. void scambia (Tpersona &x1, Tpersona &x2) { Tpersona s; s=x1; x1=x2; x2=s; } double dataNum(Tpdata x) { return x.anno*10000+x.mese*100+x.giorno; } filebin2
case 4: cout << "\n STAMPA DATI ORDINATI x NASCITA"<<endl; filepers.seekg(0,ios::beg); int j=0; for (int i=0; i<NumPers; i++) { filepers.read((char*) &persona1, Lrec); //legge i dati di un record int k=0; if (j==0) {Persone[0]=persona1;j++;} else { k=j;Persone[k]=persona1; while ((k>=0)&& ((dataNum(Persone[k].nascita)<dataNum(Persone[k-1].nascita)))) { scambia(Persone[k],Persone[k-1]); k--; } j++; } } for(corrente=0;corrente<NumPers;corrente++) Stampa(Persone[corrente]); break; Ordinamento con insertion-sort
Esercizi. 1) Sia dato un file non ordinato Azioni.dat di record del tipo: struct tipor { char azione[20]; float valore_minimo; float valore_massimo; float valori_ultima_settimana[7]; }; Costruire un array di record chiamato Affari contenente tutti i record riguardanti le azioni che hanno un valore medio, nell’ultima settimana, maggiore o uguale dei due terzi del valore_massimo. Ordinare i record dell’array Affari per valore_minimo e, a parità, per nome azione.
2) Sia dato un file di articoli di magazzino, articoli.dat, contenente record del tipo struct TpArticolo { int Codice; char Descrizione[30]; int Quantità; double Costo, Prezzo; }; Scrivere una procedura che costruisca un array di record chiamato Guadagni che conservi tutti gli articoli il cui guadagno complessivo su ogni singolo pezzo è di almeno il 20%. Tali articoli, ordinati per guadagno complessivo decrescente, devono essere scritti sul file guadagni.dat. N.B. Guadagno complessivo = (Prezzo - costo)* Quantità.
3) Consideriamo le strutture seguenti: struct TpArt { int Codice; char Descrizione[30]; int Qmag: integer; //quantità contenuta in magazzino float Prezzo; int Scorta; //quantità minima in magazzino, oltre la quale si ordina altra merce }; struct TpVend { int Codice; int Qvend; }; Un grande magazzino ha degli articoli di tipo TpArt conservati nel file Articoli.bin; il campo Qmag rappresenta la quantità di quell’articolo presente in magazzino, Scorta la quantità minima necessaria per poter ordinare altri articoli dello stesso tipo. Il file Vendite.bin contiene record del tipo TpVend che rappresentano la vendita giornaliera degli articoli; il campo Codice è lo stesso di TpArt, mentre Qvend rappresenta la quantità venduta. Entrambi i file sono ordinati per codice. Scrivere una procedura che aggiorni il file Articoli.bin e stampi tutti gli articoli la cui quantità è minore uguale al campo Scorta ordinati in maniera crescente per Scorta, .
4) - Si considerino le definizioni: struct Tpfornitore{ int CodFor; //Codice Fornitore char RagSoc[30]; //nome fornitore char via[30]; char citta[30]; char provincia[2]; double TotAcq; //totale acquisti dal fornitore } struct Tpfattura{ int CodFor; //Codice Fornitore Tpdata DataF; //data fattura int numero; //numero fattura double netto; double iva; double totale; //totale fattura } struct Tpdata{ int giorno; int mese; int anno; } Una ditta acquista vari articoli dai fornitori contenuti nel file “fornitori.dat”. Il campo TotAcq rappresenta il totale in euro degli acquisti effettuati presso il fornitore nei primi tre trimestri dell’anno. Il file “fatture.dat” contiene, invece, le fatture di acquisto della ditta emesse durante tutto l’ultimo trimestre nei confronti degli stessi fornitori. Calcolare per ogni fornitore il totale degli acquisti effettuati dalla ditta. Se la cifra supera i100.000 euro, viene riconosciuto alla ditta un ulteriore sconto del 3% sul totale degli acquisti . Scrivere una procedura che scriva su un file “sconto.dat” Nome fornitore, città, provincia, rimborso da richiedere per tutti i fornitori della ditta, ordinati alfabeticamente per nome, a cui va richiesto lo sconto.