Sie werden schnell feststellen, daß in jedem objektorientierten Programm, das Sie schreiben, die Objekte gruppiert und in Auflistungen unterschiedlicher Typen und Größen gespeichert werden müssen. Auch hier kommt wieder die MFC zu Hilfe und stellt Gruppen einfach anzuwendender Klassen und Vorlagen bereit.
Die Auflistungsklassen fallen in drei große Kategorien - Felder, Listen und Tabellen - mit jeweils speziellen Aufgabengebieten.
Felder (oder Arrays, diese Begriffe werden hier gleichberechtigt verwendet) bilden die Hauptstütze der Auflistungsklassen und eignen sich für die Implementierung von Objektcontainern. Jedes Objekt in einem Array hat eine bei null beginnende Position (Index), über die man sich auf das Objekt bezieht.
Die Listenklasse organisiert Elemente in Form einer doppelt verketteten Liste, bei der die Daten sequentiell miteinander verbunden sind. Listen bieten sich vor allem an, wenn man Elemente am Anfang (dem »Kopf«) oder am Ende der Liste schnell hinzufügen oder entfernen muß. Man kann die Liste auch vorwärts oder rückwärts von einem Element zum nächsten durchlaufen.
Bei Tabellen (Maps) verknüpft man ein Schlüsselobjekt, etwa einen String oder eine Zahl, mit einem Wertobjekt, wo Verknüpfungen von Haus aus nur selten oder zufällig vorkommen. Beispielsweise kann man eine Tabelle verwenden, um Objekte mit Postleitzahlen zu verknüpfen. Tabellen eignen sich hervorragend bei schnellen Abfragen von Objekten, wenn der Verknüpfungsschlüssel gegeben ist, und können für die temporäre Speicherung von Daten bei großen Datenbanken verwendet werden.
MFC stellt mehrere vordefinierte Feldklassen und eine allgemeine Feldvorlage bereit, so daß man Felder erstellen kann, die eigene benutzerdefinierte Objekte aufnehmen. (Auf den letzten Punkt gehen wir später in diesem Kapitel ein.)
Verschiedene vordefinierte Feldklassen bieten einen schnellen und einfachen Zugriff auf gebräuchliche Typen von Variablen und Objekten (siehe Tabelle F.1).
Für jede Feldklasse gibt es mehrere Member-Funktionen, die sich nur durch den Typ der aufgenommenen Variablen unterscheiden. Jede hier behandelte Funktion läßt sich mit allen Feldklassen verwenden, um die Variablen des entsprechenden Typs zu behandeln.
Einer der nützlichsten Aspekte dieser Feldklassen ist deren Fähigkeit, dynamisch zu
wachsen. Normale C/C++-Arrays sind in ihrer Größe vordefiniert und lassen sich nur
durch umfangreiche Neuzuweisungen von Speicher erweitern. Die Auflistungsklassen
verbergen diese Neuzuweisungen, so daß man einfach die Member-Funktion Add
eines
Feldobjekts aufrufen kann, um einen neuen Wert hinzuzufügen. Will man beispielsweise
Strings in ein CStringArray
aufnehmen, verwendet man etwa folgenden Code:
CStringArray myStringArray;
myStringArray.Add("Rot");
myStringArray.Add("Grün");
myStringArray.Add("Blau");
Die Größe eines Arrays läßt sich dann mit der Funktion GetSize
ermitteln. Führt man
im Anschluß an die obigen Zeilen die folgende Zeile aus, erhält man in nNumberOfItems
drei Elemente zurück:
int nNumberOfItems = myStringArray.GetSize();
Ein Array kann man mit der korrespondierenden Funktion SetSize
auch auf eine bestimmte
Größe einstellen. Dabei wird das Array entweder abgeschnitten oder erweitert,
um der übergebenen Größe zu entsprechen.
Mit der Funktion SetAt
, der man einen bei null beginnenden Index und den zu speichernden
Wert übergibt, kann man Werte in das Array eintragen. Man muß sicherstellen,
daß der Index innerhalb der Feldgrenzen liegt, da SetAt
ansonsten einen Assertion-Fehler
auslöst. Mit der Funktion GetAt
kann man Werte aus dem Array abrufen.
Die Funktion liefert den Wert an der angegebenen Indexposition zurück. Bei einem
CWordArray
setzt man diese Funktionen zum Beispiel folgendermaßen ein:
CWordArray myWordArray;
myWordArray.SetSize(20);
myWordArray.SetAt(0,200);
myWordArray.SetAt(19,500);
TRACE("Der Wert an Indexposition 19 lautet %d\n",
myWordArray.GetAt(19));
Diese Zeilen setzen das erste Element eines 20elementigen Arrays auf 200 und das
letzte auf 500. Die letzte Zeile zeigt dann das letzte Element - den Wert 500
- an. Mit
der Funktion Add
läßt sich das Array weiter vergrößern. Den größten gültigen Index ermittelt
man mit der Funktion GetUpperBound
. Die Funktion liefert einen bei null beginnenden
Index zurück, oder -1
, wenn keine Elemente vorhanden sind.
Mit dem Indexoperator [ ]
kann man Werte an einer bestimmten Indexposition wie
bei einem normalen C++-Array setzen und abrufen. Beispielsweise lassen sich die
Funktionen GetAt
und SetAt
in den obigen Zeilen folgendermaßen durch den Indexoperator
ersetzen:
myWordArray[0] = 200;
myWordArray[19] = 500;
TRACE("Der Wert an Indexposition 19 lautet %d\n", myWordArray.GetAt[19]);
Mit den Funktionen InsertAt
und RemoveAt
kann man Elemente an einer bestimmten
Position einfügen bzw. entfernen. Dabei werden alle Elemente ab dieser Position um
ein oder mehrere Elemente nach oben bzw. unten verschoben.
Die Funktion InsertAt
hat zwei Formen: die erste benötigt eine Indexposition und das
an dieser Stelle einzufügende Element. Optional kann man eine Anzahl übergeben,
um mehrere Kopien des angegebenen Elements einzufügen. Die zweite Form erlaubt
es, ein anderes komplettes Array an der angegebenen Indexposition einzufügen.
Die Funktion RemoveAt
braucht nur einen Parameter, den Index des zu entfernenden
Elements. Man kann auch hier einen optionalen zweiten Parameter übergeben, um
die entsprechende Anzahl von Elementen zu entfernen. Die verbleibenden Feldelemente
werden dann nach unten verschoben, um die Lücke aufzufüllen.
Alle Elemente lassen sich aus einem Array mit der Funktion RemoveAll
entfernen.
Wie Tabelle F.2 zeigt, gibt es nur drei Kategorien
von Listen und eine Vorlage (Template) für Ihre eigenen - später noch
zu behandelnden - Typen. Listen mit einfachen Ganzzahlen benötigt man nur
selten. Statt dessen haben Sie es eher mit verketteten Listen zu tun, in denen
Sie Ihre von CObject
abgeleiteten Klassen, Zeiger auf C++- Klassen
oder Strukturen verwalten.
| |
Bei verketteten Listen sind mehrere Objekte miteinander in sequentieller Art wie die
Waggons eines Zuges verkoppelt. Es gibt eine eindeutige Anfangs- und Endposition,
aber jedes andere Element kennt nur seinen unmittelbaren Nachbarn. Eine POSITION
-
Variable protokolliert eine aktuelle Position in einer Liste. Man kann mehrere POSITION
-Variablen deklarieren, um verschiedene Stellen in derselben Liste zu verfolgen.
Die Member-Funktionen der Listen verwenden dann eine POSITION
-Variable, um den
Anfang (den »Kopf«), das Ende oder das nächste bzw. vorherige Element in der Liste
zu ermitteln.
Mit den Funktionen AddHead
und AddTail
kann man Elemente in eine Liste am Anfang
bzw. am Ende hinzufügen oder mit den Funktionen InsertBefore
und InsertAfter
vor bzw. nach einer bestimmten Position einfügen. Alle Funktionen liefern
dann einen POSITION
-Wert zurück, der die Position des neu hinzugefügten Elements
angibt.
Das folgende Beispiel konstruiert eine vierelementige Liste von CString
-Elementen:
CStringList listMyStrings;
POSITION pos;
pos = listMyStrings.AddHead("Hand");
listMyStrings.AddTail("Unterarm");
listMyStrings.InsertBefore(pos, "Finger");
listMyStrings.AddTail("Ellbogen");
Diese Codezeilen liefern eine verknüpfte Liste von CString
s, die von Anfang bis Ende
folgendes Aussehen hat:
Finger-Hand-Unterarm-Ellbogen
Man kann auch andere gleichartige Listenobjekte an die Funktionen AddHead
und
AddTail
übergeben, um eine weitere Liste am Beginn oder Ende der aktuellen Liste
hinzuzufügen.
Wenn Sie eine Liste aufgebaut haben, können Sie mit Hilfe einer POSITION
-Variablen
durch die Elemente der Liste laufen. Die Anfangs- oder Endposition läßt sich mit den
Funktionen GetHeadPosition
bzw. GetTailPosition
ermitteln. Diese Funktionen geben
einen POSITION
-Wert zurück, der die aktuelle Position in der Liste bezeichnet.
Dann kann man die POSITION
-Variable als Referenz an die Funktionen GetNext
oder
GetPrev
übergeben, um das nächste bzw. vorherige Element in der Liste zu finden.
Diese Funktionen liefern dann das betreffende Objekt zurück und passen die aktuelle
Position an. Wenn das Ende der Liste erreicht ist, wird die Variable POSITION
auf NULL
gesetzt.
Die Zeilen im folgenden Beispiel durchlaufen die oben erzeugte listMyStrings
und
zeigen die Elemente nacheinander an:
POSITION posCurrent = listMyStrings.GetHeadPosition();
while(posCurrent) TRACE("%s\n", listMyStrings.GetNext(posCurrent);
Bestimmte Listenelemente lassen sich mit der Funktion Find
aufsuchen, die einen POSITION
-Wert zurückliefert, wenn der übergebene Suchparameter gefunden wurde.
Optional können Sie einen Positionswert übergeben, von dem aus die Suche beginnen
soll.
Um zum Beispiel nach dem String Finger
der oben angelegten Liste zu suchen, ruft
man die Funktion Find
wie folgt auf:
POSITION posFinger = Find("Finger");
Die Funktion Find
liefert einen NULL
-Wert zurück, wenn das gesuchte Element nicht
vorhanden ist.
Mit der Funktion FindIndex
kann man das nte Element vom Beginn der Liste an suchen
(wobei man n als Parameter übergibt).
Die Anzahl der Elemente in der Liste liefert die Member-Funktion GetCount
. Die
Funktion benötigt keine Parameter und gibt die Anzahl der Elemente zurück.
Der Wert von Elementen an einer bestimmten Position läßt sich mit den Funktionen
GetAt
und SetAt
abrufen bzw. zurücksetzen. Die Funktionen setzt man ähnlich ein wie
die äquivalenten Feldfunktionen, übergibt aber einen POSITION
-Wert statt eines Feldindexes.
Mit der Funktion RemoveAt
entfernt man Elemente aus der Liste. Der Funktion übergibt
man den POSITION
-Wert, um das zu entfernende Element zu kennzeichnen. Mit
dem folgenden Code löschen Sie zum Beispiel den Eintrag Finger
aus der Liste des
obigen Beispiels:
RemoveAt(posFinger);
Tabellenklassen (oder Map-Klassen) verbinden einen Typenwert (oder Element) mit einem Schlüsselwert, mit dem man nach dem Element suchen kann. Die verschiedenen Tabellenklassen sowie deren Schlüsselwerte und zugeordneten Elementtypen sind in Tabelle F.3 aufgeführt.
In eine Tabelle kann man mit der Funktion SetAt
Elemente einfügen. Der Funktion
übergibt man einen Schlüsselwert als ersten Parameter und den Wert des Elementes
als zweiten. Wollen Sie zum Beispiel Ihre von CObject
abgeleiteten Objekte, die mit
einem Zeichenfolgenwert indiziert sind, speichern, können Sie die Klasse
CMapStringToOb
verwenden und Elemente wie folgt hinzufügen:
CMapStringToOb mapPlanetDetails;
mapPlanetDetails.SetAt("Merkur", new CPlanetDets (4878, 0.054, 57.91, 87.969));
mapPlanetDetails.SetAt("Venus", new CPlanetDets (12100, 0.815, 108.21, 224.701));
mapPlanetDetails.SetAt("Erde", new CPlanetDets (12756, 1.000, 149.60, 365.256));
In diesem Beispiel ist CPlanetDets
eine von CObject
abgeleitete Klasse mit einem
Konstruktor, der vier Detailparameter für Planeten übernimmt. Den neuen Objekten
sind dann die Planetennamen als Schlüssel zugeordnet.
Statt der Funktion SetAt
können Sie auch den Indexoperator [ ]
verwenden, indem
Sie den Schlüsselwert in den eckigen Klammern angeben:
mapPlanetDetails["Mars"] = new CPlanetDets (6762, 0.107, 227.94, 686.98);
Nachdem Sie Daten in eine Tabelle eingetragen haben, können Sie sie mit der Member-Funktion
Lookup
wieder abrufen. Der Funktion übergeben Sie den Schlüsselwert
als Referenz auf eine Variable, die das zugehörige Element aufnimmt, falls es existiert.
Ist das Element nicht vorhanden, liefert die Funktion Lookup
den Wert FALSE
zurück.
Um zum Beispiel die Angaben über einen Planet aus dem vorherigen Beispiel abzurufen,
können Sie etwa folgende Zeilen verwenden:
CPlanetDets* pMyPlanet = NULL;
if (mapPlanetDetails.Lookup("Erde", (CObject*&)pMyPlanet))
TRACE("Umlaufzeit = %d Tage\n", pMyPlanet->m_dSidereal);
Die Typumwandlung (CObject*&)
dient dazu, den Objektzeiger pMyPlanet
in eine allgemeine
Zeigerreferenz auf CObject
umzuwandeln.
Der Funktion GetCount
liefert die Anzahl der momentan in der Tabelle vorhandenen
Elemente zurück. Die Elemente lassen sich durch Aufruf der Funktion RemoveKey
entfernen.
Der Funktion übergibt man den Schlüssel des zu entfernenden Elements:
mapPlanetDetails.RemoveKey("Jupiter");
Denken Sie daran, die reservierten Objekte zu löschen. RemoveKey
entfernt einfach
den Zeiger auf das Objekt - und nicht das Objekt selbst - und gibt den verwendeten
Speicher nicht frei. Durch Aufruf der Funktion RemoveAll
können Sie alle Elemente
auf einmal entfernen.
Mit Hilfe der Funktion GetNextAssoc
kann man die Liste der Zuordnungen durchlaufen.
Die Funktion benötigt Parameter, die eine Variable mit der aktuellen Position referenzieren,
eine Schlüsselvariable und eine Elementvariable. Die Position des ersten
Elements ermittelt man mit der Funktion GetFirstPosition
. Die Funktion liefert den
POSITION
-Wert für das erste Element zurück. Um durch die Zuordnungen zu gehen,
kann man etwa den folgenden Code verwenden:
POSITION pos = mapPlanetDetails.GetStartPosition();
while(pos!=NULL)
{
CString strPlanet;
CPlanet* pMyPlanet;
mapPlanetDetails.GetNextAssoc(pos, strPlanet, CObject*&)pMyPlanet);
TRACE("%s hat einen Durchmesser von %d km\n", strPlanet, pMyPlanet-
>m_dDiameter);
}
Nach Rückkehr aus GetNextAssoc
enthält pos
die Position für die nächste Zuordnung
oder NULL
, wenn es keine Zuordnungen mehr gibt. Die Schlüssel- und Elementwerte
(strPlanet
und pMyPlanet
im obigen Beispiel) werden nacheinander auf das jeweilige
Schlüssel/Element-Paar gesetzt.
Durch die Fähigkeit der Tabelle, schwach besetzte Datenbestände schnell und effizient abzurufen, ist es oftmals von Vorteil, eine Tabelle als Zwischenspeicher bei einer langsamen Datenbanksuche zu verwenden.
Beispielsweise sind in den folgenden Zeilen die mit strPlanetName
verbundenen Angaben
erforderlich. Beim ersten Aufruf verfügt dieser Code noch nicht über eine abgebildete
Version des angeforderten Planeten, so daß man ihn mit GetPlanetFromSlowDB
suchen muß. Da der Code dann den abgerufenen Planeten in der Tabelle
mapPlanetDetails
speichert, lassen sich beim nächsten Aufruf mit demselben
strPlanetName
die Angaben schnell aus der zwischengespeicherten Version abrufen:
CPlanetDets* pMyPlanet = NULL;
if (mapPlanetDetails.Lookup(strPlanetName, (CObject*&)pMyPlanet) == FALSE)
{
pMyPlanet = GetPlanetFromSlowDB(strPlanetName);
mapPlanetDetails.SetAt(strPlanetName,pMyPlanet);
}
return pMyPlanet;
Dieses Verfahren ist leicht zu implementieren und kann die Geschwindigkeit der Anwendung erhöhen, wenn man mit langsamen Abfragegeräten wie etwa Datenbanken oder Dateien arbeitet.
Vielleicht möchten Sie die Auflistungsklassen anpassen, um Ihre eigenen Objekte anstelle
der allgemeinen, von CObject
abgeleiteten Klassen zu verwenden. Die Anpassung
bietet mehrere Vorteile, da man ein Feld, eine Liste oder eine Tabelle erstellen
kann, die nur den speziellen Typ des Objekts akzeptiert und zurückgibt. Wenn man
versehentlich versucht, die falsche Objektart in ein benutzerdefiniertes Feld, eine Liste
oder eine Tabelle hinzuzufügen, löst der Compiler eine Fehlermeldung aus, um Sie
darüber zu informieren. Der andere Vorteil besteht darin, daß man keine Typumwandlungen
von allgemeinen Zeigern auf CObject*
(das heißt, von einem CObArray
)
zurück auf das verwendete Objekt vornehmen muß.
Diese Art der Anpassung bezeichnet man als Typsicherheit. In großen Programmen
kann es unschätzbar sein, bei versehentlichen Zuweisungen der falschen Klasse zu
stoppen. Mit einer Gruppe von Vorlagen, CArray
, CList
und CMap
, kann man in einfacher
Weise ein Array, eine Liste oder eine Tabelle erstellen, um Objekte nur des spezifizierten
Typs zu speichern, zu verwenden und zurückzugeben. Vorlagen (Templates)
sind ein kompliziertes Thema, aber Sie brauchen Vorlagen nicht selbst zu schreiben.
Die in der Header-Datei afxtempl.h
definierten und von MFC bereitgestellten Vorlagen
genügen für diese typsicheren Auflistungsklassen. In bezug auf den vorliegenden
Abschnitt stellt man sich Templates einfach als große Makros vor, die beim Kompilieren
auf der Basis Ihrer Parameter eine Menge Code generieren.
Die Vorlagen bieten den Zugriff auf alle normalen Funktionen in den Feld-, Listen-
oder Tabellenklassen, die in den vorherigen Abschnitten behandelt wurden. Statt allerdings
die auf dem allgemeinen Objekt CObject
basierenden Parameter und Rückgabewerte
zu verwenden, können Sie Ihre eigenen Typen als Parameter und Rückgabewerte
definieren.
Um die Vorlagen in Ihrem Programm einzusetzen, müssen Sie die folgende Header-
Zeile in alle Module (.cpp
/.h
-Dateien) einbinden, die auf die Vorlagendefinitionen zurückgreifen:
#include "afxtempl.h"
Sie können dann Ihre eigene benutzerdefinierte, typsichere Klasse gemäß der folgenden Vorlagensyntax für ein Array von benutzerdefinierten Objekten definieren:
CArray<CMyCustomClass*, CMyCustomClass *>myCustomClassArray;
Die Symbole <
und >
in der obigen Definition sind als spitze Klammern (und nicht als
Bedingungsoperatoren für Größer als bzw. Kleiner als) zu interpretieren. Die obige
Zeile verwendet die Vorlage CArray
, um eine Instanz von myCustomClassArray
zu erzeugen.
Der erste Parameter CMyCustomClass*
spezifiziert Typen von Objektzeigern,
die das Array zurückgeben soll, wenn man mit GetAt
und anderen Zugriffsfunktionen
arbeitet. Der zweite Parameter CMyCustomClass*
legt den Typ fest, der für die Definitionen
der Eingabeparameter zu verwenden ist. Dann akzeptieren alle Funktionen, die
Objekte speichern, wie etwa SetAt
und Add
, nur Zeiger auf Objekte der speziellen
CMyCustomClass
.
Zum Beispiel kann man ein Array erzeugen, das nur Zeiger auf die spezielle Klasse
CPlanetDets
übernimmt und zurückgibt. Die Klasse ist wie folgt definiert (und implementiert):
class CPlanetDets : public CObject
{
public:
CPlanetDets(double dDiameter, double dGravity, double dDistFromSun, double
dSidereal):
m_dDiameter(dDiameter), m_dGravity(dGravity),
m_dDistFromSun(dDistFromSun), m_dSidereal(dSidereal) {}
double m_dDiameter,m_dGravity,m_dDistFromSun,m_dSidereal;
};
Um ein typsicheres auf CArray
basierendes Array namens myPlanetArray
zu deklarieren,
können Sie dann den folgenden Code schreiben:
CArray<CPlanetDets*, CPlanetDts*> myPlanetArray;
Diese Zeile deklariert, daß myPlanetArray
nur Zeiger auf ein CPlanetDets
-Objekt akzeptiert
und Zeiger auf ein CPlanetDets
-Objekt zurückgibt. Das neue Array kann man
dann wie folgt einsetzen:
myPlanetArray.Add(new CPlanetDets (4878, 0.054, 57.91, 87.969));
myPlanetArray.Add(new CPlanetDets (12100, 0.815, 108.21, 224.701));
myPlanetArray.Add(new CPlanetDets (12756, 1.000, 149.60, 365.256));
for(int i=0;i<myPlanetArray.GetSize();i++)
TRACE("Durchmesser = %f\n", myPlanetArray[i]->m_dDiameter);
Diese Zeilen erzeugen drei neue Objekte vom Typ CPlanetDets
und fügen sie in das
Array ein. Die letzte Zeile zeigt den Durchmesser im Makro TRACE
an, ohne daß man
den Typ des Rückgabewerts von myPlanetArray[i]
umwandeln muß, da es sich bereits
um einen Zeiger auf den Typ CPlanetDets*
handelt.
Allerdings vergessen Sie vielleicht im Zuge der Programmentwicklung die genaue Natur
von myPlanetArray
und versuchen, statt dessen ein CStatic
-Objekt hinzuzufügen:
myPlanetArray.Add(new CStatic());
Zum Glück bemerkt der Compiler diese Übertretung und löst einen Compiler-Fehler wie etwa den folgenden aus:
... error C2664: 'Add' : Konvertierung des Parameters 1 von 'class CStatic *' in
'class CPlanetDets *' nicht moeglich
Der Fehler bliebe jedoch unbemerkt, wenn man ein CObArray
verwendet hätte, um die
Planetendaten zu speichern:
CObArray myPlanetArray;
Das CStatic
-Objekt läßt sich ohne weiteres zusammen mit den CPlanetDets
-Objekten
speichern, bewirkt aber unvorhersehbares Unheil, wenn man versucht, das CStatic
-
Objekt abzurufen und annimmt, daß es sich um ein CPlanetDets
-Objekt handelt.
Die Vorlage zur Generierung von typsicheren Listen ist CList
. Sie weist die gleiche
allgemeine Form wie CArray
auf:
CList<CMyCustomClass*, CMyCustomClass *> myCustomClassList;
Auch hier ist der erste Parameter der erforderliche Rückgabetyp des Objekts. Der zweite Parameter spezifiziert die akzeptierten Objekttypen für Funktionen, die Elemente für die Speicherung akzeptieren.
Alle Funktionen, die es für Listen gibt, sind auch für Ihre eigenen speziellen typsicheren benutzerdefinierten Listen verfügbar. Auch diese Funktionen prüfen und geben die spezifizierten Typen zurück. Demzufolge sieht der äquivalente Code mit einer Listenklasse für das Speichern der Planetendaten etwa folgendermaßen aus:
CList<CPlanetDets*,CPlanetDets*> myPlanetList;
myPlanetList.AddTail(new CPlanetDets (4878, 0.054, 57.91, 87.969));
myPlanetList.AddTail(new CPlanetDets (12100, 0.815, 108.21, 224.701));
myPlanetList.AddTail(new CPlanetDets (12756, 1.000, 149.60, 365.256));
POSITION pos = myPlanetList.GetHeadPosition();
while(pos) TRACE("Durchmesser = %f\n", myPlanetList.GetNext(pos)->m_dDiameter);
Die Vorlage für benutzerdefinierte Tabellen unterscheidet sich von Listen und Arrays darin, daß vier Parameter erforderlich sind: ein Eingabe- und ein Rückgabewert sowohl für den Schlüssel als auch den Elementwert. Die allgemeine Form lautet damit:
CMap<MyType, MyArgType, CMyCustomClass *, CMyCustomClassArg *> myCustomClassMap;
Der erste Parameter, MyType
, legt den intern gespeicherten Schlüsselwert für jede Tabellenzuordnung
fest. Das kann einer der Basistypen wie int
, WORD
, DWORD
, double
,
float
oder CString
sein oder ein Zeiger auf einen eigenen speziellen Typ.
Der zweite Parameter, MyArgType
, spezifiziert den Argumenttyp, der bei der Übergabe
der Schlüsselwerte in und aus Tabellenfunktionen zu verwenden ist.
Mit dem dritten Parameter, CMyCustomClasss *
, legt man fest, wie die internen Elementwerte
zu speichern sind (als spezielle typsichere Zeiger auf Ihre Objekte).
Der vierte Parameter, CMyCustomClassArg *
, spezifiziert den Argumenttyp, der für die
Übergabe der Elementwerte in und aus den Tabellenfunktionen zu verwenden ist. Um
beispielsweise die Daten der Planeten mit deren Namen zu verbinden, könnte man
folgendes kodieren:
CMap<CString, LPCSTR, CPlanetDets*, CPlanetDets*> myPlanetMap;
myPlanetMap.SetAt("Merkur",
new CPlanetDets(4878, 0.054, 57.91, 87.969));
myPlanetMap.SetAt("Venus",
new CPlanetDets(12100, 0.815, 108.21, 224.701));
myPlanetMap.SetAt("Erde",
new CPlanetDets(12756, 1.000, 149.60, 365.256));
CPlanetDets* pPlanet = NULL;
if (myPlanetMap.Lookup("Venus", pPlanet))
TRACE("Durchmesser = %f\n", pPlanet->m_dDiameter);
Die Tabellendeklaration gibt an, daß die Objekte intern als CStrings
zu speichern
sind, aber LPCSTR
(Zeiger auf konstante Zeichenarrays) als Übergabewerte in und aus
der Tabelle zu verwenden sind. Die Angaben zu den Planeten selbst werden sowohl
intern gespeichert als auch als Zeiger auf CPlanetDets
-Objekte zugänglich gemacht
(wie zum Beispiel als CPlanetDets*
).
Da Windows eine grafisch orientierte Umgebung darstellt, muß man oftmals Punktpositionen,
Rechtecke und Größen speichern. Drei MFC-Klassen unterstützen das Speichern
und Manipulieren dieser Koordinaten: CPoint
, CRect
und CSize
. Jede Klasse
verfügt über mehrere Elementfunktionen und überladene Operatoren, die den größten
Teil der Arbeit beim Addieren, Konstruieren und Finden von Ableitungen dieser Koordinaten
realisieren.
Darüber hinaus verstehen verschiedene MFC- und GDI-Funktionen diese Typen oder die zugrundeliegenden Typen als Parameterwerte, so daß man keine umständlichen Operationen auszuführen hat, um Werte an diese Funktionen zu übergeben.
CPoint
kapselt eine POINT
-Struktur, die lediglich eine x- und eine y-Position speichert,
um einen Punkt auf einer zweidimensionalen Oberfläche darzustellen. Auf die Elemente
x
und y
kann man immer direkt zugreifen, um deren aktuelle Werte zu erhalten
oder zu setzen:
CPoint ptEins;
ptEins.x = 5;
ptEins.y = 20;
TRACE("Koordinate = (%d, %d)\n", ptEins.x, ptEins.y);
Diese Werte setzen Sie, wenn Sie ein CPoint
-Objekt
konstruieren, indem Sie die Werte an einen der verschiedenen Konstruktoren von
CPoint
übergeben, wie sie Tabelle F.4 zeigt.
Zum Beispiel können Sie die letzten Beispielzeilen durch die folgenden ersetzen und das gleiche Ergebnis erzielen:
CPoint ptEins(5,20);
TRACE("Koordinate = (%d, %d)\n", ptEins.x, ptEins.y);
Einer der nützlichsten Aspekte der Klasse CPoint
sind die vielen überladenen Operatoren.
Indem man die Operatoren +
, -, +=
und -=
mit anderen CPoint
-, CRect
- oder
CSize
-Objekten verwendet, kann man Koordinatenpaare zu/von anderen Koordinatenpaaren,
Rechtecken oder Größen addieren/subtrahieren. Der längere herkömmliche
Weg, um zwei Punkte voneinander zu subtrahieren, um einen dritten zu erhalten,
sieht zum Beispiel folgendermaßen aus:
CPoint ptEins(5,20);
CPoint ptZwei(25,40);
CPoint ptDrei;
ptDrei.x = ptZwei.x - ptEins.x;
Das Ganze läßt sich mit den überladenen Operatoren folgendermaßen vereinfachen:
CPoint ptEins(5,20);
CPoint ptZwei(25,40);
CPoint ptDrei = ptZwei - ptEins;
Man kann auch die Koordinaten des einen Punktes zu einem anderen addieren:
ptZwei += ptEins;
Mit den überladenen logischen Operatoren ==
und !=
lassen sich auch Vergleiche ausführen.
Um zum Beispiel zu prüfen, ob ptZwei
sowohl im x
- als auch im y
-Wert mit
ptEins
gleich ist, schreibt man folgenden Code:
if(ptEins == ptZwei) TRACE("Die Punkte sind identisch.");
Die Funktion Offset
addiert einen Verschiebungswert (Offset), der durch die übergebenen
x
- und y
-Werte, eine CPoint
-Klasse, eine POINT
-Struktur, eine CSize
-Klasse
oder eine SIZE
-Struktur spezifiziert ist. Demzufolge sind die beiden folgenden Zeilen
funktionell identisch:
ptEins.Offset(75, -15);
ptEins-=CPoint(-75, 15);
Die Klasse CRect
kapselt eine
RECT
-Struktur, um zwei Koordinatenpaare aufzunehmen, die ein Rechteck
durch die Punkte der linken oberen und unteren rechten Ecke beschreiben. Ein
CRect
-Objekt können Sie mit mehreren Konstruktoren erstellen,
wie sie Tabelle F.5 zeigt.
Nachdem Sie ein CRect
-Objekt konstruiert haben, können Sie einzeln auf die Elemente
top
, left
, bottom
und right
zugreifen, indem Sie die Typumwandlung (LPRECT)
verwenden, um die Werte in eine RECT
-Struktur wie in den folgenden Zeilen umzuwandeln:
CRect rcEins(15,15,25,20);
((LPRECT)rcEins)->bottom += 20;
TRACE("Rechteck: (%d,%d)-(%d,%d)",
((LPRECT)rcEins)->left,((LPRECT)rcEins)->top,
((LPRECT)rcEins)->right,((LPRECT)rcEins)->bottom);
Alternativ können Sie auf die Elemente entweder über den CPoint
für oben links oder
den CPoint
für unten rechts zugreifen. Die Funktionen TopLeft
und BottomRight
liefern
Referenzen auf diese Elementobjekte zurück. Wenn man entweder auf den Punkt
oben links oder unten rechts zugreift, kann man die Punkte dann mit einer der
CPoint
-Funktionen aus dem vorherigen Abschnitt manipulieren. Beispielsweise sind
die folgenden Zeilen funktionell zu den vorherigen identisch, unterscheiden sich aber
darin, daß sie das Rechteck mit CPoint
-Objekten konstruieren und darauf zugreifen:
CRect rcEins(CPoint(15,15),CPoint(25,20));
rcEins.BottomRight().y += 20;
TRACE("Rechteck: (%d,%d)-(%d,%d)",
rcOne.TopLeft().x,rcOne.TopLeft().y,
rcOne.BottomRight().x,rcOne.BottomRight().y);
Mit der Funktion SetRect
können Sie auch die Koordinaten setzen, wobei Sie vier Integer-Werte
für die x- und y-Koordinaten der oberen linken und der unteren rechten
Ecke übergeben. Die Funktion SetRectEmpty
setzt alle Koordinaten auf null, um ein
NULL
-Rechteck zu erzeugen. Die Funktion IsRectNull
liefert TRUE
, wenn sie auf einem
derartigen NULL
-Rechteck aufgerufen wird, und IsRectEmpty
gibt TRUE
zurück, wenn
sowohl die Breite als auch die Höhe gleich null sind (selbst wenn einzelne Werte ungleich
null sind).
Mehrere Hilfsfunktionen unterstützen die Berechnung verschiedener Aspekte der
Rechteckgeometrie. Die Breite und Höhe kann man mit den Funktionen Width
bzw.
Height
ermitteln. Beide Funktionen liefern den diesbezüglichen Integer-Wert zurück.
Alternativ kann man ein CSize
suchen, das sowohl Breite als auch Höhe repräsentiert,
indem man die Funktion Size
aufruft. Beispielsweise zeigt die folgende Zeile die
Breite und Höhe des Rechtecks rcEins
an:
TRACE("Breite = %d, Höhe = %d\n", rcEins.Width(), rcEins.Height());
Oftmals muß man den Punkt im Zentrum des Rechtecks kennen. Dazu kann man die
Funktion CenterPoint
aufrufen, die ein CPoint
-Objekt zurückgibt, das den Mittelpunkt
des Rechtecks darstellt.
Das folgende Beispiel bestimmt mit dieser Funktion den Mittelpunkt des Client-Bereichs eines Fensters und zeichnet an dieser Stelle einen Punkt:
CRect rcClient;
GetClientRect(&rcClient);
dc.SetPixel(rcClient.CenterPoint(),0);
Mit den Funktionen UnionRect
und InterSectRect
kann man auch die Vereinigungs-
bzw. Schnittmenge zweier Rechtecke ermitteln. Beide Funktionen übernehmen zwei
Quellrechtecke als Parameter und setzen die Koordinaten des aufrufenden CRect
-Objekts
auf die Vereinigung oder den Schnitt. Die Vereinigung ist das kleinste Rechteck,
das die beiden Quellrechtecke umschließt. Der Schnitt ist das größte Rechteck, daß
von beiden Quellrechtecken umschlossen wird. Die Zeichnung in Abbildung F.1 zeigt
Vereinigung und Schnitt zweier Quellrechtecke mit den Bezeichnern A
und B
.
Abbildung F.1: Vereinigungs- und Schnittmenge zweier Rechtecke
Die folgenden Zeilen berechnen den Schnitt und die Vereinigung der Quellrechtecke
rcEins
und rcZwei
:
CRect rcEins(10,10,100,100);
CRect rcZwei(50,50,150,200);
CRect rcUnion, rcIntersect;
rcUnion.UnionRect(rcEins, rcZwei);
rcIntersect.IntersectRect(rcEins, rcZwei);
Wenn Sie diesen Code ausführen, wird rcUnion
auf die Koordinaten (10,10)-
(150,200) und rcIntersect
auf die Koordinaten (50,50)-(100,100) gesetzt.
Mit der Funktion SubtractRect
kann man ein Rechteck von einem anderen abziehen.
Das Ergebnis ist das kleinste Rechteck, das alle Punkte enthält, die nicht von den beiden
Quellrechtecken geschnitten werden (oder der kleinste nicht überlappende Bereich).
Wenn Sie beispielsweise die folgenden Zeilen in eine OnPaint
-Behandlungsroutine
einfügen, können Sie sich die Wirkung von SubtractRect
ansehen. Der Code
zieht rcZwei
von rcEins
ab und liefert rcDrei
. Das Ergebnis der Subtraktion ist der
Bereich, der am unteren Rand der Zeichnung in Blau dargestellt ist, wie es Abbildung
F.2 zeigt.
CRect rcEins(10,10,220,220), rcZwei(50,50,150,260), rcDrei;
rcDrei.SubtractRect(rcZwei, rcEins);
dc.FillSolidRect(rcEins, RGB(255,0,0)); // Rot
dc.FillSolidRect(rcZwei, RGB(0,255,0)); // Grün
dc.FillSolidRect(rcDrei, RGB(0,0,255)); // Blau
Wenn Sie diesen Code ausführen, enthält das resultierende Rechteck rcDrei
die Koordinaten
(50,220)-(150,26).
Abbildung F.2:
Die Wirkung einer Subtraktionsoperation auf zwei teilweise überlappende Rechtecke
Die Größe eines Rechtecks läßt
sich mit den Funktionen InflateRect
und DeflateRect
vergrößern bzw. verkleinern. Beide Funktionen haben mehrere Formen,
die verschiedene Typen von Parametern gemäß Tabelle F.6 akzeptieren.
Der folgende Beispielcode vergrößert rcEins
und verkleinert rcZwei
:
CRect rcEins(10,10,100,100);
CRect rcZwei(50,50,150,200);
rcEins.InflateRect(5,5);
rcZwei.DeflateRect(10,20,30,40);
Die Ausführung dieser Zeilen setzt rcEins
auf die Koordinaten (5,5)-(105,105) und
rcZwei
auf die Koordinaten (60,70)-(120,160).
Eine Trefferprüfung läßt sich ausführen, indem man ermittelt, ob ein bestimmter
Punkt (etwa bei einem Mausklick) innerhalb der Grenzen eines Rechtecks liegt. Dazu
ruft man die Funktion PtInRect
auf und übergibt den zu testenden Punkt. Wenn der
Punkt innerhalb des Rechtecks liegt, gibt die Funktion TRUE
zurück, andernfalls FALSE
.
In den folgenden Zeilen wird die Meldung Treffer! - ptTest1
angezeigt, da ptTest1
innerhalb des Testbereichs von rcTestArea
liegt, während ptTest2
nicht in diesen Bereich
fällt. Demzufolge liefert PtInRect
den Wert TRUE
für ptTest1
und FALSE
für
ptTest2
:
CRect rcTestArea(10,20,440,450);
CPoint ptTest1(200,200), ptTest2(500,500);
if (rcTestArea .PtInRect(ptTest1)) AfxMessageBox("Treffer! - ptTest1");
if (rcTestArea .PtInRect(ptTest2)) AfxMessageBox("Treffer! - ptTest2");
Für CRect
-Objekte gibt
es verschiedene überladene Operatoren, die in Tabelle F.7 aufgeführt
sind.
Die folgenden Zeilen zeigen, wie man mit überladenen CRect
-Operatoren das Rechteck
rcStart
manipuliert:
CRect rcStart(10,10,100,100);
rcStart = rcStart + CPoint(5,5);
rcStart -= CSize(5,5);
rcStart += CRect(1,2,3,4);
if (rcStart == CRect(9,8,103,104)) AfxMessageBox("TRUE");
Die letzte Bedingung liefert TRUE
, weil die Koordinaten nach Ausführung der Anweisungen
auf (9,8)-(103,104) gesetzt sind. Demzufolge erscheint das Meldungsfeld.
Die Klasse CSize
kapselt die Struktur SIZE
und bietet verschiedene Konstruktoren und
überladene Operatoren, mit denen sich die internen Werte cx
und cy
, die eine Größe
definieren, manipulieren lassen. Tabelle F.8 zeigt die Konstruktoren, mit denen man
eine Instanz eines CSize
-Objekts erzeugen kann.
Die Elemente cx
und cy
kann man direkt manipulieren:
CSize tstSize(10,10);
tstSize.cx = tstSize.cy * 2;
Die einzigen Funktionen, die die Klasse CSize
bietet, sind die überladenen Operatoren
gemäß Tabelle F.9.
Diese Operatoren kann man wie normale arithmetische Operatoren einsetzen. Sie beeinflussen
sowohl cx
als auch cy
, wie es die folgenden Zeilen zeigen, die den Inhalt
von tstSize
manipulieren:
CSize tstSize(10,15);
tstSize += tstSize + tstSize - CSize(1,2);
if (tstSize == CSize(29,43)) AfxMessageBox("TRUE");
Bei Ausführung des Codes zeigt das Meldungsfeld TRUE
, weil tstSize
am Ende eine
Größe von 29 mal 43 hat.
In vielen Anwendung sind Datums- und Zeitwerte zu speichern. Manchmal muß man die verstrichene Zeit und Zeitspannen zwischen gespeicherten Datums-/Zeitwerten berechnen und diese Werte in verständliche Zeichenfolgen formatieren können.
MFC stellt vier Klassen bereit, um Datum und Uhrzeit manipulieren und speichern zu
können. Ursprünglich gab es nur zwei Klassen: CTime
und CTimeSpan
, die auf dem unter
UNIX üblichen System time_t
(langer 4 Byte-Wert) basieren (die Anzahl der seit
1970 verstrichenen Sekunden). Allerdings erweist sich die Auflösung von 1 Sekunde
und der begrenzte Datumsbereich zwischen 1970 und 2038 bei vielen Anwendungen
als unzureichend. Aus diesen Gründen wurden die beiden neuen Klassen COleDateTime
und COleDateTimeSpan
eingeführt, auf die man vorzugsweise statt CTime
und CTimeSpan
in neueren Anwendungen zurückgreifen sollte.
COleDateTime
basiert auf einer zugrundeliegenden DATE
-Struktur (die eigentlich nur ein
double
-Wert ist). Durch die größere Kapazität dieses Typs kann COleDateTime
einen
Datumsbereich zwischen dem 1. Januar 100 und dem 31. Dezember 9999 bei einer
Auflösung von 1 Millisekunde abdecken. Die Differenz zwischen zwei COleDateTime
-
Werten läßt sich mit dem COleDateTimeSpan
-Objekt darstellen und manipulieren.
Aufgrund der Ähnlichkeit zwischen der Klasse CTime
und der neueren COleDateTime
beschreiben die folgenden Abschnitte lediglich COleDateTime
, auch wenn viele der
Funktionen in den Versionen von CTime
identisch sind.
Die Verbindung von COleDateTime
mit OLE ergibt sich aus der Tatsache, daß man
diese Klasse mit der Struktur VARIANT
einsetzen kann, die man häufig in der OLE-Automatisierung
antrifft. Durch den breiten Bereich von Datums-/Zeitwerten muß COleDateTime
insbesondere in OLE-Umgebungen in der Lage sein, zwischen all diesen
verschiedenen Typen konvertieren zu können. Diese Unterstützung spiegelt sich in
den zahlreichen Konstruktorformen wider, die Tabelle F.10 zeigt.
Wenn Sie COleDateTime
mit einem gültigen Datums-/Zeitwert konstruiert haben, ist
das Objekt mit einem Statusflag für den gültigen Zustand markiert (COleDateTime::valid
). Andernfalls ist das Statusflag ungültig (COleDateTime::invalid
). Dieser
Status läßt sich mit der Elementfunktion GetStatus
ermitteln. Die Funktion liefert den
relevanten Flagwert zurück. Man kann das Flag auch explizit durch Übergabe des gewünschten
Wertes an die Funktion SetStatus
setzen.
Das Statusflag wird auch aktualisiert, wenn man Datums-/Zeitwerte in das Objekt mit
Hilfe der Funktion SetDateTime
setzt. Die Funktion übernimmt sechs Parameter für
das Jahr (100 bis 9999), den Monat (1 bis 12), den Tag (1 bis 31), die Stunde (0 bis
23), die Minute (0 bis 59) und die Sekunde (0 bis 59). Die Datums- oder Zeitkomponenten
lassen sich auch separat festlegen: mit der Funktion SetDate
, der man Jahr,
Monat und Tag übergibt, bzw. mit der Funktion SetTime
, der man nur die Werte für
Stunde, Minute und Sekunde übergibt.
Die aktuelle Systemzeit läßt sich mit der statischen Funktion GetCurrentTime
ermitteln
und mit dem überladenen Operator =
in ein COleDateTime
-Objekt übertragen:
COleDateTime dtCurrent;
dtCurrent = COleDateTime::GetCurrentTIme();
Nach Ausführung dieser Zeilen enthält dtCurrent
die aktuelle Systemzeit (Datum und
Uhrzeit) des Computers.
Die gleichen Werte (in den gleichen Bereichen) lassen sich mit den Funktionen GetYear
, GetMonth
, GetDay
, GetHour
, GetMinute
und GetSecond
abrufen. Weiterhin gibt es
die abgeleiteten Funktionen GetDayOfWeek
und GetDayOfYear
. Die Funktion GetDayOfWeek
liefert den Wochentag mit den Werten 1 bis 7 zurück, wobei die 1 für
Sonntag steht. Die Funktion GetDayOfYear
liefert einen Wert im Bereich von 1 bis
366, beginnend mit dem 1. Januar.
Mit der Funktion Format
läßt sich ein CString
erstellen, der die Werte im gewünschten
Anzeigeformat ausgibt. Diese Funktion gehört mit zu den nützlichsten COleDateTime
-
Funktionen, da man verschiedenartige - in Tabelle F.11 aufgeführte - Formatcodes
übergeben kann, um das genaue Format festzulegen. Die Codes übergibt man entweder
als String oder als Bezeichner von Stringressourcen. Um verschiedene Aspekte
der Formatierung zu berücksichtigen, kann man verschiedene Einzelcodes verbinden.
Die Werte werden außerdem durch die Ländereinstellungen modifiziert. Das bezieht sich auf die Namen von Tagen und Monaten, die Schreibweise des Datums und die Angabe der Uhrzeit (24 Stunden, AM/PM).
Die folgenden Zeilen generieren ein Meldungsfeld, das Datum und Uhrzeit des Computers anzeigt:
COleDateTime dtCurrent;
dtCurrent = COleDateTime::GetCurrentTime();
AfxMessageBox(dtCurrent.Format("Heute ist %a %b %d, %Y"));
Die Ausführung dieser Zeilen zeigt die Systemzeit im Meldungsfeld mit dem folgenden Format an:
Heute ist Di Apr 20, 1999
Mit COleDateTime
können Sie versuchen, ein Datum und eine Uhrzeit zu bestimmen,
indem Sie ParseDateTime
aufrufen und einen zu analysierenden String sowie ein Flag
übergeben, um festzulegen, daß nur das Datum bzw. die Uhrzeit gefordert wird. ParseDateTime
untersucht dann den String nach der Uhrzeit im Format HH:MM:SS
und einem
Datum im Format DD/MM/YYYY
oder in einem langen Format wie January 18th,
1998. Wenn man nur nach der Uhrzeit suchen möchten, kann man als zweiten Flag-
Parameter den Wert VAR_TIMEVALUEONLY
übergeben oder alternativ
VAR_DATEVALUEONLY
nur für das Datum. Soll lediglich das für den Computer gültige
Standardformat in die Prüfung einbezogen werden und nicht die vom Benutzer überschriebenen
Formate, übergeben Sie als Flag den Wert LOCALE_NOUSEROVERRIDE
.
Es gibt auch mehrere überladene Operatoren, mit denen man COleDateTimeSpans
addieren
und subtrahieren sowie Datums-/Zeitwerte mit anderen Datums-/Zeitwerten
vergleichen kann, wie es aus Tabelle F.12 hervorgeht.
Ein COleDateTimeSpan
-Objekt kann Differenzen zwischen zwei COleDateTime
-Objekten
speichern. Die Differenz läßt sich bilden, indem man ein COleDateTime
-Objekt von
einem anderen subtrahiert oder eine der COleDateTimeSpan
-Konstruktorformen gemäß
Tabelle F.13 verwendet.
Nachdem Sie ein COleDateTimeSpan
-Objekt erzeugt haben, können Sie dessen Status
mit den Funktionen GetStatus
und SetStatus
genau wie beim COleDateTime
-Objekt
prüfen bzw. setzen. Die einzigen Unterschiede bestehen darin, daß die Flags mit
COleDateTimeSpan::valid
und COleDateTimeSpan::invalid
benannt sind.
Eine Zeitspanne kann man auch setzen, indem man die Anzahl der Tage, Stunden,
Minuten und Sekunden als Integer-Parameter an die Funktion SetDateTimeSpan
übergibt.
Diese Werte lassen sich dann aus einem gültigen COleDateTimeSpan
-Objekt mit
den Funktionen GetDays
, GetHours
, GetMinutes
und GetSeconds
abrufen, die alle
Long-Werte zurückgeben, um die einzelnen Teile des Wertes für die Zeitspanne darzustellen.
Die komplette Zeitspanne, ausgedrückt in Tagen, Stunden, Minuten oder Sekunden,
kann man mit den Funktionen GetTotalDays
, GetTotalHours
, GetTotalMinutes
bzw. GetTotalSeconds
in einem Double-Wert abrufen.
COleDateTimeSpan
-Werte kann man als Zeichenfolgen in der gleichen Weise wie
COleDateTime
-Werte formatieren, indem man einen Formatstring mit den passenden
Codes für Zeitspannen gemäß Tabelle F.11 übergibt.
Arithmetische Berechnungen mit COleDateTimeSpan
-Objekten unterstützen verschiedene
überladene Operatoren, mit denen man Zeitspannen addieren, subtrahieren und
in Bedingungen testen kann, wie es aus Tabelle F.14 hervorgeht.
Prüft, ob eine Zeitspanne kleiner oder gleich einer anderen ist. | |
Prüft, ob eine Zeitspanne größer oder gleich einer anderen ist. |
Der folgende Beispielcode zeigt, wie man zwei COleDateTime
-Ojekte mit dem überladenen
Minusoperator (-
) voneinander subtrahiert, um als Ergebnis eine COleDateTimeSpan
zu erhalten:
COleDateTime = dtMoonwalk;
dtMoonwalk = COleDateTime(1969, 7, 20, 0, 0, 0);
COleDateTimeSpan dtDiff = COleDateTime::GetCurrentTime() - dtMoonwalk;
CString strMessage;
strMessage.Format("Tage seit dem ersten Mondspaziergang: %d ",
(int)dtDiff.GetTotalDays());
AfxMessageBox(strMessage);
Vor mehreren Jahren beneideten die C-Programmierer heimlich ein (und nur ein)
Werkzeug, das BASIC-Programmierer zur Verfügung hatten: ausgeklügelte und einfache
Zeichenfolgenbehandlung. Bei C++ läßt sich diese Funktionalität natürlich nachbilden
und steht mit der MFC-Klasse CString
zur Verfügung.
Die Zeichenfolgenbehandlung ist in Anwendungen häufig gefragt, und Visual C++-
Anwendungen tendieren dazu, mit Instanzen von Objekten auf Basis der Klasse
CString
durchsetzt zu sein, um diese Aufgabe zu realisieren.
CString
-Objekte kann man in einfacher Weise als leerer String konstruieren oder initialisieren,
indem man einen der vielen verschiedenen Typen von Textdarstellungssystemen
an den Konstruktor übergibt. Die verschiedenen Formen der CString
-Konstruktion
sind in Tabelle F.15 aufgeführt.
Wenn Sie ein CString
-Objekt konstruiert haben, gibt es viele Möglichkeiten, um Text
hinzuzufügen oder zuzuweisen. Die überladenen Operatoren erlauben einfache Zuweisungen
über den Operator =
oder die Verkettung von zwei Strings mit den Operatoren
+
und +=
, wie es die folgenden Zeilen zeigen:
CString strTest;
strTest = "Mr Gorsky";
strTest = "Viel Glück " + strTest;
AfxMessageBox(strTest);
Dieses Beispiel setzt den String anfänglich auf "Mr Gorsky"
. Anschließend wird mit
dem Operator +
der Text "Viel Glück "
vorangestellt.
Die Länge eines Strings läßt sich mit der Elementfunktion GetLength
ermitteln. Die
Funktion liefert eine Ganzzahl zurück, die die momentane Anzahl der Zeichen im
String darstellt. Mit der Funktion IsEmpty
kann man auch testen, ob ein String leer ist.
Die Funktion liefert TRUE
zurück, wenn der String keine Zeichen enthält.
Die Funktion Empty
dient dazu, den Inhalt eines CString
-Objekts zu löschen. Das Objekt
weist dann die Länge null auf.
Viele Funktionen erfordern Strings im alten Stil von C und keine CString
-Objekte.
Die Typumwandlungen (const char *)
oder LPCTSTR
erlauben diesen Funktionen
den Zugriff auf den internen Puffer des CString
-Objekts als würde es sich um einen
nullterminierten C-String handeln (allerdings nur für Lesezugriff). Visual C++ wandelt
implizit den CString
in einen nullterminierten String um, wenn der Prototyp einer bestimmten
Funktion das erfordert. Da jedoch einige Funktionen einen void*
Prototyp
haben, übergibt der Compiler einen Zeiger auf das CString
-Objekt statt den erwarteten
nullterminierten String. Somit müssen Sie die Umwandlung (LPCTSTR)
auf dem
CString
-Objekt in eigener Regie erledigen.
Mit den Funktionen GetAt
und SetAt
kann man auf einen String als Array von Zeichen
zugreifen. GetAt
liefert das Zeichen an der als Parameter übergebenen (von 0 an
gerechneten) Position zurück. Die Funktion SetAt
setzt ein Zeichen (als zweiten Parameter)
an eine Position (erster Parameter) innerhalb der Länge des Strings. Anstelle
der Funktion GetAt
kann man auch mit dem Indexoperator [ ]
einen Zeichenwert
von einer bestimmten Position ermitteln. Beispielsweise tauschen die folgenden Zeilen
die Zeichen b
und g
aus, um den Rechtschreibfehler zu korrigieren:
CString strText("Rechtschreigunb ");
TCHAR ch1 = strText.GetAt(11);
strText.SetAt(11, strText[14]);
strText.SetAt(14, ch1);
Mit den überladenen Operatoren <
, <=
, ==
, !=
, >=
und >
kann man Strings gemäß der
lexikographischen Reihenfolge miteinander vergleichen. Bei Zeichenfolgenvergleichen
werden die ASCII-Codes miteinander verglichen, so daß Ziffern kleiner als Buchstaben
und Großbuchstaben kleiner als Kleinbuchstaben sind. Demzufolge bewirken
die folgenden Codezeilen, daß im Meldungsfeld TRUE
erscheint:
CString str1("123");
CString str2("ABC");
CString str3("abc");
CString str4("bcd");
if (str1 < str2 && str2 < str3 && str3 < str4) AfxMessageBox("TRUE");
Um den aktuellen String mit einem anderen zu vergleichen, kann man auch die Funktion
Compare
einsetzen. Die Funktion liefert null zurück, wenn zwei Strings gleich sind,
einen negativen Wert, wenn der aktuelle String kleiner als der getestete String ist,
oder einen positiven Wert, wenn der aktuelle String größer als der Teststring ist. Beispielsweise
bewirken die folgenden Zeilen, daß ein Meldungsfeld mit dem Text TRUE
erscheint:
CString strName("Peter");
if (strName.Compare("Piper")<0) AfxMessageBox("TRUE");
Dieser Vergleich beachtet auch die Groß-/Kleinschreibung der zu vergleichenden
Strings. Vergleiche ohne Unterscheidung der Groß-/Kleinschreibung lassen sich mit
der äquivalenten Funktion CompareNoCase
durchführen.
In der Sprache BASIC stehen drei nützliche Funktionen zur Manipulation von Strings
zur Verfügung: Mid$
, Left$
und Right$
. Diese Funktionen sind nun auch als Mid
, Left
und Right
in der Klasse CString
verfügbar. Als Ergebnis liefern die Funktionen Kopien
von Teilstrings zurück. Der Funktion Mid
übergibt man eine Anfangsposition und
optional die Anzahl der zu kopierenden Zeichen (fehlt das optionale Argument, liefert
die Funktion alle Zeichen). Die Funktion gibt dann einen anderen CString
mit dem
angegebenen Teilstring zurück. Mit der Funktion Left
extrahiert man eine Anzahl Zeichen
vom linken Teil eines Strings. Dabei übergibt man der Funktion die Anzahl der
gewünschten Zeichen. Die Funktion Right
liefert die angegebene Anzahl der Zeichen
von der rechten Seite des Strings (d.h. vom Ende des Strings an gerechnet) zurück.
Dazu das folgende Beispiel:
CString strText("Ich habe heute drei Segelschiffe gesehen");
TRACE("%s\n", strText.Left(8));
TRACE("%s\n", strText.Mid(15, 17));
TRACE("%s\n", strText.Right(7));
Die Ausgabe der drei TRACE
-Makros liefert:
Ich habe
drei Segelschiffe
gesehen
Das Wort heute
zwischen habe
und drei
erscheint nicht in der Trace-Ausgabe, da dieser
Teil des Strings überhaupt nicht extrahiert wird.
Mit den Funktionen MakeUpper
und MakeLower
ändert man alle Zeichen in einem
String in Groß- bzw. Kleinbuchstaben, während die Funktion MakeReverse
den String
in umgekehrter Zeichenfolge liefert.
Leerzeichen, Zeichen für neue Zeile und Tabulatoren lassen sich vom linken Teil eines
Strings mit der Funktion TrimLeft
entfernen. Auf der rechten Seite realisiert die Funktion
TrimRight
diese Aufgabe.
Mit den Funktionen Find
, ReverseFind
und FindOneOf
kann man nach bestimmten
Teilstrings oder Zeichen in einem String suchen.
Der Elementfunktion Find
übergibt man ein einzelnes Zeichen oder einen String, um
nach einem Vorkommen dieses Zeichens oder des Strings im Kontextstring zu suchen.
Wenn das Zeichen oder der Teilstring gefunden wurde, liefert die Funktion die
Position (gerechnet ab 0) zurück. Andernfalls lautet das Ergebnis -1
. Es kennzeichnet,
daß der Teilstring oder das Zeichen nicht vorhanden ist. Die folgenden Zeilen suchen
nach dem Wort »du« und zeigen den Teilstring ab dieser Position an:
CString strTest("Fang an, wenn du denkst");
int nPosn = strTest.Find("du");
if (nPosn!=-1) TRACE(strTest.Mid(nPosn) + "?");
Die Funktion ReverseFind
sucht nach einem bestimmten Zeichen (Teilstrings nicht
möglich) vom Ende eines Strings. Wird das Zeichen gefunden, liefert die Funktion die
Position bezüglich des Anfangs vom String (gerechnet ab Position 0) zurück, andernfalls
-1
.
Bei FindOneOf
kann man eine Anzahl von Zeichen übergeben, die in einem String zu
suchen sind. Zurückgegeben wird die Position des ersten Suchzeichens der Gruppe.
Das Ergebnis -1
gibt an, daß kein Zeichen gefunden wurde. Die folgenden Beispielzeilen
suchen nach den Zeichen d
, e
, k
und w
. Das w
wird zuerst gefunden, so daß der
String »wenn du denkst« in der Trace-Ausgabe erscheint:
CString strTest("Fang an, wenn du denkst");
int nPosn = strTest.FindOneOf("dekw");
if (nPosn!=-1) TRACE(strTest.Mid(nPosn));
Häufig setzt man CString
-Objekte ein, um Text vor der Ausgabe zu formatieren. Dazu
verwendet man die Funktion Format
, der man im ersten Parameter eine Gruppe von
Formatanweisungen als Prozentzeichencodes übergibt. Daran schließt sich die Übergabe
von Wertparametern an. Die Anzahl der Wertparameter muß der Anzahl der angegebenen
Formatcodes entsprechen. Einige dieser Formatflags und die jeweiligen
Parametertypen sind in Tabelle F.16 aufgelistet. Mehrere Codes kann man zu einem
Formatstring zusammenfassen, der dann im aufrufenden CString
-Objekt gespeichert
wird. Diesen Formatstring kann man auch als Stringressourcen-ID aus den Ressourcen
der Anwendung übergeben.
Das folgende Beispiel kombiniert einige der gebräuchlichen Typen, um ein Meldungsfeld mit dem Text »Es kommen 1609.340000 Meter auf eine Meile.« anzuzeigen:
CString strFormatMe;
char *szM = "Meter";
double dMPerMile = 1609.34;
strFormatMe.Format("Es kommen %f %s auf %d %s",
dMPerMile,szM,1,"Meile.");
AfxMessageBox(strFormatMe);
Mit zusätzlichen Formatangaben kann man für jedes Ausgabefeld die Breite, Genauigkeit und Ausrichtung spezifizieren. Diese Attribute sind nach dem Prozentzeichen aber noch vor dem (in Tabelle F.16 angegebenen) Typzeichen in folgendem Format zu schreiben:
%[Flag][Breite][.Genauigkeit]Typ
Für den Flag-Wert kann man die folgenden Zeichen angeben:
- Richtet das Feld linksbündig aus (ansonsten rechtsbündig).
+ Zeigt vor einer Zahl ein Plus- oder Minuszeichen an (per Vorgabe erscheint nur das Minuszeichen bei negativen Zahlen).
0 Füllt die Feldbreite mit Nullen auf.
Mit Breite
gibt man dann die Mindestzahl von Zeichen an, die das Feld anzeigen soll.
Der Parameter .Genauigkeit
bestimmt, wie viele Zeichen nach dem Dezimalpunkt
stehen sollen.
Den Text im vorherigen Beispiel kann man zum Beispiel so formatieren, daß »Es kommen +1609.3 Meter auf 1 Meile.« in der Ausgabe erscheint:
strFormatMe.Format("Es kommen %+.1f %s auf %d %s",
dMPerMile,szM,1,"Meile");