Как сделать для каждой функции цикла в C++ работу с пользовательским классом
Я новичок в программировании на C/C++, но я программирую на С# уже 1,5 года. Мне нравится С#, и мне нравится класс List, поэтому я подумал о создании класса List в C++ в качестве упражнения.
List<int> ls;
int whatever = 123;
ls.Add(1);
ls.Add(235445);
ls.Add(whatever);
Реализация похожа на любой класс Array List. У меня есть элемент T* vector
котором я храню элементы, и когда это хранилище собирается быть полностью заполненным, я изменяю его размер.
Пожалуйста, обратите внимание, что это не должно использоваться в производстве, это всего лишь упражнение. Я хорошо знаю vector<T>
и друзей.
Теперь я хочу просмотреть элементы моего списка. Я не люблю использовать for(int i=0;i<n; i==)
. Я напечатал for
в Visual Studio, ожидаемый для Intellisense, и он предложил мне это:
for each (object var in collection_to_loop)
{
}
Это очевидно не будет работать с моей реализацией List. Я подумал, что мог бы сделать немного магии, но это похоже на огромный взлом. На самом деле, больше всего меня беспокоит передача такого типа:
#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);
}
Мой вопрос: есть ли способ заставить этот пользовательский класс List работать с циклом, foreach-like
?
Ответы
Ответ 1
Во-первых, синтаксис цикла 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]; }
...
};
С помощью этих функций вы можете использовать цикл, основанный на диапазоне, как указано выше.
Ответ 2
Пусть 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&
), который будет иметь ожидаемые последствия (константа, ссылка вместо копии и т.д.).
Так как все расширение происходит синтаксически, вы также можете немного изменить объявление операторов, например, * вернуть ссылку или иметь! = Взять const IType& rhs
мере необходимости.
Обратите внимание: вы не можете использовать форму for (Type& x: iterable)
если *it
не возвращает ссылку (но если она возвращает ссылку, вы также можете использовать копию).
Обратите также внимание на то, что operator++()
определяет префиксную версию оператора ++
однако он также будет использоваться в качестве постфиксного оператора, если вы явно не определяете постфикс ++
. Ранжирование не будет компилироваться, если вы поставляете только постфикс ++
, который btw.can может быть объявлен как operator++(int)
(аргумент dummy 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, если ваш компилятор даже не имеет авто).
Вывод:
1234500000
Как вы можете видеть, continue
и break
будут работать с этим благодаря сложной конструкции. См. Http://www.chiark.greenend.org.uk/~sgtatham/mp/ для дальнейшего взлома C-препроцессора для создания настраиваемых структур управления.
Ответ 3
Этот синтаксис, предложенный Intellisense, не является C++; или это расширение MSVC.
C++ 11 имеет диапазон для циклов для итерации по элементам контейнера. Вам нужно реализовать функции-члены begin()
и end()
для вашего класса, которые возвратят итераторы в первый элемент, а один - за последний элемент соответственно. Это, конечно же, означает, что вам нужно также внедрять подходящие итераторы для вашего класса. Если вы действительно хотите пойти по этому маршруту, вы можете посмотреть на Boost.IteratorFacade; это уменьшает боль от реализации итераторов самостоятельно.
После этого вы сможете это написать:
for( auto const& l : ls ) {
// do something with l
}
Кроме того, поскольку вы новичок в C++, я хочу убедиться, что вы знаете, что стандартная библиотека имеет несколько классов контейнеров.
Ответ 4
C++ не имеет for_each
loop 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';
}
Ответ 5
Как предлагает @yngum, вы можете заставить VC++ for each
расширения работать с любым произвольным типом коллекции, определяя методы begin()
и end()
в коллекции, чтобы вернуть пользовательский итератор. Ваш итератор, в свою очередь, должен реализовать необходимый интерфейс (оператор разыменования, оператор инкремента и т.д.). Я сделал это, чтобы обернуть все классы коллекции MFC для устаревшего кода. Это немного работы, но это можно сделать.