Опубликован: 16.09.2005 | Уровень: для всех | Доступ: платный | ВУЗ: Московский государственный университет имени М.В.Ломоносова
Лекция 9:

Управляющие конструкции языка Си. Представление программ в виде функций. Работа с памятью. Структуры

Структуры

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

Описание структуры выглядит следующим образом:

struct имя_структуры {
    описание полей структуры
};

Здесь имя_структуры — это любое имя, соответствующее синтаксису языка Си, описания полей структуры — любая последовательность описаний переменных, имена и типы этих переменных могут быть произвольными. Эти переменные называются полями структуры. Заканчивается описание структуры закрывающей фигурной скобкой. За закрывающей фигурной скобкой в описании структуры обязательно следует точка с запятой, в отличие от конструкции составного оператора, не следует забывать об этом! Для чего здесь нужна точка с запятой, будет объяснено ниже в разделе "Структуры и оператор определения типа typedef".

Рассмотрим пример: опишем вектор в трехмерном пространстве, который задается тремя вещественными координатами x, y, z:

struct R3Vector {
    double x;
    double y;
    double z;
};

Таким образом, вводится новый тип " struct R3Vector "; объект этого типа содержит внутри себя три вещественных поля с именами x, y, z. После того как структура определена, можно описывать переменные такого типа, при этом в качестве имени типа следует использовать выражение struct R3Vector. Например, в следующей строке описываются два вещественных вектора в трехмерном пространстве с именами u, v:

struct R3Vector u, v;

С объектами типа структура можно работать как с единым целым, например, копировать эти объекты целиком:

struct R3Vector u, v;
. . .
u = v;  // Копируем вектор как единое целое

В этом примере вектор v копируется в вектор u ; копирование структур сводится к переписыванию области памяти. Сравнивать структуры нельзя:

struct R3Vector u, v;
. . .
if (u == v) { // Ошибка! Сравнивать структуры нельзя
    . . .
}

Имеется также возможность работать с полями структуры. Для этого используется операция точка " .": пусть sобъект типа структура, f — имя поля структуры. Тогда выражение

s.f

является полем f структуры s, с ним можно работать как с обычной переменной. Например, в следующем фрагменте в вектор w записывается векторное произведение векторов u и v трехмерного пространства: w = u x v.

struct R3Vector u, v, w;
. . .
// Вычисляем векторное произведение w = u * v
w.x = u.y * v.z - u.z * v.y;
w.y = (-u.x) * v.z + u.z * v.x;
w.z = u.x * v.y - u.y * v.x;

В приведенных примерах все поля структуры R3Vector имеют один и тот же тип double, однако это совершенно не обязательно. Полями структуры могут быть другие структуры, никаких ограничений нет. Пример: плоскость в трехмерном пространстве задается точкой и вектором нормали, ей соответствует структура R3Plane. Точке трехмерного пространства соответствует структура R3Point, которая определяется аналогично вектору. Полное описание всех трех структур:

struct R3Vector { // Вектор трехмерного пространства
    double x;
    double y;
    double z;
};
struct R3Point { // Точка трехмерного пространства
    double x;
    double y;
    double z;
};
struct R3Plane { // Плоскость в трехмерном пр-ве
    struct R3Point origin;  // точка в плоскости
    struct R3Vector normal; // нормаль к плоскости
};

Пусть plane — это объект типа плоскость. Для того, чтобы получить координату x точки плоскости, надо два раза применить операцию "точка" доступа к полю структуры:

plane.origin.x

Структуры и указатели

Указатели на структуры используются довольно часто. Указатель на структуру S описывается обычным образом, в качестве имени типа фигурирует struct S*. Например, в следующем фрагменте переменная p описана как указатель на структуру S:

struct S { . . . }; // Определение структуры S
struct S *p; // Описание указателя на структуру S

Описание структуры может содержать указатель на структуру того же типа в качестве одного из полей. Язык Си допускает использование указателей на структуры, определение которых еще не завершено. Например, рассмотрим структуру TreeNode (вершина дерева), которая используется при определении бинарного дерева (см. раздел 4.5.4). Она содержит указатели на родительский узел и на левого и правого сыновей, которые также имеют тип struct TreeNode:

struct TreeNode {   // Вершина дерева
    struct TreeNode *parent; // Указатель на отца,
    struct TreeNode *left;   //   на левого сына,
    struct TreeNode *right;  //   на правого сына
    void *value;             // Значение в вершине
};

Здесь при описании полей parent, left, right используется тип ``указатель на структуру TreeNode '', определение которой еще не завершено, что допустимо в языке Си. Возможны и более сложные комбинации, например, структура A содержит указатель на структуру B, а структура B — указатель на структуру A. В этом случае можно использовать предварительное описание структуры, например, строка

struct A;

просто сообщает компилятору, что имя A является именем структуры, полное определение которой будет дано ниже. Тогда упомянутое описание двух структур A и B, ссылающихся друг на друга, может выглядеть следующим образом:

struct A; // Предварительное описание структуры A
    struct B; // Предварительное описание структуры B

    struct A { // Определение структуры A
        . . .
        struct B *p; // Указатель на структуру B
        . . .
    };

    struct B { // Определение структуры B
        . . .
        struct A *q; // Указатель на структуру A
        . . .
    };

Для доступа к полям структуры через указатель на структуру служит операция стрелочка, которая обозначается двумя символами -> (минус и знак больше), их нужно рассматривать как одну неразрывную лексему (т.е. единый знак, единое слово). Пусть S — имя структуры, f — некоторое поле структуры S, p — указатель на структуру S. Тогда выражение

p->f

обозначает поле f структуры S (само поле, а не указатель не него!). Это выражение можно записать, используя операцию звездочка (доступ к объекту через указатель),

p->f    ~    (*p).f

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

Пример: рекурсивный обход дерева


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

Вершина дерева описывается структурой TreeNode, которая рассматривалась в предыдущем разделе. Если у вершины один из сыновей отсутствует, то соответствующий указатель содержит нулевой адрес.

Для подсчета числа вершин дерева используем функцию numNodes с прототипом

int numNodes(const struct TreeNode *root);

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

// Описание структуры, представляющей вершину дерева
struct TreeNode {
    struct TreeNode *parent; // Указатель на отца,
    struct TreeNode *left;   //   на левого сына,
    struct TreeNode *right;  //   на правого сына
    void *value;             // Значение в вершине
};

// Рекурсивная реализация функции,
// вычисляющей число вершин дерева.
// Вход: указатель на корень поддерева
// Возвращаемое значение: число вершин поддерева
int numNodes(const struct TreeNode *root) {
    int num = 0;
    if (root == 0) { // Для нулевого указателя на корень
        return 0;    // возвращаем ноль
    }

    if (root->left != 0) {           // Есть левый сын =>
        num += numNodes(root->left); // вызываем функцию
    }                                // для левого сына

    if (root->right != 0) {          // Есть правый сын =>
        num += numNodes(root->right); // вызываем ф-цию
    }                                 // для правого сына

    return num + 1; // Возвращаем суммарное число вершин
}

Здесь неоднократно применялась операция стрелочка -> для доступа к полю структуры через указатель на нее.

Структуры и оператор определения типа typedef

Синтаксис языка Си позволяет в одном предложении определить структуру и описать несколько переменных структурного типа. Например, строка

struct R2_point { double x; double y; } t, *p;

одновременно определяет структуру R2_point (точка на двумерной плоскости) и описывает две переменные t и p. Первая имеет тип struct R2_point (точка плоскости), вторая — struct R2_point * (указатель на точку плоскости). Таким образом, после закрывающей фигурной скобки может идти необязательный список определяемых переменных, причем можно использовать все конструкции Си для построения сложных типов (указатели, массивы, функции). Список всегда завершается точкой с запятой, поэтому даже при пустом списке точка с запятой после фигурной скобки обязательна.

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

struct { double x; double y; } t, *p;

Здесь имя структуры отсутствует. Определены две переменные t и p, первая имеет структурный тип с полями x и y типа double, вторая — указатель на данный структурный тип. Такие описания в чистом виде программисты обычно не используют, гораздо чаще анонимное определение структуры комбинируют с оператором определения имени типа typedef (см. c. 117. ). Например, можно определить два типа R2Point (точка вещественной двумерной плоскости) и R2PointPtr (указатель на точку вещественной двумерной плоскости) в одном предложении, комбинируя оператор typedef с анонимным определением структуры:

typedef struct {
    double x;
    double y;
} R2Point, *R2PointPtr;

Такая технология довольно популярна среди программистов и применяется в большинстве системных h-файлов. Преимущество ее состоит в том, что в дальшейшем при описании переменных структурного типа не нужно использовать ключевое слово struct, например,

R2Point a, b, c;    // Описываем три точки a, b, c
R2PointPtr p;       // Описываем указатель на точку
R2Point *q;         // Эквивалентно R2PointPtr q;

Сравните с описаниями, использующими приведенное выше определение структуры R2_point:

struct R2_Point a, b, c;
struct R2_Point *p;
struct R2_Point *q;

Первый способ лаконичнее и нагляднее.

Вовсе не обязательно комбинировать оператор typedef непременно с анонимным определением структуры; можно в одном предложении как определить имя структуры, так и ввести новый тип. Например, предложение

typedef struct R2_point {
    double x;
    double y;
} R2Point, *R2PointPtr;

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

Все вышесказанное касательно языка Си справедливо и в C++. Кроме того, в C++ считается, что определение структуры S одновременно вводит и новый тип с именем S. Поэтому в случае C++ нет необходимости в использовании оператора typedef при задании структурных типов. Связано это с тем, что структура с точки зрения C++ является классом, а классы и определяемые ими типы — это основа языка C++. Сравните описания Си

struct S { ... };
struct S a, b, c;
struct S *p, *q;

и C++:

struct S { ... };
S a, b, c;
S *p, *q;

Конечно, описания C++ проще и нагляднее.

Натела Кузнецова
Натела Кузнецова

Уважаемые сообучающиеся, скиньте, пожалуйста,ссылку на корректные фалы для установки Traffic. Заранее благодарна

Дарья Федотова
Дарья Федотова
Денис Шестериков
Денис Шестериков
Россия
Сергей Мамойленко
Сергей Мамойленко
Россия, Московский государственный институт стали и сплавов (Технологический университет), 2000