Die Suche ergab 233 Treffer

von bbock
06.05.2023, 18:03
Forum: Programmierung
Thema: Übungen zu "Programmieren in C mit dem z88dk"
Antworten: 11
Zugriffe: 5743

Übungen zu "Programmieren in C mit dem z88dk"

Übung 1

Der Benutzer soll die Adresse einer Person eingeben. Diese wird anschließend in einer Textdatei namens adressen.txt gespeichert. Nach dem Speichern erscheint eine Abfrage, ob eine weitere Adresse eingegeben werden soll. Wird die Abfrage bejaht, dann wird eine weitere Adresse eingegeben, die dann an die Textdatei adressen.txt angehängt wird. Wird sie verneint, dann wird das Programm beendet.

Einzugebende Felder:
Name
Vorname
Straße
PLZ
Wohnort
Telefon
E-Mail

Darstellung in der Textdatei (Beispiel):
Hugo Hirsch
Schloßallee 3
67118 Frankfurt
069 12345678
hugo.hirsch@pappnase.de
von bbock
15.04.2023, 19:06
Forum: Programmierung
Thema: Programmieren in C mit dem z88dk
Antworten: 20
Zugriffe: 9467

16. Arbeiten mit Dateien 3

Diesmal wollen wir eine C-Quelldatei bearbeiten: die Zeilenkommentare mit dem doppelten Schrägstrich sollen in gewöhnliche C-Kommentare mit Schrägstrich-Stern am Anfang und Stern-Schrägstrich am Ende des Kommentars umgewandelt werden, denn nicht alle C-Compiler unterstützen die Zeilenkommentare. Wie können wir dabei vorgehen?

Wir benötigen zwei Dateien: die Originaldatei mit den Zeilenkommentaren und die umgewandelte, die nur noch normale Kommentaren enthält. Die Originaldatei öffnen wir zum Lesen, die Ausgabedatei zum Schreiben. Wir lesen die Originaldatei Zeile für Zeile ein. Dann suchen wir nach "//". Finden wir die beiden Schrägstriche, dann ersetzen wir die beiden Zeichen durch "/*"; danach suchen wir das Zeilenende und fügen davor ein "*/" ein. Finden wir die beiden Schrägstriche nicht, dann schreiben wir die Zeile unverändert in die Ausgabedatei.

Wir beginnen mit ein paar Variablen in der Funktion main():

Code: Alles auswählen

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

#define FILENAMELENGTH  20
#define BUFSIZE        512

/* Main
 */
int main(void) {
    char filename[FILENAMELENGTH];
    char filenameOut[FILENAMELENGTH];
    FILE *fp;
    FILE *fpOut;
    char buf[BUFSIZE];
    char *cp;
    size_t len;
Wir brauchen die beiden char-Arrays für die Dateinamen, die wir während der Programmausführung über die Tastatur eingeben werden. Wir sehen eine Länge von 20 Zeichen vor, was für CP/M-Dateinamen ausreicht. Dann brauchen wir noch zwei FILE pointer und einen Puffer für eine eingelesene Zeile. Der Puffer ist mit 512 Zeichen ausreichend groß bemessen, dass man gängige C-Programme damit bearbeiten können sollte. Natürlich ist C nicht formatgebunden, d.h. man könnte sogar ein komplettes C-Programm in eine einzige Zeile schreiben. Für solche Fälle ist unser Programm nicht ausgelegt; in der Praxis sollten Zeilen mit mehr als 512 Zeichen eher nicht vorkommen. Schließlich haben wir noch einen char pointer und eine Längen-Variable vom Typ size_t. Die Typdeklaration von size_t finden wir im include-Verzeichnis des Compilers unter sys/types.h: size_t entspricht hier einem unsigned int. Variablen dieses Typs können also Werte im Bereich von 0 bis 65535 annehmen.

Es folgt die Eingabe der Dateinamen und das Öffnen der Dateien:

Code: Alles auswählen

    printf("Zu konvertierende Datei: ");
    scanf("%s", filename);
    
    fp = fopen(filename, "r");
    if (fp == NULL) {
        printf("Fehler: Datei %s nicht gefunden!\n", filename);
        return -1;
    }

    printf("Ausgabedatei: ");
    scanf("%s", filenameOut);
    
    fpOut = fopen(filenameOut, "w");
    if (fpOut == NULL) {
        printf("Fehler: Konnte Ausgabedatei %s nicht oeffnen!\n", filenameOut);
        fclose(fp);
        return -1;
    }
Wenn eine der Dateien nicht geöffnet werden kann, z.B. weil wir einen ungültigen Dateinamen eingegeben haben, dann liefert fopen() den Wert NULL zurück und wir geben eine Fehlermeldung aus und beenden das Programm mit dem Fehlercode -1.

Wir lesen der Zeilen in einer while-Schleife ein:

Code: Alles auswählen

    // Zeilen lesen
    while (fgets(buf, sizeof buf, fp) != NULL) {
Die bereits bekannte Funktion fgets() liest die Zeile in den Puffer buf ein und begrenzt das Einlesen sicherheitshalber auf die Größe des Puffers. Solange die Funktion einen Wert ungleich NULL zurückliefert, haben wir eine weitere Zeile einlesen können, die wir anschließend verarbeiten.

Zunächst ermitteln wir die Länge der Zeile:

Code: Alles auswählen

        len = strlen(buf);
Dann suchen wir den doppelten Schrägstrich mit der String-Funktion strstr(). Diese sucht in einem String nach dem Vorkommen eines Such-Strings. Wenn der Suchstring gefunden wird, dann liefert strstr() einen Pointer auf das erste gefundene Zeichen zurück, also den ersten Schrägstrich. Den wollen wir nicht verändern und inkrementieren den Pointer, damit er auf den zweiten Schrägstrich zeigt. Mit *cp = '*'; überschreiben wir den Schrägstrich mit einem Stern.

Code: Alles auswählen

        // doppelten Schraegstrich suchen
        cp = strstr(buf, "//");
        if (cp != NULL) {
            ++cp; // zeigt jetzt auf den zweiten Schraegstrich
            *cp = '*'; // "//" in "/*" umwandeln
Jetzt müssen wir noch das Zeilenende finden um dort die Kommentarende-Kennung */ einzufügen.

Code: Alles auswählen

            // Zeilenende suchen
            cp = strchr(cp, '\n');
            if (cp == NULL) {
                printf("Oh - das sollte nicht passieren: Zeilenende nicht gefunden!\n");
                return -1;
            }
            
            // Zeilenende mit " */" ueberschreiben
            *cp++ = ' ';
            *cp++ = '*';
            *cp   = '/';
Wir suchen das Newline-Zeichen mit der String-Funktion strchr(), da wir diesmal nur nach einem einzelnen Zeichen suchen wollen. Mit strstr() hätte man das auch machen können, allerdings wäre das weniger effizient.

Finden wir kein Newline-Zeichen, dann stimmt etwas ganz und gar nicht und wir steigen mit einer Fehlermeldung aus. Andernfalls überschreiben wir das Newline-Zeichen und die beiden folgenden Zeichen mit Leerzeichen, Stern und Schrägstricht, also " */". Damit zerstören wir das Zeilenende, das wir gleich aber wieder anhängen. Das tun wir in jedem Fall, also auch dann, wenn wir keinen doppelten Schrägstrich gefunden haben. Das ist nötig, weil fgets() nur das Newline-Zeichen einliest, nicht aber das Carriage-Return-Zeichen.

Vorher passen wir aber noch die Zeilenlänge an, die ja jetzt um drei eingefügte Zeichen angewachsen ist:

Code: Alles auswählen

            // Laenge anpassen: wir haben jetzt 3 Zeichen mehr
            len += 3;
        }
Jetzt schreiben wir das Zeilenende und das String-Abschlusszeichen in den Puffer und schreiben den Puffer mit der evtl. geänderte Zeile in die Ausgabedatei. Dazu verwenden wir die Funktion fwrite():

Code: Alles auswählen

        // CR, LF und String-Abschluss
        buf[--len] = '\r';
        buf[++len] = '\n';
        buf[++len] = '\0';
        
        // Zeile schreiben
        fwrite(buf, 1, len, fpOut);
Am Ende, also nach der Schleife, müssen wir noch die beiden Dateien schließen und liefern den Fehlercode 0 an das Betriebssystem zurück.

Code: Alles auswählen

    }

    fclose(fp);
    fclose(fpOut);

    return 0;
}
In file_io_3.zip befindet sich das komplette Programm file3.c und eine Quelldatei mit Zeilenkommentaren zum Ausprobieren (pointer1.c).

file_io_3.zip
file3.c und pointer1.c
(1.22 KiB) 219-mal heruntergeladen

Es sei noch anzumerken, dass das Programm in dieser Form noch nicht perfekt ist. Die Limitierung auf eine Zeilenlänge von max. 512 Zeichen (eigentlich noch etwas weniger wegen der Zeilenende- und Stringende-Zeichen) wurde bereits erwähnt. Außerdem erkennt das Programm nicht, wenn sich die doppelten Schrägstriche in einem String-Literal befinden, d.h. wenn im Code so etwas wie s = "abc // xyz"; vorkommt, dann würde hier der doppelte Schrägstrich fälschlicherweise als Kommentar erkannt. Selbiges gilt für doppelte Schrägstriche in Präprozessor-Direktiven. Natürlich kann man das Programm erweitern, damit es solche Fälle erkennt, aber das würde hier den Rahmen sprengen.
von bbock
07.04.2023, 10:41
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 33930

Re: Fragen und Antworten zum C-Kurs

Paul hat geschrieben: 07.04.2023, 09:52 ...
Was geschieht mit buf? Wird beim Aufruf der gesamte Buffer kopiert? Das wäre dann ja vergleichsweise langsam, erklärt aber warum der buf vom main nicht verändert werden kann.
Ist es dann sinnvoll die resultierende Variable mit dem gleichen Namen zu versehen? Intuitiv hätte ich lbuf verwendet was eine lokale Kopie von buf andeutet.
In Kapitel 10.2 habe ich geschrieben, dass der Name eines Arrays auch ein Pointer ist. buf ist also nicht der Wert des Arrays mit all seinen 100 Characters, sondern der Pointer, der auf den ersten Character zeigt. buf ist also eine Adresse, nicht der ganze Array. Beim Aufruf einer Funktion mit buf als Parameter wird also nicht der ganze Buffer kopiert, sondern nur die zwei Bytes des Zeigers.
Paul hat geschrieben: 07.04.2023, 09:52 Bei Parsetoken wiederum übergibst du die lokale Kopie von buf und gibst ihr den Namen start, was ja wiederum eine lokale kopie von buf ist, genauso wie field und delimiter.
Es wird die Adresse des Arrays übergeben, keine Kopie. Die Änderungen mit *start=... in parseToken ändern den Inhalt des Arrays buf in main.
Paul hat geschrieben: 07.04.2023, 09:52 du veränderst start, was erstmal keine Auswirkung hat weil es ja eine lokale Kopie ist. Am ende erhöhst du noch start um eins und gibst es als rückgabewert zurück.
Spannender wird es mit field. Das veränderst du und gibst am ende noch das Stringende Zeichen hinzu. Aber ist es nicht genau wie start eine lokale Kopie?
Delimiter wird nicht verändert, also stellt sich auch keine Frage bezüglich der Auswirkung auf die aufrufende Parseline.
Diese Fragen sollten mit o.g. Erklärungen beantwortet sein.
von bbock
07.04.2023, 10:30
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 33930

Re: Fragen und Antworten zum C-Kurs

Paul hat geschrieben: 07.04.2023, 09:43 ...
- die Größe des Buffers. Du wählst 100, die längste Zeile ist 86. Man könnte auch 255 verwenden. Aber was ist mit Buffer von 20? Wie kann ich in Etappen einlesen wenn mein Puffer kleiner ist als eine ganze Zeile?
Ja, man könnte auch 255 verwenden. Oder 2000. Wenn man mit festen Feldgrößen arbeitet, muss man immer überlegen, wie groß der zu speichernde Inhalt eigentlich sein kann. Der Buffer muss groß genug sein, damit die längste Zeile hineinpasst, oder man muss vorsehen eine Zeile in mehreren Etappen einzulesen, wie du das nennst. Das macht das Einlesen komplizierter, weil man im ersten Anlauf nicht alle Felder bekommt und Felder über zwei Lesevorgänge geteilt sein können.
Ist die maximale Länge einer Zeile bekannt, z.B. weil man weiß, aus welcher Quelle die CSV-Datei stammt, dann kann man den Buffer entsprechend dimensionieren. Man muss aber beachten, dass man auch Platz für den Zeilenumbruch und das String-Ende-Zeichen vorsieht.
Paul hat geschrieben: 07.04.2023, 09:43 - Die Größe der t_line objekte. Für den Staat reservierst du 60, für die Stadt 50, sind zusammen 110, mehr als jede Zeile.
Das sind für die Programmierung wichtige Werte aber woher bekomme ich die?
Die 60 und die 50 sind zusammen zwar mehr als jede Zeile lang ist, aber wir haben ja viele Zeilen, und so kann in einer Staat besonders lang sein und in einer anderen die Stadt. Man muss jeweils genug Platz für das längste Feld reservieren.
von bbock
31.03.2023, 13:53
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 33930

Re: Fragen und Antworten zum C-Kurs

Ja, Kurt, da gibt's noch so einiges... ;)

Was ist denn mit schombi? Ist der noch dabei?
von bbock
31.03.2023, 13:46
Forum: Programmierung
Thema: Programmieren in C mit dem z88dk
Antworten: 20
Zugriffe: 9467

15. Arbeiten mit Dateien 2

Diesmal wollen wir eine Textdatei einlesen, verarbeiten und das Ergebnis in eine zweite Textdatei ausgeben. Die Eingabedatei ist eine CSV-Datei, allerdings mit Semikolon als Trennzeichen statt Komma. Die Datei enthält Informationen über die Hauptstädte der Erde; die Daten stammen aus Wikipedia. Zur Vermeidung unnötiger Probleme habe ich sie um Sonderzeichen bereinigt.

Die Datei hat den Namen hauptst.csv und enthält die Felder Staat, Hauptstadt, Einwohner, Stand und Quelle. Wir wollen daraus eine Datei erstellen, die zu jeder Hauptstadt die Anzahl ihrer Einwohner im Format <Hauptstadt>: <Anzahl Einwohner> ausgibt.

Dazu öffnen wir zunächst die Eingabe- und die Ausgabedatei. Dann lesen wir in einer Schleife Zeile für Zeile ein, verarbeiten die Zeile und schreiben das Ergebnis in die Ausgabedatei. Zuletzt schließen wir beide Dateien.

Beginnen wir mit der Funktion main(), wo wir zuerst die benötigten Variablen deklarieren:

Code: Alles auswählen

/* Main
 */
int main(void) {
    char filename[13];
    char filenameOut[13];
    FILE *fp;
    FILE *fpOut;
    char buf[100];
    t_line line;
Die beiden Arrays für die Dateinamen sind 13 Zeichen lang; das genügt für 8.3-Dateinamen plus das String-Ende-Zeichen '\0'. Wenn man noch eine Laufwerksangabe etc. unterbringen möchte, dann muss man die Arrays natürlich entsprechend größer dimensionieren. Es folgen zwei file pointer, ein Puffer für die aktuell eingelesene Zeile und eine Variable für das Ergebnis der verarbeiteten Zeile mit den einzelnen Feldern. Letztere definieren wir folgendermaßen mit typedef:

Code: Alles auswählen

typedef struct {
    char staat[60];
    char hauptstadt[50];
    char einwohner[12];
    char stand[10];
    char quelle[40];
} t_line;
Alle Felder sind als char-Arrays definiert, weil eine Textdatei nun einmal Textzeichen enthält. Nun könnte man argumentieren, dass die Anzahl der Einwohner doch eigentlich eine Zahl ist und man besser einen Zahlentyp verwenden sollte. Aber das brauchen wir nur dann, wenn wir damit Berechnungen anstellen wollen, wie z.B. die Summe der Einwohner aller Hauptstädte zu ermitteln. Für unseren Anwendungsfall brauchen wir das nicht, also verzichten wir auf eine Typumwandlung.

Wir öffnen die Eingabedatei:

Code: Alles auswählen

    strcpy(filename, "hauptst.csv");
    fp = fopen(filename, "r");
    if (fp == NULL) {
        printf("Fehler: Eingabedatei %s nicht gefunden!\n", filename);
        return -1;
    }
Wir kopieren die String-Konstante "hauptst.csv" in die Variable filename, weil wir sie weiter unten nochmal benötigen und wir den Dateinamen nicht doppelt in den Quellcode schreiben wollen. Dafür gibt es auch noch andere Lösungen. Die Datei wird wieder mit fopen geöffnet, hier im Modus "r" (für "read"). Der resultierende file pointer fp kann in der Folge nur für Leseoperationen verwendet werden. Liefert fopen einen NULL-Pointer zurück, dann ist das Öffnen fehlgeschlagen, was wir mit einer Fehlermeldung und dem Rücksprung ins Betriebssystem mit dem Fehlercode -1 quittieren.

Die Ausgabedatei wird ganz ähnlich geöffnet:

Code: Alles auswählen

    strcpy(filenameOut, "extrakt.csv");
    fpOut = fopen(filenameOut, "w");
    if (fpOut == NULL) {
        printf("Fehler: Konnte Ausgabedatei %s nicht oeffnen!\n", filenameOut);
        fclose(fp);
        return -1;
    }
Hier verwenden wir den Modus "w" (für "write") und öffnen die Datei damit zum Schreiben. Ergo sind mit fpOut nur Schreiboperationen möglich. Die Fehlerbehandlung ist ähnlich der bei der Eingabedatei, allerdings dürfen wir hier nicht vergessen die Eingabedatei zu schließen, bevor wir zum Betriebssystem zurückkehren, denn diese ist zu diesem Zeitpunkt ja bereits geöffnet.

Die Schleife für das Lesen der Zeilen sieht folgendermaßen aus:

Code: Alles auswählen

    // Zeilen lesen
    while (fgets(buf, sizeof buf, fp) != NULL) {
        printf(".");

        // Zeile in Felder aufteilen
        parseLine(buf, &line);

        // Hauptstadt und Einwohner schreiben
        fprintf(fpOut, "%s: %s\r", line.hauptstadt, line.einwohner);
    }

    putchar('\n');
Die Funktion fgets liest eine ganze Zeile ein. Die maximale Länge der Zeile wird dabei durch die Länge des Puffers buf begrenzt, die wir mit sizeof buf ermitteln. Der Compiler-Operator sizeof ermittelt die Größe einer Variablen zur Compile-Zeit. Hier liefert er den Wert 100. fgets liefert einfach den ersten Parameter, also buf, zurück, es sei denn, wir sind am Dateiende angekommen: dann liefert fgets NULL zurück, was wir als Schleifenende-Kriterium verwenden.

Die Ausgabe eines Punkts pro Zeile soll nur etwas visuelles Feedback für das Lesen der Zeilen geben. Die Funktion parseLine soll dann die Zeile in die Felder Staat, Hauptstadt, Einwohner, Stand und Quelle aufteilen und das Ergebnis in die Struktur line schreiben. Der typedef für die Struktur sieht so aus:

Code: Alles auswählen

typedef struct {
    char staat[60];
    char hauptstadt[50];
    char einwohner[12];
    char stand[10];
    char quelle[40];
} t_line;
Schließlich werden die Felder Hauptstadt und Einwohner via fprintf in die Ausgabedatei geschrieben. Das putchar soll nur einen Zeilenumbruch nach den vielen Punkten ausgeben, damit der Cursor nach Beendigung des Programms wieder am Zeilenanfang steht, wo dann der Prompt des Betriebssystems erscheint.

Am Ende von main() müssen wir noch die Dateien schließen:

Code: Alles auswählen

    fclose(fp);
    fclose(fpOut);

    return 0;
}
Jetzt fehlt noch die Funktion parseLine zum Aufteilen der eingelesenen Zeilen:

Code: Alles auswählen

/* Zerlegt eine Zeile in Felder.
 */
char *parseLine(char *buf, t_line *line) {
    int i;
    int len;

    initLine(line);

    buf = parseToken(buf, line->staat, ';');
    buf = parseToken(buf, line->hauptstadt, ';');
    buf = parseToken(buf, line->einwohner, ';');
    buf = parseToken(buf, line->stand, ';');
    buf = parseToken(buf, line->quelle, ';');
}
Zunächst wird die Struktur line initialisiert. Beim Aufruf von parseLine hatten wir die Adresse der Struktur mit &line als zweiten Parameter übergeben. Wir haben jetzt also einen Pointer auf die Struktur. Den übergeben wir an initLine, um die Struktur zu initialisieren. initLine setzt nur das erste Zeichen aller char-Arrays auf '\0' und erzeugt damit leere Strings. Nota bene: das erste Zeichen eines Arrays ist in C immer das Zeichen mit dem Index 0!

Code: Alles auswählen

void initLine(t_line *line) {
    line->staat[0] = '\0';
    line->hauptstadt[0] = '\0';
    line->einwohner[0] = '\0';
    line->stand[0] = '\0';
    line->quelle[0] = '\0';
}
So, die Felder sind leer, also können wir sie jetzt mit den Daten der aktuellen Zeile füllen. Das erledigt die Funktion parseToken, die den char-Pointer buf sukzessive weiterschaltet, bis alle fünf Felder eingelesen sind. buf zeigt also immer auf den Anfang des nächsten Feldes im Puffer. Man beachte, dass die Parameter-Variable buf eine eigene lokale Variable der Funktion parseLine ist. Wenn wir sie verändern, dann wird die gleichnamige Variable buf der Funktion main() nicht verändert!

Code: Alles auswählen

/* Liest ein Feld bis zum Trennzeichen.
 * Gibt einen Zeiger auf das naechste Feld zurueck.
 */
char *parseToken(char *start, char *field, char delimiter) {
    while (*start != delimiter && *start != '\0') {
        *field++ = *start++;
    }
    *field = '\0';

    return ++start;
}
In der while-Schleife wird Zeichen für Zeichen aus dem Puffer in die jeweilige Feld-Variable aus der Struktur line kopiert, bis wir das Trennzeichen oder das String-Ende finden. Das Kopieren könnten wir mit der Anweisung *field = *start durchführen; anschließend müssten wir noch die beiden Pointer weiterschalten. Die hier gezeigte Variante führt die Zuweisung durch und schaltet anschließend die Pointer weiter - alles auf einmal. Nach der Schleife zeigt start auf das Trennzeichen, daher das Inkrementieren vor dem return.

Einige Dinge hätte man auch anders machen können. So wäre es z.B. möglich ohne Kopieren der Strings auszukommen, indem man '\0'-Zeichen in den Puffer schreibt, wo die Trennzeichen sind. Oder man hätte mit strtok arbeiten können - eine String-Funktion, die Tokens anhand von Trennzeichen ermittelt. Diese hat allerdings den Nachteil, dass sie aufeinanderfolgende Trennzeichen wie eines behandelt. Das taugt nichts, wenn Felder auch leer sein können.

Auch die Struktur wäre nicht unbedingt nötig. Man könnte stattdessen mit separaten Array-Variablen arbeiten, die nicht zu einer Struktur zusammengefasst sind. Dann hätte man allerdings auch alle Felder einzeln als Parameter an die Unterfunktionen übermitteln müssen - oder man hätte die Arrays gleich global gemacht. Letzteres ist aber wegen des "information hiding"-Prinzips in der Software-Entwicklung nicht wünschenswert.

Hier ist noch das fertige C-Programm und die Eingabedatei zum Download:

file_io_2.zip
file2.c und hauptst.csv
(4.85 KiB) 222-mal heruntergeladen

Das ganze wird kompiliert mit

Code: Alles auswählen

zcc +cpm -subtype=pcw80 file2.c -o file2.com
von bbock
27.03.2023, 11:28
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 33930

Re: Fragen und Antworten zum C-Kurs

Die fehlenden Klammern werden nicht als Fehler moniert, weil der Funktionsname auch ohne die Klammern syntaktisch korrekt ist: es handelt sich dann um einen function pointer, also einen Zeiger auf eine Funktion. Ohne Zuweisung macht sie wenig Sinn, ist aber auch nicht falsch.
von bbock
26.03.2023, 13:39
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 33930

Re: Fragen und Antworten zum C-Kurs

Paul hat geschrieben: 25.03.2023, 11:33 ...Bitte verrate noch wie ich die ganzen Form3 Sachen übersetzen kann.
zcc +cpm -subtype=pcw80 -lndos -create-app forms3tst.c -o forms3tst.com
hat nicht funktioniert
forms3tst.c:83:15: fatal error: Too few arguments to call to function 'initForms'
Compilation aborted
Das Forms3-Projekt ist für Fortgeschrittene. :D Es besteht aus mehreren C-Dateien, du hast aber nur eine kompiliert. Der korrekte Aufruf ist:

zcc +cpm -subtype=pcw80 -lm -DAMALLOC -create-app forms3.c input2.c convert.c box.c forms3tst.c -o f3test.com

Hier wird eine Liste von C-Dateien als Quelle übergeben, die Mathematik-Bibliothek eingebunden und die automatische, dynamische Speicherverwaltung aktiviert. Du kannst forms3tst.c durch forms3tst_2.c ersetzen, dann mit z.B. f3test2.com als Ausgabedatei, um das zweite Beispiel zu übersetzen.
Paul hat geschrieben: 25.03.2023, 11:33 Davon abgesehen hatte ich das CLS schon versucht. Dazu fand ich ein funktionierendes Beispiel:

Code: Alles auswählen

/*
 *	Test the Native Console of the Amstrad PCW
 *
 *	23/3/2021 RobertK (based on ansivt52.c by Stefano)
 *
	Manual Index:
	https://www.manualslib.com/manual/1002846/Amstrad-Pcw8256.html?page=170#manual

	Character Set:
	https://www.manualslib.com/manual/1002846/Amstrad-Pcw8256.html?page=283#manual

	Terminal codes:
	https://www.manualslib.com/manual/1002846/Amstrad-Pcw8256.html?page=308#manual
 */

#include <stdio.h>

void main()
{
    int x;

    printf("Before CLS\n");
    printf("\x1b\x45");	 // ESC E -> Clear screen
    printf("\x1b\x48");  // ESC H -> Home
    printf("If this is the first thing you can read, CLS is OK.\n");

    /* Draw an X */
    for (x = 0; x < 11; x++) {
		// ESC Y r c -> gotoxy
		printf("\x1b\x59%c%c*",32 + 10 + x, 32 + 20 + x);
		printf("\x1b\x59%c%c*",32 + 20 - x, 32 + 20 + x);
    }
}
Übersetzt mit zcc +cpm -subtype=pcw80 -lndos -create-app screen.c -o screen.com

Ich habe dann versucht das selber umzusetzen:

Code: Alles auswählen

#include <stdio.h>

void gotoXY(unsigned int x, unsigned int y) {
    putchar(27);        // ESC
    putchar('Y');       // cursor to position
    putchar(y + 32);    // row + 32
    putchar(x + 32);    // column + 32
}
void cLS() {
    printf("Before CLS\n");
    printf("\x1b\x45");	 // ESC E -> Clear screen
    printf("\x1b\x48");  // ESC H -> Home
    printf("Test\n");
 /* putchar(27);       // ESC
    putchar(69);       // cls
    putchar(27);       // ESC
    putchar(72);       // home
*/
}

int main(void) {
	cLS;
    putchar('A'); // oder printf("%s", "O");
    gotoXY(1,1);
    putchar('B'); // oder printf("%s", "O");
    gotoXY(2, 2);
    putchar('C'); // oder printf("%s", "O");
    gotoXY(14, 14);
    putchar('O'); // oder printf("%s", "O");
    gotoXY(80, 26);
    putchar('Z'); // oder printf("%s", "O");
}
Übersetzt mit zcc +cpm -subtype=pcw80 -lndos -create-app PrintAT.c -o PrintAT.com

Das funktioniert nicht.
es macht zwar HOME, aber der Bildschirm ist nicht leer.
Ich habe dann versucht die Printf vorher und hinterher hinzuzufügen.
Die werden aber auch nicht ausgegeben :cry:
In C braucht jeder Funktionsaufruf Klammern, auch wenn die Funktion gar keine Argumente hat. In der ersten Zeile von main muss es also cLS(); heißen.

Das Home und das Clear screen kann man übrigens auch in einem einzigen printf machen.

Die Escape-Sequenzen stammen aus der Terminal-Emulation, und die ist im Joyce-Handbuch beschrieben. Hast du denn kein Joyce-Handbuch (eigentlich gibt es ja zwei)? Das ist z.B. im "90 GB"-Download von Dieter enthalten (unter Literatur > Manuale > System, glaube ich). Dort schau mal unter "Anhang III - Eigenschaften des Bildschirms" nach. Unter "ESC E" steht da: "Löscht das Darstellungsfeld. Die Position des Cursors bleibt davon unberührt."
von bbock
25.03.2023, 10:35
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 33930

Re: Fragen und Antworten zum C-Kurs

Paul hat geschrieben: 25.03.2023, 08:45 ...
Jetzt werde ich gierig. Ein CLS wäre dabei noch sehr praktisch ;)
und die Zeichen um Rahmen zu malen :ugeek:
Hierzu möchte ich dich auf mein Forms3-Modul hinweisen: viewtopic.php?p=645#p645
In forms3.zip findest du input.h und input.c mit einigen nützlichen Funktionen und Definitionen, u.a. clearScreen(). Wenn du Rahmen malen möchtest, benötigst du eine Zeichentabelle, die du im Joyce-Handbuch findest. Oder du siehst dir mal box.h und box.c an; da ist bereits eine fertige Funktion zum Zeichnen von Rahmen drin (drawBox).

Sieht so aus, als würdest du bereits an einem eigenen Projekt basteln?
von bbock
25.03.2023, 10:27
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 33930

Re: Fragen und Antworten zum C-Kurs

Paul hat geschrieben: 25.03.2023, 08:43 ...
Leider gibt die Joyce kein ü aus.
Wie bekomme ich Umlaute und sz auf der Joyce ausgegeben?
Irgendwo hatte Bernd einen Trick verwendet der funktionierte. Ich habe ihn jetzt nicht wiedergefunden und damals nicht wirklich verstanden.
Nehmen wir mal an, du möchtest das Wort "Größe" ausgeben - da ist sowohl ein Umlaut als auch das "scharfe S" drin. Das könntest du so machen:

Code: Alles auswählen

#define CH_SZ        126
#define CH_LC_OUML   124
...
printf("Gr%c%ce", CH_LC_OUML, CH_SZ);
Die beiden "%c"-Platzhalter stehen jeweils für ein Zeichen; diese werden durch die weiteren Parameter CH_LC_OUML (= 124) und CH_SZ (= 126) ausgefüllt. Das sind die Zeichen-Codes für "ö" und "ß".