Turbo Pascal 7.0 [2., durchges. Aufl. Reprint 2018] 9783486805086, 9783486254990

Aus dem Inhalt: Die Prinzipien von Pascal und seiner Umgebung - Die Turbo-Pascal-Umgebung IDE - Aufbau von Pascal-Progra

277 54 24MB

German Pages 416 Year 2000

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Inhalt
1. Einleitung
2. Die Prinzipien von Pascal und seiner Umgebung
3. Die Turbo-Pascal-Umgebung IDE
4. Aufbau von Pascal-Programmen
5. Einfache Standard-Datentypen
6. Konstantenvereinbarung
7. Variablenvereinbarung
8. Elementare Ein- und Ausgabe
9. Operatoren und Ausdrücke
10. Anweisungen
11. Prozeduren und Funktionen
12. Typen und Typnamen
13. Einfache Datentypen
14. Array-Typ
15. Record-Typ
16. Set-Typ
17. Dateien
18. Datentyp Zeiger
19. Modularisierung und Units
20. Die Kommandozeilen-Version TPC
21. Turbo Pascal und MS-DOS
22. Der integrierte Assembler
23. Grafik
24. Objekt-orientiertes Programmieren
25. Fehlersuche
26. Der Turbo-Editor
27. Erweiterungen von Turbo Pascal 7.0
Anhang
Beispiele zu diesem Buch
Recommend Papers

Turbo Pascal 7.0 [2., durchges. Aufl. Reprint 2018]
 9783486805086, 9783486254990

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

Turbo Pascal 7.0 von Prof. Dr. Rudolf Herschel und Prof. Dr. Ernst-Wolfgang Dieterich Fachhochschule Ulm 2., durchgesehene Auflage

Oldenbourg Verlag München Wien

Der Verlag übernimmt keine Gewähr dafür, daß die beschriebenen Verfahren, Programme usw. frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handeisnamen, Warenbezeichnungen usw. in diesem Buch berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, daß solche Namen im Sinne der Warenzeichen- und Markenschutzgesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.

Die D e u t s c h e Bibliothek - C I P - E i n h e i t s a u f n a h m e Berschel, Rudolf: Turbo Pascal 7.0 / von Rudolf Herschel ; Emst-Wolfgang Dieterich. - 2., durchges. Aufl. - München ; Wien : Oldenbourg, 2000 ISBN 3-486-25499-5 NE: Dieterich, Em st-Wolfgang:

© 2000 Oldenbourg Wissenschaftsverlag GmbH Rosenheimer Straße 145, D-81671 München Telefon: (089) 45051-0 www.oldenbourg-verlag.de Das Werk einschließlich aller Abbildungen ist urheberrechtlich geschützt. Jede Verwertung außerhalb der Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Das gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Bearbeitung in elektronischen Systemen. Lektorat: Dr. Georg W. Botz, Irmela Wedler (Ass.) Herstellung: Rainer Hartl Umschlagkonzeption: Kraxenberger Kommunikationshaus, München Gedruckt auf säure- und chlorfreiem Papier Druck: R. Oldenbourg Graphische Betriebe Druckerei GmbH

Inhalt 1.

Einleitung

11

2.

Die Prinzipien von Pascal und seiner U m g e b u n g

13

3.

Die T u r b o - P a s c a l - U m g e b u n g I D E

17

4.

A u f b a u von P a s c a l - P r o g r a m m e n

23

5.

Einfache Standard-Datentypen

31

5.1

G a n z e Zahlen

31

5.2

G e b r o c h e n e Zahlen

33

5.3

char

35

5.4

boolean

37

6.

Konstantenvereinbarung

39

7.

Variablenvereinbarung

43

8. 8.1

E l e m e n t a r e Ein- u n d A u s g a b e Eingabe

47 48

8.2

Ausgabe

51

9.

O p e r a t o r e n und A u s d r ü c k e

61

9.1

A r i t h m e t i s c h e O p e r a t o r e n und A u s d r ü c k e

62

9.2

Bitoperatoren

64

9.3

L o g i s c h e Operatoren

68

9.4

Mathematische Standardfunktionen

70

9.5

Typwandlung

73

9.6

Zusammenfassung

77

10.

Anweisungen

79

10.1

Verbundanweisung

80

10.2

Wertzuweisung

81

10.3

Bedingte A n w e i s u n g e n

83

10.4

Schleifenanweisungen

86

10.5

Sprunganweisung

89

6

Inhalt

11. 11.1 11.2 11.3 11.4

Prozeduren und Funktionen Prozeduren Funktionen Kommunikation von Prozeduren und Funktionen Rekursion

93 93 102 109 112

12.

Typen und Typnamen

119

13. 13.1 13.2

Einfache Datentypen Aufzählungstyp Unterbereichstyp

125 126 131

14. 14.1 14.2 14.3 14.4

Array-Typ Strukturierte Datentypen Eindimensionale Arrays Mehrdimensionale Arrays String-Typ

135 135 138 158 161

15.

Record-Typ

171

16.

Set-Typ

187

17.

Dateien

195

17.1 17.2 17.3

Typdateien Textdateien Typlose Dateien

198 208 217

18. 18.1 18.2

Datentyp Zeiger Adressen und Adreßoperator Dynamische Variablen und verkettete Datenstrukturen

221 221 229

19. 19.1 19.2 19.3 19.4 19.5

Modularisierung und Units Modularisierung Units Die Standard-Units Overlays Make

249 249 251 263 269 272

20.

Die Kommandozeilen-Version TPC

275

21.

Turbo Pascal und MS-DOS

281

Inhalt

7

22.

Der integrierte A s s e m b l e r

293

22.1

Die a s m - A n w e i s u n g

293

22.2

P r o z e d u r e n und F u n k t i o n e n im A s s e m b l e r

296

23.

Grafik

307

24.

O b j e k t - o r i e n t i e r t e s P r o g r a m m i e r e n mit Turbo Pascal 6.0

331

24.1

O b j e k t e und Methoden

332

24.2

Vererbung

340

24.3

Virtuelle Methoden

347

24.4

E r w e i t e r u n g von O b j e k t e n

351

24.5

D y n a m i s c h e G e n e r i e r u n g von O b j e k t e n

353

25.

Fehlersuche

357

25.1

Klassifikation der Fehler

357

25.2

Der integrierte D e b u g g e r

359

26.

Der Turbo-Editor

367

27.

E r w e i t e r u n g e n von Turbo Pascal 7

371

Anhang A.

Syntax von Turbo Pascal

391

Verzeichnis der S y n t a x d i a g r a m m e

393

B.

Operatoren

397

C.

ASCII-Zeichensatz

399 401

D.

Compiler-Direktiven

E.

Literaturverzeichnis

407

F.

Sachwortverzeichnis

409

Verzeichnis der Beispiele 2.1

E i n f ü h r e n d e s Beispiel: B e g r ü ß u n g

13

4.1

Lieblingszahlerraten

23

5.1

D a t e n t y p double mit ( * $ N + * )

34

5.2

ASCII-Zeichen vorführen

37

5.3

Die Funktion sizeof

38

8.1

E x p e r i m e n t i e r e n mit read und write

8.2

Zahlen lesen und a u f a d d i e r e n (eof und

52 A

Z)

53

8.3

D e m o n s t r a t i o n von ioresult

54

8.4

Lesen von R E T U R N

54

8.5

Farbliche Textgestaltung

57

8.6

D e m o n s t r a t i o n von norm-, low- und h i g h v i d e o

57

8

Inhalt

8.7

Z a h l f o r m a t bei write

58 59

8.8

A u s g a b e auf D r u c k e r (PC als S c h r e i b m a s c h i n e )

9.1

D e m o n s t r a t i o n der Operatoren m o d und div

63

9.2

S h i f t o p e r a t o r e n shl und shr

65

9.3

Bitoperatoren not, and, or, xor

65

9.4

Bitweise A u s g a b e e i n e r integer-Zahl

67

9.5

Logische O p e r a t o r e n

70

9.6

N u m e r i s c h e S c h w i e r i g k e i t e n bei x/a*a

75

9.7

N u m e r i s c h e P r o b l e m e bei b + a - a

76

10.1

Beispiel f ü r richtige und falsche W e r t z u w e i s u n g e n

82

10.2

B e a n t w o r t e n einer A n f r a g e

85

10.3

A u s g a b e von Z a h l e n , die unter e i n g e g e b e n e n nicht v o r k o m m e n .

87

10.4

Beispiel f ü r f o r - a n w e i s u n g

88

10.5

Zahlenspiel (gewiße Zahlen durch * e r s e t z e n )

89

10.6

Einlesen und A u f s u m m i e r e n von Zahlen

90

11.1

Prozedur quersumme

93

11.2

Unterschied W e r t - u n d R e f e r e n z p a r a m e t e r

97

11.3

lokale und g l o b a l e G r ö ß e n

98

11.4

Typlose P a r a m e t e r

11.5

Z e i c h e n als M o r s e z e i c h e n ausgeben

98

11.6

Funktion q u e r s u m m e

105

11.7

Nullstellen einer Funktion f(x) b e s t i m m e n

106

11.8

Wert-und Referenzparameter

111

11.9

Endlose R e k u r s i o n

113

100

11.10 Vergleich Iteration - Rekursion

114

11.11 K o n v e r t i e r u n g dezimal—oktal

114

11.12 F i b o n a c c i - Z a h l e n

115

11.13 P r i m z a h l e n (Beispiel f ü r forward)

117

12.1

T y p n a m e als globale Größe

120

12.2

Funktion als P a r a m e t e r

123

13.1

Beispiel f ü r A u f z ä h l u n g s t y p

127

13.2

Kinder a u s z ä h l e n

128

13.3

Ü b e r w a c h u n g von Unterbereichs-Grenzen

132

13.4

Lottozahlen

132

14.1

Array lesen und verschieben

140

14.2

1000 Z a h l e n sortieren

141

14.3

H ä u f i g k e i t von B u c h s t a b e n

143

14.4

Verteilung von Parlamentssitzen

144

14.5

Suchen in e i n e m Array

147

14.6

Sortieren von A r r a y s

149

14.7

Kinder a u s z ä h l e n

155

Inhalt 14.8

Matrix auf S y m m e t r i e p r ü f e n

14.9

H ä u f i g k e i t von B u c h s t a b e n p ä r c h e n

9 158 159

14.10 Lesen von Strings

161

14.11 Strings als P a r a m e t e r

163

14.12 Erraten von Wörtern

166

14.13 Strings sortieren

168

15.1

Array von R e c o r d s

174

15.2

Sicheres Lesen von R e c o r d s

176

15.3

Z e i t d i f f e r e n z ermitteln

179

15.4

Flächeninhalt von Figuren (varianter Record)

15.5

B i t w e i s e Darstellung von real-Zahlen (varianter R e c o r d )

181

16.1

O p e r a t i o n e n mit M e n g e n

188

16.2

Vokale in einem Text ersetzen

189

16.3

K i n d e r auszählen

190

16.4

Lottozahlen ermitteln

193

17.1

A n l e g e n und A u s g e b e n einer Typdatei

17.2

L ö s c h e n A n f ü g e n und Ä n d e r n von Datensätzen einer Typdatei

17.3

S c h r e i b e n nach reset

. . . .

182

199 .

203 208

17.4

Textdatei kopieren

211

17.5

Textdatei kopieren und nach rechts verschieben

211

17.6

Vergleich T y p - u n d Textdatei

214

17.7

Beispiel f ü r ein D r u c k p r o g r a m m

215

17.8

Beispiel f ü r seekeoln

216

17.9

Kopieren einer Textdatei mit einem 1 K B - P u f f e r

217

17.10 Kopieren einer typlosen Datei

219

18.1

Adresse dezimal und hexadezimal ausgeben

227

18.2

Zeiger auf Array und K o m p o n e n t e n

228

18.3

G r o b e A b s c h ä t z u n g der H e a p g r ö ß e

230

18.4

Beispiel f ü r new/dispose

231

18.5

gekettete Liste anlegen und ausgeben

232

18.6

O p e r a t i o n e n auf einer ver ketteten Liste

233

18.7

Binären B a u m anlegen und ausgeben

235

18.8

Stack als gekettete Liste simulieren

237

18.9

Test der H e a p g r ö ß e

242

18.10 W a n d e r n in z w e i d i m e n s i o n a l e n R ä u m e n

244

19.1

254

Stacksimulation (zerlegt in Units)

19.2

S c h a c h t e l u n g von Units

262

20.1

Protokoll der A u f r u f p a r a m e t e r

278

20.2

Textdatei kopieren ( N a m e n als A u f r u f p a r a m e t e r )

279

21.1

Directory-Kommandos

281

21.2

Interrupt-Beispiel

284

10

Inhalt

21.3

Freier S p e i c h e r p l a t z auf Diskette

285

21.4

D a t u m holen

287

21.5

Uhrzeit holen

287

21.6

A u s g a b e mit A N S I - T r e i b e r

289

21.7

Schreiben in den Bildschirmspeicher

290

22.1

arithmetisch Schieben

294

22.2

arithmetisch Schieben,verbessert

294

22.3

arithmetisch Schieben, 8 0 1 8 6 - P r o z e s s o r

295

22.4

Pascal-Assembler-Namenskonflikt

296

22.5

Fenster scrollen

297

22.6

Variable t a u s c h e n

300

22.7

multipliziere mit 16

301

22.8

B u c h s t a b e lesen

301

22.9

Pascal-Strings in C-Strings wandeln

302

2 2 . 1 0 C u r s o r - F o r m einstellen

303

22.11 B u c h s t a b e n lesen (inline-Funktion)

305

22.12 C u r s o r - F o r m einstellen (inline-Prozedur)

306

23.1

Skelett eines G r a p h i k p r o g r a m m s

312

23.2

K o n z e n t r i s c h e Kreise mit circle

315

23.3

Sinus-Kurve

324

23.4

Beispiel f ü r drawpoly und fillpoly

325

23.5

Beispiel f ü r b a r und bar3d

326

23.6

D e m o n s t r a t i o n von aspect_ratio

327

23.7

Fenster mit R a h m e n

328

23.8

Schriftarten

24.1-5 k o m p l e x e Z a h l e n

329 333-337

24.6

Unit f ü r Position

24.7

Unit f ü r positionierter Text

342

24.8

Unit f ü r F e n s t e r - B e a r b e i t u n g

344

24.9

Unit f ü r B e a r b e i t u n g von Fenstern mit R a h m e n

346

24.10 Fenster füllen

341

348

24.11 virtuelle M e t h o d e n

349

24.12 H a u p t p r o g r a m m f ü r Fenster-Beispiel

350

24.13 Unit f ü r f a r b i g e Fenster

352

24.14 B e a r b e i t u n g von d y n a m i s c h e n Objekten

354

24.15 Beispiel f ü r typeof

356

25.1

F e h l e r h a f t e s P r o g r a m m zum Debuggen

361

27.1

Beispiel f ü r o f f e n e n a r r a y - P a r a m e t e r

375

27.2

Beispiel f ü r null-terminierte Strings

376

27.3

Beispiel f ü r O p e n S t r i n g

378

27.4

Gemischtes A u f t r e t e n v o n private und public

383

27.5

V e r w e n d u n g v o n inherited

384

1. Einleitung Als T u r b o Pascal 1984 auf d e m Markt erschien, wurde Pascal auch auf P C ' s endlich leicht, k o m f o r t a b e l und billig v e r f ü g b a r , was bis dahin ein Privileg von B A S I C war. Bei diesem B u c h wird davon a u s g e g a n g e n , d a ß der Leser eine g e w i s s e Erfahrung im U m g a n g mit C o m p u t e r n hat. Er sollte das Betriebssystem seines C o m puters k e n n e n und gelegentlich P r o g r a m m e in einer P r o g r a m m i e r s p r a c h e geschrieben haben. Dies ist j e d e n f a l l s kein L e h r b u c h über P r o g r a m m i e r e n , sondern eine Darstellung von T u r b o Pascal, der S p r a c h e i g e n s c h a f t e n und der H a n d h a b u n g . Natürlich gibt es eine g r o ß e Zahl von B e i s p i e l p r o g r a m m e n . Bei der A u s w a h l stand aber im Vordergrund, die M ö g l i c h k e i t e n von T u r b o Pascal zu d e m o n s t r i e r e n . B e i m Erlernen einer neuen P r o g r a m m i e r s p r a c h e gilt es einmal, die Eigenschaften der S p r a c h e und im Falle von T u r b o Pascal des ganzen Turbo-Systems k e n n e n z u l e r n e n . B e i m praktischen G e b r a u c h zeigt es sich aber, daß man h ä u f i g bereits G e l e s e n e s n a c h s c h l a g e n muß. D i e s e m B e d ü r f n i s k o m m t die Darstellung in diesem Buch entgegen. Es wird vor allem Wert darauf gelegt, Turbo Pascal in einer Art F o r m e l s a m m l u n g zu beschreiben, relativ kurz, aber übersichtlich und trotzdem vollständig. Den besten G e b r a u c h von diesem Buch wird wohl ein Leser m a c h e n k ö n n e n , der es neben einem P a s c a l - K u r s an einer Schule oder H o c h s c h u l e benutzt. Pascal w u r d e um 1970 von N i k i a u s Wirth konzipiert und f a n d wegen der Klarheit des S p r a c h k o n z e p t e s , der leichten Erlernbarkeit, der E r z i e h u n g zu diszipliniertem P r o g r a m m e n t w u r f und des s e l b s t d o k u m e n t i e r e n d e n P r o g r a m m t e x tes rasch weite Verbreitung. Da Pascal sowohl die G r u n d l a g e f ü r weitere S p r a c h e n t w i c k l u n g e n wie A d a und M o d u l a - 2 g e w o r d e n ist als auch Sprachen wie C und F o r t r a n 7 7 viele p a s c a l ä h n l i c h e S p r a c h e l e m e n t e enthalten, ist die Kenntnis von Pascal eine solide Basis zum Erlernen anderer Sprachen. Mitte der 70er Jahre b e g a n n e n B e s t r e b u n g e n , Pascal international zu normen. D i e s f ü h r t e 1982 zum N o r m e n t w u r f 7 1 8 5 der I S O (International Organization for Standardization), der von vielen nationalen Norminstitutionen übernommen worden ist. Es wird dabei z w i s c h e n der S t u f e 0 und der u m f a s s e n d e r e n Stufe 1 unterschieden. In Deutschland ist er 1983 als N o r m e n t w u r f DIN 66256 in deutscher Sprache v e r ö f f e n t l i c h t w o r d e n [5]. Eine l e h r b u c h m ä ß i g e Beschreibung v o n Standard-Pascal findet sich in [3]. Die E n t w i c k l u n g von T u r b o Pascal selbst ist kurz a u f g e s a g t . Nach einer bei uns relativ w e n i g verbreiteten und kurzlebigen Version 1 erschien Mitte 1984 die Version 2, die eine stürmische Verbreitung f a n d . Im Herbst 1985 gab es die Version 3.0, die sich vor allem durch f o l g e n d e E i g e n s c h a f t e n von der Version 2 unterschied: C o m p i l e r und Editor waren deutlich schneller, mit dem R U N -

12

1. Einleitung

K o m m a n d o k ö n n e n P a r a m e t e r an ein P r o g r a m m übergeben werden, M S - D O S A u f r u f e sind im P r o g r a m m m ö g l i c h , das U m g e h e n mit Dateien wird k o m f o r tabler [4]. Noch heute ist 3.0 der einfachste und billigste Z u g a n g zu T u r b o Pascal. Seit A n f a n g 1988 gibt es die Version 4.0. Damit stellt sich T u r b o Pascal in einer völlig neuen F o r m dar. Nicht nur der C o m p i l e r ist noch schneller g e w o r den, sondern es gibt eine ganz n e u e E n t w i c k l u n g s u m g e b u n g . Die wesentliche Ä n d e r u n g besteht darin, daß ein Binder integriert ist, womit ein großes Prog r a m m in Teile ( s o g e n a n n t e Units) zerlegt w e r d e n kann, die einzeln c o m p i lierbar sind. Da j e d e dieser Units ihr eigenes C o d e s e g m e n t hat, k ö n n e n Prog r a m m e auch größer als 64 K B sein. Im Herbst 1988 erschien die Version 5.0 mit einer weiter verbesserten E n t w i c k l u n g s u m g e b u n g . Vor allem b e m e r k e n s wert ist ein integrierter Debugger. Units können nun als Overlays spezifiziert werden, womit ein P r o g r a m m a u c h größer als 640 KB werden k a n n . Während die Versionen 1, 2 und 3 j e w e i l s die v o r h e r g e h e n d e ablösten, werden die Versionen 3 und 4 n e b e n e i n a n d e r a n g e b o t e n . Das m a c h t insofern einen Sinn, als die Version 3 billiger, anspruchsloser bezüglich Speicherplatz und auch e i n f a c h e r zu h a n d h a b e n ist. Ein A n f ä n g e r wird lange Zeit mit den M ö g lichkeiten a u s k o m m e n , die die Version 3 bietet. Erst bei a n s p r u c h s v o l l e r e m Gebrauch k o m m e n die E r w e i t e r u n g e n und Vorzüge der Versionen 4 - 7 richtig zum tragen. Die größte Zäsur bei der E n t w i c k l u n g von T u r b o Pascal liegt zwischen den Versionen 3.0 und 4.0, während sich 5.0 rein äußerlich wie 4.0 darbietet. Seit 1989 gibt es eine Version 5.5. Mit ihr wird der Z u g a n g zum objektorientierten P r o g r a m m i e r e n ermöglicht. Ende 1990 kam die Version 6.0 heraus. G e g e n ü b e r 5.5 wird Pascal nun in einer m a u s g e t r i e b e n e n E n t w i c k l u n g s u m g e b u n g e n t s p r e c h e n d dem S A A - S t a n dard geboten. Der Editor erlaubt es, m e h r e r e Texte gleichzeitig zu halten. Weiter gibt es eine a s m - A n w e i s u n g , mit der die E i n b i n d u n g von P r o g r a m m teilen im A s s e m b l e r erleichtert wird. Dieses Buch beschreibt die Version 6.0. Neben 6.0 gibt es noch T P W , d.h. Turbo Pascal f ü r W i n d o w s , dessen Entwicklungssystem vollständig in die W i n d o w s - U m g e b u n g eingebettet ist. Das neue Borland Pascal 7.0 bietet g e n a u e r drei C o m p i l e r (in fünf v e r s c h i e d e n e n Versionen): T U R B O . E X E f ü r D O S im Real Mode, BP.EXE f ü r D O S im Protected Mode, B P W . E X E f ü r W i n d o w s sowie die beiden K o m m a n d o z e i l e n - V e r s i o n e n B P C . E X E f ü r D O S im Protected M o d e und T P C . E X E f ü r D O S im Real M o d e . Hier wird nur T U R B O . E X E behandelt und als T u r b o Pascal 7.0 bezeichnet. Der Unterschied zu 6.0 wird in K a p . 27 beschrieben.

2. Die Prinzipien von Pascal und seiner Umgebung Bevor ab Kapitel 4 auf die Einzelheiten von Pascal eingegangen wird, sei an einem Beispiel erläutert, was die Prinzipien dieser Sprache sind und unter welcher Umgebung man sie bei Turbo Pascal benutzen kann. Beispiel 2.1 : (* Das ist ein Kommentar. *) program willkommen(input,output); uses crt ;

(* 1 *) (* 4 *)

(*********** vereinbarungsteil ***********) (* 2 *) type wort = string[20]; (* 2a *) var name : wort; (* 2b *) procedure eingäbe(var name : wort); begin clrscr; (* Bildschirm löschen*) write('Ihr Name bitte: '); readln(name); end; procedure starline(n: integer); var i:integer; begin for i := 1 to n do write('*'); writeln; end;

(* 2c *) (* 2d *)

(* 2c *)

procedure zaehle(n: integer); (* 2c *) var i:integer; begin for i := 1 to n-1 do begin write(i:3); delay(500); end; writeln; writeln('Da war doch noch etwas ...'); delay(2000); (* 2d *) writeln; writeln(n: 3 *n); end;

14

2. Die Prinzipien von Pascal und seiner Umgebung

procedure begruessung(name : wort); (* 2c *) var c:char; n : integer; begin clrscr;(* Bildschirm löschen *) (* 2d *) starline(50); lowvideo; writeln('Hallo '); (* 2d *) highvideo; writeln(name : 2 0); starline(50); writeln('Kann ich Ihnen etwas vor(er)zählen?'); writeln(' J/N'); read(c); case c of ' j ', 'J': begin writeln('Wie weit bitte?'); readln(n); zaehle(n); delay(1000); writeln('Wir sehen uns später.'); writeln('Bye Bye'); end; else writeln('Dann eben nicht!'); writeln('Bye Bye': 40); end (* case *); end; (* begrüßung *) (********** A n w e i s u n g s t e i l *************) begin eingäbe(name); begruessung(name); readln;

(* 3 *) (* 3a *) (* 3a *)

Ein Pascal-Programm enthält die drei Teile Programmkopf (* 1 *), Vereinbarungsteil (* 2 *) und Anweisungsteil (* 3 *). Der Programmkopf (* 1 *) kennzeichnet den Beginn eines Programms, gibt ihm einen (bedeutungslosen) Namen, der aber darüber hinaus im Programm nicht anderweitig verwendet werden darf. Eines der wichtigsten Prinzipien von Pascal ist, daß alle im Programm benutzten Namen vor ihrem Gebrauch erklärt werden müssen. Die Folge davon ist die strikte Zweiteilung in Vereinbarungsteil (* 2 *) und Anweisungsteil (* 3 *). Im Anweisungsteil dürfen nur Namen verwendet werden, die im Vereinbarungsteil vereinbart worden sind: - die Prozeduren eingäbe, begruessung, zaehle und starline, - die Variable name.

2. Die Prinzipien von Pascal und seiner Umgebung

15

Im Vereinbarungsteil ( * 2 * ) sind eingeführt: -

Typname w o r t ( * 2a * ) Variablenname n a m e ( * 2b * )

- Prozedurnamen eingäbe, begruessung, z a e h l e und starline ( * 2c *), wobei b e g r u e s s u n g die (zuvor erklärten) Prozeduren s t a r l i n e und z a e h l e benutzt. Bei den Namen sollte man genauer unterscheiden zwischen den Namen, die der Benutzer selbst einführt (wie alle eben genannten) und Namen, die standardmäßig vorhanden sind (wie die Typnamen i n t e g e r , c h a r oder die Pro-

zeduren clrscr, read, write, lowvideo, highvideo, delay), um deren Vereinbarung sich der Benutzer nicht zu kümmern braucht. Der Anweisungsteil ( * 3 * ) beschreibt dann mit Hilfe von Anweisungen, was das Programm mit den eingeführten Objekten tun soll. In unserem Beispiel sind es nur die beiden Prozeduranweisungen ( * 3a * ) e i n g ä b e und b e -

gruessung. Zum Schreiben des Programms gibt es in der Umgebung von Turbo Pascal einen komfortablen Editor. Den Pascaltext nennt man auch Quelltext oder Quellprogramm. Der Quelltext sollte das Attribut .PAS haben. Aus dem Quellprogramm muß durch den Compiler der Objektcode erzeugt werden, was in zwei Stufen erfolgt. Der Compiler erzeugt zunächst einen unvollständigen, noch nicht lauffähigen Objektcode, in dem Verweise auf Objekte enthalten sind, die nicht im Quelltext erklärt sind, z. B. die Standardnamen von Typen

(integer, char) und Prozeduren (clrscr, read, write, d e l a y usw.). Diese sind in den Units der Turbo Pascal Library T U R B O . T P L hinterlegt, und der Binder muß den vom Compiler erzeugten Objektcode aus den Units aus T U R B O . T P L ergänzen. Im Prinzip muß der Compiler auf einen vollständigen Quelltext treffen. Bei großen Programmen ergibt sich das Problem, ob man nicht auch Teile des Programms einzeln compilieren und für den Binder hinterlegen kann. Das ist in der Tat möglich. Solche Teile heißen Units und ihr vom Compiler erzeugter Objektcode hat das Attribut .TPU (Turbo Pascal Unit). T P L ist nichts anderes als eine Sammlung solcher TPUs. Werden Objekte aus solchen TPUs im Programm gebraucht, ist auf sie durch u s e s hinter dem Programmkopf hinzuweisen. Im obigen Beispiel ist das in ( * 4 * ) gemacht worden. In der Unit CRT.TPU sind die in ( * 2d * ) benutzten Prozeduren clrscr d e l a y ( 2000 lowvideo highvideo

= clear screen Bildschirm löschen ) = anhalten um 2000 ms = halbe Helligkeit = hervorgehobene Helligkeit

definiert, und deswegen u s e s

c r t ; in ( * 4 *).

16

2. Die Prinzipien von Pascal und seiner Umgebung

Falls Sie ein Aufsteiger von der Version 3 sind, werden Sie feststellen müssen, daß unter der Version 4 nicht mehr alle für Version 3 geschriebenen Programme lauffähig sind. Falls Sie Schwierigkeiten bekommen sollten, versuchen Sie es mit u s e s t u r b o 3 ; in dieser Unit sind einige der bei der Version 3.0 üblichen Namen definiert (s. Kap. 19.3). Außerdem gibt es noch Compiler-Optionen und Compiler-Direktiven. Beide beeinflussen das Verhalten des Compilers in einer bestimmten Weise. Die Optionen können bei dem im folgenden Kapitel zu besprechenden Hauptmenü angegeben werden, die Direktiven stehen im Quelltext und haben die Form spezieller Kommentare. Sie sind im Anhang D zusammengestellt.

3. Die Turbo-Pascal-Umgebung IDE In d i e s e m Kapitel soll ein g r o b e r Ü b e r b l i c k ü b e r d i e U m g e b u n g g e g e b e n werd e n , um m ö g l i c h s t s c h n e l l mit T u r b o P a s c a l a r b e i t e n zu k ö n n e n . M a n kann P a s c a l in z w e i V e r s i o n e n b e n u t z e n , u n t e r e i n e r i n t e g r i e r t e n , m e n ü g e t r i e b e n e n Entwicklungsumgebung ( T U R B O . E X E ) , kurz IDE (Integrated Development E n v i r o n m e n t ) g e n a n n t , u n d als K o m m a n d o z e i l e n - V e r s i o n ( T P C . E X E ) . In dies e m K a p i t e l geht es nur u m I D E . D i e T P C - V e r s i o n w i r d in K a p . 20 b e h a n d e l t . Z u v o r e i n e B e m e r k u n g ü b e r die I n s t a l l a t i o n . T u r b o P a s c a l 6 . 0 wird auf zwei 3 , 5 " - D i s k e t t e n g e l i e f e r t , auf d e n e n sich die z u m S y s t e m g e h ö r e n d e n Dateien in e i n e r a r c h i v i e r t e n F o r m b e f i n d e n . D a s P r o g r a m m I N S T A L L . E X E besorgt die Installation auf e i n e r F e s t p l a t t e und e n t a r c h i v i e r t die D a t e i e n . N a c h d e m Start von I N S T A L L ist der N a m e d e s V e r z e i c h n i s s e s a n z u g e b e n , in d e m sich d a n a c h T u r b o P a s c a l 6 . 0 b e f i n d e n soll, z.B. C : \ T P 6 . I N S T A L L legt d a n n in d e m V e r z e i c h n i s T P 6 U n t e r v e r z e i c h n i s s e an. A u f d e r F e s t p l a t t e sollten d a f ü r n o c h m i n d e s t e n s 3 M B zur V e r f ü g u n g s t e h e n . N a c h d e m Start mit T U R B O m e l d e t sich T u r b o P a s c a l mit d e m H a u p t m e n ü v o n Bild 3.1. D e r B i l d s c h i r m zeigt das E d i t - F e n s t e r , w o b e i P r o g r a m m n a m e d e r N a m e des P r o g r a m m e s ist, das in d e n E d i t o r g e l a d e n w o r d e n ist, e n t w e d e r n a c h F 3 = O p e n o d e r d u r c h N e n n u n g b e i m A u f r u f T U R B O P r o g r a m m n a m e . Ist b e i m Start mit T U R B O kein P r o g r a m m g e n a n n t w o r d e n , steht in der K o p f z e i l e des Edit-Fensters NONAMEOO.PAS.

= :[

File

F l Help

Edit

F2 Save

Search

Run Compile | Programmname |

F3 Open

Debug

A l t - F 9 Compile

Options

F9 Make

Window Help =1=[ J ] =

F10 Menu

Bild 3.1: Das Hauptmenü von Turbo Pascal 6.0 I D E ist e i n e sehr k o m f o r t a b l e , m e n ü g e s t e u e r t e E n t w i c k l u n g s u m g e b u n g , die s o w o h l ü b e r Tasten als a u c h m i t e i n e r M a u s b e n u t z t w e r d e n k a n n . Z u m L i e f e r u m f a n g v o n T u r b o P a s c a l 6 . 0 g e h ö r t ein P r o g r a m m T P T O U R . E X E , ein Lern-

18

3. Die Turbo-Pascal-Umgebung IDE

Programm zur Einführung in die Benutzung von IDE, das einem Neuling sehr empfohlen werden kann. Zuerst sei kurz die Bedienung mit Tasten erläutert. Mit Alt-Anfangsbuchstabe gelangt man zu den Punkten des Hauptmenüs (zu = mit Alt-Blank), wobei jeweils ein neues Fenster für weitere Aktivitäten geöffnet wird. Durch Eingabe eines der dort hervorgehobenen Anfangsbuchstaben oder den Cursortasten, gelangt man dorthin, wobei manchmal ein neues Fenster erscheint. Wegen des exzellenten Hilfesystems sind die folgenden Erläuterungen kurz gehalten. Wo immer man sich in dieser Hierarchie von Fenstern befindet, mit Fl bekommt man nähere Erklärungen zu dem Menüpunkt, an dem sich der Cursor gerade befindet, und Esc führt zurück zum Hauptmenü. Der Editor erlaubt mehrere Fenster übereinander. Mit Alt-Fensternummer gelangt man zu einem gewünschten Fenster. Die Nummer steht oben rechts. Es sollte schließlich noch darauf hingewiesen werden, daß sich das Hilfesystem ( F l ) nicht nur auf die Entwicklungsumgebung, sondern auf Pascal selbst bezieht. Befindet man sich im Editor, so kann man sich mit dem Tastenbefehl Ctrl-Fl Erklärungen zu den Standardnamen (Prozeduren, Funktionen, Units, Datentypen, Variablen), aber auch Wortsymbolen (s. Bild 4.1) verschaffen. Dabei muß sich der Cursor auf dem betreffenden Namen befinden. Statt CtrlFl kann man den Namen auch mit der Maus (links - rechts) anklicken. Um einen Menüpunkt zu bezeichnen, wird hier der Weg dorthin wie ein Pfadname bezeichnet, z.B. erreicht man OptionslCompiler!Destination Memory via Compile im Hauptmenü (Alt-O) mit folgendem C und D. So findet man über HelplContents!Menüs and hot keys zu einer Erklärung der Tastenbefehle. Die meisten Leser werden Turbo Pascal 6.0 wohl aber mit der Maus bedienen wollen. Im folgenden daher das wichtigste für den Mausgebrauch. Die meisten Fenster haben die Form von Bild 3.1. Befindet sich der Mauszeiger auf der oberen Doppellinie, kann man dieses Fenster bei gedrückter Maustaste verschieben. Bei der rechten unteren Ecke kann man ebenso das Fenster verkleinern bzw. vergrößern. Durch Anklicken von [ H ] links oben verschwindet das Fenster. Ein Fenster, das eine Eingabe verlangt, heißt Dialogfenster. Diese kann man nur über OK bzw. Cancel verlassen. Kleine Fenster mit einem Symbol [$] rechts oben kann man durch Anklicken dieser Stelle auf den ganzen Bildschirm vergrößern. Die meisten Fenster, insbesondere bei Help, zeigen nur einen Teil des Inhalts. Durch die Rollsymbole der Leiste unten und rechts kann man den Inhalt rollen. Liegen mehrere Editfenster übereinander, sieht man links oben die Ecken der tiefer liegenden Fenster. Durch Anklicken einer Ecke gelangt man dorthin. Im folgenden wird ein grober Überblick darüber gegeben, welche Leistungen über die Punkte des Hauptmenüs zu erreichen sind.

19

= (Alt-Blank) ist das sog. Systemmenü. About zeigt Copyright und Versionsnummer, Refresh Display restauriert den IDE-Bildschirm, Clear Desktop schließt alle Fenster und löscht alle Aufzeichnungslisten. File

lädt und sichert Dateien, behandelt Directories, ruft MS-DOS auf und verläßt Turbo Pascal, New eröffnet ein neues Editfenster, Open (F3) ermöglicht, einen Text in das Editfenster zu laden, Save (F2) speichert die Datei im aktuellen Fenster. Im Falle einer Datei NONAMEOxx.PAS wird man aufgefordert, einen Zielnamen anzugeben, Save as verlangt den Zielnamen der zu speichernden Datei, Change Dir wechselt in ein anderes Verzeichnis und Laufwerk, Print gibt den Inhalt des aktuellen Editfensters auf dem Drucker aus, Get Info liefert Angaben über die aktuelle Datei, DOS Shell wechselt von Turbo Pascal zu MS DOS (und Rückkehr mit exit), Exit (Alt-X) verläßt Turbo Pascal.

Edit

eröffnet das Edit-Menü, das Funktionen zum Ausschneiden, Kopieren und Einfügen von Textblöcken aus einem Editfenster in das sog. Clipboard und umgekehrt bietet. Die Einzelheiten sind im Kap. 26 beschrieben.

Search

erlaubt Textstellen zu finden (und ersetzen), Prozedur- und Funktionsvereinbarungen sowie Fehlerstellen im Programm zu finden.

Run

startet ein Programm (Run/Run) und den Debugger (s. Kap. 25.2).

Compile

compiliert ein Programm. Compile (Alt-F9) compiliert die im Editor stehende Datei und kehrt im Fehlerfall in den Editor zurück, Make (F9) compiliert und bindet ein auf mehrere Dateien verteiltes Programm (s. Kap. 19.5), Build bewirkt Compilieren und Binden aller an einem Projekt beteiligten Dateien (s. Kap. 19.5), Destination erlaubt die Wahl, ob in den Arbeitsspeicher oder auf Diskette compiliert wird, Primary file erlaubt die Angabe einer Datei .PAS, auf die sich Compilieren und Binden bei Make und Build beziehen. Wird keine angegeben, wird die Datei im Editor genommen.

Debug

Steuerung des integrierten Debuggers. Evaluate!Modify (Ctrl-F4) berechnet einen Ausdruck und zeigt den

20

3. Die Turbo-Pascal-Umgebung IDE Wert an, den man auch ändern kann, Watches erzeugt ein Menü zur S t e u e r u n g von H a l t e p u n k t e n und Watch-Ausdrücken, Toggle Breakpoint (Ctrl-F8) setzt/löscht H a l t e p u n k t e an der Cursorposition, Breakpoints e r ö f f n e t ein Dialogfenster zur B e a r b e i t u n g von Haltepunkten.

Options

bietet K o m m a n d o s , um Voreinstellungen anzusehen oder zu ändern. Compiler e r ö f f n e t ein D i a l o g f e n s t e r zur Eingabe von C o m p i l e r o p tionen, Memory Sizes erlaubt die Einstellung der Stack- und H e a p g r ö ß e , Linker bietet ein D i a l o g f e n s t e r zur Einstellung des integrierten Binders. Debugger betrifft Einstellungen des D e b u g g e r s (s. Kap. 25.2), Directories betrifft A n g a b e n , wo die Dateien zum C o m p i l i e r e n / B i n den zu f i n d e n und wohin Ausgaben zu schreiben sind, Environment Dialogfenster f ü r globale Einstellungen von I D E , Save options zur Sicherung der Einstellungen bei Search/Find und Search/Replace sowie Compile/Destination und CompilelPrimary file, Retrieve options können die mit Options/Save options v o r g e n o m m e nen A n g a b e n wieder laden.

Window

K o m m a n d o s zur Verwaltung der Fenster. Size/Move (Ctrl-F5) ändert Position und Größe des aktuellen Fensters, Zoom (F5) vergrößert das aktuelle Fenster bis zum M a x i m u m , Tile zeigt alle o f f e n e n Editfenster ohne Ü b e r l a p p u n g , Cascade stapelt alle offenen Editfenster, Next (F6) A k t i v i e r u n g des nächsten Fensters, Previous (Shift-F6) das zuletzt aktive Fenster wird wieder aktiviert, Close (Alt-F3) schließt das aktuelle Fenster, Watch ö f f n e t und aktiviert das Watchfenster, Register ö f f n e t und aktiviert das Registerfenster (im Z u s a m m e n hang mit dem integrierten Assembler), Output ö f f n e t und aktiviert das O u t p u t f e n s t e r , das D O S - K o m m a n dos und die P r o g r a m m a u s g a b e zeigt, Call Stack (Ctrl-F3) öffnet ein Fenster, das alle bisher v o m Prog r a m m a u f g e r u f e n e n Prozeduren mit den übergebenen P a r a m e t e r n zeigt, User screen (Alt-F5) zeigt d i e P r o g r a m m a u s g a b e auf dem ganzen B i l d s c h i r m an, List (Alt 0) listet alle o f f e n e n Fenster auf. Durch D o p p e l k l i c k gelangt m a n zu j e d e m aufgelisteten Fenster.

Help

öffnet ein Fenster, über das zum Hilfesystem zugegriffen werden kann.

3. Die Turbo-Pascal-Umgebung IDE

21

Contents zeigt das Hauptverzeichnis an, über das man zu jedem Teil des Hilfesystems kommt (jeweils durch doppeltes Anklicken der gewünschten Zeile), Index ( S h i f t - F l ) liefert eine vollständige Liste von Hilfe-Schlüsselwörtern, Topic search ( C t r l - F l ) zeigt sprachbezogene Hilfetexte zum aktuellen Element im Quelltext an, Previous Topic ( A l t - F l ) ö f f n e t das Helpfenster mit dem zuletzt gezeigten Hilfetext, Help on help ( F l ) öffnet einen Bildschirm, in dem der Umgang mit dem Hilfesystem erklärt wird. Für Version 7.0 siehe Kapitel 27. Zusammenfassung In sehr vielen Fällen wird man mit den folgenden wenigen Handgriffen auskommen: 1. Start mit T U R B O . 2. Laden des Quellprogramms in den Editor mit File/Open (F3) bzw. Programm neu erstellen. (1. und 2. auch zusammen mit Start C> T U R B O Quellprogrammname). 3. Run/Run (Ctrl-F9) Compilieren, Binden und Starten des Programms. 4. Eventuell Ändern des Quellprogramms mit dem Editor. 5. Run/Run usw 3. und 4. 6. Sichern des Quellprogramms mit File/Save (F2). 7. Verlassen von Turbo Pascal mit Alt-X. Mitunter wird man folgende Eigenschaft von Turbo Pascal 6.0 lästig finden. Ein laufendes Programm schreibt seine Ausgaben in das Outputfenster, das während des Programmlaufes sichtbar ist. Am Programmende kehrt IDE aber sofort zum Editfenster zurück, d.h. man sieht die produzierte Ausgabe nicht mehr. Dann muß man sich über Window/Output das Outputfenster zeigen lassen. Wem das zu umständlich oder lästig ist, sei folgendes empfohlen: Er beende das Programm mit r e a d l n ; wodurch das laufende Programm erst durch < R E T U R N > beendet wird, und das Outputfenster so lange sichtbar bleibt. Deshalb enden viele Programme dieses Buches mit r e a d l n . Deutlicher noch ist writeln(''); am Programmende.

readln;

4. Aufbau von Pascal-Programmen In Kapitel 2 waren die Prinzipien von Pascal und in Kapitel 3 die Turbo-Umgebung beschrieben worden. Wir wollen nun die Verhältnisse etwas detaillierter betrachten. Dazu nehmen wir wieder ein Beispiel.

Beispiel

4.1:

program lieblingszahl_erraten (input, Output); uses crt; (* Das Programm soll folgendes leisten: - es begrüßt Sie freundlich, - es soll Ihre Lieblingszahl geraten werden, - es äußert sich zu dem Ergebnis des Ratens. *)

(********** vereinbarungsteil ******************) const lieblingszahl = 367; var zahl, anzahl : integer; procedure begruessung; begin clrscr; writeln('Hallo Partner!'); writeln('Es freut mich, daß Sie mich zum '); writeln(' Laufen gebracht haben.'); writeln; writeln('Raten Sie meine Lieblingszahl!'); writeln('Zur Hilfe: Sie ist kleiner als tausend.'); end; procedure beurteilung(a:integer); begin case a of I..9 : writeln('Das war Zufall.'); 10 : writeln('Fabelhaft!'); II,12 : writeln('Ganz gut'); 13,14 : writeln('Mäßig'); eise writeln('Miserabel') end; end;

24 begin (************* A n w e i s u n g s t e i l begruessung; a n z a h l := 0 ; repeat readln(zahl); if zahl < lieblingszahl then writeln('Größer'); if zahl > lieblingszahl then writeln('Kleiner'); a n z a h l := a n z a h l +1; u n t i l zahl = lieblingszahl; beurteilung(anzahl); writeln('Bye Bye'); readln; end.

************)



Wenn m a n eine P r o g r a m m i e r s p r a c h e beschreiben will, ist es z w e c k m ä ß i g , z w i s c h e n Syntax u n d Semantik zu unterscheiden. D i e Syntax einer Sprache ist die G r a m m a t i k , d. h. die Regeln, nach denen m a n korrekte Texte in dieser Sprache bilden k a n n . Die Syntax von Pascal legt f e s t , wie P a s c a l - P r o g r a m m e gebildet w e r d e n k ö n n e n . Syntaktisch korrekte P r o g r a m m e können fehlerfrei compiliert w e r d e n . Die Semantik ist die B e d e u t u n g , die eine syntaktisch korrekte F o r m u l i e r u n g hat. Die Beschreibung der Syntax ist relativ e i n f a c h . Daf ü r haben sich die s o g e n a n n t e n B N F ( B a c k e s - N a u r - F o r m , eine textuelle Beschreibung) und S y n t a x d i a g r a m m e (als graphische B e s c h r e i b u n g ) eingebürgert. In d i e s e m B u c h werden S y n t a x d i a g r a m m e v e r w e n d e t , die anschaulicher sind. Wer diese nicht zu lesen versteht, wird auf den A n h a n g A verwiesen, in dem die S y n t a x d i a g r a m m e z u s a m m e n g e f a ß t sind. Im l a u f e n d e n Text werden S y n t a x d i a g r a m m e zur Erläuterung eines b e s t i m m t e n S a c h v e r h a l t e s benutzt. Es ist m i t u n t e r nicht z w e c k m ä ß i g , dabei gleich die a l l g e m e i n s t e Form a n z u g e ben. M a ß g e b e n d ist also die Gesamtdarstellung im A n h a n g A. Die S e m a n t i k läßt sich leider nicht so präzise und k n a p p f o r m a l darstellen. Zur B e s c h r e i b u n g der B e d e u t u n g einer P a s c a l - F o r m u l i e r u n g n e h m e n wir die deutsche U m g a n g s s p r a c h e . Der A u f b a u eines P r o g r a m m s ist definiert durch

4. Aufbau von Pascal-Programmen

25

Nach (4-1) ist der Programmkopf überhaupt nur Dekoration. Wir werden ihn immer anführen, weil er den Programmanfang deutlich macht und in Standard-Pascal auch zwingend vorgeschrieben ist. Seine Definition ist:

Das Beispiel 4.1 kann also auf dreierlei Arten anfangen: ohne Programmkopf,

program l i e b l i n g s z a h l _ e r r a t e n ; program l i e b l i n g s z a h l _ e r r a t e n

(input, Output);

In diesem Buch werden wir immer die Form p r o g r a m p r o g r a m m - n a m e ; verwenden. Der Programmname ist für das weitere Programm ohne Belang. Er darf nur nicht im Programm noch einmal für etwas anderes verwendet werden. Die dahinter (möglicherweise) aufführbaren Filenamen beschreiben die Umgebung, mit der das Programm kommuniziert. Auf den Programmkopf können sogenannte uses-Klauseln folgen, die folgende Form haben: uses-klausel

Sie haben den Zweck, auf im Programm benutzte aber anderweitig definierte Namen zu verweisen. Das hängt mit dem wichtigen Begriff Unit zusammen, der in Kapitel 19 behandelt werden wird. Im Beispiel 4.1 heißt u s e s c r t ; , daß die Prozedur c l r s c r ( = clear screen = Bildschirm löschen) benutzt werden kann, die in der Unit crt definiert ist. Nach (4-1) folgt nach Programmkopf und uses-Klauseln das eigentliche Programm in Form eines Blockes:

26

4. Aufbau von Pascal-Programmen (4-4)

block vereinbarungsteil

anweisungsteil

Sein Aufbau entspricht dem Pascal-Prinzip "Zuerst alle Größen vereinbaren, dann erst benutzen". Unter der neutralen Bezeichnung Größe verbergen sich genauer die folgenden Dinge:

(4-5)

Im Vereinbarungsteil des Beispiels 4.1 wurden benutzt: - Marken (keine), - Konstanten lieblingszahl), - Datentypen (Standardname i n t e g e r ) , - Variable (zahl, anzahl), - Prozeduren ( b e g r u e s s u n g , b e u r t e i l u n g , Standardprozeduren writeln, readln, clrscr), - Funktionen (keine), - Methoden (keine). Diese sechs Dinge werden mit Namen bezeichnet, und es muß gesagt werden, wie ein Name zu bilden ist.

4. Aufbau von Pascal-Programmen

27

buchstabe

(4-6)

ziffer

(4-7)

(4-8)

Name oder Bezeichner (identifier) ist ein zentraler Begriff einer Programmiersprache. Damit kann ein Objekt der Sprache benannt, identifiziert werden. Im obigen Beispiel die Konstante l i e b l i n g s z a h l , die Variable a n z a h l , die Prozedur b e u r t e i l u n g oder (weil der Unterstrich zu den Buchstaben gehört) der Programmname l i e b l i n g s z a h l _ e r r a t e n . Ein Name beginnt also mit einem Buchstaben, auf den Buchstaben oder Ziffern folgen können. Dabei wird kein Unterschied zwischen Groß- und Kleinschreibung gemacht: ZAHL, Z a h l und z a h l bezeichnen dasselbe Objekt. Dagegen sind NEU-ULM oder 1 2 a keine gültigen Namen, weil sie entweder andere Zeichen als Buchstaben und Ziffern enthalten oder nicht mit einem Buchstaben anfangen. Übrigens sind auch die Umlaute und ß in Namen nicht erlaubt (daher im Beispiel b e g r u e s s u n g und nicht b e g r ü ß u n g !). Die Länge eines Namens wird durch die maximale Zeilenlänge von 127 Zeichen begrenzt. Es sind nur die ersten 63 Zeichen signifikant. Nach (4-5) ist die Reihenfolge der Vereinbarungen beliebig. Das ist ein wesentlicher Unterschied zu Standard-Pascal, wo strikt die Reihenfolge Marken, Konstanten, Datentypen, Variablen gefolgt von Prozeduren und Funktionen in

28

4. Aufbau von Pascal-Programmen

beliebiger Reihenfolge einzuhalten ist. Es gilt jedoch auch in Turbo Pascal die Regel: Ein Name kann erst benutzt werden, wenn seine Definition im Programmtext vorausgegangen ist. Eine Ausnahme davon gibt es nur im Falle der Rekursion (Kap. 11.4). In diesem Buch werden wir uns an Standard-Pascal halten und davon nur abweichen, wenn die Großzügigkeit von Turbo Pascal spürbare Vorteile bietet. Es gibt eine Reihe von Wortsymbolen, die nicht als Namen verwendet werden dürfen (Bild 4.1). In Programmtexten dieses Buches werden Wortsymbole fett geschrieben.

and case div end goto inline nil or record shr type var

array const do file if interface not packed repeat string unit while

asm constructor downto for implementation label object procedure set then until with

begin destructor else function in mod of program shl to uses xor

Daneben gibt es die Standarddirektiven absolute forward virtual

assembler interrupt

external near

far private

Bild 4.1: Die Wortsymbole von Turbo Pascal 6.0 Natürlich dürfen sie auch großgeschrieben nicht in anderer Bedeutung verwendet werden. Für Version 7.0 siehe Kapitel 27. Daneben gibt es eine Reihe von Namen, die eine feste, vordefinierte Bedeutung wie z. B. i n t e g e r oder r e a d haben. Es sei ausdrücklich auf den prinzipiellen Unterschied zwischen Wortsymbolen und Standardnamen hingewiesen. Jede falsche Verwendung eines Wortsymbols führt zu einem Compilerfehler. Jede neue Definition eines Standardnamens setzt einfach die ursprüngliche Bedeutung außer Kraft. Wenn man also aus Versehen Donnerstag meint

4. Aufbau von Pascal-Programmen

29

und mit d o abkürzt, führt das wegen des Wortsymbols d o zu einem Fehler. Wenn man eine Variable

random

nennt

( v a r random: r e a l ) ,

so kann eben

nicht mehr die Standardfunktion r a n d o m , ein Zufallsgenerator, verwendet werden. D i e Standarddirektiven von Bild 4.1 nehmen eine Zwitterstellung ein. Sie können w i e die anderen Standardnamen v o m Benutzer neu definiert werden. Ein Pascal-Programm läßt sich formatfrei schreiben, und man sollte dies der besseren Lesbarkeit w e g e n tun. Es ist lediglich darauf zu achten, daß die Sprachelemente

(Namen,

Konstanten,

Wortsymbole)

ununterbrochen

zu

schreiben und voneinander durch wenigstens ein Trennzeichen (Blank, Zeilenwechsel oder Kommentar) zu trennen sind. Eine Programmzeile kann maximal 127 Zeichen lang sein. Eine längere Zeile, die der Turbo Editor durchaus erlaubt, führt beim Compilieren zu der Fehlermeldung " L i n e too long". Ein Kommentar wird durch g e s c h w e i f t e Klammern |

} oder ( *

* ) einge-

rahmt:

{Das i s t ein Kommentar,}

(*und das auch.*)

Bei einer Schachtelung ist j e w e i l s das andere Begrenzerpaar zu verwenden:

{Das i s t ein (*ganz s p e z i e l l e r * ) Kommentar.} Einen praktischen Nutzen hat man z. B. dann, wenn man durchweg ( * nutzt. Dann kann man einfach mit {

* ) be-

) ein Stück Quelltext beim Compilieren

ausblenden. Ein Kommentar kann überall stehen, w o ein Trennzeichen erlaubt ist. Man sollte keinen Kommentar mit dem Zeichen $ beginnen, w e i l { $ . . . } (*$...*)

bzw.

zu den Compiler-Direktiven gehören und beim K o m p i l i e r e n ganz

bestimmte Wirkungen hervorrufen (s. Anhang D ) .

5. Einfache Standard-Datentypen Der Datentyp legt den Wertebereich fest, aus dem eine Variable dieses Typs einen Wert bekommen kann. Es ist zugleich anzugeben, wie die Konstanten dieses Typs zu schreiben sind. Datentypen werden durch Namen bezeichnet. Gewisse elementare Datentypen stehen standardmäßig zur Verfügung und brauchen vom Benutzer nicht ausdrücklich definiert zu werden. Will der Benutzer darüber hinaus eigene Datentypen einführen, so ist dies im Typdefinitionsteil des Vereinbarungsteils anzugeben. Wie dies zu machen ist, wird in Kap. 12 beschrieben werden. Hier werden jetzt die standardmäßig vorhandenen einfachen Datentypen aufgezählt.

5.1 Ganze Zahlen Ganze Zahlen werden intern durch Dualzahlen dargestellt. Eine Dualzahl von n Bit kann man auf zweierlei Arten interpretieren: n Bit ohne Vorzeichen: n Bit mit Vorzeichen:

0 .. 2 n 1 ,n-l -2 n ~' ..

Demzufolge gibt es mehrere Typen für ganze Zahlen (Bild 5.1). Typname byte word shortint integer longint

Format 8 Bit ohne Vorzeichen 16 Bit ohne Vorzeichen 8 Bit mit Vorzeichen 16 Bit mit Vorzeichen 32 Bit mit Vorzeichen

Zahlenbereich 0 .. 255 0 .. 65535 -128 .. 127 -32768 .. 32767 - 2147483648 .. 2147483647

Bild 5.1: Formate für ganze Zahlen

vorzeichenlose ganze Zahl

byte 8 Ziffernstellen word 16 Ziffernstellen

ganze Zahl mit Vorzeichen Ziffernstellen Vorzeichenstelle (0 = + 1 =-)

shortint integer longint

Bild 5.2: Interne Darstellung der ganzen Zahlen

7 Ziffernstellen 15 31

32

5. Einfache Standard-Datentypen

Z u r Erinnerung seien hier kurz einige shortint-Werte angeführt. Bei einem negativen Wert ist das Vorzeichen 1 und die Ziffernstellen sind das 2er-Komplement des Betrages: 01101101 10010011 11111111 11111110 10000000 01111111

= = = = = =

+109 -109 -1 -2 -128 (kleinste negative Zahl) +127 (größte positive Zahl)

Wie man sich die einzelnen Dualstellen einer ganzen Zahl per Programm ansehen kann, zeigt Beispiel 9.4. Die Konstanten dieser Typen kann man dezimal oder hexadezimal angeben, hexziffer

:

(5-1)

Konstanten in dezimaler Schreibweise können ein Vorzeichen haben: 234

-4567

+1988

Beispiele für Konstanten dieses Typs in hexadezimaler Schreibweise sind: $0012

$2AB5

Konstanten in hexadezimaler Schreibweise haben kein Vorzeichen.

5.2 Gebrochene Zahlen Wird bei einem r e a d ( x ) eine Konstante eingegeben, als $FFFFFFFF ist, werden bei x : b y t e und x : (rechten) 8 Bit, bei x : w o r d und x : i n t e g e r x : l o n g i n t alle 32 Bit als Wert genommen. Ist stante größer, kommt es zu einem Laufzeitfehler.

33

deren Dualzahl kleiner s h o r t i n t die letzten die letzten 16 Bit, bei die eingegebene Kon-

Wenn es auf die Details nicht ankommt, wird für die obigen fünf Typen von ganzen Zahlen in diesem Buch der Sammelbegriff i n t e g e r verwendet.

5.2 Gebrochene Zahlen Gebrochene oder allgemein reelle Zahlen werden intern dargestellt in einer halblogarithmischen Form, bestehend aus Mantisse und Exponent. Die Mantisse gibt die gültigen Ziffernstellen, der Exponent den Zahlenbereich an. Es gibt die folgenden Typen (E bedeutet *10 hoch):

Typname

Dezimalstellen

single real double extended comp

7- 8 11- 12 15- 16 19- 20 19- 20

Zahlenbereich 1.5E-45 .. 3.4E38 2.9E-39 .. 1.7E38 5.0E-324 . 1.7E308 3.4E-4932 .. 1.1E4932 -9.2E18 .. J.2E18

Anzahl Bit 32 48 64 80 64

Bild 5.3: Formate für gebrochene Zahlen

Mantisse

Exponent

39 Bit

8 Bit

Vorzeichenstelle (0 = + 1 =-)

V

Exponent 8 Bit

V

Exponent 11 Bit

Mantisse

single

23 Bit Mantisse

double

52 Bit

Bild 5.4: Interne Darstellung der gebrochenen Zahlen

real

34 Wie sich die Zahl z aus Mantisse (m), Vorzeichenstelle (v) und Exponent (e) ergibt, sei nur für den Fall real im Detail angeführt: if 0 < e . Die eingegebene Zahl m u ß mit einem Blank enden. Vorsicht ist beim Lesen von Zeichen nach einer Zahl angebracht:

8.1 Eingabe var a , b ; c h a r ;

49

i:integer;

read(i,a,b)

Eingabe: 123 yx i 123 (ende Blank) a Blank b = 0) (e < 0)

Die anderen gebrochenen Zahlentypen in der Form x.xxxxxxxxxxxxxxE±xxxx -x.xxxxxxxxxxxxxxE+xxxx boolean

f a l s e bzw. t r u e

c h a r oder string

das oder die Zeichen

(e >= 0) (e < 0)

Das Format kann durch folgende Zusätze abweichend vom Standardformat gewählt werden: pi:d

d ist ein Ausdruck vom Typ i n t e g e r , der die Breite des Datenfeldes angibt, in das der Wert von pi (i = l,...,n) rechtsbündig geschrieben wird.

pi:d:s

pi (i = 1,..., n) ist vom Typ r e a l , d hat dieselbe Bedeutung wie eben, s ist ein Ausdruck vom Typ i n t e g e r , der die Anzahl der gewünschten Stellen nach dem Dezimalpunkt angibt (dann aber ohne Exponent!).

52

8. Elementare Ein- und Ausgabe

Ist die angegebene Datenfeldbreite d zu klein gewählt, wird d auf die benötigte Stellenzahl erweitert. Nach var i , j : i n t e g e r ; x,y:real; a,b:char; bedeutet write(i,x,a) dasselbe wie w r i t e ( i ) ; w r i t e ( x ) ; w r i t e l n ( i , x , a ) dasselbe wie w r i t e ( i ) ; w r i t e ( x ) ;

write(a); write(a); writeln;

Im Gegensatz zu r e a d , wo die Parameter Namen von Variablen sein müssen, sind die Parameter von w r i t e Ausdrücke, d. h. auch write(2*i,3+5/(x+2),ehr(123)); ist richtig. Es wird eben der Wert des Ausdrucks (s. Kapitel 9) gebildet und ausgegeben. Für den Anfang sei dem Leser empfohlen, ein kleines Programm zu machen, in dem einige Werte verschiedener Art gelesen und sogleich protokolliert werden. Das folgende Programm dient nur als Anregung für eigene Versuche. Modifizieren Sie es! Beispiel 8.1: Probieren Sie folgendes Programm und variieren Sie es, um ein Gefühl für die Wirkung von r e a d und w r i t e zu bekommen. program r e a d _ w r i t e _ v e r s u c h ; var i , j : i n t e g e r ; x , y : r e a l ; a , b : c h a r ; begin w r i t e l n ( ' l i n t und 1 r e a l ' ) ; read(i,x); writeln(i:4,x:10:4); w r i t e l n ( ' 1 i n t und 1 r e a l ' ) ; readln(i,x); writeln(i,x); w r i t e l n ( ' 1 c h a r und 1 i n t ' ) ; readln(b,j); writeln(j:5,b:3); w r i t e l n ( ' 1 i n t und 2 c h a r ' ) ; readln(i,a,b);

8.2 Ausgabe

53

writeln(i:4, a:3, b:3); readln; (* Damit das Output-Fenster nicht sofort verschwindet *) end. Wie man eine u n b e s t i m m t e Anzahl von Z a h l e n einlesen und a u f s u m m i e r e n kann, zeigt das f o l g e n d e Beispiel: Beispiel

8.2:

Es werden beliebig viele Z a h l e n e i n g e g e b e n und a u f a d d i e r t . Das Ende der Eingabe ist A Z =Ctrl-Z, falls die Variable c h e c k e o f aus der Unit crt auf t r u e gesetzt ist (Die Vorbelegung ist false!).

program read_beliebig_viele_Zahlen; uses crt; var summe, zahl:integer; begin clrscr; checkeof := true; summe := 0; writeln('Gib einige ganze Zahlen. Ende while not eof do (* Lies solange, bis eof true wird.*) begin readln(zahl); (* Protokoll der gelesenen Zahl *) writeln(zahl:2 0); summe := summe + zahl end; writeln('summe:', summe:6); readln; end. Die A u s g a b e zeigt, daß die Eingabe von schleife beendet.

A

= eof'.);

Z bei r e a d l n ( z a h l ) die LeseI

Das Beispiel 8.2 hat den Nachteil, daß es bei einer f a l s c h e n B e d i e n u n g von r e a d l n ( z a h l ) zu e i n e m A b b r u c h k o m m t und damit der Wert der bis dahin a u f g e l a u f e n e n S u m m e verloren ist. Das ist m e h r als ärgerlich. Man möchte doch, d a ß dann die E i n g a b e ignoriert wird und wiederholt werden kann. Dazu gibt es die S t a n d a r d f u n k t i o n i o r e s u l t (Bild 8.8) und die Direktiven ( *$I* ) und ( * $ I + * ) (s. A n h a n g D). Als Voreinstellung gilt ( * $ I + * ), d. h. bei Options/Compiler steht "I/O c h e c k i n g On". Wenn m a n den A b b r u c h bei einer falschen Eingabe verhindern will, m u ß m a n wie in dem f o l g e n d e n Beispiel vorgehen.

54

8. Elementare Ein- und Ausgabe

Aufruf: Parameter: Wirkung:

ioresult Funktionswert: word keine Der Funktionswert ist der Status einer I/O-Operation. Ist der I/O-Test abgeschaltet, d.h. ( * $ I - * ) , werden die folgenden E/A-Operationen ignoriert, bis i o r e s u l t aufgerufen wurde. Ist die I/O-Operation korrekt verlaufen, ist der Funktionswert 0.

Bild 8.8: Die Funktion

Beispiel

ioresult

8.3:

program ioresult_Demonstration; uses crt; var summe, zahl:integer; begin clrscr; checkeof := true; summe := 0; writeln('Gib einige ganze Zahlen. Ende AZ = eof'); while not eof do begin f*$I-*) readln(zahl); (*$I+*) if ioresult = 0 then summe := summe + zahl eise writeln('Keine Zahl'); writeln(zahls 2 0); end; writeln('summe:', summe:6); end. Beim Protokollieren mit w r i t e l n ( z a h l : 2 0 ) sieht man, daß bei einer falschen Eingabe i o r e s u l t ungleich Null wird und zahl den Wert 0 erhält. • Es ist auch noch die Frage zu klären, was eigentlich bei für Zeichen gelesen werden. Das folgende Beispiel verdeutlicht es. Beispiel

8.4:

program read_return; uses crt; var c : char; begin clrscr; checkeof := true; writeln('Gib einige Zeichen und Returns.');

8.2 Ausgabe

55

writeln('Ende AZ = e o f ' ) ; while not eof do begin read(c); writeln(c:4, ord(c):4); ( * Es wird das gelesene Zeichen und seine ASCII-Nr. ausgegeben. * ) end; end. W i e sich zeigt, bedeutet die Eingabe von < R E T U R N > die Eingabe der beiden A S C I I - Z e i c h e n Carriage return ( 1 3 ) und L i n e f e e d ( 1 0 ) ( s. Anhang C ) .



Mitunter möchte man nicht nur Zahlen und Zeichen einfach ausgeben, sondern dabei die Ausgabe auch etwas gestalten. Dazu gibt es die Prozeduren von Bild 8.9. Dabei taucht bei der Beschreibung in j e w e i l s der ersten Z e i l e crt auf. Das bedeutet, daß mit einer uses-Klausel auf die Unit crt verwiesen werden muß (s. auch Kap. 19.3).

Aufruf:

clrscr

Parameter:

keine

Wirkung:

Bildschirm löschen (clear screen).

Aufruf:

gotoxy(i,k)

Parameter:

i , k : integer

Wirkung:

B e w e g t den Cursor in die i-te Spalte der k-ten Z e i l e . D i e linke

Prozedur

crt

Prozedur

crt

obere Ecke ist (1,1). Aufruf:

lowvideo

Parameter:

keine

Wirkung:

Setzt für den Bildschirm das Attribut

Aufruf:

normvideo

Parater:

keine

Wirkung:

Setzt für den Bildschirm das Attribut "normal hell".

Aufruf:

highvideo

Parameter:

keine

Wirkung:

D i e f o l g e n d e n Zeichen werden mit größerer H e l l i g k e i t darge-

Prozedur

crt "halbhell".

Prozedur

Prozedur

crt

crt

stellt.

Aufruf:

textbackground ( f ä r b e )

Parameter:

färbe :byte;

Wirkung:

Es wird die Farbe des Hintergrunds gesetzt, f ä r b e kann ei-

Prozedur

ne der f o l g e n d e n Konstanten sein:

crt

56

8. Elementare Ein- und Ausgabe black blue green cyan red magenta brown lightGray

= = = = = = = =

0 1 2 3 4 5 6 7

Aufruf: Parameter: Wirkung:

textcolor(farbe) Prozedur crt farbe:byte; Es wird die Farbe des Vordergrundes gesetzt, f ä r b e kann einer der Werte 0..7 von t e x t b a c k g r o u n d sein und außerdem darkGray = 8 lightBlue = 9 lightGreen = 10 lightCyan = 11 lightRed = 12 l i g h t M a g e n t a = 13 yellow = 14 white = 15

Aufruf: Parameter: Wirkung:

Prozedur crt delline keine Löscht die Zeile, in der sich der Cursor befindet und bewegt alle Zeilen darunter um eine Zeile nach oben.

Aufruf: Parameter: Wirkung:

insline Prozedur crt keine Fügt an der Cursorposition eine Leerzeile ein und bewegt alle Zeilen darunter eine Zeile abwärts.

Bild 8.9: Einige Prozeduren zur Gestaltung der Ausgabe

Zu den Farben bei t e x t c o l o r und t e x t b a c k g r o u n d wäre noch zu bemerken, daß man wahlweise die Nummer als auch den Farbnamen angeben kann, also t e x t c o l o r ( y e l l o w ) oder t e x t c o l o r ( 1 4 ) . Wie man darüber hinaus andere Attribute wie Blinken, Unterstreichen und inverse Darstellung erreichen kann, ist in Kapitel 21 behandelt.

8.2 Ausgabe Beispiel

8.5:

Beispiel für farbliche Textgestaltung. program text_farben; uses crt; var i, k : integer; begin for i := 0 to 7 do begin clrscr; textbackground(i); gotoxy(50,l); write('Hintergrundfarbe: ',i); delay(600); for k := 0 to 15 do begin textcolor(k); gotoxy(2 0,k+1); write('Hallo Freunde.Farbe: ',k); delay(300); end; end; readln; end. Beispiel

8.6:

Demonstration von lowvideo, normvideo, highvideo. program low_high_video; uses crt; begin clrscr; normvideo; write('Hallo '); lowvideo; write(' Freunde'); writeln; highvideo; write('Hallo '); lowvideo; write(' Freunde'); writeln; normvideo; write('Hallo '); lowvideo; write(' Freunde'); writeln; (* normvideo hat keine Wirkung: Original Textattr. an der Stelle des Cursors beim Start.*)

58 readln; end. Beispiel



8.7:

Hier noch ein Beispiel dafür, wie bei w r i t e ( i : d ) mit d= Datenfeldbreite die Ausgabe gestaltet werden kann. Das folgende Programm schreibt den Wert von i mit i Schreibstellen: program nanu; uses c r t ; var i : w o r d ; begin clrscr; for i := 1 to 10 do w r i t e l n ( i : i ) ; (* Der Wert von i w i r d m i t i S c h r e i b s t e l l e n ausgegeben.*) readln; end.

Ausgabe: 1 2

3 4 5 6 7 8 9 10 w r i t e oder auch w r i t e ( O u t p u t , . . . ) benutzen den Bildschirm als Ausgabe. Es ist sehr einfach auch auf den Drucker auszugeben. In der Unit printer (Kapitel 19.3) ist der logische Filename I s t (= l i s t i n g ) definiert, der dem physikalischen Gerät LPT1 (Drucker) zugeordnet ist. Man schreibe dann also w r i t e l n ( I s t ) bzw. w r i t e ( I s t , . . . ). In dem folgenden Beispiel wird der PC als Schreibmaschine benutzt. Es wird eine Zeilen- und zeichengetreue Kopie der Tastatureingabe auf den Drucker ausgegeben.

Beispiel

8.8:

program Schreibmaschine; uses crt,printer; var c:char; begin checkeof :=true; while not eof do begin (* Lies und schreibe 1 Zeile.*) while not eoln do begin read(input,c); write(1st,c) (* Lesen über Tastatur, Schreiben auf Drucker.*) end; readln(input); (* Zeilenende bei input überlesen. writeln(lst); (* Zeilenwechsel auf Drucker*) end; end.

9. Operatoren und Ausdrücke Ausdrücke bestehen aus Operanden, Operatoren und runden Klammern. Der S i n n ist es, die O p e r a n d e n , die p a s s i v e n B e s t a n d t e i l e w i e K o n s t a n t e n , Variab l e n u n d F u n k t i o n s w e r t e d u r c h d i e O p e r a t o r e n , die a k t i v e n B e s t a n d t e i l e wie +, *, / u s w . zu v e r k n ü p f e n u n d einen n e u e n Wert zu b i l d e n . Ein A u s d r u c k s t e h t a l s o s t e l l v e r t r e t e n d f ü r e i n e n Wert. S t r e n g g e n o m m e n m ü ß t e m a n bei einem Ausdruck a op b g e n a u s a g e n , von w e l c h e m Typ die O p e r a n d e n a u n d b d e s O p e r a t o r s op sein d ü r f e n u n d von w e l c h e m T y p der e r z e u g t e Wert ist. Bei m e h r e r e n O p e r a n d e n m u ß m a n n o c h s a g e n , in w e l c h e r R e i h e n f o l g e bei a opl b op2 c g l e i c h b e d e u t e n d mit (a o p l b) o p 2 c o d e r a o p l ( b o p 2 c) ? die O p e r a t o r e n o p l u n d o p 2 a n z u w e n d e n s i n d . In der M a t h e m a t i k gibt es eine F ü l l e v o n O p e r a t o r e n f ü r die T y p e n reelle Z a h l , k o m p l e x e Z a h l , M e n g e n , Vekt o r e n , M a t r i z e n , l o g i s c h e A u s s a g e n usw. D a es f ü r die m e i s t e n d i e s e r O p e r a t o ren k e i n e A S C I I - Z e i c h e n g i b t , w i r d bei P r o g r a m m i e r s p r a c h e n , auch in Pascal, z i e m l i c h g r o ß z ü g i g v e r f a h r e n , d. h. ein O p e r a t o r wie + o d e r - wird in m e h r f a c h e r B e d e u t u n g v e r w e n d e t , bzw. der O p e r a t o r w i r d e n g l i s c h u m s c h r i e b e n wie not, and, or. D i e W i r k u n g von a + b h ä n g t v o n d e m Typ v o n a und b ab! In d i e s e m B u c h wird so v e r f a h r e n , d a ß w i r in den f o l g e n d e n K a p i t e l n bes c h r e i b e n , w i e m a n a r i t h m e t i s c h e A u s d r ü c k e (die O p e r a n d e n sind Werte der T y p e n i n t e g e r u n d r e a l ) u n d l o g i s c h e A u s d r ü c k e (die O p e r a n d e n sind v o m T y p b o o l e a n ) b i l d e n k a n n . In s p ä t e r e n K a p i t e l n w e r d e n a n d e r e D a t e n t y p e n b e h a n d e l t w e r d e n (String, a r r a y , set, r e c o r d , Z e i g e r ) , u n d es w i r d d a n n d o r t j e w e i l s a n g e g e b e n , w e l c h e O p e r a t o r e n und A u s d r ü c k e es gibt. U m die R e i h e n f o l g e zu b e s c h r e i b e n , in der die O p e r a t o r e n auf die O p e r a n d e n a n g e w a n d t w e r d e n und wie d a n n also K l a m m e r n zu s e t z e n sind, ist es z w e c k m ä ß i g , d i e O p e r a t o r e n in S t u f e n a n z u o r d n e n . M a n k a n n d a n n die A u s w e r t u n g e i n e s A u s d r u c k s d u r c h die R e g e l n von Bild 9.1 b e s c h r e i b e n . D i e s e R e g e l n gel-

1. D i e O p e r a t o r e n d e r S t u f e i w e r d e n vor d e n e n der S t u f e j > i a u s g e f ü h r t (je k l e i n e r die S t u f e , d e s t o e n g e r die B i n d u n g ) . 2. D i e O p e r a t o r e n d e r g l e i c h e n S t u f e w e r d e n v o n links n a c h r e c h t s a u s g e führt. 3. D a s I n n e r e v o n r u n d e n K l a m m e r n w i r d vor d e m Ä u ß e r e n a u s g e f ü h r t . Bild 9.1: Auswertung von Ausdrücken

62 ten für alle Arten von Ausdrücken. Da die Syntaxdiagramme diese Vorrangregeln beinhalten und alle Operatoren umfassen, sind sie ziemlich kompliziert. Es wird dazu auf den Anhang A (s. term - faktor - einfacher-ausdruck - ausdruck) verwiesen.

9.1 Arithmetische Operatoren und Ausdrücke Arithmetische Ausdrücke haben einen Wert vom Typ r e a l oder i n t e g e r , wobei wir hier immer mit r e a l auch s i n g l e , d o u b l e , e x t e n d e d und comp, mit i n t e g e r auch b y t e , w o r d , s h o r t i n t und l o n g i n t meinen. Eine Sonderstellung nehmen die einstelligen Operatoren + und - ein. Bei +a kann man das Pluszeichen auch weglassen, während -a den negativen Wert von a bedeutet.

Stufe 1

Operator

Operandentyp

Ergebnistyp

Bedeutung

*

real integer integer,real integer real integer,real integer integer, integer

real integer real real real real integer integer

Multiplikation M

integer real real,integer integer real real,integer

integer real real integer real real

* *

/ / / div mod 2

+ + + -

-

Division m

Division ohne Rest modulus (Rest der Division) Addition m ti Subtraktion "

Bild 9.2: Arithmetische Operatoren

Es muß darauf hingewiesen werden, daß manche Operatoren noch in anderen Bedeutungen verwendet werden, und zwar: + - * für Operanden vom Typ Menge (s. Kapitel 16).

Die elementaren Operationen + - * / werden wie in der Mathematik üblich verwendet. Da + - von der (minderen) Stufe 2, * / von der Stufe 1 sind, gelten die üblichen Regeln der Klammersetzung:

63 12*5 + 2 12* ( 5 + 2)



62 84

Bemerkenswert bei der Division / wäre, daß das Ergebnis immer vom Typ r e a l ist: 10/3

3.3333333...

Und natürlich führt die Division durch 0 zu einer Fehlermeldung. Will man das Ergebnis der Division von ganzen Zahlen in der Form ganzzahliger Teil und Rest haben, stehen dafür d i v undmod zur Verfügung. Wie das Beispiel 9.1 zeigt, gibt es auch einen Wert für i mod k mit k < 0, was in Standard-Pascal zu einem Fehler führt. In Turbo Pascal ist für negative n: i mod n = i - ( i d i v n) * n Beispiel

9.1:

Es werden die Operatoren d i v und mod demonstriert. program mod_und_div; var i , n : i n t e g e r ; begin write('i = '); readln(i); write('n = '); readln(n); w r i t e l n ( i : 3 , ' d i v ' , n : 3 , ' = ' , i div n :3); w r i t e l n ( i : 3 , ' m o d ' , n : 3 , ' = ' , i mod n : 3 ) ; readln; end. Hier einige 13 d i v -13 div 13 d i v -13 div

Ergebnisse: 4 = 3 4 = -3 -4 = -3 - 4 = 3

13 -13 13 -13

mod 4 = 1 mod 4 = - 1 mod - 4 = 1 mod - 4 = - 1

Die wichtige Frage, was geschieht, wenn in einem Ausdruck Operanden verschiedener Typen gemischt vorkommen, wird in Kapitel 9.5 behandelt werden. Zur Verdeutlichung seien noch einige Ausdrücke aus der üblichen mathematischen Notation in Pascal formuliert.

64

9. Operatoren und Ausdrücke

JL

—>

z

x / y / z (Reihenfolge von links nach rechts!)

X

x/(y/z)

I

X

x/(y*z)

yz a + 2b x 5y

a(b+c)

(a/x + 2*b/(5*y))/(l

—>

a * ( b + c)

(* n i c h t

+4*z/(-2))

vergessen!)

Blanks vor und nach den Operatoren sind reine Dekoration, a + b ist dasselbe wie a + b . Wenn man Zweifel bei der Klammersetzung hat, halte man sich an das Motto "Überflüssige Klammerpaare stören nicht".

9.2 Bitoperatoren Mitunter möchte man mit den einzelnen Bit einer Konstanten umgehen können. Dazu gibt es die Bitoperatoren von Bild 9.3. Stufe

Operator

Operandentyp

Ergebnistyp

Bedeutung

0

not

integer

integer

bitweise Negation

1

and shl shr

integer integer integer

integer integer integer

bitweises UND Linksshift Rechtsshift

2

or xor

integer integer

integer integer

bitweises ODER " exklusives ODER

Bild 9.3: Bitoperatoren

Die Shiftoperatoren s h l und s h r bedürfen noch einer Erklärung: i i

shl n shr n

Der Wert von i wird um n Stellen nach links geschoben, Der Wert von i wird um n Stellen nach rechts geschoben.

9.2 Bitoperatoren

65

Dabei gehen die nach links (bei s h l ) bzw. nach rechts (bei s h r ) hinausgeschobenen n Stellen verloren, auf der anderen Seite werden n Nullen nachgezogen. Bei Assemblern unterscheidet man gewöhnlich zwischen arithmetischem und logischem Shift. Beim arithmetischen Shift bleibt die Vorzeichenstelle erhalten, beim logischen nicht. Wie das Beispiel 9.2 demonstriert, bewirken die Operatoren s h l und s h r einen logischen Shift. Negative Werte von n führen zwar nicht zu einer Fehlermeldung. Es wird aber die vorzeichenlose Interpretation (word) zum Schieben genommen, so daß sich immer 0 ergibt. Beispiel

9.2:

Es wird die Wirkung der Shiftoperatoren demonstriert. program s h i f t _ o p e r a t o r e n _ s h l _ u n d _ s h r ; var i , n : integer; begin write('i = '); readln(i); (* Um e i n gewünschtes B i t m u s t e r f ü r i zu e r z e u g e n , d e n k e man d a r a n , daß d i e E i n g a b e auch Hexadezimal b e d i e n t werden k a n n . *) write('n = '); readln(n); w r i t e l n ( i : 3 , ' s h l ' , n : 3 , ' = ' , i shl n :3); w r i t e l n ( i : 3 , ' s h r ' , n : 3 , ' = ' , i shr n : 3 ) ; end. Hier einige 12 s h l -12 shl 128 s h l -1 shl

Ergebnisse: 2 = 48 2 = 48 1 = 256 1 = -2

12 -12 128 -1

shr 2 = 3 s h r 2 = 16381 s h r 1 = 64 s h r 1 = 32767



Der einstellige Operator n o t bildet die bitweise Negation eines integer-Wertes. Die zweistelligen Operatoren and, o r und x o r verknüpfen zwei integer-Werte bitweise. Die Eingabe der Werte in Beispiel 9.3 erfolgt zweckmäßigerweise hexadezimal ($FFFF statt -1). Auch die Ausgabe der Werte sollte hexadezimal oder bitweise erfolgen. Das wird in den Beispielen 15.5 (hexadezimal) und 9.4 (bitweise) gemacht werden. Beispiel

9.3:

Demonstration der Bitoperatoren n o t , a n d , o r , x o r . program b i t _ o p e r a t o r e n _ n o t _ a n d _ o r _ x o r ; var i , n : i n t e g e r ; begin

66

9. Operatoren und Ausdrücke

(* Um e i n g e w ü n s c h t e s B i t m u s t e r zu e r z e u g e n , d e n k e man d a r a n , daß d i e E i n g a b e a u c h Hexadezimal b e d i e n t werden k a n n . *) write('i = '); readln(i); write('n = ' ) ; readln(n); writeln('not ' , i : 3 , ' = ',not i :8); w r i t e l n ( i : 3 , ' a n d ' , n : 3 , ' = ' , i and n : 3 ) ; w r i t e l n ( i : 3 , ' or ' , n : 3 , ' = ' , i or n : 3 ) ; w r i t e l n ( i : 3 , ' x o r ' , n : 3 , ' = ' , i xor n : 3 ) ; readln; end. Hier einige Ergebnisse: - 2 5 6 and 255 = 0 - 2 5 6 o r 255 = - 1 - 2 5 6 xor 255 = - 1

15 and 1 = 1 15 o r 1 = 15 15 xor 1 = 14

- 1 and 128 = 128 - 1 o r 128 = - 1 - 1 x o r 128 = -129



Eine häufige Operation besteht darin, aus einem Bitmuster einen Teil auf 0 oder 1 zu setzen oder einen Teil herauszuschneiden. Mit o r kann man einen bestimmten Teil auf 1 setzen: x maske x ormaske

0101010101010101 0000111111110000 = $0FF0 0101111111110101

Mit and und n o t kann man einen bestimmten Teil auf 0 setzen: x 0101010101010101 maske 0000111111110000 = $0FF0 x a n d n o t m a s k e 0101000000000101 Mit a n d kann man einen bestimmten Teil herausschneiden: x maske x and m a s k e

0101010101010101 0000111111110000 = $0FF0 0000010101010000

Mit x o r kann man einen bestimmten Teil herausschneiden und davon das Komplement bilden: x maske x xor maske

0101010101010101 0000111111110000 = $0FF0 0000101010100000

In allen obigen Fällen wird man die Maske direkt hexadezimal angeben (in den obigen Beispielen also m a s k e = $0FF0). Mitunter wird man die Maske in

9.2 Bitoperatoren

67

Abhängigkeit von Variablen im Programm bilden wollen. Ist 1 die linke Bitnummer der Maske und r die rechte (die Bit von rechts nach links ab 0 numeriert): 0000111111110000 I I 1=11 r=4 so erreicht man dies durch 1 := 11; (* oder durch Einlesen *) r := 4; (* oder durch Einlesen *) maske := not(not 0 shl 1-r+l) shl r ; Mit not 0 werden 16 Einsen erzeugt, mit shl 1-r+l um die Maskenbreite nach links verschoben, so daß von rechts 1-r+l Nullen nachgezogen werden. Dieses Muster wird mit not invertiert, so daß die Maske rechtsbündig steht. Anschließend wird die Maske mit shl r an die richtige Stelle geschoben. Beispiel

9.4:

Das folgende Programm gibt unter Verwendung der Operationen and und shl die interne Darstellung einer integer-Zahl bitweise aus. Zum Verständnis sollte man wissen, was Dualzahlen sind und wie negative Zahlen dargestellt werden (1 für minus und 2er-Komplement). program integer_bitweise_zeigen; uses crt; var i,counter:integer; begin clrscr; write('Nummer : '); readln(i); for counter:=15 downto 0 do if (i and (1 shl counter) 0) then write('1') eise write('0'); (* Unter i wird also eine 1 von links nach rechts geschoben und per and mit der betreffenden Stelle von i verknüpft.*) writeln; readln; end.

. •

Die wichtige Frage, was geschieht, wenn in einem Ausdruck Operanden verschiedener Typen gemischt vorkommen, wird in Kapitel 9.5 behandelt.

68

9.3 Logische Operatoren Logische Ausdrücke haben einen Wert vom Typ b o o l e a n , also nur t r u e oder f a l s e . Variablen und Funktionen vom Typ b o o l e a n können durch die folgenden Operatoren verknüpft werden. Operator

Operandentyp

Ergebnistyp

Bedeutung

0

not

boolean

boolean

Negation

1

and

boolean

boolean

Konjunktion UND

2

or xor

boolean boolean

boolean boolean

Disjunktion, ODER Antivalenz, exklusives ODER

einf. einf. einf. einf. einf. einf.

boolean boolean boolean boolean boolean boolean

gleich ungleich kleiner kleiner oder gleich größer größer oder gleich

Stufe

3

=


=

Typ Typ Typ Typ Typ Typ

Bild 9.4: Logische Operatoren Der einstellige Operator not kehrt den Wahrheitswert um. Hat die Variable a : b o o l e a n den Wert t r u e , hat not a den Wert f a l s e . Die anderen Operatoren sind durch die Wahrheitstabelle von Bild 9.5 definiert, a and b ist dann und nur dann t r u e , wenn beide t r u e sind, a or b ist t r u e , wenn a oder b oder beide t r u e sind, a xor b ist t r u e , wenn a oder b aber nicht beide t r u e sind (exklusives Oder).

a

b

a and b

a or b

a xor b

false false true true

false true false true

false false false true

false true true true

false true true false

Bild 9.5: Wahrheitstabelle der logischen Operatoren and, o r , x o r Wegen der Vorrangregelung kommt not vor and und and vor or, d. h. a and b or c Erst a and b, dann or c , a and ( b or c ) Erst b or c, dann and a.

9.3 Logische Operatoren

69

Ein Vergleich ist offensichtlich ein Ausdruck vom Typ b o o l e a n : Entweder er ist richtig oder nicht. Nach i : i n t e g e r ist i

< 10

t r u e oder f a l s e

Nach Bild 9.4 können einfache Typen miteinander verglichen werden. Zu den einfachen Typen gehören die Standard-Datentypen i n t e g e r , r e a l , c h a r und b o o l e a n sowie die in Kapitel 12 beschriebenen Aufzählungs- und Unterbereichstypen. Ob und wie die Vergleichsoperatoren auf strukturierte Datentypen angewandt werden können, wird an den betreffenden Stellen behandelt werden. Da die Vergleiche von der Stufe 3 sind, ist auf die Klammersetzung zu achten: Bei den folgenden Beispielen sind die Klammern notwendig. ( i < 10) and (k 20) (0 < x) and (x < 10) ( ' a' ymin) and (y < ymax) then anzahl := anzahl +1; until eof; writeln ('in dem Rechteck lagen ',Anzahl,' Punkte.'); end.



Es sei daran erinnert, daß wir in Kapitel 8.1 schon einige Funktionen kennengelernt hatten, die einen Funktionswert vom Typ boolean hatten: eoln, eof und keypressed.

9.4 Mathematische Standardfunktionen In Ausdrücken können Aufrufe von Funktionen vorkommen. Ist f(x) eine Funktion, die einen Funktionswert von einem Typ integer haben möge, so ist a + 2 * f(23) ein arithmetischer Ausdruck. Die Funktion übergibt ihren Funktionswert an den Ausdruck. Es gibt in Pascal eine Reihe von Standardfunktionen für mathematische Zwecke, die im folgenden Bild aufgelistet sind. Dabei muß angegeben werden, von welchem Typ das Argument sein muß und der Funktionswert ist.

9.4 Mathematische Standardfunktionen

71

Aufruf: Parameter: Wirkung:

abs(x) Funktionswert: Typ von x x : i n t e g e r oder r e a l ; Der Betrag von x, d. h. Ixl.

Aufruf: Parameter: Wirkung:

exp(x) Funktionswert: r e a l xtreal Der Funktionswert ist die e-Funktion von x ; d. h. e x .

Aufruf: Parameter: Wirkung:

cos(x) Funktionswert: r e a l x : real Der Funktionswert ist der Cosinus von x. x muß im Bogenmaß angegeben werden.

Aufruf: Parameter: Wirkung:

sin(x) Funktionswert: r e a l x : real Der Funktionswert ist der Sinus von x. x muß im Bogenmaß angegeben werden.

Aufruf: Parameter: Wirkung:

arctan(x) Funktionswert: r e a l x : real Der Funktionswert ist der Arcustangens von x, d. h. der Hauptwert im Bereich -n/2 .. + ji/2.

Aufruf: Parameter: Wirkung:

ln(x) Funktionswert: r e a l x : real Der Funktionswert ist der natürliche Logarithmus von x. (x>0)

Aufruf: Parameter: Wirkung:

sqr(x) Funktionswert: Typ wie x x : i n t e g e r oder r e a l ; Der Funktionswert ist das Quadrat von x.

Aufruf: Parameter: Wirkung:

sqrt(x) Funktionswert: r e a l x:real Der Funktionswert ist die Quadratwurzel von x (x >= 0).

Aufruf: Parameter: Wirkung:

random(x) Funktionswert: r e a l bzw. w o r d x : w o r d ; (kann auch fehlen) Fehlt x, ist der Funktionswert eine Zufallszahl vom Typ r e a l im Bereich 0 s h o r t i n t ) oder auch erweitern (z. B. i n t e g e r —» l o n g i n t ) . Bei einer Erweiterung bleibt ein Vorzeichen natürlich erhalten: b := 1000; writeln(word(2*b) :5); w r i t e l n (byte( 2*b) : 5 ) ; w r i t e l n ( s h o r t i n t (-2 *b)

:5);

Ergebnis: 2000 Ergebnis; 208 Ergebnis: 48

Die Umwandlung eines integer-Typs in einen real-Typ macht keine Schwierigkeiten, weil er keinen Informationsverlust bringt. Im allgemeinen kann man überall, wo ein real-Typ verlangt wird, auch einen integer-Wert einsetzen. Bei der Umwandlung von r e a l nach i n t e g e r hat man selbst festzulegen, wie das genau geschehen soll. Die Funktionen von Bild 9.7 können dabei hilfreich sein. In der Mathematik gibt es viele Paare von entgegengesetzten Operationen wie *, /, +, - usw., so daß die folgenden Beziehungen gelten: x/a* a= x -i-b + a - a = b Durch die Darstellung der Zahlen als Dualzahlen ist es keineswegs selbstverständlich, daß die obigen, scheinbar trivialen Beziehungen auch auf Ihrem PC gelten. Es seien dazu zwei kleine Beispiele gebracht. Beispiel 9. 6: Nehmen wir das erste Beispiel x/a * a = x und dazu das folgende Programm program nanu; var i , j , a n z a h l t i n t e g e r ; x:real; begin anzahl : = 0 ; for i : = 2 0 0 0 to 3000 do begin x : = i /1000; x := x*1000; if i = x then i n c ( a n z a h l ) ;

76

9. Operatoren und Ausdrücke end; writeln ( ' g l e i c h : ' , a n z a h l :10); writeln ('ungleich:1001-anzahl readln; end.

:10); •

Dazu ist eine Erläuterung notwendig. Rein mathematisch würde man das Ergebnis gleich: 1001 , ungleich : 0 erwarten. Das ist leider nicht der Fall, weil i/1000 eine Wandlung in den Typ r e a l bedeutet, womit auch fast immer Rundungsfehler verbunden sind. Beispiel

9.7:

Das folgende kleine Beispiel zeigt, daß auch nicht immer b + a - a = b sein muß: program t e s t ; uses c r t ; var b : i n t e g e r ; a,x : real; begin writeln ( ' b : ' ) ; readln(b); writeln ( ' a : ' ) ; readln(a);

x := b + a - a ; clrscr; writeln('x:',x); writeln('b:',b:10); readln; end.



Nehmen wir als Beispiel b = 1 und a = le+15. Zur Bildung der Addition müssen die Exponenten der beiden Summanden angeglichen werden: 0.00000000000000le+15 + le+15 oder le+0 + 1000000000000000e+0 In beiden Fällen braucht man eine 15stellige Mantisse. Nach Bild 5.3 haben real-Zahlen aber nur Mantissen von 11-12 Dezimalstellen. Die Summe b + a läßt sich (bei b : i n t e g e r , a : r e a l ) also überhaupt nur bilden, wenn sich a und b um weniger als 12 Zehnerpotenzen unterscheiden.

9.6 Zusammenfassung

77

9.6 Zusammenfassung Im folgenden sind die vier Syntax-Diagramme angegeben, die einen Ausdruck definieren. Die Syntax-Diagramme berücksichtigen bereits die Stufen der Operatoren.

(9-3)

einfacher-ausdruck

term j

—r^v— or

term

78

term

9. Operatoren und Ausdrücke (9-4)

(9-5)

10. Anweisungen Im Anweisungsteil werden die Aktionen beschrieben, die mit den im Vereinbarungsteil eingeführten Daten auszuführen sind.

Normalerweise werden die Anweisungen in der Reihenfolge ausgeführt, in der sie im Programmtext aufgeführt sind. Diese Reihenfolge kann man durch eine Sprunganweisung ändern, wobei die als Sprungziel dienende Anweisung durch eine Marke gekennzeichnet wird. Die Marke steht durch : getrennt vor der Anweisung. Es wird darüber im Zusammenhang mit der Sprunganweisung in Kapitel 10.5 geredet. Die Anweisungen werden in einfache und strukturierte Anweisungen unterteilt. Eine einfache Anweisung ist nicht aus anderen Anweisungen zusammengesetzt.

Die Wertzuweisung wird in Kapitel 10.2, die Prozeduranweisung in Kapitel 11.1, die Sprunganweisung in Kapitel 10.5 und die Methodenanweisung in Kapitel 24 behandelt. Die leere Anweisung ist

80

10. Anweisungen

leere-anweisung

(10-3)

d. h. sie besteht aus nichts und bewirkt auch nichts. Sie ist aus syntaktischen Gründen vorhanden, damit dort, wo die Syntax eine Anweisung verlangt, auch einmal keine zu stehen braucht. Strukturierte A n w e i s u n g e n sind aus anderen Anweisungen zusammengesetzt. Man unterscheidet (10-4)

Die with-Anweisung hat erst im Zusammenhang mit dem Datentyp r e c o r d Bedeutung und wird im Kapitel 15 behandelt, die asm-Anweisung in Kapitel 22. Die anderen sind im folgenden beschrieben.

10.1 Verbundanweisung Eine Verbundanweisung besteht aus einer Reihe von Anweisungen, die in der Reihenfolge ausgeführt werden, in der sie im Text a u f g e f ü h r t sind. (10-5)

verbundanweisung begin

an Weisung

. ..

end

Eine Verbundanweisung wird durch b e g i n und end eingerahmt. Die Anweisungen werden durch ; voneinander getrennt. Der ganze Anweisungsteil (4-4) eines P r o g r a m m s ist von dieser Art.

10.2 Wertzuweisung

81 (10-6)

anweisungsteil verbundanweisung

Die Verbundanweisung dient in erster Linie dazu, aus mehreren Anweisungen syntaktisch wieder eine einzige Anweisung zu machen. Es ist häufig notwendig an Stellen, wo nur eine Anweisung zugelassen ist, mehrere Anweisungen auszuführen. Der Begriff Verbundanweisung ermöglicht es dann, diese mehreren Anweisungen in b e g i n und e n d einzufassen und so, syntaktisch gesehen, daraus nur eine einzige Anweisung zu machen. Bei der Aufzählung der Anweisungen ist das Semikolon Trennzeichen zweier Anweisungen. Eigentlich darf insofern vor dem abschließenden e n d kein Semikolon stehen. Da es aber eine leere Anweisung gibt, sind beide Formulierungen

begin

begin

read(i); write(i)

end

read(i); write(i);

end

richtig. Bei der rechten Verbundanweisung wird stillschweigend zwischen w r i t e ( i ) ; und e n d eine leere Anweisung unterstellt.

10.2 Wertzuweisung Die Wertzuweisung ist die wichtigste Anweisung einer jeden Programmiersprache. Mit ihr kann man einer Variablen einen Wert zuweisen, der in Form eines Ausdrucks vorliegt. Der Ausdruck wird ausgewertet, d. h. sein Wert ermittelt, und dieser Wert wird der Variablen zugewiesen. Damit ist der alte Wert der Variablen überschrieben worden und damit verloren. (10-7)

wertzuweisung variable

ausdruck

Diese Definition wird in Kapitel 11.2 im Zusammenhang mit Funktionen noch erweitert werden. Es ist hier sorgfältig zu formulieren. Welchen Unterschied der Leser auch immer zwischen "denselben Typ" oder "den gleichen Typ" sehen mag, die Formulierung von Original-Pascal heißt an dieser Stelle "The variable v and the

82

10. Anweisungen

expression e must be of identical type". Wie auch immer, ob derselbe oder der gleiche oder identical type, es muß gesagt werden, was darunter zu verstehen ist. Über Typen und Typnamen wird erst in Kapitel 12 geredet. Dort wird dann auch erklärt, wann zwei Typen identisch sind.

Bei der Wertzuweisung v : = e ist strikt darauf zu achten, daß die Variable v und der Ausdruck e denselben Typ haben. Es gibt davon nur die eine Ausnahme, daß die Variable von einem Typ r e a l ist und der Ausdruck einen Wert von einem Typ i n t e g e r hat. Bei den einfachen Typen in Kap. 12 wird diese Aussage etwas eingeschränkt werden. Bild 10.1: Wertzuweisung

Beispiel 10.1: Es sei var i , j : i n t e g e r ; x , y : r e a l ; a , b : c h a r ;

p,q:boolean;

Richtige Wertzuweisungen sind: p := i < 5; (* Der Ausdruck r e c h t s i s t vom Typ b o o l e a n . * ) a := ' + ' ; x := i + j mod 7; q := o d d ( j + i d i v 5) j := r o u n d ( x / 2 ) ; y := 275; Falsche Wertzuweisungen sind wegen falscher Typen: i := 3 . 6 7 8 ; b := ' h a n s ' ;

10.3 Bedingte Anweisungen

83

10.3 Bedingte Anweisungen Mit den bedingten Anweisungen kann man eine Auswahl unter mehreren möglichen Anweisungen vornehmen. bedingte-anweisung



(10-8)

if-anweisung

* case-anweisung Aus praktischen Gründen gibt es zwei Arten von bedingten Anweisungen: Die if-Anweisung ist speziell für den Fall vorgesehen, daß die Auswahl aus zwei Möglichkeiten zu treffen ist, die case-Anweisung für die Auswahl aus beliebig vielen Möglichkeiten. Natürlich käme man auch mit nur einer dieser Anweisungen aus. if-anweisung

(10-9)

Der Ausdruck nach if muß ein logischer Ausdruck sein (vom Typ b o o l e a n ) . Hat dieser den Wert t r u e , wird die Anweisung nach then ausgeführt, hat er den Wert f a l s e , wird die Anweisung nach eise ausgeführt. Fehlt der elseTeil, wird bei f a l s e zur nächsten Anweisung übergegangen. Sind nach then oder eise mehrere Anweisungen auszuführen, ist daraus durch Einrahmen in begin und end eine Verbundanweisung zu machen. Man beachte, daß vor eise nie ein Semikolon stehen darf (weil ja dort die if-Anweisung noch nicht zu Ende ist). Fehlt der else-Teil und ist die Anweisung nach then selbst wieder eine if-Anweisung, so ergibt sich aus

if a u s d r u c k l then if ausdruck2 then anweisungl eise anweisung2 die Mehrdeutigkeit, ob eise zum ersten oder zweiten if gehört. Es gilt die Regel, daß ein eise immer zu der letzten if-Anweisung gehört, die keinen else-Teile hat, also

84

10. Anweisungen

i f ausdruckl then begin i f ausdruck2 then anweisungl else anweisung2 end gemeint ist.

Nach dem Wortsymbol c a s e steht ein Ausdruck, nach o f eine Aufzählung von Anweisungen, eben die zur Auswahl anstehenden Anweisungen. Diese Anweisungen sind durch Werte, die der Ausdruck annehmen kann, markiert. Der Wert des Ausdrucks, auch Selektor genannt, wählt diejenige Anweisung aus, die mit diesem Wert markiert ist. Eine Anweisung kann mehrere solcher Konstanten als Marke tragen und auch ein ganzer Unterbereich (s. Kap. 12.2) sein. Der Wert des Ausdrucks muß vom Ordinaltyp sein. Hat der Ausdruck nach c a s e einen Wert, der unter den Konstanten nicht vorkommt, wird die Anweisung nach e i s e ausgeführt. Der Wert des Selektors ist auf -32768 .. 32767 begrenzt und kann also nicht vom Typ w o r d und l o n g i n t oder s t r i n g sein.

10.3 Bedingte Anweisungen Beispiel

85

10.2:

Mit dem folgenden Programmstück kann eine Frage beantwortet werden. var c:char; begin writeln('nochmal? gib J/N'); read(c); case c of 'j','J' : {Anweisung für Wiederholung); 'n','N' : {Anweisung für nicht Wiederholung}; eise {erneute Eingabe anfordern} end; end. (*Damit kann die Frage mit Groß- oder Kleinbuchstaben beantwortet werden.*)



Man kann natürlich auch die if-Anweisung in dieser Form schreiben: var i:integer; begin {Nachdem i einen Wert bekommen hat.} if i < 25 then {tue dieses} eise {tue jenes} ist gleichbedeutend mit case i < 25 of true : {tue dieses}; false : { tue jenes}

end;

end. Man kann ganze Unterbereiche (s. Kap. 12.2) des Ausdrucks zusammenfassen: var c:char; begin read(c); case c of '0'..'9': {c 'a'..'z': {c 'A' .. 'Z': {c eise {c end.

war war war war

eine Ziffer.}; ein Kleinbuchstabe.}; ein Großbuchstabe.}; irgendein anderes Zeichen.} end

Man beachte, daß nach dem : nur eine Anweisung stehen darf. Wenn man für diesen Fall mehrere Dinge tun will, muß man diese als Verbundanweisung schreiben, also in b e g i n ... end einschließen.

86

10. Anweisungen

10.4 Schleifenanweisungen Schleifenanweisungen dienen dazu, Anweisungen wiederholt auszuführen. Man hat dabei zwei Arten von Schleifen zu unterscheiden: Bei der ersten Art weiß man bei der Programmerstellung, wie oft die Schleife zu durchlaufen ist. Dafür gibt es die for-Anweisung. Bei der zweiten Art ist diese Anzahl unbekannt und wird zur Laufzeit von einer Bedingung abhängig gesteuert. Dabei sind wiederum drei Fälle zu unterscheiden, je nachdem, ob diese Bedingung am Anfang, im Inneren oder am Ende der Schleife abgefragt wird. In Pascal gibt es nur die Abfrage am Anfang (while-Anweisung) und am Ende (repeatAnweisung). Soll eine Schleife in der Mitte abgebrochen werden, muß man entweder eine Sprunganweisung (s. Kapitel 10.5) verwenden oder die Schleife anders formulieren, was immer möglich ist. Demzufolge gibt es in Pascal drei Arten von Schleifenanweisungen. wiederholungsanweisung

Bei der while-Anweisung wird die Bedingung zu Beginn der Schleife abgefragt.

Der Ausdruck nach while muß ein logischer Ausdruck sein. Die Anweisung nach do wird ausgeführt, solange der logische Ausdruck t r u e ist. Sind nach do, also in der Schleife, mehrere Anweisungen auszuführen, sind diese als Verbundanweisung zu schreiben, also in begin und end einzufassen. Ist der logische Ausdruck zu Anfang f alse, wird die Schleife überhaupt nicht ausgeführt.

10.4 Schleifenanweisungen

87

Der Ausdruck nach u n t i l muß ein logischer Ausdruck sein. Die Anweisungen nach r e p e a t werden solange ausgeführt, bis der logische Ausdruck nach u n t i l t r u e wird. Die repeat-Schleife wird also immer mindestens einmal ausgeführt. Man beachte den Unterschied: Die while-Schleife wird gemacht, wenn der Ausdruck t r u e ist (solange der Ausdruck t r u e ist, bis der Ausdruck f a l s e wird), die repeat-Schleife, wenn der Ausdruck f a l s e ist (solange der Ausdruck f a l s e ist, bis der Ausdruck t r u e wird). Zum Schleifenabbruch sind die Wahrheitswerte nach w h i l e und u n t i l gerade zu vertauschen. Das macht mitunter Schwierigkeiten, wenn ein solcher Ausdruck zusammengesetzt ist. So sind repeat u n t i l ( i < 10) o r ( x = 0 ) ; und w h i l e n o t ( ( i < 10) o r

(x = 0 ) )

do

bzw. while

( i >= 10) and ( x < > 0) do

dieselbe Schleifenkonstruktion (bis auf den möglicherweise bei w h i l e nicht stattfindenden ersten Durchlauf). Beispiel

10.3:

Es werden ganze, positive Zahlen in aufsteigender Reihenfolge eingegeben. Ende der Eingabe ist 0. Es sollen alle diejenigen Zahlen ausgegeben werden, die unter den eingegebenen Zahlen nicht vorkommen. program f e h l e n d e _ z a h l e n _ d r u c k e n ; var ein,aus:integer; begin clrscr; aus : = 1; w r i t e ( ' G i b p o s i t i v e ganze Zahlen i n ' ) ; writeln('aufsteigender Reihenfolge'); w r i t e l n ( ' n e u e Zahl(Ende = 0 : ' ) ; readln(ein); writeln('gelesen:',ein:3); w h i l e e i n < > 0 do begin writeln('es fehlen:'); w h i l e aus < e i n do begin w r i t e l n ( a u s ) ; aus : = aus+1; end; w r i t e l n ( ' n e u e Zahl(Ende = 0 ) : ' ) ; r e a d l n ( e i n ) ; aus : = aus+1;

88

10. Anweisungen

writeln('gelesen:',eins3); end; readln; end.

Die Schleifenvariable und die beiden Ausdrücke müssen alle vom gleichen Typ sein, der ein Ordinaltyp sein muß (also nicht vom Typ r e a l ) . Der erste Ausdruck ist der Anfangswert, der zweite der Endwert. Bei t o wird vom Anfangswert bis zum Endwert (>= Anfangswert) heraufgezählt, bei downto vom Anfangswert bis zum Endwert ( 0 then begin yl := f(mitte); xl := mitte end else begin y2 := f(mitte); x2 := mitte end; until abs(f(mitte)) < epsilon; Nullstelledrucken(mitte); end; (* Nullstelle bestimmen *) procedure Anfangsetzen(var xl,yl,x2,y2:real); begin xl:=a; yl:=f(xl); x2 := xl + deltax; y2 := f(x2); if abs(yl) < epsilon then nullstelledrucken(xl); if abs(y2) < epsilon then nullstelledrucken(x2); if yl*y2 f u n c t i o n prim(j:integer):boolean; {prim ist true, wenn j Primzahl ist, und sonst false.} v a r k:integer; begin k:=2; w h i l e (k*k 50) then ... { Das s i n d a l l e Männer n a c h 1950 g e b o r e n . ) leute[12].adresse.Stadt:='Muenchen'; {Das i s t d i e S t a d t d e r 1 2 . P e r s o n i n dem A r r a y l e u t e . } Die obige Formulierung wird dadurch umständlich, weil die einzelnen Komponenten durch record-variable.feldname zu bezeichnen sind. Um diese Schreibweise abzukürzen, gibt es die with-Anweisung (s. (10-4)).

174

15. Record-Typ

with-anweisung l :

(15-5)

Der Sinn dieser Anweisung besteht darin, den Namen der Record-Variablen bzw. der Objektvariablen (siehe Kap. 24) nur einmal nach with anzugeben, wonach in der Anweisung nach do dann nur noch die Namen der Felder anzuführen sind: with p , g e b u r t do begin p . n u m m e r : = 2198; nummer := 2198; name := ' M a i e r ' ; p.name: = 'Maier' t a g := 12: p.geburt.tag:=12; monat := 5 ; p . g e b u r t . m o n a t : =5; j ä h r := 2 5 ; p . g e b u r t . j ä h r := 2 5 ; end; Beispiel

15.1:

Es wird ein Array von Artikeln, ein Lager, angelegt und ausgegeben. Achten Sie besonders darauf, wie die Komponenten des Records a r t i k e l und des Arrays l a g e r benutzt werden. program a r t i k e l ; uses c r t ; type a r t i k e l t y p = record nummer :integer; name :string[20]; stueckzahl :integer; preis :real; lieferdatum:record t a g : 1 . . 3 1 ; monat:1..12; jahr:0..99 end; end; l a g e r t y p = a r r a y [ l . . 1 0 0 ] of a r t i k e l t y p ; var l a g e r : l a g e r t y p ; anzahl:integer; procedure l i e s a r t i k e l (var a : a r t i k e l t y p ) ; begin with a , l i e f e r d a t u m do begin

15. Record-Typ write('nr:'); readln(nummer); write('name:'); readln(name); write('Stück:'); readln(stueckzahl); write('Preis:'); readln(preis); writeln('Lieferdatum:'); write('Tag:'); readln(tag); write('Monat:'); readln(monat); write('Jahr:'); readln(jähr); end; end; (* liesartikel *) procedure lieslager(var 1:lagertyp; anz:integer); var i:integer; begin for i := 1 to anz do liesartikel(1[i]); l[anz+l].nummer := -1; (* Damit das momentane Ende erkannt werden kann.*) end; (* lieslager *) procedure druckartikel(a:artikeltyp); begin with a,lieferdatum do begin write(nummer:5,name:2 4,stueckzahl:10); write(preis:10:2,tag:12,'.',monat:2); writeln('.',jahr:2); end; end; (* druckartikel *) procedure drucklager(1:lagertyp); var i:integer; begin i := 1; while 1[i].nummer > 1 do begin druckartikel(1[i]); i := i + 1; end; end; (* drucklager *) begin (******** Anweisungsteil **********) write('Wieviel Artikel: n 1 do begin druckartikel(1[i]); inc(i); end; end; (* drucklager *)

procedure liesinteger(var z:integer; unten,oben,zeile:integer); var ok:boolean; begin gotoxy(15,zeile); repeat (*$i-*) readln(z); (*$i+*) ok := ioresult = 0; if not ok then begin write(Ag); gotoxy(15,zeile); write(' gotoxy(15,zeile); end; if (z < unten) or (z > oben) then begin write(~g); gotoxy(15,zeile); write(' gotoxy(15,zeile); ok := false; end; until ok; end;

178

15. Record-Typ

procedure liesreal(var z:real; zeile: integer); var ok:boolean; begin gotoxy(15,zeile); repeat (*$i-*) read(z); (*$i+*) ok := ioresult = 0; if not ok then begin write(^g); gotoxy(15,zeile); write(' '); gotoxy(15,zeile); end; until ok; end; procedure liesartikel (var a:artikeltyp); var x:integer; begin clrscr; lowvideo; writeln('Nr writeln('Name writeln('Stück writeln('Preis writeln('Lieferdatum:'); writeln(' Tag writeln(' Monat writeln(' Jahr highvideo; with a,lieferdatum do begin 1iesinteger(nummer,0,9999,1); gotoxy(15,2);liesname(2,name,20); liesinteger(stueckzahl,0,32767,3); liesreal(preis,4); liesinteger(x,1,31,6); tag := x; liesinteger(x,l,12,7); monat := x; liesinteger(x,0,99,8); jahr := x; end; end; procedure lieslager(var 1:lagertyp; anz:integer); var i:integer; begin for i := 1 to anz do liesartikel(1[i]);

15. Record-Typ

179

l [ a n z + l ] . n u m m e r := - 1 ; (* Damit d a s momentane Ende e r k a n n t werden k a n n . * ) end; (* l i e s l a g e r *) begin (******** A n w e i s u n g s t e i l **********) w r i t e ( ' w i e v i e l a r t i k e l : n begin if 1 = nil then writeln('Element mit ',d,' nicht gefunden') else if lA.daten d then loeschen(lA.next,d) else begin g := 1; 1 := 1A.next; {Element austragen} dispose(q); {Speicherplatz frei} end; end {of löschen}; procedure einfuegen_nach(var 1:zeiger; x,d:datentyp); {Bei der in 1 beginnenden Liste wird nach dem Element mit dem Datenteil x ein Element mit dem Datenteil d eingefügt .}

234

18. Datentyp Zeiger

var q : z e i g e r ; begin if 1 = nil then w r i t e l n ( ' E l e m e n t m i t ' , x , ' n i c h t g e f u n d e n ' ) eise if l ^ . d a t e n x then e i n f u e g e n _ n a c h ( 1 ^ . n e x t , x , d ) eise begin n e w ( g ) ; g ^ . d a t e n := d ; q ^ . n e x t s= 1 A . n e x t ; 1 " . n e x t := q ; end end {of e i n f ü g e n _ n a c h > ;



Eine besonders interessante verkettete Datenstruktur sind geordnete binäre Bäume. Ein Baum besteht aus Knoten und Ästen, an denen wieder Knoten hängen. Der Knoten, der an keinem Ast hängt, ist der Wurzelknoten. Knoten ohne Äste heißen Blätter. Damit können hierarchische Beziehungen aufgebaut werden. Jeder Knoten besteht wieder aus einem Datenteil (meist mit einem Element key, um den Knoten eindeutig zu identifizieren) und Zeigern, die auf die nachfolgenden Knoten zeigen (also die Äste). Hat jeder Knoten eines Baumes höchstens zwei Äste, spricht man von einem binären Baum. Jeder Knoten hat dann nur einen linken und einen rechten Nachfolger. Eine passende Beschreibung könnte lauten type z e i g e r = *knoten; d a t e n t y p = (* i r g e n d e i n D a t e n t y p * ) ; knoten = record d a t e n : d a t e n t y p ; l i n k s , r e c h t s : z e i g e r end; var bäum : z e i g e r ; Gilt darüber hinaus auch noch für jeden Knoten, daß alle links an einem Knoten hängenden Knoten kleiner sind (also kleinere keys haben), alle rechts daran hängenden Knoten größer sind, heißt der binäre Baum geordnet. Bild 18.6 zeigt einen solchen Baum. Wegen der Eigenschaft, geordnet zu sein, sind solche Bäume hervorragend zum Suchen geeignet. Man nennt sie auch Suchbäume. In dem folgenden Beispiel wird ein binärer geordneter Baum angelegt und ausgegeben. Die Funktion l i e s k n o t e n ( ) schafft Speicherplatz für einen neuen Knoten und liest die Daten dazu ein. Der Funktionswert ist der Zeiger auf diesen neuen Knoten h i l f . Die Funktion e i n f u e g e n ( ) fügt diesen neuen Knoten in den Baum ein. w u r z e l ist der Zeiger auf den Wurzelknoten (zu Anfang nil). Das Einfügen wird durch Vergleich der keys gesteuert: Ist h i l f . k e y kleiner als der momentan betrachtete k e y des Baumknotens, gehört der neue Knoten h i l f in den linken Teilbaum, sonst in den rechten. Am Ende von e i n f u e g e n ( ) ist der neue Knoten h i l f als neues Blatt in den Baum eingefügt.

18.2 Dynamische Variablen und verkettete Datenstrukturen

235

Bild 18.6: Geordneter binärer Baum Um alle Knoten eines binären Baumes zu durchlaufen, gibt es drei Ordnungen. Angefangen bei der Wurzel mache man für jeden Knoten rekursiv: Preorder: Inorder: Postorder:

Knoten, linker Teilbaum, rechter Teilbaum linker Teilbaum, Knoten, rechter Teilbaum linker Teilbaum, rechter Teilbaum, Knoten

Dabei kann man noch jeweils links und rechts vertauschen. Beim Durchlaufen eines geordneten binären Baumes gemäß Inorder ergeben sich die Knoten in sortierter Reihenfolge (links-Knoten-rechts steigend, rechts-Knoten-links fallend). Die Funktion b a u m _ d r u c k e n ( ) benutzt Inorder, womit die Knoten in aufsteigender Reihenfolge ausgegeben werden. Beispiel

18.7:

program binaerer_baum; uses crt; type zeiger = *knoten; knoten = record key : integer; links,rechts:zeiger

end;

236 var

18. Datentyp Zeiger bäum : zeiger; z : integer;

procedure einfuegen(var b:zeiger; k:integer); begin if b = nil then begin new(b); with b" do begin links:=nil; rechts:=nil; key:=k end; end else with b" do begin if key < k then einfuegen(links,k); if key > k then einfuegen(rechts,k); end; end (* einfuegen *); procedure druckbaum(b:zeiger; tiefe:integer); begin if b = nil then writeln(' Baum leer ') else with b" do begin tiefe:=tiefe+1; if linksonil then druckbaum(links,tiefe); write(' ':4*tiefe); writeln(key:4); if rechtsonil then druckbaum(rechts,tiefe); tiefe:=tiefe-1; end; end (* Druckbaum *); procedure baum_loeschen(var b:zeiger); (* Der von dem Baum b im Heap belegte Speicherplatz wird zurückgegeben.*) begin if b nil then begin if b*.rechts nil then baum_loeschen(b~.rechts);

18.2 Dynamische Variablen und verkettete Datenstrukturen

end;

237

if bA.links n i l then baum_loeschen(bÄ.links); dispose(b); end; (* b a u m _ l o e s c h e n *)

begin (******** Anweisungen ***********) bäum := n i l ; c h e c k e o f := t r u e ; w r i t e l n ( ' G i b Z a h l e n Ende " z ' ) ; w h i l e n o t eof do begin readln(z); einfuegen(bäum,z); end; druckbaum(bäum,0); baum_loeschen(baum); end.



Binäre Bäume sind eine exzellente Datenstruktur, um einen Datenbestand geordnet, d. h. sortiert zu halten. Die Form des Baumes von Beispiel 18.7 hängt natürlich von der Reihenfolge der einlaufen Knoten ab. Der Baum kann extrem schief werden (wenn die keys auf- oder absteigend sortiert einlaufen), er kann bei günstiger Beschaffenheit vollständig ausgeglichen entstehen. Die Qualität des Suchens hängt von dieser Schiefe des Baumes ab. Dies ist eine Einführung in Turbo Pascal und kein Lehrbuch über Programmieren. Aus Platzgründen können daher die interessanten Operationen (Löschen, Suchen, Ändern usw.) in Listen und Bäumen nicht weiter behandelt werden. Dazu sei auf das vorzügliche Buch [6] verwiesen. Das komfortable Umgehen mit Adressen macht Pascal f ü r die geketteten Datenstrukturen ganz besonders interessant. Als Demonstrationsbeispiel sei im nächsten Beispiel ein Stack mit Hilfe einer einfach geketteten Liste simuliert. Beispiel

18.8:

Es wird ein Stack mit den Operationen p o p u p und p u s h d o w n durch eine einfach gekettete Liste nachgebildet. Das Listenende ist der Boden des Stack, das erste Listenelement ist das momentan oberste im Stack. Dann bedeutet die Operation p u s h d o w n , vorn ein neues Element in die Liste einfügen. Die Operation p o p u p heißt, das erste Listenelement austragen. Der Kopf der Liste kann also als Stackpointer aufgefaßt werden. program stack_simulation; uses crt; c o n s t s t a c k m a x = 10;

238

18. Datentyp Zeiger

type stackptr = *Stackelement; datentyp = integer; stackelement = record daten : datentyp; next : stackptr end; var st : stackptr; fertig : boolean; stacksize, d : integer; (* Stacksize ist für alle Prozeduren global.*) procedure printstack(var st:stackptr); begin if st = nil then write(' stackende ') else begin writeln(' ',st*.daten:4,'|'); writeln(' 1'); printstack(st*.next) end; end; (* printstack *) procedure push(var st:stackptr; d:datentyp); var hilf : stackptr; c:char; begin new(hilf); hilf*.daten := d; hilf*.next := st; st := hilf; stacksize := stacksize + 1; end; (* push *) procedure pop(var st:stackptr; var d:datentyp); var hilf:stackptr; begin hilf := st; st := st*.next; d := hilfdaten; dispose(hilf); stacksize := stacksize - 1; end; (* pop *) procedure create(var st:stackptr); begin st := nil; stacksize := 0; end; (* create *) function isfull(st:stackptr):boolean; begin isfull := stacksize >= stackmax;

18.2 Dynamische Variablen und verkettete Datenstrukturen end; function isempty(st :stackptr):boolean; begin isempty := st = nil; end; procedure menue; var c:char; begin clrscr; fertig := false; printstack(st); gotoxy(40,5); writeln('Was wollen Sie:'); gotoxy(40,7); writeln('+ = push down'); gotoxy(40,8); writeln('- = pop up'); gotoxy(40,9); writeln('e = end'); gotoxy(40,10); readln(c); case c of '+': if isfull(st) then begin gotoxy(40,20) ; write('Keller leider'); write(' voll'); readln(c); end else begin gotoxy(40,12); writeln('Gib Daten:'); gotoxy(40,13); readln(d); push(st,d); end; '-': if isempty(st) then begin gotoxy(40,20); writeln('Keller leider leer'); readln(c); end

240

18. Datentyp Zeiger else begin pop(st,d); gotoxy(10,l); writeln('ausgekellert s ',d: 4); readln(c); end;

end;

'e','E': fertig ( * m e n u e *)

:=

true;

b e g i n (********** A n w e i s u n g s t e i l create(st); repeat menue; until fertig; end.

end;

(* c a s e

*)

***********)



Wir wollen uns nun den Heap und seine Verwaltung etwas genauer ansehen (Bild 18.7). In der System-Unit gibt es die folgenden Standardvariablen: var heaporg heapptr heapend

: : :

pointer; pointer; pointer;

heaporg-

niedrige Adresse Der Heap wächst in Richtung größerer Adressen

heapptr• freier Speicher heapend-

Bild 18.7: Organisation des Heap

hohe Adresse

18.2 Dynamische Variablen und verkettete Datenstrukturen

241

Für die Speicherverwaltung des Heap gibt es noch die folgenden StandardProzeduren und -Funktionen: Aufruf: Parameter: Wirkung:

mark (p) Prozedur v a r p:pointer; In der Variablen p wird der momentane Wert von heapptr gespeichert. Nicht zusammen mit freemem oder dispose verwenden.

Aufruf: Parameter: Wirkung:

release(p) Prozedur v a r p : pointer; Der Heap wird ab dem Wert von p freigegeben, wobei p vorher mit mark(p) bezeichnet worden sein muß. release (heaporg) gibt also den ganzen Heap frei. Nicht zusammen mit freemem oder dispose verwenden.

Aufruf: Parameter: Wirkung:

getmem(p,i) Prozedur v a r p:pointer; i:word; Im Heap werden für die Variable p * genau i Bytes Speicherplatz belegt.

Aufruf: Parameter: Wirkung:

freemem(p,i) Prozedur v a r p:zeiger; i:word; Vom Heap wird ein Block von i Bytes Speicherplatz zurückgegeben. i muß genau dem Wert vom vorherigen Aufruf von getmem entsprechen. Nicht zusammen mit mark oder release verwenden.

Aufruf: Parameter: Wirkung:

maxavail Funktionswert: longint keine Der Funktionswert liefert als Wert den größten zusammenhängenden Speicherplatz des Heap.

Aufruf: Parameter: Wirkung:

memavail Funktionswert: longint keine Der Funktionswert liefert den im Heap verfügbaren Speicherplatz in Bytes.

Bild 18.8: Prozeduren und Funktionen für den Heap Es sei noch einmal zusammengefaßt: Speicherplatz belegen im Heap kann man mit new oder getmem. Speicherplatz zurückgeben kann man mit dispose oder freemem, wobei eine Lücke im Heap entsteht, die, wenn möglich, beim nächsten Aufruf von new oder getmem wieder belegt wird, release (p) hingegen gibt den ganzen Heap oberhalb von p frei. Man soll-

242

18. Datentyp Zeiger

te mit dem Freimachen sorgfältig umgehen, d.h. entweder n e w / d i s p o s e oder g e t m e m / f r e e m e m oder n e w / m a r k / r e l e a s e verwenden und diese nicht abwechselnd benutzen. Beispiel

18.9:

Wir kommen nun noch einmal auf das grobe Verfahren der Ermittlung der Heapgröße von Beispiel 18.3 zurück und wollen es verfeinern. program h e a p t e s t ; uses c r t ; type f e l d = array[0..1023] of b y t e ; zeiger = Afeld; var i : i n t e g e r ; y, z:zeiger; procedure w r i t e _ a d d r e s s ( z : p o i n t e r ) ; begin writeln('seg:off ',seg(z*):4,':',ofs(zA):4); end; begin clrscr; write('heaporg ' ) ; write_address(heaporg); write('heapptr '); write_address(heapptr); write('heapend ' ) ; write_address(heapend); write('frei ' ) ; writeln(memavail); for i := 1 to 3 do begin new ( z ) ; w r i t e l n ; write_address(z); write('heaporg '); write_address(heaporg); write('heapptr '); write_address(heapptr); write('frei ' ) ; writeln(memavail); new(y); dispose(z); end; readln; end. Zum Schluß sei die Leistungsfähigkeit des Zeigerprinzips noch an einem Beispiel demonstriert. Computer-Spiele wie ADVENTURE beruhen darauf, daß der Spieler in einem zwei- oder dreidimensionalen Gebilde von Räumen herumwandern kann und in den einzelnen Räumen alle möglichen Abenteuer mit Geistern und Räubern erleben kann.

243 Es soll das Wandern in einem zweidimensionalen Gebilde von Räumen realisiert werden, die durch Türen miteinander verbunden sind (die Abenteuer dort werden weggelassen). Die Räume mögen z. B. wie in Bild 18.9 zusammenhängen. Die Angaben über diesen Raumplan sollen dann wie in Bild 18.10 gezeigt in einer Datei gespeichert sein.

6 3 4

5

1

2

8 7

Bild 18.9: Raumplan für Beispiel 18.10

Raum-Nr. 7 1 8 5 6 3 2 4

Tuer nach Raum Nord Ost Sued West 8 0 0 0 0 6 3 0

0 8 0 1 0 5 0 2

0 0 7 0 3 2 0 0

(0 = keine Tuer)

0 5 1 3 0 0 4 0

Bild 18.10: Daten für den Raumplan von Bild 18.9

Die Reihenfolge der Räume in dieser Liste ist offensichtlich beliebig, wie auch die Raumnummern in dem Plan. In dem folgenden Beispiel besorgt die Funktion einlesen( ) das Anlegen des Raumplanes als verkettete Datenstruktur. Es wird die Datei mit den Daten gelesen und aus den Raumnummern eine verkettete Liste angelegt (Bild 18.11a). Dann wird die Datei nochmals gelesen und aus den Türen die Verkettung der Räume erzeugt (Bild 18.11b).

244

18. Datentyp Zeiger

Bild 18.11a: Anfang der Raumliste nach erstem Lesen der Datei von Bild 18.10 und Auswerten der Raumnummern

Bild 18.11b: Anfang der Raumliste nach zweitem Lesen der Datei von Bild 18.10 und Auswerten der Türen (* = n i l )

Beispiel

18.10:

program raum_wandern; uses crt; type richtung =(nord, ost, sued, west); raumzeiger = *raum; räum = record nummer : integer; tuer s array[richtung] of raumzeiger; next : raumzeiger; end; var köpf : raumzeiger; funcrtion raum_suchen(köpf:raumzeiger; n:integer): raumzeiger; (*************** räum suchen ****************) (* Es wird in der in köpf beginnenden Liste von *) (* Räumen nach dem Raum Nr. n gesucht. Der Funk-*) (* tionswert ist ein Zeiger auf diesen Raum *) (* (Bild 18.11a). *) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

var p begin

raumzeiger;

18.2 Dynamische Variablen und verkettete Datenstrukturen

if n =0 then p :=nil; p := köpf; while (p nil) and (p^nummer n) do p := p^.next; raum_suchen := p; end; procedure einlesen (var köpf: raumzeiger); j**************** einlesen *****************) (* Die Datei mit dem Raumplan wird zweimal ge- *) (* lesen. Zuerst werden die Raumnummern der 1. *) (* Spalte benutzt, um eine verkettete Liste der *) (* Räume zu erstellen (s. Bild 18.11a). Danach *) (* werden die Angaben über die Türen benutzt, um*) (* die Verzeigerung zwischen den Räumen herzu- *) (* stellen (Bild 18.11b). *) [************************************************}

var filename:string[20]; r : richtung; f : text; p : raumzeiger; i,nr:integer; begin write('Wie heißt der Raumplan?'); readln(filename); assign(f,filename); reset(f); (* Es werden (zunächst) nur die Raumnummern gelesen, und es wird die gekettete Liste der Räume von Bild 18.11a angelegt.*) köpf := nil; while not eof (f) do begin new(p); readln(f,i); pA.nummer := i; for r :=nord to west do pA.tuer[r]:= nil; p A . next : = köpf; köpf := p end; (* Die Datei filename wird nochmals gelesen: jeweils Raumnummer und die Türen von diesem Raum aus angelegt (Liste von Bild 18.10.*) reset (f); while not eof(f) do

245

246

18. Datentyp Zeiger

begin read(f,i); p := raum_suchen(köpf,i); for r := nord to west do begin read(f,nr); if nr =0 then pA.tuer[r] := nil else p".tuer[r] := raum_suchen(kopf,nr); end; end; end; (* einlesen *) procedure wandern; var platz:raumzeiger; nr :integer; c : char; begin writeln('In welchem Raum wollen Sie anfangen?'); readln (nr); platz := raum_suchen(kopf, nr); if platz = nil then begin writeln('So einen Raum gibt es nicht.'); exit; end; writeln('Sie sind im Raum Nr.',platzA.nummer:3); repeat write ('In welcher Richtung wollen '); writeln(' Sie gehen?'); writeln('Gib n, o, s, w oder e für Ende'); readln(c); case c of 'n','N':begin platz :=platzA.tuer[nord]; if platz = nil then writeln('Geht nicht') else begin writeln('Sie sind im Raum Nr.'); writeln(platz A.nummer:3); end; end; 'o','O':begin platz :=platz".tuer[ost];

18.2 Dynamische Variablen und verkettete Datenstrukturen if platz = nil then writeln('Geht nicht') else begin write('Sie sind im Raum Nr.'); writeln(platz ".nummer:3); end; end; 's','S':begin platz :=platzA.tuer[sued]; if platz = nil then writeln('Geht nicht') else begin write('Sie sind im Raum Nr.'); writeln(platzA.nummer:3); end; end; 'w', 'W :begin platz :=platzÄ.tuerfwest]; if platz = nil then writeln('Geht nicht') else begin write('Sie sind im Raum Nr.'); writeln(platz *.nummer:3); end; end; end; (*case*) until (c ='e') or (c = 'E'); end; (* wandern *) begin (***** Anweisungsteil*****) einlesen(köpf); wandern; end.

19. Modularisierung und Units 19.1 Modularisierung Überschreitet ein Programm einen gewissen Umfang oder wollen mehrere Leute gleichzeitig an dem Problem arbeiten, sollte man das Programm in einzelne Teile zerlegen können, eben Modularisieren. Dabei kann man drei Stufen unterscheiden: - Modularisieren des Quelltextes, - Modularisieren des Objektcodes, - Getrennte Übersetzung der Quelltextmodule. Modularisierung des Quelltextes Dies ist die einfachste Stufe der Modularisierung. Der Quelltext wird in mehreren Dateien gehalten. Dies vereinfacht nur die Arbeit des Editierens des Programmtextes. In dem Hauptprogramm, dem äußeren Rahmen, wird durch die Compiler-Direktive "Einfügen" ("Include" oder "Insert") auf die Untertexte verwiesen. Diese Direktive heißt in Turbo Pascal (*$Idateiname*) An der betreffenden Stelle wird der Text von d a t e i n a m e eingefügt. Der Compiler trifft jedenfalls immer auf den vollständigen Quelltext. Dies war bei der Version 3.0 die übliche Vorgehensweise. Die Tool-Boxen der Version 3.0 enthalten die betreffenden Spezialprogramme als Pascal-Quelltext, der durch Include-Direktiven einzufügen ist. So kann es leicht sein, daß ein kurzes Grafik-Programm (mit der Graphix-Tool-Box) oder eine Dateiverwaltung (mit der Database-Tool-Box) einige Tausend Zeilen lang wird. Und der an sich sehr schnelle Turbo-Compiler erscheint dann gar nicht mehr so schnell. Modularisierung des Objektcodes (Overlays) Normalerweise muß beim Starten eines Programms der ganze Objektcode im Arbeitsspeicher zur Verfügung stehen. Das führt bei kleinen Speichern und großen Programmen zu Schwierigkeiten. Unter einem Overlay versteht man einen Teil des Objektcodes, der erst bei Bedarf zur Laufzeit in den Arbeitsspeicher nachgeladen wird. Durch geschickte Aufteilung des Objektcodes in Overlays kann man erreichen, daß sich mehrere Overlays zeitlich nacheinander denselben Speicherplatz teilen. Es sind so auch Programme lauffähig, die insgesamt nicht im Arbeitsspeicher untergebracht werden. In Turbo Pascal 3.0 konnten Prozeduren und Funktionen durch den Vorsatz OVERLAY gekennzeichnet werden. Dieser Vorsatz bewirkt, daß der Compiler

250

19. Modularisierung und Units

den O b j e k t c o d e nicht in der Datei des H a u p t p r o g r a m m s ablegt, sondern in einer g e s o n d e r t e n O v e r l a y d a t e i , aus der dann zur L a u f z e i t die einzelnen O v e r lays n a c h g e l a d e n werden. Da es bei Version 4 . 0 die M ö g l i c h k e i t der getrennten Ü b e r s e t z b a r k e i t von M o d u l e n (sogenannte Units) gibt, ist die Bildung von O v e r l a y s dort nicht m e h r vorgesehen. In T u r b o Pascal 5.0 und 6 . 0 gibt es O v e r l a y s auf der E b e n e von Units ( s . dazu Kap. 19.4).

Getrennte Übersetzung der Quelltextmodule N a c h den obigen beiden M o d u l a r i s i e r u n g e n wurde nur der Quelltext f ü r das Editieren oder der O b j e k t c o d e zur Laufzeit in Teile zerlegt. E r n s t h a f t e s M o d u larisieren bedeutet aber, d a ß Teile des Quelltextes getrennt compiliert werden k ö n n e n . Sie werden dann nach dem C o m p i l i e r e n des Quelltextes, in dem die A u f r u f e stehen, in den e r z e u g t e n O b j e k t c o d e e i n g e b u n d e n . Dazu müssen zwei Voraussetzungen erfüllt sein: -

-

Der C o m p i l e r m u ß in der Lage sein, Quelltext, der kein vollständiges Prog r a m m darstellt, übersetzen zu können. Solche P r o g r a m m t e i l e nennt m a n auch M o d u l e oder Units. Es m u ß ein Binder v o r h a n d e n sein, der nach dem Compilieren eines Prog r a m m s , das Verweise auf die vorübersetzten M o d u l e enthält und also nicht vollständig ist, diese in den erzeugten O b j e k t c o d e e i n f ü g t .

Da der B i n d e r den O b j e k t c o d e einfügt, ist es dann auch nicht notwendig, daß dieser durch Übersetzen eines P a s c a l - M o d u l s entstanden ist. Er k ö n n t e auch in einer a n d e r e n Sprache f o r m u l i e r t worden sein. Die Version 3.0 von T u r b o Pascal enthält keinen Binder in diesem Sinne. Es gibt eine L a u f z e i t - B i b l i o t h e k (Run-Time-Library), die alle Standardroutinen enthält und dem vom C o m p i l e r erzeugten O b j e k t c o d e z u g e f ü g t wird. Wenn m a n das kürzeste P a s c a l - P r o g r a m m

program n i x ; begin end. mit der C o m p i l e r o p t i o n Com compiliert, ist der U m f a n g von N I X . C O M ca. 11 KB! Es ist e i n e r der w e s e n t l i c h e n Vorzüge von Version 4.0, diesen M a n g e l nicht m e h r zu haben. C o m p i l i e r t man das obige P r o g r a m m bei 6.0 mit Compile/Destination Disk, so hat der O b j e k t c o d e nur noch etwa 1,5 K B !

19.2 Units

251

19.2 Units Mit Units ist eine echte Modularisierung möglich, d. h. Teile eines Programms können getrennt compiliert werden. Es gibt demnach zwei Arten von compiIierbaren Pascal-Quelltexten. (19-1)

Der syntaktische Begriff programm ist in (4-1) näher erklärt und alle bisherigen Programme waren entsprechend aufgebaut. Demgegenüber gibt es nun noch Units mit folgendem Aufbau:

Eine Unit dient dazu, Konstanten, Datentypen, Variablen, Prozeduren und Funktionen zu definieren, die dann von anderen Programmen benutzt werden können. Der Kopf einer Unit hat die Form unit-kopf

(19-3

Der Name der Unit wird gebraucht, wenn man sich in einer uses-Klausel (4-3) auf die Unit beziehen will. Im Interface-Teil werden die "öffentlichen" Konstanten, Typen, Variablen, Prozeduren und Funktionen vereinbart, auf die sich andere Programme oder Units mit uses u n i t n a m e beziehen können.

252

19. Modularisierung und Units

Der Aufrufer, in dem u s e s u n i t n a m e vorkommt, zen, als wären sie beim Aufrufer selbst vereinbart. global. Im Interface-Teil stehen nur die Köpfe von nen. Der Prozedur- bzw. Funktionsrumpf folgt dann der folgende Form hat:

kann diese Größen benutSie sind für den Aufrufer Prozeduren und Funktioim Implementations-Teil,

Daneben können dort Konstanten, Typen, Variablen, Prozeduren und Funktionen vereinbart werden, die nicht öffentlich sind, sondern für die Unit "privat". Funktionen und Prozeduren brauchen keine Parameterlisten mehr. Sie stehen schon in den Köpfen des Interface-Teiles, die die Wirkung eines f o r w a r d -

19.2 Units

253

Bezuges haben. Die Köpfe können im Implementations-Teil aber wiederholt werden, so daß beide Formulierungen korrekt sind: unit dies; interface procedure p(a,b s real); implementation procedure p; (*Die Parameterliste steht schon im interface-Teil.*) begin

end;(* of p *) end. unit das; interface procedure p(a,b:real); implementation procedure p(a,b:real); (*Die Parameterliste kann wiederholt werden. Sie muß aber mit der im interface-Teil übereinstimmen.*) begin

end;(* of p *) end. Im Initialisierungs-Teil initialisierungs-teil

verbundanweisung können Anweisungen aufgeführt werden, die bei der uses-Klausel uses unit-name ausgeführt werden. Dieser Teil besteht mindestens aus dem die Unit abschließenden end.

254 Aus einem Programm erzeugt der Computer mit Compile!memory disk einen lauffähigen Objektcode .EXE. Aus einer Unit wird mit Compile!memory disk ein Objektmodul .TPU, den der Binder Programmen oder anderen Units mit einer entsprechenden uses-Klausel hinzufügt. Dabei ist folgendes zu beachten. Bei Compile/Destination disk wird die Unit xxx.TPU in dem Verzeichnis hinterlegt, in dem der Quelltext xxx.PAS steht. Wird in einem Programm dann mit uses x x x ; auf diese Unit verwiesen, muß dieses Verzeichnis bei Options! Directories!EXE & TPU directory angegeben werden oder xxx.TPU steht in einem unter Options/DirectorieslUnit directories angegebenen Verzeichnis. Der Einfachheit halber halte man also gleich den Quelltext xxx.PAS der Unit in einem Verzeichnis aus Options!Directories!Unit directories und compiliere mit Compile/Destination disk die Unit als xxx.TPU dorthin. Als nicht zu triviales Beispiel wollen wir auf die Simulierung eines Stack in Beispiel 18.8 zurückgreifen. Der Einfachheit halber sei es zunächst noch einmal wiederholt. Beispiel 19.1: Hier erst einmal das vollständige Beispiel 18.8. Es wird ein Stack mit den Operationen p o p u p und p u s h d o w n durch eine einfach gekettete Liste nachgebildet. Da ein Stack nach dem LIFO-Prinzip arbeitet (Last in First out), erfolgen abkellern ( p u s h d o w n ) und auskellern ( p o p u p ) immer am Anfang der Liste. program s t a c k _ s i m u l a t i o n ; uses crt; const stackmax = 10; type s t a c k p t r = ~stackelement; datentyp = integer; s t a c k e l e m e n t = record d a t e n : d a t e n t y p ; n e x t : s t a c k p t r end; var s t : stackptr; fertig : boolean; stacksize, d : integer; (* s t a c k s i z e i s t f ü r a l l e P r o z e d u r e n g l o b a l . * )

procedure p r i n t s t a c k ( v a r s t : s t a c k p t r ) ; begin if s t = nil then w r i t e ( ' stackende ') else begin writeln(' ',stA.daten:4,'j '); writeln(' I'); printstack(stA.next) end; end; (* p r i n t s t a c k *)

procedure push(var st:stackptr; d:datentyp); var hilf : stackptr; c:char; begin new(hilf); hilf'.daten := d; hilfnext := st; st := hilf; stacksize := stacksize + 1; end; (* push *) procedure pop(var st:stackptr; var d:datentyp) var hilf:stackptr; begin hilf := st; st := st^.next; d := hilf*.daten; dispose(hilf); stacksize := stacksize - 1; end; (* pop *) procedure create(var st:stackptr); begin st := nil; stacksize := 0; end; (* create *) function isfull(st:stackptr):boolean; begin isfull := stacksize >= stackmax; end; function isempty(st:stackptr):boolean; begin isempty := st = nil; end; procedure menue; var c:char; begin clrscr; fertig := false; printstack(st); gotoxy(40,5); writeln('Was wollen Sie:'); gotoxy(40,7); writeln('+ = push down');

256

19. Modularisierung und Units gotoxy(40,8); writeln('- = pop up'); gotoxy(40,9); writeln('e = end'); gotoxy(40,10); readln(c); case c of '+': if isfull(st) then begin gotoxy(40,20); write('Keller leider'); write(' voll'); readln(c); end else begin gotoxy(40,12); writeln('Gib Daten:'); gotoxy(40,13); readln(d); push(st,d); end; '-': if isempty(st) then begin gotoxy(40,20); writeln('Keller leider leer'); readln(c); end else begin pop(st,d); gotoxy(10,1); writeln('ausgekellert: ',d:4); readln(c); end;

'e','E': fertig := true; end; (* menue *)

end; (* case *)

begin (********** Anweisungsteil ***********) create(st); repeat menue;

19.2 Units

257

until f e r t i g ; end. Wir zerlegen das Programm in die folgenden Units: T Y P E N . P A S enthält alle Datentypen, Konstanten und Variablen:

unit typen; interface const stackmax = 10; type s t a c k p t r = 'stackelement;

var

datentyp = integer; s t a c k e l e m e n t = record d a t e n next st : stackptr; fertig : boolean; stacksize, d : integer;

: :

datentyp; s t a c k p t r end;

implementation begin end. EA.PAS enthält die E/A-Operationen Menü, Zahlen lesen, Stack ausgeben. Die Unit E A benutzt T Y P E N und die gleich folgende Unit STACKOP.

unit ea; interface uses t y p e n , s t a c k o p , c r t ; procedure printstack(var s t : s t a c k p t r ) ; procedure create(var s t : s t a c k p t r ) ; procedure menue; var c : c h a r ;

implementation procedure printstack(var

begin if s t = nil then w r i t e ( ' else begin

st:stackptr); stackende

')

writeln(' ',stA.daten: 4,'|'); writeln(' I '); printstack(stA.next)

end; end; (* p r i n t s t a c k

*)

procedure create(var

begin s t := nil; s t a c k s i z e := 0; end; (* c r e a t e * )

st:stackptr);

258

procedure menue; var c:char; begin clrscr; fertig := false; printstack(st); gotoxy(40,5); writeln('Was wollen Sie:'); gotoxy(40,7); writeln('+ = push down'); gotoxy(40,8); writeln('- = pop up'); gotoxy(40,9); writeln('e = end'); gotoxy(40,10); readln(c); case c of '+': if isfull(st) then begin gotoxy(40,20); write('Keller leider'); write(' voll'); readln; end else begin gotoxy(40,12); writeln('Gib Daten:'); gotoxy(40,13); readln(d); push(st,d); end; '-': if isempty(st) then begin gotoxy(40,20); writeln('Keller leider leer'); readln; end else begin pop(st,d); gotoxy(10,1); writeln('ausgekellert: ',d:4);

19.2 Units

259

readln end; ' e ' , ' E ' : f e r t i g := t r u e ; end; ( * menue * )

end; ( * c a s e * )

begin end. STACKOP.PAS enthält die Stackoperationen pushdown und popup. Es wird die Unit TYPEN benutzt. unit s t a c k o p ; interface uses typen; procedure push(var s t : s t a c k p t r ;

d:datentyp);

procedure pop(var s t s s t a c k p t r ; var d : d a t e n t y p ) ; function i s f u l l ( s t : s t a c k p t r ) : b o o l e a n ; function i s e m p t y ( s t : s t a c k p t r ) : b o o l e a n ; implementation procedure push(var s t s s t a c k p t r ; d : d a t e n t y p ) ; var h i l f : s t a c k p t r ; c s c h a r ; begin n e w ( h i l f ) ; h i l f ^ . d a t e n : = d; h i l f n e x t := s t ; s t s= h i l f ; s t a c k s i z e s= s t a c k s i z e + 1; end; ( * push * ) procedure pop(var s t s s t a c k p t r ; var d s d a t e n t y p ) ; var h i l f s s t a c k p t r ; begin h i l f s= s t ; s t s= s t ^ . n e x t ; d := h i l f ^ . d a t e n ; dispose(hilf); s t a c k s i z e s= s t a c k s i z e - 1; end; ( * pop * ) function i s f u l l ( s t s s t a c k p t r ) s b o o l e a n ; begin i s f u l l : = s t a c k s i z e >= stackmax; end;

260

19. Modularisierung und Units

function isempty(st:stackptr):boolean; begin isempty := st = nil; end; begin end. Aus den obigen Pascaltexten sind durch Compilieren die drei Units TYPEN.TPU, STACKOP.TPU und EA.TPU (in dieser Reihenfolge!) zu erzeugen. Das ganze Programm reduziert sich dann auf: program stack (input,output); uses crt, typen, ea, stackop; begin (********** Anweisungsteil ***********) create(st); repeat menue; until fertig; end.



Falls man sich eine für seine Bedürfnisse wichtige Unit geschrieben hat, kann es nützlich sein, diese in die Standardbibliothek TURBO.TPL aufzunehmen. Man ist dann auch die Sorge los, daß sie im richtigen Directory steht. Dazu gibt es das Program TPUMOVER, mit dem man TPUs in TURBO.TPL aufnehmen bzw. auch entfernen kann, falls einem die Bibliothek TPL zu groß geworden ist. Im Kapitel 11.1 war etwas über den Gültigkeitsbereich von Namen gesagt worden. Damit zusammen hingen die Begriffe lokal und global. Sinngemäß ist das auch auf Units zu übertragen. Nehmen wir zur Erklärung das folgende kleine Beispiel: unit a; interface const kennung = 'unit a'; implementation begin end. program demo; uses a; const kennung ='demo';

19.2 Units begin writeln end.

(kennung);

Ausgabe:

261

demo

Die Unit a definiert den Namen k e n n u n g , der im Programm neu definiert wird. Im Anweisungsteil des Programms wird dann unter k e n n u n g das lokale k e n n u n g genommen. Der Name k e n n u n g aus der Unit a ist global für das Programm und wird durch den gleichen lokalen Namen ersetzt. Falls man also zufällig in einem Programm einen Namen aus einer der Standard-Units verwendet, so ist der Name in der Standard-Unit für dieses Programm außer Kraft gesetzt (neu definiert worden). Im Gegensatz zu geschachtelten Blöcken bei Prozeduren sind die globalen Größen aus der Unit im Programm aber trotzdem verfügbar. Ein Name kann auch durch Qualifizierer. name angegeben werden. Und der Qualifizierer kann ein Unitname sein. Im obigen Beispiel könnte man also mit a . k e n n u n g auch zu dem in a definierten Namen k e n n u n g zugreifen: program demo; uses a ; const k e n n u n g = ' d e m o ' ; begin w r i t e l n (kennung); w r i t e l n (a.kennung) end.

Ausgabe: Ausgabe:

demo unit a

Es bleibt noch die Frage, ob die Reihenfolge der Units nach uses eine Rolle spielt, ob also program demo; uses a , b , c , d ; und program demo uses a , c , d ,

b;

gleichberechtigt sind.

262

19. Modularisierung und Units

Beispiel

19.2:

Demonstration der Schachtelung von Units. unit a ; interface const kennung = 'unit a ' ; implementation begin end. unit b; interface const kennung = 'unit b ' ; implementation begin end. unit c; interface const kennung = 'unit c'; implementation begin end. program demo; uses a , b , c ; ( * s . B i l d 1 9 . 1 * ) const kennung = ' d e m o ' ; begin w r i t e l n (kennung); ( * - —>demo * ) w r i t e l n (a.kennung); ( * - —>unit a *) w r i t e l n (b.kennung); ( * - — > unit b *) writeln (c.kennung); ( * - — > unit c *) end.



Die Units werden in der Reihenfolge eingebunden, in der sie hinter u s e s aufgeführt sind. Sie sind in der Weise von Bild 19.1 geschachtelt. Jede innere Unit setzt die gleichnamigen Dinge der äußeren außer Kraft. Bei der Konstruktion unit a ; interface const kennung = ' u n i t implementation begin end.

a';

19.3 Die Standard-Units

263

i-systenv const kennung='unit a ' ; rb

const kennung = ' u n i t b' rC const kennung = ' u n i t c' •-program const kennung ='demo'

Bild 19.1: Schachtelung von Units entsprechend Beispiel 19.2

unit b; interface uses a ; const kennung = ' u n i t implementation begin end.

b';

program demo; uses b ; begin w r i t e l n (kennung); w r i t e l n (a.kennung); end.

(* (*

> u n i t b *) > u n i t a *)

umfaßt Unit a die Unit b, und diese das Programm demo.

19.3 Die Standard-Units Was eine Unit ist und wie man eigene schreiben kann, ist im vorigen Kapitel 19.2 beschrieben worden. Hier geht es um einen Überblick über die standardmäßig vorhandenen Units. Es gibt insgesamt sieben Standard-Units, nämlich SYSTEM, CRT, GRAPH, DOS, PRINTER, GRAPH3, TURB03. In ihnen sind

264

19. Modularisierung und Units

Hunderte von Namen für Konstanten, Typen, Variablen, Prozeduren und Funktionen definiert. Die zuerst genannten sechs Standard-Units sind in der Datei TURBO.TPL (TPL= Turbo Pascal Library) enthalten. Im folgenden wird ein Überblick darüber gegeben, was in diesen Standard-Units definiert ist. Angesichts des komfortablen Hilfe-Mechanismus beschränken wir uns dabei auf die Nennung der Namen. Die dazu notwendige Erklärung kann man sich nach Positionierung des Cursors auf den Namen durch Ctrl-Fl bzw. Anklicken (links - rechts) mit der Maus vorführen lassen. Mit HelpIContentslUnits bekommt man sehr einfach einen Überblick. Für Version 7.0 siehe Kapitel 27. Die Unit SYSTEM.TPU Sie ist die wichtigste Unit. Sie enthält die grundlegenden Standardprozeduren und -funktionen von Turbo Pascal. Auf diese Unit braucht (und darf) nicht mit u s e s s y s t e m verwiesen zu werden. Sie wird immer automatisch in jedes Programm eingebunden. SYSTEM.TPU ist gewissermaßen die Laufzeitbibliothek von Turbo Pascal 6.0. In SYSTEM.TPU sind definiert: Prozeduren und Funktionen: abs blockwrite cos eof filesize getmem ioresult memavail ord ptr rename seek sizeof succ write

addr chdir cseg eoln fillchar halt length mkdir paramcount random reset seekeof sptr swap writeln

append chr dec erase flush hi In move paramstr randomize rewrite seekeoln sqr trunc

arctan assign close concat delete dispose exp exit freemem frac inc insert lo mark new odd pos Pi read readln rmdir round seg settextbuf sqrt sseg t r u n c a t e upcase

blockread copy dseg filepos getdir int maxavail ofs pred release runerror sin str val

Variable: input,output

: text;

Typenkonstanten: erroraddr: pointer = n i l ; exitcode: exitProc: pointer = n i l ; filemode: freelist: p o i n t e r = n i l ; heapend:

i n t e g e r = 0; byte = 2; pointer = nil;

19.3 Die Standard-Units heaperror: pointer heapptr: pointer p r e f i x s e g : word stacklimitsword

= = = =

nil; nil; 0; 0;

heaporg: inoutres: randseed: test8087:

265

pointer = nil; i n t e g e r = 0; longint; byte = 0;

Die Unit CRT.TPU enthält die für eine effektive Ein- und Ausgabe notwendigen Prozeduren zur direkten Kontrolle von Bildschirm, Tastatur und Lautsprecher. In CRT.TPU sind definiert: Prozeduren und Funktionen: assigncrt delline keypressed readkey textmode

clreol gotoxy lowvideo sound wherex

clrscr highvideo nomtvideo textbackground wherey

delay insline nosound textcolor window

Die Prozedur a s s i g n c r t wird bei der Initialisierung von CRT mit as s i g n c r t ( i n p u t ) ; r e s e t (input); as s i g n c r t ( o u t p u t ) ; r e w r i t e ( o u t p u t ) ; aufgerufen, wodurch die Standard-Textdateien i n p u t und O u t p u t nicht mehr dem Standardgerät CON zugeordnet sind, sondern einem speziellen Treiberprogramm in CRT. Mit den Prozeduren s o u n d , d e l a y und n o s o u n d kann man besondere Toneffekte erzeugen, wie das Beispiel 11.5 zeigte. Variable: checkbreak: boolean; checkeof: boolean; d i r e c t v i d e o : checksnow: b o o l e a n ; l a s t m o d e : word; textattr: windmin: word; windmax: word;

boolean; byte;

c h e c k b r e a k ist standardmäßig auf t r u e gesetzt, d.h. beim Drücken von Ctrl-Break wird das Programm bei der nächsten Ein- oder Ausgabe von Text abgebrochen. Setzt man explizit checkbreak

:=false;

unterbleibt die Prüfung auf Abbruch.

266

19. Modularisierung und Units

c h e c k e o f ist standardmäßig auf f a l s e gesetzt, d.h. die Eingabe von CtrlZ wird nicht als Dateiende erkannt. Erst nach checkeof := t r u e ; wird Ctrl-Z als Dateiende erkannt (s. z.B. in Beispiel 11.5). Im übrigen sollte man sich angewöhnen, bei allen Programmen u s e s aufzuführen. Die Unit DOS.TPU

Diese Unit unterstützt viele DOS-Funktionen. In DOS.TPU sind definiert: Funktionen und Prozeduren: diskfree envcount findfirst getcbreak getftime intr setcbreak setintvec unpacktime

disksize envstr findnext getdate getintvec keep setdate settime

dosexitcode exec fsearch getenv gettime msdos setfattr setverify

dosversion fexpand fsplit getfattr getverify packtime setftime swapvectors

Datentypen: type

coinstr = pathstr = dirStr = namestr = extstr = searchrec

s t r i n g [127]; s t r i n g [79]; s t r i n g [67]; s t r i n g [8]; s t r i n g [4]; = record fill a r r a y [ 1 . . 2 1 ] of b y t e ; attr byte; time longint; size longint; name s t r i n g [ 1 2 ] ; end; datetime = record year,month,day,hour, m i n , s e c : word; end; registers = r e c o r d c a s e integer of

crt;

19.3 Die Standard-Units 0: 1:

267

(ax,bx,cx,dx,bp,si,di, d s , e s , f l a g s : word); (al,ah,bl,bh,cl,ch,dl, d h : b y t e ) ; end;

Die Prozeduren/Funktionen kann man unterteilen in solche zur Unterstützung von Interrupts ( i n t r , m s d o s , g e t i n t v e c , s e t i n t v e c ) , Prozeduren für Datum und Uhrzeit (getdate, getftime, gettime, packtime, s e t d a t e , s e t f t i m e , s e t t i m e u n p a c k t i m e ) , Statusfunktionen für Laufwerke und Festplatten ( d i s k s i z e , d i s k f r e e ) , Prozeduren zur Bearbeitung von Dateieinträgen ( g e t f a t t r , f i n d f i r s t , findnext, f s p l i t , s e t f a t t r , f e x p a n d , f s e a r c h ) , sowie Prozeduren und Funktionen für Prozesse ( d o s e x i t c o d e , e x e c , k e e p , s w a p v e c t o r s ) . Manche davon sind genauer in Kapitel 21 beschrieben. Die Unit PRINTER.TPU vereinbart die Textdatei 1st var 1 s t : t e x t ; und verbindet sie mit dem Gerät LPT1, d. h. dem Drucker. Durch Nennung von 1 s t in w r i t e und w r i t e l n kann man damit in einfacher Weise per Programm Ergebnisse auf dem Drucker ausgeben (falls er an der ersten parallelen Schnittstelle angeschlossen ist), wie das folgende kleine Beispiel zeigt: program d r u c k e r ; uses p r i n t e r ; var x : i n t e g e r ; begin readln(x); w r i t e l n ( 1 s t , ' W e r t von x : ' , x : 6 ) ; end. Die Unit TURB03.TPU dient vor allem der Kompatibilität zur Version 3.0 von Turbo Pascal. In ihr sind die in der Version 3.0 typischen Namen von Variablen und Prozeduren vereinbart. Variable: kbd: t e x t ; cbreak: boolean a b s o l u t e checkbreak; Prozeduren und Funktionen: assignkbd longfilepos lowvideo

memavail longseek ioresult

maxavail normvideo

longfilesize highvideo

268

19. Modularisierung und Units

Hervorzuheben ist, daß die Unit CRT vorausgesetzt wird. Es muß also uses c r t ,

turbo3;

heißen. Die Unit GRAPH3.TPU Bei der Version 3.0 von Turbo Pascal waren die Grafikroutinen in der Datei GRAPH.P deklariert, die mit (*$I GRAPH.P*) in das Programm einzufügen war. Dafür steht nun die Unit GRAPH3.TPU zur Verfügung, die auch die Turtle Graphik von 3.0 enthält. Konstanten: n o r t h = 0;

east

= 90;

south = 180;

west

= 270;

Prozeduren und Funktionen: graphmode palette draw getpic fillshape clearscreen home setheading turnleft turtlethere

graphcolormode graphbackground colortable putpic fillpattern forwd nowrap setpencolor turnright wrap

hires graphwindow arc getdotcolor pattern heading pendown setposition turtledelay xcor

hirescolor plot circle fillscreen back hideturtle penUp showturtle turtlewindow ycor

Diese Unit setzt CRT.TPU voraus: uses c r t , g r a p h 3 ; Die Unit GRAPH.TPU enthält ein Paket von mehr als 50 Grafikroutinen. Dazu gehören die Grafiktreiber in den Dateien .BGI (BGI = Borland Graphics interfaces) für die wichtigsten Video-Adapter. Eine nähere Beschreibung findet sich in Kapitel 23. Die Unit OVERLAY.TPU Ab der Version 5.0 gibt es auf Unit-Ebene den Begriff Overlay, der im folgenden Kapitel 19.4 behandelt werden wird. In OVERLAY.TPU sind die vom Overlay-Manager benötigten Prozeduren und Funktionen definiert.

19.4 Overlays

269

Funktionen und Prozeduren: ovrclearbuf ovrinit ovrsetretry

ovrgetbuf ovrinitems

ovrgetretry ovrsetbuf

Konstanten, Typen und Variablen: type ovrreadfunc = function(ovrseg : word): integer; const ovrok 0; ovrerror = -1 -1 ovrnotfound -2; ovrnomemory = -3 ovrioerror -4; ovrnoemsdriver = -5 ovrnoemsmemory = -6; var ovrresult:integer; ovrfilemode:byte; ovrloadcount:word; ovrreadbuf:ovrreadfunc; ovrtrapcount:word; Es sei zum Schluß noch einmal auf das Programm TPUMOVER.EXE verwiesen, mit dem sich die Bibliothek TURBO.TPL manipulieren läßt, d.h. Units hinzugefügt oder entfernt werden können.

19.4 Overlays Es kann vorkommen, daß große Programme nicht in den Arbeitsspeicher passen. Dann ist es eine klassische Methode, Teile des Objektcodes in Overlays zu verlagern, die erst bei Bedarf nachgeladen werden. Die Overlays teilen sich also einen gemeinsamen Teil des Speichers. Bei der Version 3.0 von Turbo Pascal konnten Prozeduren und Funktionen als Overlay gekennzeichnet werden. Ab der Version 5.0 gibt es Overlays auf der Ebene von Units, d. h. die kleinste Overlay-Einheit ist eine Unit. Zuerst sei beschrieben, wie aus einer Unit ein Overlay wird. Nehmen wir als Beispiel: unit das; (*$0+*) (*$F+*) Interface procedure das_proc; i mplementation procedure das_proc; begin writeln('Hier ist das_proc aus Unit das'); end; end.

270

19. Modularisierung und Units

Beim Compilieren müssen die beiden Compilerdirektiven $F und $ 0 auf + stehen. ( * $ F + * ) besagt, daß beim Aufruf und Verlassen von Routinen dieser Unit Sprünge über Segmentgrenzen erfolgen können (far). Die Direktive ( * $ 0 + * ), d. h. Options/Compiler Overlays allowed ON, veranlaßt den Compiler, gewisse zusätzliche Prüfungen auszuführen. Um es deutlich zu sagen, eine mit ( * $ 0 + * ) übersetzte Unit kann später als Overlay benutzt werden, kann aber auch als ganz normale Unit mit uses . . . verwendet werden. Ebenso gebe es noch unit dies; (*$0+*) (*$F+*) interface procedure dies_proc; implementation procedure dies_proc; begin writeln('Hier ist dies_proc aus Unit dies'); end; end. Benutzt werden sollen diese beiden Units in dem Programm OVER.PAS: program over; (*$F+*) (*$0+*) uses overlay, crt,dies,das; (*$0 dies*) (*$0 das*) begin ovrinit('over.ovr'); if ovrresult ovrok then begin writeln('Overlay-Fehler',ovrresult:4); halt end; dies_proc; das_proc; end. Hinter uses ist als erste die Standardunit OVERLAY zu nennen, worauf die anderen verwendeten Units wie üblich folgen. Die als Overlays verwendeten Units sind in der Compilerdirektive ( * $ 0 u n i t n a m e * ) aufzuführen. Der Compiler erzeugt dann (bei Compile/Destination Disc) zwei Dateien OVER.EXE und OVER.OVR, in der alle als Overlay gekennzeichneten Units enthalten sind (hier also dies.TPU und das.TPU). Die in der Unit OVERLAY enthaltenen Routinen zeigt Bild 19.3. Als erstes ist o v e r i n i t

19.4 Overlays aufzurufen, wodurch die Overlay-Verwaltung initialisiert LAY.TPU gibt es (s. Kap. 19.3) eine Variable var

271

wird. In OVER-

ovrresult:integer;

die nach einem Aufruf einer Routine aus OVERLAY (ähnlich i o r e s u l t ) einen Wert erhält (0 = o v r o k für fehlerfreie Ausführung, einen Wert < 0 beim Auftreten eines Fehlers). o v r i n i t legt einen Overlay-Puffer an, der mindestens so groß wie die größte Unit aus .OVR ist. Der Puffer liegt unmittelbar unterhalb des Heap.

Aufruf: Parameter: Wirkung:

ovrinit (filename) Prozedur overlay filename :string; Es wird die Overlayverwaltung initialisiert und die Datei f i l e n a m e geöffnet, f i l e n a m e enthält die als Overlay gekennzeichneten Units, o v r i n i t sucht nach f i l e n a m e in dem aktuellen Directory. Entsprechend der größten Overlay-Unit wird ein Overlay-Puffer angelegt (und der Heap entsprechend nach oben verschoben), o v r i n i t ist vor anderen Routinen aus OVERLAY aufzurufen.

Aufruf: Parameter: Wirkung:

ovrsetbuf (size) Prozedur overlay size: longint; Der Overlay-Puffer erhält die Größe von s i z e Bytes. Der Heap-Beginn wird entsprechend verschoben!

Aufruf: Parameter: Wirkung:

ovrgetbuf Funktionswert: l o n g i n t overlay keine Der Funktionswert ist die momentane Größe des OverlayPuffers in Byte.

Aufruf: Parameter: Wirkung:

ovrclearbuf Prozedur overlay keine Es werden alle momentan in den Overlay-Puffer geladenen Units aus dem Puffer entfernt.

Aufruf: Parameter: Wirkung:

ovrinitems Prozedur overlay keine Es wird geprüft, ob das System eine EMS-Karte enthält, die genügend Platz für die Overlay-Datei .OVR hat. Gegebenenfalls wird sie dorthin geladen. Es sind dann keine Diskettenzugriffe mehr notwendig.

272

Aufruf: Parameter: Wirkung:

WOsize:longint;

Aufruf: Parameter: Wirkung:

ovrgetRetry Funktionswert: longint keine Liefert die aktuelle Größe des Bewährungsbereichs

ovrsetRetry (size)

Prozedur

overlay

Legt die Größe des sog. "Bewährungsbereichs" fest. In diesem Bereich werden Routinen eines Overlays, die häufig aufgerufen werden, nicht ausgelagert. overlay

Bild 19.2: Routinen aus der Unit OVERLAY

Beim Aufruf einer Routine aus einer Overlay-Unit wird geprüft, ob sich diese im Puffer befindet. Wenn nicht, wird sie dorthin nachgeladen (und falls der Platz nicht ausreicht, eine andere wieder ausgeladen). Mit oversetbuf kann der Puffer vergrößert werden, um möglichst viele der Overlay-Units gleichzeitig im Puffer zu halten. Wenn Ihr PC über eine EMS-Karte verfügt (EMS= Expanded Memory Specification), kann der Overlay -Puffer auch dort eingerichtet werden (mit ovrinitems). Es werden dann zeitraubende Diskettenzugriffe gespart. Eine Warnung ist für den Fall auszusprechen, wenn das Programm dynamische Variablen (s. Kap. 18.2) im Heap benutzt. Bevor diese angelegt werden, muß die Größe des Overlay-Puffers endgültig festliegen. Zum Schluß noch der Hinweis, daß die Standard-Units CRT, GRAPH, T U R B 0 3 und GRAPH3 nicht zu Overlay-Units gemacht werden können. Auch selbstgeschriebene Units mit Interrupt-Routinen können keine Overlay-Units werden.

19.5 Make Bei einem größeren Programm wird man zweckmäßig nach dem alten römischen Motto "Divide et impera" (Teile und herrsche) vorgehen. Das Problem wird in Teilprobleme zerlegt, und diese werden als Units formuliert. Auf diese Weise wird das Gesamtprojekt nicht nur sicherer und übersichtlicher formuliert, es können auch mehrere Personen gleichzeitig an dem Projekt arbeiten. Das Beispiel 19.1 war nach diesem Prinzip aufgebaut. Bei dem Projekt "Stacksimulation" waren alle Konstanten, Datentypen und globalen Variablen in der Unit TYPEN.TPU zusammengefaßt, alle Ein- und Ausgabeoperationen in der Unit EA.TPU, alle Operationen an dem zu simulierenden Stack in der Unit STACKOP.TPU. Das eigentliche Programm STACK.PAS war dann entsprechend kurz und übersichtlich. Dabei ergaben sich die Abhängigkeiten:

19.5 M a k e

273

TYPEN.TPU

TYPEN.TPU

STACKOP.TPU

TYPEN.TPU

EA.TPU

TYPEN.TPU

STACKOP.TPU

STACK.PAS Bild 19.3: Die Abhängigkeiten von Beispiel 19.1 STACK.PAS

uses

crt,

STACKOP.TPU

uses

typen;

EA.TPU

uses

crt,typen,

TYPEN.TPU

uses

keine

typen,

ea,

stackop;

stackop;

Man kann diese Abhängigkeiten auch als Baum darstellen ( B i l d 19.3). Die Standardunit C R T ist dabei weggelassen. Bei dieser Vorgehensweise ergibt sich eine ernsthafte Schwierigkeit: W i e kann man sicherstellen, daß die Knoten dieses Baumes immer auf dem aktuellen Stand sind? Wenn jemand an der Unit S T A C K O P . P A S etwas geändert hat, muß ja S T A C K O P . P A S neu zu S T A C K O P . T P U compiliert werden, und ebenso muß E A . T P U durch Compilieren von E A . P A S neu gebildet werden. Schließlich muß auch S T A C K . P A S neu compiliert werden. Wenn also mehrere Personen gleichzeitig an den Knoten dieses Abhängigkeitsbaumes arbeiten, braucht man eine Buchführung über die Entstehungszeit eines jeden Knotens (d. h. Unit). Es darf kein Knoten jünger sein als ein darüberliegender

Knoten.

CompilelMake

einen Mechanismus, der diese zeitlichen Zusammenhänge au-

Bei

Turbo

Pascal

gibt

es

mit

dem

tomatisch prüft und Teile des Baumes neu compiliert. CompilelMake

Menüpunkt ( F 9 ) hat

f o l g e n d e Wirkung: -

Ist unter Compile/Primary

File eine "Hauptdatei" angegeben, wird diese für

M a k e benutzt, andernfalls der im Editor stehende Quelltext. Enthält er eine Klausel u s e s

x x x , wird geprüft, ob X X X . T P U älteren Datums ist als

X X X . P A S . Ist dies der Fall, wird X X X . P A S neu zu X X X . T P U compiliert. -

W i r d der Interface-Teil einer Unit verändert, werden alle Units, die diese benutzen, neu compiliert.

-

Enthält eine Unit eine Include-Direktive ( * $ I d a t e i n a m e * ) und ist d a t e i n a m e jüngeren Datums als die Unit, wird diese neu compiliert.

-

Fügt eine Unit mit ( * L * ) eine .OBJ-Datei ein und ist diese jünger als die Unit, wird diese neu compiliert.

274

19. Modularisierung und Units

Voraussetzung f ü r das Funktionieren mit M a k e ist natürlich, daß d e m C o m p i ler die Q u e l l t e x t e der Units zur Verfügung stehen. Der Vergleich von D a t u m und Uhrzeit wird nicht bei den Standard-Units in T P L v o r g e n o m m e n . Wer ganz sicher gehen will, d e m steht Compile/build zur V e r f ü g u n g . D a b e i werden D a t u m und Uhrzeit nicht verglichen, sondern alle an d e m P r o g r a m m beteiligten Units neu compiliert. Ü b r i g e n s gibt es ein eigenes P r o g r a m m M A K E . E X E , d a s eine solche P r o j e k t v e r w a l t u n g v o r n i m m t . Es wird dazu auf das B e n u t z e r h a n d b u c h [1] v e r w i e s e n .

20. Die Kommandozeilen-Version TPC Bei Turbo Pascal 6.0 gibt es neben der integrierten Entwicklungsumgebung IDE noch eine Kommandozeilen-Version TPC.EXE, mit der man den Compiler von der MS-DOS-Ebene aus aufrufen kann. Der Aufruf hat die Form: C> TPC [Optionen] Dateinamen [Optionen] Die Klammern [ ] sind nicht zu schreiben, sondern sollen bedeuten, daß die Optionen auch fehlen dürfen. Die obige Schreibweise besagt also, daß die Optionen vor oder nach dem Dateinamen stehen, aber auch ganz fehlen können. Dabei enthalten die Optionen die vielfältigen Angaben, die bei der integrierten Entwicklungsumgebung via OptionsICompiler bzw. Compile gewählt werden können. Die in OptionsICompiler angegebenen Optionen steuern den in IDE integrierten Compiler. Dem externen Compiler TPC.EXE können die gewünschten Optionen beim Aufruf übergeben werden. Es sind aber insgesamt dieselben wie die unter OptionsICompiler vorgesehenen. Eine Aufstellung zeigt Bild 20.1. Normalerweise wird man (wegen des integrierten Editors und bei 6.0 auch Debuggers) ein Pascal-Programm mit der integrierten Entwicklungsumgebung IDE entwickeln. Ein fertiges Pascal-Programm kann dann auch mit der Kommandozeilen-Version TPC UNIX-ähnlich behandelt werden: C> TPC [Optionen] Dateinamen [Optionen] Die Optionen können mit -, aber auch mit / beginnen. Die mit - beginnenden Optionen müssen durch Blank getrennt werden, die mit / beginnenden können durch Blank getrennt werden. Dabei können die Optionen wahlweise links oder rechts vom Dateinamen stehen. Beim Aufruf von TPC werden die Zeilennummern während des Compilierens gezählt und die benötigte Zeit angegeben. Bei der Option /Q werden diese Meldungen unterdrückt. Steht das Quellprogramm in PROG.PAS, wäre der einfachste Aufruf C: TPC PROG Hat der Dateiname kein Attribut, wird .PAS unterstellt. Der obige Aufruf compiliert den Quelltext und bindet das lauffähige Programm zu PROG.EXE. Das lauffähige Programm wird unter demselben Namen in dem Verzeichnis hinterlegt, in dem das Quellprogramm stand. TPC compiliert also nur, starten kann man das Programm dann von der DOS-Ebene, hier also mit

276

Option

Menüpunkt im Hauptmenü

Vorbelegung

/$A/SB+ /$D/$E/$F+ /$G+ /$I/$L/$Mxxx /$N+ /$0+ /$R/$s/$v/$x+ /B /Dxxx /Exxx /Fxxx /GS /GP /GD /Ixxx IL /M /Oxxx

Options/Compiler/Word align data Options/Compiler/Complete boolean evaluation Options!Compiler!Debug information Options! Compiler! emulation Options/Compiler/Force FAR calls Options/Compiler! 286 instruction Options /Compiler! HO checking Options/Compiler!local debug symbols Options!Memory sizes Options!Compiler!Numeric processing Options!Compiler!Overlays allowed Options!Compiler/Range checking Options!Compiler!Stack checking Options!Compiler!strict var-string Options!Compiler!Extended syntax Compile/Build Options!Compiler!Conditional defines Options/Directories/EXE & TPU directory Search/Find error Options/Linker/Map file Options!Linker!Map file Options!Linker!Map file Options!Directories!Include directories Options/Linker/Link buffer Compile/Make Options!Directories!Object directories Quiet (kein Menüpunkt) Options!Directories!Unit directories Options!Directories!Unit directories Options! Debugger! Standalone

off on off off on on off off

/Q /Txxx /Uxxx /V

8087/80287 on off off off on

segments publics detailed disk

on

Bild 20.1: Optionen der Kommandozeilen-Version B:PROG Werden keine Optionen angegeben, benutzt der Compiler die Vorbelegung von Bild 20.1. Für Version 7.0 siehe Kapitel 27.

20. Die Kommandozeilen-Version TPC

277

Die möglichen Optionen werden aufgelistet, wenn man TPC ohne Optionen und ohne Dateinamen einfach mit C> TPC startet (Bild 20.1). Die Optionen beeinflussen den Verlauf des Compilierens und Bindens. Sie bieten die Möglichkeiten, die bei der integrierten Entwicklungsumgebung über das Hauptmenü wählbar sind. Nehmen wir z.B. program nn; var k,i : 1..10; begin i := 2 ; k := 1 0 * i ; writeln(i:8,k:8); end.

Name Quelltext: NN.PAS

Compilieren mit C: TPC NN und Start mit C:NN führt wegen der Voreinstellung $R- zur Ausgabe

2

20.

Compilieren mit C: TPC /$R+ NN führt dann bei der Ausführung zu dem Laufzeitfehler 201 bei der Adresse 0000:002E. Mit der Option /F kann man sich die Fehlerstelle vorführen lassen, indem man das Programm mit C: TPC NN /FOOOO:002E erneut compiliert. Im vorliegenden Fall liegt der Fehler bei k

:=

10*i.

Enthält der Aufruf von TPC mehrere Optionen, können diese bei der Einleitung durch / aufgelistet werden als C: TPC /$R-/$I-/$V-/$F+ dateiname oder einfach als

278

20. Die Kommandozeilen-Version TPC

C: TPC /$R-,I-,V-,F+ dateiname Falls die Optionen mit - eingeleitet werden, sind sie immer durch Blank zu trennen: C:TPC -$R- -$I- -$V- -$F+ dateiname bzw. TPC -$R-,I-,V-,F+ dateiname TPC enthält einen eigenen Compiler und Binder. Es braucht aber die Bibliothek TURBO.TPL (und vielleicht TPC.CFG). Wenn man TPC startet, wird in dem aktuellen Verzeichnis nach diesen beiden Dateien gesucht. Wenn TURBO.TPL (und TPC.CFG) in dem Verzeichnis C:\TURBOP6 stehen, kann man mit der Option /T darauf verweisen: C: TPC -TC:\TURBOP6 PROG Es kann vorkommen, daß TPC immer wieder mit denselben Optionen laufen soll. Dann kann man es sich ersparen, diese jedesmal anzuführen. Man kann diese in einer Datei TPC.CFG hinterlegen und mit /T darauf verweisen. Beim Aufruf eines Programms kann man sog. Aufrufparameter angeben. Um die im Aufruf genannten Parameter im Programm verfügbar zu haben, gibt es die Standardfunktionen p a r a m c o u n t und p a r a m s t r von Bild 20.2. Aufrufparameter können sowohl bei IDE als auch TPC verwendet werden. Aufruf: Parameter: Wirkung:

paramcount Funktionswert: w o r d keine Der Funktionswert ist die Anzahl der in der Aufrufzeile stehenden Parameter.

Aufruf: Parameter: Wirkung:

paramstr(i) Funktionswert: s t r i n g i:word; Der Funktionswert ist der String des i - t e n Parameters in der Aufrufzeile. Der String ist leer, wenn i > p a r a m c o u n t ist.

Bild 20.2: Funktionen für Aufrufparameter

Beispiel

20.1:

Protokoll der Aufrufparameter. program a u f r u f p a r a m e t e r _ p r o t o k o l l i e r e n ;

uses c r t ; var i

:

integer;

279 begin writeln('Anzahl Aufrufparameter: ',paramcount); for i := 1 to paramcount do writeln(paramstr(i)); readln; end.



Heißt das Programm von Beispiel 20.1 PROT.PAS, dann bewirkt der Aufruf C>TPC prot beim Start mit C> prot dies und das die Ausgabe: Anzahl Aufrufparameter: 3 dies und das Übrigens kann man auch bei der integrierten Entwicklungsumgebung Aufrufparameter angeben. Dazu wird über Run/Parameters ein Fenster angeboten, in das man Aufrufparameter eintragen kann, die dem Programm über paramcount bzw. paramstr zur Verfügung stehen. In Beispiel 17.4 wurde eine Textdatei quelle in eine Zieldatei ziel kopiert, wobei die Namen von Quell- und Zieldatei vom Programm eingelesen wurden. Bei dem folgenden Beispiel werden die Namen der beiden Dateien im Aufruf angegeben. Beispiel

20.2:

program text_file_copieren; var quelle,ziel:text; qname,zname:string[20]; procedure copy(var q,z:text); var cschar; begin while not eof(q) do begin read(q,c); (* writeln(ord(c):4); ASCII-Nummer gelesenes Zeichen -> Bildschirm *)

280

20. Die Kommandozeilen-Version TPC

write(z,c); end; close(q); close(z); end; begin i f p a r a m c o u n t 2 t h e n w r i t e l n ( ' A u f r u f i s t : prog_name Q u e l l e e l s e begin assign(quelle,paramstr(l)); assign(ziel,paramstr(2)); reset(quelle); rewrite(ziel); copy(quelle,ziel); end; end.

Ziel')



Heißt dieses Programm KOPIE.PAS, sollte es mit O T P C KOPIE C> KOPIE quelle.t ziel.t aufgerufen werden, bzw. unter IDE bei Run!Parameters geben werden.

quelle.t ziel.t ange-

IDE ist eine so erstklassige Pascal-Umgebung, die man nicht ohne triftige Gründe verlassen wird. Insofern wird die Benutzung von TPC eher die Ausnahme sein. Ein Grund kann vorliegen, wenn das Programm größer als 640KB werden soll. Zu Turbo Pascal 6.0 Professional gehört TPCX.EXE, das im Protected Mode arbeitet und auf das Extended Memory zugreift. Falls man über mindestens 1 MB Extended Memory verfügt, kann man mit TPCX sehr große Programme erzeugen.

21. Turbo Pascal und MS-DOS Wenn man Turbo Pascal unter MS-DOS betreibt, erhebt sich die Frage, welche Dienstleistungen von MS-DOS man von einem Pascal-Programm aus beanspruchen kann. Dazu gibt es in der Pascal-Bibliothek TURBO.TPL, insbesondere in DOS.TPU und SYSTEM.TPU (s. Kap. 19.3), eine Fülle von Konstanten, Datentypen, Prozeduren und Funktionen. Von den vielen Möglichkeiten kann in diesem Kapitel nur ein Überblick über die wichtigsten Aspekte gegeben werden. Und es muß dazu eine ausreichende Kenntnis von MS-DOS vorausgesetzt werden. Ein Betriebssystem wie MS-DOS stellt vielerlei Dienstleistungen zur Verfügung, die durch Kommandos aufgerufen werden können. Es wäre denkbar, daß die Kommandos in Turbo Pascal als Standardprozeduren zur Verfügung stehen. Dies ist für eine Reihe von Kommandos tatsächlich der Fall. Bild 21.1 enthält einige davon. Andere sind schon an anderer Stelle aufgeführt worden. Beispiel

21.1:

program directories; var d : byte; name,dir : string[64]; begin getdir(0,name); writeln(name); write('Neues Verzeichnis: '); readln(dir); (*$I-*) mkdir(dir); (*$I+*) if ioresult 0 then begin write('Das Verzeichnis ', dir); writeln( ' gibt es schon.') end eise begin chdir(dir); getdir(0,name); writeln('Neues Verzeichnis: ',name); chdir('..'); end; rmdir(dir); getdir(0,name); writeln('Neues Verzeichnis: ',name); readln; end.



282

21. Turbo Pascal und MS-DOS

Aufruf: Parameter: Wirkung:

mkdir ( s ) Prozedur s:string; Es wird ein Unterverzeichnis namens s angelegt, s kann ein Laufwerksname oder ein Suchweg sein. Ein Unterverzeichnis s darf es noch nicht geben.

Aufruf: Parameter: Wirkung:

rmdir (s) Prozedur s:string; Das Unterverzeichnis namens s wird gelöscht, s kann ein Laufwerksname oder ein Suchweg sein. Wenn das Verzeichnis s nicht leer oder das aktuelle Verzeichnis ist, führt das zu einem Laufzeitfehler.

Aufruf: Parameter: Wirkung:

chdir(s) Prozedur s:string s gibt das neu zu setzende aktuelle Verzeichnis an. s kann auch den Namen eines Laufwerkes enthalten. Der Wechsel erfolgt relativ zum momentanen Verzeichnis, es sei denn, s beginnt m i t \ . Das Ergebnis kann bei $ 1 - mit i o r e s u l t überprüft werden.

Aufruf: Parameter: Wirkung:

getdir(lw,s) Prozedur lwrbyte; var s:string; Es wird das aktuelle Verzeichnis eines Laufwerkes lw ermittelt und im String s hinterlegt. Für l w gilt: 0 = aktuelles Laufwerk 1 = Laufwerk A, 2 = Laufwerk B, 3 = Laufwerk C, usw.

Bild 21.1: Einige DOS-Kommandos als Prozeduren

Bevor auf die Benutzung der MS-DOS-Funktionen eingegangen werden kann, seien die Register und ihre Bedeutung in Bild 21.2 zusammengefaßt. Die Register sind alle 16 Bit lang. Die Register *x unterscheiden die beiden Bytes: *h das linke Byte (high), *1 das rechte Byte (low). Zur Benutzung dieser Register gibt es in der Standard-Unit DOS.TPU den Datentyp r e g i s t e r s (Bild 21.3). Es handelt sich um einen Varianten Record, um wahlweise die 16-Bit-Register ax, bx, cx, dx als auch deren einzelne Bytes ah, al usw. ansprechen zu können. Da in dem Datentyp r e g i s t e r s die Register ss und sp nicht enthalten sind, kann man mit den folgenden Prozeduren i n t r und m s d o s auch keine Interrupts bzw. DOS-Funktionen aufrufen, die diese Register benutzen.

21. Turbo Pascal und MS-DOS

Bezeichnung ax bx cx dx

(ah,al) (bh,bl) (ch,cl) (dh,dl)

Inhalt Allgemeine Z w e c k e tt

M

M tt

»1 '

»r

Segmentregister: Datensegment Extrasegment Codesegment Stacksegment Spezialregister Basiszeiger (für Stackindex) Zielindex (Destination Index) Quellindex (Source Index) Stackpointer

ds es CS SS

bp di si sp Bild 21.2: Register des 80x86

type registers = record case integer of 0: (ax, bx, cx, dx, bp, si, di, es, flags : word); Is (al, ah, bl, bh, cl, ch, dl, dh : byte); end; Bild 21.3: Datentyp r e g i s t e r s in der Unit DOS

Interrupt

Wirkung

$5 $10

Bildschirmausdruck Video E/A Steuerung des Bildschirms für Text und Graphik Peripherieprüfung (installierte Geräte) Speichergröße Disketten E / A zum direkten Lesen und Schreiben auf Diskette Tastatur-Eingabe Drucker Warmstart Funktionsaufrufe Datum und Uhrzeit

$11 $12 $13 $16 $17 $19 $21 $1A

Bild 21.4: Interrupt-Routinen

283

21. Turbo Pascal und MS-DOS

284

Die Funktionen von MS-DOS werden über Interrupts aufgerufen. Die wichtigsten sind in Bild 21.4 aufgezählt. Beim Aufruf muß die Interrupt-Nummer im Register ah stehen. Die meisten von diesen Interrupts haben viele Varianten, je nachdem, welche Parameter in den Registern al, bx, cx und dx stehen. Diese Interrupts können über die Prozedur i n t r von Bild 21.5 benutzt werden.

i n t r ( i n t n r , regs) Prozedur dos i n t n r : b y t e ; var r e g s s r e g i s t e r s ; Es wird der Interrupt i n t n r ausgeführt. Die Variable r e g s muß die von dem Interrupt i n t n r verlangten Werte enthalten. Nach dem Aufruf stehen die von dem Interrupt erzeugten Ergebnisse wieder in r e g s .

Aufruf: Parameter: Wirkung:

Bild 21.5: Die Prozedur

Beispiel

intr

21.2:

program

interrupt_beispiel;

uses dos;

var s i z e

:

integer;

procedure memory_size(var var result:registers;

size:integer);

begin ( * Der I n t e r r u p t $12 ü b e r p r ü f t den A r b e i t s s p e i c h e r und h i n t e r l e g t d i e Größe des Speichers im R e g i s t e r a x . * ) intr($12,result); size:=result.ax;

end; begin memory_size(size); w r i t e ( ' I h r Rechner h a t ' , s i z e : 4 ) ; w r i t e l n ( ' kByte H a u p t s p e i c h e r ' ) ; readln;

end.



Von besonderer Qualität ist der Interrupt $21, über den viele DOS-Funktionen (die sogenannten High level functions) aufgerufen werden können (Bild 21.6). Für die Einzelheiten muß der Leser auf das Technische Handbuch verwiesen werden. Um diese Funktionen über den Interrupt $21 aufrufen zu können, gibt es die Standardprozedur msdos von Bild 21.7

21. Turbo Pascal und MS-DOS

Nummer

Wirkung

$5 $B $19 $23 $2A $2C $30 $33 $36 $39 $3A $3D $3E $41 $47 $4B $57

Druckerausgabe Tastatureingabe Aktuelles Laufwerk angeben Dateigröße Datum holen Uhrzeit holen DOS-Versionsnummer holen Prüfen auf Break Freier Diskettenspeicherplatz Anlagen eines Unterverzeichnisses (MKDIR) Löschen eines Unterverzeichnisses (RMDIR) Datei eröffnen Datei schließen Datei löschen Aktuelles Inhaltsverzeichnis holen Laden oder Ausführen eines Programmes Datum und Uhrzeit einer Datei holen/setzen

285

Bild 21.6: Funktionsaufrufe mit DOS-Interrupt $21 Aufruf: Parameter: Wirkung:

msdos (regs) Prozedur dos var regs : registers; Es wird die DOS-Funktion aufgerufen, deren Nummer im Register ax (genauer ah) steht. In der Variablen regs müssen die Parameter des Aufrufs stehen. Nach dem Aufruf stehen die Ergebnisse wieder in regs. Anmerkung: Der Datentyp registers ist in der Unit DOS definiert (s. Bild 21.3). Der Aufruf von msdos (regs) ist identisch mit dem Aufruf intr( $21, regs).

Bild 21.7: Die Prozedur msdos

Beispiel 21.3 : program freier_speicherplatz; uses dos; var lw:byte; function speicher(lw:byte):longint; var regs:registers; begin with regs do

286

21. Turbo Pascal und MS-DOS

begin ax := $3600; (* F u n k t i o n s a u f r u f : f r e i e r S p e i c h e r p l a t z *) dx := lw? (* d l i s t Nummer d e s L a u f w e r k e s *) msdos(regs); (* J e t z t s t e h t i n ax = Anzahl D i s k e t t e n s e i t e n bzw. $FFFF wenn lw ungültiges Laufwerk, bx = Anzahl f r e i e r S e k t o r e n , cx = Anzahl B y t e s p r o S e k t o r . * ) i f ax = $FFFF then S p e i c h e r := 0 eise S p e i c h e r : = l o n g i n t ( a x ) * b x * c x ; (* Der F a k t o r l o n g i n t ( a x ) v e r a n l a ß t d i e A u s f ü h r u n g d e r M u l t i p l i k a t i o n im l o n g i n t - F o r m a t ; s. Kapitel 9.5.*) end; end; (* Speicher *) begin (********** Anweisungen **********) w r i t e l n ( ' L a u f w e r k ( 0 = a k t u e l l e s , 1=A, 2=B, u s w . ) : ' ) ; readln(lw); w r i t e ( ' F r e i e r S p e i c h e r p l a t z auf Laufwerk ' , l w ) ; writeln(Speicher(lw):8); readln; end.



Häufig kommt es vor, daß man auf die im PC gehaltenen Angaben über Datum und Uhrzeit zugreifen möchte. Das kann man einmal über die DOS-Funktionen $2A und $2C mit Hilfe von MS-DOS machen. Es gibt dafür aber auch die beiden Standardprozeduren g e t d a t e und g e t t i m e von Bild 21.8. Aufruf: Parameter: Wirkung:

g e t d a t e ( j ,m, t , w t ) Prozedur dos var j , m , t , w t : w o r d ; Die Prozedur liefert das von MS-DOS benutzte Datum in Jahr j (1980..2099), Monat m (1..12), Tag t (1..31) und den Wochentag w t (0..6, 0 = Sonntag)

Aufruf: Parameter: Wirkung:

g e t t i m e ( h , m , s , slOO ) Prozedur dos var h , m , s , s l O O : w o r d ; Die Pozedur liefert die momentane Uhrzeit in Stunden h (0..23), Minuten m (0..59), Sekunden s (0..59) und hundertstel Sekunden slOO (0..99).

Bild 21.8: Datum und Uhrzeit holen

21. Turbo Pascal und MS-DOS Beispiel

21.4:

program datum_holen; uses dos; type datum_typ = record tag, monat, jahr, Wochentag : word end; var datum : datum_typ; begin with datum do getdate(jahr, monat, tag, Wochentag); with datum do begin write('Datum: '); case Wochentag of 0: write('Sonntag '); 1: write('Montag '); 2: write('Dienstag '); 3: write('Mittwoch '); 4: write('Donnerstag '); 5: write('Freitag '); 6: write('Samstag '); end; writeln(tag:2,'.', monat:3,'.', jahr:5); readln; end; end. Beispiel

21.5:

program zeit_holen; uses dos, crt; type zeit_typ = record stunde, minute, Sekunde, seklOO : word end; var zeit, zeitl, zeit2 : zeit_typ; i, anzahl : longint; procedure uhrzeit(var z : zeit_typ); begin with z do gettime(stunde, minute, Sekunde, seklOO); end; procedure uhrzeit_ausgeben(z : zeit_typ);

288

21. Turbo Pascal und MS-DOS

begin with z do begin write('Uhrzeit: '); write(stunde:2,'h :', minute:2,'m :'); writeln( Sekunde:2,'.',sekl00,'s'); end; end; function zeitdifferenz(var z2, zl : zeit_typ) : real; var sek2, sekl, msek2, msekl : real; begin sek2 := z2.stunde*3600.0 + 60*z2.minute + z2.Sekunde + z2.sekl00/100; sekl := zl.stunde*3600.0 + 60*zl.minute + zl.Sekunde + zl.seklOO/lOO; zeitdifferenz := sek2 - sekl; end; begin clrscr; writeln('Ermittlung der Rechenzeit einer Schleife.'); write('Schleifenzahl: '); readln(anzahl); uhrzeit(zeit1); for i := 1 to anzahl do; uhrzeit(zeit2); uhrzeit_ausgeben(zeitl); uhrzeit_ausgeben(zeit2); write('Rechenzeit: '); writeln(zeitdifferenz(zeit2, zeitl):6:3,' sec'); readln; end.



Den Bildschirm kann man auf zwei Arten beeinflussen. Einmal durch EscapeSequenzen, zum andern durch direktes Schreiben in den Bildschirmspeicher. Mit dem ASCII-Zeichen Nr. 27 (escape) kann man Peripheriegeräte steuern. Für einen Drucker findet man die Escape-Sequenzen im Drucker-Handbuch (s.a. Beispiel 17.7). Für den Bildschirm gibt es die sogenannten ANSI-Sequenzen, die Sie im MS-DOS-Handbuch finden. Sie sind von der Form esc [ parameter und benutzen den in ANSI.SYS definierten Treiber, d. h. in der Datei CONFIG.SYS muß es den Eintrag

21. Turbo Pascal und MS-DOS

289

DEVICE = ANSI.SYS geben. Das folgende Beispiel 21.6 macht davon Gebrauch. Beispiel

21.6:

Steuerung des Bildschirms mit ANSI-Treiber. program a n s i _ t r e i b e r ; const b l i n k e n _ e i n = ~ [ ' [ 5 m ' clrscr = *['[2J' invers_ein = A['[7m' unter_ein = *['[4m' a t t r i b _ a u s = A ['[Om' esc = #27; procedure g o t o x y ( s p a l t e , z e i l e : i n t e g e r ) ; begin write(chr(27),'[zeile,';',spalte,'H'); end; begin write(clrscr); gotoxy(5,10); writeln(blinken_ein,'Hallo',attrib_aus); gotoxy(10, 12); writeln(invers_ein,'liebe',attrib_aus); gotoxy(15,14); writeln(unter_ein, 'Freunde',attrib_aus); readln; end.



Ihr PC hat einen Bildschirmspeicher, in den bei u s e s c r t mit w r i t e ( O u t p u t , . . . ) geschrieben wird, und dessen Inhalt mit ca 50 Hz auf dem Bildschirm ausgegeben wird. Die Anfangsadresse des Bildschirmspeichers hängt von der Hardwarekonfiguration ab, insbesondere der Graphikkarte und dem Monitor. Häufige Anfangsadressen sind z.B. $B000:0000 oder $B800:0000. Steht in einem Byte ein Zeichen, so steht im folgenden Byte das sogenannte Attribut (wie invers, blinken usw.). Um direkt an eine bestimmte Stelle des Speichers zu schreiben, gibt es die StandardArray-Variable mem vom Typ a r r a y of b y t e , d. h. mit mem[$0050:$0065]

:=17;

kann man in die hinter mem angegebene Adresse die Zahl 17 schreiben.

290

21. Turbo Pascal und MS-DOS

Beispiel

21.7:

Mit dem folgenden Programm kann man eine Zeichenkette direkt an eine gewünschte Stelle des Bildschirms schreiben und dabei ein Attribut eingeben. Geeignete Attributnummern ergeben sich aus folgender Aufstellung. Bildschirmattribute (monochrom): Bit Nr.

7

6 5 4 0 0 0 0 0 0 1 1 1 0 0 0

Immer gilt: Bit 7 Bit 3

3

2 1 1 1 0 0 0 0 0 0

0 1 0 0 1

1 = Blinken ein 1 = Hell

Bedeutung Normal (weiß/schwarz) unsichtbar (schwarz/schwarz) invers (schwarz/weiß) unterstrichen 0 = Blinken aus 0 = halbhell

Bildschirmattribute (farbig): Bit Nr. Bedeutung

7 6 5 4 F R G B

— —

3 2 1 0 I R G B ^ — ^ — V o r d e r g r u n d (Rot,Grün,Blau) Intensität l=hell) Hintergrund (Rot,Grün,Blau) Blinken

Andere Farben ergeben sich durch Kombination der RGB-Bit entsprechend Bild 8.9. program bildspeicher; uses Crt; type wort = string[80]; var st : wort; a, sp, z, i: integer; procedure writescreen(z,sp,attr:integer;st:wort); const line = 160; (* 1 Bildschirmzeile besteht aus 80 Zeichen und 80 Attributbytes, also 160 Byte *) begin clrscr; for i := 1 to length(st) do begin (* Achtung die Anfangsadresse des Bildschirmspeichers kann bei Ihrem PC anders sein *)

21. Turbo Pascal und MS-DOS

291

mem[$B000:2*i+z*line+sp] := ord(st[i]); mem[$B000:2*i+l+z*line+sp] := attr; end; gotoxy(l,24); end; begin (********** Anweisungen **************) repeat writeln('worts Ende = stop'); readln(st); if st 'stop'then begin writeln('Zeile:'); readln(z); writeln('Spalte:'); readln(sp); writeln('Attribut:'); readln(a); (* Das Attribut wird zweckmäßig hexadezimal angegeben.*) writescreen(z,sp,a,st); end; until st = 'stop'; end.



Zum Schreiben in den Bildschirmspeicher könnte man auch den Interrupt $10 von Bild 21.4 benutzen (vgl. Beispiel 22.10).

22. Der integrierte Assembler Um maschinennah programmieren zu können, verfügt Turbo Pascal 6.0 über einen integrierten Assembler, der mit den Schlüsselwörtern asm bzw. a s s e m b l e r aktiviert wird.

22.1 Die asm-Anweisung Die a s m - a n w e i s u n g ist nach (10-4) eine strukturierte Anweisung und hat die Form (22-1)

asm-anweisung asm

asm-statement

end

trenner

(22-2)

trenner

zeilenwechsel



Pascal-Kommentar

Ein a s m - s t a t e m e n t ist ein Assemblerbefehl, wie man ihn im Turbo Assembler schreiben kann. Für Details sei auf die einschlägige Assembler-Literatur verwiesen (z.B. [7]). Einige Unterschiede sind zu beachten: - Anders als im Turbo Assembler können mehrere Assemblerbefehle in einer Zeile stehen, wenn man sie jeweils durch ein Semikolon oder einen PascalK o m m e n t a r trennt. - Ein Semikolon leitet also keinen Kommentar ein. K o m m e n t a r e müssen wie in Pascal mit { . . . > oder ( * . . . * ) geschrieben werden. - Mit Assembler-Direktiven können im Turbo Assembler Variable, Makros und Unterprogramme deklariert, Konstante definiert und die Übersetzung

294

22. Der integrierte Assembler

des Programmes gesteuert werden. All diese Möglichkeiten stehen bereits in Turbo Pascal 6.0 zur Verfügung, so daß der integrierte Assembler nur die Direktiven db, dw und d d kennt. - Der integrierte Assembler unterstützt alle Befehle der Prozessoren 8086/8087 bis zum 80286/80287. Beispiel

22.1:

Die Division einer ganzen Zahl durch 16 erfolgt am schnellsten durch Rechtsschieben um 4. Bei negativen Zahlen liefert der Pascal-Operator s h r ein falsches Ergebnis, da von links her Nullen nachgezogen werden und damit das Ergebnis positiv wird. Im folgenden Programm wird der arithmetische Schiebebefehl s a r verwendet, der nur im Assembler zur Verfügung steht. program a r i t h S c h i e b e n ; var z a h l , e r g : i n t e g e r ; begin w r i t e ( ' G i b Zahl e i n : ' ) ; readln(zahl); asm mov ax,zahl mov cl,4 ; sar ax,cl mov erg,ax end; w r i t e l n ( z a h l , ' d i v 16 = ' , e r g ) ; readln; end.



Im Beispiel sieht man, daß man in einer a s m - a n w e i s u n g auf Pascal-Variable einfach über ihren Namen zugreifen kann. Der Assembler-Befehl s a r rundet immer ab; so liefert obiges Programm nach der Eingabe von -17 den Wert -2 als Ergebnis. Das folgende Beispiel behebt diesen Mangel. Beispiel

22.2:

program a r i t h S c h i e b e n ; var z a h l , e r g : i n t e g e r ; begin w r i t e ( ' G i b Zahl e i n : readln(zahl); asm mov ax,zahl

');

22.1 Die asm-Anweisung

295

cmp ax,0 jge @negativ { falls zahl negativ, } add ax,15 { Korrektur für Rundung } @negativ: mov cl,4 sar ax,cl mov erg,ax end; writeln(zahl,' div 16 = ',erg); readln; end. Ist der Inhalt von zahl positiv, so wird die Addition übersprungen. Als Marke wurde hier eine lokale Marke verwendet, die mit @ beginnt und nur innerhalb von asm . . end bekannt ist. Lokale Marken müssen nicht deklariert werden. Man hätte auch eine Marke ohne @ verwenden können, die man dann allerdings als Pascal-Marke hätte vereinbaren müssen. Eine solche Marke gilt dann in dem gesamten Block, in dem sie vereinbart ist (vgl. 10.5), also auch außerhalb der asm-anweisung. • Nach der Direktive {$G+> kann man in einer asm-anweisung auch Befehle des Prozessors 80286 verwenden (vgl. Anhang D). Beispiel

22.3:

Will man einen Wert um mehr als eine Stelle verschieben, muß man beim 8086 diesen Wert in das Register c l laden und dieses Register im Schiebebefehl verwenden. Beim 80286 kann man die Zahl, um die geschoben werden soll, direkt beim Schiebebefehl angeben. program arithSchieben; var zahl,erg:integer; label weiter; {$G+} begin write('Gib Zahl ein readln(zahl); asm ax,zahl mov ax, 0 cmp jge weiter { add ax, 15 { weiter: ax, 4 sar { mov erg,ax

: ');

falls zahl negativ, Korrektur für Rundung

>

erst ab 80186 möglich

}

}

296

22. Der integrierte Assembler

end; writeln(zahl,' readln; end.

d i v 16 = ',erg); •

In einer a s m - a n w e i s u n g gilt sowohl die Syntax von Turbo Pascal wie auch die des Turbo Assemblers, sofern hierbei keine Ungereimtheiten auftreten. Ein Beispiel für eine solche Ungereimtheit wurde bereits mit dem Semikolon erwähnt. Weitere Probleme ergeben sich, wenn man in einer a s m - a n w e i s u n g auf eine Pascal-Variable zugreifen will, deren Name gleichlautend mit einem Assembler-Schlüsselwort ist. Hier hilft der Operator &. Beispiel

22.4:

Im folgenden Programm wird der Buchstabe ' A ' , der in der Pascal-Variablen c h gespeichert ist, in einen Kleinbuchstaben gewandelt. In der a s m - a n w e i s u n g bezeichnet c h das 8 Bit-Register; auf die Pascal-Variable c h greift man über &ch zu. program b e i s p i e l ; var ch:char; begin ch:='A'; asm mov ch,&ch add ch,20h mov &ch,ch end; writeln(ch); end.

{ Kleinbuchstabe >



Äußerste Vorsicht ist auch bei Ausdrücken geboten. Einige Operatoren haben im Assembler eine andere Stufe als in Pascal. Um keine Mißverständnisse aufkommen zu lassen, sollte man im Zweifelsfall immer Klammern benutzen!

22.2 Prozeduren und Funktionen im Assembler Besonders interessant wird die Verwendung des integrierten Assemblers im Zusammenhang mit Prozeduren und Funktionen, für die im folgenden der zusammenfassende Begriff Unterprogramm verwendet wird. Man kann in einer Unterprogramm-Vereinbarung a s m - a n w e i s u n g e n verwenden. Auf die formalen Parameter kann man über deren Namen zugreifen. Es besteht aber auch die Möglichkeit, die vom Pascal-Compiler verwendete Verwaltung der Para-

22.2 Prozeduren und Funktionen im Assembler

297

meter selbst in die Hand zu nehmen, um kürzeren und schnelleren Code zu erhalten. Um diese Möglichkeiten optimal ausnutzen zu können, wird im folgenden zunächst besprochen, wie der Pascal-Compiler mit Unterprogrammen und deren Parametern umgeht. Anschließend werden die Möglichkeiten diskutiert, die der integrierte Assembler zur Optimierung bietet. Vor dem Aufruf eines Unterprogramms werden die aktuellen Parameter auf den Stack gespeichert, und zwar in der aufgeschriebenen Reihenfolge. Je nach Datentyp werden hierfür ein bis drei Worte pro Parameter benötigt. Bei Datentypen, für die dies nicht ausreicht, wird die Adresse des aktuellen Parameters, die immer 32 Bit lang ist, auf den Stack gespeichert. Bild 22.1 zeigt, wie sich die Datentypen von Turbo Pascal und Turbo Assembler entsprechen. Der Aufruf über den Assembler-Befehl c a l l hinterlegt die Rückkehradresse ebenfalls auf dem Stack. Turbo Pascal

Turbo Assembler

Parameter

Rückgabe-Wert in Register

char, Boolean, byte, shortint

Byte

1 Wort

al

integer, word

Word

1 Wort

ax

longint, comp

Dword

2 Worte

dx: ax

3 Worte

dx:bx:ax

2 Worte

dx: ax

real Dword

Adresse

Bild 22.1: Korrespondierende Datentypen von Pascal und Assembler Beispiel

22.5:

Die Prozedur scrolle legt ein Textfenster auf dem Bildschirm fest. Die ersten vier Parameter geben dabei die Koordinaten der linken oberen und rechten unteren Ecke des Fensters an; der Parameter anz legt fest, um wieviele Zeilen in dem Fenster nach oben geschoben wird und der Parameter färbe legt die Farbe der geschobenen Zeilen fest (vgl. [7], 10.6). program EingFenster; uses crt; procedura begin asm mov mov mov

scrolle(Ii,ob,re,unt,anz,färbe:byte);

al,anz cl,li ch,ob

{ Anzahl der Zeilen { Spalte und { Zeile links oben

} > >

298

22. Der integrierte Assembler

mov mov mov mov int end; end;

dl,re dh,unt bh,färbe ah,6 10h

{ { { {

S p a l t e und }} Zeile rechts unten } A t t r i b u t f ü r gelöschte Zeichen } s c r o l l e nach oben }

procedure f e n s t e r _ t e x t ( I i , o b , r e , u n t , a n z : b y t e ; t : s t r i n g ) ; begin scrolle(li,ob,re,unt,anz,$17); gotoxy(li+1,unt+1); write(t); end; var z e i l e : s t r i n g ; const e l i = l ; e o b = 2 0 ; { E i n g a b e P o s i t i o n } const a l i = 3 ; a o b = 3 ; a r e = 6 0 ; a u n = 1 5 ; { A u s g a b e F e n s t e r begin clrscr; zeile:=''; w r i t e l n ( ' E I N G A B E und AUSGABE Über FENSTER.'); writeln('DEMO von F e n s t e r T e x t ' ) ; scrolle(ali,aob,are,aun,aun-aob+1,$37); repeat gotoxy(eli+10,eob); write(' ':length(zeile)); gotoxy (el i , eob); write('Eingabe : '); readln(zeile); textbackground(blue); fenster_text(ali,aob,are,aun,1,zeile); textbackground(black); until l e n g t h ( z e i l e ) = 0 ; textcolor(white);textbackground(black); clrscr; end.

}



Beim Aufruf scrolle(ali,aob,are,aun,anz,färbe) werden die Parameter und die Rückkehradresse folgendermaßen auf dem Stack abgelegt:

22.2 Prozeduren und Funktionen im Assembler

1 1 1i 1 1i 1 1i 1 i 1 i 1 i

299

ali aob are aun anz färbe

Rückkehradresse

Bild 22.2: Parameterablage auf dem Stack Um im Rumpf des Unterprogramms bequem auf die Parameter zugreifen zu können, generiert der Turbo Pascal-Compiler am A n f a n g des Unterprogramms eine Code-Sequenz, den sogenannten Standard-Vorspann, der das Zeiger-Register bp geeignet initialisiert. Falls auch noch lokale Variablen verwendet werden, wird hierfür auf dem Stack Speicherplatz reserviert. Vor Verlassen des Unterprogramms m u ß der auf dem Stack belegte Speicherplatz wieder freigegeben und das Register bp wieder auf seinen alten Wert gesetzt werden.

{ Standard-Vorspann einer Prozedur oder Funktion } push bp ;{ alten Wert von bp sichern mov bp,sp ;{ neue Basisadresse einstellen stob sp, lokale ;{ falls lokale Variable da sind

} } }

{ Standard-Nachspann einer Prozedur oder Funktion } mov sp,bp ;{ falls lokale Varible da sind } pop bp ;{ alten Wert von bp restaurieren } ret Params ;{ Rückkehr } { 'lokale' gibt den für die lokalen Größen benötigten Speicherplatz in Byte an 'Params' gibt den für die Parameter benötigten Speicherplatz in Byte an

} Bild 22.3: Standard-Vor-und-Nachspann

300

22. Der integrierte Assembler

Hierfür wird eine Code-Sequenz, der sogenannte Standard-Nachspann, generiert. Bild 22.3 zeigt den genauen Aufbau des Standard-Vorspanns und -Nachspanns. Hinweis: Der Leser kann sich den vom Compiler generierten Code mit dem externen Turbo Debugger anschauen. Dazu muß das Programm nach Einstellung des Menü-Punktes 'OptionsIDebuggerlStand alone' in eine EXE-Datei übersetzt werden (Menü: Compile!Destination Disk). In Beispiel 11.3 wurden in der Prozedur tausch einfach die Namen der Parameter x und y verwendet. Da diese Parameter als Referenz-Parameter spezifiziert sind, weiß der Compiler, wie er sie im Unterprogramm-Rumpf zu behandeln hat - nämlich als 32 Bit-Adresse. Im Assembler muß man selbst auf diese Besonderheit achten, wie das folgende Beispiel zeigt. Beispiel

22.6:

program vertauschen; procedura tausch(var x,y:word); begin asm les bx,x { lädt es:bx mit der Addresse von x mov ax,es:[bx] les bp,y { lädt es:bp mit der Addresse von y xchg ax,es:[bp] mov es:[bx],ax end end;

> }

var i, k : word; begin { vgl. Bsp. 11.3 } write('Gib zwei Zahlen:'); readln(i,k); writeln(i:6,k:6); tausch(i,k); writeln(i:6,k:6); end.



Für die Behandlung des Rückgabewertes einer Funktion steht das Symbol @Result zur Verfügung.

22.2 Prozeduren und Funktionen im Assembler Beispiel

301

22.7:

Die Funktion m u l t l 6 multipliziert eine Zahl mit 16, indem sie den Operanden um 4 Stellen nach links schiebt. program multiplikation; function mult16(x:integer):integer; begin asm mov ax,x mov cl,4 shl ax,cl mov @Result,ax end; end; var i:integer; begin write('Gib Zahl ein : ');readln(i); writeln('16-faches ist : ',multl6(i)); readln; end. Beispiel



22.8:

Das folgende Programm liest solange Zeichen von der Tastatur ein, bis ein # gedrückt wird. program BuchstabenLesen; function readkeyschar; begin asm mov ah,07h { Tastatur-Eingabe ohne Echo ohne Ctrl-C-Check } int 21h mov @Result,al end; end; var c:char; begin repeat writeln('Gib " # " until readkey='#'; end.

ein :'); •

302

22. Der integrierte Assembler

In den beiden obigen Beispielen war @Result, abhängig vom Rückgabewert, ein Wort bzw. ein Byte. Ist das Ergebnis einer Funktion vom Typ s t r i n g , ist @ R e s u l t sogar ein Doppel wort. Beispiel

22.9:

In Turbo Pascal enthalten Strings im nullten Byte die aktuelle Länge. In der Programmiersprache C werden die Zeichenreihen mit dem Bytewert 0 abgeschlossen. Die folgende Funktion PasToC übersetzt einen Pascal-String in einen C-String. program PasToCString; function PasToC(PasStr s string):string; begin asm cid { vorwärts } si,PasStr lea les di,@Result segss lodsb { Länge > xor cx,cx cl,al mov { nach cx > rep segss movsb { kopieren > xor al, al { NULL-Byte } stosb { anhängen } end; end; var PStr,CStr:String; i,l:integer; begin writeln; write('Lies Pascal-String ein : '); readln(Pstr); 1:=Length(PStr); CStr:=PasToC(PStr); for i:=0 to 1 do writeln(i,' : ' ,ord(CStr[i]),'["',CStr[i],"'] , end.

');

Da der Parameter PasStr ein Wertparameter ist, wird der Wert des aktuellen Parameters auf den Stack kopiert. Deshalb muß PasStr relativ zum StackSegment adressiert werden. Dies erledigt das Präfix segss. •

22.2 Prozeduren und Funktionen im Assembler

303

Neben s e g s s kennt der integrierte Assembler auch für die restlichen Segmentregister die Präfixe s e g d s , s e g c s und s e g e s . Ist der Rumpf eines Unterprogramms vollständig im Assembler geschrieben, kann man auch die folgende Form der Deklaration verwenden (vgl. 11-3):

Dies ist nicht nur eine kürzere Schreibweise, vielmehr wird der Turbo PascalCompiler dazu veranlaßt, bei der Codegenerierung für das Unterprogramm folgende Optimierung durchzuführen: 0.1: .Für Wertparameter vom Typ String - und allen anderen Typen, die nicht in 1, 2 oder 4 Byte darstellbar sind - wird keine Kopie auf dem Stack abgelegt. 0.2: Der Rückgabewert einer Funktion muß in die entsprechenden Register geladen werden (vgl. Bild 22.1). Das Symbol @ R e s u l t steht nicht mehr zur Verfügung, außer das Ergebnis ist vom Typ s t r i n g . 0.3: Für parameterlose Unterprogramme ohne lokale Variablen wird kein Standard-Vorspann und -Nachspann generiert. Beispiel 22.10: Das folgende Programm verändert die Form des Cursors. Die Video-Funktion Interrupt 10h erwartet dazu im Register a h die Nummer 1 (Cursor-Form setzen), in Register c h die Startzeile und in Register c l die Endzeile des Cursors. Die Zeilennummern können dabei zwischen 0 (ganz oben) und 13 (ganz unten) variieren. program CursorForm; procedure c u r s o r ( o , u : b y t e ) ; assembler; asm mov a h , 1 mov c h , o { Startzeile mov c l , u { Endzeile i n t 10h end; begin

>

}

304

22. Der integrierte Assembler cursor(0,13);write('Großer Cursor');readln; cursor(0,0); write('Cursor oben'); readln; cursor(15,0);write('kein Cursor'); readln; cursor(13,13);write('Cursor unten');readln;

end. Ist der Rumpf eines Unterprogramms sehr kurz, benötigt allein der Aufruf mehr Zeit und Speicherplatz als das eigentliche Unterprogramm. Für solche Zwecke kennen die meisten Assembler sog. Makros. Beim Aufruf eines Makros wird beim Compilieren einfach der Rumpf an die Aufrufstelle kopiert. Dieser Mechanismus ist auch in Turbo Pascal 6.0 in Form der i n l i n e - a n w e i s u n g vorhanden, bei der man aber leider den Maschinen-Code noch in numerischer Form eingeben muß. Eine i n l i n e - a n w e i s u n g tritt nach (11-3) in einem Prozedurblock auf und ist gemäß folgender Syntax aufgebaut: inline-anweisung

(22-5)

inline-element konstante

variablenname

22.2 Prozeduren und Funktionen im Assembler

305

Der Wert der angegebenen Konstante legt fest, wie groß der erzeugte Code ist: Ein Wert zwischen 0 und 255 wird in einem Byte, ein größerer Wert in einem Wort abgelegt. Diese automatische Größenfestlegung kann man mit den Operatoren < und > ausschalten: Der Operator < zeigt an, daß nur ein Byte verwendet werden soll, das den niederwertigen Teil des Operanden aufnimmt. Umgekehrt erzeugt > immer ein Wort; der höherwertige Teil erhält nötigenfalls den Wert 0. Variablennamen bezeichnen immer eine Offset-Adresse. Ein kurzes Beispiel, bei dem der Assembler-Code als Kommentar angegeben ist, soll diese Makro-Technik veranschaulichen. Beispiel

22.11:

Im folgenden Beispiel wird die Funktion r e a d k e y aus Beispiel 22.8 als i n l i n e - F u n k t i o n umgeschrieben. program BuchstabenLesen; function readkey:char; inline ($B4/$07/ { mov ah,07h > $CD/$21 { int 21h > { der Funktionswert steht in al );

}

var cschar; begin repeat writeln('Gib " # " ein :'); until readkey='#'; end.



Ist ein Unterprogramm mit einer i n l i n e - a n w e i s u n g vereinbart, wird an jede Aufrufstelle vom Compiler einfach die i n l i n e - a n w e i s u n g hinkopiert. Daraus ergibt sich eigentlich von selbst, daß die i n l i n e - a n w e i s u n g keinen ret-Befehl enthalten darf. Parameter sind allerdings erlaubt; der Compiler speichert die aktuellen Parameter wie bei einem normalen Unterprogramm-Aufruf auf den Keller. Innerhalb der Routine kann man sie wieder vom Keller holen und damit arbeiten. Die Verwendung der Parameter-Namen in der i n l i n e - a n w e i s u n g ist nicht möglich.

306

22. Der integrierte Assembler

Beispiel

22.12:

Die Prozedur C u r s o r aus Beispiel 22.10 wird als inline-Prozedur umgeschrieben. Beim Aufruf werden die Parameter auf den Keller gepusht, in der Prozedur werden sie mit zwei pop-Befehlen wieder heruntergeholt und in c h und cl geladen. program CursorForm; procedure Cursor(o,u:byte); inline( $B4 / $01 / { mov ah, 1 $59 / { pop cx ; u $5B / { pop bx ; o $8A / $EB / { mov ch,bl; $CD / $10 { int 10h }

}

geholt > geholt > o nach ch

begin Cursor(0,13);write('Großer Cursor');readln; Cursor(0,0); write('Cursor oben'); readln; Cursor(15,0);write('kein Cursor'); readln; Cursor(13,13);write('Cursor unten');readln;

23. Grafik Turbo Pascal enthält in der Standard-Unit GRAPH.TPU eine sehr leistungsfähige Sammlung von Prozeduren, Datentypen und Konstanten zur Erzeugung von Grafiken. Da sich diese gegenüber Version 3.0 sehr geändert haben, gibt es in der Bibliothek TURBO.TPL die Unit GRAPH3, damit Grafikprogramme mit Version 3.0 (insbesondere die Turtle Grafik) möglichst ohne Änderung auch unter 6.0 lauffähig sind. Um die in GRAPH.TPU steckenden Möglichkeiten zu nutzen, muß Ihr PC natürlich grafikfähig sein, d. h. über einen Video-Adapter verfügen. Zur Unit GRAPH gehören eine Reihe von Treiber-Programmen für die gängigsten Video-Adapter: CGA, MCGA, EGA, VGA, Hercules, AT&T, 3270 PC. arc circle closegraph ellipse floodfill getbkcolor getdrivername getgraphmode getmaxcolor getmaxy getpalette gettextsettings gety graphresult installuserdriver linerel moveto pieslice rectangle restorecrtmode setallpalette setcolor setgraphbufsize setpalette settextstyle setvisualpage textwidth

bar cleardevice detectgraph fillellipse getarccoords getcolor getfillpattern getimage getmaxmode getmodename getpalettesize getviewsettings graphdefaults imagesize installuserfont lineto outtext putimage registerbgidriver sector setaspectratio setfillpattern setgraphmode setrgbpalette setusercharsize setwritemode

bar 3d clearviewport drawpoly fillpoly getaspectratio getdefaultpalette getfillsettings getlinesettings getmaxx getmoderange getpixel getx grapherrormsg initgraph line moverei outtextxy putpixel registerbgifont setactivepage setbkcolor setfillstyle setlinestyle settextJustify setviewport textheight

Tabelle 23.1: In GRAPH.TPU definierte Funktionen und Prozeduren

308

23. Grafik

Diese Grafiktreiber sind in Dateien mit dem Attribut .BGI (= Borland Graphic Interface). Für Vektor-Zeichensätze gibt es Definitionsdateien .CHR. In der Unit GRAPH gibt es Dutzende von Grafikroutinen (s. Tabelle 23.1). Die wichtigsten davon sind bei den folgenden Beispielen erklärt. Ansonsten wird auf den komfortablen Help-Mechanismus verwiesen. Auf der Lieferdiskette gibt es ein Demonstrationsprogramm BGIDEMO.PAS, dessen Studium sehr nützlich ist. Die Tabelle 23.2 zeigt die in GRAPH.TPU definierten Konstanten, Tabelle 23.3 die Datentypen und Variablen Ergebnis von graphresult: grok = grnoinitgraph grnodetected grfilenotfound grinvaliddriver grnoloadmem grnoscanmem grnofloodmem grfontnotfound grnofontmem grinvalidmod grerror grioerror grinvalidfont grinvalidfontnum bar3d-Konstanten: topon = t r u e ;

0; { f e h l e r f r e i } =-1; =-2; =-3 =-4; =-5; =-6; =-7; =-8; =-9; =-10 =-11 =-12 =-13 =-14

topoff= false

Bitblock-Transfer (für p u t i m a g e ) : normalput = 0 xorput = 1 andput = 3

; {mov> ; {xor> ; {and}

copyput orput notput

Clipping-Konstanten: clipon

=true;

clipoff

=false;

= 0 = 2 = 4

; {mov} ; {or} ; {not}

23. Grafik

Farb-Konstanten: (für s e t p a l e t t e , s e t a l l p a l e t t e , textbackground) Dunkle Farben

;{

H e l l e Farben

(Vor- u.

;{

(Vordergrund)

Hintergrund)

black blue green cyan red magenta brown lightgray blink

textcolor,

;{

= 0 darkgray = 8 = 1 lightblue = 9 = 2 lightgreen = 10 = 3 lightcyan = 11 = 4 lightred 12 = 5 lightmagenta = 13 = 6 yellow = 14 = 7 white = 15 =128 {Text-Vordergrund} g e t f i l l s e t t i n g s , setf i l l s t y l e )

Füllmuster-Konstanten (für

emptyfill = 0 solidfill 1 linefill 2 ltslashfill 3 slashfill = 4 bkslashfill 5 ltbkslashfill = 6 hatchfill 7 xhatchfill 8 interleavefill= 9 widedotfill = 10 c l o s e d o t f i l l = 11 userfill 12

{Hintergrundfarbe} {Z eichnungs f ä r b e }

{ } {//////} {/// dick) {\\\ dick} {\\\W} {leicht schraffiert} {stark überkreuzend s c h r a f f i e r t } {abwechselnde Linien}

{ {

309

} }

{benutzer-definiert}

Grafik-Treiber:

detect = 0;{automatische Erkennung} cga = 1; mcga = 2 ega = 3; ega64 = 4 egamono = 5; ibm8514 = 6 hercmono 7; att400 = 8 vga = 9; pc3270 = 10 currentdriver=-128;{aktiver Treiber für getmoderange}

310

23. Grafik

Grafikmodi der einzelnen Treiber: cgacO = cgac2 = 200} cgahi = egalo = 200} egahi 350} mcgacO = 200} mcgacl = 200} mcgac2 = mcgac3 mc gamed = mcgaHi = egamonohi = hercmonohi= vgalo = — vgamed

0 2

;{320 x 200} cgacl ;{320 x 200} cgac3

1 3

4 0

;{640 x 200} ;{ 640 x 200} ega641o =

0

{ 640 x

1

;{ 640 x 350} ega64hi =

1

{ 640 x

0

;{320 x 200} att400c0 =

0

{ 320 x

1

;{320 x 200} att400cl =

1

{ 320 x

2 3 4 5 3 0 0 1

{320 {320 {640 {640 {640 {720 {640 {640

x x x x x x x x

200} 200} 200} 480} 350} 348} 200} 350}

att400c2 = att400c3 = att400med= att400Hi = ibm8514Lo= ibm8514Hi= pc3270Hi = vgahi

2 3 4 5 0 1 0 2

}{320 x 200} r { 320 x

{ 320 { 320 { 640 { 640 { 640

x 200} x 200} x 200} x 400} x 480} {1024 x 768} { 720 x 350} {640 x 480}

Justierungskonstanten (für settextjustify) lefttext bottomtext centertext centertext righttext toptext

0; 0; 1; 1; 2; 2;

= = = = =

{ab Cursorposition} {oberhalb Cursorposition} {mittig zur Cursorposition} {Tect endet mit Cursorposition} {unterhalb der Cursorposition}

Linienarten und -breiten (für getlinesettings, setlinestyle): solidln dottedin centerIn dashedln userbitln normwidth thickwidth

= = = = = = =

0;{durchgezogen} 1;{gepunktet} 2;{.-.-.} 3;{gestrichelt} 4;{benutzerdefiniert} 1;{normale Breite} 3;{dick}

Textarten (für settextstyle, gettextsettings): Defaultfont triplexfont

= =

0 1

;{8x8 Bit pixelweise} ;{Vektor-Zeichensatz}

23. Grafik smallfont = sansseriffont= gothicfont = horizdir = vertdir = usercharsize =

2 3 4 0 1 0

311

;{Vektor-Zeichensatz} ;{Vektor-Zeichensatz} ;{Vektor-Zeichensatz} ;{von links nach rechts} ;{von unten nach oben} ;{benutzerdefiniert}

Tab.23.2: In GRAPH.TPU definierte Konstanten

type arccoordstype = record x, y, {Mittelpunkt} xstart, ystart, {Startpunkt} xend, yend {Endpunkt} : integer; end; fillpatterntype = array [1..8] of Byte; {benutzerdefiniert} fillsettingstype = record pattern : Word; {Nr. des Musters} color : Word; end; linesettingstype = record linestyle,pattern, thickness : word; end;

horiz vert viewporttype = record xl, yl, x2, y2 clip end; var

Word; Word; Word; {1..10} Word; Word; end; integer; : Boolean;

graphgetmemptr, graphfreememptr : pointer;

Tab. 23.3: In GRAPH.TPU definierte Datentypen und Variablen

312

23. G r a f i k

U m das Prinzip zu demonstrieren sei das Gerippe eines Grafikprogramms angeführt:

Beispiel

23.1:

Skelett eines Grafikprogramm. D i e benutzten Grafikoperationen sind in B i l d 23.1 erklärt.

program g r a p h t e s t ; uses graph; var g r a p h d r i v e r : i n t e g e r ; graphmode :integer; errorcode s integer;

begin (* 1 *) g r a p h d r i v e r := d e t e c t ; (* 2 *) initgraph(graphdriver, graphmode,''); ( * Übergang i n den Grafikmodus. J e t z t können A u f r u f e von Grafikprozeduren folgen.*) (* 3 *) errorcode :=graphresult; closegraph; (* Beendigung des Grafikmodus.*) write ('Graphdriver:'); writeln(graphdriver:5,graphmode:5); writeln(errorcode); if e r r o r c o d e o g r o k then

begin write ('graphicserror'); writeln(grapherrormsg(errorcode)); w r i t e l n ( ' p r o g r a m aborted '); halt; end; readln; end. In Z e i l e ( * 1 * ) bekommt die Variable g r a p h d r i v e r den Wert der Konstanten d e t e c t

(das ist 0 ) zugewiesen. In Z e i l e ( * 2 * ) wird die Prozedur

initgraph

aufgerufen, mit der jedes

Grafikprogramm

anfangen

muß.

i n i t g r a p h hat die beiden Referenzparameter g r a p h d r i v e r und g r a p h m o d e . D i e Prozedur prüft die Hardware nach einem Video-Adapter, lädt den entsprechenden Treiber, wählt einen zugehörigen Grafikmodus und schaltet in diesen um. In Z e i l e ( * 3 * ) wird der Fehlerstatus von

initgraph

der letzten vorausgehenden Grafikoperation in der Variablen festgehalten, c l o s e g r a p h

oder

errorcode

beendet den Grafikmodus und geht zum T e x t m o -

dus zurück. Nach A u s g a b e der Ergebnisse von i n i t g r a p h wird im Fehlerfall das Programm mit einer entsprechenden Meldung abgebrochen.

H

23. Grafik

313

Aufruf: Parameter: Wirkung:

i n i t g r a p h ( g t , g m , t p f ) Prozedur graph var g t , g m : i n t e g e r ; t p f : s t r i n g ; Hat die Referenzvariable g t den Wert 0, wird mit Hilfe einer Prozedur d e t e c t g r a p h ein der Hardware-Ausrüstung entsprechender Grafiktreiber g t ausgewählt und und ein dazu passender Grafikmodus gm ermittelt. Der Grafiktreiber g t wird geladen und in den Grafikmodus gm umgeschaltet. Hat g t einen Wert 0, wird der entsprechende Treiber genommen, und auch gm muß einen passenden Wert haben ( d e t e c t g r a p h wird dann nicht aufgerufen). In dem String t p f ist der Pfadname der Grafiktreiber (d. h. der BGI-Dateien anzugeben. Steht hier ein NULLstring ", sucht i n i t g r a p h in dem aktuellen Directory. Die Konstanten der Grafiktreiber und deren Modi sind in GAPH.TPU definiert (s. Tab 23.2). Die möglichen Grafikmodi gm der verschiedenen Treiber beschreiben die Auflösung (z. B. 640x400 Pixel, sowie die Farbpalette).

Aufruf: Parameter: Wirkung:

closegraph Prozedur graph keine Der Grafiktreiber wird aus dem Speicher entfernt und in den Text-Modus übergegangen, der vor i n i t g r a p h gesetzt war.

Aufruf: Parameter: Wirkung:

graphresult; Funktionswert: i n t e g e r graph keine Der Funktionswert ist der Fehlerstatus der letzten Grafikoperation. Für die möglichen Werte sind in GRAPH.TPU die folgenden Konstanten definiert (s. Tab 23.2). Zu jedem dieser Werte wird mit der Funktion g r a p h e r r o r m s g ein entsprechender Textstring erzeugt.

Aufruf: Parameter: Wirkung:

g r a p h e r r o r m s g ( e c ) Funktionswert:string graph ec: integer; Entsprechend dem Fehlercode e c (s. oben) wird eine Fehlermeldung erzeugt.

Bild 23.1: Die Grafikprozeduren von Beispiel 23.1

314

23. Grafik

Um Grafik zu machen braucht man ein Koordinatensystem. Je nach dem verwendeten Grafikmodus wird ein Auflösung von 320x200 oder 640x200 Punkten verwendet. Im ersten Fall liegt dann folgende Situation vor: (0,0) linke obere Ecke

(319,0) rechte obere Ecke

(159,99)Mitte (0,199)linke untere Ecke

(319,199) rechte untere Ecke

Der Ursprung (0,0) ist also immer die linke obere Ecke des Bildschirms, die x-Achse verläuft von links nach rechts (Spalten), die y-Achse von oben nach unten (Zeilen). Um Programme unabhängig von dem von i n i t g r a p h ausgewählten Grafikmodus und seiner Auflösung zu halten, gibt es die praktischen Funktionen g e t m a x x und g e t m a x y . Somit ist also nach xm := getmaxx div 2 ; ym := g e t m a x y div 2 ; ( x m , y m ) immer der Mittelpunkt des Bildschirms. Die einfachste Figur ist eine Gerade zwischen zwei gegebenen Punkten. Wenn im folgenden Cursor gesagt wird, ist damit der Cursor im Grafikmodus gemeint, der im Gegensatz zum Cursor des Textmodus nicht sichtbar an einer Stelle blinkt. Trotzdem sollte man ihn im Auge behalten. Bei den Grafikoperationen ist immer angegeben, was der Cursor tut. Eine Gerade kann man auf zwei Arten erzeugen: Einmal mit l i n e , wobei Startpunkt und Zielpunkt angegeben werden: line (10, 20, 100, 100); ergibt eine Gerade vom Punkt (10, 20) nach (100,100). Zum anderen kann man den Cursor mit m o v e t o zum Startpunkt bewegen und dann mit l i n e t o eine Gerade zum Zielpunkt zeichnen: moveto(10, 20); l i n e t o ( 1 0 0 , 100); liefert dieselbe Gerade. Die Art der Linie kann mit s e t l i n e s t y l e gewählt werden, z. B. setlinestyle(dashedln,0,thickwidth); (*dick g e s t r i c h e l t e Linie*) l i n e (10, 20, 100, 100); Wird s e t l i n e s t y l e nicht verwendet, wird eine ausgezogene, normal dicke Linie gezeichnet.

23. Grafik Beispiel

315

23.2:

Es wird ein Achsenkreuz gezeichnet sowie eine Schar von zehn konzentrischen Kreisen. program kreise; uses crt,graph; var gr_treiber, gr_modus, errorcode:integer; xm, ym, i :integer; begin gr_treiber := detect; initgraph(gr_treiber, gr_modus, ''); errorcode :=graphresult; if errorcode grok then begin writeln('Grafik-Fehler',grapherrormsg(errorcode)); closegraph; halt; end; xm := getmaxx div 2; ym :=getmaxy div 2; line(xm,0,xm,getmaxy); line(0,ym, getmaxx, ym); for i := 1 to 10 do circle(xm,ym,i*20); moveto(0,0); outtext(''); readln; closegraph; end. • Aufruf: Parameter: Wirkung:

cleardevice Prozedur graph keine Der Bildschirm wird gelöscht. Alle Parameter des Grafiktreibers werden auf die Standardwerte zurückgesetzt.

Aufruf: Parameter: Wirkung:

s e t v i e w p o r t ( x l , y l , x 2 , y 2 , c l i p ) Prozedur graph x l , y l , x 2 ,y2 :word; c l i p r b o o l e a n ; Es wird ein Zeichenfenster mit der linken oberen Ecke ( x l , y l ) und der rechten unteren Ecke ( x 2 , y 2 ) gesetzt. Der Cursor wird auf die linke obere Ecke gesetzt (relative Koordinaten (0,0). Der Parameter c l i p legt fest, ob Zeichenaktionen am Fensterrand abgeschnitten werden ( t r u e ) oder nicht ( f a l s e ) . Dazu gibt es die Konstanten c l i p o n , c l i p o f f (s. Tab 23.2). i n i t g r a p h setzt den ganzen Bildschirm als Fenster.

316

23. Grafik

Aufruf: Parameter: Wirkung:

g e t v i e w s e t t i n g s ( v i e w p o r t ) Prozedur graph var v i e w p o r t : v i e w p o r t t y p e ; Es werden Informationen über das momentane Fenster ermittelt. In GRAPH gibt es dazu den Typ v i e w p o r t t y p e (s. Tab. 23.3).

Aufruf: Parameter: Wirkung:

Prozedur clearviewport graph keine Der Inhalt des momentanen Fensters wird gelöscht und durch die Farbe p a l e t t e ( 0 ) ersetzt.

Aufruf: Parameter: Wirkung:

setvisualpage(p) Prozedur graph p : word; Manche Grafikadadapter (z.B. EGA, VGA, Hercules) unterstützen mehrere Seiten (s. s e t a c t i v e p a g e ) . s e t v i s u a l p a g e legt fest, welche Seite sichtbar ist.

Aufruf: Parameter: Wirkung:

Prozedur graph setactivepage(p) p : word; Manche Grafikadadapter (z.B. EGA, VGA, Hercules) unterstützen mehrere Seiten, s e t a c t i v e p a g e legt fest, auf welche Seite gezeichnet wird. Damit kann man auch im Hintergrund, d. h. unsichtbar zeichnen.

Aufruf: Parameter: Wirkung:

graph getmaxx Funktionswert: w o r d keine Der Funktionswert ist die für den gesetzten Treiber und Modus maximal mögliche x-Koordinate (z. B. 319, 639).

Aufruf: Parameter: Wirkung:

getmaxy Funktionswert: w o r d graph keine Der Funktionswert ist die für den gesetzten Treiber und Modus maximal mögliche y-Koordinate (z. B. 199, 349).

Bild 23.2: Bildschirm-, Fenster- und Seitenroutinen

Aufruf: Parameter: Wirkung:

p u t p i x e l (x, y, f ) Prozedur graph x , y : i n t e g e r , fsword An der Stelle x , y wird ein Pixel mit der Farbe f gezeichnet.

Aufruf: Parameter: Wirkung:

Funktionswert: w o r d graph getpixel(x,y) x,y : integer; Der Funktionswert ist die Farbnummer des Pixels an der Stelle x , y.

23. Grafik

317

Aufruf: Parameter: Wirkung:

getx Funktionswert: i n t e g e r graph keine Der Funktionswert ist der x-Wert der momentanen Cursorposition.

Aufruf: Parameter: Wirkung:

gety Funktionswert: i n t e g e r graph keine Der Funktionswert ist der y-Wert der momentanen Cursorposition.

Bild 23.3: Punkt-Routinen

Aufruf: Parameter: Wirkung:

l i n e (xl, y l , x2 , y 2 ) Prozedur graph xl,x2,yl,y2:integer; Es wird eine Gerade vom Punkt (xl,yl) zum Punkt (x2,y2) gezeichnet. Dabei wird die durch den letzten Aufruf von s e t l i n e s t y l e festgelegte Linienart (s. dort) und die durch s e t c o l o r festgelegte Farbe verwendet. Der Cursor wird durch l i n e nicht geändert.

Aufruf: Parameter: Wirkung:

l i n e t o ( x , y) Prozedur graph x,y:integer Von der momentanen Cursorposition aus wird eine Gerade zum Punkt (x,y) gezeichnet. Dabei wird die durch s e t l i n e s t y l e definierte Linienart und die durch s e t c o l o r definierte Farbe benutzt. Nach dem Aufruf steht der Cursor auf (x, y).

Aufruf: Parameter: Wirkung:

linerel(dx,dy) Prozedur graph d x , dy : i n t e g e r ; Befindet sich der Cursor an der Stelle x , y , so wird eine Gerade zum Punkt (x+dx, y + dy) gezeichnet.

Aufruf: Parameter: Wirkung:

moveto(x,y) Prozedur graph x,y:integer; Der Cursor wird auf den Punkt (x,y) gesetzt, x und y be ziehen sich auch auf das momentane Fenster.

Aufruf: Parameter: Wirkung:

m o v e r e l ( d x , dy) Prozedur graph. d x , dy : i n t e g e r ; Befindet sich der Cursor an der Stelle ( x , y ) , so geht er an die Stelle (x+dx, y+dy).

318

23. Grafik

Aufruf: Parameter: Wirkung:

Aufruf: Parameter: Wirkung:

setlinestyle Prozedur graph ( s t i l , muster, dicke) s t i l , muster, dicke:word; Es wird die Linienart für folgende Zeichenoperationen ( l i ne, l i n e t o a r c , c i r c l e , d r a w p o l y , r e c t a n g l e ) festgelegt. Für s t i l gibt es in GRAPH Konstanten (s. Tab. 23.2). Der Parameter m u s t e r hat nur Bedeutung für s t i l = 4. Für den Parameter d i c k e gibt es die Konstanten n o r m w i d t h , t h i c k w i d t h (s. Tab. 23.2). getlinestyle(li) Prozedur graph var I i : l i n e s e t t i n g s t y p ; Es wird die momentan gesetzte Linienart ermittelt. Dazu gibt es in GRAPH den Typ l i n e s e t t i n g s t y p e (s. Tab. 23.2).

Bild 23.4: Linien-Routinen

Aufruf: Parameter: Wirkung:

a r c ( x , y , w l , w2 , r ) Prozedur graph x,y: integer; wl,w2,r:word Um den Mittelpunkt (x, y) wird ein Kreisbogen des Radius r vom Winkel wl bis zum Winkel w2 gezeichnet. Die Winkel sind in Grad anzugeben (0 = horizontal rechts, 90 =senkrecht oben usw.).

Aufruf: Parameter: Wirkung:

g e t a r c c o o r d s (ac) Prozedur graph var a c : a r c c o o r d s t y p e ; Die Prozedur liefert Angaben über den letzten Aufruf von a r c . Dazu gibt es in graph den Datentyp a r c c o o r d s t y p e (s. Tab. 23.2).

Aufruf: Parameter: Wirkung:

circle (x,y,r) Prozedur graph x , y : i n t e g e r ; r:word Um den Mittelpunkt ( x , y) wird (mit der momentanen Farbe) ein Kreis mit dem Radius r gezeichnet.

Aufruf: Parameter: Wirkung:

g e t a s p e c t r a t i o ( x , y ) Prozedur graph var x , y :word; Es wird das physikalische Höhen-/Seitenverhältnis des Bildschirmes ermittelt, das jedem Treiber und Modus zugeordnet ist. Es wird bei a r c , c i r c l e und p i e s l i c e verwendet, um die Kreisbögen als Kreise erscheinen zu lassen.

Aufruf: Parameter:

r e c t a n g l e ( x l , y 1, x 2 , y2 ) Prozedur x l , y l , x 2 , y2 s i n t e g e r ;

graph

23. Grafik

319

Wirkung:

Es wird ein Rechteck mit der linken oberen Ecke ( x l , y l ) und der rechten unteren Ecke (x2 , y2) gezeichnet. Dabei muß sein 0

< =

> ?

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95

40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F

@ A B C D E F G H I J K L M N 0 P

Q

R S T U V

w

X Y

z [

\

]

A -

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

• 60 a 61 62 b 63 c 64 d 65 e 66 f 67 g 68 h i 69 6A h 6B k 1 6C 6D m 6E n 6F 0 70 P 71 q 72 r 73 s 74 t 75 u 76 V 77 w 78 X 79 y 7A z 7B ( 1 7C 7D } ~ 7E 7F DEL

400

Anhang

Die ersten 32 ASCII-Zeichen sind als Steuerzeichen für Peripheriegeräte und Datenübertragung vorgesehen. Die wichtigsten davon sind: 0 7 8 9 10 11 12 13 27

NUL BEL BS HT LF VT FF CR ESC

Nil (nichts) Bell (Klingel) Backspace (Rückwärtsschritt) Horizontal Tabulator Line Feed (Zeilenvorschub) Vertical Tabulator Form Feed (Formularvorschub) Carriage Return Umschaltung (escape)

Für die meisten Steuerzeichen gibt es keine Tasten. Sie können zusammen mit der Taste und einem anderen Zeichen erzeugt werden, was in der ersten Spalte mit Crtl-Ch. gemeint ist (kurz als A bezeichnet). Sie werden häufig auch als Steuerzeichen, d. h. Kommandos, von Dienstprogrammen verwendet. In Turbo Pascal werden sie als Kommandos für den Editor benutzt (s. Kap. 26). Die obige Tabelle enthält den ASCII-Zeichensatz in der US-Form. Für die nationalen Zeichensätze werden einige Zeichen anders belegt. Für den deutschen ASCII-Zeichensatz gilt: [=Ä

\= Ö

]=Ü

{=ä

l= ö

}=ü

~=ß

@ =§

Üblicherweise ist der ASCII-Code ein 7-Bit-Code, d. h. es gibt 128 Zeichen. Das 8. Bit kann als Paritätsbit verwendet werden. Bei manchen PCs wird aber auch ein echter 8-Bit-Code verwendet. Es gibt dann 256 Zeichen. Die ersten 128 Zeichen stimmen mit den obigen überein. Die Zeichen 128-255 werden dann für Sonderzeichen wie ± X â Î2 ^ J> verwendet, und auch die Umlaute gehören dann zu diesen Sonderzeichen. Ein Beispiel dafür ist der IBM-Code für den IBM-PC. Es sind dann gewöhnlich bei Strings alle 256 Zeichen zugelassen, etwa ' 0 Vi |J.m\ während hingegen aber eine Variable nicht degré heißen darf. Generell wäre zu betonen, daß man bei einem 8-Bit-Zeichensatz die Zeichen 128-255 dann nicht verwenden sollte, wenn man beabsichtigt, diesen Text auf einen Computer mit einem anderen Code zu übertragen. Von besonderer Bedeutung ist das ASCII-Zeichen 27 ESCAPE. Es dient zur Steuerung von Druckern und Bildschirmen. Erhält ein Peripheriegerät das ASCII-Zeichen 27, so interpretiert es das oder die folgenden Zeichen als Steuerzeichen (s. Beispiel 17.7).

D. Compiler-Direktiven Mitunter ist es zweckmäßig, wenn man den Compilerlauf beeinflussen kann, d. h. gewisse Dinge zu tun und andere zu unterlassen. In Turbo Pascal kann dies auf folgende Arten geschehen: - über den Menüpunkt Options/Compiler, - als Parameter /$ bei der Kommandozeilenversion (s. Kap. 20), - als Compiler-Direktiven im Quelltext. Sie haben die Form spezieller Kommentare: {$direktive}

bzw. {$direktive parameter)

Man beachte, daß vor oder nach dem Zeichen $ kein Blank stehen darf. Man kann drei Arten von Direktiven unterscheiden: - Schalterdirektiven (mit + eingeschaltet und - ausgeschaltet), - Parameterdirektiven, die bestimmte Angaben machen, - bedingte Direktiven, bei denen in Abhängigkeit von einer Bedingung Teile des Quelltextes nicht compiliert werden.

Schalterdirektiven Das Zeichen + bedeutet, daß die entsprechende Compilereigenschaft eingeschaltet (aktiv), das Zeichen -, daß die entsprechende Eigenschaft abgeschaltet (passiv) ist. Man beachte auch weiter, daß jede der Direktiven einen der beiden Zustände als Voreinstellung (sogenannter default-Wert s. Optionsl Compiler im Hauptmenü) hat. Man braucht die Direktiven also nur anzuführen, wenn man die Voreinstellungen ändern will. Die meisten Direktiven können in einem Programm mehrfach ein- und abgeschaltet werden. Man hat dabei noch zwischen globalen und lokalen Direktiven zu unterscheiden. Eine globale Schalterdirektive gilt für die gesamte Compilierung. Globale Schalterdirektiven müssen ganz am Anfang des Programms stehen (nach p r o g r a m bzw. u n i t ) . Lokale Schalterdirektiven können irgendwo im Quelltext stehen, wo überhaupt ein Kommentar stehen darf. Man kann mehrere Schalterdirektiven in einer Kommentarklammer als Liste zusammenfassen (etwa {$B+,R-,S-}). Es dürfen dabei keine Blanks verwendet werden, also nicht {$B+, R-, S-}. {$A±} Daten-Ausrichtung Default: A+ global Bei $A+ werden alle Variablen und Typenkonstanten, deren Werte größer als 1 Byte sind, auf Wortgrenzen ausgerichtet, d.h. beginnen mit einer geraden Adresse. Das bewirkt einen schnelleren Speicherzugriff. Bei einer 8088 CPU hat $A+ keine Wirkung.

402

Anhang

{$B±} Auswertung von logischen Ausdrücken Default: BLokal Bei B+ wird ein logischer Ausdruck vollständig ausgewertet. Bei B- wird die Auswertung der Terme abgebrochen, wenn der Wahrheitswert des Ausdrucks feststeht. (Ein Beispiel s. Kap. 9.3) ($D±) Debug-Information Default: D+ global Bei D+ erzeugt der Compiler zusätzliche Informationen zur Fehlersuche, d. h. eine Tabelle mit Zeilennummern für jede Routine, womit der Zusammenhang zwischen den Adressen der einzelnen Befehle und der Stelle im Quelltext hergestellt wird. Damit können Laufzeitfehler im Quelltext lokalisiert werden. Bei der Compilierung von Units werden diese Zusatzinformationen in der zugehörigen TPU-Datei abgespeichert. ($G±) Generierung von 80286-Code Default: Glokal Mit $G- wird nur 8086-Code generiert, der auf allen 80x86-Prozessoren lauffähig ist. Beim Compilieren mit $G+ werden zusätzliche 80286-Befehle verwendet. So erzeugte Programme sind auf 8088- und 8086-Prozessoren nicht lauffähig. {$E±} Emulator Default E+ global Im Falle ($N+) und {$E+} werden die fünf real-Typen durch einen Emulator auf dem 80x86 nachgebildet. Ein Coprozessor 8ox87 wird nicht gebraucht. Bei {$N+} und ($E-) werden nur einige Routinen zur Ansteuerung des 80x87 eingebunden, der vorhanden sein muß. {$F±) Far Calls erzwingen Default: Flokal Mitunter erfolgen Aufrufe von Prozeduren und Funktionen über Codesegmentgrenzen hinweg. Es handelt sich dann um sogenannte far calls. Normalerweise {F-} entscheidet der Compiler über die Art des Aufrufes. Ist der Prozedur- oder Funktionskopf in (F+}....{F-) eingerahmt, werden immer Far-Aufrufe und entsprechende Rücksprünge erzeugt. Prozeduren oder Funktionen, die Parameter sind, müssen mit (F+) compiliert werden (s. Beispiel 12.2). ($I±) I/O Prüfung Default: 1+ lokal Die Direktive I legt fest, ob E/A-Operationen auf Fehler untersucht werden sollen {$1+} oder nicht {$1-}. Hat der Benutzer $1 gewählt, kann er mit Hilfe der Standardfunktion i o r e s u l t die Fehlerbehandlung selbst durchführen, (s. Beispiel 8.3). ($L+) Link Buffer Default: L+ global Beim Binden mehrerer Module werden Zwischeninformationen erzeugt, die bei (L-) temporär auf Diskette, bei {L+} im Hauptspeicher abgelegt werden.

D. Compiler-Direktiven

403

($N±) Numerische Verarbeitung Default: Nglobal Im Modus | N + ) gibt es die fünf verschiedenen real-Typen von Kapitel 5.2. Es wird ein Coprozessor 80x87 vorausgesetzt. Bei {N-} gibt es nur den Typ r e a l . S. auch die Direktive E±. {$0±} Erzeugung von Overlaycode Default: Oglobal Eine mit $ 0 + compilierte Unit kann (aber muß nicht) als Overlay-Unit verwendet werden. Gewöhnlich ist $ 0 + mit $F+ zu verbinden, damit die vom Overlaymanager geforderten far-Aufrufe ausgeführt werden können. ($R±) Range Checking Default: Rlokal Die Direktive R legt fest, ob zur Laufzeit die Indexgrenzen von Arrays und die Zuweisungen an Variable von Aufzählungs- und Unterbereichstypen auf Einhaltung der Grenzen überprüft werden {$R+} oder nicht {$R-}. Im Falle $R- kann ein Programm unsinnige Ergebnisse produzieren. Es wird empfohlen, bei der Programmentwicklung R+, bei Benutzung des fehlerfreien Programms R- zu verwenden. Bei R- wird deutlich weniger Rechenzeit gebraucht. {$S±} Stack Overflow Checking Default: S+ lokal Die Direktive {$S+} legt fest, ob der Stack vor jedem Aufruf einer Prozedur oder Funktion daraufhin überprüft wird, ob genug Speicherplatz im Stack für die lokalen Variablen und den Rücksprung vorhanden ist oder nicht. Bei {$S-} unterbleibt diese Prüfung, was aber einen Systemabsturz zur Folge haben kann. ($V±) VAR-String Checking Default: V+ lokal Die Direktive V legt fest, ob Referenzparameter vom Typ STRING auf ihre aktuelle Länge getestet werden. V+ bedeutet, daß bei einem Referenzparameter vom Typ STRING die Länge von aktuellem und formalen Parameter übereinstimmen müssen. Bei $V- können die Längen unterschiedlich sein. ($X+) Syntaxerweiterung Default: Xglobal Bei $X+ wird eine in dem Sinne erweiterte Syntax verfügbar, bei der Funktionsaufrufe als Anweisungen gelten, d.h. Funktionen werden wie Prozeduren behandelt.

Parameterdirektiven haben die Form {$direktive parameter} ($1 filename) Dateien einfügen (include) lokal Die Direktive $1 filename veranlaßt den Compiler, an der Stelle der Direktive den Text aus f i l e n a m e einzufügen. Damit kann der Quelltext über

404

Anhang

mehrere Dateien verteilt gehalten werden. Der mit {$1 filename} eingefügte Text darf nicht selbst eine include-Direktive enthalten. Es wird dabei zunächst im aktuellen Directory gesucht, danach in den über OptionsIDirectorieslInclude directories angegebenen. Eine include-Direktive darf nicht im Anweisungsteil des P r o g r a m m s stehen, d.h. alle Anweisungen zwischen begin und end des Anweisungsteils müssen in einer Datei stehen. {$L filename} Einbinden (Linken) von Objektdateien lokal Diese Direktive veranlaßt den Binder, die Datei f i l e n a m e in das Programm einzubinden, f i l e n a m e muß eine .OBJ-Datei sein. ($M stackgröße,Heapmin,Heapmax) Speicherbelegung (Memory) global Die Direktive legt fest, wieviel Platz für den Stack und minimal und maximal für den Heap reserviert werden sollen, also z . B . |$M32767,D, 6 5 5 3 6 0 ) . Stackgröße kann den Bereich 1024...65520 haben, h e a p m i n und h e a p m a x O . . . 6 5 5 3 6 0 (64o KB). Der Defaultwert ist (M 16384,0, 655360} ( $ 0 unitname) Diese Direktive gibt an, welche Units in einer .OVR-Datei statt in der .EXE-Datei hinterlegt werden. Die Direktive muß hinter der uses-Klausel im Programm stehen. Innerhalb einer Unit hat die Direktive keine Wirkung. Die in der Direktive benannte Unit muß mit $ 0 + kompiliert worden sein.

Bedingte Direktiven (Bedingte Compilierung) $ELSE $ E N D I F $ I F D E F $ I F N D E F $ I F O P T $ U N D E F Mit diesen Direktiven kann man gezielt Teile des Quelltextes von der Compilierung ausschließen. Bei der ersten Form {$IFDEF s y m b o l n a m e ) . . . ( $ E N D I F ) wird der Pascaltext ... compiliert, wenn symbolname definiert ist. Die Definition erfolgt mit ( $ D E F I N E symbolname d.h. das durch symbolname angegebene Symbol gilt von hier ab als definiert, symbolname kann irgendein Name sein, der mit anderen im Programm vork o m m e n d e n Namen nichts zu tun hat. Mit ( $ U N D E F symbolname}

D. Compiler-Direktiven

405

wird eine Definition rückgängig gemacht, d.h. das durch symbolname angegebene Symbol gilt von hier ab als nicht mehr definiert. War symbolname gar nicht definiert, hat die Direktive keine Wirkung. Eine zweite mögliche Form ist {$IF symbolname}...{$ELSE} ... {$ENDIF) Die Wirkung ist einer if-Anweisung ähnlich. Ist symbolname definiert, wird der zwischen ($IF) und {$ELSE} stehende Quelltext compiliert, sonst der zwischen {$ELSE} und {$ENDIF}. Neben ($IFDEF) gibt es auch {$IFNDEF symbolname} Die Bedingung gilt als true, wenn symbolname nicht definiert ist und sonst nicht, also das logische Gegenteil von $IFDEF. Schließlich gibt es noch {$IFOPT Schalterdirektive} Trifft die Schalterdirektive zu, gilt die Bedingung als true. Es sei noch einmal betont, daß s y m b o l n a m e nichts mit den Namen anderer Pascalobjekte in einem Programm zu tun hat. Nach ($DEFINE dies) gibt es das Symbol d i e s unabhängig davon, ob der Name d i e s anderweitig in dem Programm verwendet wird oder nicht. {IFDEF dies} writeln('das Symbol dies ist hier definiert'); {$ENDIF} führt zur Ausführung der Anweisung w r i t e l n . Die folgenden Symbole sind zu Beginn jeder Compilierung definiert: VER60

was für Version 6.0 steht. Andere Versionen definieren z.B. VER50, VER55 usw. MSDOS falls das Betriebssystem MS-DOS oder PC-DOS verwendet wird, CPU86 falls ein 80x86 Prozessor vorhanden ist, CPU87 falls ein 80x87 Coprozessor vorhanden ist. Damit liegt es nahe zu schreiben: {$IFDEF CPU87} {$N+} ($ELSE} {$N-} {$ENDIF}

406

Anhang

Noch einmal: s y m b o l n a m e ist kein Variablenname, also auch keiner vom Typ b o o l e a n . Nach const das = true; ist {$IFDEF das} durchaus nicht zutreffend, falls nicht vorher {$DEFINE das) im Programm stand.

E. Literaturverzeichnis [1] Turbo Pascal 6.0, Benutzerhandbuch. Heimsoeth & Borland, München 1990,309 S. [2] Turbo Pascal 6.0, Programmierhandbuch. Heimsoeth & Borland, München 1990, 278 S. [3] Herschel, R., Standard-Pascal, Oldenbourg, München, 8. Auflage 1992 [4] Herschel, R., Turbo Pascal 3.0, Oldenbourg, München, 7. Auflage 1990 [5] Däßler, K. und M.Sommer, Pascal- Einführung in die Sprache, NormEntwurf DIN 66256, Erläuterungen, Springer, 2. Auflage 1985 [6] Wirth, N., Algorithmen und Datenstrukturen, Teubner, 3. Aufl. 1983 [7] Dieterich, E.-W., Turbo Assembler, Oldenbourg, München, 2. Auflage 1993 [8] Turbo Pascal 7.0, Benutzerhandbuch, Borland, Langen, 1992 [9] Turbo Pascal 7.0, Programmierhandbuch, Borland, Langen, 1992 [8] Turbo Pascal 7.0, Referenzhandbuch, Borland, Langen, 1992

F. Sachwortverzeichnis A absolut-klausel 46 Adresse 221 ANSI-Treiber 288 Anweisung 79 asm - 293 bedingte - 83 case - 84 einfache - 72 for - 88 inline - 304 leere - 80 methoden - 335 repeat - 86 Schleifen- 86 Sprung- 89 Verbund- 80 while - 86 Wiederholungs- 86 with - 174 Anweisungsteil 14,81 Array 137 - komponente 139 - konstante 146 - t y p 137 ASCII-Zeichen 423 asm-block 303 Aufrufparameter 278 Aufzählunstyp 126 Ausdruck 61 arithmetischer - 62 logischer - 68

B Baum 234 Bezeichner 27 Bildschirmspeicher 289 Block 2 6 , 9 1 boolean 37, 372

break 372 Buchstabe 27 byte 31 Byte Bool 372 C char 35 checkbreak 265 checkeof 266 Clipboard 368 comp 33 Compiler-Direktiven 16,425 Compiler-Optionen 16 continue 372

D Datei 195 Typ- 198 Text- 208 typlose - 217 Datenfeld 198 Datensatz 198 Debugger 359 DOS-Funktionen 284 double 33 E Editor 367 einfacher typ 120 exclude 380 extended 33

F Fehler 357 Feldliste 171 fester-teil 171 Fibonacci Zahlen 115

410

Anhang

formal-parameter-liste 95 Formatangaben 51 Funktionen 102 - im Assembler 296 abs 69 addr 225 arctan 71 chr 36 concat 165 copy 165 cos 71 cseg 225 dseg 225 eof 49, 197 eoln 49, 209 exp 71 filepos 202 filesize 202 frac 72 getmaxx 316 getmaxy 316 gettextsettings 323 getpixel 316 getx 317 gety 317 grapherrormsg 313 graphresult 313 hi 73 int 72 ioresult 54 keypressed 50 length 165 In 71 lo 73 maxavail 241 memavail 241 odd 72 ofs 225 ord 36, 126 ovrgetbuf 271 paramcount 278 paramstr 278 pi 72 pos 165 pred 126

ptr 225 random 71 readkey 50 round 73 seekeoln 209 seekeof 209 seg 225 sin 71 sizeof 38 sqr 71 sqrt 71 sseg 225 succ 126 swap 73 trunc 72 typeof 355 upcase 36 Funktionsaufruf 104 Funktionsblock 103 Funktionskopf 103

G global 98 Grafik 307 - treiber 307 - modus 310 Grundmenge 187 H Haltepunkt 359 Hauptmenii 17 Heap 240 hexadezimal 32 hex-ziffer 32 high 374

implementations-teil 252 include 380 index-typ 137 initialisierungsteil 253 inline-element 304 input 47

F. Sachwortverzeichnis integer 31 Iteration 113 interface-teil 252 Interrupt 283

N Name 27 O

K Kapselung 341 Kommandozeilen-Version 275, 381 Kommentar 29 kompilierbare-einheit 251 komponentenliste 332 komponenten-typ 137 Konstante 38 Konstruktor 350 Koordinatensystem 314 L Linemarker 210 Liste gekettete 231 lokal 98 LongBool 372 longint 31 low 374 M Make 272 Marke 90 Matrix 158 Menge 187 Mengenkonstante 194 Methoden 331 - köpf 333 - liste 332 - fkt.-kopf 334 - proz.-kopf 334 - Vereinbarung

334

virtuelle - 347, 349 virtuelle - Tabelle 352 Modularisierung 249 MS-DOS 281

object-typ 341 Objektcode 15 objekt-konstante 356 Offset 222 Ordinaltyp 125 OpenString 378 Operand 61 Operator 5 9 , 4 0 3 Adreß - 224 arithmetischer - 62 bit - 64 cast - 74 logischer - 68 Mengen - 188 output 47 Overlay 249, 269 P Palette 320 Parameter 94 - direktiven 409 aktueller - 96 Ausgangs - 110 Eingangs - 110 formaler - 95 konstanter - 373 offener Array 374 Referenz- 97 Wert- 96 Polymorphie 331 Primzahlen 117 private 382 Programmkopf 16 Prozeduren 93 - im Assembler 296 append 210 arc 318 assign 197 bar 319

412

Anhang

bar3d 319 b l o c k r e a d 218 b l o c k w r i t e 218 chdir 282 circle 318 c l e a r d e v i c e 315 c l e a r v i e w p o r t 316 close 197 closegraph 313 clrscr 55 dec 72 delay 100 delete 164 delline 58 dispose 230 d r a w p o l y 319 erase 197 exit 92 fillpoly 319 f l o o d f i l l 320 f l u s h 209 f r e e m e m 241 getarccoords 318 getaspectratio 318 g e t b k c o l o r 321 getcolor 321 getdate 286 getdir 282 g e t m e m 241 getpalette 322 gettextsettings 323 g e t t i m e 286 getviewsettings 316 getlinestyle 318 gotoxy 55 halt 92 h i g h v i d e o 55 inc 72 initgraph 313 insert 164 insline 58 intr 2 8 4 line 317 linerel 317 lineto 317 l o w v i d e o 57

mark 241 mkdir 282 moverei 317 m o v e t o 317 msd o s 285 new 230 n o r m v i d e o 55 nosound 100 outtext 322 outtextxy 322 ovrinit 269 ovrsetbuf 271 ovrclearbuf 271 ovrinitems 271 pieslice 320 putpixel 316 r a n d o m i z e 72 read 48, 199, 210, 2 1 2 readln 4 8 , 2 0 9 , 2 1 0 rectangle 318 release 241 rename 197 reset 197 rewrite 197 rmdir 282 seek 202 setactivepage 316 setallpalette 321 setbkcolor 321 setcolor 321 setfillpattern 320 setfillstyle 3 2 0 setlinestyle 318 setpalette 321 settextbuf 210 settextjustify 3 2 3 settextstyle 3 2 2 setviewport 3 1 5 setvisualpage 316 sound 100 str 164 t e x t b a c k g r o u n d 55 textcolor 56 textheight 323 textwidth 323 truncate 202

F. Sachwortverzeichnis val 164 write 51, 199, 210, 2 1 2 writeln 5 1 , 2 0 9 , 2 1 0 P r o z e d u r b l o c k 95 P r o z e d u r k o p f 95 P r o z e d u r t y p 122 public 3 8 2

Q Quelltext

17

R real 35 Record - k o m p o n e n t e 172 - k o n s t a n t e 183 - t y p 171 v a r i a n t e r - 180 R e g i s t e r 283 R e i h u n g 137 R e k u r s i o n 112 direkte - 117 indirekte - 117 S S c h a l t e r d i r e k t i v e n 425 S e g m e n t 222 seif 336 S e m a n t i k 26 s e t - k o n s t a n t e 194 set-typ 187 shortint 31 single 33 Sortieren 149 Stack 237 S t a n d a r d n a c h s p a n n 300 S t a n d a r d n a m e 28 S t a n d a r d v o r s p a n n 302 S t r i n g t y p 161 String, null-terminiert 376 Syntax 2 4 , 3 9 1 - d i a g r a m m 391

T Tastenbefehle - d. D e b u g g e r s 3 6 2 - d. Editors 367 T e i l m e n g e 189 Textdatei 208 Turbo Assembler 293 T y p a n g a b e 119 Typdatei 198 type-cast 74 T y p e n k o n s t a n t e 41 T y p w a n d l u n g 73

U Unit 15, 251 - köpf 251 Standard - 263 U n t e r b e r e i c h s t y p 131 uses-klausel 25 V Variable 43, 136 Bezugs - 223 dynamische - 229 ganze - 136 K o m p o n e n t e n - 136 zeiger - 223 varianter-teil 180 Vereinbarung -steil 1 4 , 2 6 F u n k t i o n s - 103 K o n s t a n t e n - 39 M a r k e n - 90 Methoden - 334 P r o z e d u r - 94 Variablen - 4 3 Vererbung 3 3 1 , 3 4 0 V i d e o - A d a p t e r 307 v o r z e i c h e n l o s e - i n t e g e r - z a h l 32 v o r z e i c h e n l o s e - r e a l - z a h l 34

413

414

Anhang

W Watchausdruck 363 Watchfenster 361 Wertzuweisung 81, 104 word 31 WordBool 372 Wortsymbol 28 Z Zahl ganze - 31 gebrochene - 33 Zeichenkette 15 Zeiger 221 - typ 222 Ziffer 27 Zufallszahl 193

Beispiele zu diesem Buch Auf der Internetseite dieses Buches in unserer Buchdatenbank ("Titelsuche") auf dem Webserver www.oldenbourg-verlag.de haben Sie die Möglichkeit, Dateien zu diesen Buch konstenlos herunterzuladen.