WEBCGI-Projekt | Verbesserungsvorschläge
-
Vielen Dank euch nochmal... Die Ersetzen-Funktion klappt einwandfrei. Leider darf ich momentan keine String-Klasse benutzen, da wir noch keine OOP gemacht haben und da erst noch zu kommen.
Funktioniert aber trotzdem und ich bin den anderen ein wenig voraus

Jetzt kommt das nächste Problem... Ich soll einen Parser schreiben, der Hexcode in Sonderzeichen rückwandelt... Der vom Benutzer eingegebene Quelltext hat ja Sonderzeichen. Diese werden aber bei der Übertragung in Hexcode umgewandelt ... z.B. das Ausrufezeichen "!" wird zu "%21" usw...
Folgendes habe ich mal probiert (entschuldigt den unglaublich schlecht strukturierten Code, das mache ich immer nachdem alles funktioniert^^)
#include <iostream> #include <string> #include <iomanip> #include <stdio.h> #include <stdlib.h> char hexToAscii(const char* code); char* changeText (char* text); int main() { char* text = "Aber hallo%3f Das ist ja gut zu wissen%21"; std::cout << changeText(text) << "\n" << std::endl; } char * changeText (char * text) { int i = 0, j = 0; int searchResLen = 0; int textLen = strlen(text); char * searchRes; char * newText = new char [textLen]; searchRes = strstr(text, "%"); searchResLen = strlen(searchRes); for (; text[j] != NULL; i++, j++) { if (j == textLen-searchResLen) { newText[i] = hexToAscii(searchRes); j += 2; } else { newText[i] = text[j]; } } newText[i] = '\0'; return newText; } char hexToAscii(const char* code) { char hex[5], *stop; hex[0] = '0'; hex[1] = 'x'; hex[2] = code[1]; hex[3] = code[2]; hex[4] = 0; return strtol(hex, &stop, 16); }Ausgabe: Aber hallo? Das ist ja gut zu wissen%21
Die Funktion hexToAscii bekommt von der Funktion changeText das Suchergebnis von
strstr(text, "%")und gibt das hex-Array an die Funktionstrtolweiter, welche dann das Sonderzeichen rückliefert.Das Programm funktioniert nun aber nur bis zum ersten Sonderzeichen. Danach nicht mehr... ich weiß warum es nicht funktioniert (ich müsste ja mehrmals Suchergebnisse haben), aber ich habe keine Ahnung wie ich es machen soll? Kann mir vielleicht jemand einleuchten?
Danke schonmal
der Kev
-
1. Neues Problem -> Neuer Thread
Das ist auch für dich sinnvoll. Gibt viele Leute, welche ab einem gewissen Zeitpunkt in einem grössen Thread nicht mehr unbedingt reinschauen. Zudem ist es eine besser Ordnung, auch wenn jemand nach einem Problem sucht.
2. Keine String-Klasse benutzen, kein OOP? Ok, ich versteh schon. Ein Lehrer, welcher seinen Schülern C with Classes als C++ verkauft. In C++ kann man völlig unabhängig von C Programmieren. Um C++ zu beherrschen, muss man nicht C verstehen. Grundsätzlich sind es unabhängig Sprachen. Aber naja, wir sind uns in dem Forum ja daran gewöhnt, dass wir immer wieder von solchen Lehrern hören. Die sind leider Standard ... Fortbildung ist halt immer so eine Sache bei den Leuten.
3. Zu deinem Problem. Alles was du eigentlich machen müsstest ist, dass du changeText mehrmals aufrufst, bis alle Zeichen ersetzt sind. Du könntest auch sowas intern machen, also in changeText selber, in dem du immer wieder nach dem Prozentzeichen suchst, die Umwandlung machst usw., bis es kein Prozentzeichen mehr findet. Du solltest zudem noch dein Speicherleck schliessen. In der Funktion wird Speicher über new reserviert und nirgends per delete freigegeben.
4. Die Leute im Forum werden mich womöglich lynchen. Ich finde aber du hast schon Initiative gezeigt und ich hatte gerade Lust, dazu was zu schreiben oder zusammenzustellen. Es ist nicht gerade ausserordentlich toll, da ich nicht so der C Programmierer bin, aber ich denke es dürfte interessant für dich sein://#include <string> -> Du benutzt ja keine C++ Strings. #include <iostream> //#include <iomanip> -> brauchst du doch gar nicht. // std::endl ist in <iostream> drin //#include <stdio.h> -> falsch, in C++ ist es <cstdio> // du benötigst es aber gar nicht. //#include <stdlib.h> -> falsch, in C++ ist es <cstdlib> #include <cstdlib> #include <cstring> // -> Um mit C-Strings zu arbeiten. #include <cassert> // -> Im Debugmodus kann man damit // Sicherheitsprüfungen durchführen. // Kopf bleibt gleich. char hexToAscii(char const* code); // Den Text übergibt man als konstant, er soll in der Funktion // schliesslich nicht verändert werden, sondern wird in einen // neuen Puffer kopiert. // Um Speicherlecks zu verhindern, muss der Anwender der // Funktion einen genügend grossen Speicherblock übergeben. // Dieser Puffer wird an dest übergeben. void changeText(char* dest, char const* text); int main() { // Man sollte ein Zeiger auf ein String-Literal // als ein Zeiger auf konstanten Inhalt deklarieren. // Es ist nämlich auch der Fall. // Dass es anders geht, liegt nur an einer rückwärts- // kompatibilität zu C. char const* text = "Aber hallo%3f Das ist ja gut zu wissen%21"; // Wir erstellen uns nun ausserhalb Speicher, // damit wir ihn auch wieder sauber aufräumen können. // Nicht vergessen, zu dem new kommt ein delete. // Der neue Text wird sicher kürzer oder gleichlang sein // wie der alte Text. Also definieren wir einen // Puffer, mit gleicher Grösse. // strlen zählt die Null-Terminierung nicht mit, // deshalb zählen wir noch eins dazu. // Dies ist nötig, falls es keine zu ersetzenden // Hexadezimalzahlen hat. Es gäbe es einen Überlauf // mit undefiniertem Verhalten. char* buffer = new char[strlen(text) + 1]; // Text wird geändert und in buffer geschrieben. changeText(buffer, text); // Der Puffer wird ausgegeben. changeText hat eine // terminierende 0 reingeschrieben, wodurch wir // den Puffer als C-String behandeln können. std::cout << buffer << std::endl; delete[] buffer; // <- Das wichtige Delete, welches // vorhin fehlte. } // Zu den Funktionen :) void changeText(char* dest, char const* text) { // <cassert> wird hier z.B. verwendet. // Man hat ein Makro assert, welches etwas prüft // und wenn es den Wert false ergibt, bricht das Programm ab. // Diese Prüfung wird nur im Debugmodus durchgeführt. assert(0 != dest && 0 != text); // Es wird also geprüft, // ob beide Zeiger keine Nullzeiger sind. // Man könnte hier durchaus noch mehr Prüfungen machen, // bzw. auch im Verlauf der Funktion. // Wir müssen nun also kopieren und wenn wir // ein '%' finden, müssen wir entsprechend dieses // Zeichen ignorieren und die nächsten zwei Buchstaben // als Hexadezimalziffern interpretieren. Richtig? // Dies machen wir so lange, bis wir am Ende von text // angelangt sind. Also das null terminierende Zeichen // erreichen. // Ich gebe dies nun explizit in der Bedingung an, // damit man es gut sieht. Gründsätzlich würde aber auch // ein *text reichen. '\0' entspricht dem Wert 0, was // als false interpretiert würde. Alles andere als 0 // ist in C++ true. while('\0' != *text) { // Hexadezimalziffer? if('%' == *text) { // Ja, dann müssen wir übersetzen. // Zuerst gehen wir eine Stelle weiter. ++text; // Hier ist nun die erste Hexadezimalziffer. // Wir übergeben es an deine Funktion und // speichern das Resultat in die aktuelle // Stelle von dest. *dest = hexToAscii(text); // text muss nun um eine weitere Stelle // nach vorne geschoben werden. // Die letzte Hexadezimalziffer, wird am // Ende der Schleife noch übersprungen. ++text; } else { // Nein, also kopieren wir nur. *dest = *text; } // Die beiden Zeiger um eine Stelle weiterschieben. ++text; ++dest; } // Wir fügen noch eine terminierende 0 beim Puffer ein. *dest = '\0'; // Fertig. // Gemerkt? Wir haben keine zusätzlichen Variablen benötigt. // Nicht mal Funktionen aus der C-Bibliothek. Oder nur indirekt. } char hexToAscii(const char* code) { // Nur aus Sicherheit ein assert. assert(0 != code); // Nun, diese Funktion ändere ich ein wenig. // Am liebsten würde ich die C++ Konvertierung verwenden, // aber dann wird sicher wieder gemotzt, dass es OOP ist. // Dann verwende ich eben die C Möglichkeiten. // Dazu ist dein Code eigentlich schon ganz gut. // Allerdings ist das 0x zu begin optional, da über die // Basis 16 schon angegeben wird, dass Hexadezimalziffern // gemeint sind. Daher können wir das weglassen. // Auch ist der zweite Parameter von strtol optional. // Ein NULL reicht dort völlig. Steht übrigens alles // in der Referenz, welche du auch schon benutzt hast ;) // Wir benutzen zudem die sofortige Initialisierung. char hex[3] = { code[0], code[1], 0 }; // Den Rückgabewert konvertieren wir explizit mit // dem C++ static_cast. Dies soll nur die Sache // verdeutlichen, also was hier eigentlich passiert. return static_cast<char>(strtol(hex, NULL, 16)); } // Übrigens wird in C und teilweise C++ NULL nur für einen // Nullzeiger verwendet. Man sollte also NULL nicht für 0 // oder '\0' verwenden. In C++ gibt es zudem auch noch die // Möglichkeit ein nullptr Objekt zu bauen. Näher steht // in der FAQ. Aber da ihr ja nicht richtiges C++ macht, // dürft ihr das sicher auch nicht verwenden. Kommt übrigens // in den nächsten Standard sogar als Schlüsselwort rein.5. Ich hoffe ich habe keine groben Fehler gemacht.
6. Zwei Links, zu Einträgen in der FAQ, welche ich erwähnt habe:
FAQ - C++ - NULL oder 0 (eher veraltet)
FAQ - C++ - nullptr (neu)
7. Erwarte nicht, dass sowas zum Standard bei der Hilfestellung wird. Sonst dürftest du von diesem Forum enttäuscht werden. Ich glaube, ich habe gerade ein wenig übertrieben
Grüssli
-
Daumen hoch für den Beitrag! Hoffentlich macht er was draus, erklären kannst du jedenfalls sehr gut

-
Noch eine kurze Bemerkung. Habe meinen Code noch korrigiert. Mir ist aufgefallen, dass ich vergessen habe, dass
strlendie 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' != *textund 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;einfachtext++;benutzen würde?3. Ist die
+ 1in 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' != *textund 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;einfachtext++;benutzen würde?++text ist increment and fetch
text++ ist fetch and incrementsprich:
++text liefert text+1
text++ liefert textnach 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
+ 1in 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.
-
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...
-
@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?

-
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 :))