Вашей первой программой на языке C++, вероятно, была всеми известная программа «Hello, world!»:
1 2 3 4 5 6 7 |
#include <iostream> int main() { std::cout << "Hello, world!" << std::endl; return 0; } |
Не так ли? Но что такое Hello, world!
? Hello, world!
— это последовательность символов или просто строка (англ. «string»). В языке C++ мы используем строки для представления текста (имен, адресов, слов и предложений). Строковые литералы (такие как Hello, world!
) помещаются в двойные кавычки.
Поскольку их часто используют в программах, то большинство современных языков программирования имеют встроенный тип данных string. В языке C++ есть также этот тип, но не как часть основного языка, а как часть Стандартной библиотеки С++.
Тип данных string
Чтобы иметь возможность использовать строки в C++, сначала нужно подключить заголовочный файл string. Как только это будет сделано, мы сможем определять переменные типа string:
1 2 3 4 5 |
#include <string> // ... std::string name; // ... |
Как и с обычными переменными, мы можем инициализировать переменные типа string или присваивать им значения:
1 2 |
std::string name("Sasha"); // инициализируем переменную name строковым литералом "Sasha" name = "Masha"; // присваиваем переменной name строковый литерал "Masha" |
Строки также могут содержать числа:
1 |
std::string myID("34"); // "34" здесь - это не целое число 34! |
Стоит отметить, что присваиваемые числа тип string обрабатывает как текст, а не как числа, и, следовательно, ими нельзя манипулировать как обычными числами (например, вы не сможете выполнять с ними арифметические операции). Язык C++ автоматически не преобразовывает их в значения целочисленных типов или типов с плавающей точкой.
Ввод/вывод строк
Строки можно выводить с помощью std::cout:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> #include <string> int main() { std::string name("Sasha"); std::cout << "My name is " << name; return 0; } |
Результат выполнения программы:
My name is Sasha
А вот с std::cin дела обстоят несколько иначе. Рассмотрим следующий пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> #include <string> int main() { std::cout << "Enter your full name: "; std::string myName; std::cin >> myName; // это будет работать не так, как ожидается, поскольку извлечение данных из потока std::cin останавливается на первом пробеле std::cout << "Enter your age: "; std::string myAge; std::cin >> myAge; std::cout << "Your name is " << myName << " and your age is " << myAge; } |
Результат выполнения программы:
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():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> #include <string> int main() { std::cout << "Enter your full name: "; std::string myName; std::getline(std::cin, myName); // полностью извлекаем строку в переменную myName std::cout << "Enter your age: "; std::string myAge; std::getline(std::cin, myAge); // полностью извлекаем строку в переменную myAge std::cout << "Your name is " << myName << " and your age is " << myAge; } |
Теперь работает как надо:
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() иногда может приводить к неожиданным результатам. Например, рассмотрим следующую программу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> #include <string> int main() { std::cout << "Pick 1 or 2: "; int choice; std::cin >> choice; std::cout << "Now enter your name: "; std::string myName; std::getline(std::cin, myName); std::cout << "Hello, " << myName << ", you picked " << choice << '\n'; return 0; } |
Возможно, вы удивитесь, но когда вы запустите эту программу, и она попросит вас ввести ваше имя, она не будет ожидать вашего ввода, а сразу выведет результат (просто пробел вместо вашего имени)!
Пробный запуск программы:
Pick 1 or 2: 2
Now enter your name: Hello, , you picked 2
Почему так? Оказывается, когда вы вводите числовое значение, поток cin захватывает вместе с вашим числом и символ новой строки. Поэтому, когда мы ввели 2
, cin фактически получил 2\n
. Затем он извлек значение 2
в переменную, оставляя \n
(символ новой строки) во входном потоке. Затем, когда std::getline() извлекает данные для myName
, он видит в потоке \n
и думает, что мы, должно быть, ввели просто пустую строку! А это определенно не то, что мы хотим.
Хорошей практикой является удалять из входного потока данных символ новой строки. Это можно сделать следующим образом:
1 |
std::cin.ignore(32767, '\n'); // игнорируем символы перевода строки "\n" во входящем потоке длиной 32767 символов |
Если мы вставим эту строку непосредственно после получения входных данных, то символ новой строки будет удален из входного потока, и программа будет работать должным образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> #include <string> int main() { std::cout << "Pick 1 or 2: "; int choice; std::cin >> choice; std::cin.ignore(32767, '\n'); // удаляем символ новой строки из входного потока данных std::cout << "Now enter your name: "; std::string myName; std::getline(std::cin, myName); std::cout << "Hello, " << myName << ", you picked " << choice << '\n'; return 0; } |
Правило: При вводе числовых значений не забывайте удалять символ новой строки из входного потока данных с помощью std::cin.ignore().
Добавление строк
Вы можете использовать оператор +
для объединения двух строк или оператор +=
для добавления одной строки к другой.
В следующей программе мы протестируем эти два оператора, а также покажем, что произойдет, если вы попытаетесь использовать оператор +
для соединения двух числовых строк:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> #include <string> int main() { std::string x("44"); std::string y("12"); std::cout << x + y << "\n"; // объединяем строки x и y (а не складываем числа) x += " cats"; std::cout << x; return 0; } |
Результат выполнения программы:
4412
44 cats
Обратите внимание, оператор +
объединил две числовые строки в одну (44
+ 12
= 4412
). Он не складывал эти строки как числа.
Длина строк
Чтобы узнать длину строки, мы можем сделать следующее:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> #include <string> int main() { std::string myName("Sasha"); std::cout << myName << " has " << myName.length() << " characters\n"; return 0; } |
Результат выполнения программы:
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 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> #include <string> int main() { std::cout << "Enter your full name: "; std::string myName; std::getline(std::cin, myName); // извлекаем целую строку из входного потока в переменную myName std::cout << "Enter your age: "; int myAge; // переменная myAge должна быть типа int, а не типа string, чтобы мы могли проводить с ней арифметические операции std::cin >> myAge; int letters = myName.length(); // вычисляем длину переменной myName (учитывая пробелы) double agePerLetter = static_cast<double>(myAge) / letters; // используем оператор static_cast, чтобы изменить тип переменной myAge на double, дабы сохранить дробную часть при целочисленном делении std::cout << "You've lived " << agePerLetter << " years for each letter in your name.\n"; return 0; } |
Задание:
Юра, а почему нельзя изначально переменную возраста объявить как double, чтобы потом воспользоваться приоритетом типов операндов и не проводить преобразования с помощью static_cast?
Результат программы:
Enter your full name: Volodymyr Viesich
Enter your age: 41
2.41176
Или данное неявное преобразование опасно?
я думаю здесь уже больше работа с пользователем, вот к примеру вам в будущем нужно будет добавить проверку на правильно введенный возраст, возраст у нас должен быть целым числом, нельзя писать 18,5 и тд. поэтому нам нужен именно int для ввода и проверки данных
Как вы работаете с std::string, если необходимо работать с текстом на разных языках. На MFC все просто — там строки UNICODE.
Сначала разбил все на функции, присвоил переменной возраста сразу значение double, а кол-во букв высчитывал сразу в формуле в строке вывода результата на экран. Все работало. Но потом подсмотрел в ответ, и, для практики, добавил в main пару строк:
1 . Преобразования возраста из int в double.
2. Ввел переменную int, принимающую из строки имени кол-во букв.
Добавил исключение пробелов
Не ожидал, что с русским языком возникнут проблемы. Приложение почему-то при подсчёте длины полного имени выводит значение в два раза больше чем нужно. Также удивило, что у меня во время подсчёта количества символов в имени игнорируется пробел (из уточнения к заданию можно сделать вывод, что он вроде как должен учитываться).
Редактор Sublime Text 4, компилируется в g++, ОС macOS BigSur.
Извините, пожалуйста, но Вы не подключили кириллицу. Кроме того, непонятно деление на 2, что не соответствует условию задачи. Когда я все это исправила и ввела пропуски, необходимые для понимания, все сработало….. Извините, может я чего не поняла.
У меня кириллица работает без какого-либо подключения. Скорее всего, подключение нужно только для Windows.
Деление на 2 нужно, так как без него длина получается ровно в два раза больше.
Возможно, это просто специфика macOS ¯\_(ツ)_/¯
Решил ещё раз вернуться к этому вопросу. Получается что .length()/.size() возвращают размер строки в байтах и всё хорошо если в строке использовались латинские символы, которые занимают 1 байт, но в моём случае русские символы занимают 2 байта (так же не стоит забывать про символы типа á, é, í, ó, которые тоже занимают больше 1 байта).
В результате написал такой костыль с массивом из русского алфавита.
Это специфика любой системы с Юникодом по умолчанию ))
Так как кирилица в стандартной Windows — CP-1251 однобайтовая, а Юникод — 2 байта (UTF-8) — а length — считает длину именно в байтах, а не в символах )
Есть тонкости при работе в разных ОС…
Правда программа не такая красивая как многие другие, но работает на моем Code::Blocks 20.03 нормально. И кракозябров нет (в комментариях у многих проблемы с кракозябрами вместо русских букв). Я использовал setlocale (LC_ALL, "rus"), а в Settings выставил WINDOWS-1251.
main.ccp:
Решил упростить настолько, насколько хватает знаний, получилось адекватно. Из уроков понял, что можно сокращать, для вывода не использовал дополнительных переменных, все вычисления произвел в выводе.
Очищать поток после ввода числа не имело смысла в данном примере, решил просто его опробовать.
интересно, почему нельзя cin >> nameValue использовать в функции. В переменную у меня значение не записывается.
Можна. Нужно переделать функцию.
И в main сделать так:
Огромное Вам спасибо. Реально работает!
Вот что получилось у меня
Вот автор пишет по поводу первой программы :
Оказывается, оператор извлечения (>>) возвращает символы из входного потока данных только до первого пробела. Все остальные символы остаются внутри cin, ожидая следующего извлечения.
По моему не совсем так :
символы входного потока можно ввести до нажатия Энтер или пробел (равноценно) и следующие символы (возраст ) уже не вводятся
Оцените код
Прикольно.
Небольшая помарка в строке 20. Надо:
В условии: Возраст … делится на длину имени и фамилии …
Вот такой вот код вышел.
Сначала написал, потом посмотрел ответ и понял, что несколько усложнил… Но зато функция main очень наглядно читается 🙂
У нас есть карта
Если делать длиннее чем 195 вылезает ошибка Out of range, как увеличить длинну строки ? Может чтото в памяти как то на это выделить ?
Здравствуйте! Что за магическое число 32767 используется в качестве параметра std::cin.ignore в данном уроке? Что лучше использовать с std::cin.ignore конкретный числовой литерал или же следующий стейтмент:
?
Эта строка была откомментирована автором уроков.
Магическое число 32767 — это размер буфера объекта std::cin. Урок 72 https://ravesli.com/urok-72-obrabotka-nekorrektnogo-vvoda-cherez-std-cin/
Скажите, сколько может в себя вместить этот string из cin, ведь string объявлен на стеке, а с стек ограничен или я чего не понимаю? Как вообще разруливается эта проблема?
Со строками и выделением памяти под них и где именно эта память будет выделена не все тривиально. Как пишет Страуструп, выделение памяти на стеке работает для небольших строк, хотя понятие небольшой — относительное. Если у вас строка в 14 символов, то можно считать, что она не большая. Для длинных строк происходит выделение памяти в куче, тоже происходит если строка стала длинной. Но память снова может быть выделена на стеке, если строка стала достаточно короткой.
Скажите, какой размер имеет переменная типа string?
В этом коде результат 40. Это 40 байт?
Да, тут 40 байтов. Не очень понятно, что вы хотели получить: размер объекта в байтах или длину строки? Для длины строки нужно использовать функцию-член объекта string length(). А используя sizeof() вы можете получать разные результаты в зависимости от того, где выделена память под хранение массива символов: на стеке или в куче, и в зависимости от реализации самого объекта string. Есть ли вообще смысл использовать sizeof() для объекта string? Почитайте комментарий выше, про способы хранения символов в string. И да, у вас строка только из одного пробела, но памяти выделяется обычно больше, чтобы при изменении размера строки не приходилось постоянно перераспределять ее. Количество выделяемой памяти и в каком месте она выделяется отдано на откуп стандартной библиотеке и компилятору.
Написал маленькую программку по заданию, и всё нормально, но, решил сюда скинуть свой код и спросить может кто знает о одной проблемке которая возникает
Если в переменной strangeNumber поменять тип данных double на int, происходит ошибка при запуске
Тоесть, да, int меньше double, и прочие вещи, переполнение там, но всё же это должно работать, или я что то не правильно понимаю?
если ещё актуально
error: narrowing conversion (компилятор g++ с включенными флагами) программа не собирается, аналогично с отключенными флагами программа собирается.
(name + secondName).length() — имеет тип данных unsigned int, а он по приоритету выше чем INT. Думаю проблема в этом.
Очень интересно получилось с int.
В строке 18 signed (short) делится на unsigned (int). И в этом месте компилятор не понимает как использовать старший бит в short, то есть полагает, что результат может или поменяет знак (разновидность переполнения).
Один из путей выхода сделать: в строке 14 unsigned short age{};
Сначала подумал, что сделал лишь хуже, вводя имя и фамилию по отдельности, зато так можно посчитать результат без пробела гораздо проще.
ну вроде так
Столкнулся с проблемой вывода кириллических символов. Ранее во всех примерах использовал setlocale(LC_ALL,"ru") и вывод работал корректно. При использовании getline ( ) то, что вводится в переменную типа string, потом выводится в кривой кодировке. При использовании
обратная ситуация — вывод идет некорректный, а то что на русском вводилось в string переменную потом отображается корректно.
Подскажите, пожалуйста, куда копать и как это победить?
Спасибо.
#include <Windows.h> легитимизирует все эти SetConsol-и
Уважаемый Юрий, скажите пожалуйста, допустимо ли такое решение?
Можете подсказать,что делать если мне вместо результата выдаёт inf в тесте?
Выдаёт вот это:You have lived inf years for each letter in your name
тип double может выдавать значение бесконечности при делении на 0
Точнее использовать
Каюсь, подглядываю как описать строку и прочее)
С опытом прийдет)
Эм, написал за 4 минуты билеберду малек)
Работает, но видимо не понял, что именно просили в задании)
Здравствуйте!
У меня три вопроса:
1) Почему Вы в своём ответе на тестовый вопрос не используете стейтмент cin.ignore (32767, '\n'); ?
2) Что это вообще за магическое число такое 32767? откуда оно взялось? Почему не использовать меньшее, например 100 (врядли в мире есть имя и фамилия длиной больше ста символов. Ну если есть, то использовать 200, 300, 500 в конце концов=) ну не 32 тысячи же=) )
3) Почему у меня программа после получения имени и фамилии после первого нажатия Enter просто переходит на следующую строку в консоли и только после повторного нажатия Enter выводит запрос возраста. Заранее большое спасибо за помощь!
Вот программа:
32767 — это (как я догадываюсь) число символов, которые можно разместить в одной строке; в конце компилятор сам (втихаря) вставляет \n
"Почему у меня программа после получения имени и фамилии после первого нажатия Enter просто переходит на следующую строку в консоли и только после повторного нажатия Enter выводит запрос возраста"
std::getline забирает строку целиком, вместе с символом перевода строки. А после этого Вы говорите, что cin должен проигнорировать следующий перевод строки, прежде чем что-то делать дальше. Вот он и ждал, пока этот самый перевод строки появится в потоке ввода — то есть, пока Вы не нажмёте Enter.
По поводу getline разъяснил Георгий в комментариях (3 августа 2018), спасибо
Чуть-чуть в ступоре был, почему string русский выводил в виде символов, ведь setlocal был прописан, оказалось всё просто:) Ещё скрыл символы после точки double при выводе результата, может кому-то будет полезно (если float писать вместо "%.2lf", нужно "%.2f", где 2 (кол-во символов после точки), но результат, как я понял, округляется. Будьте осторожней при необходимости вывести точный результат (или проверьте мои слова, я не уверен). И да, я решил без функций, с ними дольше, а программа маленькая.
у меня так получилось:
У меня как-то размазано получилось по сравнению с ответом )))
Подскажите, насколько код написан красиво, вроде старался следовать всем рекомендациям.
Что-то у меня не получается. Ввожу имя "Саша", возраст — "12" и получаю "1,5" на выходе, а должен же 4. Что не так? Вот код:
Скопировал программу, у меня всё нормально выдаёт, но выдать он должен 3, а не 4 (12 / 4 = 3)
А почему сразу myAge не присвоить double, и обойтись без промежуточного agePerLetter, а деление выполнить в строке вывода?
Здравствуйте, подскажите, пожалуйста, почему в приведенной функции выскакивает предупреждение? Я так понимаю linker не доволен… но программа компилируется и работает правильно вроде. Заранее благодарна.
Функция "std::getline" получает от пользователя не цифру, а строку, так что число которое ты введёшь, будет просто строкой, а не числом (звучит странно), при этом ты пытаешься в тип данных Int "впихнуть" строчку. Проще говоря, это тоже самое, что написать:
Надеюсь, я объяснил понятно 🙂
А нет, я пересмотрел программу, и не знаю в чём проблема 😀
было бы неплохо увидеть само предупреждение
Добрый вечер! Спасибо за 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
Названия файлов просто лень переименовывать )))
Спасибо!
Метод length() возвращает тип unsigned int, который вы присваиваете переменной signed int. Из-за этого происходит неявное преобразование с возможностью потери точности. Это можно решить с помощью изначального типа переменной unsigned int, но я бы просто забил, ведь чтобы произошло переполнение, пользователь должен ввести имя длинною более чем в 2 147 483 647 символов, а это…хммм, как бы немного многовато
Get Leevz, почему-то не могу ответить на ваш комментарий, поэтому отвечаю на свой )) Спасибо вам большое за разъяснения, сама бы до этого не дошла!
Юрий, доброго дня. Подскажите, в чём разница в данных записях, если работают обе, переводя число в double и мы получаем на выходе число с плавающей точкой.
вместо
можно ли обходиться в принципе без static_cast?
Спасибо.
Привет. Ответ на ваш вопрос находится в уроке 56.
А обязательно переводить вручную? Я просто дал переменной возраста тип float. Компилятор же сам преобразовывает типы
Перечитал урок 7.
Проблема 1
Как использовать русский язык в консоли C++?
Слово в слово повторено как в программе выше и где сказано что всё работает.
У меня в CodeBlocks не работает. Возможно в студии будет работать , не проверял.
Остается в уроке 7 прочитать совет в самом низу: Во-первых, спросите Google…
Верно, если Google не поможет, спросите на StackOverflow. Код рабочий, если у вас не работает, значит у вас уже специфическая проблема: возможно кодировка в Code:Blocks у вас не та, что нужно. Возможно, у вас что-то подключено (плагин, расширение или что-либо другое), что блокирует корректное выполнение setlocale.
Ясно, спасибо. Подключена русификация интерфейса и все. Пока не искал решения проблемы, т.к. это не сильно влияет на изучение.
Огромное спасибо за такой нужный труд по переводу и поддержанию сайта с этим учебным курсом. Из всего что я находил ,это самое лучшее. Спасибо!!!
Пожалуйста 🙂
Попробуйте отключить русификацию интерфейса и посмотрите какая у вас кодировка стоит — сбросьте всё до значений по умолчанию. И если уже никак, то вы знаете, где искать.
Скажите, пожалуйста, что я не так сделал!
Ваш код рабочий. Результаты совпадают.
Что не так:
1. У вас функция im9_famili9 выполняет сразу два задания: получает строку и высчитывает её длину. (Функция должна выполнять одно задание)
2. Функции result нужно было присвоить тип void и ничего не возвращать, а сразу выводить результат в консоль. А то у вас result выводит результат и возвращает его еще в main() — зачем и почему? В main вы с переменной z ничего не делаете, её объявление вообще не нужно.
Всё остальное гуд.
Точно..
Спасибо больше!)
Отличные уроки!!!
Догоняю программу в ВУЗе с помощью этого сайта!
Если честно, то я тоже свою программу из универа догнал и перегнал именно по этим урокам 🙂
Спасибо!!!
Пожалуйста 🙂
Уважаемый автор, почему при выводе в консоль значение переменной myName, не поддерживается кириллица!
Нужно подключить Windows.h (заголовочный файл) и прописать строчки:
Вот всё работает:
Почему-то в CodeBlocks 17.12 выводит этим способом кракозябры как на ввод ,так и на вывод. Если использовать setlocale(LC_ALL, "rus"); или setlocale(LC_ALL, ""); вывод идет на русском , а на ввод кракозябры ,если использовать
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
то даже при подключенном setlocale(LC_ALL, "rus"); все равно кракозябры.
Ответ на ваш вопрос ищите в Уроке 7. Самые распространенные проблемы и их решения в C++.
Разве setlocale(LC_ALL, "rus") уже не помогает?