Mit Arrays haben Sie die Möglichkeit, eine geordnete Folge von Werten, eines bestimmten Typs ab zu speichern und zu bearbeiten. Arrays werden manches mal auch als Vektoren, Felder oder Reihungen bezeichnet. C C++ C/C++ Arrays Felder Zeichenkette String Stringfunktionen Arrays - Zeichenkette String Stringfunktionen mehrdimensionale Arrays Kapitel 14: Arrays

14.10. Mehrdimensionale Arrays initialisieren            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Werte bei mehrdimensionalen Arrays werden ähnlich übergeben wie bei einem eindimensionalen Array. Hier zum Beispiel eine Deklaration mit sofortiger Initialisierung von Werten:

int Matrix[5][4] = { {10,20,30,40,50},
                     {15,25,35,45,55},
                     {20,30,40,50,60},
                     {25,35,45,55,65}};

Dadurch ergibt sich folgende Belegung des Feldes:

Abbildung 14.7: Ein zweidimensionales Array, mit Werten initialisiert

Abbildung 14.7: Ein zweidimensionales Array, mit Werten initialisiert

Wie bei den normalen Arrays lassen sich die einzelnen Elemente mithilfe des Feldindex initialisieren. Wollen Sie beispielsweise das Element mit dem Wert 60 ändern in 100, dann geht das wie folgt:

Matrix[2][4]=100; 

Hier wurde zum Beispiel der Inhalt von Matrix[2][4] verändert. Wollen Sie das Element mit dem Wert 65 umändern in 66, dann wird folgender Feldindex verwendet:

Matrix[3][4] = 66; 

Eine weitere Möglichkeit zur Initialisierung von mehrdimensionalen Arrays ist folgende:

int Matrix[4][4] = { {0},
                     {1},
                     {0,1},
                     {0,0,1} };

Hiermit besitzen alle Feldelemente, die nicht ausdrücklich initialisiert wurden, automatisch den Wert 0. Die Belegung des Feldes sieht also folgendermaßen aus:

Abbildung 14.8: Ein zweidimensionales Array (4 x 4)
Abbildung 14.8: Ein zweidimensionales Array (4 x 4)

In der Praxis werden mehrdimensionale Arrays bei verschiedensten Arten von Berechnungen benötigt oder bei 2-D-Darstellungen von Grafiken. Das folgende Programm demonstriert die Anwendung eines mehrdimensionalen Arrays:

#include <stdio.h>

// Konstanten
#define VOL1 3   /* Anzahl Felder erste Dimension */
#define VOL2 4   /* Anzahl Felder zweite Dimension */

int main()
 {
   int i,j;
   int myarray[VOL1][VOL2];     /* [3][4] */

   /* Eingabe der Array-Elemente */
   for(i=0; i < VOL1; i++)
      {
         for(j=0; j < VOL2; j++)
            {
               printf("Wert für myarray[%d][%d]:",i,j);
               scanf("%d",&myarray[i][j]);
            }
      }
   printf("\nAusgabe von myarray[%d][%d]...\n\n",VOL1,VOL2);

   for(i=0; i < VOL1; i++)
      {
         for(j=0; j < VOL2; j++)
            {
               printf("\t%4d ",myarray[i][j]);
            }
         printf("\n\n");
      }
   return 0;
}

Das Programm nimmt nichts anderes vor, als den Anwender nach Ganzzahlen abzufragen, um diese Werte im zweidimensionalen Array zu speichern und wieder auszugeben. Um dies zu realisieren, wird eine äußere und innere for-Schleife verwendet. Die äußere for-Schleife dient dabei der Inkrementierung der Variable im linken Indizierungsoperator (oder, aus der Sicht einer Tabellenkalkulation, der Zeile). Die innere for-Schleife inkrementiert den Wert im rechten Indizierungsoperator (und somit der Spalte).

Im nächsten Beispielprogramm soll ein kleines Zeitkonto für einen Arbeitgeber verwaltet werden. Damit sollen einige Arbeitszeitberechnungen durchgeführt werden.

#include <stdio.h>
#define ARBEITER 3
#define TAGE 5

int zeitkonto[ARBEITER][TAGE];

/* Fehlerausgabe */
void error(int n)
{
   printf("%d (?) Falsche Eingabe!!\n",n);
}

/*-1- Ausgabe der Wochenarbeitszeit je Arbeiter*/
void ArbeiterWochenStunden()
{
   int i,j,tmp;
   for(i=0; i < ARBEITER; i++)
      {
         tmp=0;
         printf("Wochenarbeitszeit von Arbeiter Nr. %d\n",i+1);
         printf("-------------------------------------\n");
         for(j=0; j < TAGE; j++)
            {
               printf("|%d Std.",zeitkonto[i][j]);
               tmp+=zeitkonto[i][j];
            }
         printf("| = Ges. %d Std.\n\n",tmp);
      }
}

/*-2-Durchschnittszeiten pro Tag in der Woche je Arbeiter*/
void ArbeiterTagesDurchschnitt()
{
   int i,j,tmp;
   for(i=0; i < ARBEITER; i++)
      {
         tmp=0;
         printf("Durchschn. pro Tag/Woche Arbeiter: %d\n",i+1);
         printf("-------------------------------------------\n");
         for(j=0; j < TAGE; j++)
            {
               tmp+=zeitkonto[i][j];
            }
         printf("Durchschn. v. Arbeiter %d p. Tag: %.1f "\
                "Std/Tag\n\n" ,i+1,(float)tmp/TAGE);
      }
}

/*-3-Durchschnittszeit aller Arbeiter pro Tag*/
void TeamTagesDurchsschnitt()
{
   int i,j,tmp;
   for(i=0; i < TAGE; i++)
      {
         tmp=0;
         printf("Durchschn. Arbeitszeit aller Mitarbeiter pro "\
                "Tag %d = ",i+1);
         for(j=0; j < ARBEITER; j++)
            {
               tmp+=zeitkonto[j][i];
            }
         printf("%.1f Std.\n\n",(float)tmp/ARBEITER);
      }
}

/*-4-Gesamtstunden aller Arbeiter in der Woche*/
void TeamWochenStunden()
{
   int i,j;
   int tmp=0;
   printf("Gesamtstunden aller Arbeiter in der Woche\n");
   printf("-----------------------------------------\n");
   for(i=0; i < ARBEITER; i++)
      {
         for(j=0; j < TAGE; j++)
            {
               tmp+=zeitkonto[i][j];
            }
      }
   printf("Gesamtstunden aller Arbeiter i. d. Woche: "\
          " %d Std.\n" ,tmp);
}

/*Stundenübersicht eines einzelnen Arbeiters*/
void ArbeiterStundenUebersicht()
{
   int arb,tag;
   printf("Welcher Arbeiter: ");
   scanf("%d",&arb);
   printf("Welcher Tag: ");
   scanf("%d",&tag);
   if(arb > ARBEITER)
      {
         printf("Die Firma hat nur %d Arbeiter\n",ARBEITER);
         return;
      }
   else if(tag > TAGE)
      {
         printf("Es werden nur %d Tage gespeichert\n",TAGE);
         return;
      }
   printf("Arbeiter Nr.%d hat am Tag %d : ",arb,tag);
   printf("%d Stunden gearbeitet!\n\n" ,zeitkonto[arb-1][tag-1]);
}

/*Hauptfunktion*/
int main()
{
   int abfrage;
   int i,j;
   for(i=0; i < TAGE; i++)
      {
         printf("\n\tTag %d in der Woche\n",i+1);
         printf("\t-------------------\n\n");
         for(j=0; j < ARBEITER; j++)
            {
               printf("Arbeiter Nr.%d in Std.: ",j+1);
               scanf("%d",&zeitkonto[j][i]);
               if(zeitkonto[j][i] > 24)
                  printf("Ein Tag hat nur 24 Stunden?\n");
            }
      }

   do{
      printf("\n\n");
       printf("\t-1- Stundenwoche\n");
       printf("\t-2- Durchschnitt/Tag\n");
       printf("\t-3- Durchschnitt aller Arbeiter/Tag\n");
       printf("\t-4- Stunden aller Arbeiter/Woche\n");
       printf("\t-5- Einzelauswahl eines Arbeiters\n");
       printf("\t-6- ENDE\n");
       printf("\n\tIhre Wahl : ");
       scanf("%1d",&abfrage);
       printf("\n");

       switch(abfrage)
          {
             case 1 : ArbeiterWochenStunden();
                      break;
             case 2 : ArbeiterTagesDurchschnitt();
                      break;
             case 3 : TeamTagesDurchschnitt();
                      break;
             case 4 : TeamWochenStunden();
                      break;
             case 5 : ArbeiterStundenUebersicht();
                      break;
             case 6 : break;
             default: error(abfrage);
          }
     }while(abfrage != 6);
   return 0;
}

Die Bildschirmausgabe des Programms könnte zum Beispiel folgendermaßen aussehen:

Abbildung 14.9: Stundenverwaltung des Personals in Aktion
Abbildung 14.9: Stundenverwaltung des Personals in Aktion

Es fällt auf, dass die Funktionen immer in etwa gleich aufgebaut sind. Auf entsprechende korrekte Feldindexierung muss natürlich geachtet werden. In der Funktion ArbeiterStundenUebersicht() wird demonstriert, wie gezielt auf ein Element eines Arrays zugegriffen werden kann. Das Programm ist natürlich noch verbesserungswürdig. Warnungen, dass ein Arbeiter zu viel oder zu wenig arbeitet, ob ein Arbeiter krank war oder die Stunden als Gleitkommazahlen angegeben werden sollen, sind nur einige Vorschläge dazu.

Tatsächlich sind Arrays zwar sehr komfortabel in der Anwendung, sie sind jedoch sehr unflexibel, was die Anzahl der Elemente angeht. Die Anzahl der Elemente muss zum Zeitpunkt der Implementierung schon festgelegt werden, da sich ein Feld nicht ohne Mehraufwand dynamisch zur Laufzeit des Programms vergrößern oder verkleinern lässt. Das bedeutet, dass die Menge der im Array zu speichernden Daten schon vor Ablauf des Programms bekannt sein oder zumindest überdimensioniert werden muss.

Wenn das Array im vorgestellten Beispiel für 1000 Mitarbeiter dimensioniert würde, wäre das Programm nicht mehr benutzbar, sobald mehr als 1000 Mitarbeiter verwaltet werden sollen. Eine Lösungsmöglichkeit besteht darin, das Array sehr groß zu dimensionieren, um von vorneherein sehr große Grenzen vorzugeben, etwa MitarbeiterArray[100000].

Dieser Ansatz kostet aber sehr viel (möglicherweise) ungenutzten Arbeitsspeicher, der das Programm unter Umständen stark verlangsamt.

Da aus Performance-Gründen generell stets möglichst wenig Arbeitsspeicher von Programmen belegt werden soll, gelten Arrays bei großen Datenmengen oder stark wechselnder Anzahl der Daten als nicht so effizient wie etwa verkettete Listen.

In Kapitel 24, Dynamische Datenstrukturen, wird auf die Datenverwaltung mit verketteten Listen näher eingegangen.

14.10.1 Tic Tac Toe
Ein weiteres interessantes Beispiel zur Demonstration von zweidimensionalen Arrays ist das wohl allseits bekannte Spiel Tic Tac Toe. Sie benötigen dabei lediglich ein Kästchen von 3 x 3 Feldern. Dies lässt sich prima mit einem zweidimensionalen Array darstellen: eine Dimension für die Reihe und eine weitere für die Spalte.

char TicTacToe[3][3] = {{' ',' ',' '},
                        {' ',' ',' '},
                        {' ',' ',' '}};

Ein kurze Beschreibung des Spiels: Ein Spieler hat das Zeichen X und ein anderer das Zeichen O. Nach einem Zug ist der andere Spieler an der Reihe. Gewonnen hat der Spieler, der zuerst drei gleiche Zeichen (X oder O) in der Waagerechten, in der Senkrechten oder in der Diagonalen hat. Es gibt insgesamt acht Stellungsmöglichkeiten, um das Spiel zu gewinnen. Diese gilt es zu überprüfen. Es gibt außerdem noch eine neunte Möglichkeit, nämlich die, dass alle Felder besetzt sind, und keiner der beiden Spieler hat gewonnen. Hier der vollständige Quellcode dazu:

#include <stdio.h>
#ifdef __unix__
    #define clrscr() printf("\x1B[2J")
#elif __BORLANDC__ && __MSDOS__
    #include <conio.h>
#elif __WIN32__ || _MSC_VER
    #include <stdlib.h>
    #define clrscr() system("cls")
#else
    #define clrscr() printf("clrscr() - Fehler!!\n")
#endif

#define X 'X'
#define O 'O'
#define LEER ' '
#define GAME_OVER 0
#define A_WINNER 1
#define CONTINUE 2

/* Inhalt des 3 x 3 grossen Felds */
char TicTacToe[3][3] = {{' ',' ',' '},
                        {' ',' ',' '},
                        {' ',' ',' '}};
/* Spieler1 hat das Zeichen 'X' */
char Spieler1 = X;
/* Spieler2 hat das Zeichen 'O' */
char Spieler2 = O;
/* Anzahl der Felder, die besetzt werden können */
unsigned int felder = 9;

/* Funktionsprototypen */
void print_spielfeld();
char neuer_zug(char);
int if_win();

/* Gibt den aktuellen Zustand des Spielfelds auf dem Bildschirm
   aus */
void print_spielfeld()
{
   int i;
   clrscr();
   printf("       1   2   3  \n     +---+---+---+\n");
   for(i = 0; i < 3; i++)
      {
         printf("  %d  | ",i+1);
         printf("%c",TicTacToe[i][0]);
         printf(" | ");
         printf("%c",TicTacToe[i][1]);
         printf(" | ");
         printf("%c",TicTacToe[i][2]);
         printf(" | \n");
         if(i != 2)
            {
               printf("     +---+---+---+\n");
            }
         else
            {
               printf("     +---+---+---+\n");
            }
      }
}

/* Führt einen neuen Zug aus.
 * char ch: Zeichen des Spielers, der an der Reihe ist, 'X'
 * oder 'O'
 * Rückgabewert: Zeichen des Spielers, der eben an der Reihe war
 * falls ein Feld besetzt ist, wird der Rückgabewert vertauscht,
 * damit der aktuelle Spieler nochmals seinen Zug machen kann.
 * Hat ein Spieler gewonnen, gibt die Funktion die
 * symb. Konstante GAME_OVER zurück
 */
char neuer_zug(char ch)
{
   unsigned int row, colum;

   printf("\nSpieler \"%c\" ist an der Reihe\n\n",ch);
   printf("Zeile  (1-3): ");
   scanf("%d",&row);
   printf("Spalte (1-3): ");
   scanf("%d",&colum);

   if(TicTacToe[row-1][colum-1] == LEER)
      {
         /* Zeichen in das mehrdim. Array */
         TicTacToe[row-1][colum-1] = ch;
         print_spielfeld();
         /* Haben wir schon einen Gewinner */
         if(if_win() == A_WINNER)
            return GAME_OVER;
      }
   else
      { /* Ein bereits besetztes Feld */
         print_spielfeld();
         printf("\n!!! Feld ist bereits gesetzt !!!\n");
         return (ch == X)?O :X;
      }
   /* Sind bereits alle Felder besetzt? */
   if(--felder > 0)
      return ch;
   else
      {
         printf("\nAlle Felder sind besetzt - Unentschieden\n");
         return GAME_OVER;
      }
}

/* Auswertung aller Möglichkeiten, um einen Gewinner zu ermitteln
 * Rückgabewert: symb. Konstante A_WINNER falls ein Gewinner
 * ermittelt wurde oder die symb. Konstante CONTINUE zum
 * Weiterspielen.
 */
int if_win()
{ /* Zuerst Spieler1 'X' */
  if(TicTacToe[0][0] == Spieler1 &&
     TicTacToe[0][1] == Spieler1 &&
     TicTacToe[0][2] == Spieler1 ||
     TicTacToe[1][0] == Spieler1 &&
     TicTacToe[1][1] == Spieler1 &&
     TicTacToe[1][2] == Spieler1 ||
     TicTacToe[2][0] == Spieler1 &&
     TicTacToe[2][1] == Spieler1 &&
     TicTacToe[2][2] == Spieler1 ||
     TicTacToe[0][0] == Spieler1 &&
     TicTacToe[1][0] == Spieler1 &&
     TicTacToe[2][0] == Spieler1 ||
     TicTacToe[0][1] == Spieler1 &&
     TicTacToe[1][1] == Spieler1 &&
     TicTacToe[2][1] == Spieler1 ||
     TicTacToe[0][2] == Spieler1 &&
     TicTacToe[1][2] == Spieler1 &&
     TicTacToe[2][2] == Spieler1 ||
     TicTacToe[0][0] == Spieler1 &&
     TicTacToe[1][1] == Spieler1 &&
     TicTacToe[2][2] == Spieler1 ||
     TicTacToe[0][2] == Spieler1 &&
     TicTacToe[1][1] == Spieler1 &&
     TicTacToe[2][0] == Spieler1)
      {
         printf("Spieler1 hat gewonnen\n");
         return A_WINNER;
      }

   /* Jetzt Spieler2 'O' */
   else if(TicTacToe[0][0] == Spieler2 &&
           TicTacToe[0][1] == Spieler2 &&
           TicTacToe[0][2] == Spieler2 ||
           TicTacToe[1][0] == Spieler2 &&
           TicTacToe[1][1] == Spieler2 &&
           TicTacToe[1][2] == Spieler2 ||
          TicTacToe[2][0] == Spieler2 &&
          TicTacToe[2][1] == Spieler2 &&
          TicTacToe[2][2] == Spieler2 ||
          TicTacToe[0][0] == Spieler2 &&
          TicTacToe[1][0] == Spieler2 &&
          TicTacToe[2][0] == Spieler2 ||
          TicTacToe[0][1] == Spieler2 &&
          TicTacToe[1][1] == Spieler2 &&
          TicTacToe[2][1] == Spieler2 ||
          TicTacToe[0][2] == Spieler2 &&
          TicTacToe[1][2] == Spieler2 &&
          TicTacToe[2][2] == Spieler2 ||
          TicTacToe[0][0] == Spieler2 &&
          TicTacToe[1][1] == Spieler2 &&
          TicTacToe[2][2] == Spieler2 ||
          TicTacToe[0][2] == Spieler2 &&
          TicTacToe[1][1] == Spieler2 &&
          TicTacToe[2][0] == Spieler2)
            {
               printf("Spieler2 hat gewonnen\n");
               return A_WINNER;
            }
   return CONTINUE;
}

int main()
{
   char check = X;
   /* Leeres Spielfeld ausgeben */
   print_spielfeld();

   do{ /* War Spieler mit dem Zeichen 'X' gerade dran ... */
      if(check==X)
         { /* dann ist jetzt Spieler mit dem Zeichen 'O' dran */
            check=neuer_zug(O);
         }
      else
         { /* ... ansonsten der Spieler mit dem Zeichen 'X' */
            check=neuer_zug(X);
         }
      } while( check != GAME_OVER );
   return 0;
}

Abbildung 14.10: Das Spiel Tic Tac Toe für die Konsole
Abbildung 14.10: Das Spiel Tic Tac Toe für die Konsole

Wenn Sie jetzt noch Lust und viel Zeit haben, können Sie sich ja hinsetzen und eine Funktion basteln, um gegen den Computer antreten zu können, also eine eigene KI programmieren. Dabei können Sie so ähnlich vorgehen wie bei der Funktion if_win(), nur dass Sie auch handeln müssen. Sie sollten aber dabei beachten, dass Sie die KI so programmieren, dass der menschliche User auch noch gewinnen kann.

Hinweis
 

KI für künstliche Intelligenz steht für den Begriff einer Maschine Funktionen des Menschen, wie Denken oder Bewegungsabläufe, mithilfe von Programmen anzueignen (simulieren).

14.10.2 Dreidimensionale Arrays
Zur Veranschaulichung ein weiteres Beispiel, wie ein dreidimensionales Array direkt mit Werten initialisiert werden kann:

int dreid[2][3][4]={{{6,7,4,3},{6,4,6,9},{3,4,6,7}},
                    {{7,8,6,4},{5,99,3,5},{4,6,7,8}}};

Hier ist eine geschweifte Klammer hinzugekommen:

int dreid[][][]= {1.Feldindex{2.Feldindex{3.Feldindex}}}; 

Wenn zum Beispiel auf die erste Zahl zugegriffen werden soll, geschieht das folgendermaßen:

dreid[0][0][0]    /* erste Zahl 6 */ 

Oder auf die Zahl 99:

dreid[1][1][1]    /* die Zahl 99 */ 

Ein Beispiel dazu erspare ich mir, da Sie in der Regel selten mit einem solchen Array zu tun haben. Außerdem lässt sich so etwas nur recht schwer vorstellen.

14.11 Arrays in Tabellenkalkulation einlesen (*.CSV-Dateien)            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Kommaseparate Werte (CSV, engl. comma seperate value) lassen sich sehr gut mithilfe von Arrays erstellen. Ich möchte Ihnen ein kurzes Beispiel zeigen, wie mehrdimensionale Arrays in einer Tabellenkalkulation wie Excel oder KSpread eingelesen werden. Zuerst ein kleines Programm, das einen beliebigen Aktienstand der letzten vier Wochen bereitstellt:

#include <stdio.h>
#define WOCHEN  4
#define TAGE 7

float stand[WOCHEN][TAGE] =
           {{12.3f,13.8f,14.1f,12.2f,15.4f,16.5f,14.3f},
            {15.4f,13.6f,13.6f,14.6f,15.6f,16.3f,19.5f},
            {20.5f,20.4f,21.5f,23.4f,21.4f,23.5f,25.7f},
            {25.5f,26.6f,24.3f,26.5f,26.9f,23.6f,25.4f}};

int main()
{
   int i, j;
   printf("Tag;Montag;Dienstag;Mittwoch;Donnerstag; "\
          "Freitag;Samstag;Sonntag");
   for(i=0; i < WOCHEN; i++)
      {
         printf("\nWoche%d;",i);
         for(j=0;j < TAGE; j++)
            {
               printf("%.2f;",stand[i][j]);
            }
      }
   return 0;
}

Die Kommata zwischen den einzelnen Elementen des Feldes stand sind wichtig für CSV-Dateien. Kompilieren Sie das Programm und starten es in der Kommandozeile mit:

programmname > november.csv

Damit wird die Ausgabe des Programms programmname in eine Datei namens november.csv, welche auch gleich neu erstellt wird, umgeleitet. Jetzt befindet sich im Verzeichnis eine CSV-Datei mit dem Namen november.csv und folgendem Inhalt:

Tag;Montag;Dienstag;Mittwoch;Donnerstag;Freitag;Samstag;Sonntag
Woche0;12.30;13.80;14.10;12.20;15.40;16.50;14.30;
Woche1;15.40;13.60;13.60;14.60;15.60;16.30;19.50;
Woche2;20.50;20.40;21.50;23.40;21.40;23.50;25.70;
Woche3;25.50;26.60;24.30;26.50;26.90;23.60;25.40;

Starten Sie ein Tabellenkalkulationsprogramm wie Excel oder KSpread und öffnen Sie die CSV-Datei damit. Die eingelesenen Werte sehen in Excel beispielsweise so aus:

Abbildung 14.11: CSV-Datei, mit Excel geöffnet
Abbildung 14.11: CSV-Datei, mit Excel geöffnet

Dieses Thema wird nochmals ausführlicher in Kapitel 19, Ein-/Ausgabe-Funktionen, behandelt.

14.12 Strings/Zeichenketten (char Array)            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Arrays vom Datentyp char werden Strings genannt. Ein String ist eine Kette von einzelnen char-Zeichen mit einer abschließenden 0 (nicht gleichzusetzen mit dem Zeichen '0'). char-Arrays sind typischerweise eindimensional.

Viele Programmierer, die auf die Programmiersprache C stoßen, sind verwundert, dass es keinen eigenen Datentyp für einen String gibt. Für ein char-Array gelten nicht nur die Einschränkungen der herkömmlichen Arrays, sondern es existiert auch das Problem der maximalen Länge von Arrays. Diese scheinbare Unflexibilität kann später, wenn sie effektiv eingesetzt wird, sehr ressourcensparend und schnell sein. Belassen Sie es aber erst einmal in den nächsten Kapiteln bei den etwas eingeschränkten char-Arrays.

Mit dem char-Array können Zeichenfolgen dargestellt und verarbeitet werden. Damit können Sie Benutzerschnittstellen und Textdateien verarbeiten und erstellen.

Eine Form der Stringkonstante wurde schon öfter in diesem Buch verwendet:

printf("Ich bin die Stringkonstante");

Generell lässt sich sagen: Alles, was sich zwischen zwei Hochkommata befindet, gilt als Stringkonstante. Die Deklaration eines char-Arrays ist identisch mit der bisher bekannten Form der Array-Deklaration:

char string_array[100];

Im obigen Beispiel wird ein Array vom Datentyp char angelegt, das 100 einzelne Zeichen speichern kann. Wird das Array direkt bei der Deklaration initialisiert, dann ist es eine Stringkonstante. Dabei muss die Größe des Array-Feldes nicht mehr ausdrücklich mit angegeben werden:

char hallo[]={'H','a','l','l','o', ' ',
               'W','e','l','t','\n','\0'};

Diese Schreibweise ist ebenfalls absolut korrekt, aber sehr umständlich. Daher können Sie ein char-Array auch anders, nämlich als einen String (dtsch. Zeichenkette) deklarieren:

char hallo[] = {"Hallo Welt\n"};

Beide Variationen sind absolut gleichwertig. Hierzu die rechnerinterne Darstellung des Strings:

Abbildung 14.12: Der String
Abbildung 14.12: Der String "Hallo Welt" ist ein einfaches char-Array

Diese Zeichenkette benötigt zwölf Elemente (genauer: zwölf Bytes). Wenn Sie aber die Deklaration zu hallo[] genauer betrachten, werden Sie feststellen, dass hierfür eigentlich nur elf Zeichen erforderlich wären. Welche Bedeutung hat das zwölfte Zeichen? Bei einem String benötigen Sie immer ein Stringende-Zeichen, das das Ende eines Strings anzeigt. Das ist die Bedeutung des Zeichens '\0'. Das versehentliche Weglassen des Zeichens ist eine häufige Fehlerquelle, wenn beispielsweise Speicher für n Zeichen reserviert werden soll. Generell muss also bei einem Bedarf von n Zeichen immer für n+1 Zeichen Platz im Array reserviert werden. Hierzu ein kurzes Listing:

#include <stdio.h>

char hello1[] = {"Hallo Welt\n"};
char output[] = {"Ich bin lesbar \0 Ich nicht mehr"};
char deznu[] = {"Mich siehst du 0 Mich und die Null auch"};

int main()
{
   printf("%s",hello1);
   printf("%s\n",output);
   printf("%s\n",deznu);
   return 0;
}

Dieses Beispiel zeigt auch, wie Sie für die formatierte Ausgabe von Zeichenketten, die Formatangabe %s verwenden können (s = String). Daher benötigt ein String ein Ende-Kennungszeichen. Bei dem String

char output[] = {"Ich bin lesbar \0 Ich nicht mehr"};

werden nur die Zeichen bis '\0' angezeigt. Der hintere Teil des Strings existiert nur im Arbeitsspeicher. Da zuvor das Zeichen für das Ende des Strings '\0' steht, wird dieser Teil nie am Bildschirm ausgegeben. Im nächsten Beispiel

char deznu[] = {"Mich siehst du 0 Mich und die Null auch"}; 

wird der ganze String ausgegeben, weil das Zeichen '0' nicht gleichzusetzen ist mit dem Zeichen '\0'. Es wurde bereits erwähnt, dass es auch möglich ist, auf die einzelnen Zeichen eines Strings zuzugreifen. Wenn Sie einen String beispielsweise mithilfe einer for-Schleife auf seine Länge hin überprüfen wollen, prüfen Sie lediglich auf das Zeichen '\0'. Hier ein Beispiel dazu:

#include <stdio.h>

char hello1[] = {"Hallo Welt"};
char output[] = {"Ich bin lesbar \0 Ich nicht mehr"};
char deznu[] = {"Mich siehst du 0 Mich und die Null auch"};

int main()
{
   int i;
   printf("%c",output[0]);      /*I*/
   printf("%c'",hello1[9]);     /*t*/
   printf("%c ",deznu[5]);      /*s*/
   printf("%c",hello1[7]);      /*Gibt das Zeichen 'e' aus*/
   printf("%c",output[12]);     /*a*/
   printf("%c",deznu[5]);       /*s*/
   deznu[1]='y';                /*aus 'i' wird 'y'*/
   printf("%c\n",deznu[1]);     /*y*/

   for(i=0; hello1[i] != '\0'; i++);
      printf("Länge von '%s' = %d Zeichen\n",hello1,i);

   for(i=0; output[i] != '\0'; i++);
      printf("Länge von '%s' = %d Zeichen\n",output,i);

   for(i=0; deznu[i] != '\0'; i++);
      printf("Länge von '%s' = %d Zeichen\n",deznu,i);
   return 0;
}

Hier werden mit dem Feldindex einzelne Zeichen ausgegeben, genau wie bei den Arrays mit Zahlen. In diesem Fall wird der Text "It's easy" ausgegeben. Es ist natürlich auch möglich, den Inhalt zu verändern, etwa wie in der Zeile

deznu[1]='y'; 

geschehen. Anschließend wird mit

for(i=0; hello1[i] != '\0'; i++);

die Anzahl der Zeichen hochgezählt, die sich im String hello1[] ohne \0, befinden. Die Abbruchbedingung

hello1[i]!='\0';

ist so lange wahr, bis der Inhalt von hello1[i] == '\0' ist. In diesem Beispiel wäre das bei hello1[11]der Fall, da sich hier das Zeichen '\0' befindet. Beachten Sie hier, dass hinter der for-Schleife ein Semikolon steht. Es gibt in diesem Fall keinen Anweisungsblock zur for-Schleife. Hier wird die Variable i so lange hochgezählt, bis das Stringende-Zeichen '\0' erreicht wird. Gleiches gilt für die anderen beiden Strings.

14.12.1 Vom String zur Binärzahl
Oben wurde behauptet, dass es in C keine Datentypen gibt, welche Zeichen darstellen können. Die Zeichen wurden mithilfe der ASCII-Code-Tabelle kodiert. Wie verhält sich dies jetzt mit der folgenden Stringkonstante?

char str[] = {"Hallo!\n"};

Wird dieser String in seine einzelnen Zeichen zerlegt, ergibt sich die Zeile:

/* gleichwertig zu "Hallo!\n" */
char str[] = { 'H', 'a', 'l', 'l', 'o', '!', '\n', '\0' };

Werden die einzelnen Zeichen jetzt anhand der ASCII-Code-Tabelle dekodiert, sieht der String schon anders aus:

char str[] = { 72, 97, 108, 108, 111, 33, 10, 0 }; 

Theoretisch könnten Sie den String auch so angeben und ausgeben lassen, wie das folgende Listing demonstriert:

#include <stdio.h>

int main()
{
   char str[] = { 72, 97, 108, 108, 111, 33, 10, 0 }; //Hallo!\n
   printf("%s\n",str);
   return 0;
}

Wenn Sie jetzt noch die einzelnen Werte in Binärzahlen umrechnen, können Sie den String aus der Sicht des Computers betrachten:

0100100001100001011011000110110001101111001000010000101000000000

Diese Erklärung soll Ihnen nur zeigen, dass Zeichenketten nicht magischer sind als ganz normale Zahlen-Arrays und in gewisser Hinsicht auch solche sind. Einen Beweis? Bitte sehr:

#include <stdio.h>

int main()
{
   int i;
   int str[] = { 72, 97, 108, 108, 111, 33, 10, 0 };
   for(i=0; i<sizeof(str)/sizeof(int); i++)
      printf("%c",str[i]);
   return 0;
}

Ein kleines Programm demonstriert im Folgenden den Umgang mit Strings. Das Programm durchläuft eine Zeichenkette und wandelt alle Stringfolgen "und" in Großbuchstaben um:

#include <stdio.h>

char undbig[] = {
                 "Hund und Katze sind nicht ohne "
                 "Grund des Menschens bester Freund\n"
                };

int main()
{
   int i;

   for(i=0; undbig[i] != '\0'; i++)
      {
         if(undbig[i-1]==' '&& (undbig[i]=='u'||undbig[i]=='U'))
            {
               if(undbig[i+1]=='n'&&
                  undbig[i+2]=='d' && undbig[i+3]==' ')
                  {
                     undbig[i]='U';
                     /* n zu Grossbuchstabe konvertieren (N) */
                     undbig[i+1]-=32;
                     /* d zu Grossbuchstabe konvertieren  (D) */
                     undbig[i+2]-=32;
                  }
            }
      }
   printf("%s",undbig);
   return 0;
}

Zu Beginn des Programms sehen Sie, wie Sie eine Stringkonstante über mehrere Zeilen schreiben können.

char array[] = {"Eine Zeichenkette über"
                "2 Zeilen\n"};
/* Alternative */
char array[] = {"Eine Zeichenkette über \
2 Zeilen"};

Beide Schreibweisen erfüllen den gleichen Zweck. Weiter mit der ersten for-Schleife des Programms:

for(i=0; undbig[i] != '\0'; i++)  

Hier wird der String zeichenweise durchlaufen, bis das Stringende-Zeichen '\0' gefunden wird. Bei der nächsten Anweisung

if(undbig[i-1] == ' ' && (undbig[i]=='u' || undbig[i]=='U')) 

wird überprüft, ob das Zeichen bei dem sich der Feldindex gerade befindet, ein kleines 'u' oder großes 'U' und das Zeichen davor ein Whitespace-Zeichen (Leerzeichen) ist. Falls nicht, wird i in der for-Schleife um den Wert 1 inkrementiert. Wird ein 'u' oder 'U' gefunden, folgt die Überprüfung

if(undbig[i+1]=='n' && undbig[i+2]=='d' && undbig[i+3]==' ') 

darauf hin, ob die nächsten beiden Zeichen 'n' und 'd' sind und ob sich dahinter ebenfalls ein Whitespace-Zeichen befindet. Falls dies ebenso zutrifft, wurde eine Zeichenfolge "und" gefunden. Dann werden die einzelnen Zeichen geändert:

undbig[i]='U';
undbig[i+1]-=32;  /* 'n' - 32 */
undbig[i+2]-=32;  /* 'd' - 32 */

Sehen Sie sich zum besseren Verständnis auch die ASCII-Tabelle im Anhang an. Sehen Sie nach, welche Dezimalwerte die Zeichen 'n' und 'd' haben. In Dezimalwerten würden die Subtraktionen der Werte so aussehen:

110-32=78 und 100-32=68 

In der ASCII-Tabelle finden sich 'N' = 78 und 'D' = 68.

14.13. Einlesen von Strings            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Die Verarbeitung von Strings ist nicht so leicht, wie Sie vielleicht zunächst vermuten würden. Sehen Sie sich zur Veranschaulichung folgendes Programm an:

#include <stdio.h>

int main()
{
   char string[100];
   printf("Geben sie ein paar Wörter ein: ");
   scanf("%99s",&string[0]);

   printf("Ihre Eingabe: %s\n",string);
   return 0;
}

Es wird angenommen, dass folgende Eingabe vorgenommen wurde:

Programmieren in C macht Spass

Folgende Ausgabe bekommen Sie dabei auf dem Bildschirm zu sehen:

Abbildung 14.13: Probleme beim Einlesen von Strings mit scanf
Abbildung 14.13: Probleme beim Einlesen von Strings mit scanf

Wo sind die restlichen Zeichen? Das Problem liegt hier bei der Funktion scanf(). Denn scanf() liest lediglich bis zum ersten Leerzeichen ein. Also wird eine andere Funktion zum (sicheren) Einlesen von Zeichenketten benötigt.

Gut geeignet wäre die Funktion fgets(), die später noch genauer erklärt wird. Kurz zur Syntax von fgets():

char *fgets(char *string,int anzahl_zeichen,FILE *stream);

Diese Funktion soll jetzt gegen die Funktion scanf() im Programmbeispiel ausgetauscht werden:

#include <stdio.h>

int main()
{
   char str[100];
   printf("Geben sie ein paar Wörter ein : ");
   fgets(str, 100, stdin);

   printf("Ihre Eingabe: %s\n",str);
   return 0;
}

Bei diesem Beispiel werden mit fgets() vom Stream stdin maximal 100 Zeichen in das char-Array str eingelesen, beginnend mit der Anfangsadresse von str. Der Vorteil von fgets() ist dabei, dass mit dieser Funktion keine gefährlichen so genannten Pufferüberläufe (Buffer Overflow) passieren können. Sollten Sie in diesem Beispiel 120 Zeichen eingegeben haben, liest fgets() davon 98 sichtbare Zeichen plus Newline-Zeichen (\n) plus Stringende-Zeichen (\0) ein. fgets() hängt am Ende des Strings immer ein \n-Zeichen an.

Zum Einlesen von Strings noch ein Programmbeispiel:

#include <stdio.h>
/*Passwort*/
char p[]=  {"123xyz456"};

int check_passwort(char passw[])
{
   int i, n =  sizeof(p)/sizeof(char);
   for(i=0; i < n; i++)
      if(passw[i] != p[i])
         return 0; /*Falsches Passwort*/
   return 1;   /*Richtiges Passwort*/
}

int main()
{
   char Name[20];
   char passwort[10];

   printf("Login-Name : ");
   fgets(Name, 20, stdin);
   printf("Passwort   : ");
   fgets(passwort, 10, stdin);

   if( check_passwort(passwort) == 1)
      printf("Willkommen im System %s\n",Name);
   else
      printf("Falsches Passwort! Systemzugriff verweigert.\n");
   return 0;
}

Hier haben Sie ein Programm für eine einfache Passwort-Abfrage, die innerhalb der Funktion check_passwort ausgeführt wird. Die Funktion durchläuft n Zeichen.

Stimmen alle eingegebenen Zeichen überein, gibt die Funktion 1 zurück. Stimmt ein Zeichen nicht überein, beendet sich die Funktion mit dem Rückgabewert 0.

In Kapitel 15, Zeiger (Pointer), werden Sie noch mehr über Strings erfahren.

Weiter mit 14.14. Standard-Bibliothek - <string.h>            zum Inhaltsverzeichnis