584.850 aktive Mitglieder*
4.199 Besucher online*
Kostenfrei registrieren
Einloggen Registrieren

Anwenderprogrammierung in ANSI-C, Programmierung eines nicht banalen Anwenderprogramms

Beitrag 31.01.2012, 19:36 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Hallo,

zu meiner FIBU-Programierung hab ich ja schon einen Beitrag reingesetzt.

Die Fibu läuft.

Sie kostet nichts, hat keine Lizenzgebühren, und produziert auf den 1/10 Cent genaue Auswertungen, wenn man berücksichtigt, daß die Amis bei 0.05 Cent abrunden, die Europäer aber bei 0.05 Cent aufrunden, und das abfängt.

Es läuft alles bestens.

Es ist nur nicht so schöööööööööööööön.

Daher gehe ich jetzt aus Spaß an der Freud mal daran, eine Konsolen-Anwendung auch optisch anspruchsvoll darzustellen (aufzumotzen).

Man will ja, wenn man programmiert, immer schnell Ergebnisse erzielen. Darunter leidet nicht nur die Optik, sondern ganz allgemein ist der Fehler, daß man im Vorfeld nicht die Geduld hat, die Komponenten des Programms genügend zu ABSTRAHIEREN und zu KAPSELN.

Der Mangel an Abstraktion Kapselung wird bei jeder Programmänderung zum immer größeren Problem.

Anders gesagt, wenn man bei der Programmierung zu sehr ERGEBNISORIENTIERT denkt und die STRUKTURIERUNG vernachlässigt, wird jede noch so kleine Programmänderung zum Problem und führt zu Instabilitäten. Einer der Hauptgründe für diese Problematik ist der, daß man sich am Anfang über das Thema nicht klar ist und daher die Programmierung immer wieder nachträglich an bislang nicht bekannte Umstände anpassen muß. Irgenwann ist dann der Punkt erreicht, wo man Änderungen besser nicht mehr vornimmt, weil die Stabilität des Programms in Gefahr gerät.

Daher:

Da das Ding nun läuft, und die grundlegenden Fragen der Programmierung geklärt sind, lasse ich es unverändert. Um es zu verbessern, wird es nicht mehr geändert, sondern es soll der Braut ein völlig neues und viel schöneres Kleid geschneidert werden.

Mein Beitrag wird sich mit folgenden Dingen befassen:

1.) Reine ANSI-C Programmierung. Kein Mischmasch mit C++.

2.) Schwerpunkt vernünftige Präsentation auf der KONSOLE.

3.) Schwerpunkt sicherer IO-Dialog mit dem Anwender.

4.) Schwerpunkt Management von Dateien, Binär- und Textformat, einige Tricks dazu, die man nicht überall liest (oder nirgends).

5.) Schwerpunkt Sortierung von Daten und der Umgang mit dynnamische Listen bzw. dynamischer Speicherverwaltung,

6.) Schwerpunkt Außendarstellung durch Dateiexport und Druckvorlagen


C is an all purpose language. Eine Sprache, die für alles taugt.

Man kann damit eine FIBU schreiben, eine CNC-Maschine programmieren oder zum Mond fliegen.

Ich beschränke mich hier auf die oben dargestellten Schwerpunkte. wink.gif


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 31.01.2012, 19:52 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Ein Bild sagt mehr als tausend Worte.

Daher mal als Appetizer ein Blick auf einen leeren Bildschirm (Dateianhang).

Wir sehen da das, was ich schon immer (als alter DOS-Liebhaber) bevorzugt habe:

4 Fenster auf einem Bildschirm nebeneinander, die getrennt verwaltet werden. Keine überlappenden Fenster, sondern immer alles KONTEXT-SENSITIV auf einen Blick.

Also ein gutes altes Konsolen-Konzept.

Was wesentlich ergonomischer ist als dutzende von Windows-Fenstern, die sich überlagern, der Konzentration förderlich ist und das Auge erfreut.

Jedenfalls, wenn es darum geht, bei einer Finanzbuchhaltung Zahlen zu erfassen und zu verwalten. Das haben die FUGGER am Schreibpult per Tinte gemacht mit T-Bilanzen und ich wüßte nicht, daß die Fugger damit keinen Erfolg gehabt hätten.

Schwerpunkt also: klösterliche Abstinenz von allem, was aufgemotzt ist, karge (klösterliche) Darstellung, aber Übersicht, ERGONOMIE.

AUF EINEN BLICK ALLES SEHEN, nicht klicken und verschieben und

KEIN WECHSEL ZWISCHEN TASTATUR und MAUS.

Das alles gefällt mir persönlich nicht, und ich möchte das als unergonomisch bezeichnen.

Wir sehen auf der Abbildung ein Konsolen-Fenster.

Dieses Fenster ist zur besseren Darstellung in 4 Farbbereiche aufgeteilt.

Tatsächlich handelt es sich um 4 Fenster nebeneinander, die separat verwaltet werden (sollen).

In dem Fenster oben läuft das Menü, die Anwenderführung.

Links darunter findet der Dialog mit dem Anwender statt.

Rechts daneben ist ein Bildschirmbereich vorgesehen für die Auflistung von Daten, welche z. B. bei der EIngabe benötigt werden (hier hauptsächlich die verfügbaren Konten des KOntenrahmens SKR03, woraus man bei der Buchung eine Auswahl trifft).

Darunter ist ein kleiner Bereich für Kurzmitteilungen, z. B. daß der Anwender eine ungültige Auswahl getroffen hat oder Abfrage, was er machen will, z. B. Speichern oder verwerfen.

Man muß das farblich nicht trennen, ist nur mal so zur Demo. Und auch keine schreienden Farben verwenden, unter denen das Auge leidet.

Wie programmiert man sowas?

Ich zeige hier nurmal die Anweisungen aus dem Hauptprogramm, welche diesen Bildschirm auslösen, und erkläre das anschließend.

Programmauszug:

int main(int argc, char *argv[])
{
int i;
init();
gotomenu();

printf("Der Bereich Menue, was der Anwender waehlen kann");

gotohelp();
printf("Hilfetexte und Auswahllisten ");

gotoprog();
printf("Programm-Dialogbereich",i+1);

gotoinfo();
printf("Meldungen und Kurzhinweise");


char c;
c=getch();
return 0;
}

Angehängte Datei(en)
Angehängte Datei  bildschirm.jpg ( 67.83KB ) Anzahl der Downloads: 89
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 31.01.2012, 20:12 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Es sind 4 Anweisungen zu sehen, entsprechend den 4 Bildschirmbereichen.

goto prog, menu hilfe info

Man hätte auch schreiben können:

goto Fenster 1, Fenster 2 Fenster 3 Fenster 4

Nur daß da der Sinnzusammenhang fehlt. Denn was ist Fenster 2??

Schreibt man: goto prog ist man im Fensterbereich, der den IO-Dialog mit dem Anwender regelt.

Woher weiß daß Programm nun, was goto prog ist?

Es muß ja dafür einige Routinen erledigen:

1.) den korrekten Bilschirmbereich aufsuchen

2.) an der richtigen Stelle die printf(.. Anweisung, also die Zeichen ausgeben

3.) den Bilschirmbereich, wenn er mit alten Daten gefüllt ist, ggflls. löschen

4.) Ganz nebenbei eine andere Farbe für Text und Hintergrund einschalten

5.) verwalten, wenn die Ausgabe über die Fenstergrenzen hinausgehen sollte

Das alles soll der Anwender von MAIN aus nicht wissen, und er will es nicht wissen.

Er will einen Text schreiben in das Fenster seiner Wahl, und das Programm (bzw. die aufrufenden Routinen, bzw. Funktionen) sollen sich um die Details kümmern.

Das nennt man Kapselung.

Die Anweisung, was zu tun ist, wird nicht direkt eingegeben, sondern man ruft die dafür zuständige Funktion auf.

Will man schnell und einfach programmieren, nutzt man den ganzen Bildschirm und schmiert irgendwelche Zeichen drauf.

Will man das strukturiert erhalten, muß man kapseln, die Ausgabe an Funktionen übergeben, die den Bildschirm verwalten.

Zum Stichwort Kapselung mal folgende Abbildung:

Man hat sich überlegt, die Fenstergrößen zu verändern.

Was ist nun mit der Ausgabe der Zeichen? Hängen die im falschen Bereich?

Nein, hängen sie nicht, wie man hier sieht (Abb).

Die Aufteilung des Bildschirms wurde verändert, aber die ausgegebenen Daten "sitzen" an der richtigen Stelle, nämlich in ihrem Fensterbereich.

Weil die Ausgabe auf eine Weise gekapselt ist, die sicherstellt, daß man die Daten in dem vorgegebenen Bereich wiederfindet, auch wenn die BIldschirmaufteilung zwischenzeitlich geändert wurde.

Anders gesagt also, die Anweisungen im Programmbereich "main" wurden nicht verändert, sie erscheinen nur anders auf dem Bilschirm.
Angehängte Datei(en)
Angehängte Datei  bildschirm2.jpg ( 76.89KB ) Anzahl der Downloads: 34
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 31.01.2012, 20:47 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Das macht natürlich keinen Spaß. wink.gif

Oder doch? wink.gif

Also man will Zahlen eingeben und saldieren, und muß sich mit so einem Sch... wie der Bildschirmdarstellung beschäftigen.

Das ist genau der Punkt, wo die Windows-Programmierer drauf herumreiten, alles kapseln und besser machen und so weiter.

Sch***! Als wenn es das unter DOS nicht längst gegeben hätte und man das Rad immer neu erfinden müßte.

Programmierung ist zielorientiert, wenn ich eine Anwendung programmieren müßte, wo der letzte ZULU in KIMBABWE noch mit der Spracheinstellung klarkommen müßte ...

hab ich aber nicht.

Also, mal grundsätzlich:

Man muß, wenn man sich komfortabel bewegen will im Programm, Angestellte haben, einen Mundschenk, einen Rittmeister, einen Chef de Cuisine, die sich selbstständig um die Aufgaben kümmern.

Die Programmierung kann nicht sein:

Liste die Zutaten auf Sellerie, Petersilie, Suppengewürz,
Zeige die Zeiten Trocknen, Einlegen, Vorkochen, Zubereitung usf.

sondern das Programm muß so strukturiert sein, daß die Anweisung:

koche_gemuesesuppe()

zu dem gewünschten Ergebnis führt, egal, wie sich die Zutaten in der Zwischenzeit geändert haben.

Das ist später besonders interessant beim Management der Dateien.

Ich will jetzt mal kurz skizieren, wie man ein Programm so abstrahiert, daß die Gemüsesuppe auch das ergibt, was man wünscht:

(Programmauszug:)


#define BLACK 0
#define BLUE 1
#define GREEN 2
#define CYAN 3
#define RED 4
#define MAGENTA 5
#define BROWN 6
#define LIGHTGREY 7
#define DARKGREY 8
#define LIGHTBLUE 9
#define LIGHTGREEN 10
#define LIGHTCYAN 11
#define LIGHTRED 12
#define LIGHTMAGENTA 13
#define YELLOW 14
#define WHITE 15
#define BLINK 128

#define EOS '\0'
#define EOL '\n'
#define DINA4QUER 120
#define KON_BREITE DINA4QUER
#define KON_HOEHE 36
#define OBEN 7
#define MITTE 50
#define UNTEN KON_HOEHE-8
#define INFOLINE KON_HOEHE-10
char linie_voll[DINA4QUER+1];
char leer_voll[DINA4QUER+1];
char linie_links[MITTE+1];
char leer_links[MITTE+1];
char linie_rechts[KON_BREITE-MITTE+1];
char leer_rechts[KON_BREITE-MITTE+1];



Da wird also ein Haufen von konstanten Werten festgelegt.

Diese Werte sind erstmal nicht unbedingt einsichtig, und müssen es auch nicht sein, weil sie dann wieder durch gekapselte Funktionen verwaltet werden. Z. B. zur Aufteilung der Fenster dient die Konstante

MITTE 50

Dann wird der rechte Bildschirmbereich ab Spalte 50 genutzt, weil MITTE 50

Ändert man das, z. B. auf 60, rückt der gesamte rechte Bildschirmbereich auf 60, und das linke Fenster wird größer. Alle Funktionen, die im rechten Bereich stattfinden, greifen aber nicht auf die Zahl 50 zu, sondern auf die Konstante MITTE, und wird diese geändert, paßt sich die Bildschirmausgabe aller Funktionen an diese Änderung an.

Struktuiert man das so, wird nur ein Parameter geändert, und der Rest des Programms folgt.

Beispiel Verwaltung der Farben:

void resetcolor() {TextColor(WHITE,255,GetStdHandle(STD_OUTPUT_HANDLE));}
void colormenu() {TextColor(WHITE,DARKGREY,GetStdHandle(STD_OUTPUT_HANDLE));}
void colorprog() {TextColor(BLACK,WHITE,GetStdHandle(STD_OUTPUT_HANDLE));}
void colorhelp() {TextColor(BLACK,LIGHTGREY,GetStdHandle(STD_OUTPUT_HANDLE));}
void colorinfo() {TextColor(RED,YELLOW,GetStdHandle(STD_OUTPUT_HANDLE));}


Jeder Bildschirmbereich hat eine andere Farbe für Text und Hintergrund (muß nicht sein). Dann muß die verwaltet werden.

Wir sehen aber vom Hauptprogramm main() keinen Zugriff auf die Farben, die sind auch gekapselt, nämlich im Aufruf der Bereiche menu, prog, help usf. schon enthalten:

void gotomenu(){colormenu();gotoxy(0,0);}
void gotoprog(){colorprog();gotoxy(0,OBEN);}
void gotohelp(){colorhelp();gotoxy(MITTE,OBEN);}
void gotoinfo(){colorinfo();gotoxy(MITTE,INFOLINE);}


Bevor diese Funktionen also einen Text ausgeben, ändern sie die Bildschirmfarben. Wünscht man das später nicht mehr, bleibt der AUfruf in der aufrundenden Funktion unverändert, die Änderung findet in der gekapselten Funktion statt.

Mit anderen Worten:

Was immer man später auch verändert, die eigentliche Programmierung bleibt UNVERÄNDERT, egal wie die Darstellung auf dem Bildschirm stattfindet.

Um einzelne Bilschirmbereich zu löschen, bevor neue Daten ausgegeben werden können, dienen diese Funktionen:

void clrmenu(){int i;colormenu();for (i=0;i<OBEN;i++){gotoxy(0,i);puts(leer_voll);}gotomenu();}
void clrprog(){int i;colorprog();for (i=OBEN;i<=KON_HOEHE;i++){gotoxy(0,i);puts(leer_links);}gotoprog();}
void clrhelp(){int i;colorhelp();for (i=OBEN;i<=KON_HOEHE;i++){gotoxy(MITTE,i);puts(leer_rechts);}gotohelp();}
void clrinfo(){int i;colorinfo();for (i=INFOLINE;i<=KON_HOEHE;i++){gotoxy(MITTE,i);puts(leer_rechts);}gotohelp();}


So, und woher wissen die STRINGS, die ja aus Leerzeichen bestehen oder die Linien, wie lang sie sein müssen?

Sind sie zu kurz, löschen sie den Bereich nicht vollständig, sind die Linien zu lang, überlappen sie auf den Anfang der nächsten Zeile und zerstören den Bildschirmaufbau.

Nun könnte man sagen: um den linken Bereich zu löschen, gib 40 Leerzeichen aus.

ABER:

Was ist, wenn der linke Bereich auf 60 Zeichen erweitert wird? Dann müßte man händisch nach der 40 suchen, die aber auch in anderen Bereichen vertreten sein könnte. Mit anderen Worten: dann ginge das los, mit dem Chaos.

Man kann die Länge der STrings schon beim Programmaufruf variabel definieren, nämlich:

Erstens:

#define DINA4QUER 120
#define KON_BREITE DINA4QUER
#define KON_HOEHE 36
#define OBEN 7
#define MITTE 50
#define UNTEN KON_HOEHE-8
#define INFOLINE KON_HOEHE-10


KON = Konsole =BILSCHIRM, wie breit und wie hoch man sie möchte.

Da man dazu Strings mit der passenden Breite benötigt, läßt man die Zahlen weg. Man benutzt statt dessen die Präprozessor-Definitionen, nämlich so:

char linie_voll[DINA4QUER+1];
char leer_voll[DINA4QUER+1];
char linie_links[MITTE+1];
char leer_links[MITTE+1];
char linie_rechts[KON_BREITE-MITTE+1];
char leer_rechts[KON_BREITE-MITTE+1];




Dieses +1 ist bei C immer im Mittelpunkt Hier bedeutet es, +1 Zeichen an den String für die STringendemarkierung.

Der Sinn dieser Deklaration ist also dieser, daß die Stringlänge schon beim Programmaufruf immer passend zur Verfügung steht, und wenn man irgendwelche Änderungen durchführt, der String nicht im Programm nachträglich verändert werden muß, sondern originär, per deklaration, paßt.

Das ist hier technisch dur die PRÄPROZESSOR-Direktive gelöst, Ergebnis, daß die Länge der Strings schon bei der Kompilierung so geetzt wird, daß sie paßt und nicht nachgträglich verändert werden muß.

Tja, das ist das eben.

Bisher ist noch keine einzige Zeile Programm geschrieben (das ist ja auch längst fertig wink.gif )

Aber nur so kommt man an strukturierte PRogramme, DASS MAN DIE UNGEDULD ZUEGELT:



Danke fürs Zuhören demnächst mehr mal davon.

Gruß Sharky wink.gif

Der Beitrag wurde von sharky bearbeitet: 31.01.2012, 20:58 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 01.02.2012, 17:43 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Ich fasse mal zusammen, was bisher erreicht wurde:

Wir haben 4 definierte Bildschirmbereiche, die wie Fenster getrennt voneinander verwaltet werden. Nur daß die eben nicht überlappen, sondern nebeneinander angeordnet sind = das ist die jahrhundertealte Tradition, sich mit einem Blick ohne Klick wink.gif eine ÜBersicht zu verschaffen -> für die meisten ja doch recht banalen Anwendungen halte ich die BUNTE WINDOWS CLICK & PICK- Welt für völlig übertrieben und nicht zielführend.

Jetzt will ich aber doch einen Sündenfall begehen und zu dem Bildschirmbereich ein überlappendes Fenster programmieren.

Nur für den Fall, daß man es mal brauchen könnte. Das könnte der Fall sein, wenn man längere Infos, die man nicht unten in das kleine Infofenstesr quetschen kann, zusammenhängend am Bildschirm einblenden will, und zwar während der Bearbeitung, mittendrin eben.

Dann schreibt man einen Bildschirmbereich obendrauf, welcher die anderen Bereiche überdeckt.

Abb1: OVERLAPPING WINDOW

So weit, so schön. Das Problem ist jetzt: wie kriegt man das Fenster wieder weg, so daß der Programmablauf ungestört weiterlaufen kann?

Die eleganteste Lösung wäre physikalisch: vor dem Aufruf des OLW den Bildschirminhalt in einen BUffer kopieren, und um das OLW wieder wegzukriegen, den gespeicherten Inhalt in den Bildschirmspeicher zurückschreiben.

Mit DOS wäre das eine Lösung, unter Windows habe ich dazu nichts gefunden, höchstens die allgemeine Tendenz, Finger weg davon. Da hängen das Betriebssystem und die Grafiktreiber dran, und wenn man sich da nicht genauestens auskennt ... scheidet also aus.

Nun kann man die Sache natürlich auch softwareseitig lösen. Ist ´ne Kleinigkeit. Ein bißchen mehr formaler Aufwand, aber nur im Anfang. Das Programmieren wird dadurch insgesamt nicht aufwendiger oder unübersichtlicher, eher im Gegenteil.

Für die 4 Bildschirmbereiche MENU PROG HELP INFO bestehen separate Routinen, bislang solche, die den Bereich mit Leerzeichen üb erschreib en (d.i. löschen), allerdings in der passenden Textfarbe, so daß das Fenster kenntlich bleibt.

Die dazu erforderlichen Leerzeichen-Strings sind exakt auf den Bildschirmbereich angepaßt, weil sie mit Präprozessor-Direktiven "entstehen", das heißt die Maße werden nicht nachträglich über Konstanten eingelesen, sondern schon beim Programmstart via Compiler festgelegt. Die Präprozessor Direktiven

#define ... irgendwas

gelten heute als unmodern. Nun, hüstel, was ist nicht unmodern heute. Ich bin auch unmodern. wink.gif

Ich wills mal kurz reinkopieren, wie das aufgebaut ist:

(Programmauszug):

#define EOS '\0' // END OF STRING-MARKIERUNG
#define EOL '\n' // END OF LINE = NEWLINE (daneben gibt es noch '\f' = FORMFEED, Seitenumbruch, kommt später rein
#define DINA4QUER 120
#define KON_BREITE DINA4QUER // DinA4 quer ist die perfekte Wahl, weil so auch die DATEV-BWAs gedruckt werden WYSIWYG
#define KON_HOEHE 36 // ANzahl Zeilen Bildschirm-KOnsole
#define OBEN 5
#define MITTE 80 // AUfteilung der Bereiche.
#define INFOZ 5
#define UNTEN KON_HOEHE-INFOZ

#define MENUS KON_BREITE+1
#define MENUZ OBEN
#define PROGS MITTE
#define PROGZ KON_HOEHE-OBEN
#define HELPS KON_BREITE-MITTE+1
#define HELPZ UNTEN-OBEN
#define INFOS KON_BREITE-MITTE+1
#define OLWS 80 // overlapping window soll 80 Zeichen breit sein
#define OLWZ 20

#define MENUX 0 // xund y Positionen der Bereiche Punkt jeweils oben links
#define MENUY 0
#define PROGX 0
#define PROGY OBEN
#define HELPX MITTE
#define HELPY OBEN
#define INFOX MITTE
#define INFOY UNTEN
#define OLWX (KON_BREITE-OLWS)/2 // das OLW zentriert sich automatisch mittig, auch wenn die KOnsolenbreite oder das Fenster geändert wird
#define OLWY (KON_HOEHE-OLWZ)/2



Die Funktionen, mit denen die Bildschirmbereiche getrennt voneinander mit Leerzeichen gefüllt werden :

void resetcolor() {TextColor(WHITE,255,GetStdHandle(STD_OUTPUT_HANDLE));}
void colormenu() {TextColor(WHITE,DARKGREY,GetStdHandle(STD_OUTPUT_HANDLE));}
void colorprog() {TextColor(BLACK,WHITE,GetStdHandle(STD_OUTPUT_HANDLE));}
void colorhelp() {TextColor(BLACK,LIGHTGREY,GetStdHandle(STD_OUTPUT_HANDLE));}
void colorinfo() {TextColor(RED,YELLOW,GetStdHandle(STD_OUTPUT_HANDLE));}
void colorolw() {TextColor(BLACK,LIGHTCYAN,GetStdHandle(STD_OUTPUT_HANDLE));}

void gotomenu(){colormenu();gotoxy(MENUX,MENUY);}
void gotoprog(){colorprog();gotoxy(PROGX,PROGY);}
void gotohelp(){colorhelp();gotoxy(HELPX,HELPY);}
void gotoinfo(){colorinfo();gotoxy(INFOX,INFOY);}
void gotoolw(){colorolw();gotoxy(OLWX,OLWY);}


void clrmenu(){int z;colormenu();for (z=0;z<MENUZ;z++){gotoxy(MENUX,z+MENUY);puts(menublank);}gotomenu();}
void clrprog(){int z;colorprog();for (z=0;z<PROGZ;z++){gotoxy(PROGX,z+PROGY);puts(progblank);}gotoprog();}
void clrhelp(){int z;colorhelp();for (z=0;z<HELPZ;z++){gotoxy(HELPX,z+HELPY);puts(helpblank);}gotohelp();}
void clrinfo(){int z;colorinfo();for (z=0;z<INFOZ;z++){gotoxy(INFOX,z+INFOY);puts(infoblank);}gotoinfo();}
void clrolw(){int z;colorolw(); for (z=0;z<OLWZ;z++) {gotoxy(OLWX, z+OLWY); puts(olwblank); }gotoolw();}


Und so entstehen die Strings mit den Leerzeichen in der passenden Länge.

Nirgends ist eine Zahl zu sehen, das sollte man auch tunlichst vermeiden, die Formatierung geschieht immer

RELATIV ZU DEN #define Präprozessor-Definitionen, weil man so das Maximum an Flexibilität bei Programmänderungen sowie die größte Stabilität bei jeder Art von Programmänderung erreicht.



void init_formatstrings()
{
int i;
for (i=0;i<=KON_BREITE;i++)
{
scrblank[i]=BLANK;
}
scrblank[KON_BREITE+1]=EOS;


strncpy(menublank,scrblank,MENUS); menublank[MENUS]=EOS;
strncpy(progblank,scrblank,PROGS); progblank[PROGS ]=EOS;
strncpy(helpblank,scrblank,HELPS); helpblank[HELPS]=EOS;
strncpy(infoblank,scrblank,INFOS); infoblank[INFOS]=EOS;
strncpy(olwblank,scrblank,OLWS); olwblank[OLWS]=EOS;


}



Man sieht, bei strncpy bleibt es nicht (kopiert n Zeichen vom zweiten in den ersten STring), sondern es wird die Stringende-Markierung EOS explizit gesetzt.

Das empfiehlt sich bei C IMMER!!!!!!!!!!

Schlimmstensfalls ist es Hosenträger mit Gürtel. Besser als ohne Hose dastehen.
Angehängte Datei(en)
Angehängte Datei  OLW.jpg ( 73.97KB ) Anzahl der Downloads: 37
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 01.02.2012, 18:24 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Wie man das OLW wegbekommt, IM PRINZIP, sieht man in Abb. 2 und 3.

Erstmal überschreiben wir unseren PROG Bereich wieder mit Leerzeichen (andere Funktion derzeit noch nicht verfügbar), ABB1, und man sieht, das OLW ist im linken Bereich verschwunden, d.i. überschrieben.

Dazu reichen zwei Funktionsaufrufe:

gotoprog(); // setze Cursor in den Bereich PROG, wo immer der auch sein möge
clrprog(); // überschreibe den Bereich mit Leerzeichen

Da das OLW auch noch den Bereich HELP überdeckt, muß dieser ebenfalls ge"clear"t werden:

gotohelp();
clrhelp();



Wie die Funktionen funktionieren (alles ziemlich simpel) hab ich oben schon reinkopiert.

Ergebnis vom ÜBerschreiben des HELP Bereichs zeigt Abb 2: das OLW wurde pulversiert wink.gif

Gut und schön,weil, es fehlt natürlich was.

Denn mit dem CLEAR SCREENBEREICH sind natürlich auch die Daten aus den Fensterbereichen verloren.

Das ist nicht das, was wir wollen. Wir wollen natürlich an derselben Stelle weitermachen, wo wir beim Aufruf des OLW gestanden haben.

Da der Programmierer nicht zu jedem Zeitpunkt wissen kann, was der ANwender in den verfügbaren Fenster-Bereichen aufgerufen hat, und bei der Programmentwicklung auch nicht absehbar ist, wann das OLW erlaubt sein darf, oder ob vielleicht noch andere OLW dazukommen, gibt es softwaremäßig eigentlich nur eine vernünftige Lösung für das PRoblem.

WIR MÜSSEN ALLE AUSGABEN STÄNDIG IM ZWISCHENSPEICHER VORHALTEN, damit wir sie zu jedem Augenblick des PRogramms wiederherstellen können.

Das löst man ganz einfach so, daß man

NIEMALS DIREKT AUF DEN BILDSCHIRM SCHREIBT, SONDERN IN EINEN BUFFER (ZWISCHENSPEICHER)

statt also:

FALL A

1. gotoxy(zeile, spalte)

2. gib irgendwas auf den Bildschirm

muß es heißen:

FALL B

1. Nimm eine Information

2. Schreibe sie in den Zwischenspeicher

Im Fall B ist dann von der Info am Bildschirm noch nichts zu sehen. Sondern wir füllen erst den Buffer mit Infos, und wenn wir fertig sind sagen wir:

3. Schreibe Buffer auf den Bildschirm (automatisch in den dafür vorgesehenen Bereich)

Das klingt etwas umständlich, ist es aber gar nicht.

Der Vorteil, wenn wir Infos stets zwischenspeichern, ist z. B., daß wir diese auch jederzeit in eine Datei speichern und ausdrucken können.

Hier allerdings ist es erstmal angedacht, um den Bildschirminhalt jederzeit wiederherstellen zu können.

Ob es sich dabei um eine grafische Oberfläche handelt mit Pixeln oder eine Textausgabe, ist dabei wurst.

Wir haben eine Matrix von:

z1 =sssssssssssssssssssssssssssssssssssssssssssssssssssssssss
z2=sssssssssssssssssssssssssssssssssssssssssssssssssssssssss
z3=sssssssssssssssssssssssssssssssssssssssssssssssssssssssss
...
zn=sssssssssssssssssssssssssssssssssssssssssssssssssssssssss

n Spalten und Zeilen, also eine zweidimensionale Tabelle.

Das Problem ist eher, daß wir nicht wissen, von welchem Datentyp.

C ist ja geradezu extrem typenbezogen.

Eine Funktion zu schreiben, die verschiedene Datentypen behandeln kann, nennt man ÜBERLADEN.

OVERLOAD

Man benutzt sie so, daß die Funktion impliizit den Datentypen erkennt und danach handelt.

Mag ja gut sein und modern, ist mir aber zu modern und ich rieche bei solchen Konstrukten immer INSTABILITÄTEN. Sobald da der kleinste Fehler drin ist, spielt das Programm Mickeymouse.

BLeibt die Frage, wie wir an eine einzige Funktion z., B. solche häufigen Datentypen wie float, integer, char, stringarrays etc. etc. übergeben können.

Die Anwort ist ganz einfach:

Wir übergeben sie im Textformat, weil ja die Ausgabe auch im Textformat erfolgt.

Für das Umwandeln belieber Datentypen in formatierter Form bietet C die hervorragende Funktion sprintf()

char info[255];

(float) f= -136.1 sprintf(info,"%10.2f",f) ergibt einen Zeichenstring der Form: " -136.10"

(int) i= 4711 sprintf(info,"%8i",i) ergibt " 4711"

(char) c='#" sprintf(info,"%c",c) ergibt "#", wobei "#" nicht gleich '#' ist, das eine ist ein char, das andere ein String

Um stringarrays in stringarrays umzukopieren:

(char) text[50]="HALLO";
sprintf(info,"%20s",text) ergibt einen rechtsbündigen String etwa " HALLO";

linksbündig:

sprintf(info,"%-20s",text) ergibt "HALLO "

Vorzeichen erzwingen:

für f= 5.1 (vielleicht Prozent)
sprintf(info,"%+10.1f",f) kommt heraus: " +5.1";

Und so weiter.

Sprintf() ermöglicht uns, jeden Datentyp in formatierter Form als Textstring darzustellen (nichts anderes ist ja Drucker- und Bildschirmausgabe) und bietet alles, was das Herz braucht. wink.gif

Die Funktion, die nun den Buffer für den Fensterbereich verwaltet, muß folgendes leisten:

1.) Prüfen, ob der WUnsch des AUfrufers realisierbar ist oder zu Fehlern führt, Länge der Nachricht, Position, Breite des Fensters etc. etc.
2.) Wenn ok, dann den Wert dort unterbringen, in Zeile und Spalte, wie gewünscht
3.) Die Daten auf dem Bildschirmbereich ausgeben, für den der Buffer zuständig ist.

Die Größe eines solchen Buffers muß mindestens der Größe (Zeilen/Spalten) des Fensterbereichs entsprechen. Es liegt aber ja auf der Hand, daß man besser viel mehr Kapazität wählt, mit dem Vorteil, daß man dann den Buffer in dem Fenster nach oben und unten scrollen kann.

Also sagen wir mal: für einen Bildschirmbereich von 80 Spalten 20 Zeilen könnten wir einen Buffer vorsehen, der 80 Spalten und 200 Zeilen aufnimmt, und hätten dann zum Scrollen und Blättern 10 Seiten zur Verfügung. Oder 100. Je nachdem, wie man es braucht.

Das wäre dann die nächste allgemeine Routine, die zu schreiben wäre.

Bisher noch keine einzige Programmzeile, alles nur Vorbereitung.

Alles kommt zu dem, der warten kann (lao tse). wink.gif
Angehängte Datei(en)
Angehängte Datei  olw2.jpg ( 63.94KB ) Anzahl der Downloads: 16
Angehängte Datei  olw3.jpg ( 60.57KB ) Anzahl der Downloads: 13
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 02.02.2012, 20:07 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Aufgabe also wie oben beschrieben. Wer benötigen für alle Fenster Buffer, d.s. Zwischenspeicher, um den Bildschirminhalt dieser Fenster, wenn er durch überlappende Fenster zerstört wurde, wiederherzustellen.

Sowas zu programmieren, als PROTOTYP, ist schnell gemacht. Die Funktion dann in allen EInzelheiten anzupassen und zu verfeinern, verlangt mehr AUfwand. Allerdings man hat entweder Spaß an der Freud oder geht abends ein Bierchen trinken. Das eine wie das andere kann nützen. wink.gif

Ich stelle jetzt mal den PROTOTYP vor und beschreibe dann, wie man dahin kommt.

Wir sehen in Abb. 1, daß das Hauptfenster unten links, was dem Dialog mit dem Anwender dient, Daten enthält, und zwar pro Forma mal 3 Zeilen mit Text und Zahlen.

Dann überlappt in Abb. 2 unser Overlapping Window OLW, so daß der Text zeilweise zerstört ist.

Beim nächsten Druck auf die Taste wird das Originalfenster wiederhiergestellt, das OLW verschwindet (Abb. 3)

Das ist das, was wir haben wollen.
Angehängte Datei(en)
Angehängte Datei  DIALbuf1.jpg ( 72.33KB ) Anzahl der Downloads: 13
Angehängte Datei  DIALbuf2.jpg ( 78.54KB ) Anzahl der Downloads: 14
Angehängte Datei  DIALbuf3.jpg ( 71.16KB ) Anzahl der Downloads: 16
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 02.02.2012, 20:14 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Um das zu verdeutlichen, wie das verschwindet, damit man nicht denkt, Abb.3 sei eine Kopie von Abb.1, hier der entscheidende Zwischenschritt:

1. Schritt: stelle Bildschirminhalt vom ersten überschriebenen Bereich wieder her.

Man sieht, wie das OLW hier teilweise zerstört wird, der Rest hängt rechts noch auf dem Bildschirm herum, aber auf einem anderen Bildschirmbereich. Der Bereich Programmdialog links ist wiederhergestellt, und hat mit dem Bereich rechts nicht zu tun. Den wiederherzustellen, verlangt den Aufruf der dazu passenden Funktion für diesen Bereich.

Das erfolgt dann im 2. Schritt.

Überlappt das OLW mehr als zwei Bereiche, geht das sinngemäß so, daß man eben alles wiederherstellen muß, was überlappt wurde.

Aus dem Ganzen, wenn man nicht weiß, was alles überlappt wurde (nach Programmänderungen), könnte man natürlich eine übergeordnete Funktion zusammenstellen namens

SaveScreen ()

bzw.

RestoreScreen()

die dann eben alle Bereiche sichert und anschließend wiederherstellt.

Der Beitrag wurde von sharky bearbeitet: 02.02.2012, 20:18 Uhr
Angehängte Datei(en)
Angehängte Datei  DIALbuf4.jpg ( 76.38KB ) Anzahl der Downloads: 12
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 02.02.2012, 21:12 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Die dazu gehörigen (selbstgeschriebenen) Funktionsaufrufe sind diese:

zeigeDIAL(); // zeige den Inhalt des DIALogfensters

... drücke Taste

clrOLW(); // überblende das OLW im Zustand Leerzeichen

... drücke Taste

zeigeDIAL(); // zeige den Inhalt des DIALogfensters nochmal neu

Mit dem zweiten Aufruf des DIALogfensters wird der darüber liegende Teil des OLW zerstört, oder anders gesagt, der ursprüngliche Inhalt des Bereichs wiederhergestellt wird. Nochmal: es ist von der Organisation her egal, ob es sich um einen Textbildschirm oder um eine grafische Ausgabe (im Grafik-Modus) handelt. Das Prinzip ist exakt dasselbe. Wir haben im Grafik-Modus nur mehr Pixel, d. i. Spalten und Zeilen.

Wie man sowas organisiert, ist auch Geschmackssache. Ich bevorzuge bei der PRogrammentwicklung FLACHE HIERARCHIEN, meide also extreme Verschachtelungstiefen, jede Art von eierlegenden Wollmilchsäuen mit einer endlosen Parameterliste, mit anderen Worten alles, was das PRogramm schwer lesbar macht und zu seltsamen Seiteneffekten führen kann.

Daher wird für jeden Bildschirmbereich eine eigene Routine geschrieben, die man separat aufrufen kann/muß. Der Nachteil ist, daß man ZUNÄCHST MAL identischen Code an verschiedenen Stellen ablegt, der Nachteil muß aber kein Nachteil BLEIBEN, wenn man später die einzelnen Bereiche eben nicht mehr identisch organisieren will, sondern spezifizieren. Würde man eine einzige Funktion für die Organisation aller bestehenden und zukünftigen Programmbereiche schreiben wollen, wäre das Ergebnis die besagte eierlegende Wollmilchsau, alias ein sehr schwer lesbarer und fehleranfälliger Code.

Hier ist eine einfache Konstruktion:

int putsDIAL(char info[255],int spalte, int zeile)

Was sie macht, sieht man direkt: sie bekommt eine Information, die sie in Spalte x und Zeile y unterbringen soll,, und zwar in dem Bildschirmbereich DIAL(og). Da diese Routine nur für diesen Bereich zuständig ist, kennt sie auch die verfügbaren Spalten und Zeilen sowie die Bildschirmkoordinaten. Das kann gekapselt bleiben, wir müssen uns darum nicht kümmern.

Wir schreiben also in diesen DIAL Bereich des Bildschirms etwas hinein, nämlich Daten und Zahlen, und zwar im Textformat.

Z. B. so:

char info[255];

float se=1582.53;
float sa=825.12;

resetDIAL();

sprintf(loc_info,"%s","Betriebsergebnis"); // Überschrift
putsDIAL(loc_info,5,1);

sprintf(loc_info,"Summe Einnahmen %10.2f",se); // Fließkomma in Text umwandeln
putsDIAL(loc_info,5,5);

sprintf(loc_info,"Summe Ausgaben %10.2f",sa); // dito
putsDIAL(loc_info,5,7);

sprintf(loc_info,"Ergebnis %10.2f",se-sa); // Statt einer 3. variablen Saldo einfach Subtraktion reinsetzen
putsDIAL(loc_info,5,9);

Damit ist jetzt zunächst noch nichts SICHTBARES erreicht, die Info liegt in dem Speicherbereich, der für den Bildschirmbereich zuständig ist.

Damit man das sehen kann, ruft man die Funktion auf, die den Buffer auf dem Bildschirm ausgibt, nämlich:

zeigeDIAL();

und schwupps, erscheinen die Daten auf dem Bildschirm. Sie sind aber eben nicht flüchtig, sondern wenn sie überlappt werden durch andere Fenster, kann man den ganzen Bereich durch die Wiederholung des Befehls wiederherstellen:

zeigeDIAL();


Wie sieht die Funktion aus, die die Daten in den Buffer schreibt?

Dazu erstmal die Definition des Buffers selbst:


char MENUbuf[MENUZ][MENUS+1];
char DIALbuf[DIALZ][DIALS+1];
char INFObuf[INFOZ][INFOS+1];
char MBOXbuf[MBOXZ][MBOXS+1];



Das sind die 4 Buffer für die 4 Bildschirmbereiche. Sie enthalten soviele Zeilen und Spalten, wie dem Bereich entspricht. Wieviele, ist durch #define-Präprozessor-Anweisungen festgelegt, man sollte feste Zahlen vermeiden.

Bitte das +1 beachten! Viele Programmierer sind zu C++ geflüchtet, weil sie mit den C-arrays nicht länger zu tun haben wollten. Und schreiben dann C ind C++ Dialekt. Ich hab den anderen Weg genomen und bin zu reinem ANSI-C zurück. Zu dem Problem CHAR-ARRAYS in ANSI C gleich noch ein kleines Schmankerl.

Nun die Funktion, welche sozusagen

gotoxy(Zeile,Spalte)
Printf(Inhalt)

simuliert, indem sie dasselbe tut, aber nicht auf den Bildschirm schreibt,, sondern in den Zwischenspeicher:

int putsDIAL(char info[255],int spalte, int zeile)
{
char c;
int s; // s=SPalte
int pruef = OK;
// 1.: Prüfung, ob Anforderung realisierbar oder nicht:
if (strlen(info)+spalte>DIALS) pruef=NOTOK; // Paßt nicht in die Zeile rein
if (zeile>DIALZ) pruef =NOTOK; // DIALS und DIALZ sind die max verfügbaren Spalten/Zeilen im Bereich DIALog
// ... und so weiter, was nötig ist
if (pruef==NOTOK) return NOTOK; // Funktion bricht hier ab, keine else-SChleife erforderlich
for (s=0;s<strlen(info);s++) DIALbuf[zeile][spalte+s]=info[s]; // zeichenweises Kopieren
DIALbuf[zeile][DIALS]=EOS;
return OK;

}


Sie prüft also erstmal, passen die übergebenen Zeichenketten überhaupt rein, und wenn nicht, macht sie gar nichts, bricht ab.

Dann werden die Zeichen aus dem übergebenen Text zeichenweise kopiert. Und zwar so:

Text: "Hallo"

ANgefordert ist Hallo soll in Spalte 3, Zeile 5 stehen.

Dann wird das 'H' um die Position int spalte korrigiert kopiert, also

Quelle: "Hallo"
Ziel:__"_____Hallo"

Eigentlich selbsterklärend (die Unterstriche mache ich, damit der Browser hier im Forum mir das nicht linksbündig setzt, was er leider tut):

Bevor wir die Zeichen reinkopieren in diese Matrix aus Spalten und Zeilen, müssen wir natürlich eine Funktion haben, die die vorhandenen Zeichen löscht.

Das ist diese:

void resetDIAL()
{
int z;
for (z=0;z<DIALZ;z++) sprintf(DIALbuf[z],DIALblank);
}


Das ist vielleicht etwas unübersichtlich, wenn man sich mit C-Char-Arrays nicht auskennt.

Es gibt verschiedene Möglichkeiten, char-Arrays zu organisieren.

Die hier verwendet wurde, ist diese:

char meine_matrix [ZEILEN][SPALTEN]

Die kann man so ansprechen:

printf(meine_matrix[0]);

gibt die Zeile 0 aus, also die erste.

Übersichtlicher oder sagen wir mal überdeutlicher wäre folgende Organisation (die ich nicht gewählt habe, weil mir zu umständlich):

typedef meine_textzeile[50];
meine_textzeile textzeile[20];

Würde man so anspreichen:

int n=0;
printf(textzeile[n]);

Nach dem Prinzip des Ockhamschen Rasiermessers mache ich es so flach und einfach wie möglich. Kein Schwulst, daher:

char textmatrix[ZEILEN][SPALTEN] und nichts sonst.

Natürlich, wenn man Zeilen und Spalten verwechselt im Programm, gibt´s Mist, nur die Sprache C wurde nicht entwickelt, damit der Programmierer das DENKEN EINSTELLEN KANN.

Das ist gut so, denn sonst würde es ja keinen Spaß machen. wink.gif

Der Beitrag wurde von sharky bearbeitet: 02.02.2012, 21:14 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 02.02.2012, 21:49 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Zum Thema Char-Arrays in C und die Fluchtmöglichkeit STrings in C++

Die C-char-arrays haben es natürlich in sich, wo Rauch ist, ist auch Feuer.

Das schlimmste, was man machen kann, ist Bereichsüberschreitung. Dann schaufelt das Programm MÜLL IN DEN SPEICHER,

ohne daß der Compiler oder zur Laufzeit irgendeine Fehlermeldung kommt.

Das ist eben der Charme von C. wink.gif

Daß der Programmierer seinen Kopf nicht nur zum Haareschneiden hat. wink.gif

Mal so eine Tücke vorgestellt, mit der man ein PRogramm regelrecht abschießen kann:

Das Datum "xx.xx.xxxx" hat 10 Zeichen. Nämlich 8 Zahlenwerte und zwei Punkte =10.

Wir sagen:

char datum[10]="01.01.2012";

Und sagen:

printf(datum);

Alles bestens!

Und fragen:

printf("%i", strlen(datum)); mit der Ausgabe : 10, weil 10 Zeichen lang

Jetzt lassen wir den Compiler mal selbst entscheiden, wieviele Stellen er für das char-array haben will:

char datum2[]="01.01.2012"; // der Compiler reserviert soviel Speicher, wie er für richtig hält, daher ist in den [] keine Zahl genannt

und fragen:

printf("%i",strlen(datum2)); mit der Ausgabe 10, weil 10 Zeichen lang.

Alles klar? Alles bestens?

Mit solchen Konstruktionen kann man wie gesagt ein ganzes Programm regelrecht abschießen.

Die Funktion strlen sagt nämlich überhaupt nichts über den reservierten Speicherbereich aus, sondern nur über die Anzahl ZUFÄLLIG VERSAMMELTER ZEICHEN.

Wenn wir wissen wollen, was da wirklich los ist, müssen wir den

OPERATOR sizeof() bemühen.

Machen wir das mal:

printf("%i",sizeof(datum)); // mit der Ausgabe 10

printf("%i",sizeof(datum2)); // mit der Ausgabe 11

HUCH?

Ja, das sind die C char arrays.

Wir brauchen 10 für den Inhalt und Nr. 11 für den END OF STRING '\0'

Nehmen wir char datum[10] ist das PRogramm schon im A...., weil bei jedem Aufruf der Funktion die Stringende-Markierung ins Nirwana des Rechenspeichers geschaufelt wird.

SCHLIMM, SCHLIMM,SCHLIMM

Weils weder der Compiler merkt, noch zur Laufzeit Fehlermeldung erscheint und dreifach SCHLIMM, weil das Programm durch diese einsamen Zeichen nicht gleich abstürzen wird.

Es macht nur seltsame Ergebnisse, es arbeitet nicht zuverlässig, ist nicht stabil.

Man kann ja mal versuchen, die Fehlerursache, ein nicht druckbares Zeichen '\0' zu finden!

Daher also immer +1


[size="4"][/size]

Und wenn man zwar weiß, was geschehen soll, aber nicht abschätzen kann, was geschehen wird, z. B.:

Wir haben ein Fenster von Breite 80 Zeichen.

Und kopieren da Infos im Textformat rein, von denen wir annehmen, die seien immer kleiner als 80 Zeichen, sagen wir:

sprintf(textbuffer,"%10.2f",floatx);

Dann haben wir spätestens dann verloren, wenn wir Zuweisungen machen wie:

sprintf(textbuffer,andererbuffer);

Oder strcpy(textbuffer,quellbuffersowieso);

Wenn der textbuffer 80 Zeichen aufnimmt, und der Quellbuffer 120 Zeichen lang ist, schaufeln wir 40 Zeichen Müll in den PRogrammspeicher, und überschreiben damit unkontrolliert reservierte Speicherbereiche.

Absoluter Horror!

Daher sollte man für solche Primitiv-Formate wie char info[] immer üppig Platz vorhalten, z. B. 255 Zeichen vorsehen. Ist zwar keine Garantie, daß da keine logischen Fehler erfolgen, aber das Programm läuft dann wenigstens stabil.

Wenn man zeichenweise kopiert

for (i=0;i<strlen(irgendwas),i++) ziel[i]=quelle[i];

sollte man immer daran denken, die END OF STRING Markierung '\0' explizit zu setzen, nämlich:

ziel[strlen(quelle)+1) = '\0';

Dann brennt da nichts an.

Und wenn man die char-arrays (was besonders bei Dateien natürlich nötig bzw. sinnvoll ist) in der Länge knapp am Nötigen kalkuliert, sollte man an die +1 denken.

Also wie gesagt, zu den char-arrays in C sind die Meinungen geteilt.

Meine Meinung habe ich geäußert. Diese Dialekte, C++ PRogrammierung, wo dann doch keine OOP drin ist, sondern nur Dialekt, ein paar Funktionen übernommen werden, MISCHMASCH-PROGRAMMIERUNG, ist nicht mein DIng. Das muß aber jeder für sich selbst entscheiden.


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 02.02.2012, 22:15 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Zurück zu der Funktion, die unseren Bildschirmbereich puffert.

Um die Kommentare bereinigt, bleibt davon:


int putsDIAL(char info[255],int spalte, int zeile)
{
int s;int pruef = OK;
if (strlen(info)+spalte>DIALS||zeile>DIALZ) pruef=NOTOK;
if (pruef==NOTOK) return NOTOK;
for (s=0;s<strlen(info);s++) DIALbuf[zeile][spalte+s]=info[s];
DIALbuf[zeile][DIALS]=EOS;
return OK;
}



Die können wir natürlich nun per copy&paste sowie suchen und ersetzen für alle anderen Bildschirmbereiche übernehmen.

Bezeichner für die BIldschirmbereiche (ich hab die Namensgebung etwas geändert) sind jetzt:

MENU // für Menüführung
DIAL // für Dialog mit dem Anwender bzw. die eigentliche Programmausgabe
INFO // für zusätzliche Informationen und Auswahlfelder, hier der Kontenrahmen SKR03, der eingeblendet wird, damit wir die richtige Konto-Nr wählen können
MBOX// Message-Box für HInweise und Meldungen im Programmverlauf

Das Format des Bildschirms ist 120 Spalten = DINA4QUER.

Wollen wir WYSIWYG (what you see is what you get) die Druckausgabe am Bildschirm vorher sehen, müssen wir umschalten auf ein DIAL_QUER von 120 Zeichen. Das kommt dann später und ist auch mit Copy&paste zu haben.

Wir kopieren jetzt die Funktion aus dem DIAL für den nächsten Bereich INFO:


int putsINFO(char info[255],int spalte, int zeile)
{
int s;int pruef = OK;
if (strlen(info)+spalte>INFOS||zeile>INFOZ) pruef=NOTOK;
if (pruef==NOTOK) return NOTOK;
for (s=0;s<strlen(info);s++)INFObuf[zeile][spalte+s]=info[s];
INFObuf[zeile][DIALS]=EOS;
return OK;
}



Keine große Nummer.

Weil die Variablen alle standardisierte Bezeichner haben, wo man nur PROG gegen INFO austauschen muß und fertig.

Der nächste Schritt wäre dann, daß man die Fenster scrollt.

Und da hätten wir dann schon die Spezifizierung, nämlich die Message-Box MBOX zu scrollen wird wenig Sinn machen.

Ob man das Hauptfenster scrollen soll, nun, weiß ich noch nicht.

Das Scrollen funktioniert im Prinzip so:

Wir haben einen Buffer, der länger ist als der sichtbare Bereich.

Sagen wir, der sichtbare Bereich sei 20 Zeilen (20 darf man natürlich NIEMALS reinsetzen, sondern entsprechend der Präprozessor-Direktive die Konstante dafür, z. b. ZEILEN_BEREICH1), und der Buffer 200 (=ZEILEN_BEREICH*10), hätten wir 10 Seiten zum scrollen (ob das sinnvoll oder nötig ist, wie gesagt ...). Und könnten auch überlegen, soll man seitenweise scrollen oder zeilenweise.

Wir könnten dann die Taste Bildunten verknüpfen mit der Anweisung, daß die nächsten ZEILEN_BEREICH1 Zeilen angezeigt werden, und Bildoben mit der Anweisung, ZEILEN_BEREICH1 Zeilen zurückzuspringen bzw. zur ersten Zeile (wenn wir oben anstoßen), und Pfeiloben und Pfeilunten für zeilenweises Scrollen.

Ob das nun Sinn macht oder nicht, stellt sich oft erst später heraus. Daher werde ich unabhängig davon, ob es Sinn macht, nur aus formalen Gründen, für die Bereiche die Funktionen programmieren, damit man, wenn man aus der FORMALEN PROGRAMMIERUNG rausgeht und die VERARBEITENDE PROGRAMMIERUNG angeht, sich um solche Details nicht mehr kümmern muß und die Funktionen bei Bedarf verfügbar hat.

Das Datenformat für solche kleinen Datenbestände ist sinnvollerweise ein Index. Hätten wir es zu tun mit möglicherweise sehr großen Datenbeständen, wäre es sinnvoll, da mit dynamischen Listen zu lösen. Brauchen wir hier nicht.

Der Umgang mit dynamischen Listen folgt aber noch, nämlich in dem Zusammenhang, daß man Dateien, welche auf dem Speichermedium (Festplatte, USB-STick oder sonstwas abgelegt sind), einlesen und sortieren muß. Und das muß man, bei einer FIBU sollte das chronologisch sortiert sein, weil ja die Buchungen auch mit zurückliegenden Datumsangaben eingegeben werden können, das beim Sichten der Daten aber sehr irritierend ist, wenn man sie in der Reihenfolge der Eingabe sieht.

Kommt alles noch. Hab ich längst programmiert und ist schon in Betrieb, hier im Beitrag aber ist das noch zu früh. Erstmal kommen die Formalien der Darstellung auf dem Bildschirm, dann geht es an die eigentliche Datenverarbeitung.

Der Beitrag wurde von sharky bearbeitet: 02.02.2012, 22:29 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 02.02.2012, 22:47 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Hält man die Hierarchie flach, entstehen die Funktionen durch Kopieren und Auswechseln der MÖGLICHST STANDARDISIERTEN BEZEICHNER, wie hier an der Farbgebung sichtbar gemacht.

Funktion für Bildschirmbereich 1 (DIAL), d. i. Dialog, der eigentliche PRogrammbereich:


int putsDIAL(char info[255],int spalte, int zeile)
{
int s; int pruef = OK;
if (strlen(info)+spalte>DIALS|| (zeile>DIALZ) pruef =NOTOK;
if (pruef==NOTOK) return NOTOK;
for (s=0;s<strlen(info);s++) DIALbuf[zeile][spalte+s]=info[s];
DIALbuf[zeile][DIALS]=EOS;
return OK;
}


Durch Suchen/Ersetzen entsteht fehlerfrei, da standardisiert, die Funktion für den nächsten PRogrammbereich INFO

int putsINFO(char info[255],int spalte, int zeile)
{
int s; int pruef = OK;
if (strlen(info)+spalte>INFOS|| (zeile>INFOZ) pruef =NOTOK;
if (pruef==NOTOK) return NOTOK;
for (s=0;s<strlen(info);s++)INFObuf[zeile][spalte+s]=info[s];
INFObuf[zeile][INFOS]=EOS;
return OK;
}


Die Nachteile wurden schon genannt, würde man die erste Funktion ändern, und die Änderungen für alle folgenden übernehmen wollen, würde man die Clons mit der Hand ändern müssen, was immer so eine Sache ist. Sie würden sich nicht automatisch ändern.

Man kann aber, bei flachen Hierarchien, diese durchaus löschen und durch neue Kopien ersetzen.

Die in 20facher Verschachtelungstiefe angelegte eierlegende Wollmilchsau ist das andere Konzept, nun, wer´s mag.

Was ich überhaupt nicht mag, sind weniger die Verschachtelungstiefen als ellenlange Parameterlisten beim Aufruf einer Funktion, und je komplexer die ist, umso länger muß die Parameterliste sein, oder man verdeckt das, indem diese Funktion nicht sichtbar auf bestimmte globale Speicherbereiche Zugriff hat.

Das ist auf gut Deutsch gesagt für die Hobbyprogrammierung alles MIst, weil die Transparenz verloren geht und das in den kryptischen Bereich rückt.

Ist aber wie gesagt Ansichtssache. Es gibt bestimmt Leute, die sind voll vom Gegenteil überzeugt.


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 03.02.2012, 07:28 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Kurze Zusammenfassung, was bisher erreicht wurde.

Die Funktionen wurden für das Fenster DIALog geschrieben und getestet.

Danach werden sie per Copy&Paste und Suchen/Ersetzen für die anderen Bildschirmbereiche umkopiert. Damit können wir beliebig viele Bildschirmbereiche und beliebig viele Überblend-Fenster verwalten, ohne eine Zeile zusätzlichen Code zu schreiben.

Die Buffer werden nun zunächst mit Daten gefüllt.

Wir sagen, anstatt gotoxy(s,z) printf(irgendwas):

putsDIAL, schreibe den String in den Buffer DIAL, oder INFO, oder OLW

Das sieht dann so aus:

int main(int argc, char *argv[])
{
char loc_info[255];
char c;
init(); // die Initialisierung aller Variablen

float se=1582.53;
float sa=825.12;

resetDIAL();
putsDIAL("Betriebsergebnis",5,1);
sprintf(loc_info,"Summe Einnahmen %10.2f",se);
putsDIAL(loc_info,5,5);
sprintf(loc_info,"Summe Ausgaben %10.2f",sa);
putsDIAL(loc_info,5,7);
sprintf(loc_info,"Ergebnis %10.2f",se-sa);
putsDIAL(loc_info,5,9);

resetINFO();
putsINFO("Umsatzsteuer-Konten:",0,1);
putsINFO(" 7 % aufzuteilen 1561",0,3);
putsINFO("19 % aufzuteilen 1566",0,5);
putsINFO(" 7 % direkt abziehbar 1571",0,7);
putsINFO("19 % direkt abziehbar 1576",0,9);

resetOLW();
putsOLW("Hinweis: ",5,1);
putsOLW("Vermutlich logisch falsche Buchung",5,3);
putsOLW("Konto 8200 war bisher Einnahmenkonto, wird aber belastet",5,4);
putsOLW("Buchungssatz Nr. 364 vom 18.01.2012",5,5);


Man erkennt, die Funktion sprintf() als zusätzliche Zeile benötigen wir nur, wenn wir andere Datentypen (z.B. Zahlen) in Text formatieren wollen. Haben wir es sowieso mit Text zu tun, können wir

anstelle von putsINFO(loc_info,zeile,spalte);

auch gleich den Text eingeben: putsINFO("Mein Text",zeile,spalte);

Somit sind wir beim Programmieren sogar wesentlich ökonomischer als mit dem Begriffspaar gotoxy() und printf()

Wenn wir mit den Daten soweit fertig sind, zeigen wir sie auf dem Bildschirm, indem wir die Ausgabefunktion zeige... aufrufen.

Wie gesagt, wir müssen uns nicht kümmern, wie die Funktion das macht oder wo der Bereich ist, einfach nur aufrufen:


zeigeDIAL();
zeigeINFO();

c=getch(); // Tastendruck


(Abbildung 1).

Wir warten auf den Tastendruck und überblenden dann das OLW:

zeigeOLW(); // das OLW überblenden


Den zugehörigen Bildschirm zeigt Abb. 2

Wenn wir dann erneut eine Taste drücken, wird der ursprüngliche Zustand wiederhergestellt (wie in Abb. 1) durch den erneuten Aufruf der Funktionen der beiden Fenster, die überblendet worden sind:



zeigeDIAL();
zeigeINFO();



Das ist schon alles.

Die Philosophie dahinter kann man so beschreiben:

Halte die Funktionen einfach und übersichtlich und strapaziere dich selbst (als Programmierer) nicht mit schwer durchschaubaren Referenzen und endlosen Parameterlisten.

Später mal könnte man das Design straffen und komprimieren, anstelle also die Funktionen per copy und paste zu vervielfältigen, wie hier realisiert:

void resetDIAL(){int z;for (z=0;z<DIALZ;z++) sprintf(DIALbuf[z],DIALblank);}
void resetINFO(){int z;for (z=0;z<INFOZ;z++) sprintf(INFObuf[z],INFOblank);}
void resetOLW(){int z;for (z=0;z<OLWZ;z++) sprintf(OLWbuf[z],OLWblank);}


int putsDIAL(char info[255],int spalte, int zeile)
{
int s;
int pruef = OK;
if (strlen(info)+spalte>DIALS||zeile>DIALZ) return NOTOK;
for (s=0;s<strlen(info);s++) DIALbuf[zeile][spalte+s]=info[s];
DIALbuf[zeile][DIALS]=EOS;
return OK;
}

int putsINFO(char info[255],int spalte, int zeile)
{
int s;
int pruef = OK;
if (strlen(info)+spalte>INFOS||zeile>INFOZ) return NOTOK;
for (s=0;s<strlen(info);s++) INFObuf[zeile][spalte+s]=info[s];
INFObuf[zeile][INFOS]=EOS;
return OK;
}


... könnte man diese Funktionen zu einer einzigen zusammenfassen per

switch (window)
{
case DIAL: ....
break;
case INFO: ....
break;
}

Nicht nur die Fenster separieren, sondern alle Befehle, die sich für alle wiederholen, ausklammern.

Das ist aber zum gegenwärtigen Zeitpunkt weder sinnvoll noch erforderlich.

Abb. 3 spare ich mir, sie ist ja identisch mit Abb. 1
Angehängte Datei(en)
Angehängte Datei  Zusammenfassung1.jpg ( 90.22KB ) Anzahl der Downloads: 13
Angehängte Datei  Zusammenfassung2.jpg ( 109.07KB ) Anzahl der Downloads: 15
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 03.02.2012, 16:12 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Scrollen

Das Scrollen ist die Anwort auf den Überhang von verfügbarem und sichtbarem Text. Ist der verfügbare Text größer, als er in den Ausschnitt paßt, bewegen wir den Ausschnitt im Text vor oder zurück.Wir können blättern oder zeilenweise verschieben. Oder zum Anfang und zum Ende springen.

Prototyp einer Funktion SCROLL:

setze den DATENZEIGER AUF EINEN WERT // WElchen, das wird noch die Frage sein

Endlosschleife

AUSGABE text ab DATENZEIGER

Tastaturabfrage ergibt:

option 1 DATENZEIGER -1 (1 Zeile zurück)
option 2 DATENZEIGER +1
option 3 DATENZEIGER +1Seite
option 4 DATENZEIGER 1 Seite zurück
option 5 DATENZEIGER springe an den Anfang
option 6 DATENZEIGER springe ans Ende
option 7 Schleife abbrechen

Ende der Endlosschleife

Die in der Endlos-Schleife zwangsläufig befindliche Funktion AUSGABE muß natürlich eine Bereichsüberprüfung machen, ob die Anforderung so machbar ist. Bereichsüberschreitungen in C sind IMMER der sichere Griff in die Kloschüssel. wink.gif

Das geht alles seinen Gang, bis der Prozeß abgebrochen wird zugunsten eines neuen. In dem Augenblick, in dem während des Scrollens ein neuer Prozeß aufgerufen wird, ist der Wert für DATENZEIGER zerstört. Denn beim Verlassen einer Funktion sind die WERTE ALLER LOKALEN VARIABLEN nicht länger definiert. Daher wird es uns mit LOKALEN Variablen nicht gelingen, den Bildschirm wiederherzustellen, um das OLW zu überschreiben. Diese Daten müssen während des Funktionsaufrufes irgendwo so gespeichert werden, daß man sie auch nach dem Verlassen der Funktion wieder auslesen kann. Und sie müssen nicht nur einmal gespeichert werden, sondern während des Prozesses ständig aktualisiert werden. Das kann man ziemlich einfach erreichen, indem man sie als GLOBAL deklariert.

GLOBAL hat Nachteile und Vorteile. Z. B. daß globale Variable mit lokalen Variablen konkurrieren und so Seiteneffekte entstehen können. .

Unbeabsichtigt, bei Allerweltsnamen, kann z. B. auf diese Weise Chaos entstehen:

int info; // Global, Allerweltsname

void MacheIrgendwas()
{

int info // LOkale Variable

info = irgendwas
...

Dann greift die Funktion auf die lokale Variable info zurück.


Geht allerdings durch Editieren die lokale Deklaration verloren, versehentlich:

void MacheIrgendwas()
{

// int info gibt es nicht mehr, wurde wegeditiert

info = irgendwas
...

dann gibt es keine Fehlermeldung, sondern die FUnktion greift auf die Globale Variable info zurück. Nun, vielleicht etwas theoretisch. Oder eben auch nicht. Shit happens.

Mehr praxisnah ist folgender Programmabschuß:

Die Funktion

void MacheIrgendwasAnderes()
{

INFO = irgendwas
...

greift völlig beabsichtigt und völlig korrekt auf die nunmehr durch GROSSBUCHSTABEN als GLOBAL gekennzeichnete Variable INFO zu.

So weit, so gut.

Nur wenn diese Funktion einen Fehler enthält, und zwar an einer völlig anderen Stelle, die mit dem Aufruf der globalen Variable überhaupt nichts zu tun hat, welche zu einem BEREICHSÜBERLAUF führt, und eine derart fehlerhafte Funktion nun auf globale Variablen zugreift, BREITET SICH DIE SEUCHE AUS, wir haben dann CHAOS auch im Speicherbereich der Globalen Variablen. Grundsätzlich ist es also nicht gut, WENN JEDE XBELIEBIGE FUNKTION einfach auf GLOBALE VARIABLEN zugreifen darf. Man sollte im Idealfall alle globalen Variablen so kapseln, daß der Zugriff auf sie nur über eine Schnittstelle möglich ist. Da eine Schnittstelle wieder zusätzlichen Organisatiionsaufwand und Parameterlisten mit sich bringt, muß man aber auch fragen, ob der Aufwand lohnt.

Bis dahin wäre die Frage, welche der bisher vorhandenen bzw geplanten Funktionen nun die GLOBALEN Variablen verändern darf und welche nicht. Die Funktion SCROLL fragt die Tastatur ab und meldet WÜnsche an, den Text ab Zeile ... zu lesen. Die Funktion AUSGABE prüft, ob das realistisch ist und gibt den Text aus oder verweigert das. Die Funktion SCROLL darf den Wert für STARTZEILE nicht verändern, weil ihre Anforderungen ja noch von der der Funktion AUSGABE überprüft und evtl. verworfen werden. Also macht das (zunächst mal) die Funktion Ausgabe, was mir nicht wirklich gefällt, weil AUSGABE keine datenverarbeitende, sondern eine reine OUTPUT, eine Bildschirm- Funktion ist.

Die Organisation der globalen Variablen erfolgt sinnvollerweise NIEMALS als eine Anhäufung von irgendwelchen Daten, die irgendwo im PRogramm zu finden sind, sondern strukturiert, d. i. auf einem Platz zu finden. Nennen wir diesen Platz mal REGISTER, im REGISTER finden sich alle GLOBALEN Variablen.

Wir haben zunächst die Anforderung, theoretisch, für n Bildschirmbereiche n INTEGER verfügbar zu machen für die aktuelle Startzeile. Aktuell ist, wenn der BIldschirm überlagert wird. Was in diesem Moment anliegt, muß anschließend wiederhergestgellt werden.

int bildschirmbereiche [n] ist genau die Art von Programmierung, die man vermeiden sollte, denn was ist bildschirmbereich[0]?

Nehmen wir mal den Datentyp enum:

enum screens = {DIAL, MENU, MBOX,INFO, OLW}

wird das schon sehr viel übersichtlicher.

Jetzt brauchen wir noch einen Speicherplatz, um die Startzeilen der bislang 5 Bildschirmbereiche zu speichern:

int zeile_aktuell[OLW];

Schräg, was? Das sieht sehr wackelig aus, funktioniert aber nicht nur, sondern ist solide Programmierung.

Man könnte das noch transparenter machen durch:

enum screens = {DIAL, MENU, MBOX,INFO, OLW, LASTSCREEN};

mit:

int zeile_aktuell[LASTSCREEN];

Ist aber übertrieben, nicht zielführend und nicht ausreichend, denn wir können ziemlich sicher sein, daß die GLOBALEN VARIABLEN nicht nur von einem Typ sein werden, daher:

struct REGISTER
{
int datenzeiger[OLW]; // zunächst mal nur die aktuellen Buf-Zeilen für die Bildschirmbereiche
}

struct REGISTER REG;

Dann können wir den Datenzeiger für den Bildschirmbereich OLW so ansprechen:

int buffer_pos;

buffer_pos= REG.datenzeiger[OLW];

... liefert den aktuellen Wert für den Buffer OLW, also ab welcher Zeile ausgegeben wird.

Sinn macht das Scrollen sicher NICHT beim Dialogfenster mit dem Anwender. Da werden Daten abgefragt und ausgegeben, um eine EIngabe zu erzielen.

Wo das SInn machen könnte, das wären die Info-Fenster oder das OLW.

Man könnte dann, im OLW, während der Bearbeitung z. B: einen Text von Charles Dickens lesen. wink.gif

Der Beitrag wurde von sharky bearbeitet: 03.02.2012, 16:21 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 10:20 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Implementation der Scrolling-Funktion

Um ein Scrolling zu realisieren, benötigen wir zunächst mal einen SPeicher für den Bildschirmausschnitt, welcher größer ist als der Bildschirmbereicht. Wäre das nicht so, müßte man nicht scrollen.

#define OLWBUFLEN 200
#define MTLGBUFLEN 200

char MENUbuf[MENUZ][MENUS+1];
char DIALbuf[DIALZ][DIALS+1];
char MTLGbuf[MTLGBUFLEN][MTLGS+1];
char MBOXbuf[MBOXZ][MBOXS+1];
char OLWbuf[OLWBUFLEN][OLWS+1];


Die Buffer-Länge (=spätere Anzahl der Textzeilen) wird als Präprozessoranweisung festgelegt. OLWBUFLEN bezieht sich auf den Bereich OLW, das überlappende Fenster bekommt also 200 Zeilen spendiert, ebenso das Fenster rechts, MTLG = MItteilungen. Die Buffer für die anderen Bereiche bleiben identisch mit der sichtbaren Zeilenhöhe, also DIALZ = Zeilen im Dialogfenster, identisch mit dem Speicher für dieses Fenster.

(MTLG statt INFO weil ich INFO info komplett rausgenommen habe wg. Seiteneffekten mit Windows-Systemaufrufen)

Als nächstes muß man die Initialisierungsroutinen anpassen:

void resetDIAL(){int z;for (z=0;z<DIALZ;z++) sprintf(DIALbuf[z],DIALblank);}
void resetMTLG(){int z;for (z=0;z<MTLGBUFLEN;z++) sprintf(MTLGbuf[z],MTLGblank);}
void resetOLW(){int z;for (z=0;z<OLWBUFLEN;z++) sprintf(OLWbuf[z],OLWblank);}


Die Textfelder werden mit BLANKS überschrieben, und für OLW und MTLG benötigt man nun den größeren Zähler für den höheren Wert 200. Die anderen Fenster bleiben wie sie sind, Anzahl der Textzeilen=Bildschirmausschnitt. Macht man das nicht, hat man es mit großen Mengen nichtinitialisierten Textfeldern zu tun, wovon DRINGEND abzuraten ist.

Als nächstes werden die PUT-STRING Funktionen angepaßt, welche die Infos nach SPalten und Zeilen formatiert in diese Buffer schreiben. Die sind ja noch auf ANZAHL-ZEILEN = BILDSCHIRMBEREICH gestellt und würden sonst den längeren Text ignorieren.

Für Zeilenüberlauf wurde festgelegt, daß der Text trotzdem genommen wird, aber linksbündig, und mit einer Bestrafungs-Markierung ASCII 219 versehen wird.

int putsMTLG(char nachricht[255],int spalte, int zeile)
{
int fehler=NEIN;
int s;
int pruef = OK;
int sp=spalte; // lokale Kopie
int len=strlen(nachricht); // strlen nur einmal aufrufen und Ergebnis ablegen
if (len>MTLGS||zeile>MTLGBUFLEN) return NOTOK; // zu breit btw. Bereichsüberschreitung
if (sp+len>MTLGS)
{
sp=0;
fehler=JA;
}// zu weit rechts platziert
for (s=0;s<len;s++) MTLGbuf[zeile][sp+s]=nachricht[s];
MTLGbuf[zeile][MTLGS]=EOS; // Stringendemarkierung explizit
if (fehler==JA)
{
MTLGbuf[zeile][0]=219; // Die BEstrafung: ASCII CODE 219
return NOTOK;
} // ASCII CODE 219 zur STrafe
return OK;
}


Eine Anmerkung dazu: Die Funktion strlen in einer lokalen Variable sp zu kopieren, scheint umständlich, ist aber guter Programmierstil. Wenn man strlen mehrfach benötigt, würde diese Funktion immer wieder neu aufgerufen, was für die Performance nachteilig ist. Ist die Info insgesamt zu lang für die Zeile, erfolgt keine Ersetzung sondern die Funktion bricht ab mit return NOTOK.

Um unseren Buffer sehen zu können, brauchen wir die Funktion zeigeOLW bzw. zeigeMTLG

Diese Funktionen, die bisher von 0 bis Bildschirmbereich Ende angezeigt haben, müssen auch angepaßt werden. Und zwar so, daß man ihnenn mitteilt, ab welcher Zeile sie auszugeben haben.

Statt:

void zeigeDIAL()
{

muß es jetzt heißen:

void zeigeMTLG(int bufzeiger)


Wir begeben uns nun in das Gefahrenfeld der Bereichsüberschreitung = PROGRAMMKILLER. Man fängt Bereichsgrenzen am besten am ENDE DER KETTE ab, also hier, bei der Ausgabe. Weil man sich nicht darauf verlassen kann, daß die vorangegangenen Funktionen durchgängig den Bereich abfangen.

Die Begrenzer sind nach vorn der Datensatz 0. Wenn also bufzeiger<0 wird er auf 0 gesetzt oder eine Fehlerroutine aufgerufen.

Der Begrenzer nach hinten ist die Anzahl Zeilen der Buffer, z.B. OLWBUFLEN. Der größte sinnvolle Wert ist hier nicht die letzte Zeile (weil dann der Bildschirm nicht gefüllt wäre), sondern OLWBUFLEN-OLWZ, also Dateigröße - Bildschirmausschnitt. Insofern also:

if (bufzeiger>OLWBUFLEN-OLWZ)bufzeiger=OLWBUFLEN-OLWZ; // überlauf verhindern
if (bufzeiger<0) bufzeiger=0; // Bereichsüberschreitung nach vorn abfangen


Wir könnten also, um an den Dateianfang zu springen, den Wert -1 übergeben, die Funktion fängt das ab und macht eine 0 draus, ebenso zur anderen Seite z. B. 10000 aufrufen, um an das Dateiende zu springen (bzw. das Ende des Textbereichs).

Nachdem die Komponenten soweit bereit stehen, kommen wir zur eigentlichen SCROLLING-Funktion.

Die Steuerung erfolgt sinnvollerweise mit den dafür standardisierten Tasten: PFEILOBEN = gehe 1 zurück, BILDOBEN gehe 1 Seite zurück und so fort. Die ASCII-Werte der Tastatur finden sich als Präprozessor-Anweisung:

#define ENTER 13
#define ESCAPE 27
#define BACKSPACE 8
#define PFEILOBEN 72
#define PFEILUNTEN 80
#define BILDOBEN 73
#define BILDUNTEN 81
#define POS1 71
#define ENDE 79
#define F1 59
#define F2 60
#define DEL 83
#define F1 59
#define F2 60
#define F3 61
#define F4 62
#define F5 63
#define F6 64
#define F7 65
#define F8 66
#define F9 67
#define F10 68
#define F11 -123
#define F12 -122


so daß wir sie in der Funktion wie folgt aufrufen können:

switch ( (int) taste)
{
case ESCAPE:
return;
break;
case BILDOBEN:
bufzeiger-=maxzeilen; // SEITE ZURÜCK
if (bufzeiger<=0)bufzeiger=0;
break;
case BILDUNTEN:
bufzeiger+=maxzeilen; // SEITE VOR
if (bufzeiger>max-maxzeilen)bufzeiger=max-maxzeilen;
break;
case PFEILUNTEN:
bufzeiger++; // Zeile vor
if (bufzeiger>max-maxzeilen)bufzeiger=max-maxzeilen;
break;


Wenn wir die SCROLL-Funktion für alle Bildschirmbereiche schreiben, müssen wir mitteilen, für welchen Bildschirmbereich der Aufruf gelten soll.

Dazu dient diese ENUM-Definition:

enum screens{MENU,DIAL,MTLG,MBOX,OLW};

int screen[OLW];



Der Kopf der SCROLL-Funktion sieht dann so aus:

void scroll(int screen,int bufzeiger)
{


Und die Sortierung der betr. Fensterdaten erfolgt so:

char taste;
int maxzeilen,max;
if (screen==OLW){maxzeilen=OLWZ;max=OLWBUFLEN;}
else if(screen==MTLG){maxzeilen=MTLGZ;max=MTLGBUFLEN;}
else return; // keine anderen Optionen vorgesehen

do
{
if (screen==OLW) zeigeOLW(bufzeiger);
else if (screen=MTLG) zeigeMTLG(bufzeiger);
else return;




Die zweite else if - Anweisung erscheint überflüssig. Ist sie auch, WENN der Programmcode so BLEIBT. Wenn man aber später mal was ändert und was übersieht, ist sie eben NICHT überflüssig. Bei der Bereichsprüfung kann man gar nicht sorgfältig genug sein,

Nun hatte ich schon bemerkt, die Bereichsüberprüfung der Ausgabedatei (bzw. Textspeicher) erfolgt am Ende der Kette. Man sieht hier aber, daß die Funktion SCROLL ebenfalls eine Bereichsprüfung durchführt, und die könnte man ja nun wirklich weglassen, oder?

Nein, kann man nicht. Zwar brennt bei der Textausgabe nichts an, weil die Funktionen den Bereich abfangen, aber unser Tastenwert würde sich beim Scrollen "auskuppeln". Z. B. wenn man 5x BILDOBEN drückt, steht der Bufzeiger bei -100, Drückt man nun Pfeilunten, um eine Zeile nach unten zu gehen, müßte man 100x drücken, bevor was passiert. Daher also ...


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 10:32 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Nachdem das alles soweit parat steht, sehen wir mal auf die Anweisungen vom Hauptprogramm (von wo das vorläufig noch aufgerufen wird, später natürlich hat alles seinen Platz).

Erstmal füllen wir die Bildschirmspeicher mit Daten (nur pro Forma):

resetMTLG();
int su=0;
for (i=0;i<MTLGBUFLEN;i++)
{
sprintf(loc_nachricht,"Mitteilung Nr. %4i",i);
putsMTLG(loc_nachricht,su,i);
su++;
if (su>15)su=0;
}
resetOLW();
for (i=0;i<OLWBUFLEN;i++)
{
sprintf(loc_nachricht,"Ueberblendfenster - nachricht Nr. %4i",i+1);
putsOLW(loc_nachricht,i,i);
}


Die Nachrichten rücken (pro Forma) pro Zeile 1 Stelle nach rechts und müssen dann natürlich irgendwann am rechten Bildschirmrand anstoßen. Geschieht dies, erfolgt unsere "Strafaktion" mit dem ASCII-Zeichen 219. Bei OLW lassen wir es drauf ankommen, bei MTLG sorgen wir selbst dafür, daß es nicht geschieht.

Um die Fenster zu zeigen, rufen wir sie erstmal vom Dateianfang an auf, also mit dem Parameter 0 (null).

zeigeDIAL();
zeigeMTLG(0);

c=getch(); // Tastendruck

scroll(OLW,0); // Scrollen im Überblendfenster
zeigeDIAL(); // Bildschirm links wiederherstellen
scroll(MTLG,0); // Bildschirm rechts wiederherstellen und SCROLLEN im Fenster MITTEILUNGEN

zeigeDIAL(); //die Inhalte der beiden Fenster erneut ausgeben
zeigeMTLG(0); // damit ist der Bildschirm wiederhergestellt




Auf Tastendruck also überblendet das OLW beginnend mit der Zeile 0 (Abb. scroll0)

Beim Scrollen entdecken wir irgendwann den ÜBergang, wo der Text rechts angeschlagen wäree und korrigiert wurde (Abb scroll1) Der schwarze Balken ASCII 219 macht darauf unmißverständlich aufmerksam, daß hier was nicht gestimmt hat.

Nach dem Verlassen des Scrolling kommen wir zum Fenster MTLG (rechts), wo wir ebenfalls scrollen können (Abb scroll2)

Damit steht die Scrolling-ROutine für 2 Fenster zur Verfügung und kann später bei der eigentlichen Datenverarbeitung genutzt werden.

Um zum Bsp. den Kontenrahmen SKR03 zu sehen, kann man ihn in den Buffer kopieren und in einem Fenster seiner Wahl anzeigen und zum Scrollen freigeben, denn der SKR03 ist so ein Typ, der nicht auf den Bilschirm paßt.

Oder man kopiert sich in das stark erweiterte OLW ein Buch seiner Wahl und macht noch eine BOSS-TASTE, damit der Chef einen nicht erwischt. wink.gif

Der Beitrag wurde von sharky bearbeitet: 04.02.2012, 10:43 Uhr
Angehängte Datei(en)
Angehängte Datei  scroll0.jpg ( 227.27KB ) Anzahl der Downloads: 16
Angehängte Datei  scroll1.jpg ( 228.68KB ) Anzahl der Downloads: 12
Angehängte Datei  scroll2.jpg ( 140.03KB ) Anzahl der Downloads: 10
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 15:46 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Die Implementation der Dateiverwaltung

Die gesamte Dateiverwaltung kann man vor der eigentlichen Programmierung als PROTOTYP schon erledigen. Man muß dann nachher, wenn die endgültige Struktur der Daten feststeht, nur anpassen. Das kann mit Suchen und Ersetzen geschehen und ist schnell erledigt. Vorher möchte ich einige allgemeine Hinweise zum Arbeiten mit Dateien in ANSI-C geben.

1. Text oder binär?

Das Textformat, also ein char-array aus druckbaren Zeichen, exisistiert nur unter MSDOS/WINDOWS. In UNIX/LINUX gibt´s das gar nicht.

Weil man es eigentlich schlicht nicht benötigt.

Es ist für die Bearbeitung von bestimmten Datentypen wie z.B. allen Zahlen ein sehr sperriges Format, weil die Typen zunächst in Text umgewandelt und zur Bearbeitung wieder zurückgewandelt werden müssen. Hat man es mit reinem Text zu tun, ist das Format auch nicht wirklich handlich, weil es die \r\n Zeilenendemarkierungen benötigt. Für einen späteren Textumbruch auf eine andere Zeilenlänge muß man diese Markierungen allesamt entfernen und neu setzen.
Der Nutzen ist meiner Meinung nach für die Datenverarbeitung eigentlich NULL. Nützlich wird es erst, wenn wir uns mit anderen Programmen austauschen wollen oder müssen. Jede Textdatei kann mit den gängigen Editoren direkt gelesen und bearbeitet werden und in andere Datenformate (Tabellen) importiert werden. Die Textdateien sind also eine Schnittstelle.

Binärdateien kann man mit Standard-Programmen nicht auslesen. Das heißt aber nicht, daß man eine unbekannte Binärdatei gar nicht auslesen könnte, wenn man es denn wollte (siehe weiter unten dazu).


2. Der Dateizeiger FILE

Wir finden ihn in <stdio.h>:

typedef struct _iobuf
{
char* _ptr;
int _cnt;
char* _base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char* _tmpfname;
} FILE;

Daß da einiges mehr gespeichert sein muß als nur die physikalische Adresse des Dateianfangs, resultiert schon aus der Tatsache, daß die Standardfunktion fseek mit SEEK_END, SEEK_CUR nach dem Aufruf unmittelbar zur Verfügung steht.

Wir sehen, daß zwei File-Zeiger implementiert sind, char *_ptr und char *_base. Einer davon muß die Adresse Dateianfang sein, der andere der Laufzeiger, auf den sich SEEK_CUR bezieht. Das int _cnt kann sich nur auf die Anzahl der Datensätze beziehen, _bufsiz auf die Größe des Datensatzes, char *_tmpfname auf den Dateinamen, _flag vermute ich als Markierung für Schreib/Lesezugriff usw. Wenn man wollte, könnte man sich ganz schlau machen und den Datentyp zur Laufzeit auslesen, aber so neugierig sind wir gar nicht. Es soll nur ein Hinweis sein, daß sich hinter FILE mehr verbirgt als ein einfacher Datentyp Zeiger.

Woher die Dateiende-Markierung SEEK_END resultiert, ist einfach:

Startadresse der Datei + Anz-Datensätze*Datensatzgröße = Dateiende.

Das sollte auch gelten, wenn die Datei auf dem Massespeicher fragmentiert gespeichert wird. Ich vermute, daß das Betriebssystem die Organisation so vornimmt, daß die Datei virtuell mit fortlaufenden Adressen angesprochen werden kann. Auch das ist aber nicht so wichtig, weil man sich als Programmierer darum definitiv nicht kümmern muß.

In Vorwegnahme dessen, was später noch erläutert wird, will ich einmal mit dem Dateizeiger ein bißchen herumspielen anhand eines kleinen praktischen Beispiels.
Ein Beispiel sagt mehr als tausend Worte. wink.gif

Die Funktion:
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>

char taste;
struct ma{
int nr;
char name[30];
};
struct ma m;

void init(){sprintf(m.name,"Mueller");}

void schreib()
{
int i;
FILE *bdp;
bdp=fopen("test.bin","wb");
if (bdp==NULL) return;
for (i=0;i<3;i++)
{
m.nr=i+1;
fwrite(&m,sizeof(struct ma),1,bdp);

}
fclose(bdp);
}



Initialisiert einen Datensatz namens Müller, fügt eine fortlaufende Nr hinzu und schreibt den dreimal in die Datei test.bin, bin weil binär.

Jetzt tun wir mal so, als wenn wir die Struktur ma mit ma.nr und ma.name nicht kennen, sondern lesen diese Datei einfach mal zeichenweise aus. Und zwar mit dieser Funktion:

void lies2()
{
char c;
int pos;
FILE *bdp;
bdp=fopen("test.bin","rb");
if (bdp==NULL) return;
do
{
c=fgetc(bdp);
printf("%4i",(int)c);
}while ((int)c!=-1);
fclose(bdp);
}


Da klar ist, daß ein Gutteil der Zeichen nicht druckbar sein wird, geben wir nicht die Zeichen als solche aus, sondern den ASCII-Wert der Zeichen. Dazu casten wir den char in einen integer-Wert.

Bei diesem cast-operator kann man übrigens unterscheiden:

steht dort (int) c ist es ANSI-C
steht dort int ( c ) ist es C++

Ich bleibe bei reinem ANSI C. Nichts geht über das Original. wink.gif


Die Bildschirm-Ausgabe ist die folgende:

1 0 0 0 77 117 101 108 108 101 114 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0
77 117 101 108 108 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 77 117 101 108
108 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 -1Press any key to continue . . .


HInweis: um solche Konsolenausgaben in einen Text zu kopieren: Rechte Maustaste über dem - Symbol oben rechts am Fensterrand, Edit, Mark, dann Copy mit ENTER, und dann in den TExt einfügen.

Wir sehen also eine 1, gefolgt von 3 NUllen. Dann kommt die ASCII-Zeichenfolge für Mueller, dann folgen wieder jede Menge Nullen, dann kommt die 2, und wieder Mueller usf.

Am Ende steht -1. Das ist die Markierung für EOF bei zeichenweisem Auslesen. Anstelle !EOF könnten wir auch schreiben !=-1 (ungleich minus 1). ABER NUR, wenn wir mit GETC() auslesen! Lesen wir blockweise aus, schießen wir damit das Programm ins NIRWANA!

Wir ändern jetzt mal unsere STRUCT ma (ma soll heißen MItarbeiter) folgendermaßen:

Statt:
struct ma{
int nr;
char name[30];
};
struct ma m;

heißt es nun:
struct ma{
short nr;
char name[15];
};
struct ma m;


Name und das Format nr. wurden in ein kleineres FOrmat gebracht.

Der Programmaufruf zeigt nun folgendes Resultat:

1 0 77 117 101 108 108 101 114 0 0 0 0 0 0 0 0 0 2 0
77 117 101 108 108 101 114 0 0 0 0 0 0 0 0 0 3 0 77 117
101 108 108 101 114 0 0 0 0 0 0 0 0 0 -1Press any key to con
tinue . . .


Wir sehen, anstelle von 4 Speicherplätzen für nr. sind es nunmehr nur noch 2 (short statt int), und die Nullen hinter der ASCII Folge Mueller haben abgenommen, weil wir die Speichergröße für ma.name von 30 auf 15 verkleinert haben.

Insgesamt ist die Datei natürlich auch kleiner geworden.

Dies zur Demonstration, daß man BINÄR-Dateien unbekannten Formates sehr wohl auslesen und interpretieren kann. In diesem Falle wäre es recht einfach, die passende Datenstruktur dafür zu finden:

Wir brauchen ein short für den vorausgehenden Zahlenwert, sowie einen Text von 15 Zeichen für den nachfolgendewn ASCII-Namen.

Will man Binärcode so verschlüsseln, daß er für andere nicht lesbar sein soll, muß man also schon wesentlich mehr Aufwand betreiben als einfach binär zu codieren. wink.gif


Der Beitrag wurde von sharky bearbeitet: 04.02.2012, 15:52 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 16:09 Uhr
nixalsverdruss
nixalsverdruss
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 16.11.2003
Beiträge: 1.511

QUOTE (sharky @ 04.02.2012, 15:46 Uhr) *
1 0 77 117 101 108 108 101 114 0 0 0 0 0 0 0 0 0 2 0
77 117 101 108 108 101 114 0 0 0 0 0 0 0 0 0 3 0 77 117
101 108 108 101 114 0 0 0 0 0 0 0 0 0 -1Press any key to con
tinue . . .



Dies zur Demonstration, daß man BINÄR-Dateien unbekannten Formates sehr wohl auslesen und interpretieren kann. In diesem Falle wäre es recht einfach, die passende Datenstruktur dafür zu finden:


Will man Binärcode so verschlüsseln, daß er für andere nicht lesbar sein soll, muß man also schon wesentlich mehr Aufwand betreiben als einfach binär zu codieren. wink.gif

das ist binärcode

01100100 01100001 01110011 00100000 01101011 01100001 01101110 01101110 01110011 01110100 00100000 01100100 01110101 00100000 01110011 01100011 01101000 01101111 01101110 00100000 01101110 01101001 01100011 01101000 01110100 00100000 01101100 01100101 01110011 01100101 01101110

und ?


--------------------
There are only 10 types of people in the world: Those who understand binary, and those who don't
Wir haben einen exponentiellen Zuwachs an Doofen Pack im Forum
TOP    
Beitrag 04.02.2012, 16:28 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Spielen wir mal ein bißchen weiter herum, um die BASICS der Dateiverwaltung unter ANSI-C zu verdeutlichen. Was genau da abläuft, werde ich weiter unten noch erklären. Wir nehmen statt unserer Spionage-Routine fgetc() jetzt die passende Funktion:

void lies()
{
int pos;
FILE *bdp;
bdp=fopen("test.bin","rb");
if (bdp==NULL) return;
do
{
if (fread(&m,sizeof(struct ma),1,bdp)==1)
printf("%4i%s\n",m.nr,m.name);
}while (!feof(bdp));
fclose(bdp);
}

Und erhalten als Ausgabe:

1Mueller
2Mueller
3Mueller
Press any key to continue . . .


Jetzt öffnen wir die Datei im Lese- und Schreibmodus, und versuchen, bei den Müllers einen Schulze einzufügen.

void edit()
{
FILE *bdp;
bdp=fopen("test.bin","r+b");
if (bdp==NULL) return;
if (fread(&m,(long) sizeof(struct ma),1,bdp)==1)
{
sprintf(m.name,"%s","->Schulze");
m.nr=0;
fwrite(&m,sizeof(struct ma),1,bdp);
}
fclose(bdp);
}


Bildschirmausgabe:

1Mueller
2Mueller
3Mueller
Press any key to continue . . .


Das heißt, das hat nicht geklappt mit unserem Schulze.

Warum das nicht klappt: es ist im Internet vieles OBERFLÄCHLICHES zu lesen. Im Prinzip geht das so, heißt es. Aber:

Um Programmierung zu lernen, muß man nicht lesen, sondern SELBST PROGRAMMIEREN. So allgemeine Kenntnisse führen da nicht wirklich weiter.

Es gibt eine Verwirrung um das Makro EOF, sowie die Funktion fflush(). Von der letzteren wird behauptet, daß sie die Schreibzugriff erzwingt. Tatsächlich?

void edit()
{
FILE *bdp;
bdp=fopen("test.bin","r+b");
if (bdp==NULL) return;
if (fread(&m,(long) sizeof(struct ma),1,bdp)==1)
{
sprintf(m.name,"%s","->Schulze");
m.nr=0;
fflush(bdp);
fwrite(&m,sizeof(struct ma),1,bdp);
}
fclose(bdp);
}


Ergebnis ist negativ, die Schulzes bleiben weiter unter sich.

Wenn man allerdings folgende anscheinend sinnlose Zeile hinzufügt:

void edit()
{
FILE *bdp;
bdp=fopen("test.bin","r+b");
if (bdp==NULL) return;
if (fread(&m,(long) sizeof(struct ma),1,bdp)==1)
{
sprintf(m.name,"%s","->Schulze");
m.nr=0;
fseek(bdp,0,SEEK_CUR);
fwrite(&m,sizeof(struct ma),1,bdp);
}
fclose(bdp);
}


dann geht es plötzlich:

1Mueller
0->Schulze
3Mueller
Press any key to continue . . .


Was macht fseek(bdp,0,SEEK_CUR)?

Eigentlich gar nichts. Der Dateizeiger bdp steht auf SEEK_CUR, und die angeforderte Verschiebung ist 0, es ändert sich überhaupt nichts bis auf die Tatsache, daß wir einen leeren Funktionsaufruf haben.

Tatsächlich aber schaltet der leere Funktionsaufruf zwischen Lesen und Schreiben um. Der Aufruf von fflush() macht das nicht.

Insgesamt ist es überhaupt verwunderlich, weil fwrite() ja an Deutlichkeit nichts zu wünschen übrig läßt. Aber: ohne Umschaltung wird fwrite() ignoriert. Tatsache.

Der leere Funktionsaufruf fseek(*FILE,0,SEEK_CUR) ist also der Schalter, der zwischen Lese- und Schreibzugriff umschaltet.

Der Schreibzugriff erfolgt, wie man sieht, mitten in die Datei hinein. Er kann aber genausogut am Ende erfolgen. Wichtig nur, daß er überhaupt erfolgt.

Der Beitrag wurde von sharky bearbeitet: 04.02.2012, 16:31 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 16:47 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Wir können uns den FILE mal ansehen, Vermutung daß die Umschaltung Lesen/Schreiben mit der int _flag erfolgt.

Lesen wir das mal aus, indem wir die Funktion wie folgt ändern:

void edit()
{
FILE *bdp;

bdp=fopen("test.bin","r+b");

if (bdp==NULL) return;
printf("%4i\n",bdp->_flag);

if (fread(&m,(long) sizeof(struct ma),1,bdp)==1)
{
printf("%4i\n",bdp->_flag);

sprintf(m.name,"%s","->Schulze");
m.nr=0;
printf("%4i\n",bdp->_flag);
fseek(bdp,0,SEEK_CUR);
printf("%4i\n",bdp->_flag);

fwrite(&m,sizeof(struct ma),1,bdp);
}
fclose(bdp);
}


Ausgabe am Bildschirm dann:

128
137
137
136
1Mueller
0->Schulze
3Mueller
Press any key to continue . . .


Das bedeutet: das int-Element des Dateizeigers namens FILE->_flag befindet sich erst im NULL-Zustand mit 128.

Lesezugriff mit fread() ändert den Wert auf 137.

Und solange das so ist, werden alle frwrite() Aufrufe ignoriert. (Q.E.D. = quod erat demonstrandum, was zu beweisen war)

Erst der Aufruf der LEEREN Funktion fseek() switcht den Schalter um auf den Wert 136.

Und dann kann eben geschrieben werden. Aber nicht mehr gelesen!

Das war jetzt nur mal ein Schmankerl bzw. ein Hinweis, daß man auch mal neugierig sein darf.

Mit systematischer Darstellung ohne Schmankerl geht es dann gleich weiter ...

Der Beitrag wurde von sharky bearbeitet: 04.02.2012, 16:50 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 17:01 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

QUOTE (nixalsverdruss @ 04.02.2012, 16:09 Uhr) *
das ist binärcode

01100100 01100001 01110011 00100000 01101011 01100001 01101110 01101110 01110011 01110100 00100000 01100100 01110101 00100000 01110011 01100011 01101000 01101111 01101110 00100000 01101110 01101001 01100011 01101000 01110100 00100000 01101100 01100101 01110011 01100101 01101110

und ?


Du hast recht, ich hab mich etwas unglücklich ausgedrückt.

Solange man die Datei aber zeichenweise auslesen kann, mit fgetc(), ist das zugrundeliegende Speicherformat nicht wesentlich.

Der Gedanke war, wenn man die Ascii werte auslesen kann, kann man die Datei auch entschlüsseln.

Um das zu verhindern, müßte man umschlüsseln z. B. nach dem Bibel-Code, wie das zur Zeit Napoleons gemacht wurde. Der ist praktisch nicht zu knacken.

Gruß Sharky


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 18:10 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Weiter geht es systematisch, ohne Schmankerl.


Das ist zwar langweilig, aber nötig. Ohne systematische Kenntnisse kommt man in C sehr leicht ins Schwimmen und dann geht nichts mehr.

Der Dateizugriff mit fopen() benötigt als Parameter die Angaben:

w = write = schreiben

r = read = lesen

a = append = an die Datei anhängen

bzw. die erweiterten Parameter:

w+ = schreiben und lesen

r+ = lesen und schreiben

a+ = an das Ende schreiben und lesen

Der Parameter a verhindert, daß man mitten in die Datei hineinschreibt. Eigentlich ist der aber völlig überflüssig, denn wenn wir mit

fseek(FILE,0,SEEK_END) den Laufzeiger an das Dateiende setzen, können wir auch mit r+ an das Ende der Datei anhängen.

Also Modus a ist eigentlich Quatsch mit Soße oder etwas für Denkfaule.

Nun sehen wir: w+ kann schreiben und lesen, r+ aber auch. Wo ist der Unterschied?

Wird im w Modus geöffnet, wird die vorhandene Datei beim ersten Zugriff zerstört.

Wird im r-Modus geöffnet, bleibt sie unbeschädigt.

Um eine Datei aber neu anzulegen, muß man im w-Modus öffnen.

Um festzustellen, ob eine Datei vorhanden ist, nimmt man besser den r-Modus.

Kurz zusammengefaßt:

Will man eine Datei neu anlegen oder von vorn bis hinten schreiben, nimmt man w.

Für vorhandene Dateien nimmt man r.

Der Modus a ist überflüssig.

Das Editieren, also die Veränderung des Dateiinhalts, macht man mit r+. Das heißt, Lesen und Schreiben.

Dazu muß man den Laufzeiger der Datei mit fseek an die richtige Stelle setzen.

Daß man zwischen Lesen und Schreiben umschalten muß, wurde erwähnt, die Umschaltung erfolgt zuverlässig mit fseek(). Will man an der Stelle schreiben, wo man schon steht, ruft man fseek() mit dem Parameter 0 auf, macht also einen leeren Funktionsaufruf (schwer zu begreifen, ist aber so, Tatsache).

fseek verstellt NORMALERWEISE den Laufzeiger der Datei. Dafür sind in ANSI-C folgende Konstanten vorgesehen:

SEEK_SET = Anfang der Datei

SEEK_CUR= Current, also Aktuelle Dateizeiger-Position.

SEEK_END=Dateiende.

Die Syntax von fseek() ist etwas tückisch.

Sie lautet:

int fseek ( FILE * stream, long int offset, int origin );

Dabei handelt es sich um den schon vorgestellten Dateizeiger FILE.

Gefolgt von einem long Wert, den man in Ansi-C mit (long) casten muß. Dieser offset beschreibt, wieviele Datensätze wir vor- oder zurückspringen wollen von dem nun folgenden Wert, origin.

Ich kehr das mal um:

Für Origin gibt es die Werte SEEK_END (Dateiende), SEEK_CUR (current=aktuelle Position des Dateizeigers, hat nichts mit CURSOR zu tun wink.gif ), sowie SEEK_SET =Dateianfang.

Wir sagen also: wir wollen (long) Datensätze von SEEK_END wegbleiben, also den 5.-letzten Datensatz haben.

So geht es aber nicht! wink.gif

Weil nämlich der Dateizeiger FILE in Bytes rechnet, wir rechnen aber in Datensätzen.

Wir müssen den offset multiplizhieren mit der Größe des Datensatzes. Wollen wir umgekehrt die Position des Laufzeigers feststellen, müssen wir durch die Größe des Datensatzes dividieren.

Der ist je nach Betriebssystem unterschiedlich, und daher holen wir uns den mit dem Operator sizeof()

Handelt es sich um Ganzzahlen int, sagen wir:

sizeof(int)

Wenn wir 5 zurück wollen, sagen wir:

-5*sizeof(int)

Haben wir eine Struktur, sagen wir:

-5*sizeof(struct MeineStruktur)

bzwl, wenn MeineStruktur mit typedef definiert wurde:

-5*sizeof(MeineStruktur)

Dann rutschen wir 5 zurück. Was ist aber, wenn damit eine Bereichsüberschreitung stattfindet, von der Pos 3 FÜNF Sätze zurück geht nicht, weil der Zeiger dann im Nirwana stehen würde?

Nun, das kann man ja testen:

void lies2()
{
int pos;
FILE *bdp;
bdp=fopen("test.bin","rb");
if (bdp==NULL) return;
fseek(bdp,(long) -2*sizeof(struct ma),SEEK_CUR); // muß Mist ergeben, Frage wer den Fehler abfängt

if (fread(&m,sizeof(struct ma),1,bdp)==1)
printf("%4i%s\n",m.nr,m.name); else printf("Geht nicht");
fclose(bdp);
}


Ergebnis:

1Mueller
Press any key to continue . . .


Das heißt, die Funktion fseek() macht es so, wie weiter oben im SROLLING, sie fängt die Fehler ab und setzt -2 auf 0.

DARAUF WÜRDE ICH MICH ABER NIEMALS VERLASSEN!

IN C NICHT und NIEMALS!

Solche Schweinereien kann man sehr wohl in der aufrufenden Routine selbst abfangen und muß nicht die letzte Hoffnung auf den Papst setzen. wink.gif

Und wenn es hier mal gut geht, solchen Mist zu programmieren, ist das weder gut noch eine Garantie, daß es beim nächsten Mal ebenfalls gut geht.

Wäre noch hinzuzufügen, weil WICHTIG:

Wenn wir den Datensatz mitten in der Datei gefunden haben, dessen Daten wir verändern wollen im MODUS r+ mit:

fread()

Und ändern die Daten und wollen ihn an seine Stelle zurückschreiben und machen das mit

fwrite()

Dann haben wir Chaos angerichtet, weil:

Mit fread() rutscht der Dateizeiger an das Ende des gelesenen Datensatzes.

Der erneute Zugriff (Zurückspeichern) mit fwrite() schreibt die Daten nicht an die Stelle, von wo gelesen wurde, sondern auf den nächstfolgenden Datensatz. Ist der Nächstfolgende nicht vorhanden, wird hinter EOF angehängt, die Datei wird größer.

In jedem Falle ist das ERgebnis MIST, weil der geänderte Datensatz ZUSÄTZLICH zu dem fehlerhaften vorhanden ist.

Wir müssen also, nach dem Einlesen und Editieren, vor dem Zurückspeichern (Nochmal: geht nur im Modus r+) den Dateizeiger zurücksetzen:

fseek(FILE,(long) -1*sizeof(MEIN_DATENSATZ),1);


um die ursprüngliche Position zu überschreiben.

Dann ist alles wieder in Butter. wink.gif

Der Beitrag wurde von sharky bearbeitet: 04.02.2012, 18:23 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 18:39 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Bzw. muß es natürlich heißen:


fseek(FILE,(long) -1*sizeof(MEIN_DATENSATZ),SEEK_CUR);


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 18:54 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Für die Dateiorganisation gibt es eigentlich nur 3 Zustände:

1.) Wir müssen Datei neu anlegen

2.) Wir müssen Datei erweitern (neue Daten)

3.) Wir müssen Dateiinhalt korrigieren.

Das trifft aber nicht auf alle Dateien zu.

Bleiben wir beim BEispiel einer FIBU, Finanzbuchhaltung.

Wir haben dann folgende Gruppen von Dateien:

Gruppe 1: Kontenrahmen (Mitglieder: Kontenrahmen, Anlagekonten)

Die Datei KONTENRAHMEN (hier: SKR03). Die ist nur in Maßen veränderlich. Wir dürfen daraus nichts löschen, sobald ein Konto bebucht wurde. Wir dürfen höchstens neue Konten hinzufügen, und auch, wenn man ein altes KOnto ändert, also alle Buchungen auf ein anderes Konto macht, muß das alte KOnto aus historischen Gründen (Dokumentation) noch erhalten bleiben.

Gruppe 2: Buchungssätze

Hier finden alle laufenden Geschäftsvorgänge ihren Niederschlag. Hier wird also ständig hinzugefügt, gelöscht oder geändert. Das Löschen, wie das früher mal üblich war, zu verhindern (ich glaube nach der EInführung der EDV lag das weniger an den Gesetzen als an der Faulheit der damaligen Programmierer) ist weder gesetzlich vorgeschrieben noch sinnvoll. Wenn die Tusse (Tschuldigung) in die Primanota einen Buchungssatz 12mal falsch eingibt und anschließend 12 mal storniert, ist auch ein Betriebsprüfer nicht erfreut, so einen Mist lesen zu müssen. Die Kaufleute im Mittelalter haben solche "Bleistiftbuchungen" aus anderen Gründen verhindert. Die waren ja nicht doof.

Gruppe 3: Abschlußkonten

Werden gesondert behandelt.

Gruppe 4: alle weiteren Dateien, Infos, Hilfestellungen und so weiter.

Die kann man ganz frei halten. Es geht was nötig ist.

Das wäre nun der EInstieg in die Datenverarbeitung, die logische Programmierung, wo man Algorithmen finden muß für logische PRobleme.

Ich will aber den Einstieg noch nicht machen, bevor die FORMALITÄTEN erledigt sind.

Und als solche stehen noch auf der TO_DO_LIST:

Sortiere die Dateien.

Dazu brauchen wir dynamische Listen.

Es geht also weiter mit der Implementation der Datei-Prototypen sowie der Verwaltung der dynamischen Listen. Da wird es dann ein bißchen mehr C-like, Pointer und dergleichen, während es bislang eher pascal-like abegegangen ist (Arrays).

Wenn aber eine Aufgabe genausogut oder besser mit Arrays zu lösen ist, gibt es auch in C keine Notwendigkeit, da mit Pointern herumzuswitchen.

Die kommen schon noch. wink.gif

Der Beitrag wurde von sharky bearbeitet: 04.02.2012, 18:56 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 04.02.2012, 19:36 Uhr
sharky2014
sharky2014
Level 7 = Community-Professor
*******
Gruppe: Mitglied
Mitglied seit: 25.09.2008
Beiträge: 1.692

Behandeln wir eine Datei im Textmodus, können wir schreiben : fopen("Dateiname","rt"), für Textmodus. Der Compiler ignoriert das allerdings, weil t=Textmodus der default ist (die Vorgabe). Man sollte es trotzdem schreiben, damit die Anweisung klar ist.

Um Dateien im Binärmodus zu schreiben und zu lesen, lautet die Formulierung fopen("Dateiname","rb"), also ein b wird hinzugefügt für BINÄR, es ist egal, ob man w+b oder wb+ bzw. r+b oder rb+ schreibt, jedenfalls das b unterschlägt der Compiler nicht, sondern das ist lebenswichtig, damit die Datei auch im Binärmodus bearbeitet wird.

Unter UNIX/LINUX ist das wiederum unwichtig, da sowieso alle Dateien im Binärmodus behandelt werden.

Um eine Datei im Binärmodus zu behandeln, können wir die Daten so lassen wie sie sind, reinschreiben und zurückschreiben. Die Funktionen sind:

fwrite()

und

fread()

Um die Daten im Textmodus zu speichern, müssen sie umgewandelt werden in Text. Danach werden sie gespeichert und gelesen mit

(zeilenweise:)

fputs()

fgets()

Die Möglichkeit, Dateien zeichenweise zu speichern und zu lesen, steht so in der Mitte zwischen beiden, wie oben schon beschrieben, die Funktionen sind:

fputc()

fgetc()

Beim Auslesen der Dateien ist natürlich die EOF (END OF FILE) Markierung zu beachten, bzw. zu finden, damit das PRogramm sich nicht ins Nirwana schaufelt. Man soll sich aber an dem Begriff nicht festbeißen, der funktioniert in C nicht so, wie erwartet.

Bei Binärdateien können wir das implementieren, ohne es zu erwähnen, mit der Anweisung:

while (fread(&buffer, (long) sizeof(Datensatz),1,pFILE)==1) // pFILE ist pointer auf FILE, definiert als FILE *pFILE

Das heißt, solange der Lesezugriff mit dem angeforderten Wert 1 (=1 Datensatz) das ERgebnis 1 (=1Datensatz) zurückliefert, lies weiter. Geht das nämlich nicht weiter, liefert der Lesezugriff einen Wert UNGLEICH 1, und das ist die schamhafte Umschreibung für EOF.

Was auch geht, ist
do
{
lies die Datei }
while (!feof(pFILE)}

Diese Schleifen mit Fußbedingung müssen allerdings je nachdem anders gemanagt werden, damit der Abbruch nicht später erfolgt, als über das Dateieende hinausgelesen wurde. Man muß innen mit if() arbeiten, sonst hat man den letzten Datensatz doppelt. Hier geht es nur um das Prinzip.

Handelt es sich um Textdateien, gilt analog:

LESEZUGRIFF:

while (fgets (Datensatz, ,sizeof(Datensatz),pFILE ) != NULL )

Eigentlich alles ganz banal.

Um im Textformat zu speichern, müssen allerdings strukturierte Datentypen zuvor allesamt in ein Textzeile umgewandelt werden.

Das Prinzip wurde schon erwähnt, nämlich sprintf() wandelt ALLE Datentypen in formatierten Text um.

Und wenn wir den Text wieder auslesen, um mit den Datentypen arbeiten zu können, müssen wir eine Rückumwandlung machen

Diese geschieht bei Zahlenwerten mit

atoi() = Alphanumeric to Integer , also Zeichenfolge in Integer umwandeln oder

atof() = Alphanumeric to Float, Zeichenfolge in Fließkommazahl umwandeln,

und dazu muß der Text exakt zerschnitten werden, sonst gibt das Müll.

So ein Fall tritt aber gar nicht auf, weil kein Mensch Daten in Text verwandelt und rückumwandelt, mit denen er rechnen will.

Die Textdateien sind lediglich die Kopie der im Binärformat vorliegenden Daten und dienen als Schnittstelle, werden sonst aber nicht gebraucht.

Daher sind die wesentlichen Routinen der Dateiverwaltung in wenigen Zeilen beschrieben:

fwrite(&buffer,(long) sizeof(Datentyp),1,pFILE) schreibt einen Buffer von dem Typ Datensatz in die Datei

und

fread(&buffer,(long) sizeof(Datentyp),1,pFILE) liest ihn wieder aus.

solange gilt:

while(fread(&buffer,(long) sizeof(Datentyp),1,pFILE)==1), also !feof(pFILE)

Die Funktion fread arbeitet CALL BY REFERENCE, weil sie direkt in den Speicherbereich buffer hineinschreibt und keine Kopie übergibt, Hinweis darauf ist der

Adreßoperator &

Damit wird der Speicherbereich buffer überschrieben.

Obwohl die Funktion fwrite() auch den Adreßoperator benutzt, arbeitet sie aber nicht CALL BY REFERENCE, sondern im PRINZIP CALL BY VALUE, weil sie ja diesen Bereich nicht verändert, sondern nur als Kopie in die Datei ablegt.

Wenn man allerdings die Adresse einer Variablen & an eine Funktion übergibt (die keine Standard-Funktion ist), muß man sich nicht wundern, wenn irgendwann nach der 25. Programmüberarbeitung plötzlich seltsame Effekte auftreten. Gibst du deine EC-CARD und deine GEHEIMZAHL heraus, mußt du damit rechnen, daß auch UNANGENEHME DINGE geschehen. Nichts anderes ist CALL BY REFERENCE.

WEil die Dateiorganisation eigentlich so banal ist wie beschrieben (mit Ausnahme des Vorlaufs, daß man sich eben auskennt), werd ich das hier nicht mehr weiter ausführen sondern fortfahren mit der Frage der

SORTIERUNG VON DATEN und DATEIEN.

Der nächste Beitrag also wird sich mit der Organisationn dynamischer Listen zum Zwecke der Sortierung von Daten beschäftigen.

Wenn das erledigt ist, wäre die formale Seite der Programmierung abgeschlossen und man kann endlich zur eigentlichen Datenverarbeitung übergehen.

Als da wäre, wie man eine Finanzbuchhaltung programmiert.

Der Beitrag wurde von sharky bearbeitet: 04.02.2012, 19:51 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    



1 Besucher lesen dieses Thema (Gäste: 1)
0 Mitglieder: