- D 编程基础
- D 编程 - 首页
- D 编程 - 概述
- D 编程 - 环境
- D 编程 - 基本语法
- D 编程 - 变量
- D 编程 - 数据类型
- D 编程 - 枚举
- D 编程 - 字面量
- D 编程 - 运算符
- D 编程 - 循环
- D 编程 - 条件语句
- D 编程 - 函数
- D 编程 - 字符
- D 编程 - 字符串
- D 编程 - 数组
- D 编程 - 关联数组
- D 编程 - 指针
- D 编程 - 元组
- D 编程 - 结构体
- D 编程 - 联合体
- D 编程 - 范围
- D 编程 - 别名
- D 编程 - 混入
- D 编程 - 模块
- D 编程 - 模板
- D 编程 - 不可变对象
- D 编程 - 文件 I/O
- D 编程 - 并发
- D 编程 - 异常处理
- D 编程 - 合约
- D - 条件编译
- D 编程 - 面向对象
- D 编程 - 类和对象
- D 编程 - 继承
- D 编程 - 重载
- D 编程 - 封装
- D 编程 - 接口
- D 编程 - 抽象类
- D 编程 - 有用资源
- D 编程语言 - 快速指南
- D 编程 - 有用资源
- D 编程 - 讨论
D 编程语言 - 快速指南
D 编程 - 概述
D 编程语言是一种由 Digital Mars 的 Walter Bright 开发的面向对象的多范式系统编程语言。它的开发始于 1999 年,并在 2001 年首次发布。D 的主要版本 (1.0) 于 2007 年发布。目前,我们有 D 的 D2 版本。
D 是一种语法类似 C 的语言,并使用静态类型。D 中包含了许多 C 和 C++ 的特性,但也有一些来自这些语言的特性未包含在 D 中。D 的一些值得注意的补充包括:
- 单元测试
- 真正的模块
- 垃圾回收
- 一等公民数组
- 免费和开源
- 关联数组
- 动态数组
- 内部类
- 闭包
- 匿名函数
- 惰性求值
- 闭包
多范式
D 是一种多范式编程语言。多范式包括:
- 命令式
- 面向对象
- 元编程
- 函数式
- 并发
示例
import std.stdio; void main(string[] args) { writeln("Hello World!"); }
学习 D
学习 D 最重要的事情是专注于概念,而不是迷失在语言的技术细节中。
学习编程语言的目的是成为一名更好的程序员;也就是说,在设计和实现新系统以及维护旧系统方面变得更有效率。
D 的应用范围
D 编程有一些有趣的特性,并且 D 编程的官方网站声称 D 方便、强大且高效。D 编程在核心语言中添加了许多 C 语言以标准库形式提供的特性,例如可调整大小的数组和字符串函数。对于中级到高级程序员来说,D 是一种极好的第二语言。D 在处理内存和管理指针方面更好,而指针在 C++ 中经常导致问题。
D 编程主要用于新程序,而不是现有程序的转换。它提供内置的测试和验证,非常适合大型新项目,这些项目将由大型团队编写数百万行代码。
D 编程 - 环境
D 的本地环境设置
如果您仍然希望为 D 编程语言设置环境,则需要在您的计算机上提供以下两个软件:(a) 文本编辑器,(b) D 编译器。
D 编程的文本编辑器
这将用于键入您的程序。一些编辑器的示例包括 Windows 记事本、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 - 使用开放的 DMD 编译器源代码构建的 GCC 后端的前端。
LDC - 基于 DMD 前端,使用 LLVM 作为其编译器后端的编译器。
以上不同的编译器可以从 D 下载 下载
我们将使用 D 版本 2,建议不要下载 D1。
让我们有一个名为 helloWorld.d 的程序,如下所示。我们将将其用作我们在您选择的平台上运行的第一个程序。
import std.stdio; void main(string[] args) { writeln("Hello World!"); }
我们可以看到以下输出。
$ hello world
在 Windows 上安装 D
下载 Windows 安装程序。
运行下载的可执行文件以安装 D,这可以通过按照屏幕上的说明进行操作。
现在,我们可以通过使用 cd 切换到包含该文件的文件夹,然后使用以下步骤来构建和运行 d 文件(例如 helloWorld.d):
C:\DProgramming> DMD helloWorld.d C:\DProgramming> helloWorld
我们可以看到以下输出。
hello world
C:\DProgramming 是我用来保存示例的文件夹。您可以将其更改为您保存 D 程序的文件夹。
在 Ubuntu/Debian 上安装 D
下载 Debian 安装程序。
运行下载的可执行文件以安装 D,这可以通过按照屏幕上的说明进行操作。
现在,我们可以通过使用 cd 切换到包含该文件的文件夹,然后使用以下步骤来构建和运行 d 文件(例如 helloWorld.d):
$ dmd helloWorld.d $ ./helloWorld
我们可以看到以下输出。
$ hello world
在 Mac OS X 上安装 D
下载 Mac 安装程序。
运行下载的可执行文件以安装 D,这可以通过按照屏幕上的说明进行操作。
现在,我们可以通过使用 cd 切换到包含该文件的文件夹,然后使用以下步骤来构建和运行 d 文件(例如 helloWorld.d):
$ dmd helloWorld.d $ ./helloWorld
我们可以看到以下输出。
$ hello world
在 Fedora 上安装 D
下载 Fedora 安装程序。
运行下载的可执行文件以安装 D,这可以通过按照屏幕上的说明进行操作。
现在,我们可以通过使用 cd 切换到包含该文件的文件夹,然后使用以下步骤来构建和运行 d 文件(例如 helloWorld.d):
$ dmd helloWorld.d $ ./helloWorld
我们可以看到以下输出。
$ hello world
在 OpenSUSE 上安装 D
下载 OpenSUSE 安装程序。
运行下载的可执行文件以安装 D,这可以通过按照屏幕上的说明进行操作。
现在,我们可以通过使用 cd 切换到包含该文件的文件夹,然后使用以下步骤来构建和运行 d 文件(例如 helloWorld.d):
$ dmd helloWorld.d $ ./helloWorld
我们可以看到以下输出。
$ hello world
D IDE
在大多数情况下,我们以插件的形式为 D 提供 IDE 支持。这包括:
Visual D 插件 是 Visual Studio 2005-13 的插件
DDT 是一款 Eclipse 插件,提供代码补全、使用 GDB 进行调试的功能。
Mono-D 代码补全,使用 dmd/ldc/gdc 支持重构。它是 GSoC 2012 的一部分。
Code Blocks 是一个多平台 IDE,支持 D 项目创建、突出显示和调试。
D 编程 - 基本语法
D 非常容易学习,让我们开始创建我们的第一个 D 程序吧!
第一个 D 程序
让我们编写一个简单的 D 程序。所有 D 文件都将具有扩展名 .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 中的导入
库是可重用程序部件的集合,可以使用 import 使其可用于我们的项目。在这里,我们导入标准 io 库,它提供了基本的 I/O 操作。上面程序中使用的 writeln 是 D 标准库中的一个函数。它用于打印一行文本。D 中的库内容被分组到模块中,这些模块基于它们打算执行的任务类型。此程序使用的唯一模块是 std.stdio,它处理数据输入和输出。
主函数
主函数是程序的起点,它确定执行顺序以及程序的其他部分应如何执行。
D 中的标记
D 程序由各种标记组成,标记要么是关键字、标识符、常量、字符串文字或符号。例如,以下 D 语句包含四个标记:-
writeln("test!");
各个标记为:-
writeln ( "test!" ) ;
注释
注释就像 D 程序中的辅助文本,编译器会忽略它们。多行注释以 /* 开头,以字符 */ 结尾,如下所示:-
/* My first program in D */
单行注释在注释的开头使用 //。
// my first program in D
标识符
D 标识符是用于标识变量、函数或任何其他用户定义项目的名称。标识符以字母 A 到 Z 或 a 到 z 或下划线 _ 开头,后跟零个或多个字母、下划线和数字 (0 到 9)。
D 不允许在标识符中使用标点符号,例如 @、$ 和 %。D 是一种区分大小写的编程语言。因此,Manpower 和 manpower 在 D 中是两个不同的标识符。以下是一些可接受的标识符示例:-
mohd zara abc move_name a_123 myname50 _temp j a23b9 retVal
关键字
以下列表显示了 D 中的一些保留字。这些保留字不能用作常量或变量或任何其他标识符名称。
abstract | alias | align | asm |
assert | auto | body | bool |
byte | case | cast | catch |
char | class | const | continue |
dchar | debug | default | delegate |
deprecated | do | double | else |
enum | export | extern | false |
final | finally | float | for |
foreach | function | goto | if |
import | in | inout | int |
interface | invariant | is | long |
macro | mixin | module | new |
null | out | override | package |
pragma | private | protected | public |
real | ref | return | scope |
short | static | struct | super |
switch | synchronized | template | this |
throw | true | try | typeid |
typeof | ubyte | uint | ulong |
union | unittest | ushort | version |
void | wchar | while | with |
D 中的空白字符
仅包含空白字符(可能还有注释)的行称为空行,D 编译器会完全忽略它。
空白字符是 D 中用来描述空格、制表符、换行符和注释的术语。空白字符将语句的一部分与另一部分隔开,并使解释器能够识别语句中一个元素(例如 int)的结束位置和下一个元素的开始位置。因此,在以下语句中:-
local age
在 local 和 age 之间必须至少有一个空白字符(通常是空格),以便解释器能够区分它们。另一方面,在以下语句中
int fruit = apples + oranges //get the total fruits
在 fruit 和 = 之间,或在 = 和 apples 之间不需要空白字符,尽管您可以根据可读性需要添加一些空白字符。
D 编程 - 变量
变量只不过是我们程序可以操作的存储区域的名称。D 中的每个变量都有特定的类型,该类型决定了变量内存的大小和布局;可以存储在该内存中的值的范围;以及可以应用于变量的操作集。
变量名可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。由于 D 区分大小写,因此大写字母和小写字母是不同的。根据上一章中解释的基本类型,将存在以下基本变量类型:
序号 | 类型及描述 |
---|---|
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;这指示编译器创建名为 i、j 和 k 的 int 类型变量。
变量可以在声明中初始化(赋值初始值)。初始化程序由等号后跟常量表达式组成,如下所示:
type variable_name = value;
示例
extern int d = 3, f = 5; // declaration of d and f. int d = 3, f = 5; // definition and initializing d and f. byte z = 22; // definition and initializes z. char x = 'x'; // the variable x has the value 'x'.
在 D 中声明变量时,它始终设置为其“默认初始化程序”,可以通过手动访问T.init来访问,其中T是类型(例如int.init)。整数类型的默认初始化程序为 0,布尔值为 false,浮点数为 NaN。
D 中的变量声明
变量声明向编译器保证存在一个具有给定类型和名称的变量,以便编译器继续进行进一步的编译,而无需关于变量的完整详细信息。变量声明仅在编译时具有意义,编译器在程序链接时需要实际的变量声明。
示例
尝试以下示例,其中变量已在程序开始时声明,但在 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) - 可以作为赋值的左侧或右侧出现的表达式是左值。
右值 (rvalue) - 可以出现在赋值右侧但不能出现在左侧的表达式是右值。
变量是左值,因此可以出现在赋值的左侧。数字文字是右值,因此不能被赋值,也不能出现在左侧。以下语句有效:
int g = 20;
但以下语句无效,会生成编译时错误:
10 = 20;
D 编程 - 数据类型
在 D 编程语言中,数据类型指的是一个广泛的系统,用于声明不同类型的变量或函数。变量的类型决定了它在存储中占据多少空间以及如何解释存储的位模式。
D 中的类型可以分类如下:
序号 | 类型及描述 |
---|---|
1 | 基本类型 它们是算术类型,包含三种类型:(a) 整数,(b) 浮点数和 (c) 字符。 |
2 | 枚举类型 它们也是算术类型。它们用于定义只能在整个程序中分配某些离散整数值的变量。 |
3 | void 类型 类型说明符void表示没有可用的值。 |
4 | 派生类型 它们包括 (a) 指针类型,(b) 数组类型,(c) 结构体类型,(d) 联合体类型和 (e) 函数类型。 |
数组类型和结构体类型统称为聚合类型。函数的类型指定函数返回值的类型。我们将在下一节中看到基本类型,而其他类型将在接下来的章节中介绍。
整数类型
下表列出了标准整数类型及其存储大小和值范围:
类型 | 存储大小 | 值范围 |
---|---|---|
bool | 1 字节 | false 或 true |
byte | 1 字节 | -128 到 127 |
ubyte | 1 字节 | 0 到 255 |
int | 4 字节 | -2,147,483,648 到 2,147,483,647 |
uint | 4 字节 | 0 到 4,294,967,295 |
short | 2 字节 | -32,768 到 32,767 |
ushort | 2 字节 | 0 到 65,535 |
long | 8 字节 | -9223372036854775808 到 9223372036854775807 |
ulong | 8 字节 | 0 到 18446744073709551615 |
要获取类型或变量的确切大小,可以使用sizeof运算符。表达式type.(sizeof)以字节为单位产生对象或类型的存储大小。以下示例获取任何机器上 int 类型的 size:
import std.stdio; int main() { writeln("Length in bytes: ", ulong.sizeof); return 0; }
当您编译并执行上述程序时,它会产生以下结果:
Length in bytes: 8
浮点类型
下表列出了标准浮点类型及其存储大小、值范围及其用途:
类型 | 存储大小 | 值范围 | 用途 |
---|---|---|---|
float | 4 字节 | 1.17549e-38 到 3.40282e+38 | 6 位小数 |
double | 8 字节 | 2.22507e-308 到 1.79769e+308 | 15 位小数 |
real | 10 字节 | 3.3621e-4932 到 1.18973e+4932 | 硬件支持的最大浮点类型或 double;两者中较大的那个。 |
ifloat | 4 字节 | 1.17549e-38i 到 3.40282e+38i | float 的虚数值类型 |
idouble | 8 字节 | 2.22507e-308i 到 1.79769e+308i | double 的虚数值类型 |
ireal | 10 字节 | 3.3621e-4932 到 1.18973e+4932 | real 的虚数值类型 |
cfloat | 8 字节 | 1.17549e-38+1.17549e-38i 到 3.40282e+38+3.40282e+38i | 由两个 float 组成的复数类型 |
cdouble | 16 字节 | 2.22507e-308+2.22507e-308i 到 1.79769e+308+1.79769e+308i | 由两个 double 组成的复数类型 |
creal | 20 字节 | 3.3621e-4932+3.3621e-4932i 到 1.18973e+4932+1.18973e+4932i | 由两个 real 组成的复数类型 |
以下示例打印 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 类型
void 类型指定没有可用的值。它用于两种情况:
序号 | 类型及描述 |
---|---|
1 | 函数返回 void D 中有各种函数不返回值,或者可以说它们返回 void。没有返回值的函数的返回类型为 void。例如,void exit (int status); |
2 | 函数参数为 void D 中有各种函数不接受任何参数。没有参数的函数可以接受 void。例如,int rand(void); |
您可能目前还无法理解 void 类型,因此让我们继续学习,我们将在接下来的章节中介绍这些概念。
D 编程 - 枚举
枚举用于定义命名常数值。使用enum关键字声明枚举类型。
enum语法
枚举定义的最简单形式如下:
enum enum_name { enumeration list }
其中,
enum_name指定枚举类型名称。
enumeration list是以逗号分隔的标识符列表。
枚举列表中的每个符号都代表一个整数,比其前面的符号大 1。默认情况下,第一个枚举符号的值为 0。例如:
enum Days { sun, mon, tue, wed, thu, fri, sat };
示例
以下示例演示了枚举变量的使用:
import std.stdio; enum Days { sun, mon, tue, wed, thu, fri, sat }; int main(string[] args) { Days day; day = Days.mon; writefln("Current Day: %d", day); writefln("Friday : %d", Days.fri); return 0; }
当编译并执行以上代码时,它会产生以下结果:
Current Day: 1 Friday : 5
在上面的程序中,我们可以看到如何使用枚举。最初,我们创建了一个名为day的用户定义枚举 Days 的变量。然后,我们使用点运算符将其设置为mon。我们需要使用 writefln 方法打印已存储的 mon 的值。您还需要指定类型。它是整数类型,因此我们使用 %d 进行打印。
命名枚举的属性
以上示例使用名称 Days 作为枚举,称为命名枚举。这些命名枚举具有以下属性:
Init - 初始化枚举中的第一个值。
min - 返回枚举的最小值。
max - 返回枚举的最大值。
sizeof - 返回枚举的存储大小。
让我们修改前面的示例以使用这些属性。
import std.stdio; // Initialized sun with value 1 enum Days { sun = 1, mon, tue, wed, thu, fri, sat }; int main(string[] args) { writefln("Min : %d", Days.min); writefln("Max : %d", Days.max); writefln("Size of: %d", Days.sizeof); return 0; }
当编译并执行以上代码时,它会产生以下结果:
Min : 1 Max : 7 Size of: 4
匿名枚举
没有名称的枚举称为匿名枚举。匿名枚举的示例如下所示。
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 编程 - 字面量
在程序中作为源代码一部分键入的常数值称为字面量。
字面量可以是任何基本数据类型,可以分为整数字面量、浮点字面量、字符、字符串和布尔值。
同样,字面量就像普通变量一样对待,只是它们的值在定义后无法修改。
整数字面量
整数字面量可以是以下类型:
十进制使用正常的数字表示法,第一个数字不能为 0,因为该数字保留用于指示八进制系统。这并不包括单独的 0:0 为零。
八进制使用 0 作为数字的前缀。
二进制使用 0b 或 0B 作为前缀。
十六进制使用 0x 或 0X 作为前缀。
整数字面量还可以带一个后缀,该后缀是 U 和 L 的组合,分别表示无符号和长整数。后缀可以是大写或小写,并且可以按任何顺序排列。
当您不使用后缀时,编译器会根据值的范围自行选择 int、uint、long 和 ulong。
以下是一些整数字面量的示例:
212 // Legal 215u // Legal 0xFeeL // Legal 078 // Illegal: 8 is not an octal digit 032UU // Illegal: cannot repeat a suffix
以下是各种类型整数字面量的其他示例:
85 // decimal 0213 // octal 0x4b // hexadecimal 30 // int 30u // unsigned int 30l // long 30ul // unsigned long 0b001 // binary
浮点字面量
浮点字面量可以以十进制系统指定,如 1.568,也可以以十六进制系统指定,如 0x91.bc。
在十进制系统中,指数可以通过添加字符 e 或 E 以及其后的数字来表示。例如,2.3e4 表示“2.3 乘以 10 的 4 次方”。可以在指数的值前面指定“+”字符,但它没有效果。例如 2.3e4 和 2.3e + 4 是相同的。
在指数值前添加“ - ”字符会将其含义更改为“除以 10 的幂”。例如,2.3e-2 表示“2.3 除以 10 的 2 次幂”。
在十六进制系统中,值以 0x 或 0X 开头。指数由 p 或 P 指定,而不是 e 或 E。指数不表示“10 的幂”,而是“2 的幂”。例如,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 表示 float,L 说明符表示 real。
布尔文字
有两个布尔文字,它们是标准 D 关键字的一部分 -
值为true,表示真。
值为false,表示假。
你不应该认为 true 的值等于 1,false 的值等于 0。
字符文字
字符文字用单引号括起来。
字符文字可以是普通字符(例如,'x'),转义序列(例如,'\t'),ASCII 字符(例如,'\x21'),Unicode 字符(例如,'\u011e')或命名字符(例如 '\©','\♥', '\€')。
在 D 中,某些字符在前面加上反斜杠后将具有特殊含义,用于表示换行符(\n)或制表符(\t)等。以下列出了一些此类转义序列代码 -
转义序列 | 含义 |
---|---|
\\ | \ 字符 |
\' | ' 字符 |
\" | " 字符 |
\? | ? 字符 |
\a | 警报或铃声 |
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\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!
字符串文字
字符串文字用双引号括起来。字符串包含与字符文字类似的字符:普通字符、转义序列和通用字符。
可以使用字符串文字将长行分成多行,并用空格分隔它们。
以下是一些字符串文字的示例 -
import std.stdio; int main(string[] args) { writeln(q"MY_DELIMITER Hello World Have a good day MY_DELIMITER"); writefln("Have a good day%c",'\x21'); auto str = q{int value = 20; ++value;}; writeln(str); }
在上面的示例中,您可以找到使用 q"MY_DELIMITER MY_DELIMITER" 来表示多行字符。此外,您还可以看到 q{} 用于表示 D 语言语句本身。
D 编程 - 运算符
运算符是一个符号,它告诉编译器执行特定的数学或逻辑操作。D 语言富含内置运算符,并提供以下类型的运算符 -
- 算术运算符
- 关系运算符
- 逻辑运算符
- 按位运算符
- 赋值运算符
- 其他运算符
本章将逐一解释算术、关系、逻辑、按位、赋值和其他运算符。
算术运算符
下表显示了 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-- 得 9 |
关系运算符
下表显示了 D 语言支持的所有关系运算符。假设变量A持有 10,变量B持有 20,则 -
运算符 | 描述 | 示例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等,则条件为真。 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等,则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果大于,则条件为真。 | (A > B) 不为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果小于,则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果大于或等于,则条件为真。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果小于或等于,则条件为真。 | (A <= B) 为真。 |
逻辑运算符
下表显示了 D 语言支持的所有逻辑运算符。假设变量A持有 1,变量B持有 0,则 -
运算符 | 描述 | 示例 |
---|---|---|
&& | 称为逻辑 AND 运算符。如果两个操作数均非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑 OR 运算符。如果两个操作数中的任何一个非零,则条件为真。 | (A || B) 为真。 |
! | 称为逻辑 NOT 运算符。用于反转其操作数的逻辑状态。如果条件为真,则逻辑 NOT 运算符将使其为假。 | !(A && B) 为真。 |
按位运算符
按位运算符对位进行操作,并执行逐位操作。&、| 和 ^ 的真值表如下 -
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
假设 A = 60;和 B = 13。在二进制格式中,它们将如下所示 -
A = 0011 1100
B = 0000 1101
-----------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011
D 语言支持的按位运算符列在下表中。假设变量 A 持有 60,变量 B 持有 13,则 -
运算符 | 描述 | 示例 |
---|---|---|
& | 二进制 AND 运算符如果位存在于两个操作数中,则将其复制到结果中。 | (A & B) 将得到 12,即 0000 1100。 |
| | 二进制 OR 运算符如果位存在于任何一个操作数中,则将其复制。 | (A | B) 得到 61。即 0011 1101。 |
^ | 二进制 XOR 运算符如果位在一个操作数中设置,但在两个操作数中都不设置,则将其复制。 | (A ^ B) 得到 49。即 0011 0001 |
~ | 二进制一补码运算符是一元的,具有“翻转”位的效果。 | (~A ) 得到 -61。在 2 的补码形式中为 1100 0011。 |
<< | 二进制左移运算符。左操作数的值向左移动由右操作数指定的位数。 | A << 2 得到 240。即 1111 0000 |
>> | 二进制右移运算符。左操作数的值向右移动由右操作数指定的位数。 | A >> 2 得到 15。即 0000 1111。 |
赋值运算符
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 相同 |
>>= | 它是右移 AND 赋值运算符。 | C >>= 2 与 C = C >> 2 相同 |
&= | 它是按位 AND 赋值运算符。 | C &= 2 与 C = C & 2 相同 |
^= | 它是按位异或 AND 赋值运算符。 | C ^= 2 与 C = C ^ 2 相同 |
|= | 它是按位或 AND 赋值运算符 | C |= 2 与 C = C | 2 相同 |
其他运算符 - Sizeof 和三元运算符
还有其他一些重要的运算符,包括 D 语言支持的sizeof和? :。
运算符 | 描述 | 示例 |
---|---|---|
sizeof() | 返回变量的大小。 | sizeof(a),其中 a 是整数,返回 4。 |
& | 返回变量的地址。 | &a; 给出变量的实际地址。 |
* | 指向变量的指针。 | *a; 给出指向变量的指针。 |
? : | 条件表达式 | 如果条件为真,则值为 X:否则为值 Y。 |
D 中的运算符优先级
运算符优先级确定表达式中项的组合方式。这会影响表达式的计算方式。某些运算符比其他运算符具有更高的优先级。
例如,乘法运算符比加法运算符具有更高的优先级。
让我们考虑一个表达式
x = 7 + 3 * 2。
这里,x 被赋值为 13,而不是 20。简单的原因是,运算符 * 的优先级高于 +,因此首先计算 3*2,然后将结果加到 7 中。
这里,优先级最高的运算符出现在表格顶部,优先级最低的出现在底部。在一个表达式中,优先级较高的运算符首先被计算。
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘法 | * / % | 从左到右 |
加法 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
按位与 | & | 从左到右 |
按位异或 | ^ | 从左到右 |
按位或 | | | 从左到右 |
逻辑与 | && | 从左到右 |
逻辑或 | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
D 编程 - 循环
可能存在这样一种情况,您需要多次执行一段代码。通常,语句是按顺序执行的:函数中的第一个语句首先执行,然后是第二个语句,依此类推。
编程语言提供各种控制结构,允许更复杂的执行路径。
循环语句多次执行一个语句或一组语句。以下是大多数编程语言中循环语句的一般形式:
D 编程语言提供以下类型的循环来处理循环需求。点击以下链接查看其详细信息。
序号 | 循环类型和描述 |
---|---|
1 | while 循环
当给定条件为真时,它重复一个语句或一组语句。它在执行循环体之前测试条件。 |
2 | for 循环
它多次执行一系列语句,并缩写管理循环变量的代码。 |
3 | do...while 循环
类似于 while 语句,除了它在循环体末尾测试条件。 |
4 | 嵌套循环
您可以在任何其他 while、for 或 do..while 循环内使用一个或多个循环。 |
循环控制语句
循环控制语句更改执行的正常顺序。当执行离开作用域时,在该作用域中创建的所有自动对象都会被销毁。
D 支持以下控制语句:
序号 | 控制语句和描述 |
---|---|
1 | break 语句
终止循环或 switch 语句,并将执行转移到循环或 switch 后面的第一个语句。 |
2 | continue 语句
导致循环跳过其主体其余部分,并在重新迭代之前立即重新测试其条件。 |
无限循环
如果条件永远不会变为假,则循环将变为无限循环。for 循环传统上用于此目的。由于构成 for 循环的三个表达式都不需要,因此您可以通过将条件表达式留空来创建一个无限循环。
import std.stdio; int main () { for( ; ; ) { writefln("This loop will run forever."); } return 0; }
如果条件表达式不存在,则假定其为真。您可能有一个初始化和增量表达式,但 D 程序员更常使用 for(;;) 构造来表示无限循环。
注意 - 您可以通过按 Ctrl + C 键终止无限循环。
D 编程 - 条件语句
决策结构包含要评估的条件以及要执行的两组语句。如果条件为真,则执行一组语句;如果条件为假,则执行另一组语句。
以下是大多数编程语言中常见的典型决策结构的一般形式:
D 编程语言将任何非零和非空值视为真,如果它是零或空,则将其视为假值。
D 编程语言提供以下类型的决策语句。
序号 | 语句和描述 |
---|---|
1 | if 语句
if 语句由一个布尔表达式后跟一个或多个语句组成。 |
2 | if...else 语句
if 语句后面可以跟一个可选的else 语句,当布尔表达式为假时执行。 |
3 | 嵌套 if 语句
您可以在另一个if 或else if 语句中使用一个if 或else if 语句。 |
4 | switch 语句
switch 语句允许测试变量是否与值列表相等。 |
5 | 嵌套 switch 语句
您可以在另一个switch 语句中使用一个switch 语句。 |
D 中的 ? : 运算符
我们在上一章中介绍了条件运算符 ? :,它可以用来替换if...else 语句。它具有以下一般形式
Exp1 ? Exp2 : Exp3;
其中 Exp1、Exp2 和 Exp3 是表达式。注意冒号的使用和放置。
? 表达式的值如下确定:
计算 Exp1。如果为真,则计算 Exp2 并将其作为整个 ? 表达式的值。
如果 Exp1 为假,则计算 Exp3 并将其值作为表达式的值。
D 编程 - 函数
本章介绍了 D 编程中使用的函数。
D 中的函数定义
基本的函数定义由函数头和函数体组成。
语法
return_type function_name( parameter list ) { body of the function }
以下是函数的所有部分:
返回值类型 - 函数可能返回一个值。return_type 是函数返回值的数据类型。某些函数执行所需的运算而不返回值。在这种情况下,return_type 是关键字void。
函数名称 - 这是函数的实际名称。函数名称和参数列表共同构成函数签名。
参数 - 参数类似于占位符。当调用函数时,您将值传递给参数。此值称为实际参数或参数。参数列表是指函数的参数的类型、顺序和数量。参数是可选的;也就是说,函数可能不包含任何参数。
函数体 - 函数体包含定义函数作用的一系列语句。
调用函数
您可以按如下方式调用函数:
function_name(parameter_values)
D 中的函数类型
D 编程支持各种函数,如下所示。
- 纯函数
- Nothrow 函数
- Ref 函数
- Auto 函数
- 可变参数函数
- Inout 函数
- 属性函数
下面解释各种函数。
纯函数
纯函数是指无法访问全局或静态的可变状态(除非通过其参数)。这可以实现基于以下事实的优化:纯函数保证不会修改任何未传递给它的内容,并且在编译器可以保证纯函数不会更改其参数的情况下,它可以实现完全的功能纯度,即保证函数对于相同的参数始终返回相同的结果)。
import std.stdio; int x = 10; immutable int y = 30; const int* p; pure int purefunc(int i,const char* q,immutable int* s) { //writeln("Simple print"); //cannot call impure function 'writeln' debug writeln("in foo()"); // ok, impure code allowed in debug statement // x = i; // error, modifying global state // i = x; // error, reading mutable global state // i = *p; // error, reading const global state i = y; // ok, reading immutable global state auto myvar = new int; // Can use the new expression: return i; } void main() { writeln("Value returned from pure function : ",purefunc(x,null,null)); }
当编译并执行以上代码时,它会产生以下结果:
Value returned from pure function : 30
Nothrow 函数
Nothrow 函数不会抛出任何从 Exception 类派生的异常。Nothrow 函数与抛出函数是协变的。
Nothrow 保证函数不会发出任何异常。
import std.stdio; int add(int a, int b) nothrow { //writeln("adding"); This will fail because writeln may throw int result; try { writeln("adding"); // compiles result = a + b; } catch (Exception error) { // catches all exceptions } return result; } void main() { writeln("Added value is ", add(10,20)); }
当编译并执行以上代码时,它会产生以下结果:
adding Added value is 30
Ref 函数
Ref 函数允许函数通过引用返回。这类似于 ref 函数参数。
import std.stdio; ref int greater(ref int first, ref int second) { return (first > second) ? first : second; } void main() { int a = 1; int b = 2; greater(a, b) += 10; writefln("a: %s, b: %s", a, b); }
当编译并执行以上代码时,它会产生以下结果:
a: 1, b: 12
Auto 函数
Auto 函数可以返回任何类型的值。对要返回的类型没有限制。下面给出了 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
可变参数函数
可变参数函数是指函数的参数数量在运行时确定的函数。在 C 中,至少需要一个参数。但在 D 编程中,没有这样的限制。下面显示了一个简单的示例。
import std.stdio; import core.vararg; void printargs(int x, ...) { for (int i = 0; i < _arguments.length; i++) { write(_arguments[i]); if (_arguments[i] == typeid(int)) { int j = va_arg!(int)(_argptr); writefln("\t%d", j); } else if (_arguments[i] == typeid(long)) { long j = va_arg!(long)(_argptr); writefln("\t%d", j); } else if (_arguments[i] == typeid(double)) { double d = va_arg!(double)(_argptr); writefln("\t%g", d); } } } void main() { printargs(1, 2, 3L, 4.5); }
当编译并执行以上代码时,它会产生以下结果:
int 2 long 3 double 4.5
Inout 函数
inout 可用于函数的参数和返回类型。它类似于可变、常量和不可变的模板。可变性属性是从参数中推导出来的。这意味着 inout 将推导出的可变性属性传递给返回类型。下面显示了一个简单的示例,说明可变性是如何改变的。
import std.stdio; inout(char)[] qoutedWord(inout(char)[] phrase) { return '"' ~ phrase ~ '"'; } void main() { char[] a = "test a".dup; a = qoutedWord(a); writeln(typeof(qoutedWord(a)).stringof," ", a); const(char)[] b = "test b"; b = qoutedWord(b); writeln(typeof(qoutedWord(b)).stringof," ", b); immutable(char)[] c = "test c"; c = qoutedWord(c); writeln(typeof(qoutedWord(c)).stringof," ", c); }
当编译并执行以上代码时,它会产生以下结果:
char[] "test a" const(char)[] "test b" string "test c"
属性函数
属性允许像成员变量一样使用成员函数。它使用 @property 关键字。属性与相关函数相关联,这些函数根据需要返回值。下面给出了属性的一个简单示例。
import std.stdio; struct Rectangle { double width; double height; double area() const @property { return width*height; } void area(double newArea) @property { auto multiplier = newArea / area; width *= multiplier; writeln("Value set!"); } } void main() { auto rectangle = Rectangle(20,10); writeln("The area is ", rectangle.area); rectangle.area(300); writeln("Modified width is ", rectangle.width); }
当编译并执行以上代码时,它会产生以下结果:
The area is 200 Value set! Modified width is 30
D 编程 - 字符
字符是字符串的构建块。书写系统中的任何符号都称为字符:字母表中的字母、数字、标点符号、空格字符等。令人困惑的是,字符本身的构建块也称为字符。
小写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 编程 - 字符串
D 提供以下两种类型的字符串表示:
- 字符数组
- 核心语言字符串
字符数组
我们可以以下列两种形式之一表示字符数组。第一种形式直接提供大小,第二种形式使用 dup 方法,该方法创建字符串“Good morning”的可写副本。
char[9] greeting1 = "Hello all"; char[] greeting2 = "Good morning".dup;
示例
这是一个使用上述简单字符数组形式的简单示例。
import std.stdio; void main(string[] args) { char[9] greeting1 = "Hello all"; writefln("%s",greeting1); char[] greeting2 = "Good morning".dup; writefln("%s",greeting2); }
当编译并执行上述代码时,会产生如下结果:
Hello all Good morning
核心语言字符串
字符串内置于 D 的核心语言中。这些字符串与上面显示的字符数组互操作。以下示例显示了一个简单的字符串表示。
string greeting1 = "Hello all";
示例
import std.stdio; void main(string[] args) { string greeting1 = "Hello all"; writefln("%s",greeting1); char[] greeting2 = "Good morning".dup; writefln("%s",greeting2); string greeting3 = greeting1; writefln("%s",greeting3); }
当编译并执行上述代码时,会产生如下结果:
Hello all Good morning Hello all
字符串连接
D 编程中的字符串连接使用波浪号 (~) 符号。
示例
import std.stdio; void main(string[] args) { string greeting1 = "Good"; char[] greeting2 = "morning".dup; char[] greeting3 = greeting1~" "~greeting2; writefln("%s",greeting3); string greeting4 = "morning"; string greeting5 = greeting1~" "~greeting4; writefln("%s",greeting5); }
当编译并执行上述代码时,会产生如下结果:
Good morning Good morning
字符串长度
可以使用 length 函数获取字符串的字节长度。
示例
import std.stdio; void main(string[] args) { string greeting1 = "Good"; writefln("Length of string greeting1 is %d",greeting1.length); char[] greeting2 = "morning".dup; writefln("Length of string greeting2 is %d",greeting2.length); }
当编译并执行以上代码时,它会产生以下结果:
Length of string greeting1 is 4 Length of string greeting2 is 7
字符串比较
在 D 编程中,字符串比较非常简单。您可以使用 ==、< 和 > 运算符进行字符串比较。
示例
import std.stdio; void main() { string s1 = "Hello"; string s2 = "World"; string s3 = "World"; if (s2 == s3) { writeln("s2: ",s2," and S3: ",s3, " are the same!"); } if (s1 < s2) { writeln("'", s1, "' comes before '", s2, "'."); } else { writeln("'", s2, "' comes before '", s1, "'."); } }
当编译并执行上述代码时,会产生如下结果:
s2: World and S3: World are the same! 'Hello' comes before 'World'.
替换字符串
我们可以使用 string[] 替换字符串。
示例
import std.stdio; import std.string; void main() { char[] s1 = "hello world ".dup; char[] s2 = "sample".dup; s1[6..12] = s2[0..6]; writeln(s1); }
当编译并执行上述代码时,会产生如下结果:
hello sample
索引方法
以下示例解释了用于查找字符串中子字符串位置的索引方法,包括 indexOf 和 lastIndexOf。
示例
import std.stdio; import std.string; void main() { char[] s1 = "hello World ".dup; writeln("indexOf of llo in hello is ",std.string.indexOf(s1,"llo")); writeln(s1); writeln("lastIndexOf of O in hello is " ,std.string.lastIndexOf(s1,"O",CaseSensitive.no)); }
当编译并执行以上代码时,它会产生以下结果:
indexOf.of llo in hello is 2 hello World lastIndexOf of O in hello is 7
处理大小写
以下示例显示了用于更改大小写的方法。
示例
import std.stdio; import std.string; void main() { char[] s1 = "hello World ".dup; writeln("Capitalized string of s1 is ",capitalize(s1)); writeln("Uppercase string of s1 is ",toUpper(s1)); writeln("Lowercase string of s1 is ",toLower(s1)); }
当编译并执行以上代码时,它会产生以下结果:
Capitalized string of s1 is Hello world Uppercase string of s1 is HELLO WORLD Lowercase string of s1 is hello world
限制字符
以下示例显示了字符串中限制字符。
示例
import std.stdio; import std.string; void main() { string s = "H123Hello1"; string result = munch(s, "0123456789H"); writeln("Restrict trailing characters:",result); result = squeeze(s, "0123456789H"); writeln("Restrict leading characters:",result); s = " Hello World "; writeln("Stripping leading and trailing whitespace:",strip(s)); }
当编译并执行以上代码时,它会产生以下结果:
Restrict trailing characters:H123H Restrict leading characters:ello1 Stripping leading and trailing whitespace:Hello World
D 编程 - 数组
D 编程语言提供了一种名为数组的数据结构,它存储相同类型元素的固定大小的顺序集合。数组用于存储数据集合。通常,将数组视为相同类型变量的集合更有用。
与其声明单个变量,例如 number0、number1、... 和 number99,不如声明一个数组变量,例如 numbers,并使用 numbers[0]、numbers[1] 和 ...、numbers[99] 来表示单个变量。数组中的特定元素由索引访问。
所有数组都由连续的内存位置组成。最低地址对应于第一个元素,最高地址对应于最后一个元素。
声明数组
要在 D 编程语言中声明数组,程序员需要指定元素的类型以及数组所需的元素数量,如下所示:
type arrayName [ arraySize ];
这称为一维数组。arraySize必须是一个大于零的整数常量,而type可以是任何有效的 D 编程语言数据类型。例如,要声明一个名为balance的 10 元素数组,其类型为 double,请使用以下语句:
double balance[10];
初始化数组
您可以使用以下方法逐一初始化 D 编程语言数组元素或使用单个语句初始化:
double balance[5] = [1000.0, 2.0, 3.4, 17.0, 50.0];
右侧方括号 [ ] 中值的个数不能大于你在方括号 [ ] 中为数组声明的元素个数。以下示例分配数组的单个元素:
如果省略数组的大小,则会创建一个足够大的数组来容纳初始化。因此,如果你写
double balance[] = [1000.0, 2.0, 3.4, 17.0, 50.0];
那么你将创建与你在前一个示例中创建的完全相同的数组。
balance[4] = 50.0;
上述语句将数组中第 5 个元素的值赋为 50.0。索引为 4 的数组将是第 5 个元素,即最后一个元素,因为所有数组的第一个元素的索引都为 0,也称为基索引。下图显示了我们上面讨论的相同数组:
访问数组元素
通过索引数组名称来访问元素。这是通过在数组名称后用方括号括起元素的索引来完成的。例如:
double salary = balance[9];
上述语句从数组中获取第 10 个元素并将该值赋给变量 salary。以下示例实现了数组的声明、赋值和访问:
import std.stdio; void main() { int n[ 10 ]; // n is an array of 10 integers // initialize elements of array n to 0 for ( int i = 0; i < 10; i++ ) { n[ i ] = i + 100; // set element at location i to i + 100 } writeln("Element \t Value"); // output each array element's value for ( int j = 0; j < 10; j++ ) { writeln(j," \t ",n[j]); } }
当编译并执行以上代码时,它会产生以下结果:
Element Value 0 100 1 101 2 102 3 103 4 104 5 105 6 106 7 107 8 108 9 109
静态数组与动态数组
如果在编写程序时指定了数组的长度,则该数组为静态数组。当长度可以在程序执行期间更改时,该数组为动态数组。
定义动态数组比定义固定长度数组更简单,因为省略长度会创建一个动态数组:
int[] dynamicArray;
数组属性
以下是数组的属性:
序号 | 属性 & 描述 |
---|---|
1 |
.init 静态数组返回一个数组字面量,该字面量的每个元素都是数组元素类型的 .init 属性。 |
2 |
.sizeof 静态数组返回数组长度乘以每个数组元素的字节数,而动态数组返回动态数组引用的大小,在 32 位构建中为 8,在 64 位构建中为 16。 |
3 |
.length 静态数组返回数组中的元素个数,而动态数组用于获取/设置数组中的元素个数。Length 的类型为 size_t。 |
4 |
.ptr 返回指向数组第一个元素的指针。 |
5 |
.dup 创建一个相同大小的动态数组并将数组的内容复制到其中。 |
6 |
.idup 创建一个相同大小的动态数组并将数组的内容复制到其中。副本被类型化为不可变的。 |
7 |
.reverse 就地反转数组中元素的顺序。返回数组。 |
8 |
.sort 就地排序数组中元素的顺序。返回数组。 |
示例
以下示例解释了数组的各种属性:
import std.stdio; void main() { int n[ 5 ]; // n is an array of 5 integers // initialize elements of array n to 0 for ( int i = 0; i < 5; i++ ) { n[ i ] = i + 100; // set element at location i to i + 100 } writeln("Initialized value:",n.init); writeln("Length: ",n.length); writeln("Size of: ",n.sizeof); writeln("Pointer:",n.ptr); writeln("Duplicate Array: ",n.dup); writeln("iDuplicate Array: ",n.idup); n = n.reverse.dup; writeln("Reversed Array: ",n); writeln("Sorted Array: ",n.sort); }
当编译并执行以上代码时,它会产生以下结果:
Initialized value:[0, 0, 0, 0, 0] Length: 5 Size of: 20 Pointer:7FFF5A373920 Duplicate Array: [100, 101, 102, 103, 104] iDuplicate Array: [100, 101, 102, 103, 104] Reversed Array: [104, 103, 102, 101, 100] Sorted Array: [100, 101, 102, 103, 104]
D 语言中的多维数组
D 编程语言允许使用多维数组。以下是多维数组声明的一般形式:
type name[size1][size2]...[sizeN];
示例
以下声明创建了一个三维 5 . 10 . 4 整数数组:
int threedim[5][10][4];
D 语言中的二维数组
多维数组最简单的形式是二维数组。二维数组本质上是一维数组的列表。要声明大小为 [x, y] 的二维整数数组,您可以编写如下语法:
type arrayName [ x ][ y ];
其中 type 可以是任何有效的 D 编程语言数据类型,而 arrayName 将是有效的 D 编程语言标识符。
其中 type 可以是任何有效的 D 编程语言数据类型,而 arrayName 是有效的 D 编程语言标识符。
二维数组可以被认为是一个表格,它有 x 行 y 列。包含三行四列的二维数组 a 可以显示如下:
因此,数组 a 中的每个元素都由一个元素标识为 a[ i ][ j ],其中 a 是数组的名称,i 和 j 是唯一标识 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 个元素。您可以在上面的图中验证它。
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 编程 - 关联数组
关联数组的索引不一定是整数,并且可以是稀疏填充的。关联数组的索引称为键,其类型称为键类型。
关联数组通过在数组声明的 [ ] 中放置 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
关联数组的属性
以下是关联数组的属性:
序号 | 属性 & 描述 |
---|---|
1 | .sizeof 返回对关联数组的引用的大小;在 32 位构建中为 4,在 64 位构建中为 8。 |
2 | .length 返回关联数组中的值的数量。与动态数组不同,它是只读的。 |
3 | .dup 创建一个相同大小的新关联数组并将关联数组的内容复制到其中。 |
4 | .keys 返回动态数组,其元素是关联数组中的键。 |
5 | .values 返回动态数组,其元素是关联数组中的值。 |
6 | .rehash 就地重新组织关联数组,以便查找更有效率。例如,当程序完成加载符号表并且现在需要在其中进行快速查找时,rehash 会非常有效。返回对重新组织后的数组的引用。 |
7 | .byKey() 返回一个委托,适合用作 ForeachStatement 的聚合器,它将迭代关联数组的键。 |
8 | .byValue() 返回一个委托,适合用作 ForeachStatement 的聚合器,它将迭代关联数组的值。 |
9 | .get(Key key, lazy Value defVal) 查找键;如果它存在则返回相应的值,否则评估并返回 defVal。 |
10 | .remove(Key key) 删除键对应的对象。 |
示例
使用上述属性的示例如下所示。
import std.stdio; void main () { int[string] array1; array1["test"] = 3; array1["test2"] = 20; writeln("sizeof: ",array1.sizeof); writeln("length: ",array1.length); writeln("dup: ",array1.dup); array1.rehash; writeln("rehashed: ",array1); writeln("keys: ",array1.keys); writeln("values: ",array1.values); foreach (key; array1.byKey) { writeln("by key: ",key); } foreach (value; array1.byValue) { writeln("by value ",value); } writeln("get value for key test: ",array1.get("test",10)); writeln("get value for key test3: ",array1.get("test3",10)); array1.remove("test"); writeln(array1); }
当编译并执行以上代码时,它会产生以下结果:
sizeof: 8 length: 2 dup: ["test":3, "test2":20] rehashed: ["test":3, "test2":20] keys: ["test", "test2"] values: [3, 20] by key: test by key: test2 by value 3 by value 20 get value for key test: 3 get value for key test3: 10 ["test2":20]
D 编程 - 指针
D 编程语言的指针易于学习且有趣。一些 D 编程语言任务使用指针更容易完成,而其他 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
什么是指针?
指针是一个变量,其值为另一个变量的地址。与任何变量或常量一样,在使用指针之前必须先声明它。指针变量声明的一般形式为:
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 的指针称为空指针。
空指针是一个值为零的常量,在包括 iostream 在内的几个标准库中定义。考虑以下程序:
import std.stdio; void main () { int *ptr = null; writeln("The value of ptr is " , ptr) ; }
当编译并执行以上代码时,它会产生以下结果:
The value of ptr is null
在大多数操作系统上,程序不允许访问地址 0 处的内存,因为该内存由操作系统保留。但是;内存地址 0 具有特殊的意义;它表示指针并非旨在指向可访问的内存位置。
按照惯例,如果指针包含空(零)值,则假定它不指向任何内容。要检查空指针,您可以使用 if 语句,如下所示:
if(ptr) // succeeds if p is not null if(!ptr) // succeeds if p is null
因此,如果所有未使用的指针都赋予空值,并且避免使用空指针,则可以避免意外错误地使用未初始化的指针。很多时候,未初始化的变量会保存一些垃圾值,这使得程序调试变得困难。
指针运算
指针可以使用四种算术运算符:++、--、+ 和 -。
为了理解指针算术,让我们考虑一个名为ptr的整数指针,它指向地址 1000。假设 32 位整数,让我们对指针执行以下算术运算:
ptr++
那么ptr将指向地址 1004,因为每次 ptr 增加时,它都指向下一个整数。此操作将指针移动到下一个内存位置,而不会影响内存位置的实际值。
如果ptr指向地址为 1000 的字符,则上述操作指向地址 1001,因为下一个字符将在 1001 处可用。
递增指针
我们更喜欢在程序中使用指针而不是数组,因为变量指针可以递增,而数组名则不能递增,因为它是一个常量指针。以下程序递增变量指针以访问数组的每个后续元素:
import std.stdio; const int MAX = 3; void main () { int var[MAX] = [10, 100, 200]; int *ptr = &var[0]; for (int i = 0; i < MAX; i++, ptr++) { writeln("Address of var[" , i , "] = ",ptr); writeln("Value of var[" , i , "] = ",*ptr); } }
当编译并执行以上代码时,它会产生以下结果:
Address of var[0] = 18FDBC Value of var[0] = 10 Address of var[1] = 18FDC0 Value of var[1] = 100 Address of var[2] = 18FDC4 Value of var[2] = 200
指针与数组
指针和数组密切相关。但是,指针和数组不能完全互换。例如,考虑以下程序:
import std.stdio; const int MAX = 3; void main () { int var[MAX] = [10, 100, 200]; int *ptr = &var[0]; var.ptr[2] = 290; ptr[0] = 220; for (int i = 0; i < MAX; i++, ptr++) { writeln("Address of var[" , i , "] = ",ptr); writeln("Value of var[" , i , "] = ",*ptr); } }
在上面的程序中,您可以看到 var.ptr[2] 用于设置第二个元素,而 ptr[0] 用于设置第零个元素。递增运算符可以与 ptr 一起使用,但不能与 var 一起使用。
当编译并执行以上代码时,它会产生以下结果:
Address of var[0] = 18FDBC Value of var[0] = 220 Address of var[1] = 18FDC0 Value of var[1] = 100 Address of var[2] = 18FDC4 Value of var[2] = 290
指向指针的指针
指向指针的指针是一种多级间接寻址或指针链的形式。通常,指针包含变量的地址。当我们定义指向指针的指针时,第一个指针包含第二个指针的地址,第二个指针指向包含实际值的地址,如下所示。
作为指向指针的指针的变量必须声明为这种类型。这通过在其名称前面放置一个额外的星号来完成。例如,以下是声明指向 int 类型指针的指针的语法:
int **var;
当目标值由指向指针的指针间接指向时,访问该值需要将星号运算符应用两次,如下面的示例所示:
import std.stdio; const int MAX = 3; void main () { int var = 3000; writeln("Value of var :" , var); int *ptr = &var; writeln("Value available at *ptr :" ,*ptr); int **pptr = &ptr; writeln("Value available at **pptr :",**pptr); }
当编译并执行以上代码时,它会产生以下结果:
Value of var :3000 Value available at *ptr :3000 Value available at **pptr :3000
将指针传递给函数
D 允许您将指针传递给函数。为此,它只需将函数参数声明为指针类型。
以下简单示例将指针传递给函数。
import std.stdio; void main () { // an int array with 5 elements. int balance[5] = [1000, 2, 3, 17, 50]; double avg; avg = getAverage( &balance[0], 5 ) ; writeln("Average is :" , avg); } double getAverage(int *arr, int size) { int i; double avg, sum = 0; for (i = 0; i < size; ++i) { sum += arr[i]; } avg = sum/size; return avg; }
当以上代码编译并执行时,会产生以下结果:
Average is :214.4
从函数返回指针
考虑以下函数,该函数使用指针返回 10 个数字,这意味着第一个数组元素的地址。
import std.stdio; void main () { int *p = getNumber(); for ( int i = 0; i < 10; i++ ) { writeln("*(p + " , i , ") : ",*(p + i)); } } int * getNumber( ) { static int r [10]; for (int i = 0; i < 10; ++i) { r[i] = i; } return &r[0]; }
当编译并执行以上代码时,它会产生以下结果:
*(p + 0) : 0 *(p + 1) : 1 *(p + 2) : 2 *(p + 3) : 3 *(p + 4) : 4 *(p + 5) : 5 *(p + 6) : 6 *(p + 7) : 7 *(p + 8) : 8 *(p + 9) : 9
指向数组的指针
数组名是指向数组第一个元素的常量指针。因此,在声明中:
double balance[50];
balance 是指向 &balance[0] 的指针,它是数组 balance 的第一个元素的地址。因此,以下程序片段将p赋值为balance 的第一个元素的地址:
double *p; double balance[10]; p = balance;
将数组名用作常量指针以及反过来都是合法的。因此,*(balance + 4) 是访问 balance[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 编程 - 元组
元组用于将多个值组合成单个对象。元组包含一系列元素。这些元素可以是类型、表达式或别名。元组的数量和元素在编译时是固定的,并且在运行时不能更改。
元组具有结构体和数组的特征。元组元素可以是不同的类型,如结构体。元素可以通过索引访问,就像数组一样。它们由 std.typecons 模块中的 Tuple 模板作为库特性实现。Tuple 利用 std.typetuple 模块中的 TypeTuple 进行一些操作。
使用 tuple() 的元组
元组可以通过函数 tuple() 构造。元组的成员通过索引值访问。下面显示了一个示例。
示例
import std.stdio; import std.typecons; void main() { auto myTuple = tuple(1, "Tuts"); writeln(myTuple); writeln(myTuple[0]); writeln(myTuple[1]); }
当编译并执行以上代码时,它会产生以下结果:
Tuple!(int, string)(1, "Tuts") 1 Tuts
使用 Tuple 模板的元组
元组也可以直接通过 Tuple 模板而不是 tuple() 函数构造。每个成员的类型和名称指定为两个连续的模板参数。使用模板创建时,可以通过属性访问成员。
import std.stdio; import std.typecons; void main() { auto myTuple = Tuple!(int, "id",string, "value")(1, "Tuts"); writeln(myTuple); writeln("by index 0 : ", myTuple[0]); writeln("by .id : ", myTuple.id); writeln("by index 1 : ", myTuple[1]); writeln("by .value ", myTuple.value); }
当以上代码编译并执行时,会产生以下结果
Tuple!(int, "id", string, "value")(1, "Tuts") by index 0 : 1 by .id : 1 by index 1 : Tuts by .value Tuts
扩展属性和函数参数
Tuple 的成员可以通过 .expand 属性或切片扩展。此扩展/切片值可以作为函数参数列表传递。下面显示了一个示例。
示例
import std.stdio; import std.typecons; void method1(int a, string b, float c, char d) { writeln("method 1 ",a,"\t",b,"\t",c,"\t",d); } void method2(int a, float b, char c) { writeln("method 2 ",a,"\t",b,"\t",c); } void main() { auto myTuple = tuple(5, "my string", 3.3, 'r'); writeln("method1 call 1"); method1(myTuple[]); writeln("method1 call 2"); method1(myTuple.expand); writeln("method2 call 1"); method2(myTuple[0], myTuple[$-2..$]); }
当编译并执行以上代码时,它会产生以下结果:
method1 call 1 method 1 5 my string 3.3 r method1 call 2 method 1 5 my string 3.3 r method2 call 1 method 2 5 3.3 r
TypeTuple
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 编程 - 结构体
结构体是 D 编程语言中另一种用户定义的数据类型,它允许您组合不同类型的数据项。
结构体用于表示记录。假设您想跟踪图书馆中的书籍。您可能希望跟踪有关每本书的以下属性:
- 标题
- 作者
- 主题
- 图书 ID
定义结构体
要定义结构体,必须使用struct语句。struct 语句定义了一种新的数据类型,您的程序可以使用多个成员。struct 语句的格式如下:
struct [structure tag] { member definition; member definition; ... member definition; } [one or more structure variables];
结构体标签是可选的,每个成员定义都是一个正常的变量定义,例如 int i; 或 float f; 或任何其他有效的变量定义。在结构体定义的末尾(分号之前),您可以指定一个或多个结构体变量,这些变量是可选的。以下是声明 Books 结构体的方式:
struct Books { char [] title; char [] author; char [] subject; int book_id; };
访问结构体成员
要访问结构体的任何成员,可以使用成员访问运算符 (.)。成员访问运算符被编码为结构体变量名和我们希望访问的结构体成员之间的句点。您将使用struct关键字定义结构体类型变量。以下示例说明了结构体用法:
import std.stdio; struct Books { char [] title; char [] author; char [] subject; int book_id; }; void main( ) { Books Book1; /* Declare Book1 of type Book */ Books Book2; /* Declare Book2 of type Book */ /* book 1 specification */ Book1.title = "D Programming".dup; Book1.author = "Raj".dup; Book1.subject = "D Programming Tutorial".dup; Book1.book_id = 6495407; /* book 2 specification */ Book2.title = "D Programming".dup; Book2.author = "Raj".dup; Book2.subject = "D Programming Tutorial".dup; Book2.book_id = 6495700; /* print Book1 info */ writeln( "Book 1 title : ", Book1.title); writeln( "Book 1 author : ", Book1.author); writeln( "Book 1 subject : ", Book1.subject); writeln( "Book 1 book_id : ", Book1.book_id); /* print Book2 info */ writeln( "Book 2 title : ", Book2.title); writeln( "Book 2 author : ", Book2.author); writeln( "Book 2 subject : ", Book2.subject); writeln( "Book 2 book_id : ", Book2.book_id); }
当编译并执行以上代码时,它会产生以下结果:
Book 1 title : D Programming Book 1 author : Raj Book 1 subject : D Programming Tutorial Book 1 book_id : 6495407 Book 2 title : D Programming Book 2 author : Raj Book 2 subject : D Programming Tutorial Book 2 book_id : 6495700
结构体作为函数参数
您可以像传递任何其他变量或指针一样,将结构体作为函数参数传递。您将以与在上述示例中访问相同的方式访问结构体变量:
import std.stdio; struct Books { char [] title; char [] author; char [] subject; int book_id; }; void main( ) { Books Book1; /* Declare Book1 of type Book */ Books Book2; /* Declare Book2 of type Book */ /* book 1 specification */ Book1.title = "D Programming".dup; Book1.author = "Raj".dup; Book1.subject = "D Programming Tutorial".dup; Book1.book_id = 6495407; /* book 2 specification */ Book2.title = "D Programming".dup; Book2.author = "Raj".dup; Book2.subject = "D Programming Tutorial".dup; Book2.book_id = 6495700; /* print Book1 info */ printBook( Book1 ); /* Print Book2 info */ printBook( Book2 ); } void printBook( Books book ) { writeln( "Book title : ", book.title); writeln( "Book author : ", book.author); writeln( "Book subject : ", book.subject); writeln( "Book book_id : ", book.book_id); }
当编译并执行以上代码时,它会产生以下结果:
Book title : D Programming Book author : Raj Book subject : D Programming Tutorial Book book_id : 6495407 Book title : D Programming Book author : Raj Book subject : D Programming Tutorial Book book_id : 6495700
结构体初始化
结构体可以通过两种形式初始化,一种使用构造函数,另一种使用 {} 格式。下面显示了一个示例。
示例
import std.stdio; struct Books { char [] title; char [] subject = "Empty".dup; int book_id = -1; char [] author = "Raj".dup; }; void main( ) { Books Book1 = Books("D Programming".dup, "D Programming Tutorial".dup, 6495407 ); printBook( Book1 ); Books Book2 = Books("D Programming".dup, "D Programming Tutorial".dup, 6495407,"Raj".dup ); printBook( Book2 ); Books Book3 = {title:"Obj C programming".dup, book_id : 1001}; printBook( Book3 ); } void printBook( Books book ) { writeln( "Book title : ", book.title); writeln( "Book author : ", book.author); writeln( "Book subject : ", book.subject); writeln( "Book book_id : ", book.book_id); }
当编译并执行以上代码时,它会产生以下结果:
Book title : D Programming Book author : Raj Book subject : D Programming Tutorial Book book_id : 6495407 Book title : D Programming Book author : Raj Book subject : D Programming Tutorial Book book_id : 6495407 Book title : Obj C programming Book author : Raj Book subject : Empty Book book_id : 1001
静态成员
静态变量仅初始化一次。例如,要为书籍提供唯一的 ID,我们可以将 book_id 设置为静态并递增图书 ID。下面显示了一个示例。
示例
import std.stdio; struct Books { char [] title; char [] subject = "Empty".dup; int book_id; char [] author = "Raj".dup; static int id = 1000; }; void main( ) { Books Book1 = Books("D Programming".dup, "D Programming Tutorial".dup,++Books.id ); printBook( Book1 ); Books Book2 = Books("D Programming".dup, "D Programming Tutorial".dup,++Books.id); printBook( Book2 ); Books Book3 = {title:"Obj C programming".dup, book_id:++Books.id}; printBook( Book3 ); } void printBook( Books book ) { writeln( "Book title : ", book.title); writeln( "Book author : ", book.author); writeln( "Book subject : ", book.subject); writeln( "Book book_id : ", book.book_id); }
当编译并执行以上代码时,它会产生以下结果:
Book title : D Programming Book author : Raj Book subject : D Programming Tutorial Book book_id : 1001 Book title : D Programming Book author : Raj Book subject : D Programming Tutorial Book book_id : 1002 Book title : Obj C programming Book author : Raj Book subject : Empty Book book_id : 1003
D 编程 - 联合体
联合体是 D 中一种特殊的数据类型,它使您能够在同一内存位置存储不同的数据类型。您可以定义一个具有多个成员的联合体,但在任何给定时间,只有一个成员可以包含值。联合体提供了一种有效的方式来将同一内存位置用于多种用途。
在 D 中定义联合体
要定义联合体,必须使用 union 语句,这与定义结构体的方式非常相似。union 语句定义了一种新的数据类型,您的程序可以使用多个成员。union 语句的格式如下:
union [union tag] { member definition; member definition; ... member definition; } [one or more union variables];
联合体标签是可选的,每个成员定义都是一个正常的变量定义,例如 int i; 或 float f; 或任何其他有效的变量定义。在联合体定义的末尾,在最后一个分号之前,您可以指定一个或多个联合体变量,但这是可选的。以下是定义名为 Data 的联合体类型的方式,该类型具有三个成员i、f和str:
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 关键字定义联合体类型变量。
示例
以下示例说明了联合体的用法:
import std.stdio; union Data { int i; float f; char str[13]; }; void main( ) { Data data; data.i = 10; data.f = 220.5; data.str = "D Programming".dup; writeln( "size of : ", data.sizeof); writeln( "data.i : ", data.i); writeln( "data.f : ", data.f); writeln( "data.str : ", data.str); }
当编译并执行以上代码时,它会产生以下结果:
size of : 16 data.i : 1917853764 data.f : 4.12236e+30 data.str : D Programming
在这里,您可以看到联合体的i和f成员的值已损坏,因为分配给变量的最终值已占据了内存位置,这就是str成员的值能够很好地打印出来的原因。
现在让我们再次查看同一个示例,在该示例中,我们将一次使用一个变量,这是使用联合体的主要目的:
修改后的示例
import std.stdio; union Data { int i; float f; char str[13]; }; void main( ) { Data data; writeln( "size of : ", data.sizeof); data.i = 10; writeln( "data.i : ", data.i); data.f = 220.5; writeln( "data.f : ", data.f); data.str = "D Programming".dup; writeln( "data.str : ", data.str); }
当编译并执行以上代码时,它会产生以下结果:
size of : 16 data.i : 10 data.f : 220.5 data.str : D Programming
在这里,所有成员都能够很好地打印出来,因为一次只使用了一个成员。
D 编程 - 范围
范围是元素访问的抽象。这种抽象使得能够在大量容器类型上使用大量算法。范围强调如何访问容器元素,而不是容器如何实现。范围是一个非常简单的概念,它基于类型是否定义了某些成员函数集。
范围是D语言中不可或缺的一部分。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;否则返回false。
front - 提供对范围开头元素的访问。
popFront() - 通过移除第一个元素来从开头缩短范围。
示例
import std.stdio; import std.string; struct Student { string name; int number; string toString() const { return format("%s(%s)", name, number); } } struct School { Student[] students; } struct StudentRange { Student[] students; this(School school) { this.students = school.students; } @property bool empty() const { return students.length == 0; } @property ref Student front() { return students[0]; } void popFront() { students = students[1 .. $]; } } void main() { auto school = School([ Student("Raj", 1), Student("John", 2), Student("Ram", 3)]); auto range = StudentRange(school); writeln(range); writeln(school.students.length); writeln(range.front); range.popFront; writeln(range.empty); writeln(range); }
当编译并执行以上代码时,它会产生以下结果:
[Raj(1), John(2), Ram(3)] 3 Raj(1) false [John(2), Ram(3)]
ForwardRange(前向范围)
ForwardRange此外还需要InputRange的其他三个函数中的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函数,它从范围中移除最后一个元素。
示例
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。这将通过使用斐波那契数列和前面使用的正方形范围示例的详细示例来解释。此示例在普通的D编译器上运行良好,但在在线编译器上无法运行。
示例
import std.array; import std.stdio; import std.string; import std.range; import std.algorithm; struct FibonacciSeries { int first = 0; int second = 1; enum empty = false; // infinite range @property int front() const { return first; } void popFront() { int third = first + second; first = second; second = third; } @property FibonacciSeries save() const { return this; } } void report(T)(const dchar[] title, const ref T range) { writefln("%40s: %s", title, range.take(5)); } class SquaresRange { int first; this(int first = 0) { this.first = first; } enum empty = false; @property int front() const { return opIndex(0); } void popFront() { ++first; } @property SquaresRange save() const { return new SquaresRange(first); } int opIndex(size_t index) const { /* This function operates at constant time */ immutable integerValue = first + cast(int)index; return integerValue * integerValue; } } bool are_lastTwoDigitsSame(int value) { /* Must have at least two digits */ if (value < 10) { return false; } /* Last two digits must be divisible by 11 */ immutable lastTwoDigits = value % 100; return (lastTwoDigits % 11) == 0; } struct Together { const(int)[][] slices; this(const(int)[][] slices ...) { this.slices = slices.dup; clearFront(); clearBack(); } private void clearFront() { while (!slices.empty && slices.front.empty) { slices.popFront(); } } private void clearBack() { while (!slices.empty && slices.back.empty) { slices.popBack(); } } @property bool empty() const { return slices.empty; } @property int front() const { return slices.front.front; } void popFront() { slices.front.popFront(); clearFront(); } @property Together save() const { return Together(slices.dup); } @property int back() const { return slices.back.back; } void popBack() { slices.back.popBack(); clearBack(); } @property size_t length() const { return reduce!((a, b) => a + b.length)(size_t.init, slices); } int opIndex(size_t index) const { /* Save the index for the error message */ immutable originalIndex = index; foreach (slice; slices) { if (slice.length > index) { return slice[index]; } else { index -= slice.length; } } throw new Exception( format("Invalid index: %s (length: %s)", originalIndex, this.length)); } } void main() { auto range = Together(FibonacciSeries().take(10).array, [ 777, 888 ], (new SquaresRange()).take(5).array); writeln(range.save); }
当编译并执行以上代码时,它会产生以下结果:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 777, 888, 0, 1, 4, 9, 16]
OutputRange(输出范围)
OutputRange表示流式元素输出,类似于将字符发送到stdout。OutputRange需要支持put(range, element)操作。put()是std.range模块中定义的函数。它在编译时确定范围和元素的功能,并使用最合适的方法输出元素。下面显示了一个简单的示例。
import std.algorithm; import std.stdio; struct MultiFile { string delimiter; File[] files; this(string delimiter, string[] fileNames ...) { this.delimiter = delimiter; /* stdout is always included */ this.files ~= stdout; /* A File object for each file name */ foreach (fileName; fileNames) { this.files ~= File(fileName, "w"); } } void put(T)(T element) { foreach (file; files) { file.write(element, delimiter); } } } void main() { auto output = MultiFile("\n", "output_0", "output_1"); copy([ 1, 2, 3], output); copy([ "red", "blue", "green" ], output); }
当编译并执行以上代码时,它会产生以下结果:
[1, 2, 3] ["red", "blue", "green"]
D 编程 - 别名
Alias(别名),顾名思义,为现有名称提供替代名称。别名的语法如下所示:
alias new_name = existing_name;
以下是不推荐使用的旧语法,以防您参考一些旧格式的示例。
alias existing_name new_name;
还有一种语法用于表达式,如下所示,其中我们可以直接使用别名而不是表达式。
alias expression alias_name ;
您可能知道,typedef可以添加创建新类型的能力。Alias可以完成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
在上面的示例中,类型tuple被分配给别名变量,它简化了方法定义和变量的访问。当我们尝试重用此类类型元组时,这种访问方式更有用。
数据类型的别名
很多时候,我们可能会定义需要在整个应用程序中使用的通用数据类型。当多个程序员编写应用程序时,可能会出现一个人使用int,另一个人使用double等等的情况。为了避免此类冲突,我们通常使用类型来表示数据类型。下面显示了一个简单的示例。
示例
import std.stdio; alias int myAppNumber; alias string myAppString; void main() { myAppNumber i = 10; myAppString s = "TestString"; writeln(i,s); }
当编译并执行以上代码时,它会产生以下结果:
10TestString
类变量的别名
通常需要在子类中访问超类的成员变量,这可以通过别名实现,可能使用不同的名称。
如果您不熟悉类和继承的概念,请先查看有关类和继承的教程,然后再开始学习本节内容。
示例
下面显示了一个简单的示例。
import std.stdio; class Shape { int area; } class Square : Shape { string name() const @property { return "Square"; } alias Shape.area squareArea; } void main() { auto square = new Square; square.squareArea = 42; writeln(square.name); writeln(square.squareArea); }
当编译并执行以上代码时,它会产生以下结果:
Square 42
Alias This(别名this)
Alias this提供了用户定义类型自动类型转换的功能。语法如下所示,其中关键字alias和this分别写在成员变量或成员函数的两侧。
alias member_variable_or_member_function this;
示例
下面显示了一个示例,以展示alias this的功能。
import std.stdio; struct Rectangle { long length; long breadth; double value() const @property { return cast(double) length * breadth; } alias value this; } double volume(double rectangle, double height) { return rectangle * height; } void main() { auto rectangle = Rectangle(2, 3); writeln(volume(rectangle, 5)); }
在上面的示例中,您可以看到结构体rectangle在alias this方法的帮助下被转换为double值。
当编译并执行以上代码时,它会产生以下结果:
30
D 编程 - 混入
Mixin(混入)是允许将生成的代码混合到源代码中的结构体。Mixin可以是以下类型:
- 字符串Mixin
- 模板Mixin
- Mixin命名空间
字符串Mixin
只要该字符串在编译时已知,D语言就能够将代码插入为字符串。字符串Mixin的语法如下所示:
mixin (compile_time_generated_string)
示例
下面显示了一个字符串Mixin的简单示例。
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
模板Mixin
D语言模板定义了常见的代码模式,供编译器从该模式生成实际的实例。模板可以生成函数、结构体、联合体、类、接口以及任何其他合法的D语言代码。模板Mixin的语法如下所示。
mixin a_template!(template_parameters)
下面显示了一个字符串Mixin的简单示例,我们使用类Department创建一个模板,并使用Mixin实例化模板,从而使函数setName和printNames可用于结构体college。
示例
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命名空间用于避免模板Mixin中的歧义。例如,可能存在两个变量,一个在main中显式定义,另一个混合在其中。当混合的名称与周围作用域中的名称相同,则使用周围作用域中的名称。此示例如下所示。
示例
import std.stdio; template Person() { string name; void print() { writeln(name); } } void main() { string name; mixin Person a; name = "name 1"; writeln(name); a.name = "name 2"; print(); }
当编译并执行以上代码时,它会产生以下结果:
name 1 name 2
D 编程 - 模块
模块是D语言的基本构建块。它们基于一个简单的概念。每个源文件都是一个模块。因此,我们编写程序的单个文件是独立的模块。默认情况下,模块的名称与其文件名相同,不带.d扩展名。
当显式指定时,模块的名称由module关键字定义,它必须出现在源文件中的第一行非注释行。例如,假设源文件名为“employee.d”。然后,模块的名称由module关键字后跟employee指定。如下所示。
module employee; class Employee { // Class definition goes here. }
module行是可选的。当未指定时,它与文件名相同,不带.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 { }
同样,对于office模块:
module company.office; class Office { }
由于包名称对应于目录名称,因此深度超过一个目录级别的模块的包名称必须反映该层次结构。例如,如果“company”目录包含一个“branch”目录,则该目录中模块的名称也将包含branch。
module company.branch.employee;
在程序中使用模块
我们到目前为止在几乎每个程序中都使用过的import关键字用于将模块引入当前模块:
import std.stdio;
模块名称也可以包含包名称。例如,上面的std.部分表示stdio是一个属于std包的模块。
模块的位置
编译器通过将包和模块名称直接转换为目录和文件名来查找模块文件。
例如,对于company.employee和company.office,这两个模块employee和office将分别位于“company/employee.d”和“animal/office.d”(或“company\employee.d”和“company\office.d”,具体取决于文件系统)。
长模块名称和短模块名称
程序中使用的名称可以像下面这样用模块和包名称拼写出来。
import company.employee; auto employee0 = Employee(); auto employee1 = company.employee.Employee();
通常不需要长名称,但有时会出现名称冲突。例如,当引用出现在多个模块中的名称时,编译器无法确定指的是哪个名称。以下程序正在拼写长名称以区分在两个单独的模块(company和college)中定义的两个单独的employee结构体。
company文件夹中的第一个employee模块如下所示。
module company.employee; import std.stdio; class Employee { public: string str; void print() { writeln("Company Employee: ",str); } }
college文件夹中的第二个employee模块如下所示。
module college.employee; import std.stdio; class Employee { public: string str; void print() { writeln("College Employee: ",str); } }
hello.d中的主模块应保存在包含college和company文件夹的文件夹中。如下所示。
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 编程 - 模板
模板是泛型编程的基础,泛型编程涉及以独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。
模板是允许将代码描述为模式的特性,供编译器自动生成程序代码。源代码的部分可以留给编译器填充,直到该部分实际在程序中使用。编译器会填充缺失的部分。
函数模板
将函数定义为模板是将它使用的类型中的一个或多个类型留为未指定的,以便编译器稍后推断。未指定的类型在模板参数列表中定义,该列表位于函数名称和函数参数列表之间。因此,函数模板有两个参数列表:
- 模板参数列表
- 函数参数列表
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语言 - 不可变
我们经常使用可变变量,但很多情况下并不需要可变性。在这些情况下可以使用不可变变量。下面给出了一些可以使用不可变变量的示例。
对于像pi这样的数学常数,它们永远不会改变。
对于我们希望保留值的数组,并且不需要进行修改的情况。
不可变性使我们能够理解变量是不可变的还是可变的,从而保证某些操作不会改变某些变量。它还可以降低某些类型的程序错误的风险。D语言的不可变性概念由const和immutable关键字表示。虽然这两个词本身含义接近,但它们在程序中的职责不同,有时甚至不兼容。
D语言的不可变性概念由const和immutable关键字表示。虽然这两个词本身含义接近,但它们在程序中的职责不同,有时甚至不兼容。
D语言中不可变变量的类型
有三种类型的变量定义永远不会被修改。
- 枚举常量
- 不可变变量
- 常量变量
enum 常量在D语言中
枚举常量使我们将常数值与有意义的名称关联起来成为可能。下面显示了一个简单的示例。
示例
import std.stdio; enum Day{ Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday } void main() { Day day; day = Day.Sunday; if (day == Day.Sunday) { writeln("The day is Sunday"); } }
当编译并执行以上代码时,它会产生以下结果:
The day is Sunday
D语言中的不可变变量
不可变变量可以在程序执行期间确定。它只是指示编译器在初始化后,它就变得不可变了。下面显示了一个简单的示例。
示例
import std.stdio; import std.random; void main() { int min = 1; int max = 10; immutable number = uniform(min, max + 1); // cannot modify immutable expression number // number = 34; typeof(number) value = 100; writeln(typeof(number).stringof, number); writeln(typeof(value).stringof, value); }
当编译并执行以上代码时,它会产生以下结果:
immutable(int)4 immutable(int)100
在上面的示例中,您可以看到如何将数据类型传输到另一个变量并在打印时使用stringof。
Const 变量在D语言中
Const变量不能被修改,类似于immutable。immutable变量可以作为其不可变参数传递给函数,因此建议使用immutable而不是const。下面显示了前面使用的相同示例,但修改为const。
示例
import std.stdio; import std.random; void main() { int min = 1; int max = 10; const number = uniform(min, max + 1); // cannot modify const expression number| // number = 34; typeof(number) value = 100; writeln(typeof(number).stringof, number); writeln(typeof(value).stringof, value); }
如果我们编译并运行上述代码,则会产生以下结果:
const(int)7 const(int)100
D语言中的不可变参数
const会抹去关于原始变量是可变还是不可变的信息,因此使用immutable可以将其传递给其他函数,并保留原始类型。下面显示了一个简单的示例。
示例
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 编程 - 文件 I/O
文件由std.stdio模块的File结构体表示。文件表示字节序列,无论它是文本文件还是二进制文件。
D编程语言提供了对高级函数以及低级(操作系统级)调用的访问,以便处理存储设备上的文件。
在D语言中打开文件
标准输入和输出流stdin和stdout在程序开始运行时就已经打开了。它们可以随时使用。另一方面,必须首先通过指定文件名和所需的访问权限来打开文件。
File file = File(filepath, "mode");
这里,filename是字符串字面量,用于命名文件,访问mode可以具有以下值之一:
序号 | 模式&描述 |
---|---|
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 编程 - 并发
并发是指程序同时在多个线程上运行。并发程序的一个示例是 Web 服务器同时响应多个客户端。并发使用消息传递很容易,但如果它们基于数据共享,则非常难以编写。
在线程之间传递的数据称为消息。消息可以由任何类型和任意数量的变量组成。每个线程都有一个ID,用于指定消息的接收者。启动另一个线程的任何线程都称为新线程的所有者。
在D语言中启动线程
函数spawn()以指针作为参数,并从该函数启动一个新线程。该函数执行的任何操作,包括它可能调用的其他函数,都将在新线程上执行。所有者和工作线程都开始分别执行,就像它们是独立的程序一样。
示例
import std.stdio; import std.stdio; import std.concurrency; import core.thread; void worker(int a) { foreach (i; 0 .. 4) { Thread.sleep(1); writeln("Worker Thread ",a + i); } } void main() { foreach (i; 1 .. 4) { Thread.sleep(2); writeln("Main Thread ",i); spawn(≈worker, i * 5); } writeln("main is done."); }
当编译并执行上述代码时,它读取上一节中创建的文件,并产生以下结果:
Main Thread 1 Worker Thread 5 Main Thread 2 Worker Thread 6 Worker Thread 10 Main Thread 3 main is done. Worker Thread 7 Worker Thread 11 Worker Thread 15 Worker Thread 8 Worker Thread 12 Worker Thread 16 Worker Thread 13 Worker Thread 17 Worker Thread 18
D语言中的线程标识符
在模块级别全局可用的thisTid变量始终是当前线程的ID。您还可以从调用spawn时接收threadId。下面显示了一个示例。
示例
import std.stdio; import std.concurrency; void printTid(string tag) { writefln("%s: %s, address: %s", tag, thisTid, &thisTid); } void worker() { printTid("Worker"); } void main() { Tid myWorker = spawn(&worker); printTid("Owner "); writeln(myWorker); }
当编译并执行上述代码时,它读取上一节中创建的文件,并产生以下结果:
Owner : Tid(std.concurrency.MessageBox), address: 10C71A59C Worker: Tid(std.concurrency.MessageBox), address: 10C71A59C Tid(std.concurrency.MessageBox)
D语言中的消息传递
函数send()发送消息,函数receiveOnly()等待特定类型的消息。还有其他名为prioritySend()、receive()和receiveTimeout()的函数,将在后面解释。
在下面的程序中,所有者向其工作线程发送一个int类型的消息,并等待来自工作线程的double类型的消息。线程继续来回发送消息,直到所有者发送一个负数int。下面显示了一个示例。
示例
import std.stdio; import std.concurrency; import core.thread; import std.conv; void workerFunc(Tid tid) { int value = 0; while (value >= 0) { value = receiveOnly!int(); auto result = to!double(value) * 5; tid.send(result); } } void main() { Tid worker = spawn(&workerFunc,thisTid); foreach (value; 5 .. 10) { worker.send(value); auto result = receiveOnly!double(); writefln("sent: %s, received: %s", value, result); } worker.send(-1); }
当编译并执行上述代码时,它读取上一节中创建的文件,并产生以下结果:
sent: 5, received: 25 sent: 6, received: 30 sent: 7, received: 35 sent: 8, received: 40 sent: 9, received: 45
D语言中带有等待的消息传递
下面显示了一个带有等待的消息传递的简单示例。
import std.stdio; import std.concurrency; import core.thread; import std.conv; void workerFunc(Tid tid) { Thread.sleep(dur!("msecs")( 500 ),); tid.send("hello"); } void main() { spawn(&workerFunc,thisTid); writeln("Waiting for a message"); bool received = false; while (!received) { received = receiveTimeout(dur!("msecs")( 100 ), (string message) { writeln("received: ", message); }); if (!received) { writeln("... no message yet"); } } }
当编译并执行上述代码时,它读取上一节中创建的文件,并产生以下结果:
Waiting for a message ... no message yet ... no message yet ... no message yet ... no message yet received: hello
D 编程 - 异常处理
异常是在程序执行期间出现的错误。D语言异常是对程序运行期间出现的异常情况的响应,例如尝试除以零。
异常提供了一种将控制权从程序的一部分转移到另一部分的方法。D语言的异常处理建立在三个关键字try、catch和throw之上。
throw − 当出现问题时,程序会抛出异常。这是使用throw关键字完成的。
catch − 程序在程序中要处理问题的那个地方使用异常处理程序捕获异常。catch关键字表示捕获异常。
try − try块标识一个代码块,对于该代码块,会激活特定的异常。它后面跟着一个或多个catch块。
假设一个块会引发异常,则一个方法使用try和catch关键字的组合来捕获异常。try/catch块放置在可能生成异常的代码周围。try/catch块内的代码称为受保护代码,使用try/catch的语法如下:
try { // protected code } catch( ExceptionName e1 ) { // catch block } catch( ExceptionName e2 ) { // catch block } catch( ExceptionName eN ) { // catch block }
如果你的try块在不同情况下引发了多个异常,你可以列出多个catch语句来捕获不同类型的异常。
在D语言中抛出异常
可以使用throw语句在代码块中的任何位置抛出异常。throw语句的操作数确定异常的类型,可以是任何表达式,表达式的结果类型确定抛出的异常类型。
以下示例在出现除以零条件时抛出异常:
示例
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类型的异常。如果要指定一个catch块应该处理在try块中抛出的任何类型的异常,则必须在包含异常声明的括号之间放置省略号...,如下所示:
try { // protected code } catch(...) { // code to handle any exception }
以下示例抛出除以零异常。它在catch块中被捕获。
import std.stdio; import std.string; string division(int a, int b) { string result = ""; try { if( b == 0 ) { throw new Exception("Cannot divide by zero!"); } else { result = format("%s",a/b); } } catch (Exception e) { result = e.msg; } return result; } void main () { int x = 50; int y = 0; writeln(division(x, y)); y = 10; writeln(division(x, y)); }
当编译并执行上述代码时,它读取上一节中创建的文件,并产生以下结果:
Cannot divide by zero! 5
D语言 - 合约式编程
D语言中的合约式编程专注于提供一种简单易懂的错误处理方法。D语言中的合约式编程由三种类型的代码块实现:
- 主体块
- in块
- out块
D语言中的主体块
主体块包含实际的功能执行代码。in和out块是可选的,而主体块是必须的。下面显示了一个简单的语法。
return_type function_name(function_params) in { // in block } out (result) { // in block } body { // actual function block }
D语言中用于前置条件的In块
in块用于简单的前置条件,用于验证输入参数是否可接受以及是否在代码可以处理的范围内。in块的一个好处是,所有入口条件都可以保存在一起,并与函数的实际主体分开。下面显示了一个用于验证密码的最小长度的简单前置条件。
import std.stdio; import std.string; bool isValid(string password) in { assert(password.length>=5); } body { // other conditions return true; } void main() { writeln(isValid("password")); }
当编译并执行上述代码时,它读取上一节中创建的文件,并产生以下结果:
true
D语言中用于后置条件的Out块
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编程 - 条件编译
条件编译是选择编译哪些代码和不编译哪些代码的过程,类似于C和C++中的#if/#else/#endif。任何未编译的语句都必须在语法上正确。
条件编译涉及在编译时可评估的条件检查。像if、for、while这样的运行时条件语句不是条件编译特性。D语言的以下特性用于条件编译:
- debug
- version
- 静态if
D语言中的调试语句
debug在程序开发期间非常有用。仅当启用了-debug编译器开关时,标记为debug的表达式和语句才会编译到程序中。
debug a_conditionally_compiled_expression; debug { // ... conditionally compiled code ... } else { // ... code that is compiled otherwise ... }
else子句是可选的。只有在启用了-debug编译器开关时,才会编译上面的单个表达式和代码块。
这些行可以标记为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块也可以具有标签。
debug(mytag) { // }
可以同时启用多个debug标签。
dmd test.d -oftest -w -debug = mytag1 -debug = mytag2
D语言中的Debug (level) 语句
有时,通过数值级别关联调试语句更有用。增加级别可以提供更详细的信息。
import std.stdio; void myFunction() { debug(1) writeln("debug1"); debug(2) writeln("debug2"); } void main() { myFunction(); }
低于或等于指定级别的debug表达式和块将被编译。
$ dmd test.d -oftest -w -debug = 1 $ ./test debug1
D语言中的Version (tag) 和Version (level) 语句
Version类似于debug,并且使用方法相同。else子句是可选的。尽管version的工作原理与debug基本相同,但使用单独的关键字有助于区分它们不相关的用途。与debug一样,可以启用多个version。
import std.stdio; void myFunction() { version(1) writeln("version1"); version(2) writeln("version2"); } void main() { myFunction(); }
低于或等于指定级别的debug表达式和块将被编译。
$ dmd test.d -oftest -w -version = 1 $ ./test version1
静态if
静态if是if语句的编译时等价物。就像if语句一样,静态if接受一个逻辑表达式并对其进行评估。与if语句不同,静态if与执行流程无关;相反,它确定程序中是否应该包含一段代码。
if表达式在语法和语义上都与我们之前看到的is运算符无关。它在编译时进行评估。它生成一个int值,要么是0,要么是1;取决于括号中指定的表达式。尽管它接受的表达式不是逻辑表达式,但is表达式本身用作编译时逻辑表达式。它在静态if条件和模板约束中特别有用。
import std.stdio; enum Days { sun, mon, tue, wed, thu, fri, sat }; void myFunction(T)(T mytemplate) { static if (is (T == class)) { writeln("This is a class type"); } else static if (is (T == enum)) { writeln("This is an enum type"); } } void main() { Days day; myFunction(day); }
当我们编译并运行时,将获得如下输出。
This is an enum type
D 编程 - 类和对象
类是D编程的核心特性,支持面向对象编程,通常称为用户定义类型。
类用于指定对象的表单,它将数据表示和操作该数据的方法组合到一个简洁的包中。类中的数据和函数称为类的成员。
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确定其后面类成员的访问属性。公共成员可以从类外部在类对象的范围内任何地方访问。您还可以将类的成员指定为private或protected,我们将在一个小节中讨论。
定义D语言对象
类为对象提供蓝图,因此基本上对象是从类创建的。您可以使用与声明基本类型变量完全相同的声明来声明类对象。以下语句声明了两个Box类的对象:
Box Box1; // Declare Box1 of type Box Box Box2; // Declare Box2 of type Box
Box1和Box2这两个对象都有自己数据成员的副本。
访问数据成员
可以使用直接成员访问运算符(.)访问类对象的公共数据成员。让我们尝试以下示例来说明这一点:
import std.stdio; class Box { public: double length; // Length of a box double breadth; // Breadth of a box double height; // Height of a box } void main() { Box box1 = new Box(); // Declare Box1 of type Box Box box2 = new Box(); // Declare Box2 of type Box double volume = 0.0; // Store the volume of a box here // box 1 specification box1.height = 5.0; box1.length = 6.0; box1.breadth = 7.0; // box 2 specification box2.height = 10.0; box2.length = 12.0; box2.breadth = 13.0; // volume of box 1 volume = box1.height * box1.length * box1.breadth; writeln("Volume of Box1 : ",volume); // volume of box 2 volume = box2.height * box2.length * box2.breadth; writeln("Volume of Box2 : ", volume); }
当编译并执行以上代码时,它会产生以下结果:
Volume of Box1 : 210 Volume of Box2 : 1560
需要注意的是,私有和受保护的成员不能使用直接成员访问运算符(.)直接访问。您很快就会了解如何访问私有和受保护的成员。
D语言中的类和对象
到目前为止,您已经对D语言的类和对象有了非常基本的了解。与D语言的类和对象相关的其他有趣概念,我们将在下面列出的各个小节中进行讨论:
序号 | 概念和描述 |
---|---|
1 | 类成员函数
类成员函数是指其定义或原型在类定义中与任何其他变量一样存在的函数。 |
2 | 类访问修饰符
类成员可以定义为public、private或protected。默认情况下,成员将被假定为private。 |
3 | 构造函数和析构函数
类构造函数是类中一个特殊的函数,当创建类的新的对象时调用该函数。析构函数也是一个特殊函数,当创建的对象被删除时调用该函数。 |
4 | D语言中的this指针
每个对象都有一个特殊的指针this,它指向对象本身。 |
5 | 指向D语言类的指针
指向类的指针与指向结构的指针完全相同。事实上,类实际上只是一个包含函数的结构。 |
6 | 类的静态成员
类的成员数据和成员函数都可以声明为静态的。 |
D 编程 - 继承
面向对象编程中最重要的概念之一是继承。继承允许根据另一个类来定义类,这使得创建和维护应用程序变得更容易。这也提供了重用代码功能和快速实现时间的机会。
在创建类时,程序员可以指定新类应该继承现有类的成员,而不是编写全新的数据成员和成员函数。这个现有的类称为基类,新类称为派生类。
继承的概念实现了is a关系。例如,哺乳动物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
访问控制和继承
派生类可以访问其基类的所有非私有成员。因此,基类成员不应被派生类的成员函数访问,应在基类中声明为私有。
派生类继承所有基类方法,但以下情况除外:
- 基类的构造函数、析构函数和复制构造函数。
- 基类的重载运算符。
多级继承
继承可以有多个级别,在以下示例中显示。
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 编程 - 重载
D语言允许您在同一作用域中为函数名称或运算符指定多个定义,这分别称为函数重载和运算符重载。
重载声明是在同一作用域中之前声明具有相同名称的声明的声明,但这两个声明具有不同的参数并且显然具有不同的定义(实现)。
当您调用重载函数或运算符时,编译器会通过将您用来调用函数或运算符的参数类型与定义中指定的参数类型进行比较,来确定最合适的定义。选择最合适的重载函数或运算符的过程称为重载解析。
函数重载
您可以在同一作用域中为同一函数名称提供多个定义。函数的定义必须通过参数列表中参数的类型和/或数量彼此不同。您不能重载仅返回值类型不同的函数声明。
示例
以下示例使用相同的函数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
运算符重载类型
基本上,运算符重载有三种类型,如下所示。
D 编程 - 封装
所有D语言程序都由以下两个基本元素组成:
程序语句(代码) - 这是程序中执行操作的部分,它们称为函数。
程序数据 - 它是程序信息,受程序函数影响。
封装是一个面向对象编程的概念,它将数据和操作数据的函数绑定在一起,并使两者都免受外部干扰和误用的影响。数据封装导致了重要的OOP概念数据隐藏。
数据封装是将数据及其使用的数据捆绑在一起的机制,而数据抽象是仅公开接口并向用户隐藏实现细节的机制。
D语言通过创建称为类的用户定义类型来支持封装和数据隐藏的特性。我们已经学习过类可以包含private、protected和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、breadth和height是private。这意味着它们只能被Box类的其他成员访问,而不能被程序的任何其他部分访问。这是实现封装的一种方式。
要使类的某些部分public(即可以被程序的其他部分访问),必须在public关键字之后声明它们。在public说明符之后定义的所有变量或函数都可以被程序中的所有其他函数访问。
使一个类成为另一个类的朋友会暴露实现细节并降低封装。理想情况下,应尽可能多地隐藏每个类的细节,使其不被其他所有类访问。
D语言中的数据封装
在任何使用public和private成员实现类的D语言程序中,都是数据封装和数据抽象的示例。考虑以下示例:
示例
import std.stdio; class Adder { public: // constructor this(int i = 0) { total = i; } // interface to outside world void addNum(int number) { total += number; } // interface to outside world int getTotal() { return total; }; private: // hidden data from outside world int total; } void main( ) { Adder a = new Adder(); a.addNum(10); a.addNum(20); a.addNum(30); writeln("Total ",a.getTotal()); }
当编译并执行以上代码时,它会产生以下结果:
Total 60
上面的类将数字加在一起,并返回总和。公共成员addNum和getTotal是面向外部世界的接口,用户需要了解它们才能使用该类。私有成员total是隐藏在外部世界之外的内容,但类需要它才能正常运行。
D语言中的类设计策略
我们大多数人都通过痛苦的经验教训,除非我们确实需要公开类成员,否则默认情况下将它们设为私有。这仅仅是良好的封装。
这条经验法则最常应用于数据成员,但同样适用于所有成员,包括虚函数。
D 编程 - 接口
接口是一种强制继承它的类必须实现某些函数或变量的方法。函数不能在接口中实现,因为它们始终在继承接口的类中实现。
接口使用interface关键字而不是class关键字创建,尽管两者在很多方面都很相似。当您想从接口继承并且该类已经从另一个类继承时,则需要用逗号分隔类名和接口名。
让我们看一个简单的例子来解释接口的使用。
示例
import std.stdio; // Base class interface Shape { public: void setWidth(int w); void setHeight(int h); } // Derived class class Rectangle: Shape { int width; int height; public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } int getArea() { return (width * height); } } void main() { Rectangle Rect = new Rectangle(); Rect.setWidth(5); Rect.setHeight(7); // Print the area of the object. writeln("Total area: ", Rect.getArea()); }
当编译并执行以上代码时,它会产生以下结果:
Total area: 35
D语言中带有final和static函数的接口
接口可以包含final和static方法,这些方法的定义应包含在接口本身中。这些函数不能被派生类覆盖。下面显示了一个简单的示例。
示例
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 编程 - 抽象类
抽象是指在OOP中使类抽象的能力。抽象类是指不能被实例化的类。类的所有其他功能仍然存在,并且其字段、方法和构造函数都以相同的方式访问。您只是不能创建抽象类的实例。
如果一个类是抽象的并且不能被实例化,那么除非它是子类,否则该类没有多大用处。这通常是抽象类在设计阶段产生的方式。父类包含一组子类的公共功能,但父类本身过于抽象,无法单独使用。
在D语言中使用抽象类
使用abstract关键字声明一个抽象类。该关键字出现在类声明中,位于class关键字之前。以下显示了如何继承和使用抽象类的示例。
示例
import std.stdio; import std.string; import std.datetime; abstract class Person { int birthYear, birthDay, birthMonth; string name; int getAge() { SysTime sysTime = Clock.currTime(); return sysTime.year - birthYear; } } class Employee : Person { int empID; } void main() { Employee emp = new Employee(); emp.empID = 101; emp.birthYear = 1980; emp.birthDay = 10; emp.birthMonth = 10; emp.name = "Emp1"; writeln(emp.name); writeln(emp.getAge); }
当我们编译并运行上述程序时,我们将得到以下输出。
Emp1 37
抽象函数
类似于函数,类也可以是抽象的。此类函数的实现未在其类中给出,但应在继承包含抽象函数的类的类中提供。上面的例子用抽象函数更新了。
示例
import std.stdio; import std.string; import std.datetime; abstract class Person { int birthYear, birthDay, birthMonth; string name; int getAge() { SysTime sysTime = Clock.currTime(); return sysTime.year - birthYear; } abstract void print(); } class Employee : Person { int empID; override void print() { writeln("The employee details are as follows:"); writeln("Emp ID: ", this.empID); writeln("Emp Name: ", this.name); writeln("Age: ",this.getAge); } } void main() { Employee emp = new Employee(); emp.empID = 101; emp.birthYear = 1980; emp.birthDay = 10; emp.birthMonth = 10; emp.name = "Emp1"; emp.print(); }
当我们编译并运行上述程序时,我们将得到以下输出。
The employee details are as follows: Emp ID: 101 Emp Name: Emp1 Age: 37