it-swarm.com.de

Wie lässt sich die for-Schleife-Funktion in C++ mit einer benutzerdefinierten Klasse arbeiten?

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?

30
Ricardo Pieper

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.

41
Yuushi

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.

16
masterxilo

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.

7
Praetorian

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';
}
2
saeed

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.

0
Scott Jones