Artikel:Programmierung

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Programme sind Listen von Anweisungen. Unabhängig von der jeweils verwendeten Sprache gibt es dabei bestimmte Konzepte, die das Wesen der Programmierung ausmachen.

Computer verstehen lediglich Operationen, bei denen es um Bits (0/1) geht. Komplexere Anweisung sind durch Verkettung dieser Operationen möglich. Jede Programmiersprache ist lediglich eine Abstraktion dieser Operationen, um für Menschen leichter verständlich zu sein.

Die folgenden Beispiele sind keiner besonderen Programmiersprache zugehörig und dienen nur der Illustration der jeweiligen Konzepte.

Inhaltsverzeichnis

[Bearbeiten] Computer

Computer sind letztlich nur extrem schnelle Taschenrechner mit mehr Speicher und einigen Extras. Wie ein Taschenrechner rechnet auch ein Computer, ganz gleich, was er gerade macht, nur mit 0 und 1 – das aber sehr oft und sehr schnell.

Zahlen kann man auch mit 0 und 1 zählen: 0, 1, 10, 11, 100, 101, 110, 111, …. Die Zahlenbasis ist die 2 und diese Schreibweise nennt man „binär“. Buchstaben und andere Zeichen kann der Computer verarbeiten, indem er diese wieder zu Zahlen macht: A => 65, B => 66, …. (Ausführliche Informationen diesem Thema siehe Zeichenkodierung und geschriebene Sprache.) Mehrere Zeichen ergeben Strings, mehrere Zahlen-, Zeichen- oder Stringwerte kann man in Arrays (Listen) oder Hashes bzw. Dictionaries (Wörterbücher) speichern. Aus sehr einfachen Konzepten ergibt sich so ungeahnte Komplexität.

Glücklicherweise muss man sich in der modernen Programmierung nicht mehr mit Nullen und Einsen herumschlagen, aber es schadet durchaus nicht, im Hinterkopf zu behalten, was hinter den Kulissen passiert.

[Bearbeiten] Schreibweisen

Unterschiedliche Programmiersprachen haben unterschiedliche Schreibweisen des Codes. Dabei gibt es im Allgemeinen recht wenige Konzepte, nach denen man die Sprachen untergliedern kann. C und C-ähnliche Sprachen wie Java, JavaScript, PHP und einige mehr verlangen ein Semikolon am Ende einer Anweisung, andere nicht.

Einige andere Sprachen verwenden weitere Konzepte zur Sprachgliederung, bspw. muss bei Python ein Anweisungsblock die gleichen Einrückungszeichen haben.

Die Sprache Scheme und einige Lisp-Derivate verlangen Reverse Polish Notation (RPN), bei dem die Operatoren vor den Werten stehen, bspw. + 1 4. Diese Sprachen bilden jedoch eine weitere Minderheit.

[Bearbeiten] Variablen und Typen

Was auch immer man programmiert, man wird nicht darum herumkommen, mit Daten umzugehen, die irgendwie gespeichert werden müssen.

v = 2

Hier wird eine Variable v mit dem Wert 2 geladen. Bei automatischer Typenerkennung wie etwa in Basic ist v nun eine Zahl mit dem Wert 2. Sprachen, die starke Typisierung verwenden, bspw. C, können diesen Code nicht ausführen, da vorher mitgeteilt werden muss, in welchem Format die Variable geladen werden soll:

int v = 2;

Dabei geht es darum, wie der Computer die Werte speichert. Dies geschieht immer binär, d.h. nur mit den Zahlen 0 und 1. Je nachdem, wie viele Bits verwendet werden, um so mehr Speicher wird belegt und um so länger dauern die Operationen.

// kleiner als Zahlen

0bit:   NULL-Wert, undefiniert, nil, Variable nicht gesetzt

1bit:   Bool'sche Werte, können Wahr oder Falsch (0/1, true/false) sein

// Zahlen

8bit:   Integerzahl 0..255

16bit:  Word, 0..65535

32bit:  Double-Word, 0..4294967295

64bit:  Quadruple-Word, 0..18446744073709551615

~8b:    Float, Fließkommazahl, Genauigkeit je nach Sprache

// Strings

1b:     Char, einzelnes Zeichen

> 1b:   String, Zeichenkette

/* Programmiersprachen gehen teilweise unterschiedlich mit Strings um:
   manche speichern zuerst die Länge und dann die Zeichen, andere wiederum
   speichern die Zeichen und dann ein 0-Byte. Das spielt dann eine 
   wesentliche Rolle, wenn Strings entweder verändert werden oder ein
   0-Byte enthalten sollen, dass dann kodiert werden muss. */

Das sind zunächst einmal die Standardtypen, mit denen man arbeiten kann. Außerdem gibt es je nach Sprache auch noch mehrdimensionale Objekte (teilweise vom gleichen Typ, aber mit unterschiedlichen Eigenschaften).

array = [1,2,3];

Arrays sind Tabellen mit fortlaufender numerischer Indizierung, d.h. jedes Objekt innerhalb des Arrays ist über eine Nummer abzufragen. Ob diese Nummer ab 0 oder 1 gezählt wird, hängt von der Sprache ab. In einigen Sprachen können Arrays auch Werte unterschiedlicher Typen aufnehmen. Ein Verschachteln von Arrays ist ebenfalls möglich.

hash = { "a":"b", "c":2, "d":[3,4,5] };

Hashes oder Dictionaries sind Tabellen, deren Objekte über ein weiteres Objekt indiziert werden. In der Regel kommen als Indizes nur Skalare (einfache Werte wie Zahlen oder Strings) in Frage. Einige Sprachen nennen Hashes auch assoziative Arrays oder Dictionaries und verwenden für Arrays und Hashes nahezu die gleiche Notation.

function greet(adresse) {
  say('Hallo, ' + adresse);
}

Manche Compiler-Sprachen kennen auch die Verwendung von Macros. Dabei handelt es sich um Code-Blöcke, die mehrmals verwendet werden. Im Gegensatz zu Funktionen werden sie beim Übersetzen ersetzt, anstatt einen Aufruf zu erzeugen.

[Bearbeiten] Kontrollstrukturen

Programmiersprachen würden allenfalls als bessere Taschenrechner taugen, wenn sie nicht mit Kontrollstrukturen gesegnet wären. Mit Kontrollstrukturen kann man jene Dynamik umsetzen, die Programme ausmachen. Es gibt einige klassische Kontrollstrukturen, von denen nicht jede Sprache alle beherrscht:

if (i == 1) { echo 'eins'; }

Wenn eine Bedingung erfüllt ist, führe den nachfolgenden Code aus. Optional gibt es noch ein Alternativen-Konstrukt:

if (i == 1) { echo 'eins'; } else { echo 'nicht eins'; }

Diesmal wird für den Fall, dass die Bedingung nicht gegeben ist, der zweite Code-Block ausgeführt.

while (i != 1) { echo i; }

Solange die Bedingung erfüllt ist, führe den nachfolgenden Code wieder und wieder aus.

do { echo i; } until (i == 1);

Führe den Code so oft aus, bis die Bedingung erfüllt ist.

for (var i=0; i < 10; i++) { echo i; }

Führe den Code aus, solange i kleiner als 10 ist und erhöhe i bei jedem Durchgang um eins. Viele Sprachen kennen eine vereinfachte Syntax, etwa manche Basic-Varianten oder Lua:

for i=1,10 do print(i) end

Hier werden die Argumente Start, Ende (und optional Schrittweite) ohne Funktionsaufruf übergeben. Dabei handelt es sich aber nur um unterschiedliche Schreibweisen.

switch (i) {
  case 1:
    echo 'eins';
    break;
  case 2:
    echo 'zwei';
    break;
  default:
    echo 'weder eins noch zwei';
    break;
}

Führe je nach Inhalt den nachstehenden Code aus. Diesen Befehl gibt es nicht in jeder Sprache, er läßt sich üblicherweise aber verhältnismäßig einfach nachbauen, entweder mit einem Hash, der Funktionen enthält oder einem if … elseif … else mit den gleichen Werten.

Für den PC kommt es letztlich auf das Gleiche hinaus. Er kennt nur den Assembler-Befehl cmp (Compare – Vergleiche), mit dem man zwei Werte vergleichen kann – der nächste Befehl ist dann ein Sprung, der nur dann ausgeführt wird, wenn dessen Bedingung erfüllt ist, bspw. jz = Jump Zero: springe, wenn 0 oder jge = Jump Greater Equal: springe, wenn größer gleich.

[Bearbeiten] Mathematik

Hier kommt bei allen Programmiersprachen zum Tragen, dass der PC ursprünglich mal ein etwas besserer Taschenrechner war – einfache mathematische Operationen kann er mit Leichtigkeit durchführen.

Zusätzlich zu den Grundrechenarten bieten die Mehrzahl der Programmiersprachen den Modulo (Divisionsrest), die Potenzierung und einige weitere Funktionen, wenn auch teilweise nur durch externe Bibliotheken.

[Bearbeiten] String-Manipulation

Glücklicherweise können PCs im Gegensatz zum handelsüblichen Taschenrechner nicht nur mit Zahlen, sondern auch mit Zeichen umgehen, diese zusammenfügen, durchsuchen, abfragen usw.

Die einfachste String-Operation, die bei so gut wie jeder Sprache vorhanden ist, ist das Zusammenfügen (Concatenation) von Zeichenketten. Der Operator kann ein Plus, ein Punkt oder auch ein Befehl sein.

[Bearbeiten] Reguläre Ausdrücke

Die Mehrzahl aller modernen Programmiersprachen unterstützt nativ oder durch eine geeignete Bibliothek Reguläre Ausdrücke. Dabei handelt es sich um eine Beschreibungssprache innerhalb der Sprache. Mit einfachen Mitteln wird dabei beschrieben, welche Teile der Eingabe erfaßt werden sollen.

Reguläre Ausdrücke gibt es in mehreren unterschiedlichen „Geschmacksrichtungen“, je nach Programmiersprache. Die am weitesten entwickelten RegExp findet man unter Perl und anderen Sprachen, welche die PCRE-Bibliothek (PCRE = Perl Compatible Regular Expressions) verwenden, gefolgt von JavaScript, sed und Lua, die jeweils ihre eigenen Ausdrücke haben.

Die Ausdrücke bestehen aus Zeichenklassen, Multiplikatoren, Gruppierungen und Ersetzungen, hinzu kommen noch Modifikatoren:

// Zeichenklassen:
[ab]            findet a und b
[a-z]           findet alle Zeichen zwischen a und z (klein)
[a-zA-Z0-9]     findet alle Zeichen und Zahlen (keine Umlaute)
\w              vordefinierte Zeichenklasse für Buchstaben und Zahlen
\W              Negation von \w, also keine Buchstaben und Zahlen

// Multiplikatoren:
*               0 - unendlich oft
+               1 - unendlich oft
-               kleinste Menge
{2}             genau 2 Zeichen
{2,}            2 oder mehr Zeichen
{,3}            bis zu 3 Zeichen
{3,5}           3 bis 5 Zeichen

// Gruppierungen
(T\w+r)         findet Tür, Tor, Tour, etc. und speichert es im Stack
(am|bei)        findet "am" oder "bei"

// Ersetzungen
$1              erste Stack-Position
$2              zweite Stack-Position

// Modifikatoren    
g               globale Suche, mehrere Ergebnisse möglich
i               ignoriert Groß-/Kleinschreibung

Bei manchen Sprachen ist nicht alles davon verfügbar, andere haben zusätzliche Ausdrücke oder andere Operatoren.

Die Audrücke stellen sich folgendermaßen zusammen, hier am Beispiel von sed:

sed s/ab/xy/g   # ersetzt global ab durch xy

Daraus ergibt sich folgendes Muster:

Ausdruck: [Trenner][Suchmuster][Trenner][Ersetzung][Trenner][Modifikator]

Trenner: idR. "/", es kann aber auch ein anderes Zeichen sein

Suchmuster: [Zeichenklassen][Multiplikatoren][Gruppierungen]

Ersetzung: [Zeichenketten][Ersetzungen]

Die Ersetzung sowie der darauffolgende Trenner sind je nach Funktion optional. Bei einer Suche werden sie natürlich nicht gebraucht.

Reguläre Ausdrücke sind häufig eine Lösung für reguläre Probleme. Für irreguläre Probleme (bspw. solche, die nicht in einem linearen Ablauf verarbeitet werden können) verwende man einen lexikalischen Parser.

[Bearbeiten] Programmierparadigmen

Programmierparadigmen sind Klassifizierungen in der Art bzw. Struktur der Programmierung. So gibt es Sprachen, in denen der Programmierer schreibt, WAS zu tun ist (deklarativ) und Sprachen, in denen das WIE beschrieben wird (imperativ). Dass dies grundlegende Unterschiede sind, leuchtet wohl ein. Trotzdem ist zu beachten, dass diese Klassifizierungen nicht immer eindeutig sind und viele Sprachen Elemente verschiedener Paradigmen aufweisen. Die Konzepte der unterschiedlichen Programmierparadigmen sollen hier kurz beschrieben werden, wobei es nicht das Ziel des Artikels sein soll, dem Leser genaue Vorstellungen von den Paradigmen zu verschaffen.

[Bearbeiten] Deklarative Programmierung

Deklarative Sprachen beschreiben lediglich das Ziel selbst. Die Realisierung wird vom so genannten Interpreter der Sprache übernommen. Dies hat den Vorteil, dass entsprechende Programme relativ klein sind und partielle Auswertung ermöglichen, wodurch theoretisch unendliche Datenstrukturen in ihnen enthalten sein können.

Im weiten Sinn zählen auch Transformationssprachen wie XSLT oder Abfragesprachen wie SQL zu den deklarativen Programmiersprachen. SQL ist hier wohl eines der bekanntesten Beispiele für deklarative Sprachen, obwohl auch hier zunehmend objektorientierte und prozedurale Elemente einfließen (PL/SQL und ähnliche Erweiterungen).

Der Begriff „deklarative Programmierung“ wird häufig als Überbegriff (unter anderem) für die logische, funktionale und prozedurale Programmierung vorgestellt.

[Bearbeiten] Logische Programmierung

Die logische Programmierung hat ihren Ursprung in der Forschung nach künstlicher Intelligenz. Sie sind aus Axiomen, also Regeln, und Fakten aufgebaut, welche, wie bei deklarativen Programmiersprachen üblich, das Problem bzw. Ziel beschreiben, dessen Lösungsweg nicht vorgegeben ist. Als Beispiele bieten sich Verwandtschaftsbeziehungen an:

Fakten:

  • Paul ist Vater von Hans,
  • Louisa ist die Mutter von Hans,
  • Irene ist die Mutter von Louisa,

Regel:

  • Hans schenkt seiner Großmutter ein Bild.

Nun könnte die Frage sein: Wer bekommt das Bild von Hans?

Auch wenn dieses Beispiel wenig sinnvoll erscheint, gibt es doch eine Vorstellung von der Grundstruktur dieser Programme.

Das bekannteste und wohl auch beliebteste Beispiel einer logischen Sprache ist PROLOG, ein Programm, welches für die Forschung an künstlicher Intelligenz und der Entwicklung von Expertensystemen genutzt wird.

[Bearbeiten] Funktionale Programmierung

Unter funktionaler Programmierung versteht man die Strukturierung von Programmen als Reihe von Funktionen, die jeweils 0-n Ein- und Ausgaben haben. Besonders einfache Aufgaben kann man am leichtesten mit diesem Konzept bearbeiten.

[Bearbeiten] Prozedurale Programmierung

Prozeduale Programmierung bezeichnet die Programmierung von Algorithmen mittels einer squentiellen Abfolge von Befehlen wobei im Gegensatz zur strukturierten Programmierung wiederverwendbare Teile in Funktionen (Prozeduren) ausgelagert werden.

[Bearbeiten] Imperative Programmierung

Imperative Sprachen beschäftigen sich mit dem tatsächlichen technischen Erreichen eines gewünschten Ziels. Beim Schreiben von imperativen Programmen werden also Befehle aneinandergereiht, wodurch bestimmte Funktionen erreicht werden. In dieses Paradigma sind zum Beispiel C, Pascal und weitere Sprachen einzuordnen.

Die imperative Programmierung ist wiederum ein Überbegriff für strukturierte, objektorientierte, prozedurale Programmierung und einige weniger bedeutende mehr.

[Bearbeiten] Strukturierte Programmierung

Strukturierte Programmierung ist die einfachste Art der imperativen Programmierung. Hier werden Befehle sequentiell ausgeführt und Algorithmen durch Kontrollstrukturen wie Schleifen oder bedingte Verzweigungen umgesetzt.

[Bearbeiten] Objektorientierte Programmierung

Gerade bei großen Projekten ist die objektorientierte Programmierung ein geeignetes Mittel, um einzelne Programmteile sauber zu verarbeiten. Dabei wird alles als Objekt angesehen. Jedes Objekt kann eigene Attribute (Eigenschaften) und Methoden haben. Attribute sind sozusagen Merkmale eines Objekts, etwa der Radius eines Kreises, während Methoden Funktionen sind, die an das Objekt gebunden sind, bspw. die Darstellung des Kreises. Oftmals werden Attribute als privat gekapselt, d. h. sie können nur durch Methoden des Objekts manipuliert werden.

Kreis = Objekt{
  radius,
  liniendicke,
  position = Objekt{
    x, y
  },
  darstellen(),
  schnittmenge(objekt)
}
meinKreis = new Kreis();

Von außen kann man bpsw. auf meinKreis.radius zugreifen, während innerhalb des meinKreis-Objekts this.radius (manchmal auch self.radius, je nach Sprache) diesen Wert zurückgibt.

Manche Sprachen bringen syntaktische Abkürzungen für das Konzept der Objektorientierung mit – aber auch ohne diese Abkürzungen kann man das Konzept umsetzen (was jedoch mehr Aufwand bedeutet).

[Bearbeiten] Programmiersprachen

Es gibt eine ganze Menge unterschiedlicher Programmiersprachen, die anhand bestimmter Kriterien eingeordnet werden können. Das in den meisten Fällen primäre Kriterium ist die Frage, ob ein Programm erst in Binärcode übersetzt (kompiliert) werden muss, bevor es ausgeführt werden kann oder im laufenden Betrieb interpretiert wird. Kompilierte Programme laufen, sofern sie erst einmal fertiggestellt sind, schneller; die Entwicklung hingegen wird jedoch normalerweise mit interpretierbaren Sprachen beschleunigt. Des Weiteren gibt es Systeme, die den für Menschen besser lesbaren Programmcode in einen Zwischencode übersetzen, der erst zur Laufzeit für das jeweilige System / den jeweiligen Prozessor übersetzt wird. Damit kombiniert man den Vorteil der Portierbarkeit (auch über Systemgrenzen hinaus) mit einem auf das ausführende System exakt abstimmbaren Laufzeit-Code. So erhält man unter Umständen einen noch schneller laufenden Code als dies mit einer direkten Kompilierung möglich ist, da diese mit Rücksicht auf die Lauffähigkeit auf älteren Modellen der selben Prozessorfamilie nicht immer alle neueren Optimierungen nutzen kann.

Das nächste Kriterium ist die Nähe bzw. Abstraktion zur eigentlichen Maschinensprache. Das nächste nach dem manuellen Bearbeiten der Binärdaten im Hex-Editor ist Assembler, bei dem jeder Prozessor-Befehl als kurze Buchstabenkombination aufgeführt ist. Das hat den Nachteil, dass das Programm an den Befehlssatz der jeweiligen CPU ausgerichtet ist, aber auch den Vorteil, dass es darauf optimal läuft. Kurz darauf folgt die Sprache C, Sprachen wie Forth über Java bis hin zu Scriptsprachen wie PHP, Python, Ruby, Lua und JavaScript oder Makrosprachen die oftmals in Anwendungen eingebettet sind.

Weitere Kriterien sind die Hilfsmittel zur Objektorientierung, die eine Sprache erlaubt, sowie die Typen und der Grad der Typenfestlegung sowie die Systeme, die sie unterstützt.

All das tritt jedoch zurück hinter einen ganz wichtigen Punkt: der Umfang der mitgelieferten Funktionalität, sei es innerhalb der Syntax, interner oder externer APIs oder Bibliotheken, die ein System bereitstellt.

Meine Werkzeuge
Namensräume

Varianten
Aktionen
Übersicht
Hilfe
SELFHTML
Diverses
Werkzeuge
Flattr
Soziale Netzwerke