learnxinyminutes-docs/ru/d.md
2024-12-08 23:20:53 -07:00

754 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
filename: learnd-ru.d
contributors:
- ["Anton Pastukhov", "https://anton9.com/"]
- ["Robert Brights-Gray", "https://lhs.su/"]
- ["Andre Polykanine", "https://oire.me/"]
---
D - современный компилируемый язык общего назначения с Си-подобным синтаксисом,
который сочетает удобство, продуманный дизайн и высокую производительность.
D - это С++, сделанный правильно.
```d
// Welcome to D! Это однострочный комментарий
/* многострочный
комментарий */
/+
// вложенные комментарии
/* еще вложенные
комментарии */
/+
// мало уровней вложенности? Их может быть сколько угодно.
+/
+/
/*
Имя модуля. Каждый файл с исходным кодом на D модуль.
Если имя не указано явно, то предполагается, что оно совпадает с именем
файла. Например, для файла "test.d" имя модуля будет "test", если явно
не указать другое
*/
module app;
// импорт модуля. Std — пространство имен стандартной библиотеки (Phobos)
import std.stdio;
// можно импортировать только нужные части, не обязательно модуль целиком
import std.exception : enforce;
// точка входа в программу — функция main, аналогично C/C++
void main()
{
writeln("Hello, world!");
}
/*** типы и переменные ***/
int a; // объявление переменной типа int (32 бита)
float b = 12.34; // тип с плавающей точкой
double c = 56.78; // тип с плавающей точкой (64 бита)
/*
Численные типы в D, за исключением типов с плавающей точкой и типов
комплексных чисел, могут быть беззнаковыми.
В этом случае название типа начинается с префикса "u"
*/
uint d = 10; ulong e = 11;
bool b = true; // логический тип
char d = 'd'; // UTF-символ, 8 бит. D поддерживает UTF "из коробки"
wchar e = 'é'; // символ UTF-16
dchar f; // и даже UTF-32, если он вам зачем-то понадобится
string s = "для строк есть отдельный тип, это не просто массив char-ов из Си";
wstring ws = "поскольку у нас есть wchar, должен быть и wstring";
dstring ds = "...и dstring, конечно";
string кириллица = "Имена переменных должны быть в Unicode, но не обязательно на латинице.";
typeof(a) b = 6; // typeof возвращает тип своего выражения.
// В результате, b имеет такой же тип, как и a
// Тип переменной, помеченной ключевым словом auto,
// присваивается компилятором исходя из значения этой переменной
auto x = 1; // Например, тип этой переменной будет int.
auto y = 1.1; // этой — double
auto z = "Zed is dead!"; // а этой — string
int[3] arr = [1, 2, 3]; // простой одномерный массив с фиксированным размером
int[] arr2 = [1, 2, 3, 4]; // динамический массив
int[string] aa = ["key1": 5, "key2": 6]; // ассоциативный массив
/*
Строки и массивы в D встроенные типы. Для их использования не нужно
подключать ни внешние, ни даже стандартную библиотеку, хотя в последней
есть множество дополнительных инструментов для работы с ними.
*/
immutable int ia = 10; // неизменяемый тип,
// обозначается ключевым словом immutable
ia += 1; // — вызовет ошибку на этапе компиляции
// перечислимый (enumerable) тип,
// более правильный способ работы с константами в D
enum myConsts = { Const1, Const2, Const3 };
// свойства типов
writeln("Имя типа : ", int.stringof); // int
writeln("Размер в байтах : ", int.sizeof); // 4
writeln("Минимальное значение : ", int.min); // -2147483648
writeln("Максимальное значение : ", int.max); // 2147483647
writeln("Начальное значение : ", int.init); // 0. Это значение,
// присвоенное по умолчанию
// На самом деле типов в D больше, но все мы здесь описывать не будем,
// иначе не уложимся в Y минут.
/*** Приведение типов ***/
// to!(имя типа)(выражение) - для большинства конверсий
import std.conv : to; // функция "to" - часть стандартной библиотеки, а не языка
double d = -1.75;
short s = to!short(d); // s = -1
/*
cast - если вы знаете, что делаете. Кроме того, это единственный способ
преобразования типов-указателей в "обычные" и наоборот
*/
void* v;
int* p = cast(int*)v;
// Для собственного удобства можно создавать псевдонимы
// для различных встроенных объектов
alias int newInt; // теперь можно обращаться к newInt так, как будто бы это int
newInt a = 5;
alias newInt = int; // так тоже допустимо
alias uint[2] pair; // дать псевдоним можно даже сложным структурам данных
/*** Операторы ***/
int x = 10; // присваивание
x = x + 1; // 11
x -= 2; // 9
x++; // 10
++x; // 11
x *= 2; // 22
x /= 2; // 11
x = x ^^ 2; // 121 (возведение в степень)
x ^^= 2; // 1331 (то же самое)
string str1 = "Hello";
string str2 = ", world!";
string hw = str1 ~ str2; // Конкатенация строк
int[] arr = [1, 2, 3];
arr ~= 4; // [1, 2, 3, 4] - добавление элемента в конец массива
/*** Логика и сравнения ***/
int x = 0; int y = 1;
x == y; // false
x > y; // false
x < y; // true
x >= y; // false
x != y; // true. ! — логическое "не"
x > 0 || x < 1; // true. || — логическое "или"
x > 0 && x < 1; // false && — логическое "и"
x ^ y // true; ^ - xor (исключающее "или")
// Тернарный оператор
auto y = (x > 10) ? 1 : 0; // если x больше 10, то y равен 1,
// в противном случае y равен нулю
/*** Управляющие конструкции ***/
// if - абсолютно привычен
if (a == 1) {
// ..
} else if (a == 2) {
// ..
} else {
// ..
}
// switch
switch (a) {
case 1:
// делаем что-нибудь
break;
case 2:
// делаем что-нибудь другое
break;
case 3:
// делаем что-нибудь еще
break;
default:
// default обязателен, без него будет ошибка компиляции
break;
}
// в D есть констукция "final switch". Она не может содержать секцию "defaul"
// и применяется, когда все перечисляемые в switch варианты должны быть
// обработаны явным образом
int dieValue = 1;
final switch (dieValue) {
case 1:
writeln("You won");
break;
case 2, 3, 4, 5:
writeln("It's a draw");
break;
case 6:
writeln("I won");
break;
}
// while
while (a > 10) {
// ..
if (number == 42) {
break;
}
}
while (true) {
// бесконечный цикл
}
// do-while
do {
// ..
} while (a == 10);
// for
for (int number = 1; number < 11; ++number) {
writeln(number); // все абсолютно стандартно
}
for ( ; ; ) {
// секции могут быть пустыми. Это бесконечный цикл в стиле Си
}
// foreach - универсальный и самый "правильный" цикл в D
foreach (element; array) {
writeln(element); // для простых массивов
}
foreach (key, val; aa) {
writeln(key, ": ", val); // для ассоциативных массивов
}
foreach (c; "hello") {
writeln(c); // hello. Поскольку строки - это вариант массива,
// foreach применим и к ним
}
foreach (number; 10..15) {
writeln(number); // численные интервалы можно указывать явным образом
// этот цикл выведет значения с 10 по 14, но не 15,
// поскольку диапазон не включает в себя верхнюю границу
}
// foreach_reverse - в обратную сторону
auto container = [1, 2, 3];
foreach_reverse (element; container) {
writefln("%s ", element); // 3, 2, 1
}
// foreach в массивах и им подобных структурах не меняет сами структуры
int[] a = [1, 2 ,3 ,4 ,5];
foreach (elem; array) {
elem *= 2; // сам массив останется неизменным
}
writeln(a); // вывод: [1, 2, 3, 4, 5] Т.е изменений нет
// добавление ref приведет к тому, что массив будет изменяться
foreach (ref elem; array) {
elem *= 2;
}
writeln(a); // [2, 4, 6, 8, 10]
// foreach умеет рассчитывать индексы элементов
int[] a = [1, 2, 3, 4, 5];
foreach (ind, elem; array) {
writeln(ind, " ", elem); // через ind - доступен индекс элемента,
// а через elem - сам элемент
}
/*** Функции ***/
test(42); // Что, вот так сразу? Разве мы где-то уже объявили эту функцию?
// Нет, вот она. Это не Си, здесь объявление функции не обязательно должно быть
// до первого вызова
int test(int argument) {
return argument * 2;
}
// В D используется единый синтаксис вызова функций
// (UFCS, Uniform Function Call Syntax), поэтому так тоже можно:
int var = 42.test();
// и даже так, если у функции нет аргументов:
int var2 = 42.test;
// можно выстраивать цепочки:
int var3 = 42.test.test;
/*
Аргументы в функцию передаются по значению (т.е. функция работает не с
оригинальными значениями, переданными ей, а с их локальными копиями.
Исключение составляют объекты классов, которые передаются по ссылке.
Кроме того, любой параметр можно передать в функцию по ссылке с помощью
ключевого слова "ref"
*/
int var = 10;
void fn1(int arg) {
arg += 1;
}
void fn2(ref int arg) {
arg += 1;
}
fn1(var); // var все еще = 10
fn2(var); // теперь var = 11
// Возвращаемое значение тоже может быть auto,
// если его можно "угадать" из контекста
auto add(int x, int y) {
return x + y;
}
auto z = add(x, y); // тип int - компилятор вывел его автоматически
// Значения аргументов по умолчанию
float linearFunction(float k, float x, float b = 1)
{
return k * x + b;
}
auto linear1 = linearFunction(0.5, 2, 3); // все аргументы используются
auto linear2 = linearFunction(0.5, 2); // один аргумент пропущен, но в функции
// он все равно использован и равен 1
// допускается описание вложенных функций
float quarter(float x) {
float doubled(float y) {
return y * y;
}
return doubled(doubled(x));
}
// функции с переменным числом аргументов
int sum(int[] a...)
{
int s = 0;
foreach (elem; a) {
s += elem;
}
return s;
}
auto sum1 = sum(1);
auto sum2 = sum(1,2,3,4);
/*
модификатор "in" перед аргументами функций говорит о том, что функция имеет
право их только просматривать. При попытке модификации такого аргумента
внутри функции - получите ошибку
*/
float printFloat(in float a)
{
writeln(a);
}
printFloat(a); // использование таких функций - самое обычное
// модификатор "out" позволяет вернуть из функции несколько результатов
// без посредства глобальных переменных или массивов
uint remMod(uint a, uint b, out uint modulus)
{
uint remainder = a / b;
modulus = a % b;
return remainder;
}
uint modulus; // пока в этой переменной ноль
uint rem = remMod(5, 2, modulus); // наша "хитрая" функция, и теперь
// в modulus - остаток от деления
writeln(rem, " ", modulus); // вывод: 2 1
/*** Структуры, классы, базовое ООП ***/
// Объявление структуры. Структуры почти как в Си
struct MyStruct {
int a;
float b;
void multiply() {
return a * b;
}
}
MyStruct str1; // Объявление переменной с типом MyStruct
str1.a = 10; // Обращение к полю
str1.b = 20;
auto result = str1.multiply();
MyStruct str2 = {4, 8} // Объявление + инициализация в стиле Си
auto str3 = MyStruct(5, 10); // Объявление + инициализация в стиле D
// области видимости полей и методов - 3 способа задания
struct MyStruct2 {
public int a;
private:
float b;
bool c;
protected {
float multiply() {
return a * b;
}
}
/*
в дополнение к знакомым public, private и protected, в D есть еще
область видимости "package". Поля и методы с этим атрибутом будут
доступны изо всех модулей, включенных в "пакет" (package), но не
за его пределами. package - это "папка", в которой может храниться
несколько модулей. Например, в "import.std.stdio", "std" - это
package, в котором есть модуль stdio (и еще множество других)
*/
package:
string d;
/* помимо этого, имеется еще один модификатор - export, который позволяет
использовать объявленный с ним идентификатор даже вне самой программы !
*/
export:
string description;
}
// Конструкторы и деструкторы
struct MyStruct3 {
this() { // конструктор. Для структур его не обязательно указывать явно,
// в этом случае пустой конструктор добавляется компилятором
writeln("Hello, world!");
}
// а вот это конструкция - одна из интересных идиом и представляет собой
// конструктор копирования, т.е конструктор, возвращающий копию структуры.
// Работает только в структурах.
this(this)
{
return this;
}
~this() { // деструктор, также необязателен
writeln("Awww!");
}
}
// Объявление простейшего класса
class MyClass {
int a; // в D по умолчанию данные-члены являются public
float b;
}
auto mc = new MyClass(); // ...и создание его экземпляра
auto mc2 = new MyClass; // ... тоже сработает
// Конструктор
class MyClass2 {
int a;
float b;
this(int a, float b) {
this.a = a; // ключевое слово "this" - ссылка на объект класса
this.b = b;
}
}
auto mc2 = new MyClass2(1, 2.3);
// Классы могут быть вложенными
class Outer
{
int m;
class Inner
{
int foo()
{
return m; // можно обращаться к полям "внешнего" класса
}
}
}
// наследование
class Base {
int a = 1;
float b = 2.34;
// это статический метод, т.е метод который можно вызывать, обращаясь
// к классу напрямую, а не через создание экземпляра объекта
static void multiply(int x, int y)
{
writeln(x * y);
}
}
Base.multiply(2, 5); // используем статический метод. Результат: 10
class Derived : Base {
string c = "Поле класса - наследника";
// override означает то, что наследник предоставит свою реализацию метода,
// переопределив метод базового класса
override static void multiply(int x, int y)
{
super.multiply(x, y); // super - это ссылка на класс-предок, или базовый класс
writeln(x * y * 2);
}
}
auto mc3 = new Derived();
writeln(mc3.a); // 1
writeln(mc3.b); // 2.34
writeln(mc3.c); // Поле класса - наследника
// Финальный класс, наследовать от него нельзя
// кроме того, модификатор final работает не только для классов, но и для методов
// и даже для модулей !
final class FC {
int a;
}
class Derived : FC { // это вызовет ошибку
float b;
}
// Абстрактный класс не может быть истанциирован, но может иметь наследников
abstract class AC {
int a;
}
auto ac = new AC(); // это вызовет ошибку
class Implementation : AC {
float b;
// final перед методом нефинального класса означает запрет возможности
// переопределения метода
final void test()
{
writeln("test passed !");
}
}
auto impl = new Implementation(); // ОК
/*** Примеси (mixins) ***/
// В D можно вставлять код как строку, если эта строка известна на этапе
// компиляции. Например:
void main() {
mixin(`writeln("Hello World!");`);
}
// еще пример
string print(string s) {
return `writeln("` ~ s ~ `");`;
}
void main() {
mixin (print("str1"));
mixin (print("str2"));
}
/*** Шаблоны ***/
/*
Шаблон функции. Эта функция принимает аргументы разных типов, которые
подставляются вместо T на этапе компиляции. "T" - это не специальный
символ, а просто буква. Вместо "T" может быть любое слово, кроме ключевого.
*/
void print(T)(T value) {
writefln("%s", value);
}
void main() {
print(42); // В одну и ту же функцию передается: целое
print(1.2); // ...число с плавающей точкой,
print("test"); // ...строка
}
// "Шаблонных" параметров может быть сколько угодно
void print(T1, T2)(T1 value1, T2 value2) {
writefln(" %s %s", value1, value2);
}
void main() {
print(42, "Test");
print(1.2, 33);
}
// Шаблон класса
class Stack(T)
{
private:
T[] elements;
public:
void push(T element) {
elements ~= element;
}
void pop() {
--elements.length;
}
T top() const @property {
return elements[$ - 1];
}
size_t length() const @property {
return elements.length;
}
}
void main() {
/*
восклицательный знак - признак шаблона. В данном случае мы создаем
класс и указываем, что "шаблонное" поле будет иметь тип string
*/
auto stack = new Stack!string;
stack.push("Test1");
stack.push("Test2");
writeln(stack.top);
writeln(stack.length);
stack.pop;
writeln(stack.top);
writeln(stack.length);
}
/*** Диапазоны (ranges) ***/
/*
Диапазоны - это абстракция, которая позволяет легко использовать разные
алгоритмы с разными структурами данных. Вместо того, чтобы определять свои
уникальные алгоритмы для каждой структуры, мы можем просто указать для нее
несколько единообразных функций, определяющих, ак_ мы получаем доступ
к элементам контейнера, вместо того, чтобы описывать внутреннее устройство
этого контейнера. Сложно? На самом деле не очень.
Простейший вид диапазона - Input Range. Для того, чтобы превратить любой
контейнер в Input Range, достаточно реализовать для него 3 метода:
- empty - проверяет, пуст ли контейнер
- front - дает доступ к первому элементу контейнера
- popFront - удаляет из контейнера первый элемент
*/
struct Student
{
string name;
int number;
string toString() {
return format("%s(%s)", name, number);
}
}
struct School
{
Student[] students;
}
struct StudentRange
{
Student[] students;
this(School school) {
this.students = school.students;
}
bool empty() {
return students.length == 0;
}
Student front() {
return students[0];
}
void popFront() {
students = students[1 .. $];
}
}
void main(){
auto school = School([
Student("Mike", 1),
Student("John", 2) ,
Student("Dan", 3)
]);
auto range = StudentRange(school);
writeln(range); // [Mike(1), John(2), Dan(3)]
writeln(school.students.length); // 3
writeln(range.front()); // Mike(1)
range.popFront();
writeln(range.empty()); // false
writeln(range); // [John(2), Dan(3)]
}
/*
Смысл в том, что нам не так уж важно внутреннее устройство контейнера, если
у нас есть унифицированные методы доступа к его элементам.
Кроме Input Range в D есть и другие типы диапазонов, которые требуют
реализации большего числа методов, зато дают больше контроля. Это большая
тема и мы не будем в подробностях освещать ее здесь.
Диапазоны - это важная часть D, они используются в нем повсеместно.
*/
```
## Что дальше?
- [Официальный сайт](http://dlang.org/)
- [Онлайн-книга](http://ddili.org/ders/d.en/)
- [Официальная вики](http://wiki.dlang.org/)