Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник
Скачать 1.57 Mb.
|
Глава 6 СТРУКТУРЫ И ОБЪЕДИНЕНИЯ Структурные типы и структуры Производные типы. Из базовых типов (и типов, определенных программистом) можно формировать производные типы, к которым относятся: указатели, массивы, функции, структуры и объединения. Указатели, массивы, структуры и объединения являются производными типами данных. Иногда в качестве производного типа в руководствах по языку Си указывают строки, однако напоминаем, что в соответствии с синтаксисом языка строковый тип в нем отсутствует. Строка определяется как частный случай одномерного массива, то есть как массив с элементами типа 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; Выше был определен структурный тип «рациональная дробь», для которого введены два имени. С их помощью так вводятся конкретные структуры: /* Две структуры: */ struct rational_fraction ratio_1, ratio_2; /* Две структуры: */ fraction zin, rat_X_Y; Кроме изложенной последовательности определения структур (определяется структурный тип, затем с использованием его имени определяются структуры), в языке Си имеются еще две схемы их определения. Во-первых, структуры могут быть определены одновременно с определением структурного типа: struct имя_структурного_типа { определения_элементов } список_структур; Пример одновременного определения структурного типа и структур (объектов): struct student { char name [15]; /* Имя */ char surname [20]; /* Фамилия */ int year; /* Курс */ } student_1, student_2, student_3; Здесь определен структурный тип с именем student и три конкретные структуры student_1, student_2, student_3, которые являются полноправными объектами. В каждую из этих трех структур входят элементы, позволяющие представить имя (name), фамилию (surname), курс (year), на котором обучается студент. После приведенного определения в той же программе можно определять любое количество структур, используя структурный тип student: struct student leader, freshman; Следующий вариант определения структур является некоторым упрощением приведенного варианта. Дело в том, что можно определять структуры, приведя «внутреннее строение» структурного типа, но не вводя его названия. Такой безымянный структурный тип обычно используется в программе для однократного определения структур: struct { определения_элементов } список_структур; В качестве примера определим структуры, описывающие конфигурацию персонального компьютера с такими характеристиками: тип процессора (char [10]); рабочая частота в ГГц (int); объем основной памяти в Гбайтах (int); емкость жесткого диска в Гбайтах (int). Пример определения структур безымянного типа: struct { char processor [10]; int frequency; int memory; int disk; } ACER, HP, Compaq; В данном случае введены три структуры (три объекта) с именами ACER, HP, Compaq. В каждую из определенных структур входят элементы, в которые можно будет занести сведения о характеристиках конкретных ПК. Структурный тип «компьютер» не именован. Поэтому если в программе потребуется определять другие структуры с таким же составом элементов, то придется полностью повторить приведенное выше определение структурного типа. Выделение памяти для структур. Мы уже договорились, что определение структурного типа не связано с выделением памяти, а при каждом определении структуры (объекта) ей выделяется память в таком количестве, чтобы могли разместиться данные всех элементов. На рис. 6.1 приведены условные схемы распределения памяти для одного из объектов (структур) типа goods. На первой схеме элементы структуры размещены подряд без пропусков между ними. Однако никаких гарантий о таком непрерывном размещении элементов структур стандарт языка Си не дает. Причиной появления неиспользованных участков памяти («дыр») могут явиться требования выравнивания данных по границам участков адресного пространства. Эти требования зависят от реализации, от аппаратных возможностей системы и иногда от режимов (опций) работы компилятора. На рис. 6.1 условно изображен и второй вариант с пропуском участка памяти между элементами float percent и int vol. Пропуск («дыра») может быть и после последнего элемента структуры. В этом случае память для следующего объекта, определенного в программе, будет выделена не сразу после структуры, а с промежутком, оставляемым для выравнивания по принятой границе участка адресного пространства. Необходимость в выравнивании данных зависит от конкретной задачи. Например, доступ к целым значениям выполняется быстрее, если они имеют четные адреса, то есть выровнены по границам Определение структурного типа: |