A Deeper Look at the std::string Class
A detailed guide to std::string, covering the most essential methods and operators
In this lesson, we'll dive deeper into the std::string objects we've been so heavily relying on.
We'll investigate how they're created, how they're moved and copied, and some of the most valuable methods we have to interact with them.
std::string solves many of the problems of C-style strings and is much easier to work with. So, we should prefer to use std::string objects over C-style strings where possible.
The next few lessons will outline many of the reasons why.
Creating a std::string
The std::string class and its associated utilities are available by including <string>:
#include <string>As we've seen, std::string objects can be created from C-style strings. Typically, this is done from C-style string literals, but it doesn't need to be:
#include <iostream>
#include <string>
int main(){
std::string StringA{"Hello"};
const char* CString{" World"};
std::string StringB{CString};
std::cout << StringA << StringB;
}Hello WorldWe can go in the opposite direction, generating a C-style string from a std::string using the c_str() method:
#include <iostream>
#include <string>
int main(){
std::string StringA{"Hello World"};
const char* CString{StringA.c_str()};
std::cout << CString;
}Hello WorldUsing std::string Literals
The standard library includes a way to create std::string literals.
To use them, we need to include a using statement:
using namespace std::string_literals;In larger projects, statements like this would typically be part of a file that gets included in every other file.
Once we've implemented the using statement, we can use std::string literals in a similar way we use C-style strings.
We wrap them in double quotes, however, for std::string we append an s to the end:
#include <iostream>
#include <string>
using namespace std::string_literals;
int main(){
auto MyString{"Hello World"s};
std::cout << MyString;
if constexpr (std::same_as<
decltype(MyString), std::string>) {
std::cout << "\nThat was a std::string";
}
}Hello World
That was a std::stringString Copying
One of the main benefits of std::string over C-style strings is that copying and moving them works in much the same way as any other modern data type.
We can deep copy a std::string simply using the = operator:
#include <iostream>
#include <string>
int main(){
std::string StringA{" World"};
std::string StringB = StringA;
StringA = "Hello";
std::cout << StringA << StringB;
}Hello WorldPassing by value also creates a deep copy:
#include <iostream>
#include <string>
void Update(std::string Copy){
Copy = "Hello";
std::cout << Copy;
}
int main(){
std::string StringA{" World"};
Update(StringA);
std::cout << StringA;
}Hello WorldReferences (both l-value and r-value) work as they do with any other type:
#include <iostream>
#include <string>
using namespace std::string_literals;
void Log(std::string& LValue){
std::cout << "L-Value: " << LValue << '\n';
}
void Log(std::string&& RValue){
std::cout << "R-Value: " << RValue << '\n';
}
int main(){
std::string MyString{"Hello"};
Log(MyString);
Log("World"s);
std::string AnotherString{"Bye!"};
Log(std::move(AnotherString));
}L-Value: Hello
R-Value: World
R-Value: Bye!We cover l-values and r-values in our lesson on move semantics
Move Semantics
Learn how we can improve the performance of our types using move constructors, move assignment operators and std::move()
If we want to get a pointer to a std::string, we can do so in the usual way, using the address-of operator &:
#include <iostream>
#include <string>
int main(){
std::string MyString{"Hello"};
std::string* Pointer{&MyString};
std::cout << Pointer << ": " << *Pointer;
}0000004E9FBFF5A8: HelloAnother common way to pass std::string objects in modern C++ is through a string view, which we'll cover later in this chapter.
String Length
We can get the length of a std::string using the length() method:
#include <iostream>
#include <string>
int main(){
std::string MyString{"Hello"};
std::cout << "Length: " << MyString.length();
}Length: 5std::string objects also have the size() method, which does the same thing. This is included for parity with other standard library containers such as std::vector and std::array, thereby making it easier to write templates.
Comparing Strings
We can check if two std::string contain the same content using the == and != operators:
#include <iostream>
#include <string>
int main(){
std::string Animal1{"Bear"};
std::string Animal2{"Bear"};
std::string Animal3{"Zebra"};
if (Animal1 == Animal2) {
std::cout <<
"Animal1 and Animal2 are equal";
}
if (Animal2 != Animal3) {
std::cout <<
"\nAnimal2 and Animal3 are not equal";
}
}Animal1 and Animal2 are equal
Animal2 and Animal3 are not equalstd::string objects also have the compare() method. This returns an integer, which has the same meaning as the integer C-style string's strcmp() function:
- If the integer is negative, the first string comes before the second string in the dictionary order
- If the integer is zero, the two strings are the same
- If the integer is positive, the first string comes after the second string in the dictionary order
Here is an example:
#include <iostream>
#include <string>
int main(){
std::string Animal1{"Bear"};
std::string Animal2{"Bear"};
std::string Animal3{"Zebra"};
if (Animal1.compare(Animal2) == 0) {
std::cout <<
"Animal1 and Animal2 are equal\n";
}
if (Animal2.compare(Animal3) < 0) {
std::cout <<
Animal2 << " comes before " << Animal3;
}
}Animal1 and Animal2 are equal
Bear comes before Zebrastd::string objects also implement the usual comparison operators - <, <=, !=, and so on:
#include <iostream>
#include <string>
int main(){
std::string Animal1{"Bear"};
std::string Animal2{"Bear"};
std::string Animal3{"Zebra"};
if (Animal1 == Animal2) {
std::cout <<
"Animal1 and Animal2 are equal\n";
}
if (Animal2 < Animal3) {
std::cout <<
Animal2 << " comes before " << Animal3;
}
}Animal1 and Animal2 are equal
Bear comes before ZebraBecause the std::string class implements all the comparison operators (or, since C++20, the spaceship operator), it can automatically compare the order of its objects.
Below, we sort a range of std::string objects into alphabetical order:
#include <algorithm>
#include <string>
#include <iostream>
#include <vector>
using namespace std::string_literals;
int main(){
std::vector Animals{
"Bear"s, "Zebra"s, "Chicken"s,
"Alligator"s};
std::ranges::sort(Animals);
for (const std::string& Animal : Animals) {
std::cout << Animal << '\n';
}
}Alligator
Bear
Chicken
ZebraFinding Substrings
We can search our strings for specific substrings or individual characters using the find() and rfind() methods.
find() will return an integer representing the position where the first instance of the substring starts. rfind() will return the position of the last instance.
If there aren't multiple instances of the substring, find() and rfind() will return the same thing.
#include <string>
#include <iostream>
int main(){
std::string MyString(
"The cat slapped the other cat");
std::cout << "First cat starts at: " <<
MyString.find("cat");
std::cout << "\nSecond cat starts at: " <<
MyString.rfind("cat");
}First cat starts at: 4
Second cat starts at: 26If the substring is not found, these methods return a value that will pass an equality check with std::string::npos:
#include <string>
#include <iostream>
int main(){
std::string String(
"The cat slapped the other cat");
if (String.find("dog") == std::string::npos) {
std::cout << "There is no dog in this fight";
}
}There is no dog in this fightThe simpler starts_with(), ends_with(), and contains() methods return true if a string starts with, ends with, or contains a substring respectively.
Note: starts_with() and ends_with() were added in the C++20 specification, and contains() in C++23. As a result, they may not yet be supported by your compiler.
#include <string>
#include <iostream>
int main(){
std::string String(
"The cat slapped the other cat");
if (String.starts_with("The")) {
std::cout << "It starts with \"The\"";
}
if (String.ends_with("cat")) {
std::cout << "\nIt ends with \"cat\"";
}
if (String.contains("slapped")) {
std::cout << "\nSomething got slapped";
}
}It starts with "The"
It ends with "cat"
Something got slappedFor more complex use cases, a technique called regular expressions (regex) gives us significantly more flexibility in searching our strings.
We cover regular expressions later in this chapter.
Creating Substrings
We can generate a substring using the substr() method, passing in the position we want the substring to start, and how many characters we want it to have:
#include <string>
#include <iostream>
int main(){
std::string String("Hello World");
std::cout << "First 5 characters: "
<< String.substr(0, 5);
}First 5 characters: HelloThe second argument is optional, which means our substring will continue until the end of the original string:
#include <string>
#include <iostream>
int main(){
std::string String("Hello World");
size_t LastSpace{String.rfind(' ')};
std::cout << "Last word: "
<< String.substr(LastSpace + 1);
}Last word: WorldAccessing Individual Characters
We can access any character in std::string using the at() or [] operators, passing in the index of the character we're interested in:
#include <string>
#include <iostream>
int main(){
std::string String("Hello World");
std::cout << "The first character is "
<< String[0];
std::cout << "\nThe last character "
<< String.at(String.size() - 1);
}The first character is H
The last character dThe at() method will perform bounds-checking on the index we pass, to ensure it is valid.
The [] operator will skip this check. This has a minor performance gain, but will result in undefined behavior if the index is out of bounds.
#include <string>
#include <iostream>
int main(){
std::string String("Hello World");
String[100] = 42;
std::cout << "That was allowed, but dangerous";
try { String.at(100) = 42; }
catch (std::out_of_range& Error) {
std::cout << "\nThat was an exception:\n"
<< Error.what();
}
}That was allowed, but dangerous
That was an exception:
invalid string positionConcatenating
We can generate new std::string objects by concatenating other strings together, using the + operator:
#include <string>
#include <iostream>
int main(){
std::string StringA("Hello");
std::string StringB(" World");
std::string Combined{StringA + StringB};
std::cout << Combined;
}Hello WorldThis also works with objects that can be converted to std::strings, such as C-style strings and characters:
#include <string>
#include <iostream>
int main(){
std::string StringA("Hello");
std::string StringB("World");
std::string Combined{
StringA + " " + StringB + '!'};
std::cout << Combined;
}Hello World!Our next lesson covers a range of ways we can modify existing std::string objects in place.
Converting a String to a Number
Often, we'll have a std::string object that contains a number, and we'll want to convert it to a built-in numeric type. The most common needs can be met by:
std::stoi- convert astd::stringto anintstd::stof- convert astd::stringto afloatstd::stod- convert astd::stringto adouble
#include <string>
#include <iostream>
int main(){
std::string Pi{"3.14"};
int PiInt{std::stoi(Pi)};
float PiFloat{std::stof(Pi)};
double PiDouble{std::stod(Pi)};
std::cout
<< PiInt * 2 << '\n'
<< PiFloat * 2 << '\n'
<< PiDouble * 2;
}6
6.28
6.28Converting a Number to a String
We can go the other way, converting a numeric type to a std::string using the std::to_string() function:
#include <string>
#include <iostream>
int main(){
int Number{42};
std::string String{std::to_string(Number)};
std::cout << String;
}42In the next lesson, we'll look at more properties and methods of std::string, and how they can interact with the standard library algorithms.
Summary
In this lesson, we've explored the std::string class, demonstrating how it simplifies string manipulation compared to C-style strings.
Key Learnings
- How to include and use the
std::stringclass. - Creating
std::stringobjects from C-style strings and usingstd::stringliterals. - Copying and moving
std::stringobjects. - Comparing
std::stringobjects using operators and thecompare()method. - Finding substrings and characters within a
std::stringusingfind(),rfind(), and C++20/23 methods likestarts_with(),ends_with(), andcontains(). - Accessing and modifying characters in a
std::stringusing theat()method and[]operator. - Generating substrings with the
substr()method. - Concatenating
std::stringobjects and converting betweenstd::stringand numeric types.
Manipulating std::string Objects
A practical guide covering the most useful methods and operators for working with std::string objects and their memory