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

от admin

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

При запуске программы на Си автоматически открываются ряд потоков, основными из которых являются следующие:

Стандартный поток ввода stdin

Стандартный поток вывода stdout

Стандартный поток вывода сообщений об ошибках stderr

Стандартный поток ввода stdin по умолчанию соответствует клавиатуре, а потоки stdout и stderr — экрану монитора.

Для управления вводом-выводом с помощью этих потоков используются ряд функций:

getchar() : ввод с клавиатуры одного символа

putchar() : вывод на консоль одного символа

fgets() : ввод одной строки

puts() / fputs() : вывод одной строки на консоль

scanf() : ввод с консоли с форматированием данных

sscanf() : ввод с из строки с форматированием данных

printf() : вывод с форматированием данных

Функции printf и scanf уже рассматривались ранее, поэтому посмотрим, как применять остальные функции.

Ввод и вывод символов

Для ввода и вывода символа применяются функции getchar() и putchar() . Но следует сказать, что на самом деле они полноценными функциями не являются, а определены как макросы в заголовочном файле stdio.h :

Вывод символа

Для вывода отдельного символа на консоль предназначена функция putchar() со следующим прототипом:

Выводимый символ в виде числового кода передается в putchar в качестве параметра, он же возвращается функцией.

Ввод символа

Для ввода одного символа с клавиатуры применяется функция getchar() , которая имеет следующий прототип:

В качестве результата функция возвращает числовой код введенного символа.

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

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

При этом при нажатии клавиши Enter, в буфер также помещается код этой клавиши. То есть если мы введем один символ и нажмем на Enter, в буфере окажутся два числовых кода — введенного символа и клавиши Enter. И это надо учитывать при работе с функцией getchar. В частости, рассмотрим простой, но показательный пример:

Сначала на экран выводится цифра 1, после чего функция getchar ожидает ввода символа. Если после ввода символа мы нажмем Enter, то в буфер будет помещены два числовых кода — введеного символа и клавиши Enter. Поэтому при втором вызове getchar эта функция считывает байт из буфера — то есть числовой код клавиши Enter.

Например, введем при первом вызове функции getchar символ «a», а затем Enter:

Но если при каждом вызове getchar мы будем только нажимать клавишу Enter, тогда в буфер будет заноситься только код этой клавиши, и соответственно программа будет работать, как и ожидалось:

Применим функции getchar и putchar для ввода и вывода символов с клавиатуры:

Функция getchar() считывает числовой код символа, который потом выводится в функции putchar() . Для вывода из программы необходимо ввести комбинацию клавиш Ctrl+C.

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

Вывод строк и puts

Для вывода одной строки на консоль предназначена функция puts() со следующим прототипом:

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

При этом функция puts() будет выводить символы переданной строки, пока не дойдет до нулевого символа ‘\0’. Если же выводимый массив символов не содержит этого символа, то результат программы неопределен. Например:

Вывод строк и fputs

Функция fputs() также записывает в поток вывода строку, то есть набор символов, который завершается символом ‘\0’. При записи строки нулевой символ ‘\0’ не записывается. Она имеет следующий прототип:

Первый параметр функции — записываемая строка, а второй — указатель на поток вывода. В качестве результата функция возвращает неотрицательное целое число. При ошибках в процессе записи возвращается значение EOF .

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

Ввод строк и fgets

Для ввода строки с клавиатуры применяется функция fgets() , которая имеет следующий прототип:

char *str : строка, в которую производится считывание.

int count : сколько символов необходимо считывать.

FILE *stream : файловый поток, из которого производится считывание. В качестве потока ввода может выступать и консольный ввод.

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

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

Здесь функция fgets считывает не более 15 символов в строку name, а В реальности функция предложит ввести 14 символов, так как последний символ зарезервирован для нулевого символа ‘\0’. Если будет введено больше символов, то fgets все равно считает не более 15 символов. Таким образом функция позволяет проверить количество считываемых символов и поэтому считается безопасной.

Пример работы программы:

Стоит отметить, что функция fgets() возвращает указатель char * — указатель на буфер, в который считаны данные. В реальности это тот же самый буфер, который передавался в качестве первого параметра, то есть в примере выше — это массив name. Однако этот результат может нам пригодится для проверки успешности выполнения функции — если считывание прошло неудачно, то функция возвращает NULL :

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ввод строк

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Практика

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

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

Name already in use

hse-caos-2020 / 04-arrays / README.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink
  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Массивы, указатели, строки

Массив в Си/Си++ — это непрерывная область памяти, в которой подряд хранятся элементы одного типа.

Размер массива равен произведению размера одного элемента на количество элементов. То есть sizeof(arr1) = 16 * sizeof(int) .

Количество элементов в определении массива можно не указывать, если его можно определить из инициализатора массива.

Массив не может содержать отрицательное число элементов, но массивы с нулем элементов допустимы. Их размер равен 0, и они полезны в некоторых ситуациях.

Массив нельзя возвращать из функции. Если массив передается в качестве параметра в функцию, то фактически будет передаваться указатель на начало массива. При передаче массива в качестве параметра он не копируется.

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

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

Массив индексируется целым числом. Начальный элемент массива имеет индекс 0, последний элемент массива имеет индекс count — 1 , где count число элементов массива. Обращение к элементам за пределами массива ненулевого размера (отрицательными или >= count) — undefined behavior.

У любого указательного типа есть специальное значение NULL . Везде, где требуется указательный тип, компилятор умеет преобразовывать целое число 0 ко значению NULL . Везде, где требуется булевское значение (например, в условиях if ) значение NULL считается ложным значением, а любое другое значение — истинным.

Однако, не гарантируется, что битовое представление указателя NULL содержит все нулевые биты, например, NULL не равный всем нулевым битам возможен на процессорах с несколькими адресными пространствами. Тем не менее, на всех типичных микропроцессорных архитектурах (x86/x64, ARM, PPC, MIPS, SPARC) указатель NULL содержит все нулевые биты. Обращение по указателю NULL — undefined behavior. Поэтому компилятор может предполагать, что если некоторая указательная переменная была разыменована, она не равна NULL .

Пусть дан массив некоторого типа T с количеством элементов C :

Арифметические операции над указателями определены в терминах операций над индексами массива.

Само по себе имя массива arr , использованное в выражении, неявно преобразовывается в указатель на начальный элемент массива.

Если указатель p указывает на элемент с индексом i ( p = &arr[i] ), то p + j (прибавление целого числа к указателю) — это то же самое, что &arr[i + j] . Аналогично p — j (вычитание целого числа из указателя) — это &arr[i — j] . Если результирующий индекс i + j или i — j окажется отрицательным или большим C , то результат — undefined behavior. Однако указатель на элемент, непосредственно следующий за последним элементом массива ( &arr[C] ) допустим, хотя его нельзя разыменовывать. Эти правила применимы также и к операциям += , -= , ++ , — над указателями.

Если указатель p указывает на элемент с индексом i , а указатель q — на элемент с индексом j в том же самом массиве, то

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

Операция обращения по индексу p[i] применима к массивам и указателям и обозначает по определению *(p + i) . Если p указывает в середину массива, то вполне допустимы и отрицательные индексы, при условии, что не происходит выхода за пределы массива.

Представление строк в Си

Строки в Си (не путать с Си++) очень часто представляются в формате простого массива char* , последнее значение которого равно ‘\0’ .

Такие строки размещаются в памяти как непрерывный массив байт. Символ с кодом 0 может встречаться в середине этого массива. В таком случае функции, которые предназначены для работы со строками будут считать этот символ концом строки, несмотря на то, что размер массива может быть больше, и данные после ‘\0’ легко доступны.

Указатель на символ ( char * ) определяет адрес первого символа в строке. Все остальные символы могут быть получены инкрементом (эквивалентно — оператором [] ) этого указателя на заданное смещение.

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

В данном примере исходная строка не изменяется, а на экран выводятся ее подстроки, получаемые сдвигом указателя на один символ вперед. Обратите внимание, что переменная цикла объявлена как const char* . В данном случае модификатор const относится не к переменной substring , значение которой изменяется, а к типу данных char* , то есть нельзя изменять содержимое, на которое ссылается указатель.

Если требуется объявить неизменяемость самого указателя, необходимо писать const после символа * .

Если определяется массив элементов char , который инициализируется строковым литералом, то указанная строка будет размещена в массиве.

Размер массива str будет равен 6 (5 значащих символов + \0 -терминатор строки). В его элементах последовательно будут размещены символы строки. Элементы массива могут быть модифицированы.

Можно размещать строку в константном массиве.

В этом случае простые попытки модификации строки будут диагностированы и запрещены компилятором. Массив str вполне возможно будет размещен в памяти, закрытой на запись, и попытка обойти ограничение компилятора приведет к падению программы.

В этих двух случаях понятно, в какой области памяти будет размещена строка. Но рассмотрим следующий пример:

Локальная переменная p — это указатель, она содержит адрес начала строки, но где размещается сама строка? Этот фрагмент эквивалентен примерно следующему:

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

Если программа обрабатывает строковые данные, то вряд ли в ней можно обойтись только константными строками. Программа должна считать строковые данные откуда-то извне, их обработать, затем записать результат. Мы будем предполагать, что обрабатываются текстовые файлы, то есть файлы, не содержащие байт \0 , поэтому для работы с такими файлами можно использовать функции стандартной библиотеки языка Си.

Считанная строка должна быть размещена в памяти. Это может быть глобальный массив, локальный массив или область памяти, выделенная динамически. В любом случае это будет область памяти ограниченного размера. Назовем такую область памяти, предназначенную для хранения строки, буфером строки. Буфер строки имеет два параметра: указатель на начало буфера и размер буфера. Если размер буфера равен N, то в буфере не может размещаться строка, длина которой больше N — 1, так как один байт займет символ \0 терминатор строки.

Таким образом, мы будем различать следующие понятия: строка — это цепочка байт, заканчивающаяся байтом \0. Буфер — это область памяти, отведенная для хранения строки.

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

Иногда возможно обработать строку «на месте». То есть функция принимает указатель на начало строки и модифицирует ее в соответствии с требованиями. В этом случае неявно предполагается что размер буфера обрабатываемой строки равен длине строки + 1. В таком случае нормально, если строка уменьшится в размере, но недопустимо увеличение ее размера.

Например, рассмотрим функцию, которая преобразовывает все символы во входной строке к нижнему регистру:

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

Более универсален вариант, когда в качестве результата работы функции возвращается строка, буфер под которую выделен в динамической памяти с помощью функций malloc или realloc . Например, рассмотрим функцию, выполняющую конкатенацию двух входных строк:

Функция предполагает, что аргументы str1 и str2 не могут быть указателями NULL. В аргументах используется ключевое слово const , чтобы показать, что строки не модифицируются функцией concatenate .

Функция malloc выделяет заданный объем памяти в куче и возвращает указатель на начало. Если malloc не смогла выделить память, возвращается NULL . Выделенная память используется как буфер для хранения строки.

Функция memcpy(dst,src,size) копирует size байт с места в памяти, на которое указывает src на место, на которое указывает dst . Буфера в памяти не должны перекрываться.

Такая функция concatenate всем хороша. Важно не забыть освободить выделенную память с помощью функции free . Кроме того, динамическое выделение и особождение памяти — операция сложная и займет существенное время, если обрабатывается много коротких строк.

Часто используется еще один вариант возврата обработанной строки. В этом случае функция, обрабатывающая строку, принимает на вход два параметра буфера: адрес начала и размер. Функция записывает в буфер строку-результат. Если строка окажется длиннее, чем размер буфера — 1, она обрезается. В любом случае в выходной буфер дописывается \0 байт терминатор строки. Функция может каким-либо образом сигнализировать о том, что буфер переполнился, например, с помощью возвращаемого значения.

Эта гипотетическая функция может возвращать суммарную длину строк str1 и str2 . В буфер buf записывается конкатенация двух строк, но не более чем size — 1 символ.

Если функция принимает только один параметр для записи строки-результата — адрес буфера, при этом невозможно контролировать длину строки, которая будет записана в этот буфер, такую функцию использовать крайне опасно (по меньшей мере). Например, функция gets , которая считывает одну строку из стандартного потока ввода и сохраняет ее по указанному адресу, безусловно запрещена к использованию.

Для чтения одной строки из текстового файла используется функция fgets .

Функция возвращает NULL при ошибке чтения или достижении конца файла. В противном случае функция возвращает указатель buf .

Функция считывает одну строку текста из файла fin . Строка текста заканчивается либо символом ‘\n’, либо концом файла, либо исчерпанием места в буфере buf . Если символ \n считан, он помещается в буфер. В любом случае считывается не более чем size — 1 символ из входного файла.

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

У функций семейства scanf есть форматное преобразование %s . Оно требует указатель на буфер строки.

При чтении строки предварительно пропускаются все пробельные символы, чтение в строку ведется либо до конца файла, либо до первого пробельного символа. Использование преобразование %s в этом виде не позволяет контролировать число считанных символов, поэтому использование %s в таком виде недопустимо. Необходимо указать максимальное число считываемых символов следующим образом:

Строку можно вывести с помощью спецификации %s функции семейства printf . Например,

Если выводится только строка, можно использовать fputs

Запрещено выводить строку с помощью printf без явного указания формата

Форматное преобразование в строку

Для форматного вывода данных в строку предназначена функция snprintf .

buf и size задают буфер для формирования строки. snprintf записывает в буфер не более чем size — 1 символ, всегда записывая \0 в конце. В любом случае функция snprintf возвращает столько символов, сколько было бы записано в выходной поток, если бы размер буфера buf был неограниченным.

Пример использования функции snprintf .

Форматное чтение из строки

Для форматного преобразования из строки может использоваться функция sscanf .

В случае чтения из строки удобно использовать форматное преобразование %n .

В случае успешного преобразования строки в число sscanf вернет 1, а в переменную n будет записано число символов, считанных из строки при преобразовании в число. После этого p + n — это позиция в строке, на которой остановилось чтение из файла.

Контролируемое преобразование из строки в число

Часто требуется преобразовать строку в число целого или вещественного типа, при этом проверить корректность записи числа. При этом требуется, чтобы при чтении числа не возникало переполнение, в хвосте числа не находится «мусор». Следущие записи считаются недопустимыми: «» (пустая строка), » » (только пробельные символы в строке), «1231a» (мусор в конце строки), «1 » (пробел в конце строки), «11111111111111111111111111111111111111111111» (число не представимо значением целого типа). Следущие записи допустимы: «12», » -12″ (пробелы перед числом допускаются).

Для такого рода преобразований используются функции strtol (чтение в long), strtoll (чтение в long long), strtoul (чтение в unsigned long), strtoull (чтение в unsigned long long), strtod (чтение в double). Они определены в заголовочном файле <stdlib.h> .

Рассмотрим использование strtol .

Функция возвращает преобразованное значение. При этом если возникло переполнение, в переменную errno записывается код ошибки ERANGE .

В переменную, адрес которой передан вторым параметром, записывается указатель на первый символ, который не является частю считанного числа, то есть либо адрес нулевого байта-терминатора, либо адрес начала «мусора» после числа. Если в строке вообще нет цифр, сохраняется адрес строки str.

Параметр radix задает систему счисления для перевода. Он может принимать значения от 2 до 36. Если radix равен 0, функция пытается определить систему счисления по правилам записи целых чисел языка Си (по префиксу числа: 0x — шестнадцатеричное, 0 — восьмеричное, иначе десятичное).

Поэтому проверка корректности чтения с помощью strtol заключается в следующем:

  • проверить, что строка не пуста
  • проверить, что переменная errno не установлена
  • проверить, что eptr указывает на нулевой байт

Инициализация выделенной памяти

В заголовочном файле <string.h> объявлена одна очень полезная функция:

void *memset(void *s, int c, size_t n) — заполняет массив из n байт, который находится по адресу s однобайтными значениями c (от 0 до 0xFF ). Возвращает указатель s .

Тип параметра int c сохраняется по историческим причинам, на самом деле переданное значение преобразуется к unsigned char .

Эту функцию можно (и нужно) вызывать после выделения памяти для массива или буфера, иначе с большой вероятностью, выделенная память будет содержать какие-то случайные значения.

Динамическое выделение памяти

Функции динамического выделения и освобождения памяти определены в заголовочном файле <stdlib.h> .

void* malloc(size_t n_bytes) — выделяет память для хранения n_bytes байт, возвращает нетипизированный указатель на выделенную область

void free(void *pointer) — освобождает память, выделенную ранее функцией malloc

Функция realloc позволяет изменить размер уже выделенного блока:

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

Функция realloc является основой для реализации расширяемых массивов (в C++ им соответствует шаблонный класс vector ).

В расширяемом массиве для каждого массива хранятся три переменных: data — указатель на массив, size — число элементов, хранящихся в массиве, reserved — число элементов, под которые выделена память.

Начальный размер расширяемого массива может быть нулевым, а может быть равным, например, 32 байта.

Если при добавлении элемента в массив места в нем не осталось, память под него перевыделяется:

Обработка ошибок выделения памяти

Функции выделения памяти calloc , realloc и прочие могут возвращать NULL , если операция выделения памяти закончилась с ошибкой. Разыменование нулевого указателя при выполнении программы — это undefined behavior, что недопустимо. Поэтому результат, который вернули функции выделения памяти, должен проверяться на NULL, и ошибка выделения памяти должна как-то обрабатываться.

Однако ответить на вопрос Как нужно обрабатывать ошибки выделения памяти? сложнее. Самый простой вариант — вернуть наверх, в вызывающую функцию какое-то значение, сигнализирующее об ошибке, например, тот же NULL , переложив ответственность на обработку ошибки выделения памяти на вызывающую функцию.

Фрагмент функции добавляющий элемент в расширяемый массив, может быть тогда таким:

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

Тогда код можно переписать следующим образом:

Смысл использования функции abort() здесь в том, чтобы программа завершилась аварийно, и мы сможем, например, исследовать посмертный дамп памяти, или получим трассу стека в системных логах.

Текстовыми мы будем называть файлы, удовлетворяющие следующим ограничениям:

  • не содержит байт со значением 127;
  • из значений байтов в диапазоне 0-31 допускаются только символы \t, \r, \n; в частности, не допускается байт 0;
  • файл состоит из строк текста. Каждая строка текста заканчивается либо байтом \n, либо байтами \r \n;
  • последняя строка в файле может не заканчиваться байтами конца строки.

Пустой текстовый файл допускается и считается, что содержит 0 строк текста. Если текстовый файл содержит единственную строку, не завершающуюся байтами конца строки, он содержит одну строку текста.

Ваша программа при чтении должна корректно обрабатывать файлы и с концами строк в стиле DOS, и с концами строк в стиле Unix.

Когда ваша программа формирует в качестве результата работы текстовые файлы, каждая строка, в том числе и последняя, должна завершаться единственным символов конца строки \n. Использование \r в генерируемых файлах не допускается.

Стандартные потоки stdin , stdout , stderr по умолчанию являются текстовыми потоками и должны подчиняться описанным выше правилам.

Функции обработки строк в Cи

В программе строки могут определяться следующим образом:

  • как строковые константы;
  • как массивы символов;
  • через указатель на символьный тип;
  • как массивы строк.

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

Любая последовательность символов, заключенная в двойные кавычки «» , рассматривается как строковая константа .

Для корректного вывода любая строка должна заканчиваться нуль-символом '\0' , целочисленное значение которого равно 0. При объявлении строковой константы нуль-символ добавляется к ней автоматически. Так, последовательность символов, представляющая собой строковую константу, будет размещена в оперативной памяти компьютера, включая нулевой байт.

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

Для помещения в строковую константу некоторых служебных символов используются символьные комбинации. Так, если необходимо включить в строку символ двойной кавычки, ему должен предшествовать символ «обратный слеш»: ‘\»‘ .

Строковые константы размещаются в статической памяти. Начальный адрес последовательности символов в двойных кавычках трактуется как адрес строки. Строковые константы часто используются для осуществления диалога с пользователем в таких функциях, как printf() .

При определении массива символов необходимо сообщить компилятору требуемый размер памяти.

Компилятор также может самостоятельно определить размер массива символов, если инициализация массива задана при объявлении строковой константой:

В этом случае имена m2 и m3 являются указателями на первые элементы массивов:

  • m2 эквивалентно &m2[0]
  • m2[0] эквивалентно ‘Г’
  • m2[1] эквивалентно ‘o’
  • m3 эквивалентно &m3[0]
  • m3[2] эквивалентно ‘x’

При объявлении массива символов и инициализации его строковой константой можно явно указать размер массива, но указанный размер массива должен быть больше, чем размер инициализирующей строковой константы:

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

В этом случае объявление массива переменной m4 может быть присвоен адрес массива:

Здесь m3 является константой-указателем. Нельзя изменить m3 , так как это означало бы изменение положения (адреса) массива в памяти, в отличие от m4 .

Для указателя можно использовать операцию увеличения (перемещения на следующий символ):

Массивы символьных строк

Иногда в программах возникает необходимость описание массива символьных строк . В этом случае можно использовать индекс строки для доступа к нескольким разным строкам.

В этом случае poet является массивом, состоящим из четырех указателей на символьные строки. Каждая строка символов представляет собой символьный массив, поэтому имеется четыре указателя на массивы. Указатель poet[0] ссылается на первую строку:
*poet[0] эквивалентно 'П',
*poet[l] эквивалентно '-'.

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

Разница заключается в том, что такая форма задает «прямоугольный» массив, в котором все строки имеют одинаковую длину.

Массив строк

Свободный массив

Операции со строками

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

  • выделить блок оперативной памяти под массив;
  • проинициализировать строку.

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

Для ввода строки использована функция scanf() , причем введенная строка не может превышать 9 символов. Последний символ будет содержать '\0' .

Функции ввода строк

Для ввода строки может использоваться функция scanf() . Однако функция scanf() предназначена скорее для получения слова, а не строки. Если применять формат "%s" для ввода, строка вводится до (но не включая) следующего пустого символа, которым может быть пробел, табуляция или перевод строки.

Для ввода строки, включая пробелы, используется функция

В качестве аргумента функции передается указатель на строку, в которую осуществляется ввод. Функция просит пользователя ввести строку, которую она помещает в массив, пока пользователь не нажмет Enter.

Функции вывода строк

Для вывода строк можно воспользоваться рассмотренной ранее функцией

или в сокращенном формате

Для вывода строк также может использоваться функция

которая печатает строку s и переводит курсор на новую строку (в отличие от printf() ). Функция puts() также может использоваться для вывода строковых констант, заключенных в кавычки.

Функция ввода символов

Для ввода символов может использоваться функция

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

Функция вывода символов

Для вывода символов может использоваться функция

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

Пример Посчитать количество введенных символов во введенной строке.

Результат выполнения
Количество введенных символов в строке

Читать:
V4dirs что это за папка

Похожие статьи