D (язык программирования)

Материал из Википедии — свободной энциклопедии
Это старая версия этой страницы, сохранённая 77.108.234.195 (обсуждение) в 20:56, 28 апреля 2012 (Для чего D не предназначен). Она может серьёзно отличаться от текущей версии.
Перейти к навигации Перейти к поиску
D
Изображение логотипа
Семантика мультипарадигменный: императивное, объектно-ориентированное, обобщённое программирование
Класс языка объектно-ориентированный, процедурный язык программирования, язык функционального программирования, язык обобщённого программирования[вд], язык параллельного программирования[вд], мультипарадигмальный, императивный, компилируемый язык программирования и язык программирования
Тип исполнения компилятор
Появился в 1999
Автор Уолтер Брайт
Разработчик Уолтер Брайт и D Language Foundation[вд]
Расширение файлов .d, .dd, .di или .def
Выпуск 1.072 (5 декабря 2011; 13 лет назад (2011-12-05)[1])
Тестовая версия 2.057 (13 декабря 2011; 13 лет назад (2011-12-13)[2])
Система типов строгая, статическая
Основные реализации Digital Mars D, LDC, GDC
Испытал влияние Си, C++, Lua, C#, Java, Eiffel, Python, Ruby
Сайт d-programming-language.org
Логотип Викисклада Медиафайлы на Викискладе

D — объектно-ориентированный, императивный, мультипарадигмальный язык программирования, созданный Уолтером Брайтом из компании Digital Mars. Изначально был задуман как реинжиниринг языка C++, однако, несмотря на значительное влияние С++, не является его вариантом. В D были заново реализованы некоторые свойства C++, также язык испытал влияние концепций из других языков программирования, таких как Java, Python, Ruby, C#, Eiffel.

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

Стабильная версия компилятора 1.0 вышла 2 января 2007[3]. Экспериментальная версия компилятора 2.0 выпущена 17 июня 2007 года.[4]

Стабильная версия компилятора 1.0 работает на Windows, Linux, Mac OS, а с версии 1.043 также и на FreeBSD. Также для загрузки доступны исходные коды DMD (официальная реализация компилятора от Digital Mars)[5].

D — язык программирования общего назначения, предназначенный для прикладного и системного программирования. Разработан Уолтером Брайтом и его фирмой Digital Mars. D является языком высокого уровня, но сохраняет возможности прямого взаимодействия с программным интерфейсом операционной системы и с оборудованием[6]. D предназначен для написания средних и крупных систем с миллионами строк исходного кода, для ведения командной разработки. Язык D имеет C-подобный синтаксис, который более лаконичен и читабелен, чем синтаксис С++, для человека, знающего, например, С, C# или Java[7], поддерживает многие возможности в помощь программисту[8][9][10][11], а также пригоден для проведения неплохой оптимизации кода компилятором[12].

Возможности, унаследованные от C++

Синтаксис языка D схож с синтаксисом C++ и C#, что облегчает его изучение людям, знакомым с этими языками, а также перенос исходного кода с С++.

Для D не существует виртуальной машины. Программы, написанные на D, компилируются в объектные файлы, которые затем могут быть объединены компоновщиком в исполняемый файл. Стандартные инструменты для разработки на С++ (например make) могут быть использованы для D.

Что отсутствует в языке D

  • Совместимость с исходным кодом на языке C. Уже существуют языки программирования, в полной мере или практически полностью совместимые с исходным кодом, написанным на языке C (Objective-C, C++ и Vala). Авторы посчитали, что дальнейшая работа в этом направлении препятствует реализации существенных возможностей. Однако, они постарались не допускать нестандартного поведения заимствованых из C операторов, которое может приводить к трудно исправляемым ошибкам.
  • Препроцессор[13]. Включение файлов посредством #include заменено на модули с собственным пространством имён. Неструктурированные директивы типа #ifdef заменены на структурированные блоки version и debug.[14] Выполнение кода во время компиляции, включение любых константных выражений, а также чтение файлов во время компиляции могут в большинстве случаев эмулировать макро-язык C с большей надёжностью (тем не менее, некоторые возможности C утрачены, например, нельзя написать аналог #define TEXT(quotes) L ## quotes), а также открывают множество дополнительных возможностей, например перевод кода другого языка программирования в D «прямо во время компиляции».
  • Множественное наследование. Однако это частично компенсируется более надёжными интерфейсами, работа с которыми поддерживается языком D.
  • Пространства имён (namespaces). Пространства имён были попыткой решить проблему, возникающую при объединении разработанных независимо друг от друга кусков кода, когда пересекаются имена переменных, типов данных и так далее. Модульный подход выглядит проще и удобнее для использования.
  • Битовые поля (bit fields) произвольного размера. Битовые поля сложны, неэффективны и достаточно редко используются. Компенсируется модулем в стандартной библиотеке D 2.0[15]
  • Поддержка 16-битных компьютеров. В языке D нет никаких решений для генерирования качественного 16-битного кода.
  • Взаимная зависимость проходов компилирования (compiler passes). В языке C++ успешная обработка исходного кода основывается на таблице символов (symbol table) и различных командах препроцессора. Это делает невозможным предварительную обработку кода и значительно усложняет работу анализаторов кода.
  • Оператор разыменования с обращением к члену класса ->. В языке D оператор обращения к члену класса производит разыменование по умолчанию при необходимости.

Для чего предназначен язык D

  • Использование анализаторов кода;
  • Компиляция с максимальным количеством включенных уровней предупреждений (warning levels);
  • Упрощение и укрощение ООП C++;
  • Автоматическое управление памятью;
  • Встроенное тестирование и верификация;
  • Написание крупных приложений;
  • Встроенное автоматическое написание программ;
  • Абстрактная работа с указателями;
  • Программирование математики. Язык D поддерживает работу с комплексными числами и операторами определения NaN’ов (not a number) и бесконечностей (infinity). Они были добавлены в стандарт C99, но не были добавлены в C++;

Для чего D не предназначен

  • Для переноса на него в неизменном виде старого (legacy) кода на С/С++;
  • Скриптового программирования;
  • Изучения в качестве первого языка программирования.

Особенности языка D

Объектно-ориентированное программирование

Классы

Объектно-ориентированная природа языка D происходит от классов. Модель наследования не поддерживает наследования от нескольких классов, зато расширяется за счет использования интерфейсов. На вершине иерархии наследования находится класс Object, от которого все классы наследуют базовый набор функциональности. Экземпляры классов работают по ссылке, поэтому после обработки исключений не требуется писать сложный код для очистки памяти.

Перегрузка операторов

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

Эффективность

Модули

Файлы исходного кода взаимно однозначно соответствуют модулям. Вместо включения (#include) файлов исходного кода достаточно импортировать модуль. В этом случае нет необходимости беспокоиться о том, что один и тот же модуль будет импортирован несколько раз, а, значит, и нет необходимости обрамлять код в заголовочных файлах с использованием макросов препроцессора #ifndef/#endif или #pragma once. Однако, стоит помнить, что статические конструкторы (static this() { ... }, вызывается один раз до функции main()[16]) всех импортированных (прямо или косвенно) модулей вызываются до статического конструктора модуля-импортёра, то есть возможны циклические зависимости, которые порой трудно устранить (например, что то в модуле А зависит от модуля B, а в B — от А). Тогда инициализацию модулей приходится реализовывать вызовами функций в начале main().

Объявление против описания

C++ обычно требует, чтобы функции и классы были объявлены дважды — объявление происходит в заголовочных файлах (*.h), а описание происходит в файлах исходного кода (*.cpp). Это утомительный и подверженный ошибкам процесс. Очевидно, что программисту достаточно объявить функцию или класс лишь однажды, а компилятор должен впоследствии извлечь информацию об объявлении и сделать ее доступной для импортирования. Именно так работает язык программирования D, например:

class ABC
{
    int func() { return 7; }
    static int z = 7;
};

int q;

И более нет необходимости отдельного описания функций-членов, атрибутов и спецификаций внешних объявлений (extern), как в языке C++:

int ABC::func() { return 7; }

int ABC::z = 7;

extern int q;

Заметка: Конечно же, в C++ тривиальные функции вроде { return 7; } тоже могут быть описаны внутри класса, но более сложные рекомендуется определять вне класса. Вдобавок, если нужны опережающие ссылки (ссылки на класс или функцию, которые объявлены, но ещё не определены), то для этих объектов нужны прототипы (prototype). Следующий код не будет работать в C++:

class Foo
{
    int foo(Bar *c) { return c->bar(); }
};

class Bar
{
    public:
        int bar() { return 3; }
};

Но эквивалентный код на языке D будет рабочим:

class Foo
{
    int foo(Bar c) { return c.bar; }
}

class Bar
{
    int bar() { return 3; }
}

А то, будет ли функция встраиваемой (при компиляции вызов такой функции заменяется ее кодом) или нет, в языке D зависит от настроек оптимизатора.

Шаблоны

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

Ассоциативные массивы

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

Настоящий typedef

В языках C и C++ оператор typedef на самом деле просто создает синоним типа данных и никакого нового типа данных не объявляется. В языке D оператор typedef объявляет новый тип данных. Таким образом, код

typedef int handle;

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

int foo(int i);
int foo(handle h);

Документация

Документирование традиционно происходит в два этапа — написание комментариев, описывающих, что делают функции, а затем перенос этих комментариев вручную в отдельные HTML-файлы и файлы документации man. И, естественно, со временем то, что описывается в документации, будет отличаться от того, что на самом деле происходит в исходном коде. Возможность генерации документации напрямую из комментариев в файлах исходного кода не только экономит время при подготовке документации, но и облегчает поддержку соответствия документации исходному коду. Для языка D существует единая спецификация для генератора документации.

Для генерации документации в языке C++ существуют инструментальные средства, разрабатываемые отдельно от компиляторов. Использование этих инструментальных средств имеет ряд недостатков:

  • Очень сложно обработать C++ код на 100 % правильно, для этого на самом деле потребуется компилятор. Сторонние инструменты (third party tools) корректно работают только с подмножеством языка C++.
  • Разные компиляторы поддерживают разные версии языка C++ и разные расширения языка. Очень трудно соответствовать всем этим вариациям поддержки языка C++.
  • Инструменты для генерирования документации из C++ могут не быть реализованы для некоторых платформ или могут не соответствовать последней версии компилятора.

Будучи встроенным в компилятор, генерирование документации является единой для всех реализаций языка D.

Функции

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

Вложенные функции

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

Функциональные литералы (function literals)

Анонимные функции могут быть встроены напрямую в выражение.

Динамические делегирования (closure)

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

Спецификаторы доступа к параметрам функций: in, out и inout

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

Все эти возможности D позволяют использовать больше различных программных интерфейсов, а также избавляет от необходимости использовать различные искусственные приемы, как, например IDL (Interface Definition Languages).

Массивы

Массивы в языке C имеют несколько недостатков, которые приходится корректировать:

  • Информация о размерности массива не прилагается к массиву, поэтому должна извлекаться дополнительно и передаваться отдельно. Классический пример — это объявление функции main(int argc, char *argv[]). В языке D, функция main объявляется так: main(char[][] args);
  • Когда массив передается в функцию, то он неявно преобразуется в указатель на первый элемент массива, даже когда прототип функции говорит, что должен быть передан массив. При этом информация о размере массива теряется. (Однако, в С++ при передаче массива по ссылке (а не по значению) информация не теряется).
  • Размерность массивов языка C не может быть изменена. Это означает, что даже такая простая структура данных, как стек, должна быть реализована в виде сложного класса;
  • В языке C нельзя произвести проверку на нарушение границ массива, просто потому, что размерность массива неизвестна (однако ее реализация сильно замедляет работу программы);
  • Массивы объявляются с использованием оператора [] после названия массива. Это ведет к использованию очень неуклюжего синтаксиса при объявлении, скажем, ссылки (указателя — в терминах C) на массив:
int (*array)[3];

В языке D оператор [] при объявлении массива ставится после типа данных:

int[3]* array; // объявляется ссылка на массив из трёх целых чисел
long[] func(int x); // объявляется функция, возвращающая массив длинных целых

Этот код более легок для восприятия. В языке D можно использовать несколько видов массивов: указатели, статические массивы, динамические массивы и ассоциативные массивы.

Строки

Работа со строками нужна в большинстве современных программ, так или иначе работающих с текстом. Поддержка строк в языке С — весьма низкоуровневая, а C++ сохраняет совместимость с ним, добавляя более продвинутые механизмы работы со строками, как специальными контейнерами данных в стандартной библиотеке STL. Некоторые из популярных библиотек, например, Qt, добавляют свои типы данных для хранения строк (напр. для лучшей поддержки юникода). Поэтому, при программировании на С++ бывают ситуации, когда программисту приходится одновременно работать со строками 2-х — 3-х типов. Чтобы избежать этой ситуации — работа со строками в языке D пересмотрена.

Управление ресурсами (resource management)

Автоматическое управление памятью

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

Явное управление памятью

Несмотря на то, что язык D поддерживает автоматическую сборку мусора, операторы new и delete могут быть перегружены для определенных классов.

RAII — это современная методика разработки для управления распределением и освобождением ресурсов. Язык D поддерживает методику RAII в контролируемой и предсказуемой манере, независимой от цикла сборки мусора.

Производительность

Легковесные составные типы данных

Язык D поддерживает простые структуры в стиле языка C не только для совместимости с этим языком, но и потому что они очень полезны в тех случаях, когда возможностей классов слишком много.

Встроенный ассемблер

Драйверы устройств, высокопроизводительные системные приложения, а также встраиваемые системы (embedded systems) требуют углубления до уровня команд ассемблера. Программирование на языке D не требует использования ассемблера, но он реализован и является частью языка.

Отладочный код

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

Обработка исключений

Модель try-catch-finally предпочтительнее, чем просто try-catch, потому что не требует создания временных (dummy) объектов, деструктор которого будет выполнять то, что может сделать finally.

Синхронизация

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

synchronized int func() { ... }

Синхронизированные функции разрешают в один момент времени исполнять код функции только одному потоку.

Устойчивые к ошибкам методики

  • Динамические массивы вместо указателей;
  • Переменные-ссылки вместо указателей;
  • Ссылки на объекты вместо указателей;
  • Сборка мусора вместо явного управления памятью;
  • Встроенные возможности синхронизации потоков;
  • Встраиваемые функции вместо макросов;
  • Уменьшение необходимости использовать указатели;
  • Явные размеры целого типа данных;
  • Отсутствие неопределенности, касающейся наличия знака у символьного типа;
  • Отсутствие необходимости повторного объявления;

Проверки во время компиляции

  • Более строгая проверка на соответствие типа данных;
  • Никаких пустых условий в цикле for;
  • Присвоения не возвращают булевого значения;
  • Проверка использования устаревших API;

Проверки во время выполнения

  • Выражения assert();
  • Проверка на выход за пределы массива;
  • Исключение при нехватке памяти;

Совместимость

Приоритеты операторов и правила вычисления

Язык D сохранил операторы из языка C, а также их приоритеты и правила вычисления. Это позволит избежать ситуаций, когда операторы ведут себя неожиданным образом (тем не менее Кёрниган и Ритчи, авторы языка C, признаю́т, что где-то приоритет операторов в этом языке нелогичен[17]).

Прямой доступ к API языка C

Язык D не только имеет типы данных, соответствующие типам данных языка C, но и обеспечивает прямой доступ к функциям языка C. В таком случае нет необходимости писать функции-обертки (wrapper functions) или копировать значения членов составных типов по одному.

Поддержка всех типов данных языка C

Это делает возможным взаимодействие с API языка C или с кодом существующей библиотеки языка C. Эта поддержка включает структуры, объединения, перечисления, указатели и все типы данных, введённые в стандарте C99.

Обработка исключений операционной системы

Механизм обработки исключений языка подключается к механизмам обработки исключений операционной системы.

Использование существующих инструментариев

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

Управление проектом

Контроль версий

В языке D реализована встроенная поддержка генерирования нескольких версий программ из одного исходного кода. Это заменяет использование команд препроцессора #if и #endif.

Устаревание

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

Отсутствие предупреждений (warnings)

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

Отсутствие макросов

Отказ создателей языка от препроцессора (как, например, в языке Си) многие расценивают как рискованный и неверный шаг. Но в D имеются встроенные средства, которые позволяют обходиться без препроцессора. По мнению многих, препроцессор C чересчур мощный и опасный.

Реализация на языке Си Реализация на языке D
#if !defined(SOME_FILE_H)
//#ifndef SOME_FILE_H
#define SOME_FILE_H
// Код
#endif
// Объявление постоянной
#define MAX_INT 32767

// Создание псевдонимов
#define true TRUE

#if defined(Mac_OS_X)
// Код для Mac
#else
// Иначе
#endif
// Объявление постоянной
enum uint MAX_INT=32767

// Создание псевдонимов
alias TRUE true

version(Mac OS X)
{
    // Код для Mac
} else {
    // Иначе
}

Стандартная библиотека

В отличие от многих других языков, в D две стандартные библиотеки: Phobos и Tango. Phobos поставляется вместе с компилятором.

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

version (Tango)
{
    // Код, работающий с функциями Tango
} else
{
    // Код, работающий с функциями Phobos
}

Tangobos

Для того чтобы решить проблемы с библиотеками, был запущен проект Tangobos. Tangobos — обёртка Tango, дающая программисту интерфейс Phobos.

Примеры

Эта программа печатает аргументы командной строки. Функция main является точкой входа программы, а args — массив с параметрами запуска программы.

module main; // Желательно объявлять

version (Tango)
    import tango.io.Stdout;
else
    import std.stdio; // для writefln()

void main(char[][] args)
{
    foreach(int i, char[] a; args)
    {
        version(Tango)
            Stdout.formatln("args[{}] = {}", i, a);
        else
            writefln("args[%d] = '%s'", i, a);
    }
}

См. также

Примечания

  1. Список изменений в компиляторе языка Ди версии 1.0.
  2. Список изменений в компиляторе языка Ди версии 2.0.
  3. DMD 1.00 — here it is! (digitalmars.D.announce) (англ.)
  4. D 2.0 changelog. Дата обращения: 11 января 2009. (англ.)
  5. Исторический момент включения исходных текстов в dmd 1.041 (англ.)
  6. D x86 Inline Assembler Встроенный ассемблер в D
  7. [1] Пример программы на D (англ.)
  8. [2] Модульное тестирование D программы (англ.)
  9. [3] Контрактное программирование в D (англ.)
  10. Embedded Documentation Встроенный генератор документации языка D (англ.)
  11. [4] dmd может копмилировать программы из html файла (англ.)
  12. Named Return Value Optimization Личная техника оптимизации Уолтера Брайта (англ.)
  13. The C Preprocessor Versus D — digitalmars.com (англ.)
  14. Conditional Compilation — digitalmars.com (англ.)
  15. std.bitmanip (англ.)
  16. [5] Статические конструкторы в D
  17. [Яык программирования C. Второе издание. Введение: Как и у любых языков, у C есть свои недостатки: некоторые операции имеют нелогичный приоритет <…>]

Ссылки

  • D Programming Language (англ.) (4 мая 2006). — официальный сайт. Дата обращения: 28 июля 2008.