Данные комбинированного типа (структуры)
Структура – это объединение одного или нескольких объектов (переменных, массивов, указателей, других структур и т.д.). Как и массив, она представляет собой совокупность данных. Отличием является то, что к ее элементам необходимо обращаться по имени и что различные элементы структуры не обязательно должны принадлежать одному типу.
Объявление структуры осуществляется с помощью ключевого слова struct, за которым идет ее тип и далее список элементов, заключенных в фигурные скобки:
struct тип
{
тип элемента_1 имя элемента_1;
тип элемента_n имя элемента_n;
};
Именем элемента может быть любой идентификатор. Как и выше, в одной строке можно записывать через запятую несколько идентификаторов одного типа.
Рассмотрим пример:
sruct date
{
int day;
int month;
int year;
};
Следом за фигурной скобкой, заканчивающей список элементов, могут записываться переменные данного типа, например:
struct date {...} a, b, c;
(при этом выделяется соответствующая память). Описание без последующего списка не выделяет никакой памяти; оно просто задает форму структуры. Введенное имя типа позже можно использовать для объявления структуры, например:
struct date days;
Теперь переменная days имеет тип date.
При необходимости структуры можно инициализировать, помещая вслед за описанием список начальных значений элементов.
Разрешается вкладывать структуры друг в друга, например:
struct man
{
char name[20], fam[20];
struct date bd;
int age;
};
Определенный выше тип data включает три элемента: day, month, year, содержащий целые значения (int). Структура man включает элементы name, fam, bd и voz. Первые два – name[20] и fam[20] - это символьные массивы из 20 элементов каждый. Переменная bd представлена составным элементом (вложенной структурой) типа data. Элемент age содержит значения целого типа (int). Теперь можно определить переменные, значения которых принадлежат введенному типу:
struct man man_[100];
Здесь определен массив man_, состоящий из 100 структур типа man.
Чтобы обратиться к отдельному элементу структуры, необходимо указать его имя, поставить точку и сразу же за ней записать имя нужного элемента, например:
man_[j].age = 19;
man_[j].bd.day = 24;
man_[j].bd.month = 2
man_[j].bd.year = 1987;
При работе со структурами необходимо помнить, что тип элемента определяется соответствующей строкой описания в фигурных скобках. Например, массив man_ имеет тип man, year является целым числом и т.п. Поскольку каждый элемент структуры относится к определенному типу, его имя может появиться везде, где разрешено использование значений этого типа. Допускаются конструкции вида man_[i]=man_[j]; где man_[i] и man_[j] – объекты, соответствующие единому описанию структуры. Другими словами, разрешается присваивать одну структуру другой по их именам.
Унарная операция & позволяет взять адрес структуры. Предположим, что определена переменная day:
struct date {int d, m, у;} day;
Здесь day – это структура типа date, включающая три элемента: d, m, у. Другое определение
struct date *db;
устанавливает тот факт, что db - это указатель на структуру типа date.
Запишем выражение:
db = &day;
В этом случае для выбора элементов d, m, у структуры необходимо использовать конструкции:
(*db).d; (*db).m; (*db).y;
Действительно, db - это адрес структуры, *db - сама структура. Круглые скобки здесь необходимы, так как точка имеет более высокий, чем звездочка, приоритет. Для аналогичных целей в языке Си предусмотрена специальная операция –>. Эта операция выбирает элемент структуры и позволяет представить рассмотренные выше конструкции в более простом виде:
db -> d; db -> m; db -> у;
Пример 13. Рассмотрим структуру, в которой хранится информация о студенте. Далее в программе мы присваиваем определенным полям нужные значения, в зависимости от значения селектора варианта (Curs).
struct Student
{
char FIO[20];
int YearBirth;
bool sex;
char Group[20];
float Stipendia;
int Curs;
int SchoolNo;
char SchoolCity[7];
int KruzhokNo[5];
char DiplomTema[50];
char MestoPractiki[50];
} Stud1;
. . .
switch(Stud1.Curs)
{
case 1 : Stud1.SchoolNo = 345;
Stud1.SchoolCity = "Москва";
break;
case 2 : Stud1.KruzhokNo[1]:=22;
break;
case 5 : Stud1.DiplomTema="Программная система на Си";
Stud1.MestoPractiki="МГУП";
break;
}
Перечисления
В языке Си перечислением (множеством) называется группа элементов, ассоциированных с единым именем. Перечисляемый тип данных реализован в языке Си с целью использования в программах возможностей математической теории конечных множеств. Множество (или перечисление) описывается с помощью конструкции:
enum <имя множества> (элементы множества);
Например:
enum seasons (spring, summer, autumn, winter);
enum seasons а, b, с;
Здесь введен новый тип данных seasons и определены переменные этого типа. Каждая из них (а, b, c) может принимать одно из четырех значений: spring, summer, autumn и winter. Эти переменные можно было определить сразу при описании типа:
enum seasons (spring, summer, autumn, winter) a, b, с;
Рассмотрим еще один пример:
enum days {mon, tues, wed, thur, fri, sat, sun} my_week;
Имена, занесенные в days (также как и в seasons в предыдущем примере), представляют собой константы целого типа. Первая из них (mon) автоматически устанавливается в нуль, и каждая следующая имеет значение на единицу больше, чем предыдущая (tues=1, wed=2 и т.д.).
Можно присвоить константам определенные значения целого типа (именам, не имеющим их, будут, как и раньше, назначены значения предыдущих констант, увеличенные на единицу). Например:
enum days (man=5, tues=8, wed=10, thur, fri, sat, sun} my_week;
После этого mon=5, tues=8,wed=10, thur=11, fri=12, sat=13, sun=14.
Тип enum можно использовать для задания констант true=1 и false=0, например:
enum t_f (false, true) а, b;
Пример 14. Пусть требуется ввести строку, состоящую из латинских букв, цифр, пробелов. Требуется напечатать заданный текст, удалив из него все цифры.
int i;
char z; /*Текущий вводимый символ*/
printf("\nНапишите предложение с точкой в конце:\n");
for(i=0;z!='.';i++)
{
scanf("%c",&z);
if(z=='0'||z=='1'||z=='2'||z=='3'||z=='4')continue;
if(z=='5'||z=='6'||z=='7'||z=='8'||z=='9')continue;
printf("%c",z);
}
Объединения
Обединения – это некоторая переменная, которая может хранить (в разное время) объекты различного типа и размера. В результате появляется возможность работы в одной и той же области памяти с данными различного вида. Для описания объединения используется ключевое слово union, а соответствующий синтаксис аналогичен структурам. Пусть задано определение:
union r
{
int ir;
float fr;
char cr;
} z;
Здесь ir имеет размер 2 байта, fr - 4 байта, cr - 1 байт. Размер переменной z будет равен размеру самого большого из трех приведенных типов (т.е. 4 байтам). В один и тот же момент времени z может иметь значение только одной из переменных ir, fr или cr.
Указатели
Указатель - это адрес памяти, распределяемой для размещения идентификатора (в качестве идентификатора может выступать имя переменной, массива, структуры, строкового литерала). В том случае, если переменная объявлена как указатель, то она содержит адрес памяти, по которому может находиться скалярная величина любого типа. При объявлении переменной типа указатель, необходимо определить тип объекта данных, адрес которых будет содержать переменная, и имя указателя с предшествующей звездочкой (или группой звездочек). Формат объявления указателя:
<спецификатор типа> [< модификатор> ] * имя указателя.
Спецификатор типа задает тип объекта и может быть любого основного типа, типа структуры, смеси (об этом будет сказано ниже). Задавая вместо спецификатора-типа ключевое слово void, можно своеобразным образом отсрочить спецификацию типа, на который ссылается указатель. Переменная, объявляемая как указатель на тип void, может быть использована для ссылки на объект любого типа. Однако для того, чтобы можно было выполнить арифметические и логические операции над указателями или над объектами, на которые они указывают, необходимо при выполнении каждой операции явно определить тип объектов. Такие определения типов может быть выполнено с помощью операции приведения типов.
В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, near, far, huge. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной объявленной как указатель, зависит от архитектуры компьютера и от используемой модели памяти, для которой будет компилироваться программа. Указатели на различные типы данных не обязательно должны иметь одинаковую длину.
Для модификации размера указателя можно использовать ключевые слова near, far, huge.
unsigned int * a; /* переменная а представляет собой указатель
на тип unsigned int (целые числа без знака) */
double * x; /* переменная х указывает на тип данных с
плавающей точкой удвоенной точности */
char * fuffer; /* объявляется указатель с именем fuffer
который указывает на переменную типа char */
double nomer;
void *addres;
. . .
addres = & nomer;
(double *)addres ++;
/* Переменная addres объявлена как указатель на объект любого типа. Поэтому ей можно присвоить адрес любого объекта (& - операция вычисления адреса). Однако, как было отмечено выше, ни одна арифмитическая операция не может быть выполнена над указателем, пока не будет явно определен тип данных, на которые он указывает. Это можно сделать, используя операцию приведения типа (double *) для преобразования addres к указателю на тип double, а затем увеличение адреса. */
const * dr;
/* Переменная dr объявлена как указатель на константное выражение, т.е. значение указателя может изменяться в процессе выполнения программы, а величина, на которую он указывает, нет. */
unsigned char * const w = &obj.
/* Переменная w объявлена как константный указатель на данные типа char unsigned. Это означает, что на протяжение всей программы w будет указывать на одну и ту же область памяти. Содержание же этой области может быть изменено. */
Методы доступа к массивам
В языке Си между указателями и массивами существует тесная связь. Например, когда объявляется массив в виде int array[25], то этим определяется не только выделение памяти для двадцати пяти элементов массива, но и для указателя с именем array, значение которого равно адресу первого по счету (нулевого) элемента массива, т.е. сам массив остается безымянным, а доступ к элементам массива осуществляется через указатель с именем array. С точки зрения синтаксиса языка указатель arrey является константой, значение которой можно использовать в выражениях, но изменить это значение нельзя.
Поскольку имя массива является указателем допустимо, например, такое присваивание:
int arrey[25];
int *ptr;
ptr = array;
Здесь указатель ptr устанавливается на адрес первого элемента массива, причем присваивание ptr = arrey можно записать в эквивалентной форме ptr = &arrey[0].
Для доступа к элементам массива существует два различных способа. Первый способ связан с использованием обычных индексных выражений в квадратных скобках, например, array[16] = 3 или array[i+2] = 7. При таком способе доступа записываются два выражения, причем второе выражение заключается в квадратные скобки. Одно из этих выражений должно быть указателем, а второе - выражением целого типа. Последовательность записи этих выражений может быть любой, но в квадратных скобках записывается выражение следующее вторым. Поэтому записи array[16] и 16[array] будут эквивалентными и обозначают элемент массива с номером шестнадцать. Указатель, используемый в индексном выражении не обязательно должен быть константой, указывающей на какой-либо массив, это может быть и переменная. В частности после выполнения присваивания ptr = array доступ к шестнадцатому элементу массива можно получить с помощью указателя ptr в форме ptr[16] или 16[ptr].
Второй способ доступа к элементам массива связан с использованием адресных выражений и операции разадресации в форме *(array+16) = 3 или *(array+i+2) = 7. При таком способе доступа адресное выражение равное адресу шестнадцатого элемента массива тоже может быть записано разными способами *(array+16) или *(16+array).
При реализации на компьютере первый способ приводится ко второму, т.е. индексное выражение преобразуется к адресному. Для приведенных примеров array[16] и 16[array] преобразуются в *(array+16).
Для доступа к начальному элементу массива (т.е. к элементу с нулевым индексом) можно использовать просто значение указателя array или ptr. Любое из присваиваний
*array = 2;
array[0] = 2;
*(array+0) = 2;
*ptr = 2;
ptr[0] = 2;
*(ptr+0) = 2;
присваивает начальному элементу массива значение 2, но быстрее всего выполнятся присваивания *array=2 и *ptr=2, так как в них не требуется выполнять операции сложения.