Размещение структурных переменных в памяти
При анализе размеров структурных переменных иногда число байт, выделенных компилятором под структурную переменную оказывается больше, чем сумма байт ее полей. Это связано с тем, что компилятор выделяет участок ОП для структурных переменных с учетом выравнивания граней, добавляя между полями пустые байты по следующим правилам:
- структурные переменные, являющиеся элементами массива начинаются на границе слова, т.е. с четного адреса;
- любое поле структурной переменной начинается на границе слова, т.е. с четного адреса и имеет четное смещение по отношению к началу переменной;
- при необходимости в конец переменной добавляется пустой байт, чтобы общее число байтов было четное.
Объединения
Объединение - поименованная совокупность данных разных типов, размещаемых с учетом выравнивания в одной и той же области памяти, размер которой достаточен для хранения наибольшего элемента.
Объединенный тип данных декларируется подобно структурному типу:
union ID_объединения {
описание полей
};
Пример описания объединенного типа:
union word {
int nom;
char str[20];
};
Пример объявления объектов объединенного типа:
union word *p_w, mas_w[100];
Объединения применяют для экономии памяти в случае, когда объединяемые элементы логически существуют в разные моменты времени либо требуется разнотипная интерпретация поля данных.
Например, поток сообщений по каналу связи пусть содержит сообщения трех видов:
struct m1 {
char code;
float data[100]; };
struct m2 {
char code;
int mode; };
struct m3 {
char code, note[80]; };
Элемент code - признак вида сообщения. Удобно описать буфер для хранения сообщений в виде
struct m123 {
char code;
union {
float data[100];
int mode;
char note[80]; };
};
Практически все вышесказанное для структур имеет место и для объединений.
Декларация данных типа union, создание переменных этого типа и обращение к полям объединений производится аналогично структурам.
Пример использования переменных типа union:
. . .
typedef union q {
int a;
float 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 Float b = %f, 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));
getch();
}
Результат работы программы:
Integer a = 4, Sizeof(s.a) = 2
Float b = 1.500000, Sizeof(s.b) = 4
String a = Minsk, Sizeof(s.s) = 5
Sizeof(s) = 5
Перечисления
Перечисления - средство создания типа данных посредством задания ограниченного множества значений.
Определение перечислимого типа данных имеет вид
enum ID_перечислимого_типа {
список_значений
};
Значения данных перечислимого типа указываются идентификаторами. Например:
enum marks {
zero, two, three, four, five
};
Транслятор последовательно присваивает идентификаторам списка значений целочисленные величины 0,1,..., . При необходимости можно явно задать значение идентификатора, тогда очередные элементы списка будут получать последующие возрастающие значения. Например:
enum level {
low=100, medium=500, high=1000, limit
};
Примеры объявления переменных перечислимого типа:
enum marks Est;
enum level state;
Переменная типа marks может принимать только значения из множества {zero, two, three, four, five}.
Основные операции с данными перечислимого типа:
- присваивание переменных и констант одного типа;
- сравнение для выявления равенства либо неравенства.
Практическое назначение перечисления - определение множества различающихся символических констант целого типа.
Пример использования переменных перечислимого типа:
. . .
typedef enum {
mo=1, tu, we, th, fr, sa, su
} days;
void main(void) {
days w_day; // Переменная перечислимого типа
int cur_day, _end, _start;
// Текущий день недели, начало и конец недели, соответственно
clrscr();
puts(“ Введите день недели (от 1 до 7) : ”);
scanf(“%d”, &cur_day);
w_day = su;
_start = mo;
_end = w_day - cur_day;
printf(“\n Понедельник - %d день недели, \
сейчас %d - й день и \n\
до конца недели %d дней (дня)”, _start, cur_day, _end );
getch();
}
Результат работы программы:
Введите день недели (от 1 до 7) : 5
Понедельник - 1 день недели, сейчас 5 - й день и
до конца недели 2 дней (дня)
18. Файлы в языке С
Файл – это набор данных, размещенный на внешнем носителе и рассматриваемый в процессе обработки и пересылке как единое целое. В файлах размещаются данные, предназначенные для длительного хранения.
Различают два вида файлов: текстовые и бинарные. Текстовые файлы представляют собой последовательность ASCII символов и могут быть просмотрены и отредактированы с помощью любого текстового редактора.
Бинарные (двоичные) файлы представляют собой последовательность данных, структура которых определяется программно.
В языке Си имеется большой набор функций для работы с файлами, большинство которых находятся в библиотеках stdio.h и io.h.
Открытие файла
Каждому файлу присваивается внутреннее логическое имя, используемое в дальнейшем при обращении к нему. Логическое имя (идентификатор файла) - это указатель на файл, т.е. на область памяти, где содержится вся необходимая информация о файле. Формат объявления указателя на файл следующий:
FILE*указатель на файл;
FILE - идентификатор структурного типа, описанный в стандартной библиотеке stdio.h и содержащий следующую информацию:
type struct{
short level; | - число оставшихся в буфере непрочитанных байт; обычный размер буфера - 512 байт; как только level=0, в буфер из файла читается следующий блок данных; |
unsigned flags; | - флаг статуса файла - чтение, запись, дополнение; |
char fd; | - дескриптор файла, т.е. число, определяющее его номер; |
unsigned char hold; | - непереданный символ, т.е. ungetc-символ; |
short bsize; | - размер внутреннего промежуточного буфера; |
unsigned char buffer; | - значение указателя для доступа внутри буфера, т.е. задает начало буфера, начало строки или текущее значение указателя внутри буфера в зависимости от режима буферизации; |
unsigned char *curp; | - текущее значение указателя для доступа внутри буфера, т.е. задает текущую позицию в буфере для обмена с программой; |
unsigned istemp; | - флаг временного файла; |
short token; | - флаг при работе с файлом; |
} FILE;
Прежде, чем начать работать с файлом, т.е. получить возможность чтения или записи информации в файл, его нужно открыть для доступа. Для этого обычно используется функция
FILE* fopen(char * ID_файла, char *режим);
она берет внешнее представление - физическое имя файла на носителе (дискета, винчестер) и ставит ему в соответствие логическое имя.
Физическое имя, т.е. имя файла и путь к нему задается первым параметром - строкой, например, “a:Mas_dat.dat” - файл с именем Mas_dat.dat, находящийся на дискете, “d:\\work\\Sved.txt” - файл с именем Sved.txt, находящийся на винчестере, в каталоге work.
Внимание, обратный слеш (\), как специальный символ в строке записывается дважды!
При успешном открытии функция fopen() возвращает указатель на файл (в дальнейшем - указатель файла). При ошибке возвращается NULL. Данная ситуация обычно возникает, когда неверно указывается путь к открываемому файлу. Например, если в дисплейном классе нашего университета, указать путь, запрещенный для записи (обычно, разрешенным является d:\work\).
Второй параметр - строка, в которой задается режим доступа к файлу:
w - файл открывается для записи; если файла с заданным именем нет, то он будет создан; если такой файл существует, то перед открытием прежняя информация уничтожается;
r - файл открывается только для чтения; если такого файла нет, то возникает ошибка;
a - файл открывается для добавления в его конец новой информации;
r+ - файл открывается для редактирования данных - возможны и запись, и чтение информации;
w+ - то же, что и для r+;
a+ - то же, что и для a, только запись можно выполнять в любое место файла; доступно и чтение файла;
t - файл открывается в текстовом режиме; используются поля r, w, a, r+, w+, a+;
b - файл открывается в двоичном режиме.
Текстовый режим отличается от двоичного тем, что при открытии файла как текстового пара символов «перевод строки», «возврат каретки» заменяется на один символ: «перевод строки» для всех функций записи данных в файл, а для всех функций вывода символ «перевод строки» теперь заменяется на два символа: «перевод строки», «возврат каретки».
По умолчанию файл открывается в текстовом режиме.
Пример: FILE *f; - объявляется указатель на файл f;
f = fopen ("d:\\work\\Dat_sp.cpp", "w"); - открывается для записи файл с логическим именем f, имеющим физическое имя Dat_sp.cpp, находящийся на диске d, в каталоге work.
или более кратко:
FILE *f = fopen ("d:\\work\\Dat_sp.cpp", "w");
Закрытие файла
После работы с файлом доступ к нему необходимо закрыть. Это выполняет функция int fclose(указатель файла). Например, из предыдущего примера файл закрывается так: fclose (f);
Для закрытия нескольких файлов введена функция, объявленная следующим образом: void fcloseall(void);
Если требуется изменить режим доступа к файлу, то для этого сначала необходимо закрыть данный файл, а затем вновь его открыть, но с другими правами доступа. Для этого используют стандартную функцию:
FILE* freopen(char *ID_файла, char *режим, FILE *указатель_файла);
Эта функция сначала закрывает файл, объявленный «указателем_файла» (как это делает функция fopen), а затем открывает файл с «именем_файла» и правами доступа «режим».
В языке С имеется возможность работы с временными файлами, которые нужны только в процессе работы программы и которые надо удалить после выполнения части вычислений. В этом случае используется функция:
FILE* tmpfile(void);
которая создает на диске временный файл с правами доступа «w+b», после завершения работы программы или после закрытия временного файла он автоматически удаляется.