4.12 – Знакомство с std::string
Самая первая написанная вами программа на C++, вероятно, выглядела примерно так:
#include <iostream>
int main()
{
std::cout << "Hello, world!\n";
return 0;
}
Так что точно такое "Hello, world!"? "Hello, world!" представляет собой набор последовательных символов, называемый строкой. В C++ мы используем строки для представления текста, такого как имена, адреса, слова и предложения. Строковые литералы (например, "Hello, world!\n") помещаются в двойные кавычки, чтобы идентифицировать их как строки.
Поскольку в программах строки обычно используются, большинство современных языков программирования включают в себя базовый строковый тип данных. В C++ строки не являются базовым типом (на самом деле они являются составным типом и определены в стандартной библиотеке C++, а не как часть ядра языка). Но строки достаточно просты и полезны, поэтому мы представим их здесь, а не будем дожидаться главы о составных типах (глава 8).
std::string
Чтобы использовать строки в C++, нам сначала нужно включить через #include
заголовочный файл <string>, чтобы ввести объявления для std::string
. Как только это будет сделано, мы сможем определить переменные типа std::string
.
#include <string> // разрешает использование std::string
std::string myName {}; // пустая строка
Вы можете инициализировать или присваивать значения строкам, как и обычным переменным, что и следовало ожидать:
std::string myName{ "Alex" }; // инициализируем myName строковым литералом "Alex"
myName = "John"; // присваиваем переменной myName строковый литерал "John"
Обратите внимание, что строки также могут содержать числа:
std::string myID{ "45" }; // "45" не то же самое, что целое число 45!
В строковой форме числа обрабатываются как текст, а не как числа, и поэтому ими нельзя манипулировать как числами (например, вы не можете их умножать). C++ не будет автоматически преобразовывать строковые числа в целочисленные значения или значения с плавающей запятой.
Вывод строк
Строки можно выводить, как и ожидалось, с помощью std::cout
:
#include <iostream>
#include <string>
int main()
{
std::string myName{ "Alex" };
std::cout << "My name is: " << myName << '\n';
return 0;
}
Эта программа печатает:
My name is: Alex
Пустые строки ничего не напечатают:
#include <iostream>
#include <string>
int main()
{
std::string empty{ };
std::cout << '[' << empty << ']';
return 0;
}
Эта программа напечатает:
[]
Ввод строк с помощью std::cin
Использование строк с std::cin
может преподнести сюрпризы! Рассмотрим следующий пример:
#include <iostream>
#include <string>
int main()
{
std::cout << "Enter your full name: ";
std::string name{};
std::cin >> name; // это не будет работать должным образом,
// так как std::cin прерывается на пробелах
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;
}
Ниже показаны результаты пробного запуска этой программы:
Enter your full name: John Doe
Enter your age: Your name is John and your age is Doe
Хммм, это неправильно! Что случилось? Оказывается, что при использовании operator>>
для извлечения из cin
строки operator>>
возвращает только символы до первого попавшегося пробела. Все остальные символы остаются внутри std::cin
в ожидании следующего извлечения.
Поэтому, когда мы использовали operator>>
для извлечения строки в переменную name
, было извлечено только "John", оставив "Doe" внутри std::cin
. Когда мы снова использовали operator>>
для получения переменной age
, он извлек "Doe" вместо того, чтобы ждать, пока мы введем возраст. На этом программа заканчивается.
Для ввода текста используйте std::getline()
Чтобы прочитать всю строку входных данных в переменную строки, лучше вместо этого использовать функцию std::getline()
. std::getline()
принимает два параметра: первый – это std::cin
, а второй – ваша строковая переменная.
Вот та же программа, что и выше, с использованием std::getline()
:
#include <string> // для std::string и std::getline
#include <iostream>
#include <iomanip> // для std::ws
int main()
{
std::cout << "Enter your full name: ";
std::string name{};
std::getline(std::cin >> std::ws, name); // считываем полную строку текста в name
std::cout << "Enter your age: ";
std::string age{};
std::getline(std::cin >> std::ws, age); // считываем полную строку текста в age
std::cout << "Your name is " << name << " and your age is " << age << '\n';
return 0;
}
Теперь наша программа работает как и ожидалось:
Enter your full name: John Doe
Enter your age: 23
Your name is John Doe and your age is 23
Что еще за std::ws
?
В уроке «4.8 – Числовые типы с плавающей точкой» мы обсудили манипуляторы вывода, которые позволяют нам изменять способ отображения выводимых значений. В том уроке мы использовали функцию манипулятора вывода std::setprecision()
, чтобы изменить количество значащих цифр, отображаемых std::cout
.
C++ также поддерживает манипуляторы ввода (определенные в заголовке iomanip), которые изменяют способ приема входных данных. Манипулятор ввода std::ws
говорит std::cin
игнорировать любые начальные пробелы. Обратите внимание, что std::ws
не является функцией.
Давайте разберемся, чем он полезен. Рассмотрим следующую программу:
#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); // примечание: здесь нет std::ws
std::cout << "Hello, " << name << ", you picked " << choice << '\n';
return 0;
}
Вот пример результата работы этой программы:
Pick 1 or 2: 2
Now enter your name: Hello, , you picked 2
Данная программа сначала просит вас ввести 1 или 2 и ждет, когда вы это сделаете. Пока всё хорошо. Затем она просит вас ввести свое имя. Однако на самом деле она не будет ждать, пока вы введете свое имя! Вместо этого она печатает строку "Hello", а затем завершит работу. Что случилось?
Оказывается, когда вы вводите значение с помощью operator>>
, std::cin
не только захватывает значение, но также захватывает символ новой строки ('\n'), который появляется, когда вы нажимаете клавишу Enter. Итак, когда мы набираем 2 и нажимаем Enter, std::cin
получает строку "2\n". Затем он извлекает 2 в переменную choice
, оставляя символ новой строки на потом. Затем, когда std::getline()
переходит к чтению имени, он видит, что "\n" уже находится в потоке, и полагает, что мы, должно быть, ввели пустую строку! Это определенно не то, что было задумано.
Мы можем изменить приведенную выше программу, чтобы использовать манипулятор ввода std::ws
, чтобы указать std::getline()
игнорировать любые начальные пробельные символы:
#include <string>
#include <iostream>
#include <iomanip> // для std::ws
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); // примечание: здесь добавлен std::ws
std::cout << "Hello, " << name << ", you picked " << choice << '\n';
return 0;
}
Теперь эта программа будет работать так, как задумано.
Pick 1 or 2: 2
Now enter your name: Alex
Hello, Alex, you picked 2
Лучшая практика
Если для чтения строк используется функция std::getline
, используйте манипулятор ввода std::ws
, чтобы игнорировать начальные пробелы.
Ключевые выводы
Использование оператора извлечения (>>
) с std::cin
игнорирует начальные пробелы.
std::getline
не игнорирует начальные пробелы, если вы не используете манипулятор ввода std::ws
.
Длина строки
Если мы хотим знать, сколько символов находится в std::string
, мы можем спросить переменную std::string
о ее длине. Синтаксис для этого отличается от того, что вы видели раньше, но он довольно прост:
#include <iostream>
#include <string>
int main()
{
std::string myName{ "Alex" };
std::cout << myName << " has " << myName.length() << " characters\n";
return 0;
}
Это напечатает:
Alex has 4 characters
Обратите внимание, что вместо того, чтобы запрашивать длину строки как length(myName)
, мы говорим myName.length()
. Функция length()
не является обычной автономной функцией – это особый тип функции, которая принадлежит std::string
и называется функцией-членом. Позже мы расскажем о функциях-членах, в том числе о том, как написать свои собственные.
Заключение
std::string
сложен и использует многие языковые функции, которые мы еще не рассмотрели. К счастью, вам не нужно разбираться в этих сложностях, чтобы использовать std::string
для простых задач, таких как простейший ввод и вывод строк. Мы рекомендуем вам начать экспериментировать со строками прямо сейчас, а дополнительные возможности строк мы рассмотрим позже.
Небольшой тест
Вопрос 1
Напишите программу, которая просит пользователя ввести свое полное имя и возраст. В качестве вывода сообщите пользователю, сколько лет он прожил для каждой буквы в своем имени (для простоты считайте пробелы как букву).
Пример вывода:
Enter your full name: John Doe
Enter your age: 46
You've lived 5.75 years for each letter in your name.
Ответ
#include <iostream>
#include <string>
#include <iomanip>
int main()
{
std::cout << "Enter your full name: ";
std::string name{};
std::getline(std::cin >> std::ws, name); // читаем полную текстовую строку в name
std::cout << "Enter your age: ";
// возраст должен быть целым числом, а не строкой, чтобы мы могли
// выполнять с ним математические вычисления
int age{};
std::cin >> age;
// получаем количество букв в имени (включая пробелы)
int letters{ static_cast<int>(name.length()) };
// статическое приведение age к double, чтобы избежать целочисленного деления
double agePerLetter{ static_cast<double>(age) / letters };
std::cout << "You've lived " << agePerLetter << " years for each letter in your name.\n";
return 0;
}