Файл: В., Фомин С. С. Курс программирования на языке Си Учебник.docx
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 16.03.2024
Просмотров: 173
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Глава 6
СТРУКТУРЫ И ОБЪЕДИНЕНИЯ
Производные типы. Из базовых типов (и типов, определенных программистом) можно формировать производные типы, к которым относятся: указатели, массивы, функции, структуры и объединения. Указатели, массивы, структуры и объединения являются производными типами данных. Иногда в качестве производного типа в руководствах по языку Си указывают строки, однако напоминаем, что в соответствии с синтаксисом языка строковый тип в нем отсутствует. Строка определяется как частный случай одномерного массива, то есть как массив с элементами типа char, последний элемент которого равен '\0'.
Указатели и массивы подробно рассмотрены в предыдущих главах. Эта глава посвящена структурам и объединениям. Прежде чем рассматривать их особенности и возможности, введем некоторые терминологические соглашения стандарта, которые не всегда понятны сами собой.
Данные базовых типов (int, float, ...) считаются скалярными данными. Массивы и структуры являются агрегирующими типами данных, в отличие от объединений и скалярных данных, которые относятся к неагрегирующим типам. С массивами и скалярными данными мы уже хорошо знакомы, поэтому различие между агрегирующими и неагрегирующими типами поясним на примере массивов и скалярных данных базовых типов. Обычно агрегирующий тип включает несколько компонентов. Например, массив long B[12] состоит из 12 элементов, причем каждый элемент имеет тип long. Язык Си позволяет определять и одноэлементные массивы. Так, массив float A[1] состоит из одного элемента А[0]. Однако одноэлементные массивы есть слишком частный случай обыкновенных массивов со многими элементами. Итак, в общем случае массив есть агрегирующий тип данных, состоящий из набора однотипных элементов, размещенных в памяти подряд в соответствии с ростом индекса. Для агрегирующего типа данных (например, для массива) в основной памяти ЭВМ выделяется такое количество памяти, чтобы сохранять (разместить) одновременно значения всех элементов. Этим свойством массивов мы уже неоднократно пользовались. Например, следующее выражение позволяет вычислить количество элементов массива B[ ]:
sizeof (B) / sizeof
(B [0])
Для данных неагрегирующих типов в основной памяти ЭВМ выделяется область памяти, достаточная для размещения только одного значения. С тем, как это происходит для скалярных типов, мы уже хорошо знакомы. С особенностями выделения памяти для объединений познакомимся в этой главе.
Структурный тип. Этот тип (производный агрегирующий) задает внутреннее строение определяемых с его помощью структур. Сказанное требует пояснений. Начнем с понятия структуры. Структура - это объединенное в единое целое множество поименованных элементов (компонентов) данных. В отличие от массива, всегда состоящего из однотипных элементов, компоненты структуры могут быть разных типов и все должны иметь различные имена. Например, может быть введена структура, описывающая товары на складе, с компонентами:
В перечислении элементов структуры «товары на складе» добавлены выбранные программистом типы этих элементов. В соответствии со смыслом компоненты могут иметь любой из типов данных, допустимых в языке. Так как товаров на складе может быть много, для определения отдельных структур, содержащих сведения о конкретных товарах, программист вводит производный тип, называемый структурным. Для нашего примера его можно ввести, например, так:
struct goods {
char* name; /* Наименование */
long price; /* Цена оптовая */ float percent; /* Наценка в % */ int vol; /* Объем партии */ char date [9]; /* Дата поставки партии */ };
Здесь struct - спецификатор структурного типа (служебное слово); goods - предложенное программистом название (имя) структурного типа. (Используя транслитерацию английского термина «tag», название структурного типа в русскоязычной литературе по языку Си довольно часто называют тегом.) В фигурных скобках размещаются описания элементов, которые будут входить в каждый объект типа goods. Итак, формат определения структурного типа таков:
struct имя_структурного_типа { определения_элементов };
где struct - спецификатор структурного типа; имя_структурного_ типа - идентификатор, произвольно выбираемый программистом; определения_элементов - совокупность одного или более описаний объектов, каждый из которых служит прототипом для элементов структур вводимого структурного типа.
Следует обратить внимание, что определение структурного типа (в отличие от определения функции) заканчивается точкой с запятой.
Конструкция struct имя_структурного_типа играет ту же роль, что и спецификаторы типов, например double или int. С помощью struct goods можно либо определить конкретную структуру (как, например, объект структурного типа struct goods), либо указатель на структуры такого типа. Пример:
/* Определение структуры с именем food */ struct goods food;
/* Определение указателя point_to на структуры*/ struct goods *point_to;
Кроме такого прямого определения поименованного структурного типа, может быть введен безымянный структурный тип и не полностью определенный (незавершенный) структурный тип. О безымянном структурном типе речь пойдет в этом параграфе чуть позже при определении структур как объектов. Не полностью определенный, то есть незавершенный структурный, тип потребуется в следующем параграфе при рассмотрении указателей на структуры, вводимые в качестве элементов структур. Кстати, отметим, что незавершенным может быть не только структурный тип. В общем смысле незавершенным считается любое определение, в котором не содержится достаточно информации о размере будущего объекта.
Еще одну возможность ввести структурный тип дает служебное слово typedef, позволяющее ввести собственное обозначение для любого определения типа. В случае структурного типа он может быть введен и поименован следующим образом:
typedef struct {определения_элементов} обозначение_структурного_типа;
Пример:
typedef struct
{ double real;
double imag;
}
complex;
Приведенное определение вводит структурный тип struct {double real; double imag;} и присваивает ему обозначение (название, имя) complex. С помощью этого обозначения можно вводить структуры (объекты) так же, как с обычным именем структурного типа (например, struct goods в предыдущем примере). Пример:
/* Определены две структуры: */ complex sigma, alfa;
Структурный тип, которому программист назначает имя с помощью typedef, может в то же время иметь второе имя, вводимое стандартным образом после служебного слова struct. В качестве примера введем определение структурного типа «рациональная дробь». Будем считать, что рациональная дробь - это пара целых чисел (m;n), где число n отлично от нуля. Определение такой дроби:
typedef struct rational_fraction
{ int numerator; /* Числитель */ int denominator; /* Знаменатель */
} fraction;
Здесь fraction - обозначение структурного типа, вводимое с помощью typedef. Имя rational_fraction введено для того же структурного типа стандартным способом. После такого определения структуры типа «рациональная дробь» могут вводиться как с помощью названия fraction, так и с помощью обозначения struct rational_fraction.
С помощью директивы #define можно вводить имена типов подобно тому, как это делается с помощью typedef. Например, сведения о книге могут быть введены таким образом:
#define BOOK struct \
Содержание 3
ПРЕДИСЛОВИЕ 12
Глава 1 16
БАЗОВЫЕ ПОНЯТИЯ ЯЗЫКА 16
1.1.Алфавит, идентификаторы, служебные слова 17
1.2.Литералы 20
1.3.Переменные и именованные 27
константы 27
1.4. Операции 35
1.5. Разделители 44
1.6.Выражения 49
Контрольные вопросы 59
Глава 2 61
ВВЕДЕНИЕ 61
В ПРОГРАММИРОВАНИЕ НА СИ 61
2.1.Структура и компоненты простой программы 61
2.2.Элементарные средства 71
программирования 71
2.3. Операторы цикла 92
2.4. Массивы и вложение 108
операторов цикла 108
2.5.Функции 123
2.6.Переключатели 135
Контрольные вопросы 139
Глава 3 140
ПРЕПРОЦЕССОРНЫЕ СРЕДСТВА 140
3.1.Стадии и директивы препроцессорной обработки 141
HUB 143
3.2.Замены в тексте 145
3.3.Включение текстов из файлов 150
3.4.Условная компиляция 152
3.5.Макроподстановки средствами 157
препроцессора 157
3.6.Вспомогательные директивы 163
3.7.Встроенные макроимена 165
Контрольные вопросы 167
Глава 4 169
УКАЗАТЕЛИ, МАССИВЫ, СТРОКИ 169
4.1.Указатели на объекты 169
4.2.Указатели и массивы 178
4.3. Символьная информация и строки 193
Контрольные вопросы 201
Глава 5 204
ФУНКЦИИ 204
5.1. Общие сведения о функциях 204
5.2.Указатели в параметрах функций 209
5.3.Массивы и строки 214
как параметры функций 214
5.4.Указатели на функции 223
5.5.Функции с переменным 237
количеством аргументов 237
5.6.Рекурсивные функции 249
5.7.Классы памяти 252
и организация программ 252
5.8. Параметры функции main( ) 259
Контрольные вопросы 262
Глава 6 264
СТРУКТУРЫ И ОБЪЕДИНЕНИЯ 264
6.1.Структурные типы и структуры 264
6.2.Структуры, массивы и указатели 278
6.3.Структуры и функции 289
6.4.Динамические информационные структуры 293
6.5.Объединения и битовые поля 300
Контрольные вопросы 309
Глава 7 312
ВВОД И ВЫВОД 312
7.1. Потоковый ввод-вывод 312
7.2. Ввод-вывод нижнего уровня 349
Контрольные вопросы 359
Глава 8 360
ПОДГОТОВКА И ВЫПОЛНЕНИЕ 360
ПРОГРАММ 360
8.1.Схема подготовки программ 360
8.2.Подготовка программ 362
в операционной системе UNIX 362
8.3. Утилита make 364
8.4. Библиотеки объектных модулей 368
Контрольные вопросы 375
Приложение 1 376
ТАБЛИЦЫ КОДОВ ASCII 376
HUB 381
Приложение 2 384
Константы предельных значений 384
Приложение 3 386
Стандартная библиотека функций языка Си 386
Приложение 4 397
МОДЕЛИ ПРЕДСТАВЛЕНИЯ 397
ЧИСЕЛ НА РАЗЛИЧНЫХ 397
КОМПЬЮТЕРНЫХ ПЛАТФОРМАХ 397
Литература 400
Предметный указатель 401
}
Здесь BOOK - препроцессорный идентификатор, вслед за которым в нескольких строках размещена «строка замещения». Обратите внимание на использование символа продолжения '\' для переноса строковой константы. Кроме того, отметим отсутствие точки с запятой после закрывающей скобки '}'. Далее в программе можно определять конкретные объекты-структуры или указатели с помощью имени BOOK, введенного на препроцессорном уровне:
BOOK ss, *pi;
После препроцессорных замен и исключения комментариев это предложение примет вид:
struct
{
char author[20];
char title [80]; int year;
}
ss, *pi;
Определение структур. Определения элементов (компонентов) структурного типа внешне подобны определениям данных соответствующих типов. Однако имеется существенное отличие. При определении структурного типа его компонентам не выделяется память, и их нельзя инициализировать. Другими словами, структурный тип не является объектом.
Из нашего примера определения структурного типа с названием goods следует, что наименование товара будет связано с указателем типа char*, имеющим имя name. Оптовая цена единицы товара будет значением элемента типа long с названием price. Торговая наценка будет значением элемента типа float с именем percent и т. д. Все это следует из приведенного определения структурного типа с названием goods. Но прежде чем элементы, введенные в определении структурного типа, смогут получить значения, должна быть определена хотя бы одна структура (то есть структурный объект) этого типа. Например, следующее определение вводит две структуры, то есть два объекта, типа goods:
struct goods coat, tea;
Итак, если структурный тип определен и известно его имя, то формат определения конкретных структур (объектов структурного типа) имеет вид:
struct имя_структурного_типа список_структур;
где список_структур - список выбранных пользователем имен (идентификаторов).
Выше показано, что для структурного типа, имя которого введено с помощью служебного слова typedef, определение структур не должно содержать спецификатора типа struct. Например, указатель на структуры для представления комплексных чисел с помощью определенного выше обозначения структурного типа complex можно определить так:
complex *p_comp;
Выше был определен структурный тип «рациональная дробь», для которого введены два имени. С их помощью так вводятся конкретные структуры:
СТРУКТУРЫ И ОБЪЕДИНЕНИЯ
-
Структурные типы и структуры
Производные типы. Из базовых типов (и типов, определенных программистом) можно формировать производные типы, к которым относятся: указатели, массивы, функции, структуры и объединения. Указатели, массивы, структуры и объединения являются производными типами данных. Иногда в качестве производного типа в руководствах по языку Си указывают строки, однако напоминаем, что в соответствии с синтаксисом языка строковый тип в нем отсутствует. Строка определяется как частный случай одномерного массива, то есть как массив с элементами типа char, последний элемент которого равен '\0'.
Указатели и массивы подробно рассмотрены в предыдущих главах. Эта глава посвящена структурам и объединениям. Прежде чем рассматривать их особенности и возможности, введем некоторые терминологические соглашения стандарта, которые не всегда понятны сами собой.
Данные базовых типов (int, float, ...) считаются скалярными данными. Массивы и структуры являются агрегирующими типами данных, в отличие от объединений и скалярных данных, которые относятся к неагрегирующим типам. С массивами и скалярными данными мы уже хорошо знакомы, поэтому различие между агрегирующими и неагрегирующими типами поясним на примере массивов и скалярных данных базовых типов. Обычно агрегирующий тип включает несколько компонентов. Например, массив long B[12] состоит из 12 элементов, причем каждый элемент имеет тип long. Язык Си позволяет определять и одноэлементные массивы. Так, массив float A[1] состоит из одного элемента А[0]. Однако одноэлементные массивы есть слишком частный случай обыкновенных массивов со многими элементами. Итак, в общем случае массив есть агрегирующий тип данных, состоящий из набора однотипных элементов, размещенных в памяти подряд в соответствии с ростом индекса. Для агрегирующего типа данных (например, для массива) в основной памяти ЭВМ выделяется такое количество памяти, чтобы сохранять (разместить) одновременно значения всех элементов. Этим свойством массивов мы уже неоднократно пользовались. Например, следующее выражение позволяет вычислить количество элементов массива B[ ]:
sizeof (B) / sizeof
(B [0])
Для данных неагрегирующих типов в основной памяти ЭВМ выделяется область памяти, достаточная для размещения только одного значения. С тем, как это происходит для скалярных типов, мы уже хорошо знакомы. С особенностями выделения памяти для объединений познакомимся в этой главе.
Структурный тип. Этот тип (производный агрегирующий) задает внутреннее строение определяемых с его помощью структур. Сказанное требует пояснений. Начнем с понятия структуры. Структура - это объединенное в единое целое множество поименованных элементов (компонентов) данных. В отличие от массива, всегда состоящего из однотипных элементов, компоненты структуры могут быть разных типов и все должны иметь различные имена. Например, может быть введена структура, описывающая товары на складе, с компонентами:
-
название товара (char*); -
оптовая (закупочная) цена (long); -
торговая наценка в процентах (float); -
объем партии товара (int); -
дата поступления партии товара (char [9]).
В перечислении элементов структуры «товары на складе» добавлены выбранные программистом типы этих элементов. В соответствии со смыслом компоненты могут иметь любой из типов данных, допустимых в языке. Так как товаров на складе может быть много, для определения отдельных структур, содержащих сведения о конкретных товарах, программист вводит производный тип, называемый структурным. Для нашего примера его можно ввести, например, так:
struct goods {
char* name; /* Наименование */
long price; /* Цена оптовая */ float percent; /* Наценка в % */ int vol; /* Объем партии */ char date [9]; /* Дата поставки партии */ };
Здесь struct - спецификатор структурного типа (служебное слово); goods - предложенное программистом название (имя) структурного типа. (Используя транслитерацию английского термина «tag», название структурного типа в русскоязычной литературе по языку Си довольно часто называют тегом.) В фигурных скобках размещаются описания элементов, которые будут входить в каждый объект типа goods. Итак, формат определения структурного типа таков:
struct имя_структурного_типа { определения_элементов };
где struct - спецификатор структурного типа; имя_структурного_ типа - идентификатор, произвольно выбираемый программистом; определения_элементов - совокупность одного или более описаний объектов, каждый из которых служит прототипом для элементов структур вводимого структурного типа.
Следует обратить внимание, что определение структурного типа (в отличие от определения функции) заканчивается точкой с запятой.
Конструкция struct имя_структурного_типа играет ту же роль, что и спецификаторы типов, например double или int. С помощью struct goods можно либо определить конкретную структуру (как, например, объект структурного типа struct goods), либо указатель на структуры такого типа. Пример:
/* Определение структуры с именем food */ struct goods food;
/* Определение указателя point_to на структуры*/ struct goods *point_to;
Кроме такого прямого определения поименованного структурного типа, может быть введен безымянный структурный тип и не полностью определенный (незавершенный) структурный тип. О безымянном структурном типе речь пойдет в этом параграфе чуть позже при определении структур как объектов. Не полностью определенный, то есть незавершенный структурный, тип потребуется в следующем параграфе при рассмотрении указателей на структуры, вводимые в качестве элементов структур. Кстати, отметим, что незавершенным может быть не только структурный тип. В общем смысле незавершенным считается любое определение, в котором не содержится достаточно информации о размере будущего объекта.
Еще одну возможность ввести структурный тип дает служебное слово typedef, позволяющее ввести собственное обозначение для любого определения типа. В случае структурного типа он может быть введен и поименован следующим образом:
typedef struct {определения_элементов} обозначение_структурного_типа;
Пример:
typedef struct
{ double real;
double imag;
}
complex;
Приведенное определение вводит структурный тип struct {double real; double imag;} и присваивает ему обозначение (название, имя) complex. С помощью этого обозначения можно вводить структуры (объекты) так же, как с обычным именем структурного типа (например, struct goods в предыдущем примере). Пример:
/* Определены две структуры: */ complex sigma, alfa;
Структурный тип, которому программист назначает имя с помощью typedef, может в то же время иметь второе имя, вводимое стандартным образом после служебного слова struct. В качестве примера введем определение структурного типа «рациональная дробь». Будем считать, что рациональная дробь - это пара целых чисел (m;n), где число n отлично от нуля. Определение такой дроби:
typedef struct rational_fraction
{ int numerator; /* Числитель */ int denominator; /* Знаменатель */
} fraction;
Здесь fraction - обозначение структурного типа, вводимое с помощью typedef. Имя rational_fraction введено для того же структурного типа стандартным способом. После такого определения структуры типа «рациональная дробь» могут вводиться как с помощью названия fraction, так и с помощью обозначения struct rational_fraction.
С помощью директивы #define можно вводить имена типов подобно тому, как это делается с помощью typedef. Например, сведения о книге могут быть введены таким образом:
#define BOOK struct \
Содержание 3
ПРЕДИСЛОВИЕ 12
Глава 1 16
БАЗОВЫЕ ПОНЯТИЯ ЯЗЫКА 16
1.1.Алфавит, идентификаторы, служебные слова 17
1.2.Литералы 20
1.3.Переменные и именованные 27
константы 27
1.4. Операции 35
1.5. Разделители 44
1.6.Выражения 49
Контрольные вопросы 59
Глава 2 61
ВВЕДЕНИЕ 61
В ПРОГРАММИРОВАНИЕ НА СИ 61
2.1.Структура и компоненты простой программы 61
2.2.Элементарные средства 71
программирования 71
2.3. Операторы цикла 92
2.4. Массивы и вложение 108
операторов цикла 108
2.5.Функции 123
2.6.Переключатели 135
Контрольные вопросы 139
Глава 3 140
ПРЕПРОЦЕССОРНЫЕ СРЕДСТВА 140
3.1.Стадии и директивы препроцессорной обработки 141
HUB 143
3.2.Замены в тексте 145
3.3.Включение текстов из файлов 150
3.4.Условная компиляция 152
3.5.Макроподстановки средствами 157
препроцессора 157
3.6.Вспомогательные директивы 163
3.7.Встроенные макроимена 165
Контрольные вопросы 167
Глава 4 169
УКАЗАТЕЛИ, МАССИВЫ, СТРОКИ 169
4.1.Указатели на объекты 169
4.2.Указатели и массивы 178
4.3. Символьная информация и строки 193
Контрольные вопросы 201
Глава 5 204
ФУНКЦИИ 204
5.1. Общие сведения о функциях 204
5.2.Указатели в параметрах функций 209
5.3.Массивы и строки 214
как параметры функций 214
5.4.Указатели на функции 223
5.5.Функции с переменным 237
количеством аргументов 237
5.6.Рекурсивные функции 249
5.7.Классы памяти 252
и организация программ 252
5.8. Параметры функции main( ) 259
Контрольные вопросы 262
Глава 6 264
СТРУКТУРЫ И ОБЪЕДИНЕНИЯ 264
6.1.Структурные типы и структуры 264
6.2.Структуры, массивы и указатели 278
6.3.Структуры и функции 289
6.4.Динамические информационные структуры 293
6.5.Объединения и битовые поля 300
Контрольные вопросы 309
Глава 7 312
ВВОД И ВЫВОД 312
7.1. Потоковый ввод-вывод 312
7.2. Ввод-вывод нижнего уровня 349
Контрольные вопросы 359
Глава 8 360
ПОДГОТОВКА И ВЫПОЛНЕНИЕ 360
ПРОГРАММ 360
8.1.Схема подготовки программ 360
8.2.Подготовка программ 362
в операционной системе UNIX 362
8.3. Утилита make 364
8.4. Библиотеки объектных модулей 368
Контрольные вопросы 375
Приложение 1 376
ТАБЛИЦЫ КОДОВ ASCII 376
HUB 381
Приложение 2 384
Константы предельных значений 384
Приложение 3 386
Стандартная библиотека функций языка Си 386
Приложение 4 397
МОДЕЛИ ПРЕДСТАВЛЕНИЯ 397
ЧИСЕЛ НА РАЗЛИЧНЫХ 397
КОМПЬЮТЕРНЫХ ПЛАТФОРМАХ 397
Литература 400
Предметный указатель 401
}
Здесь BOOK - препроцессорный идентификатор, вслед за которым в нескольких строках размещена «строка замещения». Обратите внимание на использование символа продолжения '\' для переноса строковой константы. Кроме того, отметим отсутствие точки с запятой после закрывающей скобки '}'. Далее в программе можно определять конкретные объекты-структуры или указатели с помощью имени BOOK, введенного на препроцессорном уровне:
BOOK ss, *pi;
После препроцессорных замен и исключения комментариев это предложение примет вид:
struct
{
char author[20];
char title [80]; int year;
}
ss, *pi;
Определение структур. Определения элементов (компонентов) структурного типа внешне подобны определениям данных соответствующих типов. Однако имеется существенное отличие. При определении структурного типа его компонентам не выделяется память, и их нельзя инициализировать. Другими словами, структурный тип не является объектом.
Из нашего примера определения структурного типа с названием goods следует, что наименование товара будет связано с указателем типа char*, имеющим имя name. Оптовая цена единицы товара будет значением элемента типа long с названием price. Торговая наценка будет значением элемента типа float с именем percent и т. д. Все это следует из приведенного определения структурного типа с названием goods. Но прежде чем элементы, введенные в определении структурного типа, смогут получить значения, должна быть определена хотя бы одна структура (то есть структурный объект) этого типа. Например, следующее определение вводит две структуры, то есть два объекта, типа goods:
struct goods coat, tea;
Итак, если структурный тип определен и известно его имя, то формат определения конкретных структур (объектов структурного типа) имеет вид:
struct имя_структурного_типа список_структур;
где список_структур - список выбранных пользователем имен (идентификаторов).
Выше показано, что для структурного типа, имя которого введено с помощью служебного слова typedef, определение структур не должно содержать спецификатора типа struct. Например, указатель на структуры для представления комплексных чисел с помощью определенного выше обозначения структурного типа complex можно определить так:
complex *p_comp;
Выше был определен структурный тип «рациональная дробь», для которого введены два имени. С их помощью так вводятся конкретные структуры: