Programmiersprachen und Programmierparadigmen

Verfolge ein Programm von dem Code, den ein Mensch schreibt, bis zu den Binärbefehlen, die ein Prozessor ausführt, und erfahre, wie Compiler, Interpreter, Bytecode und Just-in-time-Compiler diese Übersetzung ermöglichen. Lerne, die Syntax einer Sprache von ihrer Bedeutung zu unterscheiden, und erfahre, wie Sprachen Datentypen prüfen und wo ein laufendes Programm seine Daten speichert und seinen Speicher freigibt. Vergleiche anschließend vier Arten, Programme zu schreiben (imperativ, objektorientiert, funktional und deklarativ), indem du dieselben Probleme jeweils auf diese Weise löst. Lerne außerdem, eine zu einem Projekt passende Sprache auszuwählen.

AnfängerEinheit 5 von 7 in Kurs: Informatik – Computer, Algorithmen, Internet und KI verstehen

Was du lernst

  1. Verfolge ein Programm von dem Code, den ein Mensch schreibt, bis zu den Binärbefehlen, die ein Prozessor ausführt. Vergleiche die Möglichkeiten der Übersetzung: im Voraus, während das Programm läuft oder als Mischung aus beidem.
    • Vergleiche Maschinencode, Assemblersprache und höhere Programmiersprachen
      Beschreibe die drei Ebenen, auf denen Programme geschrieben werden können, zeige, wie eine Zeile einer höheren Programmiersprache mehreren Maschinenbefehlen entspricht, und erkläre, warum die meiste Software auf der höchsten Ebene geschrieben wird.
    • Erkläre die Aufgaben eines Compilers
      Beschreibe, wie ein Compiler ein ganzes Programm im Voraus übersetzt, benenne seine wichtigsten Phasen und erkläre, was mit Fehlern und der fertigen ausführbaren Datei geschieht.
    • Erkläre Interpreter, Bytecode und Just-in-time-Kompilierung
      Beschreibe, wie ein Interpreter ein Programm ausführt, wie Sprachen wie Java und Python Kompilierung und Interpretation verbinden und wie du die Vor- und Nachteile dieser Ansätze abwägst.
  2. Trenne die Regeln für die Schreibweise von Code von den Regeln für seine Wirkung, erkenne die Grundbausteine, die die meisten höheren Programmiersprachen gemeinsam haben, und vollziehe kurze Programme nach, um ihre Ergebnisse vorherzusagen.
    • Unterscheide Syntax und Semantik
      Erkläre den Unterschied zwischen der Form eines Programms und seiner Bedeutung, unterscheide Syntax-, Laufzeit- und logische Fehler und erkenne, wo Sprachen gleich aussehendem Code eine andere Bedeutung geben.
    • Vollziehe die gemeinsamen Grundbausteine von Programmen in höheren Programmiersprachen nach
      Erkenne die Konstrukte, die in fast jeder höheren Programmiersprache vorkommen (Variablen, Ausdrücke, Kontrollfluss und Funktionen mit ihrem Gültigkeitsbereich), und vollziehe ein kurzes Programm Schritt für Schritt nach, um seine Ausgabe vorherzusagen.
  3. Beschreibe, wie Sprachen Werte nach Typen einordnen und prüfen, wo ein laufendes Programm seine Daten speichert und wie Speicher durch den Programmierer oder automatisch reserviert und freigegeben wird. Erkläre dabei die jeweiligen Vor- und Nachteile für Sicherheit und Geschwindigkeit.
    • Vergleiche, wie Programmiersprachen Datentypen nutzen und überprüfen
      Erkläre, was ein Datentyp ist, vergleiche statische und dynamische Typisierung sowie strenge und lockere Umwandlungsregeln und sage vorher, wie verbreitete Sprachen mit gemischten Typen und Zahlengrenzen umgehen.
    • Beschreibe, wo ein laufendes Programm seine Daten speichert
      Erkläre Variablen als benannte Speicherstellen, die Aufgaben von Aufrufstapel und Heap sowie den Unterschied zwischen dem Kopieren eines Wertes und dem gemeinsamen Nutzen einer Referenz.
    • Vergleiche manuelle und automatische Speicherverwaltung
      Erkläre, wie Speicher in Sprachen freigegeben wird, in denen der Programmierer ihn verwaltet, und in Sprachen mit automatischer Speicherbereinigung oder Ownership-Regeln. Stelle einen Zusammenhang zwischen Speicherfehlern, Programmabstürzen und Sicherheitslücken her.
  4. Beschreibe Programme zunächst als Befehlsfolgen, die den gespeicherten Zustand ändern, und dann als Sammlungen von Objekten, die Daten und Verhalten bündeln. Nutze Klassen, Datenkapselung, Vererbung und Polymorphie, um Code zu strukturieren.
    • Beschreibe imperative und prozedurale Programmierung
      Erkläre imperative Programmierung als schrittweise Befehle, die den Zustand eines Programms ändern, zeige, wie Prozeduren und strukturierter Kontrollfluss diese Befehle ordnen, und erkenne die Risiken eines gemeinsamen veränderlichen Zustands.
    • Modelliere Daten und Verhalten mithilfe von Klassen und Objekten
      Erkläre, wie die objektorientierte Programmierung Daten und die darauf ausführbaren Operationen in Objekten zusammenfasst, lies und entwirf eine einfache Klasse und schütze die Daten eines Objekts durch Datenkapselung.
    • Verwende Klassen wieder und erweitere sie mithilfe von Vererbung, Polymorphie und Komposition
      Nutze Vererbung, um spezialisierte Klassen zu erstellen, erkläre, wie Polymorphie ermöglicht, dass derselbe Aufruf sich bei unterschiedlichen Objekten anders verhält, und entscheide, wann du Objekte kombinierst, statt zu vererben.
  5. Erstelle Programme aus Funktionen, die neue Werte zurückgeben, statt alte zu ändern, und schreibe Programme, die das gewünschte Ergebnis angeben, während die Ausführungsumgebung der Sprache entscheidet, wie es berechnet wird.
    • Erstelle Programme aus reinen Funktionen und unveränderlichen Daten
      Erkläre reine Funktionen, Unveränderlichkeit, Funktionen erster und höherer Ordnung sowie Rekursion und schreibe eine Schleife als Abfolge von map, filter und reduce um.
    • Beschreibe das gewünschte Ergebnis mithilfe deklarativer Sprachen
      Erkläre deklarative Programmierung als Angabe des gewünschten Ergebnisses statt des Weges dorthin und lies Beispiele in einer Datenbankabfragesprache, einer logischen Sprache und anderen deklarativen Notationen.
  6. Löse dasselbe Problem in verschiedenen Stilen, erkenne, dass die meisten modernen Sprachen Paradigmen mischen, und wäge die technischen und praktischen Faktoren ab, die darüber entscheiden, welche Sprache zu einem Projekt passt.
    • Löse dasselbe Problem mit verschiedenen Programmierparadigmen
      Schreibe dieselbe kleine Aufgabe im imperativen, objektorientierten, funktionalen und deklarativen Stil, vergleiche, was jede Version erleichtert, und erkenne Multiparadigmensprachen.
    • Wähle eine passende Programmiersprache für eine Aufgabe
      Wäge die Faktoren ab, die darüber entscheiden, welche Sprache zu einem Projekt passt (Plattform, Anwendungsgebiet, Leistung, Sicherheit, Ökosystem und Menschen), und begründe eine Wahl für ein konkretes Szenario.

Programmierparadigmen einfach erklärt

6 Kapitel

1

Vom Quellcode zu ausführbaren Befehlen

Ein Prozessor führt kleine Binärbefehle aus, während Menschen Programme meist in Sprachen schreiben, die auf das zu lösende Problem ausgerichtet sind. Übersetzungsprogramme verbinden diese beiden Ebenen. Verfolge dieselbe Berechnung durch diese Schichten und vergleiche anschließend das Erstellen eines Programms vor seiner Ausführung mit der Interpretation oder der Übersetzung von Teilen während der Ausführung. Der Weg beeinflusst Portierbarkeit, Startzeit, Geschwindigkeit und den Zeitpunkt, zu dem ein Fehler auftreten kann.

1.1 Drei Ebenen von Programmcode

Eine Zeile wie x = a + b bezeichnet eine Berechnung, ohne ein Prozessorregister zu erwähnen. Abstraktion macht dies möglich: Eine Sprache verbirgt Details der darunterliegenden Ebene, sodass sich ein Programmierer auf die Aufgabe des Programms konzentrieren kann.

Was der Prozessor ausführt

Maschinencode besteht aus Befehlen, die als Binärzahlen kodiert sind. Die Befehlssatzarchitektur (ISA) eines Prozessors legt seine Befehle und deren Kodierungen fest. x86-64 ist in PCs und Servern verbreitet; ARM kommt häufig in Smartphones, Tablets und vielen Laptops vor. Für eine ISA erstellter Code muss übersetzt oder emuliert werden, damit er auf einer anderen funktioniert.

Die Hexadezimaldarstellung verkürzt die Anzeige von Befehlsbytes. Auf x86-64 schreibt B8 01 00 00 00 die Zahl 1 in das Register EAX. Die Bytes 01 D8 addieren den Wert in EBX zu EAX. Register sind kleine Speicherstellen innerhalb des Prozessors. Jeder Befehl führt eine kleine Aktion aus, etwa Daten verschieben, addieren, vergleichen oder springen. Der Befehlszyklus führt diese Befehle aus.

Namen für dieselben kleinen Aktionen

Assemblersprache ersetzt Befehlsbytes durch lesbare Namen, sogenannte Mnemonics. Die beiden x86-64-Befehle lassen sich so schreiben:

mov eax, 1
add eax, ebx

Ein Assembler wandelt Assemblersprache in Maschinencode um. Eine gewöhnliche Befehlszeile erzeugt normalerweise einen Maschinenbefehl. Registernamen und Befehle gehören weiterhin zu einer bestimmten ISA, sodass die Verwendung von Wörtern statt Bytes Assemblersprache nicht portierbar macht.

Ein Problem in weniger Zeilen beschreiben

Eine höhere Programmiersprache bietet Variablen, Ausdrücke, Bedingungen, Schleifen und Funktionen. Python, C, Java und JavaScript erlauben es dem Programmierer, Operationen zu beschreiben, ohne Register auszuwählen. Eine Anweisung benötigt häufig mehrere Maschinenbefehle. Eine vereinfachte Übersetzung von x = a + b würde:

  1. Den Wert von a in ein Register laden.
  2. Den Wert von b in ein anderes Register laden.
  3. Die Registerwerte addieren.
  4. Das Ergebnis für x speichern.

Ein tatsächlicher Compiler kann diese Schritte zusammenfassen oder umordnen und dabei das erforderliche Verhalten beibehalten.

EbeneWas ein Programmierer schreibtWer oder was kümmert sich um die Hardwaredetails?
MaschinencodeKodierte ProzessorbefehleDer Programmierer oder Codegenerator
AssemblerspracheMnemonics und RegisterDer Programmierer, wobei ein Assembler die Befehle kodiert
Code einer höheren ProgrammierspracheVariablen und Operationen auf den Daten des ProblemsEin Compiler, Interpreter oder eine Kombination

Quellcode in einer höheren Programmiersprache lässt sich leichter lesen, ändern und prüfen. Portierbarer C-Quellcode kann einmal für ein x86-64-System und erneut für ein ARM-System kompiliert werden. Jede erstellte Version benötigt weiterhin die passende Unterstützung durch das Betriebssystem und passende Bibliotheken. Die Portierbarkeit des Quellcodes macht eine native ausführbare Datei nicht universell einsetzbar.

Warum die unteren Ebenen weiterhin wichtig sind

Assemblersprache bleibt nützlich in Startcode, einigen Betriebssystem- und Treiberroutinen, kleinen eingebetteten Systemen und bei der Sicherheitsanalyse von Programmen, deren Quellcode nicht verfügbar ist. Für gewöhnlichen Anwendungscode erzeugen moderne Compiler meist Maschinencode, dessen Leistung mit der von handgeschriebenem Assemblercode vergleichbar ist.

Der Übergang zu höheren Ebenen erfolgte allmählich. Assemblersprache verbreitete sich in den späten 1940er- und frühen 1950er-Jahren. Grace Hoppers A-0-System trug 1952 zur automatischen Übersetzung bei. IBM veröffentlichte FORTRAN 1957; COBOL folgte 1959, und Lisp entstand etwa zwischen 1958 und 1960. „Höher“ bezeichnet den Abstand zu Hardwaredetails, nicht die Schwierigkeit, die Sprache zu erlernen.

Wichtige Erkenntnisse

  • Ein Prozessor führt Maschinenbefehle aus, die durch seine ISA festgelegt sind.
  • Ein Assembler kodiert Assemblerbefehle; Assemblersprache bleibt an eine ISA gebunden.
  • Anweisungen höherer Programmiersprachen verbergen Hardwareschritte und werden häufig zu mehreren Befehlen.
  • Portierbarer Quellcode lässt sich für unterschiedliche Ziele übersetzen; nativer Maschinencode ist zielspezifisch.
  • Maschinennaher Code bleibt dort nützlich, wo Hardwarezugriff oder Analyse ihn erfordern.

1.2 Wie ein Compiler ein Programm erstellt

Ein Compiler übersetzt Quellcode in Zielcode. Das Ziel kann nativer Maschinencode oder eine Zwischenform wie Bytecode sein. Die Ahead-of-time-Kompilierung (AOT) findet vor der Programmausführung statt; die Just-in-time-Kompilierung erfolgt während der Ausführung.

Vom Text zu Befehlen

Bei einer Erstellung im Voraus wird eine Quelldatei in mehreren Phasen in Befehle umgewandelt:

  1. Die lexikalische Analyse zerlegt Text in Token: bedeutungstragende Teile wie Namen, Operatoren und Zahlen.
  2. Die syntaktische Analyse prüft die Grammatik und erstellt eine Struktur, die zeigt, wie die Teile zusammenpassen.
  3. Die semantische Analyse prüft Bedeutungsregeln, darunter Deklarationen und erlaubte Typkombinationen.
  4. Die Optimierung findet einen effizienteren Weg, die erforderliche Arbeit zu erledigen.
  5. Die Codegenerierung erzeugt die Zielbefehle.

Betrachte die C-Anweisung total = price * 3;. Ihre sechs Token sind total, =, price, *, 3 und ;. Die Namen sind Bezeichner, 3 ist ein Ganzzahlliteral und das Semikolon beendet die Anweisung. Die syntaktische Analyse hält fest, dass die Multiplikation innerhalb der Zuweisung erfolgt: Berechne price * 3 und speichere dessen Wert anschließend in total.

Diese Phasen haben unterschiedliche Aufgaben. Eine korrekt geschriebene Folge von Token kann dennoch gegen die Grammatik verstoßen. Eine grammatisch korrekte Anweisung kann trotzdem einen Typ falsch verwenden. Token zu erkennen reicht nicht aus, um festzustellen, ob ein Programm gültig ist.

Dieselbe Arbeit mit weniger Aufwand erledigen

Angenommen, Quellcode enthält seconds_per_day = 60 * 60 * 24. Die Faktoren sind Konstanten, sodass der Compiler bei der Erstellung 60 mit 60 zu 3.600 und dann mit 24 zu 86.400 multiplizieren kann. Er kann den Endwert in die Ausgabe übernehmen, statt die Multiplikation zur Laufzeit zu wiederholen. Dies heißt Konstantenfaltung.

Eine Optimierung muss das von der Sprache geforderte Verhalten erhalten. Sie darf eine Berechnung nicht durch eine schnellere ersetzen, die andere als die geforderten Ergebnisse liefert. Das Ziel ist, Arbeit, Zeit oder Speicher zu sparen und dabei die Bedeutung des Programms beizubehalten.

Die Teile verbinden

Große Programme werden meist aus vielen Dateien erstellt. Ein Compiler kann für jede Quelldatei eine Objektdatei erzeugen. Eine Objektdatei enthält übersetzten Code, der möglicherweise noch Verbindungen zu anderem Code benötigt. Ein Linker verbindet die Teile und löst Aufrufe von Bibliotheksroutinen auf, etwa Ausgabe- oder Rechenfunktionen.

BindeverfahrenHerkunft des Bibliothekscodes zur Laufzeit
Statisches BindenCode, der bei der Erstellung in die ausführbare Datei aufgenommen wird
Dynamisches BindenEine kompatible gemeinsam genutzte Bibliothek, die beim Start oder während der Ausführung geladen wird

Die entstehende ausführbare Datei wird für eine Zielplattform erstellt, einschließlich einer ISA und der Konventionen des Betriebssystems. Der Benutzer benötigt den Ahead-of-time-Compiler nicht, um sie auszuführen. Gemeinsam genutzte Bibliotheken oder Laufzeitkomponenten können weiterhin erforderlich sein.

Verstehen, was eine erfolgreiche Erstellung belegt

In Java besteht int count = "five"; vor der Ausführung eine Typprüfung nicht. Text kann diese Ganzzahlvariable nicht initialisieren. Das Programm muss die erforderlichen Prüfungen zur Übersetzungszeit bestehen, bevor eine lauffähige Version vorliegt.

Andere Fehler überstehen die Übersetzung. Eine Division durch eine vom Benutzer eingegebene Null hängt von Laufzeitdaten ab. Ein Programm, das subtrahieren soll, stattdessen aber addiert, kann laufen und den falschen Wert zurückgeben. Die Übersetzung prüft Sprachregeln; Tests müssen zusätzlich prüfen, ob das Ergebnis zur beabsichtigten Aufgabe passt.

Wichtige Erkenntnisse

  • Kompilierung übersetzt Quellcode in Zielcode, der kein Maschinencode sein muss.
  • Compilerphasen prüfen Token, Struktur und Bedeutung, bevor sie Zielbefehle erzeugen.
  • Optimierung erhält das erforderliche Verhalten; Konstantenfaltung wertet Konstanten bei der Erstellung aus.
  • Ein Linker verbindet Objektdateien und Bibliothekscode.
  • Eine erfolgreiche Erstellung kann trotzdem Laufzeitfehler oder logische Fehler enthalten.

1.3 Interpretation, Bytecode und JIT-Kompilierung

Ein Programm muss nicht zu einer nativen ausführbaren Datei werden, bevor ein Benutzer es startet. Ein Interpreter führt das Programm mithilfe eines anderen laufenden Programms aus. Viele Implementierungen verbinden Interpretation mit Kompilierung. Die hilfreiche Frage lautet daher, wie das jeweilige System Code ausführt.

Mit einem Interpreter ausführen

Ein Interpreter muss immer verfügbar sein, wenn er das Programm ausführt. Eine interaktive REPL wiederholt vier Aktionen: eine Eingabe lesen, sie auswerten, ein Ergebnis ausgeben und zur nächsten Eingabe zurückkehren. Sie gibt beim Ausprobieren kleiner Ausdrücke schnell Rückmeldung.

Interpretation bedeutet nicht zwangsläufig, rohen Quellcode Zeile für Zeile zu lesen. CPython, die Standardimplementierung von Python, übersetzt eine Datei zunächst in Bytecode. Eine virtuelle Maschine als Software führt dann diese internen Befehle aus. Der Bytecode gehört zu dieser Laufzeitumgebung; er ist nicht der native Befehlssatz des Prozessors.

Warum manche Fehler früher erscheinen

Vergleiche zwei Python-Dateien. Diese hat eine gültige Syntax:

print("start")
x = 10
print(x + "5")

Sie gibt „start“ aus, bindet x an 10 und löst dann einen TypeError aus. Die Addition dieser Ganzzahl zu einer Zeichenkette schlägt erst fehl, wenn der Ausdruck ausgeführt wird. Eine ähnliche Operation in einem nicht erreichten Zweig kann unbemerkt bleiben, bis der Zweig ausgeführt wird.

Entferne nun die schließende Klammer aus der letzten Zeile. CPython kann die Datei nicht übersetzen, weil ihre Syntax unvollständig ist. Es meldet einen Syntaxfehler, bevor eine Anweisung in dieser Datei ausgeführt wird, sodass „start“ nicht ausgegeben wird. Die Syntaxprüfung der ganzen Datei und die Laufzeitprüfung einer ausgeführten Operation finden zu unterschiedlichen Zeitpunkten statt.

Bytecode weitergeben und während der Ausführung spezialisieren

Java zeigt einen verbreiteten hybriden Weg. javac Hello.java erzeugt Hello.class, das Bytecode enthält. java Hello startet eine kompatible Java Virtual Machine (JVM). Unterschiedliche JVM-Implementierungen ermöglichen, dass dieser Bytecode auf verschiedenen Prozessorfamilien läuft. Der Bytecode ist zwischen kompatiblen Laufzeitumgebungen portierbar; jede Laufzeitumgebung muss selbst auf ihrer jeweiligen Plattform funktionieren. C# zielt in ähnlicher Weise auf eine von .NET verwendete Zwischensprache.

Ein Just-in-time-Compiler (JIT-Compiler) erzeugt während der Programmausführung Maschinencode. Die HotSpot-Laufzeitumgebung von Java beginnt gewöhnlich mit Interpretation und kompiliert häufig verwendete Methoden. JavaScript-Ausführungsumgebungen in Browsern kombinieren ebenfalls Ausführungstechniken, darunter die JIT-Kompilierung. Das Kompilieren häufig ausgeführter Teile kann die dauerhafte Geschwindigkeit verbessern, benötigt aber während der Ausführung Zeit und Speicher.

WegNützlicher VorteilZu berücksichtigender Aufwand
Native Erstellung im VorausDie Arbeit wird vor dem Start übersetzt; der Benutzer benötigt keinen CompilerSeparate kompatible Versionen für Zielplattformen
InterpretationSchnelle Rückmeldung beim Ändern und Ausführen mit installiertem InterpreterWiederholte Arbeit zur Laufzeit kann rechenintensiven Code verlangsamen
Bytecode mit virtueller Maschine und JITPortierbarer Zwischencode mit Optimierung zur LaufzeitInstallation der Laufzeitumgebung, Aufwärmphase und zusätzlicher Speicherbedarf

Eine Schleife, die eine Milliarde Werte addiert, läuft in kompiliertem C typischerweise viel schneller als in CPython: CPython führt bei vielen Operationen Interpreterarbeit und Typprüfungen aus. Bei einem Skript, das 20 Dateien umbenennt, kann der Entwicklungsaufwand weit mehr ins Gewicht fallen als dieser Unterschied. Miss die tatsächliche Arbeitslast, bevor du aus der Bezeichnung einer Sprache auf ihre Geschwindigkeit schließt.

Kompiliert und interpretiert beschreiben Entscheidungen bei der Implementierung. CPython verwendet Bytecode-Interpretation; PyPy nutzt JIT-Techniken. Eine Sprache kann mehr als eine Implementierung haben, und eine Implementierung kann mehrere Ausführungsmethoden verwenden.

Wichtige Erkenntnisse

  • Ein Interpreter führt ein Programm aus und muss dafür vorhanden sein.
  • CPython übersetzt eine Datei in Bytecode, bevor es sie ausführt.
  • Ein Syntaxfehler kann den Start einer Datei verhindern; ein Laufzeitfehler erscheint, wenn die betreffende Operation ausgeführt wird.
  • Ein JIT-Compiler erzeugt während der Ausführung nativen Code, oft für häufig genutzte Teile.
  • Ausführungsmethoden gehören zu Implementierungen, und die Leistung hängt von der Arbeitslast ab.
2

Lesen, was kurze Programme tun

Beim Lesen von Code stellen sich zwei Fragen: ob seine Form den Regeln der Sprache folgt und was seine Operationen bedeuten. Ein gleich aussehender Ausdruck kann sich in zwei Sprachen unterschiedlich verhalten. Lerne, Fehler einzuordnen, und vollziehe anschließend Zuweisungen, Entscheidungen, Schleifen und Funktionsaufrufe in ihrer Ausführungsreihenfolge nach. Eine kleine Tabelle der sich ändernden Werte macht den Unterschied zwischen der Absicht des Programmierers und dem tatsächlichen Verhalten des Programms sichtbar.

2.1 Syntax, Bedeutung und drei Fehlerarten

Syntax legt die Regeln für gültigen Programmtext fest. Semantik gibt diesem Text seine Bedeutung. Ein Programm kann jede Grammatikregel befolgen und trotzdem das falsche Ergebnis berechnen. Die Prüfung seiner äußeren Form ist daher nur der erste Teil des Lesens.

Die Bestandteile und die Grammatik erkennen

Zu den Token gehören Schlüsselwörter wie if, Bezeichner wie total, Literale wie 3 oder "hello", Operatoren wie + und == sowie Satzzeichen. Eine Grammatik beschreibt, wie diese Teile Ausdrücke und Anweisungen bilden können.

Python verlangt einen Doppelpunkt nach if x > 3: und verwendet Einrückung, um die von dieser Bedingung gesteuerten Anweisungen zu kennzeichnen. C, Java und JavaScript verwenden geschweifte Klammern für Blöcke; C und Java verlangen außerdem nach vielen Anweisungen Semikolons. In diesen Sprachen hilft die Einrückung menschlichen Lesern, bestimmt aber nicht den Block.

Unterschiedliche Syntax kann eine ähnliche Aktion ausdrücken. Diese Anweisungen geben eine Begrüßung aus:

print("hello")
printf("hello\n");
System.out.println("hello");

Die C-Escape-Sequenz \n fügt einen Zeilenumbruch hinzu. Das Konzept der Ausgabe zu kennen hilft dabei, die jeweilige Ausdrucksweise jeder Sprache zu lernen.

Dieselben Symbole können Unterschiedliches bedeuten
Ausdruck und SpracheErgebnisRegel
7 / 2 in Python oder JavaScript3.5Die Division liefert ein Ergebnis mit Nachkommateil
7 / 2 mit Ganzzahloperanden in C oder Java3Die Ganzzahldivision schneidet in Richtung null ab
-7 // 2 in Python-4Die ganzzahlige Division rundet ab
-7 / 2 mit Ganzzahlen in C-3Das Abschneiden erfolgt in Richtung null
"3" * 4 in Python"3333"Eine Zeichenkette wird wiederholt
"3" * 4 in JavaScript12Text wird für die Multiplikation in eine Zahl umgewandelt

Das vertraute Aussehen eines Symbols legt seine Bedeutung nicht für alle Sprachen fest.

Auch die Auswertungsreihenfolge ist wichtig. Die Rangfolge bewirkt, dass Multiplikation und Division vor der Addition stattfinden. Bei a = 4 und b = 6 dividiert average = a + b / 2 zuerst 6 durch 2 und addiert dann 4, was 7,0 ergibt. Die Klammern in average = (a + b) / 2 bewirken, dass zuerst die Summe gebildet wird, und ergeben 5,0.

Fehler unterscheiden

Ein Syntaxfehler verstößt gegen die Grammatik, etwa ein fehlender Python-Doppelpunkt. Ein Laufzeitfehler tritt während einer ausgeführten Operation auf, etwa bei einer Division durch null in Python. Ein logischer Fehler lässt die Ausführung zu, liefert aber ein unbeabsichtigtes Ergebnis, wie bei der falschen Durchschnittsberechnung.

Manche gültigen Ausdrücke überspringen absichtlich eine Operation. In Python verwendet x != 0 and 10 / x > 2 die Kurzschlussauswertung. Ist x null, ist die erste Bedingung falsch, sodass die rechte Seite nicht ausgewertet wird und keine Division stattfindet. Eine Umkehrung dieser Bedingungen würde diesen Schutz aufheben. Python schreibt and, or und not; C, Java und JavaScript verwenden &&, || und !.

Zuweisung und Vergleich sind eine weitere wichtige Unterscheidung. = speichert einen Wert; == prüft Gleichheit. C erlaubt if (x = 5): Es weist 5 zu und behandelt diesen Wert ungleich null als wahr. Python weist if x = 5: als ungültige Syntax zurück. Derselbe Fehler kann daher in unterschiedlichen Sprachen zu einem anderen Zeitpunkt erkannt werden.

Wichtige Erkenntnisse

  • Syntax legt die gültige Form fest; Semantik legt die Bedeutung fest.
  • Operatoren können in verschiedenen Sprachen unterschiedliche Bedeutungen haben.
  • Rangfolge und Klammern bestimmen, welche Operationen zusammengefasst werden.
  • Syntax-, Laufzeit- und logische Fehler erfordern unterschiedliche Prüfungen.
  • Kurzschlussauswertung kann eine unsichere Operation überspringen.
  • Eine Zuweisung speichert einen Wert; ein Gleichheitsvergleich prüft Werte.

2.2 Verfolge Werte durch Kontrollfluss und Aufrufe

Ein kurzes Programm lässt sich leichter verstehen, wenn du jeweils eine ausgeführte Anweisung nachverfolgst. Halte die aktuellen Werte sichtbar fest, statt von der ersten Zeile direkt zu einem vermuteten Ergebnis zu springen.

Ausdrücke und Zuweisungen

Ein Ausdruck liefert einen Wert: Beispiele sind price * 3, x > 10 und len(name). Eine Anweisung führt eine Aktion aus, etwa einen Wert zuweisen oder einen Block steuern. In total = price * 3 wird der Ausdruck auf der rechten Seite ausgewertet, bevor sein Ergebnis total zugewiesen wird.

Eine Variable ist ein Name, der sich auf einen Wert bezieht. Eine Zuweisung gibt dem Namen seinen aktuellen Wert; sie ist keine mathematische Aussage, dass beide Seiten schon immer gleich waren. Enthält count den Wert 4, liest count = count + 1 zunächst 4, addiert 1 und bindet count an 5.

Die nächste ausgeführte Anweisung verfolgen

Drei Strukturen ordnen den Kontrollfluss:

  • Eine Sequenz führt Anweisungen der Reihe nach aus.
  • Eine Verzweigung wählt anhand einer Bedingung einen Weg, etwa mit if und else.
  • Eine Iteration wiederholt einen Block mit einer for- oder while-Schleife.

Der Strukturierungssatz, der mit Böhm und Jacopini im Jahr 1966 verbunden ist, zeigt, dass sich mit diesen Strukturen jeder Algorithmus ausdrücken lässt. Eine for-Schleife durchläuft gewöhnlich die Elemente einer Sammlung. Eine while-Schleife wiederholt sich, solange ihre Bedingung wahr bleibt.

Betrachte diese Python-Schleife:

total = 0
for n in [3, 8, 5]:
    total = total + n

Die eingerückte Zuweisung wird für jedes Element einmal ausgeführt. Eine Ablauftabelle hält den Zustand nach jeder Aktualisierung fest:

ZeitpunktAktuelles ntotal
Vor der SchleifeNoch kein Element0
Erste Aktualisierung33
Zweite Aktualisierung811
Dritte Aktualisierung516

Jede neue Summe baut auf der vorherigen auf. Würdest du total innerhalb der Schleife zurücksetzen, ergäbe sich eine andere Berechnung, weil frühere Additionen verloren gingen.

Eine Funktion betreten und verlassen

Eine Funktion fasst eine Aufgabe unter einem Namen zusammen. Ein Parameter ist ein Name in der Definition; ein Argument ist ein Wert, der bei einem Aufruf übergeben wird:

def area(width, height):
    return width * height

result = area(3, 4)

Der Aufruf bindet width an 3 und height an 4. Die Multiplikation ergibt 12, und return gibt den Wert an den Aufrufer zurück. Die Ausführung wird bei der Zuweisung fortgesetzt, sodass result den Wert 12 erhält. Die Namen beschreiben eine Aufgabe, die mit anderen Argumenten wiederverwendet werden kann.

Ein Name hat einen Gültigkeitsbereich

Der Gültigkeitsbereich bestimmt, wo ein Name verwendet werden kann. Innerhalb einer Funktion erzeugte Namen sind gewöhnlich lokal für diesen Aufruf. Ein Name auf oberster Ebene ist innerhalb seines Moduls global. Getrennte Aufrufe haben getrennte lokale Bindungen, selbst wenn sie identische Namen verwenden.

count = 7

def reset():
    count = 0

reset()
print(count)

Dies gibt 7 aus. Die Zuweisung innerhalb von reset erzeugt ein lokales count; sie ersetzt nicht die globale Bindung. Eine Funktion kann stattdessen ein Ergebnis zurückgeben und den Aufrufer dieses Ergebnis zuweisen lassen.

Gültigkeitsbereich und Lebensdauer beantworten unterschiedliche Fragen. Ein lokaler Name ist für Code außerhalb der Funktion nicht allein deshalb verfügbar, weil das Objekt, auf das er verwies, noch existiert. Zurückgegebene Objekte können weiterleben, und eine zurückgegebene verschachtelte Funktion kann den Zugriff auf Variablen ihres umschließenden Aufrufs behalten; diese erhaltene Umgebung ist Teil einer Closure.

Wichtige Erkenntnisse

  • Werte den Ausdruck auf der rechten Seite aus, bevor du das Ziel einer Zuweisung aktualisierst.
  • Vollziehe Sequenz, Verzweigung und Iteration in der tatsächlich ausgeführten Reihenfolge nach.
  • Parameter sind Namen in der Definition; Argumente sind übergebene Werte.
  • Ein Rückgabewert übermittelt ein Ergebnis an den Aufrufer.
  • Lokale und globale Namen können getrennte Bindungen haben; der Gültigkeitsbereich unterscheidet sich von der Lebensdauer.
3

Typen, Referenzen und Speicher

Werte brauchen einen Typ, einen Platz im Speicher und eine Lebensdauer. Diese Aspekte erklären, warum ein Text, der wie eine Zahl aussieht, Rechenoperationen zurückweisen kann, warum zwei Namen dieselbe veränderte Liste zeigen können und warum ungenutzte Daten manchmal Speicher belegt halten. Vergleiche die Prüfungen verschiedener Sprachen, verfolge einen Funktionsaufruf durch den Speicher und untersuche drei Arten, Speicher freizugeben. Jeder Ansatz verschiebt das Verhältnis von Kontrolle, Komfort und Sicherheit.

3.1 Typen bestimmen, welche Operationen sinnvoll sind

Ein Datentyp legt mögliche Werte und die darauf unterstützten Operationen fest. Eine Ganzzahl unterstützt ganzzahlige Arithmetik; ein Wahrheitswert steht für wahr oder falsch. Zeichenketten, Listen, Datensätze und Objekte verbinden oder ordnen andere Werte. Typen bestimmen auch mit, wie Werte im Speicher dargestellt werden, wobei die Größe eines Wertes vom Wert und von der Implementierung abhängen kann.

Wann werden Typen geprüft?
AnsatzZeitpunkt der PrüfungBeispiel
Statische TypisierungVor der AusführungJava weist int count = "five"; zurück
Dynamische TypisierungWährend der AusführungPython prüft, ob eine ausgeführte Operation zu ihren Operanden passt

In Python ist count = 5 gefolgt von count = "five" gültig. Der Name verweist nun auf eine Zeichenkette. Die Auswertung von count + 1 löst dann einen TypeError aus. Bei dynamischer Typisierung haben Werte bestimmte Typen; sie schafft die Typen nicht ab.

Statische Typisierung verlangt keine sichtbare Typangabe an jeder Variablen. Typinferenz ermöglicht es einem Compiler, einen Typ aus dem umgebenden Code abzuleiten. Kotlin leitet für val n = 5 eine Ganzzahl ab, und Rust kann dies für let n = 5; tun. Pythons optionale Typhinweise sind anders: Separate Werkzeuge wie mypy können sie prüfen, aber die gewöhnliche Python-Ausführung erzwingt sie nicht.

Wann werden Werte umgewandelt?

Der Prüfzeitpunkt und die Strenge der Umwandlung sind getrennte Fragen. Python weist "3" + 4 zurück; die ausdrückliche Umwandlung in int("3") + 4 ergibt 7. JavaScript wendet für verschiedene Operatoren unterschiedliche Regeln zur automatischen Umwandlung an:

  • "3" + 4 ergibt die Zeichenkette "34".
  • "3" * 4 ergibt die Zahl 12.
  • "3" - 4 ergibt die Zahl -1.

Die automatische Umwandlung heißt implizite Typumwandlung. Die Bezeichnungen „starke“ und „schwache“ Typisierung beschreiben informell die Strenge der Umwandlung; sie sind keine anderen Namen für statische und dynamische Typisierung. Python ist dynamisch und bei der obigen Addition gemischter Typen streng; C ist statisch und erlaubt dennoch viele implizite numerische Umwandlungen.

Pythons input() gibt eine Zeichenkette zurück. Gibt jemand „30“ ein, hat das Ergebnis weiterhin den Typ str. age + 1 schlägt fehl, während int(age) + 1 den Wert 31 ergibt, wenn age diesen Text enthält. Was auf dem Bildschirm erscheint, reicht nicht aus, um den Typ zu bestimmen.

Zahlen haben Darstellungsgrenzen

Eine vorzeichenbehaftete 32-Bit-Ganzzahl stellt Werte von −2.147.483.648 bis 2.147.483.647 dar. Javas int schlägt vom Maximum zum Minimum um, wenn 1 addiert wird. Ein vorzeichenbehafteter Überlauf in C ist undefiniertes Verhalten, sodass in C nicht von demselben Umschlagen ausgegangen werden darf. Ganzzahlen in Python wachsen, soweit der Speicher es erlaubt: 2 ** 100 ist genau 1.267.650.600.228.229.401.496.703.205.376.

Gleitkommazahlen stellen die meisten Dezimalbrüche binär nur näherungsweise dar. In Python und JavaScript ergibt 0.1 + 0.2 den Wert 0.30000000000000004. Ein direkter Vergleich dieses Ergebnisses mit 0.3 ergibt falsch.

Wenn eine Berechnung einen kleinen Fehler zulässt, vergleiche Werte mit einer für die Aufgabe gewählten Toleranz, zum Beispiel abs(a - b) < 1e-9. Eine so kleine absolute Toleranz eignet sich nur für manche Größenordnungen. Verwende für exakte Dezimalrechnungen im Rechnungswesen ganze kleinste Einheiten oder einen geeigneten Dezimaltyp. Eine Änderung des Vergleichs kann eine näherungsweise Darstellung nicht exakt machen.

Wichtige Erkenntnisse

  • Ein Typ legt Werte und unterstützte Operationen fest.
  • Statische und dynamische Typisierung beschreiben den Prüfzeitpunkt, während die implizite Typumwandlung die Umwandlungsregeln betrifft.
  • Typinferenz unterstützt statische Typisierung; Python-Typhinweise benötigen separate Prüfwerkzeuge.
  • Ganzzahlen mit fester Bitbreite können überlaufen; das Verhalten hängt von der Sprache ab.
  • Rundung bei Gleitkommazahlen beeinflusst Gleichheitsprüfungen und die Wahl der Zahlendarstellung.

3.2 Verfolge Namen, Objekte und Funktionsaufrufe im Speicher

Der Hauptspeicher speichert die Daten eines laufenden Programms. Jedes Byte hat eine Adresse. Sprachen stellen Namen bereit, damit Programmierer mit Werten arbeiten können, ohne für jede Operation direkte Adressen zu schreiben.

Aufrufe nutzen Rahmen; Objekte haben eine Lebensdauer

In C belegt ein int gewöhnlich 4 Bytes, und &x liefert die Adresse von x. Höhere Programmiersprachen verbergen Adressen häufig hinter Referenzen. Wichtig ist die Unterscheidung zwischen einer Bindung, die ein Objekt benennt, und dem Objekt selbst.

Der Aufrufstapel verwaltet aktive Funktionsaufrufe. Jeder Aufruf hat einen Stapelrahmen mit Informationen wie Parametern, lokalen Bindungen und der Stelle, an der die Ausführung nach der Rückkehr fortgesetzt werden soll. Ruft main die Funktion area(3, 4) auf, wird ein area-Rahmen oberhalb des Rahmens des Aufrufers hinzugefügt. Die Funktion berechnet 12, kehrt zurück und ihr Rahmen wird entfernt. Bei gewöhnlichen verschachtelten Aufrufen kehrt der neueste Aufruf zuerst zurück.

Rekursion fügt für jede noch nicht abgeschlossene Ebene einen Aufruf hinzu. Häufen sich Aufrufe immer weiter an, kann der verfügbare Stapelspeicher erschöpft werden. Python löst normalerweise nach ungefähr 1.000 verschachtelten Aufrufen einen RecursionError aus, wobei die Grenze von seinen Einstellungen abhängt. Damit eine endliche rekursive Berechnung endet, muss eine Abbruchbedingung erreicht werden.

Der Heap enthält dynamisch allokierte Objekte, etwa eine wachsende Liste oder ein Objekt, das länger als die Funktion besteht, die es erzeugt hat. Bei der Speicherallokation wird Speicher reserviert. Eine zurückgegebene Liste kann auf dem Heap bleiben, nachdem der Aufruf, der sie erzeugt hat, beendet ist.

Das Kopieren einer Referenz teilt ein Objekt

Eine Referenz bezeichnet ein Objekt. Ein Zeiger in C oder C++ stellt ausdrücklich eine Adresse dar, über die auf Daten zugegriffen wird. Das Kopieren einer Referenz kopiert den Zugriff auf das Objekt, nicht alle Daten des Objekts.

a = [1, 2]
b = a
b.append(3)

Nach der ersten Zeile gibt es eine Liste. Die zweite Zeile bindet einen weiteren Namen an sie. Die letzte Zeile verändert diese Liste, sodass sowohl a als auch b den Wert [1, 2, 3] zeigen. Diese gemeinsame Nutzung heißt Aliasing. b = a.copy() würde eine separate äußere Liste erzeugen; bei dieser Liste von Ganzzahlen ließe das Anhängen an die neue Liste a unverändert.

Vergleiche Zuweisung und Veränderung:

x = 5
y = x
y = y + 1

Die Ganzzahl 5 ist unveränderlich. Die Addition ergibt 6, und nur y wird neu gebunden. x verweist weiterhin auf 5. Python-Zahlen, -Zeichenketten und -Tupel sind unveränderlich; Listen und Wörterbücher sind veränderlich. Der feste Inhalt eines Tupels kann eine Referenz auf eine veränderliche Liste enthalten, sodass die Unveränderlichkeit des äußeren Behälters nicht jedes verschachtelte Objekt unveränderlich macht.

Dieselben Regeln auf Argumente anwenden

Wenn Python eine Liste an eine Funktion übergibt, verweist der Parameter auf dasselbe Objekt. Der Aufruf von items.append(4) innerhalb der Funktion verändert diese Liste. Die Zuweisung items = [] bindet nur den lokalen Parameternamen an eine neue Liste. Der Name des Aufrufers bleibt an das ursprüngliche Objekt gebunden.

C übergibt Argumentwerte per Wertübergabe. Damit eine Funktion die Variable eines Aufrufers über deren Adresse aktualisieren kann, übergib einen Zeiger auf sie. Stelle dir beim Nachvollziehen in jeder Sprache zwei getrennte Fragen: „Auf welches Objekt verweist dieser Name?“ und „Ändert diese Operation das Objekt oder bindet sie einen Namen an etwas anderes?“

Wichtige Erkenntnisse

  • Der Aufrufstapel verwaltet noch nicht abgeschlossene Aufrufe; der Heap enthält dynamisch allokierte Objekte.
  • Ein Objekt kann länger bestehen als die Funktion, die es erzeugt hat.
  • Das Kopieren einer Referenz ermöglicht die gemeinsame Nutzung eines Objekts; es vervielfältigt dessen Daten nicht.
  • Eine Veränderung ändert ein Objekt, während eine erneute Bindung ändert, worauf ein Name verweist.
  • Bei der Argumentübergabe gilt dieselbe Unterscheidung zwischen gemeinsam genutzten Objekten und lokalen Bindungen.

3.3 Gib Speicher frei, ohne noch benötigte Daten zu verlieren

Allokierter Speicher muss irgendwann wiederverwendbar werden. Die Herausforderung besteht darin, Daten nach ihrer letzten Verwendung freizugeben und zugleich jedes Objekt zu behalten, das das Programm noch benötigt. Sprachen weisen diese Verantwortung dem Programmierer, einem Laufzeitsystem oder vom Compiler geprüften Ownership-Regeln zu.

Manuelle Verwaltung und ihre Fehler

In C fordert malloc(n) n Bytes an und gibt bei Erfolg einen Zeiger auf den Block zurück. free(p) gibt einen durch Allokation erhaltenen Block frei. Der Programmierer muss dies zum richtigen Zeitpunkt und genau einmal tun. Auch C++ unterstützt manuelle Verwaltung, obwohl seine Standardwerkzeuge die Freigabe über die Lebensdauer von Objekten automatisieren können.

Verschiedene Fehler haben unterschiedliche Ursachen:

FehlerWas schiefging
SpeicherleckNicht benötigter allokierter Speicher wird nicht freigegeben
Use-after-freeEin Zeiger wird verwendet, nachdem der Speicher freigegeben wurde, auf den er verweist
Double FreeDerselbe allokierte Block wird zweimal freigegeben
PufferüberlaufEin Schreibvorgang geht über die Schranken eines Speicherblocks hinaus

Ein Zeiger, der weiterhin auf freigegebenen Speicher verweist, ist ein hängender Zeiger. Der Speicher könnte für etwas anderes wiederverwendet werden, sodass ein Zugriff darauf Daten beschädigen oder das Programm zum Absturz bringen kann.

Für ein C-Array int scores[10] sind die Positionen 0 bis 9 gültig. Das Schreiben auf scores[10] geht über das Array hinaus, und C bietet keine automatische Prüfung der Schranken. Python löst einen IndexError aus, wenn Index 10 für eine Liste mit zehn Elementen verwendet wird, und verhindert so, dass dieser Zugriff benachbarten Speicher überschreibt.

Erreichbarkeit verfolgen oder Referenzen zählen

Eine verfolgende automatische Speicherbereinigung beginnt bei noch aktiven Ausgangspunkten, etwa aktiven Referenzen, folgt Verbindungen zwischen Objekten und kann nicht erreichbare Objekte freigeben. Java, C#, Go und JavaScript verwenden verfolgende Verfahren. Die Bereinigung benötigt Arbeit zur Laufzeit und Speicher; je nach Verfahren kann sie Pausen verursachen.

Die Referenzzählung verfolgt die Referenzen auf ein Objekt. Für eine neue Python-Liste lautet die vereinfachte Abfolge: a = [1, 2] liefert eine Referenz; b = a liefert eine zweite; del a entfernt eine; b = None entfernt die andere. CPython gibt diese gewöhnliche Liste normalerweise frei, wenn ihre Anzahl null erreicht.

Eine Gruppe von Objekten kann die Referenzzahlen der jeweils anderen Objekte über null halten, selbst wenn kein aktiver Teil des Programms die Gruppe erreichen kann. Das ist ein Referenzzyklus. CPython ergänzt die Referenzzählung um eine Zyklenerkennung. Referenzzählung und Verfolgung sind beides Verfahren der automatischen Speicherverwaltung.

Ownership vor der Ausführung prüfen

Rust gibt jedem Wert einen Eigentümer und prüft Regeln für das Ausleihen von Referenzen. Endet die Ownership, gibt generierter Aufräumcode den Wert frei. Sicheres Rust weist Verwendungen von Referenzen zurück, die länger als ihr gültiges Ziel bestehen könnten, ohne eine verfolgende automatische Speicherbereinigung zu benötigen. Swift verwendet automatische Referenzzählung, die der Compiler einfügt.

Speichersicherheit ist auch über Abstürze hinaus wichtig. Microsoft berichtete 2019, dass ungefähr 70 % der Schwachstellen, denen das Unternehmen jährlich CVE-Kennungen zuwies, die Speichersicherheit betrafen; Chromium meldete für 2020 einen ähnlichen Anteil schwerwiegender Fehler. Im Dezember 2023 forderten CISA, NSA, FBI und internationale Partner Softwarehersteller auf, Übergänge zu speichersicheren Sprachen zu planen.

Automatische Speicherbereinigung behält weiterhin erreichbare Objekte. Eine ständig wachsende globale Liste kann jeden alten Datensatz am Leben halten, auch nachdem die Anwendung ihn nicht mehr verwendet. Eine automatische Speicherbereinigung kann nicht entscheiden, dass diese erreichbaren Datensätze für die Aufgabe unnötig geworden sind; das Programm muss aufhören, sie zu behalten.

Wichtige Erkenntnisse

  • Manuelle Allokation erfordert eine Freigabe zum richtigen Zeitpunkt.
  • Speicherlecks, Use-after-free, Double Free und Pufferüberläufe haben unterschiedliche Ursachen.
  • Verfolgende Speicherbereinigung findet nicht erreichbare Objekte; Referenzzählung reagiert auf wegfallende Referenzen.
  • CPython ergänzt die Referenzzählung um eine Zyklenerkennung.
  • Ownership kann die Freigabe ohne verfolgende Speicherbereinigung ermöglichen.
  • Automatische Speicherbereinigung kann Objekte nicht freigeben, die das Programm weiterhin behält.
4

Veränderlichen Zustand und zusammenarbeitende Objekte strukturieren

Eine Möglichkeit, ein Programm zu beschreiben, sind Befehle, die Werte aktualisieren. Eine andere fasst diese Werte mit den Operationen zusammen, die sie steuern. Beide Sichtweisen wirken oft zusammen: Eine Objektmethode kann eine gewöhnliche Schleife enthalten. Verfolge zuerst den veränderlichen Zustand und erstelle dann ein kleines Kontomodell. So siehst du, wie Klassen Regeln schützen und wie Vererbung, Polymorphie und Komposition verwandten Objekten eine gemeinsame Schnittstelle ermöglichen, ohne ihnen die falschen Zuständigkeiten zuzuweisen.

4.1 Befehle, Prozeduren und gemeinsamer Zustand

Ein Programmierparadigma ist eine Art, Code zu strukturieren und über Berechnungen nachzudenken. Bei der imperativen Programmierung gibt ein Programm Befehle, die seinen Zustand ändern: die Werte, die Variablen und Objekte aktuell enthalten.

Zustandsänderungen sichtbar machen

Eine laufende Summe ist eine einfache imperative Berechnung:

total = 0
for n in [3, 8, 5]:
    total = total + n

Der Anfangszustand ist total = 0. Die Addition von 3 ändert den Wert auf 3. Die Addition von 8 ändert ihn auf 11. Die Addition von 5 ändert ihn auf 16. Jede Zuweisung nutzt den Zustand, den der vorherige Schritt hinterlassen hat, sodass die Reihenfolge Teil des Verfahrens ist.

Dieser Stil entspricht den schrittweisen Änderungen, die der Prozessor an Registern und Speicher vornimmt. Er passt auch zu Algorithmen, die eine Sammlung gezielt an Ort und Stelle aktualisieren, etwa einer Sortierroutine, die Elemente vertauscht.

Aufgaben Namen geben

Prozedurale Programmierung gliedert imperativen Code in Funktionen oder Prozeduren. Ein Berichtsprogramm könnte read_scores(), average(scores) und print_report(avg) aufrufen. Die Namen lassen Lesende die Gesamtaufgabe erkennen, ohne sofort jeder internen Zeile folgen zu müssen. Jede Prozedur kann getrennt verstanden und getestet werden.

C, Pascal, Fortran und COBOL sind klassische prozedurale Sprachen. Auch Python unterstützt diesen Stil: Ein Skript kann benannte Funktionen verwenden, ohne Klassen zu definieren.

Strukturierte Programmierung ordnet den Kontrollfluss mit Sequenz, Verzweigung, Schleifen und Prozeduraufrufen. Sie vermeidet beliebige Sprünge, die die nächste ausgeführte Anweisung schwer vorhersehbar machen. Edsger Dijkstras Brief „Go To Statement Considered Harmful“ von 1968 trug zur Verbreitung dieses Ansatzes bei. Kontrollierte Ausstiege wie break, continue und return bleiben nützlich: Sie verlassen einen bestimmten Schleifendurchlauf, eine Schleife oder eine Funktion.

Wirkungen können über einen Rückgabewert hinausgehen

Ein Seiteneffekt verändert etwas über das von einer Funktion zurückgegebene Ergebnis hinaus. Eine Ausgabe, das Speichern einer Datei, das Senden einer Netzwerknachricht und das Ändern gemeinsam genutzter Daten sind Seiteneffekte. Programme benötigen solche Aktionen, um mit der Außenwelt zu interagieren.

Probleme entstehen, wenn nicht zusammengehöriger Code denselben Zustand unbemerkt verändert. Angenommen, apply_discount() senkt einen globalen price um 10 %, und add_shipping() addiert 5. Ausgehend von 100 ergibt erst der Rabatt 90 und anschließend der Versand 95. Zuerst der Versand ergibt 105 und anschließend der Rabatt 94,5. Beide Prozeduren führen ihre eigenen Schritte korrekt aus, aber das gemeinsame Ergebnis hängt von ihrer Reihenfolge ab.

Die Übergabe von Werten und die Rückgabe neuer Ergebnisse können diese Reihenfolge sichtbar machen:

price = add_shipping(apply_discount(100))

Der innere Aufruf läuft zuerst: Rabatt, dann Versand. Eine andere Verschachtelung drückt eine andere Regel aus. Ein klarer Datenfluss hilft zu erkennen, ob auch auf den Versand der Rabatt angewendet werden soll.

StrukturierungHauptfrage beim Lesen
Imperative BefehleWelcher Wert ändert sich als Nächstes?
Benannte ProzedurenWelche Aufgabe erfüllt dieser Aufruf?
Gemeinsame globale AktualisierungenWelche frühere Aktion hat diesen Wert geändert?

Imperativer Code ist innerhalb moderner Objektmethoden und Ereignisbehandlungsroutinen weiterhin verbreitet. Eine übergeordnete objektorientierte oder funktionale Gestaltung verhindert nicht, dass ein kleiner Teil direkte Befehle verwendet, wenn diese Schritte den Algorithmus verständlich machen.

Wichtige Erkenntnisse

  • Imperativer Code beschreibt Schritte, die gespeicherten Zustand ändern.
  • Prozeduren geben zusammengehörigen Aufgaben Namen und Grenzen.
  • Strukturierter Kontrollfluss macht Ausführungswege leichter nachvollziehbar.
  • Seiteneffekte umfassen Änderungen gemeinsam genutzter Daten und Interaktionen mit der Außenwelt.
  • Gemeinsame Aktualisierungen können Ergebnisse von der Aufrufreihenfolge abhängig machen.

4.2 Klassen halten Daten und ihre Regeln zusammen

Objektorientierte Programmierung (OOP) modelliert ein Programm als zusammenarbeitende Objekte. Jedes Objekt verbindet Daten mit Operationen auf diesen Daten. Ein Konto kann seinen Inhaber und Kontostand speichern und gleichzeitig Ein- und Auszahlungsoperationen anbieten.

Den Bauplan von seinen Instanzen unterscheiden

Eine Klasse legt die Attribute und Methoden fest, die ihre Objekte bereitstellen. Ein Objekt oder eine Instanz ist ein einzelnes konkretes Exemplar dieser Klasse. BankAccount ist der Bauplan; das Konto von Ana mit 150 und das Konto von Ben mit 40 sind verschiedene Instanzen. Ihre Kontostände können sich unabhängig voneinander ändern.

Ein Attribut speichert Daten, die zu einem Objekt gehören. Eine Methode ist eine Operation, die über ein Objekt aufgerufen wird, etwa acct.deposit(50). self in einer Python-Instanzmethode verweist auf das Objekt, für das der Aufruf erfolgt.

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("amount must be positive")
        self._balance = self._balance + amount

Der Aufruf BankAccount("Ana", 100) erzeugt eine neue Instanz. Ihre Methode __init__ initialisiert die Daten der Instanz. Ein Konstruktor richtet ein neues Objekt ein; Python trennt die Erzeugung von diesem Initialisierungsschritt. Der Aufruf acct.deposit(50) für dieses Konto ändert seinen Kontostand von 100 auf 150.

Die Regel an der Stelle der Aktualisierung platzieren

Datenkapselung bringt Daten und die Operationen zusammen, die ihre Regeln einhalten. Angenommen, ein Konto darf nie einen negativen Kontostand haben. Die Auszahlungsmethode prüft die vorgeschlagene Änderung, bevor sie sie ausführt:

def withdraw(self, amount):
    if amount <= 0:
        raise ValueError("amount must be positive")
    if amount > self._balance:
        raise ValueError("insufficient funds")
    self._balance = self._balance - amount

Diese Methode gehört in BankAccount. Bei einem Kontostand von 150 löst withdraw(200) einen Fehler aus und lässt 150 übrig. withdraw(30) gelingt und lässt 120 übrig. Der fehlgeschlagene Aufruf zieht nicht zuerst ab und entdeckt dann das Problem; die Prüfung erfolgt vor der Aktualisierung.

Anderer Code sollte diese Methoden verwenden, statt einen ungeprüften Kontostand zuzuweisen. Java und C# können den direkten Zugriff mit private einschränken. Pythons vorangestellter Unterstrich in _balance signalisiert gemäß einer Konvention ein internes Detail; er erzwingt keinen privaten Zugriff.

Das Geheimnisprinzip geht weiter: Aufrufer hängen von den Operationen ab, die eine Klasse anbietet, ohne ihre interne Darstellung kennen zu müssen. Der Kontostand könnte später anders gespeichert werden, während die öffentlichen Ein- und Auszahlungsoperationen ihre Bedeutung behalten.

Das Problem als Hinweis auf Zuständigkeiten nutzen

Ein Bibliothekssystem hat Bücher, Mitglieder und Ausleihen. Diese Nomen legen mögliche Klassen oder Attribute nahe; Aktionen wie Ausleihen und Verlängern legen Methoden nahe. Wähle für jede Klasse eine klare Zuständigkeit. Eine Kontoklasse, die außerdem jede E-Mail versendet und jeden Bericht formatiert, lässt sich schwerer ändern, weil nicht zusammengehörige Aufgaben vermischt werden.

Klassen und Objekte erschienen 1967 in Simula 67; Smalltalk trug in den 1970er-Jahren zur Verbreitung des Ansatzes bei. Java, C#, C++ und Python unterstützen OOP, aber die Verwendung einer dieser Sprachen zwingt nicht jedes Programm in Klassen. Die hilfreiche Entwurfsfrage lautet, welche Daten und Regeln zusammengehören.

Wichtige Erkenntnisse

  • Eine Klasse legt Attribute und Methoden fest; jede Instanz hat ihre eigenen Instanzdaten.
  • Methoden wirken auf das Objekt, für das sie aufgerufen werden.
  • Datenkapselung hält Prüfung und Zustandsänderungen zusammen.
  • Unterstriche kennzeichnen in Python gemäß einer Konvention interne Attribute; manche Sprachen erzwingen privaten Zugriff.
  • Das Geheimnisprinzip ermöglicht es Aufrufern, von den öffentlichen Operationen statt von den Interna der Klasse abzuhängen.
  • Eine Klasse sollte eine zusammenhängende Zuständigkeit haben.

4.3 Verhalten spezialisieren und Objekte kombinieren

Objekte müssen oft gemeinsame Operationen anbieten und sich zugleich in Einzelheiten unterscheiden. Vererbung, Polymorphie und Komposition lösen unterschiedliche Teile dieses Entwurfsproblems.

Eine bestehende Art von Objekt spezialisieren

Vererbung definiert eine Unterklasse aus einer Oberklasse. In Python gibt class SavingsAccount(BankAccount): der spezialisierten Klasse Zugriff auf das geerbte Kontoverhalten. Sie kann add_interest(rate) hinzufügen: Ein Kontostand von 1.000 wird durch eine Zinsgutschrift von 2 % zu 1.020.

Die beabsichtigte Beziehung ist eine Ist-ein-Beziehung. Ein Sparkonto ist weiterhin ein Bankkonto und muss die für Bankkonten erwarteten Regeln einhalten. Die Wiederverwendung einiger Zeilen reicht nicht aus, um zu behaupten, eine Art von Objekt sei eine andere.

Eine Unterklasse kann eine Methode mit ihrer eigenen Implementierung überschreiben. Angenommen, ein Girokonto berechnet für jede erfolgreiche Abhebung eine Gebühr von 1. Bei einem Kontostand von 100 kostet die Abhebung von 30 insgesamt 31 und lässt 69 übrig. Um die Kontoregel einzuhalten, muss die Methode die Abhebung zuzüglich der Gebühr prüfen, bevor sie den Kontostand ändert. Übersteigen diese Gesamtkosten den Kontostand, verweigert sie den gesamten Vorgang.

Einen Aufruf für unterschiedliche Objekte nutzbar machen

Polymorphie ermöglicht, dass dieselbe Operation für unterschiedliche Objekttypen funktioniert. Ein Kreis und ein Rechteck können beide area() anbieten:

for shape in shapes:
    print(shape.area())

Die Schleife benötigt kein if für jede Formklasse. Jedes Objekt stellt seine eigene Implementierung bereit. Die Auswahl dieser Implementierung während der Ausführung heißt dynamische Methodenbindung.

Für Circle(2) beträgt die Fläche ungefähr 12,57 Quadrateinheiten. Für Rectangle(3, 4) beträgt sie 12 Quadrateinheiten. Ihre gemeinsame Fläche beträgt ungefähr 24,57 Quadrateinheiten. Ein späteres Triangle kann der Sammlung beitreten, indem es eine kompatible Operation area() bereitstellt; die Schleife selbst benötigt keinen weiteren Zweig.

Eine Schnittstelle legt die Operationen fest, die ein Typ anbieten muss. Javas interface Shape { double area(); } verlangt von implementierenden Klassen, die Flächenmethode bereitzustellen. Eine abstrakte Klasse kann einen Teil der gemeinsamen Implementierung vorgeben und andere Methoden den Unterklassen überlassen. Python verwendet oft Duck-Typing: Ein Objekt kann in der Schleife funktionieren, wenn es die benötigte Operation bereitstellt, auch ohne gemeinsame Oberklasse.

Einen Teil enthalten, statt zu ihm zu werden

Komposition speichert andere Objekte als Teile. Ein Auto hat einen Motor und Räder. Es kann die Methode start() seines Motors aufrufen, ohne von Engine zu erben.

BeziehungGeeigneter EntwurfGrund
Sparkonto ist ein KontoVererbungDas erwartete Verhalten eines Kontos bleibt erhalten
Auto hat einen MotorKompositionDer Motor ist ein Bestandteil, nicht die Kategorie des Autos
Verschiedene Formen bieten eine FlächenberechnungGemeinsame Operation oder SchnittstelleDer Aufrufer kann die Operation einheitlich anfordern

Tiefe Vererbungsketten können die Auswirkungen einer Änderung der Oberklasse über viele Klassen verbreiten. Eine Unterklasse, die geerbtes Verhalten deaktivieren muss, kann auf eine ungeeignete Ist-ein-Beziehung hinweisen. Komposition hält solche Abhängigkeiten oft kleiner und übersichtlicher.

Die vier häufig genannten Säulen der OOP passen hier zusammen: Datenkapselung schützt Daten durch Operationen, Abstraktion verbirgt Implementierungsdetails, Vererbung spezialisiert einen Typ und Polymorphie ermöglicht einem Aufrufer, eine gemeinsame Operation für verschiedene Implementierungen zu nutzen.

Wichtige Erkenntnisse

  • Vererbung modelliert eine Ist-ein-Beziehung und muss erwartetes Verhalten erhalten.
  • Überschreiben ersetzt eine Methodenimplementierung in einer spezialisierten Klasse.
  • Polymorphie ermöglicht einer Operation, verschiedene Implementierungen aufzurufen.
  • Eine Schnittstelle legt erforderliche Operationen fest; Duck-Typing beruht auf den Operationen, die ein Objekt bereitstellt.
  • Komposition modelliert Objekte, die andere Objekte als Teile enthalten.
5

Neue Werte berechnen und gewünschte Ergebnisse beschreiben

Ein Programm kann sich auf die Umwandlung von Werten oder auf das Ergebnis konzentrieren, das eine Ausführungsumgebung erzeugen soll. Reine Funktionen machen Abhängigkeiten sichtbar, während unveränderliche Daten Änderungen über gemeinsam genutzte Referenzen vermeiden. Deklarative Abfragen lassen eine spezialisierte Ausführungsumgebung die Ausführungsschritte wählen. Verfolge eine Datenverarbeitungskette und eine Datenbankabfrage, um zu sehen, was diese Ansätze erleichtern und wo Eingabe, Ausgabe und konkrete Maschinenbefehle weiterhin eine Rolle spielen.

5.1 Reine Funktionen und Datenverarbeitungsketten

Funktionale Programmierung erzeugt Ergebnisse durch die Kombination von Funktionen. Ein zentraler Grundsatz ist, aus Eingaben neue Werte zu erzeugen und dabei gemeinsame Veränderungen zu begrenzen. Lisp führte um 1958 weitverbreitete funktionale Ideen ein; Haskell, Erlang, Clojure, F# und OCaml entwickeln diese Ideen auf unterschiedliche Weise weiter.

Erkennen, was eine Funktion rein macht

Eine reine Funktion liefert für dieselben Argumente dasselbe Ergebnis und hat keine Seiteneffekte. def with_tax(price): return price * 1.2 veranschaulicht die Idee: with_tax(50) ergibt 60,0. Eine Funktion, die außerdem etwas an eine globale Liste anhängt oder eine Nachricht ausgibt, hat eine Wirkung über ihren Rückgabewert hinaus. Ein Ergebnis, das von der aktuellen Uhrzeit abhängt, hängt ebenfalls von mehr als den Argumenten ab.

Ein reiner Aufruf kann durch seinen Rückgabewert ersetzt werden, ohne das Programmverhalten zu ändern. Diese Eigenschaft heißt referenzielle Transparenz. Sie erleichtert isolierte Tests und erlaubt es, unabhängige Aufrufe parallel auszuführen, ohne dass sie um die Aktualisierung eines gemeinsamen Zustands konkurrieren.

Unveränderlichkeit hält einen bestehenden Wert unverändert. [n + 1 for n in nums] erzeugt eine neue Liste und lässt nums unberührt. Ein Programm kann veränderliche Informationen weiterhin darstellen, indem es neue Versionen erzeugt; es muss die alte Version nicht überschreiben.

Verhalten als Daten übergeben

Funktionen sind Funktionen erster Klasse, wenn sie Namen zugewiesen, als Argumente übergeben und als Werte zurückgegeben werden können. Eine Funktion höherer Ordnung nimmt eine Funktion entgegen oder gibt eine zurück. Ein Lambda-Ausdruck ist ein unbenannter Funktionsausdruck, etwa lambda n: n * n.

Drei verbreitete Operationen höherer Ordnung bilden eine Verarbeitungskette:

  • filter behält Elemente, die einen Test erfüllen.
  • map wandelt jedes verbleibende Element um.
  • reduce fasst Elemente mithilfe eines Akkumulators zu einem Ergebnis zusammen.

Um die Quadrate der geraden Zahlen von 1 bis 6 zu summieren, bleiben nach dem Filtern 2, 4 und 6; das Quadrieren ergibt 4, 16 und 36; die Summe ist 56.

sum(map(lambda n: n * n,
        filter(lambda n: n % 2 == 0, range(1, 7))))

Hier prüft % 2 == 0, ob eine Zahl gerade ist, und range(1, 7) liefert 1 bis 6. Haskell drückt dieselbe Berechnung als sum (map (^2) (filter even [1..6])) aus.

Wird reduce aus Pythons functools importiert, kombiniert reduce(lambda acc, n: acc + n, [3, 8, 5], 0) zunächst 0 mit 3 zu 3, dann mit 8 zu 11 und schließlich mit 5 zu 16. Die Verarbeitungskette beschreibt Umwandlungen ohne eine umgebende Schleife, die eine gemeinsam genutzte Summe aktualisiert.

Eine Umgebung erhalten oder ein kleineres Problem aufrufen

def multiplier(k): return lambda x: k * x gibt eine Funktion zurück, die den Zugriff auf k behält. Daher gibt multiplier(3)(10) den Wert 30 zurück, auch nachdem der äußere Aufruf beendet ist. Die zurückgegebene Funktion und die erhaltene Umgebung bilden eine Closure.

Bei der Rekursion ruft sich eine Funktion für ein kleineres Problem selbst auf. Die Fakultät gibt bei n == 0 den Wert 1 zurück; andernfalls gibt sie n * factorial(n - 1) zurück. Für 5 ergeben die Produkte 120. Eine endliche Berechnung benötigt einen erreichbaren Basisfall. Nicht abgeschlossene verschachtelte Aufrufe lösen in Python schließlich einen RecursionError aus; Sprachen mit echter Endaufrufoptimierung können für Aufrufe an letzter Stelle Stapelspeicher wiederverwenden.

Die bloße Verwendung von Funktionen macht Code nicht funktional: Prozedurale Funktionen können den Zustand verändern. Tatsächliche funktionale Programme lesen weiterhin Eingaben und schreiben Ausgaben; häufig halten sie Effekte nahe an den Rändern des Programms und die zentralen Berechnungen rein.

Wichtige Erkenntnisse

  • Reine Funktionen hängen nur von ihren Argumenten ab und haben keine Seiteneffekte.
  • Aktualisierungen unveränderlicher Daten erzeugen neue Werte.
  • Funktionen erster Klasse können übergeben und zurückgegeben werden; Funktionen höherer Ordnung nehmen Funktionen entgegen oder geben sie zurück.
  • Filter wählt aus, Map wandelt um und Reduce kombiniert.
  • Eine Closure behält ihre Entstehungsumgebung.
  • Eine endliche rekursive Berechnung benötigt einen Basisfall, den sie tatsächlich erreicht.

5.2 Ein Ergebnis angeben und eine Ausführungsumgebung es finden lassen

Deklarative Programmierung beschreibt das gewünschte Ergebnis und überlässt die Ausführungsschritte einer Ausführungsumgebung der Sprache. Funktionaler Code wird gewöhnlich neben Abfragesprachen und logischer Programmierung unter diese allgemeine Idee gefasst.

Eine Abfrage als Anfrage lesen

Eine relationale Datenbank speichert Daten in Tabellen. Angenommen, students enthält die Zeilen mit Namen und Note Ana 92, Ben 75, Chen 80 und Dara 64:

SELECT name
FROM students
WHERE grade >= 80
ORDER BY name;

FROM nennt die Tabelle, WHERE gibt die Bedingung an, SELECT wählt das zurückgegebene Feld und ORDER BY verlangt alphabetische Reihenfolge. Ana und Chen erfüllen die Bedingung, einschließlich der Note von Chen, die genau 80 beträgt. Das Ergebnis sind ihre Namen in dieser Reihenfolge.

Ein imperatives Programm würde eine Ergebnisliste erstellen, Zeilen durchlaufen, jede Note prüfen, passende Namen anhängen und die Liste sortieren. SQL gibt der Datenbank das geforderte Ergebnis vor, ohne diese Schleife zu schreiben.

Ein Anfrageoptimierer wählt einen Ausführungsplan. Er könnte die Tabelle durchlaufen oder einen verfügbaren Index nutzen, eine Nachschlagestruktur, die geeignete Suchen beschleunigen kann. Viele Indizes verwenden sortierte Strukturen; andere nutzen andere Anordnungen. Das Hinzufügen eines Index kann den Plan ändern, ohne die Abfrage zu ändern. Mehr Daten bedeuten weiterhin Arbeit: Die Angabe des Ergebnisses garantiert nicht, dass jede Abfrage wenig Aufwand verursacht.

Fragen, was aus Fakten und Regeln folgt

Logische Programmierung beschreibt Fakten und Beziehungen und sucht dann nach Werten, die eine Frage erfüllen. In Prolog:

parent(tom, bob).
parent(bob, ann).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).

Die Regel bedeutet, dass X ein Großelternteil von Z ist, wenn es ein Y gibt, für das beide Elternbeziehungen gelten. Die Abfrage ?- grandparent(tom, Who). findet Who = ann: Tom ist ein Elternteil von Bob und Bob ein Elternteil von Ann. Großgeschriebene Namen stehen für Variablen; die kleingeschriebenen Namen bezeichnen bestimmte Personen.

Deklarative Werkzeuge in deiner Umgebung erkennen

Eine Tabellenkalkulationsformel =SUM(B2:B5) gibt eine Abhängigkeit an. Die Tabellenkalkulation berechnet sie neu, wenn sich relevante Zellen ändern. Der reguläre Ausdruck [0-9]{3}-[0-9]{4} beschreibt drei Ziffern, einen Bindestrich und vier Ziffern, wie in „555-0199“. Eine Suchfunktion für Textmuster führt die Suche aus.

CSS legt das Aussehen mit einer Regel wie h1 { color: navy; } fest; HTML zeichnet die Seitenstruktur aus. Es sind deklarative Notationen, die gewöhnlich als Stylesheet- und Auszeichnungssprachen eingeordnet werden. Ihre Aufgabe unterscheidet sich von der einer Allzwecksprache, die ein breites Spektrum von Berechnungen ausdrücken kann.

Art der SpracheSchwerpunktBeispiele
Domänenspezifische SpracheEine bestimmte Art von AufgabeSQL, reguläre Ausdrücke, CSS
AllzweckspracheViele Arten von ProgrammenPython, Java, Haskell

Deklarativer Code kann nahe an einer Problemstellung bleiben und einer Ausführungsumgebung ermöglichen, ihr Verfahren zu verbessern. Er gibt dem Programmierer auch weniger direkte Kontrolle über einzelne Schritte, wodurch sich ein langsamer Plan schwerer untersuchen lässt. Darunter werden weiterhin konkrete Befehle ausgeführt: Deklarativ beschreibt, wie der Programmierer die Aufgabe ausdrückt, nicht einen Prozessor, der ohne Schritte arbeitet.

Wichtige Erkenntnisse

  • Deklarativer Code gibt ein gewünschtes Ergebnis an; eine Ausführungsumgebung wählt die Ausführungsschritte.
  • SQL wählt Zeilen und Felder anhand von Bedingungen und einer verlangten Reihenfolge aus.
  • Ein Anfrageoptimierer kann einen Abfrageplan ändern, ohne die Abfrage zu ändern.
  • Logische Programmierung leitet Antworten aus Fakten und Regeln ab.
  • Domänenspezifische Sprachen sind auf bestimmte Aufgaben ausgerichtet; Allzwecksprachen unterstützen viele Arten von Programmen.
6

Einen Stil und eine Sprache für die Aufgabe wählen

Unterschiedliche Programmierstile können dieselbe Antwort liefern und die Arbeit dabei unterschiedlich strukturieren. Ein Vergleich anhand einer Aufgabe macht ihre Vor- und Nachteile leichter erkennbar. Bei der Sprachwahl kommen praktische Rahmenbedingungen hinzu: die Zielplattform, verfügbare Bibliotheken, Speicher- und Geschwindigkeitsvorgaben, Sicherheitsanforderungen und die Menschen, die den Code warten. Ein Projekt mit mehreren Teilen kann für jeden Teil eine andere Sprache und eine andere Mischung von Stilen verwenden.

6.1 Eine Aufgabe in mehreren Programmierstilen

Ein Paradigma ändert, wie eine Lösung ausgedrückt wird, nicht welches numerische Ergebnis eine korrekte Lösung liefern soll. Betrachte Bestellungen mit den Beträgen [120, 45, 300, 80]: Gesucht ist die Summe der Beträge, die echt größer als 100 sind.

Zustandsänderungen verfolgen

Die imperative Version zeigt die Schleife und die Akkumulatorvariable:

total = 0
for amount in amounts:
    if amount > 100:
        total = total + amount

Der erste Betrag ändert total auf 120. Der zweite lässt den Wert unverändert. Der dritte ändert ihn auf 420, und der vierte lässt ihn dort. Das Programm macht die Reihenfolge von Prüfung und Aktualisierung sichtbar.

Eine Sammlung umwandeln

Ein kompakter Python-Ausdruck im funktionalen Stil ist sum(a for a in amounts if a > 100). Er wählt 120 und 300 aus und fasst sie zu 420 zusammen. Eine andere Version ist sum(filter(lambda a: a > 100, amounts)). Hier beschreibt das Prädikat die Zugehörigkeit, und sum kombiniert die ausgewählten Werte.

Ein Generatorausdruck oder filter-Aufruf nutzt Pythons Multiparadigmenfunktionen; sein Vorkommen macht nicht jeden umgebenden Teil der Anwendung rein.

Regeln bei Objekten unterbringen

In einem objektorientierten Entwurf speichert ein Order ein amount und bietet is_large() an, das self.amount > 100 zurückgibt. Ein OrderBook enthält Bestellobjekte und bietet large_total() an. Diese Methode kann Bestellungen durchlaufen, bei jeder prüfen, ob sie die Bedingung erfüllt, und passende Beträge addieren.

Die Summe bleibt 420. Der Unterschied liegt darin, wo die Zuständigkeiten liegen: Die Regel für eine große Bestellung gehört zu Order, und die Arbeit auf Ebene der Sammlung gehört zu OrderBook. Andere Funktionen können die Regel wiederverwenden, ohne ihre Schwellenwertprüfung zu kopieren.

Die Datenbank fragen

Liegen die Daten bereits in einer relationalen Datenbank, kann SQL die Summe anfordern:

SELECT SUM(amount)
FROM orders
WHERE amount > 100;

Die Datenbank wählt den Plan für Abruf und Aggregation. Die Abfrage weist keinen Aufrufer an, jede Zeile zu durchlaufen oder eine lokale Akkumulatorvariable zu führen.

StilWas das Beispiel hervorhebt
ImperativGeordnete Prüfungen und Aktualisierungen einer Summe
ObjektorientiertBestellungen und Sammlungsobjekte mit festgelegten Zuständigkeiten
FunktionalAuswahl und Kombination von Werten
Deklaratives SQLDas geforderte Ergebnis über gespeicherte Daten
Stile dort kombinieren, wo sie helfen

Python unterstützt prozedurale, objektorientierte und funktionale Techniken. JavaScript bietet Objekte, Funktionen erster Klasse und Closures. Auch C++, C#, Kotlin, Scala, Swift und Rust mischen Stile. Java wird stark mit OOP verbunden, unterstützt aber seit Java 8 im Jahr 2014 Lambda-Ausdrücke; Haskell ist rein funktional.

Eine Webanwendung kann Klassen für Benutzer und Bestellungen, funktionale Umwandlungen für Berichtsdaten, imperative Aktualisierungen innerhalb von Methoden und SQL für Datenbankabfragen verwenden. Diese Entscheidungen wirken auf verschiedenen Ebenen.

Ereignisgesteuerte Programmierung ergänzt eine weitere Organisationsidee: Registriere eine Behandlungsroutine, die läuft, wenn ein Ereignis eintrifft. In JavaScript übergibt button.addEventListener("click", showMenu) die Funktion showMenu als Funktionswert. Ein späterer Klick ruft sie auf. Die Behandlungsroutine kann selbst Objekte, reine Umwandlungen oder imperative Schritte verwenden. Ihre ereignisgesteuerte Rolle sagt aus, wann sie läuft, legt aber nicht den einzigen möglichen Stil für ihren Rumpf fest.

Wichtige Erkenntnisse

  • Korrekte Lösungen in verschiedenen Paradigmen können dasselbe Ergebnis berechnen.
  • Ein Stil verändert, wo Schritte, Zustand, Regeln und Zuständigkeiten auftreten.
  • Moderne Sprachen unterstützen gewöhnlich mehrere Paradigmen.
  • Eine Ereignisbehandlungsroutine läuft als Reaktion auf ein Ereignis und kann intern andere Stile verwenden.

6.2 Unter Nebenbedingungen und Zielkonflikten wählen

Keine Programmiersprache passt für jedes Projekt am besten. Allzwecksprachen sind gewöhnlich Turing-vollständig: Mit unbegrenzter Zeit und unbegrenztem Speicher können sie dieselbe Klasse von Berechnungen ausdrücken. Die tatsächliche Wahl hängt davon ab, ob eine Lösung zur jeweiligen Plattform, den Ressourcen, Werkzeugen und Menschen passt.

Mit dem beginnen, was funktionieren muss

Die Plattform ist oft eine feste Nebenbedingung. Browser führen JavaScript aus und können kompilierte Module über WebAssembly ausführen. TypeScript ergänzt statische Prüfungen und wird zu JavaScript kompiliert. Swift ist eine Standardwahl für Apps auf Apple-Plattformen; Kotlin wird für Android empfohlen. SQL ist die übliche Sprache für relationale Abfragen.

Betrachte als Nächstes das Anwendungsgebiet und das Ökosystem. Python hat weitverbreitete Bibliotheken für Datenverarbeitung und maschinelles Lernen wie NumPy und pandas. R konzentriert sich auf Statistik. C und C++ sind bei Betriebssystemen, Spiele-Engines und eingebettetem Code verbreitet. Java und C# kommen häufig in großen Geschäftssystemen vor; Bash ist nützlich, um Betriebssystemaufgaben zu automatisieren.

Dies sind praktische Zuordnungen, keine exklusiven Einsatzgebiete. Eine geeignete Bibliothek kann weit mehr Entwicklungsarbeit sparen als ein kleiner Unterschied in der Syntax der Sprache.

Ressourcen- und Sicherheitsanforderungen untersuchen

Ein Mikrocontroller mit nur einigen Kilobyte Arbeitsspeicher benötigt möglicherweise genaue Kontrolle über den Speicher und den zusätzlichen Laufzeitaufwand. C, C++ oder Rust können geeignet sein, wenn die Zielplattform sie unterstützt. Auch anspruchsvolle Komponenten von Spielen oder Datenbanken sprechen oft für diese Sprachen, aber Ausführungsumgebungen und Ressourcenbudgets unterscheiden sich: Unity verwendet C#, und einige Datenbanken verwenden Java oder Go.

Bevor du nach Geschwindigkeit auswählst, finde den Engpass: den Teil, der die Leistung begrenzt. Ein Programm, das hauptsächlich auf eine Netzwerkantwort wartet, wird nicht allein deshalb erheblich schneller, weil eine kleine lokale Schleife effizienter kompiliert wird. Miss repräsentative Arbeitslasten auf der Zielplattform.

Speichersicherheit verringert Fehler wie ungültige Speicherzugriffe. Sicheres Rust prüft Ownership-Regeln zur Übersetzungszeit; verwaltete Sprachen wie Java, C#, Go und Python bieten Speicherverwaltung zur Laufzeit. Statische Typisierung erkennt viele unverträgliche Operationen vor der Ausführung, was bei einer großen Codebasis helfen kann. Weder Speichersicherheit noch statische Typisierung beweist, dass die Logik des Programms korrekt ist.

Die Kosten für die Wartung des Ergebnisses einbeziehen

Das Wissen des Teams, verfügbare Entwickler, Programme zur Fehlerbehebung, Testwerkzeuge, Bibliotheken, Lizenzen und vorhandener Code beeinflussen die Entscheidung. Eine vertraute, unterstützte Sprache kann die Kosten für Bereitstellung und Wartung senken. Ein funktionierendes System neu zu schreiben verursacht Migrationsarbeit und Risiken, selbst wenn die neue Sprache attraktive Eigenschaften hat.

Bei einem städtischen Wetternetz haben verschiedene Komponenten unterschiedliche Anforderungen:

KomponenteVernünftige SprachwahlGrund
Firmware kleiner SensorenC oder RustKontrolle über den Speicher und unterstützte Hardwareziele
Gespeicherte MesswerteSQLFiltern und Aggregieren in der Datenbank
Bereinigung und AnalysePythonBibliotheken zur Datenverarbeitung
Interaktive Anzeige im BrowserJavaScript oder TypeScriptBrowserunterstützung und Werkzeuge für Oberflächen

Eine nützliche Begründung verknüpft jede Wahl mit einer Anforderung: „Das Team wartet den Java-Dienst bereits, und seine Bibliotheken unterstützen die neue Funktion.“ Ein Beliebtheitsrang allein kann diese Begründung nicht liefern. Umfragen können auf eine große Gemeinschaft oder viele verfügbare Fachkräfte hinweisen; sie belegen nicht, welche Sprache für eine bestimmte Arbeitslast am besten ist.

Wichtige Erkenntnisse

  • Prüfe die Nebenbedingungen der Plattform vor persönlichen Vorlieben.
  • Bibliotheken und Werkzeuge verbinden eine Sprache mit ihrem Anwendungsgebiet.
  • Miss den Engpass, bevor du hauptsächlich nach Geschwindigkeit auswählst.
  • Speichersicherheit und statische Prüfung verringern einige Fehler, während die Logik weiterhin getestet werden muss.
  • Die Fähigkeiten des Teams, vorhandener Code und Wartungskosten gehören zur Entscheidung.
  • Unterschiedliche Komponenten können unterschiedliche Sprachen rechtfertigen.

Programmierparadigmen: Beispiele

6 Interaktive Lektionen

1

Verfolge ein Programm vom Quellcode bis zur Ausführung

13 Schritte
Verfolge Übersetzungsebenen, untersuche eine Programmerstellung und vergleiche Ausführungswege zur Laufzeit.
  1. 1
    EinführungEine Berechnung durchläuft mehrere Schichten

    Du schreibst eine Berechnung, aber ein Prozessor benötigt kleine kodierte Befehle, den sogenannten Maschinencode. Ein Übersetzungsprogramm ergänzt die fehlenden Einzelheiten.

    Verfolge x = a + b vom Quellcode aus zu den unteren Ebenen und das Programm anschließend vorwärts durch Erstellung und Ausführung. Halte zwei Fragen getrennt: In welcher Form liegt der Code vor, und welche Software führt ihn aus? Ein Compiler, ein Interpreter und ein JIT-Compiler beantworten die zweite Frage unterschiedlich. Diese Unterscheidung erklärt sowohl die Portierbarkeit als auch den Zeitpunkt von Fehlern.

  2. 2
    BeispielEine Berechnung wird zu mehreren Aktionen

    Stell dir für x = a + b vier Aktionen auf einer niedrigeren Ebene vor:

    1. Den Wert von a in ein Register laden.
    2. Den Wert von b in ein anderes Register laden.
    3. Die beiden Registerwerte addieren.
    4. Das Ergebnis für x speichern.

    Die Anweisung der höheren Programmiersprache verbirgt die Registerauswahl und Speicherungsdetails. Die tatsächlich erzeugten Befehle können anders aussehen, weil ein Compiler die Arbeit optimieren kann. Die geforderte Berechnung bleibt dieselbe.

  3. 3
    ErklärungLesbare Befehle bleiben auf einen Prozessor ausgerichtet

    Assemblersprache verwendet Befehlsnamen wie mov und add. Ein Assembler kodiert sie als Maschinenbefehle.

    Für x86-64 schreibt mov eax, 1 die Zahl 1 in EAX; add eax, ebx addiert EBX dazu. Diese Register und Kodierungen gehören zu dieser Befehlssatzarchitektur. Bytes durch lesbare Namen zu ersetzen hilft Menschen, macht die Befehle aber nicht zu einer portierbaren Beschreibung auf höherer Ebene.

  4. 4
    HerausforderungDas Programm auf einen anderen Prozessor übertragen

    Ein Team hat portierbaren C-Quellcode und eine native ausführbare x86-64-Datei. Es benötigt eine native ausführbare ARM-Datei. Welches Artefakt kann es als Ausgangspunkt verwenden, und welche Arbeit ist nötig?

    ---reveal---

    Verwende den Quellcode und kompiliere ihn mit der erforderlichen Plattformunterstützung für das ARM-Ziel. Die ausführbare x86-64-Datei enthält Befehle für eine andere ISA. Portierbarer Quellcode ermöglicht eine weitere Übersetzung; er macht diese vorhandenen Maschinenbefehle nicht zu nativem ARM-Code.

  5. 5
    BeispielToken vor der Struktur erkennen

    Betrachte total = price * 3; in C.

    Die lexikalische Analyse findet sechs Token: total, =, price, *, 3 und ;. Die syntaktische Analyse gruppiert anschließend price * 3 als den Ausdruck, dessen Ergebnis total zugewiesen wird.

    Dies sind unterschiedliche Aufgaben. Die Zerlegung in Token erkennt die Teile; die syntaktische Analyse stellt fest, wie sie zusammenpassen. Die semantische Analyse kann dann Fragen prüfen, etwa ob die Namen deklariert und die Operandentypen verträglich sind.

  6. 6
    BeispielBerechnungen mit Konstanten in die Erstellung verlegen

    Der Quellcodeausdruck 60 * 60 * 24 verwendet nur Konstanten. Konstantenfaltung kann ihn vor der Ausführung auswerten: zuerst 3.600, dann 86.400.

    Die Codegenerierung kann dieses Ergebnis statt Befehlen verwenden, die die Multiplikationen wiederholen. Dies ist eine Optimierung, weil sie die Ausführung der Arbeit ändert und dabei das geforderte Ergebnis erhält. Einen Wert durch eine lediglich zweckmäßige Näherung zu ersetzen wäre durch diese Regel nicht gerechtfertigt.

  7. 7
    ErklärungÜbersetzte Dateien mit Bibliotheksroutinen verbinden

    Die Kompilierung kann separate Objektdateien erzeugen. Ein Linker verbindet diese Dateien und löst Referenzen auf Bibliotheksoperationen auf.

    Statisches Binden nimmt Bibliothekscode in die erstellte ausführbare Datei auf. Dynamisches Binden ist auf eine kompatible gemeinsam genutzte Bibliothek angewiesen, die später geladen wird. Keiner der beiden Ansätze verlangt den Ahead-of-time-Compiler auf dem Computer des Benutzers, aber eine dynamisch gebundene ausführbare Datei benötigt weiterhin ihre Laufzeitabhängigkeiten.

  8. 8
    HerausforderungEine erfolgreiche Erstellung liefert das falsche Ergebnis

    Ein C-Programm wird erfolgreich erstellt. Sein Bericht soll eine verbrauchte Menge von einer Ausgangsmenge abziehen, aber der Code addiert sie. Hat der Compiler eine Sprachregel nicht geprüft?

    ---reveal---

    Nein. Eine Addition kann gültige Syntax mit verträglichen Typen sein und dabei die falsche Aufgabe ausdrücken. Das ist ein logischer Fehler. Der Compiler prüft Sprachregeln und erzeugt Code; ein Test mit bekanntem erwartetem Ergebnis ist nötig, um die Abweichung zwischen der Berechnung und ihrem beabsichtigten Zweck aufzudecken.

  9. 9
    ErklärungEine Laufzeitumgebung kann Zwischenbefehle ausführen

    CPython übersetzt eine gültige Python-Datei in Bytecode und führt diesen anschließend in seiner virtuellen Maschine aus. Der Prozessor führt die Maschinenbefehle der Laufzeitumgebung aus, während die Laufzeitumgebung die Python-Operationen ausführt.

    Auch Java verwendet Bytecode: javac erzeugt Klassendateien für eine kompatible JVM. Bytecode gehört zu einem Befehlsmodell einer Laufzeitumgebung und sollte daher nicht mit einem universellen Prozessorbefehlssatz verwechselt werden.

  10. 10
    BeispielZwei Zeitpunkte vergleichen, an denen die Ausführung stoppen kann

    In dieser gültigen Python-Datei wird die erste Zeile ausgeführt:

    print("start")
    x = 10
    print(x + "5")
    

    Sie gibt „start“ aus, bevor die letzte Zeile einen TypeError auslöst. Entferne nun die letzte schließende Klammer: CPython kann die Datei nicht übersetzen, sodass keine Anweisung in dieser Datei ausgeführt wird. Ein Syntaxfehler bei der Vorbereitung und ein Typfehler während einer ausgeführten Operation stoppen das Programm zu unterschiedlichen Zeitpunkten.

  11. 11
    ErklärungHäufig ausgeführten Code während des Laufs kompilieren

    Ein JIT-Compiler erzeugt Maschinencode, während das Programm läuft. Eine Laufzeitumgebung kann zunächst interpretieren, häufig verwendete Methoden beobachten und diese Methoden kompilieren.

    Das verschiebt den Aufwand: Die Kompilierung benötigt während des Laufs Zeit und Speicher, während die wiederholte Ausführung danach schneller werden kann. Ein lange laufender Dienst kann von dieser Aufwärmphase stärker profitieren als ein winziges einmaliges Skript. Das Ergebnis hängt von der Implementierung und der Arbeitslast ab.

  12. 12
    HerausforderungPortierbarkeit von Bytecode und native Geschwindigkeit unterscheiden

    Eine Java-Klassendatei läuft auf zwei unterschiedlichen ISAs über eine kompatible JVM. Während jedes Laufs kompiliert die Laufzeitumgebung eine häufig verwendete Methode. Warum kann eine Klassendatei gemeinsam genutzt werden, während sich der erzeugte Maschinencode unterscheidet?

    ---reveal---

    Die Klassendatei enthält JVM-Bytecode, den beide kompatiblen Laufzeitumgebungen verstehen. Jeder JIT-Compiler ist auf seinen eigenen Prozessor ausgerichtet. Die Portierbarkeit entsteht durch das gemeinsame Zwischenformat und kompatible Laufzeitumgebungen; die während der Ausführung erzeugten Maschinenbefehle bleiben plattformspezifisch.

  13. 13
    ZusammenfassungEbenen und Ausführungswege getrennt halten

    Maschinencode ist auf eine ISA ausgerichtet; Assemblersprache gibt ihren Befehlen lesbare Namen; Quellcode einer höheren Programmiersprache verbirgt mehr Hardwaredetails.

    Ein Compiler übersetzt Code, ein Interpreter führt ihn aus, und ein JIT-Compiler kompiliert während der Ausführung. Eine Laufzeitumgebung kann diese Methoden kombinieren. Um ein tatsächliches System zu erklären, benenne seine Quell-, Zwischen- und nativen Formen und gib dann an, welche Phase die Syntax prüft und welche die Operation ausführt, die fehlschlagen kann.

2

Verfolge die Bedeutung in einem kurzen Programm

10 Schritte
Lies Grammatik, Auswertungsreihenfolge, Zustandsänderungen und Funktionsgrenzen.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungForm und Bedeutung beantworten unterschiedliche Fragen
  2. 2
    BeispielGültiger Code kann die falsche Berechnung gruppieren
  3. 3
    ErklärungDie erste Bedingung kann die zweite schützen
  4. 4
    HerausforderungSage vorher, ob die Division ausgeführt wird
  5. 5
    ErklärungEine Zuweisung liest vor der Aktualisierung
  6. 6
    BeispielVollziehe jede Aktualisierung der Akkumulatorvariable nach
  7. 7
    BeispielVerfolge einen Aufruf und seinen Rückgabewert
  8. 8
    HerausforderungEine lokale Aktualisierung kann den Aufrufer unverändert lassen
  9. 9
    HerausforderungVerfolge Grammatik und Zustand gemeinsam
  10. 10
    ZusammenfassungLies den ausgeführten Weg
3

Verfolge Typen und gemeinsam genutzte Objekte

14 Schritte
Vergleiche Prüfzeitpunkte, verfolge Aliase und entscheide, wann Speicher freigegeben werden kann.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungVerfolge Werte, Objekte und Speicher
  2. 2
    ErklärungTypisierte Werte können zwischen Namen wechseln
  3. 3
    KernaussageInferenz und Umwandlung beantworten unterschiedliche Fragen
  4. 4
    HerausforderungLies Ziffern als den tatsächlich zurückgegebenen Wert
  5. 5
    ErklärungDie Darstellung beeinflusst numerische Ergebnisse
  6. 6
    ErklärungEin Aufrufrahmen kann vor einem Objekt enden
  7. 7
    BeispielZwei Namen verweisen auf eine Liste
  8. 8
    HerausforderungEin Parameter kann ein Objekt verändern und danach auf ein anderes verweisen
  9. 9
    ErklärungBestimme genau, wie Speicher falsch verwendet wird
  10. 10
    BeispielVerfolge Referenzen, bis die letzte verschwunden ist
  11. 11
    KernaussageEin Zyklus kann ohne aktiven Zugangspunkt bestehen
  12. 12
    ErklärungOwnership prüft Zugriffe vor der Ausführung
  13. 13
    HerausforderungFinde heraus, warum die Speicherbereinigung eine Liste nicht freigeben kann
  14. 14
    ZusammenfassungVerfolge drei unterschiedliche Entscheidungen
4

Schütze Regeln und wähle Beziehungen zwischen Objekten

12 Schritte
Verfolge reihenfolgeabhängige Befehle und nutze anschließend Datenkapselung, Überschreiben und Komposition.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungSchritte brauchen verlässliche Grenzen
  2. 2
    BeispielDie Reihenfolge der Operationen ändert den gemeinsamen Zustand
  3. 3
    ErklärungGestalte die Grenze einer Prozedur sinnvoll
  4. 4
    HerausforderungSage eine Aktualisierung in anderer Reihenfolge vorher
  5. 5
    ErklärungGib jedem Konto einen eigenen Zustand
  6. 6
    BeispielPrüfe, bevor du einen Kontostand änderst
  7. 7
    HerausforderungFinde eine umgangene Regel
  8. 8
    BeispielSpezialisiere, ohne den Vertrag zu verletzen
  9. 9
    ErklärungLass das Objekt seine Flächenmethode wählen
  10. 10
    VerbindungEnthalte die Komponente, die die Arbeit erledigt
  11. 11
    HerausforderungWähle zwei Beziehungen in einem neuen Entwurf
  12. 12
    ZusammenfassungStimme Grenzen auf Zuständigkeiten ab
5

Wandle eine Sammlung um und frage ein Ergebnis ab

13 Schritte
Verfolge eine funktionale Verarbeitungskette und eine deklarative Abfrage und prüfe dabei Effekte und Ausführung.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungGib eine Umwandlung oder eine Anfrage an
  2. 2
    ErklärungReinheit beschreibt mehr als die zurückgegebene Zahl
  3. 3
    BeispielWähle zuerst die geraden Werte aus
  4. 4
    BeispielWandle die ausgewählten Werte um und kombiniere sie
  5. 5
    HerausforderungWende die Verarbeitungskette auf einen neuen Fall an
  6. 6
    ErklärungGib Verhalten zurück, das sich seinen Faktor merkt
  7. 7
    BeispielVerkleinere das Problem, bis ein direkter Fall gilt
  8. 8
    BeispielLies SQL als Anfrage über Zeilen
  9. 9
    KernaussageBehalte das Ergebnis bei, während sich der Plan ändert
  10. 10
    BeispielVerbinde zwei Fakten durch eine Regel
  11. 11
    HerausforderungLies eine neue Abfrage einschließlich ihres Schwellenwerts
  12. 12
    HerausforderungKombiniere eine Abfrage mit einer reinen Umwandlung
  13. 13
    ZusammenfassungHalte die Beziehung zwischen Eingabe und Ergebnis sichtbar
6

Vergleiche Stile und begründe eine Sprachwahl

10 Schritte
Halte eine Aufgabe fest und wähle dann Werkzeuge für Komponenten mit unterschiedlichen Nebenbedingungen.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungHalte die Aufgabe fest
  2. 2
    BeispielHalte die Aufgabe fest und ändere den Stil
  3. 3
    BeispielPlatziere dieselbe Regel in Objekten oder einer Abfrage
  4. 4
    HerausforderungEntscheide, ob ein Entwurf nur einen Stil hat
  5. 5
    ErklärungSchließe zuerst nicht unterstützte Ziele aus
  6. 6
    BeispielWähle für ein vollständiges Wettersystem
  7. 7
    TippMiss, was die Zeit verbraucht
  8. 8
    HerausforderungBegründe eine Wahl bei konkurrierenden Nebenbedingungen
  9. 9
    HerausforderungVerbinde die Sprachwahl mit der Programmstruktur
  10. 10
    ZusammenfassungGib einen Grund an, der zur Anforderung passt

Programmierparadigmen: Übungsaufgaben

50 Übungsfragen in 5 Sets

Set 1: Lies Code und erkenne den Ausführungsweg10 Übungsfragen

Frage 1 von 10

Ein Entwickler ersetzt direkte Befehlsbytes durch Namen wie mov und jmp. Auf welcher Sprachebene ist das Programm nun geschrieben?
  1. Maschinencode
  2. Assemblersprache
  3. Eine höhere Programmiersprache
  4. Eine Datenbankabfragesprache
Antwort zeigenAntwort ausblenden
Antwort: B
Assemblersprache schreibt Operationen auf Prozessorebene mit Mnemonics und Registernamen und macht sie dadurch für Menschen leichter lesbar. Sie bleibt an eine Befehlssatzarchitektur gebunden. Lesbare Namen allein machen sie nicht zu einer höheren Programmiersprache, weil der Programmierer weiterhin einzelne maschinennahe Operationen vorgibt.

Frage 2 von 10

Bei der Programmerstellung verbindet ein Werkzeug mehrere Objektdateien und löst einen Aufruf einer Bibliotheksroutine auf. Welches Werkzeug führt diese Aufgabe aus?
  1. Ein Assembler
  2. Ein Parser
  3. Ein Interpreter
  4. Ein Linker
Antwort zeigenAntwort ausblenden
Antwort: D
Ein Linker verbindet übersetzte Teile und löst Referenzen auf Code auf, der an anderer Stelle definiert ist, einschließlich Bibliotheksroutinen. Ein Compiler kann die Objektdateien zuvor bei der Erstellung erzeugt haben. Die syntaktische Analyse prüft die Struktur des Quellcodes; sie verbindet die entstehenden Dateien nicht zu einem vollständigen Programm.

Frage 3 von 10

Eine Python-Datei enthält in der ersten Zeile eine Ausgabeanweisung und in der letzten Zeile eine nicht geschlossene Klammer. Was geschieht, wenn CPython die Datei ausführt?
  1. Es meldet einen Syntaxfehler vor der Ausführung der Ausgabe.
  2. Es führt die erste Ausgabe aus und meldet dann einen Syntaxfehler.
  3. Es führt die erste Ausgabe aus und meldet dann einen Laufzeitfehler.
  4. Es überspringt nur die letzte Zeile und führt die gültigen Zeilen aus.
Antwort zeigenAntwort ausblenden
Antwort: A
CPython muss die Datei übersetzen, bevor es ihre Anweisungen ausführt, und die nicht geschlossene Klammer verhindert diese Vorbereitung. Die erste Ausgabe wird daher nie ausgeführt. Eine Ausgabe vor dem Fehler würde zu einem Laufzeitfehler in gültigem Code passen, aber diese Datei hat einen Syntaxfehler, der früher gefunden wird.

Frage 4 von 10

Ein gültiges Python-Programm gibt den falschen Durchschnitt aus, weil es einen Wert addiert, nachdem es den anderen halbiert hat. Welche Art von Fehler ist das?
  1. Ein Syntaxfehler
  2. Ein Laufzeitfehler
  3. Ein logischer Fehler
  4. Ein Typfehler
Antwort zeigenAntwort ausblenden
Antwort: C
Die Operationen werden vollständig ausgeführt, aber ihre Bedeutung entspricht nicht dem beabsichtigten Durchschnitt. Damit ist es ein logischer Fehler. Ein Syntaxfehler würde gegen die Grammatik der Sprache verstoßen, während bei einem Laufzeitfehler eine ausgeführte Operation fehlschlagen würde; beides ist bei dieser erfolgreichen, aber falschen Berechnung nicht angegeben.

Frage 5 von 10

Welchen Endwert speichert dieser Python-Code in total?

total = 1
for n in [4, 7, 2]:
    total = total + n
  1. 13
  2. 3
  3. 15
  4. 14
Antwort zeigenAntwort ausblenden
Antwort: D
Die Summe beginnt bei 1 und wird nach der Addition von 4 zu 5, nach der Addition von 7 zu 12 und nach der Addition von 2 zu 14. Die Schleife behält das Ergebnis jeder vorherigen Aktualisierung. Das Ergebnis 13 würde den Anfangswert ignorieren und nur die Summe der Liste berechnen, was nicht der gesamten hier geschriebenen Berechnung entspricht.

Frage 6 von 10

In Python folgt auf label = "18" die Zuweisung label = 18. Der Typ, auf den der Name verweist, kann sich ändern, aber "18" + 2 löst einen Fehler aus. Wie werden diese beiden Tatsachen beschrieben?
  1. Statische Typisierung mit strengen Regeln für die Addition gemischter Typen
  2. Dynamische Typisierung mit strengen Regeln für die Addition gemischter Typen
  3. Dynamische Typisierung mit automatischer numerischer Addition
  4. Statische Typisierung mit automatischer numerischer Addition
Antwort zeigenAntwort ausblenden
Antwort: B
Einen Namen von Text an eine Ganzzahl neu zu binden, entspricht dynamischer Typisierung. Die fehlgeschlagene Addition zeigt dagegen, dass diese Operation den Text nicht stillschweigend in eine Zahl umwandelt. Prüfzeitpunkt und Umwandlungsverhalten sind getrennte Fragen. Dynamische Typisierung bedeutet nicht, dass unverträgliche Typen automatisch kombiniert werden.

Frage 7 von 10

Was enthält first, nachdem dieser Python-Code ausgeführt wurde?

first = [4, 8]
second = first
second.append(6)
  1. [4, 8]
  2. [6]
  3. [4, 8, 6]
  4. [4, 8, [6]]
Antwort zeigenAntwort ausblenden
Antwort: C
Beide Namen verweisen auf eine Liste, weil die Zuweisung der Referenz deren Inhalt nicht kopiert. Das Anhängen über second verändert diese gemeinsam genutzte Liste, sodass auch first die hinzugefügte 6 zeigt. Damit [4, 8] erhalten bliebe, wäre eine separate Liste nötig, etwa durch Kopieren vor dem Anhängen.

Frage 8 von 10

Ein Berichtsprogramm ist in read_data(), calculate_total() und save_report() aufgeteilt, die jeweils geordnete Befehle enthalten. Welche Struktur veranschaulicht dies?
  1. Prozedurale Programmierung
  2. Logische Programmierung
  3. Reine funktionale Programmierung
  4. Vererbung von Klassen
Antwort zeigenAntwort ausblenden
Antwort: A
Prozedurale Programmierung fasst imperative Schritte zu benannten Aufgaben zusammen. Genau so sind diese Berichtsoperationen strukturiert. Die Funktionsnamen allein belegen keine reine funktionale Programmierung. Insbesondere sind Lesen und Speichern Effekte, und die Beschreibung verlangt nicht, dass Berechnungen gemeinsame Änderungen vermeiden.

Frage 9 von 10

Eine rekursive Python-Funktion gibt bei n == 0 den Wert 0 zurück; andernfalls ruft sie sich mit n + 1 selbst auf. Warum beendet der geschriebene Basisfall die Berechnung nicht, wenn sie bei der positiven Zahl 2 beginnt?
  1. Die Eingaben erreichen null, aber die Rückgabe von null startet die Rekursion neu.
  2. Der Basisfall garantiert die Terminierung auch bei steigenden Eingaben.
  3. Die Rekursion wird in Richtung null abgewickelt, bevor sie den Basisfall prüft.
  4. Die rekursiven Eingaben entfernen sich von null, statt sie zu erreichen.
Antwort zeigenAntwort ausblenden
Antwort: D
Die aufeinanderfolgenden Eingaben sind 2, 3, 4 und so weiter, sodass keine die Abbruchbedingung erfüllt. Ein Basisfall muss durch den rekursiven Ablauf tatsächlich erreicht werden; sein bloßes Vorhandensein im Quellcode reicht nicht aus. Die Rückgabe von 0 in diesem nicht erreichten Zweig kann die Argumente der tatsächlich ausgeführten Aufrufe nicht ändern.

Frage 10 von 10

Eine Dashboard-Anfrage führt zwei Schritte nacheinander aus: 180 ms Warten auf Netzwerkdaten, dann 20 ms lokale Formatierung. Eine neue Sprache würde die Formatierung auf 5 ms verkürzen, ohne die Wartezeit auf das Netzwerk zu ändern. Wie lässt sich allein diese Änderung am besten beurteilen?
  1. Sie spart 15 ms, während das Netzwerk die größte Verzögerung bleibt.
  2. Sie spart 150 ms, wenn die vierfache Beschleunigung auf die Anfrage angewendet wird.
  3. Sie spart 5 ms, weil die neue Formatierungszeit der Ersparnis entspricht.
  4. Sie spart 20 ms, weil die Kompilierung den Formatierungsschritt beseitigt.
Antwort zeigenAntwort ausblenden
Antwort: A
Die angegebene Arbeit in aufeinanderfolgenden Schritten sinkt von 200 ms auf 185 ms, was 15 ms spart. Die Wartezeit auf das Netzwerk von 180 ms bleibt unverändert und macht weiterhin den größten Teil der Anfrage aus. Die vierfache Beschleunigung der Formatierung auf die gesamte Anfrage anzuwenden würde fälschlich annehmen, dass dieselbe Verbesserung auch die Wartezeit auf das Netzwerk betrifft.
Set 2: Wende Regeln für Typen, Objekte und Abfragen an10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Ein Team kann denselben portierbaren Quellcode für zwei verschiedene ISAs kompilieren. Welche Beziehung besteht zwischen dem Quellcode und den nativen Ausgaben?
  1. Der Quellcode ist derselbe; beide Ausgaben enthalten Code für dieselbe ISA.
  2. Der Quellcode muss sich unterscheiden; jede Ausgabe ist auf ihre eigene ISA ausgerichtet.
  3. Der Quellcode ist derselbe; jede Ausgabe ist auf ihre eigene ISA ausgerichtet.
  4. Der Quellcode muss sich unterscheiden; beide Ausgaben enthalten Code für dieselbe ISA.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Eine Laufzeitumgebung übersetzt eine häufig ausgeführte Methode nach dem Start der Anwendung in native Befehle. Welche Technik ist das?
  1. Just-in-time-Kompilierung
  2. Ahead-of-time-Kompilierung
  3. Statisches Binden
  4. Lexikalische Analyse
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Welche Werte ergeben in Python bei a = 8 und b = 12 die Ausdrücke a + b / 2 und (a + b) / 2 jeweils?
  1. 10,0 und 10,0
  2. 14,0 und 14,0
  3. 10,0 und 14,0
  4. 14,0 und 10,0
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Eine C-Funktion gibt einen allokierten Block frei, aber späterer Code liest ihn über einen alten Zeiger. Welcher Fehler entsteht durch diesen späteren Lesezugriff?
  1. Ein Speicherleck
  2. Use-after-free
  3. Ein Double Free
  4. Ein Pufferüberlauf
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine Klasse definiert ein Attribut balance und eine Methode deposit. Zwei Konten werden mit unterschiedlichen Anfangskontoständen erzeugt. Wo gehören diese unterschiedlichen Kontostände hin?
  1. In die gemeinsame Definition der Einzahlungsmethode
  2. In die beiden Kontoinstanzen
  3. In den Klassennamen selbst
  4. In die Parameternamen des Konstruktors
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein Fahrradobjekt speichert ein Bremssystemobjekt und fordert es auf, die Räder anzuhalten. Welche Beziehung passt am besten zu diesem Entwurf?
  1. Komposition: Das Fahrrad hat ein Bremssystem.
  2. Vererbung: Das Fahrrad ist ein Bremssystem.
  3. Überschreiben: Das Fahrrad ersetzt das Bremssystem.
  4. Duck-Typing: Das Fahrrad ist eine Bremssystem-Unterklasse.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Eine Verarbeitungskette behält aus [1, 4, 7, 10] die Werte größer als 3, zieht von jedem behaltenen Wert 1 ab und summiert sie anschließend. Was ist das Ergebnis?
  1. 21
  2. 17
  3. 18
  4. 22
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Eine Datenbank kann eine unveränderte SQL-Abfrage ausführen, indem sie Zeilen durchläuft oder einen verfügbaren Index nutzt. Was wählt zwischen diesen Plänen?
  1. Der Parser für SQL-Abfragen
  2. Der Linker der Datenbank
  3. Die SQL-Typprüfung
  4. Der Anfrageoptimierer
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Eine Python-Klassenmethode aktualisiert mit einer Schleife eine Liste und wandelt mit einer reinen Funktion jedes Element um. Wie sind ihre Stile zu beschreiben?
  1. Objektorientierte Struktur, die imperative Arbeit ausschließt
  2. Funktionale Struktur, die jede Methode rein macht
  3. Objektorientierte Struktur mit imperativer und funktionaler Arbeit
  4. Imperative Struktur, die Objektmethoden verhindert
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Welche Werte haben x und y nach diesem Python-Code?

x = [12]
y = x
x = x + [3]
y.append(6)
  1. x ist [12, 3]; y ist [12, 6].
  2. x ist [12, 3, 6]; y ist [12, 3, 6].
  3. x ist [12, 3, 6]; y ist [12].
  4. x ist [12, 6]; y ist [12, 3].
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 3: Verfolge Aufrufe, Effekte und Objektverträge10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Ein Compiler ersetzt minutes = 60 * 12 durch den vorberechneten Wert 720. Welche Optimierung wird verwendet und was muss sie erhalten?
  1. Dynamisches Binden, das Bibliotheksdateinamen erhält
  2. Lexikalische Analyse, die jedes Quellcodezeichen erhält
  3. JIT-Aufwärmphase, die die Aufrufzahlen zur Laufzeit erhält
  4. Konstantenfaltung, die das erforderliche Verhalten erhält
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Was gibt dieses Python-Programm aus?

score = 11
def change():
    score = 4
    return score + 2
print(change())
print(score)
  1. 6, dann 4
  2. 6, dann 11
  3. 4, dann 11
  4. 13, dann 11
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Welchen Wert hat items nach dem Aufruf?

items = [3]
def revise(values):
    values.append(8)
    values = [1]
    values.append(5)
revise(items)
  1. [3, 8]
  2. [1, 5]
  3. [3, 8, 5]
  4. [3]
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Zwei Objekte verweisen aufeinander. Alle Referenzen von aktiven Ausgangspunkten des Programms auf diese Objekte werden entfernt. Warum kann Referenzzählung allein sie übersehen, während eine verfolgende Speicherbereinigung sie freigeben kann?
  1. Die internen Referenzzahlen erreichen null, sodass eine Verfolgung Objekte mit Referenzzahl null freigeben muss.
  2. Die internen Referenzzahlen bleiben positiv, sodass beide Objekte erreichbar sein müssen.
  3. Die internen Referenzzahlen bleiben positiv, aber keines der beiden Objekte ist erreichbar.
  4. Die internen Referenzzahlen entsprechen der Anzahl der Ausgangspunkte, sodass beide null sind.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Ausgehend von 120 gewährt eine Operation 25 % Rabatt und eine andere addiert 8. Welche Endwerte ergeben sich bei Rabatt und anschließender Addition sowie bei Addition und anschließendem Rabatt?
  1. 96 und 98
  2. 98 und 98
  3. 98 und 96
  4. 96 und 96
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Eine Kontomethode prüft einen Auszahlungsbetrag, bevor sie den gespeicherten Kontostand ändert. Welche Entwurfsidee hält diese Regel bei der Aktualisierung, die sie kontrolliert?
  1. Datenkapselung
  2. Vererbung
  3. Konstantenfaltung
  4. Typinferenz
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Ein spezialisiertes Konto berechnet für jede erfolgreiche Abhebung eine Gebühr von 2 und darf nie einen negativen Kontostand haben. Sein Kontostand ist 43. Was soll bei einer Anforderung zur Abhebung von 42 geschehen?
  1. Die Anforderung annehmen und den Kontostand bei 1 belassen.
  2. Die Anforderung annehmen und den Kontostand bei −1 belassen.
  3. Die Anforderung nach Senkung des Kontostands auf 1 verweigern.
  4. Die Anforderung verweigern und den Kontostand bei 43 belassen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Eine Funktion gibt das Ergebnis einer festen Berechnung aus ihren Argumenten zurück und gibt außerdem eine Protokollmeldung aus. Ist sie rein, und warum?
  1. Ja; ihr Ergebnis ist vorhersehbar.
  2. Nein; die Ausgabe ist ein Seiteneffekt.
  3. Ja; Protokollierung besteht nicht aus gespeicherten Daten.
  4. Nein; jeder Code hat Seiteneffekte.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Eine Webseite registriert showDetails für ein Klickereignis. Warum kann dieselbe Behandlungsroutine trotzdem Objektmethoden und imperative Zuweisungen verwenden?
  1. Ereignisse machen sie nur objektorientiert, wenn sie Attribute ändert.
  2. Ereignisse bestimmen den Ausführungszeitpunkt; ihr Rumpf kann andere Stile nutzen.
  3. Ereignisse verlangen reine Rückruffunktionen, daher müssen Zuweisungen verlagert werden.
  4. Ereignisse machen sie deklarativ, sodass die Ausführungsumgebung ihre Aktualisierungen wählt.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Eine neue Analysefunktion muss eine etablierte Bibliothek verwenden, die nur für Python verfügbar ist, und das Team wartet bereits Python-Dienste innerhalb des geforderten Ressourcenbudgets. Welche genannten Tatsachen sprechen für die Wahl von Python?
  1. Reine Schleifengeschwindigkeit und fehlender Speicheraufwand zur Laufzeit
  2. Browserkompatibilität und erzwungene Python-Typhinweise
  3. Exakte Gleitkommaarithmetik und Unterstützung jedes Hardwareziels
  4. Unterstützung der benötigten Bibliothek und vorhandene Teamerfahrung
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 4: Denke über Ausführungs- und Entwurfsgrenzen hinweg10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Eine Anweisung einer höheren Programmiersprache wird in zwei optimierten Versionen zu einer unterschiedlichen Anzahl von Befehlen, aber beide erfüllen dasselbe von der Sprache geforderte Verhalten. Macht die unterschiedliche Befehlszahl eine der Versionen fehlerhaft?
  1. Nein; eine Anweisung muss keiner festen Befehlszahl entsprechen.
  2. Ja; jede Anweisung muss einem Maschinenbefehl entsprechen.
  3. Ja; Portierbarkeit verlangt identische native Ausgaben.
  4. Nein; optimierte Ausgaben dürfen geforderte Ergebnisse ändern.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Eine native ausführbare Datei verwendet dynamisches Binden. Dem Empfänger fehlen ein Compiler und eine benötigte kompatible gemeinsam genutzte Bibliothek. Welches der genannten fehlenden Elemente erklärt, warum die Datei möglicherweise nicht startet?
  1. Der Ahead-of-time-Compiler fehlt.
  2. Die ursprüngliche Quelldatei fehlt.
  3. Die benötigte gemeinsam genutzte Bibliothek fehlt.
  4. Der Analysebaum des Compilers fehlt.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Eine kompatible JVM führt dieselbe Klassendatei auf zwei ISAs aus und kompiliert auf jeder ihre am häufigsten ausgeführte Methode zur Laufzeit. Ein Teammitglied behauptet, dies beweise, dass die Klassendatei bereits native Befehle für beide Prozessoren enthält. Wie ist dies richtig zu beurteilen?
  1. Die Datei enthält nativen Code; die JVM bindet nur Plattformbibliotheken.
  2. Die Datei enthält Bytecode; auch die JIT-Ausgabe ist ISA-unabhängig.
  3. Die Datei enthält Quelltext; die JVM muss den ursprünglichen Quellcode erneut kompilieren.
  4. Die Datei enthält Bytecode; jeder JIT-Compiler erzeugt eigenen nativen Code.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Vergleiche in Python bei n = 0 die Ausdrücke n != 0 and 36 / n > 4 und 36 / n > 4 and n != 0. Was geschieht?
  1. Beide sind falsch, weil sie dieselben zwei Prüfungen enthalten.
  2. Der erste ist falsch; der zweite löst einen Divisionsfehler aus.
  3. Beide lösen einen Divisionsfehler aus, weil beide eine Division enthalten.
  4. Der erste löst einen Fehler aus; der zweite ist falsch.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Welcher Endwert wird in total gespeichert?

def bump(x):
    return x + 2
total = 0
for n in [2, 5, 8]:
    if n > 3:
        total = total + bump(n)
  1. 17
  2. 15
  3. 19
  4. 21
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein Compiler leitet vor der Ausführung alle Typen lokaler Variablen ab, obwohl der Programmierer keine Typannotationen schreibt. Welchen Prüfungsansatz zeigt dies?
  1. Dynamische Typisierung mit Inferenz zur Laufzeit
  2. Schwache Typisierung ohne Typinformationen
  3. Dynamische Typisierung mit ausdrücklicher Umwandlung
  4. Statische Typisierung mit Typinferenz
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Eine statisch typisierte Sprache leitet aus einem Initialisierungswert einen Ganzzahltyp ab und erlaubt eine bestimmte implizite numerische Umwandlung. Was lässt sich über diese beiden Eigenschaften folgern?
  1. Die Inferenz macht diese Variablen trotz statischer Prüfung dynamisch.
  2. Die implizite Umwandlung bedeutet, dass die abgeleitete Variable keinen festen Typ hat.
  3. Inferenz und Umwandlung können mit statischer Prüfung zusammen bestehen.
  4. Statische Prüfung bedeutet, dass die Umwandlung ausdrücklich geschrieben werden muss.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein Berichtsprogramm durchläuft Objekte in einer Schleife und ruft item.render() auf. Eine neue, nicht verwandte Klasse stellt eine kompatible Methode render() bereit. Der Python-Aufrufer verlangt keine Oberklasse. Warum kann das Objekt ohne typspezifischen Zweig an der Schleife teilnehmen?
  1. Gemeinsame Vererbung ist nötig, bevor seine Methode ausgeführt werden kann.
  2. Duck-Typing akzeptiert das Objekt; die Methodenbindung wählt seine Methode.
  3. Gleiche Methodennamen lassen jedes aufgerufene Objekt denselben Rumpf ausführen.
  4. Der Aufrufer muss vor dem Aufruf die konkrete Klasse auswählen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Eine Erzeugungsfunktion gibt eine Funktion zurück, die ihre Eingabe mit dem Faktor der Erzeugungsfunktion multipliziert. Der Faktor war 4, und die zurückgegebene Funktion wird später mit 7 aufgerufen. Welches Ergebnis und welcher Mechanismus erklären dies?
  1. 28, mithilfe eines Stapelrahmens
  2. 11, mithilfe einer Closure
  3. 28, mithilfe einer Closure
  4. 28, mithilfe einer Typumwandlung
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Ein Team betrachtet eine Beliebtheitsrangliste als ausreichenden Beleg dafür, dass eine Sprache auf seinem benötigten Einsatzziel laufen kann. Welche Kompatibilitätsprüfung fehlt?
  1. Ob die Zielplattform die benötigte Laufzeitumgebung unterstützt
  2. Ob Anleitungen die vom Team bevorzugte Einrückung verwenden
  3. Ob ihr Name in einer anderen Beliebtheitsumfrage weit oben steht
  4. Ob sie eine kürzere Veröffentlichungsgeschichte als Alternativen hat
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 5: Untersuche Grenzen und verbinde die Ideen10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Eine interpreterbasierte Implementierung verbringt Zeit mit der Ausführung von Bytecode. Eine zweite Implementierung derselben Sprache ergänzt einen JIT-Compiler und wird bei einer langen wiederholten Berechnung schneller. Was lässt sich daraus folgern, ohne die Definition der Sprache zu ändern?
  1. Die Sprache wurde statisch typisiert, weil sie einen Compiler erhielt.
  2. Die Ausführungsstrategie änderte sich, während die Semantik der Sprache gleich bleiben kann.
  3. Die erste Laufzeitumgebung führte Quellcode direkt aus, also wurde Bytecode nicht kompiliert.
  4. Die gemeinsame Sprache muss ihre Operatorbedeutungen ändern, um schneller zu werden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Was gibt dieses Python-Programm aus?

value = 6
def step(value):
    value = value + 3
    return value
for n in [1, 2]:
    result = step(value)
print(value, result)
  1. 12 12
  2. 6 12
  3. 6 9
  4. 9 9
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Ein Dienst mit automatischer Speicherbereinigung speichert jeden verarbeiteten Datensatz in einer globalen Liste, die erreichbar bleibt. Das Entfernen lokaler Variablen verringert den für die Datensätze belegten Speicher nicht. Welche Änderung behebt die genannte Ursache?
  1. Referenzen auf veraltete Datensätze aus der globalen Liste entfernen.
  2. Die Speicherbereinigung wiederholt aufrufen und das Archiv unverändert lassen.
  3. Jede lokale Bindung löschen und die globalen Archiveinträge erhalten.
  4. Die Archivliste durch eine Kopie mit denselben Datensätzen ersetzen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Zwei Prozeduren aktualisieren denselben globalen Wert. double() multipliziert ihn mit 2; add_three() addiert 3. Wie groß ist ausgehend vom selben Wert immer der Unterschied zwischen erst Verdoppeln und dann Addieren und erst Addieren und dann Verdoppeln?
  1. Erst Verdoppeln und dann Addieren ergibt ein um 3 größeres Ergebnis.
  2. Erst Addieren und dann Verdoppeln ergibt ein um 6 größeres Ergebnis.
  3. Beide Reihenfolgen ergeben immer dasselbe Ergebnis.
  4. Erst Addieren und dann Verdoppeln ergibt ein um 3 größeres Ergebnis.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine Java-Klasse kennzeichnet ihr Kontostandsfeld als privat und bietet eine Auszahlungsmethode mit Prüfung an. Code in einer nicht verwandten externen Klasse versucht, direkt einen negativen Kontostand zuzuweisen. Was schützt die Regel der Klasse an dieser Zugriffsstelle?
  1. Die Auszahlungsmethode prüft direkte Zuweisungen.
  2. Die Zuweisung erzeugt einen separaten lokalen Kontostand.
  3. Der private Zugriff erlaubt Schreiben, sperrt aber jedes Lesen.
  4. Der Compiler weist den Zugriff auf das private Feld zurück.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Eine Klasse erbt nur zur Wiederverwendung des Speichers von einer Warteschlange, deaktiviert aber deren Entnahmeoperation. Aufrufer, die eine nutzbare Warteschlange erwarten, scheitern. Welcher neue Entwurf behebt das beschriebene Beziehungsproblem?
  1. Die Vererbung von der Warteschlange beibehalten und die Entnahme in der Schnittstelle der Unterklasse verbergen.
  2. Intern eine Warteschlange enthalten und die von der Klasse zugesagten Operationen anbieten.
  3. Die Vererbung von der Warteschlange beibehalten und die Entnahme stillschweigend wirkungslos machen.
  4. Die Vererbung von der Warteschlange beibehalten und dokumentieren, dass Aufrufer die Entnahme vermeiden müssen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Eine Tabelle readings hat die Zeilen (level, active): (50, true), (65, true), (20, false), (85, true) und (90, false). Eine Abfrage fordert SUM(level) unter der Bedingung active = true AND level > 50 an. Welche Summe ist nach dem Hinzufügen eines geeigneten Index erforderlich, und darf sich der Ausführungsplan ändern?
  1. 150; der Plan darf sich ändern, wenn die Summe erhalten bleibt.
  2. 200; der Plan darf sich ändern, wenn die Summe erhalten bleibt.
  3. 240; der Plan darf sich ändern, wenn die Summe erhalten bleibt.
  4. 150; der neue Index darf sowohl den Plan als auch die Summe ändern.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Prolog enthält parent(lea, noah). und parent(noah, sora). sowie die Regel grandparent(X, Z) :- parent(X, Y), parent(Y, Z).. Was findet grandparent(lea, Who)?
  1. Who = noah
  2. Who = lea
  3. Who = sora
  4. Sowohl Who = lea als auch Who = noah
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Eine imperative Schleife, eine Objektmethode und eine SQL-Abfrage sollen in [50, 65, 20, 85] die Beträge echt über 50 summieren. Wie müssen ihre korrekten Ergebnisse lauten?
  1. 200 in allen drei Versionen
  2. 150 in allen drei Versionen
  3. 150 für die Schleife, 200 für die Methode und die Abfrage
  4. 200 für die Schleife, 150 für die Methode und die Abfrage
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Eine Funktion gibt eine neu allokierte Liste zurück, und ihr Aufrufer speichert diese Liste für die spätere Verwendung. Muss die Liste verschwinden, wenn der Aufrufrahmen der Funktion entfernt wird?
  1. Ja; das Ende des Aufrufs hebt die Referenz des Aufrufers auf.
  2. Ja; die Rückgabe kopiert ihren Inhalt und gibt sie frei.
  3. Nein; ihr erzeugender Rahmen muss aktiv bleiben, um sie zu erhalten.
  4. Nein; der Aufrufer hält weiterhin eine Referenz auf die Liste.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe

Schlüsselbegriffe

94 Schlüsselbegriffe, jeweils mit Definition

A

Ablauftabelle
Eine Ablauftabelle hält relevante Variablenwerte nach ausgeführten Schritten fest, sodass der Weg und der sich ändernde Zustand nachvollzogen werden können.
Eine Ablauftabelle für eine Akkumulatorvariable hält die neue Summe fest, nachdem jedes Element die Schleife durchlaufen hat.
Abstraktion
Abstraktion verbirgt Implementierungsdetails hinter einem einfacheren Modell oder einer einfacheren Schnittstelle, sodass sich ein Programmierer auf die auszudrückende Aufgabe konzentrieren kann.
Eine Variable ist eine Abstraktion, die Code einen Wert benennen lässt, ohne dessen physische Speicheradresse anzugeben.
Ahead-of-time-Kompilierung
Die Ahead-of-time-Kompilierung übersetzt Code vor der Programmausführung und verlegt die Übersetzungsarbeit in eine separate Erstellungsphase.
Ahead-of-time-Kompilierung kann eine native ausführbare Datei erzeugen, die ohne installierten Compiler läuft.
Aliasing
Aliasing tritt auf, wenn unterschiedliche Namen oder Referenzen zum selben Objekt führen und dessen Veränderungen über alle sichtbar machen.
Nach b = a erklärt Aliasing, warum das Anhängen über b auch ändern kann, was über a sichtbar ist.
Anfrageoptimierer
Ein Anfrageoptimierer wählt einen Plan zur Ausführung einer Datenbankabfrage und erhält dabei das von der Abfrage geforderte Ergebnis.
Ein Anfrageoptimierer kann nach einer Änderung der Daten einen verfügbaren Index verwenden, ohne dass die SQL-Anfrage umgeschrieben wird.
Anweisung
Eine Anweisung ist ein vollständiger Programmbefehl, etwa eine Zuweisung, ein bedingter Block oder eine Schleife.
Die Anweisung total = price * 3 weist einer Variablen das Ergebnis einer Multiplikation zu.
Assembler
Ein Assembler ist ein Übersetzungsprogramm, das Befehle der Assemblersprache als Maschinencode für einen bestimmten Befehlssatz kodiert.
Ein Assembler wandelt add eax, ebx in die entsprechenden x86-64-Befehlsbytes um.
Assemblersprache
Assemblersprache stellt maschinennahe Befehle mit lesbaren Mnemonics und Namen für Register oder Speicherstellen dar.
Ein Analyst liest Assemblersprache, um Operationen in einem Programm nachzuvollziehen, dessen Quellcode in einer höheren Programmiersprache nicht verfügbar ist.
Attribut
Ein Attribut ist ein benanntes Datenelement oder Mitglied, das zu einem Objekt oder einer Klasse gehört und über dieses Objekt oder diese Klasse zugänglich ist.
Das Kontostandsattribut eines Kontos hält dessen aktuellen Zustand getrennt vom Kontostand eines anderen Kontos fest.
Aufrufstapel
Der Aufrufstapel verwaltet aktive Funktionsaufrufe in verschachtelter Reihenfolge, sodass die Ausführung zum richtigen Aufrufer zurückkehren kann.
Ein Aufruf einer Funktion area fügt dem Aufrufstapel einen Eintrag hinzu und entfernt ihn, wenn das Ergebnis an den Aufrufer zurückkehrt.
Ausdruck
Ein Ausdruck ist ein Stück Code, das ausgewertet wird, um einen Wert zu erzeugen, der anschließend von einer umgebenden Anweisung verwendet werden kann.
Der Ausdruck width * height liefert den Wert, den eine Funktion zur Berechnung einer Rechteckfläche zurückgibt.
Automatische Speicherbereinigung
Die automatische Speicherbereinigung erkennt Objekte, die das laufende Programm nicht mehr erreichen kann, und gibt deren Speicher automatisch frei.
Eine verfolgende automatische Speicherbereinigung kann eine nicht erreichbare Gruppe freigeben, selbst wenn deren Objekte noch aufeinander verweisen.

B

Basisfall
Ein Basisfall gibt ein rekursives Ergebnis direkt an und ermöglicht einer endlichen Berechnung, weitere rekursive Aufrufe zu beenden.
Der Basisfall der Fakultät gibt für null 1 zurück, sodass die wartenden Multiplikationsaufrufe beendet werden können.
Befehlssatzarchitektur
Eine Befehlssatzarchitektur legt die Operationen fest, die eine Prozessorfamilie unterstützt, und die Kodierungen, mit denen sie dargestellt werden.
Eine ausführbare x86-64-Datei ist auf eine andere Befehlssatzarchitektur ausgerichtet als nativer ARM-Maschinencode.
Bytecode
Bytecode ist eine Zwischenform von Befehlen, die für eine Software-Laufzeitumgebung statt für die direkte Ausführung durch den Prozessor des jeweiligen Systems entworfen ist.
CPython erzeugt Bytecode aus einer gültigen Python-Datei, bevor seine virtuelle Maschine das Programm ausführt.

C

Closure
Eine Closure ist eine Funktion zusammen mit dem Zugriff auf Variablen aus ihrer Entstehungsumgebung, die den umschließenden Aufruf überdauern können.
Eine Multiplikator-Closure behält ihren Faktor, nachdem die Funktion, die den Multiplikator erzeugt hat, zurückgekehrt ist.
Compiler
Ein Compiler übersetzt Quellcode in eine Zieldarstellung, die aus nativen Maschinenbefehlen oder Bytecode als Zwischenform bestehen kann.
Ein Compiler kann eine unverträgliche Zuweisung zurückweisen, bevor er das Programm erzeugt, das ein Benutzer ausführen wird.

D

Datenkapselung
Datenkapselung fasst Daten mit kontrollierten Operationen zusammen, die ihre Regeln einhalten und unsichere Änderungen durch anderen Code einschränken.
Datenkapselung ermöglicht einem Konto, eine zu hohe Abhebung zu verweigern, bevor sein Kontostand negativ werden kann.
Datentyp
Ein Datentyp legt eine Menge möglicher Werte und die auf ihnen erlaubten Operationen fest und bestimmt so ihre Darstellung und Verwendung mit.
Ein Zeichenkettentyp unterstützt Textoperationen, selbst wenn die Zeichen im Wert zufällig Ziffern sind.
Deklarative Programmierung
Deklarative Programmierung gibt ein gefordertes Ergebnis oder eine Beziehung vor und überlässt die Einzelheiten der Ausführung einer Ausführungsumgebung der Sprache.
Deklarative Programmierung ermöglicht einer SQL-Abfrage, passende Zeilen anzufordern, ohne eine zeilenweise Schleife vorzugeben.
Domänenspezifische Sprache
Eine domänenspezifische Sprache ist für ein bestimmtes Problemgebiet entworfen, etwa Datenbankabfragen, Muster oder Seitengestaltung.
Eine domänenspezifische Sprache wie SQL kann eine Datenanfrage direkter ausdrücken als eine handgeschriebene Suchschleife.
Double Free
Ein Double Free versucht, denselben allokierten Speicherblock zweimal freizugeben, und verletzt damit den erwarteten Lebenszyklus der Speicherverwaltung.
Ein erneuter Aufruf von free für eine bereits freigegebene Allokation kann einen Double Free und unvorhersehbares Verhalten verursachen.
Duck-Typing
Duck-Typing akzeptiert ein Objekt anhand der Operationen, die es bereitstellt, ohne notwendigerweise eine gemeinsame deklarierte Oberklasse zu verlangen.
Pythons Duck-Typing ermöglicht einem nicht verwandten Objekt, an einer Flächenberechnung teilzunehmen, wenn es eine kompatible Methode area() bereitstellt.
Dynamische Methodenbindung
Die dynamische Methodenbindung wählt während der Ausführung eine Methodenimplementierung anhand des Objekts, für das der Aufruf erfolgt.
Dynamische Methodenbindung leitet shape.area() an die Rechteckimplementierung, wenn das aufgerufene Objekt ein Rechteck ist.
Dynamische Typisierung
Dynamische Typisierung prüft Typen von Werten während der Ausführung und erlaubt, dass ein Name im Laufe der Zeit auf Werte verschiedener Typen verweist.
Pythons dynamische Typisierung erlaubt, dass ein Name von einer Ganzzahl zu einer Zeichenkette wechselt, aber unverträgliche Arithmetik schlägt weiterhin fehl.

E

Engpass
Ein Engpass ist der Teil eines Systems, der die Gesamtleistung begrenzt und Verbesserungen an anderer Stelle weniger wirksam macht.
Ist das Warten auf das Netzwerk der Engpass, kann schnellere lokale Arithmetik die Gesamtzeit der Anfrage kaum verändern.
Ereignisbehandlungsroutine
Eine Ereignisbehandlungsroutine ist eine Funktion oder Operation, die registriert wird, um zu reagieren, wenn die Laufzeitumgebung ein bestimmtes Ereignis übermittelt.
Eine Ereignisbehandlungsroutine für Klicks kann ein Menü aktualisieren, während eine andere eine eingehende Netzwerknachricht verarbeitet.
Ereignisgesteuerte Programmierung
Ereignisgesteuerte Programmierung ordnet Arbeit um Behandlungsroutinen, die beim Eintritt von Ereignissen wie Klicks oder eingehenden Nachrichten aufgerufen werden.
Ereignisgesteuerte Programmierung wartet auf einen Klick auf eine Schaltfläche, bevor sie die registrierte Aktualisierung der Oberfläche ausführt.

F

Funktion erster Klasse
Eine Funktion erster Klasse kann als Wert behandelt werden, etwa indem sie einem Namen zugewiesen, als Argument übergeben und von einem Aufruf zurückgegeben wird.
Eine Funktion erster Klasse kann map als Operation übergeben werden, die auf jedes Element einer Sammlung anzuwenden ist.
Funktion höherer Ordnung
Eine Funktion höherer Ordnung nimmt eine Funktion als Argument entgegen oder gibt eine Funktion zurück und ermöglicht so, Verhalten selbst zu kombinieren.
Die Funktion höherer Ordnung filter nimmt ein Prädikat entgegen, das entscheidet, welche Eingabewerte behalten werden.
Funktionale Programmierung
Funktionale Programmierung strukturiert Berechnungen durch die Kombination von Funktionen und die Erzeugung neuer Werte und begrenzt dabei gemeinsame Veränderungen.
Funktionale Programmierung kann eine Berechnung für einen Bericht als Auswahl, Umwandlung und Kombination von Werten ausdrücken.

G

Geheimnisprinzip
Das Geheimnisprinzip verbirgt Implementierungsdetails hinter einer Schnittstelle, sodass anderer Code stattdessen von zugesagten Operationen abhängt.
Das Geheimnisprinzip erlaubt einem Konto, seine Speicherdarstellung zu ändern und dabei das Einzahlungsverhalten beizubehalten.
Gleitkommazahl
Eine Gleitkommazahl stellt einen Wert mit begrenzter binärer Darstellungsgenauigkeit dar, sodass die meisten Dezimalbrüche nur näherungsweise dargestellt werden.
Gleitkommazahlen können einen winzigen Unterschied zwischen 0.1 + 0.2 und dem gespeicherten Wert von 0.3 verursachen.
Gültigkeitsbereich
Der Gültigkeitsbereich ist der Teil eines Programms, in dem ein bestimmter Name verfügbar ist, unabhängig von der Lebensdauer des Objekts, auf das er verweist.
Der Gültigkeitsbereich eines lokalen Namens erstreckt sich nicht auf einen Aufrufer, nur weil die Funktion das von ihm benannte Objekt zurückgibt.

H

Hängender Zeiger
Ein hängender Zeiger verweist auf Speicher, der bereits freigegeben wurde und kein gültiges Zugriffsziel mehr ist.
Ein hängender Zeiger kann weiterhin eine alte Adresse enthalten, selbst nachdem diese Adresse für andere Daten wiederverwendet wurde.
Heap
Der Heap ist Speicher für dynamisch allokierte Objekte, darunter Objekte, die die Funktion überdauern können, die sie erzeugt hat.
Eine zurückgegebene Liste kann auf dem Heap bleiben, nachdem die erzeugende Funktion beendet und ihr Aufrufrahmen entfernt wurde.
Höhere Programmiersprache
Eine höhere Programmiersprache drückt Programme durch Konzepte wie Variablen und Funktionen aus und verbirgt dabei viele Hardwaredetails.
Eine höhere Programmiersprache ermöglicht einem Entwickler, eine Summe anzufordern, ohne die zu verwendenden Prozessorregister auszuwählen.

I

Imperative Programmierung
Imperative Programmierung drückt eine Berechnung als geordnete Befehle aus, die den Programmzustand durch Zuweisungen und Kontrollfluss aktualisieren.
Imperative Programmierung macht den neuen Wert einer Akkumulatorvariablen bei jedem Schleifendurchlauf sichtbar.
Implizite Typumwandlung
Eine implizite Typumwandlung ist eine durch Sprachregeln automatisch vorgenommene Umwandlung, wenn eine Operation einen Wert in einem anderen Typ benötigt.
Javascripts implizite Typumwandlung wandelt Zahlentext für die Multiplikation um, kann ihn bei der Addition aber verketten.
Interpreter
Ein Interpreter führt ein Programm mithilfe eines laufenden Softwaresystems aus, das Quellcode oder eine interne Darstellung ausführen kann.
Ein Interpreter kann ein kurzes Skript ausführen, ohne dem Benutzer zuerst eine separate native ausführbare Datei zu geben.

J

Just-in-time-Kompilierung
Die Just-in-time-Kompilierung erzeugt während der Programmausführung nativen Maschinencode, oft für häufig verwendete Teile.
Just-in-time-Kompilierung kann eine lange laufende Schleife beschleunigen, nachdem die Laufzeitumgebung wiederholte Ausführung beobachtet hat.

K

Klasse
Eine Klasse legt Struktur und Verhalten ihrer Instanzen fest, indem sie deren Attribute und verfügbare Methoden beschreibt.
Die Klasse Loan einer Bibliothek kann für jede Ausleihe das Mitglied, das Buch, das Fälligkeitsdatum und die Verlängerungsoperation festlegen.
Komposition
Komposition baut ein Objekt aus anderen Objekten auf, die es als Komponenten oder zur Zusammenarbeit enthält, und drückt eine Hat-ein-Beziehung aus.
Komposition gibt einem Auto ein Motorobjekt, dessen Methoden das Auto aufrufen kann, ohne zu einer Art Motor zu werden.
Konstantenfaltung
Konstantenfaltung wertet Ausdrücke aus Konstanten während der Übersetzung aus und erhält dabei das geforderte Programmverhalten.
Konstantenfaltung ermöglicht einem Compiler, 60 * 60 * 24 vor dem Programmstart durch 86.400 zu ersetzen.
Kurzschlussauswertung
Die Kurzschlussauswertung überspringt einen logischen Operanden, wenn ein früherer Operand das Ergebnis des Ausdrucks bereits bestimmt.
Durch Kurzschlussauswertung kann eine Prüfung auf Ungleichheit mit null eine unsichere Division auf der rechten Seite von and verhindern.

L

Lambda-Ausdruck
Ein Lambda-Ausdruck ist ein Funktionsausdruck ohne deklarierten Funktionsnamen, mit dem sich eine kleine Operation als Wert bereitstellen lässt.
Der Python-Lambda-Ausdruck lambda n: n * n stellt einer Umwandlungskette eine Quadrieroperation bereit.
Laufzeitfehler
Ein Laufzeitfehler tritt auf, wenn eine ausgeführte Operation nach den Regeln der Sprache oder unter den verfügbaren Bedingungen nicht fortgesetzt werden kann.
Eine Division durch null in Python erzeugt einen Laufzeitfehler, wenn die Ausführung diese Berechnung erreicht.
Lexikalische Analyse
Die lexikalische Analyse zerlegt Quelltext in bedeutungstragende Token, darunter Bezeichner, Literale, Operatoren und Satzzeichen.
Während der lexikalischen Analyse wird price * 3 in einen Namen, einen Multiplikationsoperator und eine Zahl zerlegt.
Linker
Ein Linker verbindet übersetzte Objektdateien und benötigten Bibliothekscode und löst Referenzen zwischen den Teilen eines Programms auf.
Ein Linker verbindet den übersetzten Aufruf einer Quelldatei mit der von einer Bibliothek bereitgestellten Ausgaberoutine.
Logische Programmierung
Logische Programmierung gibt Fakten und Regeln an und sucht dann nach Werten, die eine Frage unter diesen Beziehungen erfüllen.
Logische Programmierung kann eine Großelternbeziehung ableiten, indem sie zwei bekannte Elternbeziehungen verbindet.
Logischer Fehler
Ein logischer Fehler bewirkt, dass ein Programm ein unbeabsichtigtes Ergebnis berechnet, obwohl seine ausgeführten Operationen erfolgreich abgeschlossen werden können.
Die Verwendung von a + b / 2 zur Berechnung des Durchschnitts zweier Werte ist ein logischer Fehler, weil die Division vor der Addition stattfindet.

M

Maschinencode
Maschinencode ist eine Folge binär kodierter Prozessorbefehle, die einer bestimmten Befehlssatzarchitektur entsprechen.
Der Prozessor führt Maschinencode aus, nachdem ein Compiler oder Assembler kompatible Befehle erzeugt hat.
Methode
Eine Methode ist eine Operation, die zu einer Klasse oder einem Objekt gehört und oft auf den Zustand der Instanz wirkt, für die sie aufgerufen wird.
Eine Auszahlungsmethode prüft den angeforderten Betrag, bevor sie den Kontostand des betreffenden Kontos ändert.
Multiparadigmensprache
Eine Multiparadigmensprache unterstützt mehrere Stile zur Strukturierung von Berechnungen und ermöglicht so unterschiedliche Ansätze innerhalb eines Programms.
Python ist eine Multiparadigmensprache, in der eine Klassenmethode eine imperative Schleife und eine funktionale Umwandlung verwenden kann.
Mutation
Eine Mutation verändert ein bestehendes Objekt direkt, sodass jede Referenz auf dasselbe Objekt den aktualisierten Inhalt zeigt.
Das Anhängen eines Elements an eine gemeinsam genutzte Liste ist eine Mutation; die Zuweisung einer neuen Liste an einen Namen ändert nur diese Bindung.

O

Objekt
Ein Objekt ist eine Programmeinheit, die Daten mit verfügbaren Operationen verbindet, häufig als konkrete Instanz einer Klasse.
Zwei Kontoobjekte können dieselbe Auszahlungsmethode nutzen und dabei jeweils ihren eigenen Kontostand behalten.
Ownership
Ownership weist die Verantwortung für die Lebensdauer eines Wertes zu und ermöglicht Compilerregeln, Zugriffe zu prüfen und die Speicherfreigabe zu organisieren.
Rusts Ownership weist in sicherem Code den Zugriff über eine Referenz zurück, die den Wert, auf den sie verweist, überdauern würde.

Ö

Ökosystem
Ein Ökosystem ist die umgebende Sammlung von Bibliotheken, Werkzeugen, Dokumentation und Unterstützung durch die Gemeinschaft, die für eine Technologie verfügbar ist.
Ein starkes Ökosystem für Datenanalyse kann Python für ein Team praktisch machen, selbst wenn die reine Schleifengeschwindigkeit nicht sein Hauptvorteil ist.

P

Parameter
Ein Parameter ist ein Name in einer Funktionsdefinition, der beim Aufruf der Funktion einen durch ein Argument übergebenen Wert erhält.
Der Parameter width erhält 3, wenn der Aufrufer area(3, 4) auswertet.
Plattform
Eine Plattform ist die Hardware- und Softwareumgebung, auf der ein Programm laufen muss, einschließlich unterstützter Laufzeitumgebungen und Systemdienste.
Eine Browserplattform kann JavaScript direkt über ihre Ausführungsumgebung und kompilierte Module über WebAssembly ausführen.
Polymorphie
Polymorphie ermöglicht, dass eine gemeinsame Operation mit verschiedenen Objekttypen funktioniert und jedes Objekt sein passendes Verhalten bereitstellt.
Polymorphie ermöglicht einem Berichtsprogramm, area() für Kreise und Rechtecke ohne separaten Zweig für jede Form aufzurufen.
Programmierparadigma
Ein Programmierparadigma ist ein Stil zur Strukturierung von Berechnungen mit bestimmten Arten, Daten, Operationen und Kontrolle auszudrücken.
Ein Programmierparadigma kann Zustandsaktualisierungen betonen, während ein anderes die Zusammensetzung reiner Funktionen hervorhebt.
Prozedurale Programmierung
Prozedurale Programmierung fasst imperative Befehle zu benannten Funktionen oder Prozeduren zusammen, die jeweils eine zusammenhängende Aufgabe bearbeiten.
Prozedurale Programmierung trennt das Lesen von Daten, das Berechnen eines Ergebnisses und das Anzeigen eines Berichts in benannte Aufgaben.
Pufferüberlauf
Ein Pufferüberlauf schreibt über die Schranken eines Speicherblocks hinaus und kann dabei benachbarte Daten oder den Programmzustand beschädigen.
Das Schreiben auf Index 10 eines C-Arrays mit zehn Elementen erzeugt einen Pufferüberlauf, statt ein elftes Element hinzuzufügen.

R

Referenz
Eine Referenz ist ein Wert, der ein Objekt bezeichnet und Zugriff darauf erlaubt, ohne die gespeicherten Daten des Objekts zu duplizieren.
Das Kopieren einer Referenz auf eine Liste gibt einem weiteren Namen Zugriff auf dieselbe Liste und ihre späteren Veränderungen.
Referenzzählung
Die Referenzzählung verfolgt Referenzen auf ein Objekt und gibt dessen Speicher normalerweise frei, wenn die Anzahl null erreicht.
Die Referenzzählung von CPython benötigt Unterstützung durch eine Zyklenerkennung für nicht erreichbare Objekte, die gegenseitig Referenzen aufeinander halten.
Regulärer Ausdruck
Ein regulärer Ausdruck beschreibt ein Textmuster für eine Suchfunktion und verwendet dabei Regeln für Zeichen, Wiederholung und Gruppierung.
Der reguläre Ausdruck [0-9]{3}-[0-9]{4} passt auf eine Gruppe aus drei Ziffern, gefolgt von einem Bindestrich und vier Ziffern.
Reine Funktion
Eine reine Funktion gibt für dieselben Argumente dasselbe Ergebnis zurück und führt über die Rückgabe dieses Ergebnisses hinaus keine Seiteneffekte aus.
Eine reine Funktion für einen festen Multiplikator kann getestet werden, ohne globalen Zustand vorzubereiten oder Ausgaben aufzufangen.
Rekursion
Rekursion löst ein Problem durch Aufrufe derselben Funktion und führt es dabei häufig auf einen Fall zurück, der keinen weiteren Aufruf benötigt.
Die Fakultätsrekursion multipliziert mit schrittweise kleineren Ganzzahlen, bis sie das festgelegte Ergebnis für null erreicht.
REPL
Eine REPL ist eine interaktive Lese-Auswerte-Ausgabe-Schleife, die eine Eingabe entgegennimmt, sie auswertet, ein Ergebnis zeigt und auf eine weitere wartet.
Ein Lernender verwendet eine REPL, um einen kleinen Ausdruck zu testen und sein Ergebnis zu sehen, bevor er einen weiteren Ausdruck eingibt.
Rückruffunktion
Eine Rückruffunktion ist eine Funktion, die anderem Code übergeben wird, damit dieser sie aufrufen kann, wenn eine Operation oder ein Ereignis es erfordert.
Die Übergabe von showMenu als Rückruffunktion ermöglicht einem Schaltflächensystem, diese Funktion beim Klick des Benutzers aufzurufen.

S

Schnittstelle
Eine Schnittstelle legt Operationen fest, die implementierende Typen bereitstellen müssen, sodass sich Aufrufer auf einen gemeinsamen Vertrag verlassen können.
Eine Schnittstelle für Formen kann eine Methode area() verlangen und dabei jeder Form überlassen, die Fläche anders zu berechnen.
Seiteneffekt
Ein Seiteneffekt ist eine beobachtbare Aktion über die Rückgabe eines Ergebnisses hinaus, etwa eine Ausgabe oder eine Änderung gemeinsam genutzter Programmdaten.
Das Schreiben einer Berichtsdatei ist ein Seiteneffekt, selbst wenn die Funktion jede globale numerische Variable unverändert lässt.
Semantik
Semantik ist die Bedeutung, die Programmkonstrukten zugewiesen wird, einschließlich des Verhaltens von Operatoren und der Art, wie die Ausführung Werte ändert.
Die Semantik der Ganzzahldivision erklärt, warum eine gleich aussehende Division in verschiedenen Sprachen unterschiedliche Ergebnisse liefern kann.
Speicherallokation
Speicherallokation reserviert Speicher für Programmdaten, wobei der reservierte Speicher bis zu seiner Freigabe nicht erneut verwendet werden kann.
Eine wachsende Sammlung kann weitere Speicherallokation benötigen, wenn sie zusätzliche Elemente speichert.
Speicherleck
Ein Speicherleck hält allokierten Speicher belegt, der nicht mehr benötigt wird, und verringert damit den für nützliche Arbeit verfügbaren Speicher.
Ein ständig wachsender Cache ohne Verdrängung kann selbst in einer Anwendung mit automatischer Speicherbereinigung ein Speicherleck verursachen.
Speichersicherheit
Speichersicherheit verhindert Klassen ungültiger Speicherzugriffe, etwa den Zugriff auf freigegebenen Speicher oder das Schreiben außerhalb gültiger Schranken.
Speichersicherheit kann einen gefährlichen Zugriff verhindern, während das Programm weiterhin einen davon unabhängigen Fehler in seiner Berechnung enthält.
Sprachimplementierung
Eine Sprachimplementierung ist die Software, die eine Möglichkeit bereitstellt, in dieser Sprache geschriebene Programme zu übersetzen und auszuführen.
CPython und PyPy sind verschiedene Implementierungen, sodass Python-Code unterschiedliche Ausführungsstrategien nutzen kann.
SQL
SQL ist eine Sprache für die Arbeit mit relationalen Daten, deren Abfragen angeforderte Felder, Zeilenbedingungen und Reihenfolge angeben.
SQL kann die Namen von Lernenden über einem Notenschwellenwert anfordern und diese Namen alphabetisch sortieren.
Stapelrahmen
Ein Stapelrahmen speichert Informationen zu einem aktiven Funktionsaufruf, etwa lokale Bindungen und die Rücksprungstelle.
Jeder noch nicht abgeschlossene rekursive Aufruf benötigt einen eigenen Stapelrahmen, sofern eine Implementierung ihn nicht wegoptimieren kann.
Statische Typisierung
Statische Typisierung prüft die Typverträglichkeit vor der Ausführung und ermöglicht, viele ungültige Kombinationen während der Entwicklung zurückzuweisen.
Statische Typisierung ermöglicht einem Java-Compiler, Text zur Initialisierung einer Ganzzahlvariablen zurückzuweisen, bevor das Programm läuft.
Strukturierte Programmierung
Strukturierte Programmierung ordnet den Kontrollfluss durch Sequenzen, Verzweigungen, Schleifen und Aufrufe statt durch beliebige Sprünge.
Strukturierte Programmierung ermöglicht es Lesenden, verschachtelte Blöcke nachzuvollziehen und zu erkennen, welche Anweisung als Nächstes ausgeführt wird.
Syntaktische Analyse
Die syntaktische Analyse prüft Token anhand einer Sprachgrammatik und stellt die Struktur von Ausdrücken und Anweisungen dar.
Die syntaktische Analyse hält fest, dass die Multiplikation in total = price * 3; zur Zuweisung gehört.
Syntax
Syntax ist die Gesamtheit der Regeln, die festlegen, welche Anordnungen von Token gültige Ausdrücke, Anweisungen und Programme bilden.
Die Python-Syntax verlangt nach einer if-Bedingung einen Doppelpunkt und für den gesteuerten Block eine Einrückung.

T

Token
Ein Token ist ein bedeutungstragender Teil des Quelltextes, etwa ein Schlüsselwort, Bezeichner, Literal, Operator oder Satzzeichen.
Das Semikolon in einer C-Zuweisung ist ein Token, obwohl es weder eine Variable noch einen numerischen Wert bezeichnet.
Turing-Vollständigkeit
Turing-Vollständigkeit ist die Fähigkeit, jeden berechenbaren Algorithmus auszudrücken, wenn idealisiert unbegrenzte Zeit und unbegrenzter Speicher verfügbar sind.
Turing-Vollständigkeit sagt einem Team nicht, ob eine Sprache die Browserunterstützung oder Bibliotheken hat, die sein Projekt benötigt.
Typinferenz
Typinferenz leitet Typen aus dem umgebenden Code ab und ermöglicht statische Prüfungen, selbst wenn Typannotationen fehlen.
Typinferenz ermöglicht einem Compiler, einen Ganzzahltyp aus einem Initialisierungswert statt aus einer geschriebenen Typangabe zu bestimmen.

Ü

Überlauf
Ein Überlauf tritt auf, wenn ein arithmetisches Ergebnis außerhalb des Darstellungsbereichs einer numerischen Darstellung mit fester Bitbreite liegt.
Ein Ganzzahlüberlauf in Java schlägt beim Überschreiten des Maximums um, während vorzeichenbehafteter Überlauf in C undefiniertes Verhalten ist.
Überschreiben
Überschreiben gibt einer Unterklasse eine eigene Implementierung einer geerbten Methode und hält die Operation dabei für Aufrufer verfügbar.
Das Überschreiben einer Auszahlungsmethode kann eine Gebühr hinzufügen und dabei die Regel gegen einen negativen Kontostand erhalten.

U

Unveränderlichkeit
Unveränderlichkeit bedeutet, dass ein bestehender Wert nicht direkt geändert werden kann; eine Aktualisierung muss einen anderen Wert erzeugen oder auswählen.
Unveränderlichkeit hält eine ursprüngliche Sammlung verfügbar, wenn eine Umwandlung eine neue Version erstellt.
Use-after-free
Use-after-free greift über einen Zeiger auf Speicher zu, nachdem dessen Allokation freigegeben wurde, und verursacht dadurch einen ungültigen Speicherzugriff.
Use-after-free kann ein neues Objekt überschreiben, das zufällig einen zuvor freigegebenen Block belegt.

V

Vererbung
Vererbung definiert eine spezialisierte Klasse aus einer bestehenden Klasse und verwendet oder erweitert Verhalten über eine Ist-ein-Beziehung.
Vererbung eignet sich für ein Sparkonto, das weiterhin das von einem Bankkonto erwartete Verhalten erfüllen muss.
Virtuelle Maschine
Eine virtuelle Maschine ist Software, die ein Ausführungsmodell für Befehle bereitstellt, etwa für den Bytecode einer Sprachlaufzeitumgebung.
Eine kompatible virtuelle Java-Maschine ermöglicht, dass dieselbe Klassendatei auf Computern mit unterschiedlichen Prozessoren läuft.

Z

Zuweisung
Eine Zuweisung bindet einen Namen oder ein Speicherziel an einen Wert und wertet dazu zuerst den Ausdruck auf der rechten Seite anhand des aktuellen Zustands aus.
Die Zuweisung count = count + 1 liest den alten Zählwert, bevor sie ihn durch den erhöhten Wert ersetzt.

Über diese Einheit

Von StudeliFehler melden

Erstelle deine eigenen Lernmaterialien

Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.

Jetzt starten