开发者

How to get the last but not empty line in a txt file

I want to get the la开发者_运维问答st but not empty line in a txt file.

This is my code:

string line1, line2;
ifstream myfile(argv[1]);
if(myfile.is_open())
{
    while( !myfile.eof() )
    {
        getline(myfile, line1);
        if( line1 != "" || line1 != "\t" || line1 != "\n" || !line1.empty() )
            line2 = line1;
    }
    myfile.close();
}
else
    cout << "Unable to open file";

The problem is I cannot check the empty line.


Okay, let's start with the obvious part. This: while( !myfile.eof() ) is essentially always wrong, so you're not going to detect the end of the file correctly. Since you're using getline to read the data, you want to check its return value:

while (getline(myfile, line1)) // ...

Likewise, the logic here:

    if( line1 != "" || line1 != "\t" || line1 != "\n" || !line1.empty() )
        line2 = line1;

...is clearly wrong. I'm guessing you really want && instead of || for this. As it stands, the result is always true, because no matter what value line1 contains, it must be unequal to at least one of those values (i.e., it can't simultaneously contain only a tab and contain only a new-line and contain nothing at all -- but that would be necessary for the result to be false). Testing for both !line1.empty() and line1 != "" appears redundant as well.


Why not read the file backwards? That way you don't have to scan the entire file to accomplish this. Seems like it ought to be possible.

int main(int argc, char **argv)
{
   std::cout<<"Opening "<<fn<<std::endl;
   std::fstream fin(fn.c_str(), std::ios_base::in);
   //go to end
   fin.seekg(0, std::ios_base::end);
   int currpos = fin.tellg();
   //go to 1 before end of file
   if(currpos > 0)
   {
       //collect the chars here...
       std::vector<char> chars;
       fin.seekg(currpos - 1);
       currpos = fin.tellg();
       while(currpos > 0)
       {
           char c = fin.get();
           if(!fin.good())
           {
               break;
           }
           chars.push_back(c);
           currpos -= 1;
           fin.seekg(currpos);
       }
       //do whatever u want with chars...
       //this is the reversed order
       for(std::vector<char>::size_type i = 0; i < chars.size(); ++i)
       {
           std::cout<<chars[i];
       }
       //this is the forward order...
       for(std::vector<char>::size_type i = chars.size(); i != 0; --i)
       {
           std::cout<<chars[i-1];
       }
   }
   return 0;
}


It wouldn't be enough to change your ||'s to &&'s to check if the line is empty. What if there are seven spaces, a tab character, another 3 spaces and finally a newline? You can't list all the ways of getting only whitespace in a line. Instead, check every character in the line to see if it is whitespace.

In this code, is_empty will be false if any non-space character is found in the line.

bool is_empty = true;
for (int i = 0; i < line.size(); i++) {
    char ch = line[i];
    is_empty = is_empty && isspace(ch);
}

Full solution:

#include <iostream>
#include <fstream>
#include <cctype>
#include <string>

using namespace std;

int main(int argc, char* argv[]) {
    string line;
    string last_line;

    ifstream myfile(argv[1]);
    if(myfile.is_open())
    {
        while( getline(myfile, line) ) {
            bool is_empty = true;
            for (int i = 0; i < line.size(); i++) {
                char ch = line[i];
                is_empty = is_empty && isspace(ch);
            }
            if (!is_empty) {
                last_line = line;
            }
        }
        myfile.close();
        cout << "Last line: " << last_line << endl;
    }
    else {
        cout << "Unable to open file";
    }   

    return 0;
}


Additional to what the others said:
You can avoid reading whitespace by doing myfile >> std::ws before you call std::getline(). This will consume all leading whitespaces.

Then your condition reduces to !line1.empty(). This would also work when the line contains nothing but several whitespaces, for which your version fails.


I wasn't able to google an appropriate get_last_line function for my needs and here's what i came up with. You can even read multiple non-empty last lines by recalling the instream get_last_line func without resetting the seeker. It supports a 1 char only file. I added the reset parameter, which can be set to ios_base::end to allow output operations after reading the last line(s)

std::string& get_last_line(
        std::istream& in_stream, 
        std::string& output = std::string(), 
        std::ios_base::seekdir reset = std::ios_base::cur)
{
    output.clear();
    std::streambuf& buf = *in_stream.rdbuf();
    bool text_found = false;

    while(buf.pubseekoff(-1, std::ios_base::cur) >= 0)
    {
        char c = buf.sgetc();
        if(!isspace(c))
            text_found = true;
        if(text_found)
        {
            if(c == '\n' || c == -1)
                break;
            output.insert(0, sizeof c, c);
        }
    }

    buf.pubseekoff(0, reset);
    return output;
}

std::string& get_last_line(
        const std::string& file_name, 
        std::string& output = std::string())
{
    std::ifstream file_in(
        file_name.c_str(), 
        std::ios_base::in | std::ios_base::ate);
    if(!file_in.is_open())
    {
        output.clear();
        return output;
    }
    get_last_line(file_in, output);
    file_in.close();
    return output;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜