在文本模式下使用seekg()

时间:2014-11-21 06:56:47

标签: c++

在尝试以文本模式(Windows)读取简单的ANSI编码文本文件时,我遇到了一些奇怪的行为, seekg() tellg() ;任何时候我尝试使用 tellg(),保存它的值(作为pos_type),然后在以后查找它,我总是会在流中比我离开的地方更进一步。

最后我做了一次健全检查;即使我这样做......

int main()
{
   std::ifstream dataFile("myfile.txt",
         std::ifstream::in);
   if (dataFile.is_open() && !dataFile.fail())
   {
      while (dataFile.good())
      {
         std::string line;
         dataFile.seekg(dataFile.tellg());
         std::getline(dataFile, line);
      }
   }
}

...然后最终,进一步进入文件,行被截止一半。为什么会发生这种情况?

2 个答案:

答案 0 :(得分:4)

此问题是由libstdc ++使用当前剩余缓冲区与lseek64之间的差异来确定当前偏移量引起的。

使用read的返回值设置缓冲区,对于Windows上的文本模式文件,它返回在结束转换后放入缓冲区的字节数(即2字节{{1} } endline被转换为\r\n,windows似乎也会在文件的末尾附加一个虚假的换行符。

\n但是(mingw导致调用lseek64)会返回当前的绝对文件位置,一旦减去这两个值,你最终会得到一个偏离1的偏移量文本文件中的每个剩余换行符(额外换行符为+1)。

以下代码应显示问题,您甚至可以使用具有单个字符且没有换行符的文件,因为Windows会插入额外的换行符。

_lseeki64

对于具有单个#include <iostream> #include <fstream> int main() { std::ifstream f("myfile.txt"); for (char c; f.get(c);) std::cout << f.tellg() << ' '; } 字符的文件,我得到以下输出

a

第一次拨打2 3 时,显然关闭1。在第二次调用之后,文件位置是正确的,因为考虑了额外的换行符后已达到结束。

除了以二进制模式打开文件外,您还可以通过禁用缓冲来解决问题

tellg

但这远非理想。

希望mingw / mingw-w64或gcc可以解决这个问题,但首先我们需要确定谁负责修复它。我认为基本问题在于lsek的MS实现,它应该根据文件的打开方式返回适当的值。

答案 1 :(得分:0)

Thanks for this , though it's a very old post. I was stuck on this problem for more then a week. Here's some code examples on my site (the menu versions 1 and 2). Version 1 uses the solution presented here, in case anyone wants to see it .

:)

void customerOrder::deleteOrder(char* argv[]){
std::fstream newinFile,newoutFile;
newinFile.rdbuf()->pubsetbuf(nullptr, 0);
newinFile.open(argv[1],std::ios_base::in);
if(!(newinFile.is_open())){
     throw "Could not open file to read customer order. ";
} 
newoutFile.open("outfile.txt",std::ios_base::out);
if(!(newoutFile.is_open())){
     throw "Could not open file to write customer order. ";
} 
newoutFile.seekp(0,std::ios::beg);
std::string line;
int skiplinesCount = 2;

if(beginOffset != 0){
    //write file from zero to beginoffset and from endoffset to eof   If to delete is non-zero
    //or write file from zero to beginoffset    if to delete is non-zero and last record 
    newinFile.seekg (0,std::ios::beg);
    // if  primarykey < largestkey , it's a middle record
    customerOrder order;
    long tempOffset(0);

    int largestKey = order.largestKey(argv);
    if(primaryKey < largestKey) {
        //stops right before "current..." next record.
            while(tempOffset < beginOffset){
              std::getline(newinFile,line);
      newoutFile << line << std::endl;
              tempOffset = newinFile.tellg();
        }
        newinFile.seekg(endOffset);
        //skip two lines between records.
            for(int i=0; i<skiplinesCount;++i) {
             std::getline(newinFile,line);
            }
        while( std::getline(newinFile,line) ) {
            newoutFile << line << std::endl;
        } 
    } else if (primaryKey == largestKey){
        //its the last record.
        //write from zero to beginoffset. 
           while((tempOffset < beginOffset) && (std::getline(newinFile,line)) ) {
              newoutFile << line << std::endl;
              tempOffset = newinFile.tellg();
           } 
} else {
        throw "Error in delete key" 
    }
}  else {
//its the first record.
//write file from endoffset to eof 
//works with endOffset - 4  (but why??)
    newinFile.seekg (endOffset);  
    //skip two lines between records.
    for(int i=0; i<skiplinesCount;++i) {
        std::getline(newinFile,line);
    }
    while(std::getline(newinFile,line)) {
        newoutFile << line << std::endl;
   } 
}
newoutFile.close();
newinFile.close();

}

beginOffset is a specific point in the file (beginning of each record) , and endOffset is the end of the record, calculated in another function with tellg (findFoodOrder) I did not add this as it may become very lengthy, but you can find it on my site (under: menu version 1 link) :

http://www.buildincode.com