Clanintern Clanintern Clanintern

Forum

Öffentliche Foren
FORUM: Spiele & Computer THEMA: Variablen etc. in Datei auslagern !?
AUTOR BEITRAG
jaH.Da.izi

RANG Deckschrubber

#1 - 16.02 10:20

Also ich programmiere ja schon eine ganze Weile, aber eben nicht mit C/C++. Hab bisher sehr viel Perl benutzt, wirklich auch schön, aber es wird Zeit zu kompilieren, Datentypen selber anzugeben und selber Speicher zu reservieren (und alles was eben dazugehört, brauchte ich ja bisher nicht :o)).

Ich komm eigentlich auch echt gut klar, mir ist das Schema der Objektorientierung auch klar und alles, aber eine Sache bleibt mir fraglich:

Wohin lagere ich meine Variablen aus, die für die programminterne Konfiguration zuständig sind? Weiss nicht genau wie ich das erklären soll, aber in kleinen Programmen würde man halt Konstanten benutzen (#define ...). Wenn ich jetzt aber mehrere solcher Variablen habe, die auch schon auslagere in eine header-Datei, dann kann es doch unmöglich "schön" sein, wenn ich in dieser Datei 1000mal #define am Anfang jeder Zeile stehen habe.

Meine Frage ist eigentlich nur, wie kann ich die Variablen/Konstanten mit einem schönen Stil auslagern? Als Beispiel noch: Bei grafischen Oberfläche hat man ja eigentlich immer sowas wie SCREEN_WIDTH und SCREEN_HEIGHT. Bei meinen kleinen Programmen sind die Werte eben meistens fest erstmal, deshalb möchte ich sie auslagern, aber dorthin, wo ich sie schnell wiederfinde...ich hoffe ihr wisst was ich meine :o).

Achja, eines fällt mir da noch ein, wohin lagere ich zum Beispiel "Ausgaben" aus? Auch wieder ein Beispiel: Wenn ich in eine Datei schreibe, dies aber, warum auch immer nicht geht, dann möchte ich meinen eigenen Fehler senden. Dieser soll eben auch ausgelagert werden, aber wenn möglich eben nicht mit #define. Sonst haben wir ja wieder das gleiche Problem, dass ich das 1000mal in der Datei stehen habe :o).
#define ERROR_0001 "bla bla bla"
#define ERROR_0002 "hm hm hm"
...
#define ERROR_1000 "123 abc 123"


Vielleicht noch eine Sache. Ich habe in Perl einfach bisher immer Hashes erstellt, für irgendwelche Konfigurationen.
my %config = ... SCREEN_WIDTH -> '800', SCREEN_HEIGHT -> '600', ...



Ich hoffe die Beispiele reichen, um zu verstehen was ich meine/will :o).


Naja mal sehen, vielleicht kann mir ja jemand helfen :o).





edit: Ich versuch mich gleich mal selber zu beantworten. Mach ich genau sowas dann auch mit Klassen?
jaH.Da.izi

RANG Deckschrubber

#2 - 16.02 11:44

Also ich bin jetzt schon ein bisschen weiter und es funktioniert eigentlich auch schon so, wie ich das gerne hätte.

class SDL_konfiguration {
public:
static const int bla = 20;
};

Ausgabe dann mit:

cout << "BLA: " << SDL_konfiguration::bla << endl;


Oder ist das ganze völliger quatsch? Ich find das sieht so nämlich schonmal ganz schick aus :o)
Crush (korrumpiert die Jugend)

RANG Deckschrubber

#3 - 16.02 15:24

Das Beste wäre deine Konfiguration in eine externe Textdatei auszulagern (damit auch nicht-programmierer sie ändern können) und eine Config Klasse zu programmieren, die diese Config Datei parsed und dann als Container für die Schlüssel-Wert Paare fungiert. Hast du dich schon mit der Standard Template Library beschäftigt? Die Klasse std::map (praktisch ein assoziatives Array) wäre z.B. gut geeignet um Configschlüssel- und Werte zu speichern.

Übrigens sollte man #define macros nicht für Konstanten verwenden, sondern statdessen besser const variablen benutzen. Siehe http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7 So vermeidet man z.B. Kollisionen mit identischen #defines in externen Bibliotheken. Bei einem Projekt bei dem ich mitarbeite haben wir z.B. das Problem, dass die eine Bibliothek ein "#define DELETED 0xirgendwas" enthält, die andere Bibliothek aber DELETED als Identifier in einem privaten enum verwendet sodass man immer darauf achten muss, dass die erste Bibliothek erst nach der zweiten inkludiert wird, weil die zweite sich sonst nicht compilieren lässt. Würde die erste Bibliothek statdessen einfach einen globalen const int verwenden, hätten wir das Problem nicht.


Zum Thema Fehlerhandling: Du könntest dir mal das Prinzip der Exceptions ansehen (try, throw, catch). Muss aber für dich nicht die ideale Lösung sein. Die ideale Strategie der Fehlerbehandlung ist immer stark abhängig vom Aufbau des gesammten Projekts.
jaH.Da.izi

RANG Deckschrubber

#4 - 19.02 10:46

Sorry für die späte Antwort, aber irgendwie hab ich mich festprogrammiert ;-).

Das mit den assoziativen Arrays hab ich mir aber schonmal ein bisschen angeschaut, weiss aber noch nicht genau, ob ich es überhaupt verwenden müsste. Komme momentan mit dem von mir oben genannten Beispiel ganz gut zurecht.
Habe auch eine ähnliche Struktur in irgendeiner Bibiliothek gefunden. Da hatten die zum Beispiel für "Textausgabe" foldenes verwendet.

code:

struct TEXT {
   static const char* Logtext() = { return "Logtext 123 abc" };
};


Sah für mich jetzt schonmal ganz nett aus :o). Was mich dann aber gleich mal interessieren würde. Wie würde ich das dann an ne Funktion übergeben? Also die Ausgabe erhalte ich normal ja mit:

code:

cout << "TEXT: " << TEXT::Logtext() << engl;


Jetzt würd ich das gern mal an ne Funktion übergeben, die zum Beispiel in ne Datei schreibt. Wie sieht das dann aus? _write_log(TEXT::Logtext()) geht jawohl irgendwie nicht -.-



Meine aktuelle Frage wäre aber: Ich habe hier vier verschiedene Bilder, die heissen europa_50.jpg, europa_100.jpg, europa_150.jpg, europa_200.jpg. Diese Bilder möchte ich jetzt dynamisch anhand eines bestimmten Events laden. Als Beispiel: Wenn ich die Pfeiltaste nach oben drücke, soll ein Bild mit höherem Zahlenwert im Namen geladen werden. Wenn ich die Pfeiltaste nach unten drücke eben das Gegenteil.
Ich hab eigentlich überhaupt garkeine Idee, wie ich das jetzt realisieren könnte. Hier kommen nämlich ein paar Probleme zusammen. Wo "schreibe" ich meine Namen der Bilder hin (wie meine erste Frage eigentlich, lager ich die Namen sinnvoller Weise dann aus? In ein assoziatives Array?)? Erstell ich den Namen dynamisch (so hätte ich es in Perl gemacht, die 50 ausgelesen und eben ersetzt oda damit gerechnet,...hier gibt es aber Probleme mit den Typen ;-))?

Klar würde das ganze mit verschiedenen if/else und einer einfach string-Übergabe funktionieren, aber das ist doch unschön oder?




Und nicht wirklich eine Frage, aber vielleicht eine Bitte. Ich bin jetzt gerade mal 2 Tage mit C++ wirklich beschäftigt, d.h. auch mit Objektorientierung. Schon eine sehr andere Welt und ich hab mich hin und wieder ertappt, wie ich es versucht habe zu "perlen". :o)
Deshalb: Würde sich jemand mal kurz ein bisschen anschauen, wie mein Code momentan aussieht? Kurz zum Programm. Wird ein kleines 2D-Spiel, nichts besonderes, einfach nur zum Lernen. Versuche da eben Objektorientiert dranzugehen...
Geht mir jetzt garnicht um den ganzen Code, nur ein paar Ausschnitte. Schaut einfach mal:

Hier mal meine Klasse für die Engine:

code:

class myEngine {
   public:
      // Die verschiedenen Surfaces/Oberflächen/Bilder/Texturen
      SDL_Surface *display,
                  *world;

      // SDL initialisieren
      void Init();
      // Videomodus für das display (Hauptfenster) setzen
      void SetVideoMode();
      // Das display (Hauptfenster) refreshen
      void FlipDisplay();
      // Zwei Surfaces übereinanderlappen
      void BlitSurface(SDL_Surface *surface_1, SDL_Rect *surface_1_source, SDL_Surface *surface_2, SDL_Rect *surface_2_source);
      // Die Spielwelt zeichnen
      void DrawWorld(char *filename);
};


display und world sind ja einfache Bilder. Hier mal, wie ich "world" erstelle:

code:

void myEngine::DrawWorld(char *filename) {
   // Das Bild der Welt laden
   world = IMG_Load(filename);

   // Prüfen, ob das Bild geladen werden konnte. Zeiger ist NULL?
   if (world == NULL) {
      cout << (stderr ,"Welt konnte nicht erstellt werden: %s\n", SDL_GetError()) << endl; 
      exit(-1);
   }
   // Die Spielwelt auf das display (Hauptfenster) legen
   BlitSurface(world, NULL, display, NULL);
}


und hier noch, wie ich es benutze (in europa_50 ist momentan ein einfacher string enthalten):

code:

   myEngine *Engine = new myEngine();
   ...
   ...
   ...
   Engine->DrawWorld(europa_50); // Die Spielwelt zeichnen
Crush (korrumpiert die Jugend)

RANG Deckschrubber

#5 - 19.02 18:42

Der genaue Zweck dieser Struktur die du da im oberen Teil deines Postings erwähnst ist mir ziemlich schleierhaft. Wenn es dabei darum geht alle Stringvariablen unter einem Prefix "TEXT::" zusammenzufassen, würde ich keine struct sondern einen namespace verwenden.

Für Zeichenketten sollte man als C++ Programmierer keine antiquierten char arrays verwenden, ausser wenn man mit für C entwickelten Bibliotheken arbeitet. Statdessen sollte man lieber String Klassen wie "string" und "stringstream" aus der Standart Template Library verwenden. So spart man sich eine Menge Mühe mit dem Low Level Handling. http://www.cppreference.com/cppstring/index.html
http://www.cppreference.com/cppsstream/index.html


Zum Thema Strings zusammensetzen: Das ist in C/C++ nicht ganz so kinderleicht wie in $SCRIPTSPRACHE + " oder den " + 100 + " verwandten Sprachen", aber auch kein allzugroßes Problem.

Wenn du weiterhin mit char arrays arbeiten willst, kannst du die Funktion sprintf verwenden, um einen String zu formatieren. Beispiel:
code:
#include <iostream>
using namespace std;

int main()
{
    char out[255];
    int num = 42;
    char obstsorte[] = "Bananen";
    sprintf (out, "Sie haben %d %s", num, obstsorte);
    cout<<out<<endl;
}
//Ausgabe: "Sie haben 42 Bananen"
Die Platzhalter "%d" und "%s" in der sprintf Zeile sind Platzhalter, für die die nachfolgenden Variablen eingesetzt werden. Dabei wird die erste (%d) zu einer Dezimalzahl und die zweite (%s) zu einem String umgewandelt.

Wenn du richtiges C++ und nicht C mit Klassen schreiben willst, also mit Stringklassen aus der STL arbeiten willst, bietet sich hier die Klasse stringstream an, mit der du praktisch alles machen kannst, was du auch mit cout und cin machen kannst, also z.B. mit << praktisch alle gängigen Variablentypen reinstopfen. Die Typumwandlung funktioniert hier automatisch.
code:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main()
{
    stringstream out;
    int num = 42;
    string obstsorte = "Bananen";
    out<<"Sie haben "<<num<<" "<<obstsorte;
    cout<<out.str();
}
jaH.Da.izi

RANG Deckschrubber

#6 - 21.02 09:25

Ah super Antwort, danke, genau das wollte ich hören.


Also das mit dem TEXT zusammenfassen hast du schon richtig erkannt, genau das ist der Sinn (oder eben nicht Sinn :D). Ich habe es einfach ganz gerne, wenn ich Konfiguration, Text etc. schnell ändern kann. Wenn ein Benutzer eben lieber mit "Moin Moin" als "Guten Tag" begrüßt wird (oder was weiss ich ;-)), dann soll er das möglichst schnell und einfach, ohne viele Programmierkenntnisse, auch ändern können.
Ebenso die ganzen Errormessages, sollen alle schön wie auch der normale Ausgabetext in einen Block ausgelagert sein. Aber hast ja schon erwähnt, sollte mal nach "namespace" suchen, krieg ich schon hin ;-).


Das mit den Strings war mir auch aufgefallen, aber ich wundere mich eben, wieso so viele C++ Beispiele dennoch mit char und sprintf arbeiten.
Das Zusammensetzen von Strings auf diese Weise (sprintf), das kannte ich bereits. Mein Problem ist ja aber nicht die Ausgabe auf dem Bildschirm, sondern eine zusammengesetzte Variable, die ich beim Funktionsaufruf dynamisch erstelle. Hat sich jetzt aber eh erledigt, da ich keine verschiedenen Karten (Bilder) mehr brauche, weil ich eine nette Zoom-Funktion gefunden habe, aber interessieren würde es mich trotzdem noch.


Naja ich mach dann mal weiter und schmeiss den aktuellen Code über den Jordan. Hatte mich irgendwie gestern bei meiner Engine verrannt. Naja was solls ;-).
Crush (korrumpiert die Jugend)

RANG Deckschrubber

#7 - 21.02 15:53

VariablenNAMEN zur Laufzeit zusammensetzen geht in C/C++ nicht. Das liegt daran, dass in einem fertig kompilierten C/C++ Programm aus Performancegründen überhaupt keine Variablennamen mehr zu finden sind* sondern alle Variablen zu Speicheraddressen umgewandelt wurden.

Mit einer entsprechend designten Containerklasse kannst du dieses Verhalten aber zumindest simulieren.


Zu den Tutorials in denen immer wieder char-array strings auftauchen: Das liegt daran, dass
1. Viele Tutorials für C Programmierer geschrieben sind und man diese nicht gleich mit zu vielen Neuerungen verschrecken will.
2. Viele Tutorials aus der Zeit stammen als die STL noch nicht zum offiziellen C++ Standard gehörte bzw. noch nicht von allen gängigen Implementierungen vollständig unterstützt wurde.
3. Viele Leute mit Bibliotheken arbeiten die ihre eigene Stringklasse mitbringen (vielen ist std::string entweder zu umfangreich oder nicht umfangreich genug).



*) OK, OK, fast alle Compiler haben einen Debugmodus in denen sie Variablennamen als Kommentare in das Binärprogramm schreiben damit der Debugger sich besser zurecht findet. Aber auf diese hat das Programm selber keinen Zugriff.
jaH.Da.izi

RANG Deckschrubber

#8 - 22.02 19:00

"VariablenNAMEN zur Laufzeit zusammensetzen geht in C/C++ nicht. Das liegt daran, dass in einem fertig kompilierten C/C++ Programm aus Performancegründen überhaupt keine Variablennamen mehr zu finden sind* sondern alle Variablen zu Speicheraddressen umgewandelt wurden."

Klingt logisch, hätte ich echt selber drauf kommen können ;-). Manchmal sieht man aber selbst den Wald vor lauter Bäumen nicht mehr.



Das mit den char Strings hatte ich mir schon fast gedacht, war mir dann aber doch unsicher, deshalb lieber mal nachgefragt.




Ich glaube aber, dass ich jetzt schon richtig gut klarkomme. Meine kleine SDL-Engine funktioniert schonmal so weit. Zoomen und bewegen klappt auch schon :o). Ich halte euch auf dem laufenden bzw. melde mich, wenn es wieder Probleme gibt ;-).