Skip to content

4. Strings

What & why

Almost every program handles text: names, commands, file contents, output. C++ has two kinds of strings — old C-style character arrays and the modern std::string class. Use std::string. It manages its own memory, knows its length, and has a rich set of operations.

Creating strings

#include <string>

std::string empty;                 // ""
std::string name = "Ada";
std::string greeting{"Hello"};
std::string stars(5, '*');         // "*****"  (note: parentheses, not braces)
std::string copy = name;           // independent copy

A string literal like "Ada" is actually a C-style const char[4]. It converts to std::string automatically. If you want a std::string literal directly:

using namespace std::string_literals;
auto s = "Ada"s;     // std::string

Basic operations

std::string s = "Hello";

s.size()            // 5 (also s.length())
s.empty()           // false
s[0]                // 'H'   — no bounds check
s.at(0)             // 'H'   — bounds-checked, throws std::out_of_range
s.front()           // 'H'
s.back()            // 'o'

s += ", world";     // append → "Hello, world"
s + "!"             // concatenation makes a new string
s == "Hello, world" // comparison with ==, !=, <, ...

Strings are mutable:

s[0] = 'J';         // "Jello, world"

Searching and slicing

std::string s = "the quick brown fox";

s.find("quick")        // 4 — index of the first match
s.find("zebra")        // std::string::npos — "not found"
s.rfind('o')           // 17 — last match
s.substr(4, 5)         // "quick" — (start, length)
s.substr(10)           // "brown fox" — from 10 to the end
s.starts_with("the")   // true (C++20)
s.ends_with("fox")     // true (C++20)
s.contains("brown")    // true (C++23 — use find() != npos on C++20)

Always check find against npos:

auto pos = s.find("brown");
if (pos != std::string::npos) {
    std::cout << "found at " << pos << '\n';
}

Modifying

std::string s = "hello world";
s.insert(0, ">> ");        // ">> hello world"
s.erase(0, 3);             // "hello world"
s.replace(0, 5, "HELLO");  // "HELLO world"
s.pop_back();              // "HELLO worl"
s.clear();                 // ""

Characters

Individual characters are char. <cctype> has helpers:

#include <cctype>

std::isdigit(c)  std::isalpha(c)  std::isspace(c)  std::isupper(c)
std::toupper(c)  std::tolower(c)

Uppercasing a whole string:

for (char& c : s) {                    // & so we modify the original
    c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
}

(The unsigned char cast avoids undefined behavior on non-ASCII bytes.)

Formatting with std::format (C++20)

Instead of long << chains, build strings with format specifiers:

#include <format>
#include <iostream>

int main() {
    std::string name = "Ada";
    int age = 36;
    double score = 97.456;

    std::cout << std::format("{} is {} years old\n", name, age);
    std::cout << std::format("score: {:.1f}\n", score);     // 97.5
    std::cout << std::format("[{:>8}]\n", name);            // right-align in 8
    std::cout << std::format("[{:<8}]\n", name);            // left-align
    std::cout << std::format("[{:^8}]\n", name);            // center
    std::cout << std::format("{:05}\n", 42);                // 00042
    std::cout << std::format("{:x} {:b}\n", 255, 5);        // ff 101
}

C++23 adds std::println("{} is {}", name, age); in <print>, which formats and prints in one step. Use it if your compiler supports it (GCC 14+, Clang 18+ with libc++, MSVC 17.7+).

Splitting a string

There's no built-in split. The simplest way uses a string stream:

#include <sstream>
#include <string>
#include <vector>

std::vector<std::string> split_words(const std::string& text) {
    std::istringstream in{text};
    std::vector<std::string> words;
    std::string word;
    while (in >> word) {         // >> splits on whitespace
        words.push_back(word);
    }
    return words;
}

To split on a specific delimiter, use std::getline(in, part, ',').

std::string_view: look without copying

A std::string_view is a read-only view into characters owned by someone else. It's cheap to create and pass around:

#include <string_view>

void print_upper_count(std::string_view text) {
    int n = 0;
    for (char c : text) {
        if (std::isupper(static_cast<unsigned char>(c))) ++n;
    }
    std::cout << n << '\n';
}

print_upper_count("Hello World");          // works with literals
print_upper_count(std::string{"ABC"});     // and std::string

Use std::string_view for read-only function parameters. Never return one that points into a local string — the string dies and the view dangles.

Raw string literals

std::string path = R"(C:\Users\ada\notes.txt)";   // backslashes are literal
std::string json = R"({"name": "Ada"})";

Common mistakes

  • Adding two literals: "Hello, " + "world" doesn't compile (two const char*). Make one a std::string.
  • s[i] out of range — undefined behavior. Use .at(i) while learning.
  • Comparing find() with -1 or 0 — compare with std::string::npos.
  • substr(start, end) — the second argument is a length, not an end index.
  • Dangling string_view — std::string_view v = std::string{"temp"}; points at a destroyed temporary.
  • std::cin >> s for sentences — reads one word. Use std::getline.

Exercises

  1. Read a full name with std::getline and print the initials (e.g. "Ada Lovelace" → "A.L.").
  2. Write bool is_palindrome(std::string_view s) that ignores case and non-letters. Test it on "A man, a plan, a canal: Panama".
  3. Count the vowels in a line of input.
  4. Read a line and print each word on its own line, right-aligned in a 15-character column using std::format.
  5. Write std::string replace_all(std::string s, std::string_view from, std::string_view to).
  6. Read a CSV line like Ada,36,London and split it on commas with std::getline(stream, part, ',').

Recap

  • Use std::string; it owns its characters and grows as needed.
  • size, find/npos, substr(start, len), +=, starts_with/ends_with.
  • std::format("{:.2f}", x) for formatted text; std::println in C++23.
  • std::istringstream + >> or getline to split text.
  • std::string_view for cheap read-only parameters — but never let it outlive its string.

Next: Booleans & conditionals →