Programmiertechnik/Grundlagen und Konzepte

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.

Allgemeines über Computer

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.

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, ….

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.

Hauptartikel: Zeichencodierung

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.

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 Scriptsprachen 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.

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.

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.

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.

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 erfasst 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 Ausdrü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.