D Программирование - Краткое руководство

Язык программирования D - это объектно-ориентированный язык программирования с несколькими парадигмами, разработанный Уолтером Брайтом из Digital Mars. Его разработка началась в 1999 году и впервые была выпущена в 2001 году. Основная версия D (1.0) была выпущена в 2007 году. В настоящее время у нас есть версия D2 D.

D - это язык с синтаксисом в стиле C и статической типизацией. В D есть много функций C и C ++, но также есть некоторые функции из этого языка, не входящие в состав D. Некоторые из заметных дополнений к D включают:

  • Модульное тестирование
  • Истинные модули
  • Вывоз мусора
  • Массивы первого класса
  • Бесплатно и открыто
  • Ассоциативные массивы
  • Динамические массивы
  • Внутренние классы
  • Closures
  • Анонимные функции
  • Ленивая оценка
  • Closures

Множественные парадигмы

D - это язык программирования с множеством парадигм. Множественные парадигмы включают:

  • Imperative
  • Объектно-ориентированный
  • Мета-программирование
  • Functional
  • Concurrent

пример

import std.stdio; 
 
void main(string[] args) { 
   writeln("Hello World!"); 
}

Изучение D

Самое важное, что нужно делать при изучении D, - это сосредоточиться на концепциях и не теряться в технических деталях языка.

Цель изучения языка программирования - стать лучшим программистом; то есть стать более эффективными при разработке и внедрении новых систем и обслуживании старых.

Сфера действия D

Программирование на D имеет некоторые интересные особенности, и официальный сайт программирования на языке D утверждает, что язык D удобен, мощный и эффективный. Программирование на D добавляет множество функций в основной язык, которые язык C предоставил в форме стандартных библиотек, таких как массив с изменяемым размером и строковая функция. D - отличный второй язык для программистов среднего и продвинутого уровней. D лучше обрабатывает память и управляет указателями, что часто вызывает проблемы в C ++.

D программирование предназначено в основном для новых программ, которые преобразовывают существующие программы. Он обеспечивает встроенное тестирование и проверку, что идеально подходит для нового большого проекта, который будет написан большими командами с использованием миллионов строк кода.

Настройка локальной среды для D

Если вы все еще хотите настроить свою среду для языка программирования D, вам понадобятся следующие два программного обеспечения, доступные на вашем компьютере: (а) текстовый редактор, (б) D-компилятор.

Текстовый редактор для программирования на языке D

Это будет использоваться для ввода вашей программы. Примеры нескольких редакторов включают Блокнот Windows, команду редактирования ОС, Brief, Epsilon, EMACS и vim или vi.

Название и версия текстового редактора могут различаться в разных операционных системах. Например, Блокнот будет использоваться в Windows, а vim или vi можно использовать в Windows, а также в Linux или UNIX.

Файлы, которые вы создаете с помощью своего редактора, называются исходными файлами и содержат исходный код программы. Исходные файлы для программ D имеют расширение ".d".

Перед началом программирования убедитесь, что у вас есть один текстовый редактор и у вас достаточно опыта, чтобы написать компьютерную программу, сохранить ее в файл, собрать и, наконец, выполнить.

Компилятор D

Большинство текущих реализаций D компилируются непосредственно в машинный код для эффективного выполнения.

У нас есть несколько компиляторов D, в том числе следующие.

  • DMD - Компилятор Digital Mars D является официальным компилятором языка D, созданным Уолтером Брайтом.

  • GDC - Интерфейс для серверной части GCC, созданный с использованием открытого исходного кода компилятора DMD.

  • LDC - Компилятор, основанный на интерфейсе DMD, который использует LLVM в качестве внутреннего интерфейса компилятора.

Вышеупомянутые различные компиляторы можно загрузить из D загрузок.

Мы будем использовать D версии 2, и мы рекомендуем не загружать D1.

Давайте создадим следующую программу helloWorld.d. Мы будем использовать это как первую программу, которую мы запускаем на выбранной вами платформе.

import std.stdio; 
 
void main(string[] args) { 
   writeln("Hello World!"); 
}

Мы видим следующий результат.

$ hello world

Установка D в Windows

Загрузите установщик Windows .

Запустите загруженный исполняемый файл, чтобы установить D, что можно сделать, следуя инструкциям на экране.

Теперь мы можем создать и запустить рекламный файл, скажем helloWorld.d, переключившись в папку, содержащую файл, с помощью компакт-диска, а затем выполнив следующие шаги:

C:\DProgramming> DMD helloWorld.d 
C:\DProgramming> helloWorld

Мы видим следующий результат.

hello world

C: \ DProgramming - это папка, которую я использую для сохранения своих образцов. Вы можете изменить его в папку, в которой вы сохранили D-программы.

Установка D в Ubuntu / Debian

Загрузите установщик debian .

Запустите загруженный исполняемый файл, чтобы установить D, что можно сделать, следуя инструкциям на экране.

Теперь мы можем создать и запустить рекламный файл, скажем helloWorld.d, переключившись в папку, содержащую файл, с помощью компакт-диска, а затем выполнив следующие шаги:

$ dmd helloWorld.d 
$ ./helloWorld

Мы видим следующий результат.

$ hello world

Установка D в Mac OS X

Загрузите установщик для Mac .

Запустите загруженный исполняемый файл, чтобы установить D, что можно сделать, следуя инструкциям на экране.

Теперь мы можем создать и запустить рекламный файл, скажем helloWorld.d, переключившись в папку, содержащую файл, с помощью компакт-диска, а затем выполнив следующие шаги:

$ dmd helloWorld.d $ ./helloWorld

Мы видим следующий результат.

$ hello world

Установка D на Fedora

Загрузите установщик Fedora .

Запустите загруженный исполняемый файл, чтобы установить D, что можно сделать, следуя инструкциям на экране.

Теперь мы можем создать и запустить рекламный файл, скажем helloWorld.d, переключившись в папку, содержащую файл, с помощью компакт-диска, а затем выполнив следующие шаги:

$ dmd helloWorld.d 
$ ./helloWorld

Мы видим следующий результат.

$ hello world

Установка D на OpenSUSE

Загрузите установщик OpenSUSE .

Запустите загруженный исполняемый файл, чтобы установить D, что можно сделать, следуя инструкциям на экране.

Теперь мы можем создать и запустить рекламный файл, скажем helloWorld.d, переключившись в папку, содержащую файл, с помощью компакт-диска, а затем выполнив следующие шаги:

$ dmd helloWorld.d $ ./helloWorld

Мы видим следующий результат.

$ hello world

D IDE

В большинстве случаев у нас есть поддержка IDE для D в виде плагинов. Это включает в себя,

  • Плагин Visual D - это плагин для Visual Studio 2005-13.

  • DDT - это плагин eclipse, который обеспечивает автозавершение кода и отладку с помощью GDB.

  • Автодополнение кода Mono-D , рефакторинг с поддержкой dmd / ldc / gdc. Он был частью GSoC 2012.

  • Code Blocks - это многоплатформенная IDE, которая поддерживает создание, выделение и отладку D-проектов.

D довольно просто выучить, и давайте приступим к созданию нашей первой D-программы!

Первая программа D

Напишем простую программу на D. Все файлы D будут иметь расширение .d. Поэтому поместите следующий исходный код в файл test.d.

import std.stdio;  

/* My first program in D */ 
void main(string[] args) { 
   writeln("test!"); 
}

Предполагая, что среда D настроена правильно, давайте запустим программирование, используя -

$ dmd test.d 
$ ./test

Мы видим следующий результат.

test

Давайте теперь посмотрим на базовую структуру программы D, чтобы вам было легко понять основные строительные блоки языка программирования D.

Импорт в D

Библиотеки, которые представляют собой коллекции многократно используемых частей программы, могут быть доступны нашему проекту с помощью импорта. Здесь мы импортируем стандартную библиотеку io, которая обеспечивает основные операции ввода-вывода. Writeln, который используется в приведенной выше программе, является функцией стандартной библиотеки D. Он используется для печати строки текста. Содержимое библиотеки в D сгруппировано в модули в зависимости от типов задач, которые они собираются выполнять. Единственный модуль, который использует эта программа, - std.stdio, который обрабатывает ввод и вывод данных.

Основная функция

Основная функция - это запуск программы, она определяет порядок выполнения и то, как должны выполняться другие разделы программы.

Жетоны в D

Программа AD состоит из различных токенов, и токен представляет собой ключевое слово, идентификатор, константу, строковый литерал или символ. Например, следующий оператор D состоит из четырех токенов -

writeln("test!");

Отдельные токены -

writeln (
   "test!"
)
;

Комментарии

Комментарии подобны вспомогательному тексту в вашей программе на языке D и игнорируются компилятором. Многострочный комментарий начинается с / * и заканчивается символами * /, как показано ниже -

/* My first program in D */

Одиночный комментарий пишется с помощью // в начале комментария.

// my first program in D

Идентификаторы

Идентификатор AD - это имя, используемое для идентификации переменной, функции или любого другого определяемого пользователем элемента. Идентификатор начинается с буквы от A до Z, от a до z или символа подчеркивания _, за которым следует ноль или более букв, подчеркиваний и цифр (от 0 до 9).

D не допускает символов пунктуации, таких как @, $ и%, в идентификаторах. D - этоcase sensitiveязык программирования. Таким образом, рабочая сила и рабочая сила - это два разных идентификатора в D. Вот несколько примеров приемлемых идентификаторов:

mohd       zara    abc   move_name  a_123 
myname50   _temp   j     a23b9      retVal

Ключевые слова

В следующем списке показано несколько зарезервированных слов в D. Эти зарезервированные слова не могут использоваться в качестве имен констант, переменных или любых других идентификаторов.

Аннотация псевдоним выровнять как м
утверждать авто тело bool
байт дело бросать поймать
char класс const Продолжить
dchar отлаживать по умолчанию делегировать
устарел делать двойной еще
перечислить экспорт внешний ложный
окончательный Ну наконец то плавать за
для каждого функция идти к если
импорт в inout int
интерфейс инвариантный является долго
макрос миксин модуль новый
ноль из отменять пакет
прагма частный защищенный общественный
настоящий ссылка возвращение объем
короткая статический структура супер
переключатель синхронизированный шаблон это
бросить правда пытаться типичный
тип убайт uint Улонг
союз модульный тест ushort версия
пустота чар в то время как с участием

Пробел в D

Строка, содержащая только пробелы, возможно, с комментарием, называется пустой строкой, и компилятор D ее полностью игнорирует.

Пробел - это термин, используемый в D для описания пробелов, табуляции, символов новой строки и комментариев. Пробел отделяет одну часть оператора от другой и позволяет интерпретатору определить, где заканчивается один элемент в операторе, например int, и начинается следующий элемент. Следовательно, в следующем заявлении -

local age

Между local и age должен быть хотя бы один пробел (обычно пробел), чтобы интерпретатор мог их различить. С другой стороны, в следующем утверждении

int fruit = apples + oranges   //get the total fruits

Пробелы между фруктами и = или между = и яблоками не требуются, хотя вы можете включить некоторые из них, если хотите для удобства чтения.

Переменная - это не что иное, как имя, присвоенное области памяти, которой могут управлять наши программы. Каждая переменная в D имеет определенный тип, который определяет размер и структуру памяти переменной; диапазон значений, которые могут быть сохранены в этой памяти; и набор операций, которые можно применить к переменной.

Имя переменной может состоять из букв, цифр и символа подчеркивания. Он должен начинаться с буквы или символа подчеркивания. Буквы верхнего и нижнего регистра различны, потому что D чувствителен к регистру. На основе основных типов, описанных в предыдущей главе, будут следующие основные типы переменных:

Sr.No. Тип и описание
1

char

Обычно один октет (один байт). Это целочисленный тип.

2

int

Самый естественный размер целого числа для машины.

3

float

Значение с плавающей запятой одинарной точности.

4

double

Значение с плавающей запятой двойной точности.

5

void

Представляет отсутствие типа.

Язык программирования D также позволяет определять различные другие типы переменных, такие как Enumeration, Pointer, Array, Structure, Union и т. Д., Которые мы рассмотрим в следующих главах. В этой главе давайте изучим только основные типы переменных.

Определение переменной в D

Определение переменной сообщает компилятору, где и сколько места нужно создать для переменной. Определение переменной указывает тип данных и содержит список из одной или нескольких переменных этого типа следующим образом:

type variable_list;

Вот, type должен быть допустимым типом данных D, включая char, wchar, int, float, double, bool или любой определяемый пользователем объект и т. д., и variable_listможет состоять из одного или нескольких имен идентификаторов, разделенных запятыми. Здесь показаны некоторые действительные декларации -

int    i, j, k; 
char   c, ch; 
float  f, salary; 
double d;

Линия int i, j, k;оба объявляют и определяют переменные i, j и k; который инструктирует компилятор создать переменные с именами i, j и k типа int.

Переменные можно инициализировать (присвоить начальное значение) в их объявлении. Инициализатор состоит из знака равенства, за которым следует постоянное выражение:

type variable_name = value;

Примеры

extern int d = 3, f = 5;    // declaration of d and f.  
int d = 3, f = 5;           // definition and initializing d and f.  
byte z = 22;                // definition and initializes z.  
char x = 'x';               // the variable x has the value 'x'.

Когда переменная объявляется в D, для нее всегда устанавливается «инициализатор по умолчанию», к которому можно получить доступ вручную как T.init где T это тип (напр. int.init). Инициализатором по умолчанию для целочисленных типов является 0, для логических значений false и для чисел с плавающей запятой NaN.

Объявление переменной в D

Объявление переменной обеспечивает компилятору гарантию того, что существует одна переменная с данным типом и именем, так что компилятор приступит к дальнейшей компиляции, не требуя полной информации о переменной. Объявление переменной имеет значение только во время компиляции, компилятору требуется фактическое объявление переменной во время компоновки программы.

пример

Попробуйте следующий пример, где переменные были объявлены в начале программы, но определены и инициализированы внутри основной функции:

import std.stdio; 
 
int a = 10, b = 10; 
int c;
float f;  

int main () { 
   writeln("Value of a is : ", a); 
   
   /* variable re definition: */ 
   int a, b; 
   int c; 
   float f;
   
   /* Initialization */ 
   a = 30; 
   b = 40; 
   writeln("Value of a is : ", a); 
   
   c = a + b; 
   writeln("Value of c is : ", c);  
   
   f = 70.0/3.0; 
   writeln("Value of f is : ", f); 
   return 0; 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Value of a is : 10 
Value of a is : 30 
Value of c is : 70 
Value of f is : 23.3333

Значения L и R в D

В D есть два вида выражений:

  • lvalue - Выражение, которое является lvalue, может отображаться как левая или правая часть присваивания.

  • rvalue - Выражение, являющееся rvalue, может появляться справа, но не слева от присваивания.

Переменные - это lvalue, поэтому они могут появляться в левой части присваивания. Числовые литералы являются r-значениями, поэтому не могут быть присвоены и не могут отображаться в левой части. Следующее утверждение действительно -

int g = 20;

Но следующее утверждение не является допустимым и вызовет ошибку времени компиляции:

10 = 20;

В языке программирования D типы данных относятся к обширной системе, используемой для объявления переменных или функций разных типов. Тип переменной определяет, сколько места она занимает в хранилище и как интерпретируется сохраненный битовый шаблон.

Типы в D можно классифицировать следующим образом:

Sr.No. Типы и описание
1

Basic Types

Это арифметические типы и состоят из трех типов: (а) целые, (б) с плавающей точкой и (в) символьные.

2

Enumerated types

Это снова арифметические типы. Они используются для определения переменных, которым в программе могут быть присвоены только определенные дискретные целочисленные значения.

3

The type void

Спецификатор типа void указывает, что значение недоступно.

4

Derived types

Они включают (а) типы указателей, (б) типы массивов, (в) типы структур, (г) типы объединения и (д) типы функций.

Типы массивов и структурные типы вместе называются агрегатными типами. Тип функции определяет тип возвращаемого значения функции. Мы увидим основные типы в следующем разделе, тогда как другие типы будут рассмотрены в следующих главах.

Целочисленные типы

В следующей таблице приведены списки стандартных целочисленных типов с их размерами хранения и диапазонами значений.

Тип Размер хранилища Диапазон значений
bool 1 байт ложь или правда
байт 1 байт От -128 до 127
убайт 1 байт От 0 до 255
int 4 байта От -2 147 483 648 до 2 147 483 647
uint 4 байта От 0 до 4 294 967 295
короткая 2 байта От -32 768 до 32 767
ushort 2 байта От 0 до 65 535
долго 8 байт От -9223372036854775808 до 9223372036854775807
Улонг 8 байт 0 по 18446744073709551615

Чтобы получить точный размер типа или переменной, вы можете использовать sizeofоператор. Тип выражения . (Sizeof) дает размер хранилища объекта или типа в байтах. В следующем примере получается размер типа int на любой машине -

import std.stdio; 
 
int main() { 
   writeln("Length in bytes: ", ulong.sizeof); 

   return 0; 
}

Когда вы компилируете и выполняете вышеуказанную программу, она дает следующий результат:

Length in bytes: 8

Типы с плавающей точкой

В следующей таблице упоминаются стандартные типы с плавающей запятой с размерами хранилища, диапазонами значений и их назначением.

Тип Размер хранилища Диапазон значений Цель
плавать 4 байта С 1.17549e-38 до 3.40282e + 38 6 знаков после запятой
двойной 8 байт 2.22507e-308 до 1.79769e + 308 15 знаков после запятой
настоящий 10 байт 3.3621e-4932 по 1.18973e + 4932 либо самый большой тип с плавающей запятой, поддерживаемый оборудованием, либо двойной; в зависимости от того, что больше
плавать 4 байта От 1.17549e-38i до 3.40282e + 38i тип мнимого значения float
idouble 8 байт 2.22507e-308i - 1.79769e + 308i мнимое значение типа double
я реальный 10 байт 3.3621e-4932 по 1.18973e + 4932 мнимое значение тип реального
cfloat 8 байт 1.17549e-38 + 1.17549e-38i до 3.40282e + 38 + 3.40282e + 38i тип комплексного числа, состоящий из двух поплавков
cdouble 16 байт 2.22507e-308 + 2.22507e-308i до 1.79769e + 308 + 1.79769e + 308i тип комплексного числа, состоящий из двух двойных
Creal 20 байт От 3.3621e-4932 + 3.3621e-4932i до 1.18973e + 4932 + 1.18973e + 4932i комплексное число, состоящее из двух действительных чисел

В следующем примере печатается пространство для хранения, занятое типом с плавающей запятой, и его значениями диапазона:

import std.stdio;

int main() { 
   writeln("Length in bytes: ", float.sizeof); 

   return 0; 
}

Когда вы компилируете и выполняете вышеуказанную программу, она дает следующий результат в Linux:

Length in bytes: 4

Типы персонажей

В следующей таблице перечислены стандартные типы символов с указанием размеров хранилища и его назначения.

Тип Размер хранилища Цель
char 1 байт Кодовый блок UTF-8
чар 2 байта Кодовый блок UTF-16
dchar 4 байта Кодовый блок UTF-32 и кодовая точка Unicode

В следующем примере печатается объем памяти, занимаемый типом char.

import std.stdio;

int main() {
   writeln("Length in bytes: ", char.sizeof);
   
   return 0;
}

Когда вы компилируете и выполняете вышеуказанную программу, она дает следующий результат:

Length in bytes: 1

Тип пустоты

Тип void указывает, что значение недоступно. Он используется в двух ситуациях:

Sr.No. Типы и описание
1

Function returns as void

В D есть различные функции, которые не возвращают значение, или вы можете сказать, что они возвращают void. Функция без возвращаемого значения имеет тип возврата как void. Например,void exit (int status);

2

Function arguments as void

В D есть различные функции, которые не принимают никаких параметров. Функция без параметра может быть пуста. Например,int rand(void);

Тип void может быть вам непонятен на данном этапе, поэтому давайте продолжим, и мы рассмотрим эти концепции в следующих главах.

Перечисление используется для определения именованных значений констант. Перечислимый тип объявляется с помощьюenum ключевое слово.

Перечисление Синтаксис

Самая простая форма определения перечисления следующая:

enum enum_name {  
   enumeration list 
}

Где,

  • Enum_name задает имя типа перечисления.

  • Список перечисления представляет собой список идентификаторов, разделенных запятыми.

Каждый из символов в списке перечисления обозначает целочисленное значение, на единицу большее, чем предшествующий ему символ. По умолчанию значение первого символа перечисления равно 0. Например -

enum Days { sun, mon, tue, wed, thu, fri, sat };

пример

В следующем примере демонстрируется использование переменной enum -

import std.stdio;

enum Days { sun, mon, tue, wed, thu, fri, sat };

int main(string[] args) {
   Days day;

   day = Days.mon;
   writefln("Current Day: %d", day); 
   writefln("Friday : %d", Days.fri); 
   return 0;
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Current Day: 1 
Friday : 5

В приведенной выше программе мы видим, как можно использовать перечисление. Первоначально мы создаем переменную с именем day из нашего пользовательского перечисления Days. Затем мы устанавливаем его на mon, используя оператор точки. Нам нужно использовать метод writefln, чтобы распечатать сохраненное значение mon. Также необходимо указать тип. Это целое число, поэтому для печати мы используем% d.

Свойства именованных перечислений

В приведенном выше примере для перечисления используется имя Days, которое называется именованными перечислениями. Эти именованные перечисления имеют следующие свойства:

  • Init - Инициализирует первое значение в перечислении.

  • min - Возвращает наименьшее значение перечисления.

  • max - Возвращает наибольшее значение перечисления.

  • sizeof - Возвращает размер хранилища для перечисления.

Давайте изменим предыдущий пример, чтобы использовать свойства.

import std.stdio;

// Initialized sun with value 1 
enum Days { sun = 1, mon, tue, wed, thu, fri, sat };

int main(string[] args) { 
   writefln("Min : %d", Days.min); 
   writefln("Max : %d", Days.max);
   writefln("Size of: %d", Days.sizeof); 
   return 0; 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Min : 1
Max : 7
Size of: 4

Анонимный Enum

Перечисление без имени называется анонимным перечислением. Пример дляanonymous enum приведен ниже.

import std.stdio; 
 
// Initialized sun with value 1 
enum { sun , mon, tue, wed, thu, fri, sat }; 
 
int main(string[] args) { 
   writefln("Sunday : %d", sun); 
   writefln("Monday : %d", mon); 
   return 0; 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Sunday : 0
Monday : 1

Анонимные перечисления работают почти так же, как именованные перечисления, но у них нет свойств max, min и sizeof.

Enum с синтаксисом базового типа

Синтаксис для перечисления с базовым типом показан ниже.

enum :baseType {  
   enumeration list 
}

Некоторые из базовых типов включают long, int и string. Пример использования long показан ниже.

import std.stdio;
  
enum : string { 
   A = "hello", 
   B = "world", 
} 
  
int main(string[] args) { 
   writefln("A : %s", A); 
   writefln("B : %s", B); 
   
   return 0; 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

A : hello
B : world

Больше возможностей

Перечисление в D обеспечивает такие функции, как инициализация нескольких значений в перечислении с несколькими типами. Пример показан ниже.

import std.stdio;
  
enum { 
   A = 1.2f,  // A is 1.2f of type float 
   B,         // B is 2.2f of type float 
   int C = 3, // C is 3 of type int 
   D          // D is 4 of type int 
}
  
int main(string[] args) { 
   writefln("A : %f", A); 
   writefln("B : %f", B); 
   writefln("C : %d", C); 
   writefln("D : %d", D);  
   return 0; 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

A : 1.200000
B : 2.200000
C : 3
D : 4

Постоянные значения, которые набираются в программе как часть исходного кода, называются literals.

Литералы могут относиться к любому из основных типов данных и могут быть разделены на целые числа, числа с плавающей точкой, символы, строки и логические значения.

Опять же, литералы обрабатываются так же, как обычные переменные, за исключением того, что их значения не могут быть изменены после их определения.

Целочисленные литералы

Целочисленный литерал может быть одного из следующих типов:

  • Decimal использует обычное представление числа с первой цифрой не может быть 0, поскольку эта цифра зарезервирована для обозначения восьмеричной системы. Это не включает 0 сам по себе: 0 - это ноль.

  • Octal использует 0 в качестве префикса к номеру.

  • Binary использует префикс 0b или 0B.

  • Hexadecimal использует префикс 0x или 0X.

Целочисленный литерал также может иметь суффикс, который представляет собой комбинацию U и L для unsigned и long соответственно. Суффикс может быть в верхнем или нижнем регистре и может быть в любом порядке.

Если вы не используете суффикс, компилятор сам выбирает между int, uint, long и ulong в зависимости от величины значения.

Вот несколько примеров целочисленных литералов -

212         // Legal 
215u        // Legal 
0xFeeL      // Legal 
078         // Illegal: 8 is not an octal digit 
032UU       // Illegal: cannot repeat a suffix

Ниже приведены другие примеры различных типов целочисленных литералов.

85         // decimal 
0213       // octal
0x4b       // hexadecimal 
30         // int 
30u        // unsigned int 
30l        // long 
30ul       // unsigned long 
0b001      // binary

Литералы с плавающей запятой

Литералы с плавающей запятой могут быть указаны в десятичной системе, как в 1.568, или в шестнадцатеричной системе, как в 0x91.bc.

В десятичной системе показатель степени может быть представлен добавлением символа e или E и числа после этого. Например, 2.3e4 означает «2,3 умножить на 10 в степени 4». Перед значением показателя степени может быть указан символ «+», но это не имеет никакого эффекта. Например 2.3e4 и 2.3e + 4 одинаковы.

Знак «-», добавленный перед значением показателя степени, изменяет значение на «деление на 10 в степени». Например, 2.3e-2 означает «2.3, деленное на 10 в степени 2».

В шестнадцатеричной системе значение начинается с 0x или 0X. Показатель степени определяется буквой p или P вместо e или E. Показатель степени означает не «10 в степени», а «2 в степени». Например, P4 в 0xabc.defP4 означает «abc.de умножить на 2 в степени 4».

Вот несколько примеров литералов с плавающей запятой -

3.14159       // Legal 
314159E-5L    // Legal 
510E          // Illegal: incomplete exponent 
210f          // Illegal: no decimal or exponent 
.e55          // Illegal: missing integer or fraction 
0xabc.defP4   // Legal Hexa decimal with exponent 
0xabc.defe4   // Legal Hexa decimal without exponent.

По умолчанию тип литерала с плавающей запятой - double. F и F означают число с плавающей запятой, а спецификатор L означает действительный.

Логические литералы

Есть два логических литерала, и они являются частью стандартных ключевых слов D -

  • Ценность true представляющий истину.

  • Ценность false представляющий ложь.

Не следует считать, что значение true равно 1, а значение false - 0.

Символьные литералы

Символьные литералы заключаются в одинарные кавычки.

Символьный литерал может быть простым символом (например, 'x'), escape-последовательностью (например, '\ t'), символом ASCII (например, '\ x21'), символом Unicode (например, '\ u011e') или как именованный символ (например, '\ ©', '\ ♥', '\ €').

В D есть определенные символы, которым предшествует обратная косая черта, они будут иметь особое значение и используются для обозначения новой строки (\ n) или табуляции (\ t). Здесь у вас есть список некоторых таких кодов escape-последовательностей -

Последовательность выхода Имея в виду
\\ \ персонаж
\ ' ' персонаж
\ " " персонаж
\? ? персонаж
\ а Оповещение или звонок
\ b Backspace
\ f Подача формы
\ п Новая линия
Возврат каретки
\ т Горизонтальная вкладка
\ v Вертикальная табуляция

В следующем примере показано несколько символов escape-последовательности -

import std.stdio;
  
int main(string[] args) { 
   writefln("Hello\tWorld%c\n",'\x21'); 
   writefln("Have a good day%c",'\x21'); 
   return 0; 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Hello   World!

Have a good day!

Строковые литералы

Строковые литералы заключаются в двойные кавычки. Строка содержит символы, похожие на символьные литералы: простые символы, escape-последовательности и универсальные символы.

Вы можете разбить длинную строку на несколько строк, используя строковые литералы, и разделить их пробелами.

Вот несколько примеров строковых литералов -

import std.stdio;

int main(string[] args) {
   writeln(q"MY_DELIMITER
      Hello World
      Have a good day
      MY_DELIMITER");

   writefln("Have a good day%c",'\x21'); 
   auto str = q{int value = 20; ++value;}; 
   writeln(str); 
}

В приведенном выше примере вы можете найти использование q "MY_DELIMITER MY_DELIMITER" для представления многострочных символов. Кроме того, вы можете видеть q {} для представления самого оператора языка D.

Оператор - это символ, который сообщает компилятору о необходимости выполнения определенных математических или логических операций. Язык D богат встроенными операторами и предоставляет следующие типы операторов:

  • Арифметические операторы
  • Операторы отношения
  • Логические операторы
  • Побитовые операторы
  • Операторы присваивания
  • Разные операторы

В этой главе один за другим объясняются арифметические, реляционные, логические, побитовые, присваивающие и другие операторы.

Арифметические операторы

В следующей таблице показаны все арифметические операторы, поддерживаемые языком D. Предположим переменнуюA содержит 10 и переменную B держит 20, тогда -

Показать примеры

Оператор Описание пример
+ Он добавляет два операнда. A + B дает 30
- Он вычитает второй операнд из первого. A - B дает -10
* Он умножает оба операнда. A * B дает 200
/ Делит числитель на знаменатель. Б / А дает 2
% Возвращает остаток от целочисленного деления. B% A дает 0
++ Оператор инкремента увеличивает целочисленное значение на единицу. А ++ дает 11
- Оператор декремента уменьшает целое значение на единицу. A-- дает 9

Операторы отношения

В следующей таблице показаны все операторы отношения, поддерживаемые языком D. Предположим переменнуюA содержит 10 и переменную B держит 20, то -

Показать примеры

Оператор Описание пример
== Проверяет, равны ли значения двух операндов или нет, если да, то условие становится истинным. (A == B) неверно.
знак равно Проверяет, равны ли значения двух операндов или нет, если значения не равны, условие становится истинным. (A! = B) верно.
> Проверяет, больше ли значение левого операнда, чем значение правого операнда, если да, то условие становится истинным. (A> B) неверно.
< Проверяет, меньше ли значение левого операнда, чем значение правого операнда, если да, то условие становится истинным. (A <B) верно.
> = Проверяет, больше ли значение левого операнда или равно значению правого операнда, если да, то условие становится истинным. (A> = B) неверно.
<= Проверяет, меньше ли значение левого операнда или равно значению правого операнда, если да, то условие становится истинным. (A <= B) верно.

Логические операторы

В следующей таблице показаны все логические операторы, поддерживаемые языком D. Предположим переменнуюA содержит 1 и переменную B имеет 0, то -

Показать примеры

Оператор Описание пример
&& Это называется логическим оператором И. Если оба операнда не равны нулю, тогда условие становится истинным. (A && B) ложно.
|| Он называется логическим оператором ИЛИ. Если какой-либо из двух операндов не равен нулю, условие становится истинным. (A || B) верно.
! Он называется оператором логического НЕ. Используется для изменения логического состояния операнда на обратное. Если условие истинно, то оператор логического НЕ сделает ложным. ! (A && B) верно.

Побитовые операторы

Побитовые операторы работают с битами и выполняют побитовые операции. Таблицы истинности для &, | и ^ следующие:

п q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

Допустим, если A = 60; и B = 13. В двоичном формате они будут такими:

А = 0011 1100

В = 0000 1101

-----------------

A&B = 0000 1100

А | В = 0011 1101

A ^ B = 0011 0001

~ А = 1100 0011

Побитовые операторы, поддерживаемые языком D, перечислены в следующей таблице. Предположим, что переменная A содержит 60, а переменная B содержит 13, тогда -

Показать примеры

Оператор Описание пример
& Двоичный оператор И копирует бит в результат, если он существует в обоих операндах. (A & B) даст 12, означает 0000 1100.
| Оператор двоичного ИЛИ копирует бит, если он существует в любом из операндов. (A | B) дает 61. Означает 0011 1101.
^ Двоичный оператор XOR копирует бит, если он установлен в одном операнде, но не в обоих. (A ^ B) дает 49. Значит 0011 0001
~ Оператор дополнения двоичных единиц является унарным и имеет эффект «переворачивания» битов. (~ A) дает -61. Означает 1100 0011 в дополнении до двух.
<< Оператор двоичного сдвига влево. Значение левого операнда сдвигается влево на количество битов, указанное правым операндом. << 2 дает 240. Означает 1111 0000.
>> Оператор двоичного сдвига вправо. Значение левого операнда перемещается вправо на количество битов, указанное правым операндом. A >> 2 дай 15. Значит 0000 1111.

Операторы присваивания

Следующие операторы присваивания поддерживаются языком D -

Показать примеры

Оператор Описание пример
знак равно Это простой оператор присваивания. Он присваивает значения от правых операндов к левому операнду C = A + B присваивает значение A + B в C
+ = Это оператор добавления И присваивания. Он добавляет правый операнд к левому операнду и присваивает результат левому операнду C + = A эквивалентно C = C + A
знак равно Это оператор вычитания И присваивания. Он вычитает правый операнд из левого операнда и присваивает результат левому операнду. C - = A эквивалентно C = C - A
знак равно Это оператор умножения И присваивания. Он умножает правый операнд на левый операнд и присваивает результат левому операнду. C * = A эквивалентно C = C * A
знак равно Это оператор деления И присваивания. Он делит левый операнд на правый и присваивает результат левому операнду. C / = A эквивалентно C = C / A
знак равно Это оператор присваивания И по модулю. Он принимает модуль с использованием двух операндов и присваивает результат левому операнду. C% = A эквивалентно C = C% A
<< = Это оператор сдвига влево И присваивания. C << = 2 совпадает с C = C << 2
>> = Это оператор сдвига вправо И присваивания. C >> = 2 совпадает с C = C >> 2
знак равно Это побитовый оператор присваивания И. C & = 2 совпадает с C = C & 2
^ = Это побитовое исключающее ИЛИ и оператор присваивания. C ^ = 2 совпадает с C = C ^ 2
| = Это побитовое включающее ИЛИ и оператор присваивания C | = 2 совпадает с C = C | 2

Разные операторы - Sizeof и Ternary

Есть несколько других важных операторов, включая sizeof и ? : поддерживается языком D.

Показать примеры

Оператор Описание пример
размер() Возвращает размер переменной. sizeof (a), где a - целое число, возвращает 4.
& Возвращает адрес переменной. & a; дает фактический адрес переменной.
* Указатель на переменную. * а; дает указатель на переменную.
? : Условное выражение Если условие истинно, тогда значение X: в противном случае значение Y.

Приоритет операторов в D

Приоритет оператора определяет группировку терминов в выражении. Это влияет на то, как оценивается выражение. Некоторые операторы имеют приоритет перед другими.

Например, оператор умножения имеет более высокий приоритет, чем оператор сложения.

Рассмотрим выражение

х = 7 + 3 * 2.

Здесь x присвоено 13, а не 20. Простая причина в том, что оператор * имеет более высокий приоритет, чем +, поэтому сначала вычисляется 3 * 2, а затем результат добавляется к 7.

Здесь операторы с наивысшим приоритетом отображаются вверху таблицы, а операторы с самым низким - внизу. Внутри выражения в первую очередь оцениваются операторы с более высоким приоритетом.

Показать примеры

Категория Оператор Ассоциативность
Постфикс () [] ->. ++ - - Слева направо
Унарный + -! ~ ++ - - (тип) * и размер Справа налево
Мультипликативный * /% Слева направо
Добавка + - Слева направо
сдвиг << >> Слева направо
Реляционный <<=>> = Слева направо
Равенство ==! = Слева направо
Побитовое И & Слева направо
Побитовое исключающее ИЛИ ^ Слева направо
Побитовое ИЛИ | Слева направо
Логическое И && Слева направо
Логическое ИЛИ || Слева направо
Условный ?: Справа налево
Назначение = + = - = * = / =% = >> = << = & = ^ = | = Справа налево
Запятая , Слева направо

Возможна ситуация, когда вам нужно выполнить блок кода несколько раз. Как правило, операторы выполняются последовательно: сначала выполняется первый оператор функции, затем второй и т. Д.

Языки программирования предоставляют различные структуры управления, которые позволяют более сложные пути выполнения.

Оператор цикла выполняет оператор или группу операторов несколько раз. Следующая общая форма оператора цикла в основном используется в языках программирования:

Язык программирования D предоставляет следующие типы циклов для обработки требований цикла. Щелкните следующие ссылки, чтобы проверить их детали.

Sr.No. Тип и описание петли
1 пока цикл

Он повторяет утверждение или группу утверждений, пока выполняется данное условие. Он проверяет условие перед выполнением тела цикла.

2 для цикла

Он выполняет последовательность операторов несколько раз и сокращает код, управляющий переменной цикла.

3 делать ... пока цикл

Подобен оператору while, за исключением того, что он проверяет условие в конце тела цикла.

4 вложенные циклы

Вы можете использовать один или несколько циклов внутри любого другого цикла while, for или do.. while.

Заявления контроля цикла

Операторы управления циклом изменяют выполнение обычной последовательности. Когда выполнение покидает область действия, все автоматические объекты, созданные в этой области, уничтожаются.

D поддерживает следующие управляющие утверждения -

Sr.No. Положение и описание управления
1 заявление о прерывании

Завершает оператор цикла или переключателя и передает выполнение оператору сразу после цикла или переключателя.

2 продолжить заявление

Заставляет цикл пропускать оставшуюся часть своего тела и немедленно повторно проверять свое состояние перед повторением.

Бесконечный цикл

Цикл становится бесконечным, если условие никогда не становится ложным. Вforloop традиционно используется для этой цели. Поскольку ни одно из трех выражений, образующих цикл for, не требуется, вы можете создать бесконечный цикл, оставив условное выражение пустым.

import std.stdio;

int main () {

   for( ; ; ) {
      writefln("This loop will run forever.");
   }
   return 0;
}

Когда условное выражение отсутствует, оно считается истинным. У вас может быть выражение инициализации и приращения, но программисты на D чаще используют конструкцию for (;;) для обозначения бесконечного цикла.

NOTE - Вы можете прервать бесконечный цикл, нажав клавиши Ctrl + C.

Структуры принятия решений содержат условие, которое необходимо оценить, а также два набора операторов, которые должны быть выполнены. Один набор операторов выполняется, если условие истинно, а другой набор операторов выполняется, если условие ложно.

Ниже приводится общая форма типичной структуры принятия решений, встречающейся в большинстве языков программирования.

Язык программирования D предполагает любые non-zero и non-null ценности как true, и если это либо zero или же null, то предполагается, что false значение.

Язык программирования D предоставляет следующие типы операторов принятия решений.

Sr.No. Заявление и описание
1 если заявление

An if statement состоит из логического выражения, за которым следует одно или несколько операторов.

2 если ... еще заявление

An if statement может сопровождаться необязательным else statement, который выполняется, когда логическое выражение ложно.

3 вложенные операторы if

Вы можете использовать один if или же else if заявление внутри другого if или же else if заявления).

4 оператор переключения

А switch оператор позволяет проверить переменную на равенство со списком значений.

5 nested switch statements

You can use one switch statement inside another switch statement(s).

The ? : Operator in D

We have covered conditional operator ? : in previous chapter which can be used to replace if...else statements. It has the following general form

Exp1 ? Exp2 : Exp3;

Where Exp1, Exp2, and Exp3 are expressions. Notice the use and placement of the colon.

The value of a ? expression is determined as follows −

  • Exp1 is evaluated. If it is true, then Exp2 is evaluated and becomes the value of the entire ? expression.

  • If Exp1 is false, then Exp3 is evaluated and its value becomes the value of the expression.

This chapter describes the functions used in D programming.

Function Definition in D

A basic function definition consists of a function header and a function body.

Синтаксис

return_type function_name( parameter list ) { 
   body of the function 
}

Вот все части функции -

  • Return Type- Функция может возвращать значение. Вreturn_type- тип данных значения, возвращаемого функцией. Некоторые функции выполняют желаемые операции без возврата значения. В этом случае return_type - это ключевое словоvoid.

  • Function Name- Это настоящее имя функции. Имя функции и список параметров вместе составляют сигнатуру функции.

  • Parameters- Параметр похож на заполнитель. Когда функция вызывается, вы передаете значение параметру. Это значение называется фактическим параметром или аргументом. Список параметров относится к типу, порядку и количеству параметров функции. Параметры не обязательны; то есть функция может не содержать параметров.

  • Function Body - Тело функции содержит набор операторов, которые определяют, что функция делает.

Вызов функции

Вы можете вызвать функцию следующим образом -

function_name(parameter_values)

Типы функций в D

Программирование на языке D поддерживает широкий спектр функций, и они перечислены ниже.

  • Чистые функции
  • Функции Nothrow
  • Ref Функции
  • Авто функции
  • Вариативные функции
  • Функции Inout
  • Функции собственности

Ниже описаны различные функции.

Чистые функции

Чистые функции - это функции, которые не могут получить доступ к глобальному или статическому изменяемому состоянию за исключением своих аргументов. Это может обеспечить оптимизацию, основанную на том факте, что чистая функция гарантированно не изменяет ничего, что ей не передается, и в случаях, когда компилятор может гарантировать, что чистая функция не может изменять свои аргументы, он может обеспечить полную функциональную чистоту, что есть гарантия того, что функция всегда будет возвращать один и тот же результат для одних и тех же аргументов).

import std.stdio; 

int x = 10; 
immutable int y = 30; 
const int* p;  

pure int purefunc(int i,const char* q,immutable int* s) { 
   //writeln("Simple print"); //cannot call impure function 'writeln'
   
   debug writeln("in foo()"); // ok, impure code allowed in debug statement 
   // x = i;  // error, modifying global state 
   // i = x;  // error, reading mutable global state 
   // i = *p; // error, reading const global state
   i = y;     // ok, reading immutable global state 
   auto myvar = new int;     // Can use the new expression: 
   return i; 
}

void main() { 
   writeln("Value returned from pure function : ",purefunc(x,null,null)); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Value returned from pure function : 30

Функции Nothrow

Функции Nothrow не генерируют никаких исключений, производных от класса Exception. Нотроу функции ковариантны с метательными.

Nothrow гарантирует, что функция не выдаст исключение.

import std.stdio; 

int add(int a, int b) nothrow { 
   //writeln("adding"); This will fail because writeln may throw 
   int result; 
   
   try { 
      writeln("adding"); // compiles 
      result = a + b; 
   } catch (Exception error) { // catches all exceptions 
   }

   return result; 
} 
 
void main() { 
   writeln("Added value is ", add(10,20)); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

adding 
Added value is 30

Ref Функции

Функции Ref позволяют функциям возвращаться по ссылке. Это аналогично параметрам функции ref.

import std.stdio;

ref int greater(ref int first, ref int second) { 
   return (first > second) ? first : second; 
} 
 
void main() {
   int a = 1; 
   int b = 2;  
   
   greater(a, b) += 10;   
   writefln("a: %s, b: %s", a, b);   
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

a: 1, b: 12

Авто функции

Автоматические функции могут возвращать значение любого типа. Нет никаких ограничений на возвращаемый тип. Ниже приведен простой пример функции автоматического ввода.

import std.stdio;

auto add(int first, double second) { 
   double result = first + second; 
   return result; 
} 

void main() { 
   int a = 1; 
   double b = 2.5; 
   
   writeln("add(a,b) = ", add(a, b)); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

add(a,b) = 3.5

Вариативные функции

Функции Variadiac - это те функции, в которых количество параметров для функции определяется во время выполнения. В C есть ограничение наличия хотя бы одного параметра. Но в программировании на языке D такого ограничения нет. Ниже показан простой пример.

import std.stdio;
import core.vararg;

void printargs(int x, ...) {  
   for (int i = 0; i < _arguments.length; i++) {  
      write(_arguments[i]);  
   
      if (_arguments[i] == typeid(int)) { 
         int j = va_arg!(int)(_argptr); 
         writefln("\t%d", j); 
      } else if (_arguments[i] == typeid(long)) { 
         long j = va_arg!(long)(_argptr); 
         writefln("\t%d", j); 
      } else if (_arguments[i] == typeid(double)) { 
         double d = va_arg!(double)(_argptr); 
         writefln("\t%g", d); 
      } 
   } 
}
  
void main() { 
   printargs(1, 2, 3L, 4.5); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

int 2 
long 3 
double 4.5

Функции Inout

Inout может использоваться как для параметров, так и для возвращаемых функций. Это похоже на шаблон для изменяемых, константных и неизменяемых. Атрибут изменчивости выводится из параметра. Значит, inout передает выведенный атрибут изменчивости в возвращаемый тип. Ниже показан простой пример, показывающий, как изменяется изменчивость.

import std.stdio;

inout(char)[] qoutedWord(inout(char)[] phrase) { 
   return '"' ~ phrase ~ '"';
}

void main() { 
   char[] a = "test a".dup; 

   a = qoutedWord(a); 
   writeln(typeof(qoutedWord(a)).stringof," ", a);  

   const(char)[] b = "test b"; 
   b = qoutedWord(b); 
   writeln(typeof(qoutedWord(b)).stringof," ", b); 

   immutable(char)[] c = "test c"; 
   c = qoutedWord(c); 
   writeln(typeof(qoutedWord(c)).stringof," ", c); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

char[] "test a" 
const(char)[] "test b" 
string "test c"

Функции собственности

Свойства позволяют использовать функции-члены, такие как переменные-члены. Он использует ключевое слово @property. Свойства связаны со связанной функцией, которая возвращает значения в зависимости от требований. Ниже показан простой пример свойства.

import std.stdio;

struct Rectangle { 
   double width; 
   double height;  

   double area() const @property {  
      return width*height;  
   } 

   void area(double newArea) @property {  
      auto multiplier = newArea / area; 
      width *= multiplier; 
      writeln("Value set!");  
   } 
}

void main() { 
   auto rectangle = Rectangle(20,10); 
   writeln("The area is ", rectangle.area);  
   
   rectangle.area(300); 
   writeln("Modified width is ", rectangle.width); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

The area is 200 
Value set! 
Modified width is 30

Персонажи - это строительные блоки строк. Любой символ системы письма называется символом: буквы алфавита, цифры, знаки препинания, пробел и т. Д. Как ни странно, сами строительные блоки символов также называются символами.

Целочисленное значение строчной буквы a равно 97, а целочисленное значение цифры 1 - 49. Эти значения были присвоены просто условно при разработке таблицы ASCII.

В следующей таблице перечислены стандартные типы символов с указанием размеров и целей их хранения.

Символы представлены типом char, который может содержать только 256 различных значений. Если вы знакомы с типом char из других языков, возможно, вы уже знаете, что он недостаточно велик для поддержки символов многих систем письма.

Тип Размер хранилища Цель
char 1 байт Кодовый блок UTF-8
чар 2 байта Кодовый блок UTF-16
dchar 4 байта Кодовый блок UTF-32 и кодовая точка Unicode

Некоторые полезные символьные функции перечислены ниже -

  • isLower - Определяет ли строчный символ?

  • isUpper - Определяет, является ли символ в верхнем регистре?

  • isAlpha - Определяет, используется ли буквенно-цифровой символ Unicode (как правило, буква или цифра)?

  • isWhite - Определяет, есть ли пробельный символ?

  • toLower - Производит строчные буквы данного символа.

  • toUpper - Производит заглавные буквы данного символа.

import std.stdio;
import std.uni;

void main() { 
   writeln("Is ğ lowercase? ", isLower('ğ')); 
   writeln("Is Ş lowercase? ", isLower('Ş'));  
   
   writeln("Is İ uppercase? ", isUpper('İ')); 
   writeln("Is ç uppercase? ", isUpper('ç')); 
   
   writeln("Is z alphanumeric? ",       isAlpha('z'));  
   writeln("Is new-line whitespace? ",  isWhite('\n')); 
   
   writeln("Is underline whitespace? ", isWhite('_'));  
   
   writeln("The lowercase of Ğ: ", toLower('Ğ')); 
   writeln("The lowercase of İ: ", toLower('İ')); 
   
   writeln("The uppercase of ş: ", toUpper('ş')); 
   writeln("The uppercase of ı: ", toUpper('ı')); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Is ğ lowercase? true 
Is Ş lowercase? false 
Is İ uppercase? true 
Is ç uppercase? false
Is z alphanumeric? true 
Is new-line whitespace? true 
Is underline whitespace? false 
The lowercase of Ğ: ğ 
The lowercase of İ: i 
The uppercase of ş: Ş 
The uppercase of ı: I

Чтение символов в D

Мы можем читать символы, используя readf, как показано ниже.

readf(" %s", &letter);

Поскольку программирование на языке D поддерживает юникод, для чтения символов Юникода нам нужно дважды прочитать и дважды написать, чтобы получить ожидаемый результат. Это не работает в онлайн-компиляторе. Пример показан ниже.

import std.stdio;

void main() { 
   char firstCode; 
   char secondCode; 
   
   write("Please enter a letter: "); 
   readf(" %s", &firstCode); 
   readf(" %s", &secondCode); 
   
   writeln("The letter that has been read: ", firstCode, secondCode); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Please enter a letter: ğ 
The letter that has been read: ğ

D предоставляет следующие два типа строковых представлений -

  • Массив символов
  • Строка основного языка

Массив символов

Мы можем представить массив символов в одной из двух форм, как показано ниже. Первая форма предоставляет размер напрямую, а вторая форма использует метод dup, который создает доступную для записи копию строки «Доброе утро».

char[9]  greeting1 = "Hello all"; 
char[] greeting2 = "Good morning".dup;

пример

Вот простой пример, использующий вышеуказанные простые формы массива символов.

import std.stdio;

void main(string[] args) { 
   char[9] greeting1 = "Hello all"; 
   writefln("%s",greeting1); 

   char[] greeting2 = "Good morning".dup; 
   writefln("%s",greeting2); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Hello all 
Good morning

Строка основного языка

Строки встроены в базовый язык D. Эти строки совместимы с массивом символов, показанным выше. В следующем примере показано простое строковое представление.

string greeting1 = "Hello all";

пример

import std.stdio;

void main(string[] args) { 
   string greeting1 = "Hello all"; 
   writefln("%s",greeting1);  
   
   char[] greeting2 = "Good morning".dup; 
   writefln("%s",greeting2);  
   
   string greeting3 = greeting1; 
   writefln("%s",greeting3); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Hello all 
Good morning 
Hello all

Конкатенация строк

Конкатенация строк в программировании на языке D использует символ тильды (~).

пример

import std.stdio;

void main(string[] args) { 
   string greeting1 = "Good"; 
   char[] greeting2 = "morning".dup; 
   
   char[] greeting3 = greeting1~" "~greeting2; 
   writefln("%s",greeting3); 
   
   string greeting4 = "morning"; 
   string greeting5 = greeting1~" "~greeting4; 
   writefln("%s",greeting5); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Good morning 
Good morning

Длина строки

Длина строки в байтах может быть получена с помощью функции length.

пример

import std.stdio;  

void main(string[] args) { 
   string greeting1 = "Good"; 
   writefln("Length of string greeting1 is %d",greeting1.length); 
   
   char[] greeting2 = "morning".dup;        
   writefln("Length of string greeting2 is %d",greeting2.length); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Length of string greeting1 is 4 
Length of string greeting2 is 7

Сравнение строк

Сравнение строк в программировании на языке D. Вы можете использовать операторы ==, <и> для сравнения строк.

пример

import std.stdio; 
 
void main() { 
   string s1 = "Hello"; 
   string s2 = "World";
   string s3 = "World";
   
   if (s2 == s3) { 
      writeln("s2: ",s2," and S3: ",s3, "  are the same!"); 
   }
   
   if (s1 < s2) { 
      writeln("'", s1, "' comes before '", s2, "'."); 
   } else { 
      writeln("'", s2, "' comes before '", s1, "'."); 
   }
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

s2: World and S3: World are the same! 
'Hello' comes before 'World'.

Замена струн

Мы можем заменить строки, используя строку [].

пример

import std.stdio; 
import std.string; 
 
void main() {
   char[] s1 = "hello world ".dup; 
   char[] s2 = "sample".dup;
   
   s1[6..12] = s2[0..6]; 
   writeln(s1);
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

hello sample

Индексные методы

В следующем примере объясняются методы индексации подстроки в строке, включая indexOf и lastIndexOf.

пример

import std.stdio;
import std.string;

void main() { 
   char[] s1 = "hello World ".dup; 
    
   writeln("indexOf of llo in hello is ",std.string.indexOf(s1,"llo")); 
   writeln(s1); 
   writeln("lastIndexOf of O in hello is " ,std.string.lastIndexOf(s1,"O",CaseSensitive.no));
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

indexOf.of llo in hello is 2 
hello World  
lastIndexOf of O in hello is 7

Обработка случаев

Способы, используемые для изменения регистра, показаны в следующем примере.

пример

import std.stdio;
import std.string;

void main() { 
   char[] s1 = "hello World ".dup; 
   writeln("Capitalized string of s1 is ",capitalize(s1)); 
    
   writeln("Uppercase string of s1 is ",toUpper(s1)); 
    
   writeln("Lowercase string of s1 is ",toLower(s1));   
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Capitalized string of s1 is Hello world  
Uppercase string of s1 is HELLO WORLD  
Lowercase string of s1 is hello world

Ограничивающие символы

Ограничивающие символы в строках показаны в следующем примере.

пример

import std.stdio;
import std.string;

void main() { 
   string s = "H123Hello1";  
   
   string result = munch(s, "0123456789H"); 
   writeln("Restrict trailing characters:",result);  
   
   result = squeeze(s, "0123456789H"); 
   writeln("Restrict leading characters:",result); 
   
   s = "  Hello World  "; 
   writeln("Stripping leading and trailing whitespace:",strip(s)); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Restrict trailing characters:H123H 
Restrict leading characters:ello1 
Stripping leading and trailing whitespace:Hello World

Язык программирования D предоставляет структуру данных с именем arrays, в котором хранится последовательная коллекция фиксированного размера элементов одного типа. Массив используется для хранения набора данных. Часто бывает более полезно рассматривать массив как набор переменных одного типа.

Вместо объявления отдельных переменных, таких как число0, число1, ... и число99, вы объявляете одну переменную массива, например числа, и используете числа [0], числа [1] и ..., числа [99] для представления отдельные переменные. Доступ к определенному элементу в массиве осуществляется по индексу.

Все массивы состоят из непрерывных ячеек памяти. Самый низкий адрес соответствует первому элементу, а самый высокий адрес - последнему элементу.

Объявление массивов

Чтобы объявить массив на языке программирования D, программист указывает тип элементов и количество элементов, необходимых для массива, следующим образом:

type arrayName [ arraySize ];

Это называется одномерным массивом. ArraySize должно быть целым числом константа больше нуля и тип может быть любым допустимым D программирования типа данных языка. Например, чтобы объявить массив из 10 элементов под названием balance типа double, используйте этот оператор:

double balance[10];

Инициализация массивов

Вы можете инициализировать элементы массива языка программирования D либо по одному, либо с помощью одного оператора, как показано ниже.

double balance[5] = [1000.0, 2.0, 3.4, 17.0, 50.0];

Количество значений в квадратных скобках [] с правой стороны не может быть больше количества элементов, объявленных вами для массива между квадратными скобками []. В следующем примере назначается один элемент массива -

Если вы не укажете размер массива, будет создан массив, достаточно большой, чтобы вместить инициализацию. Следовательно, если вы напишете

double balance[] = [1000.0, 2.0, 3.4, 17.0, 50.0];

тогда вы создадите точно такой же массив, как и в предыдущем примере.

balance[4] = 50.0;

Приведенный выше оператор присваивает элементу номер 5 в массиве значение 50.0. Массив с 4-м индексом будет 5-м, т.е. последним элементом, потому что все массивы имеют 0 в качестве индекса своего первого элемента, который также называется базовым индексом. Следующее графическое изображение показывает тот же массив, который мы обсуждали выше -

Доступ к элементам массива

Доступ к элементу осуществляется путем индексации имени массива. Это делается путем помещения индекса элемента в квадратные скобки после имени массива. Например -

double salary = balance[9];

Приведенный выше оператор берет 10- й элемент из массива и присваивает значение переменной salary . В следующем примере реализуется объявление, присваивание и доступ к массивам:

import std.stdio;  
void main() { 
   int n[ 10 ]; // n is an array of 10 integers  
   
   // initialize elements of array n to 0 
   for ( int i = 0; i < 10; i++ ) { 
      n[ i ] = i + 100; // set element at location i to i + 100 
   }
   
   writeln("Element \t Value");
   
   // output each array element's value 
   for ( int j = 0; j < 10; j++ ) { 
      writeln(j," \t ",n[j]); 
   } 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Element   Value 
0         100 
1         101 
2         102 
3         103 
4         104 
5         105 
6         106 
7         107 
8         108 
9         109

Статические массивы и динамические массивы

Если длина массива указана при написании программы, этот массив является статическим. Если длина может изменяться во время выполнения программы, этот массив является динамическим.

Определение динамических массивов проще, чем определение массивов фиксированной длины, потому что отсутствие длины делает динамический массив -

int[] dynamicArray;

Свойства массива

Вот свойства массивов -

Sr.No. Описание недвижимости
1

.init

Статический массив возвращает литерал массива, каждый элемент которого является свойством .init типа элемента массива.

2

.sizeof

Статический массив возвращает длину массива, умноженную на количество байтов на элемент массива, в то время как динамические массивы возвращают размер ссылки на динамический массив, который составляет 8 в 32-битных сборках и 16 в 64-битных сборках.

3

.length

Статический массив возвращает количество элементов в массиве, в то время как динамические массивы используются для получения / установки количества элементов в массиве. Длина имеет тип size_t.

4

.ptr

Возвращает указатель на первый элемент массива.

5

.dup

Создайте динамический массив такого же размера и скопируйте в него содержимое массива.

6

.idup

Создайте динамический массив такого же размера и скопируйте в него содержимое массива. Копия печатается как неизменяемая.

7

.reverse

Изменяет порядок элементов в массиве на обратный. Возвращает массив.

8

.sort

Сортирует по порядку элементы в массиве. Возвращает массив.

пример

В следующем примере объясняются различные свойства массива -

import std.stdio;

void main() {
   int n[ 5 ]; // n is an array of 5 integers 
   
   // initialize elements of array n to 0 
   for ( int i = 0; i < 5; i++ ) { 
      n[ i ] = i + 100; // set element at location i to i + 100 
   }
   
   writeln("Initialized value:",n.init); 
   
   writeln("Length: ",n.length); 
   writeln("Size of: ",n.sizeof); 
   writeln("Pointer:",n.ptr); 
   
   writeln("Duplicate Array: ",n.dup); 
   writeln("iDuplicate Array: ",n.idup);
   
   n = n.reverse.dup; 
   writeln("Reversed Array: ",n);
   
   writeln("Sorted Array: ",n.sort); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Initialized value:[0, 0, 0, 0, 0] 

Length: 5 
Size of: 20 

Pointer:7FFF5A373920 
Duplicate Array: [100, 101, 102, 103, 104]
iDuplicate Array: [100, 101, 102, 103, 104] 
Reversed Array: [104, 103, 102, 101, 100] 
Sorted Array: [100, 101, 102, 103, 104]

Многомерные массивы в D

Программирование на языке D позволяет создавать многомерные массивы. Вот общая форма объявления многомерного массива -

type name[size1][size2]...[sizeN];

пример

Следующее объявление создает трехмерный 5. 10. 4 целочисленный массив -

int threedim[5][10][4];

Двумерные массивы в D

Простейшей формой многомерного массива является двумерный массив. Двумерный массив - это, по сути, список одномерных массивов. Чтобы объявить двумерный целочисленный массив размера [x, y], вы должны написать следующий синтаксис:

type arrayName [ x ][ y ];

где type может быть любым допустимым типом данных программирования D и arrayName будет действительным идентификатором программирования D.

Где type может быть любым допустимым типом данных программирования D, а arrayName - допустимым идентификатором программирования D.

Двумерный массив можно представить как таблицу, в которой есть x строк и y столбцов. Двумерный массивa содержащий три строки и четыре столбца, можно показать, как показано ниже -

Таким образом, каждый элемент в массиве a идентифицируется элементом как a[ i ][ j ], где a это имя массива, а i и j индексы, которые однозначно идентифицируют каждый элемент в.

Инициализация двумерных массивов

Многомерные массивы можно инициализировать, задав значения в квадратных скобках для каждой строки. В следующем массиве 3 строки, а в каждой строке 4 столбца.

int a[3][4] = [   
   [0, 1, 2, 3] ,   /*  initializers for row indexed by 0 */ 
   [4, 5, 6, 7] ,   /*  initializers for row indexed by 1 */ 
   [8, 9, 10, 11]   /*  initializers for row indexed by 2 */ 
];

Вложенные фигурные скобки, обозначающие предполагаемую строку, необязательны. Следующая инициализация эквивалентна предыдущему примеру -

int a[3][4] = [0,1,2,3,4,5,6,7,8,9,10,11];

Доступ к элементам двумерного массива

Доступ к элементу в 2-мерном массиве осуществляется с помощью нижних индексов, что означает индекс строки и индекс столбца массива. Например

int val = a[2][3];

Вышеупомянутый оператор берет 4-й элемент из 3-й строки массива. Вы можете проверить это в приведенной выше биграмме.

import std.stdio; 
  
void main () { 
   // an array with 5 rows and 2 columns. 
   int a[5][2] = [ [0,0], [1,2], [2,4], [3,6],[4,8]];  
   
   // output each array element's value                       
   for ( int i = 0; i < 5; i++ ) for ( int j = 0; j < 2; j++ ) {
      writeln( "a[" , i , "][" , j , "]: ",a[i][j]); 
   }
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

a[0][0]: 0 
a[0][1]: 0 
a[1][0]: 1 
a[1][1]: 2 
a[2][0]: 2 
a[2][1]: 4 
a[3][0]: 3 
a[3][1]: 6 
a[4][0]: 4 
a[4][1]: 8

Общие операции с массивами в D

Вот различные операции, выполняемые с массивами -

Нарезка массива

Мы часто используем часть массива, и нарезка массива часто бывает весьма полезной. Ниже показан простой пример нарезки массива.

import std.stdio;
  
void main () { 
   // an array with 5 elements. 
   double a[5] = [1000.0, 2.0, 3.4, 17.0, 50.0]; 
   double[] b;
   
   b = a[1..3]; 
   writeln(b); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

[2, 3.4]

Копирование массива

Мы также используем копирующий массив. Ниже показан простой пример копирования массива.

import std.stdio;

void main () { 
   // an array with 5 elements. 
   double a[5] = [1000.0, 2.0, 3.4, 17.0, 50.0]; 
   double b[5]; 
   writeln("Array a:",a); 
   writeln("Array b:",b);  
   
   b[] = a;      // the 5 elements of a[5] are copied into b[5] 
   writeln("Array b:",b);  
   
   b[] = a[];   // the 5 elements of a[3] are copied into b[5] 
   writeln("Array b:",b); 
   
   b[1..2] = a[0..1]; // same as b[1] = a[0] 
   writeln("Array b:",b); 
   
   b[0..2] = a[1..3]; // same as b[0] = a[1], b[1] = a[2]
   writeln("Array b:",b); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Array a:[1000, 2, 3.4, 17, 50] 
Array b:[nan, nan, nan, nan, nan] 
Array b:[1000, 2, 3.4, 17, 50] 
Array b:[1000, 2, 3.4, 17, 50] 
Array b:[1000, 1000, 3.4, 17, 50] 
Array b:[2, 3.4, 3.4, 17, 50]

Настройка массива

Ниже показан простой пример установки значения в массиве.

import std.stdio;

void main () { 
   // an array with 5 elements. 
   double a[5]; 
   a[] = 5; 
   writeln("Array a:",a); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Array a:[5, 5, 5, 5, 5]

Объединение массивов

Ниже показан простой пример объединения двух массивов.

import std.stdio;

void main () { 
   // an array with 5 elements. 
   double a[5] = 5; 
   double b[5] = 10; 
   double [] c; 
   c = a~b; 
   writeln("Array c: ",c); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Array c: [5, 5, 5, 5, 5, 10, 10, 10, 10, 10]

Ассоциативные массивы имеют индекс, который не обязательно является целым числом, и могут быть редко заполнены. Индекс ассоциативного массива называетсяKey, а его тип называется KeyType.

Ассоциативные массивы объявляются путем помещения KeyType в [] объявления массива. Ниже показан простой пример ассоциативного массива.

import std.stdio;

void main () { 
   int[string] e;      // associative array b of ints that are  
   
   e["test"] = 3; 
   writeln(e["test"]); 
   
   string[string] f; 
   
   f["test"] = "Tuts"; 
   writeln(f["test"]); 
   
   writeln(f);  
   
   f.remove("test"); 
   writeln(f); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

3 
Tuts 
["test":"Tuts"] 
[]

Инициализация ассоциативного массива

Ниже показана простая инициализация ассоциативного массива.

import std.stdio;

void main () { 
   int[string] days = 
      [ "Monday" : 0, 
         "Tuesday" : 1, 
         "Wednesday" : 2, 
         "Thursday" : 3, 
         "Friday" : 4, 
         "Saturday" : 5, 
         "Sunday" : 6 ]; 
   writeln(days["Tuesday"]);    
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

1

Свойства ассоциативного массива

Вот свойства ассоциативного массива -

Sr.No. Описание недвижимости
1

.sizeof

Возвращает размер ссылки на ассоциативный массив; это 4 в 32-битных сборках и 8 в 64-битных сборках.

2

.length

Возвращает количество значений в ассоциативном массиве. В отличие от динамических массивов, он доступен только для чтения.

3

.dup

Создайте новый ассоциативный массив того же размера и скопируйте в него содержимое ассоциативного массива.

4

.keys

Возвращает динамический массив, элементы которого являются ключами в ассоциативном массиве.

5

.values

Возвращает динамический массив, элементами которого являются значения в ассоциативном массиве.

6

.rehash

Реорганизует ассоциативный массив на месте, чтобы поиск был более эффективным. rehash эффективен, когда, например, программа завершила загрузку таблицы символов и теперь нуждается в быстром поиске в ней. Возвращает ссылку на реорганизованный массив.

7

.byKey()

Возвращает делегат, подходящий для использования в качестве агрегата, для ForeachStatement, который будет перебирать ключи ассоциативного массива.

8

.byValue()

Возвращает делегат, подходящий для использования в качестве агрегата, для ForeachStatement, который будет перебирать значения ассоциативного массива.

9

.get(Key key, lazy Value defVal)

Смотрит ключ; если он существует, возвращает соответствующее значение, иначе оценивает и возвращает defVal.

10

.remove(Key key)

Удаляет объект по ключу.

пример

Пример использования вышеуказанных свойств показан ниже.

import std.stdio;

void main () { 
   int[string] array1;

   array1["test"] = 3; 
   array1["test2"] = 20; 
   
   writeln("sizeof: ",array1.sizeof); 
   writeln("length: ",array1.length); 
   writeln("dup: ",array1.dup);  
   array1.rehash; 
   
   writeln("rehashed: ",array1);  
   writeln("keys: ",array1.keys); 
   writeln("values: ",array1.values);
   
   foreach (key; array1.byKey) { 
      writeln("by key: ",key); 
   }

   foreach (value; array1.byValue) { 
      writeln("by value ",value); 
   }

   writeln("get value for key test: ",array1.get("test",10)); 
   writeln("get value for key test3: ",array1.get("test3",10));  
   array1.remove("test"); 
   writeln(array1); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

sizeof: 8                                                                          
length: 2                                                                          
dup: ["test":3, "test2":20]                                                        
rehashed: ["test":3, "test2":20]                                                   
keys: ["test", "test2"]                                                            
values: [3, 20]                                                                    
by key: test                                                                       
by key: test2                                                                      
by value 3                                                                         
by value 20                                                                        
get value for key test: 3                                                          
get value for key test3: 10                                                        
["test2":20]

Указатели программирования D легко и весело изучать. Некоторые задачи программирования на языке D легче выполнять с помощью указателей, а другие задачи программирования на языке D, такие как распределение динамической памяти, не могут выполняться без них. Ниже показан простой указатель.

Вместо прямого указания на переменную указатель указывает на адрес переменной. Как вы знаете, каждая переменная является ячейкой памяти, и каждая ячейка памяти имеет свой адрес, доступ к которому можно получить с помощью оператора амперсанда (&), который обозначает адрес в памяти. Рассмотрим следующее, которое печатает адрес определенных переменных:

import std.stdio;
 
void main () { 
   int var1; 
   writeln("Address of var1 variable: ",&var1);  
   
   char var2[10]; 
   writeln("Address of var2 variable: ",&var2); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Address of var1 variable: 7FFF52691928 
Address of var2 variable: 7FFF52691930

Что такое указатели?

А pointer- переменная, значение которой является адресом другой переменной. Как и любую переменную или константу, вы должны объявить указатель, прежде чем вы сможете с ним работать. Общая форма объявления переменной-указателя -

type *var-name;

Вот, type- базовый тип указателя; это должен быть допустимый тип программирования иvar-nameэто имя переменной-указателя. Звездочка, которую вы использовали для объявления указателя, - это та же звездочка, которую вы используете для умножения. Тем не мение; в этом заявлении звездочка используется для обозначения переменной как указателя. Ниже приведено действительное объявление указателя -

int    *ip;    // pointer to an integer 
double *dp;    // pointer to a double 
float  *fp;    // pointer to a float 
char   *ch     // pointer to character

Фактический тип данных значения всех указателей, будь то целое число, число с плавающей запятой, символьный или иначе, является одинаковым, длинным шестнадцатеричным числом, представляющим адрес памяти. Единственное различие между указателями разных типов данных - это тип данных переменной или константы, на которые указывает указатель.

Использование указателей в программировании на языке D

Когда мы очень часто используем указатели, есть несколько важных операций.

  • мы определяем переменные указателя

  • назначить адрес переменной указателю

  • наконец, получить доступ к значению по адресу, доступному в переменной-указателе.

Это делается с помощью унарного оператора *который возвращает значение переменной, расположенной по адресу, указанному ее операндом. В следующем примере используются эти операции -

import std.stdio; 

void main () { 
   int var = 20;   // actual variable declaration. 
   int *ip;        // pointer variable
   ip = &var;   // store address of var in pointer variable  
   
   writeln("Value of var variable: ",var); 
   
   writeln("Address stored in ip variable: ",ip); 
   
   writeln("Value of *ip variable: ",*ip); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Value of var variable: 20 
Address stored in ip variable: 7FFF5FB7E930 
Value of *ip variable: 20

Нулевые указатели

Всегда рекомендуется присвоить указатель NULL переменной-указателю, если у вас нет точного адреса для назначения. Это делается во время объявления переменной. Указатель, которому присвоено значение null, называетсяnull указатель.

Нулевой указатель - это константа со значением нуля, определенная в нескольких стандартных библиотеках, включая iostream. Рассмотрим следующую программу -

import std.stdio;

void main () { 
   int  *ptr = null; 
   writeln("The value of ptr is " , ptr) ;  
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

The value of ptr is null

В большинстве операционных систем программам не разрешен доступ к памяти по адресу 0, поскольку эта память зарезервирована операционной системой. Тем не мение; адрес памяти 0 имеет особое значение; он сигнализирует, что указатель не предназначен для указания на доступную ячейку памяти.

По соглашению, если указатель содержит нулевое (нулевое) значение, предполагается, что он ни на что не указывает. Чтобы проверить нулевой указатель, вы можете использовать оператор if следующим образом:

if(ptr)     // succeeds if p is not null 
if(!ptr)    // succeeds if p is null

Таким образом, если всем неиспользуемым указателям присваивается значение null и вы избегаете использования нулевого указателя, вы можете избежать случайного неправильного использования неинициализированного указателя. Часто неинициализированные переменные содержат ненужные значения, что затрудняет отладку программы.

Указатель арифметики

Есть четыре арифметических оператора, которые можно использовать с указателями: ++, -, + и -

Чтобы понять арифметику указателей, давайте рассмотрим целочисленный указатель с именем ptr, который указывает на адрес 1000. Предполагая 32-битные целые числа, давайте выполним следующую арифматическую операцию над указателем:

ptr++

затем ptrбудет указывать на местоположение 1004, потому что каждый раз, когда ptr увеличивается, он указывает на следующее целое число. Эта операция переместит указатель на следующую ячейку памяти, не влияя на фактическое значение в этой ячейке памяти.

Если ptr указывает на символ с адресом 1000, то вышеуказанная операция указывает на местоположение 1001, потому что следующий символ будет доступен в 1001.

Увеличение указателя

Мы предпочитаем использовать в нашей программе указатель вместо массива, потому что указатель переменной может увеличиваться, в отличие от имени массива, которое не может быть увеличено, потому что это постоянный указатель. Следующая программа увеличивает указатель переменной для доступа к каждому последующему элементу массива:

import std.stdio; 
 
const int MAX = 3; 
 
void main () { 
   int var[MAX] = [10, 100, 200]; 
   int *ptr = &var[0];  

   for (int i = 0; i < MAX; i++, ptr++) { 
      writeln("Address of var[" , i , "] = ",ptr); 
      writeln("Value of var[" , i , "] = ",*ptr); 
   } 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Address of var[0] = 18FDBC 
Value of var[0] = 10 
Address of var[1] = 18FDC0 
Value of var[1] = 100 
Address of var[2] = 18FDC4 
Value of var[2] = 200

Указатели против массива

Указатели и массивы сильно связаны. Однако указатели и массивы не полностью взаимозаменяемы. Например, рассмотрим следующую программу -

import std.stdio; 
 
const int MAX = 3;
  
void main () { 
   int var[MAX] = [10, 100, 200]; 
   int *ptr = &var[0]; 
   var.ptr[2]  = 290; 
   ptr[0] = 220;  
   
   for (int i = 0; i < MAX; i++, ptr++) { 
      writeln("Address of var[" , i , "] = ",ptr); 
      writeln("Value of var[" , i , "] = ",*ptr); 
   } 
}

В приведенной выше программе вы можете увидеть var.ptr [2] для установки второго элемента и ptr [0], который используется для установки нулевого элемента. Оператор увеличения можно использовать с ptr, но не с var.

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Address of var[0] = 18FDBC 
Value of var[0] = 220 
Address of var[1] = 18FDC0 
Value of var[1] = 100 
Address of var[2] = 18FDC4 
Value of var[2] = 290

Указатель на указатель

Указатель на указатель - это форма множественного косвенного обращения или цепочка указателей. Обычно указатель содержит адрес переменной. Когда мы определяем указатель на указатель, первый указатель содержит адрес второго указателя, который указывает на место, которое содержит фактическое значение, как показано ниже.

Переменная, являющаяся указателем на указатель, должна быть объявлена ​​как таковая. Это делается путем добавления дополнительной звездочки перед его названием. Например, следующий синтаксис для объявления указателя на указатель типа int:

int **var;

Когда на целевое значение косвенно указывает указатель на указатель, то для доступа к этому значению необходимо дважды применить оператор звездочки, как показано ниже в примере:

import std.stdio;  

const int MAX = 3;
  
void main () { 
   int var = 3000; 
   writeln("Value of var :" , var); 
   
   int *ptr = &var; 
   writeln("Value available at *ptr :" ,*ptr); 
   
   int **pptr = &ptr; 
   writeln("Value available at **pptr :",**pptr); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Value of var :3000 
Value available at *ptr :3000 
Value available at **pptr :3000

Передача указателя на функции

D позволяет передавать указатель на функцию. Для этого он просто объявляет параметр функции как тип указателя.

В следующем простом примере передается указатель на функцию.

import std.stdio; 
 
void main () { 
   // an int array with 5 elements. 
   int balance[5] = [1000, 2, 3, 17, 50]; 
   double avg; 
   
   avg = getAverage( &balance[0], 5 ) ; 
   writeln("Average is :" , avg); 
} 
 
double getAverage(int *arr, int size) { 
   int    i; 
   double avg, sum = 0; 
   
   for (i = 0; i < size; ++i) {
      sum += arr[i]; 
   } 
   
   avg = sum/size; 
   return avg; 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Average is :214.4

Указатель возврата из функций

Рассмотрим следующую функцию, которая возвращает 10 чисел с помощью указателя, что означает адрес первого элемента массива.

import std.stdio;
  
void main () { 
   int *p = getNumber(); 
   
   for ( int i = 0; i < 10; i++ ) { 
      writeln("*(p + " , i , ") : ",*(p + i)); 
   } 
} 
 
int * getNumber( ) { 
   static int r [10]; 
   
   for (int i = 0; i < 10; ++i) {
      r[i] = i; 
   }
   
   return &r[0]; 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

*(p + 0) : 0 
*(p + 1) : 1 
*(p + 2) : 2 
*(p + 3) : 3 
*(p + 4) : 4 
*(p + 5) : 5 
*(p + 6) : 6 
*(p + 7) : 7 
*(p + 8) : 8 
*(p + 9) : 9

Указатель на массив

Имя массива - это постоянный указатель на первый элемент массива. Поэтому в декларации -

double balance[50];

balanceявляется указателем на & balance [0], который является адресом первого элемента массива balance. Таким образом, следующий фрагмент программы назначаетp адрес первого элемента balance -

double *p; 
double balance[10]; 
 
p = balance;

Допустимо использовать имена массивов в качестве указателей на константы, и наоборот. Следовательно, * (баланс + 4) - это законный способ доступа к данным на балансе [4].

Как только вы сохраните адрес первого элемента в p, вы можете получить доступ к элементам массива, используя * p, * (p + 1), * (p + 2) и так далее. В следующем примере показаны все концепции, обсужденные выше -

import std.stdio;
 
void main () { 
   // an array with 5 elements. 
   double balance[5] = [1000.0, 2.0, 3.4, 17.0, 50.0]; 
   double *p;  
   
   p = &balance[0]; 
  
   // output each array element's value  
   writeln("Array values using pointer " ); 
   
   for ( int i = 0; i < 5; i++ ) { 
      writeln( "*(p + ", i, ") : ", *(p + i)); 
   } 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Array values using pointer  
*(p + 0) : 1000 
*(p + 1) : 2 
*(p + 2) : 3.4 
*(p + 3) : 17
*(p + 4) : 50

Кортежи используются для объединения нескольких значений в один объект. Кортежи содержат последовательность элементов. Элементами могут быть типы, выражения или псевдонимы. Число и элементы кортежа фиксируются во время компиляции и не могут быть изменены во время выполнения.

Кортежи имеют характеристики как структур, так и массивов. Элементы кортежа могут быть разных типов, например структуры. Доступ к элементам можно получить через индексацию, как массивы. Они реализованы как функция библиотеки с помощью шаблона Tuple из модуля std.typecons. Tuple использует TypeTuple из модуля std.typetuple для некоторых своих операций.

Кортеж с использованием кортежа ()

Кортежи могут быть созданы функцией tuple (). Доступ к членам кортежа осуществляется по значениям индекса. Пример показан ниже.

пример

import std.stdio; 
import std.typecons; 
 
void main() { 
   auto myTuple = tuple(1, "Tuts"); 
   writeln(myTuple); 
   writeln(myTuple[0]); 
   writeln(myTuple[1]); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Tuple!(int, string)(1, "Tuts") 
1 
Tuts

Кортеж с использованием шаблона кортежа

Tuple также может быть создан непосредственно шаблоном Tuple вместо функции tuple (). Тип и имя каждого члена указываются как два последовательных параметра шаблона. Доступ к членам можно получить по свойствам, если они созданы с использованием шаблонов.

import std.stdio; 
import std.typecons; 

void main() { 
   auto myTuple = Tuple!(int, "id",string, "value")(1, "Tuts"); 
   writeln(myTuple);  
   
   writeln("by index 0 : ", myTuple[0]); 
   writeln("by .id : ", myTuple.id); 
   
   writeln("by index 1 : ", myTuple[1]); 
   writeln("by .value ", myTuple.value); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат

Tuple!(int, "id", string, "value")(1, "Tuts") 
by index 0 : 1 
by .id : 1 
by index 1 : Tuts 
by .value Tuts

Расширение параметров свойств и функций

Члены Tuple можно расширить с помощью свойства .expand или путем нарезки. Это расширенное / нарезанное значение можно передать как список аргументов функции. Пример показан ниже.

пример

import std.stdio; 
import std.typecons;
 
void method1(int a, string b, float c, char d) { 
   writeln("method 1 ",a,"\t",b,"\t",c,"\t",d); 
}
 
void method2(int a, float b, char c) { 
   writeln("method 2 ",a,"\t",b,"\t",c); 
}
 
void main() { 
   auto myTuple = tuple(5, "my string", 3.3, 'r'); 
   
   writeln("method1 call 1"); 
   method1(myTuple[]); 
   
   writeln("method1 call 2"); 
   method1(myTuple.expand); 
   
   writeln("method2 call 1"); 
   method2(myTuple[0], myTuple[$-2..$]); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

method1 call 1 
method 1 5 my string 3.3 r
method1 call 2 
method 1 5 my string 3.3 r 
method2 call 1 
method 2 5 3.3 r

Тип: пара

TypeTuple определяется в модуле std.typetuple. Список значений и типов, разделенных запятыми. Ниже приведен простой пример использования TypeTuple. TypeTuple используется для создания списка аргументов, списка шаблонов и списка литералов массива.

import std.stdio; 
import std.typecons; 
import std.typetuple; 
 
alias TypeTuple!(int, long) TL;  

void method1(int a, string b, float c, char d) { 
   writeln("method 1 ",a,"\t",b,"\t",c,"\t",d); 
} 

void method2(TL tl) { 
   writeln(tl[0],"\t", tl[1] ); 
} 
 
void main() { 
   auto arguments = TypeTuple!(5, "my string", 3.3,'r');  
   method1(arguments); 
   method2(5, 6L);  
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

method 1 5 my string 3.3 r 
5     6

В structure - еще один определяемый пользователем тип данных, доступный в программировании на языке D, который позволяет комбинировать элементы данных разных типов.

Структуры используются для представления записи. Предположим, вы хотите отслеживать свои книги в библиотеке. Возможно, вы захотите отслеживать следующие атрибуты каждой книги -

  • Title
  • Author
  • Subject
  • Идентификатор книги

Определение структуры

Чтобы определить структуру, вы должны использовать structзаявление. Оператор struct определяет новый тип данных с более чем одним членом для вашей программы. Формат оператора структуры следующий -

struct [structure tag] { 
   member definition; 
   member definition; 
   ... 
   member definition; 
} [one or more structure variables];

В structure tagявляется необязательным, и каждое определение члена является обычным определением переменной, например int i; или float f; или любое другое допустимое определение переменной. В конце определения структуры перед точкой с запятой вы можете указать одну или несколько переменных структуры, которые являются необязательными. Вот как бы вы объявили структуру Книги -

struct Books {
   char [] title;
   char [] author;
   char [] subject;
   int   book_id;
};

Доступ к членам структуры

Для доступа к любому члену структуры вы используете member access operator (.). Оператор доступа к члену кодируется как точка между именем переменной структуры и элементом структуры, к которому мы хотим получить доступ. Вы бы использовалиstructключевое слово для определения переменных структурного типа. В следующем примере объясняется использование структуры -

import std.stdio; 
 
struct Books { 
   char [] title; 
   char [] author; 
   char [] subject; 
   int   book_id; 
}; 
 
void main( ) { 
   Books Book1;        /* Declare Book1 of type Book */ 
   Books Book2;        /* Declare Book2 of type Book */ 
   
   /* book 1 specification */ 
   Book1.title = "D Programming".dup; 
   Book1.author = "Raj".dup; 
   Book1.subject = "D Programming Tutorial".dup;
   Book1.book_id = 6495407; 
   
   /* book 2 specification */ 
   Book2.title = "D Programming".dup; 
   Book2.author = "Raj".dup; 
   Book2.subject = "D Programming Tutorial".dup; 
   Book2.book_id = 6495700; 
   
   /* print Book1 info */ 
   writeln( "Book 1 title : ", Book1.title); 
   writeln( "Book 1 author : ", Book1.author); 
   writeln( "Book 1 subject : ", Book1.subject); 
   writeln( "Book 1 book_id : ", Book1.book_id);  
   
   /* print Book2 info */ 
   writeln( "Book 2 title : ", Book2.title); 
   writeln( "Book 2 author : ", Book2.author); 
   writeln( "Book 2 subject : ", Book2.subject); 
   writeln( "Book 2 book_id : ", Book2.book_id); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Book 1 title : D Programming 
Book 1 author : Raj 
Book 1 subject : D Programming Tutorial 
Book 1 book_id : 6495407 
Book 2 title : D Programming 
Book 2 author : Raj 
Book 2 subject : D Programming Tutorial 
Book 2 book_id : 6495700

Структуры как аргументы функций

Вы можете передать структуру в качестве аргумента функции так же, как и любую другую переменную или указатель. Вы можете получить доступ к структурным переменным так же, как в приведенном выше примере:

import std.stdio;

struct Books { 
   char [] title; 
   char [] author; 
   char [] subject; 
   int   book_id; 
}; 
 
void main( ) { 
   Books Book1;        /* Declare Book1 of type Book */ 
   Books Book2;        /* Declare Book2 of type Book */  
   
   /* book 1 specification */ 
   Book1.title = "D Programming".dup; 
   Book1.author = "Raj".dup; 
   Book1.subject = "D Programming Tutorial".dup; 
   Book1.book_id = 6495407;  
   
   /* book 2 specification */ 
   Book2.title = "D Programming".dup; 
   Book2.author = "Raj".dup; 
   Book2.subject = "D Programming Tutorial".dup; 
   Book2.book_id = 6495700;  
   
   /* print Book1 info */ 
   printBook( Book1 );  
   
   /* Print Book2 info */ 
   printBook( Book2 );  
}
 
void printBook( Books book ) { 
   writeln( "Book title : ", book.title); 
   writeln( "Book author : ", book.author); 
   writeln( "Book subject : ", book.subject); 
   writeln( "Book book_id : ", book.book_id); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Book title : D Programming 
Book author : Raj 
Book subject : D Programming Tutorial 
Book book_id : 6495407 
Book title : D Programming 
Book author : Raj
Book subject : D Programming Tutorial 
Book book_id : 6495700

Инициализация структур

Структуры могут быть инициализированы в двух формах: одна с использованием конструктора, а другая с использованием формата {}. Пример показан ниже.

пример

import std.stdio;

struct Books { 
   char [] title; 
   char [] subject = "Empty".dup; 
   int   book_id = -1; 
   char [] author = "Raj".dup;  
}; 
 
void main( ) { 
   Books Book1 = Books("D Programming".dup, "D Programming Tutorial".dup, 6495407 ); 
   printBook( Book1 ); 
   
   Books Book2 = Books("D Programming".dup, 
      "D Programming Tutorial".dup, 6495407,"Raj".dup ); 
   printBook( Book2 );
   
   Books Book3 =  {title:"Obj C programming".dup, book_id : 1001};
   printBook( Book3 ); 
}
  
void printBook( Books book ) { 
   writeln( "Book title : ", book.title); 
   writeln( "Book author : ", book.author); 
   writeln( "Book subject : ", book.subject); 
   writeln( "Book book_id : ", book.book_id); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Book title : D Programming 
Book author : Raj 
Book subject : D Programming Tutorial 
Book book_id : 6495407 
Book title : D Programming 
Book author : Raj 
Book subject : D Programming Tutorial 
Book book_id : 6495407 
Book title : Obj C programming 
Book author : Raj 
Book subject : Empty 
Book book_id : 1001

Статические члены

Статические переменные инициализируются только один раз. Например, чтобы иметь уникальные идентификаторы для книг, мы можем сделать book_id статическим и увеличить идентификатор книги. Пример показан ниже.

пример

import std.stdio;  

struct Books { 
   char [] title; 
   char [] subject = "Empty".dup; 
   int   book_id; 
   char [] author = "Raj".dup; 
   static int id = 1000; 
}; 
 
void main( ) { 
   Books Book1 = Books("D Programming".dup, "D Programming Tutorial".dup,++Books.id ); 
   printBook( Book1 );  
   
   Books Book2 = Books("D Programming".dup, "D Programming Tutorial".dup,++Books.id); 
   printBook( Book2 );  
   
   Books Book3 =  {title:"Obj C programming".dup, book_id:++Books.id}; 
   printBook( Book3 ); 
}
  
void printBook( Books book ) { 
   writeln( "Book title : ", book.title); 
   writeln( "Book author : ", book.author); 
   writeln( "Book subject : ", book.subject); 
   writeln( "Book book_id : ", book.book_id); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Book title : D Programming 
Book author : Raj 
Book subject : D Programming Tutorial 
Book book_id : 1001 
Book title : D Programming 
Book author : Raj 
Book subject : D Programming Tutorial 
Book book_id : 1002 
Book title : Obj C programming 
Book author : Raj 
Book subject : Empty 
Book book_id : 1003

А union- это особый тип данных, доступный в D, который позволяет хранить различные типы данных в одной и той же области памяти. Вы можете определить объединение с множеством членов, но только один член может содержать значение в любой момент времени. Объединения обеспечивают эффективный способ использования одной и той же области памяти для нескольких целей.

Определение союза в D

Чтобы определить объединение, вы должны использовать оператор union так же, как вы это делали при определении структуры. Оператор union определяет новый тип данных с более чем одним членом для вашей программы. Формат заявления объединения следующий -

union [union tag] { 
   member definition; 
   member definition; 
   ... 
   member definition; 
} [one or more union variables];

В union tagявляется необязательным, и каждое определение члена является обычным определением переменной, например int i; или float f; или любое другое допустимое определение переменной. В конце определения объединения, перед последней точкой с запятой, вы можете указать одну или несколько переменных объединения, но это необязательно. Вот как вы могли бы определить тип объединения с именем Data, который имеет три членаi, f, и str -

union Data { 
   int i; 
   float f; 
   char str[20]; 
} data;

Переменная Datatype может хранить целое число, число с плавающей запятой или строку символов. Это означает, что одну переменную (одно и то же место в памяти) можно использовать для хранения нескольких типов данных. Вы можете использовать любые встроенные или определенные пользователем типы данных внутри объединения в зависимости от ваших требований.

Память, занимаемая союзом, будет достаточно большой, чтобы вместить самого большого члена союза. Например, в приведенном выше примере тип данных будет занимать 20 байтов в памяти, потому что это максимальное пространство, которое может быть занято строкой символов. В следующем примере отображается общий размер памяти, занятой указанным выше объединением -

import std.stdio; 
  
union Data { 
   int i; 
   float f; 
   char str[20]; 
}; 
  
int main( ) { 
   Data data; 

   writeln( "Memory size occupied by data : ", data.sizeof);

   return 0; 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Memory size occupied by data : 20

Доступ к членам Союза

Чтобы получить доступ к любому члену союза, мы используем member access operator (.). Оператор доступа к члену кодируется как точка между именем переменной объединения и членом объединения, к которому мы хотим получить доступ. Вы можете использовать ключевое слово union для определения переменных типа union.

пример

В следующем примере объясняется использование объединения -

import std.stdio;

union Data { 
   int i; 
   float f; 
   char str[13]; 
};  

void main( ) { 
   Data data; 
   
   data.i = 10; 
   data.f = 220.5; 
   
   data.str = "D Programming".dup; 
   writeln( "size of : ", data.sizeof); 
   writeln( "data.i : ", data.i); 
   writeln( "data.f : ", data.f); 
   writeln( "data.str : ", data.str); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

size of : 16 
data.i : 1917853764 
data.f : 4.12236e+30 
data.str : D Programming

Здесь вы можете увидеть, что значения i и f члены union были повреждены, потому что окончательное значение, присвоенное переменной, заняло место в памяти, и это причина того, что значение str член печатается очень хорошо.

Теперь давайте снова рассмотрим тот же пример, где мы будем использовать по одной переменной за раз, что является основной целью объединения -

Измененный пример

import std.stdio;

union Data { 
   int i; 
   float f; 
   char str[13]; 
};  
void main( ) { 
   Data data; 
   writeln( "size of : ", data.sizeof);  
   
   data.i = 10; 
   writeln( "data.i : ", data.i); 
   
   data.f = 220.5; 
   writeln( "data.f : ", data.f);  
   
   data.str = "D Programming".dup; 
   writeln( "data.str : ", data.str); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

size of : 16 
data.i : 10 
data.f : 220.5 
data.str : D Programming

Здесь все члены печатаются очень хорошо, потому что одновременно используется один член.

Диапазоны - это абстракция доступа к элементам. Эта абстракция позволяет использовать большое количество алгоритмов над большим количеством типов контейнеров. Диапазоны подчеркивают, как осуществляется доступ к элементам контейнера, в отличие от того, как контейнеры реализованы. Диапазоны - это очень простая концепция, основанная на том, определяет ли тип определенные наборы функций-членов.

Диапазоны являются неотъемлемой частью срезов D. D являются реализациями самого мощного диапазона RandomAccessRange, и в Phobos есть много функций диапазона. Многие алгоритмы Phobos возвращают объекты временного диапазона. Например, filter () выбирает элементы, которые больше 10 в следующем коде, фактически возвращает объект диапазона, а не массив.

Числовые диапазоны

Числовые диапазоны используются довольно часто, и эти числовые диапазоны имеют тип int. Несколько примеров для диапазонов номеров показаны ниже -

// Example 1 
foreach (value; 3..7)  

// Example 2 
int[] slice = array[5..10];

Фобос хребты

Диапазоны, относящиеся к структурам и интерфейсам классов, - это диапазоны phobos. Phobos - это официальная среда выполнения и стандартная библиотека, которая поставляется с компилятором языка D.

Существуют различные типы диапазонов, которые включают:

  • InputRange
  • ForwardRange
  • BidirectionalRange
  • RandomAccessRange
  • OutputRange

InputRange

Самый простой диапазон - это диапазон ввода. Другие диапазоны предъявляют дополнительные требования к диапазону, на котором они основаны. Для InputRange требуются три функции:

  • empty- Указывает, пуст ли диапазон; он должен возвращать истину, если диапазон считается пустым; иначе ложь.

  • front - Обеспечивает доступ к элементу в начале диапазона.

  • popFront() - Он сокращает диапазон с самого начала, удаляя первый элемент.

пример

import std.stdio; 
import std.string; 
 
struct Student { 
   string name; 
   int number; 
   
   string toString() const { 
      return format("%s(%s)", name, number); 
   } 
}
  
struct School { 
   Student[] students; 
}
struct StudentRange {
   Student[] students; 
   
   this(School school) { 
      this.students = school.students; 
   } 
   @property bool empty() const { 
      return students.length == 0; 
   } 
   @property ref Student front() { 
      return students[0]; 
   } 
   void popFront() { 
      students = students[1 .. $]; 
   } 
}

void main() { 
   auto school = School([ Student("Raj", 1), Student("John", 2), Student("Ram", 3)]);
   auto range = StudentRange(school); 
   writeln(range);  
   
   writeln(school.students.length);
   
   writeln(range.front); 
   
   range.popFront;  
   
   writeln(range.empty); 
   writeln(range); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

[Raj(1), John(2), Ram(3)] 
3 
Raj(1) 
false 
[John(2), Ram(3)]

ForwardRange

ForwardRange дополнительно требует, чтобы часть функции-члена сохранения из трех других функций InputRange возвращала копию диапазона при вызове функции сохранения.

import std.array; 
import std.stdio; 
import std.string; 
import std.range;

struct FibonacciSeries { 
   int first = 0; 
   int second = 1; 
   enum empty = false;   //  infinite range  
   
   @property int front() const { 
      return first; 
   } 
   void popFront() { 
      int third = first + second; 
      first = second; 
      second = third; 
   }
   @property FibonacciSeries save() const { 
      return this; 
   } 
}
  
void report(T)(const dchar[] title, const ref T range) {
   writefln("%s: %s", title, range.take(5)); 
} 

void main() { 
   auto range = FibonacciSeries(); 
   report("Original range", range);
   
   range.popFrontN(2); 
   report("After removing two elements", range); 
   
   auto theCopy = range.save; 
   report("The copy", theCopy);
   
   range.popFrontN(3); 
   report("After removing three more elements", range); 
   report("The copy", theCopy); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Original range: [0, 1, 1, 2, 3] 
After removing two elements: [1, 2, 3, 5, 8] 
The copy: [1, 2, 3, 5, 8] 
After removing three more elements: [5, 8, 13, 21, 34] 
The copy: [1, 2, 3, 5, 8]

BidirectionalRange

BidirectionalRange дополнительно предоставляет две функции-члены по сравнению с функциями-членами ForwardRange. Функция back, аналогичная функции front, обеспечивает доступ к последнему элементу диапазона. Функция popBack похожа на функцию popFront и удаляет последний элемент из диапазона.

пример

import std.array; 
import std.stdio; 
import std.string; 

struct Reversed { 
   int[] range; 
   
   this(int[] range) { 
      this.range = range; 
   } 
   @property bool empty() const { 
      return range.empty; 
   }
   @property int front() const { 
      return range.back;  //  reverse 
   }
   @property int back() const { 
      return range.front; // reverse 
   } 
   void popFront() { 
      range.popBack(); 
   }
   void popBack() { 
      range.popFront(); 
   } 
} 
 
void main() { 
   writeln(Reversed([ 1, 2, 3])); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

[3, 2, 1]

Бесконечный RandomAccessRange

opIndex () дополнительно требуется по сравнению с ForwardRange. Кроме того, значение пустой функции должно быть известно во время компиляции как false. Ниже показан простой пример, поясняющий диапазон квадратов.

import std.array; 
import std.stdio; 
import std.string; 
import std.range; 
import std.algorithm; 

class SquaresRange { 
   int first;  
   this(int first = 0) { 
      this.first = first; 
   }
   enum empty = false; 
   @property int front() const { 
      return opIndex(0); 
   }
   void popFront() { 
      ++first; 
   }
   @property SquaresRange save() const { 
      return new SquaresRange(first); 
   }
   int opIndex(size_t index) const { 
      /* This function operates at constant time */ 
      immutable integerValue = first + cast(int)index; 
      return integerValue * integerValue; 
   } 
}
  
bool are_lastTwoDigitsSame(int value) { 
   /* Must have at least two digits */ 
   if (value < 10) { 
      return false; 
   } 
   
   /* Last two digits must be divisible by 11 */ 
   immutable lastTwoDigits = value % 100; 
   return (lastTwoDigits % 11) == 0; 
} 
 
void main() { 
   auto squares = new SquaresRange(); 
   
   writeln(squares[5]);
   
   writeln(squares[10]); 
   
   squares.popFrontN(5); 
   writeln(squares[0]); 
   
   writeln(squares.take(50).filter!are_lastTwoDigitsSame); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

25 
100 
25 
[100, 144, 400, 900, 1444, 1600, 2500]

Конечный RandomAccessRange

opIndex () и длина требуются дополнительно по сравнению с двунаправленным диапазоном. Это объясняется с помощью подробного примера, который использует ранее использованные ряд Фибоначчи и диапазон квадратов. Этот пример хорошо работает с обычным компилятором D, но не работает с онлайн-компилятором.

пример

import std.array; 
import std.stdio; 
import std.string; 
import std.range; 
import std.algorithm; 

struct FibonacciSeries { 
   int first = 0; 
   int second = 1; 
   enum empty = false;   //  infinite range  
   
   @property int front() const { 
      return first;
   }
   void popFront() { 
      int third = first + second; 
      first = second; 
      second = third; 
   }
   @property FibonacciSeries save() const { 
      return this; 
   } 
}
  
void report(T)(const dchar[] title, const ref T range) { 
   writefln("%40s: %s", title, range.take(5)); 
}
  
class SquaresRange { 
   int first;  
   this(int first = 0) { 
      this.first = first; 
   } 
   enum empty = false; 
   @property int front() const { 
      return opIndex(0); 
   }
   void popFront() { 
      ++first; 
   }
   @property SquaresRange save() const { 
      return new SquaresRange(first); 
   } 
   int opIndex(size_t index) const { 
      /* This function operates at constant time */ 
      immutable integerValue = first + cast(int)index; 
      return integerValue * integerValue; 
   } 
}
  
bool are_lastTwoDigitsSame(int value) { 
   /* Must have at least two digits */ 
   if (value < 10) { 
      return false; 
   }
   
   /* Last two digits must be divisible by 11 */ 
   immutable lastTwoDigits = value % 100; 
   return (lastTwoDigits % 11) == 0; 
}
  
struct Together { 
   const(int)[][] slices;  
   this(const(int)[][] slices ...) { 
      this.slices = slices.dup;  
      clearFront(); 
      clearBack(); 
   }
   private void clearFront() { 
      while (!slices.empty && slices.front.empty) { 
         slices.popFront(); 
      } 
   } 
   private void clearBack() { 
      while (!slices.empty && slices.back.empty) { 
         slices.popBack(); 
      } 
   }
   @property bool empty() const { 
      return slices.empty; 
   } 
   @property int front() const { 
      return slices.front.front; 
   }
   void popFront() { 
      slices.front.popFront(); 
      clearFront(); 
   }
   @property Together save() const { 
      return Together(slices.dup); 
   } 
   @property int back() const { 
      return slices.back.back; 
   } 
   void popBack() { 
      slices.back.popBack(); 
      clearBack(); 
   }
   @property size_t length() const { 
      return reduce!((a, b) => a + b.length)(size_t.init, slices); 
   }
   int opIndex(size_t index) const { 
      /* Save the index for the error message */ 
      immutable originalIndex = index;  

      foreach (slice; slices) { 
         if (slice.length > index) { 
            return slice[index];  
         } else { 
            index -= slice.length; 
         } 
      } 
      throw new Exception( 
         format("Invalid index: %s (length: %s)", originalIndex, this.length));
   } 
}
void main() { 
   auto range = Together(FibonacciSeries().take(10).array, [ 777, 888 ],
      (new SquaresRange()).take(5).array); 
   writeln(range.save); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 777, 888, 0, 1, 4, 9, 16]

OutputRange

OutputRange представляет потоковый вывод элемента, аналогично отправке символов в стандартный вывод. OutputRange требует поддержки операции put (range, element). put () - это функция, определенная в модуле std.range. Он определяет возможности диапазона и элемента во время компиляции и использует наиболее подходящий метод для вывода элементов. Ниже показан простой пример.

import std.algorithm; 
import std.stdio; 
 
struct MultiFile { 
   string delimiter;
   File[] files;
   
   this(string delimiter, string[] fileNames ...) { 
      this.delimiter = delimiter; 

      /* stdout is always included */ 
      this.files ~= stdout; 

      /* A File object for each file name */ 
      foreach (fileName; fileNames) { 
         this.files ~= File(fileName, "w"); 
      } 
   }
   void put(T)(T element) { 
      foreach (file; files) { 
         file.write(element, delimiter); 
      } 
   }
}
void main() { 
   auto output = MultiFile("\n", "output_0", "output_1"); 
   copy([ 1, 2, 3], output);  
   copy([ "red", "blue", "green" ], output); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

[1, 2, 3] 
["red", "blue", "green"]

Псевдоним, как следует из названия, обеспечивает альтернативное имя для существующих имен. Синтаксис псевдонима показан ниже.

alias new_name = existing_name;

Ниже приводится более старый синтаксис, на всякий случай, если вы обратитесь к некоторым более старым примерам формата. Настоятельно не рекомендуется использовать это.

alias existing_name new_name;

Существует также другой синтаксис, который используется с выражением, и он приведен ниже, в котором мы можем напрямую использовать имя псевдонима вместо выражения.

alias expression alias_name ;

Как вы, возможно, знаете, typedef добавляет возможность создавать новые типы. Псевдоним может выполнять работу typedef и даже больше. Ниже показан простой пример использования псевдонима, в котором используется заголовок std.conv, который обеспечивает возможность преобразования типов.

import std.stdio; 
import std.conv:to; 
 
alias to!(string) toString;  

void main() { 
   int a = 10;  
   string s = "Test"~toString(a); 
   writeln(s); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Test10

В приведенном выше примере вместо использования to! String (a) мы присвоили ему псевдоним toString, что сделало его более удобным и простым для понимания.

Псевдоним для кортежа

Давайте посмотрим на другой пример, где мы можем установить псевдоним для кортежа.

import std.stdio; 
import std.typetuple; 
 
alias TypeTuple!(int, long) TL; 
 
void method1(TL tl) { 
   writeln(tl[0],"\t", tl[1] ); 
} 
 
void main() { 
   method1(5, 6L);    
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

5	6

В приведенном выше примере кортеж типа назначается переменной псевдонима, что упрощает определение метода и доступ к переменным. Такой доступ еще более полезен, когда мы пытаемся повторно использовать такие кортежи типов.

Псевдоним для типов данных

Часто мы можем определять общие типы данных, которые необходимо использовать во всем приложении. Когда несколько программистов кодируют приложение, могут быть случаи, когда один человек использует int, другой - double и так далее. Чтобы избежать таких конфликтов, мы часто используем типы для типов данных. Ниже показан простой пример.

пример

import std.stdio;
  
alias int myAppNumber; 
alias string myAppString;  

void main() { 
   myAppNumber i = 10; 
   myAppString s = "TestString"; 
   
   writeln(i,s);   
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

10TestString

Псевдоним для переменных класса

Часто возникает требование, когда нам нужно получить доступ к переменным-членам суперкласса в подклассе, это можно сделать с помощью псевдонима, возможно, под другим именем.

Если вы плохо знакомы с концепцией классов и наследования, ознакомьтесь с руководством по классам и наследованию, прежде чем начинать с этого раздела.

пример

Ниже показан простой пример.

import std.stdio; 
 
class Shape { 
   int area; 
}
  
class Square : Shape { 
   string name() const @property { 
      return "Square"; 
   } 
   alias Shape.area squareArea; 
}
   
void main() { 
   auto square = new Square;  
   square.squareArea = 42;  
   writeln(square.name); 
   writeln(square.squareArea); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Square 
42

Псевдоним Это

Псевдоним обеспечивает возможность автоматического преобразования типов в пользовательские типы. Синтаксис показан ниже, где псевдоним ключевых слов и this написаны с обеих сторон переменной-члена или функции-члена.

alias member_variable_or_member_function this;

пример

Ниже показан пример, демонстрирующий силу псевдонима this.

import std.stdio;
  
struct Rectangle { 
   long length; 
   long breadth;  
   
   double value() const @property { 
      return cast(double) length * breadth; 
   }
   alias value this; 
} 
double volume(double rectangle, double height) {
   return rectangle * height; 
}
  
void main() { 
   auto rectangle = Rectangle(2, 3);  
   writeln(volume(rectangle, 5)); 
}

В приведенном выше примере вы можете видеть, что прямоугольник структуры преобразуется в значение типа double с помощью псевдонима этого метода.

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

30

Миксины - это структуры, которые позволяют смешивать сгенерированный код с исходным кодом. Миксины бывают следующих типов -

  • Струнные миксины
  • Шаблоны миксинов
  • Пространства имен Mixin

Струнные миксины

D имеет возможность вставлять код в виде строки, если эта строка известна во время компиляции. Синтаксис строковых миксинов показан ниже -

mixin (compile_time_generated_string)

пример

Ниже показан простой пример строковых миксинов.

import std.stdio; 
 
void main() { 
   mixin(`writeln("Hello World!");`); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Hello World!

Вот еще один пример, в котором мы можем передать строку во время компиляции, чтобы миксины могли использовать функции для повторного использования кода. Это показано ниже.

import std.stdio;

string print(string s) {
   return `writeln("` ~ s ~ `");`; 
}
  
void main() { 
   mixin (print("str1")); 
   mixin (print("str2")); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

str1
str2

Шаблоны миксинов

Шаблоны D определяют общие шаблоны кода, чтобы компилятор мог создавать фактические экземпляры из этого шаблона. Шаблоны могут генерировать функции, структуры, объединения, классы, интерфейсы и любой другой допустимый код D. Синтаксис шаблонных миксинов показан ниже.

mixin a_template!(template_parameters)

Ниже показан простой пример строковых миксинов, где мы создаем шаблон с классом Department и миксином, инстанцирующим шаблон и, следовательно, делая функции setName и printNames доступными для структурного колледжа.

пример

import std.stdio;

template Department(T, size_t count) { 
   T[count] names;  
   void setName(size_t index, T name) { 
      names[index] = name; 
   } 
   
   void printNames() { 
      writeln("The names");  
      
      foreach (i, name; names) { 
         writeln(i," : ", name); 
      }
   }
}
 
struct College { 
   mixin Department!(string, 2); 
}
  
void main() { 
   auto college = College();  
   college.setName(0, "name1"); 
   college.setName(1, "name2");  
   college.printNames(); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

The names 
0 : name1 
1 : name2

Пространства имен миксинов

Пространства имен миксинов используются, чтобы избежать двусмысленности в миксинах шаблонов. Например, может быть две переменные, одна определена явно в main, а другая смешанная. Когда смешанное имя совпадает с именем, которое находится в окружающей области, тогда имя, которое находится в окружающей области, получает используемый. Этот пример показан ниже.

пример

import std.stdio;

template Person() { 
   string name; 
   
   void print() { 
      writeln(name); 
   } 
}

void main() { 
   string name; 
   
   mixin Person a; 
   name = "name 1"; 
   writeln(name); 
   
   a.name = "name 2"; 
   print(); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

name 1 
name 2

Модули - это строительные блоки D. Они основаны на простой концепции. Каждый исходный файл - это модуль. Соответственно, отдельные файлы, в которых мы пишем программы, являются отдельными модулями. По умолчанию имя модуля совпадает с его именем файла без расширения .d.

При явном указании имя модуля определяется ключевым словом module, которое должно появиться как первая строка без комментариев в исходном файле. Например, предположим, что имя исходного файла - «employee.d». Затем имя модуля указывается ключевым словом module, за которым следует сотрудник . Это как показано ниже.

module employee;

class Employee {
   // Class definition goes here. 
}

Строка модуля не является обязательной. Если не указано, это то же самое, что имя файла без расширения .d.

Имена файлов и модулей

D поддерживает Unicode в исходном коде и именах модулей. Однако поддержка Unicode файловыми системами различается. Например, хотя большинство файловых систем Linux поддерживают Unicode, имена файлов в файловых системах Windows могут не различать строчные и прописные буквы. Кроме того, большинство файловых систем ограничивают символы, которые могут использоваться в именах файлов и каталогов. По причинам переносимости я рекомендую использовать только строчные буквы ASCII в именах файлов. Например, «employee.d» будет подходящим именем файла для класса с именем employee.

Соответственно, имя модуля также будет состоять из букв ASCII -

module employee;  // Module name consisting of ASCII letters 

class eëmployëë { }

D Пакеты

Комбинация связанных модулей называется пакетом. Пакеты D также представляют собой простую концепцию: исходные файлы, находящиеся в одном каталоге, считаются принадлежащими одному пакету. Имя каталога становится именем пакета, которое также необходимо указывать в качестве первых частей имен модулей.

Например, если «employee.d» и «office.d» находятся внутри каталога «company», то указание имени каталога вместе с именем модуля делает их частью одного пакета -

module company.employee; 
 
class Employee { }

Аналогично для офисного модуля -

module company.office; 
 
class Office { }

Поскольку имена пакетов соответствуют именам каталогов, имена пакетов для модулей, которые находятся на более глубоком уровне, чем один уровень каталога, должны отражать эту иерархию. Например, если каталог «компания» включает в себя каталог «ветвь», имя модуля внутри этого каталога также будет включать ветку.

module company.branch.employee;

Использование модулей в программах

Ключевое слово import, которое мы до сих пор использовали почти во всех программах, предназначено для введения модуля в текущий модуль -

import std.stdio;

Имя модуля также может содержать имя пакета. Например, std. часть выше указывает, что stdio - это модуль, который является частью пакета std.

Расположение модулей

Компилятор находит файлы модулей, преобразовывая имена пакетов и модулей непосредственно в имена каталогов и файлов.

Например, два модуля «сотрудник» и «офис» будут расположены как «компания / сотрудник.d» и «животное / офис.d» соответственно (или «компания \ сотрудник.d» и «компания \ офис.d», в зависимости от файловая система) для company.employee и company.office.

Длинные и короткие имена модулей

Имена, которые используются в программе, могут быть дополнены именами модулей и пакетов, как показано ниже.

import company.employee; 
auto employee0 = Employee(); 
auto employee1 = company.employee.Employee();

Длинные имена обычно не нужны, но иногда возникают конфликты имен. Например, при обращении к имени, которое появляется более чем в одном модуле, компилятор не может решить, какое из них имеется в виду. Следующая программа составляет длинные имена, чтобы различать две отдельные структуры сотрудников , которые определены в двух отдельных модулях: компания и колледж. .

Первый модуль сотрудников в папке company выглядит следующим образом.

module company.employee; 
 
import std.stdio;
  
class Employee {
   public: 
      string str; 

   void print() {
      writeln("Company Employee: ",str); 
   } 
}

Второй модуль сотрудников в папке College выглядит следующим образом.

module college.employee;
  
import std.stdio;  

class Employee {
   public: 
      string str;
	
   void print() {
      writeln("College Employee: ",str); 
   } 
}

Основной модуль в hello.d следует сохранить в папке, содержащей папки колледжа и компании. Это так.

import company.employee; 
import college.employee; 
 
import std.stdio;  

void main() {
   auto myemployee1 = new company.employee.Employee();
   myemployee1.str = "emp1"; 
   myemployee1.print();
   
   auto myemployee2 = new college.employee.Employee(); 
   myemployee2.str = "emp2"; 
   myemployee2.print(); 
}

Ключевого слова import недостаточно, чтобы модули стали частью программы. Он просто делает доступными функции модуля внутри текущего модуля. Это нужно только для компиляции кода.

Для построения вышеуказанной программы в строке компиляции также должны быть указаны «company / employee.d» и «college / employee.d».

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

$ dmd hello.d company/employee.d college/employee.d -ofhello.amx 
$ ./hello.amx 
Company Employee: emp1 
College Employee: emp2

Шаблоны - это основа общего программирования, которое предполагает написание кода способом, независимым от какого-либо конкретного типа.

Шаблон - это план или формула для создания универсального класса или функции.

Шаблоны - это функция, которая позволяет описывать код как шаблон, чтобы компилятор автоматически сгенерировал программный код. Части исходного кода могут быть оставлены компилятору для заполнения до тех пор, пока эта часть не будет фактически использована в программе. Компилятор восполняет недостающие части.

Шаблон функции

Определение функции в качестве шаблона оставляет один или несколько типов, которые она использует, как неопределенные, которые будут выведены компилятором позже. Типы, которые не указаны, определены в списке параметров шаблона, который находится между именем функции и списком параметров функции. По этой причине в шаблонах функций есть два списка параметров:

  • список параметров шаблона
  • список параметров функции
import std.stdio; 
 
void print(T)(T value) { 
   writefln("%s", value); 
}
  
void main() { 
   print(42);  
   
   print(1.2);
   
   print("test"); 
}

Если мы скомпилируем и запустим приведенный выше код, это даст следующий результат:

42 
1.2 
test

Шаблон функции с несколькими параметрами типа

Может быть несколько типов параметров. Они показаны в следующем примере.

import std.stdio;
  
void print(T1, T2)(T1 value1, T2 value2) { 
   writefln(" %s %s", value1, value2); 
}

void main() { 
   print(42, "Test");  
   
   print(1.2, 33); 
}

Если мы скомпилируем и запустим приведенный выше код, это даст следующий результат:

42 Test 
 1.2 33

Шаблоны классов

Так же, как мы можем определять шаблоны функций, мы можем определять шаблоны классов. В следующем примере определяется класс Stack и реализуются универсальные методы для выталкивания и выталкивания элементов из стека.

import std.stdio; 
import std.string; 
 
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() { 
   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); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Test2 
2 
Test1 
1

Мы часто используем изменяемые переменные, но во многих случаях изменчивость не требуется. В таких случаях можно использовать неизменяемые переменные. Ниже приведены несколько примеров, в которых можно использовать неизменяемую переменную.

  • В случае математических констант, таких как пи, которые никогда не меняются.

  • В случае массивов, где мы хотим сохранить значения, и это не требует изменения.

Неизменяемость позволяет понять, являются ли переменные неизменными или изменяемыми, гарантируя, что определенные операции не изменяют определенные переменные. Это также снижает риск некоторых типов программных ошибок. Концепция неизменяемости D представлена ​​ключевыми словами const и immutable. Хотя сами по себе два слова близки по смыслу, их обязанности в программах различны, и они иногда несовместимы.

Концепция неизменяемости D представлена ​​ключевыми словами const и immutable. Хотя сами по себе два слова близки по смыслу, их обязанности в программах различны, и они иногда несовместимы.

Типы неизменяемых переменных в D

Есть три типа определяющих переменных, которые нельзя изменить.

  • константы перечисления
  • неизменяемые переменные
  • переменные const

enum Константы в D

Константы перечисления позволяют связать значения констант с осмысленными именами. Ниже показан простой пример.

пример

import std.stdio;

enum Day{ 
   Sunday = 1, 
   Monday,
   Tuesday, 
   Wednesday, 
   Thursday, 
   Friday, 
   Saturday 
} 
 
void main() { 
   Day day; 
   day = Day.Sunday;
   
   if (day == Day.Sunday) { 
      writeln("The day is Sunday"); 
   } 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

The day is Sunday

Неизменяемые переменные в D

Неизменяемые переменные могут быть определены во время выполнения программы. Он просто указывает компилятору, что после инициализации он становится неизменным. Ниже показан простой пример.

пример

import std.stdio; 
import std.random; 
 
void main() { 
   int min = 1; 
   int max = 10; 
   
   immutable number = uniform(min, max + 1); 
   // cannot modify immutable expression number 
   // number = 34; 
   typeof(number) value = 100;  
   
   writeln(typeof(number).stringof, number); 
   writeln(typeof(value).stringof, value); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

immutable(int)4 
immutable(int)100

В приведенном выше примере вы можете увидеть, как можно передать тип данных в другую переменную и использовать stringof при печати.

Постоянные переменные в D

Постоянные переменные не могут быть изменены аналогично неизменяемым. неизменяемые переменные могут быть переданы функциям в качестве их неизменяемых параметров, поэтому рекомендуется использовать неизменяемые вместо const. Тот же пример, который использовался ранее, изменен для const, как показано ниже.

пример

import std.stdio; 
import std.random; 
 
void main() { 
   int min = 1; 
   int max = 10; 
   
   const number = uniform(min, max + 1); 
   // cannot modify const expression number| 
   // number = 34; 
   typeof(number) value = 100; 
   
   writeln(typeof(number).stringof, number); 
   writeln(typeof(value).stringof, value); 
}

Если мы скомпилируем и запустим приведенный выше код, это даст следующий результат:

const(int)7 
const(int)100

Неизменяемые параметры в D

const стирает информацию о том, является ли исходная переменная изменяемой или неизменной, и, следовательно, использование неизменяемой переменной заставляет ее передавать ей другие функции с сохранением исходного типа. Ниже показан простой пример.

пример

import std.stdio; 
 
void print(immutable int[] array) { 
   foreach (i, element; array) { 
      writefln("%s: %s", i, element); 
   } 
}
  
void main() { 
   immutable int[] array = [ 1, 2 ]; 
   print(array); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

0: 1 
1: 2

Файлы представлены в файловой структуре в std.stdio модуля. Файл представляет собой последовательность байтов, независимо от того, текстовый это файл или двоичный файл.

Язык программирования D обеспечивает доступ к функциям высокого уровня, а также к вызовам низкого уровня (уровень ОС) для обработки файлов на ваших устройствах хранения.

Открытие файлов в D

Стандартные потоки ввода и вывода stdin и stdout уже открыты при запуске программы. Они готовы к использованию. С другой стороны, файлы необходимо сначала открыть, указав имя файла и необходимые права доступа.

File file = File(filepath, "mode");

Вот, filename является строковым литералом, который вы используете для имени файла и доступа mode может иметь одно из следующих значений -

Sr.No. Режим и описание
1

r

Открывает существующий текстовый файл для чтения.

2

w

Открывает текстовый файл для записи, если он не существует, создается новый файл. Здесь ваша программа начнет писать контент с начала файла.

3

a

Открывает текстовый файл для записи в режиме добавления, если он не существует, создается новый файл. Здесь ваша программа начнет добавлять содержимое в существующее содержимое файла.

4

r+

Открывает текстовый файл для чтения и записи.

5

w+

Открывает текстовый файл для чтения и записи. Сначала он обрезает файл до нулевой длины, если он существует, в противном случае создает файл, если он не существует.

6

a+

Открывает текстовый файл для чтения и записи. Он создает файл, если он не существует. Чтение начнется сначала, но запись может быть только добавлена.

Закрытие файла в D

Чтобы закрыть файл, используйте функцию file.close (), где file содержит ссылку на файл. Прототип этой функции -

file.close();

Любой файл, который был открыт программой, должен быть закрыт, когда программа закончит использовать этот файл. В большинстве случаев файлы не нужно закрывать явно; они закрываются автоматически, когда завершаются объекты File.

Запись файла в D

file.writeln используется для записи в открытый файл.

file.writeln("hello");

import std.stdio; 
import std.file;
  
void main() { 
   File file = File("test.txt", "w"); 
   file.writeln("hello");
   file.close(); 
}

Когда приведенный выше код компилируется и выполняется, он создает новый файл test.txt в каталоге, из которого он был запущен (в рабочем каталоге программы).

Чтение файла в D

Следующий метод считывает одну строку из файла -

string s = file.readln();

Полный пример чтения и записи показан ниже.

import std.stdio; 
import std.file; 
 
void main() { 
   File file = File("test.txt", "w");
   file.writeln("hello");  
   file.close(); 
   file = File("test.txt", "r"); 
   
   string s = file.readln(); 
   writeln(s);
   
   file.close(); 
}

Когда приведенный выше код компилируется и выполняется, он считывает файл, созданный в предыдущем разделе, и дает следующий результат:

hello

Вот еще один пример чтения файла до конца.

import std.stdio;
import std.string;

void main() { 
   File file = File("test.txt", "w");  
   file.writeln("hello"); 
   file.writeln("world");  
   file.close();  
   file = File("test.txt", "r"); 
    
   while (!file.eof()) { 
      string line = chomp(file.readln()); 
      writeln("line -", line); 
   }
}

Когда приведенный выше код компилируется и выполняется, он считывает файл, созданный в предыдущем разделе, и дает следующий результат:

line -hello 
line -world 
line -

В приведенном выше примере вы можете увидеть пустую третью строку, поскольку после выполнения Writeln переводит ее на следующую строку.

Параллелизм заставляет программу работать одновременно в нескольких потоках. Примером параллельной программы является веб-сервер, отвечающий множеству клиентов одновременно. Параллелизм прост при передаче сообщений, но его очень сложно написать, если они основаны на совместном использовании данных.

Данные, передаваемые между потоками, называются сообщениями. Сообщения могут состоять из любого типа и любого количества переменных. У каждого потока есть идентификатор, который используется для указания получателей сообщений. Любой поток, который запускает другой поток, называется владельцем нового потока.

Запуск потоков в D

Функция spawn () принимает указатель в качестве параметра и запускает новый поток из этой функции. Любые операции, выполняемые этой функцией, включая другие функции, которые она может вызывать, будут выполняться в новом потоке. И владелец, и рабочий начинают выполняться по отдельности, как если бы они были независимыми программами.

пример

import std.stdio; 
import std.stdio; 
import std.concurrency; 
import core.thread;
  
void worker(int a) { 
   foreach (i; 0 .. 4) { 
      Thread.sleep(1); 
      writeln("Worker Thread ",a + i); 
   } 
}

void main() { 
   foreach (i; 1 .. 4) { 
      Thread.sleep(2); 
      writeln("Main Thread ",i); 
      spawn(≈worker, i * 5); 
   }
   
   writeln("main is done.");  
}

Когда приведенный выше код компилируется и выполняется, он считывает файл, созданный в предыдущем разделе, и дает следующий результат:

Main Thread 1 
Worker Thread 5 
Main Thread 2 
Worker Thread 6 
Worker Thread 10 
Main Thread 3 
main is done. 
Worker Thread 7 
Worker Thread 11 
Worker Thread 15 
Worker Thread 8 
Worker Thread 12 
Worker Thread 16 
Worker Thread 13
Worker Thread 17 
Worker Thread 18

Идентификаторы потоков в D

ThisTid переменная доступна во всем мире на уровне модуля всегда идентификатор текущего потока. Также вы можете получить threadId при вызове spawn. Пример показан ниже.

пример

import std.stdio; 
import std.concurrency;  

void printTid(string tag) { 
   writefln("%s: %s, address: %s", tag, thisTid, &thisTid); 
} 
 
void worker() { 
   printTid("Worker"); 
}
  
void main() { 
   Tid myWorker = spawn(&worker); 
   
   printTid("Owner "); 
   
   writeln(myWorker); 
}

Когда приведенный выше код компилируется и выполняется, он считывает файл, созданный в предыдущем разделе, и дает следующий результат:

Owner : Tid(std.concurrency.MessageBox), address: 10C71A59C 
Worker: Tid(std.concurrency.MessageBox), address: 10C71A59C 
Tid(std.concurrency.MessageBox)

Сообщение передается в D

Функция send () отправляет сообщения, а функция receiveOnly () ожидает сообщения определенного типа. Есть другие функции с именами prioritySend (), receive () и receiveTimeout (), которые будут объяснены позже.

Владелец в следующей программе отправляет своему работнику сообщение типа int и ожидает сообщения от рабочего типа double. Потоки продолжают отправлять сообщения туда и обратно, пока владелец не отправит отрицательное значение int. Пример показан ниже.

пример

import std.stdio; 
import std.concurrency; 
import core.thread; 
import std.conv;  

void workerFunc(Tid tid) { 
   int value = 0;  
   while (value >= 0) { 
      value = receiveOnly!int(); 
      auto result = to!double(value) * 5; tid.send(result);
   }
} 
 
void main() { 
   Tid worker = spawn(&workerFunc,thisTid); 
    
   foreach (value; 5 .. 10) { 
      worker.send(value); 
      auto result = receiveOnly!double(); 
      writefln("sent: %s, received: %s", value, result); 
   }
   
   worker.send(-1); 
}

Когда приведенный выше код компилируется и выполняется, он считывает файл, созданный в предыдущем разделе, и дает следующий результат:

sent: 5, received: 25 
sent: 6, received: 30 
sent: 7, received: 35 
sent: 8, received: 40 
sent: 9, received: 45

Сообщение передается с ожиданием в D

Ниже показан простой пример передачи сообщения с ожиданием.

import std.stdio; 
import std.concurrency; 
import core.thread; 
import std.conv; 
 
void workerFunc(Tid tid) { 
   Thread.sleep(dur!("msecs")( 500 ),); 
   tid.send("hello"); 
}
  
void main() { 
   spawn(&workerFunc,thisTid);  
   writeln("Waiting for a message");  
   bool received = false;
   
   while (!received) { 
      received = receiveTimeout(dur!("msecs")( 100 ), (string message) { 
         writeln("received: ", message); 
      });

      if (!received) { 
         writeln("... no message yet"); 
      }
   } 
}

Когда приведенный выше код компилируется и выполняется, он считывает файл, созданный в предыдущем разделе, и дает следующий результат:

Waiting for a message 
... no message yet 
... no message yet 
... no message yet 
... no message yet 
received: hello

Исключением является проблема, возникающая во время выполнения программы. Исключение AD - это реакция на исключительное обстоятельство, которое возникает во время работы программы, например, попытка деления на ноль.

Исключения позволяют передавать управление от одной части программы к другой. D обработка исключений построена на трех ключевых словахtry, catch, и throw.

  • throw- Программа выдает исключение при обнаружении проблемы. Это делается с помощьюthrow ключевое слово.

  • catch- Программа перехватывает исключение с помощью обработчика исключений в том месте программы, где вы хотите обработать проблему. Вcatch ключевое слово указывает на перехват исключения.

  • try - А tryblock определяет блок кода, для которого активированы определенные исключения. За ним следует один или несколько блоков catch.

Предполагая, что блок вызовет исключение, метод перехватывает исключение, используя комбинацию try и catchключевые слова. Блок try / catch помещается вокруг кода, который может вызвать исключение. Код в блоке try / catch называется защищенным кодом, а синтаксис для использования try / catch выглядит следующим образом:

try { 
   // protected code 
} 
catch( ExceptionName e1 ) { 
   // catch block 
} 
catch( ExceptionName e2 ) { 
   // catch block 
} 
catch( ExceptionName eN ) { 
   // catch block 
}

Вы можете перечислить несколько catch операторы для перехвата различных типов исключений в случае, если ваш try block вызывает более одного исключения в разных ситуациях.

Выбрасывание исключений в D

Исключения могут быть выброшены в любом месте блока кода, используя throwзаявления. Операнд операторов throw определяет тип исключения и может быть любым выражением, а тип результата выражения определяет тип генерируемого исключения.

В следующем примере возникает исключение при выполнении условия деления на ноль:

пример

double division(int a, int b) { 
   if( b == 0 ) { 
      throw new Exception("Division by zero condition!"); 
   }
   
   return (a/b); 
}

Выявление исключений в D

В catch блок после tryблок перехватывает любое исключение. Вы можете указать, какой тип исключения вы хотите перехватить, и это определяется объявлением исключения, которое появляется в круглых скобках после ключевого слова catch.

try { 
   // protected code 
} 

catch( ExceptionName e ) { 
   // code to handle ExceptionName exception 
}

Приведенный выше код перехватывает исключение ExceptionNameтип. Если вы хотите указать, что блок catch должен обрабатывать любой тип исключения, которое генерируется в блоке try, вы должны поставить многоточие, ..., между круглыми скобками, заключая объявление исключения следующим образом:

try { 
   // protected code 
} 

catch(...) { 
   // code to handle any exception 
}

В следующем примере создается исключение деления на ноль. Он зацеплен за блокировку.

import std.stdio; 
import std.string;
  
string division(int a, int b) { 
   string result = "";  
   
   try {  
      if( b == 0 ) {
         throw new Exception("Cannot divide by zero!"); 
      } else { 
         result = format("%s",a/b); 
      } 
   } catch (Exception e) { 
      result = e.msg; 
   }
   
   return result; 
} 
 
void main () { 
   int x = 50; 
   int y = 0;  
   
   writeln(division(x, y));  
   
   y = 10; 
   writeln(division(x, y)); 
}

Когда приведенный выше код компилируется и выполняется, он считывает файл, созданный в предыдущем разделе, и дает следующий результат:

Cannot divide by zero!
5

Контрактное программирование в программировании на языке D ориентировано на предоставление простых и понятных средств обработки ошибок. Контрактное программирование в D реализовано тремя типами кодовых блоков:

  • блок тела
  • в блоке
  • из блока

Блок тела в D

Блок тела содержит актуальный функциональный код исполнения. Блоки входа и выхода являются необязательными, а основной блок - обязательным. Ниже показан простой синтаксис.

return_type function_name(function_params) 
in { 
   // in block 
} 

out (result) { 
   // in block 
}
 
body { 
   // actual function block 
}

В блоке для предварительных условий в D

Блок In предназначен для простых предварительных условий, которые проверяют, приемлемы ли входные параметры и находятся ли они в диапазоне, который может быть обработан кодом. Преимущество блока in заключается в том, что все условия входа могут храниться вместе и отдельно от фактического тела функции. Ниже показано простое предварительное условие для проверки пароля на его минимальную длину.

import std.stdio; 
import std.string;
  
bool isValid(string password) 
in { 
   assert(password.length>=5); 
}
 
body { 
   // other conditions 
   return true; 
}
  
void main() { 
   writeln(isValid("password")); 
}

Когда приведенный выше код компилируется и выполняется, он считывает файл, созданный в предыдущем разделе, и дает следующий результат:

true

Выходные блоки для условий публикации в D

Блок out заботится о значениях, возвращаемых функцией. Он подтверждает, что возвращаемое значение находится в ожидаемом диапазоне. Ниже показан простой пример, содержащий как in, так и out, который преобразует месяцы и год в комбинированную десятичную форму возраста.

import std.stdio;
import std.string;

double getAge(double months,double years) 
in { 
   assert(months >= 0); 
   assert(months <= 12); 
}
 
out (result) { 
   assert(result>=years); 
} 

body { 
   return years + months/12; 
} 
 
void main () { 
   writeln(getAge(10,12)); 
}

Когда приведенный выше код компилируется и выполняется, он считывает файл, созданный в предыдущем разделе, и дает следующий результат:

12.8333

Условная компиляция - это процесс выбора того, какой код компилировать, а какой не компилировать, аналогично #if / #else / #endif в C и C ++. Любой оператор, который не скомпилирован, должен быть синтаксически правильным.

Условная компиляция включает проверки условий, которые можно оценить во время компиляции. Условные операторы времени выполнения, такие как if, for, while, не являются функциями условной компиляции. Следующие функции D предназначены для условной компиляции:

  • debug
  • version
  • статический, если

Заявление отладки в D

Отладки полезны при разработке программы. Выражения и операторы, помеченные как отладка, компилируются в программу только тогда, когда включен переключатель компилятора -debug.

debug a_conditionally_compiled_expression;
   
debug { 
   // ... conditionally compiled code ... 
} else { 
   // ... code that is compiled otherwise ... 
}

Предложение else является необязательным. Как одиночное выражение, так и приведенный выше блок кода компилируются, только когда включен переключатель компилятора -debug.

Вместо полного удаления строки можно пометить как отладочные.

debug writefln("%s debug only statement", value);

Такие строки включаются в программу только при включенном переключателе компилятора -debug.

dmd test.d -oftest -w -debug

Оператор отладки (тег) в D

Операторам отладки могут быть присвоены имена (теги) для выборочного включения в программу.

debug(mytag) writefln("%s not found", value);

Такие строки включаются в программу только при включенном переключателе компилятора -debug.

dmd test.d -oftest -w -debug = mytag

Блоки отладки также могут иметь теги.

debug(mytag) { 
   //  
}

Одновременно можно включить более одного тега отладки.

dmd test.d -oftest -w -debug = mytag1 -debug = mytag2

Оператор отладки (уровень) в D

Иногда бывает более полезно связать операторы отладки по числовым уровням. Повышение уровня может предоставить более подробную информацию.

import std.stdio;  

void myFunction() { 
   debug(1) writeln("debug1"); 
   debug(2) writeln("debug2");
}

void main() { 
   myFunction(); 
}

Выражения отладки и блоки, которые ниже или равны указанному уровню, будут скомпилированы.

$ dmd test.d -oftest -w -debug = 1 $ ./test 
debug1

Заявления версии (тег) и версии (уровня) в D

Версия аналогична отладочной и используется таким же образом. Предложение else является необязательным. Хотя версия работает по существу так же, как отладка, наличие отдельных ключевых слов помогает различать их несвязанные использования. Как и в случае с отладкой, можно включить более одной версии.

import std.stdio;  

void myFunction() { 
   version(1) writeln("version1"); 
   version(2) writeln("version2");     
}
  
void main() { 
   myFunction(); 
}

Выражения отладки и блоки, которые ниже или равны указанному уровню, будут скомпилированы.

$ dmd test.d -oftest -w -version = 1 $ ./test 
version1

Статический, если

Статический if является эквивалентом оператора if во время компиляции. Как и оператор if, static if принимает логическое выражение и оценивает его. В отличие от оператора if, статическое if не касается потока выполнения; скорее, он определяет, следует ли включать фрагмент кода в программу или нет.

Выражение if не связано с оператором is, который мы видели ранее, как синтаксически, так и семантически. Он оценивается во время компиляции. Он выдает значение типа int, 0 или 1; в зависимости от выражения, указанного в скобках. Хотя выражение, которое оно принимает, не является логическим выражением, само выражение is используется как логическое выражение времени компиляции. Это особенно полезно в статических условных выражениях if и ограничениях шаблонов.

import std.stdio;

enum Days { 
   sun, 
   mon, 
   tue, 
   wed, 
   thu, 
   fri, 
   sat 
}; 
 
void myFunction(T)(T mytemplate) {
   static if (is (T == class)) { 
      writeln("This is a class type"); 
   } else static if (is (T == enum)) { 
      writeln("This is an enum type"); 
   } 
}
  
void main() { 
   Days day; 
   myFunction(day); 
}

Когда мы скомпилируем и запустим, мы получим следующий результат.

This is an enum type

Классы - это центральная функция программирования на языке D, поддерживающая объектно-ориентированное программирование, и их часто называют пользовательскими типами.

Класс используется для определения формы объекта и объединяет представление данных и методы для управления этими данными в один аккуратный пакет. Данные и функции внутри класса называются членами класса.

Определения класса D

Когда вы определяете класс, вы определяете схему для типа данных. Фактически это не определяет какие-либо данные, но определяет, что означает имя класса, то есть из чего будет состоять объект класса и какие операции могут выполняться с таким объектом.

Определение класса начинается с ключевого слова classза которым следует имя класса; и тело класса, заключенное в фигурные скобки. После определения класса должна стоять точка с запятой или список объявлений. Например, мы определили тип данных Box с помощью ключевого словаclass следующим образом -

class Box { 
   public: 
      double length;   // Length of a box 
      double breadth;  // Breadth of a box 
      double height;   // Height of a box 
}

Ключевое слово publicопределяет атрибуты доступа членов следующего за ним класса. Доступ к общедоступному члену можно получить извне класса в любом месте в пределах объекта класса. Вы также можете указать членов класса какprivate или же protected которые мы обсудим в подразделе.

Определение объектов D

Класс предоставляет чертежи для объектов, поэтому в основном объект создается из класса. Вы объявляете объекты класса с тем же типом объявления, что и переменные базовых типов. Следующие утверждения объявляют два объекта класса Box -

Box Box1;          // Declare Box1 of type Box 
Box Box2;          // Declare Box2 of type Box

Оба объекта Box1 и Box2 имеют свою собственную копию членов данных.

Доступ к элементам данных

Доступ к открытым элементам данных объектов класса можно получить с помощью оператора прямого доступа к членам (.). Давайте попробуем следующий пример, чтобы прояснить ситуацию -

import std.stdio;

class Box { 
   public: 
      double length;   // Length of a box 
      double breadth;  // Breadth of a box 
      double height;   // Height of a box 
}
  
void main() { 
   Box box1 = new Box();    // Declare Box1 of type Box 
   Box box2 = new Box();    // Declare Box2 of type Box 
   double volume = 0.0;     // Store the volume of a box here  
   
   // box 1 specification 
   box1.height = 5.0; 
   box1.length = 6.0; 
   box1.breadth = 7.0; 
   
   // box 2 specification 
   box2.height = 10.0; 
   box2.length = 12.0; 
   box2.breadth = 13.0;
   
   // volume of box 1 
   volume = box1.height * box1.length * box1.breadth; 
   writeln("Volume of Box1 : ",volume);
   
   // volume of box 2 
   volume = box2.height * box2.length * box2.breadth; 
   writeln("Volume of Box2 : ", volume); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Volume of Box1 : 210 
Volume of Box2 : 1560

Важно отметить, что к закрытым и защищенным членам нельзя получить доступ напрямую с помощью оператора прямого доступа к члену (.). Вскоре вы узнаете, как можно получить доступ к закрытым и защищенным членам.

Классы и объекты в D

Пока что у вас есть очень общее представление о D-классах и объектах. Есть и другие интересные концепции, связанные с D-классами и объектами, которые мы обсудим в различных подразделах, перечисленных ниже:

Sr.No. Концепция и описание
1 Функции-члены класса

Функция-член класса - это функция, которая имеет свое определение или свой прототип в определении класса, как и любая другая переменная.

2 Модификаторы доступа к классам

Член класса может быть публичным, частным или защищенным. По умолчанию участники считаются частными.

3 Конструктор и деструктор

Конструктор класса - это специальная функция в классе, которая вызывается при создании нового объекта класса. Деструктор - это также специальная функция, которая вызывается при удалении созданного объекта.

4 Указатель this в D

У каждого объекта есть специальный указатель this который указывает на сам объект.

5 Указатель на классы D

Указатель на класс выполняется точно так же, как указатель на структуру. Фактически класс - это просто структура с функциями в ней.

6 Статические члены класса

И члены данных, и члены функций класса могут быть объявлены как статические.

Одна из важнейших концепций объектно-ориентированного программирования - это наследование. Наследование позволяет определить класс в терминах другого класса, что упрощает создание и поддержку приложения. Это также дает возможность повторно использовать функциональность кода и быстрое время реализации.

При создании класса вместо написания полностью новых элементов данных и функций-членов программист может указать, что новый класс должен наследовать члены существующего класса. Этот существующий класс называетсяbase класс, а новый класс называется derived класс.

Идея наследования реализует отношения. Например, млекопитающее - это животное, собака - это млекопитающее, следовательно, собака - это тоже животное, и так далее.

Базовые классы и производные классы в D

Класс может быть производным от нескольких классов, что означает, что он может наследовать данные и функции от нескольких базовых классов. Чтобы определить производный класс, мы используем список производных классов, чтобы указать базовый класс (классы). Список производных классов называет один или несколько базовых классов и имеет форму -

class derived-class: base-class

Рассмотрим базовый класс Shape и его производный класс Rectangle следующим образом -

import std.stdio;

// Base class 
class Shape { 
   public: 
      void setWidth(int w) { 
         width = w; 
      }

      void setHeight(int h) { 
         height = h; 
      }
   
   protected: 
      int width; 
      int height; 
}
  
// Derived class 
class Rectangle: Shape { 
   public: 
      int getArea() { 
         return (width * height); 
      } 
}
  
void main() { 
   Rectangle Rect = new Rectangle();
   
   Rect.setWidth(5); 
   Rect.setHeight(7); 
   
   // Print the area of the object. 
   writeln("Total area: ", Rect.getArea()); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Total area: 35

Контроль доступа и наследование

Производный класс может получить доступ ко всем не закрытым членам своего базового класса. Таким образом, члены базового класса, которые не должны быть доступны для функций-членов производных классов, должны быть объявлены частными в базовом классе.

Производный класс наследует все методы базового класса со следующими исключениями:

  • Конструкторы, деструкторы и конструкторы копирования базового класса.
  • Перегруженные операторы базового класса.

Многоуровневое наследование

Наследование может быть многоуровневым, и это показано в следующем примере.

import std.stdio;

// Base class 
class Shape {
   public:
      void setWidth(int w) {
         width = w; 
      }

      void setHeight(int h) {
         height = h; 
      }

   protected: 
      int width; 
      int height; 
}

// Derived class 
class Rectangle: Shape {
   public:
      int getArea() {
         return (width * height); 
      }
}
 
class Square: Rectangle {
   this(int side) {
      this.setWidth(side); 
      this.setHeight(side); 
   }
}

void main() {
   Square square = new Square(13);

   // Print the area of the object.
   writeln("Total area: ", square.getArea());
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Total area: 169

D позволяет указать более одного определения для function имя или operator в той же области, которая называется function overloading и operator overloading соответственно.

Перегруженное объявление - это объявление, которое было объявлено с тем же именем, что и предыдущее объявление в той же области, за исключением того, что оба объявления имеют разные аргументы и, очевидно, разное определение (реализацию).

Когда вы вызываете перегруженный function или же operator, компилятор определяет наиболее подходящее определение для использования, сравнивая типы аргументов, которые вы использовали для вызова функции или оператора, с типами параметров, указанными в определениях. Процесс выбора наиболее подходящей перегруженной функции или оператора называетсяoverload resolution..

Перегрузка функций

У вас может быть несколько определений для одного и того же имени функции в одной и той же области. Определение функции должно отличаться друг от друга типами и / или количеством аргументов в списке аргументов. Вы не можете перегрузить объявления функций, которые различаются только типом возвращаемого значения.

пример

В следующем примере используется та же функция print() для печати разных типов данных -

import std.stdio; 
import std.string; 

class printData { 
   public: 
      void print(int i) { 
         writeln("Printing int: ",i); 
      }

      void print(double f) { 
         writeln("Printing float: ",f );
      }

      void print(string s) { 
         writeln("Printing string: ",s); 
      } 
}; 
 
void main() { 
   printData pd = new printData();  
   
   // Call print to print integer 
   pd.print(5);
   
   // Call print to print float 
   pd.print(500.263); 
   
   // Call print to print character 
   pd.print("Hello D"); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Printing int: 5 
Printing float: 500.263 
Printing string: Hello D

Перегрузка оператора

Вы можете переопределить или перегрузить большинство встроенных операторов, доступных в D. Таким образом, программист может использовать операторы с пользовательскими типами.

Операторы могут быть перегружены, используя строку op, за которой следует Add, Sub и т. Д. В зависимости от оператора, который перегружается. Мы можем перегрузить оператор +, чтобы добавить два поля, как показано ниже.

Box opAdd(Box b) { 
   Box box = new Box(); 
   box.length = this.length + b.length; 
   box.breadth = this.breadth + b.breadth; 
   box.height = this.height + b.height; 
   return box; 
}

В следующем примере показана концепция перегрузки оператора с помощью функции-члена. Здесь в качестве аргумента передается объект, доступ к свойствам которого осуществляется с помощью этого объекта. Доступ к объекту, вызывающему этот оператор, можно получить, используяthis оператор, как описано ниже -

import std.stdio;

class Box { 
   public:  
      double getVolume() { 
         return length * breadth * height; 
      }

      void setLength( double len ) { 
         length = len; 
      } 

      void setBreadth( double bre ) { 
         breadth = bre; 
      }

      void setHeight( double hei ) { 
         height = hei; 
      }

      Box opAdd(Box b) { 
         Box box = new Box(); 
         box.length = this.length + b.length; 
         box.breadth = this.breadth + b.breadth; 
         box.height = this.height + b.height; 
         return box; 
      } 

   private: 
      double length;      // Length of a box 
      double breadth;     // Breadth of a box 
      double height;      // Height of a box 
}; 

// Main function for the program 
void main( ) { 
   Box box1 = new Box();    // Declare box1 of type Box 
   Box box2 = new Box();    // Declare box2 of type Box 
   Box box3 = new Box();    // Declare box3 of type Box 
   double volume = 0.0;     // Store the volume of a box here
   
   // box 1 specification 
   box1.setLength(6.0); 
   box1.setBreadth(7.0); 
   box1.setHeight(5.0);
   
   // box 2 specification 
   box2.setLength(12.0); 
   box2.setBreadth(13.0); 
   box2.setHeight(10.0); 
   
   // volume of box 1 
   volume = box1.getVolume(); 
   writeln("Volume of Box1 : ", volume);
   
   // volume of box 2 
   volume = box2.getVolume(); 
   writeln("Volume of Box2 : ", volume); 
   
   // Add two object as follows: 
   box3 = box1 + box2; 
   
   // volume of box 3 
   volume = box3.getVolume(); 
   writeln("Volume of Box3 : ", volume);  
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Volume of Box1 : 210 
Volume of Box2 : 1560 
Volume of Box3 : 5400

Типы перегрузки оператора

По сути, существует три типа перегрузки операторов, перечисленных ниже.

Sr.No. Типы перегрузки
1 Перегрузка унарных операторов
2 Перегрузка бинарных операторов
3 Перегрузка операторов сравнения

Все программы D состоят из следующих двух основных элементов:

  • Program statements (code) - Это часть программы, которая выполняет действия, и они называются функциями.

  • Program data - Это информация о программе, на которую влияют функции программы.

Инкапсуляция - это концепция объектно-ориентированного программирования, которая связывает данные и функции, управляющие данными, вместе и защищает как от внешнего вмешательства, так и от неправильного использования. Инкапсуляция данных привела к важной концепции ООП:data hiding.

Data encapsulation это механизм объединения данных, а функции, которые их используют, и data abstraction - это механизм, показывающий только интерфейсы и скрывающий детали реализации от пользователя.

D поддерживает свойства инкапсуляции и сокрытия данных посредством создания определяемых пользователем типов, называемых classes. Мы уже выяснили, что класс может содержатьprivate, защищенный, и publicчлены. По умолчанию все элементы, определенные в классе, являются частными. Например -

class Box { 
   public: 
      double getVolume() { 
         return length * breadth * height; 
      } 
   private: 
      double length;      // Length of a box 
      double breadth;     // Breadth of a box 
      double height;      // Height of a box 
};

Переменные длина, ширина и высота: private. Это означает, что к ним могут получить доступ только другие члены класса Box, а не какая-либо другая часть вашей программы. Это односторонняя инкапсуляция.

Чтобы сделать части класса public (т. е. доступными для других частей вашей программы), вы должны объявить их после publicключевое слово. Все переменные или функции, определенные после спецификатора public, доступны для всех других функций в вашей программе.

Сделав один класс другом другого, вы обнаружите детали реализации и уменьшите инкапсуляцию. Идеально, чтобы как можно больше деталей каждого класса было скрыто от всех других классов.

Инкапсуляция данных в D

Любая программа на языке D, в которой вы реализуете класс с общедоступными и закрытыми членами, является примером инкапсуляции и абстракции данных. Рассмотрим следующий пример -

пример

import std.stdio;
  
class Adder { 
   public: 
      // constructor 
      this(int i = 0) { 
         total = i; 
      } 
      
      // interface to outside world 
      void addNum(int number) { 
         total += number; 
      } 
      
      // interface to outside world 
      int getTotal() { 
         return total; 
      }; 
   
   private: 
      // hidden data from outside world 
      int total; 
}
 
void main( ) { 
   Adder a = new Adder(); 
   
   a.addNum(10); 
   a.addNum(20); 
   a.addNum(30);  
   writeln("Total ",a.getTotal()); 
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Total 60

Вышеупомянутый класс складывает числа и возвращает сумму. Общественные членыaddNum и getTotalявляются интерфейсами с внешним миром, и пользователь должен знать их, чтобы использовать класс. Общее количество закрытых членов - это нечто, что скрыто от внешнего мира, но необходимо для правильной работы класса.

Стратегия проектирования классов в D

Большинство из нас на горьком опыте научились делать членов класса закрытыми по умолчанию, если нам действительно не нужно их раскрывать. Это просто хорошоencapsulation.

Эта мудрость чаще всего применяется к элементам данных, но в равной степени применяется ко всем членам, включая виртуальные функции.

Интерфейс - это способ заставить классы, наследующие от него, реализовать определенные функции или переменные. Функции не должны реализовываться в интерфейсе, потому что они всегда реализуются в классах, которые наследуются от интерфейса.

Интерфейс создается с использованием ключевого слова interface вместо ключевого слова class, хотя они во многом схожи. Если вы хотите наследовать от интерфейса, а класс уже наследуется от другого класса, вам нужно разделить имя класса и имя интерфейса запятой.

Давайте посмотрим на простой пример, объясняющий использование интерфейса.

пример

import std.stdio;

// Base class
interface Shape {
   public: 
      void setWidth(int w);
      void setHeight(int h);
}

// Derived class
class Rectangle: Shape {
   int width;
   int height;
   
   public:
      void setWidth(int w) {
         width = w;
      }
      void setHeight(int h) {
         height = h; 
      }
      int getArea() {
         return (width * height);
      }
}

void main() {
   Rectangle Rect = new Rectangle();
   Rect.setWidth(5);
   Rect.setHeight(7);

   // Print the area of the object.
   writeln("Total area: ", Rect.getArea());
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Total area: 35

Интерфейс с конечными и статическими функциями в D

Интерфейс может иметь final и статический метод, определения которого должны быть включены в сам интерфейс. Эти функции не могут быть переопределены производным классом. Ниже показан простой пример.

пример

import std.stdio;

// Base class
interface Shape {
   public:
      void setWidth(int w);
      void setHeight(int h);
      
      static void myfunction1() {
         writeln("This is a static method");
      }
      final void myfunction2() {
         writeln("This is a final method");
      }
}

// Derived class
class Rectangle: Shape {
   int width;
   int height; 
   
   public:
      void setWidth(int w) {
         width = w;
      }
      void setHeight(int h) {
         height = h;
      }
      int getArea() {
         return (width * height);
      }
}

void main() {
   Rectangle rect = new Rectangle();

   rect.setWidth(5);
   rect.setHeight(7);
   
   // Print the area of the object.
   writeln("Total area: ", rect.getArea());
   rect.myfunction1();
   rect.myfunction2();
}

Когда приведенный выше код компилируется и выполняется, он дает следующий результат:

Total area: 35 
This is a static method 
This is a final method

Абстракция относится к способности сделать класс абстрактным в ООП. Абстрактный класс - это класс, который не может быть создан. Все остальные функции класса по-прежнему существуют, и все его поля, методы и конструкторы доступны одинаково. Вы просто не можете создать экземпляр абстрактного класса.

Если класс является абстрактным и не может быть создан, он не имеет особого смысла, если он не является подклассом. Обычно абстрактные классы возникают на этапе проектирования. Родительский класс содержит общие функции коллекции дочерних классов, но сам родительский класс слишком абстрактен, чтобы использовать его самостоятельно.

Использование абстрактного класса в D

Использовать abstractключевое слово для объявления абстрактного класса. Ключевое слово появляется в объявлении класса где-то перед ключевым словом class. Ниже показан пример того, как абстрактный класс может быть унаследован и использован.

пример

import std.stdio;
import std.string;
import std.datetime;

abstract class Person {
   int birthYear, birthDay, birthMonth; 
   string name; 
   
   int getAge() {
      SysTime sysTime = Clock.currTime(); 
      return sysTime.year - birthYear;
   }
}

class Employee : Person {
   int empID;
}

void main() {
   Employee emp = new Employee(); 
   emp.empID = 101; 
   emp.birthYear = 1980; 
   emp.birthDay = 10; 
   emp.birthMonth = 10; 
   emp.name = "Emp1"; 
   
   writeln(emp.name); 
   writeln(emp.getAge); 
}

Когда мы скомпилируем и запустим вышеуказанную программу, мы получим следующий результат.

Emp1
37

Абстрактные функции

Подобно функциям, классы также могут быть абстрактными. Реализация такой функции не указана в ее классе, но должна быть предоставлена ​​в классе, который наследует класс с абстрактной функцией. В приведенный выше пример добавлена ​​абстрактная функция.

пример

import std.stdio; 
import std.string; 
import std.datetime; 
 
abstract class Person { 
   int birthYear, birthDay, birthMonth; 
   string name; 
   
   int getAge() { 
      SysTime sysTime = Clock.currTime(); 
      return sysTime.year - birthYear; 
   } 
   abstract void print(); 
}
class Employee : Person { 
   int empID;  
   
   override void print() { 
      writeln("The employee details are as follows:"); 
      writeln("Emp ID: ", this.empID); 
      writeln("Emp Name: ", this.name); 
      writeln("Age: ",this.getAge); 
   } 
} 

void main() { 
   Employee emp = new Employee(); 
   emp.empID = 101; 
   emp.birthYear = 1980; 
   emp.birthDay = 10; 
   emp.birthMonth = 10; 
   emp.name = "Emp1"; 
   emp.print(); 
}

Когда мы скомпилируем и запустим вышеуказанную программу, мы получим следующий результат.

The employee details are as follows: 
Emp ID: 101 
Emp Name: Emp1 
Age: 37