Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Размещение структурных переменных в памяти
При анализе размеров структурных переменных иногда число байт, выделенных компилятором под структурную переменную, оказывается больше, чем сумма байт ее полей. Это связано с тем, что компилятор выделяет участок ОП для структурных переменных с учетом выравнивания границ, добавляя между полями пустые байты по следующим правилам: – структурные переменные, являющиеся элементами массива, начинаются на границе слова, т.е. с четного адреса; – любое поле структурной переменной начинается на границе слова, т.е. с четного адреса и имеет четное смещение по отношению к началу переменной; – при необходимости в конец переменной добавляется пустой байт, чтобы общее число байт было четное.
Объединения Объединение – поименованная совокупность данных разных типов, размещаемых с учетом выравнивания в одной и той же области памяти, размер которой достаточен для хранения наибольшего элемента. Объединенный тип данных декларируется подобно структурному типу: union ID_объединения { описание полей }; Пример описания объединенного типа: union word { int nom; char str[20]; }; Пример объявления объектов объединенного типа: union word *p_w, mas_w[100]; Объединения применяют для экономии памяти в случае, когда объединяемые элементы логически существуют в разные моменты времени либо требуется разнотипная интерпретация поля данных. Практически все вышесказанное для структур имеет место и для объединений. Декларация данных типа union, создание переменных этого типа и обращение к полям объединений производится аналогично структурам. Пример использования переменных типа union: ... typedef union q { int a; double b; char s[5]; } W; void main(void) { W s, *p = & s; s.a = 4; printf(“\n Integer a = %d, Sizeof(s.a) = %d”, s.a, sizeof(s.a)); p –> b = 1.5; printf(“\n Double b = %lf, Sizeof(s.b) = %d”, s.b, sizeof(s.b)); strcpy(p–> s, “Minsk”); printf(“\n String a = %s, Sizeof(s.s) = %d”, s.s, sizeof(s.s)); printf(“\n Sizeof(s) = %d”, sizeof(s)); } Результат работы программы: Integer a = 4, Sizeof(s.a) = 2 Double b = 1.500000, Sizeof(s.b) = 4 String a = Minsk, Sizeof(s.s) = 5 Sizeof(s) = 5
Перечисления Перечисления – средство создания типа данных посредством задания ограниченного множества значений. Определение перечисляемого типа данных имеет вид enum ID_перечисляемого_типа { список_значений }; Значения данных перечисляемого типа указываются идентификаторами, например: enum marks { zero, one, two, three, four, five }; Компилятор последовательно присваивает идентификаторам списка значений целочисленные величины 0, 1, 2,.... При необходимости можно явно задать значение идентификатора, тогда очередные элементы списка будут получать последующие возрастающие значения. Например: enum level { low=100, medium=500, high=1000, limit }; Константа limit по умолчанию получит значение, равное 1001. Примеры объявления переменных перечисляемого типа: enum marks Est; enum level state; Переменная типа marks может принимать только значения из множества {zero, one, two, three, four, five}. Основные операции с данными перечисляемого типа: – присваивание переменных и констант одного типа; – сравнение для выявления равенства либо неравенства. Практическое назначение перечисления – определение множества различающихся символических констант целого типа. Пример использования переменных перечисляемого типа: ... typedef enum { mo=1, tu, we, th, fr, sa, su } days; void main(void) { days w_day; // Переменная перечисляемого типа int t_day, end, start; // Текущий день недели, начало и конец недели соответственно puts(“ Введите день недели (от 1 до 7): ”); scanf(“%d”, & t_day); w_day = su; start = mo; end = w_day – t_day; printf(“\n Понедельник – %d день недели, \ сейчас %d – й день и \n\ до конца недели %d дн. ”, start, t_day, end ); } Результат работы программы: Введите день недели (от 1 до 7): 5 Понедельник – 1 день недели, сейчас 5-й день и до конца недели 2 дн.
Битовые поля Битовые поля – это особый вид полей структуры. Они используются для плотной упаковки данных, например, флажков типа «да/нет». Минимальная адресуемая ячейка памяти – 1 байт, а для хранения флажка достаточно одного бита. При описании битового поля после имени через двоеточие указывается длина поля в битах (целая положительная константа), не превышающая разрядности поля типа int: struct fields { unsigned int flag: 1; unsigned int mask: 10; unsigned int code: 5; }; Битовые поля могут быть любого целого типа. Имя поля может отсутствовать, такие поля служат для выравнивания на аппаратную границу. Доступ к полю осуществляется обычным способом – по имени. Адрес поля получить нельзя, однако в остальном битовые поля можно использовать точно так же, как обычные поля структуры. Следует учитывать, что операции с отдельными битами реализуются гораздо менее эффективно, чем с байтами и словами, так как компилятор должен генерировать специальные коды и экономия памяти под переменные оборачивается увеличением объема кода программы. Размещение битовых полей в памяти зависит от компилятора и аппаратуры. В основном битовые поля размещаются последовательно в поле типа int, а при нехватке места для очередного битового поля происходит переход на следующее поле типа int. Возможно объявление безымянных битовых полей, а длина поля 0 означает необходимость перехода на очередное поле int: struct areas { unsigned f1: 1; : 2; – безымянное поле длиной 2 бита; unsigned f2: 5; : 0 – признак перехода на следующее поле int; unsigned f3: 5; double data; char buffs[100]; – структура может содержать элементы любых типов данных; }; Битовые поля могут использоваться в выражениях как целые числа соответствующей длины поля разрядности в двоичной системе исчисления. Единственное отличие этих полей от обычных объектов – запрет операции определения адреса (& ). Следует учитывать, что использование битовых полей снижает быстродействие программы по сравнению с представлением данных в полных полях из-за необходимости выделения битового поля.
ГЛАВА 14. Файлы в языке Си
Файл – это набор данных, размещенный на внешнем носителе и рассматриваемый в процессе обработки как единое целое. В файлах размещаются данные, предназначенные для длительного хранения. Различают два вида файлов: текстовые и бинарные. Текстовые файлы представляют собой последовательность ASCII символов и могут быть просмотрены и отредактированы с помощью любого текстового редактора. Эта последовательность символов разбивается на строки символов, при этом каждая строка заканчивается двумя кодами «перевод строки», «возврат каретки»: 13 и 10 (0xD и 0xA). Бинарные (двоичные) файлы представляют собой последовательность данных, структура которых определяется программно. В языке Си не предусмотрены никакие заранее определенные структуры файлов. Все файлы рассматриваются компилятором как последовательность (поток байт) информации. Для файлов определен маркер или указатель чтения-записи данных, который определяет текущую позицию доступа к файлу. Напомним, что с началом работы любой программы автоматически открываются стандартные потоки stdin и stdout. В языке Си имеется большой набор функций для работы с файлами, большинство которых находятся в библиотеках stdio.h и io.h. При этом потоки данных, с которыми работают функции ввода-вывода данных по умолчанию, буферизированы. Это означает, что при открытии потока с ним автоматически связывается определенный участок ОП, который и называется буфером. Все операции чтения-записи ведутся через этот буфер. Его размер фиксирован специальной константой BUFSIZ, которая определена в файле stdio.h как 512 (хотя программно ее можно изменять).
Открытие файла Каждому файлу в программе присваивается внутреннее логическое имя, используемое в дальнейшем при обращении к нему. Логическое имя (идентификатор файла) – это указатель на файл, т.е. на область памяти, где содержится вся необходимая информация о файле. Формат объявления указателя на файл следующий: FILE * ID_указателя_на_файл; FILE – идентификатор структурного типа, описанный в стандартной библиотеке stdio.h и содержащий следующую информацию: struct FILE {
};
Прежде чем начать работать с файлом, т.е. получить возможность чтения или записи информации в файл, его нужно открыть для доступа. Для этого обычно используется функция FILE* fopen (char * ID_файла, char *режим); Данная функция берет внешнее представление – физическое имя файла на носителе (дискета, винчестер) и ставит ему в соответствие логическое имя (программное имя – указатель файла). Физическое имя, т.е. ID файла и путь к нему задается первым параметром – строкой, например, “a: Mas_dat.dat” – файл с именем Mas_dat и расширением dat, находящийся на дискете, “d: \\work\\Sved.txt” – файл с именем Sved и расширением txt, находящийся на винчестере в каталоге work. Внимание. Обратный слеш «\», как специальный символ в строке записывается дважды. При успешном открытии функция fopen возвращает указатель на файл (в дальнейшем – указатель файла). При ошибке возвращается NULL. Данная ситуация обычно возникает, когда неверно указывается путь к открываемому файлу, например, если указать путь, запрещенный для записи. Второй параметр – строка, в которой задается режим доступа к файлу. Возможные значения данного параметра следующие: w – файл открывается для записи (write); если файла с заданным именем нет, то он будет создан; если же такой файл уже существует, то перед открытием прежняя информация уничтожается; r – файл открывается для чтения (read); если такого файла нет, то возникает ошибка; a – файл открывается для добавления (append) новой информации в конец; r+ (w+) – файл открывается для редактирования данных, т.е. возможны и запись, и чтение информации; a+ – то же, что и для a, только запись можно выполнять в любое место файла (доступно и чтение файла); t – файл открывается в текстовом режиме; b – файл открывается в двоичном режиме; Последние два режима используются совместно с рассмотренными выше. Возможны следующие комбинации режимов доступа: “w+b”, “wb+”, “rw+”, “w+t”, “rt+”, а также некоторые другие комбинации. По умолчанию файл открывается в текстовом режиме. Текстовый режим отличается от двоичного тем, что при открытии файла как текстового пара символов «перевод строки» и «возврат каретки» заменяется на один символ «перевод строки» для всех функций записи данных в файл, а для всех функций вывода – наоборот – символ «перевод строки» заменяется на два символа – «перевод строки» и «возврат каретки». Пример открытия файла: FILE *f; – объявляется указатель на файл f; f = fopen (" d: \\work\\Dat_sp.dat ", " w" ); – открывается для записи файл с логическим именем f, имеющий физическое имя Dat_sp.dat и находящийся на диске d в каталоге work, или более кратко: FILE *f = fopen (" d: \\work\\Dat_sp.dat", " w" );
Закрытие файла После работы с файлом доступ к нему необходимо закрыть с помощью функции int fclose (указатель файла); Например, для предыдущего примера файл закрывается так: fclose (f); Для закрытия нескольких файлов введена функция: void fcloseall (void); Если требуется изменить режим доступа к открытому в настоящий момент файлу, то его необходимо сначала закрыть, а затем вновь открыть с другими правами доступа. Для этого используется функция FILE* freopen (char *ID_файла, char *режим, FILE *указатель_файла); которая сначала закрывает файл, заданный в третьем параметре (указатель файла), как это выполняет функция fclose, а затем выполняет действия, аналогичные функции fopen, используя указанные первый и второй параметры (открывает файл с ID_файла и правами доступа режим). В языке Си имеется возможность работы с временными файлами, которые нужны только в процессе работы программы и должны быть удалены после выполнения некоторых вычислений. В этом случае используется функция FILE* tmpfile (void); которая создает на диске временный файл с правами доступа w+b. После завершения работы программы или закрытия этого (временного) файла он автоматически удаляется.
Запись-чтение информации Все действия по чтению-записи данных в файл можно разделить на три группы: – операции посимвольного ввода-вывода; – операции построчного ввода-вывода; – операции ввода-вывода по блокам. Рассмотрим основные функции для записи-чтения данных из файлов. Для работы с текстовыми файлами в библиотеке языка Си содержится достаточно много функций, самыми распространенными из которых являются функции fprintf, fscanf, fgets, fputs. Формат параметров этих функций практически такой же, как и формат рассмотренных ранее (см. разд. 5.3, 5.4) функций printf, scanf, gets и puts. Так же практически совпадают и действия этих функций. Отличие состоит в том, что printf и другие функции работают по умолчанию с экраном монитора и клавиатурой, а функции fprintf и другие – с файлом, указатель которого является одним из параметров этих функций. Рассмотрим общий пример создания текстового файла: #include< stdio.h> void main(void) { FILE *f1; int a=2, b=3; if(! (f1 = fopen(“d: \\work\\f_rez.txt”, ”w+t”) ) ) { // f1 = NULL puts(“Open File Error! ”); return; // exit(1); } fprintf(f1, ”\t Файл результатов \n”); fprintf(f1, ” %d плюс %d = %d\n”, a, b, a+b); fclose(f1); } Просмотрев содержимое файла любым текстовым редактором, можно убедиться, что данные в нем располагаются точно так, как на экране, если воспользоваться функцией printf с такими же списками параметров. Создание текстовых результирующих файлов обычно необходимо для оформления отчетов, различных документов, а также других текстовых материалов. Бинарные (двоичные) файлы обычно используются для организации баз данных, состоящих, как правило, из объектов структурного типа. При чтении-записи бинарных файлов удобнее всего пользоваться функциями fread и fwrite, которые выполняют ввод-вывод данных блоками. Такой способ обмена данными требует меньше времени. Функция unsigned fread (void * p, unsigned size, unsigned n, FILE * f ); выполняет считывание из файла f n блоков размером size байт каждый в область памяти, адрес которой p. В случае успеха функция возвращает количество считанных блоков. При возникновении ошибки или по достижении признака окончания файла – значение EOF (End Of File – признак окончания файла). Обратное действие выполняет функция: unsigned fwrite (void * p, unsigned size, unsigned n, FILE * f ); при вызове которой в файл f будет записано n блоков размером size байт каждый из области памяти, начиная с адреса p.
Позиционирование в файле Каждый открытый файл, как уже отмечалось, имеет скрытый указатель на текущую позицию в нем. При открытии файла этот указатель устанавливается на начало данных, и все операции в файле будут производиться с данными, начинающимися в этой позиции. При каждом выполнении функции чтения или записи указатель смещается на количество прочитанных или записанных байт, т.е. устанавливается после прочитанного или записанного блока данных в файле – это последовательный доступ к данным. В языке Си/С++ можно установить указатель на некоторую заданную позицию в файле. Для этого используют стандартную функцию fseek, которая позволяет выполнить чтение или запись данных в произвольном порядке. Декларация функции позиционирования следующая: int fseek (FILE * f, long size, int code ); Значение параметра size задает количество байт, на которое необходимо сместить указатель в файле f, в направлении параметра code, который может принимать следующие значения:
Таким образом, смещение может быть как положительным, так и отрицательным, но нельзя выходить за пределы файла. В случае успеха функция возвращает нулевое значение, а в случае ошибки (например, попытка выхода за пределы файла) – единицу. Доступ к файлу с использованием функции позиционирования (fseek) называют произвольным доступом. Иногда нужно определить текущее положение в файле. Для этого используют функцию со следующей декларацией: long ftell (FILE * f ); которая возвращает значение указателя на текущую позицию в файле или –1 в случае ошибки.
Популярное:
|
Последнее изменение этой страницы: 2016-03-16; Просмотров: 1180; Нарушение авторского права страницы