Aufgabenblock — AFB III
Hier geht es nicht ums Ausrechnen, sondern ums Begründen, Beurteilen und Entwickeln: Aussagen über Sprachen prüfen, eigene SQL-Abfragen und Programmkorrekturen entwerfen, Rust gegen Go abwägen und eine Webseite sinnvoll auf Browser und Server aufteilen. Fahre mit der Maus über einen unterstrichenen Operator, um zu sehen, was er verlangt. Schreib deine Antwort erst selbst in ganzen Sätzen auf, bevor du die Musterlösung aufklappst.
Jonas sagt: „Python ist eine schlechte Sprache. C ist 25-mal schneller, also sollte man immer C nehmen.“ Beurteile diese Aussage. Gehe dabei auf mindestens drei Kriterien der Sprachwahl ein und nenne je ein Beispiel, in dem Python bzw. C die bessere Wahl ist.
Hinweis: Überlege, ob die Laufzeit in jedem Projekt das wichtigste Kriterium ist.
Musterlösung anzeigen (zählt als erledigt)
Musterlösung: Die Aussage ist in dieser Allgemeinheit falsch. Richtig ist, dass ein Python-Programm im Modell etwa 25-mal so lange braucht wie dasselbe Programm in C. Jonas beurteilt die Sprache aber nur nach einem einzigen Kriterium, der Laufzeit.
Weitere Kriterien sind die Entwicklungszeit (ein Python-Programm ist oft viel schneller geschrieben und getestet), die Bibliotheken und die Community (für Datenanalyse und KI gibt es in Python fertige Bausteine) und das Vorwissen des Teams. Für eine Auswertung, die einmal pro Woche läuft, spielen 10 statt 0,4 Sekunden keine Rolle — hier ist Python die bessere Wahl.
C ist dagegen sinnvoll, wenn Speicher und Rechenzeit knapp sind, etwa bei einem Mikrocontroller, oder wenn ein Programm sehr oft und sehr schnell laufen muss. Fazit: Es gibt keine „beste“ Sprache, sondern nur eine, die zu Plattform, Anforderungen und Team passt.
Eine Firma liefert ein Programm für Windows- und Linux-Computer aus. Vergleiche, was die Firma ausliefern muss, wenn das Programm in C geschrieben ist und wenn es in Java geschrieben ist. Begründe den Unterschied mit dem Weg vom Quelltext zur Ausführung.
Hinweis: Frage dich, wofür der Übersetzer jeweils übersetzt: für einen bestimmten Prozessor oder für eine gedachte Maschine?
Musterlösung anzeigen (zählt als erledigt)
Musterlösung: Ein C-Compiler übersetzt den Quelltext vor dem Start vollständig in Maschinencode. Dieser Maschinencode passt nur zu einem Prozessortyp und einem Betriebssystem. Die Firma muss den Quelltext deshalb für Windows und für Linux getrennt kompilieren und zwei verschiedene Programmdateien ausliefern.
Der Java-Compiler javac übersetzt den Quelltext dagegen in Bytecode — Maschinencode für eine gedachte Maschine. Diesen Bytecode führt die Java Virtual Machine (JVM) aus, die es für Windows und Linux gibt; ein JIT-Compiler übersetzt dabei häufig benutzte Teile während des Laufs in echten Maschinencode. Die Firma liefert deshalb nur eine Bytecode-Datei aus, auf den Computern muss aber eine JVM installiert sein.
Vergleich: C ist dafür meist etwas schneller und braucht keine Laufzeitumgebung; Java spart den Aufwand, für jede Plattform neu zu übersetzen. C# arbeitet mit CIL und .NET nach demselben Prinzip wie Java.
Mia behauptet: „In Python gibt es keine Typfehler, weil man keine Typen hinschreiben muss.“ Widerlege die Behauptung mit einem Beispiel. Erläutere anschließend, warum das folgende Programm wochenlang fehlerfrei laufen kann, bevor es abstürzt, und was ein Java-Compiler in der gleichen Situation getan hätte.
tag = input("Wochentag? ") if tag == "Sonntag": rabatt = "10" + 5 print("Sonntagsrabatt:", rabatt) else: print("Kein Rabatt")
Hinweis: Unterscheide, wann ein Typ geprüft wird: vor dem Lauf oder während des Laufs.
"10" + 5 → TypeError · dynamisch = Typ gehört zum Wert, Prüfung zur Laufzeit · Zeile 3 wird nur sonntags erreicht ⇒ Absturz erst am ersten Sonntag · Java: statisch, der Compiler prüft alle Zeilen vorher ⇒ Fehler vor dem Start, auch in Zweigen, die selten laufen.Musterlösung anzeigen (zählt als erledigt)
Musterlösung: Die Behauptung ist falsch. Gegenbeispiel: "10" + 5 führt in Python zu einem TypeError, weil Python einen Text und eine Zahl nicht mit + verknüpft. Python ist dynamisch typisiert: Man schreibt keine Typen hin, aber jeder Wert hat einen Typ, und der wird während des Laufs geprüft.
Im Programm steht der Fehler in Zeile 3. Der Interpreter prüft diese Zeile aber erst, wenn er sie erreicht — und das passiert nur, wenn jemand „Sonntag“ eingibt. An allen anderen Tagen läuft das Programm fehlerfrei durch den else-Zweig. Der Absturz kommt erst am ersten Sonntag.
Java ist statisch typisiert: Der Compiler prüft vor dem Start alle Zeilen, auch die in selten genutzten Zweigen. Ein Ausdruck, der einen Typfehler enthält — etwa die Zuweisung eines Textes an eine int-Variable —, wird schon beim Kompilieren gemeldet; das Programm startet gar nicht erst. (Achtung: "10" + 5 selbst ist in Java kein Fehler, sondern ergibt den Text „105“.)
Die Schulbibliothek speichert ihre Bücher in der Tabelle buecher mit den Spalten titel, autor, jahr, seiten und genre.
Entwickle SQL-Abfragen für diese Wünsche: (1) Titel und Seitenzahl aller Fantasy-Bücher mit mehr als 400 Seiten, das dickste zuerst. (2) Die Anzahl der Bücher, die vor 1980 erschienen sind oder zum Genre Krimi gehören. (3) Die durchschnittliche Seitenzahl der Bücher von Michael Ende. Erkläre anschließend an Abfrage (1), warum SQL eine deklarative Sprache ist.
Hinweis: Baue jede Abfrage nach dem Gerüst SELECT … FROM … WHERE … ORDER BY … auf.
Musterlösung anzeigen (zählt als erledigt)
-- (1) SELECT titel, seiten FROM buecher WHERE genre = 'Fantasy' AND seiten > 400 ORDER BY seiten DESC; -- (2) SELECT COUNT(*) FROM buecher WHERE jahr < 1980 OR genre = 'Krimi'; -- (3) SELECT AVG(seiten) FROM buecher WHERE autor = 'Ende';
Musterlösung: In (1) müssen beide Bedingungen gelten, deshalb AND; „das dickste zuerst“ bedeutet absteigend, also DESC. In (2) reicht eine der beiden Bedingungen, deshalb OR. Texte wie 'Fantasy' stehen in einfachen Anführungszeichen. (Falls die Tabelle den vollen Namen speichert, lautet die Bedingung in (3) entsprechend autor = 'Michael Ende'.)
SQL ist deklarativ: Abfrage (1) beschreibt nur, was im Ergebnis stehen soll — welche Spalten, welche Zeilen, welche Reihenfolge. Wie die Datenbank die Zeilen durchsucht und sortiert, legt sie selbst fest. In Python müsstest du dafür selbst eine Schleife über alle Bücher schreiben, jede Zeile prüfen, passende in eine Liste übernehmen und diese Liste sortieren — das wäre imperativ.
Die SV plant eine Webseite, auf der man das Mensa-Essen für die nächste Woche vorbestellen kann. Die Seite soll im Browser sofort warnen, wenn kein Gericht ausgewählt ist; die Bestellungen sollen in einer Datenbank gespeichert werden. Entwirf eine Aufteilung auf die Sprachen HTML/CSS, JavaScript, PHP und SQL und begründe, welcher Teil im Browser und welcher auf dem Server läuft. Beurteile außerdem den Vorschlag, die Prüfung „Ist das Guthaben ausreichend?“ nur mit JavaScript im Browser zu machen.
Hinweis: Überlege, welcher Teil des Codes beim Nutzer landet und damit von ihm gesehen und verändert werden kann.
Musterlösung anzeigen (zählt als erledigt)
Musterlösung: HTML und CSS beschreiben Aufbau und Aussehen des Bestellformulars; der Browser stellt sie dar. JavaScript läuft im Browser (clientseitig) und kann sofort, ohne neue Anfrage an den Server, eine Warnung anzeigen, wenn kein Gericht gewählt ist. Beim Abschicken gehen die Formulardaten an den Server. Dort liest ein PHP-Skript sie über $_POST aus, prüft sie, speichert die Bestellung und erzeugt die HTML-Antwortseite (serverseitig). Zum Speichern und Abfragen der Bestellungen schickt PHP SQL-Befehle an die Datenbank, etwa SELECT COUNT(*) FROM bestellungen WHERE tag = 'Montag' für die Küche.
Die Guthabenprüfung nur mit JavaScript ist abzulehnen. Der JavaScript-Code wird an den Browser geschickt; der Nutzer kann ihn lesen, abschalten oder verändern und so trotzdem bestellen. Prüfungen, auf die man sich verlassen muss, gehören auf den Server, dessen PHP-Code der Nutzer nie zu sehen bekommt. Die JavaScript-Prüfung ist nur als bequeme zusätzliche Warnung sinnvoll.
Eine Stadt lässt die Software für ihre Ampelsteuerung neu schreiben. Bisher war sie in C geschrieben, und es gab Abstürze, weil auf Speicher zugegriffen wurde, der schon freigegeben war. Diskutiere, ob Rust oder Go die bessere Wahl für die neue Fassung ist. Gehe auf Geschwindigkeit, Speicherverwaltung und den Zeitpunkt ein, zu dem Fehler entdeckt werden.
Hinweis: Vergleiche, wer bei C, Go und Rust dafür sorgt, dass nicht mehr benutzter Speicher freigegeben wird.
Musterlösung anzeigen (zählt als erledigt)
Musterlösung: In C verwaltet der Programmierer den Speicher selbst. Vergisst er, dass ein Speicherbereich schon freigegeben wurde, fällt das erst beim Lauf auf — genau das waren die Abstürze.
Go ist kompiliert, hat wenige Sprachelemente und einen Garbage Collector, der nicht mehr benutzten Speicher automatisch freigibt. Das verhindert die bisherigen Fehler. Der Garbage Collector arbeitet aber während des Laufs und kann kurze, nicht genau planbare Pausen verursachen.
Rust ist ungefähr so schnell wie C und kommt ohne Garbage Collector aus. Stattdessen hat jeder Wert genau einen Besitzer (Ownership); der Borrow Checker prüft schon beim Kompilieren, dass kein Wert nach dem Verschieben oder Freigeben noch benutzt wird. Ein Programm mit dem alten Fehler würde also gar nicht erst übersetzt.
Für eine Ampel, die zuverlässig und ohne Aussetzer laufen muss, spricht deshalb mehr für Rust. Dagegen spricht der höhere Lernaufwand, weil die Regeln des Borrow Checkers neu sind. Insgesamt überwiegen hier Sicherheit und Geschwindigkeit: Rust ist die bessere Wahl.
Beide Programme bestimmen, wie viele Wörter einer Liste mehr als 4 Buchstaben haben:
woerter = ["Baum", "Schule", "Ei", "Informatik", "Hund"] anzahl = 0 for w in woerter: if len(w) > 4: anzahl = anzahl + 1 print(anzahl)
woerter = ["Baum", "Schule", "Ei", "Informatik", "Hund"] lang = list(filter(lambda w: len(w) > 4, woerter)) print(len(lang))
Vergleiche die beiden Varianten: Welches Paradigma steckt jeweils dahinter? Was ist bei Variante A der Zustand, der sich ändert? Beurteile, welche Variante leichter zu prüfen ist, und nenne einen Vorteil der anderen.
Hinweis: Achte auf Variablen, deren Wert sich während des Laufs mehrmals ändert.
Musterlösung anzeigen (zählt als erledigt)
Musterlösung: Beide Varianten geben 2 aus („Schule“ und „Informatik“). Variante A ist imperativ: Sie beschreibt Schritt für Schritt, wie gezählt wird. Der Zustand ist die Variable anzahl, die bei jedem passenden Wort um 1 erhöht wird; zusätzlich ändert sich w in jedem Durchlauf.
Variante B ist funktional: filter wendet die Funktion len(w) > 4 auf jedes Wort an und behält die passenden. Die Funktion hat keine Seiteneffekte, und es gibt keine Variable, die hochgezählt wird. Man beschreibt eher, was man haben will.
Variante B ist leichter zu prüfen: Sie ist kürzer, und typische Fehler wie ein vergessener Startwert anzahl = 0 oder ein Zähler an der falschen Stelle können nicht auftreten. Vorteil von Variante A: Jeder Schritt ist sichtbar, sie ist für Einsteiger leichter nachzuvollziehen und lässt sich leicht erweitern, etwa um eine Ausgabe jedes langen Wortes. Dass beide Stile in Python funktionieren, zeigt: Python ist eine Multiparadigmen-Sprache.
Emre programmiert in Unity einen Münzsammler. Die Münzen werden eingesammelt, aber die Anzeige bleibt immer bei 0 Punkten, und die Spielfigur ist auf dem schnellen PC seines Freundes viel schneller als auf seinem Laptop.
public class Spieler : MonoBehaviour { int punkte; void Update() { punkte = 0; if (Input.GetKey(KeyCode.RightArrow)) { transform.Translate(0.1f, 0, 0); } // beim Einsammeln: punkte = punkte + 1; } }
Überprüfe das Skript: Erkläre beide Fehler mit der Arbeitsweise von Start(), Update() und Time.deltaTime und verändere das Skript so, dass beide Fehler behoben sind.
Hinweis: Überlege, wie oft pro Sekunde jede Zeile in Update() ausgeführt wird.
punkte. Rechne dann den Weg pro Sekunde bei 30 und bei 144 Bildern pro Sekunde aus.Warum so? Ein Fehler, der von der Bildrate abhängt, lässt sich am besten mit zwei konkreten Bildraten zeigen.punkte = 0 steht in Update ⇒ wird jedes Frame zurückgesetzt ⇒ gehört in Start (läuft einmal) · Translate(0.1f, …) pro Frame ⇒ 30 fps: 3 Einheiten/s, 144 fps: 14,4 Einheiten/s ⇒ mit tempo * Time.deltaTime Weg pro Sekunde fest.Musterlösung anzeigen (zählt als erledigt)
Musterlösung: Fehler 1: Update() wird einmal pro Frame aufgerufen, also viele Male pro Sekunde. Weil punkte = 0; in Update() steht, wird der Punktestand in jedem Frame wieder auf 0 gesetzt — eingesammelte Punkte gehen sofort verloren. Das Zurücksetzen gehört in Start(), die nur einmal beim Start des Objekts läuft.
Fehler 2: transform.Translate(0.1f, 0, 0) bewegt die Figur um 0,1 Einheiten pro Frame. Bei 30 fps sind das 3 Einheiten pro Sekunde, bei 144 fps 14,4 Einheiten pro Sekunde — auf dem schnellen PC ist die Figur fast fünfmal so schnell. Multipliziert man mit Time.deltaTime (Zeit seit dem letzten Frame in Sekunden), wird daraus ein Weg pro Sekunde, unabhängig von der Bildrate.
public class Spieler : MonoBehaviour { int punkte; float tempo = 3f; // Einheiten pro Sekunde void Start() { punkte = 0; } void Update() { if (Input.GetKey(KeyCode.RightArrow)) { transform.Translate(tempo * Time.deltaTime, 0, 0); } } }
Ein Mitschüler meint: „Assembler ist doch viel schneller als jede Hochsprache. Wer Assembler kann, braucht keine Hochsprache.“ Nimm Stellung zu dieser Aussage. Erläutere dabei, warum Hochsprachen wie FORTRAN und COBOL überhaupt entwickelt wurden, und nenne einen Fall, in dem Assembler heute noch sinnvoll ist.
Hinweis: Denke an ein Programm, das auf einem anderen Prozessortyp laufen soll, und an eine Rechnung wie a + b · c.
Musterlösung anzeigen (zählt als erledigt)
Musterlösung: Teilweise hat der Mitschüler recht: In Assembler entspricht jeder Befehl genau einem Maschinenbefehl. Wer den Prozessor sehr genau kennt, kann damit besonders kleine und schnelle Programme schreiben.
Assembler hat aber große Nachteile. Schon eine einfache Rechnung wie a + b · c braucht mehrere Befehle wie MOV, MUL und ADD; Programme werden lang, schwer lesbar und fehleranfällig. Außerdem ist Assembler prozessorabhängig: Für einen anderen Prozessortyp muss man das Programm neu schreiben. Genau deshalb entstanden Hochsprachen: FORTRAN (1957), damit Wissenschaftler Formeln fast so schreiben können wie auf Papier, und COBOL (1959) für Geschäftsdaten. Ein Übersetzer erzeugt aus demselben Quelltext Maschinencode für verschiedene Prozessoren.
Heutige Compiler optimieren so gut, dass Hochsprachen wie C oder Rust meist genauso schnell sind. Assembler ist heute nur noch für kleine, sehr zeitkritische oder hardwarenahe Teile sinnvoll, zum Beispiel für den Startcode eines Mikrocontrollers. Die Aussage ist deshalb abzulehnen.
Ein Python-Programm berechnet den Durchschnitt zweier ganzer Zahlen:
a = 7 b = 8 print((a + b) / 2)
Es soll nach JavaScript, PHP, Java und Go übertragen werden. Ermittle für eine wörtliche Übertragung (ganze Zahlen, / 2) die Ausgabe in jeder Sprache, begründe die Unterschiede und entwickle eine Vorgehensweise, mit der du beim Übertragen in eine neue Sprache solche Fehler findest.
Hinweis: Die Konzepte (Variable, Rechnung, Ausgabe) sind überall gleich — entscheidend ist, was / mit zwei ganzen Zahlen macht.
int. Überlege dann, mit welchen Testwerten du den Unterschied sofort bemerkst.Warum so? Beim Lernen einer neuen Sprache bleiben die Konzepte gleich, die Feinheiten ändern sich — Tests mit bekanntem Ergebnis decken sie auf.(a + b) / 2.0, Go float64(a+b) / 2 · Vorgehen: Vergleichstabelle, Doku lesen, Hallo-Welt, Testfälle mit bekanntem Ergebnis (gerade und ungerade Summe), Fehlermeldungen lesen.Musterlösung anzeigen (zählt als erledigt)
Musterlösung: Python gibt 7.5 aus. JavaScript (console.log((a + b) / 2)) und PHP (echo ($a + $b) / 2;) liefern ebenfalls 7.5: In beiden Sprachen liefert / ein Ergebnis mit Nachkommastellen, wenn die Division nicht aufgeht. In Java (int a = 7, b = 8;) und Go (a := 7, b := 8) sind a und b ganze Zahlen vom Typ int. Dort ist (a + b) / 2 eine Ganzzahldivision, die Ausgabe ist 7.
Korrekturen: In Java (a + b) / 2.0, in Go float64(a+b) / 2 — Go verlangt die Umwandlung ausdrücklich.
Vorgehensweise beim Übertragen: (1) Eine Vergleichstabelle der Grundbefehle anlegen (Variable, Ausgabe, Division …). (2) Die Besonderheiten in der offiziellen Dokumentation nachlesen, z. B. go.dev/tour oder learn.microsoft.com. (3) Das übertragene Programm mit Testfällen prüfen, deren Ergebnis man kennt — hier mit einer ungeraden Summe wie 7 und 8, denn bei 6 und 8 fiele der Fehler nicht auf. (4) Fehlermeldungen genau lesen: Zeile und Art des Fehlers. Das gilt auch für Code von einem KI-Assistenten: Nur verwenden, was man versteht und getestet hat.
