Dynamische Strings

Einführung

Zeichenketten wurden bereits eingeführt. Allerdings handelte es sich da vornehmlich um statische, d.h. Zeichenketten für die bei der Deklaration der benötigte Speicher angelegt und nicht mehr geändert wird und sich auf diese Weise wie statische Arrays verhalten. Zeichenketten werden aber nicht überall gleich interpretiert. So sind Zeichenketten in C++ normalerweise Zeichenketten, die mit einem '\0'-Zeichen "terminiert" werden, damit beim Auslesen bekannt ist, wo die Zeichenkette tatsächlich endet. Bei der Verwendung von COM wird vornehmlich der Datentyp 'BSTR' verwendet. Dieser unterscheidet sich von den typischen nullterminierenden Zeichenketten v.a. dadurch, dass vor der tatsächlichen Zeichenkette noch die Länge (in Anzahl der benötigten Bytes) gespeichert ist. Auf diese Weise lässt er sich in C++ im Prinzip normal wie eine gewöhnlicher String verwenden ist andersherum aber auch COM-konform und kann aufgrund dessen auch für andere Sprachen verwendet werden. Allerdings existiert noch ein wesentlicher Unterschied zwischen einem 'BSTR' und einem nullterminierendem String des Datentyps 'char'. Bei einem 'BSTR' handelt es sich um eine "UNICODE"- Zeichenkette, d.h. es wird für jedes Zeichen nicht ein sondern zwei Byte verwendet. Dies wiederum hat den Vorteil, dass der "UNICODE"-Zeichensatz alle real existierenden Zeichen erfasst. So umfasst er neben dem chinesischem, dem kyrillischem und dem griechischem Zeichensatz auch alle Sonderzeichen und ist international einheitlich festgelegt. Weiterhin vorteilhaft ist, dass der Zeichensatz, wie wir ihn hier kennen so untergebracht ist, dass sich die Werte eines einzelnen Zeichens in beiden Zeichensätzen nicht unterscheiden. So beträgt der Wert von 'A' hier genau wie im "UNICODE"-Zeichensatz 65. Der einzige Unterschied besteht darin, dass dieser Wert im "UNICODE"-Zeichensatz als ein Wert vom Datentyp 'unsigned short' gespeichert wird. Liegt also eine 'char*'-Zeichenkette vor, die nun in eine 'unsigned short*' Zeichenkette umgewandelt werden muss, dann muss einfach nur Wert für Wert übernommen werden, sofern es sich bei dieser Zeichenkette um eine Zeichenkette handelt, die aus unseren Schriftzeichen besteht. Im anderen Falle würde sich dei Konvertierung als schwieriger erweisen. Hier werde ich allerdings immer davon ausgehen, dass es sich um "geeignete" Zeichenketten handelt. Methoden, die also im folgenden die Konvertierungen durchführen gehen immer davon aus, dass es sich bei den verwendeten Zeichen nicht um kyrillische, griechische, chinesische oder andere Zeichen handelt. Da die Konvertierung immer von zwei Methoden durchgeführt werden soll, kann die Implementierung dieser Methoden aber jederzeit geändert werden, so dass alle Zeichenketten übersetzt werden und das Programm darauf entsprechend reagiert.  

Die vorgestellte String-Klasse soll ihre Informationen intern als "UNICODE"-String verwalten, bietet aber die Möglichkeit, diesen String umzuwandeln. Außerdem soll die Längenangabe des Strings nicht als eigene Membervariable gespeichert werden, sondern im Speicher direkt vor dem "UNICODE"-String stehen und somit alle Auflagen eines 'BSTR' erfüllen.

Der Rahmen

Die Klasse soll im folgenden 'CString' heißen und u.a. zwei statische und private Memberfunktionen ('_ANSItoUC' und _'UCtoANSI') besitzen, die die Konvertierung übernehmen. Die dabei übergebenen Parameter sollen aber jeweils gültige Längen besitzen. Innerhalb dieser Funktionen werden keine Strings erstellt. Des weiteren benötigt die Klasse eine Funktion zum Erstellen eines Strings beliebiger Größe ('_Create') und eine Vielzahl von Überladungen, die es ermöglichen, einen String aus einem bereits existierenden zu erstellen. Diese Funktion und ihre Überladungen werden ebenfalls als 'private' deklariert. Zudem müssen noch eine Vielzahl von Konstruktoren (darunter auch der Kopierkonstruktor) und Überladungen des Zuweisungsoperators '=' und ein Destruktor deklariert werden. Außerdem sollen zwei private Membervariablen ('m_wstr' und 'm_pcBytes') deklariert werden. Später folgen dann noch öffentliche Methoden, die es dem Benutzer dieser Klasse ermöglichen Strings nach Belieben zu erstellen, zu ändern und zu löschen. So könnte die Klassendeklaration bisher aussehen:

class CString
{
private:
	//Zeichenkette
	unsigned short* m_wstr;
	
	//Längenangabe in Bytes
	unsigned long* m_pcBytes;

	//String erstellen
	bool _Create(unsigned long = 0);
	bool _Create(const unsigned short*);
	bool _Create(const char*);
	bool _Create(const CString&);

	//Konvertierfunktionen
	static void _UCtoANSI(char*, const unsigned short*, char = '#');
	static void _ANSItoUC(unsigned short*, const char*);
public:
	//Konstruktoren
	CString(const unsigned short* = 0);
	CString(const char*);
	CString(unsigned short);
	CString(char);

	//Kopierkonstruktor
	CString (const CString&);

	//Destruktor
	~CString(void);

	//Zuweisungsoperatoren
	CString& operator = (const CString&);
	CString& operator = (const unsigned short*);
	CString& operator = (const char*);
	CString& operator = (unsigned short);
	CString& operator = (char);
	
};

Implementierung der Funktionen

Zunächst sollten die Methoden für die Konvertierung implementiert werden, da sie innerhalb der nächsten Methoden verwendet werden. Zunächst wird die Methode '_UCtoANSI' implementiert, welche eine "UNICODE"-Zeichenkette umwandelt. Alle Zeichen, die dabei auch innerhalb des anderen Zeichensatzes existieren werden direkt konvertiert. Nicht bekannte Zeichen (Werte größer als 127 bei 'char' oder 255 bei 'unsigned char') werden nicht berücksichtigt und der Zielstring bekommt an dieser Stelle ein Standardzeichen (hier willkürlich '#') zugewiesen. Die Implementierung sieht nun wie folgt aus:

//Konvertieren von UNICODE
void CString::_UCtoANSI(char* strTar, const unsigned short* wstrSrc, char Sign)
{
	//einiger Check: ungültige Zeiger
	if (!strTar || !wstrSrc) return;

	//Positionszeiger
	const unsigned short* pSrcPos = wstrSrc;
	char* pTarPos = strTar ;

	//Quellstring Zeichen für Zeichen durchlaufen
	while (*pSrcPos != L'\0')
	{
		//ungültiges Zeichen
		if (*pSrcPos & 0xFF00)	//Alternative: if (*pSrcPos > 255) 
		{*pTarPos = Sign;}
		else
		{*pTarPos = (char)*pSrcPos;}

		//Zeiger inkrementieren
		pTarPos++;
		pSrcPos++;
	}

	//'\0'-Zeichen noch übernehemen
	*pTarPos = '\0';
}

In Kombination mit der Einführung sollte der Quelltext keine weiteren Fragen aufwerfen. Es werden lediglich sowohl Quell- als auch Zielstring durchlaufen. Ist dabei der Wert eines Zeichens des Quellstrings ungültig, weil es oberhalb der 'char'-Grenze liegt, dann wird das Zeichen 'Sign' in den Zielstring kopiert. Am Ende wird dann nur noch das '\0'-Zeichen gesetzt.

Die andere Konvertiermethode erweist sich als noch einfacher. Sie sieht der vorigen sehr ähnlich. Es können allerdings keine ungültigen Werte auftreten. Hier nun die Implementierung:

//Konvertieren in UNICODE
void CString::_ANSItoUC(unsigned short* wstrTar, const char* strSrc)
{
	//einiger Check: ungültige Zeiger
	if (!wstrTar || !strSrc) return;

	//Positionszeiger
	const char* pSrcPos = strSrc;
	unsigned short* pTarPos = wstrTar ;

	//Quellstring Zeichen für Zeichen durchlaufen
	while (*pSrcPos != L'\0')
	{	
		//Zeichen kopieren
		*pTarPos = (char)*pSrcPos;

		//Zeiger inkrementieren
		pTarPos++;
		pSrcPos++;
	}

	//'\0'-Zeichen noch übernehemen
	*pTarPos = L'\0';
}

Nun sollte die Methoden zum Erstellen eines Strings folgen. Aus diesem Grunde sollte zunächst die Methode '_Create' und ihre Überladungen implementiert werden. Zuvor muss natürlich der Speicher freigegeben werden. Die Implementierung könnte also wie folgt aussehen:

//String erstellen
bool CString::_Create(unsigned long Length)
{
	//Speicher freigeben wenn nötig
	if (m_pcBytes) delete[] m_pcBytes;

	//Neue Reservierung notwendig
	if (Length == 0)
	{
		m_pcBytes = 0;
		m_wstr = 0;
		return false;
	}

	//Speichergröße ermitteln (inkl. '\0'-Zeichen und Speicher für Längenangabe)
	unsigned long cBytes = (Length + 1) * sizeof(unsigned short) + sizeof(unsigned long);

	//neuen Speicher reservieren
	unsigned char* pData = new unsigned char[cBytes];
	if (!pData)
	{
		m_pcBytes = 0;
		m_wstr = 0;
		return false;
	}

	//Member anpassen
	m_pcBytes = (unsigned long*)pData;
	*m_pcBytes = Length * sizeof(unsigned short);
	m_wstr = (unsigned short*)(m_pcBytes + 1);

	//'\0'-Zeichen am Ende setzen
	*(m_wstr + Length) = L'\0';

	return true;
	
}

Wie angekündigt wird hier zunächst der zuvor bestehende Speicher freigegeben. Anschließend wird berechnet wie viel Speicher anschließend reserviert werden muss. Die Größe des zu reservierenden Speichers hängt dabei zunächst von der gewünschten Länge der Zeichenkette ab. Da aber am Ende noch ein '\0'-Zeichen angehängt werden muss wird schon einmal ein Zeichen mehr benötigt. Außerdem müssen noch vier Bytes ('sizeof(unsigned long)' extra reserviert werden, da ja am Anfang des Speichers die Längenangabe unterkommen soll. Die berechnete Größe wird dann dazu verwendet den Speicher zu reservieren. Anschließend werden die beiden Membervariablen angepasst. Zunächst wird der Zeiger für die Längenangabe auf den ersten Byte des reservierten Speichers gesetzt in dem der Zeiger 'pData' in einen 'unsigned long*'-Zeiger gecastet und die Adresse anschließend an die Membervariable 'm_pcBytes' übergeben wird. Anschließend wird die Längenangabe im Speicher übernommen. Dabei wird das '\0'-Zeichen jedoch nicht berücksichtigt. Da diese Längenangabe jedoch eine Byteangabe ist, und jedes Zeichen zwei Byte ('sizeof(unsigned short)') einnimmt, muss als Längenangabe das Doppelte der übergebenen Länge verwendet werden. Direkt nach der Byteangabe soll dann der tatsächliche String folgen. Zu diesem Zweck bekommt also die Membervariable 'm_wstr' die Adresse direkt vier Bytes hinter der Adresse für die Byteangabe zugewiesen. Zum Schluss wird dann noch das letzte Zeichen der Zeichenkette mit dem '\0'-Zeichen initialisiert. (Das 'L' davor weist lediglich darauf hin, dass die folgende Zeichenkette bzw. wie in diesem Falle das Zeichen eine "UNICODE"-Zeichenkette bzw. ein "UNICODE"-Zeichen ist.)

Bei der zweiten Variante der Funktion '_Create', soll beim Erstellen nicht die Länge des Strings, sondern ein bereits existierender String in Form einer "UNICODE"-Zeichenkette übergeben werden.  Hier also die entsprechende Implementierung:

#include <memory.h>

//String erstellen
bool CString::_Create(const unsigned short* wstrSrc)
{
	//Speicher freigeben wenn nötig
	if (m_pcBytes) delete[] m_pcBytes;

	//Neue Reservierung notwendig?
	if (!wstrSrc)
	{
		m_pcBytes = 0;
		m_wstr = 0;
		return false;
	}

	//Länge des Strings ermitteln
	unsigned long Length = 0;
	const unsigned short* pSrcPos = wstrSrc;
	while (*pSrcPos++ != L'\0') Length++;

	//Speichergröße ermitteln (inkl. '\0'-Zeichen und Speicher für Längenangabe)
	unsigned long cBytes = (Length + 1) * sizeof(unsigned short) + sizeof(unsigned long);

	//neuen Speicher reservieren
	unsigned char* pData = new unsigned char[cBytes];
	if (!pData)
	{
		m_pcBytes = 0;
		m_wstr = 0;
		return false;
	}

	//Member anpassen
	m_pcBytes = (unsigned long*)pData;
	*m_pcBytes = Length * sizeof(unsigned short);
	m_wstr = (unsigned short*)(m_pcBytes + 1);

	//Speicher kopieren
	memcpy(m_wstr, wstrSrc, (Length + 1) * sizeof(unsigned short));

	return true;
	
}

Diese Überladung unterscheidet sich nicht wesentlich von der Vorversion. Es muss zunächst die Länge des übergebenen Strings ermittelt werden. Anschließend werden bis zum Schluss die gleichen Schritte durchgeführt, wie bei der Vorversion. Am Schluss wird dann nur noch die übergebene Zeichenkette kopiert.

Die Überladung, die einen 'char*'-String aufnehmen soll, muss diesen zunächst konvertieren, bevor er verwendet werden kann. Eine mögliche Implementierung wäre folgende:

//String erstellen
bool CString::_Create(const char* strSrc)
{
	//Länge des Strings ermitteln
	unsigned long Length = 0;
	const char* pSrcPos = strSrc;
	while (*pSrcPos++ != '\0') Length++;

	//String entsprechender Länge erstellen
	if (!_Create(Length)) return false;

	//Daten kopieren bzw. konvertieren
	_ANSItoUC(m_wstr, strSrc);

	return true;	
}

Hier wird zunächst die Länge des übergebenen Strings ermittelt und anschließend ein String entsprechender Länge erstellt. Zum Schluss wird dann die Konvertiermethode aufgerufen, um die Daten des übergebenen Strings zu konvertieren. Als Ziel wird dabei einfach der zuvor erstellte String angegeben.

Schließlich folgt hier noch die letzte Überladung von '_Create', die eine 'CString'-Instanz als Parameter erwartet. Im Prinzip handelt es sich bei dieser Variante um eine einfache Kopierfunktion:

//String erstellen
bool CString::_Create(const CString& strSrc)
{
	return _Create(strSrc.m_wstr);
}

Die Implementierung erweist sich hier als ziemlich einfach, da eine bereits existierende Variante von '_Create' direkt verwendet werden kann. Es ist also nicht nötig diese Variante ganz neu zu implementieren.

Nun scheint es angebracht die Konstruktoren und den Destruktor zu implementieren. Alle Implementierungen erweisen sich als denkbar einfach, da sie im Prinzip jeweils nur aus einem Aufruf der Methode '_Create' bestehen:

//Konstruktor
CString::CString(const unsigned short* wstr) : m_wstr(0), m_pcBytes(0)
{
	_Create(wstr);
}

//Konstruktor
CString::CString(const char* str) : m_wstr(0), m_pcBytes(0)
{
	_Create(str);
}

//Konstruktor
CString::CString(unsigned short wSign) : m_wstr(0), m_pcBytes(0)
{
	if (_Create(1)) *m_wstr = wSign;
}

//Konstruktor
CString::CString(char Sign) : m_wstr(0), m_pcBytes(0)
{
	char sztmp[] = "#";
	sztmp[0] = Sign;
	_Create(sztmp);
}

//Kopierkonstruktor
CString::CString(const CString& str) : m_wstr(0), m_pcBytes(0)
{
	_Create(str);
}

//Destruktor
CString::~CString(void)
{
	_Create();
}

Auch die Implementierungen der Zuweisungsoperatoren erweisen sich als denkbar einfach:

//Zuweisungsoperator
CString& CString::operator = (const unsigned short* wstr)
{
	_Create(wstr);
	return *this;
}

//Zuweisungsoperator
CString& CString::operator = (const char* str)
{
	_Create(str);
	return *this;
}

//Zuweisungsoperator
CString& CString::operator = (const CString& str)
{
	_Create(str);
	return *this;
}

Nun sind alle deklarierten Funktionen implementiert und es lassen sich bereits folgende Aufrufe durchführen:

CString str1 = "Hallo";
CString str2 = L"Hallo";
CString str3 = str1;

CString str4 = "Hi";
str1 = str4;
str4 = "Hallo";
str2 = L"Hi!";

Methode zum Bestimmen der Länge

Spätestens hier sollte nun eine Funktion implementiert werden, welche die Länge des Strings zurückliefert. Auch hier soll die Methode 'GetLength' heißen:

//Bestimmen der Länge
unsigned long CString::GetLength(void) const
{
	if (!m_pcBytes) return 0;
	else return (*m_pcBytes / sizeof(unsigned short));
}

Methoden zum Vergleichen zweier Strings

Solche Methoden sollten nicht fehlen. Zu diesem Zweck werde ich zunächst wiederum eine statische und private Memberfunktion implementieren, die zwei "UNICODE"-Zeichenketten vergleicht. Dabei sollen beide Zeichenketten Zeichen für Zeichen durchlaufen werden und sobald sich zwei Zeichen voneinander unterscheiden die Differenz zwischen ihnen zurückgegeben werden. Die Suche wird natürlich sofort unterbrochen, wenn bei der Suche das '\0'-Zeichen auftritt. Ist also der Wert negativ, dann kommt die zuerst übergebene Zeichenkette im Alphabet vor der zweiten. Ist der Wert positiv, dann kommt die erste Zeichenkette im Alphabet nach der zweiten. Ist hingegen der Wert '0', dann sind beide Zeichenketten identisch. Somit zeigt diese Funktion das gleiche Verhalten, wie die Funktionen der STL. Heißt die Funktion '_StrCmp', dann sieht ihre Implementierung wie folgt aus:

//Vergleichen zweier Strings
int CString::_StrCmp(const unsigned short* wstr1, const unsigned short* wstr2)
{
	//Parameterchecks
	if (!wstr1)
	{
		if (!wstr2) return 0;
		else return -(int)*wstr2;
	}
	else if (!wstr2)
	{
		return *wstr1;
	}

	if (wstr1 == wstr2) return 0;

	//Positionszeiger
	const unsigned short *pPos1 = wstr1, *pPos2 = wstr2;

	//Zeichen für Zeichen durchlaufen
	while (*pPos1 == *pPos2 && *pPos1 != L'\0')
	{
		//Zeiger inkrementieren
		pPos1++; pPos2++;
	}

	return ((int)*pPos1 - (int)*pPos2);
}

Im Prinzip ist die Funktion ganz simpel. Zunächst wird überprüft, ob nicht eine oder beide Zeichenkette ungültig also leer sind. Im ersten Fall wird einfach das erste Zeichen der nicht-leeren Zeichenkette mit entsprechendem Vorzeichen zurückgegeben. Da die Zeichen selber aber vorzeichenlos (nämlich 'unsigned short') sind, müssen sie zunächst in 'int' gecastet werden. Nachdem noch einmal überprüft wurde, ob nicht beide Zeichenketten identisch sind, werden beide Zeichenketten solange durchlaufen bis sie sich in einem Zeichen unterscheiden oder beide vollständig durchlaufen sind. Anschließend wird dann einfach die Differenz zwischen den Zeichen zurückgegeben, bei denen sie sich unterscheiden. Sind beide Zeichenketten gleich, dann ist die Differenz automatisch '0', da beide Zeichen dann automatisch am Ende das '\0'-Zeichen sind. Nun sollen nichtstatische Funktionen implementiert werden, die eine bereits existierende Instanz mit einer anderen Zeichenkette vergleichen. Dabei wird die aufrufende Instanz immer als die erste, die übergebene immer als die Zweite Zeichenkette angesehen, d.h. kommt die übergebene Zeichenkette im Alphabet hinter der Zeichenkette, welche innerhalb der Instanz gespeichert ist, dann ist der zurückgegebene Wert negativ. Die entsprechenden Funktionen sollen 'Compare' heißen. Ihre Implementierungen unterscheiden sich nicht wesentlich voneinander und sehen wie folgt aus:

//Vergleichen zweier Strings
int CString::Compare(const unsigned short* wstr) const
{
	return _StrCmp(m_wstr, wstr);
}

//Vergleichen zweier Strings
int CString::Compare(const char* str) const
{
	//CString-Instanz erstellen
	CString strtmp = str;
	return _StrCmp(m_wstr, strtmp.m_wstr);
}

//Vergleichen zweier Strings
int CString::Compare(const CString& str) const
{
	return _StrCmp(m_wstr, str.m_wstr);
}

Wenn man Methoden zum Vergleichen einführt, dann sollte man auch die entsprechenden Operatoren '==', '!=', '<', '<=', '>' und '>=' einführen. Zu jedem Operator wären auch hier drei Methoden notwendig. Da alle Operatoren jedoch einfach die Methode 'Compare' aufrufen, wäre es sinnlos alle 18 Implementierungen hier aufzuführen Ich beschränke mich auf sechs (für jeden Operator eine). Ihre Implementierungen sähen wie folgt aus:

//Operator '=='
bool CString::operator == (const unsigned short* wstr) const
{
	return (Compare(wstr) == 0);
}

//Operator '!='
bool CString::operator != (const unsigned short* wstr) const
{
	return (Compare(wstr) != 0);
}

//Operator '<'
bool CString::operator < (const unsigned short* wstr) const
{
	return (Compare(wstr) < 0);
}

//Operator '<='
bool CString::operator <= (const unsigned short* wstr) const
{
	return (Compare(wstr) <= 0);
}

//Operator '>'
bool CString::operator > (const unsigned short* wstr) const
{
	return (Compare(wstr) > 0);
}

//Operator '>='
bool CString::operator >= (const unsigned short* wstr) const
{
	return (Compare(wstr) >= 0);
}

Diese Implementierungen benötigen wohl keine weiteren Erläuterungen. Nach all den Änderungen dürfte die Klasse nun wie folgt aussehen:

class CString
{
private:
	//Zeichenkette
	unsigned short* m_wstr;
	
	//Längenangabe in Bytes
	unsigned long* m_pcBytes;

	//String erstellen
	bool _Create(unsigned long = 0);
	bool _Create(const unsigned short*);
	bool _Create(const char*);
	bool _Create(const CString&);

	//Konvertierfunktionen
	static void _UCtoANSI(char*, const unsigned short*, char = '#');
	static void _ANSItoUC(unsigned short*, const char*);

	//Vergleichen zweier Strings
	static int _StrCmp(const unsigned short*, const unsigned short*);
public:
	//Konstruktoren
	CString(const unsigned short* = 0);
	CString(const char*);

	//Kopierkonstruktor
	CString (const CString&);

	//Destruktor
	~CString(void);

	//Zuweisungsoperatoren
	CString& operator = (const CString&);
	CString& operator = (const unsigned short*);
	CString& operator = (const char*);

	//Vergleichen zweier Strings
	int Compare(const unsigned short*) const;
	int Compare(const char*) const;
	int Compare(const CString&) const;

	//Operator '=='
	bool operator == (const unsigned short*) const;
	bool operator == (const char*) const;
	bool operator == (const CString&) const;

	//Operator '!='
	bool operator != (const unsigned short*) const;
	bool operator != (const char*) const;
	bool operator != (const CString&) const;

	//Operator '<'
	bool operator < (const unsigned short*) const;
	bool operator < (const char*) const;
	bool operator < (const CString&) const;

	//Operator '<='
	bool operator <= (const unsigned short*) const;
	bool operator <= (const char*) const;
	bool operator <= (const CString&) const;

	//Operator '>'
	bool operator > (const unsigned short*) const;
	bool operator > (const char*) const;
	bool operator > (const CString&) const;

	//Operator '>='
	bool operator >= (const unsigned short*) const;
	bool operator >= (const char*) const;
	bool operator >= (const CString&) const;
};

Methoden zum Ändern des Strings

Im Prinzip ließen sich hier alle Methoden einführen, die schon vom dynamischen Array bekannt sind, d.h. 'Insert', 'Add' und 'Remove'. Ihre Implementierungen können im Prinzip auch übernommen werden. Es ist lediglich darauf zu achten, dass die Membervariable für die Länge ebenfalls mit im reservierten Speicher steckt.

1.) Die Methode 'Insert':

//Methode zum Einfügen
bool CString::Insert(const unsigned short* wstr, unsigned long Index)
{
	//Im Falle einer leeren Zeichenkette kein Einfügen notwendig
	if (wstr == 0) return false;
	
	//Länge des Strings ermitteln
	unsigned long Length = 0;
	const unsigned short* pPos = wstr;
	while (*pPos++ != '\0') Length++;

	//Im Falle einer leeren Zeichenkette kein Einfügen notwendig
	if (Length == 0) return false;

	//String soll sich nicht in sich selber einfügen
	if (m_wstr == wstr) return false;
	
	//Gültigkeit des Indexes überprüfen
	if (Index > GetLength()) Index = GetLength();

	//Alte Member aus der Instanz entfernen
	unsigned long OldLength = GetLength();
	unsigned short* wstrOld = m_wstr; m_wstr = 0;
	unsigned long* pcBytesOld = m_pcBytes; m_pcBytes = 0;

	//Instanz neu erstellen
	if (!_Create(OldLength + Length))
	{
		//Erstellung nicht erfolgreich
		delete[] pcBytesOld; 
		return false;
	}

	//Positionszeiger auf gegenwärtiges Zeichen
	const unsigned short* pSrcPos = wstrOld;
	unsigned short* pTarPos = m_wstr;
	unsigned int iPos = 0;
	
	//Alte Zeichen bis Index kopieren
	for (iPos = 0; iPos < Index; iPos++, pSrcPos++, pTarPos++)
	{
		*pTarPos = *pSrcPos;
	}
	
	//Quellpositionszeiger neu setzen
	pSrcPos = wstr;

	//Alle Zeichen der einzufügenden Zeichenkette kopieren
	for (iPos = 0; iPos < Length; iPos++, pSrcPos++, pTarPos++)
	{
		*pTarPos = *pSrcPos;
	}

	//Quellpositionszeiger neu setzen
	pSrcPos = wstrOld + Index;

	//Alte Zeichen ab Index kopieren
	for (iPos = Index; iPos < OldLength; iPos++, pSrcPos++, pTarPos++)
	{
		*pTarPos = *pSrcPos;
	}

	//Alten Speicher wieder freigeben und Funktion beenden
	delete[] pcBytesOld; 
	return true;
}

Diese Implementierung entspricht im Prinzip einer exakten Kopie der 'Insert'-Methode der Klasse 'CArray' und bedarf daher wohl keiner weiteren Erklärung. Nun die Überladungen:

//Methode zum Einfügen
bool CString::Insert(const char* str, unsigned long Index)
{
	CString strtmp = str;
	return Insert(strtmp.m_wstr, Index);
}

//Methode zum Einfügen
bool CString::Insert(const CString&* str, unsigned long Index)
{
	return Insert(str.m_wstr, Index);
}

//Methode zum Einfügen
bool CString::Insert(unsigned short wSign, unsigned long Index)
{
	CString strtmp = wSign;
	return Insert(strtmp.m_wstr, Index);
}

//Methode zum Einfügen
bool CString::Insert(char Sign, unsigned long Index)
{
	CString strtmp = Sign;
	return Insert(strtmp.m_wstr, Index);
}

Auch hier ist nicht viel neues zu entdecken. Die Überladungen erstellen zunächst eine temporäre 'CString'-Instanz und rufen dann lediglich die ursprüngliche Funktion auf.

2.) Die Methode 'Add' und die Operator '+=' und '+':

Da sowohl die Operatoren '+=' und '+' als auch die Funktion 'Add' nur jeweils die entsprechende Variante von 'Insert' aufrufen müssen, erweisen sich auch hier die Implementierungen als sehr einfach:

//Methode zum Anfügen
bool CString::Add(const unsigned short* wstr)
{
	return Insert(wstr, GetLength());
}

//Methode zum Anfügen
bool CString::Add(const char* str)
{
	return Insert(str, GetLength());
}

//Methode zum Anfügen
bool CString::Add(const CString& str)
{
	return Insert(str, GetLength());
}

//Methode zum Anfügen
bool CString::Add(unsigned short wSign)
{
	return Insert(wSign, GetLength());
}

//Methode zum Anfügen
bool CString::Add(char Sign)
{
	return Insert(Sign, GetLength());
}

//Operator '+='
CString& CString::operator += (const unsigned short* wstr)
{
	Insert(wstr, GetLength());
	return *this;
}

//Operator '+='
CString& CString::operator += (const char* str)
{
	Insert(str, GetLength());
	return *this;
}

//Operator '+='
CString& CString::operator += (const CString& str)
{
	Insert(str, GetLength());
	return *this;
}

//Operator '+='
CString& CString::operator += (unsigned short wSign)
{
	Insert(wSign, GetLength());
	return *this;
}

//Operator '+='
CString& CString::operator += (char Sign)
{
	Insert(Sign, GetLength());
	return *this;
}

//Operator '+'
CString CString::operator + (const unsigned short* wstr)
{
	//Kopie der Instanz erstellen und Zeichenkette anfügen
	CString strrtn = *this;
	strrtn.Insert(wstr, strrtn.GetLength());

	return strrtn;
}

//Operator '+'
CString CString::operator + (const char* str)
{
	//Kopie der Instanz erstellen und Zeichenkette anfügen
	CString strrtn = *this;
	strrtn.Insert(str, strrtn.GetLength());

	return strrtn;}

//Operator '+'
CString CString::operator + (const CString& str)
{
	//Kopie der Instanz erstellen und Zeichenkette anfügen
	CString strrtn = *this;
	strrtn.Insert(str, strrtn.GetLength());

	return strrtn;
}

//Operator '+'
CString CString::operator + (unsigned short wSign)
{
	//Kopie der Instanz erstellen und Zeichenkette anfügen
	CString strrtn = *this;
	strrtn.Insert(wSign, strrtn.GetLength());

	return strrtn;
}

//Operator '+'
CString CString::operator + (char Sign)
{
	//Kopie der Instanz erstellen und Zeichenkette anfügen
	CString strrtn = *this;
	strrtn.Insert(Sign, strrtn.GetLength());

	return strrtn;
}

2.) Die Methode 'Remove':

//Methode zu Entfernen von Zeichen
bool CString::Remove(unsigned long Start, unsigned long End)
{
	//Im Falle eines leeren Arrays kein Entfernen möglich
	if (GetLength() == 0) return false;

	//Indices überprüfen
	if (Start > End)
	{
		int Index = Start;
		Start = End;
		End = Index;
	}
	
	if (Start >= GetLength()) Start = GetLength() - 1;
	if (End >= GetLength()) End = GetLength() - 1;

	//Länge des Bereichs bestimmen, der gelöscht werden soll
	unsigned long RegLength = End - Start + 1;

	//Alte Member aus der Instanz entfernen
	unsigned long OldLength = GetLength();
	unsigned short* wstrOld = m_wstr; m_wstr = 0;
	unsigned long* pcBytesOld = m_pcBytes; m_pcBytes = 0;


	//Instanz neu erstellen
	if (!_Create(OldLength - RegLength))
	{
		//Erstellung nicht erfolgreich
		delete[] pcBytesOld; 
		return false;
	}

	//Positionszeiger auf gegenwärtiges Zeichen
	unsigned short* pSrcPos = wstrOld;
	unsigned short* pTarPos = m_wstr;
	unsigned long iPos = 0;
	
	//Alte Elemente bis Start kopieren
	for (iPos = 0; iPos < Start; iPos++, pSrcPos++, pTarPos++)
	{
		*pTarPos = *pSrcPos;
	}
	
	//Quellpositionszeiger neu setzen
	pSrcPos = wstrOld + End + 1;

	//Alte Elemente ab End kopieren
	for (iPos = End + 1; iPos < OldLength; iPos++, pSrcPos++, pTarPos++)
	{
		*pTarPos = *pSrcPos;
	}

	//Alten Speicher wieder freigeben und Funktion beenden
	delete[] pcBytesOld; 
	return true;
}

Auch hier wurde im Prinzip nur der Quelltext der Funktion 'Remove' aus der Klasse 'CArray' verwendet und an entsprechenden Stellen umgeschrieben. Aus diesem Grunde sollte die Implementierung soweit verständlich sein. Auch hier soll es nun zwei Überladungen geben:

//Methode zu Entfernen von Zeichen
bool CString::Remove(unsigned long Index)
{
	return Remove(Index, Index);
}

//Methode zu Entfernen von Zeichen
bool CString::Remove(void)
{
	return _Create();
}

Auch diese Überladungen entsprechen exakt denen der Klasse 'CArray'. Mit den Änderungen dieses Abschnitts dürfte die Klasse 'CString' in ihrer Deklaration wie folgt aussehen:

class CString
{
private:
	//Zeichenkette
	unsigned short* m_wstr;
	
	//Längenangabe in Bytes
	unsigned long* m_pcBytes;

	//String erstellen
	bool _Create(unsigned long = 0);
	bool _Create(const unsigned short*);
	bool _Create(const char*);
	bool _Create(const CString&);

	//Konvertierfunktionen
	static void _UCtoANSI(char*, const unsigned short*, char = '#');
	static void _ANSItoUC(unsigned short*, const char*);

	//Vergleichen zweier Strings
	static int _StrCmp(const unsigned short*, const unsigned short*);
public:
	//Konstruktoren
	CString(const unsigned short* = 0);
	CString(const char*);
	CString(unsigned short);
	CString(char);

	//Kopierkonstruktor
	CString (const CString&);

	//Destruktor
	~CString(void);

	//Zuweisungsoperatoren
	CString& operator = (const CString&);
	CString& operator = (const unsigned short*);
	CString& operator = (const char*);

	//Bestimmen der Länge
	unsigned long GetLength(void) const;

	//Vergleichen zweier Strings
	int Compare(const unsigned short*) const;
	int Compare(const char*) const;
	int Compare(const CString&) const;

	//Operator '=='
	bool operator == (const unsigned short*) const;
	bool operator == (const char*) const;
	bool operator == (const CString&) const;

	//Operator '!='
	bool operator != (const unsigned short*) const;
	bool operator != (const char*) const;
	bool operator != (const CString&) const;

	//Operator '<'
	bool operator < (const unsigned short*) const;
	bool operator < (const char*) const;
	bool operator < (const CString&) const;

	//Operator '<='
	bool operator <= (const unsigned short*) const;
	bool operator <= (const char*) const;
	bool operator <= (const CString&) const;

	//Operator '>'
	bool operator > (const unsigned short*) const;
	bool operator > (const char*) const;
	bool operator > (const CString&) const;

	//Operator '>='
	bool operator >= (const unsigned short*) const;
	bool operator >= (const char*) const;
	bool operator >= (const CString&) const;

	//Methode zum Einfügen
	bool CString::Insert(const unsigned short*, unsigned long);
	bool CString::Insert(const char*, unsigned long);
	bool CString::Insert(const CString&, unsigned long);
	bool CString::Insert(unsigned short, unsigned long);
	bool CString::Insert(char, unsigned long);

	//Methode zum Anfügen
	bool CString::Add(const unsigned short*);
	bool CString::Add(const char*);
	bool CString::Add(const CString&);
	bool CString::Add(unsigned short);
	bool CString::Add(char);

	//Operator '+='
	CString& CString::operator += (const unsigned short*);
	CString& CString::operator += (const char*);
	CString& CString::operator += (const CString&);
	CString& CString::operator += (unsigned short);
	CString& CString::operator += (char);

	//Operator '+'
	CString CString::operator + (const unsigned short*);
	CString CString::operator + (const char*);
	CString CString::operator + (const CString&);
	CString CString::operator + (unsigned short);
	CString CString::operator + (char);

	//Methode zu Entfernen von Zeichen
	bool Remove(unsigned long, unsigned long);
	bool Remove(unsigned long);
	bool Remove(void);
};

Eine Methode zum Ermitteln eines Teilstrings

Was ich bei der Klasse 'CArray' ausgelassen habe, werde ich nun hier bei der Klasse 'CString' einbinden: Eine Funktion, die es ermöglichen soll aus einer existierenden Instanz einen Teilstring auszulesen und auszugeben. Dazu müssen wie bei der Funktion 'Remove' zwei Indices übergeben werden. Heißt die Funktion 'GetSubString', dann könnte ihre Implementierung so aussehen:

//Methode zum Ermitteln eines Teilstrings
CString CString::GetSubString(unsigned long Start, unsigned long End) const
{
	//Variable zum zurückgeben
	CString strrtn = L"";

	//Im Falle eines leeren Arrays kein Entfernen möglich
	if (GetLength() == 0) return strrtn;

	//Indices überprüfen
	if (Start > End)
	{
		int Index = Start;
		Start = End;
		End = Index;
	}
	
	if (Start >= GetLength()) Start = GetLength() - 1;
	if (End >= GetLength()) End = GetLength() - 1;

	//Länge des Bereichs bestimmen, der "ermittelt" werden soll
	unsigned long RegLength = End - Start + 1;

	//Instanz neu erstellen
	if (!strrtn._Create(RegLength))
	{
		//Erstellung nicht erfolgreich
		return strrtn;
	}

	//Positionszeiger auf gegenwärtiges Zeichen
	unsigned short* pSrcPos = m_wstr + Start;
	unsigned short* pTarPos = strrtn.m_wstr;
	unsigned long iPos = 0;
	
	//Alte Elemente bis Start kopieren
	for (iPos = 0; iPos < RegLength; iPos++, pSrcPos++, pTarPos++)
	{
		*pTarPos = *pSrcPos;
	}
	
	return strrtn;
}

Der erste Teil entspricht im Prinzip einer exakten Kopie der '_Remove'-Implementierung. Nur zuvor wird eine lokale Variable ('strrtn') deklariert, die nachher zurückgegeben werden soll. Nach der Überprüfung der Indices und der Bestimmung der Länge des neuen Strings ('RegLength') wird der neue String mit der ermittelten Länge erstellt. Anschließend werden zwei Positionszeiger deklariert. Der erste ('pSrcPos') zeigt dann in der Aufrufenden Instanz auf genau das Zeichen, welches durch den Index 'Start' angegeben wurde. Der zweite Positionszeiger ('pTarPos') zeigt dann auf das erste Zeichen des temporären Strings. Dann wird der temporäre String bis zum Ende durchlaufen und Zeichen für Zeichen kopiert. Abschließend wird dann eine Kopie des soeben erstellten Strings zurückgegeben.

Zugriffsfunktionen und -operatoren

Was bisher noch gänzlich fehlt, sind Funktionen oder Operatoren, die es einem ermöglichen auf eine 'CString'-Instanz zuzugreifen. Dazu sollte zunächst der Operator '[]' überladen werden, der den direkten Zugriff auf einzelne Zeichen ermöglicht:

//Operator '[]'
unsigned short& CString::operator [] (unsigned long Index)
{
	if (Index >= GetLength()) Index = GetLength() - 1;
	
	return *(m_wstr + Index);
}

Außerdem wäre es nicht schlecht, wenn dort, wo eine "UNICODE"-Zeichenkette gewünscht wird, auch eine 'CString'-Instanz verwendet werden kann. Dazu muss einfach der Operator 'unsigned short*' überladen werden. Die Überladung sähe wie folgt aus:

//Operator 'const unsigned short*'
CString::operator unsigned short* (void) const
{
	return m_wstr;
}

Dann wird der Operator '[]' allerdings nicht mehr benötigt, da man auf den Zeiger, der durch den Operator 'unsigned short*' geliefert wird eben diesen Operator auch anwenden kann und dieser Operator implizit angewendet wird. Tatsächlich können beide Operatoren nicht nebeneinander existieren, da eine Aufruf des Operators (nicht als Memberfünktion) zu Mehrdeutigkeiten führen würde. Der Operator 'unsigned short*' sollte aber keineswegs leichtfertig verwendet werden, denn theoretisch wäre es möglich über ihn, den reservierten Speicher freizugeben, oder die Längenangabe zu ändern, etc. Man kann mithilfe dieses Operators auch einfach ein '\0'-Zeichen an beliebiger Stelle setzen und die Zeichenkette scheinbar frühzeitig terminieren, obwohl dahinter noch Zeichen verborgen sind. Die Methode 'GetLength' wird dann aber weiterhin den Wert zurückgeben, der im String gespeichert ist. Aus diesem Grunde müsste der Speicher entsprechend angepasst werden. Unter Umständen ist es aber nicht gewollt den unnötigen Speicher freizugeben. Eine andere Möglichkeit wäre also beim Aufruf der Methode 'GetLength' festzulegen, ob der interne Wert zurückgegeben oder der Wert zunächst anhand des '\0'-Zeichens bestimmt werden soll. Die Änderung könnte nun wie folgt aussehen:

//Bestimmen der Länge
unsigned long CString::GetLength(bool bUseIntern) const
{
	if (!m_pcBytes) return 0;
	if (bUseIntern) 
	{
		return (*m_pcBytes / sizeof(unsigned short));
	}
	else
	{
		//Länge errechnen
		unsigned long Length = 0;
		unsigned short* pPos = m_wstr;
		while (*pPos++ != L'\0') Length++;

		return Length;
	}
}

Da zum einen das Ausgeben des internen Wertes schneller ist und zum anderen die Memberfunktionen immer wieder diese Funktion aufrufen, sollte bei der Deklaration der Parameter standardmäßig auf 'true' gesetzt werden. Auf diese Weise brauchen die internen Funktionen auch nicht angepasst werden. So wird es auch ermöglicht nachträglich noch Zeichen hinter dem '\0'-Zeichen zu entfernen oder neue einzufügen. Andererseits ist es unter Umständen gewollt, dass der "tote" Speicher nicht mehr benötigt wird und beide Längen übereinstimmen sollten. Zu diesem Zwecke sollte eine Methode implementiert werden, welche alle Zeichen, die hinter dem ersten '\0'-Zeichen auftreten, entfernt. Diese Methode könnte 'ActualizeMemory' heißen und wie folgt implementiert werden:

//Anpassen des Speichers an die Länge
void CString::ActualizeMemory(void)
{
	//Stringlänge bis zum ersten '\0-Zeichen
	unsigned long StringLength = GetLength(false);

	//Anzahl der Zeichen im Speicher
	unsigned long MemoryLength = GetLength(true);
	
	//Aktualisierung notwendig?
	if (MemoryLength == StringLength) return;

	//Unnötige Zeichen entfernen
	Remove(StringLength, MemoryLength - 1);

}

Ich wette Funktionen dieser Form (die Erweiterung von 'GetLength' und die Funktion 'ActualizeMemory') wird man in keiner gewöhnlichen String-Klasse finden. Warum ich sie hier dennoch eingeführt habe dürfte dennoch zumindest in gewisser Weise plausibel sein. Sie stellen nur eine Art Absicherung dar, wenn der String durch ein '\0'-Zeichen getrennt wird, die Instanz davon nichts mitbekommt und somit zwei verschiedene Längenangaben zustande kommen können. Eine korrekte Anwendung dieser Klasse macht diese Funktionen eigentlich unnötig. Ich überlasse es dem Leser diese wieder zu entfernen und stattdessen beim Gebrauch des Operators 'unsigned short*' darauf zu achten keine ungültigen Werte (d.h. '\0'-Zeichen) zu setzen.

Nun noch zwei Zugriffspaare für den sicheren Ziugriff auf einzelne Elemente, d.h. das Ändern und Auslesen der einzelnen Zeichen über einen Index:

//Zeichen ermitteln
unsigned short CString::GetAt(unsigned long Index) const
{
	if (Index >= GetLength()) return L'\0';
	else return *(m_wstr + Index);
}

//Zeichen ermitteln
char CString::GetCharAt(unsigned long Index) const
{
	if (Index >= GetLength()) return '\0';
	else 
	{
		unsigned short wsztmp[] = L"#"; 
		wsztmp[0] = *(m_wstr + Index);

		char sztmp[] = "#";

		//Umwandeln
		_UCtoANSI(sztmp, wsztmp);
		
		return sztmp[0];
	}
}

//Zeichen setzen
void CString::SetAt(unsigned long Index, unsigned short wSign)
{
	if (Index < GetLength())
	{*(m_wstr + Index) = wSign;}
}

//Zeichen setzen
void CString::SetCharAt(unsigned long Index, char Sign)
{
	if (Index < GetLength())
	{
		unsigned short wsztmp[] = L"#"; 

		char sztmp[] = "#";
		sztmp[0] = Sign;

		//Umwandeln
		_ANSItoUC(wsztmp, sztmp);

		*(m_wstr + Index) = wsztmp[0];
		
	}
}

Diese Methoden setzen oder ermitteln ein Zeichen bei dem übergebenen Index. Teilweise müssen die übergebenen Zeichen zuvor noch in "UNICODE" umgewandelt werden oder umgekehrt. Zum Schluss sollten noch zwei Methoden implementiert werden, die den gesamten internen String in eine übergebene Zeichenkette kopieren und u.U. den internen String vorher noch umwandeln.

//UNICODE-Zeichenkette erhalten
unsigned short* CString::GetUCString(unsigned short* wstrTar, unsigned long Length)
{
	if (Length <= GetLength(false) || !wstrTar)
	{
		return 0;
	}
	memcpy(wstrTar, m_wstr, (GetLength(false) + 1) * sizeof(unsigned short));

	return wstrTar;
}

//ANSI-Zeichenkette erhalten
char* CString::GetANSIString(char* strTar, unsigned long Length)
{
	//Größe ausreichend?
	if (Length <= GetLength(false) || !strTar)
	{
		return 0;
	}
	_UCtoANSI(strTar, m_wstr);

	return strTar;
}

Hier wird ein bereits bestehender Puffer übergeben, der dann innerhalb der jeweiligen Methode gefüllt wird. Die Länge des Puffers muss mit übergeben werden. Nach dem Kopieren bzw. nach der Umwandlung wird der übergebene Zeiger einfach wieder zurückgegeben. Tritt innerhalb der Funktion ein Fehler auf, dann wird '0' zurückgeliefert. Mit Hilfe dieser Funktionen lassen sich bei Bedarf auch die Rohdaten auslesen ohne Änderungen an der Instanz vornehmen zu müssen.

Werden alle Methoden aus den letzten beiden Abschnitten noch ordentlich in der Klassendeklaration aufgenommen, dann dürfte diese nun wie folgt aussehen:

class CString
{
private:
	//Zeichenkette
	unsigned short* m_wstr;
	
	//Längenangabe in Bytes
	unsigned long* m_pcBytes;

	//String erstellen
	bool _Create(unsigned long = 0);
	bool _Create(const unsigned short*);
	bool _Create(const char*);
	bool _Create(const CString&);

	//Konvertierfunktionen
	static void _UCtoANSI(char*, const unsigned short*, char = '#');
	static void _ANSItoUC(unsigned short*, const char*);

	//Vergleichen zweier Strings
	static int _StrCmp(const unsigned short*, const unsigned short*);
public:
	//Konstruktoren
	CString(const unsigned short* = 0);
	CString(const char*);
	CString(unsigned short);
	CString(char);

	//Kopierkonstruktor
	CString (const CString&);

	//Destruktor
	~CString(void);

	//Zuweisungsoperatoren
	CString& operator = (const CString&);
	CString& operator = (const unsigned short*);
	CString& operator = (const char*);

	//Bestimmen der Länge
	unsigned long GetLength(bool = true) const;

	//Vergleichen zweier Strings
	int Compare(const unsigned short*) const;
	int Compare(const char*) const;
	int Compare(const CString&) const;

	//Operator '=='
	bool operator == (const unsigned short*) const;
	bool operator == (const char*) const;
	bool operator == (const CString&) const;

	//Operator '!='
	bool operator != (const unsigned short*) const;
	bool operator != (const char*) const;
	bool operator != (const CString&) const;

	//Operator '<'
	bool operator < (const unsigned short*) const;
	bool operator < (const char*) const;
	bool operator < (const CString&) const;

	//Operator '<='
	bool operator <= (const unsigned short*) const;
	bool operator <= (const char*) const;
	bool operator <= (const CString&) const;

	//Operator '>'
	bool operator > (const unsigned short*) const;
	bool operator > (const char*) const;
	bool operator > (const CString&) const;

	//Operator '>='
	bool operator >= (const unsigned short*) const;
	bool operator >= (const char*) const;
	bool operator >= (const CString&) const;

	//Methode zum Einfügen
	bool CString::Insert(const unsigned short*, unsigned long);
	bool CString::Insert(const char*, unsigned long);
	bool CString::Insert(const CString&, unsigned long);
	bool CString::Insert(unsigned short, unsigned long);
	bool CString::Insert(char, unsigned long);

	//Methode zum Anfügen
	bool CString::Add(const unsigned short*);
	bool CString::Add(const char*);
	bool CString::Add(const CString&);
	bool CString::Add(unsigned short);
	bool CString::Add(char);

	//Operator '+='
	CString& CString::operator += (const unsigned short*);
	CString& CString::operator += (const char*);
	CString& CString::operator += (const CString&);
	CString& CString::operator += (unsigned short);
	CString& CString::operator += (char);

	//Operator '+'
	CString CString::operator + (const unsigned short*);
	CString CString::operator + (const char*);
	CString CString::operator + (const CString&);
	CString CString::operator + (unsigned short);
	CString CString::operator + (char);

	//Methode zu Entfernen von Zeichen
	bool Remove(unsigned long, unsigned long);
	bool Remove(unsigned long);
	bool Remove(void);

	//Methode zum Ermitteln eines Teilstrings
	CString GetSubString(unsigned long, unsigned long) const;

	//Operator 'unsigned short*'
	operator unsigned short* (void) const;

	//Anpassen des Speichers an die Länge
	void ActualizeMemory(void);

	//Zeichen ermitteln
	unsigned short GetAt(unsigned long) const;
	char GetCharAt(unsigned long) const;

	//Zeichen setzen
	void SetAt(unsigned long, unsigned short);
	void SetCharAt(unsigned long, char);

	//UNICODE-Zeichenkette erhalten
	unsigned short* GetUCString(unsigned short*, unsigned long);

	//ANSI-Zeichenkette erhalten
	char* GetANSIString(char*, unsigned long);
};

Nachträgliche Anmerkungen

Betrachtet man noch einmal die gesamte Klassendeklaration, dann stellt man unweigerlich fest, dass es sich um eine kaum überschaubare Menge von Funktionen handelt. Auch wenn man die ganzen Überladungen weglässt, bleiben immer noch sehr viele übrig. Bei einer solchen Vielzahl an Funktionen verliert man leicht den Überblick. Aufgrund dessen ist es wichtig ausschlagkräftige Namen zu wählen, so dass der Benutzer sofort erkennt welche Funktion für was zuständig ist. Auch Operatoren können dabei sehr hilfreich sein, denn ihre Bedeutung ist (sofern ein Programmierer die Bedeutung von Operatoren nicht vollständig verändert) normalerweise leicht ersichtlich. Operatoren sind hier nun reichlich vorhanden. Zum größten Teil handelt es sich hier um Operatoren, die den Vergleich zweier Zeichenketten ermöglichen. Außerdem wurden alle Operatoren implementiert, die bereits in den Klassen 'CArray' und 'CLinkedList' verwendet wurden und das Verketten zweier Zeichenketten ermöglichen. Mit all den Funktionen und Operatoren bietet diese Klasse alle wesentlichen Funktionen, die für Zeichenketten sinnvoll sind. 

Die Klasse basiert auf "UNICODE" liefert aber vielerlei Möglichkeiten auch "ANSI"-Strings aufzunehmen. Da "UNICODE" zukunftsorientierter ist, ist "UNICODE" eigentlich angebrachter. Allerdings bieten erst Betriebssysteme der NT-Reihe (also auch 2000 und XP) ausreichend Support für "UNICODE". In Windows 98 beispielsweise existieren in der API kaum Funktionen die "UNICODE" unterstützten. Aus diesem Grunde ist diese Klasse nicht besonders gut geeignet, wenn man sie in der Verbindung mit der Windowsprogrammierung für Windows 98 verwendet. Allerdings dürfte es sich als nicht allzu schwer erweisen, die bestehende Klasse so zu verändern, dass sie intern nicht mehr als "UNICODE" läuft. Einige Implementierungen dürften sich dann sogar wesentlich vereinfachen andere sogar gänzlich entfallen. Eine derartige Änderung überlasse ich hier dem Leser.

Bereits bestehende String-Klassen (z.B. aus der MFC) stellen noch weitere Methoden zur Verfügung. Deren Aufgaben sind aber eher trivial. So existiert beispielsweise eine Methode, die aus allen Buchstaben Groß- oder aus allen Kleinbuchstaben macht. Eine solche Implementierung habe ich mir hier geschenkt. Sie würden den bereits jetzt schon sehr "fülligen" Quellcode noch weiter aufblähen. Allerdings sind diese Funktionen für Vergleiche zweier Strings ganz sinnvoll. So möchte man nämlich nicht unbedingt, dass zwischen Groß- und Kleinschreibung unterschieden wird. So könnte man beide Strings zunächst so umwandeln, dass beide entweder nur aus Groß- oder nur aus Kleinbuchstaben bestehen und diese dann vergleichen. Auch dies überlasse ich an dieser Stelle dem Leser.

Hier noch einmal der Quelltext der aktuellsten Version der Klasse 'CString':

string_src.exe (Quelltext, gepackt)

Zurück Nach oben Weiter