«C» Programmierung

Analyse, Design, ANSI-C-Implementation, Test (Version: 12. Juni 2021)

INHALTSVERZEICHNIS

1. Programmier-Grundlagen

1.1 Compiler- versus Interpretersprache

Es gibt viele verschiedene Programmiersprachen. Jede hat seine spezifischen Eigenschaften. Es wird unterschieden zwischen:

  • Interpreter-Sprache: Bsp.: Powershell, JavaScript, PHP etc. Der Programmcode wird von einem speziellen Programm interpretiert dh. in Maschinencode übersetzt. Z.B. Bei JacaScript-Code ist der Webbrowser der Interpreter.
  • Compiler-Sprache: Bsp.: C/C++, Pascal, Java, Basic etc. Das in Quellcode vorliegende Programm muss mit einer Compiler/Linker-Software in maschinenlesbaren Code umgewandelt werden. Bei Windows hat maschinenlesbarer Code die Extension xx.exe.

1.2 Kompilieren und Linken

Nur bei Kompilersprachen: Der als normalen Text lesbare Programmcode wird in die binären Maschinenkommandos übersetzt bzw. kompiliert. Die einzelnen Schritte am Beispiel vom ANSI-C Compiler LCC:

  1. Erstellen des C-Quell-Programms oder Source-File hello.c

  2. Kompilieren von hello.c:
    c:\<LCC_Programmpfad>\lcc\bin\lcc hello.c -o hello.obj
  3. Linken von hello.obj:
    c:\<LCC_Programmpfad>\lcc\bin\lcclnk hello.obj -o hello.exe
  4. Das fertige Programm hello.exe kann nun ausgeführt bzw. getestet werden.
    Das hello.exe File ist ein ausführbares Programm. Das File beinhaltet nun ausschliesslich nativen Maschinencode und kann nicht ohne weiteres vom Programmierer gelesen werden.
Sollte das Programm Fehler ausweisen, müssen diese im Source-File hello.c behoben werden und anschliessend muss neu kompiliert und gelinkt werden.

1.3 Analyse/Design/Implementation/Test

Beim Programmieren geht man IPERKA-ähnlich vor. Die Entwicklung ist in vier nacheinanderfolgende Schritte aufgeteilt:

  • Analyse: Studium der Anforderungen, Termine, Programmeigenschaften wie Eingabe/Verarbeitung/Ausgabe
  • Design: Lösungsideen finden, Programmentwurf erstellen, Struktogramm/Programmablaufplan/State-Event-Diagramm erstellen. Hinweis: In unserem Kurs behandeln wir nur die Nassi-Shneiderman Struktogramm-Erstellung.
  • Implementation: Codieren der Aufgabe gemäss den Erkenntnissen aus der Designphase Struktogramm/Programmablaufplan/State-Event-Diagramm. Die zu verwendende Programmiersprache wird aufgrund der Anforderungen erst in diesem Schritt festgelegt. In unserem Kurs beschränken wir uns auf die Programmiersprache C.
  • Testen: Informelle Tests versus formelle Tests: Formelle Tests werden geplant, d.h. es werden geeignete Testfälle definiert und dokumentiert. Beim eigentlichen Test der SW wird der Prüfling (=die SW) gemäss den Testfällen geprüft und die Testresultate in einem Testprotokoll dokumentiert. Sollte der Test unbefriedigend ausfallen, muss der Prüfling in der Programmierabteilung überarbeitet und danach nochmals vollständig gemäss den definierten Testfällen gecheckt werden. Damit will man einer "Verschlimmbesserung" entgegenwirken. In unserem Kurs werden wir formelle Tests durchführen.

1.4 Der strukturierte Programmentwurf

Erklärung anhand einer Aufgabenstellung:
Es wird eine Zahl eingegeben. Wenn die Zahl grösser als 0 ist, dann erfolgt die Ausgabe "Positive Zahl", sonst wird "Null" oder "negative Zahl" ausgegeben. Das Programm wird mit der Ausgabe "Programm-Ende" beendet.

Darstellung obiger Aufgabe mit einem Struktogramm: (Zu lesen von oben nach unten!)

Struktogramme sorgen dafür, dass die entstehende Programmstruktur automatisch den Vorgaben der strukturierten Programmierung entspricht:

  • Das Programm hat ein gemeinsamer Beginn und ein gemeinsammes Ende, keine Querein- und Ausstiege und damit Verhinderung von Spaghetti-Code (Go-To)
  • Unterprogramm (Abstraktion/Prozedur/Funktion) mit Rücksprung an die Stelle des Hauptprogramms, wo dieses für das Unterprogramm verlassen wurde.

1.5 Struktogrammelemente nach Nassi-Shneidermann


1.6 Analyse, Design und Test eines Musterprojekts

Aufgabenstellung:

Gegeben sei ein Parkhaus für Motorräder und Autos. Es befinden sich also zwei- und vierrädrige Fahrzeuge im Gebäude. Aus der Gesamtzahl der Räder und Fahrzeuge soll bestimmt werden, wieviele davon Motorräder bzw. Autos sind.

Die Analyse:

  • Auftraggeber, Sachbearbeiter, Termine, etc.
  • Zu lösende Aufgabe: Aus der Anzahl Räder und Fahrzeuge soll bestimmt werden, wieviele Motorräder und Autos sich in einem Parkhaus befinden.
  • Eingabe: Alle Eingaben erfolgen interaktiv durch Aufforderung des Programms:
    1. Grösse: Anzahl Fahrzeuge / Ganzzahl / Positive Zahl
    2. Grösse: Anzahl Räder / Ganzzahl / Muss eine gerade Zahl sein und mindestens zweimal so gross, und maximal viermal so gross wie die Anzahl der Fahrzeuge
  • Ausgabe:
    1. Grösse: Anzahl Motorräder (noch unbekannt)
    2. Grösse: Anzahl Autos (noch unbekannt)
  • Weitere Anforderungen: Mit veränderten Eingaben soll eine neue Berechnung durchgeführt werden. Nach Resultatausgabe soll entschieden werden können, ob eine weitere Berechnung stattfinden soll. Die gemachten Eingaben müssen mit der Ausgabe sichtbar sein. Fehleingaben müssen durch das Programm erkannt und zurückgewwiesen werden. Keine Speicherung der Resultate erforderlich.

Der Design:

Zuerst widmen wir uns der kreativsten und schwierigsten Sache bei der Realisierung eines Programms, der Lösungsidee. Dabei spielen wir ein paar Zahlenfälle durch:

  • Annahme: Im Parkhaus befinden sich 20 Fahrzeuge mit 50 Rädern
  • Fall 1: Hätte es nur Motorräder im Gebäude, dann wären das "nur" 2 x 20 = 40 Räder
  • Fall 2: Ausgehend von Fall Nr. 1 ersetzen wir ein Motorrad durch ein Auto: Die Anzahl der Räder nimmt damit um 2 zu.
    19 Motorräder und 1 Auto gibt zusammen: 2 x 19 + 4 x 1 = 42 Räder. (Aber immer noch keine 50)
  • Fall 3: Ausgehend von Fall Nr. 1 ersetzen wir nun zwei Motorräder durch zwei Autos: 18 Motorräder und 2 Autos gibt zusammen: 2 x 18 + 4 x 2 = 44 Räder.
Setzt man nun dieses Verfahren systematisch fort so ergibt sich:

  • 17 Motorräder und 3 Autos gibt zusammen: 2 x 17 + 4 x 3 = 46 Räder
  • 16 Motorräder und 4 Autos gibt zusammen: 2 x 16 + 4 x 4 = 48 Räder
  • 15 Motorräder und 5 Autos gibt zusammen: 2 x 15 + 4 x 5 = 50 Räder
Dieses Verfahren kann programmmässig durch eine Schleife gelöst werden. Die Schleife soll so lange durchlaufen werden, bis die verlangte Anzahl Räder erreicht ist.

Struktogramm der Lösungsidee:

(Nicht berücksichtigt ist die Überprüfung der eingegebenen Grössen auf ihre Richtigkeit)

Die Implementation:

Auf die Codierung wird hier nicht weiter eingegangen.

Das Testen:

Mit Hilfe des Testens wird überprüft, ob die gestellten Anforderungen an das Programm erfüllt werden. Jeder Test soll eine andere Situation testen. Zum Beispiel "Abbruch im Falle von zu grossen Zahlen", "Abbruch bei Text- statt Zahleneingabe", "Erfolgreiche Berechnung bei Zahlen in Grenzwerten".
Im Idealfall sollte beim Testen jede Programmzeile und Stelle einmal durchlaufen worden sein. Das Bestimmen von Tests, die ein anderes Verhalten testen, nennt man das Bilden von sogenannten Äquivalenzklassen.
Formell testen: Mit Formell ist Schriftlich gemeint. Man erhält damit u.a. eine schriftliche Quittung, das überhaupt und was dabei konkret getestet wurde und hat damit auch eine Absicherung gegen spätere Mängelrügen des Auftraggebers. Zudem dient das Protokoll als ToBeDone-Liste für denjenigen, der die Fehler danach beheben muss. (z.B. der Programmierer)
Bei einer nicht akzeptierten bzw. fehlerhaften Lieferung muss beachtet werden, dass nach der Beseitigung der Fehler durch den Programmierer, aus Programmversion V1.0 wird V1.1, alles nochmals nach der selben Testvorschrift getestet werden muss, um einer Verschlimmbesserungen vorzubeugen. Ausserdem soll der Programmierer nicht gleichzeitig Tester sein, damit eine Unabhängikeit gewährleistet ist.
Das Gegenteil von formellen Tests sind informelle Tests wie z.B. das Debuggen von Programmen während der Implementation.
Wichtig für die Testfälle ist, dass sie konkrete und demnach auch reproduzierbare Ein- bzw. Ausgabewerte enthalten und das am Schluss im Testbericht eindeutig entschieden ist, ob die Lieferung akzeptiert oder zurückgewiesen wird.

Dokumentation der Testfälle:

Testbericht:

Dies ist die Zusammenfassung der Testresultate.


2. Erste Schritte in C

2.1 Die wichtigsten Datentypen

Weitere Datentypen entnehme man der entsprechenden C-Literatur.

2.2 Die Ausgabe mit printf

#include <stdio.h>

void main(void)
{
  printf("Ich bin ein C-Programm \n");
  printf("**********************");
}

Steuerzeichen für die Ausgabe mit printf
\n    NL (new line) Der Cursor geht zum Anfang der nächsten Zeile.
\t    HT (horizontal tab) Der Cursor geht zur nächsten horizontalen Tabulatorposition.
\"    Das " Zeichen wird ausgegeben.
\'    Das ' Zeichen wird ausgegeben.
\?    Das ? Zeichen wird ausgegeben.
\\    Das \ Zeichen wird ausgegeben.

Die Ausgabe von Variablen mit printf:

Im printf keine Umlaute wie z.B. ä,ö,ü

#include <stdio.h>

void main(void)
{
  int x=10;
  float wert=0.0;
  char ch='A';

  printf("Der Integerwert lautet %i \n",x);
  printf("Der Floatwert lautet %f \n",wert);
  printf("Das Charakter ist ein %c \n",ch);
}

2.3 Inline-Kommentare

void main(void)
{
  /* Dies ist Kommentar und wird vom Compiler ignoriert */
  // Dies ist ebenfalls ein Kommentar,
  // aber nicht mehr im Standard C
  printf("Hello World \n");
}

2.4 Eingabe mit scanf

(Achtung : das &-Zeichen vor der Variable nicht vergessen !)

#include <stdio.h>

void main(void)
{
  /* Deklaration und Initialisierung der Variablen */
  int x=0;
  char ch=' ';

  printf("Bitte eine Zahl eingeben : ");
  scanf("%i",&x);

  printf("Bitte ein Buchstabe eingeben : ");
  fflush(stdin);    /* Leeren des Tastatureingabespeichers */
  scanf("%c",&ch);

  printf("Sie tippten die Zahl %i und den Buchstaben %c ein.\n",x,ch);
}

Beachten Sie das Eingabeformat bei Dezimalzahleingaben (float):
zB.  Zahl : 12.0

2.5 Die Grundrechenarten + , - , * , / , % , ()

#include <stdio.h>

void main(void)
{
  int x=0;
  int y=0;

  x=x+1;
  y=x*3-(2+1);
  y=y%7;

  printf("Resultat : y=%i",y);
}
  • Der Audruck (Term) rechts vom Gleichheitszeichen wird berechnet und das Resultat der Variablen links vom Gleichheitszeichen zugewiesen.
    So kann eine bestehende Variable mit neuem Inhalt befüllt werden wie z.B. bei x=x+1;
  • Der Modulo-Operator % (=Restwertdivision) ist in der Informatik eine wichtige Funktion und nur für ganze Zahlen. Z.B. gibt 12 % 5 das Resultat 2 weil 12 dividiert durch 5 den Rest 2 ergibt.

2.6 Inkrement und Dekrement-Operatoren

Bei den Postoperatoren wird die Variable erst "benutzt" und dann entsprechend inkrementiert oder dekrementiert. Die Preoperatoren inkrementieren bzw. dekrementieren vor der Benutzung.


3. Kontrollstrukturen

3.1 Die Vergleichsoperatoren

3.2 Die Selektion if-else und switch-case

Die Selektion if-else

#include <stdio.h>

void main(void)
{
  int zahl=100;

  if(zahl<100)
  {
    printf("Die Zahl ist kleiner 100");
  }
  else
  {
    printf("Die Zahl ist groesser-gleich 100");
  }
}

Den else-Teil kann man weglassen, wenn kein else-Fall vorgesehen ist.
Die geschweiften Klammern BEGIN/END kan man weglassen, wenn
nur ein Statemant (Sequenz) vorgesehen ist.

Die Mehrfachselektion switch-case

#include <stdio.h>   //Beispiel 1

void main(void)
{
  int zahl=0;

  // Eingeben einer Zahl
  printf("Zahl eingeben : ");
  scanf("%i",&zahl);

  //Die Mehrfachselektion  switch-case
  switch(zahl)
  {
  case 1  : printf("Zahl ist 1\n");
            printf("**********");
  break;
 
  case 3  : printf("Zahl ist 3\n");
            printf("**********");
  break;
 
  case 7  : printf("Zahl ist 7\n");
            printf("**********");
  break;
 
  default : printf("Zahl weder 1,3 oder 7\n");
  break;
  }
}
#include <stdio.h>   //Beispiel 2

void main(void)
{
  char ch='B';

  switch(ch)
  {
  case 'A' : printf("Buchstabe ist A");
  break;
 
  case 'B' : printf("Buchstabe ist B");
  break;
  }
}

Bemerkungen:

  • Nach jedem "Fall" sollte eine break-Anweisung stehen. Sonst würden auch die weiteren bzw. nächsten Anweisungen ausgeführt. Dies entspricht eigentlich nicht dem Konzept der strukturierten Progammierung und ist eigentlich eine Strukturverletzung. Duch Weglassen der break-Anweisung kann aber bewusst ein zT. gewünschter Effekt erzielt werden.
  • Die default-Anweisung entspricht dem else-Fall beim if-Konstrukt. Falls kein else oder default-Fall vorgesehen ist, kann die default-Anweisung auch weggelassen werden.
  • Switch-case funktioniert nur mit Aufzählungstypen (zB. int, char). Im case kann nicht ein Wertebereich angegeben werden.

3.3 Der Umkehr-Operator (NOT) !

#include <stdio.h>

void main(void)
{
  int zahl=100;
  if(!(wert==100))
    printf("Der Wert ist ungleich 100.\n");
}

3.4 Verknüpfungsoperatoren

Und-Verknüpfung = &&
Oder-Verknüpfung = ||

 if( (a>0) && (a<10) )
   { printf("a liegt zwischen 0 und 10"); }

3.5 BOOLEAN

In einigen Programmiersprachen gibt es den Datentyp Boolean. Nicht so in der Programmiersprache "C". Hier gilt folgendes :

  • Wert gleich 0 bedeutet FALSE (Falsch)
  • Wert  ungleich 0 bedeutet TRUE (Wahr)
#include <stdio.h>   //Beispiel 1

void main(void)
{
  int weitermachen=1;  //0=FALSE, 1=TRUE
  int i=0;
  char ch;

  while(weitermachen)
  {
    i=i+1;

    printf("Moechten Sie weitermachen (j/n) : ");
    fflush(stdin);
    scanf("%c",&ch);

    if(ch=='n')
      weitermachen=0;
  }
}

3.6 Die kopfgesteuerte Iteration

#include <stdio.h>

void main(void)
{
  int i=0;

  while(i<10)
  {
    printf("Die Zahl i hat den Wert %i",i);
    i=i+1;
  }
}

3.7 Die fussgesteuerte Iteration

#include <stdio.h>

void main(void)
{
  int i=0;

  do
  {
    printf("Die Zahl i hat den Wert %i",i);
    i=i+1;
  } while(i<10);
}

3.8 Die for-Schleife (Kopfgesteuerte Iteration)

#include <stdio.h>

void main(void)
{
  int i=0;

  for(i=0; i<10; i++)
  {
    printf("Die Zahl i hat den Wert %i",i);
  }
}

Allgemein

for(Anweisung1 ; Bedingung ; Anweisung2)
 {
   /* Anweisungsblock */
 }

4. Funktionen

(Wichtig : Die Funktionsdeklaration muss vor dem Funktionsaufruf stehen, sonst werden Prototypen erforderlich)

Einfache Funktion ohne Parameter:

#include <stdio.h>

/***Funktionsdeklaration***/
void begruessung(void)
{
  printf("Guten Tag !\n");
}

void main(void)
{
  begruessung(); //Funktionsaufruf
  begruessung(); //Funktionsaufruf
}

Funktion mit Eingabeparameter:

(Es können auch mehrere Eingabeparameter sein !)

#include <stdio.h>

/***Funktionsdeklaration***/
void verdoppelt(int a)
{
  a=a*a;
  printf("Verdoppelt : %i \n",a);
}

void main(void)
{
  int zahl=3;
  verdoppelt(zahl);  //Funktionsaufruf
  printf("zahl = %i \n",zahl);  //zahl ist immer noch 3
}

Funktion mit mehreren Eingabeparameter und Funktionsrückgabewert:

#include <stdio.h>

/***Funktionsdeklaration***/
int sum(int a, int b)
{
  int res=0;
  res=a+b;
  return(res);
}

void main(void)
{
  int zahl1=3;
  int zahl2=5;
  int summe=0;
  summe = sum(zahl1, zahl2);
  printf("Summe = %i \n",summe);
}

Bemerkung : Die Funktion kann ein Wert an das aufrufende Programm zurückgeben. Der Typ des Rückgabewertes ist aus der Funktionsdeklaration ersichtlich :

int sum(int a, int b)

Wird nichts zurückgegeben steht void  (void=Leer)


5. Vertiefung zu Variablen

5.1 Feldvariable: Array von Integer

#include <stdio.h>

void main(void)
{
  int f[10];

  f[0]=5;
  f[1]=12;
  //und so weiter ...

  printf("1. Zahl im Array : %i",f[0]);

  if(f[1]==12)
    printf("Treffer");

}

Achtung:

Es wird in diesem Beispiel ein Feld f mit 10 Elementen vom Typ integer deklariert. Das erste Element ist das "Nullte" f[0].

5.2 Feldvariablen-Spezialfall: Array von Charactern (Textstring)

Auf den Variablentyp chgar wird später eingehender eingegangen.

#include <stdio.h>

void main(void)
{
  char str1[10];
  char str2[10];

  // Eingabe mit scanf
  printf("Geben Sie Ihren Text ein : ");
  scanf("%s",str1);
  //Beachte : Kein & vor der Variable str
  //          Platzhalter %s für String !
  fflush(stdin);
  //Besser für das Einlesen von Strings ist :
  printf("Geben Sie Ihren Text ein : ");
  gets(str2);

  //Ausgabe des Strings

  //Variante 1
  printf("String : %s\n",str1);

  //Variante 2
  puts(str2);
}

Achtung:

Mit scanf wird nur bis zum ersten Leerzeichen eingelesen. Dass heisst, dass man hier nur "Hallo" gespeichert hätte.

Geben Sie Ihren Text ein : Hallo meine Leute

Als Endkennung wird nach Einlesen eines Strings mit scanf oder gets ein ASCII-Zeichen NULL  '\0' eingefügt. Dank diesem Zeichen kann immer eindeutig das String-Ende erkannt werden.

5.3 Warum deklarierte Variablen initialisiert werden müssen

Fall-1:

Gegeben sei das folgende Programm:

#include <stdio.h>

void main(void)
{
  int zahl;
  printf("Zahl = %i", zahl);
}

Prüfen sie, welche Zahl mit printf ausgegeben wird!

Fall-2:

Gegeben sei das folgende Programm:

#include <stdio.h>

void main(void)
{
  int produkt;
  int i;
  i=0;

  while(i<10)
  {
    produkt=produkt*i;
    i=i+1;
  }
  printf("Produkt = %i", produkt);
}

Prüfen sie, welches "produkt" mit printf ausgegeben wird!

Schlussfolgerung:

Aus diesem Grund müssen Variablen sowohl deklariert als auch initialisiert (mit einem Wert versehen) werden.

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Schreiben sie ein Programm, in welchem zwei Werte eingelesen werden und der grössere Wert danach am Bildschirm angezeigt wird. Erstellen sie vorher ein Struktogramm.
  • Schreiben sie ein Programm, das nach einer Zahl fragt und danach mitteilt, ob die Zahl gerade oder ungerade ist.
  • Schreiben sie ein Programm, welches nach zwei Zahlen fragt (auch negative Werte sollen erlaubt sein) und dann die Summe der zwei Zahlen ausgibt. Nachdem die Summe ausgegeben wurde, soll nach einer dritten Zahl gefragt werden, mit der die Summe dann multipliziert wird. Dieses Ergebnis soll ebenfalls ausgegeben werden.
  • Schreiben sie ein kleines Taschenrechnerprogramm das zwei Zahlen zusammenzählen, abzählen, multiplizieren oder dividieren kann. Zuerst sollen die beiden Zahlen eingegeben werden. Danach soll durch die Eingabe des Charakter '+' zusammengezählt, '-' abgezählt, '*' multipliziert oder '/' dividiert werden. Das Resultat am Bildschirm ausgeben. Welche Datentypen wählen sie? Hier der verlangte Bildschirminhalt:
>*** TBZ-Rechner V1.0 ***
>************************
>
>Bitte erste Zahl eingeben  : 12 <ENTER>
>Bitte zweite Zahl eingeben : 56 <ENTER>
>
>Bitte Operationszeichen eingeben (+ oder – oder * oder /) : + <ENTER>
>
>Ihre Rechnung 12 + 56 = 68
>
>Weitere Berechnungen ? (j=ja / n=nein) : n <ENTER>
>Auf Wiedersehen ...
>

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Schreiben sie ein Programm, das die Potenz berechnet. Das Programm soll zuerst nach der Basis und nachher nach dem Exponent fragen. Anschliessend soll das Resultat am Bildschirm ausgegeben werden.
  • Schreiben sie ein Programm, das die Fakultät einer von einer von Ihnen eingegeben Zahl berechnet.
    Beispiele zu Fakultät :
    Fakultät von 1 ist 1
    Fakultät von 2 ist 2, weil 2*1=2
    Fakultät von 3 ist 6, weil 3*2*1=6
    Fakultät von 4 ist 24, weil 4*3*2*1=24
    usw.

5.4 Typecast

Mit dem Typecast  (float)(zahl1) wird der Variablentyp von zahl1 in einen float gewandelt.

#include <stdio.h>

void main()
{
  int int_res;
  int zahl1=4;
  int zahl2=3;
  float float_res;

  int_res = zahl1/zahl2;
  printf("Resultat: %i\n",int_res);
  float_res = (float)(zahl1/zahl2);
  printf("Resultat float: %f\n",float_res);
  float_res = (float)(zahl1)/(float)(zahl2);
  printf("Resultat float: %f\n",float_res);
  float_res = zahl1/3.0;
  printf("Resultat float: %f\n",float_res);
}

 

5.5 Prototypen

Die Kompilierung dieses Codebeispiels ergibt ein Fehler (Warning). Die Funktion berechnung() wird vor ihrer Deklaration aufgerufen.

#include <stdio.h>

void main()
{
  int zahl, zahl1, res;
  printf("1. Zahl :");
  scanf("%i",&zahl);
  printf("2. Zahl :");
  scanf("%i",&zahl1);

  res = berechnung(zahl, zahl1);
  printf("Resultat: %i\n",res);
}

int berechnung(int op1, int op2)
{
  int resultat;

  resultat = op1 % op2;
  return(resultat);
}

Abhilfe schafft hier ein Prototyp von der Funktion berechnung() vor dem Aufruf bzw. vor dem main():

#include <stdio.h>
 int berechnung();  //Prototyp von berechnung()

Achtung : Strichpunkt nicht vergessen.

 


6. Character/Zeichen

6.1 Der Variablentyp char

Eine Variable vom Typ "char" (= Character) ermöglicht es , einzelne ASCII-Zeichen zu speichern.

char ch;
printf("Zeichen eingeben : ");
scanf("%c", &ch);
printf("Das Zeichen war %c",ch);

Formatzeichen für Charackter : %c
Der Bereich einer Char-Variablen geht von 0..255 (= 8 Bit oder 1 Byte).

Zuweisung eines Zeichens in eine char-Variable:

char ch;
ch = 'B';

Zeichenkonstanten stehen immer zwischen einfachen Anführungsstrichen.  Bsp. char ch='A';
Stringkonstanten stehen immer innerhalb doppelter Anführungstrichen. Bsp. printf("Hallo");

Der Compiler schreibt in die Variable nicht das 'B' selbst, sondern der Wert, den der Buchstabe 'B' in der ASCII-Tabelle repräsentiert.

6.2 Die ASCII-Tabelle

(ASCII-Tabelle auszugsweise)

Als Standsrd-ASCII-Code sind nur die 128 Zeichen des 7 Bit-Codes festgelegt.
Daher keine Umlaute wie z.B.  ä,ö und ü.

6.3 Das Doppelleben von char

Eigentlich ist die char-Variable fast identisch mit dem Variablentyp unsigned short. Deshalb kann man sie auch wie ganzzahlige Variablen behandeln.

char ch = 'A';
printf("Der Wert von %c ist %i",ch,ch);

Ein Grossbuchstaben kann so einfach in einen Kleinbuchstaben umgewandelt werden :

char ch = 'A';
ch = ch + 32;
printf("Buchstabe : %c",ch);

6.4 Einige nützliche Funktionen von ctype.h

(Genaue Prototypen siehe C-Literatur)

isalnum() // Wahr für einen Buchstaben oder eine Zahl
isalpha() // Wahr für einen Buchstaben
isdigit() // Wahr für eine dezimale Ziffer
islower() // Wahr für Kleinbuchstaben
isupper() // Wahr für Grossbuchstaben
tolower() // Umwandlung in alles Kleinbuchstaben
toupper() // Umwandlung in alles Grossbuchstaben

7. Array/Feldvariable

7.1 Variablenfelder (Array)

int x[3];

x[0] = 15;
x[1] = 45;
x[2] = 62;

//x[3] = 34; -> Unzulässig, da nur 3 Elemente deklariert sind.

Der Name eines Feldes ohne eckige Klammern steht für die Startadresse des Feldes. Der Feldname ist ein Vektor.

7.2 Felder als Funktionsparameter

Die Programmiersprache "C" kennt als Funktionsparameter nur elementare Datentypen wie Adressen char, int, float, short etc. Somit müssen wir bei der Parameterübergabe eines ganzen Feldes dessen Adresse übergeben :

#include <stdio.h>

/******************EINGABE********************/
void datenerfassung(int *a)
{
  int zaehler=0;
  while (zaehler<10)
  {
    printf("Ihre Zahl : ");
    scanf("%i", &a[zaehler]);
    zaehler++;
  }
}

/******************VERARBEITUNG***************/
void datenberechnung(int *b)
{
  int zaehler=0;
  while (zaehler<10)
  {
    b[zaehler])= b[zaehler]+10;
    zaehler++;
  }
}

/******************AUSGABE********************/
void datenausgabe(int *c)
{
  int zaehler=0;
  while (zaehler<10)
  {
    printf("Ergebniss : %i",c[zaehler]);
    zaehler++;
  }
}

/******************HAUPTPROGRAMM**************/
void main(void)
{
  int x[10];

  datenerfassung(x);
  datenberechnung(x);
  datenausgabe(x);
}

Der Zeiger auf eine Variable ist identisch mit dem Zeiger auf ein Feld.
Bei Zeigern auf Felder darf bei der Benutzung des Index kein Dereferenzierungsoperator angewendet werden.

7.3 Parameterübergabe bei Feldvariablen

#include <stdio.h>

// Das Array arr wird als Zeiger uebergeben ...
void array1_calc(int *arr)
{
  arr[3] = 144;
  printf("Neuer Wert : ");
  scanf("%i",&arr[4]);
}

// Das Array kann auch alternativ so uebergeben werden ...
void array2_calc(int arr[])
{
  arr[3] = 99;
}

// Demzufolge gilt auch für Strings ...
void str_calc(char *str)
{
  printf("Neuer String : ");
  fflush(stdin);
  gets(str);
}

void main(void)
{
  int arr1[5];
  int arr2[5];
  char str1[10];

  array1_calc(arr1);
  printf("Wert arr1[3] = %i",arr1[3]);
  printf("\n");
  printf("Wert arr1[4] = %i",arr1[4]);
  printf("\n");

  array2_calc(arr2);
  printf("Wert arr2[4] = %i",arr2[3]);
  printf("\n");

  str_calc(str1);
  printf("String = %s",str1);
}

8. Strings/Text

Ein besonderes Variablenfeld : Die Zeichenkette  (engl. array of charakter)
Formatzeichen für Charakter : %s
Der String wird nicht andes als ein Feld definiert.

Beispiel 1:

#include <stdio.h>

void main(void)
{
  char str[8];
  str[0] = 'H';
  str[1] = 'a';
  str[2] = 'l';
  str[3] = 'l';
  str[4] = 'o';
  str[5] = '\0';
  printf("Der String lautet %s",str);
}

Beispiel 2:

#include <stdio.h>

void main(void)
{
  char str[6];
  printf("Texteingabe : ");
  scanf("%s",str);

  printf("Text = %s",str);
}

Der Adressoperator vor der Variable str in scanf fehlt nicht wirklich, denn : Normalerweise würden wir dort zwar ein Adressoperator & benutzen, um scanf die Adresse der Variablen zur Veränderung des Wertes zu übergeben. Doch bei den Feldvariablen haben wir gesehen, das der Name eines Feldes ohne die eckigen Klammern die Adresse des Feldes repräsentiert.

Was auf nicht funktionieren wird:

char str[6];
str = "Hallo";  //Geht nicht !!

str steht für die Adresse und kann nicht einer Stringkonstanten zugewiesen werden.

Bessere Wahl für Ein- und Ausgabe von Strings :

gets(str);
puts(str);

8.1 Textübergabe "Call by reference"

#include <stdio.h>

void array_calc(int * arr )
{
  arr[3] = 144;
}

void main(void)
{
  int x[5];
  array_calc(x);

 printf("Wert =  %i",x[3]);
}

#include <stdio.h>

void array_calc(int arr[] )
{
  arr[3] = 144;
}

void main(void)
{
  int x[5];
  array_calc(x);

  printf("Wert =  %i",x[3]);
}
#include <stdio.h>

void array_calc(int * arr)
{
  scanf("%i", &arr[3] );
}

void main(void)
{
  int x[5];
  array_calc(x);

  printf("Wert =  %i",x[3]);
}
#include <stdio.h>

void str_calc(char * str)
{
  gets(str);
}

void main(void)
{
  int text[5];
  str_calc(text);

  printf("Text =  %s",text);
}

8.2 Einige nützliche Funktionen von string.h

(Genaue Prototypen siehe C-Literatur)

strcat() // String an einen anderen hängen
strchr() // Zeichen im String suchen
strcmp() // Zwei Strings vergleichen
strcpy() // String kopieren
strlen() // Länge des Strings ermitteln

 

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Erstellen sie ein Programm, bei dem Sie mit scanf einen Text einlesen und den Text in nur Grossbuchstaben ausgeben. Falls Ihnen bei der Texteingabe mit scanf Nachteile auffallen, benutzen Sie Alternativen.
    Erstellen Sie zuerst das Struktogramm und programmieren dann den Umwandlungsalgorithmus als Funktion. Kommentieren Sie ausreichend (Dateiheader etc.) Sie dürfen dazu die string.h bzw ctype.h nicht benutzen.
  • Erstellen sie ein Programm, bei dem sie einen Text einlesen und prüfen, ob das Wort “Hallo” eingegeben wurde. (Gross/Kleinschreibung beachten)
    Erstellen sie zuerst das Struktogramm und programmieren sie den Prüfalgorithmus als Funktion. Kommentieren sie ausreichend (Dateiheader etc.) Testen sie Ihr Programm. Sie dürfen dazu die string.h bzw ctype.h nicht benutzen.
  • Aufgabe "Textsearch": Diese Aufgabe sollte genau nach Pflichtenheft und nach allen Regeln der Programmierkunst (dh. Analyse/Design Implementation Test und Doku) ausgeführt werden. Zeitrahmen ca. 2 ½ Lektionen. Der Einsatz von <string.h> und <ctype.h> ist nicht erlaubt.
    Pflichtenheft:
    Im folgenden sehen sie zwei "Screenshots" vom User-Interface des Programms. Sie müssen ihr Programm so erstellen, dass dieselbe Funktionalität und Benutzerführung erreicht wird. Das Programm soll den Namen "textsearch.exe" erhalten. Es sind keine Abweichungen von der Vorlage zulässig.
  • Bsp. 1: cmd

Screenshots zur Aufgabe "Textsearch"

c:\>
c:\>***********************
c:\>*** TEXTSEARCH V1.0 ***
c:\>***********************
c:\>
c:\>Texteingabe (max. 30 Zeichen) : Willy, wie und wieso ?<ENTER>
c:\>Suchstring eingeben (max. 6 Zeichen) : wie<ENTER>
c:\>
c:\>Der Satz "Willy, wie und wieso ?" enthaelt 2 "wie"
c:\>

Achten sie auf die Gross/Kleinschreibung : In unserem Beispiel oben wäre "wie" nicht gleich "Wie"

c:\>
c:\>***********************
c:\>*** TEXTSEARCH V1.0 ***
c:\>***********************
c:\>
c:\>Texteingabe (max. 30 Zeichen) : Max3 arbeitet bei MAXON<ENTER>
c:\>Suchstring eingeben (max. 6 Zeichen) : ei<ENTER>
c:\>
c:\>Der Satz "Max3 arbeitet bei MAXON" enthaelt 2 "ei"
c:\>

Sie dürfen davon ausgehen, dass keine Umlaute (ä,ö,ü) eingegeben werden. Der Benutzer sitzt vor einer amerikanischen Tastatur.


9. Zeiger/Pointer

9.1 Vektoren und Zeiger

Vektoren: Konstante Adresse (zB.: int x=5;   &x => Vektor)
Zeiger: Variable Adresse (zB.: int x=5; int * px => Zeiger)

Eine Variable ist eindeutig definiert durch vier Angaben :

  • Name
  • Position (Adresse)
  • Grösse (Benötigter Speicherplatz)
  • Inhalt

Der benötigte Speicherplatz wird vom gewählten Variablentyp festgelegt ! (zB. int benötigt 32 Bit)

9.2 Der Adressoperator &

Liefert die Adresse einer Variablen.
Formatzeichen für Adressen (Pointer) : %p

(Der Adressoperator wird zB. beim scanf verwendet :  scanf("%i",&x); )

int x = 12;
printf("Adresse von x(%i) ist %p", x, &x);

Die Adresse einer Variablen wird bei ihrer Erzeugung bestimmt und bleibt für ihre Lebensdauer konstant.

9.3 Der Dereferenzierungsoperator *

Fall 1

int x = 10;
int *px;
px = &x;

Kreieren einer Zeigervariablen die auf eine Variable des Typs Integer zeigt.

Fall 2

int x = 10;
int *px;
px = &x;
*px = 23;

Eigentliche Dereferenzierung : Dort wo px hinzeigt, soll der Wert 23 gespeichert werden. Also wird tatsächlich der Inhalt der Variablen x von 10 auf 23 geändert.

Beispiele zu Adressen

#include <stdio.h>
void main(void)
{
  int x = 0;
  int * px;
  px = &x;
  *px = 12;
  *px = *px + 10;
  *px = (*px) * (*px);
}

Adresse: 0x0000F2A1
Variablenname: x
Inhalt: 848

Adresse: 0x0000E731
Variablenname: px
Inhalt: 0x0000F2A1

9.4 Adressen als Funktionsparameter

Hauptsächlich werden Zeiger benutzt, um Adressen an Funktionen zu übergeben. Diese können über diesen Umweg dann die Variablen ausserhalb verändern. (Call by value)

#include <stdio.h>

void cquadrat(int * wert)
{
  *wert = (*wert) * (*wert);
}

void main(void)
{
  int x=12;
  int *px=&x;
  cquadrat( px );
  printf("Das Quadrat ist %i",x);
}

9.5 Zeiger auf Zeiger

Bis jetzt haben wir Zeiger kennengelernt, die auf Variablen zeigten. Nun kann es auch Zeiger geben, die auf Zeiger zeigen. Zum Beispiel bei der Uebergabe eines Stringfeldes als als Funktionsparameter :

char ** p_str;

Der Zeiger p_str zeigt auf einen Zeiger, der auf eine Variable des Typs char zeigt.


10. Zeigeranwendungen

10.1 Sortieralgorithmus

Häufig stellt sich die Aufgabe, das Einträge numerisch oder alphanumerisch geordnet werden müssen. (zB. Namenseinträge im Telefonbuch). Im folgenden wird ein einfacher Sortieralgorithmus gezeigt : der Bubble-Sort. Dieser Sortieralgorithmus ist für numerische wie auch alphanumerische Sortieraufgabe geeignet. Dies darum, weil sich die Problematik in den beiden Fällen nicht unterscheidet : 5>2 aber auch 'D' > 'B'. (siehe ASCII-Tabelle)
Sollen nun ganze Sätze alphabetisch geordnet werden, müssen mehrere Charakter miteinander verglichen werden. ZB. liegt "Muheim" vor "Mutzer".

10.2 Der Bubble Sort

Ausgangslage:

1. Durchgang:

2. Durchgang:

3. Durchgang:

Ist nicht mehr nötig, weil die Sortierung bereits korrekt ist.

(Der Begriff "Bubble-Sort" kommt von (engl.) Bubbles=Blasen. Die Grossen Werte wandern von links nach rechts, die kleinen Werte von rechts nach links, ähnlich den Luftblasen im Wasser die zur Wasseroberfläche steigen)

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Programmieren Sie den Bubble-Sort als Funktion.
    Als Uebergabeparameter erhalten Sie ein Array of Integer (Call by reference) int arr[10];
    In der Funktion wird die Reihenfolge des Arrays korrigiert. Das heisst : An der ersten Stelle im Array steht der kleinste Integer-Wert. An letzter Stelle der grösste.
  • Lesen Sie 10 Strings ein und sortieren Sie diese in alphabetischer Reihenfolge. Danach geben Sie die 10 sortierten Strings mit printf() aus.

11. Strukturen in C

Zusammengehörende Daten können in struct's zusammengefasst werden. Aehnlich den Datensätzen in Datenbanken wie z.B.:
Tabelle "Personen" enthält Attribut : Name, Vorname, Adresse etc.

Beispiel 1:

#include <stdio.h>
#include <string.h>

/***Definition der Struktur***/
struct Person
{
  char nachname[40];
  char vorname[40];
  char strasse[40];
  unsigned short strnummer;
  unsigned short plz;
  char ort[40];
};

/**************AUSGABE************************/
void ausgabe (struct Person the_person)
{
  printf("Name: %s %s\n", the_person.nachname, the_person.vorname);
  printf("Strasse: %s %hu\n", the_person.strasse, the_person.strnummer);
  printf("Ort: %hu %s\n", the_person.plz, the_person.ort);
}

/**************MAIN*************************/
void main()
{
  struct Person my_Person;

 //Die Variable könnte so auch direkt initialisiert werden :
 //struct Person my_Person = {"Meier","Fritz","Cool",1,2222,"Seen"};

 strcpy(my_Person.nachname, "Meier");
 strcpy(my_Person.vorname, "Urs");
 strcpy(my_Person.strasse, "Bahnhofstr.");
 my_Person.strnummer = 65;
 my_Person.plz = 8000;
 strcpy(my_Person.ort, "Zuerich");

 ausgabe(my_person);

 printf("Strasse : ");
 fflush(stdin);
 gets(my_Person.strasse);

 printf("Strassennummer : ");
 fflush(stdin);
 scanf("%i", &my_Person.strnummer);

 ausgabe(my_person);
}

Beispiel 2 (Mit Array):

#include <stdio.h>
#include <string.h>

/***Definition der Struktur***/
struct Person
{
  char nachname[40];
  char vorname[40];
  char strasse[40];
  unsigned short strnummer;
  unsigned short plz;
  char ort[40];
};

/**************AUSGABE************************/
void ausgabe (struct Person the_person)
{
  printf("Name: %s %s\n", the_person.nachname, the_person.vorname);
  printf("Strasse: %s %hu\n", the_person.strasse, the_person.strnummer);
  printf("Ort: %hu %s\n", the_person.plz, the_person.ort);
}

/*************MAIN***************************/
void main()
{
  struct Person     personen[2];

  strcpy(personen[0].nachname, "Huber");
  strcpy(personen[0].vorname, "Karin");
  strcpy(personen[0].strasse, "Baeckerstr.");
  personen[0].strnummer = 34;
  personen[0].plz = 8000;
  strcpy(personen[0].ort, "Zuerich");

  printf("Nachname : ");
  fflush(stdin);
  gets(personen[1].nachname);

  printf("Vorname : ");
  fflush(stdin);
  gets(personen[1].vorname);

  printf("Strasse : ");
  fflush(stdin);
  gets(personen[1].strasse);

  printf("Strassen-Nummer : ");
  fflush(stdin);
  scanf("%i", &personen[1].strnummer);

  printf("PLZ : ");
  fflush(stdin);
  scanf("%i", &personen[1].plz);

  printf("Ort : ");
  fflush(stdin);
  gets(personen[1].ort);

  ausgabe(personen[0]);
  ausgabe(personen[1]);
}

Ergänzungen zu Strukturen:

Strukturen können genauso mit Hilfe des Zuweisungsoperator ' = ' kopiert werden, wie elementare Datentypen.

struct Personendaten  // Deklaration des structs
{
  char name[30];
  int alter;
};

struct Personendaten person1;
struct Personendaten person2;

person1.alter = 25;
strcpy(person1.name,"Susi");

person2 = person1;  // person2 übernimmt alles von person1

11.1 Zeiger auf Strukturen

#include <stdio.h>
#include <string.h>

// Deklaration des structs
struct Personendaten
{
  char name[30];
  int alter;
};

void main(void)
{

  struct Personendaten my_person;
  struct Personendaten * p_my_person;  // Zeiger auf struct Personendaten

  p_my_person = &my_person;

  my_person.alter = 25;

  p_my_person->alter = 33;   // Die Dereferenzierung ->
  strcpy(p_my_person->name, "Mueller");

  //Einlesen von Daten
  fflush(stdin);
  scanf("%i", &p_my_person->alter);
  fflush(stdin);
  gets(p_my_person->name);

  printf("Alter = %i \n", p_my_person->alter);
  printf("Name = %s", p_my_person->name);
}

12. Files/Dateien

12.1 Filehandling : ASCII-Files / Textdateien

void schreiben(void)
{
  FILE *fhd;
  char s[MAXZEILENLAENGE];

  fhd=fopen(DATNAME,"w");
  if(!fhd)
  {
    printf("Datei konnte nicht erzeugt werden!\n\n");
  }
  else
  {
    printf("Bitte maximal %i Zeichen pro Zeile
    eingeben.\n",MAXZEILENLAENGE);
    printf("Eingabe wird mit . beendet.\n\n");
    do
    {
      printf(">");
      gets(s);
      if(strcmp(s,"."))
      {
        fputs(s,fhd);
        fputs("\n",fhd);
      }
    } while(strcmp(s,"."));

  fclose(fhd);
  printf("\nEingabe beendet!\n");
  }
}
void lesen(void)
{
  FILE *fhd;
  char s[MAXZEILENLAENGE];
  int x=1;

  fhd=fopen(DATNAME,"r");
  if(!fhd)
  {
    printf("Datei konnte nicht geoeffnet werden!\n\n");
  }
  else
  {
    printf("Die Datei hat folgenden Inhalt:\n");

    fgets(s,MAXZEILENLAENGE,fhd);
    do
    {
      printf("%i:%s",x++,s);
      fgets(s,MAXZEILENLAENGE,fhd);
    } while(!feof(fhd));

  fclose(fhd);
  printf("\nEnde der Datei!\n");
  }
}
#include <stdio.h>
#include <string.h>

#define DATNAME "test.txt"
#define MAXZEILENLAENGE 160

void main(void)
{
  int input;

  printf("Soll die Datei 1=gelesen oder 2=beschrieben werden?");
  scanf("%i",&input);

  if(input==1)
  {
    lesen();
  }
  else
  {
    if(input==2)
    {
      schreiben();
    }
    else
    {
      printf("\nFalsche Eingabe!\n\n");
    }
  }
}

Öffnen der Textdatei:

fpt=fopen("filename.txt", "w");
  • w = Erstelllt eine Textdatei zum schreiben (write)
  • r = Öffnet eine Textdatei zum lesen (read)
  • a = Erstellt oder öffnet eine bereits existierende Datei zum Schreiben - Der Dateipositionszeiger steht am Ende der Datei (append)

Lesen/Schreiben:

fputs(mystring, fpt);   //Schreiben in File
fgets(thestring, MAXZEILENLAENGE, fpt);  //Lesen

Fileende prüfen

wert=feof(fpt);   // wert!=0 bedeutet Fileende

Achtung : feof liefert erst dann einen wahren Wert, wenn versucht wurde, Daten zu lesen, obwohl das Dateiende erreicht ist !

>File schliessen:

fclose(fpt);

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Erstellen Sie ein Programm das folgendes tut:
    >FILE LESEN
    >
    >Bitte Filename eingeben : text.txt
    >
    >tetxt.txt :
    >
    >Hallo Leute
    >dies ist ein Testfile
    >
  • Erstellen Sie ein Programm das folgende Bildschirmausgabe generiert:
    >FILE SCHREIBEN
    >
    >Filename eingeben : test2.txt
    >Bitte Zeile 1 eingeben : Hallo Karli
    >Weitere Zeile ? (y/n) : y
    >Bitte Zeile 2 eingeben : Guten Tag
    >Weitere Zeile ? (y/n) : n

12.2 Binärdateien

Die bereits bekannte Dateiform ist die Textdatei. Diese kann ohne Probleme mit Texteditoren gelesen werden. Sollten aber zB. Datensätze auf File gespeichert werden, ist es effizienter, diese Daten anstatt in ASCII in binärer Form abzulegen.
Wie bei Textfiles muss auch das binäre File zuerst geöffnet werden :

FILE *fhd;
...
fhd = fopen("filename", "mode");
...

Mode:

  • Weitere Modes siehe C-Literatur
  • "rb" = Oeffnet Binärdatei zum lesen (read)
  • "wb" = Erstellt eine Binärdatei zum schreiben (write)
  • "ab" = Erstellt oder öffnet Binärdatei (append) neue Daten werden hinten angehängt !

Nach erfolgreichem Oeffnen der Datei folgt das Lesen oder Schreiben von Daten:

anz = fwrite(adresse,groesse,anzahl,fhd);
  • anz : Anzahl komplett geschriebener Datensätze
  • adresse : Adresse des zu speichernden Datensatzes
  • groesse : Grösse des Datensatzes in Byte
  • anzahl : =1 (bei normalen Variablen)
  • fhd : Zeiger auf Filestruktur
anz = fread (adresse,groesse,anzahl,fhd);
  • anz : Anzahl komplett gelesener Datensätze
  • adresse : Adresse von der gelesen wird
  • groesse : Grösse des Datensatzes in Byte
  • anzahl : =1 (bei normalen Variablen)
  • fhd : Zeiger auf Filestruktur

Um die Grösse eines Datensatzes zu bestimmen, wird eine neue Funktion eingeführt:

size_t anz_Byte;  // Datentyp : size_t
anz_Byte = sizeof(my_var);

Hier wird die Grösse der Variablen my_var in Bytes bestimmt.

Variante:

Im folgenden Beispiel wird zur Ermittlung des Fileendes nicht feof verwendet, sondern der Rückgabewert von fread ausgewertet. Konnte nämlich kein Datensatz (anz==0) mehr gelesen werden, muss das Fileende erreicht worden sein.

#include <stdio.h>  //Binaerfile schreiben

void binaryfile_write(void)
{
  FILE *fhd;
  char name[20];
  int alter;
  int weiter=1;  //1=weiter, 0=nicht weiter
  fhd=fopen("myfile", "wb");
  if(! fhd)
    printf("File open error\n");
  else
    while(weiter)
    {
      printf("Name : ");
      fflush(stdin);
      gets(name);
      printf("Alter : ");
      scanf("%i",&alter);
      fwrite(name, sizeof(name), 1, fhd);
      fwrite(&alter, sizeof(alter), 1, fhd);
      printf("weiter ? 1=ja/0=nein : ");
      scanf("%i",&weiter);
    }
  fclose(fhd);
}

#include <stdio.h>  //Binaerfile lesen

void binaryfile_read(void)
{
  FILE *fhd;
  char name[20];
  int alter;
  size_t got=0;  // flag : 1=Daten gelesen
                 //        0=keine weitere Daten
  fhd=fopen("myfile", "rb");
  if(! fhd)
    printf("File open error\n");
  else
  {
    got=fread(name, sizeof(name), 1, fhd);
    while(got)
    {
      printf("**********************\n");
      printf("Name : %s \n",name);
      got=fread(&alter, sizeof(alter), 1, fhd);
      printf("Alter : %i \n",alter);
      got=fread(name, sizeof(name), 1, fhd);
    }
  fclose(fhd);
  }
}

13. Der Aufzählungstyp enum

enum = Enumeration, was soviel bedeutet wie Aufzählung.

// Deklaration des Aufzaehlungstyp
enum Farbkreis{gelb, orange, rot, violett, blau, gruen};

enum Farbkreis farbe;   // Variablendeklaration farbe = rot;

if(farbe == rot)
  printf("Warme Farbe");
for(farbe = gelb; farbe <= gruen; farbe++)
  { }

Achtung
Die Ausgabe...

farbe=rot; printf("%i", farbe);

...ergibt nicht rot. Dies weil "intern" den sechs Konstanten gelb, orange, rot, violett, blau und gruen Werte von 1 bis 6 zugewiesen werden.

 


14. Eigene Typen mit typedef

Es besteht die Möglichkeit, eigene Datentypen zu definieren.

Beispiel 1:

typedef enum Woche{MO, DI, MI, DO FR, SA, SO};
Woche tag;   // Variablendeklaration
tag = MO;

Beispiel 2:

struct Personendaten // Deklaration des structs
{
  char name[30];
  int alter;
};

typedef struct Personendaten TPersonendaten;
TPersonendaten my_person; // Variablendeklaration
my_person.alter = 25;

Einfachere Variante:

typedef struct Personendaten  // Deklaration des structs
{
 char name[30];
 int alter;
 };

Personendaten my_person;   // Variablendeklaration

my_person.alter = 25;

 


15. Dynamische Speicherverwaltung

Bisher war die Speicherbelegung vor "Laufzeit" des Programms definiert. Dass heisst : Zur Startzeit ist festgelegt, welche Variable mit welchem Speicherbedarf vorliegt. In einigen Fällen kann aber nicht zum vorherein gesagt werden, wieviel Speicherplatz benötigt wird. Zum Beispiel : «Eingabe eines Textes», «Erstellen einer Personenkartei» oder «Lesen eines Files». Darum gibt es auch die Möglichkeit, Speicher dynamisch, dh. zur Laufzeit anzufordern.

15.1 Reservierung eines Speicherblocks

Speicher reservieren:

void * malloc(size_t groesse);

Speicher freigeben:

void free(void * ptr);

Was bedeutet   void * ptr;

Dies ist ein typenloser Zeiger. Da malloc nicht weiss, für welchen Zweck Speicher angefordert wird, ist ein sogenannter Nullpointer als Rückgabewert definiert. Der Programmierer hat bei der Verwendung von malloc zu sorgen, dass das Typenkonzept eingehalten wird. Darum muss der Nullpointer mit einem Typecast in einen Zeiger auf den entsprechenden Datentyp umgewandelt werden. Dazu folgendes Beispiel :

#include <stdio.h>
#include <stdlib>

void main(void)
{
  void * nullptr; // Deklaration eines typenlosen Zeigers
  int * ptr;      // Deklaration eines Zeigers auf einen Integer

  nullptr = malloc(sizeof(int)); // Dynamische Speicherallozierung
  ptr = (int *) (nullptr);       // Typecast : typenloser Zeiger zu Zeiger auf int

  if(ptr)  // Sofern Allozierung geklappt hat ...
  {
    *ptr=23;   // Wertzuweisung via Dereferenzierung
    printf("Wert = %i \n", *ptr);
    free(ptr); // NICHT VERGESSEN : Freigeben des Speichers
  }
}

Einfachere Variante:

#include <stdio.h>
#include <stdlib>

void main(void)
{
  int * ptr;  // Deklaration eines Zeigers auf einen Integer

  if(ptr = (int*)(malloc(sizeof(int))))  // Allozierung, Typecast und Verifizierung in einem ...
  {
    *ptr=23;  // Wertzuweisung via Dereferenzierung
    printf("Wert = %i \n", *ptr);
    free(ptr); // NICHT VERGESSEN : Freigeben des Speichers
  }
}

Natürlich können auch alle anderen Datentypen wie strings, structs etc. dynamisch alloziert werden.

15.2 Vorwärts verkettete Liste  (Forward linked list)

Beim Lesen von Datensätzen stellte sich bisher die Frage, für wieviele Einträge bzw. wie gross der Behälter dimensioniert werden muss. Mit den neuen Kenntnissen über die dynamische Speicherallokation kann nun eine Lösung gefunden werden. Was vielleicht noch Probleme bereitet, ist wie die Daten verwaltet werden sollen. Dazu behelfen wir uns einem Trick, den wir seit frühester Schulzeit kennen: Beim Aufstellen in einer Einerkolonne merken wir uns den Vordermann. So kann beim erneuten Hinstellen die korrekte Reihenfolge sofort gefunden werden.

Definieren des "Elements":

Dieser beinhaltet zwei Teile:

  • Den Nutzteil (Kann auch ein struct sein !)
  • Den Verwaltungsteil (Zeiger auf den "Vordermann")
struct the_element
{
 int nutz;
 struct the_element * next;
};

Deklarieren der Verwaltungsvariablen:

Um die Liste aufbauen zu können, braucht es folgende Variablen:

struct the_element * element;
struct the_element * anker;
struct the_element * schlepper;
int first;

Aufbauen der "Linked List":

Auslesen der "Linked List":

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen:

  • Erstellen Sie eine Personenverwaltung. Diesmal soll die Anzahl der Personen nicht fix über ein array of struct sondern über eine linked list gelöst werden. Funktionen : new_entry, show_entry, show_entries
  • Ergänzen Sie Ihr Programm mit einer Funktion "Save to file" und "Read from file"
  • Ermöglichen Sie die Funktionen "delete_entry", sort_list"

16. Modulares Programmieren

Mein "Mathematik"-Modul , Filename : mathemodul.c

//////////////////////////////////////////
// Funktion : Potenz-Berechnen
// Autor    : ARJ
// Datum    : 6. Jan. 2017
// Zweck    : Berechnet basis hoch exponent
//            I : Basis
//            I : Exponent
//            O : Potenzwert
// Limits   : Nur positive und ganz-
//            zahlige Basis bzw.
//            Exponent
//////////////////////////////////////////
int pot(int basis, int exponent)
{
  int resultat=1;
  if (exponent==0)
  {
    return(resultat);
  }
  else
  {
    while(exponent>0)
    {
      resultat=resultat*basis;
      exponent=exponent-1;
    }
    return(resultat);
  }
}
// Headerdatei mathmodul.h ///////////////
//////////////////////////////////////////
// Funktion : Potenz-Berechnen
// Autor    : ARJ
// Datum    : 6. Jan. 2017
// Zweck    : Berechnet basis hoch exponent
//            I : Basis
//            I : Exponent
//            O : Potenzwert
// Limits   : Nur positive und ganz-
//            zahlige Basis bzw.
//            Exponent
//////////////////////////////////////////
int pot(int basis, int exponent);

Das Hauptprogramm, Filename : math.c

#include <stdio.h>
#include "mathemodul.h"  // Headerdatei fuer die pot-Funktion

/*****************************************/
/* Titel :  Uebung Potenzfunktion        */
/* Autor :  ARJ                          */
/* Datum :  6. Jan. 2017                 */
/* Zweck :  Potenz a hoch b berechnen    */
/* Version : 2.0                         */
/*****************************************/

void main(void)
{
  int b=1;
  int e=0;
  int p=1;

  printf("\n\nPotenz a hoch b V2.0");
  printf("\n********************\n\n");
  // Eingabe (User-Interface)
  printf("Basis : ");
  scanf("%i",&b);
  printf("Exponent : ");
  scanf("%i",&e);

  //Berechnung
  p=pot(b,e);

  //Ausgabe
  printf("Resultat = %i",p);
}

Wie wird kompiliert? (Bsp. mit lcc)

  • c:\Programme\lcc\bin\lcc math.c (Ergibt math.obj)
  • c:\Programme\lcc\bin\lcc mathemodul.c (Ergibt mathemodul.obj)
  • c:\Programme\lcc\bin\lcclnk math.obj mathemodul.obj (Ergibt math.exe)

Mit math.exe erhält man das ausführbare Programm.
Wichtig: Packen Sie nur dasjenige in eine Funktion, das thematisch auch zusammengehört. Die Ausgabe mit printf gehört zum Beispiel bei unserer Funktion pot() nicht dazu, weil man sich dadurch den universellen Einsatz des "mathemodul" bzw. der Funktion pot() verbaut. Man möchte ja diese Funktion vieleicht später an anderer Stelle (dh. in einem anderen Programm) wiederverwenden, wo keine Ausgabe mit printf verlangt ist oder die Ausgabe sogar in einem Grafikfenster anstatt der Konsole ausgegeben werden soll.


17. Programmbeispiele

#include <stdio.h>

/*****************************************/
/* Titel :  Patternmatching              */
/* Autor :  ARJ                          */
/* Datum :  7. Maerz 2017                */
/* Zweck :  Suchen eines Teststrings in  */
/*          einer Textzeile              */
/* Version : 1.0                         */
/*****************************************/

void main(void)
{

  char my_text[51];       // Eingabe Textstring
  char test_pattern[21];  // Zu vergleichender String

  int index_text=0;       // Textstring Aktuelle Position
  int index_pattern=0;    // Vergleichsstring Aktuelle Pos.

  int len_text=0;         // Laenge Textstring
  int len_pattern=0;      // Laenge Vergleichsstring

  int patternmatching=0;  // Anzahl Treffer

  // Eingabe der Strings
  printf("Bitte Textzeile eingeben (max. 50 Zeichen) : ");
  fflush(stdin);
  gets(my_text);

  printf("Bitte Suchbegriff eingeben (max. 20 Zeichen) : ");
  fflush(stdin);
  gets(test_pattern);

  // Laengenbestimmung der beiden Strings
  // Dies ist nötig, damit man beim Parsern des Textstrings nicht zu weit
  // vorrueckt
  for(len_text=0; my_text[len_text] != '\0'; len_text++);
  for(len_pattern=0; test_pattern[len_pattern] != '\0'; len_pattern++);

  // Gehe im Textstring soweit, dass noch ein Suchstring vorkommen kann
  while( index_text < (len_text + len_pattern) )
  {
  // Falls Charakter an der aktuellen Pos. uebereinstimmen, wird Pos. um eine
  // Stelle erhoeht. Achtung : Pos. des Textstring bleibt stehen. Es wird mit
  // einem Offset gearbeitet. Die zu pruefende Pos. im Textstring ergibt sich
  // aus index_text + index_pattern. Damit besteht auch der "lololu" "lolu"-Test
  while( (my_text[index_text + index_pattern] ==
          test_pattern[index_pattern])
          && 
          ( my_text[index_text + index_pattern] != '\0') )
    index_pattern++;
  // Wenns bis aufs letzte Zeichen im Suchstring uebereinstimmt
  // wird der Score um 1 erhoeht.
  if(test_pattern[index_pattern] == '\0')
    patternmatching++;
    // Der Positionszeiger vom Suchstring zurücksetzen.
    // Der Positionszeiger um 1 erhoehen
  index_pattern=0;
  index_text++;
  };

  // Ausgabe des Resultats
  printf("Im Satz >%s< steht >%s< %i Mal !\n",my_text, test_pattern, patternmatching);
}

/***************************************************/
/* Autor   : ARJ                                   */
/* Name    : bubble.c                              */
/* Zweck   : Sortierung der Eingegebenen Zahlen    */
/* Version : 1.0                                   */
/* Datum   : 7. Maerz 2017                         */
/***************************************************/
#include <stdio.h>
#define FALSE 0
#define TRUE 1

/*****************FUNCTION bubble_sort*******************/
void bubble_sort(int * arr, int anzahl)
{
  int i=0;              //Zaehler, Position im Array
  int abbruch=FALSE;    //Abbruch-Flag 1=Abbrechen, 0=Nicht abbrechen
  int hilf=0;           //Hilfsvariable fuer vertauschen

  while(! abbruch)
  {
    abbruch=TRUE;
    while(i<(anzahl-1))
    {
      // Pruefen der benachbarten Kugeln
      if(arr[i]>arr[i+1])
      {
        // Vertauschen der beiden Kugeln falls noetig !
        hilf=arr[i];
        arr[i]=arr[i+1];
        arr[i+1]=hilf;
        abbruch = FALSE;  //Nicht abbrechen !
      }
      i++;
    }
    i=0;
  }
}

/*****************MAIN**********************************/

void main(void)
{
  int i = 0;
  int arr[10];

  while(i<10)
  {
    printf("Eingabe %i. Zahl : ",i+1);
    scanf("%i",&arr[i]);
    i++;
  }
  printf("\n");
  bubble_sort(arr, 10);

  // Ausgabe arrays
  i=0;
  while(i<10)
  {
    printf("%i. Zahl : %i\n",i+1, arr[i]);
   i++;
  }
}

/*****************************************/
/* Titel :  Structs                      */
/* Autor :  ARJ                          */
/* Datum :  10. Maerz 2017               */
/* Zweck :  Beispiel                     */
/* Version : 1.0                         */
/*****************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define TRUE  1
#define FALSE 0
#define MAX_PERS 10

// Definition der Struktur
struct Person
{
  int   p_nummer;
  int   belegt;
  char  nachname[31];
  char  vorname[31];
  char  wohnort[31];
  int   jahrgang;
};

// Ausgabe eines Records (=struct) oder Datensatzes
void p_anzeigen(struct Person * p_thePerson)
{
  if (p_thePerson->belegt)
  {
    printf("P_Nr     : %i \n",p_thePerson->p_nummer);  // Dereferenzierung bei struct
    printf("Name     : %s \n",p_thePerson->nachname);
    printf("Vorname  : %s \n",p_thePerson->vorname);
    printf("Wohnort  : %s \n",p_thePerson->wohnort);
    printf("Jahrgang : %i \n",p_thePerson->jahrgang);
    printf("\n");
  };
};

// Erfassen eines Records (=struct) oder Datensatzes
int p_erfassen_ok(int nr, struct Person * p_thePerson)
{
  if (! (p_thePerson->belegt))
  {
    printf("\n\n");
    printf("Name    (max 30 Zeichen !) : ");
    fflush(stdin);
    gets(p_thePerson->nachname);
    printf("Vorname (max 30 Zeichen !) : ");
    fflush(stdin);
    gets(p_thePerson->vorname);
    printf("Wohnort (max 30 Zeichen !) : ");
    fflush(stdin);
    gets(p_thePerson->wohnort);
    printf("Jahrgang                   : ");
    fflush(stdin);
    scanf("%i",&p_thePerson->jahrgang);
    printf("\n");
    p_thePerson->belegt=TRUE;
    p_thePerson->p_nummer=nr;
    return(1);
  }
  else
  return(0);
}

// Funktion mit Titel
void titel(void)
{
  system("cls");
  printf("\n*******************************************\n");
  printf("***********PERSONEN DATENBANK**************\n");
  printf("***********************************V1.0****\n\n\n");
}

/////////////////////////////////////////////////////////////////////////////////
void main(void)
{
  //Variablendeklaration
  struct Person personen[MAX_PERS];  // Daten : Array of struct
  char ch='\0';                      // Menue-Eingabe
  int beenden=FALSE;                 // Menue-Beenden-Flag
  int i=0;                           // Zaehlervariable
  int ok=FALSE;                      // Flag bei Eingabe von Datensaetzen
  int Personalnummer=1;              // Eindeutige Nummer eines Datensatzes
  int nr=0;                          // Hilfsvariable fuer P_Nr zu merken

  // Initialisierung
  for(i=0; i<MAX_PERS; i++)
  personen[i].belegt=FALSE;

  while(!beenden)
  {
    titel();
    printf("****************MENUE**********************\n");
    printf("Neue Person erfassen : n und ENTER druecken\n");
    printf("Alle Person anzeigen : a und ENTER druecken\n");
    printf("Person loeschen      : l und ENTER druecken\n");
    printf("Person mutieren      : m und ENTER druecken\n");
    printf("Hilfe                : h und ENTER druecken\n\n");
    printf("EXIT                 : x und ENTER druecken\n\n");
    printf("Ihre Wahl            : ");
    fflush(stdin);
    scanf("%c",&ch);

    switch(ch)
    {
      case 'N' :
      case 'n' : titel();
                 printf("****************Neuer Datensatz************\n");
                 ok=FALSE;
                 for(i=0;!ok && (i<MAX_PERS); i++)
                 {
                   ok=p_erfassen_ok(Personalnummer, &personen[i]);
                   if (ok)
                   Personalnummer++;
                   if(i>=MAX_PERS-1)
                     printf("\n\n*** Speicher voll !!!***\n");
                  }
                  break;
       case 'A' :
       case 'a' : titel();
                  printf("****************Ausgabe Datensaetze********\n\n");
                  for(i=0; i<MAX_PERS; i++)
                  p_anzeigen(&personen[i]);
                  printf("Fuer Weiter bitte ENTER druecken ...");
                  fflush(stdin);
                  scanf("%c",&ch);
                  break;
       case 'L' :
       case 'l' : titel();
                  printf("****************Datensatz loeschen*********\n");
                  for(i=0; i<MAX_PERS; i++)
                    p_anzeigen(&personen[i]);
                  printf("Welcher Datensatz soll geloescht werden (P_Nr eingeben) : ");
                  fflush(stdin);
                  scanf("%i",&nr);
                  for(i=0; i<MAX_PERS; i++)
                    if (personen[i].p_nummer==nr)
                    {
                      printf("Datensatz %i wird geloescht !\n",nr);
                      personen[i].belegt=FALSE;
                    }
                  break;
       case 'M' :
       case 'm' : titel();
                  printf("****************Datensatz mutieren*********\n");
                  for(i=0; i<MAX_PERS; i++)
                    p_anzeigen(&personen[i]);
                  printf("Welcher Datensatz soll modifiziert werden (P_Nr eingeben) : ");
                  fflush(stdin);
                  ok=FALSE;
                  scanf("%i",&nr);
                  for(i=0;!ok && (i<MAX_PERS); i++)
                  if (personen[i].p_nummer==nr)
                  {
                    personen[i].belegt=FALSE;
                    ok=p_erfassen_ok(Personalnummer, &personen[i]);
                    if (ok)
                    Personalnummer++;
                  }
                  break;

       case 'h' :
       case 'H' : titel();
                  printf("****************Hilfe**********************\n");
                  printf("Mit diesem Programm koennen Sie bis \n");
                  printf("max. 10 Personen erfassen, mutieren oder \n");
                  printf("loeschen.  Viel Spass !\n\n");
                  printf("Fuer Weiter bitte ENTER druecken ...");
                  fflush(stdin);
                  scanf("%c",&ch);
                  break;
       case 'X' :
       case 'x' : titel();
                  printf("****************Programm beenden***********\n");
                  printf("\n\nSind Sie sicher das Sie das Programm beenden\n");
                  printf("moechten ? (j fuer ja / n fuer nein) : ");
                  fflush(stdin);
                  scanf("%c",&ch);
                  if( (ch=='J') || (ch=='j') )
                    beenden=TRUE;
                  break;
     } //End switch-case
   } //End while
} //End main

Bemerkung:

Dieses Programm hat noch einige Mängel, zB. die Mutier-Funktion, die noch verbesserungswürdig ist. Der ganze Ansatz könnte mit dynamischer Speicherverwaltung und "Linked-List" eleganter gelöst werden.