Teoria - Programmazione B
Stream come parametri di funzioni
es: funzione copia file (parametri stream sorgente e destinazione)
void copia_file(ifstream &S, ofstream &D)
{ char c;
c = s.get();
while (!s.eof())
{ d.put(c);
c = s.get();
}
return;
}
int main()
{ ...
ifstream f1;
f1.open("prova.txt");
ofstream f2;
f2.open("copia.prova.txt"); //copia_file (f1,cout) (stampa a video)
copia_file (f1,f2);
}
ifstream / ofstream sono sottoclassi (derivate) delle classi generali istream / ofstream (sottotipi).
In c++, se un parametro di una funzione f è di tipo/classe t è possibile passare a f (parametro di tipo
t un tipo t' sottotipo di t (conviene però dichiarare prima il parametro di tipo t).
In particolare:
void copia_file (istream &s, ostream &d)
{…}
cin:istream, cout:ostream parametri della copia_file
Leggi e stampa classe razionale
class razionale {
public:
void stampa (ostream &dest) { //void operator << (...)
dest << n << "/" << d;
return;
}
void leggi (istream &sorg) { //void operator >> (...)
sorg >> num >> sep >> den;
...
}
};
int main() {
razionale a(2,3);
...
a.stampa(cout); //stampa a video
...
ofstream f_out;
f_out.open("risultati.txt");
a.stampa(f_out) //stampa su file
}
Overloading operatori >> e <<
1) operatori >> e << come funzioni proprie della classe razionale (stesso di prima cambiando il
nome del void)
int main() {
razionale a(2,3);
cout << a; //errore
a << cout; //OK
cout.operator<<(a); //?
}
2) gli operatori >> e << li definiamo come funzioni esterne alla classe razionale → n e d non
accessibili → funzioni proprie della classe razionale leggi e stampa mentre operator >> e operator
<< si limitano a richiamare leggi e stampa.
Teniamo la classe razionale di partenza:
- ostream &
void operator << (ostream &dest, razionale x) {
x.stampa(dest);
return;
} //return dest
- istream &
void operator >> (istream &sorg, razionale &x) {
x.leggi(sorg);
return; } //return sorg
int main() {
razionale a(2,3);
...
cout << a; //operator << (cout,a) OK
razionale b;
cin >> b; //operator >> (cin,b) OK
}
3) miglioramento della 2: gli operatori >> e << li definiamo come funzioni esterne con tipo delle
funzioni ostream / istream
→ cout << a << endl;
“ostream &” → reference return;
In generale int f(int x) {… return z; } il valore di z è copiato nella variabile temporanea della
chiamata.
int main() {
int a, b=5;
a = f(b)+3;
temp z;
}
Per evitare la copia del risultato “int & f(int x)” z deve continuare ad esistere anche dopo la
funzione f (z non può essere dichiarato nella funzione... può essere passato come riferimento alla f).
Parametri const reference
es: inf f(const int & x) {…}
& → evita la copia
const → evita modifica da parte di f
Vantaggi:
– No side-effect sul parametro attuale (come per il passaggio per valore) il parametro passato
non può essere modificato.
– Efficienza
– No copia: evita problemi con oggetti difficili da copiare
Es: class razionale
class razionale
{ ...
razionale operator+ (const razionale & s) const {...}
razionale operator < (const razionale & s) const {...}
void stampa (ostream & dest) const {...}
}
ofstream & operator << (ostream & dest, const razionale & x) {...}
istream & operator >> (istream & sorg, razionale & x) {...}
n.b. es:
const razionale unmezzo (1,2);
...
c=a+unmezzo;
unmezzo.stampa(cout); // ½
– Il compilatore deve sapere che una funzione non modifica la const
– Tutte le funzioni invocate su un oggetto const devono essere funzioni const (la funzione
const non modifica l'oggetto di invocazione)
– Stessi problemi per gli oggetti dichiarati const &
es: ostream operator << (ostream & dest, const razionale & x) {
x.stampa(dest);
}
Invoca funzione propria di razionale su oggetto const.
Passaggio parametri – valori di default
Nella dichiarazione di una funzione (interna o esterna) è possibile dare un valore di inizializzazione
ad 1 o più parametri.
Es: int f(int x, int y=0) {…}
Nella chiamata di funzione è possibile omettere valore parametro attuale per parametro
inizializzato.
Nb: - valori di default per ultimi
- attribuiti alle funzioni overloaded
TIPO STRINGA string
Definizione (informale): sequenze di n caratteri consecutivi (n>=0)
es: "c1,c2,...cn"
" " = stringa vuota
operazioni :
- lunghezza stringa (attributi stringa)
- confronto tra stringhe ( ==, =!,< etc..)
- assegnamento tra stringhe ( r <---- s)
- selezione dell'i-esimo carattere
- lettura e scrittura di stringhe
- ricerca sottostringa
etc...
Implementazione stringhe
dobbiamo scegliere una rappresentazione dei dati, abbiamo due alternative :
- stringa come vettore: vettore statico (stringhe tipo C) o dinamico
- stringa come lista concatenata
implementazione in c++ : avviene tramite il costrutto CLASS e precisamente con una classe default
di nome STRING
#include <string>
es:
class string {
private:
int lungh;
//implementazione stringa,la rappresentazione di una struttura dati completa x la stringa
public :
//costruttori :
static const int npos= -1;
string () {
lung =0;
...}
string (char s[]) {
lungh =strlen(s); // a "lungh" assegno la lunghezza di s
...}
string (int n,char f) {
...
for(int i=0;izn;i++) { "assegna a i-esimo elem. F" }
//ora facciamo una funzione size
int size () {
return lungh;}
string operator = ( const string& r) {
...} // ottiene la copia di una stringa
string operator = ( const char r []) {
...}
bool operator == (const string& r) {...}
string operator + (const string& r) {...}
string operator + (char c) {...}
char operator [] ( int i) {...}
string substr (int p,int n){...}
int find (const string& r,int p) {
if("trovata") return "posizione iniziale di r "
else return npos;
} istream& operator >> (istream& sorg,string& s) {...}
ostream& operator << ( ostram& dest,const string & s) { ...}
istream getline (istream&sorg,string& s,char d=='\n') {...}
Uso nel main :
#include <string>
int main () {
string s; // ogg di classe string = stringa vuota
string r= "ciao"; // E' uguale a scrivere sting r("ciao"),"ciao" è un array di caratteri
//crea una stringa di nome r inizializzata con "ciao",l'attributo della stringa E' 4
string t(3,'a') // crea una stringa t e inizializza i primi 3 car. con la lettera a ( aaa )
Lunghezza stringa (size)
s.size() ---> lunchezza stringa s
es: string data = "22 maggio 2002" ;
cout <<data.size (); --- > 14
Assegnamento tra stringhe
s=r //ad s assegna la stringa r;
es: string r="ciao";
string s; //vuota
s = r; // s = ciao
es: string s;
s = "ciao"
//il "=" va ridefinito per degli array char,il primo operator usato su string nn va bene!
//infatti se si vede sopra gli "operator =" sono due!
Alcune operazioni:
=, ==,!=,<=,>= etc...
s==r ---> s.operator == (r)
Concatenazione:
s+r --> restituisce una nuova stringa concatenando r a s
es: string s1 = "ciao";
string s2 = s1 + "mondo"; --> s1.operator + ("mondo")
string s3 = s2 + '!';
cout<<s3;
//nb: string s="a";
//string s='a'; ---> errore!
Selezione singolo carattere:
s[i]
es: string s= "ciao";
cout<< s[1]; --> stampa i //s[1] verr‡ tradotta in : s.operator[] (1)
s[0] ='m'; //s[0] verr‡ tradotta in : s.operator[] (0)
cout <<s; --> miao
Sottostringa
s.substr (p,n) //restituisce la sottostringa r di s a partire da p lunga n
es: string data= "22 maggio 2002"
string mese;
mese= data.substr(3,6);
cout<<mese; ---> //stampa maggio
Ricerca sottostringa
s.find(r,p)
//cerca una sottostringa r all'interno di s a partire dalla posizione p
il risultato è un valore int che rappresenta la posizione iniziale dove E' stata trovata la stringa r,se
non la trova restituisce un valore speciale npos (string :: npos)
es: string frase="informatica";
int i;
i=frase.find("for",0) // cerco "for" a partire da 0
if( i==string :: npos) { cout<<"non trovata!"<<endl; }
else cout<<"trovata in posizione"<< i;
Lettura e scrittura stringhe
es << : string msg1= "immetti numero";
cout <<"ms1;
es: cin >> string nome,cognome;
cin >>nome;
cin>> cognome; ---> operator >> (cin,cognome)
//ignora spazi iniziali e termina al primo spazio o a capo
getline (f,s,d) // legge dallo stram di input f la stringa s;termina quando incontra il
carattere d o incontra end of file
es: string nomefile;
getline(cin,nomefile,'\n')
con stringhe di tipo c:
char nome.file[100];
cin.getline (nomefile,100,'\n');
Voglio passare da string a stringa di "tipo C" e viceversa
es: char c [10]="ciao";
string s = c; --> da cstring a string!
"nb :" per fare il contrario ( string --> cstring) usiamo una funzione della classe string: c_str
es: s.c_str () ---> r; // converte la stringa di tipo string nella stringa di "tipo c" r
"nb" : nome di un file E' una stringa "tipo c" ( = array di char)
es : string nomefile;
cin >> nomefile;
...
istrean f1;
f1.open (nomefile.c_str());
LISTA DI INTERI tipo di dato astratto
- valori: sequenza di n ( n >=0) numeri interi
es: ( i1,i2,...,in)
Operazioni :
- lunghezza della lista ( attributo)
- elemento di testa di una lista (attributo)
- elemento di cosa di una lista (attributo)
- inserisci/estrai testa
- inserisci/estrauÏi coda
- inserisci/estrai prima\dopo una certa informazione x
- concatenazione di due liste
...
nb: - no dimensione massima
- alta dinamicità
- no accesso tramite indice
Implementazione
Lista: dobbiamo decidere la rappresentazione dei dati, abbiamo due scelte:
- con un vettore (array) dinamico
- lista concatenata
Implementazione liste concatenate con class
class lista
Sol. 1) tipo elem con struct
struct elem {
int info;
elem*punt;};
class lista {
private : elem* l;
public :
lista (){
l=NULL;
}
void inseriscitesta (int x) {
elem *t;
t= new elem;
t->info =x;
t-put=l;
l=t:
return;}
Uso int main (){
lista l1; //crea lista l1 vuota
lista l2; // creo lista l2 vuota
ll.inseriscitesta (5);
Class lista
struct elem {
int info;
elem*punt;}
class lista {
private :
elem* l;
public :
lista ()
{l=NULL;}
void inseriscitesta (int x) {
elem*t;
t=new elem:
t->info=x;
t->int=l;
l=t;
return;}
void stampa (ostream & dest) {
elem * t =l;
while ( t!= NULL) {
dest<<t->info<<" ";
t= t->punt;
}
return;}
...
};
...
int main (){
lista l1;int x,y;
cout <<"dai un numero"<<endl;
cin >>x;
l1.inseriscitesta(x); // mette x in testa di l1
cout<<"dai un numero"<<endl;
cin>>y;
l1.inseriscitesta(y); //mette y in testa ;alloca un nuovo elemento e lo attacca alla lista
lista l2;
l2.inseriscitesta(y); //crea una nuova lista concatenata e mette in testa y
...
}
Lista senza class
struct elem {..};
void inseriscitesta ( elem *& l,int x) {
elem *t;
t= new elem;
t-> info=x;
t-> punt =l;
l=t;
return; }
void stampa ( ostream& dest, elem * l) {
while ( l!=NULL) {
l = l->punt;}
...
int main () {
elem + l1 = NULL;
int x,y;
cout <<"...";
cin >> x;
inseriscitesta (l1,x) ;
cout <<"...";
cin >>y;
inseriscitesta (l1,y);
Variante con typedef
typedef int t_info; // t_info E' di tipo int
struct elem {
t_info info;
elem * punt;};
class lista {
...
void inseriscitesta ( t_info x) { => maggior modificabilità del programma
...}
...
};
Variante 2 senza struct
class elem {
private : int info;
elem * punt;
public :
elem () {
punt =NULL;}
elem (int x,elem*s=NULL) {
info= x;
punt = s;
int get_info () {
return info; } //SELETTORI (getter)
elem * get punt () {
return punt; }
void put_info (int x)
info=x;}
void put_punt( elem *s) {
punt= s;}
void stampa (ostream& dest)
{dest <<info;}
}
class lista {
private : elem *l;
public :
lista () { l=NULL;}
void inseriscitesta(int x) {
elem *t;
t=new elem;
void inseriscitesta (int x) {
elem *t;
t=new elem;}
t->put->info (x);
t->put_punt(l);
l=t;
return;}
void stampa (ostream & dest){
...
dest<<t->get_info()<<" ";}
Distruttori
struct elem {
int info;
elem*punt;}
class lista {
private:
elem* l;
public:
lista ()
{l=NULL;}
Distruzione di un oggetto
void f (int x) {
razionale A;
.....
return; → distruzione di A,invoca il distruttore della classe razionale e rilascia la
memoria allocata per A
}
es: class razionale {
...
public :
...
~razionale () {
...}
...}
ogni classe ha distruttore predefinito (non fa nulla) con corpo vuoto {} il distruttore di qualsiasi
classe può essere ridefinito quando voglio.
Es: class razionale {
...
public :
...
~razionale ()
{ cout <<"sto morendo"<<endl;}
...
}
es: clase lista
void f (int x) {
lista l1;
....
l1.inseriscilista (x);
l1.inseriscilista (y);
return; ---> dealloca l1 ma NON dealloca elem. della lista ==> GARBAGE
}
int main () {
f(3);
soluzione: ridefinire il distruttore della classe lista che rimpiazza quello di default
~lista () {
while (l!=NULL){
elem t;
t= l-->punt;
delete l;
l=t;}
}
GESTIONE MEMORIA DI UN PROGRAMMA
3 tipo diversi di allocazione della memoria :
- allocazione statica → usata in particolare per variabili dichiarate globalmente del programma
- allocazione automatica → allocazione dati locali a un blocco. Per “blocco di dati locali” si
intende variabili locali, parametri formali, variabili temporanee per risultati intermedi, indirizzo di
ritorno, questi sono anche chiamati RECORD DI ATTIVAZIONE DELLA FUNZIONE
- allocazione dinamica
es – allocazione statica:
#include <iostream>
int A[10];
int x; → due variabili globali allocate staticamente (memoria allocata a compile time
e deallocata alla fine del programma) il tempo di vita è l'intero programma
int main () {...
...}
es – allocazione automatica :
int f (int z) {
int x=0; ===> record d attivazione attivato automaticamente quando si entra nel
blocco (es quando si fa la chiamata della funzione,li allora viene allocata la memoria) il record
d'attivazione viene deallocato quando si esce dal blocco,quindi il tempo di vita di variabili
automatiche è il blocco, ovvero il tempo di esecuzione del blocco in cui la variabile appare
return x+1;
}
int main () {
int A;
A=f(7); → r.a. allocati in area di memoria gestita come pila (stack)
...}
es2 - allocazione automatica :
int g (int y) {
int x=1;
…}
int f (int z) {
int x=0;
x=g(3);
... → "catena dinamica delle chiamate di funzioni"
}
int main () {
int A=7;
cout <<f(A);
...
}
Pila dei record di attivazione (dimensione non nota a priori)
es - allocazione dinamica (funzioni ricorsive)
fatt (int x)
{ ...
return fatt (x-1)*x;
}
int main () {
fatt (4);
Se la pila E' piena si ha un errore runtime "stack overflow"
es2 - allocazione dinamica (creazione tramite new)
int main() {
int* x; → allocata automaticamente
x=new int(1); → allocazione dinamica
- distruzione esplicita tramite delete
- tempo di vita variabile dinamica = intervallo di tempo tra new e delete
- allocate in heap → aree di memoria distinte tra aree libere e occupate (tramite tabella o liste)
ovviamente quelle libere sono quelle usate dalla new
es3: int A; → variabile statica
int f ()
{ int *x; → variabile automatica
x=new int(1); → variabile dinamica
...
}
int main () {
int z=5; → variabile automatica
f();
}
Variabile dinamica non deallocata al termine della funzione f ( ma il puntatore x è deallocato) → si
forma della spazzatura ("garbage") → usare delete
GESTIONE ECCEZIONI
Il problema è gestire in modo opportuno situazioni anomale che si verificano durante
l'ESECUZIONE del programma (gestire gli errori a runtime).
Soluzione SENZA gestione eccezioni: l'errore in una funzione viene tipicamente gestito nella
funzione con (ad es) un messaggio d'errore
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Programmazione web
-
Appunti Linguaggi di programmazione
-
Programmazione e controllo Appunti
-
Teoria di Informatica e Programmazione