Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 4 of 9

Lesson 3.5 showed that "a" is an array of characters ending in '\0'. That's a C string, and it's what a string literal actually is. std::string is the C++ answer.

#include <iostream>
#include <string>

int main() {
    std::string name = "Evil Genius";

    std::cout << name << "\n";
    std::cout << name.size() << "\n";
    std::cout << name[0] << name[5] << "\n";

    name += " Society";
    std::cout << name << "\n";
    return 0;
}
Evil Genius
11
EG
Evil Genius Society

It grows on demand, frees itself, and knows its length in constant time. A const char* does none of that.

What a const char* costs you

#include <cstring>
#include <iostream>

int main() {
    const char* c_style = "Evil Genius";

    std::cout << c_style << "\n";
    std::cout << std::strlen(c_style) << "\n";
    std::cout << sizeof(c_style) << "\n";
    return 0;
}
Evil Genius
11
8

Three problems visible in six lines:

  • strlen walks the whole string looking for '\0' — O(n) every call, where std::string::size() is O(1).
  • sizeof gives 8 — the pointer size, not the text length. Decay again (lesson 6.1).
  • You can't concatenate with +. a + b on two pointers is meaningless pointer arithmetic.

And the memory is yours to manage. Get it wrong and you have a leak or a dangling pointer (Module 8).

The operations you'll use

#include <iostream>
#include <string>

int main() {
    std::string s = "evil genius society";

    std::cout << s.substr(5, 6) << "\n";
    std::cout << s.find("genius") << "\n";
    std::cout << (s.find("python") == std::string::npos) << "\n";

    std::string copy = s;
    copy.replace(0, 4, "EVIL");
    std::cout << copy << "\n";

    std::cout << s.starts_with("evil") << " " << s.ends_with("society") << "\n";
    return 0;
}
genius
5
1
EVIL genius society
1 1

find returns std::string::npos when there's no match — a huge sentinel value, not −1 (lesson 3.3 again). Always compare against npos, never test < 0; the type is unsigned, so that comparison is always false.

starts_with and ends_with are C++20 additions. Before them you wrote awkward substr comparisons.

Building strings

#include <iostream>
#include <string>

int main() {
    std::string a = "Evil";
    std::string b = "Genius";

    std::cout << a + " " + b << "\n";

    std::string built;
    built.reserve(32);
    for (int i = 0; i < 3; ++i) {
        built += "ab";
    }
    std::cout << built << " (" << built.size() << ")\n";

    std::cout << std::to_string(42) + "!" << "\n";
    std::cout << std::stoi("123") + 1 << "\n";
    return 0;
}
Evil Genius
ababab (6)
42!
124

std::string has capacity and reallocation just like vector (lesson 6.3), so reserve helps in a building loop for the same reason.

std::to_string and std::stoi convert to and from numbers. Note std::stoi throws on garbage input rather than returning an error code — wrap it in try/catch when the input isn't trusted.

Where you still meet const char*

String literals are const char*. They convert to std::string implicitly, which is why std::string s = "text"; works.

C APIs want them. .c_str() hands one over:

#include <cstdio>
#include <string>

int main() {
    std::string filename = "data.txt";
    std::printf("opening %s\n", filename.c_str());
    return 0;
}
opening data.txt

.c_str() returns a pointer to the string's internal buffer, null-terminated. It's valid only while the string is alive and unmodified. This is a classic dangling bug:

const char* p = std::string("temporary").c_str();
// the temporary is destroyed at the end of this statement — p dangles

Which to use

Use std::string. It manages memory, knows its size, concatenates, compares with ==, and is safe.

Use const char* only when a C API demands it (via .c_str()), or in embedded contexts where you can't allocate.

There's one more option — std::string_view — for the specific case of looking at a string you don't own. That's the next lesson.

Try it

Write std::string reversed(const std::string& text) returning the reverse, and int count_occurrences(const std::string& text, const std::string& needle) counting non-overlapping occurrences. Test with "banana" and "ana".

Checkpoint

  • std::string owns and manages its memory, knows its size in O(1), and supports +, ==, and a large member set.
  • const char* is a C string: null-terminated, O(n) length, no concatenation, manual lifetime.
  • find returns std::string::npos on failure — compare against it, never < 0.
  • to_string/stoi convert; stoi throws on bad input.
  • .c_str() gives a const char* for C APIs, valid only while the string lives unmodified.
  • Prefer std::string everywhere except at C API boundaries.

Questions about this lesson

Log in to ask a question.