-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
462 lines (255 loc) · 629 KB
/
atom.xml
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
454
455
456
457
458
459
460
461
462
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Fl0w3r</title>
<subtitle>carpe diem</subtitle>
<link href="https://yousazoe.top/atom.xml" rel="self"/>
<link href="https://yousazoe.top/"/>
<updated>2025-01-03T06:07:21.685Z</updated>
<id>https://yousazoe.top/</id>
<author>
<name>Yousazoe</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>Serilog使用指南</title>
<link href="https://yousazoe.top/archives/74d72ec3.html"/>
<id>https://yousazoe.top/archives/74d72ec3.html</id>
<published>2025-01-03T03:18:58.000Z</published>
<updated>2025-01-03T06:07:21.685Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://serilog.net/"><img data-src="https://img.yousazoe.top/uPic/img/blog/CSHARP1/logging-serilog.png"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>Serilog 是一个 .NET 平台上的强大的日志记录库。它提供了丰富的 API 以及可插拔的日志格式化器和输出器,使得在 .NET 应用程序中实现可定制化的、可扩展的日志记录变得轻而易举。</p><p>在本文中,我们将探讨 Serilog 的一些基础知识、API、配置和示例。</p><span id="more"></span><h3 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h3><h4 id="日志级别"><a href="#日志级别" class="headerlink" title="日志级别"></a>日志级别</h4><p>Serilog 支持多个日志级别,包括以下级别(按照严重程度从高到低排列):</p><ul><li><code>Fatal</code>: 程序已经无法继续运行,需要立即解决的问题。</li><li><code>Error</code>: 一个错误发生,需要被处理。</li><li><code>Warning</code>: 一个警告,通常需要被留意,但是不需要立即处理。</li><li><code>Information</code>: 提供有用的信息,通常只有在调试应用程序时才需要关注。</li><li><code>Debug</code>: 提供调试信息,有助于调试应用程序。</li><li><code>Verbose</code>: 提供大量的细节信息,通常只用于调试复杂的问题。</li></ul><h4 id="日志输出"><a href="#日志输出" class="headerlink" title="日志输出"></a>日志输出</h4><p>Serilog 支持多种日志输出,包括:</p><ul><li>Console(控制台输出)</li><li>File(文件输出)</li><li>Seq(通过 <a href="https://datalust.co/seq">Seq</a> (opens new window)输出到日志收集器)</li><li>Elasticsearch(通过 <a href="https://www.elastic.co/elasticsearch/">Elasticsearch</a> (opens new window)输出到搜索引擎)</li></ul><p>此外,Serilog 还支持自定义日志输出器。</p><h4 id="日志格式"><a href="#日志格式" class="headerlink" title="日志格式"></a>日志格式</h4><p>Serilog 支持多种日志格式化方式,包括:</p><ul><li>简单文本格式</li><li>JSON 格式</li><li>Message Templates 格式(一种更加灵活的格式)</li></ul><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>可以通过 NuGet 安装 Serilog。</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">Install-Package Serilog</span><br></pre></td></tr></tbody></table></figure><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><h4 id="基础使用"><a href="#基础使用" class="headerlink" title="基础使用"></a>基础使用</h4><p>在应用程序中使用 Serilog 很简单。下面的示例演示了如何在控制台输出日志:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> Serilog;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title">Program</span></span><br><span class="line">{</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Main</span>()</span></span><br><span class="line"> {</span><br><span class="line"> Log.Logger = <span class="keyword">new</span> LoggerConfiguration()</span><br><span class="line"> .MinimumLevel.Debug()</span><br><span class="line"> .WriteTo.Console()</span><br><span class="line"> .CreateLogger();</span><br><span class="line"></span><br><span class="line"> Log.Information(<span class="string">"Hello, Serilog!"</span>);</span><br><span class="line"></span><br><span class="line"> Log.CloseAndFlush();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>上面的代码将 <code>Hello, Serilog!</code> 输出到控制台。</p><h4 id="详细使用"><a href="#详细使用" class="headerlink" title="详细使用"></a>详细使用</h4><h5 id="日志级别-1"><a href="#日志级别-1" class="headerlink" title="日志级别"></a>日志级别</h5><p>下面的示例演示了如何在 Serilog 中设置不同的日志级别:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> Serilog;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title">Program</span></span><br><span class="line">{</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Main</span>()</span></span><br><span class="line"> {</span><br><span class="line"> Log.Logger = <span class="keyword">new</span> LoggerConfiguration()</span><br><span class="line"> .MinimumLevel.Verbose()</span><br><span class="line"> .WriteTo.Console()</span><br><span class="line"> .CreateLogger();</span><br><span class="line"></span><br><span class="line"> Log.Verbose(<span class="string">"This is a verbose log message."</span>);</span><br><span class="line"> Log.Debug(<span class="string">"This is a debug log message."</span>);</span><br><span class="line"> Log.Information(<span class="string">"This is an informational log message."</span>);</span><br><span class="line"> Log.Warning(<span class="string">"This is a warning log message."</span>);</span><br><span class="line"> Log.Error(<span class="string">"This is an error log message."</span>);</span><br><span class="line"> Log.Fatal(<span class="string">"This is a fatal log message."</span>);</span><br><span class="line"></span><br><span class="line"> Log.CloseAndFlush();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h5 id="消息模板"><a href="#消息模板" class="headerlink" title="消息模板"></a>消息模板</h5><p>Serilog 采用消息模板来格式化日志消息。下面是一个简单的消息模板示例:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> Serilog;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title">Program</span></span><br><span class="line">{</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Main</span>()</span></span><br><span class="line"> {</span><br><span class="line"> Log.Logger = <span class="keyword">new</span> LoggerConfiguration()</span><br><span class="line"> .MinimumLevel.Debug()</span><br><span class="line"> .WriteTo.Console(outputTemplate: <span class="string">"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"</span>)</span><br><span class="line"> .CreateLogger();</span><br><span class="line"></span><br><span class="line"> Log.Information(<span class="string">"Hello, {Name}!"</span>, <span class="string">"Serilog"</span>);</span><br><span class="line"></span><br><span class="line"> Log.CloseAndFlush();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在上面的示例中,我们使用了一个带有模板的控制台输出器,并且在消息模板中使用了占位符 <code>{Name}</code>。当日志记录方法被调用时,<code>{Name}</code> 将被替换为 <code>Serilog</code>。我们可以看到 <code>Hello, Serilog!</code> 在控制台输出。</p><h5 id="日志属性"><a href="#日志属性" class="headerlink" title="日志属性"></a>日志属性</h5><p>Serilog 支持日志属性,这使得我们可以在日志消息中记录更多的信息。下面的示例演示了如何在日志消息中添加属性:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> Serilog;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title">Program</span></span><br><span class="line">{</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Main</span>()</span></span><br><span class="line"> {</span><br><span class="line"> Log.Logger = <span class="keyword">new</span> LoggerConfiguration()</span><br><span class="line"> .MinimumLevel.Debug()</span><br><span class="line"> .WriteTo.Console()</span><br><span class="line"> .CreateLogger();</span><br><span class="line"></span><br><span class="line"> Log.Information(<span class="string">"Processed {@Count} records in {Time} ms."</span>, <span class="keyword">new</span> { Count = <span class="number">10</span>, Time = <span class="number">123</span> });</span><br><span class="line"></span><br><span class="line"> Log.CloseAndFlush();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在上面的示例中,我们使用了一个匿名类型来表示日志属性。在日志消息中,我们使用了 <code>@</code> 符号来引用这个匿名类型。当日志记录方法被调用时,Serilog 将自动将匿名类型的属性添加到日志消息中。上面的代码将输出 <code>Processed { Count: 10, Time: 123 } records in 0 ms.</code>。</p><h5 id="输出模板定义"><a href="#输出模板定义" class="headerlink" title="输出模板定义"></a>输出模板定义</h5><ul><li><code>outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")</code></li></ul><p>这个也是官方的默认模板,我们可以这个扩展</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line">.WriteTo.File(<span class="string">"log.txt"</span>,</span><br><span class="line"> outputTemplate: <span class="string">"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"</span>)</span><br></pre></td></tr></tbody></table></figure><h5 id="输出到文件"><a href="#输出到文件" class="headerlink" title="输出到文件"></a>输出到文件</h5><ul><li><code>rollingInterval: RollingInterval.Day</code> 每天一个日志文件</li><li><code>outputTemplate</code> 输出格式模板</li></ul><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line">.WriteTo.File(</span><br><span class="line"> <span class="string">$"logs\\log-.txt"</span> ,</span><br><span class="line"> <span class="comment">//每天一个文件 ,生成类似:log-20230914.txt</span></span><br><span class="line"> <span class="comment">//fileSizeLimitBytes: null 文件限制大小:无 如果不配置默认是:1GB 1GB就不记录了</span></span><br><span class="line"> <span class="comment">//retainedFileCountLimit: null 保留文件数量 如果不配置只保留31天的日志</span></span><br><span class="line"> <span class="comment">//https://github.com/serilog/serilog-sinks-file </span></span><br><span class="line"> rollingInterval: RollingInterval.Day , retainedFileCountLimit: <span class="literal">null</span> ,</span><br><span class="line"> <span class="comment">//fileSizeLimitBytes: null,</span></span><br><span class="line"> <span class="comment">//单个文件大小: 1024000 1024000是1M</span></span><br><span class="line"> <span class="comment">//rollOnFileSizeLimit: true 就是滚动文件,如果超过单个文件大小,会滚动文件 产生类似:log.txt log_001.txt log_002.txt</span></span><br><span class="line"> fileSizeLimitBytes: <span class="number">3024000</span> , rollOnFileSizeLimit: <span class="literal">true</span> ,</span><br><span class="line"> <span class="comment">//非必填:指定最小等级</span></span><br><span class="line"> restrictedToMinimumLevel: LogEventLevel.Information ,</span><br><span class="line"> <span class="comment">//非必填: 也可以指定输出格式:这种格式好像与系统默认没有什么区别</span></span><br><span class="line"> <span class="comment">//outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">//outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {NewLine}{Version}{myval}"</span></span><br><span class="line"></span><br><span class="line"> outputTemplate: <span class="string">"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {NewLine}{Version}{myval} {NewLine}{UserId}{myid}{NewLine}"</span></span><br><span class="line"> )</span><br></pre></td></tr></tbody></table></figure><h5 id="结构化记录日志"><a href="#结构化记录日志" class="headerlink" title="结构化记录日志"></a>结构化记录日志</h5><h6 id="写入变量"><a href="#写入变量" class="headerlink" title="写入变量"></a>写入变量</h6><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> itemNumber = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">var</span> itemCount = <span class="number">999</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用占位符写入</span></span><br><span class="line"><span class="comment">// 结果:2023-09-15 22:23:54.576 +08:00 [INF] Processing item 10 of 999</span></span><br><span class="line"><span class="keyword">this</span>._logger.LogDebug( <span class="string">"Processing item {ItemNumber} of {ItemCount}"</span> , itemNumber , itemCount );</span><br></pre></td></tr></tbody></table></figure><h6 id="写入类"><a href="#写入类" class="headerlink" title="写入类"></a>写入类</h6><p>特别提示:记录对象后:</p><p>1.日志中会多一个<code>$type</code>属性 </p><p>2.日期类型数据格式化后都是这样格式:2023-09-16T22:26:27.5905512+08:00</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> wf = <span class="keyword">new</span> WeatherForecast</span><br><span class="line"> {</span><br><span class="line"> Date = DateTime.Now.AddDays( <span class="number">1</span> ) ,</span><br><span class="line"> TemperatureC = <span class="number">55</span> ,</span><br><span class="line"> Summary = <span class="string">""</span></span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"><span class="comment">// @表示一个对象 这样就可以把一个对象直接传递进去</span></span><br><span class="line"><span class="comment">//结果:2023-09-15 22:26:27.601 +08:00 [INF] WeatherForecast 的数据 {"Date":"2023-09-16T22:26:27.5905512+08:00","TemperatureC":55,"TemperatureF":130,"Summary":"","$type":"WeatherForecast"}</span></span><br><span class="line"><span class="keyword">this</span>._logger.LogInformation( <span class="string">"WeatherForecast 的数据 {@wf}"</span> , wf );</span><br></pre></td></tr></tbody></table></figure><h6 id="写入集合"><a href="#写入集合" class="headerlink" title="写入集合"></a>写入集合</h6><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line">List<<span class="built_in">string</span>> list1 = <span class="keyword">new</span> List<<span class="built_in">string</span>>() { <span class="string">"q1"</span> , <span class="string">"q2"</span> };</span><br><span class="line"> </span><br><span class="line"><span class="comment">//写入集合</span></span><br><span class="line"><span class="comment">//结果:2023-09-15 22:36:46.751 +08:00 [INF] 集合的数据 ["q1","q2"]</span></span><br><span class="line"><span class="keyword">this</span>._logger.LogInformation( <span class="string">"集合的数据 {@list1}"</span> , list1 );</span><br></pre></td></tr></tbody></table></figure><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line">List<WeatherForecast> listw = <span class="keyword">new</span> List<WeatherForecast>() {</span><br><span class="line"> <span class="keyword">new</span> WeatherForecast</span><br><span class="line"> {</span><br><span class="line"> Date = DateTime.Now.AddDays( <span class="number">1</span> ) ,</span><br><span class="line"> TemperatureC = <span class="number">11</span> ,</span><br><span class="line"> Summary = <span class="string">"one"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="keyword">new</span> WeatherForecast</span><br><span class="line"> {</span><br><span class="line"> Date = DateTime.Now.AddDays( <span class="number">2</span> ) ,</span><br><span class="line"> TemperatureC = <span class="number">22</span> ,</span><br><span class="line"> Summary = <span class="string">"two"</span></span><br><span class="line"> }};</span><br><span class="line"> </span><br><span class="line"><span class="comment">//写入集合</span></span><br><span class="line"><span class="comment">//结果: 2023-09-15 22:39:53.863 +08:00 [INF] 集合的数据 [{"Date":"2023-09-16T22:39:53.8634787+08:00","TemperatureC":11,"TemperatureF":51,"Summary":"one","$type":"WeatherForecast"},{"Date":"2023-09-17T22:39:53.8634842+08:00","TemperatureC":22,"TemperatureF":71,"Summary":"two","$type":"WeatherForecast"}]</span></span><br><span class="line"><span class="keyword">this</span>._logger.LogInformation( <span class="string">"集合的数据 {@listw}"</span> , listw );</span><br></pre></td></tr></tbody></table></figure><h6 id="写入匿名类"><a href="#写入匿名类" class="headerlink" title="写入匿名类"></a>写入匿名类</h6><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> user = <span class="keyword">new</span></span><br><span class="line"> {</span><br><span class="line"> Name = <span class="string">"Nick"</span> ,</span><br><span class="line"> Id = <span class="string">"nblumhardt"</span> ,</span><br><span class="line"> <span class="keyword">add</span> = <span class="keyword">new</span> List<<span class="built_in">string</span>>() { <span class="string">"add1"</span> , <span class="string">"add2"</span> } ,</span><br><span class="line"> man = <span class="keyword">new</span></span><br><span class="line"> {</span><br><span class="line"> age = <span class="number">1</span> ,</span><br><span class="line"> names = <span class="string">"qq"</span></span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"><span class="comment">// @表示一个对象(上面这个是匿名类也可以写入的) </span></span><br><span class="line"><span class="comment">//结果:2023-09-15 22:23:54.576 +08:00 [INF] Logged on user {"Name":"Nick","Id":"nblumhardt","add":["add1","add2"],"man":{"age":1,"names":"qq"}}</span></span><br><span class="line"><span class="keyword">this</span>._logger.LogInformation( <span class="string">"Logged on user {@user}"</span> , user );</span><br></pre></td></tr></tbody></table></figure></body></html>]]></content>
<summary type="html"><p><a href="https://serilog.net/"><img data-src="https://img.yousazoe.top/uPic/img/blog/CSHARP1/logging-serilog.png"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>Serilog 是一个 .NET 平台上的强大的日志记录库。它提供了丰富的 API 以及可插拔的日志格式化器和输出器,使得在 .NET 应用程序中实现可定制化的、可扩展的日志记录变得轻而易举。</p>
<p>在本文中,我们将探讨 Serilog 的一些基础知识、API、配置和示例。</p></summary>
<category term="C#编程学习 (CSharp Dev Learning)" scheme="https://yousazoe.top/categories/C-%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0-CSharp-Dev-Learning/"/>
<category term="Log" scheme="https://yousazoe.top/tags/Log/"/>
<category term="Serilog" scheme="https://yousazoe.top/tags/Serilog/"/>
<category term="C#" scheme="https://yousazoe.top/tags/C/"/>
</entry>
<entry>
<title>Inversion of Control Containers and the Dependency Injection Pattern (控制反转和依赖注入模式译文)</title>
<link href="https://yousazoe.top/archives/b69895a0.html"/>
<id>https://yousazoe.top/archives/b69895a0.html</id>
<published>2024-09-24T16:16:53.000Z</published>
<updated>2025-01-03T06:07:21.703Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://www.behance.net/gallery/132504335/They-Never-Told-Us-That-This-Was-Forever-1-14-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR5/header.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>北京中鼎项目用到了 .NET 的依赖注入框架,借此机会了解了控制反转等设计理念,追溯到 <a href="https://martinfowler.com/">Martin Fowler</a> 的这篇 <a href="https://martinfowler.com/articles/injection.html">Inversion of Control Containers and the Dependency Injection pattern</a>,特作此博文研读。</p><span id="more"></span><blockquote><p>One of the entertaining things about the enterprise Java world is the huge amount of activity in building alternatives to the mainstream J2EE technologies, much of it happening in open source. A lot of this is a reaction to the heavyweight complexity in the mainstream J2EE world, but much of it is also exploring alternatives and coming up with creative ideas. A common issue to deal with is how to wire together different elements: how do you fit together this web controller architecture with that database interface backing when they were built by different teams with little knowledge of each other. A number of frameworks have taken a stab at this problem, and several are branching out to provide a general capability to assemble components from different layers. These are often referred to as lightweight containers, examples include PicoContainer, and Spring.</p></blockquote><p>Java 社群近来掀起了一阵轻量级容器的热潮,这些容器能够帮助开发者将来自不同项目的组件组装成为一个内聚的应用程序。在它们的背后有着同一个模式,这个模式决定了这些容器进行组件装配的方式。人们用一个大而化之的名字来称呼这个模式:“控制反转”(Inversion of Control,IoC)。在本文中,我将深入探索这个模式的工作原理,给它一个更能描述其特点的名字——“依赖注入”(Dependency Injection),并将其与“服务定位器”(Service Locator)模式作一个比较。不过,这两者之间的差异并不太重要,更重要的是:应该将组件的配置与使用分离开——两个模式的目标都是这个。</p><p>在企业级 Java 的世界里存在一个有趣的现象:有很多人投入很多精力来研究主流 J2EE 技术的替代品——自然,这大多发生在开源社群。在很大程度上,这可以看作是开发者对主流 J2EE 技术的笨重和复杂作出的回应,但其中的确有很多极富创意的想法,的确提供了一些可供选择的方案。J2EE 开发者常遇到的一个问题就是如何组装不同的程序元素:如果 web 控制器体系结构和数据库接口是由不同的团队所开发的,彼此几乎一无所知,你应该如何让它们配合工作?很多框架尝试过解决这个问题,有几个框架索性朝这个方向发展,提供了更通用的“组装各层组件”的方案。这样的框架通常被称为“轻量级容器”,<code>PicoContainer</code> 和 <code>Spring</code> 都在此列中。</p><blockquote><p>Underlying these containers are a number of interesting design principles, things that go beyond both these specific containers and indeed the Java platform. Here I want to start exploring some of these principles. The examples I use are in Java, but like most of my writing the principles are equally applicable to other OO environments, particularly .NET.</p></blockquote><p>在这些容器背后,一些有趣的设计原则发挥着作用。这些原则已经超越了特定容器的范畴,甚至已经超越了 Java 平台的范畴。在本文中,我就要初步揭示这些原则。我使用的范例是 Java 代码,但正如我的大多数文章一样,这些原则也同样适用于别的 OO 环境,特别是.NET。</p><h3 id="组件服务-Components-and-Services"><a href="#组件服务-Components-and-Services" class="headerlink" title="组件服务 Components and Services"></a>组件服务 Components and Services</h3><blockquote><p>The topic of wiring elements together drags me almost immediately into the knotty terminology problems that surround the terms service and component. You find long and contradictory articles on the definition of these things with ease. For my purposes here are my current uses of these overloaded terms.</p></blockquote><p>“装配程序元素”,这样的话题立即将我拖进了一个棘手的术语问题:如何区分“服务”(<code>service</code>)和“组件”(<code>component</code>)?你可以毫不费力地找出关于这两个词定义的长篇大论,各种彼此矛盾的定义会让你感受到我所处的窘境。有鉴于此,对于这两个遭到了严重滥用的词汇,我将首先说明它们在本文中的用法。</p><blockquote><p>I use component to mean a glob of software that’s intended to be used, without change, by an application that is out of the control of the writers of the component. By ‘without change’ I mean that the using application doesn’t change the source code of the components, although they may alter the component’s behavior by extending it in ways allowed by the component writers.</p></blockquote><p>所谓“组件”是指这样一个软件单元:它将被作者无法控制的其他应用程序使用,但后者不能对组件进行修改。也就是说,使用一个组件的应用程序不能修改组件的源代码,但可以通过作者预留的某种途径对其进行扩展,以改变组件的行为。</p><blockquote><p>A service is similar to a component in that it’s used by foreign applications. The main difference is that I expect a component to be used locally (think jar file, assembly, dll, or a source import). A service will be used remotely through some remote interface, either synchronous or asynchronous (eg web service, messaging system, RPC, or socket.)</p></blockquote><p>服务和组件有某种相似之处:它们都将被外部的应用程序使用。在我看来,两者之间最大的差异在于:组件是在本地使用的(例如 JAR 文件、程序集、DLL、或者源码导入);而服务是要通过——同步或异步的——远程接口来远程使用的(例如 web service、消息系统、RPC,或者 socket)。</p><blockquote><p>I mostly use service in this article, but much of the same logic can be applied to local components too. Indeed often you need some kind of local component framework to easily access a remote service. But writing “component or service” is tiring to read and write, and services are much more fashionable at the moment.</p></blockquote><p>在本文中,我将主要使用“服务”这个词,但文中的大多数逻辑也同样适用于本地组件。实际上,为了方便地访问远程服务,你往往需要某种本地组件框架。不过,“组件或者服务”这样一个词组实在太麻烦了,而且“服务”这个词当下也很流行,所以本文将用“服务”指代这两者。</p><h3 id="一个简单的例子-A-Naive-Example"><a href="#一个简单的例子-A-Naive-Example" class="headerlink" title="一个简单的例子 A Naive Example"></a>一个简单的例子 A Naive Example</h3><blockquote><p>To help make all of this more concrete I’ll use a running example to talk about all of this. Like all of my examples it’s one of those super-simple examples; small enough to be unreal, but hopefully enough for you to visualize what’s going on without falling into the bog of a real example.</p><p>In this example I’m writing a component that provides a list of movies directed by a particular director. This stunningly useful function is implemented by a single method.</p></blockquote><p>为了更好地说明问题,我要引入一个例子。和我以前用的所有例子一样,这是一个超级简单的例子:它非常小,小得有点不够真实,但足以帮助你看清其中的道理,而不至于陷入真实例子的泥潭中无法自拔。</p><p>在这个例子中,我编写了一个组件,用于提供一份电影清单,清单上列出的影片都是由一位特定的导演执导的。实现这个伟大的功能只需要一个方法:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MovieLister</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Movie[] moviesDirectedBy(String arg) {</span><br><span class="line"> <span class="type">List</span> <span class="variable">allMovies</span> <span class="operator">=</span> finder.findAll();</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">Iterator</span> <span class="variable">it</span> <span class="operator">=</span> allMovies.iterator(); it.hasNext();) {</span><br><span class="line"> <span class="type">Movie</span> <span class="variable">movie</span> <span class="operator">=</span> (Movie) it.next();</span><br><span class="line"> <span class="keyword">if</span> (!movie.getDirector().equals(arg)) it.remove();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (Movie[]) allMovies.toArray(<span class="keyword">new</span> <span class="title class_">Movie</span>[allMovies.size()]);</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>The implementation of this function is naive in the extreme, it asks a finder object (which we’ll get to in a moment) to return every film it knows about. Then it just hunts through this list to return those directed by a particular director. This particular piece of naivety I’m not going to fix, since it’s just the scaffolding for the real point of this article.</p></blockquote><p>你可以看到,这个功能的实现极其简单:<code>moviesDirectedBy</code> 方法首先请求 <code>finder</code>(影片搜寻者)对象(我们稍后会谈到这个对象)返回后者所知道的所有影片,然后遍历 <code>finder</code> 对象返回的清单,并返回其中由特定的某个导演执导的影片。非常简单,不过不必担心,这只是整个例子的脚手架罢了。</p><blockquote><p>The real point of this article is this finder object, or particularly how we connect the lister object with a particular finder object. The reason why this is interesting is that I want my wonderful moviesDirectedBy method to be completely independent of how all the movies are being stored. So all the method does is refer to a finder, and all that finder does is know how to respond to the findAll method. I can bring this out by defining an interface for the finder.</p></blockquote><p>我们真正想要考察的是 <code>finder</code> 对象,或者说,如何将 <code>MovieLister</code> 对象与特定的 <code>finder</code> 对象连接起来。为什么我们对这个问题特别感兴趣?因为我希望上面这个漂亮的 <code>moviesDirectedBy</code> 方法完全不依赖于影片的实际存储方式。所以,这个方法只能引用一个 <code>finder</code> 对象,而 <code>finder</code> 对象则必须知道如何对 <code>findAll</code> 方法作出回应。为了帮助读者更清楚地理解,我给 <code>finder</code> 定义了一个接口:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">MovieFinder</span> {</span><br><span class="line"> List <span class="title function_">findAll</span><span class="params">()</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Now all of this is very well decoupled, but at some point I have to come up with a concrete class to actually come up with the movies. In this case I put the code for this in the constructor of my lister class.</p></blockquote><p>现在,两个对象之间没有什么耦合关系。但是,当我要实际寻找影片时,就必须涉及到 <code>MovieFinder</code> 的某个具体子类。在这里,我把“涉及具体子类”的代码放在 <code>MovieLister</code> 类的构造子中。</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MovieLister</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> MovieFinder finder;</span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">MovieLister</span><span class="params">()</span> {</span><br><span class="line"> finder = <span class="keyword">new</span> <span class="title class_">ColonDelimitedMovieFinder</span>(<span class="string">"movies1.txt"</span>);</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>The name of the implementation class comes from the fact that I’m getting my list from a colon delimited text file. I’ll spare you the details, after all the point is just that there’s some implementation.</p></blockquote><p>这个实现类的名字就说明:我将要从一个逗号分隔的文本文件中获得影片列表。你不必操心具体的实现细节,只要设想这样一个实现类就可以了。</p><blockquote><p>Now if I’m using this class for just myself, this is all fine and dandy. But what happens when my friends are overwhelmed by a desire for this wonderful functionality and would like a copy of my program? If they also store their movie listings in a colon delimited text file called “movies1.txt” then everything is wonderful. If they have a different name for their movies file, then it’s easy to put the name of the file in a properties file. But what if they have a completely different form of storing their movie listing: a SQL database, an XML file, a web service, or just another format of text file? In this case we need a different class to grab that data. Now because I’ve defined a MovieFinder interface, this won’t alter my moviesDirectedBy method. But I still need to have some way to get an instance of the right finder implementation into place.</p></blockquote><p>如果这个类只由我自己使用,一切都没问题。但是,如果我的朋友叹服于这个精彩的功能,也想使用我的程序,那又会怎么样呢?如果他们也把影片清单保存在一个逗号分隔的文本文件中,并且也把这个文件命名为“ movie1.txt”,那么一切还是没问题。如果他们只是给这个文件改改名,我也可以从一个配置文件获得文件名,这也很容易。但是,如果他们用完全不同的方式——例如 SQL 数据库、XML 文件、web service,或者另一种格式的文本文件——来存储影片清单呢?在这种情况下,我们需要用另一个类来获取数据。由于已经定义了 <code>MovieFinder</code> 接口,我可以不用修改 <code>moviesDirectedBy</code> 方法。但是,我仍然需要通过某种途径获得合适的 <code>MovieFinder</code> 实现类的实例。</p><p><img data-src="https://www.martinfowler.com/articles/injection/naive.gif" alt="Figure 1: The dependencies using a simple creation in the lister class"></p><blockquote><p>Figure 1 shows the dependencies for this situation. The MovieLister class is dependent on both the MovieFinder interface and upon the implementation. We would prefer it if it were only dependent on the interface, but then how do we make an instance to work with?</p></blockquote><p>图 1 展现了这种情况下的依赖关系:<code>MovieLister</code> 类既依赖于 <code>MovieFinder</code> 接口,也依赖于具体的实现类。我们当然希望 MovieLister 类只依赖于接口,但我们要如何获得一个 <code>MovieFinder</code> 子类的实例呢?</p><blockquote><p>In my book P of EAA, we described this situation as a Plugin. The implementation class for the finder isn’t linked into the program at compile time, since I don’t know what my friends are going to use. Instead we want my lister to work with any implementation, and for that implementation to be plugged in at some later point, out of my hands. The problem is how can I make that link so that my lister class is ignorant of the implementation class, but can still talk to an instance to do its work.</p></blockquote><p>在 <em>Patterns of Enterprise Application Architecture</em> 一书中,我们把这种情况称为“插件”(plugin):MovieFinder的实现类不是在编译期连入程序之中的,因为我并不知道我的朋友会使用哪个实现类。我们希望 <code>MovieLister</code> 类能够与 <code>MovieFinder</code> 的任何实现类配合工作,并且允许在运行期插入具体的实现类,插入动作完全脱离我(原作者)的控制。这里的问题就是:如何设计这个连接过程,使 <code>MovieLister</code> 类在不知道实现类细节的前提下与其实例协同工作。</p><blockquote><p>Expanding this into a real system, we might have dozens of such services and components. In each case we can abstract our use of these components by talking to them through an interface (and using an adapter if the component isn’t designed with an interface in mind). But if we wish to deploy this system in different ways, we need to use plugins to handle the interaction with these services so we can use different implementations in different deployments.</p></blockquote><p>将这个例子推而广之,在一个真实的系统中,我们可能有数十个服务和组件。在任何时候,我们总可以对使用组件的情形加以抽象,通过接口与具体的组件交流(如果组件并没有设计一个接口,也可以通过适配器与之交流)。但是,如果我们希望以不同的方式部署这个系统,就需要用插件机制来处理服务之间的交互过程,这样我们才可能在不同的部署方案中使用不同的实现。</p><blockquote><p>So the core problem is how do we assemble these plugins into an application? This is one of the main problems that this new breed of lightweight containers face, and universally they all do it using Inversion of Control.</p></blockquote><p>所以,现在的核心问题就是:如何将这些插件组合成一个应用程序?这正是新生的轻量级容器所面临的主要问题,而它们解决这个问题的手段无一例外地是控制反转(Inversion of Control)模式。</p><h3 id="控制反转-Inversion-of-Control"><a href="#控制反转-Inversion-of-Control" class="headerlink" title="控制反转 Inversion of Control"></a>控制反转 Inversion of Control</h3><blockquote><p>When these containers talk about how they are so useful because they implement “Inversion of Control” I end up very puzzled. Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels.</p></blockquote><p>几位轻量级容器的作者曾骄傲地对我说:这些容器非常有用,因为它们实现了“控制反转”。这样的说辞让我深感迷惑:控制反转是框架所共有的特征,如果仅仅因为使用了控制反转就认为这些轻量级容器与众不同,就好象在说“我的轿车是与众不同的,因为它有四个轮子”。</p><blockquote><p>The question is: “what aspect of control are they inverting?” When I first ran into inversion of control, it was in the main control of a user interface. Early user interfaces were controlled by the application program. You would have a sequence of commands like “Enter name”, “enter address”; your program would drive the prompts and pick up a response to each one. With graphical (or even screen based) UIs the UI framework would contain this main loop and your program instead provided event handlers for the various fields on the screen. The main control of the program was inverted, moved away from you to the framework.</p></blockquote><p>问题的关键在于:它们反转了哪方面的控制?我第一次接触到的控制反转针对的是用户界面的主控权。早期的用户界面是完全由应用程序来控制的,你预先设计一系列命令,例如“输入姓名”、“输入地址”等,应用程序逐条输出提示信息,并取回用户的响应。而在图形用户界面环境下,UI 框架将负责执行一个主循环,你的应用程序只需为屏幕的各个区域提供事件处理函数即可。在这里,程序的主控权发生了反转:从应用程序移到了框架。</p><blockquote><p>For this new breed of containers the inversion is about how they lookup a plugin implementation. In my naive example the lister looked up the finder implementation by directly instantiating it. This stops the finder from being a plugin. The approach that these containers use is to ensure that any user of a plugin follows some convention that allows a separate assembler module to inject the implementation into the lister.</p></blockquote><p>对于这些新生的容器,它们反转的是“如何定位插件的具体实现”。在前面那个简单的例子中,<code>MovieLister</code> 类负责定位 <code>MovieFinder</code> 的具体实现——它直接实例化后者的一个子类。这样一来,<code>MovieFinder</code> 也就不成其为一个插件了,因为它并不是在运行期插入应用程序中的。而这些轻量级容器则使用了更为灵活的办法,只要插件遵循一定的规则,一个独立的组装模块就能够将插件的具体实现“注射”到应用程序中。</p><blockquote><p>As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.</p></blockquote><p>因此,我想我们需要给这个模式起一个更能说明其特点的名字——“控制反转”这个名字太泛了,常常让人有些迷惑。与多位 IoC 爱好者讨论之后,我们决定将这个模式叫做“依赖注入” (Dependency Injection)。</p><blockquote><p>I’m going to start by talking about the various forms of dependency injection, but I’ll point out now that that’s not the only way of removing the dependency from the application class to the plugin implementation. The other pattern you can use to do this is Service Locator, and I’ll discuss that after I’m done with explaining Dependency Injection.</p></blockquote><p>下面,我将开始介绍 Dependency Injection 模式的几种不同形式。不过,在此之前,我要首先指出:要消除应用程序对插件实现的依赖,依赖注入并不是唯一的选择,你也可以用 Service Locator 模式获得同样的效果。介绍完 Dependency Injection 模式之后,我也会谈到 Service Locator 模式。</p><h3 id="依赖注入的几种形式-Forms-of-Dependency-Injection"><a href="#依赖注入的几种形式-Forms-of-Dependency-Injection" class="headerlink" title="依赖注入的几种形式 Forms of Dependency Injection"></a>依赖注入的几种形式 Forms of Dependency Injection</h3><blockquote><p>The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 2</p></blockquote><p>Dependency Injection 模式的基本思想是:用一个单独的对象(装配器)来获得 <code>MovieFinder</code> 的一个合适的实现,并将其实例赋给 <code>MovieLister</code> 类的一个字段。这样一来,我们就得到了图2所示的依赖图:</p><p><img data-src="https://www.martinfowler.com/articles/injection/injector.gif" alt="Figure 2: The dependencies for a Dependency Injector"></p><p>依赖注入的形式主要有三种,我分别将它们叫做构造子注入(Constructor Injection)、设值方法注入(Setter Injection)和接口注入(Interface Injection)。如果读过最近关于 IoC 的一些讨论材料,你不难看出:这三种注入形式分别就是 type 1 IoC(接口注入)、type 2 IoC(设值方法注入)和 type 3 IoC(构造子注入)。我发现数字编号往往比较难记,所以我使用了这里的命名方式。</p><h4 id="使用-PicoContainer-进行构造子注入"><a href="#使用-PicoContainer-进行构造子注入" class="headerlink" title="使用 PicoContainer 进行构造子注入"></a>使用 PicoContainer 进行构造子注入</h4><blockquote><p>I’ll start with showing how this injection is done using a lightweight container called PicoContainer. I’m starting here primarily because several of my colleagues at Thoughtworks are very active in the development of PicoContainer (yes, it’s a sort of corporate nepotism.)</p></blockquote><p>首先,我要向读者展示如何用一个名为 <code>PicoContainer</code> 的轻量级容器完成依赖注入。之所以从这里开始,主要是因为我在 ThoughtWorks 公司的几个同事在 <code>PicoContainer</code> 的开发社群中非常活跃——没错,也可以说是某种偏袒吧。</p><blockquote><p>PicoContainer uses a constructor to decide how to inject a finder implementation into the lister class. For this to work, the movie lister class needs to declare a constructor that includes everything it needs injected.</p></blockquote><p><code>PicoContainer</code> 通过构造子来判断“如何将 <code>MovieFinder</code> 实例注入 <code>MovieLister</code> 类”。因此,<code>MovieLister</code> 类必须声明一个构造子,并在其中包含所有需要注入的元素:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MovieLister</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">MovieLister</span><span class="params">(MovieFinder finder)</span> {</span><br><span class="line"> <span class="built_in">this</span>.finder = finder; </span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>The finder itself will also be managed by the pico container, and as such will have the filename of the text file injected into it by the container.</p></blockquote><p><code>MovieFinder</code> 实例本身也将由 <code>PicoContainer</code> 来管理,因此文本文件的名字也可以由容器注入:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ColonMovieFinder</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">ColonMovieFinder</span><span class="params">(String filename)</span> {</span><br><span class="line"> <span class="built_in">this</span>.filename = filename;</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>The pico container then needs to be told which implementation class to associate with each interface, and which string to inject into the finder.</p></blockquote><p>随后,需要告诉 <code>PicoContainer</code>:各个接口分别与哪个实现类关联、将哪个字符串注入 <code>MovieFinder</code> 组件。</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> MutablePicoContainer <span class="title function_">configureContainer</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">MutablePicoContainer</span> <span class="variable">pico</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultPicoContainer</span>();</span><br><span class="line"> Parameter[] finderParams = {<span class="keyword">new</span> <span class="title class_">ConstantParameter</span>(<span class="string">"movies1.txt"</span>)};</span><br><span class="line"> pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);</span><br><span class="line"> pico.registerComponentImplementation(MovieLister.class);</span><br><span class="line"> <span class="keyword">return</span> pico;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><blockquote><p>This configuration code is typically set up in a different class. For our example, each friend who uses my lister might write the appropriate configuration code in some setup class of their own. Of course it’s common to hold this kind of configuration information in separate config files. You can write a class to read a config file and set up the container appropriately. Although PicoContainer doesn’t contain this functionality itself, there is a closely related project called NanoContainer that provides the appropriate wrappers to allow you to have XML configuration files. Such a nano container will parse the XML and then configure an underlying pico container. The philosophy of the project is to separate the config file format from the underlying mechanism.</p></blockquote><p>这段配置代码通常位于另一个类。对于我们这个例子,使用我的 <code>MovieLister</code> 类的朋友需要在自己的设置类中编写合适的配置代码。当然,还可以将这些配置信息放在一个单独的配置文件中,这也是一种常见的做法。你可以编写一个类来读取配置文件,然后对容器进行合适的设置。尽管 <code>PicoContainer</code> 本身并不包含这项功能,但另一个与它关系紧密的项目 <code>NanoContainer</code> 提供了一些包装,允许开发者使用 XML 配置文件保存配置信息。<code>NanoContainer</code> 能够解析 XML 文件,并对底下的 <code>PicoContainer</code> 进行配置。这个项目的哲学观念就是:将配置文件的格式与底下的配置机制分离开。</p><blockquote><p>To use the container you write code something like this.</p></blockquote><p>使用这个容器,你写出的代码大概会是这样:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testWithPico</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">MutablePicoContainer</span> <span class="variable">pico</span> <span class="operator">=</span> configureContainer();</span><br><span class="line"> <span class="type">MovieLister</span> <span class="variable">lister</span> <span class="operator">=</span> (MovieLister) pico.getComponentInstance(MovieLister.class);</span><br><span class="line"> Movie[] movies = lister.moviesDirectedBy(<span class="string">"Sergio Leone"</span>);</span><br><span class="line"> assertEquals(<span class="string">"Once Upon a Time in the West"</span>, movies[<span class="number">0</span>].getTitle());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Although in this example I’ve used constructor injection, PicoContainer also supports setter injection, although its developers do prefer constructor injection.</p></blockquote><p>尽管在这里我使用了构造子注入,实际上 <code>PicoContainer</code> 也支持设值方法注入,不过该项目的开发者更推荐使用构造子注入。</p><h4 id="使用-Spring-进行设值方法注入"><a href="#使用-Spring-进行设值方法注入" class="headerlink" title="使用 Spring 进行设值方法注入"></a>使用 Spring 进行设值方法注入</h4><blockquote><p>The Spring framework is a wide ranging framework for enterprise Java development. It includes abstraction layers for transactions, persistence frameworks, web application development and JDBC. Like PicoContainer it supports both constructor and setter injection, but its developers tend to prefer setter injection - which makes it an appropriate choice for this example.</p></blockquote><p>Spring 框架是一个用途广泛的企业级 Java 开发框架,其中包括了针对事务、持久化框架、web 应用开发和 JDBC 等常用功能的抽象。和 <code>PicoContainer</code> 一样,它也同时支持构造子注入和设值方法注入,但该项目的开发者更推荐使用设值方法注入——恰好适合这个例子。</p><blockquote><p>To get my movie lister to accept the injection I define a setting method for that service</p></blockquote><p>为了让 <code>MovieLister</code> 类接受注入,我需要为它定义一个设值方法,该方法接受类型为 <code>MovieFinder</code> 的参数:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MovieLister</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> MovieFinder finder;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setFinder</span><span class="params">(MovieFinder finder)</span> {</span><br><span class="line"> <span class="built_in">this</span>.finder = finder;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Similarly I define a setter for the filename.</p></blockquote><p>类似地,在 <code>MovieFinder</code> 的实现类中,我也定义了一个设值方法,接受类型为 <code>String</code> 的参数:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ColonMovieFinder</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setFilename</span><span class="params">(String filename)</span> {</span><br><span class="line"> <span class="built_in">this</span>.filename = filename;</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>The third step is to set up the configuration for the files. Spring supports configuration through XML files and also through code, but XML is the expected way to do it.</p></blockquote><p>第三步是设定配置文件。Spring 支持多种配置方式,你可以通过 XML 文件进行配置,也可以直接在代码中配置。不过,XML 文件是比较理想的配置方式。</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">beans</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"MovieLister"</span> <span class="attr">class</span>=<span class="string">"spring.MovieLister"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"finder"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ref</span> <span class="attr">local</span>=<span class="string">"MovieFinder"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"MovieFinder"</span> <span class="attr">class</span>=<span class="string">"spring.ColonMovieFinder"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"filename"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>movies1.txt<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></tbody></table></figure><blockquote><p>The test then looks like this.</p></blockquote><p>于是,测试代码大概就像下面这样:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testWithSpring</span><span class="params">()</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> <span class="type">ApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileSystemXmlApplicationContext</span>(<span class="string">"spring.xml"</span>);</span><br><span class="line"> <span class="type">MovieLister</span> <span class="variable">lister</span> <span class="operator">=</span> (MovieLister) ctx.getBean(<span class="string">"MovieLister"</span>);</span><br><span class="line"> Movie[] movies = lister.moviesDirectedBy(<span class="string">"Sergio Leone"</span>);</span><br><span class="line"> assertEquals(<span class="string">"Once Upon a Time in the West"</span>, movies[<span class="number">0</span>].getTitle());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="接口注入"><a href="#接口注入" class="headerlink" title="接口注入"></a>接口注入</h4><blockquote><p>The third injection technique is to define and use interfaces for the injection. Avalon is an example of a framework that uses this technique in places. I’ll talk a bit more about that later, but in this case I’m going to use it with some simple sample code.</p></blockquote><p>除了前面两种注入技术,还可以在接口中定义需要注入的信息,并通过接口完成注入。Avalon 框架就使用了类似的技术。在这里,我首先用简单的范例代码说明它的用法,后面还会有更深入的讨论。</p><blockquote><p>With this technique I begin by defining an interface that I’ll use to perform the injection through. Here’s the interface for injecting a movie finder into an object.</p></blockquote><p>首先,我需要定义一个接口,组件的注入将通过这个接口进行。在本例中,这个接口的用途是将一个 <code>MovieFinder</code> 实例注入继承了该接口的对象。</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">InjectFinder</span> {</span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">injectFinder</span><span class="params">(MovieFinder finder)</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><blockquote><p>This interface would be defined by whoever provides the MovieFinder interface. It needs to be implemented by any class that wants to use a finder, such as the lister.</p></blockquote><p>这个接口应该由提供 <code>MovieFinder</code> 接口的人一并提供。任何想要使用 <code>MovieFinder</code> 实例的类(例如 <code>MovieLister</code> 类)都必须实现这个接口。</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MovieLister</span> <span class="keyword">implements</span> <span class="title class_">InjectFinder</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">injectFinder</span><span class="params">(MovieFinder finder)</span> {</span><br><span class="line"> <span class="built_in">this</span>.finder = finder;</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>I use a similar approach to inject the filename into the finder implementation.</p></blockquote><p>然后,我使用类似的方法将文件名注入 <code>MovieFinder</code> 的实现类:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">InjectFinderFilename</span> {</span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">injectFilename</span> <span class="params">(String filename)</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ColonMovieFinder</span> <span class="keyword">implements</span> <span class="title class_">MovieFinder</span>, InjectFinderFilename...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">injectFilename</span><span class="params">(String filename)</span> {</span><br><span class="line"> <span class="built_in">this</span>.filename = filename;</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Then, as usual, I need some configuration code to wire up the implementations. For simplicity’s sake I’ll do it in code.</p></blockquote><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Tester</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Container container;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">configureContainer</span><span class="params">()</span> {</span><br><span class="line"> container = <span class="keyword">new</span> <span class="title class_">Container</span>();</span><br><span class="line"> registerComponents();</span><br><span class="line"> registerInjectors();</span><br><span class="line"> container.start();</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>When the dependent is a class written for this container, it makes sense for the component to implement the injector interface itself, as I do here with the movie finder. For generic classes, such as the string, I use an inner class within the configuration code.</p></blockquote><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ColonMovieFinder</span> <span class="keyword">implements</span> <span class="title class_">Injector</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">inject</span><span class="params">(Object target)</span> {</span><br><span class="line"> ((InjectFinder) target).injectFinder(<span class="built_in">this</span>); </span><br><span class="line"> }</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Tester</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">FinderFilenameInjector</span> <span class="keyword">implements</span> <span class="title class_">Injector</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">inject</span><span class="params">(Object target)</span> {</span><br><span class="line"> ((InjectFinderFilename)target).injectFilename(<span class="string">"movies1.txt"</span>); </span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>The tests then use the container.</p></blockquote><p>测试代码则可以直接使用这个字段:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Tester</span>…</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testIface</span><span class="params">()</span> {</span><br><span class="line"> configureContainer();</span><br><span class="line"> <span class="type">MovieLister</span> <span class="variable">lister</span> <span class="operator">=</span> (MovieLister)container.lookup(<span class="string">"MovieLister"</span>);</span><br><span class="line"> Movie[] movies = lister.moviesDirectedBy(<span class="string">"Sergio Leone"</span>);</span><br><span class="line"> assertEquals(<span class="string">"Once Upon a Time in the West"</span>, movies[<span class="number">0</span>].getTitle());</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>The container uses the declared injection interfaces to figure out the dependencies and the injectors to inject the correct dependents. (The specific container implementation I did here isn’t important to the technique, and I won’t show it because you’d only laugh.)</p></blockquote><h3 id="使用-Service-Locator-Using-a-Service-Locator"><a href="#使用-Service-Locator-Using-a-Service-Locator" class="headerlink" title="使用 Service Locator Using a Service Locator"></a>使用 Service Locator Using a Service Locator</h3><blockquote><p>The key benefit of a Dependency Injector is that it removes the dependency that the MovieLister class has on the concrete MovieFinder implementation. This allows me to give listers to friends and for them to plug in a suitable implementation for their own environment. Injection isn’t the only way to break this dependency, another is to use a service locator.</p></blockquote><p>依赖注入的最大好处在于:它消除了 <code>MovieLister</code> 类对具体 <code>MovieFinder</code> 实现类的依赖。这样一来,我就可以把 <code>MovieLister</code> 类交给朋友,让他们根据自己的环境插入一个合适的 <code>MovieFinder</code> 实现即可。不过,Dependency Injection 模式并不是打破这层依赖关系的唯一手段,另一种方法是使用 Service Locator 模式。</p><blockquote><p>The basic idea behind a service locator is to have an object that knows how to get hold of all of the services that an application might need. So a service locator for this application would have a method that returns a movie finder when one is needed. Of course this just shifts the burden a tad, we still have to get the locator into the lister, resulting in the dependencies of Figure 3</p></blockquote><p>Service Locator 模式背后的基本思想是:有一个对象(即服务定位器)知道如何获得一个应用程序所需的所有服务。也就是说,在我们的例子中,服务定位器应该有一个方法,用于获得一个 <code>MovieFinder</code> 实例。当然,这不过是把麻烦换了一个样子,我们仍然必须在 <code>MovieLister</code> 中获得服务定位器,最终得到的依赖关系如图 3 所示:</p><p><img data-src="https://www.martinfowler.com/articles/injection/locator.gif" alt="Figure 3: The dependencies for a Service Locator"></p><blockquote><p>In this case I’ll use the ServiceLocator as a singleton Registry. The lister can then use that to get the finder when it’s instantiated.</p></blockquote><p>在这里,我把 ServiceLocator 类实现为一个 Singleton 的注册表,于是 MovieLister 就可以在实例化时通过 ServiceLocator 获得一个 MovieFinder 实例。</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MovieLister</span>...</span><br><span class="line"></span><br><span class="line"> <span class="type">MovieFinder</span> <span class="variable">finder</span> <span class="operator">=</span> ServiceLocator.movieFinder();</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ServiceLocator</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> MovieFinder <span class="title function_">movieFinder</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> soleInstance.movieFinder;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> ServiceLocator soleInstance;</span><br><span class="line"> <span class="keyword">private</span> MovieFinder movieFinder;</span><br></pre></td></tr></tbody></table></figure><blockquote><p>As with the injection approach, we have to configure the service locator. Here I’m doing it in code, but it’s not hard to use a mechanism that would read the appropriate data from a configuration file.</p></blockquote><p>和注入的方式一样,我们也必须对服务定位器加以配置。在这里,我直接在代码中进行配置,但设计一种通过配置文件获得数据的机制也并非难事。</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Tester</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">configure</span><span class="params">()</span> {</span><br><span class="line"> ServiceLocator.load(<span class="keyword">new</span> <span class="title class_">ServiceLocator</span>(<span class="keyword">new</span> <span class="title class_">ColonMovieFinder</span>(<span class="string">"movies1.txt"</span>)));</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ServiceLocator</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">load</span><span class="params">(ServiceLocator arg)</span> {</span><br><span class="line"> soleInstance = arg;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">ServiceLocator</span><span class="params">(MovieFinder movieFinder)</span> {</span><br><span class="line"> <span class="built_in">this</span>.movieFinder = movieFinder;</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Here’s the test code.</p></blockquote><p>下面是测试代码:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Tester</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testSimple</span><span class="params">()</span> {</span><br><span class="line"> configure();</span><br><span class="line"> <span class="type">MovieLister</span> <span class="variable">lister</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MovieLister</span>();</span><br><span class="line"> Movie[] movies = lister.moviesDirectedBy(<span class="string">"Sergio Leone"</span>);</span><br><span class="line"> assertEquals(<span class="string">"Once Upon a Time in the West"</span>, movies[<span class="number">0</span>].getTitle());</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>I’ve often heard the complaint that these kinds of service locators are a bad thing because they aren’t testable because you can’t substitute implementations for them. Certainly you can design them badly to get into this kind of trouble, but you don’t have to. In this case the service locator instance is just a simple data holder. I can easily create the locator with test implementations of my services.</p></blockquote><p>我时常听到这样的论调:这样的服务定位器不是什么好东西,因为你无法替换它返回的服务实现,从而导致无法对它们进行测试。当然,如果你的设计很糟糕,你的确会遇到这样的麻烦;但你也可以选择良好的设计。在这个例子中,ServiceLocator 实例仅仅是一个简单的数据容器,只需要对它做一些简单的修改,就可以让它返回用于测试的服务实现。</p><blockquote><p>For a more sophisticated locator I can subclass service locator and pass that subclass into the registry’s class variable. I can change the static methods to call a method on the instance rather than accessing instance variables directly. I can provide thread–specific locators by using thread–specific storage. All of this can be done without changing clients of service locator.</p></blockquote><p>对于更复杂的情况,我可以从 ServiceLocator 派生出多个子类,并将子类型的实例传递给注册表的类变量。另外,我可以修改 ServiceLocator 的静态方法,使其调用 ServiceLocator 实例的方法,而不是直接访问实例变量。我还可以使用特定于线程的存储机制,从而提供特定于线程的服务定位器。所有这一切改进都无须修改 ServiceLocator 的使用者。</p><blockquote><p>A way to think of this is that service locator is a registry not a singleton. A singleton provides a simple way of implementing a registry, but that implementation decision is easily changed.</p></blockquote><p>一种改进的思路是:服务定位器仍然是一个注册表,但不是 Singleton。Singleton 的确是实现注册表的一种简单途径,但这只是一个实现时的决定,可以很轻松地改变它。</p><h4 id="为定位器提供分离的接口"><a href="#为定位器提供分离的接口" class="headerlink" title="为定位器提供分离的接口"></a>为定位器提供分离的接口</h4><blockquote><p>One of the issues with the simple approach above, is that the MovieLister is dependent on the full service locator class, even though it only uses one service. We can reduce this by using a role interface. That way, instead of using the full service locator interface, the lister can declare just the bit of interface it needs.</p></blockquote><p>上面这种简单的实现方式有一个问题:<code>MovieLister</code> 类将依赖于整个 <code>ServiceLocator</code> 类,但它需要使用的却只是后者所提供的一项服务。我们可以针对这项服务提供一个单独的接口,减少 <code>MovieLister</code> 对 <code>ServiceLocator</code> 的依赖程度。这样一来,MovieLister 就不必使用整个的 <code>ServiceLocator</code> 接口,只需声明它想要使用的那部分接口。</p><blockquote><p>In this situation the provider of the lister would also provide a locator interface which it needs to get hold of the finder.</p></blockquote><p>此时,<code>MovieLister</code> 类的提供者也应该一并提供一个定位器接口,使用者可以通过这个接口获得 <code>MovieFinder</code> 实例。</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">MovieFinderLocator</span> {</span><br><span class="line"> <span class="keyword">public</span> MovieFinder <span class="title function_">movieFinder</span><span class="params">()</span>;</span><br></pre></td></tr></tbody></table></figure><blockquote><p>The locator then needs to implement this interface to provide access to a finder.</p></blockquote><p>真实的服务定位器需要实现上述接口,提供访问 <code>MovieFinder</code> 实例的能力:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">MovieFinderLocator</span> <span class="variable">locator</span> <span class="operator">=</span> ServiceLocator.locator();</span><br><span class="line"><span class="type">MovieFinder</span> <span class="variable">finder</span> <span class="operator">=</span> locator.movieFinder();</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ServiceLocator <span class="title function_">locator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> soleInstance;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> MovieFinder <span class="title function_">movieFinder</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> movieFinder;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> ServiceLocator soleInstance;</span><br><span class="line"> <span class="keyword">private</span> MovieFinder movieFinder;</span><br></pre></td></tr></tbody></table></figure><blockquote><p>You’ll notice that since we want to use an interface, we can’t just access the services through static methods any more. We have to use the class to get a locator instance and then use that to get what we need.</p></blockquote><p>你应该已经注意到了:由于想要使用接口,我们不能再通过静态方法直接访问服务——我们必须首先通过 <code>ServiceLocator</code> 类获得定位器实例,然后使用定位器实例得到我们想要的服务。</p><h4 id="动态服务定位器"><a href="#动态服务定位器" class="headerlink" title="动态服务定位器"></a>动态服务定位器</h4><blockquote><p>The above example was static, in that the service locator class has methods for each of the services that you need. This isn’t the only way of doing it, you can also make a dynamic service locator that allows you to stash any service you need into it and make your choices at runtime.</p></blockquote><p>上面是一个静态定位器的例子——对于你所需要的每项服务,ServiceLocator 类都有对应的方法。这并不是实现服务定位器的唯一方式,你也可以创建一个动态服务定位器,你可以在其中注册需要的任何服务,并在运行期决定获得哪一项服务。</p><blockquote><p>In this case, the service locator uses a map instead of fields for each of the services, and provides generic methods to get and load services.</p></blockquote><p>在本例中,ServiceLocator 使用一个 map 来保存服务信息,而不再是将这些信息保存在字段中。此外,ServiceLocator 还提供了一个通用的方法,用于获取和加载服务对象。</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ServiceLocator</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> ServiceLocator soleInstance;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">load</span><span class="params">(ServiceLocator arg)</span> {</span><br><span class="line"> soleInstance = arg;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">private</span> <span class="type">Map</span> <span class="variable">services</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>();</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title function_">getService</span><span class="params">(String key)</span>{</span><br><span class="line"> <span class="keyword">return</span> soleInstance.services.get(key);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">loadService</span> <span class="params">(String key, Object service)</span> {</span><br><span class="line"> services.put(key, service);</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Configuring involves loading a service with an appropriate key.</p></blockquote><p>同样需要对服务定位器进行配置,将服务对象与适当的关键字加载到定位器中:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Tester</span>...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">configure</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">ServiceLocator</span> <span class="variable">locator</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServiceLocator</span>();</span><br><span class="line"> locator.loadService(<span class="string">"MovieFinder"</span>, <span class="keyword">new</span> <span class="title class_">ColonMovieFinder</span>(<span class="string">"movies1.txt"</span>));</span><br><span class="line"> ServiceLocator.load(locator);</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><blockquote><p>I use the service by using the same key string.</p></blockquote><p>我使用与服务对象类名称相同的字符串作为服务对象的关键字:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MovieLister</span>...</span><br><span class="line"></span><br><span class="line"> <span class="type">MovieFinder</span> <span class="variable">finder</span> <span class="operator">=</span> (MovieFinder) ServiceLocator.getService(<span class="string">"MovieFinder"</span>);</span><br></pre></td></tr></tbody></table></figure><blockquote><p>On the whole I dislike this approach. Although it’s certainly flexible, it’s not very explicit. The only way I can find out how to reach a service is through textual keys. I prefer explicit methods because it’s easier to find where they are by looking at the interface definitions.</p></blockquote><p>总体而言,我不喜欢这种方式。无疑,这样实现的服务定位器具有更强的灵活性,但它的使用方式不够直观明朗。我只有通过文本形式的关键字才能找到一个服务对象。相比之下,我更欣赏“通过一个方法明确获得服务对象”的方式,因为这让使用者能够从接口定义中清楚地知道如何获得某项服务。</p><h4 id="用-Avalon-兼顾服务定位器和依赖注入"><a href="#用-Avalon-兼顾服务定位器和依赖注入" class="headerlink" title="用 Avalon 兼顾服务定位器和依赖注入"></a>用 Avalon 兼顾服务定位器和依赖注入</h4><blockquote><p>Dependency injection and a service locator aren’t necessarily mutually exclusive concepts. A good example of using both together is the Avalon framework. Avalon uses a service locator, but uses injection to tell components where to find the locator.</p></blockquote><p>Dependency Injection 和 Service Locator 两个模式并不是互斥的,你可以同时使用它们,Avalon 框架就是这样的一个例子。Avalon 使用了服务定位器,但“如何获得定位器”的信息则是通过注入的方式告知组件的。</p><blockquote><p>Berin Loritsch sent me this simple version of my running example using Avalon.</p></blockquote><p>对于前面一直使用的例子,Berin Loritsch 发送给了我一个简单的 Avalon 实现版本:</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyMovieLister</span> <span class="keyword">implements</span> <span class="title class_">MovieLister</span>, Serviceable {</span><br><span class="line"><span class="keyword">private</span> MovieFinder finder;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">service</span><span class="params">( ServiceManager manager )</span> <span class="keyword">throws</span> ServiceException {</span><br><span class="line"> finder = (MovieFinder)manager.lookup(“finder”);</span><br><span class="line"> } </span><br></pre></td></tr></tbody></table></figure><blockquote><p>The service method is an example of interface injection, allowing the container to inject a service manager into MyMovieLister. The service manager is an example of a service locator. In this example the lister doesn’t store the manager in a field, instead it immediately uses it to lookup the finder, which it does store.</p></blockquote><p><code>service</code> 方法就是接口注入的例子,它使容器可以将一个 <code>ServiceManager</code> 对象注入 <code>MyMovieLister</code> 对象。<code>ServiceManager</code> 则是一个服务定位器。在这个例子中,<code>MyMovieLister</code> 并不把 <code>ServiceManager</code> 对象保存在字段中,而是马上借助它找到 <code>MovieFinder</code> 实例,并将后者保存起来。</p><h3 id="作出一个选择-Deciding-which-option-to-use"><a href="#作出一个选择-Deciding-which-option-to-use" class="headerlink" title="作出一个选择 Deciding which option to use"></a>作出一个选择 Deciding which option to use</h3><blockquote><p>So far I’ve concentrated on explaining how I see these patterns and their variations. Now I can start talking about their pros and cons to help figure out which ones to use and when.</p></blockquote><p>到现在为止,我一直在阐述自己对这两个模式(Dependency Injection 模式和 Service Locator 模式)以及它们的变化形式的看法。现在,我要开始讨论他们的优点和缺点,以便指出它们各自适用的场景。</p><h4 id="Service-Locator-vs-Dependency-Injection"><a href="#Service-Locator-vs-Dependency-Injection" class="headerlink" title="Service Locator vs. Dependency Injection"></a>Service Locator vs. Dependency Injection</h4><blockquote><p>The fundamental choice is between Service Locator and Dependency Injection. The first point is that both implementations provide the fundamental decoupling that’s missing in the naive example - in both cases application code is independent of the concrete implementation of the service interface. The important difference between the two patterns is about how that implementation is provided to the application class. With service locator the application class asks for it explicitly by a message to the locator. With injection there is no explicit request, the service appears in the application class - hence the inversion of control.</p></blockquote><p>首先,我们面临 Service Locator 和 Dependency Injection 之间的选择。应该注意,尽管我们前面那个简单的例子不足以表现出来,实际上这两个模式都提供了基本的解耦合能力——无论使用哪个模式,应用程序代码都不依赖于服务接口的具体实现。两者之间最重要的区别在于:这个“具体实现”以什么方式提供给应用程序代码。使用 Service Locator 模式时,应用程序代码直接向服务定位器发送一个消息,明确要求服务的实现;使用 Dependency Injection 模式时,应用程序代码不发出显式的请求,服务的实现自然会出现在应用程序代码中,这也就是所谓 “控制反转”</p><blockquote><p>Inversion of control is a common feature of frameworks, but it’s something that comes at a price. It tends to be hard to understand and leads to problems when you are trying to debug. So on the whole I prefer to avoid it unless I need it. This isn’t to say it’s a bad thing, just that I think it needs to justify itself over the more straightforward alternative.</p></blockquote><p>控制反转是框架的共同特征,但它也要求你付出一定的代价:它会增加理解的难度,并且给调试带来一定的困难。所以,整体来说,除非必要,否则我会尽量避免使用它。这并不意味着控制反转不好,只是我认为在很多时候使用一个更为直观的方案(例如 Service Locator 模式)会比较合适。</p><blockquote><p>The key difference is that with a Service Locator every user of a service has a dependency to the locator. The locator can hide dependencies to other implementations, but you do need to see the locator. So the decision between locator and injector depends on whether that dependency is a problem.</p></blockquote><p>一个关键的区别在于:使用 Service Locator 模式时,服务的使用者必须依赖于服务定位器。定位器可以隐藏使用者对服务具体实现的依赖,但你必须首先看到定位器本身。所以,问题的答案就很明朗了:选择 Service Locator 还是 Dependency Injection,取决于“对定位器的依赖”是否会给你带来麻烦。</p><blockquote><p>Using dependency injection can help make it easier to see what the component dependencies are. With dependency injector you can just look at the injection mechanism, such as the constructor, and see the dependencies. With the service locator you have to search the source code for calls to the locator. Modern IDEs with a find references feature make this easier, but it’s still not as easy as looking at the constructor or setting methods.</p></blockquote><p>Dependency Injection 模式可以帮助你看清组件之间的依赖关系:你只需观察依赖注入的机制(例如构造子),就可以掌握整个依赖关系。而使用 Service Locator 模式时,你就必须在源代码中到处搜索对服务定位器的调用。具备全文检索能力的 IDE 可以略微简化这一工作,但还是不如直接观察构造子或者设值方法来得轻松。</p><blockquote><p>A lot of this depends on the nature of the user of the service. If you are building an application with various classes that use a service, then a dependency from the application classes to the locator isn’t a big deal. In my example of giving a Movie Lister to my friends, then using a service locator works quite well. All they need to do is to configure the locator to hook in the right service implementations, either through some configuration code or through a configuration file. In this kind of scenario I don’t see the injector’s inversion as providing anything compelling.</p></blockquote><p>这个选择主要取决于服务使用者的性质。如果你的应用程序中有很多不同的类要使用一个服务,那么应用程序代码对服务定位器的依赖就不是什么大问题。在前面的例子中,我要把 <code>MovieLister</code> 类交给朋友去用,这种情况下使用服务定位器就很好:我的朋友们只需要对定位器做一点配置(通过配置文件或者某些配置性的代码),使其提供合适的服务实现就可以了。在这种情况下,我看不出 Dependency Injection 模式提供的控制反转有什么吸引人的地方。</p><blockquote><p>The difference comes if the lister is a component that I’m providing to an application that other people are writing. In this case I don’t know much about the APIs of the service locators that my customers are going to use. Each customer might have their own incompatible service locators. I can get around some of this by using the segregated interface. Each customer can write an adapter that matches my interface to their locator, but in any case I still need to see the first locator to lookup my specific interface. And once the adapter appears then the simplicity of the direct connection to a locator is beginning to slip.</p></blockquote><p>但是,如果把 <code>MovieLister</code> 看作一个组件,要将它提供给别人写的应用程序去使用,情况就不同了。在这种时候,我无法预测使用者会使用什么样的服务定位器 API,每个使用者都可能有自己的服务定位器,而且彼此之间无法兼容。一种解决办法是为每项服务提供单独的接口,使用者可以编写一个适配器,让我的接口与他们的服务定位器相配合。但即便如此,我仍然需要到第一个服务定位器中寻找我规定的接口。而且一旦用上了适配器,服务定位器所提供的简单性就被大大削弱了。</p><blockquote><p>Since with an injector you don’t have a dependency from a component to the injector, the component cannot obtain further services from the injector once it’s been configured.</p></blockquote><p>另一方面,如果使用 Dependency Injection 模式,组件与注入器之间不会有依赖关系,因此组件无法从注入器那里获得更多的服务,只能获得配置信息中所提供的那些。这也是 Dependency Injection 模式的局限性之一。</p><blockquote><p>A common reason people give for preferring dependency injection is that it makes testing easier. The point here is that to do testing, you need to easily replace real service implementations with stubs or mocks. However there is really no difference here between dependency injection and service locator: both are very amenable to stubbing. I suspect this observation comes from projects where people don’t make the effort to ensure that their service locator can be easily substituted. This is where continual testing helps, if you can’t easily stub services for testing, then this implies a serious problem with your design.</p></blockquote><p>人们倾向于使用 Dependency Injection 模式的一个常见理由是:它简化了测试工作。这里的关键是:出于测试的需要,你必须能够轻松地在“真实的服务实现”与“供测试用的‘伪’组件” 之间切换。但是,如果单从这个角度来考虑,Dependency Injection 模式和 Service Locator 模式其实并没有太大区别:两者都能够很好地支持“伪”组件的插入。之所以很多人有 “Dependency Injection 模式更利于测试”的印象,我猜是因为他们并没有努力保证服务定位器的可替换性。这正是持续测试起作用的地方:如果你不能轻松地用一些“伪”组件将一个服务架起来以便测试,这就意味着你的设计出现了严重的问题。</p><blockquote><p>Of course the testing problem is exacerbated by component environments that are very intrusive, such as Java’s EJB framework. My view is that these kinds of frameworks should minimize their impact upon application code, and particularly should not do things that slow down the edit-execute cycle. Using plugins to substitute heavyweight components does a lot to help this process, which is vital for practices such as Test Driven Development.</p></blockquote><p>当然,如果组件环境具有非常强的侵略性(就像 EJB 框架那样),测试的问题会更加严重。我的观点是:应该尽量减少这类框架对应用程序代码的影响,特别是不要做任何可能使“编辑-执行” 的循环变慢的事情。用插件(plugin)机制取代重量级组件会对测试过程有很大帮助,这正是测试驱动开发(Test Driven Development,TDD)之类实践的关键所在。</p><blockquote><p>So the primary issue is for people who are writing code that expects to be used in applications outside of the control of the writer. In these cases even a minimal assumption about a Service Locator is a problem.</p></blockquote><p>所以,主要的问题在于:代码的作者是否希望自己编写的组件能够脱离自己的控制、被使用在另一个应用程序中。如果答案是肯定的,那么他就不能对服务定位器做任何假设——哪怕最小的假设也会给使用者带来麻烦。</p><h4 id="构造子注入-vs-设值方法注入"><a href="#构造子注入-vs-设值方法注入" class="headerlink" title="构造子注入 vs. 设值方法注入"></a>构造子注入 vs. 设值方法注入</h4><blockquote><p>For service combination, you always have to have some convention in order to wire things together. The advantage of injection is primarily that it requires very simple conventions - at least for the constructor and setter injections. You don’t have to do anything odd in your component and it’s fairly straightforward for an injector to get everything configured.</p></blockquote><p>在组合服务时,你总得遵循一定的约定,才可能将所有东西拼装起来。依赖注入的优点主要在于:它只需要非常简单的约定——至少对于构造子注入和设值方法注入来说是这样。相比于这两者,接口注入的侵略性要强得多,比起 Service Locator 模式的优势也不那么明显。</p><blockquote><p>Interface injection is more invasive since you have to write a lot of interfaces to get things all sorted out. For a small set of interfaces required by the container, such as in Avalon’s approach, this isn’t too bad. But it’s a lot of work for assembling components and dependencies, which is why the current crop of lightweight containers go with setter and constructor injection.</p></blockquote><p>所以,如果你想要提供一个组件给多个使用者,构造子注入和设值方法注入看起来很有吸引力:你不必在组件中加入什么希奇古怪的东西,注入器可以相当轻松地把所有东西配置起来。</p><blockquote><p>The choice between setter and constructor injection is interesting as it mirrors a more general issue with object-oriented programming - should you fill fields in a constructor or with setters.</p></blockquote><p>设值函数注入和构造子注入之间的选择相当有趣,因为它折射出面向对象编程的一些更普遍的问题:应该在哪里填充对象的字段,构造子还是设值方法?</p><blockquote><p>My long running default with objects is as much as possible, to create valid objects at construction time. This advice goes back to Kent Beck’s Smalltalk Best Practice Patterns: Constructor Method and Constructor Parameter Method. Constructors with parameters give you a clear statement of what it means to create a valid object in an obvious place. If there’s more than one way to do it, create multiple constructors that show the different combinations.</p></blockquote><p>一直以来,我首选的做法是尽量在构造阶段就创建完整、合法的对象——也就是说,在构造子中填充对象字段。这样做的好处可以追溯到 Kent Beck 在 Smalltalk Best Practice Patterns 一书中介绍的两个模式:Constructor Method 和 Constructor Parameter Method。带有参数的构造子可以明确地告诉你如何创建一个合法的对象。如果创建合法对象的方式不止一种,你还可以提供多个构造子,以说明不同的组合方式。</p><blockquote><p>Another advantage with constructor initialization is that it allows you to clearly hide any fields that are immutable by simply not providing a setter. I think this is important - if something shouldn’t change then the lack of a setter communicates this very well. If you use setters for initialization, then this can become a pain. (Indeed in these situations I prefer to avoid the usual setting convention, I’d prefer a method like initFoo, to stress that it’s something you should only do at birth.)</p></blockquote><p>构造子初始化的另一个好处是:你可以隐藏任何不可变的字段——只要不为它提供设值方法就行了。我认为这很重要:如果某个字段是不应该被改变的,“没有针对该字段的设值方法”就很清楚地说明了这一点。如果你通过设值方法完成初始化,暴露出来的设值方法很可能成为你心头永远的痛。(实际上,在这种时候我更愿意回避通常的设值方法约定,而是使用诸如 initFoo 之类的方法名,以表明该方法只应该在对象创建之初调用。)</p><blockquote><p>But with any situation there are exceptions. If you have a lot of constructor parameters things can look messy, particularly in languages without keyword parameters. It’s true that a long constructor is often a sign of an over-busy object that should be split, but there are cases when that’s what you need.</p></blockquote><p>不过,世事总有例外。如果参数太多,构造子会显得凌乱不堪,特别是对于不支持关键字参数的语言更是如此。的确,如果构造子参数列表太长,通常标志着对象太过繁忙,理应将其拆分成几个对象,但有些时候也确实需要那么多的参数。</p><blockquote><p>If you have multiple ways to construct a valid object, it can be hard to show this through constructors, since constructors can only vary on the number and type of parameters. This is when Factory Methods come into play, these can use a combination of private constructors and setters to implement their work. The problem with classic Factory Methods for components assembly is that they are usually seen as static methods, and you can’t have those on interfaces. You can make a factory class, but then that just becomes another service instance. A factory service is often a good tactic, but you still have to instantiate the factory using one of the techniques here.</p></blockquote><p>如果有不止一种的方式可以构造一个合法的对象,也很难通过构造子描述这一信息,因为构造子之间只能通过参数的个数和类型加以区分。这就是 Factory Method 模式适用的场合了,工厂方法可以借助多个私有构造子和设值方法的组合来完成自己的任务。经典 Factory Method 模式的问题在于:它们往往以静态方法的形式出现,你无法在接口中声明它们。你可以创建一个工厂类,但那又变成另一个服务实体了。“工厂服务”是一种不错的技巧,但你仍然需要以某种方式实例化这个工厂对象,问题仍然没有解决。</p><blockquote><p>Constructors also suffer if you have simple parameters such as strings. With setter injection you can give each setter a name to indicate what the string is supposed to do. With constructors you are just relying on the position, which is harder to follow.</p></blockquote><p>如果要传入的参数是像字符串这样的简单类型,构造子注入也会带来一些麻烦。使用设值方法注入时,你可以在每个设值方法的名字中说明参数的用途;而使用构造子注入时,你只能靠参数的位置来决定每个参数的作用,而记住参数的正确位置显然要困难得多。</p><blockquote><p>If you have multiple constructors and inheritance, then things can get particularly awkward. In order to initialize everything you have to provide constructors to forward to each superclass constructor, while also adding you own arguments. This can lead to an even bigger explosion of constructors.</p></blockquote><p>如果对象有多个构造子,对象之间又存在继承关系,事情就会变得特别讨厌。为了让所有东西都正确地初始化,你必须将对子类构造子的调用转发给超类的构造子,然后处理自己的参数。这可能造成构造子规模的进一步膨胀。</p><blockquote><p>Despite the disadvantages my preference is to start with constructor injection, but be ready to switch to setter injection as soon as the problems I’ve outlined above start to become a problem.</p></blockquote><p>尽管有这些缺陷,但我仍然建议你首先考虑构造子注入。不过,一旦前面提到的问题真的成了问题,你就应该准备转为使用设值方法注入。</p><blockquote><p>This issue has led to a lot of debate between the various teams who provide dependency injectors as part of their frameworks. However it seems that most people who build these frameworks have realized that it’s important to support both mechanisms, even if there’s a preference for one of them.</p></blockquote><p>在将 Dependecy Injection 模式作为框架的核心部分的几支团队之间,“构造子注入还是设值方法注入”引发了很多的争论。不过,现在看来,开发这些框架的大多数人都已经意识到:不管更喜欢哪种注入机制,同时为两者提供支持都是有必要的。</p><h4 id="代码配置-vs-配置文件"><a href="#代码配置-vs-配置文件" class="headerlink" title="代码配置 vs. 配置文件"></a>代码配置 vs. 配置文件</h4><blockquote><p>A separate but often conflated issue is whether to use configuration files or code on an API to wire up services. For most applications that are likely to be deployed in many places, a separate configuration file usually makes most sense. Almost all the time this will be an XML file, and this makes sense. However there are cases where it’s easier to use program code to do the assembly. One case is where you have a simple application that’s not got a lot of deployment variation. In this case a bit of code can be clearer than a separate XML file.</p></blockquote><p>另一个问题相对独立,但也经常与其他问题牵涉在一起:如何配置服务的组装,通过配置文件还是直接编码组装?对于大多数需要在多处部署的应用程序来说,一个单独的配置文件会更合适。配置文件几乎都是 XML 文件,XML 也的确很适合这一用途。不过,有些时候直接在程序代码中实现装配会更简单。譬如一个简单的应用程序,也没有很多部署上的变化,这时用几句代码来配置就比 XML 文件要清晰得多。</p><blockquote><p>A contrasting case is where the assembly is quite complex, involving conditional steps. Once you start getting close to programming language then XML starts breaking down and it’s better to use a real language that has all the syntax to write a clear program. You then write a builder class that does the assembly. If you have distinct builder scenarios you can provide several builder classes and use a simple configuration file to select between them.</p></blockquote><p>与之相对的,有时应用程序的组装非常复杂,涉及大量的条件步骤。一旦编程语言中的配置逻辑开始变得复杂,你就应该用一种合适的语言来描述配置信息,使程序逻辑变得更清晰。然后,你可以编写一个构造器(builder)类来完成装配工作。如果使用构造器的情景不止一种,你可以提供多个构造器类,然后通过一个简单的配置文件在它们之间选择。</p><blockquote><p>I often think that people are over-eager to define configuration files. Often a programming language makes a straightforward and powerful configuration mechanism. Modern languages can easily compile small assemblers that can be used to assemble plugins for larger systems. If compilation is a pain, then there are scripting languages that can work well also.</p></blockquote><p>我常常发现,人们太急于定义配置文件。编程语言通常会提供简捷而强大的配置管理机制,现代编程语言也可以将程序编译成小的模块,并将其插入大型系统中。如果编译过程会很费力,脚本语言也可以在这方面提供帮助。</p><blockquote><p>It’s often said that configuration files shouldn’t use a programing language because they need to be edited by non-programmers. But how often is this the case? Do people really expect non-programmers to alter the transaction isolation levels of a complex server-side application? Non-language configuration files work well only to the extent they are simple. If they become complex then it’s time to think about using a proper programming language.</p></blockquote><p>通常认为,配置文件不应该用编程语言来编写,因为它们需要能够被不懂编程的系统管理人员编辑。但是,这种情况出现的几率有多大呢?我们真的希望不懂编程的系统管理人员来改变一个复杂的服务器端应用程序的事务隔离等级吗?只有在非常简单的时候,非编程语言的配置文件才有最好的效果。如果配置信息开始变得复杂,就应该考虑选择一种合适的编程语言来编写配置文件。</p><blockquote><p>One thing we’re seeing in the Java world at the moment is a cacophony of configuration files, where every component has its own configuration files which are different to everyone else’s. If you use a dozen of these components, you can easily end up with a dozen configuration files to keep in sync.</p></blockquote><p>在 Java 世界里,我们听到了来自配置文件的不和谐音——每个组件都有它自己的配置文件,而且格式还各各不同。如果你要使用一打这样的组件,你就得维护一打的配置文件,那会很快让你烦死。</p><blockquote><p>My advice here is to always provide a way to do all configuration easily with a programmatic interface, and then treat a separate configuration file as an optional feature. You can easily build configuration file handling to use the programmatic interface. If you are writing a component you then leave it up to your user whether to use the programmatic interface, your configuration file format, or to write their own custom configuration file format and tie it into the programmatic interface</p></blockquote><p>在这里,我的建议是:始终提供一种标准的配置方式,使程序员能够通过同一个编程接口轻松地完成配置工作。至于其他的配置文件,仅仅把它们当作一种可选的功能。借助这个编程接口,开发者可以轻松地管理配置文件。如果你编写了一个组件,则可以由组件的使用者来选择如何管理配置信息:使用你的编程接口、直接操作配置文件格式,或者定义他们自己的配置文件格式,并将其与你的编程接口相结合。</p><h4 id="分离配置与使用"><a href="#分离配置与使用" class="headerlink" title="分离配置与使用"></a>分离配置与使用</h4><blockquote><p>The important issue in all of this is to ensure that the configuration of services is separated from their use. Indeed this is a fundamental design principle that sits with the separation of interfaces from implementation. It’s something we see within an object-oriented program when conditional logic decides which class to instantiate, and then future evaluations of that conditional are done through polymorphism rather than through duplicated conditional code.</p></blockquote><p>所有这一切的关键在于:服务的配置应该与使用分开。实际上,这是一个基本的设计原则——分离接口与实现。在面向对象程序里,我们在一个地方用条件逻辑来决定具体实例化哪一个类,以后的条件分支都由多态来实现,而不是继续重复前面的条件逻辑,这就是“分离接口与实现”的原则。</p><blockquote><p>If this separation is useful within a single code base, it’s especially vital when you’re using foreign elements such as components and services. The first question is whether you wish to defer the choice of implementation class to particular deployments. If so you need to use some implementation of plugin. Once you are using plugins then it’s essential that the assembly of the plugins is done separately from the rest of the application so that you can substitute different configurations easily for different deployments. How you achieve this is secondary. This configuration mechanism can either configure a service locator, or use injection to configure objects directly.</p></blockquote><p>如果对于一段代码而言,接口与实现的分离还只是“有用”的话,那么当你需要使用外部元素(例如组件和服务)时,它就是生死攸关的大事。这里的第一个问题是:你是否希望将“选择具体实现类”的决策推迟到部署阶段。如果是,那么你需要使用插入技术。使用了插入技术之后,插件的装配原则上是与应用程序的其余部分分开的,这样你就可以轻松地针对不同的部署替换不同的配置。这种配置机制可以通过服务定位器来实现(Service Locator 模式),也可以借助依赖注入直接完成(Dependency Injection 模式)。</p><h3 id="更多的问题-Some-further-issues"><a href="#更多的问题-Some-further-issues" class="headerlink" title="更多的问题 Some further issues"></a>更多的问题 Some further issues</h3><blockquote><p>In this article, I’ve concentrated on the basic issues of service configuration using Dependency Injection and Service Locator. There are some more topics that play into this which also deserve attention, but I haven’t had time yet to dig into. In particular there is the issue of life-cycle behavior. Some components have distinct life-cycle events: stop and starts for instance. Another issue is the growing interest in using aspect oriented ideas with these containers. Although I haven’t considered this material in the article at the moment, I do hope to write more about this either by extending this article or by writing another.</p></blockquote><p>在本文中,我关注的焦点是使用 Dependency Injection 模式和 Service Locator 模式进行服务配置的基本问题。还有一些与之相关的话题值得关注,但我已经没有时间继续申发下去了。特别值得注意的是生命周期行为的问题:某些组件具有特定的生命周期事件,例如“停止”、“开始”等等。另一个值得注意的问题是:越来越多的人对“如何在这些容器中运用面向方面(aspect oriented)的思想”产生了兴趣。尽管目前还没有认真准备过这方面的材料,但我也很希望以后能在这个话题上写一些东西。</p><blockquote><p>You can find out a lot more about these ideas by looking at the web sites devoted to the lightweight containers. Surfing from the picocontainer and spring web sites will lead to you into much more discussion of these issues and a start on some of the further issues.</p></blockquote><p>关于这些问题,你在专注于轻量级容器的网站上可以找到很多资料。浏览 PicoContainer(<a href="http://www.picocontainer.org)或者spring(http//www.springframework.org%EF%BC%89%E7%9A%84%E7%BD%91%E7%AB%99%EF%BC%8C%E4%BD%A0%E5%8F%AF%E4%BB%A5%E6%89%BE%E5%88%B0%E5%A4%A7%E9%87%8F%E7%9B%B8%E5%85%B3%E7%9A%84%E8%AE%A8%E8%AE%BA%EF%BC%8C%E5%B9%B6%E7%94%B1%E6%AD%A4%E5%BC%95%E7%94%B3%E5%87%BA%E6%9B%B4%E5%A4%9A%E7%9A%84%E8%AF%9D%E9%A2%98%E3%80%82">http://www.picocontainer.org)或者Spring(http://www.springframework.org)的网站,你可以找到大量相关的讨论,并由此引申出更多的话题。</a></p><h3 id="结论和思考-Concluding-Thoughts"><a href="#结论和思考-Concluding-Thoughts" class="headerlink" title="结论和思考 Concluding Thoughts"></a>结论和思考 Concluding Thoughts</h3><blockquote><p>The current rush of lightweight containers all have a common underlying pattern to how they do service assembly - the dependency injector pattern. Dependency Injection is a useful alternative to Service Locator. When building application classes the two are roughly equivalent, but I think Service Locator has a slight edge due to its more straightforward behavior. However if you are building classes to be used in multiple applications then Dependency Injection is a better choice.</p></blockquote><p>在时下流行的轻量级容器都使用了一个共同的模式来组装应用程序所需的服务,我把这个模式称为 Dependency Injection,它可以有效地替代 Service Locator 模式。在开发应用程序时,两者不相上下,但我认为 Service Locator 模式略有优势,因为它的行为方式更为直观。但是,如果你开发的组件要交给多个应用程序去使用,那么 Dependency Injection 模式会是更好的选择。</p><blockquote><p>If you use Dependency Injection there are a number of styles to choose between. I would suggest you follow constructor injection unless you run into one of the specific problems with that approach, in which case switch to setter injection. If you are choosing to build or obtain a container, look for one that supports both constructor and setter injection.</p></blockquote><p>如果你决定使用 Dependency Injection 模式,这里还有几种不同的风格可供选择。我建议你首先考虑构造子注入;如果遇到了某些特定的问题,再改用设值方法注入。如果你要选择一个容器,在其之上进行开发,我建议你选择同时支持这两种注入方式的容器。</p><blockquote><p>The choice between Service Locator and Dependency Injection is less important than the principle of separating service configuration from the use of services within an application.</p></blockquote><p>Service Locator 模式和 Dependency Injection 模式之间的选择并是最重要的,更重要的是:应该将服务的配置和应用程序内部对服务的使用分离开。</p><h3 id="致谢-Acknowledgments"><a href="#致谢-Acknowledgments" class="headerlink" title="致谢 Acknowledgments"></a>致谢 Acknowledgments</h3><blockquote><p>My sincere thanks to the many people who’ve helped me with this article. Rod Johnson, Paul Hammant, Joe Walnes, Aslak Hellesøy, Jon Tirsén and Bill Caputo helped me get to grips with these concepts and commented on the early drafts of this article. Berin Loritsch and Hamilton Verissimo de Oliveira provided some very helpful advice on how Avalon fits in. Dave W Smith persisted in asking questions about my initial interface injection configuration code and thus made me confront the fact that it was stupid. Gerry Lowry sent me lots of typo fixes - enough to cross the thanks threshold.</p></blockquote><p>在此,我要向帮助我理解本文中所提到的问题、并对本文提出宝贵意见的几个人表示感谢,他们是 Rod Johnson、Paul Hammant、Joe Walnes、Aslak Hellesoy、Jon Tirsen 和 Bill Caputo。另外,Berin Loritsch 和 Hamilton Verissimo de Oliveira 在 Avalon 方面给了我非常有用的建议,一并向他们表示感谢。</p></body></html>]]></content>
<summary type="html"><p><a href="https://www.behance.net/gallery/132504335/They-Never-Told-Us-That-This-Was-Forever-1-14-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR5/header.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>北京中鼎项目用到了 .NET 的依赖注入框架,借此机会了解了控制反转等设计理念,追溯到 <a href="https://martinfowler.com/">Martin Fowler</a> 的这篇 <a href="https://martinfowler.com/articles/injection.html">Inversion of Control Containers and the Dependency Injection pattern</a>,特作此博文研读。</p></summary>
<category term="CAD二次开发 (GStarCAD ObjectARX Dev)" scheme="https://yousazoe.top/categories/CAD%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91-GStarCAD-ObjectARX-Dev/"/>
<category term="C#" scheme="https://yousazoe.top/tags/C/"/>
<category term="IOC" scheme="https://yousazoe.top/tags/IOC/"/>
<category term="DI" scheme="https://yousazoe.top/tags/DI/"/>
</entry>
<entry>
<title>League Director Tutorial -- Install and Run</title>
<link href="https://yousazoe.top/archives/88c03ec3.html"/>
<id>https://yousazoe.top/archives/88c03ec3.html</id>
<published>2023-12-26T16:03:15.000Z</published>
<updated>2025-01-03T06:07:21.706Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://github.com/RiotGames/leaguedirector"><img data-src="https://img.yousazoe.top/LeagueDirector/leaguedirector.png"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>League Director is a tool for staging and recording videos from League of Legends replays.</p><span id="more"></span><h3 id="Install"><a href="#Install" class="headerlink" title="Install"></a>Install</h3><h4 id="Github-下载"><a href="#Github-下载" class="headerlink" title="Github 下载"></a>Github 下载</h4><p>最正统的官方下载渠道,下载链接:<a href="https://github.com/RiotGames/leaguedirector/releases/tag/v0.1.4">https://github.com/RiotGames/leaguedirector/releases/tag/v0.1.4</a>。</p><h4 id="服务器下载"><a href="#服务器下载" class="headerlink" title="服务器下载"></a>服务器下载</h4><p>我自己的云存储服务器上也做了一个备份方便大家下载,下载链接:</p><ul><li><a href="https://img.yousazoe.top/LeagueDirector/League%20Director.exe">联盟导演</a></li><li><a href="https://img.yousazoe.top/LeagueDirector/League%20Director-CHS%20Setup.exe">联盟导演-汉化版</a></li></ul><p>汉化版的版本比较低,但原版在国服会有一些莫名奇妙的问题,所以还是推荐下载第二个链接。</p><h3 id="Feature"><a href="#Feature" class="headerlink" title="Feature"></a>Feature</h3><blockquote><ul><li>Control replay playback and speed</li><li>First person camera controls</li><li>Attach camera to champion or minion</li><li>Toggle interface elements including HUD, health bars and notifications</li><li>Graphical Options<ul><li>Field of view<br>Near and far clipping<br>Custom skyboxes<br>Shadow direction<br>Depth and height fog<br>Depth of field</li></ul></li><li>Sequencer<ul><li>Record and playback keyframed camera position + graphical options</li><li>Timeline for viewing and editing keyframe values</li><li>Undo / Redo</li><li>Save and load pre saved sequences</li><li>Adjustable keyframe blending</li></ul></li><li>Video capture in webm or png format</li><li>Customizable key bindings</li></ul></blockquote><h3 id="How-To-Use"><a href="#How-To-Use" class="headerlink" title="How To Use"></a>How To Use</h3><blockquote><p><strong>Note: Windows Only</strong></p><ol><li><a href="https://github.com/riotgames/leaguedirector/releases/latest">Download League Director</a> from the releases page and install.</li><li>Start League Director and make sure the checkbox next to your install is checked.</li><li>Start League of Legends and launch a replay. League Director will automatically connect.</li><li>Open the options menu (ESC key) in game and ensure your Video Graphics settings are set to Very High. If you did need to change your Video Graphics settings, you’ll need to restart the replay to enable the additional rendering features like the skybox.</li><li>Select FPS Camera from the Camera Modes drop down in game.</li><li>Using the numpad keys (4, 5, 6, 8) and the mouse you can free camera move around. Key bindings for free camera can also be changed inside the game options.</li></ol></blockquote><p>安装一下 <code>SetUp.exe</code>,一路点确定即可。打开程序首先会弹出游戏的安装路径确认窗口:</p><p>选择好之后启动游戏并打开录像,就会有如下界面:</p></body></html>]]></content>
<summary type="html"><p><a href="https://github.com/RiotGames/leaguedirector"><img data-src="https://img.yousazoe.top/LeagueDirector/leaguedirector.png"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>League Director is a tool for staging and recording videos from League of Legends replays.</p></summary>
<category term="联盟导演教程 (League Director Tutorial)" scheme="https://yousazoe.top/categories/%E8%81%94%E7%9B%9F%E5%AF%BC%E6%BC%94%E6%95%99%E7%A8%8B-League-Director-Tutorial/"/>
<category term="League Director" scheme="https://yousazoe.top/tags/League-Director/"/>
</entry>
<entry>
<title>东方锅炉图纸清洗项目总结</title>
<link href="https://yousazoe.top/archives/40229a0d.html"/>
<id>https://yousazoe.top/archives/40229a0d.html</id>
<published>2023-11-05T17:34:50.000Z</published>
<updated>2025-01-03T06:07:21.703Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://www.behance.net/gallery/132504335/They-Never-Told-Us-That-This-Was-Forever-1-14-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR4/header.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>去东方电气集团旗下的东方锅炉做图纸清洗驻场开发,抽出时间总结一下。</p><span id="more"></span><h3 id="达梦数据库调用"><a href="#达梦数据库调用" class="headerlink" title="达梦数据库调用"></a>达梦数据库调用</h3><h4 id="数据库连接"><a href="#数据库连接" class="headerlink" title="数据库连接"></a>数据库连接</h4><p>与 MySQL 类似的调用方法,但是似乎 Nuget 上面的包有些问题,我是直接调用本地的 <code>DmProvider.dll</code> 添加引用:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> connection = <span class="keyword">new</span> DmConnection();</span><br><span class="line">connection.ConnectionString = <span class="string">$"Server=<span class="subst">{server}</span>; Port=<span class="subst">{port}</span>; User Id=<span class="subst">{user}</span>; PWD=<span class="subst">{password}</span>"</span>;</span><br><span class="line">connection.Open();</span><br></pre></td></tr></tbody></table></figure><h4 id="SQL语句调用"><a href="#SQL语句调用" class="headerlink" title="SQL语句调用"></a>SQL语句调用</h4><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> (<span class="keyword">var</span> command = <span class="keyword">new</span> DmCommand(<span class="string">"sql语句"</span>, connection))</span><br><span class="line"><span class="keyword">using</span> (<span class="keyword">var</span> reader = command.ExecuteReader())</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">while</span> (reader.Read())</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">var</span> ret = reader.GetString(<span class="number">0</span>);</span><br><span class="line"> ......</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="S3DownLoader"><a href="#S3DownLoader" class="headerlink" title="S3DownLoader"></a>S3DownLoader</h3><p>与数据对接的图纸对象存储容器是 <code>document-bucket</code>,我们可以通过 <code>s3browser-cli.exe</code> 将图纸下载下来。</p><p>首先通过s3文件对象模型 <code>S3FileModel</code> 将下载图纸所需信息导入,包括文件的 uid、名称、哈希路径等等:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> s3文件对象模型</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">S3FileModel</span></span><br><span class="line">{</span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> 文件在服务器的唯一id</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">string</span> FileUid { <span class="keyword">get</span>; <span class="keyword">set</span>; }</span><br><span class="line"> </span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> 文件名称</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">string</span> FileName { <span class="keyword">get</span>; <span class="keyword">set</span>; }</span><br><span class="line"> </span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> 服务器的源哈希路径</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">string</span> BucketHashPath { <span class="keyword">get</span>; <span class="keyword">set</span>; }</span><br><span class="line"> </span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> 下载至本地的目标路径</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">string</span> TargetPath { <span class="keyword">get</span>; <span class="keyword">set</span>; }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>查阅资料得知,在已经登录s3客户端的情况下直接调用方式如下:</p><figure class="highlight shell"><table><tbody><tr><td class="code"><pre><span class="line">s3browser-cli.exe /file download {账户} {服务器文件路径} {下载到本地的路径}</span><br></pre></td></tr></tbody></table></figure><p>调用这个进程即可:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> (Process process = <span class="keyword">new</span> Process())</span><br><span class="line">{</span><br><span class="line"> FileModel.BucketHashPath = Path.Combine(<span class="string">"document-bucket/root/STORE"</span>, FileModel.BucketHashPath).Replace(<span class="string">"\\"</span>, <span class="string">"/"</span>);</span><br><span class="line"> FileModel.TargetPath = Path.Combine(_downloadPath, GetBucketPathDirectories());</span><br><span class="line"></span><br><span class="line"> process.StartInfo = <span class="keyword">new</span> ProcessStartInfo(Path.Combine(_installPath, <span class="string">"s3browser-cli.exe"</span>),</span><br><span class="line"> <span class="string">$"/file download <span class="subst">{_account}</span> <span class="subst">{FileModel.BucketHashPath}</span> <span class="subst">{Path.Combine(_downloadPath, <span class="string">"document-bucket"</span>)}</span>"</span>)</span><br><span class="line"> {</span><br><span class="line"> CreateNoWindow = <span class="literal">true</span></span><br><span class="line"> };</span><br><span class="line"> process.Start();</span><br><span class="line"> process.WaitForExit();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="CAD相关"><a href="#CAD相关" class="headerlink" title="CAD相关"></a>CAD相关</h3><p>由于图纸数量很多,所以需要通过后台打开的方式打开图纸并操作:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> (Database db = <span class="keyword">new</span> Database(<span class="literal">false</span>, <span class="literal">false</span>))</span><br><span class="line">{</span><br><span class="line"> db.ReadDwgFile(PaperPath, FileOpenMode.OpenForReadAndWriteNoShare, <span class="literal">true</span>, <span class="literal">null</span>, <span class="literal">true</span>);</span><br><span class="line"> ......</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>而后台打开不能使用选择集,只能通过遍历的方式查找标题栏(<code>dbcplm_btl</code>)和明细表(<code>DBCXH</code>)。</p><h4 id="更改标题栏"><a href="#更改标题栏" class="headerlink" title="更改标题栏"></a>更改标题栏</h4><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">UpdateTitleData</span>(<span class="params">Dictionary<<span class="built_in">string</span>, <span class="built_in">string</span>> titleMap, Database db</span>)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">using</span> (<span class="keyword">var</span> trans = db.TransactionManager.StartTransaction())</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">var</span> blockTable = db.BlockTableId.GetObject<BlockTable>();</span><br><span class="line"> <span class="keyword">var</span> modelSpace = blockTable[BlockTableRecord.ModelSpace].GetObject<BlockTableRecord>();</span><br><span class="line"> <span class="keyword">foreach</span> (<span class="keyword">var</span> id <span class="keyword">in</span> modelSpace)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">var</span> titleBlock = id.GetObject<BlockReference>();</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">string</span>.Compare(titleBlock.Name, DGTitleBlockNameString, StringComparison.OrdinalIgnoreCase) != <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">foreach</span> (ObjectId attId <span class="keyword">in</span> titleBlock.AttributeCollection)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">var</span> attRef = attId.GetObject<AttributeReference>();</span><br><span class="line"> <span class="keyword">if</span> (attRef == <span class="literal">null</span> || !titleMap.TryGetValue(attRef.Tag, <span class="keyword">out</span> <span class="keyword">var</span> <span class="keyword">value</span>))</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> </span><br><span class="line"> ......</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="更改明细表"><a href="#更改明细表" class="headerlink" title="更改明细表"></a>更改明细表</h4><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">UpdateBomData</span>(<span class="params">List<Dictionary<<span class="built_in">string</span>, <span class="built_in">string</span>>> bomData, Database db</span>)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">using</span> (<span class="keyword">var</span> trans = db.TransactionManager.StartTransaction())</span><br><span class="line"> {</span><br><span class="line"> Dictionary<<span class="built_in">string</span>, ObjectId> idMap = <span class="keyword">new</span> Dictionary<<span class="built_in">string</span>, ObjectId>();</span><br><span class="line"> <span class="keyword">var</span> blockTable = db.BlockTableId.GetObject<BlockTable>();</span><br><span class="line"> <span class="keyword">var</span> modelSpace = blockTable[BlockTableRecord.ModelSpace].GetObject<BlockTableRecord>();</span><br><span class="line"> <span class="keyword">foreach</span> (<span class="keyword">var</span> id <span class="keyword">in</span> modelSpace)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">var</span> bomBlock = id.GetObject<BlockReference>();</span><br><span class="line"></span><br><span class="line"> <span class="built_in">bool</span> isBomBlock = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">foreach</span> (<span class="keyword">var</span> name <span class="keyword">in</span> _bomBlockNames)</span><br><span class="line"> isBomBlock &= <span class="built_in">string</span>.Compare(bomBlock.Name, name, StringComparison.OrdinalIgnoreCase) != <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (isBomBlock) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">foreach</span> (ObjectId attId <span class="keyword">in</span> bomBlock.AttributeCollection)</span><br><span class="line"> {</span><br><span class="line"> AttributeReference attRef = attId.GetObject<AttributeReference>();</span><br><span class="line"> <span class="keyword">if</span> (String.Compare(attRef.Tag, DGBomXuHaoNameString, StringComparison.OrdinalIgnoreCase) != <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> idMap[attRef.TextString] = id;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">foreach</span> (<span class="keyword">var</span> bomRowData <span class="keyword">in</span> bomData)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (!idMap.ContainsKey(bomRowData[DGBomXuHaoNameString]))</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> bomRowBlock = trans.GetObject(idMap[bomRowData[DGBomXuHaoNameString]], OpenMode.ForRead) <span class="keyword">as</span> BlockReference;</span><br><span class="line"> <span class="keyword">foreach</span> (ObjectId attId <span class="keyword">in</span> bomRowBlock.AttributeCollection)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">var</span> attRef = attId.GetObject<AttributeReference>();</span><br><span class="line"> <span class="keyword">if</span> (!bomRowData.TryGetValue(attRef.Tag, <span class="keyword">out</span> <span class="keyword">var</span> <span class="keyword">value</span>))</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> attRef.TextString = <span class="keyword">value</span>;</span><br><span class="line"> ......</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> trans.Commit();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="图纸另存为指定版本"><a href="#图纸另存为指定版本" class="headerlink" title="图纸另存为指定版本"></a>图纸另存为指定版本</h4><p>由于客户大部分图纸都是 AutoCAD 2010 绘制的,默认的保存会变为 2018 版本,而官方 API 没有找到直接指定版本的保存,所以只能曲线救国,通过另存为的方式指定版本:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line">db.SaveAs(<span class="string">$"<span class="subst">{Path.Combine(Path.GetDirectoryName(db.Filename), Path.GetFileNameWithoutExtension(db.Filename))}</span>-temp.dwg"</span>, DwgVersion.AC1024);</span><br></pre></td></tr></tbody></table></figure><p>这里的 <code>DwgVersion.AC1024</code> 引出了一个之前不知道的知识点,可以通过记事本直接打开 <code>.dwg</code> 文件查看图纸的版本,找到文件的首行,通常会包含类似于“AC****”的字样,这一AC代码即为文件格式版本的标识。通过对应AC代码,就可以获取到 <code>.dwg</code> 文件的版本信息:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">* AC1015:CAD2000版</span><br><span class="line">* AC1018:CAD2004版</span><br><span class="line">* AC1021:CAD2007版</span><br><span class="line">* AC1024:CAD2010版</span><br><span class="line">* AC1027:CAD2013版</span><br><span class="line">* AC1032:CAD2018版</span><br></pre></td></tr></tbody></table></figure></body></html>]]></content>
<summary type="html"><p><a href="https://www.behance.net/gallery/132504335/They-Never-Told-Us-That-This-Was-Forever-1-14-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR4/header.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>去东方电气集团旗下的东方锅炉做图纸清洗驻场开发,抽出时间总结一下。</p></summary>
<category term="CAD二次开发 (GStarCAD ObjectARX Dev)" scheme="https://yousazoe.top/categories/CAD%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91-GStarCAD-ObjectARX-Dev/"/>
<category term="C#" scheme="https://yousazoe.top/tags/C/"/>
</entry>
<entry>
<title>奇瑞万达端子线号查询定制项目总结</title>
<link href="https://yousazoe.top/archives/71415cfa.html"/>
<id>https://yousazoe.top/archives/71415cfa.html</id>
<published>2023-11-05T17:33:40.000Z</published>
<updated>2025-01-03T06:07:21.703Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://www.behance.net/gallery/116635445/Becoming-Someone-New-1-5-by-James-Lipnickas-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR3/header.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>奇瑞万达需要实现快速选择、对比、修改端子功能端子查询功能,做一下项目的总结。</p><span id="more"></span><h3 id="为实体添加扩展数据"><a href="#为实体添加扩展数据" class="headerlink" title="为实体添加扩展数据"></a>为实体添加扩展数据</h3><p>删除端子实体时需要一个识别标记,我选择加入扩展数据作为标识符:</p><ol><li>注册一个不会重名的 <code>appName</code></li><li>创建你所需要的 xData 缓冲区链表</li><li>把这个数据放到实体中</li></ol><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">const</span> CString appName = <span class="string">L"SelectXDataApp"</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">acdbRegApp</span>(appName);</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">resbuf</span>* rb = <span class="built_in">acutBuildList</span>(AcDb::kDxfRegAppName, appName,</span><br><span class="line"> AcDb::kDxfXdAsciiString, <span class="built_in">TEXT</span>(<span class="string">"xxxxxxx"</span>),</span><br><span class="line"> RTNONE);</span><br><span class="line"></span><br><span class="line">pPline-><span class="built_in">setXData</span>(rb);</span><br><span class="line"><span class="built_in">acutRelRb</span>(rb);</span><br></pre></td></tr></tbody></table></figure><p><code>acutBuildList()</code> 创建链表时前两个默认为 APP 类型和 APP 名称,后面也是以 <code>type-value</code> 的形式两两创建链表数据。</p><h3 id="模态对话框中实现用户和-CAD-的交互操作"><a href="#模态对话框中实现用户和-CAD-的交互操作" class="headerlink" title="模态对话框中实现用户和 CAD 的交互操作"></a>模态对话框中实现用户和 CAD 的交互操作</h3><h4 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h4><p>现在的业务场景需要在弹出模态对话框之后再返回 CAD 选择实体(也就是端子),再重新显示对话框。</p><p>最开始我的处理办法是将模态对话框改为非模态对话框,但在对话框的生命周期控制上很难把控,问题比较多,所以目光又重新回到如何使用模态对话框处理这类场景。</p><h4 id="文档查阅"><a href="#文档查阅" class="headerlink" title="文档查阅"></a>文档查阅</h4><p><a href="https://blog.csdn.net/a_222850215/article/details/79659155">在模态对话框中实现用户和AutoCAD 的交互操作</a> 这篇文章提到使用 <code>BeginEditorCommand()</code> 这个方法去从模态对话框切换到 CAD 应用程序,下面是 <a href="">官方文档</a> 的描述:</p><blockquote><p>Call this method to indicate an AutoCAd interactive command is starting.</p></blockquote><p>还贴心的给了一个使用示例:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">BeginEditorCommand</span>();</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">DoMyInteractiveCommand</span>())</span><br><span class="line"> <span class="built_in">CompleteEditorCommand</span>();</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> <span class="built_in">CancelEditorCommand</span>();</span><br></pre></td></tr></tbody></table></figure><ul><li><code>BeginEditorCommand</code> 函数用于将控制权(焦点)交给 CAD,一般用于开始一个交<br>互操作</li><li><code>CompleteEditorCommand</code> 函数用于从一个在 CAD 中完成的交互命令返回到应用<br>程序</li><li><code>CancelEditorCommand</code> 函数用于从一个在 CAD 中被取消的交互命令返回到应用程<br>序</li></ul><p>这三个函数组合使用,能够在模态对话框中实现用户和 CAD 的交互操作。</p><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">ads_point point{ <span class="number">0</span> };</span><br><span class="line">ads_name selectUnit { <span class="number">0</span>, <span class="number">0</span> };</span><br><span class="line"></span><br><span class="line"><span class="built_in">BeginEditorCommand</span>();</span><br><span class="line"><span class="keyword">auto</span> res = <span class="built_in">acedEntSel</span>(<span class="string">L"\n请选择需要提取的端子:\n"</span>, selectUnit, point);</span><br><span class="line"><span class="built_in">CompleteEditorCommand</span>();</span><br></pre></td></tr></tbody></table></figure><h3 id="实体高亮显示"><a href="#实体高亮显示" class="headerlink" title="实体高亮显示"></a>实体高亮显示</h3><h4 id="问题描述-1"><a href="#问题描述-1" class="headerlink" title="问题描述"></a>问题描述</h4><p>奇瑞万达方面需要在选择所有端子后所有都高亮显示,并且能够显示出各自的夹点。</p><p>一开始不知道高显这个效果该如何实现,尝试直接调用实体的 <code>highlight()</code> 方法高亮。这种方式没有夹点,在缩放后也没有虚化边框那样明显的视觉效果,客户方面不接受这样的高显只能另寻他法。后面在网上查阅到 <a href="https://www.cnblogs.com/xzh1993/p/5942750.html">ARX亮显问题</a>,里面提到了使用 <code>acedSSSetFirst</code> 这个方法。</p><h4 id="文档查阅-1"><a href="#文档查阅-1" class="headerlink" title="文档查阅"></a>文档查阅</h4><p>由于之前从来没用过这个方法,本着严谨的态度去 <a href="https://help.autodesk.com/view/OARX/2023/ENU/?guid=OARX-RefGuide-acedSSSetFirst_ads_name_ads_name">官方文档</a> 上又查了一下如何使用。</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">acedSSSetFirst</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">const</span> ads_name pset, </span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">const</span> ads_name unused</span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>;</span><br></pre></td></tr></tbody></table></figure><ul><li><code>pset</code>:Set of entities to be added to the pickfirst selection set and on which grips will be displayed</li><li><code>unused</code>:Ignored</li></ul><blockquote><p>This function sets which objects are selected and gripped.</p><p>The parameters have the following data type definition:</p><p><code>typedef long ads_name[2];</code></p><p>The selection set of objects specified by the <code>gset</code> argument are gripped, and the selection set of objects specified by <code>pset</code> are both gripped and selected. If any objects are common to both selection sets, <code>acedSSSetFirst()</code> grips and selects the selection set specified by <code>pset</code> only (it does not grip the <code>gset</code> set).</p><p>If <code>gset</code> is <code>NULL</code> and <code>pset</code> is specified, <code>acedSSSetFirst()</code> grips and selects <code>pset</code>. If <code>gset</code> and <code>pset</code> are <code>NULL</code>, <code>acedSSSetFirst()</code> turns off any existing grips and selections.</p><p>You are responsible for creating a valid selection set. For example, you may need to verify that a background paper space viewport (DXF group code 69) is not included in the selection set. You may also need to ensure that selected objects belong to the current layout.</p><p><strong>Note</strong> The <code>addCommand()</code> optional flags <code>ACRX_CMD_USEPICKSET</code> and <code>ACRX_CMD_REDRAW</code> must be used in order for <code>acedSSSetFirst()</code> to work.</p><p>Do not call <code>acedSSSetFirst()</code> when AutoCAD is in the middle of executing a command.</p></blockquote><h4 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h4><p>照着人家给的示例代码照猫画虎,也算达到效果了:</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">ads_name ssName, ssTemp;</span><br><span class="line"><span class="built_in">acedSSAdd</span>(<span class="literal">NULL</span>, <span class="literal">NULL</span>, ssTemp);</span><br><span class="line"></span><br><span class="line"><span class="built_in">acedSSGet</span>(<span class="literal">NULL</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>, ssName);</span><br><span class="line">Adesk::Int32 len = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">acedSSLength</span>(ssName, &len);</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">auto</span> i = <span class="number">0</span>; i < len; i++)</span><br><span class="line">{</span><br><span class="line"> ads_name ent = {<span class="number">0</span>, <span class="number">0</span>};</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">acedSSName</span>(ssName, i, ent) != RTNORM) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> AcDbObjectId idEnt = AcDbObjectId::kNull;</span><br><span class="line"> <span class="built_in">acdbGetObjectId</span>(idEnt, ent);</span><br><span class="line"> </span><br><span class="line"> AcDbEntity *pEnt = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">acdbOpenAcDbEntity</span>(pEnt, idEnt, AcDb::kForRead) != eOk) <span class="keyword">continue</span>;</span><br><span class="line"> <span class="built_in">acedSSAdd</span>(ent, ssTemp, ssTemp);</span><br><span class="line"></span><br><span class="line"> pEnt-><span class="built_in">close</span>();</span><br><span class="line">}</span><br><span class="line"><span class="built_in">acedSSFree</span>(ssName);</span><br><span class="line"><span class="built_in">acedSSSetFirst</span>(ssTemp, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">acedSSFree</span>(ssTemp);</span><br></pre></td></tr></tbody></table></figure><p>注意启动命令要设置为 <code>ACRX_CMD_REDRAW | ACRX_CMD_USEPICKSET</code>,<code>acedSSSetFirst</code> 可以控制加点或者选择的显示,但要注意注册命令的参数。</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">acedRegCmds-><span class="built_in">addCommand</span>(_T(<span class="string">"xxxxxx"</span>), _T(<span class="string">"xxx"</span>), _T(<span class="string">"xxx"</span>), ACRX_CMD_TRANSPARENT | ACRX_CMD_USEPICKSET | ACRX_CMD_REDRAW, function);</span><br></pre></td></tr></tbody></table></figure><h3 id="基于LRU的缓存算法实现"><a href="#基于LRU的缓存算法实现" class="headerlink" title="基于LRU的缓存算法实现"></a>基于LRU的缓存算法实现</h3><p>奇瑞万达业务上需要下拉框有记忆功能(即下拉框顺序需要以 <strong>最近最少使用</strong> 的原则去缓存),在 LeetCode 中也有类似的题目(<a href="https://leetcode.cn/problems/lru-cache/description/">146. LRU 缓存</a>)。</p><h4 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h4><p>请你设计并实现一个满足 <a href="https://baike.baidu.com/item/LRU">LRU (最近最少使用) 缓存</a> 约束的数据结构。</p><p>实现 <code>LRUCache</code> 类:</p><ul><li><code>LRUCache(int capacity)</code> 以 <strong>正整数</strong> 作为容量 <code>capacity</code> 初始化 LRU 缓存</li><li><code>int get(int key)</code> 如果关键字 <code>key</code> 存在于缓存中,则返回关键字的值,否则返回 <code>-1</code> 。</li><li><code>void put(int key, int value)</code> 如果关键字 <code>key</code> 已经存在,则变更其数据值 <code>value</code> ;如果不存在,则向缓存中插入该组 <code>key-value</code> 。如果插入操作导致关键字数量超过 <code>capacity</code> ,则应该 <strong>逐出</strong> 最久未使用的关键字。</li></ul><p>函数 <code>get</code> 和 <code>put</code> 必须以 <code>O(1)</code> 的平均时间复杂度运行。</p><p><strong>示例:</strong></p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">输入</span><br><span class="line">["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]</span><br><span class="line">[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]</span><br><span class="line">输出</span><br><span class="line">[null, null, null, 1, null, -1, null, -1, 3, 4]</span><br><span class="line"></span><br><span class="line">解释</span><br><span class="line">LRUCache lRUCache = new LRUCache(2);</span><br><span class="line">lRUCache.put(1, 1); // 缓存是 {1=1}</span><br><span class="line">lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}</span><br><span class="line">lRUCache.get(1); // 返回 1</span><br><span class="line">lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}</span><br><span class="line">lRUCache.get(2); // 返回 -1 (未找到)</span><br><span class="line">lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}</span><br><span class="line">lRUCache.get(1); // 返回 -1 (未找到)</span><br><span class="line">lRUCache.get(3); // 返回 3</span><br><span class="line">lRUCache.get(4); // 返回 4</span><br></pre></td></tr></tbody></table></figure><p><strong>提示:</strong></p><ul><li><code>1 <= capacity <= 3000</code></li><li><code>0 <= key <= 10000</code></li><li><code>0 <= value <= 10^5</code></li><li>最多调用 <code>2 * 10^5</code> 次 <code>get</code> 和 <code>put</code></li></ul><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LRUCache</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">LRUCache</span>(<span class="type">int</span> capacity) {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">get</span><span class="params">(<span class="type">int</span> key)</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">put</span><span class="params">(<span class="type">int</span> key, <span class="type">int</span> value)</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Your LRUCache object will be instantiated and called as such:</span></span><br><span class="line"><span class="comment"> * LRUCache* obj = new LRUCache(capacity);</span></span><br><span class="line"><span class="comment"> * int param_1 = obj->get(key);</span></span><br><span class="line"><span class="comment"> * obj->put(key,value);</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></tbody></table></figure><h4 id="解决方案-2"><a href="#解决方案-2" class="headerlink" title="解决方案"></a>解决方案</h4><p><strong>思路</strong>:</p><ol><li>保持把新鲜数据往链表头移动。新鲜的定义:刚被修改(<code>put</code>),或者访问过(<code>get</code>),就算新鲜,就需要 <code>splice</code> 到链表头</li><li>过期键直接 <code>pop_back()</code>,链表节点越往后,越陈旧</li></ol><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LRUCache</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">LRUCache</span>(<span class="type">int</span> capacity) : _capacity(capacity) {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">get</span><span class="params">(<span class="type">int</span> key)</span> </span>{</span><br><span class="line"> <span class="keyword">auto</span> it = _table.<span class="built_in">find</span>(key);</span><br><span class="line"> <span class="keyword">if</span> (it != _table.<span class="built_in">end</span>()) {</span><br><span class="line"> _lru.<span class="built_in">splice</span>(_lru.<span class="built_in">begin</span>(), _lru, it->second);</span><br><span class="line"> <span class="keyword">return</span> it->second->second;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">put</span><span class="params">(<span class="type">int</span> key, <span class="type">int</span> value)</span> </span>{</span><br><span class="line"> <span class="keyword">auto</span> it = _table.<span class="built_in">find</span>(key);</span><br><span class="line"> <span class="keyword">if</span> (it != _table.<span class="built_in">end</span>()) {</span><br><span class="line"> _lru.<span class="built_in">splice</span>(_lru.<span class="built_in">begin</span>(), _lru, it->second);</span><br><span class="line"> it->second->second = value;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> _lru.<span class="built_in">emplace_front</span>(key, value);</span><br><span class="line"> _table[key] = _lru.<span class="built_in">begin</span>();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (_table.<span class="built_in">size</span>() > _capacity) {</span><br><span class="line"> _table.<span class="built_in">erase</span>(_lru.<span class="built_in">back</span>().first);</span><br><span class="line"> _lru.<span class="built_in">pop_back</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> unordered_map<<span class="type">int</span>, std::list<std::pair<<span class="type">int</span>, <span class="type">int</span>>>::iterator> _table;</span><br><span class="line"> std::list<std::pair<<span class="type">int</span>, <span class="type">int</span>>> _lru;</span><br><span class="line"> <span class="type">int</span> _capacity;</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p><strong>代码要领</strong>:</p><ol><li><code>map</code> 中保存的是 <code><key, 链表节点的指针></code>,这样查找的时候就不用需要去遍历链表了,使用 <code>unordered_map</code> 就能很快找到链表节点指针</li><li>判断容量的时候,最好不使用 <code>std::list::size()</code> 方法,在 c++ 里,这个方法可能不是 <code>O(1)</code> 的。</li></ol></body></html>]]></content>
<summary type="html"><p><a href="https://www.behance.net/gallery/116635445/Becoming-Someone-New-1-5-by-James-Lipnickas-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR3/header.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>奇瑞万达需要实现快速选择、对比、修改端子功能端子查询功能,做一下项目的总结。</p></summary>
<category term="CAD二次开发 (GStarCAD ObjectARX Dev)" scheme="https://yousazoe.top/categories/CAD%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91-GStarCAD-ObjectARX-Dev/"/>
<category term="C#" scheme="https://yousazoe.top/tags/C/"/>
</entry>
<entry>
<title>送给入门主播的一份 OBS Studio 直播指南</title>
<link href="https://yousazoe.top/archives/cf9398b.html"/>
<id>https://yousazoe.top/archives/cf9398b.html</id>
<published>2023-10-14T17:08:32.000Z</published>
<updated>2025-01-03T06:07:21.713Z</updated>
<content type="html"><</li><li><a class="btn" href="https://stock.mooncape.net/mc-clock/clock02"> <i class="fa fa-"></i>clock02 </a>: [https://stock.mooncape.net/mc-clock/clock02](https://stock.mooncape.net/mc-clock/clock02)</li><li><a class="btn" href="https://stock.mooncape.net/mc-clock/clock03"> <i class="fa fa-"></i>clock03 </a>: [https://stock.mooncape.net/mc-clock/clock03](https://stock.mooncape.net/mc-clock/clock03)</li><li><a class="btn" href="https://stock.mooncape.net/mc-clock/clock04"> <i class="fa fa-"></i>clock04 </a>: [https://stock.mooncape.net/mc-clock/clock04](https://stock.mooncape.net/mc-clock/clock04)</li><li><a class="btn" href="https://stock.mooncape.net/mc-clock/clock05"> <i class="fa fa-"></i>clock05 </a>: [https://stock.mooncape.net/mc-clock/clock05](https://stock.mooncape.net/mc-clock/clock05)</li></ul><h4 id="英雄联盟直营服账号数据展示-x2F-LoboBot"><a href="#英雄联盟直营服账号数据展示-x2F-LoboBot" class="headerlink" title="英雄联盟直营服账号数据展示/LoboBot"></a>英雄联盟直营服账号数据展示/LoboBot</h4><p><video id="video" width="1500px" height="480px" controls="" preload="none" poster="https://lobobot.com/img/help/add-account-1.png"> <source id="mp4" src="https://lobobot.com/video/lobobot-lolstats-video.webm" type="video/mp4"> </video></p><ul><li>插件地址:<a href="https://lobobot.com/">https://lobobot.com/</a></li><li>官方文档:<a href="https://lobobot.com/help">https://lobobot.com/help</a></li></ul><h5 id="Creating-a-League-of-Legends-box"><a href="#Creating-a-League-of-Legends-box" class="headerlink" title="Creating a League of Legends box"></a>Creating a League of Legends box</h5><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/add-account-1.png"></p><blockquote><ol><li>Go to <code>/lol</code></li><li>Add your League Of Legends account. You must change your profile icon with the one provided by LoboBot.</li></ol></blockquote><ol><li>进入游戏 <code>/lol</code></li><li>把游戏头像换成这个狼人模样以完成 LoboBot 对你账户拥有权的验证</li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/add-account-2.png"></p><blockquote><ol start="3"><li>If all goes well, you will see a message saying that your account has been added successfully. </li><li>Click on the account and you will see a both queues (Ranked Solo and Ranked Flex). Select the queue you want to show.</li></ol></blockquote><ol start="3"><li>如果一切顺利,你会看到一条账户添加成功的信息</li><li>点击账户你会看到 <code>单/双排位</code> 和 <code>灵活组排</code> 的战绩,选择你想展示的战绩</li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/add-account-3.png"></p><blockquote><ol start="5"><li>Click on the desired box, customize it and finally click on the <strong>Create Box</strong> button.</li><li>Use the link to add the box to your stream.</li></ol></blockquote><ol start="5"><li>选择你喜欢的 box,客制化后点击 <code>Create Box</code></li><li>通过链接推流到直播间</li></ol><h5 id="Adding-the-box-in-OBS"><a href="#Adding-the-box-in-OBS" class="headerlink" title="Adding the box in OBS"></a>Adding the box in OBS</h5><blockquote><ol><li>Once you have created the box, you will see a link to add the box to your stream.</li><li>Copy the link and create a Browser Source in OBS sources.</li><li>Paste the link in the URL field. Keep default settings and click on OK.</li></ol></blockquote><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/obs-1.png"></p><ol><li>当你创建 box 之后,你会得到一个用于推流到直播间的链接</li><li>在 OBS 中创建一个浏览器源</li><li>把刚才 LoboBot 的链接拷贝过来(长度、宽度和位置自己调整)点击确定</li></ol><p>后面两种方式我自己没有尝试过,应该都是通过插入浏览器源的方式,故不做翻译了。</p><h5 id="Creating-a-Spotify-box"><a href="#Creating-a-Spotify-box" class="headerlink" title="Creating a Spotify box"></a>Creating a Spotify box</h5><blockquote><ol><li>Go to lobobot.com/spotify </li><li>Click on the Connect Spotify button. </li><li>You will be redirected to Spotify to login and authorize LoboBot to access your account. </li><li>Once you have authorized LoboBot, you will be redirected back. </li><li>In options, you can customize the box and finally click on the Save Options button. </li><li>Use the link to add the box to your stream.</li></ol></blockquote><h5 id="Adding-the-box-in-Streamlabs-Desktop"><a href="#Adding-the-box-in-Streamlabs-Desktop" class="headerlink" title="Adding the box in Streamlabs Desktop"></a>Adding the box in Streamlabs Desktop</h5><blockquote><ol><li>Once you have created the box, you will see a link to add the box to your stream. </li><li>Copy the link and create a Browser Source in Streamlabs Desktop sources. </li><li>Paste the link in the URL field. Keep default settings and click on OK.</li></ol></blockquote><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/streamlabs-1.png"></p><h4 id="自定义-Spotify-音乐播放器"><a href="#自定义-Spotify-音乐播放器" class="headerlink" title="自定义 Spotify 音乐播放器"></a>自定义 Spotify 音乐播放器</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/Amuse-Showoff2.png"></p><p>之前在看瓦的pov时候很多主播都用了这个,苦苦查了很久终于在油管上找到了定制教程 <a href="https://www.youtube.com/watch?v=7AueVTSOkNs">The BEST Now Playing Music Widget For Your Stream! (Spotify & YouTube Music)</a>:</p><iframe width="1500px" height="480px" src="https://www.youtube.com/embed/7AueVTSOkNs?si=Hwv8YnRz1wtoHorX" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe><h5 id="注册-6klabs"><a href="#注册-6klabs" class="headerlink" title="注册 6klabs"></a>注册 6klabs</h5><p><a href="https://6klabs.com/"><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/6klabs_site.png"></a></p><ul><li>网站链接:<a href="https://6klabs.com/">https://6klabs.com/</a></li></ul><p>选择 Twitch 或者 Google 账号进行登录:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/6klabs_profile.png"></p><p>选择 <code>Accounts & Apps</code> 链接 Soptify。</p><h5 id="链接-Soptify"><a href="#链接-Soptify" class="headerlink" title="链接 Soptify"></a>链接 Soptify</h5><h6 id="Create-Your-App"><a href="#Create-Your-App" class="headerlink" title="Create Your App"></a>Create Your App</h6><blockquote><p>This is a step-by-step tutorial on how to create your Spotify app and link it to 6K Labs.</p><ol><li>Visit the Spotify <a href="https://developer.spotify.com/dashboard">Developer Dashboard</a></li><li>Click on Log in in the top right</li></ol></blockquote><p>这是手把手教你如何创建自己的 Spotify 应用并链接到 6K Labs。</p><ol><li><p>访问 Spotify 的 <a href="https://developer.spotify.com/dashboard">Developer Dashboard</a></p></li><li><p>点击右上角的 <code>Log in</code> 登录自己的 Spotify 账号</p></li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/spotify_login.png"></p><blockquote><ol start="3"><li>Once logged in, click on your name. A dropdown appears. Click on <strong>Dashboard</strong>.</li></ol></blockquote><ol start="3"><li>登陆后点击自己的用户名,选择 <code>Dashboard</code></li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/spotify_dropdown.png"></p><blockquote><ol start="4"><li>Create a new app by clicking on Create app</li></ol></blockquote><ol start="4"><li>点击 <code>Create app</code> 创建一个新的应用程序</li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/spotify_createapp.png"></p><blockquote><ol start="5"><li>Enter this information, then click on Save</li></ol><ul><li>App Name: Amuse Widget</li><li>App description: This is an app for the 6K Labs Amuse widget.</li><li>Redirect URI: <code>https://api.6klabs.com/api/spotify/callback</code></li></ul></blockquote><ol start="5"><li>输入以下信息并点击保存:<ul><li><code>App Name</code>:随便填一点</li><li><code>App description</code>:随便填一点</li><li><code>Redirect URI</code>: <code>https://api.6klabs.com/api/spotify/callback</code></li></ul></li></ol><p>前面两个可以随便填,第三个 URL 必须填上面 6klabs 的这个链接</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/spotify_information.png"></p><blockquote><ol start="6"><li>Click on <strong>Settings</strong></li></ol></blockquote><ol start="6"><li>点击设置 <code>Settings</code></li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/spotify_dashboard.png"></p><blockquote><ol start="7"><li>Click on <strong>View client secret</strong></li></ol></blockquote><ol start="7"><li>点击 <code>View client secret</code></li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/spotify_settings.png"></p><blockquote><ol start="8"><li>Copy and paste the <code>Client ID</code> and <code>Client Secret</code> into the Amuse Spotify App form</li></ol></blockquote><ol start="8"><li>复制粘贴 <code>Client ID</code> 和 <code>Client Secret</code> 到之前的 6KLabs 链接界面</li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/spotify_client_secret.png"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/spotify_app.png"></p><blockquote><ol start="9"><li><p>Click on “Connect” and approve the app.</p></li><li><p>Enjoy! ✨</p></li></ol></blockquote><ol start="9"><li>点击 <code>Connect</code> 链接应用程序</li><li>ok了 ✨</li></ol><h6 id="Troubleshooting"><a href="#Troubleshooting" class="headerlink" title="Troubleshooting"></a>Troubleshooting</h6><ol><li><p><strong>INVALID_CLIENT: Invalid client</strong></p><p>In the event that you click on <strong>Connect</strong> and receive the <code>INVALID_CLIENT: Invalid client error</code>, this could indicate an incorrect <code>Client ID</code>. Please ensure to verify the correctness of your <code>Client ID</code>.</p></li><li><p><strong>INVALID_SECRET: Invalid client_secret</strong></p><p> In the event that you click on Connect and receive the <code>INVALID_SECRET: Invalid client_secret error</code>, it suggests that your <code>Client Secret</code> may be incorrect. Please ensure to verify the correctness of your <code>Client Secret</code>.</p></li><li><p><strong>INVALID_CLIENT: Invalid redirect URI</strong></p><p> In the event that you click on Connect and receive the <code>INVALID_CLIENT: Invalid redirect URI error</code>, it implies that the <code>Redirect URL</code> you entered in step 3 may be incorrect. Please ensure to verify the correctness of your <code>Redirect URL</code>.</p></li></ol><h5 id="自定义-Widgets"><a href="#自定义-Widgets" class="headerlink" title="自定义 Widgets"></a>自定义 Widgets</h5><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/6klabs_custom_widgets.png"></p><h4 id="心率展示"><a href="#心率展示" class="headerlink" title="心率展示"></a>心率展示</h4><h5 id="Garmin"><a href="#Garmin" class="headerlink" title="Garmin"></a>Garmin</h5><p><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/hyperate.jpg" alt="https://www.hyperate.io/#1"></p><p>心率显示对于佳明设备来说比较简单,在 <code>ConnectIQ</code> 中下载应用程序 <code>HypeRate</code>,之后打开手表就会广播一个你设备心率的网站地址:app.hyperate.io/xxxxx。</p><p>把这个网址放到 OBS 的浏览器源自己调整即可。</p><h5 id="Xiaomi"><a href="#Xiaomi" class="headerlink" title="Xiaomi"></a>Xiaomi</h5><p>小米我没有相应设备,网上搜到一篇文章仅供参考:<a href="https://dioye.com/D/58ccf5b8.html">直播实时显示心率(OBS-HeartRate)</a></p><h3 id="常见问题整理"><a href="#常见问题整理" class="headerlink" title="常见问题整理"></a>常见问题整理</h3><h4 id="黑屏无法捕获"><a href="#黑屏无法捕获" class="headerlink" title="黑屏无法捕获"></a>黑屏无法捕获</h4><p>部分笔记本使用了混合输出技术,需要把 OBS 设置为核显输出才能正确捕获内容。</p></body></html>]]></content>
<summary type="html"><p><a href="https://store.steampowered.com/app/1905180/OBS_Studio/"><img data-src="https://img.yousazoe.top/uPic/img/blog/OBS1/0Xlt5a.png"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>记录博主个人使用 <a href="https://obsproject.com/">OBS Studio</a> 直播的心得体会。</p></summary>
<category term="OBS 直播教程 (OBS Living Tutorial)" scheme="https://yousazoe.top/categories/OBS-%E7%9B%B4%E6%92%AD%E6%95%99%E7%A8%8B-OBS-Living-Tutorial/"/>
<category term="OBS" scheme="https://yousazoe.top/tags/OBS/"/>
</entry>
<entry>
<title>从 R3nzskin 项目窥见中国开源生态的现状及未来</title>
<link href="https://yousazoe.top/archives/7a6d5884.html"/>
<id>https://yousazoe.top/archives/7a6d5884.html</id>
<published>2023-05-05T13:46:14.000Z</published>
<updated>2025-01-03T06:07:21.717Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://github.com/R3nzTheCodeGOD/R3nzSkin"><img data-src="https://user-images.githubusercontent.com/58574988/134170370-c827d712-fcc7-432f-b9f8-96678b0c9bf6.gif"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p><a href="https://github.com/R3nzTheCodeGOD/R3nzSkin">R3nzskin</a> 这个项目我关注了小半年,也算是见证了这个开源项目的兴衰。深夜有感,决定写一篇博文来记录一下我的所见所闻,也希望能给其他开源项目和开发者一些启发和经验。</p><span id="more"></span><h3 id="与-R3-的邂逅"><a href="#与-R3-的邂逅" class="headerlink" title="与 R3 的邂逅"></a>与 R3 的邂逅</h3><p>我本人是游戏《英雄联盟》(League of Legends) 的狂热玩家,从大学开始就很喜欢玩这个游戏,也从中收获了很多快乐。</p><p>然而秉持着 “差生文具多” 的原则,我在腾讯代理的国服和拳头公司的直营服都为英雄买了很多皮肤,但我并没有足够的经济能力去购买所有我想要和喜欢的皮肤,恰好听闻现在有许多 “换肤” 软件可以使用(老玩家的话可能知道以前的多玩盒子),便踏上了使用换肤软件的道路。</p><p>最开始我找到的是大名鼎鼎的 <a href="http://leagueskin.net/p/download-mod-skin-2020-chn">LOLskin</a>:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/PL15/7FSdYC.jpg"></p><p>坦诚来说,它的使用并不方便,但是有如此海量的皮肤选择对于我而言已经足够了,更何况这个换肤软件本身就是免费的,我的钱包也终于能松一口气。</p><p>然而 LOLSkin 有一个问题:每次游戏版本更新它都必须重新从官网去下载,国服玩家还必须在更新后去等官网的 <code>.1</code> 版本,还是比较繁琐的。作为懒人代表,我把目光放到了淘宝,寄希望于消费获取比较稳定不需要每个游戏版本都要重新折腾的换肤软件。</p><p>淘宝网上这类换肤盒子软件眼花缭乱,我尝试了两个买量比较多的店铺,使用上还是比较复杂。共性上两款软件都需要通过远程服务器验证后才可以使用,付费模式就是购买体验卡包月、季、年这样,换肤模式仍是游戏开始前提前手动选择好需要的皮肤。 这种模式我也不是很喜欢,所以还是换回了 LOLskin 使用,同时也逛逛相关的帖子看看有什么比较好的换肤软件。</p><p>转机在有一天某位吧友提到了 R3nzskin 换肤效果不错,我就去搜索了一下:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/PL15/Rc3HVi.png"></p><p>居然还是开源项目,作为程序员那必须上手体验一番了。</p><h3 id="从使用者到布道者"><a href="#从使用者到布道者" class="headerlink" title="从使用者到布道者"></a>从使用者到布道者</h3><p>C++ 大师侯捷在《Effective C++》中曾经提到,许多新技术得以推广应用,除了发明者的辛劳付出,也离不开许多布道者用通俗易懂的方式去教授讲解。</p><p>本着开源共享的精神,我找到了 R3nzskin 的贴吧,发了一篇<a href="https://www.tieba.com/p/8050084006?pn=1">手把手教如何使用 R3nzskin 的教学帖</a>:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/PL15/RIk29V.png"></p><p>截止到写这篇博文为止,这个教学贴拥有 21w 阅读量、658 条回帖以及 227 个点赞,可以肯定的是这个帖子帮助了很多和我一样的人,并且让这个开源项目能够被更多人所熟知。</p><p>不仅如此,事实上我也花了一个下午去做了详细的使用教学视频投稿到 bilibili,然而不到半个小时审核就把我的视频下架了,理由是破坏计算机安全:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/PL15/T6kYSL.png"></p><p>不过吧主制作了类似的视频用以教学,只是我的视频第一次下架的理由是涉及宣传和广告还是令人啼笑皆非,一个开源软件又哪里有广告呢?</p><h3 id="更多的信徒"><a href="#更多的信徒" class="headerlink" title="更多的信徒"></a>更多的信徒</h3><p>随着许多朋友的共同努力,R3 在换肤已经小有名气,我有时间也会解答许多朋友遇到的常见问题例如缺失 dll、版本不匹配等等。有人在吧里收集皮肤 bug 提 issue,也有开发直接提交 pr 帮助完善这个项目,我自己也抽时间看了一些项目的源码,但逆向这块技术栈确实不太了解而且工作比较忙,所以后面就没再研究了。</p><p>项目中也加入了国人开发者 rainzee 支持国服版本,总之似乎一切都在向好的方向去发展。</p><p>与此同时淘宝店家自然不能放过这种赚钱的机会,他们把 R3 免费开源的软件套壳成之前那种需要服务器远程验证付费的样子去倒卖给那些无知的人,吧里也出现了一些倒狗。</p><h3 id="大洪水"><a href="#大洪水" class="headerlink" title="大洪水"></a>大洪水</h3><p>R3 很快被腾讯盯上了,也可以理解,毕竟断人财路如杀人父母,皮肤可以说占这个游戏 95%+ 的收入都不为过,在国服换肤自然不可能放过(使用 LOLSkin 也有一定概率受到处罚)。 很多人开始抱怨不能在国服使用,但也只是埋怨腾讯,而且恰逢当时拳头直营的台服刚开服,所以许多人跑去外服接着用,也没什么大问题。</p><p>大洪水源自 R3 项目原作者需要服兵役,可能未来不会频繁更新项目。</p><p>许多朋友感到惋惜,这么好用的软件以后没有人维护,我也给作者发了一封邮件去交流这个项目关于技术上的一些问题,看能不能接手下来继续维护,但是作者一直没有回复我,只好作罢。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/PL15/SVmdaZ.png"></p><p>一部分开发者开始自救,在 R3 的基础上创建仓库开始自己维护这个项目,其中不乏上面的淘宝店家混入其中推广自己套壳的付费换肤。</p><h3 id="最后一根稻草"><a href="#最后一根稻草" class="headerlink" title="最后一根稻草"></a>最后一根稻草</h3><p>压死骆驼的最后一根稻草是拳头的一次更新(<code>v13.5</code>),这次更新让之前用旧版本的 R3 玩家在角色死亡后换肤失效:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/PL15/AXVQGl.png"></p><p><img data-src="https://user-images.githubusercontent.com/4585440/224478772-5a44e7aa-ab5a-4d8f-b3c7-324ae3358b00.png"></p><p>无数质疑涌向了 R3,倒狗们宣传着 “自己开发 稳定维护” 的套壳 R3,许多人转而投向这些倒狗的怀抱,同时控诉着 R3 不作为。</p><h3 id="农夫与蛇"><a href="#农夫与蛇" class="headerlink" title="农夫与蛇"></a>农夫与蛇</h3><p>R3 后续还是更新了,但是存在使用时严重掉帧的问题,不能像之前一样正常使用。</p><p>R3 的国服开发者 rainzee 加入了 R3 贴吧,决定把自己改过的不掉帧稳定的修改版本分享给大家,但出于对套壳奸商的警惕决定也采用服务器发放许可的方式,虽然许可获取是免费的,但这恰恰成为了许多人攻击的源头。</p><p>他们认为 rainzee 就是没赚到钱所以不维护 R3 项目的后续,将开源分享的精神理念和和心怀感恩体谅的情感抛之脑后,对参与 R3 项目的开发者当作倒狗肆意谩骂,更有甚者对着那些主动无偿维护、自愿汉化的贡献者谩骂侮辱,这可能是我自参与开源社区以来见到的最荒谬的事情了。</p><h3 id="理想主义之死"><a href="#理想主义之死" class="headerlink" title="理想主义之死"></a>理想主义之死</h3><p>他们赢了,他们也输了。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/PL15/KP8MrN.jpg"></p><p>rainzee 发了声明,选择离开了这里,又一位理想主义者离我们而去,也让我想起之前看到的 <a href="https://kaiyuanshe.github.io/project/open-source-fans-manifesto/">开源人宣言</a>:</p><blockquote><p><strong>开源人宣言 - Open Source Fans Manifesto</strong></p><p>我们是一群开源的爱好者与信仰者,我们相信:开源代表着一种向善的力量!作为一场席卷全球的世界性运动,20 多年来的历史证明,开源不仅仅能够孕育最新的技术、创造更好的软件,更能够帮助这个世界变得更好。</p><p><strong>开源精神</strong></p><p>剖析开源的内涵,理解开源的精神,能够让我们理解,为何开源能够让世界变得更好。在我们看来,开源的精神体现在以下一些方面:</p><p><strong><em>分享(Sharing)</em></strong></p><p>当一个软件工程师写出一个不错的软件,他不会敝帚自珍,不会故步自封。他乐于分享,是因为他相信:这个软件可能会对别人也有帮助,更会有人帮助他,一起做出更好的软件。西谚有云:赠人玫瑰,手留余香。我们都相信:乐于分享是一切善举的开端。</p><p><strong><em>开放(openness)</em></strong></p><p>在很多方面,开放都非常重要。不仅仅是开放源代码,更包括公开透明的社区。这样的社区能够吸引更多的朋友加入。也能够帮助新来者,理解并认同社区规则。还能够促进监督以提升社区运行的程序正义。开放还包括欢迎一切的可能性,开源是世界的,也欢迎来自世界任何一个角落的使用者、参与者和贡献者。中国谚语有云:海纳百川,有容乃大。我们都相信:公开透明是一切良好协作的基石。</p><p><strong><em>平等(Equality)</em></strong></p><p>我们欢迎任何人的任何贡献,我们以统一的标准平等地评审每一次代码或文档提交,我们评审的仅仅是代码或文档本身的质量与价值,而不是以贡献者的学历、年龄、种族、性别或职位等标准来判断。人皆生而平等,所以我们都相信:对于平等的追求是社区健康的保障。</p><p><strong><em>协作(Collaboration)</em></strong></p><p>开源社区的协作,正是从接纳点滴贡献开始的,一个开放的社区,崇尚开放式的协作。这样的协作,不会在整个群体达成所有共识之后再开始,而是欢迎来自每一个人的一点一滴的改进。中国古语有云:不积小流,无以成江海。我们都相信:开放式协作,逐步凝聚共识是社区繁荣的秘诀。</p><p><strong><em>创造美好世界(Build a better world)</em></strong></p><p>每一位投身开源的朋友,都或多或少是理想主义者。我们都相信:这个并不完美的世界,理应变得更好。我们都相信:通过自己掌握的技术,借助开源的方法,能够把这个世界变得更好。我们更加相信:开源的精神内涵,应该被推广到更多的领域。因为:创造更加美好的世界,是开源的终极追求。</p><p><strong>行动倡议</strong></p><p>开源社区的朋友都相信从我做起的力量,因此,我们发出如下行动倡议:</p><p><strong><em>推而广之(Advocate widely)</em></strong></p><p>我们应该更加努力的向大众传播开源的理念与精神,让更多的人接受开源的理念,成为开源的同道中人。我们还应该在开源软件、开源硬件之外的领域,推广开源的实践,不仅仅是开放源代码,还应该开放数据、开放知识、开放一切可以帮助这个世界变得更好的知识与经验,让更多的行业、更多的群体,都接纳开源,成为开放式协作的受益者。</p><p><strong><em>互帮互助(Help each other)</em></strong></p><p>我们应该帮助更多的开源项目,不断发展成长。帮助各个开源社区,把社区的力量团结起来,共同协作。我们还应该防止开源的含义被滥用或曲解。我们要阻止割裂,反对人为设置的障碍,反对任何附加歧视条款的“伪开源”,确保开源始终是一项惠及全球的事业。</p><p><strong><em>立即行动(Just do it)</em></strong></p><p>每一个人都可以参与开源,而不是只有大咖才能做到。我们可以从翻译或撰写文档,纠正拼写做起,为代码除错,审核代码,提交代码,志愿支持开源活动,我们还可以布道演讲,吸引更多的朋友加入。</p></blockquote><p>我不知道这样的环境之后还会有多少开源项目会因此毁掉,我只知道拥抱开源共享的理念和心怀感恩体谅的情感目前还为时尚早。</p></body></html>]]></content>
<summary type="html"><p><a href="https://github.com/R3nzTheCodeGOD/R3nzSkin"><img data-src="https://user-images.githubusercontent.com/58574988/134170370-c827d712-fcc7-432f-b9f8-96678b0c9bf6.gif"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p><a href="https://github.com/R3nzTheCodeGOD/R3nzSkin">R3nzskin</a> 这个项目我关注了小半年,也算是见证了这个开源项目的兴衰。深夜有感,决定写一篇博文来记录一下我的所见所闻,也希望能给其他开源项目和开发者一些启发和经验。</p></summary>
<category term="Story" scheme="https://yousazoe.top/tags/Story/"/>
</entry>
<entry>
<title>精读 Effective C++</title>
<link href="https://yousazoe.top/archives/f1767e7a.html"/>
<id>https://yousazoe.top/archives/f1767e7a.html</id>
<published>2023-03-05T11:58:25.000Z</published>
<updated>2025-01-03T06:07:21.684Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://books.apple.com/us/book/effective-modern-c/id950075232"><img data-src="https://img.yousazoe.top/uPic/img/blog/CPP9/CPP8.png"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>深入 C++ 的诸多设计细节,了解实际场景的最佳实践,以面向对象的方式重新认识 C++。</p><span id="more"></span><h3 id="将-C-视作一系列的语言"><a href="#将-C-视作一系列的语言" class="headerlink" title="将 C++ 视作一系列的语言"></a>将 C++ 视作一系列的语言</h3><blockquote><p>Item 1: View C++ as a federation of languages</p></blockquote><p>最初,C++ 只是 C 语言加上一些面向对象的特性,所以 C++ 的原名是 “C with Classes”。 现在的 C++ 已经逐渐成熟,成为一门 <strong>多范式的程序设计语言</strong>(multiparadigm programming language)。同时支持过程式、面向对象、函数式、泛型编程,以及元编程。</p><p>C++ 的灵活使得它在很多问题上并没有统一的规则,而是取决于具体的程序设计范式和当前架构的设计意图。这样的情况下,我们最好把 C++ 看做是一系列的编程语言,而非一种特定的编程语言。</p><p>C++ 有四种主要的子语言:</p><ul><li><code>C</code>:C++ 是基于 C 设计的,你可以只使用 C++ 中 C 的那部分语法。此时你会发现你的程序反映的完全是C的特征:没有模板、没有异常、没有重载。 </li><li><code>Object-Oriented C++</code>:面向对象程序设计也是 C++ 的设计初衷:构造与析构、封装与继承、多态、动态绑定的虚函数。 </li><li><code>Template C++</code>:这是 C++ 的泛型编程部分,多数程序员很少涉及,但模板在很多情况下仍然很方便。另外 <strong>模板元编程</strong>(template metaprogramming)也是一个新兴的程序设计范式,虽然有点非主流。 </li><li><code>STL</code>:这是一个特殊的模板库,它的容器、迭代器和算法优雅地结合在一起,只是在使用时你需要遵循它的程序设计惯例。当然你也可以基于其他想法来构建模板库。</li></ul><p>总之 C++ 并非单一的一门语言,它有很多不同的规则集。因而C++可以被视为四种主要子语言的集合,每个子语言都有自己的程序设计惯例。</p><p>C++ 程序设计的惯例并非一成不变,而是取决于你使用C++语言的哪一部分。例如, 在基于C语言的程序设计中,基本类型传参时传值比传引用更有效率。 然而当你接触Object-Oriented C++时会发现,传常量指针是更好的选择。 但是你如果又碰到了STL,其中的迭代器和函数对象都是基于C语言的指针而设计的, 这时又回到了原来的规则:传值比传引用更好。</p><h3 id="避免使用-define"><a href="#避免使用-define" class="headerlink" title="避免使用 define"></a>避免使用 <code>define</code></h3><blockquote><p>Item 2: Prefer consts, enums, and inlines to #defines</p></blockquote><p>尽量使用常量、枚举和内联函数,代替 <code>#define</code>。我们知道 <code>#define</code> 定义的宏会在编译时进行替换,属于模块化程序设计的概念。 宏是全局的,面向对象程序设计中破坏了封装。因此在 C++ 中尽量避免它!</p><p>接着我们具体来看 <code>#define</code> 造成的问题。</p><h4 id="不易理解"><a href="#不易理解" class="headerlink" title="不易理解"></a>不易理解</h4><p>众所周知,由于预处理器会直接替换的原因,宏定义最好用括号括起来。#define函数将会产生出乎意料的结果:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAX(a, b) a > b ? a : b</span></span><br><span class="line"><span class="built_in">MAX</span>(i++, j)</span><br></pre></td></tr></tbody></table></figure><p><code>i</code> 自加次数将取决于 <code>j</code> 的大小,然而调用者并不知情。宏的行为不易理解,本质上是因为宏并非 C++ 语言的一部分,它只是源代码的预处理手段。</p><h4 id="不利于调试"><a href="#不利于调试" class="headerlink" title="不利于调试"></a>不利于调试</h4><p>宏替换发生在编译时,语法检查之前。因此相关的编译错误中不会出现宏名称,我们不知道是哪个宏出了问题。例如:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> PERSON alice</span></span><br><span class="line">PERSON = bob;</span><br></pre></td></tr></tbody></table></figure><p>如果 <code>alice</code> 未定义,<code>PERSON=bob;</code> 便会出错:use of undeclared identifier ‘alice’。 然而我们可能不知道 <code>alice</code> 是什么东西,<code>PERSON</code> 才是我们定义的“变量”。</p><p>宏替换是在预处理过程中进行的,原则上讲编译器不知道宏的概念。然而,在现代的编译器中(例如Apple LLVM version 6.0), 编译器会记录宏替换信息,在编译错误中给出宏的名称:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">test.cpp:<span class="number">8</span>:<span class="number">5</span>: error: use of undeclared identifier <span class="string">'alice'</span></span><br><span class="line"> PERSON = bob;</span><br><span class="line"> ^</span><br><span class="line">test.cpp:<span class="number">4</span>:<span class="number">16</span>: note: expanded from macro <span class="string">'PERSON'</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PERSON alice;</span></span><br><span class="line"> ^</span><br></pre></td></tr></tbody></table></figure><p>于是,Meyers 提到的这个问题已经不存在了。然而作者的本意在于:尽量使用编译器,而不是预处理器。 因为 <code>#define</code> 并不是 C++ 语言的一部分。</p><h4 id="enum-比-const-更好用"><a href="#enum-比-const-更好用" class="headerlink" title="enum 比 const 更好用"></a><code>enum</code> 比 <code>const</code> 更好用</h4><p>既然 <code>#define</code> 不能封装在一个类中,我们可以用 <code>static const</code> 来定义一个常量,并把它的作用于局限在当前类:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">C</span>{</span><br><span class="line"> <span class="type">static</span> <span class="type">const</span> <span class="type">int</span> NUM = <span class="number">3</span>;</span><br><span class="line"> <span class="type">int</span> a[NUM];</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p>通常 C++ 要求所有的声明都给出定义,然而数值类型(<code>char</code>, <code>int</code>, <code>long</code>)的静态常量可以只给声明。这里的 <code>NUM</code> 就是一个例子。 然而,如果你想取 <code>NUM</code> 的地址,则会得到编译错误:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">Undefined symbols for architecture x86_64:</span><br><span class="line"> "C::NUM", referenced from:</span><br><span class="line"> _main in a-88bbac.o</span><br><span class="line">ld: symbol(s) not found for architecture x86_64</span><br><span class="line">clang: error: linker command failed with exit code 1 (use -v to see invocation)</span><br></pre></td></tr></tbody></table></figure><p>因此如果你要取地址,那么就给出它的定义:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">C</span>{</span><br><span class="line"> <span class="type">static</span> <span class="type">const</span> <span class="type">int</span> NUM = <span class="number">3</span>;</span><br><span class="line"> <span class="type">int</span> a[NUM];</span><br><span class="line">};</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> C::NUM;</span><br></pre></td></tr></tbody></table></figure><p>因为声明 <code>NUM</code> 时已经给定了初始值,定义时不允许再次给初始值。 如果使用 <code>enum</code>,事情会简单很多:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">C</span>{</span><br><span class="line"> <span class="keyword">enum</span> { NUM = <span class="number">3</span> };</span><br><span class="line"> <span class="type">int</span> a[NUM];</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><h3 id="尽量使用常量"><a href="#尽量使用常量" class="headerlink" title="尽量使用常量"></a>尽量使用常量</h3><blockquote><p>Item 3: Use const whenever possible</p></blockquote><p>尽量使用常量。不需多说,这是 <strong>防卫型</strong>(defensive)程序设计的原则, 尽量使用常量限定符,从而防止客户错误地使用你的代码。</p><h4 id="常量的声明"><a href="#常量的声明" class="headerlink" title="常量的声明"></a>常量的声明</h4><p>总结一下各种指针的声明方式吧:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">char</span> greeting[] = <span class="string">"Hello"</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">char</span> *p = greeting; <span class="comment">// non-const pointer, non-const data</span></span><br><span class="line"><span class="type">const</span> <span class="type">char</span> *p = greeting; <span class="comment">// non-const pointer, const data</span></span><br><span class="line"><span class="type">char</span> * <span class="type">const</span> p = greeting; <span class="comment">// const pointer, non-const data</span></span><br><span class="line"><span class="type">const</span> <span class="type">char</span> * <span class="type">const</span> p = greeting; <span class="comment">// const pointer, const data</span></span><br></pre></td></tr></tbody></table></figure><p><code>const</code> 出现在 <code>*</code> 左边则被指向的对象是常量,出现在 <code>*</code> 右边则指针本身是常量。 然而对于常量对象,有人把 <code>const</code> 放在类型左边,有人把 <code>const</code> 放在 <code>*</code> 左边,都是可以的:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">f1</span><span class="params">(<span class="type">const</span> Widget *pw)</span></span>; <span class="comment">// f1 takes a pointer to a constant Widget object</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f2</span><span class="params">(Widget <span class="type">const</span> *pw)</span></span>; <span class="comment">// 等效</span></span><br></pre></td></tr></tbody></table></figure><p>STL 的 <code>iterator</code> 也是类似的,如果你希望指针本身是常量,可以声明 <code>const iterator</code>; 如果你希望指针指向的对象是常量,请使用 <code>const_iterator</code>:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">std::vector<<span class="type">int</span>> vec;</span><br><span class="line"></span><br><span class="line"><span class="comment">// iter acts like a T* const</span></span><br><span class="line"><span class="type">const</span> std::vector<<span class="type">int</span>>::iterator iter = vec.<span class="built_in">begin</span>();</span><br><span class="line">*iter = <span class="number">10</span>; <span class="comment">// OK, changes what iter points to</span></span><br><span class="line">++iter; <span class="comment">// error! iter is const</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//cIter acts like a const T*</span></span><br><span class="line">std::vector<<span class="type">int</span>>::const_iterator cIter = vec.<span class="built_in">begin</span>();</span><br><span class="line">*cIter = <span class="number">10</span>; <span class="comment">// error! *cIter is const</span></span><br><span class="line">++cIter; <span class="comment">// fine, changes cIter</span></span><br></pre></td></tr></tbody></table></figure><p>返回值声明为常量可以防止你的代码被错误地使用,例如实数相加的方法:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">const</span> Rational <span class="keyword">operator</span>*(<span class="type">const</span> Rational& lhs, <span class="type">const</span> Rational& rhs);</span><br></pre></td></tr></tbody></table></figure><p>当用户错误地使用 <code>=</code> 时:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">Rational a, b, c;</span><br><span class="line"><span class="keyword">if</span> (a * b = c){</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>编译器便会给出错误:不可赋值给常量。</p><h4 id="常量成员方法"><a href="#常量成员方法" class="headerlink" title="常量成员方法"></a>常量成员方法</h4><p>声明常量成员函数是为了确定哪些方法可以通过常量对象来访问,另外一方面让接口更加易懂: 很容易知道哪些方法会改变对象,哪些不会。</p><p>成员方法添加常量限定符属于函数重载。常量对象只能调用常量方法, 非常量对象优先调用非常量方法,如不存在会调用同名常量方法。 常量成员函数也可以在类声明外定义,但声明和定义都需要指定 <code>const</code> 关键字。 例如:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TextBlock</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="type">const</span> <span class="type">char</span>& <span class="keyword">operator</span>[](std::<span class="type">size_t</span> position) <span class="type">const</span> <span class="comment">// operator[] for</span></span><br><span class="line"> { <span class="keyword">return</span> text[position]; } <span class="comment">// const objects</span></span><br><span class="line"> </span><br><span class="line"> <span class="type">char</span>& <span class="keyword">operator</span>[](std::<span class="type">size_t</span> position) <span class="comment">// operator[] for</span></span><br><span class="line"> { <span class="keyword">return</span> text[position]; } <span class="comment">// non-const objects</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> std::string text;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function">TextBlock <span class="title">tb</span><span class="params">(<span class="string">"Hello"</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="type">const</span> TextBlock <span class="title">ctb</span><span class="params">(<span class="string">"World"</span>)</span></span>;</span><br><span class="line">tb[<span class="number">0</span>] = <span class="string">'x'</span>; <span class="comment">// fine — writing a non-const TextBlock</span></span><br><span class="line">ctb[<span class="number">0</span>] = <span class="string">'x'</span>; <span class="comment">// error! — writing a const TextBlock</span></span><br></pre></td></tr></tbody></table></figure><h4 id="比特常量和逻辑常量"><a href="#比特常量和逻辑常量" class="headerlink" title="比特常量和逻辑常量"></a>比特常量和逻辑常量</h4><p><strong>比特常量</strong>(bitwise constness):如果一个方法不改变对象的任何非静态变量,那么该方法是常量方法。 比特常量是 C++ 定义常量的方式,然而一个满足比特常量的方法,却不见得表现得像个常量, 尤其是数据成员是指针时:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TextBlock</span>{</span><br><span class="line"><span class="type">char</span>* text;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="type">char</span>& <span class="keyword">operator</span>[](<span class="type">int</span> pos) <span class="type">const</span>{</span><br><span class="line"> <span class="keyword">return</span> text[pos];</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> TextBlock tb;</span><br><span class="line"><span class="type">char</span> *p = &tb[<span class="number">1</span>];</span><br><span class="line">*p = <span class="string">'a'</span>;</span><br></pre></td></tr></tbody></table></figure><p>因为 <code>char* text</code> 并未发生改变,所以编译器认为我们的操作都是合法的。 然而我们定义了一个常量对象 tb,只调用它的常量方法,却能够修改tb的数据。 对数据的操作甚至可以放在 <code>operator[]()</code> 方法里面。</p><p>这一点不合理之处引发了 <strong>逻辑常量</strong>(logical constness)的讨论:常量方法可以修改数据成员, 只要客户检测不到变化就可以。可是常量方法修改数据成员 C++ 编译器不会同意的!这时我们需要 <code>mutable</code> 限定符:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CTextBlock</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">std::<span class="type">size_t</span> <span class="title">length</span><span class="params">()</span> <span class="type">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="type">char</span> *pText;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">mutable</span> std::<span class="type">size_t</span> textLength; <span class="comment">// these data members may</span></span><br><span class="line"> <span class="keyword">mutable</span> <span class="type">bool</span> lengthIsValid; <span class="comment">// always be modified</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function">std::<span class="type">size_t</span> <span class="title">CTextBlock::length</span><span class="params">()</span> <span class="type">const</span></span>{</span><br><span class="line"> <span class="keyword">if</span> (!lengthIsValid) {</span><br><span class="line"> textLength = std::<span class="built_in">strlen</span>(pText);</span><br><span class="line"> lengthIsValid = <span class="literal">true</span>; </span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> textLength;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="避免常量-x2F-非常量方法的重复"><a href="#避免常量-x2F-非常量方法的重复" class="headerlink" title="避免常量/非常量方法的重复"></a>避免常量/非常量方法的重复</h4><p>通常我们需要定义成对的常量和普通方法,只是返回值的修改权限不同。 当然我们不希望重新编写方法的逻辑。最先想到的方法是常量方法调用普通方法,然而这是 C++ 语法不允许的。 于是我们只能用普通方法调用常量方法,并做相应的类型转换:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">char</span>& <span class="keyword">operator</span>[](<span class="type">size_t</span> pos) <span class="type">const</span>{</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">char</span>& <span class="keyword">operator</span>[](<span class="type">size_t</span> pos){</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">const_cast</span><<span class="type">char</span>&>(</span><br><span class="line"> <span class="built_in">static_cast</span><<span class="type">const</span> TextBlock&>(*<span class="keyword">this</span>)</span><br><span class="line"> [pos] </span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ol><li><code>*this</code> 的类型是 <code>TextBlock</code>,先把它强制隐式转换为 <code>const TextBlock</code>,这样我们才能调用那个常量方法</li><li>调用 <code>operator[](size_t) const</code>,得到的返回值类型为 <code>const char&</code></li><li>把返回值去掉 <code>const</code> 属性,得到类型为 <code>char&</code> 的返回值</li></ol></body></html>]]></content>
<summary type="html"><p><a href="https://books.apple.com/us/book/effective-modern-c/id950075232"><img data-src="https://img.yousazoe.top/uPic/img/blog/CPP9/CPP8.png"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>深入 C++ 的诸多设计细节,了解实际场景的最佳实践,以面向对象的方式重新认识 C++。</p></summary>
<category term="精读 Effective C++ (Effective C++)" scheme="https://yousazoe.top/categories/%E7%B2%BE%E8%AF%BB-Effective-C-Effective-C/"/>
<category term="Cpp" scheme="https://yousazoe.top/tags/Cpp/"/>
</entry>
<entry>
<title>维拓标准接口开发项目总结</title>
<link href="https://yousazoe.top/archives/4bc24537.html"/>
<id>https://yousazoe.top/archives/4bc24537.html</id>
<published>2023-03-04T08:13:29.000Z</published>
<updated>2025-01-03T06:07:21.703Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://www.behance.net/gallery/116635445/Becoming-Someone-New-1-5-by-James-Lipnickas-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR2/GSTAR2.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>维拓标准接口的开发也基本完成了,这也是我首次用 C# 完成定制项目。之前 Unity 学的很多东西都忘掉了,再捡起来用还是有些吃力,所以写了这篇博客总结一下。</p><span id="more"></span><h3 id="C-编程范式"><a href="#C-编程范式" class="headerlink" title="C# 编程范式"></a>C# 编程范式</h3><h4 id="数组越界"><a href="#数组越界" class="headerlink" title="数组越界"></a>数组越界</h4><p>老生常谈的问题了,这里构造 <code>Paper</code> 的时候 <code>item[]</code> 如果没有这些索引又会抛出异常,但自己写的时候经常注意不到:</p><figure class="highlight diff"><table><tbody><tr><td class="code"><pre><span class="line"><span class="addition">+if (item.Length < 2)</span></span><br><span class="line"><span class="addition">+ continue;</span></span><br><span class="line"></span><br><span class="line"><span class="addition">+try</span></span><br><span class="line"><span class="addition">+{</span></span><br><span class="line"> Paper paper = new Paper(item[0], Double.Parse(item[1]), Double.Parse(item[2]));</span><br><span class="line"> result.Add(paper);</span><br><span class="line"><span class="addition">+}</span></span><br></pre></td></tr></tbody></table></figure><h4 id="文件路径合并"><a href="#文件路径合并" class="headerlink" title="文件路径合并"></a>文件路径合并</h4><p>之前对于需要合并的路径我都是这么通过对 <code>string</code> 直接进行加减来操作的:</p><figure class="highlight c#"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">string</span> FilePath1 = <span class="string">@"C:\Users\MFGYF-WXY\source\repos"</span>;</span><br><span class="line"><span class="built_in">string</span> FilePath2 = <span class="string">@"\PLMInterface\bin\cadext.exe"</span></span><br><span class="line"><span class="built_in">string</span> MergeFilePath = FilePath1 + FilePath2;</span><br></pre></td></tr></tbody></table></figure><p>但这种方式很业余,这种情况正确的处理是用 <code>Path.Combine()</code> 方法将两个路径进行合并:</p><figure class="highlight c#"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">string</span> FilePath1 = <span class="string">@"C:\Users\MFGYF-WXY\source\repos"</span>;</span><br><span class="line"><span class="built_in">string</span> FilePath2 = <span class="string">@"\PLMInterface\bin\cadext.exe"</span></span><br><span class="line"><span class="built_in">string</span> MergeFilePath = Path.Combine(FilePath1, FilePath2);</span><br></pre></td></tr></tbody></table></figure><h4 id="程序配置与-lt-bindingRedirect-gt"><a href="#程序配置与-lt-bindingRedirect-gt" class="headerlink" title="程序配置与 <bindingRedirect/>"></a>程序配置与 <code><bindingRedirect/></code></h4><p>在完成部分接口把程序打包成exe时重新校对了一下 NewtonJson 的版本,之前在 Nuget 上下载的是 13 版本,但是 GStarCAD 目录下的版本是 12。将版本调整之后 exe 无法正常使用:</p><figure class="highlight shell"><table><tbody><tr><td class="code"><pre><span class="line">未经处理的异常:SystemI0.Fi1eLoadException:未能加载文件或程序集“Newtonsoft.Json,Version=13.0.0.0 Culture=neutral Pub1icKeyToken=30ad4fe6b2a6aeed”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。(异常来自 HRESULT:0x80131040)--->SystemIO.Fi1eLoadException:未能加载文件或程序集“Newtonsoft.Ison,Version=12.0.0.0,Culture=neutral, PublicKe yToken=30ad4fe6b2a6aeed”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。(异常来自 HRESULT:0x80131040)</span><br></pre></td></tr></tbody></table></figure><p>再次检查 Nuget 版本已经调整过来了,但是编译之后的 exe 还是会报错 NewtonJson 版本错误。其实只需要调整一下 <code>app.config</code> 文件:</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span>?></span></span><br><span class="line"><span class="tag"><<span class="name">configuration</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">startup</span>></span> </span><br><span class="line"> <span class="tag"><<span class="name">supportedRuntime</span> <span class="attr">version</span>=<span class="string">"v4.0"</span> <span class="attr">sku</span>=<span class="string">".NETFramework,Version=v4.8"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">startup</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">runtime</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">assemblyBinding</span> <span class="attr">xmlns</span>=<span class="string">"urn:schemas-microsoft-com:asm.v1"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependentAssembly</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">assemblyIdentity</span> <span class="attr">name</span>=<span class="string">"Newtonsoft.Json"</span> <span class="attr">publicKeyToken</span>=<span class="string">"30ad4fe6b2a6aeed"</span> <span class="attr">culture</span>=<span class="string">"neutral"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">bindingRedirect</span> <span class="attr">oldVersion</span>=<span class="string">"0.0.0.0-13.0.0.0"</span> <span class="attr">newVersion</span>=<span class="string">"13.0.0.0"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">dependentAssembly</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">assemblyBinding</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">runtime</span>></span></span><br><span class="line"><span class="tag"></<span class="name">configuration</span>></span></span><br></pre></td></tr></tbody></table></figure><p>问题在于 <code><bindingRedirect/></code> 这个标签,<a href="https://learn.microsoft.com/zh-cn/dotnet/framework/configure-apps/file-schema/runtime/bindingredirect-element">微软官方API文档</a> 这里有比较详细的解释。</p><blockquote><p>将一个程序集版本重定向到另一个版本。</p></blockquote><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">bindingRedirect</span></span></span><br><span class="line"><span class="tag"><span class="attr">oldVersion</span>=<span class="string">"existing assembly version"</span> </span></span><br><span class="line"><span class="tag"><span class="attr">newVersion</span>=<span class="string">"new assembly version"</span>/></span></span><br></pre></td></tr></tbody></table></figure><ul><li><code>oldVersion</code>:指定最初请求的程序集的版本。 程序集版本号的格式为 <code>major.minor.build.revision</code>。 该版本号的每个部分的有效值介于 0 和 65535 之间。</li><li><code>newVersion</code>:指定要用来取代最初请求的版本的程序集版本(格式为:<code>n.n.n.n</code>) ,此值可以指定 oldVersion 之前的版本。</li></ul><p>官方还给出了一个示例演示如何将一个程序集版本重定向到另一个版本:</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">configuration</span>></span> </span><br><span class="line"> <span class="tag"><<span class="name">runtime</span>></span> </span><br><span class="line"> <span class="tag"><<span class="name">assemblyBinding</span> <span class="attr">xmlns</span>=<span class="string">"urn:schemas-microsoft-com:asm.v1"</span>></span> </span><br><span class="line"> <span class="tag"><<span class="name">dependentAssembly</span>></span> </span><br><span class="line"> <span class="tag"><<span class="name">assemblyIdentity</span> <span class="attr">name</span>=<span class="string">"myAssembly"</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">publicKeyToken</span>=<span class="string">"32ab4ba45e0a69a1"</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">culture</span>=<span class="string">"neutral"</span> /></span> </span><br><span class="line"> <span class="tag"><<span class="name">bindingRedirect</span> <span class="attr">oldVersion</span>=<span class="string">"1.0.0.0"</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">newVersion</span>=<span class="string">"2.0.0.0"</span>/></span> </span><br><span class="line"> <span class="tag"></<span class="name">dependentAssembly</span>></span> </span><br><span class="line"> <span class="tag"></<span class="name">assemblyBinding</span>></span> </span><br><span class="line"> <span class="tag"></<span class="name">runtime</span>></span> </span><br><span class="line"><span class="tag"></<span class="name">configuration</span>></span></span><br></pre></td></tr></tbody></table></figure><p>所以把这个标签删掉,程序就可以正常运行了,这是一个知识盲点记录下来。</p><h4 id="读取-dat-文件数据"><a href="#读取-dat-文件数据" class="headerlink" title="读取 .dat 文件数据"></a>读取 <code>.dat</code> 文件数据</h4><p>所需要读取的 <code>PaperSet.dat</code> 长相如下,包含了国标图幅的各个数据尺寸:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">Name B L a c e k BD LD</span><br><span class="line">A0 841 1189 25 10 20 1 16 12 </span><br><span class="line">A1 594 841 25 10 20 1 12 8</span><br><span class="line">A2 420 594 25 10 10 1 8 6</span><br><span class="line">A3 297 420 25 5 10 1 2 2</span><br><span class="line">A4 210 297 25 5 10 1 2 2 </span><br><span class="line">A0X2 1189 1682 25 10 20 3 16 24</span><br><span class="line">A0X3 1189 2523 25 10 20 3 16 36</span><br><span class="line">A1X3 841 1783 25 10 20 3 12 24</span><br><span class="line">A1X4 841 2378 25 10 20 3 12 32</span><br><span class="line">A2X3 594 1261 25 10 20 3 8 18</span><br><span class="line">A2X4 594 1682 25 10 20 3 8 24</span><br><span class="line">A2X5 594 2102 25 10 20 3 8 24</span><br><span class="line">A3X3 420 891 25 10 10 2 6 12</span><br><span class="line">A3X4 420 1189 25 10 10 2 6 16</span><br><span class="line">A3X5 420 1486 25 10 10 2 6 10</span><br><span class="line">A3X6 420 1783 25 10 10 3 6 12</span><br><span class="line">A3X7 420 2080 25 10 10 3 6 14</span><br><span class="line">A4X3 297 630 25 5 10 2 2 8 </span><br><span class="line">A4X4 297 841 25 5 10 2 2 12</span><br><span class="line">A4X5 297 1051 25 5 10 2 2 12</span><br><span class="line">A4X6 297 1261 25 5 10 3 2 12</span><br><span class="line">A4X7 297 1471 25 5 10 3 2 14</span><br><span class="line">A4X8 297 1682 25 5 10 3 2 16</span><br><span class="line">A4X9 297 1892 25 5 10 3 2 18</span><br></pre></td></tr></tbody></table></figure><p>观察数据结构,每行数据均以若干空格分隔图幅名称、长度、宽度等等数据,所以我们需要逐行去读取,先简单声明一个图幅信息类包含图幅名称和长宽:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 国标图幅信息类</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Paper</span></span><br><span class="line">{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Paper</span>(<span class="params"><span class="built_in">string</span> Name, <span class="built_in">double</span> Width, <span class="built_in">double</span> Length</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">this</span>.Name = Name;</span><br><span class="line"> <span class="keyword">this</span>.Length = Length;</span><br><span class="line"> <span class="keyword">this</span>.Width = Width;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> 图幅名称 </span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> eg. A4</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">string</span> Name { <span class="keyword">get</span>; }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> 图幅长度</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">double</span> Length { <span class="keyword">get</span>; }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> 图幅宽度</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">double</span> Width { <span class="keyword">get</span>; }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>之后通过 <code>StreamReader</code> 逐行读取(首行不读取)即可:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line">List<Paper> result = <span class="keyword">new</span> List<Paper>();</span><br><span class="line"><span class="built_in">string</span> path = Path.Combine(installPath, <span class="string">@"MCADSetting\PaperSet.dat"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 逐行读取 PaperSet.dat 数据</span></span><br><span class="line"><span class="keyword">using</span> (StreamReader reader = <span class="keyword">new</span> StreamReader(path))</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">int</span> index = <span class="number">0</span>;</span><br><span class="line"> <span class="built_in">string</span> line;</span><br><span class="line"> <span class="keyword">while</span> ((line = reader.ReadLine()) != <span class="literal">null</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// 首行不作为数据读取,直接跳过</span></span><br><span class="line"> <span class="keyword">if</span> (index++ == <span class="number">0</span>) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 去除字符串中的多余空格</span></span><br><span class="line"> <span class="built_in">string</span>[] item = line.Split(<span class="keyword">new</span>[] { <span class="string">' '</span>, <span class="string">'\t'</span> }, StringSplitOptions.RemoveEmptyEntries);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (item.Length < <span class="number">2</span>) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> Paper paper = <span class="keyword">new</span> Paper(item[<span class="number">0</span>], Double.Parse(item[<span class="number">1</span>]), Double.Parse(item[<span class="number">2</span>]));</span><br><span class="line"> result.Add(paper);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><code>String.Split</code> 方法可以参考微软的这篇 <a href="https://learn.microsoft.com/zh-cn/dotnet/csharp/how-to/parse-strings-using-split">如何在 C# 中使用 String.Split 分隔字符串</a>, <code>StringSplitOptions.RemoveEmptyEntrie</code>s 参数来排除返回数组中的任何空字符串。要对返回的集合进行更复杂的处理,可使用 <a href="https://learn.microsoft.com/zh-cn/dotnet/csharp/linq/">LINQ</a> 来处理结果序列。</p><h3 id="CADNET-amp-PLM-接口使用"><a href="#CADNET-amp-PLM-接口使用" class="headerlink" title="CADNET & PLM 接口使用"></a>CADNET & PLM 接口使用</h3><h4 id="GetCADApplication"><a href="#GetCADApplication" class="headerlink" title="GetCADApplication"></a>GetCADApplication</h4><p>获取 CAD 应用程序,首先明确我们的思路:</p><ol><li>若 CAD 程序已经存在,则直接跳转至已打开的 CAD 程序</li><li>否则新建 CAD 应用程序</li></ol><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">try</span></span><br><span class="line">{</span><br><span class="line"> <span class="comment">// 先尝试是否可以直接捕获打开的CAD应用程序</span></span><br><span class="line"> IGcadApplication app = Marshal.GetActiveObject(<span class="string">"GStarCAD.Application"</span>) <span class="keyword">as</span> IGcadApplication;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 避免出现后台打开的情况</span></span><br><span class="line"> app.Visible = <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">catch</span>(Exception exception)</span><br><span class="line">{</span><br><span class="line"><span class="comment">// 捕获失败则新建CAD应用程序</span></span><br><span class="line"> app = <span class="keyword">new</span> GcadApplicationClass();</span><br><span class="line"> app.Visible = <span class="literal">true</span>;</span><br><span class="line"> ......</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里捕获用到了 <code>System.Runtime.InteropServices</code> 中的 <code>Marshal.GetActiveObject()</code> 方法。</p><p>注意 <code>app.Visible</code> 可以让后台的应用程序前置显示,在忘记关闭 <code>Quit()</code> 时会出现多个后台运行程序,在项目的前期测试造成了不少麻烦。</p><h4 id="CloseFile"><a href="#CloseFile" class="headerlink" title="CloseFile"></a>CloseFile</h4><p>该命令会传入一个图纸路径 <code>filePath</code>,关闭该指定路径的图纸。</p><p>关闭图纸的业务场景相对简单,只需要判断两点:</p><ol><li>当前 CAD 是否打开过任何图纸</li><li>在已打开过的图纸中是否有和 <code>filePath</code> 相对应的图纸</li></ol><p>最后对找到的图纸进行关闭操作即可。</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 图纸数量小于等于 0 则未打开任何图纸</span></span><br><span class="line"><span class="keyword">if</span> (app.Documents.Count <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历当前已打开图纸</span></span><br><span class="line"><span class="keyword">foreach</span> (GcadDocument doc <span class="keyword">in</span> app.Documents)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">string</span>.Equals(doc.FullName, filePath, StringComparison.OrdinalIgnoreCase))</span><br><span class="line"> {</span><br><span class="line"> doc.Close();</span><br><span class="line"> <span class="keyword">return</span>; <span class="comment">// 关闭操作成功</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span>; <span class="comment">// 未找到关闭图纸</span></span><br></pre></td></tr></tbody></table></figure><p>需要注意的是这里需要用 <code>doc.FullName</code> 而非 <code>doc.Name</code>,以规避出现诸如 <code>C:\test.dwg</code> 和 <code>D:\test.dwg</code> 在 <code>Name</code> 中均为 <code>test.dwg</code> 的末端图纸路径重名的情况。</p><h4 id="OpenFile"><a href="#OpenFile" class="headerlink" title="OpenFile"></a>OpenFile</h4><p>与前面的关闭命令类似,该命令会传入一个图纸路径 <code>filePath</code>,若已经打开图纸则直接返回,否则打开该指定路径的图纸。</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 若当前已有打开图纸</span></span><br><span class="line"><span class="keyword">if</span> (app.Documents.Count > <span class="number">0</span>)</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// 遍历当前已打开图纸</span></span><br><span class="line"> <span class="keyword">foreach</span> (GcadDocument doc <span class="keyword">in</span> app.Documents)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">string</span>.Equals(doc.FullName, path, StringComparison.OrdinalIgnoreCase))</span><br><span class="line"> <span class="keyword">return</span>; <span class="comment">// 图纸已经打开</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 没找到指定图纸则尝试打开该图纸</span></span><br><span class="line">app.Documents.Open(path);</span><br></pre></td></tr></tbody></table></figure><h4 id="SaveFile"><a href="#SaveFile" class="headerlink" title="SaveFile"></a>SaveFile</h4><p>保存文件也是类似的,先传入一个图纸路径 <code>filePath</code>,若该图纸还未被打开则直接返回,否则保存该指定路径的图纸。</p><p>多的一种情况是 CAD 在图纸为只读模式的前提下逻辑上应禁止保存,这点注意一下即可。</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 图纸数量小于等于 0 则未打开任何图纸</span></span><br><span class="line"><span class="keyword">if</span> (appController.CadApp.Documents.Count <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">return</span> Message.SendMessage(<span class="literal">false</span>, <span class="string">"没有图纸被打开,保存失败"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历当前已打开图纸</span></span><br><span class="line"><span class="keyword">foreach</span> (GcadDocument doc <span class="keyword">in</span> appController.CadApp.Documents)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">string</span>.Equals(doc.FullName, path, StringComparison.OrdinalIgnoreCase))</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// 只读模式下的图纸不保存</span></span><br><span class="line"> <span class="keyword">if</span> (doc.ReadOnly)</span><br><span class="line"> <span class="keyword">return</span> Message.SendMessage(<span class="literal">false</span>, <span class="string">"指定图纸为只读模式,保存失败"</span>); ;</span><br><span class="line"></span><br><span class="line"> doc.Save();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span>; <span class="comment">// 找不到要保存的指定图纸,保存失败;</span></span><br></pre></td></tr></tbody></table></figure><h4 id="GetOpeningFile"><a href="#GetOpeningFile" class="headerlink" title="GetOpeningFile"></a>GetOpeningFile</h4><p>获取打开文件直接调用 <code>ActiveDocument</code> 即可。 </p><p>但是当一张图纸新建之后并未保存(诸如 <code>Drawing1.dwg</code> 这样),需要系统变量 <code>DWGTITLED</code> 来判断是否为这种尚未保存的情况:</p><ul><li><code>DWGTITLED = 0</code>:Drawing has not been named</li><li><code>DWGTITLED = 1</code>:Drawing has been named</li></ul><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (appController.CadApp.Documents.Count <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">return</span>; <span class="comment">// 没有图纸被打开</span></span><br><span class="line"></span><br><span class="line">GcadDocument doc = appController.CadApp.ActiveDocument;</span><br><span class="line"><span class="built_in">int</span> titled = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">string</span> dwgTitled = doc.GetVariable(<span class="string">"DWGTITLED"</span>).ToString();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">int</span>.TryParse(dwgTitled, <span class="keyword">out</span> titled))</span><br><span class="line"> <span class="keyword">return</span>; <span class="comment">// DWGTITLED类型转换失败</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> titled == <span class="number">0</span> ? <span class="literal">null</span> : doc.FullName;</span><br></pre></td></tr></tbody></table></figure><h3 id="NewtonSoftJson自定义Convertor序列化Json"><a href="#NewtonSoftJson自定义Convertor序列化Json" class="headerlink" title="NewtonSoftJson自定义Convertor序列化Json"></a>NewtonSoftJson自定义Convertor序列化Json</h3><p>这个项目一个比较关键的过程就是去解析图纸数据并转换为 Json 格式,前面 PLM 接口负责解析数据,那么该如何将这些数据转换为 Json 呢?</p><h4 id="官方样例"><a href="#官方样例" class="headerlink" title="官方样例"></a>官方样例</h4><p>查阅官网的 <a href="https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm">Custom JsonConverter</a>,给出了一个比较完整的示例:</p><figure class="highlight c#"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">KeysJsonConverter</span> : <span class="title">JsonConverter</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">readonly</span> Type[] _types;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">KeysJsonConverter</span>(<span class="params"><span class="keyword">params</span> Type[] types</span>)</span></span><br><span class="line"> {</span><br><span class="line"> _types = types;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">WriteJson</span>(<span class="params">JsonWriter writer, <span class="built_in">object</span> <span class="keyword">value</span>, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> JToken t = JToken.FromObject(<span class="keyword">value</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (t.Type != JTokenType.Object)</span><br><span class="line"> {</span><br><span class="line"> t.WriteTo(writer);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> JObject o = (JObject)t;</span><br><span class="line"> IList<<span class="built_in">string</span>> propertyNames = o.Properties().Select(p => p.Name).ToList();</span><br><span class="line"></span><br><span class="line"> o.AddFirst(<span class="keyword">new</span> JProperty(<span class="string">"Keys"</span>, <span class="keyword">new</span> JArray(propertyNames)));</span><br><span class="line"></span><br><span class="line"> o.WriteTo(writer);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">object</span> <span class="title">ReadJson</span>(<span class="params">JsonReader reader, Type objectType, <span class="built_in">object</span> existingValue, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException(<span class="string">"Unnecessary because CanRead is false. The type will skip the converter."</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> CanRead</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">get</span> { <span class="keyword">return</span> <span class="literal">false</span>; }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> <span class="title">CanConvert</span>(<span class="params">Type objectType</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> _types.Any(t => t == objectType);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Employee</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">string</span> FirstName { <span class="keyword">get</span>; <span class="keyword">set</span>; }</span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">string</span> LastName { <span class="keyword">get</span>; <span class="keyword">set</span>; }</span><br><span class="line"> <span class="keyword">public</span> IList<<span class="built_in">string</span>> Roles { <span class="keyword">get</span>; <span class="keyword">set</span>; }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><figure class="highlight c#"><table><tbody><tr><td class="code"><pre><span class="line">Employee employee = <span class="keyword">new</span> Employee</span><br><span class="line">{</span><br><span class="line"> FirstName = <span class="string">"James"</span>,</span><br><span class="line"> LastName = <span class="string">"Newton-King"</span>,</span><br><span class="line"> Roles = <span class="keyword">new</span> List<<span class="built_in">string</span>></span><br><span class="line"> {</span><br><span class="line"> <span class="string">"Admin"</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="built_in">string</span> json = JsonConvert.SerializeObject(employee, Formatting.Indented, <span class="keyword">new</span> KeysJsonConverter(<span class="keyword">typeof</span>(Employee)));</span><br><span class="line"></span><br><span class="line">Console.WriteLine(json);</span><br><span class="line"><span class="comment">// {</span></span><br><span class="line"><span class="comment">// "Keys": [</span></span><br><span class="line"><span class="comment">// "FirstName",</span></span><br><span class="line"><span class="comment">// "LastName",</span></span><br><span class="line"><span class="comment">// "Roles"</span></span><br><span class="line"><span class="comment">// ],</span></span><br><span class="line"><span class="comment">// "FirstName": "James",</span></span><br><span class="line"><span class="comment">// "LastName": "Newton-King",</span></span><br><span class="line"><span class="comment">// "Roles": [</span></span><br><span class="line"><span class="comment">// "Admin"</span></span><br><span class="line"><span class="comment">// ]</span></span><br><span class="line"><span class="comment">// }</span></span><br><span class="line"></span><br><span class="line">Employee newEmployee = JsonConvert.DeserializeObject<Employee>(json, <span class="keyword">new</span> KeysJsonConverter(<span class="keyword">typeof</span>(Employee)));</span><br><span class="line"></span><br><span class="line">Console.WriteLine(newEmployee.FirstName);</span><br><span class="line"><span class="comment">// James</span></span><br></pre></td></tr></tbody></table></figure><h4 id="抽象转换器类"><a href="#抽象转换器类" class="headerlink" title="抽象转换器类"></a>抽象转换器类</h4><p>首先继承 <code>JsonConverter</code> 构建抽象类 <code>AbstractJsonConverter</code>。该类主要用于实现 <code>JsonConverter</code> 的方法并留下 <code>RewriteWriter()</code> 作为真正重写转换方法的入口:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AbstractJsonConverter</span> : <span class="title">JsonConverter</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">readonly</span> Type _type;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AbstractJsonConverter</span>(<span class="params">Type type</span>)</span></span><br><span class="line"> {</span><br><span class="line"> _type = type;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">WriteJson</span>(<span class="params">JsonWriter writer, <span class="built_in">object</span> <span class="keyword">value</span>, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (!(<span class="keyword">value</span> <span class="keyword">is</span> IDatabase db))</span><br><span class="line"> {</span><br><span class="line"> writer.WriteNull();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> RewriteWriter(writer, db, serializer);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">object</span> <span class="title">ReadJson</span>(<span class="params">JsonReader reader, Type objectType, <span class="built_in">object</span> existingValue, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException(<span class="string">"Unnecessary because CanRead is false. The type will skip the converter."</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> CanRead => <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> CanWrite => <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> <span class="title">CanConvert</span>(<span class="params">Type objectType</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">typeof</span>(IDatabase) == _type;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">abstract</span> <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">RewriteWriter</span>(<span class="params">JsonWriter writer, IDatabase db, JsonSerializer serializer</span>)</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在这个过程中 <code>CanConvert</code> 方法的实现遇到了困难,因为该项目以 <code>.exe</code> 可执行程序为载体,所以调用诸如 <code>IDatabase</code> 这样的类型时获取到的 <code>type</code> 是COM组件而非具体的类型。</p><p>为了获取真正的类型我尝试过使用原生的类型获取方法、反射获取方法以及 VisualBasic 的内置方法,但均以失败告终,只好以官方示例为准构造时手动传入类型做判断。</p><h4 id="维拓转换器类"><a href="#维拓转换器类" class="headerlink" title="维拓转换器类"></a>维拓转换器类</h4><p>真正的维拓转换器类我们只需要关心如何实现 <code>RewriteWriter()</code> 即可:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">WeiTuoDBJsonConverter</span> : <span class="title">AbstractJsonConverter</span></span><br><span class="line">{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">WeiTuoDBJsonConverter</span>(<span class="params">Type type</span>) : <span class="title">base</span>(<span class="params">type</span>)</span> { }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">RewriteWriter</span>(<span class="params">JsonWriter writer, IDatabase db, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> writer.WriteStartObject();</span><br><span class="line"> writer.WritePropertyName(dataText);</span><br><span class="line"> WriteDataBaseJson(writer, db, serializer);</span><br><span class="line"></span><br><span class="line"> writer.WriteEndObject();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">WriteDataBaseJson</span>(<span class="params">JsonWriter writer, IDatabase db, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (db <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line"> {</span><br><span class="line"> writer.WriteNull();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> writer.WriteStartArray();</span><br><span class="line"> <span class="comment">// 解析图纸</span></span><br><span class="line"> <span class="keyword">foreach</span> (<span class="keyword">var</span> paperName <span class="keyword">in</span> db.PaperNames)</span><br><span class="line"> {</span><br><span class="line"> IPaper paper = db[paperName];</span><br><span class="line"></span><br><span class="line"> writer.WriteStartObject();</span><br><span class="line"> writer.WritePropertyName(paperName);</span><br><span class="line"> writer.WriteStartObject();</span><br><span class="line"></span><br><span class="line"> WritePaperJson(writer, paper, serializer);</span><br><span class="line"></span><br><span class="line"> writer.WriteEndObject();</span><br><span class="line"> writer.WriteEndObject();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> writer.WriteEndArray();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">WritePaperJson</span>(<span class="params">JsonWriter writer, IPaper paper, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (paper <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line"> {</span><br><span class="line"> writer.WriteNull();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> resolver = serializer.ContractResolver <span class="keyword">as</span> DefaultContractResolver;</span><br><span class="line"> </span><br><span class="line"> ITitle title = paper?.Title;</span><br><span class="line"> writer.WritePropertyName(resolver == <span class="literal">null</span></span><br><span class="line"> ? <span class="keyword">nameof</span>(title)</span><br><span class="line"> : resolver.GetResolvedPropertyName(<span class="keyword">nameof</span>(title)));</span><br><span class="line"> WriteTitleJson(writer, title);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 解析明细表</span></span><br><span class="line"> IBom bom = paper?.Bom;</span><br><span class="line"> writer.WritePropertyName(resolver == <span class="literal">null</span></span><br><span class="line"> ? <span class="keyword">nameof</span>(bom)</span><br><span class="line"> : resolver.GetResolvedPropertyName(<span class="keyword">nameof</span>(bom)));</span><br><span class="line"> writer.WriteStartArray();</span><br><span class="line"> WriteBomJson(writer, bom);</span><br><span class="line"> writer.WriteEndArray();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 解析图框</span></span><br><span class="line"> IFrame frame = paper?.Frame;</span><br><span class="line"> writer.WritePropertyName(resolver == <span class="literal">null</span></span><br><span class="line"> ? <span class="keyword">nameof</span>(frame)</span><br><span class="line"> : resolver.GetResolvedPropertyName(<span class="keyword">nameof</span>(frame)));</span><br><span class="line"> WriteFrameJson(writer, frame, serializer);</span><br><span class="line"></span><br><span class="line"> ......</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">WriteTitleJson</span>(<span class="params">JsonWriter writer, ITitle title</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (title <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line"> {</span><br><span class="line"> writer.WriteNull();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> writer.WriteStartObject();</span><br><span class="line"> <span class="keyword">foreach</span> (<span class="built_in">string</span> name <span class="keyword">in</span> title.Names)</span><br><span class="line"> {</span><br><span class="line"> writer.WritePropertyName(name);</span><br><span class="line"> writer.WriteValue(title[name]);</span><br><span class="line"> }</span><br><span class="line"> writer.WriteEndObject();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">WriteBomJson</span>(<span class="params">JsonWriter writer, IBom bom</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (bom <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line"> {</span><br><span class="line"> writer.WriteNull();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">foreach</span> (<span class="keyword">var</span> serialNumber <span class="keyword">in</span> bom.SerialNumbers)</span><br><span class="line"> {</span><br><span class="line"> IBomRow bomRow = bom[serialNumber];</span><br><span class="line"></span><br><span class="line"> writer.WriteStartObject();</span><br><span class="line"> <span class="keyword">foreach</span> (<span class="keyword">var</span> name <span class="keyword">in</span> bomRow.Names)</span><br><span class="line"> {</span><br><span class="line"> writer.WritePropertyName(name);</span><br><span class="line"> writer.WriteValue(bomRow[name]);</span><br><span class="line"> }</span><br><span class="line"> writer.WriteEndObject();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">WriteFrameJson</span>(<span class="params">JsonWriter writer, IFrame frame, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (frame <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line"> {</span><br><span class="line"> writer.WriteNull();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> writer.WriteStartObject();</span><br><span class="line"> ......</span><br><span class="line"> writer.WriteEndObject();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="解析命令使用"><a href="#解析命令使用" class="headerlink" title="解析命令使用"></a>解析命令使用</h4><p>最后使用上就非常简单了,直接在序列化 <code>SerializeObject()</code> 中传入我们定制的转换器即可:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">return</span> JsonConvert.SerializeObject(db, <span class="keyword">new</span> JsonSerializerSettings {</span><br><span class="line"> Formatting = Formatting.Indented,</span><br><span class="line"> ContractResolver = <span class="keyword">new</span> CamelCasePropertyNamesContractResolver(),</span><br><span class="line"> Converters = <span class="keyword">new</span> List<JsonConverter> { <span class="keyword">new</span> WeiTuoDBJsonConverter(<span class="keyword">typeof</span>(IDatabase)) }</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Formatting.Indented</code> 代表换行</li><li><code>CamelCasePropertyNamesContractResolver()</code> 代表属性为驼峰命名法</li></ul><h3 id="转换器优化"><a href="#转换器优化" class="headerlink" title="转换器优化"></a>转换器优化</h3><p>虽然可以看到这种方式能够转换,但其实是手动实现解析的,我们真正希望的是可以自动识别并获取相应的转换器,完全通过成员关系序列化对象。</p><p>可能这种说法有些抽象,我还是以刚才的例子来讲。<code>WeiTuoDBJsonConverter</code> 可以解析 <code>IDatabase</code>,但如果后期需要单独解析 <code>ITitle</code> 或者 <code>IBom</code> 则需要重新实现对应的转换器类,现有的代码所有的解析都是作为函数的形式(如 <code>WriteFrameJson()</code>)放在转换器类中,不够灵活并且可维护性差。我们希望的是每个类都有对应的转换器类,然后需要的时候将这些转换器进行组合。</p><h4 id="具体转换器类"><a href="#具体转换器类" class="headerlink" title="具体转换器类"></a>具体转换器类</h4><p>下面就以明细表为例来讲讲如何通过逐级实现转换器来达到目标。首先我们先梳理一下逻辑关系:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">IFrame</span><br><span class="line"> |__ IBom</span><br><span class="line"> |__ IBomRow</span><br></pre></td></tr></tbody></table></figure><p>如图所示,图框对象 <code>IFrame</code> 包含了 <code>IBom</code> 明细表对象,而 <code>IBom</code> 又包含了若干 <code>IBomRow</code> 明细表行对象。那么首先我们从最底层的 <code>IBomRow</code> 开始实现 <code>BomRowConverter</code>:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"><see cref="IBomRow"/></span> data converter for <span class="doctag"><see cref="Newtonsoft.Json"/></span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BomRowConverter</span> : <span class="title">JsonConverter</span><<span class="title">IBomRow</span>></span><br><span class="line">{</span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> CanRead => <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> CanWrite => <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">WriteJson</span>(<span class="params">JsonWriter writer, IBomRow bomRow, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (bomRow <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line"> {</span><br><span class="line"> writer.WriteNull();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> writer.WriteStartObject();</span><br><span class="line"> <span class="keyword">foreach</span> (<span class="built_in">string</span> name <span class="keyword">in</span> bomRow.Names)</span><br><span class="line"> {</span><br><span class="line"> writer.WritePropertyName(name);</span><br><span class="line"> writer.WriteValue(bomRow[name]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> writer.WriteEndObject();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> IBomRow <span class="title">ReadJson</span>(<span class="params">JsonReader reader, Type objectType, IBomRow existingValue,</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="built_in">bool</span> hasExistingValue, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>一个简单的字典结构,遍历就可以序列化完成。<code>BomConverter</code> 的实现则略有不同:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"><see cref="IBom"/></span> data converter for <span class="doctag"><see cref="Newtonsoft.Json"/></span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BomConverter</span> : <span class="title">JsonConverter</span><<span class="title">IBom</span>></span><br><span class="line">{</span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> CanRead => <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> CanWrite => <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">WriteJson</span>(<span class="params">JsonWriter writer, IBom bom, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (bom <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line"> {</span><br><span class="line"> writer.WriteNull();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> writer.WriteStartArray();</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">int</span> r = <span class="number">0</span>; r < bom.RowCount; r++)</span><br><span class="line"> serializer.Serialize(writer, bom[r]);</span><br><span class="line"> writer.WriteEndArray();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> IBom <span class="title">ReadJson</span>(<span class="params">JsonReader reader, Type objectType, IBom existingValue, <span class="built_in">bool</span> hasExistingValue,</span></span></span><br><span class="line"><span class="params"><span class="function"> JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>可以看到对于已有转换器的对象我们摒弃了先前的 <code>writer</code>,转而通过 <code>serializer.Serialize()</code> 的方式序列化 <code>IBomRow</code> 对象,<code>FrameConverter</code> 同理:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"><see cref="IFrame"/></span> data converter for <span class="doctag"><see cref="Newtonsoft.Json"/></span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">FrameConverter</span> : <span class="title">JsonConverter</span><<span class="title">IFrame</span>></span><br><span class="line">{</span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> CanRead => <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> CanWrite => <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">WriteJson</span>(<span class="params">JsonWriter writer, IFrame frame, JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (frame <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line"> {</span><br><span class="line"> writer.WriteNull();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> properties = <span class="keyword">typeof</span>(IFrame).GetProperties(BindingFlags.Instance | BindingFlags.Public);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> resolver = serializer.ContractResolver <span class="keyword">as</span> DefaultContractResolver;</span><br><span class="line"></span><br><span class="line"> writer.WriteStartObject();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">foreach</span> (PropertyInfo propertyInfo <span class="keyword">in</span> properties)</span><br><span class="line"> {</span><br><span class="line"> writer.WritePropertyName(resolver?.GetResolvedPropertyName(propertyInfo.Name) ?? propertyInfo.Name);</span><br><span class="line"> writer.WriteValue(propertyInfo.GetValue(frame));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> writer.WriteEndObject();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><inheritdoc /></span></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> IFrame <span class="title">ReadJson</span>(<span class="params">JsonReader reader, Type objectType, IFrame existingValue, <span class="built_in">bool</span> hasExistingValue,</span></span></span><br><span class="line"><span class="params"><span class="function"> JsonSerializer serializer</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>通过反射技术获取属性,遍历属性来完成对子属性的定制序列化。</p><h4 id="静态方法输出"><a href="#静态方法输出" class="headerlink" title="静态方法输出"></a>静态方法输出</h4><p>后面的事情就水到渠成了,只需要将相应的转换器放给对应的打印函数即可:</p><figure class="highlight csharp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> Structure data based on JSON extension class</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">StructureDataExtension</span></span><br><span class="line">{</span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> Can convert the interface type with one of converter in <span class="doctag"><see cref="JsonSerializerSettings.Converters"/></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="interfaceType"></span>The interface type of <span class="doctag"><see cref="AcmSymbb2"/></span><span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="settings"></span>The <span class="doctag"><see cref="JsonSerializerSettings"/></span> object or null<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><returns></span>True if can convert, otherwise false<span class="doctag"></returns></span></span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">CanConvert</span>(<span class="params">Type interfaceType, JsonSerializerSettings settings</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> settings != <span class="literal">null</span> && settings.Converters.Any(converter => converter.CanConvert(interfaceType));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> Serialize <span class="doctag"><see cref="AcmSymbb2"/></span> interface object</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="obj"></span>Interface object<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="settings"></span><span class="doctag"><see cref="JsonSerializerSettings"/></span> object<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="types"></span>Interface type and converter type list<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><returns></span>JSON string<span class="doctag"></returns></span></span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">string</span> <span class="title">SerializeObject</span>(<span class="params"><span class="built_in">object</span> obj, JsonSerializerSettings settings,</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="keyword">params</span> (Type InterfaceType, Type ConverterType</span>)[] types)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// priority: user defined > user default settings > builtin</span></span><br><span class="line"> settings = settings ?? <span class="keyword">new</span> JsonSerializerSettings();</span><br><span class="line"> <span class="keyword">var</span> defaultSettings = JsonConvert.DefaultSettings?.Invoke();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">foreach</span> ((Type interfaceType, Type converterType) <span class="keyword">in</span> types)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (CanConvert(interfaceType, settings) || CanConvert(interfaceType, defaultSettings))</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> settings.Converters.Add((JsonConverter)Activator.CreateInstance(converterType));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> JsonConvert.SerializeObject(obj, settings);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><see cref="IBomRow"/></span> object dump to JSON string</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="bomRow"></span><span class="doctag"><see cref="IBomRow"/></span> object<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="settings"></span><span class="doctag"><see cref="JsonSerializerSettings"/></span> object<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><returns></span>JSON string<span class="doctag"></returns></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><exception cref="ArgumentNullException"></span><span class="doctag"></exception></span></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">string</span> <span class="title">Dump</span>(<span class="params"><span class="keyword">this</span> IBomRow bomRow, JsonSerializerSettings settings = <span class="literal">null</span></span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (bomRow == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException(<span class="keyword">nameof</span>(bomRow));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> SerializeObject(bomRow, settings, (<span class="keyword">typeof</span>(IBomRow), <span class="keyword">typeof</span>(BomRowConverter)));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><see cref="IBom"/></span> object dump to JSON string</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="bom"></span><span class="doctag"><see cref="IBom"/></span> object<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="settings"></span><span class="doctag"><see cref="JsonSerializerSettings"/></span> object<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><returns></span>JSON string<span class="doctag"></returns></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><exception cref="ArgumentNullException"></span><span class="doctag"></exception></span></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">string</span> <span class="title">Dump</span>(<span class="params"><span class="keyword">this</span> IBom bom, JsonSerializerSettings settings = <span class="literal">null</span></span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (bom == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException(<span class="keyword">nameof</span>(bom));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> SerializeObject(bom, settings, (<span class="keyword">typeof</span>(IBom), <span class="keyword">typeof</span>(BomConverter)),</span><br><span class="line"> (<span class="keyword">typeof</span>(IBomRow), <span class="keyword">typeof</span>(BomRowConverter)));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><see cref="IFrame"/></span> object dump to JSON string</span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"></summary></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="frame"></span><span class="doctag"><see cref="IFrame"/></span> object<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><param name="settings"></span><span class="doctag"><see cref="JsonSerializerSettings"/></span> object<span class="doctag"></param></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><returns></span>JSON string<span class="doctag"></returns></span></span></span><br><span class="line"> <span class="comment"><span class="doctag">///</span> <span class="doctag"><exception cref="ArgumentNullException"></span><span class="doctag"></exception></span></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">string</span> <span class="title">Dump</span>(<span class="params"><span class="keyword">this</span> IFrame frame, JsonSerializerSettings settings = <span class="literal">null</span></span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (frame == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException(<span class="keyword">nameof</span>(frame));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> SerializeObject(frame, settings, (<span class="keyword">typeof</span>(IFrame), <span class="keyword">typeof</span>(FrameConverter)));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里还需要考虑一下 <code>JsonSerializerSettings</code> 的优先级,用户 > 定制默认 > 系统默认,如果已经包含了可以解析的转换器就没有必要再放入相应的转换器了。</p></body></html>]]></content>
<summary type="html"><p><a href="https://www.behance.net/gallery/116635445/Becoming-Someone-New-1-5-by-James-Lipnickas-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR2/GSTAR2.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>维拓标准接口的开发也基本完成了,这也是我首次用 C# 完成定制项目。之前 Unity 学的很多东西都忘掉了,再捡起来用还是有些吃力,所以写了这篇博客总结一下。</p></summary>
<category term="CAD二次开发 (GStarCAD ObjectARX Dev)" scheme="https://yousazoe.top/categories/CAD%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91-GStarCAD-ObjectARX-Dev/"/>
<category term="C#" scheme="https://yousazoe.top/tags/C/"/>
</entry>
<entry>
<title>哪些变量会自动初始化?</title>
<link href="https://yousazoe.top/archives/62e0ea85.html"/>
<id>https://yousazoe.top/archives/62e0ea85.html</id>
<published>2023-02-19T09:18:03.000Z</published>
<updated>2025-01-03T06:07:21.684Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://www.behance.net/gallery/142965367/Good-Days"><img data-src="https://img.yousazoe.top/uPic/img/blog/CPP8/CPP8.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>在 C 语言中的全局变量和静态变量都是会自动初始化为 0,堆和栈中的局部变量不会初始化而拥有不可预测的值。 C++ 保证了所有对象与对象成员都会初始化,但其中基本数据类型的初始化还得依赖于构造函数。 下文来详细探讨 C 风格的”默认初始化”行为,以及 C++ 中成员变量的初始化规则。</p><span id="more"></span><h3 id="初始化的语法"><a href="#初始化的语法" class="headerlink" title="初始化的语法"></a>初始化的语法</h3><p>很多人至今不知道 C++ 中如何正确地初始化一个变量,我们首先来解决语法的问题。 C语言中在声明时用 <code>=</code> 即可完成初始化操作。但我们偏向于使用 C++ 风格(本文中均指面向对象程序设计风格)来初始化内置类型:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// C 风格</span></span><br><span class="line"><span class="type">int</span> i = <span class="number">3</span>;</span><br><span class="line"><span class="type">int</span> arr[] = {<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>};</span><br><span class="line"></span><br><span class="line"><span class="comment">// C++ 风格</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">i</span><span class="params">(<span class="number">3</span>)</span></span>;</span><br><span class="line"><span class="type">int</span> i = <span class="built_in">int</span>(<span class="number">3</span>);</span><br><span class="line"><span class="type">int</span> *p = <span class="keyword">new</span> <span class="built_in">int</span>(<span class="number">3</span>);</span><br><span class="line"><span class="type">int</span>* arr = <span class="keyword">new</span> <span class="type">int</span>[<span class="number">3</span>] {<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>};</span><br></pre></td></tr></tbody></table></figure><p>在 C 语言中 <code>int a;</code> 表示声明了整型 <code>a</code> 但未初始化,而 C++ 中的对象总是会被初始化的,无论是否写了圆括号或者是否写了参数列表,例如:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">int</span> basic_var; <span class="comment">// 未初始化:应用"默认初始化"机制</span></span><br><span class="line">CPerson person; <span class="comment">// 初始化:以空的参数列表调用构造函数</span></span><br></pre></td></tr></tbody></table></figure><h3 id="默认初始化规则"><a href="#默认初始化规则" class="headerlink" title="默认初始化规则"></a>默认初始化规则</h3><p>定义基本数据类型变量(单个值、数组)的同时可以指定初始值,如果未指定 C++ 会去执行默认初始化(default-initialization)。 那么什么是”默认初始化”呢?</p><p>栈中的变量(函数体中的自动变量)和堆中的变量(动态内存)会保有不确定的值;<br>全局变量和静态变量(包括局部静态变量)会初始化为零。<br>C++11: If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value. Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2.</p><p>所以函数体中的变量定义是这样的规则:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">int</span> i; <span class="comment">// 不确定值</span></span><br><span class="line"><span class="type">int</span> i = <span class="built_in">int</span>(); <span class="comment">// 0</span></span><br><span class="line"><span class="type">int</span> *p = <span class="keyword">new</span> <span class="type">int</span>; <span class="comment">// 不确定值</span></span><br><span class="line"><span class="type">int</span> *p = <span class="keyword">new</span> <span class="built_in">int</span>(); <span class="comment">// 0</span></span><br></pre></td></tr></tbody></table></figure><h3 id="静态和全局变量的初始化"><a href="#静态和全局变量的初始化" class="headerlink" title="静态和全局变量的初始化"></a>静态和全局变量的初始化</h3><p>未初始化的和初始化为零的静态/全局变量编译器是同样对待的,把它们存储在进程的BSS段(这是全零的一段内存空间)中。所以它们会被”默认初始化”为零。</p><p>来看例子:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">int</span> g_var;</span><br><span class="line"><span class="type">int</span> *g_pointer;</span><br><span class="line"><span class="type">static</span> <span class="type">int</span> g_static;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="type">int</span> l_var;</span><br><span class="line"> <span class="type">int</span> *l_pointer;</span><br><span class="line"> <span class="type">static</span> <span class="type">int</span> l_static;</span><br><span class="line"></span><br><span class="line"> cout<<g_var<<endl<<g_pointer<<endl<<g_static<<endl;</span><br><span class="line"> cout<<l_var<<endl<<l_pointer<<endl<<l_static<<endl;</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p>输出:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">0 // 全局变量</span><br><span class="line">0x0 // 全局指针 </span><br><span class="line">0 // 全局静态变量</span><br><span class="line">32767 // 局部变量</span><br><span class="line">0x7fff510cfa68 // 局部指针</span><br><span class="line">0 // 局部静态变量</span><br></pre></td></tr></tbody></table></figure><p>动态内存中的变量在上述代码中没有给出,它们和局部变量(自动变量)具有相同的”默认初始化”行为。</p><h3 id="成员变量的初始化"><a href="#成员变量的初始化" class="headerlink" title="成员变量的初始化"></a>成员变量的初始化</h3><p>成员变量分为成员对象和内置类型成员,其中成员对象总是会被初始化的。而我们要做的就是在构造函数中初始化其中的内置类型成员。 还是先来看看内置类型的成员的”默认初始化”行为:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="type">int</span> v;</span><br><span class="line">};</span><br><span class="line">A g_var;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> A l_var;</span><br><span class="line"> <span class="type">static</span> A l_static;</span><br><span class="line"> cout<<g_var.v<<<span class="string">' '</span><<l_var.v<<<span class="string">' '</span><<l_static.v<<endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">0 2407223 0</span><br></pre></td></tr></tbody></table></figure><p>可见内置类型的成员变量的”默认初始化”行为取决于所在对象的存储类型,而存储类型对应的默认初始化规则是不变的。 所以为了避免不确定的初值,通常会在构造函数中初始化所有内置类型的成员。Effective C++: Item 4一文讨论了如何正确地在构造函数中初始化数据成员。 这里就不展开了,直接给出一个正确的初始化写法:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="type">int</span> v;</span><br><span class="line"> <span class="built_in">A</span>(): <span class="built_in">v</span>(<span class="number">0</span>);</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><h3 id="封闭类嵌套成员的初始化"><a href="#封闭类嵌套成员的初始化" class="headerlink" title="封闭类嵌套成员的初始化"></a>封闭类嵌套成员的初始化</h3><p>再来探讨一下当对象聚合发生时成员变量的”默认初始化”行为,同样还是只关注于基本数据类型的成员。</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="type">int</span> v;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="type">int</span> v;</span><br><span class="line"> A a;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">B g_var;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> B l_var;</span><br><span class="line"> cout<<g_var.v<<<span class="string">' '</span><<g_var.a.v<<endl;</span><br><span class="line"> cout<<l_var.v<<<span class="string">' '</span><<l_var.a.v<<endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">0 0</span><br><span class="line">43224321 -1610612736</span><br></pre></td></tr></tbody></table></figure><p>规则还是是一样的,默认初始化行为取决于它所属对象的存储类型。 封闭类(Enclosing)中成员对象的内置类型成员变量的”默认初始化”行为取决于当前封闭类对象的存储类型,而存储类型对应的默认初始化规则仍然是不变的。</p></body></html>]]></content>
<summary type="html"><p><a href="https://www.behance.net/gallery/142965367/Good-Days"><img data-src="https://img.yousazoe.top/uPic/img/blog/CPP8/CPP8.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>在 C 语言中的全局变量和静态变量都是会自动初始化为 0,堆和栈中的局部变量不会初始化而拥有不可预测的值。 C++ 保证了所有对象与对象成员都会初始化,但其中基本数据类型的初始化还得依赖于构造函数。 下文来详细探讨 C 风格的”默认初始化”行为,以及 C++ 中成员变量的初始化规则。</p></summary>
<category term="编程技巧 (Coding Skill)" scheme="https://yousazoe.top/categories/%E7%BC%96%E7%A8%8B%E6%8A%80%E5%B7%A7-Coding-Skill/"/>
<category term="Cpp" scheme="https://yousazoe.top/tags/Cpp/"/>
</entry>
<entry>
<title>中车大同旧图纸转换定制项目总结</title>
<link href="https://yousazoe.top/archives/7671ec9e.html"/>
<id>https://yousazoe.top/archives/7671ec9e.html</id>
<published>2023-02-02T14:36:22.000Z</published>
<updated>2025-01-03T06:07:21.703Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://www.behance.net/gallery/116635445/Becoming-Someone-New-1-5-by-James-Lipnickas-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR1/GSTAR1.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>中车大同的旧图纸转换功能的定制开发告一段落,这段时间和周工学到了很多有用的知识,特此记录这个项目学到的编程技巧和项目开发经验。</p><span id="more"></span><h3 id="容器越界问题"><a href="#容器越界问题" class="headerlink" title="容器越界问题"></a>容器越界问题</h3><p>在这个项目中我有很多时候对数组越界问题并不敏感,导致在一些情况下程序直接崩溃了,下面举一些具体的例子:</p><figure class="highlight diff"><table><tbody><tr><td class="code"><pre><span class="line">bool parseDTTitleBar(const CString & filePath)</span><br><span class="line">{</span><br><span class="line">const auto titleBar = parseFile(filePath, titleBarSection[0]);</span><br><span class="line">const auto splitTitleBar = splitProfileString(titleBar);</span><br><span class="line">for (auto& item : splitTitleBar)</span><br><span class="line">{</span><br><span class="line">auto vec = item.second;</span><br><span class="line"><span class="addition">+if (!vec.empty() && vec.size() == 10)</span></span><br><span class="line">titleBarDefItems_.emplace_back(item.first, vec[0], vec[1], _ttof(vec[5]), _ttof(vec[6]), _ttof(vec[7]), vec[4], _ttof(vec[2]), _ttof(vec[3]), vec[8], vec[9]);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">if (titleBarDefItems_.empty())</span><br><span class="line">return false;</span><br><span class="line"></span><br><span class="line">return true;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>本例中我去实例化一个对象,但使用的方式是直接选取 <code>vec</code> 元素没有增加数量判断。倘若 <code>vec</code> 只有 6 个元素但代码中却取到了 <code>vec[9]</code> 就会导致崩溃。</p><h3 id="前向声明"><a href="#前向声明" class="headerlink" title="前向声明"></a>前向声明</h3><p>前向声明是我编程时忽略掉的一个细节,在之前学校里写的代码只要编译能过去就不考虑这些问题了,但工作中需要注意效率。</p><figure class="highlight diff"><table><tbody><tr><td class="code"><pre><span class="line">#pragma once</span><br><span class="line"></span><br><span class="line">#include "jsoncpp/value.h"</span><br><span class="line">#include "EditableListCtrl.h"</span><br><span class="line"><span class="deletion">-#include "AbstractDetailsCreator.h"</span></span><br><span class="line"></span><br><span class="line">// DlgCvtMain 对话框</span><br><span class="line"></span><br><span class="line">#include <vector></span><br><span class="line"></span><br><span class="line">class DataRow;</span><br><span class="line">class AbstractPaperSelector;</span><br><span class="line">class PaperCreator;</span><br><span class="line">class PaperSizeDefinition;</span><br><span class="line"><span class="addition">+class AbstractDetailsCreator;</span></span><br></pre></td></tr></tbody></table></figure><p><code>#include</code> 所做的就是将整个代码复制过来,而这里我们并不关心 <code>AbstractDetailsCreator</code> 具体如何是什么,只需要知道有这样一个类型存在即可,这时就可以用前置声明而非引入整个头文件。</p><p>从代码编写的优雅程度来讲这样也会让阅读代码更加容易,不会因为引入过多无关头文件而一头雾水。前置声明最大的好处在于避免编译膨胀,一个优秀的 CPP 代码应该只包含它的最小代码集合。</p><h3 id="实体的-XData-读取"><a href="#实体的-XData-读取" class="headerlink" title="实体的 XData 读取"></a>实体的 XData 读取</h3><p>自己经常忘记如何读取实体的 XData,特此记录:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function">AcDbObjectPointer<AcDbText> <span class="title">text</span><span class="params">(textId)</span></span>;</span><br><span class="line"><span class="keyword">if</span> (text.<span class="built_in">openStatus</span>() != eOk) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="keyword">auto</span> xData = text-><span class="built_in">xDataPtr</span>(TitleTextAppName);</span><br><span class="line"><span class="keyword">if</span> (xData.<span class="built_in">isNull</span>() || xData-><span class="built_in">next</span>().<span class="built_in">isNull</span>() || xData-><span class="built_in">next</span>()-><span class="built_in">restype</span>() != OdResBuf::kDxfXdAsciiString)</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">CString itemName = xData-><span class="built_in">next</span>()-><span class="built_in">getString</span>();</span><br><span class="line">...</span><br></pre></td></tr></tbody></table></figure><p>获取链表后根据所需类型选择相应 <code>get</code> 函数即可。</p><h3 id="以组为单位的图框定位点平移"><a href="#以组为单位的图框定位点平移" class="headerlink" title="以组为单位的图框定位点平移"></a>以组为单位的图框定位点平移</h3><p>大同希望把我们产品的图框插入点由内框左下点改为外框左下点,需要以组为单位做一下平移操作。思路上还是比较清晰的:</p><ol><li>获取当前图纸中图框所在的组</li><li>以国标为例需要根据装订线有无确定平移所需的偏移量</li><li>遍历组进行平移操作</li></ol><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function">AcDbDictionaryPointer <span class="title">dict</span><span class="params">(acdbCurDwg()->groupDictionaryId(), kForRead)</span></span>;</span><br><span class="line"><span class="keyword">if</span> (dict.<span class="built_in">openStatus</span>() != eOk) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">AcDbObjectPointer<AcDbGroup> paperGroup;</span><br><span class="line"><span class="keyword">if</span> (dict-><span class="built_in">getAt</span>(_T(<span class="string">"PAPERGROUP"</span>), (AcDbGroup*&)paperGroup, kForRead) != Acad::eOk) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="function">AcDbObjectPointer<AcDbGroup> <span class="title">group</span><span class="params">(paperGroup->objectId(), kForRead)</span></span>;</span><br><span class="line"><span class="keyword">if</span> (group.<span class="built_in">openStatus</span>() != eOk) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">AcGeMatrix3d mat;</span><br><span class="line"><span class="type">double</span> xOffset = <span class="number">0.0</span>, yOffset = <span class="number">0.0</span>;</span><br><span class="line"><span class="keyword">if</span> (g_paper.bZhuangding)</span><br><span class="line">{</span><br><span class="line"> xOffset = g_paper.a;</span><br><span class="line"> yOffset = g_paper.c;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> xOffset = yOffset = g_paper.e;</span><br><span class="line"></span><br><span class="line"><span class="function">AcGeVector3d <span class="title">transVec</span><span class="params">(xOffset, yOffset, <span class="number">0</span>)</span></span>;</span><br><span class="line">mat.<span class="built_in">setToTranslation</span>(transVec);</span><br><span class="line"></span><br><span class="line"><span class="function">unique_ptr<AcDbGroupIterator> <span class="title">groupIter</span><span class="params">(group->newIterator())</span></span>;</span><br><span class="line"><span class="keyword">if</span> (groupIter == <span class="literal">nullptr</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (; !groupIter-><span class="built_in">done</span>(); groupIter-><span class="built_in">next</span>())</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// 从组中获取块引用</span></span><br><span class="line"> <span class="function">AcDbObjectPointer<AcDbBlockReference> <span class="title">blkRef</span><span class="params">(groupIter->objectId(), kForWrite)</span></span>;</span><br><span class="line"> <span class="keyword">if</span> (blkRef.<span class="built_in">openStatus</span>() != eOk) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> blkRef-><span class="built_in">transformBy</span>(mat);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="附加栏缩放"><a href="#附加栏缩放" class="headerlink" title="附加栏缩放"></a>附加栏缩放</h3><p>附加栏缩放方式由变换矩阵改为修改实体的缩放因子:</p><figure class="highlight diff"><table><tbody><tr><td class="code"><pre><span class="line">AcDbObjectId idEnt;</span><br><span class="line">if (acdbGetObjectId(idEnt, entIns) == Acad::eOk)</span><br><span class="line">{</span><br><span class="line"> AcDbObjectPointer<AcDbBlockReference> pEntity(idEnt, AcDb::kForWrite);</span><br><span class="line"> if (pEntity.openStatus() == Acad::eOk)</span><br><span class="line"> {</span><br><span class="line"><span class="deletion">- AcGeMatrix3d matrix;</span></span><br><span class="line"><span class="deletion">- matrix.setToScaling(scale);</span></span><br><span class="line"><span class="deletion">- pEntity->transformBy(matrix);</span></span><br><span class="line"></span><br><span class="line"><span class="addition">+ pEntity->setScaleFactors({ scale, scale, scale });</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="重写读取-ini-文件函数"><a href="#重写读取-ini-文件函数" class="headerlink" title="重写读取 .ini 文件函数"></a>重写读取 <code>.ini</code> 文件函数</h3><h4 id="业务场景"><a href="#业务场景" class="headerlink" title="业务场景"></a>业务场景</h4><p>业务场景需要读取下面的 <code>.def</code> 配置文件:</p><figure class="highlight ini"><table><tbody><tr><td class="code"><pre><span class="line"><span class="section">[Info]</span></span><br><span class="line"><span class="attr">bindingEditable</span>=<span class="number">0</span></span><br><span class="line"><span class="attr">bindingDefault</span>=<span class="number">1</span></span><br><span class="line"><span class="attr">bindingAreaWidth</span>=<span class="number">20</span></span><br><span class="line"><span class="attr">bindingEtcAreaWidth</span>=<span class="number">5</span></span><br><span class="line"><span class="attr">notBindingWidth</span>=<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="attr">splitEditable</span>=<span class="number">1</span></span><br><span class="line"><span class="attr">splitDefault</span>=<span class="number">1</span></span><br><span class="line"><span class="attr">splitTextHeight</span>=<span class="number">3.5</span></span><br><span class="line"></span><br><span class="line"><span class="attr">middleEditable</span>=<span class="number">1</span></span><br><span class="line"><span class="attr">middleDefault</span>=<span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="attr">attachBarEditable</span>=<span class="number">0</span></span><br><span class="line"><span class="attr">attachBarDefault</span>=<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="attr">mainTitleBarHeight</span>=<span class="number">63</span></span><br><span class="line"><span class="attr">bomHeaderHeight</span>=<span class="number">9.993</span></span><br><span class="line"><span class="attr">bomTextBlockHeight</span>=<span class="number">7</span></span><br><span class="line"><span class="attr">bomTextBlockExtHeight</span>=<span class="number">10</span></span><br><span class="line"></span><br><span class="line"><span class="section">[DTTitleBar]</span></span><br><span class="line"><span class="attr">main</span>= 主标题栏<span class="comment">; DTTitleBar; 185; 63; IRB; 0; 0; 0; bkt-1en_main.dwg;</span></span><br><span class="line"><span class="attr">lb</span>= 副标题栏签字<span class="comment">; DTTitleBar; 20; 287; ILB; 0; 0; 0; bkt-1en_lb.dwg; A3:0.75,A4:0.5,A4+:0.75</span></span><br><span class="line"><span class="attr">lt</span>= 副标题栏图样代码<span class="comment">; DTTitleBar; 137; 21; ILT; 0; 0; 0; bkt-1_lt.dwg;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[DTTitleBarText]</span></span><br><span class="line"><span class="attr">IRB001</span> = 图样代码<span class="comment">; 5; IRB; -60; 47.5; 0; MC; 120; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB002</span> = 中文名称<span class="comment">; 5; IRB; -85; 33.75; 0; MC; 70; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB003</span> = 中文材料<span class="comment">; 5; IRB; -85; 11.25; 0; MC; 70; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB004_ZhiLiang</span> = 质量<span class="comment">; 4; IRB; -26.5; 27.5; 0; MC; 17; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB005_BiLi</span> = 比例<span class="comment">; 4; IRB; -9; 27.5; 0; MC; 18; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB006</span> = 共张<span class="comment">; 3.5; IRB; -15; 17.5; 0; MC; 30; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB007</span> = 第张<span class="comment">; 3.5; IRB; -40; 17.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB008_TuFu</span> = 图幅<span class="comment">; 4; IRB; -3.5; -2.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB009</span> = 俄文名称<span class="comment">; 5; IRB; -85; 21.25; 0; MC; 70; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB010</span> = 俄文材料<span class="comment">; 5; IRB; -85; 3.75; 0; MC; 70; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB011</span> = 订货号<span class="comment">; 4; IRB; -60; 59; 0; MC; 120; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB012</span> = 识别符号<span class="number">1</span><span class="comment">; 4; IRB; -47.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB013</span> = 识别符号<span class="number">2</span><span class="comment">; 4; IRB; -42.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB014</span> = 识别符号<span class="number">3</span><span class="comment">; 4; IRB; -37.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB015</span> = 复印人员<span class="comment">; 3.5; IRB; -70; -2.5; 0; ML; 30; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ILB001</span> = (左)国家标准登记号<span class="comment">; 4; ILB; 8.5; 12.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;</span></span><br><span class="line"><span class="attr">ILB002</span> = (左)签名和日期<span class="number">1</span><span class="comment">; 4; ILB; 8.5; 42.5; 90; MC; 35; STR; 0; 11; Standard; lb; ;</span></span><br><span class="line"><span class="attr">ILB003</span> = (左)替代正本号<span class="comment">; 4; ILB; 8.5; 72.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;</span></span><br><span class="line"><span class="attr">ILB004</span> = (左)副本登记号<span class="comment">; 4; ILB; 8.5; 97.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;</span></span><br><span class="line"><span class="attr">ILB005</span> = (左)签名和日期<span class="number">2</span><span class="comment">; 4; ILB; 8.5; 127.5; 90; MC; 35; STR; 0; 11; Standard; lb; ;</span></span><br><span class="line"><span class="attr">ILB006</span> = (左)替代文件代号<span class="comment">; 4; ILB; 8.5; 197; 90; MC; 60; STR; 0; 11; Standard; lb; ;</span></span><br><span class="line"><span class="attr">ILB007</span> = (左)相应文件代号<span class="comment">; 4; ILB; 8.5; 257; 90; MC; 60; STR; 0; 11; Standard; lb; ;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ILT001</span> = (上)专利编号<span class="comment">; 3; ILT; 35; -17.5; 0; MC; 70; STR; 0; 11; Standard; lt; ;</span></span><br><span class="line"><span class="attr">ILT002</span> = (上)订货号标记<span class="comment">; 3; ILT; 77; -7; 0; MC; 14; STR; 0; 11; Standard; lt; ;</span></span><br><span class="line"><span class="attr">ILT003</span> = (上)相应文件决议编号和批准年份<span class="comment">; 3; ILT; 110.5; -3.5; 0; MC; 53; STR; 0; 11; Standard; lt; ;</span></span><br><span class="line"><span class="attr">ILT004</span> = (上)本文件决议编号和批准年份<span class="comment">; 3; ILT; 110.5; -10.5; 0; MC; 53; STR; 0; 11; Standard; lt; ;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">IRB101</span> = 更改区域<span class="number">1____</span><span class="comment">; 3; IRB; -195; 37.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB102</span> = 变更序号<span class="number">1</span><span class="comment">; 3; IRB; -181.5; 37.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB103</span> = 变更页码<span class="number">1</span><span class="comment">; 3; IRB; -173; 37.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB104</span> = 通知单号<span class="number">1</span><span class="comment">; 3; IRB; -156.5; 37.5; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB105</span> = 签名<span class="number">1</span><span class="comment">; 3; IRB; -137.5; 37.5; 0; MC; 15; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB106</span> = 日期<span class="number">1</span><span class="comment">; 3; IRB; -125; 37.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">IRB107</span> = 更改区域<span class="number">2____</span><span class="comment">; 3; IRB; -195; 42.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB108</span> = 变更序号<span class="number">2</span><span class="comment">; 3; IRB; -181.5; 42.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB109</span> = 变更页码<span class="number">2</span><span class="comment">; 3; IRB; -173; 42.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB110</span> = 通知单号<span class="number">2</span><span class="comment">; 3; IRB; -156.5; 42.5; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB111</span> = 签名<span class="number">2</span><span class="comment">; 3; IRB; -137.5; 42.5; 0; MC; 15; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB112</span> = 日期<span class="number">2</span><span class="comment">; 3; IRB; -125; 42.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">IRB113</span> = 更改区域<span class="number">3____</span><span class="comment">; 3; IRB; -195; 47.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB114</span> = 变更序号<span class="number">3</span><span class="comment">; 3; IRB; -181.5; 47.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB115</span> = 变更页码<span class="number">3</span><span class="comment">; 3; IRB; -173; 47.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB116</span> = 通知单号<span class="number">3</span><span class="comment">; 3; IRB; -156.5; 47.5; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB117</span> = 签名<span class="number">3</span><span class="comment">; 3; IRB; -137.5; 47.5; 0; MC; 15; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB118</span> = 日期<span class="number">3</span><span class="comment">; 3; IRB; -125; 47.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">IRB119</span> = 更改区域<span class="number">4____</span><span class="comment">; 3; IRB; -195; 52.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB120</span> = 变更序号<span class="number">4</span><span class="comment">; 3; IRB; -181.5; 52.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB121</span> = 变更页码<span class="number">4</span><span class="comment">; 3; IRB; -173; 52.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB122</span> = 通知单号<span class="number">4</span><span class="comment">; 3; IRB; -156.5; 52.5; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB123</span> = 签名<span class="number">4</span><span class="comment">; 3; IRB; -137.5; 52.5; 0; MC; 15; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB124</span> = 日期<span class="number">4</span><span class="comment">; 3; IRB; -125; 52.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">IRB125</span>=设计-文件数量<span class="comment">; 3; IRB; -156.5; 27.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB126</span>=审核-文件数量<span class="comment">; 3; IRB; -156.5; 22.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB127</span>=主任设计-文件数量<span class="comment">; 3; IRB; -156.5; 17.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB128</span>=工艺-文件数量<span class="comment">; 3; IRB; -156.5; 12.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB129</span>=标准化-文件数量<span class="comment">; 3; IRB; -156.5; 7.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="attr">IRB130</span>=批准-文件数量<span class="comment">; 3; IRB; -156.5; 2.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[DTTitleBarTextReference]</span></span><br><span class="line"><span class="attr">tydmfz</span> = 图样代码<span class="comment">; 3; ILT; 35; -7; 180; MC; 70; STR; 0; 11; Standard; lt; ;IRB001</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="section">[DTBOM]</span></span><br><span class="line"><span class="attr">bomHeader</span>= 主明细栏<span class="comment">; DTBOMHeader; 185; 10; IRB; 0; 0; 0; bkt-1en_bh.dwg;</span></span><br><span class="line"><span class="attr">bomTextBlock</span>= 明细文字框<span class="comment">; DTBOMTextBlock; 185; 7; IRB; 0; 0; 0; btb_w185h7.dwg;</span></span><br><span class="line"><span class="attr">bomTextBlockExt</span>= 明细文字框扩展<span class="comment">; DTBOMTextBlock; 185; 10; IRB; 0; 0; 0; btb_w185h10.dwg;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[DTBOMText]</span></span><br><span class="line"><span class="attr">BOM001</span> = 序号<span class="comment">; 3.5; IRB; -178.5; 5; 0; MC; 13; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"><span class="attr">BOM002</span> = 代码<span class="comment">; 3.5; IRB; -171; 5; 0; ML; 25; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"><span class="attr">BOM003</span> = 代号<span class="comment">; 3.5; IRB; -146; 5; 0; ML; 30; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"><span class="attr">BOM004</span> = 名称<span class="comment">; 3.5; IRB; -116; 0; 0; ML; 35; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"><span class="attr">BOM005</span> = 数量<span class="comment">; 3.5; IRB; -81; 5; 0; ML; 8; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"><span class="attr">BOM006</span> = 材料<span class="comment">; 3.5; IRB; -73; 5; 0; ML; 35; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"><span class="attr">BOM007</span> = 单件<span class="comment">; 3.5; IRB; -38; 5; 0; ML; 12; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"><span class="attr">BOM008</span> = 总计<span class="comment">; 3.5; IRB; -26; 5; 0; ML; 12; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"><span class="attr">BOM009</span> = 附注<span class="comment">; 3.5; IRB; -14; 5; 0; ML; 15; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">EXT001</span> = 外文名称<span class="comment">; 3.5; IRB; -116; 100; 0; ML; 35; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"><span class="attr">EXT002</span> = 外文材料<span class="comment">; 3.5; IRB; -73; 100; 0; ML; 35; STR; 0; 11; Standard; bomHeader; ;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[DTAttachBar]</span></span><br><span class="line"></span><br><span class="line"><span class="section">[DTAttachBarText]</span></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><h4 id="ini-文件结构"><a href="#ini-文件结构" class="headerlink" title=".ini 文件结构"></a><code>.ini</code> 文件结构</h4><p>我之前遇到的大部分配置文件的类型都是 <code>.xml</code> 或者 <code>.josn</code>(现在网络端也是这两者使用比较多,属于通用的配置文件类型了),面对 <code>.ini</code> 文件还是比较陌生。 这里引用简书一位博主的 <a href="https://www.jianshu.com/p/7f60e3ee905b">ini文件格式和读取</a>:</p><p>ini 就是英文 “initialization” 的头三个字母的缩写,当然 INI file 的后缀名也不一定是 <code>.ini</code>,也可以是 <code>.cfg</code>,<code>.conf</code> 或者是 <code>.txt</code>。</p><p>ini 文件的格式很简单,最基本的三个要素是:<code>parameters</code>,<code>sections</code> 和 <code>comments</code>。</p><h5 id="parameters"><a href="#parameters" class="headerlink" title="parameters"></a>parameters</h5><p>ini 所包含的最基本的”元素”就是 <code>parameter</code>,每一个 <code>parameter</code> 都有一个 <code>name</code> 和一个 <code>value</code>,如下所示:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">name = value</span><br></pre></td></tr></tbody></table></figure><h5 id="sections"><a href="#sections" class="headerlink" title="sections"></a>sections</h5><p>所有的 <code>parameters</code> 都是以 <code>sections</code> 为单位结合在一起的。所有的 <code>section</code> 名称都是独占一行,并且 <code>sections</code> 名字都被方括号包围着(<code>[ section's name ]</code>)。</p><p>在 <code>section</code> 声明后的所有 <code>parameters</code> 都是属于该 <code>section</code>。对于一个 <code>section</code> 没有明显的结束标志符,一个 <code>section</code> 的开始就是上一个 <code>section</code> 的结束,或者是 end of the file。 <code>section</code> 如下所示:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">[section]</span><br></pre></td></tr></tbody></table></figure><h5 id="comments"><a href="#comments" class="headerlink" title="comments"></a>comments</h5><p>在 ini 文件中注释语句是以分号 <code>;</code> 开始的。所有的所有的注释语句不管多长都是独占一行直到结束的。在分号和行结束符之间的所有内容都是被忽略的。</p><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><p>项目的工具类中有之前写好的读取 <code>.ini</code> 配置文件函数:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">......</span><br><span class="line"><span class="comment">// 从指定配置文件中读取指定段,指定属性的内容</span></span><br><span class="line"><span class="function">CString IM_PUBLIC_FUNCTION_ <span class="title">IM_GetConfigFileValue</span> <span class="params">( CString szFileName , CString szSegName , CString szKeyName , CString szDefault = _T(<span class="string">""</span>) , <span class="type">int</span> nMaxLength = <span class="number">512</span> )</span> </span>;</span><br><span class="line"><span class="comment">// 向指定配置文件中写入指定段,指定属性的内容</span></span><br><span class="line"><span class="function">BOOL IM_PUBLIC_FUNCTION_ <span class="title">IM_SetConfigFileValue</span> <span class="params">( CString szFileName , CString szSegName , CString szKeyName , CString szValue )</span> </span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> WritePrivateProfileInt IM_WritePrivateProfileInt</span></span><br><span class="line"><span class="function">BOOL IM_PUBLIC_FUNCTION_ <span class="title">IM_WritePrivateProfileInt</span> <span class="params">( LPCTSTR lpAppName , LPCTSTR lpKeyName , <span class="type">int</span> nValue , LPCTSTR lpFileName )</span> </span>;</span><br><span class="line"></span><br><span class="line"><span class="function">CString IM_PUBLIC_FUNCTION_ <span class="title">IM_GetPrivateProfileString</span> <span class="params">( LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPCTSTR lpFileName , DWORD lMaxSize = <span class="number">256</span> )</span> </span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> IM_PUBLIC_FUNCTION_ <span class="title">IM_GetPrivateProfileSectionMap</span><span class="params">( CString szAppName, CString szFilePath, map<CString,CString>& mapValue )</span> </span>;</span><br><span class="line"><span class="comment">// 获取配置文件中所有的段名</span></span><br><span class="line"><span class="function"><span class="type">void</span> IM_PUBLIC_FUNCTION_ <span class="title">IM_GetPrivateProfileAppNames</span><span class="params">(<span class="type">const</span> CString &szFilePath, CStringArray &szAppNames)</span></span>;</span><br><span class="line"><span class="function">CString IM_PUBLIC_FUNCTION_ <span class="title">IM_GetSystemSettingProfile</span> <span class="params">()</span> </span>;</span><br><span class="line">......</span><br></pre></td></tr></tbody></table></figure><p>这些函数的实现则是依赖于底层 Windows 提供的 <a href="https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprivateprofilesection">API 接口</a>。</p><p>而在具体实践中,由于业务场景中 <code>.ini</code> 文件的 <code>parameters</code> 的 <code>value</code> 值过长,导致读取的时候出现遗漏的情况(下面以读取 <code>[DTTitleBarText]</code> 为例):</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">std::map<CString, CString> resultMap;</span><br><span class="line"><span class="built_in">IM_GetPrivateProfileSectionMap</span>(<span class="string">L"DTTitleBarText"</span>, filePath, resultMap);</span><br></pre></td></tr></tbody></table></figure><p>得到的 <code>resultMap</code> 则是缺失了一部分:</p><figure class="highlight diff"><table><tbody><tr><td class="code"><pre><span class="line">[DTTitleBarText]</span><br><span class="line">IRB001 = 图样代码; 5; IRB; -60; 47.5; 0; MC; 120; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB002 = 中文名称; 5; IRB; -85; 33.75; 0; MC; 70; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB003 = 中文材料; 5; IRB; -85; 11.25; 0; MC; 70; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB004_ZhiLiang = 质量; 4; IRB; -26.5; 27.5; 0; MC; 17; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB005_BiLi = 比例; 4; IRB; -9; 27.5; 0; MC; 18; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB006 = 共张; 3.5; IRB; -15; 17.5; 0; MC; 30; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB007 = 第张; 3.5; IRB; -40; 17.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB008_TuFu = 图幅; 4; IRB; -3.5; -2.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB009 = 俄文名称; 5; IRB; -85; 21.25; 0; MC; 70; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB010 = 俄文材料; 5; IRB; -85; 3.75; 0; MC; 70; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB011 = 订货号; 4; IRB; -60; 59; 0; MC; 120; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB012 = 识别符号1; 4; IRB; -47.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB013 = 识别符号2; 4; IRB; -42.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB014 = 识别符号3; 4; IRB; -37.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;</span><br><span class="line">IRB015 = 复印人员; 3.5; IRB; -70; -2.5; 0; ML; 30; STR; 0; 11; Standard; main; ;</span><br><span class="line"></span><br><span class="line">ILB001 = (左)国家标准登记号; 4; ILB; 8.5; 12.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;</span><br><span class="line">ILB002 = (左)签名和日期1; 4; ILB; 8.5; 42.5; 90; MC; 35; STR; 0; 11; Standard; lb; ;</span><br><span class="line">ILB003 = (左)替代正本号; 4; ILB; 8.5; 72.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;</span><br><span class="line">ILB004 = (左)副本登记号; 4; ILB; 8.5; 97.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;</span><br><span class="line">ILB005 = (左)签名和日期2; 4; ILB; 8.5; 127.5; 90; MC; 35; STR; 0; 11; Standard; lb; ;</span><br><span class="line">ILB006 = (左)替代文件代号; 4; ILB; 8.5; 197; 90; MC; 60; STR; 0; 11; Standard; lb; ;</span><br><span class="line">ILB007 = (左)相应文件代号; 4; ILB; 8.5; 257; 90; MC; 60; STR; 0; 11; Standard; lb; ;</span><br><span class="line"></span><br><span class="line">ILT001 = (上)专利编号; 3; ILT; 35; -17.5; 0; MC; 70; STR; 0; 11; Standard; lt; ;</span><br><span class="line"><span class="deletion">-ILT002 = (上)订货号标记; 3; ILT; 77; -7; 0; MC; 14; STR; 0; 11; Standard; lt; ;</span></span><br><span class="line"><span class="deletion">-ILT003 = (上)相应文件决议编号和批准年份; 3; ILT; 110.5; -3.5; 0; MC; 53; STR; 0; 11; Standard; lt; ;</span></span><br><span class="line"><span class="deletion">-ILT004 = (上)本文件决议编号和批准年份; 3; ILT; 110.5; -10.5; 0; MC; 53; STR; 0; 11; Standard; lt; ;</span></span><br><span class="line"></span><br><span class="line"><span class="deletion">-IRB101 = 更改区域1____; 3; IRB; -195; 37.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB102 = 变更序号1; 3; IRB; -181.5; 37.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB103 = 变更页码1; 3; IRB; -173; 37.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB104 = 通知单号1; 3; IRB; -156.5; 37.5; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB105 = 签名1; 3; IRB; -137.5; 37.5; 0; MC; 15; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB106 = 日期1; 3; IRB; -125; 37.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="deletion">-IRB107 = 更改区域2____; 3; IRB; -195; 42.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB108 = 变更序号2; 3; IRB; -181.5; 42.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB109 = 变更页码2; 3; IRB; -173; 42.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB110 = 通知单号2; 3; IRB; -156.5; 42.5; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB111 = 签名2; 3; IRB; -137.5; 42.5; 0; MC; 15; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB112 = 日期2; 3; IRB; -125; 42.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="deletion">-IRB113 = 更改区域3____; 3; IRB; -195; 47.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB114 = 变更序号3; 3; IRB; -181.5; 47.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB115 = 变更页码3; 3; IRB; -173; 47.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB116 = 通知单号3; 3; IRB; -156.5; 47.5; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB117 = 签名3; 3; IRB; -137.5; 47.5; 0; MC; 15; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB118 = 日期3; 3; IRB; -125; 47.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="deletion">-IRB119 = 更改区域4____; 3; IRB; -195; 52.5; 0; MC; 20; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB120 = 变更序号4; 3; IRB; -181.5; 52.5; 0; MC; 7; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB121 = 变更页码4; 3; IRB; -173; 52.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB122 = 通知单号4; 3; IRB; -156.5; 52.5; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB123 = 签名4; 3; IRB; -137.5; 52.5; 0; MC; 15; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB124 = 日期4; 3; IRB; -125; 52.5; 0; MC; 10; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"></span><br><span class="line"><span class="deletion">-IRB125=设计-文件数量; 3; IRB; -156.5; 27.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB126=审核-文件数量; 3; IRB; -156.5; 22.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB127=主任设计-文件数量; 3; IRB; -156.5; 17.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB128=工艺-文件数量; 3; IRB; -156.5; 12.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB129=标准化-文件数量; 3; IRB; -156.5; 7.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br><span class="line"><span class="deletion">-IRB130=批准-文件数量; 3; IRB; -156.5; 2.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;</span></span><br></pre></td></tr></tbody></table></figure><p>直觉告诉我可能是缓冲区大小的问题,所以还是查一下微软<a href="https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprivateprofilesection">官方文档</a>看一下实现比较好,可能需要自己去重新实现一下读取函数。</p><p><strong>GetPrivateProfileSection function</strong></p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function">DWORD <span class="title">GetPrivateProfileSection</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> [in] LPCTSTR lpAppName,</span></span></span><br><span class="line"><span class="params"><span class="function"> [out] LPTSTR lpReturnedString,</span></span></span><br><span class="line"><span class="params"><span class="function"> [in] DWORD nSize,</span></span></span><br><span class="line"><span class="params"><span class="function"> [in] LPCTSTR lpFileName</span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>;</span><br></pre></td></tr></tbody></table></figure><ul><li><p>[in] <code>lpAppName</code><br>The name of the section in the initialization file.</p></li><li><p>[out] <code>lpReturnedString</code><br>A pointer to a buffer that receives the key name and value pairs associated with the named section. The buffer is filled with one or more null-terminated strings; the last string is followed by a second null character.</p></li><li><p>[in] <code>nSize</code><br>The size of the buffer pointed to by the lpReturnedString parameter, in characters.<br>The maximum profile section size is 32,767 characters.</p></li><li><p>[in] <code>lpFileName</code><br>The name of the initialization file. If this parameter does not contain a full path to the file, the system searches for the file in the Windows directory.</p></li></ul><p>这里比较关键的地方就是这个 <code>nSize</code>,可以看到它是通过一个缓冲区大小的参数设定读取的缓冲区大小,最大可以设置为 32,767 字节。经过周工排查 <code>IM_GetPrivateProfileSectionMap</code> 的缓冲区为 2k 左右,所以需要我们重新编写函数读取。</p><p>那么首先我们先重新设定最大的缓冲区大小(也就是刚才的 32,767):</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">TCHAR buffer[<span class="number">32767</span>] = { <span class="number">0</span> };</span><br><span class="line"><span class="keyword">auto</span> profileSize = <span class="built_in">GetPrivateProfileSection</span>(appName, buffer, std::<span class="built_in">size</span>(buffer), filePath);</span><br></pre></td></tr></tbody></table></figure><p>之后则需要用一个我之前几乎没有用过的 <code>string_view</code>。C++17 中我们可以使用 <code>std::string_view</code> 来获取一个字符串的视图,字符串视图并不真正的创建或者拷贝字符串,而只是拥有一个字符串的查看功能。<code>std::string_view</code> 比 <code>std::string</code> 的性能要高很多,因为每个 <code>std::string</code> 都独自拥有一份字符串的拷贝,而 <code>std::string_view</code> 只是记录了自己对应的字符串的指针和偏移位置,当我们在只是查看字符串的函数中可以直接使用 <code>std::string_view</code> 来代替。</p><p>读取出来的 <code>buffer</code> 字符串则是以下面形式排列:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">name1 = value1 \0 name2 = value2 \0 name3 = value3 \0 ... nameN = valueN \0\0</span><br></pre></td></tr></tbody></table></figure><p>这里我的算法是用左右双指针寻找 <code>=</code> 和 <code>\0</code>,以此类推直至读取到 <code>profileSize</code>:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">size_t</span> left = <span class="number">0</span>;</span><br><span class="line"><span class="type">size_t</span> right = sv.<span class="built_in">find</span>(<span class="string">L'='</span>, left);</span><br><span class="line"><span class="keyword">if</span> (right == std::wstring_view::npos)</span><br><span class="line"> right = profileSize;</span><br><span class="line"></span><br><span class="line">std::wstring_view key = sv.<span class="built_in">substr</span>(left, right - left);</span><br></pre></td></tr></tbody></table></figure><p>左指针归零,右指针先找到 <code>=</code>。此时观察可以发现左右指针已经可以把 <code>name1</code> 读取出来了:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">name1 = value1 \0 name2 = value2 \0 name3 = value3 \0 ...</span><br><span class="line">^ ^</span><br><span class="line">l r</span><br></pre></td></tr></tbody></table></figure><p>接着左指针移到右指针(也就是 <code>=</code> 所在位置)后面一位,右指针找到 <code>\0</code>。此时观察可以发现左右指针又可以把 <code>value1</code> 读取出来了:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">name1 = value1 \0 name2 = value2 \0 name3 = value3 \0 ...</span><br><span class="line"> ^ ^</span><br><span class="line"> l r</span><br></pre></td></tr></tbody></table></figure><p>依此类推,最终完整实现如下:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function">std::map<CString, CString> <span class="title">Utility::readPrivateProfile</span><span class="params">(CString appName, CString filePath)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> TCHAR buffer[<span class="number">32767</span>] = { <span class="number">0</span> };</span><br><span class="line"> <span class="keyword">auto</span> profileSize = <span class="built_in">GetPrivateProfileSection</span>(appName, buffer, std::<span class="built_in">size</span>(buffer), filePath);</span><br><span class="line"> std::wstring_view sv{ buffer, profileSize };</span><br><span class="line"> </span><br><span class="line"> <span class="type">size_t</span> left = <span class="number">0</span>;</span><br><span class="line"> std::map<CString, CString> ret;</span><br><span class="line"> <span class="keyword">while</span> (left < profileSize)</span><br><span class="line"> {</span><br><span class="line"> <span class="type">size_t</span> right = sv.<span class="built_in">find</span>(<span class="string">L'='</span>, left);</span><br><span class="line"> <span class="keyword">if</span> (right == std::wstring_view::npos)</span><br><span class="line"> right = profileSize;</span><br><span class="line"> </span><br><span class="line"> std::wstring_view key = sv.<span class="built_in">substr</span>(left, right - left);</span><br><span class="line"> </span><br><span class="line"> left = right + <span class="number">1</span>;</span><br><span class="line"> right = sv.<span class="built_in">find</span>(<span class="string">L'\0'</span>, left);</span><br><span class="line"> <span class="keyword">if</span> (right == std::wstring_view::npos)</span><br><span class="line"> right = profileSize;</span><br><span class="line"> </span><br><span class="line"> std::wstring_view value = sv.<span class="built_in">substr</span>(left, right - left);</span><br><span class="line"> left = right + <span class="number">1</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (!key.<span class="built_in">empty</span>())</span><br><span class="line"> ret.<span class="built_in">emplace</span>(<span class="built_in">CString</span>(key.<span class="built_in">data</span>(), key.<span class="built_in">length</span>()), <span class="built_in">CString</span>(value.<span class="built_in">data</span>(), value.<span class="built_in">length</span>()));</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="智能指针托管导致实体打开失败"><a href="#智能指针托管导致实体打开失败" class="headerlink" title="智能指针托管导致实体打开失败"></a>智能指针托管导致实体打开失败</h3><p>在移植的过程中发现序号实体一直没办法添加进去,返回的 <code>eWasOpenForWrite</code> 找了好久,也尝试使用升降读写权限也失败了。后经周工点拨才发现问题所在:</p><figure class="highlight diff"><table><tbody><tr><td class="code"><pre><span class="line"><span class="deletion">-shared_ptr<XuHaoEntity> pMechXH(new XuHaoEntity(m_MechXHDInfo));</span></span><br><span class="line"><span class="addition">+XuHaoEntity* pMechXH = new XuHaoEntity(m_MechXHDInfo);</span></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">AcDbBlockTableRecordPointer pModelSpace(IM_GetModalSpaceId(), AcDb::kForWrite);</span><br><span class="line">if (pModelSpace.openStatus() != eOk) return false;</span><br><span class="line"></span><br><span class="line">AcDbObjectId entId = AcDbObjectId::kNull;</span><br><span class="line"><span class="deletion">-if (pModelSpace->appendAcDbEntity(entId, pMechXH.get()) != Acad::eOk)</span></span><br><span class="line"><span class="addition">+if (pModelSpace->appendAcDbEntity(entId, pMechXH) != Acad::eOk)</span></span><br><span class="line"> return false;</span><br><span class="line">pMechXH->close();</span><br></pre></td></tr></tbody></table></figure><p>由于智能指针的广泛使用,所以我对内存管理这一块并不够敏感。张帆的那本书曾经提到 ObjectARX 是如何管理内存的:</p><blockquote><p>在操作图形数据库的各种对象时,必须遵守 AutoCAD 的打开和关闭对象的协议。该协议确保当对象被访问时在物理内存中,而未被访问时可以被分页存储在磁盘中。创建和打开数据库的对象之后,必须在不用的时候关闭它。</p><p>给初学 ObiectARX 的人两个建议。</p><ol><li><strong>不要忘记各种数据库对象的关闭</strong>:在打开或创建数据库对象之后,必须尽可能早地关闭它。在初学者所犯的错误中,未及时关闭对象的错误至少占一半!</li><li><strong>不要使用 <code>delete pLine</code> 的语句</strong>:对 C++ 比较熟悉的读者,习惯于配对使用 <code>new</code> 和 <code>delete</code> 运算符,这在 C++ 编程中是一个良好的编程习惯。但是在ObjectARX 的编程中,当编程者使用 <code>appendAcDbEntity</code> 函数将对象添加到图形数据库之后,就需要由图形数据库来操作该对象。</li></ol></blockquote><p>这里的 <code>shared_ptr<TH_XuHaoEntity></code> 就是问题关键。当我使用智能指针的时候将 <code>XuHaoEntity</code> 同时托管给 AutoCAD 和系统,这就导致在该段代码的作用域结束时会自动销毁该序号实体,而此时它已经被添加到数据库对象当中!内存泄漏的问题也就此诞生了,倘若此时用 Debug 工具测试会直接崩溃,因为该实体根本无法访问(毕竟已经在内存中被删除了)。</p><p>这个问题正好对应了建议二:不要使用 <code>delete pLine</code> 的语句。</p><h3 id="文字样式表保持同步"><a href="#文字样式表保持同步" class="headerlink" title="文字样式表保持同步"></a>文字样式表保持同步</h3><p>天喻的旧图纸中转换时明细表部分需使用文字样式 <code>Standard</code> 的字体、大字体和宽度因子,但创建明细表时字体使用的是 <code>HC_TEXTSTYLE</code>。虽然新建的图纸已经将两者调整为一致,但对于旧图纸而言仍不同步,需要将 <code>Standard</code> 的各项设置到 <code>HC_TEXTSTYLE</code>:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function">AcDbTextStyleTablePointer <span class="title">pTextStyleTable</span><span class="params">(acdbHostApplicationServices()->workingDatabase(), AcDb::kForRead)</span></span>;</span><br><span class="line"><span class="keyword">if</span> (pTextStyleTable.<span class="built_in">openStatus</span>() != Acad::eOk) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">AcDbObjectId standardTextStyleId, hcTextStyleId;</span><br><span class="line"><span class="keyword">if</span> (pTextStyleTable-><span class="built_in">getAt</span>(<span class="string">L"STANDARD"</span>, standardTextStyleId) != Acad::eOk || pTextStyleTable-><span class="built_in">getAt</span>(<span class="string">L"HC_TEXTSTYLE"</span>, hcTextStyleId) != Acad::eOk)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line">pTextStyleTable-><span class="built_in">close</span>();</span><br><span class="line"></span><br><span class="line"><span class="function">AcDbTextStyleTableRecordPointer <span class="title">pStandardTextStyleTableRecord</span><span class="params">(standardTextStyleId, AcDb::kForRead)</span>, <span class="title">pHCTextStyleTableRecord</span><span class="params">(hcTextStyleId, AcDb::kForWrite)</span></span>;</span><br><span class="line"><span class="keyword">if</span> (pStandardTextStyleTableRecord.<span class="built_in">openStatus</span>() != Acad::eOk || pHCTextStyleTableRecord.<span class="built_in">openStatus</span>() != Acad::eOk)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">GCHAR* fileName, *bigFontName;</span><br><span class="line">pStandardTextStyleTableRecord-><span class="built_in">fileName</span>(fileName);</span><br><span class="line">pStandardTextStyleTableRecord-><span class="built_in">bigFontFileName</span>(bigFontName);</span><br><span class="line"></span><br><span class="line">pHCTextStyleTableRecord-><span class="built_in">setFileName</span>(fileName);</span><br><span class="line">pHCTextStyleTableRecord-><span class="built_in">setBigFontFileName</span>(bigFontName);</span><br><span class="line">pHCTextStyleTableRecord-><span class="built_in">setXScale</span>(pStandardTextStyleTableRecord-><span class="built_in">xScale</span>());</span><br><span class="line">pHCTextStyleTableRecord-><span class="built_in">close</span>();</span><br><span class="line">pStandardTextStyleTableRecord-><span class="built_in">close</span>();</span><br></pre></td></tr></tbody></table></figure><p>用的是笨办法挨个设置,暂时没找到类似整个拷贝的方式去处理文字样式表。</p><h3 id="特殊字插入"><a href="#特殊字插入" class="headerlink" title="特殊字插入"></a>特殊字插入</h3><p>大同方提供的 <code>.shx</code> 字体没办法正常显示 <code>瞭</code> 字,并且强烈要求增加该字的插入功能。</p><p>时间有限,讨论了一下决定先将 <code>瞭</code> 字做成 <code>liao.dwg</code> 文件,然后通过 LISP 代码交互并插入:</p><figure class="highlight lisp"><table><tbody><tr><td class="code"><pre><span class="line">(<span class="name">defun</span> liao()</span><br><span class="line"> (<span class="name">initget</span> <span class="number">4</span>)</span><br><span class="line"> (<span class="name">if</span> (<span class="name">=</span> (<span class="name">setq</span> height (<span class="name">getreal</span> <span class="string">"\n请指定文字高度<2.5>: "</span>)) <span class="literal">nil</span>)</span><br><span class="line"> (<span class="name">setq</span> height <span class="number">2.5</span>)</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> (<span class="name">setq</span> echo (<span class="name">getvar</span> <span class="string">"cmdecho"</span>))</span><br><span class="line"> (<span class="name">setvar</span> <span class="string">"cmdecho"</span> <span class="number">0</span>)</span><br><span class="line"> (<span class="name">command</span> <span class="string">"insert"</span> <span class="string">"瞭"</span> <span class="string">"S"</span> height <span class="string">"\\"</span> <span class="number">0</span>)</span><br><span class="line"> (<span class="name">setvar</span> <span class="string">"cmdecho"</span> echo)</span><br><span class="line"> (<span class="name">princ</span>)</span><br><span class="line">)</span><br></pre></td></tr></tbody></table></figure><h3 id="代码交付中的文件批量删除操作"><a href="#代码交付中的文件批量删除操作" class="headerlink" title="代码交付中的文件批量删除操作"></a>代码交付中的文件批量删除操作</h3><p>项目进度后期需要交付图纸转换的源码,但不会将我们所有项目的源码交付给大同方,所以需要将其余无关项目以头文件和库文件的形式交付。</p><p>先前我做交付时会一个一个去编译每个依赖项目,看缺少哪个头文件再加进去,效率很低。后面周工教了如何使用 <code>find</code> 命令去操作文件:</p><figure class="highlight shell"><table><tbody><tr><td class="code"><pre><span class="line">find <file_path> -type f ! -name "*.h" -delete</span><br></pre></td></tr></tbody></table></figure><p>该命令会将所有非 <code>.h</code> 头文件删除,非常方便。</p></body></html>]]></content>
<summary type="html"><p><a href="https://www.behance.net/gallery/116635445/Becoming-Someone-New-1-5-by-James-Lipnickas-2021"><img data-src="https://img.yousazoe.top/uPic/img/blog/GSTAR1/GSTAR1.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>中车大同的旧图纸转换功能的定制开发告一段落,这段时间和周工学到了很多有用的知识,特此记录这个项目学到的编程技巧和项目开发经验。</p></summary>
<category term="CAD二次开发 (GStarCAD ObjectARX Dev)" scheme="https://yousazoe.top/categories/CAD%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91-GStarCAD-ObjectARX-Dev/"/>
<category term="ObjectARX" scheme="https://yousazoe.top/tags/ObjectARX/"/>
</entry>
<entry>
<title>初次体验 Github Copilot</title>
<link href="https://yousazoe.top/archives/ff967b76.html"/>
<id>https://yousazoe.top/archives/ff967b76.html</id>
<published>2023-01-24T03:21:57.000Z</published>
<updated>2025-01-03T06:07:21.682Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/BTZRHX.jpg"></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>最近一段时间,AI 技术的进步则让代码补全有了更上一层楼的机会。接下来,我们为大家介绍的 Github Copilot 就是这样一款基于 AI 的代码补全工具。</p><span id="more"></span><p>我们编程时写出的代码,在未编译前通常以纯文本格式存在。因此,实际上我们能使用任何文本编辑器来编写代码,包括系统自带的记事本。但是,好的工具能够让我们事半功倍。面对复杂的工作任务,我们需要 IDE(集成开发环境)这样的生产力工具。IDE 本质上就是更高级的文本编辑器,集成了许多人性化功能来提升效率,比如:自动补全变量,提示可能会用到的函数列表,语法高亮,显示语法错误等等。</p><p>IDE 本身也在不断进化。我的第一门使用 IDE 的编程语言是 Java,使用的是 Eclipse,当时自动补全功能还比较简陋,局限于符号的提示与选单,我也没有对 IDE 究竟有多强大建立起概念。后来,开始学习开发框架后,我慢慢接触到 JetBrains 出品的 IDEA。IDEA 的提示更智能,例如:可以在数组后输入「.for」自动构成 foreach 循环,也可以使用快捷键自动生成 Getter/Setter、构造函数、重载函数等等。毫无疑问,JetBrains 系列产品为编码工作带来了更高的效率,提供了更加全面、智能的补全功能。</p><h3 id="GitHub-Copilot-是什么"><a href="#GitHub-Copilot-是什么" class="headerlink" title="GitHub Copilot 是什么"></a>GitHub Copilot 是什么</h3><p>Github Copilot 是 GitHub 和 OpenAI 合作开发的人工智能工具,可以在编辑代码时帮助你自动生成可能会需要的代码。</p><p>GitHub Copilot 能够提取代码上下文,给出整行代码或整个函数的补全建议。它可以帮助我们完成下列任务:</p><ul><li>将注释转化为代码; </li><li>自动填充重复代码; </li><li>编写测试; </li><li>快速发现解决问题的替代方法; </li><li>无需网络搜索即可快速探索新的 API; </li><li>适应用户编写代码的方式,帮助用户更快地完成工作。</li></ul><h4 id="原理是什么?"><a href="#原理是什么?" class="headerlink" title="原理是什么?"></a>原理是什么?</h4><p>我们先介绍一下 GPT-3。GPT-3(Generative Pre-trained Transformer 3)是一个用于处理自然语言的 AI 模型,由 OpenAI 训练开发。GPT-3 通过阅读几乎一切人类可阅读的内容来进行训练,理论上,它能够完成一切通过语言完成的工作,而且完成效果还非常接近人类。已经有实验证明 GPT-3 可用于撰写文章、回答问题、编写代码生成应用程序、设计表格、开发游戏、将文字描述便携为成型的网页等等。</p><p>而 OpenAI Codex 则是基于 GPT-3 开发的一款针对编程所设计的 AI 模型。Codex 从公共代码仓库学习人类编写的代码,其代码来源包括 Github 上的公共代码仓库。官网原文如下:</p><blockquote><p>OpenAI Codex is a descendant of GPT-3; its training data contains both natural language and billions of lines of source code from publicly available sources, including code in public GitHub repositories. (OpenAI Codex 是 GPT-3 的衍生项目;它的训练数据包括自然语言和数以亿计来自公开可用来源的源代码,其中包括 Github 公开仓库的代码。)</p></blockquote><p>最后,GitHub Copilot 则是使用了 Codex 进行研发的一款商业产品。Github 将算法进行包装,做成了插件和网页,进行应用分发。现在 GitHub Copilot 支持在 Visual Studio Code、Visual Studio、JetBrains Rider 上通过插件形式集成进 IDE,以便我们使用。</p><h3 id="使用-amp-体验"><a href="#使用-amp-体验" class="headerlink" title="使用 & 体验"></a>使用 & 体验</h3><p>要想使用 GitHub Copilot,首先需要注册一个 Github 账号。有了帐号后,按下面的步骤可以找到并启用 GitHub Copilot:</p><ol><li><strong>找到设置页面</strong>:在任何页面的右上角,单击个人资料照片,然后单击“设置”。</li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/YeXRqN.jpg"></p><ol start="2"><li><p><strong>找到 GitHub Copilot 设置页面</strong>:在边栏的「代码、规划和自动化」部分,单击「GitHub Copilot」。</p></li><li><p><strong>启用 GitHub Copilot</strong>:在 GitHub Copilot 设置页面上,单击「启用 GitHub Copilot」。</p></li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/vtDoan.jpg"></p><ol start="4"><li><strong>选择付费方式(月付/年付)</strong>:GitHub Copilot 可以免费试用 60 个自然日,随后需要以 $10/月 的价格订阅。如果你是学生的话,可享受教育优惠,免费使用 GitHub Copilot。</li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/6CHQ0X.jpg"></p><h4 id="在-Rider-IDE-进行设置"><a href="#在-Rider-IDE-进行设置" class="headerlink" title="在 Rider IDE 进行设置"></a>在 Rider IDE 进行设置</h4><ol><li>在偏好设置里安装 Github Copilot Plugin; </li><li>重启 IDE; </li><li>登陆 Github 完成验证。</li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/LIJYV8.jpg"></p><p>设置完成后,IDE 提示可以使用「Tab」来自动补全代码,使用「⌥ + ]」或者「⌥ + [」来选择其他候选的补全选项。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/k8KwOS.jpg"></p><h4 id="体验如何"><a href="#体验如何" class="headerlink" title="体验如何"></a>体验如何</h4><p>在编写代码的过程中,Github Copilot 会自动提示可能的补全方案,此时按下「Tab」即可完成补全。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/exmaple.gif"></p><p>有时,AI 并不会一次给出完整的提示代码,例如,图示的代码就并非一次性生成的,而是逐行自动补全,最终生成了一个可以实际使用的函数(甚至包括注释)。下图的例子在 Unity3D 中绘制了一条射线用于检测前方是否有物品,只有第一行注释是我写下的。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/jgOixr.jpg"></p><p>下面的例子很有趣:当我尝试把乐谱的音高编写成数组时,Github Copilot 也给出了他所理解的音乐:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/qICjGP.jpg"></p><p>在这样的例子中,对重复的流行乐片段,Github Copilot 有时可以给出不错的答案。比如预先输入《卡农》的重复性模进片段,Github Copilot 往往可以完全正确地补全乐谱。可见,在面临重复性较高的功能开发,或是使用一些常用的算法时,依靠 AI 补全是一个称得上非常可靠的选择。不过,如果需求非常复杂,大部分情况下,它并不能独立地给出完美的解决方案。GitHub 团队在对一组 Python 函数进行基准性测试后发现尝试十次后,大约 57% 情况下可以给出正确的答案。部分情况下,Github Copilot 也会给出无法通过编译的代码。</p><h3 id="Github-Copilot-的不足之处"><a href="#Github-Copilot-的不足之处" class="headerlink" title="Github Copilot 的不足之处"></a>Github Copilot 的不足之处</h3><p>使用 Github Copilot 很久后,Reddit 大佬 Colin Eberhardt 指出了几点不足:</p><ol><li>Github Copilot 很多时候响应得不够快。虽然它已经快到秒出答案,但这对快速输出状态的程序员来说仍然是不够的。要么它的提示还没出现你就继续输入了,要么你会因为等它而暂时停下思路。 </li><li>Github Copilot 总是会自动提示。这种提示和输入存在冲突:有时,当你需要等等看提示怎么说另一些时,会不断有内容弹出,接着消失。或许,自动模式并不是 Github Copilot 的「最佳打开方式」? </li><li>Github Copilot 生成代码质量不足。它生成的代码可以满足大部分简单且重复的功能需求,但对于熟练的程序员,可能会额外浪费很多精力来校验它自动生成的代码是否正确。</li></ol><h4 id="Github-Copilot-版权问题"><a href="#Github-Copilot-版权问题" class="headerlink" title="Github Copilot 版权问题"></a>Github Copilot 版权问题</h4><p>许多人指出 Github Copilot 会使用有版权的代码作为提示内容(参见 Jacob Crume 的文章“GitHub Copilot is Now Available for All and Not Everyone Likes It”)。少数派作者 100gle 在《GitHub Copilot:革命未竟,未来可期》中更是举出了很多例子。最为出名的莫过于,如果你在编辑器中输入 <code>Fast inverse square root</code>,便会得到一段代码,它和当年《雷神之锤》使用的算法完全一致。</p><p>现代开源软件多使用 GPL (GNU General Public License)协议,这个协议要求你也将代码开源,且使用 GPL 协议。而通过 Github Copilot 补全时我们并不确定这段代码的作者为它指定的协议。开源许可证的主要作用是对软件的使用、复制、修改和再发布等进行限制。而显然使用 AI 补全显然会破坏这一点。</p><p>可以预见的是,同当今各种 AI 作画工具面对的种种争议一样,Github Copilot 也一定会因为版权问题,难以被大型企业所用,至少短期内如此。</p><h3 id="使用建议"><a href="#使用建议" class="headerlink" title="使用建议"></a>使用建议</h3><ul><li>Github Copilot 能帮助初学者面对不那么熟悉的编程语言或开发框架时,快速学习常用的接口调用方式和简单的实现方案。这意味着我们可以不用为了某些基础问题反复翻找 API 手册,或体验 CSDN 这样的技术博客网站的层层传送门。 </li><li>Github Copilot 可以帮助我们在不熟悉的领域快速上手,只需要一些注释便可快速生成部分业务逻辑,然后进行测试。当然,最终代码的可靠性还是需要开发者人为辨别和控制。 </li><li>Github Copilot 可以在重复性劳动时显著提升效率。比如你需要写一大堆单元测试,它们无法靠复制/粘贴批量生成,同时有一些细微的逻辑变化需要处理。又或是你需要开发一些重复性功能,比如批量声明一些数据类型好几十次。这时 Github Copilot 补全的代码往往很可靠。</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Github Copilot 或许并不能承载类似“AI 即将取代程序员”的想象,但在当下,它无疑是程序员的好帮手。作为辅助,它提供的补全并没有智能到让完全不会编程的用户完成开发,但也并不只是简单的提示工具。合理运用 Github Copilot 能够为开发者的学习成长带来很大帮助。</p><p>与此同时,它不可避免地存在一些缺陷,代码的版权问题也限制了它商业化的应用前景。不够熟练的程序员可能也会对它失望——就像它名字中的 Copilot 一样,Github Copilot 更接近优秀的副驾驶角色,但工作总归还是需要一位优秀的主驾驶领导。</p><p>最好的旅行靴已经送到我们手中,走出什么样的路还需要开发者自己去定夺。</p></body></html>]]></content>
<summary type="html"><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE5/BTZRHX.jpg"></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>最近一段时间,AI 技术的进步则让代码补全有了更上一层楼的机会。接下来,我们为大家介绍的 Github Copilot 就是这样一款基于 AI 的代码补全工具。</p></summary>
<category term="编程技巧 (Coding Skill)" scheme="https://yousazoe.top/categories/%E7%BC%96%E7%A8%8B%E6%8A%80%E5%B7%A7-Coding-Skill/"/>
<category term="Github Copilot" scheme="https://yousazoe.top/tags/Github-Copilot/"/>
</entry>
<entry>
<title>Jetbrains IDE 开发环境激活方式记录</title>
<link href="https://yousazoe.top/archives/a906a6fe.html"/>
<id>https://yousazoe.top/archives/a906a6fe.html</id>
<published>2023-01-15T04:46:31.000Z</published>
<updated>2025-01-03T06:07:21.679Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><img data-src="https://img.yousazoe.top/uPic/img/blog/BLOG13/BLOG13_jetbrains-banner.png"></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>Jetbrains 全家桶开发还是比较顺手的,但工作之后学生认证到期了,所以需要重新激活,特此写一篇博文记录自己使用 Jetbrains 产品的各种激活方式。</p><span id="more"></span><blockquote><p>需要提前声明,Jetbrains 也提供了社区版供学生和初学者使用,本文仅作激活操作记录,使用激活的软件请勿用作商业用途,如有条件请务必支持正版购买许可证。 </p></blockquote><h3 id="学生认证"><a href="#学生认证" class="headerlink" title="学生认证"></a>学生认证</h3><p><img data-src="https://cdn.jsdelivr.net/gh/Yousazoe/picgo-repo/img/007S8ZIlly1gf3sypo104j31hc0u0ae8.jpg"></p><p>Github 的学生认证可以通过上传学校信息的方式获取正规免费的许可,在 <a href="https://www.yousazoe.top/archives/e8e3308a.html">Unity Student Plan 申请指南</a> 这篇文章有详细操作。</p><p>如果是在读学生,完全可以用这种方式激活 Jetbrains 相关产品。</p><h3 id="注册机激活"><a href="#注册机激活" class="headerlink" title="注册机激活"></a>注册机激活</h3><p>不推荐这种激活方式。</p><p>之前我使用这种方式激活,因为之前有一些相关文件未删除,会导致整个软件闪退无法使用,在 Mac 上折腾了很长一段时间。</p><h3 id="服务器激活"><a href="#服务器激活" class="headerlink" title="服务器激活"></a>服务器激活</h3><p>先打开这个网站:<a href="https://search.censys.io/">https://search.censys.io/</a></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/BLOG13/BLOG13_censys-website.png"></p><p>然后搜索框输入:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">services.http.response.headers.location: account.jetbrains.com/fls-auth</span><br></pre></td></tr></tbody></table></figure><p><img data-src="https://img.yousazoe.top/uPic/img/blog/BLOG13/BLOG13_censys-search.png"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/BLOG13/BLOG13_censys-search-result.png"></p><p>选择第一个搜索结果,右击进去:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/BLOG13/BLOG13-censys-server.png"></p><p>将网址到 Jetbrains,选择许可证服务器 <code>/License server</code>,粘贴刚刚复制的网址 <code>http://134.53.225.196</code>,激活。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/BLOG13/BLOG13_licenses%20active.png"></p><p>大功告成,顺带一提,这个好像是迈阿密大学的服务器……</p></body></html>]]></content>
<summary type="html"><p><img data-src="https://img.yousazoe.top/uPic/img/blog/BLOG13/BLOG13_jetbrains-banner.png"></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>Jetbrains 全家桶开发还是比较顺手的,但工作之后学生认证到期了,所以需要重新激活,特此写一篇博文记录自己使用 Jetbrains 产品的各种激活方式。</p></summary>
<category term="博客搭建 (Blog Building)" scheme="https://yousazoe.top/categories/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA-Blog-Building/"/>
<category term="Blog" scheme="https://yousazoe.top/tags/Blog/"/>
</entry>
<entry>
<title>GStarCAD C++笔试&机试&面试总结</title>
<link href="https://yousazoe.top/archives/2fcb9e48.html"/>
<id>https://yousazoe.top/archives/2fcb9e48.html</id>
<published>2023-01-01T05:43:05.000Z</published>
<updated>2025-01-03T06:07:21.682Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://www.behance.net/gallery/117842739/Low-Poly-Game-Assets"><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE4/CODE4.png"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>GStarCAD 笔试和机试总结。笔试题主要考核 C++、代码阅读理解、基本几何运算、英文阅读能力,机试题主要考察 VC++、MFC 的实际编程操作能力。</p><span id="more"></span><h3 id="笔试部分"><a href="#笔试部分" class="headerlink" title="笔试部分"></a>笔试部分</h3><p>笔试题主要考核 C++、代码阅读理解、基本几何运算、英文阅读能力。</p><h4 id="问题1"><a href="#问题1" class="headerlink" title="问题1"></a>问题1</h4><h5 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h5><p>下列代码有问题、或有可改进之处吗?如有,请直接修改,并写明原因.</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CBase</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">CBase</span>(){m_nVal = <span class="number">100</span>;}</span><br><span class="line"> ~<span class="built_in">CBase</span>(){}</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">Get</span><span class="params">()</span> </span>{<span class="keyword">return</span> m_nVal;}</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">GetDouble</span><span class="params">()</span> <span class="type">const</span> </span>{m_nVal *= <span class="number">3</span>; <span class="keyword">return</span> m_nVal;}</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="type">int</span> m_nVal;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>* argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> CBase base;</span><br><span class="line"> std::cout << “val = ”<< base.<span class="built_in">GetDouble</span>() << std::endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h5 id="答案"><a href="#答案" class="headerlink" title="答案"></a>答案</h5><p>答:<code>int GetDouble() const {m_nVal *= 3; return m_nVal;}</code> 若需要对成员变量进行赋值需删除 <code>const</code>。</p><h4 id="问题2"><a href="#问题2" class="headerlink" title="问题2"></a>问题2</h4><h5 id="题目-1"><a href="#题目-1" class="headerlink" title="题目"></a>题目</h5><p>请写出 <code>main</code> 函数的输出结果,并写明理由.</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CBase</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="title">voidSend</span><span class="params">()</span></span>{std::cout << <span class="string">"\nSend :"</span><< m_nVal << std::endl;}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Output</span><span class="params">()</span></span>{<span class="built_in">Send</span>();}</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="type">int</span> m_nVal;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CUser</span> : <span class="keyword">public</span> CBase</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">CUser</span>(){m_nVal = <span class="number">101</span>;}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="title">voidSend</span><span class="params">()</span></span>{m_nVal++;std::cout << “\nSend :”<<m_nVal<< std::endl;}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Output</span><span class="params">()</span></span>{CBase::<span class="built_in">Send</span>();}</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>* argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> CUser user;</span><br><span class="line"> CBase &base=user;</span><br><span class="line"> base.<span class="built_in">Send</span>();<span class="comment">//输出:</span></span><br><span class="line"> CBase *pBase=&base;</span><br><span class="line"> pBase-><span class="built_in">Output</span>();<span class="comment">//输出:</span></span><br><span class="line"> user.<span class="built_in">Output</span>();<span class="comment">//输出:</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h5 id="答案-1"><a href="#答案-1" class="headerlink" title="答案"></a>答案</h5><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">Send:102</span><br><span class="line">Send:103</span><br><span class="line">Send:103</span><br></pre></td></tr></tbody></table></figure><h4 id="问题3"><a href="#问题3" class="headerlink" title="问题3"></a>问题3</h4><h5 id="题目-2"><a href="#题目-2" class="headerlink" title="题目"></a>题目</h5><p>阅读理解类声明代码,并使用之实现功能。</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">AcGePoint2d</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">AcGePoint2d</span>();</span><br><span class="line"> <span class="built_in">AcGePoint2d</span>(<span class="type">double</span> x, <span class="type">double</span> y);</span><br><span class="line"> <span class="function">AcGePoint2d& <span class="title">set</span><span class="params">(<span class="type">double</span> x, <span class="type">double</span> y)</span></span>;</span><br><span class="line"> </span><br><span class="line"> <span class="type">double</span> x, y;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AcGeLinearEnt2d</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">boolintersectWith</span>(<span class="type">const</span> AcGeLinearEnt2d& line, AcGePoint2d& intPnt,<span class="type">const</span> AcGeTol& tol = AcGeContext::gTol) <span class="type">const</span>;<span class="comment">//求直线交点函数</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">protected</span>:</span><br><span class="line"> <span class="built_in">AcGeLinearEnt2d</span> ();</span><br><span class="line"> <span class="built_in">AcGeLinearEnt2d</span> (<span class="type">const</span> AcGeLinearEnt2d&);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AcGeLineSeg2d</span>: <span class="keyword">public</span> AcGeLinearEnt2d</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:<span class="built_in">AcGeLineSeg2d</span> ();</span><br><span class="line"> <span class="built_in">AcGeLineSeg2d</span> (<span class="type">const</span> AcGeLineSeg2d & line);</span><br><span class="line"> <span class="built_in">AcGeLineSeg2d</span> (<span class="type">const</span> AcGePoint2d& pnt1, <span class="type">const</span> AcGePoint2d& pnt2);</span><br><span class="line"> <span class="function">AcGeLineSeg2d & <span class="title">set</span> <span class="params">(<span class="type">const</span> AcGePoint2d& pnt1, <span class="type">const</span> AcGePoint2d& pnt2)</span></span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>* argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">//请使用上述类和类成员函数,在本函数中实现两直线段求交点,并输出。</span></span><br><span class="line"> <span class="comment">//这两个直线段分别是从点(0,0) 到(500,500); 从点(600,0) 到(200,900)。</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h5 id="答案-2"><a href="#答案-2" class="headerlink" title="答案"></a>答案</h5><figure class="highlight diff"><table><tbody><tr><td class="code"><pre><span class="line">class AcGePoint2d</span><br><span class="line">{</span><br><span class="line"> public:</span><br><span class="line"> AcGePoint2d();</span><br><span class="line"> AcGePoint2d(double x, double y);</span><br><span class="line"> AcGePoint2d& set(double x, double y);</span><br><span class="line"> </span><br><span class="line"> double x, y;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class AcGeLinearEnt2d</span><br><span class="line">{</span><br><span class="line"> public:</span><br><span class="line"> boolintersectWith(const AcGeLinearEnt2d& line, AcGePoint2d& intPnt,const AcGeTol& tol = AcGeContext::gTol) const;//求直线交点函数</span><br><span class="line"> </span><br><span class="line"> protected:</span><br><span class="line"> AcGeLinearEnt2d ();</span><br><span class="line"> AcGeLinearEnt2d (const AcGeLinearEnt2d&);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class AcGeLineSeg2d: public AcGeLinearEnt2d</span><br><span class="line">{</span><br><span class="line"> public:AcGeLineSeg2d ();</span><br><span class="line"> AcGeLineSeg2d (const AcGeLineSeg2d & line);</span><br><span class="line"> AcGeLineSeg2d (const AcGePoint2d& pnt1, const AcGePoint2d& pnt2);</span><br><span class="line"> AcGeLineSeg2d & set (const AcGePoint2d& pnt1, const AcGePoint2d& pnt2);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">int main(int argc, char* argv[])</span><br><span class="line">{</span><br><span class="line"> //请使用上述类和类成员函数,在本函数中实现两直线段求交点,并输出。</span><br><span class="line"> //这两个直线段分别是从点(0,0) 到(500,500); 从点(600,0) 到(200,900)。</span><br><span class="line"></span><br><span class="line"><span class="addition">+ AcGeLineSeg2dline1(AcGePoint2d(0,0),AcGePoint2d(500,500));</span></span><br><span class="line"><span class="addition">+ AcGeLineSeg2dline2(AcGePoint2d(600,0),AcGePoint2d(200,900));</span></span><br><span class="line"><span class="addition">+ AcGePoint2d pt;Line1.intersectWith(line2,pt);</span></span><br><span class="line"><span class="addition">+ Printf(“x:%.2f,y:%.2f”,pt.x,pt.y);</span></span><br><span class="line"></span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="问题4"><a href="#问题4" class="headerlink" title="问题4"></a>问题4</h4><p>基本几何运算。</p><ol><li>已知两点 <code>pt1</code>,<code>pt2</code>,如何计算从起点 <code>pt1</code> 到终点 <code>pt2</code> 的向量 <code>v</code>?</li></ol><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">V = pt2 - pt1</span><br></pre></td></tr></tbody></table></figure><ol start="2"><li>已知向量 <code>v1{1.0,0.0,0.0}</code>,<code>v2{0.0,1.0,0.0}</code>,<code>v3 = v1 - v2</code> ,则v3 =?</li></ol><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">{1.0,-1.0,0.0}</span><br></pre></td></tr></tbody></table></figure><ol start="3"><li>已知向量v1{1.0,0.0,0.0},v2{0.0,1.0,0.0},v3=v1×v2 ,则v3=?</li></ol><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">{0.0,0.0,1.0}</span><br></pre></td></tr></tbody></table></figure><ol start="4"><li>两向量的点积 <code>v1·v2</code> 等于 0 ,意味着两向量是什么关系?</li></ol><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">垂直</span><br></pre></td></tr></tbody></table></figure><h4 id="问题5"><a href="#问题5" class="headerlink" title="问题5"></a>问题5</h4><p>翻译英文资料。</p><blockquote><p>An ObjectARXapplication is a dynamic link library (DLL) that shares the address space of AutoCAD and makes direct function calls to AutoCAD. You can add new classes to the ObjectARXprogram environment and export them for use by other programs. </p></blockquote><p>一个 ObjectARX 应用是一个的动态链接库(DLL),它共享 AutoCAD 地址空间,并直接调用函数操作 AutoCAD。你可以在 ObjectARX 程序环境中新增新的类,并将其导出给其他程序使用。</p><blockquote><p><code>CDialog::DoModal()</code> Call this member function to invoke the modal dialog box and return the dialog-box result when done. This member function handles all interaction with the user while the dialog box is active.</p></blockquote><p><code>CDialog::DoModal()</code>,使用这一成员函数可调出模态对话框,并且当其使用完成后可返回对话框的结果。当对话框激活时,这一成员函数处理所有与用户的交互。</p><blockquote><p>AutoCADstores the values for its operating environment in system variables. Each system variable has an associated type: integer, real, point, or text string. You can examine any system variable and change any writable system variable directly on the command line by entering the system variable name. Many system variables are also accessible through dialog box options.</p></blockquote><p>AutoCAD 保存与操作环境相关的值于系统变量中。每个系统变量有一个相关类型:整形,实型,点或字符串。你可以检测任何系统变量,并通过在命令行输入系统变量名称直接改变系统变量。许多系统变量也可以通过对话框选项设置。</p><h3 id="机试部分"><a href="#机试部分" class="headerlink" title="机试部分"></a>机试部分</h3><p>机试题主要考察 VC++、MFC 的实际编程操作能力。</p><h4 id="控件窗口操作"><a href="#控件窗口操作" class="headerlink" title="控件窗口操作"></a>控件窗口操作</h4><ul><li>新建基于对话框的 MFC 工程。</li><li>定义 <code>CEdit</code> 的派生类 <code>CMyEdit</code>。在 <code>CMyEdit</code> 类中定义成员函数 <code>void SetIndex(int index)</code>,调用本函数后,编辑框内显示 <code>index</code> 数值。</li><li>对话框初始创建4个大小不一的控件,分别是:<code>CMyEdit</code>、<code>CButton</code>、<code>CComboBox</code>、<code>CEdit</code>,分别对齐对话框4个角。</li><li>在对话框类内定义一个指针数组成员变量:<code>CArray<CWnd*> m_arrCtrl</code>,并在对话框初始化时将上述4控件的对象指针按逆时针顺序保存到 <code>m_arrCtrl</code> 数组(第1个为左上角控件)。</li><li>对话框窗口支持调整大小,对话框窗口大小改变后,4个控件大小不变,但位置自动跟随调整(总是对齐4个角)。</li><li>每隔1秒,沿逆时针方向自动旋转切换上述4个控件位置(控件大小不变,只是位置改变,左上角控件跑到左下角,左下角跑到右下角,以此类推),<code>m_arrCtrl</code> 中控件指针也同步切换位置(第1个始终是左上角控件)。每次切换控件位置后,需从指针数组中找到其中唯一的 <code>CMyEdit</code> 控件,并调用它的 <code>SetIndex(index)</code>成员函数,<code>index</code> 为 <code>CMyEdit</code> 对象在 <code>m_arrCtrl</code> 数组中的新索引(0-3)。</li><li><strong>注:对话框内可以定义其它成员变量,但除对话框的构造函数、<code>DoDataExchange</code> 和 <code>OnInitDialog</code> 函数外,对话框其它函数中只能使用 <code>m_arrCtrl</code> 成员变量,不能使用其它成员变量(也不能使用全局变量和静态变量)。</strong></li></ul><h4 id="排序操作"><a href="#排序操作" class="headerlink" title="排序操作"></a>排序操作</h4><ul><li>新建基于对话框的 MFC 工程。</li><li>在对话框上绘制一个编辑框和一个排序按钮。</li><li>编辑框内可输入一系列的数,用空格分开。单击排序按钮,则对这些数由小到大排序,并重新显示在编辑框中。</li><li><strong>注:需自写排序算法。</strong></li></ul><h4 id="目录浏览"><a href="#目录浏览" class="headerlink" title="目录浏览"></a>目录浏览</h4><ul><li>新建基于对话框的 MFC 工程。</li><li>对话框左侧显示一个树控件,显示一个两层目录,一级目录为学校,二级为班级。对话框右侧显示一个 <code>LISTBOX</code>。</li><li>当在左侧选中不同的学校或班级,右侧 <code>LISTBOX</code> 刷新显示为本学校或本班级的所有学生姓名。</li></ul><h3 id="面试部分"><a href="#面试部分" class="headerlink" title="面试部分"></a>面试部分</h3><ul><li>简单做一个自我介绍 </li><li>什么时候开始学习 C++ 和开始使用 VC++</li><li>MFC 方面实际使用过哪些类</li><li>描述链表和数组的区别、优缺点,用过链表没,是否用过和了解 STL::map</li><li>英语是否过了4级,阅读过MSDN的英文材料有没有问题</li><li>目前还掌握了什么开发技能,是否自学的</li><li>是否接触过 CAD 软件</li><li>简单描述一个最能体现自身技术能力的典型项目或程序的实现</li><li>感兴趣、爱好、热爱,对于编程,你属于哪一种?为什么?</li><li>是否有哪方面能力特别优秀,在这方面特自信,自认为超过自己周围的人?</li><li>评价一下自己有哪些缺点</li><li>有什么兴趣爱好</li><li>后续有什么职业规划</li><li>转正薪资要求?试用期 3 个月是 80%</li><li>对于我们公司和这份工作有什么需要了解的</li></ul></body></html>]]></content>
<summary type="html"><p><a href="https://www.behance.net/gallery/117842739/Low-Poly-Game-Assets"><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE4/CODE4.png"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>GStarCAD 笔试和机试总结。笔试题主要考核 C++、代码阅读理解、基本几何运算、英文阅读能力,机试题主要考察 VC++、MFC 的实际编程操作能力。</p></summary>
<category term="编程技巧 (Coding Skill)" scheme="https://yousazoe.top/categories/%E7%BC%96%E7%A8%8B%E6%8A%80%E5%B7%A7-Coding-Skill/"/>
<category term="Cpp" scheme="https://yousazoe.top/tags/Cpp/"/>
</entry>
<entry>
<title>Google开源项目C++风格指南</title>
<link href="https://yousazoe.top/archives/bf0a0e19.html"/>
<id>https://yousazoe.top/archives/bf0a0e19.html</id>
<published>2022-10-23T14:12:06.000Z</published>
<updated>2022-11-21T13:56:54.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE3/X4uzvL.jpg"></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>Google 经常会发布一些开源项目, 意味着会接受来自其他代码贡献者的代码。但是如果代码贡献者的编程风格与 Google 的不一致, 会给代码阅读者和其他代码提交者造成不小的困扰。Google 因此发布了这份自己的编程风格指南, 使所有提交代码的人都能获知 Google 的编程风格。</p><span id="more"></span><p>C++ 是 Google 大部分开源项目的主要编程语言. 正如每个 C++ 程序员都知道的, C++ 有很多强大的特性, 但这种强大不可避免的导致它走向复杂,使代码更容易产生 bug, 难以阅读和维护.</p><p>本指南的目的是通过详细阐述 C++ 注意事项来驾驭其复杂性. 这些规则在保证代码易于管理的同时, 也能高效使用 C++ 的语言特性.</p><p>风格, 亦被称作可读性, 也就是指导 C++ 编程的约定. 使用术语 “风格” 有些用词不当, 因为这些习惯远不止源代码文件格式化这么简单.</p><p>使代码易于管理的方法之一是加强代码一致性. 让任何程序员都可以快速读懂你的代码这点非常重要. 保持统一编程风格并遵守约定意味着可以很容易根据 “模式匹配” 规则来推断各种标识符的含义. 创建通用, 必需的习惯用语和模式可以使代码更容易理解. 在一些情况下可能有充分的理由改变某些编程风格, 但我们还是应该遵循一致性原则,尽量不这么做.</p><p>本指南的另一个观点是 C++ 特性的臃肿. C++ 是一门包含大量高级特性的庞大语言. 某些情况下, 我们会限制甚至禁止使用某些特性. 这么做是为了保持代码清爽, 避免这些特性可能导致的各种问题. 指南中列举了这类特性, 并解释为什么这些特性被限制使用.</p><p>Google 主导的开源项目均符合本指南的规定.</p><p>注意: 本指南并非 C++ 教程, 我们假定读者已经对 C++ 非常熟悉.</p><h3 id="头文件"><a href="#头文件" class="headerlink" title="头文件"></a>头文件</h3><p>通常每一个 <code>.cc</code> 文件都有一个对应的 <code>.h</code> 文件. 也有一些常见例外, 如单元测试代码和只包含 <code>main()</code> 函数的 <code>.cc</code> 文件.</p><p>正确使用头文件可令代码在可读性、文件大小和性能上大为改观.</p><p>下面的规则将引导你规避使用头文件时的各种陷阱.</p><h4 id="Self-contained-头文件"><a href="#Self-contained-头文件" class="headerlink" title="Self-contained 头文件"></a>Self-contained 头文件</h4><blockquote><p>头文件应该能够自给自足(self-contained,也就是可以作为第一个头文件被引入),以 <code>.h</code> 结尾。至于用来插入文本的文件,说到底它们并不是头文件,所以应以 <code>.inc</code> 结尾。不允许分离出 <code>-inl.h</code> 头文件的做法.</p></blockquote><p>所有头文件要能够自给自足。换言之,用户和重构工具不需要为特别场合而包含额外的头文件。详言之,一个头文件要有 <code>#define</code> 保护,统统包含它所需要的其它头文件,也不要求定义任何特别 symbols.</p><p>不过有一个例外,即一个文件并不是 self-contained 的,而是作为文本插入到代码某处。或者,文件内容实际上是其它头文件的特定平台(platform-specific)扩展部分。这些文件就要用 <code>.inc</code> 文件扩展名。</p><p>如果 <code>.h</code> 文件声明了一个模板或内联函数,同时也在该文件加以定义。凡是有用到这些的 <code>.cc</code> 文件,就得统统包含该头文件,否则程序可能会在构建中链接失败。不要把这些定义放到分离的 <code>-inl.h</code> 文件里(译者注:过去该规范曾提倡把定义放到 <code>-inl.h</code> 里过)。</p><p>有个例外:如果某函数模板为所有相关模板参数显式实例化,或本身就是某类的一个私有成员,那么它就只能定义在实例化该模板的 <code>.cc</code> 文件里。</p><h4 id="define-保护"><a href="#define-保护" class="headerlink" title="#define 保护"></a>#define 保护</h4><blockquote><p>所有头文件都应该有 <code>#define</code> 保护来防止头文件被多重包含, 命名格式当是: <code><PROJECT>_<PATH>_<FILE>_H_</code> .</p></blockquote><p>为保证唯一性, 头文件的命名应该基于所在项目源代码树的全路径. 例如, 项目 <code>foo</code> 中的头文件 <code>foo/src/bar/baz.h</code> 可按如下方式保护:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifndef</span> FOO_BAR_BAZ_H_</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> FOO_BAR_BAZ_H_</span></span><br><span class="line">...</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">// FOO_BAR_BAZ_H_</span></span></span><br></pre></td></tr></tbody></table></figure><h4 id="前置声明"><a href="#前置声明" class="headerlink" title="前置声明"></a>前置声明</h4><blockquote><p>尽可能地避免使用前置声明。使用 <code>#include</code> 包含需要的头文件即可。</p></blockquote><h5 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h5><p>所谓「前置声明」(forward declaration)是类、函数和模板的纯粹声明,没伴随着其定义.</p><h5 id="优点"><a href="#优点" class="headerlink" title="优点"></a><strong>优点</strong></h5><ul><li>前置声明能够节省编译时间,多余的 <code>#include</code> 会迫使编译器展开更多的文件,处理更多的输入。</li><li>前置声明能够节省不必要的重新编译的时间。 <code>#include</code> 使代码因为头文件中无关的改动而被重新编译多次。</li></ul><h5 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h5><ul><li>前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。</li></ul><ul><li>前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其 API. 例如扩大形参类型,加个自带默认参数的模板形参等等。</li><li>前置声明来自命名空间 <code>std::</code> 的 symbol 时,其行为未定义。</li><li>很难判断什么时候该用前置声明,什么时候该用 <code>#include</code> 。极端情况下,用前置声明代替 <code>#include</code> 甚至都会暗暗地改变代码的含义:</li></ul><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// b.h:</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">B</span> {};</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">D</span> : B {};</span><br><span class="line"></span><br><span class="line"><span class="comment">// good_user.cc:</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"b.h"</span></span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(B*)</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(<span class="type">void</span>*)</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">test</span><span class="params">(D* x)</span> </span>{ <span class="built_in">f</span>(x); } <span class="comment">// calls f(B*)</span></span><br></pre></td></tr></tbody></table></figure><p>如果 <code>#include</code> 被 <code>B</code> 和 <code>D</code> 的前置声明替代, <code>test()</code> 就会调用 <code>f(void*)</code> .</p><ul><li>前置声明了不少来自头文件的 symbol 时,就会比单单一行的 <code>include</code> 冗长。</li><li>仅仅为了能前置声明而重构代码(比如用指针成员代替对象成员)会使代码变得更慢更复杂.</li></ul><h4 id="内联函数"><a href="#内联函数" class="headerlink" title="内联函数"></a>内联函数</h4><blockquote><p>只有当函数只有 10 行甚至更少时才将其定义为内联函数.</p></blockquote><h5 id="定义-1"><a href="#定义-1" class="headerlink" title="定义"></a>定义</h5><p>当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用.</p><h5 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h5><p>只要内联的函数体较小, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.</p><h5 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h5><p>滥用内联将导致程序变得更慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。</p><h5 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h5><p>一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!</p><p>另一个实用的经验准则: 内联那些包含循环或 <code>switch</code> 语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 <code>switch</code> 语句从不被执行).</p><p>有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常内联. 通常, 递归函数不应该声明成内联函数.(YuleFox 注: 递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数.</p><h4 id="include-的路径及顺序"><a href="#include-的路径及顺序" class="headerlink" title="#include 的路径及顺序"></a><code>#include</code> 的路径及顺序</h4><blockquote><p>使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖: 相关头文件, C 库, C++ 库, 其他库的 .h, 本项目内的 .h.</p></blockquote><p>项目内头文件应按照项目源代码目录树结构排列, 避免使用 UNIX 特殊的快捷目录 <code>.</code> (当前目录) 或 <code>..</code> (上级目录). 例如, <code>google-awesome-project/src/base/logging.h</code> 应该按如下方式包含:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"base/logging.h"</span></span></span><br></pre></td></tr></tbody></table></figure><p>又如, <code>dir/foo.cc</code> 或 <code>dir/foo_test.cc</code> 的主要作用是实现或测试 <code>dir2/foo2.h</code> 的功能, <code>foo.cc</code> 中包含头文件的次序如下:</p><blockquote><ol><li><code>dir2/foo2.h</code> (优先位置, 详情如下)</li><li>C 系统文件</li><li>C++ 系统文件</li><li>其他库的 <code>.h</code> 文件</li><li>本项目内 <code>.h</code> 文件</li></ol></blockquote><p>这种优先的顺序排序保证当 <code>dir2/foo2.h</code> 遗漏某些必要的库时, <code>dir/foo.cc</code> 或 <code>dir/foo_test.cc</code> 的构建会立刻中止。因此这一条规则保证维护这些文件的人们首先看到构建中止的消息而不是维护其他包的人们。</p><p><code>dir/foo.cc</code> 和 <code>dir2/foo2.h</code> 通常位于同一目录下 (如 <code>base/basictypes_unittest.cc</code> 和 <code>base/basictypes.h</code>), 但也可以放在不同目录下.</p><p>按字母顺序分别对每种类型的头文件进行二次排序是不错的主意。注意较老的代码可不符合这条规则,要在方便的时候改正它们。</p><p>您所依赖的符号 (symbols) 被哪些头文件所定义,您就应该包含(include)哪些头文件,前置声明 (forward declarations) 情况除外。比如您要用到 <code>bar.h</code> 中的某个符号, 哪怕您所包含的 <code>foo.h</code> 已经包含了 <code>bar.h</code>, 也照样得包含 <code>bar.h</code>, 除非 <code>foo.h</code> 有明确说明它会自动向您提供 <code>bar.h</code> 中的 symbol. 不过,凡是 cc 文件所对应的「相关头文件」已经包含的,就不用再重复包含进其 cc 文件里面了,就像 <code>foo.cc</code> 只包含 <code>foo.h</code> 就够了,不用再管后者所包含的其它内容。</p><p>举例来说, <code>google-awesome-project/src/foo/internal/fooserver.cc</code> 的包含次序如下:</p><blockquote><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"foo/public/fooserver.h"</span> <span class="comment">// 优先位置</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sys/types.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><unistd.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><hash_map></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><vector></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"base/basictypes.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"base/commandlineflags.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"foo/public/bar.h"</span></span></span><br></pre></td></tr></tbody></table></figure></blockquote><p><strong>例外:</strong></p><p>有时,平台特定(system-specific)代码需要条件编译(conditional includes),这些代码可以放到其它 includes 之后。当然,您的平台特定代码也要够简练且独立,比如:</p><blockquote><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"foo/public/fooserver.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"base/port.h"</span> <span class="comment">// For LANG_CXX11.</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> LANG_CXX11</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><initializer_list></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">// LANG_CXX11</span></span></span><br></pre></td></tr></tbody></table></figure></blockquote><h5 id="译者-YuleFox-笔记"><a href="#译者-YuleFox-笔记" class="headerlink" title="译者 (YuleFox) 笔记"></a>译者 (YuleFox) 笔记</h5><ol><li>避免多重包含是学编程时最基本的要求;</li><li>前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;</li><li>内联函数的合理使用可提高代码执行效率;</li><li><code>-inl.h</code> 可提高代码可读性 (一般用不到吧:D);</li><li>标准化函数参数顺序可以提高可读性和易维护性 (对函数参数的堆栈空间有轻微影响, 我以前大多是相同类型放在一起);</li><li>包含文件的名称使用 <code>.</code> 和 <code>..</code> 虽然方便却易混乱, 使用比较完整的项目路径看上去很清晰, 很条理, 包含文件的次序除了美观之外, 最重要的是可以减少隐藏依赖, 使每个头文件在 “最需要编译” (对应源文件处 :D) 的地方编译, 有人提出库文件放在最后, 这样出错先是项目内的文件, 头文件都放在对应源文件的最前面, 这一点足以保证内部错误的及时发现了.</li></ol><h5 id="译者(acgtyrant)笔记"><a href="#译者(acgtyrant)笔记" class="headerlink" title="译者(acgtyrant)笔记"></a>译者(acgtyrant)笔记</h5><ol><li>原来还真有项目用 <code>#includes</code> 来插入文本,且其文件扩展名 <code>.inc</code> 看上去也很科学。</li><li>Google 已经不再提倡 <code>-inl.h</code> 用法。</li><li>注意,前置声明的类是不完全类型(incomplete type),我们只能定义指向该类型的指针或引用,或者声明(但不能定义)以不完全类型作为参数或者返回类型的函数。毕竟编译器不知道不完全类型的定义,我们不能创建其类的任何对象,也不能声明成类内部的数据成员。</li><li>类内部的函数一般会自动内联。所以某函数一旦不需要内联,其定义就不要再放在头文件里,而是放到对应的 <code>.cc</code> 文件里。这样可以保持头文件的类相当精炼,也很好地贯彻了声明与定义分离的原则。</li><li>在 <code>#include</code> 中插入空行以分割相关头文件, C 库, C++ 库, 其他库的 <code>.h</code> 和本项目内的 <code>.h</code> 是个好习惯。</li></ol><h3 id="作用域"><a href="#作用域" class="headerlink" title="作用域"></a>作用域</h3><h4 id="命名空间"><a href="#命名空间" class="headerlink" title="命名空间"></a>命名空间</h4><p>鼓励在 <code>.cc</code> 文件内使用匿名命名空间或 <code>static</code> 声明. 使用具名的命名空间时, 其名称可基于项目名或相对路径. 禁止使用 using 指示(using-directive)。禁止使用内联命名空间(inline namespace)。</p><h5 id="定义-2"><a href="#定义-2" class="headerlink" title="定义"></a>定义</h5><p>命名空间将全局作用域细分为独立的, 具名的作用域, 可有效防止全局作用域的命名冲突.</p><h5 id="优点-2"><a href="#优点-2" class="headerlink" title="优点"></a>优点</h5><p>虽然类已经提供了(可嵌套的)命名轴线 (YuleFox 注: 将命名分割在不同类的作用域内), 命名空间在这基础上又封装了一层.</p><p>举例来说, 两个不同项目的全局作用域都有一个类 <code>Foo</code>, 这样在编译或运行时造成冲突. 如果每个项目将代码置于不同命名空间中, <code>project1::Foo</code> 和 <code>project2::Foo</code> 作为不同符号自然不会冲突.</p><p>内联命名空间会自动把内部的标识符放到外层作用域,比如:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">namespace</span> X {</span><br><span class="line"><span class="keyword">inline</span> <span class="keyword">namespace</span> Y {</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">foo</span><span class="params">()</span></span>;</span><br><span class="line">} <span class="comment">// namespace Y</span></span><br><span class="line">} <span class="comment">// namespace X</span></span><br></pre></td></tr></tbody></table></figure><p><code>X::Y::foo()</code> 与 <code>X::foo()</code> 彼此可代替。内联命名空间主要用来保持跨版本的 ABI 兼容性。</p><h5 id="缺点-2"><a href="#缺点-2" class="headerlink" title="缺点"></a>缺点</h5><p>命名空间具有迷惑性, 因为它们使得区分两个相同命名所指代的定义更加困难。</p><p>内联命名空间很容易令人迷惑,毕竟其内部的成员不再受其声明所在命名空间的限制。内联命名空间只在大型版本控制里有用。</p><p>有时候不得不多次引用某个定义在许多嵌套命名空间里的实体,使用完整的命名空间会导致代码的冗长。</p><p>在头文件中使用匿名空间导致违背 C++ 的唯一定义原则 (One Definition Rule (ODR)).</p><h5 id="结论-1"><a href="#结论-1" class="headerlink" title="结论"></a>结论</h5><p>根据下文将要提到的策略合理使用命名空间.</p><ul><li>遵守命名空间命名中的规则。</li><li>像之前的几个例子中一样,在命名空间的最后注释出命名空间的名字。</li><li>用命名空间把文件包含, <a href="https://gflags.github.io/gflags/">gflags</a> 的声明/定义, 以及类的前置声明以外的整个源文件封装起来, 以区别于其它命名空间:<blockquote><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// .h 文件</span></span><br><span class="line"><span class="keyword">namespace</span> mynamespace {</span><br><span class="line"></span><br><span class="line"><span class="comment">// 所有声明都置于命名空间中</span></span><br><span class="line"><span class="comment">// 注意不要使用缩进</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyClass</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> ...</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Foo</span><span class="params">()</span></span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">} <span class="comment">// namespace mynamespace</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// .cc 文件</span></span><br><span class="line"><span class="keyword">namespace</span> mynamespace {</span><br><span class="line"></span><br><span class="line"><span class="comment">// 函数定义都置于命名空间中</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">MyClass::Foo</span><span class="params">()</span> </span>{</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">} <span class="comment">// namespace mynamespace</span></span><br></pre></td></tr></tbody></table></figure><p>更复杂的 <code>.cc</code> 文件包含更多, 更复杂的细节, 比如 gflags 或 using 声明。</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"a.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="built_in">DEFINE_FLAG</span>(<span class="type">bool</span>, someflag, <span class="literal">false</span>, <span class="string">"dummy flag"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> a {</span><br><span class="line"></span><br><span class="line">...code <span class="keyword">for</span> a... <span class="comment">// 左对齐</span></span><br><span class="line"></span><br><span class="line">} <span class="comment">// namespace a</span></span><br></pre></td></tr></tbody></table></figure></blockquote></li><li>不要在命名空间 <code>std</code> 内声明任何东西, 包括标准库的类前置声明. 在 <code>std</code> 命名空间声明实体是未定义的行为, 会导致如不可移植. 声明标准库下的实体, 需要包含对应的头文件.</li><li>不应该使用 <em>using 指示</em> 引入整个命名空间的标识符号。<blockquote><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 禁止 —— 污染命名空间</span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> foo;</span><br></pre></td></tr></tbody></table></figure></blockquote></li><li>不要在头文件中使用 <em>命名空间别名</em> 除非显式标记内部命名空间使用。因为任何在头文件中引入的命名空间都会成为公开API的一部分。<blockquote><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 在 .cc 中使用别名缩短常用的命名空间</span></span><br><span class="line"><span class="keyword">namespace</span> baz = ::foo::bar::baz;</span><br></pre></td></tr></tbody></table></figure><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 在 .h 中使用别名缩短常用的命名空间</span></span><br><span class="line"><span class="keyword">namespace</span> librarian {</span><br><span class="line"><span class="keyword">namespace</span> impl { <span class="comment">// 仅限内部使用</span></span><br><span class="line"><span class="keyword">namespace</span> sidetable = ::pipeline_diagnostics::sidetable;</span><br><span class="line">} <span class="comment">// namespace impl</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">my_inline_function</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 限制在一个函数中的命名空间别名</span></span><br><span class="line"> <span class="keyword">namespace</span> baz = ::foo::bar::baz;</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line">} <span class="comment">// namespace librarian</span></span><br></pre></td></tr></tbody></table></figure></blockquote></li><li>禁止用内联命名空间</li></ul><h4 id="匿名命名空间和静态变量"><a href="#匿名命名空间和静态变量" class="headerlink" title="匿名命名空间和静态变量"></a>匿名命名空间和静态变量</h4><blockquote><p>在 <code>.cc</code> 文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为 <code>static</code> 。但是不要在 <code>.h</code> 文件中这么做。</p></blockquote><h5 id="定义-3"><a href="#定义-3" class="headerlink" title="定义"></a>定义</h5><p>所有置于匿名命名空间的声明都具有内部链接性,函数和变量可以经由声明为 <code>static</code> 拥有内部链接性,这意味着你在这个文件中声明的这些标识符都不能在另一个文件中被访问。即使两个文件声明了完全一样名字的标识符,它们所指向的实体实际上是完全不同的。</p><h5 id="结论-2"><a href="#结论-2" class="headerlink" title="结论"></a>结论</h5><p>推荐、鼓励在 <code>.cc</code> 中对于不需要在其他地方引用的标识符使用内部链接性声明,但是不要在 <code>.h</code> 中使用。</p><p>匿名命名空间的声明和具名的格式相同,在最后注释上 <code>namespace</code> :</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">namespace</span> {</span><br><span class="line">...</span><br><span class="line">} <span class="comment">// namespace</span></span><br></pre></td></tr></tbody></table></figure><h4 id="非成员函数、静态成员函数和全局函数"><a href="#非成员函数、静态成员函数和全局函数" class="headerlink" title="非成员函数、静态成员函数和全局函数"></a>非成员函数、静态成员函数和全局函数</h4><blockquote><p>使用静态成员函数或命名空间内的非成员函数, 尽量不要用裸的全局函数. 将一系列函数直接置于命名空间中,不要用类的静态方法模拟出命名空间的效果,类的静态方法应当和类的实例或静态数据紧密相关.</p></blockquote><h5 id="优点-3"><a href="#优点-3" class="headerlink" title="优点:"></a>优点:</h5><p>某些情况下, 非成员函数和静态成员函数是非常有用的, 将非成员函数放在命名空间内可避免污染全局作用域.</p><h5 id="缺点-3"><a href="#缺点-3" class="headerlink" title="缺点"></a>缺点</h5><p>将非成员函数和静态成员函数作为新类的成员或许更有意义, 当它们需要访问外部资源或具有重要的依赖关系时更是如此.</p><h5 id="结论-3"><a href="#结论-3" class="headerlink" title="结论"></a>结论</h5><p>有时, 把函数的定义同类的实例脱钩是有益的, 甚至是必要的. 这样的函数可以被定义成静态成员, 或是非成员函数. 非成员函数不应依赖于外部变量, 应尽量置于某个命名空间内. 相比单纯为了封装若干不共享任何静态数据的静态成员函数而创建类, 不如使用 2.1. 命名空间。举例而言,对于头文件 <code>myproject/foo_bar.h</code> , 应当使用</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">namespace</span> myproject {</span><br><span class="line"><span class="keyword">namespace</span> foo_bar {</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Function1</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Function2</span><span class="params">()</span></span>;</span><br><span class="line">} <span class="comment">// namespace foo_bar</span></span><br><span class="line">} <span class="comment">// namespace myproject</span></span><br></pre></td></tr></tbody></table></figure><p>而非</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">namespace</span> myproject {</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FooBar</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">Function1</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">Function2</span><span class="params">()</span></span>;</span><br><span class="line">};</span><br><span class="line">} <span class="comment">// namespace myproject</span></span><br></pre></td></tr></tbody></table></figure><p>定义在同一编译单元的函数, 被其他编译单元直接调用可能会引入不必要的耦合和链接时依赖; 静态成员函数对此尤其敏感. 可以考虑提取到新类中, 或者将函数置于独立库的命名空间内.</p><p>如果你必须定义非成员函数, 又只是在 <code>.cc</code> 文件中使用它, 可使用匿名 2.1. 命名空间 或 <code>static</code> 链接关键字 (如 <code>static int Foo() {...}</code>) 限定其作用域.</p><h4 id="局部变量"><a href="#局部变量" class="headerlink" title="局部变量"></a>局部变量</h4><blockquote><p>将函数变量尽可能置于最小作用域内, 并在变量声明时进行初始化.</p></blockquote><p>C++ 允许在函数的任何位置声明变量. 我们提倡在尽可能小的作用域中声明变量, 离第一次使用越近越好. 这使得代码浏览者更容易定位变量声明的位置, 了解变量的类型和初始值. 特别是,应使用初始化的方式替代声明再赋值, 比如:</p><blockquote><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">int</span> i;</span><br><span class="line">i = <span class="built_in">f</span>(); <span class="comment">// 坏——初始化和声明分离</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">int</span> j = <span class="built_in">g</span>(); <span class="comment">// 好——初始化时声明</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">vector<<span class="type">int</span>> v;</span><br><span class="line">v.<span class="built_in">push_back</span>(<span class="number">1</span>); <span class="comment">// 用花括号初始化更好</span></span><br><span class="line">v.<span class="built_in">push_back</span>(<span class="number">2</span>);</span><br></pre></td></tr></tbody></table></figure><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">vector<<span class="type">int</span>> v = {<span class="number">1</span>, <span class="number">2</span>}; <span class="comment">// 好——v 一开始就初始化</span></span><br></pre></td></tr></tbody></table></figure></blockquote><p>属于 <code>if</code>, <code>while</code> 和 <code>for</code> 语句的变量应当在这些语句中正常地声明,这样子这些变量的作用域就被限制在这些语句中了,举例而言:</p><blockquote><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">while</span> (<span class="type">const</span> <span class="type">char</span>* p = <span class="built_in">strchr</span>(str, <span class="string">'/'</span>)) str = p + <span class="number">1</span>;</span><br></pre></td></tr></tbody></table></figure></blockquote><p>有一个例外, 如果变量是一个对象, 每次进入作用域都要调用其构造函数, 每次退出作用域都要调用其析构函数. 这会导致效率降低.</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">// 低效的实现</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">1000000</span>; ++i) {</span><br><span class="line"> Foo f; <span class="comment">// 构造函数和析构函数分别调用 1000000 次!</span></span><br><span class="line"> f.<span class="built_in">DoSomething</span>(i);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在循环作用域外面声明这类变量要高效的多:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">Foo f; <span class="comment">// 构造函数和析构函数只调用 1 次</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">1000000</span>; ++i) {</span><br><span class="line"> f.<span class="built_in">DoSomething</span>(i);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="静态和全局变量"><a href="#静态和全局变量" class="headerlink" title="静态和全局变量"></a>静态和全局变量</h4><blockquote><p>禁止定义静态储存周期非POD变量,禁止使用含有副作用的函数初始化POD全局变量,因为多编译单元中的静态变量执行时的构造和析构顺序是未明确的,这将导致代码的不可移植。</p></blockquote><p>禁止使用类的静态储存周期变量:由于构造和析构函数调用顺序的不确定性,它们会导致难以发现的 bug 。不过 <code>constexpr</code> 变量除外,毕竟它们又不涉及动态初始化或析构。</p><p>静态生存周期的对象,即包括了全局变量,静态变量,静态类成员变量和函数静态变量,都必须是原生数据类型 (POD : Plain Old Data): 即 int, char 和 float, 以及 POD 类型的指针、数组和结构体。</p><p>静态变量的构造函数、析构函数和初始化的顺序在 C++ 中是只有部分明确的,甚至随着构建变化而变化,导致难以发现的 bug. 所以除了禁用类类型的全局变量,我们也不允许用函数返回值来初始化 POD 变量,除非该函数(比如 <code>getenv()</code> 或 <code>getpid()</code> )不涉及任何全局变量。函数作用域里的静态变量除外,毕竟它的初始化顺序是有明确定义的,而且只会在指令执行到它的声明那里才会发生。</p><blockquote><p>Xris 译注:</p><p>同一个编译单元内是明确的,静态初始化优先于动态初始化,初始化顺序按照声明顺序进行,销毁则逆序。不同的编译单元之间初始化和销毁顺序属于未明确行为 (unspecified behaviour)。</p></blockquote><p>同理,全局和静态变量在程序中断时会被析构,无论所谓中断是从 <code>main()</code> 返回还是对 <code>exit()</code> 的调用。析构顺序正好与构造函数调用的顺序相反。但既然构造顺序未定义,那么析构顺序当然也就不定了。比如,在程序结束时某静态变量已经被析构了,但代码还在跑——比如其它线程——并试图访问它且失败;再比如,一个静态 string 变量也许会在一个引用了前者的其它变量析构之前被析构掉。</p><p>改善以上析构问题的办法之一是用 <code>quick_exit()</code> 来代替 <code>exit()</code> 并中断程序。它们的不同之处是前者不会执行任何析构,也不会执行 <code>atexit()</code> 所绑定的任何 handlers. 如果您想在执行 <code>quick_exit()</code> 来中断时执行某 handler(比如刷新 log),您可以把它绑定到 <code>_at_quick_exit()</code>. 如果您想在 <code>exit()</code> 和 <code>quick_exit()</code> 都用上该 handler, 都绑定上去。</p><p>综上所述,我们只允许 POD 类型的静态变量,即完全禁用 <code>vector</code> (使用 C 数组替代) 和 <code>string</code> (使用 <code>const char[]</code>)。</p><p>如果您确实需要一个 <code>class</code> 类型的静态或全局变量,可以考虑在 <code>main()</code> 函数或 <code>pthread_once()</code> 内初始化一个指针且永不回收。注意只能用 raw 指针,别用智能指针,毕竟后者的析构函数涉及到上文指出的不定顺序问题。</p><blockquote><p>Yang.Y 译注:</p><p>上文提及的静态变量泛指静态生存周期的对象, 包括: 全局变量, 静态变量, 静态类成员变量, 以及函数静态变量.</p></blockquote><h4 id="译者-YuleFox-笔记-1"><a href="#译者-YuleFox-笔记-1" class="headerlink" title="译者 (YuleFox) 笔记"></a>译者 (YuleFox) 笔记</h4><ol><li><code>cc</code>中的匿名命名空间可避免命名冲突, 限定作用域, 避免直接使用<code>using</code> 关键字污染命名空间;</li><li>嵌套类符合局部使用原则, 只是不能在其他头文件中前置声明, 尽量不要 <code>public</code>;</li><li>尽量不用全局函数和全局变量, 考虑作用域和命名空间限制, 尽量单独形成编译单元;</li><li>多线程中的全局变量 (含静态成员变量) 不要使用 <code>class</code> 类型 (含 STL 容器), 避免不明确行为导致的 bug.</li><li>作用域的使用, 除了考虑名称污染, 可读性之外, 主要是为降低耦合, 提高编译/执行效率.</li></ol><h4 id="译者(acgtyrant)笔记-1"><a href="#译者(acgtyrant)笔记-1" class="headerlink" title="译者(acgtyrant)笔记"></a>译者(acgtyrant)笔记</h4><ol><li>注意「using 指示(using-directive)」和「using 声明(using-declaration)」的区别。</li><li>匿名命名空间说白了就是文件作用域,就像 C static 声明的作用域一样,后者已经被 C++ 标准提倡弃用。</li><li>局部变量在声明的同时进行显式值初始化,比起隐式初始化再赋值的两步过程要高效,同时也贯彻了计算机体系结构重要的概念「局部性(locality)」。</li><li>注意别在循环犯大量构造和析构的低级错误。</li></ol><h3 id="类"><a href="#类" class="headerlink" title="类"></a>类</h3><h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h3><h4 id="输入和输出"><a href="#输入和输出" class="headerlink" title="输入和输出"></a>输入和输出</h4><h5 id="总述"><a href="#总述" class="headerlink" title="总述"></a>总述</h5><p>我们倾向于按值返回, 否则按引用返回。 避免返回指针, 除非它可以为空.</p><h5 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h5><p>C++ 函数由返回值提供天然的输出, 有时也通过输出参数(或输入/输出参数)提供. 我们倾向于使用返回值而不是输出参数: 它们提高了可读性, 并且通常提供相同或更好的性能.</p><p>C/C++ 中的函数参数或者是函数的输入, 或者是函数的输出, 或兼而有之. 非可选输入参数通常是值参或 <code>const</code> 引用, 非可选输出参数或输入/输出参数通常应该是引用 (不能为空). 对于可选的参数, 通常使用 <code>std::optional</code> 来表示可选的按值输入, 使用 <code>const</code> 指针来表示可选的其他输入. 使用非常量指针来表示可选输出和可选输入/输出参数.</p><p>避免定义需要 <code>const</code> 引用参数去超出生命周期的函数, 因为 <code>const</code> 引用参数与临时变量绑定. 相反, 要找到某种方法来消除生命周期要求 (例如, 通过复制参数), 或者通过 <code>const</code> 指针传递它并记录生命周期和非空要求.</p><p>在排序函数参数时, 将所有输入参数放在所有输出参数之前. 特别要注意, 在加入新参数时不要因为它们是新参数就置于参数列表最后, 而是仍然要按照前述的规则, 即将新的输入参数也置于输出参数之前.</p><p>这并非一个硬性规定. 输入/输出参数 (通常是类或结构体) 让这个问题变得复杂. 并且, 有时候为了其他函数保持一致, 你可能不得不有所变通.</p><h4 id="编写简短函数"><a href="#编写简短函数" class="headerlink" title="编写简短函数"></a>编写简短函数</h4><h5 id="总述-1"><a href="#总述-1" class="headerlink" title="总述"></a>总述</h5><p>我们倾向于编写简短, 凝练的函数.</p><h5 id="说明-1"><a href="#说明-1" class="headerlink" title="说明"></a>说明</h5><p>我们承认长函数有时是合理的, 因此并不硬性限制函数的长度. 如果函数超过 40 行, 可以思索一下能不能在不影响程序结构的前提下对其进行分割.</p><p>即使一个长函数现在工作的非常好, 一旦有人对其修改, 有可能出现新的问题, 甚至导致难以发现的 bug. 使函数尽量简短, 以便于他人阅读和修改代码.</p><p>在处理代码时, 你可能会发现复杂的长函数. 不要害怕修改现有代码: 如果证实这些代码使用 / 调试起来很困难, 或者你只需要使用其中的一小段代码, 考虑将其分割为更加简短并易于管理的若干函数.</p><h4 id="引用参数"><a href="#引用参数" class="headerlink" title="引用参数"></a>引用参数</h4><h5 id="总述-2"><a href="#总述-2" class="headerlink" title="总述"></a>总述</h5><p>所有按引用传递的参数必须加上 <code>const</code>.</p><h5 id="定义-4"><a href="#定义-4" class="headerlink" title="定义"></a>定义</h5><p>在 C 语言中, 如果函数需要修改变量的值, 参数必须为指针, 如 <code>int foo(int* pval)</code>. 在 C++ 中, 函数还可以声明为引用参数: <code>int foo(int &val)</code>.</p><h5 id="优点-4"><a href="#优点-4" class="headerlink" title="优点"></a>优点</h5><p>定义引用参数可以防止出现 <code>(*pval)++</code> 这样丑陋的代码. 引用参数对于拷贝构造函数这样的应用也是必需的. 同时也更明确地不接受空指针.</p><h5 id="缺点-4"><a href="#缺点-4" class="headerlink" title="缺点"></a>缺点</h5><p>容易引起误解, 因为引用在语法上是值变量却拥有指针的语义.</p><h5 id="结论-4"><a href="#结论-4" class="headerlink" title="结论"></a>结论</h5><p>函数参数列表中, 所有引用参数都必须是 <code>const</code>:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Foo</span><span class="params">(<span class="type">const</span> string &in, string *out)</span></span>;</span><br></pre></td></tr></tbody></table></figure><p>事实上这在 Google Code 是一个硬性约定: 输入参数是值参或 <code>const</code> 引用, 输出参数为指针. 输入参数可以是 <code>const</code> 指针, 但决不能是非 <code>const</code> 的引用参数, 除非特殊要求, 比如 <code>swap()</code>.</p><p>有时候, 在输入形参中用 <code>const T*</code> 指针比 <code>const T&</code> 更明智. 比如:</p><ul><li>可能会传递空指针.</li><li>函数要把指针或对地址的引用赋值给输入形参.</li></ul><p>总而言之, 大多时候输入形参往往是 <code>const T&</code>. 若用 <code>const T*</code> 则说明输入另有处理. 所以若要使用 <code>const T*</code>, 则应给出相应的理由, 否则会使得读者感到迷惑.</p><h4 id="函数重载"><a href="#函数重载" class="headerlink" title="函数重载"></a>函数重载</h4><h5 id="总述-3"><a href="#总述-3" class="headerlink" title="总述"></a>总述</h5><p>若要使用函数重载, 则必须能让读者一看调用点就胸有成竹, 而不用花心思猜测调用的重载函数到底是哪一种. 这一规则也适用于构造函数.</p><h5 id="定义-5"><a href="#定义-5" class="headerlink" title="定义"></a>定义</h5><p>你可以编写一个参数类型为 <code>const string&</code> 的函数, 然后用另一个参数类型为 <code>const char*</code> 的函数对其进行重载:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyClass</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Analyze</span><span class="params">(<span class="type">const</span> string &text)</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Analyze</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *text, <span class="type">size_t</span> textlen)</span></span>;</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><h5 id="优点-5"><a href="#优点-5" class="headerlink" title="优点"></a>优点</h5><p>通过重载参数不同的同名函数, 可以令代码更加直观. 模板化代码需要重载, 这同时也能为使用者带来便利.</p><h5 id="缺点-5"><a href="#缺点-5" class="headerlink" title="缺点"></a>缺点</h5><p>如果函数单靠不同的参数类型而重载 (acgtyrant 注:这意味着参数数量不变), 读者就得十分熟悉 C++ 五花八门的匹配规则, 以了解匹配过程具体到底如何. 另外, 如果派生类只重载了某个函数的部分变体, 继承语义就容易令人困惑.</p><h5 id="结论-5"><a href="#结论-5" class="headerlink" title="结论"></a>结论</h5><p>如果打算重载一个函数, 可以试试改在函数名里加上参数信息. 例如, 用 <code>AppendString()</code> 和 <code>AppendInt()</code> 等, 而不是一口气重载多个 <code>Append()</code>. 如果重载函数的目的是为了支持不同数量的同一类型参数, 则优先考虑使用 <code>std::vector</code> 以便使用者可以用列表初始化指定参数.</p><h4 id="缺省参数"><a href="#缺省参数" class="headerlink" title="缺省参数"></a>缺省参数</h4><h5 id="总述-4"><a href="#总述-4" class="headerlink" title="总述"></a>总述</h5><p>只允许在非虚函数中使用缺省参数, 且必须保证缺省参数的值始终一致. 缺省参数与函数重载遵循同样的规则. 一般情况下建议使用函数重载, 尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺点的情况下.</p><h5 id="优点-6"><a href="#优点-6" class="headerlink" title="优点"></a>优点</h5><p>有些函数一般情况下使用默认参数, 但有时需要又使用非默认的参数. 缺省参数为这样的情形提供了便利, 使程序员不需要为了极少的例外情况编写大量的函数. 和函数重载相比, 缺省参数的语法更简洁明了, 减少了大量的样板代码, 也更好地区别了 “必要参数” 和 “可选参数”.</p><h5 id="缺点-6"><a href="#缺点-6" class="headerlink" title="缺点"></a>缺点</h5><p>缺省参数实际上是函数重载语义的另一种实现方式, 因此所有不应当使用函数重载的理由也都适用于缺省参数.</p><p>虚函数调用的缺省参数取决于目标对象的静态类型, 此时无法保证给定函数的所有重载声明的都是同样的缺省参数.</p><p>缺省参数是在每个调用点都要进行重新求值的, 这会造成生成的代码迅速膨胀. 作为读者, 一般来说也更希望缺省的参数在声明时就已经被固定了, 而不是在每次调用时都可能会有不同的取值.</p><p>缺省参数会干扰函数指针, 导致函数签名与调用点的签名不一致. 而函数重载不会导致这样的问题.</p><h5 id="结论-6"><a href="#结论-6" class="headerlink" title="结论"></a>结论</h5><p>对于虚函数, 不允许使用缺省参数, 因为在虚函数中缺省参数不一定能正常工作. 如果在每个调用点缺省参数的值都有可能不同, 在这种情况下缺省函数也不允许使用. (例如, 不要写像 <code>void f(int n = counter++);</code> 这样的代码.)</p><p>在其他情况下, 如果缺省参数对可读性的提升远远超过了以上提及的缺点的话, 可以使用缺省参数. 如果仍有疑惑, 就使用函数重载.</p><h4 id="函数返回类型后置语法"><a href="#函数返回类型后置语法" class="headerlink" title="函数返回类型后置语法"></a>函数返回类型后置语法</h4><h5 id="总述-5"><a href="#总述-5" class="headerlink" title="总述"></a>总述</h5><p>只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法.</p><h5 id="定义-6"><a href="#定义-6" class="headerlink" title="定义"></a>定义</h5><p>C++ 现在允许两种不同的函数声明方式. 以往的写法是将返回类型置于函数名之前. 例如:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">foo</span><span class="params">(<span class="type">int</span> x)</span></span>;</span><br></pre></td></tr></tbody></table></figure><p>C++11 引入了这一新的形式. 现在可以在函数名前使用 <code>auto</code> 关键字, 在参数列表之后后置返回类型. 例如:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">foo</span><span class="params">(<span class="type">int</span> x)</span> -> <span class="type">int</span></span>;</span><br></pre></td></tr></tbody></table></figure><p>后置返回类型为函数作用域. 对于像 <code>int</code> 这样简单的类型, 两种写法没有区别. 但对于复杂的情况, 例如类域中的类型声明或者以函数参数的形式书写的类型, 写法的不同会造成区别.</p><h5 id="优点-7"><a href="#优点-7" class="headerlink" title="优点"></a>优点</h5><p>后置返回类型是显式地指定Lambda 表达式的返回值的唯一方式. 某些情况下, 编译器可以自动推导出 Lambda 表达式的返回类型, 但并不是在所有的情况下都能实现. 即使编译器能够自动推导, 显式地指定返回类型也能让读者更明了.</p><p>有时在已经出现了的函数参数列表之后指定返回类型, 能够让书写更简单, 也更易读, 尤其是在返回类型依赖于模板参数时. 例如:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>, <span class="keyword">class</span> <span class="title class_">U</span>> <span class="function"><span class="keyword">auto</span> <span class="title">add</span><span class="params">(T t, U u)</span> -> <span class="title">decltype</span><span class="params">(t + u)</span></span>;</span><br></pre></td></tr></tbody></table></figure><p>对比下面的例子:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>, <span class="keyword">class</span> <span class="title class_">U</span>> <span class="keyword">decltype</span>(<span class="built_in">declval</span><T&>() + <span class="built_in">declval</span><U&>()) <span class="built_in">add</span>(T t, U u);</span><br></pre></td></tr></tbody></table></figure><h5 id="缺点-7"><a href="#缺点-7" class="headerlink" title="缺点"></a>缺点</h5><p>后置返回类型相对来说是非常新的语法, 而且在 C 和 Java 中都没有相似的写法, 因此可能对读者来说比较陌生.</p><p>在已有的代码中有大量的函数声明, 你不可能把它们都用新的语法重写一遍. 因此实际的做法只能是使用旧的语法或者新旧混用. 在这种情况下, 只使用一种版本是相对来说更规整的形式.</p><h5 id="结论-7"><a href="#结论-7" class="headerlink" title="结论"></a>结论</h5><p>在大部分情况下, 应当继续使用以往的函数声明写法, 即将返回类型置于函数名前. 只有在必需的时候 (如 Lambda 表达式) 或者使用后置语法能够简化书写并且提高易读性的时候才使用新的返回类型后置语法. 但是后一种情况一般来说是很少见的, 大部分时候都出现在相当复杂的模板代码中, 而多数情况下不鼓励写这样复杂的模板代码.</p><h3 id="来自Google的奇技"><a href="#来自Google的奇技" class="headerlink" title="来自Google的奇技"></a>来自Google的奇技</h3><p>Google 用了很多自己实现的技巧 / 工具使 C++ 代码更加健壮, 我们使用 C++ 的方式可能和你在其它地方见到的有所不同.</p><h4 id="所有权与智能指针"><a href="#所有权与智能指针" class="headerlink" title="所有权与智能指针"></a>所有权与智能指针</h4><h5 id="总述-6"><a href="#总述-6" class="headerlink" title="总述"></a>总述</h5><p>动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权.</p><h5 id="定义-7"><a href="#定义-7" class="headerlink" title="定义"></a>定义</h5><p>所有权是一种登记/管理动态内存和其它资源的技术. 动态分配对象的所有主是一个对象或函数, 后者负责确保当前者无用时就自动销毁前者. 所有权有时可以共享, 此时就由最后一个所有主来负责销毁它. 甚至也可以不用共享, 在代码中直接把所有权传递给其它对象.</p><p>智能指针是一个通过重载 <code>*</code> 和 <code>-></code> 运算符以表现得如指针一样的类. 智能指针类型被用来自动化所有权的登记工作, 来确保执行销毁义务到位. <a href="http://en.cppreference.com/w/cpp/memory/unique_ptr">std::unique_ptr</a> 是 C++11 新推出的一种智能指针类型, 用来表示动态分配出的对象的独一无二的所有权; 当 <code>std::unique_ptr</code> 离开作用域时, 对象就会被销毁. <code>std::unique_ptr</code> 不能被复制, 但可以把它移动(move)给新所有主. <a href="http://en.cppreference.com/w/cpp/memory/shared_ptr">std::shared_ptr</a> 同样表示动态分配对象的所有权, 但可以被共享, 也可以被复制; 对象的所有权由所有复制者共同拥有, 最后一个复制者被销毁时, 对象也会随着被销毁.</p><h5 id="优点-8"><a href="#优点-8" class="headerlink" title="优点"></a>优点</h5><ul><li>如果没有清晰、逻辑条理的所有权安排, 不可能管理好动态分配的内存.</li><li>传递对象的所有权, 开销比复制来得小, 如果可以复制的话.</li><li>传递所有权也比”借用”指针或引用来得简单, 毕竟它大大省去了两个用户一起协调对象生命周期的工作.</li><li>如果所有权逻辑条理, 有文档且不紊乱的话, 可读性会有很大提升.</li><li>可以不用手动完成所有权的登记工作, 大大简化了代码, 也免去了一大波错误之恼.</li><li>对于 const 对象来说, 智能指针简单易用, 也比深度复制高效.</li></ul><h5 id="缺点-8"><a href="#缺点-8" class="headerlink" title="缺点"></a>缺点</h5><ul><li>不得不用指针(不管是智能的还是原生的)来表示和传递所有权. 指针语义可要比值语义复杂得许多了, 特别是在 API 里:这时不光要操心所有权, 还要顾及别名, 生命周期, 可变性以及其它大大小小的问题.</li><li>其实值语义的开销经常被高估, 所以所有权传递带来的性能提升不一定能弥补可读性和复杂度的损失.</li><li>如果 API 依赖所有权的传递, 就会害得客户端不得不用单一的内存管理模型.</li><li>如果使用智能指针, 那么资源释放发生的位置就会变得不那么明显.</li><li><code><span class="pre">std::unique_ptr</span></code> 的所有权传递原理是 C++11 的 move 语法, 后者毕竟是刚刚推出的, 容易迷惑程序员.</li><li>如果原本的所有权设计已经够完善了, 那么若要引入所有权共享机制, 可能不得不重构整个系统.</li><li>所有权共享机制的登记工作在运行时进行, 开销可能相当大.</li><li>某些极端情况下 (例如循环引用), 所有权被共享的对象永远不会被销毁.</li><li>智能指针并不能够完全代替原生指针.</li></ul><h5 id="结论-8"><a href="#结论-8" class="headerlink" title="结论"></a>结论</h5><p>如果必须使用动态分配, 那么更倾向于将所有权保持在分配者手中. 如果其他地方要使用这个对象, 最好传递它的拷贝, 或者传递一个不用改变所有权的指针或引用. 倾向于使用 <code>std::unique_ptr</code> 来明确所有权传递, 例如:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function">std::unique_ptr<Foo> <span class="title">FooFactory</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">FooConsumer</span><span class="params">(std::unique_ptr<Foo> ptr)</span></span>;</span><br></pre></td></tr></tbody></table></figure><p>如果没有很好的理由, 则不要使用共享所有权. 这里的理由可以是为了避免开销昂贵的拷贝操作, 但是只有当性能提升非常明显, 并且操作的对象是不可变的(比如说 <code>std::shared_ptr<const Foo></code> )时候, 才能这么做. 如果确实要使用共享所有权, 建议于使用 <code>std::shared_ptr</code> .</p><p>不要使用 <code>std::auto_ptr</code>, 使用 <code>std::unique_ptr</code> 代替它.</p><h4 id="Cpplint"><a href="#Cpplint" class="headerlink" title="Cpplint"></a>Cpplint</h4><h5 id="总述-7"><a href="#总述-7" class="headerlink" title="总述"></a>总述</h5><p>使用 <code>cpplint.py</code> 检查风格错误.</p><h5 id="说明-2"><a href="#说明-2" class="headerlink" title="说明"></a>说明</h5><p><code>cpplint.py</code> 是一个用来分析源文件, 能检查出多种风格错误的工具. 它不并完美, 甚至还会漏报和误报, 但它仍然是一个非常有用的工具. 在行尾加 <code>// NOLINT</code>, 或在上一行加 <code>// NOLINTNEXTLINE</code>, 可以忽略报错.</p><p>某些项目会指导你如何使用他们的项目工具运行 <code>cpplint.py</code>. 如果你参与的项目没有提供, 你可以单独下载 <a href="http://github.com/google/styleguide/blob/gh-pages/cpplint/cpplint.py">cpplint.py</a>.</p><h4 id="译者(acgtyrant)笔记-2"><a href="#译者(acgtyrant)笔记-2" class="headerlink" title="译者(acgtyrant)笔记"></a>译者(acgtyrant)笔记</h4><ol><li>把智能指针当成对象来看待的话, 就很好领会它与所指对象之间的关系了.</li><li>原来 Rust 的 Ownership 思想是受到了 C++ 智能指针的很大启发啊.</li><li><code>scoped_ptr</code> 和 <code>auto_ptr</code> 已过时. 现在是 <code>shared_ptr</code> 和 <code>uniqued_ptr</code> 的天下了.</li><li>按本文来说, 似乎除了智能指针, 还有其它所有权机制, 值得留意.</li><li>Arch Linux 用户注意了, AUR 有对 cpplint 打包.</li></ol><h3 id="其他C-特性"><a href="#其他C-特性" class="headerlink" title="其他C++特性"></a>其他C++特性</h3><h3 id="命名约定"><a href="#命名约定" class="headerlink" title="命名约定"></a>命名约定</h3><p>最重要的一致性规则是命名管理. 命名的风格能让我们在不需要去查找类型声明的条件下快速地了解某个名字代表的含义: 类型, 变量, 函数, 常量, 宏, 等等, 甚至. 我们大脑中的模式匹配引擎非常依赖这些命名规则.</p><p>命名规则具有一定随意性, 但相比按个人喜好命名, 一致性更重要, 所以无论你认为它们是否重要, 规则总归是规则.</p><h4 id="通用命名规则"><a href="#通用命名规则" class="headerlink" title="通用命名规则"></a>通用命名规则</h4><h5 id="总述-8"><a href="#总述-8" class="headerlink" title="总述"></a>总述</h5><p>函数命名, 变量命名, 文件命名要有描述性; 少用缩写.</p><h5 id="说明-3"><a href="#说明-3" class="headerlink" title="说明"></a>说明</h5><p>尽可能使用描述性的命名, 别心疼空间, 毕竟相比之下让代码易于新读者理解更重要. 不要用只有项目开发者能理解的缩写, 也不要通过砍掉几个字母来缩写单词.</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">int</span> price_count_reader; <span class="comment">// 无缩写</span></span><br><span class="line"><span class="type">int</span> num_errors; <span class="comment">// "num" 是一个常见的写法</span></span><br><span class="line"><span class="type">int</span> num_dns_connections; <span class="comment">// 人人都知道 "DNS" 是什么</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">int</span> n; <span class="comment">// 毫无意义.</span></span><br><span class="line"><span class="type">int</span> nerr; <span class="comment">// 含糊不清的缩写.</span></span><br><span class="line"><span class="type">int</span> n_comp_conns; <span class="comment">// 含糊不清的缩写.</span></span><br><span class="line"><span class="type">int</span> wgc_connections; <span class="comment">// 只有贵团队知道是什么意思.</span></span><br><span class="line"><span class="type">int</span> pc_reader; <span class="comment">// "pc" 有太多可能的解释了.</span></span><br><span class="line"><span class="type">int</span> cstmr_id; <span class="comment">// 删减了若干字母.</span></span><br></pre></td></tr></tbody></table></figure><p>注意, 一些特定的广为人知的缩写是允许的, 例如用 <code>i</code> 表示迭代变量和用 <code>T</code> 表示模板参数.</p><p>模板参数的命名应当遵循对应的分类: 类型模板参数应当遵循类型命名的规则, 而非类型模板应当遵循变量命名的规则.</p><h4 id="文件命名"><a href="#文件命名" class="headerlink" title="文件命名"></a>文件命名</h4><h5 id="总述-9"><a href="#总述-9" class="headerlink" title="总述"></a>总述</h5><p>文件名要全部小写, 可以包含下划线 (<code>_</code>) 或连字符 (<code>-</code>), 依照项目的约定. 如果没有约定, 那么 “<code>_</code>” 更好.</p><h5 id="说明-4"><a href="#说明-4" class="headerlink" title="说明"></a>说明</h5><p>可接受的文件命名示例:</p><ul><li><code>my_useful_class.cc</code></li><li><code>my-useful-class.cc</code></li><li><code>myusefulclass.cc</code></li><li><code>myusefulclass_test.cc</code> // <code>_unittest</code> 和 <code>_regtest</code> 已弃用.</li></ul><p>C++ 文件要以 <code>.cc</code> 结尾, 头文件以 <code>.h</code> 结尾. 专门插入文本的文件则以 <code>.inc</code> 结尾, 参见头文件自足.</p><p>不要使用已经存在于 <code>/usr/include</code> 下的文件名 (Yang.Y 注: 即编译器搜索系统头文件的路径), 如 <code>db.h</code>.</p><p>通常应尽量让文件名更加明确. <code>http_server_logs.h</code> 就比 <code>logs.h</code> 要好. 定义类时文件名一般成对出现, 如 <code>foo_bar.h</code> 和 <code>foo_bar.cc</code>, 对应于类 <code>FooBar</code>.</p><p>内联函数必须放在 <code>.h</code> 文件中. 如果内联函数比较短, 就直接放在 <code>.h</code> 中.</p><h3 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h3><h3 id="格式"><a href="#格式" class="headerlink" title="格式"></a>格式</h3><h3 id="规则特例"><a href="#规则特例" class="headerlink" title="规则特例"></a>规则特例</h3><p>前面说明的编程习惯基本都是强制性的. 但所有优秀的规则都允许例外, 这里就是探讨这些特例.</p><h4 id="现有不合规范的代码"><a href="#现有不合规范的代码" class="headerlink" title="现有不合规范的代码"></a>现有不合规范的代码</h4><h5 id="总述-10"><a href="#总述-10" class="headerlink" title="总述"></a>总述</h5><p>对于现有不符合既定编程风格的代码可以网开一面.</p><h5 id="说明-5"><a href="#说明-5" class="headerlink" title="说明"></a>说明</h5><p>当你修改使用其他风格的代码时, 为了与代码原有风格保持一致可以不使用本指南约定. 如果不放心, 可以与代码原作者或现在的负责人员商讨. 记住, <em>一致性</em> 也包括原有的一致性.</p><h4 id="Windows-代码"><a href="#Windows-代码" class="headerlink" title="Windows 代码"></a>Windows 代码</h4><h5 id="总述-11"><a href="#总述-11" class="headerlink" title="总述"></a>总述</h5><p>Windows 程序员有自己的编程习惯, 主要源于 Windows 头文件和其它 Microsoft 代码. 我们希望任何人都可以顺利读懂你的代码, 所以针对所有平台的 C++ 编程只给出一个单独的指南.</p><h5 id="说明-6"><a href="#说明-6" class="headerlink" title="说明"></a>说明</h5><p>如果你习惯使用 Windows 编码风格, 这儿有必要重申一下某些你可能会忘记的指南:</p><ul><li>不要使用匈牙利命名法 (比如把整型变量命名成 <code>iNum</code>). 使用 Google 命名约定, 包括对源文件使用 <code>.cc</code> 扩展名.</li><li>Windows 定义了很多原生类型的同义词 (YuleFox 注: 这一点, 我也很反感), 如 <code>DWORD</code>, <code>HANDLE</code> 等等. 在调用 Windows API 时这是完全可以接受甚至鼓励的. 即使如此, 还是尽量使用原有的 C++ 类型, 例如使用 <code>const TCHAR*</code> 而不是 <code>LPCTSTR</code>.</li><li>使用 Microsoft Visual C++ 进行编译时, 将警告级别设置为 3 或更高, 并将所有警告(warnings)当作错误(errors)处理.</li><li>不要使用 <code>#pragma once</code>; 而应该使用 Google 的头文件保护规则. 头文件保护的路径应该相对于项目根目录 (Yang.Y 注: 如 <code>#ifndef SRC_DIR_BAR_H_</code>, 参考#define 保护一节).</li><li>除非万不得已, 不要使用任何非标准的扩展, 如 <code>#pragma</code> 和 <code>__declspec</code>. 使用 <code>__declspec(dllimport)</code> 和 <code>__declspec(dllexport)</code> 是允许的, 但必须通过宏来使用, 比如 <code>DLLIMPORT</code> 和 <code>DLLEXPORT</code>, 这样其他人在分享使用这些代码时可以很容易地禁用这些扩展.</li></ul><p>然而, 在 Windows 上仍然有一些我们偶尔需要违反的规则:</p><ul><li>通常我们禁止使用多重继承, 但在使用 COM 和 ATL/WTL 类时可以使用多重继承. 为了实现 COM 或 ATL/WTL 类/接口, 你可能不得不使用多重实现继承.</li><li>虽然代码中不应该使用异常, 但是在 ATL 和部分 STL(包括 Visual C++ 的 STL) 中异常被广泛使用. 使用 ATL 时, 应定义 <code>_ATL_NO_EXCEPTIONS</code> 以禁用异常. 你需要研究一下是否能够禁用 STL 的异常, 如果无法禁用, 可以启用编译器异常. (注意这只是为了编译 STL, 自己的代码里仍然不应当包含异常处理).</li><li>通常为了利用头文件预编译, 每个每个源文件的开头都会包含一个名为 <code>StdAfx.h</code> 或 <code>precompile.h</code> 的文件. 为了使代码方便与其他项目共享, 请避免显式包含此文件 (除了在 <code>precompile.cc</code> 中), 使用 <code>/FI</code> 编译器选项以自动包含该文件.</li><li>资源头文件通常命名为 <code>resource.h</code> 且只包含宏, 这一文件不需要遵守本风格指南.</li></ul><h3 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h3><p>运用常识和判断力, 并且保持一致.</p><p>编辑代码时, 花点时间看看项目中的其它代码, 并熟悉其风格. 如果其它代码中 <code>if</code> 语句使用空格, 那么你也要使用. 如果其中的注释用星号 (*) 围成一个盒子状, 那么你同样要这么做.</p><p>风格指南的重点在于提供一个通用的编程规范, 这样大家可以把精力集中在实现内容而不是表现形式上. 我们展示的是一个总体的的风格规范, 但局部风格也很重要, 如果你在一个文件中新加的代码和原有代码风格相去甚远, 这就破坏了文件本身的整体美观, 也让打乱读者在阅读代码时的节奏, 所以要尽量避免.</p><p>好了, 关于编码风格写的够多了; 代码本身才更有趣. 尽情享受吧!</p></body></html>]]></content>
<summary type="html"><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CODE3/X4uzvL.jpg"></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>Google 经常会发布一些开源项目, 意味着会接受来自其他代码贡献者的代码。但是如果代码贡献者的编程风格与 Google 的不一致, 会给代码阅读者和其他代码提交者造成不小的困扰。Google 因此发布了这份自己的编程风格指南, 使所有提交代码的人都能获知 Google 的编程风格。</p></summary>
<category term="编程技巧 (Coding Skill)" scheme="https://yousazoe.top/categories/%E7%BC%96%E7%A8%8B%E6%8A%80%E5%B7%A7-Coding-Skill/"/>
</entry>
<entry>
<title>向量</title>
<link href="https://yousazoe.top/archives/8c47f151.html"/>
<id>https://yousazoe.top/archives/8c47f151.html</id>
<published>2022-09-29T01:22:20.000Z</published>
<updated>2025-01-03T06:07:21.690Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://twitter.com/Rcmedy/status/1572658550563807233"><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/GApBvy.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>本课程旨在围绕各类数据结构的设计与实现,揭示其中的规律原理与方法技巧;同时针对算法设计及其性能分析,使学生了解并掌握主要的套路与手段。本文将讲解数据结构向量及查找和排序。</p><span id="more"></span><h3 id="抽象数据类型"><a href="#抽象数据类型" class="headerlink" title="抽象数据类型"></a>抽象数据类型</h3><h4 id="接口与实现"><a href="#接口与实现" class="headerlink" title="接口与实现"></a>接口与实现</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0001.jpg"></p><p>向量属于最最基本的线性结构,我们笼统称之为线性序列。</p><p>本章我们将围绕这种数据结构展示和讨论两方面问题:</p><ol><li>如何根据统一的接口规范来定制并实现一个数据结构</li><li>围绕这种数据结构展示如何通过更加有效的算法使得我们对外的接口能够更加高效率地工作:查找、排序</li></ol><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0002.jpg"></p><p>首先我们要辨析抽象数据类型和数据结构:</p><ul><li>抽象数据类型 = 数据模型 + 定义在该模型的一组操作</li><li>数据结构 = 基于某种特定语言,实现 ADT 的一整套算法</li></ul><p>更形象一点,我们可以将数据结构比喻成某种产品比如汽车。作为用户 Application 而言,他只关心这种产品的外在特性能够提供的功能;而实现者 Implementation 则需要对这些功能以及特性具体如何落实负责。</p><h4 id="向量ADT"><a href="#向量ADT" class="headerlink" title="向量ADT"></a>向量ADT</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0004.jpg"></p><p>所谓向量,实际上是 C++ 等高级编程语言中数组这种数据组织形式的一个推广和泛化。</p><h5 id="循秩访问"><a href="#循秩访问" class="headerlink" title="循秩访问"></a>循秩访问</h5><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0005.jpg"></p><p>在这些高级程序设计语言中所谓的数组实际上就是一段连续的内存空间,它被均匀地划分为若干个单元,而每一个单元都会与一个编号彼此回应,并且可以直接访问。</p><p>而向量可以被认为是数组的抽象与泛化,它同样是由一组抽象的元素按照刚才的线性次序封装而成。不同的是原来通过下标 <code>i</code> 的访问方式变成了秩 rank。</p><p>另外向量中元素的类型得到了拓展,不限于是某一种特定的基本类型,它的所有操作、管理维护更加简化,可以通过统一的接口来完成。</p><h5 id="向量ADT接口"><a href="#向量ADT接口" class="headerlink" title="向量ADT接口"></a>向量ADT接口</h5><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0006.jpg"></p><p>可以通过这些操作接口对向量做各种操作,同时也只能通过这些操作接口对向量进行操作。</p><h4 id="接口操作实例"><a href="#接口操作实例" class="headerlink" title="接口操作实例"></a>接口操作实例</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0007.jpg"></p><h4 id="构造与析构"><a href="#构造与析构" class="headerlink" title="构造与析构"></a>构造与析构</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0010.jpg"></p><ul><li><a href="https://dsa.cs.tsinghua.edu.cn/~deng/ds/src_link/vector/vector.h.htm">SourceCode</a></li></ul><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifndef</span> SRC_VECTOR_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SRC_VECTOR_H</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEFAULT_CAPACITY 3 <span class="comment">// 默认初始容量</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> Vector {</span><br><span class="line"> <span class="keyword">using</span> Rank = <span class="type">int</span>; <span class="comment">// 秩</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">template</span><<span class="keyword">typename</span> T> <span class="keyword">class</span> <span class="title class_">Vector</span> {</span><br><span class="line"> <span class="keyword">protected</span>:</span><br><span class="line"> T* _elem; <span class="comment">// 数据</span></span><br><span class="line"> Rank _size; <span class="comment">// 规模</span></span><br><span class="line"> Rank _capacity; <span class="comment">// 容量</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">expand</span><span class="params">()</span></span>; <span class="comment">// 空间不足扩容</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">shrink</span><span class="params">()</span></span>; <span class="comment">// 装填过小压缩</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">copyFrom</span><span class="params">(T <span class="type">const</span>* A, Rank lo, Rank hi)</span></span>; <span class="comment">// 复制数组区间</span></span><br><span class="line"></span><br><span class="line"> <span class="function">Rank <span class="title">maxItem</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 选取最大元素</span></span><br><span class="line"> <span class="function">Rank <span class="title">partition</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 轴点构造算法</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">selectionSort</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 选择排序</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">bool</span> <span class="title">bubble</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 扫描交换</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">bubbleSort</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 起泡排序</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">merge</span><span class="params">(Rank lo, Rank mid, Rank hi)</span></span>; <span class="comment">// 归并算法</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">mergeSort</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 归并排序</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">heapSort</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 堆排序</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">quickSort</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 快速排序</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">shellSort</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 希尔排序</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">/* 构造函数 */</span></span><br><span class="line"> <span class="built_in">Vector</span>(<span class="type">int</span> c = DEFAULT_CAPACITY, Rank s = <span class="number">0</span>, T v = <span class="number">0</span>) {</span><br><span class="line"> _elem = <span class="keyword">new</span> T[_capacity = c];</span><br><span class="line"> <span class="keyword">for</span> (_size = <span class="number">0</span>; _size < s; ++_size)</span><br><span class="line"> _elem[_size] = v;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Vector</span>(T <span class="type">const</span>* A, Rank n) { <span class="built_in">copyFrom</span>(A, <span class="number">0</span>, n); }</span><br><span class="line"> <span class="built_in">Vector</span>(T <span class="type">const</span>* A, Rank lo, Rank hi) { <span class="built_in">copyFrom</span>(A, lo, hi); }</span><br><span class="line"> <span class="built_in">Vector</span>(Vector<T> <span class="type">const</span>& V) { <span class="built_in">copyFrom</span>(V._elem, <span class="number">0</span>, V._size); }</span><br><span class="line"> <span class="built_in">Vector</span>(Vector<T> <span class="type">const</span>& V, Rank lo, Rank hi) { <span class="built_in">copyFrom</span>(V._elem, lo, hi); }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 析构函数 */</span></span><br><span class="line"> ~<span class="built_in">Vector</span>() { <span class="keyword">delete</span>[] _elem; }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 只读接口 */</span></span><br><span class="line"> <span class="function">Rank <span class="title">size</span><span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> _size; } <span class="comment">// 规模</span></span><br><span class="line"> <span class="function"><span class="type">bool</span> <span class="title">empty</span><span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> !_size; } <span class="comment">// 判空</span></span><br><span class="line"></span><br><span class="line"> <span class="function">Rank <span class="title">find</span><span class="params">(T <span class="type">const</span>& e)</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> <span class="built_in">find</span>(e, <span class="number">0</span>, _size); } <span class="comment">// 无序向量整体查找</span></span><br><span class="line"> <span class="function">Rank <span class="title">find</span><span class="params">(T <span class="type">const</span>& e, Rank lo, Rank hi)</span></span>; <span class="comment">// 无序向量区间查找</span></span><br><span class="line"></span><br><span class="line"> <span class="function">Rank <span class="title">search</span><span class="params">(T <span class="type">const</span>& e)</span> <span class="type">const</span> </span>{ <span class="comment">// 有序向量整体查找</span></span><br><span class="line"> <span class="keyword">return</span> (_size <= <span class="number">0</span>)? <span class="number">-1</span>: <span class="built_in">search</span>(e, <span class="number">0</span>, _size);</span><br><span class="line"> }</span><br><span class="line"> <span class="function">Rank <span class="title">search</span><span class="params">(T <span class="type">const</span>& e, Rank lo, Rank hi)</span> <span class="type">const</span></span>; <span class="comment">// 有序向量区间查找 </span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 可写接口 */</span></span><br><span class="line"> T& <span class="keyword">operator</span>[] (Rank r); <span class="comment">// 重载下标操作符</span></span><br><span class="line"> <span class="type">const</span> T& <span class="keyword">operator</span>[] (Rank r) <span class="type">const</span>;</span><br><span class="line"> Vector<T>& <span class="keyword">operator</span>= (Vector<T> <span class="type">const</span>&); <span class="comment">// 重载赋值操作符</span></span><br><span class="line"></span><br><span class="line"> <span class="function">T <span class="title">remove</span><span class="params">(Rank r)</span></span>; <span class="comment">// 删除单一元素</span></span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">remove</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 删除区间元素</span></span><br><span class="line"> </span><br><span class="line"> <span class="function">Rank <span class="title">insert</span><span class="params">(Rank r, T <span class="type">const</span>& e)</span></span>; <span class="comment">// 插入元素</span></span><br><span class="line"> <span class="function">Rank <span class="title">insert</span><span class="params">(T <span class="type">const</span>& e)</span> </span>{ <span class="keyword">return</span> <span class="built_in">insert</span>(_size, e); } <span class="comment">// 插入末元素</span></span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">sort</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 区间排序</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">sort</span><span class="params">()</span> </span>{ <span class="built_in">sort</span>(<span class="number">0</span>, _size); } <span class="comment">// 整体排序</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">unsort</span><span class="params">(Rank lo, Rank hi)</span></span>; <span class="comment">// 区间置乱</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">unsort</span><span class="params">()</span> </span>{ <span class="built_in">unsort</span>(<span class="number">0</span>, _size); } <span class="comment">// 整体置乱</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 遍历接口 */</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">traverse</span><span class="params">(<span class="type">void</span>(*) (T&))</span></span>; <span class="comment">// 函数指针遍历</span></span><br><span class="line"> <span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> VST> <span class="type">void</span> <span class="title">traverse</span><span class="params">(VST&)</span></span>; <span class="comment">// 函数对象遍历</span></span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">//SRC_VECTOR_H</span></span></span><br></pre></td></tr></tbody></table></figure><p>整个 Vector 被封装起来,来自各种用户 application 的操作接口 interface 提供在外面,相当于一个 Vector 结构的使用说明书。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0011.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">/* 构造函数 */</span></span><br><span class="line"><span class="built_in">Vector</span>(<span class="type">int</span> c = DEFAULT_CAPACITY, Rank s = <span class="number">0</span>, T v = <span class="number">0</span>) {</span><br><span class="line"> _elem = <span class="keyword">new</span> T[_capacity = c];</span><br><span class="line"> <span class="keyword">for</span> (_size = <span class="number">0</span>; _size < s; ++_size)</span><br><span class="line"> _elem[_size] = v;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">Vector</span>(T <span class="type">const</span>* A, Rank n) { <span class="built_in">copyFrom</span>(A, <span class="number">0</span>, n); }</span><br><span class="line"><span class="built_in">Vector</span>(T <span class="type">const</span>* A, Rank lo, Rank hi) { <span class="built_in">copyFrom</span>(A, lo, hi); }</span><br><span class="line"><span class="built_in">Vector</span>(Vector<T> <span class="type">const</span>& V) { <span class="built_in">copyFrom</span>(V._elem, <span class="number">0</span>, V._size); }</span><br><span class="line"><span class="built_in">Vector</span>(Vector<T> <span class="type">const</span>& V, Rank lo, Rank hi) { <span class="built_in">copyFrom</span>(V._elem, lo, hi); }</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 析构函数 */</span></span><br><span class="line">~<span class="built_in">Vector</span>() { <span class="keyword">delete</span>[] _elem; }</span><br></pre></td></tr></tbody></table></figure><h4 id="复制"><a href="#复制" class="headerlink" title="复制"></a>复制</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0012.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="type">void</span> Vector<T>::<span class="built_in">copyFrom</span>(<span class="type">const</span> T *A, Rank lo, Rank hi) {</span><br><span class="line"> _elem = <span class="keyword">new</span> T[_capacity = <span class="number">2</span>*(hi - lo)];</span><br><span class="line"> _size = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (lo < hi)</span><br><span class="line"> _elem[_size++] = A[lo++];</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>复制操作将 <code>_elem</code> 空间扩展为原来的二倍,然后将区间元素依次复制。</p><h3 id="可扩充向量"><a href="#可扩充向量" class="headerlink" title="可扩充向量"></a>可扩充向量</h3><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0013.jpg"></p><h4 id="可扩充向量-1"><a href="#可扩充向量-1" class="headerlink" title="可扩充向量"></a>可扩充向量</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0014.jpg"></p><p>现在我们用 <code>_size</code> 表示实际规模,<code>_capacity</code> 表示总容量。</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">T* _elem; <span class="comment">// 数据</span></span><br><span class="line">Rank _size; <span class="comment">// 规模</span></span><br><span class="line">Rank _capacity; <span class="comment">// 容量</span></span><br></pre></td></tr></tbody></table></figure><p>这里的问题是 <code>_capacity</code> 一旦确定按照目前的方案它就将一成不变,而这样一种策略显然存在明显的不足。这种不足体现在两个方面:</p><ul><li>上溢(overflow):<code>_elem[]</code> 不足以存放所有元素,尽管此时系统仍有足够的空间</li><li>下溢(underflow):<code>_elem[]</code> 中的元素寥寥无几,装填因子 = _size/_capacity << 50%</li></ul><h4 id="动态空间管理"><a href="#动态空间管理" class="headerlink" title="动态空间管理"></a>动态空间管理</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0015.jpg"></p><p>我们需要从静态管理策略改编为动态管理策略,模仿蝉的做法在即将发生上溢时适当地扩大内部数组容量。</p><p>向量的生命周期:</p><ul><li>(a) 最开始虽然元素很多但不至于出现上溢的情况</li><li>(b) 但剩余空间有可能会逐步地占用,在某一时刻内部数组饱和</li><li>(c) 模仿蝉退掉外壳,动态申请另一个外壳:另一段存放空间,它的大小应该比原来的有所增长</li><li>(d) 把原先存放好的有效元素逐一按次序复制过来,使得它们对外界而言依旧保持原貌</li><li>(e) 新多出的空间足以存放新需要插入的元素,原来占用的空间在此之后被释放并且归还给系统</li></ul><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0016.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="type">void</span> Vector<T>::<span class="built_in">expand</span>() {</span><br><span class="line"> <span class="keyword">if</span> (_size < _capacity) <span class="keyword">return</span>;</span><br><span class="line"> _capacity = std::<span class="built_in">max</span>(_capacity, DEFAULT_CAPACITY);</span><br><span class="line"></span><br><span class="line"> T* oldElem = _elem;</span><br><span class="line"> _elem = <span class="keyword">new</span> T[_capacity <<= <span class="number">1</span>];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < _size; ++i)</span><br><span class="line"> _elem[i] = oldElem[i];</span><br><span class="line"> <span class="keyword">delete</span>[] oldElem;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>得益于向量的封装,尽管扩容之后数据区的物理地址有所改变,却不致出现野指针。</p><h4 id="递增式扩容"><a href="#递增式扩容" class="headerlink" title="递增式扩容"></a>递增式扩容</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0017.jpg"></p><p>每当发现当前的内部数组即将发生上溢,我们并不是对它进行容量的加倍而只是在原来的容量的基础上追加一个固定的数额:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0018.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">T* oldElem = _elem;</span><br><span class="line">_elem = <span class="keyword">new</span> T[_capacity += INCREMENT];</span><br></pre></td></tr></tbody></table></figure><p>对于这种策略而言,每经过 I 次插入操作它都需要进行一次扩容,每次分摊成本为 O(n)。</p><h4 id="加倍式扩容"><a href="#加倍式扩容" class="headerlink" title="加倍式扩容"></a>加倍式扩容</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0019.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">T* oldElem = _elem;</span><br><span class="line">_elem = <span class="keyword">new</span> T[_capacity <<= <span class="number">1</span>];</span><br></pre></td></tr></tbody></table></figure><p>每次的分摊成本为 O(1) 常数时间。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0020.jpg"></p><p>倍增策略通过在空间的效率上做了一个适当的牺牲换取在时间方面的巨大收益。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0021.jpg"></p><h3 id="无序向量"><a href="#无序向量" class="headerlink" title="无序向量"></a>无序向量</h3><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0022.jpg"></p><h4 id="循秩访问-1"><a href="#循秩访问-1" class="headerlink" title="循秩访问"></a>循秩访问</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0023.jpg"></p><p>首先讨论向量元素的访问。表面上看这并不是什么问题,因为在向量 ADT 中已经定义了两个标准的接口 <code>V.get(r)</code> 和 <code>V.put(r, e)</code>。通过它们我们已经可以自如地来写或者是读向量中特定的元素,但这两种接口在形式上还不是那么简洁直观。</p><p>我们期望数组那种直接地访问方式:<code>A[r]</code>,为此需要重载下标操作符 <code>[]</code>:</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line">T& Vector<T>::<span class="keyword">operator</span>[](Rank r) <span class="type">const</span> { <span class="keyword">return</span> _elem[r]; }</span><br></pre></td></tr></tbody></table></figure><h4 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0024.jpg"></p><p>再来考察向量的插入算法,如何讲某一个特定的元素插入到向量的特定位置。</p><p>因为原有向量所有元素都是紧邻排列的,所以为了能够插入新的元素我们需要将对应位置之后的所有元素称作它的后继,进行一个整体的右移操作。</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line">Rank Vector<T>::<span class="built_in">insert</span>(Rank r, T <span class="type">const</span>& e) {</span><br><span class="line"> <span class="built_in">expand</span>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = _size; i > r; --i)</span><br><span class="line"> _elem[i] = _elem[i - <span class="number">1</span>];</span><br><span class="line"> </span><br><span class="line"> _elem[r] = e;</span><br><span class="line"> _size++;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> r;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="区间删除"><a href="#区间删除" class="headerlink" title="区间删除"></a>区间删除</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0025.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="type">int</span> Vector<T>::<span class="built_in">remove</span>(Rank lo, Rank hi) {</span><br><span class="line"> <span class="keyword">if</span> (lo == hi) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(hi < _size)</span><br><span class="line"> _elem[lo++] = _elem[hi++];</span><br><span class="line"></span><br><span class="line"> <span class="built_in">shrink</span>();</span><br><span class="line"> <span class="keyword">return</span> hi - lo;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="单元素删除"><a href="#单元素删除" class="headerlink" title="单元素删除"></a>单元素删除</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0026.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line">T Vector<T>::<span class="built_in">remove</span>(Rank r) {</span><br><span class="line"> T e = _elem[r];</span><br><span class="line"> <span class="built_in">remove</span>(r, r + <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0027.jpg"></p><p>无序向量只支持判等操作,有序向量还需要支持其中的元素相互比较大小。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0028.jpg"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0029.jpg"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0030.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line">Rank Vector<T>::<span class="built_in">find</span>(T <span class="type">const</span>& e, Rank lo, Rank hi) {</span><br><span class="line"> <span class="keyword">while</span> ((lo < hi--) && (_elem[hi] != e));</span><br><span class="line"> <span class="keyword">return</span> hi;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>从 <code>hi</code> 出发逆向逐一取出向量中的各个元素,与目标元素进行比对。如果不相等,就忽略它并且考察它的前驱,整个工作会遍历向量中的所有元素。</p><h4 id="去重-x2F-唯一化"><a href="#去重-x2F-唯一化" class="headerlink" title="去重/唯一化"></a>去重/唯一化</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0031.jpg"></p><p>向量的唯一化需要把其中重复的元素都剔除掉,只保留一个拷贝。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0032.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="type">int</span> Vector<T>::<span class="built_in">deduplicate</span>() {</span><br><span class="line"> <span class="type">int</span> oldSize = _size;</span><br><span class="line"> Rank i = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">while</span> (i < _size) { </span><br><span class="line"> (<span class="built_in">find</span>(_elem[i], <span class="number">0</span>, i) < <span class="number">0</span>)? </span><br><span class="line"> i++: </span><br><span class="line"> <span class="built_in">remove</span>(i); </span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> oldSize - _size; </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="遍历"><a href="#遍历" class="headerlink" title="遍历"></a>遍历</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0033.jpg"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0034.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="type">void</span> Vector<T>::<span class="built_in">traverse</span>(<span class="built_in">void</span> (*visit) (T&)) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < _size; ++i)</span><br><span class="line"> <span class="built_in">visit</span>(_elem[i]);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T> <span class="keyword">template</span> <<span class="keyword">typename</span> VST></span><br><span class="line"><span class="type">void</span> Vector<T>::<span class="built_in">traverse</span>(VST& visit) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < _size; ++i)</span><br><span class="line"> <span class="built_in">visit</span>(_elem[i]);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li>利用函数指针机制,只读或局部性修改</li><li>利用函数对象机制,可全局性修改</li></ul><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0035.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Increase</span> {</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">operator</span><span class="params">()</span><span class="params">(T& e)</span> </span>{ e++; }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">......</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">increase</span><span class="params">(Vector<T>& V)</span> </span>{</span><br><span class="line"> v.<span class="built_in">traverse</span>(<span class="built_in">Increase</span><T>());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="有序向量"><a href="#有序向量" class="headerlink" title="有序向量"></a>有序向量</h3><h4 id="唯一化"><a href="#唯一化" class="headerlink" title="唯一化"></a>唯一化</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0036.jpg"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0037.jpg"></p><p>与起泡排序算法的理解相同,有序/无序序列中,<strong>任意/总有</strong>一对相邻元素<strong>顺序/逆序</strong>。</p><p>因此,相邻逆序对的数目,可用以度量向量的逆序程度。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0038.jpg"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="type">int</span> Vector<T>::<span class="built_in">disordered</span>() <span class="type">const</span> {</span><br><span class="line"> <span class="type">int</span> n = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i < _size; i++)</span><br><span class="line"> n += (_elem[i - <span class="number">1</span>] > _e);</span><br><span class="line"> <span class="keyword">return</span> n; </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/Vector_page-0039.jpg"></p><h4 id="二分查找"><a href="#二分查找" class="headerlink" title="二分查找"></a>二分查找</h4><h4 id="Fib查找"><a href="#Fib查找" class="headerlink" title="Fib查找"></a>Fib查找</h4><h4 id="插值查找"><a href="#插值查找" class="headerlink" title="插值查找"></a>插值查找</h4><h3 id="起泡排序"><a href="#起泡排序" class="headerlink" title="起泡排序"></a>起泡排序</h3><h3 id="归并排序"><a href="#归并排序" class="headerlink" title="归并排序"></a>归并排序</h3><h3 id="位图"><a href="#位图" class="headerlink" title="位图"></a>位图</h3></body></html>]]></content>
<summary type="html"><p><a href="https://twitter.com/Rcmedy/status/1572658550563807233"><img data-src="https://img.yousazoe.top/uPic/img/blog/DS2/GApBvy.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>本课程旨在围绕各类数据结构的设计与实现,揭示其中的规律原理与方法技巧;同时针对算法设计及其性能分析,使学生了解并掌握主要的套路与手段。本文将讲解数据结构向量及查找和排序。</p></summary>
<category term="数据结构 (Data Structure)" scheme="https://yousazoe.top/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-Data-Structure/"/>
<category term="Algorithm" scheme="https://yousazoe.top/tags/Algorithm/"/>
</entry>
<entry>
<title>Convex Hull</title>
<link href="https://yousazoe.top/archives/84b74385.html"/>
<id>https://yousazoe.top/archives/84b74385.html</id>
<published>2022-09-21T09:21:29.000Z</published>
<updated>2025-01-03T06:07:21.681Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://twitter.com/cyangmou/status/1571176793943457793"><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/qrXkrr.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>本节我们将探索计算几何的核心问题:凸包问题。计算几何领域几乎所有的问题都可以“归约”为凸包问题,因此学习凸包问题对整个计算几何体系至关重要。</p><span id="more"></span><h3 id="Convexity"><a href="#Convexity" class="headerlink" title="Convexity"></a>Convexity</h3><h4 id="Why-Convex-Hull"><a href="#Why-Convex-Hull" class="headerlink" title="Why Convex Hull"></a>Why Convex Hull</h4><p>我们计算几何的第一站就是凸包问题,它在计算几何中处于核心位置,这个核心体现在几乎所有的问题从理论上讲都可以归结为凸包问题。</p><h4 id="Nails-In-The-Table"><a href="#Nails-In-The-Table" class="headerlink" title="Nails In The Table"></a>Nails In The Table</h4><p>接下来我们通过一个具体的动手实验领会一下凸包到底是什么。</p><p>为此你需要找到一张桌子或是屏幕,假想在这个桌子上钉上一系列的钉子,然后用皮筋将其撑到足够大以至于它能将桌面上的所有钉子都包含进去。</p><p>接下来的事情非常的轻松,你只要松手就行。那么随着啪的一声,你将会看到这幅图景:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/cvGMfd.png"></p><p>刚才的皮筋就会变成这样一段一段蓝色的线段,它们首尾相连构成了一个紧绷的包围圈。这个蓝色的橡皮筋在在现在这样的一个图景状态就是我们所说的凸包,我们可以看到所谓的凸包是由这上面若干个钉子来决定的,虽然其中有一些钉子并不发挥作用,我们大致可以感觉到因为它们呆在内部。</p><p>那么,这之间的玄机又是什么呢?</p><h4 id="Paint-Blending"><a href="#Paint-Blending" class="headerlink" title="Paint Blending"></a>Paint Blending</h4><p>为了更好地理解什么是凸包,我们再来看一个应用的例子。</p><p>艺术家经常要通过混合得到某种他想要又不是从工厂直接生产出来的颜料。我们知道一般来说每种颜料都可以分成是红绿蓝三个分量的数值指标,每种组合对应的大致都是一种颜料。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/I3MrGz.png"></p><p>我们不妨为了简便起见只考虑红的以及绿的两个分量,所以这样的话每一种颜料也就是它所对应的颜色都可以用红的和绿的这样两个数字,或者说它们在整体的成份中所占的百分比来对应。</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">C = (R, G)</span><br></pre></td></tr></tbody></table></figure><p>比如说某种颜料 X 它所对应的红的分量可能是 10%,而绿的分量是 35%;另一种颜料比如叫 Y,那么它所对应的这两个分量一个是 16% 一个是 20%。 现在的问题来了,用这两种颜料能否兑出我们所希望的某些颜料呢?</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">X = (10%, 35%) Y = (16%, 20%)</span><br></pre></td></tr></tbody></table></figure><p>我们来看一下,当颜料混合在一块的时候它们的变化是多端的,有很多很多种组合,每几种颜料它们按照不同的分量、按照不同的比重勾兑在一块所得到的颜色其实都会不同。当然,艺术家有他的勾兑的方法,包括他的灵感,那么如果从数学的角度,从算法的角度来考虑,这其中应该用什么样的指导的方法呢?</p><p>那么从数学上来看我们一般来说都可以认为有一个目标的颜色,比如说这里的 U,这种颜色比如说特定的来说他希望红的占的比重是 12%,而绿的比重是 30%。</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">U = (12%, 30%)</span><br></pre></td></tr></tbody></table></figure><p>对于这样的一种目标的颜料我们应该用刚才的 X、Y,这两种来自于工厂的原始颜料用什么样的比例来对它们进行混合和勾兑呢?</p><p>好,我想你已经知道这个答案了。没错 我们应该用两份的 X 和一份的 Y 勾兑起来,就可以得到 U 了。</p><p>你不妨去做个简单的验算,两份的 10% 再加上一份*的 16% 合在一块再除以 3,正好是 12%;而两份的 35%,再加上一份的 20% 也同样的除以 3 恰好也是 30%,所以用 2 比 1 的比例是这个问题的一个解。</p><p>好,如果说我们为此花费这些时间还是值得的话,我们还是希望得到一个方法,否则的话我们会很困惑,因为如果你没有掌握这背后的、统一的方法的话,那么如果下一次换一种颜色比如说这里的 V 它要求的是 13% 和 22%,那你可能又要花费一些时间了。</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">V = (13%, 22%)</span><br></pre></td></tr></tbody></table></figure><p>那么首先一个问题是这种颜料能不能勾兑出来。并不是像我们这里所说的那样,每两种颜色给定了以后你都能勾兑出所有的颜色。其实在这个时候我们或许需要第三种颜色,比如这里我们也许从厂房里可以拿到第三种颜色 Z,它的对应的比重是 7% 和 15%。</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">Z = (07%, 15%)</span><br></pre></td></tr></tbody></table></figure><p>好了,这个时候用这三种颜色是否能把它勾兑出来呢?</p><p>好,现在我来揭晓答案。正确的比例应该是一份的 X,三份的 Y 再加上刚才我们新添的第三种颜色 Z 一份 1 比 3 比 1。你可以按照刚才同样的方法去推算一下 验算一下,我想答案应该是它。</p><p>那么所有这里讨论的事情其实都是颜色,或者准确地讲是颜料之间的那种勾兑混合。这个东西和我们这里讨论的计算几何有什么关系呢?其实它们之间有着非常深刻的联系。</p><h4 id="Color-Space"><a href="#Color-Space" class="headerlink" title="Color Space"></a>Color Space</h4><p>既然谈到几何,那么少不了就要谈到它最最基础的一个概念叫做空间,欧氏空间。</p><p>在这里我们将欧氏空间对应于颜色,我们称之为颜色空间,具体来讲我们要将每一种颜色都对应成是这个空间中的一个点。无论这种颜色或者颜料是来自于生产厂家直接供应的那种基础性的颜料,还是艺术家为了创作的需要必须重新勾兑出来的新的颜色。总而言之每一种颜色都对应这个空间中的一个点。</p><p>当然这里因为我们讨论的都是正数,那可以认为它基本上都限于第一个象限,这不是主要的问题。那么现在的问题是在于我们固然可以按照这种方法将我们刚才的三种颜料也就是 X、Y、Z 按照横轴也就是刚才比如红色的分量数值以及纵轴,也就是刚才说的绿色的分量的数值对应地画出一个一个的点,三种颜料,分别是三种点。</p><p>我们刚才看到过,在我们只有 X 和 Y 两种颜料的时候如果我们要勾兑出 U,那个比重是 2 比 1。其实这件事情倒过来,我们在给出了固定的 X 和 Y 之后我可以将我们目标的那个 U 也在这个屏幕上画出来,如果你画出来的话你就会发现其实非常地巧,我们可以验证一下它们三者是所谓共线的。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/7XidsU.png"></p><p>如果是这种情况,那么我们认为 U 肯定是能被勾兑出来的,而且它的勾兑比例可以从几何上一目了然的能解释。</p><p>你可以再去计算一下,我会告诉你其实 U 到 X 的距离相对更短,U 到 Y 的距离相对更长,而二者的距离之比其实是 1 比 2,而我们刚才勾兑的比例是反过来的 2 比 1。</p><p>其实这就是一个规律,也就是说如果我要勾兑的一种颜色恰好是位于这两个顶点的那条连接的线段上,而且它们的距离存在一个比的话,那么这种颜色就必然能够被勾兑出来。而且勾兑的方法就蕴含在刚才的那个比例中,只要把刚才那个距离比 1 比 2 颠倒过来变成 2 比 1,它就必然能得到这种颜色。</p><p>你可以作为一个极端的例子去想一下,整个的是如果要勾兑 Y 和勾兑 X 本身的时候另一个分量是 0 是同样的道理。</p><p>好,那么刚才我们也可以解释为什么 V 这种颜色必须要借助第三种颜色才能够勾兑出来。因为你大致可以看出来因为 V 并没有位于 X 和 Y 所确定的这条线段上跑偏了,在这种情况下我们说必然要借助 Z,而之所以要借助 Z 或者说准确地讲按照我们刚才那个比例必须是 1 比 3 比 1 也蕴含在这个图中,原理是一样的。</p><p>如果在这种情况下我们要做的事情就是要首先确认 V 这个颜料所对应的那个点是不是落在 X、Y、Z 所定义的这个三角形的内部,如果是它就一定能勾兑出来;如果不是,至少它是不能勾兑出来的。</p><p>好,如果它能勾兑出来,具体的勾兑的比例是多少呢?在这个图中也给出来了,为此我们只需要去量一下 V 到这三个点的距离,然后找一下它们的比。我们在这里会发现它们的比恰好是 3 比 3 比 1,所以倒过来在这里我们勾兑的比例自然也就是这个最短的最近的这个点对应的那个颜色要取的更多,反其道而行之它要取三份;而到更远的那两个点所对应的颜色所取的比例要更少,完全可以用这个来度量</p><p>当然以上的这些结论你还需要在课后再做仔细的推导和严格的验证,在这里你不妨把这个结论记下来:也就是说如果有一种颜料能够被两种已知的颜料勾兑出来,它必然位于二者之间的那条连线上;如果是对于三种颜料的情况,那么某种目标的颜色能够被勾兑出来当且仅当在颜色空间中它位于这三个点所对应的那个三角形的内部,而勾兑的比例是与他们的距离成反比的。</p><h4 id="Convex-Hull"><a href="#Convex-Hull" class="headerlink" title="Convex Hull"></a>Convex Hull</h4><p>我们虽然不是很喜欢数学,但是不得不还要用一些简单的数学把刚才我们所看到的那个结论严格地表述出来。</p><p>也就是说我们如果给定的是平面二维空间中的一系列的点的话,那么这些点所对应的颜料能构造出哪些新的颜料出来呢?我们会发现其实每一种新的颜料从几何来讲,对应于原来那些颜料的某一个调和方案。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/zuOrJ9.png"></p><p>那么在这里有一些勾兑方案专门地称之为凸的勾兑方案,或者叫作凸组合 Convex Combination。具体而言,如果是一个凸组合需要有哪些条件呢?</p><p>我们说大致有两个主要的条件:</p><ol><li>所有分量的总和必须是 100%</li><li>所有分量必须是非负的</li></ol><h3 id="Extreme-Points"><a href="#Extreme-Points" class="headerlink" title="Extreme Points"></a>Extreme Points</h3><h4 id="Extremity"><a href="#Extremity" class="headerlink" title="Extremity"></a>Extremity</h4><p>在我们最开始给定的这些点中,哪些是最终对凸包有贡献的被皮筋绷住的,哪些是没有实质作用的,这种性质可以归纳为所谓的极性。</p><p>沿着刚才的那个思路,我们观察结论可以表述为这样的一幅图。我们看到在刚才的所有那些钉子中凡事被最终的皮筋绷住的钉子,暂时没有实质作用的这些钉子我们都用青色来表示,有什么本质不同呢?</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/KWQXLc.png"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> there exists a line L through p</span><br><span class="line"> such that</span><br><span class="line"> all points of S lie on the same side of L</span><br></pre></td></tr></tbody></table></figure><p>数学上的观察告诉我们,所谓有用的点都有一个共同的特点:经过它们我们总能找到一条直线使得所有的点都落在这条直线的同一侧。</p><h4 id="Strategy"><a href="#Strategy" class="headerlink" title="Strategy"></a>Strategy</h4><p>在排序算法中有一个非常有意思的算法:起泡排序 Bubblesort。我们这里的算法设计和它是非常类似的:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/nTOTYN.png"></p><p>如何甄别极点和非极点呢?</p><p>我们需要回忆颜料勾兑的例子,一种颜料能够被其他几种颜料勾兑出来当且仅当它落在某一个三角形的内部。反过来像极点这样不能被其他颜料勾兑出来的颜色它就不可能被包含于任何三角形的内部,这样的话我们又往前转化了一步,将我们的甄别任务转化为某一个点是否会被包含于另外的三个点所确定的三角形内部。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/6fTUE0.png"></p><h4 id="In-Triangle-Test"><a href="#In-Triangle-Test" class="headerlink" title="In-Triangle Test"></a>In-Triangle Test</h4><p>根据刚才的分析,所谓凸包问题可以归结为一系列的判断:任何的一个点是否会落在其他的三个点所对应的三角形内部被它们包围,我们称这个为 In-Triangle Test。</p><p>基于 In-Triangle Test,我们就可以将非极点们一个一个地找出来并且将它们排除在我们的视野之外。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/oos30d.png"></p><p>首先做初始化,要像无罪推论一样将所有的点都设定为极点。接着枚举出所有可能的三角形,对于每个三角形我们还要去考察除它们之外的每一个点 s;一旦我们发现 s 的确是落在当前这个三角形内部,我们就可以立即断定它不是一个极点,从而将它排除在外。</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">Make all points of S as EXTREME</span><br><span class="line">For each triangle Δ(p, q, r)</span><br><span class="line"> For each s in S\{p, q, r}</span><br><span class="line"> If s in Δ(p, q, r)</span><br><span class="line"> mark s as NON_EXTREME</span><br></pre></td></tr></tbody></table></figure><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/nZts0C.png"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">extremePoint</span><span class="params">(Point S[], <span class="type">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> s = <span class="number">0</span>; s < n; s++)</span><br><span class="line"> S[s].extreme = TRUE;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// For each triangle</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> p = <span class="number">0</span>; p < n; p++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> q = p + <span class="number">1</span>; q < n; q++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> r = q + <span class="number">1</span>; r < n; r++) {</span><br><span class="line"> <span class="comment">// For each point</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> s = <span class="number">0</span>; s < n; s++) {</span><br><span class="line"> <span class="keyword">if</span> (s == p || s == q || s == r || !S[s].extreme)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">InTriangle</span>(S[p], S[q], S[r], S[s]))</span><br><span class="line"> S[s].extreme = FALSE; </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="To-Left-Test"><a href="#To-Left-Test" class="headerlink" title="To-Left Test"></a>To-Left Test</h4><p>我们给出的第一个基于极点的凸包算法虽然效率低下,但是它的意义还是很重要的,它会引出 To-Left Test,后面这个测试几乎是贯穿于我们计算几何这个课程的始终。</p><p>每当我们给定了一个点以及一个三角形后,如何来判定这个点是否落在这个三角形的内部?</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/r06PyL.png"></p><p>依然是大事化小小事化了,我们将刚才这个 In-Triangle Test 转化为三次 To-Left Test。也就是说一个点如果确实落在某一个三角形的内部的话,那么相对于这个三角形的三条边所做的 To-Left Test 都会统一的返回 true。 </p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/XvoAfS.png"></p><p>所谓 To-Left Test,就是说这个点相对于有向线段而言位于左侧还是右侧。这里的敏锐观察可以归结为一个点如果落在三角形内部,它的充要条件当且仅当它相对于这三条直线的 To-Left Test 都是 true,它同时位于这三条直线的左侧。</p><p>那么现在问题转变为如何判断一个点在线段的左侧/右侧?</p><h4 id="Determinant"><a href="#Determinant" class="headerlink" title="Determinant"></a>Determinant</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/CbRPDq.png"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">bool</span> <span class="title">ToLeft</span><span class="params">(Point p, Point q, Point s)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">return</span> <span class="title">Area</span><span class="params">(p, q, s)</span> > 0</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">Area2</span><span class="params">(Point p, Point q, Point s)</span> </span></span><br><span class="line"><span class="function"> <span class="keyword">return</span> p.x * q.y - p.y * q.x</span></span><br><span class="line"><span class="function"> + q.x * s.y - q.y * s.x</span></span><br><span class="line"><span class="function"> + s.x * p.y - s.y * p.x</span>;</span><br></pre></td></tr></tbody></table></figure><h3 id="Extreme-Edges"><a href="#Extreme-Edges" class="headerlink" title="Extreme Edges"></a>Extreme Edges</h3><h4 id="Definition"><a href="#Definition" class="headerlink" title="Definition"></a>Definition</h4><p>延续极点的思路推广到边,引入所谓的极边。</p><p>极边的候选者其实就是来自于任何两个相邻极点的连边,凡是对最终的凸包有贡献的那些边都称之为极边;凡是那些对凸包没有贡献的就不是极边,或者叫作非极边,non-extreme Edge。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/ea2Xl7.png"></p><p>就像我们定义极点一样,如果有一条这样的连边确实是极边的话,那么所有的点都会同时落在它的同侧,相应的另一侧就必然是空的。更具体来讲,以逆时针次序凸包边界每一条边都有这样一个特性:所有的点都恰好落在它的左侧,它们的右侧都是空的。</p><p>这样我们算法中的实质问题就自然地转化和具体化为如何来甄别任何两个点之间的那条连边是否为极边的问题。</p><h4 id="Algorithm"><a href="#Algorithm" class="headerlink" title="Algorithm"></a>Algorithm</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/fmhhQx.png"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">Let EE = null</span><br><span class="line"> For each directed segment pq</span><br><span class="line"> If points in S\{p, q} lie to the same side of pq</span><br><span class="line"> Let {pq} = EE</span><br></pre></td></tr></tbody></table></figure><p>按照极边的思路,我们可以将伪代码细化为这样一段真实的代码:</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">markEE</span><span class="params">(Point S[], <span class="type">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> k = <span class="number">0</span>; k < n; k++) </span><br><span class="line"> S[k].extreme = FALSE;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> p = <span class="number">0</span>; p < n; p++)</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> q = p + <span class="number">1</span>; q < n; q++) </span><br><span class="line"> <span class="built_in">checkEdge</span>(S, n, p, q);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">checkEdge</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">bool</span> LEmpty = TRUE, REmpty = TRUE;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> k = <span class="number">0</span>; k < n && (LEmpty || REmpty); k++) {</span><br><span class="line"> <span class="keyword">if</span> (k != p && k != q) {</span><br><span class="line"> <span class="built_in">ToLeft</span>(S[p], S[q], S[k])? LEmpty = FALSE: REmpty = FALSE;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (LEmpty || REmpty)</span><br><span class="line"> S[p].extreme = S[q].extreme = TRUE;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Incremental-Construction"><a href="#Incremental-Construction" class="headerlink" title="Incremental Construction"></a>Incremental Construction</h3><h4 id="Decrease-and-Conquer"><a href="#Decrease-and-Conquer" class="headerlink" title="Decrease and Conquer"></a>Decrease and Conquer</h4><p>接下来我们将从一个典型的算法思想减而治之 Decrease and Conquer 进一步改进。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/mzXh86.png"></p><p>一个经典的应该能回忆起来的算法就是插入排序 Insertionsort。插入排序整个思路可以归纳为将整个待排序序列存成线性结构,接下来在任何时候都将它分为排序和未排序两部分,在未排序部分随机找出一个(一般是两者分界的那个元素),通过一次查找在 sorted 子序列中找到这个元素对应的恰当插入位置。</p><p>同理,我们也可以应用于极边算法。</p><h4 id="In-Convex-Polygon-Test"><a href="#In-Convex-Polygon-Test" class="headerlink" title="In-Convex-Polygon Test"></a>In-Convex-Polygon Test</h4><p>递进式的核心技术是 In-Convex-Polygon Test,也就是判别多边形内部或者外部的问题。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/i6u0B3.png"></p><p>我们要判断一个新引入的点是否是当前的极点,其实本质上就是判断当前这个点是否落在此前的凸包的外面或者是里面的位置关系。</p><p>要将刚才那种直觉转化成数学上的判断:每次我们递增式新引入的这个点如果是当前的 extreme point 的话,那么充要条件其实就是看它是否落在当前这个凸包的外面:如果落在外面那它就是下一个 extreme point;否则不是。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/jScreb.png"></p><p>如果凸多边形确实是给定的,而且在此后要反复多次地做这类的查询的话,你是可以对这个多边形做一个预处理(本质上是排序)。</p><p>我们可以大致以一个点作为基础,在其余的 n - 1 个点中可以找到一个居中的连接起来确定一条有向线段。接下来又是我们刚才的惯用的 To-Left Test,经过这样一次常数成本的操作,我们确实可以判断出来这个未知的点到底是落在左边或者是右边,无论是哪边我们都可以将搜索的范围有效地收缩为原先的一半。</p><p>如此往复,我们每一次经过常数时间的成本都可以将这个问题的范围有效地降解为此前的一半,如此下去最终总会到达平凡的情况–trivial case:In-Triangle Test。</p><p>但是这个算法却不可行,最重要的是凸包并不是一成不变的,这种情况下我们的预处理是没有效力的。</p><h4 id="Why-Not-Binary-Search"><a href="#Why-Not-Binary-Search" class="headerlink" title="Why Not Binary Search"></a>Why Not Binary Search</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/vdGYz8.png"></p><p>与插入排序类似,sorted 部分本身就是动态的,即便可以使用二分查找,线性存储所带来的插入成本在最坏情况也会将这种优化无效化。</p><p>回到凸包,对于这种情况朴素的方法反而是最好的。我们可以沿着给定的凸多边形边界做习惯性的 CCW 逆时针旋转遍历,可以发现内部的点一定是在左手一侧的;反之如果我们在任何一段发现某一个点在右侧,那么可以立即断定它并非落在内部。</p><h4 id="Support-Lines"><a href="#Support-Lines" class="headerlink" title="Support-Lines"></a>Support-Lines</h4><p>其实我们还有一个任务要完成,解决如何将新引入的这个点附着或者是增加到原先的凸包上去,要使之成为一个完整的可以继续使用的结构。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/SM3vZw.png"></p><p>凸包切线又被称为 Support Line。</p><h4 id="Pattern-Of-Turns"><a href="#Pattern-Of-Turns" class="headerlink" title="Pattern Of Turns"></a>Pattern Of Turns</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/ibqaRy.png"></p><p>只需要花费两次 To-Left Test,就可以明确确定一个顶点到底是来自 ts(L + R) 还是 st(R + L)。</p><h4 id="Exterior-x2F-Interior"><a href="#Exterior-x2F-Interior" class="headerlink" title="Exterior/Interior"></a>Exterior/Interior</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/X3dd3R.png"></p><h3 id="Jarvis-March"><a href="#Jarvis-March" class="headerlink" title="Jarvis March"></a>Jarvis March</h3><h4 id="Selectionsort"><a href="#Selectionsort" class="headerlink" title="Selectionsort"></a>Selectionsort</h4><p>在介绍 GW 算法之前为了更好地理解它的算法思路,不妨温习一下之前我们很熟悉的选择排序。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/hFprPa.png"></p><p>与刚才的插入排序非常对称,在这里我们的 sorted 和 unsorted 部分是前后颠倒了,这个颠倒实际上是有本质区别的。</p><p>我们需要从 unsorted 部分中去找出一个最大的元素,接着将它进行一次交换挪到刚才 sorted 那个部分的首部。悄然之间,sorted 部分就向前迈进了一步。</p><p>那么这样一个算法思路从宏观的策略来讲我们可以概括为:每次我们都是维护一个局部的解,然后在尚未处理的部分中要去找到一个与当前的这个局部解紧密相关联的一个元素。没错,凸包就可以这么来做。</p><h4 id="Strategy-1"><a href="#Strategy-1" class="headerlink" title="Strategy"></a>Strategy</h4><p>我们如果反思一下在 Extreme Edge 那个算法中为什么会需要多达 n^3 的时间,就会发现根本的原因在于我们实际上考察的对象是遍布所有可能的那些边,这些边的总数会多达 n^2,每个又需要 n 时间鉴别。那么有什么改进的诀窍呢?</p><p>刚才的 selectionsort 就给了我们提示,也就是说我们或许能够将下一个的查找范围缩小到一个足够小的范围。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/xzIg9d.png"></p><p>Jarvis 观察注意到一些结论:</p><ol><li>所有构成凸包的那些边其实在拓扑上讲都是首尾相连构成一个环状结构的</li><li>如果构造过程确实是一条一条边构造,那么如果我在某一个时刻构造出一条边,那么接下来我必然可以沿着它的某一个端点向后继续去找到下一条 extreme edge</li></ol><h4 id="Coherence"><a href="#Coherence" class="headerlink" title="Coherence"></a>Coherence</h4><p>该图可以说明如何在当前已有的这些极边基础上沿着下一个端点拓展出新的极边:</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/pPWwjn.png"></p><p>当前节点称作 <code>k</code>,它的前驱我们称之为 <code>i</code>,下一个极边则是 <code>s</code>。根据刚才 Jarvis 的判断,这个 <code>s</code> 必然来自于其他尚未处理的那些点中的一员。</p><p>而 <code>s</code> 之所以可以脱颖而出,其资本在于它是所有这些拐角中的最小者。也许有同学已经跃跃欲试准备用三角函数和反三角函数操作了,但其实有一种基本的技术就可以解决我们的问题。</p><h4 id="To-Left-Test-1"><a href="#To-Left-Test-1" class="headerlink" title="To-Left Test"></a>To-Left Test</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/Qyx4n5.png"></p><h4 id="Lowest-Then-Leftmost"><a href="#Lowest-Then-Leftmost" class="headerlink" title="Lowest-Then-Leftmost"></a>Lowest-Then-Leftmost</h4><p>一个技术细节问题,也就是我们刚才说到的起点和第一条极边应该如何来找呢?</p><p>作为第一个点,它至少是极点。在这里针对于我们目前的算法需求,可以对问题进一步简化,也就是找到沿着 y 轴负方向最低的位置。这个点也就是所谓的 Lowest Point,在没有退化的情况下必然是 extreme point,所以我们可以以它为起点。</p><p>如果出现多个最低点的退化情况,则优先选择最左侧的点,也称为 Lowest-Then-Leftmost point。</p><h4 id="Implementation"><a href="#Implementation" class="headerlink" title="Implementation"></a>Implementation</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/x8V0zb.png"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Jarvis</span><span class="params">(Point S[], <span class="type">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> k = <span class="number">0</span>; k < n; k++)</span><br><span class="line"> S[k].extreme = FALSE</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> ltl = <span class="built_in">LTL</span>(S, n);</span><br><span class="line"> <span class="type">int</span> k = ltl;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// start with LTL</span></span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> P[k].extreme = TRUE;</span><br><span class="line"> <span class="type">int</span> s = <span class="number">-1</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> t = <span class="number">0</span>; t < n; t++)</span><br><span class="line"> <span class="keyword">if</span> (t != k && t != s && (s == <span class="number">-1</span> || !<span class="built_in">ToLeft</span>(P[k], P[s], P[t])))</span><br><span class="line"> s = t;</span><br><span class="line"> P[k].succ = s;</span><br><span class="line"> k = s;</span><br><span class="line"> } <span class="keyword">while</span>(ltl != k)</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>初始化所有点都被视为非极点,接下来找到刚才所说的 Lowest-Then-Leftmost point 并且把它作为我们的第一个点 <code>k</code> 进入下面一个迭代循环。</p><p>每一个点当它进入这个循环的时候必为极点,第一个点如此,后面的点也一样。接下来我们则要找 <code>s</code> 是逐渐优化最终找到的极点,任何时候我们都未必知道它就是,需要遍历所有候选 <code>t</code>。 </p><p>当 <code>t</code> 通过 To-Left 测试时什么都不处理,<code>s</code> 依然为候选者;反过来 To-Left 测试失败意味着出现在右侧,需要更迭 <code>s</code> 为 <code>t</code>。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/zpOE73.png"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">LTL</span><span class="params">(Point S[], <span class="type">int</span> n)</span> </span>{</span><br><span class="line"> <span class="type">int</span> ltl = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> k = <span class="number">1</span>; k < n; k++)</span><br><span class="line"> <span class="keyword">if</span> (P[k].y < P[ltl].y || (P[k].y == P[ltl].y && P[k].x < P[ltl].x))</span><br><span class="line"> ltl = k;</span><br><span class="line"> <span class="keyword">return</span> ltl; </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="Output-Sensitivity"><a href="#Output-Sensitivity" class="headerlink" title="Output Sensitivity"></a>Output Sensitivity</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/spDhK8.png"></p><h3 id="Lower-Bound"><a href="#Lower-Bound" class="headerlink" title="Lower Bound"></a>Lower Bound</h3><h4 id="Reduction"><a href="#Reduction" class="headerlink" title="Reduction"></a>Reduction</h4><p>在前面几节里我们围绕凸包的计算问题给了一系列的算法,从最开始的 n^4 极点算法一直到后面 n^3 极边的算法,再到 Jarvis march 以及 Incremental n^2,我们在沿着一条不断递减的路线在降低这个算法的复杂度。</p><p>但是如果计算模型是固定的话,必然有一个我们所说的 Low Bound 的概念:下界,也就是复杂度再低也不会低于某一个极限。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/dcT0rw.png"></p><h4 id="CAO-Chong’s-Methodology"><a href="#CAO-Chong’s-Methodology" class="headerlink" title="CAO Chong’s Methodology"></a>CAO Chong’s Methodology</h4><p>三国中曹操的儿子曹冲有个很著名的故事:曹冲称象。</p><p>我们需要度量一个东西的难度,曹冲是要称出一头象的重量,他去找中间参照物石头,通过石头的重量估算出象的重量,而 Reduction 关系就是曹冲的船和水。</p><h4 id="Transitivity"><a href="#Transitivity" class="headerlink" title="Transitivity"></a>Transitivity</h4><p>那么为什么这个问题可以像曹冲称象一样能够间接通过 A 问题的难度就得到 B 问题的难度呢?</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/AZka7O.png"></p><p>对于 A 问题的任何一个输入,我们都可以曲径通幽式的先把它转化为 B 问题的输入,接下来调用 B 问题的任意算法得到输出,再转化为 A 的输出。</p><p>如果 A 问题确实存在某一个下界,而且这个下界是严格大于 n 的,那么我们说 B 问题的所有算法都不可能低于这个复杂度下界。</p><h4 id="Reduction-Input"><a href="#Reduction-Input" class="headerlink" title="Reduction: Input"></a>Reduction: Input</h4><p>首先要把我们未知的那个问题(也就是那头象)摆在右边,这里我们考虑二维的凸包 2-dimensional convex hull 这个问题。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/RHvnFP.png"></p><p>而石头则是 Sorting。也许初看这个问题可能会很迷茫,排序这个问题和凸包这个问题一个是纯粹的抽象计算问题,一个是具体的几何计算问题,二者之间怎么会有联系呢?</p><ol><li>证明可以在线性时间内将排序问题的任何一个输入转化为凸包问题的输入</li><li>证明凸包问题的结果线性时间内转换回到排序问题</li></ol><p>排序问题的输入可以理解为在数轴或者平面上 x 轴一系列的点,在图中我们只取了四个点。为了转换为凸包问题我们需要辅助线,以抛物线作为标尺将每一个点做提升变换,将 n 个数字转化为平面上的 n 个点。</p><h4 id="Reduction-Output"><a href="#Reduction-Output" class="headerlink" title="Reduction: Output"></a>Reduction: Output</h4><p>来自抛物线上有线个点的凸包都具有这样的一个特性:最左侧的那个点和最右侧的那个点会在上面连上一条纵跨的一条单调直线。</p><p>这样我们就完成了 Reduction 的第二步:将凸包问题转化为排序问题。输入是无序的,输出是有序的,这正是排序算法的要求。</p><p>(注:这里有一个疑惑就是如果是正五边形,那么这个左右边界又该如何去界定呢?边界的连线并不单调。)</p><h4 id="Sorting-lt-x3D-N-2d-CH"><a href="#Sorting-lt-x3D-N-2d-CH" class="headerlink" title="Sorting <=N 2d-CH"></a>Sorting <=N 2d-CH</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/s350xy.png"></p><p>所以排序算法的下界是 nlogn,那么凸包问题也是如此,成为 Convex Hull 的下界。</p><h3 id="Graham-Scan-Algorithm"><a href="#Graham-Scan-Algorithm" class="headerlink" title="Graham Scan: Algorithm"></a>Graham Scan: Algorithm</h3><h4 id="Preprocessing"><a href="#Preprocessing" class="headerlink" title="Preprocessing"></a>Preprocessing</h4><p>那么我们来看一个下界意义上讲最优的算法:Graham Scan。</p><p>Graham Scan 首先要做的一件事情是一个预处理,一个排序。这个 presorting 其实就是要找到某一个特定的点,并且将其余所有的点按照这个点所对应的极坐标按极角来做一个排序。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/M8f8bt.png"></p><p>那么具体的这样第一个点应该找谁呢?</p><p>其实任何一个极点理论上都是可以的,同样为了简化算法的解释和实现,我们不妨依然采用前面所讲过的 Lowest-then-Leftmost point 为 1 号点。</p><p>接下来会有与 1 号成角度最小的 2 号点,这里不妨假设 1、2 号点为同一高度,并且没有三点共线的情况,接着按照 (1, 2) 极轴的夹角从小到大命名其他点。</p><p>Graham Scan 算法的数据结构也很简单,只需要两个栈 T 和 S。初始化时依次将 1、2 入栈 S 中,其他 n-2 个点自顶到底存入 T 栈。</p><p>而排序可以选用任意排序,只是对象变成了点,而比较器变为 To-Left Test。</p><h4 id="Scan"><a href="#Scan" class="headerlink" title="Scan"></a>Scan</h4><p>这个扫描过程中要关注三个东西:S 栈栈顶以及次栈顶、T 栈栈顶,我们可以用 <code>S[0]</code>、<code>S[1]</code>、<code>T[0]</code> 表示。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/YCf577.png"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">while</span>(!T.<span class="built_in">empty</span>()) {</span><br><span class="line"> <span class="comment">// test type of current turn</span></span><br><span class="line"> <span class="built_in">toLeft</span>(S[<span class="number">0</span>], S[<span class="number">1</span>], T[<span class="number">0</span>])?</span><br><span class="line"> <span class="comment">// step forward at a left turn</span></span><br><span class="line"> S.<span class="built_in">push</span>(T.<span class="built_in">pop</span>()):</span><br><span class="line"> <span class="comment">// or, backtrack</span></span><br><span class="line"> S.<span class="built_in">pop</span>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Graham-Scan-Correctness"><a href="#Graham-Scan-Correctness" class="headerlink" title="Graham Scan: Correctness"></a>Graham Scan: Correctness</h3><h4 id="Left-Turn"><a href="#Left-Turn" class="headerlink" title="Left Turn"></a>Left Turn</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/3oGYec.png"></p><h4 id="Right-Turn"><a href="#Right-Turn" class="headerlink" title="Right Turn"></a>Right Turn</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/Zw9WOM.png"></p><p>9 号点被包含在了某一个三角形(1-8-10)的内部,它应该被排除掉。</p><h3 id="Graham-Scan-Analysis"><a href="#Graham-Scan-Analysis" class="headerlink" title="Graham Scan: Analysis"></a>Graham Scan: Analysis</h3><h4 id="Backtracks"><a href="#Backtracks" class="headerlink" title="Backtracks"></a>Backtracks</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/fTPnpm.png"></p><h4 id="Planarity"><a href="#Planarity" class="headerlink" title="Planarity"></a>Planarity</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/i2ooE2.png"></p><p>根据欧拉公式,平面图中所有边的数量包括面数加在一起依然和顶点数目保持同阶,边数不会超过顶点数的三倍。</p><h4 id="Amortization"><a href="#Amortization" class="headerlink" title="Amortization"></a>Amortization</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/mAH8dL.png"></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line"> <span class="keyword">if</span>: S.<span class="built_in">size</span>()++; T.<span class="built_in">size</span>()--; <span class="comment">// 1 - 2</span></span><br><span class="line"><span class="keyword">else</span>: S.<span class="built_in">size</span>()++; <span class="comment">// -1 + 0</span></span><br></pre></td></tr></tbody></table></figure><h3 id="Divide-And-Conquer-1"><a href="#Divide-And-Conquer-1" class="headerlink" title="Divide-And-Conquer (1)"></a>Divide-And-Conquer (1)</h3><h4 id="Merge"><a href="#Merge" class="headerlink" title="Merge"></a>Merge</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/fbtL5x.png"></p><p>归并排序作为引子引出我们的算法。</p><p>Divide-And-Conquer 要求我们接近均匀切分 divide,接着我们把这些结果合并起来成为有序序列,变成最终结果。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/3p8soR.png"></p><p>凸包问题也是如此,把输入的点集分成大小规模接近的子集分别求出它们的凸包。问题实质就变成了我有两个凸包子集之后如何将它们合并得到更大的凸包。</p><h4 id="Common-Kernel"><a href="#Common-Kernel" class="headerlink" title="Common Kernel"></a>Common Kernel</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/tuDj5v.png"></p><p>找到一个公共核使得这两个待合并的子凸包能够同时关于这个点是角度有序的。</p><h4 id="Interior"><a href="#Interior" class="headerlink" title="Interior"></a>Interior</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/5IBdvu.png"></p><p>二路归并采用环形次序,然后 Graham Scan 即可。</p><h4 id="Exterior"><a href="#Exterior" class="headerlink" title="Exterior"></a>Exterior</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/ReaP65.png"></p><p>我们预选的那个来自第一个子凸包的 centroid point 不幸落在第二个子凸包的外面,在这种情况下我们应当如何完成二者的归并呢?</p><h3 id="Divide-And-Conquer-2"><a href="#Divide-And-Conquer-2" class="headerlink" title="Divide-And-Conquer (2)"></a>Divide-And-Conquer (2)</h3><h4 id="Preprocessing-1"><a href="#Preprocessing-1" class="headerlink" title="Preprocessing"></a>Preprocessing</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/jzuwYn.png"></p><p>不妨做一个假设,待合并的两个子凸包或者说它们对应的点集是沿着某个方向是可分割的,彼此独立。如果这样我们的合并任务就会变得更加简明、简单。</p><p>为了保证这一点,我们引入一个预处理:按 x 轴排序。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/HybE5r.png"></p><h4 id="Common-Tangents"><a href="#Common-Tangents" class="headerlink" title="Common Tangents"></a>Common Tangents</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/FGE9PW.png"></p><h4 id="Topmost-Bottommost"><a href="#Topmost-Bottommost" class="headerlink" title="Topmost + Bottommost?"></a>Topmost + Bottommost?</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/mhMYrp.png"></p><h4 id="Stitch"><a href="#Stitch" class="headerlink" title="Stitch"></a>Stitch</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/4F6fi4.png"></p><p>我们可以在最初构造一个子凸包的时候记下 leftmost 和 rightmost 各是哪两个顶点,剩下几乎不用花时间:把此前计算结果延续下来即可,而分摊到每一次合并常数时间就够了。</p><h4 id="Zig-Zag"><a href="#Zig-Zag" class="headerlink" title="Zig-Zag"></a>Zig-Zag</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/mTWeCG.png"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/a3j4Rj.png"></p></body></html>]]></content>
<summary type="html"><p><a href="https://twitter.com/cyangmou/status/1571176793943457793"><img data-src="https://img.yousazoe.top/uPic/img/blog/CG1/qrXkrr.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>本节我们将探索计算几何的核心问题:凸包问题。计算几何领域几乎所有的问题都可以“归约”为凸包问题,因此学习凸包问题对整个计算几何体系至关重要。</p></summary>
<category term="计算几何 (Computational Geometry)" scheme="https://yousazoe.top/categories/%E8%AE%A1%E7%AE%97%E5%87%A0%E4%BD%95-Computational-Geometry/"/>
<category term="Computational Geometry" scheme="https://yousazoe.top/tags/Computational-Geometry/"/>
</entry>
<entry>
<title>Introduction</title>
<link href="https://yousazoe.top/archives/ed328fdc.html"/>
<id>https://yousazoe.top/archives/ed328fdc.html</id>
<published>2022-09-21T02:18:51.000Z</published>
<updated>2025-01-03T06:07:21.680Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://twitter.com/m4ndrill/status/1570094225261301760"><img data-src="https://img.yousazoe.top/uPic/img/blog/CG0/Ofd9RT.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>计算几何的重要之处在于它是多门技术与学科的基础,例如图形学、CAD、GIS、路径规划等。这些技术的背后原理往往是基于计算几何的本质上。所以该门课程的学习对养成计算几何理论的总体认识很有帮助,这种认识将为学习者日后的研究工作提供几何的视角。</p><span id="more"></span><h4 id="What-can-we-learn-from-this-course"><a href="#What-can-we-learn-from-this-course" class="headerlink" title="What can we learn from this course?"></a>What can we learn from this course?</h4><blockquote><ul><li>Awareness of Computational Geometry theory that will help students incorporate Computational Geometry into their future research </li><li>Comprehensive understanding on fundamental paradigms/strategies for solving geometric problems, incremental construction, plane sweeping </li><li>Essential geometric structures and algorithms such as polygon decompositions, Voronoi diagrams, Delaunay triangulations</li></ul></blockquote><p>本课程的教学目标有三:</p><ul><li>对计算几何理论的总体认识,在日后的研究工作中,这种认识为你提供几何的视角</li><li>对几何问题求解范式及策略的全面领会,包括递增式构造、平面扫描、分而治之、分层化、近似以及随机化等</li><li>对基本几何结构及其算法的透彻掌握,包括凸包、多边形细分、Voronoi图、Delaunay三角剖分,以及几何求交、点定位、范围查找、截窗查询等</li></ul><h4 id="Are-you-qualified-for-learning-Computational-Geometry"><a href="#Are-you-qualified-for-learning-Computational-Geometry" class="headerlink" title="Are you qualified for learning Computational Geometry?"></a>Are you qualified for learning Computational Geometry?</h4><blockquote><p>Computational Geometry requires some skills of algorithm design and analysis as well as programming, but you don’t need to be an expert before learning this course. Actually, C/C++ programming experience and some basic knowledge of common data structures will be enough. To make sure whether you are qualified for learning this course, check the list below:</p><ul><li>C/C++ programming: variable, function, struct, class;</li><li>Algorithm design and analysis: complexity, amortized analysis, recursion, divide and conquer, linked list, binary search tree, priority queue.</li></ul></blockquote><p>计算几何这门课对数据结构和算法基础和编程基础有一定的要求,但这并不意味着你需要精通所有相关课程。实际上,你只需掌握一些常见数据结构,拥有一定的算法分析能力,以及C/C++语言编程的基本技巧。为确认自己是否适宜选修这门课程,不妨对照以下清单做一清点:</p><ul><li>C/C++语言程序设计基础:变量,函数,结构体,类</li><li>数据结构与算法分析:复杂度、摊还分析、递归、分治法、链表、栈、二叉搜索树、优先队列</li></ul><h3 id="History-of-This-Course"><a href="#History-of-This-Course" class="headerlink" title="History of This Course"></a>History of This Course</h3><p>这门课已经开设 18 年之久,虽然国外诸多著名高校都开设了这门课程,但国内做计算几何方面的学校和机构屈指可数。</p><h3 id="What’s-Computational-Geometry"><a href="#What’s-Computational-Geometry" class="headerlink" title="What’s Computational Geometry"></a>What’s Computational Geometry</h3><p>说到计算几何,我们要做一个名词辨析。</p><p>如果你第一次听到 Computational Geometry,首先注意到的肯定是几何,脑海中浮现的是曲线、曲面诸如此类。事实上我国数学家苏步青八十年代就曾出版过一本《计算几何》的书。 此计算几何非彼计算几何,这门课更加强调的是计算。现代计算几何人们公认诞生于 1978 年 Shamos 那篇著名的博士论文,所以这门学科到现在也不过区区四十年的发展历史。</p><p>当然计算几何之所以很重要,就是因为它是很多学科尤其是技术学科的基础,包括典型的图形学、CAD、GIS、路径规划等等……最后都会回到计算几何这些基本的问题。</p><p>在学习之前如果一言以蔽之概括一下的话,计算几何就是就是”算法设计与分析”的几何版,它所讨论的对象、问题的表面形式都是几何的,它求解这些问题的方法、策略高到上面的方法论其实也都是几何的。尽管从这个方面讲计算几何只是算法设计与分析的一个分支,但是正因为它融入了很多古典的一些离散几何学、组合几何学等等精华的结论和方法,所以它不仅仅是一个几何和计算两个问题的物理反应,而是很深入的化学反应。</p><h3 id="How-to-Learn-CG-Better"><a href="#How-to-Learn-CG-Better" class="headerlink" title="How to Learn CG Better"></a>How to Learn CG Better</h3><p>计算几何强调本质的东西就是要形象。</p><p>没有人喜欢复杂深奥的东西,所以这门课如果在学习过程中没办法很好理解推导和公式,不必拘泥于复杂深奥的泥潭,暂时放下它,将注意力放在图形和具体表现上。</p></body></html>]]></content>
<summary type="html"><p><a href="https://twitter.com/m4ndrill/status/1570094225261301760"><img data-src="https://img.yousazoe.top/uPic/img/blog/CG0/Ofd9RT.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>计算几何的重要之处在于它是多门技术与学科的基础,例如图形学、CAD、GIS、路径规划等。这些技术的背后原理往往是基于计算几何的本质上。所以该门课程的学习对养成计算几何理论的总体认识很有帮助,这种认识将为学习者日后的研究工作提供几何的视角。</p></summary>
<category term="计算几何 (Computational Geometry)" scheme="https://yousazoe.top/categories/%E8%AE%A1%E7%AE%97%E5%87%A0%E4%BD%95-Computational-Geometry/"/>
<category term="Computational Geometry" scheme="https://yousazoe.top/tags/Computational-Geometry/"/>
</entry>
<entry>
<title>基础算法</title>
<link href="https://yousazoe.top/archives/ce5da845.html"/>
<id>https://yousazoe.top/archives/ce5da845.html</id>
<published>2022-09-20T13:04:37.000Z</published>
<updated>2025-01-03T06:07:21.724Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><a href="https://www.behance.net/gallery/89559779/The-spirit"><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/DiUjjX.jpg"></a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>简单介绍一些基本算法,包括:搜索、贪心、二分查找与三分查找、序列分治以及排序。</p><span id="more"></span><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(3).PNG"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(4).PNG"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(5).PNG"></p><h3 id="搜索"><a href="#搜索" class="headerlink" title="搜索"></a>搜索</h3><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(6).PNG"></p><h4 id="什么是搜索"><a href="#什么是搜索" class="headerlink" title="什么是搜索"></a>什么是搜索</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(7).PNG"></p><h4 id="搜索树"><a href="#搜索树" class="headerlink" title="搜索树"></a>搜索树</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(8).PNG"></p><ul><li>以起始状态为根,每个状态向其后继状态连有向边,可以得到一棵有根树<ul><li>终止状态对应这棵树的叶子</li></ul></li><li>搜索过程可以被抽象成遍历这棵搜索树的过程</li><li>如果需要遍历整棵搜索树,则复杂度至少正比于搜索树的结点数量</li><li>如果除叶结点外的结点都有至少两个叶结点,则可以用叶结点的数量估计有根树的大小<ul><li>为什么?</li></ul></li><li>如果除终止状态之外的状态都至少有两个后继状态,则可以用终止状态的数量估计搜索的复杂度</li><li>如果除终止状态之外的状态的后继状态数量是有下限的,则可以用层数估计终止状态的数量</li></ul><h4 id="搜索复杂度"><a href="#搜索复杂度" class="headerlink" title="搜索复杂度"></a>搜索复杂度</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(9).PNG"></p><h4 id="深度优先与广度优先"><a href="#深度优先与广度优先" class="headerlink" title="深度优先与广度优先"></a>深度优先与广度优先</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(10).PNG"></p><p>深度优先搜索(<strong>Depth-First Search</strong>)优先遍历一个后继结点的子树内所有结点</p><ul><li>先一条路走到黑,再返回上一个分岔点</li></ul><p>广度优先搜索(<strong>Breadth-First Search</strong>)先遍历所有后继结点,再遍历后继结点的后继</p><ul><li>在分岔点分身,最终每个终止结点都有一个分身</li></ul><h4 id="深度优先搜索-Depth-First-Search"><a href="#深度优先搜索-Depth-First-Search" class="headerlink" title="深度优先搜索 Depth-First Search"></a>深度优先搜索 Depth-First Search</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(11).PNG"></p><h4 id="广度优先搜索-Breadth-First-Search"><a href="#广度优先搜索-Breadth-First-Search" class="headerlink" title="广度优先搜索 Breadth-First Search"></a>广度优先搜索 Breadth-First Search</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(12).PNG"></p><h4 id="搜索策略的选择"><a href="#搜索策略的选择" class="headerlink" title="搜索策略的选择"></a>搜索策略的选择</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(13).PNG"></p><p><strong>深度优先搜索 DFS</strong></p><ul><li>只需储存从初始状态到当前状态的一条路径</li><li>当递归层数较深时可能会爆栈</li><li>需要考虑回溯撤销的问题,细节可能比较麻烦<ul><li>搜索层数不确定时可能会带来问题:无限拓展</li></ul></li><li>移动棋子,绕了一大圈返回起点</li><li>子树中结点编号是连续的</li></ul><p><strong>广度优先搜索 BFS</strong></p><ul><li>需要储存所有尚待拓展的状态,空间开销大</li><li>可以动态使用堆内存</li><li>状态单向拓展,实现较为简单</li><li>可以知道从初始状态到每个状态的最少步数<ul><li>适用于边权都为 1 的最短路</li></ul></li><li>同一层的结点编号是连续的</li></ul><h4 id="扩展阅读-迭代加深搜索"><a href="#扩展阅读-迭代加深搜索" class="headerlink" title="扩展阅读:迭代加深搜索"></a>扩展阅读:迭代加深搜索</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(14).PNG"></p><h4 id="搜索剪枝-Pruning"><a href="#搜索剪枝-Pruning" class="headerlink" title="搜索剪枝 Pruning"></a>搜索剪枝 Pruning</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(15).PNG"></p><p>果树剪枝是为了让树长得更好看,结出的水果质量更高</p><p>搜索树也可以剪枝,让搜索效率更高;注意不要把最优解给剪枝掉了</p><p>可行性剪枝</p><ul><li>如果当前状态已经不满足题目的要求,则不继续拓展</li><li>可以用于最优化问题,也可以用于统计解</li></ul><p>最优性剪枝</p><ul><li>只能用于最优化问题</li><li>如果从当前状态出发,可以得到的最优解一定不比已经得到的最优解优,则不继续拓展</li></ul><p>此外还有其它剪枝思路,例如在双人游戏中有 Alpha-beta 剪枝等,在这里不详细展开</p><h4 id="经典问题"><a href="#经典问题" class="headerlink" title="经典问题"></a>经典问题</h4><h5 id="八皇后"><a href="#八皇后" class="headerlink" title="八皇后"></a>八皇后</h5><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(16).PNG"></p><h5 id="埃及分数"><a href="#埃及分数" class="headerlink" title="埃及分数"></a>埃及分数</h5><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(18).PNG"></p><h5 id="剪枝思路"><a href="#剪枝思路" class="headerlink" title="剪枝思路"></a>剪枝思路</h5><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(19).PNG"></p><ul><li>放缩!</li><li>如果怎么救都救不回来,那就应该放弃<ul><li>如果后续状态一定不合法,则不继续深入搜索</li></ul></li><li>以最小化问题为例<ul><li>为当前状态的所有后继估计解的下界,如果下界大于(或大等于,取决于具体题目)当前最小值则剪枝</li></ul></li><li>扩展阅读:分支定界法求解</li></ul><h4 id="启发式搜索"><a href="#启发式搜索" class="headerlink" title="启发式搜索"></a>启发式搜索</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(20).PNG"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(21).PNG"></p><h3 id="贪心"><a href="#贪心" class="headerlink" title="贪心"></a>贪心</h3><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(26).PNG"></p><h4 id="贪心-Greedy-Algorithm"><a href="#贪心-Greedy-Algorithm" class="headerlink" title="贪心 Greedy Algorithm"></a>贪心 Greedy Algorithm</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(27).PNG"></p><h4 id="贪心与动态规划的区别"><a href="#贪心与动态规划的区别" class="headerlink" title="贪心与动态规划的区别"></a>贪心与动态规划的区别</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(28).PNG"></p><h4 id="找零钱问题"><a href="#找零钱问题" class="headerlink" title="找零钱问题"></a>找零钱问题</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(29).PNG"></p><ul><li>假设你有面值为 1 元,5 元,10 元,20 元,50 元和 100 元的纸币各若干张</li><li>用这些纸币表示出给定的正整数金额,使得用的纸币数量最少</li><li>贪心做法:每次选取不超过尚未被表示的金额的面值最大的纸币<ul><li>127 → 100 + 20 + 5 + 1 + 1</li><li>正确性?</li></ul></li><li>假设纸币的面值是 1 元,2 元,4 元,8 元,16 元,……,贪心做法还是正确的吗?</li><li>假设纸币的面值是 1 元,5 元,10 元,20 元和 25 元,贪心做法还是正确的吗?<ul><li>反例:40 → 25 + 10 + 5,但是 20 + 20 更优</li></ul></li></ul><h4 id="证明贪心正确性的常见思路"><a href="#证明贪心正确性的常见思路" class="headerlink" title="证明贪心正确性的常见思路"></a>证明贪心正确性的常见思路</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(30).PNG"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(31).PNG"></p><h4 id="Huffman编码"><a href="#Huffman编码" class="headerlink" title="Huffman编码"></a>Huffman编码</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(32).PNG"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(33).PNG"></p><h3 id="二分与三分"><a href="#二分与三分" class="headerlink" title="二分与三分"></a>二分与三分</h3><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(34).PNG"></p><h4 id="二分查找-Binary-Search"><a href="#二分查找-Binary-Search" class="headerlink" title="二分查找 Binary Search"></a>二分查找 Binary Search</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(35).PNG"></p><h4 id="一个小故事"><a href="#一个小故事" class="headerlink" title="一个小故事"></a>一个小故事</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(36).PNG"></p><h4 id="三分"><a href="#三分" class="headerlink" title="三分"></a>三分</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(48).PNG"></p><h3 id="递归与分治"><a href="#递归与分治" class="headerlink" title="递归与分治"></a>递归与分治</h3><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(50).PNG"></p><h4 id="递归"><a href="#递归" class="headerlink" title="递归"></a>递归</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(51).PNG"></p><h4 id="分治"><a href="#分治" class="headerlink" title="分治"></a>分治</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(52).PNG"></p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(53).PNG"></p><h3 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h3><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(54).PNG"></p><h4 id="排序-1"><a href="#排序-1" class="headerlink" title="排序"></a>排序</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(55).PNG"></p><h4 id="冒泡排序"><a href="#冒泡排序" class="headerlink" title="冒泡排序"></a>冒泡排序</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(56).PNG"></p><h4 id="插入排序"><a href="#插入排序" class="headerlink" title="插入排序"></a>插入排序</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(57).PNG"></p><h4 id="选择排序"><a href="#选择排序" class="headerlink" title="选择排序"></a>选择排序</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(58).PNG"></p><h4 id="归并排序"><a href="#归并排序" class="headerlink" title="归并排序"></a>归并排序</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(59).PNG"></p><h4 id="快速排序"><a href="#快速排序" class="headerlink" title="快速排序"></a>快速排序</h4><p><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/BasicAlgorithm%20(60).PNG"></p></body></html>]]></content>
<summary type="html"><p><a href="https://www.behance.net/gallery/89559779/The-spirit"><img data-src="https://img.yousazoe.top/uPic/img/blog/THUAA3/DiUjjX.jpg"></a></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>简单介绍一些基本算法,包括:搜索、贪心、二分查找与三分查找、序列分治以及排序。</p></summary>
<category term="清华大学算法协会 (Tsinghua University Algorithm Association)" scheme="https://yousazoe.top/categories/%E6%B8%85%E5%8D%8E%E5%A4%A7%E5%AD%A6%E7%AE%97%E6%B3%95%E5%8D%8F%E4%BC%9A-Tsinghua-University-Algorithm-Association/"/>
<category term="Cpp" scheme="https://yousazoe.top/tags/Cpp/"/>
</entry>
<entry>
<title>《赛博朋克:边缘行者》与人文主义的反思</title>
<link href="https://yousazoe.top/archives/dff9eab3.html"/>
<id>https://yousazoe.top/archives/dff9eab3.html</id>
<published>2022-09-19T02:03:09.000Z</published>
<updated>2025-01-03T06:07:21.712Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><html><head></head><body><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/FQe8VN.jpg"></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>在贪墨成风的反乌托邦世界中,四处都是生化改造植入体。动画剧聚焦一个在街头长大的鲁莽天才少年努力想成为边缘行者:拿钱办事的法外之徒。</p><span id="more"></span><p>近些年里,游戏改编的影视作品越来越多,每一部都会宣称自己制作如何精良,但它们要么如《龙之血》《双城之战》那样摒弃了游戏玩法、着重于挖掘背景故事,要么如《神秘海域》或者《光环》,大幅改造甚至看不起原作剧情直接另起炉灶,没有任何一个能像《边缘行者》这样忠实地遵从原作的框架、同时还能讲好一个故事。它甚至还弥补了《2077》至今未能实现的缺憾:我们终于看到了单分子线在大杀四方的同时也能实现骇入,也终于看到了 NCART,其实是能坐人的。</p><p>《边缘行者》播出以来在各个评分网站上都收获了不错的口碑,这不仅证明了《2077》确实有着优秀的基础框架,可惜潜力没有被充分发挥出来;同时也是打了那些自大的好莱坞编剧们的脸:老老实实照着游戏内容拍,远比你们一拍脑门搞出来的那套东西更能讨好观众。</p><p>当然,出色的作画、讨喜的人设,还有以上说的种种,固然能够大幅提升玩家们的观感;但真正能够打动观众的,还是赛博朋克的内核。</p><h3 id="剧情梳理"><a href="#剧情梳理" class="headerlink" title="剧情梳理"></a>剧情梳理</h3><h4 id="PART1"><a href="#PART1" class="headerlink" title="PART1"></a>PART1</h4><p>本剧的主角,大卫·马丁内斯,在故事刚开始的时候,还是一个不谙世事的学生。他的母亲葛洛丽亚在市政部门工作,薪水微薄,日常工作是清理横死街头的赛博疯子和帮派分子。借着职务之便,能够接触到这些死人身上拆下来的义体,她便通过把义体倒卖给边缘行者们来赚取外快。而她这样辛辛苦苦、不惜违法地赚钱,目的就是供养自己的儿子在荒坂学院念书。</p><p>荒坂学院是荒坂公司附属的精英学术机构,费用高昂,但学员能够成功毕业,就有机会进入荒坂公司工作,再之后,就有机会一步一步爬到高层——这在葛洛丽亚看来,是普通人唯一能够改变命运的手段。</p><p>而对大卫来说,自己和学院里其他那些少爷终究不是一路人。尽管成绩优异,但连备用制服都买不起的贫寒家境让他处处遭到排挤。平时,他只能在黑超梦带来的感官刺激里麻醉自己,同时靠帮黑市的义体医生推销这些超梦来赚些零花钱。</p><p>本来,日子像这样平平常常地过去,也许大卫最终会成为荒坂公司的一颗螺丝钉,在无止境的工作和加班中被消磨殆尽;又或许时运眷顾,他真的会在企业里步步高升,最后出人头地呢。</p><p>但按部就班的生活因为一场车祸戛然而止。赶来救援的创伤小队把没有保险的母子二人留在原地等死,超级摩天楼里简陋医院的廉价急救套餐终于还是没能救回葛洛丽亚。</p><p>大卫把母亲火化——这是最便宜的丧葬方案——抱着母亲的骨灰回到了因为租金逾期未交而把他拒之门外的家。</p><p>他甚至没有哭泣。</p><p>在夜之城,死亡会让人麻木。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/UPABD8.jpg"></p><p>可他在母亲的遗物中发现了一件义体,他在黑超梦中见过它。斯安威斯坦,军用级义体,能够触发缓时。发动时,周围的一切仿佛静止,只有使用者能够移动自如。</p><p>到了2077年,斯安威斯坦已经发展到可以人手一件的程度,但在剧中故事发生时,装备这件义体还是一个禁忌。不只是因为它专供军用科技内部使用,外部难以获取;更是因为,普通人使用它,十有八九会发疯。</p><p>大卫没管这么多,他甚至在不知道什么是免疫抑制剂的情况下,去找那个相熟的义体大夫安装了斯安威斯坦。随后他直奔学院,在全班同学面前,把之前羞辱了自己和自己的母亲的田中痛打了一番。</p><p>在这之后又是无尽的空虚。他漫无目的地行走在那些曾经走过无数遍的道路上,不知道该做些什么。</p><p>而在这时他遇到了生命中的光。</p><h4 id="PART2"><a href="#PART2" class="headerlink" title="PART2"></a>PART2</h4><blockquote class="blockquote-center"> <i class="fa fa-quote-left"></i> <p>Lucy,我偶尔,只是很偶尔的时候,会问自己,如果我没有见过你,我到现在的人生会不会不一样?没有成为边缘行者的我,没有遇到爱情的我,没有结实这么多同伴的我。我也许会给田中道歉、回到荒坂学院、成为义体实验对象,也许,有那么一丝的可能性,我能够进入公司的高层,能够实现妈妈的愿望。那样的什么都不知道的我,会不会也很快乐呢? </p><p>但是再给我一次机会,我仍然不可能做出其他的选择。在那趟列车之前,在妈妈遇到车祸之前,在田中把我揍得体无完肤之前,我就见过你了。也许没有真的见过你,也许只是在梦里见过你。但那一头银发,是我黑暗中的光,我早已见过一次又一次,就算是在梦里我也不会认错。 </p><p>我的人生早已注定了。我注定会认识你。</p><p>而这是我遇到过的,最最最幸运的事。</p> <i class="fa fa-quote-right"></i> </blockquote><p>他之前就遇到过好几次,一头璀璨耀眼的银发,但总是转瞬即逝,以至于他会以为是幻觉。不过,这次是在轻轨上,她无法再那么轻易地消失了。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/KbQWK1.jpg"></p><p>女孩名叫露西。他看到她在偷取别人的芯片,她发现了他窥伺的目光,冲突、解释、握手言和。他提出帮忙,三七分成。之后是一番奇遇,她把他邀请到家中,分享了自己隐秘的梦想——离开夜之城的牢笼,去月球生活。他们在超梦里登月,在虚拟的低重力下跳跃、欢笑。然后美梦醒来,一伙壮汉把大卫拉回现实。他们是赛博朋克,即是边缘行者。斯安威斯坦本是那伙人中的头领曼恩向葛洛丽亚订购的,如今后者杳无音信,露西按图索骥找到了大卫,现在他们要拿回自己的东西。大卫坚定地要为他们工作来偿还债务,思忖良久,曼恩答应了下来。</p><p>就这样大卫加入了这个小团体,认识了浑身装满义体的大块头曼恩 Maine、曼恩强壮的女友多利欧 Dorio、有着一双灵活手臂的技术狂皮拉 Pilar、皮拉的萝莉妹妹瑞贝卡 Rebecca、沉默寡言的黑客专家琦薇 Kiwi、以及老练可靠的司机法尔科 Falco。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/FQe8VN.jpg"></p><p>大卫在这里找到了家的感觉、和同伴们打成一片,也在一次次任务中逐渐成长为了优秀的边缘行者。他向 Lucy 吐露了自己的感情,答应要带着她去月球。Lucy 吻了过去,两颗心贴在了一起。</p><p>如果到此为止,不过是一系列热血番中常见的展开。主角团中有人死去、有人离开,但主角总是借着光环无法倒下。可赛博朋克的世界不是童话故事。一次任务中,大哥曼恩终于无法控制住自己日渐被义体所侵蚀的神经系统,失手攻击 Kiwi,打乱了行动计划,Lucy 作为备用黑客迫不得已加入任务;后面又因为失神造成了 Dorio 的死去。面对着 NCPD 和创伤小队的双重围堵,曼恩知道自己大限已至。面对前来试图营救自己的大卫,他只是淡然地说了一句:“这就是我的终点了。”</p><p>随后,便用烈火将自己和爱人焚尽。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/yNCjfh.jpg"></p><h4 id="PART3"><a href="#PART3" class="headerlink" title="PART3"></a>PART3</h4><blockquote class="blockquote-center"> <i class="fa fa-quote-left"></i> <p>他们都说,夜之城的传奇都在坟墓里。</p><p>这大概是真的吧。那么,曼恩大哥也算是一个传奇了吧。</p><p>但如果有选择的话,我宁愿不做那个传奇。毕竟,以前你们好多人和我说,说我老是为别人的梦想而活;而现在我也有自己的梦想了呢。我的梦想,就是我之前承诺过的,帮你实现你的梦想。你那时说你的梦想是去月球,我从来没有忘记过。月球的单程票是25万欧,当然,如果想要在那里生活,应该还需要更多的钱吧。如果完成了这最后一份差事,大概就足够了。如果能拿到赏金,如果能和你一起去月球,是不是也挺不错的呢?</p> <i class="fa fa-quote-right"></i> </blockquote><p>此去经年,大卫成了小团体的新领袖,在圈子里的声望也越来越显赫。Rebecca 在和大卫搭档的过程中对他暗生情愫,可这份心思又怎么能够挑明呢?露西和他一起住进了漂亮的大公寓里,但不再参与组织的工作。当时,她在任务目标的大脑中发现,对方想要拿大卫作为荒坂的新产品“义体金刚”的实验对象。为了保护大卫,她删除了相关信息,没告诉任何人;在这几年中,她名义上拒绝参与团队工作,实际上却是在追杀任何了解实验计划的荒坂员工。</p><p>Lucy 的讳莫如深在大卫眼中看来是逐渐的疏远,可他自己又何尝没有改变呢?</p><p>当初,为了跑步时能够追上 Lucy,他给自己装上了斯安威斯坦外的第一个义体——一对人工肺。后来,曼恩嘱托他,为自己多装几个义体,变强,活下来。于是几年下来,大卫也变成了一个钢铁大块头,一个机械部分多于肉体的义体改造狂。他用的免疫抑制剂,药效也越来越猛、剂量也越来越多。他会时不时地抑制不住自己手臂的抖动,一如曼恩最后的那些日子。</p><p>明眼人都看得出来,大卫离赛博精神病不远了。</p><p>在《2077》里,也许是因为 Relix 芯片的特殊性,又或许仅仅是因为 CDPR 偷懒没有做出来,V 就算把自己浑身上下改装个遍,也感觉不到义体的副作用。可对普通人来说,你的身上不属于自己的部件越多,你的神经系统和肉体对它们的排异反应就会越大,最终,你的大脑会成为机械的奴隶,这就是赛博精神病。成为赛博疯子就是每个没在这之前就挂掉的义体改造狂最终的结局,而在终点等待着他们的,就是疯控小队。</p><p>大卫相信自己有某种天赋。这天赋从他还是十几岁的孩子、刚刚装上斯安威斯坦就能熟练掌控、随心所欲地运用就能看出端倪。要知道,就算是V,发动斯安威斯坦的效果都需要60秒游戏内时间的冷却。这天赋让他能装上一个又一个的义体,而不良反应比起其他人来说又是少之又少。这天赋让他觉得自己是“独特“的,让他觉得他能在夜之城里混出个名堂,让他觉得,带着露西去月球生活,也是有可能的。</p><p>所以说就算 Lucy 和 Rebecca 都劝说他,不要再改装自己了,拆卸下一些义体吧,他还是固执地为自己安装更多的功能模块。</p><p>在他看来,这是能让他赚到足够去月球的钱的,唯一的道路。</p><p>机会来了。一个大单子。拦截荒坂的一辆运输车,取到货,数百万欧,足够团队里每个人过上逍遥日子。当然,这自始至终都是诱饵,目的是让大卫穿上“义体金刚“、与军用科技斗个两败俱伤、最后由荒坂公司自己回收其中的实战数据。另一边,Lucy之前的行迹败露,又遭到Kiwi的出卖,被已成为荒坂哈巴狗的中间人法拉第扭送往荒坂。</p><p>计划如公司所料般进行,大卫一伙被军用科技包围,法拉第用合成的 Lucy 声音哄骗大卫穿上义体金刚,Kiwi跳反,偷袭法尔科后扬长而去。此后,按计划,大卫会发疯、与军用科技同归于尽——可 Lucy 在最后关头挣脱,向大卫发出了警告。这义体是把你赛博精神病的最后一根稻草,就算你没有疯,超量的免疫抑制剂也会让你的理智滑向边缘之外。求求你,千万别装。</p><p>可大卫有什么选择呢?穿了这义体,就不能陪你去月球;但不穿这义体,就不能救下你。这看似是两种选择,但对大卫来说,可能性只有一个。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/SkWYWR.jpg"></p><p>义体安装完成,Rebecca 帮他注入了一大瓶抑制剂。他启动机体,反重力装置和磁场发生装置风卷残云般摧毁了军用科技的包围圈。和荒坂料想中的不同,大卫还保持着清醒。下一步,他们向荒坂塔开去。</p><h4 id="PART4"><a href="#PART4" class="headerlink" title="PART4"></a>PART4</h4><blockquote class="blockquote-center"> <i class="fa fa-quote-left"></i> <p>可是我也许一开始就知道这是不可能的吧。大概从我安装上斯安威斯坦那天起,我就从它前主人的超梦中预见到了自己的结局。曼恩大哥那时对我说,那就是他的终点了。我当时不甘心,我当时觉得也许我再努力一点就能救下他了。但现在我知道了,当一个边缘行者的终点到来时,他会明白的。正如这就是我的终点了。</p><p>我没能救下妈妈,没能救下曼恩大哥,没能救下瑞贝卡,但我终于救下了你。</p><p>在月球好好生活吧。去感受地球六分之一的重力。去感受太阳的温度。</p><p>只是对不起,我们不能一起去了。</p> <i class="fa fa-quote-right"></i> </blockquote><p>穿过荒坂和军用科技的重重围堵,一行人终于来到了最后的目的地。在荒坂塔前,大卫注射了最后一管免疫抑制剂,这其实就是他的死亡宣告:就算他最后战胜了重锤,也无法活着离开。更何况我们都心知肚明,他不可能打赢。</p><p>相比于 V 的轰轰烈烈,大卫的荒坂塔之旅,结束得既迅速又潦草。早在突围时大卫就开始在疯狂和清醒的边缘游走,越接近公司广场时更是越发难以稳定智识。恍惚中他登上了荒坂塔的顶端,在某种意义上完成了母亲的梦想。随后他冲进大楼,在这里遇到了亚当·重锤,一个他以为并非真实的人物。一个几乎只有大脑是原装的机械怪物。一个全无人性的梦魇。斯安威斯坦对重锤来说不过是初级的植入物,面对他,大卫毫无胜算。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/xrSssQ.jpg"></p><p>在此时的大卫身上我仿佛看到了自己。曾经自命不凡地以为自己是独特的那个,在现实日复一日的捶打下逐渐动摇了信心,开始怀疑自己,最后终于在某一刻发现,自己的“独特”在别人眼里可能只是个笑话。看到重锤,正是让大卫明白,自己的“独特”、“对于义体的天生钝感”,在这种公司培育出来的怪物面前,根本不值一提。</p><p>于是他释然了。就像当初的曼恩那样,大卫也明白了自己的结局。他选择了他能做到的最好的事:给法尔科和露西争取时间,让他们带着钱离开。让露西能够实现去月球的梦想。至于他自己呢?</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/3qxDOb.jpg"></p><h3 id="绝望与无力"><a href="#绝望与无力" class="headerlink" title="绝望与无力"></a>绝望与无力</h3><p>从第一集开始大卫因为没有钱所以只能眼睁睁地看着本来有救的母亲去世,到最后站在荒坂大厦的顶端往下纵身一跃,他确实给垄断这个世界的大企业造成了一点小小的麻烦,但是归根结底,他都始终无法像传统的 TRIGGER 主角们那样用自己的意志去决定自己的命运。</p><p>恰巧相反,男主大卫从一开始接受移植手术到最后组装金刚机甲其实都是在接受一种看似自由选择的命运操弄,这种无论如何努力却依旧还是在既有框架体系之中的绝望和无力感,个人认为是对于 TRIGGER 传统的【钻破体系障碍】的逆反,但同时也是对于赛博朋克这一题材的绝佳诠释。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/OlIlVF.jpg"></p><p>在赛博朋克的世界里,一切的传统价值都被解构掉了,就连【相信】这个词也不能够被相信了,只有赤裸裸的能够被量化的金钱、身体机能改造或者成瘾品才能作为生存的意义,以至于大卫实际上只能够为了别人而活,为别人的梦想而活,他自己根本找不到自己为什么要活着的原因。</p><p>赛博朋克这个概念本来也就是作为一种现代化狂飙突进到极点之后的反乌托邦,因此大卫的迷茫其实也有其一定的现实意义。</p><h3 id="梦想与现实"><a href="#梦想与现实" class="headerlink" title="梦想与现实"></a>梦想与现实</h3><p>有批评者认为本作剧情不佳,觉得情节转折推进生硬、大卫行事动机薄弱,觉得露西明明可以和大卫解释清楚,觉得大卫明明可以拆下义体,觉得两个人明明可以靠着攒来的钱远走高飞,又何必走到最后那一步呢?</p><p>可我们别忘了,这里是夜之城,在这里,公司就是不坏的王权。</p><p>哪怕是当年强尼·银手和摩根·黑手把两颗战术核弹塞进了荒坂塔,把它夷为了平地,荒坂也能够在原地重新建造一座更气派的大楼。哪怕是后来无所不能的、最后成了城市之王的V,也不过是杀了几个西海岸的董事会成员、暂时阻止了荒坂三郎借尸还魂,荒坂在日本的根基并没有动摇、何况三郎的意识在别的分部可能也有备份。哪怕是荒坂就此一蹶不振,军用科技、康陶、夜氏集团也会立刻把它的份额瓜分殆尽。一切都不会有任何改变。</p><p>而大卫呢?大卫后来租住的公寓看上去相当豪华,但如果不接任务,他可能会连抑制剂都供养不起,更别提攒钱了;他的团队已经算是圈子里的顶尖队伍,在中间人法拉第眼中也不过是一批耗材;后者还幻想着一步登天进入公司,但在真正的公司人眼中也只是个逐利的小丑。在夜之城,哪怕你混成了来生的传奇,在公司眼里也是随时可以碾碎的蛆虫。大名鼎鼎、天赋异禀的大卫·马丁内斯,甚至都打不过亚当·重锤这条荒坂豢养的看门狗,更遑论撼动公司的一根汗毛。</p><p>在边缘行者们眼中,大卫最终迎来了一个壮烈的牺牲,一个传奇式的结局。</p><p>而在公司眼中,整个事件自始至终也没有惊动任何一个荒坂家族的成员,甚至可能董事会都对此漠不关心。</p><p>只是疯控小队又在公司广场上处决了一个赛博疯子,夜之城普普通通的一天而已。</p><p>对于在这样一个世界里的底层民众来说,只有梦想,只有那一点点对于未来的希望,才能支撑着人活下去。</p><p>人们总是对大卫说,你不要为了别人的梦想而活,但大卫根本不知道该梦想什么。在城市的边缘徘徊了那么久,他早已丧失里梦想的能力,哪怕最后对女主说出,“我的梦想就是完成你的梦想”,也依然没有跳出为别人梦想而活的桎梏。这其实也揭示了他注定的悲剧结局,因为直到最后,他也没有学会该怎样为自己而活。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/k6rWhY.jpg"></p><p>就算是那些有梦想的人,又能梦想到多远的地方呢?母亲的梦想是迎合,是儿子有朝一日能出人头地;曼恩的梦想是苟且,是靠不断变强的身体和同伴的支持走下去;露西的梦想是逃避,是逃往一个能够远离荒坂的触手的地方。而就连这些卑微的梦想,也会被公司一个接一个地毁灭。就算没有遇到车祸,葛洛丽亚的身体也会被不断累积的账单、债务和日夜的操劳压垮;就算任务没有出现差错,曼恩也会因为义体对神经系统的侵蚀而一步一步滑向彻底疯狂的深渊;而如果没有大卫,就算露西特意租了一间可以看到发射场的公寓,那一艘又一艘腾空而起的飞船里,也永远不会有她的身影;就算是现在,露西成功地来到了月球上,她又能躲得过荒坂的清算吗?</p><p>潘多拉因为好奇打开了众神留下的盒子,所有丑恶的东西一齐向人间四散飞去。在最后一刻她终于关上了盒子,留下了希望。有人说这是众神最后的怜悯,就算周围一片黑暗,希望仍存。</p><p>也有人说这是众神最大的恶意,因为每一个希望背后,总有绝望随行。</p><blockquote class="blockquote-center"> <i class="fa fa-quote-left"></i> <p>L’enfer est pavé de bonnes intentions. </p> <i class="fa fa-quote-right"></i> </blockquote><p>哦,还有爱情,这最后一点慰藉,最后一点美好的东西。</p><p>可夜之城的爱情,也不过是风中的烛火,轻轻一吹,就熄灭了。</p><h3 id="人文主义反思"><a href="#人文主义反思" class="headerlink" title="人文主义反思"></a>人文主义反思</h3><p>对未来的担忧往往会被人们以鲜明而极端的方式所表达出来。比如阿道司·赫胥黎的《美丽新世界》和乔治·奥威尔的《1984》等经典反乌托邦的末日预言,或者赫伯特·乔治·威尔斯的电影《先河》呈现出的未来世界完美或近乎完美的愿景。</p><p>而兴起于20世纪80年代由“控制论”和“朋克”两个概念组合而成的“赛博朋克”,正诞生于社会大变革下人们对未来的担忧的时代。于是,一场基于赛博朋克概念的文学运动逐渐蔓延,其所传达的精神文化通过各种形式的媒体传播,一种包罗万象、不断增长的亚文化随之流行。</p><p>赛博朋克展现了一种信息高度发达的未来人类社会图景,这种社会表面充满和平,内在却充斥着难以控制的阶级矛盾、资源紧缺等弊病。物质文明泛滥并高于精神文明,致使人类精神在高度发达的技术社会难以实现真正自由,从而具有明显的反乌托邦特性和悲观主义色彩。</p><p>从1984至今,科技迅速发展,新技术层出不穷,就在我们的世界随着现实时间的推进而更新的同时,赛博朋克下构建近未来世界的元素也大大增加。</p><p>尽管赛博朋克不是现实生活的完全映射,其狂想的架构更是塑造了许多个陌生的世界,以至于需要一定的接受度和反应时间。但赛博朋克作为一种基于时代环境的自我反思,揭示出了其中反映的数字时代的认知、认知局限与认知方式的转变,也持续地发人深省,供给科技伦理更多善意。</p><h4 id="赛博朋克的诞生"><a href="#赛博朋克的诞生" class="headerlink" title="赛博朋克的诞生"></a>赛博朋克的诞生</h4><p>二十世纪60年代,是一个社会大变革的年代。二战的滚滚硝烟与第三次科技革命的爆发,导致了这个黑暗压抑又有一丝光明前景的时代,未来近在眼前,历史还未走远。</p><p>一方面,曾经自由民主的国家无法抑制失业率上升或通货膨胀,国家干预也无法解决诸如种族主义或个人对意义和秩序的渴望等社会问题。超级大国利用游击队和傀儡政权作为他们争夺世界霸权的筹码。越来越多的经济学家和未来学家开始怀疑,冷战最终不过是日渐式微的西方世界的杂耍表演。</p><p>第三世界的主要国家正在崛起。日本比欧洲和美国更娴熟地玩着资本主义的游戏,中国和东南亚“七虎”在不受西方自由主义影响的情况下开始了自己的致富之路。而西方则无法与他们日益提高的生产效率和越来越多的劳动力相抗衡。</p><p>世界环境也在走向地狱,生物学家雷切尔·卡森早就在《寂静的春天》一书中对使用DDT和其他杀虫剂存在的危害发出了第一次警告,而这仅仅是个开始。事实证明,有毒废物造成的危害比任何人想象的都要多,公众的担忧似乎也无法阻止农药进入空气、土地和水中。</p><p>工厂和城市的有毒排放物不断地进入环境之中,持续的气候变化也迫在眉睫。1979年,世界气象协会(WMA)警告称,全球变冷已经持续了几十年,冰川期很可能即将来临。</p><p>另一方面,20世纪后期,控制论、信息论、计算机/网络、生物遗传工程等飞速地发展。尤其是80年代中期后,虚拟现实技术、人工智能技术,计算机图形学、仿真技术、多媒体技术、人工智能技术、计算机网络技术、并行处理技术和多传感技术的发展,人类生活水平前所未有地提高了。</p><p>现代性许诺了美好的前景和理想,诸如平等、自由和理性。人们在希望和绝望之间摇摆不定。终于,这种矛盾产生了科幻艺术创作的参考设定——赛博朋克。</p><p>事实上,赛博朋克所具备的元素在20世纪初的科幻小说中就可见端倪。在视觉文本出现以前,科幻小说是科幻领域的主要的表现形式。在整个十九世纪中,科幻创作经历了草创期以及从古典到现代的转型,工业革命引发了人类文明史上科技前所未有的大发展,这为作家们提供了用之不竭的创作激情。</p><p>进入二十世纪后,科幻领域开始出现变化,科幻电影、绘画、连环漫画、广播剧以及电视作品先后出现。梅里埃的《月球旅行记》成为了科幻电影的发端,也揭开了小说改编成电影的序幕。</p><p>1982年,世界上第一部赛博朋克电影《电子世界争霸战》在美国上映,《漫长的明天》将科幻小说和黑色电影相融合,《银翼杀手》则展现了一个雨后华丽的未来都市。</p><p>而真正开启了赛博朋克流派的发展则是1984年布鲁斯·贝斯克的《赛博朋克》和威廉·吉布森的《神经漫游者》问世。事实上,无论从哪方面来评价,《赛博朋克》和《神经漫游者》都是赛博朋克流派的权威之作。</p><p>《神经漫游者》的展望中,未来的两部分泾渭分明。一边是肮脏、充满犯罪的物质世界,一边是明亮的网络空间;一边是大街上为了生存抗争的人们,一边是绕地球环行的贵族努力找办法填补他们人为延长的寿命;一边是来自我们世界的老旧残迹——在故事早期,凯斯买了“一把50年前南美版瓦尔特PPK手枪的越南仿制品”——另一边则是能够让人们用新的肢体、眼睛和皮肤来强化身体的尖端科技,只要他们买得起。</p><p>于是,借助流行文化、科幻小说、戏剧和电影,这些基于既定事实又承载着超越想象力的故事,以《神经漫游者》为代表的赛博朋克作品从多个侧面描绘了一个关于未来的模糊信仰。</p><p>它既包含着对技术的依赖和恐惧、对未来浪漫而悲观的想象,又掺杂了身处技术爆炸时期的后人类对世界与自我的颠覆性认知。而这些杂陈的情绪以一种哲学化的方式被植入赛博空间的意象中,使它本身作为一个通信科学发展的产物,承载了更加值得深思的文化隐喻。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/KBIXfd.jpg"></p><h4 id="从浪潮到退潮"><a href="#从浪潮到退潮" class="headerlink" title="从浪潮到退潮"></a>从浪潮到退潮</h4><p>20世纪80年代明确了赛博朋克作为一种风格的界限,一并开启了赛博朋克流派作品的创作。</p><p>同时,在计算机领域突飞猛进的发展下,到了赛博朋克出现的八十年代,信息技术、生物工程、基因技术、网络、黑客等名词逐渐进入公众领域。人机联网,人工智能,虚拟空间等开始在现实生活中逐步实现。而在赛博朋克文学和电影诞生之初,赛博朋克就将这些先进技术与很多现实问题联系在一起。</p><p>其中,帕特·卡蒂甘的《合成人(1991)》构筑了一个由复杂的人机合作所掌控的世界,关注大脑改造技术的心理暗示;鲁迪·鲁克的Ware系列则延续了《神经漫游者》里有自我意识的人工智能这一思路,并得出了逻辑上的结论,即在此基础上产生的机械生命体是如何在其后代中进化的。</p><p>K.W.基特曾以《极度恐怖》而闻名,他推出的《玻璃锤》,则是一部结合了《硬线》风格的寓言故事——诺斯替主义邪教的超速者和走私者以及他们救赎世界的理念误入歧途的图景。</p><p>格雷格·贝尔则在《血音乐》一书中创造了一个复杂的未来,人类会被因基因改而拥有自我意识的细菌所破坏和改造。赛博朋克主题出现在他后来的一些作品中,尤其是以1990年的《天使女王》为开端的系列,书中的故事发生在洛杉矶,在那里纳米技术带来了根本性的变化。</p><p>布鲁斯·斯特林的作品,比如《网络岛》,对黑客这种亚文化开始特别关注。同时,斯特林是赛博朋克舞台上的一个标志,他编辑的《镜影:赛博朋克选集》是一本重要的短故事合集,包括吉布森、卡蒂甘和鲁克的作品。在这本书的前言中,斯特林写道:</p><p>“有些中心主题在赛博朋克中反复出现,比如身体入侵,包括假肢、植入电路、整容手术和基因突变。更重要的主题是心灵入侵:人脑-电脑交互,人工智能,神经化学——这都是从根本上重新定义了人性本质和自我本质的技术。”</p><p>于是,第一波浪潮中的赛博朋客作家继续他们的多元化发展,赛博朋克的思想和意象向四面八方扩散。赛博朋克的成功展示了一种思想在实现实体表达之前所具有的力量。正如乔治·奥威尔在《1984》中的构思已经成为了政治话语的一部分。因此,赛博朋克的存在也同时影响着现实世界中计算机和其他领域的发展。</p><p>然而,这并不意味着赛博朋克的发展就是一帆风顺的。事实上,在赛博朋克小说上发生的事情,同样发生在流行文化任何一个分支里的成功新事物上。布鲁斯·贝斯克说,“它从一个意料之外的、崭新的原创事物变成一股短暂的新潮,一个可重复的商业公式和一种老套的修辞。”</p><p>《神经漫游者》的主题变成了某种清单。疏离的独行者在镜影中做着毒品生意或飞快地入侵电脑,这样的故事很快成为标准内容。然而类似故事太多了,一些90年代最重要的赛博朋克故事,将这种公式推至具有讽刺意味的极端,使得赛博朋克终于在90年代走向了退潮。</p><h4 id="赛博朋克的重新出发"><a href="#赛博朋克的重新出发" class="headerlink" title="赛博朋克的重新出发"></a>赛博朋克的重新出发</h4><p>尽管看起来赛博朋克走向了消逝,但奇异的是,随着千禧年的结束,赛博朋克迎来了它最重要的时刻。它的影响力向外扩展,朝着许多不同方向突变,最终进入了主流文化。</p><p>究其根本,是因为赛博朋克本身的吸引力远不止于表面的皮革、铬合金和霓虹灯。风格显然很重要,但是赛博朋克更为重要的内核是:人们可以通过自我的表达充分说明所处的文化。</p><p>早期的赛博朋克作家们和他们的同龄人担心的很多事情都没有发生。冷战确实结束了,但不是通过核战争的形式。苏联解体了,即时它会因错位的怀旧情绪而复苏,但苏联式的共产主义对任何极端狂热分子来说都不再是未来的潮流。日本十年前陷入的经济困境依然深重,看不到真正复苏的希望。</p><p>上世纪70年代的许多大型企业要么倒闭,要么被其他企业吞并。冰川纪似乎不太可能在短时间内再次降临,人口这颗滴答作响的巨大炸弹正在缓慢而稳步地解除武装。</p><p>当然,新的恐惧总会取代旧的恐惧,全球变暖在许多人的脑海萦绕不去。曾经被认为已经解决的传染病问题又回来了,抗生素的滥用与自然进化相结合,制造出了越来越危险的微生物。</p><p>人们所担心的不再是苏联霸权,而是宗教狂热和恐怖主义。计算机化无时无刻不在给工作和娱乐的新领域带来革命,但也有代价,包括失业、数字鸿沟的扩大。精通技术的人和不具备使用高科技工具进行工作的能力的人之间的鸿沟,以及传统社区形成和维护方式的崩溃,网络互动无法(现在,也许永远)完全取代传统的社区。</p><p>社会构架偏向全球化,各个地域文化通过各种形式交融。人工智能发达,有强大的系统通过各种手段统治着所有人的生活。</p><p>在这样的背景下,赛博朋克再一次迸发出了新生的力量。当下大多数赛博朋克作品,都在二元对立下重新定义了“人”:机器人也可以为自己赋予人格,并成为新本体。《攻壳机动队》中,反抗政府过度化发展科技的群体被政府视为可弃之物,他们游走于城市边缘游行示威,最后却被政府抓走做义体人实验。生物组织通过无数次实验后,第一个真正意义上的义体人素子出现。素子竭力寻找自己的真实身份,自我觉醒让她重获新生。</p><p>《银翼杀手2049》中,复制人K的工作任务是追杀老式型号复制人。影片中,人类作为复制人的创造者,主宰复制人的生与死。</p><p>在游戏方面,《杀出重围》为CDPR创作《赛博朋克2077》奠定了基础。小岛秀夫在十年之前创作了《掠夺者》,也吸取了神经控制论和人工智能等元素,并将之运用在《合金装备》,获得了极大的成功。、</p><p>人类对世界的关注具有周期性。思想和风格会重新流行起来,故事也会不断重复。如果处理得当,旧的观念可以被打磨成新的、引人注目的东西,使人们对最原始的恐惧和希望产生强烈的共鸣。我们生活在赛博空间的临界点上,科学与人文问题和以往一样重要。</p><p>赛博朋克是我们这一代的流派。它是在计算机的体积和成本都非常巨大的时候被构想出来的,并预示了一个由微型处理器和超导体组成的世界。它赋予了黑色主题新的风格和复杂性,预示着对克隆和人类灭绝的恐惧,而这些正是今天社会关注的热点问题。</p><p>或许,这也是赛博朋克经久不衰的原因。赛博朋克作为一种具有思辨精神的基于美学的哲学,带有强烈的悲观主义色彩,却为浸淫在华丽的网络空间中逐渐模糊现实与虚幻的人类提供了一个自我审视的机会,以创造一个反乌托邦的未来世界的方式来警醒人们:任何一种进步都存在弊端,赛博朋克提出的问题都是人类在未来即将遇到且无法回避的。</p><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/xziLKI.jpg"></p></body></html>]]></content>
<summary type="html"><p><img data-src="https://img.yousazoe.top/uPic/img/blog/MV14/FQe8VN.jpg"></p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>在贪墨成风的反乌托邦世界中,四处都是生化改造植入体。动画剧聚焦一个在街头长大的鲁莽天才少年努力想成为边缘行者:拿钱办事的法外之徒。</p></summary>
<category term="影视杂谈 (Video Talk)" scheme="https://yousazoe.top/categories/%E5%BD%B1%E8%A7%86%E6%9D%82%E8%B0%88-Video-Talk/"/>
</entry>
</feed>