Blog del corso di Programmazione (9 CFU) tenuto da Marco La Cascia presso l'Universita' di Palermo per il corso di laurea in Ingegneria Informatica e delle Telecomunicazioni. Tratta la programmazione a oggetti in Java.
martedì 18 gennaio 2011
Possibili soluzioni esercitazione 8
Disponibile una possibile soluzione dell'esercitazione 8. Come sempre, commenti e richieste di chiarimenti sono benevenuti.
Prof se creo due oggetti Appuntamento a,b perché quando eseguo l'istruzione: Appuntamento b(a); Mi esegue tutto tranquillamente anche se il prototipo del costruttore prevede : Appuntamento(string="", int=8, int=0, int=8, int=30);
@Giuseppeian: i const mi sono scappati inavvertitamente ma sono una buona regola di programmazione... Il problema e' che ancora non ne abbiamo parlato a lezione. In breve se so che un metodo non deve modificare dati membro della classe (come nel caso delle funzioni get) lo posso definire const e questo implica che, se inavvertitamente modifico un dato membro, il compilatore mi segnale l'errore. Inoltre i metodi const sono gli unici che possono essere chiamati per oggetti const, ma ne parleremo dopo.
@SalvoinZ: scrivere Appuntamento b(a); (che poi e' equivalente a scrivere Appuntamento b = a;) implica che l'oggetto b viene creato come copia dell'oggetto a a quindi non viene chiamato il costruttore ma viene fatta la copia bit-a-bit. La situazione e' analoga a quando passi un oggetto per valore a una funzione e viene costruito all'interno della funzione un oggetto come copia del valore passato facendo copia bit-a-bit senza chiamare costruttori. In queste situazioni l'unico costruttore che sarebbe chiamato sarebbe il costruttore di copie se implementato.
@Roberto: se ho intuito bene cio' che hai fatto (dato che non hai postato codice) il compilatore ti dice "default argument missing for parameter 2 of `Appuntamento::Appuntamento(std::string, Time, Time)' " perche' tu provi a costruire un oggetto nel main passando solo una string e quindi gli mancano gli ultimi due parametri. Se crei l'oggetto passando una string e due oggetti Time non ti dovrebbe dare piu' l'errore. L'argomento di default di tipo int per un parametro Time ha senso perche' in Time c'e' un costruttore che permette di costruire un oggetto a partire da un int quindi come parametro di default verra' usato l'oggetto Time che otterresti ad esempio con Time t(0); Se non era questo che cio' che intendevi posta il codice che ne riparliamo.
//Appuntamento.h #ifndef APPUNTAMENTO_H #define APPUNTAMENTO_H #include "time.h" #include using std::string;
class Appuntamento{
public: Appuntamento(string = "vuoto" , Time =0 , Time =0 ); ~Appuntamento(){} void setappuntamento(string , Time , Time); Time getorai(); Time getoraf(); void stampa();
private: string descrizione; Time orai; Time oraf; };
#endif
/*metodi della classe appuntamento file appuntamento.cpp*/
#include "appuntamento.h" #include "time.h" #include using std::cout; using std::endl; using std::string;
void Appuntamento::setappuntamento(string desc, Time iniziale , Time finale ){ descrizione=desc; orai=iniziale; oraf=finale; }
Appuntamento::Appuntamento(string desc, Time iniziale , Time finale){ setappuntamento(desc,iniziale,finale); }
Time Appuntamento::getorai(){return orai;} Time Appuntamento::getoraf(){return oraf;}
se in appuntamento.h anzicché scrivere Appuntamento(string = "vuoto" , Time =0 , Time =0 ); avessi scritto: Appuntamento(string = "vuoto" , Time , Time) quindi senza inizializzare gli oggetti time , avrei ottenuto quell'errore lì
@Roberto: non avevo notato l'ordine degli argomenti di default, indipendentemente da tutti gli altri discorsi fatti nel commento precedente una funzione come Appuntamento(string = "vuoto" , Time , Time) non puo' mai compilare perche' gli argomenti di default devono essere i piu' a destra (come caso particolare possono essere anche tutti) e non a sinistra come fatto da te.
adesso funziona , mi era sfuggito questo piccolo particolare :D , non riuscivo proprio a capire xkè non funzionasse , ora è tutto + chiaro , per il resto è strutturato bene il programma?
@Roberto: come struttura va bene, la scelta dei nomi dei metodi non e' tanto felice. Per maggiore leggibilita' spesso si usano le maiuscole per separare le parole che compongono il nome, ad esempio setOra() e getOra() invece di setora() e getora().
// Lo mette subito dopo curr e aggiusta i puntatori else { tmp->prec=curr; tmp->succ=curr->succ; curr->succ = tmp; (tmp->succ)->prec = tmp; } } QUESTO È il codice che ha postato nella soluzione dell'esercitazione 5numero 11 , non ho capito bene all'interno dell'else , come faccio ad aggiornare nuovamente current al nodo corrente, (che sarebbe tmp) , ed inoltre al termine del if ed else , non dovrei deallocare tmp?
@Roberto: current non c'e' bisogno di aggiornarlo al nodo appena aggiunto. In pratica si aggiustano i puntatori per inserire il nuovo nodo fra il nodo corrente e il successivo ma il nodo corrente resta invariato. Non devi deallocare tmp; la lista e' dinamica e ogni nodo lo crei quindi con new al momento dell'inserimento e lo deallochi solo quando devi eliminarlo dalla lista.
P.S. La prossima volta se hai un quesito relativo all'esercitazione X pubblicalo nel post relativo all'esercitazione X e non nell'ultimo post disponibile.
quindi se ho ben capito , il primo nodo inserito è il corrente , il 2° è "l'ultimo della lista" che comunque è contcatenato con il corrente , ogno volta che inserisco un nodo , viene posto subito dopo il corrente e prima del'ultimo nodo inserito e vengono aggiustati i puntatori. Quindi quando scrivo tmp=new Nodo , al puntatore tmp attribuisco un nuovo indirizzo di un blocco di memoria dedicato a un nodo , e giustamente se lo cancellassi perderei le informazioni su quel nodo , o sbaglio?
mentre per quanto riguarda la cancellazione , il mio nuovo nodo corrente diventa il nodo successivo a quello corrente , (naturalmente dopo aver riordinato i puntatori) , fatto ciò dealloco l'ex nodo corrente ( il quale indirizzo di memoria è stato precedentemente salvato in tmp) .
cmq credo di aver capito !!! , ora provo a implementarla con la classe
void Lista::eliminaNodo(){ if(corrente != NULL){ // se lista vuota non fa nulla if(corrente->getSucc() == corrente){ // caso di lista con un solo nodo corrente = NULL; } else { corrente->getPrec()->setSucc(corrente->getSucc()); corrente->getSucc()->setPrec(corrente->getPrec()); corrente = corrente->getPrec(); } } }
vorrei un chiarimento su questa implementazione. quando abbiamo il caso di lista con un solo elemento perché impostiamo il corrente uguale a NULL? Non si potrebbe liberare direttamente la memoria con delete?
Se abbiamo una lista con un solo elemento e dobbiamo eliminare quell'elemento allora dobbiamo settare corrente a NULL e poi liberare la memoria che occupava quel nodo. Fare solo la delete senza aggiustare corrente non sarebbe corretto. Comunque, dal tuo post mi sono accorto il codice pubblicato non e' del tutto corretto. A causa di una mia dimenticanza, infatti si e' creato un memory leak. Osservate che non viene mai fatta la delete e quindi i nodi che vengono eliminati dalla lista restano di fatto in memoria anche se non sono piu' accessibili. Il codice che avevo pubblicato aggiusta i puntatori, setta il nuovo valore per corrente ma non dealloca il vecchio corrente che e' appunto il nodo da eliminare. Il codice corretto e' il seguente:
void Lista::eliminaNodo(){ if(corrente != NULL){ // se lista vuota non fa nulla Nodo* old_corrente = corrente; if(corrente->getSucc() == corrente){ // caso di lista con un solo nodo corrente = NULL; } else { corrente->getPrec()->setSucc(corrente->getSucc()); corrente->getSucc()->setPrec(corrente->getPrec()); corrente = corrente->getPrec(); } delete old_corrente; } }
Prof.re,
RispondiEliminaperchè nelle funzioni get dopo i parametri ha inserito const?
Esempio:
int getOra() const;
int getMin() const;
int getSec() const;
Prof se creo due oggetti Appuntamento a,b perché quando eseguo l'istruzione:
RispondiEliminaAppuntamento b(a);
Mi esegue tutto tranquillamente anche se il prototipo del costruttore prevede :
Appuntamento(string="", int=8, int=0, int=8, int=30);
ho svolto l'esercizio 5 (Appuntamentoe time) , struttuando il programma su più file , ho :
RispondiEliminaAppuntamento.cpp
Appuntamento.h
Time.cpp
Time.h
Nel file appuntamento.h ho dichiarato il costruttore nel seguente modo:
Appuntamento(string = "vuoto" , Time , Time );
ed nel file appuntamento.cpp ho scritto:
Appuntamento::Appuntamento(string desc, Time iniziale , Time finale){
setappuntamento(desc,iniziale,finale);}
tuttavia in fase di compilazione ottengo il seguente errore:
default argument missing for parameter 2 of `Appuntamento::Appuntamento(std::string, Time, Time)'
ho risolto attribuendo un valore di default =0 nel prototipo del costruttore , a gli oggetti Time , cioè scrivendo in Appuntamento.h
Appuntamento(string = "vuoto" , Time =0 , Time =0 );
Adesso sembrerebbe funzionare , xò non comprendo il perché ! e vorrei sapere cosa significa attribuire ad un oggetto Time il valore 0!!
@Giuseppeian: i const mi sono scappati inavvertitamente ma sono una buona regola di programmazione... Il problema e' che ancora non ne abbiamo parlato a lezione.
RispondiEliminaIn breve se so che un metodo non deve modificare dati membro della classe (come nel caso delle funzioni get) lo posso definire const e questo implica che, se inavvertitamente modifico un dato membro, il compilatore mi segnale l'errore.
Inoltre i metodi const sono gli unici che possono essere chiamati per oggetti const, ma ne parleremo dopo.
@SalvoinZ: scrivere Appuntamento b(a); (che poi e' equivalente a scrivere Appuntamento b = a;) implica che l'oggetto b viene creato come copia dell'oggetto a a quindi non viene chiamato il costruttore ma viene fatta la copia bit-a-bit.
RispondiEliminaLa situazione e' analoga a quando passi un oggetto per valore a una funzione e viene costruito all'interno della funzione un oggetto come copia del valore passato facendo copia bit-a-bit senza chiamare costruttori.
In queste situazioni l'unico costruttore che sarebbe chiamato sarebbe il costruttore di copie se implementato.
@Roberto: se ho intuito bene cio' che hai fatto (dato che non hai postato codice) il compilatore ti dice "default argument missing for parameter 2 of `Appuntamento::Appuntamento(std::string, Time, Time)' " perche' tu provi a costruire un oggetto nel main passando solo una string e quindi gli mancano gli ultimi due parametri. Se crei l'oggetto passando una string e due oggetti Time non ti dovrebbe dare piu' l'errore.
RispondiEliminaL'argomento di default di tipo int per un parametro Time ha senso perche' in Time c'e' un costruttore che permette di costruire un oggetto a partire da un int quindi come parametro di default verra' usato l'oggetto Time che otterresti ad esempio con Time t(0);
Se non era questo che cio' che intendevi posta il codice che ne riparliamo.
//Appuntamento.h
RispondiElimina#ifndef APPUNTAMENTO_H
#define APPUNTAMENTO_H
#include "time.h"
#include
using std::string;
class Appuntamento{
public:
Appuntamento(string = "vuoto" , Time =0 , Time =0 );
~Appuntamento(){}
void setappuntamento(string , Time , Time);
Time getorai();
Time getoraf();
void stampa();
private:
string descrizione;
Time orai;
Time oraf;
};
#endif
/*metodi della classe appuntamento file appuntamento.cpp*/
#include "appuntamento.h"
#include "time.h"
#include
using std::cout;
using std::endl;
using std::string;
void Appuntamento::setappuntamento(string desc, Time iniziale , Time finale ){
descrizione=desc;
orai=iniziale;
oraf=finale;
}
Appuntamento::Appuntamento(string desc, Time iniziale , Time finale){
setappuntamento(desc,iniziale,finale);
}
Time Appuntamento::getorai(){return orai;}
Time Appuntamento::getoraf(){return oraf;}
void Appuntamento::stampa(){
cout << "Appuntamento dalle: " ;
orai.stampa();
cout << "alle: ";
oraf.stampa();
cout << endl;
cout << descrizione << endl;
}
//Time.h
#ifndef TIME_H
#define TIME_H
#include
class Time{
public:
Time(int = 0 , int = 0 , int = 0);
~Time(){}
void setall(int , int , int);
void setora(int);
void setmin(int);
void setsec(int);
int getora();
int getmin();
int getsec();
void stampa();
private:
int ore;
int minuti;
int secondi;
};
#endif // TIME_H
//Time.cpp
#include "time.h"
#include
using std::cout;
using std::endl;
Time::Time(int h , int m , int s){setall(h,m,s);}
void Time::setall(int h, int m, int s){
setora(h);
setmin(m);
setsec(s);
}
void Time::setora(int h){ h>=0 && h<=23 ? ore=h : ore=getora();}
void Time::setmin(int m){ minuti=(m>=0 && m<=59) ? m:getmin();}
void Time::setsec(int s){ secondi=(s>=0 && s<=59) ? s:getsec();}
int Time::getora(){return ore;}
int Time::getmin(){return minuti;}
int Time::getsec(){return secondi;}
void Time::stampa(){ cout << getora() << ":" << getmin() << ":" << getsec() << endl;}
se in appuntamento.h anzicché scrivere Appuntamento(string = "vuoto" , Time =0 , Time =0 ); avessi scritto:
Appuntamento(string = "vuoto" , Time , Time) quindi senza inizializzare gli oggetti time , avrei ottenuto quell'errore lì
@Roberto: non avevo notato l'ordine degli argomenti di default, indipendentemente da tutti gli altri discorsi fatti nel commento precedente una funzione come Appuntamento(string = "vuoto" , Time , Time) non puo' mai compilare perche' gli argomenti di default devono essere i piu' a destra (come caso particolare possono essere anche tutti) e non a sinistra come fatto da te.
RispondiEliminaadesso funziona , mi era sfuggito questo piccolo particolare :D , non riuscivo proprio a capire xkè non funzionasse , ora è tutto + chiaro , per il resto è strutturato bene il programma?
RispondiElimina@Roberto: come struttura va bene, la scelta dei nomi dei metodi non e' tanto felice. Per maggiore leggibilita' spesso si usano le maiuscole per separare le parole che compongono il nome, ad esempio setOra() e getOra() invece di setora() e getora().
RispondiEliminastruct Nodo {
RispondiEliminaint dato;
Nodo* prec;
Nodo* succ;
};
int main(){
Nodo* curr = NULL; // Puntatore al nodo corrente
Nodo* tmp;
// CREA NUOVO NODO
tmp = new Nodo;
cout << "Inserisci dato del nuovo nodo: ";
cin >> tmp->dato;
if(curr == NULL){
tmp->prec=tmp;
tmp->succ=tmp;
curr = tmp;
}
// Lo mette subito dopo curr e aggiusta i puntatori
else {
tmp->prec=curr;
tmp->succ=curr->succ;
curr->succ = tmp;
(tmp->succ)->prec = tmp;
}
}
QUESTO È il codice che ha postato nella soluzione dell'esercitazione 5numero 11 , non ho capito bene all'interno dell'else , come faccio ad aggiornare nuovamente current al nodo corrente, (che sarebbe tmp) , ed inoltre al termine del if ed else , non dovrei deallocare tmp?
@Roberto: current non c'e' bisogno di aggiornarlo al nodo appena aggiunto. In pratica si aggiustano i puntatori per inserire il nuovo nodo fra il nodo corrente e il successivo ma il nodo corrente resta invariato.
RispondiEliminaNon devi deallocare tmp; la lista e' dinamica e ogni nodo lo crei quindi con new al momento dell'inserimento e lo deallochi solo quando devi eliminarlo dalla lista.
P.S. La prossima volta se hai un quesito relativo all'esercitazione X pubblicalo nel post relativo all'esercitazione X e non nell'ultimo post disponibile.
quindi se ho ben capito , il primo nodo inserito è il corrente , il 2° è "l'ultimo della lista" che comunque è contcatenato con il corrente , ogno volta che inserisco un nodo , viene posto subito dopo il corrente e prima del'ultimo nodo inserito e vengono aggiustati i puntatori. Quindi quando scrivo tmp=new Nodo , al puntatore tmp attribuisco un nuovo indirizzo di un blocco di memoria dedicato a un nodo , e giustamente se lo cancellassi perderei le informazioni su quel nodo , o sbaglio?
RispondiEliminamentre per quanto riguarda la cancellazione , il mio nuovo nodo corrente diventa il nodo successivo a quello corrente , (naturalmente dopo aver riordinato i puntatori) , fatto ciò dealloco l'ex nodo corrente ( il quale indirizzo di memoria è stato precedentemente salvato in tmp) .
cmq credo di aver capito !!! , ora provo a implementarla con la classe
sono riuscito a implementarla con la classe :)
RispondiEliminavoid Lista::eliminaNodo(){
RispondiEliminaif(corrente != NULL){ // se lista vuota non fa nulla
if(corrente->getSucc() == corrente){ // caso di lista con un solo nodo
corrente = NULL;
} else {
corrente->getPrec()->setSucc(corrente->getSucc());
corrente->getSucc()->setPrec(corrente->getPrec());
corrente = corrente->getPrec();
}
}
}
vorrei un chiarimento su questa implementazione. quando abbiamo il caso di lista con un solo elemento perché impostiamo il corrente uguale a NULL? Non si potrebbe liberare direttamente la memoria con delete?
Se abbiamo una lista con un solo elemento e dobbiamo eliminare quell'elemento allora dobbiamo settare corrente a NULL e poi liberare la memoria che occupava quel nodo. Fare solo la delete senza aggiustare corrente non sarebbe corretto.
RispondiEliminaComunque, dal tuo post mi sono accorto il codice pubblicato non e' del tutto corretto. A causa di una mia dimenticanza, infatti si e' creato un memory leak. Osservate che non viene mai fatta la delete e quindi i nodi che vengono eliminati dalla lista restano di fatto in memoria anche se non sono piu' accessibili. Il codice che avevo pubblicato aggiusta i puntatori, setta il nuovo valore per corrente ma non dealloca il vecchio corrente che e' appunto il nodo da eliminare. Il codice corretto e' il seguente:
void Lista::eliminaNodo(){
if(corrente != NULL){ // se lista vuota non fa nulla
Nodo* old_corrente = corrente;
if(corrente->getSucc() == corrente){ // caso di lista con un solo nodo
corrente = NULL;
} else {
corrente->getPrec()->setSucc(corrente->getSucc());
corrente->getSucc()->setPrec(corrente->getPrec());
corrente = corrente->getPrec();
}
delete old_corrente;
}
}