I-PROGR2-HOME

  1. Einführung

  2. Die Standarddatentypen von C/C++

  3. Die Standardoperatoren von C/C++

  4. Die Bruchklasse als neues Objekt

  5. Definition neuer Operatoren

  6. Spezielle Operatoren

  7. Testfragen und Aufgaben


I-PROGR2 SS03 - PROGRAMMIEREN2 - Vorlesung mit Übung
Eigene Klassenoperatoren

 Achtung : Skript gibt den mündlichen Vortrag nicht vollständig wieder !!!
                         

AUTHOR: Gerd Döben-Henisch
DATE OF FIRST GENERATION: April-20, 2003
DATE OF LAST CHANGE: April-29, 2003 h
EMAIL: Gerd Döben-Henisch



1. Einführung


Aus Programmieren 1 am Beispiel der Sprache C sind die Standardoperatoren bekannt, die auf Variablen der Standardtypen anwendbar waren, etwa:

In C++ kann der Anwender eigene, neue Typen einführen, insbesondere Klassen. Die Standardoperatoren sind für diese neuen Typen zunächst nicht definiert. es soll in dieser Vorlesung gezeigt werden, wie dies möglich ist.

Bevor hier gezeigt werden wird, wie man bekannte Operatorsymble für neue, eigene Typen im Kontext von Klassen verwenden kann, hier eine Zusammenfassung der Standarddatentypen und der Standardoperatoren unter C/C++.


START



2. Die Standarddatentypen von C/C++


Datentyp

Bedeutung

Grösse in Bytes
(z.B. SuSe 7.2 Linux auf 32-Bit System)

Wertebereich
(z.B. SuSe 7.2 Linux auf 32-Bit System)
(exemplarisch)

int

Ganze Zahl

4

float

Gleitkomma mit einfacher Genauigkeit

4

double

Gleitkomma mit doppelter Genauigkeit

8

char

Zeichen

1

unsigned char

Zeichen

1

[0,255]

signed char

Zeichen

1

[-128,127]

bool

Boolscher Wert (true/false)

1

enum

Aufzählungen von Namen, die ganze Zahlen repräsentieren

void

leer/ nichts; für Funktionen ohne Rückgabewert

unsigned int

4

[0,4294967295]

signed int

Ganze Zahl

4

[-2147483648,2147483647]

short int

2

signed short int

2

[-32768, 32767]

unsigned short int


4

[0,65535]

long int

4

signed long int

4

[-2147483648,2147483647]

unsigned long int

4

[0,4294967295]

long double

12

In der vorausgehenden Aufstellung werden zusätzlich Attribute zu den Basis-Datentypen verwendet:

Attribut

Bedeutung

short

eventuell kleiner als 'normal'

long

eventuell grösser als 'normal'

unsigned

vorzeichenfrei

signed

vorzeichenbehaftet

Welche Auslegungen die einzelnen Datentypen genau haben, das kann man einerseits der Headerdatei limits.h entnehmen, andererseits kann man das mit dem Operator sizeof() überprüfen. Dieser Operator wurde von der Sprache C übernommen; innerhalb von C++ hat er aber weiter keine grössere Bedeutung. Dennoch kann man ihn benutzen, wie das nachfolgende kleine beisoiel zeigt, um sich die aktuellen Datengrössen anzeigen zu lassen:




//*************************
//
// hallo2.cpp
//
// first: march-14,2002
// last: april-22,2003
//
// Prints a message on standard out
// Shows the size of some basic datatypes
//
// compile: g++ -o hallo2 hallo2.cpp
// usage: hallo2
//
//*******************************

#include <iostream>
using namespace std;

int main(){
  cout << "Hallo Welt!" << endl;

  // Using sizeof() to show the actual byte-size of some data types

  cout << "CHAR = " << sizeof(char) << endl;
  cout << "INT = " << sizeof(int) << endl;

  cout << "UNSIGNED INT = " << sizeof(unsigned int) << endl;

  cout << "SHORT INT = " << sizeof(short int) << endl;

  cout << "UNSIGNED SHORT INT = " << sizeof(unsigned short int) << endl;

  cout << "SIGNED SHORT INT = " << sizeof(signed short int) << endl;

  cout << "LONG INT = " << sizeof(long int) << endl;

  cout << "SIGNED LONG INT = " << sizeof(signed long int) << endl;

  cout << "FLOAT = " << sizeof(float) << endl;

  cout << "DOUBLE = " << sizeof(double) << endl;

  cout << "BOOL = " << sizeof(bool) << endl;

  cout << "LONG DOUBLE = " << sizeof(long double) << endl;


}





gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX7> ./hallo2
Hallo Welt!
CHAR = 1
INT = 4
UNSIGNED INT = 4
SHORT INT = 2
UNSIGNED SHORT INT = 2
SIGNED SHORT INT = 2
LONG INT = 4
SIGNED LONG INT = 4
FLOAT = 4
DOUBLE = 8
BOOL = 1
LONG DOUBLE = 12

START



3. Die Standardoperatoren von C/C++


Will man die vorhandenen Standardoperatoren für eigene Klassen in neuer Weise benutzen, dann muss man beachten, dass die Menge der möglichen Operatoren fest liegt; zusammen damit auch Priorität, Syntax und Auswertungsreihenfolge. Deswegen kann man mit den eigenen Definitionen die vorhandenen Definitionen nur erweitern. Ausserdem werden die Operationen, die für elementare Datentypen definiert sind, nicht automatisch auf abstrakte Datentypen übertragen.

Folgende Operatoren können nicht überladen werden:

{'.', '::', 'sizeof', '.*','?:'}

Sie sind schon für alle Objekte definiert ('.*' ist ein Zeiger auf die Komponente einer bestimmten Klasse).

Die Liste der Standard-Operatoren wird hier nochmals aufgelistet. Die Anordnung folgt der Prioritäts-Hierarchie der Operatoren; soweit möglich, wurden thematisch zusammengehörige Operatoren hervorgehoben.



BEREICHS OPERATOREN

BEDEUTUNG

(...)

Klammerung

::

Bereichszuordnung (z.B. Elementfunktion zur Klasse)


ZUGRIFFS OPERATOREN

BEDEUTUNG

.

Zugriff auf Komponente (z.B. a.v)

->

Zugriff auf Komponente von Zeigern

[...]

Feldindizierung


POSTFIX

BEDEUTUNG

++

Inkrement (a++)

--

Dekrement (a--)


Typumwandlung; IDs

BEDEUTUNG

type(...)

Typumwandlung (int(a);)

static_cast<typ>(...)

Typumwandlung

const_cast<typ>(...)

Typumwandlung

dynamic_cast<typ>(...)

Typumwandlung

reinterprete_<typ>(...)

Typumwandlung

typeid(...)

Klassen-ID


PRÄFIX

BEDEUTUNG

++

Inkrement (++a)

--

Dekrement (--a)

~

Bit-Komplement

!

Logische Negation

+

Positives Vorzeichen

-

negatives Vorzeichen

&

Adresse von

*

Inhalt einer Adresse (Dereferenzierung)

new

Objekt anlegen

delete

Objekt freigeben

new[]

Feld von Objekten anlegen

delete[]

Feld von Objekten freigeben

(typ)...

Typumwandlung mit der cast-Notation

sizeof(...)

Speicherplatz


KOMPONENTEN

BEDEUTUNG

.*

Komponente über Komponentenzeiger

->*

Komponente von Zeiger über Komponentenzeiger


ARITHMETISCHER
OPERATOREN

BEDEUTUNG

*

Multiplikation

/

Division

%

Modulo-Operator

+

Addition

-

Subtraktion



SHIFT

BEDEUTUNG

<<

Links-Shift

>>

Rechts-Shift


VERGLEICHSOPERATOREN

BEDEUTUNG

<

kleiner

<=

kleiner-gleich

>

grösser

>=

grösser-gleich

==

gleich

!=

nicht gleich, ungleich


BITOPERATOREN (infix)

BEDEUTUNG

&

Bitweises UND

^

Bitweises exklusives ODER (XOR)

|

Bitweises ODER


LOGISCHE OPERATOREN (infix)

BEDEUTUNG

&&

Logisches UND (AND) (Sucht von links nach rechts das erste false. Wird keines gefunden, dann ist das logische UND true)

||

Logisches ODER (OR) (Sucht von links nach rechts das erste true. Wird keines gefunden, dann ist das logische ODER false)

?:

Bedingte Bewertung (a ? x : y)


ZUWEISUNGEN (infix)

BEDEUTUNG

SCHREIBWEISE

=

Einfache Wertzuweisung

(z.B. a=5;)

*=

Multiplikation

"a op= b" entspricht "a = a op b"

/=

Division

%=

Modulo

+=

Addition

-=

Subtraktion

<<=

Shift links

>>=

Shift rechts

&=

Bitwises UND

^=

Bitwises exklusiv ODER

|=

Bitwises ODER

Zu beachten ist, dass Wertzuweisungen Teil von grösseren Ausdrücken sein können!


AUSDRUCKSMERGE

BEDEUTUNG

,

Kommaoperator; verbindet mehrere Anweisungen zu einer (speziell in For-Schleifen)


START



4. Die Bruchklasse als neues Objekt


Um nun zu zeigen, wie man die bekannten Operatoren im Kontext seiner eigenen neuen Klassen mit neuen Bedeutungen aufladen kann, betrachten wir ein einfaches Beispiel, das wir dem C++-Buch von [JOSUTTIS 2001:156ff] entnehmen (der Sourcekode der Beispiele ist weitgehend über Internet zugänglich).



bruchclass

Demo-Klasse Bruch nach Josuttis



Die Klasse Bruch hat die privaten Attribute zaehler und nenner vom Typ int. Dann verfügt die Klasse Bruch über drei öffentliche Konstruktoren, eine print()-Funktion sowie drei eigenen Operatoren, die von bekannten Operator-Symbolen Gebrauch machen.

Die zugehörige Headerdatei lautet wie folgt:




/* Die folgenden Code-Beispiele stammen aus dem Buch:
 *  Objektorientiertes Programmieren in C++
 *   Ein Tutorial für Ein- und Umsteiger
 * von Nicolai Josuttis, Addison-Wesley München, 2001
 *
 * (C) Copyright Nicolai Josuttis 2001.
 * Diese Software darf kopiert, verwendet, modifiziert und verteilt
 * werden, sofern diese Copyright-Angabe in allen Kopien vorhanden ist.
 * Diese Software wird "so wie sie ist" zur Verfügung gestellt.
 * Es gibt keine explizite oder implizite Garantie über ihren Nutzen.
 */

#ifndef BRUCH2_HPP
#define BRUCH2_HPP


namespace Bsp {


class Bruch {

  private:
    int zaehler;
    int nenner;


  public:
    // Default-Konstruktor
    Bruch ();

    // Konstruktor aus int (Zähler)
    Bruch (int);

    // Konstruktor aus zwei ints (Zähler und Nenner)
    Bruch (int, int);

    // Ausgabe
    void print ();

    // neu: Multiplikation mit anderem Bruch
    Bruch operator * (Bruch);

    // neu: multiplikative Zuweisung
    Bruch operator *= (Bruch);

    // neu: Vergleich mit anderem Bruch
    bool operator < (Bruch);
};

}

#endif    // BRUCH2_HPP



Die zugehörige Implementierung:




/* Die folgenden Code-Beispiele stammen aus dem Buch:
 *  Objektorientiertes Programmieren in C++
 *   Ein Tutorial für Ein- und Umsteiger
 * von Nicolai Josuttis, Addison-Wesley München, 2001
 *
 * (C) Copyright Nicolai Josuttis 2001.
 * Diese Software darf kopiert, verwendet, modifiziert und verteilt
 * werden, sofern diese Copyright-Angabe in allen Kopien vorhanden ist.
 * Diese Software wird "so wie sie ist" zur Verfügung gestellt.
 * Es gibt keine explizite oder implizite Garantie über ihren Nutzen.
 */
// Headerdatei mit der Klassen-Deklaration einbinden
#include "bruch2.hpp"

// Standard-Headerdateien einbinden
#include <iostream>
#include <cstdlib>

// **** BEGINN Namespace Bsp ********************************
namespace Bsp {

/* Default-Konstruktor
 */
Bruch::Bruch ()
 : zaehler(0), nenner(1)    // Bruch mit 0 initialisieren
{
    // keine weiteren Anweisungen
}

/* Konstruktor aus ganzer Zahl
 */
Bruch::Bruch (int z)
 : zaehler(z), nenner(1)    // Bruch mit z 1tel initialisieren
{
    // keine weiteren Anweisungen
}

/* Konstruktor aus Zähler und Nenner
 */
Bruch::Bruch (int z, int n)
{
    /* neu: Ein negatives Vorzeichen im Nenner kommt in den Zähler
     * Dies vermeidet u.a. eine Sonderbehandlung beim Operator <
     */
    if (n < 0) {
        zaehler = -z;
        nenner  = -n;
    }
    else {
        zaehler = z;
        nenner  = n;
    }

    // 0 als Nenner ist allerdings nicht erlaubt
    if (n == 0) {
        // Programm mit Fehlermeldung beenden
        std::cerr << "Fehler: Nenner ist 0" << std::endl;
        std::exit(EXIT_FAILURE);
    }
}

/* print
 */
void Bruch::print ()
{
    std::cout << zaehler << '/' << nenner << std::endl;
}

/* neu: Operator *
 */
Bruch Bruch::operator * (Bruch b)
{
    /* Zähler und Nenner einfach multiplizieren
     * - das Kürzen sparen wir uns
     */
    return Bruch (zaehler * b.zaehler, nenner * b.nenner);
}

/* neu: Operator *=
 */
Bruch Bruch::operator *= (Bruch b)
{
    // "x *= y"  ==>  "x = x * y"
    *this = *this * b;

    // Objekt, für das die Operation aufgerufen wurde (erster Operand), zurückliefern
    return *this;
}

/* neu: Operator <
 */
bool Bruch::operator< (Bruch b)
{
    /* Ungleichung einfach mit Nennern ausmultiplizieren
     * - da die Nenner nicht negativ sein können, kann
     *     der Vergleich dadurch nicht umgedreht werden
     */
    return zaehler * b.nenner < b.zaehler * nenner;
}

} // **** ENDE Namespace Bsp ********************************



Beispiele für eine Anwendung der Operatoren zeigt die folgende usage-Datei:




//----------------------
//
// use_bruch.cpp
//
// author: gerd d-h.
// first: april-23, 2003
//
// This usage-file uses demoprograms
// from Josuttis (see there)
//
//--------------------------------------------

#include <iostream>
#include "bruch2.hpp"

using namespace std;

int main()
{
  char input;
  int z,n;

    Bsp::Bruch x,y;           // Brueche x deklarieren
    Bsp::Bruch w(7,3);        // Bruch w deklarieren


    cout << "Bruch x vor Kopie: " ;x.print();
    x=w;
    cout << "Bruch w: " ;w.print();
    cout << "Bruch x nach Kopie: " ;x.print();

    input='y';

    while (input == 'y' ){


      cout << endl << "POSSIBLE ACTIONS:" << endl;

      cout <<  "ASSIGN y= x*w (a) ?" << endl;
      cout <<  "MULTIPLY x*w (m) ?" << endl;
      cout <<  "SQUARE w*w (s) ?" << endl;
      cout <<  "COMPARE x< w (c) ?" ;
      cin >> input;

      switch(input){


      case 'a': {cout << "CASE a (STANDARD): " << endl;
                 y = x * w;
		 y.print();


                 cout << "CASE a (EXPLICIT): " << endl;
                 y.operator= (x.operator*(w));
		 y.print();

                 break;
                 }

      case 'm': {cout << "CASE m: " << endl;
                 x = x * w;
		 x.print();
                 break;
                 }


      case 's': {cout << "CASE s: " << endl;
                 w = w * w;
		 w.print();
                 break;
                 }

      case 'c':  {cout << "CASE c: " << endl;
                  if ( x < w ) { cout << "x kleiner w: ";x.print(); w.print(); }
		  else  { cout << "x nicht kleiner w: 
";x.print(); w.print(); }
                 break;
                 }


      default: cout << "WRONG INPUT !!! " << endl;
      }

      cout << endl << "CONTINUE (y/n) ?" ;
      cin >> input;

    }//End of while input



    // solange x < 1000
    while (x < Bsp::Bruch(1000)) 
{

        // x mit w multiplizieren
        x *= w;

	//Koennte man auch schreiben: x.operator= (w.operator* (w))

        // und ausgeben
        x.print();
    }
}




Ein Aufruf des Programms 'btest2.cpp' erzeugt die folgende Ausgabe:



gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX7> ./bruch2
Bruch x vor Kopie: 0/1
Bruch w: 7/3
Bruch x nach Kopie: 7/3

POSSIBLE ACTIONS:
ASSIGN y= x*w (a) ?
MULTIPLY x*w (m) ?
SQUARE w*w (s) ?
COMPARE x< w (c) ?s
CASE s:
49/9

CONTINUE (y/n) ?y

POSSIBLE ACTIONS:
ASSIGN y= x*w (a) ?
MULTIPLY x*w (m) ?
SQUARE w*w (s) ?
COMPARE x< w (c) ?a
CASE a (STANDARD):
343/27
CASE a (EXPLICIT):
343/27

CONTINUE (y/n) ?y

POSSIBLE ACTIONS:
ASSIGN y= x*w (a) ?
MULTIPLY x*w (m) ?
SQUARE w*w (s) ?
COMPARE x< w (c) ?c
CASE c:
x kleiner w: 7/3
49/9

CONTINUE (y/n) ?y

POSSIBLE ACTIONS:
ASSIGN y= x*w (a) ?
MULTIPLY x*w (m) ?
SQUARE w*w (s) ?
COMPARE x< w (c) ?m
CASE m:
343/27

CONTINUE (y/n) ?y

POSSIBLE ACTIONS:
ASSIGN y= x*w (a) ?
MULTIPLY x*w (m) ?
SQUARE w*w (s) ?
COMPARE x< w (c) ?c
CASE c:
x nicht kleiner w: 343/27
49/9

CONTINUE (y/n) ?n
16807/243
823543/2187
40353607/19683

START



5. Definition neuer Operatoren


Die Beispiele von JOSUTTIS zeigen sehr klar, wie man neue Klassenoperatoren definieren kann. Verwirrend ist am Anfang meistens, dass ein 2-stelliger Operator als 1-stellige Funktion definiert wird. Dies wird im nachfolgenden Schaubild nochmals verdeutlicht:



operator

Definition von Klassen-Operatoren



Angenommen bei den Variablen x,y,w handelt es sich um Objekte mit der Klasse TYPE. Dann ist der Ausdruck x = y *w so zu lesen, dass dem TYPE-Objekt x mit dem Operator 'operator=' ein Wert zugewiessen ('assignment') werden soll; ausführlicher: x.operator=(...). Im vorliegenden Fall besteht der Wert nicht aus einem einzelnen Objekt --z.B. W--, sondern wiederum aus einer Operation, nämlich der Multilikation von Objekt y mit Objekt w, ausführlicher: y.operator*(w). Dies bedeutet, der Multiplikationsoperator vom Objekt x bekommt als Argument das Objekt w. Das Ergebnis dieser Multiplikation wird dann als Wert dem Zuweisungsoperator von x übergeben.


START



6. Spezielle Operatoren



START



7. Aufgaben


  1. Versuchen Sie am Beispiel der Klasse Bruch selbst einige Operatoren neu zu definieren:
    - operator+
    - operator-
    -operator/


  2. Versuchen sie am Beispiel der Klasse MensaConnection eigene Operatoren zu definieren, und zwar:
    - operator< := die Menge aller Klienten, die in der Mensa sind und eine Erwartungszeit < einem bestimten Wert haben
    - operator> := die Menge aller Klienten, die in der Mensa sind und eine Erwartungszeit > einem bestimten Wert haben
    - operator[] := die Menge aller Klienten, die in der Mensa sind und die eine bestimmten Erwartungszeit haben


  3. Versuchen Sie die Klasse MensaConnection für folgende Aufgabenstellung zu erweitern:
    - alle, die eintreten, müssen erst die Essensausgabe passieren, die mehrere Alternativen bietet
    - alle, die die Essensausgabe passiert haben, können einen der verfügbaren Plätze einnehmen (dies ist eine 'freundliche' Mensa ohne Kassen)
    - jeder, der die Mensa verlassen will, sollte sein Tablett bei der Geschirrabgabe abgeben
    Anmk: Halten sie bei der Analyse die Schritte ein, die bislang festgelegt worden sind (Problembeschreibung, UML-Klassen, UML-Interaktion, Struktogramm, C++-Klassen, C++Implementierung, C++-Use-Datei, Test).
    Anmk: Versuchen Sie die Erweiterung so zu gestalten, dass die bisherigen Funktionen ihre Gültigkeit behalten. Falls dies nicht geht, beschreiben Sie, was sind die Gründe dafür sind.



START