WEBVTT

00:00.480 --> 00:08.100
Hello again! In this video, we are going to look at stringstreams. The C++ library gives us several

00:08.160 --> 00:09.360
different types of streams.

00:09.870 --> 00:13.950
The most basic one is ios, which we are probably not going to use directly.

00:15.540 --> 00:21.060
iostream is for input and output operations. fstream is for file operations.

00:21.570 --> 00:24.540
And now we have stringstream for string operations.

00:25.140 --> 00:26.640
So how does that work?

00:30.820 --> 00:34.870
There is an input and an output string stream. They are defined in the sstream header.

00:35.500 --> 00:41.350
They have a data member, which is a standard string, and they have the same member functions as the other

00:41.350 --> 00:42.370
stream classes.

00:43.720 --> 00:48.190
So these are really wrappers around the string class, to make it look like a stream object.

00:48.910 --> 00:52.840
If you are familiar with Design Patterns, you probably recognize that as an example

00:52.840 --> 00:58.750
of the adapter pattern. That is where you have a class which has an interface which makes it look and

00:58.750 --> 01:00.340
behave like a different class.

01:00.880 --> 01:06.130
In this case, we have a string class which has an interface to make it look and behave like a stream

01:06.130 --> 01:06.550
object.

01:09.230 --> 01:15.230
For the output string stream, this has an empty string object. And then we can use stream operations

01:15.230 --> 01:16.790
to store data in that string.

01:17.150 --> 01:24.110
So, for example, we can use the left shift operator to add the data to the string. To get at

01:24.110 --> 01:29.450
this data we call the str() member function, and that will return a copy of the stream's string.

01:32.420 --> 01:37.700
One thing we could do with an output string[stream] is to implement our own version of the library to_string function.

01:38.330 --> 01:43.490
You will remember, when we looked at converting numbers to strings, that this function will take an int

01:43.490 --> 01:50.090
or a double and return a string which has characters which correspond to the digits of that number.

01:52.000 --> 01:53.650
We can now implement our own version.

01:53.770 --> 01:56.410
I am going to give it a slightly different name to make sure it does not

01:56.410 --> 01:58.510
get confused with the library function.

01:59.470 --> 02:03.280
This is going to take an int, and it will return the string with the characters.

02:04.510 --> 02:12.160
We create an output stringstream object in the function body, and then we send the argument to the stream.

02:12.460 --> 02:16.750
So that is just like sending an integer to the screen or to a file.

02:17.620 --> 02:22.990
The left shift operator is going to do this actual conversion of converting the bits in this int into

02:22.990 --> 02:24.670
the characters for the digits.

02:25.030 --> 02:30.280
And instead of sending it off to the monitor or into a file, it is going to store it in its internal

02:30.280 --> 02:30.670
string.

02:31.840 --> 02:36.340
And then we call the str() member function to get a copy of the string and we return it.

02:37.750 --> 02:42.490
So if we call this function with an argument of 42, this will return the string with the characters

02:42.490 --> 02:43.690
'4' and '2'.

02:45.040 --> 02:48.310
And this does not just work for int, it will work for doubles.

02:48.610 --> 02:53.860
And, in fact, it will work for any type where you can use the left shift operator to send the data from

02:53.860 --> 02:55.270
the object to a stream.

02:57.680 --> 03:02.750
So we can, in fact, write a generic version of this. This will work with any type that supports

03:02.750 --> 03:03.050
that.

03:04.070 --> 03:05.390
So it's going to be a template class.

03:05.390 --> 03:08.990
The templates parameter is capital 'T'.

03:09.860 --> 03:15.010
When we call this function, the compiler will look at the function call, and work out what type T

03:15.050 --> 03:16.580
needs to be for the call to work.

03:17.330 --> 03:21.580
Then it will generate a new function in which T is replaced by that type.

03:22.190 --> 03:27.230
We pass an object of that type by const reference, because that is how we pass objects.

03:27.920 --> 03:29.910
The body of the function is exactly the same.

03:30.140 --> 03:31.520
We have an output stringstream.

03:31.880 --> 03:36.500
We send the argument to it, and we return the stream's string.

03:40.430 --> 03:41.510
So let's have a look at this.

03:41.810 --> 03:43.700
There is the template function.

03:44.630 --> 03:50.360
The main function is exactly the same as for the to_string example, except we are using our own version.

03:51.620 --> 03:53.600
The argument is a double.

03:53.900 --> 03:57.020
It's a floating point literal, which will have type double by default.

03:57.680 --> 04:00.260
So the compiler will see that T needs to be a double.

04:00.920 --> 04:04.310
It will generate the code for a function, with T replaced by double.

04:04.940 --> 04:07.580
And then that will just work like a normal function call.

04:08.960 --> 04:15.550
So this is going to convert the double 3.14159 into a string with characters

04:15.560 --> 04:19.400
'3', '.', '1', '4', '1', '5', '9'. And then we append that to another string.

04:22.130 --> 04:22.750
So there we are.

04:22.750 --> 04:25.310
We get "Hello, 3.14159".

04:27.030 --> 04:32.490
Another thing we can do with an output string is to build up input. And another design pattern

04:32.490 --> 04:33.620
there, the builder pattern.

04:34.250 --> 04:40.610
So you can use this to build up a output string as the data becomes available, or if it is more convenient

04:40.610 --> 04:42.410
than doing it all in a single operation.

04:44.240 --> 04:49.400
For example, if you have input and output mixed up and/or you are formatting the output, then it is

04:49.400 --> 04:54.860
usually more convenient to use a string stream. As in this example, where we read words in from

04:54.860 --> 04:58.040
the text, and then we format the word and print it out.

04:59.800 --> 05:06.490
So we could use a separate variable for each word and then have one long output statement with all

05:06.490 --> 05:07.150
the formatting.

05:07.500 --> 05:10.090
But it is much neater just to do it just like this.

05:10.510 --> 05:16.260
So we read one word. And then we format it, and send it to the output string stream.

05:16.720 --> 05:20.170
Then we read the next word, format that, and send it to the string.

05:20.560 --> 05:25.330
So that is going to append the data to the end of the string, inside the output stream buffer.

05:26.110 --> 05:30.940
It is just like sending data to the screen. So  the data that you send is going to be printed after

05:30.940 --> 05:37.180
the data that was sent before. Or if you are writing to file, the data is going to be added to

05:37.180 --> 05:39.130
the file, after the data that you sent earlier on.

05:39.820 --> 05:44.680
And then finally, we call the the member function to get the string and print out the result.

05:47.620 --> 05:48.970
So here is that code.

05:49.400 --> 05:50.500
So let's try it out.

05:51.640 --> 05:53.080
Please enter a word. "Hello".

05:54.370 --> 05:57.730
Please enter another word. "Goodbye".

05:59.170 --> 06:01.060
And there we are, we get "Hello" and "Goodbye".

06:01.300 --> 06:05.960
And they are both formatted with fixed field widths. Incidentally,

06:06.010 --> 06:10.550
One thing I meant to show you in the video on formatting and did not.

06:12.250 --> 06:16.450
If you have a fixed field width and there is too much data to fit in that field.

06:16.460 --> 06:18.190
So here we have 12 characters.

06:18.550 --> 06:21.770
So if I enter more than... 12 characters.

06:22.450 --> 06:26.830
So what will happen here is that the string is not going to attempt to format the data, because it is

06:26.830 --> 06:27.310
too big.

06:27.730 --> 06:30.550
Instead, it will just print it as it is.

06:32.300 --> 06:33.140
So there we are.

06:33.980 --> 06:35.780
We get all the characters that we entered.

06:37.400 --> 06:41.360
So the alternative would be to truncate the data to make it fit, which is not so good.

06:42.050 --> 06:47.780
If you have a choice between badly formatted data and bad data, then give me the bad formatting any

06:47.780 --> 06:48.020
day!

06:51.050 --> 06:56.810
With an input stringstream, we give it a copy of a string, which has some data. So we pass that

06:56.810 --> 06:58.670
to the constructor of the istringstream.

06:59.870 --> 07:05.330
Then we can read the data from the string and we can do this using any other operation that we would use

07:05.330 --> 07:08.420
for an input stream, such as the right shift operator.

07:10.010 --> 07:12.110
So this is useful for processing input.

07:12.530 --> 07:18.320
If the user enters a line of text, we can break it up into words or numbers. Or reading input from a

07:18.320 --> 07:18.710
file.

07:19.070 --> 07:24.530
It is also useful for parsing things like source code or XML or JSON.

07:27.780 --> 07:33.030
The way you would use this with input is that you would read the input into a string object, and then

07:33.030 --> 07:34.600
you can check the input data.

07:34.620 --> 07:36.150
You make sure it is in the right format.

07:36.480 --> 07:37.560
All the data is there.

07:37.860 --> 07:39.420
It is all sensible and so on.

07:40.140 --> 07:41.910
And if there is a problem, you can handle the error.

07:42.600 --> 07:48.450
And then if everything is fine, then you can pass that string object to an input string stream, and

07:48.450 --> 07:52.590
then you can read the data from the input string stream just like you would from cin.

07:52.830 --> 07:56.880
But this time you know there is not going to be any problem, because you have already validated the

07:56.880 --> 07:57.300
data.

07:58.800 --> 08:05.460
So the advantage of this is that you have separated the actual parsing of the data from the input, from the

08:05.670 --> 08:06.960
reading, and validation.

08:08.490 --> 08:11.250
So this allows you to put in this extra step here, if you want it.

08:14.050 --> 08:18.940
So the way you would use this is you would call getline(), so that will read a line of input from the keyboard

08:19.240 --> 08:20.540
into the string object.

08:21.340 --> 08:22.810
Then you can do your validation.

08:23.740 --> 08:30.160
Then you pass the line of input to an input stringstream. And then you can just get the data from the

08:30.160 --> 08:31.060
input string stream.

08:32.890 --> 08:37.690
So that's just like using cin, and you can loop over this until you get to the end of the input.

08:42.330 --> 08:44.880
So here is an example. I have a data file.

08:45.900 --> 08:47.070
Let's have a look at that...

08:49.240 --> 08:50.590
So there is our data file.

08:50.860 --> 08:52.180
It is rather strangely formatted.

08:52.790 --> 08:57.730
Maybe there is meant to be 4 numbers to a line. Maybe there is meant to be five, but something there is not quite

08:57.730 --> 08:58.060
right.

09:00.710 --> 09:07.400
So we open this file. And then we read a line from the file into this string. And then we could have

09:07.400 --> 09:09.380
a validation step where we check.

09:09.920 --> 09:17.840
And if the format is not correct, then we can fix it up, or handle an error, or raise an exception, or whatever

09:17.840 --> 09:18.930
we want to do.

09:19.910 --> 09:24.800
Then we pass the input to the stringstream. And then we get the numbers from the stringstream.

09:25.490 --> 09:31.010
We are going to add these numbers to a vector. So each time we read a number, we push it back onto the

09:31.010 --> 09:31.430
vector.

09:34.890 --> 09:40.290
And then we go through the vector. We have a range-for loop and we print out each element in the vector.

09:40.950 --> 09:45.600
We also want to calculate the average of these numbers, so we are going to add them together.

09:45.720 --> 09:50.190
We have a sum variable, which initially is zero. Then we add each element to it.

09:50.640 --> 09:55.950
So when we finish the range, this will have the total of all the elements. And then we divide that by

09:55.950 --> 09:59.040
the number of elements, which we get from the size() member function.

09:59.790 --> 10:01.530
And that should print out the average.

10:04.810 --> 10:05.740
So there we are.

10:07.060 --> 10:09.730
We have our numbers and we have our average.

10:10.810 --> 10:11.830
I hope that is correct.

10:15.300 --> 10:20.730
So what are the practical applications of string streams? Output stringstreams are useful when you are

10:20.730 --> 10:27.360
interacting with systems which expect to get a string, which has a particular format. So sometimes you

10:27.360 --> 10:32.520
need to have a string which has control characters in it, or it has fields which have certain

10:32.520 --> 10:37.420
widths. And that could be for a GUI or an operating system or some third party library.

10:38.610 --> 10:43.500
And with ostringstreams, it is much easier to build those up than it is with string operations.

10:45.180 --> 10:49.980
For i stringstreams, we can use these with getline to process input, either from the keyboard

10:49.980 --> 10:57.180
or from a file. With a istringstream, we can use that with getline() to process input and that can be

10:57.180 --> 10:58.980
from the keyboard or from a file.

10:59.280 --> 11:02.940
And that is much easier than using the right shift operator directly.

11:04.080 --> 11:05.850
Okay, so that is it for this video.

11:06.420 --> 11:07.330
I'll see you next time.

11:07.350 --> 11:09.330
But meanwhile, keep coding!
