Die Suche ergab 238 Treffer

von bbock
24.02.2023, 16:59
Forum: Programmierung
Thema: Programmieren in C mit dem z88dk
Antworten: 20
Zugriffe: 103259

10. Pointer, Array, String

10.1 Pointer

Alle Daten, die ein Computer verarbeitet, werden in Speicherzellen abgelegt, auf die der Mikroprozessor über deren Adressen zugreifen kann. Man kann sich das wie eine lange Straße vorstellen, bei der die aneinandergereihten Häuser die Speicherzellen darstellen; die Adressen entsprechen dann den Hausnummern. Bei Byte-Maschinen - und zu denen zählt die Joyce - besteht eine Speicherzelle aus acht Bit oder einem Byte. Der Z80-Prozessor kann 64 kByte, also 65.535 einzelne Adressen verwalten. Das sind 2 hoch 16 Adressen; daraus folgt, dass eine Adresse 16 Bit lang ist.

char ist ein Datentyp, der aus einem einzelnen Byte besteht. Eine Variable, die als char ch deklariert wurde, kann also ein Byte speichern. Greift man im Programmcode auf ch zu, dann ist der Inhalt dieses Bytes gemeint, von dem der C-Compiler weiß, wo im Speicher es sich befindet. Wenn wir wissen müssen, wo im Speicher sich ch befindet, z.B. um ch mit der C-Funktion scanf zu füllen, dann müssen wir die Adresse von ch ermitteln. Wir haben das bereits bei der Verwendung von scanf erfahren: der &-Operator ermittelt die Adresse einer Variablen, d.h. &ch liefert die 16-Bit-Adresse der Variablen ch.

Speichert man diese Adresse wiederum in einer Variablen ab, dann nennt man diese Variable einen Zeiger oder englisch Pointer. Pointer-Variablen werden mit einem Stern und dem Typ der Ziel-Variablen gekennzeichnet. Um einen Pointer für ch zu deklarieren, schreiben wir z.B. char *p;. Damit p auf ch zeigt, weisen wir p die Adresse von ch zu: p = &ch;. Um nun auf das Zeichen in ch über den Pointer zuzugreifen, müssen wir den Pointer "dereferenzieren". Das geschieht über den *-Operator, z.B. putchar(*p);.

Ein kleines Beispielprogramm soll das veranschaulichen:

Code: Alles auswählen

#include <stdio.h>

int main(void) {
    char ch;
    char *p;
    
    ch = 'X';    // ch enthaelt jetzt das Zeichen 'X'
    putchar(ch); // gibt X auf der Konsole aus
    p = &ch;     // p erhaelt die Adresse von ch, d.h. p zeigt auf ch
    *p = 'Y';    // das Zeichen, auf das p zeigt, also ch, wird auf 'Y' gesetzt
    putchar(*p); // gibt Y auf der Konsole aus
    putchar(ch); // gibt ebenfalls Y auf der Konsole aus, da ch ueber p veraendert wurde
    
    return 0;
}
10.2 Array

Arrays sind Speicherstrukturen mit mehreren Elementen gleichen Typs; auf die Elemente kann man über einen Index zugreifen. Es gibt eindimensionale Arrays, die auch als Vektoren bezeichnet werden, und mehrdimensionale Arrays. Die Anzahl der Dimensionen bestimmt, wieviele Indizes man benötigt, um auf ein einzelnes Element zuzugreifen.

Einen Array mit fünf int-Elementen deklariert man etwa so: int a[5];. Damit erhält man eine Array-Variable a, deren Elemente man über die Indizes 0 bis 4 erreicht: a[0], a[1], ..., a[4]. Der kleinste Index ist in C-Arrays immer 0, der größte ist die Anzahl der Elemente minus eins. Da die Indizes auch über Variablen bereitgestellt werden können, hat man damit eine sehr bequeme Speicherstruktur für den Zugriff auf gleichartige Elemente.

Code: Alles auswählen

#include <stdio.h>

int main(void) {
    int a[5];
    int i;
    
    // Array mit Vielfachen von 3 belegen
    for (i = 0; i < 5; i++) {
        a[i] = i * 3;
    }
    
    // Array ausgeben
    for (i = 0; i < 5; i++) {
        printf("Zahl an Index %d: %d\n", i, a[i]);
    }

    return 0;
}
Das Programm gibt folgendes aus:

Zahl an Index 0: 0
Zahl an Index 1: 3
Zahl an Index 2: 6
Zahl an Index 3: 9
Zahl an Index 4: 12

In C sind Arrays gleichzeitig Pointer-Strukturen. a ist also in unserem Beispiel ein int-Pointer. Um das erste Element, also das Element mit dem Index 0, auf den Wert 17 zu setzen, schreiben wir normalerweise a[0] = 17;. Genausogut können wir aber auch *a = 17; schreiben, da a eben auch ein Pointer ist. Um das vierte Element auf 21 zu setzen, schreiben wir a[3] = 21; oder *(a + 3) = 21;.

Die Pointer-Schreibweise ist noch erklärungsbedürftig. a ist ein Pointer; die Variable enthält also die Adresse des Arrays bzw. die Adresse des Elements mit dem Index 0. (a + 3) ist die Adresse des Elements mit dem Index 3, wobei aber nicht 3, sondern 6 zur Adresse des Arrays addiert wird, denn a ist vom Typ int und ein int beansprucht 2 Bytes Speicherplatz. Die Multiplikation mit der Typgröße macht der Compiler automatisch. Bei Zeigerarithmetik muss man immer beachten, von welchem Typ die Pointer sind. Die Pointer-Variablen selbst belegen dagegen immer 2 Bytes, weil jede Adresse 16 Bits = 2 Bytes lang ist, egal, ob der Pointer auf einen char, einen int oder sonst etwas zeigt.
von bbock
24.02.2023, 11:49
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 501289

Re: Fragen und Antworten zum C-Kurs

Danke für den Hinweis, Kurt, ich hab's korrigiert.

Tatsächlich findet man die for-Endlosschleife am häufigsten vor. Das mag daran liegen, dass manche Compiler dann keinen unnötigen Bedingungs-Code einfügen, aber das wäre ein Thema für die Compiler-Optimierung. Die beiden Compiler des z88dk erzeugen allerdings denselben Code, egal welchen Schleifentyp man verwendet. Aus folgendem Code

Code: Alles auswählen

#include <stdio.h>

int main(void)
{
    for (;;) {
        printf(".");
    }
    
    return 0;
}

Code: Alles auswählen

#include <stdio.h>

int main(void)
{
    while (1) {
        printf(".");
    }
    
    return 0;
}

Code: Alles auswählen

#include <stdio.h>

int main(void)
{
    do {
        printf(".");
    } while (1);
    
    return 0;
}
wird jeweils folgender Assembler-Code:

sccz80:

Code: Alles auswählen

; Function main flags 0x00000000 __stdc 
; int main()
	C_LINE	4,"endlos1.c::main::0::1"
._main
.i_2
	ld	hl,i_1+0
	push	hl
	ld	a,1
	call	printf
	pop	bc
	jp	i_2	;EOS
.i_3
	ld	hl,0	;const
	ret
zsdcc:

Code: Alles auswählen

;	---------------------------------
; Function main
; ---------------------------------
_main:
l_main_00102:
	ld	hl,___str_0
	push	hl
	call	_printf
	pop	af
	jr	l_main_00102
	SECTION rodata_compiler
___str_0:
	DEFM "."
	DEFB 0x00
	SECTION IGNORE
von bbock
21.02.2023, 21:26
Forum: Programmierung
Thema: Programmieren in C mit dem z88dk
Antworten: 20
Zugriffe: 103259

9 Operatoren

9.1 Arithmetische Operatoren

Im Programm prim wurden Operatoren verwendet, die noch erläutert werden müssen. Die Grundrechenarten sind + (plus), - (minus), * (mal) und / (geteilt durch). Es gibt sie sowohl für Gleitkommazahlen wie auch für Ganzzahlen. Bei der Ganzzahldivision werden die Nachkommastellen abgeschnitten; es wird dabei nicht gerundet. Den Rest einer Ganzzahldivision kann man mit dem Modulo-Operator % ermitteln. In C gilt "Punkt- vor Strichrechnung", d.h. *, / und % haben Vorrang vor + und -. So ist 3 + 2 * 5 nicht 25, sondern 13, weil 2 * 5 zuerst berechnet wird. Möchte man eine andere Berechnungsreihenfolge haben, dann muss man Klammern setzen. (3 + 2) * 5 ergibt folglich 25.

9.2 Vergleiche und logische Verknüpfungen

Die Vergleichsoperatoren sind > (größer als), >= (größer oder gleich), < (kleiner als), <= (kleiner oder gleich) und == (gleich). Vorsicht: ein häufiger Programmierfehler ist das Verwechseln des Zuweisungsoperators = mit dem Vergleichsoperator ==.

Für logische Verknüpfungen werden die Operatoren && (und) und || (oder) verwendet. Dies sind die logischen Operatoren, die man nicht mit den bitweise Verknüpfungen & und | verwechseln sollte.

Beispiel: if (a > 5 && b <= 7) printf("passt!");

9.3 Bit-Manipulationen

In C kann man ganzzahlige Variablen bitweise manipulieren. Folgende Operatoren stehen zur Verfügung:

& - und-Verknüpfung von Bits
| - oder-Verknüpfung von Bits
^ - exklusive oder-Verknüpfung von Bits (XOR)
<< - Bit-Verschiebung nach links
>> - Bit-Verschiebung nach rechts
~ - Bit-Komplement (Einer-Komplement)

Beispiele:
  • c = n & 0177; löscht alle Bits in n bis auf die letzten sieben (0177 ist eine oktale Zahlkonstante und entspricht der Bitfolge 01111111)
  • x = x | MASK; setzt in x genau die Bits, die in MASK auf 1 gesetzt sind
9.4 Inkrement und Dekrement

Ganzzahl- und char-Variablen können inkrementiert und dekrementiert, d.h. um eins erhöht oder erniedrigt werden. Außerdem gibt es in C abkürzende Schreibweisen für bestimmte Operationen:

Code: Alles auswählen

#include <stdio.h>

int main(void)
{
    int i;

    puts("i ist zu Beginn immer gleich 5");
    
    i = 5;
    ++i;
    printf("++i -> %d\n", i); // ergibt 6

    i = 5;
    --i;
    printf("--i -> %d\n", i); // ergibt 4

    i = 5;
    i += 2; // entspricht i = i + 2;
    printf("i += 2 -> %d\n", i); // ergibt 7

    i = 5;
    i -= 2; // entspricht i = i - 2;
    printf("i -= 2 -> %d\n", i); // ergibt 3

    i = 5;
    i *= 2; // entspricht i = i * 2;
    printf("i *= 2 -> %d\n", i); // ergibt 10

    i = 5;
    i /= 2; // entspricht i = i / 2;
    printf("i /= 2 -> %d\n", i); // ergibt 2 (eigentlich 2.5, aber bei Ganzzahlen werden die Nachkommastellen abgeschnitten)

    i = 5;
    i %= 2; // entspricht i = i % 2;
    printf("i %%= 2 -> %d\n", i); // ergibt 1
    
    return 0;
}
von bbock
21.02.2023, 21:00
Forum: Programmierung
Thema: Programmieren in C mit dem z88dk
Antworten: 20
Zugriffe: 103259

8.5 Schleifen (Fortsetzung)

Wir schreiben ein Programm zur Berechnung einer Primzahlenliste:

Code: Alles auswählen

#include <stdio.h>

int main(void)
{
    int z, d;
    char prim;
    int grenze;
    int count;

    printf("Bis zu welcher Zahl sollen die Primzahlen ermittelt werden? ");
    scanf("%d", &grenze);
    printf("\nPrimzahlen bis %d:\n", grenze);
    count = 0;
    
    printf("  2 ");
    
    for (z = 3; z < grenze; z += 2) {
        prim = 'Y';
        
        for (d = 3; d < z / 2; d += 2) {
            if (z % d == 0) {
                prim = 'N';
                break;
            }
        }
        
        if (prim == 'Y') {
            printf("%4d ", z);
        }
    }
    
    return 0;
}
Wir kompilieren und binden mit:

Code: Alles auswählen

zcc +cpm -subtype=pcw80 prim.c -o prim.com
In der Funktion main werden zunächst die benötigten Variablen deklariert. Dann erfolgt die Eingabe der Obergrenze, bis zu der Primzahlen ausgegeben werden sollen. Das geschieht wieder mit der C-Funktion scanf; zur Erinnerung: wir müssen hier die Adresse der Variablen grenze übergeben, also &grenze.

Die erste Primzahl - die 2 - geben wir immer aus (was zu einer kleinen Unstimmigkeit führt, wenn wir als Obergrenze 0 oder 1 eingeben). Dann durchlaufen wir mit der Variablen z alle Zahlen bis zur Obergrenze in Zweierschritten. Wir prüfen bis z / 2, ob die Zahl durch d teilbar ist. d beginnt mit 3 und wird ebenfalls in Zweierschritten hochgezählt. Dann prüfen wir, ob die Division von z durch d einen Rest ergibt (Modulo-Division); falls nein, dann ist die Zahl z keine Primzahl und wir beenden die innere Schleife mit break.

Wurde die innere for-Schleife durchlaufen, ohne dass wir einen Teiler gefunden haben, dann ist die Zahl eine Primzahl und wir geben sie aus.

Die for-Schleifen haben drei Ausdrücke innerhalb der Klammern, die den Ablauf bestimmen. Wir sehen uns das am Beispiel der z-Schleife an:

for (z = 3; z < grenze; z += 2) {

Der erste Ausdruck ist die Initialisierung. Sie wird einmalig zu Beginn der Schleife durchgeführt. Hier wird die Variable z mit 3 initialisiert. Im ersten Schleifendurchlauf hat z also den Wert 3.

Der zweite Ausdruck ist die Schleifenfortsetzungsbedingung, also das Gegenteil der Abbruchbedingung. Solange die Bedingung erfüllt ist, läuft die Schleife.

Der dritte Ausdruck wird am Ende jedes Schleifendurchlaufs ausgeführt. Oft wird hier die Schleifenvariable hoch- oder heruntergezählt.

Jeder der drei Ausdrücke ist optional. Es können auch alle drei Ausdrücke weggelassen werden; in dem Fall erhält man eine Endlosschleife (die natürlich innerhalb des Schleifenkörpers mit break oder return verlassen werden kann). Die Semikoli müssen aber bleiben.

Da wir drei Varianten für die Formulierung von Schleifen in C kennen, gibt es auch drei Möglichkeiten eine Endlosschleife zu schreiben:

1. for (;;) {...}
2. while (true) {...
3. do {...} while (true);
von bbock
21.02.2023, 20:57
Forum: Hardware
Thema: PCW WiFi Modem
Antworten: 35
Zugriffe: 155554

PCW WiFi Modem - Artikel der Klubzeitung

Mein Artikel für die Klubzeitung der JOYCE-User-AG e.V.:

PCWWiFiModem.pdf
Artikel Klubzeitung
(424.49 KiB) 1064-mal heruntergeladen
Edit: aktuelle Version des Artikels hochgeladen
von bbock
21.02.2023, 20:54
Forum: Hardware
Thema: PCW WiFi Modem
Antworten: 35
Zugriffe: 155554

Re: PCW WiFi Modem

Jungsi hat geschrieben: 21.02.2023, 18:20 ...Leider bin ich noch PCW-Anfänger - daher habe ich keine Ahnung, wie ich QTERM richtig starten kann. Ich scheitere bedauerlicherweise schon daran, das auf eine Diskette zu bringen :-) Die Dateien sind größer als eine 180K-Disk....
Du brauchst nicht alle Dateien aus QTERM4.3f.zip; es genügt die Datei QTERMAMS.COM.
Jungsi hat geschrieben: 21.02.2023, 18:20 In meinem PCW ist ein GOTEK verbaut und funktioniert. CP/M Plus 1.4 habe ich, aber danach ???
CP/M Plus 1.4 ist gut bis 4800 Baud; das reicht auch für den Anfang. Für mehr solltest du mind. CP/M Plus 1.7 verwenden (ich nutze 1.8).
von bbock
21.02.2023, 16:08
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 501289

Re: Fragen und Antworten zum C-Kurs

Hallo Paul,

danke für's Feedback. Das hält mich motiviert weiterzumachen. :)
von bbock
19.02.2023, 13:07
Forum: Programmierung
Thema: Programmieren in C mit dem z88dk
Antworten: 20
Zugriffe: 103259

8. Kontrollstrukturen

8.1 Anweisungen und Blöcke

Einzelne Anweisungen werden in C immer mit einem Semikolon beendet, z.B.

Code: Alles auswählen

x = 0;
Eine oder mehrere Anweisungen werden mit geschweiften Klammern zu Blöcken zusammengefasst. Der Programmcode von Funktionen ist etwa immer in einem Block zusammengefasst, aber auch Befehlssequenzen von Kontrollanweisungen wie if, else, while oder for.

8.2 if-Anweisungen

Die allgemeine Syntax von if-Anweisungen ist:

Code: Alles auswählen

if (Ausdruck)
    Anweisung1
else
    Anweisung2
Anweisung1 und Anweisung2 können dabei einzelne Statements oder Blöcke sein. Das else ist optional. Beispiele:

Code: Alles auswählen

if (a > b)
    z = a;
else
    z = b;

Code: Alles auswählen

if (r > 10) {
    printf("r darf max. 10 sein.\n");
    r = 10;
}
8.3 else-if Ketten

Code: Alles auswählen

if (Ausdruck1)
    Anweisung1
else if (Ausdruck2)
    Anweisung2
else if (Ausdruck3)
    Anweisung3
else
    Anweisung4
Dies ist eine häufig anzutreffende Konstruktion. Hier wird ein Ausdruck nach dem anderen ausgewertet. Ist ein Ausdruck true, dann wird die zugehörige Anweisung ausgeführt. Trifft keine der Bedingungen zu, d.h. alle Ausdrücke ergeben false, dann wird der else-Zweig ausgeführt.

8.4 switch

Mit switch kann bequem eine von mehreren Alternativen ausgewählt werden, wobei mit Konstanten verglichen wird. Beispiel einer Menüauswahl:

Code: Alles auswählen

char c;
c = getMenuSelection();

switch (c) {
case 'x':
case 'X':
    cleanup();
    exitApp = true;
    break;
case 'd':
case 'D':
    drawPicture();
    break;
default:
    printf("ungültige Option\n");
}
Hier wird die Benutzerauswahl im Menü als Zeichen zurückgeliefert. Bei kleinem oder großem X soll die Anwendung beendet werden, bei kleinem oder großem D wird ein Bild gezeichnet. Andernfalls erscheint eine Fehlermeldung.

Wichtig sind die break-Befehle, denn ohne sie würde der Code einfach zum nächsten case "durchfallen". So passiert bei der Eingabe eines kleinen x zunächst nichts und der Code des nächsten case (mit dem großen X) wird ausgeführt. Dessen Code endet aber mit break, so dass anschließend hinter der geschweiften Klammer zu fortgesetzt wird.

Der default-Fall wird ausgeführt, wenn keine der case-Anweisungen einen Treffer liefert oder wenn im darüberstehenden case nicht mit break abgeschlossen wurde.

8.5 Schleifen - do, while und for

C kennt drei Konstrukte zur wiederholten Ausführung von Anweisungen. Die do-Schleife wird immer mindestens einmal durchlaufen; die Prüfung der Abbruchbedingung erfolgt in der while-Anweisung am Ende. Bei der while-Schleife ist es genau umgekehrt: die Abbruchbedingung wird gleich zu Beginn geprüft. Dadurch kann es sein, dass der Schleifenblock überhaupt nicht durchlaufen wird. Die for-Schleife schließlich ist ideal für Zählvorgänge, bei der eine Schleifenvariable hoch- oder heruntergezählt wird.

Beispiel do-Schleife:

Code: Alles auswählen

do {
    c = getMenuSelection();
    performFunction(c);
} while (c != 'x' && c != 'X');
Die Menüauswahl wird ermittelt und eine dazu passende Funktion via performFunction ausgeführt. Das geschieht so oft, bis ein X eingegeben wurde (unabhängig von der Groß-/Kleinschreibung). performFunction wird auch einmal mit X aufgerufen und muss das berücksichtigen.

Beispiel while-Schleife:

Code: Alles auswählen

c = '.';
i = 0;
while (c != '\0') {
    c = s[i++];
    if (c != '\0')
        putchar(c);
}
Alle Zeichen im Array s werden nacheinander ausgegeben, bis ein Null-Zeichen erreicht wird. Das entspricht der Ausgabe eines Strings, wie er ähnlich auch in der Funktion puts aus der C-Standard-Ein-Ausgabe-Bibliothek zu finden sein dürfte. Unschön ist hier allerdings die doppelte Auswertung der Bedingung c != '\0'.

Beispiel for-Schleife:

Code: Alles auswählen

for (i = 0; i < 10; i++) {
    printf("%d^2 = %d\n", i, i * i);
}
Eine Liste der Zahlen 1 bis 9 und deren Quadratzahlen wird ausgegeben:
0^2 = 0
1^2 = 1
2^2 = 4
...
9^2 = 81

Schleifen können durch die Anweisung break vorzeitig abgebrochen werden. Die Anweisung sorgt dafür, dass die innerste Schleife (oder eine switch-Anweisung) sofort verlassen wird.

Beispiel String-Ausgabe:

Code: Alles auswählen

i = 0;
while (true) {
    c = s[i++];
    if (c == '\0')
        break;
    putchar(c);
}
Mit der Anweisung continue leitet sofort die nächste Wiederholung der Schleife ein ohne den darauffolgenden Code auszuführen.

Im folgenden Beispiel werden nur die positiven Elemente eines Arrays bearbeitet:

Code: Alles auswählen

for (i = 0; i < N; i++) {
    if (a[i] < 0)
        continue; /* negative Elemente überspringen */
    ...           /* positive Elemente bearbeiten */
}
Die Anweisung goto sollte möglichst nicht verwendet werden, da sie leicht zu unübersichtlichem "Spaghetti-Code" führt und den Grundsätzen der strukturierten Programmierung widerspricht. Dennoch gibt es einen Fall, in dem goto nützlich sein kann: das Beenden einer tiefer verschaltelten Schleife. Die break-Anweisung beendet nur die innerste Schleife. Das Verlassen einer z.B. doppelt verschachtelten Schleife kann man mit goto zu einer Sprungmarke bewerkstelligen:

Code: Alles auswählen

for (...) {
    for (...) {
        ...
        if (Katastrophe)
            goto error;
    }
    ...
}
error:
    ...
von bbock
18.02.2023, 15:16
Forum: Programmierung
Thema: Vektorgrafik für die Joyce
Antworten: 61
Zugriffe: 235442

Re: Vektorgrafik für die Joyce

Das Programm vecread habe ich in Version 4.2 mit dem z88dk nach C portiert: Download

Es funktioniert genauso wie die TurboPascal-Version - nur ca. doppelt so schnell!
von bbock
18.02.2023, 14:58
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 501289

Re: Fragen und Antworten zum C-Kurs

Ja, die eckigen und geschweiften Klammern sehen mit den deutschen Tastatureinstellungen auf der Joyce "ungewohnt" aus... :D
Aber wir entwickeln und editieren ja auf dem PC (Stichwort Cross Compiler), und da haben wir dieses Umlauteproblem nicht (dafür ein anderes - dazu später mehr).

Bei mir sieht die Ausgabe von einaus.com so aus:

einaus.png
einaus.png (7.19 KiB) 28345 mal betrachtet