[Inhalt][0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17]

Wilkommen beim zweiten Teil des C-Kurses.

Wie am Ende des ersten Teils versprochen wurde, befassen wir uns heute mit der Datenein- und Datenausgabe. Und wie immer: Erstmal etwas Theorie :-) Kein Panik, ist nicht schlimmer als im ersten Teil.
 
DIE VARIABLEN

Variablen bestehen aus einem Variablennamen und einem Variablentyp. Das kann man vergleichen mit einem Behälter, wo drauf steht, was drin ist. Durch den Variablennamen geben wir dem Behälter einen eindeutigen Namen, denn wir wollen ja den Behälter später wieder finden. Wie immer ein kleines Beispiel:
 

/* Variablen sind eine feine Sache */ 
#include <stdio.h>
 

void main (  void
{

int anzahl;

anzahl=100; 
printf ("\nAnzahl = %d \n",anzahl);

}

Ich sehe schon die Fragezeichen aufleuchten: "Was bitte soll das %d hier ?" Wie immer: Mysterien sind da, um gelöst zu werden. Am Anfang wird die Variable Anzahl erzeugt. Dies geschieht, in dem nach dem Variablentyp die zu erzeugende Variable, hier Anzahl, steht. Danach wird der Variablen der Wert 100 zugewiesen. Wir haben nun einen Behälter (Variable) von einer bestimmten Größe (int). Den Behälter füllen wir nun mit einem Wert, in dem Fall 100. Danach wird der Wert ausgegeben. Nach dem starten des Programms erscheint also

Screenshot des ausgeführten Programms


Grundlegende Variablentypen

Wir machen kurz einen Werbeblock für die Behälterindustrie: C kennt folgende Grundtypen einer Variable, die dem folgenden Wertebereich entsprechen:

In eckigen Klammern dahinter habe ich die englischen Begriffe geschrieben, von denen die Bezeichnungen stammen. So ist es manchmal einfacher am Anfang sich die Begriffe zu merken.
Double-Größen unterscheiden sich von den float-Größen nur in ihrem Wertebereich. Dadurch, das die double-Variablen doppelt soviel Speicherplatz wie die float-Variablen belegen, können mit ihnen natürlich auch mehr Zahlen dargestellt werden.

Dem Variablentypen 'int' kann nun noch eins der Schlüsselwörter 'signed' oder 'unsigned' vorangehen. Steht 'signed' davor, kann das Programm Vorzeichen unterscheiden; steht 'unsigned' davor, dürfen nur positive Werte benutzt werden. Wo da der Sinn liegt ? Die Variablen belegen je nach Type eine bestimmte Menge Speicher, den sie benötigen um eine Variable abzuspeichern. Damit alles gleichmäßig verteilt ist, ist der Anteil der negativen Zahlen genauso groß wie der, der positiven. Steht ein 'signed' davor, so benutzt das Programm die positiven und negativen Wertebereiche. Steht ein 'unsigned' davor, wird nur der positive Teil betrachtet. Moment, was passiert mit dem restlichen negativen Anteil ? Genau, da er uns auch etwas bringen soll, benutzen wir ihn einfach mit und hängen ihn an den positiven Wertebereich hinten an. Damit könenn wir also doppelt soviele positive Zahlen darstellen, wie mit dem 'signed' Variablentyp. Das kann nützlich sein, wenn man eh keine negativen Zahlen benötigt. Bei den int-Variblentypen kann zusätzlich noch 'long' oder short 'short' vergesetzt werden. Wie unten zu sehen ist, gibt es zwei int-Wertebereiche; einmal für 16-Bit Maschinen und 32-Bit Maschinen. Sowas ist echt ärgerlich und kann einen ganz schön ins Fettnäpfchen treten lassen. Wenn 'short' davor steht, werden die 16-Bit Wertebreiche und bei 'long' die 32-Bit Wertebereiche verwendet.
Grundsätzlich sind alle 'int' - Variablen 'signed' , wenn nichts explizit angegeben ist!
 
 
Hier nun die Wertebereiche der Variablentypen:

Da jeder Computer nur in gewissen Grenzen rechnen kann, stellen diese auch die Grenzen der Zahlenbereiche dar, die durch Variablentypen dargestellt werden können.
 

int
- 32 768 ... 32 767 (16 Bit Maschinen) 

- 2 147 483 648 ... 2 147 483 647 (32 Bit Maschinen)

unsigned int
0 ... 65 535 (16 Bit Maschinen) 

0 ... 4 294 967 295 (32 Bit Maschinen)

short int
- 32 768 ... 32 767
long int
- 2 147 483 648 ... 2 147 483 647
unsigned short int
0 ... 65 535
unsigned long int
0... 4 294 967 295

Wie das bei 'signed short int' und 'signed long int' aussehen könnte ist Hausaufgabe :)

char
alle ASCII-Zeichen ,
das sind alle Zeichen, die man auf dem Bildschirm darstellen kann
unsigned char
alle ASCII-Zeichen

Wie das zustande kommt, am Ende der Tabelle - löst sich in wohlgefallen auf! Versprochen!

float
float -10 ^38 ... 10 ^38
double
-10 ^308 ... 10 ^308

Um kurz das 'unsigned char' - Problem zu lösen einen kleinen Ausflug in die Rechnerwelt von Vorgestern. Früher gab es zwei Arten ASCII-Zeichen, das sind die darstellbaren Zeichen, zu kodieren. Da Rechner intern nur mit Zahlen arbeiten, mußten die Zeichen in irgendeiner Form als Zahlen dargestellt werden. Die einen sagten: Wir nehmen den Wertebereich von -128 ... 127 . Die anderen sagten sagten sich: Was soll´s, ist eh nur eine Aufzählung, laßt uns bei 0 beginnen , wie es sich gehört :-) Dieser Wertebereich ging also von 0 ... 255. Um diesem Problem der Unterschiedlichen ASCII-Werte zu entgehen, wurde der 'unsigned char' Typ eingeführt, welcher nur Werte von 0 ... 255 kennt. Prinzipiell wird heute nur noch mit 'unsigned char' gearbeitet, das heißt dieses Problem werden wir einfach unter den Tisch kehren. Ist Ihnen noch was aufgefallen ? Der positive Wertebereich hat scheinbar eine Zahl weniger :-) Nein, nicht wirklich, die 0 wurde einfach den positiven Zahlen zugeordnet. So, jetzt haben wir genausoviele positive wie negative Zahlen. Zurück zu unserem Programm, schon abgetippt und gestartet ? Auf dem Bildschirm müßte jetzt stehen:

Screenshot des ausgeführten Programms

Toll! Aber was sollte das %d dort ? Wie im letzten Teil schon erwähnt ist das % ein Sonderzeichen, genau wie der \ . Das % sagt dem Programm, das dort der Wert einer Variable stehen soll. In dem Fall %d wird eine integer-Variable angezeigt.Diese muß innerhalb des printf-Befehls innerhalb der Klammer stehen.
Aufzählungen von Variablen werden mittels eines Kommas getrennt!
 
 
Variablenzuweisung

Variablen werden generell durch ' = ' mit einem Wert abhängig vom Variablenwert belegt.
 

Variable  =  Ausdruck

Zu beachten ist, das der Ausdruck vom selben Typ wie die Variable sein muß. Es wäre ja unsinnig einer Zahl einen Buchstaben zuzuweisen. Zu beachten ist, das ASCII-Zeichen mittels des einfachen Hochkommata zugewiesen werden.
 

char Zeichen  =  'x' ;

Wenn Zahlenwerte zugewiesen werden, kann dies entweder direkt oder über mathematische Ausdrücke geschehen.  Diese können auch geklammert oder mit Hilfe von anderen Variablen berechnet werden. Ein mathematischer Ausdruck kann folgende Symbole enthalten
 

+ Addition
- Subtraktion
* Multiplikation
/ Division
% Modulo

Die geläufigen + , - , * und / brauch man nicht weiter beschreiben, den sie gehören zum Grundwissen. Generell gilt auch hier: Punkt- vor Strichregel. Bei einer Division von Integerwerten wird der Nachkommaanteil abgeschnitten . Die Modulo-Funktion liefert als Ergebnis den ganzzahligen Divisionsrest zweier Zahlen zurück. x % y liefert als Ergebnis den Divisionsrest von x / y .
 

/*
Beispiel einer Modulofunktion
*/
#include <stdio.h>

 void main(void)
{

int zahl_a = 5;
int zahl_b = 2;
int div_rest;

div_rest = zahl_a % zahl_b;
printf ("\n%d %% %d = %d\n",zahl_a, zahl_b,div_rest);

}

Screenshot des ausgeführten Programms






Wie wir wissen, gilt 5 / 2 = 2 Rest 1. Wenn wir das obere Programm etwas umschreiben und die Eingangsbemerkung über Integerdivision anwenden, haben wir ein Programm um Ganzzahldivisionen durchführen zu lassen und dazu auch den Restbetrag. Wie es sich gehört, werden wir es gleich mit Eingabeaufforderungen aufpeppen.
 

/* 
Einlesen zweier Zahlen und mit denen
eine Division ausführen. Divisionsrest
wird auch angezeigt.
/*
#include <stdio.h>

 void main(void)
{

int zahl_a;
int zahl_b;
int div_rest;
int div_ergebnis;

printf ("\nBitte Zahl a eingeben : ");
scanf ("%d",&zahl_a);
printf ("\nBitte Zahl b eingeben : ");
scanf ("%d",&zahl_b);

div_ergebnis = zahl_a / zahl_b;
div_rest = zahl_a % zahl_b;

printf ("\n%d / %d = ",zahl_a, zahl_b);
printf ("%d Rest %d\n",div_ergebnis, div_rest);

}

Screenshot des ausgeführten Programms

Wer befürchtet, das man in C nur diese Grundrechenarten kennt, der irrt zum Glück. Es gibt z.B. eine Bibliothek, mit der man dann auch Cosinus, Exponenten und andere Werte berechnen kann. Dies wird aber in einem späteren Kapitel besprochen.
 
 
Typenwandlung (Casting)

Was tun, wenn man eine integer-Variable hat und diese einmal als double und einmal als float in benötigt ? Man verpackt sie und tut so, als ob die Variable den gewünschten Variablentyp entspricht. Wenn man die beliebte Darstellung der Variablentypen als Behälter mit einer Beschriftung verwendet, würde dies bedeuten, das wir einfach einen anderen Aufkleber auf den Behälter kleben. Dies erreicht man indem man der eigentlichen Variablen in Klammern den gewünschten Variablentyp voranstellt:
 

( Zieltyp ) Variable

Noch sieht das ganze etwas verworren aus, deshalb schnell ein Praxisbeispiel.
 

#include <stdio.h> 

void main ( void )

int ganze_zahl = 4; 
int i; 
double fliesz_zahl = 2.34; 
double d; 

/* 

fliesz_zahl wird in eine Zahl
des Typs integer kopiert, indem
sie als Integervariable erscheint
*/
i = ( int ) fliesz_zahl; 

/*

eine integer erscheint als
Variablentyp double
*/
d = (double)  ganze_zahl;
}

Vorsicht: Das geht nicht mit Zeigern! (Kleiner Vorgriff meinerseits) Zu beachten ist: Wenn z.B. eine Typenumwandlung von double zu integer erfolgt , so wird der Nachkommateil abgetrennt. Generell werden Informationen, wenn von einem Variablenbereich in einen gecastet wird, der eine weniger genaue Darstellung besitzt, immer Informationen abgeschnitten werden, z.B. der Nachkommaanteil.
 
 
Datenausgabe

Zurück zu unserem Beispielprogramm. DieAusgabe haben wir mit Hilfe von Hilfszeichen durchgeführt, die alle mit % beginnen.. So können wir auf diese Weise verschiedene Datentypen darstellen.
 

%d
int oder short int als Dezimalzahl darstellen
%ld
long int als Zahl darstellen
%c
char als Zeichen  ausgeben
%x
wie %d als Hexadezimal mit kleinem a...f 
%X
wie %d als Hexadezimal mit großem A...F 
%o
wie %d als Oktalzeichen
%u
wie %d , aber nur Werte ohne Vorzeichen
%f
Floatzahlen in Fließkommaschreibweise , z.B. 3.43234
%e
Floatzahlen in Exponentialschreibweise , z.B. 23e+432 
%E
wie %e nur mit großen E , z.B. 23E+432
%g
Exponential- oder Fließkommaschreibweise ,
in Abhängigkeit vom Wert
%s
char* , bzw. char[ ]
%le
double darstellen 
%%
Gibt ein % aus.  Braucht man ja ab und zu.

In unserem Programm steht also, das an der Stelle, wo das %d steht, die Variable vom Typ Ganzzahl (int) dort eingesetzt werden soll.
Probieren sie ruhig das folgende Beispiel aus und versuchen sie zu verstehen, was es macht:
 

/* Wie kann ich ein Zeichen darstellen */ 
#include <stdio.h>

void main ( void
{

    char zeichen;
    zeichen='d';

    printf ("\nDas Zeichen %c ist im ASCII-Code ein %d \n",zeichen,zeichen);

}

Nach dem Start sollte folgendes auf dem Bildschirm zu sehen sein:

Screenshot des ausgeführten Programms

Hier sehen wir zwei interessante Dinge, einmal das die Zeichen zwar als char deklariert sind, intern jedoch als Zahlen dargestellt werden. Diese entsprechen den vorher kurz erwähnten ASCII-Zeichen, das sind normierte Zahlendarstellungen für Zeichen.Eine kleine Falle stellen immer die Zahlen dar. Das Zeichen '0' hat als ASCII-Zeichen den Wert 48 und nicht 0, wie man annehmen könnte.

Screenshot des ausgeführten Programms

Ein anderes Beispiel:
 

/* Floats sind auch nur Zahlen */ 
#include <stdio.h>

void main ( void
{

    float zahl=5.01;

    printf ("\nEine Floatzahl einmal so %f und einmal so %e \n",zahl,zahl);

}

Nach dem starten...

Screenshot des ausgeführten Programms

Hier haben wir einmal ausprobiert, wie die Ergebnisse sich unterscheiden, wenn man verschiedene Darstellungen für die float-Variablen anwendet. Bei unserem letzten Beispiel haben wir noch etwas neues gemacht: Der Variablen wird gleich ein Wert bei deren Erzeugung zugewiesen! Wir können also statt:
 

float zahl; 

zahl=102.033;

auch schreiben:
 

float zahl=102.033;

 
 
Dateneingabe

Wie gewohnt ein Beispiel , an dem wir uns dann entlangseilen werden.
 

/* Variablen zu lesen macht auch Spasz */ 
#include <stdio.h>

void main ( void
{

    int anzahl;

    printf ("\nBitte eine Integer-Zahl eingeben : "); 
    scanf ("%d",&anzahl);

    printf ("\n\nSie gaben %d ein\n",anzahl); 

}

Versuchen sie zu erkennen, was das Programm macht. Das ganze Programm dürfte für sie ein Kinderspiel sein, bis auf: scanf (...). Dieser Befehl dient der Dateneingabe. Als Ausgabe steht nur, welche Variablentypen eingelesen werden und an welche Variablen die Daten übergeben werden.

In unserem Beispiel: mittels des %d erkennt der scanf-Befehl, das eine Integer-Variable eingelesen werden soll. Standartmäßig wird dieser Wert von der Tastatur eingelesen. Tippen wir einfach mal 42 ein und schließen das mit Druck auf die Return-Taste ab. Das Programm hat nun einen Wert ( 42 ) von uns, den es irgendwo ablegen muß. Mit '&anzahl' sagen wir dem Programm, das der Wert in den Speicherplatz der Variable anzahl geschrieben werden soll. Danach besitzt die Variable den von uns eingegebenen Wert, den wir , freundlich wie wir numal sind, auch auf dem Bildschirm wieder ausgeben.Später werden wir uns noch näher mit den sogenannten Adressoperatoren beschäftigen. Das sollte dann in etwa so aussehen:

Screenshot des ausgeführten Programms






Wie wir schon sehen, haben wir, ähnlich wie bei printf , ein formatiertes einlesen, wo wir jeweils angeben welche Typen eingelesen werden sollen.
 

%d
int einlesen in dezimaler Schreibweise 
%i
int einlesen, entweder oktal (mit führender 0) oder hexadezimal (mit führendem 0x oder 0X)
%o
int ganzzahlig oktal einlesen (mit 0 am Anfang)
%u
unsigned int , d.h. ohne Vorzeichen
%x
int ganzzahlig hexadezimal einlesen (führende 0x oder 0X muß nicht angegeben werden) 
%c
char Zeichen wird eingelesen
%s
Zeichenkette char[] oder char* , wobei als letztes Zeichen \0 ist
Wird die Zeichenkette in char[] abgelegt, so wird nur bis zum letzten Feldelement eingelesen.
Das Leerzeichen ist hier ein Trennzeichen für verschiedene Zeichenketten
%e , %f oder %g
Floatzahlen mit optionalen Vorzeichen, optionalen Dezimalpunkt und optionalen Exponenten

Wenn man also eine Hexadezimalzahl einlesen will, kann man dies z.B. so tun.
 

/*
Darstellen einer eingelesenen Hexadzimalzahl
in Hexadezimal-, Okta- und Dezimalform
*/
#include <stdio.h>

void main (void)
{

 int hex_zahl;

 printf ("\nBitte Zahl in Hexadezimal eingeben : ");
 scanf ("%x", &hex_zahl);

 printf ("\nHexadezimal : %X\n",hex_zahl);
 printf ("\nDezimal     : %d\n",hex_zahl);
 printf ("\nOktal       : %o\n\n\n\n",hex_zahl);

}

Und erhält z.B. folgenden Dialog auf dem Bildschirm.

Screenshot des ausgeführten Programms


getchar und putchar

Zusätzlich gibt es noch Befehle, die das einlesen und Ausgeben eines einzelnen Zeichens über die Standardeingabe und -ausgabe erlauben, also in der Regel dem einlesen über Tastatur und dem Ausgeben auf dem Bildschirm.Zu dem, was sich hinter diesen Kanälen verbirgt später mehr. getchar() liefert ein char-Zeichen zurück und  putchar() wird ein char-Zeichen übergeben. Das sieht dann Beispielsweise so aus.
 

/*
Beispiel für getchar und putchar
*/
#include <stdio.h>

void main (void)
{

char zeichen;
char abbruch = '#';

/*

char getchar();
   getchar liefert ein char-Wert zurück,

puchar (char);
   putchar gibt ein char-zeichen aus,
   bsp.weise putchar('a');
 

es wird solange ein Zeichen eingelesen,
solange kein Zeichen unsere Abbruch-
bedingung '#' erfüllt.

*/
while (( zeichen = getchar() ) !=  abbruch)
putchar (zeichen);
printf ("\n\nDanke fuer den Programmstart\n\n");
}

Das Programm gibt solange alle eingegebenen Zeichen aus, bis # gedrückt wurde. Das einlesen der Zeichen wird mit putchar realisiert. Die Schleifenbedingung while , die hier verwendet wurde, wird später erklärt. Sie braucht es also nicht zu beunruhigen, da auf sie noch später eingegangen wird. Starten wir das Programm, so können wir folgenden Dialog erhalten:

Screenshot des ausgeführten Programms

Soviel zu unserer schönen Theorie. An diesem Beispiel können wir auch schon sehen, wie unser System Daten verarbeitet. Alle Ein- und Ausgaben werden in der Regel durch die Betriebssysteme gepuffert. Geben wir also unseren Text ein, so wird er nicht zeichen für Zeichen übergeben, sondern ersteinmal in einen Zwischenspeicher, einen sogenannten Puffer, geschrieben. Nach einem return wird der Puffer an das Programm übergeben und nacheinander gibt getchar() die Zeichen an das Programm weiter. Wie wir sehen, kümmert sich das System herzlich wenig darum, welche Abbruchbedingung in unserem Programm gesetzt wurden. Der Vollständigkeit halber hier noch die generelle Darstellung von getchar und putchar.
 

char getchar();

 
char putchar(char);

Momentmal, wird hier einer sagen, wieso bekommen wir von putchar einen Wert zurück ? Das liegt daran, das auf diese Weise eine Fehlerkontrolle möglich ist. In der Regel prüft man seltenst, ob alles bei der Ausgabe korrekt verlief, aber man hat zumindest die Möglichkeit es zu prüfen. Wenn das zurückgegebene Zeichen gleich dem übergebenen Zeichen ist, konnte es ordnungsgemäß ausgegeben werden. Wenn der zurückgegebene Wert EOF ist, so ist ein Fehler aufgetreten. Veranschaulichen wir es uns am Beispiel des vorigen Programmes, was mit dieser Formulierung gemeint ist.
 

/*
Beispiel für getchar und putchar mit Fehlerprüfung
*/
#include <stdio.h>

void main (void)
{

char zeichen;
char abbruch = '#';
char check;

while (( zeichen = getchar() ) !=  abbruch)
{

check = putchar (zeichen);
if (check == EOF
printf ("\n!!Fehler aufgetreten!!\n");
}
printf ("\n\nDanke fuer den Programmstart\n\n");
}

 
Übungsaufgabe

Folgendes Beispielprogramm:
 

/* 
Einlesen zweier Floatzahlen die addiert werden
*/
#include <stdio.h>

void main (  void )
{

    float zahl1, zahl2, zahl3;

    printf ("\n\nBitte geben sie zwei Flieszkommazahlen ein [z.B. 2.34 , 5.23] ");
    scanf ("%f ,%f",&zahl1,&zahl2);

    zahl3 = zahl1 + zahl2;

    printf ("\n\n %f + %f = %f \n\n",zahl1, zahl2, zahl3);

}

Kleiner Hinweis: Die zwei einzugebenden Zahlen sind durch ein Komma zu trennen.

1) Was macht das Programm ? Versuchen sie Zeile für Zeile zu verstehen.
2) Ändern sie das Programm so ab, das keine Floatgrößen, sondern Ganzzahlige (int) Werte eingelesen und ausgegeben werden.
3) Probieren sie selbiges einmal mit char-Variablen und überlegen sie, warum das Ergebnis so aussieht und was es mit der Addition von Buchstaben auf sich hat.

[Inhalt][0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17]