3.5 Datentypen für Zeichen
In diesem Abschnitt erfahren Sie etwas zu den Zeichentypen in C. Zwar werden Sie in diesem Buch vorwiegend mit dem Datentyp char arbeiten, trotzdem sollen Sie auch kurz erfahren, welche Möglichkeiten es gibt, erweiterte bzw. umfangreichere Zeichensätze zu verwenden. Beachten Sie, dass die Verwendung von erweiterten Zeichensätzen (wie Unicode) jenseits von char kein triviales Thema mehr ist.
Für Sie ist es daher zunächst einmal wichtig, dass Sie sich mit dem Datentyp char vertraut machen. Wenn Sie anfangen, internationale Programme zu schreiben, dann werden Sie sich auch intensiver mit dem Thema »Unicode« befassen müssen, aber dafür benötigen Sie auf jeden Fall den aktuellen Unicode-Standard und separate Literatur. Wir nehmen nun erst einmal an, dass ein Zeichen von Typ char ein Byte umfasst. Wir wissen, dass dies eine sehr vereinfachte Sichtweise ist, und einige Leser und Leserinnen haben uns auch zu Recht geschrieben, dass sie stets die folgende korrektere Typendefinition benutzen
typedef unsigned char byte;
byte MyMem[0x10000];
unsigned char MyMem[0x10000];
Dies ist korrekt, und Sie werden später auch noch erkennen warum das so ist, wenn Sie mehr über Typendefinitionen mit typedef gelernt haben. Zunächst verwenden wir jedoch die primitive Betrachtungsweise.
3.5.1 Der Datentyp char
Der grundlegende Datentyp für Zeichen lautet char und belegt gewöhnlich ein Byte Speicherplatz, was somit meistens (aber nicht immer) 28 = 256 Ausprägungen ermöglicht. Der Datentyp char ist zumindest groß genug, dass alle Zeichen des Basis-Ausführungszeichensatzes in ihm gespeichert werden können. Wird außerdem ein Zeichen in einem char gespeichert, dann ist es garantiert, dass der gespeicherte Wert der nichtnegativen Kodierung im Zeichensatz entspricht. Ein Zeichensatz wiederum ist dazu da, einem Zeichen einen bestimmten Wert zuzuweisen, weil ein Rechner letztendlich nur Dualzahlen speichern kann. Dazu ein Beispiel:
char ch = 'A'; // Dezimal 65 im ASCII-Zeichensatz
Bei dem weit verbreiteten ASCII-Zeichensatz entspricht die Zeichenkonstante 'A' dem Dezimalwert 65. So wäre es beispielsweise auch möglich, stattdessen Folgendes anzugeben:
char ch = 65; // Entspricht dem Zeichen 'A' (ASCII)
Das ist ohne Probleme möglich, weil char ja auch ein Integertyp ist. In der Praxis ist die Verwendung eines Dezimalwerts anstelle einer Zeichenkonstante allerdings nicht zu empfehlen, wenn Sie char als Zeichentyp und nicht als Integer-Typ verwenden wollen: Zum einen lässt sich nicht sofort erkennen, welches Zeichen hier dahintersteckt, und zum anderen schreibt der Standard nicht vor, welcher Zeichensatz verwendet werden soll. Zwar dürfte zu 99,9 % die ASCII-Zeichensatztabelle zum Einsatz kommen, aber wenn dann doch einmal ein anderer Zeichensatz verwendet wird, sind Sie mit der Zeichenkonstante immer auf der sicheren Seite. Hierzu folgt ein einfaches Beispiel:
00 // Kapitel3/char_beispiel.c
01 #include <stdio.h>
02 int main(void) {
03 char ch01 = 'A'; // bei ASCII 65
04 char ch02 = 66; // besser wäre 'B'
05 printf("Dezimal: %d; Zeichen: %c\n", ch01, ch01);
06 printf("Dezimal: %d; Zeichen: %c\n", ch02, ch02);
07 return 0;
08 }
Das Programm gibt bei der Ausführung Folgendes aus:
Dezimal: 65; Zeichen: A
Dezimal: 66; Zeichen: B
char ist auch ein Ganzzahldatentyp, mit dem Sie rechnen können. Aufgrund des kleineren Wertebereichs wird dieser Typ dazu jedoch relativ selten genutzt. Sie werden allerdings noch sehen, dass es hier Ausnahmen gibt, z. B. wenn es darum geht, mit beliebig langen Zahlen zu rechnen. Viele moderne Kryptografie-Verfahren tun genau dies: Sie betrachten z. B. einen zu verschlüsselnden Text (oder auch den Schlüssel selbst) als sehr lange Zahl und führen mit dieser Zahl komplexe Operationen aus.
[»] Vorzeichen bei »char«
Es hängt von der Compiler-Einstellung ab, ob char auch negative Zahlen aufnehmen kann – ob char also als signed char oder unsigned char implementiert ist. Dies ist unter anderem dann wichtig zu wissen, wenn Sie char als Typ für Ganzzahlen verwenden wollen.
3.5.2 Der Datentyp wchar_t
Für die Zeichensätze mancher Sprachen – wie etwa der chinesischen mit über tausend Zeichen – ist der Datentyp char zu klein. Für die Darstellung beliebiger landesspezifischer Zeichensätze kann daher der Breitzeichentyp wchar_t (wide char = breite Zeichen) aus der Header-Datei stddef.h verwendet werden. Bei der Verwendung eines solchen Zeichens muss vor die einzelnen Hochkommata noch das Präfix L gestellt werden:
wchar_t ch = L'Z';
Entsprechend wird auch bei dem Formatzeichen für die Ausgabe oder Eingabe eines wchar_t ein l vor das c gesetzt (%lc):
wprintf("%lc", ch);
Auch die üblichen Funktionen, die mit Zeichenketten arbeiten, können Sie mit wchar_t nicht mehr verwenden, und Sie müssen stattdessen auf die entsprechenden w-Versionen (wie beispielsweise wprintf()) zurückgreifen.
Die Größe von wchar_t lässt sich nicht exakt beschreiben, meistens beträgt sie jedoch 2 oder 4 Bytes. Es lässt sich lediglich mit Sicherheit sagen, dass wchar_t mindestens so groß wie char und höchstens so groß wie long ist. wchar_t muss auf jeden Fall mindestens groß genug sein, um alle Werte des größten unterstützten Zeichensatzes aufnehmen zu können.
[»] Zeichen und Zeichensatz
Egal welchen Zeichentyp Sie verwenden, Sie sollten sich immer vor Augen halten, dass char und wchar_t selbst keine Zeichen speichern, sondern letztendlich nur Ganzzahlen, die ihre Bedeutung erst durch den Zeichensatz erhalten, der sich auf dem Rechner befindet. Dies gilt selbstverständlich auch für Ihren Quellcode und die in ihm enthaltenen Kommentare, die auf fremden Rechnern unter Umständen nicht lesbar sind. (Das betrifft z. B. die deutschen Umlaute oder französische Akzentzeichen.)
3.5.3 Unicode-Unterstützung
Der Unicode-Standard definiert mit UTF-8, UTF-16 und UTF-32 drei Zeichenkodierungsformate. Jedes Format hat seine Vor- und Nachteile. Bisher haben Programmierer char verwendet, um UTF-8 zu nutzen, unsigned short oder wchar_t für UTF-16 und unsigned long oder wchar_t für UTF-32. Ab dem C11-Standard können Sie zwei Typen mit einer plattformunabhängigen Breite mit char16_t und char32_t für UTF-16 und UTF-32 aus der Header-Datei uchar.h nutzen.
#include <uchar.h>
char utf8ch = u8'Z'; // UTF-8
char16_t utf16ch = u'Z'; // UTF-16
char32_t utf32ch = U'Z'; // UTF-32
Für UTF-8 können Sie nach wie vor char verwenden. C11 hat außerdem die Präfixe u und U für UTF-16- bzw. UTF-32-Literale und das Präfix u8 für UTF‐8-Literale eingeführt. Auch Unicode-Konvertierungsfunktionen sind in uchar.h deklariert.