char[], String und "End"-Zeichen
-
Edit2: Damit funktioniert's, Danke, damit ist die Übungseinheit abgeschlossen und die nächste kann kommen:
#include <stdlib.h> #include <string.h> #include <iostream> #define LENN 3 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) std::cout << ca << std::endl; combi(ca, len, 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((char * const ) ca, len, 0); return 0; }
Schönen Abend noch
-
Moin, ich muss noch mal nerven,
void combi(char * const ca, const int len, const int idx) {
was wird hier kopiert, was wird referenziert,
void combi(char * ca, int * len, int * idx) {
was wird hier kopiert, was wird referenziert,
zwischen
int
und einer Adresse "auf"int
gibt es keinen Geschwindigkeitsunterschied?Ist es so effizient, kann man es schneller/besser implementieren?
Was praktisch ist, ist, dass man
'\0'
einfach überall "einfügen" kann, in anderen Programmiersprachen ist das nicht so einfach/schnell möglich.Schreibt ihr öfter(s)
const
? Oder ist das Ballast, der in ASM/Maschinencode eh nicht auftaucht?Danke, wer es schafft, das zu lesen.
(Etwas flax ist es ja auch)
-
Für jeden Parameter kriegst du eine lokale Kopie. Wenn dein Parameter ein Pointer ist kriegst du eine Kopie der Adresse. Wenn dein Parameter ein int, enum, struct, class usw. ist kriegst du eine Kopie davon. Bei ints oder andere kleinen Objekten ist das schnell, da ein int auch etwa die größe eines Pointers hat (je nach System). Ohne nachgemessen zu haben würde ich sagen, dass bei kleinen Objekten wie int die Variante ohne Pointer schneller sein könnte, da man durch den Pointer eine zusätzliche Indirektion drin hat. Ich würde daher nur für größere Objekte Pointer oder Referenzen nehmen.
Beim anhängen von '\0' wird ein Byte geschrieben. Wenn du das nicht Millionen mal pro Sekunde machst wird das nicht auffallen.
Und nun zu const: Ich schreibe bei einfachen Variablen wie int eigentlich nie const dran. Wo ich const schreibe ist bei Pointern und Referenzen. Ist dir dabei der Unterschied zwischen
const char*
undchar* const
klar? Das erste ist ein Pointer auf einconst char
. Das zweite ist ein konstanter Pointer auf einchar
. Das ist ein wichtiger Unterschied! Jedenfalls schreibe ich davon höchstens die erste Variante. Die zweite habe ich glaube ich noch nie benutzt. Deine const Pointer verhindern, dass der Programmierer verändert wohin der Pointer zeigt. Das Hinzufügen von const ändert übrigens fast nie etwas am generierten Maschienencode (außer vielleicht bei globalen const Variablen).
-
Hallo Sebi, Danke für deine Antwort. Ich nehme an, es ist ein Zeiger/Pointer/Referenz. (Bedeuten alle dasselbe?)
Wäre diese Methode schneller (Millisekunden)?:
void combi2() { static const int len = LENN; static char * const ca = new char[len + 1]; static int idx = 0; if (len == idx) { return; } for (char c = 'A'; c <= 'Z'; c++) { ca[idx] = c; idx++; ca[idx] = '\0'; std::cout << ca << std::endl; combi2(); idx--; } }
Jetzt könnten len, ca und idx im Register liegen und müssten nicht kopiert werden. Was sagst du dazu?
-
Pointer und Zeiger sind das gleiche. Referenzen sind was anderes, aber so ähnlich. Steht in jedem C++ Buch.
Zu deinem Code: Keine Ahnung ob es schneller ist. Muss man wohl nachmessen. Das langsamste dürfte wohl eh die Ausgabe der Strings sein (unter Windows besonders, da ist jede Ausgabe auf der Konsole soooo langsam). Warum bist du überhaupt an dem letzten Stück Geschwindigkeit interessiert? Das ist eher ein Thema für Fortgeschrittene und den größten Geschwindigkeitsvorteil holt man meist noch aus den Algorithmen statt solchen Mikrooptimierungen raus.
-
sebi707 schrieb:
Das langsamste dürfte wohl eh die Ausgabe der Strings sein
Das stimmt, das dürfte der bottleneck sein.
Aber es könnte ja auch ein verschlüsseltes .zip "geknackt" werden.
Für variable Länge ist der Algo nicht zu verbessern, da dann muss man microoptimieren.
Microoptimierung ist nicht the root of all evil.^^
Danke nochmal.
-
Wenn ich aber nur mit einzelne Zeichen des arrays arbeiten will, dann kann ich mir die Null-Terminierungen sparen solange ich mir die Anzahl der Zeichen merke?
Oder ist das unschön?
-
Kopfsalat schrieb:
Wenn ich aber nur mit einzelne Zeichen des arrays arbeiten will, dann kann ich mir die Null-Terminierungen sparen solange ich mir die Anzahl der Zeichen merke?
Kannste machen.
Kopfsalat schrieb:
Oder ist das unschön?
Es ist nicht nur nicht unschön, sondern es ist der ordentliche Weg. Die Nullterminierung macht fast alles langsamer als es sein müßte.
Puh, mal angenommen, jemand würde für so Supi-Strings auch noch eine Klasse schreiben, die wäre sehr praktisch. Man sollte die Compilerbauer zwingen, so eine Klasse mitauszuliefern, finde ich.
-
Einfach alles umkehren was volkard sagt und schon weißt du was er mitteilen möchte.
-
Kopfsalat schrieb:
Wenn ich aber nur mit einzelne Zeichen des arrays arbeiten will, dann kann ich mir die Null-Terminierungen sparen solange ich mir die Anzahl der Zeichen merke?
Oder ist das unschön?
Dann kannst du aber auch nicht mehr die Standardfunktionen (aus cstring und cstdio oder cout) benutzen.
D.h du musst dir die entsprechend Funktionen selber schreiben.Wenn das die Compilerbauer machen, gibt es wieder unzählige unterschiedliche Klassen.
Das sollte genormt werden.
-
roflo schrieb:
Einfach alles umkehren was volkard sagt und schon weißt du was er mitteilen möchte.
Seine Beitrage lese ich sowieso nur noch selten. Bei der durchschnittlichen Sinnhaftigkeit dieser riskiere ich da eher wenig.
DirkB schrieb:
Dann kannst du aber auch nicht mehr die Standardfunktionen (aus cstring und cstdio oder cout) benutzen.
D.h du musst dir die entsprechend Funktionen selber schreiben.Das ist richtig, aber die braucht man ja auch nicht in allen Fällen.
Und wenn man sie eben nicht braucht, dann lass ich die paar Zeilen Code für die zusätzliche Null-Terminierung eben weg, solange ich weiß, dass ich mit fest definierter Länge arbeite. (Und die ganzen +1 bei größen, Schleifen Durchläufen etc.)
War jetzt aber auch eher aus C-Perspektive bzw. halt in dem Array-Fall, Alternativen sind in C++ natürlich gegeben.
-
Ich glaube volkards Beitrag sollte eine Anspielung auf
std::string
sein. Ich weiß, dass diese Klasse die Größe separat speichert und beliebige Zeichen speichern kann (auch den Null Terminator'\0'
). Daher muss der intern gespeicherte String wohl nicht ständig Null Terminiert sein, außer wenn man sich den String perc_str()
geben lässt (Allerdings istc_str()
const, hat zufällig gerade jemand eine Stelle im Standard die besagt ob und wannstd::string
den internen Buffer mit'\0'
terminieren muss?). Statt den stdio Funktionen nutzt man dann natürlich diestd::string
Funktionen, die sowieso einfacher zu benutzen sind. Hast du deine Funktion mal damit programmiert und die Zeit gemessen?
-
sebi707 schrieb:
Hast du deine Funktion mal damit programmiert und die Zeit gemessen?
Noch nicht, aber es stehen jetzt 3 Varianten/Versionen zur Auswahl:
1. 3 Parameter (auch wenn nur Pointer) müssen immer kopiert und mitgeschleppt werden,
2. 3 Parameter müssen nicht mitgeschleppt werden, dafür muss 1-mal mehr dekrementiert werden,
3. man verwendet die Klasse String/string/str/std (weiß ich nicht genau darüber Bescheid), welche nicht immutable ist?Was dauert denn länger, ein increment oder 3-mal ein load/store?
Wenn ein String ein "richtiges" Objekt ist, dann dürfte das gegenüber dem "einfachen" Array char am langsamsten sein, imo.
-
Jop, das habe ich schon auch rausgelesen.
Dieser krampfhafte Versuch sich durch Sarkasmus unverständlich auszudrücken wirkt aber einfach nur pseudo intellektuell und nervt mit der Zeit.
Ich weiß nicht genau was ihn immer dazu bewegt, dieses Stilmittel krampfhaft zu verwenden, aber nun ja.
Was die Thematik betrifft:
Sicherlich ist std::string die bessere, allgemeine Wahl, da stimme ich durchaus zu.Es ging mir aber eben nun um den konkreten Fall, in dem ein Char array verwendet wird, warum auch immer.
Da kam hier im Thread ein bisschen der Fler "Char array? Null terminieren!!!" rüber, daher wollte ich einfach mal fragen wie das ist, wenn man sie gar nicht braucht.
Da finde ich es nämlich durchaus legitim sie weg zulassen, wenn man alle Folgen bedenkt.
-
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.