14. Структуры и другие типы данных

СТРУКТУРЫ ДАННЫХ

СТРУКТУРНЫЕ ШАБЛОНЫ, ТЕГИ И ПЕРЕМЕННЫЕ

ДОСТУПНЫЕ ЧАСТИ СТРУКТУРЫ

СТРУКТУРНЫЕ УКАЗАТЕЛИ

СТРУКТУРНЫЕ МАССИВЫ

ФУНКЦИИ И СТРУКТУРЫ

ОБЪЕДИНЕНИЯ

СОЗДАНИЕ НОВЫХ ТИПОВ

КЛЮЧЕВЫЕ СЛОВА struct, union, typedef

ОПЕРАЦИИ ->

Успех программы часто зависит от удачного выбора способа представления данных, с которыми она должна работать. В этом отношении языку Си очень повезло (и не случайно), так как он обладает очень мощными средствами представления сложных данных. Этот тип данных, называемых "структурой", не только достаточно гибок для представления разнообразных данных, но, кроме того, он позволяет пользователю создавать новые типы. Если вы знакомы с "записями" языка Паскаль, вам должны быть удобны структуры.

Посмотрим на конкретном примере, почему структуры нам необходимы и как их создавать и использовать.

ТИПОВАЯ ЗАДАЧА: ИНВЕНТАРИЗАЦИЯ КНИГ

Гвен Гленн хочет напечатать опись своих книг. Она хотела бы занести в нее различную информацию о каждой книге: ее название, фамилию автора, издательство, год издания, число страниц, тираж и цену. Теперь некоторые из этих элементов, такие, как название, можно записать в массив строк. Другие элементы требуют массив целого типа или массив типа float. Если работать с семью различными массивами и следить за веси содержащейся в них информацией, можно сойти с ума, особенно если Гвен желает иметь несколько списков - список, упорядоченный по названиям, список, упорядоченный по авторам, по цене и т. д. Гораздо лучше было бы использовать один массив, в котором каждый элемент содержал бы всю информацию о книге.

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

Сначала посмотрите на программу и ее результат, а потом мы рассмотрим основные вопросы.

/* инвентаризация одной книги */

#include

#define MAXTIT 41 /* максимальная длина названия + 1 */

#define MAXAUT 31 /* максимальная длина фамилии автора + 1 */

struct book { /* шаблон первой структуры: book

является именем типа структуры */

char title [MAXTIT]; /* символьный массив для названия */

char author [MAXAUT]; /* символьный массив для фамилии автора */

float value; /* переменная для хранения цены книги */

}; /* конец шаблона структуры */

main( )

{

struct book libry; /* описание переменной типа book */

printf(" Введите, пожалуйста, название книги.\n");

gets(libry. title); /* доступ к элементу title */

printf(" Теперь введите фамилию автора.\n");

gets(libry.author);

printf(" Теперь введите цену.\n");

scanf(" %f ", &libry.value);

printf("%s, %s: %p.2f \n", libry.title, libry.autor,

libry.value);

printf("%s: \" %s \" \(%p.2f\)\n", libry.author,

libry.title, libry.value);

}

Вот образец работы программы:

Введите, пожалуйста, название книги.

Искусство программирования для ЭВМ

Теперь введите фамилию автора.

Д. Кнут

Теперь введите цену.

5р.67

Искусство программирования для ЭВМ, Д. Кнут: 5р.67

Д. Кнут: "Искусство программирования для ЭВМ" (5р. 67)



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

1. Как устанавливать формат или "шаблон" для структуры.

2. Как объявлять переменную, соответствующую этому шаблону.

3. Как осуществлять доступ к отдельным компонентам структурной переменной.

УСТАНОВКА СТРУКТУРНОГО ШАБЛОНА

Структурный шаблон является основной схемой, описывающей как собирается структура. Наш шаблон выглядел бы так:

struct book

{

char title [MAXTIT];

char author [MAXAUT];

float value;

};

Этот шаблoн описывает структуру, составленную из двух символьных массивов и одной переменной типа tloat. Давайте рассмотрим его детально.

Первым стоит ключевое слово struct; оно определяет, что все, что стоит за ним, является структурой. Далее следует необязательный "тег" (имя типа структуры) - слово book, являющееся сокращенной меткой, которую мы можем использовать позже для ссылки на эту структуру. Поэтому где-нибудь позже у нас будет описание:

struct book libry;

которое объявляет libry структурой типа book.

Далее у нас есть список "элементов" структуры, заключенный в парные фигурные скобки. Каждый элемент определяется своим собственным описанием. Например, элемент title является символьным массивом, состоящим из MAXTIT-элементов. Как мы уже отмечали, элементы могут быть данными любого типа, включая другие структуры! И наконец, мы ставим точку с запятой, завершающую определение шаблона.

Вы можете разместить этот шаблон за пределами любой функции (вне), как мы и сделали, или внутри определения функции. Если мы установили шаблон внутри функции, то он может использоваться только внутри этой функции. Если вне, то шаблон доступен всем функциям программы, следующим за его определением. Например, в другой функции вы можете определить

struct book dickens;

и эта функция должна иметь переменную dickens, которая следует за нашим шаблоном.

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

ОПРЕДЕЛЕНИЕ СТРУКТУРНЫХ ПЕРЕМЕННЫХ

Слово "структура" используется двояко. Во-первых, в смысле "структурного шаблона", о котором мы только что рассказали. Шаблон является схемой без содержания; он сообщает компилятору, как делать что-либо, но нс вызывает никаких действий в программе. Следующий шаг заключается в создании "структурной переменной"; это и есть второй смысл слона структура. Строка нашей программы, создающая структурную переменную, выглядит так:

struct book libry;

На основании этого оператора компилятор создаст переменную libry. Согласно плану, установленному шаблоном book, он выделяет память для символьного массива, состоящего из MAXTIT-элементов, для символьного массива из MAXAUT-элементов и для переменной типа float. Эта память объединяется под именем libry. (В следующем разделе мы расскажем, как ее "разъединить", если понадобится.)

РИС. 14.1. Распределение памяти для структуры.

В этом описании struct book играет ту же роль, что и int или float в своих описаниях. Например, мы могли бы описать две переменные типа struct book или даже указатель на этот тип структуры:

struct book doyle panshin, *ptbook;

Каждая структурная переменная, doyle и panshin, имела бы части title, author и value. Указатель ptbook мог бы ссылаться на doyle, panshin или любую другую book-структуру. Для компьютера оператор нашей программы

struct book libry;

является сокращенной записью

struct book libry;

является сокращенной записью

struct book {

char title [MAXTIT];

char author [MAXAUT];

float value;

} libry; /* присоединяет имя переменной к шаблону */


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

struct { /* без имени типа структуры */

char title [MAXTIT];

char author [MAXAUT];

float value;

} libry;


Форма с именем типа структуры удобнее, если вы используете структурный шаблон более одного раза.

Есть один аспект определения структурной переменной, который не нашел отражения в нашем примере - инициализация. Теперь мы хотим заняться этим вопросом.

Инициализация структуры

Мы видели, как инициализируются переменные и массивы:

int count = 0;

static int fibo[ ]={0, 1, 1, 2, 3, 5, 8};

Можно ли инициализировать и структурную переменную? Да, если структурная переменная будет внешней или статической. Здесь следует иметь в виду, что принадлежность структурной переменной к внешнему типу зависит от того, где определена переменная, а не где определен шаблон. В нашем примере шаблон book является внешним, а переменная libry - внутренней, так как она определена внутри функции и по умолчанию располагается в классе автоматической памяти. Предположим, мы создали такое описание:

static struct book libry;

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

static struct book libry={"Пират и девица",

"Рене Вивот",

1р.95 } ;

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

Продолжим наши разъяснения свойств структуры.

ДОСТУП К ЭЛЕМЕНТАМ СТРУКТУРЫ

Структура является разновидностью супермассива, в котором один элемент может быть массивом типа char, следующий - float и еще один int. Обычно можно обращаться к отдельным элементам массива, используя индекс. Как это сделать для отдельных элементов структуры? Для этого мы используем символ ".", обозначающий операцию получения элемента структуры. Например, libry .value является элементом value структуры libry. Можно применять libry.value точно так же, как вы использовали бы любую другую переменную типа float. Можно применять и libry.title точно-так же, как массив типа char. Поэтому мы могли бы использовать выражения, подобные

gets(libry.title)

и

scanf(" %f ", &libry.value);

В сущности .title, .author и .value играют роль индексов для структуры book.

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

struct book spiro; gerald;

gets (spiro.title);

gets (gerald.title);

.title ссылается на первый элемент структуры book.

Посмотрите, как в самой первой программе мы печатали содержимое структурной переменной libry в двух различных форматах; она демонстрирует нам возможность использования элементов структуры.

Мы изложили самое основное. Теперь хотелось бы расширите ваш кругозор и рассмотреть некоторые понятия, связанные ее структурами, включая массивы структур, структуры структур, указатели на структуры, а также функции и объединения.

МАССИВЫ СТРУКТУР

Настроим нашу программу инвентаризации на обработку, если потребуется, двух или трех (или, может быть, даже большего числа) книг. Очевидно, каждую книгу можно описать структурной переменной типа book. Для описания двух книг нам нужны две такие переменные и т. д. Для обработки нескольких книг потребуется массив таких структур, и мы его создали в программе, показанной на рис. 14.2.

/* инвентаризация большого количества книг */

#include

#define MAXTIT 40

#define MAXAUT 40

#define MAXBKS 100 /* максимальное количество книг */

#define STOP " " /* нулевая строка прекращает ввод */

struct book { /* создание шаблона типа book */

char title [MAXTIT];

char author [MAXAUT];

float value; };

main ( )

{

struct book libry[MAXBKS]; /* массив структур типа book */

int count = 0;

int index;

printf("Введите, пожалуйста, название книги.\n");

printf(" Нажмите клавишу [ввод] в начале строки для останова.\n");

while(strcmp(gets(libry [count].title), STOP) != 0 &&

count < MAXBKS)

{ printf("Введите теперь фамилию автора.\n");

gets(libry [count].author);

printf("Введите теперь цену.\n");

scanf(" %f", & libry [count++].value);

while(getchar()!='n'); /* очистите строку ввода */

if(counts < MAXBKS)

printf("Введите название следующей книги.\n");

} printf ("Вот список ваших книг: \n");

for(index = 0; index < count; index++)

printf("%s, %s: %p.2f\n", libry [index].title, libry[index].author,

libry[index].value);

}

РИC. 14.2. Программа инвентаризации большого количества книг.


Вот пример работы программы:

Введите, пожалуйста, название книги.

Нажмите клавишу [ввод] в начале строки для останова.

Искусство программирования

Введите теперь фамилию автора. Д.Кнут

Введите теперь цену.

5р.67

Введите название следующей книги.

... еще вводы...

Вот список ваших книг:

Искуство программирования для ЭВМ, Д.Кнут: 5р.67

ПЛ/1 для программистов, Скотт Р., Сондак Н: 1р.08

Программирование на языке Паскаль, П. Грогоно: 1р.30

Язык Фортран 77, Г. Кантан: 0р.80

Трансляция языков программирования, Ф. Вайнгартен: 0р.75

Язык Эсперанто, М.И. Исаев: 0р.60

Ассемблеры и загрузчики, Д.Баррон: 0р.30

Структурное программирование, У. Дал, Э. Дейкстра, К. Хоор: 1р.11

Операционные системы, Г. Катцан: 2р.25

Параллельные вычислительные системы, Б.А.Головкин: 2р.50


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

Описание массива структур

Процесс описания массива структур совершенно аналогичен описанию любого другого типа массива:

struct book libry [MAXBKS];

Этот оператор объявляет libry массивом, состоящим из MAXBKS-элементов. Каждый элемент массива представляет собой структуру типа book. Таким образом, libry[0] является book-структурой, libry[1] - второй book-структурой и т. д. Рис. 14.3 может помочь вам представить это. Имя libry само по себе не является именем структуры; это имя массива, содержащего структуры.

РИС. 14.3. Maccив структур.

Определение элементов массива структур

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

libry [0].value value - первый элемент массива

libry [4].title title - пятый элемент массива

Заметим, что индекс массива присоединяется к libry, а не к концу имени:

libry. value[2] /* неправильно */

libry[2].value /* правильно */


Мы используем libry[2].value, потому что libry[2] является именем структурной переменной точно так же, как libry[l] является именем другой структурной переменной, а ранее doyle было именем структурной переменной.

Между прочим, что бы это значило?

libry[2].title[4]

Это был бы пятый элемент элемента title (т. е. title[4]) структуры типа book, описанный третьей структурой (т.e. libry[2]). В нашем примере им был бы символ р. Это означает, что индексы, находящиеся справа от операции ".", относятся к отдельным элементам, в то время как индексы, расположенные слева от операции, относятся к массивам структур.

Теперь покончим с этой программой.

Детализация программы

Главное отличие ее от нашей первой программы заключается в том, что теперь создается цикл для считывания названий книг. Мы начинаем цикл с while-условия:

while(strcmp(gets(libry [count].title), STOP) != 0

&& count < MAXBKS)

Выражение gets(libry [count].title) считывает вводимую строку, содержащую название книги. Функция strcmp( ) сравнивает эту строку со STOP, которая является " " , т.e. пустой строкой. Если пользователь нажмет клавишу [ввод] в начале строки, то перепишется пустая строка и цикл закончится. Мы также должны проверять, не превысило ли число считанных на текущий момент книг предельного размера массива.

В программе есть странная строка while(getchar ( ) ! = '\n'); /* очистить строку ввода */

Она включена для того, чтобы использовать особенность функции scanf( ), которая игнорирует символы пробела и новой строки. Когда вы отвечаете на запрос об элементе value в структуре book, то вводите что-нибудь вроде

12.50 [ввод]

При этом передается последовательность символов

12.50\n

Функция scanf( ) собирает символы 1, 2, . , 5, 0, но опускает символ \n, стоящий там, и ожидает, что следом придет еще какой-нибудь оператор чтения. Если пропустить нашу странную строку, то следующим оператором чтения будет gets(libry [count].title) в операторе управления циклом. Он прочел бы оставшийся символ новой строки как первый символ, и программа решила бы, что мы послали сигнал останова. Поэтому мы и вставили такую странную строку. Если вы поняли это, то увидите, что она "проглатывает" символы до тех пор, пока не найдет и не получит символ новой строки. Функция ничего не делает с данным символом, только принимает его от устройства ввода. Это приводит к тому, что функция gets( ) снова начинает работать. Вернемся теперь к изучению структур.

ВЛОЖЕННЫЕ СТРУКТУРЫ

Иногда бывает удобно, чтобы одна структура содержалась или была "вложена" в другую. Например, Шалала Пироски строит структуру, содержащую информацию о ее друзьях. Один элемент структуры - это, конечно, имя друга. Однако имя можно представить самой структурой с разными элементами для имени и фамилии. На рис. 14.4 приведен сокращенный пример деятельности Шалалы.

/* пример вложенной структуры */

#define LEN 20

#define M1 "Спасибо за прекрасный вечер,"

#define M2 "Вы, конечно, правы, что"

#define M3 " -своеобразный парень. Мы должны собраться"

#define М4 " отведать очень вкусный"

#define M5 "и немного повеселиться."

struct names { /*первый структурный шаблон */

char first[LEN];

char last[LEN], };

struct guy { /* второй шаблон */

struct names handle; /* вложенная структура */

char favfood[LEN];

char job[LEN];

float income;

};

main( ) {

static struct guy fellow = { /*инициализация переменной */

{" Франко," " Уотэл"},

" баклажан",

" вязальщик половиков",

15435.00 };

printf("Дорогой %s, \n \n," fellow.handle.first);

printf(" %s %s.\n", M1, fellow.handle.first);

printf(" %s %s\n" , M2, fellow.job);

printf(" %s \n" , M3);

printf(" %s %s %s\n\n", M4, fellow.favfood, M5);

printf(" %40s %s \n", " " , " До скорой встречи" );

printf(" %40s %s\n", " ", Шалала");

}


РИС. 14.4. Программа вложенной структуры.

Вот результат работы программы:

Дорогой Франко,

Спасибо за прекрасный вечер, Франко.

Bы, конечно, правы. что вязальщик половиков - своеобразный парень.

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

До скорой встречи,

Шалала

Во-первых, следует рассказать о том, как поместить вложенную структуру в шаблон. Она просто описывается точно так же, как это делалось бы для переменной типа int:

struct names handle;

Это означает, что handle является переменной типа struct names. Конечно, файл должен также содержать шаблон для структуры типа names.

Во-вторых, следует рассказать, как мы получаем доступ к элементу вложенной структуры. Нужно дважды использовать операцию "." :

fellow.handle.first = = " Франко";

Мы интерпретируем эту конструкцию, перемещаясь слева направо;

(fellow.handle).first

То есть первым находим элемент fellow, далее элемент handle структуры fellow, а затем его элемент first. Теперь рассмотрим указатели.

УКАЗАТЕЛИ НА СТРУКТУРЫ

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

Следующий короткий пример (рис. 14.5) показывает, как определять указатель на структуру и как использовать его для получения элементов структуры.

/* указатель на структуру */

#define LEN 20 struct names {

char first [LEN];

char last [LEN]; };

struct guy {

struct names handle;

char favfood [LEN];

char job [LEN];

float income; };

main( ) {

static struct guy fellow [2] = {

{ "Франко", "Уотэл" }

"баклажан",

" вязальщик половиков ",

15435.00},

{{"Родней", "Свилбели" },

"лососевый мусс", "декоратор интерьера",

35000.00 } };

struct guy *him; /* ЭТО - - указатель па структуру */

printf("адрес #1: %u #2 : %u\n", &fellow[0],

&fellow[1]);

him = &fellow[0]; /* сообщает указателю, на что ссылаться */

printf("указатель #1: %u #2: %u \n ", him, him + 1);

printf("him -> доход $ %.2f: (*him).доход $ %.2f \n",

him -> доход, (*him).доход);

him++; /* указывает на следующую структуру */

printf("him -> favfood is %s : him -> names.last is %s\n",

him-> favfood, him -> handle.last);}


РИС. 14.5. Программа с использованием указателя на структуру.


Вот, пожалуйста, ее выход:

адрес #1: 12 #2: 96

указатель #1: 12 #2: 96

him -> доход $15435.00: (*him).доход $15435.00

him -> favfood лососевый мусс: him -> names.last

- Свилбели

Сначала посмотрим, как мы создали указатель на структуру guy. Затем научимся определять отдельные элементы структуры при помощи указателей.

Описание и инициализация указателя на структуру

Вот самое простое описание, какое только может быть:

struct guy *him;

Первым стоит ключевое слово struct, затем слово guy, являющееся именем структурного шаблона, далее * и за нею имя указателя. Синтаксис тот же, как для описаний других указателей, которые мы видели.

Теперь можно создать указатель him для ссылок на любые структуры типа guy. Мы инициализируем him, заставляя его ссылаться нa fellow[0]; заметим, что мы используем операцию получения адреса:

him = &fellow[0];

Первые две выведенные строки показывают результат этого присваивания. Сравнивая две строки, мы видим, что him ссылается на fellow[0], a him+1 - на fellow[l]. Заметим, что добавление 1 к him прибавляет 84 к адресу. Это происходит потому, что каждая guy-структура занимает 84 байта памяти: первое имя - 20, последнее имя - 20, favfood - 20, job - 20 и income - 4 байта (размер элемента типа float в нашей системе).

Доступ к элементу структуры при помощи указателя

him ссылается на структуру fellow[0]. Каким образом можно использовать him для получения значения элемента структуры fellow[0]? Третья выведенная строка даст для этого два способа.

Первый способ, наиболее общий, использует новую операцию ->. Она заключается в вводе дефиса (-) и следующего за ним символа "больше чем" (>). Пример помогает понять смысл сказанного:

him -> income - это fellow[0].income,

если him = &fellow[0]

Другими словами, структурный указатель, за которым следует операция ->, работает так же, как имя структуры с последующей операцией ".". (Мы не можем сказать him.income, потому что him не является именем структуры.)

Очень важно отметить, что him-указатель, а him - > income - элемент структуры, на которую делается ссылка. Таким образом, в этом случае him - > income является переменной типа float.

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

если him == &fellow[0], то *him == fellow[0]. Это так, поскольку & и * - взаимообратные операции. Следовательно, после подстановки

fellow[0].income == (*him).income

Круглые скобки необходимы, поскольку операция "." имеет приоритет выше, чем *.

Отсюда можно сделать вывод, что если him является указателем на структуру fellow[0], то следующие обозначения эквивалентны:

fellow[0].income == (*him).income == him->income

Давайте теперь посмотрим, как взаимодействуют структуры и функции.

Резюме: операции над структурами и объединениями

Эта операция используется с именем структуры или объединения для определения элемента этой структуры или объединения. Если name является именем структуры, a member - элементом, определенным структурным шаблоном, то name.member обозначает этот элемент структуры. Операция получения элемента может использоваться таким же образом для объединений.



Примеры

struct {

int code;

float cost;

} item;

item.code = 1265;


Данный оператор присваивает значение элементу code структуры item.

II. ОПЕРАЦИЯ КОСВЕННОГО ПОЛУЧЕНИЯ ЭЛЕМЕНТА: ->

Эта операция используется с указателем на структуру или объединение для определения элемента структуры или объединения. Предположим, что ptrstr является указателем на структуру и что member элемент, определенный структурным шаблоном. Тогда

ptrstr -> member

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

Пример

struct {

int code

float cost;

} item, *ptrst;

ptrst = &item;

ptrst -> code = 3451;

Операторы присваивают значение элементу code структуры item. Следующие три выражения эквивалентны:

ptrst->code item.code (*ptrst).code

ПЕРЕДАЧА ИНФОРМАЦИИ О СТРУКТУРАХ ФУНКЦИЯМ

Вспомним, что аргументы функции передают значения в функцию. Каждое значение является либо числом типа int или float, либо ASCII-кодом или адресом. Структура гораздо сложнее, чем отдельная переменная, поэтому неудивительно, что саму структуру нельзя использовать в качестве аргумента функции. (Это ограничение снято в некоторых новых рeализациях.) Однако есть способы ввести информацию о структуре внутрь функции. Рассмотрим три способа (на самом деле два с вариациями).

Использование элементов структуры

Поскольку элемент структуры является переменной с единственным значением (т.е. типа int или одного из его "родственников" - char, float, double или указатель), он может быть передан как аргумент функции. Простая программа финансовых расчетов на рис. 14.6, которая прибавляет взнос клиента к его счету, иллюстрирует этот способ. Заметим, между прочим, что мы объединили определение шаблона, описание переменной и инициализацию в один оператор.

/* передача элементов структуры как аргументов функции */

struct funds {

char *bank;

float bankfund;

char *save;

float savefund; }

stan = { " Банк синьора Помидора",

1023.43,

" Сбережения и займы Снупи",

4239.87 };

main( )

{

float total, sum( );

extern struct funds stan; /* необязательное описание */

printf("У Стэна всего %.2f долл.\n", sum(stan.bankfund,

stan.savefund));

}

/* складывает два числа типа float */

float sum(x, у);

float x, у;

{ return( x + y); }


РИС. 14.6. Программа, передающая функции аргументы, являющиеся элементами структуры.

Результат выполнения этой программы:

У Стэна всего 5263.30 долл.

Вот это да, она работает. Заметим, что функция sum( ) "не знает", или же си безразлично, являются ли элементами структуры фактические аргументы; она только "требует", чтобы они имели тип float.

Конечно, если вы хотите, чтобы программа воздействовала на значение элемента в вызывающей программе, можно передать ей адрес этого элемента:

modify(&stan.bank fund);

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

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

Использование адреса структуры

Мы будем решать ту же самую задачу, что и прежде, но при этом использовать адрес структуры в качестве аргумента. Это хорошо, поскольку адрес представляет собой только одно число. Так как функция должна работать со структурой funds, она тоже должна использовать шаблон funds. См. рис. 14.7.

/* передача адреса структуры в функцию */

struct funds {

char *bank;

float bankfund;

char *save;

float savefund;

} stan = {

"Банк синьора Помидора" ,

1023.43,

" Сбережения и займы Снупи" ,

4239.87

};

main( )

{

float total, sum( );

printf(" У Стэна всего %.2f долл.\n", sum(&stan) );

}

float sum (money)

struct funds *money;

}

return( money-> bankfund + money-> savefund);

}


PИC. 14.7. Программа, передающая функции адрес структуры. Эта программа тоже выдает

У Стэна всего 5263.30 долл.

Функция sum( ) имеет указатель (money) на структуру fund. Передача адреса &stan в функцию заставляет указатель money ссылаться на структуру stan. Затем используем операцию - > для получения значений элементов stan.bankfund и stan.savefund.

Эта функция также имеет доступ к названиям учреждений, хотя их не использует. Заметим, что мы должны применять операцию & для получения адреса структуры. В отличие от имени массива имя структуры само по себе нe является синонимом своего адреса.

Наш следующий способ применим к массивам структур и является вариантом данного способа.

Использование массива

Предположим, у нас есть массив структур. Имя массива является синонимом его адреса, поэтому его можно передать функции. С другой стороны, функции будет необходим доступ к структурному шаблону. Чтобы показать, как такая программа работает (рис. 14.8), мы расширим нашу программу таким образом, чтобы она обслуживала двух человек, а мы, следовательно, имели бы массив двух структур funds.

/* передача массива структур в функцию */

struct funds {

char *bank;

float bankfund;

char *save;

float savefund; }

jones[2] ={

{ " Банк синьора Помидора" ,

1023.43,

" Сбережения и займы Снупи" ,

4239.87 },

{ " Банк Честного Джека",

976.57,

"Накопления по предварительному плану",

1760.13 } };

main( )

{

float total, sum( );

printf("Джонсы имеют всего %.2f долл.\n", sum(jones));

}

float sum(money);

struct funds *money;

{

float total;

int i;

for( i = 0, total = 0; i < 2; i++, money++ )

total += money- > bankfund + money -> savefund;

return(total);

}


РИС. 14. 8. Программа, передающая функции массив структур.

Программа выдает

Джонсы имеют всего 8000.00 долл.

(Что за круглая сумма! Можно подумать, что эти цифры взяты с потолка.) Имя массива jones является указателем на массив. В частности, оно ссылается на первый элемент массива, который является структурой jones[0]. Таким образом, в начале указатель money за дается через

money = &jones[0];

Затем использование операции - > позволяет нам добавить два вклада для первого Джонса. Это действительно очень похоже на последний пример. Далее, цикл for увеличивает указатель money на 1. Теперь он ссылается на следующую структуру, jones[1], и остаток вкладов может быть добавлен к total.

Вот два основных замечания:


1. Имя массива можно использовать для передачи в функцию указателя на первую структуру в массиве.

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

sum(&jones[0])

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

СТРУКТУРЫ: ЧТО ДАЛЬШЕ?

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

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

РИС. 14.9. Структура двоичного дерева.

Является ли эта разветвленная конструкция более эффективной чем массив? Рассмотрим случай дерева с 10 уровнями узлов. Если вы составите его, то найдете 1023 узла, в которых вы можете запомнить, скажем, 1023 слова. Если слова упорядочены, согласно некоторому разумному плану, вы можете начать с верхнего уровня и находить любое слово в лучшем случае за 9 перемещений, если ваш поиск идет сверху вниз с одного уровня на следующий. Если слова находятся в массиве, вам, может быть, придется перебрать все 1023 элемента, прежде чем вы найдете нужное слово.

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

Это наше последнее слово о структурах. Далее мы хотим вкратце ознакомить вас с двумя другими средствами языка Си для работы с данными: объединением и функцией typedef.

ОБЪЕДИНЕНИЯ - КРАТКИЙ ОБЗОР

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

Объединения устанавливаются таким же способом, как и структуры. Есть шаблон объединения и переменные объединения. Они могут определяться одновременно или, если используется имя объединения, последовательно за два шага. Вот пример шаблона с именем объединения:

union holders {

int digit;

double bigf1;

char letter; };

А вот пример определения переменных объединения типа holdem:

union holdem fit; /* переменная объединения типа holdem */

union holdem save[10]; /* массив из 10 переменных объединения */

union holdem *pu; /* указатель на переменную типа holdem */


Первое описание создаст одну переменную fit. Компилятор выделяет достаточно памяти для размещения самой большой из описанных переменных. В этом случае наибольшей из возможных является переменная double, для которой требуется в нашей системе 64 разряда или 8 байтов. Массив save имел бы 10 элементов, каждый по 8 байтов.

Вот как используется объединение:

fit.digit = 23; /* 23 записывается в fit; используется 2 байта */

fit.double = 2.0; /* 23 стирается, 2.0 записывается; используется 8 байтов */

fit.letter = 'h'; /* 2.0 стирается, h записывается; используется 1 байт */

Вы применяете операцию получения элемента, чтобы показать, какие типы данных используются. В каждый момент времени запоминается только одно значение; нельзя записать char и int одновременно, даже если для этого достаточно памяти.

Вы сами должны следить за типом данных, записываемых в данный момент в объединение; приведенная ниже последовательность операторов показывает, что нельзя делать:

fit.lеtter = 'A';

finum = 3.02*fit.double; /* ОШИБКА ОШИБКА ОШИБКА */

Ошибка заключается в том, что записано значение типа char, a следующая строка предполагает, что содержимое fit имеет тип double.

Можно использовать операцию - > с объединениями таким же образом, как это делалось для структур:

pu = &fit;

х = рu -> digit; /* то же, что и х=fit.digit */

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

typedef - КРАТКИЙ ОБЗОР

Функция typedef позволяет нам создать свое собственное имя типа. Это напоминает директиву #define, но со следующими тремя изменениями:

1. В отличие от #define функция typedef дает символические имена, но ограничивается только типами данных.

2. Функция typedef выполняется компилятором, а не препроцессором.

3. В своих пределах функция typedef более гибка, чем #define.

Посмотрим, как она работает. Предположим, вы хотите использовать термин real для чисел типа float. Тогда вы определяете термин real, как если бы он был переменной типа float, и перед его определением ставите ключевое слово typedef:

typedef float real;

С этого момента вы можете использовать real для определения переменных:

real х, у[25], *рr;

Область действия такого определения зависит от расположения оператора typedef. Если определение находится внутри функции, то область действия локальна и ограничена этой функцией. Если определение расположено вне функции, то область действия глобальна.

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

typedef float REAL;

В последнем примере можно было бы применить директиву #define. А здесь это делать нельзя:

typedef char *STRING;

Без ключевого слова typedef оператор определял бы STRING как указатель на тип char. С ключевым словом оператор делает STRING идентификатором указателей на тип char. Так,

STRING name, sign;

означает

char *name, *sign;

Мы можем использовать typedef и для структур. Вот пример:

typedef struct COMPLEX {

float real;

float imag; };

Кроме того, можно использовать тип COMPLEX для представления комплексных чисел.

Одна из причин использования typedef заключается в создании удобных, распознаваемых имен для часто встречающихся типов. Например, многие пользователи предпочитают применять STRING или его эквивалент, как это мы делали выше. Вторая причина: имена typedef часто используются для сложных типов. Например, описание

typedef char *FRPTC ( ) [5];

приводит к тому, что FRPTC объявляет тип, являющийся функцией, которая возвращает указатель на пятиэлементный массив типа char. (См. "Причудливые описания".)

Третья причина использования typedef заключается в том, чтобы сделать программы более мобильными. Предположим, например, что вашей программе нужно использовать 16-разрядные числа. В некоторых системах это был бы тип short, в других же он может быть типом int. Если вы использовали в ваших описаниях short или int, то должны изменить все описания, когда перейдете от одной системы к другой. Вместо этого сделайте следующее, В файле директивы #include есть такое определение:

typedef short TWOBYTE;

Используйте TWOBYTE в ваших программах для переменных типа short, которые должны быть 16-разрядными. Тогда если вы перемешаете программу туда, где необходимо использовать тип int, то следует только изменить одно определение в вашем файле директивы #include:

typedef int TWOBYTE;

Это пример того, что делает язык Си столь мобильным. При использовании typedеf следует иметь в виду, что он не создаст новых типов, он только создает удобные метки.

ПРИЧУДЛИВЫЕ ОПИСАНИЯ

Язык Си позволяет вам создавать сложные формы данных. Обычно мы придерживаемся более простых форм, но считаем споим долгом указать ни потенциальные возможности языка. При создании описания мы используем имя (или "идентификатор"), которое можно изменять при помощи модификатора:


Модификатор значение
* указатель
( ) функция
[ ] массив

Язык Си позволяет использовать одновременно более одного модификатора, что даст возможность создавать множество типов:

int board[8] [8]; /* массив массивов типа int */

int **ptr; /* указатель на указатель на тип int */

int *risks[10]; /* 10-элементный массив указателей на тип int */

int (*wisks) [10]; /* указатель на 10-элемснтный массив типа int */

int *oof[3] [4]: /* 3-элементныи массив указателей на 4-элементный

массив типа int */

int (*uuf) [3][4]; /* указатель на массив 3х4 типа int */


Для распутывания этих описаний нужно понять, в каком порядке следует применять модификаторы. Три правила помогут вам справиться с этим.


1. Чем ближе кодификатор стоит к идентификатору, тем выше его приоритет.

2. Модификатиры [ ] и ( ) имеют приоритет выше, чем *.

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

Давайте применим эти правила к описанию int *oof[3] [4];

* и [3] примыкают к oof и имеют более высокий приоритет, чем [4] (правило 1). [3] имеет приоритет более высокий, чем * (правило 2). Следовательно, oof является 3-элементным массивом (перпый МОДИФИКАТОР) указателей (второй модификатор) на 4-элементный массив (третий модификатор) типа int (описание типа).

В описании

int (*uuf)[3][4];

скобки говорят, что модификатор * должен иметь первый приоритет, а это делает uuf указателем, как показано в предыдущем описании. Эти правила создают также следующие типы:

char *fump( ); /* функция, возвращающая указатель на тип char */

char (*frump) ( ); /* указатель на функцию, возвращающую тип char */

char *flump ( ) [3] /* функция, возвращающая указатель на 3-элементный

массив типа char */

char *flimp[3] ( ) /* 3-элементный массив указателей на функцию, которая

возвращает тип char */

Если вы примените структуры к этим примерам, то увидите, что возможности для описаний действительно растут причудливо. А применения ... так и быть, МЫ оставим их для более опытных программистов.

Язык Си со структурами, объединениями и typedef дает нам средства для эффективной и мобильной обработки данных.

ЧТО ВЫ ДОЛЖНЫ БЫЛИ УЗНАТЬ В ЭТОЙ ГЛАВЕ

Что такое структурный шаблон, и как его определять

Что такое имя структуры и как оно используется

Как определить структурную переменную: struct car honda;

Как обратиться к элементу структуры: honda.mpg

Как обратиться к указателю на структуру: struct car *ptcar;

Как обратиться к элементу при помощи указателя: ptcar->mpg

Как передать в функцию элемент структуры: eval(honda.mpg)

Как сообщить функции о структуре: rate(&honda)

Как создать вложенную структуру

Как обратиться к элементу вложенной структуры: honda.civic.cost

Как создавать и использовать массивы структур: struct car gm[5];

Как создать объединение: подобно структуре

Как использовать typedef: typedef struct car CRATE;

ВОПРОСЫ И ОТВЕТЫ

Вопросы

1. Что неправильно в этом шаблоне?

structure {char itible;

int num [20];

char *togs;

};

2. Вот фрагмент программы; что она напечатает?

struct house {

float sqft;

int rooms;

int stories;

char *address; };

main ( ) {

static struct house fruzt = { 1560.0, 6, 1, " 22 Spiffo Road";

struct house *sign;

sign = &fruzt;

printf(" %d %d\n" , fruzt.rooms, sign-> stories);

printf(" %s\n", frurt.address);

prinlf(" %c %c \n" sign- >address[3], fruzt.address[4]);

}

3. Придумайте структурный шаблон, который будет содержать название месяца, трехбуквенную аббревиатуру месяца, количество дней в месяце и номер месяца.

4. Определите массив, состоящий из двенадцати структур того же типа, что и в вопросе 3, и инициализируйте его для не високосного года.

5. Напишите функцию, которая получает номер месяца, а возвращает общее число дней года вплоть до этого месяца. Считайте, что структурный шаблон и массив из вопросов 3 и 4 описаны как внешние.

6. Взяв за основу нижеследующую функцию typedet, опишите 10-элементный массив указанной структуры. Затем, используя присваивание отдельного элемента попытайтесь описать третьим элементом массива линзу Ремаркатара с фокусным расстоянием 500 мм и апертурой f / 2.0.

typedef struct { /* описатель линзы */

float foclen; /* фокусное расстояние, мм */

float fstop; /* апертура */

char *brand; /* фирменная марка */ } LENS;


Ответы:

1. Должно быть ключевое слово struct, а не structure. Шаблон требует либо имени структуры перед открывающей скобкой или имени переменной после закрывающей скобки. Кроме того, точка с запятой должна стоять после *togs и в конце шаблона.

2.

6 1

22 Spiffo Road S p

Элемент fruzt.address является символьной строкой, а fruzt.address[4] является пятым элементом этого массива.

3.

struct month {

char name[10]; /* или char *name; */

char abbrev[4]; /* или char *abbrev; */

int days;

int monumb; };

4.

struct month months [12] = {

{" Январь" , " Янв" , 31, 1} , {" Февраль" , " Фев" , 28, 2} ,

и т. л. {"Декабрь", "Дек" , 31, 12}

5.

days(monlh);

inl month;

{

int index, tolal;

if(month < 1 || month > 12)

return (-1); /* признак ошибки */

else

for(index = 0, total = 0; index < month; index++)

total + = months [index].days;

return (total);}

Заметим, что index содержит номер месяца, уменьшенный на единицу, так как массивы начинаются с индекса 0; следовательно, мы используем выражение index < month вместо index <= month.

6.

ЛИНЗА tubby [10];

tubby [2].foclen = 300.0;

tubby [2].fstop = 2.0;

tubby [2].brand = "Рсмаркатар";


УПРАЖНЕНИЯ

1. Переделайте вопрос 5, используя в качестве аргумента написанное буквами название месяца вместо номера месяца. [Не забывайте о функции strcmp( ).]

2. Напишите программу, которая запрашивает у пользователя день, месяц и год. Месяц может обозначаться номером, названием месяца или его аббревиатурой. После работы программа выдает общее количество дней в году вплоть до данного дня.

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

Загрузка...