std::getline is reading line where specified delimiter is not present? - c++

I want to print each object in console from the array of the following string (stored in a file):
{ beforechars [{Object1},{Object2},{Object3}] afterchars }
I'm doing it as follows:
std::ifstream is("content.txt");
std::getline(is, content, '[');
while (std::getline(is,content,'{')) {
std::getline(is,content,'}');
std::cout << content << std::endl;
}
in.close();
But i am getting this output:
Object1
Object2
Object3
] afterchars }
My understanding is that after Object3 iteration, the ifstream should have "}] afterchars }" and the while's guard shouldn't be true because there isn't any '{' char... Am i right? Where is the mistake?

The while condition doesn't work as you expect: getline() will read successfully until it reaches an '{' or to the end of the file if not.
So what happens here ?
when you've displayed Object3 your position in the stream is after the closing '}'.
The getline() in the while condition will read all the remaining of the file into content as it encounters no '{'. As it could read something successfully, the condition is evaluated to true.
the getline() within the while block then fails to read anything, so content will remain unchanged. The stream is then in fail status. No subsequent operation will succeed until you clear this state. But nothing visible happens for now in your code.
after displaying this last result, the next loop condition will fail.
Simple workaround:
A very easy workaround would be to keep the current position in the stream before looking for '{', and in case it was not found, go back to this position. Attention: this way of parsing files is not so nice from point of view of performance, but it's ok for small files.
std::getline(is, content, '[');
auto pos = is.tellg(); // read current position
while (std::getline(is,content,'{') && !is.eof()) {
std::getline(is,content,'}');
pos = is.tellg(); // update position before iterating again
std::cout << content << std::endl;
}
is.seekg(pos); // and get back to last position
The trick here is that if '{' is not found, after the getline() the stream is not yet in fail state, but eof() is already true. We can then end the loop and go back to the last recorded position.
Online demo

std::getline reads characters until delimiter (consuming it) or until the end of the stream. It sets failbit on stream only if there were no character consumed (called on empty/invalid stream).
So your loop will terminate only when stream is empty.
Streams interface allows only to see next character, there is no way to scan input and do read if there specific character present.
If you need random access to characters, you need to read input in string and then parse it (with regular expressions or something else.)

Related

Why istringstream appends '-1' character at the end of stream?

I've noticed that when I'm using istringstream eof() doesn't return true even if the whole string is "consumed". For example:
char ch;
istringstream ss{ "0" };
ss >> ch;
cout << ss.peek() << " " << (ss.eof() ? "true" : "false");
Outputs(VS2015):
-1 false
eof() isn't supposed to return true when all the data is consumed. It's supposed to return true when you attempt to read more data than is available.
In this example, you never do that.
In particular, peek is a "request", that won't set EOF even when there's nothing left to read; because you're, well, peeking. However, it will return the value of the macro EOF (commonly -1), which is what you're seeing when you output peek()'s result. Nothing is "appended" to the stream.
Read the documentation for functions that you use.
std::istream::peek
Peek next character Returns the next character in the input sequence,
without extracting it: The character is left as the next character to
be extracted from the stream.
If any internal state flags is already set before the call or is set
during the call, the function returns the end-of-file value (EOF).
http://www.cplusplus.com/reference/istream/istream/peek/

Reading from a file without skipping whitespaces

I'm trying to make a code which would change one given word from a file, and change it into another one. The program works in a way that it copies word by word, if it's normal word it just writes it into the output file, and if it's the one i need to change it writes the one i need to change to. However, I've enountered a problem. Program is not putting whitespaces where they are in the input file. I don't know the solution to this problem, and I have no idea if I can use noskipws since I wouldn't know where the file ends.
Please keep in mind I'm a complete newbie and I have no idea how things work. I don't know if the tags are visible enough, so I will mention again that I use C++
Since each reading of word is ended with either a whitespace or end of file, you could simply check whether the thing which stop your reading is end of file, or otherwise a whitespace:
if ( reached the end of file ) {
// What I have encountered is end of file
// My job is done
} else {
// What I have encountered is a whitespace
// I need to output a whitespace and back to work
}
And the problem here is how to check the eof(end of file).
Since you are using ifstream, things will be quite simple.
When a ifstream reach the end of file (all the meaningful data have been read), the ifstream::eof() function will return true.
Let's assume the ifstream instance that you have is called input.
if ( input.eof() == true ) {
// What I have encountered is end of file
// My job is done
} else {
// What I have encountered is a whitespace
// I need to output a whitespace and back to work
}
PS : ifstream::good() will return false when it reaches the eof or an error occurs. Checking whether input.good() == false instead can be a better choice here.
First I would advise you not to read and write in the same file (at least not during reading) because it will make your program much more difficult to write/read.
Second if you want to read all whitespaces easiest is to read whole line with getline().
Program that you can use for modifying words from one file to another could look something like following:
void read_file()
{
ifstream file_read;
ofstream file_write;
// File from which you read some text.
file_read.open ("read.txt");
// File in which you will save modified text.
file_write.open ("write.txt");
string line;
// Word that you look for to modify.
string word_to_modify = "something";
string word_new = "something_new";
// You need to look in every line from input file.
// getLine() goes from beginning of the file to the end.
while ( getline (file_read,line) ) {
unsigned index = line.find(word_to_modify);
// If there are one or more occurrence of target word.
while (index < line.length()) {
line.replace(index, word_to_modify.length(), word_new);
index = line.find(word_to_modify, index + word_new.length());
}
cout << line << '\n';
file_write << line + '\n';
}
file_read.close();
file_write.close();
}

Searching for char from end of file using seekg() c++

I have a file and I want to only output the last line to the console.
Here's my thoughts on how I would do it. Use file.seekg(0, ios::end) to put myself at the end of file.
Then, I could create a decrement variable int decrement = -1; and use a while loop
while (joke.peek() != '\n')
{
decrement--;
}
and get the starting position for my final line (going backwards from the end).
Knowing this, joke.seekg(decrement, ios::end); should set me to the beginning of the final line, and assuming I previously declared string input;
I would think that
getline(joke, input);
cout << input << endl;
would output it to the console.
Full code
void displayLastLine(ifstream &joke)
{
string input;
int decrement = -1;
joke.clear();
joke.seekg(0, ios::end);
while (joke.peek() != '\n')
{
decrement--;
}
joke.clear();
joke.seekg(decrement, ios::end);
getline(joke, input);
cout << input << endl;
}
The problem is, when I go to call this method for the file, nothing happens. When I step through it, the decrement just keeps on subtracting one, far beyond where a '\n' would be. To give an example of a text file, it would look something like this:
junk
garbage
skip this line
This is the line that we're looking for!
joke.seekg(0, ios::end);
This positions the file at the end.
while (joke.peek() != '\n')
Well, here's problem #1. When you're at the end of the file, peek() always returns EOF.
decrement--;
You write:
When I step through it, the decrement just keeps on subtracting one,
Well, what did you expect to happen, since that's the only thing that the loop does? The only thing your for loop does is subtract 1 from decrement. So that's what happens.
This a common problem: a computer does only what you tell it to do, instead of what you want it to do.
Although this is not optimal, your missing step is that before you peek(), you need to seek() back by one character. Then, peek() shows you the character at the current cursor position. Then, seek() back by one more character, and check peek() again, and so on.
But that still will not be sufficient for you. Most text files end with a newline character. That is, a newline is the last character in the file. So, even if you add back the missing seek(), in nearly all cases, what your code will end up doing is finding the last character in the file, the final newline character.
My recommendation for you is to stop writing code for a moment, and, instead, come up with a logical process for doing what you want to do, and describe this process in plain words. Then, discuss your proposed course of action with your rubber duck. Only after your rubber duck agrees that what you propose will work, then translate your plain language description into code.
peek does not move the file pointer, it reads the character without extracting it. So, you are constantly peeking the same value (character) and ending up in an infinite loop.
What would you need to do is something like:
while (joke.peek() != '\n')
{
joke.seek(-1, ios::cur);
}
That would put the input position at the \n, using the 2nd overload of seekg.
Please note that this is not a perfect solution. You need to check for errors and boundary conditions, but it explains your observed behaviour and gives you something to start fixing your code.
Your loop is actually only decrementing "decrement" and not using it to make the next search.
while (joke.peek() != '\n')
{
joke.seekg(decrement, std::ios::end);
decrement--;
}

C++ Read in file with only numbers (doubles)

I'm trying to read in a file that should contain only numbers in it. I can successfully read in the entire file if it meets that criteria, but if it so happened to have a letter in it, I need to return false with an error statement.
The problem is I'm finding it hard for my program to error when it finds this character. It can find it no problem, but when it does, it decides to just skip over it.
My code to read in the file and attempt to read in only numbers:
bool compute::Read (ifstream& stream)
{
double value;
string line;
int lineNumber = 1;
if (stream)
{
while (getline(stream, line))
{
lineNumber++;
istringstream strStream(line);
while (strStream >> value)
{
cout << value << endl;
}
}
}
return true;
}
The input file which I use for this is
70.5 61.2 A8 10.2
2
Notice that there is a non-number character in my input file. It should fail and return false at that point.
Currently, all it does is once it hits the "A", it simply returns to the next line, continuing the getline while loop.
Any help with this would be much appreciated.
The stringstream does catch those errors, but you're doing nothing to stop the enclosing loop from continuing when an error is found. You need to tailor your main loop so that it stops when the stringstream finds an error, which you can't do if the stringstream is being reconstructed on each iteration. You should create a for() loop instead and construct the stringstream in the declaration part. And the condition to the loop should be "as long as the stringstream and stream do not catch an error". For example:
for (std::istringstream iss; iss && std::getline(stream, line);)
{
iss.clear();
iss.str(line);
while (iss >> value)
{
std::cout << value << '\n';
}
}
Futhermore, it doesn't look like you need to use std::getline() or std::istringstream if you just want to print each value. Just do:
while (stream >> value) {
std::cout << value << '\n';
}
The above will stop when it finds an invalid character for a double.
You need the code to stop streaming but return false if it hasn't yet reached the end of the "input".
One way, possibly not the most efficient but still one way, to do that is parse a word at a time.
If you read first into a std::string and if it works (so the string is not empty) create an istringstream from that string, or reuse an existing one, and try streaming that into a double value.
If that fails, you have an invalid character.
Of course you can read a line at a time from the file, then split that into words, so that you can output a meaningful error message showing what line the bad text was found.
The issue of reading straight into doubles is that the stream will fail when it reaches end of file.
However it is possible to workaround that too because the reason for failing has an error status which you can check, i.e. you can check if it eofbit is set. Although the f in eofbit stands for "file" it applies to any stream not just files.
Although this method may sound better than reading words into a string first, I prefer that method in normal circumstances because you want to be able to report the error so you'll want to print in the error what was read.

Reading the input and checking if it ends with two newlines

I am writing a program in which the message is complete if the user presses the return key twice. And the way to check (as prescribed) is by checking to see if two consecutive '\n' occurrences have been read. I am confused as how to do this. Studying this thread at: How do I store a previous iteration in a while loop in C++?
I got some idea and did this:
for(new_advice; getline(cin, new_advice);) {
if(new_advice.substr(new_advice.length()-2,2).compare("\n\n") == 0) {
outstream<<endl;
outstream<<advice;
}
}
I got one error and a warning.
The error is:
libc++abi.dylib: terminating with uncaught exception of type std::out_of_range: basic_string
And the warning is
expression result unused (The expression is new_advice)
What should I do? Files and streams are a bit confusing (I am new to C++)
Thanks in advance :)
getline reads a line from the given stream and stores it in the variable given in the second parameter. It overwrites the contents of the given variable. Furthermore, it does not put the newline character into the variable. That means, you don't find two newline characters in the string, not even a single one.
When the user hits the return key twice, two lines are read by your C++ application, of which the second is empty.
You should detect that case by checking if the read line is empty. If this is the case, that means the user just hit the return key for the second time, and the previously read string was his actual input. That means that you need to store the input in a different string variable such that getline doesn't overwrite your variable.
Something like this:
string new_advice;
string input;
while (getline(cin, input)) {
if (input.empty()) {
// Do something with new_advice
}
else {
// Remember input for the case where the user hits return key again
new_advice = input;
}
}
for (std::string new_advice; std::getline(std::cin, new_advice);)
{
if ('\n' == std::cin.peek()) {
// Two consecutive new line characters. Do something with new_advice.
std::cin.ignore();
}
}
How about using e.g. std::string::rfind?
if (new_advice.rfind("\n\n") == new_advice.size() - 2)
{
// Last two characters were newlines
}