std::string
std::string owns a sequence of characters and grows as needed. It is the type to reach for whenever text is more than a fixed message: a name, a line read from a file, a path built from parts. It manages its buffer itself, so there is no length to pass around and no manual free.
The class is a container of chars with the usual operations, plus a few string-specific ones such as find and substr. This lesson walks through them in the order a program needs them: build, search, edit, convert numbers, and read the characters one by one.
Build a string
A std::string is constructed from a literal, from another string or empty. The + operator joins strings and literals into a new string, and += appends to the existing one without building a temporary for the result.
size() returns the number of characters, which is the number of bytes for ASCII text and not the number of visible characters for UTF-8; empty() is the readable test for zero length. Both are constant time.
The program builds a line from three parts, prints its size, then appends to it. The size grows by seven because " Again." is seven characters, including the space and the full stop.
#include <iostream>
#include <string>
int main()
{
std::string greeting = "Hello";
std::string name = "Ada";
std::string line = greeting + ", " + name + "!";
std::cout << line << '\n';
std::cout << "size: " << line.size() << " empty: " << line.empty() << '\n';
line += " Again.";
std::cout << line << '\n';
std::cout << "size: " << line.size() << '\n';
}
g++ -std=c++23 -Wall -Wextra construct.cpp -o construct
./construct
Hello, Ada!
size: 11 empty: 0
Hello, Ada! Again.
size: 18
Search and cut
find returns the index of the first match or std::string::npos when there is none. npos is the largest value the size type can hold, so a comparison with it is the standard way to test whether a search failed.
substr takes a start index and a count and returns a new string. It copies, which is fine for a word and wasteful in a loop over a large document; lesson 018 introduces the view that does not copy.
The program finds "brown" at index 10 and cuts five characters from there. The second search fails, and the comparison prints 1 for true.
#include <iostream>
#include <string>
int main()
{
std::string text = "the quick brown fox";
std::size_t pos = text.find("brown");
std::cout << "pos: " << pos << '\n';
std::cout << "word: " << text.substr(pos, 5) << '\n';
std::size_t missing = text.find("zebra");
std::cout << "missing == npos: " << (missing == std::string::npos) << '\n';
}
g++ -std=c++23 -Wall -Wextra find.cpp -o find
./find
pos: 10
word: brown
missing == npos: 1
Insert, replace, append, erase
std::string has an editing operation for each part of a name: insert at an index, replace a range, append at the end and erase a range. All of them take indices and lengths in characters, and all of them may move the rest of the buffer.
The program starts from notes.txt and turns it into list.txt.bak in four steps. The replace call swaps the five characters at index 6 for "list"; the erase at the end removes the draft- prefix that insert added first.
An index past the end throws std::out_of_range, and an index inside the string is checked only by the operation itself. When the indices come from a search, check the result against npos before using it.
#include <iostream>
#include <string>
int main()
{
std::string path = "notes.txt";
path.insert(0, "draft-");
std::cout << path << '\n';
path.replace(6, 5, "list");
std::cout << path << '\n';
path.append(".bak");
std::cout << path << '\n';
path.erase(0, 6);
std::cout << path << '\n';
}
g++ -std=c++23 -Wall -Wextra modify.cpp -o modify
./modify
draft-notes.txt
draft-list.txt
draft-list.txt.bak
list.txt.bak
Converting numbers
std::to_string turns a number into text: the int overload prints the digits, and the double overload prints six digits after the point, which is rarely what a user wants but is what the standard specifies. Lesson 063 shows the formatting library that solves the second case.
std::stoi and std::stod parse text back into numbers. They skip leading whitespace, stop at the first character that cannot belong to the number, and throw std::invalid_argument when there are no digits at all.
The program converts 42 and 1.5 to text, then parses " 17 " and "2.5" and does arithmetic on the results. The leading spaces disappear in the parse, and the arithmetic proves the values are numbers again.
#include <iostream>
#include <string>
int main()
{
std::string count = std::to_string(42);
std::string price = std::to_string(1.5);
std::cout << count << ' ' << price << '\n';
int n = std::stoi(" 17 ");
double d = std::stod("2.5");
std::cout << n + 1 << ' ' << d * 2 << '\n';
}
g++ -std=c++23 -Wall -Wextra numbers.cpp -o numbers
./numbers
42 1.500000
18 5
Walk the characters
A range-for loop over a std::string yields char values, one per byte. The loop body can classify them, build a new string or count something, and it does not need an index unless the position matters.
The program counts lowercase letters in a fixed sentence. The condition c >= 'a' && c <= 'z' is the portable test for an ASCII lowercase letter; std::islower from <cctype> does the same and also respects the current locale, which matters for non-English text.
A char is a small integer, so it can be compared with character literals directly. For text that is not ASCII, iterating bytes is not enough, and lesson 075 returns to that problem.
#include <iostream>
#include <string>
int main()
{
std::string text = "Hello, world";
int letters = 0;
int spaces = 0;
for (char c : text) {
if (c >= 'a' && c <= 'z') {
++letters;
}
if (c == ' ') {
++spaces;
}
}
std::cout << "lowercase: " << letters << " spaces: " << spaces << '\n';
}
g++ -std=c++23 -Wall -Wextra iterate.cpp -o iterate
./iterate
lowercase: 9 spaces: 1
Exercises
- Print the first and the last character of a string, and guard both reads with empty().
- Split "one,two,three" into three strings with find and substr, then rebuild the line with a different separator.
- Convert the string "12abc" with std::stoi and print the result. How many characters did it consume, and how would you find out?
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does
std::stringmanage for the program?Answer
The buffer that holds the characters
The string allocates, grows and frees its own buffer, so there is no length to pass and no free to call.
-
What does
findreturn when the text is not there?Answer
std::string::nposThe constant is the largest value of the size type, and it can be compared with the result.
-
What does
substrreturn?Answer
A new string with a copy of the requested range
The member returns a string by value, which is why the result owns its characters.
-
Does
size()count the terminating null character?Answer
No, it counts only the characters
The size is the number of characters, and the terminator is an implementation detail of c_str().
-
What does this program print?
#include <iostream> #include <string> int main() { std::string text = "Hello, Ada!"; std::cout << text << " / size: " << text.size() << " empty: " << text.empty() << '\n'; }Answer
Hello, Ada! / size: 11 empty: 0
The greeting has eleven characters, and the string is not empty.
-
What does this program print?
#include <iostream> #include <string> int main() { std::string text = "the quick brown fox"; std::cout << "pos: " << text.find("brown") << " / word: " << text.substr(10, 5) << " / missing == npos: " << (text.find("zebra") == std::string::npos) << '\n'; }Answer
pos: 10 / word: brown / missing == npos: 1
The word starts at index 10, substr copies it, and the absent word compares equal to npos.
-
What does this program print first?
#include <iostream> #include <string> int main() { std::string name = "draft-notes.txt"; name.replace(0, 5, "draft"); std::cout << name << '\n'; }Answer
draft-notes.txt
The first replace rewrites the same characters, so the first line is unchanged.
-
What does this program print?
#include <iomanip> #include <iostream> #include <string> int main() { std::cout << std::fixed << std::setprecision(6); std::cout << std::stoi("42") << ' ' << std::stod("1.5") << '\n'; std::cout << std::stoi("18") << ' ' << std::stoi("5") * 2 << '\n'; }Answer
42 1.500000 / 18 10
stoi parses the integer, stod the double with six decimals, and the last line multiplies 5 by 2.
-
What does this program print?
#include <cctype> #include <iostream> #include <string> int main() { std::string text = "Hello World"; int lower = 0, spaces = 0; for (char c : text) { if (std::islower(static_cast<unsigned char>(c))) ++lower; if (c == ' ') ++spaces; } std::cout << "lowercase: " << lower << " spaces: " << spaces; }Answer
lowercase: 8 spaces: 1
The loop counts the lower-case letters and the one space, and the two counts are printed.
-
What does
text.find("x") == std::string::nposmean?Answer
The character was not found
npos is the not-found value, and the comparison is the standard test.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev