Video Coming Soon...

Created by Zed A. Shaw Updated 2024-11-15 13:50:32

08: Strings

This exercise is about three things at once:

  1. Standard C++ conversions from other types to strings.
  2. Standard C++ string construction/formatting with sstream.
  3. The idea of finding patterns in code to help you understand and remember it. Even though the code looks large it's really using a couple of patterns repeatedly.

The idea behind this exercise is you learn how to see the pattern in the code, and using that pattern you can better understand the other two points. Learning how to identify patterns in code will help you tremendously going forward, so take your time.

The Starter Code

To do this, we'll do something a little different and have you enter in this starter code called ex08.cpp:

View Source file ex08.cpp Only

#include <sstream>
#include <iostream>
#include <string>

using namespace std;

int main() {
  // CHALLENGE 1 HERE

  string from_number = std::to_string(123456);
  string from_float = std::to_string(1.1000f);

  ostringstream msg;

  // CHALLENGE 2 HERE

  msg << "From number " << from_number << endl;
  msg << "From float " << from_float << endl;

  cout << "The result is: " << endl << msg.str();

  return 0;
}

This code introduces the concepts of "number to string conversion", "string to number conversion", and "constructing complex strings." These are all interconnected concepts that play well together, so let's break this down to learn the concepts.

1
The first line is a new header with #include <sstream> that brings up a "string stream." We'll use this later, but it's a way to construct a large string using the same techniques you used when you write text to the screen with cout.
2-7
These lines you've seen many times already, as they simply setup your common C++ code for the exercise.
8
This is where you will place the code for Challenge 1 below.
10
This converts the number 123456 into a string named from_number. This uses some future technology we'll learn later in the module, but the only new part is the to_string(123456) part at the end. This is a "function call" and what it does is tell the C++ compiler to run a command named to_string to convert a number to a string. When you call a function you add (, a parameter, and ) to complete the call. This function then takes your parameter, does something to it, and the result is something you can assign to an lvalue on the left.
11
This is similar to the line before, but we pass 1.1000f to std::to_string and it will also convert that into a string.
13
We now create a ostringstream and name it msg. An ostringstream is an "output string stream" and it works just like cout, but instead of printing text to the Terminal, it "prints" text into a big string. This is how you would construct a string from many other variables before you store or display it.
15
Here is where you'll put Challenge #2 later.
17
Now we use the msg just like we used cout before to "output" a complex string. It really is like writing to a fake Terminal. Just do the same thing you would do for printing, but use the ostringstream msg instead.
18
Same thing as 17, but now we add a message for the from_flow variable too.
20
This finally prints out msg using the cout like normal. The important part is at the end where you type msg.str(). This is another function call, but it's more a command to the msg stream that says, "Hey, msg make a string for me." This also shows that you can call a function without a parameter with just ().
22-23
Finally the ending of our C++ program like normal.

When you look at this code the only actually new things introduced are:

  1. ostringstream but that works just like cout except it puts the contents into a string and not the Terminal.
  2. Function calls, which we'll cover in depth later, but you should be able to grasp that they are commands that do special things for you like convert numbers to strings (std::to_string()) or tell msg to do something like dump its contents as a string (msg.str()).

WARNING Be sure you get this code working a little at a time before you continue. You should also notice that when you do not have line 20 written yet this code outputs nothing, but when you finish line 20, it outputs everything. That's because msg is storing everything for you until then. Study that carefully.

Learning About Patterns

Once you're confident enough with this code I want you to attempt a challenge. I'll periodically introduce little challenges so you can build confidence in your skills and work on your problem solving abilities. For this challenge I want you to read this code which will eventually go in the // CHALLENGE 1 HERE spot of ex08.cpp, but don't type it in just yet:

// this is CHALLENGE #1 BLOCK
int a_int = std::stoi("1234");
long a_long = std::stol("37812394");
long long a_long_long = std::stoll("-68354647782938476");
unsigned long a_unsigned_long = std::stoul("84938374");
unsigned long long a_ul_long = std::stoull("68354647782938476");
float a_float = std::stof("0.1234");
double a_double = std::stod("3.23499");
long double a_long_double = std::stold("0.00234002");

That's quite a lot, but a lot of it is "noise" hiding a pattern that you can see if you remove some of the noise. Let's simplify that first line and try to see the pattern:

auto a_int = stoi("1");

I removed the noise by doing two things:

  1. I ditched the type int and just set it to be auto removing the type as visual noise.
  2. I removed the std:: from the std::stoi because it's not necessary if we say using namespace std at the beginning. I would probably still have std::stoi in my own code to avoid possible conflicts, but for this exercise we can remove it.

Take a look at this and ask yourself if there's a pattern here, then look at that whole block simplified in the same way:

auto a_int = stoi("1");
auto a_long = stol("1");
auto a_long_long = stoll("-1");
auto a_unsigned_long = stoul("1");
auto a_ul_long = stoull("1");
auto a_float = stof("1.1");
auto a_double = stod("1.1");
auto a_long_double = stold("1.1");

Can you see the pattern now? Removing noise like this is an easy way to see patterns in code. Let's write out a pattern that will work with each line:

<type> a_<type> = sto<type_letter>(<string>);

Studying each part of the pattern we have:

<type>
The type of the variable, which we just set to auto to see the pattern, but will be int, long, unsigned long, etc.
a_<type>
We create a variable that's named a_ followed by the type name we used. We couldn't put a_auto for everything, but if you go look at the original code you can see it's mostly 1-for-1 except for where I get lazy and don't want to write the whole type.
=
This will assign to the a_<type> lvalue (remember those?) whatever value is produced by the operation on the right which is the rvalue. The operation on the right is a function call, which we talked about already, but we'll talk about it again.
sto<type_letter>
This is read as "string to <type_letter>" and it's simply converting whatever string you give it into the type that matches the letter. Looking at the <type> we can see the letter matches the first letters of the type, so ul is unsigned long and i is int.
(<string>)
This part is a bit tricky because it's the first time you're encountering a "function." We'll get into functions later, but for now just remember that you write a ( then a "parameter" and then close it with a ). This will tell the C++ compiler you want to "give" this parameter to an action (function, operation, command) named stoi or stoul.

Now to remember this code, you really only have to remember this pattern. At first that's not easy but as you spend time spotting these patterns you'll start to remember them, and later parts of C++ will even allow you to create code that replaces the patterns entirely.

Challenge #1

Take some time to study the pattern and then I want you to attempt Challenge #1. Using the pattern you've learned, I want you to recreate the code in CHALLENGE #1 BLOCK but doing it from "memory." It's not really from memory since you'll use a list of types, and the pattern to recreate the code. I'll demonstrate the first line, then you can do the rest.

First, we need the list of types you need to convert:

int
long
long long
unsigned long
unsigned long long
float
double
long double

The pattern is this:

<type> a_<type> = sto<type_letter>(<string>);

Plugging in the int we have this:

Putting all of that together we get:

int a_int = stoi("1234");

The numbers you convert can be totally made up since they aren't important for the exercise. You should attempt to recreate that block of code as closely as possible from memory using the pattern, and if you get stuck take a peek at the ex08.cpp code file for clues.

As you do this, remember the rules of effective code typing:

  1. Only add a little at a time.
  2. Compile it every time you make a change that you think is ready.
  3. Fix any compiler errors and warnings.
  4. Run it and fix any bugs you find.
  5. Repeat the process on the next line.

You can also turn this into a little game by keeping track of how many times you had to look at the CHALLENGE #1 BLOCK code. Maybe give yourself "10 hit points" so you have 10 chances to look at the code before your game is over. When you're done, move on to the next challenge, and then at the end of the exercise you'll see the full solution.

The Second Set of Patterns

You now have the next block of code that's a series of patterns, which construct a new string in msg. You are simply adding more text to output the results of your conversions above. Here's the block of code you'll have to replicate, and remember don't type this in, just review it:

// CHALLENGE #2 BLOCK
msg << "An int " << a_int << endl;
msg << "A long " << a_long << endl;
msg << "A long long " << a_long_long << endl;
msg << "An unsigned long " << a_unsigned_long << endl;
msg << "An unsigned long long " << a_ul_long << endl;
msg << "An float " << a_float << endl;
msg << "An double " << a_double << endl;
msg << "An long double " << a_long_double << endl;

This code also has a pattern, which should be easier to see than CHALLENGE #1 BLOCK. The only new thing in this block of code is using msg instead of cout. Review the beginning of this exercise to confirm your understanding of what this is doing.

Your challenge is to now do what I did with the first challenge, but using this code on your own:

  1. What's the pattern being used here?
  2. List out all of the elements of this pattern that you need. Are you going by the types, or the variable names?
  3. Recreate this block of code from memory using only the elements you've listed.
  4. If you get stuck, come back and look at this block for clues.
  5. As usual, remember to do this a single line at a time. If you catch yourself typing tons of code before compiling and running it, then delete what you typed and do it again correctly.

Solution

If you did everything right, you should have a ex08.cpp that is something like this:

View Source file ex08_solution.cpp Only

#include <sstream>
#include <iostream>
#include <string>

using namespace std;

int main() {
  int a_int = stoi("1234");
  long a_long = stol("37812394");
  long long a_long_long = stoll("-68354647782938476");
  unsigned long a_unsigned_long = stoul("84938374");
  unsigned long long a_ul_long = stoull("68354647782938476");
  float a_float = stof("0.1234");
  double a_double = stod("3.23499");
  long double a_long_double = stold("0.00234002");

  string from_number = to_string(123456);
  string from_float = to_string(1.1000f);

  ostringstream msg;

  msg << "An int " << a_int << endl;
  msg << "A long " << a_long << endl;
  msg << "A long long " << a_long_long << endl;
  msg << "An unsigned long " << a_unsigned_long << endl;
  msg << "An unsigned long long " << a_ul_long << endl;
  msg << "An float " << a_float << endl;
  msg << "An double " << a_double << endl;
  msg << "An long double " << a_long_double << endl;
  msg << "From number " << from_number << endl;
  msg << "From float " << from_float << endl;

  cout << "The result is: " << endl << msg.str();

  return 0;
}

Remember that you don't have to get the numbers exactly the same. They're entirely made up and aren't important to the exercise. What is important is everything else in this code. Try to find as many differences between your code and mine, then go back and fix your code to match mine.

Break It

  1. Have stoi or any of the number conversion functions attempt to convert a number that's too large. What happens?
  2. Try giving std::to_string something that's not a number.
  3. Try giving std::stod and std:stof a floating point (decimal) number that has too many numbers of precision. What happens?
  4. Try to write cout << msg directly to see the error you get. How is this different from cout << msg.str()?

Further Study

  1. Read the reference documentation at cppreference.com basic_string and try to understand as much as you can. Remember that you're a beginner so you won't understand everything at this point.
  2. See if you can use something from the documentation to change your solution.
  3. You know how to make variables, and msg.str() makes a string, so how would you store the msg.str() result into a variable and then use that?
  4. You can also try reading the documentation for basic_ostringstream but it may be too difficult to understand, and for now the sstream works like the cout you've been using.
  5. Go back through the code and add the std:: to each of the conversion functions in the CHALLENGE #1 BLOCK code. Which way do you like better?
Previous Lesson Next Lesson

Register for Learn C++ the Hard Way

Register to gain access to additional videos which demonstrate each exercise. Videos are priced to cover the cost of hosting.