D - современный компилируемый язык общего назначения с Си-подобным синтаксисом, который сочетает удобство, продуманный дизайн и высокую производительность. D - это С++, сделанный правильно.
1// Welcome to D! Это однострочный комментарий
2
3/* многострочный
4 комментарий */
5
6/+
7 // вложенные комментарии
8
9 /* еще вложенные
10 комментарии */
11
12 /+
13 // мало уровней вложенности? Их может быть сколько угодно.
14 +/
15+/
16
17/*
18 Имя модуля. Каждый файл с исходным кодом на D — модуль.
19 Если имя не указано явно, то предполагается, что оно совпадает с именем
20 файла. Например, для файла "test.d" имя модуля будет "test", если явно
21 не указать другое
22 */
23module app;
24
25// импорт модуля. Std — пространство имен стандартной библиотеки (Phobos)
26import std.stdio;
27
28// можно импортировать только нужные части, не обязательно модуль целиком
29import std.exception : enforce;
30
31// точка входа в программу — функция main, аналогично C/C++
32void main()
33{
34 writeln("Hello, world!");
35}
36
37
38
39/*** типы и переменные ***/
40
41int a; // объявление переменной типа int (32 бита)
42float b = 12.34; // тип с плавающей точкой
43double c = 56.78; // тип с плавающей точкой (64 бита)
44
45/*
46 Численные типы в D, за исключением типов с плавающей точкой и типов
47 комплексных чисел, могут быть беззнаковыми.
48 В этом случае название типа начинается с префикса "u"
49*/
50uint d = 10; ulong e = 11;
51bool b = true; // логический тип
52char d = 'd'; // UTF-символ, 8 бит. D поддерживает UTF "из коробки"
53wchar e = 'é'; // символ UTF-16
54dchar f; // и даже UTF-32, если он вам зачем-то понадобится
55
56string s = "для строк есть отдельный тип, это не просто массив char-ов из Си";
57wstring ws = "поскольку у нас есть wchar, должен быть и wstring";
58dstring ds = "...и dstring, конечно";
59
60string кириллица = "Имена переменных должны быть в Unicode, но не обязательно на латинице.";
61
62typeof(a) b = 6; // typeof возвращает тип своего выражения.
63 // В результате, b имеет такой же тип, как и a
64
65// Тип переменной, помеченной ключевым словом auto,
66// присваивается компилятором исходя из значения этой переменной
67auto x = 1; // Например, тип этой переменной будет int.
68auto y = 1.1; // этой — double
69auto z = "Zed is dead!"; // а этой — string
70
71int[3] arr = [1, 2, 3]; // простой одномерный массив с фиксированным размером
72int[] arr2 = [1, 2, 3, 4]; // динамический массив
73int[string] aa = ["key1": 5, "key2": 6]; // ассоциативный массив
74
75/*
76 Строки и массивы в D — встроенные типы. Для их использования не нужно
77 подключать ни внешние, ни даже стандартную библиотеку, хотя в последней
78 есть множество дополнительных инструментов для работы с ними.
79 */
80immutable int ia = 10; // неизменяемый тип,
81 // обозначается ключевым словом immutable
82ia += 1; // — вызовет ошибку на этапе компиляции
83
84// перечислимый (enumerable) тип,
85// более правильный способ работы с константами в D
86enum myConsts = { Const1, Const2, Const3 };
87
88// свойства типов
89writeln("Имя типа : ", int.stringof); // int
90writeln("Размер в байтах : ", int.sizeof); // 4
91writeln("Минимальное значение : ", int.min); // -2147483648
92writeln("Максимальное значение : ", int.max); // 2147483647
93writeln("Начальное значение : ", int.init); // 0. Это значение,
94 // присвоенное по умолчанию
95
96// На самом деле типов в D больше, но все мы здесь описывать не будем,
97// иначе не уложимся в Y минут.
98
99
100
101/*** Приведение типов ***/
102
103// to!(имя типа)(выражение) - для большинства конверсий
104import std.conv : to; // функция "to" - часть стандартной библиотеки, а не языка
105double d = -1.75;
106short s = to!short(d); // s = -1
107
108/*
109 cast - если вы знаете, что делаете. Кроме того, это единственный способ
110 преобразования типов-указателей в "обычные" и наоборот
111*/
112void* v;
113int* p = cast(int*)v;
114
115// Для собственного удобства можно создавать псевдонимы
116// для различных встроенных объектов
117alias int newInt; // теперь можно обращаться к newInt так, как будто бы это int
118newInt a = 5;
119
120alias newInt = int; // так тоже допустимо
121alias uint[2] pair; // дать псевдоним можно даже сложным структурам данных
122
123
124
125/*** Операторы ***/
126
127int x = 10; // присваивание
128x = x + 1; // 11
129x -= 2; // 9
130x++; // 10
131++x; // 11
132x *= 2; // 22
133x /= 2; // 11
134x = x ^^ 2; // 121 (возведение в степень)
135x ^^= 2; // 1331 (то же самое)
136
137string str1 = "Hello";
138string str2 = ", world!";
139string hw = str1 ~ str2; // Конкатенация строк
140
141int[] arr = [1, 2, 3];
142arr ~= 4; // [1, 2, 3, 4] - добавление элемента в конец массива
143
144
145
146/*** Логика и сравнения ***/
147
148int x = 0; int y = 1;
149
150x == y; // false
151x > y; // false
152x < y; // true
153x >= y; // false
154x != y; // true. ! — логическое "не"
155x > 0 || x < 1; // true. || — логическое "или"
156x > 0 && x < 1; // false && — логическое "и"
157x ^ y // true; ^ - xor (исключающее "или")
158
159// Тернарный оператор
160auto y = (x > 10) ? 1 : 0; // если x больше 10, то y равен 1,
161 // в противном случае y равен нулю
162
163
164/*** Управляющие конструкции ***/
165
166// if - абсолютно привычен
167if (a == 1) {
168 // ..
169} else if (a == 2) {
170 // ..
171} else {
172 // ..
173}
174
175// switch
176switch (a) {
177 case 1:
178 // делаем что-нибудь
179 break;
180 case 2:
181 // делаем что-нибудь другое
182 break;
183 case 3:
184 // делаем что-нибудь еще
185 break;
186 default:
187 // default обязателен, без него будет ошибка компиляции
188 break;
189}
190
191// в D есть констукция "final switch". Она не может содержать секцию "defaul"
192// и применяется, когда все перечисляемые в switch варианты должны быть
193// обработаны явным образом
194
195int dieValue = 1;
196final switch (dieValue) {
197 case 1:
198 writeln("You won");
199 break;
200
201 case 2, 3, 4, 5:
202 writeln("It's a draw");
203 break;
204
205 case 6:
206 writeln("I won");
207 break;
208}
209
210// while
211while (a > 10) {
212 // ..
213 if (number == 42) {
214 break;
215 }
216}
217
218while (true) {
219 // бесконечный цикл
220}
221
222// do-while
223do {
224 // ..
225} while (a == 10);
226
227// for
228for (int number = 1; number < 11; ++number) {
229 writeln(number); // все абсолютно стандартно
230}
231
232for ( ; ; ) {
233 // секции могут быть пустыми. Это бесконечный цикл в стиле Си
234}
235
236// foreach - универсальный и самый "правильный" цикл в D
237foreach (element; array) {
238 writeln(element); // для простых массивов
239}
240
241foreach (key, val; aa) {
242 writeln(key, ": ", val); // для ассоциативных массивов
243}
244
245foreach (c; "hello") {
246 writeln(c); // hello. Поскольку строки - это вариант массива,
247 // foreach применим и к ним
248}
249
250foreach (number; 10..15) {
251 writeln(number); // численные интервалы можно указывать явным образом
252 // этот цикл выведет значения с 10 по 14, но не 15,
253 // поскольку диапазон не включает в себя верхнюю границу
254}
255
256// foreach_reverse - в обратную сторону
257auto container = [1, 2, 3];
258foreach_reverse (element; container) {
259 writefln("%s ", element); // 3, 2, 1
260}
261
262// foreach в массивах и им подобных структурах не меняет сами структуры
263int[] a = [1, 2 ,3 ,4 ,5];
264foreach (elem; array) {
265 elem *= 2; // сам массив останется неизменным
266}
267
268writeln(a); // вывод: [1, 2, 3, 4, 5] Т.е изменений нет
269
270// добавление ref приведет к тому, что массив будет изменяться
271foreach (ref elem; array) {
272 elem *= 2;
273}
274
275writeln(a); // [2, 4, 6, 8, 10]
276
277// foreach умеет рассчитывать индексы элементов
278int[] a = [1, 2, 3, 4, 5];
279foreach (ind, elem; array) {
280 writeln(ind, " ", elem); // через ind - доступен индекс элемента,
281 // а через elem - сам элемент
282}
283
284
285
286/*** Функции ***/
287
288test(42); // Что, вот так сразу? Разве мы где-то уже объявили эту функцию?
289
290// Нет, вот она. Это не Си, здесь объявление функции не обязательно должно быть
291// до первого вызова
292int test(int argument) {
293 return argument * 2;
294}
295
296
297// В D используется единый синтаксис вызова функций
298// (UFCS, Uniform Function Call Syntax), поэтому так тоже можно:
299int var = 42.test();
300
301// и даже так, если у функции нет аргументов:
302int var2 = 42.test;
303
304// можно выстраивать цепочки:
305int var3 = 42.test.test;
306
307/*
308 Аргументы в функцию передаются по значению (т.е. функция работает не с
309 оригинальными значениями, переданными ей, а с их локальными копиями.
310 Исключение составляют объекты классов, которые передаются по ссылке.
311 Кроме того, любой параметр можно передать в функцию по ссылке с помощью
312 ключевого слова "ref"
313*/
314int var = 10;
315
316void fn1(int arg) {
317 arg += 1;
318}
319
320void fn2(ref int arg) {
321 arg += 1;
322}
323
324fn1(var); // var все еще = 10
325fn2(var); // теперь var = 11
326
327// Возвращаемое значение тоже может быть auto,
328// если его можно "угадать" из контекста
329auto add(int x, int y) {
330 return x + y;
331}
332
333auto z = add(x, y); // тип int - компилятор вывел его автоматически
334
335// Значения аргументов по умолчанию
336float linearFunction(float k, float x, float b = 1)
337{
338 return k * x + b;
339}
340
341auto linear1 = linearFunction(0.5, 2, 3); // все аргументы используются
342auto linear2 = linearFunction(0.5, 2); // один аргумент пропущен, но в функции
343 // он все равно использован и равен 1
344
345// допускается описание вложенных функций
346float quarter(float x) {
347 float doubled(float y) {
348 return y * y;
349 }
350
351 return doubled(doubled(x));
352}
353
354// функции с переменным числом аргументов
355int sum(int[] a...)
356{
357 int s = 0;
358 foreach (elem; a) {
359 s += elem;
360 }
361 return s;
362}
363
364auto sum1 = sum(1);
365auto sum2 = sum(1,2,3,4);
366
367/*
368 модификатор "in" перед аргументами функций говорит о том, что функция имеет
369 право их только просматривать. При попытке модификации такого аргумента
370 внутри функции - получите ошибку
371*/
372float printFloat(in float a)
373{
374 writeln(a);
375}
376printFloat(a); // использование таких функций - самое обычное
377
378// модификатор "out" позволяет вернуть из функции несколько результатов
379// без посредства глобальных переменных или массивов
380uint remMod(uint a, uint b, out uint modulus)
381{
382 uint remainder = a / b;
383 modulus = a % b;
384 return remainder;
385}
386
387uint modulus; // пока в этой переменной ноль
388uint rem = remMod(5, 2, modulus); // наша "хитрая" функция, и теперь
389 // в modulus - остаток от деления
390writeln(rem, " ", modulus); // вывод: 2 1
391
392
393
394/*** Структуры, классы, базовое ООП ***/
395
396// Объявление структуры. Структуры почти как в Си
397struct MyStruct {
398 int a;
399 float b;
400
401 void multiply() {
402 return a * b;
403 }
404}
405
406MyStruct str1; // Объявление переменной с типом MyStruct
407str1.a = 10; // Обращение к полю
408str1.b = 20;
409auto result = str1.multiply();
410MyStruct str2 = {4, 8} // Объявление + инициализация в стиле Си
411auto str3 = MyStruct(5, 10); // Объявление + инициализация в стиле D
412
413
414// области видимости полей и методов - 3 способа задания
415struct MyStruct2 {
416 public int a;
417
418 private:
419 float b;
420 bool c;
421
422 protected {
423 float multiply() {
424 return a * b;
425 }
426 }
427 /*
428 в дополнение к знакомым public, private и protected, в D есть еще
429 область видимости "package". Поля и методы с этим атрибутом будут
430 доступны изо всех модулей, включенных в "пакет" (package), но не
431 за его пределами. package - это "папка", в которой может храниться
432 несколько модулей. Например, в "import.std.stdio", "std" - это
433 package, в котором есть модуль stdio (и еще множество других)
434 */
435 package:
436 string d;
437
438 /* помимо этого, имеется еще один модификатор - export, который позволяет
439 использовать объявленный с ним идентификатор даже вне самой программы !
440 */
441 export:
442 string description;
443}
444
445// Конструкторы и деструкторы
446struct MyStruct3 {
447 this() { // конструктор. Для структур его не обязательно указывать явно,
448 // в этом случае пустой конструктор добавляется компилятором
449 writeln("Hello, world!");
450 }
451
452
453 // а вот это конструкция - одна из интересных идиом и представляет собой
454 // конструктор копирования, т.е конструктор, возвращающий копию структуры.
455 // Работает только в структурах.
456 this(this)
457 {
458 return this;
459 }
460
461 ~this() { // деструктор, также необязателен
462 writeln("Awww!");
463 }
464}
465
466// Объявление простейшего класса
467class MyClass {
468 int a; // в D по умолчанию данные-члены являются public
469 float b;
470}
471
472auto mc = new MyClass(); // ...и создание его экземпляра
473auto mc2 = new MyClass; // ... тоже сработает
474
475// Конструктор
476class MyClass2 {
477 int a;
478 float b;
479
480 this(int a, float b) {
481 this.a = a; // ключевое слово "this" - ссылка на объект класса
482 this.b = b;
483 }
484}
485
486auto mc2 = new MyClass2(1, 2.3);
487
488// Классы могут быть вложенными
489class Outer
490{
491 int m;
492
493 class Inner
494 {
495 int foo()
496 {
497 return m; // можно обращаться к полям "внешнего" класса
498 }
499 }
500}
501
502// наследование
503class Base {
504 int a = 1;
505 float b = 2.34;
506
507
508 // это статический метод, т.е метод который можно вызывать, обращаясь
509 // к классу напрямую, а не через создание экземпляра объекта
510 static void multiply(int x, int y)
511 {
512 writeln(x * y);
513 }
514}
515
516Base.multiply(2, 5); // используем статический метод. Результат: 10
517
518class Derived : Base {
519 string c = "Поле класса - наследника";
520
521
522 // override означает то, что наследник предоставит свою реализацию метода,
523 // переопределив метод базового класса
524 override static void multiply(int x, int y)
525 {
526 super.multiply(x, y); // super - это ссылка на класс-предок, или базовый класс
527 writeln(x * y * 2);
528 }
529}
530
531auto mc3 = new Derived();
532writeln(mc3.a); // 1
533writeln(mc3.b); // 2.34
534writeln(mc3.c); // Поле класса - наследника
535
536// Финальный класс, наследовать от него нельзя
537// кроме того, модификатор final работает не только для классов, но и для методов
538// и даже для модулей !
539final class FC {
540 int a;
541}
542
543class Derived : FC { // это вызовет ошибку
544 float b;
545}
546
547// Абстрактный класс не может быть истанциирован, но может иметь наследников
548abstract class AC {
549 int a;
550}
551
552auto ac = new AC(); // это вызовет ошибку
553
554class Implementation : AC {
555 float b;
556
557 // final перед методом нефинального класса означает запрет возможности
558 // переопределения метода
559 final void test()
560 {
561 writeln("test passed !");
562 }
563}
564
565auto impl = new Implementation(); // ОК
566
567
568
569/*** Примеси (mixins) ***/
570
571// В D можно вставлять код как строку, если эта строка известна на этапе
572// компиляции. Например:
573void main() {
574 mixin(`writeln("Hello World!");`);
575}
576
577// еще пример
578string print(string s) {
579 return `writeln("` ~ s ~ `");`;
580}
581
582void main() {
583 mixin (print("str1"));
584 mixin (print("str2"));
585}
586
587
588
589/*** Шаблоны ***/
590
591/*
592 Шаблон функции. Эта функция принимает аргументы разных типов, которые
593 подставляются вместо T на этапе компиляции. "T" - это не специальный
594 символ, а просто буква. Вместо "T" может быть любое слово, кроме ключевого.
595 */
596void print(T)(T value) {
597 writefln("%s", value);
598}
599
600void main() {
601 print(42); // В одну и ту же функцию передается: целое
602 print(1.2); // ...число с плавающей точкой,
603 print("test"); // ...строка
604}
605
606// "Шаблонных" параметров может быть сколько угодно
607void print(T1, T2)(T1 value1, T2 value2) {
608 writefln(" %s %s", value1, value2);
609}
610
611void main() {
612 print(42, "Test");
613 print(1.2, 33);
614}
615
616// Шаблон класса
617class Stack(T)
618{
619 private:
620 T[] elements;
621
622 public:
623 void push(T element) {
624 elements ~= element;
625 }
626
627 void pop() {
628 --elements.length;
629 }
630
631 T top() const @property {
632 return elements[$ - 1];
633 }
634
635 size_t length() const @property {
636 return elements.length;
637 }
638}
639
640void main() {
641 /*
642 восклицательный знак - признак шаблона. В данном случае мы создаем
643 класс и указываем, что "шаблонное" поле будет иметь тип string
644 */
645 auto stack = new Stack!string;
646
647 stack.push("Test1");
648 stack.push("Test2");
649
650 writeln(stack.top);
651 writeln(stack.length);
652
653 stack.pop;
654 writeln(stack.top);
655 writeln(stack.length);
656}
657
658
659
660/*** Диапазоны (ranges) ***/
661
662/*
663 Диапазоны - это абстракция, которая позволяет легко использовать разные
664 алгоритмы с разными структурами данных. Вместо того, чтобы определять свои
665 уникальные алгоритмы для каждой структуры, мы можем просто указать для нее
666 несколько единообразных функций, определяющих, _как_ мы получаем доступ
667 к элементам контейнера, вместо того, чтобы описывать внутреннее устройство
668 этого контейнера. Сложно? На самом деле не очень.
669
670 Простейший вид диапазона - Input Range. Для того, чтобы превратить любой
671 контейнер в Input Range, достаточно реализовать для него 3 метода:
672 - empty - проверяет, пуст ли контейнер
673 - front - дает доступ к первому элементу контейнера
674 - popFront - удаляет из контейнера первый элемент
675*/
676struct Student
677{
678 string name;
679 int number;
680 string toString() {
681 return format("%s(%s)", name, number);
682 }
683}
684
685struct School
686{
687 Student[] students;
688}
689
690struct StudentRange
691{
692 Student[] students;
693
694 this(School school) {
695 this.students = school.students;
696 }
697
698 bool empty() {
699 return students.length == 0;
700 }
701
702 Student front() {
703 return students[0];
704 }
705
706 void popFront() {
707 students = students[1 .. $];
708 }
709}
710
711void main(){
712 auto school = School([
713 Student("Mike", 1),
714 Student("John", 2) ,
715 Student("Dan", 3)
716 ]);
717 auto range = StudentRange(school);
718 writeln(range); // [Mike(1), John(2), Dan(3)]
719 writeln(school.students.length); // 3
720 writeln(range.front()); // Mike(1)
721 range.popFront();
722 writeln(range.empty()); // false
723 writeln(range); // [John(2), Dan(3)]
724}
725/*
726 Смысл в том, что нам не так уж важно внутреннее устройство контейнера, если
727 у нас есть унифицированные методы доступа к его элементам.
728 Кроме Input Range в D есть и другие типы диапазонов, которые требуют
729 реализации большего числа методов, зато дают больше контроля. Это большая
730 тема и мы не будем в подробностях освещать ее здесь.
731
732 Диапазоны - это важная часть D, они используются в нем повсеместно.
733*/