Zeichenketten und Adressoperator in C++

Der folgende Quelltext aus Beispiel 3 der dritten Sitzung (04.11.2014, http://www.hki.uni-koeln.de/node/17010) veranschaulicht die Arbeit mit Zeichenketten in Form von char-Arrays und dem Adressoperator („&“):

#include <iostream>

using namespace std;

int main() {

	char zeile[100] = "Dies ist ein Text aus mi Nacht.";
	int worte[20];
	int anzahlWorte;

	worte[0] = 0;
	anzahlWorte = 1;

	for (int i = 0; zeile[i] != '\0'; i = i + 1) {

		if (zeile[i] == ' ') {

			zeile[i] = '\0';
			worte[anzahlWorte] = i + 1;
			anzahlWorte = anzahlWorte + 1;
		}
	}

	for (int i = 0; i<anzahlWorte; i++) {

		cout << "Ein Wort: ";
		cout << &zeile[worte[i]];
		cout << endl;
	}

	return 1;
}

Nach Kompilierung präsentiert sich der Quelltext mit einer Konsolenanwendung, in der die einzelnen Wörter der Zeichenkette „Dies ist ein Text aus mi Nacht.“ zeilenweise ausgegeben werden:

Zeichenketten und Adressoperator - Konsolenoutput
Zeichenketten und Adressoperator – Konsolenoutput

Fehlt der Adressoperator „&“ in der Ausgaberoutine „cout“ (Zeile 27), so wird nur der jeweils erste Buchstabe der einzelnen Wörter zeilenweise ausgegeben:

strings-adressoperator_output-console_terminiert

Warum das so ist, das erschließt sich mit einem Blick auf den Arbeitsspeicher und der Funktionalität, die das Konsolenprogramm realisiert. In den ersten Zeilen werden die Arbeitsvariablen deklariert und initialisiert:

// char-Array zur Speicherung der Arbeitszeichenkette
char zeile[100] = "Dies ist ein Text aus mi Nacht.";

// Das int-Array "worte" dient dazu, die Startpositionen der einzelnen Wörter zu speichern
int worte[20];

// Anzahl der verarbeiteten Wörter
int anzahlWorte;

// Initialisierung: Das erste Wort startet an Position 0 der Arbeitszeichenkette
worte[0] = 0;

// Zähler für die Anzahl der Wörter
anzahlWorte = 1;

Die erste for-Schleife (Zeilen 14-22) dient dazu, die Arbeitszeichenkette Zeichen für Zeichen, d.h. von zeile[0] (Zeichen „D“) bis zeile[31] (Zeichen „\0“) zu durchlaufen und jedes Leerzeichen durch den String-Terminator „\0“ zu ersetzen:

// Jedes Zeichen der Arbeitszeichenkette betrachten,
// solange das betrachtete Zeichen nicht das String-terminierende Zeichen "\0" ist
for (int i = 0; zeile[i] != '\0'; i = i + 1) {

	// Ist das aktuell betrachtete Zeichen ein Leerzeichen?
	if (zeile[i] == ' ') {

		// Wenn ja, dann ersetze das Leerzeichen durch den Stringterminator
		zeile[i] = '\0';

		// Startposition des Wortes speichern, das mit der vorherigen Zeile abgeschlossen wurde
		worte[anzahlWorte] = i + 1;

		// Wortzähler erhöhen
		anzahlWorte = anzahlWorte + 1;
	}
}

Hat die for-Schleife ihre Arbeit beendet, stehen die Anfangspositionen der einzelnen Wörter im Array „worte“:

  • Wort 0 beginnt an Position 0 (gespeichert in worte[0])
  • Wort 1 beginnt an Position 5 (gespeichert in worte[1])
  • Wort 2 beginnt an Position 9 (gespeichert in worte[2])
  • Wort 3 beginnt an Position 13 (gespeichert in worte[3])
  • Wort 4 beginnt an Position 18 (gespeichert in worte[4])
  • Wort 5 beginnt an Position 22 (gespeichert in worte[5])
  • Wort 6 beginnt an Position 25 (gespeichert in worte[6])

Nachvollziehen lässt sich das mit der folgenden for-Schleife:

for (int i = 0; i < anzahlWorte; i++) {
		
	cout << "Wort " << i << " beginnt an Position "
		<< worte[i] << " (gespeichert in worte[" << i << "])" << endl;
}

Mit der letzten for-Schleife werden die einzelnen Wörter der Zeichenkette, wie sie in dem char-Array „zeile“ gespeichert sind, nach und nach ausgegeben:

// Gehe jedes mit "\0" abgeschlossene Wort durch
for (int i = 0; i < anzahlWorte; i++) {

	cout << "Ein Wort: ";
	// Zugriff auf die Startadresse des 1., 2., 3., etc. Wortes
	cout << &zeile[worte[i]];
	cout << endl;
}

Dass nun durch die Verwendung des Adressoperators „&“ die entsprechenden Wörter – und nicht einzelne Zeichen / Buchstaben – ausgegeben werden, erschließt sich mit einem Blick auf die Speicherung des char-Arrays „zeile“ im Arbeitsspeicher und der Adressierung seiner Bestandteile. Nach der Initialisierung des char-Arrays „zeile“ mit der Anweisung

char zeile[100] = "Dies ist ein Text aus mi Nacht.";

belegt das Array 31 Speicherstellen vom Typ char (Unterstrich == Leerzeichen):

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
D i e s _ i s t _ e i n _ T e x
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
t _ a u s _ m i _ N a c h t . \0

Nach der ersten for-Schleife schaut das Array so aus:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
D i e s \0 i s t \0 e i n \0 T e x
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
t \0 a u s \0 m i \0 N a c h t . \0

Würde der Adressoperator „&“ in der for-Schleife weggelassen und die Ausgabe mit der Anweisung

cout << zeile[worte[i]];

vorgenommen, so würden die Inhalte der Speicherstellen 0, 5, 9, 13, 18, 22, 25 (vgl. Array „worte“) ausgegeben:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
D i e s \0 i s t \0 e i n \0 T e x
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
t \0 a u s \0 m i \0 N a c h t . \0

Wird nun in der for-Schleife mit

cout << &zeile[worte[i]];

der Adressoperator verwendet, dann wird an cout die Startadresse („los geht’s an Stelle 0 des Arrays“, „los geht’s an Stelle 5 des Arrays“) des entsprechenden Wortes übergeben. Die Funktion cout gibt anschließend alle Zeichen vom Anfang der Startadresse bis zum String-terminierenden Zeichen „\0“ aus. Weil cout in der Anweisung ohne „&“ mit dem Zeiger auf die Adresse der Speicherstelle arbeitet, lässt sich auf die einzelnen Zeichen der Zeichenkette wiederum über die folgende Anweisung zugreifen:

cout << *&zeile[worte[i]];

Das ist ein Zeiger („*“) auf die Adresse („&“) der Speicherstelle mit dem Index „worte[i]“ des char-Arrays „zeile“ – klingt kompliziert, ist eigentlich aber ganz einfach. 😉

Nützliche Links:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.