Hello again! In this video, we are going to look at stream member functions and state. In the last video,
I used the open member function without explaining it.
Maybe it is obvious, but let's quickly go over it.
So, when we opened a file before, we did that by passing the name of the file as the argument to a stream
constructor.
So when the stream is constructed, it will also open the file.
Alternatively, we can just create an empty stream object, so that is not associated with any file.
And then later on, we can call the open member function with the file name as argument.
So this will open the file. And in terms of accessing the data through the stream, it works exactly
the same way.
So in both cases, the file mode will be applied; the default if we do not give one, or the one
that we give,
if we do give one as an argument to the constructor or open. If the file is an output file and does
not already exist, it will be created.
If there is data already in it, it will be truncated by default. And so on.
We checked whether the file was open, by using the stream name in an if statement.
We can also call the is_open member function, and this will return true if the file is open.
So if this returns true, we can go ahead and use the file.
C++ streams have a state, and the state will depend on the result of the last operation.
Usually you are only interested in this for input streams.
So the state of an input stream will depend on the result of the last time that it read data. That it fetched data from
the keyboard or a file.
If the operation succeeded, then the stream is in a good state, and good(() will return true. If the operation
fails, but it is nothing too serious.
For example, data in the wrong format. Then fail()
will return true.
And if something really serious happened like a disk failure, then bad() will be true and the stream
will be in a bad state.
And presumably so is your disk!
So let's quickly try that out.
So we are going to read some data from the keyboard into an int. And then, if this works, we should get
this.
good() should be true.
If we have something that is not easily convertible, then fail() will be true.
And we hope that bad() will not be true.
Enter a number...
Yeah.
You entered number
55.
So the conversion has succeeded. 55 can be converted to an int. Which is nice to know!
good() is true and the program went into this branch.
Let's try something that's not a number.
So we can see that fail() is true.
"banana" can not be converted to an int,
and we got this message. And I am not going to try and replicate the bad() case!
There are a couple of other member functions. clear() will reset the state of the stream to valid.
It does not actually change the buffer, though. eof() will return true after the stream has reached the
end of file,
the end of input. And a lot of people when they learn C++, they think, Aha, I can write a loop like
this.
While not end of input,
I can read some data.
And is this a good thing to do?
So let's find out.
So I have a file here. [It] just contains the numbers from 0 to 9, one on each line.
And then we are going to open this file.
And then, while we have not reached the end of file, we are going to read a number from this file and
print it out.
And we are we get numbers from zero to nine, as we expect. So, yep, this is a good way to do things(?)
If I now add a new line at the end of this file, which should not make any difference at all.
Let's see what happens.
Well, we get the numbers from 0 to 9, but we get another number nine.
So what is happening?
If we think about what is happening in the loop. So imagine we have just read the number 9. So x is
9 and it's going to print out 9.
Then on the next time through, it is going to get the next character.
Which will be this new line character.
The stream will ignore that. The value of x is still 9, so it will print 9 again. And then the
file has now reached the end of file.
So on the next iteration, when it checks for end of file, that will return true.
So it does get to the end of file eventually, but it has actually done an extra iteration.
So the correct way to do this loop is to do it like that.
So this operation, this right shift, will actually return the state of the file.
So while the state of the file [stream] is good, then it is going to print out the value that it has just read.
And then if the file stream is bad or has reached the end of file, this will be false.
So it does not print anything at all.
So we do not get the extra iteration which prints out the data that we do not want.
And there we are, we get the correct results, 0 to 9.
And in fact, if we introduce something in the file which will cause the stream [read] to [fail], then that will
get picked up as well.
So there we are.
It does the first three numbers, and then it finds that there is something here which cannot be converted.
So the stream state is fail, and this loop will terminate.
So we need to think about how we are going to deal with invalid input.
The obvious approach is to have some sort of Boolean which will tell us whether the input is true or
not.
Then we have a loop which checks that, and this loop will keep repeating until the user enters some
valid input.
So we are going to read a number from the keyboard and then, if this is a valid number, good() will return
true.
And then we can set the Boolean flag to true.
If the user enters something which is not a number, then fail()
will be true. And we can ask them to try again, and read their next effort.
And then we can keep on looping until they get it right!
So I will enter something which is a number, good() will be true, and it should say "You entered the number"
Yes!
And then the flag will be true and then the loop will terminate.
If I enter something which is not a number...
So in fact, we have...
Yes, we have an infinite loop.
So why is that?
The problem is that the stream goes into a fail state when it gets invalid input, and it will remain
in the fail state indefinitely.
So we need to call clear() to reset the state of the stream.
And it still does not work!
So what is going on?
We need to look a bit more closely at what happens when this statement is executed: cin, right shift,
x.
The stream will go to the first character in its input buffer, and it will start trying to convert the
characters in that buffer to an int. And it will only stop when it encounters some white space.
If the first character is something that cannot be converted into an int, it will stop immediately
and leave that character in the buffer.
Then the next time through, it'll go to the first character in the input, which is still the same one
that made it fail before, and that will make it [fail] again.
So the problem is that it keeps reading the same user input, over and over again.
It does not just discard the user input and go onto the next one.
So the solution is to force cin to flush its buffer, to get rid of all the stale [characters] from the last
user input, and then it can start processing the new lot of user input.
Unfortunately, input streams do not support flush, but there is a member function called ignore(). And that will
remove characters from the buffer.
How many characters does it remove?
Well, we can tell it. So we can say, "remove 20 characters."
But there is a problem there.
If the user types in five characters and then presses the return key, then this "ignore" is going to
ignore the next 20 characters.
So the five characters that the user types are going to be ignored, and then the 15 characters
after that are going to be ignored. Which is not what we want. So we can provide a second argument, which
is a new line character.
So this means if you get a new line before 20 characters, then stop there.
So this will either remove the next 20 characters or everything up to the next new line. Whichever
comes first. And the characters which are removed from the buffer are thrown away.
You can cannot access them again, they are no use.
They do not exist.
Okay, so I have added that to the program. So we are now ignoring the next 20 characters or up to the
new line, whichever happens first.
So we just check it still works with numbers.
Yes.
"Banana"
So, "Please try again and enter a number".
So that seems to work.
OK, and it is still waiting for input, so I need to press control-C to stop it.
What happens if I put in more than 20 characters?
So it will ignore the first 20 characters. Those are going to be removed and thrown out of the buffer.
But there are still some characters left in the buffer, which are going to cause the read operation
to fail.
So we need to increase the number a bit.
So maybe,
we should make it 100. But someone might type in 100 characters. What about a thousand, that should
be enough!
No one is going to sit there and type a thousand characters.
Well, maybe not. But maybe someone might post in the contents of a file by mistake, or their
cat might see sit on the keyboard and generate lots of keystrokes.
So the best way to do this would be to use the buffer size, so any data that might be in the buffer
at all, no matter how much.
So we are going to completely discard the entire buffer and start with the empty buffer.
And C++ does actually provide an expression for finding that. It is, rather an ugly one.
So it is in numeric limits.
This requires the limits header, so let's include that...
And then it is the numerical limits for the stream size and we want the max.
So this will give us the maximum number of characters that can be stored in the buffer.
So if we remove all those, then we will have an empty buffer.
Or if there is a new line before that, so that will just discard the next bit of input.
So let's try that.
So we now have the cat sitting on the keyboard.
(Miaow!) OK, I think that is enough. I am sure you get the idea.
So there you are.
We only get prompted once, so everything in there was thrown away.
Okay, so that is it for this video.
I will see you next time.
Meanwhile, keep coding!