istream::getline() 的令人费解的行为
Puzzling behavior of istream::getline()
我测试了以下代码以阐明我对 istream::getline()
的理解:
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
string s("abcd efgh\nijklmnopqrst");
string s1;
stringstream ss(s);
ss >> s1;
cout << s1 << endl;
ss.getline(&s1[0], 250, '\n');
cout << s1 << endl;
ss >> s1;
cout << s1 << endl;
getchar();
return 1;
}
然后控制台打印:
abcd
efg
ijklmnopqrst
但我认为应该是
abcd
efgh
ijklmnopqrst
此外,我发现调用ss.getline()
后s1
的大小与调用ss>>
后的大小相同,但调用ss>>
后大小会发生变化再一次。谁能帮我解析一下?
两件事。
首先,>>
不消耗空格,因此 getline
将检索它。
其次,这行不正确:
ss.getline(&s1[0], 250, '\n');
由于 getline
需要 std::basic_string
,只需传入字符串:
ss.getline(s1, 250, '\n');
在您的代码中,&s1[0]
可以访问已写入的底层缓冲区,但字符串的长度是单独存储的,并且仍然是之前读取的内容(这就是为什么 h
被丢弃)。但是,由于缓冲区溢出,此时您已经调用了未定义的行为。
ss.getline(&s1[0], 250, '\n');
此 getline()
调用的第一个参数是 char *
。 ss
完全不知道这个 char
缓冲区实际上来自 std::string
,它实际上是它的内部缓冲区。
使整个事件复杂化的是这个 std::string
给人的印象是它包含四个字符。因为这就是它的全部,在这一点上。
绝对没有任何事情可以导致 std::string
改变主意。仅仅因为一个指向其内部字符缓冲区的指针被传递给 getline()
,它开始相当粗鲁地在它上面涂鸦(导致未定义的行为,我稍后会推断),std::string
仍然认为它只包含四个字符。
同时,初始格式化输入运算符,>>
提取了初始字符,但是没有提取下面的space,所以当这个流随后有这个 getline()
调用时,它开始提取以这个 space 字符开始的字符,直到下一个换行符——五个字符(如果我指望我的话),但是将它转储到一个缓冲区中,该缓冲区由 std::string
保证,其长度仅足以容纳四个字符(因为,请记住,初始格式化提取运算符 >>
, 里面只转储了四个字符).
我忽略了一些细节,例如 std::string
负责自动跟踪尾随 '[=25=]'
,但底线是这是未定义的行为。 getline 调用提取它所提供的缓冲区保证容纳的更多字符。未定义的行为。一大堆未定义的行为。这不仅仅是你第二行输出中的四个字符不是你期望看到的四个字符,只是 getline()
实际上最终提取了 more 个字符,但是这里打印的 std::string
根据宪法完全有权利相信它仍然只有四个字符,只是它的内部缓冲区被踩得一塌糊涂。
我测试了以下代码以阐明我对 istream::getline()
的理解:
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
string s("abcd efgh\nijklmnopqrst");
string s1;
stringstream ss(s);
ss >> s1;
cout << s1 << endl;
ss.getline(&s1[0], 250, '\n');
cout << s1 << endl;
ss >> s1;
cout << s1 << endl;
getchar();
return 1;
}
然后控制台打印:
abcd
efg
ijklmnopqrst
但我认为应该是
abcd
efgh
ijklmnopqrst
此外,我发现调用ss.getline()
后s1
的大小与调用ss>>
后的大小相同,但调用ss>>
后大小会发生变化再一次。谁能帮我解析一下?
两件事。
首先,>>
不消耗空格,因此 getline
将检索它。
其次,这行不正确:
ss.getline(&s1[0], 250, '\n');
由于 getline
需要 std::basic_string
,只需传入字符串:
ss.getline(s1, 250, '\n');
在您的代码中,&s1[0]
可以访问已写入的底层缓冲区,但字符串的长度是单独存储的,并且仍然是之前读取的内容(这就是为什么 h
被丢弃)。但是,由于缓冲区溢出,此时您已经调用了未定义的行为。
ss.getline(&s1[0], 250, '\n');
此 getline()
调用的第一个参数是 char *
。 ss
完全不知道这个 char
缓冲区实际上来自 std::string
,它实际上是它的内部缓冲区。
使整个事件复杂化的是这个 std::string
给人的印象是它包含四个字符。因为这就是它的全部,在这一点上。
绝对没有任何事情可以导致 std::string
改变主意。仅仅因为一个指向其内部字符缓冲区的指针被传递给 getline()
,它开始相当粗鲁地在它上面涂鸦(导致未定义的行为,我稍后会推断),std::string
仍然认为它只包含四个字符。
同时,初始格式化输入运算符,>>
提取了初始字符,但是没有提取下面的space,所以当这个流随后有这个 getline()
调用时,它开始提取以这个 space 字符开始的字符,直到下一个换行符——五个字符(如果我指望我的话),但是将它转储到一个缓冲区中,该缓冲区由 std::string
保证,其长度仅足以容纳四个字符(因为,请记住,初始格式化提取运算符 >>
, 里面只转储了四个字符).
我忽略了一些细节,例如 std::string
负责自动跟踪尾随 '[=25=]'
,但底线是这是未定义的行为。 getline 调用提取它所提供的缓冲区保证容纳的更多字符。未定义的行为。一大堆未定义的行为。这不仅仅是你第二行输出中的四个字符不是你期望看到的四个字符,只是 getline()
实际上最终提取了 more 个字符,但是这里打印的 std::string
根据宪法完全有权利相信它仍然只有四个字符,只是它的内部缓冲区被踩得一塌糊涂。