C++ Lernen - Verständnisfragen
-
Das ist aber auch ein bisschen eine alte Art und Weise, das zu schreiben. So wie anno 2010, ca. Du siehst ja vielleicht selbst, dass das nicht unbedingt intuitiv verständlich ist.
Seither sind aber zwei wesentliche Dinge dazu gekommen:
- For-Schleifen, die einfach nur über alle Elemente in einen Container iterieren sollen, schreibt man nun als ranged base for:
for (auto element: container) { // Tu etwas mit dem Element aus dem Container }
- Es sind allerlei Helferlein dazu gekommen, mit denen man einfacher mit solchen Containersequenzen umgehen kann, unter anderem reverse_view. Das Beispiel hinter dem Link passt ziemlich exakt zu deinem Problem, und zeigt wie man es mit diesen Mitteln auf verschiedene Arten und Weisen lösen kann.
Letzteres setzt aber einen einigermaßen aktuellen Compiler voraus, weil es "erst" ein paar Jahre alt ist, was für C++-Verhältnisse quasi brandneu ist.
-
@Drgreentom sagte in C++ Lernen - Verständnisfragen:
Ich habe mir jetzt dazu die Lösung im Buch angesehen:
for(int i=eingabe.size()-1; i>=0; i--) cout << eingabe.at(i) << " ";
Ich hoffe das Buch erwähnt auch, dass
int
mit Vorzeichen hier wichtig ist. Wenn man das durch einauto
ersetzt (i
bekommt dann den Typ, der voneingabe.size()
zurückgegeben wird) dann fliegt einem das um die Ohren. Nur um das nochmal zu unterstreichen, da der Großteil der Diskussion genau darum ging
-
Um mal einen anderen Ansatz zu zeigen:
std::vector<int> foobar( 10, 1 ); for ( std::size_t i = 0; i < foobar.size(); ++i ) { std::cout << foobar.at( foobar.size() - i - 1 ) << "\n"; }
Man muss eigentlich in fast keinem Fall unbedingt rückwärts iterieren und wenn doch, dann am besten über die von den anderen hier schon genannten Methoden.
Das rückwärts-durchlaufen führt insbesondere bei Neueinsteigern oft zu Problemen, wenn man die Funktionsweise von C++ ( auch die von positiven ganzzahligen Datentypen ) noch nicht komplett durchdrungen hat.Ich verwende eigentlich ausschließlich die Range-Based-Loop und eben die Variante mit Iteratoren, insbesondere dann wenn ich den Iterator z.B. zum Aufruf von "erase()" benötige.
-
@It0101 sagte in C++ Lernen - Verständnisfragen:
Ich verwende eigentlich ausschließlich die Range-Based-Loop und eben die Variante mit Iteratoren, insbesondere dann wenn ich den Iterator z.B. zum Aufruf von "erase()" benötige.
Hm. Das würde ich nicht machen, denn z.B.
vector.erase
invalidiert alle nachfolgenden Iteratoren, auch das vorherigeend
. In der Range-Loop wurde das aber am Anfang geholt. Also eine range-Loop mit erase darin - das kann doch eigentlich nicht sinnvoll gehen?
-
Das ist wahrscheinlich ungünstiger Satzbau und soll heißen, dass die Iterator-Variante dem range-based Loop vorgezogen wird, wenn es um Dinge wie erase geht. Denn anders geht es auch gar nicht, denn vector::erase arbeitet schließlich auf einem Iterator, nicht auf einem Wert. Für so etwas ist dann erase_if gut.
-
@wob sagte in C++ Lernen - Verständnisfragen:
Hm. Das würde ich nicht machen, denn z.B.
vector.erase
invalidiert alle nachfolgenden Iteratoren, auch das vorherigeend
. In der Range-Loop wurde das aber am Anfang geholt. Also eine range-Loop mit erase darin - das kann doch eigentlich nicht sinnvoll gehen?Jain. Man muss natürlich trotzdem aufpassen.
Es sieht in der Regel so aus:std::vector<int> foobar; for ( auto it = foobar.begin(); it != foobar.end(); ) { if ( hasToBeDeleted( *it ) ) it = foobar.erase( it ); else ++it; }
Das funktioniert, weil erase dann quasi schon den "neuen" iterator zurückliefert. Ich nutze das, wenn die Möglichkeit besteht, dass mehrere Elemente entfernt werden müssen.
-
@It0101 Hier hast du aber auch Iteratoren genutzt und nicht wie von wob vermutet eine range based for loop.
-
@Tyrdal sagte in C++ Lernen - Verständnisfragen:
@It0101 Hier hast du aber auch Iteratoren genutzt und nicht wie von wob vermutet eine range based for loop.
Ja natürlich. Ich fummel doch nicht innerhalb einer Range-Based-Loop mit erase rum....
Dann war das Missverständnis ein anderes, als ich dachte.
Edit: jetzt hab ich's noch mal gelesen. Es verhält sich im Grunde genau so wie @SeppJ es geschrieben hat.
-
Ah, dann hatte ich dich falsch verstanden. Jetzt, wo ich deinen Post nochmal lese, ist es wohl das "und", was ich nicht richtig verstanden hatte.
Statt
Ich verwende eigentlich ausschließlich die Range-Based-Loop und eben die Variante mit Iteratoren, insbesondere dann wenn ich den Iterator z.B. zum Aufruf von "erase()" benötige.
Wäre klarer gewesen:
Ich verwende eigentlich ausschließlich die Range-Based-Loop. Wenn ich aber den Iterator z.B. zum Aufruf von "erase()" benötige, verwende ich die Variante mit Iteratoren.
Das meintest du. Dann ist es natürlich richtig. Ich hatte den Text oben zu schnell gelesen.
Wobei: in deinen Beispiel würde ich eher das erase-remove-Idiom einsetzen:
Also statt
for ( auto it = foobar.begin(); it != foobar.end(); ) { if ( hasToBeDeleted( *it ) ) it = foobar.erase( it ); else ++it; }
...lieber sowas wie:
foobar.erase( std::remove_if(foobar.begin(), foobar.end(), hasToBeDeleted), foobar.end() );
(da vergesse ich nur sehr, sehr, sehr gerne das 2. Argument von erase. )
-
@wob Müsste nicht inzwischen sowas gehen:
auto [begin, end] = std::ranges::remove_if(foobar, hastToBeDeleted); foobar.erase(begin, end);
Edit: Code angepasst... erase hat gefehlt. Macht es so nicht viel besser.
-
@Schlangenmensch sagte in C++ Lernen - Verständnisfragen:
@wob Müsste nicht inzwischen sowas gehen:
auto [begin, end] = std::ranges::remove_if(foobar, hastToBeDeleted); foobar.erase(begin, end);
Edit: Code angepasst... erase hat gefehlt. Macht es so nicht viel besser.
Nicht in jedem fall da hier eine range erwartet wird.
Aber im folgenden falle nicht
vector enthält folgende werte1, 9, 5, 10
hastToBeDeleted hat die Bedingung <value> <= 5
Dann würde std::ranges::remove_if(foobar, hastToBeDeleted); wohl nach dem es auf den wert 9 trifft abbrechen. Und Dadurch würde der Wert 5 nicht gelöscht werden
Oder im schlimmsten falle würde die 9 auch mit gelöscht werden.
-
@firefly Nö, warum: https://godbolt.org/z/fv8G9sf7M
std::ranges::remove_if
verschiebt die Elemente so, dass die Elemente, die nicht gelöscht werden am Anfang des Ranges stehen und die, die gelöscht werden am Ende. Und zurück gegeben wird die Subrange der zu löschenden Elemente.
-
@firefly sagte in C++ Lernen - Verständnisfragen:
Dann würde std::ranges::remove_if(foobar, hastToBeDeleted); wohl nach dem es auf den wert 9 trifft abbrechen. Und Dadurch würde der Wert 5 nicht gelöscht werden
Oder im schlimmsten falle würde die 9 auch mit gelöscht werden.Nein, das erase-remove-Idiom ist nicht umsonst ein Idiom (sogar mit einenem Wikipedia-Artikel). Es wäre kein Idiom, wenn solche komischen Dinge passieren würden (wie es funktioniert, hat @Schlangenmensch ja schon geschrieben)
Edit: und lesen lohnt sich sogar, denn dann erfährt man z.B., dass es mit C++20 auch via std::erase / std::erase_if ginge.
-
@Schlangenmensch sagte in C++ Lernen - Verständnisfragen:
@firefly Nö, warum: https://godbolt.org/z/fv8G9sf7M
std::ranges::remove_if
verschiebt die Elemente so, dass die Elemente, die nicht gelöscht werden am Anfang des Ranges stehen und die, die gelöscht werden am Ende. Und zurück gegeben wird die Subrange der zu löschenden Elemente.Ah ok hab das in der beschreibung überlesen. https://en.cppreference.com/w/cpp/algorithm/ranges/remove
-
@Schlangenmensch sagte in C++ Lernen - Verständnisfragen:
@wob Müsste nicht inzwischen sowas gehen:
auto [begin, end] = std::ranges::remove_if(foobar, hastToBeDeleted); foobar.erase(begin, end);
Edit: Code angepasst... erase hat gefehlt. Macht es so nicht viel besser.
Und wo lliegt hier der große Vorteil? Ist doch auch nicht wirklich kürzer als das alte Idiom und macht vom Prinzip her dasselbe.
-
@Tyrdal Ich hatte im Kopf, dass es mit der Ranges lib auch in einem Aufruf hätte gehen müssen, ohne dass man den, von @wob erwähnten
end()
Aufruf vergessen kann. Hatte mich da aber im ersten Schuss vertan, daher der Edit. Die von @wob verlinktenstd::erase
undstd::erase_if
sind die schöneren Alternativen.
-
Hahah geil wir ihr alle gleich abgeht ^^
Vieles von dem was ihr geschrieben habt ist mir noch etwas zu hoch, auch wenn ich den Ansatz meist irgendwie nachvollziehen kann.Es ist interessant zu sehen welche unterschiedlichen Ansichten und Wege es für ein und das selbe Problem und dessen Lösung gibt.
Wenn ich das richtig sehe geht es bei eurer Diskusion dabei aber tendenziell um die Zuverlässigkeit des Codes und um die Geschwindigkeit - sowohl beim Code schreiben als auch beim Ausführen von diesem?!
-
Weitere Frage: Wenn ich
string::npos;
in eine Variable packen will, was ist der dafür geeignete Datentyp? Es funktioniert bei mirint
,char
,double
,float
.
Der Datentypbool
setzt dieif
Abfrage auf unwahr und gibt als Position: 18446744073709551615 aus .string satz{"Das ist ein Satz"}; string::size_type pos; int str_npos = string::npos; // Hier mit Integer cout << "'wird'"; pos = satz.find("wird"); if (pos == str_npos) cout << " nicht gefunden" << endl; else cout << "gefunden an Pos." << pos << endl;
Ob das Sinn macht oder nicht möchte ich mal außen vor lassen...
Gebe ich str_npos aus so erhalte ich :
cout << str_npos << endl;
- Als Interger = -1
- Als Double und float = 1.84467e+19
- Als Char = bleibt leer
- Als Bool = 1
Ich gehe also davon aus das der Integer am meisten Sinn macht. Da int -1 ist und damit nicht im String liegen kann. Also könnte ich ja nach meinem kümmerlichen Verständnis auch
if (pos == -1)
prüfen? Was auch funktioniert...Übersehe ich da was?
-
@Drgreentom sagte in C++ Lernen - Verständnisfragen:
Übersehe ich da was?
Ja, tust du.
std::string::npos
ist einsize_t
.size_t
ist ein unsigned Datentyp undnpos
ist der größte Wert, der damit darstellbar ist. Das dies alsint
interpretiert-1
ist, ist nirgends garantiert.
Fürbool
gilt, jeder Zahlenwert ungleich Null wird als wahr interpretiert.Daher, aufpassen, wenn Typen konvertiert werden.
std::string::size_type
garantiert, dass der Typ groß genug ist, um die Größe des Strings wiederzugeben.std::string::find
gibt einensize_t
zurück.size_t
garantiert, dass es die maximale Größe eines Objektes speichern kann.Ich bin mir gerade nicht sicher, ob
std::string::size_type
immer einsize_t
sein muss, halte es aber für wahrscheinlich, dass es in den meisten Implementationen so sein wird.
-
@Drgreentom sagte in C++ Lernen - Verständnisfragen:
18446744073709551615
Das ist das Maximale, was mit 64 Bit - als unsigned - darstellbar ist. Alle Bits sind auf 1 gesetzt.