« К оглавлению
↟ Вверх Сокрытие данных и инкапсуляция ›

Абстрактные типы данных

В предыдущей части было сделано утверждение относительно роли абстракций типов данных в программировании. Для того, чтобы в этом убедиться, следует вернуться к понятию “переменная”, но теперь уже – в контексте программирования. Кто-то может сказать, что переменная – это имя в программе. Что будет не совсем точно, потому что “имя”, обладает единственным свойством – его уникальностью. Очевидно, что переменные обладают и другими свойствами. Кто-то скажет, что это “хранилище”, которое принимает различные значения. Это опять не совсем точно в отношении переменных в программах, так как часто переменная не хранит значение, которое ей присваивается, при этом значение не может существовать в программе само по себе, ведь оно тоже абстрактно. Оно будет иметь некоторое конкретное представление.

Например, число 42 является значением, оно будет представлено в памяти компьютера в виде двоичного:

25+23+21 ;

а в программе – еще и в виде десятичного:

4 * 101 + 2 * 100 ;

или шестнадцатеричного:

2 * 161 + 10 * 160 ㅤилиㅤ 2 A16

набора данных.

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

Так как математические абстракции принято классифицировать по каким-либо признакам для удобства (например, переменные разделяются на такие, которые представляют целые или дробные числа), такой подход применяется и в языках программирования. Классификация производится по свойствам, перечисленным выше, а именно: какое множество значений принимают данные, какую форму принимают значения из этого множества, какие операции возможны для значений из этого множества [Wirth]. То есть, тип данных – это совокупность представления какого-то значения в программе и в памяти компьютера, которая позволяет работать с конкретным значением в программе с помощью соответствующих операций. Внутри самой программы также есть “конкретные” сущности и абстрактные сущности. Первые ассоциируются с объектами, вторые – со значениями [McJones]. В общем смысле, объект – это некоторое отношение между типом данных и самими данными в памяти компьютера. Свойства объекта определяются его типом.

Второе значение слова “абстрактный” – удаленный, то есть, рассматриваемый в отрыве от своего источника. Так разделение подходов в программировании на парадигмы (программирование может быть структурное, объектно-ориентированное, функциональное и т.д.) является абстракцией, потому что с точки зрения целевого устройства все эти парадигмы производят один и тот же машинный код, который исполняется независимо от того, какую идею вкладывал в него программист. Предназначение абстракций в программировании заключается в том, чтобы проектировать программы, понимать их и проверять их логику на правильность на основании законов, которые управляют абстракциями (например, на основании законов математики), не отвлекаясь на подробности реализации этих абстракций в конкретном компьютере.

Рассмотрим несложный пример. Предположим, что перед вами поставлена задача разработать тип данных в языке С, который будет использоваться для представления рациональных2 чисел в различных программах. Например, такое число можно было бы хранить в виде структуры:


struct rational {
    int p;
    int q;
};                        
                    

Тогда атрибут p – это числитель, а q – знаменатель. В этом случае создание рационального числа в программе могло бы выглядеть так:


rational a = {1, 2};

rational b;
b.p = 5;
b.q = 6;

rational c;
                    

Все три способа являются правильными с точки зрения компилятора (в данном примере используется gcc3). Но внимательный читатель может заметить, что тут есть проблемы. Во-первых, в качестве числителя и знаменателя можно передавать любые значения, в том числе 0. Это, конечно, противоречит правилам работы с рациональными числами, так как 0 в числителе должен обнулять все число, а в знаменателе 0 недопустим. Во-вторых, и числитель, и знаменатель можно свободно менять уже после создания переменной, что чревато всеми проблемами, перечисленными выше. В-третьих, оба атрибута можно оставить без значений, что делает такую переменную бесполезной, а поведение программы, которая ее использует, непредсказуемым.

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


rational cons(int x, int y) {
    assert(y != 0);
    if(x == 0) return {0, 0};
    rational r;
    if(y < 0) {
        r.p = -x;
        r.q = -y;
    }
    else {
        r.p = x;
        r.q = y;
    }
    return r;
}                        
                    

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


rational a = cons(1, 2);
                    

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

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


 

Упражнения

Абстрактные типы данных будут составлять основу большинства концепций, обсуждаемых в этом тексте, поэтому с ними нужно освоиться как можно лучше. Для этого читателю предлагается дополнить тип данных, описанный выше, несколькими операциями. Во-первых, было бы неплохо поддерживать сокращенную форму всех создаваемых в программе рациональных чисел (при заданных числителе и знаменателе, которые кратны одному и тому же числу, делить их на это число по умолчанию: 510 превращать в 12 , 96 – в 32 ). Во-вторых, было бы удобно отображать такие числа в текстовом виде как дроби, а не пару целых чисел. Пример этих дополнений будет ниже, но настоятельно рекомендуется сделать их самостоятельно, и только в случае полной неудачи посмотреть реализацию.

Пример решения
 

Сноски

1Программирование можно считать одной из областей математики, языки программирования можно называть абстрактными компьютерами [Strachey].

2Действительное число, которое можно представить как пару целых чисел, записанных одно над другим через прочерк, в виде дроби.

3GCC, the GNU Compiler Collection, gcc.gnu.org.


 

Copyright © 2023 Брынзан, Л. В.
GNU General Public License
“Commons Clause” License Condition v1.0
GPL Attribution-ShareAlike 4.0 International