Что такое абстрактный тип данных
Перейти к содержимому

Что такое абстрактный тип данных

  • автор:

Для чего изучать структуры и абстрактные типы данных?¶

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

Ранее мы ссылались на процедурную абстракцию, как на процесс сокрытия деталей конкретной функции, чтобы дать пользователю (или клиенту) возможность рассматривать её на очень высоком уровне. Сейчас мы переключаем наше внимание на аналогичную идею абстракции данных. Абстрактный тип данных, иногда обозначаемый аббревиатурой АТД, — это логическое описание того, как мы рассматриваем данные и разрешённые для них операции, безотносительно их реализации. Это значит, что мы сосредотачиваемся только на том, что данные из себя представляют, а не на том, как они в итоге будут сконструированы. Обеспечивая такой уровень абстракции, мы достигаем инкапсуляции данных. Идея здесь в том, что, инкапсулируя детали реализации, мы скрываем их от взгляда пользователя. Это называется сокрытием информации.

Рисунок 2 наглядно демонстрирует, что такое абстрактный тип данных и как он работает. Пользователь взаимодействует с интерфейсом, используя операции, определённые в АТД. По сути, абстрактный тип данных — это оболочка, с которой работает клиент. Реализация скрыта на уровень ниже, и её детали пользователя совершенно не беспокоят.

../_images/adt.png

Рисунок 2: Абстрактный тип данных

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

readers online now | | Back to top

© Copyright 2014 Brad Miller, David Ranum. Created using Sphinx 1.2.3.

Абстрактный тип данных. Часть 1: Данные (Тип Данных)

image

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

Вместо предисловия

Абстрактный Тип Данных (далее АТД) — это набор, включающий данные и выполняемые над ними операции.… Набор данных и методов, служащих одной цели, — это и есть АТД (С. Макконнелл, “Совершенный код”, глава 6.1. Основы классов: абстрактные типы данных). В данном цикле статей будет рассмотрен именно такой взгляд на АТД, расширенный типом данных.

Информация — сведения независимо от формы их представления. Знания относительно фактов, событий, вещей, идей и понятий, которые в определенном контексте имеют конкретный смысл (ISO/IEC 2382:2015).

Данные — это зарегистрированная информация.

Информационные Технологии — это приемы, способы и методы применения средств вычислительной техники при выполнении функций сбора, хранения, обработки, передачи и использования данных (ГОСТ 34.003-90).

Класс, наряду с понятием «объект», является важным понятием объектно-ориентированного подхода в программировании (хотя существуют и бесклассовые объектно-ориентированные языки, например, Прототипное программирование)
Класс является типом данных, определяемым пользователем.

Вычисление — это получение из входных данных нового знания.

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

Действие (формулировка данной статьи) — изменение состояния данных в процессе вычислений и регистрация данного состояния не меняя при этом самих данных которые участвуют в вычислениях. Например: мы меняем состояние автомобиля на плоскости (находился в точке А, переместился в точку Б), при этом данные самого автомобиля мы не затрагиваем. Еще один пример: мы имеем какой либо документ у которого есть срок действия, в процессе вычислений мы получаем состояние документа на какой-то момент времени и фиксируем его, сообщаем/отмечаем, что на такой то момент времени документ действителен/недействителен, при этом сам документ мы не меняем.

Теперь к сути

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

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

Из вышесказанного можно сделать вывод — объект мира и его параметры неразделимы, в то время как действие необязательная его черта. Объект мира и его параметры это просто набор данных, зарегистрированная информация о данном объекте. Далее, при написании приложения, мы применяем действие к этим наборам данных.

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

Научившись корректно выделять данные вам значительно легче будет решать логически сложные задачи, код станет более гибким т.к. изменяя логику работы с одними данными вы не будете задевать другие части программы. Блоки кода будут менее зависимы друг от друга. В голове будет ясность и понимание что да как работает.

Далее, выделенные данные (совокупность объекта мира и его параметров) буду определять как Тип Данных (далее ТД).

ТД (формулировка данной статьи) — атомарная, неделимая единица данных определяемая пользователем, представляющая из себя совокупность объекта мира и его параметров. ТД не способен производить действие.

В концепции ООП ТД это класс, в классе мы определяем поля которые хранят данные и как было написано выше, создавая класс мы создаем новый тип данных определяемый пользователем. СУБД служат для хранения данных и работы с ними — то же самое, нам надо корректно определить данные (сущности БД), с которыми мы в дальнейшем будем работать, применять действие.
Для корректного выделения ТД (данных) из окружающего нас мира определил следующие правила:

  1. Тип данных должен определять реальную сущность мира, материальную или нематериальную, т.е. быть именем существительным, отвечать на вопрос “кто?” или “что?”
  2. Если минимально избыточное количество параметров (отсутствие параметров избыточных для вычислений, например для квадрата достаточно знать длину одной стороны т.к. все остальные стороны такой же длины и углы между ними равны 90 градусов) и тип этих параметров у независимых друг от друга предметов (одушевленных или неодушевленных) одинаковые, то эти предметы можно отнести к одному и тому же типу данных. Если минимально избыточное количество параметров отличается или отличаются типы этих параметров, то это различные предметы, а значит и различный тип данных.

До данного момента говорил о данных в ООП и данных в реляционной СУБД, но в них есть существенное отличие, данные в БД не могут производить вычисления сами над собой, это просто зарегистрированная информация, в то время как ТД ООП может производить вычисление со своими данными, хотя принципы выделения ТД одинаковые. Более того, в ООП Классы данных являются плохим тоном и придают коду “запашок”, ТД в ООП должен уметь производить вычисления со своими данными, например для фигуры он должен быть способен вычислить ее площадь.
Может возникнуть закономерный вопрос — чем отличается ТД ООП от АТД по С.Макконнеллу? ТД ООП (по определению данной статьи) может производить вычисления со своими данными, но не может придавать им действие, в то время как АТД может производить вычисления со своими наборами данных, получать результаты вычислений ТД и придавать им действие (формулировку, что такое действие смотрите выше)

Какие есть требования по работе ТД ООП со своими данными смотрите ниже.

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

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

Представьте, у нас есть миллиард таких фигур и нам надо получить суммарную их площадь. У каждой фигуры есть метод получения ее площади, нам надо вызвать этот метод миллиард раз. При каждом вызове этого метода условный оператор будет задействован до трех раз, таким образом, для получения площади миллиарда фигур будет вызван условный оператор до трех миллиардов раз. А теперь сравните с другим решением — создаем интерфейс Parallelogram (Параллелограмм) с контрактном (методом) на получения площади этой фигуры и реализуем этот интерфейс для ромба, квадрата и прямоугольника по отдельности. Теперь, при получении площади миллиарда фигур, наш условный оператор не будет задействован ни одного раза, мы просто перебираем все фигуры и у каждой вызываем метод получения ее площади не задумываюсь над тем, что это за фигура. Представляете какую экономию ресурсов вы получите? Да, пускай площадь миллиарда фигур вам никогда не понадобится, но из капли получается река, здесь потеряли ресурсы, в другом месте немного потеряли, в третьем, в итоге приложение будет очень ресурсоемким и дорогим в производительности.

Какие есть требования к ТД:

  1. ТД должен производить вычисления только со своими данными, иначе на выходе мы можем получить непредсказуемый результат т.к. на входе могут оказаться данные предусмотреть которых мы не могли.
  2. Данные в ТД не должны зависеть от внешнего состояния приложения работающего с ними, это также может привести к непредсказуемому результату и лишит нас некоторых возможностей, например лишить возможности кешировать результат или использование ТД в другой части ПО, код будет менее подвижен т.к. в другой части программы, которую пишем, могут отсутствовать нужные параметры состояния.
  3. Данные ТД, участвующие в вычислениях, должны быть неизменяемы на момент работы с ними. Это так же избавит нас от непредсказуемого результата вычислений, когда один метод ТД изменил данные, а другой метод не зная об этом произвел свои вычисления с уже измененными данными, хотя на входе ожидались первоначальные значения. Или например непозволит реализовать паттерн «Легковес»

ВАЖНО: в первую очередь надо бояться непредсказуемого изменения данных, т.е. при использовании в вычислениях данные не должны меняться (требование к ТД №3), если же вы намеренно меняете их, то это изменение предсказуемо и вы можете произвести какие либо действия для получения в дальнейшем корректного результата.

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

И так, правила для достижения неизменяемости (стабильности) данных:

  1. С. Макконнелл, “Совершенный код”, глава 7.5 “Советы по использованию параметров методов” — “Не используйте параметры метода в качестве рабочих переменных. Создайте для этой цели локальные переменные”. Т.о. вы избежите непредсказуемого изменения данных вне использования метода, например если вы передали параметр по ссылке, то изменив их в методе они изменятся так же и для другого кода класса, что потенциально ведет к ошибке. Хотя С.Макконнелл говорит несколько о другом эффекте, непредсказуемость значения внутри метода, но смысл тот же, стремление избежать непредсказуемости результата.
  2. В приватных методах класса не используйте внутренние поля напрямую, передавайте их в качестве параметров этого метода. Т.о. вы избежите случайного/непредсказуемого изменения данных, когда один метод изменил данные, а другой не зная об этом приступил к вычислениям над ними.
  3. В методах класса не обращайтесь к внутренним полям напрямую, используйте для этого специально созданные методы. Т.о. вы получите единую точку входа для работы с внутренними полями и полный контроль над ними.
  4. Стремитесь к использованию приватных методов, С.Макконнелл, “Совершенный код”, глава 5 — Почаще задавайте себе вопрос «Что мне скрыть?», и вы удивитесь, сколько проблем проектирования растает на ваших глазах.

Никоем образом не хочу сказать, что это все является панацеей, конечно же есть и другие способы решения сложных задач, волшебной таблетки не существует, будьте гибче, применяйте в своей работе разные подходы. Приведу также в пример цитату одной из моих любимых статей на хабре “Топ-11 самых частых ошибок в JavaScript” — ошибка №11: Ты следуешь всем правилам, правила для того, чтобы их ломать, если вы понимаете, почему нельзя использовать тот или иной прием, то он становится инструментом, который вы можете правильно применять в правильной ситуации.

Итог:
  1. ПО работает с данными, придает им действие или производит с ними действие. Данные, это зарегистрированная информация.
  2. ТД — атомарная, неделимая единица данных определяемая пользователем, представляющая из себя совокупность объекта мира и его параметров. ТД не способен производить действие.
  3. Для корректного определения Типа Данных (выделения данных) применяйте следующие правила:

NEWOBJ.ru → Введение в ООП с примерами на C# →

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

Применяя только переменные базовых типов, программист каждый раз, работая с кодом, осознанно или неосознанно проходит путь от низкоуровневых абстракций кода к высокоуровневым абстракциям мышления. Например: «Мне нужна ордината Y вершины B треугольника ABC … Координаты вершин хранятся в массиве вещественных числе t , при этом ординаты – в нечетных позициях… Соответственно, чтобы получить искомое значение, нужно вычислить индекс в массиве по формуле i = 2 * [индекс вершины B] + 1 … Индекс вершины B равен 1… 2 * 1 + 1 = 3 … Значит, ордината вершины B хранится в элементе массива вещественных чисел t[3] .» Эти «размышления» демонстрируют проблему низкоуровневой модели данных.

Теперь рассмотрим, к чему приводит отсутствие синтаксической группировки и разделения инструкций. Что имеется в виду? С точки зрения организации кода и данных мы имеем один глобальный набор данных (переменных) и один неструктурированный блок кода (последовательность команд). Обсудим каждую из этих особенностей.

Фраза «один глобальный набор данных» значит, что весь код имеет доступ ко всем данным (переменным). Например, переменная inputCoords , объявленная в начале кода и необходимая только для обработки введенных пользователем данных и сохранения их в массив вещественных чисел t , видна всему последующему коду, где она заведомо не понадобится. Такая избыточная область видимости, то есть часть кода, где мы можем обратиться к некоторой переменной, приводит к ряду проблем. Во-первых, и это главная проблема, рано или поздно кто-то использует эту переменную там, где ее использовать не предполагалось. Это может создать побочный эффект, когда блок кода изменяет значение или полагается на значение переменных, связь с которыми этого блока кода никто не ожидает. Что приведет или к некорректной работе этого блока кода, если переменную изменят, не учитывая, где и как она используется, или к некорректной работе другого кода, если блок изменит переменную, а другой код, ее использующий, не будет этого знать. Во-вторых, избыточная область видимости синтаксически неудобна: имя резервируется в начале кода и не может быть использовано в других частях. Конечно, мы технически можем взять уже объявленную переменную и использовать ее для других целей, но это совершенно недопустимый подход, так как он усложняет понимание программы и неизбежно приводит к побочным эффектам. Каждая переменная должна использоваться строго для одной цели на протяжении всего времени жизни.

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

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

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

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

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

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

§ 5. Процедурное программирование. В примере из предыдущего параграфа мы использовали два вида абстракций: встроенные типы данных и инструкции кода. В процедурном программировании вводится новый вид абстракций – процедура. Читатель, вероятно, знает, что процедура – это не только способ сгруппировать последовательность команд, но, главное, – скрыть реализацию от пользователя процедуры (от вызывающего кода) 10 . Скрыть в том смысле, что вызывающий код видит только имя процедуры, ее параметры и возвращаемое значение, но никак не зависит от ее реализации, не может обратиться к отдельным строкам внутри процедуры и не может обратиться к локальным переменным процедуры.

Перепишем программу из предыдущего параграфа, используя процедурный подход. Реализуем три метода: Square , Length и SquareGeron (в C# процедуры называются методами, к вопросу терминологии мы вернемся позже):

 // Расчет площади треугольника с координатами вершин, заданными в массиве. float Square (float[] coords) < float a = Length (coords[0], coords[1], coords[2], coords[3]); float b = Length (coords[2], coords[3], coords[4], coords[5]); float c = Length (coords[4], coords[5], coords[0], coords[1]); return SquareGeron (a, b, c); >// Расчет расстояния между двумя точками с координатами (x0, y0) и (x1, y1). float Length (float x0, float y0, float x1, float y1) < return Math.Sqrt ( (x1 – x0) * (x1 – x0) + (y1 – y0) * (y1 – y0) ); >// Расчет площади треугольника по формуле Герона длинам сторон a, b, c. float SquareGeron (float a, float b, float c) < float p = (a + b + c) / 2; return Math.Sqrt (p * (p – a) * (p - b) * (p - c)); >

Такое объединение кода с используемыми им данными, при котором другие части кода не имеют доступа к этим данным, называется инкапсуляцией. Например, в приведенном решении в методе Square используются локальные переменные для хранения рассчитанных длин сторон – a, b, c . Эти переменные не видны и не доступны извне метода. Точно так же внешний код может вызывать только весь метод целиком, задав значения параметров, но не может обратиться к отдельным строкам метода. Такая изоляция позволяет избежать зависимости внешнего кода от реализации процедуры: изменение реализации с сохранением ее сигнатуры 11 не потребует изменения во внешнем коде. Таким образом, процедура выступает как абстракция (абстракция алгоритма), позволяя нам абстрагироваться от деталей, не нужных для решения основной задачи. Приведем концептуальное определение инкапсуляции [Буч 3]:

Инкапсуляция (encapsulation) – сокрытие реализации абстракции от пользователей абстракции.

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

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

Во-первых, у нас остается низкоуровневая модель данных. Треугольник в программе – это все также шесть не связанных друг с другом вещественных чисел.

Во-вторых, хотя в списке абстракций на уровне кода и появились алгоритмы, на практике этого недостаточно. «Процедуры сами по себе не предоставляют удовлетворительно богатого словаря абстракций» [Лисков 9]. Программист будет домысливать и строить из процедур более высокоуровневые объекты, которые не находят отражения в коде.

Условно обозначим процедурное программирование (в рассмотренном объеме) как 2 этап эволюции языков программирования.

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

§ 6. Пользовательские структуры данных. Достаточно давно языки программирования поддерживают возможность определения структур данных.

Структура данных – синтаксически связанные данные.

Примером встроенной структуры данных являются массивы. Напомним, что массив (array) – это последовательность переменных одного типа, адресуемых индексом. Элементы массива связаны синтаксически в том смысле, что мы обращаемся к ним по одному и тому же имени, но по разным индексам. Элементы также могут быть связаны семантически в том смысле, что программист, как правило, группирует не просто произвольные элементы одного типа в один массив, а элементы, имеющие некоторую смысловую связь. Кроме того, структуры данных, как правило (это уже зависит от реализации языка) группируют данные и физически, так, например, элементы массива в памяти размещаются последовательно друг за другом. Следующий код на C#, создает и заполняет массив из шести вещественных чисел.

 float[] triangle = new float[6]; for (int i = 0; i

На рисунке приведено распределение памяти в результате выполнения этого кода:

Отметим, что переменная triangle обозначает ячейку памяти, в которой хранится адрес блока памяти с элементами массива. Мы вернемся к этой особенности ниже. Сейчас обратим внимание на используемые на рисунке обозначения, мы будем применять и далее в книге. Ячейки памяти мы будем обозначать прямоугольниками (конечно, читатель понимает, что речь идет об оперативной памяти компьютера во время выполнения программы). Имена переменных, обозначающих эти ячейки в программе, будем подписывать рядом с соответствующими прямоугольниками. Значения ячеек будем записывать внутри прямоугольника. Если значение ячейки – адрес другой ячейки, то мы будем показывать это стрелкой, указывающей на адресуемую ячейку. На приведенном рисунке для большей наглядности мы показали и адреса ячеек (гипотетические), в дальнейшем мы их писать не будем.

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

В C# пользовательская структура данных создается с помощью ключевого слова class и имеет следующий синтаксис 12 , показанный на примере структур для хранения координат одной точки и для хранения центра и радиуса круга:

 // Структура для хранения точки синтаксически // группирует координаты точки. сlass Point < public float X; public float Y; >// Структура для хранения круга синтаксически // группирует радиус и центр круга. // При этом центр круга, в свою очередь, – структура Point. class Circle

Пишется ключевое слово class , далее следует имя структуры (имя определяемого пользователем нового типа данных). Именем может быть любое незарезервированное слово, удовлетворяющее требованиям к именам переменным. В C# принято называть типы с большой буквы. Далее в фигурных скобках перечисляются через точку с запятой объявления переменных, входящих в состав структуры, перед каждым объявлением указывается ключевое слово public , его назначение мы рассмотрим несколько позже. Переменные, входящие в состав структуры могут быть как встроенных, так и пользовательских (другие структуры) типов.

Таким образом, вместо того, чтобы объявить две независимых переменных float x; float y , мы объявляем одну переменную Point p , включающую в себя переменные X и Y . Для доступа к группируемым переменным используется оператор «точка»: p.X , p.Y . В C# эти составные части структуры называются полям (field). По аналогии с тем, как мы создаем массив из двух чисел для хранения координат (структура, не связанная с семантикой), мы создаем переменную типа Point (структура, связанная с семантикой). Разберите следующий пример:

 float[] pointCoords = new float[2]; pointCoords[0] = 1; pointCoords[1] = 2; Point point = new Point(); point.X = 1; point.Y = 2; 

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

Обратим внимание, что и переменная pointCoords , и переменная point обозначают ячейки, в которых хранится адрес объекта данных: в первом случае — это массив, во втором — структура Point . Соответственно, когда мы пишем point.X = 1 , выполняется переход по адресу, хранимому в ячейке point , и уже там в поле с именем X записывается значение 1. Если же мы работаем с локальной переменной и присваиваем ей значение – float x = 1 – то происходит другой процесс: переменная x обозначает ячейку, в которой непосредственно хранится значение, поэтому значение 1 записывается сразу в эту ячейку.

 float x = 1; float y = 2; 

В обоих случаях мы говорим «обозначает», имея в виду, что переменная – это лишь имя в программе некоторой ячейки памяти и при компиляции оно заменяется на адрес этой ячейки. Более того, это и есть определение переменной в программировании:

Переменная (variable) – именованная область памяти.

В C# переменные, которые обозначают ячейку, непосредственно хранящую значение, называются значимыми (value) переменными. Переменные же, обозначающие ячейку, содержащую адрес области памяти, где хранятся непосредственно данные, называются ссылочными (reference) переменными. Вид переменной в C# определяется ее типом. Так, все встроенные типы, кроме строк – значимые типы: int , long , float , double , decimal , byte , char , bool . Строка string , массивы и пользовательские типы class – ссылочные типы.

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

 float Square (Point[] vertices) < float a = Length (vertices[0], vertices[1]); float b = Length (vertices[1], vertices[2]); float c = Length (vertices[2], vertices[0]); return SquareGeron (a, b, c); >float Length (Point p0, Point p1) < return Math.Sqrt ( (x1 – x0) * (x1 – x0) + (y1 – y0) * (y1 – y0) ); >float SquareGeron (float a, float b, float c) < float p = (a + b + c) / 2; return Math.Sqrt ( p * ( p – a) * ( p -b) * (p-c)); >

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

 class Triangle < public Point A; public Point B; public Point C; > float Square (Triangle t) < float a = Length (t.A, t.B); float b = Length (t.B, t.C); float c = Length (t.C, t.A); return SquareGeron (a, b, c); >Triangle t = new Triangle(); float sq = Square(t); 

Уровень абстракции, на котором работает программа стал значительно выше. Мы уже оперируем не обезличенными массивами, а структурами с именами, соответствующими предметной области. Вспомните «размышления» программиста, которые мы приводили, демонстрируя различия в уровнях абстракции в § 4. Теперь этот процесс выглядит значительно лучше: «Мне нужна ордината Y вершины B треугольника ABC … t.B.Y – вот она.» Абстракции в программе оказываются ближе к абстракциям мышления.

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

Хотя мы существенно продвинулись в плане уровня абстракции модели данных, такой подход сопряжен с рядом проблем. Прежде всего – независимость друг от друга алгоритмов и данных. Например, площадь – это неотъемлемое свойство реального треугольника и концептуальной разницы между получением вершины A ( t.A ) и получением площади ( t.Square ?) нет. Однако в программе, эти две концепции независимы: одна абстракция – треугольник, хранит только его координаты, другая абстракция – процедура, вычисляет значение площади.

§ 7. Объединение данных с методами. Преодолеть разрыв между абстракциями данных и методов можно объединив их в одну абстракцию. Для этого нужно, чтобы пользовательская структура данных Triangle не только хранила данные треугольника, но и включала в себя методы манипулирования этими данными. В этом случае обычно используют не термин «структура данных», а термин «класс». Пока ограничимся упрощенным определением класса как структуры данных, объединенных с методами. Рассмотрим следующий код:
 public class Point public float Y; // Метод вычисляет расстояние от текущей точки, до указанной p. // Метод объявлен внутри класса Point. public float Length (Point p) < return Math.Sqrl ((X – p.X)*(X – p.X) + (Y – p.Y)*(Y – p.Y)); >> Point p1 = new Point (); p1.X = 1; p1.Y = 1; Point p2 = new Point (); p2.X = 2; p2.Y = 2; float len = p1.Length(p2); 

Вместо вызова метода float Length (Point p1, Point p2) , как в ранее рассмотренном примере, мы объявили метод Length внутри класса Point . При этом метод связан с конкретным объектом, для которого он вызывается. Что это значит? Вместо Length (p1, p2) мы пишем p1.Length(p2) . Такая запись обозначает, что метод Length вызывается для объекта p1 и внутри метода мы имеем доступ ко всем полям этого объекта. Так, мы обращаемся к полям X и Y объекта p2 , указывая его имя (имя переменной-параметра p ): p.X, p.Y . Но при этом для обращения к полям вызвавшего объекта мы просто пишем имя поля: X , Y . Таким образом, запись (X – p.X) обозначает что из значения поля X объекта p1 (для которого мы вызвали метод) мы вычитаем значение поля X объекта p2 (переданного как аргумент метода).

В действительности в любой метод класса неявно передается вызывающий объект, к которому можно обратиться внутри метода, используя ключевое слово this . Рассмотрите следующие примеры 13 :

 public class Point public float Y; // Метод вычисляет расстояние от текущей точки, до указанной p. public float Length (/* неявный параметр метода: Point this, */ Point p) < return Math.Sqrl ((X – p.X)*(X – p.X) + (Y – p.Y)*(Y – p.Y)); // эквивалентно: // return Math.Sqrl ((this.X – p.X)*(this.X – p.X) + // (this.Y – p.Y)*(this.Y – p.Y)); > > public class Circle < public Point Center; public float R; public float GetSquare( /* Circle this */ ) < return Math.PI * R * R; // эквивалентно: // return Math.PI * this.R * this.R; > > 

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

 public class Point public float y; public void Shift( /* Point this, */ float x, float y ) < this.x += x; this.y += y; > > 

В приведенном коде происходит сокрытие имен (hiding): имена переменных-параметров метода Shift скрывают имена полей класса и использование этих имен в методе обозначает переменные-параметры, а не поля. Поэтому, если мы хотим обратиться к полям, мы вынуждены явно использовать ключевое слово this .

Одним из первых известных языков, реализовавших описанный на данный момент уровень «объектности» абстракций – объединения методов и данных – был язык Simula-67, один из источников C++.

Обозначим рассмотренный этап эволюции языков программирования на нашей диаграмме как четвертый.

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

§ 8. Инкапсуляция на уровне классов. Рассмотрим две типовых ситуации, демонстрирующие, что доступность полей X и Y класса Point из предыдущего параграфа может привести к серьезным проблемам.

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

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

 class Triangle < public float Sq = -1; public float GetSquare() < if (Sq == -1) < // если еще не расчитана, то здесь расчитываем и сохраняем в Sq. >return Sq; > > 

Однако ничто не мешает поменять поле Sq извне, нарушив целостность состояния класса.

Для решения подобных проблем объектно-ориентированные языки предоставляют механизм ограничения видимости полей класса только методами класса. В C# для этого используется ключевое слово private (закрытый). Ключевые слова public и private называют модификаторами доступа ( access modifiers ) 14 :

 class Point < private float x; public float GetX() < return x; >public float SetX(float x) < this.x = x; >> // Использование: Point p = new Point(); p.SetX (5); // x == 5 float x1 = p.GetX(); // Ошибка компиляции: нельзя обратиться извне к закрытому полю. float x2 = p.x; 

Ограничение видимости полей реализует инкапсуляцию на уровне класса, позволяя скрывать закрытые поля класса от пользователей класса подобно тому, как локальные переменные метода скрываются от пользователя метода. Повторим уже приводившееся определение: инкапсуляция – сокрытие реализации абстракции от пользователей абстракции. Любая точка имеет характеристики X и Y – это существенные свойства абстракции. А вот как именно мы их храним в закрытых переменных – это вопрос реализации. Создав методы GetX / SetX для получения и изменения существенных свойств, мы скрываем от пользователя реализацию абстракции.

Обратим внимание, что ключевое слово private ограничивает видимость поля всеми методами того же класса, а не только того же объекта. Это значит, что метод класса может обратиться как закрытому ( private ) полю как того же объекта, для которого он был вызван (то есть объекта this ), так и для другого объекта того же класса:

 public class Point < private float x; private float y; // Метод вычисляет расстояние от текущей (this) точки, до указанной p. public float Length (Point p) < // Метод имеет доступ как к закрытым (private) полям объекта, // для которых он был вызван (this), так и к полям объекта p. return Math.Sqrl ((this.x – p.x)*(this.x – p.x) + (this.y – p.y)*(this.y – p.y)); >> 

Это поведение объясняется тем, что разработчик класса понимает его устройство и не будет некорректно менять другие объекты. Таким образом, принцип инкапсуляции не нарушается.

Обозначим добавление механизма инкапсуляции на уровне классов как 5 этап.

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

§ 9. Определение. Мы уже не раз формулировали следующий принцип проектирования: «Одним из главных условий эффективного программирования является максимизация части программы, которую можно проигнорировать при работе над конкретными фрагментами кода» [Макконнелл 10]. Это вполне очевидное правило является одной из формулировок принципа сокрытия информации. Считается, что впервые он был сформулирован в 1970-х годах в работе [Парнас 16] 15 .

При этом мы отмечали, что всегда и на любом языке можно написать код, вполне удовлетворяющий принципу сокрытия информации. В конце концов, мы можем структурировать код с помощью комментариев. Другой вопрос в том, что крайне сложно добиться соблюдения этого принципа, если мы не опираемся на возможности языка. Основной такой возможностью является инкапсуляция – на уровне методов (процедурное программирование) или классов (объектно-ориентированное программирование). Именно инкапсуляция позволяет создавать в коде полноценные абстракции. Полноценные в том смысле, что пользователи этих абстракций (вызывающий код) ничего не знают и не могут знать о реализации используемой абстракции, то есть могут абстрагироваться от ненужных деталей, сосредоточившись только на необходимых. «Что мы хотим от абстракции – это механизма, который позволяет выражать существенные детали и скрыть несущественные детали. В случае программирования, то, как мы можем использовать абстракцию важно, а способ, которым абстракция реализована – неважен» [Лисков 9]. Следует ясно представлять соотношение обсуждаемых понятий: сокрытие информации, инкапсуляция и абстракция. «Сокрытие информации – это прежде всего вопрос проектирования программ; сокрытие информации возможно в любой правильно спроектированной программе вне зависимости от используемого языка программирования. Инкапсуляция, однако, это прежде всего вопрос разработки конкретного языка; абстракция может быть эффективно инкапсулирована только в том случае, если язык запрещает доступ к скрытой в абстракции информации.» [18]

Классы в том объеме технических возможностей, который мы обозначили в предыдущих параграфах, реализуют идею абстрактных типов данных. Приведем развернутое определение по [Пратт 18]:

Абстрактный тип данных – новый тип данных, определяемый программистом и включающий 1) определяемый программистом тип данных; 2) набор абстрактных операций над объектами этого типа; инкапсуляцию объектов этого типа таким образом, что пользователь нового типа не может манипулировать этими объектами, иначе как только с помощью определенных при разработке типа абстрактных операций.

Это вполне исчерпывающее и точное определение, хотя оно несколько устарело в части используемых терминов. Разберем его подробно. Первый элемент – «определяемый программистом тип данных» – это собственно перечень данных, хранимых объектами определяемого типа, то есть перечень полей класса. Такая формулировка обусловлена тем, что долгое время (этапы рассмотренной эволюции 1-2-3) под типом данных понималась исключительно структура без методов. Второй элемент – «набор абстрактных операций» – это набор методов класса, имеющих доступ к полям объекта. Абстрактная операция – это, в современной терминологии, метод. Отметим, что здесь слово «абстрактная», как и в термине «абстрактный тип данных» обозначает, что речь идет о некоторой абстракции предметной области 16 . Третье утверждение в определении – об инкапсуляции – описывает возможность объявления закрытых полей, к которым имеют доступ только методы класса. Отметим, что определение предполагает, что все поля всегда будут закрытыми. В этом смысле возможность объявления открытых полей является нарушением и отходом от «теоретически правильной» реализации. Далее мы увидим, что в большинстве случаев теория совпадает с практикой и такое нарушение нежелательно. Таким образом, можно переформулировать приведенное определение на современном языке:

Абстрактный тип данных (abstract data type) – новый тип данных, определяемый программистом и включающий 1) определяемый программистом перечень данных (полей типа); 2) набор методов, имеющих доступ ко всем полям типа; инкапсуляцию объектов этого типа таким образом, что пользователь нового типа не может манипулировать этими объектами, иначе как только с помощью определенных при разработки типа методов, то есть к полям типа имеют доступ только методы этого же типа.

В чем отличие класса и абстрактного типа данных? Можно ли сказать, что абстрактный тип данных – теоретическая концепция, а класс – это реализация этой концепции в языке программирования? С одной стороны, так и есть. Но, как мы уже говорили во введении, объектно-ориентированное программирование основывается на двух базовых идеях. Первая из них и есть абстрактные типы данных. А вторая – иерархические типы. Соответственно, сформулируем следующее определение:

Класс (class) в объектно-ориентированном программировании – это определяемый программистом тип данных, удовлетворяющий определениям: 1) абстрактного и 2) иерархического типа.

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

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

Вопросы и задания

Дайте определения следующим терминам и сопоставьте русские термины с английскими: переменная, область видимости переменной, инкапсуляция, структура данных, абстрактный тип данных, класс, модификатор доступа, сокрытие имен, variable, scope, encapsulation, data structure, abstract data type, access modifier, class.

Как соотносятся следующие понятия: принцип сокрытия информации, абстракция, инкапсуляция.

Поясните по рисункам, иллюстрирующим рассмотренную эволюцию языков программирования, каждый из выделенных этапов: какие проблемы предыдущих этапов он решает и какие проблемы сохраняются?

Опишите синтаксис и семантику следующих ключевых слов C#: class , public , private , this .
Опишите различие значимых и ссылочных типов данных в C#.
* Приведите примеры встроенных (в язык программирования) структур данных помимо массивов.

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

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

9. Конечно, надо понимать, что ООП – не всегда лучшее решение. Мы вернемся к этому вопросу в заключении.

10. «Пользователь» процедуры или метода или класса или кода – это другой код, использующий (вызывающий) эту процедуру или метод или класс или фрагмент кода, либо программист, пишущий этот другой код.

11. Сигнатура метода – имя метода и перечень его параметров, определяемых типами этих параметров. Имена параметров и тип возвращаемого методом значения не входит в сигнатуру.

12. Также структура может быть определена в C# ключевым словом struct. Тут есть некоторая терминологическая путаница, забегая вперед скажем, что «структуры» в том смысле, в котором мы обсуждаем их в настоящем параграфе – это «классы» без некоторых возможностей. А структуры в C# имеют другое специфическое для этого языка назначение, мы вернемся к этому вопросу в § 11.

13. Отметим для сравнения, что в некоторых языках переменную, указывающую на объект, для которого был вызван метод класса (this), необходимо объявлять явно. Например, так работает Python: объявление метода float Length (Point p) из приведенного примера выглядело бы следующим образом: def length (self, p), но при вызове этого метода мы бы писали как и в C#: p1.length(p2). То есть в самом методе self == p1, p == p2.

14. Иногда используются не вполне точные термины спецификатор (specifier) или квалификатор (qualifier) доступа.

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

16. И не имеет отношение к абстрактным методам класса, которые мы будем рассматривать в главе 3.3.

&copy Тимофей Усов, 2019—2020.

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

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

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

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

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

Примеры АТД

См. также

  • Интерфейс программирования приложений
  • Объектно-ориентированное программирование

Ссылки

Логический • Низший тип • Коллекция • Перечисляемый тип • Исключение • First-class function • Opaque data type • Recursive data type • Семафор • Поток • Высший тип • Type class • Unit type • Void

Абстрактный тип данных • Структура данных • Интерфейс • Kind (type theory) • Примитивный тип • Subtyping • Шаблоны C++ • Конструктор типа • Parametric polymorphism

  • Типы данных
  • Структуры данных
  • Теория типов

Wikimedia Foundation . 2010 .

Полезное

Смотреть что такое «Абстрактный тип данных» в других словарях:

  • абстрактный тип данных — Тип данных (абстрактный класс), определенный посредством перечисления его методов и свойств, без создания их конкретной реализации. [http://www.morepc.ru/dict/] Тематики информационные технологии в целом EN Abstract Data TypeADT … Справочник технического переводчика
  • Алгебраический тип данных — в теории программирования любой тип, значения которого являются значениями некоторых иных типов, «обёрнутыми» конструкторами алгебраического типа. Другими словами, алгебраический тип данных имеет набор конструкторов типа, каждый из которых… … Википедия
  • Целое (тип данных) — Целое, целочисленный тип данных (англ. Integer), в информатике один из простейших и самых распространённых типов данных в языках программирования. Служит для представления целых чисел. Множество чисел этого типа представляет собой… … Википедия
  • Множество (тип данных) — У этого термина существуют и другие значения, см. Множество (значения). Множество тип и структура данных в информатике, является реализацией математического объекта множество. Данные типа множество позволяют хранить ограниченное число значений… … Википедия
  • Комплексный тип данных — Некоторые языки программирования предоставляют специальный тип данных для комплексных чисел. Наличие встроенного типа упрощает хранение комплексных величин и вычисления над ними. Содержание 1 Арифметика над комплексными 2 Поддержка в языках … Википедия
  • Указатель (тип данных) — У этого термина существуют и другие значения, см. Указатель. Диаграмма указателей Указатель (пойнтер, англ. pointer) переменная, диапазон значений которой состоит из адресов ячеек памяти и специального значения нулевого адреса.… … Википедия
  • Обобщённый алгебраический тип данных — один из видов алгебраических типов данных, который характеризуется тем, что его конструкторы могут возвращать значения не своего типа. Это понятие реализовано в нескольких языках программирования, в частности в языках ML и Haskell, причём в… … Википедия
  • Типаж (абстрактный тип) — Типаж (англ. trait) это абстрактный тип, в информатике, используемый, как «простая концептуальная модель для структурирования объектно ориентированных программ»[1]. Типажи подобны mixins, но могут включать определения методов класса.… … Википедия
  • Структура данных — Бинарное дерево, простой пример ветвящейся связной структуры данных. Структура данных (англ. data structure) программная единица, позволяющая хран … Википедия
  • Высший тип — (top type) в теории типов, часто обозначаемый как просто вершина или «закрепленным» символом (⊤), универсальный тип, то есть такой тип, который содержит в себе каждый возможный объект в нужной системе типов. Высший тип иногда именуется… … Википедия
  • Обратная связь: Техподдержка, Реклама на сайте
  • �� Путешествия

Экспорт словарей на сайты, сделанные на PHP,
WordPress, MODx.

  • Пометить текст и поделитьсяИскать в этом же словареИскать синонимы
  • Искать во всех словарях
  • Искать в переводах
  • Искать в ИнтернетеИскать в этой же категории

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *