Типы данных, определяемые пользователем (агрегативные типы данных)
Язык C позволяет программисту создавать следующие типы данных:
- переименование типов;
- перечислимый тип;
- структура;
- битовые поля;
- объединение.
Переименование типов.
Язык C позволяет дать новое имя уже существующему типу данных. Форма записи:
typedef тип новый_тип;
Поле тип определяет уже существующий тип. Поле новый_тип определяет новое имя типа.
Пример 1
typedef unsigned int uint;
unsigned int i;
uint j;
Перечислимый тип.
Перечислимый тип – это множество поименованных целых констант. Перечислимый тип определяет все допустимые значения, которые могут иметь переменные данного типа.
Форма записи:
enum имя_типа {список_названий} список_переменных;
Поле имя_типа определяет имя нового перечислимого типа. Поле список_названий определяет имена констант перечислимого типа и их значения. Имена констант должны различаться. Значения могут отсутствовать, тогда константа получит значение по умолчанию. Правило присвоения константам значений:
- если константа инициализируется явно, то её значение равно значению инициализирующего константного выражения;
- если константа не инициализируется явно, то её значение равно значению предыдущей константы + 1;
- если первая константа не инициализируется явно, то её значение равно 0.
Поле список_переменных может быть пустым или определяет список объявлений переменных перечислимого типа.
Над переменными перечислимого типа возможны следующие операции:
- присвоение значения переменной перечислимого типа другой переменной перечислимого типа или переменной целого типа;
- сравнение;
- арифметические операции с константами перечислимого типа.
Пример 2
enum constants {zero, one, minus_one=-1, _zero, ten=10} c1=zero;
constants c2, c3=one-zero;
c2=2;
if(с2<ten)
printf("%d %d %d %d %d %d %d %d",zero, one, minus_one, _zero, ten, c1, c2, c3);
Программа напечатает: 0 1 -1 0 10 0 2 1
Некоторые компиляторы допускают присвоение переменным перечислимого типа значений, не входящих во множество значений констант данного типа, но делать это не рекомендуется. Также некоторые компиляторы допускают выполнение арифметических операций для переменных перечислимого типа.
Основная причина использования перечислимого типа – улучшение читаемости программ.
Структура.
Структура – это средство компоновки разнотипных объектов в один объект. Объекты, входящие в структуру, называются членами структуры. Форма объявления структуры:
struct имя_типа {список_объявлений_членов}список_переменных;
Поле имя_типа определяет имя нового типа структур. Поле список_объявлений_членов определяет имена и типы членов структуры. Членом структуры может быть объект любого типа, базового или агрегативного. Форма объявления члена структуры аналогична форме объявления объекта данного типа в тексте программы. Поле список_переменных может быть пустым или определяет объявления одной или нескольких структур, разделенных запятыми.
Форма объявления структуры в поле список_переменных:
имя=инициализатор
Поле имя задаёт идентификатор структуры и является обязательным. Поле инициализатор задает инициализатор структуры и является необязательным.
В дальнейшем в тексте программы возможно объявление структур аналогично объявлению переменных с указанием типа структуры.
Можно объединять структуры в массивы или создавать указатели на структуры.
Образ структуры в памяти: по младшему адресу размещается первый член списка_объявлений_членов, затем второй и так далее. Внутри объявлений, содержащих более одной переменной, порядок размещения в памяти обратный.
Особенности размещения:
- размещение всегда начинается с чётного адреса;
- каждый следующий член выравнивается при записи в память на границу, соответствующую его типу;
- если размер структуры в памяти равен нечётному числу байтов, то к ней добавляется ещё один – пустой байт, чтобы следующий после структуры объект располагался с чётного адреса.
Пример 3
struct color_point
{
int x,y;
unsigned int color;
};
color_point triangle[3];
Не полностью определенный структурный тип.
Членами структур могут быть данные любых типов, за исключением структур того же типа, что и определяемый структурный тип. В то же время членом структуры может быть указатель на структуру того же типа, что и определяемый структурный тип.
При определении структурных типов может потребоваться организация взаимных перекрестных связей между структурами двух или более разных типов. В этом случае невозможно выполнить требование об определенности всех связываемых структурных типов. Выходом служит применение указателей на структуры.
Пример 4
struct s1{
s1* p1;
struct s2 *p2;
};
struct s2{
s1* p3;
s2 *p4;
};
Так как в данном примере тип s2 объявляется ниже типа s1, то при объявлении указателя p2 необходимо указать ключевое слово struct, чтобы компилятор понял тип этого указателя.
Битовые поля.
Битовые поля позволяют организовать доступ к нескольким битам в байте. Это позволяет экономить память ЭВМ. Битовые поля – это специальный тип членов структуры, в котором определено, сколько бит отводится под каждый член.
Форма записи:
struct имя_типа {
тип1 имя1:длина_в_битах;
тип2 имя2:длина_в_битах;
…
}список_переменных;
Поле имя_типа определяет имя нового типа структур. Поля типi и имяi определяют тип и имена членов структуры. Допустимые значения типа: int, unsigned int, signed int. Поле длина_в_битах определяет длину в битах соответствующего члена структуры, длина не может превышать длину базового типа в битах. Поле список_переменных может быть пустым или определяет объявления одной или нескольких структур, разделенных запятыми.
Форма объявления структуры в поле список_переменных:
имя=инициализатор
Поле имя задаёт идентификатор структуры и является обязательным. Поле инициализатор задает инициализатор структуры и является необязательным.
В дальнейшем в тексте программы возможно объявление битовых полей аналогично объявлению переменных с указанием типа битового поля.
Можно объединять битовые поля в массивы или создавать указатели на битовые поля.
Образ битового поля в памяти: в байте (слове), хранящемся по младшему адресу, размещается первый член структуры; если число бит, требуемое для его размещения, меньше максимально допустимого (размер данного типа в битах), то второй член будет располагаться в памяти, начиная с оставшихся битов предыдущего поля (если оставшихся битов недостаточно, чтобы разместить его целиком, задействуется следующий байт (слово)), и так далее.
Особенности размещения:
- неименованное битовое поле – пропуск указанного в нём количества битов памяти перед размещением следующего члена структуры;
- битовое поле нулевого размера – следующий член будет размещаться, начиная с границы машинного слова.
Длина структуры, содержащей битовые поля, в битах всегда кратна восьми, даже если задействуется меньшее число битов.
Пример 5
struct screen_point
{
unsigned int x:10;
unsigned int y:10;
unsigned int color:4;
};
screen_point screen[640][480];
Объединение.
Объединение – это объект, предназначенный для хранения в каждый момент времени только одного значения из набора возможных, в том числе и разнотипных. Объекты, входящие в объединение, называются членами объединения. Форма записи:
union имя_типа {список_объявлений_членов}список_переменных;
Поле имя_типа определяет имя нового типа объединения. Поле список_объявлений_членов определяет имена и типы членов объединения. Членом объединения может быть объект любого типа, базового или агрегативного. Форма объявления члена объединения аналогична форме объявления объекта данного типа в тексте программы. Поле список_переменных может быть пустым или определяет объявления одного или нескольких объединений, разделенных запятыми.
Форма объявления объединения в поле список_переменных:
имя=инициализатор
Поле имя задаёт идентификатор объединения и является обязательным. Поле инициализатор задает инициализатор объединения и является необязательным.
В дальнейшем в тексте программы возможно объявление объединений аналогично объявлению переменных с указанием типа объединения.
Можно объединять объединения в массивы или создавать указатели на объединения.
Образ объединения в памяти: память под объединение выделяется в объёме, необходимом для размещения члена объединения максимального размера, все члены записываются в память, начиная с одного и того же адреса памяти. Таким образом, текущее значение теряется всякий раз, как только производится новое присваивание какому-либо из членов объединения.
Пример 6
union value
{
int i;
float f;
unsigned int ui;
};
value x;
Безымянный тип структуры, объединения или битового поля.
Можно определять структуры, объединения и битовые поля, приведя внутреннее строение типа, но не вводя его названия.
Форма записи:
struct {список_объявлений_членов}список_переменных;
struct {
тип1 имя1:длина_в_битах;
тип2 имя2:длина_в_битах;
…
}список_переменных;
union {список_объявлений_членов}список_переменных;
После подобного объявления в программе можно использовать переменные данных типов, объявленные в поле список_переменных. Если в программе потребуется объявление других переменных с таким же составом членов, то придется полностью повторить приведенное выше объявление типа.
Доступ к членам структуры, объединения и битового поля.
Набор действий над структурой, объединением и битовым полем: передача в функцию в качестве аргумента или возврат из функции, получение адреса, обращение по адресу (для указателей на объекты), присваивание (для объектов одного типа), доступ к членам.
Для доступа существуют операции «обращение к члену структуры», позволяющие также получить доступ к членам объединения и битового поля.
Операция «обращение к члену структуры через переменную».
Форма записи:
операнд1.операнд2
Операнд1 – это идентификатор объекта типа структура, объединение или битовое поле, операнд2 – это идентификатор его члена.
Данная операция осуществляет доступ к члену операнд2 в объекте операнд1.
Операция «обращение к члену структуры через указатель».
Форма записи:
операнд1->операнд2
Операнд1 – это идентификатор указателя на объект типа структура, объединение или битовое поле, операнд2 – это идентификатор члена объекта, на который указывает указатель.
Данная операция осуществляет доступ к члену операнд2, сам объект хранится по адресу, на который указывает указатель операнд1.
Пример 7
color_point *pointer=&triangle[0];
for(int i=1;i<4;i++)
{
triangle[i].x=i;
triangle[i].y=i*i;
triangle[i].color=0;
printf("%d %d ",(pointer+i)->x,triangle[i].y);
}
value *p=&x;
x.f=10.5;
printf("%f ",p->f);
(*p).f-=0.4;
printf("%d ",x.i);
p->ui=5;
printf("%d ",x.ui);
Программа напечатает: 1 1 2 4 3 9 10.500000 1092721050 5
Инициализация и присваивание структур, объединений и битовых полей.
При инициализации структур и битовых полей задаются начальные значения членов структуры и битового поля.
Форма записи:
имя={список_инициализаторов}
Поле имя является идентификатором структуры или битового поля. Поле список_инициализаторов содержит список инициализаторов значений членов структуры или битового поля. Инициализаторы в списке инициализаторов должны идти в том же порядке, что и члены структуры или битового поля, которые они инициализируют. Число инициализаторов может быть меньше, чем число членов, тогда оставшаяся часть членов будет неинициализированной.
Пример 8
struct s1{
float f;
char c[5];
}d={12.5,”abc”};
При инициализации объединения задается начальное значение первого члена объединения.
Форма записи:
имя={инициализатор}
Поле имя является идентификатором объединения. Поле инициализатор содержит инициализатор, значение которого преобразуется к типу первого элемента объединения и копируется в область памяти, выделенную под объединение.
Пример 9
value v={12.5};
Объединение v будет инициализировано числом 12, хоть оно и может содержать в себе вещественные значения, так как первый член объединения имеет тип int.
Операция «присваивания» возможна только для структур, объединений и битовых полей одного типа. При присваивании производится копирование значений членов одного объекта в члены другого.