Newsgroup: it.comp.lang.c++
Titolo: Classe per numero complesso

Definisco una classe Complex per numero complesso, avente due membri 
privati: parte reale e parte immaginaria (forma algebrica o rettangolare).
Volendo specificare il numero complesso con la forma polare (modulo e 
argomento) devo aggiungere altri due membri privati: modulo e argomento.
Ma quando poi istanzio l'oggetto, come fa a capire il compilatore che è 
forma polare o algebrica?
Es.
Complex z1(10,10);
Grazie

 argomento) devo aggiungere altri due membri privati: modulo e argomento.
Non mi sembra un approccio molto sensato. Se non ci sono esigenze 
particolari la soluzione piu' semplice sarebbe quella di memorizzare i 
numeri in forma cartesiana, e di fare un costruttore per la forma polare.
E.


scegli una delle due rappresentazioni, e crea
un costruttore che converte.
Esempio il tuo Complex è nella forma a+ib,
crei un costruttore per chi vuole inserire
a e b, e un costruttore per chi vuole inserire
modulo e argomento, quest'ultimo convertirà
in a e b.

?manu* ha scritto:

Scusami, ma in alcuni casi la forma polare può essere più 
comoda/efficiente per effettuare calcoli. In quel caso è più efficiente 
(qualora efficienza sia necessaria...) "cacharsi" la rappresentazione 
polare piuttosto che ricalcolarla ogni volta.
ciao


Direi che questa è, appunto, un'esigenza particolare. Impossibile sapere 
cosa sia meglio, senza maggiori informazioni. La mia impressione è che 
l'OP non abbia bisogno di efficenza, forse non ha le idee molto chiare 
su come fare una normale implementazione.
E.

 polare piuttosto che ricalcolarla ogni volta.
IMHO la tecnica che proponi (fare caching) ha piu' probabilita' di
peggiorare le prestazioni che di migliorarle.
Il cambio di coordinate e' un operazione piuttosto costosa e con
questo approccio rischi di effettuarlo un numero di volte molto
elevato senza neanche accorgertene.
Io suggerisco di usare internamente la rappresentazione in parte reale
ed immaginaria che di norma e' piu' conveniente rispetto a quella
polare (generalmente si fanno piu' somme che moltiplicazioni). Il
construttore della classe puo' accettare entrambe le forme con un
argomento enum aggiuntivo che specifica il sistema di coordinate.
Se uno ha bisogno di ottimizzazioni pazzesche, la cosa piu' sensata,
sempre IMHO, e' abbandonare l'astrazione (usare due variabili per
complesso) per quella parte di codice che va ottimizzata pesantemente.
Saluti,
The Librarian

 Grazie

 in a e b.
Perdonami, ma sono agli inizi.
Come posso sovraccaricare il costruttore se entrambe le forme hanno la 
stessa corrispondeza numero di parametri/tipo?
Grazie


No scusa è vero, non puoi. Errore mio.

 Complex z1(10,10);
Semplice, non può. Per questo *non* dovresti avere un costruttore
Complex(double, double); il problema è stato discusso da qualcuno con
più autorità di me su:
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.8
Invece, definisci due funzioni statiche, rect e polar:
class Complex
{
public:
	 // Conversione implicita da reali, che fa comodo
	Complex(double re) : re(re), im(0.0) {}
	static Complex rect(double, double);
	static Complex polar(double, double);
	[...]
private:
	double re;
	double im;
};
L'implementazione della prima è banale:
Complex Complex::rect(double real, double imaginary)
{
	Complex z;
 
z.re
 = real;
 
z.im
 = imaginary;
	return z;
}
Anche la seconda è semplice, ma devi verificare che il modulo rho non
sia minore di zero, e devi includere l'intestazione cmath per le
funzioni trigonometriche:
#include <cassert>
#include <cmath>
Complex Complex::polar(double rho, double theta)
{
	// Se qualcuno ci dà un rho < 0 blocchiamo il programma
	assert( rho >= 0.0 )
	return rect( rho * cos( theta ), rho * sin( theta ) );
}
A questo punto, puoi sempre creare un complesso nell'uno o nell'altro
modo usando:
	Complex z1 = Complex::rect( -1, 1 );
	Complex z2 = Complex::polar( 10, 0 );
Puoi sviluppare ulteriormente il discorso usando due funzioni statiche
per le coordinate polari, polarRadian() e polarDegree() invece di
polar() e basta, visto che per come ho presentato polar() è possibile
che qualcuno poco attento alle convenzioni ti mandi un angolo in gradi,
cosa impossibile da correggere in Complex::polar() che vede solo un
double senza altre qualifiche, e che causerebbe un errore silenzioso.
Ciao,
- -Federico
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.5 (GNU/Linux)
Comment: Using GnuPG with SUSE - 
http://enigmail.mozdev.org
iD8DBQFIa2v2BIpu+y7DlLcRAo58AJ0RQUrSFLYMQqJG0OxSY3Ys5dawtQCeK5Bc
psdqLjDftabPbZO+vG2pwsQ=
=N0mb
-----END PGP SIGNATURE-----

 più autorità di me su:
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.8
Uhm... Capisco che le FAQ sono le FAQ...
Ma a questo punto ho una domanda per tutti:
Non ha piu' senso dotare il costruttore di un terzo argomento (enum)
che indica il sistema di coordinate?
Qualcosa del tipo:
Complex z1(Coord::polar, 1, PI);
Complex z2(Coord::rect, -1, 0);
Per come la vedo io in questo modo ci sono diversi vantaggi:
(1) Hai un costruttore pubblico utile (non uno che inizializza solo la
parte reale) che non fa mai male
(2) Non hai una proliferazione di funzioni pubbliche necessarie ad
inizializzare il tuo tipo.
Nel caso dei complessi non e' un gran problema, ma se si pensa ad i
punti di un grafico 2D potrebbe avere un senso avere altri sistemi di
coordinate... Che ne so: rect, polar, logxy, xlogy, logxlogy. In
questo sarebbero necessarie 5 funzioni statiche pubbliche, quando
basterebbe un solo costruttore.
(3) In qualche caso particolare ti salvi dal dover usare uno switch
(4) Lo stesso enum potrebbe essere utile in altre funzioni della
stessa classe (e.g. serializzazione) o di altre classi/funzioni
(5) Mi sembra piu' lineare, voglio costruire un oggetto e quindi
chiamo il costruttore
Commenti?
Saluti,
The Librarian

 Ciao,
Ciao
era proprio quello che cercavo. Non riuscivo come sovraccaricare il 
costruttore quando la corrispondenza parametri e tipo è la stessa.

 Ciao
grazie ovviamente.

 chiamo il costruttore
Be' suppongo che il criterio ultimo sia la leggibilità del codice,
dopodiché de gustibus. Non credo ci sia un abisso tra i due approcci,
l'importante è essere d'accordo che un costruttore Complex(double,
double) non ci debba essere.
Ciao,
- -Federico
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.5 (GNU/Linux)
Comment: Using GnuPG with SUSE - 
http://enigmail.mozdev.org
iD8DBQFIbJzvBIpu+y7DlLcRAr35AKCtsihu4MMTmKfHdPXXA2T4O4+t4wCePNGl
QEqC4VDb3b9UJ9JKNvyTR0c=
=qNw3
-----END PGP SIGNATURE-----

nembo kid ha scritto:

Potresti seguire questo approccio:
typedef double cpxType;
//---------------------------------
#define DECLARE_CPX_TYPE(a) \
class cpx##a \
{ \
   public: \
     cpx##a(cpxType v = 0) : V(v) {} \
     cpx##a(const cpx##a& other) : V(other.V) {} \
     operator cpxType() { return V; } \
   private: \
     cpxType V; \
};
//---------------------------------
DECLARE_CPX_TYPE(Real)
DECLARE_CPX_TYPE(Img)
DECLARE_CPX_TYPE(Mod)
DECLARE_CPX_TYPE(Arg)
class Complex
{
   public:
     Complex(cpxReal r, cpxImg i);
     Complex(cpxMod m, cpxArg a);
....
//---------------------------------
in questo modo dovrebbe essere sufficiente che uno degli argomenti sia 
del tipo giusto per risolvere l'ambiguità e ti costringe comunque a 
dichiarare a compile-time che tipo di argomenti usare.
Ciao
P.S. l'ho scritta di getto e senza provare, quindi passibile di errori e 
castronerie varie :-)

Ciao,
io ti consiglio innanzitutto di vedere come è fatta la libreria
standard complex (io uso quella) e ti onsiglio pure di usarla che mi
sembra abbastanza buona. In alternativa esistono tante altre librerie
già fatte, ad esempio la libreria gsl vcontiene pure il tipo complex.
Vedere come sono fatte queste potrebbe essere un buon punto di
partenza.
A presto


 Complex(double re, double im=0.0, CoordinateType type=PolarCoordinate);
Trovo preferibile dare un tipo specifico ai parametri dei costruttori
per distinguerli, ad esempio definendo la classe:
class radians
{
public:
	explicit radians(double v) : val(v) {}
	operator double() { return val; }
private:
	double val;
};
E' possibile differenziare i costruttori di complex:
class complex
{
public:
	complex(double r, double i);
	complex(double m, radians a);
// ...
};
Ed usarla ad esempio cosi':
int main()
{
	complex c = complex(4.0, radians(1.0));
}
Proseguendo su questa strada si arriva a definire una libreria di
grandezze fisiche con le relative operazioni e conversioni. Per le
necessita' dell'OP mi pare pero' un po' troppo.
Ciao,
Nicola

Nicola Musatti ha scritto:

elegante ... ma poi dentro come gestiresti la traccia del 
tipo di radiante ? Ti piace di più un approccio manuale con 
un type field, quello virtual, o il discorso della 
rappresentazione interna univoca a prescindere dal 
costruttore usato e quindi delle conversioni Da/A ad ogni 
uso diverso dal tipo internamente mantenuto ?
ciao
Soviet


 radiante ? 
refuso, ovviamente ho scritto radiante e intendevo 
complesso, uff
riciao


 formato ha la data istanza.
Direi che in C++ è una *pessima* strategia. Non è efficiente poichè fai
a runtime quello che forse potresti fare in determinati casi a compile
time. E' causa di errori estremente probabili.
-- 
-riko

 uso diverso dal tipo internamente mantenuto ?
Dipende da cosa serve. Se l'uso della rappresentazione polare e'
limitato a pochi casi eccezionali, la ricalcolerei ogni volta. Se a
volte si usa una, a volte l'altra probabilmente farei due classi, se
si dovesse continuare a saltare dall'una all'altra probabilmente
manterrei tutte e due contemporaneamente.
In tutti i casi, specialmente l'ultimo, conviene valutare se non
rendere le istanze della classe complex immutabili, facendo in modo
che qualsiasi operazione risulti nella costruzione di una nuova
istanza.
Ciao,
Nicola