使用“<<”在 istream 上读取固定数量的字符

5
我在尝试几种C++文件读取策略时发现了这个问题。
ifstream ifsw1("c:\\trys\\str3.txt");
char ifsw1w[3];
do {
    ifsw1 >> ifsw1w;
    if (ifsw1.eof())
        break;
    cout << ifsw1w << flush << endl;
} while (1);
ifsw1.close();

文件的内容如下:
firstfirst firstsecond
secondfirst secondsecond
当我看到输出时,它被打印为:
firstfirst
firstsecond
secondfirst
我期望的输出应该像下面这样:
fir
stf
irs
tfi
.....
此外,我发现"secondsecond"没有被打印。我猜最后一次读取遇到了文件结束符,并且cout可能没有被执行。但第一个行为是不可理解的。
5个回答

7
提取运算符没有 ifsw1w 变量的大小概念,并且(默认情况下)将提取字符,直到遇到空格、null 或 eof。这些字符很可能存储在 ifsw1w 变量后面的内存位置中,如果您定义了其他变量,这将导致错误的 bug。
为了获得所需的行为,您应该能够使用:
ifsw1.width(3);

限制要提取的字符数量。


我离我的编译器很远,但根据这里的文档,它应该可以工作:http://www.cplusplus.com/reference/iostream/istream/operator%3E%3E/ - irritate
4
如果我有一个十六进制字符串"12AB",并且想将其转换为两个unsigned a, b; 变量,使得 a 等于 0x12, b 等于 0xAB。可能可以通过 s >> std::hex >> setw(2) >> a; 然后 s >> std::hex >> setw(2) >> b 来实现这一点。但是,由于没有适合 istream 的相应 setw() 操纵符,因此这种方法不起作用。在这种情况下,使用s.width(2)也无法解决问题。有办法限制解析的符号数量吗? - krokoziabla
1
@krokoziabla 我来这里寻找答案,正好是你问的这个问题,但很遗憾没有人回答你。 - Veggie

2

您正在浪费内存...它读取了您定义的3个字符(它会一直读取,直到遇到空格或换行符...)。

逐个字符读取以实现您所述的输出。

编辑:Irritate是正确的,这也可以运行(需要进行一些修复并且不能得到完全相同的结果,但这就是它的精神):

char ifsw1w[4];
    do{
        ifsw1.width(4);
        ifsw1 >> ifsw1w;
        if(ifsw1.eof()) break;
        cout << ifsw1w << flush << endl;
    }while(1);
    ifsw1.close();

2
  1. 使用std::istream& operator>>(std::istream&, char *)几乎不可能安全地使用,这与gets类似,您无法指定缓冲区的大小。流将只是向您的缓冲区写入,并越过其末尾。(上面的示例会触发未定义行为)。要么使用接受std::string的重载版本,要么使用std::getline(std::istream&, std::string)

  2. 检查eof()是不正确的。你应该用fail()代替。你真正关心的不是流是否到达了文件结尾,而是你没有成功地提取信息。

对于这样的事情,最好将整个文件读入一个字符串中,然后从那一点开始使用字符串操作。您可以使用stringstream来实现:

#include <string> //For string
#include <sstream> //For stringstream
#include <iostream> //As before

std::ifstream myFile(...);
std::stringstream ss;
ss << myFile.rdbuf(); //Read the file into the stringstream.
std::string fileContents = ss.str(); //Now you have a string, no loops!

2

这段代码存在未定义行为。当你像这样做:

char ifsw1w[3];

ifsw1 >> ifsw1w;
operator>>接收一个指向缓冲区的指针,但不知道缓冲区的实际大小。因此,它无法知道在读取两个字符后应该停止(注意应该是2而不是3--需要空间来终止字符串的'\0')。
总之,在探索读取数据的方法时,最好忽略这段代码。你可以从这样的代码中学到一些应该避免的事情。然而,通常更容易遵循一些经验规则,而不是试图研究可能出现的所有问题。
  1. Use std::string to read strings.
  2. Only use fixed-size buffers for fixed-size data.
  3. When you do use fixed buffers, pass their size to limit how much is read.
  4. When you want to read all the data in a file, std::copy can avoid a lot of errors:

    std::vector<std::string> strings;   
    std::copy(std::istream_iterator<std::string>(myFile),
              std::istream_iterator<std::string>(),
              std::back_inserter(strings));
    

你是指第三个选项,也就是width()选项吗? - bsoundra
@bsoundra:width选项是一个,调用read是另一个。 - Jerry Coffin

1

为了读取空格,你可以使用“noskipws”,它不会跳过空格。

ifsw1 >> noskipws >> ifsw1w;

但是如果你只想获取3个字符,我建议你使用get方法:

ifsw1.get(ifsw1w,3);

ifstream ifsw2("c:\trys\str3.txt"); char ifsw2w[3]; do{ ifsw2.get(ifsw2w,2); if(ifsw2.eof()) break; cout << ifsw2w << flush << endl; }while(1); if(ifsw2w != NULL) cout<< ifsw2w <<endl<<flush; ifsw2.close();这段代码只打印了第一行。而且是逐个字符地打印。然后它进入了一个无限循环,打印 null。我试图让上面的代码呈现为代码格式,但是它没有出现。请帮帮我。 - bsoundra

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