C++ · Beginner
Strings in C++
In short: std::string, from <string>, is the C++ type for text that can grow and be copied safely. Read a whole line with std::getline and a single word with std::cin >>, join with +, index with [] or .at(), search with .find(), cut with .substr(), compare with == and <, and convert with std::stoi and std::to_string.
Text as a value
A string literal such as "Mill Lane" is a fixed sequence of characters baked into the program. std::string, from the header <string>, is the type you actually work with: it owns its characters, grows when you append, and behaves like a value, so std::string copy = title; makes an independent copy that can be changed without touching the original. Assigning a literal to a std::string converts it, which is why std::string city = "Leeds"; works.
s.size() (or the identical s.length()) is the number of characters, and s.empty() says whether there are none. Individual characters are reached with s[i], counting from 0, or with s.at(i), which checks the index and throws std::out_of_range if it is bad. s.front() and s.back() are the first and last characters. Each of these gives a char, so comparisons against them use single-quoted literals: s[0] == 'T'.
+ joins strings, and += appends to one in place; either side of + may be a literal or a single char as long as the other side is a std::string. Two literals cannot be added, because a literal is not a std::string, and "Total: " + 5 compiles but does pointer arithmetic on the literal rather than building text. std::to_string(count) turns a number into a string for joining; std::stoi(text) and std::stod(text) go the other way and throw std::invalid_argument when the text is not a number. All of these have been available since C++11.
Comparison operators work as you would hope: == and != test for identical text, and < orders strings character by character using character codes, so upper-case letters sort before lower-case ones and "apple" comes before "apricot". s.find("milk") returns the index of the first match, or the special value std::string::npos when there is none; the same call with a second argument starts searching from that position. s.substr(start, count) copies out a piece; omitting count takes everything to the end. Between them, find and substr are enough to split simple delimited records.
Reading text has two forms with different rules. std::cin >> word skips leading whitespace and stops at the next space, tab or newline, so it reads exactly one word. std::getline(std::cin, line) reads everything up to the end of the line, including spaces, and discards the newline. Mixing them needs care: after std::cin >> n the newline that ended the number is still waiting in the input, and the next getline reads that empty remainder. Writing std::getline(std::cin >> std::ws, line) skips the leftover whitespace first.
Characters can be classified and converted with the functions from <cctype>: std::isdigit, std::isalpha, std::toupper, std::tolower. They take an int and expect a value in the range of unsigned char, so the safe spelling is std::toupper(static_cast<unsigned char>(c)). A range-based for over a string with a char& loop variable lets you change every character in place.
Syntax
#include <string>
std::string s = "text"; // from a literal
std::string t = s; // an independent copy
s.size() s.empty() // length, emptiness
s[i] s.at(i) s.front() s.back() // single characters (char)
s + t s + "lit" s + 'c' s += t // joining and appending
s == t s != t s < t // comparison by character codes
s.find("sub") // index of first match, or std::string::npos
s.find('c', from) // search a char, starting at index from
s.substr(start, count) // copy of a piece; count may be omitted
std::getline(std::cin, s); // a whole line, newline removed
std::cin >> s; // one whitespace-delimited word
std::getline(std::cin >> std::ws, s); // skip leftover whitespace first
std::to_string(42) // "42"
std::stoi("42") std::stod("2.5") // string to int / double (C++11)std::string::npos is the largest possible size value and is the conventional answer for "not found". Compare against it with != rather than testing for -1.
Members you will reach for first
| Call | Result | Example on s = "ledger" |
|---|---|---|
| s.size() | number of characters | 6 |
| s[2] | the char at index 2 | 'd' |
| s.back() | the last char | 'r' |
| s.find("ge") | index of the first match or npos | 3 |
| s.substr(1, 3) | three chars from index 1 | "edg" |
| s.substr(4) | from index 4 to the end | "er" |
| s + "s" | a new string | "ledgers" |
| s == "ledger" | bool | true |
Reading lines, measuring, indexing and changing case
A catalogue label is built from two lines of input; the copy is upper-cased without touching the original.
#include <cctype>
#include <iostream>
#include <string>
int main() {
std::string title;
std::getline(std::cin, title);
std::string author;
std::getline(std::cin, author);
std::string label = title + " by " + author;
std::cout << label << "\n";
std::cout << "Length: " << label.size() << "\n";
std::cout << "First: " << title[0] << ", last: " << title.back() << "\n";
std::string shout = title; // a real copy
for (char& c : shout) {
c = std::toupper(static_cast<unsigned char>(c));
}
std::cout << shout << "\n";
std::cout << "Original unchanged: " << title << "\n";
return 0;
}Input given to the program: The Lighthouse Ledger ↵ Mara Quill
Output
The Lighthouse Ledger by Mara Quill Length: 35 First: T, last: r THE LIGHTHOUSE LEDGER Original unchanged: The Lighthouse Ledger
Both inputs contain spaces, so getline is the right reader. title + " by " + author works because the left operand of the first + is a std::string; the literal in the middle is converted as it goes. The loop variable char& c is a reference to each character of shout, so assigning to it changes the string. title is untouched because shout was a copy, not another name for the same text.
Splitting a record with find and substr
A stock line in the form product;quantity;pence is cut into its three fields, converted and searched.
#include <iostream>
#include <string>
int main() {
std::string line;
std::getline(std::cin, line); // product;quantity;pence
std::size_t first = line.find(';');
std::size_t second = line.find(';', first + 1);
std::string product = line.substr(0, first);
int quantity = std::stoi(line.substr(first + 1, second - first - 1));
int pence = std::stoi(line.substr(second + 1));
std::cout << "Product: " << product << "\n";
std::cout << "Line total: " << quantity * pence << "p\n";
if (line.find("milk") != std::string::npos) {
std::cout << "Contains milk\n";
}
if (product == "oat milk") {
std::cout << "Exact match\n";
}
std::cout << "Before rice alphabetically: " << (product < "rice") << "\n";
return 0;
}Input given to the program: oat milk;3;180
Output
Product: oat milk Line total: 540p Contains milk Exact match Before rice alphabetically: 1
find locates the two separators; the second search starts one past the first so it does not find the same one again. substr(0, first) is the text before the first separator, and the middle field's length is the distance between the separators minus one. std::stoi turns the digit strings into ints so they can be multiplied. find returning npos is the "not present" signal, and < orders "oat milk" before "rice" because 'o' comes before 'r'. The index variables are std::size_t, the unsigned type find and size use.
Reading words, building a string and walking its characters
Words are read one at a time until the input ends; a report line is assembled, then the longest word is inspected.
#include <iostream>
#include <string>
int main() {
std::string word;
int count = 0;
std::string longest;
while (std::cin >> word) { // one whitespace-separated word at a time
++count;
if (word.size() > longest.size()) {
longest = word;
}
}
std::string report = "Words: " + std::to_string(count);
report += ", longest: ";
report += longest;
std::cout << report << "\n";
int vowels = 0;
for (char c : longest) {
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
++vowels;
}
}
std::cout << "Vowels in it: " << vowels << "\n";
std::string reversed;
for (std::size_t i = longest.size(); i > 0; --i) {
reversed += longest[i - 1];
}
std::cout << "Reversed: " << reversed << "\n";
return 0;
}Input given to the program: the quick canal boat drifted past
Output
Words: 6, longest: drifted Vowels in it: 2 Reversed: detfird
std::cin >> word delivers one word per pass and the loop ends when the input runs out. std::to_string is needed to join the count to text; "Words: " + count would not build a string. The vowel loop reads each char by value; the reversal loop counts an unsigned index down from size() to 1 and uses i - 1, which avoids the trap of an unsigned counter that can never go below zero. Appending one char at a time with += is the usual way to build a string in a loop.
Common mistakes
Reading a value with spaces using std::cin >>
Why it goes wrong:
>>stops at the first whitespace, so "Mara Quill" becomes "Mara" and "Quill" stays in the input, where it is picked up by the next read and misinterpreted.Fix: Use
std::getline(std::cin, name)for any input that may contain spaces.Calling getline straight after cin >>
Why it goes wrong: After
std::cin >> nthe newline that ended the number is still in the buffer, so the followingstd::getlinereturns an empty string immediately.Fix: Skip the leftover whitespace first:
std::getline(std::cin >> std::ws, line);.C++ · fixint n; std::string line; std::cin >> n; std::getline(std::cin >> std::ws, line); // reads the real next lineAdding two literals, or a literal and a number
Why it goes wrong:
"Total: " + "5"does not compile because neither side is a std::string."Total: " + 5compiles but moves a pointer five characters into the literal, printing ": ".Fix: Make one operand a std::string and convert numbers with
std::to_string.C++ · fixstd::string msg = std::string("Total: ") + std::to_string(5);Comparing a char with a string literal
Why it goes wrong:
s[0] == "a"compares a char with a pointer to a literal; g++ rejects it. A character is a char, and its literal uses single quotes.Fix: Write
s[0] == 'a'. Use double quotes only when comparing whole strings:s == "a".
Where you use this
Almost every program that talks to a person has to accept slightly messy text. A quiz program that compares the typed answer with the expected one should not fail because the user typed "Paris " with a trailing space or "paris" in lower case. The usual approach is to normalise both sides before comparing: lower-case every character with std::tolower, and trim leading and trailing spaces with find_first_not_of and find_last_not_of plus substr. Once both strings are in the same canonical form, == gives the right answer, and the same helper serves for matching menu commands, product codes and file names.
std::string normalise(std::string s) {
for (char& c : s) {
c = std::tolower(static_cast<unsigned char>(c));
}
std::size_t start = s.find_first_not_of(' ');
if (start == std::string::npos) {
return "";
}
std::size_t end = s.find_last_not_of(' ');
return s.substr(start, end - start + 1);
}Key points
std::stringneeds#include <string>; it owns its text, grows on demand and copies as a value.getlinereads a whole line;>>reads one word. After>>, usestd::cin >> std::wsbeforegetline.size()gives the length;s[i]ands.at(i)give a char, so compare them with single-quoted literals.+joins when at least one side is a std::string;std::to_stringandstd::stoiconvert between numbers and text.findreturns an index orstd::string::npos;substr(start, count)copies a piece.==compares text exactly;<orders by character codes, so case matters.- Use
<cctype>functions with anunsigned charcast to change case or classify characters.
Try it yourself
The program echoes one line of input. Change it to print the line's length as Length: N and then the line reversed as Reversed: ..., building the reversed string with a loop and +=.
#include <iostream>
#include <string>
int main() {
std::string line;
std::getline(std::cin, line);
std::cout << line << "\n";
return 0;
}
level crossingLength: 14
Reversed: gnissorc level#include <iostream>
#include <string>
int main() {
std::string line;
std::getline(std::cin, line);
std::cout << "Length: " << line.size() << "\n";
std::string reversed;
for (std::size_t i = line.size(); i > 0; --i) {
reversed += line[i - 1];
}
std::cout << "Reversed: " << reversed << "\n";
return 0;
}
Practise this
Exercises for this lesson are in the C++ practice set.
Related lessons
- Variables and Types in C++Declaring C++ variables with int, double, char, bool and std::string, initialising them, using const and auto, and why an uninitialised variable is a bug.9 min
- Loops in C++: for, while and do-whileC++ loops explained: the three parts of a for loop, while and do-while, reading input until it ends, nested loops, and when to reach for break or continue.9 min
- Vectors in C++ (std::vector)How std::vector works in C++: creating and growing a vector, indexing with [] and at(), passing vectors to functions, erasing elements and building 2D grids.11 min
Frequently asked questions
What is the difference between a string literal and std::string in C++?
A literal like "Leeds" is a fixed array of characters built into the program; it cannot grow and has no methods. std::string is a class that stores characters it owns, resizes itself as you append, and provides size, find, substr and the rest. Assigning a literal to a std::string copies the characters in, which is why nearly all string work is done through std::string.
How do I convert a string to a number in C++?
Use std::stoi(text) for an int, std::stol or std::stoll for wider integers and std::stod(text) for a double; all are in <string> since C++11. They skip leading whitespace, parse as many characters as form a number and throw std::invalid_argument if none do, or std::out_of_range if the value does not fit. The reverse direction is std::to_string(value).
Why does std::getline return an empty string after std::cin >> n?
Because >> stops reading at the newline that ended the number and leaves it in the input buffer. The next getline sees that newline at once and returns the empty text before it. Write std::getline(std::cin >> std::ws, line) so the stream first skips any pending whitespace, including that newline.
How this page was checked. Every program on it was run with GCC 13 (C++17) at build time by the publishing checks, and the output shown is what it printed. Running C++ inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with GCC 13 (C++17) locally.