The very first C++ program you wrote probably looked something like this:
#include <iostream>
int main()
{
std::cout << "Hello, world!\n";
return 0;
}
So what is “Hello, world!” exactly? “Hello, world!” is a collection of sequential characters called a string. In C++, we use strings to represent text (such as names, words, and sentences). String literals (such as “Hello, world!\n”) are placed between double quotes to identify them as strings.
Because strings are commonly used in programs, most modern programming languages include a fundamental string data type. For historical reasons, strings are not a fundamental type in C++. Rather, they have a strange, complicated type that is hard to work with (we’ll cover how/why in a future lesson, once we’ve covered more fundamentals required to explain how they work). For now, we’ll call double-quoted strings “C-style strings”, as they were inherited from the C-language.
Fortunately, C++ has introduced two additional string types into the language that are much easier and safer to work with: std::string
and std::string_view
(C++17). Although std::string
and std::string_view
aren’t fundamental types, they’re straightforward and useful enough that we’ll introduce them here rather than wait until the chapter on compound types (chapter 9).
Introducing std::string
The easiest way to work with strings and string objects in C++ is via the std::string
type, which lives in the <string> header.
We can create objects of type std::string
just like other objects:
#include <string> // allows use of std::string
int main()
{
std::string name {}; // empty string
return 0;
}
Just like normal variables, you can initialize or assign values to std::string objects as you would expect:
#include <string>
int main()
{
std::string name { "Alex" }; // initialize name with string literal "Alex"
name = "John"; // change name to "John"
return 0;
}
Note that strings can hold numbers as well:
std::string myID{ "45" }; // "45" is not the same as integer 45!
In string form, numbers are treated as text, not as numbers, and thus they can not be manipulated as numbers (e.g. you can’t multiply them). C++ will not automatically convert strings to integer or floating point values or vice-versa (though there are ways to do so that we’ll cover in a future lesson).
String output
std::string
objects can be output as expected using std::cout
:
#include <iostream>
#include <string>
int main()
{
std::string name { "Alex" };
std::cout << "My name is: " << name << '\n';
return 0;
}
This prints:
My name is: Alex
Empty strings will print nothing:
#include <iostream>
#include <string>
int main()
{
std::string empty{ };
std::cout << '[' << empty << ']';
return 0;
}
Which prints:
[]
String input with std::cin
Using strings with std::cin
may yield some surprises! Consider the following example:
#include <iostream>
#include <string>
int main()
{
std::cout << "Enter your full name: ";
std::string name{};
std::cin >> name; // this won't work as expected since std::cin breaks on whitespace
std::cout << "Enter your age: ";
std::string age{};
std::cin >> age;
std::cout << "Your name is " << name << " and your age is " << age << '\n';
return 0;
}
Here’s the results from a sample run of this program:
Enter your full name: John Doe Enter your age: Your name is John and your age is Doe
Hmmm, that isn’t right! What happened? It turns out that when using operator>>
to extract a string from std::cin
, operator>>
only returns characters up to the first whitespace it encounters. Any other characters are left inside std::cin
, waiting for the next extraction.
So when we used operator>>
to extract input into variable name
, only "John"
was extracted, leaving " Doe"
inside std::cin
. When we then used operator>>
to get extract input into variable age
, it extracted "Doe"
instead of waiting for us to input an age. Then the program ends.
Use std::getline() to input text
To read a full line of input into a string, you’re better off using the std::getline()
function instead. std::getline()
requires two arguments: the first is std::cin
, and the second is your string variable.
Here’s the same program as above using std::getline()
:
#include <string> // For std::string and std::getline
#include <iostream>
int main()
{
std::cout << "Enter your full name: ";
std::string name{};
std::getline(std::cin >> std::ws, name); // read a full line of text into name
std::cout << "Enter your age: ";
std::string age{};
std::getline(std::cin >> std::ws, age); // read a full line of text into age
std::cout << "Your name is " << name << " and your age is " << age << '\n';
return 0;
}
Now our program works as expected:
Enter your full name: John Doe Enter your age: 23 Your name is John Doe and your age is 23
What the heck is std::ws?
In lesson 4.8 -- Floating point numbers, we discussed output manipulators, which allow us to alter the way output is displayed. In that lesson, we used the output manipulator function std::setprecision()
to change the number of digits of precision that std::cout
displayed.
C++ also supports input manipulators, which alter the way that input is accepted. The std::ws
input manipulator tells std::cin
to ignore any leading whitespace before extraction. Leading whitespace is any whitespace character (spaces, tabs, newlines) that occur at the start of the string.
Let’s explore why this is useful. Consider the following program:
#include <string>
#include <iostream>
int main()
{
std::cout << "Pick 1 or 2: ";
int choice{};
std::cin >> choice;
std::cout << "Now enter your name: ";
std::string name{};
std::getline(std::cin, name); // note: no std::ws here
std::cout << "Hello, " << name << ", you picked " << choice << '\n';
return 0;
}
Here’s some output from this program:
Pick 1 or 2: 2 Now enter your name: Hello, , you picked 2
This program first asks you to enter 1 or 2, and waits for you to do so. All good so far. Then it will ask you to enter your name. However, it won’t actually wait for you to enter your name! Instead, it prints the “Hello” string, and then exits.
When you enter a value using operator>>
, std::cin
not only captures the value, it also captures the newline character ('\n'
) that occurs when you hit the enter key. So when we type 2
and then hit enter, std::cin
captures the string "2\n"
as input. It then extracts the value 2
to variable choice
, leaving the newline character behind for later. Then, when std::getline()
goes to extract text to name
, it sees "\n"
is already waiting in std::cin
, and figures we must have previously entered an empty string! Definitely not what was intended.
We can amend the above program to use the std::ws
input manipulator, to tell std::getline()
to ignore any leading whitespace characters:
#include <string>
#include <iostream>
int main()
{
std::cout << "Pick 1 or 2: ";
int choice{};
std::cin >> choice;
std::cout << "Now enter your name: ";
std::string name{};
std::getline(std::cin >> std::ws, name); // note: added std::ws here
std::cout << "Hello, " << name << ", you picked " << choice << '\n';
return 0;
}
Now this program will function as intended.
Pick 1 or 2: 2 Now enter your name: Alex Hello, Alex, you picked 2
Best practice
If using std::getline()
to read strings, use std::cin >> std::ws
input manipulator to ignore leading whitespace.
Key insight
Using the extraction operator (>>) with std::cin ignores leading whitespace.
std::getline() does not ignore leading whitespace unless you use input manipulator std::ws.
String length
If we want to know how many characters are in a std::string
, we can ask a std::string
object for its length. The syntax for doing this is different than you’ve seen before, but is pretty straightforward:
#include <iostream>
#include <string>
int main()
{
std::string name{ "Alex" };
std::cout << name << " has " << name.length() << " characters\n";
return 0;
}
This prints:
Alex has 4 characters
Note that instead of asking for the string length as length(name)
, we say name.length()
. The length()
function isn’t a normal standalone function -- it’s a special type of function that is nested within std::string
called a member function. Because the length()
member function is declared inside of std::string
, it is sometimes written as std::string::length()
in documentation.
We’ll cover member functions, including how to write your own, in more detail later.
Key insight
With normal functions, we call function(object)
. With member functions, we call object.function()
.
Also note that std::string::length()
returns an unsigned integral value (most likely of type size_t
). If you want to assign the length to an int
variable, you should static_cast
it to avoid compiler warnings about signed/unsigned conversions:
int length { static_cast<int>(name.length()) };
In C++20, you can also use the std::ssize()
function to get the length of a std::string
as a signed integer:
#include <iostream>
#include <string>
int main()
{
std::string name{ "Alex" };
std::cout << name << " has " << std::ssize(name) << " characters\n";
return 0;
}
std::string
can be expensive to initialize and copy
Whenever a std::string
is initialized, a copy of the string used to initialize it is made. And whenever a std::string
is passed by value to a std::string
parameter, another copy is made. Making copies of strings is expensive, and should be avoided if possible.
Best practice
Do not pass std::string
by value, as making copies of std::string
is expensive. Prefer std::string_view
parameters.
We’ll discuss this topic (and std::string_view
) further in lesson 4.18 -- Introduction to std::string_view.
Literals for std::string
Double-quoted string literals (like “Hello, world!”) are C-style strings by default (and thus, have a strange type).
We can create string literals with type std::string
by using a s
suffix after the double-quoted string literal.
#include <iostream>
#include <string> // for std::string
#include <string_view> // for std::string_view
int main()
{
using namespace std::literals; // easiest way to access the s and sv suffixes
std::cout << "foo\n"; // no suffix is a C-style string literal
std::cout << "goo\n"s; // s suffix is a std::string literal
std::cout << "moo\n"sv; // sv suffix is a std::string_view literal
return 0;
};
Tip
The “s” suffix lives in the namespace std::literals::string_literals
. The easiest way to access the literal suffixes is via using directive using namespace std::literals
. We discuss using directives in lesson 6.12 -- Using declarations and using directives. This is one of the exception cases where using
an entire namespace is okay, because the suffixes defined within are unlikely to collide with any of your code.
You probably won’t need to use std::string
literals very often (as it’s fine to initialize a std::string
object with a C-style string literal), but we’ll see a few cases in future lessons where using std::string
literals instead of C-style string literals makes things easier.
If you try to define a constexpr std::string
, your compiler will probably generate an error:
#include <iostream>
#include <string>
using namespace std::literals;
int main()
{
constexpr std::string name{ "Alex"s }; // compile error
std::cout << "My name is: " << name;
return 0;
}
This happens because constexpr std::string
isn’t supported in C++17 or earlier, and only has minimal support in C++20. If you need constexpr strings, use std::string_view
instead (discussed in lesson 4.18 -- Introduction to std::string_view.
Conclusion
std::string is complex, leveraging many language features that we haven’t covered yet. Fortunately, you don’t need to understand these complexities to use std::string for simple tasks, like basic string input and output. We encourage you to start experimenting with strings now, and we’ll cover additional string capabilities later.
Quiz time
Question #1
Write a program that asks the user to enter their full name and their age. As output, tell the user the sum of their age and the number of letters in their name (use the std::string::length()
member function to get the length of the string). For simplicity, count any spaces in the name as a letter.
Sample output:
Enter your full name: John Doe Enter your age: 32 Your age + length of name is: 40
Reminder: std::string::length() returns an unsigned int. You should static_cast this to an int before adding the age so you don’t mix signed and unsigned values.