WEBCGI-Projekt | Verbesserungsvorschläge


  • Administrator

    Noch eine kurze Bemerkung. Habe meinen Code noch korrigiert. Mir ist aufgefallen, dass ich vergessen habe, dass strlen die Länge des C-Strings ohne die Null-Terminierung zurückgibt. Wenn also nichts ersetzt hätte werden müssen, wäre es zu einem Überlauf mit undefiniertem Verhalten gekommen, da die Null-Terminierung des Puffers in Speicher geschrieben worden wäre, welcher nicht reserviert wurde. Deshalb hat es nun ein +1 drin und deshalb wurde mein Post editiert.

    @Badestrand,
    Ehm, danke.
    *froh ist dem Galgen entkommen zu sein* 😃

    Grüssli



  • Du bist super!

    Nun hast du den Code zwar für mich geschrieben, aber ich denke mit deinen sehr ausführlichen Kommentaren kann ich mich hervorragend durch das Programm denken und jeden einzelnen Schritt nachverfolgen, was ich jetzt auch mal tun werde. Danach geb ich nochmal Feedback ab 🙂 Vorab auf jeden Fall erstmal vielen Dank für deine wirklich großartige Hilfsbereitschaft und mit meinem Lehrer werde ich mal einige Worte wechseln. So oder so möchte ich mich deiner Tipps annehmen und mich selbst schlau machen über richtiges C++, und auch werde ich meinen Lehrer bitten dies zu akzeptieren. 🙂

    Mit meinem Feedback kannst du entweder morgen schon oder spätestens Samstag rechnen! Noch 3x Danke für deine Bemühungen!

    Mit freundlichen Grüßen,
    Kevin



  • So, wie versprochen hier mein Feedback.

    Ich habe mir das Programm jetzt lange und oft angeschaut und mir quasi ein "Struktogramm" dazu gemacht. Durch Hilfe dessen und deine Erklärungen bin ich jetzt mit dem Quellcode vertraut, bzw. mit dem was er macht. Mir gefällt deine Strukturierung sehr gut und dein Code ist auf Grund der sinnvollen Kommentare und Einrückungen leicht nachvollziehbar. Allerdings habe ich noch 2-3 Fragen an dich.

    1. Warum benutzt du bei Kontrollstrukturen/Abfragen diese Variante: '\0' != *text und nicht *text != '\0' ? Hat das einen bestimmten Grund, bringt es einen Vorteil oder ändert es irgendetwas an der Geschwindigkeit?

    2. Wo läge der Unterschied, wenn ich statt ++text; einfach text++; benutzen würde?

    3. Ist die + 1 in dieser Deklaration: char* buffer = new char[strlen(text) + 1]; für den Nullterminator gedacht?

    4. Angenommen, ich lasse void changeText(char* dest, char const* text) rekursiv laufen. Welche Auswirkungen hätte das auf die Geschwindigkeit?

    Danke und mit freundl. Grüßen
    Kevin



  • root2k schrieb:

    1. Warum benutzt du bei Kontrollstrukturen/Abfragen diese Variante: '\0' != *text und nicht *text != '\0' ? Hat das einen bestimmten Grund, bringt es einen Vorteil oder ändert es irgendetwas an der Geschwindigkeit?

    Das machen Leute die ihren Compilern nicht trauen.
    Weil wenn du das ! vergisst, dann kompiliert
    '\0'=*text
    nicht und bei
    *text='\0'
    wird nur eine warnung ausgegeben.

    2. Wo läge der Unterschied, wenn ich statt ++text; einfach text++; benutzen würde?

    ++text ist increment and fetch
    text++ ist fetch and increment

    sprich:
    ++text liefert text+1
    text++ liefert text

    nach dem statement wurde text in beiden faellen um 1 erhoeht.
    ++text ist natuerlich besser als text++ da nicht erst eine kopie erstellt werden muss.

    3. Ist die + 1 in dieser Deklaration: char* buffer = new char[strlen(text) + 1]; für den Nullterminator gedacht?

    ja.

    4. Angenommen, ich lasse void changeText(char* dest, char const* text) rekursiv laufen. Welche Auswirkungen hätte das auf die Geschwindigkeit?

    kommt auf die konkrete implementierung von changeText an (in diesem thread gibt es mehrere). generell sehe ich aber keinen vorteil in einer rekursion hier.


  • Administrator

    Ein paar Ergänzungen zu Shade Of Mines Aussagen:
    1. Bei *text = '\0' wird keine Warnung ausgegeben, jedenfalls bei meinem Kompiler nicht. Es gibt schliesslich auch keinen Grund, dies könnte durchaus gewollt sein.
    Also geht es nicht darum, dass ich meinem Kompiler nicht vertraue, sondern ich vertraue mir nicht 😉

    2. Man kann das vielleicht auch noch mit Pseudo Code ein wenig verdeutlichen:

    operator ++ Prefix (also davor)
    {
      Inkrementiere Objekt.
      Gib das Objekt zurück.
    }
    
    operator ++ Postfix (also danach)
    {
      Erzeuge Kopie des Objekts.
      Inkrementiere Objekt.
      Gib die Kopie zurück.
    }
    

    Es macht zwar bei einem Zeiger nicht viel aus und in diesem speziellen Fall würde der Kompiler wohl sogar erkennen, dass die Kopie nicht benötigt wird, wodurch er sie weglässt. Es ist daher mehr eine Angewohnheit und erst bei einem komplexeren, bzw. grösseren, Datentyp kann ein Unterschied auftauchen. Es ist daher auch ein wenig eine Frage nach der Funktionalität. Brauchst du diese Kopie des alten Objektes? Nein? Dann nimm den Prefix Operator.

    3. Gibt es nichts hinzuzufügen. Habe ich wie gesagt, erst später gemerkt, dass dieses +1 noch hin muss.

    4. Eine Sache zur Rekursion:
    Eine Rekursion birgt immer die Gefahr eines Stackoverflows. Jeglicher Funktionsaufruf verbraucht Stackspeicher und je nach dem in der Funktion selbst braucht es nochmals. Bei jeder Rekursion wird somit immer wie mehr Stackspeicher benötig. Der Stackspeicher ist aber relativ klein, wodurch bei unbedachtem Vorgehen ein Stackoverflow entsteht, also es hat keinen Stackspeicher mehr -> Programmabsturz.
    Deshalb bin ich persönlich nicht wirklich ein Fan von der Rekursion und setze sie nur mit äusserster Vorsicht ein. Schliesslich ist jede Rekursion auch durch eine Iteration realisierbar.

    Grüssli



  • Hallo Dravere,

    gäbe es denn deinerseits eine beispielhafte Situation für den sinngemäßen Einsatz einer Rekursion? Das war einer der längsten Themen, die wir im Fach Programmieren durch genommen haben und scheint mir von hoher Priorität. Oder der Lehrer hat wieder mal nur Quatsch verzällt. 😛

    Mit freundl. Grüßen
    Kevin



  • Es gibt schon Situationen, in denen eine Rekursion hilfreich sein kann. Zum Beispiel, wenn man mit rekursiven Datenstrukturen wie Bäumen arbeitet.

    *
          / \
        *     *                    <- ein Binärbaum :)
       / \   / \
      *   * *   *
    

    So sollen zum Beispiel alle Knoten (*) ausgegeben werden. Man startet bei der Wurzel, jeder Knoten ruft rekursiv seine Unterknoten auf.

    Man realisiere das einmal schön mit einer Iteration...


  • Administrator

    @root2k,
    Also eine Rekursion ist schon wichtig für ein grundlegendes Verständnis. Zum Beispiel in der Templatemeta-Programmierung kommt man ohne die Rekursion nirgends hin.
    Gewisse Sachen kann man auch sehr viel einfacher oder schöner mit einer Rekursion lösen oder sagen wir Übersichtlicher. Wobei sowas natürlich teilweise auch subjektiv sein kann.
    Es ist womöglich sogar möglich, dass man durch eine Rekursion eine Berechnung beschleunigen kann, da man nur mit dem Stack arbeiten kann und nicht allenfalls noch zusätzlichen Heapspeicher benötigt. Der Heapspeicher ist unter umständen langsamer als der Stackspeicher. Kommt ganz drauf an, wie man es organisiert. Allerdings kann ich mir nicht vorstellen, dass es dann einen wesentlichen Unterschied machen wird.

    Was ich dafür inzwischen schon tausendfach gesehen habe, ist die völlig falsche Anwendung einer Rekursion, was am Ende zu einem Stackoverflow führte. Typisches Beispiel, über welches ich mich grausam geärgert habe, ist die TinyXML C++ Wrapper Bibliothek. TiXmlCpp oder wie das Ding heisst. Da finden die Aufräumarbeiten rekursiv statt. Wenn man zum Beispiel 3000 Elemente durchiteriert, gibt es am Ende vom Scope einen Stackoverflow. Gut, die ganze Speicherverwaltung ist dort sowieso so la la ...

    @Nexus,
    Nicht schlechtes Beispiel.
    Und vielleicht zur Begründung, wieso dieses Beispiel gut ist:
    Nehmen wir an, wir haben die unglaubliche Anzahl von 18'446'744'073'709'551'616 Elementen im Baum. Die Rekursion würde trotzdem nie mehr als 64 Schritte machen (-> 2 hoch 64 = 18'446'744'073'709'551'616). Man läuft ziemlich sicher keine Gefahr einen Stackoverflow auszulösen.
    Eine Iteration ist allerdings auch absolut kein Problem und man kann es auch schön machen. Nur so zur Anmerkung 🙂

    Grüssli



  • Dravere schrieb:

    Eine Iteration ist allerdings auch absolut kein Problem und man kann es auch schön machen. Nur so zur Anmerkung 🙂

    Kommt auf drauf an, was man unter "schön" versteht. Da es mich gerade interessiert, könntest du vielleicht ein solches Beispiel bringen? 😉


  • Administrator

    Ein Beispiel, schwer ohne Code des Baums. Aber sagen wir deine Nodes sind so aufgebaut:

    struct Node
    {
      std::string value;
      Node* left;
      Node* right;
    };
    

    Dann würde die Iteration so aussehen:

    void output(Node const* current)
    {
      std::stack<Node const*> nodeStack;
      nodeStack.push(current);
    
      while(!nodeStack.empty())
      {
        current = nodeStack.top();
        nodeStack.pop();
    
        if(current)
        {
          output_value(current->value);
    
          nodeStack.push(current->right);
          nodeStack.push(current->left);
        }
      }
    }
    

    Ist jetzt nur auf die Schnelle gemacht. Aber sieht das irgendwie hässlich aus? Ich finde nicht. Der Stack wird halt einfach über den Heap geführt, bzw. ich nutze meinen eigenen Stack.

    Grüssli



  • Ich finde einfach, dass Rekursion teilweise echt schön etwas darstellen kann. Man muss praktisch nicht mehr überlegen, wenn man den Code sieht, weil er so "sprechend" ist.

    Allerdings habe ich die Erfahrung gemacht, dass die Rekursion im Vergleich zur Iteration wessentlich langsamer ist. (nicht unbedingt wegen der Tiefe, sondern wegen den vielen calls, die halt schon sehr umständlich sind, wenn man sie mit einem lediglichen inkrementieren vergleicht :))


Anmelden zum Antworten