mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-23 17:41:41 +00:00
25 KiB
25 KiB
language | filename | contributors | lang | ||||||
---|---|---|---|---|---|---|---|---|---|
d | learnd-ru.d |
|
ru-ru |
D - современный компилируемый язык общего назначения с Си-подобным синтаксисом, который сочетает удобство, продуманный дизайн и высокую производительность. D - это С++, сделанный правильно.
// Welcome to D! Это однострочный комментарий
/* многострочный
комментарий */
/+
// вложенные кометарии
/* еще вложенные
комментарии */
/+
// мало уровней вложенности? Их может быть сколько угодно.
+/
+/
/*
Имя модуля. Каждый файл с исходным кодом на D — модуль.
Если имя не указано явно, то предполагается, что оно совпадает с именем
файла. Например, для файла "test.d" имя модуля будет "test", если явно
не указать другое
*/
module app;
// импорт модуля. Std — пространство имен стандартной библиотеки (Phobos)
import std.stdio;
// можно импортировать только нужные части, не обязательно модуль целиком
import std.exception : assert;
// точка входа в программу — функция main, аналогично C/C++
void main()
{
writeln("Hello, world!");
}
/*** типы и переменные ***/
int a; // объявление переменной типа int (32 бита)
float b = 12.34; // тип с плавающей точкой
double с = 56.78; // тип с плавающей точкой (64 бита)
/*
Численные типы в D, за исключением типов с плавающей точкой и типов
комплексных чисел, могут быть беззнаковыми.
В этом случае название типа начинается с префикса "u"
*/
uint d = 10, ulong e = 11.12;
bool b = true; // логический тип
char d = 'd'; // UTF-символ, 8 бит. D поддерживает UTF "из коробки"
wchar = 'é'; // символ UTF-16
dchar f; // и даже UTF-32, если он вам зачем-то понадобится
string s = "для строк есть отдельный тип, это не просто массив char-ов из Си";
wstring ws = "поскольку у нас есть wchar, должен быть и wstring";
dstring ds = "...и dstring, конечно";
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]; // ассоциативный массив
/*
Cтроки и массивы в D — встроенные типы. Для их использования не нужно
подключать ни внешние, ни даже стандартную библиотеку, хотя в последней
есть множество дополнительных инструментов для работы с ними.
*/
immutalbe 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("Максимальное значениеe : ", int.max); // 2147483647
writeln("Начальное значение : ", int.init); // 0. Это значение,
// присвоенное по умолчанию
// На самом деле типов в D больше, но все мы здесь описывать не будем,
// иначе не уложимся в Y минут.
/*** Приведение типов ***/
// Простейший вариант
int i;
double j = double(i) / 2;
// 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; // теперь можно обращаться к int так, как будто бы это newInt
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 ^^ 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;
}
// 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); // численные интервалы можно указывать явным образом
}
// 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;
}
ref 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, они используются в нем повсеместно.
*/