forked from DlangRen/Programming-in-D
-
Notifications
You must be signed in to change notification settings - Fork 1
/
class.d
453 lines (337 loc) · 13 KB
/
class.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
Ddoc
$(DERS_BOLUMU $(IX class) 类)
$(P
$(IX OOP) $(IX object oriented programming) $(IX user defined type) 与结构相似,$(C class) 具有定义新类型的功能。根据此定义,类是 $(I 自定义类型)。不同于结构的是,D 语言中的类提供的是 $(I 面向对象编程) (OOP)模型。OOP 的主要内容有以下几个方面:
)
$(UL
$(LI
$(B 封装:) 控制成员的访问($(I 封装也可用于结构,只是到本章之前一直未提及。))
)
$(LI
$(B 继承:) 获取另一个类型的成员
)
$(LI
$(B 多态性:) 能够使用较特定的类型取代较通用的类型
)
)
$(P
封装是通过 $(I 保护属性) 来实现的,关于这一点在 $(LINK2 /ders/d.cn/encapsulation.html, 后面章节) 会看到。继承是用于获取其它类型的 $(I 实现)。$(LINK2 /ders/d.cn/inheritance.html, 多态性) 是从类之间抽象出部分代码,通过 $(I 接口) 实现的。
)
$(P
本章将深入介绍类,特别强调一点,类是引用类型。稍后的章节中将展示类的更多细节。
)
$(H5 与结构对比)
$(P
一般情况下,类与结构非常相似。在下面的章节中我们已经看到结构的大部分特性也适用于类:
)
$(UL
$(LI $(LINK2 /ders/d.cn/struct.html, 结构))
$(LI $(LINK2 /ders/d.cn/member_functions.html, 成员函数))
$(LI $(LINK2 /ders/d.cn/const_member_functions.html, $(CH4 const ref) 参数和 $(CH4 const) 成员函数))
$(LI $(LINK2 /ders/d.cn/special_functions.html, 构造函数和其它特殊函数))
$(LI $(LINK2 /ders/d.cn/operator_overloading.html, 运算符重载))
)
$(P
然而,类与结构之间有重要的区别。
)
$(H6 类是引用类型)
$(P
与结构的最大区别在于结构是 $(I 值类型) 而类是 $(I 引用类型)。下面的其它不同大部分与此有关。
)
$(H6 $(IX null, class) $(new, class) 类变量可以为 $(C null))
$(P
在 $(LINK2 /ders/d.cn/null_is.html, $(CH4 null) 值和 $(CH4 is) 运算符一章)已提到过,类变量可以为 $(C null)。换句话说,类变量可以不提供对任何对象的访问。类变量并不拥有值本身;实际的类对象必须使用关键字 $(C new) 来构造。
)
$(P
大家都还记得吧,引用与运算符 $(C null) 不能通过运算符 $(C ==) 或 $(C !=) 进行比较。相反,必须相应地使用运算符 $(C is) 或 $(C !is):
)
---
MyClass referencesAnObject = new MyClass;
assert(referencesAnObject $(HILITE !is) null);
MyClass variable; // 没有引用对象
assert(variable $(HILITE is) null);
---
$(P
原因是,运算符 $(C ==) 会查询对象成员的值,并尝试通过一个潜在的 $(C null) 变量访问成员,这将引发一个内存访问错误。因此,类变量必须总是通过运算符 $(C is) 和 $(C !is) 进行比较。
)
$(H6 $(IX variable, class) $(IX object, class) 类变量与类对象)
$(P
类变量和类对象是独立的概念。
)
$(P
类对象由关键字 $(C new) 构造;它们没有名字。实际的概念是,在程序中,一个类类型由一个类对象表示。例如,有一个 $(C Student) 类,它通过姓名和成绩来表示学生, 此时 $(C Student) $(I 对象)的成员会存储这些信息。另一方面,类变量是用于访问类对象的一种语言特性。
)
$(P
另一方面,类变量一种访问类对象的语言功能。虽然语法上看起来是在类 $(I 变量) 上执行,但实际上调度了一个类 $(I object)。
)
$(P
一起来看看下面这段代码,之前在 $(LINK2 /ders/d.cn/value_vs_reference.html, 值类型和引用类型一章)已见过,如下所示:
)
---
auto variable1 = new MyClass;
auto variable2 = variable1;
---
$(P
$(C new) 关键字构造了一个匿名的类对象。上面的 $(C variable1) 和 $(C variable2) 只提供对那个匿名对象的访问:
)
$(MONO
(匿名 MyClass 对象) 变量1 变量2
───┬───────────────────┬─── ───┬───┬─── ───┬───┬───
│ ... │ │ o │ │ o │
───┴───────────────────┴─── ───┴─│─┴─── ───┴─│─┴───
▲ │ │
│ │ │
└────────────────────┴────────────┘
)
$(H6 $(IX copy, class) 复制)
$(P
复制只影响变量,而不是对象。
)
$(P
因为类是引用类型,定义一个新的类变量做为另一个副本,将产生两个访问同一对象的变量。实际的对象没有被复制。
)
$(P
由于没有复制对象, postblit 函数 $(C this(this)) 不能用于类变量。
)
---
auto variable2 = variable1;
---
$(P
在上面的代码中, $(C variable2) 由 $(C variable1) 初始化。这两个变量可访问同一个对象。
)
$(P
当需要复制实际的对象时,类必须有一个针对此目的的成员函数。为与数组兼容,该函数可以命名为 $(C dup()). 该函数必须创建并返回一个新的类对象。让我们在有各种类型成员的类上看看它::
)
---
class Foo {
S o; // 假设 S 是一个结构类型
char[] s;
int i;
// ...
this(S o, const char[] s, int i) {
this.o = o;
this.s = s.dup;
this.i = i;
}
Foo dup() const {
return new Foo(o, s, i);
}
}
---
$(P
$(C dup()) 成员函数利用 $(C Foo) 的构造函数,创建并返回新的对象。注意,构造函数通过数组的 $(C .dup) 属性显式复制 $(C s) 成员。做为值类型,$(C o) 和 $(C i) 自动被复制。
)
$(P
下面的代码利用 $(C dup()) 创建一个新的对象:
)
---
auto var1 = new Foo(S(1.5), "hello", 42);
auto var2 = var1.dup();
---
$(P
最后,与 $(C var1) 和 $(C var2) 关联的那些对象并不相同。
)
$(P
同样地,可以通过名为 $(C idup()) 的成员函数的提供对象的 $(C immutable) 副本:此时,构造函数必须同时定义为 $(C pure) 。我们会在$(LINK2 /ders/d.cn/functions_more.html, 后面章节)对关键字 $(C pure) 进行讲解。
)
---
class Foo {
// ...
this(S o, const char[] s, int i) $(HILITE pure) {
// ...
}
immutable(Foo) idup() const {
return new immutable(Foo)(o, s, i);
}
}
// ...
immutable(Foo) imm = var1.idup();
---
$(H6 $(IX assignment, class) 赋值)
$(P
就像复制,赋值只影响变量。
)
$(P
给类变量赋值,会解除变量与当前对象的关联,并关联到一个新对象。
)
$(P
如何没有别的类变量能访问已解除关联对象,那该对象将由垃圾回收器在将来某个时候销毁。
)
---
auto variable1 = new MyClass();
auto variable2 = new MyClass();
variable1 $(HILITE =) variable2;
---
$(P
上面的赋值让 $(C variable1) 离开其对象并且开始提供对 $(C variable2) 的对象的访问。由于 $(C variable1) 的原始对象没有别的变量,该对象将由垃圾回收器销毁。
)
$(P
赋值操作不能改变类。换句话说,$(C opAssign) 不能因为它们而被重载。
)
$(H6 定义)
$(P
类由 $(C class) 关键字定义而不是 $(C struct) 关键字:
)
---
$(HILITE class) ChessPiece {
// ...
}
---
$(H6 构造函数)
$(P
与结构一样,构造函数的名称是 $(C this) 。不像结构,类对象不能由 $(C { }) 语法构造。
)
---
class ChessPiece {
dchar shape;
this(dchar shape) {
this.shape = shape;
}
}
---
$(P
不像结构,构造函数参数按顺序分配给成员时,类没有自动构造对象:
)
---
class ChessPiece {
dchar shape;
size_t value;
}
void main() {
auto king = new ChessPiece('♔', 100); $(DERLEME_HATASI)
}
---
$(SHELL
Error: no constructor for ChessPiece
)
$(P
那样的语法要通过编译,就需要程序员显式的定义构造函数。
)
$(H6 析构函数)
$(P
像结构一样,析构函数的名称是 $(C ~this):
)
---
~this() {
// ...
}
---
$(P
$(IX finalizer versus destructor) 不过,与结构有所不同的是,类的析构函数在类对象的生命期结束时并不会被执行。正如上面看到的,析构函数会在未来垃圾回收周期内的某个时候被执行。(基于此点差异,类的析构函数被叫作 $(I 终结函数) 会更加确切)。
)
$(P
在后面的 $(LINK2 /ders/d.en/memory.html, 内存管理一章) 将会看到,类的析构函数必须遵循以下几条规则:
)
$(UL
$(LI 类的析构函数不能访问由垃圾回收器管理的成员。这是因为垃圾回收器没有被要求保证该对象及其成员按任何特定顺序终结。当析构函数执行时,全部成员应该已经终结。)
$(LI 类的析构函数一定不要分配由垃圾回收器管理的新内存。这是因为垃圾回收器没有被要求保证在垃圾回收周期内能分配新的对象。)
)
$(P
违反这些规则即会产生未定义行为。尝试在类的析构函数中分配一个对象,通过这种方式可以轻易地重现这种的问题:
)
---
class C {
~this() {
auto c = new C(); // ← 错误:在类的析构函数里
// 显式分配内存
}
}
void main() {
auto c = new C();
}
---
$(P
这个程序会抛一个异常,并中断:
)
$(SHELL
core.exception.$(HILITE InvalidMemoryOperationError)@(0)
)
$(P
在析构函数里 $(I 间接地) 从垃圾回收器里分配新的内存,这种做法同样是错的。例如,用于一个动态数组的元素的内存由垃圾回收器来分配。用这种方式使用一个数组,那将需要为未定义行为的元素分配一个新的内存块:
)
---
~this() {
auto arr = [ 1 ]; // ← 错误:在类的析构函数里
// 显式分配内存
}
---
$(SHELL
core.exception.$(HILITE InvalidMemoryOperationError)@(0)
)
$(H6 成员访问)
$(P
与结构一样,可以使用 $(I 点) 运算符来访问成员:
)
---
auto king = new ChessPiece('♔');
writeln(king$(HILITE .shape));
---
$(P
虽然语法上看起来像访问 $(I 变量) 的成员,实际上是 $(I 对象) 的成员。类变量没有成员,类对象有。$(C king) 变量并没有 $(C shape) 成员,匿名对象有。
)
$(P
$(I $(B 注:) 在上面的代码中,一般不这样直接访问成员。若确实需要这样的语法,应该首选属性,这将在 $(LINK2 /ders/d.cn/property.html, 后面的章节) 中解释。)
)
$(H6 运算符重载)
$(P
虽然 $(C opAssign) 不能被类重载,但与结构一样,可以实现运算符重载。对于类, $(C opAssign) 意味着 $(I 一个类变量总是关联着一个类对象)。
)
$(H6 成员函数)
$(P
虽然成员函数的定义与用法与结构相同,有个重要的不同:类成员函数默认是 $(I 可重写的) 。在 $(LINK2 /ders/d.cn/inheritance.html,继承章节) 我们将看到相关内容。
)
$(P
$(IX final) 由于可重写的成员函数有一个运行时性能消耗,在这儿不讨论更多细节,我推荐您定义全部没必要用 $(C final) 关键字重写的 $(C class) 成员函数。若没有编译错误,您可以闭着眼睛按教程来:
)
---
class C {
$(HILITE final) int func() { $(CODE_NOTE 推荐)
// ...
}
}
---
$(P
与结构不同的是一些成员函数自动继承自 $(C Object) 类。在 $(LINK2 /ders/d.cn/inheritance.html, 下一章节) 我们将看到怎样通过$(C override) 关键字来修改 $(C toString) 的定义。
)
$(H6 $(IX is, 运算符) $(IX !is) $(C is) 和 $(C !is) 运算符)
$(P
这些运算符应用在类变量上。
)
$(P
$(C is) 确定两个类变量是否提供对同一对象的访问。如果是同一对象,返回 $(C true) ,否则为 $(C false) 。$(C !is) 与 $(C is) 相反。
)
---
auto myKing = new ChessPiece('♔');
auto yourKing = new ChessPiece('♔');
assert(myKing !is yourKing);
---
$(P
由于 $(C myKing) 和 $(C yourKing) 变量来自不同的对象,$(C !is) 运算符返回 $(C true)。即使这两个对象由同一字符 $(C'♔') 参数构造,, 它们仍是两个单独的对象。
)
$(P
当变量提供对同一对象的访问时,$(C is) 返回 $(C true):
)
---
auto myKing2 = myKing;
assert(myKing2 is myKing);
---
$(P
上面的两个变量都提供对同一对象的访问。
)
$(H5 小结)
$(UL
$(LI 类和结构虽然有共同特点,但还是有很大的差异。
)
$(LI 类是引用类型。The $(C new) 关键字构造一个匿名 $(I class 对象) 并返回一个 $(I class 变量)。
)
$(LI 不与任何对象相关联的类变量为 $(C null)。检查 $(C null) 必须使用 $(C is) 或 $(C !is),而不是 $(C ==) 或 $(C !=)。
)
$(LI 复制操作将增加一个与对象关联的变量。为了复制类对象,类型必须有一个类似于命名为 $(C dup()) 的特殊函数。
)
$(LI 赋值会把一个变量与一个对象相关联。该行为不能被修改。
)
)
Macros:
SUBTITLE=类
DESCRIPTION=基本的 D 语言面向对象编程(OOP) 功能。
KEYWORDS=D 语言 编程 教程 书籍 教程 类