像许多人一样,这些天我一直在尝试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() ?

简而言之,这样做的指导方针是什么?


当前回答

我写下我的答案是因为有些人可能更喜欢简单的现实生活的例子,没有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);

其他回答

在这里,我将分享一个创建自定义类型的最简单的例子,它将与“基于范围的for循环”一起工作:

#include<iostream>
using namespace std;

template<typename T, int sizeOfArray>
class MyCustomType
{
private:
    T *data;
    int indx;
public:
    MyCustomType(){
        data = new T[sizeOfArray];
        indx = -1;
    }
    ~MyCustomType(){
        delete []data;
    }
    void addData(T newVal){
        data[++indx] = newVal;
    }

    //write definition for begin() and end()
    //these two method will be used for "ranged based loop idiom"
    T* begin(){
        return &data[0];
    }
    T* end(){
        return  &data[sizeOfArray];
    }
};
int main()
{
    MyCustomType<double, 2> numberList;
    numberList.addData(20.25);
    numberList.addData(50.12);
    for(auto val: numberList){
        cout<<val<<endl;
    }
    return 0;
}

希望,这将有助于一些新手开发像我:p:) 谢谢你!

如果你想直接用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;
}

Chris Redford的答案当然也适用于Qt容器。下面是一个调整(注意我返回了constBegin(),分别从const_iterator方法返回constEnd()):

class MyCustomClass{
    QList<MyCustomDatatype> data_;
public:    
    // ctors,dtor, methods here...

    QList<MyCustomDatatype>::iterator begin() { return data_.begin(); }
    QList<MyCustomDatatype>::iterator end() { return data_.end(); }
    QList<MyCustomDatatype>::const_iterator begin() const{ return data_.constBegin(); }
    QList<MyCustomDatatype>::const_iterator end() const{ return data_.constEnd(); }
};

我写下我的答案是因为有些人可能更喜欢简单的现实生活的例子,没有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);

我想我没有什么要解释的,因为答案已经说明了这一点。但我可能不得不引用标准(N4885)中的这句话:

(支撑。[远程]/1:(强调我的)

The range-based for statement for ( init-statement(opt) for-range-declaration : for-range-initializer ) statement(possibly curly-braced) is equivalent to: { // starts namespace scope of for-range-initializer    init-statement; (opt)    auto &&range = for-range-initializer ;    auto begin = begin-expr ;    auto end = end-expr ;   for ( ; begin != end; ++begin ) {       for-range-declaration = * begin ;       statement ;    } } // ends namespace scope of for-range-initializer where (1.1) if the for-range-initializer is an expression, it is regarded as if it were surrounded by parentheses (so that a comma operator cannot be reinterpreted as delimiting two init-declarators); (1.2) range, begin, and end are variables defined for exposition only; and (3.1) begin-expr and end-expr are determined as follows: (1.3.1) if the for-range-initializer is an expression of array type R, begin-expr and end-expr are range and range+N, respectively, where N is the array bound. If R is an array of unknown bound or an array of incomplete type, the program is ill-formed; (1.3.2) if the for-range-initializer is an expression of class type C, and [class.member.lookup] in the scope of C for the names begin and end each find at least one declaration, begin-expr and end-expr are range.begin() and range.end(), respectively; (1.3.3) otherwise, begin-expr and end-expr are begin(range) and end(range), respectively, where begin and end undergo argument-dependent lookup ([basic.lookup.argdep]).


请注意,字符串、数组和所有STL容器都是可迭代的数据结构,因此已经可以使用基于范围的for循环对它们进行迭代。为了使数据结构可迭代,它必须类似于现有的STL迭代器:

1-必须有begin和end方法对该结构进行操作,可以作为成员,也可以作为独立函数,并且返回结构的开始和结束的迭代器。

2-迭代器本身必须支持operator*()方法、operator !=()方法和operator++(void)方法,可以作为成员方法,也可以作为独立函数。


#include <iostream>
#include <vector>
#define print(me) std::cout << me << std::endl

template <class T>
struct iterator
{
    iterator(T* ptr) : m_ptr(ptr) {};
    bool operator!=(const iterator& end) const { return (m_ptr != end.m_ptr); }
    T operator*() const { return *m_ptr; }
    const iterator& operator++()
    {
        ++m_ptr;
        return *this;
    }

private:
    T* m_ptr;
};

template <class T, size_t N>
struct array
{
    typedef iterator<T> iterator;

    array(std::initializer_list<T> lst)
    {

        m_ptr = new T[N]{};
        std::copy(lst.begin(), lst.end(), m_ptr);
    };

    iterator begin() const { return iterator(m_ptr); }
    iterator end() const { return iterator(m_ptr + N); }

    ~array() { delete[] m_ptr; }

private:
    T* m_ptr;
};

int main()
{
    array<std::vector<std::string>, 2> str_vec{ {"First", "Second"}, {"Third", "Fourth"} };
    for(auto&& ref : str_vec)
        for (size_t i{}; i != ref.size(); i++) 
            print(ref.at(i));

      //auto &&range = str_vec;
      //auto begin = range.begin();
      //auto end = range.end();
      //for (; begin != end; ++begin)
      //{
         // auto&& ref = *begin;
         // for (size_t i{}; i != ref.size(); i++) 
         //     print(ref.at(i));
      //}
}

这个程序的输出是:

第一个 第二个 第三 第四