Kann eine Klasse einen Rückgabewert haben?



  • Die Adresse wird bestimmt nicht zurückgegeben, das endet bestenfalls mit einem Kompilierfehler. Dein Negierungsoperator finde ich auch nicht schön. Negiere doch einfach nur das Attribut und gibt *this zurück, man erwartet beim Negieren ja das selbe Objekt und kein Int. Außerdem ist es eine Referenz, man kann also den Member von außerhalb verändern, nicht schön, deshalb immer const-Referenzen von Membern zurückgeben. 😉

    Du willst für deine Klasse den operator <<() überladen. Als Parameter 1 brauchst du ein ostream -Objekt, etwa cout. Und als zweiten Parameter die Klasse. Der Operator muss global sein und gleichzeitig Attributzugriff haben. Deshalb verwendet man da friend

    class Test {
    private:
        int x;
    
    public:
        Test& operator- (void) {
            x = -x;
            return *this;
        }
    
        friend ostream& operator <<(ostream &os, const Test &t);  
    };
    
    ostream& operator <<(ostream &os, const Test &t) {
       os << t.x;
       return os;
    }
    

    Referenzrückgabe, damit man mehrere Ts hinternander ausgeben kann.



  • Ad aCTa schrieb:

    Die Adresse wird bestimmt nicht zurückgegeben, das endet bestenfalls mit einem Kompilierfehler. Dein Negierungsoperator finde ich auch nicht schön. Negiere doch einfach nur das Attribut und gibt *this zurück, man erwartet beim Negieren ja das selbe Objekt und kein Int. Außerdem ist es eine Referenz, man kann also den Member von außerhalb verändern, nicht schön, deshalb immer const-Referenzen von Membern zurückgeben. 😉

    Du willst für deine Klasse den operator <<() überladen. Als Parameter 1 brauchst du ein ostream -Objekt, etwa cout. Und als zweiten Parameter die Klasse. Der Operator muss global sein und gleichzeitig Attributzugriff haben. Deshalb verwendet man da friend

    ähm, okay 😉

    Du hast mich so falsch verstanden, wie es nur irgendwie möglich gewesen wäre.

    1. Ich habe oben stehende Klasse nur mal so schnell dahin geschrieben. Sie hat nichts damit zu tun, warum ich das wissen will. Ich wollte nur einfach mein Problem verdeutlichen.
    2. Ich möchte die Klasse als eine Art Typ verwenden. Daher erwarte ich beim negieren schon einen "int" (in diesem Beispiel) und nicht die Klasse selbst.
    3. Man soll das Objekt von außen verändern sollen. Die Implementierung hier ist aber Zufall.

    Punkt 2 ist der wichtigste. Ich will eine Art neuen Typ deklarieren. Daher würde ich gerne eine Instanz der Klasse auch direkt als Typ verwenden können.
    Die Überladung des Typs habe ich auch schon in Betracht gezogen und scheint die beste Lösung zu sein.



  • FrEEzE2046 schrieb:

    2. Ich möchte die Klasse als eine Art Typ verwenden. Daher erwarte ich beim negieren schon einen "int" (in diesem Beispiel) und nicht die Klasse selbst.

    Eine Klasse ist immer ein Typ, egal mit welcher Semantik du sie vollpackst. Die Folgerung ("Daher erwarte...") verstehe ich trotzdem nicht. Wieso willst du einerseits einen autonomen Typen, andererseits eine Konvertierung zu int , einem ganz anderen Typen? 😕

    Für einen eigenständigen Typen ist es wohl das beste, du überlädst die Operatoren sinnvoll. Das heisst, sie geben wieder Objekte der Klasse zurück und nicht int oder sowas.



  • Nexus schrieb:

    Eine Klasse ist immer ein Typ, egal mit welcher Semantik du sie vollpackst. Die Folgerung ("Daher erwarte...") verstehe ich trotzdem nicht. Wieso willst du einerseits einen autonomen Typen, andererseits eine Konvertierung zu int , einem ganz anderen Typen? 😕

    Für einen eigenständigen Typen ist es wohl das beste, du überlädst die Operatoren sinnvoll. Das heisst, sie geben wieder Objekte der Klasse zurück und nicht int oder sowas.

    Du hast grundsätzlich natürlich recht. Ich habe evtl. Schwierigkeiten mich auszudrücken. Ich habe mal etwas zusammengetragen:

    Angenommen wir möchten eine Klasse bilden die einen BCD-Typen bereitstellt. Dann möchte ich diese genau so verwenden können (wie ich es jetzt auch tun werde):

    #include <iostream>
    #include <math.h>
    using namespace std;
    
    class BCD 
    {
    private:
    	char*	_num;
    
    public:
    	BCD( void ) {
    		this->_num = new char[6]();
    		memset( this->_num, 48, 6 );
    	}
    
    	BCD( int num ) {
    		this->_num = new char[6]();
    		memset( this->_num, 48, 6 );
    		this->reset(num);
    	}
    
    	void reset( int num ) {
    		char* tmp = new char[5]();
    
    		_itoa_s( num, tmp, 5, 10 );
    
    		if( num > 0 ) {
    			this->_num[0] = 48;
    			memcpy( &this->_num[6-strlen(tmp)], tmp, 5-strlen(tmp) );
    		}
    		else {
    			this->_num[0] = 49;
    			memcpy( &this->_num[7-strlen(tmp)], &tmp[1], 6-strlen(tmp) );
    		}
    
    		delete[] tmp;
    	}
    
    	operator int (void) {
    		int  tmp = 0;
    		char c;
    
    		for( int i = 5; i > 0; i-- ) {
    			c = this->_num[i]; 
    			tmp += atoi(&c) * pow((double)10, (5-i)); 
    		}
    
    		if( this->_num[0] == 48 )
    			return tmp;
    		else
    			return -tmp;
    	}
    
    	int operator[] (unsigned char idx) {
    		if( idx <= 5 ) {
    			char c = this->_num[5 - idx];
    			return atoi(&c);
    		}
    	}
    };
    
    int main()
    {
    	BCD bcd = -42;
    
    	cout << bcd << endl;
    	cout << bcd[0] << endl;
    	cout << bcd[1] << endl;
    
    	return 0;
    }
    


  • Auweia.

    1. Warum new? Du kannst das Array doch direkt speichern. Viel besser so.

    2. Ich sehe den Sinn der Klasse nicht. Sie speichert ja eigentlich nur einen String. BCD heißt "binary coded decimal". Damit verbinde ich, dass in einem Oktett zwei Ziffern kodiert werden, also eine Ziffer je 4 Bit. Das ist zumindest üblich, wenn man von BCD spricht. Es sei denn, ich irre mich hier total. Wie auch immer. Du legst eine Klasse an, nur um eine Ganzzahl anders zu repräsentieren. Wozu? Rechne doch mit int und wandel dann in std::string um, wenn Du es brauchst.

    3. Du hast die 3er-Regel nicht beachtet --> Speicherleck, Doppel-Löschung, etc. Hab ich schon gesagt, dass viel zu viel new bei Dir im Quellcode steht?

    Was die implizite Konvertierung angeht. Das könnte man so machen:

    class BCD {
      static const int len = 6;
      char ziffern[len];
    public:
      // impliziter Konstruktor (für implizite int->BCD Konvertierung)
      BCD(int);             
    
      // implizite BCD->int Konvertierung
      operator int() const;
    
      BCD& operator+=(BCD const&);
      BCD& operator-=(BCD const&);
      BCD& operator*=(BCD const&);
      BCD& operator/=(BCD const&);
      BCD& operator++();
      BCD& operator--();
    
      BCD operator+() const;
      BCD operator-() const;
      BCD operator++(int);
      BCD operator--(int);
    };
    
    inline BCD operator+(BCD const& a, BCD const& b)
    { BCD ret = a; ret += b; return ret; }
    
    ...
    

    Viel Spaß beim Überladen der Operatoren. Es gibt dann ja auch noch ==, !=, <, <=, >, >=. Vielleicht findest Du auch eine sinvolle Bedeutung für &, |, ^, ~. 🙂

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Auweia.

    1. Warum new? Du kannst das Array doch direkt speichern. Viel besser so.

    2. Ich sehe den Sinn der Klasse nicht. Sie speichert ja eigentlich nur einen String. BCD heißt "binary coded decimal". Damit verbinde ich, dass in einem Oktett zwei Ziffern kodiert werden, also eine Ziffer je 4 Bit. Das ist zumindest üblich, wenn man von BCD spricht. Es sei denn, ich irre mich hier total. Wie auch immer. Du legst eine Klasse an, nur um eine Ganzzahl anders zu repräsentieren. Wozu? Rechne doch mit int und wandel dann in std::string um, wenn Du es brauchst.

    3. Du hast die 3er-Regel nicht beachtet --> Speicherleck, Doppel-Löschung, etc. Hab ich schon gesagt, dass viel zu viel new bei Dir im Quellcode steht

    Guten Morgen,

    mmmh,ich habe doch extra geschrieben, dass ich nur mal so etwas "zusammengetragen" habe. Ich habe einfach mal zwei Minuten Zeit genommen und habe etwas geschrieben, damit man sieht wie ich dass jetzt mit der operator-überladung lösen möchte.

    Ansonsten hast du natürlich recht. BCD bedeutet "binary coded decimal" und genau dass soll die Klasse ja auch machen. Aber dafür brauche ich dann schon etwas mehr als zwei Minuten für die volle Implementierung.

    Dass es hier Blödsinn ist mit new speicher zu holen (den ich dann auch nicht wieder freigebe) ist mir klar. Aber wie gesagt: Ich wollte eigentlich nur kurz zeigen wie ich dass mit dem Operator mache.

    Dass ich ziemlich viele Operatoren überladen muss ist mir klar. Wie würdest du es denn implementieren?

    Es ist ja eigentlich recht einfach. Ich nehme einen unsigned char für zwei Zahlen ...

    Ich hatte es mir so gedacht:

    Angenommen wir wollen die Zahl 42 BCD speichern.

    unsigned char c;
    
    __asm {
    [asm]    lea	edx, c
        xor ax, ax
    
        mov ah, 4
        shr ax, 4
    
        add al, 2
    
        mov byte ptr [edx], al[/asm]
    }
    

    oder so:

    unsigned char c1;
    c1 = 4 << 4;
    c1 += 2;
    
    cout << static_cast<int>(c1) << endl;
    

    oder gar so:

    struct bcd {
    	unsigned char z1:4;
    	unsigned char z2:4;
    } c2 = { 2, 4 };
    
    void* p = &c2;
    
    cout << *(reinterpret_cast<unsigned char*>(p)) << endl;
    


  • FrEEzE2046 schrieb:

    Wie würdest du es denn implementieren?

    Gar nicht. Ich sehe keinen Sinn darin, eine BCD-Klasse zu erstellen. Wozu soll das gut sein? Wie der Wert intern repräsentiert wird, interessiert doch keine Sau. Das, was Du damit machen kannst, schon. Das einzige, was Du angedeutet hast, und was BCD mehr können muss als ein int , ist das Extrahieren von Ziffern. Dafür kann man aber recht leicht eine Funktion schreiben.

    int ziffer(int zahl, int index);
    

    FrEEzE2046 schrieb:

    Angenommen wir wollen die Zahl 42 BCD speichern.

    Warum?

    FrEEzE2046 schrieb:

    unsigned char c;
    
    __asm {
    [asm]    lea	edx, c
        xor ax, ax
    
        mov ah, 4
        shr ax, 4
    
        add al, 2
    
        mov byte ptr [edx], al[/asm]
    }
    

    🙄 Sieht falsch, kompliziert und nicht portabel aus.

    unsigned char c = 0x42;
    

    FrEEzE2046 schrieb:

    oder gar so:

    struct bcd {
    	unsigned char z1:4;
    	unsigned char z2:4;
    } c2 = { 2, 4 };
    
    void* p = &c2;
    
    cout << *(reinterpret_cast<unsigned char*>(p)) << endl;
    

    Pfui! Böse! Es sei denn, Du willst, dass es nicht portabel ist.

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Sieht falsch, kompliziert und nicht portabel aus.

    Nur bedingt portabel: Ja.
    Falsch: Nein.

    Ich sehe ja ein, dass die Assembler-Variante nicht portabel ist; ist ja auch logisch. Aber was genau magst du an der letzten Variante nicht? Ich denke es ist die einfachste und eleganteste Lösung (man bedenke, dass es sich um eine Veranschaulichung und keine vollständige Implementierung handelt).

    Der Grund warum ich eine bcd-Klasse möchte ist der, dass ich bcd-Werte in einer Datei speichern möchte. Ich dachte mir, dass es komfortabler und verständlicher ist, wenn ich dafür einen Typ deklariere, der die Daten gleich auch so hält.

    Ich glaube du hast mal wieder nicht verstanden, wofür ich dass hier machen will - könnte daran liegen, dass ich es vorher nicht gesagt habe 😉

    Wir brauchen nicht darüber reden, dass es totaler Schwachsinn wäre, einen Klasse zu bauen, die im Endeffekt einen Integer repräsentatiert, aber die Daten intern anders speichert. Denn das interessiert wirklich keine Sau ...



  • FrEEzE2046 schrieb:

    Der Grund warum ich eine bcd-Klasse möchte ist der, dass ich bcd-Werte in einer Datei speichern möchte. Ich dachte mir, dass es komfortabler und verständlicher ist, wenn ich dafür einen Typ deklariere, der die Daten gleich auch so hält.

    Wenn es echt nur ums Speichern geht, dann operator<< und glücklich sein.



  • Ich denke er weiß, dass er mit dem << Operator relativ leicht speichern kann, aber er will halt die BCD Funktionalität an sich und die Laden/Speichern-Funktionalität sauber in einer Klasse zusammen haben. Irgendwo nachvollziehbar.



  • Im Prinzip geht es nur ums speichern, weil es wie gesagt keinen Sinn macht nach Außen einen Integer zu repräsentieren und nur intern die Werte anders zu halten.

    Aber ich wollte schon, dass man mit dem Typ auch rechnen kann. Ist eigentlich auch keine zu große Schwierigkeit denke ich.

    It0101 schrieb:

    Ich denke er weiß, dass er mit dem << Operator relativ leicht speichern kann, aber er will halt die BCD Funktionalität an sich und die Laden/Speichern-Funktionalität sauber in einer Klasse zusammen haben. Irgendwo nachvollziehbar.

    👍 Wenigstens einer versteht mich 😉

    Ich möchte nicht den Umstand haben immer wieder die Dateien zu speicher / laden und dann das Gelesene nicht richtig verwenden zu können.

    Zudem brauche ich auch noch eine Implementierung für Excess-3, weshalb sich eine saubere abstrakte Klasse anbietet. Ist zwar mehr Arbeit, hat man aber mehr von.



  • Ich bin auch so ein Ordnungsfreak wie du 😃

    Ich weiß zwar nicht genau was letztendlich alles in die Klasse rein soll, aber rein für BCD-Funktionalität würdest du die Klasse sicher nicht entwickeln und ich auch nicht.

    Ich würde das mit den Operatoren aber vermutlich trotzdem nicht machen... ich würde das einfach über Memberfunktionen abhandeln. Ist zwar dann von der Verwendung nicht so schön wie Operatoren, entspricht aber mehr meiner Sicht einer Klasse.

    Aber am Ende musst du selbst entscheiden, mit welcher Umsetzung du dich am wohlsten fühlst, egal was andere dazu sagen.

    Im allgemeinen empfiehlt es sich aber, aber die Experten hier zu hören. Auch wenn man es nur ungern wahr haben will, haben die Jungs meistens recht ( @Nexus: außer bei returns in der int main(...) 😃 ).



  • FrEEzE2046 schrieb:

    Sebastian Pizer schrieb:

    Sieht falsch, kompliziert und nicht portabel aus.

    Nur bedingt portabel: Ja.
    Falsch: Nein.

    __asm ist Compiler-spezifisch. Auch wenn Du dem GCC das auf einem x86 zu futtern gibst: Er will es nicht.

    xor ax, ax  // AX = 0x0000
    mov ah, 4   // AX = 0x0400
    shr ax, 4   // AX = 0x0040
    add al, 2   // AX = 0x0042
    

    Ok, hast recht. 🙂

    FrEEzE2046 schrieb:

    Ich sehe ja ein, dass die Assembler-Variante nicht portabel ist;

    Sie ist nicht nur nicht portabel sondern auch relativ unangebracht. Dort stand keine Operation, die Du nicht auch in C oder C++ hättest schreiben können. Es kommt mir fast so vor, als hättest Du das nur geschrieben, um zu demonstrieren, dass Du "Assembler kannst".

    FrEEzE2046 schrieb:

    Aber was genau magst du an der letzten Variante nicht?

    Jedem Compiler ist es selbst überlassen, wie er Bitfelder anordnet. Du verlässt Dich also auf ein Verhalten Deines Compilers, welches nicht vom C++ Standard garantiert wird.

    FrEEzE2046 schrieb:

    Ich denke es ist die einfachste und eleganteste Lösung.

    Das sehe ich nicht so. Du brauchst keine BCD-Klasse.

    FrEEzE2046 schrieb:

    Der Grund warum ich eine bcd-Klasse möchte ist der, dass ich bcd-Werte in einer Datei speichern möchte.

    Schön, dass Du das auch mal erwähnst! Aber ich sehe immer noch nicht, warum Du dafür eine Klasse anlegen musst.

    FrEEzE2046 schrieb:

    Ich dachte mir, dass es komfortabler und verständlicher ist, wenn ich dafür einen Typ deklariere, der die Daten gleich auch so hält.

    Nicht wirklich. Wie so ein Objekt im Speicher repräsentiert wird, ist relativ uninteressant für den Benutzer. Die Schnittstelle ist viel interessanter. Du kannst in Deinem BCD-Objekt auch die Zahl einfach als int speichern. Warum nicht? Es hat keinen zu interessieren, wie das Objekt intern aufgebaut wird. (Geheimnisprinzip)

    Du brauchst nur Funktionen zum Kodieren und Dekodieren. ZB so was:

    /// speichert eine vorzeichenlose Zahl im BCD-Format
    /// ("little endian")
    void bcd_encode(unsigned zahl, unsigned char* ziel, int oct_count)
    {
      for (int k=0; k<oct_count; ++k) {
        const unsigned rest = zahl % 100;
        ziel[k] = ((rest / 10) << 4) | (rest % 10);
        zahl /= 100;
      }
    }
    

    FrEEzE2046 schrieb:

    Ich glaube du hast mal wieder nicht verstanden,

    mal wieder? Hmm...

    FrEEzE2046 schrieb:

    wofür ich dass hier machen will - könnte daran liegen, dass ich es vorher nicht gesagt habe 😉

    Richtig erkannt. Und auch jetzt, wo Du es gesagt hast, frage ich mich, wieso Du eine Klasse namens BCD implementieren willst.

    FrEEzE2046 schrieb:

    Wir brauchen nicht darüber reden, dass es totaler Schwachsinn wäre, einen Klasse zu bauen, die im Endeffekt einen Integer repräsentatiert, aber die Daten intern anders speichert. Denn das interessiert wirklich keine Sau ...

    Mit anderen Worten, Du willst Dich nicht davon abbringen lassen, ein Problem umständlich zu lösen. Nur zu!

    Gruß,
    SP



  • Der Assembler war mein erster Gedankengang. Ich bin ein Querdenker, habe aber recht schnell eingesehen, dass es nicht unbedingt die schönste Lösung ist.

    Dein Code ist ganz gut. Ich finde es nur wenig schön die Oktetts angeben zu müssen.

    Du hast natürlich recht, ich "brauche" keine bcd-Klasse. Ich halte es jedoch für leichter im Umgang, wenn ich eine solche Implementieren würde. Welche Schnittstellen die wie hat, muss ich mir allerdings nochmal reichlich überlegen.

    Ich glaube mich erinnern zu kommen, dass Borland C++ sogar eine bcd-Header mit einer bcd-Klasse hatte.



  • Falls du doch einmal in die Verlegenheit kommst, die Operatoren zu überladen, lohnt sich ein Blick in die Artikel von pumuckl (Link zum ersten Artikel). Besonders wenn du Boost hast, kannst du dir so viel Redundanz sparen.


Anmelden zum Antworten