是否有用于C字符串的标准C ++迭代器?

时间:2018-08-09 15:15:22

标签: c++ string c++11 iterator

有时,我需要使用通用的C ++迭代器范围接口[first, last)将C字符串传递给函数。对于这些情况,是否有标准的C ++迭代器类,或者不需要复制字符串或调用strlen()的标准方法呢?

编辑: 我知道我可以将指针用作迭代器,但是我必须知道字符串的结尾,什么使我需要调用strlen()

EDIT2: 虽然我不知道这种迭代器是否标准化,但我当然知道这是可能的。回应讽刺的答案和评论,这是存根(不完整,未经测试):

class CStringIterator
{
public:
    CStringIterator(char *str=nullptr):
        ptr(str)
    {}

    bool operator==(const CStringIterator& other) const
    {
        if(other.ptr) {
            return ptr == other.ptr;
        } else {
            return !*ptr;
        }
    }

    /* ... operator++ and other iterator stuff */

private:
    char *ptr;
};

EDIT3: 具体来说,我对forward iterator感兴趣,因为当我知道算法只需要执行一次时,我想避免对sring进行两次迭代。

9 个答案:

答案 0 :(得分:4)

没有任何显式的迭代器 class ,但是常规的原始指针也是有效的迭代器。但是,C字符串的问题在于它们没有附带本机末端迭代器,这使得它们无法在基于范围的for循环中使用–至少直接...

不过,您可能想尝试以下模板:

template <typename T>
class Range
{
    T* b;
public:
    class Sentinel
    {
        friend class Range;
        Sentinel() { }
        friend bool operator!=(T* t, Sentinel) { return *t; }

    public:
        Sentinel(Sentinel const& o) { }

    };
    Range(T* begin)
            : b(begin)
    { }
    T* begin() { return b; }
    Sentinel end() { return Sentinel(); }
};

用法:

for(auto c : Range<char const>("hello world"))
{
    std::cout << c << std::endl;
}

它最初旨在迭代main的以null终止的argv,但是可以使用 any 指向以null终止的数组的指针-C字符串也是如此...

秘密正在与前哨进行比较,后者实际上进行了完全不同的比较(当前指针指向终止空值(指针))...

编辑:C ++ 17之前的版本:

template <typename T>
class Range
{
    T* b;
public:
    class Wrapper
    {
        friend class Range;
        T* t;
        Wrapper(T* t) : t(t) { }
    public:
        Wrapper(Wrapper const& o) : t(o.t) { }
        Wrapper operator++() { ++t; return *this; }
        bool operator!=(Wrapper const& o) const { return *t; }
        T operator*() { return *t; }
    };
    Range(T* begin)
            : b(begin)
    { }
    Wrapper begin() { return Wrapper(b); }
    Wrapper end() { return Wrapper(nullptr); }
};

答案 1 :(得分:4)

实际上,是的。在c ++ 17中。

C ++ 17引入了std::string_view,它可以由c样式的字符串构造。

std::string_view是一个随机访问(代理)容器,它当然完全支持迭代器。

请注意,虽然理论上从const char*构造string_view会调用std::strlen,但是当编译器在编译时知道字符串的长度时,它可以(并且gcc当然可以)取消调用

示例:

#include <string_view>
#include <iostream>

template<class Pointer>
struct pointer_span
{
    using iterator = Pointer;

    pointer_span(iterator first, std::size_t size)
    : begin_(first)
    , end_(first + size)
    {
    }

    iterator begin() const { return begin_; }
    iterator end() const { return end_; }

    iterator begin_, end_;
};

int main(int argc, char** argv)
{
    for(auto&& ztr : pointer_span(argv, argc))
    {
        const char* sep = "";
        for (auto ch : std::string_view(ztr))
        {
            std::cout << sep << ch;
            sep = " ";
        }
        std::cout << std::endl;
    }
}

请参见示例输出here

答案 2 :(得分:3)

  

是否有用于C字符串的标准C ++迭代器?

是的。指针是数组的迭代器。 C字符串是char的(空终止)数组。因此char*是C字符串的迭代器。

  

...使用通用的C ++迭代器范围接口[first, last)

就像所有其他迭代器一样,要具有范围,您需要具有结束迭代器。

如果您知道或可以假设一个数组完全包含字符串,仅此而已,则可以使用std::begin(arr)std::begin恒定时间获得迭代器范围(对于衰减到C的C数组是多余的)指针,但对于对称性很好)和std::end(arr) - 1。否则,您可以对数组中的偏移量使用指针算法。

必须谨慎考虑空终止符。必须记住,数组的整个范围都包含字符串的空终止符。如果希望迭代器范围表示不带终止符的字符串,请从数组的结束迭代器中减去一个,这在上一段中作了说明。

如果没有数组,而只有一个指针-begin迭代器-您可以通过将字符串的开始位置提前以获取end迭代器。由于指针是随机访问迭代器,因此这种进步是一项恒定的操作。如果您不知道长度,可以调用std::strlen来找出长度(这不是恒定的操作)。


例如,std::sort接受一定范围的迭代器。您可以像这样对C字符串进行排序:

char str[] = "Hello World!";
std::sort(std::begin(str), std::end(str) - 1);
for(char c : "test"); // range-for-loops work as well, but this includes NUL

如果您不知道字符串的长度:

char *str = get_me_some_string();
std::sort(str, str + std::strlen(str));

  

具体地说,我对forward iterator

感兴趣

指针是一个随机访问迭代器。所有随机访问迭代器也是前向迭代器。指针符合链接的迭代器概念中列出的所有要求。

答案 3 :(得分:2)

可以编写这样的迭代器,这样的方法应该起作用:

struct csforward_iterator : 
    std::iterator<std::bidirectional_iterator_tag, const char, void> {

    csforward_iterator( pointer ptr = nullptr ) : p( ptr ) {}

    csforward_iterator& operator++()  { ++p; return *this; }
    csforward_iterator operator++(int) { auto t = *this; ++p; return t; }

    csforward_iterator& operator--()  { --p; return *this; }
    csforward_iterator operator--(int) { auto t = *this; --p; return t; }

    bool operator==( csforward_iterator o ) { 
        return p == o.p or ( p ? not ( o.p or *p ) : not *o.p ); 
    }
    bool operator!=( csforward_iterator o ) { return not operator==( o ); }

    void swap( csforward_iterator &o ) { std::swap( p, o.p ); }

    reference operator*() const { return *p; }
    pointer operator->() const { return p; }
private:
    pointer p;
};

live example

不幸的是,没有提供标准的标准,它可能是char类型的模板(例如std::string)。

答案 4 :(得分:1)

恐怕不是,最后,您将需要一个指向要调用strlen的字符串末尾的指针。

答案 5 :(得分:1)

如果您有字符串文字,则无需使用std::strlen就可以得到结束迭代器。如果只有char*,则必须编写自己的迭代器类或依靠std::strlen来获取最终迭代器。

字符串文字的说明代码:

#include <iostream>
#include <utility>

template <typename T, size_t N>
std::pair<T*, T*> array_iterators(T (&a)[N]) { return std::make_pair(&a[0], &a[0]+N); }

int main()
{
   auto iterators = array_iterators("This is a string.");

   // The second of the iterators points one character past the terminating
   // null character. To iterate over the characters of the string, we need to 
   // stop at the terminating null character.

   for ( auto it = iterators.first; it != iterators.second-1; ++it )
   {
      std::cout << *it << std::endl;
   }
}

答案 6 :(得分:1)

为了获得最终的安全性和灵活性,您需要包装迭代器,并且迭代器必须带有某种状态。

问题包括:

  • 随机访问-可以通过限制指针的重载来阻止随机访问,或者在需要时通过使其strlen()来在包装的指针中解决
  • 多个迭代器-彼此比较时,不结束
  • 递减结束-您可以通过限制过载来再次“修复”
  • begin()和end()必须具有相同的类型-在c ++ 11和一些api调用中。
  • 非常量迭代器可以添加或删除内容

请注意,如果在容器范围之外随机寻找它,则不是“迭代器的问题”,它可以合法地经过string_view.end()。同样相当标准的是,这样一个损坏的迭代器不能再递增到end()。

这些情况中最令人痛苦的是,可以将end减一,减一和取消引用(通常不能,但是对于字符串来说,它是一个空字符)。这意味着结束对象需要一个标志,它是结束标志和起始地址,以便在发生以下任何一种操作时,都可以使用strlen()找到实际的结束标志。

答案 7 :(得分:0)

  

在这些情况下是否有标准的C ++迭代器类,或无需复制字符串就可以实现的标准方法

迭代器是指针的概括。具体来说,它们的设计使指针是有效的迭代器。

注意pointer specializations of std::iterator_traits

  

我知道我可以将指针用作迭代器,但是我必须知道字符串的结尾

除非您有其他方法可以知道字符串的结尾,否则调用strlen是您的最佳选择。如果有魔术迭代器包装器,则还必须调用strlen

答案 8 :(得分:0)

对不起,迭代器是通常从可迭代实例获得的东西。由于var jagged3D= new int[][][] { new int[][]{ new int[]{6}, new int[]{0}, new int[]{13} } }; 是基本类型,而不是类。您如何看待诸如var jaggedCustom = new Custom[][] { new Custom[]{ new Custom{ A =3, B =0, C = new int[]{13} } } }; char *之类的事情。

顺便说一句,如果您需要迭代.begin()并知道它已被nul终止,那么请进行循环。您只需执行以下操作即可。

.end()

但是事实是您不能使用C ++中定义的迭代器,因为char *p是基本类型,没有构造函数,没有关联的析构函数或方法。