for_each ausgabe einer klasse


  • Administrator

    Gustl schrieb:

    Jester schrieb:

    Bist Du sicher, dass der Code genauso aussieht wie Du oben sagst? Mich wundert ein bißchen, dass Du eine const-Referenz im operator>> hast.

    Ja, genauso sieht der code aus, asc hat mir empfohlen bei operator>> const zu verwenden, da diese Übergabeparameter ja auch nicht verändert werden, ist das wohl falsch?

    Es ist nicht nur falsch, sondern wäre auch gar nicht erst kompilierbar. Beim operator << ist die konstante Referenz korrekt, dort wird das Objekt schliesslich nicht verändert, aber beim operator >> veränderst du das Objekt ja. Du liesst schliesslich in das Objekt ein. Daher ist es sehr wahrscheinlich, dass du uns nicht den richtigen Code gezeigt hast. Denn der gezeigte würde nicht kompilieren und daher wäre kein Stackoverflow möglich 😉

    Grüssli



  • @ Gustl:
    Es ist mir bewusst, dass es sehr demotivierend sein kann, mehrere Stunden lang einen Fehler zu suchen. Es ist auch okay, dass du hier fragst, nur können wir dir auch beim besten Willen nicht mehr gross weiterhelfen. Dazu kommt, dass der Code mit grosser Wahrscheinlichkeit nicht dem entspricht, der den Fehler erzeugt (siehe Draveres Post). Vielleicht ist es auch möglich, dass mit dem Compiler etwas nicht stimmt.

    Das mit dem Debugger war nur ein guter Ratschlag, der kann dir Fehlersuche massiv vereinfachen.



  • 😃

    genau das const muss hier an dieser Stelle raus... dann funktioniert es, auch ohne stack overflow.

    Aber ich habe euch 100pro den Orginal code gezeigt und er komiliert auch mit den const beim operator >>... unglaublich aber wahr

    Jetzt habt ihr mir doch geholfen. 😃

    Thx

    MfG Gustl



  • In diesem Falle gibt es nur eins: Wechsle so schnell wie möglich den Compiler.

    Dass er solch fehlerhaften Code zulässt und dadurch einen Laufzeitfehler erzeugt, kann es ja nicht sein.



  • hab visual studio 2005 V2.0

    dachte eigentlich schon das der gut ist...

    hats einer probiert zu kompilieren?



  • Gustl schrieb:

    hab visual studio 2005 V2.0

    dachte eigentlich schon das der gut ist...

    Wäre mir auch nicht bekannt, dass der solche Bugs hat. Tritt der Fehler bei einer Const-Referenz auch in anderen Kontexten auf?

    Ansonsten kann ich Microsoft Visual C++ 2008 Express empfehlen, diese IDE finde ich sehr gut.



  • Also an anderen stellen merkt der compiler das und gibt laut...



  • Hallo zusammen,

    ein paar Bemerkungen zu der Diskussion:

    1. const und warum compiliert es?

    Gustl hatte auf Seite 3 dieses Themas noch folgende Funktion verwendet:

    [cpp]ostream& operator<<(ostream& outstream, Adresse & const adresse) [/cpp]
    

    Eine Seite später war es dann diese Version:

    [cpp]std::ostream& operator<<(std::ostream& outstream, Adresse const & adresse) 
    std::istream& operator>>(std::istream& instream, Adresse const & adresse)
    [/cpp]
    

    Prüfe das nochmal genau, an welcher Stelle das "const" und wo das Ampersand steht. Denn:

    [cpp]
    Adresse & const adresse    <==>     Adresse & adresse         aber nicht gleich:
    Adresse const & adresse    <==>     const Adresse & adresse[/cpp]
    

    Im ersten Fall ist es eine konstante Referenz (was redundant ist, denn eine Referenz ist per definitionem konstant, daher sollte man das const auch weglassen) auf Objekt vom Typ Adresse. Im zweiten Fall ist es eine Referenz auf ein konstantes Objekt.

    Der erste Fall dürfte ohne Probleme kompilieren, der zweite Fall natürlich nicht.

    2. Container von Objekten vs. Container von Pointern

    Meiner Erfahrung nach ist es besser, nicht die Objekte selbst im Container (list, deque...) zu speichern, sondern Pointer darauf. Ist sicher Ansichtssache, hat aber einige Vorteile:
    1. Die Objekte werden nur einmal erzeugt und liegen dann auf dem Heap. Beim Einfügen in den Container wird nicht das gesamte Objekt KOPIERT, sondern nur 4 Bytes eingefügt.
    2. Der Container selbst bleibt klein, da er ja nur den Pointer dazukriegt. Sollten also Elemente zum Container dazukommen, sich ändern oder gelöscht werden, kann die Operation schnell erfolgen, andernfalls muss nämlich der komplette Container (mit allen Objekten drin) im Speicher verschoben oder umorganisiert werden. Bei Pointern bleiben die Objekte, wo sie sind, und der Container bleibt klein.
    3. Objekte werden genau dann erzeugt, wenn man sie braucht. Fixe Objekte wie Adresse1, Adresse2, Adresse3 sind ja später nicht vorgesehen, und auch der Code der Ladefunktion

    [cpp]    for(Adresse tmp;f>>tmp;)
            liste.push_back(tmp);[/cpp]
    

    ergeugt ein Objekt zu viel (das letzte nämlich). Auch wenn es gleich wieder gelöscht wird, sollte das nicht geschehen.

    Wie wäre es daher mit diesem:

    [cpp]// in adressliste.h:
    
    typedef std::deque< Adresse* >   liste_t;   // nur ein anderer Name, leichter zu benutzen...
    
    liste_t liste;
    
    // adressliste.cpp:
    
    int Adressliste::loadfromfile( char const *dateiname )
    {
        ....
    
        while( ! f.eof( ) )
        {
            Adresse* pAdresse = new adresse;     // <-- Das Objekt wird erst hier erzeugt
            f >> *pAdresse;
            liste.push_back( pAdresse );
        }[/cpp]
    

    Jetzt werden exakt so viele Adressen erzeugt wie benötigt, und zwar schön kompakt alles innerhalb der while-Schleife. Später benutzt man die Liste wie gehabt, verwendet dann einfach -> statt . wenn man auf ein Element zugreifen will.

    3. For each

    Die for_each( ) -Schleife hat leider den Nachteil, dass ihre Benutzung so sehr anderes ist als die normale for( )-Schleife: Man braucht immer noch ein Funktionsobjekt, in dem die eigentliche Arbeit getan wird. Dadurch wird der Funktionsblock und der ausgeführte Code auseinandergerissen. Ich benutze daher gerne folgendes Makro (egal ob global oder lokal definiert). Wermutstropfen ist, dass wir den Typ des Containers angeben müssen, aber den haben wir ja vorher durch das typedef schon "klein" gemacht. ITER ist wie die Laufvariable in der for( )-Schleife, nur eben ein Iterator des Containers:

    [cpp]#define FOREACH(ITER, CONTAINER, TYPE) for(TYPE::iterator ITER = CONTAINER.begin(); ITER != CONTAINER.end(); ++ITER)[/cpp]
    

    Die Ausgabemethode würde damit (und mit der Pointerliste aus Abschnitt 2) so ausschauen (beachte: wir brauchen den globalen Iterator it nicht mehr! Global ist sowieso zu vermeiden).:

    [cpp]void Adressliste::ausgabe( )
    {
        FOREACH( adresse; liste; liste_t )
        {
            std::cout << *adresse;     // Beachte: ein Iterator ist ein Pointer!
        }   
    }[/cpp]
    

    Oder der Destruktor für die Adressenliste. Wir brauchen ihn, da der Standard-Destruktor nur die Liste selbst mit den Pointern löscht, nicht aber die Adressen, auf die die Pointer zeigen:

    [cpp]Adressliste::~Adressliste( )
    {
        FOREACH( adresse, liste, liste_t )
        {
            delete *adresse;       
        }
    }[/cpp]
    

    4. Große Rückgabewerte von Funktionen (Code von asc):

    Deine Funktion AdressenEinlesen( ) hat als Rückgabewert die komplette Liste, die - neu - in der Datei erzeugt wird. Das hat einige Nachteile:
    1. Bei der Rückgabe wird die gesamte Liste in die in main( ) erzeugte Liste KOPIERT. Das kann u.U. sehr aufwendig sein.
    2. Was machst Du im Fall eines Fehlers, z.B. Datei ist nicht da?

    Es ist eleganter, die Liste nur einmal (in main) zu erzeugen und der Funktion AdressenEinlesen( ) als Referenz zu übergeben. Diese füllt dann die Adressen gleich in die Original-Liste ein (man könnte dann sogar an eine existierende Liste anhängen). Dann hast Du den Rüchgabewert frei für z.B. einen Fehlercode. Etwas so:

    [cpp]int AdressenEinlesen( std::string const & dateiname, std::list<Adresse> & adressen )
    {
        // 1. Datei Öffnen
        std::fstream datei(dateiname.c_str(), std::ios::in);
        if(datei.bad())
            return -1;          //  <-- hier nur der Fehlercode
    
        // 2. Daten übertragen
        std::copy(
            std::istream_iterator<Adresse>(datei),  // Vom Dateibegin...
            std::istream_iterator<Adresse>(),       // ...bis kein Eintrag mehr existiert
            std::back_insert_iterator<std::list<Adresse> >(adressen)); // an die Liste hängen
    
        return 0;               //  <-- alles gutgegangen
    }
    
    // in main():
    std::list<Adresse> adressen;
    int fehler = AdressenEinlesen("datei.txt", adressen);
    if( 0 != fehler ... [/cpp]
    

    Oder eben, wie es Gustl macht, dass das Einlesen eine Methode der Klasse Adressenliste ist...



  • O je, muss mich gleich verbessern, die Ausgabefunktion muss natürlich heißen:

    [cpp]void Adressliste::ausgabe( )
    {
        FOREACH( adresse; liste; liste_t )
        {
            std::cout << *( *adresse ); 
        }  
    }[/cpp]
    

    Unser Iterator ist ja ein Pointer - auf einen Pointer - auf eine Adresse.


  • Administrator

    @minastaros,
    Hast du getrunken? Nimmst du irgendwelche Drogen? Bist du ein Troll? Oder ist es dir mit dieser Argumentation wirklich ernst?????? 😮

    Grüssli



  • @Dravere:
    Das war gemein -.- Er hats bestimmt gut gemeint (scho ma nen Troll gesehen, der so viel Zeit investiert?) und mit Sicherheit eben auch einfach mal so beigebracht bekommen und seinem Lehrer/Dozent/... geglaubt...

    1. Bei der Rückgabe wird die gesamte Liste in die in main( ) erzeugte Liste KOPIERT. Das kann u.U. sehr aufwendig sein.
    2. Was machst Du im Fall eines Fehlers, z.B. Datei ist nicht da?

    1. wird eh wegoptimiert...
    mach mal das hier:

    std::list<Adresse> a;
    std::list<Adresse> b;
    for (size_t i = 0; i != 999; ++i)
       b.push_back (Adresse);
    std::cout << sizeof (a) << " == " << sizeof (b) << std::endl;
    

    wenn dich das wundert, dann kannst du dir mal die member von std::list / std::vector / std::deque / ... angucken...
    außerdem hat die variante von asc nen weiteren Vorteil:

    const std::list<Adresse> adressen = AdressenEinlesen ("asd.txt");
    
    //vs.
    
    std::list<Adresse> adressen; // kein const möglich :<
    int r = AdressenEinlesen ("asd.txt", adressen); //und mind. 4 Zeilen mehr + keine genaue Angabe, was passiert ist
    if (r)
      return;
    

    2. exception werfen

    zur sache container <T> vs container <T*>:
    container <T> ist sehr viel einfacher (schon das freigeben beim zerstören des objektes) - wenn man das umkopieren verhindern möchte, nimmt man std::list - wenn man die objekte beim kopieren iwie komisch behandelt werden sollen, spezialisiert man std::swap entsprechend...

    zu deinem makro:

    void Adressliste::ausgabe( )
    {
        FOREACH( adresse; liste; liste_t )
        {
            std::cout << *( *adresse ); 
        }  
    }
    

    das sagt ja wohl schon alles... ~~
    keine sau sieht durch... noch dazu würde man eine ausgabe() fkt const machen, dein makro verwendet aber iterator statt const_iterator - stinkt auch wieder 😛
    weiß auch nicht, in wie fern der compiler den fkt-aufruf bei dir jeden schleifen-durchlauf wegoptimieren kann und darf (du rufst unsinnigerweise jedes mal wieder end() auf...

    typedef std::vector<TkomischesObjekt> TadressContainer;
    
    void Adressliste::ausgabe( ) const
    {
        for (TadressContainer::const_iterator iter (adresse.begin()), end (adresse.end()); iter != end; ++iter )
        {
            std::cout << *iter;
        }  
    }
    

    Es ist einfach lesbarer, es ohne ptr zu machen, ist nicht so fehleranfällig (löschen eines objektes etc...) und es ist eben max. beim hinzufügen langsamer (glaube ich aber nicht so recht ^^) - wenn man ständig was hinzufügt und löscht, nimmt man eben ne deque, wenn nur einmal was hinzugefügt wird, dann vector und wenn man selten über alle elemente iterieren muss, dann nimmt man eben ne list - oder, wenn man häufig elemente aus der mitte löscht oder dort einfügen möchte... da gibts auch nen hübsches bild iwo im internet, wann welcher container zu nutzen ist - hab aber keine url, sondern mir das bild iwann ma gespeichert ^^

    und um noch ma das bsp von oben zu verwenden:

    void foo1 ()
    {
      const std::list<Adresse> adressen = AdressenEinlesen ("asd.txt");
    
    /*arbeit machen*/
    }
    
    void foo2 ()
    {
      const std::list<Adresse *> adressen = AdressenEinlesen ("asd.txt");
    
      try
      {
    /*arbeit machen*/
      }
      catch (...)
      {
        while (! adressen.empty())
          delete adressen.pop_back(); //speichermanagement -.-
        throw; //und weiterwerfen
      }
    
      while (! adressen.empty())
         delete adressen.pop_back();
    } //wir müssen in AdressenEinlesen alle exceptions auffangen und Speicher freigeben und sie dann weiterwerfen
    
    void foo3 ()
    {
      std::list<Adresse *> adressen;
      int r = AdressenEinlesen ("asd.txt", adressen);
      if (r)
        goto tidy_up_foo
      /*wie bekommt der aufrufer jz mit, dass was schief ging?
        -> wir bräuchten wieder nen rückgabewert - und um genau das zu verhindern,
        hat uns C++ exceptions gegeben*/
    
      try
      {
    /*arbeit machen*/
      }
      catch (...)
      {
        while (! adressen.empty())
          delete adressen.pop_back();
        throw;
      }
    
     tidy_up_foo:
      while (! adressen.empty())
         delete adressen.pop_back();
    }
    /*hier braucht man eigtl keine gotos, aber spätestens bei 2 oder 3 weiteren solchen Objekten würde ich endültig gotos nehmen,
    weil man sonst die hälfte des bildschirms einrücken müsste...
    es ist eben einfach die (für mich) am übersichtlichsten wirkende Methode um viele Fkt aufzurufen, die alle nen Rückgabewert haben und abhängig davon nen Objekt erzeugen,wo man sich um die Zerstörung kümmern muss*/
    

    Meinst du noch immer, dass deine Variante besser ist?

    zu allerletzt:

    ergeugt ein Objekt zu viel (das letzte nämlich). Auch wenn es gleich wieder gelöscht wird, sollte das nicht geschehen.

    für genau so etwas ist der standard-ctor aber da... er sollte minimale ressourcen brauchen (da in 99% der fälle eh alles nur mit 0 initialisiert wird)... und wenn wir uns um EINEN solchen vorgang streiten... da sind selbst deine ständigen dereferenzierungen schlimmer (weil ihre häufigkeit eben linear ist - und nicht kostant(1) ).

    bb



  • das sagt ja wohl schon alles... ~~

    So etwas ähnliches macht durchaus Sinn. Bei komplizierten Konstrukten benutze ich noch gerne boost::foreach.


  • Administrator

    unskilled schrieb:

    @Dravere:
    Das war gemein -.- Er hats bestimmt gut gemeint (scho ma nen Troll gesehen, der so viel Zeit investiert?) und mit Sicherheit eben auch einfach mal so beigebracht bekommen und seinem Lehrer/Dozent/... geglaubt...

    Ja, ich habe schon Trolle gesehen, welche noch viel mehr Arbeit investiert haben.
    Aber du hast womöglich trotzdem recht, dass es kein Trollversuch ist. Bei mir kam wohl die absolute Frustration durch. Schon wieder ein Neuling, der schon wieder nur Halbwahrheiten kennt und den man schon wieder aufklären muss. Wir machen hier eine absolute Sisyphosarbeit. Für jeden Neuling den wir belehren, kommen zwei dazu 😃

    @minastaros,
    Möchte mich mal entschuldigen, falls das etwas beleidigend rüber kam.

    Nun aber zu deinem Text ...

    minastaros schrieb:

    1. const und warum compiliert es?
    Im ersten Fall ist es eine konstante Referenz (was redundant ist, denn eine Referenz ist per definitionem konstant, daher sollte man das const auch weglassen) auf Objekt vom Typ Adresse. Im zweiten Fall ist es eine Referenz auf ein konstantes Objekt.

    Der erste Fall dürfte ohne Probleme kompilieren, der zweite Fall natürlich nicht.

    Es ist nicht nur redundant, es ist falsch. Ohne Probleme kompilieren tut es auch nicht, bei meinem Kompiler wird eine Warnung geworfen. Es würde mich nicht erstaunen, wenn andere Kompiler sogar mit einem Fehler abbrechen.

    Die Sache ist aber, dass Gustl nun schon mehrfach gesagt hat, dass der Code genau der gleiche wäre. Zudem wäre der Fehler nicht beim operator << aufgetaucht, sondern beim operator >>. Also geht die Argumentation in meinen Augen überhaupt nicht auf.

    minastaros schrieb:

    2. Container von Objekten vs. Container von Pointern
    Meiner Erfahrung nach ist es besser, nicht die Objekte selbst im Container (list, deque...) zu speichern, sondern Pointer darauf. Ist sicher Ansichtssache, hat aber einige Vorteile:

    Ganz sarkastische Bemerkung:
    Was für Erfahrungen sind das denn gewesen?

    minastaros schrieb:

    1. Die Objekte werden nur einmal erzeugt und liegen dann auf dem Heap. Beim Einfügen in den Container wird nicht das gesamte Objekt KOPIERT, sondern nur 4 Bytes eingefügt.

    Im Kontainer wird normalerweise der Defaultallokator verwendet, um Speicher für die Objekte zur reservieren. Dieser nutzt nichts anderes, als ein new . Die Objekte im Kontainer liegen also genauso auf dem Heap.
    Eine Kopie von 100 Bytes oder 4 Bytes ist übrigens meistens nicht so schlimm. Probleme tauchen meistens an ganz anderen Stellen auf. Das was du machst, nennt man premature optimization. Man probiert die Laufzeit des Programmes zu verbessen, ohne zu wissen wo die Problemstellen tatsächlich sind. Das führt meistens zu sehr schlechtem Code.

    minastaros schrieb:

    2. Der Container selbst bleibt klein, da er ja nur den Pointer dazukriegt. Sollten also Elemente zum Container dazukommen, sich ändern oder gelöscht werden, kann die Operation schnell erfolgen, andernfalls muss nämlich der komplette Container (mit allen Objekten drin) im Speicher verschoben oder umorganisiert werden. Bei Pointern bleiben die Objekte, wo sie sind, und der Container bleibt klein.

    Dieser Punkt ist doppelt verkehrt. Da die Objekte im Kontainer über den Allokator auf dem Heap landen, verändert sich die Grösse des Kontainerobjektes überhaupt nicht.
    Zudem müssen nicht immer alle Objekt im Kontainer neuorganisiert werden, wenn ein neues Objekt dazukommt. Die Kontainer haben da ganz unterschiedliche Techniken. Ein std::vector reserviert Speicher im voraus, um die Anzahl Kopiervorgänge zu verringern. Eine std::deque arbeitet mit Speichersegmenten. Eine std::list braucht normalerweise sogar gar keine Kopie mehr, weil die Objekte einfach umgehängt werden. Eine std::map , std::set , std::multimap und std::multiset haben intern einen Baum. Dieser arbeitet mit Nodes wie eine std::list . Die Objekte werden somit auch einfach nur umgehängt.

    Ergo -> Du solltest dich mal ein wenig einlesen in den Bereich von Kontainern. Wir haben glaub ich sogar einen Artikel im Magazin.

    minastaros schrieb:

    3. Objekte werden genau dann erzeugt, wenn man sie braucht. Fixe Objekte wie Adresse1, Adresse2, Adresse3 sind ja später nicht vorgesehen, und auch der Code der Ladefunktion

    [cpp]    for(Adresse tmp;f>>tmp;)
            liste.push_back(tmp);[/cpp]
    

    ergeugt ein Objekt zu viel (das letzte nämlich). Auch wenn es gleich wieder gelöscht wird, sollte das nicht geschehen.

    Das Objekt tmp wird genau nur ein einziges Mal erstellt, nämlich am Anfang der for-Schleife. Danach wird jeweils in dieses Objekt eingelesen und das Objekt in die Liste kopiert. Es wird nie neu erstellt. Man hat ein einzelnes zusätzliche Objekt, welches sich auf dem sehr schnellen Stack befindet. Es wäre sogar möglich, dass der Kompiler es schafft, dieses Objekt gänzlich wegzuoptimieren. Mit so einem Vorgehen gibt es überhaupt gar keine Nachteile.

    minastaros schrieb:

    ....
    
        while( ! f.eof( ) )
        {
            Adresse* pAdresse = new adresse;     // <-- Das Objekt wird erst hier erzeugt
            f >> *pAdresse;
            liste.push_back( pAdresse );
        }
    

    Und WER gibt die Objekte wieder frei? Wem gehören die Objekte? Du musst hier ein zusätzliches Speichermanagement einbauen, welches nur stört. Zudem verbraucht deine Lösung sogar noch mehr Speicher und könnte gar noch langsamer sein. Wieso? Diese kleinen 4 oder 8 Byte Adressen werden zusätzlich gespeichert. Und im Kontainer wird dazu meistens der Defaultallokator verwendet. Dieser ist aber meistens nicht sehr optimal, um kleine Objekte zu allokieren und verhält sich daher oft eher langsam.

    minastaros schrieb:

    3. For each
    Die for_each( ) -Schleife hat leider den Nachteil, dass ihre Benutzung so sehr anderes ist als die normale for( )-Schleife: Man braucht immer noch ein Funktionsobjekt, in dem die eigentliche Arbeit getan wird. Dadurch wird der Funktionsblock und der ausgeführte Code auseinandergerissen.

    Vor allem der letzte Satz beweist mir eindeutig, dass du die folgenden Dinge nicht kennst:
    <functional>
    Boost.Bind, bzw. std::tr1::bind, bzw. C++0x std::bind
    Boost.Lambda, bzw. C++0x Lambda-Expressions

    Des Weiteren ist so eine Aufteilung teilweise wirklich gewünscht. Wenn ein Funktor nämlich eine allgemeingültige Aufgabe erledigen muss, eine Aufgabe, welche man an verschiedenen Stellen einsetzt mit verschiedenen Kontainer, ist das durchaus eine sinnvolle Sache. Sonst müsstest du dein FOREACH überall erneut hinschreiben müssen -> Menge an Codeduplizierung.
    Oder du lagerst die sache in eine Funktion aus, dann hast du aber nicht viel mehr als du mit einem Funktor hättest.

    minastaros schrieb:

    (beachte: wir brauchen den globalen Iterator it nicht mehr! Global ist sowieso zu vermeiden).:

    Diese Aussage finde ich aber der absolute Knüller. Was für einen globales Iterator Objekt? Seit wann braucht ein std::for_each ein globales Iterator Objekt? So ein UNSINN!

    minastaros schrieb:

    Oder der Destruktor für die Adressenliste. Wir brauchen ihn, da der Standard-Destruktor nur die Liste selbst mit den Pointern löscht, nicht aber die Adressen, auf die die Pointer zeigen:

    Den du gar nicht erst benötigen würdest, wenn du die Objekte, statt den Zeiger speichern würdest.

    minastaros schrieb:

    4. Große Rückgabewerte von Funktionen (Code von asc):
    Deine Funktion AdressenEinlesen( ) hat als Rückgabewert die komplette Liste, die - neu - in der Datei erzeugt wird. Das hat einige Nachteile:
    1. Bei der Rückgabe wird die gesamte Liste in die in main( ) erzeugte Liste KOPIERT. Das kann u.U. sehr aufwendig sein.

    minastaros schrieb:

    Es ist eleganter, die Liste nur einmal (in main) zu erzeugen und der Funktion AdressenEinlesen( ) als Referenz zu übergeben. Diese füllt dann die Adressen gleich in die Original-Liste ein (man könnte dann sogar an eine existierende Liste anhängen).

    Da gäbe ich dir recht. Das ist der einzige Punkt, wo ich das tue ^^
    Allerdings möchte ich noch etwas hervorheben, was du geschrieben hast, was aber beinahe droht unter zu gehen:
    Das kann u.U. sehr aufwendig sein.
    !Unter Umständen!
    Und meistens kann der Programmierer diese Umstände schlecht abschätzen. Denn "Unter Umständen" kann der Kompiler solche Kopien effizient wegoptimieren.

    minastaros schrieb:

    2. Was machst Du im Fall eines Fehlers, z.B. Datei ist nicht da?

    Wenn du nun den Rückgabewert der Funktion als Fehlermeldung missbrauchen willst, dann empfehle ich dir dringend die folgende Lektüre:
    http://magazin.c-plusplus.net/artikel/Exception-Handling
    http://magazin.c-plusplus.net/artikel/Modernes Exception-Handling Teil 1 - Die Grundlagen
    http://magazin.c-plusplus.net/artikel/Modernes Exception-Handling Teil 2 - Hinter den Kulissen

    minastaros schrieb:

    Dann hast Du den Rüchgabewert frei für z.B. einen Fehlercode.

    TATSÄCHLICH ... da steht dieser Unsinn. Lies unbedingt die Links im Magazin, welche ich oben hingeschrieben habe.

    drakon schrieb:

    So etwas ähnliches macht durchaus Sinn. Bei komplizierten Konstrukten benutze ich noch gerne boost::foreach.

    Ja, man kann so ein FOREACH tatsächlich manchmal anwenden. Aber Boost.Foreach ist nicht als Ersatz für std::for_each gedacht, so wie es minastaros aber vorgeschlagen hat.

    Grüssli



  • Mal locker bleiben.

    Dravere, war nicht nett, aber Entschuldigung akzeptiert.

    Container

    Selbstverständlich haben sie unterschiedliche Techniken. Gustl hat nun aber eine Deque verwendet, und diese speichert Objekte hintereinander ab und nicht auf dem Heap (dort liegen allenfalls die Speicherseiten). Über eine Deque kann man schön iterieren und einfach neue Elemente anfügen, zum Sortieren oder In-der-Mitte-Einfügen (was bei einer Adressenverwaltung nicht unüblich ist) müssen jedoch die Elemente selbst umkopiert werden. Und dann macht es - je nach Anzahl der Objekte und deren Komplexität - durchaus einen Unterschied zu Pointern.

    Dass die Objekte mitsamt ihren strings (die std-string-Objekte; die c-Strings liegen natürlich woanders) hintereinander im Speicher liegen, zeigt folgendes Programm:

    [cpp]#include <deque>
    
    class Address
    {
    public:
        std::string a;
        std::string b;
        std::string c;
        std::string d;
        std::string e;
        int i;
    
        Address( std::string, std::string, std::string, std::string, std::string, int );
    };
    
    Address::Address( std::string a_, std::string b_, std::string c_, std::string d_, std::string e_, int i_ )
        : a( a_ ) , b( b_ ) , c( c_ ) , d( d_ ) , e( e_ ) , i( i_ )
    {
    }
    
    typedef std::deque< Address > deq_t;
    
    int main() {
    
        deq_t deq;
    
        for( int i = 0; i < 5; ++i )
        {
            Address* adr = new Address( "123", "456", "789", "abcd", "defg", i );
            deq.push_back( *adr );
            delete adr;
        }
    
        for( int i = 0; i < 5; ++i )
        {
            std::cout << "Adresse von Objekt [" << i << "], id " << deq[ i ].i << " : " << &( deq[ i ] ) << std::endl;
        }
    
        deq_t::iterator it = deq.begin( );
        ++it;
        ++it;
        deq.erase( it );    // Löscht Element aus der Mitte
    
        std::cout << "Nach dem Löschen:" << endl;
    
        for( int i = 0; i < 4; ++i )
        {
            std::cout << "Adresse von Objekt [" << i << "], id " << deq[ i ].i << " : " << &( deq[ i ] ) << std::endl;
        }
    
        return 0;
    }
    [/cpp]
    

    Bei mir liefert es folgende Ausgabe:

    Adresse von Objekt [0], id 0 : 0x8053120
    Adresse von Objekt [1], id 1 : 0x8053138
    Adresse von Objekt [2], id 2 : 0x8053150
    Adresse von Objekt [3], id 3 : 0x8053168
    Adresse von Objekt [4], id 4 : 0x8053180
    Nach dem Löschen:
    Adresse von Objekt [0], id 0 : 0x8053120
    Adresse von Objekt [1], id 1 : 0x8053138
    Adresse von Objekt [2], id 3 : 0x8053150
    Adresse von Objekt [3], id 4 : 0x8053168
    

    Jedes Objekt hat demnach 24 Bytes, für jedes Member 4; je mehr Member ein Objekt hat, desto größer ist es auch in der Deque. Man sieht auch, dass die Objekte beim Entfernen eines Elements verschoben werden, ebenso wohl auch beim Sortieren.

    Sarkastische Erfahrung: Es ging um Sensor-Objekte mit zahlreichen Parametern, die dynamisch erzeugt und entfernt werden mussten. In einem Fall auch um verschiedene abgeleitete Typen, die zusammen verwaltet wurden. Das ging dann nur noch über Pointer auf Basisklasse.

    Im Übrigen hatte ich geschrieben, dass man es mit Pointern machen kann, aber man muss es nicht. Kommt eben auf das Projekt an.

    Vor allem der letzte Satz beweist mir eindeutig, dass du die folgenden Dinge nicht kennst:

    Verschätze dich nicht.

    Hab davon gehört, aber muss ich jede Bibliothek einsetzen, nur weil sie existiert? Klar kann man. Nur, bei Embedded-Geräten schaut die Welt etwas anders aus. Wozu also eine neue Bibliothek, wenn mir mein Makro dafür völlig reicht.

    FOREACH

    Danke unskilled für den Tip mit dem end()-Aufruf. Lässt sich aber auch vermeiden, und das Ganze geht natürlich auch mit const:

    [cpp]#define CFOREACH(ITER, CONTAINER, TYPE) for(TYPE::const_iterator ITER = CONTAINER.begin( ), ENDITER = CONTAINER.end( ); ITER != ENDITER; ++ITER)[/cpp]
    

    Ob da "keine" Sau mehr durchblickt, wenn da "FOREACH" steht, mag jeder selbst entscheiden. Leute, wenn es jemand mag, weil er nicht jedes Mal die komplette for-Schleife schreiben will, soll er es machen, wenn nicht, dann eben nicht.

    A propos: Der Aufruf muss selbstverständlich mit Kommas sein.

    Globaler Iterator it: Genau diesen hatte Gustl in seinen Funktionen savetofile() und ausgabe() verwendet.

    Ob die For(each)-Schleife zusammen mit dem, was sie tun soll, sein soll oder getrennt, ist doch wieder eine Frage der Aufgabe und des Geschmacks. In Gustls Programm waren es die beiden Funktionen console() und ausgabe() oder savetofile() und elementspeichern(), die man so eben zusammenfassen kann. KANN.

    Exceptions: Ja, sind eine tolle Sache. Aber sie sollten auch nicht für jeden x-beliebigen Fehler eingesetzt werden. "Fehler" kann als Rückgabewert für eine Datei-Zugriffs-Funktion recht weit verstanden werden, sagen wir besser "Status" oder "Ergebnis". Ob nun if() oder try()/catch() das Problem besser löst, kommt doch immer auf den Anwendungsfall an. Google schreibt in seinen Open-Source-Programmierrichtlinen sogar vor: "We do not use C++ exceptions." Ich verwende sie, aber nur für bestimmte Arten von Fehlern.

    Jedenfalls würde ich nicht eine komplette Liste als Rückabewert übergeben. Nur mal fiktiv: Zur Ladefunktion möchte jemand auch eine Funktion haben, die Datensätze aus einer Datei an eine bestehende Liste anhängt. Das geht dann nicht mehr als Rückgabewert. Wenn man mit Referenzen arbeitet, könnten laden() und anhängen() zumindest eine gleichartige Signatur haben.


  • Administrator

    minastaros schrieb:

    Selbstverständlich haben sie unterschiedliche Techniken. Gustl hat nun aber eine Deque verwendet, und diese speichert Objekte hintereinander ab und nicht auf dem Heap (dort liegen allenfalls die Speicherseiten).

    1. Du hast aber ALLGEMEIN von CONTAINER geredet und dich nicht speziell auf die std::deque bezogen.
    2. Ich glaube ich lese nicht recht? Gib mir bitte Mal eine Quelle, welche besagt, dass die Objekte in einer Deque mit einem Standardallokator nicht auf dem Heap liegen. Wo sollen die sonst sein? Das ist einfach kreuzfalsch. Oder hast du das Gefühl, wenn der Speicher für diese Speicherseiten auf dem Heap angefordert wird, dass die Objekte, welche dann diesen Speicher benutzen, auf einmal nicht mehr auf dem Heap liegen? Da der Speicher vom Heap kommt und die Objekte diesen Speicher benutzen, liegen sie automatisch auch auf dem Heap. Wo sollten sie sonst sein?

    minastaros schrieb:

    Über eine Deque kann man schön iterieren und einfach neue Elemente anfügen, zum Sortieren oder In-der-Mitte-Einfügen (was bei einer Adressenverwaltung nicht unüblich ist) müssen jedoch die Elemente selbst umkopiert werden. Und dann macht es - je nach Anzahl der Objekte und deren Komplexität - durchaus einen Unterschied zu Pointern.

    1. Klar, jeder Container hat seine Vorteile und Nachteile und dem entsprechend setzt man diese ein.
    2. Nein, die Sortieralgorithmen setzen normalerweise eine swap -Funktion ein. Dadurch ist ein Kopieren des ganzen Objektes meistens nicht nötig und es können hauptsächlich nur Zeiger getauscht werden, bzw. es ist nur die Kopie von ein paar sehr kleinen Datentypen nötig.
    3. Überlass diese Performance Abschätzung einem Profiler. Ich habe bisher viele Business Anwendungen und ähnliches Programmiert. Eines habe ich dabei gelernt, in 300ms lassen sich jede Menge Daten bearbeiten. Und 300ms ist der Bereich, wo der Anwender nichts merken wird, dass etwas Zeit beansprucht hat.

    minastaros schrieb:

    Dass die Objekte mitsamt ihren strings (die std-string-Objekte; die c-Strings liegen natürlich woanders) hintereinander im Speicher liegen, zeigt folgendes Programm:

    ...
    

    1. Das ein std::string intern mit eine C-String arbeitet ist nicht gegeben. Ist allerdings meistens so. Dies aber nur als Randbemerkung.
    2. Das Programm ist der grösste Witz, den ich bisher gesehen habe. Du scheinst keine Ahnung zu haben, wie eine std::deque aufgebaut ist. Mach das mal mit 100 Elementen. UPS, die liegen auf einmal nicht mehr hintereinander im Speicher.

    minastaros schrieb:

    ..., ebenso wohl auch beim Sortieren.

    Beweis es doch gleich. Mach eine std::deque<int> und sortiere diese. Mal schauen was rauskommt. Ups, die Adressen blieben gleich.
    Wenn du zudem eine Funktion swap lieferst, geht die Sortierung deutlich schneller, da zum Beispiel bei std::string dann intern nur Zeiger und ein paar Grössenangaben ausgetauscht werden können. Wie schon früher geschrieben.
    Wenn man sogar eine ganz gute std::string Implementation vorfindet, dann wird genau ein Zeiger getauscht, da intern das Pimpl-Idiom verwendet wurde.

    minastaros schrieb:

    Sarkastische Erfahrung: Es ging um Sensor-Objekte mit zahlreichen Parametern, die dynamisch erzeugt und entfernt werden mussten. In einem Fall auch um verschiedene abgeleitete Typen, die zusammen verwaltet wurden. Das ging dann nur noch über Pointer auf Basisklasse.

    Ehm, ja und? Und wo lag das Problem? Das man bei Polymorphie Zeiger im Container speichern muss, da sonst ein Slicing passiert, ist natürlich klar. Aber das heisst nicht, dass man immer Zeiger verwenden muss, um die Objekte zu speichern.

    Zudem, da kommt mir noch ein Problem in den Sinn. Alle Standardalgorithmen kannst du nicht mehr sinnvoll einsetzen. Gerade zum Beispiel eine Sortierfunktion von Adressen. Du musst dann zwingend der std::sort Funktion einen Funktor mitliefern und ich dachte, dass du solche Funktoren eher meiden willst, deshalb setzt du ja FOREACH ein.

    Könnte es sogar möglich sein, dass du std::sort nicht mal kennst? Bzw. womöglich auch nur einmal davon gehört hast? Ist ja nicht so wichtig, ist nur eine C++ Standardfunktion. 🤡

    minastaros schrieb:

    Im Übrigen hatte ich geschrieben, dass man es mit Pointern machen kann, aber man muss es nicht. Kommt eben auf das Projekt an.

    Nein, das hast du eben nicht. Du hast gesagt, dass es nach deiner Erfahrung besser ist, Zeiger zu speichern, statt die Objekte. Du hast es nicht auf ein Projekt eingeschränkt oder sonst etwas. Du hast es extrem allgemeingültig hingesetzt und dazu noch falsche Argumentation geliefert.
    Und du hast es dann sogar auf den Code von Gustl angewandt und daher empfohlen, dass es für Gustl besser wäre, wenn er Zeiger speichern sollte. Dabei gibt es im Falle von Gustl absolut gar keinen Gewinn, wenn er Zeiger speichern würde. Es würde sich im Gegenteil eher nachteilig auswirken.

    minastaros schrieb:

    Verschätze dich nicht.

    Uuuuh, jetzt habe ich Angst 🙂
    (Ich weiss, so war es wahrscheinlich nicht gemeint, aber hört sich schon irgendwie wie eine Drohung an)

    minastaros schrieb:

    Hab davon gehört, ...

    Aha, also kennst du sie nicht 🙂

    minastaros schrieb:

    ... aber muss ich jede Bibliothek einsetzen, nur weil sie existiert? Klar kann man.

    Nein, sicher nicht. Das lustige ist nur:
    Das eine ist aus der Standardbibliothek des C++98 Standards.
    Das andere ist aus der Standardbibliothek des C++03 Standards.
    Und das letzte ist aus der Boost Bibliothek, welche als Erweiterung der Standardbibliothek angesehen werden kann. Es wird interessanterweise auch vieles davon in die nächste Standardbibliothek des nächsten Standards einfliessen.

    Das sind extrem wichtige Bibliotheken. Die sollte man schon kennen, wenn man vernünftig C++ programmieren will und vor allem auch, wenn man über sie entscheiden und argumentieren will.

    minastaros schrieb:

    Nur, bei Embedded-Geräten schaut die Welt etwas anders aus. Wozu also eine neue Bibliothek, wenn mir mein Makro dafür völlig reicht.

    Jetzt sind wir plötzlich im Embedded-Bereich? Vorhin hast du noch völlig allgemein geredet. Aber die C++ Standardbibliothek sollte doch auch im Embedded Bereich bekannt sein. Kann allerdings dazu nichts sagen, ich programmiere nicht in dem Bereich.

    minastaros schrieb:

    Leute, wenn es jemand mag, weil er nicht jedes Mal die komplette for-Schleife schreiben will, soll er es machen, wenn nicht, dann eben nicht.

    Jetzt ist es plötzlich eine Geschmacksfrage?
    Zudem ging es nie um eine normale for -Schleife, sondern um die Funktion std::for_each . Das ist ein wesentlicher Unterschied.
    Und nicht zuletzt ging deine Argumentation überhaupt nicht auf, wieso man std::for_each nicht verwenden sollte.
    Und auf meinen Hinweis, dass FOREACH auch Nachteile mit sich bringt, gehst du überhaupt gar nicht ein.

    Du windest dich raus und sagst einfach nur: "Es ist Geschmackssache!"
    So kann man jedes Problem lösen, wenn man will. Eine sinnvolle Diskussionsbasis ist das aber nicht. Und vor allem hilft es dir nicht, etwas aus dieser Diskussion zu lernen.

    minastaros schrieb:

    Globaler Iterator it: Genau diesen hatte Gustl in seinen Funktionen savetofile() und ausgabe() verwendet.

    Du zerpflügst deine eigene Argumentation. Es geht hier nicht um den Code von Gustl, sondern darum, dass du die Entfernung des globalen Iterators als Argument gebracht hast, dein FOREACH Makro zu verwenden. Dabei hat das eine nichts mit dem anderen zu tun.

    Ich habe zudem gleich noch etwas nachgeprüft. Ich konnte mich einfach an keinen globalen Iterator in Gustls Code erinnern. Hey, siehe da, es hat gar keinen!!! Der Iterator ist als Member der Klasse deklariert. Das ist nicht unbedingt üblich, habe ich aber auch schon gesehen.

    Tut mir leid für die 3 Ausrufzeichen, aber es zeigt einfach so schön, wie du argumentierst. Oder vielleicht auch, wie du Begriffe verwendest und gar nicht weisst, was du eigentlich sagst. Keine Ahnung, aber es hat so viele Unstimmigkeiten...

    minastaros schrieb:

    Ob die For(each)-Schleife zusammen mit dem, was sie tun soll, sein soll oder getrennt, ist doch wieder eine Frage der Aufgabe und des Geschmacks. In Gustls Programm waren es die beiden Funktionen console() und ausgabe() oder savetofile() und elementspeichern(), die man so eben zusammenfassen kann. KANN.

    Du relativierst auf einmal alle deine Aussagen. Plötzlich ist es eine Geschmacksfrage, plötzlich ist es Abhängig von der Aufgabe. Davon hast du aber vorhin nie etwas gesagt. Du hast dich ganz klar gegen die std::for_each Version entschieden, da du der Meinung warst, dass FOREACH wesentliche Vorteile bringt. Welche ich versucht habe zu widerlegen, auf was du aber schlicht und einfach gar nicht erst eingehst.

    minastaros schrieb:

    Exceptions: Ja, sind eine tolle Sache. Aber sie sollten auch nicht für jeden x-beliebigen Fehler eingesetzt werden. "Fehler" kann als Rückgabewert für eine Datei-Zugriffs-Funktion recht weit verstanden werden, sagen wir besser "Status" oder "Ergebnis". Ob nun if() oder try()/catch() das Problem besser löst, kommt doch immer auf den Anwendungsfall an.

    Wenn ich eine Datei laden will, dann gehe ich davon aus, dass der Pfad korrekt ist, die Datei existiert und die Daten geladen werden können. Ein Resultat gibt es nicht. Wenn ein Fehler entsteht, erwarte ich, dass eine Exception geworfen wird, denn ich werde den Fehler ganz bestimmt nicht an Ort und Stelle behandeln, wo die Funktion aufgerufen wurde. Sondern weiter vorne, wo ich überhaupt angefangen habe mit der Idee vom User, dass eine Datei geladen werden soll. Deshalb ist hier erst recht die Exception der richtige Weg.

    Zudem hast du argumentiert, dass man den Rückgabewert für Fehler frei lassen soll und das sehr, sehr allgemein. Als wäre es das Normalste auf der Welt, dass man über den Rückgabewert nur Fehler zurückgibt und ansonsten nichts anderes. Und das ist einfach nur Unsinn.

    minastaros schrieb:

    Google schreibt in seinen Open-Source-Programmierrichtlinen sogar vor: "We do not use C++ exceptions."

    Google hat extrem veraltete C++ Code-Richtlinien. Nur weil es ein riesiger Konzern ist, heisst es nicht, dass sie die korrekte Wahrheit verbreiten.
    Aber auch sonst, es ist völlig irrelevant sowas zu nennen. Ich kann auch eine x-beliebige Firma nennen, welche hinschreibt, dass sie C++ Exceptions verwendet. Und dann, bringst du wieder eine Firma, welche die nicht verwendet und wir machen so weiter, bis wir alle Firmen abgezählt haben und vergleichen dann, wer mehr Firmen auf seiner Seite hat? 🤡

    minastaros schrieb:

    Ich verwende sie, aber nur für bestimmte Arten von Fehlern.

    Sehr genau beschrieben. Du benutzt sie also, wenn es dir irgendwie passt und ansonsten nicht? Scheint mir nicht sehr durchgezogen zu sein.

    minastaros schrieb:

    Jedenfalls würde ich nicht eine komplette Liste als Rückabewert übergeben.

    Dagegen habe ich mich ja auch entschieden. Ich würde auch eine Referenz verwenden.
    Ich hatte nur noch zusätzlich argumentiert, dass es unter Umständen gar nicht so viel mehr Aufwand ist, die Liste zu kopieren. Ich wiederhole mich, aber der Kompiler und Linker können teilweise unglaubliche Optimierungen durchführen.

    Das Problem ist/war, dass du falsch argumentiert hast, wieso man eine Referenz einsetzen sollte.

    minastaros schrieb:

    Nur mal fiktiv: Zur Ladefunktion möchte jemand auch eine Funktion haben, die Datensätze aus einer Datei an eine bestehende Liste anhängt. Das geht dann nicht mehr als Rückgabewert. Wenn man mit Referenzen arbeitet, könnten laden() und anhängen() zumindest eine gleichartige Signatur haben.

    Das ist auch eine super Argumentation. Nur mal fiktiv: Ich schreibe ein anderes Programm, dann muss ich den Quellcode anders aufbauen.
    Und nur mal so, wenn man bei deinem Beispiel bleibt, dann würde ich eher nur eine Funktion hinschreiben, nämlich laden (bzw. load ) und eine Referenz auf den Container oder vielleicht noch besser einen Iterator übergeben und immer die Elemente anfügen. Dann kann der Anwender der Funktion frei entscheiden, was er nun genau will. Es braucht gar keine zusätzliche Funktion.
    Wieso ein Iterator? Dann kann er sogar frei über den Container entscheiden und über ein std::back_inserter oder ähnliches neue Elemente im Container einfügen.

    Der Beitrag wurde etwas länger. Hat mich auch über 2 Stunden gekostet, ihn zu verfassen. Jetzt wird vielleicht klar, wieso ich von Frustration sprach...

    Grüssli



  • Dravere schrieb:

    Die Sache ist aber, dass Gustl nun schon mehrfach gesagt hat, dass der Code genau der gleiche wäre. Zudem wäre der Fehler nicht beim operator << aufgetaucht, sondern beim operator >>. Also geht die Argumentation in meinen Augen überhaupt nicht auf.

    Doch geht sie... da ich "gedebuggt" habe und schritt für schritt mit meinen programm fortgeschritten bin, bis zu diesem f>>tmp; also operator überladung...
    dann kam der stack overload, aber const hat er in keinster weise angesprochen^^

    Bei euren unstimmigkeiten halte ich mich dezent zurück und lass mich von euren Weisheiten berieseln. 🙂

    MfG Gustl


  • Administrator

    @Gustl,
    1. Er behauptet, dass der Fehler eben nicht beim operator >> war, sondern beim operator << . Du sagst allerdings soeben wieder, dass dem nicht der Fall ist. Deswegen geht seine Argumentation nicht auf.
    2. Man könnte vielleicht noch unter seinen Ausführungen das folgende verstehen, wobei diese Argumentation ziemlich weit hergeholt wäre.
    Du hast uns diese hier gezeigt:

    std::istream& operator>>(std::istream& instream, Adresse const & adresse)
    

    Er behauptet, du hättest tatsächlich dies hier geschrieben gehabt:

    std::istream& operator>>(std::istream& instream, Adresse & const adresse)
    

    Dies hätte aber minimum zu einer Warnung geführt. Und es ist sehr unwahrscheinlich, dass dies der Grund für den Stackoverflow gewesen wäre.

    Daher die Frage an dich Gustl, was hattest du genau hingeschrieben?

    Grüssli



  • argh, ich werde heute abend daheim das programm nochmal durchspielen und euch dann ganz genau sagen wann mit welchem vode NUR der stack overflow Fehler aufgekommen ist.

    fakt ist, das der fehler beim const im operator überladen >> kommt, da hier ja die referenz verändert wird... der compiler den code aber anstandslos kompiliert hat.

    Noch einmal, und auch von meiner Seite aus ein letztes mal, ich habe hier den code kein einziges mal geändert und es immer so geschildert wie ich es wahrgenommen habe. Ich möchte hier jedoch nicht ausschließen das ich eine Warnmeldung übersehen habe.

    im code von der seite 3 steht ja

    std::istream& operator>>(std::istream& instream, Adresse & const adresse)
    

  • Administrator

    Gustl schrieb:

    im code von der seite 3 steht ja

    std::istream& operator>>(std::istream& instream, Adresse & const adresse)
    

    Was aber im Minimum zu einer Warnung führen würde, da es falsch ist. Und auf Seite 4 steht das hier:

    std::istream& operator>>(std::istream& instream, Adresse const & adresse)
    

    Was hast du nun hingeschrieben? 🙂

    Grüssli



  • Nachdem ich darauf hingewiesen wurde das ich const nicht richtig eingesetzt habe, habe ich das verdreht und dann hier nachgefragt wo der unterschied ist, auf seite 4 oder so... dann wurde mir erklärt das es keinen gibt und ich das schreiben könne wie ich will.

    den ersten code habe ich mit borland (2002) kompiliert und den zweiten mit visual studio (2005).

    Ob nun Warnungen vorhanden waren oder nicht, kann ich nicht unterstreichen, da werde ich dann heute Abend daheim nochmal gucken und euch dann genau sagen ob es warnungen gibt.

    Aber darauf rumzureiten bringt doch jetzt auch nichts oder? Fakt ist das er somit in beiden Fällen keinen Fehler brachte und das Programm anstandslos kompiliert hat, wie gesagt, ob Warnungen dabei waren will ich mich jetzt nicht festlegen...


Anmelden zum Antworten