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.
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.
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 in der Menüleiste auswählen.
Ö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.
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.
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.
siehe auch Peer Stechert: Kriterien zur Auswahl einer Programmiersprache – Bsp. Scratch aus der Reihe Informatikdidaktik kurz gefasst (Teil 31), Video bei YouTube ↩︎
Beachten Sie dazu auch die Datenschutzbestimmungen von 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.
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.
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 ) 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 ausgeführt werden. Mit einem Klick auf das Symbol 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.
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 “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.
Ö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: |
Vollziehen Sie die folgenden Schritte nach, um einzelne Anweisungen auszutesten:
Ein kleines Beispielprojekt Unterwasserwelt.sb3 zum eigenen Erkunden können Sie hier herunterladen: Download
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.
Objekte in Scratch haben größtenteils Attribute, die bestimmen, wie die Objekte auf der Bühne dargestellt werden. Die wichtigsten Attribute sind dabei:
Die Bühne hat dagegen nur die Attribute Bühnenbild-Nummer (entspricht der Kostüm-Nummer der Figuren) und Lautstärke.
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.
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 …”:
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.
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:
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:
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 “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 bzw. 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.
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).
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 “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 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).
Da Soundeffekte auf Dauer störend sein können, ist es empfehlenswert, sie nur sparsam bzw. möglichst nur mit vorhandenen Kopfhörern einzusetzen.
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”):
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.
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:
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”:
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.
Ein Werteblock wird in Scratch durch einen ovalen Block dargestellt:
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:
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”:
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”:
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”:
Die Werte der wichtigsten Attribute können auch live auf der Bühne angezeigt werden. Suchen Sie dazu den entsprechenden Werteblock der Form 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.
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 angeklickt wird” in den Arbeitsbereich:
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”):
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.
Das angehängte Skript wird ausgeführt, wenn auf die grüne Fahne 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:
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:
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).
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.
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.
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.
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:
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:
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.
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 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:
Die 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. | |
Die Erweiterung “Musik” enthält Anweisungen zum Abspielen verschiedener Instrumente und Schlaginstrumente und eignet sich besser zum Musizieren als die “Klang”-Anweisungen. | |
Die 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. | |
Die Erweiterung “Übersetzen” stellt einen Werteblock zum Übersetzen von Wörtern in verschiedene Sprachen zur Verfügung. Auch diese Erweiterung basiert auf Amazon Web Services. |
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.
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:
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.
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).
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.
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.
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 | |
---|---|
Welche 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? |
|
Welchen Effekt hat diese Anweisung? | |
“Ballon” ist hier ein anderes Objekt auf der Bühne. Welches Objekt bewegt sich hier? Wovon hängt die Bewegungsgeschwindigkeit des Objekts ab? | |
Für welche Attribute ändern sich hier die Werte? | |
Welchen 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? |
|
Angenommen, die Grafik der Figur ist 80 mal 80 Pixel groß. Welche Größe hat sie nach Ausführung dieser Sequenz? | |
Welchen Effekt hat diese Sequenz? |
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 1 | Skript Version 2 | |
---|---|---|
Angenommen, die Figur befindet sich momentan an Position (100, 100). An welcher Position befindet sie sich jeweils, nachdem die Pfeiltaste gedrückt wurde? | ||
Angenommen, die Figur befindet sich vor dem Programmstart an Position (0, 0). An welcher Position befindet sie sich jeweils 1 Sekunde nach Programmstart? | ||
Angenommen, 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? | ||
In welchem Zeitraum nach Anklicken der Figur bewegt sie sich jeweils, in welchem Zeitraum wird die Mitteilung angezeigt? |
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).
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.
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?
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”).
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 nach dem folgenden Drehbuch ablaufen:
Kurz nach dem Programmstart mit 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”. |
|
Anschließ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.) |
|
Als Nächstes sagt Luca: “Nun hängen wir einen weiteren Block daran.” (Text B) | |
Nun 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.) |
|
Als Letztes sagt Luca noch: “Hier wurden beide Anweisungen nacheinander ausgeführt.” (Text C) | |
Zusatzaufgabe: 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 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.
Zur Planung des zeitlichen Ablaufs kann es hilfreich sein, die Aktionen der Objekte in einem Zeitdiagramm zu skizzieren ( Download als PDF):
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 auf ihren Initialzustand zurückgesetzt werden (Position (0, 0), Richtung 90°, Größe 100%).
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).
Der Startbildschirm soll beim Programmstart sichtbar sein. Durch Anklicken der Schaltfläche wird zum Abstimmungsbildschirm umgeschaltet. |
|
Auf 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. |
|
Auf 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:
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. ↩︎
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.
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:
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:
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:
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.
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:
Kontrollanweisungen werden in Scratch allgemein durch Blöcke dargestellt, die wie “Klammern” aussehen (“Klammerblockform”):
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:
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.
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:
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:
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”:
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).
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 Alternative | Bedingte Anweisung mit Alternative |
---|---|
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:
Die 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. | |
Anschließ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. |
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:
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.”:
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:
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:
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:
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.
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
Endloswiederholung | Wiederholung mit fester Anzahl | Bedingte Wiederholung |
---|---|---|
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 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.
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:
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.
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.
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.
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:
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).
Hier wird ein neuer Operator verwendet, nämlich die Umkehrung bzw. Negation eines Wahrheitswertes mit dem “nicht”-Block.
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
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):
Hierfür bietet Scratch auch einen speziellen “warte bis”-Block an, der genau dieselbe Bedeutung hat wie ein “wiederhole bis”-Kontrollblock mit leerem Inhalt:
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.
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.
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.
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.
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):
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.
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?)”.
A UND B ergibt genau dann WAHR, wenn beide Operanden den Wert WAHR haben.
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.
A ODER B ergibt genau dann WAHR, wenn mindestens ein Operand den Wert WAHR hat.
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?”).
NICHT A ergibt genau dann WAHR, wenn A den Wert FALSCH hat.
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. ↩︎
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. ↩︎
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.
Skript 1 |
Skript 2 |
Skript 3 |
Skript 4 |
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.
Skript 1 |
Skript 2 |
Skript 3 |
Skript 4 |
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.
Skript 1 |
Skript 2 |
Skript 3 |
Skript 4 |
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.
Skript 1 |
Skript 2 |
Skript 3 |
Skript 4 |
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:
Skizzieren Sie die Zeichnung, die auf der Bühne entsteht, und beantworten Sie die folgenden Fragen:
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.”
Zum Testen der Bedingungen sind hier die folgenden Blöcke hilfreich:
Der dritte Block ("… von …") sieht in der Blockbibliothek zunächst so aus:
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.
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.
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.
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.
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.
Wenn alles nach Plan läuft, sollte die Bühne nach der Ausführung des Programms folgendermaßen aussehen:
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):
Neben den Blocktypen aus der vorigen Aufgabe Navigation nach Farben können die folgenden Blöcke für diese Aufgabe hilfreich sein:
Dieser Werteblock liefert eine zufällige Ganzzahl zurück, die zwischen den beiden Parameterwerten liegt (hier z. B. zwischen 1 und 10). | |
Dieser Anweisungsblock setzt die Stoppuhr zur Zeitmessung auf Null zurück. | |
Dieser 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.
In dieser Aufgabe soll ein kleines Spiel in Scratch vervollständigt werden. Laden Sie als Vorlage das Scratch-Projekt Flappy_Seagull.sb3 herunter: Download
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.
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.
Die Spielfigur soll während der gesamten Programmausführung fortwährend ihre Grafik wechseln, um den Eindruck einer Animation entstehen zu lassen.
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 ▾):
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) ↩︎
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.
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.
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).
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:
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:
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:
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):
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:
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.
Globale Variable Tempo |
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:
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”:
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.
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
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:
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:
Damit wir den Punktestand während des Spiels sehen, muss das Kästchen links von der Variablen in der Block-Bibliothek angekreuzt werden:
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):
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:
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:
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.
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).
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.
Zustand der Variablen x zu Beginn |
Ausführen der Zuweisung “setze x auf 42” |
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.
Variablen x, y zu Beginn |
Zuweisung “setze x auf 42” |
Zuweisung “setze y auf x” |
Zuweisung “setze x auf x / 2” |
Die letzte Zuweisung ändert hier also nur den Wert von x, nicht den Wert von y.
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.
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”:
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.
Scratch unterscheidet die folgenden drei Datentypen für Werte:
In Scratch stehen die gängigsten mathematischen Operatoren und Funktionen in der Kategorie “Operatoren” (grün) zur Verfügung:
Wenn diese Blöcke abgefragt werden, geben Sie das Berechnungsergebnis für die beiden enthaltenen Werte an (Addition, Subtraktion, Multiplikation oder Division). | |
Wird dieser Block abgefragt, gibt er den Teilungsrest der ganzzahligen Division von a durch b an, wobei a und b die beiden enthaltenen Werte sind. | |
Wird dieser Block abgefragt, gibt er den enthaltenen Wert gerundet auf die nächste Ganzzahl an. | |
Wird 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. | |
Wird 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:
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:
Wird 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. | |
Wird 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. | |
Wird 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. | |
Wird 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:
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:
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:
und die logischen Verknüpfungsoperatoren:
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.
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:
Das folgende Beispiel für eine Zählvariable zählt in der Variablen “Anzahl Mausklicks”, wie oft ein Objekt nach dem Programmstart angeklickt wird:
Das folgende Beispiel demonstriert eine Akkumulatorvariable “Summe Noten”. Hier wird die Summe von 10 Noten berechnet, um die Durchschnittsnote zu ermitteln:
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:
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:
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:
Um die Bildrate aller Animationen im Programm nun nachträglich zu verändern, reicht es, einmal den Wert der Variablen “Bildrate” anzupassen.
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.
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.
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.
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. ↩︎
siehe Peer Stechert: Fehlvorstellungen und Modelle bei Variablen aus der Reihe Informatikdidaktik kurz gefasst (Teil 15), Video bei YouTube ↩︎
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.
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 | |
---|---|
Parameter 1: ____________________________ Parameter 2: ____________________________ Parameter 3: ____________________________ | |
Parameter 1: ____________________________ Parameter 2: ____________________________ | |
Parameter 1: ____________________________ | |
Parameter 1: ____________________________ |
Werteblöcke (Ausdrücke) | |
---|---|
Parameter 1: ____________________________ Parameter 2: ____________________________ Rückgabewert: ____________________________ | |
Parameter 1: ____________________________ Parameter 2: ____________________________ Rückgabewert: ____________________________ | |
Parameter 1: ____________________________ Parameter 2: ____________________________ Rückgabewert: ____________________________ | |
Parameter 1: ____________________________ Parameter 2: ____________________________ Rückgabewert: ____________________________ |
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.
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
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.
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.
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.
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.
Das Quiz könnte also beispielsweise folgendermaßen ablaufen (die richtige Antwort wäre hier 8):
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:
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.
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:
Dieser Anweisungsblock setzt die Stoppuhr zur Zeitmessung auf Null zurück. | |
Dieser Werteblock liefert die Anzahl an Sekunden zurück, die seit dem Programmstart oder letzten Zurücksetzen der Stoppuhr vergangen sind. |
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:
Das folgende Video demonstriert, wie das Spiel mit den Anpassungen aussehen könnte:
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.) ↩︎
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.
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.
In so gut wie allen moderen Programmiersprachen ist es möglich, Textkommentare zum Quellcode hinzuzufügen.
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.
Das Springen der Figur kann durch mehrere Ereignisse ausgelöst werden:
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:
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.
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”.
Außerdem erscheint im Skriptbereich des Objekts ein neuer Definitionsblock, der wie Ereignisblöcke eine “Kopfblockform” hat:
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:
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!”).
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:
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 für diesen Parameter aus dem Definitionsblock in das Eingabefeld der Wiederholung gezogen werden:
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:
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.
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.
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.
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”:
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:
Um Reaktionen auf bestimmte Nachrichten zu programmieren, gibt es einen speziellen Ereignisblock in der Kategorie “Ereignisse”:
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:
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.
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.
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.
Dazu wird im Skriptbereich von “Carol” definiert, dass beim Eintreten des Ereignisses “Wenn ich angeklickt werde” eine Nachricht “stellt euch vor” gesendet wird:
Die Reaktion der anderen beiden Figuren auf diese Nachricht wird in deren Skriptbereich mit dem Ereignisblock “wenn ich … empfange” definiert:
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:
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:
Dazu fügen wir den Figuren “Alice” und “Carol” die folgenden Skripte zur Ereignisbehandlung hinzu:
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”).
In Fällen wie diesen lässt sich die Zwei-Wege-Kommunikation auch vereinfachen, indem die Anweisung “sende Nachricht und warte” verwendet wird:
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:
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:
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”.
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:
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):
zur Erinnerung: siehe Lektion 1 – Einstieg in Scratch, Abschnitt Parameter und Werte ↩︎
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. ↩︎
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
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:
Ä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 |
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
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.
Durch Anklicken der beiden Schaltflächen und soll die Richtung des Balls geändert werden, statt durch Drücken der Pfeiltasten.
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.
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.
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:
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). ↩︎
Das Bild wurde erstellt unter Verwendung von Hintergründen von Upklyak @ Freepik. ↩︎
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:
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.
Solche temporären Objektkopien werden in Scratch als “Klone” bezeichnet und können durch bestimmte Anweisungen verwaltet werden.
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:
Erzeugt einen Klon des angegebenen Objekts oder des Objekts selbst, welches das Skript ausführt. | |
Lö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 werden automatisch alle vorhandenen Klone gelöscht – also alle Objekt, die durch Anweisungen “Erzeuge Klon” entstanden sind.
Damit ein Klon bestimmte Aktionen durchführen kann, wenn er entsteht, bietet Scratch einen Ereignisblock in der Kategorie “Steuerung” (orange) an:
Das 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.
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:
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:
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:
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:
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.
Beim Klonen des Objekts wird das folgende Skript für den Klon ausführt:
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:
Das Scratch-Projekt zu diesem Beispiel können Sie hier herunterladen: Download
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.
Als Erstes sollen Funken automatisch erzeugt und animiert werden. Dabei dient das Objekt “Partikel” als Objektvorlage für die Partikelklone.
Sorgen Sie nun dafür, dass nicht alle Partikel gleich aussehen und sich gleich verhalten, sondern leichte zufallsbedingte Variationen aufweisen:
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.
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.
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:
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.
Blocktypen | ||
---|---|---|
“Stapelblockform” | Anweisung | |
“Klammerblockform” | Kontrollstruktur (z. B. eine Fallunterscheidung oder Wiederholung), siehe Steuerung | |
“Kopfblockform”, gewölbt | Ereignisbehandlung (Beginn eines Skripts, das beim Eintreten des Ereignisses ausgeführt wird), siehe Ereignisse | |
“Kopfblockform”, flach | Methodendefinition (Beginn eines Skripts, das durch einen speziellen Anweisungsblock ausgeführt wird), siehe Meine Blöcke | |
“Werteblockform” | Wert (eine Zahl oder Zeichenkette, z. B. der Wert eines Attributs, einer Variablen oder eines Berechnungsergebnisses) | |
“Wahrheitswerteblockform” | Wahrheitswert (wahr oder falsch, wird z. B. für Bedingungen in anderen Blöcken verwendet) |
Anweisungen | |
---|---|
Versetzt die Position des Objekts um die angegebene Distanz entlang seiner Richtung. | |
Ändert die Richtung des Objekts um den angegebenen Wert im oder gegen der Uhrzeigersinn (Winkel in Grad). | |
Setzt 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. | |
Setzt die Position des Objekts auf die angegebenen Koordinaten. | |
Bewegt das Objekt in der angegebenen Zeitspanne kontinuierlich zum ausgewählten Ziel (Zufallskoordinaten oder anderes Objekt).1 | |
Bewegt das Objekt in der angegebenen Zeitspanne kontinuierlich zu den angegebenen Koordinaten.1 | |
Setzt die Richtung auf den angegebenen Wert (Winkel in Grad). | |
Dreht das Objekt in Richtung des ausgewählten Ziels. | |
Addiert den angegebenen Wert zur x- oder y-Koordinate des Objekts. | |
Setzt die x- oder y-Koordinate des Objekts auf den angegebenen Wert. | |
Falls 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. | |
Legt 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 | |
---|---|
Gibt als Wert die momentane x- oder y-Koordinate des Objekts an. | |
Gibt als Wert die momentane Richtung des Objekts an (Winkel in Grad). |
Anweisungen | |
---|---|
Zeigt den angegebenen Text am Objekt in einer Sprech- oder Denkblase an. | |
Zeigt den angegebenen Text für die angegebene Zeitspanne an.1 | |
Legt die angegebene Grafik für das Objekts fest oder wechselt zur nächsten Grafik. | |
Legt 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. |
|
Addiert den angegebenen Wert zur Größe des Objekts (in Prozent). | |
Setzt die Größe des Objekts auf den angegebenen Wert (in Prozent). | |
Addiert 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 | |
Setzt den ausgewählten Grafikeffekt des Objekts auf den angegebenen Wert. | |
Setzt alle Grafikeffekte des Objekts auf den Normalzustand zurück. | |
Macht das Objekt unsichtbar oder macht es wieder sichtbar.3 | |
Setzt 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. | |
Schiebt das Objekt im Ebenenstapel um die angegebene Anzahl von Ebenen nach vorne oder hinten. |
Werte | |
---|---|
Gibt als Wert die Nummer oder den Namen der aktuellen Grafik des Objekts an (Wahl zwischen Nummer oder Name über das Symbol ▾). | |
Gibt als Wert die Nummer oder den Namen des aktuellen Hintergrundbilds des Bühne an. | |
Gibt als Wert die momentane Größe des Objekts an (in Prozent). |
Anweisungen | |
---|---|
Startet den angegebenen Sound des Objekt und spielt ihn im Hintergrund ab. | |
Startet den angegebenen Sound des Objekts und wartet, bis der Sound zuende abgespielt wurde.1 | |
Bricht alle momentan laufenden Sounds des Objekt ab. | |
Addiert den angegebenen Wert zum ausgewählten Wiedergabeeffekt des Objekts: Tonhöhe oder Stereo-Aussteuerung (links/rechts).4 | |
Setzt den ausgewählten Wiedergabeeffekt des Objekts auf den angegebenen Wert. | |
Setzt alle Wiedergabeeffekte des Objekts auf den Normalzustand zurück. | |
Addiert den angegebenen Wert zur Lautstärke für die Sound-Ausgabe des Objekts (in Prozent). | |
Setzt die Lautstärke für die Sound-Ausgabe des Objekts auf den angegebenen Wert (in Prozent). |
Werte | |
---|---|
Gibt als Wert die momentane Lautstärke für die Sound-Ausgabe des Objekts an (in Prozent). |
Ereignisse | |
---|---|
Das angehängte Skript wird beim Klicken auf die grüne Fahne ausgeführt. | |
Das angehängte Skript wird beim Drücken der ausgewählten Taste ausgeführt (Auswahl der Taste über das Symbol ▾). | |
Das angehängte Skript wird ausgeführt, sobald das Objekt mit der Maus angeklickt wird. | |
Das 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. | |
Das angehängte Skript wird ausgeführt, sobald die momentan über das Mikrofon gemessene Lautstärke den angegebenen Wert (0 bis 100) überschreitet. | |
Das 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. |
Ereignisse | |
---|---|
Das angehängte Skript wird ausgeführt, sobald das Objekt die ausgewählte Nachricht empfängt (Auswahl der Nachricht über das Symbol ▾).6 |
Anweisungen | |
---|---|
Verschickt die ausgewählte Nachricht an alle Objekte (auch an sich selbst). | |
Verschickt die ausgewählte Nachricht und wartet anschließend, bis alle Objekte, die auf diese Nachricht reagieren, das Ereignis verarbeitet haben.7 |
Kontrollstrukturen | |
---|---|
Führt die enthaltenen Blöcke wiederholt nacheinander aus, bis das Programm abgebrochen wird (endlose Wiederholung). | |
Führt die enthaltenen Blöcke n-mal nacheinander aus, wobei n die angegebene Zahl ist (Wiederholung mit fester Anzahl). | |
Führt die enthaltenen Blöcke wiederholt nacheinander aus, bis die angegebene Bedingung erfüllt ist (bedingte Wiederholung). | |
Führt die Blöcke in der Klammer nur dann aus, falls die angegebene Bedingung erfüllt ist (bedingte Anweisung). | |
Fü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 | |
---|---|
Pausiert das Skript für die angegebene Zeitspanne.1 | |
Pausiert das Skript, bis die angegebene Bedingung erfüllt ist. | |
Bricht entweder das Skript selbst, alle anderen momentan laufenden Skripte des Objekts oder alle Skripte ab (Auswahl über das Symbol ▾).8 |
Ereignisse | |
---|---|
Das 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 | |
---|---|
Erzeugt 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 | |
Löscht das Objekt, sofern es als Klon entstanden ist. Für ein originales Objekt hat die Anweisung keinen Effekt.10 |
Anweisungen | |
---|---|
Zeigt 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: | |
Legt fest, ob ein Objekt auch im Präsentationsmodus mit der Maus verschoben werden kann. Im Entwurfsmodus sind Objekte immer mit der Maus ziehbar. |
Werte | |
---|---|
Gibt den Wert an, der als Antwort auf die zuletzt gestellte Frage eingegeben wurde. | |
Gibt 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 | |
Gibt als Wahrheitswert an, ob das Objekt gerade einen Bildschirmpunkt mit der angegebenen Farbe berührt. | |
Gibt als Wahrheitswert an, ob ein Punkt des Objekts, der die erste angegebene Farbe hat, einen Bildschirmpunkt berührt, der die zweite angegebene Farbe hat. | |
Gibt als Wert den Abstand des Objekts zum Mauszeiger oder zu einem anderen Objekt an (Auswahl des Ziels über das Symbol ▾). | |
Gibt als Wahrheitswert an, ob momentan die ausgewählte Taste gedrückt ist (Auswahl der Taste über das Symbol ▾). | |
Gibt als Wahrheitswert an, ob momentan eine Maustaste gedrückt ist. | |
Gibt als Wert die x- oder y-Koordinate des Mauszeigers an. | |
|
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 |
Gibt als Wert die Lautstärke an, die momentan über das Mikrofon gemessen wird (0 bis 100). | |
Gibt als Wert Ihren Benutzernamen an (nur in der Online-Version von Scratch, sofern Sie mit Ihrem Account eingeloggt sind, ergibt sonst eine leere Zeichenkette). |
Anweisungen | |
---|---|
Setzt die globale Zeitmessung (“Stoppuhr”) auf 0 zurück.5 |
Werte | |
---|---|
Gibt als Wert den aktuellen Stand der Stoppuhr an (in Sekunden).5 | |
Gibt 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. | |
Gibt 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). |
Werte | |
---|---|
Gibt als Wert das Berechnungsergebnis für die beiden enthaltenen Werte an (Addition, Subtraktion, Multiplikation oder Division). | |
Gibt als Wert den Teilungsrest der ganzzahligen Division von a durch b an, wobei a und b die beiden enthaltenen Werte sind (Modulo-Operator). | |
Gibt als Wert den enthaltenen Wert gerundet auf die nächste Ganzzahl an. | |
Gibt 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. | |
Gibt 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. | |
Gibt als Wahrheitswert an, ob der Vergleich der beiden enthaltenen Werte stimmt oder nicht (größer als, kleiner als, gleich). |
Werte | |
---|---|
Gibt als Wahrheitswert an, ob beide enthaltenen Bedingungen erfüllt sind (logische Konjunktion). | |
Gibt als Wahrheitswert an, ob mindestens eine der beiden enthaltenen Bedingungen erfüllt ist (logische Disjunktion). | |
Gibt als Wahrheitswert an, ob die enthaltene Bedingung nicht erfüllt ist (logische Negation). |
Werte | |
---|---|
Gibt als Wert die Verknüpfung (Konkatenation) der beiden enthaltenen Zeichenketten-Werte an. | |
Gibt als Wert das n-te Zeichen der angegebenen Zeichenkette an, wobei n die im linken Feld angegebene Zahl ist. | |
Gibt als Wert die Länge der angegebenen Zeichenkette an (d. h. die Anzahl ihrer Zeichen). | |
Gibt 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”). |
Anweisungen | |
---|---|
Setzt die ausgewählte Variable auf den angegebenen Wert (Auswahl der Variablen über das Symbol ▾).12 | |
Addiert den angegebenen Wert zum aktuellen Wert der ausgewählte Variablen. | |
Zeigt den aktuellen Wert der ausgewählten Variablen live auf der Bühne an oder entfernt die Anzeige wieder. |
Werte | |
---|---|
Gibt den momentanen Wert dieser Variablen an. |
Anweisungen | |
---|---|
Hängt den angegebenen Wert als neues Element an die ausgewählte Liste an (Auswahl der Liste über das Symbol ▾).13 | |
Löscht das Element an der angegebenen Position aus der ausgewählten Liste. | |
Löscht alle Elemente aus der ausgewählten Liste. | |
Fü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. | |
Überschreibt das Element an der angegebenen Position durch den angegebenen Wert. | |
Zeigt den aktuellen Inhalt der ausgewählten Liste live auf der Bühne an oder entfernt die Anzeige wieder. |
Werte | |
---|---|
Gibt als Wert das Element an der angegebenen Position in der ausgewählten Liste an. | |
Gibt 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. | |
Gibt als Wert die Länge der ausgewählten Liste an (d. h. die Anzahl ihrer Elemente). | |
Gibt als Wahrheitswert an, ob die ausgewählte Liste den angegebenen Wert (mindestens einmal) enthält. | |
Gibt als Wert den momentanen Inhalt dieser Liste an (als Zeichenkette, in der die Werte aller Elemente als Zeichenketten aneinandergehängt sind). |
Kopfblock/Anweisung | |
---|---|
Beginn für das selbst definierte Skript “mein Block” des Objekts mit einem Parameter “Eingabe1” (Methodendefinition). | |
Führt das selbst definierte Skript “mein Block” des Objekts aus (Methodenaufruf). |
Anweisungen | |
---|---|
Entfernt alle bisher gezeichneten Zeichenspuren und “Abdrücke” vom Bühnenhintergrund. | |
Zeichnet die Grafik des Objekts an seiner aktuellen Position auf den Bühnenhintergrund. | |
Schaltet 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. | |
Setzt die Stiftfarbe zum Zeichnen auf die angegebene Farbe. | |
Setzt 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 | |
Addiert den angegebenen Wert zum aktuellen Wert für Farbton, Sättigung, Helligkeit oder Transparenz des Zeichenstifts.[] | |
Setzt die Stiftbreite zum Zeichnen auf den angegebenen Wert (Pixel). | |
Addiert den angegebenen Wert zur aktuellen Stiftbreite (Pixel). |
Anweisungen | |
---|---|
Spielt einen Ton des ausgewählten Schlaginstruments ab (Auswahl über das Symbol ▾). | |
Wartet die angegebene Anzahl von Schlägen. | |
Spielt 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. | |
Legt ein Instrument für die Musikwiedergabe fest (Auswahl über das Symbol ▾). | |
Legt das Tempo der Musikwiedergabe auf den angegebenen Wert fest (Schläge pro Sekunde). | |
Addiert den angegebenen Wert zum aktuellen Tempo der Musikwiedergabe. |
Werte | |
---|---|
Gibt als Wert das aktuelle Tempo der Musikwiedergabe an (Schläge pro Sekunde). |
Anweisungen | |
---|---|
Spricht den angegebenen Text und pausiert das Skript, bis die Sprachausgabe abgeschlossen ist.1 | |
Legt 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). | |
Legt die Wiedergabesprache fest (Auswahl über das Symbol ▾). |
Werte | |
---|---|
Gibt als Wert die Übersetzung des angegebenen Textes in die ausgewählte Zielsprache an (Auswahl über das Symbol ▾). Die Ausgangssprache wird automatisch erkannt. | |
Gibt als Wert die Sprache der Scratch-Oberfläche an (Auswahl über das Symbol im Menü). |
Ereignisse | |
---|---|
Das 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 | |
---|---|
Schaltet die Videoerfassung an (gespiegelt oder normal) oder aus (Auswahl über das Symbol ▾). | |
Legt 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 | |
---|---|
Gibt 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). |
Diese Anweisung hat eine festgelegte Dauer. Während dieser Dauer wird das Skript pausiert. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
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 ↩︎ ↩︎
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. ↩︎ ↩︎
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. ↩︎
Die “Stoppuhr” von Scratch misst die Sekunden, die seit dem letzten Programmstart mit vergangen sind (oder vor dem ersten Programmstart seit dem Starten der Scratch-Entwicklungsumgebung) bzw. seitdem die Stoppuhr zuletzt auf 0 zurückgesetzt wurde. ↩︎ ↩︎ ↩︎
Eine neue Nachricht lässt sich erstellen, indem auf das Symbol ▾ rechts im Block geklickt und “Neue Nachricht” gewählt wird. ↩︎
Das Skript wird hier also pausiert, bis alle Skripte, die mit einem passenden Ereignisblock “Wenn ich … empfange” beginnen, zuende ausgeführt wurden. ↩︎
Der Abbruch aller Skripte hat denselben Effekt wie das Beenden des Programms durch Klicken auf das Stop-Symbol . ↩︎
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. ↩︎
Beim Programmende werden automatisch alle Klone von Objekten gelöscht. ↩︎
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. ↩︎
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. ↩︎
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. ↩︎
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:
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.
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.
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.
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.
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.
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.
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
Die Bedeutung (Semantik) des Codes ist folgendermaßen:
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.
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).
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) ↩︎
siehe z. B. Luciano Floridi: Information: A Very Short Introduction, Oxford University Press, 2010 ↩︎
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. ↩︎
In anderen Ländern kann die letzte Zeichengruppe (die Betriebsnummer) auch Buchstaben enthalten und länger oder kürzer sein. ↩︎
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.
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.
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 n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
Zweiterpotenz 2n | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16 384 | 32 768 | 65 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:
Die Binärdarstellung mit 8 Stellen lautet hier also 01000111.
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).
Dezimal | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Hexadezimal | 0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
A |
B |
C |
D |
E |
F |
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är | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 |
Hexadezimal | 0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Binär | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
Hexadezimal | 8 |
9 |
A |
B |
C |
D |
E |
F |
Tool: Mit diesem Formular können Sie die Repräsentation von Ganzzahlen zwischen dem Dezimalsystem, Hexadezimalsystem und Binärsystem umrechnen.
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:
Symbol | Name | Wert | Beispiele für die Größenordnung |
---|---|---|---|
B | Byte | 1 B = 8 bit | Zeichen in einer Textdatei |
kB | Kilobyte | 1 kB = 1000 B = 103 B | Normseite (max. 1.800 Zeichen) |
MB | Megabyte | 1 MB = 1000 kB = 106 B | Bild- und Audiodateien, Videoclips |
GB | Gigabyte | 1 GB = 1000 MB = 109 B | Bild-/Audiosammlungen, unkomprimierte Videos |
TB | Terabyte | 1 TB = 1000 GB = 1012 B | Eine 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
Symbol | Name | Wert |
---|---|---|
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 |
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är | 00000000 | 00000001 | … | 00101010 | … | 01111111 | 10000000 | 10000001 | … | 10101010 | … | 11111111 |
Dezimal | 0 | 1 | … | 42 | … | 127 | 128 | 129 | … | 170 | … | 255 |
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 Zahl | Darstellbarer 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) |
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är | 00000000 | 00000001 | … | 00101010 | … | 01111111 | 10000000 | 10000001 | … | 10101010 | … | 11111111 |
Dezimal | 0 | 1 | … | 42 | … | 127 | -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är | 00000000 | 00000001 | … | 00101010 | … | 01111111 | 10000000 | 10000001 | … | 11010110 | … | 11111111 |
Dezimal | 0 | 1 | … | 42 | … | 127 | -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 Bit11010101
← Invertieren aller Bits11010110
← Hinzuaddieren von 1Tool: 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$$
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.7510101001
← Invertieren aller Bits10101010
← Hinzuaddieren von 1In 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.
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.
In dieser Aufgabe sollen die Binärdarstellung der genetischen Information und die entstehenden Datenmengen untersucht werden.
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.
In einer Binärdatei sind Informationen über die Bevölkerungsentwicklung von Kiel gespeichert. Der Dateiinhalt hat dabei das folgende Format:
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):
Jahr | Differenz | Prozentsatz | Datensatz in der Datei (6 Byte) |
---|---|---|---|
1991 | 1540 | 100.625 | 00000111 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).
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). ↩︎
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?
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.
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.
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… | NUL | SOH | STX | ETX | EOT | ENQ | ACK | BEL | BS | HT | LF | VT | FF | CR | SO | SI |
1… | DLE | DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN | EM | SUB | ESC | FS | GS | RS | US |
2… | SP | ! | " | # | $ | % | & | ' | ( | ) | * | + | , | - | . | / |
3… | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? |
4… | @ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O |
5… | P | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ |
6… | ` | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o |
7… | p | q | r | s | t | u | v | w | x | y | z | { | | | } | ~ | 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.
Zeichen | H | a | l | l | o | , | K | i | e | l | ! | |
Code (hex.) | 48 |
61 |
6C |
6C |
6F |
2C |
20 |
4B |
69 |
65 |
6C |
21 |
Code (dez.) | 72 | 97 | 108 | 108 | 111 | 44 | 32 | 75 | 105 | 101 | 108 | 33 |
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:
0A
(dez. 10) für einen Zeilenumbruch verwendet.0D
(dez. 13), so dass ein Zeilenumbruch hier immer durch zwei aufeinanderfolgende Bytes repräsentiert wird (hex. 0D 0A
).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.
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. A0
–FF
) definiert, die Codepoints 128–159 (hex. 80
–9F
) 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:
A0
(dezimal 160) stellt das geschützte Leerzeichen (engl. non-breaking space) dar.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.
Zeichen | G | r | ü | ß | e | a | u | s | M | ö | l | l | n | ! | ||
Code (hex.) | 47 |
72 |
FC |
DF |
65 |
20 |
61 |
75 |
73 |
20 |
4D |
F6 |
6C |
6C |
6E |
21 |
Code (dez.) | 71 | 114 | 252 | 223 | 101 | 32 | 97 | 117 | 115 | 32 | 77 | 246 | 108 | 108 | 110 |
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. 80
–9F
) 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.
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.
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
…
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:
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:
C4
(hex.) bzw. 196 (dez.), die Binärdarstellung dieser Zahl ist 1100 0100.C3 84
(hex.).1F600
(hex.) bzw. 128 512 (dez.), die Binärdarstellung dieser Zahl ist 1 1111 0110 0000 0000.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.
In einer Textdatei, die im Textbearbeitungsprogramm (fälschlicherweise) mit der Zeichencodierung ISO 8859-1 angezeigt wird, kommt mehrmals die Zeichenfolge ä vor.
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.
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).
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) | A4 |
A6 |
A8 |
B4 |
B8 |
BC |
BD |
BE |
---|---|---|---|---|---|---|---|---|
Zeichen in ISO 8859-1 | € | Š | š | Ž | ž | Œ | œ | Ÿ |
Zeichen in ISO 8859-15 | ¤ | ¦ | ¨ | ´ | ¸ | ¼ | ½ | ¾ |
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. ↩︎
siehe offizielle Website des Unicode-Konsortiums: https://home.unicode.org ↩︎
So verwenden 2021 über 97% aller Webseiten UTF-8 als Zeichencodierung, siehe https://w3techs.com/technologies/cross/character_encoding/ranking ↩︎
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.
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.
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.
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).
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.
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). | |
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. | |
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). |
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:
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.
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.
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.
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.
Darstellung der RGB-Kanäle |
Darstellung des Alpha-Kanals |
Anzeige des RGBA-Farbbild über einem gemusterten Hintergrund |
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.
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.
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:
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.
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:
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.
P2 |
Die “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 8 |
Es folgt die Breite und Höhe des Bildes in Pixeln, hier also 17 × 8 Pixel. |
100 |
Bei 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.
Das in diesem Beispiel codierte Bild lässt sich in der Textdarstellung eventuell erahnen und ist hier in 10-facher Vergrößerung dargestellt:
Portable Anymap bietet die folgenden Bildformate an, die durch die “Magic Number” im Header festgelegt werden:
Magic Number | Bildtyp | Formatname | Codierung der Bilddaten | Bits/Pixel (Binärformat) | Beispieldatei |
---|---|---|---|---|---|
P1 | Binärbild | Portable Bitmap (PBM) | ASCII-Textformat | Download | |
P2 | Graustufenbild | Portable Graymap (PGM) | ASCII-Textformat | Download | |
P3 | RGB-Farbbild | Portable Pixmap (PPM) | ASCII-Textformat | Download | |
P4 | Binärbild | Portable Bitmap (PBM) | Binärformat | 1 | Download |
P5 | Graustufenbild | Portable Graymap (PGM) | Binärformat | 8 oder 16 | Download |
P6 | RGB-Farbbild | Portable Pixmap (PPM) | Binärformat | 24 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:
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.
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.
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):
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):
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):
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ß.
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.
Name | Dateiendung | Beschreibung | |
---|---|---|---|
JPEG (kurz für Joint Photographic Experts Group) | .jpg, .jpeg | In 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. | |
PNG (kurz für Portable Network Graphics | .png | In 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. | |
GIF (kurz für Graphics Interchange Format) | .gif | GIF 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. | |
BMP (kurz für Windows Bitmap) | .bmp | BMP 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. | |
TIFF (kurz für Tagged Image File Format) | .tif | TIFF 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. |
Nehmen Sie an, das Portable Anymap-Dateiformat solle um ein neues (fiktives!) Bildformat mit der “Magic Number” P9 ergänzt werden, mit dem sich Bilder mit Farbpaletten im ASCII-Textformat speichern lassen. Die Farbpalette eines Bildes darf dabei beliebig viele Farben enthalten, deren Anzahl und Farbwerte in der Datei an geeigneter Stelle mit gespeichert werden müssen.
Entwerfen Sie ein entsprechendes neues Dateiformat und beschreiben Sie den Aufbau der Datei. Veranschaulichen Sie das von Ihnen entworfene Dateiformat, indem Sie für das folgende Beispielbild angeben, wie die Datei konkret aussehen würde:
Das Dateiformat soll prinzipiell nach demselben Schema wie PNM aufgebaut sein. Der Header sollte auch hier mit der “Magic Number” (hier: P9) und der Bildgröße beginnen. Was danach kommt, ist Ihrer Fantasie überlassen.
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. ↩︎
Diese Farbdarstellung wird als indizierte Farben bezeichnet. Ein Index ist die Nummer eines Eintrags in einer Tabelle (hier der Farbpalette). ↩︎
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. ↩︎
Die Bezeichnung “Portable Pixmap” wird zum Teil auch statt “Portable Anymap” als Stellvertreter für die Formatfamilie verwendet. ↩︎
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
...
↩︎
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.
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.
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
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).
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.
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.
Der Barcode ist folgendermaßen aufgebaut:
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.
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.
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:
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:
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.
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:
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.
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.
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):
Als Ausblick: Werden mehrere Paritätsbits für eine Bitsequenz verwendet, lässt sich die Fehlererkennung verbessern und Fehler ggf. sogar beheben.
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. ↩︎
Quelle: Shopblogger, https://www.shopblogger.de/blog/archives/23370-Lustige-Strichcodes-251.html ↩︎
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. ↩︎
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. ↩︎
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:
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).
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.
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.
Um zu beschreiben, wie stark Daten komprimiert werden, wird das quantitative Verhältnis zwischen komprimierten und unkomprimierten Daten verwendet.
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):
Qualitätsfaktor 90% | Qualitätsfaktor 75% | Qualitätsfaktor 50% | Qualitätsfaktor 25% | Qualitätsfaktor 10% |
Dateigröße 87 kB | Dateigröße 50.8 kB | Dateigröße 29.8 kB | Dateigröße 17.6 kB | Dateigröß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.
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:
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%.
Bei der Dekompression werden also die folgenden Schritte wiederholt, wobei hier die komprimierten Daten als Eingabe abgearbeitet werden:
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.
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:
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:
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.
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).
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).
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:
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.
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.
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.
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.
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:
Beispiel: Die Textnachricht “OSTSEESPROTTENPOTT” soll mittels Huffman-Codierung komprimiert werden.
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.
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.
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.
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.
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.
Nun werden die Wurzelknoten der beiden Bäume mit Werten 5 und 6 verbunden, der neue Wurzelknoten erhält den Wert 5 + 6 = 11.
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
.
Um das Codewort für ein Zeichen zu ermitteln, wird dann folgendermaßen vorgegangen:
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
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:
0
ist, setze den Zeiger auf den linken Nachfolger des aktuellen Knotens, bei 1
auf den rechten. 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.
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.
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).
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.
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.
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:
Beispiel: Der schrittweise Ablauf des Algorithmus soll anhand der Codierung der Textnachricht “ANANASBANANA” veranschaulicht und erläutert werden:
Das initiale Wörterbuch enthält vier Einträge für die Buchstaben “A”, “B”, “N” und “S” mit den Nummern 0 bis 3.
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.
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.
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.
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.
Nach diesem Schema wird fortgefahren, solange noch Zeichen aus der Eingabe abzuarbeiten sind.
Nach diesem Schema wird fortgefahren, solange noch Zeichen aus der Eingabe abzuarbeiten sind.
Nach diesem Schema wird fortgefahren, solange noch Zeichen aus der Eingabe abzuarbeiten sind.
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.
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
Nummer | Zeichenfolge | Codewort |
---|
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.
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:
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?
Dieses Problem lässt sich durch die folgenden Überlegungen lösen:
Generell wird in dieser Situation also immer die im letzten Schritt ausgegebene Zeichenfolge + deren erstes Zeichen zum Wörterbuch hinzugefügt und ausgegeben.
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).
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 | |
---|---|
Das 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. | |
Das PNG-Format verwendet unter anderem eine Kombination von Lempel-Ziv-Algorithmus und Huffman-Codierung (“Deflate”-Algorithmus). Die Codierung ist also verlustfrei. | |
Das GIF-Format verwendet dagegen nur den LZW-Algorithmus und erreicht dadurch eine geringere Kompression als PNG. | |
Windows Bitmap (BMP) verwendet nur Lauflängencodierung und erreicht dadurch nur eine schwache Kompression. | |
Das TIFF-Format unterstützt verschiedene Kompressionsverfahren, unter anderem LZW und Lauflängencodierung, aber auch verlustbehaftete Kompression. |
Dateiformate für Archive | |
---|---|
Das 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). | |
Das 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. |
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. ↩︎
Die Huffman-Codierung ist nach ihrem Entwickler David A. Huffman benannt, der das Verfahren 1952 publizierte. ↩︎
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. ↩︎
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. ↩︎
Das RAR-Dateiformat (“Roshal ARchive”) ist nach seinem Entwickler Jewgeni Lasarewitsch Roschal benannt. ↩︎
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.
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.
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.
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. ↩︎
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. ↩︎
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 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.
Als anschauliches Beispiel wird hier eine sehr einfach aufgebaute Website betrachtet,2 die Sie unter der folgenden URL im Browser öffnen können:
https://sekii.informatik-sh.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):
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):
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 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.
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:
Eine HTML-Datei hat immer den folgenden grundlegenden Aufbau:
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:
Dadurch hat jedes HTML-Dokument dasselbe Grundgerüst:
<html>
und endet mit dem Tag </html>
. Diese Tags legen den gesamten dazwischenliegenden Dateiinhalt als HTML-Dokument fest.<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).</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).
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.
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.
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:
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:
src
wird die Quell-URL der Bilddatei angegeben. Für dieses Attribut muss ein Wert angegeben werden, während die folgenden Attribute optional sind.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.alt
kann eine Bildbeschreibung als Alternativtext festgelegt werden, der statt des Bildes angezeigt wird, wenn das Bild nicht geladen werden kann.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.
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:
https://de.wikipedia.org/wiki/Topanga
(Zeile 12) stellt eine vollständige Webadresse dar, die auf eine Datei im Internet verweist.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.
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.
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
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.
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
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:
<s
eingeben, erscheint eine Liste, in der alle Tag-Namen, die mit “s” beginnen, ausgewählt werden können.<a
gefolgt von einem Leerzeichen eingeben, erscheint eine Liste, in der alle Attribute des Elements ausgewählt werden können.<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.
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.
Im Aufbau
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:
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:
<li>
) nur innerhalb von Listen (<ul>
, <ol>
) vorkommen dürfen,<p>
) keine gruppierenden Elemente wie Listen (<ul>
, <ol>
) vorkommen dürfen.Beispiele für grundlegende Vorgaben, wie Webbrowser mit HTML-Elementen umzugehen haben, sind:
<a>
) soll in einem neuen Fenster geöffnet werden, wenn das Attribut target
den Wert "_blank"
hat.<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:
<body>
) gehört, wird versehentlich im Kopf der HTML-Datei (<head>
) definiert.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:
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:
<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.<body>
-Tags innerhalb des Dokumentenrumpfes Elemente vorkommen, die noch nicht geschlossen wurden (nämlich <h1>
).
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.
Diese HTML-Elemente legen die Grundstruktur des HTML-Dokuments fest.
Element | Beschreibung |
---|---|
<html> </html> |
HTML-Dokument |
<head> </head> |
Dokumentenkopf mit Metadaten, z. B. Titel der Seite |
<body> </body> |
Dokumentenrumpf mit Seiteninhalt |
Diese HTML-Elemente dienen zur Textgliederung und Auszeichnung von Textteilen im Seiteninhalt.
Element | Beschreibung |
---|---|
<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 |
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.
Element | Beschreibung | Attribute | Beschreibung |
---|---|---|---|
<a href=" URL"> </a> |
Hyperlink | href=" 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"> |
Bild | src=" 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 im Seiteninhalt werden durch verschachtelte HTML-Elemente beschrieben.
Element | Beschreibung |
---|---|
<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 ``
Die folgenden HTML-Elemente legen Informationen über das Dokument fest und kommen nur im Dokumentenkopf vor.
Element | Beschreibung |
---|---|
<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 |
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 <
(kurz für less than = kleiner gleich) und >
(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 &
(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
(kurz für non-breaking space) oder das weiche Trennzeichen ­
(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. Ä
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-Entity | Sonderzeichen |
---|---|
Ä ä … ü |
Umlaute Ä ä … ü |
ß |
Eszett-Zeichen ß |
& |
Und-Zeichen & (engl. ampersand) |
< > |
Spitze Klammern < > (bzw. Vergleichszeichen, engl. less/greater than) |
" |
Anführungszeichen " (engl. quotation mark) |
° |
Grad-Zeichen ° (engl. degree) |
€ |
Euro-Zeichen € |
© |
Copyright-Zeichen © |
|
Geschütztes Leerzeichen (engl. non-breaking space) |
­ |
Weiches Trennzeichen (engl. soft hyphen) |
&# Nummer; |
Sonderzeichen mit der angegebenen Referenznummer (dezimal angegeben) |
&#x Nummer; |
Sonderzeichen mit der angegebenen Referenznummer (hexadezimal angegeben) |
Formale Sprachen, die dieses Grundkonzept verwenden und zu denen auch HTML gehört, heißen daher “Auszeichnungssprachen” (markup languages). ↩︎
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. ↩︎
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. ↩︎
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. ↩︎
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. ↩︎
Der Dateiname index.html wird üblicherweise für die Einstiegsseite einer Website verwendet, die aus mehreren HTML-Dateien besteht. ↩︎
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. ↩︎
siehe https://www.w3.org/TR/html5 und https://html.spec.whatwg.org ↩︎
vgl. <i> </i>
für kursiv dargestellten Text (engl. italic) ↩︎
vgl. <b> </b>
für fettgedruckten Text (engl. bold) ↩︎
vgl. <tt> </tt>
für Text mit Festbreitenschrift (Schreibmaschinen- oder Fernschreiberschrift, engl. teletype) ↩︎
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. ↩︎
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. ↩︎
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 ↩︎
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 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.css
und 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.
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
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:
font-family
, werden durch die Namen der Schriftarten durch Komma getrennt beschriebentext-align
als Textausrichtung left
, right
, center
oder justify
(Blocksatz) angegeben werdenEs 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;
}
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";
}
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 */
}
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;
}
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 {
/*...*/
}
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:
padding
, der Abstand zwischen dem Rahmen und dem Element selbstborder
, der Rahmenmargin
, der Abstand um den Rahmen herumFü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;
}
Der Abschnitt “Seiten-Layout” befindet sich noch im Aufbau.
Element | Beschreibung |
---|---|
<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) |
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.
In diese Kategorie fallen unter anderem Stilattribute bezüglich Farbe, Ausrichtung, Dekoration und Abständen.
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;
}
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:
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.
Ü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;
}
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.
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.
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.
Mit dem Attribut font-size
kann die Schriftgröße von Anzeigeelementen festgelegt werden. Als Größenangabe können folgende Werte eingesetzt werden:
xx-small
, x-small
, small
, medium
, large
, x-large
und xx-large
– die genaue Gestaltung bleibt dann dem Browser überlassenlarger
oder smaller
– also größer oder kleiner als der Text der umliegenden HTML-Elemente.mm
, pt
, in
(ch) oder px
. Bevorzugt sollten px
verwendet werden, da alle anderen Einheiten der Interpretation des einzelnen Browsers unterliegen%
, 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.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:
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.
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 behaltenall-small-caps
oder all-petite-caps
für Kapitälchen, wobei auch die Großbuchstaben verkleinert werdenunicase
, wobei nur die Großbuchstaben durch Kapitälchen ersetzt werden und die Kleinbuchstaben ihre Größe behalten2Attribut | Beschreibung | Werte |
---|---|---|
color |
Textfarbe | Farbwert |
background-color |
Hintergrundfarbe | Farbwert |
font-family |
Schriftart | Name(n) der Schriftart, z. B. Arial , Liberation Sans , sans-serif 3 |
font-size |
Schriftgröße | Größenangabe |
font-style |
Schriftstil | normal , italic (kursiv) |
font-weight |
Schriftdicke | normal , bold (fett), Zahlenwert zwischen 1 und 1000 (normal entspricht 400, fett 700) |
text-align |
Horizontale Textausrichtung | left , right , center , justify (Blocksatz) |
vertical-align |
Vertikale Textausrichtung | top , bottom , middle |
border-color |
Farbe des Rahmens | Farbwert |
border-style |
Linienstil des Rahmens | z. B. solid (einfache Linie), double (doppelte Linie), dotted (gepunktet), dashed (gestrichelt), none |
border-width |
Breite des Rahmens | Größenangabe |
margin |
Außenabstand zu allen Seiten | Größenangabe |
margin-top /-bottom /-left /-right |
Außenabstand oben, unten, links, rechts | Größenangabe |
padding |
Innenabstand zu allen Seiten | Größenangabe |
padding-top /-bottom /-left /-right |
Innenabstand oben, unten, links, rechts | Größenangabe |
width , height |
Breite, Höhe | Größenangabe |
list-style-type |
Aufzählungsstil oder -symbol für Listenelemente | circle (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 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):
red
, gray
, yellow
rgb(255, 0, 0)
, rgb(128, 128, 128)
, rgb(255, 255, 0)
#ff0000
, #808080
, #ffff00
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:
1px
, 50px
1cm
, 1000mm
50%
(halb so groß wie das Elternelement)0.5em
(halb so groß wie die Schriftgröße des Elternelements)<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.
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
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 ↩︎
Falls der Text wie auf dem Cover von Frank Schätzings “Der Schwarm” aussehen soll. ↩︎
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). ↩︎
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 ↩︎
siehe offizielle Homepage des W3C zu CSS: https://www.w3.org/Style/CSS ↩︎
Das Online-Skript zum Thema “Algorithmik” befindet sich zur Zeit noch im Aufbau.
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).
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.
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.
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.
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
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.
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:
Die sogenannten Kontrollstrukturen sind spezielle Anweisungen zur Ablaufsteuerung weiterer Grundbausteine. Dazu gehören im Wesentlichen:
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).
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.
Der erste Vorschlag, ein Lösungsverfahren zu beschreiben, besteht aus einem einzigen Satz:
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.
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:
0000
einDiese 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.
Der nächste Vorschlag beinhaltet zufallsbasierte Entscheidungen:
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.
Betrachten wir noch einen weiteren Ansatz zum Einstellen der richtigen Ziffern:
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.
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.
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.
Wir betrachten hier noch einmal den 2. Ansatz für das Ermitteln der richtige Zahlenkombination:
0000
einUm 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
0
und 9
.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.
Formulieren wir als Erstes einen Unteralgorithmus für die Anweisung “stelle Code 0000
ein”, d. h. setze alle Stellen auf 0
:
0
ist:
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:
0
:
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.
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.
Wir müssen also einen Algorithmus zur “Addition von 1 mit Übertrag” formulieren:
0
:
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:
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.
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:
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
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?
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
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
siehe auch Peer Stechert: Computational Thinking aus der Reihe Informatikdidaktik kurz gefasst (Teil 8), Video bei YouTube ↩︎
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. ↩︎
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. ↩︎ ↩︎
Die Grafiken für die Zahlenschlösser stammen von Vecteezy. ↩︎
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. ↩︎
Kontrollstrukturen und Variablenzuweisungen können aber wie üblich ohne Einschränkung verwendet werden, wenn nötig. ↩︎
Daneben können aber wie üblich allgemein bekannte mathematische und logische Werte und Operatoren verwendet werden. ↩︎
Auch diese Wiederholung lässt sich äquivalent mit “bis” statt “solange” formulieren: Wiederhole bis n-te Stelle ≠ 0
oder n = 1: … ↩︎
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. ↩︎
Ein solches Verfahren wird in der Informatik als vollständige Suche, erschöpfende Suche oder auch Brute-Force-Verfahren (sinngemäß etwa “Holzhammermethode”) bezeichnet. ↩︎
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.
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.
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 (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 | |
---|---|
Einzelne elementare Anweisungen werden jeweils durch einen einfachen Block dargestellt, der mit der möglichst kurz und präzise formulierten Anweisung beschriftet ist. | |
Anweisungen, 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 | |
---|---|
Blö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 | |
---|---|
Der 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. | |
Bei Endloswiederholungen wird in der Regel die Beschriftung weggelassen und der Rahmen -förmig dargestellt (alternativ kann auch ein -förmigen Rahmen mit “wiederhole endlos” beschriftet werden). |
Fallunterscheidung | |
---|---|
Der 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). | |
Bei 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:
Grundstruktur | Darstellung im Struktogramm | Darstellung in Scratch |
---|---|---|
Anweisungssequenz | ||
Bedingte Wiederholung | ||
Endloswiederholung | ||
Bedingte Anweisung (ohne Alternative) | ||
Fallunterscheidung (Bedingte Anweisung mit Alternative) |
Betrachten wir noch einmal den einfachen Algorithmus zum Ermitteln der richtigen Kombination eines Zahlenschlosses aus dem vorigen Kapitel:
0000
einIm Struktogramm wird der Algorithmus wie folgt dargestellt:
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.
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.
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 -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:
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.
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:
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.
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.
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).
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.
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.
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 (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.
Die folgende Tabelle stellt die grundlegenden Elemente dar, aus denen Programmablaufpläne bestehen:4
Start-/Endzustand | Anweisung | Unterprogrammaufruf | Übergang zum nächsten Element | Verzweigung |
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:
Mit diesen wenigen Elementen lassen sich alle bisher behandelten Kontrollstrukturen darstellen:
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:
Grundstruktur | Darstellung im PAP | Darstellung als Struktogramm | Darstellung in Scratch |
---|---|---|---|
Anweisungssequenz | |||
Bedingte Wiederholung | |||
Endloswiederholung | |||
Bedingte Anweisung (ohne Alternative) | |||
Fallunterscheidung (Bedingte Anweisung mit Alternative) |
Auch hier soll als erstes Beispiel der einfache Algorithmus zum Ermitteln der richtigen Zahlenschloss-Kombination als Programmablaufplan untersucht werden:
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.
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.
Vom Startknoten aus gehen wir entlang des Pfeils zur nächsten Anweisung und führen diese aus.
Von dieser Anweisung gehen wir wieder entlang des Pfeils zur Folgeanweisung und führen diese ebenfalls aus.
Wir erreichen eine Verzweigung: Nun wird die darin enthaltene Bedingung ausgewertet, die zu diesem Zeitpunkt nicht erfüllt ist.
Also verfolgen wir den mit “nein” beschrifteten Pfeil zur nächsten Anweisung, die ausgeführt wird.
Von dieser Anweisung gehen wir wieder entlang des Pfeils zur Folgeanweisung und führen diese ebenfalls aus.
Über den Pfeil erreichen wir nun wieder die Verzweigung zu Beginn des Wiederholung und überprüfen ihre Bedingung erneut.
Es vergehen nun einige Schritte, bis wir im 2446. Schritt die Kombination 0814
erreichen und überprüfen.
Da die Bedingung momentan nicht erfüllt ist wird entlang des “nein”-Pfeils gegangen und die nächste Anweisung ausgeführt.
Die Ausführung der folgenden Anweisung öffnet das Schloss, da 0815
hier die richtige Kombination ist.
Wir kehren zur Verzweigung zu Beginn des Wiederholung zurück und überprüfen ihre Bedingung, die nun erfüllt ist.
Also verfolgen wir den mit “ja” beschrifteten Pfeil und erreichen den Stopknoten. Der Algorithmus endet an dieser Stelle.
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.
Bedingte Anweisungen und Fallunterscheidungen werden in Programmablaufplänen durch Verzweigungen umgesetzt, die zu zwei parallelen Wegen führen, die später wieder zusammenlaufen:
Bei einer bedingten Anweisung ohne Alternative enthält der Weg, von dem der “nein”-Pfeil ausgeht, keine weiteren Anweisungen:
Bei mehrfachen Fallunterscheidungen enthält dieser Weg dagegen weitere Verzweigungen in parallele Wege, die später mit dem “Hauptweg” zusammengeführt werden:
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:
Bei einer Wiederholung mit Laufbedingung (statt Abbruchbedingung) verläuft der Kreis vom “ja”-Pfeil aus, während der “nein”-Pfeil aus dem Kreis ausbricht:
Bei einer Endloswiederholung gibt es dagegen einen Weg ohne Verzweigung, der im Kreis führt:
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.
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.
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.
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.
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.
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?”.
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?”.
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.
Den vollständigen Programmablaufplan finden Sie unter “Schritt 8”. Wie beim Entwurf des Struktogramms sind auch hier andere Abläufe möglich.
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.
Die Elemente von Nassi-Shneiderman-Diagrammen sind in Deutschland genormt in DIN 66261. ↩︎
Tatsächlich sind blockbasierte visuelle Programmiersprachen wie Scratch, Snap! oder Blockly unübersehbar von Struktogrammen zur grafischen Darstellung von Algorithmen als Vorbild inspiriert. ↩︎
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”. ↩︎
Die grafischen Elemente von Programmablaufplänen sind genormt in DIN 66011, gemeinsam mit den ähnlichen Elementen von Datenflussplänen. ↩︎
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. ↩︎
Setzen Sie das folgende Scratch-Skript in ein Struktogramm mit gleicher Bedeutung um:
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
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.
Der folgende Programmablaufplan beschreibt einen Algorithmus zur Steuerung einer Figur in einer 2D-Welt (jeder Schritt bewegt die Figur auf ein angrenzendes Feld).
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:
Anweisungen | Zustandsabfragen |
---|---|
fahre einen Schritt vor fahre einen Schritt zurück drehe dich zufällig | wird Wand berührt? Akkuladestand (in %) |
Formulieren Sie den Algorithmus, den Sie in der Aufgabe Gewichte vergleichen aus der vorigen Übung entwickelt haben, jeweils als Struktogramm und Programmablaufplan.
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?
In dieser Aufgabe sollen gegebene Programmablaufpläne auf die darin enthaltenden algorithmischen Grundstrukturen hin untersucht werden:
Programmablaufplan 1 | Programmablaufplan 2 | Programmablaufplan 3 |
---|---|---|
Übersicht über die Grundbausteine von Nassi-Shneiderman-Diagrammen (“Struktogramme”), genormt in DIN 66261 (siehe Wikipedia):
Algorithmische Grundstruktur | Darstellung im Struktogramm |
---|---|
Anweisung | |
Sequenz | |
Endloswiederholung | |
Wiederholung mit fester Anzahl | |
Bedingte Wiederholung1 (kopfgesteuert) | |
Bedingte Wiederholung1 (fußgesteuert) | |
Bedingte Anweisung | |
Bedingte Anweisung mit Alternative | |
Mehrfache Fallunterscheidung | |
Unterprogrammaufruf2 (ggf. mit Argumenten) |
Übersicht über die Grundbausteine von Programmablaufplänen (“PAP”, flowcharts), genormt in DIN 66001 (siehe Wikipedia):
Start-/Endzustand | Anweisung | Unterprogrammaufruf | Übergang | Verzweigung |
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 Grundstruktur | Darstellung im Programmablaufplan |
---|---|
Sequenz | |
Bedingte Anweisung | |
Bedingte Anweisung mit Alternative | |
Bedingte Wiederholung3 (“wiederhole bis”, kopfgesteuert) | |
Bedingte Wiederholung3 (“wiederhole bis”, fußgesteuert) | |
Endloswiederholung |
Die Abbruchbedingung “wiederhole bis” kann hier auch ersetzt werden durch eine Laufbedingung “wiederhole solange”. ↩︎ ↩︎
Dieser Baustein ist nicht in DIN 66261 genormt und kann alternativ auch als Anweisung dargestellt werden. ↩︎
Um eine Wiederholung mit Laufbedingung (“wiederhole solange”) statt Abbruchbedingung (“wiederhole bis”) umzusetzen, muss hier nur die Beschriftung der Kanten “ja” und “nein” getauscht werden. ↩︎ ↩︎
Das Online-Skript zum Thema “Netzwerke und Internet” befindet sich zur Zeit noch im Aufbau.
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?
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,
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.
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
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:
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:
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.
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).
Das folgende Sequenzdiagramm stellt den Nachrichtenaustausch für das erste Gespräch im Café noch einmal anschaulich dar:
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.
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:
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?!”:
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:
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:
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.
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:
Der Server befindet sich hier also immer in einem von zwei Zuständen:
Während einer Sitzung kann der Client die folgenden Anfragen beliebig oft und in beliebiger Reihenfolge verschicken:
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
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.
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.
Dieser Abschnitt befindet sich noch im Aufbau.
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.
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.
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.
Dieses und die folgenden Bilder wurden erstellt unter Verwendung von Grafiken von Freepik. ↩︎
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 ↩︎
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.
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
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.
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.
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
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?
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:
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
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.
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.
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”).
Fassen wir also abschließend auf einer abstrakteren Ebene die verschiedenen Komponenten und ihre Rollen in Netzwerken zusammen und grenzen sie voneinander ab:7
Ein Switch verbindet Geräte zu einem Netzwerksegment und erlaubt es ihnen, untereinander Daten auszutauschen. | |
Ein Router (auch: Vermittlungsrechner) verbindet verschiedene Netzwerke miteinander (z. B. verschiedene LAN untereinander oder ein LAN mit dem Internet). | |
Ein Modem stellt die Verbindung zwischen Routern über weite Übertragungswege her (z. B. per DSL, Glasfaser oder Kabelnetz). | |
Ein 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). | |
Ein 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.
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
.
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.
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.
Die folgenden IP-Adressbereiche sind für private Netzwerke reserviert:
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.
…
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.
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. ↩︎
Bekannte Modelle, die sich vielleicht auch in Ihrem Haushalt oder in Ihrer Schule wiederfinden, sind etwa die FRITZ!Box oder die Vodafone EasyBox. ↩︎
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). ↩︎
Quelle: Website von AVM, Produktseite FRITZ!Box 7530 ↩︎
Quelle: Website von D-Link, Produktseite D-LINK DGS-1100 ↩︎
Router werden daher auch als “Vermittlungssrechner” bezeichnet, weil sie zwischen verschiedenen Netzwerken vermitteln. ↩︎
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. ↩︎
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”).
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.
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.
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).
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.
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.
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).
Dieser Abschnitt befindet sich noch im Aufbau.
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.
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).
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.
In HTTP kann der Client u. a. folgende Anfragen stellen
Anfrage | Erläuterung |
---|---|
GET |
Dient 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 |
POST |
Dient 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. |
HEAD |
Dient 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. |
PUT |
Dient 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. |
PATCH |
Dient dazu, ein bestehendes Dokument zu ändern, ohne es wie bei einer PUT -Anfrage vollständig zu ersetzen. |
DELETE |
Dient 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.
Jede Antwort eines Webservers beginnt mit einem dreistelligen Statuscode. Die erste Ziffer des Codes gibt dabei Aufschluss über die Art der Antwort:
Erste Ziffer | Art 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
Statuscode | Name | Erläuterung |
---|---|---|
100 | Continue | Signalisiert dem Client, dass eine sehr lange Anfrage noch nicht abgewiesen wurde und der Client mit der Anfrage fortfahren darf. |
102 | Processing | Signalisiert 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. |
200 | OK | Die Anfrage war erfolgreich und das Ergebnis wird in der Antwort übertragen. |
204 | No Content | Die Anfrage war erfolgreich, die Antwort enthält aber bewusst keine Daten. |
301/308 | Moved Permanently/Permanent Redirect | Die 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/307 | Found/See Other/Temporary Redirect | Die angefragte Ressource ist temporär an eine neue URL verschoben worden. |
400 | Bad Request | Die Anfrage war fehlerhaft aufgebaut |
401 | Unauthorized | Der Client muss sich für diese Anfrage erst autorisieren. |
403 | Forbidden | Der Client hat keine Berechtigung, diese Ressource anzufragen |
404 | Not Found | Die angeforderte Ressource konnte nicht gefunden werden, etwa weil in der Adresse ein Tippfehler vorliegt oder ein Link auf eine inzwischen gelöschte Ressource verweist. |
408 | Request Timeout | Innerhalb des gegebenen Zeitfensters wurde vom Server keine vollständige Anfrage empfangen. |
418 | I’m A Teapot1 | Der Server ist eine Teekanne. |
4512 | Unavailable For Legal Reasons | Die gesuchte Ressource ist aus rechtlichen Gründen (z.B. Internetzensur) für den Client nicht verfügbar. |
500 | Internal Server Error | Dieser Statuscode wird allgemein bei Serverfehlern zurückgegeben. |
502 | Bad Gateway | Der Server hat zur Bearbeitung der Anfrage eine weitere Ressource angefragt, aber keine gültige Antwort erhalten. |
503 | Service Unavailable | Der 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.
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.
Zu jeder Anfrage können in der Übersicht der Anfragetyp, der Statuscode, Zielrechner, abgefragte Ressource u.v.m. abgelesen werden
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.
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.
Die Domainnamen sind rückwärts zu lesen und die dazugehörigen Informationen sind in einer Baumstruktur organisiert:
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.
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:
Typ | Information |
---|---|
A |
Gibt die IPv4-Adresse zu einer Domain an |
AAAA |
Gibt die IPv6-Adresse zu einer Domain an |
CNAME |
Gibt den eigentlichen Domainnamen an, der sich hinter einer Alias-Domain verbirgt. |
MX |
Gibt den Mailserver an, der für eine bestimmte Domain zuständig ist |
NS |
Gibt den Nameserver an, der für die Subdomains einer Domain zuständig ist |
SOA |
Gibt den Start of Authority an, d.h. die Stelle, die alle Informationen zu einer Domain ursprünglich verwaltet. |
SRV |
Gibt an, welche IP-basierten Dienste innerhalb einer Domain angeboten werden. |
TXT |
Kann 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.
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 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:
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:
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 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).
Zur Übertragung von DNS-Nachrichten über die Transportschicht werden sowohl TCP als auch UDP auf dem Standardport 53 genutzt.
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:
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.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.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.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.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.DHCPACK
bestätigt die Anfrage, der Client übernimmt die IP-Adresse.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.
Für den Versand und Abruf von E-Mails kommen drei Protokolle zum Einsatz:
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.
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.
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.
Flag | Information |
---|---|
\Seen |
E-Mail wurde gelesen |
\Answered |
E-Mail wurde beantwortet |
\Flagged |
E-Mail wurde als dringend markiert |
\Deleted |
E-Mail wurde zum Löschen vorgemerkt |
\Draft |
E-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:
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 authentifizierenPREAUTH
-Begrüßung ist in Situationen sinnvoll, wenn der Nutzer sich bereits anderweitig authentifiziert hat; dann kann dieser Schritt entfallenBYE
-“Begrüßung” von vornherein ablehnen.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:
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.
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) ↩︎
Der Statusode 451 ist eine Anspielung auf den dystopischen Roman “Fahrenheit 451” von Ray Bradbury. ↩︎
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.
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
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:
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.
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. ↩︎
Ein Python-“Paket” oder Modul beinhaltet Zusatzfunktionen für Python z. B. zur Bildverarbeitung oder zur Spieleentwicklung. ↩︎
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
.
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.
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.
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.
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:
Typ | Erklärung | Beispiele |
---|---|---|
int |
für ganze Zahlen (engl. integer numbers) | 0 , 42 , 3190701205 |
float |
für Dezimalzahlen (“Kommazahlen”, engl. floating-point numbers) | 0.5 , 42.0 , 3.141592 |
str |
für beliebige Zeichenketten (engl. strings) | 'Hello World' , '42' , 'https://iqsh.landsh.de' |
bool |
für Wahrheitswerte (engl. boolean)1 | True , False |
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:
Argumentwert | int(x) |
float(x) |
str(x) |
bool(x) |
---|---|---|---|---|
x = 2 |
2 |
2.0 |
'2' |
True |
x = 2.7818 |
2 |
2.7818 |
'2.7818' |
True |
x = 'zwei' |
Fehler | Fehler | 'zwei' |
True |
x = True |
1 |
1.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 ''
.
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.
Operator | Bezeichnung | Beschreibung | in Scratch |
---|---|---|---|
x + y |
Addition | ||
x - y |
Subtraktion | ||
x * y |
Multiplikation | ||
x / y |
Division | Liefert immer einen Wert vom Datentyp float als Ergebnis zurück, auch wenn das Ergebnis ganzzahlig ist. Beispiel: 4 / 2 → 2.0 |
|
x // y |
Ganzzahlige Division | Liefert immer einen Wert vom Datentyp int zurück, ggf. wird das Ergebnis also konvertiert. Beispiel: x // y ergibt dasselbe wie int(x / y) |
|
x % y |
Modulo | Berechnet den Rest, der bei der ganzzahligen Division von x durch y übrigbleibt. Beispiel: 10 % 3 → 1 |
|
x ** y |
Potenz | Berechnet xy (“x hoch y”), also die y-fache Multiplikation von x. | siehe unten3 |
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
).
Operator | Bezeichnung | Hinweis | in Scratch |
---|---|---|---|
x == y |
Gleichheit | Achtung: In Python wird ein doppeltes Gleichheitszeichen zur Überprüfung der Gleichheit verwendet. Ein einfaches Gleichheitszeichen beschreibt dagegen eine Variablenzuweisung! | |
x != y |
Ungleichheit | ||
x < y |
Echt kleiner | ||
x <= y |
Kleiner oder gleich | ||
x > y |
Echt größer | ||
x >= y |
Größer oder gleich |
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.
Operator | Beschreibung | in Scratch |
---|---|---|
not a |
Logisches NICHT (“Negation”): not a ist genau dann True , wenn a zu False ausgewertet wird. |
|
a and b |
Logisches UND: a and b ist genau dann True , wenn sowohl a als auch b zu True ausgewertet werden. |
|
a or b |
Logisches ODER: a and b ist genau dann True , wenn a , b oder beide zu True ausgewertet werden. |
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.
Operation | Bedeutung | in Scratch |
---|---|---|
s + t |
Aneinanderhängen (“Konkatenation”) Beispiel: 'Flens' + 'burg' → 'Flensburg' |
|
n * s |
Vervielfältigung Beispiel: 3 * 'Ho' → 'HoHoHo' |
|
s in t |
Ist der Teilstring t in s enthalten? Beispiel: 'sum' in 'Husum' → True |
|
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! |
|
len(s) |
Länge des Strings s Beispiel: len('Kiel') → 4 |
Logische Wahrheitswerte werden nach George Boole, einem Pionier der mathematischen Logik, auch als “boolesche Werte” bezeichnet (im Englischen “boolean”). ↩︎
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). ↩︎
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: ↩︎
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.
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('-------------------------------------