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

Тесно связанным с понятием «класс хранения» является и понятие видимости, или области определения идентификатора. Область определения - это та часть программы, в пределах которой идентификатор может использоваться для доступа. Есть несколько типов области определения: в пределах блока (локальная видимость); в пределах функции; в пределах файла текста программы.

7.2. Область определения и видимость идентификатора.

7.2.1. Внешние переменные

Текст программы на С++ может быть размещен целиком в одном текстовом файле, в котором будут помещены функция main() и все другие функции, если они есть. Однако это не всегда удобно (а иногда и просто невозможно). В таком случае текст программы размещается в нескольких текстовых файлах, каждый из которых содержит целиком одну или несколько функций. Для объединения в одну программу эти текстовые файлы компилируются совместно. Информация (имя текстового файла, директорий) обо всех объединяемых в одну программу файлах, может быть помещена в так называемый файл проекта. Компилятор порождает для каждого исходного текстового файла отдельный объектный файл. Затем эти файлы объединяются компоновщиком в загрузочный модуль (.EXE-файл).

Объектный файл - это неготовый к исполнению программный код. Образно говоря, объектный файл - это отдельный модуль дорогого музыкального центра, состоящего из отдельных блоков: усилителя, радиоприемника, магнитофона, проигрывателя. Для того, чтобы «заиграла» музыка, необходимо эти блоки соединить проводами друг с другом. Чтобы не ошибиться, разъемы на разных сторонах проводов часто делают неодинаковыми. Разъемы в объектных файлах порождает компилятор. Один вид разъемов соответствует известному в языке ассемблера понятию PUBLIC, другой - понятию EXTERN. Процесс объединения блоков с помощью проводов похож на работу компоновщика. В роли разъемов, объединяемых в объектных файлах проводами, выступают так называемые внешние ссылки. Это либо имена функций, либо переменные. Встретив разъем EXTERN, компоновщик ищет подходящий ему разъем PUBLIC сначала в данном объектном файле, затем в других объектных файлах из числа компилируемых совместно. Потом он просматривает библиотеки. Если подходящий разъем не найден, компоновка завершается с сообщением об ошибке типа «Неразрешенная внешняя ссылка» или «неизвестное имя ...» (Unresolved external …).

НЕ нашли? Не то? Что вы ищете?

Если разъем EXTERN соответствует имени библиотечной функции, то она помещается в загрузочный модуль. Другими словами, разъем EXTERN - это указание компоновщику искать соответствующий ему по имени разъем типа PUBLIC.

Область определения идентификатора нужна компилятору для того, чтобы сгенерировать корректный машинный код. Если идентификатор описан внутри блока {}, он имеет локальную область определения, ограниченную размерами блока. Отсюда следует, что все переменные, описанные внутри функции, в том числе и формальные параметры, имеют локальную область определения. Для локальных переменных компилятор не создает никаких разъемов – ни PUBLIC, ни EXTERN. Если идентификатор описан вне каких-либо блоков, он является глобальным и имеет область определения, начинающуюся с точки описания и продолжающуюся до конца файла. Для всех глобальных переменных компилятор создает разъем PUBLIC. Глобальный идентификатор будет видимым из всех функций ниже точки описания переменной. Чтобы идентификатор стал видимым в функциях выше точки описания переменной или в функциях, определения которых расположены в других файлах, используется объявление с атрибутом EXTERN. Обратите внимание на то, что воздействие на видимость объекта не связано с резервированием памяти компилятором. Поэтому, например, строка программы

extern int var1;

является не описанием переменной, а объявлением ее как внешней ссылки. Обрабатывая такое предложение, компилятор создает разъем EXTERN для переменной var1. Отсюда вытекают два очевидных следствия:

при объявлении переменной как EXTERN просто невозможно выполнить ее инициализацию. Например, выражение

extern int var1 = 20;

является ошибкой;

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

Сначала компоновщик просматривает глобальные переменные текущего файла (т. е. все разъемы PUBLIC в том файле, в котором встретилось объявление EXTERN). Если не найден подходящий глобальный идентификатор в текущем файле, компоновщик просматривает все идентификаторы, помеченные как PUBLIC, в других объектных файлах, образующих один проект. Если и там не будет найден соответствующий идентификатор, поиск продолжается в библиотеках. Внешние переменные – один из способов «передачи» функциям аргументов и «возврата» из функций необходимых значений. Просто взаимодействующие функции используют общие области памяти.

Имена функций в языке С++ – это всегда глобальные имена, видимые по умолчанию из всех файлов одного проекта. Однако прототип функции действует только в пределах одного файла. Из-за этого, в частности, приходится помещать во все совместно компилируемые файлы директивы препроцессора, связанные с подключением. h-файлов, содержащих прототипы библиотечных функций. Приведем пример программы, текст которой размещен в двух файлах (для связи функций программы используется внешняя переменная i):

// Prim7_1.cpp

#include <stdio. h>

extern int i;

void main(void)

{

void next(void); /* прототип */

int i = 3;

i++;

printf("В main i = %d\n", i);

next();

}

void next(void)

{

void other(void); /* прототип */

i++;

printf("B next i = %d\n", i);

other();

}

// Prim7_2.cpp

#include <stdio. h>

extern int i;

void other(void)

{

i++;

printf("B other i = %d\n", i);

}

В результате выполнения программы получены следующие результаты:

B main i = 4

B next i = 5

B other i = 6

7.2.2. Автоматические переменные. Классы хранения auto и register

Большинство переменныхпрограммы на С++ – это переменные, отнесенные к классу хранения auto или автоматические переменные. Автоматические переменные – это всегда локальные переменные, но не наоборот. Автоматические переменные располагаются в стеке или внутренних регистрах процессора. В этой связи время, в течение которого определено их значение (время существования), ограничено временем выполнения функции или блока, в котором эти переменные описаны. Автоматические переменные должны явно инициализироваться, иначе содержимое ячеек памяти, выделенное для них, не будет определено.

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

7.2.3. Статические переменные. Класс хранения static

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

По умолчанию все объекты, отнесенные к статической памяти, будут инициализироваться нулевыми байтами. Если при описании статической переменной ей присваивается начальное значение, то это значение «впечатывается» в ячейки памяти, выделенные для переменной. Затем при загрузке программы инициализирующие статический объект байты переносятся в оперативную память компьютера.

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

/* Prim7_3.cpp */

#include <stdio. h>

void example(int);

void main(void)

{

int count;

for( count = 9; count >= 5; count -=2)

example(count);

}

void example(int c)

{

int f = 1; static int stat = 1;

printf("c = %d, f = %d, stat = %d\n", c, f, stat);

stat++; f++;

}

При выполнении программы будут получены следующие результаты:

c = 9, f = 1, stat = 1

c = 7, f = 1, stat = 2

c = 5, f = 1, stat = 3

Обратите внимание на то, что значение статической переменной stat после выхода из функции example() не «забывается», как это имеет место для переменной f. Переменная f – автоматическая переменная, которой при каждом вхождении в функцию вновь присваивается значение 1. Инициализация же статической переменной stat производится только однажды – при загрузке программы.

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

И, наконец, объекты с динамическим временем жизни создаются и разрушаются специальными функциями динамического управления памятью при выполнении программы. Физически динамические объекты располагаются в специально зарезервированной области памяти, называемой «куча» (heap).

8. Структуры, объединения и перечисления

8.1. Общие положения

Структуры и объединения – это один из примеров составных типов данных, называемых агрегатными типами, или просто агрегатами.

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

Объединения подобны структурам в том, что содержат поля различных типов, но помещаемые в одно и то же место памяти. Фактически объединения – это доступ к одному и тому же месту памяти, но по разному.

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

8.2. Шаблон структуры. Внешний и внутренний шаблоны. Структурная переменная

Термин «структура» в языке программирования С++ соответствует двум разным по смыслу понятиям:

1) обозначение места в памяти, где располагается информация; далее это место называется структурной переменной;

2) правила форматирования структурной переменной, используемые компилятором для выделения ей места в памяти и организации доступа к ее полям; далее такие правила называются шаблоном структуры или шаблоном структурной переменной.

Как и любую переменную, структурную переменную необходимо описать. Это описание состоит из двух шагов:

1) задание шаблона структуры;

2) собственно описание структурной переменной.

Каждый шаблон имеет собственное имя для того, чтобы компилятор мог различать различные шаблоны. В том случае, если в функции используется единственный шаблон, он может иметь имя «нет имени», что задается пропуском pattern_name при описании шаблона. Имена шаблонов должны быть уникальными в пределах их области определения, и в частности в пределах одной функции может быть только один шаблон с именем «нет имени». Синтаксис задания шаблона таков:

struct pattern_name{

type1 field_name1;

type2 field_name2;

…………………

typeN field_nameN;

};

где pattern_name – имя шаблона, удовлетворяющее правилам задания идентификаторов языка С++; type1, type2, …, typeN – любые типы, например int, char, float; field_name1, field_name2, …, field_nameN – имена полей, удовлетворяющие правилам задания идентификаторов языка С++. Например:

struct BOOK{

char name[20];

char title[44];

int year; float price;};

Имена полей в одном шаблоне должны быть уникальными. Однако в разных шаблонах можно иметь совпадающие имена полей. Кроме того, имена шаблонов проверяются на «уникальность» друг с другом, а не с метками или именами переменных. Раз так, то имя шаблона может и совпадать с именами полей, переменных или метками.

Задание шаблона никоим образом не связано с резервированием какой-либо памяти компилятором. Шаблон дает компилятору всю необходимую информацию о полях структурной переменной для резервирования места в памяти и организации доступа к этой памяти при описании структурной переменной и ссылках на отдельные поля структурной переменной. Фактически шаблон – это задание нового типа struct pattern_name. В приведенном примере был введен новый тип struct BOOK.

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

Когда задан шаблон, может быть описана структурная переменная. Описание структурной переменной состоит из задания типа и имени структурной переменной. Например:

struct BOOK first_book;

Здесь описывается структурная переменная по приведенному ранее шаблону BOOK. Компилятор выделит под переменную число байтов памяти, достаточную для хранения всех ее полей. Число байтов, выделенное под структурную переменную, не всегда равно сумме длин отдельных полей из-за влияния дополнительного фактора внутреннего представления структурных переменных, называемого выравниванием (см. подраздел 8.5). Точно выделенное число байтов всегда возвращает операция sizeof(struct pattern_name).

Синтаксис языка программирования С++ разрешает совмещать описание шаблона и структурной переменной, например, так:

struct BOOK{

char name[20];

char title[44];

int year; float price;

} first_book, child_book, dog_book;

Для структурных переменных действуют правила видимости и времени жизни, подробно рассмотренные в главе 7.

Для доступа к отдельным полям структурной переменной используют операцию ‘.’, формируя ссылку на нужное поле из имени структурной переменной и имени нужного поля. Такая ссылка может располагаться в любом месте выражений, где допустимы ссылки на простые переменные. Например, если сделано ранее рассмотренное описание структурных переменных, корректными будут следующие выражения:

first_book. year = 1992;

dog_book. year = first_book. year;

strcpy(child_book. title, "Белый Бим Черное Ухо");

scanf("%f", &first_book. price);

Ссылка на поле структурной переменной обладает всеми свойствами обычных переменных. Если поле - это массив символов, то например child_book. title - это указатель-константа на первый элемент массива. Можно определить адрес первого байта поля. Естественно, что ссылка на поле структурной переменной может располагаться как слева, так и справа от операции присваивания. Так же, как и для обычных операндов, действуют правила преобразования типов при употреблении операндов разных типов.

Если структурные переменные соответствуют одному шаблону, допускается операция присваивания таких структурных переменных. Например, для ранее описанных структурных переменных first_book, child_book и dog_book будут корректными такие выражения:

first_book = child_book;

first_book = child_book = dog_book;

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

При описании структурной переменной разрешается выполнять инициализацию полей переменной. Например:

struct BOOK{

char name[20];

char title[44];

int year; float price;

}

struct BOOK first_book =

{"", "Унесенные ветром", 1991, 5.25},

child_book = {"", "Белый Бим Черное Ухо", 1980, 2.78},

dog_book = {"", "Воспитание щенка", 1968, 0.47};

Разрешается объединять задание шаблона, описание структурных переменных и их инициализацию в одном предложении языка С++.

Для упрощения описания структурных переменных можно использовать оператор typedef описания собственного типа данных. Это упрощает текст программы. Общая форма предложения конструирования собственного типа данных:

typedef описание_типа имя_нового_типа;

Например:

typedef struct BOOK{

char name[20];

char title[44];

int year; float price;

}MY_BOOKS;

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

MY_BOOKS dog_book; //то же самое, что и struct BOOK,

//но запись несколько короче

Здесь используется для создания собственного типа именованный шаблон BOOK.

Однако использование имени шаблона совсем не обязательно, т. е. можно записать:

typedef struct {

char name[20];

char title[44];

int year; float price;

}BOOKS;

typedef struct { char class, subclass;

int number;}MY_UDC;

8.3. Вложенные структуры

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

/* Prim8_1.cpp */

struct UDC{

char class, subclass;

int number;

};

struct BOOK{

struct UDC udc_class;

char name[20];

char title[44];

int year; float price;

} first_book, child_book, dog_book;

В данном примере описывается шаблон структуры, имеющий вложенную структуру, и три структурные переменные по шаблону BOOK, т. е. и для вложенных структур действуют те же правила описания полей: задается шаблон (в данном примере это struct UDC) и имя поля (в шаблоне BOOK это udc_class).

Ссылка на поле вложенной структуры формируется из имени структурной переменной, имени структурного поля и имени поля вложенной структуры. Перечисленные имена разделяются символом '.' операции точка. Например, если сделано описание структурных переменных по приведенному ранее шаблону BOOK с вложенной структурой UDC, будут корректны следующие выражения:

first_book. udc_class. class = ‘A’;

dog_book. udc_class. number = 681;

Структура не сожет вкладываться сама в себя, т. е. некорректным будет следующее выражение:

struct BOOK{

struct BOOK my_own; // это ошибка

char name[20];

char title[44];

int year; float price;

};

Разрешается использовать описываемый шаблон, если одно из полей является указателем на описываемую структуру. Например:

struct BOOK{

struct BOOK *my_own; // это разрешается

char name[20];

char title[44];

int year; float price;

};

8.4. Указатель на структурную переменную

Описание шаблона структуры по существу новый тип, поэтому можно использовать указатель на введенный тип. Как и все указатели на данные, указатель на структурную переменную занимает либо два (near), либо четыре (far) байта. Для доступа к полям структурной переменной через указатель используется операция ->. Выражение

указатель_на_структуру->имя_поля

эквивалентно обычной записи

(*указатель_на_структуру).имя_поля

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

/* Prim8_2.cpp*/

struct UDC{char class, subclass; int number;};

struct BOOK{

struct UDC udc_class;

char name[20];

char title[44];

int year; float price;

};

struct BOOK *ptr_book, first_book;

ptr_book = &first_book;

ptr_book->year = 1992;

ptr_book->udc_class. class = ‘A’;

ptr_book->udc_class. number = 681;

strcpy(ptr_book->title, "Белый Бим Черное Ухо");

scanf("%f", &ptr_book->price);

Ссылка на поле структурной переменной через указатель обладает всеми свойствами обычной переменной. Например, если поле – это массив символов, то ptr_book->title – это указатель-константа на первый элемент массива. Можно определить адрес первого байта поля структурной переменной обычной операцией взятия адреса. Например, &ptr_book->price – это адрес первого байта поля price. Естественно, что ссылка на поле структурной переменной через указатель может располагаться как слева, так и справа от знака операции присваивания. Например, если описан шаблон BOOK и сделано описание данных:

struct BOOK *ptr_book, first_book;

ptr_book = &first_book;

то ссылка (*ptr).year эквивалентна ссылке first_book. year и ссылке ptr_book->year.

Заслуживает внимания то, как осуществляется доступ к полям вложенной структуры. Например, для того, чтобы сослаться на поле class вложенной переменной по шаблону struct UDC, сначала используют операцию ->, а затем ссылку на поле вложенной структуры операцией ‘.’:

ptr_book->udc_class. class = ‘A’;

Используя указатель на структурную переменную, можно сделать ссылку на поле любого типа, в том числе, например, на указатель. Отметим, что указатель на структуру может использоваться до объявления шаблона структуры, так как компилятору не требуется информация о размере структуры, а размер самого указателя известен. Далее приведен пример доступа к полям структуры с использованием указателя на структуру UDC, являющегося полем структуры BOOK:

/* Prim8_3.cpp */

struct BOOK{

struct UDC *udc_class; // указатель

char name[20];

char title[44];

int year;

float price;

};

struct UDC{

char class, subclass;

int number;

} my_class;

struct BOOK *ptr_book, first_book;

ptr_book = &first_book;

ptr_book->udc_class = &my_class;

ptr_book->udc_class->class = ‘A’;

ptr_book->year = 1992;

ptr_book->udc_class->number = 681;

strcpy(ptr_book->title, "Белый Бим Черное Ухо");

scanf("%f", & ptr_book->price);

В приведенном примере структурная переменная first_book, созданная по шаблону struct BOOK, включает в себя поле udc_class. Оно представляет собой указатель на структурную переменную по шаблону struct UDC. Сам шаблон struct UDC описан после описания указателя udc_class. Операция присваивания

ptr_book->udc_class = &my_class;

записывает в поле udc_class адрес первого байта структурной переменной my_class. После этого можно ссылаться на поля переменной udc_class через указатель ptr_book, например, так:

ptr_book->udc_class->class = ‘A’;

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

8.5.Массивы структурных переменных. Правила выравнивания структурных переменных в памяти

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

typedef struct {

char name[20];

char title[44];

int year; float price;

} BOOKS;

BOOKS library[25];

Доступ к элементам массива может выполняться с использованием индекса или через указатель-константу, которым является имя массива. Например, так можно выполнить доступ к полю year i-го элемента:

library[i].year = 1928; или

(*(library+i)).year = 1928; или

(library+i)->year = 1928;

А если описать рабочий указатель и инициализировать его адресом первого элемента массива

BOOKS library[25], *ptr = library;

то для доступа к полям i-го элемента массива структурных переменных можно использовать указатель ptr:

(ptr+i)->year = 1928;

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

Выделение оперативной памяти структурной переменной осуществляется по шаблону, интерпретируемому слева направо. При этом учитывается дополнительный фактор, называемый выравниванием структуры (Structure Alignement). Выравнивание задается либо как опция IDE (Options-Code Generation-Alignement), либо опцией компилятора командной строки. Может быть задано выравнивание структурной переменной на границе слова (Word Alignement в IDE), опция –а компилятора командной строки или на границе байта (Byte Alignement в IDE. Oпция –а – компилятора командной строки; принимается по умолчанию).

При выравнивании структурной переменной на границе байта отдельные ее поля в памяти располагаются без «зазоров». Начинаться переменная будет с любого (четного или нечетного) адреса; ее длина равна сумме длин полей шаблона. При выравнивании на границе слова компилятор при размещении структурной переменной в памяти вставляет между ее полями и между элементами массива структурных переменных пустые байты для того, чтобы соблюдались следующие правила:

отдельная структурная переменная (элемент массива структурных переменных) начинается на границе слова, т. е. с четного адреса;

любое поле, тип которого не совпадает с типом char, будет начинаться с четного адреса (имеет четное смещение от начала структурной переменной);

при необходимости в конце структурной переменной добавляется еще один байт так, чтобы число байтов переменной было четным.

Выравнивание сказывается критическим образом при переносе структурных переменных между диском и памятью.[Касаткин, II].

8.6. Использование структур в функциях

Как и для обычных переменных, C++ поддерживаeт как вызов функции с передачей копии всей структурной переменной или только отдельных ее полей (Call-By-Value), так и с передачей указателя на всю структурную переменную или только на отдельные поля структурной переменной (Call-By-Reference). Функция может возвращать как структурную переменную, так и указатель на нее.

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

Единственно возможные операции над структурами – это их копирование, присваивание, взятие адреса с помощью & и осуществление доступа к ее членам. Передача структур функциям в качестве аргументов и возврат их от функций в виде результата также относятся к операциям копирования и присваивания. Структуры нельзя сравнивать. Инициализировать структуру можно списком константных значений ее членов; автоматическую структуру можно инициализировать также присваиванием.

Чтобы лучше познакомиться со структурами, напишем несколько функций, санипулирующих точками и прямоугольниками. Возникает вопрос: а как передавать функциям названные объекты? Существует по крайней мере три подхода: передавать компоненты по отдельности, передавать свю структуру целиком и передавать указатель на структуру. Каждый подход имеет свои плюсы и минусы.

Первая функция, makepoint, получает два целых значения и возвращает структуру point.

// makepoint: формирует точку по компонентам х и у

struct point makepoint( int x, int y)

{

struct point temp;

temp. x = x;

temp. y = y;

return temp;

}

Заметим: никакой путаницы из-за того, что имя аргумента совпадает с именем члена структуры не возникает; более того, одно и то же имя подчеркивает родство обозначаемых им объектов.

Теперь с помощью makepoint можно выполнять динамическую инициализацию любой структуры или формировать структурные аргументы для той или иной функции:

struct rect screen;

struct point middle;

struct point makepoint( int, int);

screen. pt1 = makepoint(0, 0);

screen. pt2 = makepoint(XMAX, YMAX);

middle = makepoint((screen. pt1.x+screen. pt2.x)/2,

(screen. pt1.y+screen. pt2.y)/2);

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

// addpoint: сложение двух точек

struct point addpoint(struct point p1, struct point p2)

{

p1.x += p2.x;

p1.y += p2.y;

return p1;

}

Здесь оба аргумента и возвращаемое значение – структуры. Мы увеличиваем компоненты прямо в р1 и не используем для этого временной переменной, чтобы подчеркнуть, что структурные параметры передаются по значению так же, как и любые другие.

В качестве другого примера приведем функцию ptinrect, которая проверяет: находится ли точка внутри прямоугольника, относительно которого мы принимаем соглашение, что в него входят его левая и нижняя стороны, но не входят верхняя и правая.

// printrect: возвращает 1, если р в r, и 0 в прот. случае

int ptinrect(struct point p, struct rect r)

{

return p. x >= r. pt1.x && p. x < r. pt2.x && p. y >= r. pt1.y && p. y < r. pt2.y;

}

Здесь предполагается, что прямоугольник представлен в стандартном виде, т. е. координаты точки pt1 меньше соответствующих координат точки pt2. Следующая функция гарантирует получение прямоугольника в каноническом виде.

#define min(a, b) ((a)<(b)?(a):(b))

#define max(a, b) ((a)>(b)?(a):(b))

// canonrect: канонизация координат прямоугольника

struct rect canonrect(struct rect r)

{

struct rect temp;

temp. pt1.x = min(r. pt1.x, r. pt2.x);

temp. pt1.y = min(r. pt1.y, r. pt2.y);

temp. pt2.x = max(r. pt1.x, r. pt2.x);

temp. pt2.y = max(r. pt1.y, r. pt2.y);

return temp;

}

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

Декларация

struct point *pp;

сообщает, что рр есть указатель на структуру типа struct point. Если рр ссылается на структуру point, то *рр есть сама структура, а (*рр).х и (*рр).у – ее члены. Используя указатель рр, мы могли бы написать

sctruct point origin, *pp;

pp = &origin;

printf("origin: (%d, %d)\n", (*pp).x, (*pp).y);

Скобки в (*рр).х необходимы, поскольку приоритет оператора. выше, чем приоритет *. Выражение *рр. х будет проинтерпретировано как *(рр. х), что неверно, поскольку рр. х не является указателем.

8.7. Объединения разнотипных данных.

Со структурами в «близком родстве» находятся объединения, которые вводятся с помощью служебного слова union. Чтобы пояснить степень родства объединений со структурами, рассмотрим определение структуры STR:

struct {

long L;

int i1,i2;

char c[4] ;

} STR;

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

// Prim8_6.cpp : Размещение в памяти элементов структуры.

#include<iostream. h>

void main ()

{

struct {

long L;

int i1,i2;

char c[4];

} STR={10L,20,30, {‘a’,’b’,’c’,’d’}};

cout << "\n sizeof (STR)=" sizeof (STR) << hex;

cout << "n&STR. L= " << &STR. L;

cout << "n&STR. i1= " << &STR. i1;

cout << "n&STR. i2= " << &STR. i2;

cout << "n&STR. c= " << &STR. c;

}

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

sizeof (STR)=12

&STR. L= 0x8d800ff4

&STR. i1= 0x8d800ff8

&STR. i2= 0x8d800ffa

&STR. c= 0x8d800ffc

Определим очень похожее внешне на структуру STR объединение UNI:

union {

long L;

int i1,i2;

char c[4];

} UNI;

Количество элементов в объединении с именем UNI и их типы совпадают с количеством и типом элементов в структуре STR. Но существует одно очень важное отличие - все элементы объединения имеют один и тот же начальный адрес.

Следующая программа подтверждает сказанное:

//Prim8_7.cpp : размещение в памяти объединения.

#include<iostream. h>

void main ()

{

union{

long L;

int i1,i2;

char c[4];

} UNI={10L}

cout << "\n sizeof (UNI)=" << sizeof (UNI) << hex;

cout << "\n &UNI. L= " << &UNI. L;

cout << "\n &UNI. i1" << &UNI. i1;

cout << "\n &UNI. i2" << &UNI. i2;

cout << "\n &UNI. c " << &UNI. c;

cout << "\n sizeof (UNI. i1)= " << sizeof (UNI. i1) ;

cout << "\n sizeof (UNI. L)= " << sizeof (UNI. L) ;

}

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

sizeof (UNI)=4

&UNI. L=0x8d7d0ffc

&UNI. i1=0x8d7d0ffc

&UNI. i2=0x8d7d0ffc

&UNI. c=0x8d7d0ffc

sizeof (UNI. i1)=2

sizeof (UNI. L)= 4

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

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

Как и для структур, для объединения может быть введен программистом произвольный тип, определяющий «внутреннее строение» всех объединений, относящихся к этому типу. Если для структур английский термин tag мы заменили на структурный тип, то для union type можно говорить об объединяющем типе. Поэтому будем говорить о типе объединения:

union имя_объединяющего­_типа

{элементы объединения};

Пример объединяющего типа :

union mixture {

double d;

long E[2];

int k[4];

};

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

mixture mA, mB[4]; // объединение и массив объединений

mixture *pmix; // указатель на объединение

mixture &rmix=mA; // ссылка на объединение

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

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

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7