C++ Kurs

String Objekte I

Die Themen:

string-Klasse
Strings definieren
Ein- und Ausgabe von Strings
String-Operationen
Beispiel und Übung

string-Klasse

Zur Verarbeitung von Strings stellt die C++ Standard Bibliothek die Klasse string zur Verfügung. In den nachfolgenden Lektionen dieses Kurses werden zur Verarbeitung von Strings nur noch solche string Objekte verwendet und keine C-Strings mehr.

Sehen wir uns zunächst einmal an, was die string Klasse so interessant macht:

  1. Es wird automatisch genügend Speicher reserviert, um eine vorgegebene Zeichenfolge ablegen zu können. Und allein dies ist schon ausreichend Grund genug die string Klasse einzusetzen.
  2. Es werden diverse Operatoren überladen (definiert), um Strings auf einfache Art und Weise verarbeiten zu können. So erfahren Sie nachher gleich noch, dass Sie u.a. mittels des Operators + zwei Strings zusammenfügen können.
  3. Es stehen Konvertierungen zur Verfügung um ein string Objekt in einen char* und umgekehrt zu konvertieren.

Die Klasse string enthält noch wesentlich mehr Eigenschaften als in dieser kurzen Einführung aufgezeigt werden kann. In einer späteren Lektion werden wir uns deshalb die Klasse string nochmals etwas genauer ansehen.

Wenn Sie die Klasse string in ihrem Programm einsetzen, so müssen Sie die mittels #include <string> die dazugehörige Header-Datei einbinden. Achten Sie aber unbedingt darauf dass hier kein .h steht! Die Header-Datei string.h bzw. ihr C++ Gegenstück cstring ist eine völlig andere Header-Datei!

Ferner müssen Sie beachten, dass die Klasse (der Datentyp) string im Namensraum std definiert ist, d.h. Sie müssen entweder std::string angeben oder aber die Anweisung using std::string; verwenden.

Aber sehen wir uns zunächst einmal an, wie string Objekte definierte werden.

Sollten Sie mit dem Begriff Objekt im Augenblick noch etwas Schwierigkeiten haben, so stellen Sie sich unter einem Objekt in erster Näherung einfach eine Variable vor.

Noch einmal zur Erinnerung: Damit in Zukunft nicht die totale Verwirrung bezüglich des Begriffs String ausbricht, wird im weiteren Verlaufe des Kurses unter einem String immer ein string Objekt verstanden. Zeichenfolgen die in char-Feldern abgelegt sind (das sind diejenigen, die mit einer binären 0 abgeschlossen sind) werden dagegen als C-String bezeichnet, weil dies unter C die einzige Möglichkeit war, veränderbare Zeichenfolgen abzulegen. Daraus folgt, dass ein char-Zeiger immer auf einen C-String zeigt und niemals auf einen String.

Strings definieren

Im Prinzip definieren Sie einen String genauso wie z.B. eine short-Variable, nur dass anstelle des Datentyps short nun der Datentyp string steht. Ein so definierter String enthält noch keine Zeichen, d.h. er ist leer.

Sie können einen String bei seiner Definition aber auch bereits mit einer Zeichenfolge initialisieren. Diese Initialisierung können Sie im Prinzip genauso durchführen wie das Initialisieren einer beliebigen anderen Variable, d.h. nach dem Stringnamen folgt der Zuweisungsoperator und danach nun die Zeichenfolge, mit der der String initialisiert werden soll. Diese Zeichenfolge kann entweder ein C-String Literal, eine C-String Konstante oder aber auch ein Zeiger auf ein char-Feld sein, in dem ein entsprechender C-String abgelegt ist.

Und selbstverständlich können Sie auch Stringfelder definieren. Beachten Sie dabei aber, dass das Feld dann n Strings aufnimmt, die auch unterschiedliche Längen besitzen können. Verwechseln dies nicht mit der Definition eines char-Feldes, das ja einen C-String mit maximal n-1 Zeichen aufnehmen kann.

Eine weitere Möglichkeit besteht darin, einen String mit einem bereits bestehenden String bei seiner Definition zu initialisieren, d.h. Sie erzeugen sich eine String-Kopie. Wie dies geht sehen in der letzten Definition im Beispiel.


// Header-Datei für string einbinden
#include <string>
using std::string;

// Leeren String definieren
string empty;
// String mit C-String initialisieren
string text1 = "Ein String";
// String Feld definieren
string array[10];
// und erstem String einen C-String zuweisen
array[0] = "String-Element 0";
// String mit einem anderen String initialisieren
string text2(text1);

War doch bis nicht so kompliziert, oder? Zugegeben, wir haben uns nur die einfachen Fälle angesehen um einen String zu erstellen. Die C++ Standard Bibliothek kennt acht verschiedene Arten ein string Objekt zu erstellen. Für unsere Zwecke sollten diese drei Möglichkeiten aber ausreichen. Mehr Infos finden Sie, wie immer, in der Online-Hilfe zur Klasse string.

Ein- und Ausgabe von Strings

Fangen wir mit dem einfachen Fall an, der Ausgabe von Strings.

Die Ausgabe eines Strings unterscheidet sich nicht von der Ausgabe von anderen Daten, d.h. Sie können den String direkt im Ausgabestream angeben.


#include <iostream>
#include <string>
using namespace std;

int main()
{
   string text = "Ein string-Objekt";
   cout << text << endl;
}

Beim Einlesen eines Strings muss unterschieden werden, ob die einzelnen Wörter einer Eingabe (üblicherweise getrennt durch Leerzeichen) in mehreren Strings abgelegt werden sollen oder die ganze Eingabe (Zeile) in einem String.

Um einzelne Wörter einer Eingabe in Strings abzulegen, werden die Strings, genauso wie die bisherigen sonstigen Variablen, im Eingabestream angegeben. Und hier sehen Sie auch einen der Vorteile der Strings: egal ob ein Wort aus 4 Zeichen oder 80 Zeichen besteht, es wird automatisch genügend Speicher reserviert um das Wort im String ablegen zu können.


#include <iostream>
#include <string>
using namespace std;

int main()
{
   string word1, word2;
   cin >> word1 >> word2;
}

Wollen Sie eine ganze Zeile einlesen, so verwenden Sie hierfür die Funktion getline(...). Beachten Sie bitte, dass die hier verwendete Funktion getline(...) eine andere Signatur (Anzahl und Typ der Parameter) besitzt, als die Memberfunktion getline(...) des Eingabestreams cin. Die hier verwendete Funktion getline(...) enthält im ersten Parameter eine Referenz auf den Stream,, von dem die Zeile eingelesen werden soll. Im nachfolgenden Beispiel ist dies der Eingabestream cin. Aber ebenso möglich wäre hier z.B. die Angabe eines Dateistreams vom Typ ifstream. Der zweite Parameter ist dann die Referenz auf das string Objekt, in dem die Eingabe abgelegt wird. Und nochmals: egal wie lange die Eingabezeile ist, es wird immer genügend Speicher reserviert um die komplette Zeile im String ablegen zu können.


#include <iostream>
#include <string>
using namespace std;

int main()
{
   string line;
   getline(cin,line);
}

String-Operationen

Zuweisungen

Die einfachste Operation ist die Zuweisung. So können Sie einem String ein C-String Literal, eine C-String Konstante oder auch einen anderen String zuweisen.


// Zwei Strings definieren
string s1, s2;
// C-String-Literal einem String zuweisen
s1 = "Dies ist String1";
// String einem anderen String zuweisen
s2 = s1;

Addition

Ebenso ist es möglich, Strings einfach mithilfe des Operators + zu 'addieren', d.h. aneinander zu hängen. Zusätzlich zum Operator + stellt die Klasse string zur Addition noch den Operator += zur Verfügung.


// Zwei Strings definieren
string s1("Text1"), s2;
// C-String-Literal addieren, s1 danach "Text1 erweitert"
s1 += " erweitert";
// Strings addieren, s2 danach "Text1 erweitert in s2"
s2 = s1 + " in s2";

Zeichen-Zugriff und Stringlänge

Um auf einzelne Zeichen innerhalb eines Strings zuzugreifen, verwenden Sie, wie bei den C-Strings, den Indexoperator[]. Auch hier hat das erste Zeichen im String den Index 0. Wollen Sie den gesamten String durchlaufen, so liefert die Memberfunktion size() Ihnen hierfür die Anzahl der Zeichen im String.


// String definieren und initialisieren
string s1("Ein Text");
// Alle Zeichem im String einzeln ausgeben
for (int i=0; i<s1.size(); i++)
   cout << s1[i] << ',';

Strings vergleichen

Strings werden mit den bekannten Vergleichsoperatoren <, >, == usw. verglichen. Der Vergleich erfolgt hierbei lexikalisch, d.h. 'Aaaa' ist kleiner als 'Bb'. Für einen der beiden Operanden des Vergleichsoperators kann anstelle eines string Objekts auch ein const char Zeiger stehen.


// Zwei String definieren und initialisieren
string s1("Aaaa"), s2("Bb");
// Strings vergleichen
if (s1 < s2)
   cout << "s1 kommt vor s2" << endl;
else
   cout << "s2 kommt vor s1" << endl;

char-Zeiger Konvertierung

Um einen String in einen const char-Zeiger zu konvertieren, besitzt die string Klasse die Memberfunktion c_str(). Mithilfe dieser Konvertierung können Sie also jetzt überall dort einen String verwenden, wo Sie ansonsten einen const char-Zeiger benötigen. Im Beispiel wird so z.B. der Inhalt eines Strings in ein char-Feld kopiert.


// String definieren und initialisieren
string s1("Ein Text");
// char Feld definieren
char charArray[40];
// Zeichen aus String in char-Feld umkopieren
strcpy(charArray,s1.c_str());

Der umgekehrte Weg, also einen char-Zeiger in ein String zu konvertieren, erfolgt über den am Anfang dieser Lektion aufgeführten Zuweisungsoperator. Beachten Sie bitte, dass die Memberfunktion c_str() einen const char* liefert und Sie somit den Inhalt des Strings nicht über diesen Zeiger direkt verändern können.

Damit wollen wir an dieser Stelle die Einführung des string Datentyps abschließen. Der string Datentyp enthält, wie bereits erwähnt, noch zahlreiche weitere Memberfunktionen um z.B. Zeichen mitten im String einzufügen oder zu löschen oder sogar Zeichenfolgen in einem String zu suchen. Diese Memberfunktionen werden wir uns später in einer weiteren Lektion noch ansehen.

Beispiel und Übung

Beispiel:

Das Beispielprogramm zählt die Häufigkeit des Auftretens der einzelnen Buchstaben des Alphabets in einem bestimmten Text, wobei nicht nach Groß-/Kleinschreibung unterschieden wird. Der zu untersuchende Text liegt als ASCII-Datei test.txt vor.

Zum Zählen der Buchstaben ein einsprechendes Feld mit 26 Einträgen definiert, welches als 'Buchstabenzähler' verwendet wird. Die Häufigkeit des Buchstabens 'a' wird im ersten Feldelement gezählt, die des Buchstabens 'b' im zweiten usw. Für Sonderzeichen, wie zum Beispiel Punkt oder Komma, wird ein getrennter Zähler verwendet.

Der zu untersuchende Text wird zeilenweise mittels getline(...) aus der Datei eingelesen.

Zum Schluss wird die Häufigkeitsverteilung in Tabellenform ausgegeben.

Nachfolgend der Inhalt der Datei test.txt. Wenn Sie das Beispiel ausführen wollen, erstellen Sie zuvor im Root-Verzeichnis des VC++ Projekts diese Datei.


Sollen in einem Programm je nach Inhalt einer
ganzzahligen Variable verschiedene Anweisungen
durchlaufen werden, so kann dies, wie nebenstehend
dargestellt, durch geschachtelte IF-ELSE Anweisungen
erfolgen. Im Beispiel werden je nachdem, ob die
Variable var den Wert 1, 2 oder 3 enthält, die
Anweisungen Aktion1, Aktion2 oder Aktion3 ausgefuehrt.
Enthält var einen anderen Wert, so werden die Anweisungen
Aktion4 ausgefuehrt. Aber das kennen Sie ja schon von der
letzten Lektion her.

Anzahl der Buchstaben in der Datei test.txt

a: 32 b:  6 c:  8 d: 19 e: 72
f:  5 g: 12 h: 15 i: 27 j:  3
k:  6 l: 15 m:  5 n: 48 o: 14
p:  2 q:  0 r: 24 s: 19 t: 18
u: 11 v:  6 w: 10 x:  0 y:  0
z:  2
Sonderzeichen: 77


// Lösung zur string Klasse
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
using std::cout;
using std::endl;

// Konstanten für Dateiname und Anzahl der Buchstaben im Alphabet
const char *const pFILENAME = "test.txt";
const int NUMOFCHARS = 26;

// main() Funktion
int main()
{
   // Datei öffnen
   std::ifstream inFile(pFILENAME);
   if (!inFile)
   {
      cout << "Kann Datei " << pFILENAME << " nicht öffnen!" << endl;
      exit(1);
   }

   // Zähler für die Anzahl der Buchstaben definieren und initialisieren
   int characters[NUMOFCHARS] = {0};
   // Zähler für Sonderzeichen wie z.B. '.' oder ',' usw.
   int specialChar = 0;

   // Schleife zum Einlesen der Datei
   do
   {
      // String für eingelesene Zeile aus der Datei
      std::string line;
      // Eine Zeile aus der Datei einlesen
      std::getline(inFile,line);
      // Falls Dateiende nicht erreicht
      if (!inFile.eof())
      {
         // Komplette Zeile durchlaufen
         for (unsigned int index=0; index<line.size(); index++)
         {
            // Falls Buchstabe im String a...z
            if ((line[index]>='a') && (line[index]<='z'))
               characters[line[index]-'a']++;
            else
               // Falls Buchstabe im String A...Z
               if ((line[index]>='A') && (line[index]<='Z'))
                  characters[line[index]-'A']++;
               else
                  // Sonderzeichen erhöhen
                  specialChar++;
         }
      }
   } while (!inFile.eof());

   // Datei auch wieder schliessen!
   inFile.close();

   // Ergebnis ausgeben
   cout << "Anzahl der Buchstaben in der Datei " << pFILENAME << endl;
   // Alle Buchstabenzähler ausgeben
   for (int index=0; index<NUMOFCHARS; index++)
   {
      // Nach jedem 5. Buchstaben eine neue Zeile beginnen
      if (index%5 == 0)
         cout << endl;
      cout << static_cast<char>(index+'a') << ": " << std::setw(3)
           << characters[index] << '\t';
   }
   // Noch Anzahl der Sonderzeichen ausgeben
   cout << "\nSonderzeichen: " << specialChar << endl;
}

Übung:

So, jetzt dürfen Sie einmal Ihr ganzes Wissen einsetzen. Ziel der Übung ist es, ein Konvertierungsprogramm zu schreiben.

Das Programm soll die Textdatei gcc.mak einlesen (Inhalt folgt gleich), entsprechend den nachfolgenden Regeln konvertieren und das Ergebnis dann in der Datei conv.mak ablegen.

Die Konvertierung soll wie folgt erfolgen:

  • Alle Zeilen die mit dem Kommentarzeichen '#' beginnen sind zu entfernen.
  • Alle Kleinbuchstaben sind in Großbuchstaben umzuwandeln.

Für die Konvertierung Kleinbuchstaben -> Großbuchstaben ist die Bibliotheksfunktion int toupper(int nC); zu verwenden. Sie erhält das zu konvertierende Zeichen als Parameter übergeben und liefert als Returnwert das konvertierte Zeichen. Binden Sie dazu die Header-Datei cctype ein.

Nachfolgend der Inhalt der Datei gcc.mak. Wenn Sie das Beispiel ausführen wollen, erstellen Sie zuvor im Root-Verzeichnis des VC++ Projekts diese Datei.


#
# Note : this makefile is for gcc-2.95 and later !
#

#
# compiler
#
CC = gcc
CXX = gcc

#
# Basename for libraries
#
LIB_BASENAME = libstlport_gcc

#
# guts for common stuff
#
#
LINK=ar cr
# 2.95 flag
DYN_LINK=gcc -shared -o

OBJEXT=o
DYNEXT=so
STEXT=a
RM=rm -rf
PATH_SEP=/
MKDIR=mkdir -p
COMP=GCC$(ARCH)

all: all_dynamic all_static

include common_macros.mak

WARNING_FLAGS= -Wall -W -Wno-sign-compare -Wno-unused -Wno-uninitialized -ftemplate-depth-32

Inhalt der Datei conv.mak nach erfolgter Konvertierung:


CC = GCC
CXX = GCC
LIB_BASENAME = LIBSTLPORT_GCC
LINK=AR CR
DYN_LINK=GCC -SHARED -O
OBJEXT=O
DYNEXT=SO
STEXT=A
RM=RM -RF
PATH_SEP=/
MKDIR=MKDIR -P
COMP=GCC$(ARCH)
ALL: ALL_DYNAMIC ALL_STATIC
INCLUDE COMMON_MACROS.MAK
WARNING_FLAGS= -WALL -W -WNO-SIGN-COMPARE -WNO-UNUSED -WNO-UNINITIALIZED -FTEMPLATE-DEPTH-32

Lösung ansehen!