C++ · Beginner

Strings in C++

10 min readUpdated September 24, 2026Every example verified

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

 C++ · 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

CallResultExample on s = "ledger"
s.size()number of characters6
s[2]the char at index 2'd'
s.back()the last char'r'
s.find("ge")index of the first match or npos3
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"booltrue

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.

 C++
#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 LedgerMara 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.

 C++
#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.

 C++
#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 >> n the newline that ended the number is still in the buffer, so the following std::getline returns an empty string immediately.

    Fix: Skip the leftover whitespace first: std::getline(std::cin >> std::ws, line);.

     C++ · fix
    int n;
    std::string line;
    std::cin >> n;
    std::getline(std::cin >> std::ws, line);   // reads the real next line
  • Adding 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: " + 5 compiles 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++ · fix
    std::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.

 C++ · in practice
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::string needs #include <string>; it owns its text, grows on demand and copies as a value.
  • getline reads a whole line; >> reads one word. After >>, use std::cin >> std::ws before getline.
  • size() gives the length; s[i] and s.at(i) give a char, so compare them with single-quoted literals.
  • + joins when at least one side is a std::string; std::to_string and std::stoi convert between numbers and text.
  • find returns an index or std::string::npos; substr(start, count) copies a piece.
  • == compares text exactly; < orders by character codes, so case matters.
  • Use <cctype> functions with an unsigned char cast 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 +=.

Your program
#include <iostream>
#include <string>

int main() {
    std::string line;
    std::getline(std::cin, line);
    std::cout << line << "\n";
    return 0;
}
Input the program receives: level crossing
Expected output: Length: 14 Reversed: gnissorc level

Practise this

Exercises for this lesson are in the C++ practice set.

Open the C++ playground

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.

Progress is stored only in this browser.

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.