Search

S.4.4b — An introduction to std::string

What is a string?

The very first C++ program you wrote probably looked something like this:

So what is “Hello, world!” exactly? “Hello, world!” is a collection of sequential characters called a string. In C++, we use strings to represent text such as names, addresses, words, and sentences. String literals (such as “Hello, world!\n”) are placed between double quotes to identify them as a string.

Because strings are commonly used in programs, most modern languages include a built-in string data type. C++ includes one, not as part of the core language, but as part of the standard library.

std::string

To use strings in C++, we first need to #include the <string> header to bring in the declarations for std::string. Once that is done, we can define variables of type std::string.

Just like normal variables, you can initialize or assign values to strings as you would expect:

Note that strings can hold numbers as well:

In string form, numbers are treated as text, not numbers, and thus they can not be manipulated as numbers (e.g. you can’t multiply them). C++ will not automatically convert string numbers to integer or floating point values.

String input and output

Strings can be output as expected using std::cout:

This prints:

My name is: Alex

However, using strings with std::cin may yield some surprises! Consider the following example:

Here’s the results from a sample run of this program:

Enter your full name: John Doe
Enter your age: Your name is John and your age is Doe

Hmmm, that isn’t right! What happened? It turns out that when using operator>> to extract a string from cin, operator>> only returns characters up to the first whitespace it encounters. Any other characters are left inside cin, waiting for the next extraction.

So when we used operator>> to extract a string into variable name, only “John” was extracted, leaving “Doe” inside std::cin, waiting for the next extraction. When we then used operator>> to get variable age, it extracted “Doe” instead of waiting for us to input an age. We are never given a chance to enter an age.

Use std::getline() to input text

To read a full line of input into a string, you’re better off using the std::getline() function instead. std::getline() takes two parameters: the first is std::cin, and the second is your string variable.

Here’s the same program as above using std::getline():

Now our program works as expected:

Enter your full name: John Doe
Enter your age: 23
Your name is John Doe and your age is 23

Mixing std::cin and std::getline()

Reading inputs with both std::cin and std::getline may cause some unexpected behavior. Consider the following:

This program first asks you to enter 1 or 2, and waits for you to do so. All good so far. Then it will ask you to enter your name. However, it won’t actually wait for you to enter your name! Instead, it prints the “Hello” line, and then exits. What happened?

It turns out, when you enter a value using cin, cin not only captures the value, it also captures the newline. So when we enter 2, cin actually gets the string “2\n”. It then extracts the 2 to variable choice, leaving the newline stuck in the input stream. Then, when std::getline() goes to read the name, it sees “\n” is already in the stream, and figures we must have entered an empty string! Definitely not what was intended.

A good rule of thumb is that after reading a value with std::cin, remove the newline from the stream. This can be done using the following:

If we insert this line directly after reading variable choice, the extraneous newline will be removed from the stream, and the program will work as expected!

Rule

If reading values with std::cin, it’s a good idea to remove the extraneous newline using std::cin.ignore().

What’s that 32767 magic number in your code?

That tells std::cin.ignore() how many characters to ignore up to. We picked that number because it’s the largest signed value guaranteed to fit in a (2-byte) integer on all platforms.

Technically, the correct way to ignore an unlimited amount of input is as follows:

But this requires remembering (or looking up) that horrendous line of code, as well as remembering what header to include. Most of the time you won’t need to ignore more than a line or two of buffered input, so for practical purposes, 32767 works about as well, and has the benefit of being something you can actually remember in your head.

Throughout these tutorials, we use 32767 for this reason. However, it’s your choice of whether you want to do it the “obscure, complex, and correct” way or the “easy, practical, but not ideal” way.

Appending strings

You can use operator+ to concatenate two strings together (returning a new string), or operator+= to append a string to the end of an existing string).

Here’s an example of both, also showing what happens if you try to use operator+ to add two numeric strings together:

This prints:

4511
45 volts

Note that operator+ concatenated the strings “45” and “11” into “4511”. It did not add them as numbers.

String length

If we want to know how long a string is, we can ask the string for its length. The syntax for doing this is different than you’ve seen before, but is pretty straightforward:

This prints:

Alex has 4 characters

Note that instead of asking for the string length as length(myName), we say myName.length().

The length function isn’t a normal standalone function like we’ve used up to this point -- it’s a special type of function that belongs to std::string called a member function. We’ll cover member functions, including how to write your own, in more detail later.

Conclusion

std::string is complex, leveraging many language features that we haven’t covered yet. It also has a lot of other capabilities that we haven’t touched on here. Fortunately, you don’t need to understand these complexities to use std::string for simple tasks, like basic string input and output. We encourage you to start experimenting with strings now, and we’ll cover additional string capabilities later.

Quiz time

Question #1


Write a program that asks the user to enter their full name and their age. As output, tell the user how many years they’ve lived for each letter in their name (for simplicity, count spaces as a letter).

Sample output:

Enter your full name: John Doe
Enter your age: 46
You've lived 5.75 years for each letter in your name.

Show Solution


S.4.4c -- Using a language reference
Index
6.x -- Chapter 6 summary and quiz

535 comments to S.4.4b — An introduction to std::string

  • Imagine

    In the solution to the question why was

    static_cast used? is name.length() not already an int?

  • Brian

    Hi. Thanks for the great work on these tutorials, I'm finding them really helpful.

    I have a query about the quiz question here:
    When I call std::cin.ignore after a successful std::getline, I'm being prompted for another input again - presumably because the cin buffer is empty. I'm wondering if there's a way to only call std::cin.ignore if the buffer is not empty or whether there's something else I've done wrong?
    Here's my getName() function

  • Martin

    Suggestion: I think the explanation
    "Note that instead of asking for the string length as length(myName), we say myName.length().

    The length function isn’t a normal standalone function like we’ve used up to this point -- it’s a special type of function that belongs to std::string called a member function. We’ll cover member functions, including how to write your own, in more detail later."
    is very helpful for all those who don't know member functions. However, member functions are used earlier already, e.g.

    on this page and

    in chapter O, so maybe the explanation should come earlier, too.

  • TavonCpp

    was creating agePerLetter really necessary?

  • Tony

    Hey @nascardriver, how about this solution?

    At first I haven't thought about data-type priority. But since "double" has higher priority, the final result (double/string) will be a double. Are there any issues with this code? Thanks again :)

  • It's+Me

    Pretty neat and confusing Quiz :D

  • Math

    why did we have to make it int? Isn't the length already a number?

    • It's+Me

      I questioned that myself too!
      any answer?

      • nascardriver

        `length()` returns an unsigned integer. Because unsigned integers don't always do what we want, a cast to `int` is used.

        • yagoo

          If i do something like this:

          static_cast<double>(myAge) / myName.length()

          The other operand is already a double, so even if myName.length() is unsigned integer, it should be automatically converted to double which is higher priority right? Is this safe or length() still should be explicitly converted to int first?

          • nascardriver

            If you cast `myAge` to `double`, everything is fine, because `myName.length()` get converted to `double`.
            If you were cast `myAge` to `int`, you need a cast on `myName.length()` too, because otherwise `myAge` would be promoted to `unsigned int`.

  • Math

    Hello, I've heard that <string> header file was added to the <iostream> header file so I was wondering why you guys still include it, is it best practice or is it fine if I don't include it?

    • nascardriver

      iostream does not include string. Even if it did, it would be bad practice to rely on transitive includes. Include everything you use.

  • Pionoplayer

    Reposted because for some reason trying to use the code tags while editing the comment breaks the code block

    Hey I've run into a strange problem, specifically that visual studio doesn't seem to support concatenation via the + symbol.

    This throws a compile error, telling me that "expression must have an integral or unscoped enum type". Am I doing something wrong or do I need to screw around with settings somewhere in visual studio?

    EDIT: worth noting,

    works just fine for some reason.

    • nascardriver

      Code tags work after refreshing the page if you edit a comment. But code tags are broken right now anyway.
      `std::string` supports `+`, but "thing" is not a `std::string`, it's a string literal and string literals can't do anything. If you want to print one thing after the other, use `<<`.

      Using string concatenating for this is very wasteful. If you need to concatenate two string, use `std::string`.

      • Pionoplayer

        Ah, I get it. I don't remember exactly what I was doing that led to me discovering this, but I'll keep that in mind, thank you!

  • Luxary

    So, if std::cin captures whitespaces, which are then filtered and sent back to the input stream, then why did our previous examples using integers work fine?
    Say you have something pretty basic like this:

    We enter 2 for x and 3 for y, (which really are 2\n and 3\n), and it works just fine.
    Wouldn't the second std::cin have to deal with "\n3\n", with the first '\n' being the filtered newline from the first input?

    Does std::cin ignore leading whitespace while std::getline() doesn't? (Hence why cleaning with std::cin.ignore() is required).
    If that's the case, I feel like it should be emphasized to better explain why what we did so far worked fine, and why we should be careful from now on, knowing std::cin a little better.

  • Zolee

    Hi, I have written the code without static_cast, could you please point out why this is not a good way to do it?

    • nascardriver

      The solution used `int`, because humans usually don't say "I'm 10.3 years old". Humans round ages to the next lower integer: "I'm 10 years old.". If you make `age` a `double`, you don't need the cast.

  • tiara

    > operator>> only returns characters up to the first whitespace it encounters.

    does this operator ignores whitespace at the first of an input line? or just ignored them at the end?

    • nascardriver

      It ignores whitespace before the value. The trailing whitespace is left in the input buffer and either extracted or ignore the next time you try to read from it.

  • ashly

    Hey,

    >We picked that number because it’s the largest signed value guaranteed to fit in a (2-byte) integer on all platforms.
    Does this mean the size of a buffer is 2-byte signed integer? if so, we put a sequential of characters into a buffer, why its size considered as signed 2-byte integer value?

    • nascardriver

      There's no good reason behind this choice. The size of the buffer is unknown. If you want to do it properly, use `std::numeric_limits::max()`.

  • sami

    Isn't it better to say "into a string variable"?

    "To read a full line of input into a string..."

  • AE35_Unit

    Okay, got this one.  I must have missed something in the previous chapters, as I can't get getline() to work with int's, so I went to std::cin and added a line to .ignore(32767, '\n'); in case the age function is called again int the future.  Also, would there ever  be a time to use, std::cin.ignore(32767, "\n"); (note double-quotes "\n"),  Thanks as always.

    • nascardriver

      `std::getline` only works with strings, see https://en.cppreference.com/w/cpp/string/basic_string/getline
      `std::cin.ignore` wants an `int` (Oddly enough) as it's second argument, not a stirng.

      What's up with those "f_" prefixes?

      • AE35_Unit

        Thanks for clarification on getline, that's is what I thought but just wanted to be sure. With regard to std::ignore(32767, '\n'), you said it wants an 'int', is the 32767 the 'int' you are referring too?

        the "f_" prefix helps me to distinguish what variable I have chosen to work inside a function.  I could use age inside the function as well as outside ie. in main(), but using f_ inside a function helps me to remember which variables I'm using inside and outside of the functions. Is there a generally accepted standard for this?  I wouldn't mind switching over to that. Thanks again for your help.  cheers.

        • nascardriver

          > is the 32767 the 'int' you are referring too
          The '\n' is the int (Remember, `char` is an integral type).

          > the "f_" prefix
          I don't understand it, because you're not using the prefix in `main` and `calcYears`. Naming and formatting is up to the developer, as long as it's done consistently. There is no general best practice. Global variables are often prefixed with "g_". Variable names can be reused across different functions, in case that's what you're worried about.

  • Mike

    Hi nascardriver!

    I've found a few inconsistencies. First, the headers are not sorted alphabetically in any of the examples above. I also think it would be informative to add a line explaining that std::getline() is declared inside the "string" header.
    Also, you haven't zero-initialized variables "name" and "age" in the second example inside "string input and output" and also variable "name" in the last example inside "Mixing std::cin and std::getline()".

    I was also wondering what the value is when we're zero-initializing the string variable?

  • Denis

    In quiz answer: why do we need to explicitly cast name.length() to an int on line 14, since string.length() already returns an int?

    • nascardriver

      `string.length()` returns some unsigned integer type. Converting an unsigned integer to a signed integer is a narrowing conversion. Narrowing conversions aren't allowed in list initialization, because they can cause loss of data.

  • Gabe

    Quiz

Leave a Comment

Put all code inside code tags: [code]your code here[/code]