STDIO - Standard Input/Output Library
По мотивам А.П.Полищук, С.А.Семериков "Системное программирование в UNIX средствами Free Pascal"
StdIO - стандартная библиотека ввода-вывода, основная цель которой состоит в предоставлении эффективных, развитых и переносимых средств доступа к файлам. Библиотека предлагает набор функций для форматированного ввода, вывода и преобразования данных. Процедуры стандартного ввода-вывода являются переносимыми, так как они не привязаны к особым свойствам той или иной платформы, а являются частью группы стандартов POSIX. Любой полноценный компилятор языка С предоставляет доступ к стандартной библиотеке ввода-вывода независимо от используемой операционной системы. Компилятор Free Pascal также позволяет использовать эту библиотеку в рамках портирования библиотечных функций языка Си (модуль LibC).
Процедуры буферизованного ввода-вывода из библиотеки STDIO идентифицируют открытые файлы при помощи указателя на структуру типа FILE, содержащую описательную информацию о файле (дескриптор файла). В Free Pascal указатель на FILE объявляется типом PFILE. Переменные этого типа имеют назначение, аналогичное назначению переменных файлового типа в традиционном Паскале. Чтобы начать работу с файлом, его надо открыть при помощи функции fopen. Первым параметром этой функции указывается имя файла, вторым - строка, обозначающая режим доступа:
- 'r' - файл открывается только для чтения, 'r+' - для чтения и записи (если файл не существует, функция завершается неудачей);
- 'w' - файл открывается только для записи, 'w+' - для чтения и записи (если файл не существует - он создается, если файл существует - он усекается до нулевой длины);
- 'a' - файл открывается только для записи, при этом данные будут добавляться в конец файла, 'a+' - для чтения и записи (если файл не существует - он создается).
uses StdIO; function fopen(filename:pchar; mode:pchar):pfile; function fclose(fp:pfile):integer; function fseek(fp:pfile; offset:longint; direction:integer):longint; procedure rewind(fp:pfile); function ftell(fp:pfile):longint; | #include <stdio.h> FILE *fopen(const char *filename, const char *mode); int fclose(FILE *fp); int fseek(FILE *fp, long offset, int direction); void rewind(FILE *fp); long ftell(FILE *fp); |
При запуске любой программы ей доступны три открытых файла: стандартный ввод, стандартный вывод и диагностический вывод (вывод ошибок). Обычно все три файла связаны с терминалом (экран+клавиатура), с которого запущена программа. Однако пользователь может и перенаправить любой из этих файлов при помощи средств командного интерпретатора. Программе не требуется специально открывать эти файлы, библиотека STDIO предоставляет к ним доступ через предопределенные указатели: stdin, stdout, stderr.
Для побайтного (посимвольного) ввода-вывода используются функции
putc и getc. Функция getc возвращает
очередной байт (символ) из указанного входного потока. Функция putc
помещает байт (символ), переданный первым параметром, в выходной поток,
указанный вторым параметром. В случае ошибки эти функции возвращают значение
EOF (-1). Функция getc также возвращает EOF при достижении конца
файла. Кроме того, конец файла можно обнаружить при помощи функции feof.
Эта функция возвращает ненулевое значение, если в результате последней операции
чтения был достигнут конец файла (т.е. для того, чтобы feof вернула
корректный результат, надо попытаться прочитать или записать файл).
function getc(fp:pfile):integer; function putc(c:integer; fp:pfile):integer; function feof(fp:pfile):integer; | int getc(FILE *fp); int putc(int c, FILE *fp); int feof(FILE *fp); |
В приведенном ниже примере программа копирует символы, вводимые со
стандартного ввода (клавиатуры), в указанный файл (файл задается первым
параметром командной строки программы):
program ex1; uses StdIO; var fout:pfile; c:integer; filename:string; begin if ParamCount<1 then begin fprintf(stderr,'Missing command-line parameter'#10,[]); halt(1); end; filename:=ParamStr(1); fout:=fopen(@filename[1],'w'); if fout=nil then begin fprintf(stderr,'Failed to open file'#10,[]); halt(1); end; repeat c:=getc(stdin); if c=EOF then break; putc(c,fout); until false; fclose(fout); end. | #include <stdio.h> FILE *fout; int c; char *filename; int main(int argc, char** argv) { if (argc<2) { fprintf(stderr,"Missing command-line parameter\n"); return 1; } filename=argv[1]; fout=fopen(filename,"w"); if (!fout) { fprintf(stderr,"Failed to open file\n"); return 1; } do { c=getc(stdin); if (c==EOF) break; putc(c,fout); } while(1); fclose(fout); return 0; } |
Т.к. ввод и вывод через потоки stdin и stdout используется очень часто,
для удобства определены две дополнительных функции побайтного ввода-вывода
специально для работы с этими файлами: getchar соответствует
getc(stdin), а putchar соответствует
putchar(c,stdout).
function getchar:integer; function putchar(c:integer):integer; | int getchar(void); int putchar(int c); |
В Си эти функции, как правило, реализованы в виде макроопределений. |
Описываемые здесь функции ввода-вывода относятся к так называемому
буферизированному вводу-выводу. Это означает, что с каждым файлом связана
некоторая область данных, называемая буфером. Фактическое обращение к носителю
для чтения или записи происходит блоками большого размера для обеспечения
эффективности. Так, например, если программа записывает данные в файловый поток
побайтно, то на самом деле данные попадают на диск только тогда, когда
заполнен буфер записи (или файл закрывается).
Выполнение функции fflush приводит к сбросу на диск содержимого
буфера вывода, связанного с указанным потоком. Другими словами, данные из
буфера записываются в файл немедленно, независимо от того, заполнен буфер или
нет. Это гарантирует,что содержимое файла на диске будет соответствовать тому,
как он выглядит с точки зрения процесса. (Процесс считает, что данные записаны
в файл с того момента, как они оказываются в буфере, поскольку механизм
буферизации прозрачен.) Любые данные из буфера ввода этим вызовом
предусмотрительно отбрасываются. Поток остается открытым после завершения
процедуры fflush. Функция fflush возвращает EOF в случае ошибки и нулевое значение - в случае успеха.
function fflush(fp:pfile):integer; | int fflush(FILE *fp); |
Использование fflush наиболее актуально при работе с текстовыми
файлами, т.к. механизм буферизации откладывает запись в такой файл до получения
символа конца строки (или до достижения конца буфера). Если приведенный выше
пример модифицировать для копирования одного файла в другой, причем снабдить
его прогрессом копирования (например, через каждые 1000 байт выводить символ *),
то использование fflush становится обязательным, т.к. в противном
случае звездочки прогресса будут выведены не по ходу копирования, а когда
программа завершится или будет заполнен буфер.
program ex1; uses StdIO; var fin,fout:pfile; c:integer; filename1,filename2:string; const count:longint=0; begin if ParamCount<2 then begin fprintf(stderr,'Missing command-line parameters'#10,[]); halt(1); end; filename1:=ParamStr(1); fin:=fopen(@filename1[1],'r'); filename2:=ParamStr(2); fout:=fopen(@filename2[1],'w'); if (fin=nil) or (fout=nil) then begin fprintf(stderr,'Failed to open files'#10,[]); halt(1); end; repeat c:=getc(fin); if c=EOF then break; putc(c,fout); inc(count); if count mod 1000 = 0 then begin putchar(ord('*')); fflush(stdout); end; until false; fclose(fin); fclose(fout); end. | #include <stdio.h> #include <unistd.h> FILE *fin,*fout; int c; char *filename1,*filename2; long count=0; int main(int argc, char** argv) { if (argc<3) { fprintf(stderr,"Missing command-line parameters\n"); return 1; } filename1=argv[1]; fin=fopen(filename1,"r"); filename2=argv[2]; fout=fopen(filename2,"w"); if (!fin || !fout) { fprintf(stderr,"Failed to open files\n"); return 1; } do { c=getc(fin); if (c==EOF) break; putc(c,fout); usleep(1000); if (++count % 1000 == 0) { putchar('*'); fflush(stdout); } } while(1); fclose(fin); fclose(fout); return 0; } |
Существует также набор простых процедур для ввода и вывода строк (под
которыми понимается последовательность символов, завершаемая символом перевода
строки). Эти процедуры удобно использовать в интерактивных программах,
которые выполняют чтение с клавиатуры и вывод на экран терминала.
function gets(buf:pchar):pchar; function fgets(buf:pchar; nsize:integer; fp:pfile):pchar; function puts(str:pchar):integer; function fputs(str:pchar; fp:pfile):integer; | char *gets(char *buf); char *fgets(char *buf, int nsize, FILE *fp); int puts(const char *str); int fputs(const char *str, FILE *fp); |
Основные функции для ввода строк называются gets и fgets. Функция gets считывает последовательность символов из потока стандартного ввода (stdin), помещая все символы в буфер, на который указывает аргумент buf. Символы считываются до тех пор, пока не встретится символ перевода строки или конца файла. Символ перевода строки отбрасывается, а вместо него в буфер buf помещается нулевой символ, образуя завершенную строку. В случае возникновения ошибки или при достижении конца файла возвращается значение nil. Функция fgets является обобщенной версией функции gets. Она считывает символы из указанного потока в буфер buf до тех пор, пока не будет считано nsize-1 символов или не встретится раньше символ перевода строки или не будет достигнут конец файла. В функции fgets символы перевода строки не отбрасываются, а помещаются в конец буфера. Обе функции возвращают указатель на буфер buf в случае успеха и nil - в противном случае. Предпочтительным является использование функции fgets даже для файла стандартного ввода (stdin), т.к. при использовании функции gets не существует способа указать размер буфера для строки. Использование функции gets может привести к ошибкам типа "переполнение буфера".
Для вывода строк используются функции puts и fputs. Функция puts записывает все символы (кроме завершающего нулевого символа) из строки str на стандартный вывод (stdout). Для обеспечения совместимости со старыми версиями системы функция puts добавляет в конце символ перевода строки. Функция fputs записывает строку str в указанный поток (функция fputs не добавляет перевод строки, если он явно не указан в строке). Обе функции возвращают в случае ошибки значение EOF.
Приведенный ниже фрагмент программы содержит описание функции, возвращающей
количество строк в указанном файле, при условии, что строки файла содержат
не более 255 символов.
function fcountlines(filename:pchar):integer; var f:pfile; buf:array [0..255] of char; const cnt:integer=0; begin f:=fopen(filename,'r'); if f=nil then begin fcountlines:=-1; exit; end; while fgets(buf,256,f)<>nil do inc(cnt); fclose(f); fcountlines:=cnt; end; | int fcountlines(const char *filename) { FILE *f; char buf[256]; int cnt=0; f=fopen(filename,"r"); if (!f) return -1; while (fgets(buf,256,f)) cnt++; fclose(f); return cnt; } |
Ввод и вывод произвольных бинарных (нетекстовых) данных осуществляется
функциями fread и fwrite. Функция fread
считывает nitems объектов данных из входного файла. Считанные байты будут
помещены в массив buf. Каждый считанный объект представляется
последовательностью байтов длины size. Возвращаемое значение дает число успешно
считанных объектов. Функция fwrite записывает данные из массива buf
в указанный поток. Массив buf содержит nitems объектов, размер которых равен
size. Возвращаемое функцией значение дает число успешно записанных объектов.
Эти функции обычно используются для чтения и записи содержимого произвольных
структур данных. При этом параметр size часто содержит конструкцию sizeof(...),
которая возвращает размер структуры в байтах.
function fread(buf:pointer; size, nitems:longint; fp:pfile):longint; function fwrite(buf:pointer; size, nitems:longint; fp:pfile):longint; |
size_t fread(void *buf, size_t size, size_t nitems, FILE *fp); size_t fwrite(const void *buf, size_t size, size_t nitems, FILE *fp); |
Библиотека STDIO предоставляет средства форматированного вывода
(преобразования информации в текстовый вид). Функции printf,
fprintf и sprintf преобразуют значения аргументов args
в строку в соответствии с параметром fmt (формат). Тип и количество аргументов
определяются строкой формата. Функция printf выдает сформированную
строку на стандартный вывод, fprintf - в указанный файловый поток,
а sprintf - в строковой буфер str.
function printf(fmt:pchar; args:array of const):integer; function fprintf(fp:pfile; fmt:pchar; args:array of const):integer; function sprintf(str:pchar; fmt:pchar; args:array of const):integer; |
int printf(const char *fmt, ...); int fprintf(FILE *fp, const char *fmt, ...); int sprintf(char *str, const char *fmt, ...); |
Примеры вызова функции printf:
printf('Hello, world!'#10, []); printf('hex=%x, oct=%o, dec=%d'#10, [i, i, i]); | printf("Hello, world!\n"); printf("hex=%x, oct=%o, dec=%d\n",i,i,i); |
Строка формата fmt состоит из обычных символов, которые копируются без изменений, и набора спецификаций формата. Это подстроки, которые начинаются с символа % (если нужно напечатать сам символ процента, то он записывается два раза: %%). Для каждого из аргументов args должна быть задана своя спецификация формата, которая указывает тип соответствующего аргумента и способ его преобразования в текстовый вид. Первый пример просто выводит строку 'Hello, world!' и переводит курсор на новую строку (символ с кодом #10 - перевод строки). Поскольку в этом примере нет других аргументов, кроме строки формата, эта строка не содержит ни одной спецификации формата. Второй пример выводит значение целочисленной переменой i в шестнадцатеричном, восьмеричном и десятичном виде. Поскольку значение переменной i используется 3 раза, строка fmt содержит три спецификации формата: '%x', '%o' и '%d'. Если, например, переменная i будет содержать значение 1710, то на стандартный вывод будет выдана строка:
hex=11, oct=21, dec=17
Спецификация формата может содержать от 1 до 5 полей:
%[FLAGS][WIDTH][.PREC][SIZE]TYPEОбязательным является поле TYPE, определяющее тип и способ преобразования соответствующего аргумента. Поле TYPE кодируется одним из следующих символов:
- c - значение соответствующего аргумента интерпретируется как ASCII код символа, в результате преобразования получается символ с указанным кодом;
- s - соответствующий аргумент - указатель на строку с завершающим нулем (тип pchar), в результате преобразования - содержимое строки;
- d или i - соответствующий аргумент - целое число со знаком, результат преобразования - десятичное представление данного числа;
- u - соответствующий аргумент - целое число без знака, результат преобразования - десятичное представление данного числа;
- o - соответствующий аргумент - целое число, результат преобразования - восмеричное представление данного числа;
- x или X - соответствующий аргумент - целое число, результат преобразования - шестнадцатеричное представление данного числа (если код типа 'x', шестнадцатеричные цифры a, b, ..., f выводятся в нижнем регистре; если код типа 'X' - шестнадцатеричные цифры A, B, ..., F выводятся в верхнем регистре);
- f или F - значение соответствующего аргумента интерпретируется как вещественное число и преобразуется в вид [-]9999.9999;
- e или E - значение соответствующего аргумента интерпретируется как вещественное число и преобразуется в экспоненциальный вид [-]9.99999e±999;
- g или G - значение соответствующего аргумента интерпретируется как вещественное число и преобразуется в наиболее удобный для представления вид (f или e);
- p - соответствующий аргумент интерпретируется как указатель и преобразуется в текстовый вид, принятый для представления указателей;
- n - соответствующий аргумент считается указателем на целое число; данная спецификация не формирует никакой последовательности символов в результирующей строке, а по указанному в соответствующем аргументе адресу записывается количество символов, сформированных перед данной спецификацией в результирующей строке.
Остальные поля необязательны и могут остутствовать. Так, например, поле SIZE уточняет тип аргумента и может принимать следующее значение:
- h - применяется к типам d, i, u, o, x, X и определяет, что соответствующий аргумент 16-битный (short);
- l - применяется к типам d, i, u, o, x, X и определяет, что соответствующий аргумент 32-битный (long);
- L - применяется к типам f, F, e, E, g, G и определяет, что соответствующий аргумент является вещественным числом расширенной точности (extended или long double).
Если используется поле PREC, оно должно предваряться точкой. Для целых чисел это поле задает минимальное количество выводимых цифр (при необходимости слева будут добавлены нули). Для вещественных форматов (f, e, E) это поле задает количество цифр, выводимых после десятичной точки, для g и G - количество значимых цифр.
Поле WIDTH определяет минимальное количество выводимых символов. При необходимости результат преобразования будет дополнен пробелами.
Поля WIDTH и PREC вместо числа могут содержать '*', тогда в аргументах функции в соответствующем месте должен присутствовать целочисленный аргумент, значение которого и будет использоваться как параметр преобразования.
Поле FLAGS определяет дополнительные параметры преобразования (можно использовать несколько флагов одновременно):
- - - результат преобразования выравнивается по левому краю, справа при необходимости добавляются пробелы (если этот флаг отсутствует, результат выравнивается по правому краю, а пробелы добавляются слева);
- + - результат преобразования численного аргумента всегда выводится со знаком: с плюсом или минусом (если этот флаг отсутствует, со знаком выводятся только отрицательные числа);
- 0 - для численного аргумент определяет, что в качестве символов, обеспечивающих выравнивание, будут использоваться нули, а не пробелы.
- # - для типа o перед числом будет добавляться дополнительный ноль, для типов x или X перед числом будут добавляться 0x или 0X, для вещественных преобразований результат всегда будет содержать десятичную точку.
Примеры спецификаций:
Спецификация | Аргументы | Результат |
---|---|---|
%04X | i:integer=10 | 000A |
j:integer=1000 | 03E8 | |
%.3f | x:real=3.141592 | 3.142 |
y:real=0.5 | 0.500 | |
%+8.3G | x:real=3.141592 | +3.14 |
y:real=-0.00005678 | -5.68E-05 | |
%.*Le | n:integer=2; x:extended=0.5 | 5.00e-01 |
m:integer=3; y:extended=1 | 1.000e+00 | |
%-*c%s | n:integer=3; c:char='A'; s:pchar='qwerty' | A qwerty |
m:integer=2; a:integer=64; s:pchar='mail.ru' | @ mail.ru |
Еще один комплексный пример:
program ex2; uses StdIO; var m:integer; const n:integer=3; x:real=1; begin printf('x=%.*E%n',[n,x,@m]); printf(' [%d]'#10,[m]); end. | #include <stdio.h> int n=3,m; float x=1; int main() { printf("x=%.*E%n",n,x,&m); printf(" [%d]\n",m); } |
В результате на стандартный вывод будет выдана строка:
x=1.000E+00 [11]В первом вызове printf использовано две спецификации вывода (символы 'x=' выдаются "как есть"). Первая спецификация вывода требует два аргумента: целое число, определяющее количество цифр после запятой, и вещественное число, которое, собственно, и выдается на стандартный вывод в экспоненциальном формате. Вторая спецификация не генерирует никаких символов, а лишь помещает в целочисленную переменную, адрес которой передается очередным аргументом функции printf, количество сгенерированных к этому моменту символов. Второй вызов printf содержит лишь одну спецификацию вывода - вывод целого числа, остальные символы строки формата (пробел, квадратные скобки и перевод строки) выводятся "как есть".
Для преобразования текстовой информации в бинарный вид используются функции
форматированного ввода: scanf, fscanf и sscanf.
Исходная текстовая информация преобразуется в соответствии со строкой формата
fmt, и полученные данные размещаются по переменным, адреса которых указаны в
качестве аргументов args. Тип и количество аргументов определяются строкой
формата. Функция scanf считывает исходную информацию со стандартного
ввода, fscanf - из указанного файлового потока, а sscanf -
из строкового буфера str.
function scanf(fmt:pchar; args:array of const):integer; function fscanf(fp:pfile; fmt:pchar; args:array of const):integer; function sscanf(str:pchar; fmt:pchar; args:array of const):integer; |
int scanf(const char *fmt, ...); int fscanf(FILE *fp, const char *fmt, ...); int sscanf(const char *str, const char *fmt, ...); |
Например, следующий оператор считывает очередное целое число из стандартного ввода:
var i:integer; (* ... *) scanf('%d',[@i]); | int i; /* ... */ scanf("%d",&i); |
Важно, что функции scanf передается адрес переменной i. Это необходимо для совместимости с языком Си, в котором параметры могут передаваться только по значению, а чтобы функция scanf изменяла переменную, которая находится в вызывающей процедуре, следует передать указатель, содержащий адрес этой переменной. Можно очень легко забыть про символ @, что приведет к ошибке записи в память. Нельзя также передавать неинициализированный указатель. Новичкам также приходится бороться с искушением помещать знак @ перед всеми указателями, такими как имена символьных массивов (pchar).
Строка формата fmt может содержать:
- пробельные символы (пробелы, символы табуляции, перевода строки и страницы). Обычно они соответствуют любым пробельным символам с текущей позиции во входном потоке до первого непробельного символа;
- непробельные символы. Они должны точно совпадать с соответствующими символами во входном потоке;
- спецификации формата в виде:
%[*][WIDTH][SIZE]TYPE
Функции форматированного ввода считывают указанные в строке формата пробельные и непробельные символы, но не сохраняют их. Специфкации формата сообщают функциям scanf о необходимости считать и преобразовать очередные символы из входного потока в значения определенного типа и сохранить эти значения по адресам, задаваемым соответствующими аргументам args. Тип значений определяется по полю TYPE спецификации формата:
- c - считывается очередной любой символ (это может быть также пробельный символ), соответствующий аргумент - указатель на символ или на короткое целое (char*, в FreePascal: ^char, ^byte, pchar);
- s - считывается последовательность непробельных символов (с текущей позиции до ближайшего пробельного или конца последовательности), соответствующий аргумент - указатель на строковой буфер, в который помещается строка с завершающим нулем (char*, в FreePascal: pchar, array [0..N] of char);
- [шаблон] - считывается непустая последовательность символов, подходящих заданному шаблону, соответствующий аргумент - указатель на строковой буфер, в который помещается строка с завершающим нулем;
- d, i, o, x, X, u - считывается целое число со знаком (u - без знака) в десятичном, восьмеричном или шестнадцатеричном виде, соответствующий аргумент - указатель на целое (int*, в FreePascal: ^integer);
- e, f, g - считывается вещественное число, соответствующий аргумент - указатель на вещественное число одинарной точности (float*, в FreePascal: ^single);
- n - аргумент, соответствующий этой спецификации, должен быть указателем на целое, в него записывается текущий номер позиции (после ввода), входной поток при этом никак не затрагивается.
Поле SIZE уточняет тип результата преобразования:
- hh применяется к типам d, i, o, x, u, n и сообщает, что результат преобразования - 8-битное целое число (byte);
- h применяется к типам d, i, o, x, u, n и сообщает, что результат преобразования - 16-битное целое число (short);
- l применяется к типам d, i, o, x, u, n и сообщает, что результат преобразования - 32-битное целое число (long);
- l применяется к типам e, f, g и сообщает, что результат преобразования - вещественное число двойной точности (double);
- L применяется к типам d, i, o, x, u, n и сообщает, что результат преобразования - 64-битное целое число (long long, в Free Pascal - типы Int64 или QWord);
- L применяется к типам e, f, g и сообщает, что результат преобразования - вещественное число расширенной точности (long double, extended).
Следует отметить, что библиотека STDIO в FreePascal работает только с вещественными числами по стандарту IEEE-754, поэтому платформенно-зависимый тип REAL не поддерживается - необходимо использовать один из стандартных вещественных типов: SINGLE, DOUBLE, EXTENDED.
Поле WIDTH определяет максимальное количество символов входного потока, которое может быть использовано для преобразования по данной спецификации.
Если спецификация содержит знак '*', это означает, что данной спецификации не требуется никакой аргумент, а результат преобразования нигде не будет сохранен.
Спецификация %[шаблон] определяет набор символов. До тех пор, пока очередной символ входного потока есть среди перечисленных в этом наборе, преобразование входного потока происходит по данной спецификации. Набор может задаваться простым перечислением символов:
%[abcd]либо с использованием диапазонов:
%[a-d]либо с использованием отрицания:
%[^abc]Последний шаблон соответствует всем символам, кроме 'a', 'b' и 'c'. Если требуется включить в шаблон символ '-', он должен стоять первым или последним символом шаблона. Если требуется включить в шаблон символ '^', он должен стоять последним символом шаблона.
Примеры спецификаций:
Спецификация | Входной поток | Аргументы и результат |
---|---|---|
%d %2d %f %s | 11 12 34.07 keith ben |
i:integer=11; j:integer=12; x:single=34.07; s1:array [0..N] of char=('k','e','i','t','h',#0) |
123 456 4.7 qwerty |
i:integer=123; j:integer=45; x:single=6; s1:array [0..N] of char=('4','.','7',#0) | |
%*d%1s%1c | 123 456 | s:array [0..N] of char=('4',#0); c:char='5'; |
%*d%1c%1s | c:char=' '; s:array [0..N] of char=('4',#0); | |
%[A-Za-z]%n %s | My1stComp |
s1:array [0..N] of char=('M','y',#0); n:integer=2; s2:array [0..N] of char=('1','s','t','C','o','m','p',#0) | mycomp the1st |
s1:array [0..N] of char=('m','y','c','o','m','p',#0); n:integer=6; s2:array [0..N] of char=('t','h','e','1','s','t',#0) |
Функции scanf возвращают количество сохраненных значений (количество обработанных спецификаций, кроме %*TYPE). Функция может завершить свою работу до того, как будет разобрана вся строка формата, если во входном потоке очередной непробельный символ будет конфликтовать с очередной спецификацией формата либо входной поток завершен (достигнут конец файла). Опытные программисты обычно не используют функцию fscanf (scanf) для ввода данных, кроме случаев простого интерактивного ввода. Вместо функции fscanf (scanf) предлагается использовать комбинацию вызовов fgets (gets) и sscanf. Недостаток функции fscanf (scanf) в данном случае состоит в том, что при случайном нарушении формата строки вводимых данных эта функция может перейти к чтению следующей строки, поскольку функция fscanf (scanf) не отличает символ окончания строки от других разделителей полей. В такой ситуации сложно обработать ошибку входных данных корректно, а кроме того, ввод следующей строки тоже будет нарушен.
Еще один комплексный пример:
program ex3; uses StdIO, Strings; Type rc_list=^rc_entry; rc_entry=record key:pchar; val:single; next:rc_list; end; function parse_rc_file(filename:pchar):rc_list; var frc:pfile; buf,key:array [0..255] of char; const lrc:rc_list=nil; p:rc_list=nil; begin frc:=fopen(filename,'r'); if frc=nil then begin parse_rc_file:=nil; exit; end; while fgets(buf,255,frc)<>nil do begin p:=lrc; new(lrc); if lrc=nil then begin parse_rc_file:=nil; exit; end; lrc^.next:=p; if sscanf(buf,' %[A-Za-z0-9] = %g',[key,@lrc^.val])=2 then begin lrc^.key:=stralloc(strlen(key)+1); strcopy(lrc^.key,key); end else begin (* ошибка формата - пропускаем строку *) fprintf(stderr,'Incorrect syntax: %s'#10,[buf]); dispose(lrc); lrc:=p; end; end; fclose(frc); parse_rc_file:=lrc; end; var rlc:rc_list; filename:string; begin if ParamCount<1 then begin fprintf(stderr,'Missing cmd-line params'#10,[]); halt(1); end; filename:=ParamStr(1); rlc:=parse_rc_file(@filename[1]); while rlc<>nil do begin printf('[%s]=%g'#10,[rlc^.key,rlc^.val]); rlc:=rlc^.next; end; end. | #include <stdio.h> #include <malloc.h> #include <string.h> struct rc_entry { char *key; float val; struct rc_entry *next; }; typedef struct rc_entry *rc_list; rc_list parse_rc_file(const char* filename) { FILE *frc; char buf[255],key[255]; rc_list lrc=NULL,p; frc=fopen(filename,"r"); if (!frc) return NULL; while (fgets(buf,255,frc)) { p=lrc; lrc=malloc(sizeof(struct rc_entry)); if (!lrc) return NULL; lrc->next=p; if (sscanf(buf," %[A-Za-z0-9] = %g",key,&lrc->val)==2) { lrc->key=malloc(strlen(key)+1); strcpy(lrc->key,key); } else { /* ошибка формата - пропускаем строку */ fprintf(stderr,"Incorrect syntax: %s\n",buf); free(lrc); lrc=p; } } fclose(frc); return lrc; } int main(int argc, char **argv) { rc_list rlc; char *filename; if (argc<2) { fprintf(stderr,"Missing cmd-line params\n"); return 1; } filename=argv[1]; rlc=parse_rc_file(filename); while (rlc) { printf("[%s]=%g\n",rlc->key,rlc->val); rlc=rlc->next; } return 0; } |
Приведенная программа содержит функцию parse_rc_file, которая обрабатывает передаваемый ей текстовый файл, разбирая его по строкам. Такой текстовый файл может содержать настройки программы в виде:
параметр=значениеФункция parse_rc_file считает, что имена параметров задаются алфавитно-цифровыми идентификаторами, а их значения - вещественные числа. В результате функция возвращает список параметров (указатель на структуру rc_entry) или nil в случае ошибки. Функция корректно обрабатывает любые "лишние" пробелы благодаря использованию sscanf. В случае обнаружения в файле строки, не соответствующей описанному формату, она пропускается, а в стандартный файловый поток диагностических сообщений выдается предупреждение. Основная часть программы в данном примере лишь выводит содержимое получившегося списка на стандартный вывод.