Вторая часть перевода D Programming Language Tutorial от Ali Çehreli. Большая часть материала ориентирована на начинающих, но так как большая часть аудитории уже имеет базовые знания в программировании, то этот материал убран под хабракаты. В данной части рассматриваются фундаментальные типы, свойства типов, основы компиляции и императивного программирования.
Предыдущие части:
1. Часть 1
Компилятор
Ранее мы увидели, что наиболее часто используемые инструменты в D — текстовый редактор и компилятор. Программы на D пишутся в текстовых редакторах (прим. ваш КО).
При использовании компилируемых языков, таких как D, необходимо понимать концепцию компиляции и функцию компилятора.
Машинные коды
Большинство архитектур ЦПУ используют специфичные для них машинные коды. Эти машинные инструкции определяются с учетом ограничений аппаратных средств во время проектирования архитектуры. На самом низком уровне эти инструкции реализованы как электические сигналы. Так как простота программирования на этом уровне не является главной целью, написание программ напрямую в машиннах кодах ЦПУ является очень сложной задачей.
Эти машинные инструкции представляют собой специальные числа, которые представляют различные операции, поддерживаемые конкретным ЦПУ. Например, для воображаемого 8-битного ЦПУ, число 4 может представлять операцию загрузки, число 5 — операцию сохранения, и число 6 — операцию увеличения значения на единицу. Предполагая, что первые 3 бита слева — номер операции и 5 последующих бита — значение, которое используется в этой операции, программа-пример в машинных кодах этого ЦПУ может выглядеть следующим образом:
Operation Value Meaning
100 11110 LOAD 11110
101 10100 STORE 10100
110 10100 INCREMENT 10100
000 00000 PAUSE
Находясь настолько близко к железу, машинные коды не подходят для представления высокоуровневых понятий таких как: игральная карта или запись о студенте.
Языки программирования
Языки программирования разработаны быть эффективным способом программирования ЦПУ с возможностью представления высокоуровневых концепций. Языкам программирования не приходится иметь дело с ограничениями аппаратуры; их главная задача — простота использования и выразительность. Языки программирования легче понимаются людьми, они ближе к естественным языкам:
if (a_card_has_been_played()) {
display_the_card();
}
Однако языки программирования придерживаются гораздо более строгих и формальных правил, чем любой язык, на котором говорят.
Компилируемые языки
В некоторых языках программирования инструкции должны быть скомпилированы прежде, чем стать исполняемой программой. Такие языки создают быстро выполняющиеся программы, но процесс разработки включает в себя два основных шага: написание программы и ее компиляцию.
В общем случае компилируемые языки помогают в обнаружении ошибок даже до того, как программа начинает выполняться.
D — компилируемый язык.
Интерпретируемые языки
Некоторые языки программирования не требует компиляции. Такие языки называются интерпретируемыми. Программа может запущена прямо из свеженапечатанного исходного кода. Некоторые примеры интерпретируемых языков: Python, Ruby и Perl. Так как этап компиляции отсутствует, для таких языков разработка программы может быть проще. С другой стороны, так как инструкции программы должны быть разобраны для интерпретации каждый раз, когда программа запускается, программа на таких языках медленнее, чем их эквиваленты, написанные на компилируемых языках.
В общем случае для интерпретируемых языков, многие типы ошибок в программе не могут быть обнаружены до момента начала выполнения.
Компилятор
Назначение компилятора — трансляция: он транслирует программы, написанные на языке программирования, в машинный код. Это перевод из языка программиста на язык ЦПУ. Такая трансляция называется компиляцией. Каждый компилятор понимает какой-то конкретный язык программирования и описывается, как компилятор для этого языка, например «компилятор для D».
Ошибки компиляции
Так как компилятор компилирует программу согласно правилам языка, он останавливает компиляцию как только достигает некорректных инструкций. Некорректные инструкции — те, которые выбиваются из спецификаций языка. Проблемы, такие как: несоответсвующие скобки, отсутствующая точка с запятой, ключевое слово с опечаткой и т.д. — все вызывают ошибки компиляции.
Также компилятор генерирует предупреждения компиляции, когда он видит подозрительный кусок кода, который может вызывать беспокойство, но не обязательно является ошибкой. Однако, предупреждения практически всегда указывают на настоящую ошибку или плохой стиль, поэтому самая распространенная практика — рассматривать большинство или все предупреждения как ошибки.
Фундаментальные типы
Ранее мы выяснили, что
Наименьшая единица данных в компьтере называется битом, которая может принимать значения 0 или 1.
Так как тип данных, который может содержать только значения 0 или 1 имел бы очень ограниченное применение, ЦПУ определяет бОльшие типы данных, которые являются комбинациями из более одного бита. Например, байт содержит 8 битов. Наиболее производительный тип данных определяет битность ЦПУ: 32-битный ЦПУ, 64-битный ЦПУ и т.п.
Типов, определенных ЦПУ, все еще недостаточно: они не могут описать высокоуровневые понятия, как имя студента или игральная карта. D предоставляет множество полезных типов данных, но даже этих типов не хватает для описания многих высокоуровневых концепций. Такие концепции должны быть определены программистом как структуры (struct) или классы (class), которые мы увидим в следующих главах.
Фундаментальные типы D очень похожи на фундаментальные типы многих других языков, как показано в следующей таблице. Термины, появляющиеся в этой таблице, объяснены ниже:
Тип | Определение | Начальное значение |
---|---|---|
bool | Логическое значение Истина/Ложь | false |
byte | 8 битное число со знаком | 0 |
ubyte | 8 битное число без знака | 0 |
short | 16 битное число со знаком | 0 |
ushort | 16 битное число без знака | 0 |
int | 32 битное число соз знаком | 0 |
uint | 32 битное число без знака | 0 |
long | 64 битное число со знаком | 0 |
ulong | 64 битное число без знака | 0 |
float | 32 битное действительное число с плавающей точкой | float.nan |
double | 64 битное действительное число с плавающей точкой | double.nan |
double | 64 битное действительное число с плавающей точкой | double.nan |
real | наибольшое число с плавающей точкой, которое поддерживается оборудованием | real.nan |
ifloat | мнимая часть комплексного числа для float | float.nan * 1.0i |
idouble | мнимая часть комплексного числа для double | double.nan * 1.0i |
ireal | мнимая часть комплексного числа для real | real.nan * 1.0i |
cfloat | комплексный вариант float | float.nan + float.nan * 1.0i |
cdouble | комплексный вариант double | double.nan + double.nan * 1.0i |
creal | комплексный вариант real | real.nan + real.nan * 1.0i |
char | символ UTF-8 (code point) | 0xFF |
wchar | символ UTF-16 (code point) | 0xFFFF |
dchar | символ UTF-32 (code point) | 0x0000FFFF |
В довесок к вышеперечисленным, ключевое слово void описывает сущности не имеющие типа. Ключевые слова cent и ucent зарезервированы для будущего использования для представления знаковых и безнаковых 128-битных значений.
Вы можете использовать int для большинства значений, до тех пор пока нет особых причин не делать это. Рассмотрите double для представления понятий, которые имеют дробную часть.
Тип со знаком: Тип, который может принимать отрицательные и положительные значения. Например, byte может иметь значения от 0 до 255. Буква u в начале названия этих типов взята от слова unsigned.
Число с плавающей точкой: Тип может представлять значения с десятичной дробной частью, как у 1.25. Точность вычислений с плавающей точкой напрямую зависит от числа битов в типе: больше число битов, получаем более точные результаты.
Только числа с плавающей точкой могут представлять десятичные дроби; целочисленные типы (например, int) могут держать только такие значения, как 1 и 2.
Комлексные типы чисел: Эти типы могут представлять комплексные числа из математики.
Мнимые типы чисел: Эти типы могут представлять только мнимую часть комплексных чисел. Буква i, которая находится в колонке начальных значений, в математике является квадратным корнем из -1.
nan: Сокращение от «not a number», представляющее некорректное значение с плавающей точкой.
NaN'ы обладают интересным свойством, что в какой бы операции они не использовались, то в результате получается снова NaN. Поэтому NaN будет распространяться и будет результатом любых вычислений, где он был использован. Это означает, что внезапно появляющийся NaN является недвусмысленным индикатором того, что была использована непроиницилизированная переменная.
Если бы 0.0 использовался как начальное значение, его последствия легко было бы не заметить на выходе, поэтому если инициализация по умолчанию была непреднамеренная, баг мог бы остаться незамеченным.
Не подразумевается, что инициализатор по умолчанию должен быть полезным значением, его цель — вскрытие багов. NaN хорошо подходит под эту роль.
Но, конечно, компилятор может обнаружить и сгенерировать ошибку о переменных, которые не были проинициализированы? В большинстве случаев он может, но не всегда, и то, что он может, зависит от сложности внутреннего анализа потоков данных. Поэтому надежда на этот механизм — непереносимое и ненадежное решение.
Из-за особенностей реализации ЦПУ, NaN отсутствует для целочисленных, поэтому вместо этого D использует 0. Нулевое значение не имеет таких преимуществ для обнаружения ошибок, какими обладает NaN, но, по крайней мере, ошибки от непреднамеренной инициализации будут повторимы и поэтому более отлаживаемыми.
Свойства типов
В D у типов есть свойства. Чтобы получить значение свойства, нужно написать имя свойства после типа через точку. Например, sizeof свойство int вызывается так: int.sizeof. В этой главе рассматриваются только следующие четыре атрибута:
- stringof: имя типа
- sizeof: размер типа в байтах (для кол-ва битов умножьте на 8)
- min: минимальное значение, которое может принимать тип
- max: максимальное значение, которое может принимать тип
import std.stdio;
void main()
{
writeln("Тип : ", int.stringof);
writeln("Длина в байтах : ", int.sizeof);
writeln("Минимальное значение : ", int.min);
writeln("Максимальное значение: ", int.max);
}
size_t
Вы также встретитесь с типом size_t, имя которого расшифровывается как «size type». Он не является самостоятельным типом, а псевдонимом безнакового типа, которого хватит для представления всех возможных адресов в памяти. Поэтому этот тип зависит от системы: uint на 32-х битных, ulong для 64-х битных системах и т.д.
Можно использовать свойство .stringof, чтобы увидеть на какой тип ссылается size_t на вашей системе:
import std.stdio;
void main()
{
writeln(size_t.stringof);
}
Вывод на моей системе:
ulong
Примечание:Нельзя использовать зарезервированные типы cent и ucent в любой программе; как исключение, void не имеет свойств .min и .max.
import std.stdio;
void main()
{
writeln("Тип : ", short.stringof);
writeln("Размер в байтах : ", short.sizeof);
writeln("Минимальное значение : ", short.min);
writeln("Максимальное занчение: ", short.max);
writeln();
writeln("Тип : ", ulong.stringof);
writeln("Размер в байтах : ", ulong.sizeof);
writeln("Минимальное значение : ", ulong.min);
writeln("Максимальное занчение: ", ulong.max);
}
Оператор присваивания и порядок вычислений
Первые два камня преткновения, которые ждут изучающих программирование, это — оператор присваивания и порядок вычислений.
Оператор присваивания
Вы будете встречать подобные строчки практически во всех программах, практически во всех языках программирования:
a = 10;
Значение этой строчки — «сделай значение a равным 10». Аналогично следующая строчка обозначает «сделай значение b равным 20»:
b = 20;
На основе вышеизложенной информации что можно сказать о следущей строчке?
a = b;
К сожалению, эта строчка не содержит оператор сравнения из математики, который, я предполагаю, все знают. Выражение выше не означает «a равно b»! Когда мы используем ту же логику, что и в предыдущих двух строчках, выражение выше должно означать «сделай значение a равным значению b».
Хорошо известный символ = в математике имеет совершенно другой смысл в программировании: «сделать значение слева равным значению с правой стороны».
Порядок вычислений
Операции программы выполняются шаг за шагом в определенном порядке. Предыдущие три выражения в программе можно увидеть в следующем порядке:
a = 10;
b = 20;
a = b;
Смысл этих строк вместе: «сделай значение a равным 10, затем сделай значение b равным 20, затем сделай значение a равным значению b». Соответственно, после выполнения этих трех операций оба значения a и b будут равны 20.
Упражнение
Изучите, как следующие три операции меняют местами значения a и b. Если в начале их значения были равны 1 и 2 соответственно, то после выполнения операций значения становятся равны 2 и 1:
c = a;
a = b;
b = c;
в начале → a 1, b 2, c неважно
c = a → a 1, b 2, c 1
a = b → a 2, b 2, c 1
b = c → a 2, b 1, c 1
В конце значения a и b меняются местами.
Автор: NCrashed