Ready to practice?
Sign up to access interactive coding exercises and track your progress.
std::string for Text Handling
Store and manipulate text using the std::string class with built-in memory management.
Introduction to std::string
In the Literals lesson, we introduced C-style string literals:
#include <iostream>
int main()
{
std::cout << "Welcome, traveler!"; // "Welcome, traveler!" is a C-style string literal
return 0;
}
While C-style string literals work fine, C-style string variables are problematic: they behave oddly, are difficult to work with (e.g., you can't use assignment to change their value), and are dangerous (copying a larger C-style string into space allocated for a smaller one causes undefined behavior). In modern C++, C-style string variables should be avoided.
Fortunately, C++ introduced two additional string types that are much easier and safer: std::string and std::string_view (C++17). Unlike types we've introduced previously, std::string and std::string_view aren't fundamental types (they're class types, covered in the future). However, basic usage is straightforward and useful enough that we'll introduce them here.
Introducing std::string
The easiest way to work with strings and string objects in C++ is via the std::string type, which lives in the <string> header.
We can create objects of type std::string just like other objects:
#include <string> // allows use of std::string
int main()
{
std::string playerName {}; // empty string
return 0;
}
Just like normal variables, you can initialize or assign values to std::string objects as you would expect:
#include <string>
int main()
{
std::string playerName { "Marcus" }; // initialize playerName with string literal "Marcus"
playerName = "Sarah"; // change playerName to "Sarah"
return 0;
}
Note that strings can be composed of numeric characters as well:
std::string playerId { "5472" }; // "5472" is not the same as integer 5472!
In string form, numbers are treated as text, not as numbers, so they can't be manipulated as numbers (e.g., you can't multiply them). C++ will not automatically convert strings to integer or floating point values or vice-versa (though there are ways to do so covered in a future lesson).
String output with std::cout
std::string objects can be output as expected using std::cout:
#include <iostream>
#include <string>
int main()
{
std::string playerName { "Marcus" };
std::cout << "Player name: " << playerName << '\n';
return 0;
}
This prints:
Player name: Marcus
Empty strings will print nothing:
#include <iostream>
#include <string>
int main()
{
std::string empty {};
std::cout << '[' << empty << ']';
return 0;
}
Which prints:
[]
std::string can handle strings of different lengths
One of the most powerful features of std::string is storing strings of different lengths:
#include <iostream>
#include <string>
int main()
{
std::string playerName { "Marcus" }; // initialize playerName with string literal "Marcus"
std::cout << playerName << '\n';
playerName = "Alexander"; // change playerName to a longer string
std::cout << playerName << '\n';
playerName = "Max"; // change playerName to a shorter string
std::cout << playerName << '\n';
return 0;
}
This prints:
Marcus
Alexander
Max
In the above example, playerName is initialized with the string "Marcus", which contains six characters (five explicit characters and a null-terminator). We then set playerName to a larger string, and then a smaller string. std::string handles this seamlessly! You can even store really long strings in a std::string.
This is one of the reasons std::string is so powerful.
If `std::string` doesn't have enough memory to store a string, it will request additional memory at runtime using dynamic memory allocation. This ability to acquire additional memory makes `std::string` flexible, but also comparatively slow.
String input with std::cin
Using std::string with std::cin may yield some surprises! Consider the following example:
#include <iostream>
#include <string>
int main()
{
std::cout << "Enter your character name: ";
std::string characterName {};
std::cin >> characterName; // this won't work as expected since std::cin breaks on whitespace
std::cout << "Enter your class: ";
std::string characterClass {};
std::cin >> characterClass;
std::cout << "Your character is " << characterName << " the " << characterClass << '\n';
return 0;
}
Here's the results from a sample run of this program:
Enter your character name: Dark Knight
Enter your class: Your character is Dark the Knight
That isn't right! What happened? It turns out that when using operator>> to extract a string from std::cin, operator>> only returns characters up to the first whitespace it encounters. Any other characters are left inside std::cin, waiting for the next extraction.
So when we used operator>> to extract input into variable characterName, only "Dark" was extracted, leaving " Knight" inside std::cin. When we then used operator>> to extract input into variable characterClass, it extracted "Knight" instead of waiting for us to input a class. Then the program ends.
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() requires two arguments: the first is std::cin, and the second is your string variable.
Here's the same program using std::getline():
#include <iostream>
#include <string> // For std::string and std::getline
int main()
{
std::cout << "Enter your character name: ";
std::string characterName {};
std::getline(std::cin >> std::ws, characterName); // read a full line of text into characterName
std::cout << "Enter your class: ";
std::string characterClass {};
std::getline(std::cin >> std::ws, characterClass); // read a full line of text into characterClass
std::cout << "Your character is " << characterName << " the " << characterClass << '\n';
return 0;
}
Now our program works as expected:
Enter your character name: Dark Knight
Enter your class: Warrior
Your character is Dark Knight the Warrior
What is std::ws?
C++ supports input manipulators, which alter the way that input is accepted. The std::ws input manipulator tells std::cin to ignore any leading whitespace before extraction. Leading whitespace is any whitespace character (spaces, tabs, newlines) that occur at the start of the string.
Let's explore why this is useful. Consider the following program:
#include <iostream>
#include <string>
int main()
{
std::cout << "Choose weapon 1 or 2: ";
int weaponChoice {};
std::cin >> weaponChoice;
std::cout << "Now enter weapon name: ";
std::string weaponName {};
std::getline(std::cin, weaponName); // note: no std::ws here
std::cout << "You chose " << weaponName << " as weapon " << weaponChoice << '\n';
return 0;
}
Here's some output from this program:
Choose weapon 1 or 2: 2
Now enter weapon name: You chose as weapon 2
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 weapon name. However, it won't actually wait for you to enter your weapon name! Instead, it prints the final message, and then exits.
When you enter a value using operator>>, std::cin not only captures the value, it also captures the newline character ('\n') that occurs when you hit the enter key. So when we type 2 and then hit enter, std::cin captures the string "2\n" as input. It then extracts the value 2 to variable weaponChoice, leaving the newline character behind for later. Then, when std::getline() goes to extract text to weaponName, it sees "\n" is already waiting in std::cin, and figures we must have previously entered an empty string! Definitely not what was intended.
We can amend the above program to use the std::ws input manipulator, to tell std::getline() to ignore any leading whitespace characters:
#include <iostream>
#include <string>
int main()
{
std::cout << "Choose weapon 1 or 2: ";
int weaponChoice {};
std::cin >> weaponChoice;
std::cout << "Now enter weapon name: ";
std::string weaponName {};
std::getline(std::cin >> std::ws, weaponName); // note: added std::ws here
std::cout << "You chose " << weaponName << " as weapon " << weaponChoice << '\n';
return 0;
}
Now this program will function as intended:
Choose weapon 1 or 2: 2
Now enter weapon name: Sword
You chose Sword as weapon 2
If using `std::getline()` to read strings, use `std::cin >> std::ws` input manipulator to ignore leading whitespace. This needs to be done for each `std::getline()` call, as `std::ws` is not preserved across calls.
When extracting to a variable, the extraction operator (`>>`) ignores leading whitespace and stops when encountering non-leading whitespace. `std::getline()` does not ignore leading whitespace unless you pass `std::cin >> std::ws` as the first argument. It stops extracting when encountering a newline.
The length of a std::string
If we want to know how many characters are in a std::string, we can ask a std::string object for its length. The syntax for doing this is different than you've seen before, but is pretty straightforward:
#include <iostream>
#include <string>
int main()
{
std::string playerName { "Marcus" };
std::cout << playerName << " has " << playerName.length() << " characters\n";
return 0;
}
This prints:
Marcus has 6 characters
Although std::string is required to be null-terminated (as of C++11), the returned length of a std::string does not include the implicit null-terminator character.
Note that instead of asking for the string length as length(playerName), we say playerName.length(). The length() function isn't a normal standalone function - it's a special type of function that is nested within std::string called a member function.
With normal functions, we call `function(object)`. With member functions, we call `object.function()`. We'll cover member functions in more detail later.
Also note that std::string::length() returns an unsigned integral value (most likely of type size_t). If you want to assign the length to an int variable, you should static_cast it to avoid compiler warnings about signed/unsigned conversions:
int nameLength { static_cast<int>(playerName.length()) };
Advanced note: In C++20, you can also use std::ssize() to get the length as a signed integral type (usually std::ptrdiff_t): std::cout << std::ssize(playerName). If storing the result in an int, use static_cast<int>(std::ssize(playerName)).
Initializing a std::string is expensive
Whenever a std::string is initialized, a copy of the string used to initialize it is made. Making copies of strings is expensive, so care should be taken to minimize the number of copies made.
Do not pass std::string by value
When a std::string is passed to a function by value, the std::string function parameter must be instantiated and initialized with the argument. This results in an expensive copy.
Do not pass `std::string` by value, as it makes an expensive copy. In most cases, use a `std::string_view` parameter instead (covered in the Introduction to std::string_view lesson).
Returning a std::string
When a function returns by value to the caller, the return value is normally copied from the function back to the caller. So you might expect that you should not return std::string by value, as doing so would return an expensive copy of a std::string.
However, as a rule of thumb, it is okay to return a std::string by value when the expression of the return statement resolves to any of the following:
- A local variable of type
std::string - A
std::stringthat has been returned by value from another function call or operator - A
std::stringtemporary that is created as part of the return statement
Advanced: std::string supports move semantics, which allows objects that will be destroyed to be returned by value without making a copy. In most other cases, avoid returning std::string by value. For C-style string literals, use std::string_view return type instead. In certain cases, std::string may also be returned by (const) reference to avoid making a copy.
Literals for std::string
Double-quoted string literals (like "Welcome, traveler!") are C-style strings by default (and thus, have a strange type).
We can create string literals with type std::string by using a s suffix after the double-quoted string literal. The s must be lower case.
#include <iostream>
#include <string> // for std::string
int main()
{
using namespace std::string_literals; // easy access to the s suffix
std::cout << "quest\n"; // no suffix is a C-style string literal
std::cout << "reward\n"s; // s suffix is a std::string literal
return 0;
}
Tip: The "s" suffix lives in the namespace std::literals::string_literals.
The most concise way to access the literal suffixes is via using-directive using namespace std::literals. However, this imports all of the standard library literals into the scope of the using-directive, which brings in a bunch of stuff you probably aren't going to use.
We recommend using namespace std::string_literals, which imports only the literals for std::string. This is one of the exception cases where using an entire namespace is generally okay, because the suffixes defined within are unlikely to collide with any of your code. Avoid such using-directives outside of functions in header files.
You probably won't need to use std::string literals very often (as it's fine to initialize a std::string object with a C-style string literal), but there are cases (involving type deduction) where using std::string literals makes things easier.
Advanced note: "Quest"s resolves to std::string { "Quest", 5 } which creates a temporary std::string initialized with C-style string literal "Quest" (which has a length of 5, excluding the implicit null-terminator).
Constexpr strings
If you try to define a constexpr std::string, your compiler will probably generate an error:
#include <iostream>
#include <string>
int main()
{
using namespace std::string_literals;
constexpr std::string playerName { "Marcus"s }; // compile error
std::cout << "Player name: " << playerName;
return 0;
}
This happens because constexpr std::string isn't supported at all in C++17 or earlier, and only works in very limited cases in C++20/23. If you need constexpr strings, use std::string_view instead (discussed in the Introduction to std::string_view lesson).
Summary
- std::string is the easiest way to work with strings in C++, defined in the
<string>header - Initialization: Can be initialized with string literals or assigned new values dynamically
- Dynamic sizing:
std::stringautomatically handles strings of different lengths by requesting additional memory at runtime - String input:
std::cin >>extracts characters up to the first whitespace- Use
std::getline(std::cin >> std::ws, str)to read full lines and ignore leading whitespace
- std::ws manipulator: Ignores leading whitespace; use it with
std::getline()to avoid issues with leftover newlines - Length: Use
str.length()to get the number of characters (returns unsignedsize_t); cast tointif needed - Member functions: Called using
object.function()syntax (e.g.,playerName.length()) - Expensive copying: Initializing or passing
std::stringby value makes expensive copies - Pass by reference: Never pass
std::stringby value; usestd::string_viewinstead (covered in next lesson) - Returning std::string: Generally safe to return local
std::stringvariables by value due to move semantics - String literals: Use the
ssuffix (e.g.,"text"s) to createstd::stringliterals; requiresusing namespace std::string_literals - Constexpr:
constexpr std::stringhas limited support; usestd::string_viewfor constexpr strings
std::string is powerful but comparatively slow due to dynamic memory allocation. For read-only string operations and function parameters, prefer std::string_view (covered in the next lesson).
std::string for Text Handling - Quiz
Test your understanding of the lesson.
Practice Exercises
Introduction to std::string
Practice using std::string for text manipulation.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!