Урок №57. Строки (std::string)

  Юрий  | 

  |

  Обновл. 20 Дек 2022  | 

 304531

 ǀ   94 

Вашей первой программой на языке C++, вероятно, была всеми известная программа «Hello, world!»:

Не так ли? Но что такое Hello, world!? Hello, world! — это последовательность символов или просто строка (англ. «string»). В языке C++ мы используем строки для представления текста (имен, адресов, слов и предложений). Строковые литералы (такие как Hello, world!) помещаются в двойные кавычки.

Поскольку их часто используют в программах, то большинство современных языков программирования имеют встроенный тип данных string. В языке C++ есть также этот тип, но не как часть основного языка, а как часть Стандартной библиотеки С++.

Тип данных string

Чтобы иметь возможность использовать строки в C++, сначала нужно подключить заголовочный файл string. Как только это будет сделано, мы сможем определять переменные типа string:

Как и с обычными переменными, мы можем инициализировать переменные типа string или присваивать им значения:

Строки также могут содержать числа:

Стоит отметить, что присваиваемые числа тип string обрабатывает как текст, а не как числа, и, следовательно, ими нельзя манипулировать как обычными числами (например, вы не сможете выполнять с ними арифметические операции). Язык C++ автоматически не преобразовывает их в значения целочисленных типов или типов с плавающей точкой.

Ввод/вывод строк


Строки можно выводить с помощью std::cout:

Результат выполнения программы:

My name is Sasha

А вот с std::cin дела обстоят несколько иначе. Рассмотрим следующий пример:

Результат выполнения программы:

Enter your full name: Sasha Mak
Enter your age: Your name is Sasha and your age is Mak

Хм, что-то не так! Что же случилось? Оказывается, оператор извлечения (>>) возвращает символы из входного потока данных только до первого пробела. Все остальные символы остаются внутри cin, ожидая следующего извлечения.

Поэтому, когда мы использовали оператор >> для извлечения данных в переменную myName, только Sasha был извлечен, Mak остался внутри std::cin, ожидая следующего извлечения. Когда мы использовали оператор >> снова, чтобы извлечь данные в переменную myAge, мы получили Mak вместо 25. Если бы мы сделали третье извлечение, то получили бы 25.

Использование std::getline()

Чтобы извлечь полную строку из входного потока данных (вместе с пробелами), используйте функцию std::getline(). Она принимает два параметра: первый — std::cin, второй — переменная типа string.

Вот программа, приведенная выше, но уже с использованием std::getline():

Теперь работает как надо:

Enter your full name: Sasha Mak
Enter your age: 25
Your name is Sasha Mak and your age is 25

Использование std::getline() c std::cin


Извлечение данных из std::cin с помощью std::getline() иногда может приводить к неожиданным результатам. Например, рассмотрим следующую программу:

Возможно, вы удивитесь, но когда вы запустите эту программу, и она попросит вас ввести ваше имя, она не будет ожидать вашего ввода, а сразу выведет результат (просто пробел вместо вашего имени)!

Пробный запуск программы:

Pick 1 or 2: 2
Now enter your name: Hello, , you picked 2

Почему так? Оказывается, когда вы вводите числовое значение, поток cin захватывает вместе с вашим числом и символ новой строки. Поэтому, когда мы ввели 2, cin фактически получил 2\n. Затем он извлек значение 2 в переменную, оставляя \n (символ новой строки) во входном потоке. Затем, когда std::getline() извлекает данные для myName, он видит в потоке \n и думает, что мы, должно быть, ввели просто пустую строку! А это определенно не то, что мы хотим.

Хорошей практикой является удалять из входного потока данных символ новой строки. Это можно сделать следующим образом:

Если мы вставим эту строку непосредственно после получения входных данных, то символ новой строки будет удален из входного потока, и программа будет работать должным образом:

Правило: При вводе числовых значений не забывайте удалять символ новой строки из входного потока данных с помощью std::cin.ignore().

Добавление строк

Вы можете использовать оператор + для объединения двух строк или оператор += для добавления одной строки к другой.

В следующей программе мы протестируем эти два оператора, а также покажем, что произойдет, если вы попытаетесь использовать оператор + для соединения двух числовых строк:

Результат выполнения программы:

4412
44 cats

Обратите внимание, оператор + объединил две числовые строки в одну (44 + 12 = 4412). Он не складывал эти строки как числа.

Длина строк


Чтобы узнать длину строки, мы можем сделать следующее:

Результат выполнения программы:

Sasha has 5 characters

Обратите внимание, вместо запроса длины строки как length(myName), мы пишем myName.length().

Функция запроса длины строки не является обычной функцией как те, которые мы использовали ранее. Это особый тип функции класса std::string, который называется методом. Мы поговорим об этом детально, когда будем рассматривать классы.

Примечание: Также Вы можете приобрести Самоучитель «Уроки по С++» в .pdf-формате.

Тест

Напишите программу, которая просит у пользователя ввести его имя, фамилию и возраст. В результате, укажите пользователю, сколько лет он прожил на каждую букву из его имени и фамилии (чтобы было проще, учитывайте также пробелы). Например:

Enter your full name: Tom Cats
Enter your age: 45
You've lived 5.625 years for each letter in your name.

Уточнение: Возраст 45 делится на длину имени и фамилии «Tom Cats» (8 букв, учитывая пробел), что равно 5.625.

Ответ

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (541 оценок, среднее: 4,84 из 5)
Загрузка...

Комментариев: 94

  1. Сашуня:

    Задание:

  2. Valdemar:

    Юра, а почему нельзя изначально переменную возраста объявить как double, чтобы потом воспользоваться приоритетом типов операндов и не проводить преобразования с помощью static_cast?

    Результат программы:

    Enter your full name: Volodymyr Viesich
    Enter your age: 41
    2.41176

    Или данное неявное преобразование опасно?

    1. stasss:

      я думаю здесь уже больше работа с пользователем, вот к примеру вам в будущем нужно будет добавить проверку на правильно введенный возраст, возраст у нас должен быть целым числом, нельзя писать 18,5 и тд. поэтому нам нужен именно int для ввода и проверки данных

  3. Сергей:

    Как вы работаете с std::string, если необходимо работать с текстом на разных языках. На MFC все просто — там строки UNICODE.

  4. Alex:

    Сначала разбил все на функции, присвоил переменной возраста сразу значение double, а кол-во букв высчитывал сразу в формуле в строке вывода результата на экран. Все работало. Но потом подсмотрел в ответ, и, для практики, добавил в main пару строк:
    1 . Преобразования возраста из int в double.
    2. Ввел переменную int, принимающую из строки имени кол-во букв.

  5. Alexjazz:

    Добавил исключение пробелов

  6. Егор:

  7. Алексей:

    Не ожидал, что с русским языком возникнут проблемы. Приложение почему-то при подсчёте длины полного имени выводит значение в два раза больше чем нужно. Также удивило, что у меня во время подсчёта количества символов в имени игнорируется пробел (из уточнения к заданию можно сделать вывод, что он вроде как должен учитываться).

    Редактор Sublime Text 4, компилируется в g++, ОС macOS BigSur.

    1. Нина:

      Извините, пожалуйста, но Вы не подключили кириллицу. Кроме того, непонятно деление на 2, что не соответствует условию задачи. Когда я все это исправила и ввела пропуски, необходимые для понимания, все сработало….. Извините, может я чего не поняла.

      1. Алексей:

        У меня кириллица работает без какого-либо подключения. Скорее всего, подключение нужно только для Windows.
        Деление на 2 нужно, так как без него длина получается ровно в два раза больше.
        Возможно, это просто специфика macOS ¯\_(ツ)_/¯

        1. Алексей:

          Решил ещё раз вернуться к этому вопросу. Получается что .length()/.size() возвращают размер строки в байтах и всё хорошо если в строке использовались латинские символы, которые занимают 1 байт, но в моём случае русские символы занимают 2 байта (так же не стоит забывать про символы типа á, é, í, ó, которые тоже занимают больше 1 байта).
          В результате написал такой костыль с массивом из русского алфавита.

        2. Роман:

          Это специфика любой системы с Юникодом по умолчанию ))
          Так как кирилица в стандартной Windows — CP-1251 однобайтовая, а Юникод — 2 байта (UTF-8) — а length — считает длину именно в байтах, а не в символах )

  8. Xenter:

  9. Wendrowski:

  10. Петр:

    Есть тонкости при работе в разных ОС…

  11. Сержик:

  12. Gr1nya:

  13. ОЛЕГ:

    Правда программа не такая красивая как многие другие, но работает на моем Code::Blocks 20.03 нормально. И кракозябров нет (в комментариях у многих проблемы с кракозябрами вместо русских букв). Я использовал setlocale (LC_ALL, "rus"), а в Settings выставил WINDOWS-1251.

    main.ccp:

  14. Антон:

    Решил упростить настолько, насколько хватает знаний, получилось адекватно. Из уроков понял, что можно сокращать, для вывода не использовал дополнительных переменных, все вычисления произвел в выводе.
    Очищать поток после ввода числа не имело смысла в данном примере, решил просто его опробовать.

  15. Антон:

    интересно, почему нельзя cin >> nameValue использовать в функции. В переменную у меня значение не записывается.

    1. Андрей:

      Можна. Нужно переделать функцию.

      И в main сделать так:

      1. Антон:

        Огромное Вам спасибо. Реально работает!

  16. Виктор:

    Вот что получилось у меня

  17. Влад:

    Вот автор пишет по поводу первой программы :
    Оказывается, оператор извлечения (>>) возвращает символы из входного потока данных только до первого пробела. Все остальные символы остаются внутри cin, ожидая следующего извлечения.
    По моему не совсем так :
    символы входного потока можно ввести до нажатия Энтер или пробел (равноценно) и следующие символы (возраст ) уже не вводятся

  18. Андрей:

    Оцените код

    1. Сергей:

      Прикольно.
      Небольшая помарка в строке 20. Надо:

      В условии: Возраст … делится на длину имени и фамилии …

  19. Пётр:

    Вот такой вот код вышел.
    Сначала написал, потом посмотрел ответ и понял, что несколько усложнил… Но зато функция main очень наглядно читается 🙂

  20. Кашпировский:

    У нас есть карта

    Если делать длиннее чем 195 вылезает ошибка Out of range, как увеличить длинну строки ? Может чтото в памяти как то на это выделить ?

  21. Руслан:

    Здравствуйте! Что за магическое число 32767 используется в качестве параметра std::cin.ignore в данном уроке? Что лучше использовать с std::cin.ignore конкретный числовой литерал или же следующий стейтмент:

    ?

    1. Юлий:

      Эта строка была откомментирована автором уроков.

    2. Сергей:

      Магическое число 32767 — это размер буфера объекта std::cin. Урок 72 https://ravesli.com/urok-72-obrabotka-nekorrektnogo-vvoda-cherez-std-cin/

  22. Руслан:

  23. Vadim:

    Скажите, сколько может в себя вместить этот string из cin, ведь string объявлен на стеке, а с стек ограничен или я чего не понимаю? Как вообще разруливается эта проблема?

    1. Roman:

      Со строками и выделением памяти под них и где именно эта память будет выделена не все тривиально. Как пишет Страуструп, выделение памяти на стеке работает для небольших строк, хотя понятие небольшой — относительное. Если у вас строка в 14 символов, то можно считать, что она не большая. Для длинных строк происходит выделение памяти в куче, тоже происходит если строка стала длинной. Но память снова может быть выделена на стеке, если строка стала достаточно короткой.

  24. Богдан:

    Скажите, какой размер имеет переменная типа string?
    В этом коде результат 40. Это 40 байт?

    1. Roman:

      Да, тут 40 байтов. Не очень понятно, что вы хотели получить: размер объекта в байтах или длину строки? Для длины строки нужно использовать функцию-член объекта string length(). А используя sizeof() вы можете получать разные результаты в зависимости от того, где выделена память под хранение массива символов: на стеке или в куче, и в зависимости от реализации самого объекта string. Есть ли вообще смысл использовать sizeof() для объекта string? Почитайте комментарий выше, про способы хранения символов в string. И да, у вас строка только из одного пробела, но памяти выделяется обычно больше, чтобы при изменении размера строки не приходилось постоянно перераспределять ее. Количество выделяемой памяти и в каком месте она выделяется отдано на откуп стандартной библиотеке и компилятору.

  25. Furxie Fluke:

    Написал маленькую программку по заданию, и всё нормально, но, решил сюда скинуть свой код и спросить может кто знает о одной проблемке которая возникает

    Если в переменной strangeNumber поменять тип данных double на int, происходит ошибка при запуске
    Тоесть, да, int меньше double, и прочие вещи, переполнение там, но всё же это должно работать, или я что то не правильно понимаю?

    1. Руслан:

      если ещё актуально
      error: narrowing conversion (компилятор g++ с включенными флагами) программа не собирается, аналогично с отключенными флагами программа собирается.

    2. User:

      (name + secondName).length() — имеет тип данных unsigned int, а он по приоритету выше чем INT. Думаю проблема в этом.

    3. Сергей:

      Очень интересно получилось с int.
      В строке 18 signed (short) делится на unsigned (int). И в этом месте компилятор не понимает как использовать старший бит в short, то есть полагает, что результат может или поменяет знак (разновидность переполнения).
      Один из путей выхода сделать: в строке 14 unsigned short age{};

  26. Алексей:

  27. mloborev:

    Сначала подумал, что сделал лишь хуже, вводя имя и фамилию по отдельности, зато так можно посчитать результат без пробела гораздо проще.

  28. Inviser666:

    ну вроде так

  29. winnie:

    Столкнулся с проблемой вывода кириллических символов. Ранее во всех примерах использовал setlocale(LC_ALL,"ru") и вывод работал корректно. При использовании getline ( ) то, что вводится в переменную типа string, потом выводится в кривой кодировке. При использовании

    обратная ситуация — вывод идет некорректный, а то что на русском вводилось в string переменную потом отображается корректно.
    Подскажите, пожалуйста, куда копать и как это победить?
    Спасибо.

    1. Константин:

      #include <Windows.h> легитимизирует все эти SetConsol-и

  30. armus1:

    Уважаемый Юрий, скажите пожалуйста, допустимо ли такое решение?

    1. Артур:

      Можете подсказать,что делать если мне вместо результата выдаёт inf в тесте?
      Выдаёт вот это:You have lived inf years for each letter in your name

      1. PraiseTheSun:

        тип double может выдавать значение бесконечности при делении на 0

  31. Alex:

    Точнее использовать

  32. Игорь:

  33. Алексей:

  34. Markiyan:

  35. Steve Dekart:

  36. Алексей:

    Каюсь, подглядываю как описать строку и прочее)
    С опытом прийдет)

  37. Алексей:

    Эм, написал за 4 минуты билеберду малек)
    Работает, но видимо не понял, что именно просили в задании)

  38. havvk01:

  39. Илья:

    Здравствуйте!
    У меня три вопроса:
    1) Почему Вы в своём ответе на тестовый вопрос не используете стейтмент cin.ignore (32767, '\n'); ?
    2) Что это вообще за магическое число такое 32767? откуда оно взялось? Почему не использовать меньшее, например 100 (врядли в мире есть имя и фамилия длиной больше ста символов. Ну если есть, то использовать 200, 300, 500 в конце концов=) ну не 32 тысячи же=) )
    3) Почему у меня программа после получения имени и фамилии после первого нажатия Enter просто переходит на следующую строку в консоли и только после повторного нажатия Enter выводит запрос возраста. Заранее большое спасибо за помощь!
    Вот программа:

    1. Константин:

      32767 — это (как я догадываюсь) число символов, которые можно разместить в одной строке; в конце компилятор сам (втихаря) вставляет \n

    2. Cerberus:

      "Почему у меня программа после получения имени и фамилии после первого нажатия Enter просто переходит на следующую строку в консоли и только после повторного нажатия Enter выводит запрос возраста"

      std::getline забирает строку целиком, вместе с символом перевода строки. А после этого Вы говорите, что cin должен проигнорировать следующий перевод строки, прежде чем что-то делать дальше. Вот он и ждал, пока этот самый перевод строки появится в потоке ввода — то есть, пока Вы не нажмёте Enter.

  40. Денис:

    По поводу getline разъяснил Георгий в комментариях (3 августа 2018), спасибо
    Чуть-чуть в ступоре был, почему string русский выводил в виде символов, ведь setlocal был прописан, оказалось всё просто:) Ещё скрыл символы после точки double при выводе результата, может кому-то будет полезно (если float писать вместо "%.2lf", нужно "%.2f", где 2 (кол-во символов после точки), но результат, как я понял, округляется. Будьте осторожней при необходимости вывести точный результат (или проверьте мои слова, я не уверен). И да, я решил без функций, с ними дольше, а программа маленькая.

  41. Вячеслав:

    у меня так получилось:

  42. Игорь:

    У меня как-то размазано получилось по сравнению с ответом )))

  43. Alexey:

    Подскажите, насколько код написан красиво, вроде старался следовать всем рекомендациям.

  44. Алексей:

  45. somebox:

    Что-то у меня не получается. Ввожу имя "Саша", возраст — "12" и получаю "1,5" на выходе, а должен же 4. Что не так? Вот код:

    1. Владимир:

      Скопировал программу, у меня всё нормально выдаёт, но выдать он должен 3, а не 4 (12 / 4 = 3)

  46. Giveyn:

    А почему сразу myAge не присвоить double, и обойтись без промежуточного agePerLetter, а деление выполнить в строке вывода?

  47. Ярослав:

  48. Katerina:

    Здравствуйте, подскажите, пожалуйста, почему в приведенной функции выскакивает предупреждение? Я так понимаю linker не доволен… но программа компилируется и работает правильно вроде. Заранее благодарна.

    1. Георгий:

      Функция "std::getline" получает от пользователя не цифру, а строку, так что число которое ты введёшь, будет просто строкой, а не числом (звучит странно), при этом ты пытаешься в тип данных Int "впихнуть" строчку. Проще говоря, это тоже самое, что написать:

      Надеюсь, я объяснил понятно 🙂

      1. Георгий:

        А нет, я пересмотрел программу, и не знаю в чём проблема 😀

    2. Torgu:

      было бы неплохо увидеть само предупреждение

      1. Katerina:

        Добрый вечер! Спасибо за feedback.
        Ниже код и предупреждение:

        Файл io.h

        Файл io.cpp

        Файл main.cpp

        Предупреждение в файле io.cpp в строке int nameLength = name.length();

        Implicit conversion loses integer precision: 'std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::s

        Названия файлов просто лень переименовывать )))
        Спасибо!

        1. Get Leevz:

          Метод length() возвращает тип unsigned int, который вы присваиваете переменной signed int. Из-за этого происходит неявное преобразование с возможностью потери точности. Это можно решить с помощью изначального типа переменной unsigned int, но я бы просто забил, ведь чтобы произошло переполнение, пользователь должен ввести имя длинною более чем в 2 147 483 647 символов, а это…хммм, как бы немного многовато

        2. Katerina:

          Get Leevz, почему-то не могу ответить на ваш комментарий, поэтому отвечаю на свой )) Спасибо вам большое за разъяснения, сама бы до этого не дошла!

  49. Sergey Groysman:

    Юрий, доброго дня. Подскажите, в чём разница в данных записях, если работают обе, переводя число в double и мы получаем на выходе число с плавающей точкой.

    вместо

    можно ли обходиться в принципе без static_cast?
    Спасибо.

    1. Фото аватара Юрий:

      Привет. Ответ на ваш вопрос находится в уроке 56.

      1. Torgu:

        А обязательно переводить вручную? Я просто дал переменной возраста тип float. Компилятор же сам преобразовывает типы

  50. Серж:

    Перечитал урок 7.
    Проблема 1
    Как использовать русский язык в консоли C++?
    Слово в слово повторено как в программе выше и где сказано что всё работает.
    У меня в CodeBlocks не работает. Возможно в студии будет работать , не проверял.
    Остается в уроке 7 прочитать совет в самом низу: Во-первых, спросите Google…

    1. Фото аватара Юрий:

      Верно, если Google не поможет, спросите на StackOverflow. Код рабочий, если у вас не работает, значит у вас уже специфическая проблема: возможно кодировка в Code:Blocks у вас не та, что нужно. Возможно, у вас что-то подключено (плагин, расширение или что-либо другое), что блокирует корректное выполнение setlocale.

      1. Серж:

        Ясно, спасибо. Подключена русификация интерфейса и все. Пока не искал решения проблемы, т.к. это не сильно влияет на изучение.
        Огромное спасибо за такой нужный труд по переводу и поддержанию сайта с этим учебным курсом. Из всего что я находил ,это самое лучшее. Спасибо!!!

        1. Фото аватара Юрий:

          Пожалуйста 🙂

          Попробуйте отключить русификацию интерфейса и посмотрите какая у вас кодировка стоит — сбросьте всё до значений по умолчанию. И если уже никак, то вы знаете, где искать.

  51. Максим:

  52. Ka4:

    Скажите, пожалуйста, что я не так сделал!

    1. Фото аватара Юрий:

      Ваш код рабочий. Результаты совпадают.

      Что не так:

      1. У вас функция im9_famili9 выполняет сразу два задания: получает строку и высчитывает её длину. (Функция должна выполнять одно задание)

      2. Функции result нужно было присвоить тип void и ничего не возвращать, а сразу выводить результат в консоль. А то у вас result выводит результат и возвращает его еще в main() — зачем и почему? В main вы с переменной z ничего не делаете, её объявление вообще не нужно.

      Всё остальное гуд.

      1. Ka4:

        Точно..
        Спасибо больше!)
        Отличные уроки!!!
        Догоняю программу в ВУЗе с помощью этого сайта!

        1. Фото аватара Юрий:

          Если честно, то я тоже свою программу из универа догнал и перегнал именно по этим урокам 🙂

  53. Герман:

    Спасибо!!!

    1. Фото аватара Юрий:

      Пожалуйста 🙂

  54. Герман:

    Уважаемый автор, почему при выводе в консоль значение переменной myName, не поддерживается кириллица!

    1. Фото аватара Юрий:

      Нужно подключить Windows.h (заголовочный файл) и прописать строчки:

      Вот всё работает:

      1. Серж:

        Почему-то в CodeBlocks 17.12 выводит этим способом кракозябры как на ввод ,так и на вывод. Если использовать setlocale(LC_ALL, "rus"); или setlocale(LC_ALL, ""); вывод идет на русском , а на ввод кракозябры ,если использовать
        SetConsoleCP(1251);
        SetConsoleOutputCP(1251);
        то даже при подключенном setlocale(LC_ALL, "rus"); все равно кракозябры.

      2. GOGOLEK:

        Разве setlocale(LC_ALL, "rus") уже не помогает?

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *