[C++] Variablen in Datei abspeichern und wieder auslesen

mh1001

Lt. Commander
Registriert
Nov. 2003
Beiträge
2.039
Hallo zusammen,

zuerst möchte ich mich für den merkwürdigen Thread-Titel entschuldigen - doch mir ist einfach keine besssere Formulierung eingefallen. ;)

Ich habe in einer C++-Anwendung eine Klasse, in der sämtliche, relevanten Programmeinstellungen in Form von Variablen hinterlegt werden.
Die entsprechenden Werte sind häufig in Arrays hinterlegt und immer vom Typ INT.
Dies sieht dann beispielsweise etwa so aus:

Code:
int variable[8];
int variable2[8];
int variable3;
int variable4[8][4];
Nun möchte ich diese Variablen in einer Datei abspeichern. Dies soll nach folgendem Schema geschehen:

Code:
variable[0]        ==>  variable_0 %Wert der Variablen%
variable[1]        ==>  variable_1 %Wert der Variablen%
variable3          ==>  variable3 %Wert der Variablen%
variable4[4][3]  ==>  variable4_4_3 %Wert der Variablen%
Anschließend sollen die entsprechenden Werte natürlich auch wieder eingelesen werden und den entsprechenden Variablen zugewiesen werden.

Die Variablen besitzen in der Regel einen gültigen Wertebereich von bestimmten, aufeinanderfolgenden Ganzzahlen.

Nun habe ich mir folgendes überlegt:
Ich erstelle eine Struktur, in welcher ich die Bezeichnung, unter der die Variable gespeichert werden soll, als String, einen Pointer auf die entsprechende Variable und den gültigen Wertebereich in Form eines zulässsigen Minimal- und Maximalwertes bzw. eines Pointers auf ein Array (sofern es sich nicht um aufeinanderfolgende Werte handelt), hinterlegt werden.

Über diese Struktur könnte ich dann alle Bezüge zu den entsprechenden Variablen in Form eines Arrays oder einer Map hinterlegen.
Dann bräuchte ich zum Abspeichern und Laden der Werte lediglich das Array bzw. die Map entsprechend durchgehen und auswerten.

Allerdings bezweifle ich, dass diese Variante wirklich so optimal ist, da sie doch nicht wirklich richtig flexibel ist und auch nicht gerade wenig Code in anspruch nimmt.

Wie würdet ihr dies am besten lösen?


Vielen Dank,

MfG mh1001
 
Zuletzt bearbeitet:
In der Qt Bibliothek gibt es dafür die Klasse QSettings: http://doc.trolltech.com/4.1/qsettings.html

Im dritten Konstruktor (erstes Argument) kannst du ein Forrmat angeben. Schau dir das mal an.

Ach ja:
Die Qt-4 Bibliothek ist "frei".
Du kannst du vollversion runterladen und Open-Source-Anwendungen schreiben.
Wenn du was kommerzielels machen willst musst du Qt kaufen.
 
Vielen Dank für den Tipp. ;)
Leider konnte ich mich noch nie richtig mit irgendwelchen Klassenbibliotheken anfreunden, zumal das ganze eventuell teilweise auch im kommerziellen Rahmen genutzt werden soll.
Am liebsten greife ich in der Regel immer auf die entsprechende Schnittstelle des Betriebssystems (zumindest sofern die Anwendung nur auf ein bestimmtes Betriebssystem zugeschnitten sein soll) zu.
Da es sich bei diesem Projekt um eine Windows-Anwendung handelt, ist dies in diesem Fall auch direkt die WinAPI.

Ich habe mir dennoch eben einmal die entsprechenden Bibliotheken heruntergeladen. Leider lassen sich aus dem Code aber auch keine direkten Insperationen ziehen, da dies in der Bibliothek alles recht stark ineinander übergreifend ist.

MfG mh1001
 
Das sieht für mich sehr, nach eine ".conf" für ein Prog/Spiel aus.
Soll die Datei auch außerhalb des Programms editiert werden können ?
Worum genau gehts denn ? Die IO-Funktionen in C++ ?
Dann versuchs bei Google mit "c++ file io" und such dir was nettes raus.
Wenn es nur darum geht die Daten wiederzuerkennen, kannst Du Dich z.B. bei freien
Game-engines umschauen, die haben oft recht leistungsstarke Config Systeme.
Parser zum auswerten der Datei gibt es auch freie. Speziell im xml Bereich solltest Du da schnell was finden.

Oder suchst Du nur sowas einfaches :
http://www.parashift.com/c++-faq-lite/serialization.html#faq-36.5

-- -- muckelzwerg
 
Ja, es handelt sich um ein Datei, welche dazu dienen soll, die entsprechenden Programmeinstellungen abzuspeichern.
Bei der Anwendung handelt es sich um eine Lichtsteuerungssoftware, welche über ein entsprechendes Interface als digitales Lichtsteuerpult fungieren soll.
Dadurch kommt auch die hohe Anzahl an Werten, die gespeichert werden müssen, zustande (über 1000).
Es ist eigentlich nicht vorgesehen, dass die Dateien vom Benutzer manuell manipuliert werden. Dennoch lege ich großen Wert darauf, dass diese eine übersichtliche Struktur aufweisen und das ganze auch nicht gegen Manipulationen seitens des Benutzer anfällig ist (s. obigen Post bzgl. gültige Wertebereiche/Werte).

Mein eigentliches Problem besteht eigentlich nicht darin, dass ich nicht weiß, in welcher Form ich die Werte am besten abspeichern soll, sondern darin, wie ich diesen Abspeichervorgang und Ladevorgang am besten angehe.
Auf die entsprechende Ablage in einzelnen Variablen und in (verschachtelten) Arrays innerhalb der Klasse bin ich dabei programmseitig gebunden, da die Werte auch für aufwendigere Operation (bspw. FFTs) und viele Echtzeitberechnungen jederzeit schnell und flexibel zugriffsbereit sein müssen und eine Änderung zudem meine komplette Programmstruktur über den Haufen werfen würde. ;)

Auf die performance beim Ladevorgang und beim Abspeichern der Einstellungen kommt es mir dabei weniger an.
Trotzdem möchte ich da nicht irgendwelchen unsauberen Code verwenden und auch eine flexible Erweiterbarkeit sichern.

MfG mh1001
 
Hm, hast Du Dir den Link mal angsehen ?
Eigentlich reicht das doch ?
Du gehst halt sequentiell dein Objekt durch und schiebst die Daten in die Datei.
Die klassischen Stringprobleme bekommst Du vermutlich nichtmal.
Ausgelesen wirds ganz genauso.
Wenn Du gegen Manipulation sicher sein willst, kannst Du Checksummen einsetzen, oder
nach dem Auslesen die Werte evaluieren.

-- -- muckelzwerg
 
Den Link habe ich mir natürlich angesehen. ;)
Wie schon angesprochen ist mir aber auch das Abspeicher-Format recht wichtig (s. erster Post):

Code:
variable[0]        ==>  variable_0 %Wert der Variablen%
variable[1]        ==>  variable_1 %Wert der Variablen%
variable3          ==>  variable3 %Wert der Variablen%
variable4[4][3]  ==>  variable4_4_3 %Wert der Variablen%

Das mit der Manipulation war so nicht ganz so gemeint - ob der Benutzer einzelne Werte abändert ist egal und dies kann er - wenn er Lust hat sich durch die Dateien zu wühlen - auch gerne machen. ;)
Wichtig ist mir nur, dass letztendlich beim Einlesen überprüft wird/werden kann, ob der Wert ein zulässiger Wert ist.

Eventuell reden wir jetzt aber auch nur irgendwie aneinander vorbei. ;)
In dem Fall wäre ich dir dankbar, wenn du die entsprechende Vorgehensweise ganz grob in Worten oder an einem ganz groben Codebeispiel erläutern würdest.

MfG mh1001
 
Ähm, sowas ?
Ich glaub wir reden wirklich aneinander vorbei.

Code:
#include <iostream>
#include <fstream>

using namespace std;

class testclass
{
    private: 
        int var1;
        int var2[5];
        int var3[3][3];

    public:

        int save(char* filename)
        {
            ofstream myfile;
            myfile.open(filename);

            myfile << "variable_1 %"<< var1 << "%" << endl;

            for(int i = 0; i < 5; i++)
                myfile << "variable_2_"<< i <<" %"<< var2[i] << "%" << endl;            
            

            for(int i = 0; i < 3; i++)
                for(int j = 0; j < 3; j++)
                    myfile << "variable_3_"<< i << "_" << j << " %"<< var3[i][j] << "%" << endl;            
        
            myfile.close();          
            return 0;
        }
        

        int load(char* filename)
        {
            ifstream myfile;
            myfile.open(filename);
            myfile >> var1 ;

            for(int i = 0; i < 5; i++)
                myfile >> var2[i];
            

            for(int i = 0; i < 3; i++)
                for(int j = 0; j < 3; j++)
                    myfile >> var3[i][j];

            myfile.close();
            return 0;
        }

        void createDummyValues()
        {
            int c = 0;
            var1 = c++;
 
            for(int i = 0; i < 5; i++)
                var2[i] = c++;
 
            for(int i = 0; i < 3; i++)
                for(int j = 0; j < 3; j++)
                    var3[i][j] = c++;
                                                                                    
        }
        
        void printValues()
        {
            cout << "variable_1 %"<< var1 << "%" << endl;

            for(int i = 0; i < 5; i++)
                cout << "variable_2_"<< i <<" %"<< var2[i] << "%" << endl;
            

            for(int i = 0; i < 3; i++)
                for(int j = 0; j < 3; j++)
                    cout << "variable_3_"<< i << "_" << j << " %"<< var3[i][j] << "%" << endl;
        
        }

};

int main(int argc, char** argv)
{
    testclass* tc1;
    tc1 = new testclass();
    tc1->createDummyValues();
    tc1->save("config.cfg");
    tc1->load("config.cfg");
    tc1->printValues();
    return 0;
}


-- -- muckelzwerg
 
Besten Dank für deine Mühen.
Leider ist dies jedoch genau das, was ich eigentlich vermeiden will. So habe ich letzendlich wieder schon beim Ausgeben endlose, unübersichtliche Codemassen, wenn ich den/die Wert(e) jeder Variablen und dazu meistens noch über eine Schleife manuell in den ofstream schiebe.
Beim Auslesen würde sich dies dann natürlich ähnlich gestalten, zumal die Position der Schlüssel nicht fest an eine bestimmte Zeile gebunden ist.

Deswegen kam ich ja letztendlich auch auf die Idee dies mit einem Array oder einer Map und mittels Pointern zu realisieren. Nur scheint mir dies auch nicht ganz der optimale Weg zu sein, aufgrunddessen ich ja letztendlich zu dieser Frage kam. ;)

MfG mh1001
 
Zuletzt bearbeitet:
Ich kapier glaub ich immernoch nicht, wo Dein Problem ist.
Du kannst das ja noch runterbrechen und generalisieren, indem Du passende
Funktionen baust, die ein Array oder ein Skalar speichern.


Geht es dir darum, dass die Variablen nicht "lose rumliegen" ?
Dann musst Du eben noch ein paar Strukturen bauen.
Für solche Geschichten musst Du dann aber die Zeilen wirklich parsen,
um die Werte zu ermitteln.
Da würde ich ja eher eine instant XML Lösung nehmen und beim Auslesen die Elemente
evaluieren und dann "addProperty(...)" oder sowas verwenden.


-- -- muckelzwerg
 
Hallo mh1001,

dein Vorschlag ist doch nicht so schlecht? Weniger Code wird es nur wenn du Die Wertebereiche und Namen der Variablen weglässt. Damit wird die Datei aber unübersichtlicher.

Und viel Code ist das nicht. Die Strukturdeklaration, das laden und speichern der Struktur und fertig. Das passt doch fast auf eine Bildschirmseite. Naja vielleicht auch zwei Seiten :-).

MfG

Arnd
 
Leider kam ich die letzten Tage nicht dazu zu Antworten - dies will ich jetzt aber einmal nachholen. ;)

muckelzwerg schrieb:
Ich kapier glaub ich immernoch nicht, wo Dein Problem ist.
Du kannst das ja noch runterbrechen und generalisieren, indem Du passende
Funktionen baust, die ein Array oder ein Skalar speichern. [...]

Wahrscheinlich ist es verständlicher, wenn ich einmal ein bisschen Code liefere. ;)

Hier einmal meine bisherige Lösung (auf das wesentliche gekürzst und vereinfacht):

Header-Datei:
Code:
// ...

class ausgabe
{
  private:

    bool ausgabepuffer[8];

    // ... einige Weitere Deklerationen 

  protected:

    // ...

    int ll_geschwindigkeit[8];

    // ... ebenfalls Haufenweise weitere Deklerationen

  public:

    ausgabe();

    // ...
};

struct schluesselstruktur
{
  int standardwert;
  int minimalwert;
  int maximalwert;
  int *sonderwerte;
  int *variable;
  int elemente;
  void (*funktion)(int);
  int gruppe;
};

class optionen : protected ausgabe
{
  private:

    map<string, schluesselstruktur> schluesselmap;

    bool aenderungen;

  public:

    optionen();

    void setze_schluessel(string bezeichnung, int standardwert, int minimalwert, int maximalwert, int *sonderwerte, int *variable, int elemente, void (*funktion)(int), int gruppe);

    bool speichern(const char* dateiname);
    bool laden(const char* dateiname, bool fehlerausgabe = 1);

    void setze(string bezeichnung, int wert);
    int wert(string bezeichnung);

    void steuerelemente_aktualisieren();

    void aenderungen_setzen();
    bool aenderungen_abfragen();
};

// ...

Und hier nun die eigentlichen, relevanten Funktionen:
Code:
optionen::optionen()
{
  /////////////////////////////
  // Standardoptionen setzen //
  /////////////////////////////

  setze_schluessel("ll_geschwindigkeit", 50, 1, 100, NULL, ll_geschwindigkeit, 8, setze_ll_geschwindigkeit, 1);

  // ... und so weiter
};

void optionen::setze_schluessel(string bezeichnung, int standardwert, int minimalwert, int maximalwert, int *sonderwerte, int *variable, int elemente, void (*funktion)(int), int gruppe)
{
  schluesselmap[bezeichnung].standardwert = standardwert;
  schluesselmap[bezeichnung].minimalwert = minimalwert;
  schluesselmap[bezeichnung].maximalwert = maximalwert;
  schluesselmap[bezeichnung].sonderwerte = sonderwerte;
  schluesselmap[bezeichnung].variable = variable;
  schluesselmap[bezeichnung].elemente = elemente;
  schluesselmap[bezeichnung].funktion = funktion;
  schluesselmap[bezeichnung].gruppe = gruppe;
}

bool optionen::speichern(const char* dateiname)
{
  ofstream dateiausgabe(dateiname);

  if(dateiausgabe.is_open())
  {
    // Dateiheader schreiben und andere Operationen

    typedef map<string, schluesselstruktur>::iterator zeiger;

    for(zeiger i = schluesselmap.begin(); i != schluesselmap.end(); ++i)
    {
      dateiausgabe << endl;

      if(i->second.elemente == 1 )
      {
        dateiausgabe << i->first << " " << i->second.variable << endl;
      }
      else
      {
        for(int x = 0; x < i->second.elemente; x++)
        {
          dateiausgabe << i->first << "_" << x << " " << i->second.variable[x] << endl;
        }
      }
    }

    dateiausgabe.close();

    aenderungen = 0;

    return true;
  }
  else
  {
    MessageBox(NULL, "Beim Speichern der Optionen ist ein Fehler aufgetreten.", "Fehler", MB_ICONEXCLAMATION | MB_OK);

    return false;
  }
}

bool optionen::laden(const char* dateiname, bool fehlerausgabe)
{
  ifstream in(dateiname);

  if(in.is_open())
  {
    string name;

    while(in >> name)
    {
       // Herausfiltern von Kommentaren, Überprüfen ob Schlüssel in Map ist und Wert in gültigem Wertebereich liegt, per Pointer Wert der entsprechenden Variablen zuweisen ...
    }

    in.close();

    return true;
  }
  else
  {
    if(fehlerausgabe)
    {
      MessageBox(NULL, "Beim Laden der Optionen ist ein Fehler aufgetreten.", "Fehler", MB_ICONEXCLAMATION | MB_OK);
    }

    return false;
  }
}

void optionen::steuerelemente_aktualisieren()
{
  typedef map<string, schluesselstruktur>::iterator zeiger;

  for(zeiger i = schluesselmap.begin(); i != schluesselmap.end(); ++i)
  {
    if(i->second.funktion != NULL)
    {
      i->second.funktion(*i->second.variable);
    }
  }
}

// usw...

Ich hoffe das war jetzt einmal nicht zu viel des Codes auf einmal. ;)
Aber so sollte die Sache vielleicht deutlicher werden.


@Arnd

Ich denke du hast verstanden wie ich es gemeint hatte. ;)
Wenn dem schon so ist, dass dies schon eine "oprimale" Lösung ist (wobei das ja auch immer relativ ist ;) ), wäre das natürlich auch nicht schlecht.
Ich dachte nur, dass man vielleicht noch auf einen anderen Weg "optimaler" an dieses Problem herangehen könnte. ;)

MfG mh1001
 
Im Konstruktor die Werte per Methodenaufruf zu initialisieren, gefällt mir jetzt nicht so gut. Ich würde da eher ein statisches Array nehmen:

Code:
schluesselstruktur gValues[] = {
 {1, 0, 100, NULL, NULL, 10, pDataFunc, 5},
 ...

};

Deinen Code habe ich jetzt nicht genau angesehen. Die Methode muss statisch sein damit das funktioniert und Pointer sind auch keine so gute Idee. Aber vom Prinzip her kann man sicher alles in ein statisches Array packen. Die Funktion muss ja nicht mal darin auftauchen, wenn die Signatur immer gleich ist. Es reicht doch eine die mit einem Objekt schluesselstruktur umgehen kann.

Aber im wesentlichen hast Du doch nur eine Lade und eine Speicher Methode. Kürzer geht es kaum :-).

MfG

Arnd
 
Auch wenn das Thema etwas älter ist bin ich nun wieder einmal zeitlich dazu gekommen mich der Problematik etwas anzunehmen. ;)

Hinter der Idee mit der Map steckte der Gedanke des schnellen, direkten Zugriffs auf die einzelnen Elemente in der Containerklasse anhand des Strings, der in dem Fall die Bezeichnung des Schlüssels darstellt, unter welchem der Wert in der Datei gespeichert werden soll bzw. gespeichert wurde.
Diese Initialisierung per Methode hatte ich nur als Notlösung verwendet, da ich nicht wusste, wie ich sonst am besten die Map innerhalb des Konstruktors initialisiere ohne wieder endlose Codezeilen schreiben zu müssen.

Allerdings ist mir noch nicht klar, wie ich um die Pointer herumkommen sollte, bzw. wie ich ohne diese eine Verbindung zu den entsprechenden Variablen herstellen sollte.

Die gennante Funktion gehört eigentlich nicht zum eigentlichem Thema - die hatte ich nur vergessen herauszunehmen.
Im Kontext hatte ich mir gedacht, dass ich, wenn ich schon so eine Ansammlung in der Map habe, die Struktur gleich noch um einen entsprechenden Pointer auf eine Funktion erweitere. Diese Funktion dient jeweils dazu, abhängig von den Werten der entsprechenden Variablen, Buttons und andere Elemente der grafischen Oberfläche entsprechend zu setzen und ist jeweils vollkommen unterschiedlich aufgebaut.

MfG mh1001
 
Da mir der Pointer in der Struktur nicht so gut gefällt, würde ich den Variablen einen Typ geben und Methoden schreiben die mit Variablen eines bestimmten Typs umgehen kann.

MfG

Arnd
 
benutz einfach printf();

edit: zum auslesen aus datei scanf(); sowas solte es tun fürn anfang( hab mir den ganzen rest nicht durchgelesen also bei doppelpost bitte vergeben)
 
@Arnd

Danke für den Tipp. ;) Leider habe ich noch nicht ganz durchschaut wie du das meinst. Bezieht sich deine Aussage auf die Pointer, die jeweils auf die entsprechenden Variablen zeigen, denen der Wert letztendlich zugewießen werden soll bzw. deren abgespeichert werden soll oder auf die Pointer auf die entsprechenden Funktionen, die für die grafische Oberfläche zuständig sind?
In beiden Fällen wüsste ich nicht, wie ich dann am besten einen dynamischen Bezug zu den entsprechenden Variablen bzw. Methoden/Funktionen herstellen sollte.
Oder wie hattest du dir das gedacht?

@riod

Die Funktionen können zwar teilweise recht nüzlich sein, doch wüsste ich nicht, wie ich die nun mit der eigentlichen Problematik in Verbindung bringen sollte. ;)

MfG mh1001
 
An den Pointern für die Variablen kommst Du wohl nicht vorbei.
Aber für die Methoden dachte ich mir, Deine GUI hat ja eine endliche Anzahl von Möglichkeiten die Werte darzustellen. D.h. es werden z.B. floats oder ints dargestellt. Vielleicht sind noch Wertebereiche möglich, etc...
D.h. für jeden Typ von Darstellung definierst Du einen Enum, den Du als Member in die Struktur aufnimmst.
Dann brauchst Du nur noch eine Schleife die das Array durchgeht und abhängig von dem Enum die passende Methode aufruft.

Sinn der Sache ist einfach GUI und Daten möglichst gut zu trennen. Auf diese Weise ist die Schreib- und Leselogik gut trennbar vom Rest des Programms.

MfG

Arnd
 
Achso, jetzt verstehe ich wie das gemeint war. ;)
Ich programmiere bei dem Projekt unter direktem Zugriff auf die Windows API.
Je nach Variable kann es sein, dass diese für die Stellung von Buttons, die Position eines Schiebereglers, das zeichnen von Elementen oder sonstigem Zuständig ist.
Da dies dann auch mit den Zugriff auf verschiedenste Fenstern und der Initialisierungen von mehreren damit Variablen verbunden ist, lässt sich dort leider in der Regel keine Einheitliche behandlung noch eine Einteilung in verschiedene "Bahandlungsbereiche" festlegen.
Deswegen bleibt mir im Grunde auch nichts anderes üblich, als jeder Variablen eine eindeutige Verarbeitungsroutine (sprich die jeweilige Funktion) zuzuweisen.
Aufgrunddessen dachte ich letztendlich, dass es auch das sinnvollste sei, einen entsprechenden Bezug gleich so direkt herzustellen, wenn an dieser Stelle sowieso alle Variablen "zusammengefasst" werden.

MfG mh1001
 
D.h. beim laden der Variablen wird direkt auf graphische Objekte zugegriffen? Das kann auch ins Auge gehen.

Angenommen du machst das im Konstruktor, dann sind die graphischen Objekte ja noch gar nicht angelegt.
Angenommen Du kommst damit aus einem anderen Thread. Da können dann Abstürze auftreteten. Etc ....

Der Code wird betriebssicherer, wenn man das trennt. D.h. das laden der Variablen und die Auswertung der Inhalte dann später.
Wenn die Variablen alle so speziell sind, könnte man ihnen ja auch Namen geben. Und an Hand des Namens wird die Variable dann von dem jeweiligen Control gesucht, wenn es seine Werte darstellen will.

Aber ich will Dir Deine Lösung auch nicht verleiden. Sie ist sicher auch machbar und auch nicht schlecht.
Ich wollte nur darauf hinweisen, das ich es für sinnvoll halte Datenhaltung und graphische Darstellung zu trennen.

Dieser aktive Ansatz unter Windows ist manchmal nicht so praktisch.
Es ist sinnvoller nicht die Daten aktiv zu pushen sondern dem Objekt die Aufgabe zu überlassen sich seine Daten zu holen.

Deine Methode void optionen::steuerelemente_aktualisieren() ist ja prinzipiell gut, es ist nur die Frage wer sie wann aufruft.

MfG

Arnd
 
Zuletzt bearbeitet:
Zurück
Oben