Структурированные типы данных

Перечисление

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

Пусть идентификатор времена года (seasons) может принимать одно из четырех значений: весна, лето, осень, зима (spring, summer, autumn, winter), тогда пример будет выглядеть так:

enum seasons{spring, summer, autumn, winter} p,w;

В примере переменные p,w могут принимать одно из четырех значений времени года. После введения типа seasons можно объявлять переменные (объекты) данного типа, например enum seasons a,b,c;

Введем еще одно объявление:

enum days {mon, tues, wed, thur, fri, sat,sun} my_week;

Имена, занесенные в days, представляют собой константы целого типа. Первая из них (mon) автоматически устанавливается в нуль, и каждая следующая имеет значение на единицу больше, чем предыдущая (tues=1, wed=2 и т.п.). Можно присвоить константам определенные значения целого типа (именам, не имеющим их, будут, как и раньше, назначены значения предыдущих констант, увеличенные на единицу). Например:

enum days {mon=5, tues=8, wed=10, thur, fri, sat} 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} a, b;

К сожалению, контроль за соответствием значений констант, присваемых переменным типа enum, не осуществляется.

void main (void)

{enum my_en {a, b=10, c,d};

my_en i1, i2,i3; //введенный тип my_en можно использовать для объявления переменных

enum vit {r, l, m=10} j1, j2;

// правильные действия

i1=a; i2=b; i3=a; j1=j2;

//некорректные действия, действия выполняются но вызывают выдачу сообщений

i1=0; i1=555; i1=r;

//ошибочные действия

// a=b; a=j1; a=i1; a=0; b=10;

}

Массивы

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

Массив состоит из многих элементов одного и того же типа. Ко всему массиву целиком можно обращаться по имени. Кроме того, можно выбирать любой элемент массива. Для этого необходимо задать индекс, который указывает относительную позицию элемента в массиве. Число элементов массива назначается при его объявлении и в дальнейшем не меняется. Если массив объявлен, то к любому его элементу можно обратиться следующим образом : указать имя массива и индекс элемента в квадратных скобках. Массивы объявляются так же, как и переменные :

int a [100]; массив а из 100 элементов целого типа : а[0], a[1],.…, a[99] (индексация всегда начинается с нуля).

char b [30];

float c [42]; - элементы массива b имеют тип char, а с – float.

Двухмерный массив представляется как одномерный, элементы которого тоже массивы. Например, объявление char а[10][20]; задает двумерный массив символов. По аналогии можно установить и большее число измерений. Элементы двухмерного массива хранятся по строкам, т.е. если проходить по ним в порядке их расположения в памяти, то быстрее всего изменяется самый правый индекс. Например, обращение к девятому элементу пятой строки запишется так: а[5][9]. Пусть задано объявление: int a[2][3]; Тогда элементы массива а будут размещаться в памяти следующим образом: а[0][0], а[0][1] а[0][2], а[1][0], а[1][1], а[1][2]. Имя массива а – это указатель константы, которая содержит адрес его первого элемента ( для нашего примера – а[0][0] ). Предположим, что а=1000. Тогда адрес элемента а[0][1] будет равен 1002 (элемент типа int занимает в памяти 2 байта), адрес следующего элемента а[0] [2] – 1004 и т.п. Что же произойдет, если вы выберете элемент, для которого не выделена память. К сожалению, компилятор не следит за этим. В результате возникнет ошибка, и программа будет работать не верно.

В языке С существует сильная взаимосвязь между указателями и массивами. Любое действие, которое достигается индексированием массива, может быть выполнено и с помощью указателей, причем последний вариант будет быстрее. Объявление int а [5]; определяет массив из пяти элементов: а[0], а[1], а[2], а[3], а[4]. Если объект у объявлен как int *у; то оператор y=&a[0]; присваивает переменной у адрес элемента а[0]. Если переменная у указывает на текущий элемент массива а, то у+1 указывает на следующий элемент, причем здесь выполняется соответствующее масштабирование для приращения адреса с учетом длины объекта (для типа int – 2 байта, long – 4 байта, double – 8 байт и т.п.). Поскольку само имя массива есть адрес его нулевого элемента, то инструкцию у=&а[0]; можно записать и в другом виде: у=а;. Тогда элемент а[i] можно представить как *(а+i). С другой стороны, если у – указатель, то следующие две записи y[i] и *(y+i) эквивалентны. Между именем массива и соответствующим указателем есть одно важное различие. Указатель – это переменная и у=а; или у++; -допустимые операции. Имя же массива – константа, поэтому конструкции вида а=у; а++; использовать нельзя, так как значение константы постоянно и не может быть изменено.

Переменные с адресами могут образовывать некоторую иерархическую структуру (могут быть многоуровневыми), типа указатель на указатель ( то есть он содержит адрес другого указателя ), указатель на указатель на указатель и т.п. Их использование будет рассмотрено ниже на примере.

Если указатели адресуют элементы одного массива, то их можно сравнивать (отношения вида: <, >, = =, ! = и др. работают правильно).

В то же время нельзя сравнивать либо использовать в арифметических операциях указатели на разные массивы (соответствующие выражения не приводят к ошибкам при компиляции, но в большинстве случаев не имеют смысла). Как и выше, любой адрес можно проверять на равенство или неравенство со значением NULL. Указатели на элементы одного массива можно также вычитать. Тогда результатом будет число элементов массива, расположенных между уменьшаемым и вычитаемым объектами.

Язык С позволяет инициализировать массив при объявлении. Для этого используется такая форма:

тип имя_массива [ ] [ ] = { список значений };

Рассмотрим примеры:

int a[5] = {0,1,2,3,4};

char с[7] = { 'a','b','c','d','e','f','g'};

int b [2] [3]= {1,2,3,4,5,6};

В последнем случае: b[0][0] = 1, b[0][1] = 2, b[0][2] = 3, b[1][0] = 4,

b[1][1] = 5, b[1][2] = 6.

В языке допускаются массивы указателей, которые объявляются, например, следующим образом: char *m[5]; . Здесь [m] – массив, содержащий адреса элементов типа char.

 

Строки символов

Язык С не поддерживает отдельный строковый тип данных, но он позволяет определить строки двумя различными способами. В первом используется массив символов, а во втором – указатель на первый символ массива. Объявление char a[10]; указывает компилятору на необходимость резервирования места для максимум 10 символов. Константа а содержит адрес ячейки памяти, в который помещено значение первого из десяти объектов типа char. Процедуры, связанные с занесением конкретной строки в массив а, копируют ее по одному символу в область памяти, на которую указывает константа а, до тех пор, пока не будет скопирован нулевой символ, оканчивающий строку. Когда выполняется функция типа printf("%s",a);, ей передается значение а, т.е. адрес первого символа, на который указывает а. Если первый символ нулевой, то работа функции printf заканчивается, а если нет, то она выводит его на экран, прибавляет к адресу единицу и снова начинает проверку на нулевой символ. Такая обработка позволяет снять ограничения на длину строки (конечно, в пределах объявленной размерности): строка может быть любой длины до тех пор, пока есть место в памяти, куда ее можно поместить.

Вторым способом определения строки является использование указателя на символ. Объявление char *b; задает переменную b, которая может содержать адрес некоторого объекта. Однако в данном случае компилятор не резервирует место для хранения символов и не инициализирует переменную b конкретным значением. Когда компилятор встречает инструкцию вида b=“Москва”, он производит следующие действия. Во-первых, как и в предыдущем случае, создает в каком-либо месте объектного модуля строку Москва, за которой следует нулевой символ. Во-вторых, присваивает значение начального адреса этой строки (адрес символа М) переменной b. Функция printf(“%s”,b); работает так же, как и в предыдущем случае, осуществляя вывод символов до тех пор, пока не встретится заключительный нуль.

Массив указателей можно инициализировать, т.е. назначать его элементам конкретные адреса некоторых заданных строк при объявлении. Если объявить char my_char [3,7], в памяти выделится следущий блок памяти (рис.4.4).

Г Р И Ш А ! \0
Э Т О \0 \0 \0 \0
Я \0 \0 \0 \0 \0 \0

Рис.4.4. Вид выделенного блока памяти при статическом выделении памяти

Если же объявить char * my-char [3] и инициализировать этими словами, то в памяти будет выделено, что значительно экономит память (рис.4.5).

Г Р И Ш А ! \0
Э Т О \0  
Я \0  

Рис.4.5.Вид выделенного блока памяти при использовании указателя

Пример:

#include <stdio.h>

#include <string.h>

#include <conio.h>

Void main(void)

{int l; clrscr();

char str_array[3] [10]={"Гриша!","это","я"};//инициализация 2-мерного массива

for (l=0; l<sizeof(str_array); l++)

printf("%c:",*(str_array[0]+l)); //распечатка 2-мерного массива символов

// для распечатки шестнадцатеричных кодов 2-мерного массива символов

// заменить оператор на

//printf("%х:",*(str_array[0]+l));

printf("\nDlina_2-mernogo_massiva=%d\n",sizeof(str_array));

char* point_array[3]={"Гриша!","это","я"};//инициализация массива указателей

printf("Obschaja_dlina_massiva_ukazatelej_i_strok=%d\n",sizeof(point_array)

+strlen(point_array[0])+ strlen(point_array[1])+strlen(point_array[2]));

}

 

Структуры

Структура – это объединение одного или более объектов (переменных, массивов, указателей, других структур и т.п.). Как и массив, она представляет собой совокупность данных. Отличием является то, что к ее элементам необходимо обращаться по имени и что различные элементы структуры не обязательно должны принадлежать одному типу. Объявление структуры осуществляется с помощью ключевого слова struct, за которым идет ее тип и далее список элементов, заключенных в фигурные скобки:

struct тип {

тип_элемента_1 имя_элемента_1;

тип_элемента_n имя_элемента_n; };

Именем элемента может быть любой идентификатор. Как и выше, в одной строке можно записывать через запятую несколько идентификаторов одного типа. Рассмотрим пример:

struct date {int day;

int month;

int year; };

Cледом за фигурной скобкой, заканчивающей список элементов, могут записываться переменные данного типа, например: struct date {int day;int month;

int year;} a,b,c; В этом случае для каждой переменной выделяется память, объем которой определяется суммой длин всех элементов структуры. Описание структуры без последующего списка не вызывает выделения никакой памяти, а просто задает шаблон нового типа данных, которые имеют форму структуры. Введенное имя типа позже можно использовать для объявления структуры, например struct date days;. Теперь переменная days имеет тип date. При необходимости структуры можно инициализировать, помещая за объявлением список начальных значений элементов. Разрешается вкладывать структуры одна в другую, например:

struct man { char name [30], fam [20];

struct date bd;

int voz; };

Определенный выше тип data включает три элемента: day, month, year, содержащий целые значения (int). Структура man включает элементы: name[30], fam[20], bd и voz. Первые два name[30] и fam[20] – это символьные массивы из 30 и 20 элементов каждый. Переменная bd представлена составным элементом (вложенной структурой) типа data. Элемент voz содержит значения целого типа (int). Теперь разрешается объявить переменные, значения которых принадлежат введенному типу:

struct man _ man_ [100];

Здесь определен массив _man_, состоящий из 100 структур типа man. В языке С разрешено использовать массивы структур. Структуры могут состоять из массивов и других структур.

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

_man­­_ [i].voz = 16;

_man­­_ [j].bd.day = 22;

_man­­_ [j].bd.year =1976;

При работе со структурами необходимо помнить, что тип элемента определяется соответствующей строкой объявления в фигурных скобках. Например, _man_ имеет тип man, year - является целым числом и т.п. Поскольку каждый элемент структуры относится к определенному типу, его имя может появляться везде, где разрешено использовать значения этого типа. Допускаются конструкции вида _man_[i] = _ man_[j]; где _man_[i] и _man_[j] - объекты, соответствующие единому описанию структуры. Другими словами, разрешается присваивать одну структуру другой по их именам.

Унарная операция & позволяет взять адрес структуры. Предположим, что задано объявление

struct date { int d,m,y; } 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->y.

 

Битовые поля

Особую разновидность структур представляют поля. Поле - это последовательность соседних бит внутри одного целого значения. Оно может иметь тип signed int либо unsigned int и занимать от 1 до 16 бит. Поля размещаются в машинном cлове в направлении от младших к старшим разрядам.

Например, структура

struct prim {int a:2; unsigned b:3; int :5;

int с:1; unsigned d:5; } f,j;

обеспечивает размещение, показанное на рис. 4.6. Если бы последнее поле было задано так: unsigned d:6;, то оно размещалось бы не в первом слове, а в разрядах 0-5 второго слова.

 

d d d d d b не используется b b b a a
                                     

Рис. 4.6.Пример блока памяти, выделенного под битовое поле

В полях типа signed крайний левый бит является знаковым. На­пример, такое поле шириной 1 бит может только хранить значения -1 и 0, так как любая ненулевая величина будет интерпретироваться как -1.

Поля используются для упаковки значений нескольких переменных в одно машинное слово с целью экономии памяти. Они не могут быть массивами и не имеют адресов, поэтому к ним нельзя применять унар­ную операцию &. Пример:

#include <stdio.h>

struct {unsigned a : 1; unsigned b : 1; unsigned y : 1; unsigned c : 2; } f;

void main () { int i;

printf("размер f=%d байт \n",sizeof(f));

f.a =f.b=1;// в поля а и b записывается 1

for (i=0;i<2;i++)

{ f.y =f.a && f.b; // коньюнкция a и b

printf(" цикл %d;f.y =%d\n", i, f.y);

f.b=0; }

f.c = f.a +!f.b;// сложение значений f.а и отрицание f.b (=1)8)

printf("f.c=%d", f.c);} // f.c=2

результаты работы:

размер f=1 байта

цикл о;f.y =1

цикл 1;f.y =0

f.c=2

 

Смеси

Смесь - это разновидность структуры, которая может хранить (в разное время) о6ъекты различного типа и размера . В результате по­является возможность работы в одной и той же области памяти с дан­ными различного вида. Для описания смеси используется ключевое слово union, а соответствующий синтаксис аналогичен синтаксису структуры.

Пусть задано объявление:

union r { int ir; float fr; char сr; } z;

Здесь ir имеет размер 2 байта, fr - 4 байта и сr - 1 байт. Для z будет выделена память достаточная, чтобы сохранять самый большой из трех приведенных типов. Таким образом, размер z будет 4 байта. В один и тот же момент времени в z может иметь значение только одна из указанных переменных (ir, fr, сr). Пример:

#include <stdio.h>

union r{ int ir; float fr; char cr;} z;

Float f;

// объявл. смесь z типа r: .Размер смеси будет определяться размером самого //длинного элемента, в данном случае fr,

Void main (void)

{//в версии Borland C++ версии 3.1 обнаружена ошибка при

//использовании в вычислениях и преобразованиях вывода

//вещественных значений элементов структур . Чтобы обойти ошибку, //выбираем вещественное значение элемента union в простую

//вещественную переменную f (f=z.fr;), а затем используем f в

//выражениях и наоборот.

printf ("размер z=%d байта \n",sizeof(z));

// sizeof(z) вычисляет длину переменной z и printf распечатывает

//вычисленную длину

printf ("введите значение z.ir \n"); //выдача приглашения для ввода

scanf ("%d",&z.ir);//ввод целого значения в элемент z.ir

printf ("значение ir =%d \n",z.ir);//вывод значения z.ir

printf ("введите значение z.fr \n"); //приглашение для ввода

//вещественного значения

scanf ("%f",&f); //ввод вещественного значения в переменную f и

z.fr=f;//запись в z.fr (фактически реализован ввод: scanf ("%f",&z.ir);.

printf ("значение fr=%f \n",f); //вывод значения вещественной переменной

printf ("введите значение z.cr \n"); // приглашение на ввод информации

flushall(); // очистка буферов ввода-вывода.

//Такая очистка буфера здесь необходима, т.к. в буфере ввода остается

//символ конца строки от предыдущего ввода, который затем введется //спецификацией %c , вместо реально набираемого символа

scanf ("%c",&z.cr); //чтение символа, введенного с клавиатуры

printf ("значение сr=%c;\n",z.cr);//вывод значения символа

}

Пример сеанса работы с программой («Enter» - это нажатие этой клавиши):

размер z= 4 байта

введите значение z.ir

7 «Enter»

значение ir=7

Введите значение z.fr

З8.345678«Enter»

Значение fr=8.345678

Введите значение z/cr

P«Enter»

Значение cr= P;

 

Директива typedef

Рассмотрим описание структуры

srtuct data { int d,m,y; };

Фактически мы ввели новый тип данных - data. Теперь его можно использовать для объявления конкретных экземпляров структуры,

например:

srtuct data a,b,c;

В язык C введено специальное средство, позволяющее назначать имена новым типам данных. Таким средством является оператор typedef. Он записывается в cледующей общей форме:

typedef тип имя;

Здесь "тип" - любой разрешенный тип данных и "имя" - любой разре­шенный идентификатор.

Рассмотрим пример:

typedef int INTEGER;

После этого можно сделать объявление:

INTEGER a,b;

Оно будет выполнять то же самое, что и привычное объявление:

int a,b;

Другими словами, INTEGER можно использовать как синоним ключевого слова int. При этом можно комбинировать объявления со словами int и INTEGER, например:

INTEGER a,b;

int o,d;

Здесь объявлены четыре переменные целого типа (фактически int a,b,c,d;).

 








Дата добавления: 2016-04-14; просмотров: 1845;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

Поделитесь с друзьями:

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.052 сек.