Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C_lect8.doc
Скачиваний:
2
Добавлен:
08.09.2019
Размер:
68.61 Кб
Скачать

5

Язык c. Лекция 8

1. Массивы (продолжение)

1.4. Определение новых названий типов

Существует возможность давать имена-синонимы для существующих или определяемых программистом типов:

typedef unsigned short int word;

После этого word становится синонимом типа unsigned short int, так что можно, например, написать следующее объявление переменной:

word w;

и это будет то же самое, что

unsigned short int w;

Синтаксис конструкции typedef похож на объявление переменной, массива, структуры и т.п. с той лишь разницей, что вместо имени объекта стоит имя нового типа.

Рассмотрим пример с координатами точки в пространстве. В предыдущей лекции мы использовали массив из трех элементов для хранения координат x, y, z:

double p[3];

Можно определить новое имя типа:

typedef double Point[3];

и написать

Point p;

Объявление массива координат атомов из предыдущей программы (см. раздел 1.3)

static double at[MAXAT][3]; /* массив координат атомов */

теперь можно сделать более понятным:

static Point at[MAXAT]; /* массив координат атомов */

Смысл функции dist тоже становится более ясным, если записать ее заголовок в виде:

double dist (Point p1, Point p2)

Введение новых названий типов с помощью typedef позволяет упростить сложные объявления, сделать их более понятными.

2. Структуры

2.1. Определение структур и их свойства

Структура — второй (после массива) вид сложного типа данных, состоящего из нкскольких элементов. В отличие от массива элементы структуры имеют разные типы. Каждый элемент получает собственное имя, и к индивидуальным элементам структуры обращаются по именам, а не по индексам, как в случае массивов.

Рассмотрим пример. Чтобы определить сферу, достаточно задать ее радиус r и координаты центра. Эти данные логически связаны (они являются характеристиками одного объекта), поэтому в программе их разумно объединить:

struct sphere {

double r;

double c[3];

} s;

Это объявление говорит, что переменная s представляет собой структуру, состоящую из двух элементов — r и c, причем первый элемент является числом типа double, а второй массивом из трех элементов типа double (соответственно, радиус и три координаты центра сферы). Имя sphere после ключевого слова struct — это тег (или ярлык) структуры; им можно пользоваться в других объявлениях, чтобы не дублировать полное определение структуры, заданное в фигурных скобках. Например,

struct sphere a, b;

обявляет a и b как структуры того же вида, что и s. Обратите внимание, что слово struct необходимо повторить — без него компилятор не воспримет sphere как ярлык структуры. Ярлык (тег) — необязательный элемент описания структуры; его можно опустить, если на данную структуру не требуется ссылаться из других мест программы.

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

s.r = 1.5;

s.c[0] = 0.25;

s.c[1] = 0.75;

s.c[2] = 1.15;

Теперь s содержит информацию о сфере с радиусом 1.5 и центром в точке [0.25, 0.75, 1.15].

Важное замечание. Размер структуры (в отличие от массива) не обязательно равен сумме размеров ее элементов. Причина в том, что величины некоторых типов желательно размещать в памяти по адресам, кратным 2, 4, 8 или даже 16 (в зависимости от архитектуры процессора и типа данных). Если это требование не соблюдено, то данные извлекаются из памяти (или записываются в память) значительно медленнее, и производительность программы снижается. Размещение данных в памяти по адресу, кратному заданной величине, называется выравниванием. Для того, чтобы обеспечить правильное выравнивание данных, компилятор может вставлять пустые промежутки между элементами структуры. Рассмотрим, например, такую структуру:

struct {

char ch;

int n;

double val;

} q;

Суммарный размер ее элементов — 13 байтов (1+4+8). Однако компилятор Watcom C сообщает (если использовать sizeof(q)), что размер q равен 16 байтам. Дело в том, что на 32-разрядных процессорах Intel x86 значения типа int рекомендуется выравнивать по границе, кратной 4 байтам, а значения типа double — по границе, кратной 8 байтам. Значения типа char выравнивать не нужно — они могут располагаться по любому адресу. Всю структуру компилятор выравнивает в соответствии с наиболее сильным из требований для ее элементов (в нашем случае q будет расположена по адресу, кратному 8 байтам). Между элементами ch и n остается пустой промежуток длиной 3 байта, чтобы адрес n оказался кратным 4. Между n и val промежутка нет, т.к. элементы ch и n вместе с промежутком займут как раз 8 байтов.

Если поменять порядок элементов структуры:

struct {

char ch;

double val;

int n;

} q;

то ее общий размер окажется равным уже не 16, а 24 байтам: между ch и val промежуток длиной 7 байтов, между val и n промежутка нет, а после n (в конце структуры) добавятся еще 4 байта, чтобы общий размер был кратным 8. Последнее необходимо для того, чтобы несколько таких структур можно было расположить в памяти друг за другом без промежутков — например, если понадобится создать массив структур (элементы массива по определению обязаны размещаться в памяти без промежутков).

Структуры одинакового вида можно копировать (присваивать) целиком, например:

a = s;

a.r *= 0.5;

После выполнения этих операторов a и s соответствуют двум концентрическим сферам, причем радиус сферы a вдвое меньше, чем у s.

Возможность присваивания значений структур как единого целого означает в частности, что структуры можно возвращать в качестве значений функций. Если структуры служат аргументами функций, то они передаются по значению, т.е. копируются во временные переменные-структуры. Чтобы избежать такого копирования (особенно когда речь идет о структурах большого размера) часто передают в качестве аргументов не сами структуры, а указатели на них. Рассмотрим, например, функцию inside(p, s), которая проверяет, находится ли точка p внутри сферы s:

/* Для удобства введем имя типа Sphere для структуры struct sphere */

typedef struct sphere Sphere;

double dist (Point, Point);

int inside (Point p, Sphere s)

{

return dist(p, s.c) <= s.r;

}

Мы воспользовались здесь определенной ранее функцией dist, которая вычисляет расстояние между двумя точками. Точка p находится внутри сферы, если ее расстояние от центра не превышает величины радиуса (точка на поверхности сферы включается в этот случай). Функция inside возвращает значение 0, если точка p находится вне сферы, и значение 1, если p находится внутри или на поверхности сферы (0 и 1 соответствуют логическим значениям ложь и истина).

При вызове функции inside в нее передается указатель на p (поскольку это массив), а вот структура s копируется целиком (4 числа типа double). Более экономный вариант получится, если передавать не структуру, а указатель на нее. Для этого функцию inside придется несколько изменить:

int inside (Point p, Sphere *sp)

{

return dist(p, (*sp).c) <= (*sp).r;

}

(Мы назвали новый аргумент sp, а не s, чтобы подчеркнуть, что это указатель: буква “p” от слова “pointer” — “указатель”.) Вызов функции теперь сопровождается меньшими затратами, так как передаются лишь два указателя (размер указателя обычно совпадает с размером int или long). Однако не слишком красиво выглядит внутри функции обращение к элементам через указатель. Если sp — указатель на структуру, то *sp — сама структура. Однако написать *sp.r для обращения к элементу r нельзя, потому что операция “.” (доступ к элементу структуры) имеет более высокий приоритет, чем “*” (доступ к объекту через указатель на него). Поэтому *sp приходится заключать в скобки.

Так как работать со структурами с помощью указателей на них приходится довольно часто, то в языке C ввели специальную операцию “->” для доступа к элементам структуры через указатель на нее. Эта операция позволяет записать выражение в функции inside более кратко:

int inside (Point p, Sphere *sp)

{

return dist(p, sp->c) <= sp->r;

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]