为什么在调用istream::getline后可以打印字符串'\0'之后的部分

Why the part after '\0' of a string can be printed after calling istream::getline

为了巩固对istream::getline的理解,我测试了以下代码:

#include <iostream>
#include <sstream>
using namespace std;
int main()
{
    string s("abcdefgh \nijklmnopqrst");        
    string s1;
    stringstream ss(s);
    ss >> s1;
    cout <<"s1:"<< s1 << endl;
    ss.getline(&s1[0], 250, '\n');
    if(s1[0]==' '&&s1[1]=='[=11=]')
      cout << "new s1:"<<s1 << endl;
    getchar();
    return 1;
}

据我了解,ss.getline 调用将提取空格和终止符 '\n' 然后将空格和 '[=16=]' 分配给 s1[0]s1[1] 分别指代cpluscpluss1[1] 之后的字符保持不变,因为一旦到达终止符,提取就会停止。但是一个意想不到的问题是可以打印出s1。控制台打印

s1:abcdefgh
new s1:  cdefgh

为什么在这种情况下可以打印字符串的'\0'之后的部分?

std::string 允许包含空字符。与传统的 c 字符串不同,空字符不用于确定其长度 (尽管在其末尾存储了一个空字符以允许它与 c 字符串接受函数一起使用)。它的长度是单独存储的,可以使用 size()length() 成员函数检索。所以当你用operator<<打印出来的时候,运算符并没有在发现null时停止打印,而是在打印完s1.size()个字符后停止打印。

C++ 字符串和使用它们的 I/O 工具并不真正关心 NUL 字节。他们知道自己的长度,如果他们的数据是 N 字节长,那么其中一些字节是否 NUL 并不重要,iostreams 将继续前进直到达到记录的长度(设置时你阅读 ss >> s1;).

当您这样做时,您明显滥用了您的字符串:

ss.getline(&s1[0], 250, '\n');

因为 &s1[0] 正在绕过 std::string 的安全访问器来获取原始 char*(更糟糕的是,你告诉 getline 它最多可以提取 250 个字符,当底层缓冲区可能小得多),并保持 length/capacity 信息不变(因此它仍然认为它包含 ss >> s1; 读取的许多字符)。

您真的想要 std::getline,它面向 std::string,并且可以正常工作(包括根据需要为您调整输出大小,调整已知字符串长度,确保现有数据不会遗留在地点):

std::getline(ss, s1, '\n');