Martin Sulzmann
C++ kompakt
Weitere Themen wie Polymorphie, kopieren von Objekten/Zeigern etc werden separat behandelt.
C++ erlaubt
Variabledeklarationen gemischt mit Programmcode
geschachtelte Codeblöcke
Im Vergleich dazu in C müssen
alle Variablendeklarationen am Funktionsanfang stehen
keine Schachtelung von Codeblöcken
weil so ist es für den Compiler einfacher das Program zu analysieren. Damals, als C geboren wurde, war das entwickeln eines Compilers keine einfache Sache.
Folgendes ist ein legales C++ Programm (aber nicht legal in C).
int main() {
int x = 0;
int j = 1;
for(int i=0; i < j; i++) {
{
float x;
x = 1.0 + i;
}
}
return 0;
}
Aufgabe: Wandeln Sie obiges Program in ein äquivalentes C Program um.
IO Stream = Ein-/Ausgabe Strom = Sequenz von Ein-/Ausgaben
#include <iostream>
#include <string>
using namespace std;
int main(void) {
cout << "Ihr Name bitte: \n";
string s;
cin >> s;
cout << "Aha, Sie heissen ";
cout << s << endl;
}
<<
Ausgabestrom>>
Eingabestromendl
int x;
float y;
printf("Int %d Float %f", x, y);
cout << "Int " << x << "Float " << y;
In C, rein dynamische Analyse des Formatstrings.
In C++, statische Analyse, ermöglicht Dank der Überladung von <<
.
Betrachte
int x;
float y;
cout << x << y;
Operator <<
ist links-assoziativ. Obiges interpretiert als
(cout << x) << y;
Zwei verschiedene Instanzen von <<
: int
und float
.
ostream& operator<< (ostream &out, int &i);
ostream& operator<< (ostream &out, float &i)
Was bedeutet int &i
(ostream &out
, ...)?
Übergabe (und auch Rückgabe) als Referenz (wie in Java). Intern dargestellt durch Zeiger.
cout << "Eingabe von Integer: ";
cin >> x;
// cin erwartet Referenzparameter
cout << "Sie haben eingegeben: " << x;
printf("Eingabe von Float: ");
scanf("%f", &y);
// scanf erwartet die Referenz explizit repraesentiert als Zeiger
printf("Sie haben eingegeben: %f",y);
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main(void) {
cout << "Ihr Name bitte: \n";
string s;
cin >> s;
cout << "Aha, Sie heissen ";
cout << s << endl;
int x = 1;
float y = 2.0;
printf("Int %d Float %f", x, y);
cout << "Int " << x << " Float " << y;
// In beiden Faellen erhalten wir die Ausgabe: Int 1 Float 2.0
printf("Int %d Float %f", y, x);
// Warning! y passt nicht auf %d !!!
cout << "Int " << y << " Float " << x;
// Ausgabe liefert: Int 2.0 Float 1
// Dies ist nur ein logischer Fehler.
cout << "Eingabe von Integer: ";
cin >> x;
// cin erwartet Referenzparameter
cout << "Sie haben eingegeben: " << x;
printf("Eingabe von Float: ");
scanf("%f", &y);
// scanf erwartet die Referenz explizit repraesentiert als Zeiger
printf("Sie haben eingegeben: %f",y);
cout << "\n Das war's" << endl;
}
Namensraum effektiv eine Klassendeklaration mit genau einer Instanz.
namespace NAME { void foo(void); ... }
NAME::foo();
using namespace NAME;
foo();
#include <iostream>
#include <string>
// using namespace std;
int main(void) {
std::cout << "Ihr Name bitte: \n";
std::string s;
std::cin >> s;
std::cout << "Aha, Sie heissen ";
std::cout << s << std::endl;
}
#include <iostream>
#include <string>
// using namespace std;
int main(void) {
std::cout << "Ihr Name bitte: \n";
string s;
...
}
namespace.cpp: In function ‘int main()’:
namespace.cpp:6: error: ‘string’ was not declared in this scope
namespace.cpp:6: error: expected `;' before ‘s’
namespace.cpp:7: error: ‘s’ was not declared in this scope
Konstruktoren (optional), Destruktor (optional), Methoden und Instanzvariablen (="member")
Wir schauen uns die wichtigsten Konzepte an
this
, new
, delete
#include <iostream>
using namespace std;
class Point {
// private:
float x, y;
public:
Point() {} // Default Konstrukor, implizit vorhanden
Point(float x2, float y2) {
x = x2; y = y2;
}
~Point() {
cout << "das war's" << endl;
}
void scale(float f) {
x = f * x; y = f * y;
}
}; // anders wie in Java
int main() {
Point p = Point(1.0,2.0);
}
Mehrere Statements pro Zeile aus Platzgründen
Beachte.
Objekt p
wird auf dem Stackbereich der main
Funktion angelegt.
Stack wird automatisch verwaltet.
Deshalb wird Destrukur von p
bei verlassen von main
aufgerufen.
Siehe dazu auch C - Teil 3.
Stack: Verwaltet Funktionsaufrufe und deren lokale Variablen.
Verwaltung (anlegen und löschen) der Daten auf dem Stack ist einfach. Bei Funktionsaufruf wird neuer Stackbereich für Funktion und deren lokale Variablen angelegt. Bei Funktionsrücksprung (return) wird dieser Stackbereich gelöscht.
Heap: Verwaltet dynamisch angelegte Daten.
In der Regel via new
angelegt. In Java wird der Heap automatisch verwaltet (sobald keine Referenz mehr auf die dynamisch angelegten Daten existiert werden diese glöscht). In C++ müssen Daten auf den Heap explizit via delete
gelöscht werden.
Sprachgebrauch: Löschen von Daten. Bedeutet der von den Daten belegte Speicherbereich kann durch andere Daten belegt werden.
Sprachgebrauch: Stack versus Heap Objekte. In C++ können Objekte auf dem Stack als auch dem Heap angelegt werden. In Java werden alle Objekte auf dem Heap angelegt.
In der Regel aufgeteilt in Schnittstelle (header) und Implementierung (source)
point2.h
#ifndef __POINT__
#define __POINT__
class Point {
float x, y;
public:
Point(float x2, float y2);
~Point();
void scale(float f);
};
#endif // __POINT__
point2.cpp
#include "point2.h"
#include <iostream>
using namespace std;
Point::Point(float x2, float y2) {
x = x2; y = y2;
}
Point::~Point() {
cout << "das war's" << endl;
}
void Point::scale(float f) {
x = f * x; y = f * y;
}
mainPoint.cpp
#include "point2.h"
int main() {
Point p = Point(1.0,2.0);
}
Header/Source Files in entsprechende Ordner (im Falle von Visual Studio)
Bauen via der Kommandozeile
> g++ -c point2.cpp
> g++ -c mainPoint.cpp
> g++ point2.o mainPoint.o -o mainPoint.exe
class Point {
...
Point(int z = 0);
Point(float x2, float y2);
};
Überladung des Konstruktors
Auswahl des Kandidaten muss eindeutig sein
Default Parameter immer von links
Überladung und Default Parameter kann auch auf Klassenmethoden und Funktionen angewandt werden
Beispiel
#include <iostream>
using namespace std;
class Point {
float x, y;
public:
Point(int z = 0) {
x = z; y = z;
}
Point(float x2, float y2) {
x = x2; y = y2;
}
~Point() {
cout << "das war's " << endl;
}
void scale(float f) {
x = f * x; y = f * y;
}
};
int main() {
Point p = Point(1.0,2.0);
Point p2 = Point(1);
Point p3;
}
new
, delete
Beachte. Standardmässig werden in C++ alle Objekte auf dem Stack (Laufzeitstack) angelegt. Die Verwaltung von Stack Objekten ist einfach. Bei Funktionsaufruf wird neuer Stackbereich für Funktion und deren lokale Variablen/Objekte angelegt. Bei Funktionsrücksprung (return) wird dieser Stackbereich gelöscht.
Wir betrachten hier dynamische (Zeiger) Objekte und deren Operationen:
new
: Allokiere Speicher, anlegen des dynamischen Objekts durch Konstruktoraufrufdelete
: Destruktoraufruf, gefolgt von freigeben des Speichers des dynamischen Objekts.this
: Zugriff auf member, nur innerhalb von nicht-statischen Membernint
ObjektenWiederholung von bekannten C Konzepten im C++ Gewand.
void f(void) {
int x = 2;
int* p = new int(3);
}
x
ist eine Integervariable
p
ist eine Zeigervariable, verweist auf Integerwerte
new int(3)
p
Beachte:
Speicher belegt von Variablen x
und p
wird aufgeräumt, sobald die Funktion verlassen wird.
Momentan mal, p
verweist auf eine Speicheradrese. Dieser Speicher wird nicht automatisch aufgeräumt.
In C++ ist der Programmierer verantwortlich für das Aufräumen von dynamisch (durch new
) angelegten Speichers mit Hilfe von delete
:
void f(void) {
int x = 2;
int* p = new int(3);
delete p;
}
class MyInt {
public:
MyInt(int x = 0) {
p = new int(x);
}
~MyInt() { delete p; }
private:
int* p;
};
MyInt
beschreibt Integerwerte
Interne Repräsentation als Zeiger auf Integerwert.
Beispiel:
void g(void) {
MyInt i1;
MyInt i2 = MyInt(3);
}
i1
entspricht 0 (Konstruktor mit Defaultparameter!) und i2
entspricht 3
Speicher belegt von i1
und i2
wird aufgefäumt nach Verlassen der Funktion
Was passiert mit dem innerhalb des Konstruktors (dynamisch) angelegten Speichers? Fehlt nicht ein delete
? Richtig. Aber wo und wann?
Konvention/Vorgehen:
delete p
innerhalb des Destruktors von MyInt
Betrachte
void h(void) {
MyInt i;
MyInt* p2 = new MyInt(3);
}
i
ein Objekt der Klasse MyInt
(mit Integerwert 0)
p
ein Zeiger auf ein Objekt der Klasse MyInt
Speicherlayout von MyInt* p2 = new MyInt(3);
------------- ----------- -----
| MyInt* p2 | -----> | int* p | ----> | 3 |
------------- ---------- -----
Was passiert bei Verlassen der Funktion h
?
Speicher belegt von i
wird freigegeben. Destruktor wird auf Objekt i
aufgefrufen
Speicher belegt von p2
wird freigegeben.
Was ist mit dem dynamischen angelegten Speicher durch new MyInt(3)
? Erinnerung: delete
muss manuell aufgerufen werden (was hier nicht geschieht).
Deshalb wird der Speicher nicht freigegeben und auch nicht der Destruktor aufgerufen.
Zusammengefasst wird haben ein Speicherleck: Der von new MyInt(3)
und new int(x)
(wobei x
gleich 3) belegte Speicher wird nicht freigeben.
Vermeiden des Speicherlecks durch explizites delete
:
void h2(void) {
MyInt i;
MyInt* p2 = new MyInt(3);
delete p2;
}
SomeClass* c = new SomeClass[5]; // Zeiger auf erstes Element
delete[] c; // NICHT delete c
Für jedes Element zuerst Aufruf Destruktor, dann geben Speicher frei.
int** ptr = new int*[4];
for (int i=0; i<4; i++)
ptr[i] = new int(i);
// Freigabe Speicherplatz auf die Pointer verweisen
for (int i=0; i<4; i++)
delete ptr[i];
delete[] ptr;
// Freigabe der durch die Pointer belegte Speicherplatz
Betrachte
class Stack {
public:
Stack(int size) {
this->size = size; st = new char[size];
}
~Stack() { delete st; }
private:
char* st; int size;
};
int main() {
Stack* s = new Stack(5);
delete s; // Geschachtelter delete Aufruf!
Stack* s1 = new Stack(5);
}
Warnung: Programm ist buggy. Erklärung folgt.
Hier die korrigierte Version.
class Stack {
public:
Stack(int size) {
this->size = size; st = new char[size];
}
~Stack() {
// delete st;
delete[] st; // Loeschen des gesamten Arrays
}
private:
char* st; int size;
};
int main() {
Stack* s = new Stack(5);
delete s; // Geschachtelter delete Aufruf!
Stack* s1 = new Stack(5);
delete s1; // Manuelle Speicherverwaltung
}
Stack* s = new Stack(5);
________________
| int size = 5 |
---------------- -----------------------
| int* size | ---------------> | Array der Groesse 5 |
----------------- -----------------------
delete s
int main() {
Stack* s1 = new Stack(5);
Stack s2 = Stack(5);
delete s1;
}
new
im Zusammenhang von dynamischen (Zeiger) Objekten
delete s1
Destruktor von s2
wird aufgerufen, sobald der Speicherbereich belegt von s2
gelöscht wird (am Ende der main
Funktion)
int main() {
Stack* s1 = new Stack(5);
// delete s1;
}
int main() {
Stack* s1 = new Stack(5);
Stack* s2 = s1;
delete s1;
delete s2;
}
Kontrolle der der Zugriffsrechte auf Methoden und Variablen via protected
, private
and public
Basisklasse
class A
{
public:
int x; // Alle
protected:
int y; // Nur Kinder
private: // Nur A
int z;
};
public
class B : public A
{
// x ist public
// y ist protected
// z kein Zurgriff via B
};
protected
class C : protected A
{
// x ist protected
// y ist protected
// z kein Zugriff via C
};
private
class D : private A
{
// x ist private
// y ist private
// z kein Zugriff via D
};
private
Memberfriend
Angabe gewährt Zugriff, darf sowohl im privaten als auch öffentlichen Teil einer Klasse stehen. class Vector {
friend class Matrix; // Alle Methoden von Matrix duerfen
// auf den folgenden Bereich
// zugreifen
};
class Vector {
friend int Matrix::mult(Vector& v);
// Methode mult(Vector& v) von
// Matrix darf zugreifen
};
class Vehicle {
int wheels;
public:
Vehicle(int w=4):wheels(w) {}
int max() {return 60;}
};
class Bicycle : public Vehicle {
bool panniers;
public:
Bicycle(bool p=true):Vehicle(2),panniers(p) {}
int max() {return panniers ? 12 : 15;}
};
Bicycle
ruft Superklassenkonstruktor Vehicle
aufpanniers
mit Wert p
Java:
Alle Methoden sind virtuell.
Methodenauswahl basierend auf dem dynamischen Typ des Objektes.
C++:
Zwei Arten von Methoden: Statische und virtuelle Methoden.
Statische Methodenauswahl basierend auf dem statischen Typ des Objektes (wie er im Programmtext steht).
Dynamische Methodenauswahl wie in Java (aber dazu müssen Objekte auf dem Heap angelegt sein).
max
in Basisklasseclass Vehicle {
...
int max() {return 60;}
};
max
in Subklasse (abgeleitete Klasse)class Bicycle : public Vehicle {
...
int max() {return panniers ? 12 : 15;}
};
max
Methode wird aufgerufen?void print_speed(Vehicle &v, Bicycle &b) {
cout << v.max() << " " << b.max() << "\n";
}
int main() {
Bicycle b = Bicycle(true);
print_speed(b,b);
}
Ausgabe 60 12
und nicht 12 12
Per Default, Memberfunktionen werden ausgewählt basierend auf dem statischen Typ
Bicyle
print_speed
erwartet (statisch) als erstes Argument Vehicle
C++ verhält sich verschieden von Java.
In C++ ist per Default die Methodenauswahl basierend auf dem statischen Typ ("statische Methode")
virtual
Methoden garantieren dynamische Methodenauswahl (dann Verhalten wie bei Java)
class Vehicle {
int wheels;
public:
Vehicle(int w=4):wheels(w) {}
virtual int max() {return 60;} // virtuelle Methode
};
class Bicycle : public Vehicle {
bool panniers;
public:
Bicycle(bool p=true):Vehicle(2),panniers(p) {}
int max() {return panniers ? 12 : 15;}
};
virtual
nur in Basisklasse notwendigvoid print_speed(Vehicle* v, Bicycle* b) {
cout << v->max() << " " << b->max() << "\n";
}
int main() {
Bicycle* b = new Bicycle(true);
print_speed(b,b);
delete b;
}
Ausgabe jetzt 12 12
(Virtuelle) Methodenauswahl basierend auf dem dynamischen Typ
Für virtuelle Methodenauswahl müssen Objekte dynamisch anlegt werden.
#include <iostream>
#include <string>
using namespace std;
// Zwei Namespaces um Konflikte zu vermeiden.
namespace StaticMethod {
class Vehicle {
int wheels;
public:
Vehicle(int w=4):wheels(w) {}
int max() {return 60;}
};
class Bicycle : public Vehicle {
bool panniers;
public:
Bicycle(bool p=true):Vehicle(2),panniers(p) {}
int max() {return panniers ? 12 : 15;}
};
void print_speed(Vehicle &v, Bicycle &b) {
cout << v.max() << " " << b.max() << "\n";
}
void test() {
Bicycle b = Bicycle(true);
print_speed(b,b);
}
} // Namespace StaticMethod
namespace VirtualMethod {
class Vehicle {
int wheels;
public:
Vehicle(int w=4):wheels(w) {}
virtual int max() {return 60;} // virtuelle Methode
};
class Bicycle : public Vehicle {
bool panniers;
public:
Bicycle(bool p=true):Vehicle(2),panniers(p) {}
int max() {return panniers ? 12 : 15;}
};
void print_speed(Vehicle* v, Bicycle* b) {
cout << v->max() << " " << b->max() << "\n";
}
void test() {
Bicycle* b = new Bicycle(true);
print_speed(b,b);
delete b;
}
} // Namespace VirtualMethod
int main() {
StaticMethod::test();
VirtualMethod::test();
}
class Shape {
public:
virtual void draw() = 0;
};
Shape
kann nicht definiert werdenÄhnlich wie generics in Java.
template<typename T>
class Elem {
public:
T x;
Elem(T x) { this->x = x; }
};
template<typename T>
void replace(Elem<T> e, T x) {
e.x = x;
}
int main() {
Elem<int> e = Elem<int>(1);
replace<int>(e, 2);
}
Auch Funktion können Template Parameter haben
template<typename A, typename B>
class Pair {
private:
A a;
B b;
public:
Pair(A a2, B b2) : a(a2), b(b2) {}
A fst() { return a;}
B snd() { return b;}
};
int main() {
Pair<int,float> p = Pair<int,float>(1,2.0);
Pair<bool,Pair<int,float> > p2 = Pair<bool,Pair<int,float> >(true,p);
}
> >
wichtig weil Parser sonst mit Stream Notation durcheinander kommt >>
class Point {
...
static int counter;
static int getCounter() {
return counter;
}
}
Point
referenziert auf eine Instanz von counter
int Point::counter = 0;
Statische Methode darf nicht abhängig von nicht-statischen Werten sein
Zugriff von ausserhalb
cout << Point::counter;
cout << Point::getCounter();
#include <iostream>
using namespace std;
class Point {
float x, y;
static int counter;
public:
Point(float x2, float y2) {
x = x2; y = y2; counter++;
}
static int getCounter() {
return counter;
}
};
int Point::counter = 0;
int main() {
Point p = Point(1.0,2.0);
Point p2 = Point(1.0,2.0);
cout << "Anzahl von Points:" << Point::getCounter() << endl;
}
struct
versus class
struct
: Alle Member sind public
class
: Alle Member per Default private
const
float Point::getX() const { return x; }
class Some {
public:
Other x;
Other z;
Some (Other y) { x = y; z = y; }
};
class Some {
public:
const Other x;
Other& z;
Some (Other y) : x(y), z(y) {}
// Some (Other y) { x = y; z = y; }
// geht hier nicht
};
Wie Beispiel zeigt, Initialisierung als Teil des Konstruktoraufrufs notwendig falls const oder reference Member.
class Super {
...
Super(int) {}
};
class Sub : public Super {
...
Sub(int x) : Super(x) {}
};
Super
Sub
SuperSuper
SubSub
#include<iostream>
using namespace std;
class Super {
public:
Super() { cout << "Super" << endl; }
Super(int) { cout << "SuperSuper" << endl; }
};
class Sub : public Super {
public:
Sub() { cout << "Sub" << endl; }
Sub(int x) : Super(x) { cout << "SubSub" << endl; }
};
int main() {
Sub x;
Sub y = Sub(1);
}
explicit
KonstruktorVermeidet implizite Typkonvertierung bei Konstruktoraufruf
class C {
public:
C(int x) : y(x) {}
// explicit C(int x) : y(x) {}
int y;
};
void f(C c) {}
int main() {
f(1);
}
f
erwartet ein C, aber wir übergeben eine <explicit
vermieden werden
#include <iostream>
using namespace std;
class Base {
public:
Base() {cout<<"BaseKon\n";}
~Base() {cout<<"BaseDe\n";}
// virtual ~Base() {cout<<"BaseDe\n";}
};
class Derived : public Base {
public:
Derived() {cout<<"DerivedKon\n";}
~Derived() {cout<<"DerivedDe\n";}
};
int main()
{
Base* base = new Derived();
delete base;
}
BaseKon
DerivedKon
BaseDe
BaseKon
DerivedKon
DerivedDe
BaseDe
void increment(int& x) {
x++;
}
int& x
deklarierte Referenzübergabevoid increment2(int* x) {
*x++;
}
void increment(int& x) {
x++;
}
int main() {
int x;
increment(x);
increment2(&x);
}
Implizite Seiteneffekte durch Pointer und Referenzen
increment2(&x); // Adressoperator
void print(const bigData& x) {
...
}
+, *, =, ++, <<, [], ...
MyInt
class MyInt {
friend ostream& operator<< (ostream &out, MyInt &i);
public:
MyInt(int x = 0) {
p = new int(x);
}
~MyInt() { delete p; }
void operator++() {
(*p)++;
}
private:
int* p;
};
ostream& operator<< (ostream &out, MyInt &i) {
out << *i.p;
return out;
}
}
Operator als friend
weil Zugriff auf private Elemente.
Verwendung:
void f2(void) {
MyInt i1 = MyInt(1);
++i1;
cout << i1;
}
Beachte:
Operator ++
hier als Prefixoperator (Details + Erklärung siehe unten)
Frage: Wieso Referenzparameter für MyInt in ostream& operator<< (ostream &out, MyInt &i)
Echt trickreich. "Recall": Kopieren mit Pointern.
Angenommen wir verwenden
ostream& operator<< (ostream &out, MyInt i) { // Keine Referenze auf i !!!!
out << *i.p;
return out;
}
Destruktor wird auf i
aufgerufen was zu delete p
führt.
Durch den call-by value Aufruf cout << i2
, teilen sich i
und i2
den gleichen Zeiger!
Deshalb wird nach Rücksprung aus f2
, noch einmal delete p
ausgeführt, wobei p
auf die gleiche Adresse verweist!
struct position {
int x;
float y;
};
position& operator++(position& p) { // Prefix
p.x++;
return p;
}
position& operator++(position& p, int dummy) { // Postfix
// trick: p ++ dummy
p.x++;
return p;
}
Notation
lhs = cast<type>(rhs)
static_cast
ersetzt C-style castsdynamic_cast
nur für Ponter und Referenzen, dynamischer Check, kann fehlschlagenreinterpret_cast
nur für Ponter und Referenzen, ohne dynamischen CheckCasts vermeiden soweit es geht
Weitere Infos: http://www.cplusplus.com/doc/tutorial/typecasting/
for(int i=0; i < 10; i++) {
{ bool i=true;
{ char i = i ? 'a' : 'b';
// character i sichtbar
}
// bool i sichtbar
}
// int i sichtbar
}
i
Mutex m;
m.lock();
// do something exclusively
m.unlock();
{
Lock l;
// do something exclusively
} // Aufruf Destruktor ==> unlock Objekt l
class C {
public:
// ...
private:
enum E { Tag1, Tag2 };
class D {
// ...
};
};
Ausnahmebehandlung im Falle von z.B. Null Pointer, Speicher ist leer, File kann nicht geöffnet werden, Division durch 0 etc.
void f() { ... throw DivZeroError(); ... }
int main {
try {
f();
}
catch (DivZeroError) {
//handle error
}
throw
löst Ausnahme austry
Bereich in dem Ausnahme stattfindetcatch
sucht nach passender Ausnahmebedingung
#include <iostream>
using namespace std;
void compute(int x, int y, int z) {
if(y == z) throw 0;
if(y > z) throw 'a';
if(y < z) throw 1.0;
float f = x / (y-z);
cout << "f= " << f << "\n";
}
int main() {
try{ compute(5,3,3); }
catch(int i) { cout << "Exception " << i << "\n"; }
catch(char) { cout << "Exception char \n"; }
}
Implementierung benützt so ziemlich alle C++ Features (Templates, Operatorenüberladung, ...)
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<string> v;
cout << "Bitte zu sortierenden Text eingaben, \n"
<< "Stop mit \"stop\" \n";
for(;;) {
string s;
getline(cin,s);
if(s == "stop")
break;
v.push_back(s);
}
sort(v.begin(), v.end());
cout << "Text nach Sortierung: \n";
for(int i = 0; i<v.size(); i++)
cout << v[i] << "\n";
}
#include <vector>
...
vector<string> v;
...
v.push_back(s);
#include <algorithm>
...
sort(v.begin(), v.end());
...
v.begin()
Position des ersten Elementsv.end()
Position hinter letztem Element for(int i = 0; i<v.size(); i++)
cout << v[i] << "\n";
vector<string>::iterator iter;
for(iter = v.begin(); iter != v.end(); iter++)
cout << *iter << "\n";
vector<string>
++
und *