Как заставить функцию для каждого цикла в C++ работать с пользовательским классом
Я новичок в программировании на C/C++. Я создал собственный класс в C++, который работает как список в C#. Я использую это так:
List<int> ls;
int whatever = 123;
ls.Add(1);
ls.Add(235445);
ls.Add(whatever);
Имеет динамическое распределение. у меня есть T* vector
переменная, в которую я помещаю свой массив, и когда список собирается полностью заполниться, я изменяю его размер до такого размера: 8 * (pow(++realloc_count,4) * 4)
, Это работает хорошо, не уверен, что этот расчет является наиболее правильным для достижения этого. Но что угодно.
Мне нравится изобретать велосипед на языке более низкого уровня, когда я могу, поэтому я не использую vector<T>
учебный класс. Я делаю это просто для удовольствия.
Теперь я хочу просмотреть элементы моего списка. Я не люблю использовать старый добрый for(int i=0;i<n; i==)
петля, поэтому я подумал: "Что если бы я мог использовать красивый и красивый foreach-like
цикл, используя такой синтаксис:
foreach (int item in list) { Work(item); }
А потом я набрал for
в визуальной студии ждал Intellisense, и он предложил мне это:
for each (object var in collection_to_loop)
{
}
"Ницца! Существует цикл foreach для коллекций в C++", - подумал я. Но мой класс явно не будет работать с этим. Мой список очень прост. И после некоторого исследования я заметил, что это будет работать только с библиотеками STL...
Я могу сделать это с UGLIEST KLUDGE, я мог бы подумать, и я хочу избавиться от этого:
#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);
}
Пожалуйста, не убивай меня.
Итак, есть ли способ написать сладкий и красивый цикл, похожий на foreach?
5 ответов
Во-первых, синтаксис for-each
зациклиться C++
отличается от C#
(это также называется range based for loop
, Имеет вид:
for(<type> <name> : <collection>) { ... }
Так, например, с std::vector<int> vec
было бы что-то вроде:
for(int i : vec) { ... }
Под одеялом это эффективно использует begin()
а также end()
функции-члены, которые возвращают итераторы. Следовательно, чтобы ваш пользовательский класс мог использовать for-each
цикл, вам нужно предоставить begin()
и end()
функция. Они обычно перегружены, возвращая либо iterator
или const_iterator
, Реализация итераторов может быть сложной, хотя с векторным классом это не так уж сложно.
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]; }
...
};
С их помощью вы можете использовать цикл на основе диапазона, как описано выше.
Позволять iterable
быть типа Iterable
, Затем, чтобы сделать
for (Type x : iterable)
компилировать, должны быть типы Type
а также IType
и там должны быть функции
IType Iterable::begin()
IType Iterable::end()
IType
должны обеспечить функции
Type operator*()
void operator++()
bool operator!=(IType)
Вся конструкция действительно сложный синтаксический сахар для чего-то вроде
for (IType it = iterable.begin(); it != iterable.end(); ++it) {
Type x = *it;
...
}
где вместо Type
любой совместимый тип (например, const Type
или же Type&
), что будет иметь ожидаемые последствия (постоянство, ссылка вместо копии и т. д.).
Поскольку все раскрытие происходит синтаксически, вы также можете немного изменить объявление операторов, например, имея *, он возвращает ссылку или имея!= Take a const IType& rhs
по мере необходимости.
Обратите внимание, что вы не можете использовать for (Type& x : iterable)
форма, если *it
не возвращает ссылку (но если она возвращает ссылку, вы также можете использовать копию версии).
Обратите внимание, что operator++()
определяет префиксную версию ++
оператор - однако он также будет использоваться в качестве постфиксного оператора, если вы явно не определите постфикс ++
, Rated-for не скомпилируется, если вы предоставите только постфикс ++
который, кстати, может быть объявлен как operator++(int)
(Пустой аргумент int).
Минимальный рабочий пример:
#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);
}
}
выход
0123456789
Вы можете подделать ранжированный для каждого (например, для более старых компиляторов C++) с помощью следующего макроса:
#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);
}
(используйте declspec или передайте IType, если у вашего компилятора даже нет auto).
Выход:
1234500000
Как вы видете, continue
а также break
будет работать с этим благодаря своей сложной конструкции. См. http://www.chiark.greenend.org.uk/~sgtatham/mp/ для получения дополнительной информации о взломе C-препроцессора для создания пользовательских структур управления.
Этот синтаксис, предложенный Intellisense, не является C++; или это какое-то расширение MSVC.
В C++11 есть циклы for, основанные на диапазоне, для итерации по элементам контейнера. Вам необходимо реализовать begin()
а также end()
Функции-члены для вашего класса, которые будут возвращать итераторы к первому элементу и один после последнего элемента соответственно. Это, конечно, означает, что вам нужно также реализовать подходящие итераторы для вашего класса. Если вы действительно хотите пойти по этому пути, вы можете взглянуть на Boost.IteratorFacade; это значительно облегчает реализацию итераторов самостоятельно.
После этого вы сможете написать это:
for( auto const& l : ls ) {
// do something with l
}
Кроме того, поскольку вы новичок в C++, я хочу убедиться, что вы знаете, что в стандартной библиотеке есть несколько контейнерных классов.
C++ не имеет for_each
особенность цикла в его синтаксисе. Вы должны использовать C++11 или использовать функцию шаблона std:: for_each.
#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';
}
Как говорит @yngum, вы можете получить VC++ for each
расширение для работы с любым произвольным типом коллекции путем определения begin()
а также end()
методы в коллекции для возврата пользовательского итератора. Ваш итератор, в свою очередь, должен реализовать необходимый интерфейс (оператор разыменования, оператор приращения и т. Д.). Я сделал это, чтобы обернуть все классы коллекции MFC для унаследованного кода. Это немного работы, но можно сделать.