目录

D Programming - 快速指南

D Programming - Overview

D编程语言是由Walter Bright of Digital Mars开发的面向对象的多范式系统编程语言。 它的开发始于1999年,并于2001年首次发布.D(1.0)的主要版本于2007年发布。目前,我们有D2版本的D.

D是语法为C风格的语言,使用静态类型。 D中有许多C和C ++的特性,但这些语言中有一些特征不包含在D的一部分中.D的一些值得注意的补充包括,

  • 单元测试
  • 真正的模块
  • Garbage collection
  • First class arrays
  • Free and open
  • Associative arrays
  • Dynamic arrays
  • Inner classes
  • Closures
  • 匿名函数
  • Lazy evaluation
  • Closures

多种范式

D是一种多范式编程语言。 多种范式包括,

  • Imperative
  • 面向对象
  • 元编程
  • Functional
  • Concurrent

例子 (Example)

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

学习D.

学习D时最重要的事情是专注于概念而不是迷失在语言技术细节中。

学习编程语言的目的是成为一个更好的程序员; 也就是说,在设计和实施新系统以及维护旧系统方面变得更加有效。

D的范围

D编程有一些有趣的功能,官方D编程网站声称D方便,强大和高效。 D编程在核心语言中添加了许多功能,C语言以标准库的形式提供了这些功能,例如可调整大小的数组和字符串函数。 D为中级到高级程序员提供了出色的第二语言。 D更好地处理内存和管理经常导致C ++出现问题的指针。

D编程主要用于转换现有程序的新程序。 它提供了内置的测试和验证,是大型新项目的理想选择,大型团队将使用数百万行代码编写。

D Programming - Environment

D的本地环境设置

如果您仍然愿意为D编程语言设置环境,则需要在计算机上使用以下两个软件:(a)文本编辑器,(b)D编译器。

D编程的文本编辑器

这将用于键入您的程序。 少数编辑器的示例包括Windows Notepad,OS Edit命令,Brief,Epsilon,EMACS和vim或vi。

文本编辑器的名称和版本可能因不同的操作系统而异。 例如,Notepad将在Windows上使用,vim或vi可以在Windows以及Linux或UNIX上使用。

使用编辑器创建的文件称为源文件,包含程序源代码。 D程序的源文件以扩展名“ .d ”命名。

在开始编程之前,请确保您有一个文本编辑器,并且您有足够的经验来编写计算机程序,将其保存在文件中,构建它并最终执行它。

D编译器

大多数当前的D实现直接编译为机器代码以便有效执行。

我们有多个D编译器可用,它包括以下内容。

  • DMD - Digital Mars D编译器是Walter Bright的官方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

在Windows上安装D.

下载Windows installer

运行下载的可执行文件以安装D,可以按照屏幕上的说明进行操作。

现在我们可以使用cd切换到包含该文件的文件夹,然后使用以下步骤构建并运行ad文件helloWorld.d:

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

我们可以看到以下输出。

hello world

C:\DProgramming是文件夹,我用来保存我的样品。 您可以将其更改为已保存D程序的文件夹。

在Ubuntu/Debian上安装D.

下载debian installer

运行下载的可执行文件以安装D,可以按照屏幕上的说明进行操作。

现在我们可以使用cd切换到包含该文件的文件夹,然后使用以下步骤构建并运行ad文件helloWorld.d:

$ dmd helloWorld.d 
$ ./helloWorld

我们可以看到以下输出。

$ hello world 

在Mac OS X上安装D.

下载Mac installer

运行下载的可执行文件以安装D,可以按照屏幕上的说明进行操作。

现在我们可以使用cd切换到包含该文件的文件夹,然后使用以下步骤构建并运行ad文件helloWorld.d:

$ dmd helloWorld.d 
$ ./helloWorld

我们可以看到以下输出。

$ hello world

在Fedora上安装D.

下载fedora installer

运行下载的可执行文件以安装D,可以按照屏幕上的说明进行操作。

现在我们可以使用cd切换到包含该文件的文件夹,然后使用以下步骤构建并运行ad文件helloWorld.d:

$ dmd helloWorld.d 
$ ./helloWorld

我们可以看到以下输出。

$ hello world

在OpenSUSE上安装D.

下载OpenSUSE installer

运行下载的可执行文件以安装D,可以按照屏幕上的说明进行操作。

现在我们可以使用cd切换到包含该文件的文件夹,然后使用以下步骤构建并运行ad文件helloWorld.d:

$ dmd helloWorld.d 
$ ./helloWorld

我们可以看到以下输出。

$ hello world

D IDE

在大多数情况下,我们以插件的形式支持D. 这包括,

  • Visual D插件是Visual Studio 2005-13的插件

  • DDT是一个eclipse插件,提供代码完成,使用GDB进行调试。

  • Mono-D代码完成,使用dmd/ldc/gdc支持重构。 它已成为GSoC 2012的一部分。

  • 代码块是一个多平台IDE,支持D项目创建,突出显示和调试。

D Programming - Basic Syntax

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库,它提供基本的I/O操作。 在上述程序中使用的writeln是D的标准库中的函数。 它用于打印一行文本。 D中的库内容被分组为模块,这些模块基于它们要执行的任务类型。 该程序使用的唯一模块是std.stdio,它处理数据输入和输出。

主功能

主要功能是程序的启动,它决定了执行的顺序以及程序的其他部分应该如何执行。

D中的代币

AD程序由各种令牌组成,令牌可以是关键字,标识符,常量,字符串文字或符号。 例如,以下D语句由四个标记组成 -

writeln("test!");

个人代币是 -

writeln (
   "test!"
)
;

注释 (Comments)

注释就像支持D程序中的文本一样,编译器会忽略它们。 多行注释以/ *开头,并以字符* /结尾,如下所示 -

/* My first program in D */ 

在注释的开头使用//编写单个注释。

// my first program in D

标识符 (Identifiers)

AD标识符是用于标识变量,函数或任何其他用户定义项的名称。 标识符以字母A到Z或a到z或下划线_开头,后跟零个或多个字母,下划线和数字(0到9)。

D不允许标识符中的标点符号,如@,$和%。 D是case sensitive编程语言。 因此, Manpowermanpower是D中的两个不同的标识符。以下是可接受标识符的一些示例 -

mohd       zara    abc   move_name  a_123 
myname50   _temp   j     a23b9      retVal

关键字 (Keywords)

以下列表显示了D中的一些保留字。这些保留字不能用作常量或变量或任何其他标识符名称。

abstractaliasalignasm
assertautobodybool
bytecasecastcatch
charclassconstcontinue
dchardebugdefaultdelegate
deprecateddodoubleelse
enumexportexternfalse
finalfinallyfloatfor
foreachfunctiongotoif
importininoutint
interfaceinvariantislong
macromixinmodulenew
nulloutoverridepackage
pragmaprivateprotectedpublic
realrefreturnscope
shortstaticstructsuper
switchsynchronizedtemplatethis
throwtruetrytypeid
typeofubyteuintulong
unionunittestushortversion
voidwcharwhilewith

D中的空白

只包含空格(可能带有注释)的行称为空行,D编译器完全忽略它。

空格是D中用于描述空格,制表符,换行符和注释的术语。 空格将语句的一部分与另一部分分开,并使解释器能够识别语句中的一个元素(如int)的结束位置以及下一个元素的开始位置。 因此,在以下声明中 -

local age

在本地和年龄之间必须至少有一个空格字符(通常是空格),以便解释器能够区分它们。 另一方面,在以下声明中

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

水果和=之间,或=和苹果之间不需要空白字符,但如果您希望出于可读性目的,可以自由添加一些空白字符。

D Programming - Variables

变量只不过是我们的程序可以操作的存储区域的名称。 D中的每个变量都有一个特定的类型,它决定了变量内存的大小和布局; 可存储在该内存中的值范围; 以及可以应用于变量的操作集。

变量的名称可以由字母,数字和下划线字符组成。 它必须以字母或下划线开头。 大写和小写字母是不同的,因为D区分大小写。 基于前一章中解释的基本类型,将有以下基本变量类型 -

Sr.No. 类型和描述
1

char

通常是一个八位字节(一个字节)。 这是一个整数类型。

2

int

机器最自然的整数大小。

3

float

单精度浮点值。

4

double

双精度浮点值。

5

void

表示缺少类型。

D编程语言还允许定义各种其他类型的变量,如枚举,指针,数组,结构,联合等,我们将在后续章节中介绍。 在本章中,我们只研究基本变量类型。

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; 它指示编译器创建int类型的名为i,j和k的变量。

变量可以在其声明中初始化(分配初始值)。 初始化程序包含一个等号,后跟一个常量表达式,如下所示 -

type variable_name = value;

例子 (Examples)

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中声明变量时,它总是被设置为'default initializer',可以手动访问T.init ,其中T是类型(例如int.init )。 整数类型的默认初始值设定项为0,Booleans为false,浮点数为NaN。

D中的变量声明

变量声明为编译器提供了保证,即存在一个具有给定类型和名称的变量,以便编译器继续进行进一步编译,而无需完整的变量详细信息。 变量声明仅在编译时有意义,编译器在链接程序时需要实际的变量声明。

例子 (Example)

尝试以下示例,其中变量已在程序开头声明,但在main函数内定义和初始化 -

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

D值中的左值和右值

D中有两种表达方式 -

  • lvalue - 作为lvalue的表达式可能显示为赋值的左侧或右侧。

  • rvalue - 作为rvalue的表达式可能出现在赋值的右侧但不是左侧。

变量是左值,因此可能出现在赋值的左侧。 数字文字是右值,因此可能无法分配,也不能出现在左侧。 以下声明有效 -

int g = 20;

但以下不是有效的语句,会产生编译时错误 -

10 = 20;

D Programming - Data Types

在D编程语言中,数据类型是指用于声明不同类型的变量或函数的扩展系统。 变量的类型决定了它在存储中占用的空间大小以及如何解释存储的位模式。

D中的类型可分为以下几类 -

Sr.No. 类型和描述
1

Basic Types

它们是算术类型,由三种类型组成:(a)整数,(b)浮点和(c)字符。

2

Enumerated types

它们又是算术类型。 它们用于定义只能在整个程序中分配某些离散整数值的变量。

3

The type void

类型说明符void表示没有可用的值。

4

Derived types

它们包括(a)指针类型,(b)数组类型,(c)结构类型,(d)联合类型和(e)函数类型。

数组类型和结构类型统称为聚合类型。 函数的类型指定函数返回值的类型。 我们将在下一节中看到基本类型,而其他类型将在后续章节中介绍。

整数类型 (Integer Types)

下表列出了标准整数类型及其存储大小和值范围 -

类型 存储大小 价值范围
bool 1个字节 false or true
byte 1个字节 -128 to 127
ubyte 1个字节 0到255
int 4字节 -2,147,483,648 to 2,147,483,647
uint 4字节 0 to 4,294,967,295
short 2个字节 -32,768 to 32,767
ushort 2个字节 0 to 65,535
long 8个字节 -9223372036854775808 to 9223372036854775807
ulong 8个字节 0 to 18446744073709551615

要获得类型或变量的确切大小,可以使用sizeof运算符。 表达式type.(sizeof)产生对象或类型的存储大小(以字节为单位)。 以下示例在任何计算机上获取int类型的大小 -

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

编译并执行上述程序时,会产生以下结果 -

Length in bytes: 8 

Floating-Point Types

下表提到了具有存储大小,值范围及其用途的标准浮点类型 -

类型 存储大小 价值范围 目的
float 4字节 1.17549e-38至3.40282e + 38 6 decimal places
double 8个字节 2.22507e-308至1.79769e + 308 15 decimal places
real 10个字节 3.3621e-4932至1.18973e + 4932 硬件支持的最大浮点类型,或者双倍; 以较大者为准
ifloat 4字节 1.17549e-38i至3.40282e + 38i 浮动的虚数值类型
idouble 8个字节 2.22507e-308i至1.79769e + 308i 虚数值的double类型
ireal 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 由两个实数组成的复数类型

以下示例打印float类型占用的存储空间及其范围值 -

import std.stdio;
int main() { 
   writeln("Length in bytes: ", float.sizeof); 
   return 0; 
}

编译并执行上述程序时,它会在Linux上生成以下结果 -

Length in bytes: 4

角色类型

下表列出了具有存储大小及其用途的标准字符类型。

类型 存储大小 目的
char 1个字节 UTF-8代码单元
wchar 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中有各种功能,不接受任何参数。 没有参数的函数可以作为void接受。 例如, int rand(void);

此时可能无法理解void类型,所以让我们继续,我们将在后面的章节中介绍这些概念。

D Programming - Enums

枚举用于定义命名常量值。 使用enum关键字声明枚举类型。

enum语法

枚举定义的最简单形式如下 -

enum enum_name {  
   enumeration list 
}

Where,

  • enum_name指定枚举类型名称。

  • enumeration list是以逗号分隔的标识符列表。

枚举列表中的每个符号代表一个整数值,一个大于它之前的符号。 默认情况下,第一个枚举符号的值为0.例如 -

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

例子 (Example)

以下示例演示了枚举变量的使用 -

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

在上面的程序中,我们可以看到如何使用枚举。 最初,我们创建一个名为我们用户定义的枚举天数的变量。 然后我们使用点运算符将其设置为mon 。 我们需要使用writefln方法来打印已存储的mon值。 您还需要指定类型。 它是整数类型,因此我们使用%d进行打印。

命名枚举属性

上面的示例使用名称Days作为枚举,并称为命名枚举。 这些命名的枚举具有以下属性 -

  • Init - 初始化枚举中的第一个值。

  • min - 返回枚举的最小值。

  • max - 返回枚举的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

匿名枚举

没有名称的枚举称为匿名枚举。 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 :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

D Programming - Literals

作为源代码的一部分在程序中键入的常量值称为literals

文字可以是任何基本数据类型,可以分为整数,浮点数,字符,字符串和布尔值。

同样,文字被视为常规变量,除了它们的值在定义后无法修改。

整型常量 (Integer 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表示“4倍的功率的2.3倍10”。 可以在指数值之前指定“+”字符,但它没有效果。 例如,2.3e4和2.3e + 4是相同的。

在指数值之前添加的“ - ”字符将含义更改为“除以10的幂”。 例如,2.3e-2表示“2.3除以10到2的幂”。

在十六进制系统中,值以0x或0X开头。 指数由p或P而不是e或E指定。指数并不意味着“10的幂”,而是“2的幂”。 例如,0xabc.defP4中的P4表示“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表示true。

  • false表示false。

您不应该将true的值等于1,将false值等于0。

字符文字

字符文字用单引号括起来。

字符文字可以是普通字符(例如,'x'),转义序列(例如,'\ t'),ASCII字符(例如,'\ x21'),Unicode字符(例如,'\ u011e')或作为命名字符(例如'\'','\♥','\'')。

D中有一些字符,如果它们前面有反斜杠,它们将具有特殊含义,它们用于表示换行符(\ n)或制表符(\ t)。 在这里,您有一些此类转义序列代码的列表 -

逃脱序列 含义
\\ \字符
\' ' character(字符)
\" " character(字符)
\? ? 字符
\aAlert or bell
\bBackspace
\fForm feed
\nNewline
\rCarriage return
\t 水平标签
\v 垂直标签

以下示例显示了一些转义序列字符 -

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!

字符串常量 (String Literals)

字符串文字用双引号括起来。 字符串包含与字符文字类似的字符:普通字符,转义序列和通用字符。

您可以使用字符串文字将长行分成多行,并使用空格分隔它们。

以下是字符串文字的一些示例 -

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 Programming - Operators

运算符是一个符号,告诉编译器执行特定的数学或逻辑操作。 D语言内置运算符丰富,提供以下类型的运算符 -

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 按位运算符
  • 分配运算符
  • 其它运算符

本章逐一解释算术,关系,逻辑,按位,赋值和其他运算符。

算术运算符 (Arithmetic Operators)

下表显示了D语言支持的所有算术运算符。 假设变量A保持10,变量B保持20然后 -

显示示例

操作者 描述
+ 它增加了两个操作数。 A + B给出30
- 它从第一个操作数中减去第二个操作数。 A - B给-10
* 它将两个操作数相乘。 A * B给出200
/ 它将分子除以分子。 B/A给出2
% 它返回整数除法的余数。 B%A给出0
++ 增量运算符将整数值增加1。 A ++给出11
-- 递减运算符将整数值减1。 A-- gives 9

关系运算符 (Relational Operators)

下表显示了D语言支持的所有关系运算符。 假设变量A保持10,变量B保持20,则 -

显示示例

操作者 描述
== 检查两个操作数的值是否相等,如果是,则条件变为真。 (A == B)不是真的。
!= 检查两个操作数的值是否相等,如果值不相等则条件变为真。 (A!= B)是真的。
> 检查左操作数的值是否大于右操作数的值,如果是,则条件变为真。 (A> B)不是真的。
< 检查左操作数的值是否小于右操作数的值,如果是,则条件变为真。 (A < B) 为真
>= 检查左操作数的值是否大于或等于右操作数的值,如果是,则条件变为真。 (A> = B)不是真的。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是,则条件变为真。 (A <= B)是真的。

逻辑运算符 (Logical Operators)

下表显示了D语言支持的所有逻辑运算符。 假设变量A保持1而变量B保持0,则 -

显示示例

操作者 描述
&& 它被称为逻辑AND运算符。 如果两个操作数都不为零,则条件成立。 (A && B)是假的。
|| 它被称为逻辑OR运算符。 如果两个操作数中的任何一个非零,则条件变为真。 (A || B)是真的。
! 它被称为逻辑非运算符。 用于反转其操作数的逻辑状态。 如果条件为真,则Logical NOT运算符将为false。 !(A && B)是真的。

按位运算符 (Bitwise Operators)

按位运算符处理位并执行逐位运算。 &,|和^的真值表如下 -

p q p&q p | q p ^ q
00000
01011
11110
10011

假设A = 60; 和B = 13.在二进制格式中,它们将如下 -

A = 0011 1100

B = 0000 1101

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

A&B = 0000 1100

A | B = 0011 1101

A ^ B = 0011 0001

~A = 1100 0011

D语言支持的Bitwise运算符如下表所示。 假设变量A保持60,变量B保持13,则 -

显示示例

操作者 描述
& 如果二进制AND运算符存在于两个操作数中,则它会将结果复制到结果中。 (A&B)将给出12,表示0000 1100。
| 二进制OR运算符如果存在于任一操作数中,则复制一位。 (A | B)给出61.装置0011 1101。
^ 二进制异或运算符如果在一个操作数中设置但不在两个操作数中设置,则复制该位。 (A ^ B)给出49.装置0011 0001
~ 二元一元补语运算符是一元的,具有“翻转”位的效果。 (~A)给出-61。 以2的补码形式表示1100 0011。
<< 二进制左移运算符。 左操作数值向左移动右操作数指定的位数。 A << 2给240.手段1111 0000
>> 二进制右移运算符。 左操作数值向右移动右操作数指定的位数。 A >> 2给15.意味着0000 1111。

赋值操作符 (Assignment Operators)

D语言支持以下赋值运算符 -

显示示例

操作者 描述
= 它是简单的赋值运算符。 它将右侧操作数的值分配给左侧操作数 C = A + B将A + B的值分配给C
+= 它是添加AND赋值运算符。 它将右操作数添加到左操作数并将结果分配给左操作数 C + = A等于C = C + A.
-= 它是减法AND赋值运算符。 它从左操作数中减去右操作数,并将结果赋给左操作数。 C - = A相当于C = C - A.
*= 它是乘法AND赋值运算符。 它将右操作数与左操作数相乘,并将结果赋给左操作数。 C * = A等于C = C * A.
/= 它是除AND赋值运算符。 它将左操作数与右操作数分开,并将结果赋给左操作数。 C/= A相当于C = C/A.
%= 它是模数AND赋值运算符。 它使用两个操作数来获取模数,并将结果赋给左操作数。 C%= A等于C = C%A
<<= 它是左移AND赋值运算符。 C << = 2与C = C << 2相同
>>= 它是右移和赋值运算符。 C >> = 2与C = C >> 2相同
&= 它是按位AND赋值运算符。 C&= 2与C = C&2相同
^= 它是按位异或和赋值运算符。 C ^ = 2与C = C ^ 2相同
|= 它是按位包含OR和赋值运算符 C | = 2与C = C |相同 2

Miscillaneous Operators - Sizeof和Ternary

还有其他一些重要的运算符,包括sizeof? : ? : D语言支持。

显示示例

操作者 描述
sizeof() 返回变量的大小。 sizeof(a),其中a是整数,返回4。
& 返回变量的地址。 &一个; 给出变量的实际地址。
*Pointer to a variable. *一个; 给出一个变量的指针。
? : 条件表达式 如果condition为true则为值X:否则为Y.

D中的运算符优先级

运算符优先级确定表达式中的术语分组。 这会影响表达式的计算方式。 某些运算符优先于其他运算符。

例如,乘法运算符的优先级高于加法运算符。

让我们考虑一个表达

x = 7 + 3 * 2。

这里,x被赋值为13,而不是20.简单的原因是,运算符*的优先级高于+,因此首先计算3 * 2,然后将结果加到7中。

此处,具有最高优先级的运算符显示在表的顶部,具有最低优先级的运算符显示在底部。 在表达式中,首先计算更高优先级的运算符。

显示示例

类别 操作者 关联性
Postfix ()[] - >。 ++ - - 左到右
Unary + - ! 〜++ - - (类型)*&sizeof 右到左
Multiplicative * /% 左到右
Additive + - 左到右
Shift << >> 左到右
Relational << => = 左到右
Equality ==!= 左到右
Bitwise AND& 左到右
Bitwise XOR ^ 左到右
Bitwise OR | 左到右
Logical AND && 左到右
Logical OR || 左到右
Conditional?: 右到左
Assignment = + = - = * =/=%= >> = << =&= ^ = | = 右到左
Comma, 左到右

D Programming - Loops

当您需要多次执行代码块时,可能会出现这种情况。 通常,语句按顺序执行:首先执行函数中的第一个语句,然后执行第二个语句,依此类推。

编程语言提供各种控制结构,允许更复杂的执行路径。

循环语句多次执行语句或语句组。 以下一般形式的循环语句主要用于编程语言 -

循环架构

D编程语言提供以下类型的循环来处理循环要求。 单击以下链接以检查其详细信息。

Sr.No. 循环类型和描述
1 循环

当给定条件为真时,它重复一个陈述或一组陈述。 它在执行循环体之前测试条件。

2 for循环

它多次执行一系列语句,并缩写管理循环变量的代码。

3 做... while循环

像while语句一样,除了它测试循环体末尾的条件。

4 嵌套循环

你可以在for,for或do..while循环中使用一个或多个循环。

循环控制语句 (Loop Control Statements)

循环控制语句将执行从其正常序列更改。 当执行离开作用域时,将销毁在该作用域中创建的所有自动对象。

D支持以下控制声明 -

Sr.No. 控制声明和描述
1 break statement

终止循环或switch语句,并在循环或切换后立即将执行转移到语句。

2 continue statement

导致循环跳过其身体的其余部分,并在重复之前立即重新测试其状态。

无限循环 (The Infinite Loop)

如果条件永远不会变为假,则循环变为无限循环。 for循环传统上用于此目的。 由于不需要构成for循环的三个表达式,因此可以通过将条件表达式留空来创建无限循环。

import std.stdio;
int main () {
   for( ; ; ) {
      writefln("This loop will run forever.");
   }
   return 0;
}

当条件表达式不存在时,假定为真。 您可能有一个初始化和增量表达式,但D程序员更常使用for(;;)构造来表示无限循环。

NOTE - 您可以通过按Ctrl + C键终止无限循环。

D Programming - Decisions

决策结构包含要评估的条件以及要执行的两组语句。 如果条件为真,则执行一组语句,如果条件为假,则执行另一组语句。

以下是大多数编程语言中的典型决策结构的一般形式 -

D中的决策声明

D编程语言将任何non-zeronon-null值假定为true ,如果它zeronull ,则将其假定为false值。

D编程语言提供以下类型的决策制定语句。

Sr.No. 声明和说明
1 if 语句

if statement由布尔表达式后跟一个或多个语句组成。

2 if...else 语句

if statement后面可以跟一个可选的else statement ,该else statement在布尔表达式为false时执行。

3 嵌套if语句

您可以在另一个ifelse if语句中使用ifelse if语句。

4 switch 语句

switch语句允许测试变量与值列表的相等性。

5 嵌套的switch语句

您可以在另一个switch语句中使用一个switch语句。

的? :D中的运算符

我们覆盖了conditional operator ? : conditional operator ? :在前一章中可以用来替换if...else语句。 它具有以下一般形式

Exp1 ? Exp2 : Exp3;

Exp1,Exp2和Exp3是表达式。 注意结肠的使用和放置。

一个值? 表达式确定如下 -

  • 评估Exp1。 如果是,那么Exp2会被评估并成为整个值吗? 表达。

  • 如果Exp1为false,则计算Exp3,其值将成为表达式的值。

D Programming - 函数

本章介绍D编程中使用的功能。

D中的函数定义

基本函数定义由函数头和函数体组成。

语法 (Syntax)

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

以下是函数的所有部分 -

  • Return Type - 函数可以返回值。 return_type是函数返回的值的数据类型。 某些函数执行所需的操作而不返回值。 在这种情况下,return_type是关键字void

  • Function Name - 这是Function Name的实际名称。 函数名称和参数列表一起构成函数签名。

  • Parameters - 参数类似于占位符。 调用函数时,将值传递给参数。 该值称为实际参数或参数。 参数列表是指函数参数的类型,顺序和数量。 参数是可选的; 也就是说,函数可能不包含任何参数。

  • Function Body - 函数体包含一组语句,用于定义函数的功能。

调用一个函数 (Calling a Function)

您可以按如下方式调用函数 -

function_name(parameter_values)

D中的函数类型

D编程支持多种功能,如下所示。

  • 纯粹的功能
  • Nothrow功能
  • 参考功能
  • 自动功能
  • 变量函数
  • Inout功能
  • Property Functions

各种功能解释如下。

纯函数 (Pure Functions)

纯函数是无法通过参数访问全局或静态,可变状态的函数。 这可以基于以下事实实现优化:保证纯函数不会改变任何未传递给它的函数,并且在编译器可以保证纯函数不能改变其参数的情况下,它可以实现完整的,功能纯的,是,保证函数将始终为相同的参数返回相同的结果)。

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 Functions

Nothrow函数不会抛出从类Exception派生的任何异常。 Nothrow函数与投掷函数协变。

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函数参数。

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

Auto 函数

自动功能可以返回任何类型的值。 要返回的类型没有限制。 下面给出了自动类型函数的一个简单示例。

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

Variadic 函数

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可用于参数和返回类型的函数。 它就像是mutable,const和immutable的模板。 mutability属性是从参数中推导出来的。 means,inout将推断的mutability属性传递给返回类型。 下面显示了一个显示可变性如何变化的简单示例。

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 函数

属性允许使用成员函数,如成员变量。 它使用@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

D Programming - Characters

字符是字符串的构建块。 书写系统的任何符号都称为字符:字母,数字,标点符号,空格字符等字母。令人困惑的是,字符的构建块本身也称为字符。

小写字母a的整数值是97,数字1的整数值是49.这些值仅在设计ASCII表时由约定分配。

下表提到了标准字符类型及其存储大小和用途。

字符由char类型表示,它只能包含256个不同的值。 如果您熟悉其他语言的char类型,您可能已经知道它不足以支持许多书写系统的符号。

类型 存储大小 目的
char 1个字节 UTF-8代码单元
wchar 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编程支持unicode,为了读取unicode字符,我们需要读两次并写两次以获得预期的结果。 这不适用于在线编译器。 示例如下所示。

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 Programming - Strings

D提供以下两种类型的字符串表示 -

  • 字符数组
  • 核心语言字符串

字符数组

我们可以用两种形式之一表示字符数组,如下所示。 第一个表单直接提供大小,第二个表单使用dup方法创建字符串“早上好”的可写副本。

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

例子 (Example)

这是一个使用上述简单字符数组形式的简单示例。

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";

例子 (Example)

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 

字符串连接 (String Concatenation)

D编程中的字符串连接使用波浪号(〜)符号。

例子 (Example)

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 

字符串长度

借助长度函数可以检索字符串的长度(以字节为单位)。

例子 (Example)

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编程中字符串比较非常简单。 您可以使用==,运算符进行字符串比较。

例子 (Example)

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'.

替换字符串

我们可以使用字符串[]替换字符串。

例子 (Example)

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的字符串中子字符串位置的索引方法。

例子 (Example)

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

处理案件

以下示例显示了用于更改案例的方法。

例子 (Example)

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

限制字符

字符串中的Restring字符显示在以下示例中。

例子 (Example)

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 Programming - Arrays

D编程语言提供了一种名为arrays的数据结构,它存储了相同类型元素的固定大小顺序集合。 数组用于存储数据集合。 将数组视为相同类型的变量集合通常更有用。

您可以声明一个数组变量(例如数字)并使用数字[0],数字[1]和...,数字[99]来表示单个变量,例如number0,number1,...和number99,而不是声明单个变量。个别变数。 索引访问数组中的特定元素。

所有阵列都包含连续的内存位置。 最低地址对应于第一个元素,最高地址对应于最后一个元素。

声明数组 (Declaring Arrays)

要用D编程语言声明一个数组,程序员指定元素的类型和数组所需的元素数量,如下所示 -

type arrayName [ arraySize ];

这称为单维数组。 arraySize必须是大于零的整数常量, type可以是任何有效的D编程语言数据类型。 例如,要声明一个名为balance double类型的10元素数组,请使用此语句 -

double balance[10]; 

初始化数组 (Initializing Arrays)

您可以逐个初始化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。 具有第四个索引的数组将是第5个,即最后一个元素,因为所有数组都将0作为其第一个元素的索引,也称为基本索引。 下面的图示代表显示了我们上面讨论的相同数组 -

阵列演示

访问数组元素 (Accessing Array Elements)

通过索引数组名称来访问元素。 这是通过将元素的索引放在数组名称后面的方括号中来完成的。 例如 -

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

静态数组返回数组长度乘以每个数组元素的字节数,而动态数组返回动态数组引用的大小,在32位构建中为8,在64位构建时为16。

3

.length

静态数组返回数组中元素的数量,而动态数组用于获取/设置数组中的元素数。 长度的类型为size_t。

4

.ptr

返回指向数组第一个元素的指针。

5

.dup

创建一个相同大小的动态数组,并将数组的内容复制到其中。

6

.idup

创建一个相同大小的动态数组,并将数组的内容复制到其中。 该副本被输入为不可变的。

7

.reverse

反转数组中元素的顺序。 返回数组。

8

.sort

对数组中元素的顺序进行排序。 返回数组。

例子 (Example)

以下示例说明了数组的各种属性 -

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];

例子 (Example)

以下声明创建了一个三维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[ i ][ j ] ,其中a是数组的名称,而ij是唯一标识a中每个元素的下标。

初始化二维数组

可以通过指定每行的括号值来初始化多维数组。 以下数组有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];

访问二维数组元素

使用下标访问二维数组中的元素,表示数组的行索引和列索引。 例如

int val = a[2][3];

上面的语句从数组的第3行获取第4个元素。 您可以在上面的digram中验证它。

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] 

D Programming - Associative Arrays

关联数组的索引不一定是整数,并且可以稀疏填充。 关联数组的索引称为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

返回关联数组的引用大小; 它在32位构建中为4,在64位构建中为8。

2

.length

返回关联数组中的值的数量。 与动态数组不同,它是只读的。

3

.dup

创建一个大小相同的新关联数组,并将关联数组的内容复制到其中。

4

.keys

返回动态数组,其元素是关联数组中的键。

5

.values

返回动态数组,其元素是关联数组中的值。

6

.rehash

在适当的位置重新组织关联数组,以便查找更有效。 例如,当程序完成加载符号表并且现在需要快速查找时,rehash是有效的。 返回对重组数组的引用。

7

.byKey()

返回一个适合用作ForeachStatement的Aggregate的委托,它将迭代关联数组的键。

8

.byValue()

返回一个适合用作ForeachStatement的Aggregate的委托,它将迭代关联数组的值。

9

.get(Key key, lazy Value defVal)

查找键; 如果它存在则返回相应的值,否则计算并返回defVal。

10

.remove(Key key)

删除密钥对象。

例子 (Example)

使用上述属性的示例如下所示。

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 Programming - Pointers

D编程指针简单易学。 使用指针可以更轻松地执行某些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具有特殊意义; 它表示指针不是指向可访问的内存位置。

按照惯例,如果指针包含null(零)值,则假定它指向任何内容。 要检查空指针,可以使用if语句,如下所示 -

if(ptr)     // succeeds if p is not null 
if(!ptr)    // succeeds if p is 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]用于设置第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

指针指针

指向指针的指针是多个间接或指针链的形式。 通常,指针包含变量的地址。 当我们定义指向指针的指针时,第一个指针包含第二个指针的地址,它指向包含实际值的位置,如下所示。

C ++指针指针

必须声明一个指向指针的指针的变量。 这是通过在其名称前面放置一个额外的星号来完成的。 例如,以下是声明指向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

传递函数指针 (Passing Pointer to Functions)

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 

函数返回指针 (Return Pointer from Functions)

考虑以下函数,它使用指针返回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]的指针,它是数组余额的第一个元素的地址。 因此,以下程序片段为p分配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

D Programming - Tuples

元组用于将多个值组合为单个对象。 元组包含一系列元素。 元素可以是类型,表达式或别名。 元组的数量和元素在编译时是固定的,在运行时不能更改。

元组具有结构和数组的特征。 元组元素可以是不同类型的结构。 可以通过像数组一样的索引来访问元素。 它们由std.typecons模块中的Tuple模板实现为库特征。 Tuple使用std.typetuple模块中的TypeTuple进行某些操作。

Tuple Using tuple()

元组可以通过函数tuple()构造。 通过索引值访问元组的成员。 一个例子如下所示。

例子 (Example)

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()函数直接构造。 每个成员的类型和名称被指定为两个连续的模板参数。 使用模板创建时,可以通过属性访问成员。

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

扩展属性和功能参数

可以通过.expand属性或切片来扩展Tuple的成员。 此扩展/切片值可以作为函数参数列表传递。 一个例子如下所示。

例子 (Example)

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

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

D Programming - Structs

structure是D编程中另一种用户定义的数据类型,它允许您组合不同类型的数据项。

结构用于表示记录。 假设您想要在图书馆中跟踪您的图书。 您可能希望跟踪每本书的以下属性 -

  • Title
  • Author
  • Subject
  • Book ID

定义一个结构 (Defining a Structure)

要定义结构,必须使用struct语句。 struct语句定义一个新的数据类型,为您的程序提供多个成员。 struct语句的格式是这样的 -

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

structure tag是可选的,每个成员定义是一个普通的变量定义,例如int i; 或浮动f; 或任何其他有效的变量定义。 在分号前面的结构定义的末尾,您可以指定一个或多个可选的结构变量。 以下是您声明图书结构的方式 -

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

访问结构成员 (Accessing Structure Members)

要访问结构的任何成员,请使用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

作为函数参数的结构 (Structures as Function Arguments)

您可以将结构作为函数参数传递,与传递任何其他变量或指针的方式非常相似。 您将以与上面示例中访问过的方式类似的方式访问结构变量 -

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 

结构初始化

结构可以用两种形式初始化,一种使用construtor,另一种使用{}格式。 一个例子如下所示。

例子 (Example)

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

静态成员

静态变量只初始化一次。 例如,要获得书籍的唯一ID,我们可以将book_id设置为静态并增加书籍ID。 一个例子如下所示。

例子 (Example)

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

D Programming - Unions

union是D中可用的特殊数据类型,使您可以在同一内存位置存储不同的数据类型。 您可以定义具有许多成员的联合,但在任何给定时间只能有一个成员包含值。 联合提供了一种有效的方法,可以将相同的内存位置用于多种用途。

在D中定义 Union

要定义联合,必须以与定义结构时非常相似的方式使用union语句。 union语句定义了一个新的数据类型,为您的程序提供了多个成员。 union语句的格式如下 -

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

union tag是可选的,每个成员定义都是一个普通的变量定义,例如int i; 或浮动f; 或任何其他有效的变量定义。 在union的定义结束时,在最后一个分号之前,您可以指定一个或多个union变量,但它是可选的。 这是你定义一个名为Data的联合类型的方式,它有三个成员ifstr -

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

Data类型的变量可以存储整数,浮点数或字符串。 这意味着单个变量(相同的内存位置)可用于存储多种类型的数据。 您可以根据需要在联合内部使用任何内置或用户定义的数据类型。

联盟占用的内存足够大,可以容纳联盟中最大的成员。 例如,在上面的示例中,Data类型将占用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 

访问联盟成员

要访问union的任何成员,我们使用member access operator (.) 。 成员访问运算符被编码为联合变量名称和我们希望访问的联合成员之间的句点。 您可以使用union关键字来定义联合类型的变量。

例子 (Example)

以下示例解释了联合的用法 -

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

在这里,您可以看到union的if成员的值被破坏,因为分配给变量的最终值占用了内存位置,这就是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 Programming - Ranges

范围是元素访问的抽象。 这种抽象使得能够在大量容器类型上使用大量算法。 范围强调容器元素的访问方式,而不是容器的实现方式。 范围是一个非常简单的概念,它基于类型是否定义了某些成员函数集。

范围是D的组成部分.D的切片恰好是最强大的范围RandomAccessRange的实现,并且Phobos中有许多范围特征。 许多Phobos算法返回临时范围对象。 例如,filter()在以下代码中选择大于10的元素实际返回范围对象,而不是数组。

数字范围

数字范围非常常用,这些数字范围的类型为int。 数字范围的一些示例如下所示 -

// Example 1 
foreach (value; 3..7)  
// Example 2 
int[] slice = array[5..10];

Phobos范围

与结构和类接口相关的范围是phobos范围。 Phobos是D语言编译器附带的官方运行时和标准库。

有各种类型的范围,包括 -

  • InputRange
  • ForwardRange
  • BidirectionalRange
  • RandomAccessRange
  • OutputRange

InputRange

最简单的范围是输入范围。 其他范围在它们所基于的范围之上带来了更多要求。 InputRange需要三个函数 -

  • empty - 指定范围是否为空; 当范围被认为是空的时,它必须返回true; 否则是假的。

  • front - 它提供对范围开头元素的访问。

  • popFront() - 它通过删除第一个元素从一开始缩短范围。

例子 (Example)

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的其他三个函数的save成员函数部分,并在调用save函数时返回范围的副本。

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函数,它从范围中删除最后一个元素。

例子 (Example)

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

与ForwardRange相比,还需要opIndex()。 此外,在编译时将已知的空函数的值设置为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()和length。 在使用前面使用的Fibonacci系列和Squares Range示例的详细示例的帮助下解释了这一点。 此示例适用于普通D编译器,但不适用于在线编译器。

例子 (Example)

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表示流式元素输出,类似于将字符发送到stdout。 OutputRange需要支持put(范围,元素)操作。 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"]

D Programming - Aliases

别名,如名称所指,为现有名称提供备用名称。 别名的语法如下所示。

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,另一个人使用int等情况。 为避免此类冲突,我们经常使用数据类型的类型。 一个简单的例子如下所示。

例子 (Example)

import std.stdio;
alias int myAppNumber; 
alias string myAppString;  
void main() { 
   myAppNumber i = 10; 
   myAppString s = "TestString"; 
   writeln(i,s);   
}

编译并执行上述代码时,会产生以下结果 -

10TestString

类变量的别名

通常需要我们需要访问子类中超类的成员变量,这可以通过别名实现,可能使用不同的名称。

如果您不熟悉classesinheritance的概念,请在开始本节之前查看有关classesinheritance的教程。

例子 (Example)

一个简单的例子如下所示。

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

别名这个

别名这提供了用户定义类型的自动类型转换功能。 语法如下所示,其中关键字别名和它写在成员变量或成员函数的任一侧。

alias member_variable_or_member_function this; 

例子 (Example)

下面显示了一个示例,以显示别名的功效。

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)); 
}

在上面的示例中,您可以看到结构矩形在alias this方法的帮助下转换为double值。

编译并执行上述代码时,会产生以下结果 -

30

D Programming - Mixins

Mixins是允许将生成的代码混合到源代码中的结构。 Mixins可以是以下类型 -

  • String Mixins
  • Template Mixins
  • Mixin名称空间

String Mixins

只要该字符串在编译时已知,D就能够将代码作为字符串插入。 string mixins的语法如下所示 -

mixin (compile_time_generated_string)

例子 (Example)

字符串mixins的一个简单示例如下所示。

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

编译并执行上述代码时,会产生以下结果 -

Hello World!

这是另一个我们可以在编译时传递字符串的示例,以便mixin可以使用这些函数来重用代码。 如下所示。

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

编译并执行上述代码时,会产生以下结果 -

str1
str2

模板混合

D模板定义公共代码模式,以便编译器从该模式生成实际实例。 模板可以生成函数,结构,联合,类,接口和任何其他合法的D代码。 模板mixins的语法如下所示。

mixin a_template!(template_parameters)

字符串mixins的一个简单示例如下所示,我们使用类Department创建一个模板,并使用mixin实例化模板,从而使函数setName和printNames可用于结构学院。

例子 (Example)

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 

Mixin名称空间

Mixin名称空间用于避免模板混合中的歧义。 例如,可以有两个变量,一个在main中明确定义,另一个在混合中。当混合名称与周围作用域中的名称相同时,周围作用域中的名称得到用过的。 此示例如下所示。

例子 (Example)

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 Programming - Modules

模块是D的构建块。它们基于一个简单的概念。 每个源文件都是一个模块。 因此,我们编写程序的单个文件是单独的模块。 默认情况下,模块的名称与没有.d扩展名的文件名相同。

显式指定时,模块的名称由module关键字定义,该关键字必须显示为源文件中的第一个非注释行。 例如,假设源文件的名称是“employee.d”。 然后模块的名称由module关键字后跟employee指定。 它如下所示。

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 { }

由于包名称对应于目录名称,因此深度超过一个目录级别的模块的包名称必须反映该层次结构。 例如,如果“company”目录包含“branch”目录,则该目录中的模块名称也将包含分支。

module company.branch.employee;

在程序中使用模块

到目前为止,我们几乎在每个程序中都使用了import关键字,用于将模块引入当前模块 -

import std.stdio;

模块名称也可以包含包名称。 例如,标准。 上面的部分表示stdio是std包的一部分的模块。

模块的位置

编译器通过将包和模块名称直接转换为目录和文件名来查找模块文件。

例如,员工和办公室这两个模块分别位于“company/employee.d”和“animal/office.d”(或“company\employee.d”和“company\office.d”,具体取决于company.employee和company.office的文件系统)。

长模块和短模块名称

程序中使用的名称可以使用模块和包名称拼写,如下所示。

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

通常不需要长名称,但有时会出现名称冲突。 例如,当引用出现在多个模块中的名称时,编译器无法确定其中的名称。 以下程序将拼写出长名称,以区分两个独立的employee结构,这两个结构在两个独立的模块中定义: companycollege.

文件夹公司中的第一个员工模块如下。

module company.employee; 
import std.stdio;
class Employee {
   public: 
      string str; 
   void print() {
      writeln("Company Employee: ",str); 
   } 
}	

文件夹学院的第二个员工模块如下。

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

D Programming - Templates

模板是泛型编程的基础,它涉及以独立于任何特定类型的方式编写代码。

模板是用于创建泛型类或函数的蓝图或公式。

模板是允许将代码描述为模式的功能,以便编译器自动生成程序代码。 部分源代码可以留给编译器填写,直到该部分实际用于程序中。 编译器填写缺少的部分。

功能模板

将函数定义为模板会将其使用的一个或多个类型保留为未指定,稍后由编译器推断。 未指定的类型在模板参数列表中定义,该列表位于函数名称和函数参数列表之间。 因此,函数模板有两个参数列表 -

  • 模板参数列表
  • 功能参数列表
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 Programming - Immutable

我们经常使用可变的变量,但可能有很多情况下不需要可变性。 在这种情况下可以使用不可变的变量。 下面给出了一些可以使用不可变变量的例子。

  • 在数学常量如pi情况下永远不会改变。

  • 对于我们想要保留值的数组而言,它不是变异的要求。

不变性使得可以理解变量是不可变的还是可变的,从而保证某些操作不会改变某些变量。 它还降低了某些类型的程序错误的风险。 D的不变性概念由const和immutable关键字表示。 虽然这两个词本身的含义接近,但它们在程序中的责任是不同的,它们有时是不相容的。

D的不变性概念由const和immutable关键字表示。 虽然这两个词本身的含义接近,但它们在程序中的责任是不同的,它们有时是不相容的。

D中不可变变量的类型

有三种类型的定义变量永远不会发生变异。

  • 枚举常量
  • 不可变的变量
  • const变量

enum D中的常数

枚举常量使得将常量值与有意义的名称相关联成为可能。 一个简单的例子如下所示。

例子 (Example)

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中的不可变变量

在程序执行期间可以确定不可变变量。 它只是指导编译器在初始化之后,它变得不可变。 一个简单的例子如下所示。

例子 (Example)

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变量不能类似于不可变的修改。 不可变变量可以作为不可变参数传递给函数,因此建议使用不可变的const。 前面使用的相同示例针对const进行了修改,如下所示。

例子 (Example)

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删除有关原始变量是可变还是不可变的信息,因此使用不可变使得它传递保留原始类型的其他函数。 一个简单的例子如下所示。

例子 (Example)

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

D Programming - File I/O

文件由std.stdio模块的File结构表示。 文件表示一个字节序列,如果它是文本文件或二进制文件则无关紧要。

D编程语言提供对高级功能的访问以及低级(OS级)调用,以处理存储设备上的文件。

在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 Programming - Concurrency

并发性使程序一次在多个线程上运行。 并发程序的一个示例是Web服务器同时响应许多客户端。 通过消息传递并发很容易,但如果它们基于数据共享则很难编写。

在线程之间传递的数据称为消息。 消息可以由任何类型和任何数量的变量组成。 每个线程都有一个id,用于指定消息的收件人。 任何启动另一个线程的线程都称为新线程的所有者。

在D中启动线程

函数spawn()接受一个指针作为参数,并从该函数启动一个新线程。 由该函数执行的任何操作,包括它可能调用的其他函数,都将在新线程上执行。 所有者和工人都开始分开执行,就好像他们是独立的程序一样。

例子 (Example)

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变量始终是当前线程的id。 你也可以在调用spawn时收到threadId。 一个例子如下所示。

例子 (Example)

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()的函数,稍后将对其进行说明。

以下程序中的所有者向其worker发送一个int类型的消息,并等待来自double类型的worker的消息。 线程继续来回发送消息,直到所有者发送负int。 一个例子如下所示。

例子 (Example)

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中等待

下面显示了使用wait传递消息的简单示例。

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 

D Programming - Exception Handling

例外是在执行程序期间出现的问题。 AD异常是对程序运行时出现的异常情况的响应,例如尝试除以零。

例外提供了一种将控制从程序的一个部分转移到另一个部分的方法。 D异常处理基于三个关键字trycatchthrow构建。

  • throw - 当问题出现时,程序会抛出异常。 这是使用throw关键字完成的。

  • catch - 程序在程序中要处理问题的位置捕获异常并使用异常处理程序。 catch关键字表示捕获异常。

  • try - try块标识激活特定异常的代码块。 接下来是一个或多个捕获块。

假设块将引发异常,则方法使用trycatch关键字的组合捕获异常。 try/catch块放在可能生成异常的代码周围。 try/catch块中的代码称为受保护代码,使用try/catch的语法如下所示 -

try { 
   // protected code 
} 
catch( ExceptionName e1 ) { 
   // catch block 
} 
catch( ExceptionName e2 ) { 
   // catch block 
} 
catch( ExceptionName eN ) { 
   // catch block 
} 

如果try块在不同情况下引发多个异常,您可以列出多个catch语句以捕获不同类型的异常。

在D中抛出异常

可以使用throw语句在代码块中的任何位置抛出异常。 throw语句的操作数确定异常的类型,可以是任何表达式,表达式结果的类型决定了抛出的异常类型。

以下示例在除以零条件时抛出异常 -

例子 (Example)

double division(int a, int b) { 
   if( b == 0 ) { 
      throw new Exception("Division by zero condition!"); 
   }
   return (a/b); 
}

在D中捕获例外情况

try块后面的catch块捕获任何异常。 您可以指定要捕获的异常类型,这由关键字catch后面的括号中显示的异常声明确定。

try { 
   // protected code 
} 
catch( ExceptionName e ) { 
   // code to handle ExceptionName exception 
}

上面的代码捕获ExceptionName类型的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 - Contract Programming

D编程中的合同编程专注于提供简单且易于理解的错误处理方法。 D中的合同编程由三种类型的代码块实现 -

  • body block
  • in block
  • out block

D中的身体阻滞

正文块包含实际的执行功能代码。 输入和输出块是可选的,而主体块是必需的。 一个简单的语法如下所示。

return_type function_name(function_params) 
in { 
   // in block 
} 
out (result) { 
   // in block 
}
body { 
   // actual function block 
}

在D中的预先条件块中

块中的条件是简单的前置条件,用于验证输入参数是否可接受,以及可由代码处理的范围。 块内的好处是所有入口条件可以保持在一起并与函数的实际主体分开。 验证密码最小长度的简单前提如下所示。

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

D Programming - Conditional Compilation

条件编译是选择要编译的代码和不编译的代码的过程,类似于C和C ++中的#if/#else/#endif。 任何未编译的语句仍必须在语法上正确。

条件编译涉及在编译时可评估的条件检查。 像if,for,while这样的运行时条件语句不是条件编译功能。 D的以下特征适用于条件编译 -

  • debug
  • version
  • static if

D中的调试声明

debug在程序开发期间很有用。 仅当启用了-debug编译器开关时,标记为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(tag)语句

调试语句可以被赋予名称(标签)以有选择地包括在程序中。

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子句是可选的。 尽管版本与调试基本相同,但使用单独的关键字有助于区分其不相关的用途。 与debug一样,可以启用多个版本。

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语句不同,static if与执行流程无关; 相反,它确定一段代码是否应该包含在程序中。

if表达式与我们之前看到的is运算符无关,无论是语法还是语义。 它在编译时进行评估。 它产生一个int值,0或1; 取决于括号中指定的表达式。 虽然它所采用的表达式不是逻辑表达式,但是表达式本身用作编译时逻辑表达式。 它在静态条件和模板约束中特别有用。

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 Programming - Classes & Objects

类是D编程的核心功能,支持面向对象的编程,通常称为用户定义类型。

类用于指定对象的形式,它将数据表示和方法组合在一起,以便将数据操作到一个整齐的包中。 类中的数据和函数称为类的成员。

D类定义

定义类时,可以为数据类型定义蓝图。 这实际上并不定义任何数据,但它定义了类名的含义,即类的对象将包含什么以及可以对此类对象执行哪些操作。

类定义以关键字class开头,后跟类名; 和一个由一对花括号括起来的类体。 类定义必须遵循分号或声明列表。 例如,我们使用关键字class定义Box数据类型,如下所示 -

class Box { 
   public: 
      double length;   // Length of a box 
      double breadth;  // Breadth of a box 
      double height;   // Height of a box 
}

关键字public确定其后面的类成员的访问属性。 可以在类对象范围内的任何位置从类外部访问公共成员。 您还可以将类的成员指定为privateprotected ,我们将在子节中讨论。

定义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 类访问修饰符

类成员可以定义为public,private或protected。 默认情况下,成员将被视为私有。

3 构造函数和析构函数

类构造函数是类中的特殊函数,在创建类的新对象时调用该类。 析构函数也是一个特殊函数,在删除创建的对象时调用。

4 D中的这个指针

每个对象都有一个特殊的指针,指向对象本身。

5 指向D类的指针

指向类的指针与指向结构的指针完全相同。 实际上,一个类实际上只是一个包含函数的结构。

6 一个类的静态成员

类的数据成员和函数成员都可以声明为static。

D Programming - Inheritance

面向对象编程中最重要的概念之一是继承。 继承允许根据另一个类定义类,这使得创建和维护应用程序变得更容易。 这也提供了重用代码功能和快速实现时间的机会。

在创建类时,程序员可以指定新类应该继承现有类的成员,而不是编写全新的数据成员和成员函数。 此现有类称为base类,新类称为derived类。

继承的想法实现了这种关系。 例如,哺乳动物IS-A动物,狗IS-A哺乳动物因此也是狗IS-A动物等等。

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

访问控制和继承

派生类可以访问其基类的所有非私有成员。 因此,派生类的成员函数不应该可以访问的基类成员应该在基类中声明为private。

派生类继承所有基类方法,但以下情况除外 -

  • 基类的构造函数,析构函数和复制构造函数。
  • 重载基类的运算符。

多级继承

继承可以是多个级别,它在以下示例中显示。

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 Programming - Overloading

D允许您为同一范围内的function名称或operator指定多个定义,分别称为function overloadingoperator overloading

重载声明是一个声明,它声明的名称与同一作用域中的前一个声明的名称相同,除了两个声明都有不同的参数和明显不同的定义(实现)。

当您调用重载functionoperator ,编译器通过将用于调用函数或运算符的参数类型与定义中指定的参数类型进行比较来确定要使用的最合适的定义。 选择最合适的重载函数或运算符的过程称为overload resolution.

功能重载

您可以在同一范围内对同一函数名称具有多个定义。 函数的定义必须通过参数列表中的参数的类型和/或数量彼此不同。 您不能重载仅由返回类型不同的函数声明。

例子 (Example)

以下示例使用相同的函数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 Programming - Encapsulation

所有D程序都由以下两个基本要素组成 -

  • Program statements (code) - 这是执行操作的程序的一部分,它们被称为函数。

  • Program data - 受程序功能影响的程序信息。

封装是一种面向对象的编程概念,它将数据和功能绑定在一起,将数据一起操作,并保护其免受外部干扰和误用。 数据封装导致了重要的OOP 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 
};

变量length,widthth和height是private 。 这意味着它们只能由Box类的其他成员访问,而不能由程序的任何其他部分访问。 这是实现封装的一种方式。

要使类的某些部分成为public类(即,可以访问程序的其他部分),必须在public关键字之后声明它们。 公共说明符后定义的所有变量或函数都可由程序中的所有其他函数访问。

使一个类成为另一个类的朋友会暴露实现细节并减少封装。 保持每个类的许多细节尽可能隐藏在所有其他类中是理想的。

D中的数据封装

任何使用公共和私有成员实现类的D程序都是数据封装和数据抽象的示例。 考虑以下示例 -

例子 (Example)

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

上面的类将数字加在一起,并返回总和。 公共成员addNumgetTotal是外部世界的接口,用户需要知道它们才能使用该类。 私有成员总数是对外界隐藏的东西,但是课程需要正常运作。

D中的课堂设计策略

除非我们真的需要公开它们,否则我们大多数人都会通过痛苦的经验来学习默认情况下将类成员设为私有。 这只是很好的encapsulation

这种智慧最常用于数据成员,但它同样适用于所有成员,包括虚拟功能。

D Programming - Interfaces

接口是一种强制从其继承的类必须实现某些函数或变量的方法。 函数不能在接口中实现,因为它们总是在从接口继承的类中实现。

使用interface关键字而不是class关键字创建interface ,即使两者在很多方面相似。 如果要从接口继承并且该类已从其他类继承,则需要使用逗号分隔类的名称和接口的名称。

让我们看一个解释接口使用的简单示例。

例子 (Example)

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和static方法,其定义应包含在接口本身中。 派生类不能覆盖这些函数。 一个简单的例子如下所示。

例子 (Example)

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 Programming - Abstract Classes

抽象是指在OOP中创建类抽象的能力。 抽象类是无法实例化的类。 该类的所有其他功能仍然存在,其字段,方法和构造函数都以相同的方式访问。 您无法创建抽象类的实例。

如果一个类是抽象的并且无法实例化,那么除非它是子类,否则该类没有多大用处。 这通常是抽象类在设计阶段出现的方式。 父类包含子类集合的通用功能,但父类本身太抽象而无法单独使用。

在D中使用抽象类

使用abstract关键字声明类抽象。 关键字出现在类关键字之前的类声明中。 以下显示了如何继承和使用抽象类的示例。

例子 (Example)

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

抽象函数 (Abstract Functions)

与函数类似,类也可以是抽象的。 这个函数的实现没有在它的类中给出,但应该在使用抽象函数继承类的类中提供。 上面的例子用抽象函数更新。

例子 (Example)

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 
↑回到顶部↑
WIKI教程 @2018