Softwaretechnik in C und C++ - Das Kompendium - Beck-Shop

Werbung
Softwaretechnik in C und C++ - Das Kompendium
Modulare, objektorientierte und generische Programmierung
Bearbeitet von
Rolf Isernhagen
3. Auflage 2001. Buch. XXVI, 994 S. Hardcover
ISBN 978 3 446 21726 3
Format (B x L): 18,2 x 24,5 cm
Gewicht: 1759 g
Zu Inhaltsverzeichnis
schnell und portofrei erhältlich bei
Die Online-Fachbuchhandlung beck-shop.de ist spezialisiert auf Fachbücher, insbesondere Recht, Steuern und Wirtschaft.
Im Sortiment finden Sie alle Medien (Bücher, Zeitschriften, CDs, eBooks, etc.) aller Verlage. Ergänzt wird das Programm
durch Services wie Neuerscheinungsdienst oder Zusammenstellungen von Büchern zu Sonderpreisen. Der Shop führt mehr
als 8 Millionen Produkte.
Kapitel 1
ANSI/ISO-C, Clean-C und
Better-C
I think C has a lot of features that are very important. The way C handles pointers,
for example, was a brilliant innovation; ... I do like C as a language, especially
because it blends in with the operating system (if you’re using UNIX, for example).
Donald E. Knuth, 1993, CLB Interview
The very features of C that make it an unsuitable candidate for human consumption
– lack of structure, weak typing, ... – strengthen its appeal as a “universal assembly
language” ...
Bertrand Meyer, 1998, JOOP
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
1.10
1.11
1.12
1.13
1.14
1.15
1.16
Entwicklungsgeschichte: Von K&R-C nach C++
Clean-C und Better-C . . . . . . . . . . . . . . .
Neun Beispiele . . . . . . . . . . . . . . . . . . .
Aufbau der Sprache . . . . . . . . . . . . . . . . .
Daten, Operatoren, Ausdrücke, Anweisungen .
Steueranweisungen . . . . . . . . . . . . . . . . .
Funktionen . . . . . . . . . . . . . . . . . . . . . .
Datenstrukturen, Werte- und Zeigersemantik .
Eingabe und Ausgabe von Daten . . . . . . . .
Programmstruktur und Speicherklassen . . . .
Zeiger auf Funktionen . . . . . . . . . . . . . . .
Präprozessor . . . . . . . . . . . . . . . . . . . . .
Speicherverwaltung, Speichermodelle
. . . . .
Fehlerbehandlung/Ausnahmebehandlung . . .
Übersicht über den neuen Standard C99 . . . .
Übungen . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
6
7
17
21
44
54
67
110
128
148
152
158
160
163
167
2
1
ANSI/ISO-C, Clean-C und Better-C
Inzwischen gibt es zwei C-Standards. Der alte Standard, kurz C89, stammt aus dem
Jahre 1989, der neue, kurz C99, aus dem Jahre 1999. Der neue Standard ist eine
Erweiterung des alten, er hat bisher keine breite Unterstützung bei den CompilerHerstellern gefunden. Ein Grund dafür ist sicher die Tatsache, dass viele der Erweiterungen nicht C++-konform sind. In den folgenden Abschnitten 1.1 bis 1.14 wird C89
zu Grunde gelegt. Alle dort aufgeführten C-Texte entsprechen dem alten und dem
neuen Standard! Die Erweiterungen von C99 gegenüber C89 werden im Abschnitt
1.15 vorgestellt.
1.1
Entwicklungsgeschichte: Von K&R-C nach C++
C ist eine gewachsene Sprache, die sich über einen langen Zeitraum zum heutigen
ANSI/ISO-C entwickelt hat. Sie ist im Vergleich zu einigen anderen Sprachen wie
z.B. Pascal, Ada, Modula-2 oder Oberon nicht besonders systematisch aufgebaut und
teilweise recht kryptisch, aber sehr verbreitet in der Industrie und auch im Hochschulbereich. C reicht in etwa von der Assembler-Ebene bis zur Ebene der klassischen
höheren Sprachen und – mit der inzwischen ebenfalls standardisierten Erweiterung
C++ – bis zur Ebene der objektorientierten Sprachen.
Wesentliche Nachteile von C:
• nicht durchweg systematischer Aufbau (auch wegen Kompatibilität mit K&R-C)
• nicht konsequent statisch typisiert
• schwache Unterstützung für modulare Programmierung
• teilweise ins Kryptische gehende Notation
Wesentliche Vorteile von C:
• standardisiert und sehr stark verbreitet
• hohe Bandbreite (Assembler-Ebene bis zur Ebene der Hochsprachen)
• speicher- und laufzeiteffizient
• flexibel und anpassungsfähig
Die Sprache C – und insbesondere C++ – ist kompliziert wie das reale Leben, sie hat
begeisterte Freunde und leidenschaftliche Gegner. Auch hier bietet sich ein Vergleich
aus einem anderen Bereich an. Englisch ist eine Weltsprache: wer global kommunizieren will, muss Englisch lesen, sprechen und schreiben, egal ob ihm die Sprache
gefällt oder nicht.
Es gibt viele C-Dialekte, Entwicklungsstufen und Untermengen, Bild 1.1 gibt einen
Überblick über das Wesentliche. Das klassische Standardwerk zu K&R-C/ANSI-C
ist [1]. Hier eine kurze Skizze der Entwicklung von K&R-C nach ANSI/ISO-C++ :
1.1
3
Entwicklungsgeschichte: Von K&R-C nach C++
Bild 1.1: Entwicklungsstufen der Programmiersprache C
• K&R-C: klassischer Standard seit 1978, definiert von Kernighan und Ritchie
• ANSI-C: ANSI-Standard seit Dezember 1989 (kurz: C89), beinhaltet wesentliche
Erweiterungen gegenüber K&R-C:
– Strengere statische Typisierung, Funktionsprototypen
– Datentyp void* für nicht typisierte Zeiger (Adressen)
– Kopieren und Zuweisen von Strukturtypen
– Attribut const zur Definition von Konstanten, Aufzählungstypen
• Better-C: Untermenge von C++, und zwar C++ ohne Klassen, wesentliche Erweiterungen gegenüber C:
– weiter verbesserte statische Typisierung
– elegantere Ein- und Ausgabe mit den überladenen Operatoren << und >>
– Referenztypen, Überladen von Funktionen und Operatoren
– vereinfachte dynamische Speicherverwaltung mit new und delete
– Inline-Kommentare und Inline-Funktionen
– Namensbereiche (name spaces)
• C++: Von Stroustrup entwickelte objektorientierte Programmiersprache als Obermenge von C, seit September 1998 ebenfalls nach ANSI/ISO standardisiert. Wesentliche Erweiterungen gegenüber Better-C:
– Klassenkonzept zur Realisierung Abstrakter Datentypen (ADT)
– Vererbungsmechanismus (Inheritance) zur Erweiterung von Klassen
– dynamische Bindung von Element-Funktionen (Polymorphismus)
– generische Konstrukte durch Verwendung von Schablonen (Templates)
– Ausnahmebehandlung (Exception Handling)
4
1.2
1
ANSI/ISO-C, Clean-C und Better-C
Clean-C und Better-C
ANSI/ISO-C schleppt – insbesondere wegen seiner Abwärtskompatibilität zu
K&R-C – einiges an Ballast mit sich herum, den ANSI/ISO-C++ abgeworfen hat, d.h.
es besteht keine 100%ige Aufwärtskompatibilität. Bild 1.2 stellt den Zusammenhang
dar:
• Als Clean-C wird im Weiteren der Sprachbereich bezeichnet, der sich aus der
Schnittmenge der Sprachbestandteile von ANSI/ISO-C und ANSI/ISO-C++ ergibt, d.h. Clean-C ist ein etwas eingeschränktes ANSI/ISO-C und
• Clean-C-Programme sind praktisch gesehen Programme, die einen ANSI/ISOC-Compiler und auch einen ANSI/ISO-C++-Compiler ohne Fehlermeldung passieren.
mkno0pqrm
!" #$" !&%')(+**-,.0/21-!35476089.0:<; =>=71?A@B54%DCFE71GF=7,4; 1/H=; 14I5/2J
K LM5NOQPKR
S&T9U0V0W X>XYZV9[0Z]\2^V`_ aScb d$b Se`f)gih0V9jD_kaSb d$b S&ef)g+ll
Bild 1.2: Definition der Sprachsubsets Clean-C und Better-C
Aus Gründen der Portierbarkeit, der Zukunftssicherheit, der Robustheit und der Erweiterbarkeit empfiehlt es sich sehr, bei der Entwicklung von C-Programmen nur
Clean-C zu verwenden, d.h. nur solche Sprachmittel, die auch Bestandteil von C++
sind. Die Einschränkungen sind gering, sie werden jeweils am Ende eines Unterabschnitts zusammenfassend dargestellt. Diese Strategie ermöglicht einen gleitenden
Übergang von C nach C++. ANSI/ISO-C++ enthält gegenüber ANSI/ISO-C nicht
nur objektorientierte Erweiterungen, sondern darüber hinaus auch eine Reihe von
nichtobjektorientierten Erweiterungen, die es in vielen Fällen ermöglichen, Zusammenhänge klarer und besser lesbar zu formulieren:
• Als Better-C wird im Weiteren ein Sprachbereich bezeichnet, der eine Obermenge
von Clean-C und gleichzeitig eine Untermenge von C++ darstellt (siehe Bild 1.2).
1.3
5
Neun Beispiele
Better-C kann eine Zwischenstufe auf dem Wege von C nach C++ sein beim Erlernen der Sprache und auch in der Programmentwicklung. Die Grenze zwischen
Better-C und C++ verläuft fließend und sie ist nicht durch Compiler überprüfbar.
Die elementaren Better-C-Erweiterungen werden jeweils im Anschluss an die CleanC-Einschränkungen am Ende eines Unterabschnitts zusammengefasst, dadurch wird
ebenfalls der Übergang von C nach C++ erleichtert.
1.3
Neun Beispiele
Die folgenden neun Beispiele geben einen ersten Einblick in die Programmiersprache
ANSI/ISO-C:
• Beispiel 1: Hallo Welt
• Beispiel 2: . . . und da ist die Welt
• Beispiel 3: Dateilister Version 0
• Beispiel 4: Datei kopieren
• Beispiel 5: Zahlensumme berechnen
• Beispiel 6: Dateilister Version 1
• Beispiel 7: Dateilister Version 2
• Beispiel 8: Worthäufigkeiten bestimmen
• Beispiel 9: Ein Programm, das sich selbst reproduziert
Leser, die mit anderen höheren Programmiersprachen wie z.B. ALGOL, Pascal, Modula, Oberon oder Ada vertraut sind, werden viele Grundstrukturen wiedererkennen,
das betrifft insbesondere die Ablaufstrukturen (Kontrollstrukturen).
Eine gute Möglichkeit zum praktischen Einstieg in ANSI-C besteht darin, diese
Beispiele in den Rechner einzugeben, sie ablaufen zu lassen und dann zu modifizieren
und auch Fehler einzubauen, um zu sehen, wie der Compiler darauf reagiert.
Das wohl bekannteste Programm der Welt ist auch das erste C-Programm-Beispiel in
dem Standardwerk von Kernighan/Ritchie [1] und wird in C-Text 1.1 wiedergegeben.
C-Text 1.1: Hallo Welt
#include <stdio.h>
int main(void )
/* Das erste Beispiel */
{
printf("hallo, world\n");
return 0;
}
(./Bsp1/b1to7/b1.c)
6
1
ANSI/ISO-C, Clean-C und Better-C
Erläuterungen zu C-Text 1.1:
• Mit #include <stdio.h> wird das Modul für die Standardeingabe und -ausgabe
eingefügt. Das Suffix h bedeutet Header-Datei (kurz: H-Datei ), was hier soviel wie
Schnittstellen-Datei bedeutet. Es gibt 15 Standard-H-Dateien, z.B. stdio, string,
float, math, ..., siehe auch [1] Anh. B (S. 239).
• main bedeutet Hauptprogramm, genauer Hauptfunktion. Formal ist main eine
integer-Funktion des Betriebssystems und gibt über die return-Anweisung bei Programmende den Wert 0 (d.h. 0 Fehler, alles okay) an das Betriebssystem zurück.
• Das Klammerpaar { } steht für begin und end, Kommentare werden in /* und
*/ eingeschlossen, Kommentarschachtelungen sind nicht erlaubt!
• printf(...) ist die Standardfunktion für formatierte Ausgabe aus stdio.h, \
zeigt an, dass ein Steuerzeichen folgt, n steht dort für Zeilenende.
• Während in Sprachen wie z.B. Pascal, Modula und Oberon das Semikolon als
Trennzeichen zwischen zwei Anweisungen steht, wird in C jede Anweisung durch
ein Semikolon abgeschlossen, d.h. also, dass das Semikolon hinter return 0 zwingend notwendig ist.
Das als C-Text 1.2 wiedergegebene Programm 1 ist als kleiner einführender Scherz
gedacht. Als C-Anfänger sollte man nicht versuchen, es zu verstehen. Es generiert
eine Weltkarte, wenn es z.B. mit b2 50 50 aufgerufen wird.
C-Text 1.2: ... und das ist die Welt
(./Bsp1/b1to7/b2.c)
#include <stdlib.h>
#include <stdio.h>
main(l
,a,n,d)char **a;{
for (d=atoi(a[1])/10*80atoi(a[2])/5-596;n="@NKA\
CLCCGZAAQBEAADAFaISADJABBA^\
SNLGAQABDAXIMBAACTBATAHDBAN\
ZcEMMCCCCAAhEIJFAEAAABAfHJE\
TBdFLDAANEfDNBPHdBcBBBEA_AL\
H E L L O,
W O R L D! "
[l++-3];)for (;n-->64;)
putchar(!d+++33^
l&1);}
Das Programm erzeugt mit dem angegebenen Aufruf folgende Ausgabe:
1 Das Programm habe ich von Herrn Carsten Hoffmann, einem Hörer meiner Vorlesung. Er hat
es in einer Mailbox gefunden
1.3
7
Neun Beispiele
!!!!!!!!!!! !!!
! !!!!!!!!!!!!!!!!! !!!!! !
!!!!!!!!!!!!!!!!!!! !!!!
!!!!!!!!!!!!!!
!!!!!!!!!
!
!!!! !
!!!!!
!!!!!
!!!!!!!!
!!!!!!
!!!!
!!
!
!
!!!
!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! !! !!!!!"!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!! !!!!!!!!!!!!!!!!! !!
!
!! !
!!!!!!!!!!!!!!!!!!!! !
!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!
!!!
!!! !
!!!!!!!!!!
!
! ! !
!!!!!
!!
!!!! !
!!!!!
!!
!!!!!!!!
!! !!
!
Der C-Text 1.3 ist ein Programm, das die Textdatei b3.c – also den eigenen Quelltext
– auf den Bildschirm ausgibt.
C-Text 1.3: Dateilister Version 0
(./Bsp1/b1to7/b3.c)
#include <stdio.h>
int main(void )
/*
Eine Textdatei auf den Bildschirm kopieren (Filelister)
*/
{
FILE *fp;
int c;
fp = fopen("b3.c", "r");
c = getc(fp);
while (c != EOF )
{
putchar(c); c = getc(fp);
}
fclose(fp);
return 0;
}
Erläuterungen zu C-Text 1.3:
• fp (filepointer) ist ein Zeiger (eine Adresse), das Zeichen * ist der Inhaltsoperator
(Inhalt von).
• EOF bedeutet End Of File und ist als Konstante in stdio.h definiert.
• Für Wertzuweisungen wird in C der Operator = verwendet, für Abfragen auf
Gleichheit bzw. Ungleichheit werden die Operatoren == bzw. != verwendet.
8
1
ANSI/ISO-C, Clean-C und Better-C
• Die Funktion getc(fp) liest ein Zeichen aus der durch fp bezeichneten Datei,
die Funktion putchar(c) schreibt ein Zeichen auf das Standard-Ausgabemedium
(Bildschirm), beide sind in stdio definiert.
• Die Zeichenvariable c wird bewusst als int-Typ vereinbart, unter anderem, weil
die in stdio.h definierte Konstante EOF nicht den Wert eines bestimmten Zeichens
des definierten Zeichensatzes (z.B. des ASCII-Zeichensatzes) hat, sondern einen
Wert außerhalb des entsprechenden Zeichensatzes. Ein typischer Wert für EOF ist
z.B. -1. Aus diesem Grunde ist der Rückgabetyp der Funktion getc auch int und
nicht char.
• C erlaubt Klein- und Großschreibung für die Buchstaben innerhalb von Bezeichnern und ist case sensitive, d.h. es unterscheidet große und kleine Buchstaben. So
sind z.B. meier, Meier und MEIER drei verschiedene Bezeichner.
Das Programm C-Text 1.4 kopiert die Textdatei b4.c, also wieder den eigenen Quelltext. Die Datei mit dem kopierten Text erhält den Namen xxxxxx.
C-Text 1.4: Datei kopieren
(./Bsp1/b1to7/b4.c)
#include <stdio.h>
int main(void )
/*
Kopieren einer Textdatei (Filecopy)
*/
{
FILE *fpi, *fpo;
int c;
fpi = fopen("b4.c", "r");
fpo = fopen("xxxxxx", "w");
c = getc(fpi);
while (c != EOF )
{
putc(c, fpo); c = getc(fpi);
}
fclose(fpi); fclose(fpo);
return 0;
}
Pn2
Das Programm C-Text 1.5 bestimmt jeweils die Summe S = i=n1 i für zwei eingelesene Werte n1 und n2 mit n1 ≤ n2 und führt die entsprechende Berechnung dazu
in dem Unterprogramm (der C-Funktion) summe durch.
12.1
Klassendiagramme: Modellierung der Architektur
12.1
929
Klassendiagramme:
Modellierung der Architektur
Der wichtigste und meist verwendete Teil von UML ist sicher das Subset Klassendiagramme zur Modellierung der statischen Programmstruktur, d.h. der Architektur des Programmes. Damit werden die im Programm definierten Klassen sowie die
Beziehungen zwischen den Klassen durch eine Modellierungssprache mit grafischen
Elementen dargestellt.
Bei der Verwendung von Entwicklungswerkzeugen zum Arbeiten mit UMLDiagrammen sind zwei Vorgehensweisen nützlich, die auch von den meisten Herstellern unterstützt werden:
(1) Die wichtigste und primere Vorgehensweise ist die interaktive Erstellung eines
Programmmodells (Modellbildung, Entwurf in Form eines Klassendiagrammes)
unter Verwendung der dafür vorgesehenen Sprachelemente.
(2) Die zweite auch sehr interessante Vorgehensweise beinhaltet die rückwärtsgerichtete Erzeugung eines Programmmodells aus einem C++-Text (reverse
engineering).
Zu (1) Diese Vorgehensweise repräsentiert den Programm-Entwurf – zumindest
den Entwurf der Architektur – und führt zu der Programm-Spezifikation in Form
von UML-Klassendiagrammen. Die Programmspezifikation ist einerseits eine verbindliche Vorgabe für die nachfolgende Implementierung und kann darüber hinaus
später als Teil-Dokumentation des Programmes verwendet werden. Bild 12.1 stellt
Entwurf und Spezifikation im Rahmen des klassischen Phasenmodells dar.
Zu (2) Die umgekehrte Vorgehensweise (Reverse Engineering) – d.h. hier die Erzeugung von Klassendiagrammen aus C++-Code ist aus verschiedenen Sichten interessant. Eine ist die damit mögliche Analyse bestehenden Codes und die rückwirkende
Erzeugung der Spezifikation für Dokumentationszwecke. Ein anderer Aspekt – insbesondere interessant in Verbindung mit der Sprache C++– besteht darin, durch eine
Codeanalyse zu überprüfen, ob die Richtlinien der objektorientierten Programmierung eingehalten worden sind.
Wichtiger Hinweis UML ist eine Sprache zur Modellierung objektorientierter
Software, C++ unterstützt aber auch nicht objektorientierte Programmiermethoden,
sehr häufig wird in C++ hybrid programmiert. Deshalb ist es sinnvoll, sich beim Start
eines entsprechenden Softwareprojektes für eine bestimmte Programmiermethodik
zu entscheiden. Will man UML als Modellierungssprache für den Programmentwurf
930
verwenden, sollte bei der Implementierung auch möglichst konsequent objektorientiert programmiert werden, d.h. im Wesentlichen keine globalen Daten und keine
globalen Funktionen. Bei größeren Projekten kann es sehr sinnvoll sein, in verschiedenen Programmteilen unterschiedliche Programmiermethoden zu verwenden. In dem
Falle wird man die Verwendung von UML auf die objektorientierten Programmteile
beschränken.
%'&
(*),+.-
/0+.1
&2/0&
345/76.8(895:<;*8=)&
IKJMLNJPORQSJ
TVUNW,XYUNJ0JMOAXZUW
> +0)2+.:,??
"!$#
?)-
8(@86.8=/0+7;A/0B
CD0EFD.G,HH
Bild 12.1: Klassisches Phasenmodell der Softwareentwicklung
12.1.1
Modellierung von Klassen
Der angegebene C++-Text deklariert die Schnittstelle einer Klasse Stack und das
Bild 12.2 enthält vier UML-Modelle dieser Klasse mit verschiedenen Abstraktionsgraden. Das rechte Modell hat den höchsten Abstraktionsgrad, es besteht aus nur
einem Rechteck mit dem Namen der Klasse.
class Stack {
int *Data;
int n;
int size;
void Copy (const Stack& s);
public:
Stack (int siz=10);
~Stack ();
Stack (const Stack& s);
Stack& operator= (const Stack& s);
void Push (int x);
int Pop ();
int count () const;
};
Das linke Modell enthält die meisten Details. Im oberen Teil steht der Name der
Klasse, im mittleren Teil die Datendefinitionen und im unteren Teil stehen die Deklarationen der Elementfunktionen. Vor jeder Deklaration ist ein Symbol angegeben,
das ihre Zugriffsart definiert: + für public, - für private und # für protected.
12.1
931
Klassendiagramme: Modellierung der Architektur
Die Syntax der Deklarationen unterscheidet sich etwas von der C++-Syntax, sie soll ja
im Prinzip sprachunabhängig sein. Die Syntax der Deklarationen, die formal exakt
in der UML-Beschreibung festgelegt ist, soll hier nicht weiter erörtert werden, sie
ist für jeden C++-Programmierer leicht lesbar und bei der Erstellung von UMLModellen hilft dann das entsprechende Entwicklungswerkzeug. Beim Erstellen von
Klassendiagrammen ohne die Verwendung von Werkzeugen spricht natürlich nichts
dagegen, die C++-Syntax zu verwenden. Das zweite Modell von links verzichtet auf
die Angaben der Signaturen bei den Deklarationen der Elementfunktionen und beim
dritten Modell von links wird auf die Datendeklarationen verzichtet.
! "# %$'&#)( *
+ # " ! -,/.10"&## + # " ! " "%$'& "
+32 " &
+54
6 7 8 !&#%( *
+ -9 9, ! - %$:&# %$
+54 - &# + 6 &# ; < = &
+ &
+ &
+2 &
+4#6 7 &
+ - 9 9, &
+4 &
+ 6 &
"
&
+ &
+ &
+2 # &
+4#6 7 &
+ - -9 9, &
+4 &
+ 6 &
Bild 12.2: UML-Modelle einer Klasse Stack mit unterschiedlichen Detaillierungsgraden
Abstrakte Klassen, abstrakte Methoden
Der folgende C++-Text enthält die Klassenschnittstelle einer abstrakten Klasse mit
rein virtuellen Elementfunktionen und einem virtuellen Destruktor ; Konstruktoren
und Zuweisungsoperator machen – in der öffentlichen Schnittstelle – keinen Sinn.
Wenn eine abstrakte Klasse eigene Heapdaten verwaltet, sollten entsprechende Konstruktoren im protected-Bereich zur Verfügung gestellt werden und evtl. ebenfalls
im protected- Bereich ein Zuweisungsoperator.
class AbsStack {
public:
virtual AbsStack();
virtual void Push(int x) = 0;
virtual int Pop()
= 0;
virtual bool isEmpty()
= 0;
};
Das Bild 12.3 zeigt UML-Diagramme für diese Klasse mit verschiedenen Abstraktionsgraden. Eine abstrakte Klasse wird in der UML-Darstellung durch einen kursiv
932
geschriebenen Klassennamen dargestellt und die rein virtuellen (abstrakten) Elementfunktionen werden ebenfalls durch kursiv geschriebene Namen und Signaturen
wiedergegeben.
@
ACB(DE FGIHJ KMLON !" #
P>$QS&(RCT'UW)V X% *Y+Z [",-\ ]^ Y<_a`Z b
%
.0/21354 6879 :<;
.>=1?:<; 4 6879 :<;
cdefg hij
ˆ
km‰Cl2Š(no‹pCŒqO2r Ž ‘M’-“
”>
kt•su^–—Mv˜šow5™ › x yz{qOr
|~}€M‚ ƒ
|0„M…†‡I‚ ƒ
œ8žŸ2 ¡¢a£
½%
¤m¾C¥2¿(¦§À ¨CÁÂ2©Oª à ÄÅÆMÇ-È
É0
¤tÊ«¬^ËÌM­Íš§Î®5Ï ¯ °±²©Oª
³~´€µM¶· ¸
³0¹µº»<¼I· ¸
Ð8ÑÒÓ2Ô ÕÖ×
Bild 12.3: UML-Modelle einer abstrakten Klasse mit unterschiedlichen Detaillierungsgraden
12.1.2
Modellierung von Beziehungen zwischen Klassen
Komposition, Aggregation und Assoziation
Grob gesehen ist zwischen benutzt-Beziehungen (hat ein) und erbt-Beziehungen (ist
ein) zu unterscheiden; hier geht es zunächst um die erstgenannten. Der angegebene
C++-Text und das Bild 12.4 stellen Komposition und Aggregation als zwei Varianten
dieser Art von Beziehung dar.
Komposition
Aggregation
class Stack { ... };
class Stack { ... };
class Application {
Stack s;
Stack svec[5];
public:
void main();
};
class Application {
Stack* ps;
Stack* psvec1[20];
Stack (*psvec2)[20];
public:
Application();
~Application();
void main();
};
Komposition steht für eine stärkere Bindung, im C++-Code existiert ein konkretes
Objekt der benutzten Klasse, im Diagramm wird dafür die ausgefüllte Raute verwendet. Aggregation steht für eine schwächere Bindung, im C++-Code wird das Objekt
über einen Zeiger oder über eine Referenz benutzt, im Diagramm wird dafür die
12.1
933
Klassendiagramme: Modellierung der Architektur
3
3
5
3
(4- )#
3
021 1 3
3
0
3
"!$#&%'
)#
( (+* , )#
(.- /#
687:9<;&7&=> ?@> 7:A
BDCCFEHGCI ?$> 7:A
Bild 12.4: Die benutzt-Beziehungen Komposition und Aggregation
leere Raute verwendet. Die Namen an den Verbindungen bezeichnen das benutzte
Objekt bzw. den Zeiger oder die Referenz, über die das Objekt verwendet wird.
Die Zahlen an den Verbindungen definieren die sog. Multiplizität. Im linken Teilbild benutzt ein Application-Objekt ein Stack-Objekt s und fünf Stack-Objekte
in Form des Vektors svec. Entsprechend sind die Zahlen an den Verbindungen des
rechten Teilbildes zu interpretieren, wobei 0..1 bedeutet, dass der Zeiger mit einem
entsprechenden Objekt verbunden sein kann oder nicht. Als Angaben für Multiplizitäten werden z.B. verwendet:
• 1 für genau ein;
• n für n;
• * für viele;
• 0..1 für null oder ein;
• n..m für n bis m.
Wenn es darum geht, vorhandenen C++-Code durch Klassendiagramme zu beschreiben, sind die Beziehungen im Allgemeinen klar definiert als Komposition oder als
Aggregation. Anders ist die Situation, wenn – wie bei der realen Softwareentwicklung
nach einem Phasenmodell – die Softwarearchitektur zuerst durch Klassendiagramme
modelliert und das Modell in einer späteren Phase implementiert wird. Dann kommt
es vor, dass die Art der Beziehung im Detail noch nicht definiert werden kann. In
diesem Falle verwendet man einen entsprechenden Pfeil ohne Raute oder – falls auch
die Richtung noch offen ist – eine Verbindung ohne Pfeil. Diese Art der Beziehung
934
wird Assoziation genannt und kann als Oberbegriff zu Komposition und Aggregation
angesehen werden.
Selbstbezüge In der Praxis werden häufig rekursive Datenstrukturen verwendet,
wie z.B. Listen- oder Baumknoten. Der angegebene C++-Text und das Bild 12.5 zeigen eine entsprechende Klasse, die einen Listenknoten mit den entsprechenden Zugriffsoperationen darstellt und ihre Verwendung zur Realisierung einer Stack-Klasse.
Selbstverständlich kann eine Klasse sich nicht direkt selbst benutzen, sondern nur
indirekt über einen Zeiger oder über eine Referenz!
7
- /.
/
.
/
/
+ &,
536 ' #
&, & , 0 0
,&,
+ ,&1
,,23$
+ &,&14
89 9 7 - 53 6 - ,
89 9 7
7
"! #
$ &%' $
()*
Bild 12.5: Benutzung einer Klasse mit Selbstbezug
class Stack {
class Node {
Node* next;
int data;
public:
int
getdata();
void setdata(int x);
Node* getnext();
void setnext(Node* p);
};
Node *first;
public :
Stack ();
~Stack ();
void Push (int x);
int Pop();
bool isEmpty();
};
12.1
Klassendiagramme: Modellierung der Architektur
935
Vererbung (Typerweiterung, Subtyping, Spezialisierung)
Der angegebene C++-Text und das Bild 12.6 demonstrieren die Modellierung einer
Vererbungsbeziehung; der Pfeil geht von der Unterklasse aus und zeigt zur Oberklasse, d.h. er zeigt in Richtung der Generalisierung. Spezielle Konstrukte der Sprache
C++, wie z.B. private Vererbung, werden in UML nicht modelliert.
0 1 & 2
0 3 &
0 054 "6$ *7 " 6 81:9 " ;
3 =< 7& " &6 >81& 7? +
A@ /&B( C5 D>9 " ;
"$ " D7 " >8E& >8
A@#"6$ + " /(&D+ !#"$ % &
(') $ *+, "
" (./ - - %, "("-
Bild 12.6: Die Vererbungsbeziehung zur Erweiterung einer Klasse
Das Symbol # vor einer Deklaration bedeutet, dass das entsprechende Element die
Zugriffsart protected hat.
class Stack {
protected:
int *Data;
int n;
int size;
void Copy (const Stack& s);
public:
Stack (int siz=10);
~Stack ();
Stack (const Stack& s);
Stack& operator= (const Stack& s);
void Push (int x);
int Pop ();
int count () const;
};
class DirStack : public Stack {
public:
DirStack(int size=20);
int Top()
const;
bool isEmpty() const;
bool isFull() const;
};
936
Abstrakte Klassen als Schnittstellenklassen Bild 12.7 zeigt ein bei der Entwicklung objektorientierter Software häufig verwendetes Muster, die Verwendung
einer abstrakten Klasse als Schnittstelle zur Anwendung, der angegebene C++Text skizziert den entsprechenden Code. Die Vorteile dieses Entwurfsmusters sind
vielfältig, z.B.:
• Die Details der Implementierung werden konsequent versteckt.
• Die Implementierungen – in Form abgeleiteter konkreter Klassen – sind zur
Laufzeit austauschbar.
• Partielle und inhomogene Zuweisungen werden syntaktisch ausgeschlossen, siehe dazu Abschnitte 9.4.3.
\G]]^ _ `)abc_ d(e
f&g \G]h]^ _ `)abc_ d#ehicj
f \G]]h^ _ `)ahb_ d(eicjk!\G]h]^ _ `)abc_ d#e
fml a(_ eiLjk!nd(_ o
r
pq q r s ]t
+-,/.01 23)4
uO6vx7wh8y{9z(: | ;&
}<~ = € >#?‚ ƒU
5
@<!~#A„!B#…h= € C †
5&DEGF8H? IJK):L@
MON
PQR SUTV W#X
MZY)P[W#X R SUTV W#X
!"# $
&% "(' )" ***
Bild 12.7: Abstrakte Klasse als Schnittstelle zur Anwendung
Die Verwendung der Klasse Stack – oder allgemein die Verwendung der von der
abstrakten Klasse abgeleiteten konkreten Klassen – erfolgt über einen Zeiger vom
12.1
937
Klassendiagramme: Modellierung der Architektur
Typ Zeiger auf abstrakte Klasse (Basisklassenzeiger), der dann mit den Zeigern vom
Typ Zeiger auf abgeleitete konkrete Klasse kompatibel ist. D.h. zur Laufzeit des
Programmes kann der Basisklassenzeiger an Objekte verschiedenen Typs gebunden
werden.
C++-Text 12.1: Abstrakte Klasse als Schnittstelle
class AbsStack {
public :
virtual ~AbsStack
virtual void Push
virtual int Pop
virtual int count
};
() {}
(int x) = 0;
()
= 0;
() const = 0;
class Stack : public AbsStack {
int *Data;
int n;
int size;
void Copy (const Stack& s);
public :
12.1.3
(./bsp12/kurzecpptexte/relats4.sht)
virtual
virtual
virtual
virtual
. . .
~Stack ();
void Push (int x);
int Pop ();
int count () const ;
};
class Application {
AbsStack* ps;
public :
Application();
~Application();
void main();
};
Verwendung von Klassenschablonen
Bild 12.8 zeigt die Verwendung einer Klassenschablone (Klassen-Template) in einem UML-Diagramm, der angegebene C++-Text skizziert den zugehörigen Code.
Die Klasse Application in dem Diagramm benutzt nicht die Schablone Stack, sondern eine Klasse Stack<int>, die aus der Schablone generiert wird.
Das gestrichelt dargestellte Rechteck mit der Bezeichnung T in der Klassenschablone Stack bezeichnet den Typparameter. Der gestrichelte Pfeil von der Klasse
Stack<int> zu der Schablone bedeutet in diesem Zusammenhang eine Klasse aus
”
einer Schablone erzeugen“.
Die Verwendung von Klassenschablonen beinhaltet einen Mechanismus zum Generieren von Code, mit wenig Programmieraufwand kann viel Code erzeugt werden, der
dann statisch typisiert und damit sicher ist und der darüber hinaus sehr laufzeiteffizient sein kann, siehe die STL (Standard Template Library). Auf der anderen Seite
sollte aber auch beachtet werden, dass der Einsatz von Klassenschablonen auch ein
großes Codevolumen erzeugen kann. Der Sprachstandard von C++stellt Konstrukte
zur Spezialisierung von Schablonen zur Verfügung, die im Allgemeinen erfolgreich
eingesetzt werden können, um das Codevolumen zu reduzieren; sie werden im Abschnitt 9.7 beschrieben.
Herunterladen