Programmierung

Theorie zu Struktogramme, Powershell und ANSI-C

8. März 2021

Inhalt Teil-A «GRUNDLAGEN»

Inhalt Teil-B «POWERSHELL»

Inhalt Teil-C «ANSI C»

Teil-A: «GRUNDLAGEN»

1. Programmier-Grundlagen

1.1 Compiler- versus Interpretersprache

Es gibt viele verschiedene Programmiersprachen. Jede hat seine spezifischen Eigenschaften. Es wird unterschieden zwischen:

  • Interpreter-Sprache: Bsp.: Powershell, JavaScript, PHP etc. Der Programmcode wird von einem speziellen Programm interpretiert dh. in Maschinencode übersetzt. Z.B. Bei JacaScript-Code ist der Webbrowser der Interpreter.
  • Compiler-Sprache: Bsp.: C/C++, Pascal, Java, Basic etc. Das in Quellcode vorliegende Programm muss mit einer Compiler/Linker-Software in maschinenlesbaren Code umgewandelt werden. Bei Windows hat maschinenlesbarer Code die Extension xx.exe.

1.2 Kompilieren und Linken

Nur bei Kompilersprachen: Der als normalen Text lesbare Programmcode wird in die binären Maschinenkommandos übersetzt bzw. kompiliert. Die einzelnen Schritte am Beispiel vom ANSI-C Compiler LCC:

  1. Erstellen des C-Quell-Programms oder Source-File hello.c

  2. Kompilieren von hello.c:
    c:\<LCC_Programmpfad>\lcc\bin\lcc hello.c -o hello.obj
  3. Linken von hello.obj:
    c:\<LCC_Programmpfad>\lcc\bin\lcclnk hello.obj -o hello.exe
  4. Das fertige Programm hello.exe kann nun ausgeführt bzw. getestet werden.
    Das hello.exe File ist ein ausführbares Programm. Das File beinhaltet nun ausschliesslich nativen Maschinencode und kann nicht ohne weiteres vom Programmierer gelesen werden.
Sollte das Programm Fehler ausweisen, müssen diese im Source-File hello.c behoben werden und anschliessend muss neu kompiliert und gelinkt werden.

1.3 Analyse/Design/Implementation/Test

Beim Programmieren geht man IPERKA-ähnlich vor. Die Entwicklung ist in vier nacheinanderfolgende Schritte aufgeteilt:

  • Analyse: Studium der Anforderungen, Termine, Programmeigenschaften wie Eingabe/Verarbeitung/Ausgabe
  • Design: Lösungsideen finden, Programmentwurf erstellen, Struktogramm/Programmablaufplan/State-Event-Diagramm erstellen. Hinweis: In unserem Kurs behandeln wir nur die Nassi-Shneiderman Struktogramm-Erstellung.
  • Implementation: Codieren der Aufgabe gemäss den Erkenntnissen aus der Designphase Struktogramm/Programmablaufplan/State-Event-Diagramm. Die zu verwendende Programmiersprache wird aufgrund der Anforderungen erst in diesem Schritt festgelegt. In unserem Kurs beschränken wir uns auf die Programmiersprache C.
  • Testen: Informelle Tests versus formelle Tests: Formelle Tests werden geplant, d.h. es werden geeignete Testfälle definiert und dokumentiert. Beim eigentlichen Test der SW wird der Prüfling (=die SW) gemäss den Testfällen geprüft und die Testresultate in einem Testprotokoll dokumentiert. Sollte der Test unbefriedigend ausfallen, muss der Prüfling in der Programmierabteilung überarbeitet und danach nochmals vollständig gemäss den definierten Testfällen gecheckt werden. Damit will man einer "Verschlimmbesserung" entgegenwirken. In unserem Kurs werden wir formelle Tests durchführen.

1.4 Der strukturierte Programmentwurf

Erklärung anhand einer Aufgabenstellung:
Es wird eine Zahl eingegeben. Wenn die Zahl grösser als 0 ist, dann erfolgt die Ausgabe "Positive Zahl", sonst wird "Null" oder "negative Zahl" ausgegeben. Das Programm wird mit der Ausgabe "Programm-Ende" beendet.

Darstellung obiger Aufgabe mit einem Struktogramm: (Zu lesen von oben nach unten!)

Struktogramme sorgen dafür, dass die entstehende Programmstruktur automatisch den Vorgaben der strukturierten Programmierung entspricht:

  • Das Programm hat ein gemeinsamer Beginn und ein gemeinsammes Ende, keine Querein- und Ausstiege und damit Verhinderung von Spaghetti-Code (Go-To)
  • Unterprogramm (Abstraktion/Prozedur/Funktion) mit Rücksprung an die Stelle des Hauptprogramms, wo dieses für das Unterprogramm verlassen wurde.

1.5 Struktogrammelemente nach Nassi-Shneidermann


1.6 Analyse, Design und Test eines Musterprojekts

Aufgabenstellung:

Gegeben sei ein Parkhaus für Motorräder und Autos. Es befinden sich also zwei- und vierrädrige Fahrzeuge im Gebäude. Aus der Gesamtzahl der Räder und Fahrzeuge soll bestimmt werden, wieviele davon Motorräder bzw. Autos sind.

Die Analyse:

  • Auftraggeber, Sachbearbeiter, Termine, etc.
  • Zu lösende Aufgabe: Aus der Anzahl Räder und Fahrzeuge soll bestimmt werden, wieviele Motorräder und Autos sich in einem Parkhaus befinden.
  • Eingabe: Alle Eingaben erfolgen interaktiv durch Aufforderung des Programms:
    1. Grösse: Anzahl Fahrzeuge / Ganzzahl / Positive Zahl
    2. Grösse: Anzahl Räder / Ganzzahl / Muss eine gerade Zahl sein und mindestens zweimal so gross, und maximal viermal so gross wie die Anzahl der Fahrzeuge
  • Ausgabe:
    1. Grösse: Anzahl Motorräder (noch unbekannt)
    2. Grösse: Anzahl Autos (noch unbekannt)
  • Weitere Anforderungen: Mit veränderten Eingaben soll eine neue Berechnung durchgeführt werden. Nach Resultatausgabe soll entschieden werden können, ob eine weitere Berechnung stattfinden soll. Die gemachten Eingaben müssen mit der Ausgabe sichtbar sein. Fehleingaben müssen durch das Programm erkannt und zurückgewwiesen werden. Keine Speicherung der Resultate erforderlich.

Der Design:

Zuerst widmen wir uns der kreativsten und schwierigsten Sache bei der Realisierung eines Programms, der Lösungsidee. Dabei spielen wir ein paar Zahlenfälle durch:

  • Annahme: Im Parkhaus befinden sich 20 Fahrzeuge mit 50 Rädern
  • Fall 1: Hätte es nur Motorräder im Gebäude, dann wären das "nur" 2 x 20 = 40 Räder
  • Fall 2: Ausgehend von Fall Nr. 1 ersetzen wir ein Motorrad durch ein Auto: Die Anzahl der Räder nimmt damit um 2 zu.
    19 Motorräder und 1 Auto gibt zusammen: 2 x 19 + 4 x 1 = 42 Räder. (Aber immer noch keine 50)
  • Fall 3: Ausgehend von Fall Nr. 1 ersetzen wir nun zwei Motorräder durch zwei Autos: 18 Motorräder und 2 Autos gibt zusammen: 2 x 18 + 4 x 2 = 44 Räder.
Setzt man nun dieses Verfahren systematisch fort so ergibt sich:

  • 17 Motorräder und 3 Autos gibt zusammen: 2 x 17 + 4 x 3 = 46 Räder
  • 16 Motorräder und 4 Autos gibt zusammen: 2 x 16 + 4 x 4 = 48 Räder
  • 15 Motorräder und 5 Autos gibt zusammen: 2 x 15 + 4 x 5 = 50 Räder
Dieses Verfahren kann programmmässig durch eine Schleife gelöst werden. Die Schleife soll so lange durchlaufen werden, bis die verlangte Anzahl Räder erreicht ist.

Struktogramm der Lösungsidee:

(Nicht berücksichtigt ist die Überprüfung der eingegebenen Grössen auf ihre Richtigkeit)

Die Implementation:

Auf die Codierung wird hier nicht weiter eingegangen.

Das Testen:

Mit Hilfe des Testens wird überprüft, ob die gestellten Anforderungen an das Programm erfüllt werden. Jeder Test soll eine andere Situation testen. Zum Beispiel "Abbruch im Falle von zu grossen Zahlen", "Abbruch bei Text- statt Zahleneingabe", "Erfolgreiche Berechnung bei Zahlen in Grenzwerten".
Im Idealfall sollte beim Testen jede Programmzeile und Stelle einmal durchlaufen worden sein. Das Bestimmen von Tests, die ein anderes Verhalten testen, nennt man das Bilden von sogenannten Äquivalenzklassen.
Formell testen: Mit Formell ist Schriftlich gemeint. Man erhält damit u.a. eine schriftliche Quittung, das überhaupt und was dabei konkret getestet wurde und hat damit auch eine Absicherung gegen spätere Mängelrügen des Auftraggebers. Zudem dient das Protokoll als ToBeDone-Liste für denjenigen, der die Fehler danach beheben muss. (z.B. der Programmierer)
Bei einer nicht akzeptierten bzw. fehlerhaften Lieferung muss beachtet werden, dass nach der Beseitigung der Fehler durch den Programmierer, aus Programmversion V1.0 wird V1.1, alles nochmals nach der selben Testvorschrift getestet werden muss, um einer Verschlimmbesserungen vorzubeugen. Ausserdem soll der Programmierer nicht gleichzeitig Tester sein, damit eine Unabhängikeit gewährleistet ist.
Das Gegenteil von formellen Tests sind informelle Tests wie z.B. das Debuggen von Programmen während der Implementation.
Wichtig für die Testfälle ist, dass sie konkrete und demnach auch reproduzierbare Ein- bzw. Ausgabewerte enthalten und das am Schluss im Testbericht eindeutig entschieden ist, ob die Lieferung akzeptiert oder zurückgewiesen wird.

Dokumentation der Testfälle:

Testbericht:

Dies ist die Zusammenfassung der Testresultate.

Teil-B: «POWERSHELL»

2. Erste Schritte in Powershell

2.1 Vorbereitungen

Windows PowerShell bietet umfassende Möglichkeiten der Systemverwaltung und -automatisierung auf der Windows-Plattform. PowerShell ist eine Kommandozeilen- und Skriptumgebung, welche auf dem .NET Framework basiert. Sie erlaubt die Steuerung und Automatisierung von Windows Server aber auch von Anwendungen bzw. Services wie Active Directory, Hyper-V, Exchange Server, System Center, Skype for Business, Citrix und VMware ESX Server usw. PowerShell-Kenntnisse sind heute unerlässlich für Windows-Systemadministratorinnen und -administratoren – sei es für Installationen vor Ort oder in der Cloud. Seit der Version 5.1 steht mit PowerShell Core parallel dazu eine Cross-Plattform-Variante zur Verfügung, die auch für Linux und macOS eingesetzt werden kann.

Zur mächtigen Skriptsprache wird Powershell dank der .Net-Bibliothek, die mittlerweile eine ansehnliche Grösse erreicht hat und ständig am weiterwachsen ist. Aus diesem Grund lädt Powershell nur die am häufigsten benötigten Teile von .Net in den Arbeitsspeicher. Benötigt man allerdings eine .Net-Komponente, die standardmässig nicht geladen ist, muss man diese explizit nachladen (Siehe 1.1.2 Nachladen von .Net-Komponenten). Um beispielsweise eine komfortable GUI-Schnittstellen zu realisieren, muss man die Windows.Forms-Assembly laden, bevor man dann ein z.B. Formular-Objekt erstellen und mit Schaltflächen, Textboxen und weiteren Dingen bestücken kann.

PowerShell-Version anzeigen:

$PSVersionTable

Nachladen von .Net-Komponenten (Verschiedene Varianten):

#Load-Methode, falls man den "Full Name" der Komponete kennt:
[system.reflection.assembly]::Load("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

#Die Alternative ohne "system":
[reflection.assembly]::Load("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

#LoadForm-Methode, falls man den absoluten Pfad zur Komponente kennt:
[reflection.assembly]::LoadFrom("C:/Windows/Microsoft.Net/.../System.Windows.Forms/v4.0_4.0.0.0__b77a5c561934e089/System.Windows.Forms.dll")

#LoadWithPartialName-Methode, falls man nur Teile des "Full Name" der Komponente kennt:
[reflection.assembly]::LoadWithPartialName("Windows.Forms")

#Mit CmdLet Add-Type: (Funktioniert nicht immer. Benötigt z.T. "Full Name")
Add-Type –AssemblyName Windows.Forms

2.2 Ausführungsrichtlinien und Codierungsregeln

# ********** AUSFÜHRUNGSRICHTLINIEN IN DER POWERSHELL-KONSOLE ANPASSEN **********
# Um Scripte im Entwicklungsstadium vorbehaltlos ausführen zu können, empfiehlt es sich,
# im Powershell-Terminal die Powershell-Ausführungsrichtlinien anzupassen.
# GELOCKERTE WPS-SICHERHEITS-BARRIEREN = GELADENE PISTOLE!   ......denn sie wissen (nicht), was sie tun...
# 
Set-ExecutionPolicy AllSigned       # Nur signierte Scripts werden ausgeführt
Set-ExecutionPolicy RemoteSigned    # Aus dem Internet heruntergeladene Scripts müssen signiert sein
Set-ExecutionPolicy Unrestricted    # Bevorzugt! Alle Scripts werden ausgeführt. Unsignierten Scripts aus dem Internet müssen bestätigt werden
Set-ExecutionPolicy Bypass          # Keinerlei Einschränkungen, Warnungen oder Prompts
Set-ExecutionPolicy Undefined       # Entfernt eine zugewiesene Richtlinie

# ********** CODIERUNGSREGELN IN POWERSHELL-SKRIPT ERZWINGEN **********
# Eigentlich käme man auch ohne aus. Allerdings zwingt dies zum disziplinierten Coden und wirkt prophylaktisch gegen Programmierfehler.
#
# set-psdebug aktiviert/deaktiviert Debuggingfunktionen, Ablaufverfolgungsebene wird festgelegt bzw. Strict-Modus umgeschaltet
set-psdebug -off                  # Deaktiviert alle Skript-Debuggingfunktionen
set-psdebug -strict               # TBZ! Interpreter meldet, wenn auf eine Variable verwiesen wird, bevor ihr ein Wert zugewiesen wurde

# set-strictmode aktiviert bedeutet Abbruch, falls Codierungsregeln gebrochen werden
set-strictmode -off               # Deaktiviert Strict-Modus und set-psdebug -strict
set-strictmode -version latest    # Bevorzugt! Verhindert u.a. Verweise auf nicht initialisierte Variablen

# HINWEIS: Set-StrictMode ähnelt dem Strict-Parameter von Set-PSDebug.
# "Set-Strictmode -version 1" entspricht "Set-PSDebug -strict" mit der Ausnahme,
# dass sich Set-PSDebug auf alle Bereiche auswirkt.
# Set-StrictMode wirkt sich nur auf den festgelegten Bereich sowie auf die untergeordneten Bereiche aus.

2.3 Hilfe erhalten

# Bevor sie in der Powershell ISE stundenlang an einem Fehler herumsuchen, der unter Umständen gar keiner ist,
# beenden sie die Powershell ISE und starten sie diese neu.
# Falls sie ihre Powershell-Skripte aus der Powershell-ISE heraus ausführen, sollten sie beachten, dass ihre Variablen
# nach beendeter Skriptausführung in der Powershell ISE immer noch bestehen.
#
# Unterschied CMD-TOOL ↔ CMDLET
# cmd-Tools → Tools aus der alten Betriebssystem-Shell cmd bzw. Windows-Eingabeaufforderung)
#                  Funktionieren auch in der Powershell.
#                  Bsp.: cd, dir, xcopy etc.

# CmdLet's  → Powershell-Tools
#                  Funktionieren nicht in der alten Betriebssystem-Shell cmd
#                  Bsp: get-ChildItem, get-Process etc.

# Namensaufbau von CmdLet's: [VERB]-[NAMEN]. Beispiele:
get-process
get-help
start-Process
write-Output

# Alle möglichen Verben anzeigen lassen
get-verb                          # Zeigt alle möglichen Verben wie z.B. "get" an
#-----------------------------------------------------------------------------------------------------------------------
# So erhält man Hilfe bzw. Klarheit, wenn man einmal nicht mehr weiter weiss...
update-help                       # Lädt die aktuellsten Helpfiles herunter
get-help                          # Hilfe zum Hilfesystem anzeigen
get-help about*                   # Zeigt alle Helpfile-Einträge an
get-help about_for                # Zeigt Hilfetext zur for-Schleife an
get-help about_Functions          # Zeigt Hilfetext zu Funktionen an
get-help about_Parameters         # Zeigt Hilfetext zu Parametern an
get-help Write-Eventlog -full     # Zeigt kompletten Hilfetext zu Write-Eventlog an
get-help Write-Eventlog -examples # Zeigt Beispiel-Skript zu Write-Eventlog an
#-----------------------------------------------------------------------------------------------------------------------
# Alle vorhandenen cmd-Let's anzeigen lassen: 
get-command                       # Zeigt alle Alias/CmdLet/Function an
get-command get*                  # Zeigt alle Alias/CmdLet/Function mit dem Verb "get" an (* → Wildcard)
get-command *alias*               # Zeigt alles an, das die Bezeichnung "alias" im Namen enthält
#-----------------------------------------------------------------------------------------------------------------------
# Properties, Methods und Datentyp von einem Objekt anzeigen:
[string]$myStr = "Hello"          # Deklaration und Initialisierung der ersten Beispielvariablen
[int]$myInt = 133                 # Deklaration und Initialisierung der zweiten Beispielvariablen
$myStr | get-member               # Zeigt alle Eigenschaften und Methoden eines String-Objektes an
$myInt | get-member               # Zeigt alle Eigenschaften und Methoden eines Integer-Objektes an
$myStr.gettype().fullname         # Zeigt den genauen Datentyp an → System.String
$myInt.gettype().fullname         # Zeigt den genauen Datentyp an → System.Int32
"Hallo" | Get-Member              # | → Pipeline/Objekt-Weitergabe: Gibt Ergebnis an einen nächsten Befehl weiter
"Hallo".gettype().fullname        # Zeigt den Datentyp an.
#-----------------------------------------------------------------------------------------------------------------------
# Fallbeispiel → Hilfe für Get-Process erhalten:
get-help Get-Process              # NAME: Get-Process
                                  # ÜBERSICHT: Gets the processes that are running ...
                                  # SYNTAX: Get-Process [[-Name] ] [-ComputerName ] ...
                                  # BESCHREIBUNG: The Get-Process cmdlet gets the processes on a local or ...
                                  # VEWANDTE LINKS: Debug-Process...
                                  # HINWEISE: Zum Aufrufen der Beispiele...
								  # Beispiele:
# Die neuen Erkenntnisse anwenden:
get-process notepad               # Falls der Prozessname an erster Stelle in der Parameterliste erscheint
get-process -name notepad         # Sonst mit Parameterbezeichnung -name
get-process -id 3880              # Oder mit der Prozess-ID (falls bekannt bzw. in diesem Beispiel die 3880)

2.4 Absoluter und relativer Pfad

# Absoluter Pfad
C:\Benutzer\FelixMuster\Dokumente\MeinText.txt

# Relativer Pfad 
# (Abhängig vom CurrentWorkingDirectory CWD. Der Punkt . steht für das CWD)
.\MeinText.txt            # Falls CWD = C:\Benutzer\FelixMuster\Dokumente
.\Dokumente\MeinText.txt  # Falls CWD = C:\Benutzer\FelixMuster

# Relative Pfade verwenden
# (Um bei Funktionen, Dateien etc. mit relativen Pfaden zu arbeiten, empfiehlt es sich, 
# den aktuellen Pfad bewusst als Current Working Directory CWD z.B. auf den Skriptordner zu setzen)
# Funktioniert nur innerhalb eines abgespeicherten WPS-Skripts!
[String] $scriptPath = Split-Path $MyInvocation.MyCommand.Path
Set-Location $scriptPath

2.5 Script-Programme starten

2.5.1 Script aus der Powershell-ISE oder aus einem anderen Skript heraus starten:

# Im aktuellen Variablen-Gültigkeitsbereich («.» beachten!)
. "C:\Benutzer\FelixMuster\Dokumente\myScript.ps1"
. .\myScript.ps1

# Im eigenen Variablen-Gültigkeitsbereich («&» beachten!)
& "~\Dokumente\myScript.ps1" # ~ bedeutet Home-Verzeichnis
& .\myScript.ps1

2.5.2 Powershellscripts und CmdLet's aus der Konsole CMD aufrufen:

Konsole «cmd.exe» aufrufen und eine der folgenden Befehlszeilen ausführen:

Powershell.exe -command Get-Process                        # WPS-CmdLet «Get-Process» ausführen
Powershell.exe -command Set-ExecutionPolicy unrestricted   # Policy ändern, wie in der WPS-ISA auch, um eigener Skript ausführen zu dürfen
Powershell.exe -command .\myScript.ps1                     # Eigener Skript über relativen Pfad aufrufen

2.6 Powershell-Script-Template

Nachdem die PowerShell-ISE als Administrator gestartet wurde, soll der folgende Befehl in der Powershell-Konsole ausgeführt werden:
(Siehe auch 1.2 Ausführungsrichtlinien und Codierungsregeln)

Set-ExecutionPolicy unrestricted
Danach ein neues Powershell-Dokument erstellen und mit folgenden Zeilen füllen bzw. nach Bedarf anpassen:

#******************* «PROGRAMM-TITEL eintragen» *******************************
# VERSION: «Aktuelle Versionsnummer»
# AUTOR:   «Ihr Name»
# DATUM:   «Aktuelles Datum»
# ZWECK:   «Zweckangabe bzw. kurze Programmbeschreibung»
#******************************************************************************

set-strictmode -version latest       # Codierungsregeln verschärfen

#******************* Variablen-Deklaration & Initialisierung ******************
[int]$myInt = 0                      # Für eine Ganzzahl ...-2,-1,0,1,2...
[double]$myDouble = 0.0              # Für einer Dezimalzahl z.B. 6.25
[string]$myString = "Hello World!"   # Für eine Textzeile
[bool]$myBool = $TRUE                # Logische Variable $True oder $False

#******************* HIER BEGINNT MEIN PROGRAMM *******************************
cls                                  # Bildschirm (Konsole) löschen
 
#********** EINGABE **************
$myString = Read-Host "Bitte ihren Namen eingeben"
$myDouble = Read-Host "Bitte ihre letzte Zeugnisnote eingeben"

#********** VERARBEITUNG *********

if($myBool -eq $True)                # Beispiel für eine Selektion
{
  # Tu etwas
}
else
{
  # Tu etwas anderes
} 

while($myInt -lt 10)                 # Beispiel für eine Iteration
{
  $myInt = $myInt + 1
}
 
#********** AUSGABE *************
write-Host "Lieber $myString, deine letzte Zeugnisnote war eine $myDouble. Gratuliere!"

#******************* HIER ENDET MEIN PROGRAMM **********************************

3. Daten und Kontrollstrukturen

3.1 Datentypen inkl. Array

(Aufzählung nicht abschliessend)

[int] $intVar = 0 #32-Bit-Ganzzahl
[double] $dblVar = 0 #Fliesskommazahl
[char] $chrVar = ' ' #Ein Unicode-16-Bit-Character
[string] $strVar = "" #Textzeile
[bool] $boolVar = $true #Boolean oder logische Variable ($true oder $false)
[DateTime] $datVar = "01.31.1999"
[DateTime] $datVar = Get-Date #Variable mit aktuellem Datum füllen
[Object] $objVar = 0 #Der Stamm einer Objekthierarchie
[int[]] $intArr = 66,77,88,99 #Feldvariable / Array
$intArr[0] =22 #Überschreiben des "ersten" Elements 0
$intArr[1] = 33 #Überschreiben des "zweiten" Elements 1
Set-Variable constWert -Value 100 -option ReadOnly #Unveränderbare Konstante mit Inhalt 100

Ergänzung zum Datentyp Array/Feldvariable

Beispiel: Ziehung bzw. Speicherung der Lottozahlen: 6 Zahlen und Zusatzzahl

# ******** Codebeispiel zu Array ********************************************************
set-strictmode -version latest
cls

[int]$i=0                                           # Laufvariable
[int[]]$lottoziehung = 0,0,0,0,0,0,0                # Array deklarieren und mit 7 Werten initialisieren

# ******** Eingabe ********
while($i -lt 7)                                     # Es folgen 7 Durchgänge: Von 0 bis 6
{
  $lottoziehung[$i] = read-Host "Zahl $i eingeben"  # Einzelne Lottozahl einlesen
  $i++                                              # Laufvariable um 1 erhöhen
}

$i=0                                                # Laufvariable zurücksetzen

# ******** Ausgabe ********
while($i -lt 7)                                     # Es folgen wiederum 7 Durchgänge: Von 0 bis 6
{
  write-Host "Zahl $i =" $lottoziehung[$i]          # Variante zur Ausgabe von Variablen und Text
  $i++                                              # Laufvariable um 1 erhöhen
}
# ******** So können alle sieben Werte auf einen Schlag ausgegeben werden ******** 
write-Host "Die Lottoziehung lautet $lottoziehung"

3.2 Input/Output

  • Wert mit Read-Host einlesen:
    $intVar = Read-Host "Bitte eine Zahl eingeben:"
  • Ausgabe mit Write-Host:
    Write-Host "Das Resultat lautet:" $intVar
  • Ausgabe mit Out-GridView:
    Get-Services | Out-GridView

3.3 Vergleichsoperatoren und Logische Operatoren

Vergleichsoperatoren

-eq       # Equal oder Gleich (==)
-ne       # Not-Equal oder Ungleich (!=)
-lt       # Lower-than oder Kleiner-als (<)
-le       # Lower-Equal oder Kleiner-Gleich (<=)
-gt       # Greater-than oder Grösser-als (>)
-ge       # Greater-Equal oder Grösser-Gleich (>=)

-like     # Zeichenkette mit Wildcards finden. Bsp.: "Arnold" -like "Arn*"
-match    #Substring finden. Bsp.: "Arnold" -match "nol"
-contains # Suche in Collections. Bsp.: $w = "Do","Re","Mi" → $w -contains "Mi"

Beispiel zu Vergleichsoperatoren

if ( $x -gt 10) {..} else {..}

Logische Operatoren

-not  # Invertierung / NOT / NICHT / Aussageumkehrung
-and  # AND oder UND Verknüpfung
-or   # OR oder ODER Verknüpfung
-xor  # Exklusiv-ODER / XOR Verknüpfung

Beispiel zu Logische Operatoren

while ( $ResultatNOK -or $Abbrechen) {..}

3.4 Die Selektion inkl. Mehrfachselektion Switch-Case

Selektion → Verzweigung

  • Zweiseitige Selektion:
    if ( $userInput -ne $null )
    {
      echo "Input was [$userInput]"
    }
    else
    {
      echo "User cancelled the form!"
    }
    
  • Entscheidungen können auch mit Pipelines formuliert werden. Die Bedingung ist wahr ($true), wenn die Pipe mindestens ein Objekt zurückliefert:
    if (dir *.txt | Select-String "Steuererklaerung")
    {
      write-host "Es existiert mindestens eine Datei Steuererklaerung"
    }
  • Mehrfachselektion (Switch-Case):
    switch($A)
    {
      1       { Write-Host "Eins" }
      2       { Write-Host "Zwei" }
      3       { Write-Host "Drei" }
      default { Write-Host "Leer" }
    }
    

3.5 Die Iteration

Iteration → Schleife

  • Kopfgesteuerte Iteration:
    [int] $myVar = 0
    while ($myVar -lt 10)
    {
      Write-Host Hello
      $myVar++
    }
  • Fussgesteuerte Iteration:
    [int] $myVar = 0
    do
    {
      Write-Host "Hello"
      $myVar++
    } while ($myVar -lt 10)
    
  • Kopfgesteuerte Iteration - Spezialfall for-Schleife:
    [string] $sterne = ""
    [int] $maxPosition=8
    for ( $currPosition = 1 ; $currPosition -le $maxPosition ; $currPosition++ )
    {
      $sterne = $sterne + "*"
    }
    write-host "Anzahl" $sterne
    
  • Diese Iteration arbeitet eine Sammlung von Objekten ab.
    Die sogenannte Element-Variable $Variable speichert bei jedem Durchgang jeweils ein Objekt der Objekt-Gruppe:
    Foreach ($Variable in get-childitem C:\windows)
    {
      ...
      $Variable.Name
      $Variable.CreationTime
      ...
    }
    
  • Vereinfachte Variante mit dem CmdLet «Foreach_Object»:
    Man kann mit der Standardvariable $_ auf die einzelnen Objekte in der Pipeline zugreifen:
    (Vorsicht: Das vorangegangenen Beispiel mit der foreach-Funktion unterscheidet sich zu diesem Beispiel darin, dass hier ein foreach-CmdLet verwendet wird. Im Gegensatz zur Funktion muss beim CmdLet die geschweifte Klammer «{» auf derselben Zeile folgen.)
    get-childitem C:\windows | Foreach-Object {
      ...
      $_.Name
      $_.CreationTime
      ...
    }
    

3.6 Try and catch

Da gewisse Anweisungen einen Laufzeitfehler auslösen können, bedient man sich um diese abzufangen einer Try-and-Catch-Struktur: Wird innerhalb des Try-Blocks ein Laufzeitfehler ausgelöst, wird der Catch-Block ausgeführt. Damit kann man eine robuste Dateneingabe erreichen, den Aufruf eines nicht vorhandenen Dienstes abblocken oder Zahlen überprüfen.

  • Try-Catch in einer Iteration:
    # Variablendeklaration und Initialisierung
    [string]$theNumber = ""         # Einzugebende und zu prüfende Zahl als Text
    [int]$myNumber = 0              # Meine geprüfte Zahl
    [bool]$numOK = $False           # Hilfsvariable, dient als Flag (Flagge-hoch oder Flagge-tief) 
    do
    {
      $theNumber = Read-host "Bitte eine Zahl zwischen 0 und 100 eingeben"
      try                           # Versuche mit der Variablen zu rechnen
      {
        0 + $theNumber | Out-Null   # Rechnung und Resultat dabei verwerfen
        $numOk = $True              # Boolean-Flag-Variable auf $True → ist eine Zahl
        $myNumber = [int]$theNumber # Zahl mit Typecast umkopieren
      } # end try-Part
      catch                         # Mit der Variablen kann man offenbar nicht rechnen → Fehler abfangen mit Catch
      {
        $numOK = $False             # Boolean-Flag-Variable auf $False → ist keine Zahl
      } # end catch-Part
    } until ( ($myNumber -ge 1 -and $myNumber -lt 100) -and $numOK )
    write-host "Sie haben die Zahl $myNumber eingegeben"
    
  • Dienst auf sein Vorhandensein überprüfen:
    $servicename = Read-host "Bitte geben Sie ein Servicename ein"
    try
    { # der Fehler beim ausgeführten Cmdlet wird abgefangen!
      Get-Service $servicename -ErrorAction Stop
    } # end try-Part
    catch
    {
      Write-Warning "Diesen Service ($servicename) gibt es nicht!"
    } # end catch-Part
    
  • Robuste Zahleneingabe (Funktion):
    function isNumeric ($x)
    {
      try
      {
        0 + $x | Out-Null # Versucht mit der Variable zu rechnen
        return $true # Wenn möglich $true zurückgeben
      }
      catch
      {
        return $false #Wenn Fehler auftritt, handelt es sich nicht um eine Zahl, somit $false
      }
    }
    

3.7 Abstraktion / Funktion / Methode / Filter

3.7.1 Funktionen / Methoden

Wie die meisten Programmier­sprachen kann auch PowerShell mehrere Statements zu einer Funktion zusammenfassen. Sie vereinfacht die Wiederverwendung von Code und hilft bei der Strukturierung von Scripts. Auch wenn Funktionen in PowerShell für die Benutzer von VBScript oder PHP auf den ersten Blick vertraut aussehen, so gibt es doch einige gravierende Unterschiede.

Sowohl Cmdlets als auch in Bibliotheken bereitgestellte Funktionen (Methoden bei Objekten) stellen sogenannte Abstraktionen dar. Oft liefern diese einen Rückgabewert (ReturnValue) an das aufrufende Programm zurück. Es lassen sich aber auch eigene Funktionen erstellen. Im Quellcode müssen diese vor ihrem Aufruf definiert werden.

  • Funktion - Variante 1: Die param-Angabe bestimmt dabei erforderliche Übergabeparameter und definiert deren Namen. Über diese Namen kann ein Parameter beim Aufruf auch explizit gesetzt werden.
    function myFunction
    {
      param([int]$parameter1, [string]$parameter2, ...) #Parameter definieren
      ... # Hier steht der Inhalt der Funktion
      return ... # Damit kann ein allfälliger Rückgabewert an den Aufrufer zurückgegeben werden
    }
    
    Aufruf der Funktion:
    c:\user\...> $resultat = myFunction -parameter2 "Text" -parameter1 12 ...
    
  • Funktion - Variante 2: Kurzschreibweise ohne "param" Bei der Übergabe muss die hier definierte Reihenfolge eingehalten werden.
    function myFunction ([int]$parameter1, [string]$parameter2, ...)
    {
      ... # Hier steht der Inhalt der Funktion
      return ... # Damit kann ein allfälliger Rückgabewert an den Aufrufer zurückgegeben werden
    }
    
    Aufruf der Funktion:
    c:\user\...> write-host myFunction 12 "Text" ...
    
  • Funktion - Variante 3: Übergabe von Werten via Argumentübergabe mittels der vordefinierten Variable $args (=Array) und $arg.count (=Anzahl übergebener Parameter).
    function myFunction
    {
      ... # Hier steht der Inhalt der Funktion
      $args[...] # Der erste Wert hat den Index 0
      ... # Hier steht der Inhalt der Funktion
      return ... # Damit kann ein allfälliger Rückgabewert an den Aufrufer zurückgegeben werden
    }
    
    Aufruf der Funktion:
    c:\user\...> myFunction 12 "c:\" … | out-file resultat.txt
    

3.7.2 Beispiele zu Funktionen:

(Hinweis: Beim Aufruf mit mehreren Parametern müssen die Parameter mit Leerschlag und nicht mit Komma getrennt werden! Kommata definieren ein Array und werden als solche dann in einem Parameter abgelegt. Um zusammenhängenden Text in einen Parameter zu packen müssen Sie ihn mit " " einfassen.)

Beispiel-1:

function verdoppeln([float] $zahl)
{
  [float]$myresult = 0.0
  $myresult = 2 * $zahl
  return $myresult
}

function verdoppeln_alternativ([float] $zahl)
{ 
  return (2 * $zahl)  #Ginge auch ohne ()
}

#MAIN
cls
[float]$wert = 0.0
[float]$resultat = 0.0
[int]$umdrehungen = 0
$wert = read-host "Bitte Reifenradius in Millimeter eingeben"
$resultat = verdoppeln $wert
write-host "Der Reifenradius $wert Millimeter ergibt einen Reifendurchmesser von $resultat Millimeter"

$umdrehungen = read-host "Bitte Anzahl der Radumdrehungen eingeben"
$resultat = [Math]::Pi * [float]$umdrehungen * (verdoppeln $wert) / 1000  #[Math]::Pi → Konstante Pi 3.14...
write-host "Nach $umdrehungen Radumdrehungen wurde eine Wegstrecke von $resultat Meter zurückgelegt"

Beispiel-2:

function myFunction             # Funktion
{
  [int]$a    = 5
  [string]$b = "Hello World"
  $a
 #$b                            # Hier (ohne # Kommentarzeichen) $b oder return $b haben denselben Effekt. 
  return $b                     # Rückgabewert (siehe Kommentar eine Zeile höher)
}                               # Würde man sowohl $b und auch return $b schreiben, hätte man mit $a drei Werte 

$r = myFunction                 # Funktionsaufruf
write-host $r                   # Ist eigentlich ein Array und enthält Werte von $a und $b
write-host $r[0]                # Der Beweis: $r ist ein Array. In Element 0 steht Wert von $a
write-host $r[1]                # In Element 1 steht Wert von $b
#write-host $r[2]               # Würde man in der obigen Funktion sowohl $b (aktuell auskommentiert) und auch return $b schreiben,
                                # hätte man hier mit sogar drei Elemente in $r ($a, $b, $b). Dies macht allerdings keinen Sinn!

Beispiel-3:

function MyPing                 # Funktion
{
  $CompName = $args[0]          # Parameterübergabe Erstes Argument
  $IP = $args[1]                # Parameterübergabe Zweites Argument
  Test-Connection $CompName
  Test-Connection $IP
}

MyPing myhostname 192.168.1.10  # Funktionsaufruf myhostname = 1. Argument, 192.168.1.10 = 2. Argument

Beispiel-4:

function Start-App([String] $AppName)
{
  foreach($App in $input) {
    if($App.Name -like $AppName)
    {
      Start-Process "explorer.exe" -ArgumentList ("shell:AppsFolder\" + $App.AppID)
    }
  }
}

Get-StartApps | Start-App "Paint*"     # Daten über Pipe an Funktion übergeben

Beispiel-5:

function myMessage ($string, $title='PowerShell Message')
{
  [windows.forms.messagebox]::Show($string, $title)
}

myMessage "Diesen Text anzeigen!"; #Funktionsaufruf

Beispiel-6:

$nl = [System.Environment]::NewLine # oder `
function myHallo
{
  param $name
  $date = Get-Date
  If ($date.Hour -lt 12 -and $date.Hour -gt 6)
  {
    "Guten Morgen $name,$nl Es ist $($date.DateTime)"
  }
  elseif ($date.Hour -gt 12 -and $date.Hour -lt 19)
  {
    "Guten Tag $name,$nl Es ist $($date.DateTime)"
  }
  elseif ($date.Hour -gt 19 -and $date.Hour -lt 24)
  {
    "Guten Abend $name,$nlEs ist $($date.DateTime)"
  }
}  
  
myHallo -name Stefan Rehwald           # Funktionsaufruf
myHallo -name "Konrad Ernst Otto Zuse" # Funktionsaufruf
myHallo "Rudolf Diesel"                # Funktionsaufruf
$User = "Nikolaus Kopernikus"
myHallo $User                          #Funktionsaufruf

3.7.3 Filter

Filter werden identisch zu den Funktionen definiert. Ein Filter ist eine Art Funktion die auf jedes Objekt angewendet wird, das durch eine Pipeline angeliefert wird. Eine Funktion wird einmal für alle Objekte in der Pipeline gemeinsam aufgerufen, ein Filter jeweils einmal für jedes Objekt. Um also beispielsweise einen Befehl zu entwickeln, der die Summe einer bestimmten Eigenschaft berechnet, wird eine Funktion eingesetzt, was jedoch auch den Nachteil hat, dass die Pipeline-Verarbeitung solange angehalten wird, bis sämtliche Objekte zur Verfügung stehen. Bei einem Filter kann die Pipeline weiterarbeiten, obwohl das vorhergehende Cmdlet noch weitere Objekte liefert.

  • Filter - Variante 1
    filter name { param($parameter1, $parameter2, ...) ... }
  • Filter - Variante 2
    filter name ($parameter1, $parameter2, ...) {...}
  • Filter - Variante 3
    filter name { ... $args[...] ... }

3.7.4 Beispiele zu Filter

Beispiel-1:

Filter meinFilter
{
  Write-Host "Ich bin ein Filter!"
}
PS C:\users\FelixMuster> myFilter

Beispiel-2:

PS C:\users\FelixMuster> Filter PlusEins { $_ + 1 }
PS C:\users\FelixMuster> Echo 1 | PlusEins
2

Beispiel-3:

filter meinFilter
{
  $_
}
PS C:\users\FelixMuster> @(1,2,3) | meinFilter

# Dasselbe könnte man auch mit dieser Funktion erreichen (Anstatt $_ hier $Input):
function meineFunktion
{
  $Input
}
PS C:\users\FelixMuster> @(1,2,3) | meineFunktion

Beispiel-4:
Das folgende Beispiel zeigt den Unterschied zwischen einer Funktionen und einem Filter, wenn z.B. auf mehrere Elemente eines Arrays zugegriffen werden soll. Der wesentliche Unterschied zwischen einer Funktion und einem Filter besteht darin, wie die Pipeline verarbeitet wird. Die Funktion kann mit der Verarbeitung erst beginnen, wenn das komplette Array in der Variable $Input gespeichert ist. Und dafür braucht es bei grossen Arrys eine Menge Speicher. Im Gegensatz dazu verarbeitet der Filter jedes Element sofort nach dem Eintreffen aus der Pipe. Die Variable $_ speichert nur immer ein Element. Bei grossen Arrays arbeitet der Filter daher wesentlich effizienter.

Function meineFunktion
{
  ForEach ($i in $Input)
  {
    If($i -eq 2)
    {
      $i
    }
  }
}
PS C:\users\FelixMuster> @(1,2,3) | meineFunktion

# ---------------------------------

#(In Gegensatz zur Funktion benötigt der Filter keinen Loop)
Filter meinFilter
{
  If ($_ -eq 1)
  {
    $_
  }
}
PS C:\users\FelixMuster> @(1,2,3) | meinFilter

Beispiel-5:
Ein Performance-Vergleich Filter zu Funktion:

Function meineFunktion
{
  ForEach ($File in $Input)
  {
    $File
  }
}
PS C:\users\FelixMuster> Get-ChildItem C:\ -Recurse -ErrorAction SilentlyContinue | meineFunktion

# ---------------------------------

Filter meinFilter
{
  $_
}
PS C:\users\FelixMuster> Get-ChildItem C:\ -Recurse -ErrorAction SilentlyContinue | meinFilter

# ---------------------------------
# Definitiv im Vorteil ist der Filter hier:
Filter meinFilter
{
  if($_.Name -eq "notepad.exe")
  {
    "Datei gefunden: $_"
    Break
  }
}
PS C:\users\FelixMuster> Get-ChildItem C:\Windows -Recurse -ErrorAction SilentlyContinue | meinFilter

3.7.5 Funktionen mit Filter-Verhalten

Wie die vorangegangenen Beispielen zeigten, wird bei einer Funktion $Input und bei einem Filter $_ verwendet und es bestehen wesentliche Unterschiede in Verarbeitung und Spicherbedarf. Nun gibt es aber die Möglichkeit, einer Funktion mit dem Keyword Process dasselbe Verhalten bzw. Performance wie bei einem Filter zu verleihen:

Function meineFunktion
{
  Process { $_ }
}
PS C:\users\FelixMuster> Get-ChildItem C:\ -Recurse -ErrorAction SilentlyContinue | meineFunktion
# Bemerkung: Die Funktionsvariante ohne Process hat für spezielle Aufgaben immer noch ihre Berechtigung.

3.8 Piping

Beispiel: Get-Process explorer | Get-Member (Get-Process und Get-Member sind CmdLets. Den Pipe «|» erhält man mit ALTGR + 7)

3.9 Redirect

Redirect nennt man die Umleitung des Outputs vom Standard-Ausgabedevice (WPS-ISE-Konsole) in eine Textdatei:

# Piping/Pipeline
get-process | out-file "myProc.txt"   # Interprozesskommunikation per Pipe «|» (WIN: ALTGR + 7)

# Redirect
get-process > "myProc.txt"            # Wie bei UNIX/LINUX üblich: Es wird die Datei myProc.txt erstellt 
                                      # oder wenn bereits bestehend, ohne Rückfrage überschrieben.

# Append
get-process >> "myProc.txt"           # Append = Wenn die Datei bereits existiert, wird der Inhalt an das Dateiende angehängt,
                                      # sonst wird die Datei neu erstellt.

3.10 Alias

Das Wort Alias bedeutet «Sonst genannt / also known as» bzw. ein Alias ist ein Pseudonym für einen anderen Befehl.
Achtung: Eigene Aliase sind nur temporär vorhanden und verschwinden nach einem Neustart der WPS-ISE!

Set-Alias -Name list -Value get-childitem #get-childitem ist ein CmdLet
Set-Alias list get-childitem           # Kurzform
Set-Alias np c:\windows\notepad.exe    # Alias erzeugen
Set-Alias no1 .\myNumberOneScript.ps1  # Alias erzeugen
Get-Alias                              # Aliase anzeigen

4. Objektorientierte Programmierung

4.1 Das objektorientierte Programmierparadigma

Die objektorientierte Programmierung (OOP) ist ein auf dem Konzept der Objektorientierung basierendes Programmierparadigma. Die Grundidee besteht darin, die Architektur einer Software an den Grundstrukturen desjenigen Bereichs der Wirklichkeit auszurichten, der die gegebene Anwendung betrifft.

  • Alles ist ein Objekt
  • Objekte kommunizieren durch das Senden und Empfangen von Nachrichten
  • Objekte haben ihren eigenen Speicher
  • Jedes Objekt ist die Instanz einer Klasse
  • Die Klasse beinhaltet das Verhalten aller ihrer Instanzen

Der hauptsächliche Unterschied zwischen prozeduraler und objektorientierter Programmierung ist die Beziehung zwischen Daten und Funktionen. Während bei der objektorientierten Programmierung Daten und Funktionen, die auf diese Daten angewandt werden können, in Objekten zusammengefasst werden, haben bei der prozeduralen Programmierung Daten und Funktionen keinen Zusammenhalt.

Beispiel:

Aufgrund eines Bauplans (Class) werden Exemplare (Objects) davon erzeugt (instanziert). Nämlich in diesem Fall ein Objekt mit dem Namen «Lightning», ein anderes mit dem Namen «Blizzard» und ein drittes mit dem Namen «Thunder». Diese Objekte können nun über ihre Objektnamen angesprochen werden.

Die Objekte besitzen:

  • Eigenschaften oder Attribute (Properties), wie z.B. Farbe, Grösse und weitere Merklmale
  • Methoden, Funktionen (Methods). Methoden können Parameter erhalten, die beim Aufruf übergeben werden müssen, und einen Rückgabewert besitzen, den sie am Ende dem Aufrufer zurückgeben. Beispielsweise hat die Methode «addiere» die Parameter Zahl 1 und Zahl 2 und gibt als Rückgabewert die Summe der Zahlen zurück.

Verändern von Eigenschaften (Zugriff auf Properties):

Ausführen von Funktionen (Methods):

Spezielle Methoden zur Erzeugung und Zerstörung von Objekten heissen Konstruktoren Create() beziehungsweise Destruktoren Kill().

4.2 Zugriff auf Eigenschaften und Methoden von Objekten

Erklärt an einem Beispiel das voraussetzt, dass der Prozess Notepad auf dem System gestartet ist:

# Alle Eigenschaften und Methoden des Prozess-Objekts «Notepad» anzeigen:
Get-Process Notepad | Get-Member   # Get-Process und Get-Member sind CmdLet's

# 1. Variante → Methode ausführen:
# (Mit dem CmdLet «Get-Process» das Prozess-Objekt «Notepad» ansprechen und die Methode «GetType()» aufrufen)
(get-process notepad).GetType()

# 2. Variante → Methode ausführen:
# (Mit dem CmdLet «Get-Process» das Prozess-Objekt «Notepad» in der Object-Variablen «myObj» speichern
# und anschliessend über die Object-Variable die Methode «GetType()» ausführen)
[Object] $myObj                    # Object-Variable deklarieren
$myObj = get-process Notepad       # Object-Variable zuweisen
$myObj.GetType()                   # Über Object-Variable eine seiner Methoden ausführen

# Eigenschaft Prozess-ID anzeigen:
(Get-Process Notepad).Id
$myObj.Id

Wenn man mit einer PowerShell-Pipeline arbeitet und sich auf das Objekt beziehen möchten, das sich gerade in der Pipeline befindet, kann man eine der beiden automatisch generierten und völlig gleichwertigen Variablen $PSItem oder $_ nutzen. Dazu ein Beispiel:

Get-Process | Foreach-Object { write-host $PSItem.ProcessName $PSItem.CPU}
Get-Process | Foreach-Object { write-host $_.ProcessName $_.CPU}
Get-Process | Foreach-Object { write-host $_.ProcessName }
Get-Process | Foreach-Object { $_.ProcessName }

4.3 Statische Eigenschaften und Methoden von .Net-Klassen verwenden

[<Klassenname>]::Eigenschaft
[<Klassenname>]::Methode(<Parameter>)

Beispiele mit der .Net-Klasse «Math»

[Math]::Pi #Konstatnte Pi
$x = 0.5; [Math]::Sin($x) #Sinuswert ermitteln
$y = 2; [Math]::Sqrt($y) #Quadratwurzel ermitteln

Beispiele mit der .Net-Klasse «Convert»

$Bin2Dez = [Convert]::ToInt32("101101010101",2)   #Resultat: 290110
$Oct2Dez = [Convert]::ToInt32("1234567",8)        #Resultat: 34239110
$Dez2Hex = [Convert]::ToString("65098",16)        #Resultat: "FE4A"
$Dez2Bin = [Convert]::ToString("12345",2)         #Resultat: "11000000111001"

4.4 Klassen instanzieren

Bei Powershell ist alles ein Objekt mit Eigenschaften und Methoden, sogar der Integer und der String.
Beispiel:

[string] $myStr = "Hello"
$myStr | Get-Member
$myStr.Length              # Ergibt den Wert 5
$myStr.ToUpper()           # Ergibt HELLO

Beim folgenden Beispiel wird nicht ein neues Objekt erzeugt, sondern eine Objektvariable erstellt und dieser die Referenz auf das Prozessobjekt "notepad" zugewiesen. Das Prozessobjekt "notepad" wurde von Betriebssystem zu dem Zeitpunkt erstellt, als das Programm notepad.exe gestartet wurde:

[object]$myObject
$myObject = Get-Process notepad

Es soll mit New-Object ein Objekt von einer .Net-Klasse erstellt werden. Dies soll anhand eines Beispiels mit der Klasse Net.WebClient gezeigt werden. Diese enthält die Methode DownloadFile, mit der eine Datei aus dem Internet geladen und lokal gespeichert werden kann:

$web = New-Object -typename Net.WebClient
$web.DownloadFile("http://edu.juergarnold.ch/fach_it/wpssummary/titel.jpg", "C:\Users\FelixMuster\Pictures\titel.jpg")

Falls an ihrem Standort ein Proxiserver mit z.B. IP=192.0.200.200 an Port=8080 existiert, muss folgendes ergänzt werden:

$web = New-Object -typename Net.WebClient
$proxy = New-Object System.Net.WebProxy("http://192.0.200.200:8080")
$web.proxy = $proxy #WebClient über diesen Proxy leiten
$proxy.UseDefaultCredentials = $true #Benutzt Benutzername & PW des aktuellen Standorts
$web.DownloadFile("http://edu.juergarnold.ch/fach_it/wpssummary/titel.jpg", "C:\Users\FelixMuster\Pictures\titel.jpg")

5. Standardobjekte in Powershell

5.1 Zählfunktionen

Um Dinge zu zählen gibt es z.B. das Cmdlet Measure-Object.

Get-Process * | Measure-Object
Get-ChildItem -Filter *.txt | Measure-Object -Property length -Maximum -Minimum -Average –Sum
Get-Content MeinText.txt | Measure-Object -line -char –word -IgnoreWhiteSPace
(Get-ChildItem C:\Users\MeinNamen -Recurse –force).count

5.2 String-Methoden und Funktionen

Bei Zeichenketten (Strings) handelt es sich immer um Objekte.

"Hallo Welt" | Get-Member                  # Zeigt alle Methoden und Eigenschaften eines Stringobjektes an

$h = "Hallo"                               # Zeichenkette zusammenfügen
$w = "Welt"
$h + " " + $w                              # Eine Alternative wäre: "$h $w"

$st = @("Hallo", "Welt")                   # Elemente eines String-Arrays zusammenfügen
"$st"                                      # Dazwischen wird automatisch ein Leerzeichen eingefügt

[string]$s1 = "Hallo"
[string]$s2 = "Welt"
-join($s1,$s2)                             # Verknüpfung mit join-Operator. Kein Leerzeichen dazwischen
$s1,$s2 -join " "                          # Dazwischen wird nun ein Leerzeichen eingefügt

("Hallo Welt").split(" ")                  # Zeichenkette am Leerzeichen zerlegen. Das Leerzeichen entfällt
("Hallo Welt").Substring(2,5)              # Teil der Zeichenkette herauslösen
("Hallo Welt").Remove(2,3)                 # Teil der Zeichenkette löschen
("Hallo Welt").Replace("Hallo","Neue")     # Teil der Zeichenkette ersetzen
("Hallo Welt").Contains("ll")              # Teil der Zeichenkette suchen. Liefert $True oder $False zurück
("Hallo Welt").IndexOf("ll")               # Position eines Zeichens oder Substrings ermitteln

[string]$s1 = "Eisen"
[string]$s2 = "Bahn"
("EisenBahn").CompareTo($s1 + $s2)         # Liefert den Wert 0 → Reihenfolge identisch
("BahnEisen").CompareTo($s1 + $s2)         # Liefert den Wert -1 → Reihenfolge umgekehrt
("Eisen Bahn").CompareTo($s1 + " " + $s2)  # Liefert den Wert 0 → Reihenfolge identisch
("EisenBahn").CompareTo($s1 + " " + $s2)   # Liefert den Wert 1

("Eisen Bahn").Equals($s1 + " " + $s2)     # Liefert den Wert $True → Zeichenketten sind identisch

"eisenbahn".ToUpper()                      # Wandelt die komplette Textzeile in Grossbuchstaben um

$nl = [System.Environment]::NewLine        # Erzeugt eine Neue Zeile bei der Ausgabe
write-host "Eisen $nl Bahn"

5.3 DateTime-Methoden und Funktionen

In einer DateTime-Variable lassen sich Zeit- und/oder Datumswerte abspeichern und verarbeiten.
Da ein Datum immer ortsabhängig (Culture) angegeben werden muss, müssen wir bei der Eingabe immer über das ortssensitive Cmdlet Get-Date operieren.

Get-Date | get-member -MemberType Property                  # Eigenschaften anzeigen
Get-Date | get-member -MemberType Method                    # Methoden anzeigen

$info = new-object system.globalization.datetimeformatinfo
$info.DayNames
$info.MonthNames

[DateTime]$Zeitpunkt = Get-Date "12.3.2015 15:33"           # Konkretes Datum/Uhrzeit als String eingeben
Write-Host $Zeitpunkt

[DateTime]$Zeit = Get-Date "15:33"                          # Konkrete Zeit als String eingeben
Write-Host $Zeit

[DateTime]$Datum = Get-Date "12.3.2015"                     # Konkretes Datum eingeben (Uhrzeit ist dann 00:00:00)
Write-Host $Datum

"Day: " + $Datum.Day
"Month: " + $Datum.Month
"Year: " + $Datum.Year
"Hour: " + $Zeit.Hour
"Second: " + $Zeit.Second
$Zeitpunkt.DayOfYear                                        # Der wievielte Tag im Jahr?
$Zeitpunkt.TimeOfDay                                        # Tabelle mit allen Werten

$Zeitpunkt.AddDay(2)                                        # Anzahl Tage dazuzählen
$Zeitpunkt.AddDay(-2)                                       # Anzahl Tage abzählen
([datetime] "1.2.2006" - [datetime]"1.1.2006").TotalDays    # Datumsdifferenz

function tillXmas ()                                        # Eigene Funktion erstellen
{
  $now = [DateTime]::Now
  [Datetime]("25.12." + [string]$now.Year) - $Now
}

6. Anwendungen mit Powershell

6.1 Ausgabe in eine HTML-Datei

# Hinweis Zeilenumbruch: Für eine bessere Lesbarkeit, wurde die lange Code-Zeilen "ConvertTo-Html" auf mehrere Zeilen umgebrochen.
#                        Damit dies zu keinem Syntaxfehler führt, muss jede Zeile mit "`" ergänzt werden.
# Hinweis foreach :      foreach verlangt das Begin "{" auf der selben Zeile.

get-service | ConvertTo-Html `
              -title "MELDUNG SYSADMIN" `
              -body (get-date) `
              -pre "<p>Erstellt von Felix Muster</p>" `
              -post "<p>Weitere Infos siehe <a href=http://felixmuster> http://felixmuster</a></p>" `
              -Property Name, Status | foreach {

  if($_ -like "*<td>Running</td>*")
  {
    $_ -replace "<tr>", "<tr bgcolor=green>"
  }
  elseif($_ -like "*<td>Stopped</td>*")
  {
    $_ -replace "<tr>", "<tr bgcolor=red>"
  }
  else
  {
    $_
  }
} > .\ServiceStatus.html

6.2 Mit Dateien arbeiten

Set-Location Pfadname                             # Pfad setzen
Get-Location                                      # Pfad auslesen
Test-Path -path Pfadname                          # Existenz eines Pfades überprüfen ($True oder $False)
Get-Item Verzeichnisname                          # Verzeichnis lesen
Get-Item Dateiname                                # Datei lesen
Get-ChildItem Verzeichnisname -include Pattern    # Filterfunktion-Einschliesslich; Pattern → *.jpg 
Get-ChildItem Verzeichnisname -exclude Pattern    # Filterfunktion-Ausschliesslich; Pattern → *.gif
Get-ChildItem Verzeichnisname -recurse            # Rekursion → Zeigt auch alle Unterverzeichnisse an
(Get-Item .\meinUnterverzeichnis).delete()        # Leeres Unterverzeichnis löschen
#-------------------------------------------------------------------------------------------------------
$myFile = Get-Item *.txt                          # Ergibt Array von gefundenen Dateiobjekten
$myFile | Get-Member                              # Dateiobjekt-Array wird an Get-Member weitergereicht
#-------------------------------------------------------------------------------------------------------
$myFile = Get-Item .\meinText.txt                 # Auf Dateieigenschaften von "meinText.txt" zugreifen
Write-Host $myFile.Name                           # Dateiname anzeigen
Write-Host $myFile.Length                         # Dateigrösse in Byte anzeigen
#-------------------------------------------------------------------------------------------------------
$myFileList = get-ChildItem Pfadname -recurse     # Zeigt alle Verzeichnisse ab Pfadname an
foreach($myFile in $myFileList) {
  if($myFile.PSISContainer -eq $True)
  {
    Write-Host $myFile.Name
  }
}
#-------------------------------------------------------------------------------------------------------
dir Pfadname -r | foreach {                       # Zeigt ebenfalls alle Verzeichnisse ab Pfadname an
  if ($_.mode –match "d")
  {
    Write $_.Name
  }
}
#-------------------------------------------------------------------------------------------------------
if ((get-ChildItem Pfadname).exists -eq $True)    # Existenzprüfung:
{                                                 # Falls WPS die Datei nicht findet,
  write-host OK                                   # auf die eine Operation ausgeführt werden soll,
}                                                 # kommt es zu einer Fehlermeldung.
else                                              # Darum muss zuerst deren Existenz überprüft werden.
{
  write-host NOK
}

Dateien kopieren, verschieben, löschen oder umbenennen

# Wichtig für alle folgenden Beispiele:
# Falls der Pfadname relativ angegeben wird, empfiehlt es sich, an erster Stelle den aktuellen Pfad wie folgt zu setzen:
[environment]::CurrentDirectory = Get-Location    # Aktueller Pfad setzen, falls relative Pfadangabe folgt

# Eine Datei kopieren
$myFile = Get-Item Dateiname                      # Z.B. Get-Item .\meinText.txt
$myFile.CopyTo("Pfadname\Dateiname", -1)          # -1 → Überschreiben

# Mehrere Dateien kopieren - 1. Variante
$myFileList = get-ChildItem Pfadname\Dateiname
$destDir = "MeinZielPfadname"
foreach($myFile in $myFileList) {                 # Out-Null soll die Ausgabe unterdrücken
  $myFile.CopyTo($destDir +($myFile.Name), -1) | Out-Null
}

# Mehrere Dateien kopieren - 2. Variante
$myFileList = get-ChildItem Pfadname\Dateiname
$destDir = "MeinZielPfadname"
Copy-Item $myFileList $destinationDir -force      #force erzwingt Überschreiben

# Eine Datei verschieben
$myFile = Get-Item Dateiname
$myFile.MoveTo("Pfadname\Dateiname")

# Mehrere Dateien verschieben
$myFileList = get-ChildItem Pfadname\*.*         # z.B.: $myFileList = get-ChildItem C:\Dokumente\*.txt
$destDir = "MeinZielPfadname"
Move-Item $myFileList $destDir

# Eine Datei löschen
$myFile = Get-Item Dateiname
$myFile.Delete()

# Mehrere Dateien löschen
$myFileList = get-ChildItem Pfadname\*.*         # z.B.: $myFileList = get-ChildItem C:\Dokumente\*.txt
Remove-Item $myFileList

# Dateien umbenennen (Eine eigentliche Methode gibt es nicht. Allenfalls kann mit MoveTo() 
                      eine Umbenennung konstruiert werden.
                      Ein CmdLet existiert allerdings und nennt sich Rename-Item)
$myNewName = Read-Host "Neuer Dateinamen"
$myZaehler = 0
$myFiles = Get-ChildItem *.txt
if($myFiles -ne $NULL)
{
   foreach($File in $myFiles) {
     $myFullNewName = $myNewName + $myZaehler + ($File.extension)
     Rename-Item $File $myFullNewName
     $myZaehler++
   }
   write-host ($myZaehler)" Dateien umbenannt"
}

Mit Dateiattributen arbeiten:

# Es existieren unter MS-Windows folgende Dateiattribute:
# +R bzw -R → Schreibgeschützt setzen (+) bzw. löschen (-)
# +A bzw -A → Archiv setzen (+) bzw. löschen (-)
# +S bzw -S → Systemdatei setzen (+) bzw. löschen (-)
# +H bzw -H → Versteckte Datei setzen (+) bzw. löschen (-)
# (Im folgenden beschäftigen wir uns nur noch mit dem Archive-Attribut)
# In den Dateieigenschaften findet man unter Allgemein/Erweitert das Archive-Attribut:
# Erweiterte Attribute: ⊗ Datei kann archiviert werden
# Das Archiv-Bit kann z.B. von einer Backup-SW ausgewertet werden.
# Archiv-Bit = "1": Die Datei wurde verändert und soll bei einem Backup gespeichert werden.
# Archiv-Bit = "0": Die Datei wurde (z.B. seit dem letzten Backup) nicht verändert und muss nicht neu archiviert werden.
# Ist das Archiv-Bit auf 0, wird es automatisch vom Betriebssystem auf 1 gesetzt, wenn auf das File schreibend zugegriffen wird.
# Dieses Attribut lässt sich mit dem folgenden Win-Programm ändern:
# attrib.exe meinFile -A (Löscht das Archiv-Bit =0)
# attrib.exe meinFile +A (Setzt das Archiv-Bit =1)
# In Powershell:
set-itemproperty meinFile -Name Attributes -Value "Normal"  # → Löscht alle Attribute, auch das Archive-Attribut
set-itemproperty meinFile -Name Attributes -Value "Archive" # → Setzt das Archive-Attribut

[Object]$meineDatei = get-childitem meinText.txt            # Objektvariable initialisieren und deklarieren
if($meineDatei.mode -eq "-a----")                           # → Das Datei-Attribut in der Objekt-Eigenschaft "Mode" ausgewertet
{                                                           #   (Mode ist übrigens schreibgeschützt)
  # Tu was Gutes
}

Webseite in eine Datei kopieren:

$wc = new-object System.Net.WebClient
$wc.DownloadString('http://edu.juergarnold.ch/index.html') | out-file edu.txt
get-content edu.txt

Eingabe der Pfadnamen für Quellverzeichnis und Zielverzeichnis inklusive Überprüfung.

function validatePath                             # Pfadsyntax Ueberpruefen
{
  $trim = $args[0] -replace " ", ""               # Alle Leerschlaege loeschen
  if ($trim.Length -eq 0 -or (Test-Path -isValid $args[0]) -eq $False)
  {
    return $False;                                # Rueckgabewert ins initDirectory()
  }
  return $True;                                   # Rueckgabewert ins initDirectory()
}

function initDirectory                            # Syntax-Check
{
  if ((validatePath($args[0])) -eq $False)        # Funktionsaufruf validatePath()
  {
    return $False                                 # Rueckgabewert ins Hauptprogramm
  } 
  if ((Test-Path -path $args[0]) -eq $False)      # Existiert Verzeichnis bereits?
  { 
    new-item -path $args[0] -type directory       # Verzeichnis erstellen, falls noch nicht vorhanden
    write-host " Verzeichnis $args neu erstellt!"
  }
  else
  {
    write-host " Verzeichnis $args existiert bereits!"
  }
  return $args[0]                                 # Rueckgabewert ins Hauptprogramm
}

# ------------------ Hauptprogramm --------------------------------------------------
do                                                # Abfrage nach dem Quellverzeichnis
{
  $mySRC = Read-Host "Quellverzeichnis"
  $src = $mySRC
  $src = initDirectory($src)
} While($src -eq $False)

do                                                # Abfrage nach dem Zielverzeichnis
{
  $myDST = Read-Host "Zielverzeichnis"
  $dst = $myDST
  $dst = initDirectory($dst)
} While($dst -eq $False)

# Kontrollausgabe
write-host "Quellverzeichnis:" $mySRC " Zielverzeichnis:" $myDST

6.3 Lokale Benutzer und Gruppen verwalten

Die folgenden CmdLets helfen bei der Automatisierung des lokalen User-Managements. Dies ist insbesondere dann interessant, wenn man die Vorgaben wie Benutzername, Passworte usw. aus einer EXCEL-Datei bezieht. Wie man mit EXCEL-Dateien arbeitet, ist im nachfolgenden Kapitel "Arbeiten mit COM-Objekten" beschrieben.
(Hinweis: Für das Anlegen eines Active-Directory-Users siehe New-ADUser und Set-ADUser. Wird hier aber nicht weiter behandelt!)

Get-Command -Module Microsoft.PowerShell.LocalAccounts   # Sämtliche CmdLets für LocalAccounts
Get-LocalUser A* | select *                              # Zeigt alle User mit dem Startbuchstaben "A" an
Get-LocalGroup                                           # Zeigt alle Gruppen an

Neues Benutzerkonto erstellen, ändern und löschen
Angaben, mit denen sich die Eigenschaften eines neu zu erstellenden Kontos festlegen lassen sind:

  • AccountExpires → DateTime
  • AccountNeverExpires
  • Description → String
  • Disabled
  • FullName → String
  • Name → String (Pflichtangabe)
  • Password → SecureString (Pflichtangabe)
  • NoPassword (Alternative Pflichtangabe zum obigen Password, wenn auf ein Passwort verzichtet wird)
  • PasswordNeverExpires
  • UserMayNotChangePassword
# Interaktive-Variante
[System.Security.SecureString]$meinPasswort = Read-Host -AsSecureString
New-LocalUser -Name "FelixMuster" -Password $meinPasswort -FullName "Felix Muster" -Description "Musterknabe"

# Batchverarbeitungs-Variante
[System.Security.SecureString]$meinPasswort = ConvertTo-SecureString -String "Pa$$w0rd" -AsPlainText -Force
New-LocalUser -Name FelixMuster  -Password $meinPasswort -FullName "Felix Muster" -Description "Musterknabe"

Set-LocalUser -Name FelixMuster -Description "Musterknabe" -AccountExpires "31.12.2030" # Nachträgliche Angaben
Rename-LocalUser -Name FelixMuster -NewName FelixKuster                                 # Benutzername umbenennen
Remove-LocalUser -Name FelixMuster                                                      # Eintrag wieder löschen

Neue Gruppe erstellen, ergänzen und löschen

Get-LocalGroup                                                 # Zeigt alle Gruppen an
Get-LocalGroupMember -Name Administratoren                     # Alle in Administratoren enthaltene Benutzer/Gruppen
New-LocalGroup -Name Verkauf -Description "Abteilung Verkauf"  # Erstellt neue Gruppe für die Abteilung Verkauf
Add-LocalGroupMember -Name Verkauf -Member FelixMuster         # Fügt Felix Muster der Gruppe Verkauf hinzu
Remove-LocalGroupMember -Name Verkauf -Member FelixMuster      # Entfernt Felix Muster aus der Gruppe Verkauf
Remove-LocalGroup -Name Verkauf                                # Löscht die Gruppe Verkauf

# Anstelle von Benutzernamen kann man bei Add-LocalGroupMember und Remove-LocalGroupMember
# für Member auch eine Gruppe angeben.

6.4 Arbeiten in der Registry

Die Windows-Registrierungsdatenbank (Registry) ist die zentrale hierarchische Konfigurationsdatenbank des Betriebssystems Windows. Hier werden sowohl Informationen zu Windows selbst als auch Informationen zu Programmen gespeichert. Die Registry steht Powershell wie ein normales Laufwerk zur Verfügung. Darum kommen ähnliche CmdLets und Funktionen zur Anwedung wie beim Arbeiten mit Dateien. Registry-Schlüssel (HKEY_ = Handle to a Key_) lassen sich mit Get-ChildItem anzeigen, deren Inhalte mit Get-ChildItemProperty.

# Registry-Schlüssel und Properties in "HKEY_LOCAL_MACHINE\Software" anzeigen:
Get-ChildItem HKLM:\Software

# Properties anzeigen: (Hier der Autostart-Schlüssel)
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Run

# Neuer Registry-Schlüssel in "HKEY_LOCAL_MACHINE\Software" anlegen:
New-Item -path "HKLM:\Software" -Name "Testkey"

# Neuer Eintrag erstellen:
New-ItemProperty -literalpath "HKLM:\Software\Testkey" -Name "Testkey" -Value "Test CRM" -type String

# Eintrag entfernen:
Remove-ItemProperty -literalpath "HKLM:\Software\Testkey" -Name "Testkey"

# Registry-Schlüssel entfernen:
Remove-Item -path "HKLM:\Software\Testkey"

6.5 Arbeiten mit Eventlogs

# Die drei letzten Einträge im System-Eventlog formatiert auslesen:
get-eventlog system -newest 3 | format-list
get-winevent -logname system -computername 127.0.0.1 -maxevents 3 | format-list

# Ein Event in den Application-Eventlog eintragen: 
# (Falls noch nicht geschehen, die neue Event-Quelle einmalig registrieren)
New-EventLog -LogName Application -Source myApp            # Neue Event-Quelle registrieren
Write-EventLog -logname "Application" `
               -computername $env:COMPUTERNAME `
               -source "myApp" `
               -eventID 9999 `
               -entrytype Information `
               -message "Hello World"

# Hinweis:
# -computername [computername] ermöglicht auch die Abfrage über das Netzwerk
# $env:COMPUTERNAME ist der eigene Computername 127.0.0.1.
# In diesem Fall könnte -computername auch ganz weggelassen werden

6.6 Arbeiten mit COM-Objekten

Das Component Object Model (COM) ist eine von Microsoft entwickelte Technik zur Interprozesskommunikation unter Windows. COM-Komponenten können sowohl in Form von Laufzeitmodulen (DLLs) als auch als ausführbare Programme umgesetzt sein. COM soll eine leichte Wiederverwendung von bereits geschriebenem Programmcode ermöglichen, zum Teil auch über Betriebssystemgrenzen hinweg.  Als Beispiel sollen Automatisierungsmöglichkeiten von Office, speziell Excel und Webseitenabfragen mit Internetexplorer aufgezeigt werden.

Excel:

# Bestehendes Workbook öffnen und lesen(Schritt für Schritt):
$myExcel = New-Object -comobject Excel.Application                 # Excel-Objekt erzeugen
$myWorkBook = $myExcel.Workbooks.Open("C:\...\meineDatei.xlsx")    # Workbook öffnen
$myWorkSheet = $myWorkBook.sheets.item("Tabelle1")                 # Worksheet/Tabelle öffnen
$myExcel.Visible = $True                                           # EXCEL-Tabellen anzeigen, falls gewünscht
[string]$meinText1 = $myWorkSheet.Cells.Item(1,1).Text             # Text in Zelle A1 lesen
[string]$meinText2 = $myWorkSheet.Cells.Item(2,1).Text             # Text in Zelle A2 lesen
$myExcel.Quit()                                                    # Excel schliessen
Remove-Variable myWorkSheet, myWorkBook, myExcel                   # Variablen löschen

# Neues Workbook erstellen und befüllen (Schritt für Schritt):
$myExcel = New-Object -comobject Excel.Application                 # Excel-Objekt erzeugen
$myWorkBook = $myExcel.Workbooks.Add()                             # Neues Workbook erzeugen
$myWorkSheet = $myWorkBook.Worksheets.Item(1)                      # Neues Worksheet/Tabelle erzeugen
$myWorkSheet.Cells.Item(1,1) = "Hello World"                       # Zellen füllen
$myWorkSheet.Cells.Item(2,1) = "2+4"                               # Bsp.: Textzeile
$myWorkSheet.Cells.Item(3,1) = 2+4                                 # Bsp.: Integer
$myExcel.Visible = $True                                           # EXCEL-Tabellen anzeigen, falls gewünscht
$myWorkBook.SaveAs(".\test.xlsx")                                  # Excelsheet abspeichern... 
                                                                    (...bei relativem Pfad in Dokumentenordner)
$myExcel.Quit()                                                    # Excel schliessen
Remove-Variable myWorkSheet, myWorkbook, myExcel                   # Variablen löschen

# Hilfe erhalten
$myExcel | Get-member                                              # Eigenschaften / Methoden des Excel-Objekts
$myExcel.Workbooks | Get-Member                                    # Eigenschaften / Methoden von Workbooks
$myExcel.Workbooks | Select-Object -Property name, path, author    # Überprüfen, ob Workbooks geöffnet ist

Internet-Explorer:

# InternetExplorer-Objekt erzeugen:
$myIE = New-Object -comobject InternetExplorer.Application

# Eigenschaften und Methoden des InternetExplorer-Objekts anzeigen:
$myIE | Get-member

# InternetExplorer positionieren:
$myIE.top = 20
$myIE.width = 1800
$myIE.height = 1600
$myIE.Left = 20;

# InternetExplorer anzeigen:
$myIE.Visible = $True

# Webseite aufrufen:
$myIE.Navigate("edu.juergarnold.ch")

6.7 Arbeiten mit Webformularen

# Mit Invoke-WebRequest ein Formular aufrufen und Formular mit SessionKey merken:
$myWebForm=Invoke-WebRequest http://edu.juergarnold.ch/fach_it/wpssummary/testForm.html -SessionVariable mySession

# Im Formular "berechnung" das Feld "resultat" beschreiben:
$myWebForm.Forms["berechnung"].Fields["resultat"] = "2"

# Formularinhalt übermitteln und Formularaktion auslösen:
Invoke-WebRequest -Method POST  `
                  -URI ("http://edu.juergarnold.ch/fach_it/wpssummary/" + $myWebForm.Forms["berechnung"].action)  `
                  -Body $myWebForm.Forms["berechnung"].Fields -WebSession $mySession

6.8 Arbeiten mit .net-Objekten - Erstellen einer grafischen Benutzereingabe

# Benötigte .NET-Frameworkklassen laden:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Instanz einer Form-Klasse erstellen:
$form = New-Object System.Windows.Forms.Form

# Form-Objekt anpassen:
$form.Text = 'FORMULAR'
$form.Size = New-Object System.Drawing.Size(400,300)
$form.StartPosition = 'CenterScreen'

# OK-Button kreiern, anpassen und in das Formular einfügen:
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = 'OK'
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK $form.AcceptButton = $OKButton
$form.Controls.Add($OKButton) # Cancel-Button kreiern, anpassen und in das Formular einfügen: $CancelButton = New-Object System.Windows.Forms.Button $CancelButton.Location = New-Object System.Drawing.Point(150,120) $CancelButton.Size = New-Object System.Drawing.Size(75,23) $CancelButton.Text = 'Cancel' $CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel $form.CancelButton = $CancelButton $form.Controls.Add($CancelButton) # Beschriftung kreiern, anpassen und in das Formular einfügen: $label = New-Object System.Windows.Forms.Label $label.Location = New-Object System.Drawing.Point(10,20) $label.Size = New-Object System.Drawing.Size(280,20) $label.Text = 'Bitte Text eingeben:'
$form.Controls.Add($label) # Texteingabezeile kreiern, anpassen und in das Formular einfügen: $textBox = New-Object System.Windows.Forms.TextBox $textBox.Location = New-Object System.Drawing.Point(10,40) $textBox.Size = New-Object System.Drawing.Size(260,20) $form.Controls.Add($textBox) # Topmost-Eigenschaft auf $True setzen, um das Öffnen des Fensters über anderen geöffneten Fenstern und Dialogfeldern zu erzwingen: $form.Topmost = $true # Formular aktivieren und Fokus auf das Textfeld setzen: $form.Add_Shown({$textBox.Select()}) # Formular anzeigen lassen: $result = $form.ShowDialog() # Der Code im If-Block weist Windows an, wie mit dem Formular verfahren werden soll, # nachdem der Benutzer einen Text eingegeben und die Schaltfläche OK oder Eingabetaste gedrückt hat: if ($result -eq [System.Windows.Forms.DialogResult]::OK) { $x = $textBox.Text }

7. SW-Test

7.1 Einführung Testen von Software

Das Wort Qualität steht für viele verschiedene Aspekte eines Produktes oder einer Dienstleistung und bedeutet umgangssprachlich:

  • Etwas das gut bis sehr gut ist und/oder lange hält
  • Gute Preis/Leistung
  • Funktionsvielfalt, Verarbeitung
  • Markenname der für gute Qualität bürgt
  • Garantiedauer
  • Verständliche und vollständige Bedienungsanleitung
  • Nachhaltigkeit, Ökologische Herstellung und Entsorgung
  • Haltbarkeit
  • Sicherheit
  • Design
  • weiteres...

In verschiedenen Normen wird die Qualität beschrieben oder definiert. Als Beispiel die Begriffsdefinition aus DIN 55 350:
Qualität = Die Gesamtheit von Eigenschaften eines Produktes oder einer Tätigkeit, die sich auf deren Eignung zur Erfüllung gegebener Erfordernisse beziehen.

Qualität von Software

Die wichtigsten Qualitätsmerkmale sind in Normen wie der DIN/ISO 9126 geregelt. Gelten bei einem Softwareauftrag/Produkt für ein Grossprojekt andere Qualitätsmassstäbe? Dazu ein Fallbeispiel:

Was kann der Kunde erwarten, wenn er ein Softwareprodukt wie z.B. eine Textverarbeitung kauft?

  • Zuverlässigkeit (Reliability): Fähigkeit der Software, ihr Leistungsniveau unter festgelegten Bedingungen über einen festgelegten Zeitraum zu bewahren.
    Office bearbeitet beliebig komplexe Dokumente gleich.
    Ohne Absturz. (mit/ohne Fehlmanipulation)
  • Funktionalität (Functionality): Vorhandensein von Funktionen mit festgelegten Eigenschaften. Diese Funktionen erfüllen die definierten Anforderungen.
    Office besitzt alle in der Dokumentation angegebenen (nützlichen) Funktionen.
  • Benutzbarkeit (Usability): Aufwand, der zur Benutzung erforderlich ist, und individuelle Beurteilung der Benutzung durch eine festgelegte oder vorausgesetzte Benutzergruppe.
    Office lässt sich intuitiv erlernen und benutzen. (Benutzerführung, klare Fehlermeldung)
  • Effizienz (Efficiency): Verhältnis zwischen dem Leistungsniveau der Software und dem Umfang der eingesetzten Betriebsmittel unter festgelegten Bedingungen.
    Office erledigt alle Funktionen in angemessener Zeitspanne. (Reaktionszeit)
    Flüssiges Arbeiten möglich, Applikation ist möglichst resourcenschonend.
  • Änderbarkeit (Maintainability): Aufwand, der zur Durchführung vorgegebener Änderungen notwendig ist. Änderungen können Korrekturen, Verbesserungen oder Anpassungen an Änderungen der Umgebung, der Anforderungen und der funktionalen Spezifikationen einschliessen.
    Office lässt sich einfach konfigurieren oder erweitern. (Plug ins) 
    Für die Programmierer gilt: Klare Strukturierung, bessere Wartbarkeit.
  • Übertragbarkeit (Portability): Eignung der Software, von einer Umgebung in eine andere übertragen zu werden. Umgebung kann organisatorische Umgebung, Hardware- oder Software-Umgebung einschliessen.
    Office lässt sich auf Windows, Mac, Smartdevices etc. installieren oder aber auch cloud-basiert ausführen.

Auswirkungen von Fehlern

In der Praxis können sehr kleine Fehler sehr grosse Konsequenzen haben:

  • Systemabstürze. Kann auch andere Programme betreffen und kommt für den SW-Lieferanten dem GAU sehr nahe. Bei Powershell: Rote Fehlermeldungen
  • Programmabbrüche
  • Datenverfälschung - Anomalien (siehe auch M104)
  • Falsches Systemverhalten
  • Prestigeverlust des SW-Firma
  • Frustrierte Benutzer/Kunden
  • usw.

Ob ein vorhandener Fehler die Verbreitung einer neuen Version, die Verteilung eines Patches oder eventuell auch keine Massnahme erfordert, ist von einer Vielzahl von Kriterien anhängig. Die Folgen und Kosten von Fehlern hängen stark von der Art des Einsatzes eines Produktes ab:

  • Massenprodukt (grosse Anzahl Anwender, die dem Hersteller nicht direkt bekannt sind)
  • Individuelle Software (kleiner Anwenderkreis mit Integration in den Entwicklungsprozess)
  • Einsatz in einem sicherheitsrelevanten Bereich mit potentiellem Personenschaden, Materialschaden oder finanziellem Verlust. (Spital, technische Anlagen, Finanzsysteme usw.)

Fehlerarten

Es ist also wichtig zu ermitteln, wo welche Fehler entstehen können. Abgesehen von Syntaxfehlern, die bei der Übersetzung eines Programms automatisch erkannt werden und von Laufzeitfehlern, die unmittelbar zum Absturz führen, gibt es weitere Fehlerarten, die meist schwieriger aufzufinden sind:

  • Codierfehler (z.B. bei Zeichencodierung)
  • Logikfehler (z.B. bei Entscheidungen if-else, switch etc.)
  • Strukturfehler (z.B. bei Iterationen, while, do-while, for)
  • Entwurfsfehler (z.B. Realität in SW falsch abgebildet)
  • Datenfehler (z. B. char/integer; Array falsch definiert; int32 anstatt int64; Jahr 2000 Problematik)
  • Umsetzungsfehler (von der Spezifikation/Pflichtenheft ins Programm)
  • Fehler in der Benutzeroberfläche (bei GUI oder sonstiger Benutzerinteraktion)

7.2 Analytische Qualitätssicherung zur Vermeidung von Fehlern

Testgrundlagen

Unter Qualitätssicherung versteht man die Summe aller Massnahmen, die die Qualität des entstehenden Produktes gewährleisten sollen. Im Softwarebereich wird unter der analytischen Qualitätssicherung der Test von Software und Dokumentation verstanden. Durch den Test wird das Ziel verfolgt, Fehler aufzudecken. Jeder gefundene und beseitigte Fehler führt zu einer Produktverbesserung.

Testaktivitäten und Testplanung

Die Testaktivitäten können nicht erst kurz vor Schluss in Angriff genommen werden. Das Testen von Software muss geplant sein, nimmt es doch bei grossen Projekten ca. 10% bis 15% der gesamten Projektzeit in Anspruch.

Es gibt für das Durchführen von Testaktivitäten einige Prinzipien, die sich in der Praxis bewährt haben:

  • Fehler möglichst frühzeitig aufdecken: Das heisst, ein Test sollte frühzeitig und entwicklungsbegleitend erfolgen, um Fehlerauswirkungen und Folgefehler zu minimieren.
  • Aus Fehlern lernen und sie zukünftig vermeiden: Das heisst, Fehlerursachen sind zu analysieren, um Hinweise für eine zukünftige Fehlervermeidung zu erhalten.
  • Bei komplexen Testobjekten unabhängige Tests durchführen: Das heisst, um der Zielsetzung des Testens (Fehleraufdeckung) gerecht zu werden, sollen vor allem bei komplexen und wichtigen Produkten unabhängige Tester eingesetzt werden.
  • Testziele erreichbar und messbar formulieren: Das heisst, dass überprüfbare Ziele (z.B. Forderungen über auszuführende Testfälle) für den Test formuliert werden sollten. Das Testende ist erreicht, wenn die zuvor aufgestellten Testziele erreicht werden.
  • Testfälle professionell handhaben: Das heisst, die Testfälle sind zu speichern und für spätere Testwiederholungen (Regressionstests) verfügbar zu machen.
  • Testaktivitäten planen: Das heisst, ohne Planung werden Testaktivitäten unsystematisch und im Wortsinne "ungeplant" durchgeführt. Die Testplanung ist in die Projektplanung zu integrieren.
  • Testaktivitäten dokumentieren: Das heisst, nur durch Dokumentation ist Transparenz und Revisionssicherheit der Tests gewährleistet. Zur Testdokumentation gehören Testpläne, Testspezifikationen, Testfallbeschreibungen, sowie Test- und Fehlerprotokolle.

Testarten und Zeitpunkte

In der SW-Entwicklung werden während den verschiedenen Entwurfsphasen Vorgaben für die Tests der entsprechenden Ebenen spezifiziert. Bei der Umsetzung werden die Tests dann durchgeführt und entscheiden über den Fortgang der weiteren Entwicklung:

Systematische Tests

Zur Durchführung systematischer Tests wird in der Regel ein Testplan (Protokoll) erstellt. Dies kann bereits in der Design-Phase (Planung) geschehen. Dabei erfolgt die Aufteilung nach Fehlerkategorien. Die Durchführung dieser Tests kann je nach Entwicklungsumgebung von Debuggingtools oder speziellen Testsuiten unterstützt werden.

Datenfehler erkennen:

  • Eingabenprüfung (Richtige und falsche Werte und Typen)
  • Extremwerte prüfen (z.B. negative Zahlen, sehr grosse und sehr kleine Zahlen)
  • Ausgabenprüfung (Verwendung von Testdaten)
  • Erzeugt durch Testdaten-Generator (speziell entwickeltes Programm)
  • Daten aus der Vergangenheit/aus dem existierenden Umfeld (evtl. mit bekannten Ergebnissen)

Strukturfehler erkennen:

  • Pfadanalyse allgemein (Traces)
  • Definieren kritischer Pfade

Statische Methoden: (Bei den statischen Methoden wird der Programmcode nicht ausgeführt. Das heisst, die Programmzeilen werden visuell nach bestimmten Kriterien untersucht.)

  • Audit
  • Inspektion
  • Code-Review
  • Walkthrough

Dynamische Methoden: (Bei den dynamischen Testmethoden wird die Software gestartet und nach bestimmten Testkriterien durchlaufen.)
Es gibt einige Grundsätze für die Testfallermittlung:

  • Die Testfälle sollen minimalistisch sein. D.h dass zur Aufdeckung eines bestimmten Fehlers möglichst nur ein Testfall gebraucht wird.
  • Die Testfälle sollen so zusammengestellt werden, dass das gesamte Testobjekt abgedeckt wird.
  • Die Testfälle sollen nicht nur den Normalfall abdecken, sondern insbesondere auch Grenzfälle und Ausnahmesituationen testen.

White-Box Testmethode
Bei der White-Box-Methode werden die Testfälle mit Kenntnis der internen Strukturen des Testobjekts (Source-Code) entwickelt, d.h. diese Methode kann nur von Entwicklern eingesetzt werden. Der laufende Test beim Programmieren ist ebenfalls ein White-Box- Test. Die meisten Entwicklungsumgebungen stellen dazu umfangreiche Tools zur Verfügung (DEBUG), es sind aber auch eigenständige Applikationen für White-Box- Tests auf dem Markt erhältlich.

Black-Box Testmethode
Bei dieser Methode werden die Testfälle aus der vorliegenden Spezifikation (z.B. Pflichtenheft) oder aber aus der Oberflächenstruktur (z.B. Erfassungsmasken, GUI) hergeleitet. Die innere Struktur des Objektes wird nicht berücksichtigt und kann unbekannt sein. Diese Testmethode kann also auch vom Anwender durchgeführt werden.


7.3 Testplan, Testdefinition

Ein Testplan besteht aus den Testanweisungen in Form der Testbeschreibung, dem Testprotokoll mit den Testfällen, welche in thematische Gruppen zusammengefasst sind und dem Testbericht oder Testbefund. (Sign-Off)

Testbeschreibung

Jeder Test sollte mit einer klar verständlichen Anleitung beschrieben werden. Dazu gehören folgende Punkte:

  • Ziel des Tests
  • Art des Tests
  • Verwendete Hilfsmittel
  • Anforderungen an das Testobjekt
  • Testvorgaben (Set-Up)
  • Abbruchkriterien (Check-Points)
  • Weiteres (z.B Tear-Down/Aufräumarbeiten nach dem Test)

Spezifikation von Testfällen

Die Wahl und Anzahl der Testfälle bestimmt nicht nur die Qualität und Wirksamkeit des Tests, sondern auch über die Dauer und Kosten desselben. Da diese Anforderungen gegenläufig sind, ist ein Mittelweg zu finden. Man versucht mit möglichst wenigen, dafür klug ausgewählten Testfällen möglichst viele Fehlermöglichkeiten auszuschliessen. In der Praxis werden funktional zusammenhängende Testfälle gruppiert und als Ganzes beschrieben.

Kriterien für die Auswahl der Testfälle

  • Funktionsüberdeckung: Jede spezifizierte Funktion wird mindestens von einem Testfall geprüft.
  • Eingabeüberdeckung: Jedes korrekte Eingabedatum wird mindestens von einem Testfall verwendet.
  • Extremwerteingaben: Jedes Eingabefeld wird mit mindestens einer Extremwerteingabe getestet.
  • Falscheingaben: Jedes Eingabefeld wird mit mindestens einer Falscheingabe getestet und eine Fehlermeldung erwartet.
  • Ausgabedatum: Jedes Ausgabedatum wird von mindestens einem Testfall erzeugt.

Zusätzlich bei White-Box-Tests:

  • Anweisungs- und Zweigüberdeckung: Die Testfälle werden so ausgelegt, dass alle Zweige und Anweisungen mindestens einmal ausgeführt werden.
  • Strukturierte Pfadüberdeckung: Die Testfälle werden so gewählt, dass alle möglichen Pfade und jede Schleife mehrmals durchlaufen werden.
  • Bedingungsüberdeckung: Alle Bedingungen des Testobjektes werden mindestens einmal mit "True" und einmal mit "False" durchlaufen.

Abschliessender Testbericht bzw. Testbefund (Sign Off): 
Nach der Durchführung eines Tests sollten die Testresultate dokumentiert werden. Der Bericht umfasst folgende Punkte:

  • Einzel protokollierte Testfallergebnisse mit Status (OK / NOK / Ausnahme)
  • Eventuell Review des Tests. Dient zur Verbesserung des Testplanes.
  • Eventuell Mängelliste mit mögliche Fehlerquelle
  • Schlussstatus (Vollständig akzeptiert / Nicht akzeptiert / Bedingt akzeptiert) mit Vorgehensempfehlung.
  • Datum und Unterschriften der ausführenden Organe.
Hier das Template eines Testprotokolls herunterladen.


Zusammenfassung: SW-Testen

Was ist wichtig und worauf sollten wir in M404 mit Powershell den Fokus setzen:

  • In diesem Modul beschränken wir uns auf SW-Abnahmetests in der Form von Blackboxtests.
  • Die zu testende Lieferung soll über eine Produktbezeichnung und Versionsnummer verfügen und damit eindeutig identifizierbar sein. (Bsp. SUPERCALC_V2.3)
  • Es wird formell getestet. Dies bedingt die schriftliche Form.
  • Sämtliche Test müssen reproduzierbar bzw. nachvollziehbar sein und Darum muss mit konkreten Eingabewerten geprüft werden.
  • Nach Testende muss in einem Schlusssatz bzw. Testbericht mitgeteilt werden, ob die Lieferung akzeptiert, mit Einschränkungen akzeptiert oder nicht akzeptiert wird.

Wenn die Lieferung nicht akzeptiert wird:

  • Die Lieferung wird sie zwecks Fehlerbehebung an den Autor zurückgesandt.
  • Der Lieferant/Autor (SW-Entwickler) kann anhand des Testprotokolls seine Fehler korrigieren
  • Nach der Korrektur wird die korrigierte Version der Lieferung erneut vollständig getestet. Es wird ein weiteres Testprotokoll für die neue Version (z.B. CALC_V2.4) erstellt.

Beispiel: Testen einer Taschenrechner-SW

Pflichtenheft:

  • Robuste Eingabe der Zahl1 (-1'000'000 bis + 1'000'000)
  • Robuste Eingabe der Zahl2 (-1'000'000 bis + 1'000'000)
  • Mathematische Operationen: +, -, *, :
  • Definierter Ausgabewertebereich (Resultat) (-1'000'000'000 bis + 1'000'000'000)
  • Korrektes Resultat auf eine Nachkommastelle gerundet

Eine Auswahl von Testfällen

TESTPROTOKOLL
*************
Lieferung     : SUPERCALC
Version       : 2.3
Testart       : SW-Abnahmetest/Blackboxtest
SW-Entwickler : Felix Muster
Tester        : Hans Nötig
Datum         : dd.mm.yyy

-----------------------------------------------------------------
Falsch wäre ein Testfall wie der folgende: (Weil nicht reproduzierbar)

TESTFALL #001 (Testgruppe Eingabeüberdeckungen)
-------------
Eingabe Zahl1: Eine positive Zahl
Eingabe Zahl2: Eine positive Zahl
Operation: +
Erwartete Ausgabe: Ausgabe erfolgt ohne Fehler
Tatsächliche Ausgabe:
OK/NOK:
-----------------------------------------------------------------

Richtig hingegen sind Testfälle wie die folgenden: (Weil reproduzierbar)

TESTFALL #001 (Testgruppe Eingabeüberdeckungen)
-------------
Eingabe Zahl1: 150
Eingabe Zahl2: 230
Operation: +
Erwartete Ausgabe: 380
Tatsächliche Ausgabe: 380
OK/NOK: OK

TESTFALL #002 (Wegen dem vorangegangenen Testfall #001 ist dieser unnötig, weil kein 100%-Testen realistisch ist!)
-------------
Eingabe Zahl1: 151 (Dieser Eingabewert ist dem in Testfall #001 zu ähnlich!)
Eingabe Zahl2: 231 (Dieser Eingabewert ist dem in Testfall #001 zu ähnlich!)
Operation: +
Erwartete Ausgabe: 382
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #003 (Testgruppe Eingabeüberdeckungen)
-------------
Eingabe Zahl1: -150
Eingabe Zahl2: -200
Operation: +
Erwartete Ausgabe: -350
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #017 (Testgruppe Ausgabedaten)
-------------
Eingabe Zahl1: 2
Eingabe Zahl2: 3
Operation: :
Erwartete Ausgabe: 0.7
Tatsächliche Ausgabe: 0.66666666666666666
OK/NOK: NOK

TESTFALL #057 (Testgruppe Extremwerteingaben)
-------------
Eingabe Zahl1: 1'000'001
Eingabe Zahl2: 1'000'001
Operation: +
Erwartete Ausgabe: Meldung: Eingabewerte zu gross!
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #058 (Testgruppe Extremwerteingaben)
-------------
Eingabe Zahl1: 120
Eingabe Zahl2: 0.000001
Operation: *
Erwartete Ausgabe: 0.0
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #059 (Testgruppe Extremwerteingaben)
-------------
Eingabe Zahl1: 1'000'000
Eingabe Zahl2: 0.0001
Operation: :
Erwartete Ausgabe: Meldung: Resultat ausserhalb Wertebereich!
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #067 (Testgruppe Falscheingaben)
-------------
Eingabe Zahl1: vierzehn
Eingabe Zahl2: elf
Operation: +
Erwartete Ausgabe: Meldung: Eingabewerte sind keine Zahlen!
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #125 (Testgruppe Falscheingaben)
-------------
Eingabe Zahl1: 150
Eingabe Zahl2: 0
Operation: :
Erwartete Ausgabe: Meldung: Division durch 0 ist verboten!
Tatsächliche Ausgabe:
OK/NOK:

Testbericht:
------------
Lieferung akzeptiert:                     [JA/NEIN]
Lieferung mit Einschränkungen akzeptiert: [JA/NEIN]
Lieferung nicht akzeptiert:               [JA/NEIN]
Datum und Unterschrift des Testers:       .........

Teil-C: «GRUNDLAGEN»

8. Erste Schritte in C

8.1 Die wichtigsten Datentypen

Weitere Datentypen entnehme man der entsprechenden C-Literatur.

8.2 Die Ausgabe mit printf

#include <stdio.h>

void main(void)
{
  printf("Ich bin ein C-Programm \n");
  printf("**********************");
}

Steuerzeichen für die Ausgabe mit printf
\n    NL (new line) Der Cursor geht zum Anfang der nächsten Zeile.
\t    HT (horizontal tab) Der Cursor geht zur nächsten horizontalen Tabulatorposition.
\"    Das " Zeichen wird ausgegeben.
\'    Das ' Zeichen wird ausgegeben.
\?    Das ? Zeichen wird ausgegeben.
\\    Das \ Zeichen wird ausgegeben.

Die Ausgabe von Variablen mit printf:

Im printf keine Umlaute wie z.B. ä,ö,ü

#include <stdio.h>

void main(void)
{
  int x=10;
  float wert=0.0;
  char ch='A';

  printf("Der Integerwert lautet %i \n",x);
  printf("Der Floatwert lautet %f \n",wert);
  printf("Das Charakter ist ein %c \n",ch);
}

8.3 Inline-Kommentare

void main(void)
{
  /* Dies ist Kommentar und wird vom Compiler ignoriert */
  // Dies ist ebenfalls ein Kommentar,
  // aber nicht mehr im Standard C
  printf("Hello World \n");
}

8.4 Eingabe mit scanf

(Achtung : das &-Zeichen vor der Variable nicht vergessen !)

#include <stdio.h>

void main(void)
{
  /* Deklaration und Initialisierung der Variablen */
  int x=0;
  char ch=' ';

  printf("Bitte eine Zahl eingeben : ");
  scanf("%i",&x);

  printf("Bitte ein Buchstabe eingeben : ");
  fflush(stdin);    /* Leeren des Tastatureingabespeichers */
  scanf("%c",&ch);

  printf("Sie tippten die Zahl %i und den Buchstaben %c ein.\n",x,ch);
}

Beachten Sie das Eingabeformat bei Dezimalzahleingaben (float):
zB.  Zahl : 12.0

8.5 Die Grundrechenarten + , - , * , / , % , ()

#include <stdio.h>

void main(void)
{
  int x=0;
  int y=0;

  x=x+1;
  y=x*3-(2+1);
  y=y%7;

  printf("Resultat : y=%i",y);
}
  • Der Audruck (Term) rechts vom Gleichheitszeichen wird berechnet und das Resultat der Variablen links vom Gleichheitszeichen zugewiesen.
    So kann eine bestehende Variable mit neuem Inhalt befüllt werden wie z.B. bei x=x+1;
  • Der Modulo-Operator % (=Restwertdivision) ist in der Informatik eine wichtige Funktion und nur für ganze Zahlen. Z.B. gibt 12 % 5 das Resultat 2 weil 12 dividiert durch 5 den Rest 2 ergibt.

8.6 Inkrement und Dekrement-Operatoren

Bei den Postoperatoren wird die Variable erst "benutzt" und dann entsprechend inkrementiert oder dekrementiert. Die Preoperatoren inkrementieren bzw. dekrementieren vor der Benutzung.


9. Kontrollstrukturen

9.1 Die Vergleichsoperatoren

9.2 Die Selektion if-else und switch-case

Die Selektion if-else

#include <stdio.h>

void main(void)
{
  int zahl=100;

  if(zahl<100)
  {
    printf("Die Zahl ist kleiner 100");
  }
  else
  {
    printf("Die Zahl ist groesser-gleich 100");
  }
}

Den else-Teil kann man weglassen, wenn kein else-Fall vorgesehen ist.
Die geschweiften Klammern BEGIN/END kan man weglassen, wenn
nur ein Statemant (Sequenz) vorgesehen ist.

Die Mehrfachselektion switch-case

#include <stdio.h>   //Beispiel 1

void main(void)
{
  int zahl=0;

  // Eingeben einer Zahl
  printf("Zahl eingeben : ");
  scanf("%i",&zahl);

  //Die Mehrfachselektion  switch-case
  switch(zahl)
  {
  case 1  : printf("Zahl ist 1\n");
            printf("**********");
  break;
 
  case 3  : printf("Zahl ist 3\n");
            printf("**********");
  break;
 
  case 7  : printf("Zahl ist 7\n");
            printf("**********");
  break;
 
  default : printf("Zahl weder 1,3 oder 7\n");
  break;
  }
}
#include <stdio.h>   //Beispiel 2

void main(void)
{
  char ch='B';

  switch(ch)
  {
  case 'A' : printf("Buchstabe ist A");
  break;
 
  case 'B' : printf("Buchstabe ist B");
  break;
  }
}

Bemerkungen:

  • Nach jedem "Fall" sollte eine break-Anweisung stehen. Sonst würden auch die weiteren bzw. nächsten Anweisungen ausgeführt. Dies entspricht eigentlich nicht dem Konzept der strukturierten Progammierung und ist eigentlich eine Strukturverletzung. Duch Weglassen der break-Anweisung kann aber bewusst ein zT. gewünschter Effekt erzielt werden.
  • Die default-Anweisung entspricht dem else-Fall beim if-Konstrukt. Falls kein else oder default-Fall vorgesehen ist, kann die default-Anweisung auch weggelassen werden.
  • Switch-case funktioniert nur mit Aufzählungstypen (zB. int, char). Im case kann nicht ein Wertebereich angegeben werden.

9.3 Der Umkehr-Operator (NOT) !

#include <stdio.h>

void main(void)
{
  int zahl=100;
  if(!(wert==100))
    printf("Der Wert ist ungleich 100.\n");
}

9.4 Verknüpfungsoperatoren

Und-Verknüpfung = &&
Oder-Verknüpfung = ||

 if( (a>0) && (a<10) )
   { printf("a liegt zwischen 0 und 10"); }

9.5 BOOLEAN

In einigen Programmiersprachen gibt es den Datentyp Boolean. Nicht so in der Programmiersprache "C". Hier gilt folgendes :

  • Wert gleich 0 bedeutet FALSE (Falsch)
  • Wert  ungleich 0 bedeutet TRUE (Wahr)
#include <stdio.h>   //Beispiel 1

void main(void)
{
  int weitermachen=1;  //0=FALSE, 1=TRUE
  int i=0;
  char ch;

  while(weitermachen)
  {
    i=i+1;

    printf("Moechten Sie weitermachen (j/n) : ");
    fflush(stdin);
    scanf("%c",&ch);

    if(ch=='n')
      weitermachen=0;
  }
}

9.6 Die kopfgesteuerte Iteration

#include <stdio.h>

void main(void)
{
  int i=0;

  while(i<10)
  {
    printf("Die Zahl i hat den Wert %i",i);
    i=i+1;
  }
}

9.7 Die fussgesteuerte Iteration

#include <stdio.h>

void main(void)
{
  int i=0;

  do
  {
    printf("Die Zahl i hat den Wert %i",i);
    i=i+1;
  } while(i<10);
}

9.8 Die for-Schleife (Kopfgesteuerte Iteration)

#include <stdio.h>

void main(void)
{
  int i=0;

  for(i=0; i<10; i++)
  {
    printf("Die Zahl i hat den Wert %i",i);
  }
}

Allgemein

for(Anweisung1 ; Bedingung ; Anweisung2)
 {
   /* Anweisungsblock */
 }

10. Funktionen

(Wichtig : Die Funktionsdeklaration muss vor dem Funktionsaufruf stehen, sonst werden Prototypen erforderlich)

Einfache Funktion ohne Parameter:

#include <stdio.h>

/***Funktionsdeklaration***/
void begruessung(void)
{
  printf("Guten Tag !\n");
}

void main(void)
{
  begruessung(); //Funktionsaufruf
  begruessung(); //Funktionsaufruf
}

Funktion mit Eingabeparameter:

(Es können auch mehrere Eingabeparameter sein !)

#include <stdio.h>

/***Funktionsdeklaration***/
void verdoppelt(int a)
{
  a=a*a;
  printf("Verdoppelt : %i \n",a);
}

void main(void)
{
  int zahl=3;
  verdoppelt(zahl);  //Funktionsaufruf
  printf("zahl = %i \n",zahl);  //zahl ist immer noch 3
}

Funktion mit mehreren Eingabeparameter und Funktionsrückgabewert:

#include <stdio.h>

/***Funktionsdeklaration***/
int sum(int a, int b)
{
  int res=0;
  res=a+b;
  return(res);
}

void main(void)
{
  int zahl1=3;
  int zahl2=5;
  int summe=0;
  summe = sum(zahl1, zahl2);
  printf("Summe = %i \n",summe);
}

Bemerkung : Die Funktion kann ein Wert an das aufrufende Programm zurückgeben. Der Typ des Rückgabewertes ist aus der Funktionsdeklaration ersichtlich :

int sum(int a, int b)

Wird nichts zurückgegeben steht void  (void=Leer)


11. Vertiefung zu Variablen

11.1 Feldvariable: Array von Integer

#include <stdio.h>

void main(void)
{
  int f[10];

  f[0]=5;
  f[1]=12;
  //und so weiter ...

  printf("1. Zahl im Array : %i",f[0]);

  if(f[1]==12)
    printf("Treffer");

}

Achtung:

Es wird in diesem Beispiel ein Feld f mit 10 Elementen vom Typ integer deklariert. Das erste Element ist das "Nullte" f[0].

11.2 Feldvariablen-Spezialfall: Array von Charactern (Textstring)

Auf den Variablentyp chgar wird später eingehender eingegangen.

#include <stdio.h>

void main(void)
{
  char str1[10];
  char str2[10];

  // Eingabe mit scanf
  printf("Geben Sie Ihren Text ein : ");
  scanf("%s",str1);
  //Beachte : Kein & vor der Variable str
  //          Platzhalter %s für String !
  fflush(stdin);
  //Besser für das Einlesen von Strings ist :
  printf("Geben Sie Ihren Text ein : ");
  gets(str2);

  //Ausgabe des Strings

  //Variante 1
  printf("String : %s\n",str1);

  //Variante 2
  puts(str2);
}

Achtung:

Mit scanf wird nur bis zum ersten Leerzeichen eingelesen. Dass heisst, dass man hier nur "Hallo" gespeichert hätte.

Geben Sie Ihren Text ein : Hallo meine Leute

Als Endkennung wird nach Einlesen eines Strings mit scanf oder gets ein ASCII-Zeichen NULL  '\0' eingefügt. Dank diesem Zeichen kann immer eindeutig das String-Ende erkannt werden.

11.3 Warum deklarierte Variablen initialisiert werden müssen

Fall-1:

Gegeben sei das folgende Programm:

#include <stdio.h>

void main(void)
{
  int zahl;
  printf("Zahl = %i", zahl);
}

Prüfen sie, welche Zahl mit printf ausgegeben wird!

Fall-2:

Gegeben sei das folgende Programm:

#include <stdio.h>

void main(void)
{
  int produkt;
  int i;
  i=0;

  while(i<10)
  {
    produkt=produkt*i;
    i=i+1;
  }
  printf("Produkt = %i", produkt);
}

Prüfen sie, welches "produkt" mit printf ausgegeben wird!

Schlussfolgerung:

Aus diesem Grund müssen Variablen sowohl deklariert als auch initialisiert (mit einem Wert versehen) werden.

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Schreiben sie ein Programm, in welchem zwei Werte eingelesen werden und der grössere Wert danach am Bildschirm angezeigt wird. Erstellen sie vorher ein Struktogramm.
  • Schreiben sie ein Programm, das nach einer Zahl fragt und danach mitteilt, ob die Zahl gerade oder ungerade ist.
  • Schreiben sie ein Programm, welches nach zwei Zahlen fragt (auch negative Werte sollen erlaubt sein) und dann die Summe der zwei Zahlen ausgibt. Nachdem die Summe ausgegeben wurde, soll nach einer dritten Zahl gefragt werden, mit der die Summe dann multipliziert wird. Dieses Ergebnis soll ebenfalls ausgegeben werden.
  • Schreiben sie ein kleines Taschenrechnerprogramm das zwei Zahlen zusammenzählen, abzählen, multiplizieren oder dividieren kann. Zuerst sollen die beiden Zahlen eingegeben werden. Danach soll durch die Eingabe des Charakter '+' zusammengezählt, '-' abgezählt, '*' multipliziert oder '/' dividiert werden. Das Resultat am Bildschirm ausgeben. Welche Datentypen wählen sie? Hier der verlangte Bildschirminhalt:
>*** TBZ-Rechner V1.0 ***
>************************
>
>Bitte erste Zahl eingeben  : 12 <ENTER>
>Bitte zweite Zahl eingeben : 56 <ENTER>
>
>Bitte Operationszeichen eingeben (+ oder – oder * oder /) : + <ENTER>
>
>Ihre Rechnung 12 + 56 = 68
>
>Weitere Berechnungen ? (j=ja / n=nein) : n <ENTER>
>Auf Wiedersehen ...
>

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Schreiben sie ein Programm, das die Potenz berechnet. Das Programm soll zuerst nach der Basis und nachher nach dem Exponent fragen. Anschliessend soll das Resultat am Bildschirm ausgegeben werden.
  • Schreiben sie ein Programm, das die Fakultät einer von einer von Ihnen eingegeben Zahl berechnet.
    Beispiele zu Fakultät :
    Fakultät von 1 ist 1
    Fakultät von 2 ist 2, weil 2*1=2
    Fakultät von 3 ist 6, weil 3*2*1=6
    Fakultät von 4 ist 24, weil 4*3*2*1=24
    usw.

11.4 Typecast

Mit dem Typecast  (float)(zahl1) wird der Variablentyp von zahl1 in einen float gewandelt.

#include <stdio.h>

void main()
{
  int int_res;
  int zahl1=4;
  int zahl2=3;
  float float_res;

  int_res = zahl1/zahl2;
  printf("Resultat: %i\n",int_res);
  float_res = (float)(zahl1/zahl2);
  printf("Resultat float: %f\n",float_res);
  float_res = (float)(zahl1)/(float)(zahl2);
  printf("Resultat float: %f\n",float_res);
  float_res = zahl1/3.0;
  printf("Resultat float: %f\n",float_res);
}

 

11.5 Prototypen

Die Kompilierung dieses Codebeispiels ergibt ein Fehler (Warning). Die Funktion berechnung() wird vor ihrer Deklaration aufgerufen.

#include <stdio.h>

void main()
{
  int zahl, zahl1, res;
  printf("1. Zahl :");
  scanf("%i",&zahl);
  printf("2. Zahl :");
  scanf("%i",&zahl1);

  res = berechnung(zahl, zahl1);
  printf("Resultat: %i\n",res);
}

int berechnung(int op1, int op2)
{
  int resultat;

  resultat = op1 % op2;
  return(resultat);
}

Abhilfe schafft hier ein Prototyp von der Funktion berechnung() vor dem Aufruf bzw. vor dem main():

#include <stdio.h>
 int berechnung();  //Prototyp von berechnung()

Achtung : Strichpunkt nicht vergessen.

 


12. Character/Zeichen

12.1 Der Variablentyp char

Eine Variable vom Typ "char" (= Character) ermöglicht es , einzelne ASCII-Zeichen zu speichern.

char ch;
printf("Zeichen eingeben : ");
scanf("%c", &ch);
printf("Das Zeichen war %c",ch);

Formatzeichen für Charackter : %c
Der Bereich einer Char-Variablen geht von 0..255 (= 8 Bit oder 1 Byte).

Zuweisung eines Zeichens in eine char-Variable:

char ch;
ch = 'B';

Zeichenkonstanten stehen immer zwischen einfachen Anführungsstrichen.  Bsp. char ch='A';
Stringkonstanten stehen immer innerhalb doppelter Anführungstrichen. Bsp. printf("Hallo");

Der Compiler schreibt in die Variable nicht das 'B' selbst, sondern der Wert, den der Buchstabe 'B' in der ASCII-Tabelle repräsentiert.

12.2 Die ASCII-Tabelle

(ASCII-Tabelle auszugsweise)

Als Standsrd-ASCII-Code sind nur die 128 Zeichen des 7 Bit-Codes festgelegt.
Daher keine Umlaute wie z.B.  ä,ö und ü.

12.3 Das Doppelleben von char

Eigentlich ist die char-Variable fast identisch mit dem Variablentyp unsigned short. Deshalb kann man sie auch wie ganzzahlige Variablen behandeln.

char ch = 'A';
printf("Der Wert von %c ist %i",ch,ch);

Ein Grossbuchstaben kann so einfach in einen Kleinbuchstaben umgewandelt werden :

char ch = 'A';
ch = ch + 32;
printf("Buchstabe : %c",ch);

12.4 Einige nützliche Funktionen von ctype.h

(Genaue Prototypen siehe C-Literatur)

isalnum() // Wahr für einen Buchstaben oder eine Zahl
isalpha() // Wahr für einen Buchstaben
isdigit() // Wahr für eine dezimale Ziffer
islower() // Wahr für Kleinbuchstaben
isupper() // Wahr für Grossbuchstaben
tolower() // Umwandlung in alles Kleinbuchstaben
toupper() // Umwandlung in alles Grossbuchstaben

13. Array/Feldvariable

13.1 Variablenfelder (Array)

int x[3];

x[0] = 15;
x[1] = 45;
x[2] = 62;

//x[3] = 34; -> Unzulässig, da nur 3 Elemente deklariert sind.

Der Name eines Feldes ohne eckige Klammern steht für die Startadresse des Feldes. Der Feldname ist ein Vektor.

13.2 Felder als Funktionsparameter

Die Programmiersprache "C" kennt als Funktionsparameter nur elementare Datentypen wie Adressen char, int, float, short etc. Somit müssen wir bei der Parameterübergabe eines ganzen Feldes dessen Adresse übergeben :

#include <stdio.h>

/******************EINGABE********************/
void datenerfassung(int *a)
{
  int zaehler=0;
  while (zaehler<10)
  {
    printf("Ihre Zahl : ");
    scanf("%i", &a[zaehler]);
    zaehler++;
  }
}

/******************VERARBEITUNG***************/
void datenberechnung(int *b)
{
  int zaehler=0;
  while (zaehler<10)
  {
    b[zaehler])= b[zaehler]+10;
    zaehler++;
  }
}

/******************AUSGABE********************/
void datenausgabe(int *c)
{
  int zaehler=0;
  while (zaehler<10)
  {
    printf("Ergebniss : %i",c[zaehler]);
    zaehler++;
  }
}

/******************HAUPTPROGRAMM**************/
void main(void)
{
  int x[10];

  datenerfassung(x);
  datenberechnung(x);
  datenausgabe(x);
}

Der Zeiger auf eine Variable ist identisch mit dem Zeiger auf ein Feld.
Bei Zeigern auf Felder darf bei der Benutzung des Index kein Dereferenzierungsoperator angewendet werden.

13.3 Parameterübergabe bei Feldvariablen

#include <stdio.h>

// Das Array arr wird als Zeiger uebergeben ...
void array1_calc(int *arr)
{
  arr[3] = 144;
  printf("Neuer Wert : ");
  scanf("%i",&arr[4]);
}

// Das Array kann auch alternativ so uebergeben werden ...
void array2_calc(int arr[])
{
  arr[3] = 99;
}

// Demzufolge gilt auch für Strings ...
void str_calc(char *str)
{
  printf("Neuer String : ");
  fflush(stdin);
  gets(str);
}

void main(void)
{
  int arr1[5];
  int arr2[5];
  char str1[10];

  array1_calc(arr1);
  printf("Wert arr1[3] = %i",arr1[3]);
  printf("\n");
  printf("Wert arr1[4] = %i",arr1[4]);
  printf("\n");

  array2_calc(arr2);
  printf("Wert arr2[4] = %i",arr2[3]);
  printf("\n");

  str_calc(str1);
  printf("String = %s",str1);
}

14. Strings/Text

Ein besonderes Variablenfeld : Die Zeichenkette  (engl. array of charakter)
Formatzeichen für Charakter : %s
Der String wird nicht andes als ein Feld definiert.

Beispiel 1:

#include <stdio.h>

void main(void)
{
  char str[8];
  str[0] = 'H';
  str[1] = 'a';
  str[2] = 'l';
  str[3] = 'l';
  str[4] = 'o';
  str[5] = '\0';
  printf("Der String lautet %s",str);
}

Beispiel 2:

#include <stdio.h>

void main(void)
{
  char str[6];
  printf("Texteingabe : ");
  scanf("%s",str);

  printf("Text = %s",str);
}

Der Adressoperator vor der Variable str in scanf fehlt nicht wirklich, denn : Normalerweise würden wir dort zwar ein Adressoperator & benutzen, um scanf die Adresse der Variablen zur Veränderung des Wertes zu übergeben. Doch bei den Feldvariablen haben wir gesehen, das der Name eines Feldes ohne die eckigen Klammern die Adresse des Feldes repräsentiert.

Was auf nicht funktionieren wird:

char str[6];
str = "Hallo";  //Geht nicht !!

str steht für die Adresse und kann nicht einer Stringkonstanten zugewiesen werden.

Bessere Wahl für Ein- und Ausgabe von Strings :

gets(str);
puts(str);

14.1 Textübergabe "Call by reference"

#include <stdio.h>

void array_calc(int * arr )
{
  arr[3] = 144;
}

void main(void)
{
  int x[5];
  array_calc(x);

 printf("Wert =  %i",x[3]);
}

#include <stdio.h>

void array_calc(int arr[] )
{
  arr[3] = 144;
}

void main(void)
{
  int x[5];
  array_calc(x);

  printf("Wert =  %i",x[3]);
}
#include <stdio.h>

void array_calc(int * arr)
{
  scanf("%i", &arr[3] );
}

void main(void)
{
  int x[5];
  array_calc(x);

  printf("Wert =  %i",x[3]);
}
#include <stdio.h>

void str_calc(char * str)
{
  gets(str);
}

void main(void)
{
  int text[5];
  str_calc(text);

  printf("Text =  %s",text);
}

14.2 Einige nützliche Funktionen von string.h

(Genaue Prototypen siehe C-Literatur)

strcat() // String an einen anderen hängen
strchr() // Zeichen im String suchen
strcmp() // Zwei Strings vergleichen
strcpy() // String kopieren
strlen() // Länge des Strings ermitteln

 

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Erstellen sie ein Programm, bei dem Sie mit scanf einen Text einlesen und den Text in nur Grossbuchstaben ausgeben. Falls Ihnen bei der Texteingabe mit scanf Nachteile auffallen, benutzen Sie Alternativen.
    Erstellen Sie zuerst das Struktogramm und programmieren dann den Umwandlungsalgorithmus als Funktion. Kommentieren Sie ausreichend (Dateiheader etc.) Sie dürfen dazu die string.h bzw ctype.h nicht benutzen.
  • Erstellen sie ein Programm, bei dem sie einen Text einlesen und prüfen, ob das Wort “Hallo” eingegeben wurde. (Gross/Kleinschreibung beachten)
    Erstellen sie zuerst das Struktogramm und programmieren sie den Prüfalgorithmus als Funktion. Kommentieren sie ausreichend (Dateiheader etc.) Testen sie Ihr Programm. Sie dürfen dazu die string.h bzw ctype.h nicht benutzen.
  • Aufgabe "Textsearch": Diese Aufgabe sollte genau nach Pflichtenheft und nach allen Regeln der Programmierkunst (dh. Analyse/Design Implementation Test und Doku) ausgeführt werden. Zeitrahmen ca. 2 ½ Lektionen. Der Einsatz von <string.h> und <ctype.h> ist nicht erlaubt.
    Pflichtenheft:
    Im folgenden sehen sie zwei "Screenshots" vom User-Interface des Programms. Sie müssen ihr Programm so erstellen, dass dieselbe Funktionalität und Benutzerführung erreicht wird. Das Programm soll den Namen "textsearch.exe" erhalten. Es sind keine Abweichungen von der Vorlage zulässig.
  • Bsp. 1: cmd

Screenshots zur Aufgabe "Textsearch"

c:\>
c:\>***********************
c:\>*** TEXTSEARCH V1.0 ***
c:\>***********************
c:\>
c:\>Texteingabe (max. 30 Zeichen) : Willy, wie und wieso ?<ENTER>
c:\>Suchstring eingeben (max. 6 Zeichen) : wie<ENTER>
c:\>
c:\>Der Satz "Willy, wie und wieso ?" enthaelt 2 "wie"
c:\>

Achten sie auf die Gross/Kleinschreibung : In unserem Beispiel oben wäre "wie" nicht gleich "Wie"

c:\>
c:\>***********************
c:\>*** TEXTSEARCH V1.0 ***
c:\>***********************
c:\>
c:\>Texteingabe (max. 30 Zeichen) : Max3 arbeitet bei MAXON<ENTER>
c:\>Suchstring eingeben (max. 6 Zeichen) : ei<ENTER>
c:\>
c:\>Der Satz "Max3 arbeitet bei MAXON" enthaelt 2 "ei"
c:\>

Sie dürfen davon ausgehen, dass keine Umlaute (ä,ö,ü) eingegeben werden. Der Benutzer sitzt vor einer amerikanischen Tastatur.


15. Zeiger/Pointer

15.1 Vektoren und Zeiger

Vektoren: Konstante Adresse (zB.: int x=5;   &x => Vektor)
Zeiger: Variable Adresse (zB.: int x=5; int * px => Zeiger)

Eine Variable ist eindeutig definiert durch vier Angaben :

  • Name
  • Position (Adresse)
  • Grösse (Benötigter Speicherplatz)
  • Inhalt

Der benötigte Speicherplatz wird vom gewählten Variablentyp festgelegt ! (zB. int benötigt 32 Bit)

15.2 Der Adressoperator &

Liefert die Adresse einer Variablen.
Formatzeichen für Adressen (Pointer) : %p

(Der Adressoperator wird zB. beim scanf verwendet :  scanf("%i",&x); )

int x = 12;
printf("Adresse von x(%i) ist %p", x, &x);

Die Adresse einer Variablen wird bei ihrer Erzeugung bestimmt und bleibt für ihre Lebensdauer konstant.

15.3 Der Dereferenzierungsoperator *

Fall 1

int x = 10;
int *px;
px = &x;

Kreieren einer Zeigervariablen die auf eine Variable des Typs Integer zeigt.

Fall 2

int x = 10;
int *px;
px = &x;
*px = 23;

Eigentliche Dereferenzierung : Dort wo px hinzeigt, soll der Wert 23 gespeichert werden. Also wird tatsächlich der Inhalt der Variablen x von 10 auf 23 geändert.

Beispiele zu Adressen

#include <stdio.h>
void main(void)
{
  int x = 0;
  int * px;
  px = &x;
  *px = 12;
  *px = *px + 10;
  *px = (*px) * (*px);
}

Adresse: 0x0000F2A1
Variablenname: x
Inhalt: 848

Adresse: 0x0000E731
Variablenname: px
Inhalt: 0x0000F2A1

15.4 Adressen als Funktionsparameter

Hauptsächlich werden Zeiger benutzt, um Adressen an Funktionen zu übergeben. Diese können über diesen Umweg dann die Variablen ausserhalb verändern. (Call by value)

#include <stdio.h>

void cquadrat(int * wert)
{
  *wert = (*wert) * (*wert);
}

void main(void)
{
  int x=12;
  int *px=&x;
  cquadrat( px );
  printf("Das Quadrat ist %i",x);
}

15.5 Zeiger auf Zeiger

Bis jetzt haben wir Zeiger kennengelernt, die auf Variablen zeigten. Nun kann es auch Zeiger geben, die auf Zeiger zeigen. Zum Beispiel bei der Uebergabe eines Stringfeldes als als Funktionsparameter :

char ** p_str;

Der Zeiger p_str zeigt auf einen Zeiger, der auf eine Variable des Typs char zeigt.


16. Zeigeranwendungen

16.1 Sortieralgorithmus

Häufig stellt sich die Aufgabe, das Einträge numerisch oder alphanumerisch geordnet werden müssen. (zB. Namenseinträge im Telefonbuch). Im folgenden wird ein einfacher Sortieralgorithmus gezeigt : der Bubble-Sort. Dieser Sortieralgorithmus ist für numerische wie auch alphanumerische Sortieraufgabe geeignet. Dies darum, weil sich die Problematik in den beiden Fällen nicht unterscheidet : 5>2 aber auch 'D' > 'B'. (siehe ASCII-Tabelle)
Sollen nun ganze Sätze alphabetisch geordnet werden, müssen mehrere Charakter miteinander verglichen werden. ZB. liegt "Muheim" vor "Mutzer".

16.2 Der Bubble Sort

Ausgangslage:

1. Durchgang:

2. Durchgang:

3. Durchgang:

Ist nicht mehr nötig, weil die Sortierung bereits korrekt ist.

(Der Begriff "Bubble-Sort" kommt von (engl.) Bubbles=Blasen. Die Grossen Werte wandern von links nach rechts, die kleinen Werte von rechts nach links, ähnlich den Luftblasen im Wasser die zur Wasseroberfläche steigen)

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Programmieren Sie den Bubble-Sort als Funktion.
    Als Uebergabeparameter erhalten Sie ein Array of Integer (Call by reference) int arr[10];
    In der Funktion wird die Reihenfolge des Arrays korrigiert. Das heisst : An der ersten Stelle im Array steht der kleinste Integer-Wert. An letzter Stelle der grösste.
  • Lesen Sie 10 Strings ein und sortieren Sie diese in alphabetischer Reihenfolge. Danach geben Sie die 10 sortierten Strings mit printf() aus.

17. Strukturen in C

Zusammengehörende Daten können in struct's zusammengefasst werden. Aehnlich den Datensätzen in Datenbanken wie z.B.:
Tabelle "Personen" enthält Attribut : Name, Vorname, Adresse etc.

Beispiel 1:

#include <stdio.h>
#include <string.h>

/***Definition der Struktur***/
struct Person
{
  char nachname[40];
  char vorname[40];
  char strasse[40];
  unsigned short strnummer;
  unsigned short plz;
  char ort[40];
};

/**************AUSGABE************************/
void ausgabe (struct Person the_person)
{
  printf("Name: %s %s\n", the_person.nachname, the_person.vorname);
  printf("Strasse: %s %hu\n", the_person.strasse, the_person.strnummer);
  printf("Ort: %hu %s\n", the_person.plz, the_person.ort);
}

/**************MAIN*************************/
void main()
{
  struct Person my_Person;

 //Die Variable könnte so auch direkt initialisiert werden :
 //struct Person my_Person = {"Meier","Fritz","Cool",1,2222,"Seen"};

 strcpy(my_Person.nachname, "Meier");
 strcpy(my_Person.vorname, "Urs");
 strcpy(my_Person.strasse, "Bahnhofstr.");
 my_Person.strnummer = 65;
 my_Person.plz = 8000;
 strcpy(my_Person.ort, "Zuerich");

 ausgabe(my_person);

 printf("Strasse : ");
 fflush(stdin);
 gets(my_Person.strasse);

 printf("Strassennummer : ");
 fflush(stdin);
 scanf("%i", &my_Person.strnummer);

 ausgabe(my_person);
}

Beispiel 2 (Mit Array):

#include <stdio.h>
#include <string.h>

/***Definition der Struktur***/
struct Person
{
  char nachname[40];
  char vorname[40];
  char strasse[40];
  unsigned short strnummer;
  unsigned short plz;
  char ort[40];
};

/**************AUSGABE************************/
void ausgabe (struct Person the_person)
{
  printf("Name: %s %s\n", the_person.nachname, the_person.vorname);
  printf("Strasse: %s %hu\n", the_person.strasse, the_person.strnummer);
  printf("Ort: %hu %s\n", the_person.plz, the_person.ort);
}

/*************MAIN***************************/
void main()
{
  struct Person     personen[2];

  strcpy(personen[0].nachname, "Huber");
  strcpy(personen[0].vorname, "Karin");
  strcpy(personen[0].strasse, "Baeckerstr.");
  personen[0].strnummer = 34;
  personen[0].plz = 8000;
  strcpy(personen[0].ort, "Zuerich");

  printf("Nachname : ");
  fflush(stdin);
  gets(personen[1].nachname);

  printf("Vorname : ");
  fflush(stdin);
  gets(personen[1].vorname);

  printf("Strasse : ");
  fflush(stdin);
  gets(personen[1].strasse);

  printf("Strassen-Nummer : ");
  fflush(stdin);
  scanf("%i", &personen[1].strnummer);

  printf("PLZ : ");
  fflush(stdin);
  scanf("%i", &personen[1].plz);

  printf("Ort : ");
  fflush(stdin);
  gets(personen[1].ort);

  ausgabe(personen[0]);
  ausgabe(personen[1]);
}

Ergänzungen zu Strukturen:

Strukturen können genauso mit Hilfe des Zuweisungsoperator ' = ' kopiert werden, wie elementare Datentypen.

struct Personendaten  // Deklaration des structs
{
  char name[30];
  int alter;
};

struct Personendaten person1;
struct Personendaten person2;

person1.alter = 25;
strcpy(person1.name,"Susi");

person2 = person1;  // person2 übernimmt alles von person1

17.1 Zeiger auf Strukturen

#include <stdio.h>
#include <string.h>

// Deklaration des structs
struct Personendaten
{
  char name[30];
  int alter;
};

void main(void)
{

  struct Personendaten my_person;
  struct Personendaten * p_my_person;  // Zeiger auf struct Personendaten

  p_my_person = &my_person;

  my_person.alter = 25;

  p_my_person->alter = 33;   // Die Dereferenzierung ->
  strcpy(p_my_person->name, "Mueller");

  //Einlesen von Daten
  fflush(stdin);
  scanf("%i", &p_my_person->alter);
  fflush(stdin);
  gets(p_my_person->name);

  printf("Alter = %i \n", p_my_person->alter);
  printf("Name = %s", p_my_person->name);
}

18. Files/Dateien

18.1 Filehandling : ASCII-Files / Textdateien

void schreiben(void)
{
  FILE *fhd;
  char s[MAXZEILENLAENGE];

  fhd=fopen(DATNAME,"w");
  if(!fhd)
  {
    printf("Datei konnte nicht erzeugt werden!\n\n");
  }
  else
  {
    printf("Bitte maximal %i Zeichen pro Zeile
    eingeben.\n",MAXZEILENLAENGE);
    printf("Eingabe wird mit . beendet.\n\n");
    do
    {
      printf(">");
      gets(s);
      if(strcmp(s,"."))
      {
        fputs(s,fhd);
        fputs("\n",fhd);
      }
    } while(strcmp(s,"."));

  fclose(fhd);
  printf("\nEingabe beendet!\n");
  }
}
void lesen(void)
{
  FILE *fhd;
  char s[MAXZEILENLAENGE];
  int x=1;

  fhd=fopen(DATNAME,"r");
  if(!fhd)
  {
    printf("Datei konnte nicht geoeffnet werden!\n\n");
  }
  else
  {
    printf("Die Datei hat folgenden Inhalt:\n");

    fgets(s,MAXZEILENLAENGE,fhd);
    do
    {
      printf("%i:%s",x++,s);
      fgets(s,MAXZEILENLAENGE,fhd);
    } while(!feof(fhd));

  fclose(fhd);
  printf("\nEnde der Datei!\n");
  }
}
#include <stdio.h>
#include <string.h>

#define DATNAME "test.txt"
#define MAXZEILENLAENGE 160

void main(void)
{
  int input;

  printf("Soll die Datei 1=gelesen oder 2=beschrieben werden?");
  scanf("%i",&input);

  if(input==1)
  {
    lesen();
  }
  else
  {
    if(input==2)
    {
      schreiben();
    }
    else
    {
      printf("\nFalsche Eingabe!\n\n");
    }
  }
}

Öffnen der Textdatei:

fpt=fopen("filename.txt", "w");
  • w = Erstelllt eine Textdatei zum schreiben (write)
  • r = Öffnet eine Textdatei zum lesen (read)
  • a = Erstellt oder öffnet eine bereits existierende Datei zum Schreiben - Der Dateipositionszeiger steht am Ende der Datei (append)

Lesen/Schreiben:

fputs(mystring, fpt);   //Schreiben in File
fgets(thestring, MAXZEILENLAENGE, fpt);  //Lesen

Fileende prüfen

wert=feof(fpt);   // wert!=0 bedeutet Fileende

Achtung : feof liefert erst dann einen wahren Wert, wenn versucht wurde, Daten zu lesen, obwohl das Dateiende erreicht ist !

>File schliessen:

fclose(fpt);

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen.

  • Erstellen Sie ein Programm das folgendes tut:
    >FILE LESEN
    >
    >Bitte Filename eingeben : text.txt
    >
    >tetxt.txt :
    >
    >Hallo Leute
    >dies ist ein Testfile
    >
  • Erstellen Sie ein Programm das folgende Bildschirmausgabe generiert:
    >FILE SCHREIBEN
    >
    >Filename eingeben : test2.txt
    >Bitte Zeile 1 eingeben : Hallo Karli
    >Weitere Zeile ? (y/n) : y
    >Bitte Zeile 2 eingeben : Guten Tag
    >Weitere Zeile ? (y/n) : n

18.2 Binärdateien

Die bereits bekannte Dateiform ist die Textdatei. Diese kann ohne Probleme mit Texteditoren gelesen werden. Sollten aber zB. Datensätze auf File gespeichert werden, ist es effizienter, diese Daten anstatt in ASCII in binärer Form abzulegen.
Wie bei Textfiles muss auch das binäre File zuerst geöffnet werden :

FILE *fhd;
...
fhd = fopen("filename", "mode");
...

Mode:

  • Weitere Modes siehe C-Literatur
  • "rb" = Oeffnet Binärdatei zum lesen (read)
  • "wb" = Erstellt eine Binärdatei zum schreiben (write)
  • "ab" = Erstellt oder öffnet Binärdatei (append) neue Daten werden hinten angehängt !

Nach erfolgreichem Oeffnen der Datei folgt das Lesen oder Schreiben von Daten:

anz = fwrite(adresse,groesse,anzahl,fhd);
  • anz : Anzahl komplett geschriebener Datensätze
  • adresse : Adresse des zu speichernden Datensatzes
  • groesse : Grösse des Datensatzes in Byte
  • anzahl : =1 (bei normalen Variablen)
  • fhd : Zeiger auf Filestruktur
anz = fread (adresse,groesse,anzahl,fhd);
  • anz : Anzahl komplett gelesener Datensätze
  • adresse : Adresse von der gelesen wird
  • groesse : Grösse des Datensatzes in Byte
  • anzahl : =1 (bei normalen Variablen)
  • fhd : Zeiger auf Filestruktur

Um die Grösse eines Datensatzes zu bestimmen, wird eine neue Funktion eingeführt:

size_t anz_Byte;  // Datentyp : size_t
anz_Byte = sizeof(my_var);

Hier wird die Grösse der Variablen my_var in Bytes bestimmt.

Variante:

Im folgenden Beispiel wird zur Ermittlung des Fileendes nicht feof verwendet, sondern der Rückgabewert von fread ausgewertet. Konnte nämlich kein Datensatz (anz==0) mehr gelesen werden, muss das Fileende erreicht worden sein.

#include <stdio.h>  //Binaerfile schreiben

void binaryfile_write(void)
{
  FILE *fhd;
  char name[20];
  int alter;
  int weiter=1;  //1=weiter, 0=nicht weiter
  fhd=fopen("myfile", "wb");
  if(! fhd)
    printf("File open error\n");
  else
    while(weiter)
    {
      printf("Name : ");
      fflush(stdin);
      gets(name);
      printf("Alter : ");
      scanf("%i",&alter);
      fwrite(name, sizeof(name), 1, fhd);
      fwrite(&alter, sizeof(alter), 1, fhd);
      printf("weiter ? 1=ja/0=nein : ");
      scanf("%i",&weiter);
    }
  fclose(fhd);
}

#include <stdio.h>  //Binaerfile lesen

void binaryfile_read(void)
{
  FILE *fhd;
  char name[20];
  int alter;
  size_t got=0;  // flag : 1=Daten gelesen
                 //        0=keine weitere Daten
  fhd=fopen("myfile", "rb");
  if(! fhd)
    printf("File open error\n");
  else
  {
    got=fread(name, sizeof(name), 1, fhd);
    while(got)
    {
      printf("**********************\n");
      printf("Name : %s \n",name);
      got=fread(&alter, sizeof(alter), 1, fhd);
      printf("Alter : %i \n",alter);
      got=fread(name, sizeof(name), 1, fhd);
    }
  fclose(fhd);
  }
}

19. Der Aufzählungstyp enum

enum = Enumeration, was soviel bedeutet wie Aufzählung.

// Deklaration des Aufzaehlungstyp
enum Farbkreis{gelb, orange, rot, violett, blau, gruen};

enum Farbkreis farbe;   // Variablendeklaration farbe = rot;

if(farbe == rot)
  printf("Warme Farbe");
for(farbe = gelb; farbe <= gruen; farbe++)
  { }

Achtung
Die Ausgabe...

farbe=rot; printf("%i", farbe);

...ergibt nicht rot. Dies weil "intern" den sechs Konstanten gelb, orange, rot, violett, blau und gruen Werte von 1 bis 6 zugewiesen werden.

 


20. Eigene Typen mit typedef

Es besteht die Möglichkeit, eigene Datentypen zu definieren.

Beispiel 1:

typedef enum Woche{MO, DI, MI, DO FR, SA, SO};
Woche tag;   // Variablendeklaration
tag = MO;

Beispiel 2:

struct Personendaten // Deklaration des structs
{
  char name[30];
  int alter;
};

typedef struct Personendaten TPersonendaten;
TPersonendaten my_person; // Variablendeklaration
my_person.alter = 25;

Einfachere Variante:

typedef struct Personendaten  // Deklaration des structs
{
 char name[30];
 int alter;
 };

Personendaten my_person;   // Variablendeklaration

my_person.alter = 25;

 


21. Dynamische Speicherverwaltung

Bisher war die Speicherbelegung vor "Laufzeit" des Programms definiert. Dass heisst : Zur Startzeit ist festgelegt, welche Variable mit welchem Speicherbedarf vorliegt. In einigen Fällen kann aber nicht zum vorherein gesagt werden, wieviel Speicherplatz benötigt wird. Zum Beispiel : «Eingabe eines Textes», «Erstellen einer Personenkartei» oder «Lesen eines Files». Darum gibt es auch die Möglichkeit, Speicher dynamisch, dh. zur Laufzeit anzufordern.

21.1 Reservierung eines Speicherblocks

Speicher reservieren:

void * malloc(size_t groesse);

Speicher freigeben:

void free(void * ptr);

Was bedeutet   void * ptr;

Dies ist ein typenloser Zeiger. Da malloc nicht weiss, für welchen Zweck Speicher angefordert wird, ist ein sogenannter Nullpointer als Rückgabewert definiert. Der Programmierer hat bei der Verwendung von malloc zu sorgen, dass das Typenkonzept eingehalten wird. Darum muss der Nullpointer mit einem Typecast in einen Zeiger auf den entsprechenden Datentyp umgewandelt werden. Dazu folgendes Beispiel :

#include <stdio.h>
#include <stdlib>

void main(void)
{
  void * nullptr; // Deklaration eines typenlosen Zeigers
  int * ptr;      // Deklaration eines Zeigers auf einen Integer

  nullptr = malloc(sizeof(int)); // Dynamische Speicherallozierung
  ptr = (int *) (nullptr);       // Typecast : typenloser Zeiger zu Zeiger auf int

  if(ptr)  // Sofern Allozierung geklappt hat ...
  {
    *ptr=23;   // Wertzuweisung via Dereferenzierung
    printf("Wert = %i \n", *ptr);
    free(ptr); // NICHT VERGESSEN : Freigeben des Speichers
  }
}

Einfachere Variante:

#include <stdio.h>
#include <stdlib>

void main(void)
{
  int * ptr;  // Deklaration eines Zeigers auf einen Integer

  if(ptr = (int*)(malloc(sizeof(int))))  // Allozierung, Typecast und Verifizierung in einem ...
  {
    *ptr=23;  // Wertzuweisung via Dereferenzierung
    printf("Wert = %i \n", *ptr);
    free(ptr); // NICHT VERGESSEN : Freigeben des Speichers
  }
}

Natürlich können auch alle anderen Datentypen wie strings, structs etc. dynamisch alloziert werden.

21.2 Vorwärts verkettete Liste  (Forward linked list)

Beim Lesen von Datensätzen stellte sich bisher die Frage, für wieviele Einträge bzw. wie gross der Behälter dimensioniert werden muss. Mit den neuen Kenntnissen über die dynamische Speicherallokation kann nun eine Lösung gefunden werden. Was vielleicht noch Probleme bereitet, ist wie die Daten verwaltet werden sollen. Dazu behelfen wir uns einem Trick, den wir seit frühester Schulzeit kennen: Beim Aufstellen in einer Einerkolonne merken wir uns den Vordermann. So kann beim erneuten Hinstellen die korrekte Reihenfolge sofort gefunden werden.

Definieren des "Elements":

Dieser beinhaltet zwei Teile:

  • Den Nutzteil (Kann auch ein struct sein !)
  • Den Verwaltungsteil (Zeiger auf den "Vordermann")
struct the_element
{
 int nutz;
 struct the_element * next;
};

Deklarieren der Verwaltungsvariablen:

Um die Liste aufbauen zu können, braucht es folgende Variablen:

struct the_element * element;
struct the_element * anker;
struct the_element * schlepper;
int first;

Aufbauen der "Linked List":

Auslesen der "Linked List":

Programmieraufgaben:

Versuchen sie die folgenden Aufgaben zu lösen:

  • Erstellen Sie eine Personenverwaltung. Diesmal soll die Anzahl der Personen nicht fix über ein array of struct sondern über eine linked list gelöst werden. Funktionen : new_entry, show_entry, show_entries
  • Ergänzen Sie Ihr Programm mit einer Funktion "Save to file" und "Read from file"
  • Ermöglichen Sie die Funktionen "delete_entry", sort_list"

22. Modulares Programmieren

Mein "Mathematik"-Modul , Filename : mathemodul.c

//////////////////////////////////////////
// Funktion : Potenz-Berechnen
// Autor    : ARJ
// Datum    : 6. Jan. 2017
// Zweck    : Berechnet basis hoch exponent
//            I : Basis
//            I : Exponent
//            O : Potenzwert
// Limits   : Nur positive und ganz-
//            zahlige Basis bzw.
//            Exponent
//////////////////////////////////////////
int pot(int basis, int exponent)
{
  int resultat=1;
  if (exponent==0)
  {
    return(resultat);
  }
  else
  {
    while(exponent>0)
    {
      resultat=resultat*basis;
      exponent=exponent-1;
    }
    return(resultat);
  }
}
// Headerdatei mathmodul.h ///////////////
//////////////////////////////////////////
// Funktion : Potenz-Berechnen
// Autor    : ARJ
// Datum    : 6. Jan. 2017
// Zweck    : Berechnet basis hoch exponent
//            I : Basis
//            I : Exponent
//            O : Potenzwert
// Limits   : Nur positive und ganz-
//            zahlige Basis bzw.
//            Exponent
//////////////////////////////////////////
int pot(int basis, int exponent);

Das Hauptprogramm, Filename : math.c

#include <stdio.h>
#include "mathemodul.h"  // Headerdatei fuer die pot-Funktion

/*****************************************/
/* Titel :  Uebung Potenzfunktion        */
/* Autor :  ARJ                          */
/* Datum :  6. Jan. 2017                 */
/* Zweck :  Potenz a hoch b berechnen    */
/* Version : 2.0                         */
/*****************************************/

void main(void)
{
  int b=1;
  int e=0;
  int p=1;

  printf("\n\nPotenz a hoch b V2.0");
  printf("\n********************\n\n");
  // Eingabe (User-Interface)
  printf("Basis : ");
  scanf("%i",&b);
  printf("Exponent : ");
  scanf("%i",&e);

  //Berechnung
  p=pot(b,e);

  //Ausgabe
  printf("Resultat = %i",p);
}

Wie wird kompiliert? (Bsp. mit lcc)

  • c:\Programme\lcc\bin\lcc math.c (Ergibt math.obj)
  • c:\Programme\lcc\bin\lcc mathemodul.c (Ergibt mathemodul.obj)
  • c:\Programme\lcc\bin\lcclnk math.obj mathemodul.obj (Ergibt math.exe)

Mit math.exe erhält man das ausführbare Programm.
Wichtig: Packen Sie nur dasjenige in eine Funktion, das thematisch auch zusammengehört. Die Ausgabe mit printf gehört zum Beispiel bei unserer Funktion pot() nicht dazu, weil man sich dadurch den universellen Einsatz des "mathemodul" bzw. der Funktion pot() verbaut. Man möchte ja diese Funktion vieleicht später an anderer Stelle (dh. in einem anderen Programm) wiederverwenden, wo keine Ausgabe mit printf verlangt ist oder die Ausgabe sogar in einem Grafikfenster anstatt der Konsole ausgegeben werden soll.


23. Programmbeispiele

#include <stdio.h>

/*****************************************/
/* Titel :  Patternmatching              */
/* Autor :  ARJ                          */
/* Datum :  7. Maerz 2017                */
/* Zweck :  Suchen eines Teststrings in  */
/*          einer Textzeile              */
/* Version : 1.0                         */
/*****************************************/

void main(void)
{

  char my_text[51];       // Eingabe Textstring
  char test_pattern[21];  // Zu vergleichender String

  int index_text=0;       // Textstring Aktuelle Position
  int index_pattern=0;    // Vergleichsstring Aktuelle Pos.

  int len_text=0;         // Laenge Textstring
  int len_pattern=0;      // Laenge Vergleichsstring

  int patternmatching=0;  // Anzahl Treffer

  // Eingabe der Strings
  printf("Bitte Textzeile eingeben (max. 50 Zeichen) : ");
  fflush(stdin);
  gets(my_text);

  printf("Bitte Suchbegriff eingeben (max. 20 Zeichen) : ");
  fflush(stdin);
  gets(test_pattern);

  // Laengenbestimmung der beiden Strings
  // Dies ist nötig, damit man beim Parsern des Textstrings nicht zu weit
  // vorrueckt
  for(len_text=0; my_text[len_text] != '\0'; len_text++);
  for(len_pattern=0; test_pattern[len_pattern] != '\0'; len_pattern++);

  // Gehe im Textstring soweit, dass noch ein Suchstring vorkommen kann
  while( index_text < (len_text + len_pattern) )
  {
  // Falls Charakter an der aktuellen Pos. uebereinstimmen, wird Pos. um eine
  // Stelle erhoeht. Achtung : Pos. des Textstring bleibt stehen. Es wird mit
  // einem Offset gearbeitet. Die zu pruefende Pos. im Textstring ergibt sich
  // aus index_text + index_pattern. Damit besteht auch der "lololu" "lolu"-Test
  while( (my_text[index_text + index_pattern] ==
          test_pattern[index_pattern])
          && 
          ( my_text[index_text + index_pattern] != '\0') )
    index_pattern++;
  // Wenns bis aufs letzte Zeichen im Suchstring uebereinstimmt
  // wird der Score um 1 erhoeht.
  if(test_pattern[index_pattern] == '\0')
    patternmatching++;
    // Der Positionszeiger vom Suchstring zurücksetzen.
    // Der Positionszeiger um 1 erhoehen
  index_pattern=0;
  index_text++;
  };

  // Ausgabe des Resultats
  printf("Im Satz >%s< steht >%s< %i Mal !\n",my_text, test_pattern, patternmatching);
}

/***************************************************/
/* Autor   : ARJ                                   */
/* Name    : bubble.c                              */
/* Zweck   : Sortierung der Eingegebenen Zahlen    */
/* Version : 1.0                                   */
/* Datum   : 7. Maerz 2017                         */
/***************************************************/
#include <stdio.h>
#define FALSE 0
#define TRUE 1

/*****************FUNCTION bubble_sort*******************/
void bubble_sort(int * arr, int anzahl)
{
  int i=0;              //Zaehler, Position im Array
  int abbruch=FALSE;    //Abbruch-Flag 1=Abbrechen, 0=Nicht abbrechen
  int hilf=0;           //Hilfsvariable fuer vertauschen

  while(! abbruch)
  {
    abbruch=TRUE;
    while(i<(anzahl-1))
    {
      // Pruefen der benachbarten Kugeln
      if(arr[i]>arr[i+1])
      {
        // Vertauschen der beiden Kugeln falls noetig !
        hilf=arr[i];
        arr[i]=arr[i+1];
        arr[i+1]=hilf;
        abbruch = FALSE;  //Nicht abbrechen !
      }
      i++;
    }
    i=0;
  }
}

/*****************MAIN**********************************/

void main(void)
{
  int i = 0;
  int arr[10];

  while(i<10)
  {
    printf("Eingabe %i. Zahl : ",i+1);
    scanf("%i",&arr[i]);
    i++;
  }
  printf("\n");
  bubble_sort(arr, 10);

  // Ausgabe arrays
  i=0;
  while(i<10)
  {
    printf("%i. Zahl : %i\n",i+1, arr[i]);
   i++;
  }
}

/*****************************************/
/* Titel :  Structs                      */
/* Autor :  ARJ                          */
/* Datum :  10. Maerz 2017               */
/* Zweck :  Beispiel                     */
/* Version : 1.0                         */
/*****************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define TRUE  1
#define FALSE 0
#define MAX_PERS 10

// Definition der Struktur
struct Person
{
  int   p_nummer;
  int   belegt;
  char  nachname[31];
  char  vorname[31];
  char  wohnort[31];
  int   jahrgang;
};

// Ausgabe eines Records (=struct) oder Datensatzes
void p_anzeigen(struct Person * p_thePerson)
{
  if (p_thePerson->belegt)
  {
    printf("P_Nr     : %i \n",p_thePerson->p_nummer);  // Dereferenzierung bei struct
    printf("Name     : %s \n",p_thePerson->nachname);
    printf("Vorname  : %s \n",p_thePerson->vorname);
    printf("Wohnort  : %s \n",p_thePerson->wohnort);
    printf("Jahrgang : %i \n",p_thePerson->jahrgang);
    printf("\n");
  };
};

// Erfassen eines Records (=struct) oder Datensatzes
int p_erfassen_ok(int nr, struct Person * p_thePerson)
{
  if (! (p_thePerson->belegt))
  {
    printf("\n\n");
    printf("Name    (max 30 Zeichen !) : ");
    fflush(stdin);
    gets(p_thePerson->nachname);
    printf("Vorname (max 30 Zeichen !) : ");
    fflush(stdin);
    gets(p_thePerson->vorname);
    printf("Wohnort (max 30 Zeichen !) : ");
    fflush(stdin);
    gets(p_thePerson->wohnort);
    printf("Jahrgang                   : ");
    fflush(stdin);
    scanf("%i",&p_thePerson->jahrgang);
    printf("\n");
    p_thePerson->belegt=TRUE;
    p_thePerson->p_nummer=nr;
    return(1);
  }
  else
  return(0);
}

// Funktion mit Titel
void titel(void)
{
  system("cls");
  printf("\n*******************************************\n");
  printf("***********PERSONEN DATENBANK**************\n");
  printf("***********************************V1.0****\n\n\n");
}

/////////////////////////////////////////////////////////////////////////////////
void main(void)
{
  //Variablendeklaration
  struct Person personen[MAX_PERS];  // Daten : Array of struct
  char ch='\0';                      // Menue-Eingabe
  int beenden=FALSE;                 // Menue-Beenden-Flag
  int i=0;                           // Zaehlervariable
  int ok=FALSE;                      // Flag bei Eingabe von Datensaetzen
  int Personalnummer=1;              // Eindeutige Nummer eines Datensatzes
  int nr=0;                          // Hilfsvariable fuer P_Nr zu merken

  // Initialisierung
  for(i=0; i<MAX_PERS; i++)
  personen[i].belegt=FALSE;

  while(!beenden)
  {
    titel();
    printf("****************MENUE**********************\n");
    printf("Neue Person erfassen : n und ENTER druecken\n");
    printf("Alle Person anzeigen : a und ENTER druecken\n");
    printf("Person loeschen      : l und ENTER druecken\n");
    printf("Person mutieren      : m und ENTER druecken\n");
    printf("Hilfe                : h und ENTER druecken\n\n");
    printf("EXIT                 : x und ENTER druecken\n\n");
    printf("Ihre Wahl            : ");
    fflush(stdin);
    scanf("%c",&ch);

    switch(ch)
    {
      case 'N' :
      case 'n' : titel();
                 printf("****************Neuer Datensatz************\n");
                 ok=FALSE;
                 for(i=0;!ok && (i<MAX_PERS); i++)
                 {
                   ok=p_erfassen_ok(Personalnummer, &personen[i]);
                   if (ok)
                   Personalnummer++;
                   if(i>=MAX_PERS-1)
                     printf("\n\n*** Speicher voll !!!***\n");
                  }
                  break;
       case 'A' :
       case 'a' : titel();
                  printf("****************Ausgabe Datensaetze********\n\n");
                  for(i=0; i<MAX_PERS; i++)
                  p_anzeigen(&personen[i]);
                  printf("Fuer Weiter bitte ENTER druecken ...");
                  fflush(stdin);
                  scanf("%c",&ch);
                  break;
       case 'L' :
       case 'l' : titel();
                  printf("****************Datensatz loeschen*********\n");
                  for(i=0; i<MAX_PERS; i++)
                    p_anzeigen(&personen[i]);
                  printf("Welcher Datensatz soll geloescht werden (P_Nr eingeben) : ");
                  fflush(stdin);
                  scanf("%i",&nr);
                  for(i=0; i<MAX_PERS; i++)
                    if (personen[i].p_nummer==nr)
                    {
                      printf("Datensatz %i wird geloescht !\n",nr);
                      personen[i].belegt=FALSE;
                    }
                  break;
       case 'M' :
       case 'm' : titel();
                  printf("****************Datensatz mutieren*********\n");
                  for(i=0; i<MAX_PERS; i++)
                    p_anzeigen(&personen[i]);
                  printf("Welcher Datensatz soll modifiziert werden (P_Nr eingeben) : ");
                  fflush(stdin);
                  ok=FALSE;
                  scanf("%i",&nr);
                  for(i=0;!ok && (i<MAX_PERS); i++)
                  if (personen[i].p_nummer==nr)
                  {
                    personen[i].belegt=FALSE;
                    ok=p_erfassen_ok(Personalnummer, &personen[i]);
                    if (ok)
                    Personalnummer++;
                  }
                  break;

       case 'h' :
       case 'H' : titel();
                  printf("****************Hilfe**********************\n");
                  printf("Mit diesem Programm koennen Sie bis \n");
                  printf("max. 10 Personen erfassen, mutieren oder \n");
                  printf("loeschen.  Viel Spass !\n\n");
                  printf("Fuer Weiter bitte ENTER druecken ...");
                  fflush(stdin);
                  scanf("%c",&ch);
                  break;
       case 'X' :
       case 'x' : titel();
                  printf("****************Programm beenden***********\n");
                  printf("\n\nSind Sie sicher das Sie das Programm beenden\n");
                  printf("moechten ? (j fuer ja / n fuer nein) : ");
                  fflush(stdin);
                  scanf("%c",&ch);
                  if( (ch=='J') || (ch=='j') )
                    beenden=TRUE;
                  break;
     } //End switch-case
   } //End while
} //End main

Bemerkung:

Dieses Programm hat noch einige Mängel, zB. die Mutier-Funktion, die noch verbesserungswürdig ist. Der ganze Ansatz könnte mit dynamischer Speicherverwaltung und "Linked-List" eleganter gelöst werden.