C++ String Methods

The C++ std::string class comes with a rich set of built-in methods for measuring, searching, slicing, and modifying text. Instead of manually looping over character arrays like in C, you call methods directly on a string object — s.find("x"), s.substr(2, 5), s.replace(...), and dozens more. Knowing these methods well is essential for almost any real program: parsing input, validating data, building output, and manipulating text.

Overview: How std::string Methods Work

std::string is defined in the <string> header and is really a class template instantiation (std::basic_string<char>). Internally, a string object manages a dynamically allocated character buffer on the heap (most implementations also use small string optimization, storing short strings directly inside the object to avoid heap allocation entirely). Because the buffer is managed automatically, you never need to worry about manual memory allocation, null terminators, or buffer overflows the way you would with a raw C-style char*.

Every method you call on a string either reads data (like size(), find(), substr()) or mutates the string in place (like insert(), erase(), replace(), append()). Mutating methods resize the internal buffer as needed — if the new content is longer than the current capacity, the string reallocates a larger buffer, copies the old characters over, and frees the old one. This is why repeatedly appending to a string in a tight loop can be slow unless you first call reserve() to pre-allocate capacity.

Most search and slice methods use a zero-based index, just like array indexing, and many take a (position, length) pair rather than a start/end pair. A frequent source of confusion is that search methods return a special sentinel value, std::string::npos, when nothing is found — not -1. We’ll cover exactly why that matters in the Common Mistakes section.

Syntax

String methods are called using dot notation on a string object:

string s = "Hello, World!";
s.method_name(arguments);

The table below summarizes the most important methods you’ll use constantly:

Method Purpose Example
size() / length() Number of characters (identical behavior) s.size()
empty() True if the string has zero characters s.empty()
at(i) / s[i] Access character at index i (at throws if out of range, [] does not) s.at(0)
substr(pos, len) Returns a new string, len chars starting at pos s.substr(7, 5)
find(str) Index of first occurrence of str, or string::npos s.find("World")
rfind(str) Index of last occurrence of str s.rfind("o")
insert(pos, str) Inserts str starting at pos s.insert(0, "> ")
erase(pos, len) Removes len characters starting at pos s.erase(0, 3)
replace(pos, len, str) Replaces len characters at pos with str s.replace(0, 5, "Hi")
append(str) / += Adds characters to the end s += "!"
compare(other) Lexicographic comparison; returns negative/0/positive a.compare(b)
find_first_of / find_last_of Index of first/last char matching any in a set s.find_first_of(" ")
find_first_not_of / find_last_not_of Index of first/last char not in a set (great for trimming) s.find_first_not_of(' ')
c_str() Returns a read-only null-terminated const char* s.c_str()
clear() Removes all characters s.clear()

Examples

Example 1: Basic Inspection and Searching

#include <iostream>
#include <string>
#include <cctype>
using namespace std;

int main() {
    string name = "Alice";
    string greeting = "Hello, " + name + "!";

    cout << "Greeting: " << greeting << endl;
    cout << "Length: " << greeting.length() << endl;
    cout << "Uppercase first letter: " << (char)toupper(greeting[0]) << endl;
    cout << "Substring: " << greeting.substr(7, 5) << endl;

    size_t pos = greeting.find("Alice");
    if (pos != string::npos) {
        cout << "Found 'Alice' at index: " << pos << endl;
    }

    return 0;
}

Output:

Greeting: Hello, Alice!
Length: 13
Uppercase first letter: H
Substring: Alice
Found 'Alice' at index: 7

This example concatenates strings with +, measures length with length(), reads a single character with [], slices out a piece with substr(7, 5) (starting at index 7, 5 characters long), and searches for a substring with find(). Note the explicit check against string::npos rather than a raw number — that’s the idiomatic way to test for "not found".

Example 2: Modifying a String — Insert, Erase, Replace, Compare

#include <iostream>
#include <string>
using namespace std;

int main() {
    string sentence = "C++ is a good language.";

    sentence.replace(9, 4, "powerful");
    cout << "After replace: " << sentence << endl;

    sentence.insert(0, "Note: ");
    cout << "After insert: " << sentence << endl;

    sentence.erase(0, 6);
    cout << "After erase: " << sentence << endl;

    string a = "apple";
    string b = "banana";
    if (a.compare(b) < 0) {
        cout << a << " comes before " << b << " alphabetically." << endl;
    }

    return 0;
}

Output:

After replace: C++ is a powerful language.
After insert: Note: C++ is a powerful language.
After erase: C++ is a powerful language.
comes before comparison: apple comes before banana alphabetically.

Here, replace(9, 4, "powerful") removes 4 characters starting at index 9 (the word "good") and inserts "powerful" in its place. insert(0, "Note: ") adds text at the very front, and erase(0, 6) removes those same 6 characters again. Finally, compare() returns a negative number when the calling string is lexicographically less than the argument, which is exactly what happens comparing "apple" to "banana".

Example 3: A Realistic Parser — Tokenizing and Trimming

#include <iostream>
#include <string>
using namespace std;

int main() {
    string data = "apple,42,3.14,banana";
    string token;
    size_t pos = 0;

    cout << "Tokens in \"" << data << "\":" << endl;
    while ((pos = data.find(',')) != string::npos) {
        token = data.substr(0, pos);
        cout << "- " << token << endl;
        data.erase(0, pos + 1);
    }
    cout << "- " << data << endl;

    string word = "  Trim Me  ";
    size_t start = word.find_first_not_of(' ');
    size_t end = word.find_last_not_of(' ');
    string trimmed = word.substr(start, end - start + 1);
    cout << "Trimmed: \"" << trimmed << "\"" << endl;

    return 0;
}

Output:

Tokens in "apple,42,3.14,banana":
- apple
- 42
- 3.14
- banana
Trimmed: "Trim Me"

This program shows the classic "split by delimiter" pattern used constantly for parsing CSV-like data: repeatedly find() the delimiter, substr() out the piece before it, then erase() that piece (plus the delimiter) from the front of the string. The second part shows a common trimming technique: find_first_not_of(' ') and find_last_not_of(' ') locate the boundaries of the real content, ignoring surrounding whitespace.

How It Works Step by Step

  • Storage: A string owns a heap-allocated (or small-buffer-optimized) character array plus a tracked length and capacity.
  • Reading methods like find() and substr() scan or copy characters but never modify the original string. substr() always allocates and returns a brand-new string object.
  • Mutating methods like insert(), erase(), and replace() shift the remaining characters left or right in memory to make room or fill the gap, then update the tracked length. If the new length exceeds the current capacity, a full reallocation happens: a bigger buffer is allocated, existing characters are copied over, and the old buffer is released.
  • Comparisons (compare(), ==, <) walk character by character comparing ASCII/Unicode code point values until a difference is found or one string ends.
  • Search methods like find() perform a linear scan (conceptually similar to substring search) starting from the given position, returning the index of the first match or string::npos (defined as the maximum value of size_t) if nothing matches.

Common Mistakes

Mistake 1: Calling substr() with an Out-of-Range Position

Wrong:

string s = "Hello";
string sub = s.substr(10, 3); // s only has 5 characters!

Because s only has 5 characters (valid indices 0–4), asking for a substring starting at index 10 throws a std::out_of_range exception and crashes the program if uncaught. The pos argument must never exceed s.size().

Corrected:

string s = "Hello";
if (10 <= s.size()) {
    string sub = s.substr(10, 3);
} else {
    cout << "Position out of range" << endl;
}

Mistake 2: Storing find() Results in an int and Comparing to -1

Wrong:

string s = "Hello";
int pos = s.find("z"); // find() returns size_t, not int!
if (pos == -1) {
    cout << "Not found" << endl;
}

find() returns a size_t (an unsigned type), and "not found" is represented by string::npos — the largest possible size_t value, not -1. Assigning that huge unsigned value into a signed int is an implementation-defined narrowing conversion; it may happen to produce -1 on your compiler today, but it is not guaranteed and is a sign of misunderstanding the type. Always keep the result in a size_t and compare directly to string::npos.

Corrected:

string s = "Hello";
size_t pos = s.find("z");
if (pos == string::npos) {
    cout << "Not found" << endl;
}

Best Practices

  • Always compare search results (find, rfind, find_first_of, etc.) against string::npos, never against -1 or 0.
  • Prefer at(i) over [i] when you’re not certain the index is valid — at() throws a catchable exception instead of causing undefined behavior.
  • Use reserve(n) before building a long string in a loop with repeated += or append() calls, to avoid repeated reallocations.
  • Use c_str() only when interfacing with C APIs that require a const char*; for everything else, keep working with std::string directly.
  • Use find_first_not_of / find_last_not_of for trimming whitespace instead of writing manual character-by-character loops.
  • Remember that substr(), +, and most read-only methods return a new string — they do not modify the original.
  • When comparing strings case-insensitively, convert both to the same case first (e.g. with transform and tolower) — compare() and == are always case-sensitive.

Practice Exercises

  • Exercise 1: Write a program that reads a full name as one string (e.g. "John Smith") and uses find() and substr() to print the first name and last name on separate lines.
  • Exercise 2: Write a function-style snippet that takes a string and returns true if it contains the substring "error" (case-sensitive), using find(). Test it against "System error: disk full" and "All systems normal".
  • Exercise 3: Given the string "2026-07-18", use substr() to extract the year, month, and day into three separate strings and print them in the format Day/Month/Year. Expected output: 18/07/2026.

Summary

  • std::string provides dozens of methods for measuring, searching, and mutating text without manual memory management.
  • size()/length(), substr(), and find() are the workhorses for reading and slicing strings.
  • insert(), erase(), and replace() mutate a string in place; each may trigger an internal reallocation.
  • Search methods return string::npos, not -1, when nothing is found — always store results in size_t and compare against string::npos.
  • find_first_not_of/find_last_not_of are the idiomatic tools for trimming and boundary-finding.
  • at() is safer than [] for indices that might be out of range, since it throws instead of invoking undefined behavior.