Перейти к содержимому

Как считать строку в си

  • автор:

Ввод в языке C++ через cin

Для считывания данных со стандартного ввода используется объект cin , также определенных в заголовочном файле iostream . Его использование похоже на использование объекта cout для вывода. Например, для считывания двух переменных a и b нужно дать команду:

Также как и в случае с cout , нет нужды беспокоиться о типах данных — можно считывать целые и действительные числа, символы и строки.

Особенности считывания символов

Пусть дано время в формате HH:MM. Считать его можно следующим образом:

int h, m;
char c;
cin >> h >> c >> m;

То есть сначала считывается число, затем — символ, затем — число.

При этом при считывании символа пропускаются пробельные символы (пробелы и концы строк) и считывается следующий непробельный символ. Например, если при запуске приведенного выше кода ввести «12 34», то переменная h станет равна 12, переменная с — ‘3’, а переменная m — 4.

Если установить манипулятор noskipws :

то при считывании символов пробельные символы не будут пропускаться. Для восстановления режима пропуска пробельных символов используется манипулятор skipws :

Особенности считывания строк

Считывание строк при помощи cin >> s , где s — объект класса string или C-строка приведет к считыванию строки из непробельных символов, при этом пробельные символы пропускаются.Использовать манипулятор noskipws при чтении строк не следует.

Строку целиком до конца строки, вместе с пробельными символами можно считать следующим образом:

Отметим следующую особенность такого считывания. Пусть входные данные заданы так — сначала число строк n, затем — n строк, возможно, содержащие пробелы. Требуется считать данные строки и сохранить их в массиве строк.

Правильное решение будет таким:

int n;
string s, A[100];
cin >> n;
getline(cin, s);
for (int i = 0; i < n; ++i)
getline(cin, A[i]);

Здесь нужно обратить внимание на «лишний» вызов getline после считывания числа n. Дело в том, что cin >> n не считывает конец строки из потока, поэтому последующий вызов getline вернет пустую строку (но зато считает конец строки), поэтому нужно сделать один «холостой» вызов getline перед считыванием всех остальных строк.

Значение, возвращаемое при считывании

Для проверки успешности считывания можно преобразовать значение, возвращаемое операцией считывания к типу bool . Например, следующим образом:

bool is_correct;
int a;
is_correct = (cin >> a);

Если значение переменной is_correct будет true , то считывание было успешно, иначе — нет.

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

int h, m;
char c;
if (cin >> noskipws >> h >> c >> m)
if (c == ‘:’) .
>

Похожее использование — чтение текста «по словам», с обработкой каждого слова:

string word;
while (cin >> word)
// Обработать слово в переменной word
>

Или считывание текста по строкам:

string line;
while (getline(cin, s))
// Обработать строку в переменной line
>

Функция scanf

Для считывания данных в языке C используется функция scanf. Ее использование похоже на функцию prinf — сначала задается форматная строка, потом передаются переменные, в которые необходимо записать результат. Например, для считывания двух целых чисел функция вызывается так:

Основное отличие функции scanf в том, что при считывании чисел (или значений типа char) ей необходимо передавать адреса переменных (в языке C все параметры передаются по значению, поэтому чтобы функция scanf могла модифицировать переменную, необходимо передать в функцию адрес этой переменной). Поэтому перед названиями переменных мы пишем знак амперсанда («&»).

В функции scanf могут быть явно записаны какие-то символы, кроме форматных строк. Например, вызов

можно использовать для считывания времени, заданного в виде hh:mm — функция считает число, затем символ двоеточия, затем опять число.

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

Особенности считывания чисел

Функция scanf корректно считывает целые числа, если они начинаются с символа 0, или со знака «+». То есть числа «+123» или «0123» будут корректно считаны по форматной строке «%d», никаких дополнительных параметров задавать не нужно.

Также при считывании чисел игнорируются пробелы перед числом. Это означает, что вызов

scanf(«%d:%d», &a, &b) сможет корректно считать время, заданное в формате hh:mm при наличии пробела после двоеточия (такая запись успешно считает строки «12:34», «01:02», «01:␣23» или «␣01:␣23», поскольку дается указание считать число, затем сразу же двоеточие, затем — число, перед которым могут быть пробелы). Но такая запись не считает выражение, например, вида «01␣:␣23», поскольку после первого числа сразу должно идти двоеточие.

Чтобы считать записать вида «01␣:␣23» можно использовать форматную строку «%d :%d», причем пробел в форматной строке может означать и отсутствие пробелов.

Возможные форматные символы

Возможные форматные символы функции scanf для разных типов данных в целом соответствуют форматным символам для функции printf , но имеют меньше различных модификаторов.

Форматная
строка
Соответствующий ей тип
%hhd Считать число (десятичное) и записать его в переменную типа char (для unsigned char нужно использовать %hhu)
%hd short int (для unsigned short int нужно использовать %hu)
%d int (для unsigned int нужно использовать %u)
%ld long int (для unsigned long int нужно использовать %lu)
%lld long long int (для unsigned long long int нужно использовать %llu)
%f float
%lf double
%Lf long double
char. Считывается один символ, он может быть пробелом или символом конца строки.
%s Считывается последовательность непробельных символов (строка), записывается в C-строку (типа char * или char[])

Особенность считывание символов

Считывание одного символа «%c» считывает из потока ввода следующий символ, он может быть в том числе и пробельным символом, а также символом конца строки. Но если в форматной строке перед «%c» поставить пробел, то поскольку пробел в форматной строке обозначает последовательность пробельных символов любой длины, то в этом случае будет считан следующий непробельный символ.

Особенность считывания строк

При считывании строки результат записывается в С-строку, которая представляет собой массив символов (или указатель типа char * с выделенной памятью). Поскольку строка в языке C является адресом (указателем) в памяти, где хранится начало строки символов, то передавать в функцию scanf нужно имя переменной без указания амперсанда.

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

Иногда бывает полезно считать всю строку целиком вместе с пробелами до конца строки. Для этого используется функция gets . Например:

Функция gets считается опасной и не рекомендуется для использования, так как она не контролирует количество считанных символов и не учитывает длину передаваемой строки, что может привести к записи данных за пределами строки.

Вместо нее рекомендуется использование функции fgets , у которой три параметра — строка для записи результата, размер строки и файловый поток, из которого читаются данные. Например:

fgets(s, 101, stdin);

В данном случае мы использовали stdin для чтения со стандартного ввода.

Не следует забывать, что в языке C в конец строки добавляется нулевой символ для обозначения конца строки. То есть если необходимо считать строку, в которой может быть 4 символа, то для нее нужно создать массив char[5] , и функции fgets нужно передавать число, не меньшее 5.

Возвращаемое значение

Функция scanf возвращает значение, равное числу успешно считанных и записанных в переданные параметры значений, что можно использовать для анализа входных данных.

Например, пусть вызвали scanf(«%d:%d», &a, &b) .

Тогда при вводе строки «12:34» функция scanf считает два числа, запишет их в переменные a и b и вернет значение 2. А при вводе «12 34» будет считано только одно число, поскольку после него должно идти двоеточие, то второе число считано не будет и функция scanf вернет значение 1.

Ввод и вывод символьных строк в Си

Итак, строки в языке Си. Для них не предусмотрено отдельного типа данных, как это сделано во многих других языках программирования. В языке Си строка – это массив символов. Чтобы обозначить конец строки, используется символ ‘\0’ , о котором мы говорили в прошлой части этого урока. На экране он никак не отображается, поэтому посмотреть на него не получится.

Создание и инициализация строки

Так как строка – это массив символов, то объявление и инициализация строки аналогичны подобным операциям с одномерными массивами.

Следующий код иллюстрирует различные способы инициализации строк.

char str[10]; char str1[10] = ; char str2[10] = "Hello!"; char str3[] = "Hello!";

Объявление и инициализация строк

Рис.1 Объявление и инициализация строк

В первой строке мы просто объявляем массив из десяти символов. Это даже не совсем строка, т.к. в ней отсутствует нуль-символ \0 , пока это просто набор символов.

Вторая строка. Простейший способ инициализации в лоб. Объявляем каждый символ по отдельности. Тут главное не забыть добавить нуль-символ \0 .

Третья строка – аналог второй строки. Обратите внимание на картинку. Т.к. символов в строке справа меньше, чем элементов в массиве, остальные элементы заполнятся \0 .

Четвёртая строка. Как видите, тут не задан размер. Программа его вычислит автоматически и создаст массив символов нужный длины. При этом последним будет вставлен нуль-символ \0 .

Как вывести строку

Дополним код выше до полноценной программы, которая будет выводить созданные строки на экран.

#include int main(void) < char str[10]; char str1[10] = ; char str2[10] = "Hello!"; char str3[] = "Hello!"; for(int i = 0; i

Различные способы вывода строки на экран

Рис.2 Различные способы вывода строки на экран

Как видите, есть несколько основных способов вывести строку на экран.

  • использовать функцию printf со спецификатором %s
  • использовать функцию puts
  • использовать функцию fputs , указав в качестве второго параметра стандартный поток для вывода stdout .

Единственный нюанс у функций puts и fputs . Обратите внимание, что функция puts переносит вывод на следующую строку, а функция fputs не переносит.

Как видите, с выводом всё достаточно просто.

Ввод строк

С вводом строк всё немного сложнее, чем с выводом. Простейшим способом будет являться следующее:

#include int main(void)

Функция gets приостанавливает работу программы, читает строку символов, введенных с клавиатуры, и помещает в символьный массив, имя которого передаётся функции в качестве параметра.
Завершением работы функции gets будет являться символ, соответствующий клавише ввод и записываемый в строку как нулевой символ.
Заметили опасность? Если нет, то о ней вас любезно предупредит компилятор. Дело в том, что функция gets завершает работу только тогда, когда пользователь нажимает клавишу ввод. Это чревато тем, что мы можем выйти за рамки массива, в нашем случае — если введено более 20 символов.
К слову, ранее ошибки переполнения буфера считались самым распространенным типом уязвимости. Они встречаются и сейчас, но использовать их для взлома программ стало гораздо сложнее.

Итак, что мы имеем. У нас есть задача: записать строку в массив ограниченного размера. То есть, мы должны как-то контролировать количество символов, вводимых пользователем. И тут нам на помощь приходит функция fgets :

#include int main(void)

Функция fgets принимает на вход три аргумента: переменную для записи строки, размер записываемой строки и имя потока, откуда взять данные для записи в строку, в данном случае — stdin . Как вы уже знаете из 3 урока, stdin – это стандартный поток ввода данных, обычно связанный с клавиатурой. Совсем необязательно данные должны поступать именно из потока stdin , в дальнейшем эту функцию мы также будем использовать для чтения данных из файлов.

Если в ходе выполнения этой программы мы введем строку длиннее, чем 10 символов, в массив все равно будут записаны только 9 символов с начала и символ переноса строки, fgets «обрежет» строку под необходимую длину.

Обратите внимание, функция fgets считывает не 10 символов, а 9 ! Как мы помним, в строках последний символ зарезервирован для нуль-символа.

Давайте это проверим. Запустим программу из последнего листинга. И введём строку 1234567890 . На экран выведется строка 123456789 .

Пример работы функции fgets

Рис.3 Пример работы функции fgets

Возникает вопрос. А куда делся десятый символ? А я отвечу. Он никуда не делся, он остался в потоке ввода. Выполните следующую программу.

#include int main(void)

Вот результат её работы.

Непустой буфер stdin

Рис.4 Непустой буфер stdin

Поясню произошедшее. Мы вызвали функцию fgets . Она открыла поток ввода и дождалась пока мы введём данные. Мы ввели с клавиатуры 1234567890\n ( \n я обозначаю нажатие клавиша Enter ). Это отправилось в поток ввода stdin . Функция fgets , как и полагается, взяла из потока ввода первые 9 символов 123456789 , добавила к ним нуль-символ \0 и записала это в строку str . В потоке ввода осталось ещё 0\n .

Далее мы объявляем переменную h . Выводим её значение на экран. После чего вызываем функцию scanf . Тут-то ожидается, что мы можем что-то ввести, но т.к. в потоке ввода висит 0\n , то функция scanf воспринимает это как наш ввод, и записывается 0 в переменную h . Далее мы выводим её на экран.

Это, конечно, не совсем такое поведение, которое мы ожидаем. Чтобы справиться с этой проблемой, необходимо очистить буфер ввода после того, как мы считали из него строку, введённую пользователем. Для этого используется специальная функция fflush . У неё всего один параметр – поток, который нужно очистить.

Исправим последний пример так, чтобы его работа была предсказуемой.

#include int main(void) < char str[10]; fgets(str, 10, stdin); fflush(stdin); // очищаем поток ввода puts(str); int h = 99; printf("do %d\n", h); scanf("%d",&h); printf("posle %d\n", h); return 0; >

Теперь программа будет работать так, как надо.

Сброс буфера stdin функцией fflush

Рис.4 Сброс буфера stdin функцией fflush

Подводя итог, можно отметить два факта. Первый. На данный момент использование функции gets является небезопасным, поэтому рекомендуется везде использовать функцию fgets .

Второй. Не забывайте очищать буфер ввода, если используете функцию fgets .

На этом разговор о вводе строк закончен. Идём дальше.

Сохрани в закладки или поддержи проект.

Практика

Решите предложенные задачи:

Для удобства работы сразу переходите в полноэкранный режим

Исследовательские задачи для хакеров

  1. Проверьте как ведет себя ваш компилятор в случае переполнения буфера.

Дополнительные материалы

  1. пока нет

Чтение строк

Функция scanf() может использоваться для чтения строк из потока ввода, для чего надо указать спецификатор формата %s. %s заставляет функцию scanf() читать символы, пока не встретится специальный символ. Прочитанные символы помещаются в массив символов, на который указывает соответствующий аргумент, и результат завершается нулевым символом. Применительно к scanf() специальные символы — это пробел, новая строка, табуляция, вертикальная табуляция или перевод формата. В противоположность gets(), которая читает строку, пока не встретится возврат каретки, функция scanf() читает строку до первого специального символа. Это означает, что нельзя использовать scanf() для чтения строки типа «this is a test», поскольку первый пробел завершит процесс ввода. Для изучения эффекта воздействия спецификатора %s опробуем данную программу, введя строку «hello there»:

#include
int main(void)
char str[80];
printf(«Enter a string: «);
scanf(«%s», str);
printf («Here’s your string: %s», str);
return 0;
>

Программа выдаст только часть «hello» строки.

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

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