Подробнее действие этих модификаторов раскрыто в п. 11

Допускается более одного модификатора для одного объекта, например

int far* pascal function();

9.2. Перечислимый тип данных Объявление переменной перечислимого типа задает имя переменной и определяет список именованных констант, значения которых она может принимать. Каждому элементу списка перечисления ставится в соответствие целое число. Переменная перечислимого типа может принимать только значения из своего списка перечисления. Практически в любом контексте перечислимый тип интерпретируется как int; первый элемент списка перечисления по умолчанию равен 0.В примере ниже описывается тип day для перечисления дней недели. При описании переменной типа сразу же создается переменная workday.

enum day { SATURDAY, SUNDAY=0,MONDAY,

TUESDAY, WEDNESDAY, THURSDAY, FRIDAY }

workday;

workday = WEDNESDAY; //SATURDAY также =0!

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

typedef enum season { spring, summer, autumn, winter };season now=spring;for (int i=0; i<4; i++) printf ("\n%d", now++); //0,1,2,3В последнем примере значение item3 установлено равным 0, однако при увеличении переменной myitem в цикле она последовательно примет значения от 0 до 3.

#include <stdio.h>

typedef enum item {

item1, item2, item3=0, item4 };

void main () {

item myitem=item1;

for (int i=0; i<4; i++)

printf ("\n%d",myitem++); //0,1,2,3

myitem=item1; printf ("\n%d",myitem); //0

myitem=item2; printf ("\n%d",myitem); //1

myitem=item3; printf ("\n%d",myitem); //0

myitem=item4; printf ("\n%d",myitem); //1

}

9.3. Структуры

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

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

Общий синтаксис описания структуры следующий:

struct тег{

список объявлений элементов;

} список описателей;

Например, классический способ определения структурного типа может выглядеть так:

struct book {

char title [80];

char author [40];

float cost;

};

struct book mybook;

Рекомендуется в описании структуры использовать ключевое слово typedef:

typedef struct point {

double x,y;

};

В этом случае можно не писать ключевое слово struct в описании структур вновь созданного типа или при передаче их функциям. Обращение к полю структуры выполняется с помощью первичной операции "." (точка).

void main () {

point a,b;

a.x=a.y=0;

b=a;

printf ("\na=(%lf,%lf)",a.x,a.y);

printf ("\nb=(%lf,%lf)",b.x,b.y);

}

Альтернативный вид программы мог бы быть следующим:

struct point {

//'typedef' здесь был бы ошибкой!

double x,y;

} a,b;

void main () {

a.x=a.y=0;

b=a;

//...

}

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

Идентификаторы элементов структуры должны различаться между собой. Идентификаторы элементов разных структур могут совпадать.Элементы структуры запоминаются в памяти последовательно в том порядке, в котором они объявляются: первому элементу соответствует меньший адрес памяти, а последнему — больший.Каждый элемент в памяти может быть выровнен на границу слова, соответствующую его типу. Выравниванием управляет опция Word Alignment в компиляторах. Для процессоров на базе Intel 8086/8088 выравнивание означает, что любой тип выравнивается на четную границу адресации при размере машинного слова 2 байта.В качестве примера приведем код, работающий с текстовым экраном через структуры. При записи в видеопамять используется тот факт, что текстовый режим консоли требует на одну экранную позицию 2 последовательно расположенных байта памяти. Первый из них описывает выводимый на экран символ, а второй — его атрибут, задающий цвет и фон.#include <stdio.h>typedef struct texel_struct { unsigned char ch; unsigned char attr;} texel;typedef texel screen_array [25][80];screen_array far *screen_ptr = (screen_array far *)0xB8000000L;#define screen (*screen_ptr)void fillscr (int l,int t,int r,int b,int c,int a) { int i,j; for (i=t; i<=b; i++) for (j=l; j<=r; j++) { screen[i][j].ch=c; screen[i][j].attr=a; }}void main () { fillscr(0,0,79,24,'*',128); getchar();}Для доступа к полям структуры через указатель вместо конструкции(*ptr).fieldнеобходимой из‑за того, что приоритет операции "." выше, чем у унарной "*", используется сокращенная записьptr->fieldОба способа обращения компилируются одинаково, но второй более компактен и удобен.Работу с указателем на структуру проиллюстрируем следующим примером, в котором формируется и выводится массив из 3 переменных структурного типа.#include <stdio.h>#include <string.h>#include <alloc.h>#include <stdlib.h>struct test { char *name; //Фамилия или имя int ball; //Средний балл};struct test *tests; //Указатель на массив структурvoid check (struct test *);void main () { const int n=3; tests = (struct test *) malloc (n * sizeof (struct test)); //Выделение памяти под массив структур char buf[80]; for (int i=0; i<n; i++) { puts ("Имя? "); fgets (buf,78,stdin); tests[i].name = (char *) malloc (strlen(buf)); //Выделение памяти под одну фамилию buf[strlen(buf)-1]='\0'; //Удаляем '\n', который fgets //оставит в конце строк strcpy (tests[i].name, buf); puts ("Балл? "); fgets (buf,5,stdin); tests[i].ball = atoi (buf); check (&tests[i]); } for (i=0; i<n; i++) printf ("\n%s,%d", tests[i].name,tests[i].ball);}void check (struct test *p) { if (p->ball < 0) p->ball = 0; if (strlen (p->name)<1) { p->name = (char *) malloc (6); strcpy (p->name,"NoName"); }}Использование указателей на структурные типы в сочетании с указателями на функции обеспечивает мощь и гибкость языка Си в инкапсуляции данных даже без применения появившихся в Си++ классов (см. п. 13). Следующий пример представляет собой "заготовку" для несложной системы динамических меню.#include <conio.h>#include <stdlib.h>typedef unsigned char byte;typedef void (*FUN)(void); //Указатель на функцию обработки //пункта менюstruct MENU { int x,y; // Позиция на экране пункта меню byte *str; // Строка текста меню FUN sf; // Указатель на функцию // обработки пункта};void Exit () { exit (0); }void Start () { /* код функции обработки пункта меню */ }void DrawMenu (MENU *m) { gotoxy(m->x,m->y); cprintf ("%s",m->str);}#define ITEMS 2void main () { MENU Menu[ITEMS]={ { 1, 1, "Начать", Start }, {10, 1, "Выход", Exit } }; clrscr (); for (int i=0; i<ITEMS; i++) DrawMenu (&Menu[i]);}

9.4. Битовые поля структур используются обычно в двух целях:

· для экономии памяти, поскольку позволяют плотно упаковать значения не по границам байтов;

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

Объявление битового поля имеет следующий синтаксис:

типидентификатор: константное_выражение;

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

Идентификатор необязателен. Неименованное битовое поле означает пропуск указанного числа битов перед размещением следующего элемента структуры. Неименованное битовое поле, для которого указан размер 0, имеет специальное назначение: оно гарантирует, что память для следующей переменной в этой структуре будет начинаться на границе машинного слова (int). Это относится и к следующему битовому полю.

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

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

struct {

unsigned background: 8;

unsigned color: 4;

unsigned underline: 1;

unsigned blink: 1;

} screen [25][80];

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

union тег {

список_объявлений_элементов;

} список_описателей;

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

union sign {

int svar;

unsigned uvar;

} number;

sign number2;

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

union key {

char k[2];

unsigned kod;

};

Элементами объединений могут быть и указатели. Например, в приведенном далее коде с помощью 4‑байтового указателя "сегмент‑смещение" отслеживается нажатие клавиш Ctrl, Shift или Alt через BIOS.

#include <stdio.h>#include <conio.h>struct FAR_PTR { unsigned off, seg;};union MK_FAR { unsigned char far *ptr; struct FAR_PTR mk;} work;void main () { work.mk.off = 0x17; work.mk.seg = 0x40; //сегмент и смещение в BIOS, //где хранится флаг нажатия клавиш while (!kbhit ()) { int shift = *(work.ptr); cprintf ("\r%02d",shift); }}

Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: