std::string and Its Methods
In the last lesson, you dealt with a fair amount of hassle working with C-style arrays. You had to leave room for the null terminator manually, remember function names from the <cstring> library, deal with overflows, and so on. That’s just how C-style arrays work.
Every mainstream language that came after C streamlines the use of strings by giving you a string type that’s more intuitive to use. C++ has such a type. It’s called std::string, and it’s what you’ll use going forward.
C-style strings do still show up, but mostly in older, lower-level code.
Creating a string
To create a string, you need to include the <string> header:
- Lines 5–6: Create strings named
nameandgreetingin two different ways. - Line 7: Shows that not initializing a string defaults to the empty string.
Behind the scenes, the size of each string is automatically inferred. They can even grow or shrink as you add or remove text, but there is no need to worry about the mechanics of memory management.
Building strings with concatenation
The + operator combines two strings into a new string, while += appends the string on the right to the one on its left.
In both cases, the string resizes automatically if the result needs more space than it currently has.
When you add a std::string variable and a string literal, the literal gets converted to an std::string automatically. Two string literals can’t be added directly, though. A raw literal (like "Emmy") has type const char[], and the type conversion is only triggered when a std::string is involved.
Length and character access
Length of a string can be found in two different ways:
Here, length() and size() do exactly the same thing. This redundancy exists because other container types you’ll see later, like std::vector, all use size(), and std::string follows the same naming convention for consistency. length() is just a friendlier alias for strings specifically.
The returned value for both functions is of type std::size_t.
You can also read a character in two ways:
email[i] and email.at(i) both grab a character at a given position, but they handle mistakes differently:
- Uncommenting line 10 prints
email[100], which leads to undefined behavior since the string has only 17 characters. - Uncommenting line 11 makes
.at(100)throw an exception instead. Exceptions haven’t been covered yet, but the short version is that it’s a mechanism for reporting errors so they can be handled separately. This makes.at()the safer choice when you’re unsure if the index is valid.
Finding and extracting pieces of a string
Say you need to split an email address into its local part and domain. You first need to locate the @ character:
- Line 7:
findsearches for a character (or substring) and returns the position of the first match. The returned value is of typestd::size_t. - Lines 8–9:
substr(start, length)pulls out a substring of the given length, starting at indexstart. Soemail.substr(0, at_pos)retrieves the local part. Iflengthis omitted, the substring runs to the end of the string instead, which is whyemail.substr(at_pos + 1)retrieves the domain.
If find can’t locate your required substring, it returns a special constant called std::string::npos. This is the largest value size_t can hold:
If you skip this check and find fails, npos flows into your later calls as if it were a real position.
Modifying a string in place
A string can be modified directly in place, so you don’t have to build a new one and reassign it to the variable:
- Line 7:
insert(pos, text)insertstextstarting at indexpos. - Line 10:
erase(pos, length)removeslengthcharacters starting at indexpos. - Line 13:
replace(pos, length, text)does both in one step: it removeslengthcharacters starting at indexpos, then insertstextin their place.
Comparing strings
Unlike C-style strings, with std::string, == compares the actual characters, the way you’d expect from day one. The same goes for <, >, and the other comparison operators, which compare alphabetically.
Converting between strings and numbers
You’ll need this constantly, whether you’re formatting output or parsing input:
- Line 6:
std::to_stringconverts a number to a string, as the name suggests. - Lines 10–11:
std::stoiandstd::stodgo the other way. Thestoin these names stands for “string to,” andianddfor the target types (intanddouble).
These functions expect you to pass a valid numeric string. A call likestd::stoi("abc")on the other hand would throw an exception, causing the program to halt.
Quick recap
Two earlier ideas, applied to strings:
- The
autokeyword: We sawautobriefly in chapter 2. It’s convenient when used with strings, sincesubstrand similar methods always returnstd::string, and writingautosaves you from typing it out:
auto local_part = email.substr(0, at_pos);- Passing strings by reference: This idea came up earlier in a different context (when covering “pass by reference”). When you pass a long string to a function (like one that contains a full document loaded from a file), you’re duplicating a large amount of data. Since copying so much data is expensive, passing it by
constreference avoids the copying and also prevents the function from modifying the original:
std::size_t count_words(const std::string& document) { /* ... */ }Checkpoint
What does this code print?
std::string filename = "report.pdf";
std::size_t dot_pos = filename.find('.');
if (dot_pos != std::string::npos) {
std::cout << filename.substr(dot_pos + 1) << "\n";
}You have a string code = "TMP_12345" and need to strip the "TMP_" prefix, modifying code directly rather than creating a new string. Which line does this?
Which line compiles without error?
What is id after this code runs?
std::string id = "USR-4821";
id.replace(0, 4, "BOT");
id.erase(id.length() - 4, 4);
id.insert(3, "#");