Forum: PC-Programmierung Verkette Listen in C und C++


von Michael (Gast)


Lesenswert?

Hallo Zusammen,
ich habe eine Frage zu verketteten Listen.

Unter C++ lese ich oft bei Klassen folgende Definition:
list<CKlasse*> Klasse1

In C lese ich zum Thema verkettete Listen bzw. dynamische 
Datenstrukturen z.B.

struct ....{
...
...

struct Struktur1 *next
}


gibt es da Unterschiede zwischen Verkettete Listen/Dynamische 
Datenstrukturen und verkettete Listen in C++ mit "<list>"?

von Peter II (Gast)


Lesenswert?

Michael schrieb:
> gibt es da Unterschiede zwischen Verkettete Listen/Dynamische
> Datenstrukturen und verkettete Listen in C++ mit "<list>"?

ja. (Typsicherheit, Verwendung, Interface)

von Dr. Sommer (Gast)


Lesenswert?

Die grundlegende Funktionsweise ist praktisch gleich. In C++ wird dir 
lediglich mit std::list eine fertige Implementation geboten, bei der du 
dich nicht um die Details kümmern musst. Mithilfe von templates ist die 
dann auch typsicher. Bei der Eigenimplementation einer verketteten Liste 
gibts halt so diverse Fallstricke.

Der einzige wirkliche Unterschied besteht darin, dass wenn du z.B. eine 
std::list<int> hast, und einen Pointer auf ein Element holst - also ein 
"int*" - du anhand dieses Pointers nicht auf das nächste/vorherige 
Element schließen kannst. Bei diesen typischen C-Listen ist das anders - 
da sind die Listenelemente die "Struktur1" (in deinem Beispiel), und mit 
einem Pointer auf eine Struktur1 Instanz kannst du halt über ->next 
durchaus das nächste Element bekommen.

Verkettete Listen sind aber gar nicht so sinnvoll wie einem der 
Informatikunterricht glauben machen möchte - weil sie über den ganzen 
Speicher verteilt sind nutzen sie den Cache nicht gut und sind daher 
eher langsam.  Oft sind array-artige Container wie std::vector 
sinnvoller.

von Peter M. (r2d3)


Lesenswert?

Dr. Sommer schrieb:
> Verkettete Listen sind aber gar nicht so sinnvoll wie einem der
> Informatikunterricht glauben machen möchte - weil sie über den ganzen
> Speicher verteilt sind nutzen sie den Cache nicht gut und sind daher
> eher langsam.  Oft sind array-artige Container wie std::vector
> sinnvoller.

Diese Begründung bezweifele ich.

Algorithmen, die auf Listen operieren, haben Laufzeitprobleme.
Suchen in Listen kostet O(n), Baumstrukturen eher O(log(n)).
Listen versteht jeder, Baumoperationen strengen die grauen Zellen an.

Welche Algorithmen sind denn explizit "cache-freundlich"?

von Dr. Sommer (Gast)


Lesenswert?

Peter M. schrieb:
> Algorithmen, die auf Listen operieren, haben Laufzeitprobleme.
> Suchen in Listen kostet O(n), Baumstrukturen eher O(log(n)).
Jaja, mit den Landau-Symbolen kann man alle Effizienz-Probleme 
erschlagen und dabei alle technischen Gegebenheiten ignorieren, ein 
Traum für Mathematiker.
Die Iteration durch Listen ist zwar O(n), aber der konstante Faktor kann 
groß sein. Die Iteration durch einen Vektor ist auch O(n) aber 
typischerweise mit geringerem konstanten Faktor.

Bei der Argumentation ist natürlich vorausgesetzt dass eine "lineare" 
Datenstruktur schon die richtige ist. Nicht alles lässt sich als Baum 
darstellen. Aber auch Bäume können schneller sein, wenn sie "flach" in 
einen Vektor gepackt sind.

Peter M. schrieb:
> Welche Algorithmen sind denn explizit "cache-freundlich"?
Solche die hintereinander auf nah benachbarte Speicherstellen zugreifen.

von TriHexagon (Gast)


Lesenswert?

Der große Vorteil von verketteten Listen gegenüber von 
Arrays/std::vector sind doch die billigen Verschiebe, Einfüge... 
-Operationen. Bei Listen müssen nur die Zeiger manipuliert werden, bei 
Arrays hingegen viel hin und her kopiert werden. D.h. wenn sich dein 
Array häufig im Programmverlauf ändert, ist wahrscheinlich eine Liste 
die bessere Wahl. Wobei man da mit Speicherallokationen aufpassen muss, 
also am besten das Ganze so konstruieren, dass für die Liste nie neuer 
Speicher reserviert werden muss, aber das gilt für Arrays/std::vector 
genauso.

von Dr. Sommer (Gast)


Lesenswert?

Richtig, und daher muss man sich überlegen welche Operation man häufiger 
braucht. Wenn man 10000x pro Sekunde iteriert, aber nur 1x pro Sekunde 
neue Elemente einfügt, ist der vector vermutlich schneller. Wenn man 
gleich oft einfügt und iteriert sieht's schon anders aus.

Ganz schlau ist dann auszuprobieren was schneller ist indem man den 
Container-Typ wechselt. Bei entsprechender Programmierung unter 
Ausnutzung der Fähigkeiten von C++ muss man dabei keinerlei Code 
anpassen außer der Deklaration der Variable.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Dr. Sommer schrieb:
> Peter M. schrieb:
>> Algorithmen, die auf Listen operieren, haben Laufzeitprobleme.
>> Suchen in Listen kostet O(n), Baumstrukturen eher O(log(n)).
> Jaja, mit den Landau-Symbolen kann man alle Effizienz-Probleme
> erschlagen und dabei alle technischen Gegebenheiten ignorieren, ein
> Traum für Mathematiker.
> Die Iteration durch Listen ist zwar O(n), aber der konstante Faktor kann
> groß sein. Die Iteration durch einen Vektor ist auch O(n) aber
> typischerweise mit geringerem konstanten Faktor.
>
> Bei der Argumentation ist natürlich vorausgesetzt dass eine "lineare"
> Datenstruktur schon die richtige ist. Nicht alles lässt sich als Baum
> darstellen. Aber auch Bäume können schneller sein, wenn sie "flach" in
> einen Vektor gepackt sind.
>

Ja, ich erinnere aus meiner LISP Zeit, dass es da ganze Doktorarbeiten 
gab über die Vor-und Nachteile der Organisation (da gab es so etwas wie 
CAR-CDRing, wenn ich es richtig erinnere, das versuchte die Listen so 
gepackt wie möglich zu halten).

Allerdings ist der Knackpunkt halt, dass verkettete Listen grundsätzlich 
keine hardcodierte Maximalanzahl von Elementen haben, Arrays aber per 
Definition schon. In Embedded geht es halt immer um effiziente 
Ressourcennutzung, und da musst Du halt überlegen, ob Du einen worst 
case annimmst (also ein Array mit hardcodiert Max möglichen Anzahl 
Elemente falls bestimmbar, auch auf die Gefahr hin, zu 90% der Laufzeit 
viel vom Speicher brach liegen zu haben) oder den Speicher dynamisch zu 
nutzen (mit den bekannten Problemen). Sehr Vieles was man macht ist ja 
immer eine Abwägung zwischen Vor- und Nachteilen zweier oder mehr 
Lösungen für eine gegebene Aufgabenstellung, aber Universal richtige 
"beste" Lösungen gibt es eigentlich nie.

von Dr. Sommer (Gast)


Lesenswert?

Also std::vector hat keine fixe Maximalanzahl, funktioniert aber wie ein 
Arrays. Dann gibt es noch std::deque, ein Hybrid (ohne feste 
Max.-Anzahl).

Bei Embedded sieht das natürlich anders aus, auch weil MCU's selten 
Caches haben. Aber da brauchts auch eher selten dynamische 
Datenstrukturen:

Ruediger A. schrieb:
> also ein Array mit hardcodiert Max möglichen Anzahl
> Elemente falls bestimmbar, auch auf die Gefahr hin, zu 90% der Laufzeit
> viel vom Speicher brach liegen zu haben)
Na und? Sofern man den Speicher nicht gerade für etwas anderes nutzen 
kann, ist das völlig egal.

von RAc (Gast)


Lesenswert?

Dr. Sommer schrieb:
>
> Ruediger A. schrieb:
>> also ein Array mit hardcodiert Max möglichen Anzahl
>> Elemente falls bestimmbar, auch auf die Gefahr hin, zu 90% der Laufzeit
>> viel vom Speicher brach liegen zu haben)
> Na und? Sofern man den Speicher nicht gerade für etwas anderes nutzen
> kann, ist das völlig egal.

Schreiben wir aneinander vorbei? Mein Punkt ist doch gerade, dass man in 
Embedded oftmals sehr wohl mit Ressourcen schwäbeln muss, d.h. "sofern 
man den Speicher nicht gerade für etwas anderes nutzen kann" ist hier 
genau der Knackpunkt! Klar, wenn ich mehr RAM On Board habe als ich zur 
Laufzeit jemals benutzen werde, kann ich Alles statisch mit Reserven 
anlegen. Genau das geht aber oftmals nicht. Speicher, der statisch weg 
ist, ist weg und eben nicht mehr verwendbar, weder "gerade" noch 
"irgendwann später mal."

Da aber Embedded Architekturen normalerweise auf eine bestimmte 
Anwendung zurechtgeschnitten sind, hat man eben so viel Speicher zur 
Verfügung, dass man damit den Normalbetrieb durchsteht, aber nicht mehr. 
Damit passiert es also eher selten, dass "man den Speicher nicht gerade 
für etwas anderes nutzn kann."

Das wollte ich damit ausdrücken, mehr eigentlich nicht.

Ausserdem gibt es auch Fälle, in denen ein Maximum an Einträgen nicht 
bestimmbar ist oder im worst case temporär soviel Speicher in Aspruch 
nehmen kann, dass damit der Rest des Systemes dauerhaft nicht auskommen 
kann.

von Peter M. (r2d3)


Lesenswert?

Dr. Sommer schrieb:
> Peter M. schrieb:
>> Welche Algorithmen sind denn explizit "cache-freundlich"?
> Solche die hintereinander auf nah benachbarte Speicherstellen zugreifen.

Das setzt voraus, große Speicherbereiche zu reservieren und selbst zu 
verwalten.

von Michael (Gast)


Lesenswert?

Ok, vielen Dank. ich bin etwas verwirrt, da zwischen C und C++ doch 
viele Unterschiede sind.

Beispiel:
 Unter C hat man eine Struktur struct adressen {};

Mit struct adressen ADDR1 erzeugt man sich die erste Instanz. Unter C++ 
fällt das Wort struct eben weg --> adressen ADDR1

Unter C kann man das auch, müsste aber eben mit typedef arbeiten.

von Jobst Q. (joquis)


Lesenswert?

Der Unterschied zwischen C und C++ ist in etwa der zwischen einem 
Werkzeugkasten und einem Baukasten wie zB von LEGO.

In C lernst du deine relativ wenigen Werkzeuge kennen und bastelst dir 
dann damit die Funktionen und Strukturen, die du brauchst für deine 
Programme.

In C++ hast du eine riesige Menge von fertigen Bausteinen, die du 
einfach zusammensetzen kannst. Du verbringst weniger Zeit mit dem 
Programmieren, aber dafür umso mehr mit dem Suchen von geeigneten 
Bausteinen und dem Studieren von Features und Blibliotheksreferenzen.

Um auf die verketteten Listen zurück zu kommen, ich habe bisher nur zwei 
verschiedene gebraucht. Eine mit einem void *pointer und einem Integer 
zum Einsortieren pro Knoten sowie eine mit einem String (char *)statt 
dem int.
Damit können beliebige Elemente in beliebiger Reihenfolge eingelesen und 
sortiert behandelt oder ausgegeben werden.

von Dr. Sommer (Gast)


Lesenswert?

Jobst Q. schrieb:
> ich habe bisher nur zwei
> verschiedene gebraucht. Eine mit einem void *pointer und einem Integer
> zum Einsortieren pro Knoten sowie eine mit einem String (char *)statt
> dem int.

Mir ist nicht ganz klar was du sagen willst, aber das klingt nach einem 
vermurksten Design... Allein schon weil man std::string und nicht char* 
für Strings nimmt.

Jobst Q. schrieb:
> In C++ hast du eine riesige Menge von fertigen Bausteinen, die du
> einfach zusammensetzen kannst. Du verbringst weniger Zeit mit dem
> Programmieren, aber dafür umso mehr mit dem Suchen von geeigneten
> Bausteinen und dem Studieren von Features und Blibliotheksreferenzen.
Das klingt mehr nach Java...

von Εrnst B. (ernst)


Angehängte Dateien:

Lesenswert?

Dr. Sommer schrieb:
> Also std::vector hat keine fixe Maximalanzahl, funktioniert aber wie ein
> Arrays. Dann gibt es noch std::deque, ein Hybrid (ohne feste
> Max.-Anzahl).

du verwechselst std::vector und std::array.

das Array hat die fixe größe, der vektor kann wachsen.
Aber am Vektor kann man nur hinten in (amortisierter) konstanter Zeit 
Elemente anfügen/entfernen. (push/pop)

bei der deque (Double Ended Queue) geht das an beiden Enden, deshalb 
bietet sie sich oft als schneller Ersatz für std::list in 
FIFO-Anwendungen an...

von Dr. Sommer (Gast)


Lesenswert?

Εrnst B. schrieb:
> du verwechselst std::vector und std::array.
>
> das Array hat die fixe größe, der vektor kann wachsen.

Ja das hab ich doch gesagt. "Funktioniert wie ein Array"  sollte heißen 
dass keine Verkettung existiert.

von Dumdi D. (dumdidum)


Lesenswert?

Hier die Antwort vom Meister:
https://m.youtube.com/watch?v=YQs6IC-vgmo

von Εrnst B. (ernst)


Lesenswert?

Dr. Sommer schrieb:
> Εrnst B. schrieb:
>> du verwechselst std::vector und std::array.
>>
>> das Array hat die fixe größe, der vektor kann wachsen.
>
> Ja das hab ich doch gesagt. "Funktioniert wie ein Array"  sollte heißen
> dass keine Verkettung existiert.

Ups, sorry.. hab ich nicht exakt genug gelesen.

Zum Thema: Ob man besser eine std::list oder einen std::vector nimmt, 
hängt auch von der Art/Größe der zu speichernden Objekte ab.

Die Liste braucht für jedes Element Verwaltungsinformationen, (prev/next 
Pointer), eine list<int> ist also sehr verschwenderisch. Der Vektor 
hingegen braucht nur einmal seinen Satz Verwaltungsinfos, unabhängig vom 
Füllstand.

Andersherum kann der Vektor nur "kopierbare" Sachen speichern, und 
kopiert die auch öfters mal. Klassen mit teurem Copy-Constructor packt 
mal also besser in eine Liste.

Aber, das ist das schöne an der stl: man die Container sind alle mehr 
oder weniger Kompatibel, man kann ohne großen Aufwand verschiedene mit 
realem Workload gegeneinander profilen.

von Jobst Q. (joquis)


Lesenswert?

Dr. Sommer schrieb:
> Jobst Q. schrieb:
>> ich habe bisher nur zwei
>> verschiedene gebraucht. Eine mit einem void *pointer und einem Integer
>> zum Einsortieren pro Knoten sowie eine mit einem String (char *)statt
>> dem int.
>
> Mir ist nicht ganz klar was du sagen willst,

Dass man nicht eine lange Latte von unterschiedlichen Listen 
braucht,sondern mit wenigen universell einsetzbaren auskommt, wenn sie 
gut konzipiert sind.


> aber das klingt nach einem
> vermurksten Design... Allein schon weil man std::string und nicht char*
> für Strings nimmt.

Man nimmt std::string, wenn man wenig von Stringbearbeitung versteht und 
gelegentlich mal einen einsetzen muss. Wenn man keine Angst vor Pointern 
hat und sich damit auskennt, ist es mit char* wesentlich effizienter.

von nicht"Gast" (Gast)


Lesenswert?

Jobst Q. schrieb:
> Man nimmt std::string, wenn man wenig von Stringbearbeitung versteht und
> gelegentlich mal einen einsetzen muss. Wenn man keine Angst vor Pointern
> hat und sich damit auskennt, ist es mit char* wesentlich effizienter.

Der war gut. Du hast die Ironie Tags vergessen

von Dr. Sommer (Gast)


Lesenswert?

Wenn ich eine Klasse HansPeter auflisten will nehm ich eine 
std::list<HansPeter>. Gleiches gilt für alle möglichen anderen Klassen. 
Keine Ahnung was du da mit void* und int willst. Das sind im Endeffekt 
alles die gleichen Listen mit typsicherem angepasstem Interface.

C++ aber char* verwenden? Ist klaro. std::string ist äußerst schlau 
programmiert, das sollte bei den meisten Programmen praktisch keinen 
Unterschied machen. Dafür dass man dadurch eine Menge Komfort und 
Sicherheit gewinnt (im Gegensatz du den ganzen beliebten Buffer 
Overflows bei C String Verarbeitung mit char*)...

von Jobst Q. (joquis)


Lesenswert?

nicht"Gast" schrieb:
> Jobst Q. schrieb:
>> Man nimmt std::string, wenn man wenig von Stringbearbeitung versteht und
>> gelegentlich mal einen einsetzen muss. Wenn man keine Angst vor Pointern
>> hat und sich damit auskennt, ist es mit char* wesentlich effizienter.
>
> Der war gut. Du hast die Ironie Tags vergessen

Nein, das ist mein voller Ernst.

In std::string ist jeder String ein Objekt, das um ein vielfaches größer 
ist als ein Pointer.Dafür muss Speicher alloziiert werden, selbst wenn 
der String leer ist. Ein Array von mehreren tausend Strings, was wird 
das für ein Monster? Wenn ein String vergrößert wird, muss er 
realloziert werden und oft auch umkopiert werden. Bei jeder Änderung 
müssen Membervariablen aktualisiert werden. Wie soll das effizient sein?

In C arbeitet man mit Puffern, die man statisch oder zu Beginn einer 
Funktion auf dem Stack anlegt. Ein Puffer kann mehrere Strings in Form 
von einfachen Pointern enthalten. So kann man eine Zeile aus einer Datei 
in den Puffer lesen und dann in einzelne Strings zerlegen, indem man 
Trennzeichen durch das Nullzeichen ersetzt.

Beim Zusammensetzen von Strings kann man beliebige Strings und Zeichen 
aneinanderkopieren, solange noch Platz im Puffer ist.

Dr. Sommer schrieb:
> im Gegensatz du den ganzen beliebten Buffer
> Overflows bei C String Verarbeitung mit char*).

Mit geeigneten Funktionen sind Buffer Overflows kein Problem. Fürs 
Kopieren habe ich eine Funktion char * stpcpylim(char *t,const char *s, 
const char *lim).Dabei ist lim ein Zeiger auf das erste Byte, dass nicht 
beschrieben werden darf. Man muss es je Puffer nur einmal zuweisen: lim 
= buf +sizeof(buf);

Näheres siehe Beitrag "Re: String-Array und malloc"

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Jobst Q. schrieb:
> das um ein vielfaches größer
> ist als ein Pointer.
Korrekt. Es wird noch die Länge gespeichert, das musst du bei deinem 
Pointer ja auch, um Buffer Overflows zu vermeiden (oder rufst du jedes 
mal strlen auf - super effizient). Außerdem nutzen manche std::string 
Implementationen einen lokalen Buffer, d.h. sehr kurze strings werden 
direkt in std::string gespeichert. Dadurch wird der Zugriff deutlich 
schneller - ist also sogar effizienter als die Arbeit mit Pointern. 
Insbesondere, wenn ein Array von sehr vielen kurzen Strings hast...

Jobst Q. schrieb:
> Ein Array von mehreren tausend Strings, was wird
> das für ein Monster?
... Ist das mit std::string viel schneller!

Jobst Q. schrieb:
> Wenn ein String vergrößert wird, muss er
> realloziert werden und oft auch umkopiert werden
Und bei char* etwa nicht?

Jobst Q. schrieb:
> Bei jeder Änderung
> müssen Membervariablen aktualisiert werden.
Nur beim Reallokieren. Und da musst du bei char* deine explizit 
gespeicherte Größe auch aktualisieren.

Jobst Q. schrieb:
> Wie soll das effizient sein?
Wie gesagt, schlaue Implementierung. Anstatt zu spekulieren, kannst du 
es ja ausprobieren.

Jobst Q. schrieb:
> die man statisch oder zu Beginn einer
> Funktion auf dem Stack anlegt
D.h. du hast nur sehr wenige, kleine strings (der Stack ist ja 
typischerweise klein). Damit sind deine Argumente sowieso hinfällig, 
weil die Laufzeitunterschiede (falls vorhanden) dann ohnehin 
verschwinden. Wie gesagt nutzt std::string für kleine Strings auch 
lokalen Speicher. Und wie genau vergrößerst du solche statisch 
allokierten Strings?

Jobst Q. schrieb:
> indem man
> Trennzeichen durch das Nullzeichen ersetzt.
Das geht bei std::string auch. Nur dass man bei std::string die 
tatsächliche Länge noch behält (während strlen ja dann nicht mehr 
funktioniert).

Jobst Q. schrieb:
> Beim Zusammensetzen von Strings kann man beliebige Strings und Zeichen
> aneinanderkopieren, solange noch Platz im Puffer ist.
Ja, bei std::string auch. Der kann ja auch über-allokieren.

Jobst Q. schrieb:
> Mit geeigneten Funktionen sind Buffer Overflows kein Problem
Klar. Die muss man aber präzise und immer korrekt nutzen. Dass das viele 
Leute nicht schaffen sieht man daran, dass das Ursache Nr. 1 von 
Sicherheitslücken ist. Wenn du so ein Genie bist der das nie falsch 
macht ist das schön für dich, aber das sollte man nicht als musterhaftes 
Vorgehen verbreiten...

In vielen Anwendungen hat man viele verschiedene Strings, hauptsächlich 
für die GUI. Die sind aber absolut nicht Performance-kritisch. Daher ist 
es sehr angenehm, wenn man das super einfache und sichere std::string 
API dafür nutzen kann, anstatt bei jedem einzelnen mit der 
C-String-Verarbeitung rumzubasteln und ständig auf Overflows zu achten. 
Und selbst wenn es Performance-Kritisch wird (sehr viele/große Strings) 
ist keineswegs gesetzt dass std::string langsamer sei - das sollte man 
lieber ausprobieren anstatt wild zu spekulieren! In diesem seltenen Fall 
kann man bei std::string mit expliziter Allokierung und std::string_view 
arbeiten.

von Rolf M. (rmagnus)


Lesenswert?

Εrnst B. schrieb:
> Zum Thema: Ob man besser eine std::list oder einen std::vector nimmt,
> hängt auch von der Art/Größe der zu speichernden Objekte ab.
>
> Die Liste braucht für jedes Element Verwaltungsinformationen, (prev/next
> Pointer), eine list<int> ist also sehr verschwenderisch. Der Vektor
> hingegen braucht nur einmal seinen Satz Verwaltungsinfos, unabhängig vom
> Füllstand.

Das schon. Dafür reserviert der Vektor in der Regel mehr Speicher als 
aktuell benötigt, um nicht bei jedem einzelnen Anhängen eines neuen 
Wertes komplett reallokieren zu müssen. Und wenn er reallokieren muss, 
braucht er temporär die bisher benötige Menge Speicher plus die neue 
Menge.
Da kann also auch einiges an Overhead anfallen.

Jobst Q. schrieb:
> Man nimmt std::string, wenn man wenig von Stringbearbeitung versteht und
> gelegentlich mal einen einsetzen muss. Wenn man keine Angst vor Pointern
> hat und sich damit auskennt, ist es mit char* wesentlich effizienter.

Wer tut sich denn heute noch freiwillig String-Verarbeitung im C-Stil 
an? Von der Laufzeit her mag das effizienter sein, aber die 
umständliche, hakelige und fehlerträchtige Programmierung tue ich mir 
nur an, wenn's nicht anders geht.

Εrnst B. schrieb:
> Andersherum kann der Vektor nur "kopierbare" Sachen speichern, und
> kopiert die auch öfters mal. Klassen mit teurem Copy-Constructor packt
> mal also besser in eine Liste.

Auch eine std::list kann nur kopierbare Sachen speichern.

Jobst Q. schrieb:
> In std::string ist jeder String ein Objekt, das um ein vielfaches größer
> ist als ein Pointer.

Ja, man braucht einen Zeiger auf den Text und die Größe.

> Dafür muss Speicher alloziiert werden, selbst wenn der String leer ist.

Warum?

> Ein Array von mehreren tausend Strings, was wird das für ein Monster?

Hatte bisher kein Problem damit.

> Wenn ein String vergrößert wird, muss er realloziert werden und oft auch
> umkopiert werden.

Und bei einem char* ist das auf magische Weise nicht nötig? Oder 
schlimmer noch, bei deinen unten angegebenen Puffern, die gar nicht 
dynamisch allokiert sind. Da kannst du nicht reallokieren und hast 
halt einfach Pech gehabt, wenn mehr benötigt wird.

> Bei jeder Änderung müssen Membervariablen aktualisiert werden.

Was soll da groß aktualisiert werden? Die Größe eben.

> In C arbeitet man mit Puffern, die man statisch oder zu Beginn einer
> Funktion auf dem Stack anlegt.

So lange, bis dein Stack ohne Vorwarnung und ohne Erkennungsmöglichkeit 
überläuft.

> Ein Puffer kann mehrere Strings in Form von einfachen Pointern enthalten.
> So kann man eine Zeile aus einer Datei in den Puffer lesen und dann in
> einzelne Strings zerlegen, indem man Trennzeichen durch das Nullzeichen
> ersetzt.

Und wenn der Puffer zu klein ist? Wenn du beim Programmieren nicht gut 
aufgepast hast, ist dein Programm dann ein Sicherheitsleck. Wenn du gut 
aufgepasst hast, musst du halt einen Fehler melden a la: "Zeile zu lang. 
Ich hätt nicht gedacht, dass jemand eine so lange Zeile einlesen will. 
Da die maximale Größe hartkodiert ist, kann diese Datei leider nicht 
geöffnet werden. Viel Spaß bei der Suche nach einem flexibleren 
Programm."

Ja, dynamische Allokation ist langsamer. Aber gerade auf einem PC wirst 
du den Unterschied nur in sehr extremen Fällen merken. Da überwiegen für 
Standard-Anwendungen die Vorteile eines std::string einfach bei weitem.

> Beim Zusammensetzen von Strings kann man beliebige Strings und Zeichen
> aneinanderkopieren, solange noch Platz im Puffer ist.

Das kann man bei std::string auch. Und wenn kein Platz mehr ist, wird 
automatisch reallokiert. Man kann übrigens auch schon bei der Erzeugung 
des Strings angeben, wieviel Platz reserviert werden soll. Wenn man also 
vorher die Größe schon kennt, kann man ihn so anlegen, dass 
Reallokationen nicht gemacht werden.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
>
> Auch eine std::list kann nur kopierbare Sachen speichern.

Nein, es geht auch nur verschiebbar (nicht-kopierbar). Und das gilt auch 
für std::vector (und die anderen Container).

: Bearbeitet durch User
von Jobst Q. (joquis)


Lesenswert?

Dr. Sommer schrieb:
> Jobst Q. schrieb:
>> das um ein vielfaches größer
>> ist als ein Pointer.
> Korrekt. Es wird noch die Länge gespeichert, das musst du bei deinem
> Pointer ja auch, um Buffer Overflows zu vermeiden (oder rufst du jedes
> mal strlen auf - super effizient).

Damit zeigst du nur, wie wenig du von Stringbearbeitung und Pointern 
verstehst. Die Länge eines Strings braucht man höchst selten, und wenn, 
kann man sie meist durch eine simple Subtraktion der Anfangsadresse vom 
Endpointer berechnen.

Um Buffer Overflows zu vermeiden, braucht man nicht die Länge der 
Strings, sondern nur die Länge des Puffers oder besser einen Pointer auf 
das Ende des Puffers. Man braucht aber weit weniger Puffer als Pointer. 
Der Puffer ist das Spielfeld der Stringbearbeitung, die Pointer sind die 
Spieler, die sich darauf bewegen (können).

Bei Strings als Objekte braucht jeder Spieler sein eigenes Spielfeld, 
das macht es so ineffizient.


> Außerdem nutzen manche std::string
> Implementationen einen lokalen Buffer, d.h. sehr kurze strings werden
> direkt in std::string gespeichert. Dadurch wird der Zugriff deutlich
> schneller - ist also sogar effizienter als die Arbeit mit Pointern.

Die Derefenzierung eines Pointers braucht maximal 2 CPU-Befehle, wenn er 
als Register implementiert wird sogar nur einen. Effizienter geht es 
nicht.


> Jobst Q. schrieb:
>> Wenn ein String vergrößert wird, muss er
>> realloziert werden und oft auch umkopiert werden
> Und bei char* etwa nicht?

Nicht, wenn man den Puffer groß genug wählt. Und die Trennung zwischen 
Puffer und Pointer ist bei der Pointermethode viel klarer.

>
> Jobst Q. schrieb:
>> Bei jeder Änderung
>> müssen Membervariablen aktualisiert werden.
> Nur beim Reallokieren. Und da musst du bei char* deine explizit
> gespeicherte Größe auch aktualisieren.

Siehe oben.


> Jobst Q. schrieb:
>> die man statisch oder zu Beginn einer
>> Funktion auf dem Stack anlegt
> D.h. du hast nur sehr wenige, kleine strings (der Stack ist ja
> typischerweise klein).

Nein, ich verarbeite sehr viele Strings, da spielt die Effizienz schon 
eine Rolle. Meist sind es lange Strings aus Textdateien, die zerlegt 
werden in kurze Strings und evt wieder zu langen Strings zusammengesetzt 
werden.

Wenn der Stack typischerweise klein wäre, dürfte man auch keine 
rekursiven Funktionen wie qsort verwenden. Auf PC's hatte ich noch nie 
Stackprobleme. Auf Mikrokontrollern mag das anders sein.

> Damit sind deine Argumente sowieso hinfällig,
> weil die Laufzeitunterschiede (falls vorhanden) dann ohnehin
> verschwinden. Wie gesagt nutzt std::string für kleine Strings auch
> lokalen Speicher. Und wie genau vergrößerst du solche statisch
> allokierten Strings?

Ich alloziere eben keine Strings, sondern Puffer. Und die eben groß 
genug für die jeweilige Anwendung. Den Unterschied zwischen String, 
Puffer und Pointer solltest du dir mal klarmachen, wenn du hier über 
Stringbearbeitung diskutieren willst.

>
> Jobst Q. schrieb:
>> indem man
>> Trennzeichen durch das Nullzeichen ersetzt.
> Das geht bei std::string auch. Nur dass man bei std::string die
> tatsächliche Länge noch behält (während strlen ja dann nicht mehr
> funktioniert).
>
> Jobst Q. schrieb:
>> Beim Zusammensetzen von Strings kann man beliebige Strings und Zeichen
>> aneinanderkopieren, solange noch Platz im Puffer ist.
> Ja, bei std::string auch. Der kann ja auch über-allokieren.
>
> Jobst Q. schrieb:
>> Mit geeigneten Funktionen sind Buffer Overflows kein Problem
> Klar. Die muss man aber präzise und immer korrekt nutzen. Dass das viele
> Leute nicht schaffen sieht man daran, dass das Ursache Nr. 1 von
> Sicherheitslücken ist. Wenn du so ein Genie bist der das nie falsch
> macht ist das schön für dich, aber das sollte man nicht als musterhaftes
> Vorgehen verbreiten...

Was ist denn sonst musterhaftes Vorgehen? Sich blind auf vorgefertigte 
Funtionen und Objekte zu verlassen, ist es jedenfalls nicht.

Dass es soviele Buffer Overflows gibt, liegt an dem Fehldesign früher 
C-Standardfunktionen wie strcpy oder strncpy.  Besonders heimtückisch 
ist strncpy, da es ein begrenztes Kopieren suggeriert, aber im 
Begrenzungsfall keine Endnull schreibt und damit den String nicht 
abschließt. Wenn der so produzierte String ausgegeben wird, ist alles 
danach mit drin bis zur zufälligen nächsten Null.

Es gibt eine ganz einfache Regel, um Buffer Overflows zu verhindern: 
Jede schreibende Funktion braucht einen Parameter, in dem die Grenze des 
zu beschreibenden Puffers übergeben werden kann.

von Dr. Sommer (Gast)


Lesenswert?

Jobst Q. schrieb:
> Damit zeigst du nur, wie wenig du von Stringbearbeitung und Pointern
> verstehst.

Sind hier eigentlich letztens alle bescheuert? Überall wird man mit 
Halbwahrheiten bombardiert, und wenn man versucht diese auszurotten wird 
man nur noch beleidigt. Und das nicht nur am Freitag.

An deinem Beitrag ist so viel falsch, ich hab gar keine Lust darauf 
einzeln einzugehen. Ist auch schlecht für den Blutdruck. Ich kann dir 
nur raten mal über den Tellerrand zu blicken und zu schauen, wie 
std::string tatsächlich funktioniert.

von Wilhelm M. (wimalopaan)


Lesenswert?

Dr. Sommer schrieb:
> Jobst Q. schrieb:
>> Damit zeigst du nur, wie wenig du von Stringbearbeitung und Pointern
>> verstehst.
>
> Sind hier eigentlich letztens alle bescheuert? Überall wird man mit
> Halbwahrheiten bombardiert, und wenn man versucht diese auszurotten wird
> man nur noch beleidigt. Und das nicht nur am Freitag.

Bescheuert sind sicherlich nicht alle ...

Aber es ist schon bemerkenswert, wie in diesem (!) Forum sofort bei 
einigen der Blutdruck hoch und das Niveau herunter geht. Das kenne ich 
von nirgendwo anders. Scheint irgendwie auch ein bisschen typisch 
deutsch zu sein, diese Schulmeisterhaftigkeit! Wenn man es nicht ganz 
ernst nicht, ist es eigentlich auch wieder lustig ;-)

von Horst (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Sind hier eigentlich letztens alle bescheuert?

Nicht alle, hauptsächlich Jobst Quis. ;-)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rolf M. schrieb:
> Das schon. Dafür reserviert der Vektor in der Regel mehr Speicher als
> aktuell benötigt, um nicht bei jedem einzelnen Anhängen eines neuen
> Wertes komplett reallokieren zu müssen. Und wenn er reallokieren muss,
> braucht er temporär die bisher benötige Menge Speicher plus die neue
> Menge.
> Da kann also auch einiges an Overhead anfallen.

Ja, das ist ein Nachteil von std::vector, der oft übersehen wird. Er
führt dazu, dass ein std::vector selbst bei nichtfragmentiertem Speicher
schlimmstenfalls nur etwa ein Drittel desselben nutzen kann. Das liegt
daran, dass mit jeder Reallokation die Kapazität des Vektors verdoppelt
wird.

Folgendes Beispiel zeigt dies:

1
#include <iostream>
2
#include <vector>
3
#include <list>
4
5
struct elem_t {
6
  char a[10000]; // stellvertretend für einen etwas größere Datensatz,
7
                 // hier vereinfscht als Array dargestellt
8
};
9
10
#ifdef USE_LIST
11
std::list<elem_t> data;
12
#else
13
std::vector<elem_t> data;
14
#endif
15
16
int main() {
17
  elem_t elem;
18
19
  for(int i=0;; i++) {
20
    try {
21
      data.push_back(elem);
22
    }
23
    catch (std::bad_alloc)
24
    {
25
      std::cout << i << " sucessful push_backs\n";
26
      break;
27
    }
28
  }
29
}

Lässt man das Programm in seinen beiden Varienten auf einem 64-Bit-PC
mit GCC und einem mit

  ulimit -d 1920000

auf 1920000 kiB begrenzten Datensegment laufen, liefert es folgende
Ergebnisse:

Variante mit std::vector
1
65536 sucessful push_backs

Variante mit std::list
1
195963 sucessful push_backs

Bei fragmentiertem Speicher wird der Unterschied u.U. noch viel größer.

In dem von Dumdi Dum verlinkten Video von Stroustrup könnte der Eindruck
enstehen, dass std::list grundsätzlich schlecht ist. Die richtige Wahl
des Datentyps hängt aber, wie so oft, vom konkreten Anwendungsfall ab.
Für kleinere Datenmengen hat Stroustrup sicher recht, bei größeren
sollte man erst einmal die jeweiligen Vor- und Nachteile gegeneinander
abwägen.

Oft ist bei großen Datenmengen auch eine Kombination aus beiden, nämlich
eine Liste von Vektoren oder Arrays, die beste Lösung. IMHO benutzen die
meisten Textverarbeitungsprogramme eine solche Struktur für die interne
Textrepräsentation.

von Irgendein Benutzer (Gast)


Lesenswert?

Kleine Ergänzung:

Yalu X. schrieb:
> Das liegt daran, dass mit jeder Reallokation die Kapazität des Vektors
> verdoppelt wird.

Ist implementationsabhängig.

Wobei das keine Schikane der Bibliotheksschreiber ist, sondern dem 
Standard geschuldet (die vorgeschriebene "amortized constant time" bei 
push_back wäre sonst nicht möglich, glaube ich - darum gibt es hier 
keinen großen Spielraum).

https://stackoverflow.com/a/5232342

von Dumdi D. (dumdidum)


Lesenswert?

Yalu X. schrieb:
> Bei fragmentiertem Speicher wird der Unterschied u.U. noch viel größer.

Schönes Beispiel, danke. Ist das eigentlich Zufall, dass es genau 
65536=2^16 Mal bei vector geht?

von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:

> Lässt man das Programm in seinen beiden Varienten auf einem 64-Bit-PC
> mit GCC und einem mit
>
>   ulimit -d 1920000
>
> auf 1920000 kiB begrenzten Datensegment laufen, liefert es folgende
> Ergebnisse:
>
> Variante mit std::vector
>
>
1
> 65536 sucessful push_backs
2
>
>
> Variante mit std::list
>
>
1
> 195963 sucessful push_backs
2
>

Wer Mist misst Mist ;-)

Auch das ist eigentlich nur die halbe Wahrheit. Denn z.B. std::string 
ist selbst recht klein (SSO), die Daten liegen auf dem Heap. Etwa so wie 
diese Klasse:
1
template<size_t Size = 10000>
2
class A {
3
public:
4
    A() = default;
5
    A(const A&) = delete;
6
    A(A&&) = default;
7
    void swap(A& o) {
8
        using std::swap;
9
        swap(mData, o.mData);
10
    }
11
private:
12
    std::unique_ptr<uint8_t[]> mData = std::make_unique<uint8_t[]>(Size);
13
};
Solche Monster-Klassen wie Deine elem_t wird man (meistens) PImpl'n, so 
wie bei A. Dann ist das Vertauschen (s.o.swap()) billig und das 
Sortieren wird dadurch schnell. Was bei elem_t nicht gegen ist.

Damit ergibt sich:
1
196063 sucessful push_backs

von Michael B. (laberkopp)


Lesenswert?

Yalu X. schrieb:
> In dem von Dumdi Dum verlinkten Video von Stroustrup könnte der Eindruck
> enstehen, dass std::list grundsätzlich schlecht ist.

Eben.

Der Typ behauptet allen Ernstes, daß die lineare Suche durch statistisch 
die halbe Elementanzahl laufzeitbestimmend wäre,
und vernachlässigt, daß das Verschieben der Elemente beim insert ins 
Array ebenfalls statistisch die halbe Elementanzahl betrifft und weil es 
neben der Lese- auch eine Schreiboperation benötigt deutlich aufwändiger 
ist, zudem muss man die Stelle erst finden, eine binäre Suche läuft also 
auch nebem dem (tatsächlich vernachlässigbaren) realloc noch.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Irgendein Benutzer schrieb:
> Wobei das keine Schikane der Bibliotheksschreiber ist, sondern dem
> Standard geschuldet (die vorgeschriebene "amortized constant time" bei
> push_back wäre sonst nicht möglich, glaube ich - darum gibt es hier
> keinen großen Spielraum).

Statt um den Faktor 2 kann man die Kapazität auch jedesmal um einen
anderen konstanten Faktor >1 vergrößerrn. Ein großer Faktor ermöglicht
ein im Mittel schnelleres push_back, hat dafür aber einer schlechtere
Speichernutzung zur Folge. Der Faktor 2 scheint sich hier als guter
Kompromiss herausgebildet zu haben.

Das ändert aber nichts an der Tatsache, dass der Speicher-Overhead bei
std::vector proportional zur Gesamtgröße des Vektors, bei std::list aber
proportional zur Anzahl der Elemente ist. Der Speicher-Overhead pro
Element wächst also bei std::vector linear mit der Elementgtöße, während
er bei std::list konstant ist.

Dumdi D. schrieb:
> Schönes Beispiel, danke. Ist das eigentlich Zufall, dass es genau
> 65536=2^16 Mal bei vector geht?

Die Zahl hängt von der Speicher- und Elementgröße ab, ist aber immer bei
einem Reallokationsfaktor von 2 immer eine Zweierpotenz. Nach der
Befüllung von 32768 Elementen wird die Kapazität auf 65536 erhöht und so
lange weitergepusht, bis sie erneut erschöpft ist. Als nächstes würde
die Kapazität auf 2**17=131072 erhöht werden. Dazu müsste aber temporär
Platz für 65536+131072=196608 Elemente vorhanden sein. Da ich das
Speicherlimit aber genau auf diesen Wert eingestellt habe und der Vektor
ja auch noch einige Bytes Verwaltungsinformation belegt, klappt diese
Erhöhung nicht mehr.


Wilhelm M. schrieb:
> Wer Mist misst Mist ;-)
>
> Auch das ist eigentlich nur die halbe Wahrheit. Denn z.B. std::string
> ist selbst recht klein (SSO), die Daten liegen auf dem Heap. Etwa so wie
> diese Klasse:

Klar kann man das so machen. Dann ist aber der Geschwindigkeitsvorteil
durch den Cache und die eingesparten Dereferenzierungen wieder dahin, da
die Daten (in diesem Fall die String-Daten) nicht mehr zusammenhängend
im Speicher liegen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:

> Klar kann man das so machen. Dann ist aber der Geschwindigkeitsvorteil
> durch den Cache und die eingesparten Dereferenzierungen wieder dahin, da
> die Daten (in diesem Fall die String-Daten) nicht mehr zusammenhängend
> im Speicher liegen.

Was bei std::list dann auch der Fall ist ...
Man kann so eben keine pauschale Aussage treffen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> Man kann so eben keine pauschale Aussage treffen.

Da sind wir ja einer Meinung, denn

Yalu X. schrieb:
> Die richtige Wahl des Datentyps hängt aber, wie so oft, vom konkreten
> Anwendungsfall ab.

Ich will ja keineswegs die Vektoren verteufeln. Ganz im Gegenteil: Der
Vektor ist auch für mich der primäre Datentyp, um eine variable Anzahl
von Elements gleichen Typs in einer Variablen zusammenzufassen. Aber ich
möchte auch nicht, dass die armen Listen niedergemacht werden, denn auch
sie haben ihre Daseinsberechtigung.

von Jobst Q. (joquis)


Lesenswert?

Rolf M. schrieb:
> Jobst Q. schrieb:
>> Man nimmt std::string, wenn man wenig von Stringbearbeitung versteht und
>> gelegentlich mal einen einsetzen muss. Wenn man keine Angst vor Pointern
>> hat und sich damit auskennt, ist es mit char* wesentlich effizienter.
>
> Wer tut sich denn heute noch freiwillig String-Verarbeitung im C-Stil
> an? Von der Laufzeit her mag das effizienter sein, aber die
> umständliche, hakelige und fehlerträchtige Programmierung tue ich mir
> nur an, wenn's nicht anders geht.

Für jeden ist das einfach, was er kann und das schwierig, was er nicht 
kann.

Ich tu mir die Objektstrings nur dann an, wenn es nicht anders geht, zB 
weil eine GUI-Bibliothek das so verlangt. Das sind meist aber ziemlich 
triviale Aufgaben wie das Verketten von Strings.

Für das Zerlegen und Analysieren von Strings bietet std::string trotz 
der Vielfalt von Methoden, Members und Operatoren fast nichts, was mit 
Pointern nicht viel einfacher geht. Für meine Programme wie Compiler, 
Interpreter, Übersetzer und ähnliche bringt std::string keine Vorteile.

Ich hab nichts gegen Objektorientierung oder C++, aber Strings zu 
Objekten zu machen, ist kontraproduktiv. Ähnlichen Murks kenn ich noch 
aus meinen Anfängen im Programmieren von GWBASIC, wo Strings auch 
Variablen waren, auf die man nur mit speziellen Befehlen zugreifen 
konnte.

von Rolf M. (rmagnus)


Lesenswert?

Jobst Q. schrieb:
> Rolf M. schrieb:
>> Jobst Q. schrieb:
>>> Man nimmt std::string, wenn man wenig von Stringbearbeitung versteht und
>>> gelegentlich mal einen einsetzen muss. Wenn man keine Angst vor Pointern
>>> hat und sich damit auskennt, ist es mit char* wesentlich effizienter.
>>
>> Wer tut sich denn heute noch freiwillig String-Verarbeitung im C-Stil
>> an? Von der Laufzeit her mag das effizienter sein, aber die
>> umständliche, hakelige und fehlerträchtige Programmierung tue ich mir
>> nur an, wenn's nicht anders geht.
>
> Für jeden ist das einfach, was er kann und das schwierig, was er nicht
> kann.

Ich hab nicht geschrieben, es sei schwierig, nur umständlich, hakelig 
und fehlerträchtig. Wenn man es einmal verstanden hat, ist es nicht 
wirklich schwierig. Wobei es aber durchaus zu den größten Problemen 
gehört, die Anfänger mit der Sprache haben. Und mal ehrlich: Heutzutage 
sollte sowas grundlegendes wie der Umgang mit Strings eigentlich nicht 
mehr zu den größten Schwierigkeiten beim Erlernen einer Sprache gehören, 
zumindest nicht auf dem PC.

> Ich tu mir die Objektstrings nur dann an, wenn es nicht anders geht, zB
> weil eine GUI-Bibliothek das so verlangt. Das sind meist aber ziemlich
> triviale Aufgaben wie das Verketten von Strings.

Bei mir ist es genau umgekehrt: Sobald es über triviale Sachen 
hinausgeht, habe ich keine Lust, das mit C-Strings zu machen. Nicht weil 
ich es nicht verstehen würde, sondern weil es mir eben zu unkomfortabel 
ist.

> Für das Zerlegen und Analysieren von Strings bietet std::string trotz
> der Vielfalt von Methoden, Members und Operatoren fast nichts, was mit
> Pointern nicht viel einfacher geht. Für meine Programme wie Compiler,
> Interpreter, Übersetzer und ähnliche bringt std::string keine Vorteile.

Es gibt sicher Spezialfälle, in denen Standard-String-Klassen nicht 
wirklich passen. Und da ist es auch gut, dass man sowas auch komplett 
selber für den Anwendungsfall passend machen kann.
Dann würde ich mir aber eigene Klassen schreiben, die die "schmutzigen 
Details" hinter einem komfortablen Interface kapseln, so dass ich diese 
Teile nur einmal machen muss und nicht jedes Mal, wenn mir ein String 
über den Weg läuft.

von Jobst Q. (joquis)


Lesenswert?

Rolf M. schrieb:
> Bei mir ist es genau umgekehrt: Sobald es über triviale Sachen
> hinausgeht, habe ich keine Lust, das mit C-Strings zu machen. Nicht weil
> ich es nicht verstehen würde, sondern weil es mir eben zu unkomfortabel
> ist.
>
>> Für das Zerlegen und Analysieren von Strings bietet std::string trotz
>> der Vielfalt von Methoden, Members und Operatoren fast nichts, was mit
>> Pointern nicht viel einfacher geht. Für meine Programme wie Compiler,
>> Interpreter, Übersetzer und ähnliche bringt std::string keine Vorteile.
>
> Es gibt sicher Spezialfälle, in denen Standard-String-Klassen nicht
> wirklich passen. Und da ist es auch gut, dass man sowas auch komplett
> selber für den Anwendungsfall passend machen kann.
> Dann würde ich mir aber eigene Klassen schreiben, die die "schmutzigen
> Details" hinter einem komfortablen Interface kapseln, so dass ich diese
> Teile nur einmal machen muss und nicht jedes Mal, wenn mir ein String
> über den Weg läuft.

Der Komfort kommt durch geeignete Funktionen, die man sich eben selber 
schreiben muss, wenn sie nicht mitgeliefert werden. Das ist bei der 
Programmierung mit Pointern auch nicht anders.

Um einen String zu zerlegen habe ich zB eine häufig gebrauchte Funktion 
char* split(char *s,char c). Sie ersetzt das Trennzeichen auf Null und 
gibt den Pointer auf den Reststring zurück. Wenn kein Trennzeichen 
vorhanden ist auf die Null, was gleichbedeutend mit einem Leerstring 
ist.

Um zB einen String in zwei aufzuteilen, muss ich nur

sr=split(s,';');

schreiben. Was ist daran umständlich? Das bekommst du mit Objektstrings 
und Klassen auch nicht einfacher hin.

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Jobst Q. schrieb:

> Was ist daran umständlich? Das bekommst du mit Objektstrings
> und Klassen auch nicht einfacher hin.

Was ist denn eigentlich mit Unicode? Das wird dann nochmal 
unerfreulicher, sobald man nicht nur den Bytecount braucht, sondern auch 
die Zeichenzahl (Trivialbeispiel: Editor).

von Jobst Q. (joquis)


Lesenswert?

Nop schrieb:
> Jobst Q. schrieb:
>
>> Was ist daran umständlich? Das bekommst du mit Objektstrings
>> und Klassen auch nicht einfacher hin.
>
> Was ist denn eigentlich mit Unicode? Das wird dann nochmal
> unerfreulicher, sobald man nicht nur den Bytecount braucht, sondern auch
> die Zeichenzahl (Trivialbeispiel: Editor).

Solange die Trennzeichen normale 1:1 Zeichen sind, spielt das keine 
Rolle. Wie ich schon weiter oben geschrieben habe, ist die Stringlänge 
(Bytecount) nicht so wichtig, dass sie ständig mitgeführt werden müsste.

Der Unterschied zwischen Bytecount und Zeichenzahl ist nur in bestimmten 
Fällen der Darstellung von Bedeutung. Dann schreibt man eben dafür eine 
spezielle Funktion.

von Vlad T. (vlad_tepesch)


Lesenswert?

Dr. Sommer schrieb:
> Verkettete Listen sind aber gar nicht so sinnvoll wie einem der
> Informatikunterricht glauben machen möchte - weil sie über den ganzen
> Speicher verteilt sind nutzen sie den Cache nicht gut und sind daher
> eher langsam.  Oft sind array-artige Container wie std::vector
> sinnvoller.

naja, eine schlaue Implementierung reservert nicht für jedes Element 
einzeln den Speicher, sondern holt sich immer gleich Speicher für 
mehrere Elemente. Damit sitzen dann mehrere Elemente dann auch 
cachegünstiger nebeneinander.

Die zusätzliche Indirektion beim iterieren bleibt natürlich trotzdem.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.