像许多人一样,这些天我一直在尝试c++ 11带来的不同特性。我最喜欢的是“基于范围的for循环”。
我明白:
for(Type& v : a) { ... }
等价于:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
begin()只是返回标准容器的a.begin()。
但是如果我想让我的自定义类型“基于范围的for循环”感知呢?
我应该专门化begin()和end()吗?
如果我的自定义类型属于命名空间xml,我应该定义xml::begin()或std::begin() ?
简而言之,这样做的指导方针是什么?
我应该专门化begin()和end()吗?
据我所知,这就足够了。您还必须确保对指针进行递增可以从开始到结束。
下一个示例(它缺少begin和end的const版本)编译并正常工作。
#include <iostream>
#include <algorithm>
int i=0;
struct A
{
A()
{
std::generate(&v[0], &v[10], [&i](){ return ++i;} );
}
int * begin()
{
return &v[0];
}
int * end()
{
return &v[10];
}
int v[10];
};
int main()
{
A a;
for( auto it : a )
{
std::cout << it << std::endl;
}
}
下面是另一个使用begin/end作为函数的例子。它们必须和类在同一个命名空间中,因为ADL:
#include <iostream>
#include <algorithm>
namespace foo{
int i=0;
struct A
{
A()
{
std::generate(&v[0], &v[10], [&i](){ return ++i;} );
}
int v[10];
};
int *begin( A &v )
{
return &v.v[0];
}
int *end( A &v )
{
return &v.v[10];
}
} // namespace foo
int main()
{
foo::A a;
for( auto it : a )
{
std::cout << it << std::endl;
}
}
我应该专门化begin()和end()吗?
据我所知,这就足够了。您还必须确保对指针进行递增可以从开始到结束。
下一个示例(它缺少begin和end的const版本)编译并正常工作。
#include <iostream>
#include <algorithm>
int i=0;
struct A
{
A()
{
std::generate(&v[0], &v[10], [&i](){ return ++i;} );
}
int * begin()
{
return &v[0];
}
int * end()
{
return &v[10];
}
int v[10];
};
int main()
{
A a;
for( auto it : a )
{
std::cout << it << std::endl;
}
}
下面是另一个使用begin/end作为函数的例子。它们必须和类在同一个命名空间中,因为ADL:
#include <iostream>
#include <algorithm>
namespace foo{
int i=0;
struct A
{
A()
{
std::generate(&v[0], &v[10], [&i](){ return ++i;} );
}
int v[10];
};
int *begin( A &v )
{
return &v.v[0];
}
int *end( A &v )
{
return &v.v[10];
}
} // namespace foo
int main()
{
foo::A a;
for( auto it : a )
{
std::cout << it << std::endl;
}
}
我写下我的答案是因为有些人可能更喜欢简单的现实生活的例子,没有STL包含。
出于某种原因,我有自己的纯数据数组实现,我想使用基于范围的for循环。以下是我的解决方案:
template <typename DataType>
class PodArray {
public:
class iterator {
public:
iterator(DataType * ptr): ptr(ptr){}
iterator operator++() { ++ptr; return *this; }
bool operator!=(const iterator & other) const { return ptr != other.ptr; }
const DataType& operator*() const { return *ptr; }
private:
DataType* ptr;
};
private:
unsigned len;
DataType *val;
public:
iterator begin() const { return iterator(val); }
iterator end() const { return iterator(val + len); }
// rest of the container definition not related to the question ...
};
然后是用法示例:
PodArray<char> array;
// fill up array in some way
for(auto& c : array)
printf("char: %c\n", c);
受到BitTickler关于如何使它适用于非“容器”类型的评论的启发,这里有一个适用于双精度对象的最小示例:
class dranged {
double start, stop, step, cur;
int index;
public:
dranged(double start, double stop, double step) :
start(start), stop(stop), step(step),
cur(start), index(0) {}
auto begin() { return *this; }
auto end() { return *this; }
double operator*() const { return cur; }
auto& operator++() {
index += 1;
cur = start + step * index;
return *this;
}
bool operator!=(const dranged &rhs) const {
return cur < rhs.stop;
}
};
注意,在!=操作符中使用<保持了正确的不变量,但显然假设step是正的,并且不适用于更一般的范围。我使用整数索引来防止浮点错误的传播,但在其他方面力求简单。
这可以用于:
double sum() {
double accum = 0;
for (auto val : dranged(0, 6.28, 0.1)) {
accum += val;
}
return accum;
}
GCC和Clang在进行优化编译时都能生成非常合理的代码(例如,GCC的-Os或高于-O1, Clang的-O2)。
如果你想直接用std::vector或std::map成员来支持一个类的迭代,下面是代码:
#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <map>
using std::map;
/////////////////////////////////////////////////////
/// classes
/////////////////////////////////////////////////////
class VectorValues {
private:
vector<int> v = vector<int>(10);
public:
vector<int>::iterator begin(){
return v.begin();
}
vector<int>::iterator end(){
return v.end();
}
vector<int>::const_iterator begin() const {
return v.begin();
}
vector<int>::const_iterator end() const {
return v.end();
}
};
class MapValues {
private:
map<string,int> v;
public:
map<string,int>::iterator begin(){
return v.begin();
}
map<string,int>::iterator end(){
return v.end();
}
map<string,int>::const_iterator begin() const {
return v.begin();
}
map<string,int>::const_iterator end() const {
return v.end();
}
const int& operator[](string key) const {
return v.at(key);
}
int& operator[](string key) {
return v[key];
}
};
/////////////////////////////////////////////////////
/// main
/////////////////////////////////////////////////////
int main() {
// VectorValues
VectorValues items;
int i = 0;
for(int& item : items) {
item = i;
i++;
}
for(int& item : items)
cout << item << " ";
cout << endl << endl;
// MapValues
MapValues m;
m["a"] = 1;
m["b"] = 2;
m["c"] = 3;
for(auto pair: m)
cout << pair.first << " " << pair.second << endl;
}