std :: string和string文字不一致
我在C ++ 0x中发现了std::string
和string文字之间令人不安的不一致:
#include <iostream> #include <string> int main() { int i = 0; for (auto e : "hello") ++i; std::cout << "Number of elements: " << i << '\n'; i = 0; for (auto e : std::string("hello")) ++i; std::cout << "Number of elements: " << i << '\n'; return 0; }
输出是:
Number of elements: 6 Number of elements: 5
我理解为什么会发生这种情况的机制:string文字实际上是一个包含空字符的字符数组,当基于范围的for循环在字符数组上调用std::end()
时,它会得到一个指针数组的末尾; 由于空字符是数组的一部分,所以它得到一个超过空字符的指针。
然而,我认为这是非常不可取的: std::string
和string文字在属性的基本长度上应该是相同的?
有没有办法解决这种不一致? 例如,可以为字符数组重载std::begin()
和std::end()
,以便它们分隔的范围不包括终止空字符? 如果是这样,为什么不这样做?
编辑 :为了certificate我的愤慨更多的人说,我只是在使用C风格的string是一个“遗留function”的后果,请考虑如下代码:
template <typename Range> void f(Range&& r) { for (auto e : r) { ... } }
你会期望f("hello")
和f(std::string("hello"))
做一些不同的事情吗?
如果我们重载了const char数组的std::begin()
和std::end()
来返回一个小于数组的大小,那么下面的代码会输出4而不是预期的5:
#include <iostream> int main() { const char s[5] = {'h', 'e', 'l', 'l', 'o'}; int i = 0; for (auto e : s) ++i; std::cout << "Number of elements: " << i << '\n'; }
然而,我认为这是非常不可取的:std :: string和string文字在属性的基本长度上应该是相同的?
根据定义,string文字在string的末尾有一个(隐藏的)空字符。 Std :: strings没有。 由于std :: strings有一个长度,空字符有点多余。 string库的标准部分显式允许非空终止的string。
编辑
我认为我从来没有给出一个更有争议的答案,因为有大量的投票和大量的投票。
应用于C样式数组的auto
迭代器在数组的每个元素上迭代。 范围的确定是在编译时进行的,而不是运行时间。 这是不合格的,例如:
char * str; for (auto c : str) { do_something_with (c); }
有些人使用chartypes的数组来保存任意数据。 是的,这是一种旧式的思维方式,也许他们应该使用C ++风格的std ::数组,但是这个构造是相当有效的,而且相当有用。 如果他们的自动迭代器超过char buffer[1024];
仅仅因为该元素恰好与空字符具有相同的值而停在元素15处。 通过Type buffer[1024];
的自动迭代器Type buffer[1024];
将一路跑到最后。 是什么让一个字符数组如此值得一个完全不同的实现?
请注意,如果你希望在字符数组上的自动迭代器提前停止,有一个简单的机制来做到这一点:添加一个if (c == '0') break;
声明到你的循环体。
底线:这里没有不一致。 char []数组上的自动迭代器与自动迭代器如何处理其他C风格的数组一致。
在第一种情况下,你得到6
是一个抽象的泄漏,无法避免在C. std::string
“修复”。 为了兼容性,C风格string文字的行为在C ++中不会改变。
例如,可以为字符数组重载std :: begin()和std :: end(),以便它们分隔的范围不包括终止空字符? 如果是这样,为什么不这样做?
假设通过指针访问(而不是char[N]
),只能通过在包含字符数的string中embedded一个variables,以便不再需要查找NULL
。 哎呀! 这是std::string
。
“解决不一致”的方法根本不是使用遗留function 。
根据N3290 6.5.4,如果范围是一个数组,边界值将自动初始化,而不用begin
/ end
函数调度。
那么,准备一下如下的包装呢?
struct literal_t { char const *b, *e; literal_t( char const* b, char const* e ) : b( b ), e( e ) {} char const* begin() const { return b; } char const* end () const { return e; } }; template< int N > literal_t literal( char const (&a)[N] ) { return literal_t( a, a + N - 1 ); };
那么下面的代码将是有效的:
for (auto e : literal("hello")) ...
如果您的编译器提供用户定义的文字,可能有助于缩写:
literal operator"" _l( char const* p, std::size_t l ) { return literal_t( p, p + l ); // l excludes '\0' } for (auto e : "hello"_l) ...
编辑:下面将有较小的开销(用户定义的文字将不可用虽然)。
template< size_t N > char const (&literal( char const (&x)[ N ] ))[ N - 1 ] { return (char const(&)[ N - 1 ]) x; } for (auto e : literal("hello")) ...
如果你想要的长度,你应该使用strlen()
的Cstring和.length()
的C + +string。 你不能一视同仁Cstring和C ++string – 它们有不同的行为。
不一致性可以使用C ++ 0x的工具箱中的另一个工具来解决:用户定义的文字。 使用适当定义的用户定义的文字:
std::string operator""s(const char* p, size_t n) { return string(p, n); }
我们将能够写:
int i = 0; for (auto e : "hello"s) ++i; std::cout << "Number of elements: " << i << '\n';
现在输出预期的数字:
Number of elements: 5
有了这些新的std :: string文字,可以说没有更多的理由使用C风格的string文字了。