Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

10.1 Zeichenketten / Strings

10.1 Zeichenketten / Strings

Die Strings sind mit die wichtigsten Datentypen in der Programmierung, da viele Problemstellungen auf den Umgang mit solchen Zeichenketten beruhen. Das fängt beim Bearbeiten von Dateien an und hört bei der Netzwerkkommunikation auf. Allerdings ist dieses Kapitel, gerade in C, eines der kompliziertesten überhaupt. Wenn Sie mit Strings umgehen können, können Sie behaupten, Pointer verstanden zu haben. Komplizierter wird es dann nicht mehr.

Die Grundidee von Strings ist, sie in einem Array abzubilden, sodass jedes Zeichen in einem separaten Feld steht. Im Gegensatz zu anderen Programmiersprachen, gibt es in C nur s.g. Null-Terminierte-Strings (in C++ gibt es dann auch andere klassenbasierte Strings). Dies bedeutet, dass jeder Zeichenkette, das ASCII-Zeichen "\0" (also der Wert nul und nicht die Zahl null) an gehangen wird, um zu signalisieren, dass das Wort bzw. der Satz, zu Ende ist. Dies muss immer berücksichtigt werden, wenn man Speicher reserviert bzw. eine Puffergröße angeben soll.

Ein Stringarray anhand eines Zuges

Zum Seitenanfang
Zum Inhaltsverzeichnis

10.1.2 Dynamische Strings

10.1.2 Dynamische Strings

Da Strings ja, wie bereits erwähnt, nur Felder sind, kann man auch sie dynamisch erzeugen und verwalten.

Das Schema sieht so aus:
char* <Variable> [= "<Zeichenkette>"]|[= new char[<AnzahlDerZeichen>]]

 1
 2
 3
 4
 5
					
char* pcstrZeigerAufKonstante	= "Ich bin ein Zeiger auf eine statische Konstante.";
printf("%s\n", pcstrZeigerAufKonstante);

pcstrZeigerAufKonstante		= "Jetzt zeige ich auf eine andere Konstante.";
printf("%s\n", pcstrZeigerAufKonstante);
					

Ausgabe:

Ich bin ein Zeiger auf eine statische Konstante.
Jetzt zeige ich auf eine andere Konstante.
		

Obwohl ich in Zeile 1 einen Pointer deklariert haben, darf dieser in diesem Spezialfall nicht freigegeben werden! Der Grund dafür ist folgender. Intern legt der Compiler eine Konstante an und die Referenz auf diese Konstante wird an den Pointer übergeben. Versuchen man jetzt, den Speicher freizugeben, wird dies zu einer Schutzverletzung führen, da man mit Konstanten nichts machen darf.

Genauso dürfen Sie jetzt nicht den Inhalt der Zeichenkette ändern, weder per Funktion noch per Indexzugriff. Das einzige was Sie machen dürfen ist, wie in Zeile 4 gezeigt, den Zeiger auf eine andere Konstante zeigen lassen. Dabei sei erwähnt, dass dabei kein Speicher verloren geht, weil sich der Compiler um die Speicherverwaltung von Konstanten kümmert. Das war auch der Grund, warum ich hier keine Größe für den String angeben musste.

 1
 2
 3
 4
 5
 6
 7
 8
					
char* pstrZeigerAufDynamischenString	= new char[80];

strcpy_s(pstrZeigerAufDynamischenString, 80, "Ich bin ein dynamischer String.");
printf("%s\n", pstrZeigerAufDynamischenString);

pstrZeigerAufDynamischenString[30]	= '!';
printf("%s\n", pstrZeigerAufDynamischenString);
delete [] pstrZeigerAufDynamischenString;
					

Ausgabe:

Ich bin ein dynamischer String.
Ich bin ein dynamischer String!
		

In diesem Beispiel zeige ich, wie man selbst die Speicherverwaltung für Strings übernimmt und genau das, sehen Sie in Zeile 1 und 8. Jetzt muss man allerdings aufpassen, dass man erstens genug Speicher reserviert und man darf unter keinen Umständen, dem String eine Referenz auf eine Konstante zuweisen, wie ich das im Vorherigen Beispiel getan habe. Der Grund dafür liegt auf der Hand. Mit dem Zuweisen einer neuen Referenz, verliert man den Bezug zum vorher reservierten Speicher und somit kann jener nicht mehr freigegeben werden (Speicherleck).

Jetzt darf man aber wieder Stringfunktionen benutzen und man kann auch wieder den Inhalt über den Indexzugriff manipulieren. Dies zeige ich in Zeile 3 und 6.

Zum Seitenanfang
Zum Inhaltsverzeichnis

10.1.1 Statische Strings

10.1.1 Statische Strings

Wie Sie das von Arrays kennen, kann man logischerweise auch die Strings statisch erzeugen. Dies hat zur Folge, dass Sie sich nicht um die Speicherreservierung kümmern müssen.

Die statischen Arrays sind am leichtesten zu verwalten und man braucht auch nicht so viele Dinge zu beachten, aber leider werden sie im Stack des Arbeitsspeichers gehalten und somit kann man keine Massen an Informationen halten, wie das z.B. eine Textverarbeitung macht. Nichts desto trotz, werde ich kurz demonstrieren, was man machen kann und Hinweise geben, was Sie unterlassen sollten.

Vorab sei noch erwähnt, dass man einzelne Zeichen in einfache Anführungszeichen setzt, wohingegen ganze Zeichenketten, in doppelte Anführungszeichen gesetzt werden. Man kann auch ein einzelnes Zeichen in doppelte Anführungszeichen setzen, allerdings wird dieses Zeichen dann wie ein String behandelt und somit wird noch das ASCII Zeichen "0" an gehangen, wodurch zwei Byte benutzt werden, um einen Buchstaben zu speichern. Seien Sie sich dessen immer bewusst.

Das Schema sieht so aus: char <Variable>[<AnzahlDerZeichen>] [= "<Zeichenkette>"]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
					
#include <string.h>

// ...

char astrStatischerString1[80]		= "Mein Inhalt wurde von einer Konstante kopiert.";
printf("%s\n", astrStatischerString1);

astrStatischerString1[45]		= '!';	// Einzelnes Zeichen wird manipuliert
printf("%s\n", astrStatischerString1);

char astrStatischerString2[20];
strcpy_s(astrStatischerString2, 20, "Ich bin statisch.");
printf("%s\n", astrStatischerString2);

astrStatischerString2[16]		= '!';
printf("%s\n\n", astrStatischerString2);

char astrStatischerString3[1001];
printf("Bitte Satz eingeben (max 1000 Zeichen):\n");

gets_s(astrStatischerString3, 1001);
printf("Ihre Eingabe lautet:\n%s\n", astrStatischerString3);
					

Ausgabe:

Mein Inhalt wurde von einer Konstante kopiert.
Mein Inhalt wurde von einer Konstante kopiert!
Ich bin statisch.
Ich bin statisch!

Bitte Satz eingeben (max 1000 Zeichen):
Hallo Welt
Ihre Eingabe lautet:
Hallo Welt
		

Ihnen ist vielleicht aufgefallen, dass meine Arrays alle größer sind, als sie es sein müssten. In Zeile 5 reserviere ich 80 Elemente, obwohl 46 + 1 (Terminator) ausreichen würden. Diese Vorgehensweise ist allerdings üblich, da man zum einen zu faul zum zählen ist und zum Anderen nie weiß, was noch kommen könnte. Wie der Name "statisch" bereits suggeriert, kann man die Größe im Nachhinein nicht mehr ändern. Des Weiteren sehen Sie, wie ich gleich eine Konstante zuweise, oder um ganz korrekt zu sein, dem Compiler in Auftrag gebe, den Inhalt einer konstanten Zeichenkette, in mein Array zu kopieren.

In Zeile 8 und 15 manipuliere ich den Inhalt meines Arrays. Ich mache aus dem Punkt im Satz, ein Ausrufezeichen. Sie lernen also daraus, dass Sie statische Zeichenketten nach Belieben manipulieren können.

In den Zeilen 6, 9, 13, 16, 19 und 22 zeige ich, wie man Strings einfach ausgeben kann, ohne das Array durch iterieren zu müssen. Man benutzt "%s". Achtung! Dies funktioniert nur mit Zeichenketten. Arrays mit Integern z.B. können nicht so ausgegeben werden.

Zeile 12 zeigt, wie Sie mit Stringfunktionen umgehen müssen, wenn Sie statische Arrays benutzen. Entscheidend bei den Funktionen mit "_s" ist, dass man immer noch die Größe des Arrays angeben muss. (Achtung - Nicht die Länge des Wortes, welches ja unter Umständen kürzer sein kann!)

Im dritten Abschnitt, ab Zeile 21 zeige ich, wie man in einen String Werte/Sätze einlesen kann. Hierbei gibt es zwei Besonderheiten. Zum einen braucht man den "&" Operator nicht, da ein Array ja schon ein Zeiger ist und es besser ist die Funktion "gets" bzw. "gets_s" zu benutzen, da "scanf" bzw. "scanf_s" lediglich ein Wort einlesen. Sobald in der Eingabe ein Leerzeichen auftaucht, wird der String abgeschnitten.

Zum Seitenanfang
Zum Inhaltsverzeichnis

10.1.4 Konstante Strings und Stringkonstanten

10.1.4 Konstante Strings und Stringkonstanten

Um noch einen drauf zu setzen, möchte ich Ihnen noch die konstanten Strings zeigen. Sie werden in einem separaten Konstantenspeicher gehalten und vom Compiler verwaltet. Im Stack befindet sich dann nur noch eine Referenz, also ein Pointer auf diese Konstante. Man hat also einen quasi dynamischen String. Als es eben um dynamische Strings ging, habe ich schon kurz gezeigt, dass man einen Pointer auf einen konstanten Wert zeigen lassen kann. Ich habe auch dazu gesagt, dass es möglich ist, diesen Zeiger dann zur Laufzeit auf eine andere Konstante umzubiegen. Wenn man aber bei der Variablendeklaration den Modifikator "const" mit vor den Typ schreibt, ist dies auch nicht mehr möglich. Dies wird in der Regel aber nur in Funktionsköpfen getan, um den Nutzer der Funktion zu garantieren, dass sein String unverändert bleibt.

An folgendem Quelltext möchte ich noch einmal die Unterschiede klar machen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
					
const char*	astrConstString		= "konstant";
char*		pcstrConstString	= "konstant";
char		aStaticString[10]	= "statisch";

char*	pstrDynamicString		= new char[10];
strcpy(pstrDynamicString, "dynamisch");

printf("%s\n", astrConstString);	
printf("%s\n", pcstrConstString);
printf("%s\n", astrStaticString);
printf("%s\n", pstrDynamicString);

aConstString2				= "Konstant";	// Verweis auf neue Konstante
aStaticString[0]			= 'S';		// Zeichen Austauschen
pstrDynamicString[0]			= 'D';		// Zeichen Austauschen

printf("%s\n", aConstString2);
printf("%s\n", aStaticString);
printf("%s\n\n", pstrDynamicString);

delete [] pstrDynamicString;
					

Ausgabe:

konstant
konstant
statisch
dynamisch

Konstant
Statisch
Dynamisch
		

Speicherverwaltung verschiedener Stringtypen vor Änderungen

Hier sind zunächst die drei Speicherbereiche gut ersichtlich. Der Stack ist ja der Speicher, welcher zwar nicht so groß ist, aber auf welchem wir bedenkenlos Daten anlegen können, ohne uns um deren Verwaltung zu kümmern.

Der Const-Speicher ist ein Speicherbereich, welcher vom Compiler bereit gestellt wird und mit jedem Programmstart reserviert wird. Der Zugriff ist ausschließlich lesend und die Verwaltung übernimmt der Compiler. Er ist es auch, welcher für eine automatische Größe sorgt. Versucht man einen String auf eine Konstante zu löschen, geht das zwar im ersten Moment, aber spätestens beim Programmende, wird es einen Fehler geben, weil der Compiler Code eingefügt hat, um dies zu tun (und somit würde es doppelt passieren).

Der Heap ist der größte Bereich des Speichers und auf ihn kann man nur über eine Referenzierung heran kommen. Speicherplatz muss selbst erstellt und freigegeben werden. Wenn man versucht einen Zeiger auf einen solchen String auf eine Konstante biegt, ist das zwar im Moment ok, aber der reservierte Platz kann nicht mehr freigegeben werden, da man nicht mehr weiß, wo der Speicherbereich ist, der freizugeben ist.

In der Grafik ist auch schön zu erkennen, dass sowohl der Zeiger auf die Konstante, als auch der Zeiger auf den dynamischen String, im Stack liegen, aber dann wo anders hin verweisen. Lediglich der statische String wird komplett im Stack gehalten. Schaut man sich aber den Quelltext dazu an, sieht man nicht so schnell den Unterschied.

Gerade der Unterschied zwischen Zeile 2 und 3 ist wichtig. Ich hatte ja mal gesagt, dass man ein Array bei der Definition entweder mit dem Stern am Variablentyp oder mit den eckigen Klammern am Variablenname setzen kann. Der Unterschied bei Strings ist nun, dass die Angabe mit den eckigen Klammern und einer Größenangabe, ein Array im Stack anlegt. Zusätzlich existiert eine Konstante im Const-Speicher, welche dann in das Array kopiert wird. Somit kann man den Inhalt verändern, wie in Zeile 14 gezeigt. Die Angabe mit dem Punk ist lediglich ein Zeiger. Hier kann kein Array im Stack angelegt werden, da ja die Größe nicht explizit angegeben wird. Somit wird ein Verweis auf den Inhalt im Const-Speicher gesetzt. Dieser Verweis kann zwar zur Laufzeit umgebogen werden, wie in Zeile 13 gezeigt, aber man kann den Inhalt nicht ändern, solange der Zeiger auf den Const-Speicher zeigt.

Auch wenn das Verwirrend klingt, aber Sie müssen immer unterscheiden zwischen einem konstanten String und einer Stringkonstante. Ein konstanter String ist ein nicht veränderbarer Pointer auf einen String und dieser kann nicht umgebogen werden. Falls der Zeiger aber auf einen Bereich im Stack oder Heap zeigt, kann man den Inhalt durchaus ändern. Eine Stringkonstante hingegen ist ein Bereich im Const-Speicher. Die Referenz kann umgebogen werden, aber der Inhalt kann nicht manipuliert werden.

Um noch einen drauf zu setzen, gibt es auch konstante Stringkonstanten. Das sieht man in Zeile 1. Hier habe ich einen String definiert, welcher auf eine Stringkonstante zeigt. Dieser Zeiger kann auch später nicht mehr umgebogen werden und der String selbst ist nicht änderbar.

Nach den änderungen ab Zeile 13, sieht der Speicher wie folgt aus.

Speicherverwaltung verschiedener Stringtypen nach Änderungen

Sie sehen hier sehr schön, dass "pcstrConstString" zwar scheinbar geändert wurde, er aber im Grunde nur auf einen anderen Bereich im Const-Speicher verweist. Bei dem statischen und dem dynamischen String hingegen, wurde tatsächlich der Inhalt geändert.

Ich kann gut verstehen, wenn Sie jetzt einen Knoten im Kopf haben sollten. Wenn dem so ist, lesen Sie dieses Kapitel einfach noch einmal in Ruhe durch. Ich muss selber auch zugeben, dass ich jedes Mal aufs Neue nachdenken muss. Jetzt verstehen Sie vielleicht warum man sagt, dass Strings in C das komplizierteste Thema ist.

Zum Seitenanfang
Zum Inhaltsverzeichnis

10.1.3 Einlesen von Strings

10.1.3 Einlesen von Strings

Bisher habe ich Werte immer mit der Funktion "scanf" bzw. "scanf_s" eingelesen. Dies funktioniert auch mit Strings, allerdings ergeben sich daraus ein paar Fallstricke, was folgendes Beispiel demonstrieren soll.

statische Variante dynamische Variante
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
					
#define DEF_MAX_LENGTH 80

char astrKurzerSatz[DEF_MAX_LENGTH];

printf("Bitte schreiben Sie einen kurzen Satz:\n");
fflush(stdin);
scanf_s("%s", astrKurzerSatz, DEF_MAX_LENGTH);
printf("Sie schrieben:\n%s\n\n", astrKurzerSatz);

printf("Bitte schreiben Sie noch einen kurzen Satz:\n");
fflush(stdin);
gets_s(astrKurzerSatz, DEF_MAX_LENGTH);
printf("Sie schrieben: \n%s", astrKurzerSatz);
					
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
					
#define DEF_MAX_LENGTH 80

char* pstrKurzerSatz = new char[DEF_MAX_LENGTH];

printf("Bitte schreiben Sie einen kurzen Satz:\n");
fflush(stdin);
scanf_s("%s", pstrKurzerSatz, DEF_MAX_LENGTH);
printf("Sie schrieben:\n\"%s\"\n\n", pstrKurzerSatz);

printf("Bitte schreiben Sie noch einen kurzen Satz:\n");
fflush(stdin);
gets_s(pstrKurzerSatz, DEF_MAX_LENGTH);
printf("Sie schrieben: \n\"%s\"", pstrKurzerSatz);

delete [] pstrKurzerSatz;
					

Ausgabe:

Bitte schreiben Sie einen kurzen Satz:
Hallo Welt
Sie schrieben:
"Hallo"

Bitte schreiben Sie noch einen kurzen Satz:
Hallo Welt
Sie schrieben:
"Hallo Welt"
		

Wie Sie sehr schön im ersten Teil sehen können, ließt die Funktion "scanf_s" nur ein Wort ein und keinen ganzen Satz. Genauso kritisch ist es, wenn man so versucht ein einzelnes Leerzeichen einzulesen. Dies leistet diese Funktion nicht, im Gegenteil, es ist von den Entwicklern sogar so gewollt. Um also ein einzelnes Leerzeichen oder einen Satz einlesen zu können, muss man die Funktion "gets" bzw. "gets_s" benutzen.

Besonders auffällig ist, dass beim Einlesen von Strings, die Variable normal übergeben wird und nicht ihre Adresse, also mit einem "&" Operator. Dies liegt daran, dass ein Array ja quasi schon ein Zeiger ist. Streng genommen sind zwar nur dynamische Arrays Zeiger, aber in diesem Fall läuft es auf das Gleiche hinaus.

Ein weiterer bedeutender Fakt ist, dass bei den "_s" Funktionen immer eine Puffergröße mit angegeben werden muss, da es sonst zu Abstürzen kommt (gerade bei "scanf_s"). Dabei bezieht sich der Puffer auf die Variable, welche den Wert bekommen soll, oder im Falle von "strcat_s" oder "strcpy_s", auf die erste Variable, also das Ziel (Target). Wählt man einen kleineren Puffer, so wird die entsprechende Operation abgeschnitten oder es kommt auch zu Abstürzen. Hier ist also höchste Aufmerksamkeit geboten.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012