116
Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University Fachbereich Mechanik und Elektronik Mechatronik und Mikrosystemtechnik Vorlesungsmanuskript Informatik Die Programmiersprache C Dipl.-Ing. D. Hägele, MSc Stand 30.09.2015

2.1 Charakteristikas von C

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University Fachbereich Mechanik und Elektronik Mechatronik und Mikrosystemtechnik

Vorlesungsmanuskript

Informatik

Die Programmiersprache C

Dipl.-Ing. D. Hägele, MSc

Stand 30.09.2015

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 1 D. Hägele, Stand 30.09.2015

Die Programmiersprache C 1. Inhaltsverzeichnis

2. Einführung in C 4 2.1 Charakteristika von C 5 2.2 Überblick über C 7 2.2.1 Die Entwicklungsumgebung Code::Blocks 7 2.2.2 Ein einfaches C-Programm 11 2.2.3 Das Funktionskonzept 13 2.2.4 Datenobjekte und ihre Speicherklassen 14 3. C-Syntax 15 3.1 Bezeichner 15 3.2 Operatoren und Separatoren 15 3.3 Kommentare 16 3.4 Konstanten 16 3.4.1 Ganzzahl-Konstanten 16 3.4.2 Zeichen-Konstanten 17 3.4.3 Gleitkomma-Konstanten 18 3.4.4 Zeichenketten-Konstanten 19 4. Datentypen und Objekte 4.1 Ganzzahltypen 20 4.2 Gleitkommatypen 21 4.3 Vektoren 21 4.4 Deklaration und Initialisierung 22 4.5 Speicherbedarf 23 4.6 Typkonversion 24 4.6.1 Implizite Typkonversion 24 4.6.2 Explizite Typkonversion 26

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 2 D. Hägele, Stand 30.09.2015

5. Operatoren 5.1 Die Zuweisung 27 5.2 Arithmetische Operatoren 28 5.3 Inkrement- und Dekrementoperatoren 29 5.4 Vergleichsoperatoren 31 5.5 Logische Operatoren 32 5.6 Mathematische Funktionen 32 5.7 Bit-Operatoren 33 5.8 Verschiebeoperatoren 34 5.8.1 Die Linksverschiebung 34 5.8.2 Die Rechtsverschiebung 34 5.9 Operator-Prioritäten 35

6. Ein-/ Ausgabe (I/O) 6.1 Formatierte Ein-/ Ausgabe 36 6.1.1 Formatierte Ausgabe: printf 36 6.1.2 Formatierte Eingabe: scanf 39 6.2 Unformatierte Ein-/ Ausgabe 43 6.2.1 Unformatierte Ausgabe: put 43 6.2.2 Unformatierte Eingabe: get 43

7. Kontrollstrukturen 7.1 Verzweigung : if, if-else 44 7.2 Die bedingte Zuweisung 46 7.3 Die while-Schleife 47 7.4 Die for-Schleife 49 7.5 Die do-while Schleife 50 7.6 Die switch-Anweisung 50

8. Kontrolltransferanweisungen 8.1 Abbruchanweisung break 51 8.2 Fortsetzungsanweisung continue 53 8.3 Sprunganweisung goto 54 8.4 Leeranweisung ";" 56 8.5 Komma-Operator 56

9. Programmstruktur 9.1 Ausdruck, Anweisung 57 9.2 Blöcke 57 9.3 Funktionen 58 9.4 Speicherklassen 60 9.5 Parameter von Funktionen 66 9.5.1 Call by value 66 9.5.2 CaII by reference 67

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 3 D. Hägele, Stand 30.09.2015

10. Zeiger und Vektoren 10.1 Zeiger 68 10.2 Vektoren 71 10.3 Zeigerarithmetik 73 10.4 Stringkonstanten 77 10.5 Ragged Arrays 80 10.6 Argumente aus der Kommandozeile 81 11. Zusammengesetzte Datentypen 11.1 Strukturtypen 82 11.2 Aufzählungstypen 86 11.3 Unions 87 11.4 Die typedef-Anweisung 89 12. Der Preprocessor 12.1 Konstantendefinition 91 12.2 Include-Anweisungen 92 12.3 Makrodefinitionen 93 12.4 Bedingte Compilierung 94 13. High Level Input/Output (Bibliotheks I/O) 13.1 High Level und Low Level I/O 95 13.1.1 Der Low Level I/O 96 13.1.2 Der High Level I/O 96 13.2 Formatierte Ein-/Ausgabe 99 13.3 Zeichenweise Ein-/Ausgabe 102 13.4 Ein-/Ausgabe beliebiger Daten 103 13.5 Wahlfreier Zugriff 105 14. Funktionsfamilien 109 Glossar printf-Formattabelle C-Escapefolgen Standardfunktionen

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 4 D. Hägele, Stand 30.09.2015

2. Einführung in C

Entwickelt wurde C 1971/72 an den Bell Laboratories für das UNIX-Betriebssystem. Nahezu 90% des Betriebssystems und seiner Dienstprogramme sind in C geschrieben. Außer den maschinenabhängigen Teilen ist die Software auf nahezu allen Systemen identisch. Da C nicht von der Hardwarearchitektur abhängig ist, lässt sich daraus die hohe Portabilität von Unix erklären. Dennoch gilt C allgemein als maschinennahe System-Programmiersprache, da C mit dem Ziel entwickelt wurde, eine Sprache für hardwarenahe Implementierungen von Systemteilen zu schaffen, die anders als Assembler gleichzeitig unabhängig von der Hardware ist. Die Entwicklung begann bereits 1963 mit CPL über BCPL und B, die alle typenlose Sprachen sind und als einzigen Datentyp das Maschinenwort kennen, aus dem der Benutzer abstrakte Datentypen entwickeln muss. Abstrakte und zusammengesetzte Datentypen können nur über Zeiger und Adressarithmetik verarbeitet werden. Alle heute üblichen Kontrollstrukturen sind vorhanden. Ferner bieten die Sprachen die Möglichkeit eigene Funktionen zu definieren, die in Bibliotheken allgemein zugänglich gemacht werden können. Bei der Portierung des frühen Unix auf eine DEC-PDP-11 wurde ersichtlich, dass diese Programmiersprachen die wesentlich leistungsfähigere Hardware bei weitem nicht ausnutzen konnten. So unterstützt die PDP-11 verschiedene fundamentale Datentypen wie 1-Byte-Zeichen, 2-Byte-Integer und 4-Byte-Gleitkomma. Aufgrund dieser Situation wurde C entwickelt. Man erkannte aber sehr schnell, dass durch Lösung von hardwarenahen Sprachmitteln ein Gewinn an Portabilität zu erreichen ist, der wesentlich höher zu bewerten ist als ein minimaler Leistungsverlust. Dadurch ist die Sprache heute hardware- und betriebssystemunabhängig und auch für eine Reihe von Betriebssystemen verfügbar. Die erste exakte Definition von C erschien 19781 . Diese Definition wurde 1983 erweitert2. Die amerikanische Normungsbehörde ANSI hat 1983 einen einheitlichen Standard von C in der Norm ANSI X3.159 festgelegt (aktuelle Ausgabe von 1989). Dieser Standard wurde dann im internationalen Standard ISO/IEC 9899 übernommen. 1986 wurde C (imperativ) erweitert zu C++ (objekt-orientiert). Alle C-Standards sind aufwärtskompatibel. UNIX ist ein eingetragenes Warenzeichen der Bell Laboratories. Das UNIX Betriebssystem wird (für Europa) in Lizenz vergeben durch AT &T International.. DEC und PDP sind eingetragene Warenzeichen der Digital Equipment Corporation. 1) Kernighan, Brian W. und Ritchie, Dennis M.: The C Programming Language, Anhang C Reference

Manual, Hertfordshire 1978 2) Ritchie, Dennis M.: The C Programming Language - Reference Manual, Bell Laboratories 1983

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 5 D. Hägele, Stand 30.09.2015

Nachfolgend eine Abbildung zu die Entwicklungsstufen von C.

2.1 Charakteristika von C

Mit C verbindet man Leistungsattribute wie Kompaktheit, Schnelligkeit, Möglichkeiten und Effizienz einer Assemblersprache mit dem Komfort einer Hochsprache. In C-Programmen kann auf das einzelne Bit zugegriffen werden, Adressen sind manipulierbar (unter WINDOWS XP nur noch bedingt) und die Speicherverwaltung der Daten wird vom Programmierer durch die Festlegung von Speicherklassen definiert. Die Grenze zum Assembler ist gleichwohl klar gezogen: Registerzugriffe sind nicht möglich, Kommunikation mit der Peripherie und mit dem Systemkern nur über definierte Schnittstellen, die in Assemblerprogrammen realisiert sind.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 6 D. Hägele, Stand 30.09.2015

Nachfolgend ein Überblick über die wesentlichen Merkmale: Typenkonzept

- fundamentale Datentypen - zusammengesetzte Datentypen - Aufzählungstypen, Varianten - Zeiger - Bitfelder - benutzerdefinierte Datentypen - Funktionen - Flexibilität (keine strenge Datentypbindung) Programmstruktur

- blockorientiert - modulares Funktionenkonzept - call by value, call by reference - Selektion, Iteration, Rekursion mächtiges Operatorset

- Adressarithmetik - Speicheroperationen Systemaufrufe Standard C Subroutinen hohe Portabilität

da unabhängig von: - einem bestimmten Betriebssystem - einer speziellen Hardwarearchitektur Sprachmittel für:

- Ein-/Ausgabefunktionen - mathematische Funktionen - Prozeßerzeugung/-synchronisation/-kommunikation

sind nicht Bestandteil der Sprache, sondern werden durch Bibliotheksfunktionen und Betriebssystemfunktionen bereitgestellt.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 7 D. Hägele, Stand 30.09.2015

Der Sprachumfang von C ist deshalb vergleichsweise klein, wie aus der nachfolgenden Auflistung ersichtlich wird. Schlüsselworte: 1. automatic, extern, static, register

2. char, short, int, long, float, double, unsigned

3. if, else, switch, case, break, continue, default, for, do, while, return, goto

4. enum, struct, union

5. void, typedef, sizeof

Nichtsdestotrotz ist C eine elegante Programmiersprache. Sie steckt den Programmierer, der auf eine Maschine zugreifen möchte, nicht in eine Zwangsjacke. Mit Unvollkommenheit läßt sich einfacher leben als mit perfekten Einschränkungen. C besticht durch seinen modularen Aufbau und seine unbeschränkte Natur. Ein C-Programmierer strebt nach funktioneller Modularität, verbunden mit effektiver Minimalität.

2.2 Überblick über C

2.2.1 Die Entwicklungsumgebung Code::Blocks

Erstellung des Quellcodes, Kompilierung und Ausführung wird in der Regel im Rahmen einer IDE (Integrated Development Environment), auch Entwicklungsumgebung genannt, durchgeführt. Diese stellt Funktionen zur Quellcode-Eingabe (Editor), Fehlersuche (Debugger), schrittweisen Ausführung (Interpreter) und letztendlich auch zur Erstellung eines ausführbaren Programmes (Compiler) zur Verfügung. Die IDE ist deshalb auch auf das jeweilige Betriebssystem abgestimmt. Der Quellcode des C-Programmes ist dabei aber immer gleich und damit universell verwendbar. Der Quellcode muss dann eben nur mit dem entsprechenden Compiler übersetzt werden, um ein auf einem System lauffähiges Programm zu erhalten. Der Quellcode selbst ist dabei eine einfache Textdatei, die leicht auf andere Systeme übertragen werden kann. Da alle C-Standards aufwärtskompatibel sind, kann jeder C++-Compiler auch C-Code übersetzen. Genau dies ist auch in der Vorlesung der Fall, da das verwendete Code::Blocks eigentlich eine C++-Entwicklungsumgebung ist. Code::Blocks ist ein Open-Source-Project, das kostenlos von http://www.codeblocks.org heruntergeladen werden kann. Es beinhaltet den GNU GCC-Compiler, kann aber auch mit anderen Compilern betrieben werden.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 8 D. Hägele, Stand 30.09.2015

Erstellen eines Projektes in Code::Blocks

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 9 D. Hägele, Stand 30.09.2015

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 10 D. Hägele, Stand 30.09.2015

Bestandteile von Code::Blocks

Grundsätzlich kann man alle Befehle aus der Toolleiste auch über die Menüleiste abrufen. In der Toolleiste sind in der Regel häufig benutzte Befehle als Schaltfläche hinterlegt. Die wichtigsten sind:

Bestehendes Projekt / Datei öffnen (Open)

Speichern der aktuellen Datei (Save)

Kompilieren (Build)

Ausführen (Run)

Kompilieren und Ausführen (Build and run)

Im Editor werden unterschiedliche Farben und Schriften verwendet, um dem Programmierer bereits auf den ersten Blick eine Übersicht über das Programm zu geben: Präprozessor-Anweisungen (#include) werden grün dargestellt, Zeichenketten blau, Schlüsselwörter fett und Kommentare grau. Um ein C-Programm auszuführen, ist die Erstellung eines Projektes nicht zwingend notwendig. Es genügt die Quelldatei. Diese Datei wird dann kompiliert, das heißt aus dem Quellcode wird eine ausführbare EXE-Datei gemacht. Erst danach kann sie als Programm aufgerufen werden.

Menüleiste

Toolleisten

Projektfenster

Editor (Quellcode)

Meldungs-

fenster

Statuszeile

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 11 D. Hägele, Stand 30.09.2015

Treten bei der Kompilierung Fehler auf, werden diese in der Regel im Meldungsfenster angezeigt. Dabei wird eine Beschreibung des Fehlers und die Zeilennummer angegeben, bei der der Fehler aufgetreten ist. 2.2.2 Ein einfaches Beispiel

Beispiel 1 :

#include <stdio.h>

#include <stdlib.h>

double fkt(double x);

int main()

{

double a, d;

d = 5.56;

a = fkt(d);

printf("Die Variable a ist = %f\n" , a);

return 0;

}

double fkt(double x)

{

double rst;

rst = 3.14159 * x;

return rst ;

}

Hauptfunktion main()

Definitionsteil

Befehle

(Statements)

Programmende

Header-Dateien einbinden

Funktionsprototypen definieren

Unterprogramme (Subroutines)

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 12 D. Hägele, Stand 30.09.2015

Dieses Beispiel zeigt einige wichtige Charakteristika von C-Programmen : - Ein C-Programm besteht aus Funktionen, hier die Funktionen main() und fkt()

- Der Funktionsname main muss in jedem C-Programm genau einmal vorkommen; bei ihm beginnt und endet die Ausführung des Programms ( die Funktion main wird vom Betriebssystem gerufen )

- Hinter jedem Funktionsnamen folgen die Klammern ( ). An ihnen erkennt der Compiler, dass es sich um Namen von Funktionen handelt

- Die öffnende Klammer { kennzeichnet den Beginn des Funktionskörpers. Er enthält die Definition der Funktion und wird mit } beendet.

- Im Funktionsrumpf stehen erst die Definitionen der Variablen ( hier a und d ), die in der Funktion aufgerufen werden.

- Die Deklaration einer Funktion (Funktionsprototyp) sagt aus, welchen Datentyp die Funktion an die Aufrufstelle zurückgibt ( hier ein double ) und welche Datentypen sie als Übergabeparameter erwartet. Eine Funktion kann erst aufgerufen werden, nachdem sie deklariert wurde.

- Auf den Definitions-DeklarationsteiI folgen die Statements der Funktion: d = 5.56 ; und a = fkt(d) ; jedes Statement wird durch ein Semikolon abgeschlossen. Das zweite Statement weist der Variablen a den Wert zu, den die Funktion fkt gebildet hat.

- Die Funktion fkt errechnet den Rückgabewert mit Hilfe des aktuellen Wertes des Parameters d. Dieser Wert wird der Funktion beim Aufruf mitgegeben. In der Definition der Funktion muss für ihn in der ParameterkIammer () ein formaler Parameter, ein "Platzhalter", benannt werden. Der Datentyp dieses formalen Parameters muss bei allen UNIX-Compilern nach der schließenden Parameterklammer, vor der öffnenden Blockklammer { spezifiziert werden. Andere Compiler (MSDOS, ANSI) erlauben die Typangabe in der ParameterkIammer, unmittelbar hinter dem Parameternamen.

- printf ist eine Ausgabefunktion, die die Standardbibliothek, für deren Einbindung alle C-Compiler sorgen, dem Benutzer zur Verfügung stellt. Sie gibt Daten auf das Terminal aus.

- printf bekommt als Argument einen "Formatstring" , d.h. eine Zeichenkette in " " , die neben Text auch noch Formatsymbole enthalten kann. Sie sind durch vorangestelltes %-Zeichen gekennzeichnet. Nach dem Formatstring folgen dann soviel Ausdrücke wie Formatsymbole angegeben wurden: Im obigen Beispiel besagt das Formatsymbol %f, dass der Wert des Ausdrucks als Gleitkommawert in dezimaler Form ausgegeben werden soll.

- Der Formatstring wird in aller Regel mit \n abgeschlossen. Es wird dadurch nicht nur der Cursor auf die folgende Zeile positioniert, sondern auch dafür gesorgt, dass der Ausgabepuffer sofort "geflusht" (ausgegeben) wird (UNIX).

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 13 D. Hägele, Stand 30.09.2015

2.2.3 Das Funktionskonzept

In C gab es ursprünglich keine "Prozeduren", d.h. Funktionen, die keinen Wert an die Aufrufstelle zurückreichen. Inzwischen wurde der Datentyp void geschaffen, der genau das realisiert:

void ffx()

wäre die Deklaration einer Funktion, die keinen Wert zurückgibt. Trotzdem wurde der Terminus "Procedure" in C nicht eingeführt. Man spricht statt dessen von void-Funktionen. Wichtiger als diese rein terminologische Festsetzung ist das Prinzip: Alle Funktionen in C sind extern. Das bedeutet, dass keine Definition einer Funktion innerhalb des Rumpfes einer anderen Funktion stehen darf. Jedes C-Programm besteht aus einer Menge solcher Funktionen. Wie oben schon erwähnt, muss genau eine der Funktionen den Namen

main()

haben. Der Linker setzt bei der Funktion dieses Namens den Startpunkt der Ausführung. Als Funktionsnamen sind beliebige Zeichenketten aus Buchstaben, Ziffern und dem Underscore "_" zugelassen, sofern sie mit einem Buchstaben oder dem Underscore beginnen. Manche Compiler beachten nur die ersten 7 oder 8 Zeichen des Namens, die restlichen werden zur Unterscheidung nicht herangezogen oder dürfen nicht vorhanden sein.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 14 D. Hägele, Stand 30.09.2015

2.2.4 Datenobjekte und ihre Speicherklassen

Beispiel 2:

#include <stdio.h>

#include <stdlib.h>

void ffk(void);

int main()

{

ffk(); ffk(); ffk();

return 0;

}

void ffk()

{

int a = 0;

static int b = 0;

printf("a = %d\n" , a);

printf("b = %d\n" , b);

a = a + 1;

b = b + 1;

}

Wird dieses Programm compiliert und ausgeführt, so erscheint:

C:\main.exe a = 0

b = 0

a = 0

b = 1

a = 0

b = 2

Press any key to continue

Beide VariabIen a und b sind "lokal" zur Funktion ffk, d.h. nur in ihr bekannt. Jedoch ist b mit dem Schlüsselwort static definiert worden, a nicht. Das hat zur Folge, dass die Lebensdauer der Variablen b gleich ist der Lebensdauer des Programms. Im Gegensatz dazu ist die Variable a nach Verlassen der Funktion ffk nicht mehr bekannt.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 15 D. Hägele, Stand 30.09.2015

3. C-Syntax

Unter Syntax versteht man die Form, genauer die exakte Notation, in der ein Programm aufgeschrieben werden muss. Bei der Formulierung eines C-Programmes ist auf Einhaltung dieser Sprachdefinition unbedingt zu achten. Für den Compiler (Übersetzer) ist ein Programm eine Folge von Zeichen, die nach einem bestimmten Regelwerk aufgebaut sein müssen. Fehler in der Syntax werden vom Compiler erkannt und als Fehlermeldung ausgegeben. In der Regel kann er dann kein Programm erzeugen. Zu den zulässigen Zeichen gehören:

Kleinbuchstabe: abc...... z Großbuchstabe: ABC...... Z Ziffern: 0 1 2...... 9 Sonderzeichen: +=-(){ }[ ]*/%&1!<>...'"$#\?~ Nichtdruckbare Zeichen: Leerzeichen, Tabulator, Neue-ZeiIe, etc.

Diese Zeichen werden vom Compiler zu syntaktischen Einheiten (Token) zusammengefasst. Token sind Worte, die den Sprachumfang einer Programmiersprache auszeichnen. Dazu gehören SchIüsseIworte, Operatoren, Konstanten, Separatoren und Bezeichner.

3.1 Bezeichner

Bezeichner sind benutzerdefinierte Wörter, die aus Klein-, Großbuchstaben, Ziffern und dem Sonderzeichen "_“ (Underscore) bestehen können, wobei das erste Zeichen ein Buchstabe oder ein Underscore sein muss. Wichtig in diesem Zusammenhang ist, dass C-Compiler "case-sensitive" sind d. h. es wird zwischen Groß- und Kleinschreibung unterschieden.

gültige Bezeichner: umu_foo Nummer_5 n_Zaehler

ungültiger Bezeichner: 1001_Nacht B_oder_C?! 1.Zaehler

Mit einem Bezeichner wird Objekten wie z. B. Daten und Funktionen eindeutige Namen zugeordnet.

3.2 Operatoren und Separatoren

Operatoren sind spezielle Zeichen um z. B. arithmetische Operationen durchzuführen und sind in der Menge der Sonderzeichen enthalten. Operatoren wirken gleichzeitig als Separatoren d. h. als Trennzeichen zwischen Token. Weitere Trennzeichen (Separatoren) sind Leerzeichen, Komma und Strichpunkt. Auch Operatoren sind Token und die Interpretation ist abhängig vom Kontext.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 16 D. Hägele, Stand 30.09.2015

3.3 Kommentare

Für die Lesbarkeit und Wartbarkeit eines Programmes ist die Erklärung eines Programms äußerst wichtig. Für die Dokumentation im Programm kann zwischen die Begrenzer /* und */ ein Kommentar eingefügt werden. Kommentare dürfen nicht geschachtelt werden. Kommentare werden vom Compiler als Leerzeichen interpretiert und sind deshalb nicht Teil des ausführbaren Programms. Aus diesem Grund sollte ein Programm paralleI zur Erstellung ausreichend kommentiert werden, um die Klarheit und Korrektheit eines Programms widerzuspiegeln. Beispiele:

/* dies ist ein Kommentar */

/*

** noch

** ein

** langer

** Kommentar

*/

/**********************************

* gerahmter Kommentar *

**********************************/

3.4 Konstanten

3.4.1 Ganzzahl-Konstanten

Ganze Zahlen können in C in Dezimalform angegeben werden:

a = 33;

bx = 678;

oder in Oktalform:

/* führende 0 kennzeichnet eine Oktalzahl */

cy = 0456;

oder in hexadezimaler Form:

bvr = 0x4e; /* führende 0 mit folgendem x oder X */

/* bedeutet, dass eine Hexadezimalzahl folgt;*/

xy = 0xfff; /* die Hexaziffern a bis f können auch mit */

/* A bis F notiert werden. */

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 17 D. Hägele, Stand 30.09.2015

3.4.2 Zeichen-Konstanten

Die Codewerte des Zeichensatzes können statt mit ihren Zahlenwerten lesbarer in der Hochkomma-Notation geschrieben werden:

z1 = 'A' /* ASCII-Codewert für A wird auf z1 zugewiesen */

T2 = '6' /* ASCII-Codewert des Zeichens 6 wird */

/* auf T2 zugewiesen */

Insbesondere ist vereinbart:

nn = '\0' /* Angabe der binären 0 als Zeichen; \0 */

/* ist ein Zeichen */

nn = 0 /* ist dasselbe, aber nicht üblich, wenn */

/* nn als char definiert ist */

nl = '\n' /* Codewert für newline */

nt = '\t' /* Codewert für Tab */

nr = '\r' /* Codewert für CR */

nb = '\b' /* Codewert für backspace */

nf = '\f' /* Codewert für formfeed */

nv = '\v' /* Codewert für den vertikalen Tab */

na = '\'' /* Codewert für das Apostroph */

nd = '\"' /* Codewert für das Gänsefüßchen */

ns = '\\' /* Codewert für den Backslash */

no = '\xxx' /* xxx ist dreistellige Oktalzahl */

Beispiel:

no = '\123'

no = 0123

no = 83

no = 'S'

Jedesmal wird derselbe Wert auf no zugewiesen.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 18 D. Hägele, Stand 30.09.2015

Hinweis Speziell bei der Darstellung von Umlauten können diese Zeichenkonstanten hilfreich sein. Es gilt insbesondere: ä \204 Ä \216 ö \224 Ö \231 ü \201 Ü \232 ß \341 (s. auch ASCII- und ANSI-Zeichensatz im Grundlagen-Skript) 3.4.3 Gleitkomma-Konstanten

Gleitkommazahlen werden in Dezimalform oder in Exponentialform angegeben :

xx = 23.779

y44 = .99

a0 = 88.

a1 = 1.2e-3 /* e oder E bezeichnet die Basis 10 */

/* die folgende ganze Zahl ist Exponent */

/* zur Basis 10 */

aav = .1E+6

Die vor dem Dezimalpunkt stehende Ziffernkette heißt integer part, die hinter dem Dezimalpunkt stehende fractional part, die auf e oder E folgende Ziffernkette heißt exponential part der Gleitkommazahl. Beide Ziffernketten dürfen ein Vorzeichen haben, beide werden dezimal interpretiert. Der integer part der Zahl darf leer sein, der mit dem fractional part muss vorhanden sein. Anmerkung:

Das Wort "Gleitkommazahl“ ist unausrottbar, obwohl die betreffenden Zahlen in fast allen Programmiersprachen mit dem Dezimalpunkt und nicht mit dem Komma notiert werden.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 19 D. Hägele, Stand 30.09.2015

3.4.4 Zeichenketten-Konstanten

Eine Zeichenketten-Konstante (String) ist eine Folge von Zeichen, die in Anführungszeichen eingeschlossen sind. Innerhalb der Anführungszeichen darf eine beliebige Zeichenfolge stehen. Soll der String über eine Zeile hinausgehen, so muss das Fortsetzungszeichen '\' an der Stelle des ZeiIenumbruchs verwendet werden. Ist das Anführungszeichen " Bestandteil des Strings, so muss ihm das Fluchtsymbol '\' vorangestellt werden (siehe Zeichen-Konstanten). Wichtig ist die Unterscheidung von Zeichen-Konstanten und Strings. So ist z.B. 'A' eine Zeichen-Konstante, "A" jedoch ein String (Strings werden intern mit der binären NulI abgeschlossen). Beispiele:

"Dies ist ein String"

"mit einem \" Anführungszeichen !"

"a=b+c;" /* es wird nichts berechnet ! */

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 20 D. Hägele, Stand 30.09.2015

4. Datentypen

In C gibt es skalare und nicht-skalare Datentypen, manchmal auch fundamentale und zusammengesetzte Datentypen genannt. Die skalaren Datentypen bilden ihrerseits zwei Gruppen, die Ganzzahltypen und die Gleitkommatypen.

4.1 Ganzzahltypen

Name Größe Wertebereich (signed) Wertebereich (unsigned)

char 1 Byte (8 Bit) -128 ... +127 0 ... 255

short 2 Byte (16 Bit) -32768 ... +32767 0 ... 65535

int Wortlänge des Systems z. B. 2 Byte, 16 Bit

-32768 ... +32767 0 ... 65535

long 4Byte (32 Bit) -2147483648 ... +2147483647 0 ... 4294967295

Der int-Datentyp kann also auch 16 Bit umfassen, wenn nämlich mit einem 16-Bit-Prozessor oder einem 16-Bit-Compiler gearbeitet wird. Immer ist short kürzer als long, und int entweder gleich short oder gleich Iong. Achtung:

Die Kongruenz von Wortlänge und int-Typ kann durchbrochen sein, wenn C unter einem Betriebssystem läuft, das ihrerseits auf einem anderen aufsetzt, z.B. C unter UNIX, das auf BS 2000 oder OS 1100 betrieben wird. Der char-Typ ist nichts weiter als eine 8 Bit lange Ganzzahl. Auf den meisten, wenngleich nicht allen, Implementierungen wird auch das höchstwertige Bit beim char-Objekt als Vorzeichenbit interpretiert. Objekte vom char-Typ werden natürlich oft zum Speichern von Codewerten des Zeichensatzes benutzt, aber das ist kein in der Sache liegender Zusammenhang. Zu den Ganzzahltypen existiert der Zusatz unsigned, der die Vorzeicheninterpretation des höchstwertigen Bits unterdrückt. Für Implementierungen, bei denen der char-Typ keine Vorzeicheninterpretation hat, erlauben manche Compiler den Zusatz signed zum char-Typ, um Vorzeicheninterpretation bei 8-Bit Ganzzahlen erzwingen zu können (ANSI, MSDOS).

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 21 D. Hägele, Stand 30.09.2015

4.2 Gleitkommatypen

Name Größe Wertebereiche

float 4 Byte (32 Bit) -3,402823466•1038 ... -1,175494351•10-38 0 +1,175494351•10-38 ... +3,402823466•1038

double 8 Byte (64 Bit) -1,7976931348623157•10308 ... -2,2250738585072014•10-308 0 +2,2250738585072014•10-308 ... +1,7976931348623157•10308

Der Gebrauch des float-Datentyps wird nicht empfohlen, weil sämtliche Rechnungen intern sowieso mit double durchgeführt werden, insbesondere werden an Funktionen auch dann Objekte mit der Speicherbelegung des double übergeben, wenn aktuelle wie formale Parameter als float definiert wurden, dasselbe ist beim return der Fall. Deshalb sind float-Objekte nur dann sinnvoll, wenn große Mengen von Gleitkommawerten gespeichert werden müssen.

4.3 Vektoren

Ein Vektor (Array) ist eine Aneinanderreihung einer bestimmten Anzahl von Objekten desselben Datentyps und damit der einfachste zusammengesetzte Datentyp. Jedes der im Vektor vorkommenden Datenobjekte heißt eine Komponente des Vektors. Die Anzahl der Komponenten eines Vektors ist seine Länge, sie wird bei der Definition des Vektors in eckigen Klammern angegeben. Die Komponenten werden mit den Zahlen 0 bis (Länge -1) angesprochen. Beispiele:

int x [4]; /* ist ein Vektor aus 4 int-Objekten */

double rr[12]; /* ist ein Vektor aus 12 double Objekten */

x[0] = 88; /* dem 1. Objekt des Vektors x wird die */

/* Zahl 88 zugewiesen... */

rr[11] = 44.4; /* ... und hier dem letzten (!) die 44.4 */

Charactervektoren (Strings) Den Datentyp String gibt es in C nicht. Seine Rolle übernehmen die Vektoren vom Typ char. Üblicherweise wird im Falle dieser Vektoren ein Endekennzeichen in den Vektor hineingesetzt: hinter die letzte Komponente, die noch ein Zeichen speichert, wird die binäre 0 geschrieben. Der Vektor heißt dann nullterminiert. Alle Standardfunktionen von C, die char-Vektoren verarbeiten, setzen sie als nullterminiert voraus. Insbesondere auch die Funktion printf.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 22 D. Hägele, Stand 30.09.2015

Beispiel 3:

#include <stdio.h>

#include <stdlib.h>

int main()

{

char cc[5];

cc[0] = 'H';

cc[1] = 'a';

cc[2] = 'n';

cc[3] = 's';

cc[4] = '\0'; /* Die binäre 0 als Zeichen */

printf ("Der Vektor cc enthält die Zeichen %s.\n", cc);

return 0;

}

Formatsymbol für nullterminierte Strings ist das s.

4.4 Deklaration, Initialisierung

Variablen sind Datenobjekte, die manipuliert werden können und damit verschiedene Werte annehmen können (im Gegensatz zu Konstanten). Bevor ein Datenobjekt benutzt werden kann, muss es eingeführt werden (declare before use). Das Bekanntmachen (Deklaration) wird erreicht durch Angabe der Speicherklasse, des Datentyps und des Objektnamens (Bezeichner). Wird das Objekt auf einen Startwert gesetzt (Initialisierung zur Übersetzungszeit) so entspricht dies einer Definition. Die Angabe einer Speicherklasse ist optional, da für dieses Attribut automatisch ein Wert gesetzt wird, falls keine Angabe erfolgte (Default-Mechanismus). Deshalb wird dieses Thema erst zu einem späteren Zeitpunkt behandelt ("Geltungsbereich und Speicherklassen"). Ein Objekt darf innerhalb eines Blocks ("Blockstruktur") nur einmal deklariert sein (unique).

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 23 D. Hägele, Stand 30.09.2015

Allgemeine Form: Speicherklasse Datentyp Bezeichner = Initialwert

Beispiel: static int b = 0

Alle skalaren Objekte können initialisiert werden. Vektoren der Speicherklasse static und alle externen Vektoren dürfen mit Anfangswerten versehen werden:

int y[ ] = { 1 , 2 , 6 , 8 };

Damit ist ein Vektor der Länge 4 aus int-Objekten definiert. Der Compiler bestimmt in diesem Falle die Länge des Vektors aus der Anzahl der Anfangswerte. Es darf auch beides, Länge und Anfangswerte, angegeben werden. Dann aber muss die Länge mindestens so groß sein wie die Zahl der Anfangswerte.

int y[4] = { 1 , 44 , 66 , 119 };

double rf[10] = { 1.7 , .999 };

Im zweiten Beispiel wird ein Vektor aus double-Objekten mit der Länge 10 angelegt, die ersten beiden Komponenten werden mit Anfangswerten versehen, die anderen nicht.

4.5 Speicherbedarf

Mit der Deklaration einer Variablen wird nicht nur ein neuer Bezeichner eingeführt, sondern auch gleichzeitig Speicherplatz reserviert (Allokation). Die Anzahl der Speicherbytes ist abhängig vom Datentyp, der damit auch den Wertebereich einer Variablen festlegt. Der Speicherbedarf ist zum Teil auch abhängig von der Wortlänge des Prozessors. Bei zusammengesetzten Datentypen und dynamischen Variablen kann zumeist nur der Compiler den jeweils benötigten Speicherbedarf einer Variablen ermitteln. Der Operator

sizeof ( Datentyp )

gibt den Speicherbedarf zurück, als wäre er eine Funktion (er ist aber keine und darf nicht deklariert werden).

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 24 D. Hägele, Stand 30.09.2015

4.6 Typkonversion

Unter einer Typkonversion versteht man die Umwandlung eines Datentyps in ein anderes Format. Dies kann bei der Abarbeitung von Ausdrücken automatisch geschehen (implizite Konvertierung) oder durch den Programmierer gesteuert werden (explizite Konvertierung). 4.6.1 Implizite Typkonversion

Für arithmetische Ausdrücke gilt:

1. Alle char- und short-Objekte werden intern auf int gewandelt

2. Trägt ein Ganzzahlobjekt den Zusatz unsigned, so werden intern alle Objekte als unsigned betrachtet

3. Ist der Ausdruck nach diesen Schritten gemischt, so wird gemäß der Hierarchie

in den nächsthöheren Datentyp konvertiert bis alle Objekte vomselben Datentyp sind.

Daraus ergeben sich folgende Konsequenzen :

Ist ein Objekt vom Typ long, so werden alle Ganzzahlobjekte auf long gewandelt.

Alle Ganzzahltypen werden auf Gleitkommatyp gewandelt, faIls ein Gleitkommaobjekt vorkommt. Das interne Resultat ist double.

Alle float-Objekte werden intern auf double gewandelt. Das interne Resultat ist double.

Außerdem gilt:

Die Wandlung von float auf double wird durchgeführt, sobald ein float-Objekt an eine Funktion als Argument übergeben wird, und zwar auch dann, wenn der Parameter als float definiert wurde. Analog für den Returnwert.

Die Wandlung von int in float oder double ist unproblematisch; es wird einfach das Speicherbild der float bzw. double-Zahl gebildet, das denselben Zahlenwert darstellt:

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 25 D. Hägele, Stand 30.09.2015

Beispiel 4:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int a = 4;

double d;

d = a;

printf("d = %f, i = %f\n", d, a + 1.0 );

/* 4.0 und 5.0 wird ausgegeben */

return 0;

}

Die Wandlung von GIeitkommatypen in GanzzahItypen ist nicht einheitlich, da in C nicht festgelegt. In der Praxis gilt, dass unter

UNIX die nächst kleinere ganze Zahl zugewiesen wird

während unter

MSDOS/WINDOWS die Kommastellen abgeschnitten werden.

In beiden Fällen wird also 12.67 auf 12 gewandelt, unter UNIX aber die -12.45 in die -13, unter MSDOS/WINDOWS in die -12. Beispiel 5:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int a;

double rx = 55.87;

a = rx;

printf("a = %d\n" , a); /* 55 wird ausgegeben */

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 26 D. Hägele, Stand 30.09.2015

4.6.2 Explizite Typkonversion

Außer der impliziten Typkonversion gibt es die Möglichkeit der expliziten Typkonversion mittels des cast-Operators. Der cast-Operator dient dazu, den Datentyp eines Objektes für den Moment zu ändern. Soll etwa aus zwei Ganzzahlobjekten der Quotient gebildet werden, aber nicht der ganzzahlige, sondern der "Bruch" als Dezimalzahl, so würde es nach dem eben Dargelegten reichen, eines der Objekte, etwa den Zähler, und das Resultat auf einen double zuzuweisen. Bessere Portabilität und Lesbarkeit wird mit dem cast-Operator erreicht. Allgemeine Form des cast-Operators:

( Datentyp ) Objekt_name

der Datentyp in den runden Klammern gibt das Format an, in den ein gegebenes Objekt umgewandelt werden soll. Beispiel 6:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int z, n, q;

double res, x;

z = 12;

n = 5;

q = z/n;

printf("k = %d\n", q); /* k = 2 */

x = z;

res = x / n;

printf("res = %f\n", res); /* res = 2.4 */

/* Mit cast-Operator */

res = (double) z / n;

printf("res = %f\n", res ); /* res = 2.4 */

/* dagegen */

res = (double) (z / n);

printf("res = %f\n", res ); /* res = 2.0 */

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 27 D. Hägele, Stand 30.09.2015

5. Operatoren

Die Sprache C verfügt über ein sehr mächtiges Operatorset. Durch Verknüpfung von Variablen und Konstanten mit Operatoren kann der Wert einer Variablen geändert werden. Es existieren Operatoren für:

Zuweisung

Arithmetik

Vergleich

Logik

Typkonvertierung

Speicherbedarf

Bit-Manipulation

Adress-Manipulation

5.1 Die Zuweisung

Die Zuweisung ist für alle Datentypen definiert, außer für Vektoren (Arrays).

a = b;

a = b = c = d = jj;

Die Zuweisung bedeutet, dass der rechts stehende Wert (des Objektes) auf das links stehende Objekt zugewiesen wird. Verkettung ist möglich: die zweite Zeile hat die Bedeutung:

d = jj;

c = d;

b = c;

a = b;

Die verkettete Zuweisung wird also von rechts nach links abgearbeitet.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 28 D. Hägele, Stand 30.09.2015

5.2 Arithmetische Operatoren

Arithmetische Operationen werden in C mit den üblichen Zeichen angegeben:

a = a + b; /* Addition */

a = a - b; /* Subtraktion */

a = a * b; /* Multiplikation */

a = a / b; /* Division */

und außerdem gibt es die Modulo-Operation:

a = a % b; /* nur für Ganzzahlobjekte sinnvoll */

Berechnet wird der Rest der Division von a durch b, das Resultat wird auf a zugewiesen (ganzzahliger Rest). Diese Operatoren sind binär, da sie zwei Operanden haben. In Abhängigkeit vom Kontext kann der Minus-Operator auch unär sein. So z.B. bei

a * -b /* Negation */

Kurzformschreibweise In C lassen sich diese Rechenoperationen in kürzerer Form notieren, wobei im allgemeinen auch kürzerer Objektcode generiert wird:

a += b; ist dasselbe wie a = a + b;

a -= b; ist dasselbe wie a = a - b;

a *= b; ist dasselbe wie a = a * b;

a /= b; ist dasselbe wie a = a / b;

a %= b; ist dasselbe wie a = a % b;

Rechts vom Zuweisungszeichen sollten, wenn die Kurzformschreibweise angewendet wird, immer Leerzeichen (mindestens eins) stehen. Das hat folgenden Grund: Früher war auch die Schreibweise

a =+ b; a =- b; usw.

in C üblich. Um die alten Programme nicht zu entwerten, erlaubt der Compiler diese Form

noch. Werden hier die Leerzeichen rechts weggelassen, so ist z.B. a=-b nicht mehr

eindeutig. Deshalb gibt der Compiler grundsätzlich eine Warnung aus, wenn er Kurzformoperationen ohne Leerzeichen auf der rechten Seite findet. Diese Situation tritt noch bei anderen Rechenzeichen auf, die später besprochen werden.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 29 D. Hägele, Stand 30.09.2015

5.3 Inkrement- und Dekrementoperatoren

Als weitere Möglichkeit der Ausdrucksverkürzung sind in C die Inkrement und Dekrementoperatoren (unär) eingeführt worden, die sowohl als Präfix (Ausführung vorher) als auch als Suffix (Ausführung nachher) auftreten können:

a = a + 1;

läßt sich kürzer schreiben :

a++; oder ++a;

Entsprechend: Für

a = a - 1;

kann stehen:

a--; oder --a;

Auch hier wird in der Regel nochmals kürzerer Code generiert. Ob die Inkrement- bzw. Dekrementzeichen vor das Objekt oder dahinter geschrieben werden, ist solange egal wie der Inkrement- oder Dekrementausdruck alleine steht. In einer Zuweisung ist es nicht gleichgültig, denn es gilt:

Steht der Inkrement/Dekrementoperator hinter dem Objekt, so wird der bisherige Wert des Objektes als Wert des ganzen Ausdrucks genommen;

Steht der Inkrement/Dekrementoperator dagegen vor dem Objekt, so ist der veränderte Wert des Objektes der Wert des ganzen Ausdrucks.

Also bei

y = a++;

wird erst der alte Wert von a auf y zugewiesen, dann wird a inkrementiert; bei

y = ++a;

wird erst a inkrementiert, dann der neue Wert von a nach y zugewiesen. Bei der Dekrementierung natürlich genauso.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 30 D. Hägele, Stand 30.09.2015

Beispiel 7:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int a, y, x;

a = 3;

y = ++a;

x = a--;

printf("y = %d\tx = %d\n", y , x);

return 0;

}

Achtung: Schreibweisen wie x[a++] = y[a++]

also Inkrement/Dekrementoperatoren auf beiden Seiten einer Zuweisung sind zu vermeiden, weil das Resultat undefiniert ist. Unbedenklich ist dagegen: x[i] = y[k++]

Es wird die k-te Komponente von y auf die i-te von x zugewiesen, und dann wird k inkrementiert. Auch x[i++] = y[j++]

ist einwandfrei.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 31 D. Hägele, Stand 30.09.2015

5.4 Vergleichsoperatoren

Bei der Selektion oder der lteration wird der weitere ProgrammabIauf durch Bedingungen gesteuert, d. h. es werden Operanden auf Gleichheit, Ungleichheit usw. verglichen. Für Bedingungen stehen folgende Operatoren (binär) zur Verfügung: == gleich

!= nicht gleich

< kleiner als

> größer als

<= kleiner oder gleich

>= größer oder gleich

Achtung: Schreibweisen wie '< =', '=<' und '> =‘, ‘= >' sind nicht erlaubt. Jeder Ausdruck der Form Operand Bedingung Operand erhält den Wert 1 (Nicht-Nullbewertung) genau dann, wenn (g.d.w.) die Bedingung zutrifft, sonst erhält er den Wert 0 (Nullbewertung).In C gibt es keinen Datentyp zur Darstellung von BOOLEschen Wahrheitswerten, deshalb wird jeder Wert, der ungleich 0 ist, als logisch wahr Interpretiert. Ein Ausdruck der obigen Form heißt Relationalausdruck. Beispiele: 7 /* Nichtnullbewertung, Konstante */

x = 7 /* Nichtnullbewertung, Zuweisung! */

x == 7 /* Nichtnullbewertung g.d.w. x gleich 7 ist,

Vergleich! */

(x = f()) != -1 /* Nichtnullbewertung g.d.w. die Funktion

einen Wert returniert, der ungleich -1 ist */

x = f() != -1

Nichtnullbewertung g.d.w. f() einen Wert returniert, der ungleich -1 ist. Im Unterschied zu vorigen Beispiel wird aber die Bewertung des Relationalausdrucks auf x zugewiesen, nicht der Returnwert von f(). denn Zuweisungen werden von rechts nach links abgearbeitet. x = yy >= ab

Ist yy größer oder gleich ab, so wird die 1 nach x zugewiesen und der ganze Ausdruck erhält die Nichtnullbewertung.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 32 D. Hägele, Stand 30.09.2015

5.5 Logische Operatoren

Die logischen Operatoren liefern als Ergebnis ebenfalls die Wahrheitswerte "wahr" und "falsch" ab. Zur Bestimmung des Wahrheitsgehalts einer Aussage stehen folgende Operatoren zur Verfügung: ! Negation, logisches NICHT /* unär */ && Konjunktion, logisches UND /* binär */ || Disjunktion, logisches ODER /* binär */ Für die Operanden a und b läßt sich folgende Wahrheitstabelle aufstellen :

a b !a a&&b a||b

w w f w w

w f f f w

f w w f w

f f w f f

Werden Ausdrücke mit logischen Operatoren verknüpft, so wird dieser verknüpfte Ausdruck solange abgearbeitet, bis der Wahrheitswert eindeutig feststeht !

5.6 Mathematische Funktionen

In C gibt es keine speziellen Operatoren für mathematische Funktionen. Diese werden durch Bibliotheksfunktionen wie exp() log() pow() sqrt() sin() cos() und noch eine Vielzahl anderer zur Verfügung gestellt (s. Kapitel 14 – Funktionsfamilien - Mathematische Funktionen).

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 33 D. Hägele, Stand 30.09.2015

5.7 Bit-Operatoren

Diese Operatoren sind nur für Ganzzahlobjekte definiert. Es gibt das bitweise BOOLEsche "and" &

bitweise BOOLEsche "or" | bitweise BOOLEsche "xor" ^ bitweise BOOLEsche "non" ~

Funktionsweise: Bei &,| und ^ werden zwei Ganzzahlobjekte Bit für Bit verknüpft, so wie die Regeln der BOOLEschen Algebra es verlangen, und das Resultat wird auf das entsprechende Bit des Objektes geschrieben, auf das zugewiesen wird:

X 1110 0000 1100 0111 0000 1100 0111 0010

Y 0001 1111 0001 1000 1010 1000 0011 1100

z=x&y 0000 0000 0000 0000 0000 1000 0011 0000

z = x | y 1111 1111 1101 1111 1010 1100 0111 1110

z = x ^ y 1111 1111 1101 1111 1010 0100 0100 1110

z = ~x 0001 1111 0011 1000 1111 0011 1000 1101

Das ~ (non) verkehrt jedes Bit des Operanden in das Gegenteil. Kurzformschreibweise ist erlaubt: z &= y steht für z = z & y z |= y steht für z = z | y z ^= y steht für z = z ^ y Beispiele: Bitmaske 0377 (oktal), 0xFF (hexadezimal), 255 (dezimal)

15 0

0000 0000 1111 1111 x= x & 0xFF reduziert x auf seine untersten 8 Bits.

Bitmaske 020 (oktal), 0x10 (hexadezimal), 16 (dezimal)

15 0

0000 0000 0001 0000 x= x & 0x10 weist das 5. Bit aus.

Damit können zum Beispiel Schalterzustände abgefragt oder maskiert werden.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 34 D. Hägele, Stand 30.09.2015

5.8 Verschiebeoperatoren

Diese Operatoren sind nur für Ganzzahlobjekte (char, int, short, long) definiert. 5.8.1 Die Linksverschiebung << (Left-Shift)

x = y << 5 verschiebt das Bitmuster von y um 5 Stellen nach links. Rechts werden Nullen nachgezogen.

5.8.2 Die Rechtsverschiebung >> (Right-Shift)

x = y >> 1 verschiebt das Bitmuster von y um eine Stelle nach rechts. Dabei wird auf fast allen Maschinen links das Vorzeichenbit nachgezogen (arithmetical shift); auf einigen wenigen wird die 0 nachgezogen (logical shift).

Ist der Operand des Rechts-Shifts jedoch ein unsigned Objekt, so wird links immer die Null nachgezogen.

unsigned vu = -1;

int x;

x = vu >> 1; /* x ist positiver Wert */

Diese Operatoren wurden direkt aus Assembler / Maschinensprache übernommen. Da es dafür einen Maschinenbefehl gibt, werden diese Operationen sehr schnell ausgeführt. Wie bereits erwähnt, können mit diesen Operatoren auch sehr einfach Verdopplungen (Left-Shift) oder Halbierungen (Right-Shift) durchgeführt werden.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 35 D. Hägele, Stand 30.09.2015

5.9 Operator-Prioritäten

Die Prioritäten der verschiedenen Operatoren von C gehen aus der folgenden Tabelle hervor. Je weiter oben ein Operator in der Tabelle steht umso höher ist seine Priorität. Es wird empfohlen, Ausdrücke möglichst zu klammern.

Assoziativität Operatoren

links nach rechts () [] -> .

unär rechts nach links ~ ! ++ -- sizeof (type)

unär - *(refer) &(address)

binär links nach rechts *(mult)/(div)%(modulo)

binär links nach rechts + (plus) – (minus)

binär links nach rechts << >> (shift)

binär links nach rechts < <= > >= (predicate)

binär links nach rechts == != (predicate)

binär links nach rechts & (bitwise)

binär links nach rechts ^(bitwise)

binär links nach rechts | (bitwise)

binär links nach rechts && (logical)

binär links nach rechts || (logical)

binär rechts nach links ?:

binär rechts nach links = += -= *= /= %= etc.

binär links nach rechts , (Komma-Operator)

Als unär gilt ein Operator, wenn er sich nur auf ein Argument bezieht (z. B. i++). Binär ist

er bei 2 Argumenten (z. B. a + b). Dabei kann das selbe Zeichen sowohl als unärer als

auch als binärer Operator verwendet werden. Hier bestimmt der Kontext die Art der Verwendung. Beispiele:

c = getchar( ) != -1; ist dasselbe wie c = (getchar() != -1);

a = b==c; ist dasselbe wie a = (b == c);

a<b == c<d; ist dasselbe wie (a < b) == (c < d);

x = y«4+z; ist dasselbe wie x = y«(4 + z);

c = a == b && x - y; ist dasselbe wie c = (a == b)&&(x - y);

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 36 D. Hägele, Stand 30.09.2015

6. Ein- / Ausgabe (I/O)

Mit den Funktionen für formatierte Ein- / Ausgabe ist es möglich, die Daten in einer besser lesbaren Form aufzubereiten. Außerdem wird durch die Angabe eines Formatsymbols für den Datentyp sichergestellt, dass die Daten korrekt interpretiert werden.

6.1 Formatierte Ein- / Ausgabe

6.1.1 Formatierte Ausgabe: printf

Die formatierte Ausgabe ist durch die Funktion printf() möglich. Die Funktion ist schon wiederholt aufgetreten, so dass die prinzipielle Arbeitsweise bereits bekannt ist. Nachzutragen ist erstens, dass printf als Returnwert die Anzahl der ausgegebenen Objekte zurückgibt (bei Fehler die -1), zweitens sind einige Formatsymbole nachzutragen und drittens wird die Feldbreitenspezifikation erläutert. Formatsymbole: Sie werden im Formatstring durch das vorangestellte % gekennzeichnet: soll % selbst ausgegeben werden, muss %% angegeben werden. Es steht das d oder i für dezimale Ganzzahl o oktale Ganzzahl ohne Vorzeichen x oder X hexadezimale Ganzzahl ohne Vorzeichen u dezimale Ganzzahl ohne Vorzeichen c Ausgabe als Zeichen s Ausgabe als (Nulltermin.) String f Gleitkommazahl ( dezimal ) e oder E Gleitkommazahl ( Exponent ) g oder G Gleitkommazahl ( dezimal oder Exponent ) Das Zeichen l bei einem der Symbole d, i, o, x, X oder u besagt, dass das zugehörige Argument ein long-Typ ist. Feldbreiten : Hinter dem %-Zeichen kann durch eine ganze Zahl eine minimale Feldbreite für die Ausgabe spezifiziert werden. In

printf("%4d\t%6d\n" , a , b)

werden für den Wert der Variablen a 4, für den der Variablen b 6 Zeichenbreiten reserviert. Benötigen die Zahlen mehr Zeichen, um ausgegeben zu werden, so wird die Feldbreitenangabe ignoriert.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 37 D. Hägele, Stand 30.09.2015

Steht vor der Feldbreite ein Minuszeichen, so erfolgt die Ausgabe in Linksausrichtung, sonst in Rechtsausrichtung. Die Auffüllung erfolgt normalerweise mit Leerzeichen, beginnt die Feldbreitenangabe mit einer 0, so wird mit Nullen aufgefüllt. Ein Pluszeichen vor der Feldbreite (oder vor dem Formatsymbol bei fehlender Feldbreite) bewirkt die Ausgabe eines Vorzeichens ( + oder - ) bei allen Ausgaben von Argumenten mit Vorzeichen (d, i, f, e, E, g, G). Für die Ausgaben mit o und x (oder X) kann das Flag # angegeben werden. Es werden dann alle o-Ausgaben mit einer führenden 0, alle x bzw., X-Ausgaben mit führendem 0x versehen. Präzisionsangaben: Mit einer ZahI, die auf einen Punkt folgt, kann eine Genauigkeit der Ausgabe gefordert werden. Die Präzisionsangabe muss auf die Feldbreite folgen, falls eine Feldbreite spezifiziert wurde. Die Präzision ist die Zahl der Ziffern hinter dem Dezimalpunkt für Gleitkommawerte, die minimale Zahl der Ziffern, mit der Ganzzahlen erscheinen sollen. Bei der Stringausgabe wird damit die maximale Zeichenzahl angegeben, mit der die Zeichenkette gedruckt wird. Beispiele:

printf("%-3d\n" , a)

Ausgabe der Ganzzahl in Dezimalform, linksbündig, rechts werden Leerzeichen aufgefüllt.

printf("%-+3d\n" , a)

Wie eben, aber die Ausgabe wird immer mit dem Vorzeichen versehen (im vorigen Beispiel nur bei negativen Werten). printf("%+03.5d\n" , a)

Ausgabe der Ganzzahl mit Vorzeichen in dezimaler Form mit minimal 3, maximal 5 Ziffern, rechtsbündig gestellt und links mit Nullen aufgefüllt.

printf(":%-10.20s:\n" , "hallo, old chap")

Der String wird so ausgegeben:

:hallo, old chap:

Wird 10.20 ersetzt durch 20, so rutscht der rechte Doppelpunkt 4 Stellen nach rechts. In der Regel werden Präzisionsangaben aber nicht bei Zeichenketten verwendet.

printf("%010.8f\n" , x)

Ausgabe der Gleitkommazahl x mit minimal 10 Zeichen, 8 nach dem Dezimalpunkt; rechts wird mit Nullen aufgefüllt.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 38 D. Hägele, Stand 30.09.2015

printf-Formattabelle Das für die printf-Funktionen bestimmte Format lautet: %[Angaben][Breite][.Präzision][Typpräfix][Formattyp] Angaben - linksbündig + Präfix mit Vorzeichen � Präfix mit Leerzeichen # ergänzt o, x, X, e, E, f, g, G 0 Feld links mit 0 auffüllen (nicht sinnvoll bei linksbündiger Ausrichtung) Breite Mindestfeldbreite (wird erhöht falls notwendig) Präzision Anzahl der Nachkommastellen Typpräfix (Typvorsatz) F Far-Zeiger N Near-Zeiger h short int l,L long int oder double Formattyp d, i Dezimalzahl des Typs signed u Dezimalzahl des Typs unsigned o Oktal-Ganzzahl des Typs unsigned x, X Hexadezimal-Ganzzahl des Typs unsigned f Gleitkommazahl mit Festkomma e, E Exponentialdarstellung g, G %e oder %f; das kürzere davon c einzelnes Zeichen s Zeichenfolge p... Adresse auf den ein Zeiger verweist Studiengang Technik 1 Elektronik und Informationstechnik

Programmierung 1, C, Glossar 2 D. Hägele, Stand 01.10.2004

C-Escapefolgen Da verschiedene Zeichen im ASCII-Code keine Entsprechung haben bzw. von der Programmiersprache als Trennzeichen verwendet werden, kann man mit den sogenannten Escapefolgen Sonderzeichen für die Ausgabe mit printf darstellen. Esc-Folge Beschreibung \a Signalton \b Rückschritt \f Seitenvorschub \n Neue Zeile \r Wagenrücklauf \t Horizontaltab. \v Vertikaltabulator \' Apostroph \" Anführungszeichen \\ Schrägstrich links \ddd ASCII-Zeichen in Oktal-Schreibweise \xdd ASCII-Zeichen in Hexadezimal-Schreibweise

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 39 D. Hägele, Stand 30.09.2015

6.1.2. Formatierte Eingabe. scanf

Die Funktion scanf liest Zeichenketten vom Terminal (Tastatur) und interpretiert sie nach der Vorgabe bestimmter Formatsymbole. Das Resultat der Interpretation schreibt sie auf eine angegebene Adresse. Nach ihrem Aufruf wartet der Prozess bis eine Eingabe vom Terminal erfolgt (und sei es EOF, sprich CTRL-d auf die leere Zeile). Per Eingabeumlenkung für den Prozess liest scanf von der betreffenden Datei. Der Returnwert von scanf gibt die Anzahl der gelesenen Objekte an. Bei Fehler wird –1 zurückgegeben. In C, anders als in manchen anderen Programmiersprachen, wird einer Funktion nicht die Adresse einer Variablen bekannt gemacht, wenn ihr der Name der Variablen in der Parameterzeile beim Aufruf übergeben wird (call by reference) sondern nur der Wert (call by value). Dies bedeutet dass nur eine physische Kopie des Wertes angelegt wird und die Variable selbst nicht verändert werden. Die Adresse einer Variablen wird mit dem &-Zeichen angesprochen. Nur bei Vektoren (und Funktionsadresse ) ist es anders: Hier bezeichnet der Name ohne folgendes Klammersymbol die Adresse des Objektes (s. “10. Zeiger und Vektoren“). Der Aufruf von scanf hat die Form: scanf(formatstring, adresse1, adresse2, ...)

Es müssen so viele Adressen hinter dem Formatstring stehen wie Formatsymbole im Formatstring angegeben wurden. Die Adresse bilden den Adress- oder Argumentteil der scanf-Parameterliste. Beispiel :

int x;

scanf("%d" , &x);

Steht in der Eingabe :

123 so wird die Ganzzahl 123 auf die Adresse von x zugewiesen. Dasselbe wäre geschehen, wenn in der Eingabe

123abc gestanden hätte.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 40 D. Hägele, Stand 30.09.2015

Durch das Format d für dezimale Ganzzahl, wird dafür gesorgt, dass die Eingabekette nur soweit interpretiert wird, wie es für die Bildung einer Ganzzahl sinnvoll ist. Also auch mit

123,456 oder

123.456 in der Eingabe wäre die 123 auf x zugewiesen worden. Sind im Formatstring Textzeichen vorhanden, so werden diese an der entsprechenden SteIle in der Eingabe erwartet (besser ist es meistens, wenn man sie grundsätzlich weglässt) : int x;

double y;

scanf("%d%lf" , &x , &y); /* Format fuer double ist lf */

/* bei Format f wird eine */

/* float-Adresse erwartet. */

Eingabe:

12 .67 bringt natürlich die 12 nach x und die .67 nach y. Genauso würde akzeptiert werden:

12.67 Letzteres aber nicht mehr bei dem Aufruf

scanf("%d %lf" , &x , &y);

Jetzt muss nach einer Zeichenkette für eine Ganzzahl erst (mindestens) ein Space folgen. Bei

scanf("%dX%lf" , &x , &y);

müsste genau ein X hinter der Zeichenkette für die Ganzzahl erscheinen.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 41 D. Hägele, Stand 30.09.2015

Regel : scanf liest Felder, wobei ein Feld eine Zeichenkette aus Non-White-Spaces ist. Die Interpretation jedes Feldes indes endet bei einem Zeichen, das nicht zum geforderten Format paßt; sie endet ferner, sobald die Feldbreite erreicht ist, falls eine angegeben wurde.

Feldbreitenangaben wirken wie bei printf, Präzisionsangaben gibt es bei scanf nicht.

Die Formatangabe %1s wird benutzt, um das nächste Non-White-Space Zeichen von der Eingabe zu holen; denn %c liest einfach des nächste Zeichen, u.U. also ein Leerzeichen, ein Tab oder ähnliches.

Die Formatsymbole für scanf Sind zumeist dieselben mit derselben Bedeutung wie für die printf-Funktion; zu merken ist aber, dass das l vor dem f stehen muss, wenn auf ein double-Objekt zugewiesen werden solI. Vor den Formatsymbolen d, u, x, o und i muss l ebenfalls stehen, wenn das Ganzzahlobjekt, auf das zugewiesen werden soll, ein long int ist. Mit h wird die Zuweisung für ein short-Objekt gekennzeichnet.

Ferner: Das Format i für Ganzzahlzuweisungen bedeutet bei scanf, dass die Ziffernzeichen in der Eingabe als Oktalstellen interpretiert werden, wenn sie mit einer 0 beginnen, und dass sie als Stellen einer Hexadezimalzahl interpretiert werden, wenn sie mit 0x anfangen. Beginnt die Ziffernkette nicht mit einer 0, so werden sie aIs Dezimalziffern gedeutet.

Das Auslassungszeichen "*":

Bei einem Format kann gesagt werden, dass die Zuweisung eines Feldes nicht erfolgen soll. SinnvoIl ist das natürlich nur, wenn die Eingabedaten in einer Datei stehen, von der per Eingabeumlenkung gelesen wird. Beispiel :

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int a;

double x , y ;

char str[20];

scanf("%d%lf%*d%s" , &a , &x , str);

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 42 D. Hägele, Stand 30.09.2015

Folgende Daten werden jetzt eingegeben:

33 777.0 80 hans

Es geht jetzt die 33 auf a, die 777.0 auf das x , die 80 wird überlesen, hans wird auf str geschrieben. Einlesen variabler Strings Mit der eckigen Klammer [ ] kann eine Klasse von Zeichen definiert werden, die in der Eingabe erscheinen müssen, damit sie auf das Objekt zugewiesen werden. Die zugehörige Adresse im Argumentteil muss die Adresse eines char-Objektes sein, in der Regel steht dort der Name eines char-Vektors. Deshalb entfällt der Typbezeichner „s“. Beispiel :

char str[20] ;

scanf("%[ab0-9]" , str) ;

Steht in der Eingabe

Xenia

so wird diese Kette nicht auf die Komponenten von str geschrieben. Wenn aber

baba8

erscheint, so wird es zugewiesen. Ist das erste Zeichen in der Klammer ein ^, so wird jedes Zeichen akzeptiert, das nicht in der Klammer spezifiziert ist (Komplementbildung).

char str[10];

scanf("%[^ab0-9]" , str);

Die Eingabe Xenophon wird akzeptiert. Anmerkung :

Code::Blocks erkennt und verarbeitet die Eingabe eines Bereiches (z.B. "[0-9]" bezeichnet alle Ziffernzeichen von 0 bis 9), andere Compiler tun dies nicht unbedingt. In der Klammer müssen dann alle Zeichen angegeben werden, die verwendet werden sollen (z.B. "[0123456789]"). Bei allen Compilern werden jedoch zwischen Groß- und Kleinbuchstaben unterschieden.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 43 D. Hägele, Stand 30.09.2015

6.2 Unformatierte Ein- / Ausgabe

Bei der unformatierten Ein- und Ausgabe können keine Variablen mit Platzhaltern eingebaut werden. Deshalb gibt es verschiedene Funktionen, je nachdem welcher Datentyp ein- oder ausgegeben werden soll. 6.2.1 Unformatierte Ausgabe: put

Die Mitglieder der „put“-Familie geben jeweils einen bestimmten Datentyp aus.

putc schreibt Zeichen auf Datenstrom (Stream muss angegeben werden)

putch schreibt Zeichen auf Bildschirm

putchar schreibt Zeichen auf stdout

puts schreibt String auf stdout

Die Formatierungsmöglichkeiten vor allem für Zahlen fallen so natürlich weg. Escape-Folgen wie '\n’ und '\t' werden ebenso ignoriert. Deshalb beschränken sich die verfügbaren Befehle auf die Ausgabe von Zeichen bzw. Zeichenketten. In der Regel ist bei den heutigen Computersystemen stdout gleichbedeutend mit dem Bildschirm. Es ist jedoch denkbar, das Standard-Ausgabegerät in eine Datei oder einen Drucker umzuleiten. Normalerweise bewirken jedoch putch und putchar das gleiche. Die Hauptanwendung ist jedoch puts, das einfach nur benutzt wird, um Zeichenketten auf dem Bildschirm auszugeben. Nach jeder Ausgabe mit puts wird automatisch eine neue Zeile begonnen (Newline). Beispiel:

puts("Lasagne ist gut für die Linie.");

6.2.2 Unformatierte Eingabe: get

In der Regel werden Eingaben in C mit dieser Funktionsfamilie programmiert. Dies liegt vor allem daran, dass nicht auf ein 'Return' gewartet werden muss, um den Tastatur-Puffer zu leeren und den Inhalt weiter zu verarbeiten, wie bei scanf. Die Funktion getch wartet also bis eine Taste gedrückt wird und gibt diesen Wert zurück. Ein typisches Eingabe-Modul in C könnte in etwa so aussehen:

int i;

puts("Bitte Taste drücken.");

i = getch(); /* Tastatur überwachen */

printf("Taste %c wurde gedrückt.\n", i);

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 44 D. Hägele, Stand 30.09.2015

7. Kontrollstrukturen

Ein Programm wird normalerweise sequentiell abgearbeitet, d. h. alle Anweisungen werden nacheinander ausgeführt. Durch Kontrollstrukturen ist es möglich, den Programmablauf zu steuern. Hierfür sehen in C eine Reihe von Strukturbausteinen zur Verfügung.

7.1 Verzweigung: if, if-else

Die Verzweigungsanweisung hat in C zwei mögliche Formen :

if ( Ausdruck )

Anweisung

oder

if ( Ausdruck )

Anweisung

else

Anweisung

Die Anweisung kann eine Verbundanweisung ("Programmstruktur") sein, d.h. sie kann aus einer Folge von Anweisungen bestehen, die von Blockklammern { } umschlossen sind:

if ( Ausdruck )

{

Anweisung

...

Anweisung

}

oder

if ( Ausdruck )

{

Anweisung

...

Anweisung

}

else

{

Anweisung

...

Anweisung

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 45 D. Hägele, Stand 30.09.2015

Funktionsweise: Hat der Ausdruck in den runden Klammern die Nicht-Nullbewertung (WAHR), so wird die Anweisung ausgeführt, die auf die schließende runde Klammer folgt; hat der Ausdruck die Nullbewertung (FALSCH), so wird die auf die schließende runde Klammer folgende Anweisung nicht ausgeführt, sondern, falls vorhanden, die hinter else stehende Anweisung. Achtung: Folgen mehrere if aufeinander, so wird das erste else auf das letzte if bezogen, das noch kein else hat. Also:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

if (x < 0)

if (y<0)

printf("x und y sind beide negativ.\n");

else

printf("x ist negativ, y ist positiv.\n");

else

printf("x ist positiv oder Null.\n");

return 0;

}

Anmerkung:

Die Einrückungen beeinflussen den Compiler nicht, sie dienen nur der Lesbarkeit.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 46 D. Hägele, Stand 30.09.2015

7.2 Die bedingte Zuweisung

Beispiel 8 a:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int a = 3, b = 34 , max , min;

if ( a < b )

{

min = a;

max = b;

}

else

{

min = b;

max = a;

}

return 0;

}

Das lässt sich auch mit einer bedingten Zuweisung schreiben: Beispiel 8 b:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int a = 3 , b=34 , min , max ;

min = (a < b) ? a : b;

max = (a < b) ? b : a;

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 47 D. Hägele, Stand 30.09.2015

Die allgemeine Form ist:

name = Ausdruck ? Ausdruck1 : Ausdruck2;

Es wird der Ausdruck1 auf name zugewiesen, wenn Ausdruck die Nicht-Nullbewertung hat, sonst Ausdruck2. Der spezielle Sinn der bedingten Zuweisung ist der, dass sie ein Ausdruck ist und keine Anweisung. Sie eröffnet also die Möglichkeit, Bedingungen dort zu formulieren, wo nur Ausdrücke, aber keine Anweisungen stehen dürfen.

7.3 Die while-Schleife

Form:

while ( Ausdruck )

Anweisung

Die Anweisung kann eine Verbundanweisung (s. "9. Programmstruktur") sein, d. h. sie kann aus einer Folge von Anweisungen bestehen, die von Blockklammern { } umschlossen sind.

while ( Ausdruck )

{

Anweisung

...

Anweisung

}

Funktion: Die Anweisung wird solange ausgeführt wie Ausdruck in den runden Klammern die Nicht-Nullbewertung hat. Sobald er die Nullbewertung bekommt, wird das Programm hinter der Anweisung fortgeführt, . Die while-Schleife ist eine kopfgesteuerte Schleife, d. h. die Bedingung ("Ausdruck") wird zuerst überprüft und dann der Programmablauf entsprechend fortgesetzt. Die Anweisung muss deshalb nicht zwingend ausgeführt werden. Nichtterminierende Schleife:

while (1)

Anweisung; /* endlos */

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 48 D. Hägele, Stand 30.09.2015

Beispiel :

Die Funktion getchar() liest ein Zeichen aus dem TerminaIpuffer und gibt es als Returnwert ins Programm zurück. Das Ende der Eingabe wird erkannt, wenn CTRL-z (bei manchen Systemen auch CTRL-d) eingegeben wird. Die Funktion getchar() gibt dann die –1 zurück. Der Zahlenwert –1 wird sehr oft als Rückgabewert benutzt, wenn ein Fehler auftritt oder das Dateiende bei Schreib-/Lesevorgängen erreicht ist (EOF). Der Terminalpuffer wird für gewöhnlich erst dann gefüllt, wenn entweder CR oder CTRL-z betätigt wird. Ersteres bewirkt, dass ein newline-Zeichen hinzugefügt wird; letzteres stellt die reine EingabezeiIe in den Puffer, ggf. die leere, was getchar() als EOT (End-of-Text) interpretiert. Mit putchar(c) wird das Argument c, das den Codewert eines Zeichens enthalten sollte, auf das Terminal ausgegeben. Die Ausgabe könnte auch mit printf erfolgen. Für die zeichenweise Ausgabe ist dann das Format %c zu verwenden. Beispiel 9:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int c;

while ( ( c = getchar() ) != -1 )

{

putchar(c) ; /* oder: printf("%c" , c);*/

}

printf("\nFertig ! c ist %d\n" , c ); /* Ausgabe der -1 */

return 0;

}

Anmerkung:

Die Variable c ist als int definiert, Auf den meisten UNIX Implementierungen dürfte sie ruhig vom Typ char sein; aber unbedingt ist es eben nicht so, dass beim char das Vorzeichen "propagiert" wird, d.h., das höchstwertige bit als Vorzeichen gilt. Ist dies nicht der Fall, würde die auf 8 Bit gekürzte Zahl -1 nicht als negativ erkannt werden. Deshalb ist getchar() als Funktion vom Typ int definiert und im Sinne guter Portabilität ist es besser, ihren Rückgabewert auf eine Variable desselben Typs zu schreiben.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 49 D. Hägele, Stand 30.09.2015

7.4 Die for-Schleife

Das Steuerkonstrukt der for-Schleife ist nichts anderes als die while-Schleife, lediglich (für manche Fälle) etwas übersichtlicher geschrieben. In Programmstücken wie:

i = 1

while (i<100)

{

..... /* viele Anweisungen */

i++;

}

kann es lästig sein, die Stelle aufzusuchen, an der die Bedingungsvariable i verändert wird. In der for-Schleife stehen alle relevanten Ausdrücke beieinander.

for ( Ausdruck1; Ausdruck2; Ausdruck 3)

Anweisung;

Das obige Beispiel schreibt sich:

for ( i = 1; i < 100 ; i++)

{

.... /* viele Anweisungen */

}

Nichtterminierende Schleife:

for ( ; ; )

Anweisung; /* endlos */

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 50 D. Hägele, Stand 30.09.2015

7.5 Die do-while-Schleife

Form:

do

{

Anweisung(en)

} while ( Ausdruck );

Funktion:

Die Anweisung(en) im Körper der Schleife werden solange wiederholt, wie der Ausdruck in der Bedingungsklammer die Nicht-Nullbewertung hat. Im Unterschied zur while-Schleife wird aber erst nach der Ausführung der Anweisungen entschieden, ob sie nochmals ausgeführt werden. Das bedeutet, dass die erstmalige Ausführung der Anweisungen unbedingt erfolgt. Die do-while-Schleife ist somit eine fußgesteuerte Schleife.

7.6 Die switch-Anweisung

Form:

switch ( Ausdruck )

{ /* Definition von Variablen nicht möglich */

case Wert1: Anweisung1.1

.....

Anweisung1.n

case Wert2: Anweisung2.1

....

Anweisung2.m

........

default: Anweisungd.1

....

Anweisungd.x

}

Funktion: Der Ausdruck von switch wird ausgewertet. Sein Wert wird unter denen gesucht, die mit dem Schlüsselwort case aufgeführt sind. Wird er gefunden, setzt die Programmsteuerung bei der ersten dort stehenden Anweisung auf; alle folgenden Anweisungen, bis zum Ende des switch, werden ausgeführt.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 51 D. Hägele, Stand 30.09.2015

8. Kontrolltransferanweisungen

Die Funktionsweise der switch-Anweisung entspricht nicht der in den meisten Fällen gewünschten Selektion. Für die Steuerung des Programmablaufs wie z. B. das Verlassen des Kontrollblocks oder das erneute Durchlaufen der Schleife wurden deshalb Kontrolltransferanweisungen eingeführt.

8.1 Abbruchanweisung break

Die "unstrukturierte" Funktionsweise der switch-Anweisung ist sehr unpraktisch. Sie kann aber korrigiert werden, wenn die Anweisungsfolge eines case-Zweiges mit der Anweisung

break;

abgeschlossen wird. Mit break wird das Programm hinter der schließenden Klammer des switch weiter ausgeführt.

Unter jedem case dürfen nur Konstanten stehen, keine allgemeinen Ausdrücke, insbesondere keine Oder-Verknüpfungen von Werten. Jedoch ist es erlaubt, gar keine Anweisung unter ein case zu schreiben. Das ersetzt die Funktion einer Oder-Verknüpfung. Im folgenden Beispiel werden Spaces, Newlines, Tabs, alle Satzzeichen und alle sonstigen Zeichen in der Eingabe gezählt.

Beispiel 10:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int nl = 0, sp = 0, tab = 0, sonst = 0, satzz = 0, c;

while (( c = getchar() ) != -1 )

switch ( c )

{

case '.':

case ',': /* Oder-Verknüpfung */

case ';': /* der Satzzeichen */

case '!':

case '?': satzz++;

break ; /* Verlassen switch */

case '\n': nl++;

break ;

case ' ': sp++;

break;

case '\t': tab++;

break;

default: sonst++;

break;

}

printf("Tab = %d\tSpace = %d\tNewl = %d\n", tab, sp, nl);

printf("Satzzeichen =%d\tSonstige = %d\n", satzz, sonst);

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 52 D. Hägele, Stand 30.09.2015

Im Beispiel wäre das break im default-Zweig überflüssig. Aber weil default auch in der Mitte stehen kann, ist es so vorsichtiger, falls nachträglich noch weitere Zweige angefügt werden. Die Anwendungsmöglichkeit der break-Anweisung ist aber breiter. In einer while- oder for- oder do-while-Schleife bricht es die Ausführung der Schleife ab; die Programmsteuerung setzt hinter der kleinsten umfassenden Schleife auf. Beispiel 11 :

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

int main(int argc, char *argv[])

{

double x;

while ( 1 )

{

scanf("%lf", &x);

if ( x < 0.0 )

break;

printf("Wurzel von %f ist %f\n" , x , sqrt(x));

}

printf("Programm beendet.\n") ;

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 53 D. Hägele, Stand 30.09.2015

8.2 Fortsetzungsanweisung continue

Die Fortsetzungsanweisung continue funktioniert invers zum break: In jeder Schleife sorgt es dafür, dass sofort wieder zum Schleifenkopf der kleinsten umfassenden Schleife zurückgekehrt wird (nächste Iteration). Außer in Schleifen ist continue nicht definiert. Beispiel 12:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int c; /* andere Definitionen */

while ((c = getchar()) != -1 )

{

if ( c < '0' || c > '9' )

continue;

... /* Verarbeitung der Ziffernzeichen */

}

/* Weitere Verarbeitung */

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 54 D. Hägele, Stand 30.09.2015

8.3 Sprunganweisung goto

Die Sprunganweisung goto sollte nicht verwendet werden. Bei goto muss eine Marke, also ein Bezeichner, stehen. Dieselbe Marke muss im Programm genau einmal auftauchen, mit einem ":" dahinter. Marken sind lokal zur Funktion, d. h. Sprünge können nur innerhalb von Funktionen erfolgen, aber über Blockgrenzen hinweg. Soll in eine andere Funktion gesprungen werden, so ist eine markierte Anweisung (labeled statement) eine Möglichkeit. Beispiel 13 (goto):

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

double x;

while (1)

{

scanf("%lf", &x);

if(x < 0.0)

goto ende;

printf("Wurzel aus %f ist %f\n", x, sqrt(x));

}

ende:

printf("Programm beendet\n");

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 55 D. Hägele, Stand 30.09.2015

Beispiel 14 (labeled statement):

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

...

goto fehler;

...

fehler: fatal(msg);

...

return 0;

}

void fatal(char *msg)

{

...

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 56 D. Hägele, Stand 30.09.2015

8.4 Leeranweisung ";"

Eine Leeranweisung ";" ist notwendig, wenn formal eine Anweisung verlangt wird, jedoch syntaktisch keine Notwendigkeit besteht. Am häufigsten wird die Leeranweisung als Leerrumpf für Wiederholungsanweisungen verwendet.

8.5 Komma-Operator ","

Es kommt vor, dass bei der for-Schleife nicht nur ein Init oder Reinit gemacht werden soll, sondern mehrere. Aus diesem Grund wurde der Begriff des Ausdrucks so erweitert, dass mehrere Ausdrücke zu einem zusammengefaßt werden können, indem ein Komma zwischen sie gesetzt wird. Dies ist auch bei der Variablendeklaration eine Möglichkeit für eine kürzere Schreibweise für eine Deklaration von Variablen vom selben Datentyp. Beispiel 15: Berechnung der Summe aller ganzen Zahlen von 1 bis 100:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int i, sum, start = 0, ende = 100;

for (i = start, sum = 0; i <= ende; sum += i, i++)

;

printf ("\nDie Summe aller Zahlen von %d", start);

printf (" bis %d ist %d.\n", ende, sum);

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 57 D. Hägele, Stand 30.09.2015

9. Programmstruktur

9.1 Ausdruck, Anweisung

Die genaue Unterscheidung der Begriffe "Ausdruck" (expression) und "Anweisung" (statement) ist in C sehr wichtig: Ein Ausdruck kann eine Konstante, eine Variable oder ein Funktionsaufruf sein, außerdem ist eine arithmetische Verknüpfung zweier Ausdrücke wieder einer, eine BOOLEsche Verknüpfung führt genauso wieder zu einem Ausdruck und aus einem sog. Term wird ein Ausdruck, wenn Inkrement- oder Dekrementzeichen hinzukommen. Ein Ausdruck wird überall dort verwendet, wo in Abhängigkeit seines errechneten Rückgabewertes Anweisungen ausgeführt werden sollen (z.B. bei Kontrollstrukturen). Ein Ausdruck wird zu einer Anweisung (expression statement), indem der Ausdruck durch ein Semikolon abgeschlossen wird. Auch Anweisungen geben einen errechneten Wert zurück, jedoch wird dieser ignoriert. Daneben sind alle Kontrollstrukturen statements, und zwar von Hause aus, und nicht aus einer expression durch Hinzufügen des Semikolons entstanden. Diese Unterscheidung ist deshalb von Bedeutung, weil in C oft eine expression stehen muss, aber kein statement stehen darf, z.B. in der Bedingungsklammer der Schleifen-Konstrukte und der Verzweigungen, aber auch als Übergabeparameter in Funktionsaufrufen können beliebig komplizierte expressions auftauchen, aber kein statement.

9.2 Blöcke

Eine Folge von Anweisungen verbunden mit Deklarationen nennt man einen Block. Gekennzeichnet wird ein Block durch eine öffnende (Beginn) und eine schließende (Ende) geschweifte Klammer { }. Fehlen die Deklarationen, so wird ein solcher Block auch Verbundanweisung (compound statement) genannt. Blöcke und Verbundanweisungen sind syntaktisch äquivalent mit einer einzelnen Anweisung und können geschachtelt werden. Blöcke und Verbundanweisungen sind nach Kontrollstrukturen zu finden. Das Blockkonzept dient im wesentliche dazu, den Geltungsbereich und die Lebensdauer von Variablen zu regeln.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 58 D. Hägele, Stand 30.09.2015

9.3 Funktionen

Funktionen sind kleine überschaubare Lösungen, die speziell auf ein Problem zugeschnitten sind. Durch das Funktionenkonzept wird die Methode der Top-Down-Programmierung unterstützt, d. h. ein Problem wird soweit in Teilprobleme zerlegt und atomisiert, bis mehrfach verwendbare und einzeln austestbare Funktionen entstehen. Ein Programm besteht demnach aus einer oder mehreren Funktionen, die in einer oder mehreren Dateien abgelegt sein können. In einem Programm muss jedoch genau einmal die Funktion main vorkommen. Desweiteren kann eine Vielzahl von Bibliotheksfunktionen

benützt werden. Allgemeine Form der Definition einer C-Funktion: Speicherklasse Datentyp Funktionsname (Parameterliste)

Definition der formalen Parameter Funktionskopf

{

Definitionen der lokalen Variablen

Anweisungen

}

Funktionskörper

Diese Form der Funktionsdefinition ist stets erlaubt. Nach der ANSI-Norm sollte die Definition eines formalen Parameters innerhalb der Parameterklammern erfolgen:

ANSI C

C

double fkt(double xx, int a)

{

...

}

double fkt(xx, a)

double xx;

int a;

{

...

}

UNIX-Compiler unterstützen die ANSI-Schreibweise meist nicht. Als Datentypen für den Rückgabewert der Funktion kommen alle skalaren Datentypen in Frage. Vektoren können nicht Rückgabetyp sein. Die meisten C-Compiler erlauben Strukturobjekte (zusammengesetzte Datentypen) als Rückgabetypen wie auch als Parameterwerte.

Außerdem kann der Datentyp void als Rückgabetyp vereinbart werden. Er besagt, dass

die Funktion keinen Wert an ihre Aufrufstelle zurückreicht.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 59 D. Hägele, Stand 30.09.2015

Die Rückgabe selbst wird mit der Rücksprunganweisung return vollzogen; es hat die Form:

return expression; oder return (expression);

Die Programmsteuerung kehrt in die rufende Funktion zurück und setzt hinter der Aufrufstelle auf. Eine void-Funktion darf kein return-Statement mit einem expression als Argument enthalten. Ohne Argument bewirkt return lediglich die Terminierung der Funktion und die Rückkehr des Programms an die Aufrufstelle.

Jede Funktion muss für den Block, in dem sie gerufen wird, deklariert sein. Diese Deklaration, der sogenannte Funktions-Prototyp, hat die Form

Speicherklasse Datentyp Funktionsname(Datentyp Parametername, ...);

Parameternamen müssen in der Deklaration nicht angegeben werden, erleichtern jedoch die Übersichtlichkeit im Programm. Jeder Funktionsname, der nicht deklariert ist, wird als Name einer Funktion angenommen, die einen Wert vom Typ int zurückgibt (default).

Beispiel:

#include <stdio.h>

#include <stdlib.h>

int test(char para1, double para2);

int main(int argc, char *argv[])

{

...

i = test('A', 3.14159);

...

return 0;

}

int test(char para1, double para2)

{

...

return

}

In diesem Fall hat die Funktion test( ) eine Variable vom Datentyp int als Rückgabewert und 2 Übergabeparameter para1 und para2. para1 ist vom Typ char, para2 vom Typ double.

Funktionsprototyp (Deklaration der Funktion)

Funktionsbeschreibung (aufgerufene Funktion)

aufrufende Funktion

Funktionsaufruf

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 60 D. Hägele, Stand 30.09.2015

9.4 Speicherklassen

Die Speicherklassen legen die Lebensdauer und den Ansprechbereich (Scope) eines Datenobjektes fest.

Objekte können statisch (zur Übersetzungszeit) oder dynamisch (zur Ausführungszeit) angelegt sein.

Durch den Ort der Deklaration werden interne und externe Speicherklassen unterschieden. Ein Objekt ist immer dann von interner Speicherklasse, wenn seine Definition in einem Funktionsblock steht, oder wenn es Parameter einer Funktion ist.

Ein Objekt ist von externer Speicherklasse, wenn es kein Parameter ist und seine Definition außerhalb aller Funktionsblöcke steht.

Speicherklasse Lebensdauer Ansprechbereich (Scope)

(intern) auto Funktion Block

(intern) static Prozess Block

extern Prozess Sourcedatei, erweiterbar

extern static Prozess Sourcedatei

Erläuterung :

Die internen Speicherklassen sind in dem Block ansprechbar, in dem sie definiert sind, außerdem allerdings noch in allen "Subblöcken" des Definitionsblockes. Beispiel 16:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int a;

a = 34;

printf ("a ist %d\n", a);

{ /************/

printf ("a ist im Subblock %d\n", a); /* Verbund- */

} /* anweisung*/

/************/ return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 61 D. Hägele, Stand 30.09.2015

Es wird aber für jeden inneren Block ein neuer Stackframe (Stapelspeicher) angelegt, und deshalb ist es möglich, dort ein Objekt gleichen Namens zu definieren; es hat dann nur im inneren Block Gültigkeit. Wird keine Neudefinition eines Namens vorgenommen, so wird das Objekt aus dem äußeren Block in den inneren kopiert. Beispiel 17:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int a;

a = 34;

printf ("a ist %d\n", a);

{

int a;

a = 56;

printf ("a ist im Subblock %d\n", a);

}

return 0;

}

Dasselbe gilt auch für die internen static-Objekte. Diese unterscheiden sich hinsichtlich des Scopes nicht von den auto-Objekten. In der Einleitung war schon ein Beispiel betrachtet worden.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 62 D. Hägele, Stand 30.09.2015

Der Scope der externen Objekte ist zunächst auf die Datei beschränkt, in der die Definition steht, und selbst da ist das Objekt bei den meisten Compilern erst ab der Aufschreibungsstelle ansprechbar (declare before use). Beispiel 18:

#include <stdio.h>

#include <stdlib.h>

int ax; /* ax ist in der ganzen Datei ansprechbar */

void fkt(void);

int main(int argc, char *argv[])

{

ax = 88;

fkt();

return 0;

}

double by; /* by ist auf vielen Systemen in main nicht */

/* ansprechbar, sondern erst in fkt() */

void fkt()

{

by = 99.99;

printf ("ax ist %d by ist %f\n", ax, by);

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 63 D. Hägele, Stand 30.09.2015

Die externen Objekte können in anderen Sourcedateien durch eine Deklaration mit dem Schlüsselwort extern bekannt gemacht werden. Beispiel 19:

#include <stdio.h>

#include <stdlib.h>

#include "\_XX\Beisp19b.c"

int ax; /* ax ist in der ganzen Datei ansprechbar. */

void fkt1(void), fkt2(void), fkt3(void);

int main(int argc, char *argv[])

{

ax = 88;

fkt1(); fkt2(); fkt3();

printf ("MAIN: ax ist %d by ist %f\n", ax, by);

return 0;

}

double by; /* by ist ab der Deklarationsstelle bekannt */

void fkt1()

{

by = 99.99;

printf ("FKT1: ax ist %d by ist %f\n", ax, by);

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 64 D. Hägele, Stand 30.09.2015

/* SOURCE2.C */

extern int ax ; /* globale Deklaration */

void fkt2()

{

ax = ax + 6; /* das in source1.c definierte Objekt ax */

/* wird verändert. */

}

void fkt3()

{

int ax;

extern double by; /* by wird lokal deklariert. Es */

/* ist nur in diesem Block bekannt */

by = 3.8 * by;

ax = 789; /* intern hat Priorität vor extern */

printf ("FKT3: ax ist hier %d das by ist %f\n",ax,by);

}}

Die meisten UNIX-Compiler, aber nicht alle, akzeptieren es, wenn in zwei Sourcedateien externe Objekte mit demselben Namen auftauchen; eine extern-Deklaration dieser Namen ist selbstverständlich nicht möglich, denn die Referenz wäre ja nicht mehr eindeutig herstellbar. Bei Namensgleichheit externer und interner Objekte gilt:

Stets haben interne Definitionen Vorrang vor externen Definitionen oder Deklarationen, selbstverständlich nur innerhalb ihres Ansprechbereichs. Die Speicherklasse static extern

Die externen Objekte sind eine sehr bequeme (und schnelle) Möglichkeit, Daten zwischen Funktionen auszutauschen. Ihr Nachteil ist ihre totale Nichtabgeschirmtheit, unbeabsichtigte Veränderungen sind leicht möglich. Funktionen, die über externe Objekte kommunizieren, sind natürlich nicht mehr modular. Aus diesem Grunde wurde in C die Speicherklasse static für externe Objekte geschaffen. Ein externes Objekt, das mit dem Zusatz static definiert ist, kann in anderen Sourcedateien nicht bekannt gemacht werden. Die Menge der Funktionen, die ein static externes Objekt verändern und lesen können, ist damit genau abgegrenzt. Diese Datenobjekte haben alle Vorteile der übrigen externen Objekte, aber nicht deren Nachteile.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 65 D. Hägele, Stand 30.09.2015

Der Zusatz static kann auch bei Funktionsdefinitionen stehen - denn Funktionen sind externe Objekte. Die Bedeutung ist genau dieselbe wie bei Datenobjekten: Diese Funktionen können von anderen Sourcedateien aus nicht angesprochen werden, d. h. nicht aufgerufen werden. Sinnvoll ist das etwa dann, wenn erreicht werden soll, dass bestimmte Funktionen nur über einen festgelegten Aufrufpunkt angesprochen werden sollen. Dann werden diese Funktionen in eine gemeinsame Sourcedatei geschrieben und alle, bis auf eine, mit dem Zusatz static definiert. Lediglich über die eine Funktion, die nicht als static definiert wurde, kann jetzt der Aufruf erfolgen (information hiding). Die Speicherklasse register

Bei internen Objekten vom Ganzzahltyp kann die Speicherklasse register gefordert werden (statt static oder auto). Der Benutzer fordert damit den Compiler auf, das Objekt möglichst auf einem Hardware-Register zu verwalten (nur Compiler-Empfehlung!). Wirkung: Effizienzverbesserung. Heutige Compiler arbeiten jedoch so gut, dass dies in der Regel keine Vorteile bringt und deshalb nicht mehr notwendig ist. Anmerkung über ANSI-Compiler

Die ANSI-Norm legt die Unterscheidung von Definition und Deklaration anders fest, als die Compiler für UNIX oder MSDOS/WINDOWS:

Steht das Schlüsselwort extern, so handelt es sich stets um eine Deklaration – wie bei UNIX oder MSDOS/WINDOWS.

Fehlt extern und ist eine Initialisierung angegeben, so wird das Objekt an dieser Stelle definiert.

Fehlt extern und die Initialisierung, so sucht der Compiler in der selben Datei nach einer Zeile, in der dasselbe Objekt initialisiert wird. Diese Zeile ist dann die Definition des Objekts; wird keine Initialisierungszeile gefunden, so ist die erste Zeile in der Datei Definitionszeile des Objekts, die anderen sind Deklarationszeilen. Um diesem Verwirrspiel zu entgehen, und natürlich erst recht um der Portabilität willen, sollten Definitionszeilen externer Variablen stets mit einer Initialisierung versehen, und alle Deklarationen durch das extern-Schlüsselwort gekennzeichnet werden.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 66 D. Hägele, Stand 30.09.2015

9.5 Parameter von Funktionen

9.5.1 Call by value

Alle Parameterübergaben an C-Funktionen sind "call by value"-Übergaben (Wertübergaben). Das bedeutet, dass die gerufene Funktion die Objekte, von denen die Werte stammen, nicht kennt. Folglich ist es nicht möglich, dass die gerufene Funktion diese Objekte ändert. Beispiel 20a:

#include <stdio.h>

#include <stdlib.h>

void tausch(int x, int y);

int main(int argc, char *argv[])

{

int a = 77, b = 54;

printf("a = %d\tb = %d\n", a, b);

tausch(a,b);

printf("a = %d\tb = %d\n", a, b);

return 0;

}

void tausch (x, y)

{

int tmp;

tmp = x;

x = y;

y = tmp;

printf("TAUSCH: x = %d\ty = %d\n", x, y);

}

Diese Funktion tausch() tauscht die mitgereichten Argumente. Aber die Funktion, die tausch() aufgerufen hat, merkt davon nichts. Die ursprünglichen a und b im Block der Funktion main() bleiben von der ganzen Aktion unberührt. Erst die Zeigervariablen werden hier grundsätzliche Abhilfe schaffen. Einstweilen aber ist der Returnwert die einzige Möglichkeit für eine Funktion, Änderungen in die rufende Funktion zurückzureichen.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 67 D. Hägele, Stand 30.09.2015

9.5.2 Call by reference

Der Name eines Vektors beinhaltet die Adresse der Komponenten mit dem Index 0. Durch diese Tatsache wird bei der Übergabe von Vektornamen an Funktionen eine Referenz auf Objekt in der rufenden Funktion hergestellt. So ist die im vorigen Abschnitt geschilderte Schwierigkeit beim Versuch, den Tausch zweier Variablenwerte durch eine Funktion durchführen zu lassen, nicht vorhanden, wenn die zu tauschenden Objekte Komponenten eines Vektors sind. Beispiel 20b:

#include <stdio.h>

#include <stdlib.h>

void tausch(int *x);

int main(int argc, char *argv[])

{

int a = 77, b = 54, v[2];

v[0] = a;

v[1] = b;

printf("a = %d\tb = %d\n", a, b);

tausch(v);

a = v[0];

b = v[1];

printf("a = %d\tb = %d\n", a, b);

return 0;

}

void tausch (int *x)

{

int tmp;

tmp = x[0];

x[0] = x[1];

x[1] = tmp;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 68 D. Hägele, Stand 30.09.2015

10. Zeiger und Vektoren

10.1 Zeiger

Um mit Zeigern arbeiten zu können, werden zwei zusätzliche Operatoren benötigt:

&: Adressoperator (Referenzierungsoperator)

*: Verweisoperator (Dereferenzierungsoperator)

Das folgende Beispiel zeigt die wichtigsten Aspekte des Zeigerbegriffs und die Verwendung dieser Operatoren. Beispiel 21:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int v, l, *pv; /* Definition eines Zeigerobjekts */

pv = &v; /* Zuweisung einer Adresse auf den Zeiger */

*pv = 5; /* Referenzierung des Zeigers */

printf("v = %d\t*pv = %d\n", v, *pv);

l = *pv; /* Referenzierung des Zeigers */

printf("l = %d\n", l);

return 0;

}

der * in der Definition: die Variable ist ein Zeiger

der * im Statement: der Zeiger wird referenziert, d.h. der Inhalt des Objektes

ist Gegenstand der Handlung

* bedeutet "Inhalt von" (Referenz)

& bedeutet "Adresse von" Ein Zeiger ist eine Variable, die eine Adresse enthält. Die Zeigervariable selbst besitzt jedoch auch eine Adresse, an der sie physikalisch gespeichert wird. Die Zuweisung auf eine Variable ist nichts anderes als das Zuweisen auf die Adresse der Variablen. Intern betrachtet ist die Variable eine Adresse.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 69 D. Hägele, Stand 30.09.2015

Befehl Adresse Speicherausschnitt

int v, l, *pv; ...

4711H

Speicher für int v

...

487FH Speicher für int l

Funktion ...

Speicherreservierung (Deklaration) 51F7H Speicher für int *pv

... Befehl Adresse Speicherausschnitt

pv = &v; ...

4711H Speicher für int v

...

487FH Speicher für int l

Funktion ...

Speicherbelegung (Zuweisung) 51F7H

4711H

... Befehl Adresse Speicherausschnitt

*pv = 5; ...

4711H 5

...

487FH Speicher für int l

Funktion ...

Speicherbelegung (Zuweisung) 51F7H 4711H

... Befehl Adresse Speicherausschnitt

l = *pv; ...

4711H 5

...

487FH 5

Funktion ...

Speicherbelegung (Zuweisung) 51F7H 4711H

...

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 70 D. Hägele, Stand 30.09.2015

Als erste Anwendung wird die Funktion tausch() neu formuliert. Beispiel 22:

#include <stdio.h>

#include <stdlib.h>

void tausch(int *x, int *y);

int main(int argc, char *argv[])

{

int a = 77, b = 54;

printf("a = %d\tb = %d\n", a, b);

tausch(&a, &b);

printf("a = %d\tb = %d\n", a, b);

return 0;

}

void tausch (int *x, int *y)

{

int tmp;

tmp = *x;

*x = *y;

*y = tmp;

}

Die Parameter mit Zeigerwerten realisieren de facto einen "call by reference", die Variable an sich wird damit übergeben, nicht nur der Wert. Dabei hat sich an dem Prinzip von C nichts geändert, dass alle Parameterübergaben im Modus des "call by value" erfolgen.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 71 D. Hägele, Stand 30.09.2015

10.2 Vektoren

Der reine Name eines Vektors ist eine Adresse, nämlich die der Komponente mit dem Index 0; der Vektorname ist also ein Zeigerwert. Beispiel 23:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int i, v_i[10], *p_i;

p_i = v_i; /* die Adresse von vi[0] wird pi zugewiesen */

p_i = & v_i[0]; /* dasselbe */

p_i[2] = 456; /* entspricht v_i[2] = 456 */

p_i = & v_i[3]; /* Adresse von v_i[3] nach p_i */

p_i[2] = 123; /* entspricht v_i[5] = 123 */

printf("p_i = %u\t*p_i = %d\n", p_i, *p_i);

for (i=0; i<10; i++)

printf("v_i[%d] = %d\tp_i[%d] = %d\n", i, v_i[i], i, p_i[i]);

return0;

}

ACHTUNG:

Der Name eines Vektors ist ein Zeigerwert, aber damit noch keine Zeigervariable; er ist eine Konstante. Das bedeutet: Ihm darf nie etwas zugewiesen werden.

legal illegal p_i = v_i

p_i++

v_i = p_i

v_i++

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 72 D. Hägele, Stand 30.09.2015

Kopieren von Strings; die Funktion strcpy()

Wie bereits erwähnt, gibt es in C keinen Datentyp für Zeichenketten. Hier muss ein Vektor vom Typ char definiert werden, der dann die Zeichenkette enthält. Üblicherweise wird die Zeichenkette mit einer binären Null abgeschlossen (Nullterminierung). Im folgenden Beispiel ist in der main-Funktion ein char-Vektor definiert. Dem Vektor wird durch die Funktion strcpy() eine Zeichenkette zugewiesen. Beispiel 24:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

char str[20];

strcpy(str, "Hans");

printf("str = %s\n", str);

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 73 D. Hägele, Stand 30.09.2015

10.3 Zeigerarithmetik

Addition von Ganzzahlen zu Zeigerwerten :

Es sei die Adresse einer Komponente eines Vektors gegeben. Zu errechnen ist die Adresse der Komponente mit dem um k größeren Index. Dann reicht es offenbar nicht, einfach den Zahlenwert von k auf die gegebene Adresse zu addieren, es muss noch mit der Länge der Komponente (in Byte) multipliziert werden. Es ist fehleranfällig, wenn der Benutzer diese Länge des Objektes berücksichtigen muss, sobald er Adressen weiterschalten will. In C braucht er das auch nicht. Wird auf eine Zeigervariable ein Ganzzahlwert addiert, so wird diese Zahl intern mit der Länge des Datentyps multipliziert, für die die Zeiger-Variable definiert ist: Offset = Anzahl Objekte * sizeof(Datentyp).

Beispiel 26:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int v_i[10] , *p_i ; /* p_i ist Zeiger auf int */

p_i = v_i;

p_i++; /* p_i enthält jetzt die Adresse von v_i[1] */

printf("p_i = %u\t&v_i[1] = %u\n", p_i, &v_i[1]);

*(p_i + 2) = 55; /* 55 wird v_i[3] zugewiesen */

/* p_i + 2 ist die Adresse, die um */

/* 2 int-Objekte höher ist, als die */

/* in p_i */

printf("v_i[3] = %d\t&v_i[3] = %u\n", v_i[3], &v_i[3]);

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 74 D. Hägele, Stand 30.09.2015

Referenzierung durch * und [ ]

Die Beispiele zeigen: Wird eine Zeigervariable mit der Anfangsadresse eines Vektors geladen, so sind folgende Schreibweisen gleichwertig: p_i[i] und *(p_i + i)

v_i[i] und *(v_i + i)

Zeiger- und Vektorschreibweise für Zeiger und Vektoren

Diese Sachverhalt fasst man in der Formel

zo[i] <==> *(zo + i) zusammen; zo steht dabei für "beliebiges Zeigerobjekt". Der Index kann auch entfallen, da zo[ ] bereits das erste Element des Vektors (i = 0) referenziert. Somit gilt auch:

zo[ ] <==> *zo Anmerkung:

Im Definitionsteil einer Funktion und ebenso bei der Definition externer Objekte gibt es einen Unterschied zwischen

typ_bezeichner var[ ]

und

typ_bezeichner *var

Ersteres definiert einen Vektor ohne Länge, d.h. eine Konstante, auf die nichts zugewiesen werden kann. Das Zweite ist eine Variable, die veränderbar ist. Bei den Parameterdefinitionen kommt das nicht zum Tragen, weil ein Parameter von Hause aus eine Variable ist.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 75 D. Hägele, Stand 30.09.2015

Differenz von Zeigerwerten

Die Addition einer (ganzen) Zahl zu einem Zeigerwert ist die erste Rechenoperation, die für Zeigerobjekte erklärt ist. Es kann jedoch auch die Differenz gebildet werden. Die Differenz zweier Zeiger des gleichen Typs gibt die Anzahl der Objekte dieses Datentyps an, die zwischen den beiden Zeigerwerten gespeichert werden können.

Zeiger, die auf denselben Typ zeigen, werden auch Zeiger vom selben Typ genannt. (Es haben aber alle Zeiger dasselbe Speicherbild, nämlich in der Regel das des unsigned long )

Die Differenz zweier Zeiger von verschiedenem Typ ist undefiniert und darf nicht gebildet werden.

Das folgende Beispiel zeigt den Unterschied zwischen Zeigerdifferenz und Zahlendifferenz (tatsächlicher Offset).

Beispiel 27:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

char c , *p_c1, *p_c2;

int i, *p_i1, *p_i2;

double d, *p_d1 , *p_d2;

p_c1 = &c;

p_i1 = &i;

p_d1 = &d;

p_c2 = p_c1 + 1;

p_i2 = p_i1 + 1;

p_d2 = p_d1 + 1;

printf("Differenz dreier Zeiger %ld\t%ld\t%ld\n",

p_c2 - p_c1, p_i2 - p_i1, p_d2 - p_d1);

printf("Differenz der drei Zeiger als Zahlen %d\t%d\t%d\n\n",

(int) p_c2 - (int) p_c1,

(int) p_i2 - (int) p_i1,

(int) p_d2 - (int) p_d1);

/* Die cast's machen aus den Zeigern Zahlen */

return 0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 76 D. Hägele, Stand 30.09.2015

Als Ausgabe erscheint dann:

C:\main.exe Differenz dreier Zeiger 1 1 1

Differenz der drei Zeiger als Zahlen 1 4 8

Press any key to continue

Das Programm gibt erst dreimal die 1 aus und dann die 1, die 4 (bei 16-Bit-Compiler ergibt sich hier die 2) und die 8.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 77 D. Hägele, Stand 30.09.2015

10.4 Stringkonstanten

Findet der Compiler im Source-Code eine Zeichenkette in " " eingeschlossen, so bildet er auf dem Datensegment einen nullterminierten char-Vektor, dessen Komponenten die in " " stehenden Zeichen enthalten. Die Adresse der ersten Komponente dieses Vektors wird im ausführbaren Code an die Stelle gesetzt, an der im Source die Zeichenkette steht. Solche Zeichenketten heißen Stringkonstanten. Beispiel 28:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(int argc, char *argv[])

{

char *p_x , s_x[32] , s_y[32];

p_x = "Odie";

/* Der Compiler speichert die Zeichenkette */

/* Odie\0 */

/* auf dem Datensegment und weist die Adresse */

/* ihrer ersten Komponente auf px zu */

strcpy(s_x , p_x);

/* Ab der Adresse in px bis zur Nullterminierung */

/* werden char-Objekte nach sx zugewiesen */

strcpy(s_y , "Garfield isst gern heisse Lasagne");

/* Die Zeichenkette kann natürlich genauso gut */

/* dem Parameter direkt zugewiesen werden */

printf("p_x: %s\ns_x: %s\ns_y: %s\n\n", p_x, s_x, s_y);

return 0;

}

Anmerkung:

Der Ausdruck Stringkonstante ist nicht sehr treffend, denn konstant ist daran nichts, außer der Adresse, und die ist es bei jedem Vektor.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 78 D. Hägele, Stand 30.09.2015

Externalität der Stringkonstanten

Der Vektor der Zeichenkette " " wird auf dem Datensegment gespeichert, d.h. er ist ein externes Objekt und damit von anderen Funktionen direkt und ohne Parameterübergabe ansprechbar: Beispiel 29:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

char *p_x , *p_y; /* externe (globale) Zeiger */

void fkt(void);

int main(int argc, char *argv[])

{

fkt();

printf("p_x = %ld\tp_y = %ld\n" , p_x , p_y );

/* Die Adresswerte von v aus fkt und der */

/* Zeichenkette sind noch verfügbar */

printf("*p_x = %s\n*p_y = %s\n" , p_x , p_y );

/* der Inhalt des Vektors v der beendeten Funktion fkt */

/* und die Zeichenkette sind noch verfügbar */

return0;

}

void fkt()

{

char v[30];

strcpy(v, "Garfield" ) ;

p_x = v; /* p_x -> &v[0] , Adresse des auto-Zeigers */

p_y = "Odie"; /* p_y -> Adresse der Zeichenkettenkonstante */

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 79 D. Hägele, Stand 30.09.2015

Stringkonstanten und Initialisierung Zeigervariablen sind keine Vektoren, sondern Skalare. Folglich können sie auch initialisiert werden, wenn sie die Speicherklasse automatic haben. Insbesondere können sie mit Stringkonstanten initialisiert werden. Auch für Vektoren ist die Initialisierung mit Stringkonstanten erlaubt, aber sie müssen die Speicherklasse static oder extern haben : Beispiel 30:

#include <stdio.h>

#include <stdlib.h>

char vec[] = "Garfield liebt Odie"; /* externer Vektor */;

int main(int argc, char *argv[])

{

static char vtr[] = "Garfield ist zu dünn!";

/* static Vektor */

char *p_c = "Lasagne"; /* auto Zeiger */

/* Programm */

...

...

...

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 80 D. Hägele, Stand 30.09.2015

10.5 Ragged Arrays

Zeigerobjekte lassen sich genauso zu Vektoren zusammenfassen wie andere Objekte. Bilden speziell Zeiger-auf-char die Vektorkomponenten, so heißen diese Vektoren „Ragged Arrays“. Beispiel 31:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int i;

char *bp[10];

/* Vektor aus Zeigern auf char;

jede Komponente ist vom Typ Zeiger auf char */

bp[0] = "Linux";

bp[1] = "Diese Komponenten bilden ein 'ragged array'.";

bp[2] = "FH Heilbronn";

bp[3] = "A";

bp[4] = (char )0; /* 0 ist die Adresse keines

Objektes (Vektor-Ende)*/

for (i=0; i<4; i++)

printf("bp[%d]: %s\n",i, bp[i]);

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 81 D. Hägele, Stand 30.09.2015

10.6 Argumente aus der Kommandozeile

Die Parameterklammer bei der main-Funktion kann genau wie die bei anderen Funktionen zur Definition formaler Parameter verwendet werden. Ihr Typ liegt allerdings fest. Zum Testen kann man Übergabeparameter bereits in der Entwicklungsumgebung vorgeben. In Code::Blocks findet sich diese Option unter "Projects -> Set programs' arguments -> Program arguments".

Beispiel 32:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

printf("%d Parameter übergeben.\n\n", argc);

while (argc--)

{

printf("%s\n", argv[argc]);

}

return0;

}

Der erste Parameter der main-Funktion gibt an, wie viele Argumente beim Aufruf mitgereicht wurden. Sein Wert ist mindestens 1, weil der Vektor argv, der die Zeiger auf die Argumente (die eingetippten Strings) enthält, mindestens die Länge 1 hat. In argv[0] wird der Aufrufname des Programms gespeichert. Auf vielen Systemen ist das Ende des Vektors argv auch daran zu erkennen, dass die letzte Komponente den Zeigerwert NULL enthält. Dann könnte die obige while-Schleife auch so geschrieben werden:

...

while (*argv)

{

printf("%s\n", *argv++);

}

...

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 82 D. Hägele, Stand 30.09.2015

11. Zusammengesetzte Datentypen

11.1 Strukturtypen

Der Begriff der structure kann als Verallgemeinerung des Vektors aufgefasst werden. Besteht der Verktor noch aus einer Aneinanderreihung von Objekten desselben Typs (homogener Datenverbund), so werden beim Strukturtyp Objekte verschiedener Typen zusammengefasst (heterogener Datenverbund). Unter den Objekten, die eine structure definieren, dürfen wieder Objekte vom Typ structure vorkommen, allerdings nicht solche vom Typ der zu definierenden structure (keine Rekursion, erlaubt ist aber ein Zeiger auf dieselbe structure).

struct kunde{ int kd_num;

char name[30];

char adresse[30];

int konto;}

Es ist noch kein Speicher für Objekte des Typs reserviert (nur Strukturmuster, Beschreibung). Die Variablendefinition kann unmittelbar hinter der schließenden Klammer vorgenommen werden oder, falls der Typ einen Namen hat (was nicht sein muss, hier: kunde), auch später in der Form:

struct kunde kunde_1 , kunde_2 , kdvek[10] ;

Das Schlüsselwort struct muss stets mit angegeben werden. Der Name für sich genügt nicht, weil er auch als Name von Objekten vorkommen darf, sogar als Name eines Objektes desselben Typs:

struct kunde kunde ;

Zuweisungen, die Punktnotation

kunde_1.kd_num = 1001;

strcpy(kunde_1.name, "Hugo Hugo");

strcpy(kunde_1.adresse, "Berlin");

kunde_1.konto = 2002;

kunde_2 = kunde_1;

Die Zuweisung ganzer Strukturobjekte aufeinander ist unter allen neuen Compilern möglich, in ANSI sogar als Standard festgelegt.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 83 D. Hägele, Stand 30.09.2015

Mit Zeigern auf Strukturobjekte wird wie mit denen auf skalare Objekte gearbeitet: Beispiel 33:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

struct kunde { int kd_num;

char name[30];

char adresse[30];

int konto; };

int main(int argc, char *argv[])

{

struct kunde kunde_1 , kunde_2, kdvek[10], *p_kd, *p_kv;

p_kd = &kunde_1;

p_kv = kdvek;

/* , aber nicht p_kv = kunde_2 ! */

/* Name eines Strukturobjektes ist kein Zeigerwert */

(*p_kd).konto = 1700;

p_kd -> kd_num = 1111;

/* Zeiger auf Strukturobjekte können mit dem Pfeil */

/* referenziert werden. Der Pfeil ersetzt ausserdem */

/* den Punkt. */

*p_kv = *p_kd; /* das Objekt mit der Adresse, die in */

/* p_kd steht wird zugewiesen auf das */

/* Objekt, dessen Adresse in p_kv ist: */

/* die erste Komponente von kdvek */

strcpy(kunde_1.name, "Musterkunde");

strcpy(kunde_1.adresse, "Musterhausen");

printf("Kunde_1:\t%d\t%s\t%s\t%d",

kunde_1.kd_num, kunde_1.name,

kunde_1.adresse, kunde_1.konto);

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 84 D. Hägele, Stand 30.09.2015

Strukturobjekte werden an Funktionen wie skalare Objekte übergeben, nämlich als Werte (call by value). Anders als bei Vektoren ist der Name eines Strukturobjektes keine Adresse. Insbesondere werden alle eventuell im Strukturobjekt enthaltenen Vektoren als Werte übergeben - was natürlich nichts anderes heißt, als dass sie Komponente für Komponente auf den Stackframe der gerufenen Funktion kopiert werden. Deshalb kann die gerufene Funktion keine Änderungen an den übergebenen Objekten vornehmen, die in der rufenden Funktion wirksam werden. Dazu muss wieder, wie bei skalaren Objekten der explizit gebildete Zeigerwert verwendet werden. Funktionen können Strukturobjekte an die Aufrufstelle als Returnwert zurückreichen, ebenso wie es bei skalaren Objekten möglich ist. Diese beiden Eigenschaften der Strukturobjekte sind bei allen neueren Compilern gegeben, die ANSI-Norm legt sie fest. Das heißt aber nicht, dass es sehr effizient ist, so zu verfahren. Vor allem bei großen Objekten wird es zu bedeutend besseren Laufzeiten führen, wenn mit Zeigern gearbeitet wird.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 85 D. Hägele, Stand 30.09.2015

Beispiel 34:

#include <stdio.h>

#include <stdlib.h>

struct kunde { int kd_num;

char name[30];

char adresse[30];

int konto; };

void wrtkd(struct kunde x);

struct kunde readkd(void);

int main(int argc, char *argv[])

{

struct kunde kunde_1, kunde_2;

puts("Bitte geben Sie den ersten Kunden ein:");

kunde_1 = readkd();

puts("\nBitte geben Sie den zweiten Kunden ein:");

kunde_2 = readkd();

printf("\n1. Kunde:\t");

wrtkd(kunde_1);

printf("\n2. Kunde:\t");

wrtkd(kunde_2);

return0;

}

void wrtkd(struct kunde x)

{

printf("%d\t%s\t%s\n", x.kd_num, x.name, x.adresse);

}

struct kunde readkd(void)

{

struct kunde result;

printf("KD-Nr.: \t");

scanf("%d" , &result.kd_num );

printf("Name: \t");

scanf("%s" , result.name);

printf("Adresse:\t");

scanf("%s" , result.adresse);

return result;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 86 D. Hägele, Stand 30.09.2015

11.2 Aufzählungstypen

Die Aufzählungstypen (engl.: enum types, enumeration) dienen der Definition von Datentypen mit Wertebereichen, die der Benutzer festlegt. enum farbtopf {rot, blau, gelb, weiss, gruen, schwarz};

Dies legt erst einen Typ fest: es ist noch kein Objekt definiert. Das kann auf zweierlei Weise geschehen :

enum farbtopf farbe; /* Die Variable ist farbe vom */

/* Typ farbtopf und kann Werte */

/* dieses Typs annehmen */

Zweitens können sofort bei der Definition des Typs Objekte definiert werden :

enum auto {audi, mercedes, opel, ford, fiat} car, kfz;

In diesem Falle braucht der Typ nicht unbedingt einen Namen. Der Typname ist nur nötig, wenn später darauf Bezug genommen werden soll:

enum { engl , fra , ital , germ , spa } sprache , cntry;

Zuweisungen:

sprache = fra;

kfz = audi;

farbe = gelb;

Bei den Werten der enum-Types werden intern ganze Zahlen zugeordnet, und zwar bei 0 beginnend und in Einerschritten aufsteigend. Deshalb kann mit enum-Type Variablen gerechnet werden:

sprache++;

farbe += weiss ;

Die Nummernzuordnung kann vom Benutzer verändert werden:

enum obst { apfel , birne = 6 , orange , pfirsich = 9 ,

kirsche , kiwi , banane = 54 , melone };

Dem apfel entspricht die 0, der orange 7, der kirsche 10, der kiwi 11, der melone 55. Vom Gebrauch dieser Möglichkeit wird abgeraten.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 87 D. Hägele, Stand 30.09.2015

Datentypen, enum types, Strukturtypen ebenso wie die im folgenden erklärten Unions können wie Objekte extern, static und automatic definiert werden. In der Regel werden sie extern definiert. Es ist nicht möglich, Datentypen in anderen Dateien mit extern bekannt zu machen. Die Typdefinition muss in jeder Sourcedatei wiederholt werden. Sie ist dort jeweils ab AufschreibungsstelIe bekannt.

11.3 Unions

Mit dem Typ union wird in C realisiert, was in PASCAL der variant record, in COBOL das redefine-Statement, und in PL/1 das defined-statement leistet: Ein Speicherbereich wird mehrfach verwendet.

union var { int a;

double b;

char c;

} var_1, var_2;

Die Definition der union ist demnach der des Strukturtyps analog. Die union ist eine struct, bei der alle Komponenten auf derselben Speicherstelle beginnen. Es wird soviel reserviert, wie die größte Komponente Bytes benötigt. Sinn hat die Verwendung solcher Objekte nur dann, wenn sie sorgfältig verwaltet werden, und es muss von Fall zu Fall entschieden werden, ob der Aufwand lohnt. Am praktischsten wird so verfahren, dass die union als Komponente einer struct geschrieben wird, die ein tag_field enthält, das Auskunft über die jeweilige Belegung der union gibt:

struct vartag { int tag ; /* int=0, double=1, char=2 */

union {

int a;

double b;

char c;

} var;

} var_1 , var_2;

Hier ließe sich mit Nutzen ein enum-Typ für das tag_field einführen.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 88 D. Hägele, Stand 30.09.2015

Beispiel 35:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

union { char zeichen[16];

long ziffern[4];

} uv, *p;

p = &uv;

p -> ziffern[0] = 0x65747547; /* etuG */

p -> ziffern[1] = 0x6f4d206e; /* oM n */

p -> ziffern[2] = 0x6e656772; /* negr */

p -> ziffern[3] = 0x00202120; /* ! */

/* Guten Morgen ! */

printf ("\n\t\t%s\n", uv.zeichen);

printf ("\n\t\t%s\n", p->zeichen);

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 89 D. Hägele, Stand 30.09.2015

11.4 Die typedef-Anweisung

Die ständige Wiederholung des Schlüsselwortes struct bei den Strukturobjekten oder des enum bei den Aufzählungstypen ist mehr als lästig. Auch für die Portabilität und die Änderungsfähigkeit wäre es nützlich, wenn die Typen mit nur einem Namen ange-sprochen werden könnten, und nicht mehr jedesmal ausgeschrieben werden müssten.

typedef struct kunde { int kd_num;

char name[30];

char adresse[30];

int konto; } consumer;

Durch das Schlüsselwort typedef wird einer Struktur eine allgemein verwendbare Bezeichnung zugeordnet. Es ist also nicht mehr der Name eines einzelnen Objekts. Jetzt kann geschrieben werden:

void main()

{

consumer kunde_1, kunde_2, *p_kd;

........

n = sizeof(consumer);

}

Auch für typedef's gilt: Stehen sie extern, so kann in derselben Datei genauso auf sie zugegriffen werden wie auf externe Objekte. Sie können aber in anderen Sourcedateien nicht mit extern deklariert werden. Die Definition muss wiederholt werden.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 90 D. Hägele, Stand 30.09.2015

12. Der Preprocessor

Bis ein ausführbares Programm vorliegt, sind bei einer C-Prozedur 4 Schritte durchzuführen. Durch entsprechende Compileroptionen können die einzelnen Schritte beeinflußt oder auch verhindert werden. Die folgende Abbildung veranschaulicht den allgemeinen Ablauf.

Preprocessor

C Quellcode .c oder .cpp

Header-Dateien .h

Compiler

C Code

Assembler

Assembler-Code .asm

Linker

Object-Code / Relocatable Code .obj

Bibliotheken .a, .lib oder .dll

Lauffähiges Programm / Image .exe

Bibliotheken .dll

evtl.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 91 D. Hägele, Stand 30.09.2015

12.1 Konstantendefinition

Aus Gründen der Portabilität, der Wartbarkeit und der Lesbarkeit von Programmen ist es zweckmäßig, Konstanten in Form von Namen anzugeben. Zum C-Compiler (UNIX, MSDOS,ANSI) existiert ein Preprocessor (Präprozessor), der Stringsubstitutionen vornimmt (Textersatz). Er wird vom Compiler aufgerufen, wenn ein #-Zeichen in der Spalte 1 der Sourcedatei steht:

#define EOF (-1)

Der Preprocessor setzt jetzt, von der Definitionszeile an, überall in der Datei, die Zeichenkette (-1) dort ein, wo er die Zeichenkette EOF vorfindet. Die Ersetzung findet jedoch nicht statt:

in Zeichenketten, die in " " eingeschlossen sind

in Kommentaren

wo links und/oder rechts von EOF sich Buchstaben oder Ziffern befinden Der zu ersetzende String (im Beispiel das EOF) darf keine white-space Zeichen enthalten. Der Ersetzungsstring, hier die(-1), reicht vom ersten non- white-space hinter dem ersten String bis zum Zeilenende. Soll der Ersetzungsstring über das Zeilenende hinweg fortgesetzt werden, ist das CR- Zeichen zu maskieren (Fortsetzungszeichen "\" ). Beispiel 36:

#include <stdio.h>

#include <stdlib.h>

#define EOF (-1)

int main(int argc, char *argv[])

{

int c;

while (( c = getchar()) != EOF )

{

putchar(c);

}

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 92 D. Hägele, Stand 30.09.2015

Preprocessor-Kommandos sind: #include #undef #ifndef #else #endif #pragma #asm

#define #ifdef #if #elif #line #error #endasm

Nicht alle Kommandos sind überall verfügbar, da sie vom Preprocessor abhängig sind. Vordefinierte Makros sind: __FILE__ Name der Quelldatei __LINE__ Aktuelle Zeilennummer __DATE__ Aktuelles Datum __TIME__ Aktuelle Uhrzeit

12.2 IncIudeanweisungen

Der Preprocessor kann auch ganze Dateien in eine Datei hineinziehen. Das ist besonders dann erwünscht, wenn immer wieder dieselben define- Anweisungen benötigt werden oder Bibliotheksfunktionen bekannt gemacht werden sollen.

#include <stdio.h>

#include "my_infile"

Der Name der Datei, die eingezogen werden soll, steht in den Spitzklammern, wenn es eine "Systemdatei" ist: diese sind in bestimmten, dem Preprocessor bekannten, Katalogen verzeichnet. Steht der Name in " ", so wird der Pfadname benutzt. Achtung:

Es ist nicht empfehlenswert, die zweite Form an die Stelle der ersten zu setzen, etwa weil der Pfad der Systemkataloge bekannt ist, also zu schreiben:

#include "/user/include/stdio.h"

Diese Formulierung kann schon bei der nächsten Systemgenerierung falsch sein. Es dürfen include-Anweisungen in Dateien stehen, die mit include-Anweisungen in den Source gezogen werden MSDOS: Die Verzeichnisse, in denen der Preprocessor die Systemdateien findet, müssen in einer Environmentvariablen (Umgebungsvariablen) angegeben werden:

INCLUDE=C:\MSC\INCLUDE

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 93 D. Hägele, Stand 30.09.2015

12.3 Makrodefinitionen

Mit einem Makro können häufig verwendete Funktionen kürzer und einprägsamer geschrieben werden. Dabei wird ähnlich wie bei den Konstanten vom Preprocessor eine Ersetzung im Text durchgeführt. Eine Makrodefinition hat die Form:

#define identifier(arg,....,arg) tokenstring

Achtung:

Zwischen dem Makronamen und der öffnenden "Parameterklammer" ( darf kein Leerzeichen stehen. Beispiel:

#define LOWBYTE(x) ((x) & 0377)

Es wird jetzt eine doppelte Ersetzung veranlasst. Wo in der Sourcedatei sich der String "LOWBYTE" befindet, wird der Ersetzungsstring an seine Stelle gesetzt; zweitens wird das in der Klammer bei LOWBYTE stehende x durch den Namen ersetzt, der an der Referenzstelle in der Klammer bei LOWBYTE steht:

void main()

{

........

z = LOWBYTE(vv[3]);

.......

}

Der Preprocessor ersetzt erstens LOWBYTE(...) durch ( (x) & 0377 ) und zweitens x durch vv[3] . Das Ganze hat ungefähr den Anschein eines Funktionsaufrufes, bei dem vv[3] der aktuelle Parameter ist, der für den formalen Parameter x gesetzt wird. Doch es handelt sich um etwas gänzlich anderes. Es werden keine Parameter übergeben, weder durch call by value noch durch caII by reference. Es wird nur eine Stringsubstitution durchgeführt. Im Ersetzungsstring sind immer reichlich Klammern zu verwenden, weil sonst Ausdrücke entstehen können, die unsinnig sind oder eine ganz andere Bedeutung haben.

#define SQ(x) x * x

führt zu den Ersetzungen:

SQ(5) ==> 5 * 5

SQ (a + b) ==> a + b * a + b

Letzteres war wohl nicht beabsichtigt. Mit

#define SQ(x) (x) * (x)

wird falsche Assoziierung vermieden. Um auch Abschirmungen gegen davor und dahinter stehende Ausdrücke zu gewährleisten, werden meistens noch Außenklammern gesetzt:

#define SQ(x) ( (x) * (x) )

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 94 D. Hägele, Stand 30.09.2015

Die define-Anweisungen dürfen auf voranstehende define-Anweisungen Bezug nehmen:

#define SQ(x) ( (x) * (x) )

#define CUB(x) ( SQ(x) * (x) )

#define SEVEN(x) ( CUB(x) * CUB(x) * (x) )

12.4 Bedingte Compilierung

Eine simple, aber häufig verwendete Methode des Debuggens von Programmen besteht darin, in den Code printf-Aufrufe einzustreuen, die über den aktuellen Stand der Programmausführung informieren. Der Nachteil dieses Verfahrens ist es natürlich, dass der ursprüngliche Code verändert wird. Bei der Rückänderung entstehen dann nicht selten wieder Fehler. Durch die Preprocessoranweisung

#define DEBUG 0

#if DEBUG

........ Code

#endif

wird erreicht, dass der zwischen #if DEBUG und #endif stehende Code nur dann compiliert wird, wenn DEBUG einen Wert ungleich 0 hat. Die Bedingung bezieht sich auf den gesamten Code zwischen #if und #endif, also auch auf eventuell darin vorkommende Preprocessoranweisungen. Es existiert noch eine Anweisung

#else

zum #if, mit der Code alternativ zur Bedingung compiliert werden kann. Die Konstante DEBUG selbst wird in der define-Anweisung entweder auf 0 oder auf 1 gesetzt. Manchmal besteht Veranlassung, eine define-Anweisung davon abhängen zu lassen, ob die betreffende Konstante schon definiert wurde oder nicht:

#ifdef EOF /* falls EOF definiert, definiere ERR, sonst EOF */

#define ERR (-1)

#else

#define EOF (-1)

#endif

#ifndef EOF /* definiere, falls noch nicht definiert */

#define EOF (-1)

#endif

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 95 D. Hägele, Stand 30.09.2015

13. High Level Input/Output (Bibliotheks I/O)

13.1 High Level und Low Level l/O

Ein- und Ausgabe ist eigentlich keine Sache der Programmiersprache, sondern des Betriebssystems. Nur vom Betriebssystem aus ist sichtbar, wie ein File beschaffen ist, welche Parameter nötig sind, um auf das File zuzugreifen, welche Arten des Zugriffs möglich sind und welche nicht. Die Sprache kann nicht wissen, wie viele Prozesse zugleich auf ein File zugreifen können und was gegebenenfalls passiert, wenn sie es tun, oder wie es verhindert werden kann, und ob es überhaupt möglich ist, Mehrfachzugriff zu verhindern (in älteren UNIX-Versionen war es z.B. nicht möglich). Es ist, wenigstens grundsätzlich, in der Sprache nicht einmal bekannt, ob es ein Zeichen für End-of-File gibt, oder wie sonst einem lesenden Programm das Ende eines Files bekannt gemacht wird. Erst recht unbekannt ist die Dateiorganisation: Sind sie reine Bytesequenzen (wie unter UNIX oder MSDOS), oder sind sie "Recordweise" aufgebaut wie unter BS2000 oder OS 1100? Eins allerdings ist möglich : Durch eine Menge von Bibliotheksfunktionen ein Arbeiten mit virtuellen Dateien zu konstruieren, in der Sprache gleichsam mit einem "Modell-Filesystem" an Stelle des realen zu arbeiten. Es ergibt sich dann natürlich die Aufgabe, das C-Ein-/Ausgabesystem, das System der virtuellen Dateien und der zugeordneten Funktionen, auf das reale Dateisystem des jeweiligen Betriebssystems abzubilden. Diese Abbildung ist dann Sache der eigentlichen Systemschnittstelle. Sie muss die Verbindung zwischen diesem virtuellen Dateisystem und dem realen herstellen. Den C-Programmierer geht diese Schnittstelle dann jedenfalls solange nichts an, wie er nicht aus irgendwelchen Gründen gezwungen wird, auf sie zurückzugreifen, etwa weil im virtuellen Dateisystem gewisse Möglichkeiten seines realen Filesystems keine Entsprechung haben. Im Falle von UNIX (System V und BSD) ist das z.B. der Fall, sobald das sog. "Record Locking" benutzt werden soll. Es gibt bis jetzt keine Bibliotheksfunktion, die das übernimmt. Durch die Schaffung der mit virtuellen Dateien arbeitenden Bibliotheksfunktionen ist eine weitgehende Ablösung der Ein-/Ausgabe in C von der ursprünglich vorgegebenen UNIX-Welt gelungen. Hundertprozentig ist sie nicht. So wird an vielen Stellen der Hintergrund der UNIX-Dateien als homogener Bytesequenzen, ohne jedwede Struktur sichtbar. Einige der Bibliotheksfunktionen haben deshalb zwar nicht allein unter UNIX einen Sinn, aber eine Beschränkung auf Systeme mit einem ähnlichen Dateiaufbau ist schon vorhanden. Für MSDOS gibt es allerdings keine nennenswerten Einschränkungen in der Anwendung des hier beschriebenen C-Ein-/Ausgabesystems.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 96 D. Hägele, Stand 30.09.2015

Die Beziehung zwischen der Ein-/Ausgabe mit Bibliotheksfunktionen einerseits und Systemschnittstelle andererseits wirkt dadurch zuweilen etwas verwirrend, dass die Programmierung der Systemschnittstelle ebenfalls über bestimmte Bibliotheksfunktionen (einer andern Bibliothek) vollzogen wird, die wie normale C-Funktionen aufgerufen werden. Diese Funktionen, die sog. "Systemcalls", haben aber einen völlig anderen Status: Sie enthalten wesentlich Assembler-Coding und verändern direkt systeminterne Größen, wie den Stackpointer, den Programmcounter und andere Register. Sie besorgen (unter UNIX), dass der Prozeß, der sie aufruft, in einen anderen Modus (den sog. Kernel-Modus) versetzt wird, sobald ihr Coding angesteuert wird, und dass die es in einem anderen Stackbereich ausgeführt werden; die Parameterübergabe wird ganz anders vollzogen u.a.m. Kurz: Der Aufruf der Systemschnittstelle sieht zwar wie ein gewöhnlicher Funktionsaufruf aus, ist es aber nicht. 13.1.1 Der Low Level l/O

Beim Low Level I/O wird das Betriebssystem teilweise umgangen und die Aufrufe direkt ans BIOS gesendet. Die wichtigsten Funktionen des Low Levels sind:

creat() Anlegen einer Datei

open() Öffnen einer Datei

read() Lesen aus einer Datei

write() Schreiben in eine Datei

lseek() Positionieren in eine Datei 13.1.2 Der High Level I/O

Jede Datei wird durch ein Strukturobjekt vom Typ FILE ( definiert in der stdio.h) repräsentiert. Die Funktion, die das "Eröffnen" der Datei für den Benutzerprozeß besorgt, tut nichts, als ein solches Strukturobjekt mit der tatsächlichen Datei zu verbinden (evtl. muss sie es vorher kreieren). Dem Programm wird der Zeiger auf das der Datei zugeordnete Strukturobjekt an die Aufrufstelle der Öffnungsfunktion zurückgegeben. Alle Schreib- und Lesevorgänge, der gesamte Zugriff vollzieht sich über den Zeiger auf das repräsentierende Strukturobjekt, die "virtuelle Datei". Dieser Zeiger auf die virtuelle Datei wird manchmal auch als "Zeiger auf die Datei" bezeichnet - eine Redeweise, die leicht mißverständlich sein kann.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 97 D. Hägele, Stand 30.09.2015

Die Funktion fopen() Alle wesentlichen typedef's und Konstantendefinitionen für den High level I/O stehen in der Include-Datei stdio.h, für die von jetzt an in allen Programmdateien ein include-statement stehen muss.

FILE *fopen(name , mode)

char *name; /* Name der Datei */

char *mode; /* Öffnungsmodus */

Die Funktion fopen legt eine virtuelle Datei (= Objekt vom Typ FILE) an und sorgt dafür, dass diese virtuelle Datei über die Systemschnittstelle mit der realen Datei verbunden wird. Der Zeiger auf die virtuelle Datei wird returniert. Jeder Aufruf von fopen legt eine virtuelle Datei an, ohne Rücksicht darauf, ob für die reale Datei, deren Name der erste Parameter von fopen ist, schon eine virtuelle Datei existiert. Es können also beliebig viele virtuelle Dateien zu ein und derselben realen Datei existieren. Die Datei wird zum Lesen geöffnet, wenn als Öffnungsmodus "r", zum Schreiben, wenn "w" mitgegeben wird. Außerdem gibt es noch die Öffnungsmodi:

"a" Schreiben ans Ende der Datei ( append-Modus )

"r+" Lesen und Schreiben

"w+" Schreiben und Lesen

"a+" Schreiben ans Ende der Datei und Lesen Wird "w" oder "w+" verlangt, so wird die Datei angelegt, falls sie nicht vorhanden ist; existiert sie bereits, geht der bisherige Inhalt (nicht die Datei) verloren. Wird "a" oder "a+" angegeben, so wird die Datei zum Schreiben an das Dateiende geöffnet, sofern sie schon existiert; sie wird angelegt, wenn sie noch nicht existiert. Unter UNIX bzw. MSDOS wird der Datei-Offset auf die Dateilänge gesetzt. Bei noch nicht vorhandener Datei ist die Wirkung dieselbe wie bei "w" bzw. "w+". Bei bereits existierender Datei aber wird der bisherige Inhalt nicht zerstört. Bei "a+" wird wiederum zugleich zum Lesen geöffnet. Mit "r" und "r+" wird zum Lesen eröffnet, bei "r+" auch zum Schreiben. Eventuell vorhandener Inhalt geht nicht verloren. Die Funktion fopen meldet einen Fehler-(durch Rückgabe des Zeigers NULL),wenn sie die Datei nicht öffnen kann. Unter UNIX: Bei den "w" und "a"-Modi, wenn sie (d.h. die effektive User-ld des sie ausführenden Prozesses) keine "w"-Berechtigung im Katalog hat, falls die Datei noch nicht existiert, oder wenn sie keine "w"-Berechtigung für die Datei hat. Bei den "r"-Modi muss die Datei existieren und sie muss die "r"-Berechtigung für die User-Id des Prozesses haben.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 98 D. Hägele, Stand 30.09.2015

MSDOS: Unter MSDOS existieren die Zusätze b und t zum Öffnungsmodus:

b binary mode

t translate mode (kein ANSI-Standard) Die Zeichen b oder t werden nach oder vor dem +-Zeichen an den r- oder w-Modus angehängt:

fopen(name , "wb+") fopen (name , "rb+")

fopen (name , "w+b") fopen (name , "r+b") Bedeutung : Der Zusatz b bewirkt, dass alle Zeichen ungeändert gelesen bzw. geschrieben werden. Bei einem ASCII-File wird also das newline nicht in linefeed+carriage-return gewandelt. Das t hat das Umgekehrte zur Folge: Das File wird als Textfile gelesen, die Wandlungen newline <-> linefeed+carriage-return erfolgt. Beispiel 38:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

FILE *fp;

fp = fopen("datx" , "w" );

/* Die Deklaration von fopen() ist in stdio.h enthalten. */

if (fp == (FILE *)NULL )

{

printf("Keine Datei datx erhalten\n");

exit(1);

}

/* Der Zeigerwert NULL ist nur die einfache binaere */

/* Null, evtl. mit cast auf "Zeiger auf char". */

/* Die Definition steht in stdio.h */

/* Die Bibliotheksfunktion exit beendet den Prozess */

/* Das Argument ist der aus der Shell bekannte */

/* exit-status, der dem parent-Prozess uebergeben wird */

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 99 D. Hägele, Stand 30.09.2015

Die Funktion fclose()

int fclose(fp)

FILE *fp;

/* Return: 0 oder –1 bei Fehler */

Mit dem Aufruf von fclose wird das Objekt, auf das fp zeigt "released", d.h. es steht für eine andere Datei zur Verfügung. Fehler wird nur zurückgegeben, wenn fp kein durch fopen erhaltener Zeiger ist. Terminiert ein Prozeß, so werden alle von ihm geöffneten realen Dateien geschlossen, alle virtuellen Dateien released.

13.2 Formatierte Ein- / Ausgabe

Mit den Funktionen der formatierten Ein- / Ausgabe wird genauso auf beliebige Dateien geschrieben bzw. von ihnen gelesen wie mit den Funktionen printf() bzw. scanf(), die mit dem Terminal kommunizieren (printf() bzw. scanf() sind lediglich vereinfachte Formen, bei denen der Zeiger auf das FILE-Objekt fehlt, da er standardmäßig auf stdin bzw. stdout gesetzt ist). Die Funktion fprintf()

int fprintf(fp, format, arg1, arg2, .... )

FILE *fp ; /* returnwert von fopen() */

char *format ;

/* arg1, arg2: Argumente zu Formatsymbolen in format */

/* Return: Anzahl der ausgegebenen Items */

Die Funktion fscanf()

int fscanf(fp, format, adr1, adr2, ....)

FILE *fp;

char *format;

/* adr1 , adr2: Adressen zu Formatsymbolen in format */

/* Return : Anzahl der gelesenen Items */

Das einzige, was hinzutritt, ist also der Zeiger auf das FILE-Objekt. Sämtliche Formatdefinitionen zu printf und scanf sind für fprintf und fscanf gültig. Andere gibt es nicht.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 100 D. Hägele, Stand 30.09.2015

Beispiel 39: 1. Programm:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

FILE *fpw;

char *pw = "Al Capone";

if ( ( fpw = fopen ("daty.txt", "w" ) ) == (FILE *)NULL )

{

printf ( "Kein Schreibzugriff auf daty.txt\n" );

exit( 1 );

}

fprintf ( fpw , "Hallo, %s!", pw );

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 101 D. Hägele, Stand 30.09.2015

2. Programm:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

FILE *fpr;

char vc[80];

if ( ( fpr = fopen("daty.txt" , "r") ) == (FILE *)NULL )

{

printf ( "Kein Lesezugriff auf daty.txt\n" );

exit( 1 );

}

/* fscanf(fpr, "%s", vc); liest nur bis zu Leertaste */

fgets( vc, 80, fpr );

fprintf ( stderr, "Folgendes wurde gelesen: %s\n", vc);

}

/* stdin , stdout , stderr sind Zeiger auf FILE, die in */

/* der stdio.h definiert sind. Sie zeigen auf FILE-Objekte, */

/* die die Dateien Terminal-Eingabe, Terminal-Ausgabe und */

/* Terminal-Fehlerausgabe repräsentieren */

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 102 D. Hägele, Stand 30.09.2015

13.3 Zeichenweise Ein- / Ausgabe

Die Funktion

int getc(fp)

FILE *fp;

liest ein Zeichen von der Datei, die repräsentiert wird von dem Objekt, auf das fp zeigt. Sie gibt die -1 zurück, wenn sie einen Fehler erkennt oder wenn sie das Dateiende erreicht hat.

int putc(c , fp)

int c;

FILE *fp;

Die Funktion schreibt das Zeichen, das in der int-Variablen c steht, auf das File, das repräsentiert wird durch das Objekt, auf das fp zeigt. In stdio.h stehen Makros, die dasselbe machen wie die Funktionen getc() und putc(). Ebenso sind getchar() und putchar(c) in Wahrheit keine Funktionen, sondern Makros in stdio.h, die für getc(stdin) bzw. putc(c,stdout) stehen. Die Funktionen fgetc() und fputc(): Es existieren noch Funktionen fgetc(fp) und fput(c,fp), die dasselbe bewirken wie getc(fp) bzw. putc(c,fp), zu denen aber gleichnamige Makros nicht vorhanden sind. Sie werden also immer als Funktionen aufgerufen. Da das keinen Vorteil hat, werden sie kaum benutzt. Übrigens braucht, wer, weshalb auch immer, die Makroversion nicht will, ja nur zwischen dem Namen und der Parameterklammer ein Leerzeichen einzugeben. Ein- / Ausgabe von Zeilen

char *gets(str)

char *str;

Es wird eine "Zeile" von stdin gelesen, d.h. alle eingegebenen Zeichen bis zum ersten newline oder bis zum EOT-Zeichen (CTRL-d).Der Anwender muss hinreichend Speicher ab der Adresse str zur Verfügung stellen. Das newIine wird nicht in den Zielvektor geschrieben, die Nullterminierung wird hinzugefügt.

char *fgets(str, len, fp)

char *str;

int len;

FILE *p;

Von der Datei, auf die fp zeigt, werden alle Zeichen bis zum nächsten newIine (oder bis zum Dateiende) gelesen, jedoch maximal len - 1 Zeichen. Das newline wird mit in den Zielvektor geschrieben, die Nullterminierung wird hinzugefügt. .Von beiden Funktionen wird str zurückgegeben, bei Fehler der NULL-Pointer.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 103 D. Hägele, Stand 30.09.2015

Zur Ausgabe von Zeilen dienen die Funktionen

int puts(str)

char * str;

int fputs(str, fp)

char * str;

FILE * fp;

Es wird jeweils ein Nullterminierter char-Vektor geschrieben, der bei str beginnt, von puts auf stdout, von fputs auf das File, auf das fp zeigt. Im Fehlerfalle erscheint die -1 als Rückgabewert, sonst die 0. Die Funktion puts schließt die Ausgabe durch ein hinzugefügtes newline ab, fputs gibt nur den Inhalt des Vektors aus.

13.4 Ein- / Ausgabe beliebiger Daten

Die bisherigen Ein- / Ausgabe-Funktionen sind für ASCII-Files bestimmt. Für binäre Daten sind sie nicht sinnvoll. Die in diesem Abschnitt beschriebenen Funktionen machen keine Voraussetzungen über die Daten, die zu transferieren sind. Sie führen auch keine Formatwandlungen durch, sondern lesen und schreiben Byteketten spezifizierter Länge.

int fread( buf, size, cnt, fpr )

char *buf;

int size, cnt;

FILE *fpr;

/* liest von der Datei fpr in den Puffer buf, der ein Zeiger

*/

/* auf einen Array von cnt Elementen der Größe size ist */

int fwrite( buf, size, cnt, fpw )

char *buf;

int size, cnt ;

FILE *fpw ;

/* wie oben, nur schreibt fwrite den Inhalt von buf */

/* auf die Datei fpw, statt von ihr zu lesen */

Soll ein Puffer geschrieben oder gelesen werden, der nicht aus char-Objekten besteht, so ist die Adresse buf auf (char *) zu casten. (Wird der cast weggelassen, passiert im Allgemeinen nichts, fread und fwrite funktionieren trotzdem, aber das Programm wäre auf jeden Fall nicht mehr "int-sauber", manche Compiler geben ein warning aus.) Beide Funktionen geben als Returnwert die Zahl der Objekte zurück, die sie geschrieben bzw. gelesen haben. Die Funktion fread gibt bei Dateiende die 0 zurück. Die Rückgabe im Fehlerfalle ist nicht dokumentiert, de facto kommt in der Regel die -1 zurück. Bei fwrite kann die Zahl der tatsächlich geschriebenen Objekte nur dann von cnt differieren, wenn der maximale Dateiumfang (Betriebssystemparameter) erreicht ist. Bei fread differieren cnt und der Returnwert dann, wenn nicht mehr soviel Objekte in der Datei sind wie cnt fordert.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 104 D. Hägele, Stand 30.09.2015

Anmerkung: Unter UNIX und MSDOS wird daran das Dateiende erkannt. Beispiel 40:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

FILE *fpr, *fpw ;

int i, ibufw[10], ibufr[10] ;

for (i = 0; i < 10; i++)

ibufr[i] = 0 ;

for (i = 0; i < 10; i++)

{

printf("Zahl %d = ?\t" , i );

scanf("%d" , ibufw + i);

}

fpw = fopen("intdat.txt" , "w" );

fpr = fopen("intdat.txt" , "r" );

if ( fpw == NULL || fpr == NULL )

{

fprintf(stderr,"Kein intdat.txt\n" );

exit( 1 );

}

fwrite((char *)ibufw , sizeof(int) , 10 , fpw);

fclose(fpw);

/* Die virt. Datei fpw wird gelöscht, ihr Datenpuffer */

/* auf die reale Datei geschrieben, andernfalls könnte */

/* auf diese Daten nicht via fpr zugegriffen werden. */

fread((char *)(ibufr), sizeof(int), 10, fpr);

for(i = 0; i < 10 ; i++)

fprintf(stdout, "Zahl %d = %d\n" , i, ibufr[i] );

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 105 D. Hägele, Stand 30.09.2015

13.5 Wahlfreier Zugriff

Alle Lese- und Schreibvorgänge verliefen bis jetzt rein sequentiell. Es wurden stets vom Anfang der Datei beginnend Byteketten geschrieben oder gelesen. Nach jeder Kommunikation setzte der folgende Funktionsaufruf hinter der Stelle auf, zu der der vorangehende positioniert hatte. Deshalb hatte es bisher auch keinen Sinn, den Öffnungsmodus "w+" zu benutzen: Nach dem Schreiben hätte der folgende Aufruf einer lesenden Funktion, etwa fread, ja nur hinter dem zuletzt Geschriebenen lesen können - und da ist nichts. Anmerkung: Das nimmt stark auf UNIX Bezug. In einem Betriebssystem, das zwei Dateioffsets führt, einen für Schreib- einen für Leseoperationen wäre die eben gemachte Aussage nicht richtig. Mit der Funktion

int fseek(fp, offset , whence)

FILE *fp ;

long int offset;

int whence;

/* Return: im Erfolgsfall die 0 , ungleich 0 sonst */

wird der Dateioffset in der Datei fp verändert. Der Dateioffset wird manchmal auch "Schreib- Lesezeiger" genannt. Diese Bezeichnung wird hier nicht verwendet. Sie ist unsauber, es handelt sich um keinen Zeiger. Bei der Addition um 1 wird der Dateioffset um 1 inkrementiert und nicht nach den Regeln der Zeigerarithmetik. Auch darf der Dateioffset allen arithmetischen Operationen unterworfen werden, was bei einem Zeiger nicht ginge. Der Parameter whence gibt den Bezugspunkt der Veränderung an:

whence = 0 offset wird auf Dateianfang bezogen whence = 1 offset wird auf aktuellen Stand bezogen whence = 2 offset wird auf Dateiende bezogen

Hat whence den Wert 0, so darf offset nicht negativ sein: auch sonst darf durch die Veränderung kein negativer Dateioffset entstehen, d.h., es ist nicht zulässig, vor die Datei zu positionieren.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 106 D. Hägele, Stand 30.09.2015

Die Funktion ftell (fp)

Die Funktion

long int ftell(fp)

FILE *fp ;

gibt den aktuellen Stand des Dateioffsets zurück. Im FehIerfall wird meist –1 returniert, doch ist das im AT & T-Manual nicht dokumentiert.

Warnung: Die Portabilität dieser Funktionen (fseek , ftell) ist unter UNlX generell gewährleistet, jedoch kann es Probleme geben, wenn fseek unter anderen Betriebssystemen angewendet wird. Nicht alle Systeme können vom aktuellen Dateioffset aus "seeken", nicht alle können ihren Dateioffset um Byte-Größen verändern.

Beispiel 41 :

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int ibufr[10], ibufw[10], i;

long int ftell();

for ( i = 0; i < 10 ; i++)

{

printf("Zahl %d = ?\t", i );

scanf("%d" , ibufw + i);

}

if((fprw = fopen("intdat.txt" , "w+" )) == NULL)

{

fprintf(stderr, "Keine indat.txt\n");

exit( 1 );

}

fwrite((char *)ibufw, sizeof(int), 10, fprw);

fseek(fprw, -3L * sizeof(int), 2);

/* long-Konstanten werden mit L oder l gekennzeichnet */

/* oder mit dem cast-Operator (long) versehen */

fread((char *)ibufr, sizeof(int), 3, fprw);

for (i = 0; i < 3; i++)

fprintf(stdout, "Zahl %d = = %d\n", i, ibufr[i]);

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 107 D. Hägele, Stand 30.09.2015

Die Funktion fflush()

Wie schon angedeutet, stehen Daten, die in eine virtuelle Datei geschrieben wurden, auch nur in dieser virtuellen Datei zur Verfügung. Aus einer zweiten, derselben realen Datei zugeordneten virtuellen Datei können sie nicht ausgelesen werden. Der in jeder virtuellen Datei enthaltene Datenpuffer (seine Größe wird in stdio.h definiert und umfaßt meist mindestens einen Block = 1 K Byte) wird nur auf die reale Datei geschrieben, wenn er voll ist, wenn die virtuelle Datei reIeased (fclose) wird oder wenn die Funktion fflush() es erzwingt.

Das Gesagte gilt ohne Einschränkung für die UNIX-Compiler. Zu MSDOS vergleiche die Ausführungen weiter unten.

Beispiel 42:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

FILE *fpw , *fpr;

double xx[10], yy[10];

int i;

for ( i = 0; i < 10 ; i++)

{

printf("Zahl %d = ?\t", i );

scanf("%lf" , xx + i);

}

for(i = 0; i < 10; i++)

yy[i] = 0; /* Initialisierung des Arrays yy */

if ((fpw = fopen("doubledat.txt", "w")) == NULL ||

(fpr = fopen("doubledat.txt", "r")) == NULL )

{

printf("keine doubledat.txt\n");

exit( 1 );

}

fwrite((char *)xx, sizeof(double), 10, fpw);

fflush(fpw);

/* der Puffer der virtuellen Datei wird in die Datei */

/* geschrieben; die Datei fpw bleibt bestehen */

fseek(fpr, 3L * sizeof(double), 2);

fread((char *)yy, sizeof(double), 3, fpr);

for(i = 0; i < 3; i++)

printf("%f\n" , yy[i]);

return0;

}

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 108 D. Hägele, Stand 30.09.2015

Es besteht kein Unterschied zwischen den I/O - Bibliotheksfunktionen unter UNIX und denen des MSDOS-Compilers.

Dennoch würde das obige Beispielprogramm zumindest auf einigen Realisierungen des MSDOS nicht funktionieren. Es kann nötig sein, die Öffnung zum Lesen hinter den fflush bzw. fclose zu setzen. Mit der obigen Sequenz kann es passieren, dass das Programm einen eventuell früheren Inhalt der Datei doubledat liest - obwohl der wegen der w-Öffnung eigentlich gar nicht mehr vorhanden sein dürfte.

Beispiel 43:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(int argc, char *argv[])

{

FILE *fprw;

char buf[30];

int n;

if (( fprw = fopen(argv[1], "w")) == NULL)

{

fprintf(stderr, "Datei %s ohne Schreibzugriff\n", argv[1]);

exit(1);

}

fwrite(argv[2], sizeof(char), strlen(argv[2]), fprw);

fflush(fprw) ;

/* oder fclose(fprw) */

if (( fprw = fopen(argv[1], "r")) == NULL)

{

fprintf(stderr, "Keine Datei %s mit Lesezugrif\n", argv[1]);

exit(1) ;

}

n = fread(buf, sizeof(char) , 30, fprw);

buf[n] = '\0';

fprintf(stdout , "Gelesen: %s\n" , buf);

return 0;

}

Mit der Leseöffnung nach dem fflush (oder fclose) funktioniert das Programm erwartungsgemäß, doch das tut es nicht mehr, sobald der zweite fopen-Aufruf vor den fflush gestellt wird. Eine solche Abhängigkeit ist nicht dokumentiert.

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 109 D. Hägele, Stand 30.09.2015

14. Funktionsfamilien

Es gibt unter C Funktionen, die von der Funktion und von der Syntax her ähnlich sind. Man kann Sie als Funktionsfamilien oder Derivate bezeichnen. Die wichtigsten Familien werden im folgenden kurz vorgestellt. Oft gibt es auch Funktionen, die einmal mit einem Underscore ("_") beginnen und einmal nicht. Hier handelt es sich um unterschiedliche Definitionen derselben Funktion. Nach allgemeiner Übereinkunft sind Funktionen, die mit Underscore beginnen als Makros in den Bibliotheken definiert. Dies kann unter bestimmten Umständen Vorteile haben, führt aber hier zu weit. is. Funktionen, die den Typ eines Zeichens prüfen.

Funktion Beschreibung isalnum alphanumerisches Zeichen

isalpha Buchstabe

isascii ASCII-Zeichen (< 128)

iscntrl Steuerzeichen (ASCII<31 oder =127)

isdigit Zahl

isgraph druckbares Zeichen ohne Leerzeichen

islower Kleinbuchstabe

isprint druckbares Zeichen

ispunct Satzzeichen

isspace Leerzeichen, TAB, NL, CR

isupper Großbuchstabe

isxdigit Hexadezimalzahl

.to. Hier handelt es sich um reine Umwandlungsfunktionen, die verschiedene Datentypen ineinander umwandeln. Dabei können auch Beträge in Zeichenketten in ihren numerischen Wert gewandelt werden.

Funktion Beschreibung atof wandelt string in float

atoi wandelt string in integer

atol wandelt string in long

itoa wandelt integer in string

ltoa wandelt long in string

_tolower, tolower wandelt in Kleinbuchstaben

_toupper, toupper wandelt in Großbuchstaben

ultoa wandelt unsigned long in string

strtod wandelt string in integer

strtol wandelt string in long

strtoul wandelt string in unsigned long

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 110 D. Hägele, Stand 30.09.2015

.get. Unformatierte Eingabefunktionen

Funktion Beschreibung

cgets liest String von Tastatur (console)

fgetc liest Zeichen aus einem Datenstrom (Datei)

fgetchar liest Zeichen aus dem Standard-Datenstrom

fgetpos aktuelle Position im Datenstrom bestimmen

fgets String von Datenstrom lesen

getc liest Zeichen aus einem Datenstrom

getch liest Zeichen von Tastatur

getche liest Zeichen von Tastatur mit Echo

getenv Umgebungsvariable lesen

getpid ID des aufrufenden Prozesses lesen

gets String von Datenstrom lesen

getw Integer von Datenstrom lesen

ungetc schreibt Zeichen wieder auf Datenstrom zurück

ungetch schreibt Zeichen in Tastatur-Buffer zurück

.scanf Formatierte Eingabefunktionen

Funktion Beschreibung

cscanf liest von Tastatur

fscanf liest von Datei

scanf liest von Tastatur (stdin)

sscanf liest aus String

vscanf liest von Tastatur mit Argumentliste

vfscanf liest von Datei mit Argumentliste

vsscanf liest von Buffer mit Argumentliste

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 111 D. Hägele, Stand 30.09.2015

.put. Unformatierte Ausgabefunktionen

Funktion Beschreibung

cputs schreibt string auf Bildschirm (console)

fputc schreibt Zeichen auf Datenstrom

fputs schreibt String auf Datenstrom

fputchar schreibt Zeichen auf Standard-Ausgabegerät (Bildschirm)

fputs schreibt String in Datei

putc schreibt Zeichen auf Datenstrom

putch schreibt Zeichen auf Bildschirm

putchar schreibt Zeichen auf Datenstrom

putenv schreibt Umgebungsvariable

puts schreibt String auf Datenstrom

putw schreibt Wort auf Datenstrom

.printf Formatierte Ausgabefunktionen

Funktion Beschreibung

cprintf Ausgabe auf Bildschirm (console)

fprintf Ausgabe in Datei (Datenstrom)

printf Ausgabe auf Bildschirm (stdout)

sprintf schreibt in eine Zeichenkette

vfprintf schreibt in Datei mit Argumentliste

vprintf schreibt auf Bildschirm mit Argumentliste

vsprintf schreibt in Zeichenkette mit Argumentliste

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 112 D. Hägele, Stand 30.09.2015

_bios_... (nicht in allen C-Dialekten verfügbar) Diese Familie ruft das BIOS (Basic Input Output System) direkt auf. Diese Funktionen setzen Interrupts direkt an den Prozessor ab.

Funktion Beschreibung

_bios_disk wählt Laufwerk

_bios_equiplist prüft vorhandene Geräte

_bios_keybrd greift auf Tastatur-Dienst zu (Status der Tasten etc.)

_bios_memsize gibt Speichergröße zurück

_bios_printer Drucker-Dienste (Schnittstelle etc.)

_bios_serialcom Dienste der seriellen Schnittstelle

_bios_timeofday schreibt oder liest den Wert der Systemuhr

_dos_... (nicht in allen C-Dialekten verfügbar) Funktionen, die das Betriebssystem aufrufen.

Funktion Beschreibung

_dos_allocmem ordnet einen Speicherblock zu

_dos_close schließt offene Datei

_dos_creat erzeugt eine neue Datei (Inhalt einer bestehenden Datei wird gelöscht)

_dos_creatnew erzeugt eine neue Datei (Fehler, falls Datei bereits besteht)

_dos_findfirst sucht ersten passenden Dateinamen

_dos_findnext sucht nächsten Dateinamen

_dos_freemem löst die Zuordnung von _dos_allocmem wieder

_dos_getdate aktuelles Systemdatum holen

_dos_getdiskfree freien Speicher auf Laufwerk bestimmen

_dos_getdrive aktuellen Laufwerksnamen holen

_dos_getfileattr Dateiattribute einer Datei holen

_dos_getftime Datum und Zeit des letzten Dateizugriffs

_dos_gettime aktuelle Systemzeit holen

_dos_getvect aktuellen Wert des Interrupt-Vektors holen

_dos_keep TSR (Terminate and Stay Resident) installieren

_dos_open Datei öffnen

_dos_read Datei lesen

_dos_setblock Segmentgröße ändern

_dos_setdate aktuelles Systemdatum setzen

_dos_setdrive aktuelles Laufwerk ändern

_dos_setfileattr Datei-Attribute ändern

_dos_setftime Datum und Zeit einer Datei ändern

_dos_settime aktuelle Systemzeit setzen

_dos_setvect Interrupt-Vektor setzen

_dos_write in Datei schreiben

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 113 D. Hägele, Stand 30.09.2015

.alloc Funktionen zur Speicherverwaltung

Funktion Beschreibung

calloc für Arrays mit n Datenfeldern

halloc für Arrays vom Typ huge

malloc reserviert Hauptspeicher

_fmalloc für large

_nmalloc für small

realloc ändert Größe von reserviertem Speicherplatz

exec. Führen andere Programme aus.

Funktion Beschreibung

execl

execle Funktionen unterscheiden sich nur durch

execlp Art und Anzahl der Übergabeparameter

execlpe

execv

execve

execvp

execvpe

f. Dateien (Datenströme) verwalten

Funktion Beschreibung

fclose schließt Datei

fcloseall schließt alle offenen Dateien

feof Ende einer Datei erkennen

ferror Fehler bei Dateifunktionen erkennen

fflush Datenstrom leeren

fopen öffnet Datei

fread liest Daten vom Strom

fseek Position in einer Datei verschieben

fsetpos Position in einer Datei setzen

fstat Info über offene Datei

ftell gibt aktuellen Datei-Zeiger zurück

ftime aktuelle Zeit lesen

fwrite in Datei schreiben

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 114 D. Hägele, Stand 30.09.2015

str. Funktionen zur Behandlung von Zeichenketten

Funktion Beschreibung

strcat Zeichenfolge anhängen

strchr erstes Zeichen in Zeichenfolge finden

strcmp vergleichen

strcpy kopieren

strcspn Zeichenfolge in Zeichenfolge finden

strdup Zeichenfolge duplizieren und Speicher zuweisen

stricmp Zeichenfolge ohne groß/klein vergleichen

strcmpi Zeichenfolge ohne groß/klein vergleichen

strlen Länge einer Zeichenkette

strlwr in Kleinbuchstaben wandeln

strncat Teil einer Zeichenfolge anhängen

strncmp Teil vergleichen

strncpy Teil kopieren

strnicmp Teil ohne groß/klein vergleichen

strncmpi Teil ohne groß/klein vergleichen

strnset Teil initialisieren

strpbrk sucht nach einem Vorgabezeichen

strrchr letztes Zeichen in Zeichenfolge finden

strrev Zeichenfolge umkehren

strset Zeichenfolge initialisieren

strspn erste Zeichenfolge in Zeichenfolge finden

strstr Zeichenfolge in Zeichenfolge finden

strtok Grundsymbol finden

strupr in Großbuchstaben wandeln

Hochschule Heilbronn Technik ● Wirtschaft ● Informatik Heilbronn University

Fakultät Mechanik und Elektronik Studiengang Mechatronik und Mikrosystemtechnik

Programmierung C 115 D. Hägele, Stand 30.09.2015

Mathematische Funktionen

Funktion Beschreibung

abs() Absolutbetrag berechnen

acos() Arkuskosinus berechnen

asin() Arkussinus berechnen

atan() Arkustangens berechnen

atan2() Arkustangens für y/x berechnen

cabs() Absolutbetrag einer komplexen Zahl berechnen

ceil() Aufrundung eines Wertes auf Ganzzahl

cos() Kosinus berechnen

cosh() Kosinus hyperbolicus berechnen

div() Quotienten und Rest zweier Ganzzahlwerte

berechnen

exp() Exponentialwert berechnen

fabs() Gleitkomma-Absolutbetrag berechnen

fmod() Berechne Gleitkomma-Rest

frexp() Mantisse und Exponenten der Gleitkommazahl holen

hypot() Hypotenuse berechnen

labs() Absolutbetrag einer long-Ganzzahl berechnen

ldexp() Mantisse und Exponenten zu Gleitkomma

umwandeln

ldiv() Quotienten und Rest für long-Ganzzahl berechnen

log() Natürlichen Logarithmus berechnen

log10() Logarithmus zur Basis 10 berechnen

max() Größeren Wert ergeben

min() Kleineren Wert ergeben

modf() Gleitkommawert in Mantisse und Exponenten teilen

pow() x hoch y berechnen

raise() Signal zum ausführenden Programm senden

rand() Pseudo-Zufallszahl schaffen

sin() Sinus berechnen

sinh() Sinus hyperbolicus berechnen

sqrt() Quadratwurzel ziehen

srand() Zufälligen Anfangspunkt bestimmen

tan() Tangens berechnen

tanh() Tangens hyperbolicus berechnen