Text with std::string
Text as a value you can measure, join, search and change a character at a time: the handful of string methods that cover nearly all real work.
A string knows its own length
std::string is a value that grows and shrinks, unlike the character arrays C used. It knows how long it is, so title.size() is an answer rather than a walk to a terminator, and it can be copied, compared and returned from a function like any other value.
The size comes back as std::size_t, an unsigned whole number, which is why the type shows up in loops and as the result of a search. For reading and printing, treat it as an ordinary number.
Changing and searching
Square brackets reach one character, counting from zero: title[0] is the first. Assigning to it replaces that character in place, so title[0] = 'T'; fixes a lower-case opening without rebuilding the string. substr(start, count) copies out a piece, and find answers where a piece begins — or a special value meaning "nowhere", which you test against std::string::npos.
+ joins two strings into a new one, and += appends to the one on the left. Joining is a copy, which matters once strings get long or sit inside a tight loop; for a program like this one it is the clearest thing to write.
One character at a time
A range-based for walks the characters directly: for (char c : title). Reach for that whenever you want to look at every character, because it cannot run past the end — the bug that ruins hand-written index loops.
The characters are real characters, not words, so turning text into capitals means walking them and calling toupper on each. Text handling in C++ is built from these small pieces rather than from one large function, and that is why the same few methods keep coming back.
Text is bytes, and that is not always letters
A std::string holds bytes. For plain ASCII one byte is one character, so size() and the number of letters agree. For text with an accent, one character can take two or more bytes, and size() counts the bytes rather than the letters. title[0] then reaches a byte, and cutting the string at a position can split a character in half.
The standard string is not Unicode-aware. When the text comes from users, that distinction is worth knowing before you slice it: a limit of ten characters may be ten bytes, and a name that fits in one language may not fit in another.
Comparing and sorting text
== compares two strings character by character, case included, so "Dune" and "dune" are different values. < compares them in dictionary order, which is what std::sort uses on a list of strings. There is no automatic trimming: a trailing space is part of the value, and it is a common reason a search or a comparison misses.
Normalise the text before comparing when the input comes from outside — trim it, lower-case it — and the comparison then means what a reader expects. Doing that in one function keeps the rule in one place instead of repeating it at every call.
The string methods you will keep using
#include <iostream>
#include <string>
int main() {
std::string title = "the c++ course";
std::cout << "Length: " << title.size() << '\n';
std::cout << "Piece: " << title.substr(0, 3) << '\n';
title[0] = 'T';
std::cout << "Fixed: " << title << '\n';
std::size_t at = title.find("c++");
std::cout << "Found at " << at << '\n';
std::string upper;
for (char c : title) {
upper += (char)toupper(c);
}
std::cout << "Shouting: " << upper << '\n';
return 0;
}Measure it, search it, shout it
Run it and check each printed number against the string above it. Then print the last three characters with substr and rebuild the shouting line using a range-based loop over a second string.
A search that finds nothing answers with the value npos, not with a small negative number. Compare against std::string::npos rather than testing the result with a sign.
Positions, sizes and the unsigned trap
size() answers a std::size_t, an unsigned type. That is fine until you subtract: on an empty string, title.size() - 1 does not become minus one, it wraps to the largest value the type can hold. Check empty() first, or compare positions with < rather than subtracting.
Every position is a count from the front, and every count starts at zero. substr(2, 3) copies three characters beginning at position 2, so the last one it reads is position 4. If the start is past the end, or the count runs beyond it, substr throws std::out_of_range — a loud failure, which is better than the quiet one square brackets give.
A character is also a small number. toupper and tolower work on character codes and hand back an int, so the cast back to char keeps the type honest and silences the warning. Print the raw result and the stream shows a number; cast it and the stream shows the letter.
Three string mistakes: reading title[title.size()], which is the terminating character rather than a letter; passing a start past the end to substr, which throws; and forgetting #include <cctype> before toupper or tolower. Square brackets do not check, so an out-of-range read is undefined behaviour, not an exception.
Try these next
- Build a user name from a full name: lower-case it and replace spaces with dots.
- Count the vowels in a sentence with a range-based loop.
- Search for a word, print its position, and print a fallback message when
findreturnsnpos.
Initials, vowels and a length
Run it and check that every initial comes from the start of a word. Then change the name to your own, and add a line that prints the last three characters with `substr`.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does find answer when the text is not there?
