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
aufstd::string s
aufrufen? Gibt es einen Unterschied zwischenchar charArr[5];
undchar * 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 sehrkomplexen 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. Beistd::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 mitnew
anlegen. DeinDataClazz 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];
undchar * 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 istchar * 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 mitdelete[]
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 einemstd::string
musst du keinnew
benutzen! Das der Code überhaupt compiliert liegt daran, dass du ein std::string mit einemconst char*
initialisieren kannst. Außerdem hast du in die falsche Richtung gerechnet. Wenn du Platz für 2char
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 mitstd::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 ichdelete
& Heap,
type * const name =
kann ich aber nur mitnew
"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 mitconst 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 auchpush_back
undpop_back
benutzt. Die verändern tatsächlich die Länge des Strings.Sonst noch ein paar kleinere Anmerkungen:
-Warum packst du denstd::string
in deineDataClazz
statt ihn direkt zu benutzen?
-Warum hat deine Klasse einen virtual Destructor?
-Statts = std::string(len, '\0');
kann man auchs.resize(len);
schreiben. Zumindest wenn der String vorher leer war oder man danach eh nochmal füllt.EinGastredner schrieb:
new
vermeiden, weil dann brauche ichdelete
& 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 mitnew
"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. Eindelete
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 mitdelete
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 denstd::string
unbedingt in deine eigene Klasse verpacken? Dieindex
Variable kannst du dir beispielsweise komplett sparen, da das ja identisch mit der Länge des Strings ist und von derstd::string
Klasse bereits gespeichert wird. Ansonsten noch ein Hinweis zur Optimierung: Stattstr = 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,
FinneganP.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
-
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.