Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
25
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

ленность идентификатора, безотносительно к строке замещения, которую он может представлять (dom.txt, domPrint.txt).

!Вводя сложные макроопределения, используйте переносы (символ '\') в строке замещения (10_08.с, 10_11.с, …).

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

!Макросы удобны для моделирования многомерных массивов с помощью одномерных (10_16.с).

!Макроопределение позволяет вводить шаблон семейства функций, конкретная из которых выбирается с помощью аргументов макрообращения (10_17.с – 10_20.с).

!Макросредства препроцессора позволяют расширить язык Си, настраивая его на удобное решение конкретного класса задач

(10_16.с – 10_20.с).

Тема 11

Структуры, объединения, битовые поля

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

К. Хоор. О структурной организации данных

Основные вопросы темы

!Определение структур и структурных типов.

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

!Ввод и вывод значений элементов структур.

!Присваивание структур и операции с их элементами.

!Строка и указатель на строку как элементы структуры.

!Размеры структур (выделяемая память для структур и их элементов).

!Массивы в качестве элементов структур.

!Указатель на динамически выделяемый участок памяти как элемент структуры.

!Структура как элемент структуры другого типа

!Массивы структур и их инициализация

!Динамическое формирование структур

!Обращение к элементам структуры с помощью указателя на неё.

!Передача структуры в функцию через аппарат параметров.

!Структура как возвращаемое функцией значение.

!Указатель на структуру как параметр функции.

!Функция, возвращающая адрес структуры.

!Битовые поля и доступ к отдельным разрядам кода объекта.

!Динамические информационные конструкции на основе самоссылочных структур.

!Односвязный список упорядоченных звеньев.

!Бинарное дерево. Итерационная и рекурсивная обработка бинарных деревьев.

442

11.1.Структурные типы и структуры

Напомним определение: структура – набор объектов базовых и агрегированных типов, объединенных программистом в единое целое для описания какого-либо явления, процесса или объекта. Объекты, входящие в структуру, называют ее элементами, или компонентами, или полями.

При работе со структурами на языке Си следует понимать различие между структурным типом и конкретной структурой.

ЗАДАЧА 11-01. Определите структурный тип "банковский счет", включив в него элементы: номер счета (тип long), фамилию вкладчика (тип char[40]), дату открытия (в формате "dd- mm-yy"), сумму на счете (тип int), дату последней операции по счету ("dd-mm-yy"). Определите и инициализируйте конкретную структуру этого типа. Выведите на печать значения элементов структуры, размеры участков памяти, занимаемых ими, и размер памяти, выделенной для всей структуры.

Напомним, что размер структуры можно определить, применяя операцию sizeof к имени структурного типа или к имени конкретной структуры.

/* 11_01.c Структура "Банковский счет". */

#include <stdio.h>

/* банковсий счет

*/

struct

account {

long

acNumber;

/* номер счета */

 

char

name[40];

/* фамилия вкладчика */

int sum;

/* сумма на счете

*/

char

openDate[9];

/* дата открытия */

char

renewDate[9];

/* дата последней

операции */

};

 

 

 

#define PRINTZ(TERM) \ printf("sizeof("#TERM")=%d\n",sizeof(TERM))

int main()

{

struct account myAccount =

{782493L,"Scott Halpern",4378,"15-08-99", "17-01-02"};

443

printf ("myAccount.acNumber = %ld\n", myAccount.acNumber);

printf("myAccount.name: %s\n", myAccount.name); printf("myAccount.sum = %d\n", myAccount.sum); printf("myAccount.openDate: %s\n",

myAccount.openDate); printf("myAccount.renewDate: %s\n",

myAccount.renewDate);

PRINTZ(myAccount.acNumber);

PRINTZ(myAccount.name);

PRINTZ(myAccount.sum);

PRINTZ(myAccount.openDate);

PRINTZ(myAccount.renewDate);

PRINTZ(myAccount); PRINTZ(struct account); return 0;

}

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

myAccount.acNumber = 782493 myAccount.name: Scott Halpern myAccount.sum = 4378 myAccount.openDate: 15-08-99 myAccount.renewDate: 17-01-02 sizeof(myAccount.acNumber)=4 sizeof(myAccount.name)=40 sizeof(myAccount.sum)=4 sizeof(myAccount.openDate)=9 sizeof(myAccount.renewDate)=9 sizeof(myAccount)=68 sizeof(struct account)=68

Структурный тип struct account определен вне тела функции main(). Введен макрос PRINTZ() для печати размеров памяти, выделенной для его аргумента. В теле функции main() определена и инициализирована структура myAccount. Затем выведены на экран значения ее элементов и их размеры в памяти. Заслуживает внимания тот факт, что суммарный размер элементов (4+40+4+9+9)=66 не равен размеру структуры в целом (68)! Несовпадение размеров связано с принятыми правилами размещения структур в памяти ЭВМ. Эти

444

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

ЭКСПЕРИМЕНТ. В определении структурного типа account подберите размер элементов так, чтобы их суммарный размер совпал с размером структуры. (В компиляторе DJGPP этому условию удовлетворяет, например, char name[38].)

ЗАДАНИЕ. В предыдущей программе используйте безымянный структурный тип "банковский счет". Другими словами, определите структуру myAccount одновременно с определением ее безымянного структурного типа.

Решение (см. программу 11_01_1.с):

struct {

/* банковсий счет

*/

long acNumber;

/* номер счета */

 

char name[40];

/* фамилия вкладчика */

int sum;

/* сумма на счете

*/

char openDate[9];

/* дата открытия */

char renewDate[9];

/* дата последней

операции */

}myAccount =

{782493L,"Scott Halpern",4378,"15-08-99", "17-01-02"};

printf("myAccount.acNumber = %dl\n", myAccount.acNumber);

.....

Естественное изменение – в программе 11_01_1.с удален оператор PRINTZ(struct account); (Нельзя обратиться к безымянному типу!)

ЭКСПЕРИМЕНТ. В некоторых языках программирования разрешается печатать значения всех элементов структуры (структуры в этих языках часто называют записями), используя только ее имя. Попытайтесь вывести на экран значения элементов структуры myAccount, используя в функции printf() только ее имя.

445

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

ЗАДАЧА 11-02. Определите структурный тип "сведения об экзамене" сэлементами: фамилияпреподавателя(char teachName[26]); название дисциплины (указатель subject на строку, определенную в программе); дата экзамена ("dd-mm-yy"); фамилия студента (char studName[26]); оценка (тип int). Определите и частично инициализируйте конкретную структуру, указав при инициализации только фамилию преподавателя и название дисциплины. Определите еще одну структуру того же типа и присвойте ей значение инициализированной структуры. Введите недостающие данные в обе структуры и напечатайте значения элементов второй из них. (Печатать строку, адресованную указателем subject.)

/* 11_02.c - структура "Сведения об экзамене" */

#include <stdio.h>

 

#include <string.h>

/* сведения об экзамене */

struct exam {

char date[9];

/* дата экзамена */

char teachName[26];

/* фамилия преподавателя */

char * subject;

/* название дисциплины */

char studName[26];

/* фамилия студента */

int mark;

/* оценка */

};

 

int main()

 

{

struct exam exam1 = {"","Abramov M.S.", "Analytical geometry"};

struct exam exam2; exam2 = exam1;

printf("\nInput date (dd-mm-yy): "); scanf("%s", exam1.date); printf("Student: ");

scanf("%s", exam1.studName);

446

printf("Mark: "); scanf("%d", &exam1.mark);

strcpy(exam2.teachName,"Sulisperro E.M."); printf("Input date (dd-mm-yy): "); scanf("%s", exam2.date);

printf("Student: "); scanf("%s", exam2.studName); printf("Mark: "); scanf("%d", &exam2.mark);

printf("\nExam 2: date: %s",exam2.date); printf("\nsubject: %s", exam2.subject); printf("\nteachName: %s", exam2.teachName); printf("\nStudent: %s", exam2.studName); printf("\nMark: %d", exam2.mark);

return 0;

}

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

Input date (dd-mm-yy): 21-05-01<ENTER>

Student: Isaacs<ENTER>

Mark: 5<ENTER>

Input date (dd-mm-yy): 22-05-01<ENTER>

Student: Grice<ENTER>

Mark: 4<ENTER>

Exam 2: date: 22-05-01 subject: Analytical geometry teachName: Sulisperro E.M. Student: Grice

Mark: 4

Структура exam1 инициализирована частично – определены значения teachName[] и subject. Так как инициализацию элементов всегда нужно начинать с первого из них, то элементу char date[s] в начале списка инициализации поставлена в соответствие пустая строка "". Элементы строки exam2 получают значения в результате операции присваивания (exam2=exam1). Ввод недостающих значений элементов обеих структур, по-видимому, не требует пояснений. Но обратите внимание, что лишь для одного из аргументов функции

447

scanf() приходится применять операцию получения адреса

(&exam2.mark).

Оперировать с элементами структур, имеющих тип char[] и представляющих строки в стиле Си, нужно как со строками, например, используя функцию strcpy() для присваивания.

Структурный тип struct exam имеет одну особенность – название учебной дисциплины не входит в создаваемые структуры. В каждую из структур входит только указатель char * subject на строку с названием, и эта строка должна быть определена и доступна в программе. В каких случаях это хорошо, а в каких неудобно или недопустимо зависит от решаемой задачи.

ЗАДАНИЕ. Для структурного типа struct exam из программы 11_02.с вычислите размеры структуры и ее элементов.

Используем макрос PRINTZ() из программы 11_01.с. Не приводя текст программы (см. файл 11_02_1.с), остановимся на результатах:

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

sizeof(exam1.date)=9

sizeof(exam1.teachName)=26

sizeof(exam1.subject)=4

sizeof(exam1.studName)=26

sizeof(exam1.mark)=4

sizeof(exam1)=72

Тот факт, что сумма длин элементов (9+26+4+26+4=69) не равна общей длине структуры (72), нас уже не должен тревожить. А вот совершенно естественное отсутствие сведений о том, какую длину занимает строка "Analytical geometry", адресованная указателем exam1.subject, необходимо твердо запомнить.

ЗАДАЧА 11-03. Определите структурный тип "материальная точка". Определите две структуры этого типа. Значения элементов одной из структур задайте с помощью инициализации. Введите с клавиатуры значения элементов второй структуры и вычислите центр масс системы из этих двух точек.

448

mi . Координаты центра масс:

Напомним, что материальной точкой в физике называют тело, размерами и формой (но не массой) которого можно пренебречь в решаемой задаче. Материальная точка характеризуется массой (mi) и тремя координатами (xi, yi, zi). Общая масса системы точек

ma =

xc =

mi xi

, yc =

mi yi

, zc =

mi zi

.

 

 

 

 

 

 

mc

mc

mc

Продемонстрируем использование массива фиксированных размеров в качестве элемента структуры. В структуру, представляющую материальную точку, входят: массив координат точки double coord[3] и значение ее массы: double mass.

/* 11_03.c - структура "материальная точка" */ #include <stdio.h>

struct massPoint {

/* материальная точка */

double coord[3];

/*

координаты точки */

double mass;

/*

масса точки */

};

 

 

int main()

 

 

{

struct massPoint point1 = {2.0, -4.0, 5.0, 16}; struct massPoint point2, result;

int i;

printf("Input point2: "); printf("\nmass: "); scanf("%lf",&point2.mass); for (i=0; i < 3; i++)

{printf("coord[%d] : ", i+1); scanf("%lf",&point2.coord[i]);

}

result.mass = point1.mass + point2.mass; for (i=0; i < 3; i++)

result.coord[i] =

(point1.coord[i] + point2.coord[i])/result.mass; printf("\nMass center:\n");

printf("result.mass = %f\nCoordinates:\n", result.mass);

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

449

printf("\t[%d]=%5.2f", i+1, result.coord[i]); return 0;

}

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

Input point2: mass: 4<ENTER>

coord[1] : -2<ENTER> coord[2] : 4<ENTER> coord[3] : -5<ENTER>

Mass center:

 

 

result.mass = 20.000000

 

 

Coordinates:

[2]= 0.00

[3]= 0.00

[1]= 0.00

Алгоритм тривиален и непосредственно виден из текста программы. Для центра масс в программе введена структура result. После ввода значений элементов структуры point2, представляющей вторую точку, вычисляются значения элементов структуры result. Затем выполняется печать сведений о центре масс.

ЗАДАНИЕ. Дополните программу 11_03.с, введя в нее еще одну структуру "материальная точка". Присвойте ей значение структуры result. Затем "обнулите" все элементы структуры result и выведите значения всех элементов новой структуры.

Выполнение задания (см. программу 11_03_1.с) позволяет обратить внимание на особенности присваивания структур, элементами которых являются массивы. Определив в программе структуру:

struct massPoint center;

всем ее элементам можно присвоить значения одной операцией присваивания:

center=result;

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

450