Главная > Уроки > Ввод и вывод символьных строк в Си

Записывайся на этот курс на Stepike!

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

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

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

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

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

Листинг 1.

  char str[10];
  char str1[10] = {'Y','o','n','g','C','o','d','e','r','\0'};
  char str2[10] = "Hello!";
  char str3[] = "Hello!";
Объявление и инициализация строк

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

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

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

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

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

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

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

Листинг 2.

#include <stdio.h>

int main(void) {
  
  char str[10];
  char str1[10] = {'Y','o','n','g','C','o','d','e','r','\0'};
  char str2[10] = "Hello!";
  char str3[] = "Hello!";

  for(int i = 0; i < 10; i = i + 1)
    printf("%c\t",str[i]);
  printf("\n");

  puts(str1);
  printf("%s\n",str2);
  puts(str3);

  return 0;
}

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

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

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

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

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

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

Ввод строк

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

Листинг 3.

#include <stdio.h>
int main(void) {
  char str[20];
  gets(str); 
  puts(str);
  return 0;
}

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

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

Листинг 4.

#include <stdio.h>
int main(void) {
  char str[10];
  fgets(str, 10, stdin);
  puts(str);
  return 0;
}

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

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

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

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

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

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

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

Листинг 5.

#include <stdio.h>
int main(void) {
  char str[10];
  fgets(str, 10, stdin);
  puts(str);

  int h = 99;

  printf("do %d\n", h);
  scanf("%d",&h);
  printf("posle %d\n", h);

  return 0;
}

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

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

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

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

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

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

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

Листинг 6.

#include <stdio.h>
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. пока нет

Оставить комментарий

Чтобы код красиво отображался на странице заключайте его в теги [code] здесь писать код [/code]

Комментарии

Сергей
в листинге 5 не инициализирована переменная h (h=99), иначе на выводе смотрится значение 99 как мусор, который был в памяти. И соответственно рисунок 4 показывает нам работу программы без изменений... Но смысл понятен.
KaDeaT
Рисунок 4 неправильный) Поправил его, спасибо.
Макар
В листинге 2 нет fputs, а на рисунке 2 есть её выполнение.
Максим Н
Мне кажется, что на рис.1 в str: не может быть чисел 9932 и 33, у ASCII таблицы есть же только цифры от 0 до 9. И в одной ячейке может быть только символ, соответствующий одному из 256 адресов таблицы. Или я что-то не так понял?
KaDeaT
Отличное замечание! Вы всё верно поняли. Не подумал об этом, когда готовил рисунок. Большое спасибо за внимательность!

Оставлю картинку без изменений и добавлю это в задание для хакеров.)
Легионер
У меня листинг 5 не работает "так как надо", не чистится буфер ввода.
Легионер
fflush(stdin) работает только в Windows
cssfish
в главе "вывод строк" описан fputs а в коде примера его нет
cssfish
главное, что стоит добавить про fgets:
Если ввели на 2 символа меньше указанной длины (или еще короче!), то он запишет ВВОД ('n', 10 в ascii) в строку! Т.е. она становится уже не на один, а на ДВА символа длиннее! (т.е. если в fgets(str, 5, stdin); ввели "hi" и нажали `ввод` - получаем строку - {'h','i','n','',''})

Это сэкономит многие часы людям, уходящим на практику по данной теме.
cssfish
блин парсер съел слэши у перевода строки и нулевого символа :(
если записать итог в ascii, то будет так :
{'h','i', 10 , 0, 0})
SanSanich
В Листинг 2. видимо в конце предполагалось вместо
puts(str3);
поставить
fputs(str3,stdout);
Именно тогда у меня получился тот же вывод на экран, как в Рис.2
Рустам
Проверил в 2х компиляторах код
#include <stdio.h>
int main(void) {
char str[10];
fgets(str, 10, stdin);
fflush(stdin); // очищаем поток ввода
puts(str);

int h = 99;
printf("do %dn", h);
scanf("%d", &h);
printf("posle %dn", h);

return 0;
}

fflush(stdin); - не очищает буфер и следовательно сканф не работает, что не так?
Арсений
"Если в ходе выполнения этой программы мы введем строку длиннее, чем 10 символов, в массив все равно будут записаны только 9 символов с начала и символ переноса строки". и нуль-символ ведь?
JustFlow
Как видите, есть несколько основных способов вывести строку на экран.

использовать функцию printf со спецификатором %s
Мне кажется или тут ошибка? а правильный спецификатор это %с

KaDeaT
с -- для вывода одного символа,
s -- для вывода строки символов.
Александр
Материал по "Ввод и вывод символьных строк в Си" выдан слабо, не рассказано про действия по поиску символов в таблице ASCII. Не указано про такие преобразования как n = s & 0x0F; Вместо того, чтобы пользоваться курсом, приходится искать варианты исполнения и методы возможных решений в других местах и курсах. Когда программа верно исполняет требования задачи, а автопроверка возвращает ее как неправильную без каких либо подсказок это ужас, стоит тонны нервов, угадайка какая-то а не блок заданий. Не удивительно, что блок решенийв с одними из самыми низкими результатами за курс. Рекомендую давать больше "подводящих" упражнений и обеспечить задачи большим количеством подсказок по методике проверки.
Makalser
В листинге 2 не увидел функции fputs, хотя описание под листингом утверждает, что она там есть.
Гоголь
Очередная криво написанная теория. Авторы, вы хоть обращайте внимание на то,Ю что люди пишут. Устраняйте явные коряки. Про отсутствие fputs только ленивый наверное не написал(((
Эк0с
ъУЕ8ВШл
ЛХЮлЛЖПв4Р8Нб
фыва
ХАХАХАХАХАХ НЕГРЫ
Володяев
Теория по этой теме написана с ошибками так например в начале в Листинг 2 приведен код программы а на рис.2 результат ее работы, но он не соответствует выводу этой программы.
Во вторых далее в комментариях по этому коду написано "Как видите, есть несколько основных способов вывести строку на экран." и далее "использовать функцию fputs" но функции fputs в коде нет.