Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Код Положение в файле




0 начало файла

1 текущая позиция

2 конец файла

 

Функция fseek() возвращает 0, если все хорошо, и -1, если есть ошибка, например попытка перемещаться за границы файла.

Теперь мы можем разъяснить наш маленький цикл:

 

while (fseek(fp, offset ++, 0) == 0)

putchar(getc(fp));

 

Поскольку переменная offset инициализирована нулем, при первом прохождении через цикл мы имеем выражение

 

fseek(fp, 0L, 0)

 

означающее, что мы идем в файл, на который ссылается указатель fp, и находим байт, отстоящий на 0 байт от начала, т. е. первый байт. Затем функция putchar() печатает содержимое этого байта. При следующем прохождении через цикл переменная offset увеличивается до 1L, печатается следующий байт. По существу, переменная offset действует подобно индексу для элементов файла. Процесс продолжается до тех пор, пока offset не попытается попасть в fseek() после конца файла. В этом случае возвращает значение — 1 и цикл прекращается.

Этот последний пример чисто учебный. Нам не нужно использовать fseek(), потому что getc() так или иначе проходит через файл байт за байтом; fseek() приказала getc() «посмотреть» туда, куда она сама уже собиралась посмотреть.

Вот пример, в котором выполняется что-то несколько более необычное (Мы благодарим Вильяма Шекспира за этот пример в пьесе «Двенадцатая ночь»).

 

/* чередование печати в прямом и обратном направлениях */

#include <stdio.h>

void main(int number,char *names[]) /* вам не нужно применять argc и argv */

{

FILE *fp;

long offset = 0L;

if (number < 2)

puts(" Мне нужно имя файла в качестве аргумента.");

else

{

if ((fp = fopen(names[1], "r")) ==0)

printf(" Я не могу открыть %s.\n", names[1]);

else

{

while (fseek(fp, offset ++, 0) == 0)

{

putchar(getc(fp));

if (fseek(fp,-(offset + 3), 2) == 0)

putchar(getc(fp));

}

fclose(fp);

}

}

}

Применение этой программы к файлу, содержащему имя «Мальволио», дает такой приятный результат:

 

МоаилльоввоьллиаоМ

 

Наша программа печатает первый символ файла, затем последний, затем второй, затем предшествующий последнему и т. д. Мы только добавили вот эти строки в последнюю программу:

 

if (fseek(fp,-(offset + 3), 2) == 0)

putchar(getc(fp));

 

Код 2 в операторе предполагает, что мы будем считать позиции от конца файла. Знак минус означает счет в обратном направлении. + 3 стоит здесь потому, что мы начинаем с последнего регулярного символа файла и пропускаем несколько символов «новая строка» и EOF в самом конце файла. (Точное значение этой корректировки зависит от типа системы. Наши файлы имеют в конце по два символа новой строки, за которыми следуют два EOF, поэтому мы как раз их и обходим.)

Таким образом, эта часть программы чередует печать в обратном направлении и печать в прямом направлении. Следует заметить, что в некоторых системах может не предусматриваться код 2 для fseek().

Функции fseek(), ftell() и rewind() можно использовать для определения или изменения местоположения маркера файла. Функция fseek() смещает маркер файла, на который указывает pf, на число ibytes байтов либо относительно начала файла (ifrom=()), либо относительно текущего положения маркера (ifrom=1), либо относительно конца файла (ifrom=2). В С имеется три предопределенных константы, которые можно использовать вместо переменной ifrom: SEEK_SET (смещение относительно начала файла), SEEK_CUR (текущее положение маркера) и SEEK_END (смещение относительно конца файла). Функция fseek() возвращает ноль, если операция была успешной, и EOF — в противном случае. В общем виде синтаксис функции fseek() следующий: fseek(pf, ibytes, ifrom);

Функция ftell() возвращает текущее положение маркера файла, на который указывает pf. Это положение представляет собой смещение в байтах от на-чала файла. Синтаксис функции ftell() следующий:

 

long_variable=ftell(pf);

 

Значение, возвращаемое ftell(), можно использовать при последующем вызове функции fseek().

Функция rewind() просто устанавливает маркер файла, на который ссылается pf, в начало этого файла. Синтаксис функции rewind() выглядит следующим образом:

 

rewind(pf);

 

В следующей программе на С иллюстрируется использование функций fseek(), ftell() и rewind():

 

/*

Программа на С, иллюстрирующая использование функций fseek,

ftell и rewind

*/

#include "stdafx.h"

#include "E:\LECTURE\AlgorithmProgramming\TestProg\Universal_HederFile.h"

void StopWait(void);

 

main()

{

FILE *pf;

char c;

long llocation;

pf=fopen("E:\\LECTURE\\AlgorithmProgramming\\TestProg\\test.dat","r+");

c=fgetc(pf);

putchar(c);

c=fgetc(pf);

putchar(c);

llocation=ftell(pf);

c=fgetc(pf);

putchar(c);

fseek(pf,llocation,0);

c=fgetc(pf);

putchar(c);

fseek(pf,llocation,0);

fputc('E',pf);

fseek(pf,llocation,0);

c=fgetc(pf);

putchar(c);

rewind(pf);

c=fgetc(pf);

putchar(c);

 

StopWait(); /* Wait a little */

return (0);

}

 

По определению переменная llocation имеет тип long. Это вызвано тем, что С поддерживает файлы длиннее, чем 64К. Входной файл TEST.DAT содержит строку "ABCD". После того как программа открывает этот файл, при первом обращении к функции fgetc() считывается символ "А", который затем печатается на дисплее. Следующая пара операторов считывает и печатает символ "В".

После вызова функции ftell() переменная llocation становится равной текущему положению маркера файла. Оно измеряется как смещение в байтах от начала файла. Поскольку символ "В" уже обработан, llocation содержит 2. Это означает, что маркер файла указывает на третий символ, который на 2 байта отстоит от первого символа — "А".

После этого другая пара операторов ввода/вывода считывает и печатает на дисплее символ "С". После выполнения этих операторов маркер файла смещен на 3 байта от начала файла и указывает на четвертый символ — "D".

В этой точке программы вызывается функция fseek(). Она должна сместить маркер на хранящееся в llocation число байт (то есть на 2 байта) относительно начала файла (поскольку третий параметр функции fseek() — ноль; это объяснялось выше). В результате этой операции маркер файла устанавливается на третий символ в файле. Переменной с опять присваивается значение символа "С", которое печатается во второй раз.

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

"С" (смещение 2 байта от начала). Однако следующий оператор не читает символ "С" в третий раз, а записывает вместо него новый символ — "Е". Поскольку после этого маркер смещается за это новое значение, функция fseek() вызывается еще раз для того, чтобы проверить запись этого символа в файл.

Следующая пара операторов считывает новое значение, "Е", и печатает его на дисплее. После этого программа вызывает функцию rewind(), которая снова перемещает указатель pf к началу файла. Затем, когда вызывается функция fgetc(), считывается символ "А" и печатается на экране. Результат работы программы выглядит так:

 

АВССЕА

 

Эти же принципы, проиллюстрированные на простом примере, можно использовать для создания файла записей со свободным доступом. Пред положим, что для записи в файл персонала имеется следующая информация: номер социальной страховки, имя и адрес. Допустим, что для номера страховки выделено 11 символов в формате ddd-dd-dddd, а имя и адрес занимают дополнительно 60 символов (или байт). Тогда каждая запись будет иметь длину 11 + 60 = 71 байт.

Заполненными могут быть не все возможные последовательные записи в дисковом файле со свободным доступом; нужно, чтобы запись имела некий флаг, указывающий на использование данной дисковой записи. Для этого к записи нужно добавить еще один байт и 2 дополнительных байта для номера записи; длина записи для одного человека в итоге составит 74 байта. Отдельная запись может выглядеть следующим образом:

 

1 U111-22-3333Linda Lossannie, 521 Alan Street, Anywhere, USA

 

Запись номер 1 в файле будет занимать байты с нулевого по 73-й; запись 2 занимает байты с 74 по 147; запись 3 занимает байты с 148 по 221; и так далее. Если использовать номер записи в сочетании с функцией fseek(), то на диске можно найти любую запись. Например, для того чтобы найти начало записи 2, можно использовать следующие операторы:

 

loffset=(iwhich_record - 1) * sizeof(stA_PERSON); fseek(pfi,loffset,0);

 

После того как маркер перемещен к началу выбранной записи, информация из этой записи может считываться или обновляться при помощи раз-личных функций ввода/вывода, подобных fread() и fwrite().

За исключением символов комментария /* и */ и заголовочного файла stdio.h описанная программа может работать и в C++. Достаточно заменить символом // оба разделителя /* и */, а файл stdio.h заменить на iostream.h.

 

7.32.1. Использование встроенного отладчика.

Попробуйте ввести следующую программу и после запроса на поиск 25-й записи распечатать значение переменной stcurrent_person.irecordnum:

 

Программа на С, обрабатывающая файл с произвольным доступом при

помощи функций fseek, fread и fwrite

*/

#include "stdafx.h"

#include "E:\LECTURE\AlgorithmProgramming\TestProg\Universal_HederFile.h"

void StopWait(void);

 

#define iFIRST 1

#define iLAST 50

#define iSS_SIZE 11

#define iDATA_SIZE 60

#define cVACANT 'V'

#define cUSED 'U'

typedef struct strecord

{

int irecordnum;

char cavailable; /* V - свободна, U - используется */

char csoc_sec_num[iSS_SIZE];

char cdata[iDATA_SIZE ];

} stA_PERSON;

 

main()

{

FILE *pfi;

stA_PERSON stcurrent_person;

int i,iwhich_record;

long int loffset;

pfi=fopen("E:\\LECTURE\\AlgorithmProgramming\\TestProg\\sample.fil","r+");

for(i = iFIRST; i <= iLAST; i++)

{

stcurrent_person.cavailable=cVACANT;

stcurrent_person.irecordnum=i;

fwrite(&stcurrent_person,sizeof(stA_PERSON),1,pfi);

}

/* Пожалуйста, введите номер записи для поиска */

printf("Please enter the record you would like to find.");

/* "Возможны значения от 1 до 50:" */

printf("\nYour response must be between 1 and 50: ");

scanf("%d",&iwhich_record);

loffset=(iwhich_record - 1) * sizeof(stA_PERSON);

fseek(pfi,loffset,0);

fread(&stcurrent_person,sizeof(stA_PERSON),1,pfi);

fclose (pfi);

 

StopWait(); /* Wait a little */

return (0);

}

 

Ключевое слово typedef определяет stA_PERSON как структуру, имеющую следующие поля: 2-байтный номер записи irecordnum, однобайтный символьный флаг доступности записи cavailable, 11-байтный массив символов для хранения номера страховки csoc_sec_num и 60-байтное поле данных cdata. В результате общий размер структуры равен 2 + 1 + 11 + 60 = 74 байтам.

После того как программа открыла текстовый файл в режиме "считывание/запись", она создает и запоминает 50 записей, каждая из которых имеет уникальный номер irecordnum; все записи помечены как "свободные" — cVACANT. Для выполнения оператора fwrite() нужно знать адрес записываемой структуры, размер в байтах записываемой информации, количество выводимых блоков и указатель на файл вывода. После всего этого программа запрашивает у пользователя номер записи, которую он хочет найти.

Поиск записи осуществляется в два этапа. Во-первых, нужно вычислить смещение относительно начала файла. Например, первая запись запоминается в байтах с нулевого по 73-й, вторая — с 74 по 148 и так далее. После вычитания единицы из номера записи, введенного пользователем, программа умножает это значение на число байтов, занимаемых каждой структурой и определяет значение смещения loffset. Например, для нахождения второй записи выполняются следующие вычисления: (2-1) х 74. В результате получается, что вторая запись начинается со смещением 74. После вычисления этого значения выполняется вызов функции fseek(), которая перемещает маркер файла на loffset байт.

Пока выполняется трассировка программы при поиске записей от 1 до 10, все идет нормально. Однако, что происходит, когда вы просите найти одиннадцатую запись? Получается "мусор". Причина этого кроется в том, что программа открыла файл в текстовом режиме. Записи с 1 по 9 точно равны 74 байтам, однако записи с 10-й и далее имеют длину 75 байт. Следовательно, десятая запись имеет соответственно вычисленное смещение loffset, однако в файле занимает на один байт больше. Поэтому одиннадцатая запись начинается с адреса, полученного в результате следующего мо-дифицированного выражения:

 

loffset=((iwhich_record - 1) * sizeof(stA_PERSON)) + 1;

 

Однако эти вычисления не будут работать с первыми девятью записями. Решение проблемы — открыть файл в двоичном режиме:

 

pfi=fopen("А:\\sample.fil","r+b");

 

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

 

7.33. Форматированный ввод.

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

 

Таблица 11.3. Управляющие символы для функций scanf(), fscanf() и sscanf()

 

Символ Представление Пример ввода Тип принимающего адресного параметра
c Символ W char
s Строка William char
d int   int
hd short -99 short
id long   long
o octat   int
ho short восьмеричное   short
io long восьмеричное   long
x шестнадцатеричное   int
hx short шестнадцатеричное   short
tx long шестнадцатеричное   long
e float как float 3.14159e+03 float
f То же, что “e”   double
ie float как double 3.14159e+03 double
if То же, что “ie”    
[A-Za-z] Строки из одних символов Test string char
[0-9] Строки из одних цифр   char

 

7.34. Использование функций scanf(), fscanf() и sscanf().

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

 

scanf("%2d%5s%4f",&ivalue,psz,&fvalue);

 

Этот оператор вводит целое число из двух цифр, строку из пяти символов и вещественное число, занимающее максимально четыре позиции (2.97, 12.5 и так далее). Сможете ли вы определить, что выполняет следующий оператор:

 

scanf ("%*[ \t\n] \"%[^A-Za-z] %[^\"] \"",ps1,ps2);

 

Вначале оператор считывает, но не запоминает любой пустой символ (пробел). Это выполняется при помощи следующей спецификации форма-та: "%*[ \t\n]". Символ (*) указывает функции, что она должна получить указанные данные, но не запоминать их ни в одной переменной. Если во входной строке содержатся только символы пробела, табуляции или перевода строки, то функция scanf() продолжает считывание до тех пор, пока не встретит двойную кавычку ("). Для этого используется спецификатор формата \", который указывает на то, что введенное значение должно совпадать с заданным символом. Символ двойной кавычки, однако, не вводится. огда функция scanf() обнаружила двойную кавычку, она получает указание вводить все символы, являющиеся цифрами, в переменную ps1. Для этого в спецификации формата %[^A-Za-z] используется модификатор — знак вставки (^), который указывает на то, что можно вводить любые символы, за исключением заглавных букв от "А" до "Z" и строчных букв от "а" до "z". Если бы знака вставки не было, то строка должна была бы содержать только буквенные символы. Знак тире между двумя символами "А" и "Z" и "а" и "z" говорит о том, что нужно рассматривать весь диапазон значений.

Мы уже использовали ранее функцию scanf() и формат %s для считывания строки. Основное различие между scanf() и gets() заключается в том, как они определяют, что достигли конца строки; scanf () предназначена скорее для получения слова, а не строки. Функция gets (), как мы уже видели, принимает все символы до тех пор, пока не встретит первый символ «новая строка». Функция scanf () имеет два варианта. Для любого из них строка начинается с первого встретившегося непустого символа. Если вы используете формат %s, строка продолжается до (но не включая) следующего пустого символа (пробел, табуляция или новая строка). Если вы определяете размер поля как %10s, то функция scanf() считает не более 10 символов или же считает до любого пришедшего первым пустого символа.

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

 

/* scanf () и подсчет количества */

#include <stdio.h>

void main ()

{

static char name1[40], name2[11];

int count;

printf(" Введите, пожалуйста, 2 имени.\n");

count = scanf(" %s %10s", name1, name2);

printf(" Я считал %d имен %s и %s.\n", count, name1, name2);

}

Вот два примера работы программы:

 

Введите, пожалуйста, два имени.

Джессика Джукс

Я считал 2 имени Джессика и Джукс.

Введите, пожалуйста, 2 имени.

Лиза Апплеботтхэм

Я считал 2 имени Лиза и Апплеботтх.

 

Во втором примере были считаны только первые 10 символов от Апплеботтхэм, так как мы использовали формат %10s.

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

Следующая спецификация формата, %[^\"], сообщает функции ввода, что необходимо читать все оставшиеся символы, исключая двойные кавычки, в переменную ps2. Последняя спецификация, \", указывает на то, что строка должна закончиться двойной кавычкой. Одни и те же символы форматирования ввода можно использовать с функциями fscanf() и sscanf(). Различие между функциями scanf() и fscanf() заключается лишь в том, что для последней необходимо указывать файл ввода. Функция sscanf() аналогична scanf(), за исключением того, что она читает данные из массива, а не из файла.

Следующий пример показывает, как можно использовать функцию sscanf() для преобразования строки (состоящей из цифр) в целое число. Если ivalue имеет тип int, a psz является массивом типа char, содержащим строку цифр, то при помощи следующего оператора можно преобразовать строку psz в тип int и запомнить его в переменной ivalue:

 

sscanf(psz,"%d",&ivalue);

 

Очень часто функции gets() и sscanf() используются совместно, так как функция gets() читает целую входную строку, а функция sscanf(0 просматривает строку и преобразует ее согласно спецификациям формата.

Часто при работе с функцией scanf(), если программисты пытаются использовать ее в сочетании с другими функциями символьного ввода, такими как getc(), getch(), getchar(), gets() и так далее, возникает одна проблема. Типичная последовательность действий выглядит так: функция scanf() используется для ввода символов и преобразования их в данные других типов. Затем используется какая-нибудь функция символьного ввода, например, getch(), и оказывается, что она работает не так как ожидалось. Это происходит потому, что иногда функция scanf() считывает не все предназначенные для ввода данные, а оставшиеся (не введенные) данные ошибочно считываются другими функциями ввода (в том числе и следующей функцией scanf()). Если вы используете функцию scanf() в программе, то в целях безопасности не используйте в той же программе другие функции ввода.

 

7.35. Распределение памяти: malloc() и calloc().

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

 

char place [] = " Залив Свиной печенки";

 

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

Или мы можем быть более конкретны и запросить определенный объем памяти:

 

int plates[100];

 

Это описание выделяет 100 ячеек памяти, каждая из которых предназначена для запоминания целого значения.

Язык Си не останавливается на этом. Он позволяет вам распределять дополнительную память во время работы программы. Предположим, например, вы пишете диалоговую программу и не знаете заранее, сколько данных вам придется вводить. Можно выделить нужный вам (как вы считаете) объем памяти, а затем, если понадобится, потребовать еще. Ниже дан пример, в котором используется функция malloc(), чтобы сделать именно это. Кроме того, обратите внимание на то, как такая программа применяет указатели.

 

/* добавляет память, если необходимо */

#include <stdio.h>

#define STOP /* сигнал прекращения ввода */

#define BLOCK 100 /* байты памяти */

#define LIM 40 /* предельная длина вводимой строки */

#define MAX 50 /* максимальное число вводимых строк */

#define DRAMA 20000 /* большая задержка времени */

main()

{

char store[BLOCK]; /* исходный блок памяти */

char symph[LIM]; /* приемник вводимых строк */

char *end; /* указывает на конец памяти */

char *starts[MAX]; /* указывает на начала строк */

int index = 0; /* количество вводимых строк */

int count; /* счетчик */

char *malloc(); /* распределитель памяти */

starts [0] = store;

end = starts [0] + BLOCK - 1;

puts(" Назовите несколько симфонических оркестров.");

puts(" Вводите по одному: нажмите клавишу [ввод] в начале");

puts(" строки для завершения вашего списка. Хорошо, я готова.");

while(strcmp(fgets(symph,LIM, stdin), STOP)!= 0 && index < MAX)

{

if (strlen(symph) > end - starts [index])

{ /* действия при недостатке памяти для запоминания вводимых данных*/

puts(" Подождите секунду. Я попробую найти дополнительную память.");

starts [index] = malloc(BLOCK);

end = starts [index] + BLOCK - 1;

for(count = 0; count < DRAMA; count++);

puts(" Нашла немного!");

}

strcpy(starts[index], symph);

starts [index + 1] = starts [index] + strlen(symph) + 1;

if (++index < MAX)

printf("Это %d. Продолжайте, если хотите\n", index);

}

puts(" Хорошо, вот что я получила:");

for(count = 0; count < index; count++)

puts(starts [count]);

}

 

Вот образец работы программы:

 

Назовите несколько симфонических оркестров.

Вводите их по одному; нажмите клавишу [ввод] в начале

строки для завершения вашего списка. Хорошо, я готова.




Поделиться с друзьями:


Дата добавления: 2015-01-03; Просмотров: 756; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.008 сек.