A Deeper Look at the std::string Class

A detailed guide to std::string, covering the most essential methods and operators
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
3D Character Concept Art
Ryan McCombe
Ryan McCombe
Updated

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 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 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::string

String 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 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.

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: 5

std::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 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:

  • 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 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.

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
Zebra

Finding 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 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 fight";
  }
}
There is no dog in this fight

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.

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: 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

Accessing 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 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 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 position

Concatenating

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 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.

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 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

Converting 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(){
  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.

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::string class.
  • Creating std::string objects from C-style strings and using std::string literals.
  • Copying and moving std::string objects.
  • Comparing std::string objects using operators and the compare() method.
  • Finding substrings and characters within a std::string using find(), rfind(), and C++20/23 methods like starts_with(), ends_with(), and contains().
  • Accessing and modifying characters in a std::string using the at() method and [] operator.
  • Generating substrings with the substr() method.
  • Concatenating std::string objects and converting between std::string and numeric types.

Was this lesson useful?

Next Lesson

Manipulation and Memory Management of std::string

A practical guide covering the most useful methods and operators for working with std::string objects and their memory
3D Character Concept Art
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 124 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Manipulation and Memory Management of std::string

A practical guide covering the most useful methods and operators for working with std::string objects and their memory
3D Character Concept Art
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved