对于C字符串,是否有一个标准的C++迭代器

Is there a standard C++ iterator for C strings?

本文关键字:标准 C++ 迭代器 有一个 是否 字符串 对于      更新时间:2023-10-16

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

编辑:我知道我可以使用指针作为迭代器,但我必须知道字符串的结束位置,以及调用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版:具体来说,我对前向迭代器感兴趣,因为我想避免在sring上迭代两次,因为我知道算法只需要迭代一次。

没有任何显式迭代器class,但常规原始指针也是有效的迭代器。然而,C字符串的问题在于,它们没有自带本地端迭代器,这使得它们在基于范围的循环中不可用–至少直接。。。

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

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,但可与指向以null结尾数组的任何指针一起使用–C字符串也是。。。

Secret是与sentinel进行比较,sentinel实际上进行了完全不同的比较(当前指针指向终止的null(指针))。。。

编辑:Pre-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); }
};

实际上,是的。在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;
}
}

请参阅此处的示例输出

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

是。指针是数组的迭代器。C字符串是char的(以null结尾的)数组。因此char*是一个C字符串的迭代器。

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

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

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

必须小心一点来考虑空终止符。必须记住,数组的整个范围都包含字符串的null终止符。如果您希望迭代器范围表示不带终止符的字符串,那么从数组的结束迭代器中减去一个,这解释了上一段中的减法。

如果您没有数组,但只有一个指针(begin迭代器),则可以通过将开头提前字符串的长度来获得结束迭代器。这种进步是一种持续的操作,因为指针是随机访问迭代器。如果您不知道长度,可以调用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));

具体来说,我对前向迭代器感兴趣

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

可以编写这样的迭代器,类似这样的东西应该可以工作:

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

实例

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

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

如果您有一个字符串文字,您可以在不使用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;
}
}

为了获得最终的安全性和灵活性,您最终需要包装迭代器,它必须携带一些状态。

问题包括:

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

请注意,如果在容器的范围之外随机查找它,并且它可以合法地通过string_view.end()进行查找,则"不是迭代器的问题"。这样一个损坏的迭代器不能再增加到end()也是相当标准的。

这些条件中最痛苦的是end可以递减、相减和取消引用(通常不能,但对于字符串,它是一个null字符)。这意味着end对象需要一个标志,表明它是结束,以及开始的地址,这样,如果发生这两种操作中的任何一种,它都可以使用strlen()找到实际的结束。

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

迭代程序是指针的一种泛化。特别是,它们的设计使指针成为有效的迭代器。

注意std::iterator_traits的指针特殊化。

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

除非你有其他方法来知道字符串的结尾,否则调用strlen是最好的方法。如果有一个神奇的迭代器包装器,它也必须调用strlen

对不起,迭代器通常是从可迭代实例中获得的。因为char *是一个基本类型,不再是一个类。你认为.begin().end()这样的东西是如何实现的。

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

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

但问题是,不能像C++中定义的那样使用迭代器,因为char *是一个基本类型,没有构造函数,也没有相关的析构函数或方法。