PDA

View Full Version : [C++]confrontare 2 stringhe


Bandit
01-10-2012, 15:59
Ciao a tutti ragazzi mi date una mano per favore a capire questo programma?

Ci sono alcune cose che non ho capito e spero nel vostro aiuto e spiegazione per allontanarmi da ciò che c'è scritto sul libro che non riesco a mettere in pratica.
Questo è un programma che deve confrontare 2 stringhe

Considerando quel main potete dirmi perchè ci sono 3 costruttori?
quello a 0 argomenti, quello con argomenti e quello di copia




#ifndef _STRINGA_H_
#define _STRINGA_H_

#include <iostream>
#include <stdlib.h>

using namespace std;

class Stringa{

private:
char* str;
int l;//lunghezza stringa
public:
Stringa():str(0){};
Stringa(const char*);
Stringa(const Stringa &);
~Stringa() {delete [] str;}

int Lunghezza() const {return l;}
void Stampa() const {cout<<str;}

const Stringa& operator=(const Stringa&);
bool operator==(const Stringa&);

friend ostream& operator<<(ostream& out,const Stringa &s) { s.Stampa(); return out;}


};


potreste spiegarmi cosa fanno le implementazioni dei 2 costruttori?
commento a lato per evidenziare cosa ho capito



#include "string.h"



Stringa::Stringa(const char* sr){
const char* app = sr; //inizializzo puntatore a carattere con sr
l=0; //lunghezza stringa nulla
while(*app++) l++; //mentre il puntatore avanza, aumenta la lunghezza


app = sr; //questa non l'ho capita
str = new char[l+1];// per il temrinatore ???ma che vuol dire?
char* app2 = str;
while (*app2++=*app++);
}



Stringa::Stringa(const Stringa & s){
const char* app = s.str;
l=s.l;
str = new char [l+1];
char* app2 = str;
while (*app2++ = *app++);
}



const Stringa& Stringa::operator=(const Stringa& s){
if(this == &s); //self-assignment(a=a);
else{
delete [] str;
const char* app = s.str;
l=s.l;
str = new char [l+1];
char* app2 = str;
while (*app2++ = *app++);
}
return *this;
}



bool Stringa::operator==(const Stringa& s){
if(l != s.l) return false;
else{
char* app = str;
char* app2 = s.str;
for(;*app == *app2 && *app != '\0';*app++,*app2++);
if( *app == '\0' && *app2=='\0') return true;
else return false;
}
}




perchè c'è un overload dell'operatore di assegnamento e di confronto?
mi potreste spiegare la loro implementazione cosa fa di preciso?






#include "string.h"




int main(){

Stringa s1("ciao");
Stringa s2("gatto ");
cout<<s1<<endl;
cout<<s2<<endl;

if( s1 == s2) cout<<"\nuguali"; else cout<<"\ndiversi";


system("pause");
return 0;

}

[Kendall]
01-10-2012, 16:31
Allora, andiamo per passi:



Considerando quel main potete dirmi perchè ci sono 3 costruttori?
quello a 0 argomenti, quello con argomenti e quello di copia



Non capisco la domanda. Il costruttore per copia (il terzo) quando si hanno campi puntatore (come in questo caso) bisogna sempre scriverlo, o almeno è buona pratica farlo. Questo perchè è il costruttore utilizzato quando ad un metodo/funzione si passa come argomento un istanza di quella classe.

Quello senza argomenti ovviamente inizializza la stringa ad una stringa nulla (in quanto azzera il puntatore a char "str".

Il secondo infine inizializza la stringa fornendogli una stringa in stile c.

Riguardo al codice:


app = sr;

A quel punto del codice il puntatore app si trova a puntare il carattere nullo della stringa sr, in quanto è stato utilizzato per l'appunto per contare i caratteri non nulli contenuti in sr. Con quella riga di codice il programmatore semplicemente torna a far puntare app al primo carattere della stringa sr.


str = new char[l+1];


Qui alloca lo spazio necessario, comprensivo del carattere nullo finale.



char* app2 = str;
while (*app2++=*app++);

E qui va a copiare tutti i caratteri della stringa str (usando app2, un puntatore al suo primo carattere) comprensivo dell'ultimo carattere nullo (infatti il while in oggetto termina non appena ha copiato il carattere nullo).



perchè c'è un overload dell'operatore di assegnamento e di confronto?
mi potreste spiegare la loro implementazione cosa fa di preciso?

Perchè è pratico utilizzare la sintassi dell' "=" e del "==" quando si manipolano oggetti di una classe definita dall'utente. Più per famigliarità con la scrittura che per un reale vantaggio (la stessa cosa si potrebbe fare definendo semplicemente un metodo d'istanza che fa la stessa cosa degli operatori).

Bandit
01-10-2012, 17:43
Veramente grazie :) per ciò che hai scritto, non sai quanto mi stai aiutando grazie mille :)

;38217293']Allora, andiamo per passi:

Non capisco la domanda. Il costruttore per copia (il terzo) quando si hanno campi puntatore (come in questo caso) bisogna sempre scriverlo, o almeno è buona pratica farlo. Questo perchè è il costruttore utilizzato quando ad un metodo/funzione si passa come argomento un istanza di quella classe.


Quando si hanno campi puntatori? cioè dove lo noti, nell'implementazione dei costruttori? o da quale altra parte? Perchè vedendo il main , forse mi sarei fermata al costruttore con 1 argomento.



Perchè è pratico utilizzare la sintassi dell' "=" e del "==" quando si manipolano oggetti di una classe definita dall'utente. Più per famigliarità con la scrittura che per un reale vantaggio (la stessa cosa si potrebbe fare definendo semplicemente un metodo d'istanza che fa la stessa cosa degli operatori).
ok però dal main vedo che si usa solo l'overloading del confronto non l'altro di assegnamento.




Cerco di spiegarmi prima l'implementazione dell'operatore di assegnamento.
Questo viene implementata come metodo di una classe e quindi ha al primo operando un oggetto della classe stessa, oppure la definivo come funzione esterna (eventualmente dichiarandola friend della classe se accede ai membri privati della classe).

const Stringa& Stringa::operator=(const Stringa& s){
if(this == &s); //self-assignment(a=a); //se il puntatore all'oggetto stringa ha lo stesso indirizzo della stringa s. Non fa nulla
else{
delete [] str; //oppure cancella l'estensione dinamica di str
const char* app = s.str; //assegna ad app la stringa str (come l'operazione che mi hai spiegato prima)

l=s.l; //questa non l'ho capita
str = new char [l+1];

//queste operazioni sono quelle che mi hai detto prima
char* app2 = str;
while (*app2++ = *app++);
}
return *this; //ritorna l'oggetto stesso, poichè il risultato dell'operazione è l'oggetto stesso
}

[Kendall]
01-10-2012, 17:59
Quando si hanno campi puntatori? cioè dove lo noti, nell'implementazione dei costruttori? o da quale altra parte? Perchè vedendo il main , forse mi sarei fermata al costruttore con 1 argomento.

Nel caso specifico della classe che stai analizzando, viene utilizzato un puntatore a char (char* str) per contenere i caratteri della stringa.
Per questo serve un costruttore per copia, perchè se non specificato dall'utente viene generato un costruttore per copia di default. Questo non fa altro che eseguire una copia membro per membro.
Puoi immaginare come questa operazione sia errata nel caso di un campo puntatore, in quanto si andrebbe a copiare solo il puntatore stesso, cioè l'indirizzo del primo carattere della stringa memorizzata. Questo porterebbe le due istanze a puntare alla stessa stringa in memoria, e quindi le operazioni sulla prima avrebbero ripercussioni anche sulla seconda.
Per questo si va a specificare un costruttore per copia, così da cambiare questo comportamento e far si che venga generata una stringa completamente nuova (per l'appunto una copia).



ok però dal main vedo che si usa solo l'overloading del confronto non l'altro di assegnamento.

Nel main evidentemetne non viene utilizzata la funzionalità di assegnamento ma viene mostrata solo quella di confronto.



l=s.l; //questa non l'ho capita
str = new char [l+1];


Semplicemente viene aggiornato il parametro l, ponendolo uguale al parametro l della istanza s.

Bandit
01-10-2012, 18:17
;38217823']Nel caso specifico della classe che stai analizzando, viene utilizzato un puntatore a char (char* str) per contenere i caratteri della stringa.
Per questo serve un costruttore per copia, perchè se non specificato dall'utente viene generato un costruttore per copia di default. Questo non fa altro che eseguire una copia membro per membro.
Puoi immaginare come questa operazione sia errata nel caso di un campo puntatore, in quanto si andrebbe a copiare solo il puntatore stesso, cioè l'indirizzo del primo carattere della stringa memorizzata. Questo porterebbe le due istanze a puntare alla stessa stringa in memoria, e quindi le operazioni sulla prima avrebbero ripercussioni anche sulla seconda.
Per questo si va a specificare un costruttore per copia, così da cambiare questo comportamento e far si che venga generata una stringa completamente nuova (per l'appunto una copia).


C'è spiegato la stessa cosa sul libro, ma ti dico che sei stato molto più chiaro tu :)
questo è stato un ottimo caso per specificare il problema che hai posto.





Semplicemente viene aggiornato il parametro l, ponendolo uguale al parametro l della istanza s.
scusa , ma a che serve non capisco. Poichè sono due stringhe diverse la lunghezza cioè l, è diversa e perciò si assegna l'una all'altra?





Anche per questo vale ciò che ho scritto prima
"Questo viene implementata come metodo di una classe e quindi ha al primo operando un oggetto della classe stessa, oppure la definivo come funzione esterna (eventualmente dichiarandola friend della classe se accede ai membri privati della classe)."

ma perchè viene definita in questo modo in bool e non con Stringa &



bool Stringa::operator==(const Stringa& s){
if(l != s.l) return false; //se le due lunghezze son diverse return false
else{
char* app = str; //copia le due stringhe in app e app2
char* app2 = s.str;
for(;*app == *app2 && *app != '\0';*app++,*app2++);
//il primo ; non l'ho capito, per le due strighe che sono uguali e per il puntatore app che non ha ancora raggiunto l'ultimo carattere, incrementa i due puntatori


if( *app == '\0' && *app2=='\0') return true;
//se i due puntatori puntano al carattere terminazione ,quindi stringhe vuote return true
else return false;
}

sottovento
01-10-2012, 20:30
Attenzione - Si tratta di un programma di esempio, a puro scopo didattico.
Il codice, infatti, non e' sicuro e potrebbe portare a problemi (i.e. crash) se usato in un software "vero".

[Kendall]
01-10-2012, 21:27
scusa , ma a che serve non capisco. Poichè sono due stringhe diverse la lunghezza cioè l, è diversa e perciò si assegna l'una all'altra?

Sono diverse a livello di istanze (nel senso che ognuna di esse occupa una distinta parte della memoria ed è pertanto indipendente dall'altra), ma il contenuto è il medesimo. Stiamo parlando dell'operatore di assegnamento d'altro canto, lui DEVE copiare il contenuto ergo la nuova stringa sarà formalmente la stessa (e quindi della stessa lunghezza).



ma perchè viene definita in questo modo in bool e non con Stringa &



bool Stringa::operator==(const Stringa& s){
if(l != s.l) return false; //se le due lunghezze son diverse return false
else{
char* app = str; //copia le due stringhe in app e app2
char* app2 = s.str;
for(;*app == *app2 && *app != '\0';*app++,*app2++);
//il primo ; non l'ho capito, per le due strighe che sono uguali e per il puntatore app che non ha ancora raggiunto l'ultimo carattere, incrementa i due puntatori


if( *app == '\0' && *app2=='\0') return true;
//se i due puntatori puntano al carattere terminazione ,quindi stringhe vuote return true
else return false;
}




Attento/a che quello è un operatore di confronto, non di assegnazione. Deve restituire un risultato booleano altrimenti perderebbe il suo senso.
Un riferimento all'istanza stessa lo restituisci nel caso di operatori come quello di uguaglianza, di somma, moltiplicazione, ecc... Questo tra l'altro non è NECESSARIO (potresti restituire anche un void), ma viene utilizzato come tecnica per permettere la concatenazione dell'operazione (es° a * b * c, oppure a = b = c)

Bandit
02-10-2012, 14:29
Grazie Kendall :)

molto chiaro grazie

ti posso chiedere una spiegazione sul ciclo for dell'overload dell'operatore di confronto?

Poi metto tutto insieme e spero che tutto fili :D

grzie ancora

[Kendall]
02-10-2012, 15:07
Grazie Kendall :)

molto chiaro grazie

ti posso chiedere una spiegazione sul ciclo for dell'overload dell'operatore di confronto?

Poi metto tutto insieme e spero che tutto fili :D

grzie ancora

for(;*app == *app2 && *app != '\0';*app++,*app2++);

Allora, in quel ciclo for viene fatto questo:
app e app2 sono puntatori a char che all'inizio del ciclo for puntano al carattere iniziale delle rispettive stringhe.
il primo ";" preceduto dal nulla significa banalmente che non viene inizializzato alcun indice o altra variabile (tipo il classico "int i = 0", quindi la condizione di uscita non necessita di variabili aggiuntive).
La condizione " *app == *app2 && *app != '\0' "controlla che il carattere puntato attualmente da app e app2 corrisponda, nonchè che sia diverso dal carattere nullo. Infine vengono fatti avanzare entrambi i puntatori per passare al carattere successivo. In questa maniera il ciclo termine o se viene trovata una differenza nei caratteri, o se viene raggiunta la fine della stringa cioè il carattere nullo (nel primo caso le stringhe son diverse, nel secondo sono uguali).

Bandit
03-10-2012, 13:46
ciao kendall

ora mi è venuto un dubbio :D


in un altro post , ed in un'altra classe avevo ad esempio

char * marca

che quindi nel costruttore avevo
Moto::Moto(const char *mar)
{
Marca=new char[strlen(mar)+1]
strcpy(Marca,mar)
}






ritornando al problema poichè nella classe abbiamo
char * str
ho anche qui allocazione dinamica di una stringa
perchè poi ho anche str=new char[l+1]
però si va ad eseguire l'operazione di copia cioè strcpy


perchè?

perchè in questo problema si devono solo confrontare le stringhe che lo si fa tramite i puntatori.
PErò poi perchè si sceglie una strada o un altra?

[Kendall]
03-10-2012, 17:23
ciao kendall

ora mi è venuto un dubbio :D


in un altro post , ed in un'altra classe avevo ad esempio

char * marca

che quindi nel costruttore avevo
Moto::Moto(const char *mar)
{
Marca=new char[strlen(mar)+1]
strcpy(Marca,mar)
}






ritornando al problema poichè nella classe abbiamo
char * str
ho anche qui allocazione dinamica di una stringa
perchè poi ho anche str=new char[l+1]
però si va ad eseguire l'operazione di copia cioè strcpy


perchè?

perchè in questo problema si devono solo confrontare le stringhe che lo si fa tramite i puntatori.
PErò poi perchè si sceglie una strada o un altra?

A dire il vero la classe di questo thread mi sembra più un esercizio didattico che altro. Come hai notato pure tu esistono diverse potenti funzioni che lavorano sulle stringhe stile c (tutte funzioni ereditate dal c), nonchè membri della classe string (del c++) che spesso e volentieri bastano per quello che può essere un uso comune delle stringhe.

Se ti guardi anche solo la lista di funzioni per la manipolazione di stringhe contenuto nell'header cstring (http://en.cppreference.com/w/c/string/byte) (o string.h se lavori sotto c) vedrai come contenga tutto e molto di più del contenuto della classe che hai analizzato in questo thread.

si parla ovviamente di manipolazioni a basso livello, per un utilizzo più generico delle stringhe ricorda di utilizzare sempre la classe String del c++.

Bandit
03-10-2012, 17:42
te l'ho chiesto poichè in quell'occasione il costruttore di copia non è stato usato. E questo mi fa pensare :eek: :eek:


Mi posso per esempio regolare così?se scelgo di implementare con i puntatori allora vado a considerare il costruttore di copia se no , uso quell'altra forma.
Avevo sotto mano un altro esercizio dove appunto c'è il costruttore di copia, però poi l'implementazione non è svolta con i puntatori.

Perciò capisco ciò che mi hai spiegato così chiaramente rispetto al libro


Nel caso specifico della classe che stai analizzando, viene utilizzato un puntatore a char (char* str) per contenere i caratteri della stringa.
Per questo serve un costruttore per copia, perchè se non specificato dall'utente viene generato un costruttore per copia di default. Questo non fa altro che eseguire una copia membro per membro.
Puoi immaginare come questa operazione sia errata nel caso di un campo puntatore, in quanto si andrebbe a copiare solo il puntatore stesso, cioè l'indirizzo del primo carattere della stringa memorizzata. Questo porterebbe le due istanze a puntare alla stessa stringa in memoria, e quindi le operazioni sulla prima avrebbero ripercussioni anche sulla seconda.
Per questo si va a specificare un costruttore per copia, così da cambiare questo comportamento e far si che venga generata una stringa completamente nuova (per l'appunto una copia).


però poi mi chiedo sempre, quando lo devo usare questo costruttore?
Il discriminante dei puntatori vengono meno perchè in quest'esercizio che sto visionando adesso non ce ne son. A meno che non li si utilizzi quando ho estensione dinamica.

[Kendall]
03-10-2012, 18:08
Il costruttore per copia, così come l'operatore di assegnamento, lo devi utilizzare quando questo si discosta rispetto al comportamento di default (cioè nel caso nel quale tu non specifichi nulla).

Per default, sia il costruttore per copia che l'operatore di assegnazione fanno una copia membro a membro (con il comportamento che ti ho spiegato prima). Se questo non è ciò che desideri allora devi specificarli da te.