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:
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:
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 (twoconst char*). Make one astd::string. s[i]out of range — undefined behavior. Use.at(i)while learning.- Comparing
find()with-1or0— compare withstd::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 >> sfor sentences — reads one word. Usestd::getline.
Exercises¶
- Read a full name with
std::getlineand print the initials (e.g. "Ada Lovelace" → "A.L."). - Write
bool is_palindrome(std::string_view s)that ignores case and non-letters. Test it on "A man, a plan, a canal: Panama". - Count the vowels in a line of input.
- Read a line and print each word on its own line, right-aligned in a 15-character column using
std::format. - Write
std::string replace_all(std::string s, std::string_view from, std::string_view to). - Read a CSV line like
Ada,36,Londonand split it on commas withstd::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::printlnin C++23.std::istringstream+>>orgetlineto split text.std::string_viewfor cheap read-only parameters — but never let it outlive its string.