Ich bin neu in der C/C++ - Programmierung, aber ich programmiere bereits seit 1,5 Jahren in C #. Ich mag C # und ich mag die List-Klasse. Daher dachte ich daran, eine List-Klasse in C++ als Übung zu erstellen.
List<int> ls;
int whatever = 123;
ls.Add(1);
ls.Add(235445);
ls.Add(whatever);
Die Implementierung ähnelt einer beliebigen Array List-Klasse. Ich habe ein T* vector
-Mitglied, in dem ich die Artikel ablege, und wenn dieser Speicher vollständig gefüllt ist, verkleinere ich ihn.
Bitte beachten Sie, dass dies nicht in der Produktion verwendet werden darf, dies ist nur eine Übung. Ich kenne vector<T>
und Freunde gut.
Jetzt möchte ich die Elemente meiner Liste durchlaufen. Ich benutze for(int i=0;i<n; i==)
nicht gern. Ich habe im Visual Studio for
eingetippt und auf Intellisense gewartet. Das hat mir folgendes nahegelegt:
for each (object var in collection_to_loop)
{
}
Dies funktioniert offensichtlich nicht mit meiner List-Implementierung. Ich dachte mir, ich könnte Makro-Magie machen, aber das fühlt sich an wie ein riesiger Hack. Was mich am meisten stört, ist, den Typ so weiterzugeben:
#define foreach(type, var, list)\
int _i_ = 0;\
##type var;\
for (_i_ = 0, var=list[_i_]; _i_<list.Length();_i_++,var=list[_i_])
foreach(int,i,ls){
doWork(i);
}
Meine Frage ist: Gibt es eine Möglichkeit, diese benutzerdefinierte List-Klasse mit einer foreach-like
-Schleife arbeiten zu lassen?
Erstens unterscheidet sich die Syntax einer for-each
-Schleife in C++
von C#
(sie wird auch als range based for loop
bezeichnet. Sie hat die Form:
for(<type> <name> : <collection>) { ... }
Bei einem std::vector<int> vec
wäre es zum Beispiel so:
for(int i : vec) { ... }
Unter den Deckblättern verwendet dies effektiv die Memberfunktionen begin()
und end()
, die Iteratoren zurückgeben. Damit Ihre benutzerdefinierte Klasse eine for-each
-Schleife verwenden kann, müssen Sie eine begin()
- und eine end()
-Funktion bereitstellen. Diese sind im Allgemeinen überladen und geben entweder eine iterator
oder einen const_iterator
zurück. Die Implementierung von Iteratoren kann schwierig sein, obwohl es bei einer vektorähnlichen Klasse nicht zu schwer ist.
template <typename T>
struct List
{
T* store;
std::size_t size;
typedef T* iterator;
typedef const T* const_iterator;
....
iterator begin() { return &store[0]; }
const_iterator begin() const { return &store[0]; }
iterator end() { return &store[size]; }
const_iterator end() const { return &store[size]; }
...
};
Mit diesen Implementierungen können Sie wie oben beschrieben eine bereichsbasierte Schleife verwenden.
Sei iterable
vom Typ Iterable
. Dann, um zu machen
for (Type x : iterable)
kompilieren, es muss Typen geben, die als Type
und IType
bezeichnet werden, und es muss Funktionen geben
IType Iterable::begin()
IType Iterable::end()
IType
muss die Funktionen bereitstellen
Type operator*()
void operator++()
bool operator!=(IType)
Die gesamte Konstruktion ist wirklich raffinierter syntaktischer Zucker für so etwas
for (IType it = iterable.begin(); it != iterable.end(); ++it) {
Type x = *it;
...
}
dabei kann anstelle von Type
ein beliebiger kompatibler Typ (z. B. const Type
oder Type&
) verwendet werden, der die erwarteten Auswirkungen hat (Konstanz, Referenz statt Kopie usw.).
Da die gesamte Erweiterung syntaktisch erfolgt, können Sie auch die Deklaration der Operatoren ein wenig ändern, z. mit * es gibt eine Referenz zurück oder mit! = nehmen Sie nach Bedarf einen const IType& rhs
.
Beachten Sie, dass Sie das Formular for (Type& x : iterable)
nicht verwenden können, wenn *it
keine Referenz zurückgibt (wenn jedoch eine Referenz zurückgegeben wird, können Sie auch die Kopierversion verwenden).
Beachten Sie auch, dass operator++()
die prefix -Version des ++
-Operators definiert. Sie wird jedoch auch als Postfix-Operator verwendet, sofern Sie nicht ausdrücklich ein Postfix ++
definieren. Das Ranging-for wird nicht kompiliert, wenn Sie nur ein Postfix ++
angeben, das btw. als operator++(int)
(dummy int-Argument) deklariert werden kann.
Minimales Arbeitsbeispiel:
#include <stdio.h>
typedef int Type;
struct IType {
Type* p;
IType(Type* p) : p(p) {}
bool operator!=(IType rhs) {return p != rhs.p;}
Type& operator*() {return *p;}
void operator++() {++p;}
};
const int SIZE = 10;
struct Iterable {
Type data[SIZE];
IType begin() {return IType(data); }
IType end() {return IType(data + SIZE);}
};
Iterable iterable;
int main() {
int i = 0;
for (Type& x : iterable) {
x = i++;
}
for (Type x : iterable) {
printf("%d", x);
}
}
ausgabe
0123456789
Sie können den Bereich für jeden (etwa für ältere C++ - Compiler) mit dem folgenden Makro vormachen:
#define ln(l, x) x##l // creates unique labels
#define l(x,y) ln(x,y)
#define for_each(T,x,iterable) for (bool _run = true;_run;_run = false) for (auto it = iterable.begin(); it != iterable.end(); ++it)\
if (1) {\
_run = true; goto l(__LINE__,body); l(__LINE__,cont): _run = true; continue; l(__LINE__,finish): break;\
} else\
while (1) \
if (1) {\
if (!_run) goto l(__LINE__,cont);/* we reach here if the block terminated normally/via continue */ \
goto l(__LINE__,finish);/* we reach here if the block terminated by break */\
} \
else\
l(__LINE__,body): for (T x = *it;_run;_run=false) /* block following the expanded macro */
int main() {
int i = 0;
for_each(Type&, x, iterable) {
i++;
if (i > 5) break;
x = i;
}
for_each(Type, x, iterable) {
printf("%d", x);
}
while (1);
}
(Verwenden Sie declspec oder übergeben Sie IType, wenn Ihr Compiler nicht einmal über "auto" verfügt).
Ausgabe:
1234500000
Wie Sie sehen, funktionieren continue
und break
dank ihrer komplizierten Konstruktion. Unter http://www.chiark.greenend.org.uk/~sgtatham/mp/ finden Sie weitere C-Pre-Prozessor-Hacks, um benutzerdefinierte Kontrollstrukturen zu erstellen.
Diese von Intellisense vorgeschlagene Syntax ist nicht C++. oder es ist eine MSVC-Erweiterung.
In C++ 11 gibt es range-basierte for-Schleifen zum Durchlaufen der Elemente eines Containers. Sie müssen für Ihre Klasse begin()
- und end()
-Memberfunktionen implementieren, die Iteratoren an das erste Element und einen nach dem letzten Element zurückgeben. Das bedeutet natürlich, dass Sie auch für Ihre Klasse geeignete Iteratoren implementieren müssen. Wenn Sie wirklich auf diese Route gehen möchten, sollten Sie Boost.IteratorFacade ; Es reduziert den Aufwand für die Implementierung von Iteratoren.
Danach kannst du folgendes schreiben:
for( auto const& l : ls ) {
// do something with l
}
Da Sie mit C++ noch nicht vertraut sind, möchte ich sicherstellen, dass die Standardbibliothek über mehrere container -Klassen verfügt.
In C++ ist die for_each
-Schleifenfunktion nicht in der Syntax enthalten. Sie müssen c ++ 11 oder die Template-Funktion std :: for_each verwenden.
#include <vector>
#include <algorithm>
#include <iostream>
struct Sum {
Sum() { sum = 0; }
void operator()(int n) { sum += n; }
int sum;
};
int main()
{
std::vector<int> nums{3, 4, 2, 9, 15, 267};
std::cout << "before: ";
for (auto n : nums) {
std::cout << n << " ";
}
std::cout << '\n';
std::for_each(nums.begin(), nums.end(), [](int &n){ n++; });
Sum s = std::for_each(nums.begin(), nums.end(), Sum());
std::cout << "after: ";
for (auto n : nums) {
std::cout << n << " ";
}
std::cout << '\n';
std::cout << "sum: " << s.sum << '\n';
}
Wie @yngum vorschlägt, können Sie die VC++ - Erweiterung for each
mit jedem beliebigen Sammlungstyp verwenden, indem Sie die Methoden begin()
und end()
für die Sammlung definieren, um einen benutzerdefinierten Iterator zurückzugeben. Ihr Iterator muss wiederum die erforderliche Schnittstelle implementieren (Dereferenzierungsoperator, Inkrementierungsoperator usw.). Ich habe das getan, um alle MFC-Auflistungsklassen für Legacy-Code einzubinden. Es ist ein bisschen Arbeit, kann aber gemacht werden.