代码之家  ›  专栏  ›  技术社区  ›  lvella

C字符串有标准的C++迭代器吗?

  •  4
  • lvella  · 技术社区  · 6 年前

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

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

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

    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;
    };
    

    编辑3: 具体来说,我对 forward iterator ,因为我想避免在sring上重复两次,因为我知道算法只需要重复一次。

    9 回复  |  直到 6 年前
        1
  •  1
  •   Tom    6 年前

    恐怕不行,最后需要一个指向字符串末尾的指针 strlen .

        2
  •  4
  •   Aconcagua    6 年前

    没有任何显式迭代器 ,但常规原始指针也是有效的迭代器。不过,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的以空结尾的argv上进行迭代,但是可以使用 任何 指向以空结尾的数组的指针-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); }
    };
    
        3
  •  4
  •   Richard Hodges    6 年前

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

    C++ 17介绍 std::string_view 它可以由c样式的字符串构造。

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

    请注意,尽管从 const char* 理论上来说 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

        4
  •  3
  •   eerorika    6 年前

    C字符串有标准的C++迭代器吗?

    对。指针是数组的迭代器。C字符串是(以空结尾)的数组 char . 因此 char* 是C字符串的迭代器。

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

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

    如果您知道或可以假设一个数组完全包含字符串而不包含其他内容,那么您可以使用 std::begin(arr) ( std::begin 对于C数组来说是多余的,但是对于对称性来说是很好的),并且 std::end(arr) - 1 . 否则,可以在数组中使用带偏移量的指针算术。

    对于空终止符,必须小心一点。必须记住,数组的整个范围包含字符串的空终止符。如果希望迭代器范围表示不带结束符的字符串,则从数组的结束迭代器中减去一个,这将解释上一段中的减法。

    如果没有数组,但只有一个指针(begin iterator),则可以通过按字符串长度推进开始来获得结束迭代器。这个过程是一个常量操作,因为指针是随机访问迭代器。如果你不知道长度,你可以打电话给 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

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

        5
  •  2
  •   Slava    6 年前

    可以编写这样的迭代器,如下所示:

    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 ).

        6
  •  1
  •   R Sahu    6 年前

    如果您有一个字符串文本,则可以不使用 std::strlen . 如果你只有一个 char* ,您必须编写自己的迭代器类或依赖于 标准::斯特伦 获取结束迭代器。

    字符串文本的演示代码:

    #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;
       }
    }
    
        7
  •  1
  •   Gem Taylor    6 年前

    为了达到最终的安全性和灵活性,最终包装迭代器,并且必须携带一些状态。

    问题包括:

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

    请注意,如果在容器的范围之外随机查找它,并且它可以合法地在string-view.end()之后进行查找,则“不是迭代器的问题”。这样一个中断的迭代器不能再递增到end()也是相当标准的。

    这些条件中最痛苦的是可以对end进行减法、减法和取消引用(通常不能,但对于string,它是一个空字符)。这意味着end对象需要一个标志,表明它是end和start的地址,这样,如果发生任何一个操作,它都可以使用strlen()找到实际的end。

        8
  •  0
  •   Useless    6 年前

    对于那些情况,是否有一个标准的C++迭代器类,或者不必复制字符串的一种标准方法

    迭代器是指针的泛化。特别是,它们的设计使得指针是有效的迭代器。

    注意 pointer specializations of std::iterator_traits .

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

    除非你有其他方法知道字符串的结尾,调用 strlen 是你能做的最好的。如果有一个神奇的迭代器包装器,它还必须调用 斯特伦 .

        9
  •  0
  •   Luis Colorado    6 年前

    抱歉,迭代器通常是从iterable实例获得的。作为 char * 是一种基本类型,不再是类。你觉得怎么样 .begin() .end() ,可以实现。

    顺便说一下,如果您需要迭代 char *p 知道它是nul终止的。你可以做以下的事情。

    for( char *p = your_string; *p; ++p ) {
        ...
    }
    

    但问题是不能在C++中使用迭代器,因为 字符* 是基本类型,没有构造函数,没有析构函数或关联的方法。