книги / Практикум по программированию на языке Си
..pdfленность идентификатора, безотносительно к строке замещения, которую он может представлять (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) и тремя координатами (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