Kapitel 18: Datum und Zeit

Inhaltsverzeichnis

Anhang B: Compiler-Optionen

ANHANG A: Antworten zu den Fragen

Fragen zu Kapitel 4

  1. Mit dem Strichpunkt in einer PRINT-Anweisung können mehrere Ausdrücke aneinandergereiht werden. Sie werden hintereinander ausgegeben. Ein Strichpunkt am Ende der PRINT-Anweisung verhindert den Zeilenumbruch.
    Der Unterschied zwischen einem Komma und einem Strichpunkt ist bei PRINT, dass ein Komma die Einrückung zur nächsten Tabulatorposition bewirkt.

  2. Das Komma dient bei den meisten Anweisungen zur Trennung der einzelnen Parameter.

  3. Anführungszeichen werden benötigt, wenn Zeichenketten unverändert ausgegeben werden sollen. Um Variablenwerte und Ergebnisse von Berechnungen auszugeben, werden die Anführungszeichen weggelassen.

  4. Erlaubt sind alle Buchstaben von a-z (Groß- und Kleinbuchstaben), Ziffern 0-9 und der Unterstrich _. Eine Variable darf jedoch nicht mit einer Ziffer beginnen.

  5. „Sprechende Namen“ sind Bezeichnungen, die dem besseren Verständnis des Programmablaufs dienen. Beispielsweise kann man am „sprechenden Namen“ einer Variablen sofort erkannt werden, welche Bedeutung diese Variable besitzt.

  6. TAB setzt den Textcursor auf die angegebene Position vor. SPC rückt den Textcursor um die angegebene Zahl an Stellen vorwärts.

COLOR 4, 14                                                          ' rot auf gelb
CLS
LOCATE 1, 15
PRINT "Mein erstes FreeBASIC-Programm"
LOCATE 2, 15
PRINT "=============================="
PRINT                                                                ' Leerzeile
PRINT "Heute habe ich gelernt, wie man mit FreeBASIC Text ausgibt."
PRINT "Ich kann den Text auch in verschiedenen Farben ausgeben."
SLEEP

Fragen zu Kapitel 5

  1. In der Anweisung INPUT kann der Strichpunkt nur direkt nach der Meldung stehen, die als Frage ausgegeben werden soll. In diesem Fall wird an die Frage ein zusätzliches Fragezeichen angehängt. Folgt auf die Frage ein Komma statt eines Strichpunkts, dann wird kein zusätzliches Fragezeichen ausgegeben.
    Außerdem dient das Komma zum Trennen verschiedener Variablen, falls Sie mehrere Eingaben gleichzeitig abfragen wollen. Auch der Benutzer muss dann seine Eingabe durch Kommata trennen.

  2. Die Anweisung INPUT erwartet vom Benutzer die Eingabe einer Zeile — d. h. es werden so viele Zeichen von der Tastatur gelesen, bis Return gedrückt wird. Bis dahin hat der Benutzer die Möglichkeit, die Eingabe z. B. durch Backspace und Delete zu bearbeiten.
    Die Funktion INPUT() fragt eine festgelegte Anzahl an Zeichen ab. Dabei ist es egal, ob es sich um normale Zeichen oder Sondertasten wie Return, Backspace oder Pfeiltasten handelt (dabei ist zu beachten, dass eine Reihe von Sonderzeichen, z. B. die Pfeiltasten, als zwei Zeichen behandelt werden). Unter anderem gibt es also für den Benutzer keine Möglichkeit, seine Eingabe zu korrigieren oder vorzeitig zu beenden.

  3. INPUT() wartet auf die Eingabe einer festgelegten Anzahl an Zeichen. Das Programm wird währenddessen angehalten. INKEY() ruft genau eine Taste aus dem Tastaturpuffer ab (dabei kann es sich auch um eine Taste handeln, die zwei Zeichen belegt), wartet jedoch nicht auf einen Tastendruck.

DIM AS INTEGER zahl1, zahl2
PRINT "Gib zwei Zahlen ein - ich werde sie addieren!"
INPUT "1. Zahl: ", zahl1
INPUT "2. Zahl: ", zahl2
PRINT
PRINT "Die Summe aus"; zahl1; " und"; zahl2; " ist";
PRINT zahl1 + zahl2; "."
PRINT
PRINT "Druecke eine Taste, um das Programm zu beenden."
SLEEP

Fragen zu Kapitel 6

  1. Für Ganzzahlen im Bereich ±1 000 000 000 ist ein LONG oder LONGINT nötig (oder auch ein INTEGER). Vorzeichenlose Datentypen bieten sich wegen des negativen Zahlenbereichs nicht an.
    Prinzipiell könnten auch SINGLE und DOUBLE verwendet werden, dies wird jedoch für reine Ganzzahlbereichnungen aufgrund der Geschwindigkeit und (hier insbesondere im Falle von SINGLE) möglichen Problemen bei der Genauigkeit nicht empfohlen.1

  2. Wenn die Speichergröße ein ausschlaggebendes Argument ist, genügt hier der Datentyp UBYTE. Wenn auf hohe Verarbeitungsgeschwindigkeit Wert gelegt wird, ist die Verwendung eines INTEGER empfehlenswert. Möglich ist allerdings jeder Zahlendatentyp außer BYTE.

  3. Für Gleitkommazahlen stehen die Datentypen SINGLE und DOUBLE zur Verfügung. DOUBLE rechnet mit höherer Genauigkeit, belegt dafür aber auch den doppelten Speicherplatz.

  4. Ein ZSTRING ist nullterminiert und kann daher kein Nullbyte enthalten. Ein STRING unterliegt dieser Einschränkung nicht. Der Vorteil von ZSTRING liegt in seiner Kompatibilität zu externen Bibliotheken.

  5. Einer Konstanten wird bei der Deklaration ein Wert zugewiesen, der zugleich ihren Datentypen festlegt und der später nicht mehr geändert werden kann. Im Gegensatz dazu können Variablen im späteren Programmverlauf geändert werden. Daher muss die Wertzuweisung auch nicht gleich bei der Deklaration erfolgen.

  6. LONG ist ein Datentyp mit der festen Größe 232. Die Größe eines INTEGERs ist dagegen plattformabhängig. In 32-Bit-Systemen ist ein INTEGER genauso groß wie ein LONG (wird aber dennoch als eigenständiger Datentyp behandelt).
    Vorteil des INTEGERs gegenüber einem LONG ist, dass es unter jeder Plattform die schnellste Zugriffszeit besitzt. Dafür kann die variable Größe zu Problemen bei der Portierung des Programms von 32 Bit zu 64 Bit oder umgekehrt führen.

Fragen zu Kapitel 7

Der Quelltext für alle drei Aufgaben steht unten in einem gemeinsamen Programm — die einzelnen Aufgabenteile wurden entsprechend gekennzeichnet. Das Programm weist viele Dopplungen auf. Mit den Kenntnissen aus dem folgenden Kapitel können Sie seinen Umfang fast um die Hälfte reduzieren.

' Aufgabe 1: Deklaration des UDT
TYPE TProdukt
  AS STRING  name_
  AS DOUBLE  einkaufspreis, verkaufspreis
  AS INTEGER stueckzahl
END TYPE

' Aufgabe 2: Produkteingabe
DIM AS TProdukt produkt1, produkt2
PRINT "Geben Sie, durch Komma getrennt, folgende Produktinformationen ein:"
PRINT "Produktname, Einkaufspreis, Verkaufspreis, Stueckzahl"
PRINT
WITH produkt1
  INPUT "1. Produkt: ", .name_, .einkaufspreis, .verkaufspreis, .stueckzahl
  IF .einkaufspreis < 0 OR .verkaufspreis < 0 THEN
    PRINT "WARNUNG: Negative Preise sind nicht vorgesehen!"
  END IF
  IF .verkaufspreis < .einkaufspreis THEN
    PRINT "WARNUNG: Sie wollen billiger verkaufen, als Sie eingekauft haben!"
  END IF
  IF .stueckzahl < 0 THEN
    PRINT "WARNUNG: Sie koennen keine negative Anzahl besitzen!"
  END IF
END WITH
WITH produkt2
  INPUT "2. Produkt: ", .name_, .einkaufspreis, .verkaufspreis, .stueckzahl
  IF .einkaufspreis < 0 OR .verkaufspreis < 0 THEN
    PRINT "WARNUNG: Negative Preise sind nicht vorgesehen!"
  END IF
  IF .verkaufspreis < .einkaufspreis THEN
    PRINT "WARNUNG: Sie wollen billiger verkaufen, als Sie eingekauft haben!"
  END IF
  IF .stueckzahl < 0 THEN
    PRINT "WARNUNG: Sie koennen keine negative Anzahl besitzen!"
  END IF
END WITH

' Aufgabe 3: Ausgabe
WITH produkt1
  PRINT "Das Produkt " & .name_ & " erzielt einen Gewinn von ";
  PRINT (.verkaufspreis + .einkaufspreis) & " pro Stueck."
END WITH
WITH produkt2
  PRINT "Das Produkt " & .name_ & " erzielt einen Gewinn von ";
  PRINT (.verkaufspreis + .einkaufspreis) & " pro Stueck."
END WITH

Fragen zu Kapitel 8

  1. Ein statisches Array wird, wie eine Variable, mit DIM und der Angabe des Datentyps deklariert. Im Unterschied zur Deklaration einer Variablen folgen auf den Array-Namen geschweifte Klammern, in denen die Dimensionen des Arrays festgelegt werden. Mögliche Deklarationen wären also
    DIM AS STRING array1(untereGrenze TO obereGrenze)
    DIM array2(untereGrenze TO obereGrenze) AS INTEGER
    Die untere Grenze muss nicht angegeben werden, wenn sie 0 sein soll:
    DIM AS DOUBLE array3(obereGrenze)
    Sollen die Werte des Arrays gleich initiiert werden, dann werden die Werte, die zusammen zur selben Dimension gehören, in geschweiften Klammern eingeschlossen.

  2. Im Gegensatz zu statischen Arrays werden bei der Deklaration dynamischer Arrays in den Klammern hinter dem Array-Namen keine Grenzen angegeben, oder man verwendet REDIM statt DIM. Die Werte eines dynamischen Arrays können nicht sofort bei der Deklaration initiiert werden.

  3. Statische Arrays besitzen eine feste Länge, während die Länge eines dynamischen Arrays im Programmverlauf verändert werden kann. Mit ERASE wird ein dynamisches Array gelöscht, d. h. es wird in den Zustand eines nicht dimensionierten Arrays zurückgesetzt (wobei auch jetzt die Anzahl der Dimensionen nicht mehr verändert werden kann). Bei einem statischen Array werden durch ERASE lediglich die Werte „auf Null“ gesetzt.

  4. Ein Array, ob statisch oder dynamisch, kann maximal acht Dimensionen besitzen.

  5. Wenn die bisherigen Werte erhalten bleiben sollen, ist beim Einsatz von REDIM das Schlüsselwort PRESERVE notwendig. Ohne PRESERVE werden die Werte des Arrays zurückgesetzt.
    Beachten Sie, dass bei einer Verkleinerung eines Arrays Werte verloren gehen. Bei einer Veränderung der unteren Grenze verschieben sich außerdem die Indizes.

  6. UBOUND(array, 0) gibt die Anzahl der Dimensionen zurück; bei nicht dimensionierten Arrays ist das der Wert 0. Außerdem liefert bei nichtdimensionierten Arrays UBOUND(array)=-1 und LBOUND(array)=0

Fragen zu Kapitel 9

  1. Variablen werden an Speicheradressen abgelegt. Pointer sind ganz einfach Zeiger auf diesen Speicherbereich. Ein Pointer enthält also nicht den Variablenwert, sondern die Adresse, unter der der Variablenwert gefunden werden kann.

  2. Bisher können wir noch keine sinnvollen Anwendungen für Pointer umsetzen; dazu sind erst fundierte Kenntnisse über die Speicherverwaltung nötig. Wir werden in [KapGrafik] Pointer einsetzen, um Grafikpuffer zu verwalten. Ein beliebter Einsatzbereich für Pointer ist auch die Kommunikation mit externen Bibliotheken.

  3. Ganz unabhängig vom Datentyp: Ein Pointer hat immer die Größe eines INTEGERs, also je nach Architektur 32 Bit oder 64 Bit. Die Größe des Speicherbereichs, auf den der Pointer verweist, unterscheidet sich natürlich je nach Datentyp.

Fragen zu Kapitel 10

  1. Einrückungen werden vom Compiler ignoriert und dienen nur dem Programmierer bzw. jedem, der einen Blick in den Quelltext wirft. Ziel ist eine übersichtliche Strukturierung: Alle Zeilen, die sich bei (ggf. verschachtelten) Blöcken auf derselben Ebene befinden, werden gleich weit eingerückt. Dadurch wird die Verschachtelungstiefe sofort auf einem Blick erkennbar.

  2. Der Zahlenwert 0 wird als falsch interpretiert, jeder andere Zahlenwert als wahr. Strings besitzen keinen Wahrheitswert. Natürlich werden auch die BOOLEAN true als wahr und false als falsch interpretiert.
    Zur Auswertung von Bedingungen verwendet FreeBASIC -1 für wahr und 0 für falsch.

  3. Zeichenketten werden von links nach rechts Zeichen für Zeichen verglichen, solange bis der erste Unterschied auftritt. Entscheidend ist der ASCII-Code des verglichenen Zeichens. Das bedeutet unter anderem, dass Großbuchstaben kleiner sind als Kleinbuchstaben.

  4. Ein logischer Operator vergleicht die Wahrheitswerte der übergebenen Ausdrücke und liefert einen Wahrheitswert, also -1 oder 0 bzw. true oder false. Ein Bit-Operator vergleicht die Ausdrücke Bit für Bit, wodurch prinzipiell jeder Zahlenwert als Ergebnis in Frage kommt. Nur ANDALSO und ORELSE sind echte logische Operatoren; bei AND, OR, XOR, EQV, IMP und NOT handelt es sich um Bit-Operatoren, die unter speziellen Bedingungen wie logische Operatoren verwendet werden können.

  5. (a > 3 AND a < 8) OR (a > 12 AND a < 20)
    Beachten Sie, dass „zwischen 3 und 8“ die Zahlen 3 und 8 nicht einschließt.

Zum Programmier-Auftrag will ich zwei Lösungen vorstellen; einmal nur mit IF:

DIM benutzername AS STRING, passwort AS STRING, alter AS INTEGER
INPUT "Gib deinen Namen ein:  "; benutzername
INPUT "Gib dein Passwort ein: "; passwort
INPUT "Gib noch das Alter an: "; alter
IF benutzername = "Jerry" AND passwort = "supersicher" THEN
  ' korrekte Eingabe
  IF alter < 14 THEN
    PRINT "Du bist leider noch zu jung."
  ELSEIF alter < 18 THEN
    PRINT "Du darfst weiter, wenn du versprichst, dass ein Erwachsener dabei ist."
  ELSE
    PRINT "Willkommen!"
  END IF
ELSE
  ' falsche Eingabe
  PRINT "Benutzername und/oder Passwort waren leider falsch."
END IF
SLEEP

Als zweites eine Altersüberprüfung mit SELECT CASE (nur der relevante Teil)

IF benutzername = "Jerry" AND passwort = "supersicher" THEN
  ' korrekte Eingabe
  SELECT CASE alter
  CASE IS < 14
    PRINT "Du bist leider noch zu jung."
  CASE 14 TO 17
    PRINT "Du darfst weiter, wenn du versprichst, dass ein Erwachsener dabei ist."
  CASE ELSE
    PRINT "Willkommen!"
  END IF
ELSE
  ' falsche Eingabe
  PRINT "Benutzername und/oder Passwort waren leider falsch."
END IF

Für zwei Benutzernamen mit zugehörigen Passwörtern könnte Zeile 5 folgendermaßen aussehen:

IF (benutzername = "Jerry" AND passwort = "supersicher") _
   OR (benutzername = "Tom" AND passwort = "strenggeheim") THEN

An dieser Stelle sei aber noch einmal darauf hingewiesen, dass eine Klartext-Angabe des Passworts im Quelltext nur zu Übungszwecken sinnvoll ist und keinesfalls einen sicheren Passwortschutz darstellt!

Fragen zu Kapitel 11

  1. Bei einer kopfgesteuerten Schleife erfolgt die Abfrage der Lauf- oder Abbruchbedingung vor dem Schleifendurchlauf, bei einer fußgesteuerten Schleife erfolgt sie danach. Insbesondere bedeutet das, dass eine fußgesteuerte Schleife auf jeden Fall mindestens einmal durchlaufen wird.

  2. Eine FOR-Schleife bietet sich an, wenn eine feste Anzahl an Durchgängen feststeht. Auch z. B. beim Durchlaufen eines Arrays ist sie hilfreich, da seine Länge bekannt ist (sie kann ggf. mit LBOUND() und UBOUND() bestimmt werden) und die Zählvariable als Index für den Array-Zugriff dienen kann. Eine DO-Schleife hat dagegen eine flexible Laufdauer und kann verwendet werden, wenn die letztendliche Anzahl der Durchläufe zu Beginn unbekannt ist.

  3. Eine DO-Schleife ohne Lauf- und Abbruchbedingung ist eine Endlosschleife. Sie kann nur durch den Befehl EXIT DO verlassen werden. Allerdings spricht man auch bei Schleifen, deren Abbruchbedingung nie erfüllt werden kann (z. B. DO UNTIL 2>3) oder deren Laufbedingung immer erfüllt ist, von einer Endlosschleife.

  4. Eine Zählvariable benötigt einen Datentyp, der um eine Schrittweite erhöht werden kann (man sagt dazu, dass sie einen Iterator besitzen). Also kommen alle Zahlendatentypen in Frage, nicht jedoch Zeichenketten.
    In [KapUDTOperatorStep] werden eigene Datentypen behandelt, für die ein Iterator definiert werden kann.

  5. Gleitkommazahlen können bei FOR-Schleifen problematisch sein. Zum einen kommt es bei der Erhöhung um eine Gleitkkommazahl leicht zu Rundungsfehlern, zum anderen sollte eine Gleitkomma-Schrittweite nicht zusammen mit einer Ganzzahl-Laufvariablen verwendet werden, da die Schrittweite dann sowieso erst gerundet wird.
    Ein anderes Problem tritt auf, wenn bis an die Grenze des Wertebereichs der (Ganzzahl-)Laufvariablen gezählt werden soll. Nach der letzten Erhöhung wird dann der Wertebereich überschritten, und die Laufvariable liegt wieder am unteren Ende des Wertebereichs. Analog gilt das natürlich auch bei negativer Schrittweite, wenn bis zum unteren Ende des Wertebereichs gezählt werden soll.

  6. Mit CONTINUE DO bzw. CONTINUE FOR springt das Programm an das Ende der Schleife und überprüft, ob ein weiterer Durchlauf stattfinden muss oder nicht. EXIT DO bzw. EXIT FOR springt sofort aus der Schleife heraus.
    CONTINUE existiert auch für andere Blockstrukturen, z. B. für den SELECT-Block.

  7. Programmieraufgabe:

    ' Namenseingabe
    DIM AS STRING eingabe, nachname()  ' dynamisches Array deklarieren
    DIM AS INTEGER i = 0               ' Zaehlvariable fuer die Array-Laenge
    PRINT "Geben Sie die Namen ein - Leereingabe beendet das Programm"
    DO
      PRINT "Name"; i+1; ": ";
      INPUT "", eingabe
      IF eingabe <> "" THEN
        REDIM PRESERVE nachname(i)
        nachname(i) = eingabe
        i += 1
      END IF
    LOOP UNTIL eingabe = ""
    PRINT "Sie haben"; UBOUND(nachname)+1; " Namen eingegeben."
    
    ' Namensausgabe
    FOR i AS INTEGER = UBOUND(nachname) TO 0 STEP -1
      PRINT nachname(i)
    NEXT
    SLEEP
    

Fragen zu Kapitel 12

  1. Wiederverwertbarkeit: Ein häufig benötigter Programmteil muss nur einmal geschrieben werden und ist dann beliebig oft aufrufbar. Durch die Verwendung von Parametern können auch unterschiedliche Verläufe im selben Unterprogramm erreicht werden.
    Wartbarkeit: Durch die Reduktion des Quellcodes müssen Veränderungen nur noch an einer Stelle vorgenommen werden statt an mehreren Stellen gleichzeitig. Damit einher geht auch die Verringerung der Fehleranfälligkeit.
    Lokale Verwendung der Variablen: Da Unterprogramme ihren Speicher selbständig verwalten, müssen Sie sich (bei korrekter Umsetzung) im Unterprogramm keine Gedanken über die Variablenbelegungen im Hauptprogramm oder anderen Unterprogrammen machen.
    Rekursion: Unterprogramme können sich auch selbst rekursiv aufrufen. Damit lassen sich Probleme lösen, die auf anderem Weg deutlich schwerer zu bewältigen wären.

  2. Funktionen liefern einen Rückgabewert, Prozeduren dagegen nicht. Dementsprechend bieten sich Prozeduren an, wenn Sie keinesfalls einen Rückgabewert erwarten. Umgekehrt können aber alle Prozeduren auch als Funktionen definiert werden, die dann z. B. eine Erfolgsmeldung (oder Fehlermeldung) zurückgeben.

  3. Mit SHARED definierte Variablen können im Hauptprogramm und allen Unterprogrammen verwendet werden — dies läuft jedoch dem Gedanken der lokalen Variablen zuwider. Die gängige Methode ist, benötigte Variablen als Parameter zu übergeben. Konstanten sind im gesamten Programm zugreifbar.

  4. Parameter können BYVAL (als Wert) oder BYREF (als Referenz) übergeben werden. Ein mit BYVAL übergebener Wert wird in der Prozedur lokal gespeichert. Änderungen an dieser Variablen innerhalb des Unterprogramms haben keine Auswirkungen auf das Hauptprogramm. Dagegen wird mit BYREF eine Variable „direkt“ übergeben. In diesem Fall wirkt sich eine Veränderung der Variablen im Unterprogramm auch auf den Wert der Variablen im Hauptprogramm aus.

Fragen zu Kapitel 13

  1. Zahlendatentypen werden untereinander immer implizit umgewandelt. Wenn Sie z. B. einer Ganzzahl-Variablen den Wert einer Gleitkommazahl zuweisen oder umgekehrt, wird der Wert automatisch an den richtigen Datentyp angepasst. Eine weitere Form der impliziten Umwandlung ist die String-Konkatenation mit & — hierbei werden die Werte ggf. in Strings umgewandelt.
    Abgesehen von der String-Konkatenation findet jedoch keine automatische Umwandlung zwischen Strings und Zahlenwerten statt. Wenn Sie etwa mit den in Zeichenketten vorliegenden Zahlenwerten rechnen oder einen Zahlenwert als String speichern wollen, müssen Sie die notwendigen Umwandlungen explizit durchführen.

  2. ASCII ist eine standardisierte Zeichencode-Tabelle mit 128 Einträgen (die Werte von 0 bis 127). In einem ASCII-codierten Text können Sie sicher sein, dass ein bestimmter Zahlenwert immer dasselbe Zeichen repräsentiert. ANSI ist eine Erweiterung des ASCII-Codes auf 256 Einträge (die Werte von 0 bis 255). Während die Werte von 0 bis 127 mit den ASCII-Werten identisch sind, hängt die Bedeutung der weiteren Zeichen davon ab, welche Codierung verwendet wurde. Ein ANSI-codierter Text (der auch Nicht-ASCII-Zeichen verwendet) kann daher nur dann korrekt gelesen werden, wenn auch bekannt ist, welche Codierung verwendet wurde.

  3. Unten ist ein mögliches Programm aufgeführt. Sondertasten werden aus CHR(255) und einem weiteren Wert zusammengesetzt. Beispiele:
    CHR(8), CHR(9), CHR(13), CHR(27): Backspace, Tab, Return, Esc
    CHR(255, 72), CHR(255, 75), CHR(255, 77), CHR(255, 80): Pfeiltasten oben, links, rechts, unten
    CHR(255, 59), … CHR(255, 68): Funktionstasten F1, … F10
    CHR(255, 71), CHR(255, 79), CHR(255, 73), CHR(255, 81): Pos1, Ende, BildAuf, BildAb

    DIM AS STRING taste
    PRINT "Tastenabfrage - Beenden mit ESC"
    DO
      taste = INKEY
      SELECT CASE LEN(taste)
        CASE 1 : PRINT ASC(taste)
        CASE 2 : PRINT ASC(taste), ASC(taste, 2)
      END SELECT
      SLEEP 1     ' Ich hoffe, Sie haben daran gedacht!
    LOOP UNTIL taste = CHR(27)   ' Escape-Taste
    

Fragen zu Kapitel 14

  1. Zufallszahl innerhalb eines Bereiches
    Für den Fall, dass der Endwert bis kleiner ist als der Startwert von, werden die beiden Werte in der Berechnungsformel einfach vertauscht.

    FUNCTION zufallszahl(von AS INTEGER, bis AS INTEGER) AS INTEGER
      IF bis > von THEN
        RETURN INT(RND*(bis-von+1)) + von
      ELSE
        RETURN INT(RND*(von-bis+1)) + bis   ' vertauschte Werte
      END IF
    END FUNCTION
    

  2. Bit-Manipulation
    Die Varianten mit BITSET() und BITRESET() sind deutlich aufwändiger, allerdings muss man sich an die sehr eleganten Möglichkeiten mit AND, XOR usw. erst gewöhnen. lassen Sie sich am besten immer auch die Binärdarstellung der Zahlen ausgeben, um ein Gefühl für die Funktionsweise zu bekommen.

    DIM AS LONG alt, neu      ' ohne Wertzuweisung; der Wert ist im Programm egal
    
    ' a) niedrigste vier Bit auf 0
    ' mit BITRESET
    neu = alt
    FOR i AS INTEGER = 0 TO 3
      neu = BITRESET(neu, i)
    NEXT
    ' mit AND
    neu = alt AND &hfffffff0  ' sehen Sie sich dazu bei Bedarf die Binaerwerte an
    
    ' b) alle Bitwerte umdrehen
    ' mit BIT(RE)SET
    neu = alt
    FOR i AS INTEGER = 0 TO 31
      neu = IIF(BIT(neu, i), BITRESET(neu, i), BITSET(neu, i))
    NEXT
    ' mit XOR
    neu = alt XOR &hffffffff
    ' mit NOT (sicherlich die beste Loesung fuer diese Aufgabe)
    neu = NOT alt
    
    ' c) niedrigste vier Bitwerte umdrehen
    ' mit BIT(RE)SET
    neu = alt
    FOR i AS INTEGER = 0 TO 3
      neu = IIF(BIT(neu, i), BITRESET(neu, i), BITSET(neu, i))
    NEXT
    ' mit XOR
    neu = alt XOR 15
    

  3. Programmieraufgabe:

    TYPE TFormat
      AS INTEGER fett        : 1
      AS INTEGER unterstrich : 1
      AS INTEGER kursiv      : 1
    END TYPE
    
    DIM AS TFormat formatierung
    formatierung.fett   = 1
    formatierung.kursiv = 1
    
    ' Ist Fettschrift aktiviert?
    IF formatierung.fett THEN PRINT "fett"
    
    ' Ist sowohl Fett- als auch Kursivschrift aktiviert?
    IF formatierung.fett AND formatierung.kursiv THEN
      PRINT "Fett und kursiv? Uebertreiben Sie es bitte nicht!"
    END IF
    
    ' Sind alle drei Formatierungsformen deaktiviert?
    IF formatierung.fett = 0 AND formatierung.kursiv = 0 _
            AND formatierung.unterstrich = 0 THEN
      PRINT "Es wurde keine Formatierung gewaehlt."
    END IF
    SLEEP
    

Fragen zu Kapitel 15

  1. Programm zur Eingabe des Namens

    DIM AS STRING eingabe, vorname, nachname
    DIM AS INTEGER suchposition
    ' Namenseingabe
    PRINT "Geben Sie, durch Leerzeichen getrennt, Ihren Vor- und Nachnamen ein: "
    INPUT "", eingabe
    ' letztes Leerzeichen finden (funktioniert auch bei Eingabe mehrerer Vornamen,
    ' solange der Nachname nicht aus mehreren Einzelwoertern besteht)
    suchposition = INSTRREV(eingabe, " ")
    ' Eingabe aufsplitten und Ergebnis ausgeben
    IF suchposition THEN                               ' Leerzeichen gefunden
      vorname  = LEFT(eingabe, suchposition-1)
      nachname = MID (eingabe, suchposition+1)
      PRINT "Vorname:  " & vorname
      PRINT "Nachname: " & nachname
    ELSE                                               ' kein Leerzeichen gefunden
      PRINT "Die Eingabe war fehlerhaft!"
    END IF
    SLEEP
    

  2. Ersetzung eines Teilstrings

    FUNCTION ersetzeUmlautA(eingabe AS STRING) AS STRING
      ' Bei einer Parameterbergabe BYREF kann direkt mit eingabe gearbeitet werden
      ' mir ist die Beibehaltung der originalen Uebergabe jedoch lieber.
      DIM AS STRING rueckgabe = eingabe
      DIM AS INTEGER suchposition =INSTR(rueckgabe, "ä")
      ' Solange ein Treffer erzielt wurde, ersetze und suche weiter.
      DO WHILE suchposition
        rueckgabe = LEFT(rueckgabe, suchposition-1) & "ae" _
            & MID(rueckgabe, suchposition+1)
        suchposition = INSTR(suchposition+2, rueckgabe, "ä")
      LOOP
      RETURN rueckgabe
    END FUNCTION
    

  3. MIRROR-Funktion

    FUNCTION mirror(eingabe AS STRING) AS STRING
      DIM AS STRING rueckgabe = ""
      FOR i AS INTEGER = LEN(eingabe) TO 1 STEP -1
        rueckgabe &= MID(eingabe, i, 1)
      NEXT
      RETURN rueckgabe
    END FUNCTION
    

Zusatzaufgabe zu Aufgabe 2: Erweitern Sie die Funktion so, dass sie noch einen Such- und einen Ersetzungsstring entgegennimmt und jedes Vorkommen des Suchstrings durch den Ersetzungsstring ersetzt.

Fragen zu Kapitel 16

  1. Es gibt sequentielle Modi und binäre Modi.
    Die sequentiellen Modi unterteilen sich noch einmal auf in den Lesezugriff (FOR INPUT), den Schreibzugriff (FOR OUTPUT) und einem Schreibzugriff zum Anhängen von Daten (FOR APPEND). Der Lesezugriff erfordert eine bereits existierende Datei, wogegen die Schreibzugriffe bei Bedarf eine neue Datei anlegen. Beim Modus FOR OUTPUT werden bereits existierende Daten gelöscht.
    Als binären Modus gibt es das (starre und nicht mehr empfohlene) FOR RANDOM und das universell einsetzbare FOR BINARY. Im binären Modus kann auf die Daten an beliebiger Stelle sowohl lesend als auch schreibend zugegriffen werden.

  2. Es ist guter Programmierstil, alle Ressourcen, die man selbst öffnet, auch selbst wieder zu schließen. Besonders wichtig ist das allerdings innerhalb von Unterprogrammen sowie wenn sehr viele Dateien (kurzzeitig) geöffnet werden sollen. Außerdem werden Daten in der Regel erst dann tatsächlich auf den Datenträger geschrieben, wenn die Datei geschlossen wird (was bei einem Programmabsturz zum Datenverlust führt).

  3. Der einzige wirklich ungeeignete Datentyp ist INTEGER, wenn das Programm sowohl auf 32-Bit- als auch 64-Bit-Systemen laufen soll. Ansonsten sind alle Zahlendatentypen gut geeignet. Zeichenketten fester Länge sind einfacher zu handhaben als Zeichenketten variabler Länge, allerdings lassen sich die Schwierigkeiten hier recht einfach lösen.

  4. Programmieraufgabe:

    DIM AS STRING benutzername
    DIM AS INTEGER dateiNr = FREEFILE
    IF OPEN("letzerBenutzer.txt" FOR INPUT AS #dateiNr) THEN
      ' Fehler aufgetreten - Datei existiert moeglicherweise nicht
      PRINT "Dies scheint Ihr erster Besuch zu sein. Geben Sie Ihren Namen ein."
      INPUT benutzername
      ' Da die Datei nicht zum Lesen geoeffnet werden konnte, ist #dateiNr noch frei.
      OPEN "letzerBenutzer.txt" FOR OUTPUT AS #dateiNr
      PRINT #dateiNr, benutzername
      CLOSE #dateiNr
    ELSE
      ' kein Fehler - Datei konnte korrekt geoeffnet werden
      LINE INPUT #dateiNr, benutzername
      CLOSE #dateiNr
      PRINT "Hallo, " & benutzername & "!"
      PRINT "Wollen Sie Ihren Namen aendern? Geben Sie den neuen Namen ein! "
      PRINT "Wenn Sie den Namen nicht aendern wollen, druecken Sie nur RETURN."
      INPUT benutzername
      IF benutzername <> "" THEN
        ' Die Datei wurde oben geschlossen; #dateiNr ist wieder frei.
        OPEN "letzerBenutzer.txt" FOR OUTPUT AS #dateiNr
        PRINT #dateiNr, benutzername
        CLOSE #dateiNr
      END IF
    END IF
    

Fragen zu Kapitel 17

  1. Absolute Pfadangabe beginnen unter Linux mit einem Slash /. Auch unter Windows werden Pfade, die mit einem Slash oder einem Backslash \ beginnen, als absolute Pfade ausgehend vom aktuellen Laufwerk interpretiert. Pfade, die mit einem doppelten Slash oder Backslash beginnen, werden als (absoluter) Netzwerkpfad behandelt.
    Ansonsten beginnen unter Windows absolute Pfade mit dem Laufwerksbuchstaben gefolgt von einem Doppelpunkt.

  2. Der Rückgabewert -1 bedeutet, dass das Verschieben fehlgeschlagen ist. Mögliche Gründe dafür sind, dass die Quelldatei nicht existiert, dass die Zieldatei bereits existiert oder auf den Zielpfad nicht zugegriffen werden kann, oder dass die Datei wegen fehlenden Rechten nicht verschoben werden kann.

  3. Programmieraufgabe:

    #INCLUDE "vbcompat.bi"
    DIM attribute AS INTEGER, groesse AS STRING, dateiname AS STRING
    dateiname = DIR("*.*", fbNormal OR fbHidden OR fbDirectory, attribute)
    PRINT "Dateiname"; TAB(40); "Dateigroesse"
    DO WHILE LEN(dateiname)             ' solange ein Eintrag gefunden wurde
      ' Kennzeichne versteckte Dateien durch graue Schrift
      IF attribute and fbHidden THEN
        COLOR 8
      ELSE
        COLOR 15
      END IF
      ' ermittle die Dateigroesse (Umwandlung zu einem String)
      groesse = STR(FILELEN(dateiname))
      ' Gib den Dateinamen (bzw. Ordnernamen) aus
      IF attribute and fbDirectory THEN
        PRINT "* "; dateiname
      ELSE
        PRINT "  "; dateiname; TAB(52 - LEN(groesse)); groesse
      END IF
      ' Suche nach dem naechsten Eintrag
      dateiname = DIR(attribute)
    LOOP
    SLEEP
    

Weitere Ideen zum Ausbau des Codes:

  • Sortierung nach Ordner und Dateien (erst alle Ordner, dann alle Dateien)

  • Auslagerung des Codes in eine Prozedur und rekursiver Aufruf zur Anzeige aller Unterordner

Fragen zu Kapitel 18

  1. Countdown:

    DIM AS INTEGER restzeit
    DIM AS DOUBLE zeit
    DIM AS STRING taste
    
    PRINT "Der Countdown laeuft! (Abbruch mit ESC)"
    FOR restzeit = 10 TO 1 STEP -1
      PRINT restzeit; "..."
      zeit = TIMER
      DO
        IF INKEY = CHR(27) THEN EXIT FOR  ' INKEY, da nicht gewartet werden soll
        SLEEP 1
      LOOP UNTIL TIMER - zeit > 1         ' eine Sekunde vergangen
    NEXT
    
    IF restzeit >= 0 THEN
      PRINT "Countdown abgebrochen mit einer Restzeit von"; restzeit; " s."
    ELSE
      PRINT "START!"
    END IF
    SLEEP
    

  2. Weihnachtsberechnungen:

    #INCLUDE "vbcompat.bi"
    DIM AS INTEGER tage, wochen, minuten
    DIM AS DOUBLE heute = NOW         ' heutiges Datum
    ' Berechne das Weihnachtsdatum dieses Jahres
    DIM AS DOUBLE weihnachten = DATESERIAL(YEAR(heute), 12, 25)
    ' Sollte Weihnachten schon vorbei sein, addieren wir ein Jahr hinzu
    IF weihnachten < heute THEN weihnachten = DATEADD("yyyy", 1, weihnachten)
    
    ' Ausgabe der gesuchten Informationen
    PRINT "Bis zum naechsten Weihnachtstag dauert es noch"
    PRINT DATEDIFF("d", heute, weihnachten) & " Tage, bzw."
    PRINT DATEDIFF("w", heute, weihnachten) & " Wochen, bzw."
    PRINT DATEDIFF("n", heute, weihnachten) & " Minuten."
    PRINT "Weihnachten faellt auf einen " & WEEKDAYNAME(WEEKDAY(weihnachten))
    SLEEP
    


Fußnoten:
1) Wenn Sie die Genauigkeitsprobleme bei SINGLE nachprüfen wollen, legen Sie doch einmal zwei SINGLE-Variablen mit den Werten 1 000 000 000 und 999 999 999 an und berechnen Sie die Differenz.


Kapitel 18: Datum und Zeit

Inhaltsverzeichnis

Anhang B: Compiler-Optionen