mbMarket 2.0 Blog-/Webshop-System veröffentlicht

mbMarket Demo Website https://portfolio.mbergmann-sh.de

Ich habe mein eigenes Blog-/Webshopsystem entwickelt: mbMarket.

mbMarket – Portfolio, Blog und Shop in einem

mbMarket ist ein schlankes, zweisprachiges (Deutsch/Englisch) Website-System für Autoren, Entwickler und Bastler. Es stellt Ihre Bücher, Programme und Projekte vor, enthält einen Blog und verkauft Software, Literatur und Hardware im eigenen Shop. Es braucht nur PHP und MySQL/MariaDB und lädt keine externen Bibliotheken, Schriften oder Tracker.

Shop und Warenwirtschaft

  • Artikel als Download, per Post oder zur Abholung, mit Bestand, Versandkosten und Produktfoto
  • Bezahlung per PayPal oder Vorkasse; kostenlose Artikel bekommen automatisch einen Spendenbutton
  • Downloads sofort, nur per E-Mail oder individuell, z. B. als verschlüsseltes Archiv mit persönlichem Schlüssel
  • Testmodus zum gefahrlosen Ausprobieren

Rechnungen

  • PDF-Rechnungen mit allen Pflichtangaben, fortlaufend nummeriert und unveränderlich archiviert
  • Kleinunternehmer oder Regelbesteuerung, Stornorechnungen auf Knopfdruck
  • E-Rechnung (XRechnung) für Geschäftskunden und Export für den Steuerberater

Inhalte

  • Texteditor mit Überschriften, Bildern (links, rechts oder zentriert) und Vorschau vor dem Speichern
  • Blog, Portfolio und bis zu fünf eigene Menüseiten
  • Shop-Artikel und das Kontaktformular lassen sich per Kurz-Markup direkt im Text verlinken

Design

  • Drei mitgelieferte Themes und ein Theme-Creator mit Kopfgrafik- und Logo-Generator
  • Passt sich automatisch an Desktop, Tablet und Handy an
  • Dezente 3D-Menüs: links das Hauptmenü, oben seitenspezifische Dropdowns

Datenschutz und Sicherheit

  • DSGVO-freundlich: nur ein technisch notwendiges Cookie, kein Tracking
  • SSL-Pflicht, Mailversand ausschließlich über SMTP mit Anmeldung
  • Schutz von Fotos durch Wasserzeichen und begrenzte Bildgröße
  • Kundenkonto mit Bestellhistorie, Rechnungen, Datenexport und Kontolöschung

Einfach einrichten

  • Web-Installer richtet die Datenbank automatisch ein
  • Updates per Datei-Upload, die Datenbank wird automatisch angepasst
  • Ausführliches Handbuch mit Screenshots

mbMarket kann man sich in Aktion hier ansehen: https://portfolio.mbergmann-sh.de/

MacBook Pro 2012 auf MacOS Catalina zurücksetzen

MacOS Installer herunterladenDie radikale Lösung zur Wiederherstellung des letzten unterstützten MacOS für dieses Modell

Jüngst ist mir sehr günstig ein MacBook Pro „late 2012“ mit Retina-Display, 256 GB SSD und 8 GB Arbeitsspeicher zugelaufen. Auf dem vorinstallierten macOS „Catalina 10.15.7“ laufen etliche aktuelle Anwendungen nicht oder lassen sich erst gar nicht installieren – also beschloss ich, das Gerät mit dem OpenCore Legacy Patcher auf den aktuellen Stand zu bringen. Großer Fehler!

Das Upgrade auf eine aktuelle Version des Betriebssystems funktioniert mit OpenCore zwar super – aber leider ist die alte Hardware tatsächlich nicht leistungsfähig genug, um neben dem Betriebssystem auch noch etwas anspruchsvollere Anwendungen, wie Affinity oder Presonus Studio ONE reibungslos auszuführen – es kommt zu unangenehmen Seiteneffekten, wie z.B. stark gebremster Ausführungsgeschwindigkeit bis hin zum Einfrieren des Desktops. Nichts ist so ätzend wie eine abgestürzte DAW mitten in einer Recording Session!

Da mein Modell leider mit fest verlötetem Arbeitsspeicher und (aufgrund fest eingeklebter Akkus) null Platz für eine 2,5-Zoll SSD daherkommt, ist Hardwareaufrüstung keine echte Option – ich habe mich deshalb dazu entschlossen, einen radikalen Schnitt zu machen und das MacBook komplett zurück zu setzen.

Die folgende Anleitung bezieht sich zwar explizit auf macOS Catalina, lässt sich aber inhaltlich auch auf andere Versionen übertragen:

1. Backup wichtiger Daten, Downloads und Programme

Es ist nicht die schlechteste Idee, umfangreiche Downloads vor der Neuinstallation von macOS Catalina auf einem ausreichend großen Medium zu sichern – das erspart dir viel Zeit, wenn du dein neu aufgesetztes System einrichtest. Kopiere also den Inhalt des Download-Ordners auf eine externe SSD oder einen weiteren USB-Stick.

Gleiches gilt für bereits installierte, wichtige Programme. Wenn ein Programm per Drag&Drop installiert wurde, ohne dass zusätzliche Systemdateien angelegt werden mussten, dann stehen die Chancen nicht schlecht, dass es unter Catalina auch wieder laufen könnte. Ziehe solche Programme einfach aus dem Programme-Ordner auf dein externes Speichermedium.

Daten in deinem Dokumente-Ordner: Falls du dessen Inhalte auch weiterhin benötigst, solltest du sie ebenfalls sichern.

2. MacOS „Catalina“ besorgen

Zuallererst braucht es dafür natürlich das passende macOS – in diesem Fall ist Catalina 10.15.7 die letzte voll unterstützte Version. Um es herunter zu laden, öffnet man auf dem MacBook eine Shell, lässt sich die verfügbaren unterstützten macOS-Versionen anzeigen und lädt dann das passende Installationsprogramm herunter:

softwareupdate --list-full-installers
softwareupdate --fetch-full-installer --full-installer-version 10.5.7

Nach dem Download findet sich der Installer unter dem Namen „macOS Catalina installieren“ im Ordner Programme.

3. Boot-Stick vorbereiten und testen

Catalina ist die letzte macOS-Version, die noch mit einem 16 GB USB-Stick auskommt. Neuere Versionen benötigen bereits mindestens 32 GB. Es ist aus Geschwindigkeitsgründen ratsam, einen USB3-kompatiblen Stick zu verwenden!

  1. Schließe deinen USB-Stick direkt an deinen Mac an. Starte das Festplatten-Dienstprogramm und lösche den Stick. Wenn du danach das startfähige Installationsprogramm erstellst, wird dieser Stick automatisch im entsprechenden Format gelöscht: Mac OS Extended (Journaled).

  2. Ändere den Namen des Flash-Laufwerks in „MyVolume“. Dies ist der Name, der von den folgenden Terminal-Befehlen erwartet wird.
  3. In der Shell, gib den folgenden Befehl ein und folge den Anweisungen des Programms:
sudo /Applications/Install\ macOS\ Catalina.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume

Sobald der Stick vollständig vorbereitet ist, solltest du ihn testen – das verhindert ein langes Gesicht, falls dein Mac nach dem Plattmachen nicht mehr booten kann…
Starte deinen Mac neu. Halte sofort die Wahltaste (Alt) gedrückt, bis das Boot-Menü erscheint. Wähle den eben erstellten Stick („install macOS Catalina“) und beobachte den Bootvorgang. Fasse dich in Geduld – nach einem Weilchen erscheint ein Screen mit macOS-Dienstprogrammen. Alles ist gut. Schalte deinen Mac aus.

4. Alte OCLP-Installation bereinigen und macOS Catalina neu installieren

Um den Mac komplett platt zu machen, muss man bei vorhandener OpenCore-Installation beim Booten vom neuen Boot-Stick zuerst das NVRAM des Rechners reinitialisieren und danach die komplette interne SSD löschen und neu formatieren. Gehe dazu wie folgt vor:

  1. NVRAM zurücksetzen: Schalte deinen Mac aus. Schalte den Mac bei angeschlossenem Boot-Stick ein und halte dann sofort diese vier Tasten gleichzeitig gedrückt: Wahltaste, Befehlstaste, P und R.

  2. Lasse die Tasten nach etwa 20 Sekunden los. Währenddessen führt der Mac scheinbar einen Neustart durch. Du hörst möglicherweise mehrmals den Startton, oder du siehst das Apple-Logo mehrere Male. Wenn diese Prozedur abgeschlossen ist und dein Mac zuvor mit einer OpenCore-Installation gepatcht war, dann kann es in einigen Fällen passieren, dass er jetzt nur noch über den zuvor erstellten und (hoffentlich) getesteten Boot-Stick gestartet werden kann.

  3. Wähle im Bootmenü die Option „install macOS Catalina“ – hierzu musst du schnell sein, denn das Menü verschwindet nach wenigen Sekunden und versucht, von der internen SSD zu booten! Warte den Boot-Vorgang vom Installationsmedium ab.
  4. Im Installer angekommen, wähle zunächst deine gewünschte Sprache für die Neuinstallation.
  5. Im Fenster macOS-Dienstprogramme, wähle Festplattendienstprogramm. 
  6. Entferne sämtliche Partitionen auf der internen SSD (Partition anwählen, dann Minus-Zeichen im Programm anklicken)
  7. Wähle die interne SSD und klicke auf Löschen. Gib der SSD einen sinnvollen Namen. Als zu installierendes Dateisystem wähle AFS. Beende das Festplattendienstprogramm, sobald alle Aktionen abgeschlossen sind.
  8. Starte das Installationsprogramm und folge den Anweisungen auf dem Bildschirm. Wappne dich mit Geduld – der Installationsprozess kann schon mal bis zu 20 Minuten dauern, bevor du überhaupt dazu kommst, deine Benutzerdaten einzugeben. Unternimm nichts – die Grundinstallation wird automatisch ausgeführt!
  9. Sobald die Grundinstallation abgeschlossen ist und dein Mac automatisch von der internen SSD gestartet wurde, geht es an die Konfiguration deines Gerätes. Folge dazu den Anweisungen auf dem Bildschirm.
  10. Falls es mit deiner Anmeldung per Apple-ID nicht klappen sollte und die Installation bei den AGB hängen bleibt, gehe zurück und klicke auf „AppleID später einrichten“.
  11. Es bleibt Geschmackssache, ob man sich mit Siri unterhalten möchte. In der ex-DDR wäre die Stasi glücklich darüber gewesen, wenn die Bürger sich freiwillig eine Wanze ins Wohnzimmer gestellt hätten. Ich persönlich deaktiviere grundsätzlich die Option „Siri fragen“.
  12. Falls dir Sicherheits-Updates angezeigt werden, so installiere diese (Neustart erforderlich). Die Installation des letzten verfügbaren Sicherheits-Updates für Catalina benötigt bummelig weitere 32 Minuten deiner Lebenszeit.
  13. Falls nicht bereits geschehen, richte deinen Mac nun für den Zugriff per AppleID ein – so kannst du später im App-Store auf deine früher heruntergeladenen Apps zugreifen. In der Regel wird macOS dabei erkennen, ob es sich um für deinen Rechner zugelassene Apps handelt und dir ggf. alternative Versionen anbieten.
  14. Passe die Systemeinstellungen deines Mac an deine Bedürfnisse an, konfiguriere eventuell vorhandene USB-Mäuse, Tasturen usw. und installiere anschließend deine benötigten Programme. Geschafft!

Du findest diesen Artikel hilfreich? Dann freue ich mich über eine kleine finanzielle Anerkennung!

 

 

Bebbo’s amiga-gcc unter debian Linux installieren

Bebbo's amiga-gcc install

Bebbo’s amiga-gcc install

amiga-gcc ist eine C/C++ Cross-Compiler Suite, die Amiga-Programmierern die GNU Compiler Collection gcc und den C-Compiler VBCC, aber auch Werkzeuge wie vamos unter Linux, Windows und macOS zur Verfügung stellt. Die Suite kommt mit zwei vollständigen C-Entwicklungsumgebungen inklusive der Standard-Header und integrierten Erweiterungsbibliotheken, wie z.B. MUI und ReAction, für AmigaOS 1.3 bis AmigaOS 3.2 daher und kann als ein neuer Standard bei der Entwicklung von Programmen auf dem Commodore Amiga (68k) betrachtet werden.

Stefan ‚Bebbo‘ Franke, Autor der Cross-Compiler Suite, stellt amiga-gcc auf seiner Website kostenlos zur Verfügung. Um die Suite unter debian Linux zu installieren, genügen einige wenige Schritte – und ein bisschen Geduld. Auf einem durchschnittlichen PC dauert es mit einer schnellen Internet-Verbindung bummelig 40 Minuten, um Alles herunter zu laden, zu übersetzen und einzurichten.

Vorbereitungen

Zunächst muss das System auf den aktuellen Stand gebracht und einige Abhängigkeiten installiert werden:

sudo apt update && sudo apt upgrade
sudo apt install sudo apt install make wget git gcc g++ lhasa libgmp-dev libmpfr-dev
sudo apt install libmpc-dev flex bison gettext texinfo ncurses-dev autoconf rsync 
sudo apt install libreadline-dev

mkdir ~/amiga

Clonen und Compilieren des Repository

git clone https://franke.ms/git/bebbo/amiga-gcc
cd amiga-gcc
make PREFIX=~/amiga NDK=3.2 update
make PREFIX=~/amiga NDK=3.2 all -j8
make PREFIX=~/amiga NDK=3.2 vbcc -j8
make PREFIX=~/amiga NDK=3.2 vlink -j8

Anpassen der Pfade, Setzen von Aliases

Damit amiga-gcc und seine Komponenten einwandfrei aus jedem Pfad heraus arbeiten, müssen die Pfade dem System bekannt gemacht werden. Bei der Gelegenheit kannst du auch gleich ein paar Aliases setzen, die dir Tipparbeit ersparen. Füge am Ende deiner ~.bashrc das Folgende ein:

# Bebbo's amiga-gcc
export VBCC=~/amiga
export AMIGA_GCC=~/amiga
export PATH=$AMIGA_GCC:$AMIGA_GCC/bin:$PATH
alias agcc='m68k-amigaos-gcc'
alias ag++='m68k-amigaos-g++'
alias astrip='m68k-amigaos-strip'

Mache die Änderungen anschließend deiner Shell bekannt:

source ~/.bashrc

Testen der Installation

Gib das folgende, kurze Listing ein und speichere es unter dem Namen hello.c

#include <dos/dos.h>
#include <proto/dos.h>

int main()
{
  Printf("Hello, World!\n");
  return(RETURN_OK);
}

Compiliere es anschließend mit gcc und vbcc, führe die Resultate im Emulator deiner Wahl in einer Amiga Shell aus:

agcc -Wall -lamiga -s -Os hello.c -o hello_gcc -noixemul
hello_gcc

vc -size -lamiga hello.c -o hello_vc
hello_vc

Programme für AmigaOS 1.3 compilieren

Um Programme für das alte Amiga-Betriebssystem mit Kickstart 1.3 zu übersetzen, musst du für gcc und vbcc die entsprechenden Parameter angeben:

gcc:
m68k-amigaos-gcc test.c -o test_gcc -mcrt=nix13

VBCC:
vc +kick13 test.c -o test_vc

Beachte, dass unser Beispiel-Listing so nicht fehlerfrei compiliert werden kann! Unter AmigaOS 1.3 ist zum Einen die Verzeichnisstruktur der Header-Files anders aufgebaut und zum Anderen gab es zu dieser Zeit die interne Printf()-Routine noch nicht. Du musst das Programm also umschreiben:

#include <stdio.h>  
int main(int argc, char *argv[]) 
{ 
  printf("Hello, World!\n"); 
  return(0); 
}

Du findest diesen Artikel hilfreich? Dann freue ich mich über eine kleine finanzielle Anerkennung!

 

Bebbo’s amiga-gcc Cross Compiler Toolchain ist umgezogen

bebbo's amiga-gcc moved

amiga-gcc ist eine C/C++ Cross-Compiler Suite, die Amiga-Programmierern die GNU Compiler Collection gcc und den C-Compiler VBCC, aber auch Werkzeuge wie vamos unter Linux, Windows und macOS zur Verfügung stellt. Die Suite kommt mit zwei vollständigen C-Entwicklungsumgebungen inklusive der Standard-Header und integrierten Erweiterungsbibliotheken, wie z.B. MUI und ReAction, für AmigaOS 1.3 bis AmigaOS 3.2 daher und kann als ein neuer Standard bei der Entwicklung von Programmen auf dem Commodore Amiga (68k) betrachtet werden.

Stefan ‚Bebbo‘ Franke, genialer Programmierer und Schöpfer der amiga-gcc Cross Compiler Toolchain, hat vor einiger Zeit aus Protest seine Repositorien auf GitHub entfernt:

„As a sign of protest against the refusal to restore 2FA accounts, this account is now empty. Some repos are back on https://franke.ms/git/bebbo“

Amiga-Programmierer, die nicht auf den Komfort und die Geschwindigkeit seiner Werkzeuge verzichten wollen, finden amiga-gcc und weitere Werkzeuge nun auf seiner Homepage:

https://franke.ms/git/bebbo

Die Macher der AmigaOS 3.2 Developer Website haben ebenfalls einen Snapshot von Bebbo’s Werken geforkt und stellen ihn öffentlich zur Verfügung:

https://github.com/AmigaPorts/m68k-amigaos-gcc

Zur Installation des amiga-gcc siehe auch https://mbergmann-sh.de/2025/10/04/bebbos-amiga-gcc-unter-debian-linux-installieren/

ENGLISH

amiga-gcc is a C/C++ cross-compiler suite that provides Amiga programmers with the GNU Compiler Collection gcc and the C compiler VBCC, as well as tools such as vamos under Linux, Windows, and macOS. The suite comes with two complete C development environments, including standard headers and integrated extension libraries such as MUI and ReAction, for AmigaOS 1.3 to AmigaOS 3.2, and can be considered a new standard for developing programs on the Commodore Amiga (68k).

Unfortunately Stefan ‘Bebbo’ Franke, brilliant programmer and creator of the amiga-gcc cross compiler toolchain, removed his repositories from GitHub some time ago in protest against GitHub restrictions.

Nevertheless, the tool chain can still be obtained by the follwing links:

https://franke.ms/git/bebbo

https://github.com/AmigaPorts/m68k-amigaos-gcc

Amiga Version String für mit SAS/C, VBCC und amiga-gcc compilierte Programme

Amiga Version String Macro www.mbergmann-sh.de

Auf dem Commodore Amiga besitzen gut geschrieben Programme einen Versions-String, der über die Shell mit dem Befehl ‚version <progname> file full‘ abgefragt werden werden kann.

Gerade dann, wenn man als C-Programmierer zu Testzwecken verschiedene C-Compiler verwendet kann es hilfreich sein, ein Template zu schreiben, um einen solchen Versions-String automatisch zu generieren. Man kann ein solches Template z. B. unter ‚S:myHeader.txt‘ ablegen und bei Bedarf einfach in den Texteditor laden.

Um einen Versions-String per Macro mit dem Programmnamen, der Versionsnummer, dem aktuellen Compilerdatum und Informationen zum verwendeten Compiler zu erstellen, geht man so vor:

#define PROGNAME "meinProgramm"
#define VERSIONR "1.0.0"
#define COPYRIGHT "Copyright © 2025 by Micha B."

/* construct version tag */
#if defined(__VBCC__)
 #define PROGDATE __AMIGADATE__
 #define COMPILER "VBCC"
#endif
#if defined(__SASC)
 #define PROGDATE __AMIGADATE__
 #define COMPILER "SAS/C v6.58"
#endif
#if defined(__GNUC__)
 #define PROGDATE __DATE__
 #define COMPILER "GCC"
#endif

/* AmigaOS Version String */
const char *version = "\0$VER: " PROGNAME " " VERSIONR " (" PROGDATE ") - (" COMPILER ") - " COPYRIGHT "\n";

Warum der Aufwand?

Natürlich könnte man den Versions-String auch einfach so schreiben:

const char *version ="\o$VER: meinProgramm ((01. April 2025)) - (SAS/C) - Copyright by IchSelbst\n";

Man müsste dann halt den String jedesmal verändern wenn die Programmversion wechselt oder ein anderer C-Compiler benutzt wird. Da ist es schon komfortabler, wenn man sich ein entsprechenes Makro angelegt hat.

Ich selbst verwende bei der Programmentwicklung grundsätzlich Templates, da mir das viel Arbeit für Kommentare usw. erspart. Für meine mit dem ReBuild GUI Designer entworfenen Projekte sieht mein Standard-Template so aus:

/*********************************************
 * Project:  ReBuild F.A.Q                   *
 * Purpose:  show some ReAction GUI features *
 * File:     meinprogramm.c                  *
 * Version:  1.0 RELEASE                     *
 * Author:   Michael Bergmann                *
 * Date:     2025/04/04                      *
 *                                           *
 * GUI made with ReBuild v1.3 beta           *
 * Tested Compilers:                         *
 *           SAS/C v6.58                     *
 *           VBCC v0.91 pre                  *
 *           amiga-gcc v6.5                  *
 * HINT: Might be linked against             *
 *       amiga.lib and reaction.lib!         *
 *********************************************
 */
/* --- Put INCLUDE-Files HERE: --- *

 
#define myDebug TRUE  /* Debugging Texts TRUE or FALSE */
#ifdef myDebug
  #include <stdio.h>  /* used for puts(), printf() */
  char *str_greet = "Hello Stranger!\n We're ready to DEBUG now!\n\n";
  char *str_bye = "\n\nAll Done!\nThis Demo quits now.\nHope you liked it?";
#endif

/* construct version tag */
#define PROGNAME "meinProgramm"
#define VERSIONR "1.0.0"
#define COPYRIGHT "Copyright © 2025 by Micha B."

#if defined(__VBCC__)
  #define PROGDATE __AMIGADATE__
  #define COMPILER "VBCC"
#endif
#if defined(__SASC)
  #define PROGDATE __AMIGADATE__
  #define COMPILER "SAS/C v6.58"
#endif
#if defined(__GNUC__)
  #define PROGDATE __DATE__
  #define COMPILER "GCC"
#endif

/* AmigaOS Version String */
const char *version = "\0$VER: " PROGNAME " " VERSIONR " (" PROGDATE ") - (" COMPILER ") - " COPYRIGHT "\n";

/* Function Proto Types */
int main(void);
void window_main( void );
int setup( void );
void cleanup( void );
void runWindow( Object *window_object, int window_id, struct Menu *menu_strip, struct Gadget *win_gadgets[] );

/* --- Additional global stuff goes HERE: --------------------------------------------------------------- */

/* --- Menu IDs ----------------------------------------------------------------------------------------- */
enum menus
{
  /* Menu "Project"  */
  MN_ITEM1
};

/* --- Requesters ---------------------------------------------------------------------------------------- */
/* 'No OS 3.2' box */
struct EasyStruct warnreq =
{
  sizeof(struct EasyStruct),
  0,
  "Error",
  "Needs AmigaOS 3.2.x\n\nNo Chance to run this program on your machine!",
  "Ok"
};

/* 'requester.class problem' box */
struct EasyStruct noreqclassreq =
{
  sizeof(struct EasyStruct),
  0,
  "Critical Error",
  " requester.class could not be opened.\nThis App refuses to start!",
  "Ok"
};

/* --- Function: setup() --------------------------------------------------------------------------------- */
/* --- Open libs, gadgets, classes and other ressources -------------------------------------------------- */
int setup( void )
{
  if( !(IntuitionBase = (struct IntuitionBase*) OpenLibrary("intuition.library",0L)) ) return 0;
  if( !(GadToolsBase = (struct Library*) OpenLibrary("gadtools.library",0L) ) ) return 0;
  if( !(WindowBase = (struct Library*) OpenLibrary("window.class",0L) ) ) return 0;
  if( !(IconBase = (struct Library*) OpenLibrary("icon.library",0L) ) ) return 0;
  
  /* --- Check for a sufficient OS! --- */
  if( !(LayoutBase = (struct Library*) OpenLibrary("gadgets/layout.gadget",47) ) )
  {
    if (myDebug)
       puts(" FAIL: layout.gadget");
    EasyRequest(NULL, &warnreq, NULL);    
    return 0;
  }
  /* Additional Stuff: Place HERE! */
  
} /* END Function setup() */

/* --- Function: cleanup() -------------------------------------------------------------------------------- */
/* --- Close used ressources and clean up for shutdown ---------------------------------------------------- */
void cleanup( void )
{
   /* Copy content of Function cleanup() HERE: */
} /* END Function cleanup() */
 

/* --- Function: runWindow() ------------------------------------------------------------------------------ */
/* --- IDCMP Event Handling is done here ------------------------------------------------------------------ */
void runWindow( Object *window_object, int window_id, struct Menu *menu_strip, struct Gadget *win_gadgets[] )
{
  struct Window	*main_window = NULL;  
  struct MenuItem *menuitem = NULL;
  LONG myItem = 0;                  /* GT menu identifier     */
  ULONG selected;                   /* used for Content query */
  ULONG status = 0;                 /* used for Status query  */
  ULONG req_result = 0;
  if ( window_object )
  {
    if ( main_window = (struct Window *) RA_OpenWindow( window_object ))
    {
      WORD Code;
      ULONG wait = 0, signal = 0, result = 0, done = FALSE;
      GetAttr( WINDOW_SigMask, window_object, &signal );
      if ( menu_strip)  SetMenuStrip( main_window, menu_strip );
      
      if(myDebug)
        printf("%s\n", str_greet);
        
      while ( !done)
      {
        wait = Wait( signal | SIGBREAKF_CTRL_C );

        if ( wait & SIGBREAKF_CTRL_C )
          done = TRUE;
        else
          while (( result = RA_HandleInput( window_object, &Code )) != WMHI_LASTMSG)
          {
            switch ( result & WMHI_CLASSMASK )
            {
              /* --- WINDOW -------------------------------------------- */
              case WMHI_CLOSEWINDOW:
                if(myDebug)
                  printf("%s\n", str_bye);
                done = TRUE;
                break;
                
              /* --- MENUS -------------------------------------------- */
              case WMHI_MENUPICK:
                if(myDebug)
                  puts("\nMenu pick!");
                 
                menuitem = ItemAddress(menu_strip, result & WMHI_MENUMASK);
                if(myDebug)
                  printf(" ItemAdress = %ld\n", menuitem); 
                  
                /* Check if menuitem equals to Zero. If Zero, an enforcer hit will occour when */
                /* attempting to examine, so we do not allow switching on menuitem == 0!       */
                if(!(menuitem == 0))
                {
                  /* --- ...get menu entries by using GadTools' GTMENUITEM_USERDATA field macro --- */
                  myItem = ((long)GTMENUITEM_USERDATA(ItemAddress(menu_strip, result & WMHI_MENUMASK)));
                  /* --- Identify selected menu entry */
                  switch(myItem)
                  {
                    case MN_ITEM1:
                      if(myDebug)
                        puts("\n Menu 'Project': <ITEM> was selected.");
                      
                      break;
                  } /* END switch(myItem) */ 
                } /* END if(!(menuitem == 0)) */
                break;  /* END WMHI_MENUPICK */

              /* --- GADGETS ----------------------------------------- */
              case WMHI_GADGETUP:
                switch (result & WMHI_GADGETMASK)
                {
                  /* --- Gadget 1 --- */
                  case 
                    break;          					
          				           
                } /* END switch (result & WMHI_GADGETMASK) */
                break;  /* END case WMHI_GADGETUP */
                
              /* --- ICONIFY ----------------------------------------- */
              case WMHI_ICONIFY:
                if ( RA_Iconify( window_object ) )
                  main_window = NULL;
                break;

              /* --- UNICONIFY --------------------------------------- */
              case WMHI_UNICONIFY:
                main_window = RA_OpenWindow( window_object );
                if ( menu_strip)  SetMenuStrip( main_window, menu_strip );
              break;
            } /* END switch ( result & WMHI_CLASSMASK ) */
          } /* END while (( result = RA_HandleInput( window_object, &Code )) != WMHI_LASTMSG) */
      } /* END while ( !done) */
    } /* END if ( main_window = (struct Window *) RA_OpenWindow( window_object )) */
  } /* END if ( window_object ) */
} /* END Function runWindow() */


/* --- Function: window_main() ----------------------------------- */
/* --- All window-specific setup for main Window is done here. --- */ 
/* ---'runWindow()' is called for message handling afterwards. --- */
void window_main( void )
{
  /* We changed ReBuild's menu struct output in order to obey to GadTools Menu handling! */
  /* Replaced: Last NULL argument by (APTR) <Item identifier>                            */

} /* END Function window_main() */


/* --- Main Function ---------------------------------------------------------------------------------- */
int main(void)
{
  if ( setup() )    /* This will satisfy all needs... */
  {
    window_main();  /* ...and open Application's main Window + implement Event handling by running 'runWindow()' */
  }
  cleanup();        /* Clean up stuff and exit */
} /* END main() */

Du findest dieses Tutorial hilfreich? Dann freue ich mich über einen kleinen Obolus für meine IT-Kasse. Besten Dank!  🙂

[Übersicht]

 

Amiga E - Fließkommazahl ausgeben www.mbergmann-sh.de

Schnell-Tipp: Float-Wert unter Amiga E ausgeben

Amiga E und Fließkommazahlen (Float)

Amiga E besitzt keinen eigenen Datentyp für Fließkommazahlen, wie man das von C gewöhnt ist. Fließkommazahlen sind eine Art „Hack“. Sie werden als 32-Bit-Werte mit einfacher Genauigkeit gespeichert, so dass sie auch in die Vorstellung passen, dass alle Variablen 4 Byte groß sind. Da E lose typisiert ist, musst du E durch Zuweisung mit Dezimalpunkt mitteilen, wenn du Floats verwendest. Auch bei der Zuweisung an ganzzahlige Variablen muss mittels eines Ausrufezeichens hinter der Variablen zunächst konvertiert werden – und das macht die Float-Ausdrücke manchmal sehr unübersichtlich.

Im Wesentlichen sind Fließkommazahlen unter E sehr umständlich und E weiß nicht wirklich, dass der Wert, den eine Variable enthält, eine Fließkommazahl sein könnte. Der Wert ist in 4 Bytes gehalten, so dass, wenn du einen Float mit einem anderen Float vergleichst, es auch für die Art, wie sie gespeichert sind funktioniert. Um einen Float-Wert per Write(), WriteF() oder StringF() ausgeben zu können, musst du Umwege gehen und zunächst die Variable mit der Funktion RealF() in einen String umwandeln:

Float per WriteF() ausgeben

Da WriteF() keinen Platzhalter zur Ausgabe von Floats besitzt, musst du eine Float-Wert halt eben als String ausgeben. Hier kommt die Funktion RealF() ins Spiel. Sie wandelt die mit einem Float-Wert initialisierte LONG-Variable um und weist sie einem String zu:

Syntax: RealF( e-string, Float-Wert, Anzahl_Nachkommastellen)

Die Anzahl der Nachkommastellen ist auf maximal 8 Stellen begrenzt. Längere Werte werden gerundet. Der e-string muss vor der Verwendung der Funktion deklariert werden.

Beispiel:

/*
       Fließkommazahlausgabe in Amiga E
*/

PROC main()
  DEF fzahl1:LONG, fzahl2:LONG, summe:LONG
  DEF output[20]:STRING -> e-String-Variable für die Ausgabe

  fzahl1:=47.11345
  fzahl2:=42.81576

  summe:=! fzahl1 + fzahl2 -> summe per ! zu Float umwandeln

  WriteF('fzahl1 = \t\s\n', RealF(output, fzahl1, 5))
  WriteF('fzahl2 = \t\s\n', RealF(output, fzahl2, 5))
  WriteF('summe = \t\s\n', RealF(output, summe, 5))
ENDPROC

Eine Besonderheit des Fließkommazahl-Handlings unter E muss bei der Verwendung von zur Ausgabe bestimmten Floats in Zuweisungen unbedingt beachtet werden: Da der Default-Datentyp einer Variablen grundsätzlich LONG ist und LONG nur durch die Initialisierung mit einer Fließkommazahl Kenntnis darüber erlangt, nun einen Float-Wert zu speichern, muss einer nicht initialisierte Variablen erst mitgeteilt werden, dass sie gleich einen Float-Wert erhalten wird. Dazu dient der Integer/Float-Umwandlungsoperator, welcher durch das Ausrufezeichen ! symbolisiert wird. Er funktioniert bidirektional und wandelt einen Variablenwert, je nach Kontext, Position und Ausgangswert, entweder in einen Float-, oder aber in einen LONG-Wert um.


Ich stecke viel Freizeit und Herzblut in meine Tutorials. Wenn dir meine Arbeit gefällt, dann freue ich mich über eine kleine Spende für meine IT-Kasse. Besten Dank! 🙂

[Übersicht]

Schnelltipp: Bildschirm unter BlitzBasic löschen

Beim Schreiben von Programmen für die Shell steht man manchmal vor dem Problem, den Bildschirm (eigentlich: Den Inhalt der Shell) löschen zu müssen. Die meisten BASIC-Dialekte verfügen zu diesem Zweck über Befehle wie CLS oder CLEARSCR. Bei BlitzBasic/AmiBlitz3 sind die aber bereits zu anderen Zwecken belegt, es existiert kein eigener Befehl für diesen Zweck.

Abhilfe schafft hier der Umweg über die Ausgabe von Escape-Codes. Dazu hat man zwei Möglichkeiten:

Kompatible Variante

Print Chr$($0c)

Diese Variante läuft unter allen BlitzBasic-Versionen einschließlich AmiBlitz3. Sie druckt das ESC-Zeichen zum Löschen des Bildschirms. Wenn der Befehl öfter im Programm gebraucht wird, dann kann man ihn auch in einer „sprechenden“ String-Variable ablegen:

scrclr.s = Chr$($0c)

Der Aufruf erfolgt dann einfach über die Anweisung: Print scrclr.

Diese Lösung funktioniert sogar unter PureBasic 4.0 Amiga:

; ----------------------------------
; File: scrclr.pb
; Bildschirm löschen
; Version für PureBasic v4.0 (Amiga)
; Revision: v1.0.1
; ----------------------------------


; -- Variablen definieren --
scrclr.s = chr($0c)

Print(scrclr)
PrintN( "***********************************")
PrintN( "* Bildschirm loeschen - Variante 1 *")
PrintN( "*************************************")
PrintN("")
PrintN( "Ich loesche nun den Bildschirm...")
PrintN( "Weiter: Linke Maustaste ")
MouseWait()
Print(scrclr)

PrintN( "E N D E ")
PrintN("")

 

Das folgende Listing zeigt die BlitzBasic-Variante:

; -----------------------------------
; File: scrclr1.ab3
; Bildschirm loeschen per ESC-Sequenz
; Version: 1.0 (BlitzBasic/AmiBlitz3)
; -----------------------------------

; Amiga Version String und das Compilerdatum
!version {"ScrClr 1.0 (\\__DATE_GER__)"}

; -- Globale Variablen deklarieren --
bScrClr.s = Chr$($0c)            ; bb2-Variante

; -- ScrClr: bb2-Variante --
NPrint "***********************************"
NPrint "* Bildschirm loeschen - Variante 1 *"
NPrint "*************************************"
NPrint"":NPrint "Ich loesche nun den Bildschirm..."
Print "Weiter: Linke Maustaste "
MouseWait
Print bScrClr
NPrint "E N D E "

End

 

AmiBlitz3-Variante

Selbstverständlich funktioniert die zuvor beschriebene Methode auch unter AmiBlitz3 – allerdings hält der neue Compiler auch eine eigene Möglichkeit vor: Hier muss man aber etwas mehr an Vorbereitung investieren. Zunächst muss AmiBlitz3 für seinen erweiterten Syntax konfiguriert werden: OPTIMIZE 4
Danach gibt man zwei ESC-Sequenzen in Fluchtzeichendarstellung aus:
Print „\\1B[1m“ : Print „\\1Bc“ 

Das folgende Listing zeigt die AmiBlitz3-Variante:

; -----------------------------------
; File: scrclr2.ab3
; Bildschirm loeschen per ESC-Sequenz
; Version: 1.0 (AmiBlitz3-only)
; -----------------------------------
OPTIMIZE 4  ; aktiviere den neuen Syntax von AmiBlitz3
SYNTAX 2

; Amiga Version String und das Compilerdatum
!version {"ScrClr 1.0 (\\__DATE_GER__)"}

; -- Globale Variablen deklarieren --
aScrClr.s = "\\1B[1m" + "\\1Bc"  ; ab3-Variante

; -- ScrClr: ab3-Variante --
NPrint "***********************************"
NPrint "* Bildschirm loeschen - Variante 2 *"
NPrint "*************************************"
NPrint"":NPrint "Ich loesche nun den Bildschirm..."
Print "Weiter: Linke Maustaste "
MouseWait
Print aScrClr
NPrint "E N D E "

End

Diese Variante ist nicht abwärtskompatibel zu BlitzBasic!


[Übersicht]

Archive entpacken unter Linux

Nahezu jeder Anwender, der mit Linux in Berührung kommt, wird ganz schnell über die für Linux typischen Archive stolpern. Zwar verfügen heutige Linux-Desktops meistens auch über ein grafisches Entpacker-Werkzeug, aber oft ist das zu umständlich und man möchte direkt vom Terminal aus arbeiten. Welche Archivarten gibt es also  – und wie packt man sie eigentlich aus?

Entpacken für Eilige

Falls du nach einer Anleitung gesucht hast, um nur schnell einmal etwas zum Dekomprimieren von Archiven unter Linux nachzuschlagen, findest du die wichtigsten Befehle in der folgenden Schnellübersicht:

|-------------------------------------------|
|- Schnellübersicht: Entpacken unter Linux -|
|-------------------------------------------|
Suffix:              Befehl:
 .tgz                tar -xvf archiv.tgz
                     bunzip2 archiv.tgz
 .tar.gz             tar -xvf archiv.tar.gz
                     bunzip2 archiv.tar.gz 
 .bz2                tar -xvfj archiv.bz2 
                     bunzip2 archiv.bz2
 .zip                unzip archiv.zip
 .7z                 7z x archiv.7z
 .lha                lha -x archiv.lha

Tar

Tar-Archive, oft auch als Tarballs bezeichnet, werden durch Konvertierung einer Gruppe von Dateien in ein Archiv erstellt. Diese Archive werden meist für die Sicherung oder Verteilung von Software verwendet. In Linux- und Unix-Systemen kannst du tar-Archive mit dem Befehl tar erstellen. Es unterstützt eine Vielzahl von Kompressionsprogrammen wie gzip, bzip2, lzip, lzma, lzop, xz und compress. Tar wurde ursprünglich für die Erstellung von Archiven zur Speicherung von Dateien auf Magnetbändern entwickelt, weshalb es auch „Tape ARchive“ genannt wird. Tar selbst komprimiert beim Packen allerdings nicht selbst, sondern verlässt sich auf externe Protokolle:

Gzip ist der am weitesten verbreitete Algorithmus zur Komprimierung von tar-Dateien. Konventionell sollte der Name eines mit gzip komprimierten tar-Archivs entweder mit .tar.gz oder .tgz enden.

Kurz gesagt, eine Datei, die auf .tar.gz endet, ist ein mit gzip komprimiertes .tar-Archiv.

Neben der Erstellung neuer Archive kann der tar-Befehl auch für verschiedene andere Operationen verwendet werden, z. B. zum Extrahieren von tar-Archiven, zum Anzeigen einer Liste der im Archiv enthaltenen Dateien und zum Hinzufügen zusätzlicher Dateien zu einem bestehenden Archiv.

In diesem Artikel erfährst du, wie du tar.gz- und tgz-Archive extrahieren (oder entpacken) kannst.

Entpacken einer tar.gz-Datei

Der Befehl tar ist bei den meisten Linux-Distributionen und macOS standardmäßig vorinstalliert.

Um eine tar.gz-Datei zu entpacken, verwendest du die Option –extract (-x) und gibst den Namen der Archivdatei nach der Option f an:

$ tar -xf archiv.tar.gz

Der Befehl tar erkennt automatisch den Komprimierungstyp und entpackt das Archiv. Du kannst den selben Befehl verwenden, um tar-Archive zu extrahieren, die mit anderen Algorithmen komprimiert wurden, z. B. .tar.bz2

Die Option -v macht den tar-Befehl gesprächiger und gibt die Namen der entpackten Dateien auf dem Terminal aus.

$ tar -xvf archiv.tar.gz

Standardmäßig extrahiert tar den Inhalt des Archivs in das aktuelle Arbeitsverzeichnis. Verwende die Option –directory (-C), um ein Verzeichnis anzugeben, in das du das Archiv entpacken möchtest.

Um zum Beispiel den Inhalt des Archivs in das Verzeichnis /home/micha/files zu entpacken, kannst du den folgenden Befehl verwenden:

$ tar -xf archive.tar.gz -C /home/micha/files

Extrahieren bestimmter Dateien aus einer tar.gz-Datei

Um nur bestimmte Dateien aus einem tar.gz-Archiv zu extrahieren, fügst du eine durch Leerzeichen getrennte Liste der Dateinamen hinter dem Archivnamen ein:

$ tar -xf archiv.tar.gz datei1 datei2

Extrahieren bestimmter Verzeichnisse aus einer tar.gz-Datei

Beim Extrahieren von Dateien musst du deren genaue Namen zusammen mit dem Pfad angeben, wie er von tar –list (tar -t) aufgelistet wird. Um ein oder mehrere Verzeichnisse aus einem Archiv zu extrahieren, kannst du den gleichen Prozess wie beim Extrahieren einzelner Dateien befolgen:

tar -xf archive.tar.gz dir1 dir2

Wenn du versuchst, eine Datei zu entpacken, die nicht existiert, erhältst du eine Fehlermeldung ähnlich der folgenden:

$ tar -xf archiv.tar.gz README

tar: README: Not found in archiv
tar: Exiting with failure status due to previous errors

Extrahieren bestimmter Dateien mittels Wildcard

Du kannst auch Dateien aus einer tar.gz-Datei anhand eines Platzhaltermusters (Wildcard) extrahieren, indem du die Option –wildcards verwenden und das Muster in Anführungszeichen setzt, um zu verhindern, dass die Shell es interpretiert.

Um beispielsweise Dateien zu extrahieren, deren Namen auf .js (Javascript-Dateien) enden, würdest du Folgendes verwenden:

$ tar -xf archiv.tar.gz --wildcards '*.js'

Entpacken einer tar.gz-Datei von stdin

Wenn du eine komprimierte tar.gz-Datei extrahieren, indem du das Archiv von stdin liest (normalerweise über eine Pipe), musst du die Dekomprimierungsoption angeben. Die Option, die tar anweist, die Archive durch gzip zu lesen, ist -z.

Im folgenden Beispiel laden wir die Quellen des Raytracing-Programms „Blender“ mit dem Befehl wget herunter und leiten dessen Ausgabe mittels einer Pipe zum Entpacken an den Befehl tar weiter:

$ wget -c https://download.blender.org/source/blender-4.0.2.tar.xz -O - | sudo tar -xz

Auflisten einer tar.gz-Datei

Um den Inhalt einer tar.gz-Datei aufzulisten, rufst du den Befehl tar mit der Option –list (-t) auf:

$ tar -tf archiv.tar.gz

Erweiterte Ausgabe

Wenn du die Option –verbose (-v) hinzufügen, gibt tar zusätzliche Informationen wie Dateiberechtigungen, Eigentümer, Größe und Zeitstempel aus.

$ tar -tvf archiv.tar.gz

Die Ausgabe könnte dann so aussehen:

-rw-r--r-- micha/Downloads       0 2023-12-15 01:19 file1
-rw-r--r-- micha/Downloads       0 2023-12-15 01:19 file2
-rw-r--r-- micha/Downloads       0 2023-12-15 01:19 file3

GNU unzip (gunzip)

Das Programm kommt zum Dekomprimieren von Archiven mit dem Suffix .gz zum Einsatz:

$ gunzip archiv.gz

bzip2

bzip2 ist die moderne Variante von gzip. Im Gegensatz zu gzip benutzt bzip2 einen anderen Algorithmus für das Komprimieren. In manchen Fällen kann es sein das dieser Algorithmus effektiver ist. Es dekomprimiert sowohl Archive mit dem Suffix .gz, als auch mit .bz2:

$ bunzip2 archiv.gz
$ bunzip2 archiv.bz2

 

ZIP

ZIP ist im Windows-Umfeld weit verbreitet und zieht daher natürlich auch seine Spuren in der Linux-Welt. Eventuell musst du das Paket Zip auf deinem System noch nachinstallieren (apt install zip unzip). ZIP-Archive haben den Suffix .zip und werden mit dem Programm unzip dekomprimiert:

$ unzip archiv.zip

7z

7z  zeichnet besonders der hocheffiziente LZMA-Algorithmus aus und kennt viele verschiedene Formate, darunter .zip, .lha, .rar und alle .tar-Formate. Verschlüsselungen mit zeitgemäßen AES-256 Bit sind überhaupt kein Problem. Der Standard-Suffix des Programms ist .7z – es muss allerdings erst nachinstalliert werden:

$ apt install p7zip p7zip-full p7zip-rar

7z-Archiv entpacken:

$ 7z x archiv.7z

7z-Archiv komprimiert erstellen:

$ 7z a archiv.7z Datei1 Ordner2 Datei3

7z-Archiv erstellen und mit einem Passwort schützen:

$ 7z a -p archiv.7z *

7z-Archiv auflisten:

$ 7z l archivname.7

LhA

Das Programm LhA ist vor allem für Anwender nützlich, die auf ihrem Linux-System einen Commodore Amiga emulieren und regelmäßig mit Archiven für diesen Computer zu tun haben. LhA-Archive sind auf dem Amiga der Standart und haben .lha als Suffix. Das Programm muss unter Linux erst nachinstalliert werden:

$ sudo apt install lhsa

LhA-Archive entpacken:

$ lha -x archiv.lha

LhA-Archive auflisten:

$ lha -l archiv.lha

$ lha -v archiv.lha

LhA-Archive testen:

$ lha -t archiv.lha

Du findest dieses Tutorial hilfreich? Dann freue ich mich über einen kleinen Obolus für meine IT-Kasse. Besten Dank!  🙂

[Zurück zur Übersicht]

BlitzBasic mit AmiBlitz3 - ein Tutorial für Amiga-Programmierer www.mbergmann-sh.de

BlitzBasic für Einsteiger – Kontrollstrukturen für den Programmfluss

Warnung: In Bearbeitung!

Dieser Teil des Tutorials ist erst im Entstehen begriffen. Schau doch bitte auch später noch einmal vorbei!

 

Programme unter BlitzBasic/AmiBlitz3 werden „top down“ von oben nach unten abgearbeitet – doch es gibt Ausnahmen zu diesem Paradigma. Einige davon, wie z.B. Prozeduren, hast du bereits kennen gelernt.

Ein Programm, das stur der Reihe nach nur einen Befehl nach dem anderen abarbeitet, ist nicht sehr flexibel und wäre unnötig lang und anstrengend zu schreiben. Was ist, wenn bestimmte Befehlsfolgen mehrfach hintereinander ablaufen sollen? Oder wie sieht’s aus, wenn das Programm unterschiedlich auf verschiedene Bedingungen reagieren muss? Nun, für solche Fälle verfügt BlitzBasic über mächtige Kontroll- strukturen, die Fallunterscheidungen und bedingte Verzweigungen genauso ermöglichen, wie Wiederholungsschleifen – und darum geht es in diesem Teil des Tutorials.

Bedingungen auswerten

Als Programmierer steht man häufig vor dem Problem, dass das Programm mit unterschiedlichen Reaktionen auf bestimmte Bedingungen reagieren muss. BlitzBasic bringt für diesen Zweck verschiedene Kontrollstrukturen mit, die alle auf unterschiedliche Bedingungen hin zu unterschiedlichen Programmteilen verzweigen.

If … Then … Else … EndIf

Die If-Anweisung kommt gleich in mehreren Geschmacksrichtungen:

Variante 1 reagiert auf eine Bedingung und wenn diese zutrifft, so werden die Befehle im Anweisungsblock ausgeführt. Trifft die Bedingung nicht zu, dann wird die if-Anweisung übersprungen und das Programm danach fortgesetzt.

Variante 2 reagiert auf eine Bedingung und wenn diese zutrifft, so werden die Befehle im Anweisungsblock ausgeführt. Trifft die Bedingung nicht zu, dann wird der Anweisungsblock im Else-Teil abgearbeitet und das Programm danach fortgesetzt.

Die Varianten 1 und 2 benötigen das Schlüsselwort EndIf, um abgeschlossen zu sein.

Variante 3 reagiert auf eine Bedingung und wenn diese zutrifft, so wird der darauf folgende Befehl ausgeführt. Trifft die Bedingung nicht zu, dann wird die if-Anweisung übersprungen und das Programm danach fortgesetzt. Diese Variante benötigt nicht das Schlüsselwort EndIf.

Schablone:

; Variante 1
If Bedingung   
  ... Anweisungsblock ...  
EndIf

oder

; Variante 2
If Bedingung
  ... Anweisungsblock ...
Else
  ... Anweisungsblock ...
EndIf

oder

; Variante 3
If Bedingung Then Anweisung

Bedingungen können einfache Vergleiche, aber auch komplizierte, zusammengesetzte Ausdrücke sein. Verdeutlichen wir uns die Zusammenhänge mit dem Listing „if.ab3“:

; ----------------------------
; File: if.ab3
; 3 x if-Anweisung
; Version: 1.0
; ----------------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"if 1.0 (\\__DATE_GER__)"}

DEFTYPE .b myInput       ;Globale Variablen deklarieren

; -- Benutzer-Eingabe --
Print "Gib entweder 0 oder 1 ein: "
myInput.b = Edit(1)
NPrint ""

; -- Auswertung --
; Variante 3
If (myInput.b <> 0) AND (myInput.b <> 1)  Then NPrint "Falsche Zahl!"
If (myInput.b = 0) Then NPrint "Selber Null!"

; Variante 1
If myInput.b = 1
  NPrint "Einmal ist keinmal!"
EndIf

If myInput.b = 0
  NPrint "Eine große Null ist fast schon eine kleine Eins."
  NPrint "Die Eins wolltest du wohl nicht haben?"
EndIf

; Variante 2
If myInput.b = 1
  NPrint "Die Null wolltest du wohl nicht haben?"
Else
  NPrint "Kannst du auch was richtig?"
EndIf

End

Ausgabe:

|- Gültige Zahl: 0 -|
Gib entweder 0 oder 1 ein: 0

Selber Null!
Eine große Null ist fast schon eine kleine Eins.
Die Eins wolltest du wohl nicht haben?
Kannst du auch was richtig?
 |- Gültige Zahl: 1 -|
Gib entweder 0 oder 1 ein: 1

Einmal ist keinmal!
Die Null wolltest du wohl nicht haben?
|- Ungültige Zahl -|
Gib entweder 0 oder 1 ein: 9

Falsche Zahl!
Kannst du auch was richtig?

Programmanalyse:

Unser Programm fordert vom Benutzer die Eingabe von Null oder Eins an (Zeile 16) und gibt dann auf der Basis von Entscheidungen bestimmte Meldungen aus.

  • Zeile 21 prüft mittels einer einzeiligen If-Anweisung und einer aus zwei Teilbedingungen bestehenden Bedingung, ob die eingegebene Ziffer den gestellten Anforderungen entspricht. dabei verwenden wir die logischen Vergleichsoperatoren <> und  And, um die beiden Teilbedingungen zu prüfen und miteinander zu verknüpfen. Das liest sich so:
    WENN die Eingabe ungleich Null UND die Eingabe ungleich Eins ist, DANN sage "Falsche Zahl!"

    Wir werden gleich noch intensiver über Vergleichsoperatoren sprechen – bis dahin merke dir: Der Operator für „ungleich“ ist <>, der Operator für das logische UND ist And.
    Trifft die Bedingung nicht zu, so fährt das Programm in Zeile 22 fort.

  • Zeile 16 liest mittels des Befehls Edit(1) die Benutzereingabe ein. Der Parameter „(1)“ legt fest, dass der Benutzer nur eine Ziffer eingeben darf.
  • Zeile 22 prüft mittels einer einzeiligen If-Anweisung und der Bedingung „ist die Eingabe identisch mit der Ziffer Null“, ob die Null gewählt wurde und gibt in diesem Fall die Meldung „Selber Null!“ aus.  Andernfalls fährt das Programm in Zeile 25 fort. Zur Überprüfung der Bedingung kommt der Gleicheitsoperator „=“ zum Einsatz: If myInput.b = 0
    Merke: Das Gleichheitszeichen ist kontextabhängig entweder der Zuweisungs- operator (z.B. Zahl = 42), oder aber der Gleichheitsoperator.
  • die Zeilen 25 bis 27 stellen ein einfaches If … EndIf-Konstrukt dar, zwischen dem der Anweisungsblock steht. Der besteht hier allerdings aus nur einer Anweisung. Wenn die Bedingung „Ziffer = 1“ erfüllt ist, wird eine Meldung ausgegeben.
  • die Zeilen 29 bis 32 funktionieren identisch. Hier besitzt der Anweisungsblock zwei Anweisungen.
  • Zeilen 35 bis 39 bestehen aus einem If … Else … EndIf-Konstrukt: WENN die Bedingung erfüllt ist, gib eine Meldung aus, ANDERNFALLS gib eine andere Meldung aus.

So richtig rund läuft unser Programm aber noch nicht. Wir möchten, das die Meldung „Kannst du auch was richtig?“ nur bei einer Fehleingabe ausgegeben wird. Eine Lösungsmöglichkeit wäre es, anstatt einer einzeiligen If … Then …-Anweisung eine If … EndIf-Anweisung zu verwenden und beide Ausgaben in den Anweisungsblock zu packen:

...
If (myInput.b <> 0) AND (myInput.b <> 1)
  NPrint "Falsche Zahl!"
  NPrint "Kannst du auch was richtig?"
EndIf
...

Die zweite – kompliziertere – Möglichkeit besteht im Verschachteln von If-Konstrukten.

Verschachtelte If-Anweisungen

Betrachte dir die folgende Programmsequenz mit ineinander verschachtelten If-Anweisungen:

...
If (myInput.b <> 0) AND (myInput.b <> 1)  ; äußere Bedingung
  NPrint "Falsche Zahl!" 
  If myInput.b > 1                        ; innere Bedingung
    NPrint "Kannst du auch was richtig?"
  EndIf
Endif
...

Hier wird zunächst die äußere Bedingung geprüft und nur wenn sie zutrifft, dann wird die innere Bedingung abgearbeitet. Sie benutzt den Vergleichsoperator „größer als“ (>), um festzustellen, ob eine andere Ziffer als 0 oder 1 eingegeben wurde.
Solche Verschachtelungen kann man natürlich noch viel weiter vertiefen. Ich rate dir allerdings dringend davon ab. Tiefe Verschachtelungen werden schnell unübersicht- lich und sind fehleranfällig. Nur in Ausnahmefällen sollte man mehr als zwei Verschachtelungen vornehmen.

Select … Case … End Select

Die Select … Case … End Select-Strukturen sind oft der beste Weg, um einfache Informationen zu verarbeiten, die von einem Benutzer kommen. Für jedes Case können in einem Anweisungsblock mehrere Anweisungen hintereinander abgearbeitet werden.

Schablone:

Select Ausdruck ; kann eine Variable, Konstante oder Rückgabewert sein
  Case Wert 1
    ... Anweisungsblock ...
  Case Wert 2
    ... Anweisungsblock ...
  Case  weitere Werte
    ... usw ...
  Default
    ... Anweisungsblock ...
    (wird ausgeführt, wenn keine andere Bedingung zutraf)
End Select

Das Konstrukt funktioniert folgendermaßen:

  • Dem Schlüsselwort Select wird ein Ausdruck übergeben. Dieser kann der Wert einer Variablen, Konstanten, der Rückgabewert einer Funktion oder eine komplexe Bedingung sein.
  • Dem Schlüsselwort Case wird ein möglicher Wert dieses Ausdrucks als Bedingung übergeben. Diese wird geprüft. Trifft sie zu, dann wird der zugehörige Anweisungsblock abgearbeitet. Trifft sie nicht zu, dann wird das Programm mit dem nächsten Case fortgesetzt.
  • Das Schlüsselwort Default ist optional. Der Anweisungsblock nach Default wird abgearbeitet, wenn keine der vorherigen Bedingungen zutraf.
  • Das Konstrukt wird mit dem Schlüsselwort End Select abgeschlossen.
  • Auch Select-Konstrukte können ineinander verschachtelt werden.

Mit Select … Case-Konstrukten können wir unser Beispiel if.ab3 bequem so umbauen, dass es genau die Ausgaben liefert, die wir erwarten. Das Listing „select.ab3“ verdeutlicht das:

; ------------------------------
; File: select.ab3
; Select ... Case ... End Select
; Version: 1.0
; ------------------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"select 1.0 (\\__DATE_GER__)"}

DEFTYPE .b myInput       ;Globale Variablen deklarieren

; -- Benutzer-Eingabe --
Print "Gib entweder 0 oder 1 ein: "
myInput.b = Edit(1)
NPrint ""

; -- Auswertung --
Select myInput.b
  Case 0
    NPrint "Selber Null!"
    NPrint "Die Eins wolltest du wohl nicht haben?"
    NPrint "Eine große Null ist fast schon eine kleine Eins."
  Case 1
    NPrint "Einmal ist keinmal!"
    NPrint "Die Null wolltest du wohl nicht haben?"
  Default
    NPrint "Falsche Zahl!"
    NPrint "Kannst du auch was richtig?"
End Select

End

Ganz schön kurz und viel übersichtlicher, oder?

Programmanalyse:

  • in Zeile 16 lesen wir die Benutzereingabe ein. Diese wird in den Zeilen 21 bis 31 dann mit einem Select … Case … Default-Konstrukt ausgewertet.
  • Zeile 21 bestimmt, dass die Variable myInput.b durch Select ausgewertet werden soll.
  • Wenn der Wert der Variablen myInput.b gleich 0 ist, so wird das Case in Zeile 21 abgearbeitet.
  • Wenn der Wert der Variablen myInput.b gleich 1 ist, so wird das Case in Zeile 25 abgearbeitet.
  • Wenn der Wert der Variablen myInput.b weder 0, noch 1 ist, dann wird Default in Zeile 28 abgearbeitet.

Select … Case-Konstrukte eigenen sich z.B. ideal, um Benutzermenüs aufzubauen, wie das folgende Listing „menu.ab3“ zeigt:

; ------------------------------
; File: menu.ab3
; Select ... Case ... End Select
; Version: 1.0
; ------------------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"select 1.0 (\\__DATE_GER__)"}

DEFTYPE .b mySelection  ;Globale Variablen deklarieren
DEFTYPE .s myQuit

; -- Titel --
ANFANG :
NPrint "**************************"
NPrint "* Menueauswahl           *"
NPrint "* ============           *"
NPrint "* Pizza            - 1 - *"
NPrint "* Pasta            - 2 - *"
NPrint "* Leberwurst       - 3 - *"
NPrint "*                        *"
NPrint "* Programm beenden - 0 - *"
NPrint "**************************"
; -- Benutzer-Eingabe --
Print "Deine Wahl: "
mySelection.b = Edit(1)
NPrint ""

Select mySelection.b
  Case 0
    Print "Programm beenden? (j/n): "
    myQuit.s = Edit$(1)
    Select myQuit.s
      Case "j"
        End
      Case "J"
        End
      Default
        NPrint "Das Programm wird fortgesetzt..."
        NPrint ""
        Goto ANFANG
    End Select
  Case 1
    NPrint "Du hast Pizza bestellt."
    NPrint "Weiter: Maustaste"
    MouseWait
    NPrint ""
    Goto ANFANG
  Case 2
    NPrint "Du hast Pasta bestellt."
    NPrint "Weiter: Maustaste"
    MouseWait
    Goto ANFANG
  Case 3
    NPrint "Du willst Leberwurst."
    NPrint "Weiter: Maustaste"
    MouseWait
    NPrint ""
    Goto ANFANG
  Default
    NPrint "Falsche Eingabe!"
    NPrint "Weiter: Maustaste"
    MouseWait
    NPrint ""
    Goto ANFANG
End Select

End

Ausgabe:

**************************
* Menueauswahl           *
* ============           *
* Pizza            - 1 - *
* Pasta            - 2 - *
* Leberwurst       - 3 - *
*                        *
* Programm beenden - 0 - *
**************************
Deine Wahl: 0

Programm beenden? (j/n): j

Das Listing verwendet drei bisher unbekannte Elemente: Eine Sprungmarke und die Befehl Goto und MouseWait. Wir gehen nachher nochmal genauer auf die ersten beiden Elemente ein.

Programmanalyse:

Am Anfang wird das Menü ausgegeben und der Benutzer zu einer Eingabe aufgefordert, dann verzweigen wir mittels Select … Case … Default-Konstrukt zur Ausgabe einer passenden Meldung. Beachte das verschachtelte Select … Case … Default-Konstrukt bei der Sicherheitsabfrage zum Verlassen des Programms!

  • die Zeilen 16 bis 28 bilden das Menü und verlangen eine Benutzereingabe.
  • die Sprungmarke in Zeile 16 dient dazu, mittels des Goto-Befehls nach einer Ausgabe das Menü neu zu zeichnen.
  • die Zeilen 31 bis 66 bilden das Herzstück des Programms – hier wird im einem Select … Case … Default-Konstrukt die Benutzereingabe ausgewertet und auf sie reagiert.
  • das Case 0 in Zeile 32 reagiert auf die Eingabe der Ziffer 0, ist somit fürs Beenden des Programms (mit Sicherheitsabfrage) zuständig und fordert in Zeile 34 vom Benutzer eine Bestätigung an, die in der Variablen myQuit.s abgelegt wird.
  • Zeile 35 wertet diese Bestätigung mittels eines weiteren Select … Case … Default-Konstrukts aus und prüft ob ein „j“ oder ein „J“ eingegeben wurde (Zeilen 36 und 38). Trifft eins der Beiden zu, so wird das Programm beendet, andernfalls wird der Default-Zweig in Zeile 40 ausgeführt und das Programm läuft weiter. Dabei springt es in Zeile 43 mit dem Befehl Goto zur Sprungmarke ANFANG. Danach wird das Programm nach der Sprungmarke fortgesetzt, was dazu führt, dass das Menü erneut aufgebaut wird.Diesen Programmabschnitt hätten wir auch mit einem If … Then … EndIf-Konstrukt lösen können, aber wenn wir schonmal dabei sind…
  • Case 1 in Zeile 45 veranlasst die Ausgabe der Pizza-Meldung und springt danach wieder zu ANFANG. Damit der Benutzer nicht sofort wieder mit dem Menü konfrontiert wird, warten wir mit dem Befehl MouseWait, bis er die linke Maustaste gedrückt hat.
  • Die beiden anderen Case-Anweisungen funktionieren synonym zu Case 1.

Schon ganz eindrucksvoll, aber es geht noch besser! Anstatt das Menü immer wieder nacheinander auszugeben wäre es doch schick, wenn es immer ganz oben in der Shell aufgehen würde!? Das geht – man muss der Shell dazu nur die Escape-Sequenz zum Löschen des Bildschirms senden. BlitzBasic verfügt leider nicht über einen entsprechenden Befehl, aber dafür besitzt AmiBlitz3 einen neuen Modus, der das Senden von Escape-Sequenzen erlaubt. Dafür musst du allerdings das Programm mit der Option OPTIMIZE 4 compilieren! Diese Methode ist nicht kompatibel zu BlitzBasic v2.1 und älter. Realisiert wird das Löschen des Bildschirms dann mittels zweier Print-Befehle:

Print "\\1B[1m":Print "\\1Bc"

Hinweis: Bisher haben wir Anweisungen immer nur einzeilig geschrieben – jede Anweisung für sich. BlitzBasic erlaubt es jedoch auch, mehrere eigenständige Anweisung in einer Zeile unterzubringen. Dazu trennt man die Anweisungen durch einen Doppelpunkt.

Es gibt aber auch eine abwärtskompatible Lösung (besten Dank an Rob Cranley):

Print Chr$($0c) ; kompatibel zu BlitzBasic v2.1

Um das Listing um die Möglichkeit, den Bildschirm zu löschen, zu erweitern, gehst du wie folgt vor:

  1. Ändere in Zeile 6 OPTIMIZE 1 auf OPTIMIZE 4 (nur für AmiBlitz3-kompatible Lösung)
  2. füge nach Zeile 16 eine Leerzeile ein und trage dort die Print-Befehle mit den erforderlichen Escape-Sequenzen ein:
    Print „\\1B[1m“:Print „\\1Bc“
    oder
    Print Chr$($0c) (kompatibel zu BlitzBasic v2.1)
  3. Lösche die Zeilen 41 und 42 – ihre Ausgaben wären beim Löschen des Bildschirms ohnehin nicht mehr sichtbar.

Wenn du in einem Programm öfter den Bildschirm löschen möchtest, dann ist es übersichtlicher, die Escape-Sequenz in einer Variablen abzulegen und diese dann bei Bedarf aufzurufen:

shellClr.s = Chr$($0c) ; "sprechende" Variable
Print shellClr         ; Bildschirm löschen

Tipp: Eine Auflistung der auf dem Amiga gebräuchlichen Escape-Sequenzen findest du im AmigaOS-Wiki unter
https://wiki.amigaos.net/wiki/AmigaOS_Manual:_Control_Sequences

Aufgabe:

  • Schreibe ein Programm zur Berechnung der Grundrechenarten, der Potenz zweier Zahlen und lasse es bestimmen, ob die Division zweier Ganzzahlen einen Rest besitzt (Modulo)
  • Verwende für die Berechnungen Funktionen und rufe sie über ein Menü auf.
  • Verwende für die Benutzereingabe der beiden Zahlen ein Statement.
    Tipp: Lokale und globale Variablen, Schlüsselwort SHARED.
  • Lasse vor Aufruf der Funktion zur Division prüfen, ob der Divisor Null ist. Die Berechnung soll nur ausgeführt werden, wenn der Divisor ungleich Null ist – andernfalls soll seine Fehlermeldung ausgegeben und die Funktion nicht ausgeführt werden.
  • Die Modulo-Funktion soll Aussagen darüber treffen, ob eine Division mit oder ohne Rest erfolgt ist und anschließend das Ergebnis der Division zum Vergleich als Fließkommawert ausgeben.
  • Sorge dafür, dass das Programm so lange läuft, bis der Benutzer es beendet.
    Tipp: Sprungmarke, Goto.
  • Achte auf passende Datentypen!

Eine mögliche Lösung findest du hier: shellcalc.ab3

Befehlssequenzen wiederholen

Nehmen wir einmal an, du möchtest fünfzigmal hintereinander einen bestimmten Text ausgeben. Natürlich könntest du dafür fünfzigmal hintereinander eine entsprechende NPrint-Anweisung schreiben – aber das wäre umständlich. Ist aber auch nicht nötig, denn BlitzBasic stellt auch für dieses Problem verschiedene Kontrollstrukturen zur Verfügung, die im Fachjargon Wiederholungsschleifen genannt werden.

Die For … Next Schleife

 

 

Ausblick


[Zurück zur Übersicht] | [zurück] | [vowärts]

BlitzBasic mit AmiBlitz3 - ein Tutorial für Amiga-Programmierer www.mbergmann-sh.de

BlitzBasic für Einsteiger – Grundrechenarten und eigene Funktionen

Rechner sind zum Rechnen da – das ist ihr Kerngeschäft und da bildet der Amiga keine Ausnahme. BlitzBasic/AmiBlitz3 bringt bereits eine große Anzahl an mathematischen Bibliotheksfunktionen für alle Anwendungsfälle mit, die sich bei Bedarf jederzeit um selbstgeschriebene, eigene Funktionen erweitern lassen – und darum geht es in diesem Teil unseres Tutorials.

Die Grundrechenarten

Um einfache Berechnungen anzustellen, bedarf es keiner umfangreichen Mathe-Bibliotheken und -funktionen. BlitzBasic beherrscht ohne weiteren Aufwand Addition (+), Subtraktion (-), Multiplikation (*) und Division (/). Um eine Berechnung durchzuführen genügt es, zwei Zahlen mit dem entsprechenden Operator zu verknüpfen und das Ergebnis einer Variablen zuzuweisen. Beispiele:

summe.w = 47 + 11     ; Addition
summe.w = 50 - 8      ; Subtraktion
produkt.w = 33 * 3    ; Multiplikation
quotient.q = 42 / 3   ; Division
quotient.q = 42 / 0   ; Division durch Null nicht erlaubt!

 

Priorität von Rechenoperationen

Tauchen in einem Term (einer Anweisung) unterschiedliche Rechenoperationen auf, so muss man die Priorität der einzelnen Operationen beachten. Die Hierarchie ist wie folgt aufgebaut:

  1. Potenzierung
  2. Multiplikation und Division („Punktrechnung“)
  3. Addition und Subtraktion („Strichrechnung“)

Beachtet man diese Rangordnung nicht, so kann es zu unerwarteten Seiteneffekten aufgrund falscher Ergebnisse kommen. Beispiel:

meinErgebnis.w = 5 + 5 * 3

Erwartetes Ergebnis: 30
Tatsächliches Ergebnis: 20

Überrascht? Wir haben in diesem Beispiel die Faustregel „Punkt- vor Strichrechnung“ nicht beachtet und denken uns: 5 + 5 = 10, 10 * 3 = 30. Der Computer macht alles richtig und rechnet stattdessen: 5 * 3 = 15, 5 + 15 = 20.

Klammerung von Termen

Um die Priorität einer verketteten Berechnung zu ändern, setzt man runde Klammern entsprechend der gewünschten Priorisierung:

meinErgebnis.w = (5 + 5)* 3 

Erwartetes Ergebnis: 20 
Tatsächliches Ergebnis: 20

Jetzt werden tatsächlich 5 + 5 addiert, bevor die Multiplikation durchgeführt wird.

Die Wahl eines „passenden“ Datentyps

Wenn man das Ergebnis einer Berechnung einer Variablen zuweisen möchte, dann muss man sich bereits im Vorfeld Gedanken darüber machen, welche Art von Zahlen und Zahlengrößen dabei anfallen können. Die Zahl muss ja schließlich auch in die Variable hinein passen. Werden nur sehr kleine Zahlen als Ergebnis erwartet? Kann ich mich auf Ganzzahlen beschränken, oder sind Fließkommazahlen zu erwarten? Wie groß kann ein Ergebnis ausfallen, falls sich die Werte der Berechnung ändern?

Der Datentyp einer Variable bestimmt, ob eine abzulegende Zahl fehlerfrei dargestellt werden kann (siehe Anhang A) oder nicht. Ausschlaggebend ist dabei sein Wertebereich. Ist er zu klein bemessen, dann könnte die Variable überlaufen und falsche Resultate liefern. Bemisst man ihn zu groß, so verschwendet man Ressourcen. Weist man eine Fließkommazahl einem Ganzzahlen-Typen zu, dann gehen die Nachkommastellen verloren, was wiederum zu Fehlern im Programm führen kann. Gerade bei der Division zweier Zahlen muss z.B. regelmäßig mit einem Ergebnis in Form einer Fließkommazahl gerechnet werden. Du siehst, es erfordert Hirnschmalz. Der Leitsatz zur Wahl eines passenden Datentyps könnte etwa so lauten: „So viel wie nötig, aber so wenig wie möglich“.

Das Listing „calc1.ab3“ führt die Grundrechenarten vor und zeigt kleine Unterschiede zwischen Berechnungen mit Ganzzahlen und solchen mit Fließkommazahlen auf:

; -----------------------
; File:calc1.ab3
; Grundrechenarten
; Version: 1.0
; -----------------------
OPTIMIZE 1 ; Optimierung für Amiga mit Turbokarte/A1200
SYNTAX 1   ; strenge Syntaxprüfung

; Amiga Version String und das Compilerdatum
!version {"calc1 1.0 (\\__DATE_GER__)"}

; Variablen deklarieren
DEFTYPE .l l_zahl1,l_zahl2, l_result   ; Datentyp: Long
DEFTYPE .f f_zahl1, f_zahl2, f_result  ; Datentyp: Float

; Variablen initialisieren
l_zahl1 = 3201
l_zahl2 = 500
f_zahl1 = 3201.5
f_zahl2 = 500.2

; Addition
l_result = l_zahl1 + l_zahl2
f_result = l_zahl1 + l_zahl2
NPrint "Addition (integer) Ergebnis          : " + Str$(l_result)
NPrint "Addition (float) Ergebnis            : " + Str$(f_result)
NPrint "Addition (float-Werte) Ergebnis      : " + Str$(f_zahl1 + f_zahl2)
NPrint ""

; Subtraktion
l_result = l_zahl1 - l_zahl2
f_result = l_zahl1 - l_zahl2
NPrint "Subtraktion (integer) Ergebnis       : " + Str$(l_result)
NPrint "Subtraktion (float) Ergebnis         : " + Str$(f_result)
NPrint "Subtraktion (float-Werte) Ergebnis   : " + Str$(f_zahl1 - f_zahl2)
NPrint ""

; Multiplikation
l_result = l_zahl1 * l_zahl2
f_result = l_zahl1 * l_zahl2
NPrint "Multiplikation (integer) Ergebnis    : " + Str$(l_result)
NPrint "Multiplikation (float) Ergebnis      : " + Str$(f_result)
NPrint "Multiplikation (float-Werte) Ergebnis: " + Str$(f_zahl1 * f_zahl2)
NPrint ""

; Multiplikation
l_result = l_zahl1 / l_zahl2
f_result = l_zahl1 / l_zahl2
NPrint "Division (integer) Ergebnis          : " + Str$(l_result)
NPrint "Division (float) Ergebnis            : " + Str$(f_result)
NPrint "Division (float-Werte) Ergebnis      : " + Str$(f_zahl1 / f_zahl2)
NPrint ""

End

Ausgabe:

Addition (integer) Ergebnis : 3701
Addition (float) Ergebnis : 3701
Addition (float-Werte) Ergebnis : 3701.699

Subtraktion (integer) Ergebnis : 2701
Subtraktion (float) Ergebnis : 2701
Subtraktion (float-Werte) Ergebnis : 2701.3

Multiplikation (integer) Ergebnis : 1600500
Multiplikation (float) Ergebnis : 1600500
Multiplikation (float-Werte) Ergebnis: 1601390

Division (integer) Ergebnis : 6
Division (float) Ergebnis : 6.401999
Division (float-Werte) Ergebnis : 6.400439

Programmanalyse:

  • in den Zeilen 13 bis 20 deklarieren und initialisieren wir, wie gewohnt, die Variablen des Programms. Verwendet werden Ganzzahlenwerte (Integerwerte) vom Typ Long und große Fließkommawerte vom Typ Float.
  • die Zeilen 23 bis 28 führen Additionen durch und geben anschließend deren Summen aus. Dabei addiert die Berechnung in Zeile 24 zwei Ganzzahlen und weist die Summe einer Float-Variablen zu. Beachte: Die Ausgabe zeigt trotzdem lediglich eine Ganzzahl an.
  • die Ausgabe in Zeile 27 erfolgt mit einer direkt an NPrint übergebenen, mittels Str$ in einen String umgewandelten Berechnung mit zwei Fließkommazahlen. Die Ausgabe zeigt hier, wie erwartet, eine Fließkommazahl als Ergebnis an.
  • Analog zum Gesagten erfolgt in den Zeilen 31 bis 38 die Subtraktion, in den Zeilen 39 bis 44 die Multiplikation und in den Zeilen 46 bis 52 die Division.

Erinnerung: Print und NPrint erwarten bei der Verwendung von aus Zahlen und Strings zusammengesetzten Parametern, dass die Zahlen in einem zusammengesetzten String ebenfalls als String vorliegen! Darum wandeln wir solche Zahlen per Str$ um.

Anstatt des „großen“ Typs Float hätten wir hier auch den kleineren Typ Quick verwenden können. Beide Typen unterscheiden sich allerdings sowohl in ihrem Wertebereich, als auch in ihrer Genauigkeit bezüglich ihrer Anzahl an Nachkommastellen. Das kurze Listing „floatcalc.ab3“ veranschaulicht die Unterschiede beider Datentypen:

; --------------------------
; File:floatcalc.ab3
; Division von Float-Zahlen
; Version: 1.0
; --------------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"floatcalc 1.0 (\\__DATE_GER__)"}

; Variablen deklarieren
DEFTYPE .q q_zahl1,q_zahl2, q_result   ; Datentyp: Quick
DEFTYPE .f f_zahl1, f_zahl2, f_result  ; Datentyp: Float

; Variablen initialisieren
q_zahl1 = 3201.5123456
q_zahl2 =  500.7123478
f_zahl1 = 3201.5123456
f_zahl2 =  500.7123456

; Division
q_result = q_zahl1 / q_zahl2
f_result = f_zahl1 / f_zahl2
NPrint "Division (quick) Ergebnis: " + Str$(q_result)
NPrint "Division (float) Ergebnis: " + Str$(f_result)
NPrint ""

End

 

Ausgabe:

Division (quick) Ergebnis: 6.3939
Division (float) Ergebnis: 6.393915

Zum Ablauf dieses Programms gibt es nicht viel zu sagen. Wir initialisieren die Variablen jeweils mit den gleichen Inhalten. Quick liefert als Ergebnis der Berechnung eine Zahl mit 4 Nachkommastellen, bei Float sind es 6.

Aufgaben:

  1. Versuche spaßeshalber einmal, alle Variablen mit zusätzlichen Nachkommastellen zu initialisieren und compiliere das Programm erneut. Beachte die Fehlermeldung des Compilers.
  2. Initialisiere eine Quick-Variable mit dem Wert 37900001.5123456 und compiliere das Programm. Was sagt der Compiler?
  3. Schreibe ein Programm mit einer Quick-Variablen und weise dieser den Wert 32767.1 zu. Gib diesen Wert aus. Addiere nun 1.0 zum Wert und gib die Variable erneut aus. Was passiert?

Modulo – der Rest einer Division

Modulo ist eine mathematische Operation, die den Rest einer ganzzahligen Division bezeichnet. Programmierer verwenden diese Operation z.B. zur Prüfung, ob eine ganzzahligen Division einen Nachkommaanteil besitzt. Ist dies der Fall, so liefert die Operation den Wert 1 zurück, andernfalls 0. BlitzBasic verwendet den Befehl MOD, um die Operation durchzuführen. Beispiele:

rest.w = 4 MOD 3  ; Ergebnis: 1, es gibt einen Rest
rest.w = 4 MOD 2  ; Ergebnis: 0, es gibt keinen Rest

Das Listing „modulus.ab3“ demonstriert die Verwendung:

; -----------------------
; File: modulus.ab3
; Rest einer Division
; Version: 1.0
; -----------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"modulus 1.0 (\\__DATE_GER__)"}

DEFTYPE .w modulus, zahl1, zahl2

; Werte eingeben
NPrint "Rest einer Division testen"
NPrint "Gib zwei Ganzzahlen ein!"
Print "Erste Zahl:  "
zahl1 = Edit(10)
Print "Zweite Zahl: "
zahl2 = Edit(10)
NPrint""

; Modulus-Operation
modulus = zahl1 MOD zahl2

; Auswertung
If (modulus = 0)
  NPrint "Die Division hat keinen Rest."
Else
  NPrint "Die Division weist einen Rest auf."
EndIf

End

Programmanalyse:

  • in den Zeilen 16 bis 20 fragen wir den Benutzer nach der Eingabe von zwei Ganzzahlen und lesen diese jeweils in den Zeilen 18 und 20 ein. Dazu verwenden wir die Funktion Edit(), das Gegenstück für Zahlen zu Edit$() (Letzteres haben wir bereits in anderen Programmen verwendet, um Zeichenketten einzulesen). Der Parameter in Klammern gibt dabei die Anzahl zulässiger Ziffern an – hier sind es 10.
  • Zeile 24 führt die Modulus-Operation aus und weist deren Resultat der Variablen modulus zu.
  • in den Zeilen 27 bis 31 untersuchen wir mit einer if…else…endif-Abfrage, ob ein Rest vorliegt und geben eine entsprechende Meldung aus. WENN der Wert der Variablen modulus Null beträgt, so gibt es keinen Rest – ANDERNFALLS existiert ein Rest (später mehr zu vergleichenden Abfragen).

Potenzierung

Eine Potenz  ist das Ergebnis des Potenzierens (der Exponentiation), das wie das Multiplizieren seinem Ursprung nach eine abkürzende Schreibweise für eine wiederholte mathematische Rechenoperation ist. Wie beim Multiplizieren ein Summand wiederholt addiert wird, so wird beim Potenzieren ein Faktor wiederholt multipliziert. Dabei heißt die Zahl, die zu multiplizieren ist, Basis. Wie oft diese Basis als Faktor auftritt, wird durch den Exponenten angegeben. Man schreibt:

BlitzBasic verwendet zur Potenzierung das Zeichen „^“. Auch diese Rechenoperation schauen wir uns an einem Beispiel an – „power.ab3“:

; -----------------------
; File: power.ab3
; Potenzieren
; Version: 1.0
; -----------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"power 1.0 (\\__DATE_GER__)"}

DEFTYPE .w potenz, basis, faktor

; Werte eingeben
NPrint "Potenzieren:"
NPrint "Gib zwei Ganzzahlen ein!"
Print "Basis:  "
basis = Edit(5)
Print "Faktor: "
faktor = Edit(5)
NPrint""

; Potenz-Operation
potenz = basis ^ faktor

; Ausgabe
NPrint "Die Potenz von " + Str$(basis) + " hoch " + Str$(faktor) + " ist " + Str$(potenz)

End

Ausgabe:

Potenzieren:
Gib zwei Ganzzahlen ein!
Basis: 2 
Faktor: 16 

Die Potenz von 2 hoch 16 ist 65536

Zum Programmaufbau gibt es nicht viel zu sagen. Den Datentyp der Variablen haben wir mit Long Word (.l) angegeben – Word (.w) wäre zu klein, da beim Potenzieren sehr große Zahlen entstehen können. Den Ablauf dieses Programms solltest du inzwischen ohne weitere Erläuterung verstehen können.

 

Prozeduren, Statements und Funktionen

Eine Prozedur (Procedure) ist eine Möglichkeit, Routinen (wie z.B. wiederkehrende Berechnungen) in eigenständige Teile des Programms zu „verpacken“. Sobald eine Routine in eine Prozedur verpackt ist, kann sie von deinem Hauptcode aus aufgerufen werden. Parameter können übergeben werden, und ein optionaler Wert wird an den Hauptcode zurückgegeben. Da eine Prozedur ihren eigenen lokalen Variablenbereich enthält, kannst du sicher sein, dass keine deiner Haupt- oder globalen Variablen durch den Aufruf der Prozedur verändert wird. Diese Eigenschaft bedeutet, dass Prozeduren sehr portabel sind, d.h. sie können in andere Programme portiert werden, ohne dass es zu Konflikten mit dort verwendeten Variablennamen gibt.

Einfache Prozeduren geben keine Werte an ihren Aufrufer zurück und werden als Statements bezeichnet. Prozeduren, die Werte zurück liefern, heißen unter BlitzBasic Funktionen. Procedure ist also lediglich der Oberbegriff für Beides.

Funktionen und Statements unter BlitzBasic haben die folgenden Eigenschaften:

  • Die Anzahl der Parameter ist auf 6 begrenzt.
  • Gosub und Goto zu Labels außerhalb des Codes einer Prozedur sind streng verboten.
  •  Alle lokalen Variablen, die innerhalb einer Prozedur verwendet werden, werden bei jedem Aufruf neu initialisiert. Ihre Werte sind also nur so lange gültig, wie die Prozedur läuft.

Statements

Ein Statement definiert man nach folgender Schablone:

Statement Name{Parameter}
   ... Anweisungen ...
End Statement

Der Name des Statements ist frei wählbar. Einem Statement können innerhalb der geschweiften Klammern bis zu 6 Parameter mitgegeben werden, die Statement-intern weiterverarbeitet werden. Einem Statement muss nicht zwingend ein Parameter übergeben werden – in dem Fall bleiben die geschweiften Klammern leer. Variablen innerhalb eines Statements sind stets lokal, d.h. nur innerhalb des Statements gültig – es sei denn, man macht sie mit dem Schlüsselwort SHARED allgemein zugänglich.

Das Listing „statement.ab3“ verwendet ein Statement, um den Fakultätsfaktor einer Zahl fünfmal auszugeben.
Beachte, dass bei Verwendung der strengen Syntaxprüfung mittels OPTION 1 sämtliche Variablen im Vorfeld deklariert werden müssen! Außerdem müssen sie in diesem Fall mit ihrem vollen Namen inklusive der Extension (hier: .l) angesprochen werden.

; ----------------------------
; File: statement.ab3
; Funktion ohne Rueckgabewert
; Version: 1.0
; ----------------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"statement 1.0 (\\__DATE_GER__)"}

DEFTYPE .l k             ; globale Variable: k

; ein Statement definieren
Statement fact{n.l}
  DEFTYPE .l a, k        ; Lokale Variablen: a, k

  a.l = 1
  For k.l = 2 To n.l
    a.l = a.l * k.l
  Next

  NPrint a
End Statement

; Hauptteil
For k.l = 1 To 5
  fact{k.l}      ; Aufruf des Statements
Next

End

Ausgabe:

1
2
6
24
120

Programmanalyse:

  • Zeile 12 deklariert die im Hauptteil (außerhalb des Statements) verwendete globale Variable als Long Word.
  • die Zeilen 15 bis 24 definieren das Statement fact{n.l}.
  • Zeile 15 ist der Kopf des Statements, bestehend aus dem Schlüsselwort Statement, dem Namen (fact) und der Parameterliste in geschweiften Klammern. Sie enthält nur einen Parameter: n. Beachte, dass aufgrund der strengen Syntaxprüfung der Parameter n mit einem Datentyp deklariert werden muss.
  • die Zeilen 16 bis 23 sind der Rumpf des Statements, der in Zeile 24 mit der Anweisung End Statement abgeschlossen wird.
  • Zeile 16 deklariert die lokalen Variablen des Statements als Long Word.
  • Zeile 18 initialisiert die lokale Variable a.l mit dem Wert 1.
  • in den Zeilen 19 bis 21 wird mittels einer for…next-Schleife (später mehr dazu!) der Wert der Variablen a.l mit sich selbst multipliziert.
  • der berechnete Wert wird in Zeile 23 ausgegeben.
  • Auch im Hauptteil ab Zeile 27 wird eine for…next-Schleife verwendet. Sie läuft fünf mal und ruft bei jedem Durchlauf einmal das Statement fact{n.l} auf. Würde man also den Zähler der Schleife erhöhen, so würden dementsprechend mehr Zahlen ausgegeben werden. Versuche es spaßeshalber: Ersetze die Zahl 5 in Zeile 27 mit der Zahl 10 und compiliere und starte das Programm erneut.
    Anmerkung: Das geht nur bis maximal zur Zahl 16 gut – ab 17 läuft die Variable über!

Funktionen

Im Gegensatz zu Statements liefern Funktionen über die Anweisung Function Return einen weiter verarbeitbaren Wert an den Aufrufer zurück. Eine Funktion definiert man nach folgender Schablone:

Funktion Name{Parameter}    
   ... Anweisungen ... 
   Function Return Variable  ; Rückgabewert
End Funktion

Unser Beispiel für ein Statement lässt sich ohne Aufwand auch als Funktion realisieren. Dabei geben wir die berechnete Zahl nicht gleich direkt aus, sondern übergeben sie dem Aufrufer im Hauptteil des Programms. Das Listing „function.ab3“ zeigt, wie es funktioniert:

; ----------------------------
; File: function.ab3
; Funktion mit Rueckgabewert
; Version: 1.0
; ----------------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"function 1.0 (\\__DATE_GER__)"}

DEFTYPE .l k       ;Globale Variablen deklarieren

; Funktion definieren
Function fact{n.l}
  DEFTYPE .l a, k  ; Lokale Variablen deklarieren

  a.l = 1
  For k.l = 2 To n.l
    a.l = a.l * k.l
  Next

  Function Return a.l ; Rueckgabewert
End Function

; Hauptteil
For k.l = 1 To 5
  NPrint fact{k.l}  ; Rueckgabewert drucken
Next

End

Die Programmlogik ist nahezu identisch zum vorherigen Listing – allerdings geben wir nun in Zeile 23 den berechneten Wert mit Function Return an den Aufrufer (NPrint) in Zeile 28 zurück.

Zugriff auf globale Variablen

Manchmal ist es notwendig, dass eine Prozedur auf eine oder mehrere globale Variablen eines Programms zugreifen kann. Zu diesem Zweck erlaubt der SHARED-Befehl, bestimmte Variablen innerhalb einer Prozedur als globale Variablen zu behandelt. Dazu ein schnelles Beispiel:

Statement example{}
  SHARED k
  NPrint k
End Statement
For k=1 To 5
  example{}
Next

Per SHARED-Befehl teilst du dem Compiler mit, dass die Prozedur die globale Variable k verwenden soll, anstatt eine lokale Variable k zu erzeugen. Versuche dasselbe Programm ohne den SHARED-Befehl: Jetzt ist k innerhalb der Prozedur eine lokale Variable und wird daher jedes Mal 0 sein, wenn die Prozedur aufgerufen wird.

Rekursion

Der von den lokalen Variablen einer Prozedur verwendete Speicher ist nicht nur für die eigentliche Prozedur, sondern für jeden Aufruf der Prozedur reserviert. Jedes Mal, wenn eine Prozedur aufgerufen wird, wird ein neuer Speicherblock zugewiesen und erst nach Beendigung der Prozedur wieder freigegeben. Dies hat zur Folge, dass eine Prozedur sich selbst aufrufen kann, ohne ihre eigenen lokalen Variablen zu beschädigen. Das ermöglicht ein Phänomen, das als Rekursion bekannt ist. Hier ist eine neue Version der faktoriellen Funktion, die Rekursion verwendet:

; ----------------------------
; File: recursion.ab3
; rekursiver Funktionsaufruf
; Version: 1.0
; ----------------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"recursion 1.0 (\\__DATE_GER__)"}

DEFTYPE .l n       ;Globale Variablen deklarieren

; Funktion definieren
Function fact{n.l}
  If n.l > 2 Then n.l = n.l * fact{n.l - 1} ; Rekursiver Aufruf
  Function Return n.l ; Rueckgabewert
End Function

; Hauptteil
For n.l = 1 To 5
  NPrint fact{n.l}  ; Rueckgabewert drucken
Next

End

Dieses Beispiel beruht auf dem Konzept, dass die Berechnung der Fakultät einer Zahl eigentlich die Zahl, multipliziert mit dem Faktor von Eins weniger als die Zahl darstellt (Zahl – 1, vergl. Zeile 16).

Zusammenfassung

In diesem Teil des Tutorials haben wir gelernt

  • wie man unter BlitzBasic die Grundrechenarten und den Modulo-Operator verwendet und wie man Zahlen potenziert.
  • wie die Hierarchie der Rechenoperationen aufgebaut ist und wie man sie durch Klammerung verändern kann.
  • auf was es bei der Wahl des Datentyps für eine Variable ankommt.
  • wie man einen numerischen Wert mittels des Edit()-Befehls einliest.
  • das bei Zuweisung einer Fließkommavariablen an eine Ganzzahlvariable der Nachkommaanteil verloren geht.
  • das Prozeduren ein Oberbegriff für Statements und Funktionen sind.
  • das man Prozeduren maximal 6 Parameter übergeben kann.
  • das Statements im Gegensatz zu Funktionen keinen Rückgabewert liefern.
  • das Variablen innerhalb von Prozeduren lokaler Natur sind und nur innerhalb der jeweiligen Prozedur Gültigkeit besitzen.
  • Das lokale Variablen für andere Prozeduren und das Hauptprogramm nicht sichtbar sind, mittels des Schlüsselwortes SHARED aber sichtbar gemacht werden können.
  • das Prozeduren sich selbst rekursiv aufrufen können, wobei die Werte lokaler Variablen erhalten bleiben.

Ausblick

Im kommenden Teil des Tutorials werden wir uns Kontrollstrukturen zur Fallunterscheidung und Verzweigung, Wiederholungsschleifen, Wahrheitswerte und Vergleichsoperatoren näher betrachten.


[Zurück zur Übersicht] | [zurück] | [vowärts]