d.md

Личный сайт Go-разработчика из Казани

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*/

Что дальше?