Sscanf форматный вывод из буфера


Содержание

Форматированный вывод. Функция printf

Пожалуйста, приостановите работу AdBlock на этом сайте.

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

Общий синтаксис функции printf следующий:

Рис.1. Общий синтаксис функции printf.

У функции printf есть один обязательный параметр – строка, заключенная в двойные кавычки. Эту строку еще называют формат-строкой .

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

Кроме обязательной строки форматирования есть и необязательные параметры. Они пишутся через запятую после формат-строки.

Формат-строка.

Любой символ в формат-строке относится к одной следующих групп:

  • символы, которые выводятся на экран без изменений
  • escape-последовательности
  • спецификаторы формата

Еscape-последовательности

С этой группой символов мы уже встречались в первом уроке. Символ \n . Он, как вы наверное помните, переносит выводимый текст на новую строку. Есть и другие эскейп-последовательности (иногда можно встретить название управляющие последовательности). Любая такая последовательность начинается с символа обратный слеш \ .

Часто используемые escape-последовательности:

\n — новая строка
\t — горизонтальная табуляция. Сдвигает выводимые данные вправо на ближайшую позицию табуляции. Обычно используется для выравнивания текста внутри строки.
\’ — вывод символа ‘
— вывод символа »
\\ — вывод символа \
\? — вывода символа ?

Как видите, последние четыре последовательности нужны лишь для того, чтобы вывести на экран символы «»», «’», «\» и «?». Дело в том, что если эти символы просто записать в формат-строку, то они не отобразятся на экране, а в некоторых случаях программа и вовсе не скомпилируется.

Следующая программа иллюстрирует работу escape-последовательностей.

Хотя escape-последовательности состоят из нескольких символов, но в потоке вывода они воспринимаются как цельный символ, который имеет своё собственное значение.

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

Спецификаторы формата.

Спецификаторы формата всегда начинаются с символа % , и предназначены для вывода на экран значений переменных и выражений.

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

Основные спецификаторы формата:

%d, %i — целые числа
%f, %g — вещественные числа
%c — символы

Есть и другие спецификаторы формата. Мы познакомимся с ними тогда, когда они нам понадобятся.

Сами спецификаторы формата на экран не выводятся. Вместо них выводятся данные, которые передаются в функцию printf после строки форматирования.

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

Посмотрим на примерах.

Рис.2 Вывод Листинг 2.

Рис.3 Вывод Листинг 3.

Рис.4 Вывод Листинг 4.

Рис.5 Вывод Листинг 5.

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

Рис.6 Принцип работы функции printf.

По сути, формат строка задаёт некоторый трафарет(шаблон), в который подставляются данные для вывода, в том порядке, в котором они указаны.

Два основных правила, которые нужно соблюдать при работе с функцией printf :

  • количество спецификаторов формата должно совпадать с количеством данных для вывода
  • спецификаторы формата должны точно соответствовать типам выводимых данных

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

Напишем небольшую программу, которая иллюстрирует использование спецификаторов формата.

Модификаторы формата.

Обратите внимание, что вещественные числа через спецификатор %f выведены с шестью знаками после запятой. У вас на компьютере может быть выведено как больше, так и меньше знаков. Обычно такая точность не нужна. К счастью, этим можно управлять. Для этого предназначены модификаторы формата .

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

Рис.7 Модификатор формата

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

В примере на картинке под вещественное число мы выделяем 8 символов и хотим видеть 3 знака после запятой.

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

У первого числа может спереди еще стоять знак минус, например %-8.3f . Этот минус говорит о том, что необходимо выровнять число по левому краю используемой области.

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

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

Рис.8 Вывод Листинг 8.

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

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

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

Практика

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

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

  1. Теперь вы можете увидеть, как выглядит мусор, который хранится в переменной после её объявления. Для этого объявите переменную любого типа и выведете её значение на экран использую функцию printf .
  2. Найдите в стандарте языка, в справочной системе компилятора полный список escape-последовательностей. Попробуйте использовать их в вашей программе. Разберитесь как они работают.
  3. Выясните, что произойдет, если в формат-строку функции printf вставить \c , где c — любой символ, не входящий в список escape-последовательностей. [K&R]
  4. Разберитесь с тем, как работает модификатор точности для целых чисел.
  5. Попробуйте вывести символы «»», «’», «\» и «?» без использования escape-последовательностей.
  6. Изучите как работает спецификатор %e

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

  1. Ранее я говорил, что тип double называется типом двойной точности, и что он в некотором смысле лучше, чем тип float . Запустите следующую программу, чтобы убедиться в этом.

Причина подобного результата в том, что количество памяти выделенное для хранения любой переменной ограничено Из-за этого вещественные числа хранятся в памяти компьютера приближенно. Для типа float памяти выделяется обычно меньше, чем для типа double . Поэтому возникают такие забавные моменты. Отдельно обратите внимание на результат деления целых чисел, записанный в переменную z . Об этом мы поговорим в следующем уроке.

Sscanf форматный вывод из буфера

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

Функция scanf читает символы из стандартного входного потока, интерпретирует их согласно спецификациям строки format и рассылает результаты в свои остальные аргументы. Аргумент-формат мы опишем позже; другие аргуметы, каждый из которых должен быть указателем , определяют, где будут запоминаться должным образом преобразованные данные. Как и для printf , в этом параграфе дается сводка наиболее полезных, но отнюдь не всех возможностей данной функции.

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

Существует также функция sscanf , которая читает из строки, (а не из стандартного ввода).

Функция sscanf просматривает строку string согласно формату format и рассылает полученные значения в arg 1 , arg 2 , и т.д. Последние должны быть указателями.

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


  • Пробелы или табуляции, которые игнорируются.
  • Обычные символы (исключая % ), которые, как ожидается, совпадут с очередными символами, отличными от символов-разделителей входного потока.
  • Спецификации преобразования, каждая из которых начинается со знака % и завершается символом-спецификатором типа преобразования. В промежутке между этими двумя символами в любой спецификации могут располагаться, причем в том порядке, как они здесь указаны: знак * (признак подавления присваивания); число, определяющее ширину поля; буква h , l или L , указывающая на размер получаемого значения; символ преобразования ( o , d или x ).

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

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

Таблица 7.2. Основные преобразования scanf

Символ Вводимые данные; тип аргумента
d десятичное целое; int *
i целое; int * . Число может быть восьмеричным (с 0 слева) или шестнадцатеричным (с 0x или 0X слева)
o восьмеричное целое (с нулем слева или без него); int *
u беззнаковое десятичное целое; unsigned int *
x шестнадцатеричное целое (с 0x или 0X слева или без них); int *
c символы; char * . Следующие символы из входного потока (по умолчанию один) размещаются в указанном месте. Обычный пропуск символов-разделителей подавляется; чтобы прочесть очередной символ, отличный от символа-разделителя, используйте спецификацию %1s
s строка символов (без обрамляющих кавычек); char * , указывающий на массив символов, достаточный для хранения строки и завершающего символа ‘\0’ , который будет добавлен автоматически
e , f , g число с плавающей точкой, возможно со знаком; обязательно присутствие либо десятичной точки, либо экспоненциальной части, а возможно, и обеих вместе; float *
% сам знак % , никакое присваивание не выполняется

Перед символами-спецификаторами d , l , o , u и x может стоять буква h , указывающая на то, что соответствующий аргумент должен иметь тип short * (а не int * ), или l , указывающая на тип long * . Аналогично, перед символами-спецификаторами e , f и g может стоять буква l , указывающая, что тип аргумента — double * (а не float * ).

Чтобы построить первый пример, обратимся к программе калькулятора из главы 4, в которой организуем ввод с помощью функции scanf :

Предположим, что нам нужно прочитать вводимые строки, содержащие данные вида

Обращение к scanf выглядит следующим образом:

Знак & перед monthname не нужен, так как имя массива является указателем.

В строке формата могут присутствовать символы, не участвующие ни в одной из спецификаций; это значит, что эти символы должны появиться на вводе. Так, мы могли бы читать даты вида mm/dd/yy с помощью следующего обращения к scanf :

В своем формате функция scanf игнорирует пробелы и табуляции. Кроме того, при поиске следующей порции вводимых данных она пропускает во входном потоке все символы-разделители (пробелы, табуляции, новые строки и т.д.). Воспринимать входной поток, не имеющий фиксированного формата, часто оказывается удобнее, если вводить всю строку целиком и для каждого отдельного случая подбирать подходящий вариант sscanf . Предположим, например, что нам нужно читать строки с датами, записанными в любой из приведенных выше форм. Тогда мы могли бы написать:

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

В завершение еще раз напомним, что аргументы функций scanf и sscanf должны быть указателями.

Одна из самых распространенных ошибок состоит в том, что вместо того, чтобы написать

Компилятор о подобной ошибке ничего не сообщает.

Как очистить буфер scanf() ?

Мне нужно, чтобы scanf() вызывался до тех пор, пока не будет введено целое число, но происходит зацикливание.

18.02.2020, 21:28

Scanf считывает enter, как символ конца строки. Как исправить?
Добрый день. Пишу меню для программы в Visual Studio 2015, возникает проблема: scanf запоминает.

Как пользоваться scanf и printf?
Ребят,расскажите пожалуйста как пользоваться scanf() и printf().Какие там типы бывают. Буду очень.

Не понимаю как работает scanf
Здравствуйте, вот код программы. При работе программы второй scanf пропускается. Почему так.

Как написать функцию scanf?
Как написать функцию scanf? Добавлено через 5 минут Не вызвать,а написать

Sscanf форматный вывод из буфера

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

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

Свойства библиотечных функций специфицированы в более чем дюжине заголовочных файлов; вам уже встречались некоторые из них, в том числе , и . Мы не рассматриваем здесь всю библиотеку, так как нас больше интересует написание Си-программ, чем использование библиотечных функций. Стандартная библиотека подробно описана в приложении B.

7.1 Стандартный ввод-вывод

Как уже говорилось в главе 1, библиотечные функции реализуют простую модель текстового ввода-вывода. Текстовый поток состоит из последовательности строк; каждая строка заканчивается символом новой строки. Если система в чем-то не следует принятой модели, библиотека сделает так, чтобы казалось, что эта модель удовлетворяется полностью. Например, пара символов — возврат-каретки и перевод-строки — при вводе могла бы быть преобразована в один символ новой строки, а при выводе выполнялось бы обратное преобразование.

Простейший механизм ввода — это чтение одного символа из стандартного ввода (обычно с клавиатуры) функцией getchar:

В качестве результата каждого своего вызова функция getchar возвращает следующий символ ввода или, если обнаружен конец файла, EOF. Именованная константа EOF (аббревиатура от end of file — конец файла) определена в . Обычно значение EOF равно -1, но, чтобы не зависеть от конкретного значения этой константы, обращаться к ней следует по имени (EOF).

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

будет направлять стандартный вывод не на экран, а в outfile. А командная строка

соединит стандартный вывод программы prog со стандартным вводом программы anotherprog.

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

Любой исходный Си-файл, использующий хотя бы одну функцию библиотеки ввода-вывода, должен содержать в себе строку

причем она должна быть расположена до первого обращения к вводу-выводу. Если имя заголовочного файла заключено в угловые скобки , это значит, что поиск заголовочного файла ведется в стандартном месте (например в системе UNIX это обычно директорий /usr/include).

Многие программы читают только из одного входного потока и пишут только в один выходной поток. Для организации ввода-вывода таким программам вполне хватит функций getchar, putchar и printf, а для начального обучения уж точно достаточно ознакомления с этими функциями. В частности, перечисленных функций достаточно, когда требуется вывод одной программы соединить с вводом следующей. В качестве примера рассмотрим программу lower, переводящую свой ввод на нижний регистр:

Функция tolower определена в . Она переводит буквы верхнего регистра в буквы нижнего регистра, а остальные символы возвращает без изменений. Как мы уже упоминали, «функции» вроде getchar и putchar из библиотеки и функция tolower из библиотеки часто реализуются в виде макросов, чтобы исключить накладные расходы от вызова функции на каждый отдельный символ. В параграфе 8.5 мы покажем, как это делается. Независимо от того, как на той или иной машине реализованы функции библиотеки , использующие их программы могут ничего не знать о кодировке символов.

Упражнение 7.1. Напишите программу, осуществляющую перевод ввода с верхнего регистра на нижний или с нижнего на верхний в зависимости от имени, по которому она вызывается и текст которого находится в arg[0].

7.2 Форматный вывод (printf)

Функция printf переводит внутренние значения в текст.

В предыдущих главах мы использовали printf неформально. Здесь мы покажем наиболее типичные случаи применения этой функции: полное ее описание дано в приложении B.

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

Форматная строка содержит два вида объектов: обычные символы, которые напрямую копируются в выходной поток, и спецификации преобразования, каждая из которых вызывает преобразование и печать очередного аргумента printf. Любая спецификация преобразования начинается знаком % и заканчивается символом-спецификатором. Между % и символом-спецификатором могут быть расположены (в указанном ниже порядке) следующие элементы:

  • Знак минус, предписывающий выравнивать преобразованный аргумент по левому краю поля.
  • Число, специфицирующее минимальную ширину поля. Преобразованный аргумент будет занимать поле по крайней мере указанной ширины. При необходимости лишние позиции слева (или справа при левостороннем расположении) будут заполнены пробелами.
  • Точка, отделяющая ширину поля от величины, устанавливающей точность.
  • Число (точность), специфицирующее максимальное количество печатаемых символов в строке, или количество цифр после десятичной точки — для чисел с плавающей запятой, или минимальное количество цифр — для целого.
  • Буква h, если печатаемое целое должно рассматриваться как short, или l (латинская буква ell), если целое должно рассматриваться как long.

    Символы-спецификаторы перечислены в таблице 7.1. Если за % не помещен символ- спецификатор, поведение функции printf будет не определено. Ширину и точность можно специфицировать с помощью *; значение ширины (или точности) в этом случае берется из следующего аргумента (который должен быть типа int). Например, чтобы напечатать не более max символов из строки s, годится следующая запись:

    Таблица 7.1 Основные преобразования printf

    Символ Тип аргумента; вид печати
    d, i int; десятичное целое
    o unsigned int; беззнаковое восьмеричное (octal) целое (без нуля слева)
    x, X unsigned int; беззнаковое шестнадцатеричное целое (без 0x или 0X слева), для 10. 15 используются abcdef или ABCDEF
    u unsigned int; беззнаковое десятичное целое
    c int; одиночный символ
    s char *; печатает символы, расположенные до знака \0, или в количестве, заданном точностью
    f double; [-]m.dddddd, где количество цифр d задается точностью (по умолчанию равно 6)
    e, E double; [-]m.dddddde±xx или [-]m.ddddddE±xx, где количество цифр d задается точностью (по умолчанию равно 6)
    g, G double; использует %e или %E, если порядок меньше, чем -4, или больше или равен точности; в противном случае использует %f. Завершающие нули и завершающая десятичная точка не печатаются
    p void *; указатель (представление зависит от реализации)
    % Аргумент не преобразуется; печатается знак %

    Большая часть форматных преобразований была продемонстрирована в предыдущих главах. Исключение составляет задание точности для строк. Далее приводится перечень спецификаций и показывается их влияние на печать строки «hello, world», состоящей из 12 символов. Поле специально обрамлено двоеточиями, чтобы была видна его протяженность.

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

    Функция sprintf выполняет те же преобразования, что и printf, но вывод запоминает в строке

    Эта функция форматирует arg1, arg2 и т. д. в соответствии с информацией, заданной аргументом format, как мы описывали ранее, но результат помещает не в стандартный вывод, а в string. Заметим, что строка string должна быть достаточно большой, чтобы в ней поместился результат.

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

    7.3 Списки аргументов переменной длины

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

    Объявление стандартной функции printf выглядит так:

    Многоточие в объявлении означает, что число и типы аргументов могут изменяться. Знак многоточие может стоять только в конце списка аргументов. Наша функция minprintf объявляется как

    поскольку она не будет выдавать число символов, как это делает printf.

    Вся сложность в том, каким образом minprintf будет продвигаться вдоль списка аргументов, — ведь у этого списка нет даже имени. Стандартный заголовочный файл содержит набор макроопределений, которые устанавливают, как шагать по списку аргументов. Наполнение этого заголовочного файла может изменяться от машины к машине, но представленный им интерфейс везде одинаков.

    Тип va_list служит для описания переменной, которая будет по очереди указывать на каждый из аргументов; в minprintf эта переменная имеет имя ap (от «argument pointer» — указатель на аргумент). Макрос va_start инициализирует переменную ap) чтобы она указывала на первый безымянный аргумент. К va_start нужно обратиться до первого использования ap. Среди аргументов по крайней мере один должен быть именованным: от последнего именованного аргумента этот макрос «отталкивается» при начальной установке.

    Макрос va_arg на каждом своем вызове выдает очередной аргумент, а ap передвигает на следующий: но имени типа он определяет тип возвращаемого значения и размер шага для выхода на следующий аргумент. Наконец, макрос va_end делает очистку всего, что необходимо. К va_end следует обратиться перед самым выходом из функции.

    Перечисленные средства образуют основу нашей упрощенной версии prinf.


    Упражнение 7.3. Дополните minprintf другими возможностями printf.

    7.4 Форматный ввод (scanf)

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

    Функция scanf читает символы из стандартного входного потока, интерпретирует их согласно спецификациям строки format и рассылает результаты в свои остальные аргументы. Аргумент format мы опишем позже; другие аргументы, каждый из которых должен быть указателем, определяют, где будут запоминаться должным образом преобразованные данные. Как и для printf, в этом параграфе дается сводка наиболее полезных, но отнюдь не всех возможностей данной функции.

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

    Существует также функция sscanf, которая читает из строки (а не из стандартного ввода).

    Функция sscanf просматривает строку string согласно формату format и рассылает полученные значения в arg1, arg2 и т. д. Последние должны быть указателями.

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

  • Пробелы или табуляции, которые игнорируются.
  • Обычные символы (исключая %), которые, как ожидается, совпадут с очередными символами, отличными от символов-разделителей входного потока.
  • Спецификации преобразования, каждая из которых начинается со знака % и завершается символом-спецификатором типа преобразования. В промежутке между этими двумя символами в любой спецификации могут располагаться, причем в том порядке, как они здесь указаны: знак * (признак подавления присваивания); число, определяющее ширину поля; буква h, l или L, указывающая на размер получаемого значения; и символ преобразования (o, d, x).

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

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

    Перед символами-спецификаторами d, l, o, u и x может стоять буква h, указывающая на то, что соответствующий аргумент должен иметь тип short * (а не int *),или l (латинская ell), указывающая на тип long *. Аналогично, перед символами-спецификаторами e, f и g может стоять буква l, указывающая, что тип аргумента — double * (а не float *).

    Таблица 7.2 Основные преобразования scanf

    Символ Вводимые данные; тип аргумента
    d десятичное целое: int *
    i целое: int *. Целое может быть восьмеричным (с 0 слева) или шестнадцатеричным (с 0x или 0X слева)
    o восьмеричное целое (с нулем слева или без него); int *
    u беззнаковое десятичное целое; unsigned int *
    x шестнадцатеричное целое (с 0x или 0X слева или без них); int *
    c символы; char *. Следующие символы ввода (по умолчанию один) размещаются в указанном месте. Обычный пропуск символов- разделителей подавляется; чтобы прочесть очередной символ, отличный от символа-разделителя, используйте %1s
    s Строка символов(без обрамляющих кавычек); char *, указывающая на массив символов, достаточный для строки и завершающего символа ‘\0’, который будет добавлен
    e, f, g число с плавающей точкой, возможно, со знаком; обязательно присутствие либо десятичной точки, либо экспоненциальной части, а возможно, и обеих вместе; float *
    % сам знак %, никакое присваивание не выполняется

    Чтобы построить первый пример, обратимся к программе калькулятора из главы 4, в которой организуем ввод с помощью функции scanf:

    Предположим, что нам нужно прочитать строки ввода, содержащие данные вида

    Обращение к scanf выглядит следующим образом:

    Знак & перед monthname не нужен, так как имя массива есть указатель.

    В строке формата могут присутствовать символы, не участвующие ни в одной из спецификаций; это значит, что эти символы должны появиться на вводе. Так, мы могли бы читать даты вида mm/dd/yy с помощью следующего обращения к scanf:

    В своем формате функция scanf игнорирует пробелы и табуляции. Кроме того, при поиске следующей порции ввода она пропускает во входном потоке все символы- разделители (пробелы, табуляции, новые строки и т.д.). Воспринимать входной поток, не имеющий фиксированного формата, часто оказывается удобнее, если вводить всю строку целиком и для каждого отдельного случая подбирать подходящий вариант sscanf. Предположим, например, что нам нужно читать строки с датами, записанными в любой из приведенных выше форм. Тогда мы могли бы написать:

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

    В завершение еще раз напомним, что аргументы функций scanf и sscanf должны быть указателями.

    Одна из самых распространенных ошибок состоит в том, что вместо того, чтобы написать

    Компилятор о подобной ошибке ничего не сообщает.

    Упражнение 7.4. Напишите свою версию scanf по аналогии с minprintf из предыдущего параграфа.

    Упражнение 7.5. Перепишите основанную на постфиксной записи программу калькулятора из главы 4 таким образом, чтобы для ввода и преобразования чисел она использовала scanf и/или sscanf.

    7.5 Доступ к файлам

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

    Следующий шаг — научиться писать программы, которые имели бы доступ к файлам, заранее не подсоединенным к программам. Одна из программ, в которой возникает такая необходимость, — это программа cat, объединяющая несколько именованных файлов и направляющая результат в стандартный вывод. Функция cat часто применяется для выдачи файлов на экран, а также как универсальный «коллектор» файловой информации для тех программ, которые не имеют возможности обратиться к файлу по имени. Например, команда

    направит в стандартный вывод содержимое файлов x.c и y.c (и ничего более).

    Возникает вопрос: что надо сделать, чтобы именованные файлы можно было читать; иначе говоря, как связать внешние имена, придуманные пользователем, с инструкциями чтения данных?

    На этот счет имеются простые правила. Для того чтобы можно было читать из файла или писать в файл, он должен быть предварительно открыт с помощью библиотечной функции fopen. Функция fopen получает внешнее имя типа x.c или y.c, после чего осуществляет некоторые организационные действия и «переговоры» с операционной системой (технические детали которых здесь не рассматриваются) и возвращает указатель, используемый в дальнейшем для доступа к файлу.

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

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

    Это говорит, что fp есть указатель на FILE, a fopen возвращает указатель на FILE. Заметим, что FILE — это имя типа) наподобие int, а не тег структуры. Оно определено с помощью typedef. (Детали того, как можно реализовать fopen в системе UNIX, приводятся в параграфе 8.5.)

    Обращение к fopen в программе может выглядеть следующим образом:

    Первый аргумент — строка, содержащая имя файла. Второй аргумент несет информацию о режиме. Это тоже строка: в ней указывается, каким образом пользователь намерен применять файл. Возможны следующие режимы: чтение (read — «r»), запись (write — «w») и добавление (append — «a»), т. е. запись информации в конец уже существующего файла. В некоторых системах различаются текстовые и бинарные файлы; в случае последних в строку режима необходимо добавить букву «b» (binary — бинарный).

    Тот факт, что некий файл, которого раньше не было, открывается на запись или добавление, означает, что он создается (если такая процедура физически возможна). Открытие уже существующего файла на запись приводит к выбрасыванию его старого содержимого, в то время как при открытии файла на добавление его старое содержимое сохраняется. Попытка читать несуществующий файл является ошибкой. Могут иметь место и другие ошибки; например, ошибкой считается попытка чтения файла, который по статусу запрещено читать. При наличии любой ошибки fopen возвращает NULL. (Возможна более точная идентификация ошибки; детальная информация по этому поводу приводится в конце параграфа 1 приложения B.)

    Следующее, что нам необходимо знать, — это как читать из файла или писать в файл, коль скоро он открыт. Существует несколько способов сделать это, из которых самый простой состоит в том, чтобы воспользоваться функциями getc и putc. Функция getc возвращает следующий символ из файла; ей необходимо сообщить указатель файла, чтобы она знала откуда брать символ.

    Функция getc возвращает следующий символ из потока, на который указывает *fp; в случае исчерпания файла или ошибки она возвращает EOF.

    Функция putc пишет символ c в файл fp

    и возвращает записанный символ или EOF в случае ошибки. Аналогично getchar и putchar, реализация getc и putc может быть выполнена в виде макросов, а не функций.

    При запуске Си-программы операционная система всегда открывает три файла и обеспечивает три файловые ссылки на них. Этими файлами являются: стандартный ввод, стандартный вывод и стандартный файл ошибок; соответствующие им указатели называются stdin, stdout и stderr; они описаны в . Обычно stdin соотнесен с клавиатурой, а stdout и stderr — с экраном. Однако stdin и stdout можно связать с файлами или, используя конвейерный механизм, соединить напрямую с другими программами, как это описывалось в параграфе 7.1.

    С помощью getc, putc, stdin и stdout функции getchar и putchar теперь можно определить следующим образом:

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

    Вот теперь мы располагаем теми сведениями, которые достаточны для написания программы cat, предназначенной для конкатенации (последовательного соединения) файлов. Предлагаемая версия функции cat, как оказалось, удобна для многих программ. Если в командной строке присутствуют аргументы, они рассматриваются как имена последовательно обрабатываемых файлов. Если аргументов нет, то обработке подвергается стандартный ввод.

    Файловые указатели stdin и stdout представляют собой объекты типа FILE*. Это константы, а не переменные, следовательно, им нельзя ничего присваивать.

    — обратная по отношению к fopen; она разрывает связь между файловым указателем и внешним именем (которая раньше была установлена с помощью fopen), освобождая тем самым этот указатель для других файлов. Так как в большинстве операционных систем количество одновременно открытых одной программой файлов ограничено, то файловые указатели, если они больше не нужны, лучше освобождать, как это и делается в программе cat. Есть еще одна причина применить fclose к файлу вывода, — это необходимость «опорожнить» буфер, в котором putc накопила предназначенные для вывода данные. При нормальном завершении работы программы для каждого открытого файла fclose вызывается автоматически. (Вы можете закрыть stdin и stdout, если они вам не нужны. Воспользовавшись библиотечной функцией freopen, их можно восстановить.)

    7.6 Управление ошибками (stderr и exit)

    Обработку ошибок в cat нельзя признать идеальной. Беда в том, что если файл по какой-либо причине недоступен, сообщение об этом мы получим по окончании конкатенируемого вывода. Это нас устроило бы, если бы вывод отправлялся только на экран, a не в файл или по конвейеру другой программе. Чтобы лучше справиться с этой проблемой, программе помимо стандартного вывода stdout придается еще один выходной поток, называемый stderr. Вывод в stderr обычно отправляется на экран, даже если вывод stdout перенаправлен в другое место. Перепишем cat так, чтобы сообщения об ошибках отправлялись в stderr.

    Программа сигнализирует об ошибках двумя способами. Первый — сообщение об ошибке с помощью fprintf посылается в stderr с тем, чтобы оно попало на экран, а не оказалось на конвейере или в другом файле вывода. Имя программы, хранящееся в argv[0], мы включили в сообщение, чтобы в случаях, когда данная программа работает совместно с другими, был ясен источник ошибки.

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

    Инструкция return exp главной программы main эквивалентна обращению к функции exit(exp). Последний вариант (с помощью exit) имеет то преимущество, что он пригоден для выхода и из других функций, и, кроме того, слово exit легко обнаружить с помощью программы контекстного поиска, похожей на ту, которую мы рассматривали в главе 5. Функция ferror выдает ненулевое значение, если в файле fp была обнаружена ошибка.

    Хотя при выводе редко возникают ошибки, все же они встречаются (например, оказался переполненным диск); поэтому в программах широкого пользования они должны тщательно контролироваться.

    Функция feof(FILE *fp) aнaлoгичнa функции ferror; oнa вoзвpaщaeт нeнулевое значение, если встретился конец указанного в аргументе файла.

    В наших небольших иллюстративных программах мы не заботились о выдаче статуса выхода, т. е. выдаче некоторого числа, характеризующего состояние программы в момент завершения: работа закончилась нормально или прервана из-за ошибки? Если работа прервана в результате ошибки, то какой? Любая серьезная программа должна выдавать статус выхода.

    7.7 Ввод-вывод строк

    В стандартной библиотеке имеется программа ввода fgets, аналогичная программе getline, которой мы пользовались в предыдущих главах.

    Функция fgets читает следующую строку ввода (включая и символ новой строки) из файла fp в массив символов line, причем она может прочитать не более MAXLINE-1 символов. Переписанная строка дополняется символом ‘\0’. Обычно fgets возвращает line, а по исчерпании файла или в случае ошибки — NULL. (Наша getline возвращала длину строки, которой мы потом пользовались, и нуль в случае конца файла.)

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

    Эта функция возвращает EOF, если возникла ошибка, и неотрицательное значение в противном случае.

    Библиотечные функции gets и puts подобны функциям fgets и fputs. Отличаются они тем, что оперируют только стандартными файлами stdin и stdout, и кроме того, gets выбрасывает последний символ ‘\n’, a puts его добавляет.

    Чтобы показать, что ничего особенного в функциях вроде fgets и fputs нет, мы приводим их здесь в том виде, в каком они существуют в стандартной библиотеке на нашей системе.

    Стандарт определяет, что функция ferror возвращает в случае ошибки ненулевое значение; fputs в случае ошибки возвращает EOF, в противном случае — неотрицательное значение.

    С помощью fgets легко реализовать нашу функцию getline:

    Упражнение 7.6. Напишите программу, сравнивающую два файла и печатающую первую строку, в которой они различаются.


    Упражнение 7.7. Модифицируйте программу поиска по образцу из главы 5 таким образом, чтобы она брала текст из множества именованных файлов, а если имен файлов в аргументах нет, то из стандартного ввода. Будет ли печататься имя файла, в котором найдена подходящая строка?

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

    7.8 Другие библиотечные функции

    В стандартной библиотеке представлен широкий спектр различных функций. Настоящий параграф содержит краткий обзор наиболее полезных из них. Более подробно эти и другие функции описаны в приложении B.

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

    Мы уже упоминали функции strlen, strcpy, strcat и strcmp, описание которых даны в . Далее, до конца пункта, предполагается, что s и t имеют тип char *, c и n — тип int.

    strcat(s,t) — приписывает t в конец s.
    strncat(s,t,n) — приписывает n символов из t в конец s.
    strcmp(s,t) — возвращает отрицательное число, нуль или положительное число для s t, соответственно.
    strncmp(s,t,n) — делает то же, что и strcmp, но количество сравниваемых символов не может превышать n
    strcpy(s,t) — копирует t в s.
    strncpy(s,t,n) — копирует не более n символов из t в s.
    strlen(s) — возвращает длину s.
    strchr(s,c) — возвращает указатель на первое появление символа c в s или, если c нет в s, NULL.
    strrchr(s,c) — возвращает указатель на последнее появление символа c в s или, если c нет в s, NULL.

    7.8.2 Анализ класса символов и преобразование символов

    Несколько функций из библиотеки выполняют проверки и преобразование символов. Далее, до конца пункта, переменная c — это переменная типа int, которая может быть представлена значением unsigned, char или EOF. Все эти функции возвращают значения типа int.

    isalpha(c) — не нуль, если c — буква; 0 в противном случае.
    isupper(c) — не нуль, если c — буква верхнего регистра; 0 в противном случае.
    islower(c) — не нуль, если c — буква нижнего регистра; 0 в противном случае.
    isdigit(c) — не нуль, если c — цифра; 0 в противном случае.
    isalnum(c) — не нуль, если или isalpha(c), или isdigit(c) истинны; 0 в противном случае.
    isspace(c) — не нуль, если c — символ пробела, табуляции, новой строки, возврата каретки, перевода страницы, вертикальной табуляции.
    toupper(c) — возвращает c, приведенную к верхнему регистру.
    tolower(c) — возвращает c, приведенную к нижнему регистру.

    7.8.3 Функция ungetc

    В стандартной библиотеке содержится более ограниченная версия функции ungetch по сравнению с той, которую мы написали в главе 4. Называется она ungetc. Эта функция, имеющая прототип

    отправляет символ c назад в файл fp и возвращает c, а в случае ошибки EOF. Для каждого файла гарантирован возврат не более одного символа. Функцию ungetc можно использовать совместно с любой из функций ввода вроде scanf, getc, getchar и т. д.

    7.8.4 Исполнение команд операционной системы

    Функция system(char *s) выполняет команду системы, содержащуюся в строке s, и затем возвращается к выполнению текущей программы.

    Содержимое s, строго говоря, зависит от конкретной операционной системы. Рассмотрим простой пример: в системе UNIX инструкция

    вызовет программу date, которая направит дату и время в стандартный вывод. Функция возвращает зависящий от системы статус выполненной команды. В системе UNIX возвращаемый статус — это значение, переданное функцией exit.

    7.8.5 Управление памятью

    Функции malloc и calloc динамически запрашивают блоки свободной памяти. Функция malloc

    возвращает указатель на n байт неинициализированной памяти или NULL, если запрос удовлетворить нельзя. Функция calloc

    возвращает указатель на область, достаточную для хранения массива из n объектов указанного размера (size), или NULL, если запрос не удается удовлетворить. Выделенная память обнуляется.

    Указатель, возвращаемый функциями malloc и calloc, будет выдан с учетом выравнивания, выполненного согласно указанному типу объекта. Тем не менее к нему должна быть применена операция приведения к соответствующему типу (Как уже отмечалось (см. примеч. в параграфе 6.5), замечания о приведении типов значений, возвращаемых функциями malloc или calloc, — неверно. — Примеч. авт.), как это сделано в следующем фрагменте программы:

    Функция free(p) освобождает область памяти, на которую указывает p, — указатель, первоначально полученный с помощью malloc или calloc. Никаких ограничений на порядок, в котором будет освобождаться память, нет, но считается ужасной ошибкой освобождение тех областей, которые не были получены с помощью calloc или malloc.

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

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

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

    7.8.6 Математические функции

    В описано более двадцати математических функций. Здесь же приведены наиболее употребительные. Каждая из них имеет один или два аргумента типа double и возвращает результат также типа double.

    sin(x) — синус x, x в радианах
    cos(x) — косинус x, x в радианах
    atan2(y,x) — арктангенс y/x, y и x в радианах
    exp(x) — экспоненциальная функция e в степени x
    log(x) — натуральный (по основанию e) логарифм x (x>0)
    log10(x) — обычный (по основанию 10) логарифм x (x>0)
    pow(x,y) — x в степени y
    sqrt(x) — корень квадратный x (x > 0)
    fabs(x) — абсолютное значение x

    7.8.7 Генератор случайных чисел

    Функция rand() вычисляет последовательность псевдослучайных целых в диапазоне от нуля до значения, заданного именованной константой RAND_MAX, которая определена в . Привести случайные числа к значениям с плавающей точкой, большим или равным 0 и меньшим 1, можно по формуле

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

    Функция srand(unsigned) устанавливает семя для rand. Реализации rand и srand, предлагаемые стандартом и, следовательно, переносимые на различные машины, рассмотрены в параграфе 2.7.

    Упражнение 7.9. Реализуя функции вроде isupper, можно экономить либо память, либо время. Напишите оба варианта функции.

    Обзор средств ввода-вывода в C++

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

    В стандартном C++ существует два основных пути ввода-вывода информации: с помощью потоков, реализованных в STL (Standard Template Library) и посредством традиционной системы ввода-вывода, унаследованной от C. Если копнуть немного глубже, то окажется, что и потоки, и традиционная система ввода-вывода для осуществления необходимых действий используют вызовы операционной системы. И это правильно.

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

    Традиционный ввод-вывод

    Для использования традиционного ввода-вывода в духе C, в программу необходимо включить заголовочный файл . (Разумеется, компилятор должен иметь доступ к соответствующей объектной библиотеке для правильной сборки исполняемого файла.)

    Библиотека stdio предоставляет необходимый набор функций для ввода и вывода информации как в текстовом, так и в двоичном представлении. Следует отметить, что в отличие от классической C‑библиотеки, в современных библиотеках имеются более безопасные аналоги «классических» функций. Как правило, они имеют такое же имя, к которому добавлен суффикс _s. Рекомендуется использовать именно эти, безопасные функции.

    Самая Первая Программа с использованием библиотеки stdio выглядит так:

    При запуске консольного приложения неявно открываются три потока: stdin — для ввода с клавиатуры, stdout — для буферизованного вывода на монитор и stderr — для небуферизованного вывода на монитор сообщений об ошибках. Эти три символа определены посредством .

    В stdio для консольного ввода-вывода предусмотрена отдельная группа функций. Однако эти функции, как правило, являются обёртками для аналогичных функций файлового ввода-вывода, для которых аргумент типа FILE задан по умолчанию.

    Самая Первая Программа с использование файлового вывода из библиотеки stdio выглядит так:

    Некоторые популярные функции из stdio:

    Сущность FILE представляет собой структуру, в которой хранится вся информация для управления потоком ввода-вывода.

    Файл открывается функцией fopen(), которой передаются два параметра. Первый параметр определяет имя файла. Второй — определяет режим открытия файла: чтение, запись, произвольный доступ и т.п., а также указание на то, как работать с данными: в текстовом или двоичном режиме. Подробности — см. в документации.

    Пример использования stdio

    Следует отметить, что существует еще одна библиотека, ориентированная исключительно на консольный ввод-вывод — .

    Ввод-вывод с помощью потоков STL

    Для использования объектно-ориентированного консольного ввода-вывода с помощью потоков (stream) STL в программу необходимо включить заголовочный файл , а для файлового ещё и . (Разумеется, компилятор должен иметь доступ к соответствующей объектной библиотеке для правильной сборки исполняемого файла.)

    Самая Первая Программа с использованием потоков STL выглядит так:

    При запуске консольного приложения неявно открываются четыре потока: сin — для ввода с клавиатуры, сout — для буферизованного вывода на монитор, сerr — для небуферизованного вывода на монитор сообщений об ошибках и clog — буферизованный аналог cerr. Эти четыре символа определены посредством .

    Потоки cin, cout и cerr соответствуют потокам stdin, stdout и stderr соответственно.

    Иерархия классов ввода-вывода STL достаточно сложна. Любители тонких ощущений могут найти её описание в литературе. Впрочем, остальных также не минует чаша сия, но только позже, когда потребуются знания чуть повыше того базового уровня, который описывается здесь.

    Для ввода-вывода сначала необходимо создать поток — экземпляр соответствующего класса STL, а затем связать его с файлом. Для потока вывода используется класс ofstream, для потока ввода — ifstream, для потока ввода-вывода — fstream. В каждом из этих классов есть метод open(), который связывает поток с файлом. Проще говоря, открывает файл. Методу передаются два параметра: имя файла и режим открытия файла. Второй параметр представляет собой набор битовых флагов, определяющих режим открытия файла (чтение, запись и пр.) и способ работы с данными (текстовый или двоичный режим). Второй параметр опционален, т.е. имеет значение по умолчанию, соответствующее классу.

    Вышеупомянутые классы имеют также конструкторы, позволяющие открыть файл сразу при создании потока. Параметры этих конструкторов полностью совпадают с параметрами метода open().

    При ошибке открытия файла (в контексте логического выражения) поток получает значение false.

    Файл закрывается методом close(). Этот метод также вызывается при разрушении экземпляров классов потоков.

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

    Некоторые наиболее употребляемые методы:

    Пример использования потоков STL

    Взаимодействие потокового и традиционного ввода-вывода

    Апологеты C++ рекомендуют использовать для ввода-вывода только потоки STL и отказаться от использования традиционного ввода-вывода в духе C. Однако, ничто не мешает, по крайней мере пока, использовать традиционную систему ввода-вывода. Более того, предусмотрена специальная функция для синхронизации ввода-вывода, выполненного посредством потоков и посредством старых функций.

    Заключение

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

    Использование механизма потоков считается более безопасным. Но, как известно, плохую программу можно написать на любом языке программирования. Это также относится и к использованию библиотек. Автор статьи: Череп.

    Ввод и вывод данных средствами языка C


    В системе ввода-вывода в Си для программ поддерживается единый интерфейс, не зависящий от того, к какому конкретному устройству осуществляется доступ. То есть в Си между программой и устройством находится нечто более общее, чем само устройство. Такое обобщенное устройство ввода или вывода (устройство более высокого уровня абстракции) называется потоком. В то же время конкретное устройство называется файлом. Наша задача — понять, каким обрзом происходит взаимодействие потоков и файлов.
    Файловая система Си предназначена для работы с разными устройствами, в том числе с терминалами, дисководами и накопителями. Даже, если какое-то устройство очень сильно отличается от других устройств, буферизованная файловая система все равно представит его в виде логического устройства, которое называется потоком. Все потоки ведут себя похожим образом. И так как они в основном не зависят от физических устройств, то та же функция, которая выполняет запись в дисковый файл, может ту же операцию выполнить и на другом устройстве. Например, на консоли. Потоки бывают двух видов: текстовые и двоичные.
    В языке Си файлом может быть все, что угодно, начиная в дискового файла и заканчивая терминалом или принтером. Поток связывают с определенным файлом, выполняя обязательную операцию открытия. Как только файл открыт, можно проводить обмен информацией между ним и программой.
    Но не у всех файлов одинаковые возможности. Например, к дисковому файлу прямой доступ возможен, в то время как к некоторым принтерам — он не возможен. Таким образом, вы видите, что напрашивается определенный вывод, являющийся принципом системы ввода-вывода языка Си: все потоки одинаковы, а файлы — нет!
    Если файл может поддерживать запросы на местоположение (указатель текущей позиции), то при открытии такого файла указатель текущей позиции в файле устанавливается в начало файла. При чтении каждого символа из файла (или записи в файл) указатель текущей позиции увеличивается. Тем самым обеспечивается продвижение по файлу.
    Файл отсоединяется от определенного потока (то есть разрывается связь между файлом и потоком) с помощью операции закрытия файла. При закрытии файла, открытого с целью вывода, содержимое (если оно, конечно, есть) связанного с ним потока записывается на внешнее устройство. Этот процесс обычно называют дозаписью потока. При этом гарантируется, что никакая информация случайно не останется в буфере диска.
    Если программа завершает работу нормально, то есть либо main() возвращает управление операционной системе, либо выход происходит через exit(), то все файлы закрываются автоматически.
    В случае же аварийного завершения работы программы, например, в случа краха или завершения путем вызова abort(), файлы не закрываются.
    У каждого потока, связанного с файлом, имеется управляющая структура, содержащая информацию о файле. Она имеет тип FILE. Блок управления файлом — это небольшой блок памяти, временно выделенный операционной системой для хранения информации о файле, который был открыт для использования. Блок управления файлом обычно содержит информацию об идентификаторе файла, его расположении на диске и указателе текущей позиции в файле.
    Для выполнения всех операций ввода-вывода следует использовать только понятия потоков и применять всего лишь одну файловую систему. Ввод или вывод от каждого устройства автоматически преобразуется системой в легко управлемый поток. И это является достижением языка Си.
    Таковы основополагающие замечания относительно существования различных потоков информации и связанных с ними файлов.
    Файловая система языка Си состоит из нескольких взаимосвязанных между собой функций. Для их работы в Си требуется заголовочный файл и такой же аналогичный ему заголовочный файл требуется для работы в С++.
    Ниже приведена таблица основных (часто используемых) функций файловой системы языка Си.

    Имя Что делает эта функция Имя Что делает эта функция
    fopen() Открывает файл feof() Возвращает значение true (истина), если достигнут конец файла
    fclose() Закрывает файл ferror() Возвращает значение true (истина), если произошла ошибка
    putc() Записывает символ в файл remove() Стирает файл
    fputc() То же, что и putc() fflush() Дозапись потока в файл
    getc() Читает символ из файла rewind() Устанавливает указатель текущей позиции в начало файла
    fgetc() То же, что и getc() ftell() Возвращает текущее значение указателя текущей позиции в файле
    fgets() Читает строку из файла fprintf() Для файла то же, что printf() для консоли
    fputs() Записывает строку в файл fscanf() Для файла то же, что scanf() для консоли
    fseek() Устанавливает указатель текущей позиции на определенный байт файла

    Заголовок представляет прототипы функций ввода-вывода в Си и определяет следующие три типа: size_t, fpos_t и FILE. Первые два: size_t, fpos_t представляют собой разновидности такого типа, как целое без знака. Отдельно рассмотрим третий тип: FILE.
    Указатель файла — это то, что соединяет в единое целое всю систему ввода-вывода языка Си. Указатель файла — это указатель на структуру типа FILE. Он указывает на структуру, содержащую различные сведения о файле, например, его имя, статус, и указатель текущей позиции в начало файла. В сущности указатель файла определяет конкретный файл и используется соответствующим потоком при выполнении функции ввода-вывода.
    Чтобы выполнять в файлах операции чтения и записи, программы должны использовать указатели соответствующих файлов. Чтобы объвить переменную-указатель файла необходимо использовать следующий оператор:
    FILE *fp;
    Функция fopen() открывает поток и связывает с этим потоком файл. Затем она возвращает указатель этого файла. Прототип функции имеет вид:
    FILE *fopen(const char *имя_файла, const char *режим);
    Здесь имя_файла — это указатель на строку символов, представляющую собой допустимое имя файла, в которое может входить спецификация файла (включает обозначение логического устройства, путь к файлу и собственно имя файла).
    Режим — определяет, каким образом файл будет открыт. Ниже в таблице показаны допустимые значения режимов.

    Режим Что обозначает данный режим
    r Открыть текстовый файл для чтения
    w Создать текстовый файл для записи
    a Добавить в конец текстового файла
    wb Создать двоичный файл для записи
    rb Открыть двоичный файл для чтения
    ab Добавить в конец двоичного файла
    r+ Открыть текстовый файл для чтения/записи
    w+ Создать текстовый файл для чтения/записи
    a+ Добавить в конец текстового файла или создать текстовый файл для чтения/записи
    r+b Открыть двоичный файл для чтения/записи
    w+b Создать двоичный файл для чтения/записи
    a+b Добавить в конец двоичного файла или создать двоичный файл для чтения/записи

    Приведем фрагмент программы, в котором используется функция fopen() для открытия файла по имени TEST.
    FILE *fp;
    fp = fopen(«test», «w»);
    Следует сразу же указать на недостаточность такого кода в программе. Хотя приведенный код технически правильный, но его обычно пишут немного по-другому.
    FILE *fp;
    if ((fp = fopen(«test», «w»)==NUL)
    <
    printf(«Ошибка при открытии файла.\n\r»)»
    exit(1);
    >

    Рис. 1
    Этот метод помогает при открытии файла обнаружить любую ошибку.
    Например, защиту от записи или полный диск. Причем, обнаружить еще до того, как программа попытается в этот файл что-то записать. Поэтому всегда нужно вначале получить подтверждение, что функция fopen() выполнилась успешно, и лишь затем выполнять c файлом другие операции. Ниже на рисунке 1 приведена небольшую часть программы, которая. подтверждает или не подтверждает открытие файла. Результаты работы указанной программы приведены на рисунке 2.

    Потоковый ввод-вывод
    На уровне потокового ввода-вывода обмен данными производится побайтно. Такой ввод-вывод возможен как для собственно устройств побайтового обмена (печатающее устройство, дисплей), так и для файлов на диске, хотя устройства внешней памяти, строго говоря, являются устройствами поблочного обмена, т.е. за одно обращение к устройству производится считывание или запись фиксированной порции данных. Чаще всего минимальной порцией данных, участвующей в обмене с внешней памятью, являются блоки в 512 байт или 1024 байта. При вводе с диска (при чтении из файла) данные помещаются в буфер операционной системы, а затем побайтно или определенными порциями передаются программе пользователя. При выводе данных в файл они накапливаются в буфере, а при заполнении буфера записываются в виде единого блока на диск за одно обращение к последнему. Буферы операционной системы реализуются в виде участков основной памяти. Поэтому пересылки между буферами ввода-вывода и выполняемой программой происходят достаточно быстро в отличие от реальных обменов с физическими устройствами.
    Функции библиотеки ввода-вывода языка Си, поддерживающие обмен данными с файлами на уровне потока, позволяют обрабатывать данные различных размеров и форматов, обеспечивая при этом буферизованный ввод и вывод. Таким образом, поток — это файл вместе с предоставляемыми средствами буферизации.
    При работе с потоком можно производить следующие действия:
    · открывать и закрывать потоки (связывать указатели на потоки с конкретными файлами);
    · вводить и выводить: символ, строку, форматированные данные, порцию данных произвольной длины;
    · анализировать ошибки потокового ввода-вывода и условие достижения конца потока (конца файла);
    · управлять буферизацией потока и размером буфера;
    · получать и устанавливать указатель (индикатор) текущей позиции
    При открытии потока могут возникнуть следующие ошибки: указанный файл, связанный с потоком, не найден (для режима «чтение»); диск заполнен или диск защищен от записи и т.п. Необходимо также отметить, что при выполнении функции fopen() происходит выделение динамической памяти. При её отсутствии устанавливается признак ошибки «Not enough memory» (недостаточно памяти). В перечисленных случаях указатель на поток приобретает значение NULL. Заметим, что указатель на поток в любом режиме, отличном от аварийного никогда не бывает равным NULL.
    Приведем типичную последовательность операторов, которая используется при открытии файла, связанного с потоком:
    if ((fp = fopen(«t.txt»,»w»)) == NULL)
    perror(«ошибка при открытии файла t.txt \n»);
    exit(0);
    >
    Где NULL — нулевой указатель, определенный в файле stdio.h.
    Открытые на диске файлы после окончания работы с ними рекомендуется закрыть явно. Для этого используется библиотечная функция
    int fclose (указатель_на_поток);
    Открытый файл можно открыть повторно (например, для изменения режима работы с ним) только после того, как файл будет закрыт с помощью функции fclose().
    Когда программа начинает выполняться, автоматически открываются пять потоков, из которых основными являются:
    · стандартный поток ввода (на него ссылаются, используя предопределенный указатель на поток stdin);
    · стандартный поток вывода (stdout);
    · стандартный поток вывода сообщений об ошибках (stderr).
    По умолчанию стандартному потоку ввода stdin ставится в соответствие клавиатура, а потокам stdout и stderr соответствует экран дисплея.
    Одним из наиболее эффективных способов осуществления ввода-вывода одного символа является использование библиотечных функций getchar( ) и putchar(). Прототипы этих функций имеют следующий вид:
    int getchar(void);
    int putchar(int c);
    Функция getchаr( ) осуществляет ввод одного символа. При обращении она возвращает в вызвавшую ее функцию один введенный символ.
    Функция putchar( ) выводит в стандартный поток один символ, при этом также возвращает в вызвавшую ее функцию только что выведенный символ.
    Обратите внимание на то, что функция getchar( ) вводит очередной байт информации (символ) в виде значения типа int. Это сделано для того, чтобы гарантировать успешность распознавания ситуации «достигнут конец файла». Дело в том, что при чтении из файла с помощью функции getchar() может быть достигнут конец файла. В этом случае операционная система в ответ на попытку чтения символа передает функции getchar() значение EOF (End of File). Константа EOF определена в заголовочном файле stdio.h и в разных операционных системах имеет значение 0 или -1. Таким образом, функция getchar() должна иметь возможность прочитать из входного потока не только символ, но и целое значение. Именно с этой целью функция getchar( ) всегда возвращает значение типа int.
    В случае ошибки при вводе функция getchar() также возвращает EOF.
    При наборе текста на клавиатуре коды символов записываются во внутренний буфер операционной системы, Одновременно они отображаются (для визуального контроля) на экране дисплея. Набранные на клавиатуре символы можно редактировать (удалять и набирать новые). Фактический перенос символов из внутреннего буфера в программу происходит при нажатии клавиши . При этом код клавиши также заносится во внутренний буфер. Таким образом, при нажатии на (Клавишу ‘А’ и клавишу (завершение ввода) во внутреннем буфере оказываются: код символа ‘А’ и код клавиши . ) Об этом необходимо помнить, если вы рассчитываете на ввод функцией getchar() одиночного символа.
    Приведём в пример программу копирования из стандартного ввода в стандартный вывод:
    #include
    int main()
    <
    int c;
    while ((c=getchar())!=EOF)
    Putchar(c);
    return 0;
    >
    Для завершения приведенной выше программы копирования необходимо ввести с клавиатуры сигнал прерывания Ctrl+C.
    Одной из наиболее популярных операций ввода-вывода является операция ввода-вывода строки символов. В библиотеку языка Си для обмена данными через Стандартные потоки ввода-вывода включены функции ввода-вывода строк gets() и puts(), которые удобно использовать при создании диалоговых систем. Прототипы этих функций имеют следующий вид:
    char * gets (char * s); /* Функция ввода */
    int puts (char * s); /* Функция вывода */
    Обе функции имеют только один аргумент — указатель s на массив символов. Бели строка прочитана удачно, функция gets( ) возвращает адрес того массива s, в который производился ввод строки. Если произошла ошибка, то возвращается NULL.
    Функция puts() в случае успешного завершения возвращает последний выведенный символ, который всегда является символом ‘\n’. Если произошла ошибка, то возвращается EOF.

    32) Ввод и вывод данных средствами языка C++.

    В языке С имеется весьма развитая библиотека функций ввода-вывода. Однако в самом языке отсутствуют какие-либо предопределенные файловые структуры. Все данные обрабатываются как последовательность байт. Имеется три основных типа функций: потоковые, работающие с консолью и портами ввода-вывода и низкоуровневые.
    Потоковые функции.
    В потоковых функциях файлы данных рассматриваются как поток отдельных символов.
    Когда программа открывает файл для ввода вывода при помощи потоковых функций, то открытый файл связывается с некоторой переменной типа FILE (определенной в stdio.h), содержащей базовую информацию об этом файле. После открытия потока с помощью функции fopen возвращается указатель на эту переменную. Этот указатель используется для ссылки к файлу при всех последующих операциях ввода-вывода и называется указателем потока.
    Все потоковые функции обеспечивают буферизованный, форматированный или неформатированный ввод/вывод. Буферизация потока разгружает приложение. Однако следует иметь ввиду, что при аварийном завершении программы содержимое буферов вывода может быть потеряно.
    Аналогичным образом выглядят функции, работающие с консолью и портами. Они позволяют читать и писать на терминал или в порт ввода/вывода (например в порт принтера). Функции портов ввода/вывода выполняют простое побайтное считывание или запись. Функции ввода/вывода на консоль обеспечивают несколько дополнительных возможностей, например можно определить момент, когда будет введен символ с консоли и т.п.
    Для использования потоковых функций в программу должен быть включен заголовочный файл stdio.h. В нем содержатся описания прототипов функций ввода/вывода, а также — описания ряда констант. Константа EOF определяется как значение, возвращаемое при обнаружении конца файла, BUFSIZE — размер буферов потока, тип FILE определяет структуру, используемую для получения информации о потоке.
    Поток открывается с помощью функций fopen(), fdopen() или freopen(). В момент открытия задаются режим файла и способ доступа. Эти функции возвращают указатель на переменную типа FILE, например
    FILE *file1;
    file1=fopen(“input.dat”,”r”);
    открывает файл input.dat для чтения. Переменная file1 получает указатель на поток.
    Возможные типы доступа:
    “a” – запись в режиме добавления в конец файла,
    ”a+” –тоже, что и “a”, но возможно и чтение,
    ”r” – только для чтения,
    ”r+” – для чтения и записи,
    ”w” – открывается пустой файл для записи,
    ”w+” – открывается пустой файл для записи и чтения.
    Когда начинается выполнение приложения автоматически открывается следующие потоки: стандартное устройство ввода stdin, стандартное устройство вывода stdout, устройство сообщений об ошибках stderr, устройство печати stdprn и стандартное дополнительное устройство stdaux. Эти файловые указатели можно использовать во всех функциях ввода/вывода в качестве указателя на поток. Некоторые функции автоматически используют указатели на поток, например getchar() и putchar() используют stdin и stdout соответственно.
    Для закрытия потоков используются функции fclose() и fcloseall(). Если программа не закрывает поток явно, то он закрывается автоматически по ее завершению.
    Для перемещения по файлу можно использовать функции fseek(), ftell() и rewind().
    Низкоуровневый ввод и вывод.
    Функции низкоуровневого ввода-вывода не выполняю никакой буферизации и форматирования. Они непосредственно обращаются к средствам ввода/вывода операционной системы.
    При открытии файла на этом уровне возвращается описатель файла (file handle), представляющий собой целое число, используемое затем для обращения к этому файлу при дальнейших операциях. Для открытия используется функция open(), для закрытия – функция close().
    Функция read() читает данные в указанный массив, а write() – выводит данные из массива в файл, lseek() – используется для перемещения по файлу.
    Низкоуровневые функции не требуют включения заголовочного файла stdio.h, вместо него используется файл io.h.
    Низкоуровневая система ввода-вывода не вошла в стандарт ANSI C, поэтому ее не рекомендуют для дальнейшего использования.
    Ввод и вывод символов, строк, слов.
    Наиболее общими функциями являются те, которые работают с отдельными символами. Функция getc() вводит один символ из указанного файлового потока в переменную типа int:

    int ic;
    ic=getc(stdin);

    Вводится один символ из потока stdin.
    Функция putc() передает один символ в указанный файловый поток:

    Для стандартных потоков stdin и stdout можно использовать функции getchar() и putchar() соответственно:

    int ic;
    ic=getchar();
    putchar(ic);

    Функции getch() и putch() являются низкоуровневыми функциями. Обычно функция getch() используется для перехвата символа, введенного с клавиатуры сразу после его нажатия. Нажатия клавиши “Enter” не требуется.
    Для ввода/вывода текстовых строк можно использовать функции gets(), fgets(), puts(), fputs(). Функция fgets() имеет вид:
    fgets(имя_массива, размер_массива, указатель_на_поток);
    Она считывает символы в указанный массив и добавляет в конце null-символ. Считывание производится до заполнения массива, или до достижения конца файла. Символ перевода строки переписывается в массив.
    Для ввода и вывода целых чисел можно использовать функции getw() и putw(). Они работают с двоичными файлами.
    Форматированный ввод и вывод.
    Богатый ассортимент средств управления форматом позволяет легко создавать таблицы, графики или отчеты. Функциями, выполняющими этот вывод являются printf() — для стандартного потока вывода stdout, и fprintf() – для любого потока. Функция fprintf() имеет вид:
    fprintf(поток_вывода, “формат”, перем_1, перем_2,…);
    Работает она аналогично printf() и выводит данные в указанный поток вывода.
    Для форматированного ввода используются функции scanf() и fscanf().
    Для преобразования текстовой строки можно использовать sscanf(). Она работает аналогично fscanf(), но данные берет из сроки, а не из файла.
    Потоки cin, cout, cerr.
    В языке С++ имеется другая библиотека ввода/вывода, определяемая заголовочным файлом iostream.h. Ввод/вывод в ней определяется набором специальных классов. Аналогами потоков stdin, stdout и stderr являются cin, cout и cerr. Они открываются автоматически при запуске программы.
    Операции выделения >> и вставки > выделяет данные из входного потока и помещает в указанные переменные, а операция >ivalue>>dvalue>>cvalue;

    Аналогично для вывода:

    printf(“Integer:%d double: %lf”,ivalue,dvalue);
    cout
    #include
    using namespace std;

    int main()
    <
    int num[10];

    for(int i=0; i
    #include
    #include
    using namespace std;

    int main()
    <
    int num[10];
    int i, j, k;
    int size = 10;

    // Меняем элементы местами.
    i = num[k-1];
    num[k-1] = num[k];
    num[k] = i;
    >
    >
    >
    // Конец сортировки.

    // Отображаем отсортированный массив.
    cout
    #include
    using namespace std;

    Форматный ввод (scanf)

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

    int scanf(char *format, …)

    Функция scanf читает символы из стандартного входного потока, интерпретирует их согласно спецификациям строки format и рассылает результаты в свои остальные аргументы. Аргумент format мы опишем позже; другие аргументы, каждый из которых должен быть указателем, определяют, где будут запоминаться должным образом преобразованные данные. Как и для printf , в этом параграфе дается сводка наиболее полезных, но отнюдь не всех возможностей данной функции.

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

    Существует также функция sscanf , которая читает из строки (а не из стандартного ввода).

    int sscanf(char *string, char *format, arg1, arg2,…)

    Функция sscanf просматривает строку string согласно формату format и рассылает полученные значения в arg1 , arg2 и т. д. Последние должны быть указателями.

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

    • Пробелы или табуляции, которые игнорируются.

    • Обычные символы (исключая %), которые, как ожидается, совпадут с очередными символами, отличными от символов-разделителей входного потока.

    • Спецификации преобразования, каждая из которых начинается со знака % и завершается символом-спецификатором типа преобразования. В промежутке между этими двумя символами в любой спецификации могут располагаться, причем в том порядке, как они здесь указаны: знак * (признак подавления присваивания); число, определяющее ширину поля; буква h, l или L, указывающая на размер получаемого значения; и символ преобразования (o, d, x).

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

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

    Перед символами-спецификаторами d , l , o , u и x может стоять буква h , указывающая на то, что соответствующий аргумент должен иметь тип short * (а не int * ),или l (латинская ell), указывающая на тип long * . Аналогично, перед символами-спецификаторами e , f и g может стоять буква l , указывающая, что тип аргумента — double * (а не float * ).

    Таблица 7.2 Основные преобразования scanf

    Символ Вводимые данные; тип аргумента
    d десятичное целое: int *
    i целое: int * . Целое может быть восьмеричным (с 0 слева) или шестнадцатеричным (с 0x или 0X слева)
    o восьмеричное целое (с нулем слева или без него); int *
    u беззнаковое десятичное целое; unsigned int *
    x шестнадцатеричное целое (с 0x или 0X слева или без них); int *
    c символы; char * . Следующие символы ввода (по умолчанию один) размещаются в указанном месте. Обычный пропуск символов- разделителей подавляется; чтобы прочесть очередной символ, отличный от символа-разделителя, используйте %1s
    s Строка символов(без обрамляющих кавычек); char * , указывающая на массив символов, достаточный для строки и завершающего символа ‘\0’, который будет добавлен
    e , f , g число с плавающей точкой, возможно, со знаком; обязательно присутствие либо десятичной точки, либо экспоненциальной части, а возможно, и обеих вместе; float *
    % сам знак % , никакое присваивание не выполняется

    Чтобы построить первый пример, обратимся к программе калькулятора из главы 4, в которой организуем ввод с помощью функции scanf :

    По мотивам «Обрабатываем строки на Arduino»

    Прочитал сегодня пост на Geektimes, и хочу поделиться своим опытом. Не хочу обсуждать плюсы и минусы Arduino, но условия проекта, в котором применил нижеописанное — должно быть сделано под неё. Суть — нужно предоставить пользователю терминал для управления устройством. Соотвественно, не малая часть работы является работой со строками. Применять или нет предложенное решение — пусть каждый решает сам.

    От класса String решил отказаться: ошибка линкера. Она у меня появлялась исключительно при попытке использовать класс String.

    Что требовалось? Вывод информации и обработка введённых пользователем строк. Например:

    Ethernet controller — ok
    STATIC mode
    >time
    2015-11-16 22:35:27

    Собственно, надо сравнить стоки. Нет, сначала надо разбить текст на фрагменты разделителем ( например пробел), но потом всё равно сравнить строки. Поскольку команд было «раз, два — и обчёлся», то разбивку текста на фрагменты убрал. Из-за указанной выше ошибки класс String использовать не получалось, то как можно по другому? Arduino использует библиотеку AVR-libc, то резонно в первую очередь обратиться к ней.
    Что имеем?

    1. stdlib.h — функции взаимного преобразования чисел и строк (в обе стороны).
    2. string.h — функции работы со строками. Основной наш интерес.
    3. stdio.h — функции стандартного ввода-вывода.

    Этим не ограничивается функционал. Упомянуто то, что связано с задачей.

    Итак, №1 рекомендую для ознакомления — вдруг пригодится отдельно где-то. Сам по себе используется только для работы string.h.

    №2 — используем функции memset для заполнения или очистки буфера, memcmp — для сравнения. strcmp не использую, так как нужно явно ограничивать длину сравниваемого фрагмента. №3 — для форматного чтения и вывода: sprintf, sprint_P, sscanf, sscanf_P. Функции с суффиксом _P отличаются тем, что строку форматирования берут из памяти программ PROGMEM, он же макрос F() в библиотеках Arduino.

    У меня сравнение строк выглядит так:

    Пожалуй, стоит оговориться, что сравниваются начала строк. Для поиска фрагментов можно использовать memmem.

    То есть поиск команды может выглядеть так:

    Строки «собираю» следующим образом: читаю байты с порта, пока не превышена допустимая длинна строки или пока не встречен один из символов перевода строки \r или \n.

    Лучше доработать бы… Пока как есть. Вызывается всё время в основном кольце. Если нет работы — максимально быстро на выход, возвращаем false. Если набрали новую строку — true.

    Ещё очень полезен форматный ввод-вывод. Например, разбор строки с ведённой датой и временем выглядит так:
    Получение строки для вывода IP:

    Подробней о строке формата можно почитать, например, здесь scanf и здесь (printf).

    Вот собственно и всё. Надеюсь, кому-то данный материал поможет «отвязаться» от Arduino или просто лучше и за меньшее время писать свои программы. Но более типичная ситуация — обойти ограничения Wiring.

    Автору первоначальной статьи спасибо, хотя бы за то, что заставил меня набрать этот материал.

    Scanf не превышать переполнение буфера

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

    Я использую scanf и сделал так:

    Тем не менее, я знаю, что я защищен, если пользователь вводит более 30. Однако, если пользователь вводит более 30, будет ли нулевой буфер завершен?

    scanf() с помощью спецификатора преобразования «% s» добавляет в буфер завершающий нулевой символ.

    Но, вы запрашиваете 30 символов, что на самом деле означает 31, и у вас есть только место для 30. Вы должны использовать максимальную ширину поля 29.

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

    В руководстве scanf :

    s Соответствует последовательности символы небелого пробела; следующий указатель должен быть указателем на char, и массив должен быть достаточно большим, чтобы принять всю последовательность и завершающий символ NUL. строка ввода останавливается в белом пространстве или при максимальной ширине поля, в зависимости от того, что наступит раньше.

    Вы вызываете UB. Попробуйте:

    Чтобы игнорировать что-либо за пределами BUFSIZE , символы подавляют назначение:

    Вы также должны проверить, ввел ли пользователь return/newline и завершает scanf , если у него есть:

    и рекомендуется проверить возвращаемые значения, поэтому:

    и, наконец, проверьте, осталось ли что-либо (например, новая строка и его потребление):

    Илон Маск рекомендует:  Тег var
  • Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL