std :: string和字符串文字之间的不一致

时间:2011-07-17 23:12:57

标签: c++ string foreach c++11 string-literals

我发现{+ 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::stringstd::begin(),以便它们分隔的范围不包括终止空字符吗?如果是这样,为什么没有这样做?

编辑:为了向那些说我正在遭受使用C风格字符串作为“遗留功能”的后果的人多一点义务,请考虑像以下内容:

std::end()

您希望template <typename Range> void f(Range&& r) { for (auto e : r) { ... } } f("hello")做些不同的事吗?

6 个答案:

答案 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风格的字符串文字了。