std::string的strlen(str.c_str())和str.length()有什么区别?

10
作为一种默契的理解,我一直认为std::string的每个实现必须满足对于每个字符串str都有strlen(str.c_str()) == str.length()

C++标准对此有何规定?(有吗?)
背景:至少Visual C++和gcc附带的实现没有这个特性。请考虑以下示例(在此处查看实时示例):
// Output:
// string says its length is: 13
// strlen says: 5
#include <iostream>
#include <cstring>
#include <string>

int main() {
  std::string str = "Hello, world!";
  str[5] = 0;
  std::cout << "string says its length is: " << str.length() << std::endl;
  std::cout << "strlen says: " << strlen(str.c_str()) << std::endl;
  return 0;
}

当然,没有通知str的写操作会导致“问题”。但这不是我的问题。我想知道标准对这种行为有何规定。


4
std::string 支持空字符(NUL 字符),而 C 字符串不支持。 - chris
@chris:你有标准的参考资料吗? - Xlea
我认为Lightness已经解决了这个问题。虽然我没有看到任何东西,但你期望一个知道其长度的字符串需要明确声明的唯一原因是与C字符串进行对比。请注意,您当然可以在char *中嵌入NULs。例如,BSTR包含一个长度前缀,后跟数据,后跟两个NUL(其中的字符为wchar_t,但相同的想法适用)。 - chris
我一直认为std::string的每个实现都必须满足strlen(str.c_str()) == str.length(),对于每个字符串str。你为什么会这样想,为什么这是必要的?解决你困惑的根源可能更有建设性。 - Lightness Races in Orbit
一个std::string会记住它的长度,所以std::string::length()不会每次调用strlen(),它只是返回存储的长度。在字符串中插入一个nul字节不会改变存储的长度。 - Jonathan Wakely
3个回答

14

你的理解是不正确的,有点问题。

std::string 可能包含值为'\0'char;当你提取 C 字符串时,除了扫描\0以外,你没有办法知道它有多长,而这些\0由于必要性无法处理 "二进制数据"。

这是 strlen 的限制,而std::string 通过实际记住作为封装的char数目的元数据来“修复”此限制。

标准并不需要对此“说明”任何内容,除了std::string::length可以给出字符串长度,无论您将什么值的char插入到字符串中,而且插入一个'\0'也是不被禁止的。相比之下,strlen 的定义是告诉你它有多少个char存在直到下一个 \0,这是基本上不同的定义。

关于这一点没有明确的措辞,因为不需要有。如果有非常简单的规则例外 ("这里有一个字符串;它有char;它可以告诉你它有多少char") 那么将明确说明...但是没有。


2
@Xlea:它不需要被明确指定。字符串接受char(没有提到其值的限制),而字符串类可以告诉您字符串中char的数量。没有措辞说明“除了这与strlen相同的错误之外,尽管该类的整个目的是改进可怕的C字符串语义”。我无法证明否定! - Lightness Races in Orbit
5
据我所知,在标准中没有明确说明"str.length()可能会与std::strlen(str.c_str())的结果不同"。但这并不必要,因为你可以从这些函数的定义中推导出来。string::length被定义为返回字符串中字符的数量,而strlen被定义为返回第一个0字符之前的字符数量。 - Steve Jessop
@LightnessRacesinOrbit: 如果将std::string的语义(我能想到的最合理的)定义为“字符向量”,那么这是正确的设计。当从头开始设计std::string时,我完全同意这种设计。然而,由于需要向后兼容,因此在将旧代码移植到std::string时必须更加小心。另外,我也可以使用C风格字符串(即带有特殊终止符号的字符串,例如在特征中给出)来定义“一致的行为”。因此,“一致地工作”恰好涉及到“字符串”的定义,因此并没有解释什么。 - Xlea
1
@Xlea,胡说八道,这不是隐式的。标准清晰地规定了length()size()是字符串中包含的元素数量,而不是最左边非零元素的数量。请参考NathanOliver的答案。至于向后兼容性,std::string不应向后兼容char*,而是应该具有其他语义,例如存储其长度。如果您想要一个C字符串,请调用c_str()并使用C字符串函数。 - Jonathan Wakely
1
@Xlea,不,标准在多个地方都非常明确。请查看basic_string(const char_type* s, size_type n, const Allocator&)构造函数,它将字符串长度设置为n而不是strlen(s),请注意length()需要是常数时间(不像strlen那样是线性的),请注意resize(size_type)通过向字符串添加'\0'字符来增加大小!显然,通过附加零字节到末尾,您无法更改strlen()的结果!在要求他人为您提供参考之前,您是否尝试在标准中找到答案? - Jonathan Wakely
显示剩余10条评论

3

标准 C 函数 std::strlen 根据字符数组中终止零的存在来计算其长度。

另一方面,类 std::string 的对象可能包含嵌入的零。因此,应用于 c_str() 的 strlen 函数可能产生与成员函数 length 返回值不同的结果。

考虑一个简单的例子:

std::string s( 10, '\0' );

std::cout << s.length() << std::endl;
std::cout << std::strlen( s.c_str() ) << std::endl;

在这种情况下,第一条输出语句将输出10,而第二条输出语句将输出0。
此外,如果您有一个字符串,例如
std::string s( "Hello" );

然后调用成员函数resize

s.resize( 10 );

然后该函数使用四个char()类型的零值将原始字符串追加。成员函数s.length()返回10。


1

关于字符串的length(),标准如下所述:

返回值:size()。

size()的定义为:

返回值:当前字符串中char-like对象的数量。

因此,您可以看到,即使char-like对象的值为'\0',您也将获得字符串中char-like对象的数量。


1
@LightnessRacesinOrbit,因为这是21.2 [strings.general]定义的,用于引用存储在basic_string中的任何字符类型。 "本条款描述了用于操作任何非数组POD(3.9)类型序列的组件。在本条款中,这些类型称为_char-like types_,char-like类型的对象称为_char-like objects_或简称_characters_。" - Jonathan Wakely
这个引用来自 basic_string 21.4.4 的定义,也许是为了掩盖它还可以容纳宽字符,它们类似于 char 而不是 char - NathanOliver
1
@LightnessRacesinOrbit,在一些使用第21条款的上下文中,该措辞中没有“_CharT”模板参数“在范围内”。 - Jonathan Wakely
1
@LightnessRacesinOrbit,在此之后的条款中,basic_string 对象中保存的 char 类型对象的类型由 charT 指定。我猜这只是因为 charT 是根据这个定义的,而不是相反。 - chris
再次强调,这是对底层语义定义的问题。什么是“字符串内部”?例如,在C语言中,字符串被定义为从起始位置右侧的所有字符,严格位于第一个0字符的左侧(假设简单起见只有一个)。我猜最好的提示就是所有字符的绝对对称性。 - Xlea
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接