Unterabschnitte

18.1 Aufgaben

18.1.1 Unser erstes

Ein Programm ist zum Laufen zu bringen, welches den Text Hallo! auf den Bildschirm schreibt.


18.1.2 Zeilenvorschub

Für diese Aufgabe ist die Funktion printf() nötig; diese ist in stdio.h definiert.

Der Text Hallo! ist in einer Zeile auf den Bildschirm zu schreiben, danach sollen 2 Leerzeilen kommen und in der 4. Zeile der Text: Ich bin Dein dummer Rechner! .

Die ganze Ausgabe soll also so aussehen:

Hallo!


    Ich bin Dein dummer Rechner!

Das Zeichen soll dabei ein Leerzeichen darstellen; die letzte Zeile sollte also mit 4 Leerzeichen beginnen.

18.1.3 Ganze Zahlen ausgeben (1)

Die Quadratzahlen (1, 4, 9, 16, usw.) bis 100 sind in jeweils einer Zeile auf den Bildschirm zu schreiben.

18.1.4 Ganze Zahlen ausgeben (2)

Die Zahlenfolge -1, 1, -4, 4, -9, 9, -16, 16 ist analog zur vorherigen Aufgabe auszugeben.

18.1.5 Tage im Monat

In dem folgenden Fragment ist die Funktion tage() zu ergänzen:

#include <stdio.h>

int tage( int monat )
{
  /* ??? */
}

main()
{
  printf( "Januar hat %d Tage\n",     tage(1)  );
  printf( "Februar hat %d Tage\n",    tage(2)  );
  printf( "Maerz hat %d Tage\n",      tage(3)  );
  printf( "April hat %d Tage\n",      tage(4)  );
  printf( "Mai hat %d Tage\n",        tage(5)  );
  printf( "Juni hat %d Tage\n",       tage(6)  );
  printf( "Juli hat %d Tage\n",       tage(7)  );
  printf( "August hat %d Tage\n",     tage(8)  );
  printf( "September hat %d Tage\n",  tage(9)  );
  printf( "Oktober hat %d Tage\n",    tage(10) );
  printf( "November hat %d Tage\n",   tage(11) );
  printf( "Dezember hat %d Tage\n",   tage(12) );
}

Schaltjahre brauchen nicht berücksichtigt zu werden.

tage() soll also beim Aufruf mit dem Argument 1 den Wert 31 liefern, beim Aufruf mit 2 den Wert 28 usw..

18.1.6 strcpy() selbst schreiben

Bei dieser und den folgenden Aufgaben werden Zeiger benötigt. Weiterhin muß man wissen, wie in C ein String aussieht (abschließende Null!).

Die Funktion

char *strcpy( char *wohin, const char *woher );
ist zu programmieren und auszutesten.

strcpy() kopiert ab woher Zeichen nach wohin, bis das Stringende von woher erreicht ist, also bis ein Nullbyte kopiert wurde. Zurückgeliefert wird ein Zeiger auf die erzeugte Zeichenkette, also der Wert von wohin.

18.1.7 strcat() selbst schreiben

Die Funktion

char *strcat( char *wohin, const char *woher );
ist zu programmieren und auszutesten.

strcat() kopiert ab woher Zeichen hinter das letzte Zeichen des Strings ab wohin, bis das Stringende von woher erreicht ist, also bis ein Nullbyte kopiert wurde. Zurückgeliefert wird ein Zeiger auf die erzeugte Zeichenkette, also der Wert von wohin.


18.1.8 Pascalstrings

Mit einem Datentyp

typedef char        pstring[81];
lassen sich Pascal-ähnliche Strings nachbilden.

Das nullte Element speichert dabei die aktuell verwendete Länge; in den Positionen 1 bis maximal 80 wird der eigentliche String gehalten.

Solche Strings dürfen im Gegensatz zu C-Strings auch Nullbytes speichern, da dieses Zeichen nicht als Stringende benötigt wird.

Dementsprechend können die Funktionen aus der C-Bibliothek nicht zur Manipulation der pstrings verwendet werden.

Aufgabe ist es, folgende Funktionen zu schreiben:

Die Funktionen brauchen keine Fehlerüberprüfung.

18.1.9 Stringausgabe

Für diese Aufgabe benötigt man die Funktion putchar() aus der Standardbibliothek und etwas Kenntnisse von Zeigern und Strings.

Das folgende Programm ist um den fehlenden Rest der Funktion schreibwas() zu ergänzen:

/* Hier ist putchar() vereinbart:
 */
#include <stdio.h>

/* Der String ab der Stelle s wird zeichenweise auf dem
 * Bildschirm ausgegeben, anschließend ein Zeilenvorschub.
 */
void schreibwas( char *s )
{

        /* ???   */
}

/* Testprogramm.
 */
main()
{
       schreibwas( "hallo!" );
       schreibwas( "nochmal hallo!" );
}

schreibwas() soll den übergebenen String zeichenweise auf den Bildschirm ausgeben und anschließend einen Zeilenvorschub erzeugen, also genauso arbeiten wie die Funktion puts() aus der Standardbibliothek. puts() ist für die Lösung dieser Aufgabe aber nicht zu benutzen! Vielmehr soll der String zeichenweise ausgegeben werden, bis die abschließende Null erreicht ist.


18.1.10 Struktur für 5 Personen

Nötige Kenntnisse: Strings, Felder, Strukturen, printf() und scanf() aus der Standardbibliothek.

Zu schreiben ist ein kleines Programm, welches eine Struktur als Datentyp vereinbart zum Speichern einer Person. Eine Person soll aus Vorname, Nachname und Alter bestehen.

Von diesem Datentyp ist ein Feld für 5 Personen zu definieren.

Weiterhin soll eine Funktion enthalten sein, welche die Daten einer Person von der Tastatur einliest, und eine Funktion, welche die Daten einer Person ausgibt.

Das Hauptprogramm liest in einer Schleife mit der erstgenannten Funktion alle 5 Personen ein. Danach erst werden mit der zweiten Funktion alle 5 Personen wieder ausgegeben.

18.1.11 Struktur für 10 Personen

Wie die vorhergehende Aufgabe Struktur für 5 Personen, aber für 10 Personen!

18.1.12 lsearch()

Für diese Aufgabe muß man etwas mit Zeigern umgehen können, außerdem braucht man casts für Typumwandlungen, Zeiger auf Funktionen und Felder.

Das Beispiel zum Aufruf der zu schreibenden Funktion verwendet Strukturen; für die Aufgabe selbst ist deren Kenntnis aber unwichtig.

Eine Funktion mit der Deklaration

void *lsearch( const void *schluessel,
               const void *feldanfang,
               size_t elemente,
               size_t elementgroesse,
               int(* vergleichsfkt)( const void *e1,
                                     const void *e2
                                   )
             );

ist zu schreiben. Die Funktion soll in einem eventuell unsortierten Feld ab feldanfang mit linearer Suche ein Element finden, für das die Funktion (*vergleichsfkt)() beim Vergleich mit dem Element (*schluessel) den Wert 0 liefert, also Gleichheit.

Wird ein solches Element gefunden, dann wird ein Zeiger darauf zurückgegeben. Wird kein Element gefunden, dann soll der NULL-Zeiger zurückgegeben werden.

Der Aufruf ist identisch mit dem von bsearch() aus der Standardbibliothek.

Das Feld ab feldanfang muß (elemente) Werte beinhalten; jedes Element hat die Größe (elementgroesse).

vergleichsfkt zeigt auf eine Funktion, die man mit 2 Zeigern als Parameter aufrufen kann und die ein Resultat vom Typ int liefert. Wenn die Parameter auf Daten von dem Typ zeigen, den auch jedes Element in dem Feld ab feldanfang hat, dann muß diese Funktion den Wert 0 liefern, wenn die beiden Daten als gleich zu betrachten sind.

Für den Parameter vergleichsfkt kann man also gegebenfalls die gleiche Funktion verwenden wie für bsearch() und qsort().

Der Rückgabewert von lsearch() ist ein Zeiger auf das gefundene Element, oder NULL, wenn in dem Feld kein Element steht, für das (*vergleichsfkt) beim Vergleich mit dem Schlüssel (*schluessel) den Wert 0 liefert.

lsearch() soll ebenso wie qsort() oder bsearch() für beliebige Datentypen funktionieren, da die Behandlung der Daten (Feld und Schlüssel) nur in der übergebenen Vergleichsfunktion erfolgt, die wiederum vom Aufrufer zur Verfügung zu stellen ist.

lsearch() soll von der Funktion und vom Aufruf her identisch sein mit bsearch(), außer daß das Feld für bsearch() sortiert sein muß. Für lsearch() ist dies nicht nötig.

18.1.12.1 Beispiel:

Es wird in einem Programm eine Struktur gebildet, in der ein Autoname und der zugehörige Intelligenzquotient des Fahrers Platz finden. Dieser Datentyp soll Auto_t heißen:
typedef struct
{
    char    name[30];   /* Autoname          */
    int     iq;         /* IQ des Fahrers    */
}
Auto_t;

In dem Beispielprogramm wird von diesem Typ ein Feld vereinbart und mit einigen Werten initialisiert.

Der Benutzer wird beim Programmlauf aufgefordert, einen Autonamen einzugeben. Dann sucht das Programm mit der noch unbekannten Funktion lsearch() nach dem Feldelement, in dem der eingegebene Autonamen enthalten ist und gibt den zugehörigen IQ aus.32

/* aulsear.c 30. 7.95 kw
 * Beispielprogramm fuer lsearch().
 */

#include <string.h>

/* lsearch() deklarieren:
 */
void *lsearch( const void *schluessel,
               const void *feldanfang,
               size_t elemente,
               size_t elementgroesse,
               int(* vergleichsfkt)( const void *e1,
                                     const void *e2
                                     )
               );

/* Den Datentyp Auto_t vereinbaren:
 */
typedef struct
{
  char    name[30];   /* Autoname         */
  int     iq;         /* IQ des Fahrers   */
}
Auto_t;

/* Davon ein Feld vereinbaren und initialisieren:
 */
#define N       4
Auto_t  Auto[N] =
{   { "525",   40 },
    { "190",   20 },
    { "Manta", 0  },
    { "Polo",  80 }
};

/* Die fuer lsearch() noetige Vergleichsfunktion erhaelt
 * zwei Zeiger auf Daten, in unserem Fall vom Typ Auto_t.
 * Sind die darin enthaltenen Autonamen gleich, dann
 * soll der Wert 0 geliefert werden.
 * Die Unterscheidung zwischen "groesser" und "kleiner"
 * ist fuer lsearch() nicht noetig, wird aber so gleich
 * mitgeliefert:
 */
int verglname( void *Auto1, void *Auto2 )
{
  return strcmp( ((Auto_t*)Auto1)->name,
                 ((Auto_t*)Auto2)->name  );
}


/* Das Hauptprogramm liest von der Standardeingabe einen
 * Autonamen und versucht, den IQ dazu zu finden:
 */

int main()
{
  Auto_t
    suchauto,               /* danach suchen            */
    *gefunden;              /* Zeiger auf gefundenes    */
                                /* Auto                     */
  /* Den Autonamen lesen:
   */
  puts( "Bitte einen Autonamen:" );
  gets( suchauto.name );

  /* suchauto dient fuer lsearch() als Schluessel.
   * Da in der Vergleichsfunktion verglname() nur
   * der Name verwendet wird, ist der iq in suchauto
   * egal.
   */

  /* Jetzt mit lsearch() einen Zeiger auf das Element
   * holen, das den gleichen Namen enthaelt wie
   * suchauto:
   */
  gefunden = lsearch( &suchauto,
                      Auto,
                      (size_t)N,
                      sizeof(Auto_t),
                      verglname       );

  /* Wenn gefunden ungleich NULL ist, dann wurde ein
   * Element gefunden:
   */
  if( gefunden )
    printf( "Der IQ eines %s-Fahrers ist etwa %d\n",
            gefunden->name,
            gefunden->iq
            );
  else
    printf( "%s-Fahrer kenne ich nicht.\n",
            suchauto.name
            );

  return 0;
}

Gesucht ist also die Funktion lsearch().


18.1.13 Zahlen aus Datei lesen

Voraussetzungen: Öffnen von Dateien und Lesen daraus.

Aus der Textdatei werte.dat, die so aussehen kann:

4711
4712
42
0
0
4713

sollen Zahlenwerte vom Typ int gelesen werden, bis das Dateiende erreicht wird.

Das zu schreibende Programm soll die Zahlen einlesen, die Anzahl der gelesenen Werte ausgeben sowie den Durchschnitt der Zahlen.

18.1.14 Operatoren auf die Schnelle

Welchen Wert hat in C der Ausdruck -1<0<1 ?


18.1.15 Mit Zeigern jonglieren, aber richtig! (I)

Hierfür muß man mit Zeigern gut umgehen können.

Die Aufgabe lautet: Was macht das folgende Programm33? Kann es überhaupt richtig funktionieren?

/* aujong1.c 30. 7.95 kw
 * Was macht dieses Programm?
 */

char *c[]  =
{  
  "he dast ga",
  "lllt dumm",
  "C i",
  "dar nich"
};

char **cp[] =
{
  c+3,
  c+2,
  c+1,
  c
};


char ***cpp  =   cp;

main( int Anzarg, char **Arg )
{
  printf( "%s", **++cpp );
  printf( "%s", *--*++cpp+5 );
  printf( "%s", cpp[-2][0]+2 );
  printf( "%s\n", *(cpp[1]+1)+3 );
  getchar();
}

Das Programm soll nicht abgetippt und ausprobiert werden, bis die Lösung gefunden ist! Vielmehr soll die Lösung nur durch Überlegen gefunden werden. Das Aufzeichnen der Variablen auf einem Blatt Papier erleichtert die Lösung enorm.

Noch mehr Aufgaben dieser Art findet man in [C-Puzzle].


18.1.16 Mit Zeigern jonglieren, aber richtig! (II)

Für diese Aufgabe muß man etwas mit Zeigern umgehen können, außerdem braucht man casts für Typumwandlungen, Zeiger auf Funktionen und Felder. Schließlich muß man auch die Rekursion verstanden haben.

Aufgabe ist, die Funktion qsort() aus der Standardbibliothek selbst zu schreiben.

Zum Testen kann das Beispielprogramm zu qsort() zweckentfemdet werden.

Der quick sort-Algorithmus sortiert ein Feld folgendermaßen:

Die Rekursion bricht ab, wenn ein Teilfeld weniger als zwei Elemente hat.

Zur Auswahl des Elementes: Am günstigsten wäre es, das Element so zu wählen, daß die entstehenden Teilfelder etwa gleich groß sind. Da aber dieses Element nicht bekannt ist, wählt man einfach das Element in der Feldmitte.


18.1.17 Kleiner Praeprozessor

Zu schreiben ist ein Programm pp, welches einige Funktionen des Präprozessors von C ausführt.

Folgende Funktionen sollten mindestens enthalten sein:

Als Bonusaufgabe darf das Programm auch gerne die #define-Anweisung mit Parametern beherrschen!

Die anderen Direktiven sowie die Spezialitäten # und ## brauchen nicht vorgesehen zu werden.

Das Programm soll mit zwei Parametern aufgerufen werden.

Der zu schreibende Praeprozessor liest also eine Datei und erzeugt in einer weiteren Datei eine Ausgabe. Nach den üblichen Regeln des C-Praeprozessors sollen dabei mit #include weitere Dateien in die Ausgabe eingefügt werden, mit #define definierte Namen durch ihren Wert ersetzt werden, und so fort.

Die mit #include eingefügten Dateien durchlaufen wiederum den Praeprozessor.

Da das fertige Programm unabhängig vom C-Compiler ist, kann man es genausogut zum Entwickeln von FORTRAN- oder Pascal-Programmen oder zur Arbeit mit TEX verwenden.

Auf Fehleingaben (zu wenige, zu viele Parameter, falsche Namen etc.) soll das Programm sinnvoll reagieren (Erklärung ausgeben, abbrechen...).

AnyWare@Wachtler.de