Weiterbildung Informatik

Inhaltsverzeichnis

Sekundarstufe I

1. Visuelle Programmierung
2. Informationsdarstellung
3. Algorithmik
4. Netzwerke & Internet
5. Programmierung in Python
6. Links

Sekundarstufe II

7. Blick über die Informatik
8. Algorithmen
9. Grundlagen der Programmierung
10. Programmiertechniken
11. Funktionen und Prozeduren
12. Programmierung mit Zeichenketten
13. Programmierung mit Arrays
14. Rekursion
15. Syntaxbeschreibung mit (E)BNF
16. Terme und ihre Auswertung
17. Objekte und ihre Identität
18. Sortieren und Effizienz
19. Definition von Objekten
20. Hierarchische Modularisierung
21. Rechnerarchitektur
22. Netzwerke
23. Dynamische Webseiten
24. Digitale Bildverarbeitung
25. Backtracking
26. Künstliche Intelligenz für Spiele
27. Neuronale Netze
28. Reguläre Ausdrücke
29. Algorithmen und Datenstrukturen
30. Relationale Datenbanken
31. Datenbankprogrammierung in Python
32. Webprogrammierung in Python
33. Verteilte Versionskontrolle

Sekundarstufe I

1. Visuelle Programmierung


1.1 Visuelle Programmierung

Einleitung

In diesem Modul werden Sie anhand der Programmiersprache Scratch in die Grundlagen der Programmierung eingeführt.

Scratch ist eine visuelle Programmiersprache inklusive einer Entwicklungsumgebung (engl. integrated development environment oder IDE), die von der Lifelong Kindergarten Group am MIT Media Lab entwickelt wurde und unter didaktischen Gesichtspunkten für Kinder und Jugendliche konzipiert ist. In visuellen Programmiersprachen werden Programme im Gegensatz zur textbasierten Programmierung aus grafisch gestalteten Elementen nach dem “Baukastenprinzip” zusammengesetzt, deren Bedeutung über ihre visuelle Repräsentation (Form, Farbe und Beschriftung) intuitiv erschlossen werden kann. Im Vordergrund steht die einfache Bedienbarkeit.

Visuelle Programmierungsprachen vermeiden so, dass eine komplizierte, abstrakte Syntax gelernt und eingehalten werden muss und ermöglichen einen spielerischen und explorativen Zugang zum Programmierenlernen.1 Scratch und verwandte visuelle Programmiersprachen wie Blockly, NEPO, MakeCode oder Snap! haben sich als Einstieg in die Programmierung im Informatikunterricht bewährt und stellen in ihren Online Communities umfangreiches didaktisch aufbereitetes Material für den Informatikunterricht zur Verfügung (siehe Materialsammlung). Ausgewählte Unterrichtsmaterialien werden auch im Rahmen der Weiterbildung behandelt.

Vorbereitung

Scratch-Projekte lassen sich direkt im Webbrowser oder auf Ihrem Rechner mit der Desktop-Anwendung Scratch-App erstellen. Am einfachsten ist die Verwendung der Browserversion, hierzu benötigen Sie aber eine laufende Internetverbindung, während Sie arbeiten.

Wenn Sie lieber offline arbeiten möchten oder sicherstellen möchten, dass keine persönlichen Daten übermittelt werden2, installieren Sie als Erstes die Desktop-Anwendung auf Ihrem Arbeitsrechner.

Scratch als Desktop-Anwendung

Die Scratch-App wird auf Ihrem Rechner installiert und läuft dort dann auch ohne Internetverbindung. Scratch-Projekte werden lokal auf Ihrem Rechner gespeichert und von dort geladen.

Die Installationsdatei kann von der Scratch-Homepage unter https://scratch.mit.edu/download heruntergeladen werden. Als Betriebssysteme werden momentan Windows 10, macOS (ab Version 10.13), sowie ChromeOS und Android für Tablets unterstützt.

Wenn Sie ein Linux-System verwenden, können Sie auf die inoffizielle Linux-Portierung Scratux ausweichen, die Sie über den Snap Store oder über die Projekt-Homepage https://scratux.org installieren können.

Nachdem Sie die Scratch-App gestartet haben, sollten Sie das Fenster als Erstes maximieren und als Sprache “Deutsch” für die Oberfläche über das Symbol Icon in der Menüleiste auswählen.

Scratch im Webbrowser

Öffnen Sie in Ihrem Webbrowser die Seite https://scratch.mit.edu/projects/editor, um direkt im Webbrowser Scratch-Projekte zu erstellen und abzuspielen.

Die Browserversion von Scratch bietet denselben Umfang wie die Desktop-Anwendung und kann auch verwendet werden, ohne einen Account zu registrieren.

Account registrieren

Die Registrierung eines Accounts bietet den Vorteil, dass Sie Ihre Projekte zusätzlich online speichern und veröffentlichen können. Mit einem Account für Lehrkräfte können Sie darüber hinaus Accounts für Ihre Schülerinnen und Schüler anlegen und in Klassen verwalten.

Mit einem Lehrkräfte-Account können Sie auch gemeinsam nutzbare Ressourcen-Pools (“Lager”) anlegen, über die Programmcode, Grafiken und Sound-Effekte innerhalb der Klassen ausgetauscht werden können, sowie “Studios”, in denen die Schülerinnen und Schüler ihre erstellten Projekte innerhalb der Klasse veröffentlichen können.

Weitere Informationen zum Anlegen und Nutzen eines Lehrkräfte-Accounts finden Sie auf der Scratch-Homepage unter https://scratch.mit.edu/educators.

Scratch-Projektdateien

Ein Scratch-Projekt kann über das Dateimenü der Scratch-Oberfläche lokal in einer Datei mit der Endung .sb3 gespeichert und daraus geladen werden. Eine Projektdatei enthält immer alle Skripte und Ressourcen (also Grafiken und Soundeffekte), die im Projekt verwendet werden.

In der browserbasierten Version von Scratch lassen sich Projekte auch online speichern, wenn Sie einen Account registriert und sich darin angemeldet haben.

Beachten Sie, dass wir in der Weiterbildung ausschließlich mit Version 3 von Scratch arbeiten werden. Projektdateien älterer Scratch-Versionen können mit Scratch 3 aber in der Regel auch geöffnet werden.


  1. siehe auch Peer Stechert: Kriterien zur Auswahl einer Programmiersprache – Bsp. Scratch aus der Reihe Informatikdidaktik kurz gefasst (Teil 31), Video bei YouTube ↩︎

  2. Beachten Sie dazu auch die Datenschutzbestimmungen von Scratch. ↩︎

1.2 Einstieg in Scratch

Zu Beginn werden Sie als Einstieg in die visuelle Programmierung die Handhabung der Scratch-Entwicklungsumgebung kennenlernen und erste kleine Projekte erstellen.

Dabei werden Sie die ersten grundlegenden Programmierkonzepte kennenlernen: Die Konstruktion von Programmen aus elementaren Anweisungen und Anweisungssequenzen, hier um Objekte zu steuern und Reaktionen auf bestimmte Ereignisse festzulegen.

Grundlagen

In einem Scratch-Projekt agieren Spielfiguren in einer 2D-Welt, die in Scratch als “Bühne” bezeichnet wird. Figuren werden durch Bilder dargestellt, ihr “Kostüm”. Das Verhalten und Aussehen der Figuren lässt sich mit Hilfe von kleinen Bausteinen steuern, den “Blöcken”. So lassen sich mit einzelnen Blöcken beispielsweise Figuren bewegen, das Bild einer Figur wechseln, Soundeffekte abspielen oder auf der Bildschirmfläche zeichnen, wobei jeder Block eine andere elementare Anweisung repräsentiert. Blöcke können wie Puzzleteile zu komplexeren Steuerungsvorschriften zusammengesetzt werden, den “Skripten”.

Um von der “Theater-Metapher”, die Scratch verwendet, zu abstrahieren, werden Figuren im Folgenden allgemeiner als “Objekte” bezeichnet. Statt “Bühnenbild” oder “Kostüm” werden die Bilder, die zur Darstellung von Objekten und Hintergrund verwendet werden, hier allgemeiner als “Grafik” bezeichnet.

Image

Die Benutzeroberfläche

Die Oberfläche der Entwicklungsumgebung von Scratch ist im Entwurfsmodus in die folgenden Bereiche aufgeteilt:

  • Über die obere Menüleiste können Sie Projekte laden und speichern (Menüpunkt “Datei”), die Sprache der Entwicklungsumgebung wählen (Symbol Icon) und Tutorialvideos zu verschiedenen Themen ansehen.

  • Auf der linken Seite befindet sich der Arbeitsbereich. In diesem Bereich wird das momentan ausgewählte Objekt (Figur oder Bühne) bearbeitet. Über die Reiter lässt sich zwischen den verschiedenen Bereichen für Skripte, Grafiken und Soundeffekte des Objekts wechseln.

  • Auf der rechten oberen Seite befindet sich das Vorschaufenster des erstellten Programms, das den Bühnenbereich und die darin befindlichen Figuren darstellt. Das Programm kann jederzeit in der Vorschau durch Klicken auf die grüne Fahne Icon ausgeführt werden. Mit einem Klick auf das Symbol Icon oben rechts wird in den Präsentationsmodus gewechselt, in dem das Programm im Vollbild ausgeführt werden kann.

  • Unterhalb des Vorschaufensters befindet sich die Objektliste mit allen im Projekt vorhandenen Objekten, also Figuren und Bühne. Wählen Sie hier ein Objekt per Mausklick aus, um es im Arbeitsbereich zu öffnen. Mit einem Rechtsklick auf eine Figur können Sie diese löschen oder eine Kopie erzeugen. Dabei werden auch alle Skripte, Grafiken und Soundeffekte der Figur kopiert.

Screenshot

Das Verhalten jedes Objekts kann individuell durch Skripte programmiert werden. Auch das Verhalten der Bühne kann durch Skripte gesteuert werden. Wählen Sie den Reiter Icon “Skripte” des Arbeitsbereichs, um den Skriptbereich der momentan ausgewählten Figur oder der Bühne zu öffnen.

Auf der linken Seite des Skriptbereichs finden Sie die Block-Bibliothek (auch als “Block-Palette” bezeichnet). Hier sind die Programmier-Bausteine oder “Blöcke”, aus denen Skripte zusammengestellt werden, in verschiedene, farblich unterschiedlich gekennzeichnete Kategorien aufgeteilt. Die Kategorien beziehen sich auf verschiedene Programmieraspekte, auf welche die entsprechenden Blöcke Einfluss haben, z. B. Bewegung von Objekten, Steuerung der Objektgrafik oder Soundeffekte.

Der mittlere Bereich enthält die Skripte, die zum ausgewählten Objekt gehören. Erstellen Sie Skripte, indem Sie Blöcke aus der Bibliothek mit der Maus in die Arbeitsfläche ziehen und miteinander kombinieren. Mit einem Rechtsklick auf einen einzelnen Block oder einen Verbund von Blöcken können Sie diesen löschen oder kopieren.

🎓 Erste Schritte

Öffnen Sie ein neues Scratch-Projekt. Zu Beginn befindet sich auf der Bühne ein einzelnes Objekt namens “Figur1” (das Scratch-Maskottchen), das ausgewählt ist.

Zunächst werden wir uns auf Anweisungen aus den ersten drei Kategorien konzentrieren:

“Bewegung” (blau)“Aussehen” (violett)“Klang” (pink)
Steuerung der Position und Drehung von Objekten, zum Beispiel:Steuerung der Grafik (“Kostüm”-Wahl, Grafikeffekte, Sichtbarkeit und Größe) und Textausgabe (“Sprechblasen”) für Objekte, zum Beispiel:Abspielen und Steuern von Soundeffekten von Objekten, zum Beispiel:
Block Block BlockBlock Block Block BlockBlock Block Block

Vollziehen Sie die folgenden Schritte nach, um einzelne Anweisungen auszutesten:

  • Ziehen Sie den folgenden Block aus der Bibliothek auf die Arbeitsfläche:
    Block
  • Wenn Sie einen Block in der Bibliothek oder auf der Arbeitsfläche anklicken, wird seine Aktion sofort ausgeführt: In diesem Fall wird das Objekt um 10 Einheiten entlang seiner (“Blick-")Richtung versetzt.
  • Die x- und y-Koordinaten des ausgewählten Objekts und sein Richtungswinkel werden über der Objektliste angezeigt. Die Initialwerte sind (0, 0) und 90° (“schaut” nach rechts).
  • Klicken Sie auf den Wert 10 im Block und geben Sie einen anderen Wert ein, um kleinere oder größere Schritte auszuführen (z. B. 5-er oder 20-er Schritte). Probieren Sie auch negative Werte aus.
  • Drehen Sie das Objekt mehrmals in 15°-Schritten im Uhrzeigersinn, indem Sie den folgenden Block auf die Arbeitsfläche ziehen und mehrmals anklicken:
    Block
  • Wenn Sie anschließend den Block “gehe Schritt” anklicken, stellen Sie fest, dass sich das Objekt nun in eine andere Richtung bewegt, nämlich entlang seiner neuen (“Blick-")Richtung.
  • Ziehen Sie einen weiteren Block aus der Kategorie “Aussehen” auf die Arbeitsfläche:
    Block
    Dieser Block bewirkt, dass die Grafik des Objekts sich ändert: Es wird die nächste Grafik in der “Kostüm”-Liste der Figur ausgewählt. Welche Grafiken das Objekt momentan besitzt, sehen Sie, wenn Sie über den Reiter “Kostüm” in den Grafik-Arbeitsbereich wechseln.
  • Auf diese Weise können Sie weitere einzelne Anweisungen austesten, beispielsweise auch das Abspielen von Soundeffekten des Objekts:
    Block
  • Wählen Sie nun einen Block und verschieben Sie ihn mit gedrückter Maustaste an den unteren Rand eines anderen Blocks, so dass sich die Blöcke verbinden. Wenn Sie nun auf den verbundenen Blockstapel klicken, werden Sie nacheinander ausgeführt.
  • So können beliebig lange Anweisungsfolgen (Sequenzen) erstellt werden. Fügen Sie testhalber mehrere Blöcke zusammen, die eine kurze Animationssequenz umsetzen.
  • Speichern Sie das Projekt nun als Datei auf Ihrem Rechner (z. B. als Erste_Schritte.sb3), indem Sie im “Datei”-Menü die Option “Auf deinen Computer herunterladen” wählen.

🎓 Projekt erkunden

Ein kleines Beispielprojekt Unterwasserwelt.sb3 zum eigenen Erkunden können Sie hier herunterladen: Download

  • Öffnen Sie das Projekt es in Scratch, indem Sie im “Datei”-Menü die Option “Von deinem Computer hochladen” wählen.
  • Starten Sie das Programm, indem Sie erst auf das Symbol Icon oben rechts und anschließend auf das Symbol Icon klicken, und erkunden Sie es.
  • Kehren Sie anschließend durch Klicken auf das Symbol Icon zur Entwurfsmodus zurück. Versuchen Sie intuitiv anhand der Bausteine im Arbeitsbereich nachzuvollziehen, wie sich die Figuren verhalten und auf welche Eingaben sie reagieren.
  • Klicken Sie auf die Figurensymbole im Bereich unter der Bühne, um die Skripte der verschiedenen Figuren anzuzeigen. Auch die Bühne besitzt eigene Skripte.
  • Falls Ihnen dabei Interaktionsmöglichkeiten auffallen, die Sie im laufenden Programm noch nicht entdeckt haben, starten Sie das Programm erneut und testen Sie diese.

Figuren und Bühne

Mit Hilfe der oben eingeführten Anweisungen kann das Verhalten und die Darstellung der Objekte gesteuert werden, also etwa ihre Position oder ihr Bild auf der Bühne.

Abstrakter gesprochen besitzt jedes Objekt verschiedene Attribute, also Eigenschaften, deren Werte sich durch ihre Skripte ändern lassen. Der Zustand eines Objekts zu einem bestimmten Zeitpunkt ist im Wesentlichen durch die Werte seiner Attribute definiert.

Daneben besitzt jedes Objekt einen Namen, durch den es identifiziert wird, eigene Skripte, durch die es gesteuert wird, sowie eigene Grafiken (“Kostüme”) und Soundeffekte, die in seinen Skripten verwendet werden können.

Image

Attribute der Objekte

Objekte in Scratch haben größtenteils Attribute, die bestimmen, wie die Objekte auf der Bühne dargestellt werden. Die wichtigsten Attribute sind dabei:

  • Die Position der Figur wird durch ihre x- und y-Koordinaten auf der Bühne beschrieben (-240 bis 240 für x, -180 bis 180 für y).
  • Die Richtung der Figur wird durch einen Winkel in Grad beschrieben (-180° bis 180°).
  • Das Attribut Kostüm-Nummer gibt an, welche ihrer Grafiken für die Figur momentan angezeigt wird.
  • Das Attribut Größe der Figur legt die Skalierung ihrer Darstellung in Prozent fest (100 für normale Größe).
  • Die Sichtbarkeit der Figur legt fest, ob die Figur auf der Bühne angezeigt wird oder nicht.
  • Das Attribut Lautstärke der Figur legt in Prozent fest, wie laut die Soundeffekte der Figur abgespielt werden (100 für normale Lautstärke).

Die Bühne hat dagegen nur die Attribute Bühnenbild-Nummer (entspricht der Kostüm-Nummer der Figuren) und Lautstärke.

Image Image Image

Eine Figur kann sichtbar oder unsichtbar sein. Unsichtbare Objekte werden nicht auf der Bühne angezeigt und sie können nicht angeklickt werden.

Daneben lassen sich noch über die “Effekt”-Blöcke aus der Kategorie “Aussehen” verschiedene Grafikeffekte für jede Figur und die Bühne einstellen, z. B. Transparenz, Helligkeit oder Farbverschiebung. Außerdem hat jede Figur einen Drehtyp, der festlegt, wie sich ihre Richtung auf die Darstellung auswirkt (siehe Koordinatensystem).

Die Werte der wichtigsten Attribute (Position, Richtung, Größe und Sichtbarkeit) werden im Attributfenster unterhalb der Bühne für die momentan ausgewählte Figur angezeigt und können dort auch im Entwurfsmodus manuell geändert werden.

Screenshot

Dort können Sie auch einen eindeutigen Namen für jedes Objekt festlegen. Über diesen Namen wird das Objekt in Skripten identifiziert, siehe z. B. die Auswahlmöglichkeiten im Block “gleite zu …”:
Block

Koordinatensystem

Alle Positionen auf der Bühne werden durch ihre x- und y-Koordinaten in einem kartesischen Koordinatensystem beschrieben. Der Punkt (0, 0) befindet sich dabei im Mittelpunkt. Die x-Koordinaten (horizontal) reichen von -240 (links) bis 240 (rechts), die y-Koordinaten von -180 (unten) bis 180 (oben). Richtungen werden im Uhrzeigersinn angegeben, wobei 0° den Richtungspfeil parallel zur y-Achse (nach oben) angibt. 90° entspricht also dem Richtungpfeil nach rechts, -90° nach links und 180° (bzw. -180°) nach unten.

Die Position eines Objekts bezieht sich immer auf ihren Drehpunkt (bei den in der Figurenbibliothek vorhandenen Objekten meistens der Mittelpunkt der Grafik). Um diesen Punkt dreht sich das Objekt auch, wenn seine Richtung geändert wird. Öffnen Sie das “Kostüm” eines Objekts im Arbeitsbereich, um zu sehen, wo sein Drehpunkt liegt.

Screenshot

Die Grafik einer Figur wird üblicherweise um ihre Richtung gedreht dargestellt. Für jede Figur kann aber auch ein anderes Verhalten (ein anderer “Drehtyp”) festgelegt werden:

  • Das Standardverhalten (Grafik drehen) stellt der Drehtyp “rundherum” dar. Diese Darstellung eignet sich besonders für Objekte in der Draufsicht.
  • Der Drehtyp “links-rechts” spiegelt die Figur bei Richtungen < 0° und stellt Sie sonst normal dar, was sich beispielsweise für Figuren in der Seitenansicht eignet.
  • Bei “nicht drehen” wird die Figur immer ungedreht dargestellt, unabhängig von ihrer momentanen Richtung.
  • Der Drehtyp kann manuell im Attributfenster festgelegt werden (auf den Wert von “Richtung” klicken) oder über einen Anweisungsblock in einem Skript geändert werden:
    Block

Auch die Position des Mauszeigers wird in den Koordinaten der Bühne gemessen und kann mit bestimmten Blöcken benutzt werden, z. B. um eine Figur auf die Position des Mauszeigers zu setzen oder in Richtung des Mauszeigers zu drehen:

Block Block

Grafiken

Jedes Objekt besitzt eine oder mehrere eigene Grafiken, die zur Darstellung des Objekts verwendet werden können. Bei Figuren werden diese Grafiken in Scratch “Kostüme” genannt, für die Bühne stellen sie die Hintergrundbilder dar. Mit den Anweisungsblöcken aus der Kategorie “Aussehen” kann in einem Skript die Grafik des Objekts, zu dem das Skript gehört, gewechselt werden. Wählen Sie den Reiter Icon “Kostüme”, um in den Grafik-Arbeitsbereich zu wechseln.

Hier sehen Sie links die Liste der Grafiken, die zur momentan ausgewählten Figur gehören (bzw. die Hintergrundbilder, die zur Bühne gehören). Klicken Sie auf eine Grafik, um Sie in der Zeichenfläche zu öffnen und zu bearbeiten. Je nachdem, ob es sich um eine Rastergrafik oder eine Vektorgrafik handelt, stehen verschiedene Werkzeuge zum Zeichnen zur Verfügung. Im Vektorgrafikmodus können Sie einfache geometrische Formen erstellen und nachträglich durch Verschieben einzelner Punkte verformen. Die Zeichenwerkzeuge ähneln denen bekannter Grafikprogramme wie Office Draw, Inkscape, GIMP oder Photoshop, sind aber deutlich reduzierter.
Über das Symbol Icon bzw. Icon unten links können Sie weitere Grafiken hinzufügen. Es erscheint eine Menüleiste, in der Sie auswählen können, ob Sie eine Grafik aus der Bildersammlung von Scratch wählen, eine neue Grafik auf der Zeichenfläche erstellen oder eine Bilddatei von Ihrem Rechner hochladen möchten. Scratch erkennt die gängigsten Bildformate BMP, PNG, JPEG und GIF (GIF-Animationen werden als Bildsequenzen importiert), sowie das Vektorgrafikformat SVG.

Screenshot

Da die Werkzeuge zur Bildbearbeitung in Scratch eher rudimentär sind, kann es hilfreich sein, Grafiken mit einem komfortableren Tool zu erstellen (z. B. GIMP, Inkscape) oder aus einer Online-Sammlung herunterzuladen und in Scratch zu importieren. Umgekehrt können auch Bilder aus Scratch heraus in gängigen Formaten exportiert werden (PNG, SVG).

Soundeffekte

Neben Grafiken kann jedes Objekt auch seine eigenen Soundeffekte besitzen, die in seinen Skripten mit Anweisungsblöcken aus der Kategorie “Klang” abgespielt werden können. Wählen Sie den Reiter Icon “Klänge”, um zum Sound-Arbeitsbereich zu wechseln.

Ähnlich wie im Grafik-Arbeitsbereich sehen Sie links die Liste der Soundeffekte, die zur momentan ausgewählten Figur oder der Bühne gehören. Wählen Sie einen Soundeffekt per Mausklick aus, um ihn zu bearbeiten. Dazu stehen einfache Werkzeuge wie Beschneiden, Ändern der Geschwindigkeit, Lautstärke und Ein-/Ausblenden zur Verfügung.
Über das Symbol Icon unten links können Sie weitere Soundeffekte hinzufügen (aus der Soundsammlung von Scratch auswählen, eine Audiodatei von Ihrem Rechner hochladen oder mit dem Mikrofon aufnehmen).

Screenshot

Da Soundeffekte auf Dauer störend sein können, ist es empfehlenswert, sie nur sparsam bzw. möglichst nur mit vorhandenen Kopfhörern einzusetzen.

🎓 Übung

  • Öffnen Sie Ihr Scratch-Projekt aus Erste Schritte und benennen Sie das Objekt, das momentan “Figur1” heißt, sinnvoller um (z. B. “Scratch”).
  • Der Bühnenhintergrund ist momentan noch leer. Fügen Sie ein Szenenbild zur Bühne hinzu und wählen Sie ein Bild aus der Sammlung aus. Das leere Bühnenbild kann gelöscht werden.
  • Fügen Sie der Figur eine neue Grafik für eine Sprunganimation hinzu. Wählen Sie aus der Bildersammlung “Cat Flying-b” (benennen Sie die Grafiken in der Grafikliste des Objekts ggf. um, z. B. “Sprung”).
  • Nun soll als weiteres Objekt ein Ballon hinzugefügt werden. Wählen Sie ein passendes Objekt aus der Figurensammlung von Scratch und positionieren Sie den Ballon auf der Bühne.
  • Als Nächstes soll dem Ballon ein Gesicht verpasst werden. Öffnen Sie dazu die Grafik des Ballons im Zeichenbereich und bearbeiten Sie sie. Alternativ können Sie auch das Gesicht der Katze kopieren (im Zeichenbereich öffnen, Elemente auswählen und mit Icon kopieren) und in die Grafik des Ballons einfügen.

Skripte und Ereignisse

Anweisungen und Sequenzen

Anweisungen sind – wie wir bereits kennengelernt haben – elementare, eindeutige Befehle, mit denen sich beispielsweise Objekte steuern lassen. Blöcke, die eine Anweisung repräsentieren (also z. B. “bewege dich”, “ändere deine Grafik”), haben in Scratch die Form eines Puzzleteils, an das oben und unten angelegt werden kann (“Stapelblockform”):

Block

Diese Blöcke können durch vertikales Aneinanderhängen zu Sequenzen verbunden (“gestapelt”) werden. Solche Blockstapel werden immer im Verbund von oben nach unten Anweisung für Anweisung (sequenziell) ausgeführt. Sequenzen stellen die einfachste Form von Programmen dar.

Block

Beachten Sie, dass einige Anweisungen eine bestimmte Dauer zur Ausführung benötigen und während dieser Zeit der Ablauf der Sequenz pausiert wird. Vergleichen Sie dazu, wie sich der Ablauf ändert, wenn Sie an den Beginn einer Sequenz einmal den Block “sage …” und einmal den Block “sage … für … Sekunden” setzen:

Block

Dasselbe gilt beispielsweise auch für Anweisungen wie “gleite” (eine kontinuierliche Bewegung über einen bestimmten Zeitraum), “spiele Klang ganz” (wartet bis der Soundeffekt zuende abgespielt wurde) oder die einfache “warte”-Anweisung aus der Kategorie “Steuerung”:

Block Block Block

Parameter und Werte

Bei den bisher vorgestellten Anweisungen hängt das Verhalten meistens von einem oder mehreren Werten ab, die in die ovalen Eingabefelder des Blocks eingetragen werden, z. B. um wie viel Grad ein Objekt bei einem “drehe dich”-Block gedreht werden soll oder was für wie viele Sekunden bei einem “sage”-Block angezeigt werden soll. Solche Werte werden im Allgemeinen als Parameterwerte (oder auch Argumente) der Anweisung bezeichnet. In Scratch können Parameterwerte direkt eingetragen werden oder aus speziellen Blöcken, den “Werteblöcken” abgefragt werden.

Block

Ein Werteblock wird in Scratch durch einen ovalen Block dargestellt:

Block

Werteblöcke können für alle Parameter von Anweisungen verwendet werden, indem sie in den entsprechenden ovalen Eingabefeldern des Anweisungsblocks platziert werden. Wird der Anweisungsblock ausgeführt, so wird zunächst der aktuelle Wert des Werteblocks abgefragt und für den entsprechenden Parameter der Anweisung verwendet. Sie können den aktuellen Wert eines Werteblocks im Entwurfsmodus auch jederzeit manuell überprüfen, indem Sie ihn in der Block-Bibliothek einfach anklicken.

Das folgende Beispiel lässt ein Objekt seinen aktuellen Richtungswinkel in einer Sprechblase anzeigen:

Block Block

Für jedes Attribut eines Objekts gibt es einen speziellen Werteblock in der entsprechenden Kategorie, z. B. Werteblöcke für die x- und y-Koordinate und den Richtungswinkel in der Kategorie “Bewegung”:

Block Block Block

Daneben gibt es einen speziellen Block in der Kategorie “Fühlen” (türkis), über den jedes Attribut eines beliebigen Objekts oder der Bühne abgefragt werden kann. Über die beiden Auswahllisten ▾ wird das gewünschte Objekt und Attribut ausgewählt, hier beispielsweise die Nummer des momentan ausgewählten Hintergrundbilds der Bühne oder die y-Koordinate des Objekts “Figur1”:

Block Block

Neben Werteblöcken zum Abfragen von Objekt-Attributen gibt es weitere Blöcke, die globale Werte messen, etwa die Position des Mauszeigers, die Lautstärke, die das Mikrofon gerade misst, oder die Anzahl an Sekunden, die von der in Scratch integrierten Stoppuhr bisher gezählt wurden. Auch diese Blöcke befinden sich in der Kategorie “Fühlen”:

Block Block Block Block

Image Die Werte der wichtigsten Attribute können auch live auf der Bühne angezeigt werden. Suchen Sie dazu den entsprechenden Werteblock der Form Block in der Block-Bibliothek der Figur oder Bühne und kreuzen Sie das Kästchen links davon an.
Dadurch können Sie während der Programmausführung überprüfen, welche Werte die Attribute zu jedem Zeitpunkt haben und in welchen Situationen sich die Werte ändern. Diese Information kann hilfreich sein, um das Programmverhalten nachzuvollziehen und durch Abweichungen vom erwarteten Verhalten Fehler zu finden.

Ereignisse

Bisher haben wir Anweisungen direkt in der Arbeitsfläche oder in der Block-Bibliothek durch Anklicken ausgeführt, um ihr Verhalten zu untersuchen. Dieses Ausprobieren ist nur im Entwurfsmodus möglich. Um ein “echtes” Programm zu erstellen (das im Präsentationsmodus vernünftig genutzt werden kann), muss festgelegt werden, durch welches Ereignis ein Skript automatisch ausgelöst werden soll.

Wie oben erwähnt, werden Programme in Scratch üblicherweise durch Anklicken der grünen Fahne gestartet. Um festzulegen, dass ein Skript durch dieses Ereignis automatisch ausgeführt wird, wählen Sie die Kategorie “Ereignisse” in der Block-Bibliothek und ziehen Sie den Block “Wenn Icon angeklickt wird” in den Arbeitsbereich:

Block

Hängen Sie nun mehrere Anweisungsblöcke an diesen Blöck an und beobachten Sie, was passiert, wenn Sie die grüne Fahne anklicken: Die Anweisungssequenz wird ausgeführt (auch im Präsentationsmodus).

Um Skripte für verschiedene Ergebnisse (beispielsweise Eingaben über Tastatur oder Maus) zu definieren, stellt Scratch neben dem “Startereignis”-Block noch weitere Blöcke bereit. Solche Ereignisblöcke stehen immer am Anfang eines Skripts und haben in Scratch die Form eines Puzzleteils, das oben gewölbt ist (“Kopfblockform”):

Block

Sobald das entsprechende Ereignis eintritt, wird die Anweisungssequenz, die an den Ereignisblock angehängt ist, sofort abgearbeitet – unabhängig davon, in welchem Zustand sich das Programm gerade befindet. Dabei können sogar beliebig viele Skripte zur gleichen Zeit ausgeführt werden, also parallel nebeneinander laufen! Im Folgenden werden die wichtigsten Ereignisblöcke beschrieben, mit denen sich interaktive Anwendungen umsetzen lassen.

Block

Verhalten beim Programmstart

Script

Das angehängte Skript wird ausgeführt, wenn auf die grüne Fahne Icon geklickt wird. Das Anklicken der grünen Fahne wird üblicherweise für den Programm- bzw. Spielstart genutzt.

Dieses Ereignis wird oft verwendet, um die Objekte und die Welt als Erstes zu initialisieren, also in einen gewünschten Anfangszustand zu bringen. Beispielsweise kann für jedes Objekt auf der Bühne ein Skript für das Startereignis festgelegt werden, das es auf seine Startposition setzt, seine initiale Grafik, Richtung und Sichtbarkeit festlegt. Wenn das Objekt zu Beginn noch nicht gleich sichtbar sein soll, kann das durch einen “verstecke dich”-Block im Startskript erreicht werden.

Anschließend können weitere Anweisungen folgen, die dafür sorgen, dass das Objekt beim Programmstart sofort anfängt, etwas zu machen – beispielsweise einen Begrüßungstext anzeigen, Hintergrundmusik abspielen oder eine Animationssequenz starten: Das folgende Skript sorgt beispielsweise beim Programmstart dafür, dass das Hintergrundbild “Titelbild” angezeigt wird und eine Titelmelodie abgespielt wird:

Script

Reaktion auf Tastatureingabe

Script

Das angehängte Skript wird immer dann ausgeführt, sobald eine bestimmte Taste auf der Tastatur gedrückt wird. Die Taste kann über die Auswahlliste ▾ festgelegt werden.

Dieses Ereignis kann zum Beispiel verwendet werden, um eine Figur mit den Pfeiltasten der Tastatur auf der Bühne zu bewegen. Das oben stehende Beispiel bewirkt, dass die Figur bei jedem Drücken der Pfeiltasten nach links oder rechts gedreht und um 10 Einheiten entlang seiner Blickrichtung bewegt wird.

Das folgende Beispiel lässt die Figur beim Drücken der Pfeiltaste kurz nach oben “springen”, wobei eine andere Grafik angezeigt wird, welche die Figur springend zeigt:

Script

Im Gegensatz zum ersten Beispiel benötigt dieses Skript eine bestimmte Zeit, bis es zuende ausgeführt wird, da eine “warte”-Anweisung verwendet wird (in diesem Fall 0.4 Sekunden).

Reaktion auf Mausklick

Script

Das angehängte Skript wird immer dann ausgeführt, sobald mit der Maus auf die Figur geklickt wird, zu der das Skript gehört. Ist das Skript für die Bühne definiert, wird es durch jeden Mausklick in die Bühnenfläche gestartet. Es spielt dabei keine Rolle, welche Maustaste gedrückt wird.

Das Ereignis lässt sich wie das vorige verwenden, um Interaktionsmöglichkeiten zwischen Menschen und dem Programm zu realisieren. Im oben stehenden Beispiel wird als Reaktion auf das Anklicken der Figur, zu der das Skript gehört, ein Soundeffekt abgespielt und eine Mitteilung in Form einer Sprechblase angezeigt.

Verhalten beim Szenenwechsel

Script

Das angehängte Skript wird ausgeführt, sobald das Hintergrundbild der Bühne zu dem angegebenen Bild wechselt. Das Bild kann über die Auswahlliste ▾ festgelegt werden.

Durch verschiedene Bühnenbilder werden oft verschiedene Abschnitte des Programms repräsentiert. Dadurch lassen sich insbesondere umfangreichere Programme besser strukturieren. In einem Animationsfilm können das beispielsweise verschiedene Szenen oder in einem Spiel verschiedene Spielabschnitte (“Level”) sein, sowie Titelbild und Spielende (“Game Over”) oder Abspann.

Dafür ist es hilfreich, bei jedem Wechsel zu einem neuen Abschnitt die Objekte geeignet zu (re-)initialisieren, ähnlich wie beim Programmstart. Auf einem Titelbild oder “Game Over”-Bildschirm sollten die Spielfiguren in der Regel nicht sichtbar sein, während Sie beim Wechsel zu einem Spielabschnitt auf ihre entsprechenden Startpositionen gesetzt werden müssen.

Das oben dargestellte Beispiel lässt ein Objekt beim Wechsel zur zweiten Szene “von der Bühne abtreten”, während es beim Wechsel zurück zur ersten Szene wieder sichtbar wird.

Image

Zur Umsetzung interaktiver Anwendungen mit Scratch bietet es sich an, von den Ereignissen bzw. Eingaben aus zu denken, also: “Was soll passieren, sobald …?” bzw. “Wie soll Objekt X reagieren, sobald …?”. Eine komplexere Aufgabe wird also gedanklich ereignisbasiert in kleinere Teilaufgaben zerlegt.
Die “Start”- und “Szenenwechsel”-Ereignisse können verwendet werden, um den gewünschten Initialzustand der Objekte und der Welt herzustellen und gegebenenfalls einen automatischen Ablauf für die Szene zu starten. Mit den Ereignissen “Figur angeklickt” und “Taste gedrückt” kann dagegen auf Eingaben reagiert werden.

🎓 Übung

  • Öffnen Sie Ihr Scratch-Projekt aus Erste Schritte und fügen Sie die oben dargestellten Skripte zur Figur “Scratch” hinzu. Alternativ können Sie das Projekt Erste_Schritte.sb3 auch hier herunterladen: Download
  • Passen Sie die beiden Skripte zum Gehen nach links/rechts so an, dass bei jedem Schritt eine Animationsequenz der Form “gehe Schritt”, “wechsle zu Kostüm 2”, “warte”, “gehe Schritt”, “wechsle zu Kostüm 1”, “warte” abgespielt wird.
  • Die Sprunganimation der Figur “Scratch” ist momentan etwas ruckartig. Ersetzen Sie die Anweisungen “setze y auf” und “warte” durch die Anweisung “gleite zu Position”, um eine kontinuierliche Auf- und Abwärtsbewegung umzusetzen.
  • Fügen Sie der Figur “Ballon” ein Skript hinzu, das ihn beim Anklicken zu einer zufälligen Position gleiten lässt.
  • Prüfen Sie, ob der Zustand der Figuren bei jedem Programmstart immer gleich ist. Überprüfen Sie dazu, welche Attribute der Figuren durch Eingaben geändert werden können. Korrigieren Sie die Skripte zum Initialisieren der Figuren, falls nötig.
  • Erweitern Sie das Projekt nun um ein Bühnenbild, das ein Titelbild darstellt. Zwischen Titelbild und Szenenbild soll beim Drücken der Leertaste gewechselt werden. Beim Programmstart soll die Bühne immer das Titelbild darstellen.
  • Passen Sie das Projekt nun so an, dass alle Figuren nur auf dem Szenenbild sichtbar sind, aber nicht auf dem Titelbild. Verwenden Sie dazu den Ereignisblock “Wenn das Bühnenbild wechselt”.

Parallele Skriptabläufe

Wie oben erwähnt können in Scratch quasi beliebig viele Skripte verschiedener Objekte oder auch desselben Objekts quasi gleichzeitig ausgeführt werden. Das spielt insbesondere bei Skripten, die eine gewisse Ausführungsdauer haben, eine Rolle.

Zur Veranschaulichung betrachten wir noch einmal das “Sprung”-Beispiel von oben:

Script

Dieses Skript benötigt 0.4 Sekunden zur Ausführung, da es eine “warte”-Anweisung enthält. Während das Skript läuft, können aber andere Skripte des Objekts zeitlich parallel zu diesem Skript ausgeführt werden. Wird etwa kurz nach der Pfeiltaste nach oben die Pfeiltaste nach rechts bewegt, bewegt sich die Figur in der Luft einen Schritt nach rechts, bevor sie wieder auf den Boden fällt.

Um parallele Skriptabläufe examplarisch zu untersuchen, können diese grafisch in Form eines Balkendiagramms über eine Zeitachse aufgetragen werden. Die Länge der Balken entspricht dabei der Dauer der Skriptabläufe, ihre Startpositionen sind durch eine vorgegebene Beispielsequenz von Ereignissen bzw. Eingaben gegeben:

Diagram

Parallele Skriptabläufe können problematisch sein, wenn verschiedene Skripte dieselben Attribute eines Objekts ändern und sich damit gegenseitig in die Quere kommen. Diese Problematik sollte bei der ereignisorientierten Programmentwicklung im Hinterkopf behalten werden.

Scratch-Erweiterungen

Für Scratch sind verschiedene ergänzende Block-Kategorien vorhanden, die zu Beginn nicht sichtbar sind, und Scratch um Multimedia-Funktionen, Kommunikation mit externer Hardware und Online-Diensten erweitert. Die Erweiterungen lassen sich über das Symbol Icon unten links auswählen.

Viele dieser Erweiterungen stellen größtenteils neue Anweisungs- und Werteblöcke bereit, deren Verhalten sich durch Ausprobieren und Intuition schnell erschließt. Für den Einstieg eignen sich hier besonders die folgenden Erweiterungen:

IconDie Erweiterung “Malstift” stellt Anweisungen zum Zeichnen auf der Bühne bereit. Figuren können damit als Zeichenstifte fungieren, ähnlich den sogenannten Turtle-Grafiken: Wenn die Anweisung “schalte Stift ein” ausgeführt wird, hinterlässt die Figur eine Zeichenspur während sie sich über die Bühne bewegt, bis der Stift wieder abgeschaltet wird. Über weitere Anweisungen kann die Stiftfarbe und -dicke zum Zeichnen gewählt werden.
IconDie Erweiterung “Musik” enthält Anweisungen zum Abspielen verschiedener Instrumente und Schlaginstrumente und eignet sich besser zum Musizieren als die “Klang”-Anweisungen.
IconDie Erweiterung “Text zu Sprache” stellt Anweisungen bereit, um Texte mit verschiedenen Stimmen vorlesen zu lassen, statt sie über Sprechblasen anzuzeigen. Diese Erweiterung basiert auf Amazon Web Services und benötigt eine laufende Internetverbindung.
IconDie Erweiterung “Übersetzen” stellt einen Werteblock zum Übersetzen von Wörtern in verschiedene Sprachen zur Verfügung. Auch diese Erweiterung basiert auf Amazon Web Services.

Fachkonzepte

In diesem Einstieg in die visuelle Programmierung mit Scratch haben wir bereits eine ganze Reihe von Programmierkonzepten kennengelernt, die von modernen Programmiersprachen unterstützt werden. Im Folgenden werden diese Konzepte und ihre Fachbegriffe kurz rekapituliert.

Imperative Programmierung

Als imperative Programmierung wird in der Informatik das Konzept bezeichnet, nach dem ein Programm aus einer klar definierten Abfolge von Handlungsanweisungen besteht. Im einfachsten Fall listet ein Programm Anweisungen auf, die in der angegebenen Reihenfolge nacheinander vom Computer abgearbeitet werden. Eine solche lineare Aneinanderreihung von Anweisungen wird als Sequenz bezeichnet, zum Beispiel:

Diagram

Eine Anweisung (auch Befehl, Kommando oder Instruktion genannt) stellt eine einzelne Handlungsvorschrift dar, die je nach Programmiersprache oder programmiertem System unterschiedlich sein kann. Dazu gehören unter anderem das Ändern der Attributwerte von Objekten (in Scratch zum Beispiel das Ändern der Position einer Figur oder Wechsel zum nächsten Kostüm) oder visuelle und akustische Ausgaben (in Scratch zum Beispiel die Anzeige einer Sprechblase oder das Abspielen eines Soundeffekts). Anweisungen können über Parameter weitere Informationen zu ihrer Ausführung übergeben bekommen.

Um die Abfolge der Anweisungen zu steuern, also beispielweise Anweisungen wiederholt oder in Abhängigkeit von bestimmten Bedingungen auszuführen, werden in der imperativen Programmierung sogenannte Kontrollstrukturen verwendet, die wir in der nächsten Lektion kennenlernen werden.

Ereignisorientierte Programmierung

Das Konzept zu programmieren, indem für bestimmte Ereignisse festgelegt wird, welche Anweisungen beim Eintreten des Ereignisses ausgeführt werden sollen, wird in der Informatik als ereignisorientierte Programmierung bezeichnet. Dieses Programmierkonzept eignet sich besonders gut, um interaktive Systeme zu programmieren, also solche, in denen das System auf Eingaben oder Ereignisse reagieren muss, die zu beliebigen Zeitpunkten auftreten können (asynchrone Ereignisse).

Besonders in der Entwicklung von grafischen Benutzeroberflächen (GUIs) und Webanwendungen hat sich dieses Konzept bewährt. Hier stellen die Ereignisse meist Aktionen von Menschen dar, die mit der GUI interagieren, beispielsweise durch eine Eingabe in ein Textfeld oder einen Mausklick auf eine Schaltfläche.

Scratch kombiniert also, soweit wir bisher gesehen haben, die Konzepte der imperativen Programmierung und der ereignisorientierten Programmierung: Jedes einzelne Skript besteht aus einer Folge von Anweisungen, die nacheinander abgearbeitet werden. Ein Skript startet dabei immer dann, sobald das als Einstiegspunkt für das Skript festgelegte Ereignis eintritt (repräsentiert durch den gelben Ereignis-Block am Kopf eines Skripts).

Nebenläufigkeit

Insbesondere für die ereignisorientierte Programmierung ist entscheidend, dass das System, auf dem das Programm ausgeführt wird, in der Lage ist, mehrere Programmteile quasi gleichzeitig, also zeitlich parallel abarbeiten zu können, damit es nicht zu Verzögerungen kommt, wenn mehrere Ereignisse auf einmal eintreten, auf die reagiert werden muss. Dieses Konzept wird in der Informatik als Nebenläufigkeit bezeichnet.

Scratch unterstützt parallele Programmausführung: Skripte verschiedener Objekte, die auf dasselbe Ereignis reagieren, werden (scheinbar) gleichzeitig ausgeführt.

Objekte, Attribute und Methoden

Die Figuren und die Bühne in Scratch stellen Objekte im Sinne der Programmierung dar. Sie haben eine Identität, einen Zustand und ein Verhalten. Der Zustand eines Objekts ist durch die Werte seiner Attribute definiert (z. B. Position, Größe). Das Verhalten der Objekte ist durch ihre Skripte definiert. Skripte sind hier also (Teil-)Programme, die zu jeweils einem Objekt gehören. Solche (Teil-)Programme werden auch als Methoden des Objekts bezeichnet.

1.2.1 Übungsaufgaben

Stoffwiederholung

Aufgabe 1: Anweisungen erläutern

Beantworten Sie die folgenden Fragen zu den dargestellten Anweisungen, die jeweils zum Skript des Objekts “Figur1” gehören.
Testen Sie die Anweisungen in Scratch aus, wenn Sie sich nicht sicher sind.

Anweisung/Sequenz
BlockWelche Attribute werden geändert? Von welchen weiteren Attributen hängt der Effekt ab?
Zusatzfrage: Welchen Effekt hätte diese Anweisung, wenn ein negativer Parameterwert (z. B. -50) angegeben wird?
BlockWelchen Effekt hat diese Anweisung?
Block“Ballon” ist hier ein anderes Objekt auf der Bühne. Welches Objekt bewegt sich hier? Wovon hängt die Bewegungsgeschwindigkeit des Objekts ab?
BlockFür welche Attribute ändern sich hier die Werte?
BlockWelchen Effekt hat diese Anweisung, wenn gerade das letzte Kostüm der Figur ausgewählt ist?
Was passiert, wenn die Figur nur ein Kostüm hat?
BlockAngenommen, die Grafik der Figur ist 80 mal 80 Pixel groß. Welche Größe hat sie nach Ausführung dieser Sequenz?
BlockWelchen Effekt hat diese Sequenz?

Aufgabe 2: Skripte vergleichen

Vergleichen Sie jeweils das linke und das rechte Skript miteinander und ermitteln Sie jeweils, worin sich die Programmausführung in beiden Versionen unterscheidet. Beantworten Sie dazu die folgenden Fragen.

Skript Version 1Skript Version 2
SkriptSkriptAngenommen, die Figur befindet sich momentan an Position (100, 100). An welcher Position befindet sie sich jeweils, nachdem die Pfeiltaste gedrückt wurde?
SkriptSkriptAngenommen, die Figur befindet sich vor dem Programmstart an Position (0, 0). An welcher Position befindet sie sich jeweils 1 Sekunde nach Programmstart?
SkriptSkriptAngenommen, der Sound “Jingle” dauert 5 Sekunden. In welchem Zeitraum nach Anklicken der Figur wird der Sound jeweils gespielt, in welchem Zeitraum wird die Mitteilung angezeigt?
SkriptSkriptIn welchem Zeitraum nach Anklicken der Figur bewegt sie sich jeweils, in welchem Zeitraum wird die Mitteilung angezeigt?

Aufgabe 3: Programmablauf untersuchen

In dieser Aufgabe soll der zeitliche Ablauf von Skripten eines Programms bei bestimmten Eingaben nachvollzogen und grafisch dargestellt werden.

Laden Sie dazu die Projektdatei Unterwasserwelt.sb3 aus der Einführung herunter und öffnen Sie das Projekt in Scratch: Download

Das folgende Diagramm zeigt eine vertikale Zeitachse, in der die Zeitpunkte markiert sind, zu denen bestimmte Eingaben auftreten ( Download als PDF).

Diagram

Ablauf skizzieren

Markieren Sie die Zeiträume, in denen Skripte ausgeführt werden, durch Balken in den Spalten der betreffenden Objekte. Beachten Sie dabei, dass ggf. auch mehrere Skripte eines Objekts gleichzeitig ausgeführt werden können. Kennzeichnen Sie dabei jedes Skript durch einen separaten Balken im Zeitdiagramm. Die zeitlichen Abläufe von Skripten der Figur “Fisch3” sind hier zur Orientierung bereits eingezeichnet.

Ablauf analysieren

Starten Sie das Programm und klicken Sie die Figur “Fisch2” wiederholt in schneller Folge an. Erklären Sie das beobachtete Programmverhalten. Welcher Sonderfall lässt sich daraus über die Ausführung von Skripten in Scratch ableiten?

Praktische Übungen

Animationssequenzen

Als erster Einstieg in Scratch werden oft kurze Filme, Dialoge oder Animationen erstellt, die zunächst größtenteils ohne Interaktion und mit rein sequenziellen Abläufen auskommen. So können sich die Schülerinnen und Schüler mit der Entwicklungsumgebung in kreativer Weise vertraut machen und die grundlegende Steuerung der Figuren, die Konzepte der Anweisungssequenzen und Ereignisse (Start und Szenenwechsel), sowie das Timing von Abläufen mittels Warteanweisungen und Ereignissen kennenlernen.

Als Vorlage für die Erstellung eines solchen Projekts kann zunächst ein Drehbuch (“Storyboard”) für den Ablauf entworfen werden, in dem die Aktionen aller Objekte in einer Bildsequenz (siehe Aufgabe Animationssequenz nach Drehbuch erstellen), tabellarisch oder über einer Zeitachse dargestellt werden. Der Ablauf lässt sich mit Hilfe verschiedener Bühnenbilder in einzelne Abschnitte aufteilen, die jeweils separat geplant werden können.

Ideen zu solchen Projekten finden sich beispielsweise in den offiziellen Scratch-Tutorials (siehe Projekte “Stell dir eine Welt vor” und “Erzähl eine Geschichte”) oder in der Broschüre “Scratch Projektideen” der Pädagogischen Hochschule Schwyz (siehe Projektidee “Ritter, Löwen und Prinzessinnen”).

Aufgabe 4: Animationssequenz nach Drehbuch erstellen

In dieser Aufgabe soll nach einem vorgegebenen Drehbuch eine kurze Animationssequenz – passend zum Thema “Anweisungen und Sequenzen” – mit mehreren automatisch agierenden Objekten in Scratch umgesetzt werden.

Laden Sie dazu die Projektdatei Drehbuch.sb3 als Vorlage herunter: Download

Das Projekt enthält vier Figuren (die Sprecherin “Luca”, zwei “Anweisungsblöcke” und ein “Fisch”-Objekt) und zwei Bühnenhintergründe (Titelbild und Hintergrund der Lektion). Die Animationssequenz soll beim Starten des Programms über das Symbol Icon nach dem folgenden Drehbuch ablaufen:

BlockKurz nach dem Programmstart mit Icon sagt Luca zuerst: “Als Erstes wird der Fisch mit einem einzelnen Block bewegt.” (Text A)
Während Luca spricht, soll sie immer mit der Grafik “Sprechen” angezeigt werden, sonst mit der Grafik “Warten”.
BlockAnschließend gleitet der “gehe”-Block nach rechts in den Skriptbereich (1.) und dort erscheint kurz die Meldung “Klick!” (2.)
Beim “Klick!” bewegt sich der Fisch ein Stück nach rechts. (3.)
BlockAls Nächstes sagt Luca: “Nun hängen wir einen weiteren Block daran.” (Text B)
BlockNun gleitet der “drehe”-Block ebenfalls nach rechts zur Unterseite des “gehe”-Blocks (1.) und dort erscheint wieder kurz “Klick!” (2.)
Beim “Klick!” bewegt sich der Fisch ein Stück nach rechts (3.) und dreht sich um 90°. (4.)
BlockAls Letztes sagt Luca noch: “Hier wurden beide Anweisungen nacheinander ausgeführt.” (Text C)
BlockZusatzaufgabe: Beim Programmstart soll zuerst 2 Sekunden lang das Titelbild angezeigt werden, bevor zum Hintergrund der Lektion gewechselt wird und die Animationssequenz wie oben beschrieben beginnt. Die Figuren sollen auf dem Titelbild nicht sichtbar sein.

Beachten Sie, dass die Animationssequenz auch mehrmals nacheinander durch Klicken auf Icon richtig abgespielt werden soll. Initialisieren Sie die Attribute der Objekte, die sich während der Programmausführung ändern, beim Start bzw. Szenenwechsel also geeignet.

Das Projekt enthält in den Skriptbereichen der Figuren und der Bühne bereits alle Blöcke, die Sie zur Umsetzung des Drehbuchs benötigen (ggf. müssen aber noch Blöcke kopiert oder fehlende Parameterwerte eingetragen werden). Der “warte”-Block kann dabei verwendet werden, um das richtige Timing der Aktionen für die verschiedenen Objekte abzustimmen.

Block Block Block

Block Block Block Block Block

Block Block Block Block Block

Zur Planung des zeitlichen Ablaufs kann es hilfreich sein, die Aktionen der Objekte in einem Zeitdiagramm zu skizzieren ( Download als PDF):

Diagram

Lösungstipps

Interaktive Programme

Aufgabe 5: 2D-Transformation

In dieser Aufgabe sollen Interaktionen zum Bewegen, Drehen und Skalieren eines Objekts verwendet werden.

Laden Sie dazu die Projektdatei 2D-Transformation.sb3 als Vorlage herunter: Download

Auf dem Koordinatengitter befinden sich eine grüne und eine rote geometrische Figur. Die grüne Figur wird beim Programmstart zufällig positioniert, gedreht und skaliert (siehe Skript der Figur “Ziel”). Ziel des Spiels ist es, die rote Figur durch Eingaben so zu transformieren, dass sie mit der grünen Zielfigur übereinstimmt.1

Fügen Sie dazu der roten Figur Skripte hinzu, so dass die Figur mit den Pfeiltasten horizontal und vertikal bewegt werden kann (in 10-er Schritten), mit den Tasten “S” und “W” schrumpft oder wächst (in 10%-Schritten) und per Mausklick gedreht werden kann (in 30°-Schritten).

Außerdem soll die rote Figur beim Programmstart mit Icon auf ihren Initialzustand zurückgesetzt werden (Position (0, 0), Richtung 90°, Größe 100%).

Image
Lösungstipps

Aufgabe 6: App-Mockup entwickeln

Visuelle Entwicklungsumgebungen werden in der Praxis besonders zum Entwickeln von grafischen Benutzeroberflächen verwendet. Häufig werden dabei zunächst Prototypen oder “Mockups” erstellt, die keine wirkliche Funktionalität haben sondern nur simulierte, aber ähnlich aussehen wie das geplante Produkt und so einen ersten Eindruck von dessen Gestaltung und Bedienung vermitteln.

In dieser Aufgabe soll mit Scratch ein Mockup für eine Abstimmungs-App entwickelt werden. Hierbei reicht es, Anweisungen aus den Kategorien “Bewegung”, “Aussehen”, “Klang” und “Ereignisse” zu verwenden.

Laden Sie dazu die Projektdatei VotesApp.sb3 als Vorlage herunter: Download

Das Projekt besitzt vier Figuren (Smiley, Zeiger, Schaltflächen und ) und drei Hintergründe (Start-, Abstimmungs- und Ergebnisbildschirm).

ImageDer Startbildschirm soll beim Programmstart sichtbar sein.
Durch Anklicken der Schaltfläche wird zum Abstimmungsbildschirm umgeschaltet.
ImageAuf dem Abstimmungsbildschirm ist ein Smiley zu sehen, der die abzugebende Bewertung repräsentiert. Durch Anklicken kann zwischen den verschiedenen Smileys gewechselt werden.
Durch Anklicken der Schaltfläche wird zum Ergebnisbildschirm umgeschaltet.
ImageAuf dem Ergebnisbildschirm wird das Gesamtergebnis der Abstimmung durch den Zeiger angezeigt. In unserem Mockup soll der Zeiger einfach manuell durch die beiden Pfeiltasten und auf der Tastatur nach links oder rechts gedreht werden können.
Durch Anklicken der Schaltfläche wird zum Abstimmungsbildschirm zurückgegangen.

Das folgende Video demonstriert, wie die Anwendung verwendet wird:

Aufgaben
  • Positionieren Sie die Figuren an den richtigen Stellen auf dem Bildschirm und erstellen Sie Skripte, so dass sich das Mockup wie oben beschrieben verhält.
  • Beim Anklicken der Schaltflächen soll als akustisches Feedback ein Klickgeräusch abgespielt werden (Soundeffekt “Klick” der Objekte).
  • Initial soll immer der Smiley ausgewählt sein. Wenn zwischen dem Abstimmungs- und Ergebnisbildschirm hin- und hergewechselt wird, soll sich der Zustand des Smileys nicht ändern.
  • Der Zeiger soll beim Programmstart auf eine zufällige Richtung aus dem Bereich -145° bis 145° gesetzt werden. Dazu kann der folgende Werteblock verwendet werden, den Sie in der Kategorie “Operatoren” (grün) finden:
    Block
Lösungstipps

  1. Ob das Ziel erreicht wurde, können wir momentan im Programm noch nicht automatisch überprüfen – in der nächsten Lektion werden wir Möglichkeiten dazu kennenlernen. ↩︎

1.3 Kontrollstrukturen

Bisher haben Sie in Scratch nur lineare Programmabläufe kennengelernt: Die bisherigen Programme bestehen aus Anweisungsfolgen, die durch Ereignisse ausgelöst und anschließend von der ersten bis zur letzen Anweisung sequenziell abgearbeitet werden.

So lassen sich allerdings nur vergleichsweise simple Lösungsverfahren umsetzen – meistens ist es nötig, vom sequenziellen Ablauf abzuweichen. Dazu werden nun Kontrollanweisungen eingeführt, die es ermöglichen, den Programmablauf zu steuern: Sie werden dabei die bedingte Ausführung von Programmabschnitten und die wiederholte Ausführung von Programmabschnitten kennenlernen und zur Lösung typischer Problemstellungen verwenden.

Grundlagen

Wenn wir ein Lösungsverfahren schrittweise formulieren, geben wir eine Folge eindeutiger, elementarer Handlungsanweisungen an. Eine solche eindeutige Handlungsvorschrift aus endlich vielen, wohldefinierten Einzelschritten wird als Algorithmus bezeichnet.

Als Beispiel soll eine Handlungsanleitung für ein Frage-Antwort-Spiel dienen. Hier kann die Handlungsfolge für die Moderatorin oder den Moderator so aussehen:

  • Begrüße Gast
  • Ziehe eine Fragekarte
  • Stelle die Frage
  • Nimm die Antwort entgegen

Dabei gelangen wir oft in die Situation, Anweisungen zu formulieren, die in Wiederholung ausgeführt werden müssen, bis eine bestimmte Bedingung erfüllt ist:

  • Begrüße Gast
  • Wiederhole bis Kartenstapel leer ist:
    • Ziehe eine Fragekarte
    • Stelle die Frage
    • Nimm die Antwort entgegen

Die Sequenz der drei Anweisungen “ziehe Frage”, “stelle Frage”, “nimm Antwort entgegen” wird hier also wiederholt ausgeführt, bis die Bedingung “Kartenstapel leer?” erfüllt ist.

Außerdem gibt es Situationen, in denen eine Fallunterscheidung getroffen werden muss, die Ausführung einer oder mehrerer Anweisungen zu einem Zeitpunkt also von einer Bedingung abhängt – Falls A dann mache dies, anderenfalls mache jenes:

  • Begrüße Gast
  • Wiederhole bis Kartenstapel leer ist:
    • Ziehe eine Frage
    • Stelle die Frage
    • Nimm die Antwort entgegen
    • Falls die Antwort richtig ist:
      • Sage “Richtig”
      • Vergib einen Punkt
    • Anderenfalls:
      • Sage “Falsch”

Hier wird in jeder Wiederholung nach der Anweisung “nimm Antwort entgegen” überprüft, ob die Bedingung “Antwort ist richtig?” erfüllt ist und je nach Ergebnis entweder die Anweisungen “sage richtig”, “vergib Punkt” oder die Anweisung “sage falsch” ausgeführt.

Wiederholung und Fallunterscheidung (bzw. bedingte Anweisungen) stellen die beiden grundlegendsten Möglichkeiten dar, die Reihenfolge, in der Handlungsschritte eines Algorithmus abgearbeitet werden, zu steuern bzw. zu “kontrollieren”. Diese Konstrukte werden daher allgemein als Kontrollstrukturen bezeichnet. In der imperativen Programmierung werden sie durch spezielle Kontrollanweisungen umgesetzt. Wie das obige Beispiel zeigt, können diese Kontrollanweisungen auch in Sequenzen vorkommen und sogar ineinander geschachtelt werden, wodurch sich komplexe Algorithmen modellieren lassen. Im Folgenden werden wir uns mit verschiedenen Ausprägungen und Anwendungsfällen dieser beiden Kontrollstrukturen in Scratch beschäftigen.

Kontrollstrukturen in Scratch

In Scratch gibt es besondere Anweisungsblöcke zur Programmablaufsteuerung mittels Wiederholungen und Fallunterscheidungen, die Kontrollblöcke. Sie befinden sich in der Block-Bibliothek in der Kategorie “Steuerung” (orange). Das folgende Beispiel zeigt bedingte Anweisungen – wenn die Ausführung des Skripts bei diesem Kontrollblock ankommt, wird die Sequenz “spiele Klang”, “verstecke dich” nur dann ausgeführt, falls das Objekt zu diesem Zeitpunkt gerade den Mauszeiger berührt:

Script

Kontrollanweisungen werden in Scratch allgemein durch Blöcke dargestellt, die wie “Klammern” aussehen (“Klammerblockform”):

Block

Solche Klammerblöcke können andere Blöcke oder Sequenzen umschließen, indem diese einfach innerhalb der Klammer platziert werden. Der Kontrollblock bestimmt nun, ob oder wie oft die von ihm umschlossenen Blöcke ausgeführt werden. Daneben lassen sich Kontrollblöcke aber auch genau wie Anweisungsblöcke vertikal mit anderen Blöcken zu einer Sequenz verbinden:

Block

Bedingungen in Scratch

Kontrollblöcke, deren Ausführung von einer Bedingung abhängt, besitzen ein sechseckiges “Loch” im Blockkopf – ähnlich den ovalen Eingabefeldern für Parameterwerte in anderen Blöcken. Bedingungen sind in Scratch entsprechend durch sechseckige Blöcke, die sogenannten “Wahrheitswerteblöcke” dargestellt, die in diese Eingabefelder eingefügt werden können – genau wie ovale Werteblöcke in die Parameter-Eingabefelder anderer Blöcke eingefügt werden können.

Block Block

Ein Wahrheitswerteblock ist ein besonderer Werteblock, der nur zu den beiden Werten wahr oder falsch ausgewertet werden kann. Solche Werteblöcke werden mit einer sechseckigen Form statt einer ovalen Form dargestellt:

Block

Scratch bietet eine Reihe von Wahrheitswerteblöcken an, um bestimmte Objekt- oder Systemzustände zu überprüfen, beispielsweise ob ein Objekt gerade den Mauszeiger, ein anderes Objekt oder eine bestimmte Farbe berührt oder ob eine bestimmte Taste gerade gedrückt ist:

Block Block Block

Diese Wahrheitswerteblöcke befinden sich in der Kategorie “Fühlen” (türkis). Wenn Sie einen Wahrheitswertblock im Entwurfsmodus anklicken, zeigt er seinen momentanen Wert an (genau wie die ovalen allgemeinen Werteblöcke).

Daneben lassen sich auch Vergleiche zwischen Attributen und Werten als Bedingung angeben, beispielsweise “Ist die x-Koordinate des Objekts kleiner als 0?”. Dazu finden sich in der Kategorie “Operatoren” (grün) drei Wahrheitswerteblöcke für die Vergleichsoperationen “größer als”, “kleiner als” und “gleich”:

Block Block Block

In die beiden ovalen Eingabefelder können beliebige (ovale) Werteblöcke eingefügt werden oder feste Werte eingetragen werden, die verglichen werden sollen. Sobald eine solche Bedingung im Programm ausgewertet wird, werden zunächst die momentanen Werte der inneren Werteblöcke abgefragt und verglichen. Der Block gibt dann je nach Vergleichsergebnis wahr oder falsch zurück (auf die Vergleichsoperatoren gehen wir später unter Logische Ausdrücke noch genauer ein).

Bedingte Anweisungen

Die Kontrollstruktur für Fallunterscheidungen wird in der imperativen Programmierung als bedingte Anweisung bezeichnet. In Scratch gibt es zwei Kontrollblöcke für bedingte Anweisungen, die so auch in so gut wie allen imperativen Programmiersprachen zu finden sind: Die Variante ohne Alternative (“falls … dann …”) und die Variante mit Alternative (“falls … dann … sonst …”).1

Bedingte Anweisung ohne AlternativeBedingte Anweisung mit Alternative
BlockBlock

Als Beispiel für die Varianten der bedingten Anweisung dient hier die Umsetzung eines einfachen Frage-Antwort-Spiels in Scratch. Das vollständige Projekt Quiz.sb3 können Sie hier herunterladen: Download

Um Eingaben abzufragen und auszuwerten, werden hier zwei neue Blöcke aus der Kategorie “Fühlen” (türkis) eingeführt:

BlockDie Anweisung “frage … und warte” zeigt eine Mitteilung an (wie bei “sage …”) und pausiert das Skript anschließend. Es erscheint ein Eingabefeld, in das eine Antwort eingegeben werden kann. Das Skript fährt erst dann fort, sobald die Eingabe mit der Eingabetaste abgeschlossen wird.
BlockAnschließend kann der Werteblock “Antwort” verwendet werden, um die eingegebene Antwort auszuwerten. Der Wert des “Antwort”-Blocks ist immer die zuletzt bei einer Frage eingegebene Antwort.

Bedingte Anweisung

Die einfachste Form der bedingten Anweisung führt die enthaltenen Anweisungen nur dann aus, falls eine bestimmte Bedingung erfüllt ist, anderenfalls nicht. Sobald der Programmablauf diesen Block erreicht, wird die Bedingung ausgewertet. Ist die Bedingung erfüllt, werden die enthaltenen Anweisungen ausgeführt. Anderenfalls fährt der Programmablauf nach dem Kontrollblock fort.

Im folgenden Beispiel wird zuerst eine Frage gestellt und anschließend nur dann die Mitteilung “Das ist richtig!” angezeigt, falls der Wert der Antwort mit dem Wert 31 übereinstimmt:

Script

Bedingte Anweisung mit Alternative

Um eine einfache Fallunterscheidung umzusetzen (also “Falls A dann mache dies, anderenfalls mache jenes.”), wird die Variante der bedingten Anweisung mit Alternative verwendet. Dieser Kontrollblock besteht aus zwei “Klammern”: Sobald der Programmablauf diesen Block erreicht, wird die Bedingung ausgewertet. Ist die Bedingung erfüllt, werden die in der oberen Klammer enthaltenen Anweisungen ausgeführt. Anderenfalls werden die in der unteren Klammer enthaltenen Anweisungen ausgeführt. In beiden Fällen fährt der Programmablauf anschließend nach dem Kontrollblock fort.

Im folgenden Beispiel wird die Mitteilung “Das ist richtig!” angezeigt, falls der Wert der Antwort mit dem Wert 31 übereinstimmt, und anderenfalls die Mitteilung “Nein, das ist nicht richtig.”:

Script

Mehrfache Fallunterscheidung

Eine mehrfache Fallunterscheidung – also eine Fallunterscheidung mit mehreren einander ausschließenden Fällen (“Falls A, dann mache dies, falls B dann mache das, falls C, dann mache jenes, …”) – lässt sich prinzipiell durch eine Sequenz von einfachen bedingten Anweisungen umsetzen, in denen jeweils einer der Fälle geprüft wird. Das folgende Beispiel unterscheidet die drei Fälle “Antwort = 31”, “Antwort > 31” und “Antwort < 31”, so dass bei einer falschen Antwort in Abhängigkeit davon, ob sie zu klein oder zu groß ist, eine unterschiedliche Mitteilung angezeigt wird:

Script

Dieses Konstrukt hat allerdings den Nachteil, dass unnötigerweise immer alle Bedingungen überprüft werden, auch wenn bereits die erste zutrifft. Schlimmer noch: Es kann sogar zu Fehlern bei der Programmausführung kommen, wenn bei der Ausführung des zutreffenden Falls die Bedingung der Fallunterscheidung verändert wird, wie das folgende Beispiel zeigt:

Script

Wird hier zuerst 31 und anschließend eine Zahl zwischen 1 und 3 eingegeben, wird “versehentlich” auch der zweite Fall ausgeführt.

Sinnvoller ist hier die Strukturierung mittels verschachtelten einfachen Fallunterscheidungen: Um eine mehrfache Fallunterscheidung mit garantiert einander ausschließenden Fällen umzusetzen, können mehrere bedingte Anweisungen mit Alternative so ineinandergesetzt werden, dass die weiteren Fallunterscheidungen jeweils im alternativen Anweisungsteil vorkommen:

Script

Hier wird die Bedingung “Antwort > 31” nur geprüft, falls “Antwort = 31” nicht zutrifft. Falls “Antwort > 31” ebenfalls nicht zutrifft, bleibt als letzte Möglichkeit “Antwort < 31” übrig.

🎓 Übung

  • Laden Sie das unter Bedingte Anweisungen verlinkte Scratch-Projekt herunter und öffnen Sie es.
  • Fügen Sie eine neue Frage hinzu. Duplizieren Sie dazu das Skript der Figur “Oktopus” und passen Sie es geeignet an.
    • Passen Sie das Ereignis des duplizierten Skripts so an, dass es beim Wechsel zum Bühnenbild “Frage2” startet.
    • Gestalten Sie das Bühnenbild von “Frage2” passend zu Ihrer Frage, wenn Sie möchten.
  • Stellen Sie eine Frage, bei der es zwei richtige Antworten gibt.
  • Bei einer falschen Antwort reicht es hier, einfach ohne weitere Fallunterscheidung “Nein, das ist nicht richtig.” anzuzeigen.

Wiederholungen

In Scratch gibt es drei Kontrollblöcke für Wiederholungen: Die Endloswiederholung, die Wiederholung mit fester Anzahl und die bedingte Wiederholung. Diese Kontrollstrukturen sind fester Bestandteil aller imperativen Sprachen, wobei manchmal auch nur die bedingte Wiederholung vorkommt, da sich die anderen Varianten der Wiederholung auch durch sie darstellen lassen (dazu später mehr).2

EndloswiederholungWiederholung mit fester AnzahlBedingte Wiederholung
BlockBlockBlock

Endloswiederholung

Die einfachste Form der Wiederholung stellt die Endloswiederholung dar: Hier werden die enthaltenen Anweisungen (zumindest theoretisch) unendlich oft wiederholt nacheinander abgearbeitet. Die Wiederholung endet in Scratch erst, sobald das Programm explizit über das Symbol Icon abgebrochen wird.

Diese Kontrollstruktur eignet sich also für Aufgaben, die das gesamte Programm über (oder ab einem bestimmten Ereignis) permanent in Endlosschleife im Hintergrund ausgeführt werden sollen. Das folgende Beispiel setzt eine einfache Animation in Scratch um: Die einzelnen Grafiken (bzw. “Kostüme”) der Figur stellen die einzelnen Animationsschritte dar. Das Skript sorgt dafür, dass im Hintergrund permanent von einer Grafik zur nächsten gewechselt wird und so wie bei einem Daumenkinos der Eindruck einer flüssigen Animation entsteht. Durch die “warte”-Anweisungen wird hier eine Animationsrate von 8 Bildern/Sekunde umgesetzt.

Script

Es fällt auf, dass der “wiederhole fortlaufend”-Block unten flach ist, dort also keine weiteren Blöcke angehängt werden können. Warum? Sobald der Block im Programmablauf erreicht wird, werden die enthaltenen Blöcke in endloser Wiederholung ausgeführt – damit kommt der Ablauf niemals bei einer Anweisung nach diesem Kontrollblock an. Da also nachfolgende Anweisungen niemals ausgeführt werden können (in der Programmierung werden solche Programmteile als “toter Code” bezeichnet), ist es bei Scratch von vornherein gar nicht möglich, hier Programmteile anzufügen.

Um eine Endloswiederholung abzubrechen, wenn Sie nicht mehr benötigt wird, kann die Anweisung “stoppe dieses Skript” verwendet werden – beispielsweise falls die Stoppuhr 60 Sekunden seit Programmstart gezählt hat:

Script

In der Regel ist es aber am sinnvollsten, Endloswiederholungen wirklich nur dann zu verwenden, wenn etwas während des gesamten Programmablaufs permanent wiederholt werden soll. Anderenfalls macht es Sinn, vorher darüber nachzudenken, wie lange die Wiederholung laufen soll und eine der beiden folgenden Varianten der Wiederholung zu wählen.

Wiederholung mit fester Anzahl

Die zweite Form der Wiederholung ermöglicht Abläufe, bei denen von vornherein feststeht, wie oft bestimmte Anweisungen wiederholt ausgeführt werden sollen. Die Anzahl der Wiederholungen wird hier als Parameterwert in das Eingabefeld eingetragen. Die umschlossenen Anweisungen werden genau so oft nacheinander ausgeführt, wie durch die Anzahl festgelegt ist.

Das folgende Beispiel setzt eine Bewegungsanimation um: Wird die Pfeiltaste nach oben gedrückt, soll die Figur hochspringen. Das Springen und anschließende Fallen wird hier durch je 5 Einzelschritte umgesetzt, in denen sich die Figur um jeweils 10 Pixel auf- oder abwärts bewegt. Vor dem Wechsel zum nächsten Schritt wird dabei jeweils kurz pausiert, um den Eindruck einer Stop-Motion-Animation zu erzeugen.

Script

Solche Wiederholungen lassen sich zwar prinzipiell auch mittels Kopieren der enthaltenen Anweisungen durch eine einfache Sequenz ausdrücken (wie zum Teil in den Beispielen aus der letzten Lektion umgesetzt) – das sollte aber aus mehreren Gründen vermieden werden: Zum einen ist die Überarbeitung des Programms (z. B. zur Fehlerkorrektur) in solchen Fällen aufwendig, da eine Änderung an einer Anweisung in alle Kopien übernommen werden muss. Zum anderen wird der Code bereits ab einer geringen Anzahl von Wiederholungen unübersichtlich – von einer 100- oder 1000-fachen Wiederholung ganz zu schweigen. Darüber hinaus kann die Anzahl der Wiederholungen auch aus einem Werteblock abgefragt werden – eventuell entscheidet sich also erst zur Ausführungszeit, wie oft die Wiederholung durchlaufen werden soll.

Bedingte Wiederholung

Die beiden oben vorgestellten Varianten der Wiederholung kamen bisher ohne Bedingung aus, da die Wiederholungsanzahl jeweils von vornherein festgelegt war (n-mal bzw. unendlich oft). Die allgemeinste Form der Wiederholung führt die enthaltenen Anweisungen wiederholt nacheinander aus, bis eine bestimmte Bedingung erfüllt ist. Sie wird daher als “bedingte Wiederholung” oder präziser “Wiederholung mit Abbruchbedingung” bezeichnet.

So lässt sich beispielsweise die Endloswiederholung mit Abbruch, wenn die Stoppuhr 60 Sekunden gezählt hat, einfacher durch eine bedingte Wiederholung formulieren:

Script

Dabei ist darauf zu achten, wann die Abbruchbedingung überprüft wird: Sobald der Programmablauf diesen Block erreicht, wird die Bedingung ausgewertet. Ist die Bedingung bereits zu diesem Zeitpunkt erfüllt, fährt der Programmablauf nach dem Kontrollblock fort, die enthaltenen Anweisungen werden übersprungen. Ist die Bedingung dagegen nicht erfüllt, werden die enthaltenen Anweisungen ausgeführt und der Programmablauf beginnt wieder am Anfang der Wiederholung. Die Bedingung wird erneut ausgewertet (dieses Mal könnte sie einen anderen Wert haben als bei der letzten Auswertung) und je nach Ergebnis wird eine weitere Wiederholung durchgeführt oder die Wiederholung beendet und nach dem Kontrollblock weitergemacht. Die enthaltene Anweisungssequenz wird also in jeder Wiederholung vollständig durchlaufen, bis die Bedingung das nächste Mal überprüft wird.

Da die Bedingung zu Beginn der Wiederholung überprüft wird, wird diese Art der bedingten Wiederholung auch “kopfgesteuerte Wiederholung” genannt.

Das folgende Beispiel modifiziert die Sprunganimation aus dem Beispiel zur Wiederholung mit fester Anzahl: Die Sprunghöhe soll nun davon abhängen, wie lange die Pfeiltaste gedrückt bleibt. Während zuvor also jeweils 5 Schritte auf- und abwärts gemacht wurden, soll die Figur sich nun solange aufwärts bewegen, bis die Pfeiltaste nicht mehr gedrückt ist, und anschließend fallen, bis ihre y-Koordinate wieder den Ausgangspunkt erreicht hat (hier -80).

Script

Hier wird ein neuer Operator verwendet, nämlich die Umkehrung bzw. Negation eines Wahrheitswertes mit dem “nicht”-Block.

🎓 Übung

  • Öffnen Sie das Scratch-Projekt, das unter Bedingte Anweisungen entwickelt wurde.
  • Passen Sie das Skript der Figur “Oktopus” so an, dass die Frage solange wiederholt gestellt wird, bis sie richtig beantwortet wurde.

Bedingtes Warten

In der Praxis tritt gelegentlich die Situation auf, dass ein Skript an einer bestimmten Stelle warten soll, bis eine bestimmte Bedingung erfüllt ist, bevor es mit der Ausführung fortfährt.

Als Beispiel: Ein Objekt soll erscheinen, sobald die Leertaste gedrückt wird, und danach wieder verschwinden, sobald die Leertaste nicht mehr gedrückt ist. Das Ereignis “Leertaste wird gedrückt” startet also ein Skript, in dem der Reihe nach

  • zuerst das Objekt sichtbar wird,
  • anschließend gewartet wird bis die Bedingung “Leertaste ist nicht gedrückt?” erfüllt ist,
  • danach das Objekt wieder unsichtbar wird.

Diese Anforderung lässt sich durch eine bedingte Wiederholung lösen, deren Inhalt leer ist (es wird also wiederholt “nichts” gemacht, bis die Bedingung erfüllt ist):

Script

Hierfür bietet Scratch auch einen speziellen “warte bis”-Block an, der genau dieselbe Bedeutung hat wie ein “wiederhole bis”-Kontrollblock mit leerem Inhalt:

Script

Wiederholte Zustandsabfrage

Bisher wurden Eingaben ereignisorientiert behandelt, also durch Skripte, die durch bestimmte Eingabeereignisse ausgelöst werden (“Taste/Maustaste wird gedrückt”). Diese Skripte werden potenziell parallel – also quasi gleichzeitig – ausgeführt, was den Nachteil hat, dass sich die Programmausführung so teils schwierig nachvollziehen lässt.

Mit Hilfe von Wiederholungen und bedingten Anweisungen lässt sich ein alternatives Konzept zur Eingabebehandlung umsetzen: Nach dem Programmstart wird einfach (ggf. endlos) wiederholt geprüft, ob bestimmte Tasten gedrückt sind oder nicht. Falls ja, wird entsprechend darauf reagiert – beispielsweise die Figur um 10 Pixel nach links verschoben, falls die linke Pfeiltaste gerade gedrückt ist oder um 10 Pixel nach rechts, falls die rechte Pfeiltaste gerade gedrückt ist.

Das folgende Beispiel zeigt ein Skript zur Bewegung einer Figur mittels wiederholter Abfrage (links) und zum Vergleich die ereignisorientierte Steuerung der Figur (rechts). Damit die Figur sich nicht zu schnell bewegt, wird die Abfrage hier in 0.05-Sekunden-Intervallen wiederholt durchgeführt – die Abfrage- und Schrittrate beträgt hier also 20 mal pro Sekunde. Beide Lösungen setzen prinzipiell dieselbe Steuerung um, bei der Ausführung fällt aber auf, dass die ereignisorientierte Variante im Vergleich zur wiederholten Abfrage etwas verzögert und weniger flüssig reagiert.

Script Script

Dieses Prinzip, den Zustand von Eingabegeräten innerhalb einer Wiederholung zyklisch abzufragen, wird in der Informatik als Polling (engl. poll = abfragen) bezeichnet. Hierbei findet die Abfrage und Bearbeitung von Eingaben innerhalb eines einzelnen Skripts statt, was es einfacher macht, den Programmablauf zu kontrollieren als bei ereignisorientierter Eingabeverarbeitung, wobei mehrere Skripte parallel unabhängig voneinander ausgeführt werden (z. B. je ein Skript pro Taste, über welche eine Figur gesteuert werden kann). Polling erlaubt es in Scratch außerdem, auch auf Zustandsänderungen zu reagieren, für die keine Ereignisblöcke vorhanden sind (z. B. Maustaste wird an beliebiger Position gedrückt, zwei Objekte berühren sich), sofern es entsprechende Wahrheitswerteblöcke in der Kategorie “Fühlen” gibt.

🎓 Übung

  • Laden Sie die Projektdatei Polling_vs_Ereignisse.sb3 herunter und öffnen Sie das Projekt in Scratch: Download
  • Die obere Figur wird mittels Polling gesteuert (siehe Skript links), die untere Figur mittels Ereignissen (siehe Skript rechts). Starten Sie das Programm und vergleichen Sie in der Praxis, wie sich beide Figuren beim Drücken bzw. Gedrücktlassen der Pfeiltasten verhalten.
  • Ändern Sie den Parameterwert der “warte”-Anweisung für die obere Figur (d. h. die Abfragerate), um ihre Bewegungsgeschwindigkeit zu erhöhen oder zu verringern.

Logische Ausdrücke

In den obenstehenden Beispielen haben wir bereits mehrere Operatoren verwendet, um Bedingungen aus mehreren Werten zu berechnen – zum einen Vergleichsoperatoren, zum anderen die Negation. In diesem Abschnitt werfen wir einen genaueren Blick auf zusammengesetzte logische Ausdrücke und logische Operatoren in Scratch.

Ein logischer Ausdruck ist – wie oben bereits beschrieben – ein Ausdruck, der zu einem Wahrheitswert (also wahr oder falsch, auch Boolesche Werte genannt) ausgewertet wird. Logische Ausdrücke können auch mit logischen Operatoren aus anderen Werten zusammengesetzt werden. In Scratch sind zwei Arten von logischen Operatoren vorhanden, Vergleiche von Werten (Zahlen oder Texte) und Verknüpfungen von Wahrheitswerten.

Vergleichsoperatoren

Vergleiche von Werten stellen logische Ausdrücke dar, z. B. kann das Ergebnis eines Ausdrucks wie “ist y kleiner als 0?” nur wahr oder falsch sein, je nachdem welchen Wert das Attribut y des betrachteten Objekts zum Zeitpunkt der Auswertung gerade hat.

In Scratch werden die mathematische Vergleichsoperatoren “größer als”, “kleiner als” und “gleich groß” unterstützt. Diese Operatoren werden durch Wahrheitswerteblöcke in der Kategorie “Operator” (grün) dargestellt.

Block Block Block

Die Operanden, also die beiden Werte, die durch den Operator verglichen werden, können durch Werteblöcke angegeben werden, die in den beiden Eingabefeldern platziert werden, oder direkt als fester Wert angegeben werden, z. B. zum Vergleich der y-Koordinate eines Objekts mit dem Wert 0 (links) oder mit der y-Koordinate des Mauszeigers (rechts):

Block Block

Verknüpfungsoperatoren

So lassen sich bisher allerdings nur einzelne Vergleiche als Bedingungen prüfen, aber nicht mehrere Vergleiche. Um mehrere logische Ausdrücke zu verknüpfen, werden logische Verknüpfungsoperatoren benötigt, also Rechenoperationen, die aus mehreren (meist zwei) Wahrheitswerten einen neuen Wahrheitswert berechnen. Die grundlegenden zweistelligen logischen Operatoren sind das logische UND, sowie das logische ODER. Daneben gibt es noch den einstelligen Operator NICHT zum Negieren eines Wahrheitswertes.

Block Block Block

Konjunktion

Mit dem logischen UND (auch als Konjunktion bezeichnet), werden zwei logische Ausdrücke zu einem neuen Ausdruck verknüpft, der angibt, ob beide verknüpften Ausdrücke wahr sind, z. B. “(ist Taste Pfeil nach oben gedrückt?) UND (ist y kleiner als 0?)”.

Block

A UND B ergibt genau dann WAHR, wenn beide Operanden den Wert WAHR haben.

Disjunktion

Mit dem logischen ODER (auch als Disjunktion bezeichnet), werden zwei logische Ausdrücke zu einem neuen Ausdruck verknüpft, der angibt, ob mindestens einer der verknüpften Ausdrücke wahr ist, z. B. “(ist Taste Pfeil nach oben gedrückt?) ODER (ist y kleiner als Maus y?)”. Das bedeutet also nicht, dass genau ein Ausdruck erfüllt ist (wie das umgangssprachliche “entweder A oder B”) – auch wenn beide verknüpften Ausdrücke wahr sind, ist der gesamte Ausdruck wahr.

Block

A ODER B ergibt genau dann WAHR, wenn mindestens ein Operand den Wert WAHR hat.

Negation

Neben den beiden zweistelligen logischen Operatoren gibt es noch einen einstelligen Operator, die Negation bzw. das logische NICHT. Der Operand wird hier formal hinter den Operator geschrieben, z. B. “NICHT (wird Mauszeiger berührt?)” (statt des natürlich-sprachlichen “wird Mauszeiger nicht berührt?”).

Block

NICHT A ergibt genau dann WAHR, wenn A den Wert FALSCH hat.


  1. Bedingte Anweisungen werden im Deutschen meist als “wenn … dann” formuliert. Da das Wort “wenn” in Scratch allerdings bereits für Ereignisse verwendet wird (wobei hier “sobald” treffender wäre), wird im Kontext von Scratch zur besseren Unterscheidung das Wort “falls” für Fallunterscheidungen verwendet. ↩︎

  2. Für die Wiederholungsstruktur ist im Deutschen auch der Begriff “Schleife” (von engl. loop) sehr verbreitet. Dieser Begriff wird im didaktischen Kontext allerdings kontrovers diskutiert, da er zu Fehlvorstellungen führen kann, wie beispielsweise die berüchtigte Wortschöpfung “if-Schleife” zeigt. In den Fachanforderungen wird daher der Begriff “Wiederholung” verwendet. Zur Diskussion siehe z. B. Ludger Humbert: Informatische Bildung – Fehlvorstellungen und Standards. In: Münsteraner Workshop zur Schulinformatik 2006, S. 37–46, Münster, 2006. ↩︎

1.3.1 Übungsaufgaben

Stoffwiederholung

Aufgabe 1: Scratch-Skripte interpretieren

Aufgabe 1.1: Objekt fangen

Auf der Bühne befindet sich ein Objekt, das immer dann, wenn es mit dem Mauszeiger berührt wird, an eine andere zufällig ausgewählte Position auf der Bühne springen soll.

Die folgenden Skripte stehen für das Objekt zur Auswahl. Geben Sie alle Skripte an, welche die Aufgabe richtig erfüllen (mindestens ein Skript ist richtig, möglicherweise auch mehrere).

Überlegen Sie auch, worin die Fehler in den anderen Skripten bestehen.

Diagram
Skript 1
Diagram
Skript 2
Diagram
Skript 3
Diagram
Skript 4

Aufgabe 1.2: Minigolf

Ein Ball bewegt sich über die Bühne und prallt dabei vom Bühnenrand ab. Das Programm soll enden, wenn der Ball das schwarze Loch berührt.

Die folgenden Skripte stehen für den Ball zur Auswahl. Geben Sie alle Skripte an, welche die Aufgabe richtig erfüllen (mindestens ein Skript ist richtig, möglicherweise auch mehrere).

Überlegen Sie auch, worin die Fehler in den anderen Skripten bestehen.

Diagram
Skript 1
Diagram
Skript 2
Diagram
Skript 3
Diagram
Skript 4

Aufgabe 1.3: Wrap-Around

Eine Figur bewegt sich horizontal über die Bühne. Die Bewegungsrichtung kann mit den Pfeiltasten links/rechts festgelegt werden. Sobald die Figur den linken oder rechten Bühnenrand erreicht, soll sie auf der anderen Seite wieder auftauchen.

Die folgenden Skripte stehen für die Figur zur Auswahl. Geben Sie alle Skripte an, welche die Aufgabe richtig erfüllen (mindestens ein Skript ist richtig, möglicherweise auch mehrere).

Überlegen Sie auch, worin die Fehler in den anderen Skripten bestehen.

Diagram
Skript 1
Diagram
Skript 2
Diagram
Skript 3
Diagram
Skript 4

Aufgabe 1.4: Drehung um 360°

Ein Zeiger soll sich innerhalb von einer Sekunde um 360° drehen, wenn er mit der Maus angeklickt wird.

Die folgenden Skripte stehen für den Zeiger zur Auswahl. Geben Sie alle Skripte an, welche die Aufgabe richtig erfüllen (mindestens ein Skript ist richtig, möglicherweise auch mehrere).

Überlegen Sie auch, worin die Fehler in den anderen Skripten bestehen.

Diagram
Skript 1
Diagram
Skript 2
Diagram
Skript 3
Diagram
Skript 4

Aufgabe 2: Programmausführung nachvollziehen

In dieser Aufgabe soll der Ablauf eines Programms in Scratch nachvollzogen werden. Hier wird die Erweiterung “Malstift” verwendet, mit der ein Objekt eine Spur auf der Bühne zeichnet, während es sich bewegt:

Script

Skizzieren Sie die Zeichnung, die auf der Bühne entsteht, und beantworten Sie die folgenden Fragen:

  • An welcher Position befindet sich der Stift am Ende?
  • In welche Richtung zeigt der Stift am Ende?
  • Wie oft wird die Anweisung “setze Richtung auf 0°” in der oberen Wiederholung ausgeführt?
  • Wie oft wird die Anweisung “setze x auf x - 10” in der unteren Wiederholung ausgeführt?

Praktische Übungen

Aufgabe 3: 2D-Transformation überprüfen

In dieser Aufgabe soll die Anwendung zur Aufgabe 2D-Transformation aus der vorigen Übung um eine Ergebnisüberprüfung ergänzt werden.

Laden Sie dazu die Projektdatei 2D-Transformation.sb3 herunter: Download

Sobald die Leertaste gedrückt wird, soll nun überprüft werden, ob die Form der roten Figur mit der grünen Zielfigur übereinstimmt. In diesem Fall soll kurz die Mitteilung “Passt genau!” angezeigt werden. Anschließend soll die grüne Figur eine neue zufällig ausgewählte Position, Rotation und Größe annehmen (siehe Anweisungen im Startskript der Figur).

Anderenfalls soll eine Eigenschaft mitgeteilt werden, die noch nicht übereinstimmt, z. B. “Die Position stimmt nicht.”, “Die Größe stimmt nicht.” oder “Die Richtung stimmt nicht.”

Image

Zum Testen der Bedingungen sind hier die folgenden Blöcke hilfreich:

Block Block Block

Der dritte Block ("… von …") sieht in der Blockbibliothek zunächst so aus: Block
Dieser Block wird verwendet, um den Wert eines Attributs eines anderen Objekts oder der Bühne abzufragen. Über die linke Auswahlliste (Symbol ▾) kann das gewünschte Attribut (z. B. y-Koordinate) und über die rechte Auswahlliste das Objekt oder die Bühne ausgewählt werden.

Lösungstipps

Aufgabe 4: Navigation nach Farben

In dieser Aufgabe soll eine Simulation mit Fallunterscheidungen und Wiederholungen umgesetzt werden. Das Szenario ist folgendermaßen:

In einem Labyrinth aus quadratischen farbigen Kacheln befindet sich ein Roboter, der sich schrittweise eine Kachel vorwärts bewegen oder in 90°-Schritten drehen kann. Beim Starten des Programms befindet sich der Roboter auf der linken oberen weißen Kachel mit Blickrichtung nach rechts und beginnt loszufahren. Nach jedem Schritt prüft er die Farbe der Kachel, auf der er sich befindet: Bei einer blauen Kachel dreht er sich um 90° nach links, bei einer roten Kachel nach rechts. Befindet er sich auf einer schwarzen Kachel, dreht er sich um 180°. Wenn er es schafft, die grüne Kachel zu erreichen, ist er am Ziel angekommen: Jubel ertönt und das Programm endet.

Damit wir den Ablauf gut beobachten können, soll der Roboter nur 10 Schritte (Bewegung oder Drehen) pro Sekunde ausführen.

Image

Laden Sie das Scratch-Projekt Farbnavigation.sb3 herunter: Download

Auf der Bühne finden Sie bereits alle Anweisungs- und Werteblöcke, die Sie zur Umsetzung der Lösung benötigen – es fehlen allerdings noch die Kontrollstrukturen.

Script

Neu sind hier die Wahrheitswerteblöcke “wird Farbe berührt?” aus der Kategorie “Fühlen” (türkis). Diese Blöcke liefern den Wert wahr zurück, falls das Objekt momentan einen Punkt auf dem Bildschirm berührt, der die angegebene Farbe hat.

Überlegen Sie, wie die oben abgebildeten Anweisungen mit Hilfe von geeigneten Kontrollstrukturen zu einem Programm umgesetzt werden können, das den oben skizzierten Algorithmus für den Roboter umsetzt. Setzen Sie den Algorithmus in Scratch um und überprüfen Sie, ob der Roboter zum Ziel findet.

Zusatzaufgabe: Weg einzeichnen

Fügen Sie die folgenden Anweisungen aus der Erweiterung “Malstift” zum Projekt hinzu, um den Weg, den der Roboter zurücklegt, in das Labyrinth einzuzeichnen. Beim Starten des Programms sollten zunächst alle alten Malspuren beseitigt werden.

Block Block Block Block

Wenn alles nach Plan läuft, sollte die Bühne nach der Ausführung des Programms folgendermaßen aussehen:

Image

Aufgabe 5: Simulation eines Staubsaugroboters

In dieser Aufgabe soll eine weitere Simulation umgesetzt werden, dieses Mal allerdings ohne vorgegebene Blöcke. Hier soll das Verhalten eines Staubsaugroboters simuliert werden.1 Laden Sie dazu das Scratch-Projekt Staubsaugroboter.sb3 herunter: Download

Ein einfacher Staubsaugroboter funktioniert folgendermaßen: Er dreht sich zunächst in eine zufällige Richtung und fängt an, geradeaus zu fahren. Immer wenn er auf ein Hindernis stößt, fährt er ein Stück zurück (entgegen seiner Blickrichtung), dreht sich erneut in eine zufällige Richtung und fährt weiter geradeaus. So versucht er, jeden Winkel im Raum zu erreichen.

Der Roboter soll hier beim Programmstart in der Mitte der Bühne starten, nach der oben skizzierten Strategie herumfahren und dabei den Raum reinigen. Dazu zeichnet er, während er sich bewegt, eine helle Spur (z. B. in der Farbe des Bodens) hinter sich her, wobei die Stiftdicke etwas kleiner als sein Durchmesser gewählt wird. Die Raumwände sind durch schwarze Linien dargestellt.

Der Roboter kann dabei nicht endlos herumfahren, da sich sein Akku irgendwann erschöpfen würde. In diesem Programm soll der Roboter 30 Sekunden nach dem Programmstart anhalten.

Das folgende Bild zeigt, wie die Bühne ein paar Sekunden nach dem Start aussehen könnte (die Zeichenspur ist hier zur besseren Sichtbarkeit hellblau dargestellt):

Image

Neben den Blocktypen aus der vorigen Aufgabe Navigation nach Farben können die folgenden Blöcke für diese Aufgabe hilfreich sein:

BlockDieser Werteblock liefert eine zufällige Ganzzahl zurück, die zwischen den beiden Parameterwerten liegt (hier z. B. zwischen 1 und 10).
BlockDieser Anweisungsblock setzt die Stoppuhr zur Zeitmessung auf Null zurück.
BlockDieser Werteblock liefert die Anzahl an Sekunden zurück, die seit dem Programmstart oder letzten Zurücksetzen der Stoppuhr vergangen sind.

Setzen Sie den oben beschriebenen Algorithmus in Scratch um. Ermitteln Sie dabei experimentell geeignete Parameterwerte für die Bewegungsanweisungen und die zufällige Drehung bei Kollision mit einer Wand.

Aufgabe 6: Spielsteuerung ergänzen

In dieser Aufgabe soll ein kleines Spiel in Scratch vervollständigt werden. Laden Sie als Vorlage das Scratch-Projekt Flappy_Seagull.sb3 herunter: Download

Programm erkunden

In diesem Spiel wird eine Möwe gesteuert, die sich am linken Bildrand bewegt. Solange die Maustaste nicht gedrückt wird, fällt sie abwärts. Während die Maustaste gedrückt ist, steigt sie dagegen auf.

Ziel ist es, den gegnerischen Figuren auszuweichen, die am rechten Bildrand erscheinen und sich nach links bewegen (tatsächlich gibt es nur ein Gegnerobjekt, das nach dem Erreichen des linken Bildrands verschwindet und kurz darauf auf der rechten Seite wieder erscheint).

Untersuchen Sie zunächst die vorhandenen Skripte der Figuren und der Bühne.

Spielfigur bewegen

Ergänzen Sie das Skript der Spielfigur so, dass sie sich wie oben beschrieben verhält und gesteuert wird.

Setzen Sie die Steuerung dabei innerhalb des Wiederholungsblocks mittels “Polling” um.

Spielfigur animieren

Die Spielfigur soll während der gesamten Programmausführung fortwährend ihre Grafik wechseln, um den Eindruck einer Animation entstehen zu lassen.

Spielende

Das Spiel soll enden (Wechsel zum Hintergrund “Spielende”), wenn die Spielfigur das Gegnerobjekt berührt, oder wenn sie zu tief fällt und den unteren Bildrand erreicht.

Um zu prüfen, ob ein Objekt mit einem anderen kollidiert, kann der “Fühlen”-Block “wird … berührt?” verwendet werden (Auswahl des Zielobjekts über das Symbol ▾):

Block

Lösungstipps

  1. Diese Aufgabe basiert auf der Aufgabenstellung “Simulation eines Mähroboters” von Dr. Annika Eickhoff-Schachtebeck, Lehrerbildungszentrum Informatik, Universität Göttingen, lizensiert unter CC BY-NC-SA (https://www.uni-goettingen.de/de/629174.html↩︎

1.4 Programmieren mit Variablen

In dieser Lektion werden wir das Fachkonzept der Variablen in der Programmierung behandeln und uns detaillierter mit (u. a. mathematischen) Ausdrücken, Operatoren und Funktionen beschäftigen, um Werte aus anderen Werten zu berechnen.

Variablen

Bisher haben wir in unseren Programmen mit den von Scratch vorgegebenen Daten gearbeitet – im Wesentlichen die Attribute von Figuren und Bühne, sowie globale Werte wie Mauszeigerposition, Lautstärke, Wert der Stoppuhr oder Antwort auf die letzte Frage. Um diese Werte abzufragen, werden bestimmte Werteblöcke verwendet, und es gibt zum Teil bestimmte Anweisungsblöcke, um diese Werte zu verändern (z. B. “setze x auf …”).

In vielen Situationen ist es allerdings nötig, weitere Daten zu speichern, um mit Informationen zu arbeiten, sie sich während der Programmausführung ändern können – beispielsweise wenn Sie einen Punktezähler in ein Spiel einbauen möchten, die Anzahl der richtigen Antworten in einem Quiz mitgezählt werden soll, oder es eine einstellbare Geschwindigkeit für bewegte Objekte geben soll. Um beliebige Daten zu speichern und wieder abzurufen, werden in der Programmierung Variablen verwendet, deren Verwendungszweck wir selbst festlegen.

In Scratch können dazu neue, von uns benannte Werteblöcke definiert werden, sogenannte “Variablenblöcke”, in denen jeweils ein beliebiger Wert gespeichert werden kann. So lässt sich beispielweise ein neuer Werteblock namens “Punkte” erzeugen, dessen Wert mit einer bestimmten Anweisung (“setze Punkte auf …”) verändert werden kann. Dieser Werteblock lässt sich dann im Programm verwenden, um die aktuelle Punktezahl zu speichern, bei bestimmten Ereignissen zu erhöhen und auf der Bühne anzuzeigen.

Definition und Zuweisung

Eine neue Variable muss zunächst definiert werden. Dazu wird in Scratch die Schaltfläche “Neue Variable” in der Block-Kategorie “Variablen” ausgewählt und ein eindeutiger Bezeichner für die neue Variable vergeben – am besten ein aussagekräftiger Name, der angibt, was die Variable im Programm bedeutet (zum Beispiel “Tempo” für die Bewegungsgeschwindigkeit von Objekten). Anschließend erscheint ein neuer Werteblock mit dem Namen der neuen Variablen in der Block-Bibliothek. Durch Ankreuzen des Kästchens links neben dem Werteblock kann der Wert, der momentan in der Variablen gespeichert ist, live auf der Bühne angezeigt werden (wie wir es auch bereits von anderen Werten und Objekt-Attributen kennen).

Block

Initial enthält jede neue Variable in Scratch den Wert 0. Um einen anderen Wert in der Variablen zu speichern wird der Anweisungsblock “setze Variable auf …” verwendet. Wenn diese Anweisung ausgeführt wird, speichert Sie den Wert, der für den Parameter angegeben wird, in der ausgewählten Variablen und überschreibt dabei den momentan vorhandenen Wert. Diese Anweisung wird als “Wertezuweisung” oder kurz Zuweisung bezeichnet. Das folgende Beispiel setzt den Wert der Variablen “Tempo” auf 25:

Block

Um einen Wert zum aktuellen Wert der Variablen hinzuzuaddieren, kann auch die Anweisung “ändere Variable um …” verwendet werden (“inkrementelle Zuweisung”). Das folgende Beispiel zieht 5 vom aktuellen Wert der Variablen “Tempo” ab:

Block

Als Parameter für die Zuweisung kann auch ein beliebiger Werteblock eingefügt werden (wie schon von anderen Anweisungen bekannt). In diesem Fall wird beim Ausführen der Zuweisung zunächst der momentane Wert dieses Werteblocks abgefragt und dieser Wert anschließend in die Variable geschrieben. So können auch Berechnungsergebnisse in einer Variablen gespeichert werden, hier beispielsweise die aktuelle x-Koordinate des Mauszeigers dividiert durch 10:

Block Block

Der Variablenblock kann genau wie jeder andere Werteblock als Parameter in anderen Anweisungen verwendet werden. Wird die Anweisung ausgeführt, wird der momentan im Variablenblock gespeicherte Wert abgefragt. Hier wird beispielsweise der aktuellen Tempo-Wert zur x-Koordinate eines Objekts hinzuaddiert (links):

Block

Genauso kann der Variablenblock auch als Operand in Operator-Werteblöcken verwendet werden, z. B. um zu prüfen, ob der aktuell gespeicherte Wert > 0 ist:

Block

Globale Variablen und Objektvariablen

Bisher hatten wir Zustandswerte kennengelernt, die zu einzelnen Objekten gehören (die Attribute der Objekte, z. B. Position, Größe oder Kostümnummer) oder global sind, d. h. sich auf den gesamten Programmzustand beziehen (z. B. Position des Mauszeigers oder zuletzt eingegebene Antwort).

Variablen können ebenfalls als Objektvariablen oder globale Variablen definiert werden.

  • Eine globale Variable gibt es nur einmal. Es kann also keine andere Variable mit demselben Namen geben. Jedes Objekt (bzw. konkreter: jedes Skript von jedem Objekt) kann den Wert einer globalen Variablen abfragen und durch Zuweisungen ändern. Globale Variablen werden also gemeinsam von allen Objekten genutzt.
  • Eine Objektvariable gehört zu einem Objekt. Jedes Objekt hat seine eigenen Objektvariablen, auch wenn diese gleich heißen. Objektvariablen stellen also im Grunde selbst definierte Attribute dar. Andere Objekte können den Wert dieser Variablen zwar abfragen, aber nur Skripte des Objekts selbst können den Wert durch Zuweisungen ändern. Zur Unterscheidung von globalen Variablen werden sie im Kontext von Scratch daher auch als private Variablen oder lokale Variablen bezeichnet.1
Image
Globale Variable Tempo
Image
Objektvariablen Tempo von zwei verschiedenen Objekten

Beim Erzeugen eines neuen Werteblocks über die Schaltfläche “Neue Variable” erscheint ein Dialog, in dem ausgewählt werden kann, ob eine globale Variable (“Für alle Figuren”) oder eine Objektvariable für die aktuell ausgewählte Figur (“Nur für diese Figur”) erzeugt werden soll:

Dialog

Soll es nur einen Tempowert geben, die durch alle Objekte im Programm gemeinsam genutzt wird, sollte diese Variable global definiert werden. Wenn verschiedene Objekte dagegen eigene Tempowerte haben sollen, die unabhängig voneinander unterschiedlich sein können, sollte die Variable in jedem dieser Objekte privat definiert werden.

Zu beachten ist noch, dass zum Abfragen einer Objektvariablen im Skript eines fremden Objekts nicht der Variablenblock verwendet wird (private Variablenblöcke erscheinen nur in der Block-Bibliothek, wenn “ihr” Objekt ausgewählt ist), sondern der Werteblock “Attribut von Objekt” aus der Kategorie “Fühlen” – z. B. für eine Objektvariable “Tempo” des Objekts “Rennwagen”:

Block

Um zu entscheiden, ob eine Variable global oder privat definiert werden soll, sollte also überlegt werden, ob die Variable nur innerhalb von Skripten eines bestimmten Objekts verwendet wird, oder ob sie von mehreren Objekten gemeinsam oder unabhängig von Objekten genutzt wird. Variablen, die nur innerhalb eines Objekts genutzt werden, sollten privat sein, damit sie nicht mit anderen Variablen verwechselt oder fälschlicherweise von anderen Objekten verändert werden. Dieses Konzept wird in der Programmierung als Datenkapselung bezeichnet.

Beispiel: Fische fangen

Als praktisches Beispiel zur Verwendung von Variablen dient hier ein kleines Point & Click-Spiel in Scratch. Laden Sie das Projekt Fische_Fangen.sb3 hier herunter: Download

Screenshot

In diesem Spiel bewegt sich ein Fisch zufällig durch ein Aquarium und kann durch einen Mausklick gefangen werden. In diesem Fall verschwindet er und taucht dann eine Sekunde später an einer anderen Position wieder auf. Das Spiel soll nun durch Variablen folgendermaßen erweitert werden:

Punktestand

Es soll ein Punktezähler hinzugefügt werden. Jeder erfolgreiche Klick auf den Fisch soll mit einem Punkt belohnt werden. Der aktuelle Punktestand soll auf der Bühne angezeigt werden.

Dazu definieren wir zunächst eine neue Variable namens “Punkte”. Diese Variable kann als globale Variable angelegt werden (beim Anlegen der neuen Variablen “Für alle Figuren” wählen), da sie unabhängig von einem bestimmten Objekt ist.

Anschließend fügen wir die Anweisung “ändere Punkte um 1” zum Skript für das Ereignis “Wenn diese Figur angeklickt wird” hinzu, so dass bei jedem Mausklick auf den Fisch der Wert der Variablen “Punkte” um 1 hochgezählt wird:

Script

Damit wir den Punktestand während des Spiels sehen, muss das Kästchen links von der Variablen in der Block-Bibliothek angekreuzt werden:

Image

Der Punktestand soll zu Beginn jedes Spiels auf 0 zurückgesetzt werden. Dieses Skript wird am besten zur Bühne hinzugefügt, da es einmal beim Programmstart ausgeführt werden soll und sich nicht auf bestimmte Objekte bezieht, sondern nur auf globale Werte (für solche Skripte ist die Bühne der beste Ort, da sie ebenfalls “global” ist):

Script

Treffer zählen

Der Fisch soll höchstens dreimal gefangen werden können. Beim dritten Klick auf den Fisch soll er verschwinden und anschließend nicht wieder auftauchen.

Wenn der Fisch angeklickt wird und danach nur noch einen Treffer übrig hat, soll er außerdem rot eingefärbt werden. Dazu kann der Block “setze Effekt Farbe auf -25” verwendet werden:

Block

Um die Anzahl der bisherigen Treffer zu zählen, legen wir ein neue Variable namens “meine Treffer” an. Da diese Variable unmittelbar zum Objekt “Fisch1” gehört, definieren wir sie als Objektvariable (beim Anlegen der Variablen “Nur für diese Figur” auswählen). Sie stellt quasi ein neues Attribut des Objekts dar.

Außerdem ergänzen wir das Skript so, dass:

  • der Trefferwert zu Beginn auf 3 gesetzt wird, (1.)
  • der Trefferwert bei jedem Anklicken der Figur um 1 verringert wird, (2.)
  • die Figur nach ihrem Verschwinden nur dann wieder auftaucht, falls noch Treffer übrig sind, (3.)
  • und sie ihre Farbe ändert, falls der Trefferwert 1 erreicht. (4.)

Script

Um zu verhindern, dass das Objekt nach seinem letzten Verschwinden unnötigerweise unsichtbar weiter über die Bühne gleitet, wird die Endloswiederholung durch eine bedingte Wiederholung ersetzt, die endet, wenn der Trefferwert des Objekts 0 erreicht.

Nun möchten wir noch, dass mehrere Fische im Spiel vorhanden sind, die sich unabhängig voneinander bewegen. Das lässt sich einfach dadurch erreichen, dass das Objekt “Fisch1” in der Objektliste durch einen Rechtsklick dupliziert wird. Da die Variable “meine Treffer” als Objektvariable definiert wurde, hat jeder Fisch nun seine eigene Trefferanzahl. Hätten wir die Variable global definiert, würden sich alle Fische fälschlicherweise eine gemeinsame Trefferanzahl teilen.

Fachkonzept: Variable

Im allgemeinsten Sinne ist eine Variable in der Programmierung ein benannter Wertespeicher, der jeweils einen Wert zur Zeit speichern kann. Im Programmverlauf lässt sich lesend auf den Wert zugreifen oder der Wert überschreiben. Die Anweisung, um einen Wert in einer Variablen zu speichern, wird als Zuweisung bezeichnet. Der Wert einer Variablen kann beliebig oft durch Zuweisungen überschrieben werden (der Wert ist also “variabel”, daher auch die Bezeichnung).

Analogien für Variablen

Bildlich lassen sich Variablen anhand des “Tafel-Modells” (auch “Whiteboard-Modell”) gut veranschaulichen. Hier wird jede Variable durch eine kleine Tafel mit einem eindeutigen Namen repräsentiert, auf welcher der aktuelle Wert geschrieben steht. Initial steht auf jeder solchen Tafel hier der Wert 0. Bei jeder Zuweisung wird der aktuelle Wert auf der Tafel ausgewischt und durch den neuen Wert überschrieben.

Block

Image
Zustand der Variablen x zu Beginn
Image Image
Ausführen der Zuweisung “setze x auf 42”
Image Image
Ausführen der Zuweisung “setze x auf x / 2”

Bei der letzten Zuweisung wird zuerst der momentan vorhandene Wert von x gelesen, um die Division x / 2 zu berechnen, und anschließend der vorhandene Wert durch das Divisionsergebnis überschrieben.

Diese Analogie erklärt auch anschaulich, was passiert, wenn einer Variablen der Wert einer anderen Variable zugewiesen wird: Hier wird einfach der Inhalt der anderen Tafel abgeschrieben.

Block

Image
Image
Variablen x, y zu Beginn
Image
Image
Zuweisung “setze x auf 42”
Image
Image
Zuweisung “setze y auf x”
Image
Image
Zuweisung “setze x auf x / 2”

Die letzte Zuweisung ändert hier also nur den Wert von x, nicht den Wert von y.

Typische Fehlvorstellungen

Andere Metaphern eignen sich nur bedingt, da sie anfällig dafür sind, bestimmte Fehlvorstellungen von Variablen zu entwickeln. Verbreitet sind neben dem “Tafel-Modell” etwa das “Behälter-Modell” (auch “Schubladen-Modell” oder “Schachtel-Modell”) oder die Analogie zu Variablen in der Mathematik.2

Die Vorstellung einer Variable als Kiste oder Schublade, die einen Zettel mit dem gespeicherten Wert enthält, kann zu der Fehlvorstellung führen, dass eine Variable eine Liste bzw. Historie aller jemals in ihr gespeicherten Werte enthält. Tatsächlich enthält eine Variable zu jedem Zeitpunkt nur einen Wert.

Durch das Gleichsetzen von Variablen in der Programmierung und Variablen in mathematischen Gleichungen kann die Fehlvorstellung entstehen, dass Variablen durch Zuweisungen wie y = x + 1 logisch miteinander verknüpft werden – also eine spätere Änderung des Wertes der Variablen x gleichzeitig den Wert der Variablen y ändert.

Ein weiterer beliebter Fehler besteht darin, dass Bezeichner und Wert einer Variablen verwechselt werden, dass also beispielsweise angenommen wird, der Vergleich einer Variablen namens x mit dem Buchstaben “x” muss zwangsläufig wahr ergeben, auch wenn die Variable einen ganz anderen Wert enthält (siehe auch unten unter Zeichenkettenausdrücke). Tatsächlich zählt aber bei Vergleichen oder Berechnungen mit Variablen nur deren aktueller Inhalt, der klar von ihrem Namen unterschieden werden muss.

Ausdrücke

Als Ausdruck wird in der Programmierung ein Konstrukt bezeichnet, das sich zu einem Wert auswerten lässt – in Scratch repräsentiert durch Werteblöcke – also beispielsweise mathematische oder logische Ausdrücke.

Ohne Variablen hatten wir Ausdrücke bisher nur zum Ermitteln von Parameterwerten für Anweisungen oder Vergleiche verwendet. Mit Variablen können Berechnungsergebnisse nun gespeichert und an späterer Stelle oder in einem anderen Skript wiederverwendet werden, wodurch komplexere Berechnungen möglich sind. Die folgende Anweisung berechnet zum Beispiel den Radius eines Kreises aus der zuvor eingegebenen Kreisfläche entsprechend der Formel \(r = \sqrt{\frac{A}{\pi}}\) und speichert das Ergebnis in der Variablen “Radius”:

Block

Variablen können – wie oben gesehen haben – beliebige Werte speichern und sogar verschiedene Arten von Werten, beispielsweise Zahlen oder Texte. Die Arten von Werten werden als Datentypen bezeichnet. Üblicherweise wird eine Variable im Programm nur zum Speichern von Werten eines bestimmten Datentyps verwendet, die von ihrem Verwendungszweck abhängt (z. B. ganze Zahlen für eine Variable, die einen Punktestand darstellt oder die Anzahl richtiger Antworten zählt). Das ist aber nur eine Konvention – Variablen sind in Scratch aber nicht per se festgelegt auf einen bestimmten Datentyp.

Die Ergebnisse von Berechnungen hängen zum einen vom Operator (z. B. Divisionsoperator /) und zum anderen von den Werten der Operanden ab. Ob und wie ein Operator auf seine Operanden angewendet werden kann, hängt dabei auch von den Datentypen der Operanden ab, also welche Art von Wert sie haben. Beispielsweise macht es keinen Sinn, den Divisionsoperator / auf eine Zahl und einen Text anzuwenden. Das gilt auch für Parameterwerte von Anweisungen und Kontrollstrukturen: Die Kontrollstruktur “wiederhole … mal” erwartet beispielsweise eine Ganzzahl, die Anweisung “gehe … Schritt” eine Zahl.

Datentypen

Scratch unterscheidet die folgenden drei Datentypen für Werte:

  • Zahlen: Zahlenwerte dienen als Operanden und Berechnungsergebnisse mathematischer Funktionen oder als numerische Parameterwerte für Anweisungen (z. B. “Wie viele Sekunden soll gewartet werden?”, “Wie oft soll wiederholt werden?”, “Um wie viel Grad soll gedreht werden?”). Sie können noch weiter unterschieden werden in Ganzzahlen und Dezimalzahlen (Achtung: Dezimalzahlen werden in Scratch nicht mit einem Komma geschrieben, sondern mit einem Dezimalpunkt, z. B. 3.1416).
  • Zeichenketten: Zeichenketten (engl. string) sind Aneinanderreihungen von Zeichen wie Buchstaben, Ziffern, Satz- und Sonderzeichen und werden allgemein zur Darstellung von Texten verwendet (z. B. Fragen und Antworten, Namen, Mitteilungen).
  • Wahrheitswerte: Die beiden Werte wahr oder falsch (auch: Boolesche Werte) werden für Bedingungen verwendet und können als Berechnungsergebnisse von Vergleichen und logischen Verknüpfungen entstehen.

Mathematische Ausdrücke

In Scratch stehen die gängigsten mathematischen Operatoren und Funktionen in der Kategorie “Operatoren” (grün) zur Verfügung:

Block Block Block BlockWenn diese Blöcke abgefragt werden, geben Sie das Berechnungsergebnis für die beiden enthaltenen Werte an (Addition, Subtraktion, Multiplikation oder Division).
BlockWird dieser Block abgefragt, gibt er den Teilungsrest der ganzzahligen Division von a durch b an, wobei a und b die beiden enthaltenen Werte sind.
BlockWird dieser Block abgefragt, gibt er den enthaltenen Wert gerundet auf die nächste Ganzzahl an.
BlockWird dieser Block abgefragt, gibt er das Berechnungsergebnis für den enthaltenen Wert an, wobei verschiedene Funktionen ausgewählt werden können (über das Symbol ▾), u. a.: Betrag, ab-/aufrunden, Wurzel, Sinus, Kosinus, Tangens, Logarithmus und Exponentialfunktion.
BlockWird dieser Block abgefragt, gibt er eine zufällig ausgewählte Zahl zwischen a und b an, wobei a und b die beiden enthaltenen Zahlen sind. Wenn a und b beide Ganzzahlen sind, wird eine Ganzzahl ausgewählt, sonst eine Dezimalzahl.

Die mathematischen Operatoren haben alle einen Zahlenwert als Ergebnis und erwarten generell Zahlenwerte als Operanden. Wird stattdessen eine Zeichenkette als Parameterwert verwendet, die nicht als Zahlenwert interpretiert werden kann, wird sie wie der Zahlenwert 0 behandelt, wie das folgende Beispiel zeigt:

Image

Zeichenkettenausdrücke

Für Zeichenketten gibt es eigene Werteblöcke in der Kategorie “Operatoren”, etwa um mehrere Zeichenketten verbinden oder um bestimmte Eigenschaften von Zeichenketten zu überprüfen:

BlockWird dieser Block abgefragt, gibt er die Aneinanderhängung (Konkatenation) der beiden enthaltenen Zeichenketten-Werte an. Für “Apfel” und “Banane” wird beispielsweise die Zeichenkette “ApfelBanane” als Wert zurückgegeben.
BlockWird dieser Block abgefragt, gibt er das n-te Zeichen der angegebenen Zeichenkette an, wobei n die im linken Feld angegebene Zahl ist. Für die Werte 1 und “Apfel” wird beispielsweise das Zeichen “A” zurückgegeben.
BlockWird dieser Block abgefragt, gibt er die Länge der angegebenen Zeichenkette an, also die Anzahl ihrer Zeichen. Für “Apfel” wird beispielsweise der Zahlenwert 5 zurückgegeben.
BlockWird dieser Block abgefragt, gibt er als Wahrheitswert an, ob die links angegebene Zeichenkette die rechts angegebene Zeichenkette enthält. Groß- und Kleinschreibung spielt dabei keine Rolle. Für “Apfel” und “a” wird also wahr zurückgegeben, für “Apfel” und “PF” ebenfalls. Dabei muss die Reihenfolge der Operanden berücksichtigt werden – für links “fel” und rechts “Apfel” wird beispielsweise falsch zurückgegeben, andersherum dagegen wahr.

Als Operanden können hier natürlich auch andere Werteblöcke und Variablenblöcke verwendet werden. Die Auswertung macht allerdings nur Sinn, wenn die Blöcke Werte der erwarteten Datentypen zurückgeben. Das folgende Beispiel speichert zunächst die Eingaben auf zwei “frage”-Anweisungen in den beiden Variablen namens “Nadel” und “Heuhaufen” und wertet dann die Bedingung aus, ob der Wert der einen Zeichenfolge in der anderen enthalten ist:

Block

In diesem Beispiel liegt es nahe zu denken, dass die Bedingung niemals erfüllt sein kann, da die Zeichenkette “Nadel” ja nicht in der Zeichenkette “Heuhaufen” enthalten ist. Aber Achtung: Das sind die Namen der Variablen und nicht ihre Werte! Die Werte der Variablen sind hier die beiden Texte, die wir jeweils als Antwort auf die beiden “frage”-Anweisungen eingeben. Geben wir zum Beispiel bei der ersten Frage “Speisekammer” und bei der zweiten “Eis” ein, so wird in der Variablen namens “Heuhaufen” der Wert “Speisekammer” und in der Variablen namens “Nadel” der Wert “Eis” gespeichert, so dass die Bedingung “[Wert der Variablen] Heuhaufen enthält [Wert der Variablen] Nadel” zu wahr ausgewertet wird.

Wird ein Zahlenwert für einen Parameter verwendet, für den eine Zeichenkette erwartet wird, so wird er intuitiv als Zeichenkette interpretiert, wie das folgende Beispiel zeigt:

Image

Logische Ausdrücke

In Lektion 2 haben wir bereits logische Ausdrücke kennengelernt, die als Bedingungen für die bedingte Wiederholung und Fallunterscheidung verwenden werden. Neben den Wahrheitswerteblöcken können mit Hilfe der logischen Operatoren auch logische Ausdrücke aus anderen Werten zusammengesetzt werden, etwa durch die Vergleichsoperatoren:

Block Block Block

und die logischen Verknüpfungsoperatoren:

Block Block Block

Die Operanden für die Vergleichsoperatoren können sowohl Zahlen als auch Zeichenketten sein. Der Datentyp der beiden Operanden sollte aber gleich sein. Die logischen Verknüpfungen erwarten dagegen Wahrheitswerte als Operanden. Das Ergebnis aller logischen Ausdrücke ist immer ein Wahrheitswert.

Anwendungsfälle für Variablen

Obwohl Variablen prinzipiell für beliebige Zwecke verwendet werden können, gibt es in der Praxis eine Reihe typischer Anwendungsfälle für Variablen, die wir teils hier bereits kennengelernt haben und teils in den praktischen Übungen vertiefen werden. Solche typischen Anwendungsfälle sind unter anderem:

  • Zählen bestimmter Ereignisse oder Daten (“Zählvariable”), zum Beispiel: Wie viele Züge hat eine Spielfigur bereits gemacht, wie viele Wiederholungen wurden ausgeführt oder wie oft wurde eine bestimmte Taste gedrückt?
  • Aufsummieren von Werten während der Programmausführung (“Akkumulatorvariable”), zum Beispiel: Es soll der Gesamtwert oder der Mittelwert eine Messreihe berechnet werden. Die Einzelmessungen werden hier in einer Variablen zusammensummiert und am Ende ausgegeben.
  • Speichern von (Zwischen-)Ergebnissen von Berechnungen, die in mehreren Schritten durchgeführt werden
  • Anlegen von “Sicherungskopien” von Werten zu einem bestimmten Zeitpunkt, die später benötigt werden und sich in der Zwischenzeit ändern könnten, zum Beispiel: die Antwort, die auf die letzte Frage eingegeben wurde (die anderenfalls bei der nächsten Frage verloren gehen würde)
  • Informationen über Objekte oder das Programm, die sich im Laufe der Zeit ändern können, zum Beispiel: die momentane Bewegungsgeschwindigkeit oder Beschleunigung eines Fahrzeugs, der einstellbare Schwierigkeitsgrad eines Spiel

Beispiele

Das folgende Beispiel für eine Zählvariable zählt in der Variablen “Anzahl Mausklicks”, wie oft ein Objekt nach dem Programmstart angeklickt wird:

Block

Das folgende Beispiel demonstriert eine Akkumulatorvariable “Summe Noten”. Hier wird die Summe von 10 Noten berechnet, um die Durchschnittsnote zu ermitteln:

Block

Variablen für Konstanten

Auch für Parameterwerte, die sich im Programmablauf selbst nicht ändern (also konstante Werte), kann es sich anbieten, den Wert in einer Variablen zu speichern, statt ihn direkt in die Anweisungen zu schreiben. So können Sie den Wert später schnell ändern, wenn sich herausstellt, dass er zu klein oder zu groß gewählt wurde. Anderenfalls müssten Sie den Wert manuell an jeder Stelle im Programm anpassen, an der er verwendet wird, was aufwendig und fehleranfällig ist.

Zum Beispiel: Alle Objekte im Spiel sollen mit 8 Bildern pro Sekunde animiert werden. Also hat jedes Objekt ein Skript der Form:

Script

Stellen wir nun später fest, dass die Animationen zu langsam wirken, müssten wir den Parameterwert von “warte” in allen Skripten anpassen. Es ist also hilfreich, die Bildrate in einer globalen Variable zu speichern und deren Wert in allen Animationsskripten zu verwenden, statt einem festen Wert:

Script

Die Variable können wir manuell mit dem Wert 8 belegen, indem wir den entsprechenden Block in der Block-Bibliothek anklicken, oder ihn zu Beginn des Startskripts der Bühne ausführen lassen:

Script

Um die Bildrate aller Animationen im Programm nun nachträglich zu verändern, reicht es, einmal den Wert der Variablen “Bildrate” anzupassen.

Überprüfen von Programmen

Um den Ablauf von Programmen oder Programmabschnitten, deren Verhalten von ihren Variablenbelegungen abhängt, besser nachvollziehen zu können und gegebenenfalls Fehler in der Programmierung zu finden, kann es hilfreich sein, die Wertebelegungen der Variablen über die Zeit zu protokollieren. Ein einfaches Werkzeug dafür stellen die sogenannten Trace-Tabellen dar.

Trace-Tabellen

Eine Trace-Tabelle (auch: Ablaufverfolgungstabelle) protokolliert die Änderungen von Variablenwerten während des Programmablaufs in tabellarischer Form. Jede Spalte stellt dabei eine Variable dar, deren Zustand beobachtet werden soll. Die Zeilen stellen diejenigen Anweisungen dar, durch die sich ein beobachteter Wert ändert – bei Variablen also also Variablenzuweisungen. Die Anweisungen werden dabei in der Tabelle zeilweise in genau der Reihenfolge dargestellt, in der sie während der Programmausführung abgearbeitet werden.

Neben Variablen lassen sich auch andere Werteblöcke (z. B. Attribute von Objekten, der Wert des “Antwort-Blocks) in der Tracetabelle beobachten, wenn sie für den Programmablauf relevant sind und sich ihre Werte während des Ablaufs ändern.

Der folgende Abschnitt eines Scratch-Skripts (links) berechnet die Summe von mehreren Noten, die nacheinander eingegeben werden, in der Variablen “Summe Noten”. Die Anzahl der Noten wird zu Beginn eingegeben und am Ende die Durchschnittsnote angezeigt. Um den Ablauf besser nachzuverfolgen, nummerieren wir hier die Zuweisungen und “frage”-Anweisungen von oben nach unten durch und listen die Nummer der Anweisung, auf die sich die Zeile bezieht, in der Trace-Tabelle mit auf (rechts).

Testhalber vollziehen wir den Ablauf hier für die folgende Sequenz von Eingaben bei den “frage”-Anweisungen nach: 4, 2, 1, 3, 3. Hier sollte also der Mittelwert der vier Noten 2, 1, 3 und 3 berechnet werden. Anhand der Trace-Tabelle lässt sich nachvollziehen, dass hier am Ende das Ergebnis von 9 / 4, also korrekterweise der Wert 2.25 angezeigt wird.

Script Table

Hinweise zur Darstellung der Trace-Tabelle: Vor der ersten Zuweisung im beobachteten Programmabschnitt ist der momentane Wert der Variablen unbekannt (analog der Wert des “Antwort”-Blocks vor der ersten “frage”-Anweisung). Der Eintrag ? in der Trace-Tabelle kennzeichnet einen unbekannten Wert. Die hellgrün hinterlegten Felder kennzeichnen, dass ein Wert zugewiesen wird und den vorigen Wert überschreibt.


  1. Die Begriffe “lokale Variable” und “private Variable” haben in der objektorientierten Programmierung üblicherweise eine andere Bedeutung, weswegen hier vorrangig der formal richtige Begriff “Objektvariable” verwendet wird. ↩︎

  2. siehe Peer Stechert: Fehlvorstellungen und Modelle bei Variablen aus der Reihe Informatikdidaktik kurz gefasst (Teil 15), Video bei YouTube ↩︎

1.4.1 Übungsaufgaben

Stoffwiederholung

Aufgabe 1: Datentypen

In dieser Lektion haben wir verschiedene Datentypen von Werten identifiziert, nämlich Zahlen (Ganzzahlen und Dezimalzahlen), Zeichenketten (Texte) und Wahrheitswerte. Ausdrücke und parametrisierte Anweisungen erwarten in der Regel bestimmte Datentypen für ihre Parameterwerte. Werden trotzdem Werte von anderen Datentypen übergeben, werden diese zum erwarteten Datentyp uminterpretiert.

Datentypen für Parameter- und Rückgabewerte

Geben Sie für die folgenden Blöcke jeweils an, welche Datentypen für ihre Parameterwerte erwartet werden. Geben Sie bei den Werteblöcken ebenfalls an, welchen Datentyp der Rückgabewert hat (Zahl, Zeichenkette oder Wahrheitswert).

Überlegen Sie auch, ob in diesen Beispielen Parameter vorkommen, bei denen nur eine Ganzzahl als Wert sinnvoll ist (das heißt, Dezimalzahlen als Eingabe würden gerundet werden).

Anweisungen und Kontrollstrukturen
BlockParameter 1: ____________________________ Parameter 2: ____________________________ Parameter 3: ____________________________
BlockParameter 1: ____________________________ Parameter 2: ____________________________
BlockParameter 1: ____________________________
BlockParameter 1: ____________________________
Werteblöcke (Ausdrücke)
BlockParameter 1: ____________________________ Parameter 2: ____________________________ Rückgabewert: ____________________________
BlockParameter 1: ____________________________ Parameter 2: ____________________________ Rückgabewert: ____________________________
BlockParameter 1: ____________________________ Parameter 2: ____________________________ Rückgabewert: ____________________________
BlockParameter 1: ____________________________ Parameter 2: ____________________________ Rückgabewert: ____________________________

Aufgabe 2: Programm mit Variablen überprüfen

Das folgende Scratch-Skript ( Download) soll die Summe von mehreren nicht-negativen Messwerten (z. B. Zeit- oder Längenmessungen) berechnen, die nacheinander abgefragt werden. Die Abfrage wird durch Eingabe einer Zahl < 0 beendet. Diese Zahl soll dabei nicht mehr zur Summe dazugezählt werden, sondern dient nur dazu, dem Programm mitzuteilen, dass alle Messwerte eingegeben wurden. Zu Beginn wird außerdem noch nach der Maßeinheit gefragt (z. B. “ms” oder “cm”), die am Ende zusammen mit der berechneten Summe angezeigt wird.

Block

Trace-Tabelle erstellen

Vollziehen Sie den Ablauf des Skripts mit Hilfe einer Trace-Tabelle nach. Protokollieren Sie dabei die Werteänderungen der Variablen “Summe” und “Einheit”, sowie des Werteblocks “Antwort” während des Programmablaufs.

Gehen Sie dabei davon aus, dass der Reihe nach die folgenden Eingaben bei den “frage”-Anweisungen stattfinden: cm, 10, 12, 8, -1

Fehler korrigieren

Welcher Wert wird als Ergebnis erwartet und welcher wird am Ende angezeigt? Finden Sie anhand der Ergebnisse der Trace-Tabelle den Fehler im Skript und korrigieren Sie das Skript geeignet.

Aufgabe 3: Fehlvorstellungen zu Variablen

Das folgende Beispiel stammt aus dem Schulalltag:1 Beim ersten Aufruf der folgenden Sequenz wird 0 angezeigt. Die Schülerinnen und Schüler hatten aber mit 12 gerechnet. Beim zweiten Aufruf wird plötzlich wie erwartet 12 angezeigt. Jetzt sind alle vollständig verwirrt.

Block

Erklären Sie das Szenario und erläutern Sie, welche Fehlvorstellung von Variablen dem Missverständnis zugrundeliegt. Geben Sie außerdem die korrekte Version der Sequenz an.

Praktische Übungen

Aufgabe 4: Kopfrechenquiz

Laden Sie das Scratch-Projekt Kopfrechenquiz.sb3 herunter und untersuchen Sie das Skript der Figur: Download

Das Projekt setzt ein einfaches Kopfrechenquiz um, in dem nacheinander ein Startwert und mehrere Rechenoperationen genannt werden, die im Kopf berechnet werden sollen. Die Abfolge der Rechenoperationen ist hier fest, für den Startwert und die Operanden werden aber jeweils zufällig ausgewählte Zahlen ausgegeben.

Image

Das Quiz könnte also beispielsweise folgendermaßen ablaufen (die richtige Antwort wäre hier 8):

  • Wir starten mit 4
  • Addiere 8
  • Multipliziere mit 5
  • Subtrahiere 2
  • Dividiere durch 7
  • Wie lautet das Ergebnis gerundet?
Ergebnis überprüfen

Bisher gibt es noch keine Möglichkeit, das am Ende eingegebene Ergebnis zu überprüfen, da das erwartete Ergebnis momentan gar nicht vom Programm selbst berechnet wird.

Passen Sie das Skript also mit Hilfe von Variablen so an, dass das richtige Ergebnis begleitend zu den Ausgaben berechnet wird. Am Ende soll dann eine Rückmeldung gegeben werden, ob die eingegebene Antwort richtig oder falsch war.

Hilfreich für diese Aufgabe sind neben den Variablenblöcken die Operatorblöcke für mathematische Ausdrücke:

Block Block Block Block Block

Lösungstipps

Aufgabe 5: Zeitmessung

Laden Sie das Scratch-Projekt Reaktionstest.sb3 herunter: Download

Das Programm stellt einen Reaktionstest dar: Der rote Kreis erscheint nach einer zufällig ausgewählten Zeit. Sobald er erscheint, wird die Stoppuhr auf Null zurückgesetzt. Sobald der Kreis nach seinem Erscheinen angeklickt wird, beginnt das Spiel von vorne: Die Stoppuhr wird zurückgesetzt, der Kreis verschwindet und wird nach einer zufällig ausgewählten Zeit wieder sichtbar. Ziel ist es, den Kreis so schnell wie möglich nach seinem Erscheinen anzuklicken. Wird zu früh geklickt oder statt des Kreises die Bühne angeklickt, endet das Spiel.

Image

Durchschnittliche Reaktionszeit ermitteln

Passen Sie das Programm mit Hilfe von Variablen so an, dass die durchschnittliche Zeit, die benötigt wird, um den Kreis nach seinem Erscheinen anzuklicken, gespeichert und während der Programmausführung auf der Bühne angezeigt wird. Definieren Sie dazu ggf. weitere Hilfsvariablen.

Neben den Blöcken aus der Kategorie “Variablen” sind die folgenden Blöcke zur Zeitmessung für diese Aufgabe hilfreich:

BlockDieser Anweisungsblock setzt die Stoppuhr zur Zeitmessung auf Null zurück.
BlockDieser Werteblock liefert die Anzahl an Sekunden zurück, die seit dem Programmstart oder letzten Zurücksetzen der Stoppuhr vergangen sind.
Lösungstipps

Aufgabe 6: Spiel um Variablen ergänzen

In dieser Aufgabe soll das Spiel Flappy Seagull aus der vorigen Übung um mehrere Variablen ergänzt werden.

Laden Sie dazu das Scratch-Projekt Flappy_Seagull.sb3 herunter: Download

Die Figuren in diesem Spiel agieren mit einer Animationsrate von 20 Schritten pro Sekunde, da in der Wiederholung in den Skripten der Figuren am Ende jeweils 1/20 Sekunde gewartet wird.

Das Spiel soll nun um die folgenden Funktionen ergänzt werden:

  • Die Möwe darf dreimal von der Gegner-Figur berührt werden, bevor das Spiel vorbei ist. Die Anzahl, wie oft sie noch berührt werden darf, soll dabei auf dem Bildschirm sichtbar sein.
  • Wenn die Möwe berührt wird, soll sie für eine kurze Zeit vor weiteren Treffern geschützt sein. Der Schutz soll dabei 20 Animationsschritte andauern.
    • Damit besser erkennbar ist, wann die Möwe geschützt ist, soll sie während dieser Zeit mit einer Transparenz von 50% angezeigt werden.
  • Zusatzaufgabe: Führen Sie eine neue globale Variable für die Animationsrate ein, die von allen Figuren statt dem festen Wert von 20 Schritten pro Sekunde genutzt wird.
    • Die Spielgeschwindigkeit soll während der Programmausführung anpassbar sein, indem mit den Pfeiltasten nach oben und unten der Wert der Animationsrate vergrößert und verringert wird (Achtung: Der Wert sollte nicht kleiner als 1 werden können!).

Das folgende Video demonstriert, wie das Spiel mit den Anpassungen aussehen könnte:

Lösungstipps

  1. Quelle: Informationssammlung zur Informatikdidaktik der Pädagogischen Hochschule Schwyz: Fehlvorstellungen beim Programmieren, https://mia.phsz.ch/Informatikdidaktik/MisconceptionsInProgramming
    der korrigierte Link zu Juha Sorva: Misconception Catalogue lautet http://urn.fi/URN:ISBN:978-952-60-4626-6 (Appendix A, S. 358 ff.) ↩︎

1.5 Strukturierung von Programmen

In den vorigen Lektionen haben wir Techniken kennengelernt, um eine Aufgabenstellung in kleinere Bausteine zu zerlegen, etwa in Abschnitte, die wiederholt oder bedingt ausgeführt werden, sowie Teilprogramme, die beim Eintreten bestimmter Ereignisse ausgeführt werden. In dieser Lektion werden wir selbst definierte Anweisungen (Unterprogramme/Methoden) und selbst definierte Ereignisse (Nachrichten/Signale) in Scratch einführen, um komplexere Programme zu strukturieren.

Ein Ziel der Programmstrukturierung ist es, Programme leichter lesbar zu machen und Code-Redundanzen zu vermeiden – also Programmteile, die als Kopie an mehreren Stellen im Programm vorkommen, was nicht nur den Programmumfang vergrößert, sondern auch zu Problemen führt: Soll nachträglich eine Änderung an einer Stelle im Programm vorgenommen werden, muss diese ggf. an mehreren anderen Stellen ebenfalls durchgeführt werden. Das ist zum einen zeitaufwendig, zum anderen potenziell fehleranfällig, da schnell eine Stelle übersehen werden kann.

Kommentare

Um umfangreichere Programme besser nachvollziehbar zu machen, kann es helfen, Programmabschnitte mit Kommentaren zu versehen, also kurzen Anmerkungen, in denen die Bedeutung eines Programmabschnitts zusammengefasst wird. In Scratch lassen sich Kommentare durch einen Rechtsklick in den Skriptbereich in Form von “Notizzetteln” einfügen.

Script

In so gut wie allen moderen Programmiersprachen ist es möglich, Textkommentare zum Quellcode hinzuzufügen.

Unterprogramme

Komplexere Programm können schnell unübersichtlich werden. Insbesondere kann es vorkommen, dass bestimmte Programmabschnitte zum Lösen derselben Aufgabe an mehreren Stellen vorkommen, was den Programmumfang unnötig vergrößert.

Als anschauliches Beispiel dient hier das Grundgerüst für ein Jump & Run-Spiel in Scratch. Laden Sie das Projekt Jump_and_Run.sb3 hier herunter: Download

In diesem Spiel wird eine Figur gesteuert, die sich mit den Pfeiltasten nach links und rechts bewegen lässt und außerdem springen kann.

Image

Das Springen der Figur kann durch mehrere Ereignisse ausgelöst werden:

  • Als Eingabe wird die Pfeiltaste nach oben gedrückt oder die Figur wird mit der Maus angeklickt.
  • Die Figur berührt den Stein. Hier wird die Figur anschließend auf die Startposition zurückgesetzt.

Die Aktion “springen” besteht dabei aus zwei Wiederholungen für die Auf- und Abwärtsbewegung, die innerhalb einer bedingten Anweisung stehen (“Befindet sich die Figur momentan auf dem Boden?”). Der entsprechende Programmabschnitt kommt also an drei verschiedenen Stellen im Programm in exakt gleicher Form vor:

Script

Um den Umfang des Programm zu verringern wäre es also hilfreich, die Anweisungen der “springen”-Aktion zu einer neuen Anweisung zusammenzufassen. Das lässt sich in Scratch mit Hilfe selbst definierter Blöcke umsetzen. Ein solcher “eigener” Block definiert ein Unterprogramm, das von anderen Skripten des Objekts mit Hilfe eines speziellen Anweisungsblocks ausgeführt werden kann. Ein Unterprogramm, das zu einem Objekt gehört, wird auch als Methode dieses Objekts bezeichnet.

Unterprogramme definieren und aufrufen

Ein Unterprogramm muss zunächst definiert werden. Dazu wird in Scratch die Schaltfläche “Neuer Block” in der Kategorie “Meine Blöcke” (rot) ausgewählt und ein eindeutiger Bezeichner für den neuen Block vergeben. Wie bei eigenen Variablen sollte der Bezeichner möglichst aussagekräftig sein (hier zum Beispiel “springe”). Anschließend erscheint ein neuer Anweisungsblock in der Block-Bibliothek unter “Meine Blöcke”.

Block

Außerdem erscheint im Skriptbereich des Objekts ein neuer Definitionsblock, der wie Ereignisblöcke eine “Kopfblockform” hat:

Block

An diesen Block können nun die Anweisungen des Unterprogramms angehängt werden. Der neue Anweisungsblock kann nun in anderen Skripten des Objekts zum Aufruf des Unterprogramms verwendet werden. Wenn dieser Block ausgeführt wird, werden die Anweisungen im Unterprogramm ausgeführt. Das aufrufende Skript pausiert dabei, bis das Unterprogramm zuende ausgeführt wurde und fährt danach erst fort.

Damit lässt sich das Beispielprogramm deutlich vereinfachen: In diesem Fall wird die Anweisungssequenz, welche die Sprung-Aktion darstellt, als Skript an den Definitionsblock angehängt. An den drei Programmstellen, an denen die Sprung-Aktion ausgeführt werden soll, wird nun stattdessen nur der selbst definierte Anweisungsblock “springe” eingefügt:

Script

Im aufrufenden Skript sieht der Aufruf des Unterprogramms nun also genauso aus wie jede andere elementare Anweisung (z. B. “gehe zu Position”, “setze Richtung auf”, “warte”). Das Unterprogramm “verkapselt” dabei die eigentlichen Anweisungen, die beim Aufruf ausgeführt werden. Solange wir wissen, was der Effekt des Anweisungsblock ist (in diesem Fall “führe innerhalb von 1 Sekunde eine Sprungbewegung aus, falls die Figur sich auf dem Boden befindet”), können wir den Block zur Programmierung verwenden ohne genau wissen zu müssen, wie dieser Effekt konkret umgesetzt wird.

Ein Unterprogramm ist also allgemein ein wiederverwendbarer Programmabschnitt, der an anderen Stellen im Programm aufgerufen werden kann, um eine bestimmte Aufgabe zu übernehmen. Wir unterscheiden dabei die Definition des Unterprogramms (“Was macht es?”) vom eigentlichen Aufruf des Unterprogramms (“Mach es!”).

Unterprogramme mit Parametern

Oft sind Programmabschnitte zum Lösen bestimmter Aufgaben, die an verschiedenen Stellen im Programm vorkommen, nicht exakt identisch, sondern hängen von bestimmten Werten ab. Als Beispiel: Angenommen, die Sprung-Aktion aus dem vorigen Abschnitt soll mit verschiedenen Sprunghöhen durchgeführt werden – beim Drücken der Pfeiltaste oder Maustaste soll die Figur 10 Schritte hoch springen, beim Berühren des Steins dagegen nur 5 Schritte. Das Unterprogramm hängt nun also von einem Parameter ab, in diesem Fall der Sprunghöhe.

Zu diesem Zweck lassen sich parametrisierte Unterprogramme, also Unterprogramme mit Parametern definieren. Beim Aufruf eines parametrisierten Unterprogramms werden – genau wie bei den bisher bekannten parametrisierten Anweisungen in Scratch1 – verschiedene Parameterwerte angegeben, die bei der Ausführung des Unterprogramms berücksichtigt werden können. Dazu müssen beim Erstellen eines eigenen Blocks im Dialog “Neuer Block” entsprechende Eingabefelder für Parameter angelegt werden. Dabei stehen ovale Eingabefelder für Zahlenwerte und Zeichenketten, sowie sechseckige Eingabefelder für Wahrheitswerte zur Verfügung:

Dialog

In diesem Beispiel wird ein ovales Eingabefeld namens “N” für die Anzahl der Sprungschritte hinzugefügt.2 Im Unterprogramm-Skript wird nun der Wert des Parameters N (statt wie zuvor der feste Wert 10) in der ersten Wiederholung verwendet. Dazu muss der entsprechende Werteblock Block für diesen Parameter aus dem Definitionsblock in das Eingabefeld der Wiederholung gezogen werden:

Script

Beim Aufruf des Unterprogramms können nun – wie bereits von normalen Anweisungen bekannt – verschiedene Werte im Eingabefeld angegeben werden, die jeweils bei der Ausführung des Unterprogramms als Wert für den Parameter N verwendet werden und für unterschiedlich hohe Sprünge sorgen:

Script

Das Verhalten von Unterprogrammen ist also durch Parameter variierbar.
Parameter sind dabei aus Sicht von Unterprogrammen lokale Variablen, denen beim Aufruf des Unterprogramms Werte zugewiesen werden. Diese Variablen können nur innerhalb des Unterprogramms verwendet werden (daher “lokal”). In Scratch können Sie außerdem – im Gegensatz zu normalen Variablen – während der Ausführung des Unterprogramms nur gelesen, aber nicht überschrieben werden.

Vorteile von Unterprogrammen

Unterprogramme eignen sich also, um Programme übersichtlich zu strukturieren und zusammengehörende Programmteile zusammenzufassen (“Modularisierung” von Programmen). Dadurch kann ein Programm in kleinere Bausteine zerlegt werden, die sich unabhängig voneinander entwickeln lassen und zu komplexeren Programmen zusammensetzen lassen.

Durch Unterprogramme lässt sich Code-Redundanz vermeiden und Programme werden besser wartbar, da nachträgliche Änderungen am Unterprogramm nur in dessen Definition durchgeführt werden müssen. Programme werden außerdem leichter testbar, indem zunächst die Unterprogramme als kleinere Einheiten getestet werden.

Ein entscheidender Vorteil ist auch die Wiederverwendbarkeit: Ein einmal entwickeltes Unterprogramm für eine bestimmte Aufgabe kann immer dann, wenn diese Aufgabe gelöst werden soll, einfach aufgerufen werden, anstatt dass der Inhalt noch einmal neu programmiert werden muss.

So erleichtern Unterprogramme auch die Zusammenarbeit, wenn viele Menschen an einem Projekt arbeiten: Hier kann zuerst überlegt werden, wie sich das Programm am besten in Unterprogramme aufteilen lässt, und anschließend werden die einzelnen Unterprogramme auf mehrere Teams verteilt und parallel entwickelt.

Nachrichten

In komplexeren Scratch-Projekten kann es nötig sein, dass ein Skript eines Objekts eine Aktion eines anderen Objekts auslösen soll. Das ist bisher nicht möglich: Wird beispielsweise ein Objekt angeklickt, kann in dem Skript, das auf dieses Ereignis reagiert, nur das Objekt selbst bewegt oder verändert werden, aber nicht ein anderes Objekt. Ebenso können Objekte in Scratch nur ihre eigenen Methoden direkt aufrufen, aber nicht Methoden von anderen Objekte (“fremder Methodenaufruf”).

Um einem Programmierobjekt mitzuteilen, dass es etwas machen soll, werden in der Programmierung Signale verwendet, die in Scratch als Nachrichten bezeichnet werden. Ein solches Signal kann mit einer speziellen Anweisung von einem Objekt ausgesendet werden und von allen Objekten empfangen werden kann (“Broadcasting”). Für Objekte kann über einen bestimmten Ereignisblock angegeben werden, auf welche Signale sie wie reagieren sollen. Nachrichten ermöglichen es also, “eigene” Ereignisse zu definieren, mittels derer verschiedene Figuren und die Bühne miteinander kommunizieren und auf einander reagieren können.

Image

Nachrichten erstellen und versenden

Um eine Nachricht an alle Objekte zu senden, wird die Anweisung “sende Nachricht an alle” aus der Kategorie “Ereignisse” (gelb) verwendet. Die Nachricht selbst kann über die Auswahlliste ▾ ausgewählt werden. Mit der Option “Neue Nachricht” kann eine neue Nachricht erstellt werden, hier beispielsweise eine Nachricht namens “Alarm”:

Block

Wird diese Anweisung in einem Skript ausgeführt, so wird die Nachricht “Alarm” an alle Objekte (auch an den Sender selbst) gesendet und sofort mit der nächsten Anweisung im Skript weitergemacht.

Soll das Skript dagegen warten, bis alle Objekte ihre Reaktion auf die Nachricht zuende ausgeführt haben, bevor es mit seiner nächsten Anweisung fortfährt, wird stattdessen die Anweisung “sende Nachricht an alle und warte” verwendet:

Block

Auf Nachrichten reagieren

Um Reaktionen auf bestimmte Nachrichten zu programmieren, gibt es einen speziellen Ereignisblock in der Kategorie “Ereignisse”:

Script

Das angehängte Skript wird ausgeführt, sobald das Objekt die angegebene Nachricht empfängt (hier die Nachricht namens “Alarm”). Ein Objekt kann dabei auch unterschiedlich auf verschiedene Nachrichten reagieren, indem Ereignisblöcke mit verschiedenen Nachrichtenbezeichnern im Skriptbereich angelegt werden:

Script

Das obenstehende Beispiel lässt ein Objekt verschwinden, wenn ein anderes Objekt die Nachricht “Alarm” sendet, und wieder erscheinen, wenn ein anderes Objekt die Nachricht “Entwarnung” sendet.

Beispiel: Kommunikation zwischen Objekten

Das folgende Beispiel demonstriert die Kommunikation zwischen mehreren Objekten anhand von Nachrichten. Sie können das Scratch-Projekt Kommunikation.sb3 hier herunterladen: Download

Hier befinden sich drei Figuren auf der Bühne (“Alice”, “Bob” und “Carol”), die Personen in einem Netzwerk darstellen. Beim Anklicken einer Figur sollen jeweils beide oder eine andere Figur reagieren.

Ein-Weg-Kommunikation

Wenn die Figur “Carol” angeklickt wird, sollen (nachdem sie eine Mitteilung angezeigt hat) Aktionen der Figuren “Alice” und “Bob” ausgelöst werden: Beide sollen ebenfalls eine Mitteilung anzeigen.

Image

Dazu wird im Skriptbereich von “Carol” definiert, dass beim Eintreten des Ereignisses “Wenn ich angeklickt werde” eine Nachricht “stellt euch vor” gesendet wird:

Script

Die Reaktion der anderen beiden Figuren auf diese Nachricht wird in deren Skriptbereich mit dem Ereignisblock “wenn ich … empfange” definiert:

Script

Script

Diese Art der Kommunikation wird als “Ein-Weg-Kommunikation” bezeichnet, da das sendende Objekt kein Antwortsignal von den empfangenden Objekten erwartet. Der zeitliche Ablauf der Skriptausführung lässt sich in dem folgenden Balkendiagramm nachvollziehen:

Script

Zwei-Wege-Kommunikation

In der “Zwei-Wege-Kommunikation” synchronisieren zwei Objekte eine Aktionssequenz, indem sie Nachrichten hin- und herschicken. In diesem Beispiel soll beim Anklicken der Figur “Alice” eine Aktion der Figur “Carol” ausgelöst werden. Wenn “Carol” ihre Aktion beendet hat, soll wiederum eine weitere Reaktion von “Alice” ausgelöst werden. Dazu werden zwei Nachrichten “prüfe Alices Aufträge” und “bedanke dich, Alice” verwendet. Der zeitliche Ablauf der Skriptausführung ist im folgenden Balkendiagramm dargestellt:

Script

Dazu fügen wir den Figuren “Alice” und “Carol” die folgenden Skripte zur Ereignisbehandlung hinzu:

  • Wird “Alice” angeklickt, sagt sie etwas und sendet die Nachricht “prüfe Alices Aufträge” (1. Aktion, Beginn der Aktionssequenz)
  • Wenn “Carol”, die Nachricht “prüfe Alices Aufträge” empfängt, sagt sie etwas und sendet die Nachricht “bedanke dich, Alice” (2. Aktion)
  • Wenn “Alice”, die Nachricht “bedanke dich, Alice” empfängt, sagt sie etwas (3. Aktion, Ende der Aktionssequenz)

Script

Script

Da die Figur “Bob” keine Ereignisblöcke für diese beiden Nachrichten besitzt, reagiert sie auf diese nicht.

Analog können wir auch den Dialog zwischen “Bob” und “Carol” umsetzen (siehe Beispielvideo), indem wir weitere Nachrichten dafür verwenden (z. B. “prüfe Bobs Aufträge”, “bedanke dich, Bob”).

Kommunikation mit Warten

In Fällen wie diesen lässt sich die Zwei-Wege-Kommunikation auch vereinfachen, indem die Anweisung “sende Nachricht und warte” verwendet wird:

Script

Hier pausiert das Skript des sendenden Objekts (hier “Alice”) automatisch, bis alle Objekte, die einen Ereignisblock für diese Nachricht besitzen (hier “Carol”), ihre entsprechende Reaktion zuende ausgeführt haben. In der Reaktion von “Carol” muss nun also keine Antwortnachricht mehr versendet werden:

Script

Der zeitliche Ablauf ist hier im Resultat genauso wie beim vorigen Beispiel – der Unterschied besteht darin, dass hier nur ein Skript von “Alice” ausgeführt wird (das zwischenzeitlich pausiert), statt wie im vorigen Beispiel je ein Skript für die 1. und 3. Aktion:

Script

Anwendungsfälle für Nachrichten

Nachrichten (auch Signale) werden in der Programmierung zur ereignisgesteuerte Kommunikation zwischen Programmobjekten verwendet.

Nachrichten (“Signale”) eignen sich gut, um komplexere zeitliche Programmabläufe zu koordinieren, in denen Aktionen von Objekten durch Aktionen anderer Objekte ausgelöst werden, beispielsweise in einer Animationssequenz. Die zeitliche Abstimmung verschiedener Programmabläufe aufeinander wird als Synchronisation bezeichnet.

Mit Hilfe von Nachrichten lassen sich Objekte durch Skripte anderer Objekte steuern, beispielsweise lässt sich so eine Figur durch das Anklicken von Schaltflächen bewegen.

Nachrichten lassen sich allgemein verwenden, um selbst definierte Ereignisse eines Objekts an andere Objekte zu melden, beispielsweise “Wenn anderes Objekt angeklickt wird”, “Wenn anderes Objekt den Rand berührt” oder “Wenn anderes Objekt verschwindet/erscheint”.

Fremdmethodenaufruf

In den meisten Programmiersprachen, in denen mit Objekten gearbeitet wird, können Objekte nicht nur ihre eigenen Methoden aufrufen, sondern auch Methoden anderer Objekte (fremde Methoden). In Scratch können Nachrichten verwendet werden, um solche Fremdmethodenaufrufe umzusetzen, indem einem anderen Objekt mittels einer Nachricht mitgeteilt wird, dass es eine eigene Methode aufrufen soll.

Dazu muss für jede Methode, die durch andere Objekte aufgerufen werden kann, eine eigene Nachricht verwendet werden. Um nicht den Überblick zu verlieren, bietet es sich an, diese Nachrichten nach einem bestimmten Schema zu benennen, beispielsweise “Objekt.Methode”. Das folgende Beispiel ergänzt das Objekt “Bär” aus dem Beispiel zu Unterprogramme um das entsprechende Ereignis:

Image

Die Methode “springe” dieses Objekts kann nun durch andere Objekte mit der Anweisung “sende Bär.springe an alle” aufgerufen werden (bzw. “sende … und warte”, wenn das aufrufende Skript erst nach dem Fremdmethodenaufruf fortfahren soll):

Image


  1. zur Erinnerung: siehe Lektion 1 – Einstieg in Scratch, Abschnitt Parameter und Werte ↩︎

  2. Neben Eingabefeldern für Parameter können in Scratch auch weitere Textteile zum Namen des neuen Blocks hinzugefügt werden, um ihn in natürlicher Sprache lesbarer zu machen oder für die programmierenden Menschen hilfreiche Informationen zu den Parametern zu ergänzen (z. B. Einheiten). In diesem Beispiel heißt der Block “springe (N) Schritte”, wobei (N) ein Eingabefeld für einen Parameter darstellt, der im Unterprogramm-Skript “N” heißt. ↩︎

1.5.1 Übungsaufgaben

Praktische Übungen

Aufgabe 1: Parametrisierte Unterprogramme

Aufgaben zum Zeichnen geometrischer Formen eignen sich gut, um die Verwendung von Unterprogrammen zu motivieren: Anweisungssequenzen zum Zeichnen einfacher geometrischer Formen können als Unterprogramme definiert werden und anschließend zum Zeichnen komplexerer Formen verwendet werden.

In dieser Aufgabe soll ein Unterprogramm zum Zeichnen von Kreisen definiert werden und zum Zeichnen einer aus Kreisen zusammengesetzten Form verwendet werden. Laden Sie zunächst die Projektdatei Kreise_zeichnen.sb3 herunter und öffnen Sie sie in Scratch: Download

Das Programm enthält als Vorlage eine Anweisungssequenz, mittels der ein Kreis mit einem Radius von 80 Pixeln um den Mittelpunkt x = 0, y = 0 gezeichnet wird:1

Image

Definieren Sie ein Unterprogramm “zeichne Kreis” mit geeigneten Parametern, das einen Kreis mit beliebigem Radius um einen beliebigen Mittelpunkt zeichnet. Als Grundlage für das Unterprogramm können Sie das vorhandene Skript verwenden.

Verwenden Sie das Unterprogramm dann, um beim Programmstart den Buchstaben “Ö” zu zeichnen. Der Buchstabe besteht aus vier Kreisen wie hier dargestellt:

ImageÄußerer Kreis: Mittelpunkt (0, 0), Radius 80 Pixel
Innerer Kreis: Mittelpunkt (0, 0), Radius 60 Pixel
Linker Punkt: Mittelpunkt (-50, 90), Radius 10 Pixel
Rechter Punkt: Mittelpunkt (50, 90), Radius 10 Pixel

Aufgabe 2: Synchronisation mittels Nachrichten

In den Übungsaufgaben zu Lektion 1 wurde eine Animationssequenz entwickelt, indem die Aktionen der einzelnen Figuren mittels “warte”-Anweisungen zeitlich aufeinander abgestimmt wurden (siehe Aufgabe Animationssequenz nach Drehbuch erstellen). Diese Vorgehensweise ist relativ unflexibel: Wenn wir die Aktionen eines Objekts nachträglich ändern möchten und sich dadurch deren Dauer ändert, müssen wir die “warte”-Anweisungen für alle folgenden Aktionen in allen Skripten der anderen Objekte anpassen. Nachrichten bieten eine flexiblere Möglichkeit, den zeitlichen Ablauf von Aktionen verschiedener Objekte zu koordinieren (d. h. die Abläufe zu synchronisieren).

In dieser Aufgabe soll eine Animationssequenz mit Hilfe von Nachrichten umstrukturiert werden. Laden Sie zunächst die Projektdatei Knock_Knock.sb3 herunter und öffnen Sie sie in Scratch: Download

Überprüfen Sie die Skripte der beiden Figuren. Die Skripte verwenden “warte”-Anweisungen, um beim Programmstart eine aufeinander abgestimmte Animations- und Dialogsequenz abzuspielen.

Ändern Sie das Programm so, dass die Aktionen der Figuren mit Hilfe von Nachrichten synchronisiert werden. Das folgende Diagramm zeigt skizzenhaft, in welcher Reihenfolge die Nachrichten nach dem Programmstart zwischen den Objekten ausgetauscht werden und welche Aktionen jeweils ausgelöst werden:2

Image

Aufgabe 3: Steuerung mittels Nachrichten

Laden Sie die Projektdatei Baseball.sb3 als Vorlage herunter: Download

Das Projekt enthält eine Figur “Ball”, deren Richtung mit den Pfeiltasten nach oben und unten gedreht werden kann. Beim Drücken der Leertaste führt die Figur “Spielerin” eine Schlaganimation aus und der Ball fliegt entlang seiner eingestellten Richtung zum Bildschirmrand. Danach erscheint er wieder an seiner Ausgangsposition.

Das Programm soll nun so umgeschrieben werden, dass die Figuren durch Anklicken der drei Schaltflächen oben links gesteuert werden, statt über die Tastatur.

Richtung einstellen

Durch Anklicken der beiden Schaltflächen und soll die Richtung des Balls geändert werden, statt durch Drücken der Pfeiltasten.

Ball schlagen

Beim Anklicken der Schaltfläche der mittleren Schaltfläche soll die Schlaganimation der Spielerin und die Bewegung des Balls ausgelöst werden, statt durch Drücken der Leertaste.

Schaltflächen deaktivieren

Sobald der Schlag durch die mittlere Schaltfläche ausgelöst wurde, sollen alle drei Schaltflächen verschwinden, bis der Ball den Bildschirmrand erreicht hat und sich wieder an seiner Ausgangsposition befindet.

Aufgabe 4: Programm umstrukturieren

In dieser Aufgabe soll die Anwendung zur 2D-Transformation aus der vorigen Übung überarbeitet werden.

Laden Sie die dazu Projektdatei 2D-Transformation_mit_Test.sb3 herunter: Download

Momentan wird beim Drücken der Leertaste geprüft, ob die Form der roten Figur mit der grünen Zielfigur übereinstimmt. Strukturieren Sie das Programm mit Hilfe von Unterprogrammen und Nachrichten folgendermaßen um:

  • Die Überprüfung soll nun nach jeder Eingabe für die rote Figur (also Bewegung, Drehung oder Größenänderung) automatisch durchgeführt werden. Die Leertaste wird also nicht mehr benötigt. Dabei sollen keine Code-Duplikate im Skript vorkommen.
  • Falls die Form der roten Figur mit der Zielfigur übereinstimmt, nimmt die Zielfigur eine neue zufällige Transformation ein. Anderenfalls passiert nichts (die Mitteilungen “Die Position/Größe/Richtung stimmt nicht.” können also gelöscht werden). Auch hier sollen Code-Duplikate im Skript vermieden werden.
Lösungstipps

  1. Der Kreis wird hier, wie in der Computergrafik üblich, durch ein regelmäßiges Vieleck approximiert – in diesem Fall durch ein 36-Eck. Da für einen Kreis mit dem Radius r der Umfang 2·π·r beträgt, ist jede Seite des 36-Ecks hier π/18·r lang (also ungefähr 0.1745·r). ↩︎

  2. Das Bild wurde erstellt unter Verwendung von Hintergründen von Upklyak @ Freepik↩︎

1.5.2 Vertiefung

Klonen von Objekten

In der Lektion zu Variablen wurde das Spiel Fische fangen entwickelt, in dem mehrere Kopien eines Objekts sich unabhängig voneinander über die Bühne bewegen und per Mausklick gefangen werden können. Dazu haben wir zwei Duplikate des Objekts “Fisch1” erstellt.

Dieses Vorgehen hat allerdings mehrere Nachteile, die zu Programmierfehlern führen können:

  • Sobald viele Kopien eines Objekts benötigt werden, geht schnell der Überblick verloren. Außerdem muss von vornherein bekannt sein, wie viele Kopien des Objekts benötigt werden. Manchmal entscheidet sich das allerdings erst zur Laufzeit.
  • Wenn nachträglich eine Änderung in einem Skript der Figur vorgenommen werden soll, muss das Skript jeder Kopie der Figur angepasst werden, was zeitaufwendig und fehleranfällig ist. Auch in diesem Fall haben wir es also mit Code-Redundanz zu tun.

Eine bessere Lösung besteht darin, erst zur Laufzeit des Programms temporäre Kopien eines Objekts erstellen zu lassen, die nach Programmende automatisch gelöscht werden.

Image

Solche temporären Objektkopien werden in Scratch als “Klone” bezeichnet und können durch bestimmte Anweisungen verwaltet werden.

Erzeugen und Löschen von Klonen

In Scratch gibt es je eine Anweisung zum Erzeugen und zum Löschen von Klonen eines Objekts, die sich in der Kategorie “Steuerung” (orange) befinden:

BlockErzeugt einen Klon des angegebenen Objekts oder des Objekts selbst, welches das Skript ausführt.
BlockLöscht das Objekt, zu dem das Skript gehört, sofern es als Klon entstanden ist. Anderenfalls hat diese Anweisung keinen Effekt.

Ein Klon ist eine exakte Kopie des Objekts, aus dem es entstanden ist. Es hat also die gleichen Attribute, Objektvariablen, Grafiken und Soundeffekte. Sein Zustand – also die Werte seiner Attribute – entspricht dem Zustand des Ursprungsobjekts zu dem Zeitpunkt, zu dem die Anweisung “Erzeuge Klon” ausgeführt wird. Ab diesem Zeitpunkt ist es aber komplett unabhängig von dem Objekt, aus dem es entstanden ist.

Beim Beenden des Programms durch Klicken auf das Stop-Symbol Icon werden automatisch alle vorhandenen Klone gelöscht – also alle Objekt, die durch Anweisungen “Erzeuge Klon” entstanden sind.

Initialisieren von Klonen

Damit ein Klon bestimmte Aktionen durchführen kann, wenn er entsteht, bietet Scratch einen Ereignisblock in der Kategorie “Steuerung” (orange) an:

BlockDas angehängte Skript wird ausgeführt, sobald ein Klon dieses Objekts entsteht. Das Skript wird dabei für den erzeugten Klon und nicht für das Objekt, das als Vorlage für den Klon dient, ausgeführt.

Dieses Ereignis dient dazu, ein neu als Klon entstandenes Objekt zu initialisieren, also seinen Anfangszustand festzulegen, beispielsweise seine Sichtbarkeit oder Position. Oft bietet es sich an, bei der Initialisierung bestimmte Attribute auf Zufallswerte zu setzen, um das Verhalten der Klone zu variieren.

Beispiel: Fische fangen

Im folgenden Beispiel überarbeiten wir das Spiel Fische fangen so, dass mit Klonen statt mit “echten” Duplikaten von Objekten gearbeitet wird. Dazu entfernen wir die beiden Duplikate der Figur “Fisch” und passen das Skript der Figur folgendermaßen an:

  • Das Skript zur Initialisierung und dauerhaften Bewegung des Objekts wird ausgeführt, wenn das Objekt als Klon entsteht, nicht beim Programmstart. (1.)
  • Falls beim Anklicken die Trefferzahl den Wert 0 erreicht, kann der Klon wieder gelöscht werden, um nicht unnötig Speicherplatz und Rechenzeit der Scratch-Entwicklungsumgebung zu verbrauchen. (2.)
  • Beim Löschen des Klons werde alle ggf. noch laufenden Skript von ihm abgebrochen. Diese Endloswiederholung bricht also ab, sobald der Klon das dritte Mal angeklickt wurde. (3.)

Script

Die Figur “Fisch” selbst setzen wir über das Attributfenster über der Objektliste manuell auf “unsichtbar”. Diese Figur bleibt nun auch unsichtbar, da sie nur noch als Vorlage für die Klone dient und selbst im Spiel gar nicht mehr vorkommt.

Als Nächstes müssen die Klone noch erzeugt werden. Dazu wird im Startskript der Bühne eine Wiederholung ergänzt, die drei Klone der Figur “Fisch” erstellt:

Script

Ein weiterer Vorteil der Verwendung von Klonen ist, dass die Anzahl der Objekte nicht von vornherein festgelegt ist. Statt eine feste Anzahl von Fischen zu erstellen, könnte das Skript der Bühne beispielsweise auch in Endloslosschleife im Hintergrund alle drei Sekunden neue Fische generieren:

Script

Das Scratch-Projekt zu diesem Beispiel können Sie hier herunterladen: Download

Das Objekt, das geklont wird, dient oft nur noch als Objektvorlage für die Klone und kommt im Programm selbst nicht mehr vor – dazu wird seine Sichtbarkeit auf “unsichtbar” gesetzt. Da die Klone damit initial ebenfalls unsichtbar sind, müssen sie in diesem Fall in ihrem Initialisierungsskript ihre Sichtbarkeit auf “sichtbar” ändern:
Script

Partikelsysteme

Ein typisches Beispiel für die Verwendung von Objektklonen stellen Partikelsysteme dar – also Animationen, in denen eine größere Anzahl von gleichartigen Objekten (“Partikel”) animiert wird. Solche Partikelsysteme können etwa zur Simulation von Feuer-, Rauch- oder Explosionseffekten oder für Wettereffekte wie Regen oder Schnee verwendet werden. Hier werden beispielsweise alle Regentropfen oder Schneeflocken durch Objektklone dargestellt, die sich alle gleich oder ähnlich (mit leichten, meist zufällig gewählten Variationen) verhalten und aussehen.

Das folgende Beispiel demonstriert ein einfaches Partikelsystem zur Darstellung von Regentropfen. Hier gibt es ein einzelnes Objekt “Partikel”, dessen Grafik einen Regentropfen darstellt. Dieses Objekt dient als Objektvorlage für die Regentropfen, die zur Laufzeit erzeugt werden, und wird selbst auf unsichtbar gesetzt.

Script

Beim Klonen des Objekts wird das folgende Skript für den Klon ausführt:

Script

Dieses Skript legt die Startposition des neuen Partikels fest und lässt es auf der Bühne erscheinen. Die x-Koordinate wird dabei für jedes Partikel unabhängig voneinander zufällig gewählt. Anschließend bewegt es sich abwärts, bis es den Boden erreicht und gelöscht werden kann.

Um die Partikel zu variieren, könnten weitere Attribute zu Beginn dieses Skript zufällig festgelegt werden (z. B. ein Zufallswert zwischen 50% und 100% für die Größe).

In diesem Beispiel sollen nach dem Starten des Programms fortwährend neue Partikel erzeugt werden (hier: 5 Partikel pro Sekunde). Dazu wird zur Bühne ein entsprechendes Skript hinzugefügt:

Script

Das Scratch-Projekt zu diesem Beispiel können Sie hier herunterladen: Download

Praktische Übungen

Aufgabe: Partikelsystem mit Klonen

In dieser Aufgabe soll ein einfaches Partikelsystem in Scratch umgesetzt werden, in dem die einzelnen Partikel durch Klone einer Objektvorlage realisiert werden. Laden Sie dazu die Projektdatei Partikelsystem.sb3 herunter: Download

Auf der Bühne befindet sich eine Rakete, die mit den Pfeiltasten nach links und rechts gedreht werden kann (siehe Skript im Objekt “Rakete”), sowie ein Objekt “Partikel”, das ein einzelnes Funkenpartikel darstellt. Ziel ist es nun, einen Funkenschweif zur Rakete hinzuzufügen.

Partikel erzeugen und bewegen

Als Erstes sollen Funken automatisch erzeugt und animiert werden. Dabei dient das Objekt “Partikel” als Objektvorlage für die Partikelklone.

  • Die Rakete soll alle 50 ms einen neuen Funken erzeugen. Die initiale Posion der Funken ist also die aktuelle Position der Rakete.
  • Sobald ein Funke entsteht, bewegt er sich in einer geraden Linie in entgegengesetzter Richtung von der Rakete weg (z. B. in 10-er Schritten alle 50 ms).
  • Wenn ein Funke den Bildschirmrand berührt, verschwindet er von der Spielfläche und kann gelöscht werden.
Partikelverhalten variieren

Sorgen Sie nun dafür, dass nicht alle Partikel gleich aussehen und sich gleich verhalten, sondern leichte zufallsbedingte Variationen aufweisen:

  • Die Größe der Partikel kann zwischen 50% bis 100% der Objektvorlage variieren.
  • Die Bewegungsrichtung kann bis zu 10° von der entgegengesetzten Richtung der Rakete abweichen.
  • Jedes Partikel hat eine eigene Geschwindigkeit, die zwischen 10 bis 20 Pixel pro 50 ms betragen kann.

Für die letzte Anforderung kann es hilfreich sein, die Geschwindigkeit der einzelnen Partikel als Objektvariable zu speichern. Zur Erinnerung: Jeder Klon hat seine eigene Version der Objektvariablen seiner Vorlage.

Aufgabe: Nachrichten und Objektklone

In dieser Aufgabe wird das Aufrufen von Skripten anderer Objekte mittels Nachrichten, sowie das Arbeiten mit Objektklonen behandelt. Laden Sie zunächst die Projektdatei Windrad.sb3 herunter: Download

Das Projekt enthält eine Figur “Windrad”, die beim Starten des Programms dem Mauszeiger folgt und per Mausklick eine Wirbelanimation ausführt. Außerdem befindet sich ein Objekt “Ball” auf der Bühne, der beim Programmstart an einer zufälligen Position erscheint.

Skript eines anderen Objekts aufrufen

Passen Sie das Programm so an, dass der Ball mittels einer Nachricht informiert wird, wenn das Windrad seine Wirbelanimation ausführt. Beträgt der Abstand vom Ball zum Windrad weniger als 150 Pixel, so soll er sich in einer geraden Linie vom Windrad wegbewegen, bis er den Bildschirmrand berührt.

Um den Ball zum Windrad hinzudrehen, kann der Block “drehe dich zu …” verwendet werden:

Block

Objektklone erstellen

Machen Sie den Ball nun unsichtbar und passen Sie das Programm so an, dass zu Beginn 10 Klone des Balls zufällig auf der Bühne verteilt werden. Das Objekt “Ball” selbst dient jetzt nur noch als Vorlage für die Klone.

Wenn ein Klon den Bildschirmrand berührt, soll er nun verschwinden (“lösche diesen Klon”). Außerdem soll die Bühne nach Programmstart jede Sekunde einen neuen Klon erzeugen, der zufällig auf der Bühne positioniert wird.

1.6 Anhang

1.6.1 Scratch-Referenz

Blöcke in Scratch

Blocktypen
Block“Stapelblockform”Anweisung
Block“Klammerblockform”Kontrollstruktur (z. B. eine Fallunterscheidung oder Wiederholung), siehe Steuerung
Block“Kopfblockform”, gewölbtEreignisbehandlung (Beginn eines Skripts, das beim Eintreten des Ereignisses ausgeführt wird), siehe Ereignisse
Block“Kopfblockform”, flachMethodendefinition (Beginn eines Skripts, das durch einen speziellen Anweisungsblock ausgeführt wird), siehe Meine Blöcke
Block“Werteblockform”Wert (eine Zahl oder Zeichenkette, z. B. der Wert eines Attributs, einer Variablen oder eines Berechnungsergebnisses)
Block“Wahrheitswerteblockform”Wahrheitswert (wahr oder falsch, wird z. B. für Bedingungen in anderen Blöcken verwendet)

Bewegung

Anweisungen
BlockVersetzt die Position des Objekts um die angegebene Distanz entlang seiner Richtung.
Block BlockÄndert die Richtung des Objekts um den angegebenen Wert im oder gegen der Uhrzeigersinn (Winkel in Grad).
BlockSetzt die Position des Objekts auf das ausgewählte Ziel: zufällig ausgewählte Koordinaten auf der Bühne, die aktuelle Position des Mauszeigers oder die Koordinaten eines anderen Objekts. Das Ziel kann über das Symbol ▾ festgelegt werden.
BlockSetzt die Position des Objekts auf die angegebenen Koordinaten.
BlockBewegt das Objekt in der angegebenen Zeitspanne kontinuierlich zum ausgewählten Ziel (Zufallskoordinaten oder anderes Objekt).1
BlockBewegt das Objekt in der angegebenen Zeitspanne kontinuierlich zu den angegebenen Koordinaten.1
BlockSetzt die Richtung auf den angegebenen Wert (Winkel in Grad).
BlockDreht das Objekt in Richtung des ausgewählten Ziels.
BlockBlockAddiert den angegebenen Wert zur x- oder y-Koordinate des Objekts.
Block BlockSetzt die x- oder y-Koordinate des Objekts auf den angegebenen Wert.
BlockFalls das Objekt den Rand der Bühne überschneidet, wird es vom Rand weg in die Bühne versetzt und ggf. gedreht, so dass es den Rand gerade noch berührt. Anderenfalls hat die Anweisung keinen Effekt.
BlockLegt den Drehtyp fest, d. h. wie die Richtung des Objekts seine Darstellung beeinflusst: rotiert um den Richtungswinkel, gespiegelt bei negativem Richtungswinkel oder keine Änderung.
Werte
Block BlockGibt als Wert die momentane x- oder y-Koordinate des Objekts an.
BlockGibt als Wert die momentane Richtung des Objekts an (Winkel in Grad).

Aussehen

Anweisungen
Block BlockZeigt den angegebenen Text am Objekt in einer Sprech- oder Denkblase an.
Block BlockZeigt den angegebenen Text für die angegebene Zeitspanne an.1
Block BlockLegt die angegebene Grafik für das Objekts fest oder wechselt zur nächsten Grafik.
Block BlockLegt das angegebene Hintergrundbild für die Bühne fest oder wechselt zum nächsten Bild.
Löst das Ereignis “Bühnenbild wechselt zu Bild” aus.
BlockAddiert den angegebenen Wert zur Größe des Objekts (in Prozent).
BlockSetzt die Größe des Objekts auf den angegebenen Wert (in Prozent).
BlockAddiert den angegebenen Wert zum ausgewählten Grafikeffekt des Objekts. Es gibt Grafikeffekte zum Ändern des Farbton, der Helligkeit und Transparenz, sowie zum Verzerren (Fischauge, Wirbel), Verpixeln (Pixel) und Vervielfältigen (Mosaik) der Grafik.2
BlockSetzt den ausgewählten Grafikeffekt des Objekts auf den angegebenen Wert.
BlockSetzt alle Grafikeffekte des Objekts auf den Normalzustand zurück.
Block BlockMacht das Objekt unsichtbar oder macht es wieder sichtbar.3
BlockSetzt das Objekt auf die vorderste oder hinterste Ebene (Auswahl über das Symbol ▾). Die Objekte werden nach Ebenen sortiert auf der Bühne gezeichnet, so dass Objekte andere Objekte überdecken, die weiter hinten liegen.
BlockSchiebt das Objekt im Ebenenstapel um die angegebene Anzahl von Ebenen nach vorne oder hinten.
Werte
BlockGibt als Wert die Nummer oder den Namen der aktuellen Grafik des Objekts an (Wahl zwischen Nummer oder Name über das Symbol ▾).
BlockGibt als Wert die Nummer oder den Namen des aktuellen Hintergrundbilds des Bühne an.
BlockGibt als Wert die momentane Größe des Objekts an (in Prozent).

Klang

Anweisungen
BlockStartet den angegebenen Sound des Objekt und spielt ihn im Hintergrund ab.
BlockStartet den angegebenen Sound des Objekts und wartet, bis der Sound zuende abgespielt wurde.1
BlockBricht alle momentan laufenden Sounds des Objekt ab.
BlockAddiert den angegebenen Wert zum ausgewählten Wiedergabeeffekt des Objekts: Tonhöhe oder Stereo-Aussteuerung (links/rechts).4
BlockSetzt den ausgewählten Wiedergabeeffekt des Objekts auf den angegebenen Wert.
BlockSetzt alle Wiedergabeeffekte des Objekts auf den Normalzustand zurück.
BlockAddiert den angegebenen Wert zur Lautstärke für die Sound-Ausgabe des Objekts (in Prozent).
BlockSetzt die Lautstärke für die Sound-Ausgabe des Objekts auf den angegebenen Wert (in Prozent).
Werte
BlockGibt als Wert die momentane Lautstärke für die Sound-Ausgabe des Objekts an (in Prozent).

Ereignisse

Ereignisse
BlockDas angehängte Skript wird beim Klicken auf die grüne Fahne Icon ausgeführt.
BlockDas angehängte Skript wird beim Drücken der ausgewählten Taste ausgeführt (Auswahl der Taste über das Symbol ▾).
BlockDas angehängte Skript wird ausgeführt, sobald das Objekt mit der Maus angeklickt wird.
BlockDas angehängte Skript wird ausgeführt, sobald das Bühnenbild zum ausgewählten Bild wechselt (Auswahl des Bildnamen über das Symbol ▾), z. B. weil in einem anderen Skript die Anweisung “wechsle zu Bühnenbild …” ausgeführt wird.
BlockDas angehängte Skript wird ausgeführt, sobald die momentan über das Mikrofon gemessene Lautstärke den angegebenen Wert (0 bis 100) überschreitet.
BlockDas angehängte Skript wird ausgeführt, sobald der Wert der Stoppuhr den angegebenen Wert (in Sekunden) überschreitet.5 Dazu muss im vorigen Block in der Auswahlliste über das Symbol ▾ der Messwert “Stoppuhr” statt “Lautstärke” ausgewählt werden.

Nachrichten

Ereignisse
BlockDas angehängte Skript wird ausgeführt, sobald das Objekt die ausgewählte Nachricht empfängt (Auswahl der Nachricht über das Symbol ▾).6
Anweisungen
BlockVerschickt die ausgewählte Nachricht an alle Objekte (auch an sich selbst).
BlockVerschickt die ausgewählte Nachricht und wartet anschließend, bis alle Objekte, die auf diese Nachricht reagieren, das Ereignis verarbeitet haben.7

Steuerung

Kontrollstrukturen
BlockFührt die enthaltenen Blöcke wiederholt nacheinander aus, bis das Programm abgebrochen wird (endlose Wiederholung).
BlockFührt die enthaltenen Blöcke n-mal nacheinander aus, wobei n die angegebene Zahl ist (Wiederholung mit fester Anzahl).
BlockFührt die enthaltenen Blöcke wiederholt nacheinander aus, bis die angegebene Bedingung erfüllt ist (bedingte Wiederholung).
BlockFührt die Blöcke in der Klammer nur dann aus, falls die angegebene Bedingung erfüllt ist (bedingte Anweisung).
BlockFührt die Blöcke in der oberen Hälfte nur dann aus, falls die angegebene Bedingung erfüllt ist. Anderenfalls werden die Blöcke in der unteren Hälfte ausgeführt (bedingte Anweisung mit Alternative, Fallunterscheidung).
Anweisungen
BlockPausiert das Skript für die angegebene Zeitspanne.1
BlockPausiert das Skript, bis die angegebene Bedingung erfüllt ist.
BlockBricht entweder das Skript selbst, alle anderen momentan laufenden Skripte des Objekts oder alle Skripte ab (Auswahl über das Symbol ▾).8

Objekte klonen

Ereignisse
BlockDas angehängte Skript wird ausgeführt, sobald ein Klon dieses Objekts entsteht. Das Skript wird dabei für den erzeugten Klon und nicht für das Objekt, das den Klon erzeugt, ausgeführt.
Anweisungen
BlockErzeugt einen Klon des ausgewählten Objekts, also ein neues Objekt, das die gleichen Attributwerte wie das Vorlageobjekt hat und Kopien aller Skripte und Ressourcen dieses Objekts enthält.9
BlockLöscht das Objekt, sofern es als Klon entstanden ist. Für ein originales Objekt hat die Anweisung keinen Effekt.10

Fühlen

Anweisungen
BlockZeigt den angegebenen Text am Objekt in einer Sprechblase an und pausiert das Skript dann, bis eine Antwort über die Tastatur eingegeben und die Eingabetaste gedrückt wurde. Die Antwort befindet sich anschließend im “Antwort”-Werteblock: Block
BlockLegt fest, ob ein Objekt auch im Präsentationsmodus mit der Maus verschoben werden kann. Im Entwurfsmodus sind Objekte immer mit der Maus ziehbar.
Werte
BlockGibt den Wert an, der als Antwort auf die zuletzt gestellte Frage eingegeben wurde.
BlockGibt als Wahrheitswert an, ob das Objekt gerade den Mauszeiger, den Bühnenrand oder ein bestimmtes anderes Objekt berührt (Auswahl des Ziels über das Symbol ▾).3
BlockGibt als Wahrheitswert an, ob das Objekt gerade einen Bildschirmpunkt mit der angegebenen Farbe berührt.
BlockGibt als Wahrheitswert an, ob ein Punkt des Objekts, der die erste angegebene Farbe hat, einen Bildschirmpunkt berührt, der die zweite angegebene Farbe hat.
BlockGibt als Wert den Abstand des Objekts zum Mauszeiger oder zu einem anderen Objekt an (Auswahl des Ziels über das Symbol ▾).
BlockGibt als Wahrheitswert an, ob momentan die ausgewählte Taste gedrückt ist (Auswahl der Taste über das Symbol ▾).
BlockGibt als Wahrheitswert an, ob momentan eine Maustaste gedrückt ist.
Block BlockGibt als Wert die x- oder y-Koordinate des Mauszeigers an.
Block
Block
Gibt den Wert eines beliebigen Attributs/einer Objektvariable eines anderen Objekts oder der Bühne an. Über die linke Auswahlliste (Symbol ▾) kann das gewünschte Attribut (z. B. x-/y-Koordinate oder Richtung) und über die rechte Auswahlliste das Objekt oder die Bühne ausgewählt werden.11
BlockGibt als Wert die Lautstärke an, die momentan über das Mikrofon gemessen wird (0 bis 100).
BlockGibt als Wert Ihren Benutzernamen an (nur in der Online-Version von Scratch, sofern Sie mit Ihrem Account eingeloggt sind, ergibt sonst eine leere Zeichenkette).

Zeitmessung

Anweisungen
BlockSetzt die globale Zeitmessung (“Stoppuhr”) auf 0 zurück.5
Werte
BlockGibt als Wert den aktuellen Stand der Stoppuhr an (in Sekunden).5
BlockGibt als Wert einen Teil der aktuellen Zeit an. Über die Auswahlliste (Symbol ▾) kann festgelegt werden, ob die Sekunden, Minuten, Stunden, der Wochentag, Monatstag, Monat oder die Jahreszahl der aktuellen Zeit ermittelt werden sollen.
BlockGibt als Wert die Tage an, die seit dem 1.1.2000 vergangen sind (als Dezimalzahl, z. B. 7500.25 für 7500 Tage und 6 Stunden).

Operatoren

Werte
Block Block Block BlockGibt als Wert das Berechnungsergebnis für die beiden enthaltenen Werte an (Addition, Subtraktion, Multiplikation oder Division).
BlockGibt als Wert den Teilungsrest der ganzzahligen Division von a durch b an, wobei a und b die beiden enthaltenen Werte sind (Modulo-Operator).
BlockGibt als Wert den enthaltenen Wert gerundet auf die nächste Ganzzahl an.
BlockGibt als Wert das Berechnungsergebnis für den enthaltenen Wert an, wobei verschiedene Funktionen ausgewählt werden können (über das Symbol ▾), u. a.: Betrag, ab-/aufrunden, Wurzel, Sinus, Kosinus, Tangens, Logarithmus und Exponentialfunktion.
BlockGibt als Wert eine zufällig ausgewählte Ganzzahl zwischen a und b an, wobei a und b die beiden enthaltenen Zahlen sind. Wenn a und b Ganzzahlen sind, wird eine Ganzzahl ausgewählt, sonst eine Dezimalzahl.
Block Block BlockGibt als Wahrheitswert an, ob der Vergleich der beiden enthaltenen Werte stimmt oder nicht (größer als, kleiner als, gleich).

Wahrheitswerte

Werte
BlockGibt als Wahrheitswert an, ob beide enthaltenen Bedingungen erfüllt sind (logische Konjunktion).
BlockGibt als Wahrheitswert an, ob mindestens eine der beiden enthaltenen Bedingungen erfüllt ist (logische Disjunktion).
BlockGibt als Wahrheitswert an, ob die enthaltene Bedingung nicht erfüllt ist (logische Negation).

Zeichenketten

Werte
BlockGibt als Wert die Verknüpfung (Konkatenation) der beiden enthaltenen Zeichenketten-Werte an.
BlockGibt als Wert das n-te Zeichen der angegebenen Zeichenkette an, wobei n die im linken Feld angegebene Zahl ist.
BlockGibt als Wert die Länge der angegebenen Zeichenkette an (d. h. die Anzahl ihrer Zeichen).
BlockGibt als Wahrheitswert an, ob die links angegebene Zeichenkette die rechts angegebene Zeichenkette enthält. Groß- und Kleinschreibung spielt dabei keine Rolle (“Apfel” enthält also sowohl “a” als auch “PF”).

Variablen

Anweisungen
BlockSetzt die ausgewählte Variable auf den angegebenen Wert (Auswahl der Variablen über das Symbol ▾).12
BlockAddiert den angegebenen Wert zum aktuellen Wert der ausgewählte Variablen.
Block BlockZeigt den aktuellen Wert der ausgewählten Variablen live auf der Bühne an oder entfernt die Anzeige wieder.
Werte
BlockGibt den momentanen Wert dieser Variablen an.

Listen

Anweisungen
BlockHängt den angegebenen Wert als neues Element an die ausgewählte Liste an (Auswahl der Liste über das Symbol ▾).13
BlockLöscht das Element an der angegebenen Position aus der ausgewählten Liste.
BlockLöscht alle Elemente aus der ausgewählten Liste.
BlockFügt den angegebenen Wert als neues Element an der angegebenen Position in die ausgewählte Liste ein. Alle ab dieser Position vorhandenden Elemente werden um eine Position nach rechts verschoben.
BlockÜberschreibt das Element an der angegebenen Position durch den angegebenen Wert.
Block BlockZeigt den aktuellen Inhalt der ausgewählten Liste live auf der Bühne an oder entfernt die Anzeige wieder.
Werte
BlockGibt als Wert das Element an der angegebenen Position in der ausgewählten Liste an.
BlockGibt als Wert die Position des angegebenen Wertes in der ausgewählten Liste an. Wenn der angegebene Wert mehrmals in der Liste vorkommt, wird die Position des ersten Vorkommens ermittelt.
BlockGibt als Wert die Länge der ausgewählten Liste an (d. h. die Anzahl ihrer Elemente).
BlockGibt als Wahrheitswert an, ob die ausgewählte Liste den angegebenen Wert (mindestens einmal) enthält.
BlockGibt als Wert den momentanen Inhalt dieser Liste an (als Zeichenkette, in der die Werte aller Elemente als Zeichenketten aneinandergehängt sind).

Meine Blöcke

Kopfblock/Anweisung
BlockBeginn für das selbst definierte Skript “mein Block” des Objekts mit einem Parameter “Eingabe1” (Methodendefinition).
BlockFührt das selbst definierte Skript “mein Block” des Objekts aus (Methodenaufruf).

Erweiterungen

Malstift

Anweisungen
BlockEntfernt alle bisher gezeichneten Zeichenspuren und “Abdrücke” vom Bühnenhintergrund.
BlockZeichnet die Grafik des Objekts an seiner aktuellen Position auf den Bühnenhintergrund.
Block BlockSchaltet den Zeichenstift ein oder aus. Solange der Stift eingeschaltet ist, wird bei jeder Bewegung des Objekts eine Spur auf dem Bühnenhintergrund gezeichnet. Zum Zeichnen wird die momentan festgelegte Stiftfarbe und -breite verwendet.
BlockSetzt die Stiftfarbe zum Zeichnen auf die angegebene Farbe.
BlockSetzt den Wert für den Farbton, die Sättigung, Helligkeit oder Transparenz des Zeichenstifts auf den angegebenen Wert (Auswahl des Attributs über das Symbol ▾).2
BlockAddiert den angegebenen Wert zum aktuellen Wert für Farbton, Sättigung, Helligkeit oder Transparenz des Zeichenstifts.[]
BlockSetzt die Stiftbreite zum Zeichnen auf den angegebenen Wert (Pixel).
BlockAddiert den angegebenen Wert zur aktuellen Stiftbreite (Pixel).

Musik

Anweisungen
BlockSpielt einen Ton des ausgewählten Schlaginstruments ab (Auswahl über das Symbol ▾).
BlockWartet die angegebene Anzahl von Schlägen.
BlockSpielt einen Ton mit der angegebenen Dauer ab und wartet für die entsprechende Zeitspanne.1 Dazu wird das momentan für die Musikwiederfabe festgelegte Instrument verwendet.
BlockLegt ein Instrument für die Musikwiedergabe fest (Auswahl über das Symbol ▾).
BlockLegt das Tempo der Musikwiedergabe auf den angegebenen Wert fest (Schläge pro Sekunde).
BlockAddiert den angegebenen Wert zum aktuellen Tempo der Musikwiedergabe.
Werte
BlockGibt als Wert das aktuelle Tempo der Musikwiedergabe an (Schläge pro Sekunde).

Text zu Sprache

Anweisungen
BlockSpricht den angegebenen Text und pausiert das Skript, bis die Sprachausgabe abgeschlossen ist.1
BlockLegt die Stimme für die Sprachausgabe fest (Auswahl über das Symbol ▾): Alt (weiblich), Tenor (männlich), Quietschen (sehr hoch), Riese (sehr tief) oder Kätzchen (gibt jedes Wort als “Miau” aus).
BlockLegt die Wiedergabesprache fest (Auswahl über das Symbol ▾).

Übersetzen

Werte
BlockGibt als Wert die Übersetzung des angegebenen Textes in die ausgewählte Zielsprache an (Auswahl über das Symbol ▾). Die Ausgangssprache wird automatisch erkannt.
BlockGibt als Wert die Sprache der Scratch-Oberfläche an (Auswahl über das Symbol Icon im Menü).

Videoerfassung

Ereignisse
BlockDas angehängte Skript wird ausgeführt, wenn die im Kamerabild gemessene Bewegungsintensität im Bereich des Objekts, zu dem das Skript gehört, über den angegebenen Grenzwert steigt (0 bis 100). Gehört das Skript zur Bühne, wird die Bewegungsintensität im Bereich des Hintergrundbildes ausgewertet.
Anweisungen
BlockSchaltet die Videoerfassung an (gespiegelt oder normal) oder aus (Auswahl über das Symbol ▾).
BlockLegt die Transparenz für das Kamerabild-Overlay auf dem Bühnenhintergrund fest (in Prozent). Bei einem Wert von 100 (vollständig transparent) wird das Kamerabild nicht angezeigt, die Videoerfassung findet aber trotzdem statt.
Werte
BlockGibt als Wert die Intensität oder Richtung der im Kamerabild gemessenen Bewegung an. Dabei wird entweder die Bewegung auf dem Hintergrundbild oder auf dem Objekt gemessen (Auswahl des Ziels über die rechte Auswahlliste ▾). In der linken Auswahlliste kann zwischen Bewegungsintensität und Richtung gewählt werden. Die Bewegungsintensität wird auf einer Skala von 0 (keine Bewegung) bis 100 (sehr starke Bewegung) gemessen, die Richtung als Winkel in Grad (relativ zur momentanen Richtung des Objekts).

  1. Diese Anweisung hat eine festgelegte Dauer. Während dieser Dauer wird das Skript pausiert. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Mit einem Klick auf das Symbol ▾ rechts im Block kann aus den verschiedenen Grafikeffekten ausgewählt werden. Die Wertebereiche und genauere Beschreibungen der Grafikeffekte können im Scratch-Wiki nachgelesen werden: https://de.scratch-wiki.info/wiki/Grafikeffekte ↩︎ ↩︎

  3. Unsichtbare Objekte erscheinen nicht auf der Bühne und können nicht mit der Maus angeklickt werden. Außerdem können sie keine anderen Objekte berühren. Auf andere Ereignisse reagieren sie aber nach wie vor. ↩︎ ↩︎

  4. Mit einem Klick auf das Symbol ▾ rechts im Block kann aus den verschiedenen Wiedergabeeffekten ausgewählt werden. Für die Stereo-Aussteuerung kann ein Wert zwischen -100 (nur links abspielen) und 100 (nur rechts abspielen) angegeben werden. ↩︎

  5. Die “Stoppuhr” von Scratch misst die Sekunden, die seit dem letzten Programmstart mit Icon vergangen sind (oder vor dem ersten Programmstart seit dem Starten der Scratch-Entwicklungsumgebung) bzw. seitdem die Stoppuhr zuletzt auf 0 zurückgesetzt wurde. ↩︎ ↩︎ ↩︎

  6. Eine neue Nachricht lässt sich erstellen, indem auf das Symbol ▾ rechts im Block geklickt und “Neue Nachricht” gewählt wird. ↩︎

  7. Das Skript wird hier also pausiert, bis alle Skripte, die mit einem passenden Ereignisblock “Wenn ich … empfange” beginnen, zuende ausgeführt wurden. ↩︎

  8. Der Abbruch aller Skripte hat denselben Effekt wie das Beenden des Programms durch Klicken auf das Stop-Symbol Icon↩︎

  9. Wenn das zu klonende Objekt ein Skript mit dem Ereignisblock “Wenn ich als Klon entstehe” besitzt, wird dieses zum Zeitpunkt des Klonens für das neu entstandene Objekt (den Klon) ausgeführt. ↩︎

  10. Beim Programmende werden automatisch alle Klone von Objekten gelöscht. ↩︎

  11. Mit diesem Werteblock können auch die Werte von Objektvariablen oder globalen Variablen ermittelt werden. Globale Variablen befinden sich hier in der Auswahlliste der Bühne. ↩︎

  12. Wenn das Skript zu einem Objekt gehört, können hier Variablen des Objekts selbst und globale Variablen gewählt werden. Gehört das Skript zur Bühne, können nur globale Variablen gewählt werden. ↩︎

  13. Wenn das Skript zu einem Objekt gehört, können hier Listen des Objekts selbst und globale Listen gewählt werden. Gehört das Skript zur Bühne, können nur globale Listen gewählt werden. ↩︎

2. Informationsdarstellung


2.1 Informationsdarstellung

Einleitung

In diesem Modul beschäftigen wir uns damit, wie Informationen auf Grundlage von Daten dargestellt werden. Dazu sollen zuerst die zentralen Begriffe erläutert werden.

Informationen und Daten sind zentrale Objekte der Informatik, wie in den Fachanforderungen zum inhaltsbezogenen Kompetenzbereich “Daten und Informationen” festgestellt wird:

Informatik ist die Wissenschaft von der systematischen Darstellung, Speicherung, Verarbeitung und Übertragung von Informationen.
Insofern sind Daten als Repräsentation von Informationen Grundlage jeglicher Informationsverarbeitung.

Obwohl die Begriffe “Daten” und “Informationen” in der Umgangssprache oft gleichbedeutend verwendet werden, muss deutlich zwischen beiden Begriffen unterschieden werden:

  • Daten (Singular: “Datum”) sind einzelne “nackte” Werte oder Fakten, aus denen Information besteht, die aber nicht gleichbedeutend mit Information sind.1 Erst indem Daten strukturiert, in einen bestimmten Kontext gesetzt und interpretiert werden, entsteht aus Daten Information.
  • Information besteht nach der gängigen Definition aus mindestens einem Datum oder mehreren Daten, die wohlgeformt sind und Bedeutung tragen.2

Vereinfacht ausgedrückt ist also Information = Daten + Bedeutung.
Information lässt sich durch Daten darstellen und Daten liefern Information, indem sie gedeutet werden.

Beispiel: Die Zeichenfolgen 10:30, 12:15, 15:20 sind Daten, aber noch keine Information, da ihre Bedeutung nicht bekannt ist. Es könnte sich um Uhrzeiten handeln, aber auch um Punktestände von Basketballspielen oder um Abstimmungsergebnisse. Auch wenn durch den Kontext festgelegt ist, dass es sich um Uhrzeiten handelt, beschreiben die Daten noch keine brauchbare Information – dazu benötigen wir beispielsweise noch als weiteres Vorwissen, dass sie die nächsten Abfahrtzeiten eines bestimmten Zuges angeben.

Syntax und Semantik

Die “Wohlgeformtheit” der Daten bedeutet, dass sie bezüglich bestimmter Regeln richtig repräsentiert sind, also beispielsweise nur zulässige Zeichen enthalten und eine bestimmte Struktur haben. Regeln zur Zusammensetzung von Zeichenfolgen aus einzelnen Zeichen werden als Syntax bezeichnet.3 Die Interpretation der Datenrepräsentationen, also die Zuordnung von Bedeutung zu den Zeichenfolgen, heißt Semantik.

Beispiel: Für Daten, die in Formulare eingetragen werden, wird oft eine Syntax festgelegt, die vorschreibt, wie die Daten formal anzugeben sind. Für eine Uhrzeit kann etwa vorgegeben sein, dass sie mit 2 Ziffern beginnt, danach kommt ein Doppelpunkt und anschließend weitere 2 Ziffern. Zeichenfolgen wie 1:30 oder 10 Uhr wären hier also syntaktisch nicht korrekt. Die Zeichenfolge 99:99 wäre in diesem Beispiel dagegen syntaktisch korrekt, aber semantisch nicht sinnvoll.

Einzelne Daten liefern meist nur wenig Information. Durch Datenverarbeitung lässt sich aus mehreren Daten weitere Information ableiten. Damit Information nützlich oder brauchbar ist, muss also eine Fragestellung vorliegen, zu deren Beantwortung die bedeutungstragenden, wohlgeformten Daten verwendet werden können.

Einzelne Temperaturmesswerte stellen beispielsweise Daten mit einem geringen Informationsgehalt dar, aus denen wir relevantere Information ableiten können, indem wir etwa den Mittelwert, Höchstwert oder Verlauf für einen bestimmten Zeitraum auswerten und ggf. in Bezug zu anderen Messdaten oder Ereignissen setzen. Dabei muss natürlich berücksichtigt werden, dass durch statistische Auswertungen auch falsche Schlüsse gezogen werden können.

Ein wichtiger Anwendungsbereich der Informatik – das Data Mining – beschäftigt sich genau damit, Informationen aus Messdaten zu gewinnen, indem computergestützt nach Mustern, Trends oder Zusammenhängen in meist sehr großen Datenbeständen (“Big Data”) gesucht wird, die bisher unbekannt und nützlich sind. Dazu werden auch in zunehmendem Maße Verfahren der Künstlichen Intelligenz verwendet.

Codierung

Zur Repräsentation von Information bzw. der zugrundeliegenden Daten werden bestimmte Zeichen und syntaktische Regeln verwendet. Dabei kann dieselbe Art von Daten auch durch verschiedene Repräsentationen dargestellt werden: Ein Name lässt sich beispielsweise sowohl durch Buchstaben, im Morse-Code oder mit Hilfe des Fingeralphabets darstellen; ein Kalenderdatum kann im Format 12. August 2021, 12.08.2021, 2021-08-12 oder einfach als 18851 (= Tage seit dem 1.1.1970) darstellt werden.

Image

Codierung bezeichnet die Repräsentation abstrakter Information in einem konkreten Zeichensystem gemäß vereinbarter syntaktischer und semantischer Regeln, und wird darüber hinaus auch als Begriff für die Umwandlung einer Datenrepräsentation in eine andere verwendet.

Im Alltag sind wir umgeben von codierter Information: Beim Einkaufen finden sich Strichcodes zur Kennzeichnung auf Lebensmitteln, Büchern und anderen Artikeln, auf Eiern gibt ein Erzeugercode deren Herkunft an, Kfz-Kennzeichen codieren Information über die Zulassung von Fahrzeugen. Der QR-Code als zweidimensionales Pendant zum Strichcode ist allgegenwärtig, um schnell Informationen durch ein Kamerabild mit dem Handy abzufragen. In Chatnachrichten codieren wir Informationen piktografisch durch Emojis oder durch Abkürzungen, deren Bedeutungen bekannt sind.

Solche Beispiele eignen sich gut als Einstiegspunkt für das Thema “Codierung” im Schulunterricht.

Beispiel: Kfz-Kennzeichen

Ein Kfz-Kennzeichen (siehe Wikipedia) beginnt mit einem Unterscheidungszeichen (1–3 Buchstaben) für den Standort des Fahrzeugs, gefolgt von der Erkennungsnummer (in der Regel 1–2 Buchstaben, gefolgt von 1–4 Ziffern ohne führende Nullen, zusammen mit dem Unterscheidungszeichen aber nicht mehr als 8 Zeichen). Die Buchstaben- und Zifferngruppen werden üblicherweise durch Leerzeichen, seltener durch Bindestriche getrennt dargestellt. Diese Beschreibung legt sowohl den Aufbau (Syntax) als auch die Interpretation (Semantik) der Kennzeichen fest.

Image

Beispiel: Das Kfz-Kennzeichen ECK IG 987 gibt an, dass der reguläre Standort des Fahrzeugs im Kreis Rendsburg-Eckernförde (ECK) liegt, seine Erkennungsnummer ist IG 987.

Beispiel: Eierkennzeichnung

Der Erzeugercode (siehe Wikipedia), der auf Hühnereiern im Handel in der EU zu finden ist, codiert Informationen über die Herkunft des Eis und die Haltungsform der Hennen. Der Code besteht aus Ziffern und Buchstaben und hat in Deutschland die Form (Syntax): eine Ziffer zwischen 0 und 3, zwei Buchstaben, sieben Ziffern, wobei die Ziffern- und Buchstabengruppen durch Bindestriche getrennt werden.4

Image

Die Bedeutung (Semantik) des Codes ist folgendermaßen:

  • Die erste Ziffer gibt die Haltungsform an (0 für Ökologische Erzeugung, 1 für Freilandhaltung, 2 für Bodenhaltung, 3 für Käfighaltung).
  • Die beiden Buchstaben geben das Herkunftsland an (DE für Deutschland).
  • Der letzte Teil gibt die Betriebsnummer an, wobei in Deutschland die ersten beiden Stellen das Bundesland (01 für Schleswig-Holstein), Stellen 3–6 den Betrieb und Stelle 7 den Stall identifizieren.

Beispiel: Der Erzeugercode 1-DE-0123456 gibt an, dass das Ei aus Freilandhaltung (1) in Schleswig-Holstein (DE-01) vom Betrieb mit der Kennnummer 2345 aus Stall 6 stammt.

In beiden Beispielen (Kfz-Kennzeichen und Erzeugercode) hat der Code eine bestimmte Struktur und besteht aus einer begrenzten Menge von Zeichen, wobei bestimmte Zeichen nur an bestimmten Positionen im Code erlaubt sind. Dabei werden Teilinformationen durch bestimmte Teile des Codes repräsentiert, beispielsweise ein Land (Herkunftsland des Eis) durch zwei Buchstaben an Position 2 und 3 im Erzeugercode oder ein Land-/Stadtkreis (Standort des Kfz) durch 2–3 Buchstaben zu Beginn des Kfz-Kennzeichens.

Digitale Codierung

Bei der rechnergestützten Informationsverarbeitung werden Informationen durch digitale Codes dargestellt, das bedeutet, dass sie in Form von endlich vielen diskreten Werten – also Werten aus einem abzählbaren und ebenfalls endlichen Wertebereich – dargestellt werden. Digitale Daten lassen sich also unter anderem durch endliche Folgen von Ganzzahlen beschreiben. Das Gegenstück sind analoge Daten, die aus stufenlos darstellbaren Werten bestehen.

Beispiel: Eine Digitaluhr (ohne Sekundenanzeige) repräsentiert die kontinuierliche (analoge) Tageszeit durch zwei diskrete Werte: eine von 24 Stunden und eine von 60 Minuten, es werden also nur 24⋅60 = 1440 verschiedene Zustände der Zeit unterschieden.

Der Prozess, analoge Daten in digitale Daten umzuwandeln, wird als Digitalisierung bezeichnet. Typische Beispiele sind die Digitalisierung von Schalldruckmessungen bei Tonaufnahmen mit einem Mikrofon oder die Digitalisierung von Helligkeitswerten beim Scannen von analogen Dokumenten. Inzwischen liefern die meisten Aufnahmegeräte und Sensoren, denen wir im Alltag begegnen, von vornherein digitale Daten, siehe beispielsweise die Bild- und Sprachaufnahme mit dem Handy oder die Messergebnisse eines Digitalthermometers.

Für IT-Systeme wie Rechner oder Netzwerkgeräte ist insbesondere die Darstellung durch binäre Codes relevant, also durch endliche Folgen der Werte 0 und 1, da Daten bei solchen Systemen aus technischen Gründen auf diese Werte intern (im Speicher, bei der Datenübertragung) repräsentiert werden.

Ziel dieses Moduls ist es, einen Überblick über verschiedene Methoden zu bekommen, wie Informationen digital repräsentiert werden können und konkrete Anwendungsbeispiele untersuchen: unter anderem Dateiformate für Texte und Bilder und die Darstellung von Informationen im Internet in Form von HTML-Dokumenten.

In den ersten Lektionen werden wir uns systematisch mit der digitalen bzw. speziell der binären Codierung verschiedener Arten von Daten beschäftigen – konkret von Zahlen und Textzeichen (vgl. Fachanforderungen D10/11) sowie Bildern (vgl. Fa. D24/25) – und anschließend Verfahren zur grafischen Codierung digitaler Daten (u. a. in Form von Barcodes) und zur Datenkompression untersuchen (vgl. Fa. D12/13).


  1. Laut Duden sind Daten ganz allgemein durch Beobachtungen, Messungen u. a. gewonnene (Zahlen-)Werte und darauf beruhende Angaben, siehe https://www.duden.de/rechtschreibung/Daten (Stand August 2021) ↩︎

  2. siehe z. B. Luciano Floridi: Information: A Very Short Introduction, Oxford University Press, 2010 ↩︎

  3. Der Begriff “Zeichen” muss hier in einem sehr allgemeinen Sinne verstanden werden, so lassen sich natürlich auch Zusammensetzungsregeln – also eine Syntax – für grafische oder akustische Daten festlegen. ↩︎

  4. In anderen Ländern kann die letzte Zeichengruppe (die Betriebsnummer) auch Buchstaben enthalten und länger oder kürzer sein. ↩︎

2.2 Codierung digitaler Daten

Zuerst werden wir uns mit der digitalen Darstellung von Zahlenwerten als elementaren Daten in verschiedenen Zahlensystemen beschäftigen, insbesondere im Binärsystem.

Als Aufgabe zum Einstieg vorweg: Der Bahnhof St. Gallen in der Schweiz besitzt seit 2018 eine Uhr der besonderen Art. Finden Sie heraus, wie spät es auf diesem Bild gerade ist? Als Tipp: Die obere Zeile stellt die Stunden dar, die mittlere die Minuten und die untere die Sekunden.

Image

Lösung

Bits und Bytes

Die einfachste Form der digitalen Darstellung ist die Binärdarstellung, in der nur zwei verschiedene Werte verwendet werden, die üblicherweise als 0 und 1 dargestellt werden. Diese kleinste digitale Informationseinheit wird als Bit bezeichnet. Binärdaten werden durch Bitfolgen, also Folgen von Nullen und Einsen dargestellt.

Digitale Daten lassen sich dabei immer auch durch Binärdaten repräsentieren, indem jedem der abzählbar vielen Werte eine eindeutige Bitfolge zugewiesen wird.

Beispiel: Die Minuten einer digitalen Uhrzeit können 60 verschiedene Werte annehmen. Jeder Minutenwert lässt sich binär mit 6 Bit beschreiben, da eine Bitfolge der Länge 6 insgesamt 26 = 64 verschiedene Zustände einnehmen kann.

Die Binärdarstellung ist besonders relevant, da Rechner Daten intern als Bitfolgen speichern und verarbeiten. Üblicherweise verarbeiten Rechner Bits aber nicht einzeln, sondern in 8-Bit-Blöcken. Ein solcher 8-Bit-Block wird als Byte bezeichnet und kann entsprechend 28 = 256 viele Zustände einnehmen, die sich als Ganzzahlen von 0 bis 255 interpretieren lassen.

Die Binärdarstellung von Zahlen ist in vielen Anwendungsfällen hilfreich, beispielsweise um Daten im Speicher zu interpretieren, die Funktionsweise eines Rechners auf Hardwareebene zu verstehen, die Kommunikation zwischen Rechnern in Netzwerken oder die Adressierung von Rechnern in hierarchischen Rechnernetzen nachzuvollziehen.

Binärzahlen

Wenn wir binäre Daten untersuchen, macht es Sinn, die einzelnen Werte nicht im Dezimalsystem, sondern als Binärzahl darzustellen, also im Dualsystem (“Zweiersystem”), dem Stellenwertsystem zur Basis 2. Im Dualsystem werden Zahlen nur mit den beiden Ziffern 0 und 1 dargestellt. Die Stellenwerte sind durch die Potenzen der Basis 2 festgelegt, also (von rechts nach links) 20 = 1, 21 = 2, 22 = 4, … – analog zu den Stellenwerten im Dezimalsystem: 100 = 1, 101 = 10, 102 = 100, …

Die folgende Tabelle stellt die Zweierpotenzen bis 216 dar, um einen Eindruck von den Größenordnungen zu bekommen:

Exponent n012345678910111213141516
Zweiterpotenz 2n1248163264128256512102420484096819216 38432 76865 536

Um eine Binärzahl in eine Dezimalzahl umzuwandeln, werden die Ziffern mit den entsprechenden Stellenwerten (= Zweierpotenzen) multipliert und summiert (oder einfacher: Es werden diejenigen Zweierpotenzen summiert, an deren Stellen die Ziffer 1 steht).

Tool: In dieser interaktiven Anzeige können Sie die Binärdarstellung von Ganzzahlen selbst untersuchen. Klicken Sie auf die oberen Binärziffern, um ihre Werte zu ändern.

In der Binärdarstellung entspricht jede Ziffer einem Datenbit. Ein Byte ist also durch eine Binärzahl mit 8 Stellen repräsentiert (ggf. mit führenden Nullen).

Beispiel: Die Binärzahl 00101010 entspricht der Dezimalzahl 25 + 23 + 21 = 32 + 8 + 2 = 42.

Um eine Dezimalzahl ins Binärsystem umzuwandeln, wird die Zahl wiederholt mit Rest durch die Basis 2 geteilt, bis der Quotient 0 ergibt. Die Werte der Teilungsreste ergeben dann von oben nach unten gelesen die Ziffern der Binärdarstellung von rechts nach links gelesen.

Beispiel: Hier wird die Zahl 71 ins Binärsystem umgewandelt:

  • 71 / 2 = 35 Rest 1
  • 35 / 2 = 17 Rest 1
  • 17 / 2 = 8 Rest 1
  • 8 / 2 = 4 Rest 0
  • 4 / 2 = 2 Rest 0
  • 2 / 2 = 1 Rest 0
  • 1 / 2 = 0 Rest 1

Die Binärdarstellung mit 8 Stellen lautet hier also 01000111.

Hexadezimalzahlen

Da Byte-Folgen in der Binärdarstellung schnell sehr lang und unübersichtlich werden, wird zur Darstellung von Bytes statt des Binärsystems oft auch das Hexadezimalsystem verwendet, in dem Zahlen in einem Stellenwertsystem zur Basis 16 dargestellt werden. Neben den Zeichen 0 bis 9 werden hier die Buchstaben A bis F für die sechs zusätzlichen Ziffern verwendet (A entspricht dabei der Dezimalzahl 10 und F der Dezimalzahl 15).

Dezimal0123456789101112131415
Hexadezimal0123456789ABCDEF

Tool: In dieser interaktiven Anzeige können Sie die Hexadezimaldarstellung von Ganzzahlen selbst untersuchen. Klicken Sie auf die oberen Hexadezimalziffern, um ihre Werte zu ändern.

Beispiel: Die Hexadezimalzahl CAFE entspricht der Dezimalzahl 12·163 + 10·162 + 15·16 + 14·1 = 51966.

Um eine Dezimalzahl ins Hexadezimalsystem umzuwandeln, wird die Zahl wiederholt mit Rest durch 16 geteilt.

Beispiel: Für die Dezimalzahl 172 gilt: 172 / 16 = 10 Rest 12, also lautet ihre Hexadezimaldarstellung AC.

Ein Byte entspricht im Hexadezimalsystem jeweils einer Hexidezimalzahl mit zwei Ziffern (ggf. mit einer führenden Null).

Die Umrechnung zwischen Binär- und Hexadezimalsystem lässt sich sehr einfach lösen, indem je 4 Binärziffern zusammengefasst und durch die entsprechende Hexadezimalziffer ersetzt werden:

Binär00000001001000110100010101100111
Hexadezimal01234567
Binär10001001101010111100110111101111
Hexadezimal89ABCDEF

Konvertierung

Tool: Mit diesem Formular können Sie die Repräsentation von Ganzzahlen zwischen dem Dezimalsystem, Hexadezimalsystem und Binärsystem umrechnen.

Größeneinheiten

Um größere Datenmengen von Bits und Bytes zu beschreiben, werden meistens die bekannten Dezimalpräfixe für Maßeinheiten (auch SI-Präfixe genannt) verwendet, mit denen 1000er-Potenzen von Bytes zu je einer Maßeinheit zusammengefasst werden:

SymbolNameWertBeispiele für die Größenordnung
BByte1 B = 8 bitZeichen in einer Textdatei
kBKilobyte1 kB = 1000 B = 103 BNormseite (max. 1.800 Zeichen)
MBMegabyte1 MB = 1000 kB = 106 BBild- und Audiodateien, Videoclips
GBGigabyte1 GB = 1000 MB = 109 BBild-/Audiosammlungen, unkomprimierte Videos
TBTerabyte1 TB = 1000 GB = 1012 BEine oder mehrere Festplatten

Daneben werden auch Binärpräfixe (auch IEC-Präfixe genannt) verwendet, die Vielfache bestimmter Zweierpotenzen bezeichnen (hier: Potenzen von 1024), da binäre Datenmengen aus technischen Gründen oft in diesen Größenordnungen auftreten. Grund dafür ist, dass die SI-Präfixe früher je nach Kontext mal als Dezimalpräfixe und mal als Binärpräfixe verwendet wurden, z. B. konnte mit “1 Kilobyte” entweder 1000 Byte oder 1024 Byte gemeint sein. Um hier Klarheit zu schaffen, wurden Ende der 90er Jahre von der IEC (International Electrotechnical Commission) eigene Präfixe für Zweierpotenzen eingeführt:1

SymbolNameWert
KiB“Kibibyte”1 KiB = 1024 B = 210 B
MiB“Mebibyte”1 MiB = 1024 KiB = 220 B
GiB“Gibibyte”1 GiB = 1024 MiB = 230 B
TiB“Tebibyte”1 TiB = 1024 GiB = 240 B

Codierung von Zahlen

Ganzzahlen ohne Vorzeichen

Natürliche Zahlen (also vorzeichenlose Ganzzahlen) werden in Rechnern als Bitfolgen codiert, wobei sich die Werte der einzelnen Bits aus der Binärdarstellung der Zahl ergeben.

Formel: Für die Bitfolge b1 b2 … bN-1 bN der Länge N ist die entsprechende Dezimalzahl d also durch die folgende Formel gegeben: $$d = b_1 \cdot 2^{N-1} + b_2 \cdot 2^{N-2} + … + b_{N-1} \cdot 2 + b_N$$

Dabei wird üblicherweise eine feste Länge für die Bitfolgen verwendet – es wird also festgelegt, also wie vielen Bits eine Zahl besteht. In der Praxis werden meistens 1, 2, 4 oder 8 Byte verwendet. Der Umfang des darstellbaren Zahlenbereichs ist durch die Anzahl an Bits, die zur Darstellung einer Zahl verwendet werden, eingeschränkt.

Die folgende Tabelle zeigt einen Ausschnitt aus dem Coderaum der 8-Bit-Ganzzahlen:

Binär0000000000000001001010100111111110000000100000011010101011111111
Dezimal0142127128129170255

Wird 1 Byte (= 8 Bit) pro Zahl verwendet, lassen sich damit 28 verschiedene Werte darstellen, also alle natürlichen Zahlen von 0 bis einschließlich 28-1 = 255. Allgemein wäre für N Byte (= 8N Bit) die größte darstellbare natürliche Zahl 28N-1.

Bits (Bytes) pro ZahlDarstellbarer Zahlenbereich
8 Bit (1 Byte)0, …, 28-1 = 255
16 Bit (2 Byte)0, …, 216-1 = 65 535
32 Bit (4 Byte)0, …, 232-1 = 4 294 967 295 (ca. 4,3 Milliarden)
64 Bit (8 Byte)0, …, 264-1 = 18 446 744 073 709 551 615 (ca. 18,4 Trillionen)

Ganzzahlen mit Vorzeichen

Damit sich auch negative Ganzzahlen (bzw. allgemeiner vorzeichenbehaftete Ganzzahlen) darstellen lassen, gibt es verschiedene Möglichkeiten. Die einfachste Variante besteht darin, dass das erste Bit der Bitfolge als Vorzeichenbit verwendet wird (0 für positives Vorzeichen, 1 für negatives Vorzeichen). Die Darstellung der Zahl -42 als 8-Bit-Zahl mit Vorzeichenbit lautet so beispielsweise: 10101010

Da hier 7 Bit für den Betrag ohne Vorzeichen übrigbleiben, wäre der darstellbare Zahlenbereich -127 bis 127. Bei 16 Bit lassen sich die Zahlen -32 767 bis 32 767 darstellen (es gilt: 215-1 = 32 767).

Die folgende Tabelle zeigt einen Ausschnitt aus dem Coderaum der 8-Bit-Ganzzahlen in der Darstellung mit Vorzeichenbit:

Binär0000000000000001001010100111111110000000100000011010101011111111
Dezimal0142127-0-1-42-127

Diese Darstellung hat allerdings zwei Nachteile: Zum einen ist die Darstellung der Null uneindeutig, da auch sie zwei Repräsentationen besitzt (mit Vorzeichenbit 0 oder 1, also quasi “+0” und “-0”).

Zum anderen muss beim Addieren von Ganzzahlen in dieser Darstellung unterschieden werden, ob Summanden negativ sind und in diesem Fall ein anderer Algorithmus verwendet werden. Dieses Problem entsteht dadurch, dass die negativen Zahlen im Coderaum “falschherum” angeordnet sind (von der größten zur kleinsten).

Beispiel: Werden die 8-Bit-Repräsentationen der Zahlen -42 und 1 ohne Rücksicht auf das Vorzeichenbit summiert, ist das Ergebnis 10101011, was der Zahl -43 entspricht, nicht der erwarteten Zahl -41.

Daher verwenden Rechner in der Regel ein anderes Format zur Binärcodierung von negativen Ganzzahlen, die sogenannte Zweierkomplementdarstellung: Die erste Hälfte der N-Bit-Binärcodes stellt die nicht-negativen Ganzzahlen von 0 bis 2N-1-1 dar, danach folgen die negativen Zahlen von 2N-1 bis -1.

Auf diese Weise wird das doppelte Vorkommen der Null verhindert, die Null wird nun nur noch durch eine Folge aus 0-Bits repräsentiert (hier: 00000000). Außerdem liefert die binäre Addition von Ganzzahlen hier unabhängig von den Vorzeichen der Summanden das richtige Ergebnis, da die negativen Zahlen im Coderaum “richtigherum” angeordnet sind (also in aufsteigender Reihenfolge).

Die folgende Tabelle zeigt einen Ausschnitt aus dem Coderaum der 8-Bit-Ganzzahlen mit Vorzeichen in der Zweierkomplementdarstellung:

Binär0000000000000001001010100111111110000000100000011101011011111111
Dezimal0142127-128-127-42-1

Auch bei der Zweierkomplementdarstellung übernimmt das erste Bit die Rolle eines Vorzeichenbits – sein Wert bestimmt, ob eine Bitfolge eine negative oder nicht-negative Zahl darstellt. Bei 8 Bit werden also durch die Bitfolgen 00000000 bis 01111111 alle nicht-negativen Zahlen dargestellt (0 bis 27-1 = 127) und durch 10000000 bis 11111111 alle negativen Zahlen (-27 = -128 bis -1).

Um das Vorzeichen einer Zahl zu ändern, kann ein sehr einfacher Algorithmus verwendet werden: Es werden alle Bits der Binärdarstellung der Zahl “gekippt” (invertiert) und zum Ergebnis 1 hinzuaddiert. Diese Operation wird als Zweierkomplement bezeichnet (daher der Name dieser Darstellung).

Beispiel: Es soll die Zweierkomplementdarstellung der Zahl -42 mit 8 Bit berechnet werden:

  • 00101010 ← Binärdarstellung von 42 mit 8 Bit
  • 11010101 ← Invertieren aller Bits
  • 11010110 ← Hinzuaddieren von 1

Tool: In dieser interaktiven Anzeige können Sie die Zweierkomplementdarstellung selbst untersuchen. Klicken Sie auf die oberen Binärziffern, um ihre Werte zu ändern.

In der Zweierkomplementdarstellung mit N Bit können vorzeichenbehaftete Ganzzahlen aus dem Bereich -2ᴺ⁻¹ bis 2ᴺ⁻¹-1 repräsentiert werden. Das erste (linke) Bit gibt das Vorzeichen an.
Ist das Vorzeichenbit 0, entspricht die Repräsentation der vorzeichenlosen Ganzzahl. Anderenfalls stellt die Bitfolge diejenige negative Ganzzahl dar, die man erhält, wenn von der entsprechenden vorzeichenlosen Ganzzahl der Wert 2ᴺ abgezogen wird. Die Darstellung im Zweierkomplement kann also auch ganz einfach interpretiert werden, indem die höchste (linke) Stelle der Binärzahl, die 2ᴺ⁻¹ entspricht, negativ in die Summe eingeht.

Formel: Für die Bitfolge b1 b2 … bN-1 bN der Länge N ist die entsprechende Dezimalzahl d also durch die folgende Formel gegeben: $$d = -b_1 \cdot 2^{N-1} + b_2 \cdot 2^{N-2} + … + b_{N-1} \cdot 2 + b_N$$

Rationale Zahlen

Um rationale Zahlen oder “Kommazahlen” binär darzustellen, gibt es ebenfalls mehrere Möglichkeiten. Sehr einfach zu interpretieren ist die Darstellung als Festkommazahl. Hier wird festgelegt, dass eine bestimmte Anzahl von Bits zur Darstellung der Nachkommastellen verwendet wird. Bei einer Repräsentation mit 8 Bit kann beispielsweise vereinbart werden, dass die ersten 5 Bit die Stellen vor dem Komma darstellen und die letzten 3 Bit für die Nachkommastellen stehen. Die Stellen entsprechen dann von links nach rechts den Zweierpotenzen 24, …, 20, 2-1, 2-2 und 2-3. Zur Erinnerung: 2-n ist gleich 1 / 2n.

Beispiel: Die Binärdarstellung von 10.75 als Festkommazahl mit 8 Bit, davon 3 Nachkommastellenbits, lautet: 01010110. Die linken 5 Stellen codieren die Ganzzahl 10, die rechten 3 Stellen den Nachkommaanteil 1·2-1 + 1·2-2 + 0·2-3 = 0.5 + 0.25 = 0.75.

Tool: In dieser interaktiven Anzeige können Sie die Binärdarstellung von Festkommazahlen selbst untersuchen. Klicken Sie auf die oberen Binärziffern, um ihre Werte zu ändern, und verwenden Sie die unteren Schaltflächen, um die Anzahl der Nachkommastellenbits zu ändern.

Eine Festkommazahl mit N Bit, von denen M Nachkommastellenbits sind, lässt sich als Bruch Z / 2ᴹ mit ganzzahligem Zähler Z schreiben. Ihre Binärdarstellung ist dann identisch mit der Binärdarstellung der Ganzzahl Z mit N Bit.
Die Binärdarstellung von 10.75 als 8-Bit-Festkommazahl mit 3 Nachkommastellenbits ist also identisch mit der 8-Bit-Binärdarstellung der Ganzzahl 86, da 10.75 = 86 / 2³.

Formel: Für die Bitfolge b1 b2 … bN-1 bN der Länge N mit M Nachkommstellenbits ist die entsprechende Dezimalzahl d also durch die folgende Formel gegeben: $$d = \left( b_1 \cdot 2^{N-1} + b_2 \cdot 2^{N-2} + … + b_{N-1} \cdot 2 + b_N \right) /\ 2^M$$

Rationale Zahlen mit Vorzeichen lassen sich auf dieselbe Weise repräsentieren wie Ganzzahlen mit Vorzeichen, also üblicherweise in der Zweierkomplementdarstellung.

Beispiel: Die Zweierkomplementdarstellung von -10.75 als 8-Bit-Festkommazahl mit 3 Nachkommastellenbits lautet:

  • 01010110 ← Binärdarstellung von 10.75
  • 10101001 ← Invertieren aller Bits
  • 10101010 ← Hinzuaddieren von 1

Vertiefung: Byte-Reihenfolge

In Binärdateien wird die Reihenfolge der Bytes, durch die eine Ganzzahl repräsentiert wird, aus technischen Gründen manchmal umgedreht. Die 16-Bit-Darstellung der Zahl 260 lautet dann 00000100 00000001, und nicht 00000001 00000100, wie eigentlich erwartet.

Dieses Format heißt Little Endian (“kleinendiges” Format), da das Byte, dass die kleinsten Stellen enthält, vorne (links) steht. Die übliche Reihenfolge, die der Darstellung als Binärzahl entspricht, heißt entsprechend Big Endian (“großendiges” Format). Wir gehen hier in den Übungsaufgaben der Einfachheit davon aus, dass Big Endian-Codierung verwendet wird.

Übungsaufgaben

Aufgabe 1: DNS-Sequenzen

Die genetische Information von Lebewesen – das Genom – ist im Zellkern in der Desoxyribonukleinsäure (DNS) gespeichert, konkret wird sie durch die Abfolge der Nukleinbasen in den DNS-Strängen bestimmt. Dabei kommen nur vier Nukleinbasen vor (Adenin, Cytosin, Guanin und Thymin), womit sich die in der DNS gespeicherte Information durch eine Sequenz von vier Zeichen (in der Regel die Buchstaben A, C, G und T) darstellen lässt.

Image

In dieser Aufgabe sollen die Binärdarstellung der genetischen Information und die entstehenden Datenmengen untersucht werden.

  • Wie viele Bit werden benötigt, um ein Zeichen der Basensequenz zu codieren?
  • Wie würde der Binärcode für die Basensequenz CGACAT in der von Ihnen gewählten Codierung lauten?
  • Wie lang kann eine Basensequenz sein, die sich mit 1 MB codieren lässt?
  • Die Basensequenz der DNS von Kugelfischen (Takifugu rubripes) hat eine Gesamtlänge von 365 Millionen Zeichen. Wie groß ist die Datenmenge, die zur Speicherung dieser Sequenz benötigt wird? Geben Sie den Wert in einer geeigneten Größeneinheit an.
  • Bei menschlicher DNS umfasst das Genom etwa 3.1 Milliarden Basen. Zum Speichern der Daten können Sie einen USB-Stick mit 750 MB, 1 GB, 2 GB, 4 GB oder 8 GB Speicherkapazität verwenden. Welcher USB-Stick reicht hier aus?

Angenommen, in einer Binärdatei sollen mehrere unterschiedliche lange Basensequenzen gespeichert werden. Dazu wird neben A, C, G und T ein zusätzliches Trennzeichen verwendet, um kennzuzeichnen, wo eine Sequenz endet und die nächste beginnt.

  • Wie viele Bit wären nun zur Codierung jedes Zeichens in der Datei nötig?
  • Wie groß wäre nun eine Datei, die nur die Basensequenz von Takifugu rubripes enthält?
  • Fällt Ihnen eine andere ggf. bessere Möglichkeit ein, wie sich mehrere unterschiedlich lange Sequenzen in einer Datei speichern lassen?

Aufgabe 2: Binärdatei interpretieren

In einer Binärdatei sind Informationen über die Bevölkerungsentwicklung von Kiel gespeichert. Der Dateiinhalt hat dabei das folgende Format:

  • Die Datei enthält nacheinander mehrere Datensätze, die jeweils 6 Byte umfassen.
  • Jeder Datensatz besteht aus den folgenden drei Daten (jeweils 2 Byte):
    • die Jahreszahl als vorzeichenlose Ganzzahl
    • die Bevölkerungsdifferenz zum Vorjahr als Ganzzahl mit Vorzeichen in Zweierkomplementdarstellung
    • der Prozentsatz der Bevölkerungsanzahl im Vergleich zum Vorjahr als Festkommazahl mit 6 Nachkommastellenbits

Ihre Aufgabe besteht darin, den Inhalt der Binärdatei Kiel_Daten.bin zu decodieren ( Download):

00000111 11000111 00000110 00000100 00011001 00101000
00000111 11001000 00001000 00101100 00011001 00111000
00000111 11001001 11111110 11110100 00011000 11111001

Tragen Sie die in der Datei codierten Daten in die folgende Tabelle ein (als Hilfestellung ist der erste Datensatz bereits decodiert):

JahrDifferenzProzentsatzDatensatz in der Datei (6 Byte)
19911540100.62500000111 11000111 00000110 00000100 00011001 00101000
???00000111 11001000 00001000 00101100 00011001 00111000
???00000111 11001001 11111110 11110100 00011000 11111001

Verwenden Sie einen Binär-/Hexadezimaleditor, um die Datei zu untersuchen, z. B. den Online Editor HexEd.it.
Achten Sie darauf, den Editor so einzustellen, dass er das Format Big Endian verwendet, um Ganzzahlen zu interpretieren (z. B. in HexEd.it im linken Menü den unteren Bereich “Daten-Inspektor (Big-Endian)” durch Anklicken des Symbols + aufklappen).


  1. Es wird von vielen Standardisierungsorganisationen ausdrücklich empfohlen, die SI-Präfixe ausschließlich für Zehnerpotenzen und die IEC-Präfixe für Zweierpotenzen zu verwenden. Die Akzeptanz und Verbreitung der IEC-Präfixe ist im IT-Bereich bis heute zwar eher gering, nimmt aber zu (Stand 2021). ↩︎

2.2.1 Codierung von Textdaten

Nachdem Sie die Binärdarstellung von Ganzzahlen kennengelernt haben: Wie würden Sie einen Namen binär codieren? Überlegen Sie dazu: Wie viele Bits werden pro Zeichen benötigt, um die dafür mindestens benötigten Zeichen zu codieren? Wie könnte der Name “BEA BUX” in dieser Codierung aussehen?

Lösung

Sollen neben Groß- auch Kleinbuchstaben a–z dargestellt werden können, wird ein zusätzliches Bit benötigt, da sich der Zeichenumfang verdoppelt. Mit Ziffern, Satz- und Sonderzeichen wie Währungssymbolen, diakritischen Zeichen (z. B. Umlauten) und anderen Buchstaben des lateinischen Schriftsystems kommen schnell mehrere 100 Zeichen zusammen, für die unterschiedliche Codes benötigt werden. Für eine universelle Zeichencodierung, die auch umfangreichere Symbol- und Schriftsysteme wie das Chinesische oder Japanische umfasst, müssen dagegen mehrere 10 000 bis 100 000 Zeichen codierbar sein. Damit diese Daten eindeutig interpretierbar sind, muss in einem Standard festgelegt werden, durch welche Zahl welches Zeichen repräsentiert wird und wie diese Zahlenwerte binär codiert werden.

In dieser Lektion werden wir uns systematisch mit der Codierung von Textzeichen beschäftigen und die verbreiteten Standards zur Zeichencodierung (z. B. für Textdateien) untersuchen.

Zeichencodierung

Ein Zeichensatz (engl. character set oder kurz charset) beschreibt die Menge der zur Verfügung stehenden Zeichen, die dargestellt werden können, und legt für jedes Zeichen einen Zahlenwert fest. Die darstellbaren Zeichen werden also durchnummeriert. Der feste Zahlenwert eines Zeichens in einem Zeichensatz wird als Codepoint bezeichnet. Codepoints werden in der Regel hexadezimal, aber auch dezimal dargestellt.

Eine Zeichencodierung legt dagegen für jeden Codepoint bestimmte Byte-Werte fest, durch die das Zeichen binär codiert wird. Die früheren Zeichensätze wie ASCII oder ANSI (siehe unten) umfassten nicht mehr als 256 Zeichen, so dass der Unterschied zwischen Codepoint und Zeichencodierung keine Rolle spielte: Hier wurde ein Zeichen einfach durch die Binärdarstellung seines Codepoints codiert. Bei aktuellen Zeichensätzen wie Unicode, die mehrere 100 000 Zeichen umfassen, sind Codepoint und Codierung nicht unbedingt identisch, aber dazu später mehr.

Wie beginnen zunächst mit den einfachen Zeichencodierungen, in denen jedes Zeichen durch 1 Byte codiert wird.

ASCII

Der Standard ASCII (kurz für American Standard Code for Information Interchange) wurde ursprünglich bereits 1963 entwickelt. Er definiert eine 7-Bit-Zeichencodierung, die als Grundlage der meisten heute gebräuchlichen Zeichencodierungen dient. Der ASCII-Zeichensatz umfasst 27 = 128 Zeichen, die als Byte mit führendem 0-Bit dargestellt werden. Dabei stellen nur die Zeichen 32 bis 126 (hexadezimal 20 bis 7E) druckbare Symbole dar.

Die folgende Tabelle zeigt alle Zeichen und ihre Hexadezimal-Codes im ASCII-Zeichensatz (spezielle, meist nicht druckbare Zeichen sind kursiv dargestellt):

Code (hex.)…0…1…2…3…4…5…6…7…8…9…A…B…C…D…E…F
0…NULSOHSTXETXEOTENQACKBELBSHTLFVTFFCRSOSI
1…DLEDC1DC2DC3DC4NAKSYNETBCANEMSUBESCFSGSRSUS
2…SP!"#$%&'()*+,-./
3…0123456789:;<=>?
4…@ABCDEFGHIJKLMNO
5…PQRSTUVWXYZ[\]^_
6…`abcdefghijklmno
7…pqrstuvwxyz{|}~DEL

Das Zeichen SP mit dem Hexadezimal-Code 20 (dezimal 32) stellt dabei das Leerzeichen (engl. space) dar.

Beispiel: Die Zeichenfolge “Hallo, Kiel!” wird im ASCII-Zeichencode durch die Bytefolge 48 61 6C 6C 6F 2C 20 4B 69 65 6C 21 (hexadezimal) repräsentiert.

ZeichenHallo,Kiel!
Code (hex.)48616C6C6F2C204B69656C21
Code (dez.)729710810811144327510510110833

Steuerzeichen

Historisch gesehen baut ASCII auf Zeichencodierungen für Fernschreiber (engl. teletype writer) auf, beispielsweise dem Baudot-Code und insbesondere dem Murray-Code, die wiederum auf Zeichencodierungen für frühere Telegrafen wie dem Morse-Code basieren.

Die ersten 32 Zeichen und das letzte Zeichen im ASCII-Code sind Steuerzeichen für das Ausgabegerät und werden nicht als sichtbare Symbole im Text dargestellt. Diese Steuerzeichen sind historisch durch ihre Verwendung für Fernschreiber begründet, was sich auch an ihren Bezeichnungen erkennen lässt (z. B. “Zeilenvorschub”, “Wagenrücklauf”). Viele dieser Zeichen haben heute in Textdateien keine Bedeutung mehr und können hier ignoriert werden. Für uns sind im Wesentlichen nur die folgenden Steuerzeichen relevant:

Das Tabulator-Zeichen (engl. horizontal tabulator, kurz HT) mit dem Hexadezimal-Code 09 (dez. 9) wird insbesondere in Textdateien verwendet, die Tabellendaten beschreiben, um Daten in einer Zeile voneinander zu trennen. Das Zeichen wird in den meisten Textverarbeitungsprogrammen mit der Tabulator-Taste erzeugt und durch mehrere aufeinanderfolgende Leerzeichen angezeigt.

Ein Zeilenumbruch, der in Textverarbeitungsprogrammen mit der Eingabe-Taste erzeugt wird, wird je nach Betriebssystem unterschiedlich codiert:

  • In Linux, iOS und anderen Unix-ähnlichen Betriebssystemen wird das Zeilenvorschub-Zeichen (engl. line feed, kurz LF) mit dem Hexadezimal-Code 0A (dez. 10) für einen Zeilenumbruch verwendet.
  • In Windows steht vor dem Zeilenvorschub-Zeichen zusätzlich das Wagenrücklauf-Zeichen (engl. carriage return, kurz CR) mit dem Hexadezimal-Code 0D (dez. 13), so dass ein Zeilenumbruch hier immer durch zwei aufeinanderfolgende Bytes repräsentiert wird (hex. 0D 0A).

Erweitertes ASCII

Der ASCII-Zeichensatz wurde ursprünglich nur zur Codierung englischsprachiger Texte entwickelt und enthält daher keinerlei regionale Sonderzeichen wie beispielweise die deutschen Umlaute, das Eszett oder Vokale mit Akzenten – von Buchstaben anderer Alphabete ganz abgesehen.

Aus diesem Grund wurden ab Ende der 1970er verschiedene Standards für Erweiterungen des ASCII-Zeichensatzes veröffentlicht, in denen die übrigen 128 Zeichen, die bei einer 8-Bit-Codierung festgelegt werden können, für verschiedene regionale Schriftsysteme festgelegt wurden. Die bekanntesten dieser Erweiterungen sind zum einen die ISO 8859-Codierungen, zum anderen die Windows Codepages, deren jeweilige Varianten für westeuropäische Sprachen (ISO 8859-1 und Windows-1252) zum Teil auch heute noch (wenn auch im Vergleich zu Unicode in sehr viel geringerem Maße) verwendet werden. Wir betrachten diese beiden Zeichencodierungen hier hauptsächlich deswegen, weil Unicode auf ihnen aufbaut.

ISO 8859-1

Die Normenfamilie ISO 8859 definiert verschiedene 8-Bit-Zeichencodierungen, die den ASCII-Zeichensatz um Sonderzeichen erweitern. Hier werden nur Symbole für die Codepoints 160–255 (hex. A0FF) definiert, die Codepoints 128–159 (hex. 809F) werden nicht festgelegt. Die Normenfamilie umfasst Standards für verschiedene Schriftsysteme (neben Lateinisch z. B. Arabisch, Griechich, Kyrillisch), die als ISO 8859-1 bis 8859-16 bezeichnet sind.

ISO 8859-1 (auch als Latin-1 bezeichnet) ist dabei die Zeichencodierung für westeuropäische Sprachen und die am weitesten verbreitete Variante der ISO 8859-Normen.1

Die folgende Tabelle stellt die zusätzlichen Zeichen im ISO 8859-1-Zeichensatz dar:

Code (hex.)…0…1…2…3…4…5…6…7…8…9…A…B…C…D…E…F
A…NBSP¡¢£¤¥¦§¨©ª«¬SHY®¯
B…°±²³´µ·¸¹º»¼½¾¿
C…ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ
D…ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß
E…àáâãäåæçèéêëìíîï
F…ðñòóôõö÷øùúûüýþÿ

Hier werden zwei weitere gebräuchliche Steuerzeichen definiert:

  • Das Zeichen NBSP mit dem Hexadezimal-Code A0 (dezimal 160) stellt das geschützte Leerzeichen (engl. non-breaking space) dar.
  • Das Zeichen SHY mit dem Hexadezimal-Code AD (dezimal 173) ist das weiche Trennzeichen (engl. soft hyphen).

Beispiel: Die Zeichenfolge “Grüße aus Mölln!” wird in ISO 8859-1 durch die Bytefolge 47 72 FC DF 65 20 61 75 73 20 4D F6 6C 6C 6E 21 (hexadezimal) repräsentiert.

ZeichenGrüßeausMölln!
Code (hex.)4772FCDF6520617573204DF66C6C6E21
Code (dez.)7111425222310132971171153277246108108110

Windows-1252 (ANSI)

Die von Microsoft für das Betriebssystem Windows entwickelten Windows Codepages definieren alternative ASCII-Erweiterungen, die zu großen Teilen identisch mit den ISO 8859-Standards sind. Das Äquivalent zu ISO 8859-1 ist Windows-1252 (auch Codepage 1252 oder Western European). Diese Zeichencodierung wird auch oft ANSI-Zeichencode genannt (ANSI steht für American National Standards Institute).2

Windows-1252 unterscheidet sich von ISO 8859-1 nur darin, dass hier auch druckbare Symbole für die meisten der Codepoints 128–159 (hex. 809F) festlegt werden:

Code (hex.)…0…1…2…3…4…5…6…7…8…9…A…B…C…D…E…F
8…ƒˆŠŒŽ
9…˜šœžŸ

Auf diese Weise entstand eine Vielzahl unterschiedlicher Zeichencodierungen für verschiedene Alphabete, in denen dasselbe 8-Bit-Muster je nach Kontext verschiedene Zeichen bedeuten kann. Neben 8-Bit-Zeichencodierungen entstanden dabei auch Codierungen, die mehr Bits pro Zeichen verwenden, um alle relevanten Zeichen zu codieren, etwa für das chinesische oder japanische Schriftsystem.

Unicode

Der Unicode-Zeichensatz wurde entwickelt, um dem Chaos aus regionalspezifischen Codierungen ein Ende zu bereiten und alle Zeichen in einem Standard zusammenzufassen. Unicode (auch ISO 10646 oder UIC, kurz für Universal Coded Character Set) ist heute der mit Abstand am weitesten verbreitete internationale Standard zur Definition eines global einheitlichen Zeichensatzes.

Ziel des Unicode-Standards ist es, langfristig für jedes Symbol aller weltweit bekannten Schrift- und Zeichensysteme einen Codepoint (“Unicode-Nummer”) festzulegen. Der aktuelle Unicode-Standard (Version 13.0.0, Stand August 2021) umfasst 143 859 Zeichen, darunter neben Schriftzeichen und Währungssymbolen auch geometrische Symbole und Emojis.3

Die ersten 256 Codepoints sind in Unicode identisch mit ASCII und ISO 8859-1 bzw. ANSI (bis auf die Codepoints 128–159, für die in Unicode keine druckbaren Symbole definiert sind, sondern weitere Steuerzeichen).

Da ein Byte hier klarerweise nicht mehr ausreicht, um ein Unicode-Zeichen darzustellen, gibt es verschiedene Standards zur Zeichencodierung, also um die Unicode-Nummern als Byte-Codes darzustellen. Verfahren zur Abbildung von Unicode-Nummern auf Byte-Codes werden als UTF (Unicode Transformation Format) bezeichnet.

UTF-32

Die einfachste, aber auch speicheraufwendigste Methode besteht darin, jedes Unicode-Zeichen mit vier Byte (= 32 Bit) darzustellen, so dass bis zu 232 (also mehr als 4 Mrd.) Zeichen darstellbar sind. Diese Zeichencodierung wird als UTF-32 bezeichnet. Eine UTF-32-codierte Textdatei ist also viermal so groß wie eine mit ANSI oder ISO 8859-1 codierte Datei, auch wenn nur Zeichen aus dem ANSI- bzw. ISO 8859-1-Zeichensatz verwendet werden.

Beispiel: Die Zeichenfolge “Grüße aus Mölln!” wird in ANSI und ISO 8859-1 durch die Bytefolge 47 72 FC DF 65 20 61 75 73 20 4D F6 6C 6C 6E 21 (hexadezimal) repräsentiert, also durch 16 Byte. Wird UTF-32-Codierung verwendet, werden 64 Byte benötigt, nämlich 00 00 00 47 00 00 00 72 00 00 00 FC 00 00 00 DF

UTF-8

Eine bessere Methode besteht darin, variable Codelängen zu verwenden – die Zeichencodierung UTF-8 setzt eine solche Strategie um. UTF-8 ist momentan die mit Abstand am weitesten verbreitete Zeichencodierung für Unicode-Textdateien.4

Hier werden häufiger auftretende Zeichen (geringere Unicode-Nummern) mit weniger Byte codiert als spezielle, seltenere Zeichen (höhere Unicode-Nummern). Das Codierungsverfahren ist folgendermaßen definiert:

  • Beginnt ein Byte mit dem höchsten Bit 0, so codieren die restlichen 7 Bit das entsprechende ASCII-Zeichen.
    0xxx xxxx
  • Beginnt ein Byte dagegen mit der Bitfolge 110, so wird es zusammen mit dem folgenden Byte als Unicode-Zeichen interpretiert. Dabei wird erwartet, dass das folgende Byte mit der Bitfolge 10 beginnt (anderenfalls ist der UTF-8-Code nicht “wohlgeformt”, also bzgl. der Syntaxregeln für UTF-8-Codes formal nicht korrekt). Der Codepoint des Unicode-Zeichens wird durch die letzten 5 Bit des ersten Zeichens und die letzten 6 Bit des zweiten Zeichens gebildet. Auf diese Weise werden mit 11 Bit die Unicode-Zeichen mit den Nummern 128 bis 2047 dargestellt (u. a. Lateinisch, Griechisch, Kyrillisch, Arabisch und Hebräisch).
    110x xxxx 10xx xxxx
  • Beginnt es mit der Bitfolge 1110, wird es mit den beiden folgenden Byte zusammen als Unicode-Zeichen interpretiert. Auf diese Weise werden mit 16 Bit die Unicode-Zeichen mit den Nummern 2048 bis 65 535 dargestellt (u. a. Pfeile, mathematische Symbole, Chinesisch, Japanisch).
    1110 xxxx 10xx xxxx 10xx xxxx
  • Beginnt es mit 11110, wird es mit den folgenden drei Bytes zusammen als Unicode-Zeichen interpretiert. Auf diese Weise werden mit 21 Bit die Unicode-Zeichen mit den Nummern 65 536 bis 2 097 151 dargestellt (u. a. antike Schriftzeichen, Emojis).
    1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx

Ein Unicode-Zeichen wird in UTF-8 also durch ein bis vier Bytes repräsentiert, wobei gewährleistet ist, dass über 2 Mio. Zeichen darstellbar sind (von denen momentan knapp 7% genutzt werden).

Beispiele:

  • Das Unicode-Zeichen Ä hat die Unicode-Nummer C4 (hex.) bzw. 196 (dez.), die Binärdarstellung dieser Zahl ist 1100 0100.
    Die UTF-8-Codierung benötigt also 2 Byte und lautet: 1100 0011 1000 0100 (binär) bzw. C3 84 (hex.).
  • Das Unicode-Zeichen 😀 hat die Unicode-Nummer 1F600 (hex.) bzw. 128 512 (dez.), die Binärdarstellung dieser Zahl ist 1 1111 0110 0000 0000.
    Die UTF-8-Codierung benötigt also 4 Byte und lautet: 1111 0000 1001 1111 1001 1000 1000 0000 (binär) bzw. F0 9F 98 80 (hex.).

Für Textdateien sollte in der Regel immer UTF-8 als Zeichencodierung verwendet werden, sofern Sonderzeichen verwendet werden, die über den ASCII-Zeichensatz hinaus gehen, da dieses Format am gebräuchlichsten ist.

Übungsaufgaben

Aufgabe 1: Zeichencodierung

In einer Textdatei, die im Textbearbeitungsprogramm (fälschlicherweise) mit der Zeichencodierung ISO 8859-1 angezeigt wird, kommt mehrmals die Zeichenfolge ä vor.

Image

  • Welchen Bitcode hat die Zeichenfolge ä in ISO 8859-1?
  • Welche Zeichencodierung ist vermutlich eigentlich richtig für diese Textdatei?
  • Welches Zeichen wird statt ä in der eigentlich richtigen Zeichencodierung dargestellt?

Aufgabe 2: Textdateigröße

Der folgende Text soll als erweiterter ASCII-Code dargestellt werden (ISO 8859-1 oder Windows-1252/ANSI):

Bei UTF-8 werden
1-4 Byte verwendet,
um ein Zeichen zu
speichern.
  • Wie viele Bytes werden zur Darstellung des Textes benötigt?
  • Überprüfen Sie Ihre Vorhersage, indem Sie den Text in einen Texteditor eingeben/kopieren, als Textdatei in einem geeigneten Format (ISO 8859-1 oder Windows-1252/ANSI) speichern und anschließend die Dateigröße überprüfen.
  • Macht es für die Dateigröße einen Unterschied, ob Sie die Textdatei in Windows oder Linux erstellen?
  • Ändert sich die Dateigröße, wenn der Text mit UTF-8 codiert wird?

Aufgabe 3: UTF-8-Codierung

Welches Unicode-Zeichen verbirgt sich hinter dem (hier hexadezimal dargestellten) UTF-8-Code F0 9F 91 8D?

Ermitteln Sie dazu seine Unicode-Nummer aus dem Bitcode und suchen Sie in der Unicode-Zeichentabelle auf https://unicode-table.com/de danach (im Suchfeld oben die Unicode-Nummer als Hexadezimal- oder Dezimalzahl angeben).


  1. Der später eingeführte Standard ISO 8859-15 (auch Latin-9) für westeuropäische Sprachen unterscheidet sich nur in 8 Zeichen von ISO 8859-1 (Latin-1):

    Code (hexadezimal)A4A6A8B4B8BCBDBE
    Zeichen in ISO 8859-1ŠšŽžŒœŸ
    Zeichen in ISO 8859-15¤¦¨´¸¼½¾
     ↩︎
  2. Genauer formuliert: Windows-1252 basiert auf einem früheren Entwurf der ANSI-Zeichencodierung, die später mit Änderungen für die Codepoints 128–159 zu ISO 8859-1 wurde. Die Bezeichnung “ANSI” wird heute aber überwiegend synonym für Windows-1252 verwendet. ↩︎

  3. siehe offizielle Website des Unicode-Konsortiums: https://home.unicode.org ↩︎

  4. So verwenden 2021 über 97% aller Webseiten UTF-8 als Zeichencodierung, siehe https://w3techs.com/technologies/cross/character_encoding/ranking ↩︎

2.2.2 Codierung von Bilddaten

In dieser Lektion werden wir uns mit den Grundlagen der Binärdarstellung von Bildern als Rastergrafiken beschäftigen, Merkmale von Rastergrafiken sowie einfache Dateiformate für Rastergrafiken kennenlernen.

Rastergrafiken eignen sich gut als anschauliches und aus dem Alltag bekanntes Beispiel zur Vertiefung des Themas “Codierung” im Schulunterricht, weil sie sich zum einen vergleichsweise einfach codieren und repräsentieren lassen, dabei zum anderen aber auch Diskussionsspielraum offen lassen. Die Codierung von Bilddaten motiviert außerdem die Notwendigkeit, Daten zu komprimieren, was augenscheinlich wird, wenn der Datenumfang von Bildern untersucht und bewertet wird.

Rastergrafiken

Wenn Sie eine Bilddatei in einem Format wie JPG oder PNG in einem Bildanzeigeprogramm öffnen und stark vergrößern, erkennen Sie, dass das Bild aus einzelnen quadratischen Bildpunkten zusammengesetzt ist, die in einem Raster angeordnet sind. Aus diesem Grund wird dieses Bildformat als Rastergrafik bezeichnet.

Image

Eine Rastergrafik beschreibt also Bilddaten in digitaler Form, indem das Bild in ein Raster von Bildpunkten, die sogenannten Pixel (kurz für engl. pixel elements), aufgeteilt wird und jedem Pixel ein diskreter Farbwert zugeordnet wird. Die wichtigsten Attribute einer Rastergrafik sind die Bildgröße, also Breite und Höhe (in Pixeln), sowie die Farbtiefe, also die Anzahl an Bits, die benötigt werden, um den Wert eines Pixels darzustellen. Wird beispielsweise nur 1 Bit pro Pixel verwendet, lassen sich nur 2 verschiedene Farbwerte pro Pixel unterscheiden (z. B. schwarz und weiß), während sich mit 8 Bit (also 1 Byte) bereits 28 = 256 verschiedene Farbwerte darstellen lassen.

Image

Struktur

Die Pixelwerte werden im Speicher üblicherweise zeilenweise von oben links nach unten rechts als Bitfolge repräsentiert. Dabei codieren jeweils D aufeinanderfolgende Bits den Wert eines Pixels, wobei D die Farbtiefe darstellt (z. B. 8 Bit pro Pixel). Bei einer Farbtiefe von D Bit kann jedes Pixel 2D verschiedene Farbwerte annehmen.

Beispiel: Ein Bild der Größe 8 × 8 Pixel, die jeweils nur schwarz oder weiß sind, lässt sich als Bitfolge der Länge 64 Bit darstellen. Jedes Bit stellt ein Pixel dar (hier: 0 = weiß, 1 = schwarz).

Image

Der Datenumfang einer Rastergrafik der Größe W × H Pixel (W ist die Breite und H die Höhe) mit einer Farbtiefe von D Bit pro Pixel umfasst somit W⋅H⋅D Bit, wenn die Daten unkomprimiert vorliegen.

Wenn die Größe und Farbtiefe des Bildes nicht fest vorgegeben ist, müssen diese Informationen zusammen mit den Bilddaten gespeichert werden, damit die Bilddaten richtig interpretiert werden können.

Farbwerte

Wir unterscheiden Bilder nach den Werten, die ihre Pixel annehmen können, als Schwarz-Weiß-Bilder (Binärbilder), Graustufenbilder und Farbbilder.

In einem Binärbild kann jedes Pixel nur einen von zwei Werten annehmen, die üblicherweise als schwarz oder weiß dargestellt werden (“Schwarz-Weiß-Bild”). In diesem Fall reicht ein Bit pro Pixel, um die Bilddaten binär darzustellen (Farbtiefe D = 1). Bei Binärbildern repräsentiert 0 in der Regel ein Hintergrundpixel (hier weiß dargestellt) und 1 ein Vordergrundpixel (hier schwarz dargestellt).Image
In Graustufenbildern kann ein Pixel dagegen verschiedene Graustufen als Wert haben. Wird jedes Pixel durch 1 Byte (= 8 Bit) repräsentiert, bedeutet das etwa, dass 28 = 256 verschiedene Graustufen im Bild vorkommen können. Der Wert 0 repräsentiert dabei schwarz, 255 weiß und die Werte 1 bis 254 in linearer Abstufung die dazwischen liegenden Grautöne.Image
In Farbbildern wird pro Pixel üblicherweise ein Rot-, Grün- und Blauwert gespeichert (RGB-Werte). Mit diesen drei Werten lassen sich alle Farben des RGB-Farbraums darstellen, in dem durch das additive Mischen der drei Grundfarben Rot, Grün und Blau jeder beliebige Farbeindruck nachgebildet wird. Gelb ergibt sich beispielsweise durch 100% Rot + 100% Grün + 0% Blau, während 50% Rot + 75% Grün + 100% Blau ein Himmelblau ergibt.
Alternativ kann auch ein “Farbpalette” verwenden werden zur Repräsentation von Bildern mit mehreren Farben verwendet werden (siehe unten).
Image

RGB-Farbwerte

Wird für den Rot-, Grün- und Blauwert je 1 Byte verwendet, ergibt sich eine Farbtiefe von 3 Byte bzw. 24 Bit, womit insgesamt (28)3 = 224 ≈ 16 Mio. verschiedene Farben pro Pixel darstellbar sind. Diese Werte liegen im Speicher in der Regel direkt aufeinanderfolgend in der Reihenfolge RGB (seltener auch in anderen Reihenfolgen wie BGR) und können als Hexadezimalcode mit sechs Ziffern (für 3 Byte) dargestellt werden:

Image

Die hexadezimale Farbdarstellung wird sehr häufig in Grafikprogrammen oder auch in Webseiten zur Definition von Farben verwendet. Daneben ist aber auch die Darstellung durch drei RGB-Werte aus dem Bereich 0–255 oder 0–100% üblich.

Beispiel: Der Hexadezimalcode 80BEFF entspricht den dezimalen RGB-Farbwerten 128, 190, 255, bzw. als Prozentangaben bzgl. 255 als Maximalwert 50.2%, 74.5% und 100%.

Tool: In dieser interaktiven Anzeige können Sie verschiedene Farben über ihre RGB-Werte (8 Bit pro Kanal) zusammenmischen. Das untere Feld zeigt den RGB-Farbcode der resultierenden Farbe (in der Mitte der drei Kreise) im Hexadezimalformat an.




Farbkanäle

Jedes Pixel in einem RGB-Farbbild der Farbtiefe 3⋅D enthält also im Grunde drei Helligkeitswerte, nämlich jeweils einen D-Bit-Wert für Rot, Grün und Blau. Die eigentliche Farbe des Pixels entsteht durch die Kombination dieser drei Werte. Ein Farbbild lässt sich also auch durch drei Bilder repräsentieren, bei denen jedes Pixel nur je einen D-Bit-Wert hat, nämlich entweder den Rot-Wert, Grün-Wert oder Blau-Wert des Farbbildes. Die so repräsentierten Bilder werden als Farbkanäle bezeichnet.

ImageImageImageImage
RGB-Farbbild
Darstellung des Rot-Kanals
Darstellung des Grün-Kanals
Darstellung des Blau-Kanals

Ein RGB-Bild wird dementsprechend auch als Mehrkanal-Bild bezeichnet, das sich aus dem Rot-, Grün- und Blaukanal zusammensetzt, während ein Graustufenbild (bzw. auch ein Binärbild) ein Einkanal-Bild ist. Die Farbtiefe wird bei Farbbildern oft auch pro Kanal angegeben, also “8-Bit pro Kanal” statt “24-Bit”.

In den getrennten Darstellungen der Kanäle lässt sich gut erkennen, welche Farbe aus welchen Rot-, Grün- und Blau-Anteilen zusammengesetzt ist: Die gelben Bildbereiche auf dem Leuchtturm enthalten hohe Anteile Rot und Grün, aber kaum Blau, während die hellen himmelblauen Bereiche im Hintergrund hohe Grün-/Blau-Anteile und einen etwas geringeren Rot-Anteil enthalten.

Alphakanal

Um Bildbereiche mit Abstufungen transparent – also mehr oder weniger durchsichtig – darzustellen, kann ein zusätzlicher Kanal verwendet werden, der für jedes Pixel einen Transparenzwert enthält. Dieser zusätzliche Kanal wird als Alphakanal bezeichnet.1 Jedes Pixel in einem Farbbild mit Transparenz enthält dann vier Helligkeitswerte, je einen für Rot, Grün, Blau und “Undurchsichtigkeit” (Alpha-Wert), die auch als RGBA-Werte bezeichnet werden. Bei einem RGBA-Bild mit 8 Bit pro Kanal bedeutet ein Alpha-Wert 0 in der Regel, dass das Pixel vollständig transparent ist (also unsichtbar), während 255 ein vollständig undurchsichtiges Pixel kennzeichnet.

ImageImageImage
Darstellung der RGB-Kanäle
Darstellung des Alpha-Kanals
Anzeige des RGBA-Farbbild über einem gemusterten Hintergrund

Farbpaletten

Enthält ein Bild nur eine geringe Anzahl von Farben (z. B. nur schwarz, weiß, gelb und rot), können die Farben alternativ auch in Form einer Farbpalette repräsentiert werden. Die Farbpalette gibt eine begrenzte Menge von Farben vor, und in jedem Pixel wird nun statt Farbwert die Nummer der entsprechenden Farbe gespeichert.2 Bei einer Farbpalette mit 4 Einträgen reichen dazu beispielsweise 2 Bit pro Pixel.

Image

Die Paletteninformation – also die Anzahl der Paletteneinträge und der RGB-Farbwert für jeden Eintrag – müssen mit den Bilddaten zusammen gespeichert werden, damit aus den Daten entnommen werden kann, welche Nummer welche Farbe darstellt.

Dateiformate

Damit Bilddaten gespeichert und interpretiert werden können, wird in einem Dateiformat genau festgelegt, welche Informationen in welcher Codierung an welcher Stelle in der Datei stehen. Ein Datenformat legt also Syntax und Semantik fest. Um ein Dateiformat zu spezifizieren müssen die folgenden Fragen berücksichtigt werden:

  • Was muss alles gespeichert werden? Dazu gehören bei Rastergrafiken beispielsweise die folgenden Informationen:
    • Wie groß ist das Bild? (Höhe, Breite)
    • Wie viele Bits werden pro Pixelwert verwendet? (Farbtiefe)
    • Wie ist ein Pixelwert zu interpretieren? (Schwarz-Weiß-Werte, Grauwerte, RGB-Farbwerte)
    • ggf. Kommentare und weitere Informationen über das Bild (z. B. Autor*in, Lizenz)
    • die eigentlichen Bilddaten (Pixelwerte)
  • In welcher Form wird es gespeichert (Syntax)?
    • Wo stehen welche Daten? (Struktur des Headers, Reihenfolge der Pixelwerte)
    • Wie sind welche Daten codiert? (Textformat, Binärformat)

Examplarisch werden wir hier Portable Anymap (kurz PNM) als ein sehr einfaches Dateiformat für Rastergrafiken vorstellen und anhand dessen grundlegende Merkmale von Dateiformaten für Rastergrafiken veranschaulichen.

Portable Anymap

PNM (kurz für Portable Anymap) ist ein sehr einfaches, unkomprimiertes Dateiformat für Binär-, Graustufen und Farbbilder, in dem sich Bilder auch als reine Textdateien speichern lassen. PNM eignet sich daher gut als Einstiegsbeispiel für Bilddateiformate.

PNM lässt sich für Binärbilder, Graustufenbilder und RGB-Farbbilder (ohne Transparenz) mit 8 Bit oder 16 Bit Farbtiefe verwenden.3 Es wird von vielen (aber längst nicht allen) modernen Grafikprogrammen (z. B. GIMP) und Bildbetrachtern unterstützt.

Das PNM-Format für Binärbilder heißt Portable Bitmap (PBM), Portable Graymap (PGM) für Graustufenbilder und Portable Pixmap (PPM) für Farbbilder.4 Die Abkürzungen werden üblicherweise auch als Dateiendungen für die Formate (.pbm, .pgm, .ppm) verwendet. PNM erlaubt es, die Bilddaten im Binärformat, aber auch als reine Textdatei im ASCII-Format zu speichern. Dadurch lassen sich PNM-Dateien prinzipiell auch mit einem einfachen Texteditor erstellen und bearbeiten – allerdings so nicht als Bild anzeigen.

Tool: Wenn auf Ihrem Rechner kein Grafikprogramm oder Bildbetrachter installiert ist, mit dem Sie PNM-Dateien als Bild darstellen können, können Sie alternativ diesen einfachen Online-Bildbetrachter für PNM-Dateien verwenden:

Dateistruktur

PNM definiert sechs Bildformate: PBM, PGM und PPM, jeweils mit Bilddaten im Binärformat oder im Textformat. Die Formate sind alle nach dem selben Schema aufgebaut, das anhand des folgenden Beispiels erläutert wird ( Download):

P2
17 8
100
10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
10 50 10 50 10 10 10 10 10 80 10 80 10 10 10 10 10
10 50 10 50 10 65 65 65 10 80 10 80 10 10 10 10 10
10 50 50 50 10 10 10 65 10 80 10 80 10 95 95 95 10
10 50 10 50 10 65 65 65 10 80 10 80 10 95 10 95 10
10 50 10 50 10 65 10 65 10 80 10 80 10 95 10 95 10
10 50 10 50 10 65 65 65 10 80 10 80 10 95 95 95 10
10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10

Die ersten drei Zeilen stellen den Dateikopf oder Header dar, der Informationen über das Bildformat enthält, anschließend folgen die Pixelwerte, also die eigentlichen Bilddaten.

P2Die “Magic Number” zu Beginn codiert den Bildtyp (Binär-, Graustufen- oder Farbbild) und das Format, in dem die Bilddaten gespeichert sind (binär oder im ASCII-Textformat). “P2” bedeutet “Graustufenbild im ASCII-Textformat”.
17 8Es folgt die Breite und Höhe des Bildes in Pixeln, hier also 17 × 8 Pixel.
100Bei Graustufen- und Farbbildern folgt der Wert, der 100% Helligkeit entspricht (max. 65536). Da hier 100 steht, stehen in den folgenden Bilddaten nur Werte zwischen 0 und 100.
10 ...Nach dem Header folgen die eigentlichen Bilddaten, entweder als Dezimalzahlen im ASCII-Textformat oder binär codiert.

Der Header ist immer im ASCII-Textformat gespeichert, Breite, Höhe und max. Helligkeit sind hier als Dezimalzahlen angegeben. Die einzelnen Werte können durch beliebig viele Leerzeichen, Zeilenumbrüche oder Tabulatorzeichen voneinander getrennt sein.5

Auf den letzten Eintrag des Headers folgen die eigentlichen Bilddaten, je nach “Magic Number” entweder als Dezimalzahlen im ASCII-Format (jeweils durch Leerzeichen, Tabulatorzeichen oder Zeilenumbrüche getrennt) oder binär codiert.

  • Bei Schwarz-Weiß-Bildern kommen hier nur 0 und 1 als Werte vor, wobei 0 in der Regel als weiß und 1 als schwarz interpretiert wird.
  • Bei Graustufen- und Farbbildern kommen hier nur Werte zwischen 0 und M vor, wobei M der maximale Helligkeitswert ist, der nach der Breite und Höhe im Header steht. In einem Graustufenbild wird 0 als schwarz, M als weiß und 1 bis M-1 als Graustufen dazwischen interpretiert.

Das in diesem Beispiel codierte Bild lässt sich in der Textdarstellung eventuell erahnen und ist hier in 10-facher Vergrößerung dargestellt:

Image

Bildformate

Portable Anymap bietet die folgenden Bildformate an, die durch die “Magic Number” im Header festgelegt werden:

Magic NumberBildtypFormatnameCodierung der BilddatenBits/Pixel (Binärformat)Beispieldatei
P1BinärbildPortable Bitmap (PBM)ASCII-Textformat Download
P2GraustufenbildPortable Graymap (PGM)ASCII-Textformat Download
P3RGB-FarbbildPortable Pixmap (PPM)ASCII-Textformat Download
P4BinärbildPortable Bitmap (PBM)Binärformat1 Download
P5GraustufenbildPortable Graymap (PGM)Binärformat8 oder 16 Download
P6RGB-FarbbildPortable Pixmap (PPM)Binärformat24 oder 48 Download

Ob es sich um ein Binärbild, Graustufenbild oder Farbbild handelt, wird ausschließlich anhand der “Magic Number” entschieden, nicht anhand der Dateiendung! Eine Bilddatei im Portable Anymap-Format mit der Dateiendung .ppm, das zu Beginn den Eintrag P2 enthält, wird also als Graustufenbild (PGM) interpretiert, nicht als Farbbild (PPM)!

Wenn die Bilddaten im Binärformat codiert werden, gilt:

  • Bei Schwarz-Weiß-Bildern werden je 8 Pixelwerte pro Byte gespeichert (1 Bit pro Pixel).
  • Bei Graustufenbildern werden je 1–2 Byte pro Pixel gespeichert, bei RGB-Farbbildern je 1–2 Bytes pro Farbkanal (also 3–6 Byte pro Pixel). Die Anzahl an Bytes wird hier aus dem maximalen Helligkeitswert abgeleitet: Ist der Wert kleiner als 256, wird 1 Byte verwendet, anderenfalls 2 Byte.

Im Beispiel wird 100 als maximaler Helligkeitswert verwendet, hier werden die Bilddaten also mit Farbwerten zwischen 0 (schwarz) und 100 (weiß) angegeben. Würden die Bilddaten im Binärformat codiert werden (“Magic Number” P5), würde hier aus technischen Gründen 1 Byte pro Pixel verwendet werden – auch wenn 7 Bit prinzipiell ausreichen, um die Werte 0 – 100 zu codieren. In der Praxis ist es am üblichsten, in Portable Anymap-Dateien als maximalen Helligkeitswert 255 anzugeben. So können 256 Graustufen bzw. 24-Bit RGB-Werte als Farbwerte gespeichert werden (8 Bit/Kanal). Der maximale Helligkeitswert 65535 (= 16 Bit/Kanal) ist eher unüblich, da er von den meisten Anzeigegeräten nicht sinnvoll dargestellt werden kann.

Beispiele

In der Bildformat-Tabelle finden Sie für jedes Format P1–P6 eine Datei als Beispiel verlinkt. Im folgenden sehen wir uns den Dateiinhalt dieser Dateien genauer an und interpretieren den Inhalt.

Binärbild

Image Die Dateien smiley_ascii.pbm und smiley_binary.pbm stellen ein Schwarz-Weiß-Bild dar. Der Header ist bis auf die “Magic Number” identisch. In der ersten Datei stehen nach dem Header die Pixelwerte im Textformat, durch Leerzeichen und Zeilenumbrüche getrennt, so dass sich in einem Texteditor gut erkennen lässt, wie das Bild aussieht.

P1
8 8
0 0 1 1 1 1 0 0
0 1 0 1 1 0 1 0
1 1 0 1 1 0 1 1
1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 1
1 1 0 0 0 0 1 1
0 1 1 0 0 1 1 0
0 0 1 1 1 1 0 0
P4
8 8
<ZÛÿ.Ãf<

In der zweiten Datei steht zu Beginn P4 statt P1, was bedeutet, dass die auf den Header folgenden Pixelwerte binär codiert sind. Wenn Sie diese Datei in einem Texteditor öffnen, sehen Sie nach dem Header nur acht unverständliche Zeichen: <ZÛÿ.Ãf< (. steht hier für ein nicht druckbares Zeichen).

Das liegt daran, dass der Texteditor versucht, die folgenden Zeichen ebenfalls als Textzeichen im ASCII-Format zu interpretieren – in Wirklichkeit stehen hier aber die binär codierten Pixelwerte. Wenn Sie diese acht Byte-Werte dagegen als Binärzahlen darstellen, wird der Inhalt etwas klarer:

00111100 01011010 11011011 11111111 10000001 11000011 01100110 00111100

Wenn Sie diese Bitfolge zeilenweise aufschreiben (je 8 Bit pro Zeile, da das Bild 8 Pixel breit ist), lässt sich der Bildinhalt darin wieder erkennen (zur besseren Sichtbarkeit sind 0-Bits hier heller dargestellt):

Image

Graustufenbild

Image Die Dateien smiley_ascii.pgm und smiley_binary.pgm stellen jeweils das gleiche Graustufenbild dar. Hier steht nach der Bildgröße im Header jeweils 255, das heißt, das die Pixelwerte 0–255 zur Darstellung der Graustufen verwendet werden. Auch hier lässt sich die ersten Datei in einem Texteditor einfach interpretieren: Die Pixelwerte stehen hier als Dezimalzahlen im Textformat, netterweise mit Leerzeichen und Zeilenumbrüchen so ausgerichtet, dass sich der Bildinhalt erkennen lässt:

P2
8 8
255
255 255 0   0   0   0   255 255
255 0   160 160 160 160 0   255
0   160 64  160 160 64  160 0
0   160 64  160 160 64  160 0
0   160 160 160 160 160 160 0
0   160 64  64  64  64  160 0
255 0   160 64  64  160 0   255
255 255 0   0   0   0   255 255
P5
8 8
255
ÿÿ....ÿÿÿ.    .ÿ. @  @ .. @  @ ..      .. @@@@ .ÿ. @@ .ÿÿÿ....ÿÿ

Wird die zweite Datei dagegen in einem Texteditor geöffnet, werden auch hier wieder nach dem Header 64 unverständliche Zeichen dargestellt, die durch die sinnlose Interpretation der binär codierten Grauwerte als ASCII-Zeichen entstehen. In einem Hexadezimal-Editor lässt sich dieser Teil sinnvoller interpretieren. Hier sind die Binärdaten hexadezimal dargestellt, wobei jedes Byte einen Grauwert zwischen 0 und 255 codiert (die Leerzeichen, Zeilenumbrüche und Farben dienen hier als Hilfestellung):

Image

Farbbild

Image Die Dateien smiley_ascii.ppm und smiley_binary.ppm stellen jeweils das gleiche RGB-Farbbild dar. Prinzipiell lassen sich diese Dateien genauso interpretieren wie die Graustufenbilder, nur stehen hier pro Pixel drei Werte – die Rot-, Grün- und Blau-Werte – direkt nacheinander, was das visuelle Interpretieren des eigentlichen Bildinhalts etwas erschwert. Es lassen sich aber zumindest die verschiedenen RGB-Werte erkennen, die im Bild vorhanden sind, z. B. 255 255 255 (weiß), 127 94 0 (dunkles Gelb), usw.

P3
8 8
255
255 255 255 255 255 255 127 94  0  127 94  0 127 94  0 127 94  0 255 255 255 255 255 255
255 255 255 127 94  0   255 191 0  255 191 0 255 191 0 255 191 0 127 94  0   255 255 255
127 94  0   255 191 0   0   128 32 255 191 0 255 191 0 0  128 32 255 191 0   127 94  0
127 94  0   255 191 0   0   128 32 255 191 0 255 191 0 0  128 32 255 191 0   127 94  0
127 94  0   255 191 0   255 191 0  255 191 0 255 191 0 255 191 0 255 191 0   127 94  0
127 94  0   255 191 0   255 0   0  255 0   0 255 0   0 255 0   0 255 191 0   127 94  0
255 255 255 127 94  0   255 191 0  255 0   0 255 0   0 255 191 0 127 94  0   255 255 255
255 255 255 255 255 255 127 94  0  127 94  0 127 94  0 127 94  0 255 255 255 255 255 255
P6
8 8
255
ÿÿÿÿÿÿ.^..^..^..^.ÿÿÿÿÿÿÿÿÿ.^.ÿ¿.ÿ¿.ÿ¿.ÿ¿..^.ÿÿÿ.^.ÿ¿..€ ÿ¿.ÿ¿..€ ÿ¿..^..^.ÿ¿..€ ÿ¿.ÿ¿..€ ÿ¿..^..^.ÿ¿.ÿ¿.ÿ¿.ÿ¿.ÿ¿.ÿ¿..^..^.ÿ¿.ÿ..ÿ..ÿ..ÿ..ÿ¿..^.ÿÿÿ.^.ÿ¿.ÿ..ÿ..ÿ¿..^.ÿÿÿÿÿÿÿÿÿ.^..^..^..^.ÿÿÿÿÿÿ

Nach dem Header stehen 3⋅64 = 192 Zeichen, die in einem Texteditor durch willkürliche ASCII-Zeichen dargestellt werden und sich in der Hexadezimaldarstellung besser interpretieren lassen (hier wieder zur besseren Erkennbarkeit zeilenweise angeordnet und eingefärbt):

Image

Speicheraufwand

Da die Bilddaten im PNM-Format nicht komprimiert werden, sind PNM-Dateien im Vergleich zu anderen Dateiformaten sehr groß: Für ein 8-Bit-Graustufenbild der Größe W × H Pixel werden im Binärdatenformat W ⋅ H Byte + ein paar Byte für den Header benötigt, für ein RGB-Bild die dreifache Menge. Ein Farbbild der Größe 4000 × 3000 Pixel (12 Megapixel) benötigt im PPM-Binärformat (“P6”) also ca. 36 MB an Speicher.

Im Textformat steigt der Speicherbedarf noch einmal um das Drei- bis Vierfache, da jeder Helligkeitswert hier nicht mit einem Byte, sondern mit 2–4 Byte (1–3 Dezimalziffern + Trennzeichen) gespeichert wird, so dass die Datei für ein 12-Megapixel-Farbbild im PPM-Textformat (“P3”) im schlimmsten Fall ca. 144 MB groß wird! Zum Vergleich: Eine JPEG- oder PNG-Datei derselben Bildgröße ist je nach Kompressionsgrad und Bildinhalt meistens nur etwa 4–16 MB groß.

Weitere Dateiformate

Abschließend finden Sie hier eine kurze Beschreibung weiterer bekannter Dateiformate für Rastergrafiken. Viele dieser Formate verwenden im Gegensatz zum PNM Datenkompression – dieses Thema werden wir im übernächsten Kapitel Datenkompression untersuchen.

NameDateiendungBeschreibung
ImageJPEG (kurz für Joint Photographic Experts Group).jpg, .jpegIn JPEG-Dateien werden die Bilddaten nach der JPEG-Norm komprimiert. Die Kompression ist sehr effektiv, aber es geht ein Teil der Bildinformation dabei verloren (verlustbehaftete Kompression), allerdings so, dass die Unterschiede visuell in normaler Vergrößerung kaum auffallen (insbesondere bei Fotos). Der Kompressionsgrad und damit die Bildqualität kann beim Speichern gewählt werden. Da JPEG-Dateien durch die Kompression sehr klein sind, wird dieses Format speziell im Internet sehr häufig verwendet.
ImagePNG (kurz für Portable Network Graphics.pngIn PNG-Dateien werden die Bilddaten mit Methoden komprimiert, bei denen keine Bildinformation verloren geht (verlustfreie Kompression). Die Kompression funktioniert besonders gut, wenn die Bildern wenig Schattierungen, sondern eher gleichfarbige Linien und Flächen enthalten, also eher für Grafiken als für Fotos. Im Gegensatz zu JPG wird auch Transparenz unterstützt. PNG ist inzwischen im Internet relativ weit verbreitet.
ImageGIF (kurz für Graphics Interchange Format).gifGIF ist ein Dateiformat für Bilder mit Farbpalette, das wie PNG verlustfreie Kompression verwendet. GIF-Dateien können auch mehrere Einzelbilder, die von Anzeigeprogrammen wie Webbrowern als Animationen abgespielt werden und sind daher im Internet ebenfalls sehr verbreitet.
ImageBMP (kurz für Windows Bitmap).bmpBMP ist ein sehr bekanntes und einfaches Dateiformat für RGB-Bilder, in dem ein verlustfreies Kompressionsverfahren verwendet wird, das allerdings eher schwach ist. Dadurch sind die Dateien größer als etwa PNG-Dateien, weswegen BMP im Internet kaum verwendet wird.
ImageTIFF (kurz für Tagged Image File Format).tifTIFF ist ein sehr flexibles Dateiformat, das ebenfalls in der Regel verlustfreie Kompression verwendet und Bilder mit hoher Qualität und Größe speichern kann. Die Dateien sind allerdings in der Regel sehr groß und finden daher im Internet kaum Verwendung, sondern vorwiegend im Printbereich.

  1. Die Bezeichnung “Alpha” für die Transparenzinformation geht auf die Formel zur Anzeige des Bildes zurück, bei der jedes Bildpixel A mit seinem Transparenzfaktor α gewichtet mit dem entsprechenden Hintergrundpixel B verrechnet wird (“Alpha Blending”), um den angezeigten Pixelwert C zu berechnen: C = α⋅A + (1-α)⋅B. ↩︎

  2. Diese Farbdarstellung wird als indizierte Farben bezeichnet. Ein Index ist die Nummer eines Eintrags in einer Tabelle (hier der Farbpalette). ↩︎

  3. Später wurde PNM noch um ein weiteres Format namens Portable Arbitrary Map (PAM, “Magic Number” P7) erweitert, mit dem sich beliebige Bildformate mit 1–4 Kanälen und 8/16 Bit pro Kanal darstellen lassen. Dieses Format hat aber eine andere Struktur als die anderen PNM-Formate und wird nicht von allen Grafikprogrammen, die PNM-Dateien öffnen können, unterstützt. ↩︎

  4. Die Bezeichnung “Portable Pixmap” wird zum Teil auch statt “Portable Anymap” als Stellvertreter für die Formatfamilie verwendet. ↩︎

  5. Der Header kann nach der “Magic Number” außerdem Textkommentare enthalten, die immer durch das Zeichen # eingeleitet werden und bis zum nächsten Zeilenumbruch laufen. Diese Kommentare werden beim Lesen einer PNM-Datei ignoriert, z. B.:
    P2
    # Erstellt am 24.08.2021
    17 8
    100 # max. Helligkeit
    ... ↩︎

2.2.3 Grafische Codes

In vielen praktischen Anwendungen werden digitale Codes grafisch dargestellt, beispielsweise als Aufdruck auf Artikeln, Tickets oder Postern, so dass sie mit optischen Lesegeräten (z. B. Barcode-Scanner) oder Kameras eingelesen und automatisch interpretiert werden können.

Die in der Praxis am häufigsten verwendeten maschinenlesbaren grafischen Codes sind Barcodes (auch Strich-Code oder Balken-Code) und Matrix-Codes (auch 2D-Barcode oder Pixel-Code), die auch mit dem Smartphone mit Hilfe bestimmter Apps gescannt werden können.

Barcodes

Ein Barcode ist ein grafischer Code, der aus parallelen Balken und Lücken unterschiedlicher Breite besteht (in der Regel einfarbig und vertikal). Die digitalen Daten werden hier also als Binärfolgen (= Zeilenpixel) codiert, aus den sich die Darstellung der vertikalen Balken ergibt. Als Beispiel für einen Barcode, dem wir im Alltag sehr häufig begegnen, wird hier der EAN-Barcode behandelt, der für Artikelnummern verwendet wird.

EAN-Barcode

Der EAN-Barcode (EAN ist kurz für European Article Number) spezifiziert einen Barcode zur grafischen Codierung von global eindeutigen Artikelnummern (GTIN, kurz für Global Trade Item Number), der im Einzelhandel und anderen Branchen sehr verbreitet ist.1 Der Standard-EAN-Barcode hat 13 Ziffern (EAN-13), für kürzere Artikelnummern gibt es auch eine Variante mit 8 Ziffern (EAN-8). Oft ist die Artikelnummer zusätzlich als Klartext unterhalb des Barcodes abgebildet, so dass sie auch manuell erfasst werden kann, wenn das Einscannen nicht funktionieren sollte, beispielsweise weil der Barcode beschädigt ist.

Hier ein dekoratives Beispiel:2 Image

GTIN/ISBN

Eine Artikelnummer im Format GTIN-13 ist immer nach dem folgenden Muster aufgebaut (ähnlich z. B. dem Erzeugercode auf Hühnereiern): Sie besteht aus 13 Ziffern, wobei üblicherweise die ersten 2–3 Ziffern das Länderpräfix3 darstellen, die folgenden 4–5 Ziffern den Hersteller des Produkts kennzeichnen und die folgenden 5 Stellen das Produkt selbst kennzeichnen. Die letzte Ziffer stellt eine Prüfziffer dar, die aus den restlichen 12 Stellen berechnet wird und anhand derer sich überprüfen lässt, ob die GTIN-13 eventuell falsch gelesen wurde (dazu unten mehr).

Image

Eine ISBN (International Standard Book Number) bzw. ISBN-13 ist eine Sonderform der GTIN-13, die speziell zur Kennzeichnung von Büchern verwendet wird und ebenfalls mit dem EAN-Barcode dargestellt wird. Hier wird zu Beginn als Länderpräfix immer die Ziffernfolge 978 oder 979 verwendet (“Bookland”). Die Stellen 4–12 geben hier die Ländergruppe, Verlagsnummer und Titelnummer des Buches an.

Image

Struktur

Der EAN-Barcode stellt die Ziffern der Artikelnummer durch vertikale schwarz-weiße Streifenmuster dar. Dabei werden nur die Stellen 2 bis 13 durch Streifenmuster dargestellt, die Ziffer an der ersten Stelle wird nur indirekt codiert (darauf kommen wir später zurück). Zur Codierung der Ziffern in Streifenmuster werden drei verschiedene Codetabellen verwenden, die hier “Code A”, “Code B” und “Code C” genannt werden.

Image

Der Barcode ist folgendermaßen aufgebaut:

  • Der Barcode besteht aus vertikalen Streifen gleicher Breite, die entweder schwarz oder weiß sind, und unterschiedlich breite “Balken” bilden.
  • Insgesamt enthält der Barcode je 7 Streifen für jede der 12 dargestellten Ziffern, sowie je 3 abwechselnd schwarze und weiße Streifen als Randzeichen links und rechts und 5 als Trennzeichen in der Mitte.
  • Links vom Trennzeichen werden die Stellen 2 bis 7 der Artikelnummer durch Streifenmuster dargestellt, rechts vom Trennzeichen die Stellen 8 bis 13.
  • Die sechs Ziffern in der linken Hälfte werden mit den Streifenmustern aus den Tabellen “Code A” und “Code B” dargestellt.
  • Die sechs Ziffern in der rechten Hälfte werden dagegen nur mit den Streifenmustern aus Tabelle “Code C” dargestellt (Invertierung von Code A).

Image

Jede Ziffer wird also quasi durch einen 7-stelligen Binärcode codiert, so dass durch die schwarzen und weißen Streifen je zwei schwarze und zwei weiße Balken unterschiedlicher Breite pro Ziffer entstehen. Die Streifenmuster sind dabei so gewählt, dass erkannt werden kann, ob der Barcode von links oder von rechts gescannt wurde, so dass es dadurch nicht zu Verwechslungen kommen kann.

Image

Codevarianten

Welche der beiden Codevarianten A oder B für eine Ziffer aus der linken Hälfte verwendet wird, hängt von der Position der Ziffer, sowie von der ersten Ziffer der Artikelnummer ab. Die Tabelle auf der rechten Seite stellt dar, in welcher Reihenfolge die beiden Codevarianten für die 6 Ziffern in der linken Hälfte in Abhängigkeit von der ersten Ziffer der Artikelnummer gewählt werden.

Die erste Ziffer ist im Barcode also indirekt über die Ziffern in der linken Hälfte codiert: Je nachdem, welchen Wert die 1. Ziffer hat, wird für jede der Ziffern an den Stellen 2–7 ein Streifenmuster aus Codetabelle A oder B gewählt. Beim Decodieren werden die Ziffern in der linken Hälfte mit den beiden Codetabelle A und B decodiert und aus der Reihenfolge der Codevarianten die 1. Ziffer decodiert.

Der Grund für diese umständliche Codierung der ersten Ziffer liegt darin, dass der EAN-Barcode den in den USA verbreiteten 12-stelligen UPC-Barcode (Universal Product Code) erweitert, so dass Lesegeräte für EAN-Barcodes auch UPC-Barcodes lesen können. Der UPC-Barcode verwendet dabei für die Ziffern in der linken Hälfte nur die Codevariante A, entspricht also einer GTIN-13 mit führender Null.

Interaktiver EAN-Barcode

Tool: Im folgenden interaktiven EAN-Barcode können Sie austesten, wie sich der Barcode für verschiedene Eingabewerte ändert. Klicken Sie auf eine Ziffer, um sie zu ändern oder tragen Sie eine GTIN über das Eingabefeld ein (ohne Bindestriche oder Leerzeichen). Mit den Schaltflächen können Sie die verwendeten Codierungen (A/B/C) anzeigen, Hilfslinien ein-/ausblenden und die Prüfziffer der Eingabe überprüfen.

Erkunden Sie den Aufbau des Barcodes beispielsweise folgendermaßen:

  • Klicken Sie auf “Codierung anzeigen” und anschließend mehrmals auf die erste Ziffer, um zu sehen, wie sich die Codierungen in der linken Hälfte in Abhängigkeit von der ersten Ziffer ändern (Code A/B), während die Codierungen in der rechten Hälfte gleich bleiben (Code C).
  • Klicken Sie auf “Prüfziffer anzeigen” und anschließend mehrmals auf Ziffern an geraden oder ungeraden Stellen, um zu sehen, wie sich die erwartete Prüfziffer ändert.
    Im folgenden Abschnitt wird erläutert, wie die Prüfziffer zustandekommt.

Prüfziffer

Beim Einscannen des Barcodes kann es passieren, dass die GTIN-Artikelnummer falsch eingelesen wird, beispielsweise aufgrund von Verdeckungen, Deformationen oder Beschädigungen des Barcodes. Um solche Situationen zu erkennen, enthält die GTIN redundante Information: Konkret codieren nur die ersten 12 Ziffern die relevanten Informationen über das Produkt, während die letzte Ziffer (“Prüfziffer”) nach einer bestimmten Formel aus den übrigen Daten berechnet wird. So kann anhand der gelesenen Ziffern überprüft werden, ob bestimmte Fehler beim Einlesen aufgetreten sind.

Die Prüfziffer einer GTIN wird immer so gewählt, dass die Quersumme der 13 Ziffern, wobei alle Ziffern an geraden Stellen mit dreifacher Gewichtung in die Summe eingehen, ein ganzzahlig Vielfaches von 10 ergibt.

Die Prüfziffer der GTIN wird also mit dem folgenden Algorithmus berechnet:

  • Summiere die ersten 12 Ziffern der GTIN. Multipliziere dabei alle Ziffern an geraden Stellen mit 3.
  • Die Prüfziffer ist die Differenz der Summe zur nächstgrößeren Zahl, die ohne Rest durch 10 teilbar ist (oder 0 wenn die Summe selbst ohne Rest durch 10 teilbar ist).

Image

Formel: Wenn also die einzelnen Ziffern der GTIN mit z1 bis z13 bezeichnet werden, ergibt sich die Prüfziffer z13 durch die folgende Formel:4

$$z_{13} = (10 - ((z_1 + 3 \cdot z_2 + z_3 + 3 \cdot z_4 + … + 3 \cdot z_{12}) \ mod \ 10)) \ mod \ 10$$

Beispiel: Für die ISBN 978-3-486-71751-? soll die Prüfziffer berechnet werden. Die Quersumme mit dreifacher Gewichtung der geraden Stellen ergibt:

$$9 + 8 + 4 + 6 + 1 + 5 + 3 \cdot (7 + 3 + 8 + 7 + 7 + 1) = 33 + 3 \cdot 33 = 132$$

Also lautet die Prüfziffer 8, nämlich die Differenz zu 140 bzw. das Ergebnis von:

$$z_{13} = (10 - (132 \ mod \ 10)) \ mod \ 10 = (10 - 2) \ mod \ 10 = 8$$

Die vollständige ISBN lautet also 978-3-486-71751-8.

Um eine gegebene GTIN zu überprüfen, wird einfach die gewichtete Quersumme inkl. Prüfziffer berechnet und geprüft, ob das Ergebnis ohne Rest durch 10 teilbar ist.

Beispiel: Der Barcode-Scanner liest aus einem EAN-Barcode die ISBN 978-1-234-56789-1. Die Quersumme mit dreifacher Gewichtung der geraden Stellen ergibt:

$$9 + 8 + 2 + 4 + 6 + 8 + 1 + 3 \cdot (7 + 1 + 3 + 5 + 7 + 9) = 38 + 3 \cdot 32 = 134$$

Das Ergebnis ist nicht ohne Rest durch 10 teilbar. Der EAN-Barcode ist also vom Barcode-Scanner falsch gelesen worden, er muss entweder erneut gescannt werden oder die Artikelnummer wird manuell eingegeben.

Matrix-Codes

Ein Matrix-Code stellt Binärdaten als zweidimensionale Muster dar, meist als schwarze und weiße quadratische Pixel innerhalb eines Rasters (einer Matrix). Im Gegensatz zu Barcodes können Matrix-Codes deutlich größere Datenmengen speichern und eignen sich so für verschiedenste Anwendungsfälle. Das erlaubt es auch, größere Mengen an redundanter Zusatzinformation zu speichern, so dass die Daten im Zweifelsfall auch dann noch korrekt ausgelesen werden können, wenn Teile des Codes verdeckt oder zerstört sind. Dadurch werden die Codierverfahren aber auch deutlich komplizierter als bei Barcodes.

Bekannte Beispiele sind der Actec-Code (z. B. auf Bahntickets), DataMatrix-Code (z. B. auf Briefmarken) und der QR-Code (vielseitig einsetzbar, u. a. zum Codieren von Internetadressen). Der folgende QR-Code codiert beispielsweise die URL dieses Online-Skripts – wird dieser QR-Code mit einer geeignete Smartphone-App gescannt, kann die URL im Browser geöffnet werden:

Image

Fachkonzept: Prüfsumme

Bei den oben beschriebenen GTINs wurde eine zusätzliche Ziffer angehängt, so dass die (auf eine bestimmte Weise gewichtete) Quersumme ein Vielfaches von 10 ist. So konnte beim Lesen einer GTIN aus einem EAN-Barcode ggf. erkennt werden, ob die Daten falsch decodiert wurden. Hier wird also eine spezielle Prüfsumme zur Fehlererkennung verwendet.

Eine Prüfsumme (engl. checksum) ist allgemein ein Wert, der aus den Daten und zusätzlicher redundanter Information berechnet wird. Die redundante Information (hier die Prüfziffer) wird dabei so gewählt, dass die Prüfsumme eine bestimmte Eigenschaft hat. Das Lesegerät kann dann durch Berechnen der Prüfsumme und Überprüfen der erwarteten Eigenschaft die Daten validieren, also auf Fehler überprüfen, und bei Abweisungen eventuell sogar erkannte Fehler zu korrigieren.

Prüfsummenbasierte Verfahren stellen eine der einfacheren Möglichkeiten zur Fehlererkennung für digitale Daten dar, die eingelesen oder übertragen werden und dienen oft als Grundlage für komplexere Verfahren. Ziel ist es, typische Fehler zu erkennen, z. B. dass einzelne Werte oder Bits durch widrige Umstände vertauscht, ausgelassen, verdoppelt oder invertiert werden.

Als Prüfsumme für Sequenzen von Ziffern oder Ganzzahlen wird oft die Quersumme (= Summe über alle Elemente der Sequenz) oder die gewichtete Quersumme (mit fest vorgegebenen Gewichtungsfaktoren) verwendet und als Eigenschaft überprüft, ob diese ein ganzzahlig Vielfaches einer vorgegebenen Zahl M darstellt. Um diese Eigenschaft zu erreichen, werden ein oder mehrere Werte zur Sequenz hinzugefügt (Prüfziffern/-zahlen).

Beispiel: Bei der GTIN-13 wird eine zusätzliche Ziffer zur Sequenz aus 12 Ziffern hinzugefügt, so dass die mit den Faktoren 1, 3, 1, … gewichtete Quersumme ein ganzzahlig Vielfaches von 10 ergibt.

Parität

Für binäre Daten wird als Prüfsumme üblicherweise die Parität verwendet (= Quersumme modulo 2), die anschaulich angibt, ob die Anzahl der 1-Bits in einer Bitsequenz gerade (= 0) oder ungerade (= 1) ist. Hier wird an die Daten meist ein zusätzliches Bit angehängt (Paritätsbit), so dass die Parität der Bitsequenz inkl. Paritätsbit gerade ist.

Beispiel: Die Bitsequenz 0110 0010 soll um ein Paritätsbit ergänzt werden, so dass die Gesamtparität gerade ist. Da die Bitsequenz 3 1-Bits enthält, muss das Paritätsbit auch den Wert 1 bekommen.

Image

Ist die Parität einer gelesenen Bitsequenz ungerade, muss ein Fehler vorlegen. Mit einfacher Parität bleiben viele Fehler allerdings unentdeckt, beispielsweise wenn ein 0- und ein 1-Bit vertauscht werden oder wenn gleich viele 0- und 1-Bits “gekippt” sind (hier rot dargestellt):

Image

Als Ausblick: Werden mehrere Paritätsbits für eine Bitsequenz verwendet, lässt sich die Fehlererkennung verbessern und Fehler ggf. sogar beheben.


  1. Die entsprechenden Artikelnummern wurden früher ebenfalls als EAN bezeichnet, wurden aber 2005 in GTIN umbenannt. Trotzdem hat sich die Bezeichnung “EAN” für den Barcode zur grafischen Darstellung der Artikelnummern gehalten. ↩︎

  2. Quelle: Shopblogger, https://www.shopblogger.de/blog/archives/23370-Lustige-Strichcodes-251.html ↩︎

  3. Das Länderpräfix gibt Auskunft über das Land, das die GTIN für das Produkt vergeben hat, was nicht unbedingt auch das Produktionsland sein muss. Produkte, deren GTIN von Deutschland vergeben wurde, beginnen mit den Ziffernfolgen 40–43 oder 440. ↩︎

  4. Die Operation x mod y (“Modulo”) berechnet den Teilungsrest der ganzzahligen Division von x durch y, zum Beispiel: 72 mod 7 = 2, da 72 / 7 = 10 Rest 2. ↩︎

2.2.4 Datenkompression

Einleitung

Im Alltag sind wir von immer größer werdenden Datenmengen umgeben, die wir auf Rechnern speichern oder durch das Internet schicken. Besonders kritisch sind Mediendaten wie Bilder, Tonaufnahmen und Videos – selbst Ihre Urlaubsfotosammlung würde als Rohdaten gespeichert schnell viele hundert Gigabyte einnehmen. Darüber hinaus müssen von relevanten Daten in regelmäßigen Abständen Sicherungskopien (Backups) erstellt und zum Teil über längere Zeit archiviert werden, was das Problem des Speicherbedarfs noch vergrößert. Aus diesem Grund sind Verfahren zur Datenkompression unerlässlich.

Verfahren zur Datenkompression sind dabei im Grunde nichts anderes als Codierungsverfahren – also Verfahren, die Daten von einer Repräsentation in eine andere umwandeln – mit zwei entscheidenden Eigenschaften:

  • Die codierten Daten haben einen geringeren Speicherumfang als die Originaldaten (Kompression).
  • Es gibt ein entsprechendes Decodierungsverfahren, mit dem die komprimierten Daten wieder in ihre ursprüngliche Repräsentation umgewandelt werden (Dekompression).

Dabei werden verlustfreie Kompressionsverfahren und verlustbehaftete Kompressionsverfahren unterschieden, je nachdem ob die Originaldaten bei Kompression und anschließender Dekompression exakt oder nur ungefähr wiederhergestellt werden (siehe Kompressionsverlust).

Anwendungen

Datenkompression wird in der Praxis unter anderem in “Packprogrammen” (auch Archivierungs- oder Kompressionsprogramme) verwendet, mit denen sich mehrere Dateien in einem komprimierten Format in eine Archiv-Datei verpacken lassen. Bekannte Dateiformate für solche komprimierten Archive sind etwa ZIP oder RAR (siehe auch Dateiformate). Solche Programme werden immer dann verwendet, wenn größere Datenmengen ein Problem darstellen – beispielsweise um Dateien per E-Mail zu verschicken, auf einer Webseite zum Download anzubieten oder auf einem Datenträger zu sichern.

Image

Daneben wird Datenkompression auch häufig in Dateiformaten zur Codierung von Mediendaten – also Bild-, Audio- und Videodaten – verwendet, wobei hier oft auch verlustbehaftete Kompressionsverfahren zum Einsatz kommen, da hier Informationsverlust zugunsten stärker Speicherreduktion in einem gewissen Umfang tolerierbar ist. Fast alle heute gängigen Mediendateiformate wie JPEG oder PNG für Rastergrafiken, MP3 für Audiodaten oder MPEG für Videos verwenden Datenkompression, da umkomprimierte Mediendaten schnell sehr groß werden.

Kompressionsfaktor

Um zu beschreiben, wie stark Daten komprimiert werden, wird das quantitative Verhältnis zwischen komprimierten und unkomprimierten Daten verwendet.

  • Der Kompressionsfaktor gibt an, wie groß die Daten nach der Kompression im Verhältnis zu den Originaldaten sind:
    Kompressionsfaktor = komprimierte Datenmenge / originale Datenmenge
    Dieser Faktor wird üblicherweise als Dezimalzahl, Prozentzahl oder Verhältnis in der Form “1 zu x” angegeben.
    Wird beispielsweise eine Datei von ursprünglich 320 kB Größe auf 80 kB Größe komprimiert, beträgt der Kompressionsfaktor 80 / 320 = 0.25 (bzw. 25% oder 1 zu 4).
  • Die Kompressionsrate gibt umgekehrt das Verhältnis der originalen Dateimenge zur komprimierten Dateimenge an (im Beispiel also 4 zu 1).

Kompressionsverlust

Bei bestimmten Kompressionsverfahren gehen bei der Codierung und anschließenden Decodierung Teile der Information verloren, die Originaldaten können also nach der Kompression nicht mehr exakt wiederhergestellt werden. Solche verlustbehafteten Verfahren werden überwiegend zur Kompression von Bild-, Video- oder Audiodaten verwendet – dabei wird versucht, nur “unwichtige” Information zu reduzieren, also kleine Bilddetails oder leise Töne, deren Fehlen oder Verfremdung kaum auffällt. Hier lässt sich meistens durch einen Parameter steuern, wie stark die Daten komprimiert werden sollen, wobei mit zunehmender Kompressionsstärke die Bild- oder Tonqualität immer stärker leidet.

Beispiel: Das JPEG-Dateiformat für Rastergrafiken verwendet verlustbehaftete Kompression, deren Stärke sich über einen “Qualitätsfaktor” steuern lässt. Die folgende Übersicht zeigt ein Bild, das mit verschiedenen Qualitätsfaktoren komprimiert wurde, sowie die resultierenden Dateigrößen (in der Vergrößerung der rechten beiden Bilder lassen sich blockartige Bildstörungen erkennen, z. B. auf dem Leuchtturm):

ImageImageImageImageImage
Qualitätsfaktor 90%Qualitätsfaktor 75%Qualitätsfaktor 50%Qualitätsfaktor 25%Qualitätsfaktor 10%
Dateigröße 87 kBDateigröße 50.8 kBDateigröße 29.8 kBDateigröße 17.6 kBDateigröße 9.3 kB

Bei der Kompression anderer Daten wie Textdateien oder Programmcode kommen solche Informationsverluste dagegen nicht in Frage: Hier müssen die Originaldaten aus den komprimierten Daten auf das Bit genau wieder hergestellt werden können. In dieser Lektion werden wir nur solche verlustfreien Kompressionverfahren betrachten.

Im Folgenden werden drei bekannte verlustfreie Kompressionsverfahren vorgestellt, die auch in der Praxis zur Datenkompression in verschiedenen Dateiformaten verwendet werden, und dabei unterschiedliche Grundstrategien verfolgen. Zur Veranschaulichung werden diese Verfahren hier auf kleine Beispiele zur Kompression von Textdaten und Bilddaten angewendet.

In den folgenden Beispielen werden die Ein- und Ausgabedaten der Einfachheit halber meistens als Textzeichen und Dezimalzahlen dargestellt. In der Praxis werden die Ausgabedaten der Kompressionsverfahren aber natürlich binär codiert.

Lauflängencodierung

Die Lauflängencodierung (engl. run-length encoding, kurz RLE) ist ein grundlegendes verlustfreies Kompressionsverfahren für beliebige Daten, das darauf abzielt, längere Wiederholungen von Zeichen zu komprimieren.

Die Idee ist relativ einfach: Statt Daten wie bisher Zeichen für Zeichen zu codieren, wird jeweils das Zeichen und seine “Lauflänge”, also wie oft es wiederholt wird, codiert. Die Ausgabe der Lauflängencodierung besteht dann abwechselnd aus Zeichen und Ganzzahlen.

Zur Kompression werden also die folgenden Schritte wiederholt, bis die Eingabe vollständig verarbeitet wurde:

  • Lies solange ein Zeichen x aus der Eingabe, bis ein anderes Zeichen folgt, und zähle dabei, wie oft x nacheinander vorkommt (= Lauflänge N).
  • Schreibe das Zeichen x und die Ganzzahl N in die Ausgabe.

Beispiel: Gegeben ist die Zeichenfolge:

A B B A C C C C D E A A A A A E E E E E E E E E

Nach Anwendung der Lauflängencodierung wird daraus (die Lauflängen sind hier blau markiert):

A 1 B 2 A 1 C 4 D 1 E 1 A 5 E 9

Hier reichen 4 Bit zur Codierung der Längenangaben aus. Wenn die 24 Textzeichen mit 8 Bit pro Zeichen codiert werden, erhalten wir einen Datenumfang von 24⋅8 Bit = 192 Bit, für die lauflängencodierten Daten dagegen nur 8⋅8 Bit (Zeichen) + 8⋅4 Bit (Längenangaben) = 96 Bit. Damit reduziert sich der Datenumfang auf 96 / 192 = 50%.

Anmerkung: Binärcodierung der Lauflängen

Bei der Dekompression werden also die folgenden Schritte wiederholt, wobei hier die komprimierten Daten als Eingabe abgearbeitet werden:

  • Lies ein Zeichen x und eine Ganzzahl N aus der Eingabe.
  • Schreibe das Zeichen x N-mal nacheinander in die Ausgabe.

Wie gut sich Daten durch die Lauflängencodierung komprimieren lassen, hängt davon ab, wie wahrscheinlich es ist, dass Zeichen wiederholt vorkommen. Im schlimmsten Fall kann sich der Datenumfang durch die Lauflängencodierung sogar vergrößern, wenn Zeichenwiederholungen selten sind und viele Zeichen einzeln stehen.

Beispiel: Gegeben ist die Zeichenfolge:

A B A C A D E E E E E B A C A

Nach Anwendung der Lauflängencodierung wird daraus:

A 1 B 1 A 1 C 1 A 1 D 1 E 5 B 1 A 1 C 1 A 1

Wenn 8 Bit pro Zeichen und 3 Bit pro Längenangabe verwendet werden, erhalten wir 15·8 Bit = 120 Bit für die unkomprimierten Daten, aber 11·8 Bit (Zeichen) + 11·3 Bit (Längenangaben) = 121 Bit für die lauflängencodierten Daten.

Verbesserte Lauflängencodierung

Aus diesem Grund werden bei der Lauflängencodierung Zeichen nur dann durch das Format Zeichen Lauflänge ersetzt, wenn sie geeignet oft wiederholt vorkommen, z. B. mindestens 3-mal in Folge. Anderenfalls bleiben die Zeichen unverändert.

Nun muss aber in der Ausgabe markiert werden, ob ein Zeichen mit oder ohne Längenangabe vorkommt. Anderenfalls kann bei der Decodierung nicht entschieden werden, ob es sich um ein einzelnes Zeichen oder eine Zeichenwiederholung handelt. Dazu wird üblicherweise ein bestimmtes Zeichen als “Markierungszeichen” ausgewählt. Dieses Zeichen markiert nun in der Ausgabe, dass eine Zeichenwiederholung im Format Zeichen Lauflänge folgt. Kommt in der Eingabe eine Zeichenwiederholung mit ausreichender Länge vor, wird diese also durch das Markierungszeichen, gefolgt vom Zeichen aus der Eingabe und der Länge der Wiederholung ersetzt.

Der Algorithmus zur Lauflängencodierung wird also folgendermaßen angepasst:

  • Lies solange ein Zeichen x aus der Eingabe, bis ein anderes Zeichen folgt, und zähle dabei die Lauflänge N.
  • Wenn N zu klein ist (z. B. N < 3): Schreibe x N-mal nacheinander in die Ausgabe.
  • Anderenfalls: Schreibe Markierungszeichen, x und Lauflänge N in die Ausgabe.

Beispiel: Wir codieren eine Zeichenfolge, die sowohl einzelne Zeichen als auch Zeichenwiederholungen enthält:

A B A C A D E A A A A A E E E E E E E E E B A C C A B A A

Wenn das Zeichen “E” als Markierungszeichen für Zeichenwiederholungen verwendet wird, erhalten wir die folgende Ausgabe:

A B A C A D E E 1 E A 5 E E 9 B A C C A B A A

Die ersten 6 und letzten 8 Zeichen werden unverändert ausgegeben, da sie einzeln oder nur doppelt stehen. Die längeren Wiederholungen der Zeichen “A” und “E” werden im Lauflängenformat ausgegeben. Auch das einzelne Zeichen “E” muss im Lauflängenformat mit Länge 1 ausgegeben werden.

Bei 8 Bit pro Zeichen und 4 Bit pro Längenangabe erhalten wir hier: 14 einfache Zeichen (jeweils 8 Bit) + 3 Zeichenwiederholungen (jeweils 8 + 8 + 4 Bit = 20 Bit) = 172 Bit. Im Vergleich dazu: Bei Lauflängencodierung ohne Markierungszeichen werden je 8 + 4 Bit = 12 Bit für jedes einfache Zeichen und für jede Zeichenwiederholung benötigt, also 15·12 = 180 Bit. Je mehr weitere einzelne Zeichen in den Originaldaten enthalten wären, desto schlechter würde die Lauflängencodierung ohne Markierungszeichen im Vergleich zur verbesserten Version abschneiden.

Der Algorithmus zur Decodierung von lauflängencodierten Daten wird ebenfalls entsprechend angepasst:

  • Lies ein Zeichen x aus der Eingabe.
  • Wenn x das Markierungszeichen ist:
    • Lies ein Zeichen y und eine Ganzzahl N aus der Eingabe.
    • Schreibe das Zeichen y N-mal in die Ausgabe.
  • Anderenfalls:
    • Schreibe das Zeichen x in die Ausgabe.

Beachten Sie: Wenn in der Eingabe das Zeichen vorkommt, das die Rolle des Markierungszeichens übernimmt (hier: E), wird es immer in der Form “Markierungszeichen, Markierungszeichen, Lauflänge” ausgegeben, selbst wenn es einzeln steht. Daher sollte das Markierungszeichen so gewählt werden, dass es möglichst selten in den Eingabedaten vorkommt (bzw. selten einzeln steht). Wenn das Markierungszeichen beliebig gewählt werden kann, muss es zusammen mit den komprimierten Daten gespeichert werden (z. B. als erstes Zeichen zu Beginn), damit es bei der Decodierung bekannt ist.

Lauflängencodierung für Bilder

Die Lauflängencodierung kann natürlich nicht nur auf Textdaten angewendet werden, sondern prinzipiell auf beliebige Daten – beispielsweise auch auf Rasterbilddaten, wobei hier jeweils ein Grauwert oder ein RGB-Wert als einzelnes Zeichen betrachtet wird.

In Praxis zeigt sich, dass die Lauflängencodierung für die Kompression von Textdaten weniger gut geeignet ist, da mehrfache Zeichenwiederholungen in diesen eher selten vorkommen (abgesehen vielleicht von Steuerzeichen wie Leerzeichen oder Tabulatorzeichen bei mehrfach eingerückten Texten). Bei Rasterbilddaten kann sie dagegen sehr gute Ergebnisse erzielen, wenn die Bilder größere Bereiche mit gleicher Farbe und eher wenige Schattierungen/Farbverläufe enthalten.

Beispiel: Zur Veranschaulichung soll die Lauflängencodierung hier auf ein 8-Bit-Grauwertbild der Größe 10 × 8 Pixel angewendet werden. Die Bilddaten bestehen hier aus 80 Grauwerten, die zeilenweise von oben links nach unten rechts aneinandergereiht werden. Als Markierungszeichen wird hier der Grauwert 0 (hex. 00) festgelegt, für die Codierung der Lauflängen werden 4 Bit verwendet (max. Längenangabe ist also 16).

Image

In der Abbildung steht rechts neben den Bildzeilen, welche Grauwerte (hier im Hexadezimalformat) in der Zeile nacheinander wie oft vorkommen.

Wir erhalten hier das folgende (hier gekürzte) Ergebnis: Die obere Zeile stellt die Grauwerte und ihre Lauflängen in den Bilddaten dar, darunter steht die Ausgabe der Lauflängencodierung (die Längenangaben sind hier wieder als Dezimalzahlen dargestellt, in der “eigentlichen” Ausgabe werden sie als 4-Bit-Binärzahlen codiert).

Image

Bitweise Lauflängencodierung

Wenn die Lauflängencodierung auf Bit-Ebene angewendet wird, also nur die beiden Werte 0 und 1 als Zeichen betrachtet werden, vereinfacht sich das Verfahren. Da Bitsequenzen abwechselnd aus Folgen von Nullen und Einsen bestehen, muss hier in der Ausgabe nicht explizit angegeben werden, welches Zeichen als nächstes folgt: Auf jede Null-Folge folgt eine Eins-Folge und umgekehrt. Daher reicht es bei der bitweisen Lauflängencodierung, die Längen der abwechselnd aufeinanderfolgenden Null- und Eins-Folgen auszugeben. Dabei muss vereinbart werden, ob sich die erste Längenangabe auf eine Null-Folge oder Eins-Folge bezieht (per Konvention üblicherweise auf eine Null-Folge).

Beispiel: Gegeben ist die folgende Bitsequenz (Leerzeichen hier nur zur besseren Übersicht):

1111 1110 0000 0111 1100 0001

Die Lauflängencodierung der Bitfolge ergibt nun die folgende Zahlenfolge:

0, 7, 6, 5, 5, 1

Die Längen der Null-Folgen sind hier der einfacheren Zuordnung halber grau dargestellt, die Längen der Eins-Folgen schwarz. Wenn hier jede Längenangabe als Ganzzahl mit 3 Bit gespeichert wird, erhalten wir als Resultat die folgende Bitsequenz (Leerzeichen hier nur zur besseren Übersicht):

000 111 110 101 101 001

Die Codelänge wurde damit von 24 Bit auf 18 Bit reduziert, also auf 75% der ursprünglichen Codelänge.

Dieses Verfahren lässt sich generell für Daten im Binärformat verwenden, unabhängig davon, was die Daten bedeuten – solange relativ häufig längere Bitfolgen mit demselben Wert erwartet werden. Besonders geeignet ist dieses Verfahren für die Kompression von Schwarz-Weiß-Bildern. Im folgenden Beispiel werden die Pixel eines 8×8-Schwarz-Weiß-Bildes zeilenweise von oben links nach unten rechts per Lauflängencodierung codiert:

Image

Die Lauflängencodierung der Bitfolge ergibt die folgenden 17 Zahlen:

2, 4, 3, 6, 1, 1, 2, 2, 2, 18, 6, 1, 1, 6, 3, 4, 2

Wenn auch hier jede Längenangabe als Ganzzahl mit 3 Bit gespeichert wird, ist die maximal darstellbare Längenangabe 2³ − 1 = 7. Die Zahl 18 muss also durch die Zahlenfolge 7, 0, 7, 0, 4 ersetzt werden. Damit erhalten wir 21 Zahlen und eine Codelänge von 3 ⋅ 21 = 63 Bit. Ingesamt haben wir in diesem Beispiel also nur 1 Bit gespart.

Klarerweise wird die Kompressionsrate aber umso besser, je mehr aufeinanderfolgende schwarze bzw. weiße Pixel in den Eingabedaten vorkommen – beispielsweise bei einem Schwarz-Weiß-Scan einer Textseite, in dem sich große zusammenhängende weiße Bereiche mit kleineren schwarzen Bereichen abwechseln. Lauflängencodierung kommt daher unter anderem bei der Bildübertragung per Fax zum Einsatz.

Häufigkeitsbasierte Kompression

Bei der Lauflängencodierung werden direkt aufeinanderfolgende Zeichenwiederholungen ausgenutzt, um Daten zu komprimieren. Eine alternative Strategie besteht darin, Daten auf Grundlage der Häufigkeit der vorkommenden Zeichen zu komprimieren.1

Die Grundidee besteht darin, einzelne Zeichen nicht durch binäre Codewörter derselben Länge (z. B. 8 Bit pro Zeichen) zu codieren, sondern durch Codewörter unterschiedlicher Länge. Häufigere Zeichen werden dabei durch Codewörter kürzerer Länge repräsentiert und seltenere Zeichen durch längere Codewörter.

Der Morse-Code verwendet beispielsweise diese Strategie (die Buchstaben E und T werden mit nur einem Symbol codiert, J, Q, X und Z dagegen mit vier Symbolen). Beim Morsecode werden Pausen als Trennzeichen zwischen den Codewörtern verwendet, damit klar ist, wo ein Codewort endet und das nächste beginnt. Anderenfalls könnte eine Nachricht wie •••---••• nicht eindeutig als “SOS” decodiert werden, sondern könnte z. B. auch “EUGI” bedeuten.

Image

Wenn keine Trennzeichen zwischen Codewörten verwendet werden, muss sichergestellt sein, dass kein Codewort mit einem anderen Codewort beginnt. Eine solche Codierung heißt präfixfrei. Wenn beispielsweise ein Zeichen durch das binäre Codewort 01 repräsentiert wird, darf kein anderes Zeichen durch ein Codewort dargestellt werden, das mit der Bitfolge 01 beginnt, wie etwa 010 oder 0110. Anderenfalls ist die Decodierung nicht mehr eindeutig, wie das folgende Beispiel zeigt.

Beispiel: Angenommen, die zu codierenden Daten bestehen aus den Buchstaben A – F und wir ordnen diesen Zeichen die folgenden Codewörter zu:

A = 00, B = 01, C = 100, D = 11, E = 110, F = 111

Diese Codierung ist nicht präfixfrei, da die Codewörter für “E” und “F” jeweils mit dem Codewort für “D” beginnen.

Nun soll die Bitfolge 11100 decodiert werden. Hier ist nicht klar, ob das erste Zeichen ein “D” (Bitfolge 11) oder ein “F” (Bitfolge 111) ist, da es den codierten Daten nicht anzusehen ist, wie lang die einzelnen Codewörter sind. Die Bitfolge ließe sich gleichermaßen zu “DC” oder “FA” decodieren.

Die folgende Zuordnung von Codewörtern zu den Buchstaben ist dagegen präfixfrei:

A = 00, B = 01, C = 100, D = 101, E = 110, F = 111

Hier lässt sich die Bitfolge 11100 nur noch zu “FA” decodieren, die Decodierung ist also eindeutig.

Huffman-Codierung

Die Huffman-Codierung2 beschreibt ein Verfahren, um optimale binäre Codewörter basierend auf den Zeichenhäufigkeiten zu berechnen – also Codewörter, die zu einer möglichst kurzen Gesamtcodelänge führen. Das Verfahren garantiert dabei, dass die Codewörter präfixfrei, also eindeutig decodierbar sind.

Zur Codierung wird eine spezielle Datenstruktur, der sogenannte Huffman-Baum, aufgebaut, aus dem die Codewörter für die einzelnen Zeichen abgelesen werden können.

Exkurs: Was ist ein Baum?

Codierung

Zuerst wird die Häufigkeit jedes Zeichens berechnet – es wird also gezählt, wie oft jedes Zeichen in den zu codierenden Daten vorkommt.

Anschließend wird der Huffman-Baum nach dem folgenden Algorithmus erstellt:

  • Erstelle für jedes Zeichen einen einzelnen Knoten (also einen Baum mit nur einem Element), dessen Wert die Häufigkeit des Zeichens ist.
  • Wiederhole, bis nur noch ein Baum übrig ist:
    • Wähle die beiden Bäume, deren Wurzelknoten die geringsten Werte haben.
    • Erstelle einen neuen Wurzelknoten, dessen Nachfolger die Wurzelknoten der beiden Bäume sind.
    • Der Wert des neuen Wurzelknotens ist die Summe der Werte seiner beiden Nachfolger.

Beispiel: Die Textnachricht “OSTSEESPROTTENPOTT” soll mittels Huffman-Codierung komprimiert werden.

Image

Als Erstes zählen wir, wie oft jedes Zeichen in der Nachricht vorkommt. Anschließend erstellen wir für jedes der sieben Zeichen einen Baum mit nur einem Knoten, dessen Wert die Häufigkeit des Zeichens ist.

Image

Die Knoten für die Zeichen “N” und “R” haben die geringste Werte (jeweils 1), also werden sie mit einem neuen Wurzelknoten verbunden, dessen Wert 1 + 1 = 2 ist.

Image

Nun haben der neue Baum und der Baum für das Zeichen “P” die geringsten Werte an der Wurzel stehen (jeweils 2). Ihre Wurzelknoten werden mit einem neuen Wurzelknoten dessen Wert 2 + 2 = 4 ist, zu einem neuen Baum verbunden.

Image

Nun gibt es drei Knoten mit dem Wert 3. Es ist egal, welche der beiden wir in diesem Schritt verbinden. Hier werden die Knoten der Zeichen “O” und “S” gewählt und mit einem neuen Wurzelknoten mit dem Wert 3 + 3 = 6 verbunden.

Image

Als Nächstes werden die Wurzelknoten der beiden Bäume mit den momentan geringsten Werten 3 und 4 verbunden. Der neue Baum hat den Wert 3 + 4 = 7 an der Wurzel stehen.

Image

Nun werden die Wurzelknoten der beiden Bäume mit Werten 5 und 6 verbunden, der neue Wurzelknoten erhält den Wert 5 + 6 = 11.

Image

Als Letztes werden die Wurzelknoten der letzten beiden Bäume mit Werten 7 und 11 verbunden, der neue Wurzelknoten erhält den Wert 7 + 11 = 18. Damit ist der Huffman-Baum fertiggestellt.

Auf diese Weise werden Schritt für Schritt jeweils zwei Bäume zusammengefügt, bis nur noch ein einziger Baum (der Huffman-Baum) übrig ist, dessen Wurzel als Wert die Gesamtanzahl aller Zeichen enthält. Jedes Blatt des Huffman-Baums repräsentiert ein Zeichen. Die Reihenfolge, in der die Bäume zusammengefügt werden, sorgt dafür, dass die Blätter seltenerer Zeichen weiter von den Wurzel entfernt sind, während die Blätter häufigerer Zeichen näher an der Wurzel liegen (gemessen in Kanten).

Aus dem Huffman-Baum lassen sich nun die Codewörter für jedes Zeichen ablesen. Dazu wird zuerst jede linke Kante von einem Knoten zu seinem Nachfolger mit 0 beschriftet und jede rechte Kante mit 1.

Image

Um das Codewort für ein Zeichen zu ermitteln, wird dann folgendermaßen vorgegangen:

  • Starte bei der Wurzel und gehe abwechselnd nach links oder rechts, bis das Blatt für das Zeichen erreicht ist.
  • Die Bit-Werte auf den Kanten des Pfades von der Wurzel zum Blatt ergeben das Codewort für das Zeichen.

Beispiel: Aus dem oben konstruierten Huffman-Baum erhalten wir das Codewort 01 für den Buchstaben “E”, da wir von der Wurzel aus erst nach links und anschließend nach rechts gehen müssen, um zum Blatt für das Zeichen “E” zu gelangen. Die Codewörter der einzelnen Buchstaben sind in der rechten Spalte der Tabelle eingetragen.

Damit erhalten wir die folgende Bitsequenz, wenn wir die gesamte Textnachricht Zeichen für Zeichen durch die Huffman-Codewörter ersetzen:

100101111010101101001000110011110100000011001111

Decodierung

Um eine Bitfolge zu decodieren, die mittels Huffman-Codierung entstanden ist, wird der Huffman-Baum benötigt, mit dem die Daten codiert wurden. Dabei dient die Bitfolge quasi als Anleitung, wie wir während der Decodierung durch den Huffman-Baum laufen. Immer wenn ein Blatt erreicht wird, wird das entsprechende Zeichen ausgegeben und bei der Wurzel neu gestartet.

Die Decodierung läuft also nach dem folgenden Algorithmus ab:

  • Setze einen Zeiger auf den Wurzelknoten des Huffman-Baums.
  • Wiederhole, bis die gesamte Bitfolge gelesen wurde:
    • Lies das nächste Bit.
    • Wenn das Bit 0 ist, setze den Zeiger auf den linken Nachfolger des aktuellen Knotens, bei 1 auf den rechten.
    • Wenn ein Blatt erreicht wurde, gib das Zeichens des Blatts aus und setze den Zeiger auf den Wurzelknoten zurück.

Image

Beispiel: Mit dem oben konstruierten Huffman-Baum werden die ersten drei Bit 100 des Huffman-Codes zum Zeichen “O” decodiert: Beim Lesen der Bits wird von der Wurzel aus einmal nach rechts und anschließend zweimal nach links gegangen. Dabei wird das Blatt des Zeichens “O” erreicht, dieses Zeichen ausgegeben und der Zeiger auf den Wurzelknoten zurückgesetzt.

Speicheraufwand

In dem oben gezeigten Beispiel ergibt sich eine Codelänge von 48 Bit. Zum Vergleich: Wenn die Textdaten unkomprimiert im üblichen (erweiterten) ASCII-Format gespeichert werden, werden 8 Bit pro Zeichen benötigt – da die Nachricht 18 Zeichen enthält also ingesamt 8·18 = 144 Bit. Damit beträgt der Kompressionsfaktor der Huffman-Codierung hier 48 / 144 = 33.3%, die Kompressionsrate liegt bei 3 : 1.

Wenn wir stattdessen jedes Zeichen mit einem Codewort der gleichen Länge codieren würden, würden wir 3 Bit pro Zeichen benötigen, um die 7 verschiedenen Buchstaben im Text zu unterscheiden – insgesamt also 3·18 = 54 Bit. Der Kompressionsfaktor für dieses Verfahren beträgt hier 54 / 144 = 37.5%, schneidet also schlechter ab als die Huffman-Codierung.

Bei dieser Berechnung wird allerdings ignoriert, dass zusätzlicher Speicherbedarf für den Huffman-Baum nötig ist: Da der Huffman-Baum zur Decodierung bekannt sein muss, muss er zusammen mit den komprimierten Daten gespeichert werden. Wenn die Eingabedaten groß genug sind, wiegt die Speicherersparnis, die sich durch das Komprimieren der Daten ergibt, den zusätzlichen Speicherbedarf für den Huffman-Baum aber wieder auf.

Anmerkung: Speicheraufwand für den Huffman-Baum

Wörterbuchbasierte Kompression

Bestimmte Zeichenfolgen kommen in der Regel mit unterschiedlicher Häufigkeit in Daten vor. So enthalten deutschsprachige Texte beispielsweise sehr häufig Zeichenfolgen wie “ein”, “der”, “die” oder “sch”. Solche häufig vorkommenden Zeichenfolgen können ebenfalls ausgenutzt werden, um Daten zu komprimieren. Dazu werden Tabellen verwendet, die Zeichenfolgen auf binäre Codewörter abbilden und als Wörterbücher bezeichnet werden.

Bei wörterbuchbasierten Kompressionsverfahren besteht also die Grundidee darin, dass nicht nur einzelne Zeichen codiert werden, sondern auch häufiger vorkommende Zeichenfolgen durch einzelne Codewörter dargestellt werden. Dazu muss zunächst ermittelt werden, welche Zeichenfolgen besonders oft in den zu codierenden Daten vorkommen. In einem Wörterbuch wird für jedes Zeichen und jede Zeichenfolge ein Codewort festgehalten, durch den diese in der Ausgabe dargestellt werden. Das Codewort ist dabei in Regel einfach die Nummer des Eintrags im Wörterbuch (binär codiert).

Image

Um die Daten zu decodieren, muss das Wörterbuch bekannt sein. Wenn es möglich ist, das Wörterbuch während der Decodierung mit derselben Strategie zu rekonstruieren, mit der es während der Codierung aufgebaut wurde, kann aber darauf verzichtet werden, das Wörterbuch mit den komprimierten Daten zusammen zu speichern.

LZW-Algorithmus

Ein sehr bekanntes Kompressionsverfahren, das auf dieser Idee basiert, ist der Lempel-Ziv-Welch-Algorithmus (kurz LZW-Algorithmus).3

Beim LZW-Algorithmus wird während der Codierung der Eingabedaten ein Wörterbuch aufgebaut, in das Schritt für Schritt Zeichenfolgen steigender Länge, die bisher aus den Eingabedaten gelesen wurden, hinzugefügt werden. Das Codewort für eine Zeichenfolge im Wörterbuch ist dabei die Nummer des Eintrags, also die Position des Eintrags im Wörterbuch (binär codiert als Bitfolge) – der LZW-Algorithmus liefert als Ausgabe also eine Sequenz von Nummern von Wörterbucheinträgen. Der Einfachheit halber werden diese Nummern im Folgenden immer als Dezimalzahlen dargestellt.

Anmerkung: Binärcodierung der Ausgabe

Codierung

Zu Beginn enthält das Wörterbuch je einen Eintrag für alle Zeichen, die in der zu codierenden Nachricht vorkommen. Die Einträge des Wörterbuchs werden beginnend mit 0 durchnummeriert.

Der Algorithmus zur Codierung ist folgendermaßen beschrieben:

  • Starte am Anfang der Eingabe.
  • Wiederhole, bis die Eingabe vollständig verarbeitet wurde:
    • Lies zeichenweise eine Zeichenfolge s aus der Eingabe, bis die Zeichenfolge s + das folgende Zeichen nicht mehr im Wörterbuch vorkommt.
    • Schreibe die Eintragsnummer von s in die Ausgabe.
    • Füge als neuen Wörterbucheintrag s + das folgende Zeichen hinzu.
    • Verarbeite die Eingabe nun ab dem folgenden Zeichen weiter.

Beispiel: Der schrittweise Ablauf des Algorithmus soll anhand der Codierung der Textnachricht “ANANASBANANA” veranschaulicht und erläutert werden:

Image

Das initiale Wörterbuch enthält vier Einträge für die Buchstaben “A”, “B”, “N” und “S” mit den Nummern 0 bis 3.

Image

Das Zeichen “A” wird gelesen (längere Zeichenfolgen enthält das Wörterbuch zu diesem Zeitpunkt noch nicht).
Die Eintragsnummer des Zeichens “A” wird in die Ausgabe geschrieben, also 0.
Als neuer Eintrag mit der Nummer 4 wird die Zeichenfolge “AN” zum Wörterbuch hinzugefügt.

Image

Das Zeichen “N” wird gelesen (das Wörterbuch enthält die nächstlängere Zeichenfolge “NA” bisher nicht).
Die Eintragsnummer des Zeichens “N” wird in die Ausgabe geschrieben, also 2.
Als neuer Eintrag Nummer 5 wird “NA” hinzugefügt.

Image

Die Zeichenfolge “AN” wird gelesen, die im 1. Schritt zum Wörterbuch hinzugefügt wurde.
Die Eintragsnummer 4 der Zeichenfolge “AN” wird ausgegeben.
Als neuer Eintrag Nummer 6 wird “ANA” hinzugefügt.

Image

Das Zeichen “A” wird gelesen (das Wörterbuch enthält die nächstlängere Zeichenfolge “AS” bisher nicht).
Die Eintragsnummer 0 des Zeichens “A” wird in die Ausgabe geschrieben.
Als neuer Eintrag Nummer 7 wird “AS” hinzugefügt.

Image

Nach diesem Schema wird fortgefahren, solange noch Zeichen aus der Eingabe abzuarbeiten sind.

Image

Nach diesem Schema wird fortgefahren, solange noch Zeichen aus der Eingabe abzuarbeiten sind.

Image

Nach diesem Schema wird fortgefahren, solange noch Zeichen aus der Eingabe abzuarbeiten sind.

Image

Im letzten Schritt wird kein neuer Eintrag zum Wörterbuch hinzugefügt.

Als Ausgabe erhalten wir hier die folgende Sequenz von Eintragsnummern:

0, 2, 4, 0, 3, 1, 6, 5

Verwenden Sie den folgenden Simulator, um diesen Algorithmus selbst Schritt für Schritt bis zum Ende nachzuvollziehen.

Interaktiver LZW-Codierer

Tool: In dieser interaktiven Anzeige können Sie die LZW-Codierung einer Textnachricht Schritt für Schritt simulieren und dabei den Aufbau des Wörterbuchs nachvollziehen. Die letzte Spalte “Codewort” stellt hier die Nummer des Wörterbucheintrags als Binärzahl dar, die für die Ausgabe im Binärformat verwendet wird.
Mit der Schaltfläche “Nächster Schritt” wird die nächste Zeichenfolge aus der Eingabe gelesen und codiert. Wählen Sie “Codierung zurücksetzen”, um eine neue Textnachricht einzugeben.

ANANASBANANA

binäre Ausgabe?
NummerZeichenfolgeCodewort

Decodierung

Für die Decodierung muss nicht das vollständige Wörterbuch bekannt sein, es wird nur der Initialzustand benötigt, also die Einträge für die einzelnen Zeichen, die in der codierten Nachricht vorkommen. Alle weiteren Einträge werden während der Decodierung Schritt für Schritt rekonstruiert.

Die Decodierung startet also mit demselben initialen Wörterbuch wie die Codierung. Als Eingabe dient hier die Sequenz der Eintragsnummern, die bei der Codierung erzeugt wurde.

  • Wiederhole, bis die Eingabe vollständig verarbeitet wurde:
    • Lies die nächste Nummer N aus der Eingabe.
    • Schreibe die Zeichenfolge, die im N-ten Wörterbucheintrag steht, in die Ausgabe.
    • Füge als neuen Wörterbucheintrag die folgende Zeichenfolge hinzu: die im letzten Schritt ausgegebene Zeichenfolge + das erste Zeichen der im aktuellen Schritt ausgegebenen Zeichenfolge (im 1. Schritt der Decodierung wird diese Anweisung übersprungen)

Die letzte Anweisung wirkt auf den ersten Blick etwas seltsam: Hier wird das Hinzufügen der neuen Wörterbucheinträge während der Codierung nachvollzogen, allerdings mit einem Schritt Verzögerung.4

Hierdurch ergibt sich ein Fallstrick: Bei der Codierung kann es passieren, dass eine Zeichenfolge zum Wörterbuch hinzugefügt wird und gleich im nächsten Schritt für die Ausgabe verwendet wird. Überprüfen Sie als Beispiel die Codierung des Textes “ABABABA” im interaktiven LZW-Codierer:

  • Im 3. Schritt wird die Nummer des Eintrags “AB” ausgegeben und als neuer Eintrag Nummer 4 die Zeichenfolge “ABA” hinzugefügt.
  • Im 4. Schritt wird die Nummer des Eintrags “ABA” ausgegeben, der im vorigen Schritt zum Wörterbuch hinzugefügt wurde.

Bei der Decodierung tritt nun 4. Schritt also das Problem auf, dass die Nummer 4 decodiert werden soll, der 4. Eintrag aber erst in diesem Decodierungsschritt zum Wörterbuch hinzugefügt wird. Wie lautet also die Zeichenfolge mit der Nummer 4?

Image

Image

Dieses Problem lässt sich durch die folgenden Überlegungen lösen:

  • Laut Decodierungsalgorithmus wird in diesem Schritt als neuer Wörterbucheintrag (Nummer 4) die im letzten Schritt ausgegebene Zeichenfolge + das erste Zeichen der im aktuellen Schritt auszugebenden Zeichenfolge eingetragen.
  • Im letzten Schritt wurde “AB” ausgegeben, also lautet der neue Wörterbucheintrag “AB?”, wobei das Zeichen ? noch unbekannt ist.
  • In diesem Schritt wird also “AB?” ausgegeben, die im aktuellen Schritt ausgegebene Zeichenfolge beginnt also mit “A”.
  • Also ist “A” das unbekannte Zeichen ?, die Zeichenfolge, die in diesem Schritt ausgegeben und zum Wörterbuch hinzugefügt wird, lautet demnach “ABA”.

Generell wird in dieser Situation also immer die im letzten Schritt ausgegebene Zeichenfolge + deren erstes Zeichen zum Wörterbuch hinzugefügt und ausgegeben.

Binäre Codewörter

Im einfachsten Fall werden die Nummern, die der LZW-Algorithmus als Ausgabe produziert, als binär codierte Ganzzahlen mit fester Bitlänge (z. B. 12 Bit pro Nummer) repräsentiert. Das Wörterbuch kann dann aber nur eine begrenzte Anzahl von Einträgen aufnehmen (bei 12 Bit/Nummer insgesamt 212= 4096 Einträge). Wenn das Wörterbuch während der Codierung/Decodierung seine maximale Größe erreicht, können keine weiteren Einträge mehr hinzugefügt werden und im weiteren Verlauf nur die bereits vorhandenen Einträge verwendet werden.

Dieses Verfahren ist außerdem unnötig speicheraufwendig: Angenommen, das Wörterbuch enthält zu Beginn 10 Einträge. Dann reichen 4 Bit, um die Nummern der Einträge zu Beginn der Codierung darzustellen. Erst wenn das Wörterbuch nach 6 Schritte 16 Einträge umfasst, sind ab dann 5 Bit nötig, um die Nummern in der Ausgabe darzustellen. Verdoppelt sich die Anzahl der Einträge nach weiteren 16 Schritten auf 32, ist ein weiteres Bit nötig.

Diese Strategie wird beim LZW-Algorithmus verwendet: Es werden immer nur soviele Bit zur Codierung der Nummern in der Ausgabe verwendet, wie nötig sind, um die Nummern aller momentan im Wörterbuch vorhandenen Einträge darzustellen. Sobald die Wörterbuchlänge die nächste Zweierpotenz erreicht, wird 1 Bit zur Binärdarstellung der Nummern in der Ausgabe dazugenommen.

Bei der Decodierung wird die gleiche Strategie verwendet, um zu bestimmen, wie viele Bit jeweils für das nächste Codewort aus der Eingabe gelesen werden. Kurz zusammengefasst gilt in jedem Schritt: Wenn das Wörterbuch N Einträge enthält, werden ⌈log2(N)⌉ Bit für jedes Codewort gelesen/ausgegeben.

Im interaktiven LZW-Codierer können Sie über die Option ☑ binäre Ausgabe? nachvollziehen, wie die Codewörter im Binärformat mit wachsender Länge ausgegeben werden (die Leerzeichen dienen hier nur dazu, dass Sie die einzelnen Codewörter in der Ausgabe einfacher auseinanderhalten können).

Dateiformate

Die Kompressionsverfahren, die in dieser Lektion exemplarisch vorgestellt werden, finden (zum Teil in modifizierter Form) in vielen gängigen Dateiformaten Verwendung. Da die Verfahren unterschiedliche Stärken haben, werden sie meistens nicht einzeln, sondern in Kombination angewendet. Sehr verbreitet ist beispielsweise der “Deflate”-Algorithmus: Dabei wird neben der Huffman-Codierung eine spezielle Variante des Lempel-Ziv-Algorithmus verwendet, der dem LZW-Algorithmus ähnlich ist.

Abschließend finden Sie hier einen kurzen Überblick über bekannte Dateiformate für Rastergrafiken und Archive und die darin verwendeten Kompressionsverfahren.

Dateiformate für Rastergrafiken
ImageDas JPEG-Format verwendet verlustbehaftete Kompression, wobei sich über einen Parameter das Verhältnis zwischen Kompressionsfaktor und Bildqualität steuern lässt. Je höher der Kompressionsfaktor, desto ungenauer lässt sich das Originalbild rekonstruieren. Der JPEG-Algorithmus verwendet dabei Lauflängencodierung und Huffman-Codierung als Zwischenschritte.
ImageDas PNG-Format verwendet unter anderem eine Kombination von Lempel-Ziv-Algorithmus und Huffman-Codierung (“Deflate”-Algorithmus). Die Codierung ist also verlustfrei.
ImageDas GIF-Format verwendet dagegen nur den LZW-Algorithmus und erreicht dadurch eine geringere Kompression als PNG.
ImageWindows Bitmap (BMP) verwendet nur Lauflängencodierung und erreicht dadurch nur eine schwache Kompression.
ImageDas TIFF-Format unterstützt verschiedene Kompressionsverfahren, unter anderem LZW und Lauflängencodierung, aber auch verlustbehaftete Kompression.
Dateiformate für Archive
ImageDas ZIP-Dateiformat (von engl. zipper = Reißverschluss) ist ein Format für verlustfrei komprimierte Dateien, das zur Archivierung oder zum Versand von Dateien verwendet wird. Die Dateien werden dabei einzeln komprimiert und in einer Archiv-Datei zusammengefasst. ZIP unterstützt verschiedene verlustfreie Kompressionsverfahren. Standardmäßig wird der “Deflate”-Algorithmus verwendet (Kombination von Lempel-Ziv-Algorithmus und Huffman-Codierung).
ImageDas RAR-Dateiformat5 ist ein Archiv-Dateiformat, das stärkere Kompression als ZIP erreicht. Das Dateiformat ist allerdings nicht frei und der Kompressionsalgorithmus nicht offen zugänglich, weswegen RAR inzwischen weniger stark verbreitet ist.

  1. Kompressionsverfahren, die den einzelnen Zeichen der zu codierenden Daten basierend auf ihrer Häufigkeit unterschiedlich lange Bitfolgen zuordnen, werden unter der Bezeichnung Entropiecodierung zusammengefasst. Die Entropie ist in der Informationstheorie anschaulich ausgedrückt ein Maß dafür, wie gleichmäßig die Zeichen in den zugrundeliegenden Daten verteilt sind. ↩︎

  2. Die Huffman-Codierung ist nach ihrem Entwickler David A. Huffman benannt, der das Verfahren 1952 publizierte. ↩︎

  3. Der LZW-Algorithmus ist nach seinen ursprünglichen Entwicklern Abraham Lempel und Jacob Ziv, sowie nach Terry A. Welch benannt. Lempel und Ziv veröffentlichten 1977 die erste Version des Verfahrens unter dem Namen LZ77, sowie 1978 dessen Nachfolger LZ78, der nach Detailverbesserungen durch Welch 1984 unter dem Namen LZW publiziert wurde. ↩︎

  4. Zur Erklärung: Bei der Codierung wird in jedem Schritt die aktuell verarbeitete Zeichenfolge + das erste Zeichen der im nächsten Schritt verarbeiteten Zeichenfolge zum Wörterbuch hinzugefügt. Bei der Decodierung wird analog dazu in jedem Schritt die im vorigen Schritt ausgegebene Zeichenfolge + das erste Zeichen der aktuell ausgegebenen Zeichenfolge zum Wörterbuch hinzugefügt. ↩︎

  5. Das RAR-Dateiformat (“Roshal ARchive”) ist nach seinem Entwickler Jewgeni Lasarewitsch Roschal benannt. ↩︎

2.3 Informationsdarstellung im Internet

World Wide Web

Wenn Sie mit ihrem Rechner oder Smartphone durch das Internet surfen um beispielsweise Informationen zu recherchieren, Bilder und Videos abzurufen oder online einzukaufen, nutzen Sie in den Regel einen konkreten Dienst des Internets, nämlich das World Wide Web.

Das World Wide Web (kurz “WWW” genannt) ist ein Informationssystem, das aus untereinander verknüpften Dokumenten – den Webseiten – besteht, die über das Internet bereitgestellt werden und weltweit auf vielen Rechnern verteilt gespeichert sind. Diese Webseiten stellen Informationen in Form von strukturierten Texten, Bildern, Videos und anderen Multimedia-Elementen dar. Sie enthalten Verweise (Hyperlinks) zu weiteren Webseiten und Dokumenten, über die diese abgerufen werden können. Auf diese Weise kann zwischen Webseiten hin- und hergewechselt werden kann. Ein solches System von vernetzten Text- und Mediendokumenten wird als Hypertext- oder Hypermedia-System bezeichnet.

Eine Website ist dabei ein Internetauftritt, der in der Regel aus mehreren, untereinander verknüpften Webseiten (engl. web pages) besteht. Websites werden umgangssprachlich auch “pars pro toto” nach ihrer Einstiegsseite als Homepage bezeichnet.

Webseiten

Um Webseiten anzuzeigen wird ein Webbrowser benötigt (z. B. Firefox, Chrome oder Edge) – also ein bestimmtes Anwendungsprogramm auf Ihrem Rechner, das Webseiten-Dokumente aus dem Internet anfordert und darstellt. Dieses Programm fungiert also als Web-Client.1

Um eine Webseite aufzurufen, geben Sie in Ihrem Browser deren Webadresse (URL, kurz für Uniform Resource Locator) ein.2 Ihr Browser schickt anschließend über das Internet eine Anfrage an den Webserver – also das Programm, das die Daten der Website bereitstellt und über die gegebene URL erreichbar ist. Der Webserver antwortet, indem er das angeforderte Webseiten-Dokument an Ihren Browser zurückschickt. Das Webseiten-Dokument wird dabei als Textdatei in einem bestimmten Format dargestellt, nämlich als HTML-Datei. Ihr Browser nimmt dieses Dokument entgegen, interpretiert es und präsentiert es grafisch aufbereitet so, dass Sie mit der Webseite interagieren können.

Zur Darstellung der Webseite liefert der Webserver ggf. noch weitere benötigte Dateien zurück, zum Beispiel Bilder, die in der Seite dargestellt werden. Informationen über die grafische Gestaltung der Webseiten werden in der Regel ebenfalls in separaten Dateien neben den HTML-Dateien bereitgestellt, die ein anderes Format verwenden, nämlich in Form von CSS-Dateien.

Image

Beschreibung von Webseiten

In den folgenden Lektionen werden wir einen näheren Blick darauf werfen, wie Informationen im Internet dargestellt werden – also HTML zur Beschreibung der Struktur von Webseiten und CSS zur Beschreibung der grafischen Gestaltung – und wie sich Webseiten mit HTML/CSS selbst erstellen und gestalten lassen.

Wie die Kommunikation zwischen Webserver und -client über das Internet konkret umgesetzt ist und die Webdokumente auf Ihren Rechner kommen, werden wir an dieser Stelle nicht weiter beleuchten – dieses Thema wird ausführlich im später folgenden Kapitel “Netzwerke und Internet” behandelt.


  1. Als Client wird ein Computerprogramm bezeichnet, das auf dem Endgerät eines Netzwerks ausgeführt wird und mit einem Server kommuniziert, also einem Computerprogramm, das meist auf einem anderen Gerät im Netzwerk ausgeführt wird und Daten bereitstellt, die von den Clients abgerufen werden. ↩︎

  2. Die URL können Sie sich hier noch der Einfachheit halber ähnlich wie eine Dateiangabe mit Dateipfad vorstellen, nur dass in diesem Fall eine Datei auf einem anderen Rechner im Internet adressiert wird. Die URL https://www.winf-sh.de/kapitel2/intro.html bezieht sich beispielsweise auf eine Datei intro.html, die in einem Unterverzeichnis kapitel2 auf dem Rechner liegt, der im Internet über die Adresse www.winf-sh.de erreichbar ist. ↩︎

2.3.1 Strukturierung mit HTML

Wie ist eine Webseite aufgebaut? Welche Arten von Elementen kann sie enthalten? Und wie lässt sich die Struktur von Webseiten beschreiben, so dass ein Browser sie interpretieren und uns präsentieren kann? Dazu wird eine speziell dafür entwickelte Sprache verwendet, nämlich HTML.

In dieser Lektion werden wir uns mit den grundlegenden Konzepten und Bestandteilen von HTML beschäftigen, die nötig sind, um einfache Webseiten mit einem Texteditor selbst zu erstellen und ihren Aufbau nachzuvollziehen. Ziel ist es nicht, einen vollständigen Überblick über HTML zu bekommen, sondern einen Einstiegspunkt zu finden und die grundlegende Idee zur Beschreibung strukturierter Hypertext-Dokumente anhand von HTML nachzuvollziehen. Zur Vertiefung eignen sich HTML-Referenzen und Tutorials wie W3Schools oder SELFHTML.

HTML

HTML steht kurz für Hypertext Markup Language (also “Auszeichnungssprache für Hypertext”) und stellt eine formale Sprache dar, mit der sich die Struktur von Webseiten in textueller Form beschreiben lässt. Dabei ist in erster Linie die semantische Struktur (Gliederung nach Bedeutung) gemeint, nicht die visuelle Struktur (grafische Präsentation) der Webseiten.

Im Rahmen der Weiterbildung wird ausschließlich die aktuelle HTML-Version HTML5 betrachtet, die langfristig ältere HTML-Standards als Kernsprache des World Wide Web ablöst. “HTML” wird im Folgenden also gleichbedeutend mit “HTML5” verwendet, wenn nicht anderes angegeben.

Webseiten werden in ihrer einfachsten Form wie Textdokumente gegliedert und formatiert, also mit Hilfe von Strukturelementen wie Überschriften, Absätzen, Listen und Tabellen. Weitere wichtige Elemente von Webseiten sind Hyperlinks (kurz “Links”), also speziell markierte Textteile, die Verweise zu anderen Webseiten darstellen, sowie eingebettete Bilder oder andere Multimedia-Elemente.

Eine HTML-Datei ist eine reine Textdatei, in der die Inhalte und die Struktur einer Webseite mit HTML beschrieben werden. Dazu werden Textabschnitte auf eine bestimmte Weise mit zusätzlicher Bedeutung versehen – sie werden also semantisch markiert oder “ausgezeichnet” (engl. to markup)1 – so dass ein Webbrowser sie interpretieren und geeignet darstellen kann.

Einstiegsbeispiel

Als anschauliches Beispiel wird hier eine sehr einfach aufgebaute Website betrachtet,2 die Sie unter der folgenden URL im Browser öffnen können:
https://weiterbildung-informatik.wollw.de/content/examples/bandpage/index.html

Die Website besteht aus mehreren Webseiten und Bildern, die wie folgt miteinander verknüpft sind (die Datei index.html dient hier als Einstiegsseite):

Image

Sie können den HTML-Quelltext einer Webseite selbst im Browser untersuchen, indem Sie die Webseite öffnen, anschließend einen Rechtsklick auf die Seite ausführen und im Kontextmenü “Quelltext anzeigen” auswählen (je nach verwendetem Webbrowser heißt der Menüeintrag leicht unterschiedlich).

Die folgende Abbildung zeigt rechts den HTML-Quelltext der Webseite und links zum Vergleich die Präsentation der Webseite im Browser (zum Vergrößern anklicken):

Image Image

Im Quelltext lassen sich ab Zeile 10 alle Textinhalte der Seite wiederfinden. Dabei lässt sich erkennen, dass Textabschnitte durch bestimmte Zeichenfolgen markiert sind, die ihnen eine spezielle Bedeutung verleihen – beispielsweise in Zeile 10:

<h1>Bandpage der Crispy Crablets</h1>

Hier stehen zu Beginn und am Ende der Zeile die Zeichenfolgen <h1> und </h1>, durch die der Beginn und das Ende einer Überschrift markiert wird (“h1” steht für dabei für heading level 1). Solche Zeichenfolgen in spitzen Klammern werden als Auszeichnungen oder Tags bezeichnet (engl. tag = Markierung, Etikett). Tags treten meistens paarweise in Form eines “öffnenden” und eines “schließenden” Tags auf und umschließen einen Inhalt, der durch die Tags semantisch beschrieben wird.

Tags

Tags sind also bestimmte Zeichenfolgen in HTML, mit denen sich Textteile und Abschnitte mit bestimmten Bedeutungen versehen lassen, die durch den Browser interpretiert werden. Mittels Tags lassen sich unter anderem die Inhalte der Seite gliedern. Im Beispiel sind mehrere solcher Tags zu finden:

Überschriften:

Die Hauptgliederung ist durch Überschriften verschiedener Stufen beschrieben. Neben einer Überschrift erster Stufe für den Seitentitel, die durch die Tags <h1> und </h1> markiert ist (Zeile 10), kommen auch auch Überschriften zweiter Stufe mit <h2> und </h2> als Abschnittstitel vor (Zeile 20 und 27).

<h1>Bandpage der Crispy Crablets</h1>
...
<h2>Die Band</h2>
...
<h2>Alben</h2>
...

Absätze: Die Textabsätze sind durch die Tags <p> und </p> (für paragraph) markiert, siehe z. B. Zeile 11–19:

<p>
  Text im ersten Absatz
</p>
<p>
  Text im zweiten Absatz
</p>
<p>
  Text im dritten Absatz
</p>

Liste: Die Liste wird durch die Tags <ul> und </ul> (für unordered list) eingeleitet und abgeschlossen wird (Zeile 21 und 26), während die einzelnen Listeneinträge innerhalb der Liste durch die Tags <li> und </li> (für list item) gekennzeichnet sind (Zeile 22–25):

<ul>
  <li>Erster Listeneintrag</li>
  <li>Zweiter Listeneintrag</li>
  <li>Dritter Listeneintrag</li>
  <li>Vierter Listeneintrag</li>
</ul>

Wenn Sie die Beispiel-Webseiten durchstöbern, finden Sie auf den beiden Seiten, die über die Cover-Bilder verlinkt sind, weitere Listen, in denen die Einträge nummeriert dargestellt werden. Hier besteht der einzige Unterschied darin, dass die Liste durch die Tags <ol> und </ol> (für ordered list) beschrieben wird.

Betonter Text: Daneben finden sich auch Auszeichnungen, um Textteile innerhalb von Absätzen zu betonen, z. B. die Tags <em> und </em> (für emphasized) zu Beginn des ersten Absatzes (Zeile 12):

  Die <em>Crispy Crablets</em> sind eine Band aus ...

und die Tags <strong> und </strong> für den stark betonten Text zu Beginn des dritten Absatzes (Zeile 18):

  <strong>Achtung:</strong> Hier findet ihr die aktuellen ...

Hyperlink: Hyperlinks werden durch die Tags <a> und </a> (für anchor) markiert, wobei zwischen den Tags der “anklickbare” Seiteninhalt steht (siehe z. B. Zeile 18):

  <a href="tour.html">Tourdaten</a>

Am Beispiel des Hyperlinks ist zu sehen, dass eine Auszeichnung auch zusätzliche Informationen enthalten kann – in diesem Fall wird die Ziel-URL, auf die der Hyperlink verweist, im öffnenden Tag nach href= angegeben (auf diesen Fall wird unter HTML-Attribute genauer eingegangen).

In allen Beispielen ist außerdem erkennbar, dass sich Textauszeichnungen unterscheiden lassen, die vom Browser als Absätze bzw. voneinander abgesetzte “Blöcke” innerhalb der Seite dargestellt werden (z. B. Überschriften, Textabsätze oder Listen, aber auch Tabellen oder Bilder), und Auszeichnungen für Textabschnitte, die im Fließtext dargestellt werden (z. B. betonte Textteile oder Hyperlinks im Text).

Außerdem finden sich noch weitere Tags außerhalb des eigentlichen Seiteninhalts im HTML-Dokument: So wird das Dokument selbst beispielsweise durch das Tag <html> eingeleitet (Zeile 2) und durch </html> abgeschlossen (Zeile 36). Im nächsten Abschnitt werden wir einen systematischen Blick auf die Grundstruktur von HTML-Dokumenten, sowie Syntax und Semantik der HTML-Tags werfen.

HTML-Dokumente

Zunächst fassen wir kurz zusammen, was wir anhand der Beispiel-Webseite gelernt haben: Ein HTML-Dokument beschreibt einen strukturierten Text, der unter anderem Überschriften, Absätze, Bilder, Listen und Tabellen sowie Hyperlinks enthalten kann. Dazu werden Textteile mit Tags ausgezeichnet, wodurch der Text in Elemente unterschiedlicher Bedeutung gegliedert wird. Die folgenden Abbildungen stellen die jeweiligen Abstraktionsstufen für das Beispiel grafisch dar:

Image Image

Image Image

Image Image

Image Image

Grundgerüst

Eine HTML-Datei hat immer den folgenden grundlegenden Aufbau:

Image

In der ersten Zeile steht die Dokumenttyp-Deklaration, die dem Browser die Art des Dokuments mitteilt. Die Angabe <!DOCTYPE html> sagt aus, dass es sich um ein HTML5-Dokument handelt.3

Das HTML-Dokument besteht immer aus zwei Teilen:

  • dem Dokumentenkopf, der Informationen über das Dokument (“Metadaten”) enthält, z. B. den Titel der Seite oder die verwendete Zeichencodierung,
  • dem Dokumentenrumpf, der die vom Browser dargestellten Seiteninhalte.

Dadurch hat jedes HTML-Dokument dasselbe Grundgerüst:

  • Es beginnt mit dem Tag <html> und endet mit dem Tag </html>. Diese Tags legen den gesamten dazwischenliegenden Dateiinhalt als HTML-Dokument fest.
  • Nach <html> beginnt der Dokumentenkopf mit <head> und endet mit </head>. Dazwischen stehen die Metadaten, beispielsweise hier der Seitentitel (markiert durch die Tags <title> und </title>) und die Zeichencodierung des Dokuments (für HTML5 üblicherweise UTF-8).
  • Nach </head> beginnt der Dokumentenrumpf mit <body> und endet mit </body>, direkt vor </html>. Dazwischen stehen die eigentlichen Seiteninhalte.

Die Informationen, die im Dokumentenkopf stehen, werden nicht innerhalb der Seite im Browser dargestellt, ggf. aber an anderer Stelle – der Seitentitel wird beispielsweise üblicherweise in der Kopfzeile des Browserfensters dargestellt, und im Dokumentenkopf lässt sich auch ein Symbolbild (“Favicon”) für die Webseite (z. B. für Lesezeichen) festlegen (siehe Weitere HTML-Elemente im Dokumentenkopf).

HTML-Elemente

Die durch Tags markierten Textteile – also die Einheiten aus öffnendem Tag, Inhalt und schließendem Tag – stellen die Grundbausteine dar, aus denen das HTML-Dokument zusammengesetzt ist und werden daher als HTML-Elemente bezeichnet. Dabei werden unterschiedliche Tag-Bezeichner verwendet, um verschiedene Typen von HTML-Elementen zu beschreiben – z. B. html für das Dokument an sich, body für den Dokumentenrumpf, p für einen Absatz oder a für einen Hyperlink.

Image

Ein HTML-Element ist immer nach demselben Schema aufgebaut: Es beginnt mit einem öffnenden Tag der Form <Tag-Name>, gefolgt vom Inhalt des Elements, und wird durch ein schließendes Tag der Form </Tag-Name> beendet. Der Inhalt kann dabei reiner Text sein, aber auch weitere HTML-Elemente enthalten – HTML-Elemente können also “ineinander verschachtelt” werden.

Daneben gibt es auch HTML-Elemente ohne Inhalt, die nur aus einem einzelnen Tag der Form <Tag-Name> bestehen. Beispielsweise stellt <br> ein einfaches HTML-Element ohne Inhalt dar, nämlich einen Zeilenumbruch im Fließtext.

Welche HTML-Elemente an welcher Stelle im HTML-Dokumente zulässig sind und auf welche Weise HTML-Elemente ineinander verschachtelt werden dürfen, wird dabei durch die HTML-Spezifikation geregelt (siehe Validierung von HTML). In vielen Fällen erschließen sich diese Regeln aber relativ intuitiv aus der Bedeutung der Elemente: So darf das HTML-Element <title>, das den Seitentitel angibt, nur im <head>-Element (also im Dokumentenkopf) stehen, während HTML-Elemente wie <h1> für Überschriften oder <p> für Textabsätze nur im <body>-Element (also im Seiteninhalt) erlaubt sind.Listeneinträge mit <li> machen dagegen nur innerhalb von Listen Sinn, also z. B. im Inhalt von <ol>-Elementen.

HTML-Attribute

Die meisten HTML-Elemente besitzen Attribute, über die bestimmte Eigenschaften für das Element festgelegt werden können. Bei einem Hyperlink-Element <a> gibt beispielsweise der Inhalt an, wie der Hyperlink im Browser dargestellt wird (hier durch den Text “Topanga”), während die Ziel-URL über ein Attribut namens href (kurz für hyperlink reference) festgelegt wird:

Image

Die Zuweisung von Werten zu Attributen erfolgt immer im öffnenden Tag in der Form Attributname = Wert.

Für einige Attribute wie href muss ein Wert angegeben werden, damit das HTML-Element sinnvoll interpretiert werden kann. Andere Attribute sind optional – wenn kein Wert explizit zugewisen wird, wird ein Standardwert verwendet.

Ein weiteres Beispiel für HTML-Elemente mit Attributen (in diesem Fall ohne Inhalt) ist das HTML-Element <img>, mit dem sich Bilder in Webseiten einbinden lassen:

Image

  • Über das Attribut src wird die Quell-URL der Bilddatei angegeben. Für dieses Attribut muss ein Wert angegeben werden, während die folgenden Attribute optional sind.
  • Mit dem Attribut width kann die gewünschte Bildbreite zur Darstellung festgelegt werden. Ist das Bild größer oder kleiner, wird es vom Browser zur Darstellung entsprechend skaliert. Alternativ kann auch die gewünschte Bildhöhe mit height festgelegt werden.
  • Mit dem Attribut alt kann eine Bildbeschreibung als Alternativtext festgelegt werden, der statt des Bildes angezeigt wird, wenn das Bild nicht geladen werden kann.
  • Mit dem Attribut title kann ein Text festgelegt werden, der als “Popup” erscheint, wenn sich der Mauszeiger über dem Bild befindet.

HTML-Attribute sind also benannte Eigenschaften von HTML-Elementen, denen sich Werte zuweisen lassen, wodurch sich das Verhalten der HTML-Elemente genauer steuern lässt (ähnlich den Attributen von Scratch-Objekten in der Visuellen Programmierung). Welche Attribute welches Element besitzt, welche Werte für welche Attribute zulässig sind und was sie bedeuten, ist für jedes HTML-Element in der HTML-Spezifikation festgelegt und lässt sich auch in HTML-Referenzen wie z. B. bei W3Schools nachlesen.

URLs in Webseiten

Mit Hyperlinks und Bildern haben wir zwei HTML-Elemente kennengelernt, die Verknüpfungen zu anderen Dokumenten beschreiben. In beiden Fällen wird die Verknüpfung dadurch spezifiziert, dass die URL der verknüpften Datei als Wert für ein bestimmtes Attribut festgelegt wird (href bei Hyperlinks, src bei Bildern).

Hierbei muss zwischen zwei Arten von URL-Angaben unterschieden werden: absoluten URL-Angaben und relativen URL-Angaben. Betrachten Sie dazu die beiden Hyperlinks, die in der Beispiel-Webseite index.html vorkommen:

  • Eine absolute URL wie beispielsweise https://de.wikipedia.org/wiki/Topanga (Zeile 12) stellt eine vollständige Webadresse dar, die auf eine Datei im Internet verweist.
  • Eine relative URL wie beispielsweise tour.html (Zeile 18) stellt dagegen eine Webadresse dar, die relativ zum Dateipfad auf dem Webserver angegeben ist, unter dem die Webseite erreichbar ist, in der diese URL verwendet wird.

Im zweiten Fall wird also erwartet, dass sich die Datei tour.html im selben Ordner auf dem Webserver befindet wie die Datei index.html, in welcher der Hyperlink mit dieser URLs vorkommt. Dasselbe gilt für die URLs der Bilddateien (Zeile 32–33), die ebenfalls im selben Verzeichnis liegen. Angenommen, die Bilder würden in einen Unterordner images verschoben werden. In diesem Fall müssten die URLs in den src-Attributen geändert werden zu “images/cover1.jpg” bzw. “images/cover2.jpg”.

Üblicherweise werden Verknüpfungen zu Dateien, die auf demselben Webserver liegen, in relativer Form angegeben, während Verknüpfungen zu Dateien, die auf anderen Webservern liegen in absoluter Form angegeben werden.

Vertiefung: Objektmodell

Da ein HTML-Dokument aus ineinander verschachtelten HTML-Elementen und Textelementen besteht, lässt sich seine Grundstruktur auch als Baumstruktur darstellen, in der jeder Knoten ein HTML-Element repräsentiert, z. B. eine Überschrift, einen Absatz oder einen Hyperlink. Die Kindknoten eines HTML-Elements sind dabei die Elemente, die in seinem Inhalt liegen. Das Element <html> repräsentiert dabei den Wurzelknoten, der genau zwei Kindknoten besitzt (<head> und <body>), die jeweils weitere, im Inhaltsteil auch beliebig tief weiterverzweigte Knoten enthalten. Auch Textteile, die als Inhalt von HTML-Elementen auftreten, stellen Knoten in diesem Baum dar.

graph TD html --- head html --- body head --- title title --- title_inner([Meine Seite]) head --- meta body --- h1 h1 --- h1_inner([Überschrift]) body --- p1[p] p1 --- p1_inner([Erster Absatz]) p1 --- a a --- a_inner([Ein Link]) body --- p2[p] p2 --- p2_inner([Noch ein Absatz])

Die HTML-Elemente (und Text-Elemente), aus denen ein HTML-Dokument besteht, lassen sich als Objekte im Sinne der Programmierung betrachten: Sie haben Attribute mit bestimmten Werten, durch die ihre Eigenschaften festgelegt werden, und stehen entsprechend der Baumstruktur in Beziehung zueinander. Im Webbrowser werden HTML-Dokumente intern tatsächlich auf diese Weise repräsentiert: Beim Lesen eines HTML-Dokuments wird im Speicher ein Baum von Objekten erzeugt, den der Browser zur Präsentation der Seite verwendet. Diese Darstellung wird auch als Document Object Model (kurz DOM, engl. für “Dokumenten-Objekt-Modell”) bezeichnet.4

Webseiten erstellen

Um eine einfache Website mit HTML zu erstellen, benötigen Sie nur einen Texteditor zum Bearbeiten der HTML-Dateien und einen Webbrowser zum Anzeigen der Seiten. Sie benötigen keinen Webserver, der Ihre Dateien über das Internet zur Verfügung stellt, sondern können die Dateien einfach lokal auf Ihrem Rechner speichern und bearbeiten.5

Legen Sie zunächst einen Ordner auf Ihrem Rechner an, in dem Sie die HTML-Dateien und andere Dateien (z. B. Bilder) für Ihre Website speichern. Erstellen Sie dort eine neue Textdatei mit der Dateiendung .html (zum Beispiel homepage.html oder index.html6), öffnen Sie sie im Texteditor und fügen Sie das Grundgerüst als Inhalt ein (den Seitentitel können Sie durch einen eigenen, passenden Titel ersetzen):

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Seitentitel</title>
</head>
<body>

</body>
</html>

Achten Sie darauf, dass UTF-8 als Zeichencodierung verwendet wird. Alternativ können Sie die HTML-Datei homepage.html mit dem Grundgerüst auch hier als Vorlage herunterladen und in Ihrem Arbeitsordner speichern: Download

Im Dokumentenrumpf können Sie nun eigenen Inhalt ergänzen, zum Beispiel Überschriften und Textabsätze. Eine Überblick über grundlegende HTML-Elemente, die Ihnen beim Erstellen erster eigener Webseiten helfen können, finden Sie im Anhang unter HTML-Referenz.

Öffnen Sie die Datei nun in einem Webbrowser, um die grafische Darstellung der Seite präsentiert zu bekommen.

Image

Generell ist es bei der Entwicklung von Webseiten im Texteditor hilfreich, sowohl den Editor als auch die Browseransicht gleichzeitig in zwei nebeneinander auf Ihrem Desktop positionierten Fenstern geöffnet zu haben, damit Sie Änderungen an der Textdatei schnell visuell überprüfen können. Speichern Sie dazu nach einer Änderung die Textdatei und aktualisieren Sie dann die Browseransicht der Webseite (z. B. in Firefox die Taste F5 drücken).

Wenn Sie Verknüpfungen zu anderen Webseiten oder Bildern erstellen, die in Ihrem Arbeitsordner liegen, reicht als URL der Dateiname. Wenn Sie beispielsweise ein Bild in eine Webseite einfügen möchten, das in einer Datei leuchtturm.jpg gespeichert ist, kopieren Sie die Datei den Ordner, in dem auch die entsprechende HTML-Datei liegt, und fügen Sie ein HTML-Element <img> zum Seiteninhalt hinzu, z. B.:

<img src="leuchtturm.jpg" alt="Bild eines Leuchtturms">

Als umfangreicheres Beispiel können Sie auch die Beispiel-Website aus der Einleitung auf Ihren Rechner herunterladen, untersuchen und verändern: Download

Entwicklungsumgebungen

Prinzipiell können Sie jeden einfachen Texteditor, der auf Ihrem Rechner installiert ist, zur Bearbeitung von HTML-Dateien verwenden. Einige Texteditoren sind aber geeigneter als andere, da sie über nützliche Funktionen zur HTML-Bearbeitung verfügen.

Die meisten Texteditoren verwenden Syntax Highlighting für HTML, heben also spezielle HTML-Bestandteilen wie Tags, Attributnamen oder -werte im Text durch spezielle Farben oder Formatierungen hervor, was die Übersichtlichkeit des Quelltextes erhöht.

Einige Texteditoren beherrschen darüber hinaus Code-Vervollständigung, dass heißt, dass sie während der Eingabe Vorschläge zur Textergänzung machen, wenn Sie HTML-Code erkennen, zum Beispiel:

  • Wenn Sie <s eingeben, erscheint eine Liste, in der alle Tag-Namen, die mit “s” beginnen, ausgewählt werden können.
  • Wenn Sie <a gefolgt von einem Leerzeichen eingeben, erscheint eine Liste, in der alle Attribute des Elements ausgewählt werden können.
  • Wenn Sie das Tag <h1> eingegeben haben, erscheint automatisch das dazu passende schließende Tag </h1>.

Fortgeschrittenere Editoren enthalten manchmal auch eine integrierte Browservorschau, die während der Eingabe automatisch aktualisiert wird, so dass Sie den visuellen Effekt einer Änderung im Quelltext sofort überprüfen können.

Eine Liste von Texteditoren mit solchen Funktionen finden Sie in der Linksammlung bei den Software-Werkzeugen. Für Windows ist Notepad++ aufgrund seiner Einfachheit empfehlenswert. Unter Linux und iOS verfügt der vorinstallierte Standard-Texteditor in der Regel bereits über Syntax Highlighting und Code-Vervollständigung für HTML.

Online-Editoren

Daneben finden Sie im Internet auch eine Reihe von Online-Entwicklungsumgebungen für kleinere HTML-Projekte, die einfach im Webbrowser geöffnet werden können und nicht auf Ihrem Rechner installiert werden müssen. Diese Tools besitzen in der Regel eine integrierte Browservorschau und ermöglichen es zum Teil auch, online auf die erstellten Websites zuzugreifen – das heißt, sie können von anderen Personen im Webbrowser geöffnet werden, statt nur lokal auf Ihrem Rechner verfügbar zu sein.

Die E-Learning-Webseite W3Schools stellt beispielsweise für ihre HTML/CSS-Tutorials einen sehr einfachen Online-Editor zum Bearbeiten und Anzeigen einzelner HTML-Seiten bereit.

In der Online-Entwicklungsumgebung Glitch lassen sich Websites erstellen, die aus mehreren HTML-Seiten sowie weiteren Dateien (z. B. Bilder, CSS-Dateien) bestehen. Die erstellten Websites können über einen öffentlichen Link von jedem angesehen und “remixt” werden, so dass sich Glitch auch im Rahmen des Schulunterrichts zum Bereitstellen von Webseiten eignet, die durch die Schülerinnen und Schüler angepasst, korrigiert oder erweitert werden sollen.

Sie finden Links zu Glitch und weiteren Online-Editoren in der Linksammlung bei den Software-Werkzeugen.

HTML-Inspektoren

Im Aufbau

Validierung

Damit HTML-Dokumente sinnvoll interpretiert werden können, müssen sie logisch und strukturell korrekt sein. Wie korrekter HTML-Quellcode aussieht, ist dabei durch Spezifikationen7 der Hypertext Markup Language festgelegt. HTML-Quellcode, der sich an alle Konventionen und Spezifikationen hält, wird als valide bezeichnet, die Überprüfung als Validierung.

Die offiziellen Spezifikationen für HTML werden vom World Wide Web Consortium (W3C) und der Web Hypertext Application Technology Working Group (WHATWG) entwickelt.8 Darin wird unter anderem festgelegt:

  • welche Grundstruktur ein valides HTML-Dokument hat,
  • welche Attribute für bestimmte HTML-Elemente erlaubt sind,
  • welche Attribute optional sind und welche zwingend benötigt werden,
  • welche HTML-Elemente innerhalb von welchen anderen HTML-Elemente erlaubt sind,
  • welche HTML-Elemente und -Attribute veraltet sind und nicht mehr genutzt werden sollten,
  • wie ein Webbrowser HTML-Elemente mit bestimmten Eigenschaften interpretieren sollte.

Dabei gibt es Unterschiede zwischen verschiedenen Versionen von HTML. Die HTML-Version eines HTML-Dokuments wird durch die Angabe des Dokumenttyp mit <!DOCTYPE > zu Beginn festgelegt.

Ein valides HTML-Dokument besteht immer aus der Dokumenttyp-Deklaration und den Elementen <html>, <head>, <title> und <body> (siehe Grundgerüst). Beispiele für eine valide Struktur von HTML-Elementen im Seiteninhalt sind etwa:

  • Listeneinträge (<li>) nur innerhalb von Listen (<ul>, <ol>) vorkommen dürfen,
  • innerhalb eines Paragraphen (<p>) keine gruppierenden Elemente wie Listen (<ul>, <ol>) vorkommen dürfen.

Beispiele für grundlegende Vorgaben, wie Webbrowser mit HTML-Elementen umzugehen haben, sind:

  • Das Ziel eines Hyperlinks (<a>) soll in einem neuen Fenster geöffnet werden, wenn das Attribut target den Wert "_blank" hat.
  • Für ein Bild (<img>) soll dessen Alternativtext (Wert des Attributs alt) angezeigt werden, wenn das Bild nicht geladen werden konnte.

So können sich Webentwicklerinnen und -entwickler darauf verlassen, dass ihre HTML-Dokumente auf eine vorgegebene Weise von Webbrowsern interpretiert und dargestellt werden, solange sie sich an die entsprechenden Vorgaben halten.

Die meisten Webbrowser können HTML-Dokumente aber auch dann einigermaßen sinnvoll darstellen, wenn der HTML-Quellcode nicht vollständig valide ist, also in einem gewissen Umfang logische oder strukturelle Fehler enthält. In solchen Fällen ist aber nicht gesichert, ob die Seite wie erwartet dargestellt wird, weswegen solche Fehler in der Praxis möglichst vermieden werden sollten. Typische Fehler sind:

  • Ein öffnendes Tag wird an der falschen Stelle oder gar nicht geschlossen.
  • Ein Element, das in den Inhaltsbereich (<body>) gehört, wird versehentlich im Kopf der HTML-Datei (<head>) definiert.
  • Die Namen von Tags oder Attributen enthalten Buchstabendreher oder andere Schreibfehler.

Tipp: Um Fehler schneller zu finden, ist es sinnvoll, HTML-Quelltext übersichtlich zu strukturieren. Die folgende Abbildung zeigt sehr chaotischen Quelltext, in dem es schwer ist, die vorhandenen Fehler ausfindig zu machen (links). In der aufgeräumten Version (rechts) lässt sich dagegen schnell feststellen, dass zwei schließende Tags fehlen bzw. syntaktisch falsch angegeben sind:

Image Image

W3C Online-Validator

Tool: Um zu überprüfen, ob ein HTML-Dokument valide ist, kann der Online-Validator des W3C verwendet werden: https://validator.w3.org

In diesem Tool kann eine HTML-Datei hochgeladen, die URL einer HTML-Datei im Internet eingegeben oder HTML-Quellcode in ein Texteingabefeld eingefügt werden und validiert werden. Erkannte Fehler und Warnungen werden anschließend angezeigt.

Die folgende Abbildung zeigt, wie HTML-Quellcode über das Texteingabefeld validiert wird (links), wobei ein schließendes Tag fehlt. Nachdem auf die Schaltfläche “Check” geklickt wird, erscheint ein Validierungsbericht (rechts), in dem als Fehler das fehlende Tag <\h1> (3.), sowie zwei Folgefehler (1. und 2.) erkannt werden:

  1. Da das Tag <h1> nicht geschlossen wird, wird das folgende Absatz-Element <p> als Kindknoten des Überschrift-Elements <h1> interpretiert, was aber laut HTML-Spezifikation nicht erlaubt ist.
  2. Es wird erkannt, dass beim Schließen des <body>-Tags innerhalb des Dokumentenrumpfes Elemente vorkommen, die noch nicht geschlossen wurden (nämlich <h1>).

Image Image

HTML-Referenz

Hier finden Sie einen kurzen Überblick über die wichtigsten, grundlegenden HTML-Elemente zum Erstellen einfacher Webseiten, jeweils nach Kategorien aufgeteilt. Umfangreichere Listen zum Weiterlernen finden Sie bei W3Schools und SELFHTML.

Dokument

Diese HTML-Elemente legen die Grundstruktur des HTML-Dokuments fest.

ElementBeschreibung
<html> </html>HTML-Dokument
<head> </head>Dokumentenkopf mit Metadaten, z. B. Titel der Seite
<body> </body>Dokumentenrumpf mit Seiteninhalt

Text

Diese HTML-Elemente dienen zur Textgliederung und Auszeichnung von Textteilen im Seiteninhalt.

ElementBeschreibung
<h1> </h1><h6> </h6>Überschriften (Stufe 1 bis 6)
<p> </p>Textabsatz (Paragraph)
<br>Zeilenumbruch (engl. break line)
<hr>Horizontale Trennlinie (engl. horizontal ruler)
<em> </em>Betonter Text (engl. emphasized), in der Regel kursiv dargestellt9
<strong> </strong>Stark betonter Text, in der Regel fett dargestellt10
<code> </code>Quellcode, in der Regel mit Festbreitenschrift dargestellt11
<q> </q>Zitat im Fließtext
<blockquote> </blockquote>Zitat als Absatz

Verknüpfungen

Diese HTML-Elemente dienen zum Verknüpfen von Webseiten untereinander und zum Einfügen von Bilddateien in den Seiteninhalt. Die URLs und weitere (optionale) Informationen werden über die Attribute der Elemente festgelegt.

ElementBeschreibungAttributeBeschreibung
<a href="URL"> </a>Hyperlinkhref="URL"legt das Ziel der Verknüpfung fest
optional: target="_blank"legt fest, dass das Ziel in einem neuen Fenster geöffnet werden soll
<img src="URL">Bildsrc="URL"legt die URL der Bilddatei fest
optional: Attributen alt="Text"legt Alternativtext fest, der angezeigt wird wenn das Bild nicht geladen werden konnte
optional: width="Zahl" und/oder height="Zahl"legt die gewünschte Bildgröße zur Darstellung fest
optional: title="Text"legt Beschreibungstext fest, der beim Draufzeigen mit der Maus angezeigt wird

Listen und Tabellen

Listen und Tabellen im Seiteninhalt werden durch verschachtelte HTML-Elemente beschrieben.

ElementBeschreibung
<ul> </ul>Liste ohne Nummerierung
<ol> </ol>Nummerierte Liste
<li> </li>Listeneintrag innerhalb einer Liste (innerhalb <ul> oder <ol>)
<table> </table>Tabelle
<tr> </tr>Tabellenzeile in einer Tabelle (innerhalb <table>)
<td> </td>Datenzelle in einer Tabellenzeile (innerhalb <tr>)
<th> </th>Spaltenüberschrift in einer Tabellenzeile (innerhalb <tr>), in der Regel die Datenzellen der ersten Tabellenzeile

Die Art der Nummerierung für die Listeneinträge in einem <ol>-Element kann durch das HTML-Attribut ``

Metadaten

Die folgenden HTML-Elemente legen Informationen über das Dokument fest und kommen nur im Dokumentenkopf vor.

ElementBeschreibung
<link>Externe Datei im Dokumentenkopf einbinden mit Attributen href="URL" zum Festlegen der Quelle und rel=Relation für die Bedeutung der externen Datei, z. B. rel="stylesheet" für eine CSS-Datei oder rel="icon" für das Favicon12
<meta>Weitere Metadaten, z. B. <meta charset="utf-8"> zum Festlegen der Zeichencodierung als UTF-8

Vertiefung: HTML-Entities

Da im HTML-Quellcode bestimmte Zeichen eine Sonderbedeutung haben – insbesondere die spitzen Klammern < und >, die zur Kennzeichnung von Tags verwendet werden – müssen solche Zeichen auf eine andere Weise dargestellt werden, wenn sie im Seiteninhalt als Textzeichen vorkommen sollen.

Beispiel: Angenommen, der HTML-Quellcode enthält die folgende Zeile, in der die Bedeutung des HTML-Elements <em> erläutert wird:

<p>Das Tag <em> betont Text und wird durch das Tag </em> geschlossen.</p>

Der entsprechende Absatz wird vom Browser aber folgendermaßen dargestellt, da die Zeichenfolgen <em> und </em> natürlich als HTML-Tags interpretiert werden:

Das Tag betont Text und wird durch das Tag geschlossen.

Um solche Sonderzeichen als reine Textzeichen darzustellen, werden in HTML bestimmte alternative Zeichenfolgen verwendet, die sogenannten HTML-Entities. Diese werden in der Form &Entity-Name; angegeben, wobei die Entity-Namen meistens Kurzformen der repräsentierten Zeichen darstellen. Die HTML-Entities für die Zeichen < und > sind beispielsweise &lt; (kurz für less than = kleiner gleich) und &gt; (kurz für greater than = größer gleich).

Da das Zeichen & eine HTML-Entity einleitet, muss dieses ebenfalls durch eine HTML-Entity ersetzt werden, wenn es als Textzeichen dargestellt werden soll – in diesem Fall durch die Zeichenfolge &amp; (kurz für ampersand = “Kaufmanns-Und”).

HTML-Entities existieren für viele Sonderzeichen, auch für solche, die in HTML keine Sonderbedeutung haben. Das ist hilfreich für Steuerzeichen, die sich nicht direkt per Tastatur eingeben lassen, beispielsweise das geschützte Leerzeichen &nbsp; (kurz für non-breaking space) oder das weiche Trennzeichen &shy; (kurz für soft hyphen).13

Alternativ lässt sich jedes Sonderzeichen in HTML auch im Format &#Nummer; mit der Unicode-Nummer des gewünschten Zeichens angeben, z. B. &#196; für den Buchstaben Ä.

Die Definition von HTML-Entities ist hauptsächlich dadurch motiviert, dass HTML-Dateien früher in 8-Bit-Zeichencodierungen (in der Regel ISO-8859-1) codiert wurden. Seitdem sich HTML5 als Standard für HTML-Dokumente etabliert hat, wird standardmäßig UTF-8 als Zeichencodierung empfohlen, weswegen HTML-Entities in HTML5-Dokumenten bis auf wenige Ausnahmen kaum noch verwendet werden.

Die folgende Liste zeigt der Vollständigkeit halber einige der vor HTML5 am häufigsten verwendeten HTML-Entities:14

HTML-EntitySonderzeichen
&Auml; &auml;&uuml;Umlaute Ä äü
&szlig;Eszett-Zeichen ß
&amp;Und-Zeichen & (engl. ampersand)
&lt; &gt;Spitze Klammern < > (bzw. Vergleichszeichen, engl. less/greater than)
&quot;Anführungszeichen " (engl. quotation mark)
&deg;Grad-Zeichen ° (engl. degree)
&euro;Euro-Zeichen
&copy;Copyright-Zeichen ©
&nbsp;Geschütztes Leerzeichen (engl. non-breaking space)
&shy;Weiches Trennzeichen (engl. soft hyphen)
&#Nummer;Sonderzeichen mit der angegebenen Referenznummer (dezimal angegeben)
&#xNummer;Sonderzeichen mit der angegebenen Referenznummer (hexadezimal angegeben)

  1. Formale Sprachen, die dieses Grundkonzept verwenden und zu denen auch HTML gehört, heißen daher “Auszeichnungssprachen” (markup languages). ↩︎

  2. Generell empfielt es für den Einstieg in HTML, spezielle didaktisch reduzierte Webseiten zu untersuchen. Die meisten “echten” Webseiten eignen sich eher nicht als Lernbeispiele, da sie sehr umfangreichen, oft automatisch generierten und damit sehr unübersichtlichen HTML-Code enthalten. ↩︎

  3. Wird die Dokumenttyp-Deklaration weggelassen, wird die Webseite zwar in der Regel trotzdem im Webbrowser angezeigt, eventuell aber nicht wie erwartet, da der Webbrowser in dem Fall die verwendete HTML-Version raten muss. ↩︎

  4. Der Begriff HTML DOM bedeutet konkret nicht nur die Darstellung der HTML-Dokuments durch einen Baum von Objekten, sondern beschreibt darüber hinaus eine Programmierschnittstelle, die von Programmiersprachen wie JavaScript genutzt werden, um im Browser geladene HTML-Dokumente dynamisch zu ändern. ↩︎

  5. So ist Ihre Website zwar nicht über das Internet von außen zugänglich, sondern kann nur lokal auf Ihrem Rechner geöffnet werden – für den Anfang reicht das aber, um erste Projekte zu erstellen, anhand derer sich HTML praktisch erlernen lässt. ↩︎

  6. Der Dateiname index.html wird üblicherweise für die Einstiegsseite einer Website verwendet, die aus mehreren HTML-Dateien besteht. ↩︎

  7. Eine Spezifikation im Sinne der Informatik legt die Eigenschaften und die gewünschte Umsetzung einer Technologie (z. B. einer Software, einer Programmiersprache, eines technischen Systems) fest. ↩︎

  8. siehe https://www.w3.org/TR/html5 und https://html.spec.whatwg.org ↩︎

  9. vgl. <i> </i> für kursiv dargestellten Text (engl. italic↩︎

  10. vgl. <b> </b> für fettgedruckten Text (engl. bold↩︎

  11. vgl. <tt> </tt> für Text mit Festbreitenschrift (Schreibmaschinen- oder Fernschreiberschrift, engl. teletype↩︎

  12. Ein Favicon (engl. favorite icon) ist ein kleines Symbolbild für eine Webseite, das im Browser oben neben dem Seitentitel und im Lesezeichenmenü angezeigt wird. ↩︎

  13. Eine Zeile wird bei einem geschützten Leerzeichen nicht umgebrochen, außerdem ersetzt der Webbrowser mehrfache aufeinanderfolgende geschützte Leerzeichen nicht durch ein einzelnes Leerzeichen, wie bei normalen Leerzeichen im HTML-Quelltext. Eine weiches Trennzeichen wird dagegen nur dargestellt, wenn das Wort an dieser Stelle durch einen Zeilenumbruch getrennt wird. ↩︎

  14. siehe auch Listen bei W3Schools und SELFHTML. Eine vollständige Liste aller HTML-Entity-Namen des W3C finden Sie hier:
    https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references ↩︎

2.3.2 Gestaltung mit CSS

Mit HTML lässt sich die Struktur von Webseiten definieren und Textinhalte können semantisch ausgezeichnet werden, z. B. als Hyperlinks, betonter Text, Code oder Zitat. Wie diese Elemente grafisch dargestellt werden, hängt dabei vom Webbrowser ab, also beispielsweise welche Schriftart für Überschriften, Textinhalte oder Code jeweils verwendet wird oder mit welcher Hintergrundfarbe die Seite dargestellt wird.

Als Beispiel ist hier eine Webseite zu sehen, die ohne jede Gestaltungsvorgaben in den Browsern Firefox und Opera angezeigt wird.

Die grafische Darstellung kann also je nach Webbrowser und Betriebssystem unterschiedlich sein (z. B. werden hier üblicherweise verschiedene Standardschriftarten verwendet). Dabei gibt es Konventionen, die von den meisten Webbrowser eingehalten werden, z. B. wird <em> kursiv dargestellt, <strong> fett, <code> mit einer Festbreitenschriftart, Unterschriften höherer Ebenen größer als Unterschriften niedrigerer Ebenen. Davon abgesehen lässt sich mit reinem HTML bisher aber kein Einfluss auf die grafische Gestaltung der Seite nehmen.

Wie Elemente einer HTML-Webseite dargestellt werden sollen, wird durch eine andere Sprache beschrieben, nämlich CSS.

CSS

CSS steht kurz für Cascading Style Sheets (also etwa “gestufte Gestaltungsbögen”) und stellt eine formale Sprache dar, mit der sich die grafische Darstellung von HTML-Elementen in textueller Form beschreiben lässt. Dazu werden zusätzliche Attribute verwendet, die als Stilattribute (oder CSS-Attribute) bezeichnet werden.

In einem CSS-Dokument wird beschrieben, wie HTML-Elemente dargestellt werden sollen, indem ihren Stilattributen bestimmte Werte zugewiesen werden. Solche Wertezuweisungen für ein bestimmtes Element werden als Stilregeln (oder CSS-Regeln) bezeichnet. Diese Regeln stellen Gestaltungsanweisungen für den Browser dar.

Ähnliche Stilregeln kennen wir aus akademischen Kontexten, wenn es etwa um die Ausfertigung von schriftlichen Arbeiten geht. Beispielsweise heißt es in §11 APVO Lehrkräfte über die schriftliche Hausarbeit im Rahmen des Vorbereitungsdienstes:

Die Schriftart ist Arial mit dem Zeilenabstand 1,0; der Schriftgrad beträgt 12 Pt.

In CSS formuliert könnte diese Stilregel so aussehen:

body {
  font-family: Arial;
  line-height: 1.0;
  font-size: 12pt;
}

Das CSS-Dokument wird mit dem HTML-Dokument verknüpft, indem im Dokumentenkopf des HTML-Dokuments ein entsprechendes <link>-Element angegeben wird:

Verwechslungsgefahr! Das <link>-Element dient nicht dazu, Links auf andere Seiten zu setzen! Dafür muss der Tag <a> verwendet werden. Das <link>-Element verknüpft mehrere Dateien, die zusammengesetzt eine Webseite ergeben.

<head>
  ...
  <link href="style.css" rel="stylesheet" type="text/css">
</head>

In diesem Fall heißt die CSS-Datei style.cssund liegt im selben Verzeichnis auf dem Webserver wie die HTML-Seite, von der sie verwendet wird.

Dieses Prinzip erlaubt es, ein und die selbe CSS-Datei in allen HTML-Dateien einer Website einzubinden, so dass die Gestaltungsregeln der gesamten Website an einer zentralen Stelle festgelegt werden können.

CSS-Regeln

Eine CSS-Regel ist immer nach demselben Schema aufgebaut: Sie beginnt mit einer Angabe, für welche(s) HTML-Element(e) die Regel gelten soll. Dieser Teil der Regel wird als Selektor bezeichnet. Es folgen in geschweiften Klammern die Wertezuweisungen zu den Stilattributen, jeweils im Format: Attributname Doppelpunkt Wert(e) Semikolon

Image

Als Selektor kann ein einzelnes HTML-Element oder mehrere HTML-Elemente durch Komma getrennt angegeben werden, für welche die Stilregel gelten soll.

Die meisten Stilattribute erwarten einen einzelnen Wert, der zum erwarteten Datentyp passen muss. Die für den Einstieg relevanten Datentypen, die in CSS unterschieden werden, sind:

  • Farbwerte: z. B. für Schriftfarben, Linien- und Hintergrundfarben von HTML-Elementen (siehe Referenz zu Farbwerten)
  • Größenangaben: z. B. für Abstände, Schriftgrößen oder Linienbreiten von HTML-Elementen (siehe Referenz zu Größenangaben)
  • Schriftarten: z. B. für das Attribut font-family, werden durch die Namen der Schriftarten durch Komma getrennt beschrieben
  • Aufzählungen: Attribut-spezifische Bezeichner, z. B. kann für das Attribut text-align als Textausrichtung left, right, center oder justify (Blocksatz) angegeben werden
  • Zahlenwerte (Ganzzahlen, Dezimalzahlen) und Prozentangaben: z. B. auch für Größenangaben

Es gibt auch Stilattribute, die mehrere Werte (durch Leerzeichen getrennt) erwarten – diese Attribute sind meistens Kurzformen, die mehrere andere Attribute zusammenfassen. Beispielsweise gibt es die Stilattribute border-width, border-style und border-color, mit denen sich jeweils die Breite, der Linienstil (z. B. durchgezogen, gestrichelt) und die Farbe für den Rahmen eines HTML-Elements festlegen lässt:

table {
  border-width: 1px;
  border-style: solid;
  border-color: black;
}

Daneben gibt es ein Attribut border, mit dem sich in Kurzform alle drei Werte auf einmal zuweisen lassen. Die Werte werden dabei in der Reihenfolge Breite Stil Farbe erwartet:

table {
  border: 1px solid black;
}

Selektoren

CSS erlaubt es, durch die Angabe von speziellen Selektoren sehr präzise zu definieren, für welche Elemente spezielle Gestaltungsregeln gelten sollen.

Der universelle Selektor * erlaubt es, Regeln für alle HTML-Elemente der Seite festzulegen, z.B. um Standardschriftarten zu definieren:

* {
  font-family: "Libertinus Serif";
}

Tags

Der Name eines Tags kann einfach so, ohne die spitzen Klammern, als Selektor verwendet werden. Sofern keine anderen Regeln das einschränken, gelten diese Regeln dann für alle Elemente dieses Tags.

h1 {
  /* Regeln für alle Überschriften auf Ebene 1 */
}

img {
  /* Regeln für alle Bilder */
}

Klassen und IDs

Mit dem HTML-Attribut class können HTML-Elemente zu so genannten Klassen hinzugefügt werden. Diese Klassen können dann genutzt werden, um z. B. für manche Absätze separate Regeln festzulegen. Wenn eine Klasse als Selektor verwendet wird, muss vor ihren Namen ein Punkt gesetzt werden.

Stellen wir uns als Beispiel ein Schulbuch vor, das neben normalem Text noch Infokästen und Hilfestellungen enthält. Das alles sind normale Textabsätze, die mit <p>-Tags gekennzeichnet werden. Die Infokästen werden dann als <p class="infokasten">...</p> und die Hilfestellungen als <p class="hilfestellung">...</p> aufgeschrieben. Die CSS-Regeln dazu könnten dann so aussehen:

.infokasten {
  border: 1px solid black;
  padding: 10px;
}

.hilfestellung {
  border-left: 5px solid #feef00;
  padding-left: 10px;
  background-color: #feef0080;
}

HTML-Elemente können auch zu mehreren Klassen gehören. Die Klassennamen werden dann mit Leerzeichen getrennt in ein class-Attribut geschrieben:

<a class="external email" href="mailto:mail@example.org">Schreib mir eine E-Mail</a>

Genauso wie Klassen können auch IDs vergeben werden, diese kennzeichnen aber einzelne HTML-Elemente, während Klassen in der Regel mehrere Elemente umfassen. Das entsprechende HTML-Element wird mit dem Attribut id versehen und die ID als CSS-Selektor mit einer vorangestellten Raute markiert:

#seitenmenue {
  background-color: lightblue;
  color: navy;
}

Selektoren kombinieren

Um eine Regel für mehrere Selektoren gelten zu lassen, können diese mit Kommata separiert werden:

/* Überschriften erster bis dritter Ordnung werden unterstrichen. */
h1, h2, h3 {
  text-decoration: underline;
}

/* i-Tags, em-Tags und Elemente der Klasse "notice" werden kursiv dargestellt. */
i, em, .notice {
  font-style: oblique;
}

Ineinandergeschachtelte HTML-Elemente können gezielt addressiert werden, indem die Selektoren von außen nach innen mit Leerzeichen getrennt aneinandergehängt werden:

/* Bilder in Infokästen dürfen maximal 30 % von dessen Breite ausfüllen. */
.infokasten img {
  max-width: 30%;
}

/* Links in Überschriften werden doppelt unterstrichen. */
h1 a {
  text-decoration: underline double;
}

Die Schnittmenge von einem Tag und einer Klasse kann addressiert werden, indem man den Selektor der Klasse direkt hinter den des Tags schreibt, ohne trennendes Leerzeichen:

/* Alle <p class="example">-Elemente */
p.example {
  /*...*/
}

/* Alle <_ class="example">-Elemente innerhalb von <p>-Elementen */
p .example {
  /*...*/
}

/* Alle <p>-Elemente und alle <_ class="example">-Elemente */
p, .example {
  /*...*/
}

Layout

Abstände und Rahmen

Abstände und Rahmen werden in CSS mit dem so genannten Box-Modell erzeugt. Um jedes HTML-Element sind drei konzentrische Boxen angeordnet. Diese sind, von innen nach außen:

  1. padding, der Abstand zwischen dem Rahmen und dem Element selbst
  2. border, der Rahmen
  3. margin, der Abstand um den Rahmen herum

Für padding und margin kann jeweils nur die Größe der Box angegeben werden, für border zusätzlich zur Rahmenbreite auch ein Linienstil (etwa solid für durchgezogen, dashed für gestrichelt oder dotted für gepunktet).

Zu beachten ist, dass alle diese drei Boxen unabhängig voneinander definiert werden können.

Diese Attribute können auf unterschiedliche Weisen verwendet werden. Die folgende Regel sorgt beispielsweise dafür, dass innerhalb von Tabellenzellen zehn Pixel Abstand zwischen der Rahmenlinie und dem Text bleiben.

td {
    padding: 10px;
}

Statt nur einen Parameter anzugeben, der auf alle vier Seiten der Box angewendet wird, können auch vier Parameter angegeben werden, die in dieser Reihenfolge die Größen für die obere, rechte, untere und linke Seite der Box vorgeben:

h2 {
    margin: 2.5rem 0 1.5rem 0;
}

Diese Gestaltungsregel wird übrigens auch hier in diesem Skript angewendet.

Statt die vier Parameter hintereinander anzugeben, können mit den Zusätzen -top, -bottom, -left und -right auch separate Regeln für die einzelnen Seiten einer Box definiert werden:

p {
    border-top: 30px solid red;
    margin-bottom: 0;
}

Grafische Darstellung des CSS-Box-Modells

Seiten-Layout

Der Abschnitt “Seiten-Layout” befindet sich noch im Aufbau.

HTML-Elemente für Layout

ElementBeschreibung
<span> </span>Allgemeiner Container für Textbereiche ohne besondere Bedeutung
<div> </div>Allgemeiner Container für Inhalte (engl. division element), in der Regel als Block dargestellt
<header> </header>Container für den Kopfbereich einer Seite (z. B. Logo, Titel)
<nav> </nav>Container für die Navigationsleiste einer Seite
<main> </main>Container für den Hauptinhalt einer Seite
<aside> </aside>Container für eine Seitenleiste neben dem Hauptinhalt (z. B. Menü)
<footer> </footer>Container für die Fußzeile einer Seite (z. B. Link zum Impressum)

Textgestaltung

Für die Textgestaltung ließe sich auch einfaches HTML nutzen – Tags wie <b> für Fettdruck, <font> zur Änderung von Schriftart, -größe und -farbe oder <sup> für hochgestellten Text existieren und werden in den meisten Browsern korrekt dargestellt. Wenn Sie eine Kapitelüberschrift in 16pt großer “Comic Sans”-Schriftart, fett und unterstrichen darstellen möchten, können Sie einfach die Tag-Kombination <font size="16pt" face="Comic Sans" color="blue"><b><u> ... </u></b></font> verwenden.

Das geht aber nur so lange gut, bis Sie diese Formatierung regelmäßig benutzen oder gar ändern möchten. Stattdessen empfiehlt es sich, diese Textteile zu einer Klasse zusammenzufassen und für diese Klasse Gestaltungsregeln in CSS festzulegen.

Aus <font size="16pt" face="Comic Sans" color="blue"><b><u> ... </u></b></font> wird dann z. B. <span class="kapitelueberschrift"> ... </span> mit der dazugehörigen CSS-Regel:

.kapitelueberschrift {
    font-family: Comic Sans;
    font-size: 16pt;
    font-weight: bold;
    text-decoration: underline;
}

Möchten Sie nun die Farbe der Kapitelüberschrift ändern, müssen Sie nicht mehr den HTML-Code an sechzehn Stellen anpassen, sondern nur den CSS-Code an einer.

Die CSS-Stilattribute für Textgestaltung lassen sich in zwei Kategorien unterteilen: diejenigen für die Formatierung, die das Aussehen eines ganzen Textteils ändern, deren Namen üblicherweise mit text- beginnen, und diejenigen für die Schriftart, die das Aussehen der einzelnen Zeichen verändern, deren Namen üblicherweise mit font- beginnen.

Textformatierung

In diese Kategorie fallen unter anderem Stilattribute bezüglich Farbe, Ausrichtung, Dekoration und Abständen.

Farbe

Das Attribut, um die Farbe des Textes in einem HTML-Element zu ändern, heißt color.

Was insbesondere gern durcheinandergebracht wird: das Attribut, um die Farbe des HTML-Elements selbst zu verändern, ist nicht color (das wäre die Textfarbe), sondern background-color.

Farben können entweder mit ihren Namen (z. B. dark-blue, hot-pink oder gainsboro) oder mit RGB(A)-Codes bezeichnet werden, wobei die Werte für den roten, grünen, blauen und transparenten (“Alpha”) Farbkanal angegeben werden, wie wir es bereits von der Codierung von Bilddaten kennen.

Die Farbwerte können entweder in dezimaler oder hexadezimaler Notation angegeben werden. In der Dezimalschreibweise notieren wur rgb(R, G, B) bzw. rgba(R, G, B, A), wobei R, G, B Zahlen zwischen 0 und 255 sein müssen (die Rot-, Grün- und Blauwerte) und A (der “Alpha”-Wert bzw. die Deckkraft) als Dezimalzahl zwischen 0 und 1 oder als Prozentangabe angegeben werden kann. In der Hexadezimalschreibweise notieren wir #RRGGBB oder #RRGGBBAA, wobei RR, GG, BB und AA jeweils zwei Hexadezimalziffern sind, die eine Zahl zwischen 0 und 255 beschreiben (z. B. #0080FF für Himmelblau).

Um alle Links in einem leicht durchscheinenden Pink einzufärben, ließe sich folgende CSS-Regel verwenden:

a {
    color: rgb(192, 1, 186, 0.75)
}

oder die äquivalente Schreibweise

a {
    color: #C001BABE;
}

Ausrichtung

Mit CSS können Texte in einem HTML-Element horizontal und vertikal ausgerichtet werden. Für die horizontale Ausrichtung des Textes in einem HTML-Element wird das Attribut text-align auf left für linksbündigen Text, right für rechtsbündigen Text, center für zentrierten Text oder justify für Blocksatz gesetzt.

Bei der Verwendung von Blocksatz mittels text-align: justify kann zusätzlich eine Methode spezifiziert werden, mit der der Text ausgerichtet wird. Mit text-justify: inter-word wird festgelegt, dass nur die Abstände zwischen den Wörtern angepasst werden sollen. Mit der Einstellung text-justify: inter-character werden auch die Abstände zwischen den einzelnen Zeichen verändert.

Die erste Zeile eines Absatzes kann mit dem Attribut text-indent eingerückt werden. Als Parameter kann entweder eine feste Länge oder eine relative Breite in Prozent angegeben werden.

Die letzte Zeile eines in Blocksatz gesetzten Textes erscheint üblicherweise linksbündig. Dies lässt sich aber mit dem CSS-Attribut text-align-last anpassen, das dieselben Werte wie text-align annehmen kann.

Die vertikale Ausrichtung ist kompliziert, weswegen an dieser Stelle ausdrücklich nicht alle Möglichkeiten erörtert werden, die CSS bietet. Es gibt ein CSS-Attribut vertical-align, das sich aber in unterschiedlichen Kontexten unterschiedlich verhält.

In Tabellenzellen kann vertical-align u. a. die Werte top, middle und bottom annehmen, um die Inhalte der Tabellenzelle an ihrem oberen oder unteren Rand bzw. in ihrer Mitte auszurichten.

Möchten Sie beispielsweise ein Icon im Kontext einer Textzeile ausrichten, gibt es diverse Möglichkeiten, die in der Abbildung unten aufgezählt sind. Hierbei muss berücksichtigt werden, woran genau sich CSS ausrichtet. Zu jeder Textzeile sind mehrere Hilfslinien definiert, die in der folgenden Abbildung dargestellt sind:

Eine Textzeile mit allen Linien, an denen CSS sich orientiert

Rot dargestellt sind hier die Basislinien für normalen, hoch- und tiefgestellten Text. Die Objekte [1], [2] und [3] in der Abbildung sind an den Basislinien ausgerichtet. Die entsprechenden CSS-Regeln sind vertical-align: baseline [1], vertical-align: super [2] und vertical-align: sub [3]. Das Objekt wird selbst mit seiner eigenen Basislinie am umgebenden Text ausgerichtet.

Die Objekte [4] und [5] sind an der Schrifthöhe ausgerichtet, also dem Abstand zwischen dem höchsten Punkt des höchsten Zeichens und dem tiefsten Punkt des tiefsten Zeichens. Die Linien für die Schrifthöhe sind blau eingezeichnet. Objekt [4] ist mit der Regel vertical-align: top am oberen Rand der Schrifthöhe ausgerichtet, Objekt [5] mit vertical-align: bottom am unteren Rand. Das Objekt [4] wird dabei mit seiner eigenen Oberkante an der Oberkante des umgebenden Texts ausgerichtet, das Objekt 5 mit seiner eigenen Unterkante an der Unterkante des umgebenden Texts.

Das Objekt [6] ist mit der Regel vertical-align: middle mittig am umgebenden Text ausgerichtet. Genauer wird der Mittelpunkt der Höhe des Objekts ausgerichtet an dem violett markierten Mittelpunkt zwischen der rot markierten Basislinie und der ebenfalls violett markierten Höhe der Kleinbuchstaben des umgebenden Texts.

Mit den Regeln vertical-align: <feste Länge> und vertical-align: <Prozentangabe> kann die Basislinie des Objekts relativ zur Basislinie des umgebenden Textes um eine angegebene Höhe verschoben werden (Objekt [7]). 100 % entsprechen dabei der Zeilenhöhe, die in der Abbildung grün markiert ist. Zu den Größenangaben siehe auch die Referenz zu Größenangaben.

Ein Inhalt soll vertikal in einem Objekt zentriert sein, aber die Größe des Objekts ist irrelevant? Dafür kann einfach das padding-Attribut benutzt werden. Mehr dazu im Abschnitt Rahmen.

Dekoration

Über-, Durch- und Unterstreichungen können mit dem Attribut text-decoration gestaltet werden. Hierzu können Parameter für die Position (overline für Überstreichung, line-through für Durchstreichung oder underline für Unterstreichung), Linienfarbe (siehe Referenz zu Farbwerten), -stil (solid für durchgezogen, dashed für gestrichelt, dotted für gepunktet, wavy für Wellenline oder double für doppelte Linie) und -breite (siehe Referenz zu Größenangaben) angegeben werden.

/* Überschriften werden mit einer 5px breiten doppelten Linie unterstrichen. */
h1 {
  text-decoration: underline double 5px;
}

/* Errata werden mit einer roten Schlangenline durchgestrichen. */
.erratum {
  text-decoration: line-through red wavy;
}

Mit dem Attribut text-shadow können den Texten Schatten hinzugefügt werden. Als Parameter können (in dieser Reihenfolge) der horizontale und vertikale Abstand des Schattens zum Text, die Schärfe des Schattens sowie dessen Farbe angegeben werden.

/* Ein leicht versetzter scharf konturierter grauer Schatten */
h1 {
    text-shadow: 3px 3px 0px gray;
}

/* Ein rotes Glühen direkt hinter dem Text */
h2 {
    text-shadow: 0px 0px 10px red;
}

/* CSS, das die Leute zu ihren Lesebrillen greifen lässt */
h3 {
    color: #00000000;
    text-shadow: 0px 0px 2px black;
}

Abstände

Die Zeilenhöhe, die u. a. für die vertikale Ausrichtung von Objekten in Textzeilen relevant ist (siehe Ausrichtung), kann mit dem Attribut line-height festgelegt werden. line-height kann als Parameter eine absolute oder relative Längeneinheit übergeben bekommen.

Empfehlenswert ist es, eine relative Angabe in Form einer Zahl ohne Maßeinheit anzugeben, z. B. line-height: 1.5. Angaben in Prozent können unerwartete und unerwünschte Ergebnisse produzieren.

Die Abstände zwischen einzelnen Zeichen und ganzen Wörtern können mit den Attributen letter-spacing und word-spacing definiert werden, die jeweils Größenangaben als Parameter erhalten. Es sind auch negative Größenangaben zulässig, um Zeilen, Wörter und Zeichen enger aneinander rücken zu lassen.

Schrift

In diese Kategorie fallen alle Attribute, die das Aussehen einzelner Zeichen, sprich Buchstaben, Zahlen o.ä. ändern.

Alle nachfolgend beschriebenen Attribute können auf einmal mit dem Attribut font gesetzt werden, etwa

font: italic small-caps bold 12px/30px Georgia, serif;

Die Reihenfolge der Parameter ist hier:

font: font-style font-variant font-weight font-size/line-height font-family;

Die genaue Verwendung dieser Parameter werden in den folgenden Abschnitten einzeln erläutert.

Schriftart

Auf verschiedenen Computern sind verschiedene Schriftarten verfügbar, je nachdem, welches Betriebssystem und welche Software dort installiert sind. Wer Microsoft Office benutzt, kann zum Beispiel u. a. auf die Schriftarten Calibri, Candara und Constantia zugreifen, auf Mac-Geräten stehen Avenir Roman und Trattatello zur Verfügung. Bei der Gestaltung von Webseiten sollte dies berücksichtigt werden, damit die Seite für alle wie gewollt aussieht, auch wenn unterschiedliche Betriebssysteme verwendet werden.

Aus diesem Grund akzeptiert das CSS-Attribut font-family nicht nur eine Schriftart als Parameter, sondern gestattet es auch, mehrere anzugeben. Beispielsweise sind für diesen Text folgende Schriftarten vorgesehen:

body {
    font-family: -apple-system, system-ui, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}

Diese Regel wird von links nach rechts ausgewertet, wobei die erste verfügbare Schriftart ausgewählt wird. Einige dieser Einträge stehen für konkrete Schriftarten: "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell und "Helvetica Neue". Zu beachten ist hier, dass Namen, die Leerzeichen enthalten, in Anführungszeichen gesetzt werden müssen.

Andere Einträge stehen für Schriftfamilien. Zum Beispiel bedeutet system-ui, dass die Standard-Schriftart des Betriebssystems verwendet werden soll, auf dem die Seite geöffnet wird. Einige ältere Versionen des Safari-Browsers verwenden stattdessen -apple-system.

Der letzte Eintrag (hier: sans-serif) wird als fallback (engl. für “Rückfallebene”) bezeichnet. Er legt fest, dass irgendeine serifenlose Schriftart verwendet werden soll – egal ob Arial, Calibri oder Liberation Sans – falls keine der vorher erwähnten Schriftarten verfügbar ist. Ein solcher Fallback-Eintrag sollte auf jeden Fall einsetzt werden, da er wenigstens ein Mindestmaß an Kontrolle über die Gestaltung der Schriftart gewährleistet. Mögliche Fallback-Parameter sind sans-serif (eine Schriftart ähnlich z. B. Arial), serif (ähnlich z. B. Times New Roman), monospace (für Schriftarten, in denen alle Zeichen gleich breit sind, wie z. B. Courier*_), cursive (für Schreibschrift) und fantasy (für dekorative und verspielte Schriftarten).

Nicht verwechseln: font-family: cursive bedeutet nicht, dass die Schrift kursiv gesetzt wird. Dazu muss font-style: italic angegeben werden.

Schriftgröße

Mit dem Attribut font-size kann die Schriftgröße von Anzeigeelementen festgelegt werden. Als Größenangabe können folgende Werte eingesetzt werden:

  • eins der absoluten Schlüsselwörter xx-small, x-small, small, medium, large, x-large und xx-large – die genaue Gestaltung bleibt dann dem Browser überlassen
  • eins der relativen Schlüsselwörter larger oder smaller – also größer oder kleiner als der Text der umliegenden HTML-Elemente.
  • eine absolute Größenangabe in mm, pt, in(ch) oder px. Bevorzugt sollten px verwendet werden, da alle anderen Einheiten der Interpretation des einzelnen Browsers unterliegen
  • eine relative Größenangabe in %, em, ex oder rem. Hierbei beziehen sich %, em und ex auf die Schriftgröße des umschließenden HTML-Elements, rem auf die Schriftgröße des HTML-Bodys. 1em entspricht dabei der normalen Schriftgröße, 1ex der Höhe der Kleinbuchstaben.

Schriftstil

Um Texte durch Fettdruck oder Kursivdruck hervorzuheben, können die Attribute font-style und font-weight benutzt werden.

font-style kann die Werte normal, italic für kursive Schrift und oblique für schräge Schrift annehmen. In vielen Schriftarten sehen kursive und schräge Schrift identisch aus. Der Unterschied ist, dass für Schrägschrift einfach die normalen, aufrechten Zeichen etwas geneigt werden, während in einigen Schriften für Kursivschrift eigene Zeichen verwendet werden. Die folgende Abbildung demonstriert den Unterschied:

Die Schriftstile normal, kursiv und schräg

Für Fett- oder Leichtdruck kann das Attribut font-weight verwendet werden. Als Parameter können entweder die festen Werte normal, bold (fett), lighter (leichter als der umgebende Text) und bolder (fetter als der umgebende Text) verwendet werden. Präziser kann der Wert mit einer Zahlenangabe zwischen 1 und 1000 spezifiziert werden, wobei ältere Browser nur die Werte 100, 200, 300, 400 (entspricht der Angabe normal) 500, 600, 700 (entspricht der Angabe bold), 800 und 900 unterstützen und alle anderen Angaben gerundet werden. Für relative Angaben, also lighter und bolder werden nur die Gewichte 100, 400, 700 und 900 berücksichtigt.

Die Schriftgewichte von 100 bis 900

Noch ausgefallenere Textgestaltung erlaubt das Attribut font-variant, mit dem unter anderem die Verwendung von besonderen Schriftschnitten für Kapitälchen und Großbuchstaben definiert werden kann.1 Dieses Attribut ist allerdings nicht mit allen Schriftarten kompatibel und sollte daher vorsichtig eingesetzt werden. Einige Werte, die font-variant annehmen kann, sind:

  • small-caps oder petite-caps für Kapitälchen, wobei die Großbuchstaben ihre originale Höhe behalten
  • all-small-caps oder all-petite-caps für Kapitälchen, wobei auch die Großbuchstaben verkleinert werden
  • unicase, wobei nur die Großbuchstaben durch Kapitälchen ersetzt werden und die Kleinbuchstaben ihre Größe behalten2

Die möglichen Schrift-Varianten

Referenz

Attribute

AttributBeschreibungWerte
colorTextfarbeFarbwert
background-colorHintergrundfarbeFarbwert
font-familySchriftartName(n) der Schriftart, z. B. Arial, Liberation Sans, sans-serif3
font-sizeSchriftgrößeGrößenangabe
font-styleSchriftstilnormal, italic (kursiv)
font-weightSchriftdickenormal, bold (fett), Zahlenwert zwischen 1 und 1000 (normal entspricht 400, fett 700)
text-alignHorizontale Textausrichtungleft, right, center, justify (Blocksatz)
vertical-alignVertikale Textausrichtungtop, bottom, middle
border-colorFarbe des RahmensFarbwert
border-styleLinienstil des Rahmensz. B. solid (einfache Linie), double (doppelte Linie), dotted (gepunktet), dashed (gestrichelt), none
border-widthBreite des RahmensGrößenangabe
marginAußenabstand zu allen SeitenGrößenangabe
margin-top/-bottom/-left/-rightAußenabstand oben, unten, links, rechtsGrößenangabe
paddingInnenabstand zu allen SeitenGrößenangabe
padding-top/-bottom/-left/-rightInnenabstand oben, unten, links, rechtsGrößenangabe
width, heightBreite, HöheGrößenangabe
list-style-typeAufzählungsstil oder -symbol für Listenelementecircle (Kreis), disc (gefüllter Kreis), square (gefülltes Quadrat), decimal (nummeriert), lower-/upper-roman (römische Ziffern), none, Zeichenkette (z. B. "* ")

Rahmenstile lassen sich auch in Kurzform mit dem Stilattribut border festlegen, das drei Argumente erwartet: Breite, Linienstil und Farbe (z. B.: border: 1px solid black;). Bei allen Rahmen-Stilattributen kann border auch durch border-top, border-left usw. ersetzt werden, um einen Rahmenstil für eine bestimmte Seite festzulegen.

Farbwerte

Farbwerte lassen sich in CSS als dezimale RGB-Werte oder RGB-Werte im Hexadezimalformat angeben (jeweils mit 8 Bit pro Farbkanal). Häufig verwendete Farbwerte sind auch durch einen Namen spezifiziert, der alternativ angegeben werden kann (siehe Farbreferenz bei W3Schools):

  • Farbname, z. B.: red, gray, yellow
  • RGB-Werte (dezimal), z. B.: rgb(255, 0, 0), rgb(128, 128, 128), rgb(255, 255, 0)
  • RGB-Werte (hexadezimal), z. B.: #ff0000, #808080, #ffff00

Größenangaben

Größenangaben (z. B. Schriftgrößen, Höhe und Breite von Elementen, Linienbreiten, Abstände) lassen sich in Pixeln, in absoluten Maßeinheiten oder als relative Größen bzgl. der Größe oder Schriftgröße des Elternelements angeben:

  • Wert in Pixeln, z. B.: 1px, 50px
  • Absoluter Wert in cm/mm,4 z. B.: 1cm, 1000mm
  • Wert relativ zur Größe des Elternelements, z. B.: 50% (halb so groß wie das Elternelement)
  • Wert relativ zur Schriftgröße des Elternelements, z. B.: 0.5em (halb so groß wie die Schriftgröße des Elternelements)
  • Wert relativ zur Schriftgröße des <html>-Elements, z. B.: 2rem (doppelt so groß wie die Schriftgröße des <html>-Elements)

Wird bei einer Größenangabe ein numerischer Wert ohne Einheit verwendet, wird dieser in der Regel als Wert in Pixeln interpretiert.

Zum Gestalten von Webseiten, die am Bildschirm betrachtet werden, sollten möglichst keine absolute Maßeinheiten wie cm verwendet werden, da Bildschirmgrößen stark variieren können und unterschiedlich große Bildschirme meist nicht von der gleichen Entfernung aus betrachtet werden. Stattdessen sollten hier möglichst nur die Einheiten px oder em/rem verwendet werden, die auf der Einheit Pixel basieren.

Validierung

Für CSS-Daten gilt wie für HTML (und formale Sprachen im Allgemeinen), dass sie nur dann valide sind, wenn sie die oben beschriebenen formalen Regeln einhalten, die in den CSS-Standards des W3C genauer spezifiziert sind.5

Um CSS-Dokumente auf Fehler zu überprüfen, können CSS-Validatoren verwendet werden, die auf ähnliche Weise wie HTML-Validatoren funktionieren, indem sie das Dokument auf die Einhaltung der aktuellen CSS-Standards überprüfen.

Tool: Um zu überprüfen, ob ein CSS-Dokument valide ist, kann der Online-Validator des W3C verwendet werden: https://jigsaw.w3.org/css-validator


  1. font-variant ist ein sehr umfangreiches Attribut, an dessen Oberfläche hier nur gekratzt wird. Eine vollständige Dokumentation findet sich auf den Seiten des Mozilla Developer Network: font-variant ↩︎

  2. Falls der Text wie auf dem Cover von Frank Schätzings “Der Schwarm” aussehen soll. ↩︎

  3. Werden mehrere Schriftarten durch Komma getrennt angegeben, wird die erste Schriftart zur Darstellung gewählt, die auf dem System vorhanden ist.
    Wird einer der folgenden generischen Schriftartbezeichner angegeben, wählt der Browser selbst eine geeignete vorhandene Schriftart zur Darstellung aus: sans-serif (serifenlose Schrift), serif (Serifenschrift), monospace (Festbreitenschrift), cursive (Schreibschrift). ↩︎

  4. Daneben gibt es weitere absolute Maßeinheiten, die aus dem internationalen Gebrauch (in für Inch), oder der Typographie (pc für Pica, pt für Point) bekannt sind. Das Umrechnungsverhältnis zwischen diesen Maßeinheitn ist definiert als: 1 in = 2.54 cm = 25.4 mm = 6 pc = 72 pt ↩︎

  5. siehe offizielle Homepage des W3C zu CSS: https://www.w3.org/Style/CSS ↩︎

3. Algorithmik


3.1 Einstieg in die Algorithmik

Das Online-Skript zum Thema “Algorithmik” befindet sich zur Zeit noch im Aufbau.

Einleitung

Algorithmen – also abstrakte Beschreibungen von Programmen oder allgemeiner: Lösungsverfahren für bestimmte Aufgabenstellungen – sind uns bereits in mehreren Zusammenhängen begegnet: In der visuellen Programmierung haben wir selbst Algorithmen entwickelt und in der Programmiersprache Scratch umgesetzt, etwa um eine Spielfigur zu steuern oder ein Quiz zu entwickeln, im Kapitel “Informationsdarstellung” wurden Algorithmen zum Berechnen von Prüfziffern für Barcodes oder zur Datenkompression behandelt.

In diesem Kapitel werden wir Algorithmen unter abstrakteren Gesichtspunkten betrachten, also welche Eigenschaften Algorithmen haben, welche grundlegenden Strategien sich in Algorithmen finden lassen, und wie sich Algorithmen unabhängig von einer konkreten Programmiersprache formulieren lassen.

Dieser allgemeinere Blick ist sinnvoll, da die Bildungsziele, die mit den Themen “Programmierkompetenz” und “Algorithmik” verknüpft sind, weniger auf rein technisches Verständnis abzielen, also etwa das Beherrschen einzelner Programmiersprachen, sondern auf die Förderung des sogenannten “algorithmischen Denkens” (Computational Thinking) als Metakompetenz.

So heißt es in den Fachanforderungen in der Einleitung zum inhaltsbezogenen Kompetenzbereich “Algorithmen und Programmierung”:

Indem die Schülerinnen und Schüler Handlungsabläufe in natürlicher Sprache strukturiert darstellen, erlernen sie mit Kontrollstrukturen die Grundelemente imperativer Programme sowie des algorithmischen Denkens (Computational Thinking).

Computational Thinking

Der Begriff “Computation Thinking”1 (oft als “Informatisches Denken” oder “Algorithmisches Denken” übersetzt) bezeichnet kurz zusammengefasst eine Kompetenz zum (meist maschinellen) Lösen komplexer Probleme. Es beschreibt einen gedanklichen Prozess zur Lösungsplanung, der darauf basiert, Problemstellungen und ihre Lösungen so darzustellen, dass die Problemlösungen auch durch eine Maschine, z. B. einen Computer, durchgeführt werden können. Dazu wird der Lösungsprozess in immer kleinere Teilschritte zerlegt, bis nur noch maschinell durchführbare Grundstrukturen übrigbleiben – also etwa elementare Rechenoperationen bzw. Anweisungen, Sequenzen, Wiederholungen und Fallunterscheidungen.

Computational Thinking wird manchmal fälschlicherweise mit dem Programmieren gleichgesetzt, geht aber als Denkmodell zum Lösen komplexer Probleme darüber hinaus und gilt heute weitestgehend übereinstimmend als Schlüsselkompetenz. Auf der anderen Seite ist aber auch klar, dass Programmierenlernen und der Kompentenzerwerb des “Computational Thinking” intrinsisch miteinander verwoben sind – das Erlernen einer Programmiersprache stellt einen wichtigen Zugang zum “Computational Thinking” dar, während andersherum der strukturierte Problemlöseprozess des “Computational Thinking” eine wesentliche Voraussetzung zum erfolgreichen Entwickeln von Programmen ist.

Was ist ein Algorithmus?

Der Begriff “Algorithmus” lässt sich folgendermaßen definieren:

Ein Algorithmus ist eine eindeutige Handlungsvorschrift zur Lösung eines Problems, die aus endlich vielen wohldefinierten Einzelschritten besteht. Dabei wird beschrieben, wie eine Eingabe Schritt für Schritt in eine Ausgabe überführt wird.

Die Eingabedaten beschreiben dabei das gegebene Problem und werden durch den Algorithmus verarbeitet, dessen Ausgabedaten die Lösung beschreiben. Die Verarbeitungsvorschrift – also der Algorithmus – muss dabei so präzise und eindeutig formuliert sein, dass selbst eine Maschine (z. B. ein Computer) sie durch stures Befolgen der Befehle durchführen kann.

Image

Alltagsbeispiele, die oft als Analogien für Algorithmen verwendet werden, sind etwa Kochrezepte oder Spielanleitungen.2

Ein Algorithmus lässt sich quasi als abstrakte Form eines Programms auffassen. Programme wiederum setzen Algorithmen in einer konkreten Programmiersprache um, sie implementieren die Algorithmen.

Eigenschaften von Algorithmen

Damit eine Handlungsvorschrift als Algorithmus gilt, muss sie mehrere grundlegende Eigenschaften erfüllen, die sicherstellen, dass auch eine rein maschinelle Ausführung möglich ist:

  • Allgemeinheit: Das Lösungsverfahren muss eine ganze Klasse von Problemen lösen und nicht nur eine spezielle Probleminstanz.
    Eine Handlungsanweisung, die angibt, wie die Wurzel der Zahl 81 berechnet wird, wäre nicht allgemein (hier würde es prinzipiell reichen, einfach den Wert 9 zurückzugeben), während eine Anleitung, wie die Wurzel einer beliebigen positiven Zahl berechnet wird, als allgemein gelten würde.

  • Ausführbarkeit: Jeder Einzelschritt und jede einzelne Anweisung, die im Algorithmus vorkommt, muss ausführbar sein. Dazu muss jede Anweisung insbesondere so formuliert sein, dass klar ist, wie sie durchgeführt werden muss. Ist eine Anweisung dagegen zu komplex, muss sie gegebenenfalls durch einen Unteralgorithmus in Form von weiteren Einzelschritten präzisiert werden.
    Hierbei spielt die Frage eine Rolle, was als “elementare Anweisung” gilt. Dazu muss berücksichtigt werden, durch wen und in welchem Kontext der Algorithmus ausgeführt wird, und was mit den hierbei verwendeten Objekten gemacht werden kann: Bei einem Computerprogramm ist die Menge der ausführbaren Anweisungen durch die verwendete Programmiersprache und die Methoden der Datenstrukturen beschränkt. Wenn wir unserem Hund (oder vielleicht auch einem Roboterhund) Kunststücke beibringen möchten, müssen wir uns auf die Befehle beschränken, die er versteht.

  • Endlichkeit: Die Beschreibung der Handlungsvorschrift muss eine endliche Länge besitzen, also mit endlich vielen Anweisungen auskommen, beispielsweise als Text auf einer Seite. Diese Eigenschaft wird auch als Finitheit oder auch spezifischer als “statische Endlichkeit” bezeichnet, um zu betonen, dass hier die Begrenztheit der Beschreibung des Algorithmus gemeint ist und nicht seiner Ausführungsdauer.

  • Eindeutigkeit: Die Abfolge der einzelnen Schritte in der Handlungsvorschrift muss genau festgelegt sein. Zu jedem Zeitpunkt der Ausführung muss also klar sein, welcher Schritt als Nächstes ausgeführt wird. Das bedeutet nicht, dass es nur eine einzige Reihenfolge gibt, in der die Einzelschritte eines Algorithmus durchlaufen werden: Durch bedingte Wiederholungen und Fallunterscheidungen sind je nach Eingabe unterschiedliche Abfolgen möglich. Dabei muss aber klar festgelegt sein, wann und unter welcher Bedingung welcher Weg gewählt wird.

Verfahren, die zufallsbasiert Entscheidungen fällen, zählen aber auch als Algorithmen. Der Begriff der “Eindeutigkeit” wird in der theoretischen Informatik daher noch weiter differenziert in Determinismus und Determiniertheit.

  • Determinismus: Ein Algorithmus gilt als deterministisch, wenn er bei wiederholter Ausführung mit der gleichen Eingabe immer den gleichen Ablauf hat – also dieselben Einzelschritte in derselben Reihenfolge durchläuft.

  • Determiniertheit: Der Begriff der Determiniertheit ist weniger streng als Determinismus: Ein Algorithmus gilt als determiniert, wenn er bei wiederholter Ausführung mit der gleichen Eingabe immer das gleiche Ergebnis liefert, aber nicht unbedingt immer auf demselben Weg. Das kann etwa der Fall sein, wenn bestimmte Entscheidungen im Algorithmus zufallsbasiert getroffen werden, aber trotzdem sichergestellt ist, dass der Algorithmus immer die richtige (und damit gleiche) Lösung berechnet.3

  • Korrektheit: Nicht zuletzt muss das Lösungsverfahren für jede Eingabe die richtige Lösung liefern. Die Korrektheit eines Algorithmus kann exemplarisch anhand von repräsentativen Testfällen gezeigt werden oder mit formalen Methode theoretisch bewiesen werden. Formale Beweise für Algorithmen sind eines der wichtigsten Themengebiete der Theoretischen Informatik, setzen aber höhere mathematische Kenntnisse voraus.

Prozessbezogene Eigenschaften

Ein Algorithmus lässt sich neben seiner Beschreibung auch auf Aspekte untersuchen, die sich auf seinen Ausführungsprozess beziehen.

  • Terminiertheit: Ein Algorithmus sollte für jede Eingabe nach einer endlichen Rechenzeit zu einer Lösung kommen – er muss also nach endlich vielen Einzelschritten terminieren. Anderenfalls hätte der Algorithmus keinen praktischen Nutzen.

  • Dynamische Endlichkeit: Der Speicherplatz, der während der Ausführung für Variablen und Datenstrukturen benötigt wird, muss ebenfalls endlich sein. In der Praxis muss der Speicherbedarf darüber hinaus in einem angemessenen Rahmen bleiben in Bezug auf das System, auf dem der Algorithmus als Programm ausgeführt werden soll (z. B. auf einem Handy, PC oder einem Hochleistungsrechner).

  • Komplexität: Der theoretische Aufwand an Rechenzeit und Speicherbedarf eines Algorithmus wird unter dem Begriff “Komplexität” zusammengefasst.

  • Effizienz: Ein Algorithmus gilt als effizient, wenn seine Komplexität – also Rechenzeit und Speicherbedarf in Abhängigkeit von der Größe des Problems – “gut” ist, also so niedrig wie nötig oder möglich für die Problemklasse, die er löst.

Beschreibung von Algorithmen

Wir wissen bereits aus den vorigen Lektionen (z. B. aus der Einführung in die visuelle Programmierung), dass sich Algorithmen mit Hilfe von bestimmten Grundbausteinen konstruieren lassen, nämlich aus:

  • einzelnen Anweisungen
  • Sequenzen von Grundbausteinen
  • Kontrollstrukturen

Die sogenannten Kontrollstrukturen sind spezielle Anweisungen zur Ablaufsteuerung weiterer Grundbausteine. Dazu gehören im Wesentlichen:

  • Wiederholungen
  • Fallunterscheidungen

Bei Wiederholungen werden bestimmte Grundbausteine mehrmals nacheinander ausgeführt, wobei es in der Regel von bestimmten angegebenen Bedingungen abhängt, wie viele Wiederholungsschritte durchgeführt werden. Bei Fallunterscheidungen (bzw. bedingten Anweisungen) werden bestimmte Grundbausteine in Abhängigkeit von bestimmten Bedingungen ausgeführt oder nicht. Daneben kommen als weitere Bestandteile von Algorithmen Ausdrücke vor, zum Beispiel mathematische Ausdrücke oder Vergleiche, die unter anderem zur Berechnung von Parameterwerten für Anweisungen, Werten für Variablen oder als Bedingungen der Kontrollstrukturen dienen.

Welche Anweisungen konkret ausführbar sind, hängt dabei wie oben erwähnt vom Kontext ab (in der Programmierung z. B. von der verwendeten Programmiersprache). Variablenzuweisungen – also das Speichern eines Wertes, der durch einen Ausdruck berechnet wird, in einer Variablen – sind Anweisungen, die in der imperativen Programmierung in der Regel immer zur Verfügung stehen. In der objektbasierten Programmierung (z. B. in Scratch) gibt es daneben größtenteils Anweisungen, um den Zustand von Objekten zu ändern, sowie Ausdrücke, um ihren Zustand abzufragen (z. B. Position einer Figur auf der Zeichenfläche abfragen oder eine Figur auf der Zeichenfläche um 10 Pixel verschieben).

Beispiel: Code knacken

Als praktisches Beispiel für den Entwurf und die Analyse von Algorithmen soll das folgende Problem betrachtet werden: Angenommen, Sie haben Ihr Fahrrad mit einem Zahlenschloss gesichert, in dem vier Stellen auf die Ziffern 0 bis 9 gedreht werden können. Leider haben Sie die richtige Zahlenkombination vergessen. Es soll nun ein Algorithmus entwickelt werden, der beschreibt, wie sich durch systematisches Überprüfen aller Kombinationen von 0000 bis 9999 die richtige Kombination ermitteln lässt.

Dabei lassen sich die folgenden Aktionen durchführen: Die einzelnen Ziffern können durch Vor- und Zurückdrehen der einzelnen Räder eingestellt werden, und es kann durch Ziehen am Bügel geprüft werden, ob das Schloss für die gerade eingestellte Zahl offen ist.

Tool: In dieser interaktiven Anzeige können Sie Ihren Algorithmus testen.4 Klicken Sie die Schaltfläche an, um zu versuchen, das Schloss zu öffnen. Die Schaltfläche setzt das Schloss auf eine zufällige Kombination zurück und legt eine neue Kombination als Lösung fest. Mit den Schaltflächen und können Sie die Anzahl der Stellen verringern (um das Problem zu vereinfachen) oder erhöhen.

Dabei sollten Sie maximal für zwei Stellen versuchen, manuell die richtige Lösung zu finden, da das Ermitteln der richtigen Kombination sehr langwierig werden kann – bei 4 Stellen müssen immerhin 10000 verschiedene Kombinationen ausgetestet werden. Solche Aufgaben sind also eher für Maschinen geeignet, weswegen der Algorithmus möglichst präzise und eindeutig formuliert werden sollte.

Wir untersuchen als Nächstes verschiedene Vorschläge, wie ein Lösungsverfahren formuliert werden könnte, und überprüfen daran jeweils die grundlegenden Eigenschaften von Algorithmen.

1. Ansatz

Der erste Vorschlag, ein Lösungsverfahren zu beschreiben, besteht aus einem einzigen Satz:

  • Teste nacheinander jede Kombination der Ziffern 0 bis 9.

Diese Beschreibung ist allgemein, da sie unabhängig davon funktioniert, welches Ergebnis das richtige ist – wenn dagegen nur vorgeschlagen würde “Teste die Zahl 8361” kann das für ein bestimmtes Zahlenschloss die richtige Lösung liefern, im Allgemeinfall aber nicht. Die Beschreibung ist natürlich auch endlich, da sie in einem Satz formuliert wird.

Sie ist allerdings weder ausführbar noch eindeutig: Für Menschen ist die Anleitung aus dem Kontext heraus zwar verständlich, für eine Maschine ist aber im Detail unklar, wie die Einzelschritte ausgeführt werden sollen. Darüber hinaus ist die Reihenfolge der Einzelschritte unklar.

Image

2. Ansatz

Um das Lösungsverfahren möglichst eindeutig und ausführbar zu beschreiben, sollten wir uns also auf einfache, elementare Anweisungen beschränken, die darüber hinaus so formuliert sind, dass klar wird, in welcher Reihenfolge, in Abhängigkeit von welchen Bedingungen und wie oft die Einzelschritte ausgeführt werden sollen:

  • Stelle Code 0000 ein
  • Versuche Schloss zu öffnen
  • Wiederhole bis Schloss offen ist:
    • Stelle nächsten Code ein
    • Versuche Schloss zu öffnen

Diese Anleitung erfüllt die Mindestanforderungen an einen Algorithmus: Sie ist endlich, allgemein, eindeutig und ausführbar, sofern die enthaltenen Anweisungen “stelle Code 0000 ein”, “stelle nächsten Code ein” für das ausführende System verständlich genug sind – anderenfalls müssten wir diese Anweisungen präzisieren, indem wir uns auf elementarste Anweisungen beschränken, was wir im nächsten Abschnitt noch vertiefen werden.

Image

3. Ansatz

Der nächste Vorschlag beinhaltet zufallsbasierte Entscheidungen:

  • Wiederhole bis Schloss offen ist:
    • Stelle zufälligen Code ein
    • Versuche Schloss zu öffnen

Auch diese Beschreibung stellt einen Algorithmus dar, sofern die Anweisung “stelle zufälligen Code ein” als ausführbar gilt. Hier wird also ein Zufallszahlengenerator benötigt. Außerdem ist die Anzahl der Wiederholungen hier nicht in jedem Ablauf für dieselbe Eingabe (das heißt hier: für dasselbe Schloss) gleich, der Algorithmus ist also nicht deterministisch. Auf der anderen Seite ist zu jedem Zeitpunkt klar, welche Anweisung als nächste ausgeführt wird, der Algorithmus ist also trotzdem eindeutig.

Diese Beschreibung stellt also ein typisches Beispiel für einen randomisierten Algorithmus dar, also einen Algorithmus, in dem einzelne Entscheidungen zufallsbasiert getroffen werden.3 Theoretisch kann es passieren, dass dieser Algorithmus nie terminiert (wenn zufälligerweise die richtige Zahlenkombination niemals gewählt wird), was aber extrem unwahrscheinlich ist. Wir können davon ausgehen, dass der Algorithmus in endlicher Zeit die richtige Lösung liefert, er ist also determiniert.

Image

4. Ansatz

Betrachten wir noch einen weiteren Ansatz zum Einstellen der richtigen Ziffern:

  • Wiederhole für 1. bis 4. Stelle:
    • Wiederhole bis Ziffer an dieser Stelle richtig ist:
      • Stelle nächste Ziffer ein
  • Öffne Schloss

Dieses Verfahren ist zwar allgemein, endlich und eindeutig formuliert, für die gegebene Problemstellung aber nicht ausführbar, da die Überprüfung, ob eine Ziffer an einer bestimmten Stelle richtig ist, nicht möglich ist. Wir können nur überprüfen, ob die gesamte eingestellte Zahlenkombination richtig ist, indem wir versuchen, das Schloss zu öffnen.

Auf den Kontext objektbasierter Programmierung übertragen bedeutet das, dass wir hier Methoden von Objekten benötigen würden, die von diesen nicht unterstützt werden.

Image

Zwischenfazit

Ein Algorithmus sollte so einfach wie möglich, aber so genau wie nötig formuliert werden. Hier finden Sie Hinweise, wie dazu am besten vorgegangen werden sollte.

  • Zuerst sollte überlegt werden, welche elementaren Anweisungen und Werteabfragen verwendet werden dürfen, um das Problem zu lösen. Das hängt natürlich stark vom gegebenen Problemkontext ab.
  • Diese elementaren Anweisungen werden dann mit den grundlegenden Kontrollstrukturen (“wiederhole solange/bis …”, “falls … mache … sonst …”) kombiniert, um eine eindeutige Abfolge von Einzelschritten zu formulieren.
  • Daneben dürfen in der Regel immer auch eigene Variablen zur Problemlösung verwendet werden.5 Variablenzuweisungen sind also Anweisungen, die wir unabhängig vom konkreten Problemkontext immer verwenden dürfen.
  • Außerdem dürfen immer logische Ausdrücke (z. B. Vergleich, Verknüpfung von Vergleich mit “und”, “oder”) und mathematische Ausdrücke (z. B. Addition, Multiplikation, Funktionsaufrufe) zur Berechnung von Parameterwerten, Variablenwerten oder Bedingungen verwendet werden. Solche Ausdrücke können als allgemein bekannt gelten oder vereinbart werden.

Komplexere Anweisungen können in Form von Unteralgorithmen formuliert werden, ggf. auch mit Parametern (vgl. Unterprogramme in Scratch). Das macht besonders dann Sinn, wenn solche komplexeren Anweisungen an mehreren Stellen im Algorithmus verwendet werden.

Auf diese Weise erhalten wir klar verständliche, wenn auch sprachlich nicht ganz natürlich formulierte Handlungsanweisungen. Diese stark reduzierte und formalisierte Sprache wird als Pseudocode bezeichnet, da sie schon relativ nahe am Code einer Programmiersprache liegt.

Vertiefung: Code knacken

Wir betrachten hier noch einmal den 2. Ansatz für das Ermitteln der richtige Zahlenkombination:

  • Stelle Code 0000 ein
  • Versuche Schloss zu öffnen
  • Wiederhole bis Schloss offen ist:
    • Stelle nächsten Code ein
    • Versuche Schloss zu öffnen

Um das Verfahren in allen Einzelschritten möglichst klar zu beschreiben, beschränken wir uns nun aber bei den elementaren Anweisungen auf die folgenden:6

Drehe Stelle ... weiter Versuche Schloss zu öffnen

In Bedingungen und anderen Ausdrücken beschränken wir uns bei den Zustandsabfragen für dieses Problem auf die folgenden:7

  • Ziffer an Stelle ... ist eine Ziffer zwischen 0 und 9.
  • Zustand des Schlosses ist “offen” oder “geschlossen”.

Zur Angabe der Stellenposition sollte hierbei jeweils eine Zahl zwischen 1 und 4 gewählt werden, z. B. “Drehe Stelle 1 weiter”.

Die Anweisungen “stelle Code 0000 ein” und “stelle nächsten Code ein” gelten unter diesen Voraussetzungen als zu komplex und damit als nicht ausführbar. Daher werden wir diese Anweisungen durch Unteralgorithmen präzisieren.

Unteralgorithmen

Code 0000 einstellen

Formulieren wir als Erstes einen Unteralgorithmus für die Anweisung “stelle Code 0000 ein”, d. h. setze alle Stellen auf 0:

  • Wiederhole für 1. bis 4. Stelle:
    • Wiederhole solange die aktuelle Stelle ≠ 0 ist:
      • Drehe die aktuelle Stelle weiter

Image

Formulierungen wie “die aktuelle Stelle” können allerdings verwirrend sein und für die maschinelle Ausführung zu unklar. Besser ist es, solche Formulierungen durch Variablen zu präzisieren:

  • Setze n auf 1
  • Wiederhole 4-mal:
    • Wiederhole solange n-te Stelle ≠ 0:
      • Drehe n-te Stelle weiter
    • Erhöhe n um 1

Hier wird eine Variable n verwendet, um die “aktuelle Stelle” zu kennzeichnen: Wir beginnen bei Stelle 1 und wiederholen für alle Stellen 1 bis 4. Hier ist zu jedem Zeitpunkt klar, was mit der “aktuellen Stelle” gemeint ist.

Nächsten Code einstellen

Sehen wir uns nun an, wie die Anweisung “stelle nächsten Code ein” mit Hilfe elementarer Anweisungen formuliert werden kann: Im Prinzip soll hier nur die letzte Stelle einmal weitergedreht werden. Dabei müssen wir aber Überträge berücksichtigen: Wenn wir die letzte Stelle auf 0 drehen (z. B. von 1899 auf 1890), müssen wir ebenfalls die vorletzte Stelle weiterdrehen (von 1890 auf 1800). Das muss wiederholt werden (1800 auf 1900), bis wir eine Stelle nicht auf 0 gedreht haben, sondern auf eine andere Ziffer.

Image

Wir müssen also einen Algorithmus zur “Addition von 1 mit Übertrag” formulieren:

  • Setze n auf 4
  • Drehe n-te Stelle weiter
  • Wiederhole solange n-te Stelle = 0:
    • Verringere n um 1
    • Drehe n-te Stelle weiter

Wir beginnen bei der letzten Stelle, drehen diese einmal weiter, und solange wir beim Weiterdrehen 0 als neuen Wert der Stelle erhalten wiederholen wir diesen Prozess für die jeweils vorige Stelle. Die Wiederholung lässt sich auch äquivalent mit “bis ungleich 0” statt “solange gleich 0” formulieren:

  • Wiederhole bis n-te Stelle ≠ 0:

Der so formulierte Algorithmus mit Unteralgorithmen ist nun bezüglich der festgelegten elementaren Anweisungen und Zustandsabfragen ausführbar und könnte so auch in einer einfachen Programmiersprache umgesetzt werden.

Image

Sie haben sich eventuell schon gefragt, ob es wirklich nötig ist, zu Beginn die Zahlenkombination 0000 einzustellen. Die Antwort lautet: nein. Im Prinzip können wir von jeder beliebigen Zahlenkombination aus starten – nach spätestens 10000 Versuchen müssen wir den richtigen Code gefunden haben. In diesem Fall müssten wir den Unteralgorithmus “stelle nächsten Code ein” aber anpassen, da es dann passieren kann, dass wir von der Zahlenkombination 9999 aus weiterdrehen.

Warum ist dieser Fall problematisch? Es werden alle Stellen von der 4-ten aus jeweils von 9 auf 0 weitergedreht, bis die 1. Stelle erreicht und auf 0 gedreht wird. Laut Algorithmus wird nun n auf 0 gesetzt und die 0-te Stelle weitergedreht, was Unsinn ist. Dieser Sonderfall ist für Menschen klar (an dieser Stelle wird abgebrochen), für eine Maschine aber nicht: Bei der Programmausführung wird hier in der Regel ein Fehler auftreten, da es keine “0-te Stelle” gibt. Wir müssen die Abbruchbedingung im Algorithmus für diesen Sonderfall also explizit anpassen:

  • Wiederhole solange n-te Stelle = 0 und n > 1:

Auf diese Weise wird die Wiederholung auch dann beendet, wenn die 1. Stelle erreicht und weitergedreht worden ist (auch wenn sie dabei ebenfalls auf 0 gedreht wird).8

Algorithmus verallgemeinern

Wir haben so also einen eindeutigen und mit grundlegendsten Anweisungen ausführbaren Algorithmus zum Ermitteln der richtigen Zahlenkombination gefunden, der das Problem allgemein für alle Zahlenschlösser mit 4 Stellen löst – unabhängig davon, was die richtige Kombination ist. Was aber, wenn wir ein Zahlenschloss mit mehr Stellen, ein Schloss mit Buchstabenkombination oder gar ein Schloss mit Farbkombination haben?

Image

Zunächst verallgemeinern wir das Lösungsverfahren weiter, indem wir die Beschränkung auf 4 Stellen aufheben und stattdessen die Anzahl der Stellen als frei wählbaren Eingabeparameter angeben. Nennen wir diesen Wert beispielsweise Anzahl, so muss in den Beschreibungen des Algorithmus und seiner Unteralgorithmen aus diesem Abschnitt jedes Vorkommen des Werts 4 durch den Wert von Anzahl ersetzt werden. Nun lassen sich mit demselben Algorithmen auch Zahlenschlösser mit 3, 5 oder 100 Ziffern knacken, indem einfach der Wert für Anzahl variiert wird.

Ähnlich können wir den Algorithmus so verallgemeinern, dass wir nicht auf die Ziffern 0 bis 9 für die einzelnen Stellen beschränkt sind, sondern beliebige Zeichen für die Kombination verwenden können – etwa die Buchstaben A bis Z, verschiedene Farben oder mysteriöse Symbole. In diesem Fall muss das Zeichen 0 in der Abbruchbedingung des Unteralgorithmus “stelle nächsten Code ein” einfach durch ein beliebiges (z. B. das erste) der verwendeten Zeichen ersetzt werden, das hier mit dem Parameter Startzeichen bezeichnet wird:9

  • Wiederhole solange n-te Stelle = Startzeichen und n > 1:

Für ein Buchstabenschloss mit 8 Stellen würden wir den Algorithmus dann mit den konkreten Parameterwerten Anzahl = 8 und Startzeichen = A durchführen (und müssten im schlimmsten Fall 268 ≈ 200 Mrd. verschiedene Kombinationen austesten…).

Auf einer abstrakteren Ebene beschreibt dieser Algorithmus ein sehr einfaches (und im Zweifelsfall sehr aufwendiges) Suchverfahren nach einem Codewort oder Passwort mit einer bestimmten Länge und begrenzten Zeichenmenge, hier durch ein Kombinationsschloss verschaulicht. Dabei werden der Reihe nach alle möglichen Zeichenkombinationen durchgegangen, bis die richtige Kombination gefunden wurde.10


  1. siehe auch Peer Stechert: Computational Thinking aus der Reihe Informatikdidaktik kurz gefasst (Teil 8), Video bei YouTube ↩︎

  2. Solche Beispiele eignen sich teils aber nur bedingt, da ihre Beschreibungen zwar für einen Menschen verständlich, aber für eine Maschine im Detail meist zu ungenau formuliert werden. ↩︎

  3. Tatsächlich können Computerprogramme nicht mit “echtem” Zufall arbeiten: Zufallszahlen werden in Rechnern meist durch zufällig wirkende, aber in Wirklichkeit deterministische Verfahren generiert, die daher auch als Pseudozufallszahlengeneratoren bezeichnet werden. Dabei werden in der Regel unkontrollierbare (also “zufällige”) Faktoren als Eingabe mit einbezogen, z. B. die aktuelle Rechnerzeit beim Starten des Programms. ↩︎ ↩︎

  4. Die Grafiken für die Zahlenschlösser stammen von Vecteezy↩︎

  5. Eventuell bietet es sich in Abhängigkeit von der Zielgruppe und dem Problemkontext aber auch an, Variablen als greifbare Objekte in das Gesamtszenario zu integrieren, z. B. als kleine Notizzettel oder Schieberegler zu umschreiben. ↩︎

  6. Kontrollstrukturen und Variablenzuweisungen können aber wie üblich ohne Einschränkung verwendet werden, wenn nötig. ↩︎

  7. Daneben können aber wie üblich allgemein bekannte mathematische und logische Werte und Operatoren verwendet werden. ↩︎

  8. Auch diese Wiederholung lässt sich äquivalent mit “bis” statt “solange” formulieren: Wiederhole bis n-te Stelle ≠ 0 oder n = 1: … ↩︎

  9. Der Unteralgorithmus “stelle Code 0000” müsste ähnlich angepasst werden: Wiederhole solange n-te Stelle ≠ Startzeichen: … Wir sollten ihn dann auch allgemeiner in “stelle Startcode ein” umbenennen. ↩︎

  10. Ein solches Verfahren wird in der Informatik als vollständige Suche, erschöpfende Suche oder auch Brute-Force-Verfahren (sinngemäß etwa “Holzhammermethode”) bezeichnet. ↩︎

3.1.1 Übungsaufgaben

Praktische Übungen

Aufgabe: Gewichte vergleichen

Gegeben sind N außerlich gleiche Säckchen, die Sand enthalten (die Anzahl N beträgt dabei mindestens 2). Eines der Säckchen enthält goldhaltigen Sand und ist daher etwas schwerer als die anderen. Alle anderen Säckchen sind gleich schwer. Sie erhalten eine Balkenwaage, mit der Sie das Gewicht von jeweils zwei Säckchen vergleichen können, um festzustellen, welches der beiden Säckchen schwerer ist bzw. ob beide Säckchen gleich schwer sind.

Ihr Aufgabe besteht nun darin, einen Algorithmus anzugeben, mit dem sich systematisch das Säckchen mit dem Goldanteil ermitteln lässt.

  • Überlegen Sie dabei, welche Anweisungen und Abfragen hier sinnvoll sind.
  • Beschreiben Sie Ihr Lösungsverfahren sprachlich möglichst eindeutig und allgemeingültig (das Verfahren soll für beliebige N ≥ 2 anwendbar sein).

Tool: In der folgenden interaktiven Simulation können Sie verschiedene Lösungsstrategien selbst durchspielen, um ein Lösungsverfahren zu entwickeln und zu testen. Ziehen Sie die Säckchen auf die freien Felder in der Waage, um ihr Gewicht zu vergleichen. Die Fläche in der Mitte können Sie nutzen, um die Säckchen während des Lösungsverfahrens zu verwalten (z. B. oben die noch nicht betrachteten Säckchen, unten die bereits betrachteten Säckchen). Das gesuchte Säckchen können Sie im grünen Feld rechts platzieren.

Klicken Sie auf “Lösung anzeigen”, wenn Sie glauben, das richtige Säckchen gefunden zu haben. Mit den anderen Schaltflächen lässt sich die Simulation neu starten und die Anzahl der Säckchen ändern.

3.2 Darstellung von Algorithmen

Motivation

Wir haben bisher verschiedene Möglichkeiten kennengelernt, um Algorithmen zu beschreiben: Zum einen in natürlicher Sprache, die allerdings Uneindeutigkeiten enthalten kann und es erschwert, einen Algorithmus wirklich präzise und eindeutig zu beschreiben. Auf der anderen Seite haben wir die Umsetzung als Programm in einer konkreten Programmiersprache, also die Implementierung eines Algorithmus in Form von Programmcode. Diese Darstellung ist zwar maximal eindeutig, da ihre Syntax und Semantik durch die verwendete Programmiersprache genau vorgegeben sind, für Menschen aber nur dann verständlich, wenn die entsprechende Programmiersprache beherrscht wird. Zwischen diesen beiden Welten liegt der sogenannte Pseudocode, also eine Beschreibung in natürlicher Sprache, die sich aber auf ganz bestimmte Formulierungen und Anweisungen beschränkt – etwa “wiederhole … bis”, “falls … dann … sonst …” – so dass eine Beschreibung entsteht, die schon relativ nah am Code einer textuellen imperativen Programmiersprache ist, dabei aber allgemeiner verständlich ist.

Um einen Algorithmus zu einer gegebenen Problemstellung zu entwickeln und darüber zu kommunizieren, kann es hilfreich sein, den Ablauf zunächst auf Papier zu entwerfen, bevor er in Scratch oder einer anderen Programmiersprache umgesetzt wird. Als Alternative zu einer textuellen Beschreibung gibt es auch Möglichkeiten, Algorithmen unabhängig von einer konkreten Programmiersprache grafisch darzustellen. Zwei verbreitete Darstellungsformen dafür sind Struktogramme und Programmablaufpläne.

Diese grafischen Darstellungen sind sowohl für das Lesen von Algorithmen als auch für den Algorithmenentwurf hilfreich: Zum einen sind sie durch ihre reduzierte, strukturierte Darstellung übersichtlicher und eindeutiger als Texte – zum anderen leiten sie uns durch ihre formalen Vorgaben und die zur Verfügung stehenden grafischen Grundbausteine dazu an, uns beim Algorithmenentwurf auf bestimmte Ablaufstrukturen zu beschränken.

Struktogramme

Struktogramme (auch nach ihren Entwicklern Nassi-Shneiderman-Diagramme genannt) sind Diagramme zur grafischen Beschreibung von Algorithmen.1 In einem Struktogramm werden rechteckige Blöcke als Grundbausteine verwendet, die gestapelt und ineinander geschachtelt werden können.

Anweisung
DiagramEinzelne elementare Anweisungen werden jeweils durch einen einfachen Block dargestellt, der mit der möglichst kurz und präzise formulierten Anweisung beschriftet ist.
DiagramAnweisungen, die Unterprogramme aufrufen, können durch einen Block mit zwei Seitenstreifen dargestellt werden, um sie von elementaren Anweisungen zu unterscheiden (hier in Anlehnung an Unterprogrammaufrufe in Scratch rot schattiert).
Sequenz
DiagramBlöcke können vertikal zu größeren Blöcken gestapelt werden, so dass sich Sequenzen ergeben.

Für die Kontrollstrukturen (Fallunterscheidungen und Wiederholungen) gibt es spezielle Blöcke, in die andere Blöcke eingepackt werden.

Wiederholung
DiagramDer Block für eine bedingte Wiederholung besteht aus einem Γ-förmigen Rahmen, der einen anderen Block umschließt – nämlich denjenigen Block, der wiederholt ausgeführt wird. Der Rahmen ist mit der Wiederholungsbedingung beschriftet (“wiederhole bis …” oder “wiederhole solange …”). Für eine Wiederholung mit fester Anzahl kann stattdessen “wiederhole n-mal” geschrieben werden.
DiagramBei Endloswiederholungen wird in der Regel die Beschriftung weggelassen und der Rahmen C-förmig dargestellt (alternativ kann auch ein Γ-förmigen Rahmen mit “wiederhole endlos” beschriftet werden).
Fallunterscheidung
DiagramDer Block für eine Fallunterscheidung (“falls … dann … sonst …”) besteht aus einer Kopfzeile, in der die Bedingung steht, gefolgt von zwei Blöcken, die nebeneinander stehen: Links der Block, der ausgeführt wird, wenn die Bedingung erfüllt ist, rechts der Block, der anderenfalls ausgeführt wird (beide Bereiche können auch leer sein).
DiagramBei rein bedingten Anweisungen (“falls … dann” ohne “sonst”) bleibt der rechte Teilblock leer.

Die Blöcke innerhalb von Wiederholungen und Fallunterscheidungen können dabei einfache Anweisungsblöcke, Sequenzen oder komplexere, aus anderen Blöcken zusammengesetzte Teilalgorithmen sein.

Ihnen ist vermutlich schon aufgefallen, dass Struktogramme den aus Scratch bekannten, ebenfalls aus Blöcken zusammengesetzten Skripten stark ähneln – mit dem Hauptunterschied, dass die alternativen Anweisungen einer Fallunterscheidung nebeneinander gestellt werden statt übereinander.2 Die folgende Tabelle stellt zur Veranschaulichung ein paar Struktogramm-Beispiele den jeweiligen Umsetzungen in Scratch gegenüber:

GrundstrukturDarstellung im StruktogrammDarstellung in Scratch
AnweisungssequenzDiagramScript
Bedingte WiederholungDiagramScript
EndloswiederholungDiagramScript
Bedingte Anweisung (ohne Alternative)DiagramScript
Fallunterscheidung
(Bedingte Anweisung mit Alternative)
DiagramScript

Beispiel: Code knacken

Betrachten wir noch einmal den einfachen Algorithmus zum Ermitteln der richtigen Kombination eines Zahlenschlosses aus dem vorigen Kapitel:

  • Stelle Code 0000 ein
  • Versuche Schloss zu öffnen
  • Wiederhole bis Schloss offen ist:
    • Stelle nächsten Code ein
    • Versuche Schloss zu öffnen

Im Struktogramm wird der Algorithmus wie folgt dargestellt:

Diagram

Die einzelnen Anweisungen werden durch entsprechend beschriftete Blöcke dargestellt, die gestapelt werden. Für die Wiederholung verwenden wir einen Γ-förmigen Block, der mit der Wiederholungsbedingung “solange Schloss nicht offen ist” beschriftet ist und den Block der beiden zu wiederholenden Anweisungen enthält.

Vergleich mit Scratch

Varianten der Wiederholungen

Die Kontrollstruktur “Wiederholung” gibt es in verschiedenen Varianten, von denen wir ein paar bereits in Scratch kennengelernt haben: die Endloswiederholung, Wiederholung mit fester Anzahl und bedingte Wiederholung. An dieser Stelle werfen wir einen genaueren Blick auf diese verschiedenen Variante und grenzen sie voneinander ab.

Aus Scratch kennen wir die Endloswiederholung und die Wiederholung mit fester Anzahl (“wiederhole n-mal”) als Formen der Wiederholung ohne Bedingung.

Die aus Scratch bekannte bedingte Wiederholung ist eine Wiederholung mit Abbruchbedingung – sprachlich formuliert als “wiederhole bis Bedingung” – und wird folgendermaßen ausgeführt: Zuerst wird überprüft, ob die Bedingung erfüllt ist. Falls sie nicht erfüllt ist, wird der enthaltene Block einmal ausgeführt und anschließend erneut die Bedingung geprüft. Falls sie erfüllt ist, wird die Wiederholung beendet und das Programm fährt nach dem Wiederholungsblock fort.

Image

Manchmal ist es aber intuitiver, eine Wiederholung stattdessen als “wiederhole solange Bedingung” zu formulieren. In diesem Fall erfüllt die Bedingung die Rolle einer Laufbedingung: Falls sie zu Beginn bzw. nach einem Durchlauf der Wiederholung erfüllt ist, wird ein weiterer Wiederholungsdurchlauf durchgeführt, anderenfalls wird die Wiederholung beendet (also genau entgegengesetzt zu einer Wiederholung mit Abbruchbedingung).3

Ein weiterer Unterschied besteht darin, ob die Lauf- oder Abbruchbedingung das erste Mal vor dem ersten Wiederholungsdurchlauf (bzw. zu Beginn einer Wiederholung) oder nach dem ersten Durchlauf (bzw. am Ende der Wiederholung) überprüft und ausgewertet wird. Bei den oben beschriebenen Varianten wird die Bedingung bereits zu Beginn einmal ausgewertet, weswegen sie im Block auch oben steht (im “Kopf” des Blocks). Diese Form der Wiederholung wird daher als kopfgesteuerte Wiederholung bezeichnet.

In Struktogrammen lassen sich auch fußgesteuerte Wiederholungen darstellen, bei denen die Bedingung nur am Ende jeden Durchlaufs geprüft wird – hier wird intuitiverweise ein L-förmiger Block verwendet und die Bedingung ans Ende gestellt. Im Gegensatz zur kopfgesteuerten Wiederholung wird der enthaltende Block hier mindestens einmal ausgeführt, selbst wenn die Abbruchbedingung bereits zu Beginn erfüllt ist (bzw. die Laufbedingung bereits zu Beginn nicht erfüllt ist). Die kopfgesteuerte Wiederholung würde in diesem Fall gar nicht ausgeführt werden.

Die folgende Übersicht zeigt alle Varianten der Wiederholungen (kopf- oder fußgesteuert mit Lauf- oder Abbruchbedingung, Endloswiederholung und Wiederholung mit fester Anzahl), die sich in Struktogrammen darstellen lassen:

Diagram

Für die bedingten Wiederholungen gilt: Ob kopf- oder fußgesteuerte Form, ob Lauf- oder Abbruchbedingung zur Formulierung eines Algorithmus am besten geeignet ist, hängt immer von der konkreten Situation ab. Manchmal kann eine der Varianten zu intuitiver verständlichen oder eleganter wirkenden Formulierungen führen. Es sollte allerdings beachtet werden, dass nicht alle Varianten in jeder Programmiersprachen direkt umsetzbar sind – in Scratch gibt es beispielsweise keine fußgesteuerten Wiederholungen – und daher bei der Implementierung eventuell umformuliert werden müssen.

Aufgabe: Geschirrspülmaschine leeren

Zur praktischen Veranschaulichung soll nun ein einfacher Algorithmus als Struktogramm entworfen werden. Als Problemstellung soll eine Geschirrspülmaschine ausgeräumt werden, die Tassen, Teller und Schüsseln enthält. Dabei sollen die verschiedenen Geschirrteile unterschiedlich behandelt werden:

Tassen sollen in den Schrank geräumt werden, alle anderen Geschirrteile ins Regal.
Geschirrteile, die beim Spülen kaputtgegangen sind, werden dagegen weggeworfen.
Dabei soll mitgezählt werden, wie viele Geschirrteile weggeworfen werfen.

Als Erstes legen wir fest, welche einfachen Anweisungen wir zur Formulierung des Lösungsverfahrens verwenden können, mit welchen Objekten gearbeitet wird und welche Eigenschaften der Objekte hier für uns relevant sind. Als problemspezifische Anweisungen reichen hier “nimm das nächste Teil aus der Spülmaschine”, “stelle das Teil in den Schrank”, “stelle das Teil ins Regal” oder “wirf das Teil weg”. Da wir außerdem zählen müssen, wie oft die Anweisung “wirf das Teil weg” ausgeführt wurde, verwenden wir zusätzlich eine Variable namens “Zähler” und als weitere Anweisungen Variablenzuweisungen wie “setze Zähler auf Wert” oder “erhöhe Zähler um Wert”.

Die naheliegenden Objekte, mit denen hier gearbeitet wird, sind hier die Geschirrteile, die wir aus der Spülmaschine nehmen. Uns interessieren nur zwei Attribute dieser Objekte: die Sorte (Tasse oder etwas anderes) und der Zustand (kaputt oder nicht). Außerdem müssen wir überprüfen können, ob die Spülmaschine leer ist oder nicht, um zu entscheiden, wann wir fertig sind.

Wir werden uns im Struktogramm also auf die folgenden elementaren Anweisungen, Variablenzuweisungen und Zustandsabfragen beschränken:

Image

Nun geht es darum, die elementaren Anweisungen mit Hilfe von Kontrollstrukturen in den richtigen Ablauf zu bringen, wobei wir die Zustandsabfragen in den Bedingungen der Kontrollstrukturen verwenden werden.

Wir nähern uns Schritt für Schritt an die Lösung an, indem wir das Problem zunächst auf das Wesentliche reduzieren und Details wie das Zählen der weggeworfenen Geschirrteile erst einmal weglassen, um sie später zu unserer Lösung hinzuzufügen.

Wir beginnen also mit dem Grundgerüst: Es sollen nacheinander alle Geschirrteile aus der Spülmaschine genommen und verarbeitet werden, bis diese leer ist. Die Anweisung “nimm nächstes Teil” wird also innerhalb einer bedingten Wiederholung platziert. Als Abbruchbedingung wird geprüft, ob die Spülmaschine leer ist.

Diagram

Wir ergänzen nun innerhalb des Blocks, der wiederholt ausgeführt wird, die eigentliche Verarbeitung der Geschirrteile. Es sollen unterschiedliche Aktionen durchgeführt werden, je nach Zustand und Sorte des zuletzt genommenen Objekts.

Als Erstes fügen wir also eine Fallunterscheidung zur Unterscheidung zwischen kaputten und nicht kaputten Objekten hinzu. Falls die Bedingung “ist kaputt?” erfüllt ist, soll die Anweisung “wirf weg” ausgeführt werden, die dazu in der linken Seite des Fallunterscheidungs-Blocks platziert wird.

Diagram

In der rechten Seite des Fallunterscheidung-Blocks fügen wir nun den Block ein, der beschreibt, was im anderen Fall getan werden soll. Wenn das zuletzt genommene Objekt nicht kaputt ist, hängt es von seiner Sorte ab, was mit ihm gemacht werden soll.

Wir platzieren rechts also einen weiteren Fallunterscheidungs-Block, der als Bedingung prüft, ob das Objekt eine Tasse ist. Falls ja, soll die Anweisung “stelle in den Schrank” ausgeführt werden (links), anderenfalls die Anweisung “stelle ins Regal” (rechts).

Diagram

Nun fehlt noch das Zählen der weggeworfenen Geschirrteile: Dazu ergänzen wir die Anweisung “erhöhe Zähler um 1” im linken Bereich der äußeren Fallunterscheidung, so dass sie zusätzlich zu “wirf weg” ausgeführt wird, wenn ein kaputtes Objekt aus der Spülmaschine genommen wurde. Der Vollständigkeit halber sollten wir den Zähler ganz zu Beginn noch auf 0 als Startwert setzen.

Diagram

Das vollständige Struktogramm finden Sie unter “Schritt 4”. Es sind natürlich auch Variationen dieses Lösungsverfahrens möglich, z. B. könnten die Eigenschaften der Objekte auch in anderer Reihenfolge überprüft oder das Zählen der weggeworfenen Objekte auf andere Weise gelöst werden.

Fazit

Um einen Algorithmus als Struktogramm darzustellen, sind wir “gezwungen”, das Gesamtproblem in kleinere Teilprobleme zu zerlegen, bis nur noch Grundstrukturen wie Sequenzen und Kontrollstrukturen zur Lösung der Teilprobleme benötigt werden, die sich mit den Blöcken der Struktogramme darstellen lassen und zur Gesamtlösung zusammengebaut werden – ganz im Sinne der strukturierten Programmierung bzw. des “Computational Thinking”. Durch die Beschränkung auf diese einfachen Grundstrukturen lassen sich Algorithmen, die als Struktogramme formuliert werden, sogar in gewissem Maße automatisch in Programmcode verschiedener imperativer Programmiersprachen übersetzen.

Als Struktogramm formulierte Algorithmen lassen sich außerdem durch die Ähnlichkeit der Darstellung relativ einfach in Scratch implementieren – und andersherum Scratch-Programme sehr direkt als Struktogramme abstrakter skizzieren. Es kann also nützlich sein, Algorithmen, die später in Scratch umgesetzt werden sollen, zunächst auf Papier oder an der Tafel als Struktogramm zu entwickeln.

Struktogramme können mit Papier und Stift oder mit einem einfachen digitalen Zeichenwerkzeug wie LibreOffice Draw erstellt werden. Bequemer ist aber die Erstellung mit einem speziellen Struktogramm-Editor. Solche Tools sind oft auch in der Lage, automatisch Programmcode verschiedener Programmiersprachen aus dem erstellten Struktogramm zu erzeugen. Verweise auf Struktogramm-Editoren finden Sie in der Linksammlung bei den Software-Werkzeugen.

Programmablaufpläne

Programmablaufpläne (auch kurz “PAP”, engl. flowcharts) stellen eine weitere Möglichkeit zur grafischen Darstellung von Algorithmen dar, die sich deutlich von Struktogrammen unterscheidet. Hier werden Algorithmen als Graphen repräsentiert, die mögliche zeitliche Abfolgen von Anweisungen in einem Algorithmus abbilden. Die Knoten in diesem Graphen sind Anweisungen, Verzweigungen und Start-/Endzustände.

  • Einzelne Anweisungen oder Unterprogrammaufrufe werden hier (wie bei Struktogrammen) durch Blöcke dargestellt, die mit der Anweisung beschriftet sind.
  • Pfeile kennzeichnen den Übergang von einem Knoten zum nächsten. Ein Pfeil, der von einem Anweisungsblock ausgeht, gibt also beispielsweise an, mit welcher Anweisung als Nächstes fortgefahren wird.
  • Verzweigungen werden durch Rauten dargestellt, die mit einer Bedingung beschriftet sind und von denen zwei Pfeile ausgehen. Sie repräsentieren Entscheidungen im Programmablauf, bei denen in Abhängigkeit von der angegebenen Bedingung entweder über den “ja”-Pfeil oder den “nein”-Pfeil fortgefahren wird.
  • Start und Ende des Ablaufs werden durch kleine runde Knoten dargestellt. Jeder Algorithmus hat genau einen Startknoten, an dem der Ablauf beginnt.

Die folgende Tabelle stellt die grundlegenden Elemente dar, aus denen Programmablaufpläne bestehen:4

DiagramDiagramDiagramDiagramDiagram
Start-/EndzustandAnweisungUnterprogrammaufrufÜbergang zum nächsten ElementVerzweigung

Auffällig ist, dass es keine Grundbausteine für bedingte Anweisungen/Fallunterscheidungen oder bedingte Wiederholungen gibt wie bei Struktogrammen. Stattdessen werden diese Kontrollstrukturen alle mit Hilfe von Verzweigungen zusammengesetzt.

Der Ablauf eines Algorithmus kann für einen gegebenen PAP einfach nachvollzogen werden, indem vom Startknoten aus entlang der Pfeile gegangen wird und alle Anweisungen auf dem Weg ausgeführt werden. Bei jeder Verzweigung wird die in der Raute enthaltene Bedingung ausgewertet, um zu entscheiden, ob der mit “ja” beschriftete Pfeil oder der mit “nein” beschriftete Pfeil weiterverfolgt wird.

Damit ein Algorithmus, der als Programmablaufplan dargestellt wird, eindeutig ist, müssen ein paar Regeln eingehalten werden:

  • Es muss genau einen Startknoten geben und mindestens einen Stopknoten.
  • Von jedem Element des Programmablaufplans muss genau ein Pfeil ausgehen, der dieses Element mit dem als Nächstes auszuführenden Element verbindet. Einzige Ausnahmen sind Verzweigungen und Stopknoten.
  • Von jeder Verzweigung gehen genau zwei Pfeile aus, von denen einer mit “ja” und der andere mit “nein” (oder “wahr” und “falsch”) beschriftet ist.
  • Von Stopknoten gehen keine Pfeile aus, der Ablauf des Algorithmus endet hier also, da nicht weitergangen werden kann.

Mit diesen wenigen Elementen lassen sich alle bisher behandelten Kontrollstrukturen darstellen:

  • Fallunterscheidungen bestehen aus einer Verzweigung, die den Ablauf in zwei parallele Abläufe aufteilt (der “wenn”-Fall und der “sonst”-Fall), die anschließend wieder zusammenführen.
  • Wiederholungen entstehen, wenn Pfeile einen Kreis bilden. Üblicherweise muss hier am Beginn (bei einer kopfgesteuerten Wiederholung) oder am Ende (bei einer fußgesteuerten Wiederholung) dieses Kreises eine Verzweigung stehen, die es ermöglicht, in Abhängigkeit von einer Abbruchbedingung aus dem Kreis auszusteigen. Anderenfalls erhalten wir eine Endloswiederholung.

Die folgende Tabelle stellt die bekannten Kontrollstrukturen anhand von Beispielen als PAP dar und stellt sie zum Vergleich den jeweiligen Struktogrammen und Umsetzungen in Scratch gegenüber:

GrundstrukturDarstellung im PAPDarstellung als StruktogrammDarstellung in Scratch
AnweisungssequenzDiagramDiagramScript
Bedingte WiederholungDiagramDiagramScript
EndloswiederholungDiagramDiagramScript
Bedingte Anweisung (ohne Alternative)DiagramDiagramScript
Fallunterscheidung
(Bedingte Anweisung mit Alternative)
DiagramDiagramScript

Beispiel: Code knacken

Auch hier soll als erstes Beispiel der einfache Algorithmus zum Ermitteln der richtigen Zahlenschloss-Kombination als Programmablaufplan untersucht werden:

Diagram

Auf den Startknoten – den Beginn des Algorithmus – folgt eine Sequenz von zwei Anweisungen, die jeweils durch Pfeile miteinander verbunden sind. Anschließend folgt mit der bedingten Wiederholung der eigentlich interessante Teil: Hier wird eine Verzweigung verwendet, in der die Abbruchbedingung der Wiederholung “ist Schloss offen?” überprüft wird. Falls das der Fall ist, endet der Algorithmus: Der mit “ja” beschriftete Pfeil aus der Verzweigung wird also mit einem Stopknoten verbunden. Anderenfalls soll die Sequenz der beiden Anweisungen “stelle nächsten Code ein” und “versuche Schloss zu öffnen” ausgeführt werden. Der mit “nein” beschriftete Pfeil führt daher zur ersten dieser beiden Anweisungen und von dieser ein Pfeil zur nächsten. Nach der zweiten Anweisung soll erneut die Abbruchbedingung ausgewertet werden: Der von ihr ausgehende Pfeil führt daher wieder zur Verzweigung zurück.

Ablaufsimulation

Die folgenden Bilder stellen einen möglichen Ablauf dieses Algorithmus dar (die richtige Kombination lautet hier 0815):

Wir beginnen im Startknoten, der eindeutig festgelegt ist.

Diagram

Vom Startknoten aus gehen wir entlang des Pfeils zur nächsten Anweisung und führen diese aus.

Diagram

Von dieser Anweisung gehen wir wieder entlang des Pfeils zur Folgeanweisung und führen diese ebenfalls aus.

Diagram

Wir erreichen eine Verzweigung: Nun wird die darin enthaltene Bedingung ausgewertet, die zu diesem Zeitpunkt nicht erfüllt ist.

Diagram

Also verfolgen wir den mit “nein” beschrifteten Pfeil zur nächsten Anweisung, die ausgeführt wird.

Diagram

Von dieser Anweisung gehen wir wieder entlang des Pfeils zur Folgeanweisung und führen diese ebenfalls aus.

Diagram

Über den Pfeil erreichen wir nun wieder die Verzweigung zu Beginn des Wiederholung und überprüfen ihre Bedingung erneut.

Diagram

Es vergehen nun einige Schritte, bis wir im 2446. Schritt die Kombination 0814 erreichen und überprüfen.

Diagram

Da die Bedingung momentan nicht erfüllt ist wird entlang des “nein”-Pfeils gegangen und die nächste Anweisung ausgeführt.

Diagram

Die Ausführung der folgenden Anweisung öffnet das Schloss, da 0815 hier die richtige Kombination ist.

Diagram

Wir kehren zur Verzweigung zu Beginn des Wiederholung zurück und überprüfen ihre Bedingung, die nun erfüllt ist.

Diagram

Also verfolgen wir den mit “ja” beschrifteten Pfeil und erreichen den Stopknoten. Der Algorithmus endet an dieser Stelle.

Diagram

An diesem Beispielalgorithmus ist erkennbar, dass eine Wiederholung in einem PAP durch eine Verzweigung beschrieben wird, von der ein Weg ausgeht, der später wieder zu dieser Verzweigung zurückführt. Als Nächstes werden wir uns genauer ansehen, wie die verschiedenen üblichen Kontrollstrukturen in Programmablaufplänen umgesetzt werden.

Fallunterscheidungen im PAP

Bedingte Anweisungen und Fallunterscheidungen werden in Programmablaufplänen durch Verzweigungen umgesetzt, die zu zwei parallelen Wegen führen, die später wieder zusammenlaufen:

Diagram Diagram

Bei einer bedingten Anweisung ohne Alternative enthält der Weg, von dem der “nein”-Pfeil ausgeht, keine weiteren Anweisungen:

Diagram Diagram

Bei mehrfachen Fallunterscheidungen enthält dieser Weg dagegen weitere Verzweigungen in parallele Wege, die später mit dem “Hauptweg” zusammengeführt werden:

Diagram Diagram

Wiederholungen im PAP

Generell werden Wiederholungen in Programmablaufplänen durch Verbindungen von Elementen mittels Pfeilen umgesetzt, die einen Weg bilden, der im Kreis läuft.

Bei bedingten Wiederholungen enthält dieser Kreis mindestens eine Verzweigung, die es ermöglicht, in Abhängigkeit von einer Abbruchbedingung aus dem Kreislauf auszubrechen:

Diagram Diagram

Bei einer Wiederholung mit Laufbedingung (statt Abbruchbedingung) verläuft der Kreis vom “ja”-Pfeil aus, während der “nein”-Pfeil aus dem Kreis ausbricht:

Diagram Diagram

Bei einer Endloswiederholung gibt es dagegen einen Weg ohne Verzweigung, der im Kreis führt:

Diagram Diagram

Aufgabe: Geschirrspülmaschine leeren

Im Folgenden wird noch einmal der Algorithmus aus dem Beispiel Geschirrspülmaschine leeren betrachtet und Schritt für Schritt als Programmablaufplan formuliert. Dazu gehen wir von einem möglichen Ablauf des Algorithmus aus und untersuchen, welche Anweisungen nacheinander ausgeführt werden und welche Entscheidungen dabei gefällt werden müssen.

Als Erstes muss überprüft werden, ob die Spülmaschine leer ist, um zu entscheiden, was als Nächstes gemacht werden soll. Der Startknoten wird also durch einen Pfeil mit einer Verzweigung mit der Bedingung “Spülmaschine ist leer?” verbunden.

Diagram

Falls die Bedingung erfüllt ist, sind wir fertig. Der “ja”-Pfeil der Verzweigung führt also zum Stopknoten. Anderenfalls soll das nächste Geschirrteil aus der Spülmaschine genommen werden.

Diagram

Um zu entscheiden, was mit dem genommenen Geschirrteil gemacht werden soll, muss anschließend überprüft werden, ob das Teil kaputt ist oder nicht. Dazu wird eine weitere Verzweigung eingeführt, deren Bedingung in diesem Fall “Teil ist kaputt?” lautet.

Diagram

Falls es kaputt ist, soll es weggeworfen werden und anschließend mit dem nächsten Geschirrteil (wenn vorhanden) weitergemacht werden. Dazu wird der “ja”-Pfeil der Verzweigung mit der Anweisung “wirf weg” verbunden, die wiederum zurück zur Verzweigung führt, in der geprüft wird, ob die Spülmaschine weitere Teile enthält.

Diagram

Anderenfalls soll es entweder in den Schrank oder ins Regal gestellt werden, je nachdem, ob eine Tasse oder ein anderes Geschirrteil genommen wurde. Dazu wird eine weitere Verzweigung eingeführt, die als Bedingung “Teil ist Tasse?” prüft.

Diagram

Tassen werden in den Schrank gestellt: Der “ja”-Pfeil der Verzweigung führt also zur Anweisung “stelle in Schrank”. Da anschließend mit dem nächsten Geschirrteil weitergemacht werden soll, führt der ausgehende Pfeil von dieser Anweisung zurück zur Verzweigung “Spülmaschine ist leer?”.

Diagram

Andere Geschirrteile werden ins Regal gestellt: Der “nein”-Pfeil der Verzweigung führt also zur Anweisung “stelle ins Regal”. Auch diese Anweisung verweist anschließend zurück zur Verzweigung “Spülmaschine ist leer?”.

Diagram

Abschließend müssen noch die Anweisungen zum Zählen der weggeworfenen Geschirrteile ergänzt werden: Zu Beginn (zwischen Startknoten und erster Verzweigung) wird der Zähler auf den Startwert 0 gesetzt und direkt nach dem Wegwerfen eines Geschirrteils um 1 erhöht.

Diagram

Den vollständigen Programmablaufplan finden Sie unter “Schritt 8”. Wie beim Entwurf des Struktogramms sind auch hier andere Abläufe möglich.

Fazit

Anhand von Programmablaufplänen lässt sich die Ausführung von Algorithmen intuitiver nachvollziehen als bei Struktogrammen. Sie verfolgen einen anderen Ansatz beim Entwurf von Algorithmen, da hier vom Ablauf ausgegangen wird statt von der Struktur des Algorithmus. Allerdings erfordert der Entwurf von Algorithmen mit PAP eine gewisse Disziplin: Umfangreichere Algorithmen können schnell chaotisch und unübersichtlich werden und es können leicht uneindeutige Diagramme entstehen, wenn Kanten fehlen oder zu viele Kanten eingezeichnet werden.

Ein Problem von Programmablaufplänen ist, dass hier mit Sprüngen gearbeitet wird: Der Kontrollfluss kann von jeder Anweisung zu jeder beliebigen anderen Anweisung übergehen. Dadurch lassen sich Abläufe darstellen, die mit den üblichen Kontrollstrukturen gar nicht direkt beschreibbar sind und sich auch nicht in jeder Programmiersprache implementieren lassen.5 In Struktogrammen sind die möglichen Abläufe dagegen auf die üblichen Kontrollstrukturen eingeschränkt.


  1. Die Elemente von Nassi-Shneiderman-Diagrammen sind in Deutschland genormt in DIN 66261. ↩︎

  2. Tatsächlich sind blockbasierte visuelle Programmiersprachen wie Scratch, Snap! oder Blockly unübersehbar von Struktogrammen zur grafischen Darstellung von Algorithmen als Vorbild inspiriert. ↩︎

  3. Jede Wiederholung mit Laufbedingung kann in eine Wiederholung mit Abbruchbedingung umformuliert werden und umgekehrt, indem die Lauf- bzw. Abbruchbedingung einfach negiert wird – z. B. ist “wiederhole bis Ziel erreicht” gleichbedeutend mit “wiederhole solange Ziel nicht erreicht”. ↩︎

  4. Die grafischen Elemente von Programmablaufplänen sind genormt in DIN 66011, gemeinsam mit den ähnlichen Elementen von Datenflussplänen. ↩︎

  5. Um Programmablaufpläne uneingeschränkt in einer Programmiersprache implementieren zu können werden sogenannte Sprunganweisungen benötigt, die es erlauben, den Kontrollfluss an einer beliebigen anderen Stelle im Programm fortzusetzen. Sprunganweisungen gelten in der imperativen Programmierung aber als tendenziell fehleranfällig, da sie dazu verleiten, unübersichtlichen und schwer überprüfbaren Programmcode zu schreiben. ↩︎

3.2.1 Übungsaufgaben

Praktische Übungen

Aufgabe 1: Scratch-Skript als Struktogramm

Setzen Sie das folgende Scratch-Skript in ein Struktogramm mit gleicher Bedeutung um:

Script

Formulieren Sie die Anweisungen im Struktogramm dabei ähnlich wie in den Scratch-Blöcken, z. B.:

gehe zu Position (0, 0) wechsle zu Kostüm 1

Aufgabe 2: Struktogramm aufstellen

Erstellen Sie ein Struktogramm, das den Ablauf eines Würfelspiels nach den folgenden Regeln beschreibt:

Sie starten mit 0 Punkten und würfeln wiederholt mit einem Würfel, bis Sie genau 21 Punkte erreicht haben.
Sie erhalten dabei bei jedem Wurf 1 bis 6 Punkte, je nachdem, wie viele Augen Sie gewürfelt haben.
Wenn Sie dabei 21 Punkte überschreiten, fallen Sie wieder auf 0 zurück.

Beschränken Sie sich dabei möglichst auf die folgenden ausführbaren Anweisungen (Zahl ist hierbei ein frei wählbarer Parameter):

würfle setze Punkte auf Zahl erhöhe Punkte um Zahl

Kontrollstrukturen und Variablenzuweisungen können aber wie üblich ohne Einschränkung verwendet werden, wenn nötig.

Verwenden Sie in den Vergleichen für die Bedingungen die Begriffe “Punkte” und “Würfelergebnis” für den Wert des aktuellen Punktestands bzw. die Anzahl der im letzten Wurf gewürfelten Augen.

Aufgabe 3: Programmablaufplan interpretieren

Der folgende Programmablaufplan beschreibt einen Algorithmus zur Steuerung einer Figur in einer 2D-Welt (jeder Schritt bewegt die Figur auf ein angrenzendes Feld).

  • Vollziehen Sie den Ablauf des Algorithmus nach und zeichnen Sie den Weg, den die Figur während der Ausführung zurücklegt.
  • Übersetzen Sie den Programmablaufplan anschließend in ein Struktogramm, das denselben Algorithmus beschreibt.

Diagram Diagram

Aufgabe 4: Programmablaufplan aufstellen

Erstellen Sie einen Programmablaufplan zur Steuerung eines Staubsaugroboters, der nach den folgenden Regeln durch den Raum fährt:

Nach dem Start fährt der Roboter schrittweise geradeaus.
Wenn er dabei auf eine Wand stößt, fährt er einen Schritt zurück und dreht sich in eine zufällige Richtung, bevor er weitermacht.
Wenn der Akkuladestand unter 10% sinkt, stoppt der Roboter.

Im Programmablaufplan sollten möglichst nur die folgenden Anweisungen und Zustandsabfragen (für Bedingungen und Vergleiche) verwendet werden, die der Roboter ausführen kann:

AnweisungenZustandsabfragen
fahre einen Schritt vor fahre einen Schritt zurück drehe dich zufälligwird Wand berührt? Akkuladestand (in %)

Aufgabe 5: Gewichte vergleichen

Formulieren Sie den Algorithmus, den Sie in der Aufgabe Gewichte vergleichen aus der vorigen Übung entwickelt haben, jeweils als Struktogramm und Programmablaufplan.

Diagram

Vergleichen Sie beide Darstellungen: Welche empfanden Sie als einfacher zu entwickeln? Welche ist übersichtlicher oder leichter zu verstehen? Welche eignet sich besser, um den Ablauf des Algorithmus nachzuvollziehen?

Aufgabe 6: Algorithmische Grundstrukturen

In dieser Aufgabe sollen gegebene Programmablaufpläne auf die darin enthaltenden algorithmischen Grundstrukturen hin untersucht werden:

  • Ermitteln Sie für jeden der folgenden Programmablaufpläne an, welche der Ihnen bekannten grundlegenden Kontrollstrukturen (bedingte Wiederholung, Endloswiederholung und/oder Fallunterscheidung) jeweils darin wiederzufinden sind.
  • Übersetzen Sie die Programmablaufpläne dann jeweils in Struktogramme mit der gleichen Bedeutung, sofern möglich.
Programmablaufplan 1Programmablaufplan 2Programmablaufplan 3
DiagramDiagramDiagram

3.3 Anhang

3.3.1 Diagramme zur Algorithmendarstellung

Struktogramme

Übersicht über die Grundbausteine von Nassi-Shneiderman-Diagrammen (“Struktogramme”), genormt in DIN 66261 (siehe Wikipedia):

Algorithmische GrundstrukturDarstellung im Struktogramm
AnweisungDiagram
SequenzDiagram
EndloswiederholungDiagram
Wiederholung mit fester AnzahlDiagram
Bedingte Wiederholung1 (kopfgesteuert)Diagram
Bedingte Wiederholung1 (fußgesteuert)Diagram
Bedingte AnweisungDiagram
Bedingte Anweisung mit AlternativeDiagram
Mehrfache FallunterscheidungDiagram
Unterprogrammaufruf2 (ggf. mit Argumenten)Diagram

Programmablaufpläne

Übersicht über die Grundbausteine von Programmablaufplänen (“PAP”, flowcharts), genormt in DIN 66001 (siehe Wikipedia):

DiagramDiagramDiagramDiagramDiagram
Start-/EndzustandAnweisungUnterprogrammaufrufÜbergangVerzweigung

Darstellung einfacher Beispiele zu den algorithmischen Grundstrukturen als Programmablaufpläne (an jeder Stelle, an der hier eine einzelne Anweisung steht, kann auch ein komplexerer Unteralgorithmus stehen):

Algorithmische GrundstrukturDarstellung im Programmablaufplan
SequenzDiagram
Bedingte AnweisungDiagram
Bedingte Anweisung mit AlternativeDiagram
Bedingte Wiederholung3 (“wiederhole bis”, kopfgesteuert)Diagram
Bedingte Wiederholung3 (“wiederhole bis”, fußgesteuert)Diagram
EndloswiederholungDiagram

  1. Die Abbruchbedingung “wiederhole bis” kann hier auch ersetzt werden durch eine Laufbedingung “wiederhole solange”. ↩︎ ↩︎

  2. Dieser Baustein ist nicht in DIN 66261 genormt und kann alternativ auch als Anweisung dargestellt werden. ↩︎

  3. Um eine Wiederholung mit Laufbedingung (“wiederhole solange”) statt Abbruchbedingung (“wiederhole bis”) umzusetzen, muss hier nur die Beschriftung der Kanten “ja” und “nein” getauscht werden. ↩︎ ↩︎

4. Netzwerke & Internet


4.1 Netzwerke und Internet

Das Online-Skript zum Thema “Netzwerke und Internet” befindet sich zur Zeit noch im Aufbau.

4.2 Kommunikationsprotokolle

Protokolle stellen ein zentrales Konzept in der Netzwerkkommunikation dar. Jeder Ablauf und jeder Nachrichtenaustausch – sei es das Abrufen und Versenden von E-Mails mit einem Mailprogramm, das Öffnen einer Webseite im Webbrowser oder ein Sprachchat während eines Onlinespiels – wird durch Protokolle geregelt. Aber was genau wird in der Informatik unter einem Protokoll verstanden – was ist und was macht ein Protokoll und wie lässt es sich formal beschreiben?

Protokolle

Im alltäglichen Sprachgebrauch kennen wir verschiedene Bedeutungen des Begriffs “Protokoll”: In einem Versuchsprotokoll werden die Einzelschritte, Beobachtungen und Ergebnisse eines wissenschaftlichen Versuchs festgehalten, ein Verlaufsprotokoll dokumentiert Ablauf, Beiträge und Beschlüsse einer Sitzung, während ein diplomatisches Protokoll Vorschriften über den Ablauf von Staatsbesuchen festlegt. Allgemein lässt sich sagen: In einem Protokoll wird aufgezeichnet oder vorgegeben, zu welchem Zeitpunkt oder in welcher Reihenfolge welcher Vorgang durch wen oder was veranlasst wird.

Damit Informatiksysteme miteinander kommunizieren können (das heißt: Informationen austauschen können), muss genau festgelegt werden,

  • welche Nachrichten verschickt werden und was sie bedeuten,
  • in welchem Format die Nachrichten dargestellt werden und wie sie codiert werden,
  • in welcher Reihenfolge welche Nachrichten verschickt werden dürfen
  • und welche Aktionen beim Verschicken oder Empfangen bestimmter Nachrichten ausgeführt werden.

Diese Regeln werden durch Kommunikationsprotokolle festgelegt.

In der Informatik ist ein Kommunikationsprotokoll also eine Vereinbarung, wie Datenübertragung zwischen mehreren Systemen abläuft. Ein Protokoll wird durch Regeln definiert, die Syntax (also Form), Semantik (also Bedeutung) und Synchronisation (also Reihenfolge und Taktung) der Kommunikation festlegen. Die Spezifikation des Protokolls ist ein Dokument, in dem diese Regeln formal beschrieben werden. Ein Netzwerkprotokoll ist wiederum ein Protokoll, das die Kommunikation in Rechnernetzwerken beschreibt, z. B. im Internet.

Beispiel: Im Café

Um die verschiedenen Aspekte eines Kommunikationsprotokolls anschaulich nachzuvollziehen, werden wir uns ein kleines (hier natürlich stark reduziertes) Alltagsbeispiel ansehen: das Verkaufsgespräch in einem Café. In diesem Café gelten allerdings strikte Regeln, wie Gespräche zwischen Kund*innen und Bedienungen ablaufen. Jedes Gespräch wird dadurch begonnen, dass die Kundin mit einem “Hallo?” Kontakt zur Bedienung aufnimmt, die wiederum, wenn sie bereit ist, mit “Hallo!” antwortet.

Die Kundin hat nun mehrere Möglichkeiten: Sie kann verschiedene Artikel aus den Artikelgruppen (z. B. Getränke, Kuchen oder Snacks) bestellen oder sie kann sich darüber informieren, welche Artikel es jeweils in einer Gruppe gibt. Diese Aktionen können beliebig oft und in beliebiger Reihenfolge durchgeführt werden. In jedem Fall wartet die Kundin anschließend auf die Antwort der Bedienung: Im Normalfall eine Bestätigung (z. B. “OK!”, “Mal sehen!”) und je nach Anfrage eine Liste von Artikelnamen oder der gewünschte Artikel selbst.

Das Gespräch wird durch die Kundin mit “Tschüß!” beendet und von der Bedienung mit “Tschüß!” bestätigt. Danach steht die Bedienung wieder für ein Gespräch mit anderen Kund*innen bereit, ebenfalls wieder eingeleitet mit “Hallo?”. Das Bild zeigt einen möglichen Gesprächsverlauf:1

Image

Beginnt die Kundin dagegen gleich mit einer Anfrage ohne das Gespräch zuvor mit “Hallo?” einzuleiten, reagiert die Bedienung mit einem genervten “Häh?!”, da das Protokoll für Verkaufsgespräche hier nicht eingehalten wurde:

Image

In diesem Szenario kann sich die Bedienung nur jeweils um eine Person zur Zeit kümmern. Wenn also eine Kundin mit “Hallo?” Kontakt aufnehmen möchte, während sich die Bedienung gerade im Gespräch mit einer anderen Kundin befindet, so lehnt sie die Anfrage mit der Antwort “Bin beschäftigt!” ab, statt ein weiteres Gespräch zu beginnen:

Image

Sequenzdiagramme

Um den Ablauf des Nachrichtenaustauschs für Kommunikationsbeispiele grafisch formal darzustellen, werden meist Sequenzdiagramme verwendet. Diese Diagramme enthalten vertikale Zeitlinien für alle Kommunikationspartner, zwischen denen Nachrichten ausgetauscht werden. Jede Nachricht wird durch einen beschrifteten Pfeil repräsentiert, der vom Absendezeitpunkt auf der Zeitlinie des Senders zum Empfangszeitpunkt auf der Zeitlinie des Empfängers verläuft. Auf diese Weise lässt sich der zeitliche Ablauf der Kommunikation nachvollziehen, indem die Nachrichten von oben nach unten entlang der Pfeile gelesen werden.

Image

Um zu kennzeichnen, dass ein Kommunikationspartner nach dem Absenden einer Anfrage wartet, bis er eine Antwort vom anderen System empfangen hat, kann in die Zeitlinie ein Balken eingezeichnet werden, der den Wartezeitraum markiert (siehe linke Zeitlinie in der Abbildung).

Client-Server-Kommunikation

Das folgende Sequenzdiagramm stellt den Nachrichtenaustausch für das erste Gespräch im Café noch einmal anschaulich dar:

Image

Der im Beispiel dargestellte Ablauf ist typisch für eine Client-Server-Kommunikation: Der Client (“Kunde”) muss zunächst eine Sitzung (engl. session) mit dem Server (“Bedienung”) beginnen, die durch eine “Begrüßung” oder Anmeldung eingeleitet wird (hier die Anfrage “Hallo?”). Der Server bestätigt diese Anfrage und eröffnet somit die Sitzung, oder er lehnt sie ab (hier beispielsweise wenn er gerade mit einem anderen Client beschäftigt ist). Anschließend stellt der Client während der Sitzung eine oder mehrere Anfragen, die vom Server beantwortet werden, und er beendet die Sitzung abschließend mit einer “Verabschiedung” oder Abmeldung (hier die Nachricht “Tschüß!”), die wiederum vom Server bestätigt wird.

Oft merkt der Server sich während einer Sitzung mit einem Client zusätzliche Informationen über diese Sitzung (z. B. mit wem er gerade kommuniziert oder welche Aktionen während der Sitzung durchgeführt werden). Sitzungen sind aber nicht zwingend notwendig für Client-Server-Kommunikation bzw. Kommunikation zwischen Systemen im Allgemeinen.

Die Nachrichten des Clients stellen in dieser Kommunikation überwiegend Anfragen dar, auf die der Server mit Bestätigungen oder Fehlermeldungen antwortet. Dabei werden in einigen Fällen auch weitere Informationen oder Objekte mitgeliefert – hier beispielsweise die angefragte Liste der vorhandenen Getränke oder die bestellte Tasse Kaffee. Auf der technischen Ebene werden solche Daten in der Netzwerkkommunikation als Nutzdaten (engl. payload) bezeichnet: Nutzdaten sind also Teile einer Nachricht, die keine direkte Steuer- oder Protokollinformation enthalten, sondern weitere zu übermittelnde Informationen darstellen, z. B. HTML-Dokumente, Bilder oder Datensätze, die vom Client angefragt wurden oder auf den Server hochgeladen werden sollen.

Ausnahmebehandlung

Ein Kommunikationsprotokoll muss ebenfalls genau festlegen, wie mit Fehlern und Ausnahmesituationen umgegangen wird – also beispielsweise mit Anfragen, die nicht beantwortet werden können, unverständlichen Anfragen oder Nachrichten, die in einer anderen Reihenfolge als im Protokoll vorgesehen verschickt werden. In der Client-Server-Kommunikation antwortet der Server auf ungültige Anfragen des Clients in der Regel mit unterschiedlichen Fehlermeldungen.

Die folgenden Beispiele zeigen, welche Ausnahmesituationen bei der Kommunikation im Café auftreten könnten. Zum einen könnte die Kundin einen Artikel bestellen, der nicht vorhanden ist. In diesem Fall antwortet die Bedienung mit der spezifischen Fehlermeldung “Gibt’s nicht!” und das Gespräch geht weiter:

Image

Zum anderen könnte die Kundin eine komplett unvorhergesehene Anfrage stellen, die laut Protokoll keinen Sinn macht. Hier antwortet die Bedienung mit der generischen Fehlermeldung “Häh?!”:

Image

Eine weitere Ausnahmesituation wurde oben bereits gezeigt: Wenn die Bedienung eine Anfrage von einer Kundin erhält, während sie sich gerade in einer Sitzung mit einer anderen Person befindet, antwortet sie mit der spezifischen Fehlermeldung “Bin beschäftigt!” – so weiß die Kundin, dass sie es zu einem späteren Zeitpunkt noch einmal versuchen sollte:

Image

Eine andere Situation ergibt sich, wenn die Bedienung nach einem Gespräch einschläft (oder in der Realität: wenn ein Server zwischenzeitlich ausfällt oder abgeschaltet wird). In diesem Fall wird die Kundin auf eine Anfrage wie “Hallo?”, um ein Gespräch zu beginnen, gar keine Antwort erhalten. In der Praxis werden solche Situationen in der Regel dadurch gelöst, dass ein Client nur eine gewisse Dauer (z. B. maximal 5 Sekunden) auf eine Antwort des Servers wartet, bevor eine Zeitüberschreitung (engl. timeout) auftritt und der Client den Wartevorgang abbricht. Anschließend kann er die Anfrage entweder erneut abschicken oder die Kommunikation abbrechen:

Image

Timeouts können aber auch während einer Sitzung mit laufendem Server auftreten – bei der Kommunikation im Internet beispielsweise dann, wenn Datenpakete einer Anfrage auf dem Übertragungsweg verlorengehen.

Protokollspezifikation

Fassen wir also einmal die Spezifikation des Café-Protokolls zusammen, indem der generelle Ablauf einer Sitzung beschrieben wird und alle Nachrichten mit den jeweils möglichen Folgenachrichten aufgelistet werden.

Wir beginnen mit den Nachrichten zur Steuerung der Sitzungen:

  • Eine Sitzung wird mit der Anfrage “Hallo?” vom Client eingeleitet.
    • Der Server bestätigt mit “Hallo!” , falls er bereit ist, eine neue Sitzung zu beginnen.
    • Anderenfalls lehnt er die Sitzung ab und antwortet mit der Fehlermeldung “Bin beschäftigt!”.
  • Eine Sitzung wird durch die Nachricht “Tschüß!” vom Client beendet.
    • Der Server bestätigt das Ende der Sitzung mit “Tschüß!”.

Der Server befindet sich hier also immer in einem von zwei Zuständen:

  • bereit: Der Server hat momentan keine laufende Sitzung, kann also eine neue Sitzung annehmen.
  • beschäftigt mit Client X: Der Server befindet sich momentan in einer Sitzung mit Client X.

Während einer Sitzung kann der Client die folgenden Anfragen beliebig oft und in beliebiger Reihenfolge verschicken:

  • Der Client kann mit der Nachricht “Welche Name der Artikelgruppe gibt es?” die verfügbaren Artikel einer Artikelgruppe (z. B. Getränke, Kuchen oder Snacks) abfragen.
    • Der Server antwortet mit der Bestätigung “Mal sehen!” und der Liste der Artikelnamen (als Nutzdaten), wenn die Artikelgruppe bekannt ist.
    • Anderenfalls antwortet er mit der Fehlermeldung “Gibt’s nicht!”.
  • Der Client kann mit der Nachricht “Ich nehme Name des Artikels.” einen Artikel kaufen.
    • Der Server bestätigt mit “OK!” und dem gewünschten Artikel (als Nutzdaten), falls dieser bekannt und vorhanden ist.
    • Anderenfalls antwortet er auch hier mit der Fehlermeldung “Gibt’s nicht!”.

Wenn sich der Server gerade im Zustand “beschäftigt mit Client X” befindet, antwortet er auf alle Anfragen anderer Clients mit der Fehlermeldung “Bin beschäftigt!”.

In allen anderen Fällen – also auf unverständliche (das heißt: falsch formatierte) oder unbekannte (das heißt: im Protokoll nicht spezifizierte) Anfragen oder Anfragen zum falschen Zeitpunkt (z. B. “Tschüß!” ohne dass eine Sitzung läuft) – antwortet der Server mit der generischen Fehlermeldung “Häh?!”.

Wie Sie vielleicht bereits festgestellt haben, sieht das Protokoll der Einfachheit halber keine Bezahlung von bestellten Artikeln vor – in diesem Café ist alles umsonst. Anderenfalls müsste das Protokoll um weitere Nachrichten, mögliche Abläufe, Ausnahmebehandlungen und Fehlermeldungen zum Bezahlen ergänzt werden.

Ähnlich werden auch reale Protokolle für die Kommunikation zwischen Informatiksystemen beschrieben, wobei diese Spezifikationen natürlich deutlich technischer und umfangreicher sind. Hier spielen beispielsweise – je nach Aufgabenbereich des Protokolls – auch Aspekte wie die digitale Codierung der Daten, erwartete Datentypen und Wertebereiche von Anfrageparametern oder die physikalische Umsetzung der Datenübertragung eine Rolle.2

Protokolle in der Praxis

Das Café-Beispiel ist zwar anschaulich, aber auch etwas künstlich, da alltägliche Kommunikation normalerweise nicht nach strikten syntaktischen Regeln abläuft. Für ein realistischeres Szenario könnte etwa die Kommunikation mit einem IT-System, z. B. einem Geld- oder Ticketautomaten oder einem einfachen Sprachassistenzsystem betrachtet werden.

Image

Generell werden Kommunikationsprotokolle in der Praxis verwendet, um die Datenübertragung zwischen Informatiksystemen zu regeln und Standards aufzustellen, auf die in der Entwicklung von Kommunikationssystemen zurückgegriffen werden kann. So regeln etwa Druckprotokolle den Datenaustausch zwischen einem Rechner und einem Drucker, um digitale Dokumente auszudrucken, während Netzwerkprotokolle den Datenaustausch in Rechnernetzen beschreiben – womit sie eine der Grundlagen des Internets darstellen. Bekannte Anwendungsprotokolle im Internet sind etwa HTTP, das verwendet wird, wenn Sie über ihren Webbrowser Dokumente von einem Webserver herunterladen, oder Protokolle wie POP3 oder IMAP zum Abrufen von E-Mails von einem Mailserver.

Reale Protokolle unterscheiden sich dabei sehr stark in ihren Zielen und ihrer Komplexität: So gibt sehr beispielsweise sehr einfache Druckprotokolle, die nur die reinen Druckdaten übermitteln, während umfangreichere Druckprotokolle auch Aspekte wie Datenkompression und -verschlüsselung, Fehlerkorrektur oder Auftragssteuerung nach Priorität, Druckkosten und -dauer mit berücksichtigen.

Protokollarchitektur

Dieser Abschnitt befindet sich noch im Aufbau.

Sender-Empfänger-Modell

Zur Beschreibung von Kommunikationsprozessen wird hier das Sender-Empfänger-Modell verwendet – ein einfaches Kommunikationsmodell, das den Austausch von Nachrichten zwischen zwei Systemen, dem Sender und dem Empfänger, beschreibt. Die Nachrichten sind Daten bzw. codierte Informationen, die vom Sender zum Empfänger über ein physikalisches Übertragungsmedium geschickt werden. In einem typischen Kommunikationsablauf nehmen die Kommunikationspartner abwechselnd die Rolle von Sender und Empfänger an.

Informationen werden dabei mittels physikalischer Signale übertragen, beispielsweise durch elektrische, optische oder akustische Signale oder elektromagnetische Wellen. Technisch gesehen übermittelt der Sender das Signal in Form einer physikalischen Größe (z. B. Spannung oder Strom bei elektrischen Signalen) an den Empfänger, der das eingehende Signal misst und interpretiert.

Beispiel: Morse-Schach

Protokollstapel

Ein Protokollstapel ist in der Datenübertragung eine Architektur von Kommunikationsprotokollen, in der Protokolle als Schichten übereinander angeordnet sind. Jede Schicht nutzt zur Erfüllung ihrer Aufgaben die Funktionen (Dienste) der jeweils darunterliegenden Schicht und stellt der jeweils über ihr liegenden Schicht ihre Dienste bereit.

Ausblick: Internetprotokollstapel

Zur Einordnung der Netzwerkprotokolle für die Kommunikation im Internet wird der Internetprotokollstapel verwendet. Hier werden die Protokolle in vier Schichten eingeteilt: Anwendungsschicht, Transportschicht, Internetschicht (auch: Vermittlungsschicht) und Netzzugangsschicht.

  • Anwendung: Die Protokolle dieser Schicht beschreiben, wie Anwendungsprogramme auf verschiedenen Rechnern miteinander kommunizieren (z. B. ein Webbrowser mit einem Webserver, oder ein Mailprogramm mit einem Mailserver).
  • Transport: Die Protokolle dieser Schicht beschreiben, wie die End-Kommunikationspartner (also Anwendungen auf den Endgeräten) Daten austauschen, unabhängig vom anwendungsspezifischen Inhalt der Nachrichten. Diese Art der Kommunikation wird als Ende-zu-Ende-Kommunikation bezeichnet – hierbei spielt es keine Rolle, auf welchem Weg die Datenpakete einer Nachricht durch das Netzwerk kommen.
  • Internet/Vermittlung: Im Internet sind die miteinander kommunizierenden Endgeräte in der Regel nicht direkt verbunden, sondern befinden sich meist in verschiedenen Netzwerken, die über viele Zwischengeräte verbunden sind. Die Protokolle dieser Schicht beschreiben, wie ein Datenpaket von Punkt zu Punkt durch die Netzwerke gelangt. Ein wichtiger Bestandteil dieser Protokolle ist die Wegefindung (Routing).
  • Netzzugang: Die Protokolle dieser Schicht beschreiben, wie Daten zwischen Geräten ausgetauscht werden, die sich im selben Netzwerkabschnitt befinden, also direkt per Kabel, WLAN oder Verteiler miteinander verbunden sind. Diese Schicht beinhaltet die Übertragungsschicht, deren Protokolle beschreiben, wie Binärdaten zwischen zwei direkt verbundenen Systemen über ein physikalisches Medium übertragen werden.

  1. Dieses und die folgenden Bilder wurden erstellt unter Verwendung von Grafiken von Freepik↩︎

  2. Um eine Vorstellung davon zu bekommen, können Sie einmal einen Blick in die Spezifikation des Protokolls HTTP (Hypertext Transfer Protocol) werfen, das beschreibt, wie Anwendungsdaten im Internet ausgetauscht werden (z. B. um Webseiten mit einem Webbrowser abzurufen): https://datatracker.ietf.org/doc/html/rfc7540 ↩︎

4.3 Netzwerke

Grundlagen und Aufbau

Das Internet ist ein “Netzwerk von Netzwerken” bzw. konkreter ein hierarchisch aufgebautes Netzwerk aus verschiedenen Geräten. Die kleinsten Netzwerke stellen dabei die sogenannten lokalen Netzwerke dar.

LAN

Ein lokales Netzwerk (engl. local area network oder kurz LAN) ist in der Regel ein Netzwerk, das sich auf eine kleinere räumliche Umgebung beschränkt, etwa einen privaten Haushalt (“Heimnetz”), eine Schule oder Firma. Ein LAN ist üblicherweise kabelgebunden, ein drahtloses lokales Netzwerk, in dem die Geräte per Funk kommunizieren, wird dagegen als WLAN (engl. wireless LAN) bezeichnet.1

Netzwerkkomponenten

Sehen wir uns dazu zunächst die Geräte an, aus denen solche Netzwerke – beispielsweise auch das Netzwerk bei Ihnen zuhause – zusammengesetzt sind, und welche verschiedenen Funktionen sie erfüllen.

Image

Wir finden hier zunächst einmal Geräte wie PCs, Laptops und Smartphones, also die Endgeräte, mit denen Sie interagieren, beispielsweise um auf das Internet zuzugreifen. Eventuell kommunizieren aber auch Geräte innerhalb des lokalen Netzwerks direkt untereinander, z. B. wenn Sie einen Netzwerkdrucker verwenden, der von allen Rechnern im LAN gemeinsam genutzt wird.

Router und Modem

In Ihrem Heimnetzwerk befindet sich üblicherweise ein Gerät, das als Router bezeichnet wird, über das alle anderen Geräte miteinander kommunizieren können und das gleichzeitig die Schnittstelle “nach außen”, also ins Internet darstellt.2

Um den Router mittels z. B. DSL, Glasfaser oder Kabelnetz mit dem Internet zu verbinden (oder genauer gesagt: mit der Vermittlungsstelle bei Ihrem Internetdienstanbieter) wird ein weiteres Gerät benötigt: das Modem. Mittlerweile enthalten die meisten Router-Geräte (insbesondere wenn Sie von Ihrem Internetdienstanbieter zur Verfügung gestellt werden) aber bereits ein integriertes Modem.

An einen solchen Router können üblicherweise mehrere Endgeräte per LAN-Kabel oder drahtlos per Funk angeschlossen werden.3 Inzwischen sind die meisten Router, die in Heimnetzen zum Einsatz kommen, WLAN-tauglich, unterstützen also auch drahtlose Verbindungen. Die folgende Abbildung stellt die Vorder- und Rückseite eines handelsüblichen DSL-Routers mit WLAN (also eines WLAN-tauglichen Routers mit integriertem DSL-Modem) dar:4

Image Image

Hier lassen sich 4 Endgeräte per LAN-Kabel (über die 4 gelben Anschlüsse rechts) und beliebig viele weitere per Funk anschließen. Über den linken Anschluss wird das Gerät mit dem DSL-Anschluss verbunden, über den das Internet erreichbar wird.

Was aber, wenn weitere Endgeräte per LAN-Kabel an das Gerät angeschlossen werden sollen? Oder wenn Sie ganz auf einen Router verzichten und stattdessen ein lokales Netzwerk ohne Internetanbindung einrichten möchten?

Switch

Um mehrere Geräte direkt miteinander zu verbinden, gibt eine weitere Komponente in Netzwerken: den sogenannten Switch, den sie vielleicht aus dem Computerraum Ihrer Schule kennen. Über ein Switch lassen sich einfach mehrere Endgeräte (oder auch weitere Switches) in der Regel über LAN-Kabel zusammenschließen, so dass sie untereinander kommunizieren können. Sie bilden dann einen Teil eines Netzwerks bzw. ein Netzwerksegment. So könnte beispielsweise ein Switch mit 5 LAN-Anschlüssen verwendet werden, um jeweils 4 Rechner mit einem LAN-Anschluss eines Routers (oder eines weiteres Switches) zu verbinden:

Image

Die folgende Abbildung zeigt handelsübliche Switches verschiedener Größe (5 bis 24 LAN-Anschlüsse pro Switch), die für kleinere Heimnetze oder größere Firmennetzwerke geeignet sind:5

Image

Ein drahtloser Switch (also ein Switch, mit dem sich Geräte per Funk verbinden können) wird üblicherweise als (Wireless) Access Point (engl. für “(drahtloser) Zugangspunkt”, kurz AP) bezeichnet.

Router als Multifunktionsgerät

Was ist nun der Unterschied zwischen einem Router-Gerät wie dem oben dargestellten DSL-Router, an den ja auch mehrere Endgeräte angeschlossen werden können, und einem Switch? Strenggenommen stellt ein Router im eigentlichen Sinne nur die Verbindung zwischen verschiedenen Netzwerken her – also beispielsweise zwischen Ihrem Heimnetz und dem Netzwerk Ihres Internetdienstanbieters oder zwischen mehreren lokalen Netzwerken innerhalb eines größeren Unternehmens.6 Ein Switch verbindet dagegen Geräte innerhalb eines lokalen Netzwerks.

Tatsächlich ist in so gut wie alle umgangssprachlich als “Router” bezeichneten Geräte auch ein Switch (und in WLAN-Router zusätzlich ein Access Point) integriert, damit sich mehrere Geräte mit dem Router verbinden können. Das oben dargestellte Gerät besteht also in Wirklichkeit aus vier Komponenten: Es kombiniert einen Router, einen Switch mit Anschlüssen für 4 Endgeräte, einen Access Point für WLAN, sowie ein DSL-Modem.

Image

Der “Router”, der bei Ihnen im Wohnzimmer, Büro oder im Computerraum Ihrer Schule steht, kann aber noch viel mehr: Diese Geräte beinhalten in der Regel auch einen Server, auf dem verschiedene Anwendungsdienste laufen – zum Beispiel DNS zur Übersetzung von URLs in IP-Adressen oder DHCP zur automatischen Vergabe von IP-Adressen an Geräte im lokalen Netzwerk. Oft läuft auf dem Gerät auch ein kleiner Webserver, so dass Sie im Browser über die IP-Adresse des Routers eine grafische Benutzeroberfläche aufrufen können, über die das Netzwerk konfiguriert werden kann.

Üblicherweise enthalten Router daneben auch Sicherheitskomponenten wie etwa eine Firewall, die unerwünschte Zugriffe auf das Netzwerk blockiert (dazu mehr im Kapitel “Netzwerksicherheit”).

Zusammenfassung

Fassen wir also abschließend auf einer abstrakteren Ebene die verschiedenen Komponenten und ihre Rollen in Netzwerken zusammen und grenzen sie voneinander ab:7

ImageEin Switch verbindet Geräte zu einem Netzwerksegment und erlaubt es ihnen, untereinander Daten auszutauschen.
ImageEin Router (auch: Vermittlungsrechner) verbindet verschiedene Netzwerke miteinander (z. B. verschiedene LAN untereinander oder ein LAN mit dem Internet).
ImageEin Modem stellt die Verbindung zwischen Routern über weite Übertragungswege her (z. B. per DSL, Glasfaser oder Kabelnetz).
ImageEin Endgerät bildet den Netzabschluss und stellt in der Regel die Schnittstelle zur Benutzerin/zum Benutzer dar, ohne selbst notwendiger Bestandteil des Netzes zu sein (z. B. ein PC, Smartphone oder Netzwerkdrucker).
ImageEin Server ist ein Endgerät, auf dem hauptsächlich Anwendungen laufen, die auf Anfragen von Client-Anwendungen auf anderen Endgeräten warten und diese über das Netwzerk beantworten.

Die kleinsten lokalen Netzwerke – die Netzwerksegmente – bestehen nur aus Switches (bzw. APs) und Endgeräten, sowie ggf. einer Schnittstelle zu einem Router, der es mit der Außenwelt verbindet. Diese Routerschnittstelle wird als Gateway (engl. für “Ein-/Ausfahrtstor”) des lokalen Netzwerks bezeichnet. Mittels Routern (und Modems) werden kleinere Netzwerke hierarchisch zu komplexeren, weiträumigeren Netzwerken verbunden – von kleinen lokalen Nerkwerken (LAN) über städteumfassenden “Metropolitan Area Networks” (MAN) und länderumfassende “Wide Area Networks” (WAN) bis hin zu weltumspannenden “Global Area Networks” (GAN), die das Internet bilden.

Adressierung im Netzwerk

IP-Adressen

Eine der wichtigsten Aufgaben des Internetprotokolls (IP) besteht darin, einzelne Geräte im Netzwerk zu finden. Dazu muss jedes Gerät im Netzwerk mit einer eindeutigen Adresse identifiziert werden, die als IP-Adresse bezeichnet wird. Diese Adressen liegen als Bitfolgen aus Einsen und Nullen vor, damit sie von digitalen Geräten möglichst einfach verarbeitet werden können.

Im Protokoll IPv4 ist jede IP-Adresse 32 Bit (= 4 Byte) lang. Der besseren Lesbarkeit halber werden diese Bitfolgen üblicherweise in Dezimalpunktschreibweise (engl. dotted decimal notation) angegeben: Hierbei wird jedes Byte in eine Dezimalzahl umgewandelt und die Zahlen mit einem Punkt dazwischen notiert, z. B. 192.168.1.20 statt 11000000 10101000 00000001 00010100.

Subnetze

Eine wichtige Voraussetzung für diese Adressen ist, dass sie hierarchisch aufgebaut sind, damit an ihnen abgelesen werden kann, zu welchem Netzwerkbereich sie gehören – ähnlich einer Telefonnummer, die aus Ländervorwahl, Ortsvorwahl und Rufnummer besteht. Eine IP-Adresse beginnt dazu mit einem Netzwerkteil (quasi die “Vorwahl”), gefolgt von einem Geräteteil (die “Rufnummer”). Die Werte der Netzwerkteil-Bits sind dabei für alle Adressen innerhalb desselben Netzwerks festgelegt (und damit für alle Geräte im Netzwerk identisch), während die Werte der Geräteteil-Bits frei wählbar sind. Ein solcher zusammengehöriger Netzbereich innerhalb eines größeren Netzwerks, der einen abgeschlossenen IP-Adressbereich verwendet, wird auch als Subnetz bezeichnet. Subnetze können in weitere Subnetze unterteilt werden. Lokale Netzwerke (LAN) bzw. Netzwerksegmente stellen dabei die kleinsten Subnetze dar.

Ein IP-Adressbereich lässt sich in Kurzform in Präfixnotation angeben, also durch die erste IP-Adresse im Bereich, gefolgt von der Länge des Netzwerkteils in Bit (die sogenannte Präfixlänge), z. B. 192.168.1.0/24 für den Adressbereich von 192.168.1.0 bis 192.168.1.255.

Beispiel: Für die Uni Kiel ist der IP-Adressbereich von 134.245.0.0 bis 134.245.255.255 registriert. Alle Adressen beginnen mit 134.245 bzw. mit der Bitfolge 10000110 11110101. Die ersten 16 Bit stellen hier den Netzwerkteil dar, an dem erkannt werden kann, dass es sich um eine Adresse der Uni Kiel handelt. Die Präfixnotation dieses Bereich lautet demnach 134.245.0.0/16.

Innerhalb dieses Adressbereichs können weitere Teilbereiche unterschiedlicher Größe festgelegt werden: So könnte beispielsweise der Bereich 134.245.10.0 bis 134.245.10.255 für das lokale Netzwerk der Unibibliothek reserviert sein (in Präfixnotation 134.245.10.0/24). Für dieses Subnetz stellen die ersten 24 Bit den Netzwerkteil dar, alle Adressen im Subnetz der Unibibliothek beginnen also mit 134.245.10 bzw. mit der Bitfolge 10000110 11110101 00001010.

Bei der Einrichtung eines Netzwerks wird jedem Endgerät eine IP-Adresse aus dem Adressbereich des Netzwerks zugewiesen. Dabei muss jedem Gerät eine unterschiedliche IP-Adresse zugewiesen werden, damit die Geräte im Subnetz eindeutig identifiziert werden können.

Die erste und letzte Adresse aus dem Netzwerk-Adressbereich werden nicht vergeben, da sie Sonderbedeutungen haben: Die erste Adresse steht für das Netzwerk selbst (“Netzwerkadresse”), die letzte Adresse ist die sogenannte Broadcast-Adresse. Pakete, die an diese Adresse verschickt werden, werden an alle Geräte im Netzwerk weitergeleitet statt an ein bestimmtes.

Die Größe eines Adressbereich lässt sich direkt aus der Präfixlänge berechnen: Beim Subnetz 134.245.10.0/24 mit einer Präfixlänge von 24 Bit (= Länge des Netzwerkteils) bleiben 8 Bit übrig (= Länge des Geräteteils), die frei wählbar sind, also umfasst das Subnetz 28 = 256 Adressen (von denen max. 254 an Geräte vergeben werden können). Das Subnetz 134.245.0.0/20 umfasst dagegen 212 = 4096 Adressen, da 12 Bit auf den Geräteteil entfallen.

Subnetzmaske

Der Adressbereich für ein Subnetz wird oft auch mit Hilfe der sogenannten Subnetzmaske angegeben: eine Bitfolge mit derselben Länge wie die IP-Adressen, die mit einer bestimmten Anzahl von 1-Bits beginnt, gefolgt von 0-Bits. Die 1-Bits geben an, welche Bits der IP-Adresse fest sind (der Netzwerkteil), während die 0-Bits angeben, welche Bits frei gewählt werden können (der Geräteteil).

Jedes Gerät im Netzwerk kennt seine eigene IP-Adresse sowie die Subnetzmaske und kann daraus den Adressbereich seines Subnetzes ermitteln. Die Subnetzmaske wird üblicherweise im selben Format angegeben wie die IP-Adresse, bei IPv4 also in Dezimalpunktschreibweise.

Beispiel 1: Einem Gerät im Netzwerk wurde die IP-Adresse 192.168.1.20 zugewiesen. Die Subnetzmaske lautet 255.255.255.0.

Um den Adressbereich des Netzwerks zu ermitteln, betrachten wir die Binärdarstellung der Subnetzmaske: 11111111 11111111 11111111 00000000. Das bedeutet, die ersten 24 Bit (= 3 Byte) sind bei allen Adressen im Netzwerk gleich, die letzten 8 Bit können frei gewählt werden. Jede IP-Adresse in diesem Subnetz beginnt also mit 192.168.1., gefolgt von einer Zahl zwischen 0 und 255. Die Netzwerkadressen umfassen also den Bereich 192.168.1.0 bis 192.168.1.255.

Die Adresse 192.168.1.0 steht für das Netzwerk selbst, die Adresse 192.168.1.255 für einen Broadcast im Netzwerk. Es können also bis zu 254 IP-Adressen für Geräte im Netzwerk vergeben werden, nämlich die Adressen 192.168.1.1 bis 192.168.1.254.

Beispiel 2: Einem Gerät im Netzwerk wurde die IP-Adresse 134.245.180.100 zugewiesen. Die Subnetzmaske lautet 255.255.240.0.

Um den Adressbereich des Netzwerks zu ermitteln, betrachten wir die Binärdarstellung der Subnetzmaske: 11111111 11111111 11110000 00000000. Das bedeutet, die ersten 20 Bit (= 2 Byte und die ersten 4 Bit des 3. Bytes) sind bei allen Adressen im Netzwerk gleich, die letzten 12 Bit können frei gewählt werden.

Hier ist es etwas komplizierter, den Adressbereich zu ermitteln. Die ersten beiden Byte sind fest, die Netzwerkadresse beginnt also mit 134.245. Beim nächsten Byte sind die ersten 4 Bit fest. Die Binärdarstellung der Dezimalzahl 180 lautet 10110100. Die erste 8-Bit-Binärzahl, die mit 1011 beginnt, ist 10110000 (dezimal 176), die letzte ist 10111111 (dezimal 191). Das letzte Byte kann komplett frei gewählt werden. Die Netzwerkadressen umfassen hier also den Bereich 134.245.176.0 bis 134.245.191.255.

Private Netzwerke

Die folgenden IP-Adressbereiche sind für private Netzwerke reserviert:

  • 192.168.0.0 - 192.168.255.255 (umfasst 65 536 Adressen)
  • 172.16.0.0 - 172.31.255.255 (umfasst ca. 1 Mio. Adressen)
  • 10.0.0.0 - 10.255.255.255 (umfasst ca. 16,7 Mio. Adressen)

Private Netzwerke können wiederum in Subnetze aufgeteilt werden (z. B. ein privates Netzwerk mit dem Adressbereich 192.168.0.0/16 in 256 Subnetze mit jeweils 256 Adressen).

Diese IP-Adressen sind nicht global eindeutig, sondern können in beliebig vielen lokalen Netzwerken vorkommen. Da sie nicht eindeutig sind, können diese Adressen nicht über lokale Netzwerkgrenzen hinaus, also auch nicht im Internet geroutet werden. Stattdessen werden Rechner, die solche privaten IP-Adressen haben, im Internet durch die IP-Adresse ihres Gateway-Routers repräsentiert.

MAC-Adressen

Eine MAC-Adresse entspricht also in etwa der Personalausweis-ID einer Person: Sie ist global eindeutig, fest mit der Person verbunden und unveränderlich. Die IP-Adresse entspricht dagegen der Postadresse einer Person: Sie ist hierarchisch aufgebaut (Land, PLZ/Ort, Straße, Hausnummer, Name am Briefkasten), gibt Ausschluss darüber, wo sich eine Person befindet und ändert sich, wenn die Person umzieht. Beide Adressierungsarten können unter verschiedenen Umständen nützlich sein.


  1. Im englischen Sprachraum ist dagegen der Begriff “Wi-Fi” statt “WLAN” für Funknetzwerke üblich, während dieser Begriff im deutschen Sprachraum nur für den Standard verwendet wird, der die in WLAN genutzte Funkübertragung beschreibt. ↩︎

  2. Bekannte Modelle, die sich vielleicht auch in Ihrem Haushalt oder in Ihrer Schule wiederfinden, sind etwa die FRITZ!Box oder die Vodafone EasyBox. ↩︎

  3. Der Begriff “LAN-Kabel” wird allgemein für Kabel zum Verbinden von Geräten in Rechnernetzen verwendet. Üblicherweise sind damit aber konkret Ethernet-Kabel gemeint. Ethernet ist dabei die Bezeichnung für eine Technik und das dazugehörige Protokoll zur kabelgebundenen Datenübertragung (analog zu WLAN oder “Wi-Fi” für Funkverbindungen). ↩︎

  4. Quelle: Website von AVM, Produktseite FRITZ!Box 7530 ↩︎

  5. Quelle: Website von D-Link, Produktseite D-LINK DGS-1100 ↩︎

  6. Router werden daher auch als “Vermittlungssrechner” bezeichnet, weil sie zwischen verschiedenen Netzwerken vermitteln. ↩︎

  7. Die hier verwendeten Grafiken stammen aus der Lernsoftware Filius (siehe Linksammlung) und werden dort zur Repräsentation der Komponenten Switch, Router (in Filius: Vermittlungsrechner), Modem und Client-Rechner (in Filius: Notebook) verwendet. ↩︎

4.4 Kommunikation im Netzwerk

Kommunikation im Subnetz

Bei der Konfiguration eines lokalen Netzwerks wird jedem Endgerät eine eindeutige IP-Adresse im Adressbereich des Subnetzes zugewiesen. Außerdem kennt jedes Gerät die Subnetzmaske und in der Regel die IP-Adresse des Gateway-Routers im lokalen Netzwerk, der die Schnittstelle nach außen darstellt (sofern vorhanden). Diese Informationen können beispielsweise manuell von der Person festgelegt werden, die das lokale Netzwerk administriert (“Netzwerkadmin”).

Image

Jedes Endgerät in einem Netzwerksegment kennt üblicherweise alle anderen Endgeräte, die sich im selben Segment befinden. Konkret bedeutet das, dass auf jedem Endgerät eine Tabelle der IP-Adressen und zugehörigen MAC-Adressen aller Geräte im Subnetz gespeichert ist – die sogenannte ARP-Tabelle (auch “ARP Cache”, siehe Address Resolution Protocol). In einem Router können mehrere ARP-Tabellen gespeichert sein, je eine für jedes Netzwerksegment, mit dem er verbunden ist.

Image

Das Versenden von Datenpaketen innerhalb des Subnetzes findet größtenteils über Protokolle der Netzzugangsschicht statt – da hier keine Wegefindung über Netzwerkgrenzen hinaus stattfindet, werden die entsprechenden Protokolle der Internetschicht (Vermittlungsschicht) nicht benötigt.

Wenn ein Endgerät eine Nachricht (konkreter: ein IP-Paket) an eine bestimmte Ziel-IP-Adresse verschicken möchte, kann es anhand der Subnetzmaske erkennen, ob das Ziel im selben Subnetz liegt: In diesem Fall stimmt der Netzwerkteil der eigenen IP-Adresse mit dem Netzwerkteil der Ziel-IP-Adresse überein. Ist das der Fall, schaut das sendende Gerät in seiner ARP-Tabelle unter der Ziel-IP-Adresse nach, um die Ziel-MAC-Adresse zu ermitteln. Dann verpackt es die Nachricht in ein Paket der Netzzugangsschicht, adressiert es mit der eigenen MAC-Adresse als Absender und der MAC-Adresse des Ziels als Empfänger und übertragt es.

Switching

Wenn Sender und Empfänger direkt verbunden sind, wird das Paket direkt übertragen. Wenn das Paket dagegen bei einem Switch ankommt, muss dieser entscheiden, an welchen seiner Ausgänge er das Paket weiterleitet – er muss also wissen, hinter welchem Ausgang das Gerät mit der Ziel-MAC-Adresse steckt. Diese Information ist in einer internen Tabelle des Switches gespeichert (Switching-Tabelle). Kennt der Switch die Ziel-MAC-Adresse nicht, leitet er das Paket einfach an alle Ausgänge weiter (alle Geräte, die das Paket empfangen und feststellen, dass die Ziel-MAC-Adresse nicht mit ihrer eigenen MAC-Adresse übereinstimmt, ignorieren das Paket).

Image

Um einem Switch beizubringen, welche MAC-Adressen im Netzwerksegment vorhanden sind und wo die Geräte liegen, gibt es zwei Möglichkeiten: statisch oder dynamisch. Bei der statischen Variante trägt die Person, die das lokale Netzwerk administriert, die MAC-Adressen in jedem Switch manuell ein. In der dynamischen Variante lernt der Switch diese Information selbst: Kommt bei einem Switch ein Paket mit einer Absender-MAC-Adresse an, die er noch nicht kennt, trägt er diese Adresse in seiner internen Tabelle mit der Nummer des Ausgangs ein, über die er das Paket empfangen hat.

Üblicherweise enthält die Switching-Tabelle eines Switches also die meiste Zeit über die MAC-Adressen aller Geräte im selben Netzwerksegment. Bei dynamischem Aufbaue der Switching-Tabellen sind die Einträge in der Regel mit einem Ablaufzeitpunkt versehen, der Switch “vergisst” nach einiger Zeit also, wo welches Gerät liegt und muss diese Information neu lernen. So kann auf Änderungen im Netzwerkaufbau reagiert werden.

Address Resolution Protocol

Der Name der ARP-Tabelle, die für jede IP-Adresse im Subnetz die MAC-Adresse des entsprechenden Gerät enthält, leitet sich vom Address Resolution Protocol (engl. für “Adressauflösungsprotokoll”, kurz ARP) ab. Dieses Protokoll erlaubt es jedem Endgerät im Subnetz die MAC-Adressen der anderen Geräte im Subnetz abzufragen.

Beispiel: Angenommen, Gerät A möchte ein Datenpaket an Gerät D mit der IP-Adresse 192.168.1.18 schicken, kennt dessen MAC-Adresse aber nicht. Gerät A schickt nun eine Adressanfrage (ARP Request) an alle anderen Geräte im Subnetz (siehe Lokaler Broadcast). Wenn ein Gerät die MAC-Adresse zur angefragten IP-Adresse kennt, weil sie in seiner lokalen ARP-Tabelle steht, schickt es diese Information als Antwort an Gerät A zurück (ARP Reply). Spätestens Gerät D muss diese Anfrage beantworten können, es können aber auch Antworten von mehreren Geräten zurückkommen.

Sobald Gerät A eine Antwort bekommt, trägt es die nun bekannte MAC-Adresse zur IP-Adresse 192.168.1.18 in seine ARP-Tabelle ein. Weitere folgende Antworten können nun ignoriert werden. Anschließend kann das Paket von Gerät A an D wie oben beschrieben über die Netzzugangsschicht übertragen werden.

Lokaler Broadcast

Um eine Nachricht an alle anderen Geräte im selben Subnetz zu verschicken (“Broadcast”), wird als Ziel-IP-Adresse 255.255.255.255 und als Ziel-MAC-Adresse FF-FF-FF-FF-FF-FF verwendet. Kommt ein Paket mit dieser speziellen, für Broadcasts reservierten Ziel-MAC-Adresse bei einem Switch an, leitet er es an alle Ausgänge weiter (außer natürlich an den Ausgang, über den er das Paket empfangen hat).

Kommunikation zwischen Netzwerken

Dieser Abschnitt befindet sich noch im Aufbau.

Image

Routing

Routing-Protokolle

Distanzvektor-Algorithmus

Breitensuche

4.5 Anwendungsprotokolle

Im Internet gibt es Computer, die Dienste anbieten, genannt Server und Computer, die diese Dienste in Anspruch nehmen, genannt Clients. Beispielsweise bieten die Server von YouTube den Dienst an, auf dort gespeicherte Videos zuzugreifen oder selbst welche hochzuladen. Einige Netzwerkdienste und die von ihnen verwendeten Anwendungsprotokolle wollen wir hier näher betrachten.

World Wide Web

Das World Wide Web (WWW) ist der Teil des Internets, mit dem wohl die meisten vertraut sind und dem wir bereits im Kapitel Informationsdarstellung im Internet begegnet sind: Es besteht aus Websites aus der ganzen Welt, die durch Hyperlinks untereinander verknüpft sind. Websites werden auf weltweit verteilten Webservern vorrätig gehalten (“gehostet”) und können mit Webbrowsern wie Apple Safari, Google Chrome, Microsoft Edge oder Mozilla Firefox abgerufen werden. Übertragen werden sie mit dem Hypertext Transfer Protocol (HTTP).

HTTP

Ein Client und ein Server, die über HTTP miteinander kommunizieren, tauschen Nachrichten aus, die Anfrage (engl. request, vom Client an den Server) und Antwort (engl. response, vom Server an den Client) genannt werden. Dabei behandelt der Server jede Anfrage völlig isoliert, so als hätte der Client noch nie zuvor eine geschickt und würde nie wieder eine schicken. Protokolle, die sich so verhalten, nennt man zustandslos.

Um trotzdem eine Art von Zustand zu simulieren, werden so genannte Cookies verwendet. Cookies sind kleine Textschnipsel, die mit jeder Anfrage und Antwort ausgetauscht und auf dem Client (z. B. im Datenspeicher des Webbrowsers) zwischengespeichert werden. Mittels Cookies kann der Server beispielsweise verschiedene IDs an Clients vergeben, sie so unterscheiden und serverseitig individuelle Informationen für die Clients speichern. Ein Analogon ist etwa eine Auftragsnummer, die man bei der Bestellung erhält, bei der Bezahlung angeben und bei jeder Reklamation bereithalten muss – wobei man sich natürlich auch jedes Mal mit Namen und Adresse identifizieren kann, was den Vorgang aber unnötig verkompliziert.

Anfragen

In HTTP kann der Client u. a. folgende Anfragen stellen

AnfrageErläuterung
GETDient dazu, eine Ressource vom Server abzufragen. Mit GET können auch Parameter an den Server übertragen werden, die dann in der URL sichtbar werden. GET-Anfragen sollten nicht dazu genutzt werden, Daten zur Speicherung oder Weiterverarbeitung an den Server zu übertragen
POSTDient dazu, Daten zur weiteren Verarbeitung an den Server zu senden. Diese Daten werden nicht in der URL sichtbar, weswegen diese Methode z.B. zum Versand von Login-Daten bevorzugt verwendet werden sollte. POST-Anfragen sollten auch genutzt werden, um Ressourcen auf dem Server zu ändern.
HEADDient ebenfalls zum Datenabruf, allerdings wird nicht der Inhalt einer Web-Ressource, sondern nur Metadaten, die sog. Header abgerufen. Damit kann z.B. geprüft werden, ob eine im Cache zwischengespeicherte Seite noch gültig ist.
PUTDient dazu, eine Ressource auf den Server hochzuladen, wobei die übergebenen Anfragedaten unter der angegebenen URL gespeichert werden. Anders als POST sollen PUT-Anfragen einfach nur Dateien ablegen, statt ggf. komplexere Änderungen anzustoßen.
PATCHDient dazu, ein bestehendes Dokument zu ändern, ohne es wie bei einer PUT-Anfrage vollständig zu ersetzen.
DELETEDient dazu, eine Ressource auf dem Server zu löschen

Die meisten Anfragen sind GET- und POST-Anfragen. Eine Analyse von 2017 ergab, dass rund 77% der HTTP-Anfragen GET- und weitere 20% POST-Anfragen sind. Oft wird POST pauschal für alle Anfragen verwendet, die Daten auf dem Server ändern (also Daten hinzufügen, verändern oder löschen), während GET für rein lesende Anfragen verwendet wird.

Antworten

Jede Antwort eines Webservers beginnt mit einem dreistelligen Statuscode. Die erste Ziffer des Codes gibt dabei Aufschluss über die Art der Antwort:

Erste ZifferArt der Antwort
1__Die Anfrage wird bearbeitet, dies wird aber noch einige Zeit dauern
2__Die Anfrage wurde erfolgreich bearbeitet.
3__Umleitung – die gewünschte Ressource liegt an einem anderen Ort.
4__Die Anfrage ist fehlgeschlagen und es liegt (wahrscheinlich) am Client
5__Die Anfrage ist fehlgeschlagen und es liegt (wahrscheinlich) am Server

Die häufigsten Statuscodes sind

StatuscodeNameErläuterung
100ContinueSignalisiert dem Client, dass eine sehr lange Anfrage noch nicht abgewiesen wurde und der Client mit der Anfrage fortfahren darf.
102ProcessingSignalisiert dem Client, dass die Anfrage akzeptiert worden ist, aber die Bearbeitung bis zur Antwort noch einige Zeit benötigen wird. Dieser Statuscode wird verwendet, um Timeouts zu vermeiden.
200OKDie Anfrage war erfolgreich und das Ergebnis wird in der Antwort übertragen.
204No ContentDie Anfrage war erfolgreich, die Antwort enthält aber bewusst keine Daten.
301/308Moved Permanently/Permanent RedirectDie angefragte Ressource ist dauerhaft an eine neue URL verschoben worden; der Client möge eine neue Anfrage stellen. Link-Shortener-Dienste wie bit.ly oder tinyurl.com reagieren auf Anfragen häufig mit einer 301-Antwort.
302/303/307Found/See Other/Temporary RedirectDie angefragte Ressource ist temporär an eine neue URL verschoben worden.
400Bad RequestDie Anfrage war fehlerhaft aufgebaut
401UnauthorizedDer Client muss sich für diese Anfrage erst autorisieren.
403ForbiddenDer Client hat keine Berechtigung, diese Ressource anzufragen
404Not FoundDie angeforderte Ressource konnte nicht gefunden werden, etwa weil in der Adresse ein Tippfehler vorliegt oder ein Link auf eine inzwischen gelöschte Ressource verweist.
408Request TimeoutInnerhalb des gegebenen Zeitfensters wurde vom Server keine vollständige Anfrage empfangen.
418I’m A Teapot1Der Server ist eine Teekanne.
4512Unavailable For Legal ReasonsDie gesuchte Ressource ist aus rechtlichen Gründen (z.B. Internetzensur) für den Client nicht verfügbar.
500Internal Server ErrorDieser Statuscode wird allgemein bei Serverfehlern zurückgegeben.
502Bad GatewayDer Server hat zur Bearbeitung der Anfrage eine weitere Ressource angefragt, aber keine gültige Antwort erhalten.
503Service UnavailableDer Dienst steht nicht zur Verfügung, z.B. weil der Server überlastet ist.

Im Webbrowser kann man Anfragen und Antworten sichtbar machen, indem man die Entwicklerwerkzeuge öffnet (was in den meisten Browsern mit der Tastenkombination Umschalt+Strg+I bzw. Command+Option+I möglich ist) und den Reiter “Netzwerk” oder “Netzwerkanalyse” auswählt.

Das Entwicklerwerkzeug &ldquo;Netzwerkanalyse&rdquo; in Mozilla Firefox 106

Sobald man dann eine HTTP-Anfrage absendet, etwa indem man eine URL in die Adresszeile eingibt oder einen Link anklickt, werden diese und alle folgenden Anfragen mit den Antworten im Datenfenster aufgelistet.

Das Entwicklerwerkzeug &ldquo;Netzwerkanalyse&rdquo; zeigt einen Seitenaufruf auf inf-schule.de

Zu jeder Anfrage können in der Übersicht der Anfragetyp, der Statuscode, Zielrechner, abgefragte Ressource u.v.m. abgelesen werden

Zoom auf einen Ausschnitt der Liste der Anfragen und Antworten

Mit einem Klick auf eine Anfrage können noch mehr Details dazu betrachtet werden, z.B. die gesendeten Header von Anfrage und Antwort oder die übertragenen Cookies.

Details zu einer Anfrage zum oben abgebildeten Seitenaufruf

DNS

Das Domain Name System (DNS) sorgt dafür, dass Anfragen, die an Domainnamen wie uni-kiel.de oder iqsh.oncampus.de gerichtet sind, auch beim richtigen Rechner ankommen, z. B. verbirgt sich hinter dem Domainnamen uni-kiel.de der Webserver mit der IP-Adresse 134.245.13.22. Diesen Prozess bezeichnet man als Namensauflösung. Die dafür notwendigen Daten, die Resource Records sind weltweit auf vielen sogenannten Nameservern verteilt gespeichert.

Domain-Namen

Die Domainnamen sind rückwärts zu lesen und die dazugehörigen Informationen sind in einer Baumstruktur organisiert:

flowchart TD root --- de & com & org & sh de --- uni-kiel & oncampus com --- google uni-kiel --- informatik & klassalt & praesidium & bwl informatik --- ddi & theorie & zs & se & rtsys zs --- git oncampus --- iqsh sh --- nah google --- mail & maps & images & video

Der Domainname iqsh.oncampus.de ist folgendermaßen zu lesen: de ist die Top-Level-Domain (TLD) und gibt an, dass die Domain in Deutschland registriert ist. Andere Top-Level-Domains sind etwa at für Österreich, sh für St. Helena oder edu für Bildungseinrichtungen. oncampus ist die Domain und gibt an, dass diese Webseite auf einem Computer des E-Learning-Anbieters oncampus liegt. iqsh ist eine Subdomain und identifiziert genau denjenigen unter den Computern von oncampus, auf dem die Seite liegt. Subdomains sind ebenfalls hierarchisch organisiert und schlüsseln die Organisation der Server genauer auf. Beispielsweise ist die Webanwendung GitLab der Arbeitsgruppe Zuverlässige Systeme am Institut für Informatik der Uni Kiel über die Domain git.zs.informatik.uni-kiel.de erreichbar. Eine so zusammengesetzte Namensangabe wird als Fully Qualified Domain Name (FQDN) bezeichnet.

Resource Records

Zu einem Domainnamen können die verschiedensten Informationen bei einem DNS-Server hinterlegt sein. Diese Informationen werden in so genannten Resource Records gespeichert, die jeweils einen bestimmten Typ haben und dort öffentlich zugänglich sind. Einige dieser Typen sind:

TypInformation
AGibt die IPv4-Adresse zu einer Domain an
AAAAGibt die IPv6-Adresse zu einer Domain an
CNAMEGibt den eigentlichen Domainnamen an, der sich hinter einer Alias-Domain verbirgt.
MXGibt den Mailserver an, der für eine bestimmte Domain zuständig ist
NSGibt den Nameserver an, der für die Subdomains einer Domain zuständig ist
SOAGibt den Start of Authority an, d.h. die Stelle, die alle Informationen zu einer Domain ursprünglich verwaltet.
SRVGibt an, welche IP-basierten Dienste innerhalb einer Domain angeboten werden.
TXTKann beliebige Informationen zur Domain in Textform speichern

Mit Kommandozeilenwerkzeugen wie nslookup (für Windows) oder dig/dog (für Linux) können diese Resource Records abgefragt werden.

Verschiedene DNS-Abfragen mit dem Werkzeug dog

DNS-Abfragen

Jede Anfrage an einen Domainnamen muss zuerst in eine IP-Adresse übersetzt werden. Diese Anfrage geht theoretisch zunächst an einen der 13 weltweit verteilten Root-Nameserver, welche die Adressauflösung an der Wurzel des Adressbaums erledigen und in der Regel nur eine Liste der DNS-Server zurückliefern, die für die nächsttiefere Domain zuständig sind (die sogenannten autoritativen Nameserver) und die Anfrage dann weiterverarbeiten.

Wenn ein DNS-Server die Informationen zur angefragten Domain vorrätig hält, wird die gesuchte IP-Adresse direkt zurückgegeben, ansonsten wird die Anfrage entweder iterativ oder rekursiv weitergeleitet.

Iterative Namensauflösung

Iterative Namensauflösung bedeutet, dass der DNS-Server an den Client eine Liste von anderen DNS-Servern weiterleitet, die Genaueres zur gesuchten Domain wissen könnten.

Eine iterative Namensauflösung für die Adresse iqsh.oncampus.de könnte etwa so ablaufen:

sequenceDiagram participant A as Client participant B as b.root-servers.net participant C as z.nic.de participant D as dns-3.dfn.de A->>+B: iqsh.oncampus.de IN A note over A, B: Client fordert IP-Adresse (A) vom Root-Server B->>-A: de 172800 NS IN a.nic.de,
de 172800 NS IN f.nic.de,
de 172800 NS IN l.de.net,
de 172800 NS IN n.de.net,
de 172800 NS IN s.de.net,
de 172800 NS IN z.nic.de note over A, B: Root-Server liefert eine Liste der DNS-Server,
die für .de zuständig sind A->>+C: iqsh.oncampus.de IN A note over A, C: Client fordert IP-Adresse (A) von einem dieser Server,
hier z.nic.de C->>-A: oncampus.de 86400 NS IN dns-1.dfn.de,
oncampus.de 86400 NS IN dns-3.dfn.de,
oncampus.de 86400 NS IN dns.fh-luebeck.de note over A, C: z.nic.de liefert eine Liste der DNS-Server,
die für .oncampus.de zuständig sind. A->>+D: iqsh.oncampus.de IN A note over A, D: Client fordert IP-Adresse (A) von dns-3.dfn.de D->>-A: iqsh.oncampus.de 86400 A IN 193.175.122.162 note over A, D: dns-3.dfn.de liefert die IP-Adresse von iqsh.oncampus.de an den Client

Rekursive Namensauflösung

Rekursive Namensauflösung bedeutet, dass der DNS-Server die Anfrage an einen anderen DNS-Server weiterleitet, wartet, bis er von diesem ein Ergebnis bekommt (das wiederum rekursiv weitergeleitet worden sein könnte) und dieses an den Client zurückliefert.

Eine rekursive Namensauflösung für die Adresse iqsh.oncampus.de könnte etwa so ablaufen:

sequenceDiagram participant A as Client participant B as b.root-servers.net participant C as z.nic.de participant D as dns-3.dfn.de A->>+B: iqsh.oncampus.de IN A note over A, B: Client fordert IP-Adresse (A) vom Root-Server B->>+C: iqsh.oncampus.de IN A note over B, C: Root-Server leitet die Anfrage weiter
an einen DNS-Server, der für die Domain
.de zuständig ist, hier z.nic.de. C->>+D: iqsh.oncampus.de IN A note over C, D: z.nic.de leitet die Anfrage weiter
an einen DNS-Server, der für die Domain
.oncampus.de zuständig ist, hier dns-3.dfn.de D->>-C: iqsh.oncampus.de
86400 A IN 193.175.122.162 note over C, D: dns-3.dfn.de liefert die IP-Adresse
von iqsh.oncampus.de an z.nic.de C->>-B: iqsh.oncampus.de
86400 A IN 193.175.122.162 note over B, C: z.nic.de liefert die IP-Adresse
von iqsh.oncampus.de an den Root-Server B->>-A: iqsh.oncampus.de
86400 A IN 193.175.122.162 note over A, B: Root-Server liefert die IP-Adresse an den Client

Der Nachteil von rekursiver Namensauflösung ist, dass die Server am Anfang dieser Abfragekette – vor allem also die Root-Server – die Anfragen über einen längeren Zeitraum zwischenspeichern und die Antworten der anderen Nameserver abwarten müssen.

Vorteile der rekursiven Namensauflösung sind, dass die DNS-Client-Software simpler gehalten werden kann und dass alle DNS-Server auf dem Weg der Abfrage die IP-Adresse ebenfalls erhalten und gegebenenfalls für folgende DNS-Abfragen zwischenspeichern können.

DNS-Protokoll

DNS bezeichnet desweiteren auch das Anwendungsprotokoll, das beschreibt, wie ein Client mit einem DNS-Server kommuniziert, um die IP-Adresse zu einem Domainnamen zu ermitteln. Die Kommunikation besteht aus Anfragen (DNS Query) und Antworten (DNS Reply).

  • Eine DNS Query beinhaltet einen oder mehrere Domainnamen, deren IP-Adressen ermittelt werden sollen. Daneben werden in der Nachricht Optionen angegeben, z. B. ob eine rekursive Namensauflösung gewünscht ist oder nicht.
  • Eine DNS Reply beinhaltet die Anfragen und Antworten für jede Anfrage, in denen u. a. die ermittelten IP-Adressen der Domainnamen angegeben sind (oder die IP-Adressen der nächsten verantwortlichen DNS-Server, wenn der Name noch nicht vollständig aufgelöst wurde).

Zur Übertragung von DNS-Nachrichten über die Transportschicht werden sowohl TCP als auch UDP auf dem Standardport 53 genutzt.

DHCP

Das Dynamic Host Configuration Protocol DHCP dient dazu, Computern, die einem Netzwerk neu beitreten, automatisch eine IP-Adresse zuzuweisen. Das geschieht zum Beispiel (in der Regel), wenn man sich im heimischen WLAN einloggt oder auf dem Handy die mobile Datennutzung aktiviert. Damit dies gelingt, muss im lokalen Netzwerk ein DHCP-Server aktiviert sein, in heimischen Netzwerken ist dies überwiegend Teil des Funktionsumfangs des WLAN-Routers.

Da der suchende Computer weder eine eigene IP-Adresse hat noch die des DHCP-Servers kennt, werden alle Anfragen und Antworten an die Broadcast-Adresse 255.255.255.255 gesende. Das bedeutet, dass diese Datenpakete bei allen Rechnern im lokalen Netz ankommen. In jeder Anfrage und jeder Antwort wird die MAC-Adresse des suchenden Rechners mitgeschickt; dadurch kann dieser Rechner erkennen, welche Nachrichten an ihn gesendet sind.

Die Zuweisung einer IP-Adresse durch DHCP verläuft regelhaft in fünf Schritten:

  1. DHCPDISCOVER. Der Client sendet eine DHCPDISCOVER-Anfrage mit seiner MAC-Adresse an die Broadcast-IP-Adresse 255.255.255.255. Als Absenderadresse gibt der Client die Netzwerk-Adresse 0.0.0.0 an.
  2. DHCPOFFER. Der DHCP-Server reagiert auf die Anfrage und bietet dem Client eine IP-Adresse aus seinem Adressbereich an. Zusätzlich übermittelt der DHCP-Server noch weitere Informationen wie die verwendete Subnetzmaske, die IP-Adresse des Standardgateways oder des zu verwendenden DNS-Servers. Da der Client noch keine IP-Adresse hat, wird auch dieses Angebot an die 255.255.255.255 geschickt.
  3. ARP REQUEST. Um sicherzugehen, dass die angebotene IP-Adresse nicht schon anderweitig vergeben ist (vielleicht hat ein anderer Nutzer seinen Computer händisch konfiguriert?), schickt der Client einen ARP REQUEST an alle Geräte.
  4. Nun gibt es zwei Möglichkeiten:
    1. DGHCPDECLINE, falls der ARP REQUEST beantwortet wird, denn dann ist die angebotene IP-Adresse schon vergeben. In diesem Fall schickt der Client einen DHCPDECLINE an den Server und beginnt das ganze Prozedere von Schritt 1. Der Server merkt sich, dass diese IP-Adresse schon belegt ist, und wird diese in Zukunft nicht mehr anbieten.
    2. DHCPREQUEST, falls der ARP REQUEST ungehört verhallt, denn dann ist die angebotene IP-Adresse noch frei. Der Client schick nun einen DHCPREQUEST und bittet den DHCP-Server darum, die angebotene IP-Adresse nutzen zu dürfen.
  5. Der Server kann darauf auf zweierlei Arten reagieren:
    1. DHCPACK bestätigt die Anfrage, der Client übernimmt die IP-Adresse.
    2. DHCPNAK lehnt die Anfrage ab, der Client muss den ganzen Prozess von vorn mit einem DHCPDISCOVER beginnen.

Zur Übertragung von DHCP-Nachrichten über die Transportschicht wird UDP auf den Ports 67 (für den Server) und 68 (für den Client) genutzt.

Mailprotokolle

Für den Versand und Abruf von E-Mails kommen drei Protokolle zum Einsatz:

  1. das Post Office Protocol, das in Version 3 (POP3) zum Abruf von E-Mails verwendet wird
  2. das Interactive Mail Access Protocol (IMAP), das ebenfalls dem E-Mail-Abruf dient
  3. das Simple Mail Transfer Protocol (SMTP) zum Versand von E-Mails

POP3

POP3 ist ein einfaches Protokoll zum Abruf von E-Mails von Mail-Servern. Typischerweise lauscht POP3 auf dem TCP-Port 110. Da POP3 normalerweise keine verschlüsselte Datenübertragung vorsieht, gibt es auch die SSL-verschlüsselte Variante POP3S, die auf Port 995 lauscht.

sequenceDiagram participant A as Client participant B as Server A->>B: stellt Verbindung her B->>A: +OK Willkommen bei mustermail.de note over A, B: Server sendet +OK zur Bestätigung oder -ERR bei Fehlern sowie ergänzende Erklärungen A->>B: USER max.mustermann B->>A: +OK Passwort bitte? A->>B: PASS s00p3rs1ch3r3sPA$$W0RT note over A, B: Nutzernamen und Passwörter werden prinzipiell unverschlüsselt übertragen.
Es gibt aber auch Erweiterungen für POP3, um das Passwort oder die gesamte Kommunikation verschlüsselt zu übertragen. B->>A: +OK Passwort passt. A->>B: STAT note over A, B: Client fragt mit STAT ab, wie viele ungelesene Nachrichten vorliegen B->>A: +OK 2 892 note over A, B: lies 2 ungelesene Nachrichten, die zusammen 892 Byte lang sind A->>B: LIST B->>A: +OK 2 ungelesene Nachrichten (892 Byte)
1 196
2 696
. note over A, B: die einzelnen Mails werden durchnummeriert aufgezählt, ihre Größe wird dabei mit angegeben.
Längere Antworten des Servers werden mit einem Punkt in einer Extrazeile beendet. A->>B: RETR 1 note over A, B: Client ruft E-Mail Nr. 1 ab B->>A: +OK Hier ist Mail Nr. 1
From: erika.mustermann@mustermail.de
To: max.mustermann@mustermail.de
... A->>B: RETR 2 B->>A: +OK Hier ist Mail Nr. 2... A->>B: DELE 1 B->>A: +OK Mail 1 zum Löschen markiert note over A, B: Es gibt keine Möglichkeit in POP3, gelesene E-Mails auf dem Server zu hinterlassen.
Entweder lädt man sich die Mail herunter und löscht die Kopie auf dem Server
oder die Kopie auf dem Server bleibt ungelöscht und wird bei jedem folgenden E-Mail-Abruf als ungelesen angezeigt. A->>B: DELE 2 B->>A: +OK Mail 2 zum Löschen markiert A->>B: QUIT note over A, B: Gelesene Mails werden nur gelöscht, wenn die Verbindung sauber mit QUIT beendet wird. B->>A: +OK Auf Wiedersehen

Gegenüber IMAP, das ebenfalls zum Mail-Abruf genutzt wird, bietet POP3 den Nachteil, dass es nur ein Postfach für eingehende E-Mails gibt und diese nach dem Abruf vom Server gelöscht werden müssen. Mehrere E-Mail-Clients zu synchronisieren, wird dadurch enorm erschwert.

IMAP

Im Gegensatz zum POP, an dem die Entwicklung zum Erliegen gekommen ist, hat sich IMAP als Standard zum E-Mail-Abruf etabliert. IMAP ermöglicht es, E-Mails auf dem Server in Ordnern zu sortieren und mittels Flags zusätzliche Informationen zu einer E-Mail zu speichern.

FlagInformation
\SeenE-Mail wurde gelesen
\AnsweredE-Mail wurde beantwortet
\FlaggedE-Mail wurde als dringend markiert
\DeletedE-Mail wurde zum Löschen vorgemerkt
\DraftE-Mail ist noch im Entwurfsstadium

Diese Flags erleichtern es enorm, E-Mails über mehrere Geräte abzurufen, weil die Mails auf dem Server verbleiben können und durch die Flags auf allen Clients angezeigt werden kann, ob die Mails bereits gelesen und bearbeitet worden sind.

Die Kommunikation verläuft vom Herstellen bis zum Schließen in mehreren Zuständen:

flowchart TD C(Connection established) -->|OK-Begrüßung| N(Not authenticated) C -->|PREAUTH-Begrüßung| A(Authenticated) C -->|BYE-Begrüßung| L(Logout) N -->|Authentifizierung| A N -->|Logout| L A -->|Mailbox auswählen| S(Selected) A -->|Logout| L S -->|Mailbox schließen| A S -->|Logout| L L --> X(Close connection)

IMAP hat hierbei drei Möglichkeiten, auf einen Verbindungsaufbau zu reagieren:

  • OK ist der Standardfall; dann muss der Nutzer sich zunächst auf irgendeine Art und Weise authentifizieren
  • Eine PREAUTH-Begrüßung ist in Situationen sinnvoll, wenn der Nutzer sich bereits anderweitig authentifiziert hat; dann kann dieser Schritt entfallen
  • Wenn der Client sich aus welchem Grund auch immer nicht mit dem Server verbinden soll, kann dieser die Verbindung mit einer BYE-“Begrüßung” von vornherein ablehnen.

SMTP

SMTP dient anders als POP und IMAP dem Versand von E-Mails und lauscht auf dem Port 25. Praktisch kann SMTP an vielen Schulen ausprobiert werden, da die Schulverwaltungssoftware IServ einen unverschlüsselten SMTP-Server zur Verfügung stellt, der zumindest eingehende E-Mails zu autorisierten Accounts akzeptiert.

Der Versand einer E-Mail mit SMTP läuft folgendermaßen ab:

sequenceDiagram participant A as Client participant B as Server A->>B: stellt Verbindung her B->>A: 220 Willkommen bei mustermail.de A->>B: HELO max.mustermann B->>A: 250 Hallo max.mustermann A->>B: MAIL FROM: max.mustermann@mustermail.de B->>A: 250 OK A->>B: RCPT TO: erika.gabler@mustermail.de B->>A: 250 OK A->>B: DATA B->>A: 354 Schick mir den Inhalt der Mail,
beende diese mit einem . auf einer Extra-Zeile. note over A, B: Die Daten der E-Mail können sehr umfangreich sein,
sodass es für den Client Sinn ergibt, diese in mehreren Anfragen abzusenden.
Der Server akzeptiert schweigend alle Nachrichten des Clients,
bis ihm ein einzelner Punkt in einer Zeile gesendet wird.
Dies ist das Signal für das Ende der Nachricht. A->>+B: From: Max Mustermann A->>B: To: Erika Gabler A->>B: Subject: Urkunden sind abgeschickt! A->>B: Erika, mein Mausepups! A->>B: Ich habe unsere Geburtsurkunden ans Standesamt geschickt! A->>B: Unserer Hochzeit steht nichts mehr im Wege! A->>B: Bald bist du auch eine Mustermann! A->>B: In Liebe, A->>B: Dein Maxibär A->>B: . note over A, B: Dieser . signalisiert dem Server das Ende der Nachricht, deswegen sendet er eine bestätigende Antwort. B->>-A: 250 Die Mail wird versendet A->>B: QUIT B->>A: 221 Auf Wiedersehen

In SMTP ist eine Authentifizierung und Autorisierung des Nutzers standardmäßig nicht vorgesehen. Deswegen bergen öffentlich zugängliche unverschlüsselte SMTP-Server ein enorm hohes Risiko für Missbrauch und Spam. Die meisten SMTP-Server sind darum nur durch verschlüsselte Schichten erreichbar, die eine Authentifizierung mit TLS erzwingen.


  1.  Dieser Statuscode stammt aus der scherzhaften HTTP-Erweiterung HTCPCP (Hyper Text Coffee Pot Control Protocol), das der Steuerung von Kaffeemaschinen dienen soll. Siehe RFC 2324: Hyper Text Coffee Pot Protocol (HTCPCP) ↩︎

  2. Der Statusode 451 ist eine Anspielung auf den dystopischen Roman “Fahrenheit 451” von Ray Bradbury. ↩︎

5. Programmierung in Python


5.1 Programmierung in Python

Die Sprache Python

Nachdem wir mit der visuellen Programmiersprache Scratch die wichtigsten Grundlagen der Programmierung in einer lernendengerechten Aufbereitung kennengelernt haben, wollen wir diese Kenntnisse nun mit einer textuellen Programmiersprache vertiefen. Wir werden hierfür die Programmiersprache Python und die Entwicklungsumgebung Thonny verwenden.

Sowohl Python als auch Thonny wurden ursprünglich für den Einsatz in schulischen Kontexten entwickelt. Dennoch ist Python eine vollwertige Programmiersprache, die z. B. unter der Haube von Anwendungen wie Facebook, Dropbox oder Spotify steckt. Python zeichnet sich durch eine kompakte, gut lesbare Syntax aus, die mit wenigen eingebauten Schlüsselwörtern auskommt und durch eine große Vielzahl von Bibliotheken ergänzt wird.

Mit einigen elementaren Programmierkenntnissen lassen sich in Python recht schnell funktionierende Anwendungen auf die Beine stellen. Dadurch eignet sich Python gut, um in kurzer Zeit Prototypen oder kleine Skripte für einfache Berechnungen zu programmieren.

Python gehört zur Familie der interpretierten Programmiersprachen, was bedeutet, dass der menschengeschriebene Programmtext direkt von einem Interpreter genannten Programm ausgeführt wird. Dadurch müssen Programme nicht erst in eine maschinenlesbare Form übersetzt werden, was das Entwickeln und Testen vereinfacht, aber auf der anderen Seite auch verhindert, dass bestimmte Programmierfehler vor der Ausführung entdeckt werden.

Die Entwicklungsumgebung Thonny

Software-Entwicklung hat viele Facetten: Der Code muss geschrieben, verwaltet und getestet werden. Für all das lassen sich unterschiedliche Programme benutzen: Wir können den Code mit einem einfachen Texteditor schreiben, mit dem Explorer in Ordnern organisieren und mit der Kommandozeile testen, oder wir können eine integrierte Entwicklungsumgebung (engl. integrated development environment, kurz IDE) einsetzen, die all diese Funktionalitäten in einer einzigen Anwendung bündelt.

Eine Art von IDE haben wir bereits kennen gelernt, nämlich Scratch, das auf einer Seite Ansichten zum Programmieren, zur Verwaltung von Grafiken und Soundeffekten, sowie auf der anderen Seite zum Ausführen und Testen des Programms bereithält.

Für Python steht eine Vielzahl von Entwicklungsumgebungen zur Verfügung, wie etwa PyCharm, Visual Studio Code, PyDev, IDLE usw.

Wir werden eine besonders lernendenfreundliche Entwicklungsumgebung benutzen, die speziell für den Einsatz als Lernhilfsmittel und nicht für die Verwendung in großen, komplexen Programmierprojekten konzipiert wurde: Thonny.1

Scratch //

Thonny ist eine einfache IDE, die zwar Anwendungsbestandteile zum Schreiben, Testen, Analysieren und zur Fehlerkorrektur von Programmen enthält, die aber mit relativ wenig Aufwand in Betrieb zu nehmen ist und die keine besonderen Vorkenntnisse in der Organisation von größeren Codemengen erfordert. Außerdem beinhaltet Thonny bereits den Python-Interpreter (Python muss also nicht extra installiert werden), sowie eine einfache Paket-Verwaltung.2

Die Benutzeroberfläche von Thonny ist modular aufgebaut. Im Anwendungsfenster lassen sich u. a. die folgenden Bereiche anzeigen:

  • Links oben befindet sich der Text-Editor. In diesem Unterfenster schreiben wir unsere Python-Programme. Es können mehrere Python-Dateien gleichzeitig in mehreren Tabs geöffnet werden.
  • Links unten befindet sich das Konsolenfenster mit der Kommandozeile. Wenn wir ein Python-Programm ablaufen lassen, werden hier Ein- und Ausgaben sowie Fehlermeldungen angezeigt. Wenn nicht gerade ein Programm abläuft, steht uns die Kommandozeile als interaktive Konsole zur Verfügung, in der wir Python-Anweisungen eingeben können, die dann sofort ausgeführt werden. Nach Beendigung eines Programmablaufs können wir über die Konsole auf sämtliche Variablen und Funktionen dieses Programms zugreifen.
  • Der Assistent zeigt (wenn ein Fehler auftritt) ausführlichere Fehlermeldungen an und macht teilweise Verbesserungsvorschläge für unseren Code.
  • Eine Auflistung aller im aktuellen Programm verwendeten Variablen und ihrer aktuellen Werte
  • Eine Ansicht, mit der wir durch die Dateien des Projekts bzw. des aktuell geöffneten Ordners navigieren können
  • Eine Notizen-Ansicht, um Ideen und Gedanken festzuhalten
  • Eine Ansicht der Hilfe-Seiten für Thonny (auf englisch)

Beim Programmstart von Thonny werden der Text-Editor und die Kommandozeile standardmäßig angezeigt. Alle weiteren Bereiche können über den Menüpunkt “Ansicht” an- oder ausgeschaltet werden.


  1. Die Installationsdateien für Thonny für verschiedene Betriebssysteme können von der offiziellen Website https://thonny.org heruntergeladen laden. Dort finden sich auch kurze Installationsanleitungen. ↩︎

  2. Ein Python-“Paket” oder Modul beinhaltet Zusatzfunktionen für Python z. B. zur Bildverarbeitung oder zur Spieleentwicklung. ↩︎

5.2 Einstieg in Python

Python wurde mit dem Ziel entwickelt, besonders gut lesbar zu sein und besonders kompakten Code zu produzieren. Die hohe Lesbarkeit wird unter anderem dadurch erreicht, dass für einige Operatoren, die in anderen Programmiersprachen durch Symbole ausgedrückt werden, in Python englische Wörter verwendet werden. Ein Beispiel dafür sind die logischen Operatoren, mit denen zwei Aussagen verknüpft werden können. In C heißen diese z.B. !, && und ||, in Python dagegen not, and und or.

Einrückungen

Um die Lesbarkeit von Programmen zu erhöhen, ist es üblich, den Quellcode durch Zeilenumbrüche und Einrückungen zu strukturieren. In Python ist dies nicht nur üblich (und damit dem Geschmack und der Disziplin der Programmiererin unterworfen), sondern explizit vorgeschrieben: bestimmte Strukturelemente, die in anderen Programmiersprachen durch Klammern oder Schlüsselwörter vorgegeben sind, werden in Python durch Einrückung gesteuert. Durch diese Festlegung soll der Quellcode automatisch besser lesbar werden, da durch die Einrückungstiefe intuitiv erfasst werden kann, welche Codezeile zu welchem Kontrollblock gehört.

5.3 Variablen und Ausdrücke

Variablen

Das Konzept der Variablen ist uns bereits aus der visuellen Programmierung vertraut (siehe Abschnitt Programmieren mit Variablen). Kurz zusammengefasst, ist eine Variable ein Bezeichner, der eine Referenz, also einen Verweis auf eine Stelle im Objektspeicher von Python enthält. Im Objektspeicher sind Objekte wie die Zahl 42, der Text “Hallo Welt” oder eine Liste mit den Elementen 1, 2 und 3 abgelegt.

Definition und Zuweisung

Anders als in Scratch oder vielen anderen Programmiersprachen müssen Variablen in Python vor der Verwendung nicht definiert werden. Man weist ihnen einfach einen Wert zu, etwa:

nettobetrag = 16.80

und kann den Wert dieser Variablen anschließend für weitere Berechnungen verwenden:

bruttobetrag = nettobetrag * 1.19

Die Syntax ist hier: Bezeichner = Ausdruck, wobei der Bezeichner mit einem Buchstaben beginnen muss (bei Variablen üblicherweise ein Kleinbuchstabe) und hinter dem Gleichheitszeichen jeder beliebige Ausdruck stehen darf, der ein Ergebnis zurückliefert. Beispiele:

  • adresse = 'Musterstr. 1, 12345 Musterstadt'
  • hoechstpunktzahl = 42
  • c = math.sqrt(a**2 + b**2)
  • name = input('Bitte gib deinen Vornamen an')

Wie schon bei der visuellen Programmierung erwähnt, sollte der Bezeichner dabei möglichst aussagekräftig gewählt werden. Generische Variablenbezeichner wie “x” oder “variable” sollten möglichst vermieden werden.

Dass Variablen vor der Verwendung nicht definiert werden müssen, birgt das Risiko von Laufzeitfehlern, wenn man sich vertippt. Betrachten wir die folgenden Codebeispiele:

groesse = input('Bitte geben Sie Ihre Größe in cm ein! ')
groesse = groesse / 100
print('Sie sind ' + str(groesse) + ' m groß.')

und

groesse = input('Bitte geben Sie Ihre Größe in cm ein! ')
greosse = groesse / 100
print('Sie sind ' + str(groesse) + ' m groß.')

Wird das erste Programm gestartet, so erhalten wir die folgende Ausgabe im Konsolenfenster:

Bitte geben Sie Ihre Größe in cm ein! 203
Sie sind 2.03 m groß.

Zur Erklärung: Das Programm pausiert nach der ersten Ausgabe (“Bitte geben Sie Ihre Größe in cm ein!”) und wartet, bis wir eine Eingabe in die Konsole eingeben und mit der Eingabetaste bestätigen. In diesem Fall haben wir die Zahl 203 eingegeben.

Wird anschließend das zweite Programm ausgeführt, erzeugt es die folgende Ausgabe in der Konsolenfenster (auch hier geben wir wieder 203 ein):

Bitte geben Sie Ihre Größe in cm ein! 203
Sie sind 203 m groß.

Was ist passiert? Das zweite Programm enthält in Zeile 2 einen Tippfehler: Statt groesse steht dort greosse. Der Python-Interpreter legt darum eine neue Variable namens greosse an und speichert darin den Wert 2.03. Der Wert von groesse bleibt unverändert.

Datentypen

Nicht mit allen Arten von Daten lassen sich die gleichen Operationen durchführen. Betrachten wir als Beispiel den Operator *:

>>> 6 * 7
42
>>> 4 * 3.141592653587
12.566370614348
>>> 'kartoffel' * 'salat'
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'
>>> 2 * 'moin '
'moin moin '
>>> 2.5 * 'moin'
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'float'
>>> 4 * [1,2,3]
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

Mit dem *-Operator können Zahlen multipliziert und Zeichenketten vervielfältigt werden. Hier gibt es Unterschiede bezüglich der Datentypen der Operanden (d. h. der Werte bzw. Ausdrücke, die links und rechts des Operators stehen).

Die wichtigsten elementaren Datentypen in Python sind:

TypErklärungBeispiele
intfür ganze Zahlen (engl. integer numbers)0, 42, 3190701205
floatfür Dezimalzahlen (“Kommazahlen”, engl. floating-point numbers)0.5, 42.0, 3.141592
strfür beliebige Zeichenketten (engl. strings)'Hello World', '42', 'https://iqsh.landsh.de'
boolfür Wahrheitswerte (engl. boolean)1True, False
Anmerkung: Datentyp abfragen

Konvertierung von Datentypen

Mit den Funktionen int, float, str und bool können Objekte in andere Datentypen umgewandelt (“konvertiert”) werden. Das wird als Type Casting bezeichnet (auf deutsch “Datentypumwandlung” oder kurz “Konvertierung”). Dabei können Daten verloren gehen, und es sind nicht alle Konvertierungen möglich. Zum Beispiel gehen bei der Konvertierung int(25.95) zwangsläufig die Nachkommastellen verloren, das Ergebnis ist die Ganzzahl 25.2

Die folgende Tabelle zeigt anhand von Beispielen, welche Konvertierungen zwischen den vier elementaren Datentypen möglich sind und welche Ergebnisse wir erhalten:

Argumentwertint(x)float(x)str(x)bool(x)
x = 222.0'2'True
x = 2.781822.7818'2.7818'True
x = 'zwei'FehlerFehler'zwei'True
x = True11.0'True'True

Hinweis: Die Konvertierung von Zahlen oder Strings in Wahrheitswerte mit bool ergibt immer True, außer für die Werte 0, 0.0 und den leeren String ''.

Operatoren

Mathematische Operatoren

Mathematische Operatoren werden für Berechnungen mit numerischen Werten (Datentypen int und float) verwendet. Die Operanden (hier x und y genannt) sind numerische Werte oder Ausdrücke, die numerische Werte ergeben. Die Berechnungsergebnisse sind ebenfalls numerische Werte.

OperatorBezeichnungBeschreibungin Scratch
x + yAdditionScratch +
x - ySubtraktionScratch -
x * yMultiplikationScratch *
x / yDivisionLiefert immer einen Wert vom Datentyp float als Ergebnis zurück, auch wenn das Ergebnis ganzzahlig ist.
Beispiel: 4 / 22.0
Scratch /
x // yGanzzahlige DivisionLiefert immer einen Wert vom Datentyp int zurück, ggf. wird das Ergebnis also konvertiert.
Beispiel: x // y ergibt dasselbe wie int(x / y)
Scratch //
x % yModuloBerechnet den Rest, der bei der ganzzahligen Division von x durch y übrigbleibt.
Beispiel: 10 % 31
Scratch %
x ** yPotenzBerechnet xy (“x hoch y”), also die y-fache Multiplikation von x.siehe unten3

Vergleichsoperatoren

Mit Vergleichsoperatoren können zwei Werte oder die Ergebnisse zweier Ausdrücke verglichen werden. Das Ergebnis ist ein Wahrheitswert True oder False (Ausdrücke mit einem Vergleichsoperatoren sind also logische Ausdrücke).

Die Operanden können dabei verschiedene Datentypen haben, so lassen sich numerische Werte, Zeichenketten und auch andere Objekte miteinander vergleichen. In den folgenden Beispielen stehen x und y also für Werte beliebiger Datentypen (z. B. int, float oder str).

OperatorBezeichnungHinweisin Scratch
x == yGleichheitAchtung: In Python wird ein doppeltes Gleichheitszeichen zur Überprüfung der Gleichheit verwendet. Ein einfaches Gleichheitszeichen beschreibt dagegen eine Variablenzuweisung!Scratch ==
x != yUngleichheitScratch !=
x < yEcht kleinerScratch <
x <= yKleiner oder gleichScratch <=
x > yEcht größerScratch >
x >= yGrößer oder gleichScratch >=

Logische Operatoren

Logische Operatoren werden für Berechnungen mit Wahrheitswerten (Datentyp bool) verwendet, z. B. um mehrere Wahrheitswerte oder logische Ausdrücke zu verknüpfen. Das Berechnungsergebnis ist ebenfalls ein Wahrheitswert. In den folgenden Beispielen stehen a und b jeweils für Wahrheitswerte oder logische Ausdrücke.

OperatorBeschreibungin Scratch
not aLogisches NICHT (“Negation”): not a ist genau dann True, wenn a zu False ausgewertet wird.Scratch >
a and bLogisches UND: a and b ist genau dann True, wenn sowohl a als auch b zu True ausgewertet werden.Scratch
a or bLogisches ODER: a and b ist genau dann True, wenn a, b oder beide zu True ausgewertet werden.Scratch
Exkurs: Fortgeschrittenere Verwendung von logischen Operatoren

Operatoren für Zeichenketten

Die folgenden Operatoren sind zum Arbeiten mit Zeichenketten (Datentyp str) hilfreich. In der Regel ist der Ergebniswert ebenfalls eine Zeichenkette (Ausnahme: Für den Operator in ist das Ergebnis ein Wahrheitswert). In den folgenden Beispielen stehen s und t jeweils für Zeichenketten und n für eine ganze Zahl.

OperationBedeutungin Scratch
s + tAneinanderhängen (“Konkatenation”)
Beispiel: 'Flens' + 'burg''Flensburg'
Scratch >
n * sVervielfältigung
Beispiel: 3 * 'Ho''HoHoHo'
s in tIst der Teilstring t in s enthalten?
Beispiel: 'sum' in 'Husum'True
Scratch >
s[n]Zeichen an der Position n im String s
Beispiel: 'Rendsburg'[1]'e'
Achtung: In Python wird von 0 an gezählt, in Scratch von 1 an!
Scratch >
len(s)Länge des Strings s
Beispiel: len('Kiel')4
Scratch >

  1. Logische Wahrheitswerte werden nach George Boole, einem Pionier der mathematischen Logik, auch als “boolesche Werte” bezeichnet (im Englischen “boolean”). ↩︎

  2. Bei der Umwandlung von Dezimalzahlen mit Nachkommastellen in Ganzzahlen mit der Funktion int wird in Python zur Null hin gerundet (bei positiven Zahlen wird also abgerundet, bei negativen aufgerundet). ↩︎

  3. Es gibt für die Potenz mit beliebiger Basis keinen Block in Scratch, aber mit Hilfe der Exponentialfunktion und des natürlichen Logarithmus lässt sich die Potenz folgendermaßen umformen: \(x^y = \mathrm{e}^{y\cdot\ln(x)}\) und in Scratch umsetzen: Scratch ** ↩︎

5.4 Funktionen und Bibliotheken

Funktionen aufrufen

Funktionen kennen wir aus der Mathematik als (vereinfacht gesagt) Rechenvorschriften, um ein oder mehrere Objekte in andere umzurechnen. Zum Beispiel erhält die Funktion \(f(x) = x^2\) einen Wert für den Parameter \(x\) und gibt dessen Quadrat als Ergebnis zurück. Nun können wir Funktionswerte wie \(f(4) = 16\) berechnen.

Dieses Konzept existiert auch in Python. Zum Beispiel existiert die Funktion round(x), die eine als Parameter übergebene Zahl x kaufmännisch rundet.

>>> round(4.2)
4
>>> round(6.66)
7
>>> round(-5)
-5

Die Schreibweise funktionsname(parameterwert) orientiert sich an der mathematischen Notation.

Funktionen definieren

In Python lassen sich auch eigene Funktionen definieren. Dieses Konzept kennen wir bereits aus der visuellen Programmierung: In Scratch können wir “neue Blöcke” erstellen, hinter denen sich selbst definierte Unterprogramme verbergen (siehe Abschnitt Unterprogramme).

In Python lassen sich mit dem Schlüsselwort def eigene Funktionen definieren. Die einfachste Form dafür lautet def Funktionsname ():

Das folgende Beispiel definiert eine Funktion bzw. Unterprogramm mit dem Funktionsnamen “waagerechte_linie”, das beim Aufruf eine Zeile mit 40 Strichzeichen in die Konsole schreibt:

def waagerechte_linie():
    print('----------------------------------------')

Die erste Zeile, die mit def eingeleitet wird, nennen wir den Funktionskopf, den Rest den Funktionsrumpf. Der Funktionsrumpf enthält das eigentliche Unterprogramm, also die Anweisungen, die beim Aufruf der Funktion ausgeführt werden sollen.

Im Funktionskopf können wir zusätzlich Parameter festlegen, über die der Funktion beim Aufruf Werte übergeben werden können, die in der Funktionsausführung eine Rolle spielen. Dazu schreiben wir in die Klammern die Namen der Parameter. Bei der Abarbeitung dieser Funktion werden die Parameter dann wie lokale Variablen verwendet, die nur innerhalb der Funktion sichtbar sind. Mehrere Parameter werden mit Komma getrennt angegeben.

def unterschiedlich_lange_linie(laenge):
    print('-' * laenge)

def sehr_flexible_linie(laenge, zeichen):
    print(zeichen * laenge)

Beim Funktionsaufruf muss darauf geachtet werden, dass für alle Parameter geeignete Werte angegeben werden.

Die Aufrufe der drei oben definierten Funktionen könnten dann beispielsweise folgendermaßen aussehen:

>>> waagerechte_linie()
-------------------------------------------
>>> unterschiedlich_lange_linie(10)
----------
>>> sehr_flexible_linie(15, '=')
===============
>>> sehr_flexible_linie(5, '-')
-----

Die Definition einer Funktion ist für sich genommen wertlos, solange die Funktion nirgendwo im Programm aufgerufen wird. Erst beim Aufruf einer Funktion wird der Programmteil, der durch die Funktion definiert wird, ausgeführt.

Rückgabewerte

Mit dem Schlüsselwort return kann das Ergebnis einer Funktion an die Aufrufstelle zurückgegeben werden. Die Funktionsausführung endet an dieser Stelle.

def quadrat(x):
    return x * x

Funktionen mit Rückgabewert können im Programm nicht nur als Anweisungen, sondern als Ausdrücke eingesetzt werden. Das bedeutet, dass sie z. B. in mathematischen Berechnungen oder Parametern von weiteren Funktionsaufrufen eingesetzt werden können.

>>> quadrat(5)
25
>>> quersumme(1234)
10
>>> quersumme(3190701205)
28
>>> quersumme(quersumme(3190701205))
10
>>> quadrat(11) + quersumme(11)
123

Funktionen können natürlich sehr viel umfangreicher sein, als nur einfache mathematische Ausdrücke auszuwerten und als Ergebnis zurückzugeben. Prinzipiell können die Unterprogramme, die in den Funktionsrümpfen stehen, beliebig komplex sein – also Anweisungssequenzen, Kontrollstrukturen, Ein- und Ausgaben sowie weitere Funktionsaufrufe enthalten.

def quersumme(zahl):
    zahl_als_string = str(zahl)
    summe = 0
    for ziffer in zahl_als_string:
        summe = summe + int(ziffer)
    return summe

Lokale und globale Variablen

Dieser Abschnitt wird noch ergänzt.

Bibliotheken

Wie viele andere Programmiersprachen verfügt Python nur über sehr wenige eingebaute Funktionen, z. B. len(), int() oder range(). Diese Funktionen stellen die wichtigsten Funktionalitäten zum Programmieren und die wichtigsten Datentypen zur Verfügung. Alles, was man darüber hinaus benötigen könnte, wird von so genannten Bibliotheken zur Verfügung gestellt, wobei jede Bibliothek eine klar definierte Funktionalität erfüllt.

In Python sind einige Bibliotheken standardmäßig vorinstalliert und können mit import genutzt werden. Dazu gehören u. a.:

NameZweck
mathMathematische Funktionen wie Wurzeln, Sinus/Kosinus, Logarithmen usw.
osZugriff auf das Betriebssystem
sysZugriff auf Attribute und Funktionen des Python-Interpreters
randomGenerieren von Zufallszahlen
tkinterProgrammieren von grafischen Benutzeroberflächen
turtleZeichnen von Turtle-Grafiken
datetimeOperationen mit Datum und Zeit
shutilKomplexere Operationen auf Dateien

Daneben lassen sich auch weitere Bibliotheken von Drittanbietern nachinstallieren. Die offizielle Sammlung von Python-Bibliotheken ist der Python Package Index (PyPI) mit der Homepage https://pypi.org. Python-Programmierende können eigene Bibliotheken erstellen und über den PyPI veröffentlichen. Dort findet man unter anderem nützliche Bibliotheken wie:

NameZweck
bottleEin einfacher Webserver
cryptographyEine Bibliothek mit kryptografischen Funktionen
numpyVerbesserte mathematische Operationen
pandasEin mächtiges Werkzeug zur Datenanalyse
pillowFunktionen zur Bildverarbeitung
urllib3Ein einfacher HTTP-Client

Im PyPI sind beliebte und vielgenutzte Pakete wie die Webserver Django, Bottle und Flask zu finden, aber auch Nonsens-Pakete wie shittypackage und teilweise sogar bösartige Pakete. Darum sollte man beim Benutzen von PyPI-Paketen eine gewisse Vorsicht walten lassen und sich vorher über diese Pakete informieren.

Bibliotheken einbinden und nutzen

Zum Einbinden einer Bibliothek in ein Python-Programm wird das Schlüsselwort import verwendet. Entweder bindet man die ganze Bibliothek mit import math ein oder pickt sich einzelne Funktionen heraus, etwa die Funktion sqrt zum Berechnen der Quadratwurzel. In diesem Fall schreibt man from math import sqrt. Mit from math import * können alle Funktionen und Konstanten aus der math-Bibliothek importiert werden.

Im ersten Fall muss man dann, um auf die Konstanten und Funktionen der Bibliothek zugreifen zu können, den Namen der Bibliothek (mit Punkt getrennt) vor die Aufrufe setzen: Für \(\sqrt{4}\) schreibt man dann math.sqrt(4). Im zweiten Fall (from math import sqrt oder from math import *) schreibt man einfach sqrt(4), um die Funktion aufzurufen.

Ganze Bibliothek importierenEinzelne Funktionen aus einer Bibliothek importierenAlle Funktionen aus einer Bibliothek importieren
Einbindenimport mathfrom math import sqrtfrom math import *
\(\sqrt{4}\)math.sqrt(4)2sqrt(4)2sqrt(4)2
\(\pi\)math.pi3.141592653589793pi → Fehler, weil nur sqrt importiert wurde!pi3.141592653589793
VorteileKeine Namensverwirrung, weil beim Funktionsaufruf der Bibliotheksname mit angegeben werden mussEntlastet den Arbeitsspeicher, weil nur wirklich benötigte Funktionen geladen werdenImportiert eine ganze Bibliothek, ermöglicht aber kompakte Notation.
NachteileGrößerer Schreibaufwand und unintuitive Notationen wie datetime.datetimeVerwirrende Fehlermeldungen, wenn man nicht importierte Funktionen verwenden möchteRisiko von Namensverwirrungen, z. B. gibt es ceil-Funktionen in den Bibliotheken math, numpy und torch.

Bibliotheken in Thonny installieren

Thonny enthält eine Paket-Verwaltung für PyPI, die sich im Menü unter ExtrasVerwalte Plug-Ins… aufrufen lässt.

Thonnys Paket-Manager

Dieses Fenster gestattet es, im PyPI nach Paketen zu suchen:

Thonnys Paket-Manager

Details zu einzelnen Bibliotheken anzuzeigen:

Thonnys Paket-Manager

und Pakete zu installieren oder deinstallieren:

Thonnys Paket-Manager

5.4.1 Die datetime-Bibliothek

Um Daten und Zeitangaben in Python verarbeiten zu können, nutzen wir die Bibliothek datetime.

Diese Bibliothek stellt uns u.a. die Datentypen date, time, datetime und timedelta zur Verfügung.

Datums-Objekte erzeugen

Ein neues date-Objekt kann man beispielsweise mit dem Aufruf

>>> import datetime
>>> declaration_of_independence = datetime.date(1776,7,4)
>>> print(declaration_of_independence)
  1776-07-04

erzeugen. Die drei angegebenen Parameter stehen dabei für Jahr, Monat und Tag und sind verpflichtend.

Ein time-Objekt kann mit dem Konstruktor datetime.time() erzeugt werden, der Parameter für Stunde, Minute, Sekunde und Mikrosekunde (in dieser Reihenfolge) annimmt, die aber optional sind.

>>> fuenfuhrtee = datetime.time(17)
>>> print(fuenfuhrtee)
  17:00:00
>>> sehr_exakte_uhrzeit = datetime.time(16, 17, 25, 172623)
>>> print(sehr_exakte_uhrzeit)
16:17:25.172623

Die Kombination aus date und time ist datetime, das alle Angaben enthält.

>>> birth = datetime.datetime(2021, 11, 27, 8, 34)
>>> print(birth)
  2021-11-27 08:34:00

Die Reihenfolge der Parameter ist hier datetime(Jahr, Monat, Tag, [Stunde], [Minute], [Sekunde], [Mikrosekunde]), wobei die letzten vier Parameter optional sind und auf 0 gesetzt werden, wenn sie nicht explizit angegeben werden.

Achten Sie auf die Syntax beim Erzeugen neuer Objekte. Wenn Sie die gesamte Bibliothek mit import datetime importieren, müssen Sie datetime.datetime(...) zum Erzeugen neuer Objekte schreiben. Wenn Sie nur die Klasse datetime aus der Bibliothek datetime importieren (from datetime import datetime) müssen Sie zum Erzeugen neuer Objekte datetime(...) schreiben.

Um ein date-Objekt zu erzeugen, das auf den heutigen Tag verweist, kann die Funktion date.today() benutzt werden. datetime.now() erzeugt ein datetime-Objekt, das auf die Mikrosekunde genau auf den jetzigen Zeitpunkt verweist.

>>> print(datetime.date.today())  -->>> print(datetime.datetime.now())  -- ::

Operationen auf Datums-Objekten

Die Differenz zwischen zwei Daten kann man einfach mit dem --Operator berechnen. Dabei wird ein timedelta-Objekt erzeugt, das die Differenz zwischen den beiden Daten enthält.

>>> birth = datetime.date(1912, 6, 23)
>>> death = datetime.date(1954, 6, 7)
>>> print(death - birth)
15324 days, 0:00:00

timedelta-Objekte kann man auch mit dem Aufruf datetime.timedelta([Tage], [Sekunden], [Mikrosekunden], [Millisekunden], [Minuten], [Stunden], [Wochen]) erzeugt werden, wobei alle Parameter optional sind und standardmäßig mit 0 initialisiert werden.

Mit timedelta-Objekten sind noch viel mehr Berechnungen möglich. Ein paar Beispiele:

>>> jahr = datetime.timedelta(365)
>>> woche = datetime.timedelta(7)
>>> neujahr = datetime.date(2022, 1, 1)
>>> print(neujahr + jahr) # welcher Tag ist ein Jahr nach Neujahr?
  2023-01-01
>>> print(neujahr - jahr) # welcher Tag ist ein Jahr vor Neujahr?
  2021-01-01
>>> print(jahr / woche) # Wie viele Wochen sind in einem Jahr?
  52.142857142857146
>>> print(jahr + woche) # Wie lange dauern ein Jahr und eine Woche?
  372 days, 0:00:00
>>> print(jahr - woche) # Wie lange dauert ein Jahr minus eine Woche?
  358 days, 0:00:00
>>> print(jahr // woche) # Wie viele ganze Wochen passen in ein Jahr?
  52
>>> print(jahr % woche) # Und wie viele Tage bleiben dann noch übrig?
1 day, 0:00:00

Ob zwei datetime-Objekte gleichzeitig sind oder welches davon später ist, kann mit den Vergleichsoperatoren ==, < und > geprüft werden. Das spätere Datum gilt hier als das größere.

>>> print(death > birth)
  True
>>> print(declaration_of_independence > birth)
  False

Referenzen

Didaktisch reduzierte Version

Um Daten und Zeitangaben in Python verarbeiten zu können, nutzen wir die Bibliothek datetime.

Diese Bibliothek stellt uns u.a. die Datentypen date, time, datetime und timedelta zur Verfügung.

Datums-Objekte erzeugen

Ein neues Datum kann man beispielsweise mit dem Aufruf

>>> import datetime
>>> declaration_of_independence = datetime.date(1776,7,4)
>>> print(declaration_of_independence)
  1776-07-04

erzeugen. Die drei angegebenen Parameter stehen dabei für Jahr, Monat und Tag und sind verpflichtend.

Ein time-Objekt kann mit dem Konstruktor datetime.time() erzeugt werden, der Parameter für Stunde, Minute, Sekunde und Mikrosekunde (in dieser Reihenfolge) annimmt, die aber optional sind.

>>> fuenfuhrtee = datetime.time(17)
>>> print(fuenfuhrtee)
  17:00:00
>>> sehr_exakte_uhrzeit = datetime.time(16, 17, 25, 172623)
>>> print(sehr_exakte_uhrzeit)
16:17:25.172623

Die Kombination aus date und time ist datetime, das alle Angaben enthält.

>>> birth = datetime.datetime(2021, 11, 27, 8, 34)
>>> print(birth)
  2021-11-27 08:34:00

Die Reihenfolge der Parameter ist hier datetime(Jahr, Monat, Tag, [Stunde], [Minute], [Sekunde], [Mikrosekunde]), wobei die letzten vier Parameter optional sind und auf 0 gesetzt werden, wenn sie nicht explizit angegeben werden.

Achten Sie auf die Syntax beim Erzeugen neuer Objekte. Wenn Sie die gesamte Bibliothek mit import datetime importieren, müssen Sie datetime.datetime(...) zum Erzeugen neuer Objekte schreiben. Wenn Sie nur die Klasse datetime aus der Bibliothek datetime importieren (from datetime import datetime) müssen Sie zum Erzeugen neuer Objekte datetime(...) schreiben.

Um ein date-Objekt zu erzeugen, das auf den heutigen Tag verweist, kann die Funktion date.today() benutzt werden. datetime.now() erzeugt ein datetime-Objekt, das auf die Mikrosekunde genau auf den jetzigen Zeitpunkt verweist.

>>> print(datetime.date.today())  -->>> print(datetime.datetime.now())  -- ::

Operationen auf Datums-Objekten

Die Differenz zwischen zwei Daten kann man einfach mit dem --Operator berechnen. Dabei wird ein timedelta-Objekt erzeugt, das die Differenz zwischen den beiden Daten enthält.

>>> birth = datetime.date(1912, 6, 23)
>>> death = datetime.date(1954, 6, 7)
>>> print(death - birth)
15324 days, 0:00:00

timedelta-Objekte kann man auch mit dem Aufruf datetime.timedelta([Tage], [Sekunden], [Mikrosekunden], [Millisekunden], [Minuten], [Stunden], [Wochen]) erzeugt werden, wobei alle Parameter optional sind und standardmäßig mit 0 initialisiert werden.

Mit timedelta-Objekten sind noch viel mehr Berechnungen möglich. Ein paar Beispiele:

>>> jahr = datetime.timedelta(365)
>>> woche = datetime.timedelta(7)
>>> neujahr = datetime.date(2022, 1, 1)
>>> print(neujahr + jahr) # welcher Tag ist ein Jahr nach Neujahr?
  2023-01-01
>>> print(neujahr - jahr) # welcher Tag ist ein Jahr vor Neujahr?
  2021-01-01
>>> print(jahr / woche) # Wie viele Wochen sind in einem Jahr?
  52.142857142857146
>>> print(jahr + woche) # Wie lange dauern ein Jahr und eine Woche?
  372 days, 0:00:00
>>> print(jahr - woche) # Wie lange dauert ein Jahr minus eine Woche?
  358 days, 0:00:00
>>> print(jahr // woche) # Wie viele ganze Wochen passen in ein Jahr?
  52
>>> print(jahr % woche) # Und wie viele Tage bleiben dann noch übrig?
1 day, 0:00:00

Ob zwei datetime-Objekte gleichzeitig sind oder welches davon später ist, kann mit den Vergleichsoperatoren ==, < und > geprüft werden. Das spätere Datum gilt hier als das größere.

>>> print(death > birth)
  True
>>> print(declaration_of_independence > birth)
  False

Referenzen

5.5 Kontrollstrukturen

Bedingte Verzweigung (if, elif, else)

Zur Realisierung einer Fallunterscheidung werden in Python die Schlüsselworte if, elif und else genutzt.

Bedingte Anweisung mit if

Flussdiagramm zur Visualisierung von if

Der einfachste Fall ist eine Sequenz von Anweisungen, die nur dann ausgeführt werden soll, wenn eine bestimmte Bedingung erfüllt ist, sonst nicht. Die Schreibweise ist if Bedingung: und dann folgen die Anweisungen, die in dem Fall ausgeführt werden sollen, dass die Bedingung wahr ist. Diese Anweisungen müssen eingerückt werden, damit der Interpreter identifizieren kann, welche Anweisungen in Abhängigkeit von der Bedingung und welche immer ausgeführt werden sollen.

Im folgenden Beispiel wird die print-Anweisung nur ausgeführt, falls der Ausdruck (notendurchschnitt > 4) zu wahr (True) ausgewertet wird:

if notendurchschnitt > 4.0:
    # Teil A
    print('Die Klausur muss von der Schulleitung genehmigt werden.')

Bedingungen

Als Bedingung kann jeder Ausdruck eingesetzt werden, der zu True oder False ausgewertet werden kann. Hier werden also in der Regel logische Ausdrücke verwendet, zum Beispiel Vergleiche mit den Vergleichsoperatoren < (kleiner als), > (größer als) oder == (gleich).

Mehrere Vergleiche lassen sich mit den logischen Verknüpfungsoperatoren and (logisches UND) und or (logisches ODER) zu einer Bedingung verknüpfen. Im folgenden Beispiel wird die print-Anweisung ausgeführt, falls der Wert der Variablen notendurchschnitt größer als 4 oder kleiner als 1.5 ist (d. h. wenn mindestens einer der mit or verknüpften Vergleiche wahr ist):

if notendurchschnitt > 4.0 or notendurchschnitt < 1.5:
    # Teil A
    print('Die Klausur muss von der Schulleitung genehmigt werden.')

Werden Vergleiche dagegen mit and verknüpft, müssen alle einzelnen Vergleiche zu wahr ausgewertet werden, damit die Gesamtbedingung wahr ist:

if notendurchschnitt > 4.0 and anzahl_klausuren >= 2:
    # Teil A
    print('Die Klausur muss von der Schulleitung genehmigt werden.')

Alternative mit else

Flussdiagramm zur Visualisierung von if-else

Um alternative Anweisungen auszuführen, wenn die Bedingung nicht erfüllt ist, wird das Schlüsselwort else: (engl. sonst) verwendet. Das else wird im Gegensatz zu den Anweisungen für den Dann- und den Sonst-Fall nicht eingerückt, steht also auf derselben Einrückungstiefe wie das if.

if notendurchschnitt > 4.0:
    # Teil A
    print('Die Klausur muss von der Schulleitung genehmigt werden.')
else:
    # Teil B
    print('Alles okay, die Klausur kannst du problemlos zurückgeben.')

Mehrfache bedingte Verzweigung mit elif

Flussdiagramm zur Visualisierung von if-elif-else

Mit elif (kurz für else if) können mehrere Bedingungen nacheinander abgeprüft werden. In diesem Fall werden die Bedingungen solange abgeprüft, bis eine davon wahr ist. Nur die Anweisungen, die zu dieser Bedingung gehören werden dann ausgeführt.

Nach einem if können beliebig viele elifs folgen. Für den Fall, dass gar keine der abgeprüften Bedingungen wahr ist, kann nach den elifs ein else folgen.

if notendurchschnitt > 4.0:
    # Teil A
    print('Die Klausur muss von der Schulleitung genehmigt werden.')
elif notendurchschnitt > 2.0:
  print('Na, das ist doch ganz okay gelaufen.')
else:
    # Teil C
    print('Wow, das ist ja ein Hammer-Durchschnitt!')

Im obigen Beispiel würde ein Notendurchschnitt von 4.2 beide Bedingungen erfüllen, aber es würde nur der Text “Die Klausur muss von der Schulleitung genehmigt werden.” ausgegeben werden, da die Auswertung nach dem ersten Auffinden einer erfüllten Bedingung endet.

Bedingte Wiederholung (while)

Flussdiagramm zur Visualisierung von while

Um eine Anweisung oder eine Sequenz von Anweisungen in Abhängigkeit von einer Bedingung zu wiederholen, nutzen wir das Schlüsselwort while, welches genau so genutzt wird wie if: nach dem while folgt eine Bedingung, die

Im folgenden Beispiel soll der Nutzer seine Postleitzahl eingeben. Die Eingabe wird geprüft und die Eingabeaufforderung wiederholt, solange wie keine Zahl eingegeben wurde:

plz = input('Bitte geben Sie Ihre Postleitzahl ein: ')
while not input.isnumeric():
    # Teil C
    plz = input('Das ist keine Zahl. Bitte versuchen Sie es erneut: ')

Anders als in Scratch werden hier die Anweisungen nicht wiederholt, bis die Bedingung wahr ist, sondern solange sie wahr ist. Die folgenden Code-Schnipsel sind äquivalent:

Flussdiagramm zur Visualisierung von while

while x >= 1:
  x = x - 1
  print(x)

Eine endlos laufende Wiederholung lässt sich realisieren, indem nach dem while eine immer wahre Bedingung gestellt wird:

Flussdiagramm zur Visualisierung von while

while True:
    # Teil C
    print('Diese Wiederholung läuft ewig.')
  
while 1 == 1:
    # Teil C
    print('Diese theoretisch auch - praktisch wird sie nie gestartet, weil die Wiederholung davor endlos läuft.')

Auch eine endlos laufende Wiederholung kann aber mit dem Schlüsselwort break unterbrochen werden. Wenn mehrere Wiederholungen ineinander geschachtelt werden, wird dabei nur die innerste unterbrochen, in der sich die break-Anweisung befindet.

Dies kann vor allem nützlich sein, wenn die Abbruchbedingung der Wiederholung sehr komplex oder mehrstufig ist. In folgendem Beispiel soll der Nutzer seine Postleitzahl eingeben. Die Eingabe wird in zwei Schritten auf Korrektheit überprüft: zuerst wird mit isnumeric() geprüft, ob der eingegebene String plz nur aus Ziffern besteht; falls dies der Fall ist, wird überprüft, ob die eingegebene Zahl zwischen 01001 und 99998 liegt und damit eine gültige deutsche Postleitzahl sein könnte. Hierbei wird die Eingabe in ein int konvertiert, was bei einer nicht-numerischen Eingabe zu einer Fehlermeldung und dem Abbruch des Programms führen würde.

while True:
    plz = input('Bitte geben Sie Ihre Postleitzahl ein: ')
    if not plz.isnumeric():
        print('Sie haben keine Zahl eingegeben.')
    elif int(plz) < 1001 or int(plz) > 99998:
        print('Das ist keine gültige Postleitzahl.')
    else:
        break

Wiederholungen über eine Datenstruktur (for)

Das Schlüsselwort for kann man benutzen, um für alle Elemente in einer Datenstruktur dieselben Anweisungen auszuführen. Das kann eine Liste sein, ein String oder ähnliches. Die Syntax der Wiederholung ist for Element in Liste1: und danach folgen die Anweisungen, wieder eingerückt.

klassenliste = ['Felix', 'Noah', 'Leonie', 'Hanna', 'Emma', 'Nele', 'Enna', 'Hannah', 'Mohammed', 'David', 'Neven', 'Paula', 'Moritz', 'Maximilian']
print('Moin. Wer fehlt heute?')
for kind in klassenliste:
    print(kind + ', bist du da?')

Wiederholungen mit fester Anzahl (range)

Scratch-Block &ldquo;wiederhole n mal&rdquo;

Für eine Wiederholung mit fester Anzahl, wie sie aus Scratch bekannt ist, kann die Datenstruktur range benutzt werden. Eine range zählt dabei von einem ggf. vorgegebenen Startwert zu einem vorgegebenen Endwert mit einer ggf. ebenfalls vorgegebenen Schrittweite hoch. Dies kann für eine for-Wiederholung genutzt werden. Der range können dabei bis zu drei Parameter übergeben werden:

  • ein Startwert, von wo die range zu zählen beginnen soll, dieser ist optional und wird standardmäßig auf 0 gesetzt
  • ein Endwert, bei dem die range aufgehört haben soll, zu zählen, dieser muss auf jeden Fall angegeben werden.
  • eine Schrittweite, die beim Zählen eingesetzt werden soll, diese ist optional und wird standardmäßig auf 1 gesetzt. Wenn die range rückwärts zählen soll, muss eine negative Schrittweite eingesetzt werden. Die Schrittweite darf nicht auf 0 gesetzt werden.

Wird nur ein Parameter übergeben, wird dieser als Endwert interpretiert, zwei Parameter werden als Start- und Endwert interpretiert.

Der übergebene Endwert wird nicht mitgezählt! Eine range(0,5) zählt also nur bis 4!

Betrachten wir das Verhalten einiger ranges:

>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(3,7))
[3, 4, 5, 6]
>>> list(range(2,18,3))
[2, 5, 8, 11, 14, 17]
>>> list(range(3,-4,-1))
[3, 2, 1, 0, -1, -2, -3]

Eine for-Wiederholung in Verbindung mit einer range lässt sich wie folgt als while-Wiederholung darstellen:

for x in range(start,finish,step):
  do_some_stuff_with(x)

entspricht:2

x = start
while x < finish:
  do_some_stuff_with(x)
  x = x + step

Dass der Endwert, der einer range übergeben wird, nicht mitgezählt wird, hat durchaus Vorteile. Zum Beispiel kann man so die Länge einer Liste an eine range übergeben, die dann durch alle Indizes der Liste von 0 bis Länge-1 hochzählt.


  1. Statt einer Liste kann jede beliebige Datenstruktur verwendet werden, die iterierbar (engl. iterable) ist, d. h. es zulässt, eine Menge an Elementen in einer gewissen Reihenfolge durchzugehen. ↩︎

  2. Der Einfachheit und Lesbarkeit halber ist die while-Wiederholung hier nur für positive Schrittweiten dargestellt. Überlegen Sie: wie müsste man die Bedingung der while-Wiederholung anpassen, damit sie auch für negative Schrittweiten funktionieren würde? ↩︎

5.6 Debugging

Der erste Computer-Bug, Bild mit freundlicher Genehmigung des Naval Surface Warfare Center, Dahlgren, VA., 1988., Public domain, via Wikimedia Commons

Die Abbildung rechts zeigt einen Bug, zu deutsch ein Krabbeltier, sowohl im wörtlichen als auch im übertragenen Sinne. Die abgebildete Motte hatte 1947 einen Kurzschluss in einem Computer verursacht und noch heute werden alle metaphorischen kleinen Krabbeltiere, die den Ablauf eines Computerprogramms stören, als Bugs bezeichnet1. Analog bezeichnet man das Entfernen dieser Bugs als Debugging und die dazu verwendeten Werkzeuge als Debugger. Eine analoge Form des Debuggings haben wir bereits bei der visuellen Programmierung verwendet: Trace-Tabellen.

Mithilfe eines Debuggers kann man ein Programm schrittweise durchlaufen und dabei z.B. die verwendeten Variablen oder den belegten Speicher im Auge behalten.

Thonny verfügt über einen eingebauten Debugger mit recht simpler Funktionalität, der aber für die meisten schulischen Zwecke genügt.

Die Tastenkombination [Shift]+[F5] oder das Icon mit dem Käfer 🪲 starten den Debugger. Im Debug-Modus stehen dann diverse Werkzeuge zur Verfügung, um das Programm kontrolliert und schrittweise ablaufen zu lassen.

Eins der wichtigsten Werkzeuge sind Breakpoints. Breakpoints können gezielt platziert werden, um den Programmfluss an einer bestimmten Stelle zu unterbrechen und die weitere Ausführung der Kontrolle des Entwicklers*der Entwicklerin zu überlassen.

Einen Breakpoint kann man mit einem Klick auf die Zeilennummer derjenigen Anweisung setzen, an der der Programmablauf unterbrochen werden soll. Neben dieser Zeilennummer erscheint dann ein roter Punkt 2🔴.

Sobald der Programmablauf beim Debuggen einen Breakpoint erreicht, wird er gestoppt, die aktuell betrachtete Programmzeile farblich hervorgehoben und das weitere Vorgehen dem*der Debuggenden überlassen. Falls keine Breakpoints gesetzt sind, wird von Anfang an so verfahren.

Für das weitere Vorgehen stehen folgende Werkzeuge zur Verfügung, die entweder über die Icon-Symbolleiste oder das “Ausführen”-Menü zu erreichen sind:

WerkzeugErläuterung
↷ EinzelschrittDie hervorgehobene Zeile wird vollständig ausgeführt und zur nächsten auszuführenden Zeile gesprungen.
↴ EintretenKomplexere Anweisungen werden so kleinschrittig wie möglich abgearbeitet.
↱ VerlassenEs wird ans Ende der schrittweisen Ausführung einer komplexen Anweisung gesprungen.
↦ FortfahrenDer Programmablauf wird normal bis zum nächsten Breakpoint bzw. falls es keinen gibt, zum Programmende fortgeführt.
⇥ Bis zum Cursor ausführenDer Programmablauf wird bis zur Position des Textcursors im Programmcode fortgeführt.
Zurück schreitenEs wird einen Schritt im Programmablauf zurückgesprungen.

Beim Eintreten geht der Debugger so kleinschrittig wie möglich vor. Die folgenden elf Screenshots illustrieren, wie eine Benutzereingabe Schritt für Schritt vom Debugger verarbeitet wird, bis am Ende die Eingabe in der Variablen startwert gespeichert worden ist:

Schritt 1Schritt 2Schritt 3Schritt 4
Schritt 1Schritt 2Schritt 3Schritt 4
Schritt 5Schritt 6Schritt 7Schritt 8
Schritt 5Schritt 6Schritt 7Schritt 8
Schritt 9Schritt 10Schritt 11
Schritt 9Schritt 10Schritt 11

Insbesondere bei der Auswertung von komplexeren Ausdrücken, die nur in einigen Fällen Fehler produzieren, kann es hilfreich sein, sich nach jedem kleinsten Schritt des gegenwärtigen Zwischenstandes der Ausführung bewusst zu sein.

Besonders hilfreich ist hierbei das andockbare Variablen-Fenster. In diesem Fenster kann der Inhalt aller verwendeten Variablen eines Programms eingesehen werden. Dies funktioniert jedoch nur, wenn der Programmablauf angehalten ist; während z.B. eine besonders lange Wiederholung läuft, zeigt das Variablenfenster keine Änderungen an. In diesem Fall muss der Debugger benutzt werden, um den Ablauf der Wiederholung Schritt für Schritt nachvollziehen zu können.


  1. Die Begriffe bug und debugging waren bereits lange vor dem Vorfall mit der Motte gebräuchlich, aber die Anekdote ist einfach so schön. ↩︎

5.7 Datenstrukturen

Aus der visuellen Programmierung kennen wir die Liste als Datenstruktur, um eine variable Anzahl semantisch zusammengehöriger Informationen zu speichern. Auch in Python können wir mit Listen arbeiten, darüber hinaus stehen uns aber noch weitere Datenstrukturen zur Verfügung: Tupel, Mengen und Wörterbücher.

Die grundlegenden Eigenschaften und Unterschiede zwischen den anderen drei Datenstrukturen sollen hier kurz zusammengefasst werden:

Eigenschaft/​FunktionalitätListeTupelMenge
Anzahl Elementevariabelfestvariabel
Elemente dürfen mehrmals vorkommenjajanein
Elemente dürfen verändert werdenjaneinnein
Reihenfolge der Elementefestfestbeliebig
Definitiona = [42, True, 'Hello']b = (42, True, 'Hello')c = {42, True, 'Hello'}
Element an Position $x$ abrufena[x]b[x]nicht möglich, da Mengen keine Reihenfolge haben
Größe bestimmenlen(a)len(b)len(c)
Element hinzufügena.append(3.141)nicht möglich, da die Länge von Tupeln beschränkt istc.add(3.141), hat keine Auswirkung, wenn das Element schon vorher in der Menge enthalten war.
Prüfen, ob $x$ enthalten istx in ax in bx in c

Slicing

Python gestattet nicht nur den Zugriff auf einzelne Elemente einer Liste, sondern ermöglicht auch unkompliziert das Erstellen von Teillisten, das so genannte Slicing. Dafür können bis zu drei Indizes angegeben werden:

  1. der Beginn der Teilliste
  2. das erste Element, das nicht mehr Teil der Teilliste sein soll
  3. der Abstand zwischen den Elementen der Teilliste

Notiert werden diese Indizes durch Doppelpunkte getrennt in eckigen Klammern hinter dem Namen der Liste, also Liste[Start:Ende:Schritt].

Alle drei Indizes können leer gelassen werden. In diesem Fall werden folgende Standardwerte eingesetzt:

  1. 0 für den Beginn
  2. len(Liste) für das Ende
  3. 1 für die Schrittweite
>>> a = [1,2,3,4,5,6,7,8,9,10]
>>> a[3:7]
[4, 5, 6, 7]
>>> a[3:]
[4, 5, 6, 7, 8, 9, 10]
>>> a[:5]
[1, 2, 3, 4, 5]
>>> a[1:7:2]
[2, 4, 6]
>>> a[1::2]
[2, 4, 6, 8, 10]
>>> a[:7:2]
[1, 3, 5, 7]
>>> a[::]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Für alle Indizes können auch negative Werte eingesetzt werden. Dies bedeutet, dass vom Ende der Liste an gezählt wird statt vom Anfang. Ein Start- oder End-Index von -1 bezieht sich also auf das letzte Element der Liste. Wenn der Endwert kleiner ist als der Startwert, kann eine negative Schrittweite angegeben, um die Elemente rückwärts aufzuzählen

>>> a[2:-2]
[3, 4, 5, 6, 7, 8]
>>> a[-9:7]
[2, 3, 4, 5, 6, 7]
>>> a[-5:-1]
[6, 7, 8, 9]
>>> a[-5:-1:2]
[6, 8]
>>> a[-1:-7:-2]
[10, 8, 6]
>>> a[7:3]
[]
>>> a[7:3:-1]
[8, 7, 6, 5]
>>> a[::-2]
[10, 8, 6, 4, 2]

Strings als Sonderfall von Listen

Strings werden in vieler Hinsicht wie Listen behandelt und unterstützen einige Listenoperationen, insbesondere Slicing:

>>> b = 'Panamakanal'
>>> b[5]
'a'
>>> b[1::2]
'aaaaa'
>>> b[::-1]
'lanakamanaP'
>>> 'e' in b
False
>>> 'n' in b
True
>>> len(b)
11

Anders als Listen können Strings aber nicht verändert werden:

>>> a[2] = 0
>>> a
[1, 2, 0, 4, 5, 6, 7, 8, 9, 10]
>>> a.append(11)
>>> a
[1, 2, 0, 4, 5, 6, 7, 8, 9, 10, 11]
>>> b[1] = 'e'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> b.append('?')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'append'

Exkurs: Wörterbücher

Wörterbücher, englisch dictionaries, sind eine Schlüssel-Wert-Datenstruktur, d.h. die Daten werden in Paaren von einem Schlüssel und einem dazugehörigen Wert abgelegt. Mithilfe des Schlüssels kann der Wert dann effizient gefunden werden.

>>> englischvokabeln = {'cat': 'Katze', 'dog': 'Hund', 'mouse': 'Maus'}
>>> englischvokabeln
{'cat': 'Katze', 'dog': 'Hund', 'mouse': 'Maus'}

Es gibt zwei Möglichkeiten, einen Wert mithilfe eines Schlüssels aus einem Wörterbuch abzufragen:

  1. Man setzt den Schlüssel wie einen Listen-Index in eckige Klammern; dies führt zu einer Fehlermeldung, wenn man einen nicht vorhandenen Schlüssel abfragt:

    >>> englischvokabeln['cat']
    'Katze'
    >>> englischvokabeln['dog']
    'Hund'
    >>> englischvokabeln['duck']
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'duck'
    
  2. Man verwendet die get-Methode; die Abfrage eines nicht vorhandenen Schlüssels sorgt dann dafür, dass ein Standardwert zurückgegeben wird, den man optional ebenfalls angeben kann:

    >>> englischvokabeln.get('dog')
    'Hund'
    >>> englischvokabeln.get('duck')
    >>> englischvokabeln.get('dog', 'Das weiß ich nicht')
    'Hund'
    >>> englischvokabeln.get('duck', 'Das weiß ich nicht')
    'Das weiß ich nicht'
    

Mit den Methoden keys, values und items können die Schlüssel, Werte und Schlüssel-Wert-Paare des Wörterbuches in Form von Listen ausgewertet werden:

>>> englischvokabeln.keys()
dict_keys(['cat', 'dog', 'mouse'])
>>> englischvokabeln.values()
dict_values(['Katze', 'Hund', 'Maus'])
>>> englischvokabeln.items()
dict_items([('cat', 'Katze'), ('dog', 'Hund'), ('mouse', 'Maus')])

5.8 Objekte

Objekte

Den Begriff Objekt haben wir bereits im Kapitel über visuelle Programmierung kennengelernt. Genau wie Scratch ist auch Python eine objektorientierte Sprache. Das heißt, dass jeder Bestandteil der Ausführung eines Python-Programms als ein Objekt behandelt wird, das eine Identität, einen Zustand und ein definiertes Verhalten aufweist.

Objektorientierte Programmierung ermöglicht eine sehr genaue Kontrolle darüber, welche Programmteile welche Funktionen ausführen dürfen. Insbesondere behalten die Objekte selbst die Kontrolle über ihre Daten.

Beispiel: Stellen wir uns eine fiktive Software vor, die ein Bankkonto implementiert. In diesem Fall möchte man aus einer Vielzahl von Gründen Änderungen am Kontostand nicht uneingeschränkt der ganzen Software ermöglichen:

  • Es gibt für die meisten Konten ein Abbuchungslimit, das überprüft werden muss.
  • Ebenso muss überprüft werden, ob die Abbuchung den Disporahmen ausschöpfen würde.
  • Wenn das Konto dadurch ins Minus gerät, müssen Dispozinsen erhoben und der Kunde ggf. benachrichtigt werden.
  • Verdächtige Abbuchungen müssen überprüft und ggf. mit dem Kunden besprochen werden.

Durch die objektorientierte Programmierung kann man diese Zugriffsbeschränkungen den Konto-Objekten überlassen. Dadurch wird der ganze Code, der das Verhalten dieses Objekts kontrolliert, an einer Stelle konzentriert und der Zugriff darauf kann den anderen Objekten besser verwehrt werden.

Ähnliches gibt es in der visuellen Programmierung, wo man z.B. die Position eines anderen Objektes zwar abfragen, aber nicht selbsttätig verändern kann. Die einzige Möglichkeit dazu wäre, eine Nachricht an das andere Objekt zu schicken und es zu bitten, seine Position zu verändern.

Dieses Prinzip, Daten und Informationen vor dem Zugriff von außen zu verbergen und nur über definierte Schnittstellen zuzulassen, nennt man Kapselung.

Klassen

Objekte sind in Python immer Instanzen von Klassen.

Der Objekt-Inspektor in Thonny

Die Identität eines Objektes wird definiert über seine(n) Namen. Namen sind in Python Referenzen, die auf Stellen im Objektspeicher verweisen, an denen die Objektdaten liegen. Mit Thonnys Objekt-Inspektor können wir uns zu jedem Objekt Details anzeigen lassen, wie die Abbildung zeigt.

Bei dem betrachteten Objekt handelt es sich um die Zahl 1, auch diese ist in Python (anders als in anderen objektorientierten Sprachen wie Java) ein Objekt mit Attributen und Methoden.

Oben im Objekt-Inspektor steht int @ 0x7fbf10218110, Typ und Speicheradresse des Objekts. Wenn man diese Informationen in einem Python-Programm benötigen sollte, kann man sie mit type(x) und id(x) abfragen.

Gleichheit und Identität

“Das gleiche” ist nicht dasselbe wie “dasselbe”. Zwei Python-Objekte sind gleich, wenn sie den gleichen Wert haben. Die Gleichheit wird mit dem Operator == abgeprüft.

>>> a = 5
>>> b = 5
>>> c = 'hello'
>>> d = 'hello'
>>> e = [1,2,3]
>>> f = [1,2,3]
>>> a == b
True
>>> c == d
True
>>> e == f
True
>>> a == c
False
>>> a == f
False

Zwei Python-Objekte sind identisch, wenn ihre Bezeichner auf dieselbe Speicheradresse verweisen. Atomare Daten wie Zahlen oder Strings, die gleich sind, sind in der Regel auch identisch. Auf zusammengesetzte Datentypen wie Listen trifft dies jedoch nicht zu. Die Identität wird mit dem Operator is abgeprüft.

>>> a = 5
>>> b = 5
>>> c = 'hello'
>>> d = 'hello'
>>> e = [1,2,3]
>>> f = [1,2,3]
>>> a is b
True
>>> c is d
True
>>> e is f
False
>>> a is c
False
>>> a is f
False

Wenn mehrere Objekte identisch sind und man eines davon verändert, verändern sich ebenso alle anderen, da sie alle auf dieselbe Speicherstelle verweisen:

>>> a = [1,2,3]
>>> b = [1,2,3]
>>> c = a
>>> a is b
False
>>> a is c
True
>>> b is c
False
>>> a.append(4)
>>> b
[1, 2, 3]
>>> c
[1, 2, 3, 4]

Mit der copy-Methode können Kopien von Objekten angelegt werden, die zwar gleich, aber nicht identisch sind.

>>> d = a.copy()
>>> a == d
True
>>> a is d
False

6. Links


6.1 Materialsammlungen

Auf dieser Seite finden Sie Links zu Materialsammlungen aus anderen Weiterbildungen zur Informatik in der Sekundarstufe I, sowie Materialien für die Unterrichtsgestaltung, die zum Teil auch in dieser Weiterbildung behandelt werden. Die Sammlung wird nach und nach erweitert und um Informationen ergänzt.

Weiterbildungen

Unterrichtsmaterialien

Fachdidaktik

Programmierwettbewerbe

Programmierung

Scratch

Python

HTML & CSS

  • W3Schools (englisch): https://www.w3schools.com
    • Umfangreiche Tutorials, Referenzen und Übungsaufgaben zur Webentwicklung, unter anderem zu HTML und CSS
  • SELFHTML: https://selfhtml.org
    • Deutschsprachige Dokumentation zu HTML/CSS mit Tutorials und Referenzen im Wiki, sowie Forum zum Austausch

6.2 Software-Werkzeuge

Auf dieser Seite finden Sie einen Überblick über die Software, die im Rahmen der Weiterbildung verwendet wird, sowie über weitere Werkzeuge zur Unterstützung Ihres Unterrichts. Die Sammlung wird nach und nach erweitert und um Informationen ergänzt.

Programmierwerkzeuge

Visuelle Programmierung

Textuelle Programmierung

  • Thonny: Didaktisch orientierte Entwicklungsumgebung für Python
    • Entwickler: Aivar Annamaa (Universität Tartu) u. a.
    • Lizenz: MIT-Lizenz (Open Source)
    • Offizielle Website: https://thonny.org
  • TigerJython: Didaktisch orientierte Entwicklungsumgebung für Python

Webentwicklung

  • W3Schools Online Code Editor: Online-Editor zum Bearbeiten und Anzeigen von HTML-Dokumenten im Browser (im Rahmen der W3Schools-Tutorials zu HTML und CSS)
  • Glitch: Online-Entwicklungsumgebung zum Bearbeiten und Anzeigen von HTML/CSS im Browser (auch umfangreichere Projekte aus mehreren Dateien), die sich online bereitstellen und remixen lassen
    • Kosten: “Starter”-Version kostenlos nutzbar, hier sind alle erstellten Projekte öffentlich
    • Offizielle Website (englisch): https://glitch.com
  • CodePen: Online-Entwicklungsumgebung mit ähnlichem Umfang und Einsatzmöglichkeiten wie Glitch, unterstützt allerdings nur jeweils eine einzelne HTML- und CSS-Datei pro Projekt
    • Kosten: Freie Version mit eingeschränktem Funktionsumfang (alle erstellten Projekte öffentlich)
    • Offizielle Website (englisch): https://codepen.io
  • W3C Online-Validator für HTML-Dokumente: https://validator.w3.org
  • W3C Online-Validator für CSS: https://jigsaw.w3.org/css-validator/it
  • X-Ray Googles: Online-Tool zum Live-Inspizieren und Verändern von HTML-Seiten im Browser

Modellierung

Netzwerke und Internet

Simulation von Netzwerken

Allgemeine Werkzeuge

Grafikprogramme

  • GIMP: Freie Software zum Erstellen und Bearbeiten von Rastergrafiken
  • Inkscape: Freie Software zum Erstellen und Bearbeiten von Vektorgrafiken

Texteditoren

  • Atom: Frei verfügbarer Texteditor mit vielen Erweiterungen (u. a. für HTML/CSS, Programmierung in Python, verteilte Versionsverwaltung mit Git, gemeinsames Bearbeiten von Dokumenten)
    • Entwickler: GitHub
    • Lizenz: MIT-Lizenz (Open Source)
    • Offizielle Website: https://atom.io
  • Visual Studio Code: Frei verfügbarer Texteditor mit vielen Erweiterungen (siehe Atom)
  • Notepad++: Freier Texteditor für Windows, auch als portable Version verfügbar; weniger Erweiterungsmöglichkeiten als Atom/VS Code, dafür leichtgewichtiger und einfacher zu bedienen
  • HexEd.it: Browserbasierter Hex-Editor zum Bearbeiten und Inspizieren von Binärdateien im Hexadezimalformat

Dateiverwaltung

  • 7-Zip: Freie Software zur Datenkompression, unterstützt Verschlüsselung

  1. setzt voraus, dass die Java Runtime Environment (JRE) Version 11 auf dem Rechner installiert ist ↩︎

Sekundarstufe II

7. Blick über die Informatik


8. Algorithmen


9. Grundlagen der Programmierung


9.1 Arithmetische Ausdrücke und Variablen

In Python können Zahlen als primitive Werte verwendet werden. Sie werden dabei automatisch in einer geeigneten Darstellung im Speicher abgelegt. Wie genau die Daten intern dargestellt werden, ist bei der Programmierung in der Regel irrelevant. Es genügt, vordefinierte Funktionen und Operationen zu kennen, mit denen wir Zahlen verarbeiten können.

Durch Verknüpfung mit Funktionen und Operationen entstehen komplexe Ausdrücke, die von Python automatisch ausgewertet werden. Die interaktive Python-Umgebung erlaubt es, beliebigen Python-Code in einem Terminal auszuführen, kann also auch dazu verwendet werden, arithmetische Ausdrücke auszuwerten.

>>> 3 + 4
7

Arithmetische Ausdrücke

Aus der Mathematik kennen wir Ausdrücke wie \(x^2+2y+1\) oder \((x+1)^2\), die auch Variablen enthalten können. Diese entstehen aus Basiselementen

  • Konstanten (z. B. \(1\), \(2\) oder \(\pi\))
  • Variablen (z.B. \(x\), \(y\))

und können durch Anwendung von Funktionen wie \(+\), \(-\), \(\cdot\) auf bereits existierende Ausdrücke gebildet werden. Diese Funktionen (auch Operatoren genannt) sind zweistellig, verknüpfen also zwei Ausdrücke zu einem neuen Ausdruck.

Auch der Ausdruck \(\frac{\sqrt{x^2+1}}{x}\) entsteht durch Anwendung unterschiedlicher Funktionen, allerdings ungewöhnlich notiert. Python erfordert eine einheitlichere Darstellung von Ausdrücken. Zum Beispiel müssen wir

  • x**2 statt \(x^2\),
  • math.sqrt(x) statt \(\sqrt{x}\) und
  • a/b statt \(\frac{a}{b}\)

schreiben. Den Ausdruck \(\frac{\sqrt{x^2+1}}{x}\) schreiben wir in Python also als math.sqrt(x**2+1)/x. Hierbei können wir durch festgelegte Präzedenzen (Punktrechnung vor Strichrechnung) auf Klammern verzichten. Schreiben wir stattdessen math.sqrt(x**(2+1))/x, so ergibt sich nicht der gleiche Ausdruck, da die Funktion ** stärker bindet als +.

Der größte Teil der Funktionalität von Python wird in Form von Modulen zur Verfügung gestellt, die man beim Programmieren explizit importieren muss, um sie verwenden zu können. Die Funktion sqrt ist Teil des Moduls math und kann mit import math oder from math import sqrt importiert werden.

Im Folgenden werten wir beispielhaft einige arithmetische Ausdrücke in der Python-Umgebung aus:

>>> import math
>>> 3**2
9
>>> math.sqrt(25)
5.0
>>> 9/3
3.0
>>> math.sqrt(5**2-9)/4
1.0

Variablen und Zuweisungen

In der Mathematik können arithmetische Ausdrücke Variablen enthalten, die als Platzhalter für unbekannte Werte stehen. Auch in Programmiersprachen können wir Variablen verwenden, wenn wir ihnen vorher einen Wert zuweisen.

Als Beispiel für einen Ausdruck mit Variablen betrachten wir die Formel \(\pi \cdot r^2\) zur Bestimmung des Flächeninhalts eines Kreises mit gegebenem Radius \(r\).

In Python können wir diese Formel wie folgt schreiben:

>>> import math
>>> math.pi * r**2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'r' is not defined

Da wir der Variablen r jedoch noch keinen Wert zugewiesen haben, liefert Python beim Versuch, die Formel auszuwerten, eine Fehlermeldung. Durch Zuweisung verschiedener Werte an r können wir den Flächeninhalt von Kreisen mit unterschiedlichen Radien berechnen.

>>> import math
>>> r = 2
>>> math.pi * r**2
12.566370614359172
>>> r = 4
>>> math.pi * r**2
50.26548245743669

Die Zeilen r = 2 und r = 4 sind anders als alles bisher eingegebene keine Ausdrücke, sondern Zuweisungen, also eine spezielle Form sogenannter Anweisungen oder Instruktionen. Anweisungen haben anders als Ausdrücke keinen Wert. Zuweisungen speichern den Wert des Ausdrucks rechts vom Gleichheitszeichen in der Variablen links vom Gleichheitszeichen.

Während in der Mathematik die Gleichung \(x = x + 1\) keine Lösungen hat, ist die Zuweisung x = x + 1 durchaus üblich:

>>> x = 41
>>> x
=> 41
>>> x = x + 1
>>> x
42

Sie weist der Variablen x den Wert x+1 zu, also ihren eigenen um eins erhöhten (alten) Wert.

9.2 Weitere primitive Datentypen

In Python können wir nicht nur arithmetische, sondern zum Beispiel auch logische Ausdrücke auswerten und solche, deren Wert eine Zeichenkette, also Text, ist.

Zeichenketten

Eine Zeichenkette (englisch: string) wird dazu in Anführungszeichen eingeschlossen. Mehrere Zeichenketten können mit dem +- Operator aneinandergehängt werden.

>>> 'Hallo'
'Hallo'
>>> 'Welt'
'Welt'
>>> 'Hallo' + 'Welt'
'HalloWelt'
>>> 'Hallo' + ' ' + 'Welt'
'Hallo Welt'
>>> 'Hallo' + ' ' + 'Welt' + '!'
'Hallo Welt!'

Zahlen können wir mit der Funktion str() in Zeichenketten konvertieren. Auf diese Weise können wir Zeichenketten mit arithmetischen Ausdrücken kombinieren:

>>> str(42)
'42'
>>> str(17+4)
'21'
>>> str(17) + str(4)
'174'
>>> 'Die Antwort ist ' + str(2*(17+4))
=> 'Die Antwort ist 42'

Der Operator + wird also sowohl zur Addition von Zahlen als auch zur Konkatenation von Zeichenketten verwendet. Beim Versuch, + mit einer Zahl und einer Zeichenkette aufzurufen, erhalten wir allerdings einen Fehler:

>>> '40' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
>>> 40 + '2'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Die Fehlermeldungen deuten darauf hin, dass Zahlen und Zeichenketten nicht automatisch ineinander konvertiert werden, denn es ist unklar, ob als Ergebnis die Zahl 42 oder die Zeichenkette '402' herauskommen soll. Diese Unklarheit müssen wir durch explizite Konvertierung (mittels str()) aufklären. Wollen wir eine Zeichenkette, die eine Zahl enthält, in eine Zahl konvertieren, können wir int() (für Ganzzahlen, englisch integer) oder float() (für Dezimalbrüche bzw. Gleitkommazahlen, englisch floating point number) benutzen.

>>> int('40') + 2
42
>>> '40' + str(2)
'402'
>>> 40 + float('2')
42.0

Logische Ausdrücke

Logische Ausdrücke beschreiben Wahrheitswerte. Sie sind aus den Konstanten True und False aufgebaut, wobei komplexere Ausdrücke durch Anwendung logischer Operationen gebildet werden können. Eine Konjunktion (logisches “und”) wird durch den Operator and gebildet, eine Disjunktion (logisches “oder”) durch or und eine Negation (logisches “nicht”) durch ein vorangestelltes not. Hier sind einige Beispiele für logische Ausdrücke in der Python-Umgebung:

>>> True and False
False
>>> not False
True
>>> False or (True and not False)
True

Auch Vergleichsoperatoren haben logische Werte als Ergebnis. Zum Beispiel liefert der Ausdruck 3 < 4 das Ergebnis True. Hier sind weitere Beispiele für logische Ausdrücke mit Vergleichsoperatoren:

>>> 4 < 3
False
>>> 5+2 >= 6
True
>>> 5+2 >= 6+3
False
>>> 5+2 >= 6+3 or 3 <= 10/2
True
>>> 5+2 >= 6+3 or 3 <= 10/5
False
>>> 5+2 == 3+4
True
>>> 5+2 != 3+4
False

Nicht verwechseln! Ein einfaches Gleichheitszeichen = steht in Python für eine Wertzuweisung (z. B. x = 4). Für den Vergleich, ob zwei Werte identisch sind, muss ein doppeltes Gleichheitszeichen == verwendet werden:

>>> x = 4       # Wertzuweisung
>>> x == 4      # Vergleichsoperation
True
>>> x == 2+2
True
>>> 3 == 4
False
>>> 3 = 4
  File "<stdin>", line 1
    3 = 4
    ^
SyntaxError: cannot assign to literal

Variablen für Text und Wahrheitswerte

Auf rechten Seiten einer Zuweisung können beliebig komplizierte Ausdrücke stehen, deren Werte nicht unbedingt Zahlen zu sein brauchen.

>>> antwort = 2*(17+4)
>>> antwort
42
>>> text = 'Die Antwort ist ' + str(antwort)
>>> text
'Die Antwort ist 42'
>>> antwort = text == 'Die Antwort ist 42'
>>> antwort
True

Hier wird der Variablen antwort zunächst der Wert 42 zugewiesen und dieser dann zur Definition der Variablen text verwendet. Schließlich wird der Wert der Variablen antwort auf True geändert, indem ihr das Ergebnis eines Vergleiches zugewiesen wird.

9.2.1 Übungsaufgaben

Aufgaben

Aufgabe 1: Ausdrücke und Zuweisungen

Starten Sie die interaktive Python-Umgebung python in einem Terminal und bestimmen Sie den Wert ausgewählter Ausdrücke. Achten Sie dabei darauf, in welcher Reihenfolge komplexe Ausdrücke ausgewertet werden.

Verwenden Sie Zuweisungen, um den Wert von Teilausdrücken in einer Variablen zu speichern. Welche Vorteile hat die Definition von Variablen?

Aufgabe 2: Programme ergänzen

Ergänzen Sie in den im folgenden gezeigten Programmen jeweils die markierte Zeile darart, dass das Programm 42 ausgibt.

Hinweise:

  • Die letzte Programmzeile der gezeigten Programme enthält jeweils eine print()-Anweisung. Der Effekt einer solchen Anweisung ist, dass der Wert des Ausdrucks, der an print() übergeben wird, in eine Zeichenkette umgewandelt und im Terminal ausgegeben wird. Zeichenketten werden dabei ohne Anführungszeichen ausgegeben.
  • Das #-Zeichen ist ein Kommentarzeichen. Es bewirkt, dass alle folgenden Zeichen bis zum Zeilenende vom Python-Interpreter ignoriert werden.

a)

zahl = 41

zahl + 1    # diese Zeile ergänzen oder korrigieren

print(zahl)

b)

wort = "40"

# hier eine Zeile einfügen

print(int(wort) + zahl)

c)

wort = "2"

# hier eine Zeile einfügen

print(str(zahl) + wort)

d)

zahl = 41

zahl + 1 = zahl    # diese Zeile ergänzen oder korrigieren

print(zahl)

e)

x = 6

x * 7    # diese Zeile ergänzen oder korrigieren

print(result)

f)

x = 6
s = str(5 * x)

ergebnis = s + 2*x    # diese Zeile ergänzen oder korrigieren

print(ergebnis)

g)

zwei = 2
vierzig = 40

# hier eine Zeile einfügen

print(zwei + und + vierzig)

h)

zwei = "2"
vier = "4"

# hier eine Zeile einfügen

print(vier + zigund + zwei)

i)

a = 0
b = 1
c = a
d = a + b

# hier eine Zeile einfügen

f = d - e

print(a * 2**0 + b * 2**1 + c * 2**2 + d * 2**3 + e * 2**4 + f * 2**5)

j)

a = 1 + 3 + 5
b = a + 7 + 9 + 11

str(a + b - 1)    # diese Zeile ergänzen oder korrigieren

print(value)

9.3 Bedingte Anweisungen

Nachdem wir im vorherigen Abschnitt Zuweisungen kennen gelernt haben, mit denen der Wert eines Ausdrucks in einer Variablen gespeichert werden kann, wenden wir uns nun einer weiteren Form der Anweisung zu. In bedingten Anweisungen ist die Ausführung einzelner Anweisungen vom Wert eines logischen Ausdrucks abhängig.

Die folgende Anweisung, in der das Schlüsselwort1 if vorkommt, demonstriert diese Idee:

if x < 0: x = -1 * x

Hier wird die Anweisung x = -1 * x nur dann ausgeführt, wenn der Wert des logischen Ausdrucks x < 0 gleich True ist, wenn also der Wert von x kleiner als 0 ist. Ist das nicht der Fall (ist also der Wert des logischen Ausdrucks x < 0 gleich False) dann wird die Zuweisung x = -1 * x nicht ausgeführt. In jedem Fall hat also nach der Ausführung der bedingten Anweisung die Variable x einen nicht-negativen Wert, nämlich den Absolutbetrag ihres ursprünglichen Wertes. Die folgenden Aufrufe demonstrieren die Auswertung dieser bedingten Anweisung:

>>> x = -4
>>> x
-4
>>> if x < 0: x = -1 * x
...
>>> x
4
>>> if x < 0: x = -1 * x
...
>>> x
4

In der interaktiven Python-Umgebung zeigt ... an, dass der if-Block noch nicht abgeschlossen ist und weitere Anweisungen als Teil des Blocks hinzugefügt werden können. Zur Beendung des if-Blocks muss Enter gedrückt werden.

Bedingte Anweisungen können auch Alternativen enthalten, die ausgeführt werden, wenn die Bedingung nicht erfüllt ist. Dazu verwenden wir das Schlüsselwort else wie im folgenden Beispiel:

if x > y: z = x
else: z = y

Hier wird der Variablen z der Wert der Variablen x zugewiesen, falls dieser größer ist als der Wert von y. Ist das nicht der Fall, erhält z den Wert von y. In jedem Fall hat also die Variable z nach dieser Anweisung den Wert des Maximums der Werte von x und y.

Die folgende Anweisungsfolge demonstriert die Auswertung einer solchen Berechnung:

>>> x = 4
>>> y = 5
>>> if x < y: z = x
... else: z = y
...
>>> z
5

Da der Wert des logischen Ausdrucks x > y gleich False ist, wird die Zuweisung z = y ausgeführt. Danach hat die Variable z also den Wert 5.

Bedingte Anweisungen mit Alternative werden Bedingte Verzweigungen genannt. Bedingte Anweisungen ohne Alternative heißen auch Optionale Anweisungen.

Statt Anweisungen in der interaktiven Python-Umgebung einzugeben, können wir sie auch in einer Textdatei speichern. Dabei können wir einzelne Anweisungen auf mehrere Zeilen verteilen, was der Lesbarkeit des Programms zugute kommt.

Wir können zum Beispiel das folgende Programm in einer Datei max.py speichern.

x = 4
y = 5
if x > y:
  z = x
else:
  z = y
print(z)

Hier ist die bedingte Anweisung auf mehrere Zeilen verteilt und zusätzlich eingerückt. Dies dient nicht nur der besseren Lesbarkeit, sondern hat auch eine syntaktische Funktion. Mehr dazu im Absatz Einrückungen.

Die Ausgabe-Anweisung print(z) dient dazu, den Wert von z im Terminal auszugeben. Dies ist nötig, da wir das Programm nicht in einer interaktiven Umgebung, die Ergebnisse von Ausdrücken automatisch anzeigt, sondern mit dem Interpreter python auswerten. Dazu wechseln wir im Terminal in das Verzeichnis, in dem wir das Programm max.py gespeichert haben und führen es dann mit dem folgenden Kommando aus:

$ python max.py
5

Als letzter Schritt der Ausführung wird die Zahl 5 im Terminal ausgegeben.

Einrückungen

Anders als in vielen anderen Sprachen wird in Python ausschließlich über Einrückungen festgelegt, welche Anweisungen von einer Bedingung abhängen.

Betrachten wir die folgenden Beispiele:

if x < 0:
  x = -1 * x
  y = y + 1

und

if x < 0:
  x = -1 * x
y = y + 1

Im ersten Fall wird die Anweisung y = y + 1 nur ausgeführt, falls der Ausdruck x < 0 zu True ausgewertet wird, im zweiten Fall wird y = y + 1 immer ausgeführt:

>>> x = -4
>>> y = 0
>>> if x < 0:
...   x = -1 * x
...   y = y + 1 # Diese Zeile ist abhängig von x < 0
...
>>> x
4
>>> y
1
>>> if x < 0:
...   x = -1 * x
...   y = y + 1 # Diese Zeile ist abhängig von x < 0
...
>>> x
4
>>> y
1
>>> if x < 0:
...   x = -1 * x
...
>>> y = y + 1 # Diese Zeile ist NICHT abhängig von x < 0
>>> x
4
>>> y
2

Dies gilt insbesondere, wenn die bedingten Anweisungen ineinander geschachtelt sind – wenn also die Alternativen selbst auch wieder bedingte Anweisungen sind. Als Beispiel einer geschachtelten bedingten Anweisung betrachten wir das folgende Programm xor.py, das das Ergebnis der Exklusiv-Oder-Verknüpfung zweier Variablen ausgibt.

x = True
y = False
if x:
  if y:
    z = False
  else:
    z = True
else:
  if y:
    z = True
  else:
    z = False
print(str(x) + " xor " + str(y) + " = " + str(z))

Hier werden die logischen Ausdrücke x und y als Bedingungen für bedingte Anweisungen verwendet, wobei die zweite im so genannten “then”-Zweig der ersten und die dritte im so genannten “else”-Zweig der ersten steht. Bei der Ausführung dieses Programms wird das Ergebnis von True xor False ausgegeben.

$ python xor.py
True xor False = True

  1. Schlüsselworte sind von einer Programmiersprache reservierte Namen mit besonderer Bedeutung. Sie dürfen daher nicht als Variablennamen verwendet werden. ↩︎

9.3.1 Übungsaufgaben

Hausaufgabe: Programme mit Bedingten Anweisungen

Schreiben Sie ein Python-Programm max3.py, das das Maximum dreier Werte im Terminal ausgibt, die in den ersten drei Zeilen des Programms den Variablen x, y, und z zugewiesen werden. Achten Sie auf korrekte Einrückungen. Testen Sie Ihre Implementierung mit geeigneten Werten.

Schreiben Sie Python-Programme not.py, and.py und or.py, die logische Negation, Konjunktion beziehungsweise Disjunktion von am Programmanfang zugewiesenen Variablen im Terminal ausgeben. Verwenden Sie dabei keine vordefinierten logischen Operationen sondern nur bedingte Anweisungen analog zum Programm für die exklusive Oder-Verknüpfung aus der Vorlesung. Definieren Sie ihre Programme so, dass Ausgaben der Form not false = true, true and false = false und true or false = true erzeugt werden. Testen Sie Ihre Implementierung mit allen möglichen Werten und protokollieren Sie dabei die Ausgabe Ihres Programms.

9.4 Schleifen

Neben bedingten Anweisungen gibt es in höheren Programmiersprachen auch Sprachkonstrukte zur wiederholten Ausführung von Anweisungen. Im folgenden werden zwei verschiedene solcher Konstrukte vorgestellt: die Zähl-Schleife und die bedingte Schleife.

Zähl-Schleifen

Eine Zähl-Schleife wiederholt eine Anweisung (oder einen Anweisungsblock), wobei eine Zählvariable einen festgelegten Zahlenbereich durchläuft. Die Anzahl der Wiederholungen ist also durch den definierten Zahlenbereich festgelegt. Als Beispiel für eine Zähl-Schleife schreiben wir ein Programm 1bis100.py, das die Zahlen von 1 bis 100 addiert:

sum = 0
for i in range(1, 101):
  sum = sum + i
print(sum)

Hier sind for und in Schlüsselworte. Die so genannte Zählvariable i nimmt während der wiederholten Ausführung des so genannten Schleifenrumpfes sum = sum + i nacheinander die Werte von 1 bis 100 an, sodass in sum nach Ausführung der Wiederholung die Summe der Zahlen von 1 bis 100 gespeichert ist, die mit der letzten Anweisung ausgegeben wird. Der zweite Parameter der range-Funktion gibt den Wert an, der nicht mehr berücksichtigt werden soll.

$ python 1bis100.py
5050

Als Grenzen für den von der Zählvariable durchlaufenen Zahlenbereich können wir beliebige Ausdrücke verwenden, deren Wert eine Zahl ist – insbesondere auch Variablen, wie das folgende Beispiel zeigt:

n = 7
q = 0
for i in range(1,n+1):
  u = 2*i - 1
  q = q + u
print(str(n) + "zum Quadrat ist " + str(q))

Bei diesem Programm besteht der Rumpf der Wiederholung aus zwei Zuweisungen. Die erste definiert u als die i-te ungerade Zahl und die zweite addiert diese zur Variablen q hinzu. Nach Ausführung der Wiederholung ist in q also die Summe der ersten n ungeraden Zahlen gespeichert, also n zum Quadrat. Wenn wir dieses Programm in der Datei quadrat.py speichern und diese dann ausführen, erhalten wir die folgende Ausgabe:

$ python quadrat.py
7 zum Quadrat ist 49

Bedingte Schleifen

Sogenannte bedingte Schleifen sind ein weiteres Konstrukt höherer Programmiersprachen zur Wiederholung von Anweisungen. Anders als bei Zähl-Schleifen hängt die Anzahl der Wiederholungen bei einer bedingten Schleife nicht von einem vorab definierten Zahlenbereich ab, sondern von einem logischen Ausdruck, der vor jedem Schleifendurchlauf ausgewertet wird. Ist der Wert dieser sogenannten Schleifenbedingung gleich True, so wird der Rumpf (ein weiteres Mal) ausgeführt, ist er gleich False, so wird die Ausführung der bedingten Schleife beendet. Bei einer bedingten Schleife ist also nicht immer vorab klar, wie oft der Schleifenrumpf ausgeführt wird, da der Wert der Bedingung von Zuweisungen im Schleifenrumpf abhängen kann.

Als erstes Beispiel für eine bedingte Schleife berechnen wir wieder die Summe der Zahlen von 1 bis 100:

i = 0
sum = 0
while i < 100:
  i = i + 1
  sum = sum + i
print(sum)

Nach dem Schlüsselwort while steht die Schleifenbedingung, danach folgt der eingerückte Schleifenrumpf. Anders als mit der Zähl-Schleife müssen wir hier den Wert der Zählvariable i explizit setzen, da bedingte Schleifen keine eingebaute Zählvariable haben. Falls i bei der Prüfung der Schleifenbedingung nicht mehr kleiner 100 ist, wird die Schleife beendet und die Summe der ersten 100 Zahlen ausgegeben.

In diesem Beispiel ist die Anzahl der Schleifendurchläufe einfach ersichtlich, da die Schleifenbedingung nur von dem Wert der Variablen i abhangt, die in jedem Schleifendurchlauf um eins erhöht wird. Im folgenden Programm ist die Anzahl der Schleifendurchläufe nicht so einfach ersichtlich.:

n = 144
i = 0
q = 0
while q < n:
  i = i + 1
  q = q + 2*i - 1
print(i)

Hier wird in jedem Durchlauf die Zählvariable i um eins erhöht und (wie beim Programm quadrat.py) der Variablen q die i-te ungerade Zahl hinzuaddiert. Die Schleife wird ausgeführt, solange der Wert von q kleiner als n ist. Sie bricht also ab, sobald q größer oder gleich n ist.

Wie im Programm quadrat.py ist nach jedem Schleifendurchlauf q = i*i. Das obige Programm gibt also die kleinste Zahl i aus, deren Quadrat größer oder gleich n ist. Ist n eine Quadratzahl, so ist die Ausgabe des Programms deren Quadratwurzel.

$ python wurzel.py
12

Bei der Programmierung mit bedingten Schleifen ist Vorsicht geboten, da nicht sichergestellt ist, dass die Schleifenbedingung irgendwann nicht mehr erfüllt ist. In diesem Fall bricht die Schleife nie ab, läuft also (potentiell) endlos weiter.

Eine einfache Endsloswiederholung können wir wie folgt definieren:

while True:
  print("hi!")

Da diese Schleife nie beendet wird, werden nach ihr folgende Anweisungen nie ausgeführt. Eine häufige Fehlerquelle sind Zählvariablen, die wir vergessen im Rumpf zu erhöhen. Auch das folgende Programm terminiert also nicht:

i = 0
sum = 0
while i < 100:
  sum = sum + i

Um versehentliche Nicht-Terminierung von vornherein auszuschließen sollten Sie, wenn möglich, Zähl-Schleifen verwenden. Nur wenn die Anzahl der Schleifendurchläufe nicht (einfach) ersichtlich ist, sollten Sie auf bedingte Schleifen zurückgreifen.

9.4.1 Übungsaufgaben

Aufgabe: Fakultäts-Berechnung mit Schleifen

Die Fakultät einer Zahl n ist definiert als das Produkt der Zahlen von 1 bis n. Schreiben Sie ein Programm fakultaet.py, das die Fakultät einer am Programmanfang zugewiesenen Variablen n ausgibt. Berechnen Sie das Ergebnis einmal mit einer for-Schleife und einmal mit einer while-Schleife. Vergleichen Sie die beiden Implementierungen. Welche bevorzugen Sie? Begründen Sie Ihre Antwort.

Hausaufgabe: Fachsprache zur Beschreibung von Programmen

Benennen Sie im folgenden Programm alle Programmkonstrukte mit ihrem korrekten Namen. Benennen Sie insbesondere alle Anweisungen und alle Ausdrücke und geben Sie dabei auch die Teilausdrücke komplexer Ausdrücke an.

text = "Ho"
zahl = 3
for i in range(1,zahl+1):
  if i % 2 == 1:
    text = text + text
print(text)

Beschreiben Sie den Ablauf des Programms umgangsprachlich und geben Sie an, was es ausgibt.

Bonusaufgabe: Zählschleifen untersuchen

Ergründen Sie experimentell, wie sich for-Schleifen in Python verhalten, wenn im Schleifenrumpf Zuweisungen an die Zählvariable enthalten sind. Welche Werte hat die Zahlvariable jeweils vor einer solchen Zuweisung? Können Sie mit Hilfe von Zuweisungen an die Zählvariable eine nicht terminierende for-Schleife schreiben?

Bonusaufgabe: Python-Programm, das sich selbst ausgibt

Schreiben Sie ein nicht leeres Python-Programm, das, wenn es ausgeführt wird, seinen eigenen Quelltext im Terminal ausgibt. Verwenden Sie nur Python-Sprachkonstrukte, die bisher in der Vorlesung besprochen wurden.

Hinweise:

  • Diese Bonus-Aufgabe ist sehr schwer. Selbst die Übungsgruppenleiter dürften Schwierigkeiten haben, sie zu lösen. Bei Detailfragen können sie aber trotzdem helfen.
  • Alle Lösungen aus dem Internet, die ich gefunden habe, verwenden Sprachkonstrukte, die wir noch nicht besprochen haben und die sich nicht ohne Weiteres mit unseren Mitteln ausdrücken lassen.
  • Zeilenumbrüche können in Python gelegentlich weggelassen (oder durch Semikolons ersetzt) werden.
  • Zeichenketten können entweder in einfache oder in doppelte Hochkommata eingeschlossen werden. Dies ist nützlich, um Zeichenketten, die Hochkommata enthalten, zu definieren. Zum Beispiel ist "'" eine Zeichenkette, die ein einfaches Kochkomma enthält und '"' eine Zeichenkette, die ein doppeltes Hochkomma enthält.

9.5 Tabellarische Programmausführung

In diesem Abschnitt lernen wir eine systematische Methode kennen, die Ausführung eines Programms zu dokumentieren. Sich im Kopf zu überlegen, welche Variablen wann mit welchen Werten belegt sind, wird bei größeren Programmen schnell unübersichtlich. Übersichtlicher ist eine tabellarische Notation, die zeilenweise festhält, wie sich die Werte von Variablen schrittweise verändern.

Um verschiedene Positionen in einem Programm zu benennen, schreiben wir hinter jede Anweisung einen Kommentar mit einer fortlaufenden Nummer. Auch die Bedingung in bedingten Anweisungen und bedingten Wiederholungen benennen wir mit solchen sogenannten Programmpunkten.

Das folgende Programm zur Berechnung des Absolutbetrags des Wertes einer Variablen x ist mit Programmpunkten annotiert.

x = -4      #1
if x < 0:   #2
  x