Programmiersprache

Aus besserwiki.de
Der Quellcode für ein einfaches Computerprogramm, das in der Programmiersprache C geschrieben wurde. Die grauen Zeilen sind Kommentare, die helfen, das Programm in einer natürlichen Sprache zu erklären. Wenn es kompiliert und ausgeführt wird, gibt es die Ausgabe "Hallo, Welt!".

Eine Programmiersprache ist ein Satz von Regeln, der Zeichenketten oder grafische Programmelemente (im Fall von visuellen Programmiersprachen) in verschiedene Arten von Maschinencode umwandelt. Programmiersprachen sind eine Art von Computersprache und werden in der Computerprogrammierung zur Implementierung von Algorithmen verwendet.

Die meisten Programmiersprachen bestehen aus Anweisungen für Computer. Es gibt programmierbare Maschinen, die nicht mit allgemeinen Programmiersprachen arbeiten, sondern mit einer Reihe von spezifischen Anweisungen. Seit den frühen 1800er Jahren werden Programme verwendet, um das Verhalten von Maschinen wie Jacquard-Webstühlen, Spieldosen und Klavieren zu steuern.

Tausende von verschiedenen Programmiersprachen wurden entwickelt, und jedes Jahr kommen neue hinzu. Viele Programmiersprachen sind in imperativer Form geschrieben (d. h. als eine Folge von auszuführenden Operationen), während andere Sprachen die deklarative Form verwenden (d. h. das gewünschte Ergebnis wird angegeben, nicht aber, wie es erreicht werden soll).

Die Beschreibung einer Programmiersprache wird in der Regel in die beiden Komponenten Syntax (Form) und Semantik (Bedeutung) aufgeteilt, die in der Regel durch eine formale Sprache definiert werden. Einige Sprachen werden durch ein Spezifikationsdokument definiert (z. B. wird die Programmiersprache C durch eine ISO-Norm spezifiziert), während andere Sprachen (z. B. Perl) eine dominante Implementierung haben, die als Referenz behandelt wird. Einige Sprachen haben beides, wobei die Basissprache durch einen Standard definiert ist und Erweiterungen aus der vorherrschenden Implementierung üblich sind.

Die Theorie der Programmiersprachen ist ein Teilgebiet der Informatik, das sich mit dem Entwurf, der Implementierung, der Analyse, der Charakterisierung und der Klassifizierung von Programmiersprachen beschäftigt.

Quelltext eines Programms in der Programmiersprache C++.
Quelltext eines Programms in der Programmiersprache Scratch.

Eine Programmiersprache ist eine formale Sprache zur Formulierung von Datenstrukturen und Algorithmen, d. h. von Rechenvorschriften, die von einem Computer ausgeführt werden können. Sie setzen sich üblicherweise aus schrittweisen Anweisungen aus erlaubten (Text-)Mustern zusammen, der sogenannten Syntax.

Während die ersten Programmiersprachen noch unmittelbar an den Eigenschaften der jeweiligen Rechner ausgerichtet waren, werden heute meist problemorientierte oder auch (allgemeiner) höhere Programmiersprachen verwendet, die eine maschinenunabhängigere und somit für den Menschen leichter verständliche Ausdrucksweise erlauben. In diesen Sprachen geschriebene Programme können automatisiert in Maschinensprache übersetzt werden, welche unmittelbar von einem Prozessor ausgeführt werden kann. Zunehmend kommen auch visuelle Programmiersprachen zum Einsatz, welche den Zugang zu Programmiersprachen erleichtern.

Bei deklarativen Programmiersprachen ist der Ausführungsalgorithmus schon vorab festgelegt und wird nicht im Quelltext ausformuliert/beschrieben, sondern es werden nur seine Anfangswerte und Bedingungen festgelegt, sowie die Regeln, die das Ergebnis erfüllen muss.

Definitionen

Eine Programmiersprache ist eine Notation zum Schreiben von Programmen, d. h. von Spezifikationen für Berechnungen oder Algorithmen. Einige Autoren beschränken den Begriff "Programmiersprache" auf solche Sprachen, die alle möglichen Algorithmen ausdrücken können. Zu den Merkmalen, die oft als wichtig für die Definition einer Programmiersprache angesehen werden, gehören:

Funktion und Ziel
Eine Computerprogrammiersprache ist eine Sprache, die zum Schreiben von Computerprogrammen verwendet wird, bei denen ein Computer eine Art von Berechnung oder Algorithmus ausführt und möglicherweise externe Geräte wie Drucker, Festplattenlaufwerke, Roboter usw. steuert. PostScript-Programme werden zum Beispiel häufig von einem anderen Programm erstellt, um einen Computerdrucker oder ein Display zu steuern. Allgemeiner ausgedrückt, kann eine Programmiersprache die Berechnung auf einer, möglicherweise abstrakten, Maschine beschreiben. Es ist allgemein anerkannt, dass eine vollständige Spezifikation für eine Programmiersprache eine - möglicherweise idealisierte - Beschreibung einer Maschine oder eines Prozessors für diese Sprache enthält. In den meisten praktischen Kontexten beinhaltet eine Programmiersprache einen Computer; folglich werden Programmiersprachen normalerweise auf diese Weise definiert und untersucht. Programmiersprachen unterscheiden sich von natürlichen Sprachen dadurch, dass natürliche Sprachen nur für die Interaktion zwischen Menschen verwendet werden, während Programmiersprachen es auch Menschen ermöglichen, Anweisungen an Maschinen zu übermitteln.
Abstraktionen
Programmiersprachen enthalten in der Regel Abstraktionen zur Definition und Manipulation von Datenstrukturen oder zur Steuerung des Ausführungsablaufs. Die praktische Notwendigkeit, dass eine Programmiersprache angemessene Abstraktionen unterstützt, wird durch das Abstraktionsprinzip ausgedrückt. Dieses Prinzip wird manchmal als Empfehlung an den Programmierer formuliert, solche Abstraktionen angemessen zu nutzen.
Ausdruckskraft
In der Rechentheorie werden Sprachen nach den Berechnungen klassifiziert, die sie ausdrücken können. Alle Turing-kompletten Sprachen können dieselbe Menge von Algorithmen implementieren. ANSI/ISO SQL-92 und Charity sind Beispiele für Sprachen, die nicht Turing-komplett sind, aber oft als Programmiersprachen bezeichnet werden.

Auszeichnungssprachen wie XML, HTML oder troff, die strukturierte Daten definieren, werden normalerweise nicht als Programmiersprachen bezeichnet. Programmiersprachen können jedoch die Syntax mit Auszeichnungssprachen teilen, wenn eine rechnerische Semantik definiert ist. XSLT zum Beispiel ist eine vollständige Turing-Sprache, die ausschließlich XML-Syntax verwendet. Auch LaTeX, das hauptsächlich zur Strukturierung von Dokumenten verwendet wird, enthält eine vollständige Turing-Teilmenge.

Der Begriff Computersprache wird manchmal synonym mit Programmiersprache verwendet. Die Verwendung beider Begriffe variiert jedoch von Autor zu Autor, einschließlich des genauen Umfangs der beiden Begriffe. In einem Fall werden Programmiersprachen als eine Teilmenge der Computersprachen bezeichnet. In ähnlicher Weise werden Sprachen, die in der Informatik verwendet werden und ein anderes Ziel als den Ausdruck von Computerprogrammen haben, allgemein als Computersprachen bezeichnet. So werden z. B. Auszeichnungssprachen manchmal als Computersprachen bezeichnet, um zu betonen, dass sie nicht zum Programmieren verwendet werden sollen.

Ein anderer Sprachgebrauch betrachtet Programmiersprachen als theoretische Konstrukte zur Programmierung abstrakter Maschinen und Computersprachen als die Teilmenge davon, die auf physischen Computern läuft, die über endliche Hardware-Ressourcen verfügen. John C. Reynolds betont, dass formale Spezifikationssprachen ebenso Programmiersprachen sind wie die für die Ausführung vorgesehenen Sprachen. Er argumentiert auch, dass textuelle und sogar grafische Eingabeformate, die das Verhalten eines Computers beeinflussen, Programmiersprachen sind, obwohl sie in der Regel nicht Turing-komplett sind, und merkt an, dass die Unkenntnis von Programmiersprachenkonzepten der Grund für viele Fehler in Eingabeformaten ist.

Geschichte

Frühe Entwicklungen

Sehr frühe Computer, wie z. B. Colossus, wurden ohne ein gespeichertes Programm programmiert, indem man ihre Schaltkreise veränderte oder physikalische Steuerungen einstellte.

Etwas später konnten Programme in Maschinensprache geschrieben werden, wobei der Programmierer jeden Befehl in einer numerischen Form schreibt, die die Hardware direkt ausführen kann. Die Anweisung zum Addieren der Werte in zwei Speicherplätzen könnte beispielsweise aus drei Zahlen bestehen: einem "Opcode", der die Operation "Addieren" auswählt, und zwei Speicherplätzen. Die Programme wurden in dezimaler oder binärer Form von Lochkarten, Lochstreifen oder Magnetbändern eingelesen oder über Schalter an der Vorderseite des Computers umgeschaltet. Die Maschinensprachen wurden später als Programmiersprachen der ersten Generation (1GL) bezeichnet.

Der nächste Schritt war die Entwicklung der so genannten Programmiersprachen der zweiten Generation (2GL) oder Assemblersprachen, die immer noch eng mit der Befehlssatzarchitektur des jeweiligen Computers verbunden waren. Sie dienten dazu, das Programm für den Menschen besser lesbar zu machen und entlasteten den Programmierer von langwierigen und fehleranfälligen Adressberechnungen.

Die ersten höheren Programmiersprachen oder Programmiersprachen der dritten Generation (3GL) wurden in den 1950er Jahren geschrieben. Eine frühe Hochsprache, die für einen Computer entwickelt wurde, war Plankalkül, das zwischen 1943 und 1945 von Konrad Zuse für die deutsche Z3 entwickelt wurde. Sie wurde jedoch erst 1998 und 2000 implementiert.

Der 1949 von John Mauchly vorgeschlagene Short Code war eine der ersten Hochsprachen, die jemals für einen elektronischen Computer entwickelt wurden. Im Gegensatz zum Maschinencode stellten Short Code-Anweisungen mathematische Ausdrücke in verständlicher Form dar. Allerdings musste das Programm bei jeder Ausführung in Maschinencode übersetzt werden, was den Prozess wesentlich langsamer machte als die Ausführung des entsprechenden Maschinencodes.

Anfang der 1950er Jahre entwickelte Alick Glennie an der Universität von Manchester Autocode. Bei dieser Programmiersprache wurde ein Compiler verwendet, um die Sprache automatisch in Maschinencode umzuwandeln. Der erste Code und Compiler wurde 1952 für den Mark 1-Computer an der Universität Manchester entwickelt und gilt als die erste kompilierte Programmiersprache auf hoher Ebene.

Der zweite Autocode wurde 1954 von R. A. Brooker für den Mark 1 entwickelt und wurde "Mark 1 Autocode" genannt. Brooker entwickelte in den 1950er Jahren in Zusammenarbeit mit der Universität von Manchester auch einen Autocode für den Ferranti Mercury. Die Version für den EDSAC 2 wurde 1961 von D. F. Hartley vom Mathematischen Labor der Universität Cambridge entwickelt. Bekannt als EDSAC 2 Autocode, war es eine direkte Weiterentwicklung von Mercury Autocode, angepasst an die lokalen Gegebenheiten, und zeichnete sich durch seine für die damalige Zeit fortschrittliche Objektcode-Optimierung und Quellsprachendiagnose aus. Eine zeitgleiche, aber separate Entwicklung war Atlas Autocode, das für die Atlas 1 Maschine der Universität Manchester entwickelt wurde.

1954 wurde FORTRAN bei IBM von John Backus erfunden. Es war die erste weit verbreitete universelle Hochsprachenprogrammierung mit einer funktionalen Implementierung und nicht nur mit einem Entwurf auf Papier. Sie ist nach wie vor eine beliebte Sprache für Hochleistungsrechner und wird für Programme verwendet, mit denen die schnellsten Supercomputer der Welt bewertet und eingestuft werden.

Eine weitere frühe Programmiersprache wurde von Grace Hopper in den USA entwickelt und heißt FLOW-MATIC. Sie wurde zwischen 1955 und 1959 für die UNIVAC I bei Remington Rand entwickelt. Hopper stellte fest, dass die Kunden der Wirtschaftsinformatik mit der mathematischen Notation nicht zurechtkamen, und Anfang 1955 schrieben sie und ihr Team eine Spezifikation für eine englische Programmiersprache und implementierten einen Prototyp. Der FLOW-MATIC-Compiler wurde Anfang 1958 öffentlich zugänglich und war 1959 im Wesentlichen fertiggestellt. FLOW-MATIC hatte einen großen Einfluss auf die Entwicklung von COBOL, da zu dieser Zeit nur diese Sprache und ihr direkter Nachfolger AIMACO in Gebrauch waren.

Verfeinerung

Die zunehmende Verwendung von Hochsprachen führte zu einem Bedarf an Low-Level-Programmiersprachen oder Systemprogrammiersprachen. Diese Sprachen bieten in unterschiedlichem Maße Möglichkeiten, die zwischen Assemblersprachen und Hochsprachen liegen. Sie können zur Ausführung von Aufgaben verwendet werden, die einen direkten Zugriff auf Hardware-Einrichtungen erfordern, bieten aber dennoch Kontrollstrukturen auf höherer Ebene und Fehlerprüfung.

In der Zeit von den 1960er bis zu den späten 1970er Jahren wurden die heute gebräuchlichen Sprachparadigmen entwickelt:

  • APL führte die Array-Programmierung ein und beeinflusste die funktionale Programmierung.
  • ALGOL verfeinerte sowohl die strukturierte prozedurale Programmierung als auch die Disziplin der Sprachspezifikation; der "Revised Report on the Algorithmic Language ALGOL 60" wurde zu einem Modell dafür, wie spätere Sprachspezifikationen geschrieben wurden.
  • Lisp, 1958 eingeführt, war die erste dynamisch typisierte funktionale Programmiersprache.
  • In den 1960er Jahren war Simula die erste Sprache, die die objektorientierte Programmierung unterstützte; Mitte der 1970er Jahre folgte mit Smalltalk die erste "rein" objektorientierte Sprache.
  • C wurde zwischen 1969 und 1973 als Systemprogrammiersprache für das Unix-Betriebssystem entwickelt und ist nach wie vor beliebt.
  • Prolog, 1972 entwickelt, war die erste logische Programmiersprache.
  • Im Jahr 1978 baute ML ein polymorphes Typsystem auf Lisp auf und leistete damit Pionierarbeit für statisch typisierte funktionale Programmiersprachen.

Jede dieser Sprachen hat Nachkommen hervorgebracht, und die meisten modernen Programmiersprachen zählen mindestens eine von ihnen zu ihren Vorfahren.

In den 1960er und 1970er Jahren gab es auch eine beträchtliche Debatte über die Vorzüge der strukturierten Programmierung und darüber, ob Programmiersprachen so gestaltet werden sollten, dass sie sie unterstützen. Edsger Dijkstra argumentierte 1968 in einem berühmten Brief, der in den Communications of the ACM veröffentlicht wurde, dass Goto-Anweisungen aus allen höheren Programmiersprachen entfernt werden sollten.

Strukturierte Programmierung ist Anfang der 1970er Jahre auch aufgrund der Softwarekrise populär geworden. Es beinhaltet die Zerlegung eines Programms in Unterprogramme (prozedurale Programmierung) und die Beschränkung auf die drei elementaren Kontrollstrukturen Anweisungs-Reihenfolge, Verzweigung und Wiederholung.

Konsolidierung und Wachstum

Eine Auswahl von Programmiersprachen-Lehrbüchern; nur einige von Tausenden, die es gibt.

Die 1980er Jahre waren die Jahre der relativen Konsolidierung. C++ kombinierte objektorientierte und Systemprogrammierung. Die Regierung der Vereinigten Staaten standardisierte Ada, eine von Pascal abgeleitete Systemprogrammiersprache, die von Rüstungsunternehmen verwendet werden sollte. In Japan und anderswo wurden große Summen für die Erforschung der so genannten "fünften Generation" von Sprachen ausgegeben, die logische Programmierkonstrukte enthielten. Die Gemeinschaft der funktionalen Sprachen bemühte sich um die Standardisierung von ML und Lisp. Bei all diesen Bewegungen wurden keine neuen Paradigmen erfunden, sondern die in den vorangegangenen Jahrzehnten entwickelten Ideen weiterentwickelt.

Ein wichtiger Trend beim Sprachdesign für die Programmierung großer Systeme in den 1980er Jahren war die verstärkte Konzentration auf die Verwendung von Modulen oder großen organisatorischen Codeeinheiten. Modula-2, Ada und ML entwickelten in den 1980er Jahren bemerkenswerte Modulsysteme, die oft mit generischen Programmierkonstrukten verbunden waren.

Das rasche Wachstum des Internets Mitte der 1990er Jahre schuf Möglichkeiten für neue Sprachen. Perl, ursprünglich ein Unix-Skripting-Tool, das 1987 zum ersten Mal veröffentlicht wurde, setzte sich in dynamischen Websites durch. Java wurde für die serverseitige Programmierung verwendet, und virtuelle Bytecode-Maschinen wurden in kommerziellen Umgebungen mit ihrem Versprechen "Write once, run anywhere" wieder populär (UCSD Pascal war in den frühen 1980er Jahren eine Zeit lang populär gewesen). Diese Entwicklungen waren nicht grundlegend neu; vielmehr handelte es sich um Verfeinerungen vieler bestehender Sprachen und Paradigmen (auch wenn ihre Syntax oft auf der C-Familie von Programmiersprachen basierte).

Die Entwicklung von Programmiersprachen geht weiter, sowohl in der Industrie als auch in der Forschung. Zu den aktuellen Richtungen gehören Sicherheits- und Zuverlässigkeitsüberprüfungen, neue Arten der Modularität (Mixins, Delegierte, Aspekte) und Datenbankintegration wie LINQ von Microsoft.

Programmiersprachen der vierten Generation (4GL) sind Computerprogrammiersprachen, die darauf abzielen, eine höhere Abstraktionsebene der internen Computerhardware-Details zu bieten als 3GLs. Bei den Programmiersprachen der fünften Generation (5GL) handelt es sich um Programmiersprachen, die auf der Lösung von Problemen mit Hilfe von Beschränkungen beruhen, die dem Programm vorgegeben sind, anstatt einen vom Programmierer geschriebenen Algorithmus zu verwenden.

Elemente

Alle Programmiersprachen verfügen über einige primitive Bausteine für die Beschreibung von Daten und den darauf angewandten Prozessen oder Transformationen (wie die Addition zweier Zahlen oder die Auswahl eines Elements aus einer Sammlung). Diese primitiven Bausteine werden durch syntaktische und semantische Regeln definiert, die ihre Struktur bzw. Bedeutung beschreiben.

Syntax

Parse-Baum von Python-Code mit eingeschobener Tokenisierung
Die Syntaxhervorhebung wird häufig verwendet, um Programmierer bei der Erkennung von Elementen des Quellcodes zu unterstützen. Die obige Sprache ist Python.

Die Oberflächenform einer Programmiersprache wird als Syntax bezeichnet. Die meisten Programmiersprachen sind reine Textsprachen; sie verwenden Textabfolgen mit Wörtern, Zahlen und Interpunktion, ähnlich wie geschriebene natürliche Sprachen. Andererseits gibt es auch Programmiersprachen, die eher grafisch aufgebaut sind und visuelle Beziehungen zwischen Symbolen verwenden, um ein Programm zu spezifizieren.

Die Syntax einer Sprache beschreibt die möglichen Kombinationen von Symbolen, die ein syntaktisch korrektes Programm bilden. Die Bedeutung, die einer Kombination von Symbolen gegeben wird, wird von der Semantik behandelt (entweder formal oder fest in einer Referenzimplementierung kodiert). Da die meisten Sprachen textuell sind, befasst sich dieser Artikel mit der textuellen Syntax.

Die Syntax von Programmiersprachen wird normalerweise mit einer Kombination aus regulären Ausdrücken (für die lexikalische Struktur) und der Backus-Naur-Form (für die grammatikalische Struktur) definiert. Im Folgenden finden Sie eine einfache Grammatik, die auf Lisp basiert:

Ausdruck ::= Atom | Liste
atom ::= Zahl | Symbol
Zahl ::= [+-]?['0'-'9']+
symbol ::= ['A'-'Za'-'z'].*
Liste ::= '(' Ausdruck* ')' <span title="Aus: Englische Wikipedia, Abschnitt &quot;Syntax&quot;" class="plainlinks">[https://en.wikipedia.org/wiki/Programming_language#Syntax <span style="color:#dddddd">ⓘ</span>]</span>

Diese Grammatik spezifiziert das Folgende:

  • ein Ausdruck ist entweder ein Atom oder eine Liste;
  • ein Atom ist entweder eine Zahl oder ein Symbol;
  • eine Zahl ist eine ununterbrochene Folge von einer oder mehreren Dezimalziffern, wahlweise mit vorangestelltem Plus- oder Minuszeichen;
  • ein Symbol ist ein Buchstabe, gefolgt von null oder mehr beliebigen Zeichen (außer Leerzeichen); und
  • eine Liste ist ein übereinstimmendes Paar von Klammern mit null oder mehr Ausdrücken darin.

Die folgenden Beispiele sind wohlgeformte Token-Sequenzen in dieser Grammatik: 12345, () und (a b c232 (1)).

Nicht alle syntaktisch korrekten Programme sind auch semantisch korrekt. Viele syntaktisch korrekte Programme sind nach den Regeln der Sprache dennoch schlecht geformt und können (je nach Sprachspezifikation und Solidität der Implementierung) bei der Übersetzung oder Ausführung zu einem Fehler führen. In einigen Fällen können solche Programme ein undefiniertes Verhalten aufweisen. Selbst wenn ein Programm in einer Sprache wohldefiniert ist, kann es eine Bedeutung haben, die von der Person, die es geschrieben hat, nicht beabsichtigt war.

In der natürlichen Sprache ist es beispielsweise nicht möglich, einem grammatikalisch korrekten Satz eine Bedeutung zuzuordnen, oder der Satz kann falsch sein:

  • "Farblose grüne Ideen schlafen wütend." ist grammatikalisch wohlgeformt, hat aber keine allgemein akzeptierte Bedeutung.
  • "John ist ein verheirateter Junggeselle." ist grammatikalisch wohlgeformt, drückt aber eine Bedeutung aus, die nicht wahr sein kann.

Das folgende C-Sprachfragment ist syntaktisch korrekt, führt aber Operationen aus, die semantisch nicht definiert sind (die Operation *p >> 4 hat keine Bedeutung für einen Wert, der einen komplexen Typ hat, und p->im ist nicht definiert, weil der Wert von p der Null-Zeiger ist):

komplex *p = NULL;
komplex abs_p = sqrt(*p >> 4 + p->im); <span title="Aus: Englische Wikipedia, Abschnitt &quot;Syntax&quot;" class="plainlinks">[https://en.wikipedia.org/wiki/Programming_language#Syntax <span style="color:#dddddd">ⓘ</span>]</span>

Würde die Typdeklaration in der ersten Zeile weggelassen, würde das Programm bei der Kompilierung einen Fehler für die undefinierte Variable p auslösen. Das Programm wäre jedoch immer noch syntaktisch korrekt, da Typdeklarationen nur semantische Informationen liefern.

Die Grammatik, die zur Spezifikation einer Programmiersprache benötigt wird, kann anhand ihrer Position in der Chomsky-Hierarchie klassifiziert werden. Die Syntax der meisten Programmiersprachen kann mit einer Typ-2-Grammatik spezifiziert werden, d. h. es handelt sich um kontextfreie Grammatiken. Einige Sprachen, darunter Perl und Lisp, enthalten Konstrukte, die eine Ausführung während der Parsing-Phase ermöglichen. Sprachen, die über Konstrukte verfügen, die es dem Programmierer erlauben, das Verhalten des Parsers zu verändern, machen die Syntaxanalyse zu einem unentscheidbaren Problem und verwischen im Allgemeinen die Unterscheidung zwischen Parsen und Ausführen. Im Gegensatz zum Makrosystem von Lisp und den BEGIN-Blöcken von Perl, die allgemeine Berechnungen enthalten können, sind C-Makros lediglich Zeichenfolgenersetzungen und erfordern keine Codeausführung.

Semantik

Der Begriff Semantik bezieht sich auf die Bedeutung von Sprachen, im Gegensatz zu ihrer Form (Syntax).

Statische Semantik

Die statische Semantik definiert Einschränkungen für die Struktur gültiger Texte, die sich nur schwer oder gar nicht in standardmäßigen syntaktischen Formalismen ausdrücken lassen. Für kompilierte Sprachen umfasst die statische Semantik im Wesentlichen diejenigen semantischen Regeln, die zur Kompilierzeit überprüft werden können. Beispiele hierfür sind die Überprüfung, dass jeder Bezeichner deklariert ist, bevor er verwendet wird (in Sprachen, die solche Deklarationen verlangen), oder dass die Bezeichnungen an den Armen einer Case-Anweisung eindeutig sind. Viele wichtige Einschränkungen dieser Art, wie die Überprüfung, dass Bezeichner im richtigen Kontext verwendet werden (z. B. keine ganze Zahl an einen Funktionsnamen anhängen) oder dass Unterprogrammaufrufe die richtige Anzahl und den richtigen Typ von Argumenten haben, können durch die Definition von Regeln in einer Logik, die als Typsystem bezeichnet wird, durchgesetzt werden. Andere Formen der statischen Analyse wie die Datenflussanalyse können ebenfalls Teil der statischen Semantik sein. Neuere Programmiersprachen wie Java und C# haben eine definitive Zuweisungsanalyse, eine Form der Datenflussanalyse, als Teil ihrer statischen Semantik.

Dynamische Semantik

Sobald die Daten spezifiziert sind, muss die Maschine angewiesen werden, Operationen mit den Daten durchzuführen. Die Semantik kann zum Beispiel die Strategie festlegen, mit der Ausdrücke zu Werten ausgewertet werden, oder die Art und Weise, in der Kontrollstrukturen Anweisungen bedingt ausführen. Die dynamische Semantik (auch als Ausführungssemantik bezeichnet) einer Sprache legt fest, wie und wann die verschiedenen Konstrukte einer Sprache ein Programmverhalten erzeugen sollen. Es gibt viele Möglichkeiten, die Ausführungssemantik zu definieren. Natürliche Sprache wird oft verwendet, um die Ausführungssemantik von Sprachen zu spezifizieren, die in der Praxis häufig verwendet werden. Ein beträchtlicher Teil der akademischen Forschung hat sich mit der formalen Semantik von Programmiersprachen befasst, die es ermöglicht, die Ausführungssemantik auf formale Weise zu spezifizieren. Die Ergebnisse aus diesem Forschungsbereich finden außerhalb der akademischen Welt nur begrenzt Anwendung bei der Entwicklung und Implementierung von Programmiersprachen.

Typensystem

Ein Typsystem definiert, wie eine Programmiersprache Werte und Ausdrücke in Typen klassifiziert, wie sie diese Typen manipulieren kann und wie sie zusammenwirken. Das Ziel eines Typensystems ist es, ein bestimmtes Maß an Korrektheit in Programmen, die in dieser Sprache geschrieben wurden, zu verifizieren und in der Regel auch durchzusetzen, indem bestimmte fehlerhafte Operationen erkannt werden. Jedes entscheidbare Typsystem ist mit einem Kompromiss verbunden: Während es viele falsche Programme zurückweist, kann es auch einige korrekte, wenn auch ungewöhnliche Programme verhindern. Um diesen Nachteil zu umgehen, gibt es in einer Reihe von Sprachen Typ-Schlupflöcher, in der Regel ungeprüfte Würfe, die vom Programmierer verwendet werden können, um eine normalerweise verbotene Operation zwischen verschiedenen Typen ausdrücklich zu erlauben. In den meisten typisierten Sprachen wird das Typsystem nur zur Typüberprüfung von Programmen verwendet, aber eine Reihe von Sprachen, in der Regel funktionale Sprachen, leiten Typen ab und entbinden den Programmierer von der Notwendigkeit, Typkommentare zu schreiben. Der formale Entwurf und die Untersuchung von Typsystemen ist als Typentheorie bekannt.

Typisierte und nicht typisierte Sprachen

Eine Sprache ist typisiert, wenn die Spezifikation jeder Operation Datentypen definiert, auf die die Operation anwendbar ist. Zum Beispiel sind die Daten, die durch "diesen Text zwischen den Anführungszeichen" dargestellt werden, eine Zeichenkette, und in vielen Programmiersprachen hat die Division einer Zahl durch eine Zeichenkette keine Bedeutung und wird nicht ausgeführt. Die ungültige Operation kann bei der Kompilierung des Programms erkannt werden ("statische" Typprüfung) und wird vom Compiler mit einer Fehlermeldung zurückgewiesen, oder sie kann während der Ausführung des Programms erkannt werden ("dynamische" Typprüfung), was zu einer Laufzeitausnahme führt. Viele Sprachen erlauben eine Funktion, die als Exception Handler bezeichnet wird, um diese Ausnahme zu behandeln und z.B. immer "-1" als Ergebnis zurückzugeben.

Ein Sonderfall der typisierten Sprachen sind die einfach typisierten Sprachen. Dabei handelt es sich oft um Skript- oder Markup-Sprachen wie REXX oder SGML, die nur einen Datentyp haben - meist Zeichenketten, die sowohl für symbolische als auch für numerische Daten verwendet werden.

Im Gegensatz dazu erlaubt eine untypisierte Sprache, wie z. B. die meisten Assemblersprachen, die Durchführung beliebiger Operationen mit beliebigen Daten, im Allgemeinen Bitfolgen unterschiedlicher Länge. Zu den nicht typisierten Hochsprachen gehören BCPL, Tcl und einige Varianten von Forth.

In der Praxis gibt es zwar nur wenige Sprachen, die von der Typentheorie her als typisiert gelten (d.h. die alle Operationen verifizieren oder ablehnen), aber die meisten modernen Sprachen bieten ein gewisses Maß an Typisierung. Viele Produktionssprachen bieten die Möglichkeit, das Typsystem zu umgehen oder zu unterlaufen, indem sie die Typsicherheit gegen eine bessere Kontrolle über die Programmausführung eintauschen (siehe Casting).

Statische versus dynamische Typisierung

Bei der statischen Typisierung werden die Typen aller Ausdrücke vor der Ausführung des Programms festgelegt, in der Regel zur Kompilierungszeit. So sind beispielsweise 1 und (2+2) ganzzahlige Ausdrücke; sie können nicht an eine Funktion übergeben werden, die eine Zeichenkette erwartet, oder in einer Variablen gespeichert werden, die für Datumsangaben definiert ist.

Statisch typisierte Sprachen können entweder offenkundig typisiert oder typinfiziert sein. Im ersten Fall muss der Programmierer an bestimmten Textstellen (z. B. bei der Deklaration von Variablen) ausdrücklich Typen angeben. Im zweiten Fall erschließt der Compiler die Typen von Ausdrücken und Deklarationen aus dem Kontext. Die meisten gängigen statisch typisierten Sprachen, wie C++, C# und Java, sind offensichtlich typisiert. Vollständige Typinferenz wird traditionell mit weniger verbreiteten Sprachen wie Haskell und ML in Verbindung gebracht. Viele offenkundig typisierte Sprachen unterstützen jedoch eine partielle Typinferenz; C++, Java und C# beispielsweise leiten alle in bestimmten Fällen Typen ab. Darüber hinaus erlauben einige Programmiersprachen die automatische Umwandlung einiger Typen in andere Typen; so kann beispielsweise ein int verwendet werden, wo das Programm einen float erwartet.

Die dynamische Typisierung, auch latente Typisierung genannt, bestimmt die Typsicherheit von Operationen zur Laufzeit; mit anderen Worten, Typen sind mit Laufzeitwerten und nicht mit textuellen Ausdrücken verbunden. Wie bei typinfizierten Sprachen ist es bei dynamisch typisierten Sprachen nicht erforderlich, dass der Programmierer explizite Typ-Annotationen zu Ausdrücken schreibt. Dies kann unter anderem dazu führen, dass eine einzelne Variable an verschiedenen Stellen der Programmausführung auf Werte unterschiedlichen Typs verweisen kann. Typfehler können jedoch erst dann automatisch erkannt werden, wenn ein Teil des Codes tatsächlich ausgeführt wird, was die Fehlersuche erschweren kann. Lisp, Smalltalk, Perl, Python, JavaScript und Ruby sind alles Beispiele für dynamisch typisierte Sprachen.

Schwache und starke Typisierung

Die schwache Typisierung ermöglicht es, einen Wert eines Typs als einen anderen zu behandeln, z. B. eine Zeichenkette als eine Zahl. Dies kann gelegentlich nützlich sein, aber es kann auch dazu führen, dass einige Arten von Programmfehlern bei der Kompilierung und sogar bei der Ausführung unentdeckt bleiben.

Starke Typisierung verhindert diese Programmfehler. Der Versuch, eine Operation mit einem Wert des falschen Typs auszuführen, führt zu einem Fehler. Stark typisierte Sprachen werden oft als "typsicher" oder "sicher" bezeichnet.

Eine alternative Definition für "schwach typisiert" bezieht sich auf Sprachen wie Perl und JavaScript, die eine große Anzahl impliziter Typkonvertierungen zulassen. In JavaScript beispielsweise wandelt der Ausdruck 2 * x x implizit in eine Zahl um, und diese Umwandlung ist auch dann erfolgreich, wenn x null, undefiniert, ein Array oder eine Buchstabenkette ist. Solche impliziten Konvertierungen sind oft nützlich, können aber Programmierfehler verschleiern. Starke und statische Typisierung werden heute allgemein als orthogonale Konzepte betrachtet, aber die Verwendung in der Literatur ist unterschiedlich. Einige verwenden den Begriff stark typisiert für stark statisch typisiert oder, was noch verwirrender ist, für einfach statisch typisiert. So wurde C sowohl als stark typisiert als auch als schwach statisch typisiert bezeichnet.

Es mag einigen professionellen Programmierern seltsam erscheinen, dass C "schwach statisch typisiert" sein könnte. Es ist jedoch zu beachten, dass die Verwendung des generischen Zeigers, des void*-Zeigers, das Casting von Zeigern in andere Zeiger ermöglicht, ohne dass ein explizites Casting erforderlich ist. Dies ist vergleichbar mit der Umwandlung eines Arrays von Bytes in einen beliebigen Datentyp in C ohne explizite Umwandlung, wie (int) oder (char).

Standardbibliothek und Laufzeitsystem

Die meisten Programmiersprachen verfügen über eine zugehörige Kernbibliothek (manchmal auch als "Standardbibliothek" bezeichnet, insbesondere wenn sie Teil des veröffentlichten Sprachstandards ist), die üblicherweise von allen Implementierungen der Sprache zur Verfügung gestellt wird. Kernbibliotheken enthalten in der Regel Definitionen für häufig verwendete Algorithmen, Datenstrukturen und Mechanismen für die Eingabe und Ausgabe.

Die Grenze zwischen einer Sprache und ihrer Kernbibliothek ist von Sprache zu Sprache unterschiedlich. In einigen Fällen behandeln die Sprachentwickler die Bibliothek als eine von der Sprache getrennte Einheit. Die Kernbibliothek einer Sprache wird jedoch von ihren Benutzern oft als Teil der Sprache behandelt, und einige Sprachspezifikationen verlangen sogar, dass diese Bibliothek in allen Implementierungen zur Verfügung gestellt wird. Einige Sprachen sind sogar so konzipiert, dass die Bedeutung bestimmter syntaktischer Konstrukte nicht einmal beschrieben werden kann, ohne auf die Kernbibliothek zu verweisen. In Java beispielsweise ist ein String-Literal als Instanz der Klasse java.lang.String definiert; in ähnlicher Weise konstruiert in Smalltalk ein anonymer Funktionsausdruck (ein "Block") eine Instanz der BlockContext-Klasse der Bibliothek. Umgekehrt enthält Scheme mehrere kohärente Teilmengen, die ausreichen, um den Rest der Sprache als Bibliotheksmakros zu konstruieren, und so machen sich die Sprachdesigner nicht einmal die Mühe zu sagen, welche Teile der Sprache als Sprachkonstrukte und welche als Teile einer Bibliothek implementiert werden müssen.

Entwurf und Implementierung

Programmiersprachen haben mit natürlichen Sprachen die Eigenschaften gemeinsam, dass sie als Kommunikationsmittel dienen, dass sie eine von der Semantik getrennte syntaktische Form haben und dass sie Sprachfamilien von verwandten Sprachen darstellen, die sich voneinander ableiten. Aber als künstliche Konstrukte unterscheiden sie sich auch in grundlegender Weise von Sprachen, die sich durch den Gebrauch entwickelt haben. Ein wesentlicher Unterschied besteht darin, dass eine Programmiersprache in ihrer Gesamtheit beschrieben und untersucht werden kann, da sie eine präzise und endliche Definition hat. Im Gegensatz dazu haben natürliche Sprachen wechselnde Bedeutungen, die von ihren Benutzern in verschiedenen Gemeinschaften gegeben werden. Konstruierte Sprachen sind zwar auch künstliche Sprachen, die von Grund auf für einen bestimmten Zweck entwickelt wurden, doch fehlt ihnen die präzise und vollständige semantische Definition, die eine Programmiersprache hat.

Viele Programmiersprachen wurden von Grund auf neu entwickelt, verändert, um neuen Anforderungen gerecht zu werden, und mit anderen Sprachen kombiniert. Viele sind schließlich nicht mehr verwendet worden. Es wurde zwar versucht, eine "universelle" Programmiersprache zu entwickeln, die für alle Zwecke geeignet ist, aber alle haben sich nicht durchgesetzt, weil sie diese Rolle nicht erfüllen. Der Bedarf an verschiedenen Programmiersprachen ergibt sich aus der Vielfalt der Kontexte, in denen Sprachen verwendet werden:

  • Die Programme reichen von winzigen Skripten, die von einzelnen Hobbyisten geschrieben werden, bis hin zu riesigen Systemen, die von Hunderten von Programmierern entwickelt werden.
  • Die Erfahrung der Programmierer reicht von Anfängern, die vor allem auf Einfachheit Wert legen, bis hin zu Experten, die sich mit einer beträchtlichen Komplexität anfreunden können.
  • Programme müssen Geschwindigkeit, Größe und Einfachheit auf Systemen von Mikrocontrollern bis hin zu Supercomputern in Einklang bringen.
  • Programme können einmal geschrieben werden und über Generationen hinweg unverändert bleiben, oder sie können ständig geändert werden.
  • Programmierer haben einfach unterschiedliche Vorlieben: Sie sind es vielleicht gewohnt, Probleme zu diskutieren und sie in einer bestimmten Sprache auszudrücken.

Ein allgemeiner Trend bei der Entwicklung von Programmiersprachen besteht darin, mehr Möglichkeiten zur Lösung von Problemen auf einer höheren Abstraktionsebene zu schaffen. Die ersten Programmiersprachen waren sehr eng mit der zugrunde liegenden Hardware des Computers verbunden. Mit der Entwicklung neuer Programmiersprachen wurden Funktionen hinzugefügt, die es den Programmierern ermöglichen, Ideen auszudrücken, die von der einfachen Übersetzung in die zugrunde liegenden Hardwarebefehle weiter entfernt sind. Da die Programmierer weniger an die Komplexität des Computers gebunden sind, können ihre Programme mehr Rechenleistung mit weniger Aufwand für den Programmierer erbringen. Dadurch können sie mehr Funktionen pro Zeiteinheit schreiben.

Die Programmierung in natürlicher Sprache wurde als eine Möglichkeit vorgeschlagen, die Notwendigkeit einer speziellen Programmiersprache zu beseitigen. Dieses Ziel liegt jedoch noch in weiter Ferne und seine Vorteile sind umstritten. Edsger W. Dijkstra vertrat den Standpunkt, dass die Verwendung einer formalen Sprache unerlässlich ist, um die Einführung bedeutungsloser Konstrukte zu verhindern, und bezeichnete die Programmierung in natürlicher Sprache als "töricht". Alan Perlis lehnte die Idee in ähnlicher Weise ab. Hybride Ansätze wurden in Structured English und SQL verfolgt.

Die Entwickler und Benutzer einer Sprache müssen eine Reihe von Artefakten schaffen, die das Programmieren regeln und ermöglichen. Die wichtigsten dieser Artefakte sind die Sprachspezifikation und die Implementierung.

Spezifikation

Die Spezifikation einer Programmiersprache ist ein Artefakt, mit dessen Hilfe sich die Benutzer der Sprache und die Implementierer darauf einigen können, ob ein Stück Quellcode ein gültiges Programm in dieser Sprache ist, und wenn ja, wie es sich verhalten soll.

Eine Programmiersprachenspezifikation kann verschiedene Formen annehmen, darunter die folgenden:

  • Eine explizite Definition der Syntax, der statischen Semantik und der Ausführungssemantik der Sprache. Während die Syntax üblicherweise mit Hilfe einer formalen Grammatik spezifiziert wird, können semantische Definitionen in natürlicher Sprache (z.B. wie in der Sprache C) oder in einer formalen Semantik (z.B. wie in den Standard-ML- und Scheme-Spezifikationen) verfasst sein.
  • Eine Beschreibung des Verhaltens eines Übersetzers für die Sprache (z. B. die C++- und Fortran-Spezifikationen). Die Syntax und Semantik der Sprache müssen aus dieser Beschreibung abgeleitet werden, die in einer natürlichen oder einer formalen Sprache verfasst sein kann.
  • Eine Referenz- oder Modellimplementierung, die manchmal in der spezifizierten Sprache geschrieben ist (z. B. Prolog oder ANSI REXX). Die Syntax und Semantik der Sprache sind im Verhalten der Referenzimplementierung explizit enthalten.

Implementierung

Um ein in einer bestimmten Programmiersprache erstelltes Programm ausführen zu können, muss anstatt dessen Quellcode eine äquivalente Folge von Maschinenbefehlen ausgeführt werden. Das ist notwendig, da der Quellcode aus Zeichenfolgen besteht (z. B. „A = B + 100 * C“), die der Prozessor nicht „versteht“.

Die in der Geschichte der Computertechnik und der Softwaretechnologie eingetretenen Entwicklungssprünge brachten auch unterschiedliche Werkzeuge zur Erzeugung von Maschinencode, ggf. über mehrere Stufen, mit sich. Diese werden beispielsweise als Compiler, Interpreter, Precompiler, Linker etc. bezeichnet.

In Bezug auf die Art und den Zeitpunkt, wie der Computer zu einem äquivalenten Maschinencode kommt, können zwei Prinzipien unterschieden werden:

  • Wird ein Programmtext als Ganzes „übersetzt“, also aus dem Quelltext ein Maschinenprogramm erstellt, so spricht man in Bezug auf den Übersetzungsmechanismus von einem Compiler. Der Compiler selbst ist ein Programm, das als Dateneingabe den Programm-Quellcode liest und als Datenausgabe den Maschinencode (z. B. Objectcode, EXE-Datei, „executable“) oder einen Zwischencode liefert.
  • Wenn abhängig vom Programmtext während der Ausführung entsprechende Maschinencodeblöcke ausgeführt werden, spricht man von einer interpretierten Sprache. Das Programm wird in einer Laufzeitumgebung (z. B. veraltete JVM) interpretiert und je nach Programmbefehl ein entsprechender Maschinenbefehlblock ausgeführt.

Daneben existieren verschiedene Mischvarianten:

  • Bei der „Just-in-Time-Kompilierung“ wird der Programmtext direkt vor jedem Programmlauf neu übersetzt; ggf. werden erst während des (interpretierten) Programmlaufs einzelne Programmabschnitte kompiliert.
  • Zum Teil erzeugen Compiler einen noch nicht ausführbaren Programmcode, der von nachfolgenden Systemprogrammen zu ausführbarem Maschinencode umgeformt wird. Hier sind die Konzepte „plattformunabhängiger Zwischencode“ (z. B. im Rahmen der Software-Verteilung) und „plattformgebundener Objektcode“ (wird zusammen mit weiteren Modulen zu ausführbarem Code, z. T. Lademodul genannt, zusammengebunden) zu unterscheiden.
  • Mit Precompilern können spezielle, in der Programmiersprache selbst nicht vorgesehene Syntax-Konstrukte (zum Beispiel Entscheidungstabellen) bearbeitet und, vor-übersetzt in die gewählte Programmiersprache, im Quellcode eingefügt werden.

Zur Steuerung des Übersetzens kann der Quelltext neben den Anweisungen der Programmiersprache zusätzliche spezielle Compiler-Anweisungen enthalten. Komplexe Übersetzungsvorgänge werden bei Anwendung bestimmter Programmiersprachen / Entwicklungsumgebungen durch einen Projekterstellungsprozess und die darin gesetzten Parameter gesteuert.

Eine Implementierung einer Programmiersprache bietet eine Möglichkeit, Programme in dieser Sprache zu schreiben und sie auf einer oder mehreren Konfigurationen von Hardware und Software auszuführen. Es gibt im Wesentlichen zwei Ansätze für die Implementierung von Programmiersprachen: Kompilierung und Interpretation. Im Allgemeinen ist es möglich, eine Sprache mit einer der beiden Techniken zu implementieren.

Die Ausgabe eines Compilers kann von der Hardware oder einem Programm, dem Interpreter, ausgeführt werden. Bei einigen Implementierungen, die den Interpreter-Ansatz verwenden, gibt es keine klare Grenze zwischen Kompilieren und Interpretieren. Beispielsweise kompilieren einige BASIC-Implementierungen den Quelltext und führen ihn dann Zeile für Zeile aus.

Programme, die direkt auf der Hardware ausgeführt werden, laufen in der Regel viel schneller als solche, die in Software interpretiert werden.

Eine Technik zur Verbesserung der Leistung von interpretierten Programmen ist die Just-in-Time-Kompilierung. Dabei übersetzt die virtuelle Maschine kurz vor der Ausführung die zu verwendenden Bytecode-Blöcke in Maschinencode, der dann direkt auf der Hardware ausgeführt wird.

Proprietäre Sprachen

Obwohl die meisten der gebräuchlichsten Programmiersprachen über vollständig offene Spezifikationen und Implementierungen verfügen, existieren viele Programmiersprachen nur als proprietäre Programmiersprachen, deren Implementierung nur von einem einzigen Anbieter erhältlich ist, der für sich in Anspruch nehmen kann, dass eine solche proprietäre Sprache sein geistiges Eigentum ist. Bei proprietären Programmiersprachen handelt es sich in der Regel um domänenspezifische Sprachen oder interne Skriptsprachen für ein einzelnes Produkt; einige proprietäre Sprachen werden nur intern innerhalb eines Anbieters verwendet, während andere für externe Benutzer verfügbar sind.

Einige Programmiersprachen bewegen sich an der Grenze zwischen proprietär und offen; so macht beispielsweise die Oracle Corporation proprietäre Rechte an einigen Aspekten der Programmiersprache Java geltend, und die Programmiersprache C# von Microsoft, die für die meisten Teile des Systems offene Implementierungen aufweist, verfügt auch über die Common Language Runtime (CLR) als geschlossene Umgebung.

Viele proprietäre Sprachen sind trotz ihrer proprietären Natur weit verbreitet; Beispiele sind MATLAB, VBScript und Wolfram Language. Einige Sprachen können den Übergang von einer geschlossenen zu einer offenen Umgebung vollziehen; so war Erlang ursprünglich eine interne Programmiersprache von Ericsson.

Verwenden Sie

Es wurden Tausende verschiedener Programmiersprachen entwickelt, vor allem im Bereich der Informatik. Einzelne Softwareprojekte verwenden in der Regel fünf oder mehr Programmiersprachen.

Programmiersprachen unterscheiden sich von den meisten anderen menschlichen Ausdrucksformen dadurch, dass sie ein höheres Maß an Präzision und Vollständigkeit erfordern. Bei der Verwendung einer natürlichen Sprache zur Kommunikation mit anderen Menschen können menschliche Autoren und Sprecher mehrdeutig sein und kleine Fehler machen, und trotzdem erwarten, dass ihre Absicht verstanden wird. Im übertragenen Sinne tun Computer jedoch "genau das, was man ihnen sagt", und können nicht "verstehen", welchen Code der Programmierer zu schreiben beabsichtigte. Die Kombination aus der Sprachdefinition, einem Programm und den Eingaben des Programms muss das externe Verhalten, das bei der Ausführung des Programms auftritt, innerhalb des Kontrollbereichs des Programms vollständig spezifizieren. Andererseits können Ideen über einen Algorithmus dem Menschen ohne die für die Ausführung erforderliche Präzision vermittelt werden, indem Pseudocode verwendet wird, der die natürliche Sprache mit dem in einer Programmiersprache geschriebenen Code verschmilzt.

Eine Programmiersprache bietet einen strukturierten Mechanismus zur Definition von Datenteilen und der Operationen oder Transformationen, die automatisch auf diesen Daten ausgeführt werden können. Ein Programmierer verwendet die in der Sprache vorhandenen Abstraktionen, um die an einer Berechnung beteiligten Konzepte darzustellen. Diese Konzepte werden als eine Sammlung der einfachsten verfügbaren Elemente (Primitive genannt) dargestellt. Programmieren ist der Prozess, bei dem Programmierer diese Primitive kombinieren, um neue Programme zu erstellen oder bestehende Programme an neue Verwendungszwecke oder eine veränderte Umgebung anzupassen.

Programme für einen Computer können in einem Batch-Prozess ohne menschliche Interaktion ausgeführt werden, oder ein Benutzer kann Befehle in einer interaktiven Sitzung eines Interpreters eingeben. In diesem Fall sind die "Befehle" einfach Programme, deren Ausführung aneinander gekoppelt ist. Wenn eine Sprache ihre Befehle über einen Interpreter (z. B. eine Unix-Shell oder eine andere Befehlszeilenschnittstelle) ausführen kann, ohne zu kompilieren, nennt man sie eine Skriptsprache.

Messung der Sprachverwendung

Die Bestimmung der am weitesten verbreiteten Programmiersprache ist schwierig, da die Definition der Verwendung je nach Kontext variiert. Eine Sprache kann die meisten Programmierstunden beanspruchen, eine andere hat mehr Codezeilen, und eine dritte verbraucht vielleicht die meiste CPU-Zeit. Einige Sprachen sind für bestimmte Arten von Anwendungen sehr beliebt. So ist COBOL in den Rechenzentren von Unternehmen, oft auf großen Mainframes, nach wie vor stark vertreten; Fortran in wissenschaftlichen und technischen Anwendungen; Ada in der Luft- und Raumfahrt, im Transportwesen, im Militär, in Echtzeit- und eingebetteten Anwendungen; und C in eingebetteten Anwendungen und Betriebssystemen. Andere Sprachen werden regelmäßig zum Schreiben vieler verschiedener Arten von Anwendungen verwendet.

Es wurden verschiedene Methoden zur Messung der Sprachpopularität vorgeschlagen, die jeweils eine unterschiedliche Ausrichtung auf das, was gemessen wird, aufweisen:

  • Zählen der Anzahl der Stellenanzeigen, in denen die Sprache erwähnt wird
  • die Anzahl der verkauften Bücher, in denen die Sprache gelehrt oder beschrieben wird
  • Schätzungen der Anzahl der vorhandenen Codezeilen, die in der Sprache geschrieben wurden - wobei Sprachen, die bei öffentlichen Suchanfragen nicht häufig gefunden werden, möglicherweise unterschätzt werden
  • die Anzahl der Sprachreferenzen (d. h. der Verweise auf den Namen der Sprache), die über eine Internetsuchmaschine gefunden wurden.

Durch Kombination und Mittelwertbildung von Informationen aus verschiedenen Internetseiten hat stackify.com die folgenden zehn beliebtesten Programmiersprachen ermittelt (in absteigender Reihenfolge nach Gesamtpopularität): Java, C, C++, Python, C#, JavaScript, VB .NET, R, PHP und MATLAB.

Dialekte, Geschmacksrichtungen und Implementierungen

Ein Dialekt einer Programmiersprache oder einer Datenaustauschsprache ist eine (relativ kleine) Variation oder Erweiterung der Sprache, die ihre eigentliche Natur nicht verändert. Bei Sprachen wie Scheme und Forth können Standards von den Implementierern als unzureichend, unangemessen oder unzulässig angesehen werden, so dass sie oft vom Standard abweichen und einen neuen Dialekt entwickeln. In anderen Fällen wird ein Dialekt für die Verwendung in einer domänenspezifischen Sprache, oft einer Untermenge, erstellt. In der Lisp-Welt werden die meisten Sprachen, die eine grundlegende S-Ausdruckssyntax und eine Lisp-ähnliche Semantik verwenden, als Lisp-Dialekte betrachtet, obwohl sie sich stark unterscheiden, wie beispielsweise Racket und Clojure. Da es für eine Sprache häufig mehrere Dialekte gibt, kann es für einen unerfahrenen Programmierer ziemlich schwierig werden, die richtige Dokumentation zu finden. Die Programmiersprache BASIC hat viele Dialekte.

Die explosionsartige Zunahme von Forth-Dialekten führte zu dem Sprichwort "Wenn du ein Forth gesehen hast... hast du ein Forth gesehen."

Taxonomien

Es gibt kein übergreifendes Klassifikationsschema für Programmiersprachen. Eine bestimmte Programmiersprache hat in der Regel nicht nur eine einzige Vorgängersprache. Sprachen entstehen in der Regel durch die Kombination von Elementen mehrerer Vorgängersprachen mit neuen Ideen, die zu dieser Zeit im Umlauf sind. Ideen, die ihren Ursprung in einer Sprache haben, verbreiten sich in einer Familie verwandter Sprachen, um dann plötzlich über die Lücken in der Familie hinweg in einer ganz anderen Familie aufzutauchen.

Die Aufgabe wird zusätzlich durch die Tatsache erschwert, dass Sprachen entlang mehrerer Achsen klassifiziert werden können. Java zum Beispiel ist sowohl eine objektorientierte Sprache (weil sie eine objektorientierte Organisation fördert) als auch eine nebenläufige Sprache (weil sie eingebaute Konstrukte für die parallele Ausführung mehrerer Threads enthält). Python ist eine objektorientierte Skriptsprache.

In groben Zügen lassen sich Programmiersprachen in Programmierparadigmen und eine Klassifizierung nach dem beabsichtigten Anwendungsbereich unterteilen, wobei zwischen allgemeinen und bereichsspezifischen Programmiersprachen unterschieden wird. Traditionell wurden Programmiersprachen so betrachtet, dass sie Berechnungen in Form von imperativen Sätzen beschreiben, d. h. Befehle erteilen. Diese werden allgemein als imperative Programmiersprachen bezeichnet. Ein großer Teil der Forschung im Bereich der Programmiersprachen zielt darauf ab, die Unterscheidung zwischen einem Programm als einer Reihe von Anweisungen und einem Programm als einer Aussage über die gewünschte Antwort zu verwischen, was das Hauptmerkmal der deklarativen Programmierung ist. Zu den verfeinerten Paradigmen gehören die prozedurale Programmierung, die objektorientierte Programmierung, die funktionale Programmierung und die logische Programmierung; einige Sprachen sind Mischformen von Paradigmen oder multiparadigmatisch. Eine Assemblersprache ist weniger ein Paradigma als vielmehr ein direktes Modell einer zugrunde liegenden Maschinenarchitektur. Je nach Zweck können Programmiersprachen als Allzwecksprachen, Systemprogrammiersprachen, Skriptsprachen, domänenspezifische Sprachen oder nebenläufige/verteilte Sprachen (oder eine Kombination dieser Sprachen) betrachtet werden. Einige Allzwecksprachen wurden hauptsächlich mit pädagogischen Zielen entwickelt.

Eine Programmiersprache kann auch nach Faktoren klassifiziert werden, die nichts mit dem Programmierparadigma zu tun haben. So verwenden beispielsweise die meisten Programmiersprachen englische Schlüsselwörter, während eine Minderheit dies nicht tut. Andere Sprachen können als absichtlich esoterisch oder nicht esoterisch eingestuft werden.

Paradigmen in Programmiersprachen (Auswahl)
Name funktional imperativ objektorientiert deklarativ logisch nebenläufig
Ada X X X
C X
Prolog X X
Scheme X X (X) X (X)
Haskell X (X) X (X)
Scala X (X) X (X) X

Übersicht

Panorama

Die Bedeutung von Programmiersprachen für die Informatik drückt sich auch in der Vielfalt der Ausprägungen und der Breite der Anwendungen aus.

  • Maschinensprache, Assemblersprachen oder C erlauben eine hardwarenahe Programmierung.
  • Höhere Programmiersprachen erlauben komfortableres, schnelleres Programmieren.
  • Skriptsprachen dienen zur einfachen Steuerung von Rechnern, wie bei der Stapelverarbeitung.
  • Sprachen mit visuellen Programmierumgebungen erleichtern die graphische Gestaltung von Benutzeroberflächen.
  • Esoterische Programmiersprachen sind experimentelle Sprachen mit unüblichen Programmierkonzepten und/oder Berücksichtigung themenfremder Aspekte, z. B. ästhetisches Aussehen des Quellcodes.
  • Grafische Programmiersprachen sollen einen besonders leichten Zugang zum Programmieren bieten; statt Quelltext zu schreiben, kann das Programm aus Verarbeitungsblöcken zusammengeklickt werden.
  • Minisprachen sollen Kinder früh ans Programmieren heranführen (nicht zu verwechseln mit minilanguages, einem Synonym für domain-specific languages).

Umgangssprachlich wird auch in anderen Bereichen von Programmiersprachen gesprochen. Nachfolgende Sprachen sind jedoch nicht für die Beschreibung von Algorithmen und allgemeine Datenverarbeitung entworfen, also keine General Purpose Languages:

  • Auszeichnungssprachen werden für die Formatierung von Texten und Dateien verwendet.
  • CNC-Programmiersprachen sind (oder dienen der Erzeugung von) Steuerungsinformationen für Werkzeugmaschinen.
  • Datenbanksprachen sind für den Einsatz in und die Abfrage von Datenbanken gedacht.
  • Seitenbeschreibungssprachen sowie sonstige Beschreibungssprachen (z. B. VHDL) sind eine imperative Form eines Dateiformats.
  • Stylesheet-Sprachen werden verwendet um das Erscheinungsbild zu bestimmen

Derartige Sprachen fallen unter die domänenspezifischen Sprachen.

Anweisungskategorien

Die Anweisungen von Programmiersprachen (Beispiele siehe hier) lassen sich nach folgenden Gruppen klassifizieren:

  • Eingabe- und Ausgabe-Befehle – lesen Daten von der Tastatur, von einer Datei oder aus anderen Quellen ein oder sie geben sie auf/über ein bestimmtes Ausgabegerät (Bildschirm, Datei, Drucker, …) aus.
  • Zuweisungen und Berechnungen – verändern oder erzeugen Dateninhalte.
  • Kontrollflussanweisungen: Entscheidungsanweisungen (auch Verzweigungsanweisungen), Iterationsanweisungen, Sprunganweisungen entscheiden aufgrund der vorliegenden Daten, welche Befehle als Nächstes ausgeführt werden.
  • Deklarationen – reservieren Speicherplatz für Variablen oder Datenstrukturen unter einem fast frei wählbaren Namen. Über diesen Namen können sie später angesprochen werden.
  • Aufrufe „programm-externer“ Unterroutinen/Module wie Systemfunktionen (z. B. „Read“) oder funktionaler Module, auch aus anderen Programmiersprachen.

Imperative Programmiersprachen

Ein in einer imperativen Programmiersprache geschriebenes Programm besteht aus Anweisungen (latein. imperare = befehlen), die beschreiben, wie das Programm seine Ergebnisse erzeugt (zum Beispiel Wenn-dann-Folgen, Schleifen, Multiplikationen etc.).

Deklarative Programmiersprachen

Den genau umgekehrten Ansatz verfolgen die deklarativen Programmiersprachen. Dabei beschreibt der Programmierer, welche Bedingungen die Ausgabe des Programms (das Was) erfüllen muss. Wie die Ergebnisse konkret erzeugt werden, wird bei der Übersetzung, zum Beispiel durch einen Interpreter festgelegt. Ein Beispiel ist die Datenbankabfragesprache SQL.

Ein Programm muss nicht unbedingt eine Liste von Anweisungen enthalten. Stattdessen können grafische Programmieransätze, zum Beispiel wie bei der in der Automatisierung verwendeten Plattform STEP 7, benutzt werden.

Die Art der formulierten Bedingungen unterteilen die deklarativen Programmiersprachen in logische Programmiersprachen, die mathematische Logik benutzen, und funktionale Programmiersprachen, die dafür mathematische Funktionen einsetzen.

Objektorientierte Programmiersprachen

Im Gegensatz zur prozeduralen Programmierung, wo zuerst die verarbeitenden Prozeduren im Fokus stehen („Was will ich rechnen?“) und die Daten „irgendwie durchgeschleust“ werden, konzentriert sich die objektorientierte Programmierung zunächst auf die Daten: „Mit welchen Dingen (der Real-/Außenwelt) soll gearbeitet werden? Welche Attribute/Daten beschreiben diese (→ Objekt-Klassen)?“ Erst anschließend wird die Handhabung zu den Objekten entworfen (→ Methoden, „was kann man mit diesem Objekt machen? Was kann dieses Objekt für das Programm machen?“). Die Methoden werden den Daten zugeordnet, und zusammen werden beide in Objekten/Objekt-Klassen zusammengefasst. Objektorientierung verringert die Komplexität der entstehenden Programme, macht sie wiederverwendbarer und bildet die Realität meist genauer ab als dies bei rein prozeduraler Programmierung der Fall ist.

Objektorientierung bietet die folgenden Paradigmen:

Datenkapselung
Verbergen von Implementierungsdetails: Ein Objekt bietet dem Verwender eine festgelegte Menge an Möglichkeiten (Methoden), es zu ändern, zu beeinflussen, etwas zu berechnen oder Auskünfte zu erhalten. Darüber hinausgehende Hilfsroutinen oder Zustandsspeicher werden verborgen, auf sie kann nicht (direkt) zugegriffen werden.
Vererbung, Spezialisierung und Generalisierung
Vererbung heißt vereinfacht, dass eine abgeleitete Klasse die Methoden und Attribute der Basisklasse ebenfalls besitzt, also erbt. Zudem kann sie zusätzliche Attribute und Eigenschaften besitzen und zusätzliche Handlungsmöglichkeiten bieten – eine abgeleitete Klasse ist ein „Spezialfall“ der Basisklasse.
Umgekehrt kann gleiche Funktionalität mehrerer Klassen in eine gemeinsamen Basisklasse „ausgelagert“ werden, wo sie nur noch 1 Mal programmiert ist, was Code spart, leichter wartbar ist und ggf. für weitere Spezialklassen wiederverwendbar ist – sie erben einfach von dieser Basisklasse; die Basisklasse beschreibt das generelle Verhalten aller abgeleiteten (Spezial-)Klassen.
Polymorphie
Ein Objekt einer Spezialklasse kann stets auch als Mitglied der Basisklasse betrachtet werden. Dadurch kann in einer Variable, die ein Objekt der Basisklasse aufnehmen kann, auch ein Objekt einer abgeleiteten Klasse gespeichert werden, denn aufgrund der Vererbung bietet es ja die Methoden und Attribute der Basisklasse.

Sonstiges

Ein beliebter Einstieg in eine Programmiersprache ist es, mit ihr den Text Hello World (oder deutsch „Hallo Welt“) auf den Bildschirm oder einem anderen Ausgabegerät auszugeben (siehe Hallo-Welt-Programm). Entsprechend gibt es Listen von Hallo-Welt-Programmen und eigene Webseiten, die beispielhafte Implementierungen in verschiedenen Programmiersprachen gegenüberstellen.