std::string
Classstd::string
, covering the most essential methods and operatorsIn 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, where possible, 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.
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 World
We 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 World
std::string
LiteralsThe 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::string
std::string
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 World
Passing 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 World
References (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
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: Hello
Another common way to pass std::string
objects in modern C++ is through a string view, which we’ll cover later in this chapter.
std::string
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: 5
std::string
objects also have the size()
method, which does the same thing and is included for parity with other std::library
containers such as std::vector
and std::array
std::string
objectsWe 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 equal
std::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:
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 Zebra
std::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 Zebra
Because 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:
#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
Zebra
std::string
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 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: 26
If 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 tale";
}
}
There is no dog in this tale
The 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 slapped
For 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.
std::string
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 d
The at()
method will perform bounds-checking on the index we pass, to ensure it is valid.
The []
operator will skip this check when compiling in a non-debug configuration. This has a minor performance gain at the cost of 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 fine";
try { String.at(100) = 42; }
catch (std::out_of_range& Error) {
std::cout << "\nThat was an exception:\n"
<< Error.what();
}
}
That was fine
That was an exception:
invalid string position
std::string
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: Hello
The 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: World
std::string
We can generate new std::string
objects by concatenating other strings together, using the +
operator. The `std::string:
#include <string>
#include <iostream>
int main(){
std::string StringA("Hello");
std::string StringB(" World");
std::string Combined{StringA + StringB};
std::cout << Combined;
}
Hello World
This 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.
std::string
to a NumberOften, 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 a std::string
to an int
std::stof
- convert a std::string
to a float
std::stod
- convert a std::string
to a double
#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.28
std::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(){
float Number{42};
std::string String{std::to_string(Number)};
std::cout << String;
}
42
In the next lesson, we’ll look at more properties and methods of std::string
, and how they can interact with the standard library algorithms.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.