Which [[std::string]] methods handle everyday text is a short list you will use constantly: finding a substring, taking a piece of one, replacing part of one, removing characters, inserting text, comparing and converting between types. These cover the vast majority of the text processing that application code actually does.
Finding text
.find() searches for a substring and returns the position of the first match, or std::string::npos if there is none.
#include
#include
int main() {
std::string title = "The Silent Shelf";
std::size_t first = title.find("Shelf");
std::cout << "first: " << first << '\n';
if (first != std::string::npos) {
std::cout << "found: " << title.substr(first) << '\n';
} else {
std::cout << "not found\n";
}
std::cout << "rfind: " << title.rfind("e") << '\n';
std::cout << "missing: " << (title.find("Zebra") == std::string::npos) << '\n';
return 0;
}The output shows the first match, the last match, and the safe way to test for absence:
first: 11
found: Shelf
rfind: 13
missing: 1Always compare against std::string::npos rather than against -1. The difference matters because npos is the largest possible value of std::size_t, which is unsigned, so it can never be produced by accident.
Taking a piece
.substr() returns a new string starting at a position, with an optional length.
#include
#include
int main() {
std::string line = "ISBN: 978-1-4028-9462-6";
std::string label = line.substr(0, 4);
std::string digits = line.substr(5);
std::cout << "label: " << label << '\n';
std::cout << "digits: " << digits << '\n';
std::cout << "isbn: " << line.substr(5, 3) << '\n';
return 0;
}The output shows the three substrings:
label: ISBN
digits: 978-1-4028-9462-6
isbn: 97.substr() throws std::out_of_range if the position is past the end, so check the length when the input is not trusted.
Replacing, erasing and inserting
.replace() swaps one substring for another. .erase() removes characters. .insert() puts new text at a position.
#include
#include
int main() {
std::string title = "The Silent Shelf";
title.replace(4, 6, "Golden");
std::cout << "replaced: " << title << '\n';
title.erase(0, 4);
std::cout << "erased: " << title << '\n';
title.insert(0, "Shelf: ");
std::cout << "inserted:" << title << '\n';
return 0;
}The output shows each edit applied in turn:
replaced: The Golden Shelf
erased: Golden Shelf
inserted:Shelf: Golden ShelfNote that .replace() and .erase() take a position and a *count*, not a substring. To replace a substring you have to find it first.
Comparing and converting
.compare() returns a negative number, zero or a positive number, like strcmp. The comparison operators work too, so == and < are usually clearer.
For conversion, std::stoi and std::stod parse a number out of a string, and they throw std::invalid_argument when the text is not a number at all.
#include
#include
int main() {
std::string pages_text = "342";
int pages = std::stoi(pages_text);
std::cout << "pages: " << pages << '\n';
std::string price_text = "12.50";
double price = std::stod(price_text);
std::cout << "price: " << price << '\n';
std::string a = "apple";
std::string b = "apricot";
std::cout << "compare: " << a.compare(b) << '\n';
std::cout << "equal?: " << (a == std::string("apple")) << '\n';
return 0;
}The output shows the parsed numbers and the comparison result:
pages: 342
price: 12.5
compare: -2
equal?: 1When the text might not be a number, guard the conversion. std::stoi without a check on bad input throws, and in a program that reads user input that is the normal case rather than the exception.
Trimming and cleaning text
Two idioms cover most cleanup work. To trim whitespace, find the first and last non-space characters and take the substring between them.
#include
#include
std::string trim(const std::string& text) {
const std::string spaces = " \t\n\r";
std::size_t start = text.find_first_not_of(spaces);
if (start == std::string::npos) {
return "";
}
std::size_t end = text.find_last_not_of(spaces);
return text.substr(start, end - start + 1);
}
int main() {
std::string padded = " The Silent Shelf ";
std::cout << "trimmed: [" << trim(padded) << "]\n";
std::string shout;
for (char c : padded) {
shout += static_cast(std::toupper(static_cast(c)));
}
std::cout << "upper: " << trim(shout) << '\n';
return 0;
}The output shows both the trimming and the case change:
trimmed: [The Silent Shelf]
upper: THE SILENT SHELFThe cast to unsigned char before calling std::toupper is not optional. Passing a negative char value is , and it is one of the classic bugs in text handling.undefined behaviour
Iterating over a string
A std::string behaves like a sequence of characters, so it can be walked with a range-based for loop or with the standard algorithms.
#include
#include
#include
#include
int main() {
std::string title = "Fiction";
std::cout << "characters: ";
for (char c : title) {
std::cout << c << '.';
}
std::cout << '\n';
int vowels = 0;
for (char c : title) {
if (std::string("aeiou").find(c) != std::string::npos) {
++vowels;
}
}
std::cout << "vowels: " << vowels << '\n';
std::string upper = title;
std::transform(upper.begin(), upper.end(), upper.begin(),
[](unsigned char c) { return static_cast(std::toupper(c)); });
std::cout << "upper: " << upper << '\n';
return 0;
}The output shows all three results:
characters: F.i.c.t.i.o.n.
vowels: 3
upper: FICTIONThe lambda converts each character to upper case in place. The cast to unsigned char inside the lambda is the same safety rule as before: std::toupper is undefined for negative values.
The everyday checklist
- Find —
.find(),.rfind(),.find_first_of(),.find_last_of(); compare againstnpos. - Extract —
.substr(),.at(),.front(),.back(), and indexing. - Edit —
.replace(),.erase(),.insert(),.append(),+=. - Inspect —
.size(),.length(),.empty(),.compare(). - Convert —
std::stoi,std::stod,std::stol, and thestd::to_stringfamily. - Transform —
.assign(),.swap(),.clear()and the copy operations.
Quick reference
.find()returns a position orstd::string::npos; never compare it with-1..substr()takes a position and an optional count, and throws if the position is out of range..replace()and.erase()work on positions and counts, not on substrings.std::stoiandstd::stodthrow on text that is not a number — guard them.- Cast to
unsigned charbeforestd::toupperandstd::tolower. std::string::size_typeis the correct type for an index, notint.
Try it yourself: write a function that takes a line of the form "title|author|year" and returns the three parts separately. Use [[.find()]] rather than a stream extraction so it works on any separator. Then extend it to reject a line that is missing any part, and finally write a version that trims whitespace around each part before returning it.