char[], String und "End"-Zeichen



  • Hallo, ich noch mal, jetzt hab ich das mit std::string gelöst und hab dazu nur zwei Fragen:

    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    
    #define LENN 2
    
    class DataClazz {
    public:
    	DataClazz(int len) {
    		s = new char[len];
    		s[len] = '\0';
    	}
    	~DataClazz() {
    		// delete s; mag er nicht
    		// s = 0;
    	}
    	void set(int idx, char chr) {
    		// s.insert(idx, 1, chr); verhält sich komisch
    		s[idx] = chr;
    		std::cout << s << std::endl;
    	}
    	int get() {
    		return s.length() - 1;
    	}
    private:
    	std::string s;
    };
    
    void combi(char * const ca, const int len, const int idx) {
    	if (len == idx) {
    		return;
    	}
    	for (char c = 'A'; c <= 'Z'; c++) {
    		ca[idx] = c;
    		ca[idx + 1] = '\0';
    		// if (idx <= 1) nur für Debuggen
    		std::cout << ca << std::endl;
    		combi(ca, len, idx + 1);
    	}
    }
    
    void combiWithDataClazz(DataClazz * const dc, const int idx) {
    	if (dc->get() == idx) {
    		return;
    	}
    	for (char c = 'A'; c <= 'Z'; c++) {
    		dc->set(idx, c);
    		combiWithDataClazz(dc, idx + 1);
    	}
    }
    
    int main(int count, char** args) {
    	int x;
    	x = 5;
    	std::cout << x << std::endl;
    	std::cout << "Hello World..." << std::endl;
    
    	for (int i = 0; i < count; i++) {
    		std::cout << args[i] << "   ";
    	}
    	std::cout << std::endl;
    
    	const int len = LENN;
    	char * const ca = new char[len + 1];
    	ca[len] = '\0';
    	// combi( ca, len, 0); siehe combiWithDataClazz
    
    	DataClazz * const dc = new DataClazz(len);
    	combiWithDataClazz(dc, 0);
    	delete dc;
    
    	return 0;
    }
    

    Relevant sind:
    Zeile 7 bis 27,
    Zeile 42 bis 50,
    Zeile 68 bis 70.

    Wieso kann ich kein delete auf std::string s aufrufen? Gibt es einen Unterschied zwischen char charArr[5]; und char * charArr = new char[5]; (Stack, Heap, wieder deallozieren usw.)?

    Ich hab btw. gelesen, int usw. sollte man nicht die Adresse übergeben, das Kopieren ist schneller, außer bei sehr komplexen Typen , dann sollte man die Adresse übergeben (formaler Parameter).



  • Ähm ja. Deine Verwendung von std::string ist mal "kreativ" um es vorsichtig zu sagen. Du machst es dir damit ja noch viel komplizierter als mit Speicher den man selbst reserviert. Bei std::string muss man sich um diese ganze Geschichte nicht mehr kümmern. Ebenso sind wir hier nicht bei Java und müssen nicht alle Objekte mit new anlegen. Dein DataClazz dc Objekt z.B. kannst du auch einfach direkt auf dem Stack anlegen. Da dir ein paar Tipps wohl nicht helfen werden gibts hier mal eine mögliche Lösung:

    void combiWithString(std::string& str, const int len) {
      if(str.size() == len) {
        return;
      }
      str.push_back(' ');  // String um ein Element vergrößern (hier Anfangs ein Leerzeichen)
      for(char c = 'A'; c <= 'Z'; c++) {
        str.back() = c;  // Letztes Zeichen verändert
        std::cout << str << std::endl;
        combiWithString(str, len);
      }
      str.pop_back();  // String wieder um ein Element verkleinern
    }
    
    int main()
    {
      std::string str;
      combiWithString(str, 2);
    }
    

    EinGastredner schrieb:

    Gibt es einen Unterschied zwischen char charArr[5]; und char * charArr = new char[5]; (Stack, Heap, wieder deallozieren usw.)?

    Ja char charArr[5]; ist ein Array und lebt auf dem Stack. Außerdem muss die Anzahl der Elemente eine Compilezeit Konstant sein. Der Speicher wird automatisch wieder freigegeben, wenn das Array den Gültigkeitsbereich verlässt. Dagegen ist char * charArr = new char[5]; ein Pointer auf ein char* der auf von dir reserviertem Speicher (auf dem Heap) zeigt. Hier muss man den Speicher am Ende wieder mit delete[] freigeben.



  • Jetzt bin ich an drei Stellen verwirrt 😮

    Danke für deine Erklärung erst mal.

    std::string& str
    

    Wieso an dieser Stelle kein Pointer mit Asterix/Asterisk? & bedeutet doch Referenzierungsoperator (Adresse)?

    str.push_back(' ');
    

    Verlängert den String jew. um 1, könnte langsam sein ggü. char zuweisen.

    str.pop_back();
    

    Verkürzt den String jew. um 1, könnte langsam sein ggü. '\0' zuweisen/setzen.

    Dann ist mir aufgefallen:

    std::string s = new char[2]; // s hat nun die Länge 3 und liegt auf dem Heap? (Er besteht aber aus Zufallszeichen.)
    

    Schönen Abend noch, bis dann



  • EinGastredner schrieb:

    std::string& str
    

    Wieso an dieser Stelle kein Pointer mit Asterix/Asterisk? & bedeutet doch Referenzierungsoperator (Adresse)?

    Hier bedeutet das & nicht den Address-Of-Operator, sondern markiert eine Referenz (steht in jedem C++ Buch).

    EinGastredner schrieb:

    str.push_back(' ');
    

    Verlängert den String jew. um 1, könnte langsam sein ggü. char zuweisen.

    str.pop_back();
    

    Verkürzt den String jew. um 1, könnte langsam sein ggü. '\0' zuweisen/setzen.

    Kann vieles sein. Kannst ja nachmessen was schneller ist. Da ich aber nicht für jeden Durchlauf die Größe ändere oder das '\0' neu schreibe ist es bestimmt recht fix.

    EinGastredner schrieb:

    std::string s = new char[2]; // s hat nun die Länge 3 und liegt auf dem Heap? (Er besteht aber aus Zufallszeichen.)
    

    Warum denn schon wieder new ? Bei einem std::string musst du kein new benutzen! Das der Code überhaupt compiliert liegt daran, dass du ein std::string mit einem const char* initialisieren kannst. Außerdem hast du in die falsche Richtung gerechnet. Wenn du Platz für 2 char reservierst kannst du nur ein ein Zeichen langen String speichern. Einen String in dem du 3 Zeichen (+Null Terminator, den der String automatisch anfügt) speichern kannst kriegst du mit

    std::string str(3, ' ');  // String der 3 Leerzeichen enthält
    

    Oder statt den Leerzeichen kannst du auch ein beliebiges anderes Zeichen nehmen. Ein solches Initialisieren macht aber nur dann Sinn wenn du danach irgendwie 3 Zeichen selbst reinschreiben willst. Ansonsten kannst du Strings auch einfach so etwas zuweisen:

    std::string s0 = "Bla";
    std::string s1 = s0 + " Blupp";
    std::cout << s1 << std::endl;
    


  • Hallo sebi,

    kannst du noch mal drüber schauen?:

    #include <iostream>
    
    #define LENN 2
    
    class DataClazz {
    public:
    	DataClazz(int len) {
    		std::cout << s << std::endl;
    		s = std::string(len, '\0');
    	}
    	virtual ~DataClazz() {
    	}
    	void set(int idx, char chr) {
    		s[idx] = chr;
    		s[idx + 1] = '\0';
    		std::cout << s << std::endl;
    	}
    	int get() {
    		return s.length();
    	}
    private:
    	std::string s;
    };
    
    void combi(char * const ca, const int len, const int idx) {
    	if (len == idx) {
    		return;
    	}
    	for (char c = 'A'; c <= 'Z'; c++) {
    		ca[idx] = c;
    		ca[idx + 1] = '\0';
    		std::cout << ca << std::endl;
    		combi(ca, len, idx + 1);
    	}
    }
    
    void combiWithDataClazz(DataClazz * const dc, const int idx) {
    	if (dc->get() == idx) {
    		return;
    	}
    	for (char c = 'A'; c <= 'Z'; c++) {
    		dc->set(idx, c);
    		combiWithDataClazz(dc, idx + 1);
    	}
    }
    
    int main(int count, char** args) {
    	int x;
    	x = 5;
    	std::cout << x << std::endl;
    	std::cout << "Hello World..." << std::endl;
    
    	for (int i = 0; i < count; i++) {
    		std::cout << args[i] << "   ";
    	}
    	std::cout << std::endl;
    
    	const int len = LENN;
    	char * const ca = new char[len + 1];
    	ca[len] = '\0';
    	// combi( ca, len, 0);
    	delete ca;
    
    	DataClazz dc = DataClazz(len);
    	combiWithDataClazz(&dc, 0);
    	dc = 0;
    
    	return 0;
    }
    

    Es geht jetzt um Folgendes,
    new vermeiden, weil dann brauche ich delete & Heap,
    type * const name = kann ich aber nur mit new "anlegen",
    DataClazz dc = darf ich nicht deleten,
    char * const ca = darf ich nicht 0 setzen,

    ich hab jetzt gelesen, mal soll mit jedem Pointer delete aufrufen und dann 0 setzen. Stimmt das, ist meine Anwendung so richtig?



  • Der Code ist jetzt schonmal deutlich vernünftiger. Eigentlich habe ich nur eine größere Anmerkung. Die std::string Klasse speichert sich die Länge des Strings selbst. Da eben die Länge sparat gespeichert ist bräuchte man eigentlich keinen Null Terminator am Ende und kann sogar mitten im String einen solchen haben:

    std::string x = "Some_long_string";
    x[6] = '\0';
    std::cout << x << std::endl;  // Gibt "Some_lng_string" aus
    

    Tatsächlich wird automatisch am Ende doch immer ein '\0' angehängt, falls man sich per .c_str() den Zeiger auf den Anfang des Strings holt, um damit Funktionen zu beliefern die noch mit const char* als String arbeiten. Jedenfalls sollte man es vermeiden selbst am Ende ein '\0' anzuhängen (und erst recht bei Indizes größer gleich der Länge) weil sonst die .length() Funktion auch falsche Werte liefert (in deinem Beispiel etwa immer 2). Darum habe ich in meinem Beispiel auch push_back und pop_back benutzt. Die verändern tatsächlich die Länge des Strings.

    Sonst noch ein paar kleinere Anmerkungen:
    -Warum packst du den std::string in deine DataClazz statt ihn direkt zu benutzen?
    -Warum hat deine Klasse einen virtual Destructor?
    -Statt s = std::string(len, '\0'); kann man auch s.resize(len); schreiben. Zumindest wenn der String vorher leer war oder man danach eh nochmal füllt.

    EinGastredner schrieb:

    new vermeiden, weil dann brauche ich delete & Heap,

    Der Heap ist ja gar nicht mal schlecht. Der std::string nutzt intern auch den Heap. Aber einfach selbst sich darum zu kümmern ist aufwändig und fehleranfällig. Zuerst also mal überlegen ob man die Variable nicht auf dem Stack haben kann.

    EinGastredner schrieb:

    type * const name = kann ich aber nur mit new "anlegen",

    Man kann es auch auf etwas auf dem Stack zeigen lassen, das ist aber meist nicht das was möchte.

    EinGastredner schrieb:

    DataClazz dc = darf ich nicht deleten,

    Ja.

    EinGastredner schrieb:

    char * const ca = darf ich nicht 0 setzen,
    ich hab jetzt gelesen, mal soll mit jedem Pointer delete aufrufen und dann 0 setzen. Stimmt das, ist meine Anwendung so richtig?

    Man darf Pointer auf 0 setzen und das ist gerade nach einem delete auch recht praktisch. Ein delete gibt nämlich nur den Speicher wohin der Pointer zeigt wieder frei. Der Pointer zeigt dann immer noch auf den gleichen Speicher, der aber jetzt gar nicht mehr uns gehört. Wenn man den Pointer manuell auf 0 setzt verhindert man versehentliche Zugriffe dahin (weil das Programm dann abstürtzt, statt möglicherweise plausible Werte zu lesen) und auch das man den Speicher 2mal mit delete freigibt.



  • Hallo sebi, ich melde mich spääät, es ist nicht mehr lang bis Weihnachten, aber ich hab deinen Ratschlag angenommen, mein Proggi weiter verbessert und bin zu neuen Problemen gekommen:

    class DataClazz {
    public:
    	DataClazz(int len) :
    			str(std::string()), length(len), index(0) {
    	}
    	virtual ~DataClazz() {
    	}
    	void pushChr(char chr) {
    		str.push_back(chr);
    		std::cout << str << std::endl;
    	}
    	bool next() {
    		return index < length;
    	}
    	void incre() {
    		index++;
    	}
    	void decre() {
    		index--;
    		str = str.substr(0, str.length() - 1); // nicht c11 kennt pop_back nicht
    	}
    private:
    	std::string str;
    	const int length;
    	int index;
    };
    
    void combiWithDataClazz(DataClazz * const dc) {
    	if (!dc->next()) {
    		return;
    	}
    	for (char c = 'A'; c <= 'Z'; c++) {
    		dc->pushChr(c);
    		dc->incre();
    		combiWithDataClazz(dc);
    		dc->decre();
    	}
    }
    

    Funktionsaufruf:

    const int len = LENN; // Präprozessorkonstante
    	DataClazz dc(len);
    	combiWithDataClazz(&dc);
    	dc = 0;
    

    Die member initializer list kann nicht damit umgehen, wenn length konstant ist ... Wieso ist das so? Was mache ich jetzt?

    Grüße, besinnliche Feiertage,

    ---

    nicht vergessen, bin noch am Lernen



  • Und was ist deine Frage? Bis auf das dc = 0; compiliert nämlich alles. Ich habe es zwar schon gefragt, aber ich frage es nochmal: Warum möchtest du den std::string unbedingt in deine eigene Klasse verpacken? Die index Variable kannst du dir beispielsweise komplett sparen, da das ja identisch mit der Länge des Strings ist und von der std::string Klasse bereits gespeichert wird. Ansonsten noch ein Hinweis zur Optimierung: Statt

    str = str.substr(0, str.length() - 1);
    

    kann man besser

    std.erase(str.length() - 1);
    

    schreiben, da dann keine Kopie angelegt wird (ich nehme mal an, dass der Optimizer nicht so gut ist um die Kopie in der ersten Variante zu eliminieren).



  • Folgendes sagt er mir (Version: Mars.1 Release (4.5.1)):

    21:34:35 **** Incremental Build of configuration Debug for project CPPRaetsel ****
    Info: Internal Builder is used for build
    g++ -O0 -g3 -Wall -c -fmessage-length=0 -o "src\\CPPRaetsel.o" "..\\src\\CPPRaetsel.cpp" 
    ..\src\CPPRaetsel.cpp: In member function 'DataClazz& DataClazz::operator=(const DataClazz&)':
    ..\src\CPPRaetsel.cpp:13:7: error: non-static const member 'const int DataClazz::length', can't use default assignment operator
     class DataClazz {
           ^
    ..\src\CPPRaetsel.cpp: In function 'int main(int, char**)':
    ..\src\CPPRaetsel.cpp:83:5: note: synthesized method 'DataClazz& DataClazz::operator=(const DataClazz&)' first required here 
      dc = 0;
         ^
    
    21:34:37 Build Finished (took 1s.830ms)
    

    Edit: Sry, mit default assignment operator meint er die Zuweisung, nicht den initializer (list). Instrumentenflug ...

    Wieso darf ich nicht 0 "zuweisen"?



  • Dadurch, dass deine Klasse einen Konstruktor mit nur einem int als Parameter hat, kann der der Compiler automatische Konvertierungen von ints zu deiner Klasse durchführen (falls das nicht passieren soll siehe explicit). Wenn du also soetwas wie

    dc = 0;
    

    schreibst, erstellt der Compiler ein Objekt deiner Klasse (mit length = 0) und versucht es deinem vorhandenen Objekt zuzuweisen. Da deine Klasse aber const Membervariablen hat, ist der Standard Zuweisungsoperator deaktiviert. Es gibt jetzt verschiedene Möglichkeiten den Fehler zu beheben aber dazu müsste man erstmal wissen was du durch die Zuweisung überhaupt erreichen möchtest. Kann es sein, dass du das hier mit Pointern verwechselt hast? Normale Variablen auf dem Stack muss man am Ende nicht auf 0 setzen oder löschen oder irgendwas.



  • EinGastredner schrieb:

    Wieso darf ich nicht 0 "zuweisen"?

    Weil der Member length konstant ist.
    Was hier passiert:

    class DataClazz
        {
            ...
            // Compiler-generierter Assignment-Operator:
            DataClazz& operator=(const DataClazz& rhs)
            {
                // Kopiere alle Member von rhs in diese Instanz.
                ...
                // Und natürlich auch den length-member...
                this->length = rhs.length;
                // ... der ist aber const(!) und erlaubt daher obige Zuweisung nicht. 
            }
    
            ...
            const int length;
        }
    
        ...
        DataClazz dc(len);
        ...
        // dc = 0; ... der Compiler macht daraus unter der Haube:
        // dc = DataClazz(0);
        // bzw. den Funktionsaufruf:
        dc.operator=(DataClazz(0)); 
        // ... womit obiger compiler-generierter Assignment-Operator aufgerufen wird.
    

    Gruss,
    Finnegan

    P.S.: Sorry, sebi707 ... das war wohl zeitgleich mit deiner Antwort 😉



  • Das ist ja schlimm, weil Parameter Anzahl und Typ(en) gleich sind (genau 1-mal const int len/length),

    wie kann ich diesen "nicht-pointer" trotzdem einfach 0 setzen?

    Wieso rufe ich überhaupt delete und 0 auf? Weil ich gelesen hatte, für alle "nicht-automatischen" "Variablen" auf dem Heap sollte man das beides machen.

    Wie kann ich selbiges/gleiches mit Variablen auf dem Stack machen? Ein den Scope verkleinernden Block einfügen?

    Außerdem, insgesamt müsste es doch "übersichtlicher" und schneller sein, weil jetzt nicht mehr 3 Variablen kopiert werden müssen, sondern nur noch der Pointer/Referenz eines Objekts einer Klasse, auf dem dann ein paar Funktionen aufgerufen werden.

    Habt ihr einen Link, der noch mal auf Unterschied Pointer und Referenz eingeht?

    Danke und guten Abend 😉


  • Mod

    EinGastredner schrieb:

    Wieso rufe ich überhaupt delete und 0 auf? Weil ich gelesen hatte, für alle "nicht-automatischen" "Variablen" auf dem Heap sollte man das beides machen.

    Wo hast du denn den Quatsch her? Entweder falsch verstanden oder schlechte Quelle (oder beides).



  • EinGastredner schrieb:

    Wie kann ich selbiges/gleiches mit Variablen auf dem Stack machen? Ein den Scope verkleinernden Block einfügen?

    Ja. Wenn du unbedingt möchtest, dass eine automatische Variable schon frühzeitig zerstört wird dann kann man diese in einen kleineren {} Block packen. Allerdings macht es meistens nichts wenn die Variable noch bis zum Ender der aktuellen Funktion existieren.


Anmelden zum Antworten