Modulare Programmierung in C und in Better-C

Ähnliche Dokumente
Teil IV : Abstrakte Datentypen (ADT)

ALGOL 68 im Aspekt einer modernen Programmiersprache???

Modulare Programmierung und Bibliotheken

Repetitorium Informatik (Java)

Programmierung mit C Zeiger

PIWIN I. Praktische Informatik für Wirtschaftsmathematiker, Ingenieure und Naturwissenschaftler I. Vorlesung 3 SWS WS 2007/2008

Informationsverarbeitung im Bauwesen

Anwendungsentwicklung mit Java. Grundlagen der OOP, Vererbung, Schnittstellen, Polymorphie

Deklarationen in C. Prof. Dr. Margarita Esponda

Grundlagen der Informatik. Prof. Dr. Stefan Enderle NTA Isny

Softwaretechnik (Allgemeine Informatik) Überblick

Pass by Value Pass by Reference Defaults, Overloading, variable Parameteranzahl

Interaktionen zwischen Objekten durch Senden von Nachrichten und Reagieren auf empfangene Nachrichten

Eine Einführung in C-Funktionen

Ziel, Inhalt. Programmieren in C++ Wir lernen wie man Funktionen oder Klassen einmal schreibt, so dass sie für verschiedene Datentypen verwendbar sind

Advanced Programming in C

Einführung in die C-Programmierung

1.3 Geschichte der Programmiersprachen

Unterprogramme. Funktionen. Bedeutung von Funktionen in C++ Definition einer Funktion. Definition einer Prozedur

Übungspaket 23 Mehrdimensionale Arrays

Die Bedeutung abstrakter Datentypen in der objektorientierten Programmierung. Klaus Kusche, September 2014

Objektorientierte Programmierung

Objektorientierte Programmierung

Objekt-basiert oder objekt-orientiert? Moderne Low Level Treiberprogrammierung mit C/C++ Programmier-Paradigmen Programmiersprachen C und C++

Vorkurs C++ Programmierung

Übung 9. Quellcode Strukturieren Rekursive Datenstrukturen Uebung 9

Inhaltsverzeichnis. Vorwort...XIII. Aufbau des Buches...

C++ Templates - eine kleine Einführung. Allgemein. Funktionstemplates. Allgemein. Funktionstemplates. Klassentemplates

Grundlagen der Informatik für Ingenieure I

Kapitel 9: Klassen und höhere Datentypen. Klassen und höhere. Objekte, Felder, Methoden. Küchlin/Weber: Einführung in die Informatik

C allgemein. C wurde unter und für Unix entwickelt. Vorläufer sind BCPL und B.

Erste Schritte der Programmierung in C

Die Programmiersprache C99: Zusammenfassung

Knasmüller.book Seite vii Mittwoch, 28. März : vii. Inhaltsverzeichnis

Algorithmen & Datenstrukturen

SS / 16 schrittweise Verfeinerung -> Wirth, 1971, Programm Development by Stepwise Refinement

Fakultät Angewandte Informatik Lehrprofessur für Informatik

Software-Engineering

2. Programmierung in C

Klausur C-Programmierung / / Klingebiel / 60 Minuten / 60 Punkte

Angewandte Mathematik und Programmierung

Inhaltsverzeichnis. Einführende Bemerkungen 11. Das Fach Informatik 11 Zielsetzung der Vorlesung Grundbegriffe

II. Grundlagen der Programmierung. 9. Datenstrukturen. Daten zusammenfassen. In Java (Forts.): In Java:

Vorlesung Datenstrukturen

Theorie zu Übung 8 Implementierung in Java

Programmieren in C. Macros, Funktionen und modulare Programmstruktur. Prof. Dr. Nikolaus Wulff

Teil V. Generics und Kollektionen in Java

Algorithmen und Programmierung II

Grundlagen. Kapitel 1

C für Java-Programmierer

Algorithmen und Datenstrukturen (für ET/IT)

C# im Vergleich zu Java

Einführung in die Informatik

6 Speicherorganisation

TEIL I: OBJEKTORIENTIERUNG UND GRUNDKURS JAVA GRUNDLAGEN DER PROGRAMMIERUNG... 4

Objektorientierte Programmierung mit Java. Grundlagen Übersicht. Programmiersprachen

Praxisorientierte Einführung in C++ Lektion: "Die Compiler-Chain (Vom Quellcode zum ausführbaren Programm)"

Grundlagen von Python

Integer Integer Integer (Voreinstellung) Integer Gleitkomma Gleitkomma leer/unbestimmt Integer ohne Vorzeichen Integer (explizit) mit Vorzeichen

Dr. Monika Meiler. Inhalt

Einführung in C++ mit Microsoft VS

Hochschule Darmstadt Informatik-Praktikum (INF 1) WS 2015/2016 Wirtschaftsingenieur Bachelor 5. Aufgabe Datenstruktur, Dateieingabe und -ausgabe

Inhaltsüberblick. I. Grundbegriffe - Objekte und Klassen. Organisatorisches. I. Grundbegriffe - Objektorientierte Konzepte

Projektverwaltung Problem Lösung: Modulare Programmierung

Informatik 11 Kapitel 2 - Rekursive Datenstrukturen

Modellierung und Programmierung 1

Java Einführung Methoden in Klassen

8. Generics Grundlagen der Programmierung 1 (Java)

1. Vom Sourcecode zum Programm

Programmieren II Klassen. Programmieren II Klassen. Programmieren II Klassen. Programmieren II Klassen. Zur Erinnerung.

Zusammengesetzte Datentypen -- Arrays und Strukturen

Vorlesung Informatik I

7. Schnittstellen Grundlagen zu Schnittstellen. 7. Schnittstellen

Praktikum Betriebssysteme 1. Aufgabe (1)

4.Grundsätzliche Programmentwicklungsmethoden

Übungspaket 29 Dynamische Speicherverwaltung: malloc() und free()

RO-Tutorien 3 / 6 / 12

Einführung in die Programmierung

J.5 Die Java Virtual Machine

Einführung in die Informatik für Hörer aller Fakultäten II. Andreas Podelski Stephan Diehl Uwe Waldmann

Vererbung & Schnittstellen in C#

Dr. Monika Meiler. Inhalt

Objektorientiertes Programmieren für Ingenieure

Präsentation Interfaces

Zählen von Objekten einer bestimmten Klasse

5.4 Klassen und Objekte

Hochschule Darmstadt Informatik-Praktikum (INF 1) WS 2014/2015 Wirtschaftsingenieur Bachelor 4. Aufgabe Datenstruktur, Dateieingabe und -ausgabe

Willkommen zur Vorlesung. Objektorientierte Programmierung Vertiefung - Java

Zusammenfassung des Handzettels für Programmieren in C

Methoden. von Objekten definiert werden, Methoden,, Zugriffsmethoden und Read-Only

Faustregeln zu Zusicherungen

Programmiersprachen. Organisation und Einführung. Berthold Hoffmann. Studiengang Informatik Universität Bremen

Klassen und Objekte. Einführung in Java. Folie 1 von Mai Ivo Kronenberg

Einführung in die. objektorientierte Programmierung

VBA-Programmierung: Zusammenfassung

zu große Programme (Bildschirmseite!) zerlegen in (weitgehend) unabhängige Einheiten: Unterprogramme

C++ - Lernen und professionell anwenden

Transkript:

Kapitel 3 Modulare Programmierung in C und in Better-C... the art of programming is the art of organising complexity, of mastering multitude and avoiding its bastard chaos as effectively as possible. Edsger W. Dijkstra, Structured Programming Ein Modul ist eine Sammlung von Algorithmen und Datenstrukturen zur Bearbeitung einer in sich abgeschlossenen Aufgabe. Die Verwendung des Moduls (d.h. seine Integration in ein Programmsystem) erfordert keine Kenntnis seines inneren Aufbaus und der konkreten Realisierung der gekapselten Algorithmen und Datenstrukturen, und seine Korrektheit ist ohne Kenntnis seiner Einbettung in ein bestimmtes Programmsystem nachprüfbar. G. Pomberger, Informatik-Handbuch 3.1 Module und Modulschnittstellen................... 192 3.2 Fallstudie 1: Komplexe Arithmetik.................. 200 3.3 Fallstudie 2: Strings........................... 203 3.4 Fallstudie 3: Set (Menge)....................... 207 3.5 Fallstudie 4: Generische Sortierte Liste............... 221 3.6 Better-C: Namensbereiche....................... 235 3.7 Übungen.................................. 244

192 3 Modulare Programmierung in C und in Better-C Modularisierung in C und in Better-C Die Vorgehensweisen beim Entwickeln von Programm-Modulen sind die gleichen in C und in C++, sie basieren auf der Verwendung von H-Dateien und C- bzw. CPP- Dateien. Alle C-Texte in den Abschn. 3.1 bis 3.5 entsprechen sowohl dem C90- und dem C99-Standard als auch dem C++98-Standard. Im Abschn. 3.6 wird dann das Sprachmittel namespace eingeführt, das der Vermeidung von Namenskollisionen dient, das aber leider nur von C++ unterstützt wird. Das C++-Sprachmittel namespace ist völlig unabhängig von den C++-Sprachmitteln zur Untersrützung der objektorientierten und der generischen Programmierung und es wird hier deshalb als typischer Better-C-Bestandteil angesehen. Im Abschn. 3.6 wird dann auch gezeigt, wie man C-Module sehr einfach in entsprechende C++-Module mit Namensbereichen konvertieren kann. 3.1 Module und Modulschnittstellen 3.1.1 Softwareentwurf und Modularisierung Jeder erfahrene Softwareentwickler weiß, dass die Beherrschung der Komplexität das Hauptproblem bei der Entwicklung großer Softwaresysteme ist. Unter dem Stichwort Strukturierte Programmierung ist das Thema in den siebziger Jahren gründlich behandelt worden, die Ergebnisse haben sich in Sprachen wie z.b. Pascal, MODULA-2 und Ada niedergeschagen. Auf der unteren Abstraktionsebene (Programmierung im Kleinen) geht es dabei um Steueranweisungen (Ablaufstrukturen, Kontrollstrukturen) sowie um Funktionale Abstraktion in Form von Funktionen und Prozeduren und um Datenabstraktion in Form von Datenstrukturen. Auf einer höheren Abstraktionsebene (Programmierung im Großen) ist das wesentliche Thema die Gestaltung und Anordnung von Modulen. MODULA-2 und Ada unterstützen mit ihrem Modul- bzw. Package- Konzept dieses Programmierparadigma, das dann später von anderen Sprachen wie z.b. von Oberon und Java übernommen wurde. Die Grundidee der Modularen Programmierung besteht darin, oberhalb der Funktionalen Abstraktion und der Datenabstraktion Programmteile meist Module oder Packages genannt zu definieren, die im Wesentlichen Definitionen von Daten und Funktionen (bzw. Prozeduren) zu einer Programmeinheit zusammenfassen. Die Programmiersprache C unterstützt nicht direkt logische Konzepte der Modularen Programmierung, aber es unterstützt die Möglichkeit, Programme in Übersetzungseinheiten (compilation units) in Form von Dateien zu unterteilen und ermöglicht es, Beziehungen zwischen Daten und Funktionen über Dateigrenzen hinweg zu definieren. Auf dieser Basis ist es möglich, Modulkonzepte, wie sie in MODULA-2, Oberon und Java verankert sind, mit gewissen Einschränkungen zu simulieren. Die sprachlichen Grundlagen für das Arbeiten mit Modulen in C und einige kleine Beispiele sind im Abschn. 1.10.6 beschrieben.

3.1 Module und Modulschnittstellen 193 3.1.2 Geheimnisprinzip, Datenkapselung, Abstrakte Datentypen Ein wichtiger Leitgedanke beim Softwareentwurf ist das Geheimnisprinzip (Information Hiding). Um nicht in Komplexität zu ersticken, werden z.b. in Headerdateien Schnittstellen in Form von Funktionsprototypen definiert, der Anwender benutzt die Schnittstellen und interessiert sich im Allgemeinen nicht für die Details der Realisierung, die deshalb in Implementationsdateien versteckt werden. Eine spezielle aber ganz besonders wichtige Art, das Geheimnisprinzip umzusetzen, ist die Datenkapselung. Typisches Beispiel hierfür ist eine Liste, die irgendwelche Einträge z.b. Adressen verwaltet. Die Realisierung der Liste in Form einer speziellen Datenstruktur wird dem Benutzer verborgen, die Liste wird gekapselt durch Zugriffsfunktionen, die die Schnittstelle zu der Liste definieren. Die Datenkapselung hat zwei wichtige Vorteile: sie ermöglicht eine spätere Änderung der verborgenen Datenstruktur ohne Einfluss auf die Funktionalität der Schnittstelle, und sie hindert den Benutzer der Liste, Programmierfehler bei der direkten Benutzung der Daten zu machen. Die Datenkapselung und der Umgang mit Datenkapseln z.b. in Form von Klassen ist das Basisthema der Objektorientierten Programmierung. Es gibt verschiedene Vorgehensweisen und Kriterien bei der Modularisierung von Software. Es kann z.b. sinnvoll sein, Konstanten-, Variablen- und Typdefinitionen jeweils separat oder insgesamt in Modulen zusammenzufassen oder auch Gruppen logisch zusammengehöriger Funktionen. Beispiele dafür sind die meisten C- Bibliotheksmodule. Eine besonders interessante und viel verwendete Struktur bei der Gestaltung von Modulen ist der sog. Abstrakte Datentyp (ADT), wie er z.b. zur Realisierung von Datencontainern in Containerbibliotheken verwendet wird. Solche Datencontainer sind Strukturen wie z.b. Warteschlange (Queue), Stapelspeicher (Stack), Prioritätswarteschlange, Menge (Set), Tasche (Bag), geordnete Liste, Assoziativspeicher (Map), Array (Vektor) und andere. Bild 3.1 skizziert die Idee des ADT am Beispiel eines Stapelspeichers (Stack). Im Zentrum befinden sich die Daten, repräsentiert durch einen Typ Stapel. Der Stapel wird umhüllt von Funktionen für das Arbeiten mit Daten vom Typ Stapel, nur über sie kann auf entsprechende Daten zugegriffen werden, und auch die Struktur des Typs Daten ist dem Benutzer verborgen, ebenso wie z.b. die Struktur eines Typs float dem Anwender verborgen ist. Die Struktur eines ADT wird manchmal mit einem gekochten Ei verglichen, das Eigelb entspricht den Daten, das Eiweiß den Zugriffsfunktionen. 3.1.3 Realisierung von Abstrakten Datentypen in C Die Sprache C bietet explizit keine Unterstützung zur Realisierung Abstrakter Datentypen, insbesondere fehlt leider die Möglichkeit, Daten zu privatisieren, d.h. nach außen vor direktem Zugriff zu schützen. Die folgenden Beispiele demonstrieren die

194 3 Modulare Programmierung in C und in Better-C Bild 3.1: Abstrakter Datentyp Stapelspeicher Realisierung des im vorigen Abschnitt skizzierten Stapelspeichers als ADT. Der nachstehende Code zeigt die Typedefinitionen und die Prototypen für den ADT Stapel, entsprechend ließe sich die H-Datei aufbauen. Das hat, wie gesagt, den Nachteil, dass die Datenstruktur an der Benutzerschnittstelle sichtbar und direkt zugreifbar wird: typedef struct { int* data; int top, size; } Header; typedef Header* Stapel; Stapel Init (int Size); void Push (Stapel s, int x); int Pop (Stapel s); int isempty (Stapel s); void Dispose (Stapel s); Wie schon oben hervorgehoben, kann ein Zugriff des Benutzers auf die Daten unter Umgehung der Zugriffsfunktionen in C nicht völlig verhindert werden, aber es gibt immerhin die Möglichkeit, die Datenstruktur in die C-Datei zu verlagern, wie es durch die beiden C-Texte 3.1 und 3.2 demonstriert wird. In der H-Datei steht als Vorwärts-Deklaration die unvollständige Definition des Zeigers Stapel, die Definition der zugehörigen Struktur steht in der C-Datei.

3.1 Module und Modulschnittstellen 195 C-Text 3.1: Modul Stapel, H-Datei (./bsp3/stack/stapel.h) #ifndef _Stapel_h #define _Stapel_h typedef struct header* Stapel; Stapel Init (int Size); void Push (Stapel s, int x); int Pop (Stapel s); int isempty (Stapel s); void Dispose (Stapel s); #endif #include<stdlib.h> #include"stapel.h" C-Text 3.2: Modul Stapel, C-Datei (./bsp3/stack/stapel.sht) typedef struct header { int* data; int top, size; } Header; Stapel Init (int Size) {... } void Push (Stapel s, int x) {... } int Pop (Stapel s) {... } int isempty (Stapel s) {... } void Dispose (Stapel s) {... } Diese Form ist C-Standard-konform, sie ist vorgesehen worden, um insbesondere die Definition rekursiver Datenstrukturen zu unterstützen. Der C-Text 3.3 skizziert die Benutzung des Moduls. #include<stdio.h> #include"stapel.h" C-Text 3.3: Modul Stapel, Anwendung (./bsp3/stack/demo.c) int main() { Stapel s1 = Init(10), s2 = Init(100), s3 = Init(50); int i; for (i=0; i<10; i++) Push(s1, i); /* printf("%d", s1->data[3]); Syntaxfehler, hier kein Zugriff!!! */ while (! isempty(s1) ) printf("%d ", Pop(s1)); /*... */ Dispose(s1); Dispose(s2); Dispose(s3); return 0; }

196 3 Modulare Programmierung in C und in Better-C Der Zeiger Stapel kann erst hinter der vollständigen Definition der entsprechenden Struktur dereferenziert werden; damit wird also zweierlei erreicht: Zum einen ist die Datenstruktur an der Benutzerschnittstelle, der H- Datei, nicht explizit sichtbar, und zum anderen ist ein direkter Zugriff auf die Daten bei der Verwendung des Moduls über die H-Datei, wie in C-Text 3.3, nicht möglich. 3.1.4 Realisierung von generischen Modulen in C Der im vorigen Abschnitt beschriebene ADT Stack hat einen ganz entscheidenden Nachteil, der seine Benutzbarkeit minimiert: er kann nur Datenelemente vom Typ int verwalten. Wünschenswert hingegen wäre ein generischer ADT Stapel, der Elemente beliebigen Typs verwalten könnte (wobei die Typen der Elemente innerhalb eines Stapels dann aber untereinander gleich sein sollten, d.h. er wäre homogen gefüllt). In C gibt es kein Sprachelement, das Generizität explizit unterstützt, aber auch hier können wir uns helfen, indem wir Generizität durch das Verwenden von void*-zeigern simulieren. Dafür muss allerdings ein hoher Preis gezahlt werden, weil die statische Typüberprüfung durch den Compiler umgangen wird. Der C-Text 3.4 zeigt die Headerdatei eines entsprechenden generischen ADT. Bei Push wird die Anfangsadresse des Datenelements übergeben, die Größe des Datenelements in Byte wird bei der Initialisierung über Init im Feld ItemSize abgelegt, und das übergebene Element wird intern byteweise in den Stapel kopiert. Entsprechend gibt Pop die Adresse des entsprechenden Datenelements zurück, die dann in einen benutzerdefinierten Zeigertyp konvertiert werden kann. #ifndef _GStapel2_h #define _GStapel2_h C-Text 3.4: Modul GStapel, H-Datei (./bsp3/stack/gstapel.h) typedef struct header* Stapel; Stapel Init (int ItSize, int MaxIt); void Push (Stapel s, void* x); void* Pop (Stapel s); int isempty (Stapel s); void Dispose (Stapel s); #endif Der C-Text 3.5 skizziert die Implementierung des generischen ADT, weitere Details sind an dieser Stelle nicht relevant, verschiedene Fallstudien in den folgenden Abschnitten beleuchten auch entsprechende Realisierungen.

3.1 Module und Modulschnittstellen 197 #include<string.h> #include<stdlib.h> #include"gstapel.h" C-Text 3.5: Modul GStapel, C-Datei (./bsp3/stack/gstapel.sht) typedef struct header{ void* data; int ItemSize, top, MaxItems; } Header; Stapel Init (int ItSize, int MaxIt) {... } void Push (Stapel s, void* x) {... } void* Pop (Stapel s) {... } int isempty (Stapel s) {... } void Dispose (Stapel s) {... } Schließlich demonstriert der C-Text 3.6 die Anwendung des generischen ADT. Bei Initialisieren von s1 wird die Größe des Elementtyps übergeben und im Feld s1->itemsize abgelegt. Bei der Funktion Push wird die Adresse des zu verwaltenden Elements übergeben, und von dort werden s1->itemsize Bytes in den internen Puffer kopiert. #include<stdio.h> #include"gstapel.h" C-Text 3.6: Modul GStapel, Anwendung (./bsp3/stack/gdemo.c) int main(void) { Stapel s1 = Init(sizeof(int), 10); /* Stapel s2 = Init(sizeof(somewhat), 100), s3 = Init(sizeof(xyz), 50); */ int i, *px; for (i=0; i<10; i++) Push(s1, &i); while (! isempty(s1) ) { px = (int*)pop(s1); printf("%d ", *px); } /*... */ } Dispose(s1); /* Dispose(s2); Dispose(s3); */ return 0; Der so realisierte generische ADT hat eine hohe Anwendungsbandbreite, aber er ist wie schon gesagt leider auch fehleranfällig, weil der Compiler beim Aufruf von Push und Pop nicht die Möglichkeit der Typüberprüfung hat; void*-zeiger sind mit allen Zeigertypen kompatibel. Hier gibt es zwei Möglichkeiten, diesen Nachteil zu umgehen: