Das deutsche QBasic- und FreeBASIC-Forum Foren-Übersicht Das deutsche QBasic- und FreeBASIC-Forum
Für euch erreichbar unter qb-forum.de, fb-forum.de und freebasic-forum.de!
 
FAQFAQ   SuchenSuchen   MitgliederlisteMitgliederliste   BenutzergruppenBenutzergruppen  RegistrierenRegistrieren
ProfilProfil   Einloggen, um private Nachrichten zu lesenEinloggen, um private Nachrichten zu lesen   LoginLogin
Zur Begleitseite des Forums / Chat / Impressum
Aktueller Forenpartner:

Datensatz als Array in Speicher laden
Gehe zu Seite 1, 2  Weiter
 
Neues Thema eröffnen   Neue Antwort erstellen    Das deutsche QBasic- und FreeBASIC-Forum Foren-Übersicht -> Allgemeine Fragen zu FreeBASIC.
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen  
Autor Nachricht
kilix



Anmeldungsdatum: 05.02.2022
Beiträge: 175

BeitragVerfasst am: 22.05.2022, 16:12    Titel: Datensatz als Array in Speicher laden Antworten mit Zitat

Ich habe nun alle für mein Projekt benötigten Daten aus den CSV-Dateien des Vorsystems eingelesen, nach Bedarf bearbeitet und verknüpft. Das funktioniert schon gut und, durch eure Hilfe, auch schnell.
Ich habe mir auch die von euch ins Forum gestellten Codebeispiele angesehen und (soweit ich es schaffen konnte) analysiert. Das betrifft vorallem das Einlesen und Zerlegen von CSV-Dateien (das Schreiben und Ändern von CSV-Dateien benötige ich nicht). Was mich auch sehr interessiert hat - weil ich es für den nächsten Schritte brauche - ist das Einlesen einer Datei in einen Array. Wie man das macht ist mir klar. Ich habe das ja auch schon für's Sortieren benötigt (Danke nemored für den Quicksort!). Aber mich hat doch sehr interessiert wie ihr das macht. Nun, mit dem Beispiel von nemored habe ich meine Probleme, das ist mir zum gegenwärtigen Zeitpunkt noch viel zu hoch. Aber das Beispiel von grindstone verstehe ich und kann es praktisch 1:1 übernehmen.

Allerdings habe ich dabei ein Problem: irgend etwas stimmt mit der Satzlänge im Array nicht. Wenn ich einen Namen suche erhalte nur für den ersten Satz ein Ergebnis. Danach sind die Daten verschoben und die Suche läuft ins Leere. Ich habe deshalb die Längen von tDatensatz und tZumSpeichern angesehen. Diese sind mit Field=1 (wie im Beispiel) 136 bzw. 137. Allerdings wird beim Speichern in den Array die Länge von tZumSpeichern um 1 reduziert und damit sind die Satzlängen wieder gleich und es sollte eigentlich stimmen.
Der Code von grindstone ist aber richtig. Dort passt es für die im Beispiel angelegte Beispielsdatei. Nur das Ganze auf meine Datei angepasst funktioniert nicht obwohl es eigentlich codegleich ist. Ich denke, da muss irgendwo in den Definitionen der Hund begraben sein.
Die größten Abweichungen von grindstones Code sind in TYPE tDatensatz. Diese enthält bei mir alle Felder der entsprechenden Datei bis auf das letzte. Da habe ich in der Datei noch ein Stringfeld AS Stríng*100 Reserve aber das muss ich ja nicht in dien Array übernehmen.
Ich hänge den Code hier herein:
Code:

'
'   Datensatz-Array definieren
'   (Felder in Type entsprechen der Recordbeschreibung der Mitgliederdatei)
'
TYPE tDatensatz FIELD = 1
   AS INTEGER iNummer
   AS STRING * 35 sName
   AS STRING * 45 sVorname
   AS STRING * 2 sStand
   AS INTEGER iVerein
   AS BYTE bInakt
   AS STRING * 13 sFktCd
   AS STRING * 18 sFkt
   AS INTEGER iAnzGes
   AS INTEGER iAnz
   AS BYTE bSCd
END TYPE
'
TYPE tZumSpeichern FIELD = 1
   UNION
      datensatz AS tDatensatz
      datensatz_ AS STRING * SIZEOF(tDatensatz)
   END UNION
END TYPE
'
DIM AS INTEGER x
DIM AS STRING sSuchNam
DIM AS tDatensatz datensatz
DIM AS tZumSpeichern speichern
'
'   Mitglieder.dat in Array einlesen
'   --------------------------------
'
dim as integer fa = FREEFILE
OPEN "\Dateien\MITGLIEDER.DAT" FOR Binary AS #fa

REDIM AS tDatensatz datenarray(0)
'
x = 1
DO
   speichern.datensatz_ = INPUT(SIZEOF(tZumSpeichern) - 1, #fa)
   REDIM PRESERVE datenarray(x)
   datenarray(x) = speichern.datensatz
   x +=1
LOOP UNTIL EOF(fa)
CLOSE fa
'
'   Namen in Tabelle suchen
'
Do
   INPUT "Mitgliedername/@:     "; sSuchNam
   IF sSuchNam = "@" THEN EXIT DO ENDIF
   FOR x = 1 TO UBOUND(datenarray)
      WITH datenarray(x)
         IF .sName = sSuchNam THEN
            PRINT "Name: ";.sName
            EXIT for
         ENDIF
      END WITH
   NEXT
   If x = UBound(datenarray) + 1 Then
      Print "Name nicht gefunden"
   EndIf
loop
END

Nachdem ich diesen Code später mehrfach benötigen werden möchte ich ihn dann in eine SUB oder FUNCTION legen.
_________________
Grüße
kilix
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
nemored



Anmeldungsdatum: 22.02.2007
Beiträge: 4594
Wohnort: ~/

BeitragVerfasst am: 22.05.2022, 19:06    Titel: Antworten mit Zitat

Ich weiß jetzt nicht, ob das der Grund deines Problems ist, aber STRING*x belegt x+1 Byte an Speicher (weil intern noch ein CHR(0) an Ende angehängt wird). Daher sind tDatensatz und tZumSpeichern auch nicht gleich groß.
Vielleicht ist es erfolgsversprechender, tDatensatz künstlich um ein Byte aufzublähen?

Code:
print sizeof(STRING*10)
TYPE tDatensatz FIELD = 1
   AS INTEGER iNummer
   AS STRING * 35 sName
   AS STRING * 45 sVorname
   AS STRING * 2 sStand
   AS INTEGER iVerein
   AS BYTE bInakt
   AS STRING * 13 sFktCd
   AS STRING * 18 sFkt
   AS INTEGER iAnzGes
   AS INTEGER iAnz
   AS BYTE bSCd
   AS BYTE dummy   'Platzhalter für das Nullbyte von tZumSpeichern.datensatz_
END TYPE
'
TYPE tZumSpeichern FIELD = 1
   UNION
      datensatz AS tDatensatz
      datensatz_ AS STRING * (SIZEOF(tDatensatz)-1)
   END UNION
END TYPE

_________________
Deine Chance beträgt 1:1000. Also musst du folgendes tun: Vergiss die 1000 und konzentriere dich auf die 1.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
kilix



Anmeldungsdatum: 05.02.2022
Beiträge: 175

BeitragVerfasst am: 22.05.2022, 21:41    Titel: Antworten mit Zitat

Danke, aber leiderhilft das nicht. grindstone hatte im folgenden Befehl:
Code:

x = 1
DO
   speichern.datensatz_ = INPUT(SIZEOF(tZumSpeichern) - 1, #fa) << hier
   REDIM PRESERVE datenarray(x)
   datenarray(x) = speichern.datensatz
   x +=1
LOOP UNTIL EOF(fa)
CLOSE fa

von SIZEOF(tZumSpeichern) - 1 auch schon 1 Byte abgezogen. Damit wären die beiden wieder gleich groß.
Ich habe zuerst nur das Dummy-Byte eingefügt und im zweiten Versuch das Dummy-Byte eingefügt und das -1 weggenommen.
Beides hilft nicht.
Aber irgendwie muss es mit dem Befehl TYPE tDatensatz FIELD = 1 zusammenhängen. Ich hab jetzt auch noch versucht weniger Felder in TYPE tDatensatz zu definieren und habe alle nach sName weggenommen. Das hat auch nichts verändert.
Mir gefällt diese Art der Lösung weil sie wirklich übersichtlich und rasch zu kodieren ist.
Für den von mir bisher verwendeten Sort-Array habe ich die Datensätze durch Stringaddition zusammengefügt und nach dem Sort mit den Stringfunktionen LEFT, MID und Right wieder zerlegt. Das hat gut funktioniert ist aber weit nicht so elegant!
_________________
Grüße
kilix
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
grindstone



Anmeldungsdatum: 03.10.2010
Beiträge: 1208
Wohnort: Ruhrpott

BeitragVerfasst am: 23.05.2022, 12:30    Titel: Antworten mit Zitat

Hallo kilix,

obwohl du es nicht ausdrücklich erwähnst, entnehme ich aus deiner Beschreibung, daß du versuchst, eine vorhandene CSV - Datei mithilfe des von mir geposteten Codes in ein Array einzulesen. Das funktioniert definitiv nicht, da das dort verwendete Datenformat keinerlei Trennzeichen kennt und diese daher 1:1 mit einliest, wodurch das Ganze schon beim zweiten Datenfeld aus dem Tritt gerät.

Ich habe den von dir geposteten Code so umgeschrieben, daß er CSV - kompatibel ist. Obwohl ich ihn in Ermangelung der Originaldatei nicht wirklich testen kann, sollte er eigentlich funktionieren. Zur eventuellen Fehlersuche habe ich die Teile zur Erzeugung einer (rudimentären) Testdatei auskommentiert dringelassen.
Code:
Type tDatensatz
   As Integer iNummer
   As String * 35 sName
   As String * 45 sVorname
   As String * 2 sStand
   As Integer iVerein
   As Byte bInakt
   As String * 13 sFktCd
   As String * 18 sFkt
   As Integer iAnzGes
   As Integer iAnz
   As Byte bSCd
   Declare Sub speichern(dateinr As ULong)
   Declare Sub lesen(dateinr As ULong)
End Type

Sub tDatensatz.speichern(dateinr As ULong)
   With This
      Print #dateinr, _
      .iNummer; ""","""; _
      .sName; ""","""; _
      .sVorname; ""","""; _
      .sStand; ""","""; _
      .iVerein; ""","""; _
      .bInakt; ""","""; _
      .sFktCd; ""","""; _
      .sFkt; ""","""; _
      .iAnzGes; ""","""; _
      .iAnz; ""","""; _
      .bSCd
   End With
End Sub

Sub tDatensatz.lesen(dateinr As ULong)
   With This
      Input #dateinr, .iNummer
      Input #dateinr, .sName
      Input #dateinr, .sVorname
      Input #dateinr, .sStand
      Input #dateinr, .iVerein
      Input #dateinr, .bInakt
      Input #dateinr, .sFktCd
      Input #dateinr, .sFkt
      Input #dateinr, .iAnzGes
      Input #dateinr, .iAnz
      Input #dateinr, .bSCd
   End With
End Sub

Dim As Integer x
Dim As String sSuchNam
ReDim As tDatensatz datenarray(1)

''datenarray anlegen
''ReDim datenarray(30)
'ReDim datenarray(5)
'For x = 1 To UBound(datenarray)
'   With datenarray(x)
'      .iNummer = x
'      .sName = "Meier" + Str(x)
'      .sVorname = "Sepp"
'      .bSCd = 5
'   End With
'Next
'
''datenarray im CSV-format in datei schreiben
'Open ExePath + "\datei.dat" For Output As #1
'For x = 1 To UBound(datenarray)
'   datenarray(x).speichern(1)
'Next
'Close 1
'
'ReDim datenarray(1) 'datenarray zurücksetzen

'CSVdatei einlesen und in array schreiben
'Open ExePath + "\datei.dat" For Input As #1
Dim As Integer fa = FreeFile
Open "\Dateien\MITGLIEDER.DAT" For Input As #fa

x = 1
Do
   ReDim Preserve datenarray(x) 'neues arrayelement anlegen
   datenarray(x).lesen(fa) 'datensatz in arrayelement schreiben
   x += 1 'nächstes element
Loop Until Eof(fa)
Close fa

'   Namen in Tabelle suchen
'
Do
   Input "Mitgliedername/@:     "; sSuchNam
   If sSuchNam = "@" Then Exit Do EndIf
   For x = 1 To UBound(datenarray)
      With datenarray(x)
         If .sName = sSuchNam Then
            Print "Name: ";.sName
            Exit For
         EndIf
      End With
   Next
   If x = UBound(datenarray) + 1 Then
      Print "Name nicht gefunden"
   EndIf
Loop
End



Gruß
grindstone
_________________
For ein halbes Jahr wuste ich nich mahl wie man Proggramira schreibt. Jetzt bin ich einen!
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden
kilix



Anmeldungsdatum: 05.02.2022
Beiträge: 175

BeitragVerfasst am: 23.05.2022, 14:15    Titel: Antworten mit Zitat

Hallo grindstone,
ich lese keine CSV-Datei ein (das Kapitel CSV ist abgeschlossen). Ich lese eine Binary-Datei ein. Siehe diese beiden Befehlszeilen.
Code:

dim as integer fa = FREEFILE
OPEN "\Dateien\MITGLIEDER.DAT" FOR Binary AS #fa

Diese Datei entstand zwar aus einer CSV-Datei ist aber eindeutig binär und ich habe sie auch schon in anderen Programmen so verwendet.
Was mir aufgefallen ist, dass tDatensatz und tZumSpeichern, beide mit FIELD=1, um ein Byte unterschidlich groß sind. tDatensatz hat 136 Byte und tZumSpeichern hat 137. Allerdings, und ich dachte das soll den Unterschied ausgleichen ziehst du beim folgenden Befehl von 1 Byte ab
Code:

speichern.datensatz_ = INPUT(SIZEOF(tZumSpeichern) - 1, #fa)

_________________
Grüße
kilix
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
grindstone



Anmeldungsdatum: 03.10.2010
Beiträge: 1208
Wohnort: Ruhrpott

BeitragVerfasst am: 23.05.2022, 15:33    Titel: Antworten mit Zitat

Wie nemored ganz richtig sagte, wird an Strings fester Länge intern noch ein Nullbyte angehängt, deshalb ist der String datensatz_ ein Byte länger als der UDT tDatensatz, und das wird durch die -1 ausgeglichen.

Ich vermute aber, daß der Fehler schon beim Abspeichern passiert ist. Hast du bei
Code:
Print #1, speichern.datensatz_;
an das Semikolon gedacht? Denn sonst wird an den String noch ein Chr(10, 13) angehängt, und dann ist jeder Datensatz 2 Bytes zu lang.

Gruß
grindstone
_________________
For ein halbes Jahr wuste ich nich mahl wie man Proggramira schreibt. Jetzt bin ich einen!
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden
kilix



Anmeldungsdatum: 05.02.2022
Beiträge: 175

BeitragVerfasst am: 23.05.2022, 17:57    Titel: Antworten mit Zitat

Jetzt bin ich verwirrt! die Zeile Print #1, speichern.datensatz_; steht doch in dem Abschnitt 'Datei mit 30000 Einträgen erstellen. Den hast du doch eingefügt, um eine Datei mit 30000 Datensätzen zu erzeugen die dann in den Array gespeichert werden soll.
Ich habe aber diese Datei bereits und daher habe ich diesen Abschnitt weggelassen und setze fort bei 'datei in array einlesen
_________________
Grüße
kilix
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
kilix



Anmeldungsdatum: 05.02.2022
Beiträge: 175

BeitragVerfasst am: 24.05.2022, 13:30    Titel: Antworten mit Zitat

Ich habe nun die Suche umprogragrammiert. Ich habe den Array so befüllt wie ich das in anderen Programmen für den Sortarray gemacht habe. D.h. ich habe die relevanten Felder der Binary-Datei in Strings umgewandelt und mit & in einen String zusammengeschlossen den ich dann in die Array-Elemente gespeichert habe. Das funktionierte einwandfrei.

Ich vermute daher, dass der Fehler in einem Verhalten von Freebasic liegt das ich noch nicht kenne oder übersehe.
_________________
Grüße
kilix
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
grindstone



Anmeldungsdatum: 03.10.2010
Beiträge: 1208
Wohnort: Ruhrpott

BeitragVerfasst am: 24.05.2022, 15:03    Titel: Antworten mit Zitat

Entschuldige, ich war davon ausgegangen, daß du deine jetzige Datei aus der ursprünglichen CSV-Datei erzeugt hast.

Das Problem ist folgendes: An einen String fester Länge wird -wie schon erwähnt- vom Compiler automatisch ein Nullbyte angehängt. Innerhalb eines UDT ist dieses Nullbyte Bestandteil des Strings, d.h., ein String*40 belegt 41 Bytes, und es werden auch alle 41 Bytes eingelesen. Wenn das Datenfeld in der Quelldatei tatsächlich nur 40 Bytes groß ist, wird immer 1 Byte zuviel eingelesen, und das bei jedem String.

Du könntest also einmal probeweise versuchen, jeden der Strings um 1 Byte zu verkürzen. Das letzte Byte jedes Strings bliebe dann ungenutzt, denn es ist immer 0, deshalb ist die Lösung auch nicht ganz sauber, insbesondere mit sStand (und vermutlich auch mit sFktCd und sFkt ) dürfte es Probleme geben.

Bliebe dann noch die Frage, ob die Zahlen (iNummer, iVerein, iAnzGes, iAnz) wirklich im 4-Byte-Binärformat abgespeichrt werden oder doch als Ziffern.

Es geht auch ohne die Nullbytes, aber das ist etwas komplizierter. Ich versuche mal etwas zusammenzubasteln.

Gruß
grindstone
_________________
For ein halbes Jahr wuste ich nich mahl wie man Proggramira schreibt. Jetzt bin ich einen!
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden
kilix



Anmeldungsdatum: 05.02.2022
Beiträge: 175

BeitragVerfasst am: 24.05.2022, 19:27    Titel: Antworten mit Zitat

Der Irrtum mit der CSV-Datei ist ja kein Problem, hatte doch lang immer wieder damit zu tun.

Du schreibst jedes String-Feld wird um eine Nullstring verlängert. Damit hat ein String*40 eben 41 Bytes. Bisher dachte ich an jedes Feld wird ein Null-Sting angehängt.
Ich wollte das prüfen und habe alle Feldlängen der Mitgliederdatei addiert. Dabei habe ich für Integer 4 Bytes für Byte 1 Byte und für Strings die definierte Länge genommen. Damit komme ich auf 231 Byte.
In einem Programm das diese Datei verwendet habe den Inhalt der Variablen recordLength mit Print recordlength angezeigt. recordLength hat 244 Bytes, also 13 Bytes mehr. Wenn ich zu den 231 Bytes für jedes Stringfeld 1 Byte dazu zähle erhalte ich immer noch 237. Jetzt fehlen noch 7 Bytes auf die 244. Allerdings gibt es noch 6 Integer- bzw. Byte-Felder. Das gäbe aber auch nur 6 Bytes. D.h. es fehlt immer noch eines.
Zu diesem Thema hab ich eigentlich auch noch nicht wirklich was gefunden.
_________________
Grüße
kilix
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
ThePuppetMaster



Anmeldungsdatum: 18.02.2007
Beiträge: 1837
Wohnort: [JN58JR]

BeitragVerfasst am: 25.05.2022, 04:35    Titel: Antworten mit Zitat

öööm ....

Code:
'...
As String * 35 sName
'...

ein >String< ist selbst ein TYPE mit Länge 3x Int, welches KEINE 0 am Ende anhängt.

Versuche mal zString (das hängt ein 0 dran)
Code:
'...
As zString * 35 sName
'...



Frage: Hast du "Zeilenumbrüche" (CR / LF) in den String Elementen drin?


MfG
TPM
_________________
[ WebFBC ][ OPS ][ ToOFlo ][ Wiemann.TV ]
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
kilix



Anmeldungsdatum: 05.02.2022
Beiträge: 175

BeitragVerfasst am: 25.05.2022, 07:23    Titel: Antworten mit Zitat

ich verstehe die Aussage nicht
Zitat:
ein >String< ist selbst ein TYPE mit Länge 3x Int, welches KEINE 0 am Ende anhängt.

und davor dieses Beispiel
Code:
'...
As String * 35 sName
'...

Wie passen 3x Int (zu je 4 Byte) mit der Länge des String von 35 zusammen?
Wo kann ich eine Erklärung für alle diese angehängten bzw. nicht angehängten 0 und "" finden. Ich denke, dass ich dazu eine umfassende Erklärung brauche, um das zu verstehen.

Zur Frage nach Zeilenumbrüchen im String: nein, die habe ich in keinem String.
_________________
Grüße
kilix
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
grindstone



Anmeldungsdatum: 03.10.2010
Beiträge: 1208
Wohnort: Ruhrpott

BeitragVerfasst am: 25.05.2022, 14:21    Titel: Antworten mit Zitat

kilix hat Folgendes geschrieben:
ich verstehe die Aussage nicht
Das glaube ich dir. grinsen Die Stringverwaltung ist ein Kapitel für sich, das zu verstehen einiges Hintergrundwissen erfordert. Ich empfehle dir dazu die Lektüre von nemoreds Einsteigerhandbuch, Kapitel 15. Die dort erwähnten Metadaten nennt man auch Stringdescriptor (@nemored: Das wäre vielleicht einen ergänzenden Hinweis wert, nur zur Klärung der Begrifflichkeiten).

Und (@alle) wie ich festgestellt habe, wird die Sache noch komplizierter, wenn UDTs ins Spiel kommen:
Code:
Sub drucken
   Open ExePath + "\datei.dat" For Binary As #1
   Do
      Print Hex(Asc(Input(1, #1)), 2);" ";
   Loop Until Eof(1)
   Print
   Close 1
End Sub

Dim As String*10 ts
ts = "12345678901234567890"
'ts = "12345" + Chr(0,0) + "678901234567890"
Open ExePath + "\datei.dat" For Output As #1
Print #1, ts;
'Put #1,,ts
Close 1
Print "        String*10: ";
drucken

Type tTest
   As String*10 ts
End Type
Dim As tTest test
test.ts = "12345678901234567890"
'test.ts = "12345" + Chr(0,0) + "678901234567890"
Open ExePath + "\datei.dat" For Output As #1
Put #1,,test
Close 1
Print " String*10 im UDT: ";
drucken

Dim As ZString*10 zts
zts = "12345678901234567890"
'zts = "12345" + Chr(0,0) + "678901234567890"
Open ExePath + "\datei.dat" For Output As #1
Print #1, zts;
'Put #1,,ts
Close 1
Print "       ZString*10: ";
drucken

Type tZTest
   As zString*10 zts
End Type
Dim As tZTest ztest
ztest.zts = "12345678901234567890"
'ztest.zts = "12345" + Chr(0,0) + "678901234567890"
Open ExePath + "\datei.dat" For Output As #1
Put #1,,ztest
Close 1
Print "ZString*10 im UDT: ";
drucken

Sleep
Anders als beim Abspeichern von "normalen" ZStrings und Strings fester Länge bleiben beim Abspeichern des UDT-Inhalts die intern angehängten Nullbytes erhalten.

Und noch ein seltsames Verhalten von Strings fester Länge (egal ob einzeln oder als UDT-Member) habe ich entdeckt: Nullbytes im zugewiesenen Stringinhalt (siehe die auskommentierten Teststings) werden schlicht und einfach ignoriert. Solche versteckten Features können einen Programmierer in den Wahnsinn treiben! mit dem Kopf durch die Mauer wollen

Gruß
grindstone
_________________
For ein halbes Jahr wuste ich nich mahl wie man Proggramira schreibt. Jetzt bin ich einen!


Zuletzt bearbeitet von grindstone am 25.05.2022, 14:38, insgesamt einmal bearbeitet
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden
nemored



Anmeldungsdatum: 22.02.2007
Beiträge: 4594
Wohnort: ~/

BeitragVerfasst am: 25.05.2022, 14:23    Titel: Antworten mit Zitat

@ThePuppetMaster
Da liegt, glaube ich, eine Verwechslung vor. Ein String (variabler Länge) ist ein Datentyp mit der Länge von drei Integer (also 3x32 Bit bzw. 3x64 Bit je nach Plattform). Ein String fester Länge (also z. B. STRING*35) funktioniert aber wie ein ZSTRING, nur ohne Nullbyte-Terminierung. Sieht man auch schön mit SIZEOF:
Code:
PRINT SIZEOF(STRING)
PRINT SIZEOF(STRING*35)

_________________
Deine Chance beträgt 1:1000. Also musst du folgendes tun: Vergiss die 1000 und konzentriere dich auf die 1.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
nemored



Anmeldungsdatum: 22.02.2007
Beiträge: 4594
Wohnort: ~/

BeitragVerfasst am: 25.05.2022, 14:28    Titel: Antworten mit Zitat

@grindstone, was meinst du mit "Nullbytes werden einfach ignoriert"? Meinst du bei der Ausgabe? Ich könnte mir vorstellen, dass da gar nicht FreeBASIC verantwortlich ist, sondern die betriebssystem-interne Behandlung.

Wenn ich mich recht erinnere, ist es auch recht lustig, Strings fester Länge an eine Prozedur zu übergeben - weil man sie nur als ZSTRING übergeben kann und dann die Nullbytes plötzlich wieder terminierend wirken.

grindstone hat Folgendes geschrieben:
Stringdescriptor (@nemored: Das wäre vielleicht einen ergänzenden Hinweis wert, nur zur Klärung der Begrifflichkeiten)

Danke für den Vorschlag, nehme ich gern mit auf. lächeln
_________________
Deine Chance beträgt 1:1000. Also musst du folgendes tun: Vergiss die 1000 und konzentriere dich auf die 1.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
grindstone



Anmeldungsdatum: 03.10.2010
Beiträge: 1208
Wohnort: Ruhrpott

BeitragVerfasst am: 25.05.2022, 15:16    Titel: Antworten mit Zitat

Nein, das Ignorieren geschieht schon bei der Zuweisung des Stringinhalts (ich habe es gerade noch einmal ausprobiert, ohne Umweg über die Datei). Die Nullbytes werden so behandelt, als wären sie gar nicht vorhanden.

Das bedeutet, daß man mit Strings fester Länge keine beliebigen Inhalte transportieren kann.

Die Zuweisung bei ZStrings (auch das habe ich gerade ausprobiert) per Literal erfolgt übrigens korrekt, allerdings wird der Inhalt bei Zuweisung an eine andere ZString - Variable beim ersten Nullbyte abgeschnitten. Das Verhalten bei Zuweisung an einen String variabler Länge ist noch seltsamer, aber das muß ich erst einmal näher untersuchen...

Gruß
grindstone
_________________
For ein halbes Jahr wuste ich nich mahl wie man Proggramira schreibt. Jetzt bin ich einen!
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden
kilix



Anmeldungsdatum: 05.02.2022
Beiträge: 175

BeitragVerfasst am: 26.05.2022, 11:13    Titel: Antworten mit Zitat

Einsteigerhandbuch, Kapitel 15 beschreibt die verschiedenen Strings sehr gut. Aber was die Anwendung betrifft habe ich jetzt aber noch mehr Fragen weil sich mir der Sinn des Ganzen (ich nehme WSTRINGs aus) noch verschließt:
1) Freebasic hängt aus Gründen der Kompatibilität einen Nullstring an einen String fester Länge (und nur solche verwende ich bisher) an, die aber scheinbar, bis auf die Ausnahme von SIZEOF nicht auffallen. Bei SIZEOF wird der Nullstring aber mitgezählt (siehe obiges Beispiel von nemored).
Gibt es da vielleicht auch andere Situationen in den dieses Nullstring eine Rolle spielt?
2) ich habe bisher nur Strings fester Länge (keine ZSTRINGs) an Prozeduren übergeben. Wenn nur diese Strings (intern) als ZSTRINGs übergeben werden, wird das Nullstring hinten angehängt oder in das letzte Byte gesetzt wo es ein Zeichen überschreiben kann?
3) es sieht so aus als ob dieses "Nullstring-Problem" bei mir zu den Problemen führt die ich in meinem ersten Beitrag zu diesem Posting beschrieben habe. Ich kann mir aber immer noch nicht erklären wo das Problem eigentlich liegt. Vorallem auch deshalb weil ich eigentlich nur den Code von grindstone übernommen habe. Der einzige Unterschied liegt ja nur darin, dass grindstone in diesem Code eine Datei mit 30000 Records erstellt hat weil er sonst keine Datei hatte und ich eine vorhandene Datei als Input genommen habe. Ich vermute das Problem liegt hier
Code:

Type tDatensatz Field = 1
   As ULong mitgliedsnummer
   As String*40 Name
   As String*40 vorname
   As ULong plz
   As String*40 wohnort
   As Byte dummy
End Type
'
Type tZumSpeichern Field = 1
   Union
      datensatz As tDatensatz
      datensatz_ As String*SizeOf(tDatensatz)
   End Union
End TYPE

Denn bei Field=1 ist die Länge von tZumSpeichern um 1 größer als die von tDatensatz und, soweit ich es verstehe sollen durch UNION die Variablen datensatz und datensatz_ den gleichen Speicherplatz belegen, anders gesagt es sind 2 Definitionen für den gleichen Platz und daher müssen sie doch gelich groß sein (????)
Allerdings wurde dieses Byte hier wieder abgezogen:
Code:

Do
   speichern.datensatz_ = Input(SizeOf(tZumSpeichern) - 1, #1)
.

Ich habe zwar, für mich, diese Aufgabe über einen Workaround, basierend auch einer Stringaddition, gelöst aber mich interessiert die elegantere Lösung von grindstone sehr!
_________________
Grüße
kilix
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
nemored



Anmeldungsdatum: 22.02.2007
Beiträge: 4594
Wohnort: ~/

BeitragVerfasst am: 26.05.2022, 14:19    Titel: Antworten mit Zitat

Ich hoffe, ich habe da nichts Falsches im Kopf, aber wenn ich mich nicht irre, ist es folgendermaßen:
  • Ein ZSTRING*10 belegt 10 Byte an Speicher, wovon das letzte ein Nullbyte ist - man kann also nur maximal 9 Byte lange Zeichenkettten darin dpeichern.
  • Ein STRING*10 belegt 11 Byte (das letzte ist ein Nullbyte), kann also eine 10 Byte lange Zeichenkette speichern.
  • Ein WSTRING*10 arbeitet im Wesentlichen wie ein ZSTRING, jedoch belegt jedes Zeichen mehrere Byte, und zwar abhängig vom Betriebssystem 2 Byte pro Zeichen oder 4 Byte pro Zeichen. Das terminierende "Nullbyte" ist in Wirklichkeit eine Folge von 2 bzw. 4 Nullbytes.
  • Wenn du einen String oder ZString fester Länge an eine Prozedur übergeben willst, übergibst du stattdessen einen ZSTRING PTR. Da wird dann kein Zeichen abgeschnitten; der Inhalt des ZSTRING PTR (bzw. genauer gesagt der Inhalt der Zeichenkette, auf die der Pointer verweist) wird ausschließlich durch das Nullbyte terminiert.
  • Du kannst einen STRING (oder ZSTRING) fester Länge aber auch als STRING variabler Länge übergeben. Was da aber genau passiert, ist mir nicht ganz klar - vermutlich wird der Wert erst in eine neue lokale Stringvariable kopiert und am Ende der Prozedur wieder zurückkopiert (solange du kein BYVAL verwendest.

Code:
SUB test(txt as STRING)
  PRINT txt, SIZEOF(txt), STRPTR(txt),@txt
  txt = "Halliholla!"
  PRINT txt, SIZEOF(txt), STRPTR(txt),@txt
END SUB

DIM AS STRING*5 txt = "Hallo"
PRINT txt, SIZEOF(txt), STRPTR(txt),@txt
test(txt)
PRINT txt, SIZEOF(txt), STRPTR(txt),@txt

_________________
Deine Chance beträgt 1:1000. Also musst du folgendes tun: Vergiss die 1000 und konzentriere dich auf die 1.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
kilix



Anmeldungsdatum: 05.02.2022
Beiträge: 175

BeitragVerfasst am: 26.05.2022, 15:51    Titel: Antworten mit Zitat

Danke für die kurze und prägnante Erklärung!
Die Erklärung und dein Code-Beispiel ordnet einiges.
1) SIZEOFF zeigt die gesamte interne Länge, also inkl. Nullbyte, des Strings, egal ob ZSTRING oder STRING
2) LEN(txt) zeigt die tatsächliche Länge des String wobei hier ein Unterschied zwischen STRING (ohne Leerstring) und ZSTRING (inkl. Leerstring) besteht.
3) in der SUB ist durch (txt as STRING) txt als variabler String definiert, daher SIZEOF=12

ich habe in den Code von grindstone nun Prints eingebaut:
Code:

Type tDatensatz Field = 1
   As ULong mitgliedsnummer
   As String*40 Name
   As String*40 vorname
   As ULong plz
   As String*40 wohnort
   As Byte dummy
End TYPE
PRINT SIZEOF(tdatensatz)
PRINT LEN(tdatensatz)

in diesem Fall sind SIZEOF und LEN mit 132 gleich lang.
Wie wird hier gezählt?

Und hängt das irgendwie (wie?) mit meinem Problem zusammen?
_________________
Grüße
kilix
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
grindstone



Anmeldungsdatum: 03.10.2010
Beiträge: 1208
Wohnort: Ruhrpott

BeitragVerfasst am: 26.05.2022, 16:17    Titel: Antworten mit Zitat

Du möchtest es eleganter? Bitte sehr! Zunächst mal ein UDT, der dir alle Sorgen abnimmt. Die Daten werden nicht in einzelnen Variablen abgespeichert, sondern in einem allozierten Speicherbereich, der den kompletten Datensatz aufnimmt. Der Zugriff erfolgt über Properties, das sind in den UDT integrierte Prozeduren, die aber von aussen wie Variablen angesprochen werden. Zur Vereinfachung kopiere zunächst einmal den folgenden Code und speichere ihn unter dem Namen "mitgliederlisteUDT.bi" in demselben Verzeichnis, in dem auch deine Programme stehen.
Code:
'macro für die deklaration der properties
#Macro declareprop(feldname, typ)
   Declare Property feldname As typ
   Declare Property feldname(n As typ)
#EndMacro

'macro zum anlegen der properties
#Macro prop(feldname, itemnummer, typ)
   Property tDatensatz.feldname As typ
      #If typ = ULong
         feldname = Cvl(textlesen(itemnummer)) '4 bytes aus datensatz lesen und in zahl umwandeln
      #ElseIf typ = UByte
         feldname = Asc(textlesen(itemnummer)) '1 byte aus datensatz lesen und in byte umwandeln
      #Else
         feldname = RTrim(textlesen(itemnummer)) 'text aus datensatz lesen und rechtsseitige leerzeichen entfernen
      #EndIf
   End Property
   
   Property tDatensatz.feldname(s As typ)
      #If typ = ULong
         textschreiben(Mkl(s), itemnummer) 'zahl in 4 bytes umwandeln und in datensatz schreiben
      #ElseIf typ = UByte
         textschreiben(Chr(s), itemnummer) 'byte in character umwandeln und in datensatz schreiben
      #Else
         textschreiben(s, itemnummer) 'text in datensatz schreiben
      #EndIf
   End Property
#EndMacro

Type tDatensatz
   'As Integer iNummer
   'As String * 35 sName
   'As String * 45 sVorname
   'As String * 2 sStand
   'As Integer iVerein
   'As Byte bInakt
   'As String * 13 sFktCd
   'As String * 18 sFkt
   'As Integer iAnzGes
   'As Integer iAnz
   'As Byte bSCd
         
   Enum 'festlegen der itemnummern
      _iNummer = 0
      _sName
      _sVorname
      _sStand
      _iVerein
      _bInakt
      _sFktCd
      _sFkt
      _iAnzGes
      _iAnz
      _bSCd   
   End Enum
   
   Static As ULong laenge(_iNummer To _bSCd) 'array für längenangaben der datenfelder
   Static As ULong zeiger(_iNummer To _bSCd) 'array für pointer auf datenfelder
   Static As ULong datensatzlaenge 'länge des datensatzes
   
   As UByte Ptr daten 'pointer auf datensatz
      
   Declare Constructor()
   Declare Destructor()
         
   Declare Sub textschreiben(text As String, item As ULong)
   Declare Function textlesen(item As ULong) As String
   
   'deklarieren der properties zum lesen und schreiben der einzelnen datenfelder
   declareprop(iNummer,  ULong)
   declareprop(sName,    String)
   declareprop(sVorname, String)
   declareprop(sStand,   String)
   declareprop(iVerein,  ULong)
   declareprop(bInakt,   UByte)
   declareprop(sFktCd,   String)
   declareprop(sFkt,     String)
   declareprop(iAnzGes,  ULong)
   declareprop(iAnz,     ULong)
   declareprop(bSCd,     UByte)
   
   Declare Property datensatz As String 'zum schreiben
   Declare Property datensatz(As String) 'zum lesen
      
End Type

'statische variablen deklarieren
Static As ULong tDatensatz.datensatzlaenge 'länge des datensatzes
Static As ULong tDatensatz.laenge(tDatensatz._iNummer To tDatensatz._bSCd) 'array für die längen der datenfelder
With tDatensatz 'länge der datenfelder setzen
   .laenge(._iNummer)  = 4
   .laenge(._sName)    = 35
   .laenge(._sVorname) = 45
   .laenge(._sStand)   = 2
   .laenge(._iVerein)  = 4
   .laenge(._bInakt)   = 1
   .laenge(._sFktCd)   = 13
   .laenge(._sFkt)     = 18
   .laenge(._iAnzGes)  = 4
   .laenge(._iAnz)     = 4
   .laenge(._bSCd)     = 1
End With

Static As ULong tDatensatz.zeiger(tDatensatz._iNummer To tDatensatz._bSCd) 'array für zeiger auf datenfelder

Constructor tDatensatz

   'gesamtlänge des datensatzes berechnen
   datensatzlaenge = 0
   For x As Integer = LBound(laenge) To UBound(laenge)
      datensatzlaenge += laenge(x)
   Next
   
   'speicher für den datensatz reservieren
   daten = Callocate(datensatzlaenge)
   
   'zeiger auf den anfang jedes datenfeldes berechnen
   zeiger(LBound(laenge)) = 0
   For x As Integer = LBound(laenge) + 1 To UBound(laenge)
      zeiger(x) = zeiger(x - 1) + laenge(x - 1)
   Next
   
End Constructor

Destructor tDatensatz
   'speicher des datensatzes freigeben
   DeAllocate daten
End Destructor

Sub tDatensatz.textschreiben(g As String, item As ULong)
   If daten = 0 Then Return
   g = Left(g + String(laenge(item), " "), laenge(item)) 'text auf datenfeldlänge bringen und mit leerzeichen auffüllen
   
   For x As Integer = 0 To Len(g) - 1 'text in datensatz schreiben
      daten[zeiger(item) + x] = g[x]
   Next
End Sub

Function tDatensatz.textlesen(item As ULong) As String
   If daten = 0 Then Return ""
   Dim As String g = String(laenge(item), " ") 'rückgabestring initialisieren
   
   For x As Integer = 0 To laenge(item) - 1 'text aus datensatz extrahieren
      g[x] = daten[zeiger(item) + x]
   Next
   Return g
End Function

'properties zum lesen und schreiben der einzelnen datenfelder
prop(iNummer,  _iNummer,  ULong)
prop(sName,    _sName,    String)
prop(sVorname, _sVorname, String)
prop(sStand,   _sStand,   String)
prop(iVerein,  _iVerein,  ULong)
prop(bInakt,   _bInakt,   UByte)
prop(sFktCd,   _sFktCd,   String)
prop(sFkt,     _sFkt,     String)
prop(iAnzGes,  _iAnzGes,  ULong)
prop(iAnz,     _iAnz,     ULong)
prop(bSCd,     _bSCd,     UByte)

Property tDatensatz.datensatz As String 'datensatz als string ausgeben
   If daten = 0 Then Return ""
   Dim As String g = String(datensatzlaenge, " ")
   For x As Integer = 0 To datensatzlaenge - 1
      g[x] = daten[x]
   Next
   datensatz = g
End Property

Property tDatensatz.datensatz(g As String) 'datensatz aus string einlesen
   If daten = 0 Then Return
   g = Left(g + String(datensatzlaenge, " "), datensatzlaenge) 'eingabestring auf korrekte länge bringen
   For x As Integer = 0 To datensatzlaenge - 1
      daten[x] = g[x]
   Next
End Property
Der UDT ist so programmiert, daß er sich bei Bedarf mit möglichst wenig Aufwand an andere Datenformate anpassen lässt. Eigentlich habe ich ein etwas ambivalentes Verhältnis zu Makros, aber hier war es eine echte Arbeitserleichterung.

Nun dazu 2 Beispielprogramme. Das erste zeigt, wie du auf Datensätze in einer Datei zugreifen kannst (schreibend und lesend). Wenn du im Leseteil den Namen durch deine eigene Mitgliederdatei ersetzt, müsstest du damit dort schon durchblättern können.
Code:
#Include "mitgliederlisteUDT.bi"

Dim As tDatensatz ds

'schreiben und lesen von testdaten
Open ExePath + "\datei.dat" For Output As #1
With ds
   For x As Integer = 1 To 10 '10 datensätze anlegen
      'werte in datensatz schreiben
      .iNummer = x
      .sName = "1234567890123456789012345678901234567890"
      .sVorname = "1234567890123456789012345678901234567890"
      .sStand = "1234567890123456789012345678901234567890"
      .iVerein = 500
      .bInakt = 60
      .sFktCd = "1234567890123456789012345678901234567890"
      .sFkt = "1234567890123456789012345678901234567890"
      .iAnzGes = 500000 + 2 * x
      .iAnz = 499999 - 3 * x
      .bSCd = 255
      Put #1,, .datensatz
   Next
End With

Close #1
   
With ds
   Open ExePath + "\datei.dat" For Binary As #1
   
   Do
      Cls
      .datensatz = Input(.datensatzlaenge, 1) 'datensatz aus datei lesen
            
      Print ds.datensatzlaenge, SizeOf(ds)'berechnete gesamtlänge des datensatzes
      Print
      
      'hexdump des datensatzes
      For x As Integer = 0 To .datensatzlaenge - 1
         Print Hex(.daten[x], 2);" ";
      Next
      ?
      
      'auslesen und anzeigen der datenfelder
      ?
      ? "iNummer  ";.iNummer; "*"
      ? "sName    ";.sName; "*"
      ? "sVorname ";.sVorname; "*"
      ? "sStand   ";.sStand; "*"
      ? "iVerein  ";.iVerein; "*"
      ? "bInakt   ";.bInakt; "*"
      ? "sFktCd   ";.sFktCd; "*"
      ? "sFkt     ";.sFkt; "*"
      ? "iAnzGes  ";.iAnzGes; "*"
      ? "iAnz     ";.iAnz; "*"
      ? "bSCd     ";.bSCd; "*"
      Sleep
   Loop Until Eof(1)
   Close #1
End With

Das zweite Beispiel zeigt, wie du mit einem Array dieser UDTs arbeiten kannst, ohne Gedanken an störende Nullbytes und falsche Stringlängen.
Code:
#Include "mitgliederlisteUDT.bi"

'Dim As tDatensatz ds
ReDim As tDatensatz array(0)

'schreiben und lesen von testdaten
For x As Integer = 1 To 10 '10 datensätze anlegen
   ReDim Preserve array(x) 'arrayelement anlegen
   With array(x)
      'werte in datensatz schreiben
      .iNummer = x
      .sName = "1234567890123456789012345678901234567890"
      .sVorname = "1234567890123456789012345678901234567890"
      .sStand = "1234567890123456789012345678901234567890"
      .iVerein = 500
      .bInakt = 60
      .sFktCd = "1234567890123456789012345678901234567890"
      .sFkt = "1234567890123456789012345678901234567890"
      .iAnzGes = 500000 + 2 * x
      .iAnz = 499999 - 3 * x
      .bSCd = 255
   End With   
Next

For x As Integer = 1 To UBound(array)
   With array(x)
      Cls
      Print .datensatzlaenge, SizeOf(array(x))'berechnete gesamtlänge des datensatzes
      Print
      
      'hexdump des datensatzes
      For y As Integer = 0 To .datensatzlaenge - 1
         Print Hex(.daten[y], 2);" ";
      Next
      ?
      
      'auslesen und anzeigen der datenfelder
      ?
      ? "iNummer  ";.iNummer; "*"
      ? "sName    ";.sName; "*"
      ? "sVorname ";.sVorname; "*"
      ? "sStand   ";.sStand; "*"
      ? "iVerein  ";.iVerein; "*"
      ? "bInakt   ";.bInakt; "*"
      ? "sFktCd   ";.sFktCd; "*"
      ? "sFkt     ";.sFkt; "*"
      ? "iAnzGes  ";.iAnzGes; "*"
      ? "iAnz     ";.iAnz; "*"
      ? "bSCd     ";.bSCd; "*"
      Sleep
   End With
Next

Viel Spaß beim Ausprobieren! lächeln

Gruß
grindstone

EDIT: Ich habe das ganze noch einmal überarbeitet, insbesondere habe ich die Tabellen mit den Feldlängen und Zeigern als statische Variablen ausgelagert, wodurch sich die Länge des einzelnen UDT von 96 auf 4 Bytes verkürzt (bzw. 8 Bytes bei 64-bit-Sytemen, die Größe eines UByte-Pointers). Das spart nicht nur Speicherplatz, sondern dürfte auch Sortieralgorithmen deutlich beschleunigen.

Die obigen Codes habe ich durch die neuen Versionen ersetzt.
_________________
For ein halbes Jahr wuste ich nich mahl wie man Proggramira schreibt. Jetzt bin ich einen!
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden
Beiträge der letzten Zeit anzeigen:   
Neues Thema eröffnen   Neue Antwort erstellen    Das deutsche QBasic- und FreeBASIC-Forum Foren-Übersicht -> Allgemeine Fragen zu FreeBASIC. Alle Zeiten sind GMT + 1 Stunde
Gehe zu Seite 1, 2  Weiter
Seite 1 von 2

 
Gehe zu:  
Du kannst keine Beiträge in dieses Forum schreiben.
Du kannst auf Beiträge in diesem Forum nicht antworten.
Du kannst deine Beiträge in diesem Forum nicht bearbeiten.
Du kannst deine Beiträge in diesem Forum nicht löschen.
Du kannst an Umfragen in diesem Forum nicht mitmachen.

 Impressum :: Datenschutz