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:
strlenwalks the whole string looking for'\0'— O(n) every call, wherestd::string::size()is O(1).sizeofgives 8 — the pointer size, not the text length. Decay again (lesson 6.1).- You can't concatenate with
+.a + bon 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::stringowns 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.findreturnsstd::string::nposon failure — compare against it, never< 0.to_string/stoiconvert;stoithrows on bad input..c_str()gives aconst char*for C APIs, valid only while the string lives unmodified.- Prefer
std::stringeverywhere except at C API boundaries.
Questions about this lesson
Log in to ask a question.