我发现{+ 1}}和C ++ 0x中的字符串文字之间存在令人不安的不一致:
std::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;
}
我理解为什么会发生这种情况的机制:字符串文字实际上是一个包含空字符的字符数组,当基于范围的for循环在字符数组上调用Number of elements: 6
Number of elements: 5
时,它得到一个指针超过数组的末尾;因为null字符是数组的一部分,所以它得到一个指针经过空字符。
但是,我认为这是非常不受欢迎的:肯定std::end()
和字符串文字在属性和基本长度等方面的行为应该相同吗?
有没有办法解决这种不一致?例如,可以为字符数组重载std::string
和std::begin()
,以便它们分隔的范围不包括终止空字符吗?如果是这样,为什么没有这样做?
编辑:为了向那些说我正在遭受使用C风格字符串作为“遗留功能”的后果的人多一点义务,请考虑像以下内容:
std::end()
您希望template <typename Range>
void f(Range&& r)
{
for (auto e : r)
{
...
}
}
和f("hello")
做些不同的事吗?
答案 0 :(得分:29)
如果我们为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';
}
答案 1 :(得分:21)
但是,我认为这是非常不受欢迎的:当涉及到属性基本作为长度时,std :: string和字符串文字应该表现得一样吗?
根据定义,字符串文字在字符串的末尾有一个(隐藏的)空字符。 Std :: strings没有。因为std :: strings有一个长度,所以null字符有点多余。字符串库上的标准部分显式允许非空终止字符串。
修改强>
从大量的投票和大量的投票来看,我认为我从未给出过更具争议性的答案。
应用于C样式数组时,auto
迭代器遍历数组的每个元素。范围的确定是在编译时进行的,而不是在运行时进行的。这是不正确的,例如:
char * str;
for (auto c : str) {
do_something_with (c);
}
有些人使用char类型的数组来保存任意数据。是的,这是一种旧式的C思维方式,也许他们应该使用C ++风格的std :: array,但是这个结构非常有效且非常有用。如果他们的自动迭代器超过char buffer[1024];
停止在元素15,那些人会因为该元素恰好与null字符具有相同的值而感到不安。 Type buffer[1024];
上的自动迭代器将一直运行到最后。什么使char数组如此值得完全不同的实现?
请注意,如果您希望字符数组上的自动迭代器提前停止,则可以使用一种简单的机制:向循环体添加if (c == '0') break;
语句。
底线:这里没有矛盾。 char []数组上的auto
迭代器与自动迭代器如何处理任何其他C样式数组一致。
答案 2 :(得分:19)
在第一种情况下得到6
是一个在C中无法避免的抽象泄漏。std::string
“修复”那个。为了兼容性,C风格的字符串文字的行为在C ++中不会改变。
例如,可以重载std :: begin()和std :: end() 字符数组,以便它们分隔的范围不包括 终止空字符?如果是这样,为什么没有这样做?
假设通过指针(而不是char[N]
)进行访问,只需在包含字符数的字符串中嵌入变量,这样就不再需要寻找NULL
了。哎呀!那是std::string
。
“解决不一致”的方法是根本不使用旧版功能。
答案 3 :(得分:6)
根据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")) ...
答案 4 :(得分:4)
如果你想要长度,你应该使用strlen()
作为C字符串,.length()
作为C ++字符串。您不能完全相同地处理C字符串和C ++字符串 - 它们具有不同的行为。
答案 5 :(得分:3)
可以使用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风格的字符串文字了。