guenni
Goto Top

Überladen von Funktionen und Operatoren in C plus plus

Normalerweise hat in jeder Programmiersprache jede Funktion ihren
eindeutigen Namen. Allgemein ausgedrückt hieße das:

Variable_Max_Integer = Max_Integer(5,7)
Variable_Max_Fließkomma = Max_Fließkomma(3.4,1.34)
usw.

In C++ ist es möglich, einen einzigen Funktionsnamen mehrmals zu
gebrauchen, vorausgesetzt, der Rückgabetyp und/oder der/die
Parametertyp(en) unterliegen einem unterschiedlichem Kontext.

Anhand dieser Kriterien sucht sich der Compiler die passende
Implementation der Funktion selber heraus. Die Implementation selber
wird vernachlässigt, die kann bei jeder Funktion gleich sein.

Zum Überladen von Operatoren mehr im 2. Kapitel

1. Funktionsüberladung
2. Operatorüberladung in Klassen
3. Ausgabe in Datei
4. Eingabe aus Datei
5. Ein Test-Script
6. Anhang – Ein Überladungstest

1. Funktionsüberladung

Wir möchten beispielsweise den Maximalwert von Variablen ermitteln.
Als Funktionsprototypnamen nehmen wir cmax, ermittelt werden soll
dabei der größere Wert zweier Argumente.

Funktionsprototyp für Ganzzahlvariablen:

int cmax(int,int);

Funktionsprototyp für Fließkommavariablen:

double cmax(double,double);

Funktionsprototyp für Zeichen:

char cmax(char,char);

Die Implementation ist für alle drei gleich:

Rückgabetyp cmax(argument_typ a, argument_typ b){

return a<b?b:a;

}

Der zusammengesetzte Operator prüft zuerst, ob a kleiner ist als b.
Wenn ja, gibt er b zurück, ansonsten a.

Somit haben wir den Funktionsnamen cmax zweimal überladen und brauchen künftig
nur noch eine Funktion, um den Maximalwert dreier versch. Datentypen zu
ermitteln. In der Praxis sieht es dann so aus:

int i_max = cmax(5,7);
double d_max = cmax(2.3,1.45);
char c_max = cmax('C','B');

printf("%d",i_max);
printf("%f",d_max);
printf("%c",c_max);

Im letzten Beispiel muß man sich den Ascii-Code in Erinnerung rufen:

A=Ascii-Code 65
B=Ascii-Code 66
C=Ascii-Code 67
usw.

Daher liefert die Funktion cmax "C" zurück, weil 67 größer ist als 66.


2.Operatorüberladung

Normalerweise benutzt man arithmetische Operatoren für das, wofür
sie nun mal da sind: Addition, Subtraktion usw. Einige Programmier/Script-
sprachen lassen diese Operatoren auch auf Zeichenketten zu, da kann man
Ausdrücke verwenden wie:

var s=" Text";
print/write "ein" + s

Ergebnis/Ausgabe: Ein Text

Um eine solche Syntax auch in C++ vorzunehmen, ist es nötig, arithmetische
Operatoren zu überladen. Überladen im Zusammenhang mit Operatoren
heißt nichts anderes, als dass man dem Operator mittels einer
Funktionsdefinition eine neue/andere Aufgabe zuweist.

Allerdings kann dieses nur in Verbindung mit Klassen passieren.

Operatoren liefern wie Funktionen ein Ergebnis zurück, daher werden sie
auch wie Funktionen definiert:

Rückgabetyp& operator+(Argumententyp&);

Wie bei Funktionen ist auch ein mehrmaliges Überladen von
Operatoren möglich, dazu später mehr.

Die Implementation kann, analog zu Methoden, innerhalb oder
außerhalb der Klasse liegen.

Wer mit C++ schon mal Zeichenketten verarbeitet oder manipuliert hat,
der weiß, wie mühselig es sein kann, besonders, wenn man mit Zeigern
auf Zeichenketten arbeitet.

Da liegt es nahe, dass man für zukünftige Programme eine Klasse für Zeichenketten
entwickelt und weiter pflegt, heißt, die Klasse Methode für Methode
erweitert, bis alle Bereiche abgedeckt sind, die zur Verarbeitung von
Zeichenketten nötig sind.

Ungeachtet dessen, ob eine solche Klasse bereits existiert, ist es
vielleicht interessant, sich selber mal dran zu setzen.

Unsere Klasse nennen wir einfach CString:

class CString{

};

Daten der Klasse werden mit dem Schlüsselwort private vor
Zugriff geschützt. Die Manipulation der Daten soll ja ausschließlich
von Methoden vorgenommen werden, die durch das Schlüsselwort public
dazu bestimmt sind.

class CString{

private:

Variablendeklaration

public:

Konstruktoren
Methoden
Destruktor
};

Der Einsatz von private und public ist hierbei mehrmals möglich:

class CString{

private:
Daten
public:
Konstruktoren
Destruktor
private:
noch mehr Daten
public
Methoden
usw.
};

Die Namen der Konstruktoren/ Destruktoren sind stets indentisch mit
dem Namen der Klasse. Dem Namen des Destruktors wird die Tilde
vorangestellt: ~Destruktor
Einen Rückgabetyp gibt es für beide nicht.

Anhand dieser Info können wir jetzt unsere Klasse aufbauen:

class CString{

private:
char *string; Zeiger auf eine Zeichenkette
int size;
Länge der Zeichenkette
public:
CString(); Standard-Konstruktor
CString(const char*);
Konstruktor mit Initialisierung
CString& operator+(CString&); Additions-Operator überladen, Objekt + Objekt
CString& operator+(const char*);
Additions-Operator nochmals überladen, Objekt + Zeichenkette
CString& operator=(CString&); Zuweisungs-Operator überladen
void CString_Print();
Ausgabe string
~CString(); //Destruktor
};

Die Implementierung der Methoden und Operatoren kann innerhalb oder
außerhalb der Klasse erfolgen. Erfolgt sie außerhalb, muß dazu der
der Scope-Operator :: benutzt werden.

Die Syntax der Konstruktoren/Destruktoren ist hierbei:

Klassenname::Konstruktor([argument]){

ToDo
}

bzw.

Klassenname::~Destruktor(){

ToDo
}

Die Syntax der Methoden/Operatoren:

Rückgabetyp& Klassenname::operator op/Methode([Parameter]){
ToDo
}

innerhalb () heißt, optional.

Der Standard-Konstruktor...

CString::CString(){
string=0;
size=0;
}
… erzeugt ein leeres Objekt.

Syntax im Programm: CString s;

Der Konstruktor mit Initialisierung initialisiert das Objekt mit Werten.
Interessant ist hierbei die Verwendung des Operators new.
Zur Speicherreservierung von Zeigern auf Zeichenketten wurde sonst
die Bibliothek malloc.h herangezogen, z.B.:

char *string;
string=(char*)malloc(sizeof(char)*String_Länge);

Das erledigt nun für uns new.

Der Konstruktor mit Initialisierung belegt die Variable(n) des
Objekts bei der Erzeugung mit Werten.

CString::CString(const char *s){
size = strlen(s);
string = new char[size + 1];
strcpy(string, s);
}

Syntax im Programm: CString s("Text");

Der Operator = weißt dem Objekt einen neuen Wert zu:

CString& CString::operator=(CString& k){
delete string;
string = new char[(size = k.size) + 1];
strcpy(string,k.string);
return *this;
}

Syntax im Programm: CString s; CString t("Text"); s=t;

Der Operator + wurde ja bei der Klassendefinition zweimal überladen:

Zum einen soll er einem Objekt Typ CString ein Objekt Typ CString
anhängen, zum anderen einem Objekt Typ CString einen String anhängen.

CString& CString::operator+(CString& k){
size += k.size;
char *s = new char[size + 1];
strcpy(s,string);
strcat(s,k.string);
delete string;
string = s;
return *this;
}

CString& CString::operator+(const char *k){
size += strlen(k);
char *s = new char[size + 1];
strcpy(s,string);
strcat(s,k);
delete string;
string = s;
return *this;
}

Syntax im Programm:

CString s("Ein"); CString t(" Text"); CString st=s+t;

CString s("Eine"); char t=" Demo"; CString st=s+t;

Die Methode CString_Print() gibt den Inhalt des Objekts
einfach nur aus:

void CString::CString_Print(){
cout << string << endl;
}

Syntax im Programm:

Objekt.CString_Print();

Der Destruktor ~CString gibt einfach den Speicher wieder frei:

CString:: ~CString()
{
delete string;
}

Der Destruktor wird nie aufgerufen, er erledigt seine Arbeit automatisch.

3. Ausgabe in Datei

Wie speichern wir nun den Inhalt unserer Klasse in eine Datei?

Ein Zugriff via Punktoperator "Schreibe s.string in Datei..." ist nicht
möglich, da die Variable string unter private deklariert wurde:

CString s("Ein Text");
ofstream datei("c:\test.txt",ios::out);
datei << s.string;

... schlägt also fehl,

datei << s; schlägt ebenso fehl, da mäkelt der Compiler, dass es für unseren
Typ Klasse und dem Operator << keine Übereinstimmung gibt. Na, dann
überladen wir den Operator doch einfach. Aber wie? Eine Überladung
innerhalb der Klasse legt als linken Operanden immer die Klasse fest:

CString& CString::operator<<(... ?

Auf der linken Seite sollte doch, im Sinne von cout << Variable, der
Ausgabestrom stehen.

Die Lösung liegt im Schlüsselwort friend. Hiermit wird das Klassenprinzip,
der Zugriff auf Daten nur durch klasseneigene Methoden, wieder
aufgehoben, indem einer anderen Klasse der Zugriff auf unsere Klasse
gewährt wird!! Wir sagen unserer Klasse also jetzt mal, vertraue der
Klasse ostream:

class CString{
private:

public:

friend ostream& operator<<(ostream &h, CString &s){
h << s.string;
return h;
}

};

Durch diese Überladung von << ist es nun möglich, ein Objekt vom
Typ CString in eine Datei zu schreiben:

CString s("Ein Text");
ofstream datei("c:\test.txt",ios::out);
datei << s;

4.Eingabe aus Datei

Es liegt nun nahe, dass der Eingabeoperator ebenso überladen werden muß,
damit wir Dateiinhalte in unser Objekt übertragen können:

const int PUFFER = 2048;
...
...
class CString{
...
public:
friend istream& operator>>(istream &h, CString &s){
char *puffer = new char[PUFFER + 1];
h.getline(puffer,PUFFER);
delete s.string;
s.string = new char[(s.size = h.count())+1];
strcpy(s.string,puffer);
delete puffer;
return h;
}

};

CString s;
ofstream datei("c:\test.txt",ios::in);
datei >> s;


5.Ein Testscript

Das folgende Script ist erfolgreich kompiliert mit Visual C++ 2005 Express Editition.
Es werden zwar 7 Warnmeldungen ausgegeben, die aber nur darauf hinweisen, dass
bestimmte Funktionen veraltet sind. Die produzierte EXE-Datei läßt sich problemlos
ausführen.


#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string.h>
const int PUFFER = 2048;
using namespace std;

// Überladener Funktionsptototyp cmax
int cmax(int,int);
double cmax(float,float);
char cmax(char,char);
// Implementation der Funktionen
int cmax(int a, int b){
 return a<b?b:a;
}
double cmax(double a, double b){
 return a<b?b:a;
}
char cmax(char a,char b){
 return a<b?b:a;
}
// Eine Klasse String
class CString{
 private:
		 char *string;
         int size; 
 public:
        CString(); //Standard-Konstruktor
        CString(const char*); //Konstruktor mit Initialisierung
        CString& operator+(CString&); //Additions-Operator überladen, Objekt + Objekt
        CString& operator+(const char*); // Additions-Operator überladen, Objekt + Zeichenkette
		CString& operator=(CString&); // Zuweisungs-Operator überladen
        void CString_Print(); // Ausgabe CString
        ~CString(); //Destruktor
        friend ostream& operator<<(ostream &h, CString &s){ // Ausgabeoperator überladen
         h << s.string;
         return h;
        }
		friend istream& operator>>(istream &h,CString &s){ // Eingabeoperator überladen
		 char *puffer = new char[PUFFER + 1];
		 h.getline(puffer,PUFFER);
		 delete  s.string;
		 s.string = new char[(s.size = h.gcount())+1];
		 strcpy(s.string,puffer);
		 delete  puffer;
		 return h;
		}
 };



// Implementierung der Methoden
CString::CString(){
 string=0;
 size=0;
}

CString::CString(const char *s){
 size = strlen(s);
 string = new char[size + 1];
 strcpy(string, s);
}

CString& CString::operator=(CString& k){
 delete  string;
 string = new char[(size = k.size) + 1];
 strcpy(string,k.string);
 return *this;
}
CString& CString::operator+(CString& k){
 size += k.size;
 char *s = new char[size + 1];
 strcpy(s,string);
 strcat(s,k.string);
 delete  string;
 string = s;
 return *this;      
} 
CString& CString::operator+(const char *k){
 size += strlen(k);
 char *s = new char[size + 1];
 strcpy(s,string);
 strcat(s,k);
 delete  string;
 string = s;
 return *this;      
} 

void CString::CString_Print(){
 cout << string << endl;
}

CString:: ~CString()
{
  delete string;
}

int main(int argc, char *argv)
{
    ofstream out("c:\\cstring.txt",ios::out);  
	ifstream in("c:\\cstring.txt",ios::in);  
	CString a("Ein ");  
	CString b("Text");  
	CString c=a+b;
    CString d("Noch");  
    char e[64]=" ein Text";  
    CString f=d+e;
	CString g;
	c.CString_Print(); // Ausgabe: Ein Text
    f.CString_Print(); // Ausgabe: Noch ein Text
    printf("%d\n",cmax(5,7)); // Ausgabe: 7  
    printf("%f\n",cmax(2.3,1.45)); // Ausgabe: 2.300000  
    printf("%c\n",cmax('C','B')); // Ausgabe: C  
    out << f; // Schreibt Inhalt von f in Datei
	out.close();
	in >> g; // Liest Datei in g ein
	in.close();
	g.CString_Print(); // Ausgabe: Noch ein Text
	getchar();
	return 0;
}

6.Anhang – Ein Überladungstest

Um zu testen, ob das Überladen auch mit C eigenen Funktionen
klappt, überladen wir jetzt einfach mal printf. Printf gibt einen
int-Wert zurück und erwartet als Parameter einen String und
optional eine oder mehrere Variablen:

printf("Hallo");
printf("%c %d, Variable_Typ_Character, Variable_Typ_Int);

Wir hingegen übergeben der überladenen Funktion printf zwei
Fließkommawerte, der Rückgabetyp bleibt gleich,die Implementation
überprüft einfach diese Werte auf Gleichheit.

int printf(double x, double y){
return x==y;
}

Die Anweisung printf("%d", printf(zahl,zahl)); gibt am Bildschirm eine
Null aus bei Ungleichheit, eine Eins bei Gleichheit der Argumente. Klappt also.

So, ich glaub, das Tutorial ist jetzt lang genug, kommen wir jetzt mal langsam
zum Ende. Was ich noch erwähnen möchte, ist die Syntax zur Erstellung der
Ausgabe bzw. Eingabe in Dateien.

Zur Ausgabe in Dateien:

ofstream out("c:\\cstring.txt",ios::out);

Der zweite Parameter, ios::out, erzeugt eine Datei zum Schreiben.

Zur Eingabe aus Dateien:

ifstream in("c:\\cstring.txt",ios::in);

Der zweite Parameter, ios::in, öffnet eine Datei zum Lesen.

Da gibt's, analog zur Funktion fopen(char handle, char Modi), natürlich noch
mehr Argumente.

Zum Operator <<, cout << Variable, existieren noch Konstanten, um die
Ausgabe zu formatieren.

Diese beiden Themen werden vom nächsten Tutorial behandelt.

Grüße
Günni

Content-Key: 40074

Url: https://administrator.de/contentid/40074

Ausgedruckt am: 29.03.2024 um 13:03 Uhr

Mitglied: 65593
65593 26.05.2008 um 22:11:56 Uhr
Goto Top
Gut erklärt!Aber deb Assembler hätte man deutlicher hervorheben sollen,da diese Funktion für die Operanden(binärcodiert) einen beachtlichen Wert darstellt!