-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
538 lines (258 loc) · 341 KB
/
local-search.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
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>一些日常可能会用得上的技术(二)</title>
<link href="/%E4%B8%80%E4%BA%9B%E6%97%A5%E5%B8%B8%E5%8F%AF%E8%83%BD%E4%BC%9A%E7%94%A8%E5%BE%97%E4%B8%8A%E7%9A%84%E6%8A%80%E6%9C%AF%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<url>/%E4%B8%80%E4%BA%9B%E6%97%A5%E5%B8%B8%E5%8F%AF%E8%83%BD%E4%BC%9A%E7%94%A8%E5%BE%97%E4%B8%8A%E7%9A%84%E6%8A%80%E6%9C%AF%EF%BC%88%E4%BA%8C%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="一些日常可能会用得上的技术(二)"><a href="#一些日常可能会用得上的技术(二)" class="headerlink" title="一些日常可能会用得上的技术(二)"></a>一些日常可能会用得上的技术(二)</h2><h3 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h3><blockquote><p>虽然很多人能够熟练掌握一门编程语言,也能用这门编程语言方便的批处理数据,但是每次总要借助这门语言的 编译器/解释器,然后调用一些系统函数之类的,总感觉缺了点意思。</p><p>Linux/Windows 其实在 shell 中已经提供了一门完备的编程语言来直接处理文件或者数据,这相比与写一份代码来运行它要有趣很多。</p><p>你不必记下本文的所有细节,只需要知道有这些所介绍的功能即可,记忆会在你的使用过程中一步步完成,AI 能提供的信息会比本文更详尽。</p></blockquote><h3 id="一些常用命令"><a href="#一些常用命令" class="headerlink" title="一些常用命令"></a>一些常用命令</h3><h4 id="grep"><a href="#grep" class="headerlink" title="grep"></a>grep</h4><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css">grep <span class="hljs-selector-attr">[选项]</span> 模式 <span class="hljs-selector-attr">[文件...]</span><br></code></pre></td></tr></table></figure><ul><li>选项<ul><li><code>-i</code>:忽略大小写。</li><li><code>-r</code>:递归搜索子目录。</li></ul></li><li>模式<ul><li>默认是标准正则。</li><li>用 <code>-F</code> 参数禁止正则(所有正则符号被视为文本)。</li><li>用 <code>-E</code> 支持扩展正则表达式。</li></ul></li><li>文件<ul><li>用 <code>-</code> 表示标准输入。(第一期讲过 “一切皆文件”)</li><li>支持多个文件,每个文件用空格隔开。</li><li>文件名可以采用通配符语法(见附录,大部分文件名参数都可以支持通配符)。<ul><li>通配符语法匹配的并不是相对路径,而是对于每一层目录和文件名独立匹配,也就是说 <code>f1/*.txt</code> 不能匹配 <code>f1/</code> 子目录下的 .txt 文件。</li></ul></li></ul></li><li>Windows 平替<ul><li>可以去下载一个 <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> 加入系统路径后当平替,用法基本一致,命令改为 <code>rg</code>。</li></ul></li></ul><h4 id="echo"><a href="#echo" class="headerlink" title="echo"></a>echo</h4><ul><li><p>用于输出字符串。</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">echo</span><span class="hljs-meta"> [一个字符串]</span><br></code></pre></td></tr></table></figure></li><li><p>常用参数</p><ul><li><code>-e</code>:指定转义 <code>\</code> 相关的换行(<code>\n</code>),制表符(<code>\t</code>)等。</li><li><code>-E</code>:指定不转义。</li></ul></li><li><p>备注:</p><ul><li>不同操作系统的 <code>echo</code> 标准不一样,所以有 <code>-e-E</code> 这种看上去反人类的参数。</li><li>Windows 系统的 <code>PowerShell</code> 也有 <code>echo</code>,但是它的转义符是 “`”,系统默认发生转义操作。</li></ul></li></ul><h4 id="cat"><a href="#cat" class="headerlink" title="cat"></a>cat</h4><ul><li><p>题外话</p><ul><li><p>见下图。</p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202411252351829.jpg" style="zoom: 80%;" /></li><li><p>为什么学计算机的很多人都喜欢猫~~?</p></li></ul></li><li><p>查看文件内容</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">cat</span><span class="hljs-meta"> [文件...]</span><br></code></pre></td></tr></table></figure><ul><li>会输出结果到终端。</li></ul></li><li><p>其它用法</p><ul><li><p>写入文件:</p><figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs stata"><span class="hljs-keyword">cat</span> [文件...] >> <span class="hljs-keyword">file</span>.txt<br></code></pre></td></tr></table></figure><ul><li><code>></code>:覆盖写入。</li><li><code>>></code>:追加写入。</li></ul></li><li><p>文件部分可以接受命令行输入,<code>></code> 或者 <code>>></code> 前不填写任何参数就使用命令行输入。</p></li><li><p>使用 EOF</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cat</span> > file.txt << <span class="hljs-string">EOF</span><br></code></pre></td></tr></table></figure><ul><li><p>接下来的输入中,输入 EOF 并回车结束输入,EOF 不会被写入。</p></li><li><p>如果不使用 << EOF 也不使用管道</p></li></ul></li></ul></li></ul><h4 id="wc"><a href="#wc" class="headerlink" title="wc"></a>wc</h4><ul><li><p>统计文件字数</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">wc</span><span class="hljs-meta"> [文件...]</span><br></code></pre></td></tr></table></figure></li><li><p>常用参数</p><ul><li><code>-l</code>:统计行数。</li></ul></li><li><p>其它</p><ul><li><p>可以接受命令行输入。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">command</span> | <span class="hljs-built_in">wc</span> -m<br></code></pre></td></tr></table></figure></li></ul></li></ul><h4 id="head-tail"><a href="#head-tail" class="headerlink" title="head/tail"></a>head/tail</h4><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">head</span><span class="hljs-meta"> [文件名...]</span><br></code></pre></td></tr></table></figure><ul><li>用于显示文件的最开始/最后 10 行。</li><li>常用参数:<ul><li><code>-n</code>:紧接着可以指定行数。</li></ul></li><li>其它:<ul><li>支持命令行输入</li></ul></li></ul><h4 id="rm"><a href="#rm" class="headerlink" title="rm"></a>rm</h4><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">rm</span><span class="hljs-meta"> [文件名...]</span><br></code></pre></td></tr></table></figure><ul><li><p>用于删除文件。</p></li><li><p>常用参数:</p><ul><li><code>-r</code>:用于删除目录。</li><li><code>-f</code>:忽略警告直接强制删除。</li></ul></li><li><p>某个很厉害的命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">rm</span> -rf /<br></code></pre></td></tr></table></figure><p>功能是删除本操作系统下所有文件,<strong>请不要执行或者被人忽悠执行该命令!</strong></p><p>数据无价,谨慎操作。</p></li></ul><h4 id="zip"><a href="#zip" class="headerlink" title="zip"></a>zip</h4><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css">zip <span class="hljs-selector-attr">[选项]</span> <span class="hljs-selector-attr">[压缩文件名]</span> <span class="hljs-selector-attr">[被压缩文件/被压缩目录...]</span><br></code></pre></td></tr></table></figure><ul><li>用于创建压缩文件。</li><li>常用选项:<ul><li><strong>-r</strong> 或 <strong>–recursive</strong>:递归地包含目录,即包括指定目录及其所有子目录。</li><li><strong>-u</strong> 或 <strong>–update</strong>:更新现有的 zip 文件,添加新文件或更新已存在的文件。如果不用改参数覆盖时会提示。</li><li><strong>-x</strong> 或 <strong>–exclude</strong>:后面输入一些 <code>[被压缩文件/被压缩目录...]</code>,排除指定的文件或目录。</li><li><strong>-v</strong> 或 <strong>–verbose</strong>:详细模式,显示正在处理的文件名。</li><li><strong>-q</strong> 或 <strong>–quiet</strong>:安静模式,不显示任何输出。</li><li><strong>-0</strong> 到 <strong>-9</strong>:设置压缩级别,从无压缩(0)到最大压缩(9)</li></ul></li></ul><h4 id="uzip"><a href="#uzip" class="headerlink" title="uzip"></a>uzip</h4><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css">unzip <span class="hljs-selector-attr">[压缩文件名]</span> <span class="hljs-selector-attr">[参数]</span><br></code></pre></td></tr></table></figure><ul><li><p>用于解压文件,默认解压到当前目录。</p></li><li><p>常用参数:</p><ul><li><code>-d</code>:后面接一个目录,解压到这个目录,不存在会创建。</li><li><code>-l</code>:只查看里面的文件,不解压。</li></ul></li><li><p>其它用法</p><ul><li><p>只解压部分文件</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css">unzip <span class="hljs-selector-attr">[压缩文件名]</span> <span class="hljs-selector-attr">[要解压的文件在压缩文件中的路径...]</span> <span class="hljs-selector-attr">[参数]</span><br></code></pre></td></tr></table></figure></li></ul></li></ul><h3 id="管道"><a href="#管道" class="headerlink" title="管道"></a>管道</h3><ul><li><p>管道的一般用法是 <code>command1 | command2</code> ,功能是将 <code>command1</code> 的标准输出作为 <code>command2</code> 的标准输入运行。</p></li><li><p>举例:</p><ul><li><p>使用 <code>sort</code> 对文件内容排序,并用 <code>uniq</code> 去除重复行:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sort</span> somefile.txt | <span class="hljs-built_in">uniq</span><br></code></pre></td></tr></table></figure></li><li><p>使用 <code>ls</code> 列出目录内容,<code>grep</code> 过滤特定模式的文件,然后 <code>sort</code> 排序结果:</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs vim"><span class="hljs-keyword">ls</span> -<span class="hljs-keyword">l</span> | <span class="hljs-keyword">grep</span> <span class="hljs-string">"^-"</span> | <span class="hljs-keyword">sort</span><br></code></pre></td></tr></table></figure></li></ul></li></ul><h3 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h3><h4 id="声明"><a href="#声明" class="headerlink" title="声明"></a>声明</h4><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs abnf"><span class="hljs-attribute">key</span><span class="hljs-operator">=</span>value<br></code></pre></td></tr></table></figure><ul><li><code>=</code> 的左右不能用空格。</li><li><code>value</code> 加上双引号被声明为字符串,不加声明为整形变量或者浮点变量。</li><li>PowerShell 的声明使用 <code>$key=value</code>。</li></ul><h4 id="变量引用"><a href="#变量引用" class="headerlink" title="变量引用"></a>变量引用</h4><figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs crystal"><span class="hljs-variable">${</span>变量名}<br><span class="hljs-variable">$变</span>量名<br></code></pre></td></tr></table></figure><ul><li>第一种方式是为了防止歧义。</li><li>用 <code>\</code> 可以转义 <code>$</code>。</li></ul><h4 id="运算"><a href="#运算" class="headerlink" title="运算"></a>运算</h4><ul><li><p>$((expr)) 语法:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$</span><span class="language-bash">((算术表达式))</span><br></code></pre></td></tr></table></figure><ul><li><p>举例:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs routeros"><span class="hljs-attribute">a</span>=5<br><span class="hljs-attribute">b</span>=3<br><span class="hljs-attribute">c</span>=$((a + b))<br>echo <span class="hljs-variable">$c</span> # 输出 8<br></code></pre></td></tr></table></figure></li></ul></li><li><p>自增自减:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$</span><span class="language-bash">((a++))</span><br></code></pre></td></tr></table></figure></li><li><p>PowerShell 特性:</p><figure class="highlight gams"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs gams"><span class="hljs-meta"><span class="hljs-keyword">$a</span>++</span><br></code></pre></td></tr></table></figure><ul><li>可以直接这样使用,但是在 <code>echo</code> 等命令中这样做不行。</li></ul></li><li><p>Shell 特性:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">let</span> <span class="hljs-string">"new_var = var1 + var2"</span><br></code></pre></td></tr></table></figure><ul><li><code>new_var</code> 可以是一个新变量名。</li></ul></li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ul><li>大部分时候,常用命令的正则表达式、通配符等功能就可以完成大部分需要循环和分支的操作,用得上循环和分支的地方实际上不会很多。就算真的需要用到循环这些操作,这个时候使用 Python 等脚本语言可能会更方便了。</li><li>好好学习正则表达式。</li></ul><h3 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h3><h4 id="关于一些语法"><a href="#关于一些语法" class="headerlink" title="关于一些语法"></a>关于一些语法</h4><ul><li>可变参数列表:[文件…] 是一个可变参数列表,习惯上命令行参数用空格隔开,所以它实际上表示 <code>[文件1]</code> 或 <code>[文件1] [文件2]</code> 或 <code>[文件1] [文件2] [文件3]</code> 等等。</li></ul><h4 id="关于标准正则(BRE)"><a href="#关于标准正则(BRE)" class="headerlink" title="关于标准正则(BRE)"></a>关于标准正则(BRE)</h4><ul><li><code>.</code>:匹配任意单个字符(除换行符外)。</li><li><code>^</code>:匹配每一行的开头(不占位,第一个字符仍需要被模式匹配)。<ul><li>也用于脱字,放在 <code>[]</code> 内部的最开始表示匹配除了 <code>[]</code> 中表达字符之外的所有字符</li></ul></li><li><code>$</code>:匹配行的结尾。</li><li><code>[]</code>:匹配括号内的任意字符。</li><li><code>|</code>:逻辑或操作符。</li><li><code>*</code>:匹配前面的元素0次或多次。</li><li><code>+</code>:匹配前面的元素1次或多次。</li><li><code>-</code>:行为视上下文而定。<ul><li>在 <code>[]</code> 内它是一个特殊字符,它表示一个范围,例如<code>[a-z]</code> 可以匹配任何小写字母。</li><li>在 <code>[]</code> 外是一个普通字符。</li></ul></li><li><code>?</code>:匹配前面的元素0次或1次。</li><li><code>{n}</code>:精确匹配n次。</li><li><code>{n,}</code>:至少匹配n次。</li><li><code>{n,m}</code>:至少匹配n次,但不超过m次。</li><li><code>\</code>(反斜杠):转义特殊字符或表示特殊序列的开始。</li><li><code>()</code>(圆括号):将多个表达式组合成一个子表达式,用于分组和捕获。</li></ul><h4 id="关于拓展正则(ERE)"><a href="#关于拓展正则(ERE)" class="headerlink" title="关于拓展正则(ERE)"></a>关于拓展正则(ERE)</h4><ul><li><code>\b</code>:匹配一个单词字符和非单词字符之间的边界。</li><li><code>\d</code>:等价于<code>[0-9]</code>。</li><li><code>\D</code>:等价于<code>[^0-9]</code>。</li><li><code>\s</code>:等价于<code>[ \t\n\r\f\v]</code>。(空白字符)</li><li><code>\S</code>:等价于<code>[^ \t\n\r\f\v]</code>。(非空白字符)</li><li><code>\w</code>:等价于<code>[a-zA-Z0-9_]</code>。(单词字符)</li><li><code>\W</code>:等价于<code>[^a-zA-Z0-9_]</code>。(非单词字符)</li><li><code>\n</code>:匹配换行符。</li><li><code>\t</code>:匹配制表符。</li><li><code>?</code>:非贪婪匹配(lazy quantifier)。</li></ul><h4 id="关于通配符"><a href="#关于通配符" class="headerlink" title="关于通配符"></a>关于通配符</h4><p>通配符拥有部分正则的特性,支持不如正则完整,语法有微小差异,具体细节如下:</p><ol><li><code>*</code>:匹配任意数量的字符(包括零个字符)。<ul><li>例如:<code>*.txt</code> 匹配所有以 <code>.txt</code> 结尾的文件。</li></ul></li><li><code>?</code>:匹配任意单个字符。<ul><li>例如:<code>file?.txt</code> 匹配 <code>file1.txt</code>、<code>filea.txt</code> 等。</li></ul></li><li><code>[...]</code>:匹配括号内的任意单个字符。<ul><li>例如:<code>file[123].txt</code> 匹配 <code>file1.txt</code>、<code>file2.txt</code> 和 <code>file3.txt</code>。</li><li>也可以使用范围:<code>file[a-z].txt</code> 匹配 <code>filea.txt</code> 到 <code>filez.txt</code>。</li></ul></li><li><code>[!...]</code> 或 <code>[^...]</code>:匹配不在括号内的任意单个字符。<ul><li>例如:<code>file[!123].txt</code> 或 <code>file[^123].txt</code> 匹配除了 <code>file1.txt</code>、<code>file2.txt</code> 和 <code>file3.txt</code> 之外的所有以 <code>.txt</code> 结尾的文件。</li></ul></li><li><code>{...}</code>:匹配花括号内的任意选项(选项之间用逗号分隔)。<ul><li>例如:<code>file.{txt,pdf}</code> 匹配 <code>file.txt</code> 和 <code>file.pdf</code>。</li></ul></li><li><code>\</code>:用于转义自己和以上特殊符号。</li></ol>]]></content>
<categories>
<category>科普</category>
</categories>
<tags>
<tag>命令行</tag>
<tag>shell</tag>
</tags>
</entry>
<entry>
<title>一些日常用的上的计算机技术(一)</title>
<link href="/%E4%B8%80%E4%BA%9B%E6%97%A5%E5%B8%B8%E7%94%A8%E7%9A%84%E4%B8%8A%E7%9A%84%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%8A%80%E6%9C%AF%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<url>/%E4%B8%80%E4%BA%9B%E6%97%A5%E5%B8%B8%E7%94%A8%E7%9A%84%E4%B8%8A%E7%9A%84%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%8A%80%E6%9C%AF%EF%BC%88%E4%B8%80%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="一些日常用的上的计算机技术(一)"><a href="#一些日常用的上的计算机技术(一)" class="headerlink" title="一些日常用的上的计算机技术(一)"></a>一些日常用的上的计算机技术(一)</h2><h3 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h3><blockquote><p>在 AI 背景下,很多日常处理数据的工作完全可以交给 AI 来完成,学习它们的必要性似乎已经不那么强?</p><p>在大模型时代,更重要的是了解一些技术的存在,而非具体掌握这些技术,本文旨在提出某些可能对日常工作有帮助的技术,大致给出一个框架。</p><p>最后,我想说,一个全栈工程师,需要掌握的不是整个计算机系统中所有方向的技术,而是整个计算机系统的脉络。当一个需求被提出时,他能够沿着大脑中的脉络找到所需的枝叶技术,并运用自己熟练的信息检索能力来找到它们的技术细节,并最终将这些技术组合成解决方案。</p></blockquote><h3 id="可持久化"><a href="#可持久化" class="headerlink" title="可持久化"></a>可持久化</h3><ul><li><p>其实用 “可持久化” 来形容它并不准确,这也包括把 hardcode 部分分离以便让用户不必修改源代码运行程序的相关技术。</p><ul><li><p>举例来说比如爬虫要爬一个网站,你有可能直接把一些参数写进代码里,例如:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs python">URL = <span class="hljs-string">"www.google.com"</span><br>PROXY = {<br>http_proxy: <span class="hljs-string">"localhost:7890"</span>,<br> https_proxy: <span class="hljs-string">"localhost:7890"</span><br>}<br></code></pre></td></tr></table></figure></li><li><p>但是你某一台电脑的代理端口是 <code>localhost:7891</code>,所以一个方案是直接修改源代码,但是这太麻烦了,因为代码可能会比较长然后你需要翻。</p></li><li><p>所以一个方案是单独把一些可能会变的配置给提出来单独写一个配置文件,然后每次运行代码的时候你输入配置文件的路径,或者<strong>仅 hardcode 配置文件路径</strong>。这样都比一个个改参数更好。</p></li><li><p>配置文件可以用 .yaml 或者 .json 文件,我习惯用前者,但其实后者的适用性更广,大部分编程语言内置了处理 .json 文件的相关函数。</p></li></ul></li><li><p>yaml 使用:</p><ul><li><p>准备:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pip install PyYaml<br></code></pre></td></tr></table></figure></li><li><p>读取:你大可直接复制下面的代码。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">yaml2dict</span>(<span class="hljs-params">file_path, encoding=<span class="hljs-string">'utf-8'</span></span>):<br> <span class="hljs-keyword">if</span> check_suffix(file_path, <span class="hljs-string">'yaml'</span>):<br> <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(file_path, <span class="hljs-string">'r'</span>, encoding=encoding) <span class="hljs-keyword">as</span> f:<br> <span class="hljs-keyword">return</span> yaml.safe_load(f)<br> <span class="hljs-keyword">else</span>:<br> <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"file suffix name not support"</span>)<br></code></pre></td></tr></table></figure></li><li><p>写入:用 <code>yaml.safe_dump(obj, stream)</code></p><ul><li>obj:一个 Python 对象,一般只支持基本数据类型。</li><li>stream:一个可写流对象,可以用 <code>with</code> 操作打开。</li></ul></li></ul></li></ul><h3 id="批量发送电子邮件"><a href="#批量发送电子邮件" class="headerlink" title="批量发送电子邮件"></a>批量发送电子邮件</h3><ul><li><p>用 <code>email</code> 和 <code>smtplib</code> 来发送,这俩是标准库,不用安装。</p></li><li><p>大概可以看看下面的代码:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> smtplib<br><span class="hljs-keyword">from</span> email.mime.text <span class="hljs-keyword">import</span> MIMEText<br><span class="hljs-keyword">from</span> email.mime.multipart <span class="hljs-keyword">import</span> MIMEMultipart<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">send_mail</span>(<span class="hljs-params">receiver, html, subject</span>):<br> message = MIMEMultipart(<span class="hljs-string">"alternative"</span>) <span class="hljs-comment"># 交替 attach 模式</span><br> message[<span class="hljs-string">"Subject"</span>] = subject <span class="hljs-comment"># 邮件标题</span><br> message[<span class="hljs-string">"From"</span>] = SENDER <span class="hljs-comment"># 发送者邮箱</span><br> message[<span class="hljs-string">"To"</span>] = receiver <span class="hljs-comment"># 接收者邮箱</span><br><br>part = MIMEText(html, <span class="hljs-string">"html"</span>, <span class="hljs-string">"utf-8"</span>) <span class="hljs-comment"># 三个参数是: html 字符串, 格式名, 编码方式</span><br>message.attach(part) <span class="hljs-comment"># 将对应的 part 正式加入 message</span><br> <br> <span class="hljs-comment"># SMTP服务器和端口,这是 google 邮箱的网址和端口,服务器和端口取决于发送者的邮箱。</span><br> smtp_server = <span class="hljs-string">"smtp.gmail.com"</span><br> port = <span class="hljs-number">587</span><br><br> <span class="hljs-keyword">try</span>:<br> server = smtplib.SMTP(smtp_server, port) <span class="hljs-comment"># 建立 SMTP 连接</span><br> server.starttls() <span class="hljs-comment"># 启用TLS安全传输模式</span><br> server.login(SENDER, PWD) <span class="hljs-comment"># 两个参数分别为邮箱账号和密码</span><br> server.sendmail(SENDER, receiver, message.as_string()) <span class="hljs-comment"># 发送</span><br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"邮件发送成功"</span>)<br> <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"邮件发送失败"</span>, e)<br> <span class="hljs-keyword">finally</span>:<br> server.quit() <span class="hljs-comment"># 断开服务器连接</span><br></code></pre></td></tr></table></figure></li></ul><h3 id="Excel-Word-处理"><a href="#Excel-Word-处理" class="headerlink" title="Excel/Word 处理"></a>Excel/Word 处理</h3><h4 id="Excel-处理"><a href="#Excel-处理" class="headerlink" title="Excel 处理"></a>Excel 处理</h4><ul><li><p>我使用的是 pandas 库。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">pip install pandas<br>pip install openyxl<br></code></pre></td></tr></table></figure></li><li><p>这样读取:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd<br>xls = pd.ExcelFile(<span class="hljs-string">r"C:\Users\huany\Desktop\帆软杯\第六届帆软杯报名信息("</span> + group + <span class="hljs-string">f") - 含准入码.xlsx"</span>)<br>sheet_name = xls.sheet_names[<span class="hljs-number">0</span>]<br>df = pd.read_excel(xls, sheet_name=sheet_name)<br></code></pre></td></tr></table></figure></li><li><p>df 是转出来的 DataFrame 对象,有以下几个注意点:</p><ul><li>一个表格的第一行自动作为表头,每一列的内容作为索引指标。</li><li>用 <code>df['col_name']</code> 来获取一列,它是一个 <code>iterable</code> 对象。</li><li>用 <code>df.iterows()</code> 来迭代每一行,得到的行是一个类 <code>dict</code> 对象,用表头的数据作为指标。</li></ul></li></ul><h4 id="Word-处理"><a href="#Word-处理" class="headerlink" title="Word 处理"></a>Word 处理</h4><ul><li>word (.docx 文件)是基于 xml 的文档。</li><li>.docx 文件本质是一个 zip 文件,改一下后缀名就可以解压。</li><li>主要文字是放在 <code>/word/document.xml</code>,似乎每个文本可以检索到两次,把这两个都改了,然后压缩回去就可以实现改动 .docx 文件。</li><li>可以看看我新开的<a href="https://github.com/huan-yp/DocxGenerator">开源项目</a>。</li></ul><h3 id="selenium-爬虫"><a href="#selenium-爬虫" class="headerlink" title="selenium 爬虫"></a>selenium 爬虫</h3><p>cloudflare 等工具的广泛使用使得传统的 requests 爬虫适用性逐步降低,如果没有太高的性能要求,不妨使用 selenium 这样的更上层的库来实现爬虫功能。</p><h4 id="关于消除自动化特征"><a href="#关于消除自动化特征" class="headerlink" title="关于消除自动化特征"></a>关于消除自动化特征</h4><ul><li><p>使用 <code>undetected_chromedriver</code> 库可以消除大部分的自动化特征。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmake">pip <span class="hljs-keyword">install</span> undetected-chromedriver<br></code></pre></td></tr></table></figure></li><li><p>使用方式和传统 selenium 几乎相同,有两点注意。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python">options = uc.ChromeOptions()<br>driver = uc.Chrome(use_subprocess=<span class="hljs-literal">True</span>, options=options)<br></code></pre></td></tr></table></figure><ul><li><code>uc.ChromeOptions()</code> 是该库准备的消除自动化特征的 options 参数</li><li><code>use_subprocess</code> 的功能是让 <code>WebDriver</code> 在独立的进程中运行,不受主进程崩溃的影响。(其实可以不管)</li></ul></li></ul><h4 id="关于配置自动下载"><a href="#关于配置自动下载" class="headerlink" title="关于配置自动下载"></a>关于配置自动下载</h4><ul><li><p>用 <code>perfs</code> 可以配置自动下载相关的功能</p><figure class="highlight sas"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sas"><span class="hljs-keyword">options</span>.add_experimental_optio<span class="hljs-meta">n</span>(<span class="hljs-string">"prefs"</span>, CONFIG.PREFS)<br></code></pre></td></tr></table></figure><ul><li><p>参数值是一个 dict,常用的参数有:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">download.prompt_for_download:</span> <span class="hljs-literal">false</span> <span class="hljs-comment"># 不要弹出提示框选择下载路径</span><br></code></pre></td></tr></table></figure></li><li><p>似乎配置默认下载路径的办法无法生效。</p></li></ul></li></ul><h4 id="lazy-load"><a href="#lazy-load" class="headerlink" title="lazy load"></a>lazy load</h4><ul><li><p>有时候不太想等待所有资源加载完毕,可以使用 <code>page_load_strategy</code> 来控制</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">options.page_load_strategy = <span class="hljs-string">'eager'</span><br></code></pre></td></tr></table></figure><ul><li>“eager” 不会加载图片等资源,初步解析完成后便会返回。</li><li><code>page_load_strategy</code> 是 <code>ChromeOptions</code> 类定义的一个属性。</li></ul></li></ul><h4 id="处理网页"><a href="#处理网页" class="headerlink" title="处理网页"></a>处理网页</h4><ul><li><p>用 BeautifulSoup 库可以来层次化读取和处理整个 html 页面。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> BeautifulSoup<br>soup = BeautifulSoup(driver.page_source, <span class="hljs-string">"html.parser"</span>)<br>all_artist = soup.find(<span class="hljs-string">"div"</span>, class_=<span class="hljs-string">'home-rows-videos-wrapper'</span>)<br></code></pre></td></tr></table></figure><ul><li><code>BeautifulSoup.find()</code> 可以通过标签名和 <code>class</code> 属性来查找,返回值同样是一个结构化的 HTML 对象,仍然有 <code>find</code> 等方法。</li></ul></li></ul><h4 id="一些关于-HTTP-请求的小知识"><a href="#一些关于-HTTP-请求的小知识" class="headerlink" title="一些关于 HTTP 请求的小知识"></a>一些关于 HTTP 请求的小知识</h4><ul><li><p>看 http 请求的时候,有时候 payload 是没有数据的,数据可以通过 headers 传递。</p><p>请求一个超大二进制文件时可能的 headers:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python">headers = {<br> <span class="hljs-string">'Range'</span>: <span class="hljs-string">f'bytes=<span class="hljs-subst">{<span class="hljs-number">0</span>}</span>-'</span><br>}<br></code></pre></td></tr></table></figure></li></ul><h3 id="压缩文件的处理"><a href="#压缩文件的处理" class="headerlink" title="压缩文件的处理"></a>压缩文件的处理</h3><p>Python 一般选用 zipfile 来出来压缩文件。</p><p>zipfile 的打开方式和文件流对象差不多,都需要用 <code>with</code> 指定打开模式,文件名,编码等信息。</p><h4 id="解压"><a href="#解压" class="headerlink" title="解压"></a>解压</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">with</span> zipfile.ZipFile(<span class="hljs-string">'example.zip'</span>, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> zip_ref:<br> zip_ref.extractall(<span class="hljs-string">'extracted/'</span>)<br> zip_ref.extract(<span class="hljs-string">"folder/subfolder/file.txt"</span>, <span class="hljs-string">'extracted'</span>)<br></code></pre></td></tr></table></figure><ul><li>若不存在,<code>extracted</code> 目录会被自动创建。</li><li><code>folder/subfolder/file.txt</code> 是以 .zip 文件中,被压缩文件的目录,解压后,<code>extracted</code> 目录会视为其根目录,然后解压过程保持目录结构不变,中间不存在的目录自动创建,也就是说解压出来 <code>folder/subfolder/file.txt</code> 会位于 <code>extracted/folder/subfolder/file.txt</code>。</li><li><code>extractall</code> 即对每一个压缩文件中的文件做 <code>extract</code> 操作。</li></ul><h4 id="压缩"><a href="#压缩" class="headerlink" title="压缩"></a>压缩</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">with</span> zipfile.ZipFile(<span class="hljs-string">'new_example.zip'</span>, <span class="hljs-string">'w'</span>) <span class="hljs-keyword">as</span> zip_ref:<br> zip_ref.write(<span class="hljs-string">'file1.txt'</span>)<br> zip_ref.write(<span class="hljs-string">'file2.txt'</span>, <span class="hljs-string">"folder1/folder2/file3.txt"</span>)<br></code></pre></td></tr></table></figure><ul><li><p>注意到 ZipFile.write() 有两个参数,这个函数用于把一个文件添加到压缩文件对象中,第一个参数是被添加的文件的路径,第二个参数是压缩文件中,被压缩文件的路径。</p></li><li><p>第二个参数如果留空则默认为第一个参数。</p></li><li><p>例如,这个 <code>new_example.zip</code> 压缩文件的目录结构如下:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs python">new_example.<span class="hljs-built_in">zip</span>:<br>├── file1.txt<br>└── folder1/<br> └── folder2/<br> └── file3.txt<br></code></pre></td></tr></table></figure></li><li><p>它不支持直接压缩一个文件夹,所以如果要压缩文件夹需要用 <code>os.path.walk()</code> 方法来遍历文件并添加。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">with</span> zipfile.ZipFile(zip_file_path, <span class="hljs-string">'w'</span>) <span class="hljs-keyword">as</span> zipf:<br> <span class="hljs-keyword">for</span> root, dirs, files <span class="hljs-keyword">in</span> os.walk(folder_path):<br> <span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> files:<br> <span class="hljs-comment"># 创建文件的绝对路径</span><br> file_path = os.path.join(root, file)<br><br> <span class="hljs-comment"># 创建文件在ZIP中的相对路径</span><br> <span class="hljs-comment"># 这里使用 os.path.relpath() 来获取相对于 folder_path 的相对路径</span><br> <span class="hljs-comment"># 这样ZIP文件中的目录结构会与原始文件夹结构相同</span><br> arcname = os.path.relpath(file_path, start=folder_path)<br> zipf.write(file_path, arcname)<br></code></pre></td></tr></table></figure><ul><li><code>root</code> 是正在遍历的目录路径,<code>dirs</code> 是该目录下所有子目录的名字列表(相对 <code>root</code> 的路径),<code>filenames</code> 是该目录下所有文件的文件名(相对 <code>root</code> 的路径)。</li></ul></li></ul>]]></content>
<categories>
<category>科普</category>
</categories>
<tags>
<tag>python</tag>
<tag>爬虫</tag>
</tags>
</entry>
<entry>
<title>1123活动</title>
<link href="/1123%E6%B4%BB%E5%8A%A8/"/>
<url>/1123%E6%B4%BB%E5%8A%A8/</url>
<content type="html"><![CDATA[<h1 id="11-23活动"><a href="#11-23活动" class="headerlink" title="11.23活动"></a>11.23活动</h1><h2 id="Git-工具的使用"><a href="#Git-工具的使用" class="headerlink" title="Git 工具的使用"></a>Git 工具的使用</h2><p>【Git工作流和核心原理 | GitHub基本操作 | VS Code里使用Git和关联GitHub】<a href="https://www.bilibili.com/video/BV1r3411F7kn?vd_source=6ad092c4c097102be92aa2d50a2c519e">https://www.bilibili.com/video/BV1r3411F7kn?vd_source=6ad092c4c097102be92aa2d50a2c519e</a></p><h3 id="为什么需要用-Git?"><a href="#为什么需要用-Git?" class="headerlink" title="为什么需要用 Git?"></a>为什么需要用 Git?</h3><ul><li><p><strong>版本控制</strong>:</p><ul><li><p>Git 是一种分布式版本控制工具,可以记录代码的历史修改,方便回溯到任意历史版本。</p></li><li><p>支持分支与合并,便于多人协作和功能开发。</p></li></ul></li><li><p><strong>协作开发</strong>:</p><ul><li><p>通过 Git 和远程仓库(如 GitHub、GitLab),团队成员可以共享代码。</p></li><li><p>提供强大的冲突解决机制,避免覆盖他人代码。</p></li></ul></li><li><p><strong>代码备份</strong>:</p><ul><li>通过远程仓库,代码有了可靠的备份,减少因硬盘损坏或意外删除造成的损失。</li></ul></li></ul><h3 id="怎么用?"><a href="#怎么用?" class="headerlink" title="怎么用?"></a>怎么用?</h3><ol><li><strong>基础命令</strong>:<ul><li><strong>初始化项目</strong>:<code>git init</code></li><li><strong>克隆远程仓库</strong>:<code>git clone <仓库地址></code></li><li><strong>添加修改到暂存区</strong>:<code>git add <文件></code> 或 <code>git add .</code></li><li><strong>提交更改</strong>:<code>git commit -m "提交信息"</code></li><li><strong>查看历史记录</strong>:<code>git log</code></li><li><strong>创建分支</strong>:<code>git branch <分支名></code></li><li><strong>切换分支</strong>:<code>git checkout <分支名></code></li><li><strong>合并分支</strong>:<code>git merge <分支名></code></li></ul></li><li><strong>工作流</strong>:<ul><li><strong>Feature 分支开发</strong>:<ol><li>从主分支创建功能分支:<code>git checkout -b feature-xxx</code></li><li>在分支上开发功能并提交更改。</li><li>合并分支到主分支:<code>git checkout main</code>,然后 <code>git merge feature-xxx</code>。</li></ol></li><li><strong>多人协作</strong>:<ol><li>每个开发者拉取最新代码:<code>git pull origin main</code></li><li>提交自己的改动到远程:<code>git push origin feature-xxx</code></li><li>使用 Pull Request 或 Merge Request 进行代码审查。</li></ol></li></ul></li></ol><h3 id="怎么和远程仓库协作?"><a href="#怎么和远程仓库协作?" class="headerlink" title="怎么和远程仓库协作?"></a>怎么和远程仓库协作?</h3><ol><li><strong>SSH 配置</strong>:<ul><li>生成 SSH 密钥:<code>ssh-keygen -t rsa -C "你的邮箱"</code></li><li>添加公钥到远程仓库账户设置中。</li><li>验证连接:<code>ssh -T [email protected]</code>。</li></ul></li><li><strong>Git 设置代理</strong>:<ul><li>设置 HTTP 代理:<code>git config --global http.proxy http://<代理地址>:<端口></code></li><li>设置 Socks 代理:<code>git config --global http.proxy socks5://<代理地址>:<端口></code></li></ul></li><li><strong>同步操作</strong>:<ul><li>拉取代码:<code>git pull</code></li><li>推送代码:<code>git push</code></li><li>查看远程仓库:<code>git remote -v</code></li></ul></li></ol><h3 id="开源社区的参与方式"><a href="#开源社区的参与方式" class="headerlink" title="开源社区的参与方式"></a>开源社区的参与方式</h3><ul><li><p>一个可以上手的Demo</p><p> <a href="https://github.com/huan-yp/DocxGenerator">https://github.com/huan-yp/DocxGenerator</a></p></li></ul><ol><li><strong>寻找感兴趣的项目</strong>:<ul><li>在 GitHub 或 GitLab 上搜索感兴趣的开源项目。</li><li>查看 <code>README.md</code> 和 <code>CONTRIBUTING.md</code> 了解项目的目标和贡献方式。</li></ul></li><li><strong>贡献代码</strong>:<ul><li><strong>Fork 仓库</strong>:创建项目的个人副本。</li><li><strong>修改代码</strong>:在本地完成修改并提交。</li><li><strong>提交 PR</strong>:将修改提交到原始仓库,等待项目维护者审核。</li></ul></li><li><strong>其他贡献形式</strong>:<ul><li>提交 issue 反馈问题。</li><li>编写或优化文档。</li><li>提供测试案例。</li></ul></li></ol><hr><h2 id="留一个作业"><a href="#留一个作业" class="headerlink" title="留一个作业"></a>留一个作业</h2><ul><li>创建 GitHub 账号,在群里/私信你的id/个人主页地址(我需要拉你进俱乐部仓库)</li><li>为<a href="https://github.com/IppClub/legend-of-ipp%E8%BF%99%E4%B8%AA%E5%BA%93%E4%B8%AD%E7%9A%84">https://github.com/IppClub/legend-of-ipp这个库中的</a> <code>/practice/practice24</code> 路径下贡献一个任意的文件(体积不要太大)<ul><li>fork</li><li>new branch</li><li>pull request</li></ul></li></ul><h2 id="C-速通"><a href="#C-速通" class="headerlink" title="C++速通"></a>C++速通</h2><p><strong>略</strong></p><h2 id="各种语言的区别"><a href="#各种语言的区别" class="headerlink" title="各种语言的区别"></a>各种语言的区别</h2><table><thead><tr><th><strong>特性</strong></th><th><strong>C</strong></th><th><strong>C++</strong></th><th><strong>Java</strong></th><th><strong>Python</strong></th><th><strong>JavaScript</strong></th><th><strong>Go</strong></th><th><strong>Rust</strong></th><th><strong>Ruby</strong></th><th><strong>PHP</strong></th><th><strong>Swift</strong></th><th><strong>Kotlin</strong></th><th><strong>R</strong></th><th><strong>MATLAB</strong></th></tr></thead><tbody><tr><td><strong>语言类型</strong></td><td>编译型</td><td>编译型</td><td>编译型 + 虚拟机</td><td>解释型</td><td>解释型</td><td>编译型</td><td>编译型</td><td>解释型</td><td>解释型</td><td>编译型 + 解释型</td><td>编译型 + 解释型</td><td>解释型</td><td>解释型</td></tr><tr><td><strong>用途</strong></td><td>系统编程</td><td>系统编程、高性能开发</td><td>企业级开发</td><td>脚本语言、AI、数据分析</td><td>Web 前端开发</td><td>后端服务开发</td><td>系统编程、安全开发</td><td>脚本语言、Web 开发</td><td>Web 后端开发</td><td>iOS 开发</td><td>Android、多平台开发</td><td>数据分析、统计</td><td>科学计算、工程建模</td></tr><tr><td><strong>运行性能</strong></td><td>高</td><td>高</td><td>较高</td><td>较低</td><td>较低</td><td>高</td><td>高</td><td>较低</td><td>较低</td><td>较高</td><td>较高</td><td>较低</td><td>较低</td></tr><tr><td><strong>学习曲线</strong></td><td>陡峭</td><td>陡峭</td><td>平缓</td><td>平缓</td><td>平缓</td><td>平缓</td><td>陡峭</td><td>平缓</td><td>平缓</td><td>中等</td><td>中等</td><td>平缓</td><td>中等</td></tr><tr><td><strong>静/动态类型</strong></td><td>静态</td><td>静态</td><td>静态</td><td>动态</td><td>动态</td><td>静态</td><td>静态</td><td>动态</td><td>动态</td><td>静态</td><td>静态</td><td>动态</td><td>动态</td></tr><tr><td><strong>垃圾回收机制</strong></td><td>无</td><td>有(可选智能指针)</td><td>有</td><td>有</td><td>有</td><td>有</td><td>无</td><td>有</td><td>有</td><td>有</td><td>有</td><td>有</td><td>有</td></tr><tr><td><strong>跨平台性</strong></td><td>高(依赖编译器)</td><td>高(依赖编译器)</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>中等</td></tr><tr><td><strong>并发支持</strong></td><td>低(通过库支持)</td><td>高</td><td>中</td><td>中</td><td>中</td><td>高</td><td>高</td><td>中</td><td>低</td><td>中</td><td>中</td><td>低</td><td>低</td></tr><tr><td><strong>典型应用场景</strong></td><td>嵌入式、操作系统开发</td><td>游戏开发、图形渲染</td><td>Web 服务、企业级开发</td><td>AI、自动化脚本、科学计算</td><td>Web 前端交互</td><td>微服务、云计算</td><td>高性能计算、安全开发</td><td>Web 开发、自动化脚本</td><td>Web 后端服务</td><td>iOS 应用开发</td><td>Android 应用开发</td><td>数据统计、可视化</td><td>工程仿真与建模</td></tr></tbody></table><hr><h3 id="扩展语言补充"><a href="#扩展语言补充" class="headerlink" title="扩展语言补充"></a><strong>扩展语言补充</strong></h3><table><thead><tr><th><strong>特性</strong></th><th><strong>HTML</strong></th><th><strong>CSS</strong></th><th><strong>SQL</strong></th><th><strong>Perl</strong></th><th><strong>Lua</strong></th><th><strong>Shell</strong></th><th><strong>Haskell</strong></th><th><strong>Scala</strong></th><th><strong>Dart</strong></th><th><strong>TypeScript</strong></th><th><strong>C#</strong></th><th><strong>Assembly</strong></th><th><strong>Elixir</strong></th></tr></thead><tbody><tr><td><strong>语言类型</strong></td><td>标记语言</td><td>样式语言</td><td>描述语言</td><td>脚本语言</td><td>脚本语言</td><td>脚本语言</td><td>函数式编程</td><td>编译型 + JVM</td><td>编译型 + 解释型</td><td>编译型 + 解释型</td><td>编译型 + 解释型</td><td>汇编语言</td><td>编译型 + BEAM VM</td></tr><tr><td><strong>用途</strong></td><td>Web 页面结构</td><td>Web 页面样式</td><td>数据库查询</td><td>数据处理、文本操作</td><td>游戏嵌入脚本</td><td>自动化脚本</td><td>高阶数学与函数式开发</td><td>大数据、分布式计算</td><td>Web 前端、移动开发</td><td>强类型 Web 开发</td><td>企业级开发</td><td>系统级编程</td><td>并发应用开发</td></tr><tr><td><strong>运行性能</strong></td><td>N/A</td><td>N/A</td><td>高</td><td>较低</td><td>较低</td><td>较低</td><td>较高</td><td>较高</td><td>较高</td><td>中</td><td>较高</td><td>极高(接近机器码)</td><td>高</td></tr><tr><td><strong>学习曲线</strong></td><td>平缓</td><td>平缓</td><td>平缓</td><td>中等</td><td>中等</td><td>平缓</td><td>陡峭</td><td>中等</td><td>平缓</td><td>中等</td><td>中等</td><td>陡峭</td><td>中等</td></tr><tr><td><strong>静/动态类型</strong></td><td>无</td><td>无</td><td>无</td><td>动态</td><td>动态</td><td>动态</td><td>静态</td><td>静态</td><td>静态</td><td>静态</td><td>静态</td><td>N/A</td><td>静态</td></tr><tr><td><strong>垃圾回收机制</strong></td><td>无</td><td>无</td><td>无</td><td>有</td><td>有</td><td>无</td><td>有</td><td>有</td><td>有</td><td>有</td><td>有</td><td>无</td><td>有</td></tr><tr><td><strong>跨平台性</strong></td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>高</td><td>低</td><td>高</td></tr><tr><td><strong>并发支持</strong></td><td>N/A</td><td>N/A</td><td>无</td><td>中</td><td>中</td><td>无</td><td>高</td><td>高</td><td>高</td><td>中</td><td>中</td><td>低</td><td>高</td></tr><tr><td><strong>典型应用场景</strong></td><td>Web 页面结构定义</td><td>Web 页面样式设计</td><td>数据库操作</td><td>文本处理、快速脚本</td><td>游戏逻辑嵌入</td><td>系统运维、脚本编写</td><td>数学模型、高阶函数式</td><td>数据流处理、Web 开发</td><td>Web、Flutter 开发</td><td>Web 全栈开发</td><td>游戏、桌面开发</td><td>内核开发</td><td>分布式与高并发服务</td></tr></tbody></table><hr><h3 id="一些举例"><a href="#一些举例" class="headerlink" title="一些举例"></a>一些举例</h3><h3 id="1-C-C-静态类型语言"><a href="#1-C-C-静态类型语言" class="headerlink" title="1. C/C++: 静态类型语言"></a><strong>1. C/C++</strong>: 静态类型语言</h3><p>C 和 C++ 是静态类型语言,变量的类型在编译时确定,且需要显式声明。</p><h3 id="特点:"><a href="#特点:" class="headerlink" title="特点:"></a>特点:</h3><ul><li><strong>强类型检查</strong>:类型必须严格匹配,否则会导致编译错误。</li><li><strong>高性能</strong>:由于类型在编译时固定,运行时没有额外的类型检查开销。</li></ul><h3 id="示例代码:"><a href="#示例代码:" class="headerlink" title="示例代码:"></a>示例代码:</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">int</span> a = <span class="hljs-number">10</span>; <span class="hljs-comment">// 整型变量</span><br> <span class="hljs-type">double</span> b = <span class="hljs-number">3.14</span>; <span class="hljs-comment">// 浮点型变量</span><br> <span class="hljs-type">char</span> c = <span class="hljs-string">'A'</span>; <span class="hljs-comment">// 字符型变量</span><br><br> <span class="hljs-comment">// 类型错误示例</span><br> <span class="hljs-comment">// int d = "Hello"; // 编译错误:无法将字符串赋值给整型变量</span><br><br> cout << <span class="hljs-string">"a: "</span> << a << <span class="hljs-string">", b: "</span> << b << <span class="hljs-string">", c: "</span> << c << endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><hr><h3 id="2-Java-Python-强类型语言,但类型系统不同"><a href="#2-Java-Python-强类型语言,但类型系统不同" class="headerlink" title="2. Java/Python: 强类型语言,但类型系统不同"></a><strong>2. Java/Python</strong>: 强类型语言,但类型系统不同</h3><ul><li><strong>Java</strong>: 静态强类型语言,变量类型在编译时确定。</li><li><strong>Python</strong>: 动态强类型语言,变量类型在运行时确定,但不同类型间的操作依然有严格的限制。</li></ul><h3 id="Java-示例代码:"><a href="#Java-示例代码:" class="headerlink" title="Java 示例代码:"></a>Java 示例代码:</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> {<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {<br> <span class="hljs-type">int</span> <span class="hljs-variable">a</span> <span class="hljs-operator">=</span> <span class="hljs-number">42</span>; <span class="hljs-comment">// 整型变量</span><br> <span class="hljs-type">String</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> <span class="hljs-string">"Hello"</span>; <span class="hljs-comment">// 字符串变量</span><br><br> <span class="hljs-comment">// 类型错误示例</span><br> <span class="hljs-comment">// int c = b; // 编译错误:不能将字符串赋值给整型变量</span><br><br> System.out.println(<span class="hljs-string">"a: "</span> + a + <span class="hljs-string">", b: "</span> + b);<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="Python-示例代码:"><a href="#Python-示例代码:" class="headerlink" title="Python 示例代码:"></a>Python 示例代码:</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs python">a = <span class="hljs-number">42</span> <span class="hljs-comment"># 整型变量</span><br>b = <span class="hljs-string">"Hello"</span> <span class="hljs-comment"># 字符串变量</span><br><br><span class="hljs-comment"># 类型错误示例</span><br><span class="hljs-comment"># c = a + b # 运行时错误:不能将整型和字符串相加</span><br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f"a: <span class="hljs-subst">{a}</span>, b: <span class="hljs-subst">{b}</span>"</span>)<br></code></pre></td></tr></table></figure><h3 id="区别:"><a href="#区别:" class="headerlink" title="区别:"></a>区别:</h3><ul><li><strong>Java</strong> 需要在代码中明确指定类型,类型错误在编译阶段发现。</li><li><strong>Python</strong> 的类型是动态的,可以在运行时绑定不同类型的值,但不允许不合法的操作(如整型和字符串相加)。</li></ul><hr><h3 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h3><ul><li><strong>Java 的编译机制</strong></li></ul><ol><li><strong>源代码 (.java)</strong> 会被编译器(<code>javac</code>)编译成字节码(<code>.class</code> 文件),这种字节码是与平台无关的。</li><li>字节码通过 <strong>Java 虚拟机(JVM)</strong> 在运行时解释执行,或者由 JVM 的 <strong>即时编译器(JIT Compiler)</strong> 转换为机器码运行。</li><li>因此,Java 是一种 <strong>编译型 + 解释型</strong> 的混合语言。</li></ol><h3 id="Java-编译与运行流程:"><a href="#Java-编译与运行流程:" class="headerlink" title="Java 编译与运行流程:"></a>Java 编译与运行流程:</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 示例代码 (HelloWorld.java)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">HelloWorld</span> {<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {<br> System.out.println(<span class="hljs-string">"Hello, Java!"</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><p>编译过程:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">javac HelloWorld.java <span class="hljs-comment"># 编译为字节码 (HelloWorld.class)</span><br>java HelloWorld <span class="hljs-comment"># 由 JVM 运行字节码</span><br></code></pre></td></tr></table></figure><hr><h3 id="Python-的编译机制"><a href="#Python-的编译机制" class="headerlink" title="Python 的编译机制"></a><strong>Python 的编译机制</strong></h3><ul><li><strong>Python 不需要显式编译</strong>,但实际上也会经历一个编译步骤。</li></ul><ol><li><strong>Python 源代码 (.py)</strong> 会在运行时由 <strong>解释器</strong> 编译为字节码(<code>.pyc</code> 文件),这种字节码是解释器能直接执行的中间代码。</li><li>Python 的字节码由 <strong>Python 虚拟机(PVM)</strong> 执行,完成实际的程序运行。</li><li>这种编译过程是透明的,用户不需要手动执行,因此 Python 通常被称为 <strong>解释型语言</strong>。</li></ol><h3 id="Python-运行示例:"><a href="#Python-运行示例:" class="headerlink" title="Python 运行示例:"></a>Python 运行示例:</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 示例代码 (hello.py)</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">"Hello, Python!"</span>)<br></code></pre></td></tr></table></figure><p>运行过程:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">python hello.py <span class="hljs-comment"># 自动编译为字节码并执行</span><br></code></pre></td></tr></table></figure><hr><h3 id="3-JSON-数据交换格式,弱类型"><a href="#3-JSON-数据交换格式,弱类型" class="headerlink" title="3. JSON: 数据交换格式,弱类型"></a><strong>3. JSON</strong>: 数据交换格式,弱类型</h3><p>JSON 是一种轻量级的数据交换格式,用于存储和传输键值对数据。它不支持复杂的类型系统,通常被视为弱类型语言。</p><h3 id="特点:-1"><a href="#特点:-1" class="headerlink" title="特点:"></a>特点:</h3><ul><li>支持基本类型:数字、字符串、布尔值、数组、对象和 <code>null</code>。</li><li>没有严格的类型约束,常用于数据序列化和传输。</li></ul><h3 id="示例-JSON-数据:"><a href="#示例-JSON-数据:" class="headerlink" title="示例 JSON 数据:"></a>示例 JSON 数据:</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Alice"</span><span class="hljs-punctuation">,</span> <span class="hljs-comment">// 字符串</span><br> <span class="hljs-attr">"age"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">25</span><span class="hljs-punctuation">,</span> <span class="hljs-comment">// 数字</span><br> <span class="hljs-attr">"is_student"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><span class="hljs-punctuation">,</span> <span class="hljs-comment">// 布尔值</span><br> <span class="hljs-attr">"grades"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">85</span><span class="hljs-punctuation">,</span> <span class="hljs-number">90</span><span class="hljs-punctuation">,</span> <span class="hljs-number">92</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span> <span class="hljs-comment">// 数组</span><br> <span class="hljs-attr">"address"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-comment">// 嵌套对象</span><br> <span class="hljs-attr">"city"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"New York"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"zip"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"10001"</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><h3 id="使用-JSON-的代码(Python):"><a href="#使用-JSON-的代码(Python):" class="headerlink" title="使用 JSON 的代码(Python):"></a>使用 JSON 的代码(Python):</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> json<br><br><span class="hljs-comment"># JSON 字符串</span><br>data = <span class="hljs-string">'''</span><br><span class="hljs-string">{</span><br><span class="hljs-string"> "name": "Alice",</span><br><span class="hljs-string"> "age": 25,</span><br><span class="hljs-string"> "is_student": false,</span><br><span class="hljs-string"> "grades": [85, 90, 92],</span><br><span class="hljs-string"> "address": {</span><br><span class="hljs-string"> "city": "New York",</span><br><span class="hljs-string"> "zip": "10001"</span><br><span class="hljs-string"> }</span><br><span class="hljs-string">}</span><br><span class="hljs-string">'''</span><br><br><span class="hljs-comment"># 解析 JSON 数据</span><br>parsed_data = json.loads(data)<br><br><span class="hljs-built_in">print</span>(parsed_data[<span class="hljs-string">"name"</span>]) <span class="hljs-comment"># 输出: Alice</span><br><span class="hljs-built_in">print</span>(parsed_data[<span class="hljs-string">"grades"</span>][<span class="hljs-number">0</span>]) <span class="hljs-comment"># 输出: 85</span><br></code></pre></td></tr></table></figure><hr><h3 id="4-HTML-标记语言,无类型系统"><a href="#4-HTML-标记语言,无类型系统" class="headerlink" title="4. HTML: 标记语言,无类型系统"></a><strong>4. HTML</strong>: 标记语言,无类型系统</h3><p>HTML 是一种标记语言,用于定义 Web 页面结构。它不支持编程语言的类型系统,所有数据内容均以字符串形式存储。</p><h3 id="特点:-2"><a href="#特点:-2" class="headerlink" title="特点:"></a>特点:</h3><ul><li>定义页面的内容和结构。</li><li>数据类型在解析时由其他技术(如 JavaScript)或用户代理决定。</li></ul><h3 id="示例-HTML-代码:"><a href="#示例-HTML-代码:" class="headerlink" title="示例 HTML 代码:"></a>示例 HTML 代码:</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-meta"><!DOCTYPE <span class="hljs-keyword">html</span>></span><br><span class="hljs-tag"><<span class="hljs-name">html</span>></span><br><span class="hljs-tag"><<span class="hljs-name">head</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">title</span>></span>HTML 示例<span class="hljs-tag"></<span class="hljs-name">title</span>></span><br><span class="hljs-tag"></<span class="hljs-name">head</span>></span><br><span class="hljs-tag"><<span class="hljs-name">body</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">h1</span>></span>Hello, World!<span class="hljs-tag"></<span class="hljs-name">h1</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">p</span>></span>Age: 25<span class="hljs-tag"></<span class="hljs-name">p</span>></span> <span class="hljs-comment"><!-- 数字以文本形式存储 --></span><br> <span class="hljs-tag"><<span class="hljs-name">p</span>></span>Is Student: false<span class="hljs-tag"></<span class="hljs-name">p</span>></span> <span class="hljs-comment"><!-- 布尔值以文本形式存储 --></span><br><span class="hljs-tag"></<span class="hljs-name">body</span>></span><br><span class="hljs-tag"></<span class="hljs-name">html</span>></span><br></code></pre></td></tr></table></figure><h3 id="使用-HTML-的数据(JavaScript):"><a href="#使用-HTML-的数据(JavaScript):" class="headerlink" title="使用 HTML 的数据(JavaScript):"></a>使用 HTML 的数据(JavaScript):</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs jsx"><span class="hljs-comment">// 获取 HTML 中的数据</span><br><span class="hljs-keyword">const</span> ageText = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'p:nth-child(2)'</span>).<span class="hljs-property">textContent</span>;<br><span class="hljs-keyword">const</span> isStudentText = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'p:nth-child(3)'</span>).<span class="hljs-property">textContent</span>;<br><br><span class="hljs-comment">// 转换为对应类型</span><br><span class="hljs-keyword">const</span> age = <span class="hljs-built_in">parseInt</span>(ageText.<span class="hljs-title function_">split</span>(<span class="hljs-string">': '</span>)[<span class="hljs-number">1</span>]);<br><span class="hljs-keyword">const</span> isStudent = isStudentText.<span class="hljs-title function_">split</span>(<span class="hljs-string">': '</span>)[<span class="hljs-number">1</span>] === <span class="hljs-string">'true'</span>;<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Age: <span class="hljs-subst">${age}</span>, Is Student: <span class="hljs-subst">${isStudent}</span>`</span>);<br></code></pre></td></tr></table></figure><hr>]]></content>
<categories>
<category>科普</category>
</categories>
<tags>
<tag>C++</tag>
<tag>I++Club</tag>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>爱与魔法</title>
<link href="/%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/"/>
<url>/%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/</url>
<content type="html"><![CDATA[<h1 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h1><p>本文的标题是<strong>爱与魔法</strong>,所以本文的重点将是两个部分</p><ul><li><p>如何使用魔法访问国际互联网</p></li><li><p>如何使用AI工具辅助学习</p></li></ul><p>由于我们的博客部署在 <code>Github</code> 上面,所以我们可以大声的说出第一条所指代的词语:翻墙(大声)!!</p><h2 id="为何需要魔法"><a href="#为何需要魔法" class="headerlink" title="为何需要魔法"></a>为何需要魔法</h2><p>那么为什么我们将这个知识点如此提前,必然是因为魔法是十分重要的技能,尤其是对于计算机专业的学生,何以见得?</p><p><em>Tips: 下面列出了许多例子,如果你不想完全阅读,可以点击目录来向后跳转</em></p><ul><li><p><strong>获取技术资源</strong></p><p>很多计算机领域的前沿技术、学术论文、开发工具、教程和开源项目都发布在国际平台上,如GitHub、Stack Overflow、Google Scholar、YouTube、Reddit等。</p></li><li><p><strong>参与开源社区</strong></p><p>开源社区是计算机技术发展的重要驱动力,例如Linux、Python、TensorFlow等项目背后都有庞大的国际开发者社区。要参与这些项目,或者使用这些技术,我们可能需要访问GitHub、DockerHub等平台。</p></li><li><p><strong>学习和使用主流技术</strong></p><p>很多技术培训和教学资源都托管在国外的视频平台或在线学习平台上,如YouTube、Coursera、Udemy等。</p></li><li><p><strong>突破国内网络限制进行科研</strong></p><p>许多国际期刊、学术数据库和科研资源在某些地区的访问会受到限制。魔法可以帮助你突破这些限制,查阅必要的学术资源,从而进行研究和学术写作。</p></li><li><p><strong>访问全球开发者社区与工具</strong></p></li></ul><p> 不仅是开源项目,很多开发工具和平台也是全球通用的,如Node.js包管理器(npm)、Python的pip、甚至是浏览器插件市场等。如果这些工具和平台受到了访问限制,会影响你的编程和开发效率。</p><h2 id="魔法从何而来"><a href="#魔法从何而来" class="headerlink" title="魔法从何而来"></a>魔法从何而来</h2><ul><li><p><strong>2003年:“防火长城”(Great Firewall, GFW)项目的启动</strong></p><p>“防火长城”是我国互联网监管的核心技术体系,通过该系统,政府能够过滤和屏蔽特定的国际网站和信息。随着时间的推移,GFW的技术不断升级,屏蔽了越来越多的外国网站和服务,包括谷歌、Facebook、Twitter、YouTube等。</p></li><li><p><strong>2008年:北京奥运会和互联网审查</strong></p><p> 在北京奥运会期间,我国加大了对互联网的控制,屏蔽了许多海外网站和新闻平台。这一时期,政府意识到互联网对社会舆论的影响,因此进一步强化了信息管制。</p></li><li><p><strong>2010年:谷歌事件</strong></p><p>谷歌在2010年决定将其搜索服务从大陆转移至香港,主要原因是无法接受中国的审查要求。这一事件进一步标志了中国与国际互联网的部分脱钩。</p></li></ul><p>这就可以解释为什么不是“渡海”而要叫”翻墙“,因为翻过的是防火墙(防火长城)</p><h2 id="魔法的技术依赖"><a href="#魔法的技术依赖" class="headerlink" title="魔法的技术依赖"></a>魔法的技术依赖</h2><p>作为用户,几乎无需知晓其原理,只需使用即可。以下内容为 <code>ChatGPT</code> 生成,如需了解更多详细信息,可以自行访问 <code>YouTube</code> 等平台查阅。</p><p>魔法的原理主要依赖于绕过互联网服务提供者(ISP)和网络防火墙的内容过滤和封锁机制。简而言之,魔法工具通过隐藏或修改用户的网络流量,使其绕过本地或国家层面的网络限制,访问被屏蔽的网站或服务。</p><p>以下是魔法的主要技术原理:</p> <div class="fold"> <div class="fold-title fold-info collapsed" data-toggle="collapse" href="#collapse-26dbf51c" role="button" aria-expanded="false" aria-controls="collapse-26dbf51c"> <div class="fold-arrow">▶</div>主要技术原理 </div> <div class="fold-collapse collapse" id="collapse-26dbf51c"> <div class="fold-content"> <ul><li><p><strong>代理(Proxy)</strong></p><p>代理服务器是最常见的魔法工具之一。它充当中介,在用户和目标网站之间转发流量。用户的网络请求首先发送到代理服务器,然后代理服务器代表用户向目标网站发出请求,最后再将目标网站的响应传回给用户。</p></li><li><p><strong>虚拟专用网络(VPN)</strong></p><p>VPN是一种常用的魔法工具。VPN通过在用户与VPN服务器之间创建一个加密的隧道,隐藏用户的实际IP地址,并通过VPN服务器来访问被屏蔽的资源。</p></li><li><p><strong>Shadowsocks</strong></p><p>Shadowsocks是一种加密的代理协议,通常用于魔法。它通过加密用户流量来绕过防火墙。</p><ul><li><p><strong>工作原理</strong>:Shadowsocks使用加密协议将用户流量发送到海外的Shadowsocks服务器,然后服务器代理用户访问互联网。</p></li><li><p><strong>优点</strong>:由于它使用的流量看起来像普通的HTTPS流量,因此在防火墙上更难被识别。</p></li><li><p><strong>缺点</strong>:需要配置服务器,依赖于稳定的海外服务器。</p></li></ul></li><li><p><strong>Tor(The Onion Router)</strong></p><p>Tor是一个匿名网络,通过多层代理(称为“节点”)来隐藏用户身份。每一层加密一部分数据,流量通过多个中间节点传递,最后到达目标服务器。</p></li><li><p><strong>混淆协议(Obfuscation Protocols)</strong></p><p>混淆技术的目的是隐藏流量的真实特性,使防火墙无法识别它是代理或VPN流量。防火墙可以通过深度包检测(DPI)识别常见的代理或VPN流量,混淆技术会将这些流量伪装成其他流量(如普通的HTTPS流量)。</p></li></ul> </div> </div> </div><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>魔法的核心技术原理是通过代理、VPN、加密、流量混淆等方式绕过防火墙的审查,隐藏或修改网络流量的真实目标。每种方法都有各自的优缺点,选择合适的工具和技术需要根据防火墙的具体封锁手段和个人需求。</p><h1 id="使用魔法"><a href="#使用魔法" class="headerlink" title="使用魔法"></a>使用魔法</h1><p>前面提到了各类魔法技术,其共同点都是“需要一个稳定的服务提供商”。</p><p>这个你需要自行找人推荐,笔者在这边不适合直接放出链接。</p><p>现在假设你已经知道了自己想要购买的服务,但是问题出现了:这些服务提供商的官网通常也需要魔法才能访问,坏了!死循环了!</p><h2 id="破局之法"><a href="#破局之法" class="headerlink" title="破局之法"></a>破局之法</h2><p>前面说到“需要一个稳定的服务提供商”,那解决方法也很简单,你用不稳定的就行了。</p><p>你有许多方法可以获得“不稳定的魔法服务”:</p><ul><li>使用 <code>Bing</code> 搜索引擎,搜索“机场”相关关键字,随意找一个提供免费/低价试用的提供商</li><li>像拥有魔法的人寻求帮助,让ta代为购买</li><li>拷打俱乐部成员(误)</li></ul><h2 id="选择你的魔法工具"><a href="#选择你的魔法工具" class="headerlink" title="选择你的魔法工具"></a>选择你的魔法工具</h2><p>现在默认你已经有个一个可以访问的服务提供商,无论是临时的还是你托人买的稳定的,后续步骤都是同理。</p><h3 id="推荐客户端"><a href="#推荐客户端" class="headerlink" title="推荐客户端"></a>推荐客户端</h3> <div class="fold"> <div class="fold-title fold-info collapsed" data-toggle="collapse" href="#collapse-a282b679" role="button" aria-expanded="false" aria-controls="collapse-a282b679"> <div class="fold-arrow">▶</div>推荐客户端 </div> <div class="fold-collapse collapse" id="collapse-a282b679"> <div class="fold-content"> <ul><li>karing</li><li>Clash Verge REV</li><li>Clash Nyanpasu</li><li>Clash-Meta For Android</li><li>NekoBox</li><li>NekoRay</li><li>Clash Verge (Clash.Meta Core)</li><li>V2RayN</li><li>v2RayNG</li><li>ShadowRocket</li><li>Hiddify</li><li>A GUI For Clash</li></ul> </div> </div> </div><h3 id="不推荐客户端"><a href="#不推荐客户端" class="headerlink" title="不推荐客户端"></a>不推荐客户端</h3> <div class="fold"> <div class="fold-title fold-info collapsed" data-toggle="collapse" href="#collapse-80e3be53" role="button" aria-expanded="false" aria-controls="collapse-80e3be53"> <div class="fold-arrow">▶</div>不推荐客户端 </div> <div class="fold-collapse collapse" id="collapse-80e3be53"> <div class="fold-content"> <ul><li>Clash For Windows</li><li>Clash For Android</li><li>Quantumult X (不支持Vless和Hysteria协议)</li><li>Sing-Box</li></ul> </div> </div> </div><h3 id="下载链接"><a href="#下载链接" class="headerlink" title="下载链接"></a>下载链接</h3> <div class="fold"> <div class="fold-title fold-info collapsed" data-toggle="collapse" href="#collapse-5f9fc749" role="button" aria-expanded="false" aria-controls="collapse-5f9fc749"> <div class="fold-arrow">▶</div>下载链接 </div> <div class="fold-collapse collapse" id="collapse-5f9fc749"> <div class="fold-content"> <h4 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h4><ul><li><a href="https://karing.app/">karing(推荐)</a></li><li><a href="https://github.com/clash-verge-rev/clash-verge-rev/releases/latest">Clash Verge REV(推荐)</a></li><li><a href="https://github.com/LibNyanpasu/clash-nyanpasu/releases/latest">Clash Nyanpasu</a></li><li><a href="https://github.com/Z-Siqi/Clash-for-Windows_Chinese/releases">Clash For Windows (不推荐)</a></li><li><a href="https://github.com/hiddify/hiddify-next/releases/latest">Hiddify</a></li><li><a href="https://github.com/SagerNet/sing-box/releases/latest">Sing-Box</a></li><li><a href="https://github.com/GUI-for-Cores/GUI.for.Clash/releases/latest">GUI For Clash</a></li></ul><h4 id="MacOS"><a href="#MacOS" class="headerlink" title="MacOS"></a>MacOS</h4><ul><li><a href="https://karing.app/">karing(推荐)</a></li><li><a href="https://github.com/clash-verge-rev/clash-verge-rev/releases/latest">Clash Verge REV(推荐)</a></li><li><a href="https://github.com/clashbk/clashx/releases/download/1.96.2/ClashX.dmg">Clash X</a></li><li><a href="https://github.com/clash-verge-rev/clash-verge-rev/releases/latest">Clash Verge Rev</a></li><li><a href="https://github.com/LibNyanpasu/clash-nyanpasu/releases/latest">Clash Nyanpasu</a></li><li><a href="https://github.com/hiddify/hiddify-next/releases/latest">Hiddify</a></li></ul><h4 id="Android"><a href="#Android" class="headerlink" title="Android"></a>Android</h4><ul><li><a href="https://karing.app/">karing(推荐)</a></li><li><a href="https://github.com/MetaCubeX/ClashMetaForAndroid/releases/latest">Clash-Meta For Android</a></li><li><a href="https://github.com/MatsuriDayo/NekoBoxForAndroid/release/latest">NekoBox</a></li><li><a href="https://github.com/Z-Siqi/Clash-for-Windows_Chinese/releases">Clash For Android (不推荐)</a></li><li><a href="https://github.com/hiddify/hiddify-next/releases/latest">Hiddify</a></li><li><a href="https://github.com/SagerNet/sing-box/releases/latest">Sing-Box</a></li></ul><h4 id="iOS-iPadOS"><a href="#iOS-iPadOS" class="headerlink" title="iOS / iPadOS"></a>iOS / iPadOS</h4><ul><li><a href="https://karing.app/">karing(推荐)</a></li><li><a href="https://apps.apple.com/us/app/shadowrocket/id932747118">ShadowRocket</a></li></ul> </div> </div> </div><h2 id="使用教程"><a href="#使用教程" class="headerlink" title="使用教程"></a>使用教程</h2><h3 id="Windows(其它系统同理)"><a href="#Windows(其它系统同理)" class="headerlink" title="Windows(其它系统同理)"></a>Windows(其它系统同理)</h3><p>在你的服务提供商官网,大概率有类似这样的一堆按钮</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019002512724.png" alt="image-20241019002512724"></p><p>你可以理解为这些是魔法咒语的下载链接,一般我们点击<strong>复制</strong></p><p>然后打开你下载的软件,这里使用 <code>Clash</code> 系列和 <code>v2Ray</code> 系列演示</p><ul><li><p>导入你的配置</p><table><thead><tr><th><img src="https://pic.superbed.cc/item/65e9cd0a792284bad6d01abd.png" alt="Clash Nyanpasu"></th><th><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019002924126.png" alt="image-20241019002924126"></th></tr></thead></table></li><li><p>按照你的需要选择线路(一般区别在于节点位置、费用、是否解锁ChatGPT、是否解锁Netflix等)</p><table><thead><tr><th><img src="https://pic.superbed.cc/item/65e9cd1d792284bad6d01ae0.png" alt="测速并选择线路"></th><th><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019003146740.png" alt="image-20241019003146740"></th></tr></thead></table></li><li><p>按照你的需要开启系统代理(各种选项都是字面意思)</p><table><thead><tr><th align="center"><img src="https://pic.superbed.cc/item/65e9cd30792284bad6d01b07.png" alt="系统代理"></th><th align="center"><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019003300915.png" alt="image-20241019003300915"></th></tr></thead></table></li></ul><h3 id="Android"><a href="#Android" class="headerlink" title="Android"></a>Android</h3><p>安卓这个只是单纯想写一个目录hhh,具体操作和Windows区别不大,相信以你的聪明才智很容易就可以解决。</p><p>另外提醒,设备在使用魔法时会带来额外的功耗,移动端尤为明显,具体体现可能有发热、浏览器访问国内网页变慢等等</p><p>不是中毒了!!!<br>不是中毒了!!!<br>不是中毒了!!!</p><h1 id="使用AI"><a href="#使用AI" class="headerlink" title="使用AI"></a>使用AI</h1><p>笔者在此大发暴论,一个不会使用AI的大学生是不合格的!!!</p><p>AI 的种类有很多,他们的偏向各不相同,在不同的领域也应该使用不同的AI。</p><h2 id="生成式人工智能在文档方面的应用"><a href="#生成式人工智能在文档方面的应用" class="headerlink" title="生成式人工智能在文档方面的应用"></a>生成式人工智能在文档方面的应用</h2><h3 id="为什么要使用?"><a href="#为什么要使用?" class="headerlink" title="为什么要使用?"></a>为什么要使用?</h3><p>使用生成式人工智能能够极大地提高编写各类文档的效率。通过合理利用这些先进技术,不仅可以显著提高工作效率,还能增强文档的专业度和吸引力。</p><p>然而,值得注意的是,尽管AI能够很好地辅助人类工作,但它并不能完全取代人的角色。最佳实践是在充分利用AI潜力的同时,保持批判性思维,确保所有产出都经过仔细校验,从而共同创造出更加出色的工作成果。</p><h3 id="注意什么"><a href="#注意什么" class="headerlink" title="注意什么"></a>注意什么</h3><p>和人一样,大模型的产出也需要通过不断的反馈来提高输出质量</p><p><del>但和人不同的是,你说了它真的回去做(虽有有时未必会改</del></p><h4 id="定义清晰的目标"><a href="#定义清晰的目标" class="headerlink" title="定义清晰的目标"></a>定义清晰的目标</h4><p>清晰定义你希望通过AI达成的目标是什么,比如特定主题的文章、产品描述等,尽可能使目标不具有二义性。</p><h4 id="提供详细输入"><a href="#提供详细输入" class="headerlink" title="提供详细输入"></a>提供详细输入</h4><p>向模型提供更多关于所需输出的具体信息,如背景资料、期望语气等。</p><p>通常更详细的背景信息能使模型输出更贴合主题的内容。</p><p>有时将一个示例提交给它能使得它更好地完成任务。</p><h4 id="迭代反馈"><a href="#迭代反馈" class="headerlink" title="迭代反馈"></a>迭代反馈</h4><p>根据初步结果进行调整并重复过程,逐步优化直到满意为止。</p><p><strong>非常重要</strong></p><p>这时请把自己想象成一个无情的甲方。</p><h4 id="编辑后审查"><a href="#编辑后审查" class="headerlink" title="编辑后审查"></a>编辑后审查</h4><p>即使是最先进的AI系统也可能产生错误,因此对最终产物进行全面检查是必要的步骤。</p><p>对于一些专业领域,请确保自己对该领域有一定了解,不要全权交给大模型。</p><h3 id="一些使用体验"><a href="#一些使用体验" class="headerlink" title="一些使用体验"></a>一些使用体验</h3><p>GPT系列的综合能力在诸多大模型中处于领先地位,但不同大模型在不同的领域仍有一些优势,譬如更全面的中文语料、趋近于免费的廉价使用成本以及访问的便捷性等</p><h4 id="GPT系列"><a href="#GPT系列" class="headerlink" title="GPT系列"></a>GPT系列</h4><p>无需多言,但中文语料的实时性稍逊。</p><p>适用于各种文本生成任务,包括但不限于文章撰写、修改,代码生成、问答等。</p><p>特别适合需要高质量和多样化内容输出的应用场景</p><h4 id="智谱清言"><a href="#智谱清言" class="headerlink" title="智谱清言"></a>智谱清言</h4><p>在文本生成领域,各方面优点都具备一些但没有特别突出。</p><p>逊于GPT的代码能力,但在国产大模型中较为领先。<strong>输出大规模代码会出错</strong></p><p>具备一定线上搜索能力,且标定来源。<strong>总结会糅合多个结果,信息有时错位,请注意核实</strong></p><h4 id="通义"><a href="#通义" class="headerlink" title="通义"></a>通义</h4><p>国产大模型中较为领先的多模态,但逊于GPT,额度不足时可少量替代。</p><p>强悍的结合线上搜索结果风格化文案生成,<del>比如社团招新文案</del></p><p>提供PPT生成,长文本解读等小工具,傻瓜式操作</p><h4 id="文心"><a href="#文心" class="headerlink" title="文心"></a>文心</h4><p>更像一个助手而非生成工具</p><p>有时会突发奇怪的风格化语句</p><h4 id="讯飞星火"><a href="#讯飞星火" class="headerlink" title="讯飞星火"></a>讯飞星火</h4><p>体验稍差,有时理解存在偏差,尚堪一用</p><p>提供更丰富的小工具</p><p>中英翻译功能在国产大模型中准确性较好(仅个人感受)</p><h4 id="WPS-AI"><a href="#WPS-AI" class="headerlink" title="WPS AI"></a>WPS AI</h4><p>翻译功能可堪一用</p><p>文本理解和生成能力较差,有时结果中会混入奇怪的符号,复杂要求的文本工作尽量不要使用</p><p>但和WPS相关功能结合,在办公三件套领域专业性很强</p><h2 id="AI-代码补全的使用"><a href="#AI-代码补全的使用" class="headerlink" title="AI 代码补全的使用"></a>AI 代码补全的使用</h2><p>笔者最推荐的是 <code>GitHub Copilot</code>,无需多言,你应该也在各种地方听说过它。</p><p>注意:作为一名大学生,你可以免费使用 <code>GitHub Education</code> 提供的 <code>Copilot</code>,具体申请方式请自行搜索。</p><p>如果你懒得搞这个,那也有替代项,<code>CodeGeex</code> 也是不错的选择。</p><h3 id="生成式代码补全"><a href="#生成式代码补全" class="headerlink" title="生成式代码补全"></a>生成式代码补全</h3><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019162440183.png" alt="image-20241019162440183"></p><p>如图,对于简单的功能,你只需要对你的类或函数进行合理的命名,<code>Copilot</code> 会自动猜测你的下文,呈现为图中的灰色字体,按下 <code>Tab</code> 键就可以一键补全。</p><p>这也就是我们常说的 <strong>面向Tab编程</strong></p><h3 id="代码纠错"><a href="#代码纠错" class="headerlink" title="代码纠错"></a>代码纠错</h3><table><thead><tr><th><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019162834584.png" alt="image-20241019162834584"></th><th><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019162914976.png" alt="image-20241019162914976"></th></tr></thead></table><p>左图,我故意将快速幂的 <code>b >>= 1</code> 这一步的 <code>=</code> 删除,使用 <code>Copilot</code> 内联聊天来询问(纠错快捷指令: <code>/fix</code> ),它会给出修改建议,覆盖在原代码之上,点击接受即可使用。</p><h3 id="全新生成"><a href="#全新生成" class="headerlink" title="全新生成"></a>全新生成</h3><p>如果你已经添加了 <code>Copilot</code> 插件,那么应该会有类似的界面</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019163339999.png" alt="image-20241019163339999"></p><p>你可以像使用 <code>ChatGPT</code> 一样来跟它对话,阐述你的要求。</p><h1 id="深度解析-ChatGPT"><a href="#深度解析-ChatGPT" class="headerlink" title="深度解析 ChatGPT"></a>深度解析 ChatGPT</h1><h2 id="写在前面-1"><a href="#写在前面-1" class="headerlink" title="写在前面"></a>写在前面</h2><ul><li>项目教学部分基于chatGPT生成</li><li>部分文章内容基于chatGPT生成</li></ul><p>关于AI的具体实现技术,我们在此不多做探讨,本篇博客聚焦于如何使用AI,工欲善其事必先利其器,在学习如何使用之前,我们首先要了解我们的工具。</p><p>在AI火得一塌糊涂的当下,不少公司都推出了自己的大模型,生成式AI已经成为强大的生产力,纯AI创作的短片、AI主播等昭示了AI流水线工作的实力,而你需要的是做一个优秀的“资本家”,学会如何使用恰当的AI做恰当的事情。</p><p>从chatGPT横空出世震惊世界开始,我就一直在用AI进行各种尝试,如今AI已经成为我最重要的生产力,每天不打开AI用一用都会觉得难受。在此我分享一些我使用AI的心得体验,也欢迎大家多多进行AI用法开发,搭上时代的快车,提高生产力。</p><p>我们主要结合生成文本的对话AI模型进行使用教学,绘画咒语或者其他特殊应用在此不多涉及,大家也可以在学会今天的内容之后拷打AI进行学习。</p><h2 id="关于如何使用chatGPT?"><a href="#关于如何使用chatGPT?" class="headerlink" title="关于如何使用chatGPT?"></a>关于如何使用chatGPT?</h2><p>对于正版的chatGPT4,openAI提供了两种方式进行选择,一种是官网直接访问,另一种是通过调用API进行各种类型的访问,两种方式价格不同,使用方式也有差别。</p><p>官网访问呢非常简单,只需要施展魔法,打开官网,点击注册登录后就可以使用,免费的chatGPT3.5的代码能力还是略差一些,有钱可以试试chatGPT4,但是官网收费还是比较贵的(<del>富哥忽略</del>)。</p><p>如果没有账号或者不想自己注册账号充钱用gpt4,可以tb买共享账号,但登录仍然需要魔法。个人觉得,如果用3.5,不如国内选一个好用的AI平替,省点梯子费,效果也差不多。</p><p>登录官网使用gpt4需要注意两点,一个是魔法建议采用全局模式,另一个是注册账号尽量不要用qq邮箱之类的,可以申请gmail或者outlook邮箱,因为openAI对国内访问限制还是比较严格,这些一不小心就暴露你是国人的地方(比如非全局模式被发现,比如采用疑似中国人爱用的qq邮箱等)可能会导致卡你资格,不让注册。</p><p>调用API访问就比较多样了,国内你看到的有些套皮GPT4就是调了API,然后搭了一个页面给你用,真假未知。如果自己不会官网买API然后用,也可以到中间商那里购买API,tb搜索关键字gpt key,可以找到对应的商家,不同tokens数价格不同。由于openAI官网的一些收费机制,比如先用后付等,很多商家会钻空子用各种方式降低成本卖API获得利润。(仔细甄别小心被骗哦)</p><p>API访问GPT4需要部署,tb还有一个好处就是可使用的模型比较全,一般也会提供一个部署好的页面,你直接输入API就可以在页面上访问进行对话,不需要自己部署。</p><p>如果要自己部署,可以买一个API,然后开始开发各种好玩的用法啦,GitHub上也有一些有趣的部署应用。</p><p>至于怎么部署一个简单API应用,先卖个关子,我们先继续探讨一些其他的东西。</p><p><del>更多chatGPT4相关信息可以拷打群主</del></p><h2 id="主流对话式AI模型"><a href="#主流对话式AI模型" class="headerlink" title="主流对话式AI模型"></a>主流对话式AI模型</h2><p>笔者列出了当前主流的一些AI大模型以及对应网站和信息,供大家参考,主要列举国内的(因为大部分较便宜或免费且不需要梯子以及复杂的验证,具有一定可用性)。</p><p>若会使用ChatGPT4,则推荐强烈使用,工业水准上GPT4吊打国内产品,而专门训练写代码的AI吊打GPT4。除了生成质量高,最重要的是速度非常快!!!如果有过使用对比体验会深刻感受到快有多么重要。</p><h3 id="世界主流对话式AI模型"><a href="#世界主流对话式AI模型" class="headerlink" title="世界主流对话式AI模型"></a><strong>世界主流对话式AI模型</strong></h3><p>1.<strong>OpenAI - ChatGPT</strong></p><ul><li><p><strong>简介</strong>:ChatGPT是由OpenAI开发的基于GPT-4的对话式AI。它能够进行多轮对话,支持多种语言,广泛应用于客服、内容创作、编程等场景。</p></li><li><p><strong>收费</strong>:</p><p>免费版:基于GPT-3.5。</p><p>付费版:ChatGPT Plus,基于GPT-4,每月$20。</p></li><li><p><strong>官网</strong>:<a href="https://openai.com/chatgpt">https://openai.com/chatgpt</a></p></li></ul><p>2.<strong>Anthropic - Claude</strong></p><ul><li><strong>简介</strong>:Claude是Anthropic开发的AI聊天机器人,以安全性和对话质量著称。Claude也有多版本的迭代,支持复杂对话和任务。</li><li><strong>收费</strong>:提供免费版本以及企业定制服务,定价根据企业需求而定。</li><li><strong>官网</strong>:<a href="https://www.anthropic.com/">https://www.anthropic.com</a></li></ul><p>3.<strong>Google - Bard</strong></p><ul><li><strong>简介</strong>:Bard是由Google开发的基于其语言模型PaLM的对话式AI工具,专注于生成高质量的文本、进行复杂查询等。</li><li><strong>收费</strong>:目前免费使用。</li><li><strong>官网</strong>:<a href="https://bard.google.com/">https://bard.google.com</a></li></ul><p>4.<strong>Microsoft - Azure OpenAI (GPT-powered models)</strong></p><ul><li><strong>简介</strong>:微软通过其Azure平台提供GPT模型服务,企业用户可以通过API调用GPT-4等语言模型,用于客户服务、数据分析等场景。</li><li><strong>收费</strong>:按API调用次数和计算资源收费,具体费用取决于使用量。</li><li><strong>官网</strong>:<a href="https://azure.microsoft.com/en-us/services/openai/">https://azure.microsoft.com/en-us/services/openai/</a></li></ul><p>5.<strong>Meta - LLaMA (Large Language Model Meta AI)</strong></p><ul><li><strong>简介</strong>:Meta推出的LLaMA是一款专注于高效推理和训练的语言模型,开放给研究人员进行开发和实验。</li><li><strong>收费</strong>:主要用于研究,暂未提供商业定价。</li><li><strong>官网</strong>:<a href="https://ai.facebook.com/blog/large-language-models-are-having-their-stable-diffusion-moment/">https://ai.facebook.com/blog/large-language-models-are-having-their-stable-diffusion-moment/</a></li></ul><h3 id="中国主流对话式AI模型"><a href="#中国主流对话式AI模型" class="headerlink" title="中国主流对话式AI模型"></a><strong>中国主流对话式AI模型</strong></h3><p>1.<strong>百度 - 文心一言 (Ernie Bot)</strong></p><ul><li><strong>简介</strong>:文心一言是百度推出的对话式AI,基于文心大模型(Ernie),能够进行中文和英文对话,并支持文本生成、语言翻译、情感分析等功能。</li><li><strong>收费</strong>:<ul><li>免费版:提供基础功能。</li><li>企业版:根据API调用量和服务种类进行收费。</li></ul></li><li><strong>官网</strong>:<a href="https://yiyan.baidu.com/">https://yiyan.baidu.com</a></li></ul><p>2.<strong>阿里巴巴 - 通义千问 (Tongyi Qianwen)</strong></p><ul><li><strong>简介</strong>:通义千问是阿里巴巴推出的AI大模型,支持多种场景的自然语言处理、智能客服、语音识别等功能。</li><li><strong>收费</strong>:提供免费体验和企业定制服务,具体定价根据使用量。</li><li><strong>官网</strong>:<a href="https://tongyi.aliyun.com/">https://tongyi.aliyun.com</a></li></ul><p>3.<strong>讯飞 - 星火认知大模型</strong></p><ul><li><strong>简介</strong>:由科大讯飞开发的星火认知大模型,专注于中文处理和行业解决方案,应用于教育、医疗、法律等领域。</li><li><strong>收费</strong>:提供免费版本及付费企业解决方案。</li><li><strong>官网</strong>:<a href="https://xinghuo.xfyun.cn/">https://xinghuo.xfyun.cn</a></li></ul><p>4.<strong>商汤科技 - 商量SenseChat</strong></p><ul><li><strong>简介</strong>:商汤科技推出的商量大模型,主打智能对话、客服和内容创作,支持多种语言。</li><li><strong>收费</strong>:根据企业需求提供定制化方案。</li><li><strong>官网</strong>:<a href="https://www.sensetime.com/">https://www.sensetime.com</a></li></ul><p>5.<strong>华为 - Pangu Model(盘古大模型)</strong></p><ul><li><strong>简介</strong>:华为推出的盘古大模型覆盖多种场景,主要应用于金融、医疗、制造业等领域,支持多种语言和对话功能。</li><li><strong>收费</strong>:根据服务和API调用收费,主要面向企业客户。</li><li><strong>官网</strong>:<a href="https://www.huawei.com/">https://www.huawei.com</a></li></ul><p>国内如果要免费的AI,文心一言免费的就够用了,收费版我没用过(钱都投给GPT4了),访问不需要梯子,效果似乎也不错。</p><h2 id="你可以用AI做任何你想做的事情"><a href="#你可以用AI做任何你想做的事情" class="headerlink" title="你可以用AI做任何你想做的事情"></a>你可以用AI做任何你想做的事情</h2><p>这个标题放在这里,乍一看有些唬人哈,但是毫不夸张地说,如果你真的会使用AI,已经可以做到很多你想做的事情了。</p><p>在讨论如何使用AI之前,我们先来看看“正确的使用AI的思维方式”</p><ul><li>AI可以做到什么?</li><li>如何正确提问</li></ul><h4 id="AI可以做到什么?"><a href="#AI可以做到什么?" class="headerlink" title="AI可以做到什么?"></a><strong>AI可以做到什么?</strong></h4><p>日常生活中,简单的文本生成,比如发通稿写点文案以及一些简单的代码,这些AI都可以轻松做到,利用一些固定的prompt甚至可以做得更优。但是这些仅仅是AI使用的很小的一部分——你甚至可以让它帮忙做心理疏导,工数考试复习计划表,个人爬虫学习计划制定,各类事情决策分析,把一列文本里所有空格去掉,把表格里所有特定信息提取等,都可以用AI完成。</p><p>毫不客气地说,任何人类可以做到的比较轻松但花心力的小事情,都可以用AI来解放生产力!</p><p>AI可以做到什么?思考这件事也许能帮助你开法更多AI用法,不断提高你的生产力。而作为一位计算机学生,如何用AI辅助学习是一件很值得研究的事情。</p><p>众所周知,做项目是提升能力很重要的一个方法,因此,<strong>在这里我们以一个项目为例子,来手把手教你如何用AI做到你想做的任何事情,来学习新技术</strong>。</p><h4 id="如何正确提问"><a href="#如何正确提问" class="headerlink" title="如何正确提问"></a><strong>如何正确提问</strong></h4><p>以下内容由chatGPT和笔者共同完成。</p><p>向AI提问的技巧在使用对话式AI(如ChatGPT、Claude或其他大型语言模型)时至关重要。如何组织你的问题或提示词(prompt)会直接影响AI生成结果的质量和精确度。以下是一些详细的技巧和策略,帮助你更好地与AI互动、提高回复的有效性。</p><h5 id="1-明确清晰的提问"><a href="#1-明确清晰的提问" class="headerlink" title="1. 明确清晰的提问"></a>1. <strong>明确清晰的提问</strong></h5><p>避免模糊不清的问题,确保问题的结构清晰并准确传达你希望获取的答案。AI通常会根据你的提问生成相应的内容,因此问题的精确性会决定输出的相关性。</p><p><strong>示例:</strong></p><ul><li>不明确的提问:<em>“给我讲个故事。”</em></li><li>更明确的提问:<em>“请写一个关于勇敢和友谊的短篇故事,字数控制在300字以内。”</em></li></ul><p>个人认为这点非常重要,清晰的描述,准确的形容词动词能够让AI很好完成你真正需要的任务。</p><h5 id="2-设置具体的上下文"><a href="#2-设置具体的上下文" class="headerlink" title="2. 设置具体的上下文"></a>2. <strong>设置具体的上下文</strong></h5><p>提供足够的上下文信息,使AI更好地理解你的需求。上下文包括任务的背景、对输出的要求、以及你想要涵盖的内容。</p><p><strong>示例:</strong></p><ul><li>无上下文:<em>“解释人工智能。”</em></li><li>有上下文:<em>“用简单的语言向一位高中生解释什么是人工智能,并举一个日常生活中的例子。”</em></li></ul><p>对于一些特殊情境,比如小组作业、发言稿、发给大众的通告、课程大作业等需要不同的语气和侧重点的生成内容,这些都是重要信息,需要向AI解释。有时也许会有意想不到的好效果。</p><h5 id="3-指明输出格式"><a href="#3-指明输出格式" class="headerlink" title="3. 指明输出格式"></a>3. <strong>指明输出格式</strong></h5><p>如果你对输出格式有要求(如表格、列表、代码片段等),可以明确告诉AI,以确保AI生成的内容符合你的期望。</p><p><strong>示例:</strong></p><ul><li>普通提问:<em>“帮我总结一下这个章节的要点。”</em></li><li>指定格式:<em>“请用一个三段式的结构总结本章的要点,每段100字左右。”</em></li></ul><p><strong>代码输出示例:</strong></p><ul><li><em>“请使用Python编写一个计算两个数之和的函数,并加上注释。”</em></li></ul><p>建议大家让AI写代码都说句加上详细注释,尽管它自己也会加,但是这句可以让它解释更详细更清楚。</p><h5 id="4-逐步引导,分解复杂问题"><a href="#4-逐步引导,分解复杂问题" class="headerlink" title="4. 逐步引导,分解复杂问题"></a>4. <strong>逐步引导,分解复杂问题</strong></h5><p>对于复杂任务,可以将其分解成多个步骤,逐步引导AI进行每个步骤的处理,这样AI的生成结果会更加准确。</p><p><strong>示例:</strong></p><ul><li>复杂提问:<em>“帮我写一个商业计划书。”</em></li><li>分解步骤:<em>“首先,帮我写一个商业计划书的执行摘要。然后,描述一下市场分析和目标受众。”</em></li></ul><p>这大概是我等会要介绍最重要的一点,一个大项目,一篇论文,都是非常复杂的,AI无法一次输出很多东西,我们需要分解任务,将简单的小任务丢给AI,然后我们做大老板验收成果并组合。</p><h5 id="5-控制文本长度"><a href="#5-控制文本长度" class="headerlink" title="5. 控制文本长度"></a>5. <strong>控制文本长度</strong></h5><p>如果你希望生成的文本有特定的长度,或者你需要简明的回答,可以在提问时设置文本的长度或限制。<br><strong>示例:</strong></p><ul><li>没有限制:<em>“请解释一下量子计算。”</em></li><li>有长度限制:<em>“请在50字以内解释量子计算。”</em><br>个人觉得AI生成的字数有时不是很准确,主要起到控制一下长度,使得AI更有重点的作用。</li></ul><h5 id="6-多轮对话上下文"><a href="#6-多轮对话上下文" class="headerlink" title="6. 多轮对话上下文"></a>6. <strong>多轮对话上下文</strong></h5><p>在多轮对话中,AI可以记住前面的对话内容,并根据先前的讨论进行扩展。因此,在多轮对话中,不必重复已知的信息,可以直接基于上下文提问。</p><p><strong>示例:</strong></p><ul><li>你可以先问:<em>“解释一下机器学习是什么?”</em></li><li>接着问:<em>“它和深度学习有什么区别?”</em></li><li>AI会基于之前的讨论,直接进行对比和解释。</li></ul><p>国内大部分AI的上下文记忆能力都堪忧,所以建议你说句“结合上文”来让AI重复学习前面的内容,以免遗忘,性能不好的AI,大概几轮对话下来它就会忘掉前面所有的,哪怕你再说结合上文AI也纹丝不动,这点也需要在使用时格外注意。</p><h5 id="7-提示设计(Prompt-Engineering)"><a href="#7-提示设计(Prompt-Engineering)" class="headerlink" title="7. 提示设计(Prompt Engineering)"></a>7. <strong>提示设计(Prompt Engineering)</strong></h5><p>提示设计是影响AI生成内容质量的核心技巧之一。你可以通过优化提示词,引导AI给出更符合预期的答案。</p><ul><li><p><strong>提供示例</strong>:有时提供示例可以更好地引导AI生成类似风格的内容。<br><strong>示例:</strong> <em>“写一个类似于这段话的故事开头:‘在一座古老的森林里,一位年轻的猎人迷失了方向。’”</em></p></li><li><p><strong>给出角色或身份</strong>:让AI以特定身份回答问题,可以生成不同风格或视角的内容。<br><strong>示例:</strong> <em>“假装你是一位历史学家,解释一下罗马帝国的衰落。”</em></p></li></ul><p>相信大家都刷到过prompt提高AI回答效果的文章或者信息。我对于prompt最初的印象,大约就是AI绘画,高三紧迫的时刻刷着魔法宝典学习各种咒语以求绘制符合要求的精美的图,似乎就在不久之前,而如今AI已经走进千家万户,个人认为,将prompt用到极致的大概就是AI绘画了。</p><p>其特点大概就是,极致准确的关键词,明确的重点,合理的提问顺序安排。</p><p>而人设法,应该都听烂了吧,在此不多赘述,感兴趣可以查阅相关信息。</p><h5 id="8-调整“温度”与“最大token”"><a href="#8-调整“温度”与“最大token”" class="headerlink" title="8. 调整“温度”与“最大token”"></a>8. <strong>调整“温度”与“最大token”</strong></h5><p>如果使用API进行调用,你可以通过调整参数(如温度和最大token数)来控制生成内容的多样性和长度。</p><ul><li><strong>温度(temperature)</strong>:调整生成的随机性。值越高,生成的内容越随机;值越低,内容越确定。<ul><li>示例:将温度设为0.8时,输出会更具创意;将温度设为0.2时,输出会更加保守和严谨。</li></ul></li><li><strong>最大token(max_tokens)</strong>:控制生成内容的长度。增加token数可生成更多内容,减少则生成较短的回答。</li></ul><p>部署页面一般会提供,感兴趣查阅相关的</p><h5 id="9-连续优化"><a href="#9-连续优化" class="headerlink" title="9. 连续优化"></a>9. <strong>连续优化</strong></h5><p>有时你可能会对AI的初次回复不满意,可以根据其生成的内容进行进一步的优化,通过追加问题或重新描述提问方式获得更好的结果。</p><p><strong>示例:</strong></p><ul><li>初始提问:<em>“帮我写一个介绍环保的段落。”</em></li><li>回复后:<em>“这段文字过于技术化了,能用更简单的语言改写一下吗?”</em></li><li>优化提问:<em>“请用通俗易懂的语言为初中生解释环保的重要性。”</em></li></ul><p>有时候一直问得可能会红温,人工智障总爱走极端,慎用吧,有时效果也许不错。</p><h5 id="总结:"><a href="#总结:" class="headerlink" title="总结:"></a>总结:</h5><p>与AI的互动不仅仅是简单提问,提示设计和问题结构会极大影响生成结果。通过提供足够的上下文信息、明确的目标、控制输出的格式和长度、分解复杂任务,甚至多轮对话的引导,你可以显著提升AI的回答质量和实用性。</p><h2 id="用AI部署一个AI"><a href="#用AI部署一个AI" class="headerlink" title="用AI部署一个AI"></a><strong>用AI部署一个AI</strong></h2><p>在我向chatGPT提问如何写一个分享使用AI技巧的博客时,GPT向我推荐了部署一个调用chatGPT的API的应用的项目,只能说不愧是自家的产品,这广告打得真有水平。</p><p>好,接下来我们的任务就是,用openAI的API部署一个可以进行对话的web应用。</p><p><em>ps:以下内容纯新手向,几乎不需要你有任何基础,如果有我没提到但是你不懂的,问AI就对了。</em></p><p>很多人可能要摸不着头脑了,这要怎么做呢?我也不会搭页面啊,我也完全没思路啊,要怎么问AI比较好呢?很简单,我们直接问—— </p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019155807712.png" alt="image-20241019155807712"></p><p>我向AI直接表达了我的需求如上,说明了我要做的事情,要求它告诉我怎么做,以及所有具体的实现。</p><p>我们有时候可能会想到底要怎么做怎么问AI,但其实最重要的是直接问,让AI给你一个回答,也许这个回答你并不满意,很多也看不懂,但是最重要的是——我大概知道我需要干什么了,或者说,我可以通过它提供的步骤,知道我不知道什么。</p><p>gpt给了我如下回答:(代码部分没放)</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019160221053.png" alt="image-20241019160221053"></p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019160249354.png" alt="image-20241019160249354"></p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019160349753.png" alt="image-20241019160349753"></p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019160402760.png" alt="image-20241019160402760"></p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019160415931.png" alt="image-20241019160415931"></p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019160423823.png" alt="image-20241019160423823"></p><p>ok,接下来我们就好好读读它的回答。</p><p>我们可以看到,其实主要就做两件事,第一是做一个前端页面,也就是一个用于展示对话的页面,使用的语言是HTML和js,再构建后端API即可。</p><p>有的人可能就疑惑了,什么是前端什么是后端,什么是API,什么又是Node.js,什么又是Experss。</p><p>来,我们继续问AI——</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019160939815.png" alt="image-20241019160939815"></p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019160928621.png" alt="image-20241019160928621"></p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019160956804.png" alt="image-20241019160956804"></p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019161007383.png" alt="image-20241019161007383"></p><p>怎么办,还是好多看不明白的东西,但是总有你能明白的吧,对,我们的原则就是——看不懂的狂问AI(<em>你甚至可以开两个AI,一个专门负责给你解释各类名词教会你,另一个负责进行具体项目的指导,把AI当成老师,你当成最不耻下问的学生,提问的方式只要表达清晰就可以,其他花哨方法等到没有特别好回答时候再用即可</em>)</p><p>然后,你的知识库就多了很多名词,有的不理解,问了也不理解,没关系,只要你有这个印象,以后总会遇到的,说不定就懂了,我们的能力就是在这种一次次不理解和一次次看着不懂的东西里成长起来的。</p><p><strong>最重要的是,我们要直面这些海量的无法理解的东西,理解能理解的,想办法理解不能理解的——这就是自主学习,这就是高中课堂无法教会你的。</strong></p><p>没人可以告诉你,这些你是否应该理解或者你的能力是否能知道,这些全都交给你自己去判断,这就是为什么,同样的AI在不同人手中有不同的效果——总有人能够从中挖掘出自己需要的。</p><p>现在我们大概知道了,就是有一个东西叫node.js,我们需要安装一下,来提供运行环境(原理继续问AI),还有一个东西叫做Experss,是一个框架,也就是工具,我们就要用Experss来做具体的API调用这个事情,它会非常方便。</p><p>好了,接下来我们就照着gpt老师的方法开干——</p><p>首先要有一个API密钥,大家通过前面提到的途径,假设已经有了这么一个访问API的密钥。</p><p>接着继续第二步,安装node.js,这个简单,直接找官网。</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019162500417.png" alt="image-20241019162500417"></p><p><em>官网还挺好看的</em></p><p>直接下载安装,这些都简单。基本就是download,目录,next,next,next…finish这类流程,多安装一些就熟悉了。</p><p>安装完之后继续按照说明进行。</p><p>gpt给了我们一些命令,需要在命令行窗口运行,这个应该都会打开吧。各类命令有一定规律,我们可以读一下命令,理解大概意思,这里主要是创建文件夹,进入目录,安装包管理器并确认。如果看不懂直接问AI,关于命令语法,感兴趣可以学一下。</p><p>在此我们直接打开终端,cv命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">mkdir</span> openai-chatbot<br><span class="hljs-built_in">cd</span> openai-chatbot<br>npm init -y<br></code></pre></td></tr></table></figure><p>结果如下,输出了一些信息,大概是安装好了,其他看不懂没关系,我们继续。</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019210541520.png" alt="image-20241019210541520"></p><p>依据gpt说的,下一步要安装一些需要的包,它给了一行命令,我们也直接cv进去,等着安装就行。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmake">npm <span class="hljs-keyword">install</span> express openai axios dotenv<br></code></pre></td></tr></table></figure><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019163350924.png" alt="image-20241019163350924"></p><p>读一下输出,没有报错,看样子也是成功安好了。那我们继续——</p><p>gpt让我们在项目根目录创建一个.env文件,等等,.env文件是什么?怎么创建?于是我们复制粘贴这一步到输入框进行提问,来请教gpt老师。</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019164159693.png" alt="image-20241019164159693"></p><p>gpt老师说:</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019164240092.png" alt="image-20241019164240092"></p><p>ok,这下我们明白了,直接在之前创建的openai-chatbot文件夹里创建文件就行,我们的命令行输入提示前有文件夹路径,看到是openai-chatbot,说明我们就在根目录,这就没问题了,我我们继续。</p><p>gpt老师给我们解释了一下这个文件干什么的(环境变量是什么?不知道的话不如问问AI试试看呢!),接着提供了详细的操作步骤。</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019164926145.png" alt="image-20241019164926145"></p><p>但是当我们兴冲冲输入命令,却发现报错了?</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019165447399.png" alt="image-20241019165447399"></p><p>我们继续问gpt老师这是为什么,gpt老师说:</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019165510520.png" alt="image-20241019165510520"></p><p>原来不同操作系统的命令有的地方也不同啊,我们又知道了很多东西,加深了理解。gpt老师将功补过,给了我们一个新方法,我们cv进去。</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd"><span class="hljs-built_in">echo</span> > .env<br></code></pre></td></tr></table></figure><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019165619124.png" alt="image-20241019165619124"></p><p>看样子也是创建好了,接下来我们打开.env文件并编辑。</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019170003163.png" alt="image-20241019170003163"></p><p><em>ps:不要完全无脑照抄哦,右边需要存储你的密钥,而不是原本代码里的your_openai_api_key,这里我放上了我的密钥</em>,.env文件我采用记事本打开,vscode等代码编辑器都可以。</p><p>接着编写创建服务器代码,需要在根目录下创建serve.js文件,并添加代码,我采用vscode创建文件添加代码,也可以使用命令行创建文件,用记事本打开再添加代码。(命令行代码和具体操作也可以问AI,流程如上)</p><p>添加AI写的代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 导入依赖</span><br><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);<br><span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>);<br><span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>);<br><br><span class="hljs-comment">// 读取 .env 文件中的配置信息</span><br>dotenv.<span class="hljs-title function_">config</span>();<br><br><span class="hljs-keyword">const</span> app = <span class="hljs-title function_">express</span>();<br><span class="hljs-keyword">const</span> port = <span class="hljs-number">3000</span>;<br><br><span class="hljs-comment">// 解析 JSON 请求体</span><br>app.<span class="hljs-title function_">use</span>(express.<span class="hljs-title function_">json</span>());<br><br><span class="hljs-comment">// OpenAI API URL 和密钥</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">OPENAI_API_URL</span> = <span class="hljs-string">'https://api.openai.com/v1/chat/completions'</span>;<br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">OPENAI_API_KEY</span> = process.<span class="hljs-property">env</span>.<span class="hljs-property">OPENAI_API_KEY</span>;<br><br><span class="hljs-comment">// 聊天接口</span><br>app.<span class="hljs-title function_">post</span>(<span class="hljs-string">'/chat'</span>, <span class="hljs-title function_">async</span> (req, res) => {<br> <span class="hljs-keyword">const</span> { message } = req.<span class="hljs-property">body</span>;<br><br> <span class="hljs-keyword">if</span> (!message) {<br> <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Message is required'</span> });<br> }<br><br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.<span class="hljs-title function_">post</span>(<br> <span class="hljs-variable constant_">OPENAI_API_URL</span>,<br> {<br> <span class="hljs-attr">model</span>: <span class="hljs-string">'gpt-3.5-turbo'</span>, <span class="hljs-comment">// 选择模型</span><br> <span class="hljs-attr">messages</span>: [{ <span class="hljs-attr">role</span>: <span class="hljs-string">'user'</span>, <span class="hljs-attr">content</span>: message }]<br> },<br> {<br> <span class="hljs-attr">headers</span>: {<br> <span class="hljs-string">'Authorization'</span>: <span class="hljs-string">`Bearer <span class="hljs-subst">${OPENAI_API_KEY}</span>`</span>,<br> <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,<br> }<br> }<br> );<br><br> <span class="hljs-keyword">const</span> chatResponse = response.<span class="hljs-property">data</span>.<span class="hljs-property">choices</span>[<span class="hljs-number">0</span>].<span class="hljs-property">message</span>.<span class="hljs-property">content</span>;<br> res.<span class="hljs-title function_">json</span>({ <span class="hljs-attr">response</span>: chatResponse });<br><br> } <span class="hljs-keyword">catch</span> (error) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(error);<br> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">500</span>).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Error calling OpenAI API'</span> });<br> }<br>});<br><br><span class="hljs-comment">// 启动服务器</span><br>app.<span class="hljs-title function_">listen</span>(port, <span class="hljs-function">() =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Server is running on http://localhost:<span class="hljs-subst">${port}</span>`</span>);<br>});<br></code></pre></td></tr></table></figure><p>注意,上述的API_URL是openai官网提供的,购买时应该选直连官网的API。密钥就是购买后给你的一连串乱七八糟的字符,已经存到.env文件里了。</p><p>vscode添加后:</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019203711078.png" alt="image-20241019203711078"></p><p>上述代码感兴趣的话,可以一句一句问AI让它解释,有C++语法基础的话,直接看代码应该也不是很难理解,尽管不知道语法,<strong>但是适当阅读一下代码,理解大概意思,判断是否可以直接套用,是否需要修改部分细节也是必要的,我们可以不会写完整的代码,但是也需要“动着脑子抄”</strong>。</p><p>接着,再创建public文件夹,在里面添加html文件,并向文件中添加代码。</p><p>上一步是创建了一个服务器,这一步则是写一个网页。我这里也采用vscode进行文件创建和编写,同样,命令行创建,记事本打开进行编辑也是ok的,在此不多赘述。</p><p>添加代码如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-meta"><!DOCTYPE <span class="hljs-keyword">html</span>></span><br><span class="hljs-tag"><<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>></span><br><span class="hljs-tag"><<span class="hljs-name">head</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">title</span>></span>OpenAI Chatbot<span class="hljs-tag"></<span class="hljs-name">title</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">style</span>></span><span class="language-css"></span><br><span class="language-css"> <span class="hljs-selector-tag">body</span> { <span class="hljs-attribute">font-family</span>: Arial, sans-serif; }</span><br><span class="language-css"> <span class="hljs-selector-id">#chatbox</span> { <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>; <span class="hljs-attribute">height</span>: <span class="hljs-number">300px</span>; <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>; <span class="hljs-attribute">overflow-y</span>: scroll; }</span><br><span class="language-css"> <span class="hljs-selector-id">#user-input</span> { <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>; }</span><br><span class="language-css"> </span><span class="hljs-tag"></<span class="hljs-name">style</span>></span><br><span class="hljs-tag"></<span class="hljs-name">head</span>></span><br><span class="hljs-tag"><<span class="hljs-name">body</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">h1</span>></span>Chat with OpenAI<span class="hljs-tag"></<span class="hljs-name">h1</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"chatbox"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"user-input"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Type your message here"</span> /></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"sendMessage()"</span>></span>Send<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br><br> <span class="hljs-tag"><<span class="hljs-name">script</span>></span><span class="language-javascript"></span><br><span class="language-javascript"> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">sendMessage</span>(<span class="hljs-params"></span>) {</span><br><span class="language-javascript"> <span class="hljs-keyword">const</span> message = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'user-input'</span>).<span class="hljs-property">value</span>;</span><br><span class="language-javascript"> <span class="hljs-keyword">if</span> (!message) <span class="hljs-keyword">return</span>;</span><br><span class="language-javascript"></span><br><span class="language-javascript"> <span class="hljs-comment">// 更新聊天框</span></span><br><span class="language-javascript"> <span class="hljs-keyword">const</span> chatbox = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'chatbox'</span>);</span><br><span class="language-javascript"> chatbox.<span class="hljs-property">innerHTML</span> += <span class="hljs-string">`<p><strong>You:</strong> <span class="hljs-subst">${message}</span></p>`</span>;</span><br><span class="language-javascript"></span><br><span class="language-javascript"> <span class="hljs-comment">// 发送请求到后端</span></span><br><span class="language-javascript"> <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">'/chat'</span>, {</span><br><span class="language-javascript"> <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,</span><br><span class="language-javascript"> <span class="hljs-attr">headers</span>: { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span> },</span><br><span class="language-javascript"> <span class="hljs-attr">body</span>: <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({ message })</span><br><span class="language-javascript"> });</span><br><span class="language-javascript"></span><br><span class="language-javascript"> <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.<span class="hljs-title function_">json</span>();</span><br><span class="language-javascript"> <span class="hljs-keyword">const</span> reply = data.<span class="hljs-property">response</span>;</span><br><span class="language-javascript"></span><br><span class="language-javascript"> <span class="hljs-comment">// 显示 OpenAI 的回复</span></span><br><span class="language-javascript"> chatbox.<span class="hljs-property">innerHTML</span> += <span class="hljs-string">`<p><strong>AI:</strong> <span class="hljs-subst">${reply}</span></p>`</span>;</span><br><span class="language-javascript"> chatbox.<span class="hljs-property">scrollTop</span> = chatbox.<span class="hljs-property">scrollHeight</span>;</span><br><span class="language-javascript"></span><br><span class="language-javascript"> <span class="hljs-comment">// 清空输入框</span></span><br><span class="language-javascript"> <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'user-input'</span>).<span class="hljs-property">value</span> = <span class="hljs-string">''</span>;</span><br><span class="language-javascript"> }</span><br><span class="language-javascript"> </span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br><span class="hljs-tag"></<span class="hljs-name">body</span>></span><br><span class="hljs-tag"></<span class="hljs-name">html</span>></span><br></code></pre></td></tr></table></figure><p>由于这段代码是写了一个网页页面,所以不牵扯什么具体需要改的东西,我们不需要实现很好看的页面,只要有这么一个页面能用就行了,如果想写出更好看的页面,也可以阅读代码,让AI进行修改(用AI写前端的体验真的很无敌)。</p><p>搜一下前端相关的知识,大概了解一下有哪些语言,然后就一通描述,交给AI实现,你看看对不对就行。</p><p>这里我们不对页面进行修改了,直接下一步。</p><p>vscode添加后:</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019203738689.png" alt="image-20241019203738689"></p><p>我们写了一个页面,也写了服务器,这两个步骤该怎么连起来呢?gpt老师也告诉我们了,直接打开server.js文件,加入静态文件托管即可。</p><p>在文件末尾添加代码:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">app.<span class="hljs-keyword">use</span>(express.<span class="hljs-built_in">static</span>(<span class="hljs-string">'public'</span>));<br></code></pre></td></tr></table></figure><p>ok,所有的代码编写就完成了,接着进行最后一步,启动服务器,就可以登上我们的网页了!</p><p>终端中输入:</p><figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs crmsh"><span class="hljs-keyword">node</span> <span class="hljs-title">server</span>.js<br></code></pre></td></tr></table></figure><p>显示如下:</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019203906683.png" alt="image-20241019203906683"></p><p>意思大概就是我们的服务器正在运行,按住ctrl右键网址,就可以打开页面啦!</p><p>看看我们的页面长什么样~</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019204004353.png" alt="image-20241019204004353"></p><p>嗯…似乎有些简陋,但是也可以用,不错,我们来试试能否和AI对话。</p><p><img src="/./%E7%88%B1%E4%B8%8E%E9%AD%94%E6%B3%95/image-20241019204131876.png" alt="image-20241019204131876"></p><p>ok,AI正常回复啦!</p><p>至此,我们已经实现了构建了一个简单的多文件小项目,搭建一个简单的服务器,在一个自己的网页上部署了我们的chatBot,并调用API进行连接对话。</p><p>而我们甚至不理解服务器原理,不会js和html!仅仅是使用AI,花费一点点时间,就已经能实现这样的效果了。这放在几年前,简直是无法想象的事情,我们跳过了大量的学习时间,直接以最快的路径走向了我们需要的地方。</p><p>诚然,基础知识仍然是必要的,但是我们不再需要花费大量精力熟练语法或debug,也不再需要花费大量精力编写重复的代码,我们的注意力可以自然而然放在代码的思路和架构,放在背后的原理上,解放了多少时间!</p><p>而在手动做一个小项目的基础上,我们对很多东西都有了一个了解,又能学到很多东西,往后再看相关内容,也会有深刻印象,学起来更轻松。</p><p>这句话毫不夸张,AI几乎可以帮你做到你想做的任何事情。</p><p>我们必须遗憾地承认,随着大量工具的开发,程序员的门槛在不断降低,造轮子的效益和意义也无限下降,但是这并不意味着死路一条,相反,我们更要拥抱新技术,搭上时代的快车,只有懂得运用工具,敢于创新,不断学习的人,才有底气和能力实现自己想做的事情。</p><p>我希望以上内容能帮助大家体会到使用AI工具的一个简单思路和方法——把它当最任劳任怨的工人,最耐心教导的老师,而我们是背后的操纵者设计者,也是最不耻下问的学生。</p><p>还等什么呢?今天就可以开始,打开AI,朝着你想做的事情出发!</p><p>我们要体悟着时代,而不是被洪流裹挟,最后,以我非常喜欢的一句话作结。</p><p><strong>The Best of Times, The Worst of times</strong></p><p><em>以上内容若有不理解的地方,欢迎与我讨论!</em></p>]]></content>
<categories>
<category>科普</category>
</categories>
<tags>
<tag>AI</tag>
</tags>
</entry>
<entry>
<title>面向新接触计算机的同学的计算机基础知识科普(一)</title>
<link href="/%E9%9D%A2%E5%90%91%E6%96%B0%E6%8E%A5%E8%A7%A6%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%9A%84%E5%90%8C%E5%AD%A6%E7%9A%84%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%A7%91%E6%99%AE%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<url>/%E9%9D%A2%E5%90%91%E6%96%B0%E6%8E%A5%E8%A7%A6%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%9A%84%E5%90%8C%E5%AD%A6%E7%9A%84%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%A7%91%E6%99%AE%EF%BC%88%E4%B8%80%EF%BC%89/</url>
<content type="html"><![CDATA[<h1 id="文件系统"><a href="#文件系统" class="headerlink" title="文件系统"></a>文件系统</h1><blockquote><p>文件就是字节序列,仅此而已。每个 IO 设备,包括磁盘、键盘、显示器、甚至网络,都可以看成是文件。</p></blockquote><p>你手上的笔记本电脑,本质上就是一个文件系统,由文件和管理文件的工具组成。</p><p>因此我们将从 “文件” 这个基本概念,开始我们的计算机基础知识科普。</p><h2 id="路径"><a href="#路径" class="headerlink" title="路径"></a>路径</h2><p>每一个文件都被放在计算机中一个特定的地方,而 “路径” 用来描述这个地方具体在哪里。</p><p><strong>“路径” 二字的中文意思会影响你对它的理解</strong>。总之,用 “坐标” 的中文意思来理解 “路径” 这个<strong>计算机概念</strong>会更合适。</p><p><em>下文中 “路径” 都指计算机概念里的 ”路径“。</em></p><p>做个类比,有人问我 “梅园宿舍” 在哪里,我会说 “九龙湖校区东北角”,这里 “九龙湖校区东北角” 就是 “梅园宿舍” 的路径。</p><p>但是,假设现在我在 “梅园快递中心”,又有人问我 “梅园宿舍” 在哪里,我更可能会说 “顺着两江东路往南走,在前面的路口左拐”,这里 “顺着两江东路往南走,在前面的路口左拐” <strong>也是 “梅园宿舍” 的路径</strong>。</p><p>在计算机中,<strong>文件的路径有两种</strong>,分别是 <strong>“相对路径”</strong> 和 **”绝对路径”**,对应我们刚刚梅园宿舍的例子。</p><p>很显然,绝对路径不管你身在何方,都不会变化,而相对路径可能会随着你所在位置的变化而变化。</p><p>“九龙湖校区东北角” 是 “梅园宿舍” 的绝对路径。</p><p>“顺着两江东路往南走,在前面的路口左拐” 是<strong>当前位于 “梅园快递中心”</strong> 时,”梅园宿舍” 的相对路径。</p><h2 id="终端"><a href="#终端" class="headerlink" title="终端"></a>终端</h2><p>影视作品中的计算机高手们,似乎都在使用一个黑色的框框操作计算机。这个黑色的框框,我们称之为 **”终端 (Terminal)”**。</p><p>作为一个计算机专业的学生,你也有必要学会高手们的玩法。</p><p>当然,使用终端不仅仅是为了帅,而是切切实实的有很多好处:</p><ul><li><p>熟练以后使用终端进行操作比使用图形化界面操作要快很多。</p></li><li><p>由于制作图形化界面的成本太高,你会接触到很多<strong>只能通过终端使用</strong>的软件。</p></li></ul><p><em>下图是一个终端:</em></p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101531697.png" alt="image-20241010153117599"></p><h3 id="打开终端"><a href="#打开终端" class="headerlink" title="打开终端"></a>打开终端</h3><p>打开终端的办法挺多的,常用的有以下几种:</p><ol><li>按下 “Win徽标” + “r” 键先打开 “运行”,输入 “PowerShell”,按下回车。</li><li>在桌面或者一个文件夹的空白部分右键,选择 “在终端中打开”(Windows11 操作系统才可以这样做)。</li><li>在 Windows 搜索框中输入 “PowerShell”,选择 “Windows PowerShell” 并打开。</li></ol><h2 id="工作目录"><a href="#工作目录" class="headerlink" title="工作目录"></a>工作目录</h2><p>工作目录也叫工作路径,<strong>它就是你在计算机系统中的位置</strong>。</p><p>目录本质是一个<strong>文件夹</strong>。</p><p>在上面的例子中,我的工作目录是 “梅园快递中心”,”梅园宿舍” 的<strong>相对路径</strong>是 “顺着两江东路往南走,在前面的路口左拐”。</p><p><em>只要没有特别说明,”相对路径” 都指相对于工作目录的路径。</em></p><p>终端会显示工作目录,例如上图,最后一行是:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">PS C:\Users\huany><br></code></pre></td></tr></table></figure><p>其中:</p><ul><li>“PS” 是 “PowerShell” 的简写。</li><li>“C:\Users\huany” 是工作目录/工作路径。(”huany” 是我电脑的用户名,你的电脑用户名可能是其它的)</li><li>‘>’ 是一个提示符,作用是分割前后的内容。</li></ul><p>用上文中第二种方法打开终端时,初始工作目录就是桌面或者对应的文件夹,用其它两种方法打开时,初始工作目录被称为<strong>用户目录</strong>。</p><h3 id="切换工作目录"><a href="#切换工作目录" class="headerlink" title="切换工作目录"></a>切换工作目录</h3><p>我们用 <code>cd</code> 命令来切换工作目录,以下图为例:</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101709591.png" alt="image-20241010170941510"></p><p>例如,在上图靠上的文件夹,用第二种方式打开终端,我们当前的工作目录是 <code>C:\Users\huany\Desktop\英语四级真题</code>。(这个目录对应了电脑桌面上的 “英语四级真题文件夹”)</p><p>现在希望把工作目录切换到 2021 这个文件夹下。</p><p>这个文件夹的相对路径是 <code>2021</code>,绝对路径是 <code>C:\Users\huany\Desktop\英语四级真题\2021</code>。</p><p>在终端中输入<strong>以下两行命令中的任意一行</strong>都可以改变工作目录。</p><p><em>命令输入完成后按下回车执行这条命令。</em></p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">cd</span> <span class="hljs-number">2021</span><br><span class="hljs-attribute">cd</span> C:\Users\huany\Desktop\英语四级真题\<span class="hljs-number">2021</span><br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101718947.png" alt="image-20241010171825910"></p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101719998.png" alt="image-20241010171901967"></p><p>如果我们此时的工作目录在另外的地方,又该怎么办?</p><p>假设我们在桌面打开了终端,此时的工作目录是 <code>C:\Users\huany\Desktop</code>。</p><p>尝试上面的两行命令:</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101722529.png" alt="image-20241010172206488"></p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101722970.png" alt="image-20241010172218933"></p><p>第一个命令执行失败了,出现了红色的文字,提示发生错误。</p><p>第二个命令能够正常执行。</p><p><em>一般来说,终端中的红色文字出现时表明程序执行发生了错误,阅读这部分文字可以知道错误是什么。</em></p><p>第一条命令使用的是之前的相对路径,<strong>但由于我们的工作目录发生了改变</strong>,所以这个相对路径不再正确,自然无法执行成功。</p><p>第二条命令使用了绝对路径,<strong>绝对路径不会随工作目录而改变</strong>,所以能够执行成功。</p><p>工作目录是 <code>C:\Users\huany\Desktop</code> 时,<code>C:\Users\huany\Desktop\英语四级真题\2021</code> 文件夹的相对路径是:<code>英语四级真题\2021</code>,所以使用以下命令就可以完成切换。</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">cd</span> 英语四级真题\<span class="hljs-number">2021</span><br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101727547.png" alt="image-20241010172716511"></p><p>此时我们的工作目录是 <code>C:\Users\huany\Desktop\英语四级真题\2021</code>,我们想查看 2022 年的真题,又该怎么切换工作目录过去呢?</p><ol><li><p>用绝对路径:这个不难吧。</p></li><li><p>用相对路径:</p><p>此时有一个问题,从 <code>C:\Users\huany\Desktop\英语四级真题</code> 到<code>C:\Users\huany\Desktop\英语四级真题\2021</code> 时,后者是前者的一个子文件夹,直接用文件夹名字表示相对路径就行了, 那么,应该如何表示父文件夹的相对路径呢?</p><p>我们用 <code>..</code> 表示父文件夹的相对路径,例如,工作目录是 <code>C:\Users\huany\Desktop\英语四级真题\2021</code> 时, <code>C:\Users\huany\Desktop\英语四级真题</code> 的相对路径表示为 <code>..</code>。</p><p>执行 <code>cd ..</code> 可以切换回 <code>C:\Users\huany\Desktop\英语四级真题</code> :</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101732357.png" alt="image-20241010173240318"></p><p>再执行一个 <code>cd</code> 命令就可以切换到 <code>C:\Users\huany\Desktop\英语四级真题\2022</code> 了。 </p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101734945.png" alt="image-20241010173405903"></p><p>我们也可以一步到位:</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410101733329.png" alt="image-20241010173323295"></p></li></ol><h4 id="cd-命令的格式"><a href="#cd-命令的格式" class="headerlink" title="cd 命令的格式"></a>cd 命令的格式</h4><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">cd</span><span class="hljs-meta"> [绝对路径或者相对路径]</span><br></code></pre></td></tr></table></figure><p><em>在这条命令中,如果路径中含有空格,需要把路径用双引号括起来。</em></p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121628855.png" alt="image-20241012162830804"></p><h4 id="路径的格式"><a href="#路径的格式" class="headerlink" title="路径的格式"></a>路径的格式</h4><ul><li>绝对路径:<code>[盘符]:\[第一级目录]\[第二级目录]\[第三级目录]\[文件名(filename)]</code></li><li>相对路径:<code>[第一级目录]\[第二级目录]\[文件名(filename)]</code><ul><li>用 <code>..</code> 表示父文件夹。</li></ul></li></ul><h4 id="文件和目录"><a href="#文件和目录" class="headerlink" title="文件和目录"></a>文件和目录</h4><p>一个路径,它所描述的对象要么是一个目录(文件夹),要么是一个文件。</p><p>还是以上面的图片为例,路径 <code>C:\Users\huany\Desktop\英语四级真题\2021</code> 的对象是一个目录(文件夹),路径 <code>C:\Users\huany\Desktop\英语四级真题\2021\3.jpg</code> 的对象是一个文件。</p><p>显然,由于工作目录必须是一个目录(文件夹),所以 <code>cd</code> 命令的第二个路径参数只能是 “文件夹” 的路径。</p><h4 id="文件的拓展名"><a href="#文件的拓展名" class="headerlink" title="文件的拓展名"></a>文件的拓展名</h4><p>文件名(filename)由<strong>文件名(base name)</strong>和<strong>拓展名</strong>两部分,以 <code>C:\Users\huany\Desktop\英语四级真题\2021\3.jpg</code> 为例,它的文件名(filename)是 <code>3.jpg</code>,文件名(base name)是 <code>3</code>,拓展名是 <code>.jpg</code>。</p><ul><li><a href="https://www.cnblogs.com/xiaoheidiannao/p/12430346.html">什么是<strong>文件拓展名</strong></a></li><li><a href="https://www.bilibili.com/read/cv23705186/"><strong>怎么查看文件拓展名</strong></a></li></ul><h4 id="用终端打开文件"><a href="#用终端打开文件" class="headerlink" title="用终端打开文件"></a>用终端打开文件</h4><p>在终端中输入文件的<strong>路径</strong>,就可以直接打开这个文件。</p><p><em>如果没有特别说明,”路径” 既可以是绝对路径,也可以是相对路径。</em></p><h2 id="系统路径"><a href="#系统路径" class="headerlink" title="系统路径"></a>系统路径</h2><p><em>在网上的部分教程中,这个概念和 “环境变量” 是混为一谈的,实际上,“系统路径” 是 “环境变量” 之一,但对于不是程序员的人来说,唯一会用到的 “环境变量” 就是 “系统路径” 了。</em></p><p>我们知道用终端可以打开文件,而软件是文件的一种,同样可以用终端打开。</p><p>我电脑上,软件 “计算器” 所在的路径是 <code>C:\Windows\System32\calc.exe</code>,难道我每次打开计算器,都需要把这么长一段路径输入进去吗?</p><p>答案是否定的:</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121026028.png" alt="image-20241012102607962"></p><p>只需要输入 <code>calc</code> 就可以打开 <code>C:\Windows\System32\calc.exe</code> 这个文件,我们发现两件事情:</p><ul><li>没有输入文件拓展名:<strong>直接打开</strong> <code>.bat</code> 和 <code>.exe</code> 为拓展名的文件时,文件名(filename)不需要包含拓展名,只需要输入文件名(base name)。</li><li>只输入了文件名(filename),没有输入路径:该文件所在的目录(文件夹)被加入了 “系统路径”。</li></ul><p>当输入一个文件名(filename/base name)时,操作系统会根据以下规则尝试寻找这个文件:</p><ol><li>将该文件名直接作为相对路径查找对应文件。</li><li>按顺序在每一个系统路径项下查找拥有该文件名(filename/base name)的文件。</li></ol><p>注意,如果输入的子串中包含目录,例如 <code>System32/calc.exe</code>,那么它不会被识别为文件名,自然也不会在系统路径中查找。</p><p>请看下面的例子:</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121635876.png" alt="image-20241012163555786"></p><p><em><code>bin</code> 文件夹的路径被加入了系统路径,但 <code>123</code> 文件夹中的文件不能通过 <code>123/文件名</code> 的方式在其它地方访问。而 <code>g++.exe</code> 可以在任何地方输入 <code>g++.exe</code> 打开。</em></p><h3 id="如何添加系统路径"><a href="#如何添加系统路径" class="headerlink" title="如何添加系统路径"></a>如何添加系统路径</h3><p>快捷键 Win+R,输入”system.cpl” 后回车。</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121039468.png" alt="image-20241012103937390"></p><p>会打开系统配置界面,然后点击高级,再点击环境变量。</p><p><a href="https://pythonjishu.com/wp-content/uploads/2022/10/Snipaste_2022-10-30_19-38-46.jpg"><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121039547.jpeg" alt="怎么把Python添加到环境变量中?"></a></p><p>打开环境变量之后,上方为当前的用户变量,下方为系统全局变量。我们选中用户变量的 Path 变量,点击编辑。</p><p><a href="https://pythonjishu.com/wp-content/uploads/2022/10/Snipaste_2022-10-30_19-42-23.jpg"><img src="https://pythonjishu.com/wp-content/uploads/2022/10/Snipaste_2022-10-30_19-42-23.jpg" alt="怎么把Python添加到环境变量中?"></a></p><p>点击新建按钮,可以增加新的系统路径。</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121042751.png" alt="image-20241012104239693"></p><p>一路点击确定,完成添加。</p><p><em>完成添加后,需要重新启动终端,才能使系统路径在终端中生效。</em></p><p>*点击上图中下方的系统变量 Path,可以看到有一项是 <code>C:\Windows\System32</code>*。</p><h2 id="实践:用终端来编译并运行你的-C-程序"><a href="#实践:用终端来编译并运行你的-C-程序" class="headerlink" title="实践:用终端来编译并运行你的 C++ 程序"></a>实践:用终端来编译并运行你的 C++ 程序</h2><p>不会还有人不知道 C++ 代码要<strong>先编译成可执行文件</strong>才能运行吧。</p><p><del>不会吧不会吧</del></p><h3 id="MSVC"><a href="#MSVC" class="headerlink" title="MSVC"></a>MSVC</h3><p>C++ 课老师第一节课喊你下载的十多个GB的 Visual Studio 自带了一个 C++ 的编译器,它的名字是 MSVC,你在 Visual Studio 中运行 C++ 代码的时候就是用它编译的。</p><h3 id="MinGW-套件"><a href="#MinGW-套件" class="headerlink" title="MinGW 套件"></a>MinGW 套件</h3><p>由于一些原因,<strong>你不应该在除了 Visual Studio 之外的地方使用 MSVC</strong>,相信打开 Visual Studio 的用时也让你苦不堪言。</p><p>所以你应该下载一个 MinGW 套件,这里面含有一个名叫 g++ 的软件,可以用来编译 C++ 代码。</p><p><a href="https://files.1f0.de/mingw/mingw-w64-gcc-9.3-stable-r32.7z">点我下载</a></p><h3 id="尝试编译"><a href="#尝试编译" class="headerlink" title="尝试编译"></a>尝试编译</h3><p>将下载的压缩包解压为一个文件夹,这个文件夹中有一个子文件夹 <code>bin</code>,<code>bin</code> 文件夹下有一个 <code>g++.exe</code>,<code>g++.exe</code> 就是编译 C++ 代码的直接工具。</p><p>首先我们在桌面上写一份代码,将它保存为 <code>hello.cpp</code>。</p><p><em>你可以打开记事本把下面的代码复制进去,然后保存到桌面。</em></p><p><em>注意,记事本保存的文件拓展名可能是 <code>.txt</code>,你需要看看上面的教程,把拓展名改为 <code>.cpp</code>。</em></p><p><em>也有可能保存的时候可以自己写拓展名,你将文件名(filename)输入为 <code>hello.cpp</code> 即可。</em></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span><span class="hljs-string"><bits/stdc++.h></span></span><br><span class="hljs-comment">// 当你使用 MinGW 套件编译时, 只需要 include 这一个文件就可以了。</span><br><span class="hljs-comment">// Visual Studio 配套的 MSVC 没有这个头文件, 无法通过编译!!!</span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span>{<br>cout << <span class="hljs-string">"Hello World\n"</span>;<br><span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在桌面打开终端(或者打开终端后将工作目录切换到桌面),我们可以用以下格式的命令来使用 g++ 编译 C++ 文件:</p><p><code>[g++.exe 的路径] -o [可执行文件路径] [源代码(.cpp)文件的路径] </code>。</p><p><em>第一个内容是程序名,后面的三个内容被成为这个程序的<strong>参数</strong>。程序名和参数,参数和参数之间用空格分开,所以如果参数中包含空格,必须用双引号引起来。</em></p><p>例如,我的 <code>g++</code> 在以下文件夹中,路径是:<code>C:\Users\huany\Desktop\C++ 环境配置入门\mingw-w64-gcc-9.3-stable-r32\bin\g++.exe</code>。</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121653228.png" alt="image-20241012165356096"></p><p>我的源代码放在桌面上,路径是:<code>C:\Users\huany\Desktop\hello.cpp</code>。</p><p>可执行文件是编译后生成的文件,按照习惯,它应该放在源文件所在的目录下,文件名(base name)和源代码文件相同,因此它的路径是 <code>C:\Users\huany\Desktop\hello.exe</code>。</p><p>因此,我们执行以下编译命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">& <span class="hljs-string">"C:\Users\huany\Desktop\C++ 环境配置入门\mingw-w64-gcc-9.3-stable-r32\bin\g++.exe"</span> -o <span class="hljs-string">"C:\Users\huany\Desktop\hello.exe"</span> <span class="hljs-string">"C:\Users\huany\Desktop\hello.cpp"</span><br></code></pre></td></tr></table></figure><p><em>特别的,如果执行的程序的路径被双引号引起来了,必须在命令前面加上字符 <code>&</code>。</em></p><p>执行完这个命令后,桌面上生成了一个文件 <code>hello.exe</code>,我们现在可以用终端执行这个文件了。</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121711196.png" alt="image-20241012171117110"></p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121718966.png" alt="image-20241012171821909"></p><p><em>通过输入相对路径的方法<strong>执行</strong>一个文件时,需要在路径前面加上 <code>./</code>。</em></p><h3 id="运用你的知识来简化命令"><a href="#运用你的知识来简化命令" class="headerlink" title="运用你的知识来简化命令"></a>运用你的知识来简化命令</h3><h4 id="使用相对路径"><a href="#使用相对路径" class="headerlink" title="使用相对路径"></a>使用相对路径</h4><p><em>为了确保你真的执行成功了,请在每次编译生成 <code>hello.exe</code> 并执行它后将它从桌面上删除。</em></p><p>编译一个程序,真的要输入这么长的命令吗,答案是否定的,我们首先可以使用相对路径来减少输入内容,执行这个命令:</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121720745.png" alt="image-20241012172049671"></p><p><em>终端的原则是 “No News is Good News”,如果你执行完一个命令后什么都没输出,那么它大概率执行成功了。</em></p><h4 id="省略-exe-拓展名"><a href="#省略-exe-拓展名" class="headerlink" title="省略 .exe 拓展名"></a>省略 .exe 拓展名</h4><p>我们提到一个文件的拓展名是 <code>.exe</code> 时,可以省略拓展名只写文件名:</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121722603.png" alt="image-20241012172253523"></p><h4 id="使用系统路径"><a href="#使用系统路径" class="headerlink" title="使用系统路径"></a>使用系统路径</h4><p>将 <code>g++.exe</code> 的目录 <code>C:\Users\huany\Desktop\C++ 环境配置入门\mingw-w64-gcc-9.3-stable-r32\bin</code> 加入系统路径,我们就可以像使用计算器一样使用 g++ 了。</p><p><img src="https://raw.githubusercontent.com/huan-yp/image_space/master/img/202410121724499.png" alt="image-20241012172440418"></p>]]></content>
<categories>
<category>科普</category>
</categories>
<tags>
<tag>C/C++</tag>
</tags>
</entry>
<entry>
<title>I++ 俱乐部诚招新成员!</title>
<link href="/I-%E4%BF%B1%E4%B9%90%E9%83%A8%E8%AF%9A%E6%8B%9B%E6%96%B0%E6%88%90%E5%91%98/"/>
<url>/I-%E4%BF%B1%E4%B9%90%E9%83%A8%E8%AF%9A%E6%8B%9B%E6%96%B0%E6%88%90%E5%91%98/</url>
<content type="html"><![CDATA[<p>🎉 <strong>I++ 俱乐部诚招新成员!</strong> 🎉</p><p>🌟 <strong>I++,加你所想,向创造和想象的边界进发!</strong> </p><p>这里是技术和创意的世界!我们有经验丰富的指导老师,有优秀的学长学姐,还有和谐有趣的氛围。无论你是初学者还是有经验的技术爱好者,都可以在俱乐部中找到志同道合的伙伴、获得充分的交流和指导。</p><p>📂<strong>开源文化</strong> </p><p>想要拥抱开源文化,参与开源项目?I++俱乐部提供了进行开源交流的平台,让你能够熟悉Github等开源平台的基础操作,了解开源文化的内涵,并有机会成为各大开源社区的贡献者!</p><p>💻 <strong>计算机图形学</strong> </p><p>你是否被《黑神话:悟空》等游戏精美的游戏画面与光影效果所震撼?如果你想了解背后的三维重建、图像渲染等技术,那么欢迎加入I++俱乐部!在这里,你能够学习并实践计算机图形学技术,打造出令人惊叹的三维世界。</p><p>📚 <strong>程序设计与基础算法</strong> </p><p>想要了解程序设计流程与相关的算法?I++俱乐部会与你一起探索这些技术!从设计思维到编译运行,从编程语言到算法应用,你可以在这里打下扎实的编程基础,迈出成为技术大佬的第一步!</p><p>🌐 <strong>JavaWeb 全栈开发与服务器搭建</strong> </p><p>想成为全领域开发高手?I++俱乐部为你提供从前端到后端,从数据库管理到服务器搭建的交流平台,帮助你掌握 JavaWeb开发的关键技术,打造功能齐全的All in One服务器!</p><p><strong>尽情提出你的想法和兴趣,他们有可能成为俱乐部未来的方向!</strong></p><p>🚀 <strong>加入”I++”俱乐部,探索前沿技术,开创属于你的精彩未来!</strong> </p><p>📱 扫码进群,加入我们~ 🎉</p><p><em><strong>计软智俱乐部联合招新宣讲将在10月9日(周三)举行,进群填写抽奖收集表,就有可能在宣讲现场领取精美小礼品~</strong></em></p><p><img src="/./I-%E4%BF%B1%E4%B9%90%E9%83%A8%E8%AF%9A%E6%8B%9B%E6%96%B0%E6%88%90%E5%91%98/qrcode.jpg" alt="qrcode"></p>]]></content>
<categories>
<category>招新</category>
</categories>
<tags>
<tag>I++Club</tag>
</tags>
</entry>
<entry>
<title>提交文章格式要求</title>
<link href="/%E6%8F%90%E4%BA%A4%E6%96%87%E7%AB%A0%E6%A0%BC%E5%BC%8F%E8%A6%81%E6%B1%82/"/>
<url>/%E6%8F%90%E4%BA%A4%E6%96%87%E7%AB%A0%E6%A0%BC%E5%BC%8F%E8%A6%81%E6%B1%82/</url>
<content type="html"><![CDATA[<h1 id="提交文章格式要求"><a href="#提交文章格式要求" class="headerlink" title="提交文章格式要求"></a>提交文章格式要求</h1><h2 id="front-matter"><a href="#front-matter" class="headerlink" title="front-matter"></a>front-matter</h2><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs markdown">---<br>title: {{ title }}<br>date: {{ date }}<br>tags:<br>categories: <br>author: <br>index<span class="hljs-emphasis">_img: </span><br><span class="hljs-emphasis">banner_</span>img: <br>---<br></code></pre></td></tr></table></figure><ul><li><p>title直接跟字符串名称,不需要引号,建议不要使用特殊符号、表情等</p></li><li><p>date格式符合 <code>YYYY-MM-DD HH:mm:ss</code> ,这个有很多工具可以生成</p></li><li><p>tags是标签,有多个需要采用数组写法</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs markdown">tags:<br><span class="hljs-bullet">-</span> tag1<br><span class="hljs-bullet">-</span> tag2<br></code></pre></td></tr></table></figure></li><li><p>categories是分类,写法同上</p></li><li><p>author是作者,不支持多个,但其实逗号连接也行</p></li><li><p>index_img是封面,建议:国内图床 > 国外图床 >= 相对路径引用(可以留空)</p></li><li><p>banner_img是页面顶部大图,要求同上(可以留空)</p></li></ul><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><ul><li>author 和 index_img 和 banner_img 可以为空</li><li>有latex公式则需要添加 <code>math: true</code></li><li>需要引用的资源放入文章同名文件夹中,只能使用markdown语法,内嵌html标签可能无法正确引用资源</li></ul>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>blog</tag>
</tags>
</entry>
<entry>
<title>不到100行代码写出愤怒的小鸟</title>
<link href="/%E4%B8%8D%E5%88%B0100%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%86%99%E5%87%BA%E6%84%A4%E6%80%92%E7%9A%84%E5%B0%8F%E9%B8%9F/"/>
<url>/%E4%B8%8D%E5%88%B0100%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%86%99%E5%87%BA%E6%84%A4%E6%80%92%E7%9A%84%E5%B0%8F%E9%B8%9F/</url>
<content type="html"><![CDATA[<h1 id="这款中国程序员开源的游戏引擎,让你用不到100行代码就写出“愤怒的小鸟”"><a href="#这款中国程序员开源的游戏引擎,让你用不到100行代码就写出“愤怒的小鸟”" class="headerlink" title="这款中国程序员开源的游戏引擎,让你用不到100行代码就写出“愤怒的小鸟”"></a>这款中国程序员开源的游戏引擎,让你用不到100行代码就写出“愤怒的小鸟”</h1><p>大家好,我是一个游戏引擎技术探索者,同时也是一名做过不少前端开发工作的程序员。如果你想知道如何从编写网页到开发游戏,那你来对地方了!</p><p>今天我们聊聊如何使用 Dora SSR,一个支持 TSX 且跨平台在 native 运行的游戏引擎,助你轻松跨入游戏开发的世界。</p><p>不必担心,说到游戏引擎并不是啥高不可攀的技术,反而和我们熟悉的前端开发工具可以有惊人相似之处。</p><p><img src="/./%E4%B8%8D%E5%88%B0100%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%86%99%E5%87%BA%E6%84%A4%E6%80%92%E7%9A%84%E5%B0%8F%E9%B8%9F/640.gif"></p><h2 id="一、游戏客户端开发也可以是一种前端开发"><a href="#一、游戏客户端开发也可以是一种前端开发" class="headerlink" title="一、游戏客户端开发也可以是一种前端开发"></a>一、游戏客户端开发也可以是一种前端开发</h2><p>首先,让我们解释一下什么是游戏引擎。</p><p>简单来说,游戏引擎就是一套工具和库的集合,帮助开发者构建游戏,管理图形、声音、物理计算或碰撞检测等。</p><p>对于前端开发者来说,你可以把它想象成就是一种特殊的浏览器,专门用来运行游戏。</p><p>Dora SSR 的游戏场景管理使用了类似于 HTML DOM 的树形结构,这对我们来说再熟悉不过了。</p><p>想象一下,将 div 元素换成游戏中的各种对象,CSS 动画换成游戏动画,概念也差不多,代码写法上可能也差不多,是不是觉得有点意思了?</p><h2 id="二、从-TypeScript-到-TSX:前端技术在游戏中的应用"><a href="#二、从-TypeScript-到-TSX:前端技术在游戏中的应用" class="headerlink" title="二、从 TypeScript 到 TSX:前端技术在游戏中的应用"></a>二、从 TypeScript 到 TSX:前端技术在游戏中的应用</h2><p>许多前端开发者都熟悉 TypeScript 和 React 的 JSX 语法。</p><p>在 Dora SSR 开源游戏引擎中,我们通过支持 TSX,提供了与前端开发编程模式相似的游戏开发接口。</p><p>是的你没听错,就是那个 TSX。</p><p>使用 TSX 开发游戏,意味着你可以利用已有的前端技术栈 — 组件、模块和其他现代前端技术,直接在游戏开发中复用这些概念。</p><p>而且,Dora SSR 的性能优化确保了即使是在复杂的游戏场景中,也能保持流畅的运行。</p><h2 id="三、挑战-100-行代码以内,教你写一个-“愤怒的小鸟”-like-的游戏"><a href="#三、挑战-100-行代码以内,教你写一个-“愤怒的小鸟”-like-的游戏" class="headerlink" title="三、挑战 100 行代码以内,教你写一个 “愤怒的小鸟” like 的游戏"></a>三、挑战 100 行代码以内,教你写一个 “愤怒的小鸟” like 的游戏</h2><p>好了,理论知识够多了,让我们来点实际操作吧。</p><p>来看看如何在 Dora SSR 中用 100 行以内的 TSX 代码编写一个类似 “愤怒的小鸟” 的小游戏。</p><p>当然,在开始之前还是要准备开发环境,做这个事用 Dora SSR 就很简单:我有一个<strong>安装包一装</strong>,我有一个<strong>浏览器一开</strong>,嗯,开始<strong>写代码</strong>运行吧。</p><p>安装启动参见:Dora 启动!<br><em>(<a href="https://dora-ssr.net/zh-Hans/docs/tutorial/quick-start">https://dora-ssr.net/zh-Hans/docs/tutorial/quick-start</a>)</em></p><p><img src="/./%E4%B8%8D%E5%88%B0100%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%86%99%E5%87%BA%E6%84%A4%E6%80%92%E7%9A%84%E5%B0%8F%E9%B8%9F/640.png"></p><p><em>不小心装成了APK包在手机上?那就在同局域网下访问,直接在手机上进行开发调试吧</em></p><h3 id="1-编写最简单游戏场景"><a href="#1-编写最简单游戏场景" class="headerlink" title="1. 编写最简单游戏场景"></a>1. 编写最简单游戏场景</h3><p>在编写实际的代码之前,我们可以先写一个有特别功能的注释,它可以告诉 Dora SSR 的 Web IDE 在我们按下 Ctrl + S 保存文件时,自动热更新运行的代码,以实现代码运行结果的实时预览功能。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// @preview-file on</span><br></code></pre></td></tr></table></figure><p>然后,我们引入必要的库和组件。</p><p>当然我们的代码编辑器也会提示辅助我们自动引入需要的模块,可以放到后面编码过程中再完成:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">React</span>, toNode, useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'DoraX'</span>;<br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Body</span>, <span class="hljs-title class_">BodyMoveType</span>, <span class="hljs-title class_">Ease</span>, <span class="hljs-title class_">Label</span>, <span class="hljs-title class_">Line</span>, <span class="hljs-title class_">Scale</span>, <span class="hljs-title class_">TypeName</span>, <span class="hljs-title class_">Vec2</span>, tolua } <span class="hljs-keyword">from</span> <span class="hljs-string">'Dora'</span>;<br></code></pre></td></tr></table></figure><p>在 Dora SSR 中显示一个图片很简单,只要使用 <code><sprite></code> 标签,最后通过 <code>toNode()</code> 函数将标签实例化为一个游戏对象就可以了。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-title function_">toNode</span>(<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">sprite</span> <span class="hljs-attr">file</span>=<span class="hljs-string">'Image/logo.png'</span> <span class="hljs-attr">scaleX</span>=<span class="hljs-string">{0.2}</span> <span class="hljs-attr">scaleY</span>=<span class="hljs-string">{0.2}/</span>></span></span>);<br></code></pre></td></tr></table></figure><p>好的,至此你已经基本掌握大部分 Dora SSR 游戏开发的诀窍了,开始做你自己的游戏吧(认真)。</p><h3 id="2-编写游戏箱子组件"><a href="#2-编写游戏箱子组件" class="headerlink" title="2. 编写游戏箱子组件"></a>2. 编写游戏箱子组件</h3><p>接下来我们在游戏中碰撞的箱子会由 <code>Box</code> 组件定义,它接受 <code>num</code>、<code>x</code>、<code>y</code> 和 <code>children</code> 等属性:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">BoxProps</span> {<br> <span class="hljs-attr">num</span>: <span class="hljs-built_in">number</span>;<br> x?: <span class="hljs-built_in">number</span>;<br> y?: <span class="hljs-built_in">number</span>;<br> children?: <span class="hljs-built_in">any</span> | <span class="hljs-built_in">any</span>[];<br>}<br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">Box</span> = (<span class="hljs-params"><span class="hljs-attr">props</span>: <span class="hljs-title class_">BoxProps</span></span>) => {<br> <span class="hljs-keyword">const</span> numText = props.<span class="hljs-property">num</span>.<span class="hljs-title function_">toString</span>();<br> <span class="hljs-keyword">return</span> (<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">body</span> <span class="hljs-attr">type</span>=<span class="hljs-string">{BodyMoveType.Dynamic}</span> <span class="hljs-attr">scaleX</span>=<span class="hljs-string">{0}</span> <span class="hljs-attr">scaleY</span>=<span class="hljs-string">{0}</span> <span class="hljs-attr">x</span>=<span class="hljs-string">{props.x}</span> <span class="hljs-attr">y</span>=<span class="hljs-string">{props.y}</span> <span class="hljs-attr">tag</span>=<span class="hljs-string">{numText}</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">rect-fixture</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{100}</span> <span class="hljs-attr">height</span>=<span class="hljs-string">{100}/</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">draw-node</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">rect-shape</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{100}</span> <span class="hljs-attr">height</span>=<span class="hljs-string">{100}</span> <span class="hljs-attr">fillColor</span>=<span class="hljs-string">{0x8800ffff}</span> <span class="hljs-attr">borderWidth</span>=<span class="hljs-string">{1}</span> <span class="hljs-attr">borderColor</span>=<span class="hljs-string">{0xff00ffff}/</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">draw-node</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">fontName</span>=<span class="hljs-string">'sarasa-mono-sc-regular'</span> <span class="hljs-attr">fontSize</span>=<span class="hljs-string">{40}</span>></span>{numText}<span class="hljs-tag"></<span class="hljs-name">label</span>></span></span><br><span class="language-xml"> {props.children}</span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">body</span>></span></span><br> );<br>};<br></code></pre></td></tr></table></figure><p>我们使用仿 React 的函数组件的写法来完成我们箱子组件的定义,其中:</p><ul><li><code>body</code> 组件的 <code>tag</code> 属性:用于存储箱子的分数。</li><li><code>rect-fixture</code> :定义了箱子的碰撞形状。</li><li><code>draw-node</code> :用于绘制箱子的外观。</li><li><code>label</code> :用于显示盒子的分数。</li></ul><h3 id="3-创建-TSX-实例化后的对象引用"><a href="#3-创建-TSX-实例化后的对象引用" class="headerlink" title="3. 创建 TSX 实例化后的对象引用"></a>3. 创建 TSX 实例化后的对象引用</h3><p>使⽤ useRef 创建两个引⽤变量进行备用,分别指向⼩⻦和分数标签:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-keyword">const</span> bird = useRef<<span class="hljs-title class_">Body</span>.<span class="hljs-property">Type</span>>();<br><span class="hljs-keyword">const</span> score = useRef<<span class="hljs-title class_">Label</span>.<span class="hljs-property">Type</span>>();<br></code></pre></td></tr></table></figure><h3 id="4-创建发射线"><a href="#4-创建发射线" class="headerlink" title="4. 创建发射线"></a>4. 创建发射线</h3><p>发射线由 <code>line</code> 变量创建,并添加触摸(同时也是鼠标点击)的事件处理:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-keyword">let</span> start = <span class="hljs-title class_">Vec2</span>.<span class="hljs-property">zero</span>;<br><span class="hljs-keyword">let</span> delta = <span class="hljs-title class_">Vec2</span>.<span class="hljs-property">zero</span>;<br><span class="hljs-keyword">const</span> line = <span class="hljs-title class_">Line</span>();<br><br><span class="hljs-title function_">toNode</span>(<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">physics-world</span></span></span><br><span class="hljs-tag"><span class="language-xml"> <span class="hljs-attr">onTapBegan</span>=<span class="hljs-string">{(touch)</span> =></span> {</span><br><span class="language-xml"> start = touch.location;</span><br><span class="language-xml"> line.clear();</span><br><span class="language-xml"> }}</span><br><span class="language-xml"> onTapMoved={(touch) => {</span><br><span class="language-xml"> delta = delta.add(touch.delta);</span><br><span class="language-xml"> line.set([start, start.add(delta)]);</span><br><span class="language-xml"> }}</span><br><span class="language-xml"> onTapEnded={() => {</span><br><span class="language-xml"> if (!bird.current) return;</span><br><span class="language-xml"> bird.current.velocity = delta.mul(Vec2(10, 10));</span><br><span class="language-xml"> start = Vec2.zero;</span><br><span class="language-xml"> delta = Vec2.zero;</span><br><span class="language-xml"> line.clear();</span><br><span class="language-xml"> }}</span><br><span class="language-xml"> ></span><br><span class="language-xml"> {/* ...在物理世界下创建其它游戏元素 ... */}</span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">physics-world</span>></span></span><br>);<br></code></pre></td></tr></table></figure><ul><li>在 <code>onTapBegan</code> 事件中,记录触摸开始的位置并清除发射线。</li><li>在 <code>onTapMoved</code> 事件中,计算触摸移动的距离并更新发射线。</li><li>在 <code>onTapEnded</code> 事件中,根据触摸移动的距离设置小鸟的发射速度并清除发射线。</li></ul><h3 id="5-创建其它游戏元素"><a href="#5-创建其它游戏元素" class="headerlink" title="5. 创建其它游戏元素"></a>5. 创建其它游戏元素</h3><p>接下来,我们以 <code><physics-world></code> 作为游戏场景的父级标签,在它下面继续创建游戏场景中的各个元素:</p><h4 id="5-1-地面"><a href="#5-1-地面" class="headerlink" title="5.1 地面"></a>5.1 地面</h4><p>首先,我们使用 <code>body</code> 组件创建一个地面,并将其设置为静态刚体:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><body <span class="hljs-keyword">type</span>={<span class="hljs-title class_">BodyMoveType</span>.<span class="hljs-property">Static</span>}><br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">rect-fixture</span> <span class="hljs-attr">centerY</span>=<span class="hljs-string">{-200}</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{2000}</span> <span class="hljs-attr">height</span>=<span class="hljs-string">{10}/</span>></span></span><br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">draw-node</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">rect-shape</span> <span class="hljs-attr">centerY</span>=<span class="hljs-string">{-200}</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{2000}</span> <span class="hljs-attr">height</span>=<span class="hljs-string">{10}</span> <span class="hljs-attr">fillColor</span>=<span class="hljs-string">{0xfffbc400}/</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">draw-node</span>></span></span><br></body><br></code></pre></td></tr></table></figure><ul><li><code>type={BodyMoveType.Static}</code>:表明这是一个静态刚体,不会受到物理模拟的影响。</li><li><code>rect-fixture</code>:定义地面碰撞形状为一个矩形。</li><li><code>draw-node</code>:用于绘制地面的外观。</li><li><code>rect-shape</code>:绘制一个矩形,颜色为黄色。</li></ul><h4 id="5-2-箱子"><a href="#5-2-箱子" class="headerlink" title="5.2 箱子"></a>5.2 箱子</h4><p>接下来,我们使用之前写好的 <code>Box</code> 组件创建 5 个箱子,并设置不同的初始位置和分数,在创建时播放出场动画:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs tsx">{<br> [<span class="hljs-number">10</span>, <span class="hljs-number">20</span>, <span class="hljs-number">30</span>, <span class="hljs-number">40</span>, <span class="hljs-number">50</span>].<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">num, i</span>) =></span> (<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">Box</span> <span class="hljs-attr">num</span>=<span class="hljs-string">{num}</span> <span class="hljs-attr">x</span>=<span class="hljs-string">{200}</span> <span class="hljs-attr">y</span>=<span class="hljs-string">{-150</span> + <span class="hljs-attr">i</span> * <span class="hljs-attr">100</span>}></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">sequence</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">delay</span> <span class="hljs-attr">time</span>=<span class="hljs-string">{i</span> * <span class="hljs-attr">0.2</span>}/></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">scale</span> <span class="hljs-attr">time</span>=<span class="hljs-string">{0.3}</span> <span class="hljs-attr">start</span>=<span class="hljs-string">{0}</span> <span class="hljs-attr">stop</span>=<span class="hljs-string">{1}/</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">sequence</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">Box</span>></span></span><br> ))<br>}<br></code></pre></td></tr></table></figure><ul><li><code>map</code> 函数:用于遍历分数数组从 10 到 50,并为每个分数创建一个需要小鸟撞击的箱子。</li><li><code>Box</code> 组件:用于创建箱子,并传入以下属性:<ul><li><code>num={num}</code>:箱子的分数,对应数组中的数字。</li><li><code>x={200}</code>:箱子的初始 x 轴位置,为 200。</li><li><code>y={-150 + i * 100}</code>:箱子的初始 y 轴位置,根据创建序号递增。</li></ul></li><li><code>sequence</code> 组件:用于创建要在父节点上播放的动画序列,包含以下动画:<ul><li><code>delay time={i * 0.2}</code>:延迟播放动画,延迟时间根据创建序号递增。</li><li><code>scale time={0.3} start={0} stop={1}</code>:缩放动画,从不显示到完全显示,耗时 0.3 秒。</li></ul></li></ul><h4 id="5-3-小鸟"><a href="#5-3-小鸟" class="headerlink" title="5.3 小鸟"></a>5.3 小鸟</h4><p>最后,我们使用 <code>body</code> 组件创建小鸟,并设置碰撞形状、外观和分数标签:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><body ref={bird} <span class="hljs-keyword">type</span>={<span class="hljs-title class_">BodyMoveType</span>.<span class="hljs-property">Dynamic</span>} x={-<span class="hljs-number">200</span>} y={-<span class="hljs-number">150</span>} onContactStart={<span class="hljs-function">(<span class="hljs-params">other</span>) =></span> {<br> <span class="hljs-keyword">if</span> (other.<span class="hljs-property">tag</span> !== <span class="hljs-string">''</span> && score.<span class="hljs-property">current</span>) {<br> <span class="hljs-comment">// 累加积分</span><br> <span class="hljs-keyword">const</span> sc = <span class="hljs-built_in">parseFloat</span>(score.<span class="hljs-property">current</span>.<span class="hljs-property">text</span>) + <span class="hljs-built_in">parseFloat</span>(other.<span class="hljs-property">tag</span>);<br> score.<span class="hljs-property">current</span>.<span class="hljs-property">text</span> = sc.<span class="hljs-title function_">toString</span>();<br> <span class="hljs-comment">// 清除被撞箱子上的分数</span><br> <span class="hljs-keyword">const</span> label = tolua.<span class="hljs-title function_">cast</span>(other.<span class="hljs-property">children</span>?.<span class="hljs-property">last</span>, <span class="hljs-title class_">TypeName</span>.<span class="hljs-property">Label</span>);<br> <span class="hljs-keyword">if</span> (label) label.<span class="hljs-property">text</span> = <span class="hljs-string">''</span>;<br> other.<span class="hljs-property">tag</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-comment">// 播放箱子被撞的动画</span><br> other.<span class="hljs-title function_">perform</span>(<span class="hljs-title class_">Scale</span>(<span class="hljs-number">0.2</span>, <span class="hljs-number">0.7</span>, <span class="hljs-number">1.0</span>));<br> }<br>}}><br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">disk-fixture</span> <span class="hljs-attr">radius</span>=<span class="hljs-string">{50}/</span>></span></span><br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">draw-node</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">dot-shape</span> <span class="hljs-attr">radius</span>=<span class="hljs-string">{50}</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0xffff0088}/</span>></span></span><br><span class="language-xml"> <span class="hljs-tag"></<span class="hljs-name">draw-node</span>></span></span><br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{score}</span> <span class="hljs-attr">fontName</span>=<span class="hljs-string">'sarasa-mono-sc-regular'</span> <span class="hljs-attr">fontSize</span>=<span class="hljs-string">{40}</span>></span>0<span class="hljs-tag"></<span class="hljs-name">label</span>></span></span><br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">scale</span> <span class="hljs-attr">time</span>=<span class="hljs-string">{0.4}</span> <span class="hljs-attr">start</span>=<span class="hljs-string">{0.3}</span> <span class="hljs-attr">stop</span>=<span class="hljs-string">{1.0}</span> <span class="hljs-attr">easing</span>=<span class="hljs-string">{Ease.OutBack}/</span>></span></span><br></body><br></code></pre></td></tr></table></figure><ul><li><code>ref={bird}</code>:使用 <code>ref</code> 创建引用变量,方便后续操控小鸟。</li><li><code>type={BodyMoveType.Dynamic}</code>:表明这是一个动态刚体,会受到物理模拟的影响。</li><li><code>onContactStart={(other) => {}}</code>:小鸟的物理体接触到其它物体时触发的回调处理函数。</li><li><code>disk-fixture</code>:定义小鸟形状为一个圆盘。</li><li><code>draw-node</code> :用于绘制小鸟的外观。</li><li><code>label</code> :用于显示小鸟的累积分数。</li><li><code>scale</code> :用于播放小鸟的出场动画。</li></ul><h3 id="6-完成游戏逻辑"><a href="#6-完成游戏逻辑" class="headerlink" title="6. 完成游戏逻辑"></a>6. 完成游戏逻辑</h3><p>至此,我们已经完成了小游戏的核心逻辑。你可以根据自己的想法进一步完善游戏逻辑和增加功能。</p><p>完整的 demo 代码可以见这个链接:</p><p><em><a href="https://github.com/IppClub/Dora-SSR/blob/main/Assets/Script/Test/Birdy.tsx">https://github.com/IppClub/Dora-SSR/blob/main/Assets/Script/Test/Birdy.tsx</a></em></p><p>下面是一些运行效果的截图。</p><p><img src="/./%E4%B8%8D%E5%88%B0100%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%86%99%E5%87%BA%E6%84%A4%E6%80%92%E7%9A%84%E5%B0%8F%E9%B8%9F/641.png"></p><p><em>拖拽屏幕发射了“愤怒的小鸟”</em></p><p><img src="/./%E4%B8%8D%E5%88%B0100%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%86%99%E5%87%BA%E6%84%A4%E6%80%92%E7%9A%84%E5%B0%8F%E9%B8%9F/642.png"></p><p><em>高超的技巧,使我一击获得了所有得分</em></p><h2 id="四、简单揭秘一下"><a href="#四、简单揭秘一下" class="headerlink" title="四、简单揭秘一下"></a>四、简单揭秘一下</h2><h3 id="1-是鹿还是马"><a href="#1-是鹿还是马" class="headerlink" title="1. 是鹿还是马"></a>1. 是鹿还是马</h3><p>事实上我们写的这段游戏代码,在 Dora SSR 引擎的能力下是可以确保在跨 Linux、Android、iOS、macOS 和 Windows 获得一致的运行结果。</p><p>但是为了运行这段代码,我们的 Dora SSR 引擎甚至都没有做 JavaScript 运行环境的支持……(你说什么?)</p><p>是的,Dora SSR 的底层技术实现其实是基于 Lua 和 WASM 虚拟机作为脚本语言运行环境的。</p><p>对 TypeScript 的支持其实是通过整合了 TypescriptToLua 这个编译器提供的。<br>(<em><a href="https://github.com/TypeScriptToLua/TypeScriptToLua">https://github.com/TypeScriptToLua/TypeScriptToLua</a></em> )</p><p>TSTL 通过重新编写了 TypeScript 语言编译器的后端,将 TS 和 TSX 的代码编译为了等价运行的 Lua 代码,从而使得 TS 代码可以在 Dora 上加载运行。</p><p>在 Dora 自带的 Web IDE 的代码编辑器下,可以帮助大家做 TS 的语言检查和补全以及 Dora 内置库 API 的提示。</p><p>最终的使用体验,大家就可以不用管最后是鹿还是马,只要代码能通过了 TS 的编译检查,拉出来那都是一样的跑啦。</p><h3 id="2-和-React-有关系吗"><a href="#2-和-React-有关系吗" class="headerlink" title="2. 和 React 有关系吗"></a>2. 和 React 有关系吗</h3><p>这个问题的答案目前是:可以有(所以截至发文前还没有)。</p><p>React 最重要的能力是通过 Virtual DOM 和执行 Tree Diff 处理的过程来进行渲染组件和业务数据的状态同步,目前这个机制还没有在 Dora SSR 中实现,所以大家目前看到的用 TSX 编写出的类似 VDOM 的构建代码只会在运行时做一次性的游戏渲染对象的构建,往后都是底层 C++ 实现的引擎功能在负责继续处理。</p><p>也许有一天我们会为游戏 UI 的开发,提供仿 React 通过执行 Tree Diff 做状态同步的能力,或是仿 SolidJS 基于 TSX 实现其它的渲染组件状态同步的机制。</p><p>所以在这里也诚挚地邀请广大前端开发的朋友,加入我们,一起玩 Dora SSR 项目,一起研究怎么运用前端开发技术思想,为游戏开发也引入更多好用便捷的轮子吧。</p><p>最后我们的 Q 群在这里,欢迎过来玩:<br>512620381</p><br /><blockquote><p><strong>延伸阅读:</strong><a href="http://mp.weixin.qq.com/s?__biz=MjM5NzM0MjcyMQ==&mid=2650204639&idx=1&sn=01192c5d715677a63e894032d8abf7f2&chksm=bed932b189aebba7f95b8ed5d3c97584f4d330d98928ac31ebfa0db89d4a17bf14b5546dad04&scene=21#wechat_redirect"><strong>用Rust开发跨平台游戏是怎样的体验?</strong></a></p></blockquote><hr><p><strong>作者介绍</strong></p><p>李瑾:金融行业大数据工程师,Dora SSR 和 Yuescript 开源软件作者。</p><p><strong>项目介绍</strong></p><p>Dora SSR(多萝珍奇引擎)是一个用于多种设备上快速开发2D游戏的游戏引擎。<br>它内置易用的开发工具链,支持在手机、开源掌机等设备上直接进行游戏开发。</p><p><img src="/./%E4%B8%8D%E5%88%B0100%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%86%99%E5%87%BA%E6%84%A4%E6%80%92%E7%9A%84%E5%B0%8F%E9%B8%9F/dorassr.png"></p><p><strong>项目仓库</strong></p><p><em><a href="https://gitee.com/pig/Dora-SSR">https://gitee.com/pig/Dora-SSR</a><br><a href="https://github.com/IppClub/Dora-SSR">https://github.com/IppClub/Dora-SSR</a></em></p><div class="note note-primary"> <p>本文转载自 <a href="https://mp.weixin.qq.com/s/kJEGSNGdC7VsY3H-B_GNJA">这款中国程序员开源的游戏引擎,让你用不到100行代码就写出“愤怒的小鸟”</a></p> </div>]]></content>
<categories>
<category>游戏开发</category>
<category>转载</category>
</categories>
<tags>
<tag>TSX</tag>
<tag>DoraSSR</tag>
</tags>
</entry>
<entry>
<title>mongo 源码阅读感悟(一)</title>
<link href="/mongo-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E6%84%9F%E6%82%9F%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<url>/mongo-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E6%84%9F%E6%82%9F%EF%BC%88%E4%B8%80%EF%BC%89/</url>
<content type="html"><![CDATA[<p>其实开头的这一篇不完全是阅读 mongo 时的感悟,也包括了我在 leveldb 中学到的技巧。看过 leveldb 的应该都知道,这份代码的注释不算清楚,文档也少,阅读体验不算好,需要 C++ 开发者有足够的经验。因此有些东西我到了mongo才真正了解它们的作用。也是十分惭愧。</p><p>最近在阅读 mongo/bson 的源码,我试图去实现一个 bson,在造轮子的过程中学习他们的编程技巧,事实证明阅读大型且较新的项目的源码要远远好过阅读小而老的项目。</p><p>这篇文章我来介绍两个很小的类,<code>Status</code> 和 <code>StringData</code>,<code>StringData</code> 在 leveldb 中是 <code>Slice</code>,为了方便,之后我都会用 <code>Slice</code>。</p><h3 id="Status"><a href="#Status" class="headerlink" title="Status"></a>Status</h3><p>许多大型 C++ 项目的编程规范要求 exception free,在实际的编程中,开发者会发现使用 error 来代替 exception 其实十分实用(golang开发中更多使用 error 而非 exception),因为很多的编程错误被强行放到了 exception 中,比如 java 的 <code>IndexOutOfBoundException</code>。再者,exception 也常常拉低了执行效率。对于不在意性能的语言(比如Java),exception 的罪状更多的在于如果不将异常及时catch,exception会蔓延到外部。</p><p>这里我们介绍一个设施,它可以保证错误只会被手动的抛到外部,甚至我们可以保存错误的状态,延迟抛出(异常能做这件事吗?)。</p><p>在 mongo 和 leveldb 中,都用了 Status 封装错误信息。</p><p>Status 封装了 enum 类型的错误码,并且可以为每个错误绑定它的具体错误信息。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">static</span> Status <span class="hljs-title">FailedToParse</span><span class="hljs-params">(<span class="hljs-type">const</span> Slice &msg)</span></span>;<br></code></pre></td></tr></table></figure><p>一个 FailedToParse 错误就可以这样被抛出来。</p><p>为了节约内存,enum 被转为 <code>unsigned char</code>,用以存储至多 256 种错误。当然,大型项目可以多于这个数。</p><p><code>Status::OK()</code> 表示这个函数调用没有错误,跟 C 语言里返回值为 0 类似,Status 通常也是通过函数返回值抛出。我们要尽量使得成功的函数调用几乎无需处理错误,只有在错误发生的时候才去处理错误。因此我们这样实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Status</span> {<br> <span class="hljs-keyword">private</span>:<br> std::unique_ptr<ErrorInfo> info_;<br>};<br></code></pre></td></tr></table></figure><p><code>Status</code> 内部只保存指向错误信息的指针。<code>Status::OK()</code> 抛出一个未初始化的指针(指向 null),只有在错误的信息抛出时,才会生成信息给指针。</p><h3 id="Slice"><a href="#Slice" class="headerlink" title="Slice"></a>Slice</h3><p><code>Slice</code> 是对字符串的一种简单的 reference 的封装,它仅保存字符串的指针(<code>char*</code>)和字符串的长度。与 <code>std::string</code> 用途不同,它不会对字符串进行复制操作,在这一点上比 <code>std::string</code> 要好用许多。当然,我们也可以在整个项目中,完全使用 <code>std::string</code>,并且在函数接口中使用 <code>const std::string&</code>,这样在一个需要大量使用字符串的代码中,函数的接口会变得混乱不堪。</p><p>使用了 <code>Slice</code> 之后,我们可以这样编写一个函数的接口。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp">Parser::<span class="hljs-built_in">parseInt</span>(Slice int_string);<br></code></pre></td></tr></table></figure><p>一方面这样可以简化函数接口(如果要 forward declaration 则需要引用),一方面这样可以兼容 raw string 和 <code>std::string</code>,不会出现 raw string 传参给 <code>const std::string &</code> 时,出现复制的情况。</p><p>唯一的缺点是我们需要去重新实现一些基本的工具函数。好在有 <code><cstring></code>,使得这一切并不算难。</p><h3 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h3><p>附上我的实现:</p><ul><li><p>Slice: <a href="https://github.com/neverchanje/bson-cpp11/blob/master/src%2FSlice.h">github link</a></p></li><li><p>Status: <a href="https://github.com/neverchanje/bson-cpp11/blob/master/src%2FStatus.h">github link</a></p></li></ul>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>C++</tag>
<tag>mongo</tag>
</tags>
</entry>
<entry>
<title>读 leveldb 的感悟 :Cache的设计</title>
<link href="/%E8%AF%BB-leveldb-%E7%9A%84%E6%84%9F%E6%82%9F-%EF%BC%9ACache%E7%9A%84%E8%AE%BE%E8%AE%A1/"/>
<url>/%E8%AF%BB-leveldb-%E7%9A%84%E6%84%9F%E6%82%9F-%EF%BC%9ACache%E7%9A%84%E8%AE%BE%E8%AE%A1/</url>
<content type="html"><![CDATA[<p>leveldb允许Cache(一个缓冲区的抽象)作为一个Option,用户可以自定义它的实现。具体来说就是这样:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Options</span> {<br> ...<br> Cache *block_cache;<br>};<br></code></pre></td></tr></table></figure><p>这是一个难度较高的设计需求。</p><p>表面上按照逻辑,首先我们定义好Cache的接口,用户可以自行设置Cache的实现,leveldb默认用自己的 <code>ShardedLRUCache</code>(也是LRU Cache的一种)。</p><p>然而我们会遇到几个问题:</p><ol><li><p>Cache无法使用模板,因为它是一个Option。当然,这意味着Cache就是一个包含纯虚函数的接口类。</p></li><li><p>Cache中所存储的key/value的value类型不确定,key的类型已知是 string</p></li><li><p>Cache查询时返回一个句柄(Handle),这个Handle应该是什么</p></li><li><p>Handle的内容可以被用户自定义吗?如果是的话,那如何设计?</p></li></ol><p>这几个问题的本质在于Option中无法得到Cache的更多信息,由于模板与继承不同,我们无法使用未指定类型的模板类的指针。简单说就是,我们不能这样写:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp">Cache<K, V> *block_cache;<br></code></pre></td></tr></table></figure><p>在其他一切开源的Cache实现里,Cache通常设计为模板类,他们可以这样:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">template</span> < <span class="hljs-keyword">class</span> <span class="hljs-title class_">TKey</span>, <span class="hljs-keyword">class</span> <span class="hljs-title class_">TValue</span>, <span class="hljs-keyword">class</span> <span class="hljs-title class_">TStrategy</span>><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">AbstractCache</span><br></code></pre></td></tr></table></figure><p>我们通过问题的解决慢慢来解释我们的设计。</p><p>问题2 的解决可以引入 <code>boost::any</code>(其实<code>void*</code>也可以),使用Cache的用户需要知道自己的Value是什么类型,并且要保证自己只加入了一种类型的值。这个做法让我们依然无力控制value的类型。</p><p>问题3 我们定义Handle。Handle 的定义与 Iterator 有区别,Iterator的Concept指出Iterator是一种可以用来遍历容器内每个元素的抽象,而Handle是不可遍历的,我们只能取得Handle所指的元素的值,这点和Iterator一样。</p><p>问题4 Handle 的内容。首先要知道,Handle在一个接口类中定义,那么它本身就无法包含任何信息(It doesn’t carry any properties)。如果是这样,那么它只能是一个 opaque object。或许它可以包含纯虚函数,但与其在Handle中定义,不如在Cache中定义,因为这意味着Handle对象构造时会多一个vptr,而Cache本来已经有vptr了,Handle的纯虚函数定义在Cache中就无所谓,比如说leveldb这样来获取一个Handle所指元素的value的:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-title">Cache::Value</span><span class="hljs-params">(Handle* h)</span> <span class="hljs-type">const</span> </span>= <span class="hljs-number">0</span>;<br></code></pre></td></tr></table></figure><p>其次,Handle的内容并不重要。可以说,Handle是C++的一种设计模式。Handle自身不能做任何事,它只能用来与Cache交互。具体的思路可以参照 windows 的 <code>HWND</code>。其实原理很简单,我们在Cache的子类,例如 LRUCache,实现一个 LRUHandle,它与 <code>Cache::Handle</code> 没有任何继承关系,然后我们只要用 <code>reinterpret_cast<Handle*></code>就能将 LRUHandle 在内部转为 Handle。</p><p>按照这个思路,Cache的value类型不确定是一个隐患,这是由于Cache是一个Option所导致的。我们要如何设计Cache的抽象?</p><p>Cache的值类型不确定导致它不能像 <code>poco::AbstractCache</code> 或者 guava 的 cache 那样,定义一个全套的Cache所应该有的标准API,它只能作为一个组件来使用,我们采用组合模式来使用它。<br>按照这个思路设计,Cache应该是主要算法的抽象,我们需要将算法,和其他组件一起组装起来才能构成一个真正的Cache,因此,我将leveldb 的Cache改名为 CacheStrategy,由此就符合了我们的设计思路:CacheStrategy 不保证Cache里值的类型都一样,这由Cache来做。</p><p>总结一下我们的设计:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Options</span> {<br>…<br>CacheStategy *block_cache_strategy; <span class="hljs-comment">//</span><br>};<br></code></pre></td></tr></table></figure><p><code>class CacheStrategy</code> 代替 <code>leveldb::Cache</code>。</p>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>C++</tag>
<tag>LevelDB</tag>
</tags>
</entry>
<entry>
<title>C++ 的三种实现接口的方式</title>
<link href="/C-%E7%9A%84%E4%B8%89%E7%A7%8D%E5%AE%9E%E7%8E%B0%E6%8E%A5%E5%8F%A3%E7%9A%84%E6%96%B9%E5%BC%8F/"/>
<url>/C-%E7%9A%84%E4%B8%89%E7%A7%8D%E5%AE%9E%E7%8E%B0%E6%8E%A5%E5%8F%A3%E7%9A%84%E6%96%B9%E5%BC%8F/</url>
<content type="html"><![CDATA[<h2 id="传统-interface"><a href="#传统-interface" class="headerlink" title="传统 interface"></a>传统 interface</h2><p>想要实现 interface,在绝大多数 OOP 语言中,会被认为只有一种。如 java 和 C# 的 <code>interface</code> specifier,C++ 的 pure virtual function。通常接口类中强制的不能拥有实现,继承接口的子类至少享有<strong>两个名字</strong>,一个是自身定义的命名,一个是接口名。通常子类转父类(up-cast)在编译期(compile-time)决定,父类转子类(down-cast)在运行时(run-time)决定。</p><p>我们太过于习惯这种接口的方式,而这种接口方式也确实能十分灵活地应用在编程中,以至于我们常常不自觉的在设计中增加不必要的层数。如果你也喜欢 “flat is better than nested” 的理念的话,我接下来将会介绍两种 alternatives,用来代替传统的接口实现。</p><h2 id="Functor"><a href="#Functor" class="headerlink" title="Functor"></a>Functor</h2><p>使用 C 语言同样也可以进行 OOP,思路就是 function pointer。通过注册回调函数,用来替代传统的通过继承来实现接口。 C++11 所提供的<code>std::function</code>,<code>std::bind</code> 和 lambda function 为此提供了极大的便利。</p><h4 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h4><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Comparator</span> {<br> <span class="hljs-keyword">public</span>:<br><br> <span class="hljs-keyword">typedef</span> std::function<<span class="hljs-type">int</span>(<span class="hljs-type">const</span> Slice &, <span class="hljs-type">const</span> Slice &)> CompareFunc;<br><br> <span class="hljs-built_in">Comparator</span>(<span class="hljs-type">const</span> CompareFunc &compare_fn) :<br> <span class="hljs-built_in">compare_</span>(compare_fn)<br> }<br><br> <span class="hljs-comment">// Three-way comparison.</span><br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">int</span> <span class="hljs-title">Compare</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &lhs,</span></span><br><span class="hljs-params"><span class="hljs-function"> <span class="hljs-type">const</span> std::string &rhs)</span> <span class="hljs-type">const</span> </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">compare_</span>(lhs, rhs);<br> }<br><br> <span class="hljs-keyword">private</span>:<br> <span class="hljs-type">const</span> CompareFunc compare_;<br>};<br></code></pre></td></tr></table></figure><p>每种 <code>Comparator</code> 的示例都可能实现某一种策略(Strategy)。而如果为每一个策略都定义一个子类,会将问题复杂化(flat is better than nested)。</p><h4 id="限制"><a href="#限制" class="headerlink" title="限制"></a>限制</h4><ul><li>所有实例只能拥有父类的名字,不拥有自己的名字。当然设计上也不应该使实例拥有自己的名字。</li></ul><h4 id="适用"><a href="#适用" class="headerlink" title="适用"></a>适用</h4><ul><li><a href="https://en.wikipedia.org/wiki/Strategy_pattern">Strategy Pattern</a></li><li>可以通过<a href="https://en.wikipedia.org/wiki/Decorator_pattern">Decorator Pattern</a>来进行向下扩展。</li></ul><h2 id="CRTP"><a href="#CRTP" class="headerlink" title="CRTP"></a>CRTP</h2><p>所谓接口其实是一种协议,换言之就是,继承接口的子类必须<strong>全部</strong>实现某些功能(函数),由于子类必须不可妥协地全部实现,因此从设计的角度讲,接口的粒度也应该越细越好。</p><p>为了某一个类去实现多个接口,可能不是一种干净的方法,因为这些接口将来可能不一定会被使用。C++ 的设计者为我们提供了无比强大的 template,用来更灵活地设计模块间关系。</p><p>CRTP,即 <a href="https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern">Curiously recurring template pattern</a>。简单说就是将类自身作为自身模板的参数。 <a href="http://www.boost.org/doc/libs/1_54_0/libs/iterator/doc/iterator_facade.html">boost::iterator_facade</a> 就利用了这种方法设计了一个 iterator 的抽象类,我们可以通过继承 <code>boost::iterator_facade</code> 轻易地实现一个 iterator。(吐槽一下,Boost.Iterator 这个库实在是太臃肿了,实际工程上完全可以写一个简易版本)</p><p>我们通过实现一个 <code>IteratorFacade</code> 来介绍这种方法,然而这种方法的实例较长,所以我们分开来讲。</p><h4 id="IteratorFacade"><a href="#IteratorFacade" class="headerlink" title="IteratorFacade"></a>IteratorFacade</h4><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">template</span><<span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span>, <span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">IteratorFacade</span> {<br></code></pre></td></tr></table></figure><p>我们自己定义的 <code>Iterator</code> 只要继承 <code>IteratorFacade</code> 就能拥有一堆跟标准库容器一样的细枝末节的功能了(这些功能确实多而且杂)。我们首先假设 <code>Iterator</code>需要是一个 <a href="http://en.cppreference.com/w/cpp/concept/ForwardIterator">forward iterator</a>。</p><figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs angelscript"><span class="hljs-keyword">class</span> <span class="hljs-symbol">Iterator:</span><br><span class="hljs-symbol"> <span class="hljs-symbol">public</span></span> <span class="hljs-symbol">IteratorFacade</span><<br> <span class="hljs-symbol">Iterator,</span><br><span class="hljs-symbol"> <span class="hljs-symbol">int</span>,</span><br><span class="hljs-symbol"> <span class="hljs-symbol">std</span>::<span class="hljs-symbol">forward_iterator_tag</span></span>> {<br><br></code></pre></td></tr></table></figure><p>其中 <code>Derived</code> 参数就是 <code>Iterator</code> 自己了,而 <code>int</code> 对应的 <code>T</code> 参数表示这个 iterator 所指的值。</p><p>现在我们来仔细看看 <code>IteratorFacade</code> 的定义:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">template</span><<br> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span>,<br> <span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>,<br> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Category</span>><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">IteratorFacade</span> {<br> <span class="hljs-keyword">public</span>:<br> <span class="hljs-keyword">typedef</span> T& Reference;<br> <span class="hljs-function">Derived <span class="hljs-title">Next</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-function">Derived <span class="hljs-title">tmp</span><span class="hljs-params">(derived())</span></span>;<br> IteratorCoreAccess::<span class="hljs-built_in">increment</span>(<span class="hljs-built_in">derived</span>());<br> <span class="hljs-keyword">return</span> tmp;<br> }<br> <span class="hljs-function"><span class="hljs-keyword">template</span><<span class="hljs-keyword">class</span> Facade></span><br><span class="hljs-function"> <span class="hljs-type">static</span> <span class="hljs-keyword">typename</span> Facade::reference <span class="hljs-title">dereference</span><span class="hljs-params">(Facade <span class="hljs-type">const</span> &f)</span> </span>{<br> <span class="hljs-keyword">return</span> f.<span class="hljs-built_in">dereference</span>();<br> }<br> <span class="hljs-function">Reference <span class="hljs-title">Value</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{<br> <span class="hljs-keyword">return</span> IteratorCoreAccess::<span class="hljs-built_in">dereference</span>(<span class="hljs-built_in">derived</span>());<br> }<br> <span class="hljs-keyword">protected</span>:<br> <span class="hljs-function">Derived &<span class="hljs-title">derived</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">return</span> *<span class="hljs-built_in">static_cast</span><Derived *>(<span class="hljs-keyword">this</span>);<br> }<br>};<br></code></pre></td></tr></table></figure><p>这里为了方便,我们使用 <code>Next</code> 而非 <code>operator++</code>(因为 ++ 操作符还分 postfix 和 prefix…),希望阅读到此处的读者不要奇怪。</p><h4 id="实现接口"><a href="#实现接口" class="headerlink" title="实现接口"></a>实现接口</h4><p>我们要在 <code>IteratorFacade</code> 中实现的就是,用户只需实现 <code>increment</code>,<code>dereference</code> 函数,并且将 <code>IteratorCoreAccess</code> 设为友元类,即可实现一个 forward iterator(<code>Next()</code>函数可用)。如果我们想要实现一个 <a href="http://en.cppreference.com/w/cpp/concept/BidirectionalIterator">bidirectional iterator</a>,我们可以实现 <code>decrement</code>,这样 <code>Prev()</code>函数即可用。</p><p>这里的秘密在于 <code>IteratorCoreAccess</code>,由于其中的成员函数用 static 声明,例如只有在 <code>decrement</code> 被调用时,编译器才会去检查 <code>Facade</code> 类型是否具有 member function <code>decrement</code>。</p><figure class="highlight perl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs perl"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IteratorCoreAccess</span> </span>{<br> template<<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">D</span>, <span class="hljs-title">class</span> <span class="hljs-title">T</span>, <span class="hljs-title">class</span> <span class="hljs-title">C</span>> <span class="hljs-title">friend</span></span><br><span class="hljs-class"> <span class="hljs-title">class</span> <span class="hljs-title">IteratorFacade</span></span>;<br> private:<br> template<<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Facade</span>></span><br><span class="hljs-class"> <span class="hljs-title">static</span> <span class="hljs-title">void</span> <span class="hljs-title">increment</span>(<span class="hljs-title">Facade</span> &<span class="hljs-title">f</span>) </span>{<br> f.increment();<br> }<br> template<<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Facade</span>></span><br><span class="hljs-class"> <span class="hljs-title">static</span> <span class="hljs-title">void</span> <span class="hljs-title">decrement</span>(<span class="hljs-title">Facade</span> &<span class="hljs-title">f</span>) </span>{<br> f.decrement();<br> }<br> template<<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Facade</span>></span><br><span class="hljs-class"> <span class="hljs-title">static</span> <span class="hljs-title">typename</span> <span class="hljs-title">Facade</span>::<span class="hljs-title">reference</span> <span class="hljs-title">dereference</span>(<span class="hljs-title">Facade</span> <span class="hljs-title">const</span> &<span class="hljs-title">f</span>) </span>{<br> <span class="hljs-keyword">return</span> f.dereference();<br> }<br> // Objects of this <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">are</span> <span class="hljs-title">useless</span>.</span><br><span class="hljs-class"> <span class="hljs-title">IteratorCoreAccess</span>() = <span class="hljs-title">delete</span></span>;<br>};<br></code></pre></td></tr></table></figure><p>相对于传统的 interface,这种方法(或者说设计模式)是强耦合的。它提供给用户有限的几种方案,用户根据自身需求(比如需要 forward iterator 还是 bidirectional iterator),实现相应的函数。</p><h4 id="限制-1"><a href="#限制-1" class="headerlink" title="限制"></a>限制</h4><ul><li>这种方法几乎没有多态可言,因为父类的名字不可被使用(由于模板参数的不同,<code>IteratorFacade</code> 的两个子类不一定享有同一个父类)。这导致一定程度上扩展性降低,适用于需求极其稳定的情形。</li><li>不可向下扩展。</li></ul><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ul><li>灵活性强。</li><li>简化了接口的设计。有点像 @叛逆者 曾经在知乎上回答的,<a href="https://www.zhihu.com/question/37208466/answer/71549555">游戏主机效率PC高的回答</a>中提到的,把原先积木拼装结构,变成电焊焊死的,更加结实,也失去了可扩展性。</li></ul>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>Swift之AnyClass与动态类型</title>
<link href="/Swift%E4%B9%8BAnyClass%E4%B8%8E%E5%8A%A8%E6%80%81%E7%B1%BB%E5%9E%8B/"/>
<url>/Swift%E4%B9%8BAnyClass%E4%B8%8E%E5%8A%A8%E6%80%81%E7%B1%BB%E5%9E%8B/</url>
<content type="html"><![CDATA[<blockquote><p>这次写一下关于Swift中AnyClass的使用以及动态类型的实例化和使用场景</p></blockquote><h1 id="AnyClass与AnyObject"><a href="#AnyClass与AnyObject" class="headerlink" title="AnyClass与AnyObject"></a>AnyClass与AnyObject</h1><p>Swift中,任何自定义的对象都是<code>AnyObject</code>的子类,类似于Java的Object类(但注意,这和Objective-C的NSObject不同,后者在Swift中是专门的UIKit或者AppKit框架里面定义的类型,而非语言所规定的类型)</p><blockquote><p>AnyClass<br>The protocol to which all class types implicitly conform.</p></blockquote><p>Declaration</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs swift"><span class="hljs-keyword">typealias</span> <span class="hljs-type">AnyClass</span> <span class="hljs-operator">=</span> <span class="hljs-type">AnyObject</span>.<span class="hljs-keyword">Type</span><br></code></pre></td></tr></table></figure><blockquote><p>AnyObject</p></blockquote><p>protocol AnyObject { … }</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs swift"><span class="hljs-type">The</span> <span class="hljs-keyword">protocol</span> <span class="hljs-title class_">to</span> which all classes implicitly conform.<br></code></pre></td></tr></table></figure><p>但是注意,这个AnyObject.Type是一个毕竟是一个接口的Property,所以只能在函数的参数里面使用,如果想要直接获取某个类(而非实例)的类型,使用类名.self即可;如果想要获取一个实例的类的类型,使用.dynamicType;对了,如果对象是NSObject的实例(iOS开发中常用),用classForCoder也是一个选择</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs ruby"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Test</span> {}<br>func f(<span class="hljs-symbol">s:</span> <span class="hljs-title class_">AnyClass</span>) {<br> print(s)<br>}<br>let a = <span class="hljs-title class_">Test</span>.<span class="hljs-variable language_">self</span><br>let b = <span class="hljs-title class_">Test</span>().dynamicType<br>let <span class="hljs-symbol">c:</span><span class="hljs-title class_">AnyClass</span> = <span class="hljs-string">"test"</span>.classForCoder /<span class="hljs-regexp">/注意此时加了classForCoder的方法调用,编译器会推导出""是一个NSString的实例而不是String</span><br><span class="hljs-regexp">f(a) /</span><span class="hljs-regexp">/Test</span><br><span class="hljs-regexp">f(b) /</span><span class="hljs-regexp">/Test</span><br><span class="hljs-regexp">f(c) /</span><span class="hljs-regexp">/NSString</span><br></code></pre></td></tr></table></figure><h1 id="动态实例化"><a href="#动态实例化" class="headerlink" title="动态实例化"></a>动态实例化</h1><p>既然我们有了某个AnyObject的Type,这样我们就可以直接构造出一个类型的实例。使用AnyObject都有的.init方法即可,当然,AnyObject本身并没有init的构造方法……不过首先,我们得改写Test类的init初始化方法,在前面加上required关键字</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs ruby"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Test</span> {<br>required init(){}<br>}<br>let d = a.init() /<span class="hljs-regexp">/ d is an instance of Test</span><br><span class="hljs-regexp">let e = (c as! NSString.Type).init(stringLiteral: "test") /</span><span class="hljs-regexp">/ e is "test"</span><br></code></pre></td></tr></table></figure><p>当然,你也许肯定奇怪那个required的init是什么意思,其实这是Swift编译器为了避免出现你使用的这个Test.Type有继承的子类,然后子类的构造函数中使用了这个Type来构造父类这种边界情况出现(考虑的真细……)</p><blockquote><p>Use an initializer expression to construct an instance of a type from that type’s metatype value. For class instances, the initializer that’s called must be marked with the required keyword or the entire class marked with the final keyword.</p></blockquote><p>而且对于一个Protocol,可以用.Protocol来获取这个Protocol的类型(还是AnyClass),也可以用self来统一处理,因为实际上</p><p><code>metatype-type(.self) -> type.Type | type.Protocol</code></p><p>这样的话,有了动态就可以开始干活了</p><h1 id="Swift的反射"><a href="#Swift的反射" class="headerlink" title="Swift的反射"></a>Swift的反射</h1><p>或许你也想,既然我有了动态的类型实例,那么是不是能通过类似Java的反射,获取某个类型的所有Property,然后直接访问这个Property呢?答案也是有的,不过在Swift中很少用到,这里用到到了Mirror</p><blockquote><p>struct Mirror { … }<br>Representation of the sub-structure and optional “display style” of any arbitrary subject instance.</p></blockquote><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs ruby"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Person</span> {<br> var <span class="hljs-symbol">whatThisProperty:</span><span class="hljs-title class_">String</span><br> init(<span class="hljs-symbol">name:</span><span class="hljs-title class_">String</span>) {<br> <span class="hljs-variable language_">self</span>.whatThisProperty = name<br> }<br>}<br>let p = <span class="hljs-title class_">Person</span>(<span class="hljs-symbol">name:</span> <span class="hljs-string">"Bob"</span>)<br>let mirror = <span class="hljs-title class_">Mirror</span>(<span class="hljs-symbol">reflecting:</span> p)<br>mirror.children.forEach {<br> print(<span class="hljs-string">"\($0.label!): \($0.value)"</span>) /<span class="hljs-regexp">/ whatThisProperty: Bob</span><br><span class="hljs-regexp">}</span><br></code></pre></td></tr></table></figure><p>这里的children返回的是一个AnyForwardCollection,也是可以像数组一般用index来访问或者forEach遍历的,不过索引顺是序按照你的Property的声明顺序</p><h1 id="最后的小应用"><a href="#最后的小应用" class="headerlink" title="最后的小应用"></a>最后的小应用</h1><p>由于我也不怎么会写iOS,有时候遇到这样一种情况:<br>我提供了一个3D Touch的按钮,四个按钮会对应四种ViewController,iOS提供的API可以获取到用户点击的那个按钮对应的设置的一个字符串值,那么,我可以这样来玩……</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs ruby">/<span class="hljs-regexp">/ 3D Touch 传入的字符串来判断返回某个ViewController</span><br><span class="hljs-regexp">static func shortcutToViewController(type:String) -> UIViewController.Type {</span><br><span class="hljs-regexp"> switch type {</span><br><span class="hljs-regexp"> case "pe":</span><br><span class="hljs-regexp"> return RunningViewController.self</span><br><span class="hljs-regexp"> case "curriculum":</span><br><span class="hljs-regexp"> return CurriculumViewController.self</span><br><span class="hljs-regexp"> case "card":</span><br><span class="hljs-regexp"> return SeuCardViewController.self</span><br><span class="hljs-regexp"> case "nic":</span><br><span class="hljs-regexp"> return NicViewController.self</span><br><span class="hljs-regexp"> default: return UIViewController.self</span><br><span class="hljs-regexp"> }</span><br><span class="hljs-regexp">}</span><br></code></pre></td></tr></table></figure><p>这是用来判断字符串来产生对应的ViewController.Type的,然后,在真正需要实例化一个新的ViewController来显示一个View的时候,再实例化</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs ruby">func pushToViewController(<span class="hljs-symbol">vc:</span> <span class="hljs-title class_">UIViewController</span>.<span class="hljs-title class_">Type</span>) {<br> <span class="hljs-regexp">//</span> 确保要显示的<span class="hljs-title class_">ViewController</span>不是顶层显示的<span class="hljs-title class_">ViewController</span><br> guard let duplication = navigationController?.topViewController?.isKindOfClass(vc) <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }<br> <span class="hljs-keyword">if</span> duplication { <span class="hljs-regexp">//</span> 检查失败,重复的<span class="hljs-title class_">ViewController</span>,不需要跳转<br> <span class="hljs-keyword">return</span><br> }<br> let viewController = vc.init(<span class="hljs-symbol">nibName:</span> <span class="hljs-string">"\(vc.classForCoder())"</span>, <span class="hljs-symbol">bundle:</span> <span class="hljs-literal">nil</span>) /<span class="hljs-regexp">/ 初始化ViewController</span><br><span class="hljs-regexp"> navigationController?.pushViewController(viewController, animated: true)</span><br><span class="hljs-regexp">}</span><br></code></pre></td></tr></table></figure><p>这也是一种比较奇怪的方式……不过如果不这样,就会导致在主类和上层里面引入过多的Switch Case或者导致代码中出现纯字符串定义的nibName,这对以后重构非常不利。</p><h1 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h1><p>嗯,好久没写东西了……主要最近在学编译原理,之后会有几篇讲解通过Java实现一个简单的支持CFG的Yacc(误)的东西……顺便就当复习编译原理的Parser部分了。</p><p>Swift如今开源了:<a href="https://github.com/apple/swift">Swift</a>,不到几天Star就超过了2年出头的Rust(lol)……现如今Star也到了2W5的程度</p><p>虽然UIKit和AppKit这种宝贵的财富Apple肯定不会开源,不过Swift标准库的实现也是越来越完善,而且也有Linux的版本,很多第三方的库开始加入了对Linux的支持(非面向iOS和OS X开发的,比如对SQLite,Redis的wrapper)</p><p>Swift作为一个Rust的对手,一个完全抛弃了C的现代语言,之后在除客户端开发外,更可能的领域就是系统编程和服务端编程了吧。希望能够在在安全性,易用性,效率上达到一个更大的高度,让我们这种开发者也能用的爽,用的顺。</p>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title>我对C++的编程思考</title>
<link href="/%E6%88%91%E5%AF%B9C-%E7%9A%84%E7%BC%96%E7%A8%8B%E6%80%9D%E8%80%83/"/>
<url>/%E6%88%91%E5%AF%B9C-%E7%9A%84%E7%BC%96%E7%A8%8B%E6%80%9D%E8%80%83/</url>
<content type="html"><![CDATA[<p>我最近想要这样一个分享平台:它既可以作为我的笔记工具,记录一些文字不太多又不太少的技术知识,又可以作为一个社交平台让别人认识我,让我可以装逼。微博限制字数,朋友圈也不太合适,于是我还是在博客里开一个以后可能会继续更新的文章。现在 I++ 里的博文只有我在做 C++ 的分享,以后如果有人做同样的事情,甚至写了同名的文章,那就可能要给这整个博客做点修改了,比如在博客的 timeline 上加上作者头像和信息,以前一直想做,因为比较懒所以搁置了。</p><h3 id="应该使用-Returned-Value-Optimization-还是-std-move"><a href="#应该使用-Returned-Value-Optimization-还是-std-move" class="headerlink" title="应该使用 Returned Value Optimization 还是 std::move"></a>应该使用 Returned Value Optimization 还是 <code>std::move</code></h3><hr><p>分享一篇我读过的文章 <a href="https://www.ibm.com/developerworks/community/blogs/5894415f-be62-4bc0-81c5-3956e82276f3/entry/RVO_V_S_std_move?lang=zh">RVO V.S. std::move</a></p><p>这里讲到了几个以前我未曾求解的顾虑和疑惑:</p><ul><li><p>C++11 其实已经将 RVO 写在标准中并为大多数编译器支持。这样编写合适的代码就不会再有顾虑了。</p></li><li><p>std::move 的使用场景。实际上 std::move 还是会造成一定的开销,只是相比于复制,移动会更快。</p></li></ul><h3 id="C-使用指针还是创建对象?"><a href="#C-使用指针还是创建对象?" class="headerlink" title="C++ 使用指针还是创建对象?"></a>C++ 使用指针还是创建对象?</h3><hr><p>阅读这篇文章:<a href="http://blog.jobbole.com/90147/">C++中为什么要用指针,而不直接使用对象?</a></p><p>最近写一些代码的时候,仅仅只是因为写的顺了,没经过思考地使用了 <code>std::unique_ptr</code>,现在觉得有些后悔,因为这些地方完全可以用对象代替。但是因为除了代码稍微混乱一些之外,并无伤大雅,所以还在犹豫是否要对他们进行修改。以我现在比较浅薄的经验看来,指针能够让我感觉自然的使用场景大概有这些:</p><ul><li>延迟分配内存(动态分配内存)</li></ul><p>简单的情况是</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs css"><span class="hljs-selector-tag">Object</span> arr<span class="hljs-selector-attr">[100]</span>;<br><span class="hljs-selector-tag">Object</span>* arr<span class="hljs-selector-attr">[100]</span>;<br></code></pre></td></tr></table></figure><p>对比于上下两种情况,后者在数组初始化的时候开销更小,在有些时候是更好的做法。</p><ul><li>用于 forward declaration</li></ul><p>因为 C++ 要求声明对象必须要有类的定义,这就要求必须要 include 相关头文件,为了减少头文件依赖,有时候会用到指针。这种情况也不多。</p><ul><li>shared_ptr</li></ul><p>针对某一内存块会被多个使用者同时使用的情况。</p><ul><li>用于保留容器元素的继承属性</li></ul><p>假如有 <code>B: public A</code> 与集合 <code>std::set<A></code>。现在我们需要把 B 放入 A 的集合中,在 Java 中,集合内存储的元素依然是 B,而在 C++ 中,编译器会将 B 复制给类型为 A 的集合元素。此时集合内实际存储的是 A。我们希望实现 Java 的效果,就要使用指针。显然 <code>std::unique_ptr</code> 在这里可以发挥作用。</p><p>讲到这里,我想谈一谈我最近遇到的关于 <code>std::unique_ptr</code> 的几个小经历。</p><p>###有关 <code>std::unique_ptr</code> 的编程经历</p><hr><p>语法多如 C++ 的语言总有很多隐形的坑,在编写代码的过程中有时会陷入困境,不知道如何解决,但是山重水复之后,往往会发现柳暗花明的方法。</p><ul><li>在 stl 容器中存储 unique_ptr</li></ul><p>我们以 <code>std::set</code> 来举例。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">template</span> < <span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>, <span class="hljs-comment">// set::key_type/value_type</span><br> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Compare</span> = less<T>, <span class="hljs-comment">// set::key_compare/value_compare</span><br> <span class="hljs-keyword">class</span> Alloc = allocator<T> <span class="hljs-comment">// set::allocator_type</span><br> > <span class="hljs-keyword">class</span> set;<br></code></pre></td></tr></table></figure><p>这个接口告诉我们,用户需要在 <code>std::set</code> 声明处定义比较函数 comparator。换句话说,一个 <code>std::set</code> 由两个东西定义,一个是元素类型,一个是元素之间比较的方法(不考虑 allocator)。</p><p>我们其实可以将 comparator 从 set 的定义中解耦出来。实际上 C++ 有多种实现自定义比较的方法。如这篇文章所讲 <a href="http://fusharblog.com/3-ways-to-define-comparison-functions-in-cpp/">3 Ways to Define Comparison Functions in C++</a>。简单说就是通过重载 <code>operator<</code> 来自定义比较方法,这样做就<strong>无需显式定义 comparator</strong>。当然,更多的时候,我还是喜欢显式定义 comparator,这样能更清楚的表明,<strong>一个 set 只允许一种comparator</strong>。</p><p>然而,这与 Java 不同。Java 的模板类型会<a href="http://www.infoq.com/cn/articles/cf-java-generics">在运行时用具体的类型代替</a>。也就是说,给定集合 <code>set<T></code> 可以代表的不只是 T 的集合,也可以是 T 的子类 SubT1 的集合,SubT2 的集合等等,并且他们之间也互相 compatible(可以直接用等号赋值)。这对面向对象来说是十分自然的一种设计,这意味着当我们将 SubT 继承自 T 之后,我们也同时将 SubT 的 container 继承自 T 的 container。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//Example Code</span><br>TreeSet<E> treeSet = <span class="hljs-keyword">new</span> <span class="hljs-title class_">TreeSet</span><E>();<br><span class="hljs-type">SubE</span> <span class="hljs-variable">se</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">SubE</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>);<br>treeSet.add(se);<br><span class="hljs-comment">// E 的 print 会输出 E</span><br><span class="hljs-comment">// SubE 的 print 会输出 SubE</span><br><span class="hljs-comment">// 这里输出的是 SubE</span><br>treeSet.first().print();<br></code></pre></td></tr></table></figure><p>而在 C++ 中,我们却需要显式地声明 <code>set<T></code> 和 <code>set<SubT1></code>, <code>set<SubT2></code> … 遇到这个问题,我们可能会开始想到使用 <code>dynamic_cast</code>,然后随即又被<a href="https://www.zhihu.com/question/22445339">它的低效率</a>(见 effective c++ 中的条款27)给吓跑。如果我们想要避免 <code>dynamic_cast</code>,本质上,我们就需要一些 workaround **避免 <code>dynamic_cast</code>**。</p><p>整理一下思路,我们希望能在 <code>std::set</code> 中实现多态(Polymorphism),这其中的核心问题在于实现多态的 comparator。而我们又不希望通过 RTTI 实现 downcast(dynamic_cast),这意味着,我们<strong>不能直接使用子类 SubT 进行自定义比较</strong>。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//Example Code</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">A</span> {<br><span class="hljs-keyword">public</span>:<br><span class="hljs-type">int</span> a;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">B</span> : <span class="hljs-keyword">public</span> A {<br><span class="hljs-keyword">public</span>:<br><span class="hljs-type">int</span> b;<br>};<br><br><span class="hljs-keyword">inline</span> <span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span><(<span class="hljs-type">const</span> std::unique_ptr<A> &i1,<br> <span class="hljs-type">const</span> std::unique_ptr<A> &i2) {<br><span class="hljs-keyword">return</span> i1->a < i2->a;<br>}<br><br><br><span class="hljs-keyword">typedef</span> std::set<std::unique_ptr<A> > ASet;<br></code></pre></td></tr></table></figure><p>上面的代码中,在 <code>ASet</code> 中插入 B 的指针时,不会对属性 <code>b</code> 的值进行比较。简单说,相同 <code>a</code> 值,不同 <code>b</code> 值的 B 会被视作相同。</p><p>排除了几种选择之后,我们可以想到用 virtual function,利用指针保留继承链。最终我使用的方法是<em><strong>留后门</strong></em>:既然不能直接比较 <code>b</code> 的值,就只能绕弯路,<strong>间接地进行比较</strong>。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">A</span> {<br><span class="hljs-keyword">public</span>:<br><span class="hljs-function"><span class="hljs-keyword">virtual</span> boost::any <span class="hljs-title">Others</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{<br><span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">bool</span> <span class="hljs-title">CompareOthers</span><span class="hljs-params">(<span class="hljs-type">const</span> A& a)</span> <span class="hljs-type">const</span> </span>{<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>}<br><br><span class="hljs-type">int</span> a;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">B</span> : <span class="hljs-keyword">public</span> A {<br><span class="hljs-keyword">public</span>:<br><span class="hljs-function"><span class="hljs-keyword">virtual</span> boost::any <span class="hljs-title">Others</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{<br><span class="hljs-keyword">return</span> b;<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">bool</span> <span class="hljs-title">CompareOthers</span><span class="hljs-params">(<span class="hljs-type">const</span> A& a)</span> <span class="hljs-type">const</span> </span>{<br><span class="hljs-keyword">return</span> b < boost::<span class="hljs-built_in">any_cast</span><<span class="hljs-type">int</span>>(rhs.<span class="hljs-built_in">Others</span>());<br>}<br><br><span class="hljs-type">int</span> b;<br>};<br></code></pre></td></tr></table></figure><p>这里就可以看到我们留后门的两个函数 <code>Others</code> 和 <code>CompareOthers</code>,间接地利用类型转换来实现。最终的自定义比较函数就可以这么写:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">inline</span> <span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span><(<span class="hljs-type">const</span> std::unique_ptr<A> &i1,<br> <span class="hljs-type">const</span> std::unique_ptr<A> &i2) {<br><span class="hljs-keyword">if</span>(i1->a == i2->a) {<br><span class="hljs-keyword">return</span> i1-><span class="hljs-built_in">CompareOthers</span>(*i2);<br>}<br><span class="hljs-keyword">return</span> i1->a < i2->a;<br>}<br></code></pre></td></tr></table></figure><p>这样我们算是基本完成了需求的实现。</p><p>还有一点。上面我们使用了 <code>boost::any</code> 表示可以接受任何值。这样写比较直观,但是<a href="http://www.nullptr.me/2011/05/10/boostany/"><code>any_cast</code> 的实际性能上貌似与 <code>dynamic_cast</code> 相差不大</a>,都是使用 RTTI。在 benchmark 下,甚至可能出现 <a href="https://felipedelamuerte.wordpress.com/2012/04/06/why-you-shouldnt-use-boostany-especially-not-in-time-critical-code/"><code>boost::any</code> 比 <code>void *</code> 性能相差近50倍的情况</a>。不过这里有充足的优化空间,我们可以完全不必使用 <code>boost::any_cast</code>,而是简单使用 <code>static_cast</code> 就可完成任务。所以这个方法可行。</p><p>看上去,我们好像漂亮地完成了一个(可以不使用 RTTI 的)<a href="https://en.wikipedia.org/wiki/Type_erasure"><em><strong>Type Erasure</strong></em></a>。然而,这意味着我们依然需要使用 downcast 来实现 <em><strong>Type inference</strong></em>,否则,我们的子类 SubT 将无法在任何使用了 <code>std::set<std::unique_ptr<T> ></code> 的地方使用。除非我们可以将 T 与其子类完全地从它们的 caller 中解耦出来,这种方案才有意义。换句话说,我们需要让 caller 完全不必在乎它们使用的类型是 T,还是 SubT1,SubT2,所有的细节都由虚函数来完成。如果可以做到这样,我们就能在不使用 RTTI 的情况下,像 Java 那样完成工作。</p><p>当然,如果使用了 RTTI,那 C++ 和 Java 的区别就很小了。</p><p>多数情况下,我们没有办法不使用 downcast。那么现在问题的核心在于,有没有其他的方法,可以在不使用 RTTI 的前提下,实现 downcast。</p><p>如果在对象较小的情况下,我们可以使用 <a href="http://www.cplusplus.com/forum/articles/18757/"><em><strong>the clone pattern</strong></em></a> 来实现,但在这里不是 clone,我们定义一个 DownCast 函数。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">template</span><<span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>><br><span class="hljs-keyword">typename</span> std::enable_if<std::is_same<T, B>::value, B>::<span class="hljs-function">type</span><br><span class="hljs-function"><span class="hljs-title">DownCast</span><span class="hljs-params">(<span class="hljs-type">const</span> std::unique_ptr<A> &pA)</span> </span>{<br> <span class="hljs-function">B <span class="hljs-title">ret</span><span class="hljs-params">(pA->a, boost::any_cast<<span class="hljs-type">int</span>>(pA->Others()))</span></span>;<br> <span class="hljs-keyword">return</span> ret;<br>}<br><br><span class="hljs-comment">// 我们在这里利用复制来进行 downcast,不使用 RTTI</span><br>B b = <span class="hljs-built_in">DownCast</span><B>(pA);<br></code></pre></td></tr></table></figure><p>当然,我们这里依然假设 <code>boost::any_cast</code> 是一个 exception safe 且 without RTTI 的转换函数。因为我们在这里完全可以优化到这样。</p><p>顺便扔出一个刚刚学会的名词:<a href="https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error">SFINAE</a></p><hr><h3 id="更新一下"><a href="#更新一下" class="headerlink" title="更新一下"></a>更新一下</h3><p>之前在 <a href="https://felipedelamuerte.wordpress.com/2012/04/06/why-you-shouldnt-use-boostany-especially-not-in-time-critical-code/"><code>boost::any</code> 比 <code>void *</code> 性能相差近50倍</a><br>的链接里,博主提供了测试代码。测试程序测试对基本类型<code>size_t</code>转换所用时长。</p><p>benchmark的结果和我的差不太多。<br>在我的 MacBook Air (13-inch, Early 2014),clang version 3.6.2 下</p><p>debug mode 的数据是:</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">boost</span>::any - <span class="hljs-number">18</span>.<span class="hljs-number">8269</span><br><span class="hljs-attribute">boost</span>::spirit::hold_any - <span class="hljs-number">3</span>.<span class="hljs-number">50993</span><br><span class="hljs-attribute">void</span> ptr - <span class="hljs-number">0</span>.<span class="hljs-number">860663</span><br></code></pre></td></tr></table></figure><p>release mode 的数据是</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">boost</span>::any - <span class="hljs-number">8</span>.<span class="hljs-number">41195</span><br><span class="hljs-attribute">boost</span>::spirit::hold_any - <span class="hljs-number">0</span>.<span class="hljs-number">499917</span><br><span class="hljs-attribute">void</span> ptr - <span class="hljs-number">0</span>.<span class="hljs-number">043697</span><br></code></pre></td></tr></table></figure><p>公有变量 x 也用了 <code>volatile</code> 修饰,结果应该可靠,可见,<code>boost::any</code> 确实较慢。拖累性能的地方在于exception,可能还因为一部分中间的执行代码,但应该与代码中极少数使用 <code>typeid</code> 的地方无关,知乎上 R大 也曾经解释过,<a href="https://www.zhihu.com/question/38997922/answer/79179526">而当typeid运算符应用在一个指向多态类型对象的指针上的时候,typeid的实现才需要有运行时行为。</a></p><p>所以我们在上面实现 CompareOthers 和 DownCast 的时候,完全可以不用考虑异常,类型转换失败则程序应停止运行而不是抛出异常。</p><hr><p>###C++ 中指针的不便之处</p><p>跑题了很多,将这次的经历归纳在 <code>std::unique_ptr</code> 下更多的是因为,C++ 中指针与对象之间被区分开,这就意味着,<br>我们在判断两个 <code>std::set<std::unique_ptr<A> ></code> 是否相同时,默认对指针进行比较,而不是对对象进行比较。<br>这个时候,我们要为此编写 <code>std::set</code> 的自定义比较函数。</p><p>我们在使用 <code>std::unordered_set<std::unique_ptr<A> ></code> 时也会有相同的问题,同样的,我们也要编写自定义 hasher。</p><p>如果这被 C++ 标准认为是自然的行为的话,那么,我们来看看它会带来的不便之处。</p><p>不便之处在我们使用 <code>std::set<std::set<std::unique_ptr> ></code> 时尤为突出。虽然 <code>unique_ptr</code> 禁止 copy,但是 set<br>是允许 copy 的,这就意味这我们可能需要重新编写 set<unique_ptr> 的复制函数。这让我很疑惑为什么 unique_ptr<br>没有提供默认的 deep copy 接口来复制所指的对象。</p><p>我们为了使用 <code>std::set<std::unique_ptr></code>,编写了custom comparator。在使用 <code>std::set<std::set<std::unique_ptr> ></code> 时,我们无需再次编写 comparator(stl 为提供 <code>std::set</code> 了 rational operator)。然而,如果我们使用的是 <code>std::unordered_set<std::set<std::unique_ptr<A> > ></code>,我们可能会这么写:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">AHasher</span> {<br> <span class="hljs-function"><span class="hljs-type">size_t</span></span><br><span class="hljs-function"> <span class="hljs-title">operator</span><span class="hljs-params">()</span><span class="hljs-params">(<span class="hljs-type">const</span> std::set<std::unique_ptr<A> &val)</span> <span class="hljs-type">const</span> </span>{<br> <span class="hljs-keyword">return</span> boost::<span class="hljs-built_in">hash_value</span>(val);<br> }<br>};<br></code></pre></td></tr></table></figure><p>这里使用了 <code>boost/functional/hash.hpp</code>。这里看起来合理,但程序运行的时候,我们会发现这与我们预期的不符。这是因为,我们忽略了为 <code>std::unique_ptr<A></code> 自定义 hasher。这里默认地是对指针的值进行 hash,而不是对对象进行 hash。</p><p>重写一下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">AHasher</span> {<br> <span class="hljs-function"><span class="hljs-type">size_t</span></span><br><span class="hljs-function"> <span class="hljs-title">operator</span><span class="hljs-params">()</span><span class="hljs-params">(<span class="hljs-type">const</span> std::set<std::unique_ptr<A> &val)</span> <span class="hljs-type">const</span> </span>{<br> std::<span class="hljs-type">size_t</span> seed = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> &it : val) {<br> boost::<span class="hljs-built_in">hash_combine</span>(seed, it-><span class="hljs-built_in">HashValue</span>());<br> }<br> <span class="hljs-keyword">return</span> seed;<br> }<br>};<br></code></pre></td></tr></table></figure><p>原来我们在 set 中存储本来的对象,只需要定义好对象本身的 rational operator 和 copy function,亦或者是 <code>unordered_set</code><br>中的 hasher,就可以很自然地使用了。使用了 <code>unique_ptr</code> 之后,我们还需要重新定义 unique_ptr 的对应函数,才能将 <code>unique_ptr</code> 完全视作对象。</p>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>React-Native -- 下一代UI开发思维</title>
<link href="/React-Native-%E4%B8%8B%E4%B8%80%E4%BB%A3UI%E5%BC%80%E5%8F%91%E6%80%9D%E7%BB%B4/"/>
<url>/React-Native-%E4%B8%8B%E4%B8%80%E4%BB%A3UI%E5%BC%80%E5%8F%91%E6%80%9D%E7%BB%B4/</url>
<content type="html"><![CDATA[<h2 id="React-与状态"><a href="#React-与状态" class="headerlink" title="React 与状态"></a>React 与状态</h2><p><code>React.js</code>自从Facebook一推出,就受到Web前端工程师的强烈推崇。虽说曾经火过一时的<code>Angular.js</code>颠覆了前端的工程,但是<code>React</code>更多颠覆的,是下一代UI编程的思维。</p><p>传统UI编程,基本很多地方都需要将数据来源,绑定到对应的UI对象,比如用户点击了一个操作,更改了名称,那么你需要更新执行一个回调函数来处理点击操作,并且把新的数据更新原有的UI对象的属性,比如大概就是这样的东西</p><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs haskell"><span class="hljs-title">func</span> onClick(sender) {<br>var <span class="hljs-class"><span class="hljs-keyword">data</span> = getData(<span class="hljs-title">sender</span>);</span><br>self.button.title = <span class="hljs-class"><span class="hljs-keyword">data</span>.name;</span><br>self.button.color = <span class="hljs-class"><span class="hljs-keyword">data</span>.color;</span><br>}<br></code></pre></td></tr></table></figure><p>这样虽然说直观,但是有很大的问题。试想,假如有很多种的回调函数,每个回调函数监听不同的操作,比如<code>onMounseDown</code>,<code>onMouseUp</code>,<code>onKeyDown</code>,<code>onScroll</code>……甚至根据不同的sender,我们会有不同的操作,我们就必须得手写很多机械的</p><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs haskell"><span class="hljs-title">self</span>.xx = <span class="hljs-class"><span class="hljs-keyword">data</span>.xx</span><br><span class="hljs-title">self</span>.xy = <span class="hljs-class"><span class="hljs-keyword">data</span>.xy</span><br></code></pre></td></tr></table></figure><p>这类的代码,这就给日后维护和扩展带来了灾难,假如我要换一个UI组件,又得一个个检查是否赋值成功;假如我要把这个UI组件在新的UIView里面重用,我又得改动所有的赋值代码,对于大型项目这种UI对象成百上千,UI属性上万,这样是非常可怕的。</p><p>而<code>React</code>,就将所有的赋值,数据绑定,抽象成为一个个状态,不同的事件监听,就是不同的状态而已,而这些状态之间相互独立,不会受到某些全局变量更改而造成UI混乱的情况,更好的是,开发者不需要考虑到底这个属性什么时候赋值,是在数据更新之前还是之后,需不需要定时刷新这种无意义的苦力活上。</p><h2 id="React-Native-示例"><a href="#React-Native-示例" class="headerlink" title="React-Native 示例"></a>React-Native 示例</h2><blockquote><p>完整代码:<a href="https://facebook.github.io/react-native/docs/tutorial.html#content">Tutorial</a></p></blockquote><p>看看<code>React-Native</code>的sample,需求就是实现一个电影列表显示的View。类似这样:</p><p><img src="https://facebook.github.io/react-native/img/TutorialFinal.png" alt="iOS"> <img src="https://facebook.github.io/react-native/img/TutorialFinal2.png" alt="Android"></p><p>这段代码中,只有View和ViewModel(Model就是临时的JSON),React用状态把UI属性和数据绑定起来,从而避免了事件监听手动判断时机来赋值</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> <span class="hljs-title class_">AwesomeProject</span> = <span class="hljs-title class_">React</span>.<span class="hljs-title function_">createClass</span>({<br> <span class="hljs-comment">//初始状态,不渲染</span><br> <span class="hljs-attr">getInitialState</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">return</span> {<br> <span class="hljs-attr">dataSource</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">ListView</span>.<span class="hljs-title class_">DataSource</span>({<br> <span class="hljs-attr">rowHasChanged</span>: <span class="hljs-function">(<span class="hljs-params">row1, row2</span>) =></span> row1 !== row2,<br> }),<br> <span class="hljs-attr">loaded</span>: <span class="hljs-literal">false</span>,<br> };<br> },<br><br> <span class="hljs-comment">//模型层变动导致状态改变,渲染</span><br><br> <span class="hljs-attr">fetchData</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-title function_">fetch</span>(<span class="hljs-variable constant_">REQUEST_URL</span>)<br> .<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">response</span>) =></span> response.<span class="hljs-title function_">json</span>())<br> .<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">responseData</span>) =></span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>({<br> <span class="hljs-attr">dataSource</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">dataSource</span>.<span class="hljs-title function_">cloneWithRows</span>(responseData.<span class="hljs-property">movies</span>),<br> <span class="hljs-attr">loaded</span>: <span class="hljs-literal">true</span>,<br> });<br> })<br> .<span class="hljs-title function_">done</span>();<br> },<br><br> <span class="hljs-comment">//渲染层,绑定状态和UI属性</span><br><br> <span class="hljs-attr">render</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">return</span> (<br> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">ListView</span>//<span class="hljs-attr">这里是JSX语法</span>,<span class="hljs-attr">在JS里面返回标签</span></span></span><br><span class="hljs-tag"><span class="language-xml"> <span class="hljs-attr">dataSource</span>=<span class="hljs-string">{this.state.dataSource}</span></span></span><br><span class="hljs-tag"><span class="language-xml"> <span class="hljs-attr">renderRow</span>=<span class="hljs-string">{this.renderMovie}</span></span></span><br><span class="hljs-tag"><span class="language-xml"> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.listView}</span></span></span><br><span class="hljs-tag"><span class="language-xml"> /></span></span><br> );<br> }<br> }<br></code></pre></td></tr></table></figure><blockquote><p>完整代码:<a href="https://github.com/lizhuoli1126/iOS-Demo-Project">iOS Demo</a></p></blockquote><p>相比来说,原生Cocoa Touch的实现,就要丑陋的多了,尤其是渲染部分绑定UI对象的属性和数据来源,假如你有多处数据来源,多种UI属性,你就得写很多判断来保证你的UI对象的属性符合预期的赋值顺序。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">//初始化UI对象,调用模型去获取数据</span><br>override func <span class="hljs-title function_">viewDidLoad</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-variable language_">super</span>.<span class="hljs-title function_">viewDidLoad</span>()<br> <span class="hljs-title function_">fetchData</span>()<br>}<br><br><span class="hljs-comment">//获取数据,需要手动维护状态,比如开启indicator(旋转等待条),数据获取成功后渲染</span><br>func <span class="hljs-title function_">fetchData</span>(<span class="hljs-params"></span>) {<br> indicator.<span class="hljs-title function_">startAnimating</span>()<br> <span class="hljs-title function_">request</span>(.<span class="hljs-property">GET</span>, <span class="hljs-variable constant_">API_URL</span>, <span class="hljs-attr">parameters</span>: [<span class="hljs-string">"apikey"</span>: <span class="hljs-variable constant_">API_KEY</span>, <span class="hljs-string">"page_limit"</span>: <span class="hljs-variable constant_">PAGE_SIZE</span>])<br> .<span class="hljs-property">responseJSON</span>{ _, _, data, _ <span class="hljs-keyword">in</span><br> self.<span class="hljs-title function_">render</span>(<span class="hljs-title class_">JSON</span>(data!))<br> }<br>}<br><br><span class="hljs-comment">//渲染UI对象,需要手动维护状态,比如关闭indicator(旋转等待条)</span><br>func <span class="hljs-title function_">render</span>(<span class="hljs-params">result:<span class="hljs-built_in">JSON</span></span>) {<br> indicator.<span class="hljs-title function_">stopAnimating</span>()<br> movieJSON = result<br> self.<span class="hljs-property">tableView</span>.<span class="hljs-title function_">reloadData</span>()<br>}<br><br><span class="hljs-comment">//这都算好的了,TableView会自动回调你的代码,返回这个TableView里面的Cell个数</span><br>func <span class="hljs-title function_">tableView</span>(<span class="hljs-attr">tableView</span>: <span class="hljs-title class_">UITableView</span>, numberOfRowsInSection <span class="hljs-attr">section</span>: <span class="hljs-title class_">Int</span>) -> <span class="hljs-title class_">Int</span> {<br> <span class="hljs-keyword">var</span> movieNum = movieJSON[<span class="hljs-string">"movies"</span>].<span class="hljs-property">arrayValue</span>.<span class="hljs-property">count</span><br> <span class="hljs-keyword">return</span> movieNum<br>}<br><br><span class="hljs-comment">//可怕的地方,需要手动赋值给UI对象的属性</span><br>func <span class="hljs-title function_">tableView</span>(<span class="hljs-attr">tableView</span>: <span class="hljs-title class_">UITableView</span>, cellForRowAtIndexPath <span class="hljs-attr">indexPath</span>: <span class="hljs-title class_">NSIndexPath</span>) -> <span class="hljs-title class_">UITableViewCell</span> {<br> <span class="hljs-keyword">let</span> cell = self.<span class="hljs-property">tableView</span>.<span class="hljs-title function_">dequeueReusableCellWithIdentifier</span>(<span class="hljs-string">"movieIdentifier"</span>, <span class="hljs-attr">forIndexPath</span>: indexPath)<br> <span class="hljs-keyword">as</span>! <span class="hljs-title class_">MovieTableViewCell</span><br><br> <span class="hljs-keyword">let</span> row = indexPath.<span class="hljs-property">row</span><br><br> <span class="hljs-comment">//手动维护状态,JSON解析再赋值给对应的UI对象,这里省略了UI对象的代码和样式</span><br> <span class="hljs-comment">//(一般可以通过Interface Builder做或者专门一个类写UI代码)</span><br> cell.<span class="hljs-property">movieTitle</span>.<span class="hljs-property">text</span> = movieJSON[<span class="hljs-string">"movies"</span>][row][<span class="hljs-string">"title"</span>].<span class="hljs-property">stringValue</span><br> cell.<span class="hljs-property">movieYear</span>.<span class="hljs-property">text</span> = movieJSON[<span class="hljs-string">"movies"</span>][row][<span class="hljs-string">"year"</span>].<span class="hljs-property">stringValue</span><br> <span class="hljs-keyword">var</span> movieImageUrl = movieJSON[<span class="hljs-string">"movies"</span>][row][<span class="hljs-string">"posters"</span>][<span class="hljs-string">"thumbnail"</span>].<span class="hljs-property">stringValue</span><br><br> <span class="hljs-comment">//最可怕是渲染中还需要新的数据,再回调模型,就很难维护了</span><br> <span class="hljs-keyword">if</span> cell.<span class="hljs-property">movieImage</span>.<span class="hljs-property">image</span> == nil {<br> <span class="hljs-title function_">request</span>(.<span class="hljs-property">GET</span>, movieImageUrl).<span class="hljs-property">response</span>{ _, _, data, _ <span class="hljs-keyword">in</span><br> <span class="hljs-keyword">let</span> movieImage = <span class="hljs-title class_">UIImage</span>(<span class="hljs-attr">data</span>: data <span class="hljs-keyword">as</span>! <span class="hljs-title class_">NSData</span>)<br> cell.<span class="hljs-property">movieImage</span>.<span class="hljs-property">image</span> = movieImage<br> }<br> }<br><br> <span class="hljs-keyword">return</span> cell<br>}<br></code></pre></td></tr></table></figure><h1 id="React-与函数式"><a href="#React-与函数式" class="headerlink" title="React 与函数式"></a>React 与函数式</h1><p><code>React</code>更多的,就是一种类似函数式的想法,把UI对象属性,数据来源,当作一个Monad包裹起来,传统意义上的不同数据来源进行UI属性赋值,相当于这个Monad经过不同的函数作用,达到状态的切换,好处就是大大减少了开发者手动维护UI属性的工作,而且可以达到更高的开发效率。而且再也不怕扩展了,因为这时候可以把多个组件分配给不同的人,每个人完全不需要管别人内部的变量名是什么,UI属性是什么,只要把自己的状态管理好,Model层接口统一,剩下的合并即可。</p><h1 id="React-与效率"><a href="#React-与效率" class="headerlink" title="React 与效率"></a>React 与效率</h1><p>既然提到了状态,因为<code>React</code>采取<code>VirtualDOM</code>来diff需要进行状态更新的UI对象,每次确保了只更新属性发生改变的部分。实现也很高效,使用了一个普通的二叉树<code>vtree</code>。每个结点<code>vnode</code>就是对应的Tag,比如<Image>之类,结点还存储了一个struct用来保存这些Tag的属性(比如Image的size)</p><p>其中的diff算法可以一看……代码地址在:<a href="https://github.com/Matt-Esch/virtual-dom/blob/master/vtree/diff.js">GitHub-vtree</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">//主要逻辑,就是对b树进行reorder,找出a树与b树的同结点不同属性的diff</span><br><span class="hljs-comment">//剩下可以无痛patch的部分只需要下面的for循环就可以处理</span><br><span class="hljs-keyword">var</span> aChildren = a.<span class="hljs-property">children</span><br><span class="hljs-keyword">var</span> orderedSet = <span class="hljs-title function_">reorder</span>(aChildren, b.<span class="hljs-property">children</span>)<br><span class="hljs-keyword">var</span> bChildren = orderedSet.<span class="hljs-property">children</span><br><br><span class="hljs-keyword">var</span> len = aLen > bLen ? aLen : bLen<br><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i < len; i++) {<br> <span class="hljs-keyword">var</span> leftNode = aChildren[i]<br> <span class="hljs-keyword">var</span> rightNode = bChildren[i]<br> index += <span class="hljs-number">1</span><br><br> <span class="hljs-keyword">if</span> (!leftNode) {<br> <span class="hljs-keyword">if</span> (rightNode) {<br> <span class="hljs-comment">//这里处理的多余的结点,直接加到b树上即可</span><br> apply = <span class="hljs-title function_">appendPatch</span>(apply,<br> <span class="hljs-keyword">new</span> <span class="hljs-title class_">VPatch</span>(<span class="hljs-title class_">VPatch</span>.<span class="hljs-property">INSERT</span>, <span class="hljs-literal">null</span>, rightNode))<br> }<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-title function_">walk</span>(leftNode, rightNode, patch, index)<br> }<br>}<br></code></pre></td></tr></table></figure><p>这是核心reorder代码,目标找到是同一结点不同属性的diff,多出来的不需要管</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">//遍历a树,如果b树中结点集合含有a的key,标记下b树中这些key,更新旧的标记</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span> ; i < aChildren.<span class="hljs-property">length</span>; i++) {<br> <span class="hljs-keyword">var</span> aItem = aChildren[i]<br> <span class="hljs-keyword">var</span> itemIndex<br><br> <span class="hljs-keyword">if</span> (aItem.<span class="hljs-property">key</span>) {<br> <span class="hljs-keyword">if</span> (bKeys.<span class="hljs-title function_">hasOwnProperty</span>(aItem.<span class="hljs-property">key</span>)) {<br> <span class="hljs-comment">// Match up the old keys</span><br> itemIndex = bKeys[aItem.<span class="hljs-property">key</span>]<br> newChildren.<span class="hljs-title function_">push</span>(bChildren[itemIndex])<br><br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// Remove old keyed items</span><br> itemIndex = i - deletedItems++<br> newChildren.<span class="hljs-title function_">push</span>(<span class="hljs-literal">null</span>)<br> }<br> }<br> <span class="hljs-comment">//......</span><br>}<br><br><br><span class="hljs-comment">//遍历b树,添加上面标记的所有key到一个集合,暂时未排序</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> j = <span class="hljs-number">0</span>; j < bChildren.<span class="hljs-property">length</span>; j++) {<br> <span class="hljs-keyword">var</span> newItem = bChildren[j]<br> <span class="hljs-keyword">if</span> (newItem.<span class="hljs-property">key</span>) {<br> <span class="hljs-keyword">if</span> (!aKeys.<span class="hljs-title function_">hasOwnProperty</span>(newItem.<span class="hljs-property">key</span>)) {<br> newChildren.<span class="hljs-title function_">push</span>(newItem)<br> }<br> <span class="hljs-comment">//......</span><br> }<br><br><span class="hljs-comment">//对上述集合,真实的b树删除多余结点,直到上述集合为空为止,复杂度O(n)</span><br><span class="hljs-keyword">var</span> simulate = newChildren.<span class="hljs-title function_">slice</span>()<br><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> k = <span class="hljs-number">0</span>; k < bChildren.<span class="hljs-property">length</span>;) {<br><span class="hljs-keyword">var</span> wantedItem = bChildren[k]<br>simulateItem = simulate[simulateIndex]<br><br><span class="hljs-comment">//最后删除剩下虚拟的结点</span><br><span class="hljs-keyword">while</span>(simulateIndex < simulate.<span class="hljs-property">length</span>) {<br> simulateItem = simulate[simulateIndex]<br> removes.<span class="hljs-title function_">push</span>(<span class="hljs-title function_">remove</span>(simulate, simulateIndex, simulateItem && simulateItem.<span class="hljs-property">key</span>))<br>}<br></code></pre></td></tr></table></figure><p>整体的时间复杂度,达到了<br>$$ O(M+N) $$<br>已经是理论下界了。比起手动管理状态来说,效率可以说是直接持平,甚至对部分滥用事件监听的写法效率会更高。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>虽然我并不喜欢UI编程,但是自从图形化出现之后,UI已经成为了继数据结构、算法外,面向终端用户的应用又一个大工程。</p><p>从最早的指令式跳转赋值UI,函数指针响应处理,到中期的面向对象,消息发送回调事件,手动管理属性,在到如今的React以状态和VirualDOM来绑定数据和组件。</p><p>UI编程其实也是在不断进化的,也许今后会有更好的开发方式让我们这群不会写UI的人也能够轻松写起来UI。</p>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>React-Native</tag>
<tag>IOS</tag>
<tag>Javascript</tag>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title>Random Binary Search Tree - Treap</title>
<link href="/Random-Binary-Search-Tree-Treap/"/>
<url>/Random-Binary-Search-Tree-Treap/</url>
<content type="html"><![CDATA[<h2 id="BST插入顺序与平衡性"><a href="#BST插入顺序与平衡性" class="headerlink" title="BST插入顺序与平衡性"></a>BST插入顺序与平衡性</h2><p>众所周知,二查搜索树(BST)的搜索、插入、删除的复杂度等于树高,所以平衡度越高,越接近$ O(nlogn) $,越有序越退化为$ O(n) $<br><img src="http://opendatastructures.org/ods-java/img2843.png" alt="线性BST"> ……………………<img src="http://opendatastructures.org/ods-java/img2844.png" alt="随机BST"></p><ul><li>对于左侧的BST来说,只有唯一的构造序列:$ <1,2,\dots,14> $</li><li>但对于右侧的BST,可以存在21964800种不同序列</li></ul><p>也就是说,随即插入序列到二叉树所形成的平衡度,将大于部分有序插入所形成的二叉树</p><p>形式化证明可以得到(具体证明过程,参见<a href="http://opendatastructures.org/ods-java/7_1_Random_Binary_Search_Tr.html#fig:rbst-records">Open Data Structures</a>:</p><p>对每个$ x \in{0,\ldots,{n}-1} $, x所需要的搜索长度(即深度)是$ H_{x+1} + H_{n-x} - O(1) $</p><p>对每个$ x \in(-1,n)\setminus{0,\ldots,n-1} $,x所需要的搜索长度是$ H_{\lceil x \rceil} H_{n-\lceil x \rceil} $</p><h2 id="Treap-Random-BST实现"><a href="#Treap-Random-BST实现" class="headerlink" title="Treap - Random BST实现"></a>Treap - Random BST实现</h2><blockquote><p><a href="https://en.wikipedia.org/wiki/Treap">Treap-wikipedia</a></p></blockquote><p>Treap,顾名思义,就是<code>Tree</code>和<code>Head</code>的结合体,除了要满足BST的要求外,还需要满足堆的要求,即</p><ol><li><code>BST</code>: 对每个结点,左子女的值 < 根的值 < 右子女的值</li><li><code>Heap</code>: 对除了跟结点的每个结点,双亲结点的优先级要小于该结点的优先级</li></ol><p>所以Treap的每个结点除了包括BST结点的值value外,还需要包括一个唯一的优先级p</p><p>比如这样就是一个典型的Treap,每个结点表示为(value,p)<br><img src="http://opendatastructures.org/versions/edition-0.1e/ods-java/img1086.png" alt="Treap"></p><p>并且可以证明,由Heap的约束,最小优先级将成为根结点,而BST又保证了小于根的值将在左子树上,大于根的值在右子树</p><p>由于Heap的约束,我们可以认为Treap是按照优先级排序插入BST的,比如上述Treap可以由以下序列构造</p><p>$ (3,1), (1,6), (0,9), (5,11), (4,14), (9,17), (7,22), (6,42), (8,49), (2,99) $</p><h2 id="旋转"><a href="#旋转" class="headerlink" title="旋转"></a>旋转</h2><p>为了确保<code>Heap原则</code>,那么就需要对树进行旋转,在旋转中同时还要确保<code>BST原则</code>,比如这样的例子:</p><p><img src="http://opendatastructures.org/versions/edition-0.1e/ods-java/img1102.png" alt="BST旋转"></p><p>对w.value < u.value,旋转将交换w和u的父子关系,同时将把原来的B放在新的儿子上。比如右旋,可以看作左旋和右旋是一个对称的操作</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">rotateRight</span><span class="hljs-params">(Node *u)</span> </span>{<br> Node* w = u->left;<br> w->parent = u->parent; <span class="hljs-comment">//parent -> w</span><br> <span class="hljs-keyword">if</span> (w->parent != <span class="hljs-literal">NULL</span>) {<br> <span class="hljs-keyword">if</span> (w->parent->left == u) { <span class="hljs-comment">//u is left or right</span><br> w->parent->left = w;<br> } <span class="hljs-keyword">else</span> {<br> w->parent->right = w;<br> }<br> }<br> u->left = w->right; <span class="hljs-comment">//u.left = B</span><br> <span class="hljs-keyword">if</span> (u->left != <span class="hljs-literal">NULL</span>) {<br> u->left->parent = u; <span class="hljs-comment">// B.parent = u</span><br> }<br> u->parent = w; <span class="hljs-comment">// w.right = u</span><br> w->right = u;<br> <span class="hljs-keyword">if</span> (u == root) { <span class="hljs-comment">//if u is root</span><br> root = w;<br> root->parent = <span class="hljs-literal">NULL</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><h2 id="添加-删除"><a href="#添加-删除" class="headerlink" title="添加/删除"></a>添加/删除</h2><ul><li>添加</li></ul><p>比如,对上述的Treap,加入一个值1.5,生成的优先级为4,即插入结点为(1.5,4)。首先使用BST的Add,插入在(2,99)的左子女上。为了满足<code>Heap规则</code>,依次进行以下旋转:</p><p>99 > 4 =>右旋;<br>6 > 4 => 左旋;<br>1 < 4 => 停止;</p><p><img src="http://opendatastructures.org/versions/edition-0.1e/ods-java/img1108.png" alt="Treap插入"></p><p>由前面的引理,可以知道,旋转的次数为$ 2ln(n) + O(1) $,复杂度为$ O(logn) $</p><ul><li>删除</li></ul><p>核心基本为添加的逆操作,对上述的Treap,删除一个结果(9),那么将删除结点(9,17)。首先使用BST的Find,找到value为9的结点,然后将以(9,17)开始,向下不断旋转,直到最终为叶结点,然后把这个叶子剪去(Cut down)。</p><p>旋转原则:</p><ol><li>left = NULL & right = NULL ,不动</li><li><strong>left = NULL,左旋(right = NULL则右旋)</strong><code>注:原文这里描述写反了</code></li><li>left.p < right.p,右旋(right.p < left.p则左旋)</li></ol><p>依次进行以下旋转:</p><p>right = NULL => 右旋;<br>right = NULL => 右旋;<br>叶结点 => 停止;</p><p><img src="http://opendatastructures.org/versions/edition-0.1e/ods-java/img1114.png" alt="Treap删除"></p><p>和添加相反,复杂度为$ O(logn) $</p><h2 id="复杂度"><a href="#复杂度" class="headerlink" title="复杂度"></a>复杂度</h2><p><code>构造</code>: $ O(logn) $<br><code>查找</code>: $ O(logn) $<br><code>添加</code>: $ O(logn) $<br><code>删除</code>: $ O(logn) $</p><p>虽说都是$ O(logn) $,但是对比另一种高级数据结构<a href="https://en.wikipedia.org/wiki/Skip_list">Skip List(跳表)</a>,查找复杂度在常数上有不同:</p><p><code>Skip List</code>: $ elnn + O(1) \approx 1.884log(n) + O(1) $<br><code>Treap</code>: $ 2ln(n) + O(1) \approx 1.386log(n) + O(1) $</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><blockquote><p>完整版C++实现,这里面的随机优先级,直接使用了value当种子srand()然后rand()获取随机数……(先进行BST的Add,确保value不会重复),实际中可以采用其他随机数方式获得更好的期望复杂度<br>(吐槽……开始没注意这是Java版伪代码,以后一定用Java或者JavaScript写……指针地狱)</p></blockquote><p>代码链接:<a href="http://www.dreampiggy.com/source/413-2/">Treap</a></p>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>数据结构</tag>
</tags>
</entry>
<entry>
<title>用 C++ 写一个 Inet4Address 类</title>
<link href="/%E7%94%A8-C-%E5%86%99%E4%B8%80%E4%B8%AA-Inet4Address-%E7%B1%BB/"/>
<url>/%E7%94%A8-C-%E5%86%99%E4%B8%80%E4%B8%AA-Inet4Address-%E7%B1%BB/</url>
<content type="html"><![CDATA[<p><code>Inet4Address</code> 在 .net 和 facebook/folly 中也叫做 <code>IPAddress</code>。<br>(顺便黑一下,muduo 中居然把 <code>SocketAddress</code> 叫做 <code>InetAddress</code> 真是有失偏颇。</p><hr><h3 id="构造ip地址"><a href="#构造ip地址" class="headerlink" title="构造ip地址"></a>构造ip地址</h3><br/><p>ip 地址的构造,比较麻烦的地方就是网络序和主机序之间的问题。</p><p>我们的 raw ipv4 address 存储在一个 <code>uint32_t</code> 的整数中,按照 <a href="http://man7.org/linux/man-pages/man7/ip.7.html">linux 官方手册</a>中的描述,以下结构体表示 ipv4 address:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Internet address. */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">in_addr</span> {</span><br> <span class="hljs-type">uint32_t</span> s_addr; <span class="hljs-comment">/* address in network byte order */</span><br>};<br></code></pre></td></tr></table></figure><p><code>s_addr</code> 是按网络序存储的,然而在下面的测试中,有与我预期不符合的地方:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-built_in">BOOST_AUTO_TEST_CASE</span>(test_byteorder_inaddr) {<br> in_addr addr;<br> <span class="hljs-built_in">inet_pton</span>(AF_INET, <span class="hljs-string">"192.168.0.1"</span>, &addr);<br> <span class="hljs-built_in">BOOST_CHECK_EQUAL</span>(addr.s_addr, <span class="hljs-number">0x0100A8C0</span>);<br>}<br></code></pre></td></tr></table></figure><p>我原先以为高位的 192 应该在 b3,而实际上在 b0。在 Java 中 的 <code>Inet4Address.getAddress()</code> 也有注释说明</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * Returns the raw IP address of this {<span class="hljs-doctag">@code</span> InetAddress}</span><br><span class="hljs-comment"> * object. The result is in network byte order: the highest order</span><br><span class="hljs-comment"> * byte of the address is in {<span class="hljs-doctag">@code</span> getAddress()[0]}.</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> the raw IP address of this object.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-type">byte</span>[] getAddress() {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>}<br></code></pre></td></tr></table></figure><p>(一开始没有理解这一点,我在 s_addr 存储的是 little-endian 值,以至于<a href="https://github.com/neverchanje/sugen/blob/master/sugen/net/EndianConv.h">我引入了 redis 的 endianconv</a> 作为工具函数,现在看来暂时没有用武之地了)</p><p>我们首先规定<strong>我们将使用 big-endian 存储 IP 地址</strong>,并统一使用</p><p>对于 IP 地址:<strong>b0.b1.b2.b3</strong>,我们用</p><p><strong>0x b3 b2 b1 b0 来构造大端序</strong></p><p><strong>0x b0 b1 b2 b3 来构造小端序</strong></p><ol><li><p>我们允许使用者通过 <code>Byte4</code> 类型,也就是 <code>std::array<uint8_t, 4></code> 作为参数来构造 <code>Inet4Address</code>,前提是 <code>Byte4</code> 参数是网络序:</p><p> <strong>即对于 “192.168.0.1”, b0 = 192, b1 = 168, b2 = 0, b3 = 1</strong></p></li><li><p>我们也允许使用者通过 NBO 的 <code>uint32_t</code> 类型的地址进行构造,这时候我们该用 <code>ntohl</code> 或 <code>htonl</code> 来实现。</p><hr></li></ol><h3 id="特殊的-ip-地址"><a href="#特殊的-ip-地址" class="headerlink" title="特殊的 ip 地址"></a>特殊的 ip 地址</h3><br/><ul><li><p><a href="https://github.com/neverchanje/sugen/blob/master/sugen/net/Inet4Address.h#L82">loopback address</a><br>环回地址。也就是<code>127.0.0.1</code></p></li><li><p><a href="https://github.com/neverchanje/sugen/blob/master/sugen/net/Inet4Address.h#L88">link local address</a></p></li><li><p><a href="https://github.com/neverchanje/sugen/blob/master/sugen/net/Inet4Address.h#L76">multicast address</a></p></li><li><p><a href="https://github.com/neverchanje/sugen/blob/master/sugen/net/Inet4Address.h#L96">private address</a></p></li></ul><p>这些地址在代码注释中有提供描述他们的 RFC 文档。</p><hr><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><br /><p>因为理解的偏差,造轮子的时候走了一些远路(但不是歧路):</p><ul><li><p><code>in_addr.s_addr</code> 是否为 NBO</p></li><li><p>b0 为 ipv4 的高8位地址,而不是 b3</p></li><li><p>引入 redis 的 endianconv,写了一些无用代码,希望以后会派上用场。</p></li><li><p>当然还有阅读 RFC 文档,理解这些特殊 IP 地址的定义和用途。</p></li></ul>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>Implementing an unique_ptr</title>
<link href="/Implementing-an-unique-ptr/"/>
<url>/Implementing-an-unique-ptr/</url>
<content type="html"><![CDATA[<p>事实证明实现一个 unique_ptr 对其原理的认知并没有什么提升。</p><p>为了简便,我们只实现 single object 版本,不实现存储 array type 的 unique_ptr。</p><hr><p>我们首先要对 UniquePtr 的定义进行声明:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">template</span><<span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>, <span class="hljs-keyword">class</span> <span class="hljs-title class_">D</span>><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">UniquePtr</span> {<br></code></pre></td></tr></table></figure><p>T 是 UniquePtr 所存储的指针的类型,D 是其 Deleter 的类型,UniquePtr 在析构时调用 Deleter,从而保指针离开所在域之后即被释放,这是简单原理,不详谈。</p><p>首先是一个模板类型的细节,在实现容器类的时候常常需要用到并且注意的:<br>UniquePtr 中所存储的类型不总是 T,而是 T 及其子类,我们记作 U。于是乎,我们需要提供这样一个构造函数:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">template</span><<span class="hljs-keyword">class</span> U></span><br><span class="hljs-function"><span class="hljs-title">UniquePtr</span><span class="hljs-params">(U *ptr)</span></span>;<br></code></pre></td></tr></table></figure><p>函数的原型在 <a href="http://en.cppreference.com/w/cpp/memory/unique_ptr">cppreference/memory/unique_ptr</a> 中都是有提供的,所以其实实现的时候可以不用考虑设计。</p><p>然而我们要注意一个关于 Deleter 的细节:</p><blockquote><p>a) If D is non-reference type A, then the signatures are:</p><ul><li><code>unique_ptr(pointer p, const A& d);</code> (requires that Deleter is nothrow-CopyConstructible)</li><li><code>unique_ptr(pointer p, A&& d);</code> (requires that Deleter is nothrow-MoveConstructible)</li></ul><p>b) If D is an lvalue-reference type A&, then the signatures are:</p><ul><li><code>unique_ptr(pointer p, A& d);</code></li><li><code>unique_ptr(pointer p, A&& d);</code></li></ul><p>c) If D is an lvalue-reference type const A&, then the signatures are:</p><ul><li><code>unique_ptr(pointer p, const A& d);</code></li><li><code>unique_ptr(pointer p, const A&& d);</code></li></ul></blockquote><p>由于每种情况提供两种 Deleter 类型不同的构造函数,我们将上面一种的 Deleter 类型记作 <code>del_arg1_type</code>,下面的记作 <code>del_arg2_type</code>。</p><p>我们可以轻易地整理出逻辑:如果 D 是 non-reference,则</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">typedef</span> <span class="hljs-keyword">typename</span> <span class="hljs-type">const</span> remove_reference<D>::type& del_arg1_type;<br></code></pre></td></tr></table></figure><p>否则</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">typedef</span> D del_arg1_type;<br></code></pre></td></tr></table></figure><p>另一方面,<code>del_arg2_type</code> 的逻辑就很简单了:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">typedef</span> <span class="hljs-keyword">typename</span> remove_reference<D>::type && del_arg2_type;<br></code></pre></td></tr></table></figure><p>这样一来,我们就解决了这部分的问题。</p>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>关于 boost::noncopyable 的一点思考</title>
<link href="/%E5%85%B3%E4%BA%8E-boost-noncopyable-%E7%9A%84%E4%B8%80%E7%82%B9%E6%80%9D%E8%80%83/"/>
<url>/%E5%85%B3%E4%BA%8E-boost-noncopyable-%E7%9A%84%E4%B8%80%E7%82%B9%E6%80%9D%E8%80%83/</url>
<content type="html"><![CDATA[<p>一般通过继承 <code>boost::noncopyable</code> 来保证类的对象不会被复制。我们会采用如下写法:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Buffer</span> : boost::noncopyable {<br></code></pre></td></tr></table></figure><p>注意的是,我们既可以采用公有继承 (public),也可以采用默认继承方式 (private),具体使用哪一种方式,需要我们的思考。</p><p>譬如,当父类拥有了 noncopyable 的特性,一般情况下,我们希望子类在继承父类的同时,也能继承其 noncopyable 的特性,于是子类就 <strong>无需重复继承 <code>boost::noncoyable</code></strong> 。举个例子:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">FixedSizeBuffer</span> : <span class="hljs-keyword">public</span> Buffer {<br></code></pre></td></tr></table></figure><p>由于 <code>Buffer</code> 不应该被复制,在 <code>FixedSizeBuffer</code> 公有继承了 <code>Buffer</code>之后,我们希望 <code>FixedSizeBuffer</code> 能够 <strong>同时继承 <code>Buffer</code> 的 noncopyable 的特性</strong>。我们就不必这样写:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">FixedSizeBuffer</span> : <span class="hljs-keyword">public</span> Buffer, boost::noncopyable {<br></code></pre></td></tr></table></figure><p>我们可以做到吗?</p><p>首先我们来了解 boost::noncopyable 的内部实现。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">noncopyable</span><br>{<br> <span class="hljs-keyword">protected</span>:<br> <span class="hljs-built_in">noncopyable</span>() {}<br> ~<span class="hljs-built_in">noncopyable</span>() {}<br> <span class="hljs-keyword">private</span>: <span class="hljs-comment">// emphasize the following members are private</span><br> <span class="hljs-built_in">noncopyable</span>( <span class="hljs-type">const</span> noncopyable& );<br> noncopyable& <span class="hljs-keyword">operator</span>=( <span class="hljs-type">const</span> noncopyable& );<br> };<br>}<br></code></pre></td></tr></table></figure><p>这是 C++99 的版本,如果是 C++11 则会使用如下写法:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">protected</span>:<br> <span class="hljs-built_in">noncopyable</span>( <span class="hljs-type">const</span> noncopyable& ) = <span class="hljs-keyword">delete</span>;<br> noncopyable& <span class="hljs-keyword">operator</span>=( <span class="hljs-type">const</span> noncopyable& ) = <span class="hljs-keyword">delete</span>;<br></code></pre></td></tr></table></figure><p>我们知道 protected 声明的成员在公有继承下依然可见,而 private 不再可见,那是否意味着 C++99 和 C++11 的两种实现有所不同?</p><hr><p>以上是我们平时的思考。如果已知答案,这些思考只是矫情跟无聊而已。</p><p>实际上,这两种方法完全相同,使用 public 继承与使用 private 继承也 <strong>完全没有任何区别</strong>。</p><p>因为子类在使用 copy constructor 和 assignment operator 的时候,必须调用父类的相应函数。显然,如果把相应函数</p><ul><li>放在 private 内,或者</li><li>放在 protected 块内,并用 <code>=delete</code> 标识;</li></ul><p>那子类即使实现了相应函数也无法成功调用。</p><hr><p>所以我们得出一个结论:只要父类继承了 <code>boost::noncopyable</code>,那么子类就无需重复继承。另一方面,我们一般使用 private 继承来为类增加功能,所以我们一般使用默认继承方式(private 继承)来继承 <code>boost::noncopyable</code>,即:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Buffer</span> : boost::noncopyable<br></code></pre></td></tr></table></figure><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">FixedSizeBuffer</span> : <span class="hljs-keyword">public</span> Buffer<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>C++</tag>
<tag>Boost</tag>
</tags>
</entry>
<entry>
<title>关于 Redis 3.0 中的数据结构</title>
<link href="/%E5%85%B3%E4%BA%8E-Redis-3-0-%E4%B8%AD%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<url>/%E5%85%B3%E4%BA%8E-Redis-3-0-%E4%B8%AD%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/</url>
<content type="html"><![CDATA[<h4 id="Redis-是什么?"><a href="#Redis-是什么?" class="headerlink" title="Redis 是什么?"></a>Redis 是什么?</h4><p>Redis 官方网站:<a href="http://redis.io/">http://redis.io/</a></p><hr><h4 id="为什么要阅读-redis-的源代码?"><a href="#为什么要阅读-redis-的源代码?" class="headerlink" title="为什么要阅读 redis 的源代码?"></a>为什么要阅读 redis 的源代码?</h4><p>等价于问题:<a href="http://www.zhihu.com/question/29852167">知乎:我们(大多数人)为什么喜欢造轮子?</a></p><p>redis 中有功能精简的模块,如 <code>anet</code>, <code>ae</code>,也有针对 redis 高度优化的数据结构模块,如 <code>ziplist</code>, <code>dict</code>, <code>intset</code> 等。这些模块相对易于学习,也非常有料。redis 本身就是一个相当优秀的开源作品,并且使用广泛,学习其中的实现,也能对 redis 有更深入的了解。</p><hr><h4 id="简单开头"><a href="#简单开头" class="headerlink" title="简单开头"></a>简单开头</h4><p>Redis 对内存效率的要求常常会高于对时间效率的要求。所以接下来我们更多地会看到 redis 针对数据结构的内存上的优化。</p><hr><h4 id="Data-Structure-1-adlist"><a href="#Data-Structure-1-adlist" class="headerlink" title="Data Structure #1: adlist"></a>Data Structure #1: <code>adlist</code></h4><p><code>adlist</code> 是 redis 实现的一个双向链表。算法原理和普通的双向链表一样。<br>但是由于 C 语言没有复制构造函数之类的东西,所以深复制的实现要靠函数指针如:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> *(*dup) (<span class="hljs-type">void</span> *ptr);<br></code></pre></td></tr></table></figure><p>与之对应的还有深释放和深匹配(自己造的词)</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> (*<span class="hljs-built_in">free</span>) (<span class="hljs-type">void</span> *ptr);<br><span class="hljs-type">int</span> (*match) (<span class="hljs-type">void</span> *ptr, <span class="hljs-type">void</span> *key);<br></code></pre></td></tr></table></figure><hr><h4 id="Data-Structure-2-intset"><a href="#Data-Structure-2-intset" class="headerlink" title="Data Structure #2: intset"></a>Data Structure #2: <code>intset</code></h4><p>首先说明, <code>intset</code> 内部由数组实现。而 <code>intset</code> 顾名思义,只能够存放整数数组。但即使是整数依然有多种类型。<code>uint32_t</code>,<code>int16_t</code>,<code>int8_t</code> 等等。</p><p>由于 C 语言不具有多态,如果要一次性存放这些类型的数组,我们可以这样:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">intset</span> {</span><br> <span class="hljs-type">uint32_t</span> *ui32;<br> <span class="hljs-type">uint16_t</span> *ui16;<br> <span class="hljs-type">int8_t</span> *i8;<br> ....<br>} intset;<br></code></pre></td></tr></table></figure><p>虽然这样可以解决问题,但这显然是一种非常麻烦的方式。而且内存开销大。</p><p>redis 提供的思路是:<br>可以看出,一个 <code>int16_t</code> 可以用 2 个 <code>int8_t</code> 保存,同理地,一个 <code>int32_t</code> 可以用 4 个 <code>int8_t</code> 保存。所以,我们只需要一个 <code>int8_t[]</code> 数组即可。</p><p>我们给出结构体:</p><figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs angelscript"><span class="hljs-keyword">typedef</span> struct <span class="hljs-built_in">int</span><span class="hljs-keyword">set</span> {<br> <span class="hljs-built_in">uint32</span>_t encoding;<br> <span class="hljs-built_in">uint32</span>_t length;<br> <span class="hljs-built_in">int8</span>_t contents[];<br>} <span class="hljs-built_in">int</span><span class="hljs-keyword">set</span>;<br></code></pre></td></tr></table></figure><ul><li><p>第一个问题:<strong>如何知道 <code>intset</code> 中存储的值的类型?</strong></p><p>redis 用一个 encoding 值表示 <code>intset</code> 中值的类型。</p></li><li><p>第二个问题:<strong>当值超过 <code>int8_t</code> 的范围,但在 <code>int16_t</code> 的范围内,如何对 <code>intset</code> 做出修改?</strong></p><p>重新调整 <code>contents</code> 数组的大小,以容纳 <code>length</code> 个 <code>int16_t</code> 值,并修改 <code>encoding</code> ,即:</p></li></ul><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs abnf"><span class="hljs-attribute">contents</span> <span class="hljs-operator">=</span> realloc(sizeof(int16_t) * length)<span class="hljs-comment">;`</span><br><span class="hljs-attribute">encoding</span> <span class="hljs-operator">=</span> INTSET_ENC_INT16<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure><p><code>intset</code> 本身还提供了普通的 set 应有的特性,比如 <em>有序性</em>,<em>唯一性</em></p><hr><h4 id="Data-Structure-3-ziplist"><a href="#Data-Structure-3-ziplist" class="headerlink" title="Data Structure #3: ziplist"></a>Data Structure #3: <code>ziplist</code></h4><p><code>ziplist</code> 是一个压缩的双向链表,只能储存整数和字符串</p><p>在了解之前,我们首先要理解压缩的意义。压缩的数据一定是静态的,我们很难直接对压缩的数据进行动态操作。如果要进行动态操作,首先要进行解压(decode),等到操作结束后,再进行压缩(encode)。</p><p>回想一下,一个双向链表节点的基本结构是</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">listNode</span> {</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">listNode</span> *<span class="hljs-title">prev</span>;</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">listNode</span> *<span class="hljs-title">next</span>;</span><br><span class="hljs-type">void</span>* value;<br>} listNode;<br></code></pre></td></tr></table></figure><p>看起来无法压缩,其实仔细观察会发现,<code>prev</code> 和 <code>next</code> 指针都会占用 <code>8bytes</code> 的空间,可以从这里下手。</p><p>redis 提供的思路是:把整个 <code>ziplist</code> 放在一块连续的内存块上。这样就可以通过一些计算来获得 <code>next</code> 和 <code>prev</code>。</p><p>假设指向当前节点的指针 <code>p</code>,当前节点的内存长度 <code>len</code>,那么下一个节点的指针 <code>next</code> 显然是:</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-attr">next</span> = p + len<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure><p>如果要获取 <code>prev</code>,则还要存储前一个节点的内存长度 <code>prevlen</code>,然后计算:</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs abnf"><span class="hljs-attribute">prev</span> <span class="hljs-operator">=</span> p - prevlen<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure><p>所以我们可以由此设计出 <code>listNode</code> 内存块的分配</p><figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs gherkin">|<span class="hljs-string">prevlensize</span>|<span class="hljs-string">prevlen</span>|<span class="hljs-string">lensize</span>|<span class="hljs-string">len</span>|<span class="hljs-string">value</span>|<br></code></pre></td></tr></table></figure><p>由于 <code>prevlen</code> 和 <code>len</code> 都是根据值的大小分配不同内存的(如果值小于 <code>int8_t</code> 就只会分配 <code>1byte</code>),我们就用固定长度为 <code>1byte</code> 的 <code>prevlensize</code> 和 <code>lensize</code> 来保存 <code>prevlen</code> 和 <code>len</code> 所花费的内存大小。</p><p>这样根据值的大小来动态分配值的内存空间,从而达到节约内存的目的,是 redis 常用的一种手段。</p><p>这只是大概思路,具体实现还要考虑到很多细节,比较麻烦。</p><hr><h4 id="Data-Structure-4-zipmap"><a href="#Data-Structure-4-zipmap" class="headerlink" title="Data Structure #4: zipmap"></a>Data Structure #4: <code>zipmap</code></h4><p>与 <code>ziplist</code> 用的几乎是同样一种手段。<code>zipmap</code> 是一个压缩的 string key-value 数组。获取 value 的复杂度是 O(n) 的,因此适合数据不多的场合。</p><p>key-value 节点的结构是</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">klen</span>></span><span class="hljs-tag"><<span class="hljs-name">key</span>></span>|<span class="hljs-tag"><<span class="hljs-name">vlen</span>></span><span class="hljs-tag"><<span class="hljs-name">free</span>></span><span class="hljs-tag"><<span class="hljs-name">value</span>></span><br>----key----|-------value-------<br></code></pre></td></tr></table></figure><ul><li>Question:<strong>free 区有什么作用?</strong></li></ul><p>这要牵扯到 redis 处理内存和时间平衡的一个技巧。</p><p>我们储存一个值 <code>value</code>,并为其分配长度为 <code>len</code> 的空间,这个值在动态改变的时候,占用的内存可能会变为 <code>newlen</code>。 如果 <code>newlen < len</code> ,意味着我们会多余出一些空间,这个空间记作 <code>free</code>。我们接下来可能要做两种操作:</p><ul><li>用 <code>realloc</code> 搭配 <code>memmove</code> 等操作将内存空间减少至 <code>newlen</code></li><li>将这段多余的空间暂且放着,作为一段 <code>free</code> 空间。</li></ul><p>从时间效率考虑,后者肯定是最优的。(感觉解释起来有点麻烦)。但从内存效率考虑,前者肯定是最优的。</p><p>根据具体情况,我们可能只考虑前者(不考虑内存),或只考虑后者(时间要求不高),也可以综合考虑,设置一个参数 <code>MAX_FREE_VALUE</code> 表示最大能够容忍的 <code>free</code> 空间。如果超过了 <code>MAX_FREE_VALUE</code> 我们就把 <code>free</code> 空间腾出来。如果没超过,就暂且放着这块 <code>free</code> 空间。</p><hr><h4 id="Data-Structure-5-dict"><a href="#Data-Structure-5-dict" class="headerlink" title="Data Structure #5: dict"></a>Data Structure #5: <code>dict</code></h4><p>跟普通的 Hashtable 差不多,普通的 Hashtable 实现可参照 <code>java.util.Hashtable<K,V></code></p><p>redis 中的 dict 进行了优化,也更复杂:</p><ul><li><p>一个 dict 其实维护了两个 Hashtable</p><p>在 rehash 操作时,我们要进行 Hashtable 的迁移工作,这时候其实可以让两个 Hashtable 一起工作。比如查找节点时,在表1中找不到,则去表2中找。这样允许我们可以不必在 rehash 的时候一次性完成全部的迁移。</p></li><li><p>渐进式 rehash</p><p><em><strong>为什么要使用渐进式 rehash ?</strong></em></p><p>显然,rehash 操作是整个 hashtable 的瓶颈。可以采用分摊的思想,将 rehash 操作分摊给其他操作。比如分摊给 dictAdd, dictFind, dictDelete, dictReplace。由于这些操作平均复杂度都是 O(1) 的,所以每个操作都只能分摊一次 rehash 操作。</p></li></ul>]]></content>
<categories>
<category>未分类</category>
</categories>
<tags>
<tag>Redis</tag>
</tags>
</entry>
<entry>
<title>I++俱乐部</title>
<link href="/I-Club/"/>
<url>/I-Club/</url>
<content type="html"><![CDATA[<p>  东南大学江宁区九龙湖校区南门软件学院,当年我的好基友总是说我不是在学院就是在去学院的路上。<br>  真的,多年前的软件学院那是一个怎样的存在,整层楼6间不用抢座宽敞明亮的自习教室,再来一层楼6间不用抢座宽敞明亮的机房,再来一排各式供社团使用的独立工作室,4层楼每层楼梯转角有乒乓球桌;自习室机房和工作室各式该有的网电空调工位齐全;1楼设有专用的图书室和文娱室;门外立着闪亮的东南大学软件学院的招牌。这样一个高大上的独立软院楼配上300多人每人4倍学费的学院,作为当时学院的一名学生,这样的求学环境真的够炫酷。<br>  可那又怎么样呢。那时的IT业也没现在这么热,软院也只是一个在学校官网上拉到页面最下面点倒数第几个连接的学院。同年级的同学绝大都是调剂专业而来的学生,带着各式的不满,只要上下课做作业考试、魔兽、Dota、LOL就可以轻易用掉大学4年。优秀的同学们带着优秀的惯性,继续钻研着各门课程科目,刷个前百分之几的绩点可以保研。并没有很多人真正关心就读于这个专业是为了做什么。学院楼其实也只是一个空架子,除了某些课程使用机房或是自习教室时强制学生过来。其它时间里特别是周末里我甚至可以在空荡荡的楼层间漫步,然后独占一个机房教室或者工作室。<br>  这才不是我想要的软件学院。读高中的时候我沉迷游戏有网瘾,玩魔兽地图编辑器,觉得制作一个游戏能创造一个自己的世界,简直是世界上最棒的事。所以猜想软件工程正是同样的,是最接近能创造一个世界的学科。所以为了在混学位和刷绩点之外,在大学里聚集更多不同的价值观的同学们,先是成立了中二病性质的,The9th软院游戏制作社,初期专教制作魔兽地图后期教写C++游戏,后又与一个接学校内部小网站开发的叫WE的俱乐部,合并成为I++俱乐部。(这里爆一点黑历史,The9th这个名字是创始人说电子游戏是一种人类八大艺术形式之外的第九类艺术,WE这个名字是说这个俱乐部的创始人有11个,We have Eleven people)。<br>  I++的名字是大家一起选的,i++也是编程当中最常写的一段代码,表示一点一点的增加有积累和进步,配合了新俱乐部的目标,就是大家能在这里随性地一起研究学习的任何感兴趣的IT技术,一起组队做软件开发项目,参加国内外各式开发比赛,努力在专业上积累和提升自己的动手能力(不知不觉这么官腔哟)。所以正是那句话,如果有一天你觉得这不是你想要的大学生活,就来I++看看吧,也许真能找到和你一样想法的同伴,一起来努力做点什么不一样的事。</p>]]></content>
<categories>
<category>未分类</category>
</categories>
</entry>
<entry>
<title>I++ 俱乐部</title>
<link href="/"/>
<url>/</url>
<content type="html"><![CDATA[<h3 id="我们是谁"><a href="#我们是谁" class="headerlink" title="我们是谁"></a>我们是谁</h3><p>2015 常驻用户</p><p>吴涛</p><p>2016 常驻用户</p><p>吕炀</p><img alt="lvyang" src="/img/people/lvyang.jpg" style="zoom: 50%; display: block; margin: auto" /><p>2024 常驻用户</p><p>何山</p><img alt="heshan" src="/img/people/heshan.png" style="zoom: 50%; display: block; margin: auto" /><h3 id="联系我们"><a href="#联系我们" class="headerlink" title="联系我们"></a>联系我们</h3><p>I++ 俱乐部是东南大学计软智学院的技术俱乐部。南京江宁区东南大学九龙湖校区计算机拔尖基地。</p>]]></content>
</entry>
</search>