
- 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