-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal-search.xml
491 lines (237 loc) · 389 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>2048(基于Qt6框架开发)</title>
<link href="/posts/aaf64fe7.html"/>
<url>/posts/aaf64fe7.html</url>
<content type="html"><![CDATA[<h1 id="2048-Games"><a href="#2048-Games" class="headerlink" title="2048 Games"></a>2048 Games</h1><h2 id="编译环境"><a href="#编译环境" class="headerlink" title="编译环境"></a>编译环境</h2><p>我使用的Qt编译套件:<code>Desktop Qt 6.5.8 MinGW 64-bit</code>(只要是Qt6框架即可)</p><p>Qt Creator版本:<code>Qt Creator 15.0.0 (Enterprise)</code></p> <div style="background-color:#f1f9ff; border-left: 5px solid #4285f4; padding: 10px; margin-bottom: 10px;"> 注:Qt5框架在播放背景音乐时可能出现问题,因为Qt5中的setMedia在Qt6中改成了setSource</div><h2 id="运行步骤"><a href="#运行步骤" class="headerlink" title="运行步骤"></a>运行步骤</h2><h3 id="exe运行"><a href="#exe运行" class="headerlink" title="exe运行"></a>exe运行</h3><p>将build/Desktop_Qt_6_5_8_MinGW_64_bit-Release/release文件夹中的exe文件下载下来,直接双击2048.exe文件运行即可(需要Qt6框架)</p><p>若没有qt框架,可以将整个release文件夹下载下来,在文件夹里面运行exe文件。</p> <div style="background-color:#f1f9ff; border-left: 5px solid #4285f4; padding: 10px; margin-bottom: 10px;"> 注:可能出现的2个问题:<br>1.弹窗显示找不到Qt6Core.dll。要么是Qt框架太老,要么是没有将Qt编译器加载到环境变量<br>2.一直在加载。正常情况,因为wav音频文件比较大,并且打包了所有依赖环境,耐心等待即可🥹</div><h3 id="Qt编译运行"><a href="#Qt编译运行" class="headerlink" title="Qt编译运行"></a>Qt编译运行</h3><ol><li>克隆到本地:</li></ol><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">git clone [email protected]:wang-jiahao/2048.git<br></code></pre></td></tr></table></figure><ol start="2"><li><p>删除<code>2048.pro.user</code>文件与<code>build</code>文件夹。</p></li><li><p>Qt Creator打开项目,选择文件<code>2048.pro</code>,然后按默认步骤build即可。</p></li><li><p>打开<code>main.cpp</code>,运行即可。(build时间可能较长,因为wav音频文件比较大)</p></li></ol><h2 id="选题原因"><a href="#选题原因" class="headerlink" title="选题原因"></a>选题原因</h2><p><a href="https://2048game.com/">2048</a>是一款很火的小游戏。2048在4×4的网格上进行。玩家可以使用上、下、左、右四个方向键移动所有方块。游戏开始时,网格上会出现两个数值为2或4的方块。每次移动后,另一个数值为2或4的新方块会随机出现在空方格上。方块会沿着指定的方向滑动,直到被其它方块或网格边缘阻挡。如果两个相同数值的方块碰撞,它们将合并成一个方块,其数值等于两个方块的数值之和。玩家获取胜利的条件是获得数值为2048的方块。当玩家没有合法的移动方法时,即出现了网格上没有空方格,且相邻方块的数值均不相同的情况,游戏就直接结束。</p><p>因为最近在自学21spring的<a href="https://sp21.datastructur.es/">CS61B</a>,其中作业的<a href="https://sp21.datastructur.es/materials/proj/proj0/proj0">proj0</a>就是2048,而我前不久刚好用Java写了<a href="https://github.com/wang-jiahao/CS61B/tree/master/proj0">这个项目</a>,看到课程的项目要求,索性就自学Qt,用C++重新写了功能更丰富的2048,增加了音乐,新游戏等功能。</p><div style="background-color:#f1f9ff; border-left: 5px solid #4285f4; padding: 10px; margin-bottom: 10px;"><ul> <li><b>2048官网</b>: <a href="https://2048game.com/">https://2048game.com/</a></li><li><b>CS61B网址(21spring版)</b>: <a href="https://sp21.datastructur.es/">https://sp21.datastructur.es/</a></li><li><b>proj0网址</b>: <a href="https://sp21.datastructur.es/materials/proj/proj0/proj0">https://sp21.datastructur.es/materials/proj/proj0/proj0</a></li><li><b>当初我完成的Java版2048</b>: <a href="https://github.com/wang-jiahao/CS61B/tree/master/proj0">https://github.com/wang-jiahao/CS61B/tree/master/proj0</a></li></ul></div><h2 id="开发流程"><a href="#开发流程" class="headerlink" title="开发流程"></a>开发流程</h2><ol><li>先参考助教建议<a href="https://www.bilibili.com/video/BV1g4411H78N/?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=c06338b0283c611d7a47c62b0ed23dfa">视频</a>自学qt。</li><li>下载最新版本的Qt Creator及其套件。</li><li>创建通过qmake管理的项目。</li><li>先在Qt的设计页面中增加对象,调整参数,得到美观的ui界面。</li><li>再在mainwindow.h文件中定义需要实现的函数等。</li><li>再在mainwindow.cpp文件中具体实现头文件中的函数,不会实现的功能通过google/StackOverflow/<a href="https://doc.qt.io/qt-6/">qt帮助文档</a>/ChatGPT解决。</li><li>最后再main.cpp中调用。</li><li>通过得到的exe文件不断发现问题(比如音乐,重新开始,回退功能等),不断重复5-7步骤。</li></ol><h2 id="项目预期"><a href="#项目预期" class="headerlink" title="项目预期"></a>项目预期</h2><ol><li>游戏流畅</li><li>玩家与单位交互流畅</li><li>用户交互界面完整美观</li><li>设计交互方式</li><li>用户操作反馈明确</li><li>实现存档功能</li><li>设计数值等机制</li><li>git提交规范</li><li>体现C++面向对象特性</li><li>多文件编程</li><li>难度适中,游玩体验好</li><li>游戏界面设计美观</li><li>有与游戏内容相符的背景音乐</li></ol><h2 id="最终完成度及得分点"><a href="#最终完成度及得分点" class="headerlink" title="最终完成度及得分点"></a>最终完成度及得分点</h2><h3 id="基础功能(65分)"><a href="#基础功能(65分)" class="headerlink" title="基础功能(65分)"></a>基础功能(65分)</h3><h4 id="游戏逻辑(20分)"><a href="#游戏逻辑(20分)" class="headerlink" title="游戏逻辑(20分)"></a>游戏逻辑(20分)</h4><ul><li>游戏场景(10分):游戏流畅,无卡顿,提供了try again与new game2个选项,满分</li><li>角色或单位(10分):该游戏中的单位为包含数字的方块,最初为2个,最高有16个,满分</li></ul><h4 id="交互界面(25分)"><a href="#交互界面(25分)" class="headerlink" title="交互界面(25分)"></a>交互界面(25分)</h4><ul><li>完整界面(15分):用户交互界面(ui)完整美观,按钮提示明确,满分</li><li>交互方式(5分):设计了2种键盘交互方式,通过WSAD或↑↓←→控制方块的移动,满分</li><li>操作反馈(5分):用户操作反馈明确,按下方向键后画面变化流畅,满分</li></ul><h4 id="其他功能(20分)"><a href="#其他功能(20分)" class="headerlink" title="其他功能(20分)"></a>其他功能(20分)</h4><ul><li>存档功能(10分):关闭游戏后再次打开从存档点可以继续游戏(如果是第一次打开,则为新游戏);将存档保存在build/debug/savegame.json文件中,可以直接修改实现进度调整。满分</li><li>数值或升级机制(10分):设计分数这一数值机制,每次合并得到更大的数字,分数就会加上该数字,5分</li></ul><h3 id="代码管理(20分)"><a href="#代码管理(20分)" class="headerlink" title="代码管理(20分)"></a>代码管理(20分)</h3><ul><li>git提交规范(10分):最开始初始化项目,之后不断优化,提交十余次,且提交合理,满分</li><li>体现C++面向对象特性(5分):代码封装了许多不同的类,如<code>QApplication</code>,<code>MainWindow</code>等,其中<code>MainWindow</code>继承自<code>QMainWindow</code>。满分</li><li>多文件编程(5分):<code>2048.pro</code>文件定义如何编译、链接以及生成最终的可执行文件或库,<code>main.cpp</code>文件用于管理主线程,<code>mainwindow.h</code>用于定义主窗口(<code>MainWindow</code>)的功能(包括事件处理,界面更新等),<code>mainwindow.cpp</code>用于实现主窗口的功能,<code>resources.qrc</code>用于管理资源文件(如音乐等)。代码注释清晰、变量名表意明确。满分</li></ul><h3 id="选做内容(15分)"><a href="#选做内容(15分)" class="headerlink" title="选做内容(15分)"></a>选做内容(15分)</h3><ul><li>玩家游戏体验(5分):难度适中,设计的键盘交互方式符合操作逻辑(如W或↑代表上移等),游玩体验好,满分</li><li>酷炫UI(5分):游戏界面设计美观,用户每次按下方向键都有方块移动的动态效果,界面元素合理,满分</li><li>背景音乐(5分):使用C418的Subwoofer Lullaby(minecraft的背景音乐之一),与游戏内容相符,让人怀旧,涤荡心灵,满分</li></ul>]]></content>
<categories>
<category>C++</category>
<category>Qt</category>
</categories>
<tags>
<tag>Qt6</tag>
<tag>C++</tag>
<tag>Games</tag>
</tags>
</entry>
<entry>
<title>动态规划经典问题</title>
<link href="/posts/4a9de9ff.html"/>
<url>/posts/4a9de9ff.html</url>
<content type="html"><![CDATA[<p>老师说期末机试动态规划(Dynamic Programming, DP)考课件里的,所以我总结了一下这五个问题。</p><hr><h3 id="1-The-Rod-Cutting-Problem-切割钢条问题"><a href="#1-The-Rod-Cutting-Problem-切割钢条问题" class="headerlink" title="1. The Rod-Cutting Problem (切割钢条问题)"></a>1. The Rod-Cutting Problem (切割钢条问题)</h3><h4 id="问题描述:"><a href="#问题描述:" class="headerlink" title="问题描述:"></a>问题描述:</h4><p>给定一个长度为 <code>n</code> 的钢条和一个价格数组 <code>p[]</code>,<code>p[i]</code> 表示长度为 <code>i+1</code> 的钢条的价格。我们需要确定如何将这根钢条切割成多个部分,使得总的销售额最大。</p><h4 id="动态规划解法:"><a href="#动态规划解法:" class="headerlink" title="动态规划解法:"></a>动态规划解法:</h4><ul><li>定义 <code>dp[i]</code> 为长度为 <code>i</code> 的钢条的最大价格。</li><li>对于每个长度 <code>i</code>,可以将其分成两部分,分别求解这两部分的最大值并加上它们的价格。</li><li>递推式:<code>dp[i] = max(p[j] + dp[i - j - 1])</code>,其中 <code>j</code> 为切割点。</li></ul><h4 id="C-代码:"><a href="#C-代码:" class="headerlink" title="C++代码:"></a>C++代码:</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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><algorithm></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">rodCutting</span><span class="hljs-params">(<span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>>& prices, <span class="hljs-type">int</span> n)</span> </span>{<br> <span class="hljs-function">vector<<span class="hljs-type">int</span>> <span class="hljs-title">dp</span><span class="hljs-params">(n + <span class="hljs-number">1</span>, <span class="hljs-number">0</span>)</span></span>;<br> <br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">1</span>; i <= n; ++i) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j < i; ++j) {<br> dp[i] = <span class="hljs-built_in">max</span>(dp[i], prices[j] + dp[i - j - <span class="hljs-number">1</span>]);<br> }<br> }<br> <span class="hljs-keyword">return</span> dp[n];<br>}<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> vector<<span class="hljs-type">int</span>> prices = {<span class="hljs-number">1</span>, <span class="hljs-number">5</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>, <span class="hljs-number">17</span>, <span class="hljs-number">17</span>, <span class="hljs-number">20</span>}; <span class="hljs-comment">// 各个长度的钢条价格</span><br> <span class="hljs-type">int</span> n = <span class="hljs-number">8</span>; <span class="hljs-comment">// 钢条长度</span><br> cout << <span class="hljs-string">"最大销售额是: "</span> << <span class="hljs-built_in">rodCutting</span>(prices, n) << endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="解释:"><a href="#解释:" class="headerlink" title="解释:"></a>解释:</h4><ul><li>我们使用 <code>dp[i]</code> 来存储长度为 <code>i</code> 的最大价值。</li><li>外层循环遍历每个可能的钢条长度,内层循环遍历每个切割点,计算最大销售额。</li></ul><hr><h3 id="2-Matrix-chain-Multiplication-矩阵链乘法"><a href="#2-Matrix-chain-Multiplication-矩阵链乘法" class="headerlink" title="2. Matrix-chain Multiplication (矩阵链乘法)"></a>2. Matrix-chain Multiplication (矩阵链乘法)</h3><h4 id="问题描述:-1"><a href="#问题描述:-1" class="headerlink" title="问题描述:"></a>问题描述:</h4><p>给定 <code>n</code> 个矩阵的维度,如何确定矩阵的乘法顺序,以使得总的乘法运算次数最小。</p><h4 id="动态规划解法:-1"><a href="#动态规划解法:-1" class="headerlink" title="动态规划解法:"></a>动态规划解法:</h4><ul><li>定义 <code>dp[i][j]</code> 为从矩阵 <code>i</code> 到矩阵 <code>j</code> 之间的最小乘法次数。</li><li>递推式:<code>dp[i][j] = min(dp[i][k] + dp[k+1][j] + dim[i-1] * dim[k] * dim[j])</code>,其中 <code>k</code> 为分割点。</li><li>目标是计算 <code>dp[1][n]</code>,即矩阵从第一个到最后一个的最小乘法次数。</li></ul><h4 id="C-代码:-1"><a href="#C-代码:-1" class="headerlink" title="C++代码:"></a>C++代码:</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><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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><climits></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">matrixChainOrder</span><span class="hljs-params">(<span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>>& dims)</span> </span>{<br> <span class="hljs-type">int</span> n = dims.<span class="hljs-built_in">size</span>() - <span class="hljs-number">1</span>;<br> vector<vector<<span class="hljs-type">int</span>>> <span class="hljs-built_in">dp</span>(n, <span class="hljs-built_in">vector</span><<span class="hljs-type">int</span>>(n, <span class="hljs-number">0</span>));<br><br> <span class="hljs-comment">// l 为链的长度,从2到n</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> l = <span class="hljs-number">2</span>; l <= n; ++l) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < n - l + <span class="hljs-number">1</span>; ++i) {<br> <span class="hljs-type">int</span> j = i + l - <span class="hljs-number">1</span>;<br> dp[i][j] = INT_MAX;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> k = i; k < j; ++k) {<br> dp[i][j] = <span class="hljs-built_in">min</span>(dp[i][j], dp[i][k] + dp[k + <span class="hljs-number">1</span>][j] + dims[i] * dims[k + <span class="hljs-number">1</span>] * dims[j + <span class="hljs-number">1</span>]);<br> }<br> }<br> }<br> <span class="hljs-keyword">return</span> dp[<span class="hljs-number">0</span>][n - <span class="hljs-number">1</span>];<br>}<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> vector<<span class="hljs-type">int</span>> dims = {<span class="hljs-number">30</span>, <span class="hljs-number">35</span>, <span class="hljs-number">15</span>, <span class="hljs-number">5</span>, <span class="hljs-number">10</span>, <span class="hljs-number">20</span>, <span class="hljs-number">25</span>}; <span class="hljs-comment">// 矩阵维度数组</span><br> cout << <span class="hljs-string">"最小乘法次数: "</span> << <span class="hljs-built_in">matrixChainOrder</span>(dims) << endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="解释:-1"><a href="#解释:-1" class="headerlink" title="解释:"></a>解释:</h4><ul><li>我们定义了一个 <code>dp[i][j]</code> 表示矩阵 <code>i</code> 到矩阵 <code>j</code> 之间的最小乘法次数。</li><li>使用两层循环计算每个矩阵链的最优分割点。</li></ul><hr><h3 id="3-Edit-Distance-编辑距离"><a href="#3-Edit-Distance-编辑距离" class="headerlink" title="3. Edit Distance (编辑距离)"></a>3. Edit Distance (编辑距离)</h3><h4 id="问题描述:-2"><a href="#问题描述:-2" class="headerlink" title="问题描述:"></a>问题描述:</h4><p>给定两个字符串 <code>str1</code> 和 <code>str2</code>,我们需要计算将 <code>str1</code> 转换为 <code>str2</code> 所需要的最小操作次数。操作包括插入一个字符、删除一个字符、替换一个字符。</p><h4 id="动态规划解法:-2"><a href="#动态规划解法:-2" class="headerlink" title="动态规划解法:"></a>动态规划解法:</h4><ul><li>定义 <code>dp[i][j]</code> 为将 <code>str1[0..i-1]</code> 转换为 <code>str2[0..j-1]</code> 所需的最小操作次数。</li><li>递推式:<code>dp[i][j] = min(dp[i-1][j-1] + (str1[i-1] != str2[j-1]), dp[i-1][j] + 1, dp[i][j-1] + 1)</code>。</li></ul><h4 id="C-代码:-2"><a href="#C-代码:-2" class="headerlink" title="C++代码:"></a>C++代码:</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><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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><algorithm></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">editDistance</span><span class="hljs-params">(<span class="hljs-type">const</span> string& str1, <span class="hljs-type">const</span> string& str2)</span> </span>{<br> <span class="hljs-type">int</span> m = str<span class="hljs-number">1.</span><span class="hljs-built_in">size</span>(), n = str<span class="hljs-number">2.</span><span class="hljs-built_in">size</span>();<br> vector<vector<<span class="hljs-type">int</span>>> <span class="hljs-built_in">dp</span>(m + <span class="hljs-number">1</span>, <span class="hljs-built_in">vector</span><<span class="hljs-type">int</span>>(n + <span class="hljs-number">1</span>));<br><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i <= m; ++i) dp[i][<span class="hljs-number">0</span>] = i;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j <= n; ++j) dp[<span class="hljs-number">0</span>][j] = j;<br><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">1</span>; i <= m; ++i) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">1</span>; j <= n; ++j) {<br> <span class="hljs-keyword">if</span> (str1[i - <span class="hljs-number">1</span>] == str2[j - <span class="hljs-number">1</span>]) {<br> dp[i][j] = dp[i - <span class="hljs-number">1</span>][j - <span class="hljs-number">1</span>];<br> } <span class="hljs-keyword">else</span> {<br> dp[i][j] = <span class="hljs-built_in">min</span>({dp[i - <span class="hljs-number">1</span>][j - <span class="hljs-number">1</span>], dp[i - <span class="hljs-number">1</span>][j], dp[i][j - <span class="hljs-number">1</span>]}) + <span class="hljs-number">1</span>;<br> }<br> }<br> }<br> <span class="hljs-keyword">return</span> dp[m][n];<br>}<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> string str1 = <span class="hljs-string">"kitten"</span>, str2 = <span class="hljs-string">"sitting"</span>;<br> cout << <span class="hljs-string">"编辑距离是: "</span> << <span class="hljs-built_in">editDistance</span>(str1, str2) << endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="解释:-2"><a href="#解释:-2" class="headerlink" title="解释:"></a>解释:</h4><ul><li><code>dp[i][j]</code> 存储从 <code>str1[0..i-1]</code> 到 <code>str2[0..j-1]</code> 的最小编辑操作数。</li><li>如果两个字符相同,<code>dp[i][j]</code> 继承自 <code>dp[i-1][j-1]</code>,否则取三者最小值(替换、插入、删除)。</li></ul><hr><h3 id="4-Maximum-Independent-Set-最大独立集"><a href="#4-Maximum-Independent-Set-最大独立集" class="headerlink" title="4. Maximum Independent Set (最大独立集)"></a>4. Maximum Independent Set (最大独立集)</h3><h4 id="问题描述:-3"><a href="#问题描述:-3" class="headerlink" title="问题描述:"></a>问题描述:</h4><p>给定一个树,求该树的最大独立集(一个独立集是一个节点集合,其中没有任何两个节点是相邻的)。</p><h4 id="动态规划解法:-3"><a href="#动态规划解法:-3" class="headerlink" title="动态规划解法:"></a>动态规划解法:</h4><p>这个问题通常通过树的 <strong>动态规划</strong> 处理:</p><ul><li><code>dp[u][0]</code> 表示不选节点 <code>u</code> 时,子树的最大独立集。</li><li><code>dp[u][1]</code> 表示选节点 <code>u</code> 时,子树的最大独立集。</li></ul><h4 id="C-代码:-3"><a href="#C-代码:-3" class="headerlink" title="C++代码:"></a>C++代码:</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><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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><algorithm></span></span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><br>vector<<span class="hljs-type">int</span>> tree[<span class="hljs-number">100</span>]; <span class="hljs-comment">// 树的邻接表表示</span><br><span class="hljs-type">int</span> dp[<span class="hljs-number">100</span>][<span class="hljs-number">2</span>]; <span class="hljs-comment">// dp[u][0] 表示不选 u,dp[u][1] 表示选 u</span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">dfs</span><span class="hljs-params">(<span class="hljs-type">int</span> u, <span class="hljs-type">int</span> parent)</span> </span>{<br> dp[u][<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>; <span class="hljs-comment">// 不选 u 的最大独立集大小</span><br> dp[u][<span class="hljs-number">1</span>] = <span class="hljs-number">1</span>; <span class="hljs-comment">// 选 u 的最大独立集大小</span><br> <br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> v : tree[u]) {<br> <span class="hljs-keyword">if</span> (v == parent) <span class="hljs-keyword">continue</span>;<br> <span class="hljs-built_in">dfs</span>(v, u);<br> dp[u][<span class="hljs-number">0</span>] += <span class="hljs-built_in">max</span>(dp[v][<span class="hljs-number">0</span>], dp[v][<span class="hljs-number">1</span>]);<br> dp[u][<span class="hljs-number">1</span>] += dp[v][<span class="hljs-number">0</span>];<br> }<br>}<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> n, m;<br> cin >> n >> m; <span class="hljs-comment">// 输入节点数和边数</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < m; ++i) {<br> <span class="hljs-type">int</span> u, v;<br> cin >> u >> v;<br> tree[u].<span class="hljs-built_in">push_back</span>(v);<br> tree[v].<span class="hljs-built_in">push_back</span>(u);<br> }<br><br> <span class="hljs-built_in">dfs</span>(<span class="hljs-number">1</span>, <span class="hljs-number">-1</span>); <span class="hljs-comment">// 从根节点1开始DFS</span><br> cout << <span class="hljs-string">"最大独立集大小: "</span> << <span class="hljs-built_in">max</span>(dp[<span class="hljs-number">1</span>][<span class="hljs-number">0</span>], dp[<span class="hljs-number">1</span>][<span class="hljs-number">1</span>]) << endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="解释:-3"><a href="#解释:-3" class="headerlink" title="解释:"></a>解释:</h4><ul><li><p><code>dp[u][0]</code>和<code>dp[u][1]</code>分别表示不选和选节点<code>u</code> 时的最大独立集大小。</p></li><li><p>通过 DFS 递归遍历树,并更新每个子树的最大独立集大小。</p></li></ul><hr><h3 id="5-Subset-Sum-子集和问题"><a href="#5-Subset-Sum-子集和问题" class="headerlink" title="5. Subset Sum (子集和问题)"></a>5. Subset Sum (子集和问题)</h3><h4 id="问题描述:-4"><a href="#问题描述:-4" class="headerlink" title="问题描述:"></a>问题描述:</h4><p>给定一个整数集合 <code>S</code> 和一个目标值 <code>T</code>,问是否存在一个子集,其元素之和为 <code>T</code>。</p><h4 id="动态规划解法:-4"><a href="#动态规划解法:-4" class="headerlink" title="动态规划解法:"></a>动态规划解法:</h4><ul><li>定义 <code>dp[i]</code> 为是否能通过某些元素的和得到值 <code>i</code>。</li><li>递推式:<code>dp[i] = dp[i] || dp[i - num]</code>。</li></ul><h4 id="C-代码:-4"><a href="#C-代码:-4" class="headerlink" title="C++代码:"></a>C++代码:</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><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 cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></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">bool</span> <span class="hljs-title">subsetSum</span><span class="hljs-params">(<span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>>& nums, <span class="hljs-type">int</span> target)</span> </span>{<br> <span class="hljs-function">vector<<span class="hljs-type">bool</span>> <span class="hljs-title">dp</span><span class="hljs-params">(target + <span class="hljs-number">1</span>, <span class="hljs-literal">false</span>)</span></span>;<br> dp[<span class="hljs-number">0</span>] = <span class="hljs-literal">true</span>; <span class="hljs-comment">// 和为0的子集为空集</span><br><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> num : nums) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = target; i >= num; --i) {<br> dp[i] = dp[i] || dp[i - num];<br> }<br> }<br> <span class="hljs-keyword">return</span> dp[target];<br>}<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> vector<<span class="hljs-type">int</span>> nums = {<span class="hljs-number">3</span>, <span class="hljs-number">34</span>, <span class="hljs-number">4</span>, <span class="hljs-number">12</span>, <span class="hljs-number">5</span>, <span class="hljs-number">2</span>};<br> <span class="hljs-type">int</span> target = <span class="hljs-number">9</span>;<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">subsetSum</span>(nums, target)) {<br> cout << <span class="hljs-string">"存在子集和为 "</span> << target << endl;<br> } <span class="hljs-keyword">else</span> {<br> cout << <span class="hljs-string">"不存在子集和为 "</span> << target << endl;<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="解释:-4"><a href="#解释:-4" class="headerlink" title="解释:"></a>解释:</h4><ul><li><code>dp[i]</code> 表示是否存在子集,使得其和为 <code>i</code>。</li><li>每次遍历一个数,更新可能的和,最终判断是否存在目标和 <code>target</code>。</li></ul>]]></content>
<categories>
<category>数据结构与算法</category>
<category>动态规划</category>
</categories>
<tags>
<tag>动态规划</tag>
<tag>DP</tag>
<tag>Rod-Cutting</tag>
<tag>Matirx-chain Multiplication</tag>
<tag>Edit Distance</tag>
<tag>MaxIS of trees</tag>
<tag>Subset Sum</tag>
</tags>
</entry>
<entry>
<title>训练类神经网络一般步骤</title>
<link href="/posts/a902142a.html"/>
<url>/posts/a902142a.html</url>
<content type="html"><![CDATA[<h1 id="什么是机器学习?"><a href="#什么是机器学习?" class="headerlink" title="什么是机器学习?"></a>什么是机器学习?</h1><p>一句话概括:<strong>机器学习就是让机器具备找一个函数的能力</strong>。</p><p>目前机器学习应用的最多的3个问题,分别是<strong>Regression</strong>(回归)、<strong>Classification</strong>(分类)和<strong>Structured Learing</strong>(结构学习)</p><p>Regression:要找的函数,他的输出是一个数值</p><p>Classification:函数的输出,就是从设定好的选项裡面,选择一个当作输出</p><p>Structured Learning:机器产生有结构的东西的问题——学会创造</p><h1 id="机器学习的一般步骤"><a href="#机器学习的一般步骤" class="headerlink" title="机器学习的一般步骤"></a>机器学习的一般步骤</h1><p>刚刚说了,机器学习实际上就是找到一个函数,能达成我们的目标,拿语言识别举例,我们要找的函数,就是输入为一段音频,输出为这个音频的文字版。而寻找这个函数一般需要3个步骤:(这里以预测youtube的观看人数为例,classification其实同理,即用[1,0,0,0,0,0]这样的one-hot vector表示输出,具体参考<a href="https://www.bilibili.com/video/BV1Wv411h7kN?spm_id_from=333.788.videopod.episodes&vd_source=c06338b0283c611d7a47c62b0ed23dfa&p=22%EF%BC%89">https://www.bilibili.com/video/BV1Wv411h7kN?spm_id_from=333.788.videopod.episodes&vd_source=c06338b0283c611d7a47c62b0ed23dfa&p=22)</a></p><h2 id="1-Function-with-Unknown-Parameters(写出一个带有未知参数的函式)"><a href="#1-Function-with-Unknown-Parameters(写出一个带有未知参数的函式)" class="headerlink" title="1.Function with Unknown Parameters(写出一个带有未知参数的函式)"></a>1.Function with Unknown Parameters(写出一个带有未知参数的函式)</h2><p><img src="/../img/blog/youtube.png" alt="youtube观看人数预测"></p><ul><li><p>$y$<strong>是我们准备要预测的东西</strong>,我们准备要预测的人</p></li><li><p>$x_1$<strong>是这个频道前一天总共观看的人数</strong>,跟y一样都是数值,</p></li><li><p><strong>b跟w是未知的参数,它是准备要透过资料去找出来的</strong>,我们还不知道w跟b应该是多少</p><p><strong>猜测:</strong>未来点阅次数的函式F,是前一天的点阅次数,乘上w 再加上b</p><p>⇒猜测往往就来自于对这个问题本质上的了解⇒<strong>Domain knowledge</strong></p></li></ul><h3 id="名词定义:"><a href="#名词定义:" class="headerlink" title="名词定义:"></a>名词定义<strong>:</strong></h3><p>**Feature:**Function里面我们已知的信息【 $x_1$】</p><p>**Weight:**未知参数,跟feature直接相乘</p><p>**Bias:**未知参数,直接相加</p><h2 id="2-Define-Loss-from-Training-Data"><a href="#2-Define-Loss-from-Training-Data" class="headerlink" title="2.Define Loss from Training Data"></a>2.Define Loss from Training Data</h2><p><img src="/../img/blog/loss.png" alt="loss function"></p><p>Loss也是一个Function,它的输入,是Model里面的<strong>参数</strong></p><blockquote><p>这里:输入为w,b</p></blockquote><ul><li><p><strong>物理意义:Function输出的值代表,现在如果我们把这一组未知的参数,设定某一个数值的时候,这笔数值好还是不好。</strong></p><p><strong>L越大,代表一组参数越不好,这个大L越小,代表现在这一组参数越好</strong></p></li><li><p><strong>计算方法:求取估测的值跟实际的值(Label) 之间的差距</strong></p><ul><li>MAE(mean absolute error) </li><li>MSE(mean square error)</li><li>Cross-entropy:计算<strong>概率分布</strong>之间的差距</li></ul></li></ul><p><strong>Error Surface</strong>:试了不同的参数,然后计算它的Loss,画出来的等高线图</p><h2 id="3-Optimization(优化)"><a href="#3-Optimization(优化)" class="headerlink" title="3.Optimization(优化)"></a>3.Optimization(优化)</h2><p>找到能让损失函数值最小的参数。</p><p><img src="/../img/blog/optimization.png" alt="optimization steps"></p><p><strong>具体方法:Gradient Descent(梯度下降)</strong></p><ol><li><p>随机选取初始值 $w_0$</p></li><li><p>计算在 $w=w_0$的时候,<em>w</em>这个参数对<em>loss</em>的微分是多少</p></li><li><p>根据微分(梯度)的方向,改变参数的值</p><p><strong>改变的大小取决于:</strong></p><ol><li>斜率的大小</li><li>学习率的大小<strong>(超参数)</strong></li></ol></li></ol><h1 id="train不起来怎么办?"><a href="#train不起来怎么办?" class="headerlink" title="train不起来怎么办?"></a>train不起来怎么办?</h1><p><img src="/../img/blog/%E4%BC%98%E5%8C%96%E6%80%BB%E6%B5%81%E7%A8%8B.png" alt="general guide of train"></p><h2 id="1-分析在训练数据上的Loss"><a href="#1-分析在训练数据上的Loss" class="headerlink" title="1. 分析在训练数据上的Loss"></a>1. 分析在训练数据上的Loss</h2><p>首先分析在训练数据上的Loss,如果training data的loss都很大,那么testing data的loss当然大。</p><h3 id="1-Model-Bias"><a href="#1-Model-Bias" class="headerlink" title="(1)Model Bias"></a>(1)Model Bias</h3><p>所有的function集合起来,得到一个function的set.但是这个function的set太小了,没有包含任何一个function,可以让我们的loss变低⇒<strong>可以让loss变低的function,不在model可以描述的范围内。</strong></p><p><strong>⇒解决方法:重新设计一个Model,</strong>一个更复杂的、更有弹性的、有未知参数的、需要更多features的function(还是以预测youtube为例,可以选前七天甚至前一个月的数据作为feature,其次,可以改变model architecture,即多叠几层,或者增加Relu的数量等等)</p><h3 id="2-Optimization"><a href="#2-Optimization" class="headerlink" title="(2)Optimization"></a>(2)Optimization</h3><p><img src="/../img/blog/local_minima.png" alt="local minima"></p><p>可能会卡在local minima(局部极小值/鞍点)的地方,没有办法找到一个真的可以让loss很低的参数。(例如卡在$\theta$*,此时gradient也为0,因此不会到真正的最低点)</p><p><strong>⇒解决方法:</strong><a href="https://diamond-mule-bee.notion.site/02-2-DeepLearning-25ed6d30c1ee446e964bbe2fddc5220f#0f45335b92034851a4e4a3821c565ea2">https://diamond-mule-bee.notion.site/02-2-DeepLearning-25ed6d30c1ee446e964bbe2fddc5220f#0f45335b92034851a4e4a3821c565ea2</a></p><h3 id="3-如何区分两种情况?"><a href="#3-如何区分两种情况?" class="headerlink" title="(3)如何区分两种情况?"></a>(3)如何区分两种情况?</h3><ul><li><p>Start from shallower networks (or other models), which are easier to train.</p><p>看到一个你从来没有做过的问题,也许你可以先跑一些比较小的,比较浅的network,或甚至用一些,不是deep learning的方法⇒**比较容易做Optimize的,**它们比较不会有optimization失败的问题</p></li><li><p>If deeper networks do not obtain smaller loss on training data, then there is optimization issue.</p><p>如果你发现你深的model,跟浅的model比起来,深的model明明弹性比较大,但loss却没有办法比浅的model压得更低,那就代表说你的<strong>optimization</strong>有问题</p></li></ul><h2 id="2-分析测试数据上的Loss"><a href="#2-分析测试数据上的Loss" class="headerlink" title="2.分析测试数据上的Loss"></a>2.分析测试数据上的Loss</h2><h3 id="Overfitting:training的loss小-testing的loss大-这个有可能是overfitting"><a href="#Overfitting:training的loss小-testing的loss大-这个有可能是overfitting" class="headerlink" title="Overfitting:training的loss小,testing的loss大,这个有可能是overfitting"></a>Overfitting:training的loss小,testing的loss大,这个有可能是overfitting</h3><p>如果你的model它的<strong>自由度很大</strong>的话,它可以<strong>产生非常奇怪的曲线</strong>,导致训练集上的结果好,但是测试集上的loss很大,如图:</p><p><img src="/../img/blog/overfitting.png" alt="overfitting"></p><h3 id="解决:"><a href="#解决:" class="headerlink" title="解决:"></a>解决:</h3><h3 id="(1)增加训练集"><a href="#(1)增加训练集" class="headerlink" title="(1)增加训练集"></a>(1)增加训练集</h3><p>虽然你的model它的弹性可能很大,但是因为数据样本非常非常的多,它就可以限制住</p><p><strong>Data Augmentation(数据增强):用一些你对于这个问题的理解,从已有的数据中,自己创造出新的数据⇒注意合理性</strong></p><h3 id="(2)限制模型,使之不要有那么大弹性"><a href="#(2)限制模型,使之不要有那么大弹性" class="headerlink" title="(2)限制模型,使之不要有那么大弹性"></a>(2)限制模型,使之不要有那么大弹性</h3><ul><li>给它<strong>比较少的参数(比如神经元的数目);模型共用参数</strong></li><li>使用<strong>比较少的features</strong>(feature selecting,利用domain knowledge筛选你认为有用的feature,去除没用的knowledge)</li><li>Early Stopping</li><li>Regularization</li><li>Dropout</li></ul><h2 id="3-如何选出有较低testing-loss的模型?"><a href="#3-如何选出有较低testing-loss的模型?" class="headerlink" title="3.如何选出有较低testing-loss的模型?"></a>3.如何选出有较低testing-loss的模型?</h2><p><strong>Cross Validation</strong></p><ol><li>把Training的资料分成两半,一部分叫作Training Set,一部分是Validation Set</li><li>在Validation Set上面,去衡量它们的分数,你根据Validation Set上面的分数,去挑选结果,不要管在public testing set上的结果,避免overfiting</li></ol><p>参考链接:</p><p><a href="https://diamond-mule-bee.notion.site/01-Regression-db3f17ba626a43668e016d09d39e35e5#a0f58993757b45da878ed97bbe309538">01-Regression</a></p><p><a href="https://diamond-mule-bee.notion.site/02-1-DeepLearning-General-Guidance-9e355df2c60d45b48038304cf122d103">02.1-DeepLearning-General Guidance</a></p><p><a href="https://diamond-mule-bee.notion.site/02-2-DeepLearning-25ed6d30c1ee446e964bbe2fddc5220f">02.2-DeepLearning-类神经网络优化技巧</a></p><p><a href="https://diamond-mule-bee.notion.site/02-3-DeepLearning-Loss-of-Classification-b6ffdac5af43440bbd703d9521046bd4">02.3-DeepLearning-Loss of Classification</a></p>]]></content>
<categories>
<category>深度学习</category>
<category>国立台湾大学:李宏毅机器学习</category>
</categories>
<tags>
<tag>深度学习</tag>
<tag>类神经网络</tag>
</tags>
</entry>
<entry>
<title>Linux推荐课程</title>
<link href="/posts/1f81650b.html"/>
<url>/posts/1f81650b.html</url>
<content type="html"><![CDATA[<ul><li><p><a href="https://missing-semester-cn.github.io/">MIT-Missing-Semester</a>(此课程不只教授Linux,还包括许多实用技巧,包括vim、shell等等)</p></li><li><p><a href="https://decal.ocf.berkeley.edu/">UCB: Linux System Administration Decal</a></p></li><li><p><a href="https://101.lug.ustc.edu.cn/">中国科大《Linux 101》在线讲义</a></p></li></ul>]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>vim</tag>
<tag>shell</tag>
</tags>
</entry>
<entry>
<title>单源最短路径 多源最短路径</title>
<link href="/posts/a3c2a82.html"/>
<url>/posts/a3c2a82.html</url>
<content type="html"><![CDATA[<p>参考链接:</p><p><a href="https://blog.csdn.net/qq_42500831/article/details/89608104?ops_request_misc=%257B%2522request%255Fid%2522%253A%25223f125175167567dee887c0b7de5f0dcc%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=3f125175167567dee887c0b7de5f0dcc&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-89608104-null-null.142%5Ev100%5Epc_search_result_base2&utm_term=%E5%8D%95%E6%BA%90%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84&spm=1018.2226.3001.4187">单源最短路径(Dijkstra算法)</a></p><p><a href="https://blog.csdn.net/m0_70980326/article/details/133916402?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522689c988ede407f8b1ed22330523e9ca6%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=689c988ede407f8b1ed22330523e9ca6&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-133916402-null-null.142%5Ev100%5Epc_search_result_base2&utm_term=Bellman-Ford%20%E7%AE%97%E6%B3%95&spm=1018.2226.3001.4187">单源最短路径(Bellman-Ford算法)</a></p><h3 id="单源最短路径"><a href="#单源最短路径" class="headerlink" title="单源最短路径"></a>单源最短路径</h3><p><strong>单源最短路径问题(Single-Source Shortest Path Problem)</strong> 是图论中的一个经典问题,目的是在一个加权图中,从一个指定的起点出发,计算到其他所有顶点的最短路径长度。这里的图可以是有向图或无向图,边的权值通常是非负的。该问题广泛应用于路由算法、网络优化、地理信息系统(GIS)等领域。</p><h4 id="问题定义"><a href="#问题定义" class="headerlink" title="问题定义"></a>问题定义</h4><p>给定一个加权图 $G(V,E)$,其中 $V$ 表示顶点集,$E$ 表示边集。每条边 $(u,v)$ 具有一个非负的权重 $w(u,v)$,目标是计算从一个源点 $S$ 到图中所有其他顶点的最短路径。</p><h4 id="常见算法"><a href="#常见算法" class="headerlink" title="常见算法"></a>常见算法</h4><ol><li><strong>Dijkstra 算法</strong>:用于图中所有边的权重为非负数时的单源最短路径问题。</li><li><strong>Bellman-Ford 算法</strong>:能够处理带负权边的情况,并且可以检测负权环,但时间复杂度较高。</li><li><strong>Floyd-Warshall 算法</strong>:用于求解任意两点之间的最短路径,适用于所有顶点之间的最短路径问题,虽然也可以处理负权边,但不适用于负权环。</li></ol><p>这里,我们主要介绍 <strong>Dijkstra 算法</strong>和<strong>Bellman-Ford 算法</strong>,它是解决单源最短路径问题的最常用方法,适用于没有负权边的图。</p><h4 id="Dijkstra-算法"><a href="#Dijkstra-算法" class="headerlink" title="Dijkstra 算法"></a>Dijkstra 算法</h4><p>Dijkstra 算法基于贪心策略,逐步确定最短路径。在每一步,它选择当前距离源点最近的未访问节点,并更新与其邻接的所有节点的最短路径。</p><h4 id="算法步骤"><a href="#算法步骤" class="headerlink" title="算法步骤"></a>算法步骤</h4><ol><li>初始化:设定源点的距离为 0,其他所有节点的距离为无穷大。将源点加入一个未处理的集合。</li><li>在未处理节点中选择距离源点最近的节点。</li><li>更新该节点的所有邻居的最短路径。如果通过当前节点到达某个邻居的路径更短,就更新该邻居的最短距离。</li><li>标记当前节点为已处理,表示其最短路径已确定。</li><li>重复步骤 2 和 3,直到所有节点都被处理完毕。</li></ol><h4 id="C-实现"><a href="#C-实现" class="headerlink" title="C++实现"></a>C++实现</h4><p>以下是 Dijkstra 算法的 C++ 实现,使用了优先队列(<code>priority_queue</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><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><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><queue></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><climits></span></span><br><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><br><span class="hljs-comment">// 定义图的结构:邻接表</span><br><span class="hljs-keyword">typedef</span> pair<<span class="hljs-type">int</span>, <span class="hljs-type">int</span>> pii; <span class="hljs-comment">// pair<邻接点, 边的权重></span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">dijkstra</span><span class="hljs-params">(<span class="hljs-type">int</span> n, vector<vector<pii>>& adj, <span class="hljs-type">int</span> src)</span> </span>{<br> <span class="hljs-comment">// 最短路径数组,初始化为无穷大</span><br> <span class="hljs-function">vector<<span class="hljs-type">int</span>> <span class="hljs-title">dist</span><span class="hljs-params">(n, INT_MAX)</span></span>;<br> dist[src] = <span class="hljs-number">0</span>;<br><br> <span class="hljs-comment">// 优先队列,存储距离和节点,按距离升序排列</span><br> priority_queue<pii, vector<pii>, greater<pii>> pq;<br> pq.<span class="hljs-built_in">push</span>({<span class="hljs-number">0</span>, src});<br><br> <span class="hljs-keyword">while</span> (!pq.<span class="hljs-built_in">empty</span>()) {<br> <span class="hljs-type">int</span> u = pq.<span class="hljs-built_in">top</span>().second; <span class="hljs-comment">// 当前节点</span><br> <span class="hljs-type">int</span> d = pq.<span class="hljs-built_in">top</span>().first; <span class="hljs-comment">// 当前最短路径</span><br> pq.<span class="hljs-built_in">pop</span>();<br><br> <span class="hljs-comment">// 如果当前路径已经不优于已知路径,跳过</span><br> <span class="hljs-keyword">if</span> (d > dist[u]) <span class="hljs-keyword">continue</span>;<br><br> <span class="hljs-comment">// 遍历邻接节点</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span>& edge : adj[u]) {<br> <span class="hljs-type">int</span> v = edge.first; <span class="hljs-comment">// 邻接节点</span><br> <span class="hljs-type">int</span> weight = edge.second; <span class="hljs-comment">// 边的权重</span><br><br> <span class="hljs-comment">// 更新最短路径</span><br> <span class="hljs-keyword">if</span> (dist[u] + weight < dist[v]) {<br> dist[v] = dist[u] + weight;<br> pq.<span class="hljs-built_in">push</span>({dist[v], v});<br> }<br> }<br> }<br><br> <span class="hljs-comment">// 输出从源点到其他节点的最短路径</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < n; i++) {<br> <span class="hljs-keyword">if</span> (dist[i] == INT_MAX)<br> cout << <span class="hljs-string">"Node "</span> << i << <span class="hljs-string">" is unreachable from source."</span> << endl;<br> <span class="hljs-keyword">else</span><br> cout << <span class="hljs-string">"Shortest path to node "</span> << i << <span class="hljs-string">" is "</span> << dist[i] << endl;<br> }<br>}<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> n, m; <span class="hljs-comment">// n是节点数,m是边数</span><br> cout << <span class="hljs-string">"Enter the number of nodes and edges: "</span>;<br> cin >> n >> m;<br><br> vector<vector<pii>> <span class="hljs-built_in">adj</span>(n);<br> <br> cout << <span class="hljs-string">"Enter the edges (u, v, w):"</span> << endl;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < m; i++) {<br> <span class="hljs-type">int</span> u, v, w;<br> cin >> u >> v >> w;<br> adj[u].<span class="hljs-built_in">push_back</span>({v, w});<br> }<br><br> <span class="hljs-type">int</span> src;<br> cout << <span class="hljs-string">"Enter the source node: "</span>;<br> cin >> src;<br><br> <span class="hljs-built_in">dijkstra</span>(n, adj, src);<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><br></code></pre></td></tr></table></figure><h4 id="代码解释:"><a href="#代码解释:" class="headerlink" title="代码解释:"></a>代码解释:</h4><ol><li><strong>输入部分:</strong><ul><li><code>n</code> 是图的顶点数,<code>m</code> 是边的数量。</li><li>每条边由三个整数表示:<code>u</code>、<code>v</code>、<code>w</code>,分别表示从顶点 <code>u</code> 到顶点 <code>v</code> 的边,权重为 <code>w</code>。</li><li><code>source</code> 表示计算单源最短路径的起始顶点。</li></ul></li><li><strong>图的表示:</strong><ul><li>使用 <code>vector<vector<PII>> graph(n)</code> 来存储图的邻接表,每个元素是一个 <code>PII</code> 对,表示一个邻接节点及其边的权重。</li></ul></li><li><strong>Dijkstra 算法:</strong><ul><li>初始化 <code>dist</code> 数组,表示从源点到其他点的最短路径距离,初始值为 <code>INT_MAX</code>,表示不可达。</li><li>使用优先队列 <code>pq</code> 来动态选择当前最短的未访问节点。</li><li>对每个节点的邻接节点进行松弛操作,更新最短路径。</li></ul></li><li><strong>输出结果:</strong><ul><li>输出源点到每个节点的最短距离。如果某个节点不可达,则输出 <code>INF</code>。</li></ul></li></ol><h4 id="时间复杂度:"><a href="#时间复杂度:" class="headerlink" title="时间复杂度:"></a>时间复杂度:</h4><ul><li><p>使用优先队列(堆)实现 Dijkstra 算法时,时间复杂度为 </p><p>$O((V+E)logV)$</p></li></ul><h4 id="Bellman-Ford-算法简介"><a href="#Bellman-Ford-算法简介" class="headerlink" title="Bellman-Ford 算法简介"></a>Bellman-Ford 算法简介</h4><p>Bellman-Ford 算法是一种用于求解单源最短路径问题的经典算法。与 Dijkstra 算法不同,Bellman-Ford 算法不仅可以处理非负权边的图,还能够处理具有负权边的图,甚至能检测图中是否存在负权环。(在图论中,<strong>负权环</strong>指的是图中一个环(即路径可以回到起点),该环上所有边的权重之和小于零。简单来说,负权环是一个循环路径,沿着该路径走一圈所得到的权重总和是负数。)</p><h4 id="算法原理"><a href="#算法原理" class="headerlink" title="算法原理"></a>算法原理</h4><p>Bellman-Ford 算法的核心思想是通过逐步“松弛”图中每条边来逐渐逼近最短路径的解。松弛操作指的是,假设当前节点 <code>u</code> 到达节点 <code>v</code> 的路径的已知距离为 <code>d[u]</code> 和 <code>d[v]</code>,如果通过 <code>u</code> 到 <code>v</code> 的路径会使得 <code>d[v]</code> 的值变小(即 <code>d[u] + w(u,v) < d[v]</code>),则更新 <code>d[v]</code>。</p><h4 id="算法步骤-1"><a href="#算法步骤-1" class="headerlink" title="算法步骤"></a>算法步骤</h4><ol><li><strong>初始化</strong>:<ul><li>对于所有节点 <code>v</code>,设置初始距离 <code>d[v]</code> 为无穷大(表示从源节点到该节点不可达)。</li><li>对源节点 <code>s</code>,设置 <code>d[s] = 0</code>,因为源节点到自身的距离为 0。</li></ul></li><li><strong>松弛操作</strong>:<ul><li>对图中的每一条边 <code>(u, v)</code>,如果 <code>d[u] + w(u, v) < d[v]</code>,则更新 <code>d[v] = d[u] + w(u, v)</code>。</li><li>重复上述松弛操作 <code>V - 1</code> 次,其中 <code>V</code> 是图中节点的数量。这是因为最长的最短路径可能经过图中最多 <code>V - 1</code> 条边。</li></ul></li><li><strong>负权环检测</strong>:<ul><li>在完成 <code>V - 1</code> 次松弛操作之后,若仍然存在边 <code>(u, v)</code>,使得 <code>d[u] + w(u, v) < d[v]</code>,则图中存在负权环。</li></ul></li></ol><h4 id="算法时间复杂度"><a href="#算法时间复杂度" class="headerlink" title="算法时间复杂度"></a>算法时间复杂度</h4><ul><li>由于每次松弛操作需要遍历所有的边,而最多需要进行 <code>V - 1</code> 次松弛操作,因此时间复杂度为 **O(V * E)**,其中 <code>V</code> 是图中的节点数,<code>E</code> 是图中的边数。</li></ul><h4 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h4><p><strong>优点</strong>:</p><ul><li>可以处理包含负权边的图,且能够检测负权环。</li><li>算法实现相对简单。</li></ul><p><strong>缺点</strong>:</p><ul><li>时间复杂度相对较高,尤其是在稠密图中,可能不如 Dijkstra 算法高效。</li></ul><h4 id="C-实现代码"><a href="#C-实现代码" class="headerlink" title="C++ 实现代码"></a>C++ 实现代码</h4><p>下面是 Bellman-Ford 算法的 C++ 实现:</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><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><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><climits></span></span><br><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><br><span class="hljs-comment">// 边的结构体</span><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Edge</span> {<br> <span class="hljs-type">int</span> u, v, weight;<br>};<br><br><span class="hljs-comment">// Bellman-Ford 算法</span><br><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">BellmanFord</span><span class="hljs-params">(<span class="hljs-type">int</span> V, <span class="hljs-type">int</span> E, vector<Edge>& edges, <span class="hljs-type">int</span> src)</span> </span>{<br> <span class="hljs-comment">// 初始化距离数组,所有节点距离源节点的距离为无穷大</span><br> <span class="hljs-function">vector<<span class="hljs-type">int</span>> <span class="hljs-title">dist</span><span class="hljs-params">(V, INT_MAX)</span></span>;<br> dist[src] = <span class="hljs-number">0</span>;<br><br> <span class="hljs-comment">// 松弛操作:对每条边进行 V-1 次松弛</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">1</span>; i < V; ++i) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j < E; ++j) {<br> <span class="hljs-type">int</span> u = edges[j].u;<br> <span class="hljs-type">int</span> v = edges[j].v;<br> <span class="hljs-type">int</span> weight = edges[j].weight;<br><br> <span class="hljs-comment">// 如果可以通过 u 到 v 更短,则更新 dist[v]</span><br> <span class="hljs-keyword">if</span> (dist[u] != INT_MAX && dist[u] + weight < dist[v]) {<br> dist[v] = dist[u] + weight;<br> }<br> }<br> }<br><br> <span class="hljs-comment">// 检查是否存在负权环</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < E; ++i) {<br> <span class="hljs-type">int</span> u = edges[i].u;<br> <span class="hljs-type">int</span> v = edges[i].v;<br> <span class="hljs-type">int</span> weight = edges[i].weight;<br><br> <span class="hljs-keyword">if</span> (dist[u] != INT_MAX && dist[u] + weight < dist[v]) {<br> cout << <span class="hljs-string">"Graph contains negative weight cycle"</span> << endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <span class="hljs-comment">// 如果发现负权环,则返回 false</span><br> }<br> }<br><br> <span class="hljs-comment">// 打印每个节点的最短距离</span><br> cout << <span class="hljs-string">"Vertex Distance from Source "</span> << src << endl;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < V; ++i) {<br> cout << <span class="hljs-string">"Vertex "</span> << i << <span class="hljs-string">": "</span> << dist[i] << endl;<br> }<br><br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; <span class="hljs-comment">// 算法成功执行</span><br>}<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> V = <span class="hljs-number">5</span>; <span class="hljs-comment">// 图中节点的数量</span><br> <span class="hljs-type">int</span> E = <span class="hljs-number">8</span>; <span class="hljs-comment">// 图中边的数量</span><br><br> <span class="hljs-comment">// 创建图的边</span><br> vector<Edge> edges = {<br> {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">-1</span>}, {<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>}, {<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}, {<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">2</span>}, {<span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">2</span>},<br> {<span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-number">5</span>}, {<span class="hljs-number">3</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>}, {<span class="hljs-number">4</span>, <span class="hljs-number">3</span>, <span class="hljs-number">-3</span>}<br> };<br><br> <span class="hljs-comment">// 调用 Bellman-Ford 算法,从源节点 0 开始</span><br> <span class="hljs-built_in">BellmanFord</span>(V, E, edges, <span class="hljs-number">0</span>);<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="代码说明"><a href="#代码说明" class="headerlink" title="代码说明"></a>代码说明</h4><ul><li><p><strong>Edge 结构体</strong>:表示图中的一条边,包含起始节点 <code>u</code>,目标节点 <code>v</code> 和边的权重 <code>weight</code>。</p></li><li><p>BellmanFord 函数:</p><ul><li><code>V</code> 表示节点数,<code>E</code> 表示边数,<code>edges</code> 是图中的边。</li><li>在第一次循环中进行 <code>V - 1</code> 次松弛操作,更新最短路径。</li><li>第二次循环用于检查是否存在负权环,如果发现负权环,则输出提示信息并返回 <code>false</code>。</li></ul></li><li><p>main 函数:</p><ul><li>构建一个包含 5 个节点和 8 条边的有向图,图中包含一些负权边。</li></ul></li><li><p>调用 <code>BellmanFord</code> 函数计算从源节点 0 出发的最短路径。</p></li></ul><h4 id="输出示例"><a href="#输出示例" class="headerlink" title="输出示例"></a>输出示例</h4><p>假设运行该程序,输出结果可能如下:</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><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 apache"><span class="hljs-attribute">Vertex</span> Distance from Source <span class="hljs-number">0</span><br><span class="hljs-attribute">Vertex</span> <span class="hljs-number">0</span>: <span class="hljs-number">0</span><br><span class="hljs-attribute">Vertex</span> <span class="hljs-number">1</span>: -<span class="hljs-number">1</span><br><span class="hljs-attribute">Vertex</span> <span class="hljs-number">2</span>: <span class="hljs-number">2</span><br><span class="hljs-attribute">Vertex</span> <span class="hljs-number">3</span>: -<span class="hljs-number">2</span><br><span class="hljs-attribute">Vertex</span> <span class="hljs-number">4</span>: <span class="hljs-number">1</span><br></code></pre></td></tr></table></figure><p>如上所示,从源节点 <code>0</code> 出发,经过松弛操作后得到每个节点的最短距离。</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>Bellman-Ford 算法通过反复松弛每条边来找到图中各个节点到源节点的最短路径。它能够处理包含负权边的图,并且具有较强的负权环检测能力。尽管其时间复杂度较高,但在一些特殊情况下(例如图中可能包含负权边或需要检测负权环)是非常有效的算法。</p><h3 id="全源最短路径"><a href="#全源最短路径" class="headerlink" title="全源最短路径"></a>全源最短路径</h3><p>全源最短路径问题(All-Pairs Shortest Path,简称 APSP)是指给定一个加权图,要求计算图中所有顶点对之间的最短路径长度。与单源最短路径(如 Dijkstra 算法)不同,全源最短路径算法需要计算每一对顶点之间的最短路径。</p><h4 id="常见算法-1"><a href="#常见算法-1" class="headerlink" title="常见算法"></a>常见算法</h4><ol><li><p><strong>Floyd-Warshall 算法</strong></p><p>Floyd-Warshall 算法是一种动态规划算法,用于解决全源最短路径问题。它通过逐步检查所有可能的中间顶点,更新路径的最短值。该算法适用于处理稠密图(即边的数量较多的图),时间复杂度为 $O(V^3)$,其中 V 是图的顶点数。(这个我怎么我感觉我都能想出来🤓)</p></li><li><p><strong>Johnson 算法</strong></p><p>Johnson 算法使用了 Dijkstra 算法来求解全源最短路径。它通过对图进行加权重标定,转化为一组单源最短路径问题。适用于稀疏图(边的数量较少的图),时间复杂度为 $O(V^2 \log V + VE)$,其中 E 是边数,V 是顶点数。</p></li></ol><h4 id="Floyd-Warshall-算法详细介绍"><a href="#Floyd-Warshall-算法详细介绍" class="headerlink" title="Floyd-Warshall 算法详细介绍"></a>Floyd-Warshall 算法详细介绍</h4><p>Floyd-Warshall 算法的核心思想是,假设图中存在一个中间顶点 k,并计算从每个顶点 i 到顶点 j 的最短路径。然后通过反复迭代更新最短路径。具体的更新方式是:</p><p>$$<br>d(i, j) = \min(d(i, j), d(i, k) + d(k, j))<br>$$<br>其中,$d(i,j)$ 表示从顶点 i 到顶点 j 的最短路径,k 是图中一个中间顶点。</p><h4 id="算法流程"><a href="#算法流程" class="headerlink" title="算法流程"></a>算法流程</h4><ol><li>初始化一个 $V \times V$ 的距离矩阵 dist,其中 $dist[i][j]$表示顶点 i 到顶点 j 的初始距离。如果存在边,则为该边的权重,否则为无穷大。</li><li>对于每个顶点 k,遍历所有顶点对 i,j,尝试通过顶点 k 来更新 $dist[i][j]$。</li><li>最终,矩阵 dist 中的值即为所有顶点对之间的最短路径。</li></ol><h4 id="C-实现-1"><a href="#C-实现-1" class="headerlink" title="C++ 实现"></a>C++ 实现</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><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><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><climits></span></span><br><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><br><span class="hljs-comment">// 定义无穷大的值</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> INF = INT_MAX;<br><br><span class="hljs-comment">// Floyd-Warshall 算法</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">floydWarshall</span><span class="hljs-params">(vector<vector<<span class="hljs-type">int</span>>>& dist, <span class="hljs-type">int</span> V)</span> </span>{<br> <span class="hljs-comment">// dist[i][j] 表示从 i 到 j 的最短路径</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> k = <span class="hljs-number">0</span>; k < V; ++k) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < V; ++i) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j < V; ++j) {<br> <span class="hljs-comment">// 更新最短路径</span><br> <span class="hljs-keyword">if</span> (dist[i][k] != INF && dist[k][j] != INF && dist[i][k] + dist[k][j] < dist[i][j]) {<br> dist[i][j] = dist[i][k] + dist[k][j];<br> }<br> }<br> }<br> }<br>}<br><br><span class="hljs-comment">// 打印最短路径矩阵</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">printSolution</span><span class="hljs-params">(<span class="hljs-type">const</span> vector<vector<<span class="hljs-type">int</span>>>& dist, <span class="hljs-type">int</span> V)</span> </span>{<br> cout << <span class="hljs-string">"最短路径矩阵:"</span> << endl;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < V; ++i) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j < V; ++j) {<br> <span class="hljs-keyword">if</span> (dist[i][j] == INF)<br> cout << <span class="hljs-string">"INF "</span>;<br> <span class="hljs-keyword">else</span><br> cout << dist[i][j] << <span class="hljs-string">" "</span>;<br> }<br> cout << endl;<br> }<br>}<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-comment">// 顶点数目</span><br> <span class="hljs-type">int</span> V = <span class="hljs-number">4</span>;<br> <br> <span class="hljs-comment">// 初始化图的邻接矩阵</span><br> vector<vector<<span class="hljs-type">int</span>>> <span class="hljs-built_in">dist</span>(V, <span class="hljs-built_in">vector</span><<span class="hljs-type">int</span>>(V, INF));<br><br> <span class="hljs-comment">// 自己到自己距离为 0</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < V; ++i) {<br> dist[i][i] = <span class="hljs-number">0</span>;<br> }<br><br> <span class="hljs-comment">// 给定图的边和权重</span><br> dist[<span class="hljs-number">0</span>][<span class="hljs-number">1</span>] = <span class="hljs-number">5</span>;<br> dist[<span class="hljs-number">0</span>][<span class="hljs-number">2</span>] = <span class="hljs-number">10</span>;<br> dist[<span class="hljs-number">1</span>][<span class="hljs-number">2</span>] = <span class="hljs-number">3</span>;<br> dist[<span class="hljs-number">1</span>][<span class="hljs-number">3</span>] = <span class="hljs-number">1</span>;<br> dist[<span class="hljs-number">2</span>][<span class="hljs-number">3</span>] = <span class="hljs-number">2</span>;<br><br> <span class="hljs-comment">// 调用 Floyd-Warshall 算法</span><br> <span class="hljs-built_in">floydWarshall</span>(dist, V);<br><br> <span class="hljs-comment">// 输出最短路径矩阵</span><br> <span class="hljs-built_in">printSolution</span>(dist, V);<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="代码说明-1"><a href="#代码说明-1" class="headerlink" title="代码说明"></a>代码说明</h4><ol><li><strong>邻接矩阵初始化</strong>:<ul><li>初始化一个 $V \times V $的矩阵 <code>dist</code>,其中 <code>dist[i][j]</code> 代表从顶点 i 到顶点 j 的距离。如果存在边,则设置为该边的权重;否则,设置为 <code>INF</code>(无穷大)。</li></ul></li><li><strong>Floyd-Warshall 算法</strong>:<ul><li>通过三层嵌套的循环来遍历每对顶点 i 和 j,对于每个可能的中间顶点 k,更新 <code>dist[i][j]</code> 为更小的值,即 <code>min(dist[i][j], dist[i][k] + dist[k][j])</code>。</li></ul></li><li><strong>打印结果</strong>:<ul><li>最终,<code>dist</code> 矩阵中存储了每对顶点之间的最短路径。如果某一对顶点之间没有路径,值为 <code>INF</code>。</li></ul></li></ol><h4 id="时间复杂度和空间复杂度"><a href="#时间复杂度和空间复杂度" class="headerlink" title="时间复杂度和空间复杂度"></a>时间复杂度和空间复杂度</h4><ul><li><strong>时间复杂度</strong>:Floyd-Warshall 算法的时间复杂度是$O(V^3)$,因为我们有三重循环,每一重循环都遍历 V 次。</li><li><strong>空间复杂度</strong>:空间复杂度是 $O(V^2)$,用于存储最短路径的矩阵。</li></ul><h4 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h4><p>Floyd-Warshall 算法适用于顶点数量相对较小或者图较为稠密的场景。当图中的边非常多时,它比一些基于优先队列的算法(如 Dijkstra)更高效,尤其是在计算所有顶点对之间的最短路径时。</p><h4 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h4><p>Floyd-Warshall 算法是解决全源最短路径问题的一种经典算法,适用于求解图中任意两点之间的最短路径。尽管其时间复杂度较高,但在处理小规模的图时,它依然是一个非常有效和直观的选择。</p>]]></content>
<categories>
<category>数据结构与算法</category>
<category>图论</category>
</categories>
<tags>
<tag>图</tag>
<tag>贪心</tag>
<tag>Dijkstra算法</tag>
<tag>Bellman-Ford算法</tag>
<tag>Floyd-Warshall算法</tag>
</tags>
</entry>
<entry>
<title>正则化</title>
<link href="/posts/ce0afb50.html"/>
<url>/posts/ce0afb50.html</url>
<content type="html"><![CDATA[<p>参考链接:<a href="https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E5%8C%96_(%E6%95%B0%E5%AD%A6)">正则化 (数学) - 维基百科,自由的百科全书</a></p><p>一直觉得一些来自西方的术语翻译过来很奇怪,比如计算机中的robustness被译为鲁棒性,乍一听不知所云,深入了解后发现其实就是稳定的意思,今天又学到正则化,不学根本不知道这是啥意思😅。咱就是说,这到底是哪些“专家”翻译的?</p><p>在机器学习中,<strong>正则化(Regularization)</strong>是一种防止模型过拟合(overfitting)并提高其泛化能力的技术。通过引入额外的约束或惩罚项,正则化能够限制模型复杂度,从而避免在训练集上的表现过于理想化,而忽略了对新数据的适应能力。正则化的核心目标是让模型更加简洁、稳健,并减少因噪声而导致的学习误差。</p><h3 id="1-过拟合的原因与影响"><a href="#1-过拟合的原因与影响" class="headerlink" title="1. 过拟合的原因与影响"></a>1. 过拟合的原因与影响</h3><p>在训练机器学习模型时,尤其是在模型复杂度较高(例如具有大量特征或参数的深度神经网络)时,模型往往能够非常精确地拟合训练数据。虽然这可能在训练集上取得极好的结果,但却不能有效地应用于新的、未见过的数据。这种现象称为<strong>过拟合</strong>。过拟合通常表现为训练误差低,但测试误差较高,意味着模型学到了数据中的噪声,而非一般性的规律。</p><h3 id="2-正则化的基本思想"><a href="#2-正则化的基本思想" class="headerlink" title="2. 正则化的基本思想"></a>2. 正则化的基本思想</h3><p>正则化的核心思想是在损失函数(如最小化误差)中加入一个惩罚项,该项会对模型的复杂度进行约束。例如,如果模型的权重过大(说明模型可能过于复杂),正则化将对其施加惩罚,促使模型的权重保持较小,从而降低复杂度。</p><h3 id="3-正则化的常见形式"><a href="#3-正则化的常见形式" class="headerlink" title="3. 正则化的常见形式"></a>3. 正则化的常见形式</h3><p>正则化有几种常见的形式,其中最常见的是<strong>L1正则化</strong>和<strong>L2正则化</strong>。</p><h4 id="3-1-L1正则化(Lasso回归)"><a href="#3-1-L1正则化(Lasso回归)" class="headerlink" title="3.1 L1正则化(Lasso回归)"></a>3.1 L1正则化(Lasso回归)</h4><p>L1正则化通过在损失函数中添加权重绝对值的和(即权重的L1范数)来约束模型。其惩罚项如下所示:</p><p>$$<br>L1=λ∑i=1n∣wi∣L_1 = \lambda \sum_{i=1}^{n} |w_i|<br>$$</p><p>其中,$w_i$是模型参数,$\lambda$是正则化强度的控制参数。L1正则化的一个重要特性是,它倾向于将一些权重推到零,从而实现<strong>特征选择</strong>。这意味着L1正则化可以产生一个稀疏的模型,其中许多特征的权重为零,进而减少模型的复杂度。</p><h4 id="3-2-L2正则化(Ridge回归)"><a href="#3-2-L2正则化(Ridge回归)" class="headerlink" title="3.2 L2正则化(Ridge回归)"></a>3.2 L2正则化(Ridge回归)</h4><p>L2正则化则是通过在损失函数中添加权重平方和(即权重的L2范数)来实现约束。其惩罚项如下:<br>$$<br>L2=λ∑i=1nwi2L_2 = \lambda \sum_{i=1}^{n} w_i^2<br>$$<br>与L1正则化不同,L2正则化不会导致权重完全为零,而是将所有权重缩小至较小的值。L2正则化能够在一定程度上减少模型复杂度,但不会像L1正则化那样产生稀疏解。L2正则化对于多重共线性问题(即特征之间存在较强相关性)具有一定的缓解作用。</p><h4 id="3-3-弹性网(Elastic-Net)"><a href="#3-3-弹性网(Elastic-Net)" class="headerlink" title="3.3 弹性网(Elastic Net)"></a>3.3 弹性网(Elastic Net)</h4><p>弹性网正则化结合了L1和L2正则化的优点,它在惩罚项中同时包含了L1范数和L2范数。其形式为:</p><p>$$<br>LElasticNet=λ1∑i=1n∣wi∣+λ2∑i=1nwi2L_{\text{ElasticNet}} = \lambda_1 \sum_{i=1}^{n} |w_i| + \lambda_2 \sum_{i=1}^{n} w_i^2<br>$$<br>其中,$\lambda_1$和$\lambda_2$分别控制L1和L2正则化的强度。弹性网正则化特别适用于高维度数据或特征之间存在相关性的情况。</p><h3 id="4-正则化参数"><a href="#4-正则化参数" class="headerlink" title="4. 正则化参数"></a>4. 正则化参数</h3><p>正则化的强度通常由参数$\lambda$来控制。具体来说,$\lambda$的值决定了惩罚项在总损失函数中的权重。较大的$\lambda$值会使正则化项对模型参数施加更强的惩罚,可能导致模型过于简单,从而出现欠拟合(underfitting)。较小的$\lambda$值则可能导致正则化效果较弱,模型可能会出现过拟合。</p><p>一般来说,$\lambda$的选择通常通过交叉验证等方法来调优。</p><h3 id="5-正则化的效果"><a href="#5-正则化的效果" class="headerlink" title="5. 正则化的效果"></a>5. 正则化的效果</h3><p>正则化对模型的影响主要体现在以下几个方面:</p><ul><li><strong>减少过拟合</strong>:通过约束模型的复杂度,正则化可以防止模型过度拟合训练数据。</li><li><strong>提升泛化能力</strong>:正则化通过惩罚大权重,使得模型能够更好地适应未见过的数据。</li><li><strong>特征选择</strong>:L1正则化尤其有助于特征选择,因为它倾向于将一些权重推向零,进而去除冗余或无关特征。</li><li><strong>提高数值稳定性</strong>:在高维度或特征之间高度相关的情况下,正则化有助于提升模型的数值稳定性,避免模型参数出现极端的估计。</li></ul><h3 id="6-正则化与偏差-方差权衡"><a href="#6-正则化与偏差-方差权衡" class="headerlink" title="6. 正则化与偏差-方差权衡"></a>6. 正则化与偏差-方差权衡</h3><p>正则化方法通过增加对模型复杂度的约束,改变了偏差(bias)与方差(variance)的平衡。通常:</p><ul><li>当$\lambda$较大时,正则化效果显著,模型的方差降低,但可能增加偏差,导致欠拟合。</li><li>当$\lambda$较小时,正则化效果较弱,模型复杂度较高,可能会过拟合训练数据,导致方差过大。</li></ul><p>因此,选择合适的正则化参数是至关重要的,它需要根据实际数据和任务通过交叉验证等方法来进行调优。</p><h3 id="7-其他正则化方法"><a href="#7-其他正则化方法" class="headerlink" title="7. 其他正则化方法"></a>7. 其他正则化方法</h3><p>除了L1和L2正则化外,还有其他一些正则化方法,如:</p><ul><li><strong>Dropout</strong>(在深度学习中使用):在每次训练时随机忽略一部分神经元,避免神经网络过度依赖某些特征,从而提高模型的泛化能力。</li><li><strong>数据增强</strong>:通过在训练过程中对数据进行变换、旋转、缩放等操作,来扩展训练集,间接提高模型的泛化能力。</li></ul><h3 id="8-总结"><a href="#8-总结" class="headerlink" title="8. 总结"></a>8. 总结</h3><p>正则化是机器学习中一个重要的技术手段,旨在通过在损失函数中添加惩罚项,控制模型的复杂度,防止过拟合,提高模型的泛化能力。常见的正则化方法包括L1正则化、L2正则化和弹性网正则化。选择合适的正则化强度对于模型的性能至关重要,需要通过交叉验证等方法进行调整。</p>]]></content>
<categories>
<category>深度学习</category>
<category>国立台湾大学:李宏毅机器学习</category>
</categories>
<tags>
<tag>机器学习</tag>
<tag>过拟合</tag>
<tag>正则化</tag>
</tags>
</entry>
<entry>
<title>随笔(持续更新)</title>
<link href="/posts/c983d0ae.html"/>
<url>/posts/c983d0ae.html</url>
<content type="html"><![CDATA[<ul><li><strong>2025-1-27</strong> 美赛最后一天!撑过去爽玩!加油加油!🤓</li><li><strong>2025-1-26</strong> 美赛第三天,加油!</li><li><strong>2025-1-25</strong> 美赛第二天,想似了😭</li><li><strong>2025-1-19</strong> 开始准备美赛😂,报名费有点贵😇</li></ul><p><img src="/../img/blog/%E7%BE%8E%E8%B5%9B%E6%8A%A5%E5%90%8D.png" alt="美赛报名"></p><ul><li><p><strong>2025-1-18</strong> 提前一天写完项目,哈哈,准备晚点发出来</p></li><li><p><strong>2025-1-14</strong> 回家就开始写C++项目,太惨了😒,1月19截止,写不完了🙂↕️,呜呜</p></li><li><p><strong>2025-1-12</strong> 考完了考完了!放假咯!</p></li><li><p><strong>2025-1-11</strong> 明天最后一堂考试了,算法复习,加油!</p></li><li><p><strong>2025-1-8</strong> </p><ul><li><p>因为我于12月29日更换了一个2T的硬盘(逆天戴尔只提供一个槽位搞得我只能换硬盘,相当于重装系统了),所以一直没更新😵💫(因为之前的git,node.js,hexo环境都得重新配置,而当时正值期末周)。直到考试考的差不多了,我参考了这篇文章<a href="https://ink-bottle.github.io/2019/05/09/%E7%94%B5%E8%84%91%E9%87%8D%E8%A3%85%E7%B3%BB%E7%BB%9F%E5%90%8E%E5%A6%82%E4%BD%95%E6%81%A2%E5%A4%8D%E9%83%A8%E7%BD%B2hexo%E5%8D%9A%E5%AE%A2/">电脑重装系统后如何恢复部署hexo博客</a>,才终于恢复更新🤩!还有很多我之前下载的插件,以后再下吧。</p></li><li><p>顺便一提考试,计组因为复习时间充足所以问题不大,c++还好,习概开卷,毛概刚好有一个没背的地方考了20分大题🙃,现在只剩下一门12号的算法考试。</p></li></ul></li><li><p><strong>2024-12-28</strong> 开始复习计组!预计一天2章,4天复习完。</p></li><li><p><strong>2024-12-27</strong> 机试考3h,半个小时之后就再也没拿过分了😭,坐牢1坤时</p></li><li><p><strong>2024-12-24</strong> 偶然看到一位大佬的<a href="https://github.com/sun0225SUN">github主页</a>,于是自己折腾着也魔改了一下自己的,快来看看<a href="https://github.com/wang-jiahao">我的主页</a>好不好看😎</p></li><li><p><strong>2024-12-23</strong> 卡了好久的打字终于慢慢提升了!✌️</p></li><li><p><strong>2024-12-22</strong> 之前一直觉得博客图有点暗,今天终于发现原因了,有没有感觉主页大图变亮了🍋🟩</p></li><li><p><strong>2024-12-20</strong> </p><ul><li><p>typing club卡55wpm两三周了😤,速度上去正确率就下来了,要维持98%以上的正确率的话速度就一直卡在那,没提升,是不是到瓶颈了🥹,这种情况还要继续练吗,还是说这个速度已经够了,有没有大佬指点一下🫥</p></li><li><p>初试深度学习的hw1,感觉train model的过程好麻烦啊,激活函数要选,model architecture要选,optimizer要选,feature要select,learning rate要调,为了避免overfitting还要L2正则化或者dropout,正则化的weight decay也要调,loss一直没变小的话还要early stop,数据不够还要数据增强,关键每个改了之后还要等GPU跑个几分钟才能知道结果,要洗了😇</p><p>当然也有可能是我学的还不够深吧,学完之后再看说不定又是不一样的感受。</p></li></ul></li><li><p><strong>2024-12-19</strong> </p><ul><li><p>刚刚学了学vim,熟练了确实可以完全抛弃鼠标啊,寒假开始练吧。</p><p>突然想起寒假要学的东西有点多啊,之前就打算寒假学linux运维,还报了美赛。管他那么多,学就完了!🤠</p></li></ul></li><li><p><strong>2024-12-16</strong> 深度学习代码好难懂啊🤕,谁来教教我😭</p></li><li><p><strong>2024-12-13</strong> 小站访问量破千啦😊</p></li><li><p><strong>2024-12-11</strong> 还有几天六级,啥都没学,哈哈!</p></li><li><p><strong>2024-12-10</strong> 预立项答辩没发挥好😔,中期好好加油吧!</p></li><li><p><em><strong>2024-12-5</strong></em> 搭建博客,博客建站时间</p></li></ul>]]></content>
<categories>
<category>随笔</category>
</categories>
<tags>
<tag>随笔</tag>
<tag>生活</tag>
</tags>
</entry>
<entry>
<title>github copilot免费申请+嵌入IDE中使用(限高校在校生)</title>
<link href="/posts/49998.html"/>
<url>/posts/49998.html</url>
<content type="html"><![CDATA[<p>最近许多朋友在问我是如何申请到github copilot的,索性写出来与大家分享,水平有限,欢迎大佬批评指正。</p><p>声明:此方法仅限<strong>高校在校生</strong>,理论上不在学校也可通过虚拟位置申请,请自行google或百度,此处不做赘述。</p><h1 id="1-注册GitHub"><a href="#1-注册GitHub" class="headerlink" title="1.注册GitHub"></a>1.注册GitHub</h1><p>这个步骤没什么好说的,还没有账户的朋友们进入<a href="https://github.com/">GitHub</a>自行注册,有邮件就行</p><h1 id="2-设置信息"><a href="#2-设置信息" class="headerlink" title="2.设置信息"></a>2.设置信息</h1><p>首先,进入<a href="https://education.github.com/discount_requests/application">申请页面</a>,这里选择学生即可。</p><p><img src="/../img/blog/image-20241210213351614.png" alt="申请页面"></p><p>一直下滑到如下页面,然后按照它的提示一步一步往下做。</p><p><img src="/../img/blog/image-20241210213733462.png" alt="申请步骤"></p><h2 id="2-1-检测浏览器"><a href="#2-1-检测浏览器" class="headerlink" title="2.1 检测浏览器"></a>2.1 检测浏览器</h2><p>建议使用chrome,edge,firefox,safari中的一种。(推荐Edge,原因是一位朋友使用Chrome申请没通过,换Edge通过了🤡)</p><p>检查自己的浏览器是否关闭了位置权限:</p><p>Chrome:设置-隐私和安全-网站设置-位置信息,改为网站可以请求取得您的位置信息。</p><p>Edge:设置-Cookie和网站权限-站点权限-位置-访问前权限,如果关闭了打开即可。</p><p>其他:自行google。</p><h2 id="2-2-设置账单信息"><a href="#2-2-设置账单信息" class="headerlink" title="2.2 设置账单信息"></a>2.2 设置账单信息</h2><p>进入<a href="https://github.com/settings/billing/payment_information">账单界面</a>,按照如下格式修改Billing&plans(无需设置付款方式)。</p><p><img src="/../img/blog/c3261d5d4771c11e3f1d9221bd794a5.jpg" alt="账单界面"></p><p>(注意:这里填完一般会显示姓太短了,一个解决办法是在后面加上空格,First name同理)</p><h2 id="2-3-设置你的学校邮箱并验证"><a href="#2-3-设置你的学校邮箱并验证" class="headerlink" title="2.3 设置你的学校邮箱并验证"></a>2.3 设置你的学校邮箱并验证</h2><p>进入github首页,点击右上角头像,Settings-Emails-Add email address,输入学校提供的邮箱并验证。当然,如果你注册时用的就是学校邮箱,那么不必进行这一步。</p><h2 id="2-4-设置two-factor-authentification(2FA)"><a href="#2-4-设置two-factor-authentification(2FA)" class="headerlink" title="2.4 设置two-factor authentification(2FA)"></a>2.4 设置two-factor authentification(2FA)</h2><p>Settings-Password and authentification-enable two-factor authentification</p><p>然后手机上下载它推荐的软件来扫描它提供给你的二维码,安卓推荐Authenticator,苹果如果你已经存储过github的密码,则可以相机直接扫描。</p><h2 id="2-5-设置profile"><a href="#2-5-设置profile" class="headerlink" title="2.5 设置profile"></a>2.5 设置profile</h2><p>Settings-public profile,Profile picture设置成自己的大头照(如果觉得丑,后续申请通过了改回来即可),name改成自己的真名,pronouns改成自己性别对应的称呼,按理来说改到这里已经够了,但是按理来说越详细越容易通过,所以建议把bio,location,social accounts,company,display local time全填了🤣。这里不知道怎么填的可以参考我的<a href="https://github.com/wang-jiahao">github profile</a>。</p><h2 id="2-6-写一个README-md"><a href="#2-6-写一个README-md" class="headerlink" title="2.6 写一个README.md"></a>2.6 写一个README.md</h2><p>还是回到首页,左上角create repository(从未新建过仓库)/new(已经建过仓库)新建仓库,Repository name填自己的用户名(注意:这里一定要与用户名一模一样),勾选Add a README file,其他的不用改,点create respository。</p><p><img src="/../img/blog/9be4ce8f1696eda3b1d9e107e5bbc6b.jpg" alt="README.md"></p><p>点击这里的按钮,编辑这份Markdown文档,不会Markdown语法没关系(对Markdown语法感兴趣的可以看看<a href="https://github.com/wang-jiahao/learngit/blob/master/Markdown%2BTypora.md">Markdown+Typora</a>),在这份文档里写自己的兴趣,状况什么的都可以。改完之后点击右上角的commit changes,再次点击commit changes即可。</p><h2 id="2-7-提交申请"><a href="#2-7-提交申请" class="headerlink" title="2.7 提交申请"></a>2.7 提交申请</h2><p><img src="/../img/blog/image-20241210224713209.png" alt="申请"></p><p>在此之前,关闭所有梯子或者VPN,因为这一步需要检测你的真实位置。继续回到之前打开的<a href="https://education.github.com/discount_requests/application">申请页面</a>,下滑到申请板块,如果你的学校名像上面一样自动出现则可以继续。点击Continue,(注意:如果接下来出现让你提供不在校原因,那么大概率申请不过,解决办法自行google)接下来提交自己的学籍证明,建议使用学信网的教育部学籍在线验证报告。静心等待几分钟再打开<a href="https://education.github.com/discount_requests/application">申请页面</a>就可以看到自己的申请结果了,如果出现以下页面,那么恭喜你,申请通过了!(如果出现rejected,github会详细列出原因,按照说明修改即可)</p><p>之后只需静待3天,直到收到github education的邮件,就可以白嫖github copilot了!</p><p><img src="/../img/blog/7109ca4e2954443f8d2ce72cbaf67975.png" alt="request approved!"></p><h1 id="3-在github上使用copilot"><a href="#3-在github上使用copilot" class="headerlink" title="3. 在github上使用copilot"></a>3. 在github上使用copilot</h1><p>收到邮件后,还是回到首页,右上角头像-Your Copilot-Start free trial-get access to Github Copilot,跳转到设置界面,如下所示,全部功能都Enabled或Allowed,不出意外的话过一会儿你的github右下角就会出现github copilot的图标了,你可以直接点击与之对话</p><p><img src="/../img/blog/image-20241210230658345.png" alt="copilot settings"></p><h1 id="4-将Github-Copilot嵌入IDE"><a href="#4-将Github-Copilot嵌入IDE" class="headerlink" title="4. 将Github Copilot嵌入IDE"></a>4. 将Github Copilot嵌入IDE</h1><h2 id="4-1-嵌入VSCode(强烈推荐)"><a href="#4-1-嵌入VSCode(强烈推荐)" class="headerlink" title="4.1 嵌入VSCode(强烈推荐)"></a>4.1 嵌入VSCode(强烈推荐)</h2><p>很多朋友可能倾向于使用jetbrains全家桶(比如我😅),但是VSCode中github copilot是最给力的🤔。</p><p>如下图,它甚至免费提供GPT-o1(当然,限制次数)。</p><p><img src="/../img/blog/image-20241210231422928.png" alt="Copilot in VSCode"></p><p>左下角-设置-扩展-搜索Github Copilot-安装-重启IDE-登录Github即可。</p><p>注意:github copilot会自动启用<strong>代码补全</strong>,如果觉得不利于自己学习进步可以自行关闭。</p><h2 id="4-2-嵌入Jetbrains-IDE"><a href="#4-2-嵌入Jetbrains-IDE" class="headerlink" title="4.2 嵌入Jetbrains IDE"></a>4.2 嵌入Jetbrains IDE</h2><p>这里以IntelliJ为例,其他大同小异。</p><p>左上角-文件-设置-插件-搜索-搜索github-copilot-intellij-安装-重启IDE-登录Github即可。</p><p>未尽之处,欢迎<a href="mailto:[email protected]">邮件</a>或评论询问。</p>]]></content>
<categories>
<category>技术科普</category>
</categories>
<tags>
<tag>github copilot</tag>
<tag>VSCode</tag>
<tag>Jetbrains</tag>
</tags>
</entry>
<entry>
<title>CS自学资源整理(持续更新)</title>
<link href="/posts/20392.html"/>
<url>/posts/20392.html</url>
<content type="html"><![CDATA[<p><strong>排序由易到难,没打标记的就是正在学,哈哈</strong></p><h1 id="Missing-Semester"><a href="#Missing-Semester" class="headerlink" title="Missing Semester"></a>Missing Semester</h1><h2 id="课程网站"><a href="#课程网站" class="headerlink" title="课程网站"></a>课程网站</h2><p>英文:<a href="https://missing.csail.mit.edu/2020/">https://missing.csail.mit.edu/2020/</a></p><p>中文:<a href="https://missing-semester-cn.github.io/">https://missing-semester-cn.github.io/</a></p><p>详见<a href="https://csdiy.wiki/%E7%BC%96%E7%A8%8B%E5%85%A5%E9%97%A8/MIT-Missing-Semester/">MIT-Missing-Semester - CS自学指南</a></p><h2 id="课程视频"><a href="#课程视频" class="headerlink" title="课程视频"></a>课程视频</h2><p>英文:<a href="https://www.youtube.com/playlist?list=PLyzOVJj3bHQuloKGG59rS43e29ro7I57J">https://www.youtube.com/playlist?list=PLyzOVJj3bHQuloKGG59rS43e29ro7I57J</a></p><p>中文:<a href="https://space.bilibili.com/518734451?spm_id_from=333.337.search-card.all.click">https://space.bilibili.com/518734451?spm_id_from=333.337.search-card.all.click</a></p><h2 id="我的代码"><a href="#我的代码" class="headerlink" title="我的代码"></a>我的代码</h2><p><a href="https://github.com/wang-jiahao/MIT-Missing-Semester">https://github.com/wang-jiahao/MIT-Missing-Semester</a></p><h1 id="Sysadmin-DeCal"><a href="#Sysadmin-DeCal" class="headerlink" title="Sysadmin DeCal"></a>Sysadmin DeCal</h1><h2 id="课程网站-1"><a href="#课程网站-1" class="headerlink" title="课程网站"></a>课程网站</h2><p><a href="https://decal.ocf.berkeley.edu/">https://decal.ocf.berkeley.edu/</a></p><p>详见<a href="https://csdiy.wiki/%E7%BC%96%E7%A8%8B%E5%85%A5%E9%97%A8/DeCal/#_1">UCB: Sysadmin DeCal - CS自学指南</a></p><h2 id="课程视频-1"><a href="#课程视频-1" class="headerlink" title="课程视频"></a>课程视频</h2><p>英文:<a href="https://drive.google.com/file/d/1lbHR03faWPv4QxKypl5DnmG1TXklA9_4/view">https://drive.google.com/file/d/1lbHR03faWPv4QxKypl5DnmG1TXklA9_4/view</a></p><p>中文:<a href="https://www.bilibili.com/video/BV1kM4m1y7ct/?spm_id_from=333.788.videopod.episodes&vd_source=c06338b0283c611d7a47c62b0ed23dfa">https://www.bilibili.com/video/BV1kM4m1y7ct/?spm_id_from=333.788.videopod.episodes&vd_source=c06338b0283c611d7a47c62b0ed23dfa</a></p><h1 id="CS61A-已学完"><a href="#CS61A-已学完" class="headerlink" title="CS61A(已学完)"></a>CS61A(已学完)</h1><h2 id="课程网站-2"><a href="#课程网站-2" class="headerlink" title="课程网站"></a>课程网站</h2><p><a href="https://cs61a.org/">https://cs61a.org/</a></p><p>详见<a href="https://csdiy.wiki/%E7%BC%96%E7%A8%8B%E5%85%A5%E9%97%A8/Python/CS61A/">UCB CS61A: Structure and Interpretation of Computer Programs - CS自学指南</a></p><h2 id="课程教材"><a href="#课程教材" class="headerlink" title="课程教材"></a>课程教材</h2><p>英文:<a href="https://www.composingprograms.com/">https://www.composingprograms.com/</a></p><p>中文:<a href="https://composingprograms.netlify.app/">https://composingprograms.netlify.app/</a></p><h2 id="我的代码-1"><a href="#我的代码-1" class="headerlink" title="我的代码"></a>我的代码</h2><p><a href="https://github.com/wang-jiahao/CS61A%EF%BC%8824">https://github.com/wang-jiahao/CS61A(24</a> spring)</p><h1 id="CS106L(已学完)"><a href="#CS106L(已学完)" class="headerlink" title="CS106L(已学完)"></a>CS106L(已学完)</h1><h2 id="课程网站-3"><a href="#课程网站-3" class="headerlink" title="课程网站"></a>课程网站</h2><p><a href="https://web.stanford.edu/class/cs106l/">https://web.stanford.edu/class/cs106l/</a></p><p>详见<a href="https://csdiy.wiki/%E7%BC%96%E7%A8%8B%E5%85%A5%E9%97%A8/cpp/CS106L/">Stanford CS106L: Standard C++ Programming - CS自学指南</a></p><h2 id="课程视频-2"><a href="#课程视频-2" class="headerlink" title="课程视频"></a>课程视频</h2><p>英文:<a href="https://www.youtube.com/channel/UCSqr6y-eaQT_qZJVUm_4QxQ/playlists">https://www.youtube.com/channel/UCSqr6y-eaQT_qZJVUm_4QxQ/playlists</a></p><p>中文:<a href="https://www.bilibili.com/video/BV19x4y1E79V/?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=c06338b0283c611d7a47c62b0ed23dfa">https://www.bilibili.com/video/BV19x4y1E79V/?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=c06338b0283c611d7a47c62b0ed23dfa</a></p><h2 id="我的代码-2"><a href="#我的代码-2" class="headerlink" title="我的代码"></a>我的代码</h2><p><a href="https://github.com/wang-jiahao/CS106L">https://github.com/wang-jiahao/CS106L</a></p><h2 id="我的笔记"><a href="#我的笔记" class="headerlink" title="我的笔记"></a>我的笔记</h2><p><a href="https://wang-jiahao.github.io/2024/12/05/%E7%BB%A7%E6%89%BF%E5%92%8C%E6%A8%A1%E6%9D%BF%E7%B1%BB-C/">10-Inheritance and Template Classes</a></p><p><a href="https://wang-jiahao.github.io/2024/12/05/RAII-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/">11-RAII and Smart Pointers</a></p><p><a href="https://wang-jiahao.github.io/2024/12/05/%E5%A4%9A%E7%BA%BF%E7%A8%8B-C/">12-Multithreading</a></p><p><a href="https://wang-jiahao.github.io/2024/12/05/%E6%A8%A1%E6%9D%BF%E5%85%83%E7%BC%96%E7%A8%8B-C/">13-Template Metaprogramming</a></p><h2 id="课程大纲"><a href="#课程大纲" class="headerlink" title="课程大纲"></a>课程大纲</h2><ul><li><input checked="" disabled="" type="checkbox"> streams(istringstream and ostringstream) </li><li><input checked="" disabled="" type="checkbox"> types(pair, tuple and structured binding)</li><li><input checked="" disabled="" type="checkbox"> sequence containers(vector, deque, list, array)</li><li><input checked="" disabled="" type="checkbox"> container adaptors(stack, queue)</li><li><input checked="" disabled="" type="checkbox"> associative containers(map, set)</li><li><input checked="" disabled="" type="checkbox"> templates and functions(templates, lambda functions and bind)</li><li><input checked="" disabled="" type="checkbox"> algorithms(sort, nth_element, stable_partition, copy_if, erase-remove idiom)</li><li><input checked="" disabled="" type="checkbox"> operators overloading(inside the class or outside the class and why)</li><li><input checked="" disabled="" type="checkbox"> move semantics(move function, move constructor, move assignment, l-value, r-value, && and copy_elision)</li><li><input checked="" disabled="" type="checkbox"> inheritance and template classes(virtual functions, class inheritance and C++20 <concepts>)</li><li><input checked="" disabled="" type="checkbox"> RAII and smart pointers(exceptions, RAII, fstream, smart pointers)</li><li><input checked="" disabled="" type="checkbox"> Multithreading(thread, lock)</li><li><input checked="" disabled="" type="checkbox"> Template Metaprogramming(meta function, constexpr)</li></ul><h1 id="CS61B"><a href="#CS61B" class="headerlink" title="CS61B"></a>CS61B</h1><h2 id="课程网站-4"><a href="#课程网站-4" class="headerlink" title="课程网站"></a>课程网站</h2><p><a href="https://sp24.datastructur.es/">https://sp24.datastructur.es/</a></p><p>详见<a href="https://csdiy.wiki/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/CS61B/#_1">UCB CS61B: Data Structures and Algorithms - CS自学指南</a></p><h2 id="课程视频-3"><a href="#课程视频-3" class="headerlink" title="课程视频"></a>课程视频</h2><p>英文:<a href="https://www.youtube.com/playlist?list=PLnp31xXvnfRq5wRDN8wZFy7GrrJXUtr1q">https://www.youtube.com/playlist?list=PLnp31xXvnfRq5wRDN8wZFy7GrrJXUtr1q</a></p><p>中文:<a href="https://www.bilibili.com/video/BV1q3411V7rS/?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=c06338b0283c611d7a47c62b0ed23dfa">https://www.bilibili.com/video/BV1q3411V7rS/?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=c06338b0283c611d7a47c62b0ed23dfa</a></p><h2 id="我的代码-3"><a href="#我的代码-3" class="headerlink" title="我的代码"></a>我的代码</h2><p><a href="https://github.com/wang-jiahao/CS61B">https://github.com/wang-jiahao/CS61B</a></p><h1 id="CS188-已学完"><a href="#CS188-已学完" class="headerlink" title="CS188(已学完)"></a>CS188(已学完)</h1><h2 id="课程网站-5"><a href="#课程网站-5" class="headerlink" title="课程网站"></a>课程网站</h2><p><a href="https://inst.eecs.berkeley.edu/~cs188/fa24/">https://inst.eecs.berkeley.edu/~cs188/fa24/</a></p><p>详见<a href="https://csdiy.wiki/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/CS188/">UCB CS188: Introduction to Artificial Intelligence - CS自学指南</a></p><h2 id="课程视频-4"><a href="#课程视频-4" class="headerlink" title="课程视频"></a>课程视频</h2><p><a href="https://inst.eecs.berkeley.edu/~cs188/fa22/">https://inst.eecs.berkeley.edu/~cs188/fa22/</a></p><h2 id="我的代码-4"><a href="#我的代码-4" class="headerlink" title="我的代码"></a>我的代码</h2><p><a href="https://github.com/wang-jiahao/CS188">https://github.com/wang-jiahao/CS188</a></p><h1 id="李宏毅深度学习"><a href="#李宏毅深度学习" class="headerlink" title="李宏毅深度学习"></a>李宏毅深度学习</h1><h2 id="课程网站-6"><a href="#课程网站-6" class="headerlink" title="课程网站"></a>课程网站</h2><p><a href="https://speech.ee.ntu.edu.tw/~hylee/ml/2022-spring.php">https://speech.ee.ntu.edu.tw/~hylee/ml/2022-spring.php</a></p><p>详见<a href="https://csdiy.wiki/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/LHY/#_2">国立台湾大学: 李宏毅机器学习 - CS自学指南</a></p><h2 id="课程视频-5"><a href="#课程视频-5" class="headerlink" title="课程视频"></a>课程视频</h2><p><a href="https://www.bilibili.com/video/BV1Wv411h7kN/?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=c06338b0283c611d7a47c62b0ed23dfa">https://www.bilibili.com/video/BV1Wv411h7kN/?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=c06338b0283c611d7a47c62b0ed23dfa</a></p><h2 id="我的代码-5"><a href="#我的代码-5" class="headerlink" title="我的代码"></a>我的代码</h2><p><a href="https://github.com/wang-jiahao/Deep-Learning">https://github.com/wang-jiahao/Deep-Learning</a></p>]]></content>
<categories>
<category>CS自学</category>
</categories>
<tags>
<tag>CS自学</tag>
<tag>代码</tag>
</tags>
</entry>
<entry>
<title>机器学习基本概念</title>
<link href="/posts/5149.html"/>
<url>/posts/5149.html</url>
<content type="html"><![CDATA[<p><a href="https://www.bilibili.com/video/BV1Wv411h7kN?spm_id_from=333.788.videopod.episodes&vd_source=c06338b0283c611d7a47c62b0ed23dfa&p=3">第一节 2021 - (上) - 机器学习基本概念简介</a></p><p><a href="https://diamond-mule-bee.notion.site/01-Regression-db3f17ba626a43668e016d09d39e35e5#6aac348ef027455e825a2969e071000f">高质量笔记</a></p><p>机器学习(Machine Learning,简称ML)是一种让计算机通过数据和经验进行学习、改进和决策的技术。它是人工智能(AI)领域的一个重要分支,旨在通过算法使计算机能够自动识别数据中的模式和规律,从而做出预测或决策。机器学习广泛应用于自然语言处理、计算机视觉、推荐系统、语音识别等多个领域。</p><p>以下是机器学习的基本概念和相关内容的详细介绍:</p><h3 id="1-机器学习的定义"><a href="#1-机器学习的定义" class="headerlink" title="1. 机器学习的定义"></a>1. <strong>机器学习的定义</strong></h3><p>机器学习是人工智能的一个分支,它使得计算机系统通过从数据中提取信息、寻找规律,并进行自我改进,而不需要显式编程。传统编程依赖于人工编码规则,而机器学习则依赖于通过训练数据建立模型,然后通过模型做出预测或判断。</p><h3 id="2-机器学习的主要类型"><a href="#2-机器学习的主要类型" class="headerlink" title="2. 机器学习的主要类型"></a>2. <strong>机器学习的主要类型</strong></h3><p>根据学习过程和目标的不同,机器学习主要分为以下几种类型:</p><h4 id="2-1-监督学习(Supervised-Learning)"><a href="#2-1-监督学习(Supervised-Learning)" class="headerlink" title="2.1 监督学习(Supervised Learning)"></a>2.1 监督学习(Supervised Learning)</h4><p>监督学习是一种通过已标注的数据进行学习的方式。数据集包含输入数据和对应的标签(目标值),算法通过学习这些输入和标签之间的关系,来预测新的输入数据的标签。常见的任务包括分类和回归。</p><ul><li><strong>分类</strong>:预测数据所属的类别,如垃圾邮件检测、图像识别。</li><li><strong>回归</strong>:预测连续的数值,如股票价格预测、房价预测。</li></ul><p>常用的监督学习算法包括:</p><ul><li>线性回归(Linear Regression)</li><li>支持向量机(SVM,Support Vector Machine)</li><li>决策树(Decision Tree)</li><li>k近邻算法(K-Nearest Neighbors, KNN)</li><li>神经网络(Neural Networks)</li></ul><h4 id="2-2-无监督学习(Unsupervised-Learning)"><a href="#2-2-无监督学习(Unsupervised-Learning)" class="headerlink" title="2.2 无监督学习(Unsupervised Learning)"></a>2.2 无监督学习(Unsupervised Learning)</h4><p>无监督学习与监督学习不同,它不依赖于标签数据,而是从未标记的数据中寻找内在的结构或模式。无监督学习的目标通常是数据的聚类、降维等。</p><ul><li><strong>聚类</strong>:将数据集中的数据点分成若干个类别,常见的算法有K-means、层次聚类(Hierarchical Clustering)等。</li><li><strong>降维</strong>:减少数据中的特征数量,同时尽可能保留数据的主要信息,常用的算法有主成分分析(PCA)。</li></ul><h4 id="2-3-半监督学习(Semi-supervised-Learning)"><a href="#2-3-半监督学习(Semi-supervised-Learning)" class="headerlink" title="2.3 半监督学习(Semi-supervised Learning)"></a>2.3 半监督学习(Semi-supervised Learning)</h4><p>半监督学习介于监督学习和无监督学习之间,它利用大量未标记数据和少量标记数据的组合来进行训练。这种方法特别适用于标注数据成本高但未标数据丰富的情况。</p><h4 id="2-4-强化学习(Reinforcement-Learning)"><a href="#2-4-强化学习(Reinforcement-Learning)" class="headerlink" title="2.4 强化学习(Reinforcement Learning)"></a>2.4 强化学习(Reinforcement Learning)</h4><p>强化学习是一种让智能体(agent)通过与环境交互来学习策略的学习方式。智能体通过在环境中执行动作,观察结果,并根据结果调整策略。目标是通过不断的试错过程,学习到最优的行为策略。</p><ul><li>强化学习的核心概念包括<strong>奖励(Reward)</strong>、<strong>状态(State)</strong>、**动作(Action)**和**策略(Policy)**。</li><li>常见的强化学习算法包括Q-learning、深度强化学习(Deep Reinforcement Learning)等。</li></ul><h3 id="3-机器学习的工作流程"><a href="#3-机器学习的工作流程" class="headerlink" title="3. 机器学习的工作流程"></a>3. <strong>机器学习的工作流程</strong></h3><p>机器学习的流程通常分为以下几个阶段:</p><ol><li><strong>数据收集</strong>:获取并收集相关的数据,数据质量对模型的训练效果至关重要。</li><li><strong>数据预处理</strong>:清洗数据、去除噪声、填补缺失值、标准化/归一化数据等。</li><li><strong>特征工程</strong>:选择、提取和转换特征,以便用于模型的训练。</li><li><strong>模型选择</strong>:根据任务选择合适的机器学习算法和模型。</li><li><strong>模型训练</strong>:将数据输入到模型中,利用训练数据进行学习。</li><li><strong>模型评估</strong>:使用测试集(未参与训练的数据)来评估模型的性能,常用评估指标有准确率、精确率、召回率、F1分数等。</li><li><strong>模型优化</strong>:通过调整超参数、选择不同算法或增加数据量等方式优化模型。</li><li><strong>模型部署与应用</strong>:将训练好的模型部署到实际应用中,如推荐系统、预测系统等。</li></ol><h3 id="4-常见的机器学习算法"><a href="#4-常见的机器学习算法" class="headerlink" title="4. 常见的机器学习算法"></a>4. <strong>常见的机器学习算法</strong></h3><p>以下是几种常用的机器学习算法:</p><h4 id="4-1-线性回归(Linear-Regression)"><a href="#4-1-线性回归(Linear-Regression)" class="headerlink" title="4.1 线性回归(Linear Regression)"></a>4.1 线性回归(Linear Regression)</h4><p>线性回归用于解决回归问题,即预测一个连续的数值。它假设输入特征与输出之间存在线性关系。通过最小化误差平方和,模型可以找到最佳的回归线。</p><h4 id="4-2-支持向量机(SVM,Support-Vector-Machine)"><a href="#4-2-支持向量机(SVM,Support-Vector-Machine)" class="headerlink" title="4.2 支持向量机(SVM,Support Vector Machine)"></a>4.2 支持向量机(SVM,Support Vector Machine)</h4><p>支持向量机是一个强大的分类和回归模型,特别适合高维数据。它的目标是找到一个超平面,以最大化类别间的间隔,从而实现良好的分类。</p><h4 id="4-3-决策树(Decision-Tree)"><a href="#4-3-决策树(Decision-Tree)" class="headerlink" title="4.3 决策树(Decision Tree)"></a>4.3 决策树(Decision Tree)</h4><p>决策树是一个树状结构,用于决策或分类。每个节点代表一个特征,分支代表特征值,叶子节点代表类别标签或预测值。它直观易懂,适用于分类和回归任务。</p><h4 id="4-4-K近邻(KNN,K-Nearest-Neighbors)"><a href="#4-4-K近邻(KNN,K-Nearest-Neighbors)" class="headerlink" title="4.4 K近邻(KNN,K-Nearest Neighbors)"></a>4.4 K近邻(KNN,K-Nearest Neighbors)</h4><p>KNN是一种基于实例的学习方法,分类时根据输入样本与训练样本之间的距离来决定分类标签。KNN简单且易于理解,但在大数据集上计算效率较低。</p><h4 id="4-5-神经网络(Neural-Networks)"><a href="#4-5-神经网络(Neural-Networks)" class="headerlink" title="4.5 神经网络(Neural Networks)"></a>4.5 神经网络(Neural Networks)</h4><p>神经网络模拟了生物神经系统的工作方式。它由多个节点(神经元)组成,通过激活函数连接成层级结构。神经网络在图像识别、语音处理等领域表现突出。</p><h4 id="4-6-随机森林(Random-Forest)"><a href="#4-6-随机森林(Random-Forest)" class="headerlink" title="4.6 随机森林(Random Forest)"></a>4.6 随机森林(Random Forest)</h4><p>随机森林是一种集成学习方法,通过构建多棵决策树并取它们的平均值或投票结果来提高模型的稳定性和准确性。它广泛应用于分类和回归问题。</p><h3 id="5-过拟合与欠拟合"><a href="#5-过拟合与欠拟合" class="headerlink" title="5. 过拟合与欠拟合"></a>5. <strong>过拟合与欠拟合</strong></h3><p>在机器学习中,<strong>过拟合(Overfitting)</strong>和<strong>欠拟合(Underfitting)</strong>是常见的问题:</p><ul><li><strong>过拟合</strong>:模型在训练集上表现很好,但在测试集上效果差。过拟合通常发生在模型过于复杂时,能够“记住”训练数据中的噪声和细节。</li><li><strong>欠拟合</strong>:模型过于简单,无法捕捉到数据中的规律,导致训练集和测试集的性能都很差。</li></ul><p>解决过拟合和欠拟合的方法包括选择适当的模型复杂度、使用正则化技术、增加训练数据、交叉验证等。</p><h3 id="6-评估与调优"><a href="#6-评估与调优" class="headerlink" title="6. 评估与调优"></a>6. <strong>评估与调优</strong></h3><p>机器学习模型的评估和调优是确保其性能的重要步骤。常用的评估指标有:</p><ul><li><strong>准确率(Accuracy)</strong>:正确分类的样本数占总样本数的比例。</li><li><strong>精确率(Precision)</strong>:预测为正类的样本中,真正正类的比例。</li><li><strong>召回率(Recall)</strong>:实际为正类的样本中,预测为正类的比例。</li><li><strong>F1分数</strong>:精确率和召回率的调和平均数,常用来衡量模型在分类问题中的综合表现。</li></ul><p>超参数调优(如通过网格搜索或随机搜索)和交叉验证(如K折交叉验证)可以进一步提高模型的性能。</p><h3 id="7-深度学习(Deep-Learning)"><a href="#7-深度学习(Deep-Learning)" class="headerlink" title="7. 深度学习(Deep Learning)"></a>7. <strong>深度学习(Deep Learning)</strong></h3><p>深度学习是机器学习的一个子领域,特别关注多层神经网络(深度神经网络)的训练和优化。深度学习通过自动提取特征、端到端的学习方式,尤其在计算机视觉、自然语言处理等领域表现出色。</p><p>常见的深度学习模型有:</p><ul><li>卷积神经网络(CNN):用于图像处理和计算机视觉。</li><li>循环神经网络(RNN):用于序列数据处理,如语音识别和自然语言处理。</li><li>生成对抗网络(GAN):用于生成数据,如图像生成。</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>机器学习是通过让计算机学习数据中的模式和规律,从而在未知数据上做出预测或决策的技术。它涉及监督学习、无监督学习、半监督学习、强化学习等多种学习类型,并通过不同的算法和技术来解决实际问题。机器学习的应用范围广泛,几乎涉及所有行业,并在不断推动科技发展和创新。</p>]]></content>
<categories>
<category>深度学习</category>
<category>国立台湾大学:李宏毅机器学习</category>
</categories>
<tags>
<tag>机器学习</tag>
<tag>Machine Learning</tag>
</tags>
</entry>
<entry>
<title>最小生成树</title>
<link href="/posts/57313.html"/>
<url>/posts/57313.html</url>
<content type="html"><![CDATA[<p><a href="https://wang-jiahao.github.io/files/15.%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91.pdf">最小生成树.pdf</a></p><h2 id="最小生成树"><a href="#最小生成树" class="headerlink" title="最小生成树"></a>最小生成树</h2><p>我们可以使用 Kruskal 算法或 Prim 算法来求解图的最小生成树。这里选择使用 Kruskal 算法,它的思想是按边的权重从小到大排序,然后逐步添加边到最小生成树中,保证添加的每条边不会形成环,最终得到一个包含所有节点的生成树。</p><h3 id="步骤:"><a href="#步骤:" class="headerlink" title="步骤:"></a>步骤:</h3><ol><li><strong>输入解析</strong>:读取节点数 <code>N</code> 和边数 <code>M</code>,接着读取每条边的节点对和权重。</li><li><strong>排序</strong>:将所有边按照权重从小到大排序。</li><li><strong>并查集</strong>:使用并查集(Union-Find)来管理图中的连通性,确保在添加一条边时不会形成环。</li><li><strong>逐步添加边</strong>:从最小的边开始,如果这条边连接的两个节点属于不同的连通块,则将其加入生成树,并合并这两个连通块。直到生成树包含 <code>N-1</code> 条边。</li></ol><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><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><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><algorithm></span></span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><br><span class="hljs-comment">// 并查集数据结构</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">UnionFind</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">UnionFind</span>(<span class="hljs-type">int</span> n) {<br> parent.<span class="hljs-built_in">resize</span>(n);<br> size.<span class="hljs-built_in">resize</span>(n, <span class="hljs-number">1</span>);<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < n; i++) {<br> parent[i] = i;<br> }<br> }<br><br> <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">find</span><span class="hljs-params">(<span class="hljs-type">int</span> u)</span> </span>{<br> <span class="hljs-keyword">if</span> (parent[u] != u) {<br> parent[u] = <span class="hljs-built_in">find</span>(parent[u]); <span class="hljs-comment">// 路径压缩</span><br> }<br> <span class="hljs-keyword">return</span> parent[u];<br> }<br><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">unionSets</span><span class="hljs-params">(<span class="hljs-type">int</span> u, <span class="hljs-type">int</span> v)</span> </span>{<br> <span class="hljs-type">int</span> rootU = <span class="hljs-built_in">find</span>(u);<br> <span class="hljs-type">int</span> rootV = <span class="hljs-built_in">find</span>(v);<br><br> <span class="hljs-keyword">if</span> (rootU != rootV) {<br> <span class="hljs-comment">// 按秩合并,保证树的深度较小</span><br> <span class="hljs-keyword">if</span> (size[rootU] < size[rootV]) {<br> parent[rootU] = rootV;<br> size[rootV] += size[rootU];<br> } <span class="hljs-keyword">else</span> {<br> parent[rootV] = rootU;<br> size[rootU] += size[rootV];<br> }<br> }<br> }<br><br><span class="hljs-keyword">private</span>:<br> vector<<span class="hljs-type">int</span>> parent;<br> vector<<span class="hljs-type">int</span>> size;<br>};<br><br><span class="hljs-comment">// 边的结构体</span><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Edge</span> {<br> <span class="hljs-type">int</span> u, v, w;<br> <span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span><(<span class="hljs-type">const</span> Edge& other) <span class="hljs-type">const</span> {<br> <span class="hljs-keyword">return</span> w < other.w; <span class="hljs-comment">// 按权重升序排序</span><br> }<br>};<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> N, M;<br> cin >> N >> M;<br> <span class="hljs-function">vector<Edge> <span class="hljs-title">edges</span><span class="hljs-params">(M)</span></span>;<br><br> <span class="hljs-comment">// 读取边的信息</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < M; i++) {<br> cin >> edges[i].u >> edges[i].v >> edges[i].w;<br> }<br><br> <span class="hljs-comment">// 对边按权重进行排序</span><br> <span class="hljs-built_in">sort</span>(edges.<span class="hljs-built_in">begin</span>(), edges.<span class="hljs-built_in">end</span>());<br><br> <span class="hljs-comment">// 使用并查集处理最小生成树</span><br> <span class="hljs-function">UnionFind <span class="hljs-title">uf</span><span class="hljs-params">(N)</span></span>;<br> <span class="hljs-type">int</span> mstWeight = <span class="hljs-number">0</span>;<br> <span class="hljs-type">int</span> edgeCount = <span class="hljs-number">0</span>;<br><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span>& edge : edges) {<br> <span class="hljs-keyword">if</span> (uf.<span class="hljs-built_in">find</span>(edge.u) != uf.<span class="hljs-built_in">find</span>(edge.v)) {<br> uf.<span class="hljs-built_in">unionSets</span>(edge.u, edge.v);<br> mstWeight += edge.w;<br> edgeCount++;<br> <span class="hljs-keyword">if</span> (edgeCount == N - <span class="hljs-number">1</span>) {<br> <span class="hljs-keyword">break</span>; <span class="hljs-comment">// 最小生成树包含 N-1 条边</span><br> }<br> }<br> }<br><br> <span class="hljs-comment">// 输出最小生成树的权重</span><br> cout << mstWeight << endl;<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="解释:"><a href="#解释:" class="headerlink" title="解释:"></a>解释:</h3><ol><li><strong>UnionFind 类</strong>:实现了并查集的数据结构,支持 <code>find</code>(查找根节点)和 <code>unionSets</code>(合并两个节点的连通块)。</li><li><strong>Edge 结构体</strong>:每个边包含两个节点和该边的权重,并重载了 <code><</code> 操作符,以便可以按权重排序。</li><li>主函数:<ul><li>先读取所有边的输入并存储在 <code>edges</code> 向量中。</li><li>将边按权重升序排序。</li><li>使用并查集处理每条边,判断其是否可以加入到最小生成树中(即它的两个端点是否属于不同的连通块)。</li><li>当已加入的边数为 <code>N-1</code> 时,停止算法。</li><li>输出最小生成树的总权重。</li></ul></li></ol><h3 id="复杂度:"><a href="#复杂度:" class="headerlink" title="复杂度:"></a>复杂度:</h3><ul><li><strong>时间复杂度</strong>:排序的时间复杂度为 <code>O(M log M)</code>,每次并查集的 <code>find</code> 和 <code>union</code> 操作平均时间复杂度为 <code>O(α(N))</code>(α 是反阿克曼函数,几乎是常数),因此总时间复杂度是 <code>O(M log M + M α(N))</code>。在最坏情况下,<code>M ≤ 100</code> 和 <code>N ≤ 100</code>,所以效率足够满足题目要求。</li></ul><h3 id="测试样例:"><a href="#测试样例:" class="headerlink" title="测试样例:"></a>测试样例:</h3><h4 id="输入:"><a href="#输入:" class="headerlink" title="输入:"></a>输入:</h4><figure class="highlight basic"><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 basic"><span class="hljs-symbol">4 </span><span class="hljs-number">4</span><br><span class="hljs-symbol">0 </span><span class="hljs-number">1</span> <span class="hljs-number">1</span><br><span class="hljs-symbol">1 </span><span class="hljs-number">2</span> <span class="hljs-number">2</span><br><span class="hljs-symbol">2 </span><span class="hljs-number">3</span> <span class="hljs-number">3</span><br><span class="hljs-symbol">0 </span><span class="hljs-number">3</span> <span class="hljs-number">4</span><br></code></pre></td></tr></table></figure><h4 id="输出:"><a href="#输出:" class="headerlink" title="输出:"></a>输出:</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">6<br></code></pre></td></tr></table></figure><h3 id="解释:-1"><a href="#解释:-1" class="headerlink" title="解释:"></a>解释:</h3><p>最小生成树包含的边是:</p><ul><li>边 (0, 1) 权重 1</li><li>边 (1, 2) 权重 2</li><li>边 (2, 3) 权重 3</li></ul><p>总权重为 1 + 2 + 3 = 6。</p>]]></content>
<categories>
<category>数据结构与算法</category>
<category>图论</category>
</categories>
<tags>
<tag>图</tag>
<tag>最小生成树</tag>
</tags>
</entry>
<entry>
<title>图 深度优先遍历 广度优先遍历</title>
<link href="/posts/28191.html"/>
<url>/posts/28191.html</url>
<content type="html"><![CDATA[<p><a href="https://wang-jiahao.github.io/files/13.%E5%9B%BE%E5%8F%8A%E5%85%B6%E9%81%8D%E5%8E%86.pdf">图及其遍历.pdf</a></p><h2 id="图,BFS-DFS"><a href="#图,BFS-DFS" class="headerlink" title="图,BFS, DFS"></a>图,BFS, DFS</h2><h3 id="1-图的定义与应用"><a href="#1-图的定义与应用" class="headerlink" title="1. 图的定义与应用"></a>1. 图的定义与应用</h3><ul><li>图是由<strong>节点</strong>(顶点)和<strong>边</strong>构成的数据结构,广泛应用于交通网络、通信网络、社交网络等。</li><li>常见的图问题包括:<ul><li>图着色(节点染色,最少颜色数目)</li><li>最短路径(最小移动次数或最小代价)</li><li>复杂问题如魔方复原等。</li></ul></li></ul><hr><h3 id="2-图的表示方式"><a href="#2-图的表示方式" class="headerlink" title="2. 图的表示方式"></a>2. 图的表示方式</h3><h4 id="邻接矩阵(Adjacency-Matrix)"><a href="#邻接矩阵(Adjacency-Matrix)" class="headerlink" title="邻接矩阵(Adjacency Matrix)"></a>邻接矩阵(Adjacency Matrix)</h4><ul><li><p>使用一个二维矩阵表示图,矩阵大小为 n×nn \times n (nn 是顶点数)。</p></li><li><p>特点:</p><ul><li><strong>优点</strong>:快速查询两个节点是否相邻。</li><li><strong>缺点</strong>:空间复杂度为 Θ(n2)\Theta(n^2),不适合稀疏图。</li></ul></li><li><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-type">const</span> <span class="hljs-type">int</span> INF = <span class="hljs-number">1e9</span>; <span class="hljs-comment">// 表示无穷大</span><br><span class="hljs-type">int</span> adjMatrix[<span class="hljs-number">100</span>][<span class="hljs-number">100</span>]; <span class="hljs-comment">// 假设最多有100个节点</span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">initializeMatrix</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span> </span>{<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < n; ++i) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j < n; ++j) {<br> adjMatrix[i][j] = (i == j ? <span class="hljs-number">0</span> : INF); <span class="hljs-comment">// 自环为0,其他初始化为无穷大</span><br> }<br> }<br>}<br></code></pre></td></tr></table></figure></li></ul><h4 id="邻接表(Adjacency-List)"><a href="#邻接表(Adjacency-List)" class="headerlink" title="邻接表(Adjacency List)"></a>邻接表(Adjacency List)</h4><ul><li><p>使用链表或向量(vector)存储每个节点的邻接信息。</p></li><li><p>特点:</p><ul><li><strong>优点</strong>:空间复杂度为 Θ(n+m)\Theta(n + m) (mm 是边数),适合稀疏图。</li><li><strong>缺点</strong>:查询两个节点是否相邻速度较慢。</li></ul></li><li><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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><br>vector<<span class="hljs-type">int</span>> adjList[<span class="hljs-number">100</span>]; <span class="hljs-comment">// 假设最多有100个节点</span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">addEdge</span><span class="hljs-params">(<span class="hljs-type">int</span> u, <span class="hljs-type">int</span> v)</span> </span>{<br> adjList[u].<span class="hljs-built_in">push_back</span>(v);<br> adjList[v].<span class="hljs-built_in">push_back</span>(u); <span class="hljs-comment">// 如果是无向图</span><br>}<br></code></pre></td></tr></table></figure></li></ul><hr><h3 id="3-图的遍历算法"><a href="#3-图的遍历算法" class="headerlink" title="3. 图的遍历算法"></a>3. 图的遍历算法</h3><h4 id="广度优先搜索(BFS)"><a href="#广度优先搜索(BFS)" class="headerlink" title="广度优先搜索(BFS)"></a>广度优先搜索(BFS)</h4><ul><li><p><strong>特点</strong>:按层遍历节点,常用于最短路径计算。</p></li><li><p>实现思路</p><p>:</p><ol><li>使用队列维护待访问节点。</li><li>每次访问队首节点,并将其所有未访问的邻接节点加入队列。</li></ol></li><li><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><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 cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><queue></span></span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">BFS</span><span class="hljs-params">(<span class="hljs-type">int</span> start, <span class="hljs-type">int</span> n)</span> </span>{<br> <span class="hljs-function">vector<<span class="hljs-type">bool</span>> <span class="hljs-title">visited</span><span class="hljs-params">(n, <span class="hljs-literal">false</span>)</span></span>;<br> queue<<span class="hljs-type">int</span>> q;<br> q.<span class="hljs-built_in">push</span>(start);<br> visited[start] = <span class="hljs-literal">true</span>;<br><br> <span class="hljs-keyword">while</span> (!q.<span class="hljs-built_in">empty</span>()) {<br> <span class="hljs-type">int</span> node = q.<span class="hljs-built_in">front</span>(); q.<span class="hljs-built_in">pop</span>();<br> <span class="hljs-comment">// 访问当前节点</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> neighbor : adjList[node]) {<br> <span class="hljs-keyword">if</span> (!visited[neighbor]) {<br> q.<span class="hljs-built_in">push</span>(neighbor);<br> visited[neighbor] = <span class="hljs-literal">true</span>;<br> }<br> }<br> }<br>}<br></code></pre></td></tr></table></figure></li></ul><h4 id="深度优先搜索(DFS)"><a href="#深度优先搜索(DFS)" class="headerlink" title="深度优先搜索(DFS)"></a>深度优先搜索(DFS)</h4><ul><li><p><strong>特点</strong>:尽可能深入探索一条路径,遇到死胡同时回溯。</p></li><li><p><strong>实现方式</strong>:可以递归或用栈实现。</p></li><li><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><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-function"><span class="hljs-type">void</span> <span class="hljs-title">DFS</span><span class="hljs-params">(<span class="hljs-type">int</span> node, vector<<span class="hljs-type">bool</span>>& visited)</span> </span>{<br> visited[node] = <span class="hljs-literal">true</span>;<br> <span class="hljs-comment">// 访问当前节点</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> neighbor : adjList[node]) {<br> <span class="hljs-keyword">if</span> (!visited[neighbor]) {<br> <span class="hljs-built_in">DFS</span>(neighbor, visited);<br> }<br> }<br>}<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">startDFS</span><span class="hljs-params">(<span class="hljs-type">int</span> start, <span class="hljs-type">int</span> n)</span> </span>{<br> <span class="hljs-function">vector<<span class="hljs-type">bool</span>> <span class="hljs-title">visited</span><span class="hljs-params">(n, <span class="hljs-literal">false</span>)</span></span>;<br> <span class="hljs-built_in">DFS</span>(start, visited);<br>}<br></code></pre></td></tr></table></figure></li></ul><hr><h3 id="4-BFS与DFS的比较"><a href="#4-BFS与DFS的比较" class="headerlink" title="4. BFS与DFS的比较"></a>4. BFS与DFS的比较</h3><table><thead><tr><th><strong>算法</strong></th><th><strong>优点</strong></th><th><strong>缺点</strong></th><th><strong>时间复杂度</strong></th></tr></thead><tbody><tr><td>BFS</td><td>找最短路径,层次遍历直观</td><td>内存开销大(需要维护队列)</td><td>O(n+m)O(n + m)</td></tr><tr><td>DFS</td><td>内存使用低,适合搜索深层信息</td><td>无法保证最短路径</td><td>O(n+m)O(n + m)</td></tr></tbody></table><hr><h3 id="5-应用与扩展"><a href="#5-应用与扩展" class="headerlink" title="5. 应用与扩展"></a>5. 应用与扩展</h3><ul><li>BFS的扩展:<ul><li>求无权图的最短路径。</li><li>遍历所有连通分量。</li></ul></li><li>DFS的扩展:<ul><li>生成DFS树,记录每个节点的发现和完成时间。</li><li>边分类(树边、回边、前向边、交叉边)。</li></ul></li></ul><hr><p>如需更详细的代码或具体例子,可以根据课程内容的重点深入补充!</p><h2 id="图的定义与分类"><a href="#图的定义与分类" class="headerlink" title="图的定义与分类"></a>图的定义与分类</h2><h3 id="1-什么是图?"><a href="#1-什么是图?" class="headerlink" title="1. 什么是图?"></a>1. 什么是图?</h3><p>图(Graph)是一种数据结构,由 <strong>顶点(Vertex)</strong> 和 <strong>边(Edge)</strong> 组成,表示事物之间的关系。一个图通常记为 G=(V,E)G = (V, E),其中:</p><ul><li>VV 是顶点的集合,表示图中的所有节点。</li><li>EE 是边的集合,表示节点之间的连接关系。</li></ul><h3 id="2-无权图(Unweighted-Graph)"><a href="#2-无权图(Unweighted-Graph)" class="headerlink" title="2. 无权图(Unweighted Graph)"></a>2. 无权图(Unweighted Graph)</h3><ul><li><strong>无权图</strong>是一类特殊的图,在无权图中,每条边没有权重,即所有边的代价或距离视为相等(通常为1)。</li><li>无权图通常用于简化问题,例如寻找两点之间的最短路径等。</li></ul><p><strong>示例:</strong></p><ul><li>如果 AA 和 BB 之间有一条边,这条边没有具体的权重,仅表示 AA 和 BB 之间有连接关系。</li></ul><p>无权图的一个简单表示:</p><figure class="highlight ada"><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 ada">A <span class="hljs-comment">--- B</span><br>| |<br>C <span class="hljs-comment">--- D</span><br></code></pre></td></tr></table></figure><p>在上述图中,所有边的权重隐含为1(默认值),因此路径的长度等于经过的边数。</p><hr><h3 id="3-图的分类"><a href="#3-图的分类" class="headerlink" title="3. 图的分类"></a>3. 图的分类</h3><p>根据顶点和边的不同特性,图可以分为多种类型。</p><h4 id="3-1-按边是否有方向分类"><a href="#3-1-按边是否有方向分类" class="headerlink" title="3.1 按边是否有方向分类"></a>3.1 按边是否有方向分类</h4><ol><li><p><strong>无向图(Undirected Graph)</strong>:</p><ul><li>图中的边没有方向,即 (u,v)(u, v) 和 (v,u)(v, u) 表示同一条边。</li><li>无向图适用于对称关系,如社交网络中的“好友关系”。</li></ul><p><strong>示例:</strong></p><figure class="highlight ada"><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 ada">A <span class="hljs-comment">--- B</span><br>| |<br>C <span class="hljs-comment">--- D</span><br></code></pre></td></tr></table></figure></li><li><p><strong>有向图(Directed Graph,或Digraph)</strong>:</p><ul><li>图中的边有方向,即 (u,v)(u, v) 表示从 uu 到 vv 的有向边,(v,u)(v, u) 表示从 vv 到 uu 的另一条独立的有向边。</li><li>有向图适用于单向关系,如网页之间的超链接、任务的依赖关系。</li></ul><p><strong>示例:</strong></p><figure class="highlight css"><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 css"><span class="hljs-selector-tag">A</span> → <span class="hljs-selector-tag">B</span><br>↑ ↓<br>C ← D<br></code></pre></td></tr></table></figure></li></ol><hr><h4 id="3-2-按边是否有权分类"><a href="#3-2-按边是否有权分类" class="headerlink" title="3.2 按边是否有权分类"></a>3.2 按边是否有权分类</h4><ol><li><p><strong>无权图(Unweighted Graph)</strong>:</p><ul><li>边没有权重,所有边的代价相等。</li><li>通常用于简单的路径或连通性问题。</li></ul></li><li><p><strong>有权图(Weighted Graph)</strong>:</p><ul><li>边带有权重,用于表示边的“代价”或“距离”。</li><li>有权图适用于需要考虑代价的场景,例如最短路径(Dijkstra算法)或最小生成树(Kruskal算法)。</li></ul><p><strong>示例:</strong></p><figure class="highlight brainfuck"><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 brainfuck"><span class="hljs-comment">A</span> <span class="hljs-literal">--</span><span class="hljs-comment">3</span><span class="hljs-literal">--</span> <span class="hljs-comment">B</span><br><span class="hljs-comment">| /</span><br><span class="hljs-comment">2 4</span><br><span class="hljs-comment">| /</span><br><span class="hljs-comment">C</span> <span class="hljs-literal">--</span><span class="hljs-comment">5</span><span class="hljs-literal">--</span> <span class="hljs-comment">D</span><br></code></pre></td></tr></table></figure></li></ol><hr><h4 id="3-3-按是否允许边重复或自环分类"><a href="#3-3-按是否允许边重复或自环分类" class="headerlink" title="3.3 按是否允许边重复或自环分类"></a>3.3 按是否允许边重复或自环分类</h4><ol><li><strong>简单图(Simple Graph)</strong>:<ul><li>无自环(边的两个端点相同,如 (u,u)(u, u))且没有重边(两个顶点之间有多条边)。</li></ul></li><li><strong>多重图(Multi Graph)</strong>:<ul><li>允许有重边,即两个节点之间可以有多条边。</li></ul></li><li><strong>伪图(Pseudo Graph)</strong>:<ul><li>允许有自环。</li></ul></li></ol><hr><h4 id="3-4-按图的连通性分类"><a href="#3-4-按图的连通性分类" class="headerlink" title="3.4 按图的连通性分类"></a>3.4 按图的连通性分类</h4><ol><li><p><strong>连通图(Connected Graph)</strong>:</p><ul><li>对于无向图,任意两个顶点之间都有路径相连。</li><li>对于有向图,若任意两个顶点之间都存在路径(无论方向),则称为强连通图(Strongly Connected Graph)。</li></ul><p><strong>示例:</strong></p><figure class="highlight brainfuck"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs brainfuck"><span class="hljs-comment">A</span> <span class="hljs-literal">---</span> <span class="hljs-comment">B</span> <span class="hljs-literal">---</span> <span class="hljs-comment">C</span><br></code></pre></td></tr></table></figure></li><li><p><strong>非连通图(Disconnected Graph)</strong>:</p><ul><li>图中存在不连通的部分,即某些顶点之间没有路径相连。</li></ul><p><strong>示例:</strong></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"><span class="hljs-selector-tag">A</span> --- <span class="hljs-selector-tag">B</span> C<br></code></pre></td></tr></table></figure></li></ol><hr><h4 id="3-5-特殊图的分类"><a href="#3-5-特殊图的分类" class="headerlink" title="3.5 特殊图的分类"></a>3.5 特殊图的分类</h4><ol><li><p><strong>完全图(Complete Graph)</strong>:</p><ul><li>图中任意两个顶点之间都有边相连。</li><li>一个有 nn 个顶点的完全图有 n(n−1)2\frac{n(n-1)}{2} 条边。</li></ul><p><strong>示例:</strong></p><figure class="highlight ada"><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 ada">A <span class="hljs-comment">--- B</span><br>| \ / |<br>C <span class="hljs-comment">--- D</span><br></code></pre></td></tr></table></figure></li><li><p><strong>树(Tree)</strong>:</p><ul><li>一种特殊的连通无向图,没有环路,且任意两个节点之间有唯一路径。</li><li>树是一个有 nn 个节点和 n−1n-1 条边的无向连通图。</li></ul><p><strong>示例:</strong></p><figure class="highlight mathematica"><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 mathematica"> <span class="hljs-variable">A</span><br> <span class="hljs-operator">/</span> \<br><span class="hljs-variable">B</span> <span class="hljs-built_in">C</span><br> <span class="hljs-operator">/</span> \<br> <span class="hljs-built_in">D</span> <span class="hljs-built_in">E</span><br></code></pre></td></tr></table></figure></li><li><p><strong>二分图(Bipartite Graph)</strong>:</p><ul><li>图的顶点集合可以分为两个不相交的子集 UU 和 VV,且所有边的两个端点分别属于 UU 和 VV。</li><li>典型应用:匹配问题(如婚姻匹配、任务分配)。</li></ul><p><strong>示例:</strong></p><figure class="highlight css"><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 css">U: {<span class="hljs-selector-tag">A</span>, <span class="hljs-selector-tag">B</span>}<br>V: {C, D}<br>Edges: { (<span class="hljs-selector-tag">A</span>, C), (<span class="hljs-selector-tag">B</span>, C), (<span class="hljs-selector-tag">B</span>, D) }<br></code></pre></td></tr></table></figure></li><li><p><strong>稀疏图和稠密图</strong>:</p><ul><li><strong>稀疏图(Sparse Graph)</strong>:边数接近于顶点数, ∣E∣≈O(∣V∣)|E| \approx O(|V|)。</li><li><strong>稠密图(Dense Graph)</strong>:边数接近于最大可能边数, ∣E∣≈O(∣V∣2)|E| \approx O(|V|^2)。</li></ul></li><li><p><strong>平面图(Planar Graph)</strong>:</p><ul><li>图可以嵌入平面且没有边相交。</li></ul></li></ol><hr><h3 id="4-图的表示方法"><a href="#4-图的表示方法" class="headerlink" title="4. 图的表示方法"></a>4. 图的表示方法</h3><p>图通常有两种表示方法:</p><ol><li><strong>邻接矩阵</strong>:<ul><li>使用 n×nn \times n 的矩阵存储,适合稠密图。</li><li>时间复杂度:<ul><li>查询节点是否相邻:O(1)O(1)</li><li>遍历节点的所有邻居:O(n)O(n)。</li></ul></li></ul></li><li><strong>邻接表</strong>:<ul><li>使用数组或链表存储每个节点的邻居列表,适合稀疏图。</li><li>时间复杂度:<ul><li>查询节点是否相邻:O(n)O(n)</li><li>遍历节点的所有邻居:O(k)O(k),其中 kk 是邻居数。</li></ul></li></ul></li></ol><hr><h3 id="5-图分类的总结表"><a href="#5-图分类的总结表" class="headerlink" title="5. 图分类的总结表"></a>5. 图分类的总结表</h3><table><thead><tr><th>分类依据</th><th>类型</th><th>描述</th></tr></thead><tbody><tr><td><strong>边方向</strong></td><td>无向图、有向图</td><td>边是否有方向</td></tr><tr><td><strong>边权重</strong></td><td>无权图、有权图</td><td>边是否带权重</td></tr><tr><td><strong>边特性</strong></td><td>简单图、多重图、伪图</td><td>是否允许重边或自环</td></tr><tr><td><strong>连通性</strong></td><td>连通图、非连通图</td><td>节点是否都连通</td></tr><tr><td><strong>特殊结构</strong></td><td>完全图、树、二分图</td><td>特定性质的图</td></tr><tr><td><strong>边的稠密程度</strong></td><td>稀疏图、稠密图</td><td>图的边数与节点数的关系</td></tr><tr><td><strong>几何属性</strong></td><td>平面图</td><td>图是否可以嵌入平面且无边相交</td></tr></tbody></table><p>通过这些分类,可以根据问题的特性选择合适的算法和表示方式。</p>]]></content>
<categories>
<category>数据结构与算法</category>
<category>图论</category>
</categories>
<tags>
<tag>图</tag>
<tag>DFS</tag>
<tag>BFS</tag>
</tags>
</entry>
<entry>
<title>模板元编程 C++</title>
<link href="/posts/3991.html"/>
<url>/posts/3991.html</url>
<content type="html"><![CDATA[<p><a href="https://www.bilibili.com/video/BV19x4y1E79V?spm_id_from=333.788.videopod.episodes&vd_source=c06338b0283c611d7a47c62b0ed23dfa&p=19">CS106L p19 Part 6.3 Template Metaprogramming(Guest Lecture)</a></p><h2 id="Template-Metaprogramming"><a href="#Template-Metaprogramming" class="headerlink" title="Template Metaprogramming"></a>Template Metaprogramming</h2><p>C++中的<strong>Template Metaprogramming</strong>(模板元编程)是一种通过模板技术在编译时进行计算和逻辑推导的技术。模板元编程利用C++模板机制,使得程序的某些部分在编译阶段就能被计算出来,而不是在运行时。这不仅能够提高程序的执行效率,还能为程序员提供更加灵活和强大的工具来设计类型安全、通用性强的代码。</p><h3 id="1-模板元编程的基本概念"><a href="#1-模板元编程的基本概念" class="headerlink" title="1. 模板元编程的基本概念"></a>1. <strong>模板元编程的基本概念</strong></h3><p>在C++中,模板是泛型编程的基础。模板可以用于生成函数、类或结构体的多个实例,具体取决于模板参数。模板元编程正是利用这种机制,通过在编译时计算、推导和选择不同的模板实例,来实现一些复杂的计算和算法。</p><p>模板元编程的核心思想是将计算过程从运行时转移到编译时,从而减少程序的运行时开销。例如,计算一个数的阶乘(factorial)就可以通过模板递归在编译时完成。</p><h3 id="2-模板元编程的基本用法"><a href="#2-模板元编程的基本用法" class="headerlink" title="2. 模板元编程的基本用法"></a>2. <strong>模板元编程的基本用法</strong></h3><h4 id="2-1-递归计算(如阶乘)"><a href="#2-1-递归计算(如阶乘)" class="headerlink" title="2.1 递归计算(如阶乘)"></a>2.1 <strong>递归计算(如阶乘)</strong></h4><p>模板元编程最常见的应用之一是递归计算。在模板元编程中,可以通过递归地嵌套模板实例来实现类似于递归函数的行为。</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><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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><br><span class="hljs-comment">// 基础模板,默认阶乘为1</span><br><span class="hljs-keyword">template</span> <<span class="hljs-type">int</span> N><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">factorial</span> {<br> <span class="hljs-type">static</span> <span class="hljs-type">const</span> <span class="hljs-type">int</span> value = N * factorial<N - <span class="hljs-number">1</span>>::value;<br>};<br><br><span class="hljs-comment">// 特化模板,当N=0时,阶乘为1</span><br><span class="hljs-keyword">template</span> <><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">factorial</span><<span class="hljs-number">0</span>> {<br> <span class="hljs-type">static</span> <span class="hljs-type">const</span> <span class="hljs-type">int</span> value = <span class="hljs-number">1</span>;<br>};<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> std::cout << <span class="hljs-string">"Factorial of 5 is "</span> << factorial<<span class="hljs-number">5</span>>::value << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>这里,<code>factorial<N></code>是一个递归模板,它会计算<code>N * factorial<N-1>::value</code>,直到<code>N</code>为0时,模板特化返回1。这段代码在编译时就会计算出阶乘的值,因此运行时并不需要进行计算。</p><h4 id="2-2-类型推导和SFINAE(Substitution-Failure-Is-Not-An-Error)"><a href="#2-2-类型推导和SFINAE(Substitution-Failure-Is-Not-An-Error)" class="headerlink" title="2.2 类型推导和SFINAE(Substitution Failure Is Not An Error)"></a>2.2 <strong>类型推导和SFINAE(Substitution Failure Is Not An Error)</strong></h4><p>模板元编程中,<code>SFINAE</code>是一个非常重要的概念。<code>SFINAE</code>允许我们根据模板参数的类型来选择不同的模板实例,从而在编译时做出决策。</p><p>以下是一个简单的例子,展示如何使用<code>SFINAE</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><type_traits></span></span><br><br><span class="hljs-comment">// 如果类型是整数类型,则启用这个模板</span><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">typename</span> std::enable_if<std::is_integral<T>::value, <span class="hljs-type">void</span>>::<span class="hljs-function">type</span><br><span class="hljs-function"><span class="hljs-title">print</span><span class="hljs-params">(T t)</span> </span>{<br> std::cout << <span class="hljs-string">"Integer: "</span> << t << std::endl;<br>}<br><br><span class="hljs-comment">// 如果类型是浮点数类型,则启用这个模板</span><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">typename</span> std::enable_if<std::is_floating_point<T>::value, <span class="hljs-type">void</span>>::<span class="hljs-function">type</span><br><span class="hljs-function"><span class="hljs-title">print</span><span class="hljs-params">(T t)</span> </span>{<br> std::cout << <span class="hljs-string">"Floating point: "</span> << t << std::endl;<br>}<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-built_in">print</span>(<span class="hljs-number">42</span>); <span class="hljs-comment">// 输出 Integer: 42</span><br> <span class="hljs-built_in">print</span>(<span class="hljs-number">3.14</span>); <span class="hljs-comment">// 输出 Floating point: 3.14</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>这里,<code>std::enable_if</code>与<code>std::is_integral</code>和<code>std::is_floating_point</code>配合使用,根据传递给模板的类型,选择正确的函数重载。<code>SFINAE</code>确保只有当类型符合要求时,才会激活相应的模板,避免了类型不匹配的错误。</p><h4 id="2-3-常量表达式(constexpr)与模板元编程"><a href="#2-3-常量表达式(constexpr)与模板元编程" class="headerlink" title="2.3 常量表达式(constexpr)与模板元编程"></a>2.3 <strong>常量表达式(constexpr)与模板元编程</strong></h4><p><code>constexpr</code>在C++11中引入,它允许在编译时计算常量值,并与模板元编程配合使用,进一步提高了程序的编译时计算能力。<code>constexpr</code>函数可以在编译时求值,从而让程序更高效。</p><p>例如,可以用<code>constexpr</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></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><br><span class="hljs-function"><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span> </span>{<br> <span class="hljs-keyword">return</span> n <= <span class="hljs-number">1</span> ? <span class="hljs-number">1</span> : n * <span class="hljs-built_in">factorial</span>(n - <span class="hljs-number">1</span>);<br>}<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> std::cout << <span class="hljs-string">"Factorial of 5 is "</span> << <span class="hljs-built_in">factorial</span>(<span class="hljs-number">5</span>) << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>这种方法比模板元编程的递归方式更加简洁,并且<code>constexpr</code>函数也能在编译时进行求值。</p><h3 id="3-高级模板元编程技巧"><a href="#3-高级模板元编程技巧" class="headerlink" title="3. 高级模板元编程技巧"></a>3. <strong>高级模板元编程技巧</strong></h3><h4 id="3-1-类型列表(Type-List)"><a href="#3-1-类型列表(Type-List)" class="headerlink" title="3.1 类型列表(Type List)"></a>3.1 <strong>类型列表(Type List)</strong></h4><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><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 cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><br><span class="hljs-comment">// 定义类型列表</span><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span>... Types><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">type_list</span> {};<br><br><span class="hljs-comment">// 类型列表中的第一个类型</span><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T, <span class="hljs-keyword">typename</span>... Rest><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">front</span> {<br> <span class="hljs-keyword">using</span> type = T;<br>};<br><br><span class="hljs-comment">// 递归地将类型列表中的第一个类型提取出来</span><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span>... Types><br><span class="hljs-keyword">using</span> <span class="hljs-type">front_t</span> = <span class="hljs-keyword">typename</span> front<Types...>::type;<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-keyword">using</span> my_types = type_list<<span class="hljs-type">int</span>, <span class="hljs-type">double</span>, <span class="hljs-type">char</span>>;<br><br> <span class="hljs-comment">// 提取类型列表中的第一个类型</span><br> std::cout << <span class="hljs-string">"First type in the list is: "</span> << <span class="hljs-built_in">typeid</span>(<span class="hljs-type">front_t</span><<span class="hljs-type">int</span>, <span class="hljs-type">double</span>, <span class="hljs-type">char</span>>).<span class="hljs-built_in">name</span>() << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="3-2-模板元编程中的-std-tuple-和-std-index-sequence-结合使用"><a href="#3-2-模板元编程中的-std-tuple-和-std-index-sequence-结合使用" class="headerlink" title="3.2 模板元编程中的 std::tuple 和 std::index_sequence 结合使用"></a>3.2 <strong>模板元编程中的</strong> <code>std::tuple</code> <strong>和</strong> <code>std::index_sequence</code> <strong>结合使用</strong></h4><p>C++11引入了<code>std::tuple</code>和<code>std::index_sequence</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><tuple></span></span><br><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span>... Args><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print_tuple</span><span class="hljs-params">(<span class="hljs-type">const</span> std::tuple<Args...>& t)</span> </span>{<br> <span class="hljs-built_in">print_tuple_impl</span>(t, std::index_sequence_for<Args...>{});<br>}<br><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span>... Args, std::<span class="hljs-type">size_t</span>... I><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print_tuple_impl</span><span class="hljs-params">(<span class="hljs-type">const</span> std::tuple<Args...>& t, std::index_sequence<I...>)</span> </span>{<br> <span class="hljs-comment">// 展开tuple并打印每个元素</span><br> ((std::cout << std::<span class="hljs-built_in">get</span><I>(t) << <span class="hljs-string">" "</span>), ...);<br> std::cout << std::endl;<br>}<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-function">std::tuple<<span class="hljs-type">int</span>, <span class="hljs-type">double</span>, <span class="hljs-type">char</span>> <span class="hljs-title">t</span><span class="hljs-params">(<span class="hljs-number">1</span>, <span class="hljs-number">2.5</span>, <span class="hljs-string">'a'</span>)</span></span>;<br> <span class="hljs-built_in">print_tuple</span>(t); <span class="hljs-comment">// 输出: 1 2.5 a</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在这个例子中,<code>std::index_sequence_for<Args...></code>帮助我们生成一组索引,<code>print_tuple_impl</code>通过展开<code>index_sequence</code>来遍历并打印元组中的每个元素。</p><h3 id="4-模板元编程的优势与挑战"><a href="#4-模板元编程的优势与挑战" class="headerlink" title="4. 模板元编程的优势与挑战"></a>4. <strong>模板元编程的优势与挑战</strong></h3><h4 id="4-1-优势"><a href="#4-1-优势" class="headerlink" title="4.1 优势"></a>4.1 <strong>优势</strong></h4><ol><li><strong>提高性能</strong>:通过在编译时计算,模板元编程可以减少运行时的计算开销,尤其是对于复杂的算法或计算任务。</li><li><strong>增强类型安全性</strong>:模板元编程可以在编译时捕获类型错误,从而提高程序的类型安全性。</li><li><strong>灵活性和可扩展性</strong>:模板元编程能够使代码更加通用和抽象,可以在不同的场景下复用和扩展。</li></ol><h4 id="4-2-挑战"><a href="#4-2-挑战" class="headerlink" title="4.2 挑战"></a>4.2 <strong>挑战</strong></h4><ol><li><strong>代码复杂度</strong>:模板元编程通常使得代码更加复杂,调试和理解起来较为困难,尤其是对于新手。</li><li><strong>编译时性能</strong>:尽管模板元编程能够提高运行时性能,但过多的模板实例化可能会导致编译时间增加。</li><li><strong>错误信息难以理解</strong>:模板错误通常非常难以解读,错误信息冗长且不直观,需要一定的经验来理解和修正。</li></ol><h3 id="5-总结"><a href="#5-总结" class="headerlink" title="5. 总结"></a>5. <strong>总结</strong></h3><p>C++中的模板元编程是一个强大的工具,允许程序员在编译时进行计算、决策和类型推导,从而优化程序的性能和灵活性。通过递归模板、<code>SFINAE</code>、<code>constexpr</code>、类型列表和<code>std::tuple</code>等工具,可以实现许多高级的编译时计算和类型操作。然而,模板元编程也带来了一些挑战,如代码复杂性、调试困难等,因此在使用时需要平衡其优势和挑战。</p><h2 id="constexpr"><a href="#constexpr" class="headerlink" title="constexpr"></a>constexpr</h2><p><code>constexpr</code> 是 C++11 引入的一个关键字,用于声明常量表达式。常量表达式是指在编译时能够求值的常量。使用 <code>constexpr</code>,可以告诉编译器某个函数或变量在编译时就能够确定其值,从而提高程序的效率。</p><h3 id="1-constexpr-的基本用途"><a href="#1-constexpr-的基本用途" class="headerlink" title="1. constexpr 的基本用途"></a>1. <strong><code>constexpr</code> 的基本用途</strong></h3><h4 id="1-1-声明常量变量"><a href="#1-1-声明常量变量" class="headerlink" title="1.1 声明常量变量"></a>1.1 <strong>声明常量变量</strong></h4><p>最简单的 <code>constexpr</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></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><br><span class="hljs-function"><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> <span class="hljs-title">square</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span> </span>{<br> <span class="hljs-keyword">return</span> x * x;<br>}<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-keyword">constexpr</span> <span class="hljs-type">int</span> value = <span class="hljs-number">5</span>;<br> <span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> result = <span class="hljs-built_in">square</span>(value); <span class="hljs-comment">// 在编译时计算</span><br> std::cout << <span class="hljs-string">"Square of "</span> << value << <span class="hljs-string">" is "</span> << result << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在这个例子中,<code>value</code> 和 <code>result</code> 都是常量表达式。<code>constexpr</code> 确保 <code>value</code> 和 <code>result</code> 的值在编译时就被确定下来,因此程序运行时不再需要重新计算它们的值。</p><h4 id="1-2-声明常量函数"><a href="#1-2-声明常量函数" class="headerlink" title="1.2 声明常量函数"></a>1.2 <strong>声明常量函数</strong></h4><p><code>constexpr</code> 也可以用于函数的声明。这告诉编译器该函数可以在编译时求值,从而将其计算结果嵌入到生成的代码中,减少运行时开销。</p><p>例如,可以用 <code>constexpr</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></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><br><span class="hljs-function"><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span> </span>{<br> <span class="hljs-keyword">return</span> (n <= <span class="hljs-number">1</span>) ? <span class="hljs-number">1</span> : n * <span class="hljs-built_in">factorial</span>(n - <span class="hljs-number">1</span>);<br>}<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-keyword">constexpr</span> <span class="hljs-type">int</span> result = <span class="hljs-built_in">factorial</span>(<span class="hljs-number">5</span>); <span class="hljs-comment">// 在编译时计算</span><br> std::cout << <span class="hljs-string">"Factorial of 5 is "</span> << result << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在这个例子中,<code>factorial(5)</code> 是一个常量表达式,编译器会在编译时计算并将结果嵌入到最终的程序中,而不是在运行时调用该函数。</p><h3 id="2-constexpr-的特点"><a href="#2-constexpr-的特点" class="headerlink" title="2. constexpr 的特点"></a>2. <strong><code>constexpr</code> 的特点</strong></h3><h4 id="2-1-编译时求值"><a href="#2-1-编译时求值" class="headerlink" title="2.1 编译时求值"></a>2.1 <strong>编译时求值</strong></h4><p><code>constexpr</code> 的最重要特点是它要求函数的返回值在编译时就可以求出。只有在表达式可以在编译时计算的情况下,<code>constexpr</code> 才能生效。</p><ul><li><code>constexpr</code> 函数中的所有参数和局部变量必须是常量表达式。</li><li>函数体中只能包含常量表达式,可以使用递归、条件表达式等编译时可求值的代码结构。</li></ul><h4 id="2-2-限制"><a href="#2-2-限制" class="headerlink" title="2.2 限制"></a>2.2 <strong>限制</strong></h4><p>尽管 <code>constexpr</code> 提供了很多优势,但它也有一些限制:</p><ul><li><strong>函数体的限制</strong>:<code>constexpr</code> 函数只能包含常量表达式(如常量、字面量、编译时常量),不能使用运行时才能确定的值。</li><li><strong>不能使用动态内存分配</strong>:<code>constexpr</code> 函数不能使用 <code>new</code>、<code>delete</code> 等动态内存分配操作,也不能调用运行时动态确定的函数。</li><li><strong>递归深度限制</strong>:如果使用递归时,递归深度过深可能会导致编译时栈溢出,特别是对于较大的递归函数,可能会受到编译器的限制。</li></ul><h4 id="2-3-编译时和运行时"><a href="#2-3-编译时和运行时" class="headerlink" title="2.3 编译时和运行时"></a>2.3 <strong>编译时和运行时</strong></h4><p>在 C++11 中,<code>constexpr</code> 函数的结果如果能够在编译时计算出来,编译器会将其计算结果嵌入到最终的程序中,这样程序运行时就不需要再执行这些计算。对于不能在编译时确定的值,<code>constexpr</code> 函数依然可以在运行时调用,并且其行为和普通函数一样。</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-function"><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> <span class="hljs-title">square</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span> </span>{<br> <span class="hljs-keyword">return</span> x * x;<br>}<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-keyword">constexpr</span> <span class="hljs-type">int</span> compile_time_result = <span class="hljs-built_in">square</span>(<span class="hljs-number">5</span>); <span class="hljs-comment">// 编译时计算</span><br> <span class="hljs-type">int</span> runtime_value = <span class="hljs-number">10</span>;<br> <span class="hljs-type">int</span> runtime_result = <span class="hljs-built_in">square</span>(runtime_value); <span class="hljs-comment">// 运行时计算</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在这个例子中,<code>compile_time_result</code> 会在编译时计算出 <code>25</code>,而 <code>runtime_result</code> 会在程序运行时根据 <code>runtime_value</code> 计算出 <code>100</code>。</p><h3 id="3-constexpr-与-const-的区别"><a href="#3-constexpr-与-const-的区别" class="headerlink" title="3. constexpr 与 const 的区别"></a>3. <strong><code>constexpr</code> 与 <code>const</code> 的区别</strong></h3><ul><li><code>const</code> 用来声明常量,它确保变量的值在程序执行期间不会改变,但它不要求在编译时就能确定值。</li><li><code>constexpr</code> 用来声明常量表达式,要求变量的值必须在编译时就能确定。</li></ul><p>例如:</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-type">const</span> <span class="hljs-type">int</span> a = <span class="hljs-number">5</span>; <span class="hljs-comment">// 运行时可以初始化</span><br><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> b = <span class="hljs-number">5</span>; <span class="hljs-comment">// 编译时必须初始化</span><br></code></pre></td></tr></table></figure><ul><li><code>const</code> 可以在运行时通过外部输入或其他计算值进行初始化。</li><li><code>constexpr</code> 则要求常量值必须在编译时已知,因此它的计算必须是确定性的。</li></ul><h3 id="4-constexpr-的应用场景"><a href="#4-constexpr-的应用场景" class="headerlink" title="4. constexpr 的应用场景"></a>4. <strong><code>constexpr</code> 的应用场景</strong></h3><h4 id="4-1-常量计算"><a href="#4-1-常量计算" class="headerlink" title="4.1 常量计算"></a>4.1 <strong>常量计算</strong></h4><p>最常见的应用场景是通过 <code>constexpr</code> 来计算一些在程序运行时需要使用的常量,尤其是那些计算成本较高的常量。例如,计算数学常数、数据结构的尺寸等。</p><h4 id="4-2-优化性能"><a href="#4-2-优化性能" class="headerlink" title="4.2 优化性能"></a>4.2 <strong>优化性能</strong></h4><p>通过将某些计算移到编译时,程序运行时就不需要进行这些计算,从而提升程序的性能。例如,某些复杂的算法可以通过 <code>constexpr</code> 进行编译时计算,避免了运行时的开销。</p><h4 id="4-3-模板元编程"><a href="#4-3-模板元编程" class="headerlink" title="4.3 模板元编程"></a>4.3 <strong>模板元编程</strong></h4><p><code>constexpr</code> 可以与模板元编程结合使用,用来在编译时进行类型推导、计算常量值或实现条件编译等。例如,<code>constexpr</code> 函数可以用于计算模板参数,驱动模板的选择和推导。</p><h3 id="5-总结-1"><a href="#5-总结-1" class="headerlink" title="5. 总结"></a>5. <strong>总结</strong></h3><ul><li><code>constexpr</code> 是 C++11 引入的关键字,用于声明在编译时就能确定值的常量和函数。</li><li><code>constexpr</code> 提供了在编译时进行计算的能力,优化了程序的执行效率,尤其适用于编写常量表达式和编译时计算的场景。</li><li>与 <code>const</code> 不同,<code>constexpr</code> 要求在编译时就能计算出常量值。</li><li><code>constexpr</code> 函数可以包含递归和条件表达式,但有一些限制,如不能使用动态内存分配等。</li></ul><p>通过使用 <code>constexpr</code>,可以显著提升程序的性能和可读性,尤其在常量表达式和模板元编程中,<code>constexpr</code> 是一个非常强大的工具。</p>]]></content>
<categories>
<category>C++</category>
<category>CS106L</category>
</categories>
<tags>
<tag>元编程</tag>
<tag>模板</tag>
</tags>
</entry>
<entry>
<title>多线程 C++</title>
<link href="/posts/28135.html"/>
<url>/posts/28135.html</url>
<content type="html"><![CDATA[<p><a href="https://www.bilibili.com/video/BV19x4y1E79V?spm_id_from=333.788.videopod.episodes&vd_source=c06338b0283c611d7a47c62b0ed23dfa&p=18">CS106L p18 Part 6.2 Multithreading</a></p><h2 id="多线程"><a href="#多线程" class="headerlink" title="多线程"></a>多线程</h2><p>C++中的<strong>multithreading</strong>(多线程)是指在程序中并发地执行多个线程的能力。线程是程序执行的基本单元,而多线程则允许程序在同一时间内处理多个任务,通常用来提高程序的效率,尤其是在多核处理器上,通过并行处理来减少任务的总执行时间。多线程广泛应用于高性能计算、图形渲染、网络通信、游戏开发等领域。</p><h3 id="1-线程的基本概念"><a href="#1-线程的基本概念" class="headerlink" title="1. 线程的基本概念"></a>1. <strong>线程的基本概念</strong></h3><p>线程是程序中的一个执行流,它有自己的执行栈、程序计数器和局部变量。多个线程共享同一进程的资源,例如内存、文件句柄等。相比于单一的执行流(即单线程),多线程能够让程序在同一时刻执行多个任务,从而更充分地利用多核处理器的计算能力。</p><h4 id="线程和进程的区别:"><a href="#线程和进程的区别:" class="headerlink" title="线程和进程的区别:"></a>线程和进程的区别:</h4><ul><li><strong>进程</strong>是资源分配的基本单位,包含了地址空间、数据、堆栈等。</li><li><strong>线程</strong>是程序执行的基本单位,属于进程的一部分,它共享进程的资源。</li></ul><p>在C++中,线程的创建、管理和同步是通过标准库提供的工具实现的,尤其是C++11引入的<code><thread></code>库和相关功能。</p><h3 id="2-C-中的多线程实现"><a href="#2-C-中的多线程实现" class="headerlink" title="2. C++中的多线程实现"></a>2. <strong>C++中的多线程实现</strong></h3><p>从C++11开始,C++标准库加入了对多线程的直接支持,提供了<code><thread></code>头文件,其中包括了创建和管理线程的类和函数。以下是一些常见的多线程编程元素:</p><h4 id="2-1-线程的创建与执行"><a href="#2-1-线程的创建与执行" class="headerlink" title="2.1 线程的创建与执行"></a>2.1 <strong>线程的创建与执行</strong></h4><p>要创建一个线程,首先需要定义一个函数或者可调用对象(如函数指针、lambda表达式等),然后将其传递给<code>std::thread</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><thread></span></span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">hello</span><span class="hljs-params">()</span> </span>{<br> std::cout << <span class="hljs-string">"Hello from thread!"</span> << std::endl;<br>}<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-comment">// 创建并启动线程</span><br> <span class="hljs-function">std::thread <span class="hljs-title">t</span><span class="hljs-params">(hello)</span></span>;<br> <br> <span class="hljs-comment">// 等待线程结束</span><br> t.<span class="hljs-built_in">join</span>();<br> <br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在上述代码中:</p><ul><li><code>std::thread t(hello);</code>:创建并启动一个新线程,执行<code>hello</code>函数。</li><li><code>t.join();</code>:主线程等待子线程<code>t</code>执行完成。</li></ul><h4 id="2-2-传递参数到线程"><a href="#2-2-传递参数到线程" class="headerlink" title="2.2 传递参数到线程"></a>2.2 <strong>传递参数到线程</strong></h4><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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><thread></span></span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print_number</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span> </span>{<br> std::cout << <span class="hljs-string">"The number is: "</span> << x << std::endl;<br>}<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-function">std::thread <span class="hljs-title">t</span><span class="hljs-params">(print_number, <span class="hljs-number">5</span>)</span></span>; <span class="hljs-comment">// 向线程传递参数5</span><br> t.<span class="hljs-built_in">join</span>();<br> <br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>这里,<code>print_number</code>函数接收一个参数<code>x</code>,并在线程中打印出来。</p><h4 id="2-3-线程的同步与互斥"><a href="#2-3-线程的同步与互斥" class="headerlink" title="2.3 线程的同步与互斥"></a>2.3 <strong>线程的同步与互斥</strong></h4><p>多线程编程的核心挑战之一是<strong>共享资源的同步问题</strong>。当多个线程访问同一资源时,可能会发生数据竞争(data race),导致不一致的结果。为了防止这种情况,可以使用<strong>互斥锁(mutex)</strong>来同步对共享资源的访问。</p><p>C++标准库提供了<code>std::mutex</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><thread></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><mutex></span></span><br><br>std::mutex mtx;<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print_hello</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-function">std::lock_guard<std::mutex> <span class="hljs-title">lock</span><span class="hljs-params">(mtx)</span></span>; <span class="hljs-comment">// 自动加锁与解锁</span><br> std::cout << <span class="hljs-string">"Hello from thread!"</span> << std::endl;<br>}<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-function">std::thread <span class="hljs-title">t1</span><span class="hljs-params">(print_hello)</span></span>;<br> <span class="hljs-function">std::thread <span class="hljs-title">t2</span><span class="hljs-params">(print_hello)</span></span>;<br> <br> t<span class="hljs-number">1.</span><span class="hljs-built_in">join</span>();<br> t<span class="hljs-number">2.</span><span class="hljs-built_in">join</span>();<br> <br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在上述代码中,<code>std::mutex mtx</code>是一个互斥锁,<code>std::lock_guard<std::mutex></code>用于自动加锁和解锁。<code>lock_guard</code>确保在作用域结束时,锁会自动被释放,从而避免死锁的发生。</p><h4 id="2-4-线程的并发和并行"><a href="#2-4-线程的并发和并行" class="headerlink" title="2.4 线程的并发和并行"></a>2.4 <strong>线程的并发和并行</strong></h4><ul><li><strong>并发(Concurrency)</strong>指的是多个任务在同一时间段内交替执行,可能是在单核或多核处理器上。在多核处理器上,任务可以在不同核心上并行执行,但并不一定同时执行。</li><li><strong>并行(Parallelism)</strong>是指多个任务在同一时刻在不同的处理器核心上执行,通常只有在拥有多核CPU的系统中,才能实现真正的并行。</li></ul><p>C++的多线程通常提供并发执行的能力,实际的并行执行取决于硬件和操作系统的调度。</p><h4 id="2-5-线程的同步机制:条件变量"><a href="#2-5-线程的同步机制:条件变量" class="headerlink" title="2.5 线程的同步机制:条件变量"></a>2.5 <strong>线程的同步机制:条件变量</strong></h4><p>在多线程程序中,有时需要让某些线程等待某些条件发生。这种情况下,可以使用<strong>条件变量(<code>std::condition_variable</code>)</strong>。</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><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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><thread></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><mutex></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><condition_variable></span></span><br><br>std::mutex mtx;<br>std::condition_variable cv;<br><span class="hljs-type">bool</span> ready = <span class="hljs-literal">false</span>;<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print_id</span><span class="hljs-params">(<span class="hljs-type">int</span> id)</span> </span>{<br> <span class="hljs-function">std::unique_lock<std::mutex> <span class="hljs-title">lck</span><span class="hljs-params">(mtx)</span></span>;<br> <span class="hljs-keyword">while</span> (!ready) cv.<span class="hljs-built_in">wait</span>(lck); <span class="hljs-comment">// 等待条件</span><br> std::cout << <span class="hljs-string">"Thread "</span> << id << <span class="hljs-string">'\n'</span>;<br>}<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">go</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-function">std::unique_lock<std::mutex> <span class="hljs-title">lck</span><span class="hljs-params">(mtx)</span></span>;<br> ready = <span class="hljs-literal">true</span>; <span class="hljs-comment">// 设置条件为真</span><br> cv.<span class="hljs-built_in">notify_all</span>(); <span class="hljs-comment">// 通知所有等待的线程</span><br>}<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> std::thread threads[<span class="hljs-number">10</span>];<br> <br> <span class="hljs-comment">// 创建多个线程</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; ++i)<br> threads[i] = std::<span class="hljs-built_in">thread</span>(print_id, i);<br> <br> std::cout << <span class="hljs-string">"Ready... set... go!"</span> << std::endl;<br> <span class="hljs-built_in">go</span>(); <span class="hljs-comment">// 改变条件,通知所有线程开始执行</span><br> <br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span>& t : threads) t.<span class="hljs-built_in">join</span>(); <span class="hljs-comment">// 等待所有线程结束</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在这个例子中,<code>print_id</code>函数的线程会等待<code>ready</code>变量变为<code>true</code>,只有在<code>go()</code>函数中通知后,所有线程才会开始执行。</p><h3 id="3-多线程中的常见问题"><a href="#3-多线程中的常见问题" class="headerlink" title="3. 多线程中的常见问题"></a>3. <strong>多线程中的常见问题</strong></h3><p>多线程编程虽然能够带来性能上的优势,但也伴随着许多挑战和潜在问题,包括但不限于:</p><ul><li><strong>数据竞争(Data Race)</strong>:多个线程并发地读写共享数据,导致数据不一致。</li><li><strong>死锁(Deadlock)</strong>:多个线程因互相等待对方释放资源,导致程序无法继续执行。</li><li><strong>活锁(Livelock)</strong>:类似死锁,线程不断尝试某些操作,但始终无法完成任务。</li><li><strong>优先级反转(Priority Inversion)</strong>:低优先级的线程占用了资源,导致高优先级线程无法执行。</li></ul><p>为了避免这些问题,必须合理设计线程同步和资源管理策略。</p><h3 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. <strong>总结</strong></h3><p>C++中的多线程编程能够帮助开发者更好地利用现代多核处理器,提高程序的并发性和执行效率。C++11引入了强大的线程支持,包括线程创建、同步机制(如互斥锁、条件变量)等。掌握多线程的基本操作和同步原理是开发高效、稳定应用程序的关键。然而,多线程编程也需要小心避免数据竞争、死锁等问题,因此需要更加细致地设计和调试。</p>]]></content>
<categories>
<category>C++</category>
<category>CS106L</category>
</categories>
<tags>
<tag>多线程</tag>
</tags>
</entry>
<entry>
<title>RAII 智能指针 C++</title>
<link href="/posts/60866.html"/>
<url>/posts/60866.html</url>
<content type="html"><![CDATA[<p><a href="https://www.bilibili.com/video/BV19x4y1E79V?spm_id_from=333.788.videopod.episodes&vd_source=c06338b0283c611d7a47c62b0ed23dfa&p=17">CS106L p17 Part 6.1 RAII and Smart Pointers</a></p><h2 id="RAII"><a href="#RAII" class="headerlink" title="RAII"></a>RAII</h2><p><strong>RAII(Resource Acquisition Is Initialization)</strong> 是 C++ 编程中一种非常重要且常用的编程技术,旨在通过对象生命周期管理资源(如内存、文件句柄、网络连接等)的获取和释放,确保资源能够在程序执行过程中得到有效管理并避免资源泄露。RAII 的核心思想是“资源的获取就是初始化,资源的释放就是对象销毁时进行的析构操作”。</p><h3 id="1-RAII-的基本原理"><a href="#1-RAII-的基本原理" class="headerlink" title="1. RAII 的基本原理"></a>1. RAII 的基本原理</h3><p>RAII 的基本思想是通过在对象的构造函数中获得资源,并在对象的析构函数中释放资源。换句话说,资源(如内存、文件句柄、数据库连接等)通过对象的生命周期来管理,确保资源在对象超出作用域时自动释放,从而避免了手动释放资源的复杂性。</p><h4 id="关键点:"><a href="#关键点:" class="headerlink" title="关键点:"></a>关键点:</h4><ul><li><strong>资源的获取</strong>:在对象的构造函数中获得资源,通常在构造函数中进行动态内存分配、文件打开、数据库连接等操作。</li><li><strong>资源的释放</strong>:在对象的析构函数中释放资源,通常在析构函数中执行释放内存、关闭文件、断开数据库连接等操作。</li></ul><h3 id="2-RAII-的工作原理"><a href="#2-RAII-的工作原理" class="headerlink" title="2. RAII 的工作原理"></a>2. RAII 的工作原理</h3><p>RAII 在 C++ 中依赖于自动变量的生命周期。程序中定义的局部变量在作用域结束时会被销毁,而其析构函数会被自动调用。如果这些变量管理了某些资源(如内存、文件、互斥锁等),那么在析构函数中释放这些资源,就能确保资源得到了及时的释放。</p><p>举个例子,假设我们有一个管理文件资源的类 <code>FileGuard</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><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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><fstream></span></span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">FileGuard</span> {<br><span class="hljs-keyword">private</span>:<br> std::ifstream file;<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-comment">// 构造函数中打开文件</span><br> <span class="hljs-built_in">FileGuard</span>(<span class="hljs-type">const</span> std::string& filename) {<br> file.<span class="hljs-built_in">open</span>(filename);<br> <span class="hljs-keyword">if</span> (!file) {<br> <span class="hljs-keyword">throw</span> std::<span class="hljs-built_in">runtime_error</span>(<span class="hljs-string">"File could not be opened."</span>);<br> }<br> std::cout << <span class="hljs-string">"File opened successfully!"</span> << std::endl;<br> }<br> <br> <span class="hljs-comment">// 析构函数中关闭文件</span><br> ~<span class="hljs-built_in">FileGuard</span>() {<br> <span class="hljs-keyword">if</span> (file.<span class="hljs-built_in">is_open</span>()) {<br> file.<span class="hljs-built_in">close</span>();<br> std::cout << <span class="hljs-string">"File closed."</span> << std::endl;<br> }<br> }<br>};<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-keyword">try</span> {<br> <span class="hljs-function">FileGuard <span class="hljs-title">fg</span><span class="hljs-params">(<span class="hljs-string">"example.txt"</span>)</span></span>; <span class="hljs-comment">// 自动打开文件</span><br> <span class="hljs-comment">// 在此处可以进行文件操作</span><br> } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> std::exception& e) {<br> std::cout << e.<span class="hljs-built_in">what</span>() << std::endl;<br> }<br> <span class="hljs-comment">// 当fg超出作用域时,它会被销毁,析构函数自动关闭文件</span><br>}<br></code></pre></td></tr></table></figure><p>在这个例子中,<code>FileGuard</code> 类负责打开和关闭文件。通过在构造函数中打开文件,并在析构函数中关闭文件,我们确保了文件始终在使用后被关闭,即使在发生异常时也能自动关闭文件。这就是 RAII 的一个典型应用。</p><h4 id="输出:"><a href="#输出:" class="headerlink" title="输出:"></a>输出:</h4><figure class="highlight fortran"><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 fortran"><span class="hljs-keyword">File</span> <span class="hljs-keyword">opened</span> successfully<span class="hljs-comment">!</span><br><span class="hljs-keyword">File</span> closed.<br></code></pre></td></tr></table></figure><h3 id="3-RAII-的优点"><a href="#3-RAII-的优点" class="headerlink" title="3. RAII 的优点"></a>3. RAII 的优点</h3><p>RAII 的核心优势之一就是它确保了资源的自动释放,避免了资源泄漏。它提供了以下几个明显的优点:</p><ul><li><strong>自动管理资源</strong>:RAII 自动管理资源的生命周期,不需要手动释放。程序员不必担心忘记释放资源,或因异常导致资源没有释放。</li><li><strong>异常安全</strong>:在 RAII 中,资源的释放发生在对象的析构函数中,这样即使发生异常,也能确保资源会被正确释放(避免了资源泄漏)。因此,RAII 提供了一种异常安全的机制,即使在复杂的控制流中(例如,在异常发生时)也能保持资源的正确管理。</li><li><strong>简化代码</strong>:RAII 使得资源的管理更加直观和简洁。程序员只需要关注对象的创建和销毁,而不需要显式地释放资源。</li><li><strong>增强可维护性</strong>:由于资源管理是与对象的生命周期紧密绑定的,程序的维护和扩展更加方便。对象的创建、使用和销毁过程通常在同一个地方进行管理,减少了跨多个函数或类管理资源的复杂性。</li></ul><h3 id="4-RAII-和资源泄漏"><a href="#4-RAII-和资源泄漏" class="headerlink" title="4. RAII 和资源泄漏"></a>4. RAII 和资源泄漏</h3><p>资源泄漏通常发生在程序没有正确释放资源的情况下。RAII 通过将资源的获取和释放与对象的生命周期紧密关联起来,从而有效避免了资源泄漏。例如,在上述 <code>FileGuard</code> 类中,无论是正常退出作用域还是发生异常,<code>fg</code> 对象都会被销毁,<code>~FileGuard</code> 析构函数会被自动调用,从而确保了文件被正确关闭。</p><p>假设不使用 RAII,我们可能需要手动在每个可能的退出点释放资源:</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">openFile</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string& filename)</span> </span>{<br> std::ifstream file;<br> file.<span class="hljs-built_in">open</span>(filename);<br> <br> <span class="hljs-keyword">if</span> (!file) {<br> std::cerr << <span class="hljs-string">"Failed to open file."</span> << std::endl;<br> <span class="hljs-keyword">return</span>;<br> }<br><br> <span class="hljs-comment">// 其他处理代码</span><br><br> file.<span class="hljs-built_in">close</span>(); <span class="hljs-comment">// 必须显式关闭文件</span><br>}<br></code></pre></td></tr></table></figure><p>如果在文件打开后发生异常(例如在“其他处理代码”中发生了异常),那么 <code>file.close()</code> 可能就永远不会被执行,导致资源泄漏。而使用 RAII 技术,可以避免这种问题。</p><h3 id="5-RAII-的应用场景"><a href="#5-RAII-的应用场景" class="headerlink" title="5. RAII 的应用场景"></a>5. RAII 的应用场景</h3><p>RAII 不仅适用于内存管理,还可以应用于其他资源的管理。以下是一些常见的应用场景:</p><ul><li><p><strong>动态内存管理</strong>:使用 <code>std::unique_ptr</code> 和 <code>std::shared_ptr</code> 来管理动态分配的内存。<code>std::unique_ptr</code> 在对象生命周期结束时自动释放内存,避免了手动释放内存的麻烦。</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">std::unique_ptr<<span class="hljs-type">int</span>> ptr = std::<span class="hljs-built_in">make_unique</span><<span class="hljs-type">int</span>>(<span class="hljs-number">10</span>); <span class="hljs-comment">// 自动分配内存</span><br><span class="hljs-comment">// ptr 超出作用域时,内存会自动释放</span><br></code></pre></td></tr></table></figure></li><li><p><strong>文件管理</strong>:如前所述,通过定义一个类来在对象构造时打开文件,在析构时自动关闭文件,避免了文件泄漏问题。</p></li><li><p><strong>锁管理</strong>:使用 <code>std::lock_guard</code> 或 <code>std::unique_lock</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">std::mutex mtx;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">threadSafeFunction</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-function">std::lock_guard<std::mutex> <span class="hljs-title">lock</span><span class="hljs-params">(mtx)</span></span>; <span class="hljs-comment">// 锁定互斥量</span><br> <span class="hljs-comment">// 执行线程安全操作</span><br>} <span class="hljs-comment">// 当lock超出作用域时,mutex会自动解锁</span><br></code></pre></td></tr></table></figure></li><li><p><strong>数据库连接</strong>:使用 RAII 管理数据库连接。当对象创建时建立数据库连接,析构时断开连接,确保连接始终处于正确的状态。</p></li></ul><h3 id="6-RAII-的限制"><a href="#6-RAII-的限制" class="headerlink" title="6. RAII 的限制"></a>6. RAII 的限制</h3><p>尽管 RAII 是一种非常有用的技术,但它也有一些限制和挑战:</p><ul><li><strong>资源的类型限制</strong>:RAII 主要适用于那些在对象销毁时需要释放的资源。如果资源的释放不依赖于对象的生命周期(例如某些全局资源或跨多个对象共享的资源),RAII 可能就不适用。</li><li><strong>跨线程资源管理</strong>:在多线程环境中,RAII 可能无法有效地管理共享资源的访问,特别是在多个线程之间共享资源时,需要额外的同步机制来保证线程安全。</li></ul><h3 id="7-总结"><a href="#7-总结" class="headerlink" title="7. 总结"></a>7. 总结</h3><p>RAII(Resource Acquisition Is Initialization)是 C++ 中一种强大且优雅的技术,它通过将资源的管理与对象的生命周期绑定,自动管理资源的获取和释放,避免了资源泄漏和内存管理的复杂性。RAII 不仅可以用于内存管理,还可以广泛应用于文件、网络连接、数据库连接、互斥锁等资源的管理,是 C++ 编程中非常重要的设计思想之一。</p><h2 id="Smart-Pointer"><a href="#Smart-Pointer" class="headerlink" title="Smart Pointer"></a>Smart Pointer</h2><p>C++中的<strong>智能指针</strong>(smart pointer)是一种封装原始指针(raw pointer)的对象,它通过自动管理内存的分配和释放,帮助程序员避免内存泄漏和悬空指针等问题。智能指针的核心思想是将动态内存的管理从程序员手中转移到一个管理对象(智能指针)中,减少手动管理内存的复杂性和风险。</p><p>C++标准库提供了几种类型的智能指针,主要包括:</p><ul><li><code>std::unique_ptr</code></li><li><code>std::shared_ptr</code></li><li><code>std::weak_ptr</code></li></ul><p>这些智能指针是C++11引入的,旨在提高代码的安全性和可维护性,特别是在复杂的内存管理场景中。</p><h3 id="1-std-unique-ptr"><a href="#1-std-unique-ptr" class="headerlink" title="1. std::unique_ptr"></a>1. <code>std::unique_ptr</code></h3><p><code>std::unique_ptr</code> 是最基本的智能指针,它表示对动态分配内存的独占所有权。一个 <code>std::unique_ptr</code> 在任何时候只能指向一个对象,并且该指针是唯一的(即只能有一个 <code>std::unique_ptr</code> 拥有某个资源)。当 <code>std::unique_ptr</code> 被销毁时,它所管理的对象会自动被销毁,内存会被释放。</p><h4 id="特性:"><a href="#特性:" class="headerlink" title="特性:"></a>特性:</h4><ul><li><strong>独占所有权</strong>:每个资源只能由一个 <code>std::unique_ptr</code> 拥有,不能复制(没有复制构造函数和复制赋值运算符)。</li><li><strong>自动释放内存</strong>:当 <code>std::unique_ptr</code> 超出作用域时,它会自动调用析构函数释放资源,避免了手动 <code>delete</code> 的需要。</li></ul><h4 id="使用示例:"><a href="#使用示例:" class="headerlink" title="使用示例:"></a>使用示例:</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><span class="line">19</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><memory></span> <span class="hljs-comment">// 引入 std::unique_ptr</span></span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyClass</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">MyClass</span>() {<br> std::cout << <span class="hljs-string">"MyClass constructor\n"</span>;<br> }<br> ~<span class="hljs-built_in">MyClass</span>() {<br> std::cout << <span class="hljs-string">"MyClass destructor\n"</span>;<br> }<br>};<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-comment">// 创建一个 unique_ptr,管理 MyClass 对象</span><br> std::unique_ptr<MyClass> ptr1 = std::<span class="hljs-built_in">make_unique</span><MyClass>();<br><br> <span class="hljs-comment">// ptr1 离开作用域时,MyClass 的对象会被销毁,自动释放内存</span><br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight delphi"><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 delphi">MyClass <span class="hljs-function"><span class="hljs-keyword">constructor</span></span><br><span class="hljs-function"><span class="hljs-title">MyClass</span> <span class="hljs-title">destructor</span></span><br></code></pre></td></tr></table></figure><h4 id="转移所有权:"><a href="#转移所有权:" class="headerlink" title="转移所有权:"></a>转移所有权:</h4><p><code>std::unique_ptr</code> 的所有权不能被复制,但可以通过 <code>std::move</code> 转移所有权。转移所有权后,原来的 <code>std::unique_ptr</code> 变为空指针(<code>nullptr</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">std::unique_ptr<MyClass> ptr2 = std::<span class="hljs-built_in">move</span>(ptr1); <span class="hljs-comment">// 转移所有权</span><br></code></pre></td></tr></table></figure><h3 id="2-std-shared-ptr"><a href="#2-std-shared-ptr" class="headerlink" title="2. std::shared_ptr"></a>2. <code>std::shared_ptr</code></h3><p><code>std::shared_ptr</code> 是一种<strong>共享所有权</strong>的智能指针,允许多个指针共享同一块内存资源。每当创建一个新的 <code>std::shared_ptr</code> 来指向同一个对象时,它们的引用计数会增加。当一个 <code>std::shared_ptr</code> 被销毁时,引用计数会减少,直到没有任何 <code>shared_ptr</code> 指向该对象时,资源会被释放。</p><h4 id="特性:-1"><a href="#特性:-1" class="headerlink" title="特性:"></a>特性:</h4><ul><li><strong>共享所有权</strong>:多个 <code>std::shared_ptr</code> 可以共同拥有同一个资源。每个指针都可以访问资源,并通过引用计数机制来判断何时释放资源。</li><li><strong>引用计数</strong>:每个 <code>std::shared_ptr</code> 都会持有一个引用计数,记录有多少个智能指针共享这个资源。当引用计数归零时,资源会自动释放。</li></ul><h4 id="使用示例:-1"><a href="#使用示例:-1" class="headerlink" title="使用示例:"></a>使用示例:</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><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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><memory></span> <span class="hljs-comment">// 引入 std::shared_ptr</span></span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyClass</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">MyClass</span>() {<br> std::cout << <span class="hljs-string">"MyClass constructor\n"</span>;<br> }<br> ~<span class="hljs-built_in">MyClass</span>() {<br> std::cout << <span class="hljs-string">"MyClass destructor\n"</span>;<br> }<br>};<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-comment">// 创建一个 shared_ptr,管理 MyClass 对象</span><br> std::shared_ptr<MyClass> ptr1 = std::<span class="hljs-built_in">make_shared</span><MyClass>();<br><br> std::cout << <span class="hljs-string">"ptr1 use_count: "</span> << ptr<span class="hljs-number">1.</span><span class="hljs-built_in">use_count</span>() << <span class="hljs-string">"\n"</span>;<br><br> <span class="hljs-comment">// 创建另一个 shared_ptr,指向相同的对象</span><br> std::shared_ptr<MyClass> ptr2 = ptr1;<br><br> std::cout << <span class="hljs-string">"ptr1 use_count: "</span> << ptr<span class="hljs-number">1.</span><span class="hljs-built_in">use_count</span>() << <span class="hljs-string">"\n"</span>;<br> std::cout << <span class="hljs-string">"ptr2 use_count: "</span> << ptr<span class="hljs-number">2.</span><span class="hljs-built_in">use_count</span>() << <span class="hljs-string">"\n"</span>;<br><br> <span class="hljs-comment">// 退出作用域时,ptr1 和 ptr2 都会被销毁,引用计数为 0,资源自动释放</span><br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight delphi"><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 delphi">MyClass <span class="hljs-function"><span class="hljs-keyword">constructor</span></span><br><span class="hljs-function"><span class="hljs-title">ptr1</span> <span class="hljs-title">use_count</span>:</span> <span class="hljs-number">1</span><br>ptr1 use_count: <span class="hljs-number">2</span><br>ptr2 use_count: <span class="hljs-number">2</span><br>MyClass <span class="hljs-function"><span class="hljs-keyword">destructor</span></span><br></code></pre></td></tr></table></figure><h4 id="引用计数:"><a href="#引用计数:" class="headerlink" title="引用计数:"></a>引用计数:</h4><ul><li><code>use_count()</code> 方法返回当前 <code>std::shared_ptr</code> 所管理资源的引用计数。</li></ul><h4 id="循环引用:"><a href="#循环引用:" class="headerlink" title="循环引用:"></a>循环引用:</h4><p><code>std::shared_ptr</code> 的引用计数机制可能导致<strong>循环引用</strong>的问题。例如,两个对象通过 <code>shared_ptr</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></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">class</span> <span class="hljs-title class_">B</span> {<br><span class="hljs-keyword">public</span>:<br> std::shared_ptr<A> a;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">A</span> {<br><span class="hljs-keyword">public</span>:<br> std::shared_ptr<B> b;<br>};<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> std::shared_ptr<A> a = std::<span class="hljs-built_in">make_shared</span><A>();<br> std::shared_ptr<B> b = std::<span class="hljs-built_in">make_shared</span><B>();<br><br> a->b = b;<br> b->a = a; <span class="hljs-comment">// 循环引用,内存不会被释放</span><br>}<br></code></pre></td></tr></table></figure><p>为避免循环引用的问题,可以使用 <code>std::weak_ptr</code>。</p><h3 id="3-std-weak-ptr"><a href="#3-std-weak-ptr" class="headerlink" title="3. std::weak_ptr"></a>3. <code>std::weak_ptr</code></h3><p><code>std::weak_ptr</code> 是一种不拥有对象的智能指针,它主要用来解决 <code>std::shared_ptr</code> 中的循环引用问题。<code>std::weak_ptr</code> 不会增加引用计数,因此它不会影响对象的生命周期。<code>std::weak_ptr</code> 主要用于观察某个对象是否已经被销毁,或者作为缓存机制的补充。</p><h4 id="特性:-2"><a href="#特性:-2" class="headerlink" title="特性:"></a>特性:</h4><ul><li><strong>不增加引用计数</strong>:<code>std::weak_ptr</code> 不会导致引用计数增加,因此它不会阻止对象的销毁。</li><li>**可以转换为 <code>shared_ptr</code>**:通过调用 <code>std::weak_ptr::lock()</code>,可以将 <code>std::weak_ptr</code> 转换为一个 <code>std::shared_ptr</code>。如果对象已经被销毁,<code>lock()</code> 会返回一个空指针。</li></ul><h4 id="使用示例:-2"><a href="#使用示例:-2" class="headerlink" title="使用示例:"></a>使用示例:</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><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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><memory></span> <span class="hljs-comment">// 引入 std::weak_ptr</span></span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyClass</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">MyClass</span>() {<br> std::cout << <span class="hljs-string">"MyClass constructor\n"</span>;<br> }<br> ~<span class="hljs-built_in">MyClass</span>() {<br> std::cout << <span class="hljs-string">"MyClass destructor\n"</span>;<br> }<br>};<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> std::shared_ptr<MyClass> ptr1 = std::<span class="hljs-built_in">make_shared</span><MyClass>();<br> std::weak_ptr<MyClass> weakPtr = ptr1; <span class="hljs-comment">// weak_ptr 不增加引用计数</span><br><br> std::cout << <span class="hljs-string">"ptr1 use_count: "</span> << ptr<span class="hljs-number">1.</span><span class="hljs-built_in">use_count</span>() << <span class="hljs-string">"\n"</span>;<br><br> std::shared_ptr<MyClass> ptr2 = weakPtr.<span class="hljs-built_in">lock</span>(); <span class="hljs-comment">// 将 weak_ptr 转换为 shared_ptr</span><br> <span class="hljs-keyword">if</span> (ptr2) {<br> std::cout << <span class="hljs-string">"Successfully locked weak_ptr\n"</span>;<br> } <span class="hljs-keyword">else</span> {<br> std::cout << <span class="hljs-string">"Failed to lock weak_ptr\n"</span>;<br> }<br><br> ptr<span class="hljs-number">1.</span><span class="hljs-built_in">reset</span>(); <span class="hljs-comment">// 销毁 ptr1</span><br><br> std::shared_ptr<MyClass> ptr3 = weakPtr.<span class="hljs-built_in">lock</span>(); <span class="hljs-comment">// weak_ptr 转换失败</span><br> <span class="hljs-keyword">if</span> (ptr3) {<br> std::cout << <span class="hljs-string">"Successfully locked weak_ptr\n"</span>;<br> } <span class="hljs-keyword">else</span> {<br> std::cout << <span class="hljs-string">"Failed to lock weak_ptr\n"</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight oxygene"><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 oxygene">MyClass <span class="hljs-keyword">constructor</span><br><span class="hljs-title function_">ptr1</span> <span class="hljs-title function_">use_count</span>: <span class="hljs-number">1</span><br>Successfully <span class="hljs-keyword">locked</span> weak_ptr<br>MyClass <span class="hljs-keyword">destructor</span><br><span class="hljs-title function_">Failed</span> <span class="hljs-title function_">to</span> <span class="hljs-title function_">lock</span> <span class="hljs-title function_">weak_ptr</span><br></code></pre></td></tr></table></figure><h3 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h3><p>C++中的智能指针(<code>std::unique_ptr</code>、<code>std::shared_ptr</code> 和 <code>std::weak_ptr</code>)是现代C++中非常重要的工具,它们帮助程序员有效地管理动态内存和其他资源,减少内存泄漏、悬空指针和重复释放资源等问题。每种智能指针有其独特的使用场景:</p><ul><li>**<code>std::unique_ptr</code>**:用于独占资源,适用于动态内存管理,资源的所有权不能被共享。</li><li>**<code>std::shared_ptr</code>**:用于共享资源,适用于多个指针共享同一块内存,引用计数机制确保资源在没有指针指向时被释放。</li><li>**<code>std::weak_ptr</code>**:用于避免循环引用,允许观察资源而不干扰其生命周期。</li></ul><p>通过智能指针的使用,可以大大简化内存管理并提升程序的安全性和可维护性。</p><h2 id="文件流"><a href="#文件流" class="headerlink" title="文件流"></a>文件流</h2><p>在C++中,文件流(<code>fstream</code>)是用于处理文件输入输出的类库。它允许程序读取和写入文件,广泛应用于文件操作。文件流提供了三个主要的类,分别对应不同的操作方式:</p><ol><li><code>ifstream</code>(Input File Stream):用于从文件中读取数据。</li><li><code>ofstream</code>(Output File Stream):用于将数据写入文件。</li><li><code>fstream</code>(File Stream):即可以读取也可以写入文件。</li></ol><h3 id="1-ifstream(输入文件流)"><a href="#1-ifstream(输入文件流)" class="headerlink" title="1. ifstream(输入文件流)"></a>1. <strong><code>ifstream</code>(输入文件流)</strong></h3><p><code>ifstream</code> 是用来从文件读取数据的流对象。它继承自 <code>istream</code> 类,并可以通过成员函数如 <code>open()</code> 和 <code>close()</code> 来操作文件。</p><h4 id="示例:使用-ifstream-读取文件内容"><a href="#示例:使用-ifstream-读取文件内容" class="headerlink" title="示例:使用 ifstream 读取文件内容"></a>示例:使用 <code>ifstream</code> 读取文件内容</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><span class="line">19</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><fstream></span> <span class="hljs-comment">// 引入文件流头文件</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><string></span></span><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-function">std::ifstream <span class="hljs-title">file</span><span class="hljs-params">(<span class="hljs-string">"example.txt"</span>)</span></span>; <span class="hljs-comment">// 打开文件</span><br> <span class="hljs-keyword">if</span> (!file) { <span class="hljs-comment">// 检查文件是否成功打开</span><br> std::cerr << <span class="hljs-string">"无法打开文件!"</span> << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br> }<br> <br> std::string line;<br> <span class="hljs-keyword">while</span> (std::<span class="hljs-built_in">getline</span>(file, line)) { <span class="hljs-comment">// 逐行读取文件</span><br> std::cout << line << std::endl; <span class="hljs-comment">// 输出文件内容</span><br> }<br> <br> file.<span class="hljs-built_in">close</span>(); <span class="hljs-comment">// 关闭文件流</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="ifstream-常用成员函数:"><a href="#ifstream-常用成员函数:" class="headerlink" title="ifstream 常用成员函数:"></a><code>ifstream</code> 常用成员函数:</h4><ul><li><code>open()</code>: 打开文件进行读取。</li><li><code>close()</code>: 关闭文件。</li><li><code>getline()</code>: 逐行读取文件内容。</li><li><code>eof()</code>: 检查是否到达文件末尾。</li><li><code>fail()</code>: 检查文件操作是否成功。</li></ul><h3 id="2-ofstream(输出文件流)"><a href="#2-ofstream(输出文件流)" class="headerlink" title="2. ofstream(输出文件流)"></a>2. <strong><code>ofstream</code>(输出文件流)</strong></h3><p><code>ofstream</code> 用于向文件写入数据。它继承自 <code>ostream</code> 类,可以通过 <code>open()</code> 函数打开文件,使用 <code><<</code> 运算符将数据写入文件。</p><h4 id="示例:使用-ofstream-写入文件内容"><a href="#示例:使用-ofstream-写入文件内容" class="headerlink" title="示例:使用 ofstream 写入文件内容"></a>示例:使用 <code>ofstream</code> 写入文件内容</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><fstream></span> <span class="hljs-comment">// 引入文件流头文件</span></span><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-function">std::ofstream <span class="hljs-title">file</span><span class="hljs-params">(<span class="hljs-string">"output.txt"</span>)</span></span>; <span class="hljs-comment">// 打开文件进行写入</span><br> <span class="hljs-keyword">if</span> (!file) { <span class="hljs-comment">// 检查文件是否成功打开</span><br> std::cerr << <span class="hljs-string">"无法打开文件!"</span> << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br> }<br> <br> file << <span class="hljs-string">"Hello, World!"</span> << std::endl; <span class="hljs-comment">// 向文件写入数据</span><br> file << <span class="hljs-string">"C++ 文件流示例。"</span> << std::endl;<br> <br> file.<span class="hljs-built_in">close</span>(); <span class="hljs-comment">// 关闭文件流</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="ofstream-常用成员函数:"><a href="#ofstream-常用成员函数:" class="headerlink" title="ofstream 常用成员函数:"></a><code>ofstream</code> 常用成员函数:</h4><ul><li><code>open()</code>: 打开文件进行写入。</li><li><code>close()</code>: 关闭文件。</li><li><code>flush()</code>: 将缓冲区内容强制写入文件。</li><li><code>eof()</code>: 检查是否到达文件末尾。</li></ul><h3 id="3-fstream(文件流)"><a href="#3-fstream(文件流)" class="headerlink" title="3. fstream(文件流)"></a>3. <strong><code>fstream</code>(文件流)</strong></h3><p><code>fstream</code> 类结合了 <code>ifstream</code> 和 <code>ofstream</code> 的功能,它既可以用于读取文件,也可以用于写入文件。使用 <code>fstream</code> 可以在一个程序中同时执行文件的输入输出操作。</p><h4 id="示例:使用-fstream-进行读写操作"><a href="#示例:使用-fstream-进行读写操作" class="headerlink" title="示例:使用 fstream 进行读写操作"></a>示例:使用 <code>fstream</code> 进行读写操作</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><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 cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><fstream></span> <span class="hljs-comment">// 引入文件流头文件</span></span><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-function">std::fstream <span class="hljs-title">file</span><span class="hljs-params">(<span class="hljs-string">"example.txt"</span>, std::ios::in | std::ios::out)</span></span>; <span class="hljs-comment">// 打开文件进行读写</span><br> <span class="hljs-keyword">if</span> (!file) { <span class="hljs-comment">// 检查文件是否成功打开</span><br> std::cerr << <span class="hljs-string">"无法打开文件!"</span> << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br> }<br> <br> <span class="hljs-comment">// 读取文件</span><br> std::string line;<br> <span class="hljs-keyword">while</span> (std::<span class="hljs-built_in">getline</span>(file, line)) {<br> std::cout << line << std::endl; <span class="hljs-comment">// 输出文件内容</span><br> }<br> <br> <span class="hljs-comment">// 移动文件指针到文件开头</span><br> file.<span class="hljs-built_in">clear</span>(); <span class="hljs-comment">// 清除 EOF 标志</span><br> file.<span class="hljs-built_in">seekg</span>(<span class="hljs-number">0</span>, std::ios::beg); <span class="hljs-comment">// 文件读取指针移到文件开头</span><br> <br> <span class="hljs-comment">// 向文件写入数据</span><br> file << <span class="hljs-string">"\n新的数据行\n"</span>;<br> <br> file.<span class="hljs-built_in">close</span>(); <span class="hljs-comment">// 关闭文件流</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="fstream-常用成员函数:"><a href="#fstream-常用成员函数:" class="headerlink" title="fstream 常用成员函数:"></a><code>fstream</code> 常用成员函数:</h4><ul><li><code>open()</code>: 打开文件进行读写操作。</li><li><code>close()</code>: 关闭文件流。</li><li><code>seekg()</code>: 设置读取文件的指针位置。</li><li><code>seekp()</code>: 设置写入文件的指针位置。</li><li><code>eof()</code>: 检查是否到达文件末尾。</li><li><code>clear()</code>: 清除文件流状态标志。</li></ul><h3 id="文件操作模式"><a href="#文件操作模式" class="headerlink" title="文件操作模式"></a>文件操作模式</h3><p>在打开文件时,除了可以指定文件名外,还可以传递一个文件打开模式。常用的打开模式包括:</p><ul><li><strong><code>std::ios::in</code></strong>: 打开文件用于输入(读取)。</li><li><strong><code>std::ios::out</code></strong>: 打开文件用于输出(写入)。</li><li><strong><code>std::ios::app</code></strong>: 打开文件用于追加写入(文件末尾)。</li><li><strong><code>std::ios::ate</code></strong>: 打开文件并将文件指针指向文件末尾。</li><li><strong><code>std::ios::trunc</code></strong>: 打开文件并清空文件内容(默认行为,当以 <code>std::ios::out</code> 模式打开时)。</li><li><strong><code>std::ios::binary</code></strong>: 以二进制模式打开文件。</li></ul><h4 id="示例:使用不同的文件模式"><a href="#示例:使用不同的文件模式" class="headerlink" title="示例:使用不同的文件模式"></a>示例:使用不同的文件模式</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><fstream></span></span><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-function">std::ofstream <span class="hljs-title">file</span><span class="hljs-params">(<span class="hljs-string">"example.txt"</span>, std::ios::app)</span></span>; <span class="hljs-comment">// 以追加模式打开文件</span><br> <span class="hljs-keyword">if</span> (!file) {<br> std::cerr << <span class="hljs-string">"无法打开文件!"</span> << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br> }<br> <br> file << <span class="hljs-string">"追加的内容"</span> << std::endl;<br> file.<span class="hljs-built_in">close</span>();<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="错误处理和流状态"><a href="#错误处理和流状态" class="headerlink" title="错误处理和流状态"></a>错误处理和流状态</h3><p>在文件操作中,错误处理是一个重要环节。流类提供了几个成员函数来检测流的状态:</p><ul><li><strong><code>fail()</code></strong>: 流的操作失败时返回 <code>true</code>。</li><li><strong><code>eof()</code></strong>: 是否到达文件末尾。</li><li><strong><code>good()</code></strong>: 流的状态是否正常。</li><li><strong><code>bad()</code></strong>: 流的状态是否出现致命错误。</li></ul><h4 id="错误处理示例:"><a href="#错误处理示例:" class="headerlink" title="错误处理示例:"></a>错误处理示例:</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><fstream></span></span><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-function">std::ifstream <span class="hljs-title">file</span><span class="hljs-params">(<span class="hljs-string">"nonexistent.txt"</span>)</span></span>;<br> <span class="hljs-keyword">if</span> (!file) { <span class="hljs-comment">// 检查是否成功打开文件</span><br> std::cerr << <span class="hljs-string">"文件打开失败!"</span> << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br> }<br> <span class="hljs-comment">// 读取文件操作</span><br> file.<span class="hljs-built_in">close</span>();<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>C++中的文件流类库提供了一系列强大和灵活的接口来操作文件,无论是读取、写入还是同时进行文件的输入输出操作。通过使用 <code>ifstream</code>、<code>ofstream</code> 和 <code>fstream</code>,程序员可以轻松实现各种文件操作,并且可以在不同模式下打开文件。流状态的管理也是进行文件操作时需要注意的重点。</p><h2 id="重定向(指定输入来源或输出去向)"><a href="#重定向(指定输入来源或输出去向)" class="headerlink" title="重定向(指定输入来源或输出去向)"></a>重定向(指定输入来源或输出去向)</h2><p><code>freopen("input.txt", "r", stdin);</code> 这一行是 C 或 C++ 中的一个标准库函数 <code>freopen</code> 的调用。它的作用是重新指定标准输入流(<code>stdin</code>)的来源,使其从文件 <code>input.txt</code> 中读取数据,而不是从默认的标准输入设备(通常是键盘)读取。</p><h3 id="解释:"><a href="#解释:" class="headerlink" title="解释:"></a>解释:</h3><p><code>freopen</code> 函数用于重新打开一个文件并将其与一个标准流(如 <code>stdin</code>、<code>stdout</code> 或 <code>stderr</code>)关联。其基本语法是:</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">FILE* <span class="hljs-title function_">freopen</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* filename, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* mode, FILE* stream)</span>;<br></code></pre></td></tr></table></figure><ul><li><code>filename</code>: 要打开的文件名(可以是相对路径或绝对路径)。</li><li><code>mode</code>: 文件的打开模式(类似于 <code>fopen</code> 的模式,比如 <code>"r"</code>、<code>"w"</code> 等)。</li><li><code>stream</code>: 要重定向的标准流,通常是 <code>stdin</code>(标准输入)、<code>stdout</code>(标准输出)或 <code>stderr</code>(标准错误输出)。</li></ul><h3 id="在你的例子中:"><a href="#在你的例子中:" class="headerlink" title="在你的例子中:"></a>在你的例子中:</h3><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">freopen(<span class="hljs-string">"input.txt"</span>, <span class="hljs-string">"r"</span>, <span class="hljs-built_in">stdin</span>);<br></code></pre></td></tr></table></figure><ul><li><code>input.txt</code>:这是你想要打开的文件名,它作为标准输入流的数据来源。</li><li><code>"r"</code>:表示以“只读”模式打开文件。</li><li><code>stdin</code>:是标准输入流,表示程序从哪里读取输入数据。默认情况下,它指向键盘输入。通过 <code>freopen</code>,你将标准输入流重定向到 <code>input.txt</code> 文件。</li></ul><h3 id="作用:"><a href="#作用:" class="headerlink" title="作用:"></a>作用:</h3><p>这行代码的作用是将标准输入重定向为从 <code>input.txt</code> 文件中读取数据。之后,所有从标准输入读取的数据(例如通过 <code>scanf</code> 或 <code>cin</code>)都会从 <code>input.txt</code> 文件中获取,而不是从键盘输入。</p><h3 id="示例代码:"><a href="#示例代码:" class="headerlink" title="示例代码:"></a>示例代码:</h3><p>假设有一个 <code>input.txt</code> 文件,内容如下:</p><figure class="highlight"><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">5<br>10<br>15<br></code></pre></td></tr></table></figure><p>如果你有如下的 C++ 代码:</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><cstdio></span></span><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-comment">// 将标准输入重定向到 input.txt 文件</span><br> <span class="hljs-built_in">freopen</span>(<span class="hljs-string">"input.txt"</span>, <span class="hljs-string">"r"</span>, stdin);<br><br> <span class="hljs-type">int</span> a, b, c;<br> std::cin >> a >> b >> c; <span class="hljs-comment">// 从 input.txt 文件中读取三个整数</span><br><br> std::cout << <span class="hljs-string">"a: "</span> << a << <span class="hljs-string">", b: "</span> << b << <span class="hljs-string">", c: "</span> << c << std::endl;<br> <br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><ul><li><p>这时程序执行时,<code>std::cin</code> 会从 <code>input.txt</code> 文件中读取数据。</p></li><li><p><code>input.txt</code> 文件的内容会按顺序被读取并存储到 <code>a</code>、<code>b</code> 和 <code>c</code> 变量中。</p></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">a</span>: <span class="hljs-number">5</span>, b: <span class="hljs-number">10</span>, c: <span class="hljs-number">15</span><br></code></pre></td></tr></table></figure></li></ul><h3 id="使用场景:"><a href="#使用场景:" class="headerlink" title="使用场景:"></a>使用场景:</h3><ul><li><strong>文件输入重定向</strong>:这种用法常见于处理文件输入输出的程序中。通过将 <code>stdin</code> 重定向为文件,可以模拟用户从文件中提供输入,特别在批量测试中非常有用。</li><li><strong>多文件处理</strong>:如果程序需要处理多个输入文件,可以用 <code>freopen</code> 来切换不同的输入源,而不需要修改程序中所有读取输入的代码。</li><li><strong>竞赛编程</strong>:在编程竞赛中,常常会将输入从文件中读取,而不是通过键盘输入。这时使用 <code>freopen</code> 可以方便地将标准输入重定向到文件。</li></ul><h3 id="注意事项:"><a href="#注意事项:" class="headerlink" title="注意事项:"></a>注意事项:</h3><ul><li>在使用 <code>freopen</code> 时,一定要确保文件存在且可访问,否则会导致程序出现错误。如果 <code>freopen</code> 打开文件失败,它会返回 <code>nullptr</code>,这时应该检查返回值来处理错误。</li><li><code>freopen</code> 会关闭原本的 <code>stdin</code>,如果你需要恢复标准输入,可以在适当的时机调用 <code>freopen</code> 重新将标准输入流恢复为控制台或其他流。</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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><cstdio></span></span><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-comment">// 将标准输入重定向到 input.txt 文件</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">freopen</span>(<span class="hljs-string">"input.txt"</span>, <span class="hljs-string">"r"</span>, stdin) == <span class="hljs-literal">nullptr</span>) {<br> std::cerr << <span class="hljs-string">"无法打开 input.txt 文件"</span> << std::endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>; <span class="hljs-comment">// 出现错误时退出</span><br> }<br><br> <span class="hljs-type">int</span> a, b, c;<br> std::cin >> a >> b >> c;<br><br> std::cout << <span class="hljs-string">"a: "</span> << a << <span class="hljs-string">", b: "</span> << b << <span class="hljs-string">", c: "</span> << c << std::endl;<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在上面的代码中,如果 <code>freopen</code> 无法成功打开 <code>input.txt</code> 文件,程序会输出错误信息并退出。</p><h3 id="总结:"><a href="#总结:" class="headerlink" title="总结:"></a>总结:</h3><p><code>freopen("input.txt", "r", stdin);</code> 通过将标准输入流重定向到文件 <code>input.txt</code>,使得后续通过 <code>std::cin</code> 或 <code>scanf</code> 的输入都来自该文件,而不是来自键盘。这种方法在需要从文件读取输入数据时非常有用。</p><h2 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h2><p>C++中的异常处理是一种程序控制机制,它使程序能够在运行时检测并响应错误或异常情况,从而避免程序崩溃并允许程序员采取适当的补救措施。C++的异常处理系统通过关键字 <code>try</code>、<code>catch</code> 和 <code>throw</code> 来实现,这种机制与其他语言如Java或Python中的异常处理机制有相似之处,但也具有其独特性。</p><h3 id="1-异常的基本概念"><a href="#1-异常的基本概念" class="headerlink" title="1. 异常的基本概念"></a>1. 异常的基本概念</h3><p>在C++中,异常是一种被抛出的对象,通常用于指示程序发生了错误或不正常的情况。抛出的异常通常是一个对象的实例,这个对象可以是任何类型,C++并不限制异常的类型,因此,程序员可以定义自己的异常类型。</p><p>异常处理机制的基本目标是:</p><ul><li><strong>捕获异常</strong>:当发生异常时,程序能够捕获异常并做出适当的处理。</li><li><strong>恢复程序流</strong>:程序能够在处理完异常后恢复正常的控制流。</li></ul><h3 id="2-异常处理的关键字"><a href="#2-异常处理的关键字" class="headerlink" title="2. 异常处理的关键字"></a>2. 异常处理的关键字</h3><p>C++中异常处理的核心有三个关键字:<code>try</code>、<code>throw</code> 和 <code>catch</code>。</p><h4 id="2-1-try-块"><a href="#2-1-try-块" class="headerlink" title="2.1 try 块"></a>2.1 <code>try</code> 块</h4><p><code>try</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">// 可能抛出异常的代码</span><br>}<br></code></pre></td></tr></table></figure><p><code>try</code> 块中的代码会被正常执行,如果在执行过程中抛出了异常,则控制流会立即跳转到相应的 <code>catch</code> 块。</p><h4 id="2-2-throw-关键字"><a href="#2-2-throw-关键字" class="headerlink" title="2.2 throw 关键字"></a>2.2 <code>throw</code> 关键字</h4><p><code>throw</code> 关键字用于抛出异常。它后面跟着异常对象,异常对象可以是任何类型的对象。常见的做法是抛出类对象、基础类型或标准库异常类的实例。</p><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">throw</span> exception_object;<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">throw</span> <span class="hljs-number">10</span>; <span class="hljs-comment">// 抛出整数 10</span><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><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 cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyException</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">const</span> <span class="hljs-type">char</span>* <span class="hljs-title">what</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> <span class="hljs-keyword">noexcept</span> </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-string">"An error occurred!"</span>;<br> }<br>};<br><br><span class="hljs-keyword">throw</span> <span class="hljs-built_in">MyException</span>();<br></code></pre></td></tr></table></figure><h4 id="2-3-catch-块"><a href="#2-3-catch-块" class="headerlink" title="2.3 catch 块"></a>2.3 <code>catch</code> 块</h4><p><code>catch</code> 块用于捕获 <code>try</code> 块中抛出的异常,并对其进行处理。<code>catch</code> 块必须紧随 <code>try</code> 块之后,且它可以根据异常的类型定义不同的处理方式。</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">// 可能抛出异常的代码</span><br>} <span class="hljs-built_in">catch</span> (exception_type& e) {<br> <span class="hljs-comment">// 异常处理代码</span><br>}<br></code></pre></td></tr></table></figure><p>其中,<code>exception_type</code> 是捕获的异常类型,<code>e</code> 是该类型的引用变量,允许在 <code>catch</code> 块中访问异常对象。</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">throw</span> <span class="hljs-number">10</span>;<br>} <span class="hljs-built_in">catch</span> (<span class="hljs-type">int</span> e) {<br> std::cout << <span class="hljs-string">"Caught an integer exception: "</span> << e << std::endl;<br>}<br></code></pre></td></tr></table></figure><h3 id="3-异常传递与处理过程"><a href="#3-异常传递与处理过程" class="headerlink" title="3. 异常传递与处理过程"></a>3. 异常传递与处理过程</h3><p>当异常发生时,程序会开始从 <code>try</code> 块中的代码执行位置开始寻找一个合适的 <code>catch</code> 块来处理该异常。如果在当前函数内没有找到匹配的 <code>catch</code> 块,异常会被传递到调用该函数的上层函数中,并继续进行匹配过程,直到找到合适的 <code>catch</code> 块或者程序终止。</p><p>在C++中,异常处理是基于栈展开机制进行的。即当异常发生时,程序会依次销毁栈上所有的局部对象,执行它们的析构函数,这一过程称为“栈展开”。这种机制有助于确保资源能够被正确地释放。</p><h3 id="4-自定义异常类型"><a href="#4-自定义异常类型" class="headerlink" title="4. 自定义异常类型"></a>4. 自定义异常类型</h3><p>C++允许程序员定义自己的异常类型。自定义异常通常通过继承标准异常类(如 <code>std::exception</code>)来实现。<code>std::exception</code> 是C++标准库中所有异常类的基类,它提供了一个 <code>what()</code> 方法来返回异常的描述信息。</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><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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><exception></span></span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyException</span> : <span class="hljs-keyword">public</span> std::exception {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">const</span> <span class="hljs-type">char</span>* <span class="hljs-title">what</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> <span class="hljs-keyword">noexcept</span> <span class="hljs-keyword">override</span> </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-string">"Custom exception occurred!"</span>;<br> }<br>};<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-keyword">try</span> {<br> <span class="hljs-keyword">throw</span> <span class="hljs-built_in">MyException</span>();<br> } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> MyException& e) {<br> std::cout << e.<span class="hljs-built_in">what</span>() << std::endl;<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="5-异常处理中的资源管理"><a href="#5-异常处理中的资源管理" class="headerlink" title="5. 异常处理中的资源管理"></a>5. 异常处理中的资源管理</h3><p>在异常处理中,资源管理是一个重要的考虑因素。为了避免资源泄露,C++推荐使用 RAII(Resource Acquisition Is Initialization)模式来管理资源。RAII是通过在对象的生命周期内管理资源来确保资源得到适时释放的一种技术。</p><p>例如,使用 <code>std::unique_ptr</code> 或 <code>std::shared_ptr</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><memory></span></span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">function</span><span class="hljs-params">()</span> </span>{<br> std::unique_ptr<<span class="hljs-type">int</span>> p = std::<span class="hljs-built_in">make_unique</span><<span class="hljs-type">int</span>>(<span class="hljs-number">10</span>);<br> <span class="hljs-comment">// 其他可能抛出异常的操作</span><br> <span class="hljs-comment">// p会在函数结束时自动释放内存</span><br>}<br></code></pre></td></tr></table></figure><h3 id="6-异常规范(Exception-Specification)"><a href="#6-异常规范(Exception-Specification)" class="headerlink" title="6. 异常规范(Exception Specification)"></a>6. 异常规范(Exception Specification)</h3><p>在C++98和C++03中,函数可以声明它们可能抛出哪些类型的异常,使用 <code>throw</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-function"><span class="hljs-type">void</span> <span class="hljs-title">myFunction</span><span class="hljs-params">()</span> <span class="hljs-title">throw</span> <span class="hljs-params">(std::exception)</span></span>;<br></code></pre></td></tr></table></figure><p>这表示 <code>myFunction</code> 可能抛出 <code>std::exception</code> 类型的异常。然而,这种异常规范在C++11及以后版本被废弃,取而代之的是 noexcept 关键字,后者用于声明函数不会抛出任何异常:</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-function"><span class="hljs-type">void</span> <span class="hljs-title">myFunction</span><span class="hljs-params">()</span> <span class="hljs-keyword">noexcept</span> </span>{<br> <span class="hljs-comment">// 这个函数不会抛出异常</span><br>}<br></code></pre></td></tr></table></figure><h3 id="7-异常安全"><a href="#7-异常安全" class="headerlink" title="7. 异常安全"></a>7. 异常安全</h3><p>在编写C++程序时,必须考虑异常安全。异常安全指的是程序在发生异常时,能够保持一致性并防止资源泄漏。C++提供了不同级别的异常安全保证:</p><ul><li><strong>基本保证(Basic Guarantee)</strong>:即使发生异常,程序的状态不会处于不一致的状态。对象的析构函数将被正确调用,资源将被释放。</li><li><strong>强保证(Strong Guarantee)</strong>:即使发生异常,程序的状态也不会发生变化,相当于回滚到异常发生前的状态。</li><li><strong>无保证(No Guarantee)</strong>:没有提供任何保证,异常发生时可能会破坏程序状态。</li></ul><p>对于复杂的代码,通常使用“强保证”或“基本保证”来确保程序在异常发生时的稳定性。</p><h3 id="8-总结"><a href="#8-总结" class="headerlink" title="8. 总结"></a>8. 总结</h3><p>C++的异常处理机制为程序员提供了一种优雅的方式来处理运行时错误。通过 <code>try</code>、<code>catch</code> 和 <code>throw</code> 关键字,程序员可以在错误发生时捕获并处理异常,避免程序崩溃。同时,C++中的异常处理还涉及到自定义异常类型、异常规范、资源管理和异常安全等多个方面,这些都需要程序员仔细设计和实现。</p><h2 id="标准库中的异常类"><a href="#标准库中的异常类" class="headerlink" title="标准库中的异常类"></a>标准库中的异常类</h2><p>是的,C++标准库包含了一些用于异常处理的类。这些异常类都继承自 <code>std::exception</code> 类,<code>std::exception</code> 是所有标准异常类的基类,它提供了基本的异常信息功能。除了 <code>std::exception</code>,C++标准库还定义了一些具体的异常类,用于表示不同类型的错误情况。</p><p>以下是一些常见的C++标准库异常类的概述:</p><h3 id="1-std-exception"><a href="#1-std-exception" class="headerlink" title="1. std::exception"></a>1. <code>std::exception</code></h3><p><code>std::exception</code> 是所有标准异常类的基类。它提供了一个 <code>what()</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><exception></span></span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">func</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">throw</span> std::<span class="hljs-built_in">exception</span>();<br>}<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-keyword">try</span> {<br> <span class="hljs-built_in">func</span>();<br> } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> std::exception& e) {<br> std::cout << <span class="hljs-string">"Caught exception: "</span> << e.<span class="hljs-built_in">what</span>() << std::endl;<br> }<br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight ada"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ada">Caught <span class="hljs-keyword">exception</span>: std::<span class="hljs-keyword">exception</span><br></code></pre></td></tr></table></figure><h3 id="2-std-runtime-error"><a href="#2-std-runtime-error" class="headerlink" title="2. std::runtime_error"></a>2. <code>std::runtime_error</code></h3><p><code>std::runtime_error</code> 是 <code>std::exception</code> 的派生类,用于表示程序运行时发生的错误,通常用于逻辑错误、状态不一致等。它的构造函数接受一个 <code>const char*</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdexcept></span></span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">func</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">throw</span> std::<span class="hljs-built_in">runtime_error</span>(<span class="hljs-string">"Runtime error occurred!"</span>);<br>}<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-keyword">try</span> {<br> <span class="hljs-built_in">func</span>();<br> } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> std::runtime_error& e) {<br> std::cout << <span class="hljs-string">"Caught exception: "</span> << e.<span class="hljs-built_in">what</span>() << std::endl;<br> }<br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">Caught</span> exception: Runtime <span class="hljs-literal">error</span> occurred!<br></code></pre></td></tr></table></figure><h3 id="3-std-logic-error"><a href="#3-std-logic-error" class="headerlink" title="3. std::logic_error"></a>3. <code>std::logic_error</code></h3><p><code>std::logic_error</code> 也是 <code>std::exception</code> 的一个派生类,用于表示程序逻辑上的错误,通常是违反了某种约定、使用不当等。例如,访问空指针、无效的输入等问题可能会引发此类异常。</p><p>常见的 <code>std::logic_error</code> 的子类有:</p><ul><li><code>std::invalid_argument</code>:表示传递给函数的参数无效。</li><li><code>std::domain_error</code>:表示输入值不在定义的范围内。</li><li><code>std::length_error</code>:表示容器超出了允许的最大长度。</li><li><code>std::out_of_range</code>:表示尝试访问超出范围的元素。</li></ul><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><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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdexcept></span></span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">func</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span> </span>{<br> <span class="hljs-keyword">if</span> (n < <span class="hljs-number">0</span>) {<br> <span class="hljs-keyword">throw</span> std::<span class="hljs-built_in">invalid_argument</span>(<span class="hljs-string">"Negative number is not allowed!"</span>);<br> }<br>}<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-keyword">try</span> {<br> <span class="hljs-built_in">func</span>(<span class="hljs-number">-5</span>);<br> } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> std::invalid_argument& e) {<br> std::cout << <span class="hljs-string">"Caught exception: "</span> << e.<span class="hljs-built_in">what</span>() << std::endl;<br> }<br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs xquery">Caught exception: Negative<span class="hljs-built_in"> number</span> <span class="hljs-literal">is</span><span class="hljs-built_in"> not</span> allowed!<br></code></pre></td></tr></table></figure><h3 id="4-std-bad-alloc"><a href="#4-std-bad-alloc" class="headerlink" title="4. std::bad_alloc"></a>4. <code>std::bad_alloc</code></h3><p><code>std::bad_alloc</code> 是 <code>std::exception</code> 的一个派生类,专门用来表示内存分配失败的异常。它通常在 <code>new</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><new></span> <span class="hljs-comment">// 引入 std::bad_alloc</span></span><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-keyword">try</span> {<br> <span class="hljs-type">int</span>* arr = <span class="hljs-keyword">new</span> <span class="hljs-type">int</span>[<span class="hljs-number">1000000000</span>]; <span class="hljs-comment">// 试图分配过多的内存</span><br> } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> std::bad_alloc& e) {<br> std::cout << <span class="hljs-string">"Caught exception: "</span> << e.<span class="hljs-built_in">what</span>() << std::endl;<br> }<br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight ada"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ada">Caught <span class="hljs-keyword">exception</span>: std::bad_alloc<br></code></pre></td></tr></table></figure><h3 id="5-std-out-of-range"><a href="#5-std-out-of-range" class="headerlink" title="5. std::out_of_range"></a>5. <code>std::out_of_range</code></h3><p><code>std::out_of_range</code> 继承自 <code>std::logic_error</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdexcept></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><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> std::vector<<span class="hljs-type">int</span>> v = {<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>};<br><br> <span class="hljs-keyword">try</span> {<br> std::cout << v.<span class="hljs-built_in">at</span>(<span class="hljs-number">5</span>) << std::endl; <span class="hljs-comment">// 超出范围</span><br> } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> std::out_of_range& e) {<br> std::cout << <span class="hljs-string">"Caught exception: "</span> << e.<span class="hljs-built_in">what</span>() << std::endl;<br> }<br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs kotlin">Caught exception: vector::_M_range_check: __n (which <span class="hljs-keyword">is</span> <span class="hljs-number">5</span>) >= <span class="hljs-keyword">this</span>->size() (which <span class="hljs-keyword">is</span> <span class="hljs-number">3</span>)<br></code></pre></td></tr></table></figure><h3 id="6-std-overflow-error-和-std-underflow-error"><a href="#6-std-overflow-error-和-std-underflow-error" class="headerlink" title="6. std::overflow_error 和 std::underflow_error"></a>6. <code>std::overflow_error</code> 和 <code>std::underflow_error</code></h3><p><code>std::overflow_error</code> 和 <code>std::underflow_error</code> 都是继承自 <code>std::runtime_error</code> 的异常类,分别用于表示算术运算中的溢出和下溢错误。</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdexcept></span></span><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-keyword">try</span> {<br> <span class="hljs-keyword">throw</span> std::<span class="hljs-built_in">overflow_error</span>(<span class="hljs-string">"Overflow error occurred!"</span>);<br> } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> std::overflow_error& e) {<br> std::cout << <span class="hljs-string">"Caught exception: "</span> << e.<span class="hljs-built_in">what</span>() << std::endl;<br> }<br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">Caught</span> exception: Overflow <span class="hljs-literal">error</span> occurred!<br></code></pre></td></tr></table></figure><h3 id="7-std-ios-base-failure"><a href="#7-std-ios-base-failure" class="headerlink" title="7. std::ios_base::failure"></a>7. <code>std::ios_base::failure</code></h3><p><code>std::ios_base::failure</code> 是一个用于处理输入输出流错误的异常类。它继承自 <code>std::exception</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></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><fstream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdexcept></span></span><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-keyword">try</span> {<br> <span class="hljs-function">std::ifstream <span class="hljs-title">file</span><span class="hljs-params">(<span class="hljs-string">"non_existent_file.txt"</span>)</span></span>;<br> <span class="hljs-keyword">if</span> (!file) {<br> <span class="hljs-keyword">throw</span> std::ios_base::<span class="hljs-built_in">failure</span>(<span class="hljs-string">"Failed to open file!"</span>);<br> }<br> } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> std::ios_base::failure& e) {<br> std::cout << <span class="hljs-string">"Caught exception: "</span> << e.<span class="hljs-built_in">what</span>() << std::endl;<br> }<br>}<br></code></pre></td></tr></table></figure><p><strong>输出:</strong></p><figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs fsharp">Caught <span class="hljs-keyword">exception</span><span class="hljs-operator">:</span> Failed <span class="hljs-keyword">to</span> <span class="hljs-keyword">open</span> file<span class="hljs-operator">!</span><br></code></pre></td></tr></table></figure><h3 id="8-std-bad-cast-和-std-bad-typeid"><a href="#8-std-bad-cast-和-std-bad-typeid" class="headerlink" title="8. std::bad_cast 和 std::bad_typeid"></a>8. <code>std::bad_cast</code> 和 <code>std::bad_typeid</code></h3><ul><li><code>std::bad_cast</code>:当尝试进行不合法的类型转换(例如,<code>dynamic_cast</code> 失败)时抛出此异常。</li><li><code>std::bad_typeid</code>:当对一个空指针调用 <code>typeid</code> 时抛出此异常。</li></ul><h3 id="9-总结"><a href="#9-总结" class="headerlink" title="9. 总结"></a>9. 总结</h3><p>C++标准库提供了丰富的异常类,能够涵盖程序中多种常见的错误类型。通过继承自 <code>std::exception</code> 和其它专门的子类,C++的异常机制能够为程序提供强大的错误处理能力。这些标准异常类帮助程序员捕获和处理各种不同类型的错误,确保程序的稳定性和健壮性。</p>]]></content>
<categories>
<category>C++</category>
<category>CS106L</category>
</categories>
<tags>
<tag>RAII</tag>
<tag>智能指针</tag>
<tag>文件流</tag>
<tag>异常处理</tag>
</tags>
</entry>
<entry>
<title>继承和模板类 C++</title>
<link href="/posts/35825.html"/>
<url>/posts/35825.html</url>
<content type="html"><![CDATA[<p><a href="https://www.bilibili.com/video/BV19x4y1E79V?spm_id_from=333.788.videopod.episodes&vd_source=c06338b0283c611d7a47c62b0ed23dfa&p=16">CS106L p16 Part 5.6 Inheritance and Template Classes</a></p><h2 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h2><h3 id="C-中的继承概述"><a href="#C-中的继承概述" class="headerlink" title="C++中的继承概述"></a>C++中的继承概述</h3><p>继承是面向对象编程(OOP)中的一种机制,它允许创建一个新的类(派生类),该类可以继承并扩展或修改已有类(基类)的属性和方法。在C++中,继承提供了一种组织代码的方式,使得可以在不重复代码的情况下重用和扩展已有的类。</p><p>继承的基本概念如下:</p><ul><li><strong>基类(Base Class)</strong>:也称为父类,它包含一些通用的属性和方法。</li><li><strong>派生类(Derived Class)</strong>:也称为子类,它继承了基类的成员,并可以在此基础上进行扩展或修改。</li></ul><p>C++支持单继承(一个派生类只能继承一个基类)和多继承(一个派生类可以继承多个基类)。C++的继承是通过使用<code>:</code>符号来实现的。</p><h3 id="继承的基本语法"><a href="#继承的基本语法" class="headerlink" title="继承的基本语法"></a>继承的基本语法</h3><p>在C++中,继承的基本语法结构如下:</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">class</span> <span class="hljs-title class_">DerivedClass</span> : accessSpecifier BaseClass {<br> <span class="hljs-comment">// DerivedClass的成员</span><br>};<br></code></pre></td></tr></table></figure><ul><li><code>DerivedClass</code> 是派生类的名称。</li><li><code>BaseClass</code> 是基类的名称。</li><li><code>accessSpecifier</code> 是继承方式,可以是<code>public</code>、<code>protected</code>或<code>private</code>,它决定了基类成员在派生类中的可访问性。</li></ul><h3 id="继承的访问控制"><a href="#继承的访问控制" class="headerlink" title="继承的访问控制"></a>继承的访问控制</h3><p>C++允许通过三种访问控制修饰符来指定基类成员在派生类中的可访问性:</p><ol><li><p><strong><code>public</code>继承</strong>:基类的<code>public</code>成员在派生类中依然是<code>public</code>,<code>protected</code>成员在派生类中是<code>protected</code>,<code>private</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-type">int</span> publicVar;<br><span class="hljs-keyword">protected</span>:<br> <span class="hljs-type">int</span> protectedVar;<br><span class="hljs-keyword">private</span>:<br> <span class="hljs-type">int</span> privateVar;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">show</span><span class="hljs-params">()</span> </span>{<br> std::cout << publicVar << std::endl; <span class="hljs-comment">// 可以访问</span><br> std::cout << protectedVar << std::endl; <span class="hljs-comment">// 可以访问</span><br> <span class="hljs-comment">// std::cout << privateVar << std::endl; // 错误,不能访问private成员</span><br> }<br>};<br></code></pre></td></tr></table></figure></li><li><p><strong><code>protected</code>继承</strong>:基类的<code>public</code>和<code>protected</code>成员会变为派生类中的<code>protected</code>,<code>private</code>成员不可访问。</p></li><li><p><strong><code>private</code>继承</strong>:基类的所有成员(无论是<code>public</code>、<code>protected</code>还是<code>private</code>)都会变为派生类中的<code>private</code>,这意味着它们不能被派生类外部访问。</p></li></ol><h3 id="继承的类型"><a href="#继承的类型" class="headerlink" title="继承的类型"></a>继承的类型</h3><ol><li><p><strong>单继承(Single Inheritance)</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">show</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Base class"</span> << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Derived class"</span> << std::endl; }<br>};<br></code></pre></td></tr></table></figure></li><li><p><strong>多继承(Multiple Inheritance)</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base1</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">show1</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Base1"</span> << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base2</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">show2</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Base2"</span> << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base1, <span class="hljs-keyword">public</span> Base2 {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Derived class"</span> << std::endl; }<br>};<br></code></pre></td></tr></table></figure></li><li><p><strong>虚继承(Virtual Inheritance)</strong>:解决多继承中的“钻石问题”。虚继承确保派生类只继承基类的一份数据。通过<code>virtual</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-type">int</span> baseVar;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">A</span> : <span class="hljs-keyword">virtual</span> <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">showA</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Class A"</span> << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">B</span> : <span class="hljs-keyword">virtual</span> <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">showB</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Class B"</span> << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> A, <span class="hljs-keyword">public</span> B {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">showDerived</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Derived Class"</span> << std::endl; }<br>};<br></code></pre></td></tr></table></figure></li></ol><h3 id="构造函数与析构函数的继承"><a href="#构造函数与析构函数的继承" class="headerlink" title="构造函数与析构函数的继承"></a>构造函数与析构函数的继承</h3><ol><li><p><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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">Base</span>(<span class="hljs-type">int</span> x) { std::cout << <span class="hljs-string">"Base class constructor, x = "</span> << x << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">Derived</span>(<span class="hljs-type">int</span> x) : <span class="hljs-built_in">Base</span>(x) { std::cout << <span class="hljs-string">"Derived class constructor"</span> << std::endl; }<br>};<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-function">Derived <span class="hljs-title">d</span><span class="hljs-params">(<span class="hljs-number">10</span>)</span></span>; <span class="hljs-comment">// 会调用Base(int)构造函数</span><br>}<br></code></pre></td></tr></table></figure></li><li><p><strong>基类析构函数</strong>:如果派生类的对象被销毁,析构函数会从派生类开始,逐步调用基类的析构函数。如果基类的析构函数是虚拟的,C++将确保在删除通过基类指针指向派生类的对象时,能够正确地调用派生类和基类的析构函数。</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-keyword">virtual</span> ~<span class="hljs-built_in">Base</span>() { std::cout << <span class="hljs-string">"Base class destructor"</span> << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> ~<span class="hljs-built_in">Derived</span>() { std::cout << <span class="hljs-string">"Derived class destructor"</span> << std::endl; }<br>};<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> Base* b = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Derived</span>();<br> <span class="hljs-keyword">delete</span> b; <span class="hljs-comment">// 会先调用Derived类的析构函数,再调用Base类的析构函数</span><br>}<br></code></pre></td></tr></table></figure></li></ol><h3 id="方法重写与多态"><a href="#方法重写与多态" class="headerlink" title="方法重写与多态"></a>方法重写与多态</h3><p>继承使得派生类可以重写基类的方法。这种现象称为<strong>方法重写(Override)</strong>。如果基类中的方法被标记为<code>virtual</code>,则C++会启用<strong>运行时多态(Runtime Polymorphism)</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Base class"</span> << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{ std::cout << <span class="hljs-string">"Derived class"</span> << std::endl; }<br>};<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> Base* b = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Derived</span>();<br> b-><span class="hljs-built_in">display</span>(); <span class="hljs-comment">// 输出 "Derived class"</span><br> <span class="hljs-keyword">delete</span> b;<br>}<br></code></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>C++中的继承机制支持代码复用和扩展,可以帮助实现类的层次化结构。通过继承,派生类可以继承基类的属性和方法,进而对其进行修改或扩展,支持单继承和多继承(通过虚继承解决多继承中的问题)。此外,C++支持方法的重写与多态,使得面向对象编程更具灵活性和表达力。在实际编程中,理解并合理运用继承能大大提高代码的可复用性和维护性。</p><h2 id="虚继承"><a href="#虚继承" class="headerlink" title="虚继承"></a>虚继承</h2><p>虚继承(Virtual Inheritance)是C++中多重继承的一种特殊机制,用于解决多重继承中的“菱形继承问题”(Diamond Problem)。为了更好地理解虚继承,我们首先需要了解多重继承、菱形继承问题的概念以及虚继承的解决方案。</p><h3 id="1-多重继承与菱形继承问题"><a href="#1-多重继承与菱形继承问题" class="headerlink" title="1. 多重继承与菱形继承问题"></a>1. 多重继承与菱形继承问题</h3><h4 id="1-1-多重继承"><a href="#1-1-多重继承" class="headerlink" title="1.1 多重继承"></a>1.1 多重继承</h4><p>在C++中,类可以继承多个基类,称为多重继承。例如:</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></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-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">class</span> <span class="hljs-title class_">C</span> : <span class="hljs-keyword">public</span> A {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-type">int</span> c;<br>};<br></code></pre></td></tr></table></figure><p>在上面的例子中,类 <code>B</code> 和类 <code>C</code> 都继承自 <code>A</code>,这是一个典型的多重继承。虽然多重继承在某些情况下是非常有用的,但它可能带来一些问题,尤其是在多个基类之间存在共同部分时。</p><h4 id="1-2-菱形继承问题"><a href="#1-2-菱形继承问题" class="headerlink" title="1.2 菱形继承问题"></a>1.2 菱形继承问题</h4><p>菱形继承问题是指,当一个类通过多个路径继承同一个基类时,基类的成员会被重复继承,从而导致冲突或冗余。这种问题通常出现在多个派生类继承自同一个基类,然后再被另一个类继承的情况下。</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><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 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-type">int</span> x;<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> y;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">C</span> : <span class="hljs-keyword">public</span> A {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-type">int</span> z;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">D</span> : <span class="hljs-keyword">public</span> B, <span class="hljs-keyword">public</span> C {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-type">int</span> w;<br>};<br></code></pre></td></tr></table></figure><p>在上面的代码中,<code>B</code> 和 <code>C</code> 都继承了 <code>A</code>,而 <code>D</code> 又继承了 <code>B</code> 和 <code>C</code>。这就导致了 <code>D</code> 类中有两个 <code>A</code> 类的副本,这样会带来以下问题:</p><ol><li><strong>数据冗余</strong>:类 <code>D</code> 会有两个 <code>A</code> 类的副本,两个 <code>x</code>。</li><li><strong>歧义问题</strong>:如果 <code>D</code> 类要访问 <code>x</code>,会产生歧义,因为编译器无法确定是通过 <code>B</code> 继承来的 <code>A</code> 还是通过 <code>C</code> 继承来的 <code>A</code>。</li></ol><h3 id="2-虚继承的概念"><a href="#2-虚继承的概念" class="headerlink" title="2. 虚继承的概念"></a>2. 虚继承的概念</h3><p>虚继承就是通过关键字 <code>virtual</code> 来标明基类是以虚拟方式继承的,目的就是解决菱形继承中基类的重复继承问题。虚继承的基本思想是:多个派生类之间共享同一个基类的实例,从而避免重复继承,确保只有一个基类的实例存在。</p><h4 id="2-1-语法"><a href="#2-1-语法" class="headerlink" title="2.1 语法"></a>2.1 语法</h4><p>要实现虚继承,需要在派生类声明继承时使用 <code>virtual</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></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-type">int</span> x;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">B</span> : <span class="hljs-keyword">virtual</span> <span class="hljs-keyword">public</span> A {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-type">int</span> y;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">C</span> : <span class="hljs-keyword">virtual</span> <span class="hljs-keyword">public</span> A {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-type">int</span> z;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">D</span> : <span class="hljs-keyword">public</span> B, <span class="hljs-keyword">public</span> C {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-type">int</span> w;<br>};<br></code></pre></td></tr></table></figure><p>在上面的代码中,<code>B</code> 和 <code>C</code> 都是虚继承自 <code>A</code>,而 <code>D</code> 则继承了 <code>B</code> 和 <code>C</code>。这样,<code>D</code> 只有一个 <code>A</code> 类的实例。</p><h4 id="2-2-如何解决菱形继承问题"><a href="#2-2-如何解决菱形继承问题" class="headerlink" title="2.2 如何解决菱形继承问题"></a>2.2 如何解决菱形继承问题</h4><p>虚继承通过在 <code>B</code> 和 <code>C</code> 中将 <code>A</code> 类声明为虚继承,确保 <code>D</code> 类只拥有一个 <code>A</code> 类的实例。即使 <code>B</code> 和 <code>C</code> 都从 <code>A</code> 继承,最终只有一个 <code>A</code> 的副本存在,并且只有一个 <code>x</code> 成员。</p><h3 id="3-虚继承的实现原理"><a href="#3-虚继承的实现原理" class="headerlink" title="3. 虚继承的实现原理"></a>3. 虚继承的实现原理</h3><p>虚继承背后的实现机制比常规继承复杂,因为编译器需要确保不同路径继承的基类共享同一实例。为了实现这一点,编译器需要引入一个虚拟基类表(Virtual Base Table,VBT)。这个表用于维护不同派生类对虚拟基类的引用关系。</p><p>具体来说,虚继承会影响构造函数的调用顺序和数据布局:</p><ul><li><strong>构造顺序</strong>:虚基类的构造函数会在最底层派生类的构造函数调用之前进行调用。这个顺序确保了虚拟基类的唯一实例在派生类构造之前被正确初始化。</li><li><strong>内存布局</strong>:虚继承还可能影响对象的内存布局,因为虚基类的成员需要被唯一实例共享,所以虚基类可能会通过指针或其他机制来访问。</li></ul><h3 id="4-示例与行为分析"><a href="#4-示例与行为分析" class="headerlink" title="4. 示例与行为分析"></a>4. 示例与行为分析</h3><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><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></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-keyword">class</span> <span class="hljs-title class_">A</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">A</span>() { cout << <span class="hljs-string">"A constructor"</span> << endl; }<br> <span class="hljs-type">int</span> x;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">B</span> : <span class="hljs-keyword">virtual</span> <span class="hljs-keyword">public</span> A {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">B</span>() { cout << <span class="hljs-string">"B constructor"</span> << endl; }<br> <span class="hljs-type">int</span> y;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">C</span> : <span class="hljs-keyword">virtual</span> <span class="hljs-keyword">public</span> A {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">C</span>() { cout << <span class="hljs-string">"C constructor"</span> << endl; }<br> <span class="hljs-type">int</span> z;<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">D</span> : <span class="hljs-keyword">public</span> B, <span class="hljs-keyword">public</span> C {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">D</span>() { cout << <span class="hljs-string">"D constructor"</span> << endl; }<br> <span class="hljs-type">int</span> w;<br>};<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> D d;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p><strong>输出结果:</strong></p><figure class="highlight delphi"><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 delphi">A <span class="hljs-function"><span class="hljs-keyword">constructor</span></span><br><span class="hljs-function"><span class="hljs-title">B</span> <span class="hljs-title">constructor</span></span><br><span class="hljs-function"><span class="hljs-title">C</span> <span class="hljs-title">constructor</span></span><br><span class="hljs-function"><span class="hljs-title">D</span> <span class="hljs-title">constructor</span></span><br></code></pre></td></tr></table></figure><p><strong>分析:</strong></p><ol><li>虚基类 <code>A</code> 的构造函数被最先调用,确保 <code>A</code> 的实例在派生类之前构造。</li><li><code>B</code> 和 <code>C</code> 的构造函数分别被调用,而不是 <code>D</code> 自己去调用 <code>A</code> 的构造函数。</li><li>最后,<code>D</code> 的构造函数被调用。</li></ol><h3 id="5-虚继承的优缺点"><a href="#5-虚继承的优缺点" class="headerlink" title="5. 虚继承的优缺点"></a>5. 虚继承的优缺点</h3><h4 id="优点:"><a href="#优点:" class="headerlink" title="优点:"></a>优点:</h4><ol><li><strong>解决菱形继承问题</strong>:虚继承通过共享基类的实例,避免了冗余的数据成员和可能的歧义。</li><li><strong>提高代码复用性</strong>:多个派生类可以共同使用同一个基类实例,避免了多次初始化和冗余数据。</li></ol><h4 id="缺点:"><a href="#缺点:" class="headerlink" title="缺点:"></a>缺点:</h4><ol><li><strong>性能开销</strong>:虚继承需要额外的指针或表格来管理虚基类的共享实例,因此可能带来一定的性能开销。</li><li><strong>复杂性</strong>:虚继承使得类的继承关系变得复杂,构造函数的调用顺序和内存布局需要更加小心。</li></ol><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><p>虚继承是C++提供的一个机制,主要用于解决多重继承中的菱形继承问题,它通过确保多个派生类共享同一个基类实例,避免了冗余和歧义。尽管虚继承增加了程序的复杂性,并可能带来一定的性能开销,但它在处理复杂的继承关系时是非常有用的,尤其是在大规模的面向对象设计中。</p><h2 id="虚函数"><a href="#虚函数" class="headerlink" title="虚函数"></a>虚函数</h2><h3 id="虚函数(Virtual-Function)详解"><a href="#虚函数(Virtual-Function)详解" class="headerlink" title="虚函数(Virtual Function)详解"></a>虚函数(Virtual Function)详解</h3><p>在C++中,虚函数是一种成员函数,其目的是通过基类指针或引用来动态调用派生类的重写版本,从而实现<strong>运行时多态性</strong>。通过虚函数,C++支持<strong>动态绑定</strong>,即在程序运行时确定调用哪个函数,而不是在编译时静态绑定。</p><p>虚函数是面向对象编程中的重要特性之一,使得同一类型的对象能够表现出不同的行为,增强了代码的扩展性和可维护性。</p><h3 id="1-虚函数的基本概念"><a href="#1-虚函数的基本概念" class="headerlink" title="1. 虚函数的基本概念"></a>1. 虚函数的基本概念</h3><h4 id="1-1-什么是虚函数?"><a href="#1-1-什么是虚函数?" class="headerlink" title="1.1 什么是虚函数?"></a>1.1 什么是虚函数?</h4><p>虚函数是基类中声明为 <code>virtual</code> 的成员函数。一个基类中的成员函数如果被声明为虚函数,那么在派生类中可以对其进行重写(覆盖),并且通过基类指针或引用来调用派生类的版本。</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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> </span>{ <span class="hljs-comment">// 声明虚函数</span><br> std::cout << <span class="hljs-string">"Base display"</span> << std::endl;<br> }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{ <span class="hljs-comment">// 重写虚函数</span><br> std::cout << <span class="hljs-string">"Derived display"</span> << std::endl;<br> }<br>};<br></code></pre></td></tr></table></figure><h4 id="1-2-虚函数的工作原理"><a href="#1-2-虚函数的工作原理" class="headerlink" title="1.2 虚函数的工作原理"></a>1.2 虚函数的工作原理</h4><p>虚函数的核心在于<strong>动态绑定</strong>(或称为<strong>晚绑定</strong>)。当通过基类的指针或引用调用虚函数时,C++不会立即调用编译时确定的函数,而是根据对象的实际类型(即指针或引用指向的派生类类型)来调用相应的函数。</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">Base* basePtr = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Derived</span>();<br>basePtr-><span class="hljs-built_in">display</span>(); <span class="hljs-comment">// 调用Derived类的display()</span><br></code></pre></td></tr></table></figure><p>即使 <code>basePtr</code> 是 <code>Base*</code> 类型,C++会根据它实际指向的对象类型(此处为 <code>Derived</code>)来调用 <code>Derived</code> 中的 <code>display()</code> 方法,而不是 <code>Base</code> 中的版本。</p><h3 id="2-虚函数的实现细节"><a href="#2-虚函数的实现细节" class="headerlink" title="2. 虚函数的实现细节"></a>2. 虚函数的实现细节</h3><p>虚函数的实现依赖于一个称为<strong>虚函数表</strong>(Virtual Function Table,VTable)的机制。每个包含虚函数的类都会有一个虚函数表,该表保存指向类的虚函数的指针。对于一个类的每个对象,它都会包含一个指向虚函数表的指针,称为<strong>虚指针</strong>(VPointer)。</p><h4 id="2-1-虚函数表(VTable)"><a href="#2-1-虚函数表(VTable)" class="headerlink" title="2.1 虚函数表(VTable)"></a>2.1 虚函数表(VTable)</h4><p>虚函数表是一个数组,其中每个元素是指向虚函数的指针。当类中有虚函数时,编译器会为该类生成一个虚函数表,并将所有虚函数的地址存储在表中。</p><ul><li>每个包含虚函数的类都有一个虚函数表。</li><li>每个对象会有一个隐式的指针,指向该类的虚函数表。</li><li>当调用虚函数时,程序会通过虚指针查找虚函数表,并跳转到相应的函数地址。</li></ul><h4 id="2-2-虚指针(VPointer)"><a href="#2-2-虚指针(VPointer)" class="headerlink" title="2.2 虚指针(VPointer)"></a>2.2 虚指针(VPointer)</h4><p>虚指针是编译器自动为每个对象添加的一个指针,用于指向虚函数表。当通过指针或引用调用虚函数时,程序会利用虚指针查找虚函数表,进而确定要调用哪个派生类中的虚函数。</p><h3 id="3-虚函数的应用"><a href="#3-虚函数的应用" class="headerlink" title="3. 虚函数的应用"></a>3. 虚函数的应用</h3><p>虚函数在C++中的主要作用是实现<strong>运行时多态性</strong>,从而使得程序在运行时能够根据对象的实际类型执行不同的操作。常见的应用场景包括:</p><h4 id="3-1-多态性"><a href="#3-1-多态性" class="headerlink" title="3.1 多态性"></a>3.1 多态性</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><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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Animal</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">sound</span><span class="hljs-params">()</span> </span>{ <span class="hljs-comment">// 虚函数</span><br> std::cout << <span class="hljs-string">"Animal sound"</span> << std::endl;<br> }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Dog</span> : <span class="hljs-keyword">public</span> Animal {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">sound</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{ <span class="hljs-comment">// 重写虚函数</span><br> std::cout << <span class="hljs-string">"Bark"</span> << std::endl;<br> }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Cat</span> : <span class="hljs-keyword">public</span> Animal {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">sound</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{ <span class="hljs-comment">// 重写虚函数</span><br> std::cout << <span class="hljs-string">"Meow"</span> << std::endl;<br> }<br>};<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> Animal* animal1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Dog</span>();<br> Animal* animal2 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Cat</span>();<br><br> animal1-><span class="hljs-built_in">sound</span>(); <span class="hljs-comment">// 输出: Bark</span><br> animal2-><span class="hljs-built_in">sound</span>(); <span class="hljs-comment">// 输出: Meow</span><br><br> <span class="hljs-keyword">delete</span> animal1;<br> <span class="hljs-keyword">delete</span> animal2;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在上述示例中,<code>Animal</code> 类有一个虚函数 <code>sound()</code>,而 <code>Dog</code> 和 <code>Cat</code> 类分别重写了这个函数。通过基类指针调用虚函数时,C++会根据对象的实际类型来决定调用哪个版本的 <code>sound()</code> 方法,从而实现多态性。</p><h4 id="3-2-抽象基类和接口"><a href="#3-2-抽象基类和接口" class="headerlink" title="3.2 抽象基类和接口"></a>3.2 抽象基类和接口</h4><p>虚函数通常与<strong>纯虚函数</strong>结合使用,用于定义<strong>抽象基类</strong>和<strong>接口</strong>。</p><ul><li><strong>纯虚函数</strong>:虚函数后面加 <code>= 0</code> 表示纯虚函数。纯虚函数没有函数体,必须在派生类中实现。包含纯虚函数的类是抽象基类,无法实例化。</li></ul><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_">Shape</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">draw</span><span class="hljs-params">()</span> </span>= <span class="hljs-number">0</span>; <span class="hljs-comment">// 纯虚函数</span><br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Circle</span> : <span class="hljs-keyword">public</span> Shape {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">draw</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{<br> std::cout << <span class="hljs-string">"Drawing Circle"</span> << std::endl;<br> }<br>};<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-comment">// Shape s; // 错误,无法实例化抽象类</span><br> Circle c;<br> c.<span class="hljs-built_in">draw</span>(); <span class="hljs-comment">// 输出: Drawing Circle</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在上面的例子中,<code>Shape</code> 是一个抽象基类,包含纯虚函数 <code>draw()</code>。<code>Circle</code> 类继承自 <code>Shape</code> 并实现了 <code>draw()</code> 方法。由于 <code>Shape</code> 是抽象的,无法直接创建其对象,只能通过派生类来实例化。</p><h3 id="4-虚函数的特点"><a href="#4-虚函数的特点" class="headerlink" title="4. 虚函数的特点"></a>4. 虚函数的特点</h3><h4 id="4-1-重写虚函数"><a href="#4-1-重写虚函数" class="headerlink" title="4.1 重写虚函数"></a>4.1 重写虚函数</h4><p>派生类可以重新定义基类的虚函数,称为<strong>重写</strong>(Override)虚函数。使用 <code>override</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Base display"</span> << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{ std::cout << <span class="hljs-string">"Derived display"</span> << std::endl; }<br>};<br></code></pre></td></tr></table></figure><h4 id="4-2-析构函数的虚拟化"><a href="#4-2-析构函数的虚拟化" class="headerlink" title="4.2 析构函数的虚拟化"></a>4.2 析构函数的虚拟化</h4><p>如果一个类具有虚函数,并且这个类被用作基类,则<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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-keyword">virtual</span> ~<span class="hljs-built_in">Base</span>() { <span class="hljs-comment">// 虚析构函数</span><br> std::cout << <span class="hljs-string">"Base destructor"</span> << std::endl;<br> }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> ~<span class="hljs-built_in">Derived</span>() <span class="hljs-keyword">override</span> {<br> std::cout << <span class="hljs-string">"Derived destructor"</span> << std::endl;<br> }<br>};<br></code></pre></td></tr></table></figure><h4 id="4-3-虚函数与性能"><a href="#4-3-虚函数与性能" class="headerlink" title="4.3 虚函数与性能"></a>4.3 虚函数与性能</h4><p>虚函数的机制引入了一定的开销,主要体现在以下几个方面:</p><ul><li><strong>虚函数表的查找</strong>:每次通过基类指针或引用调用虚函数时,都需要查找虚函数表,这会带来一定的运行时开销。</li><li><strong>内存开销</strong>:每个包含虚函数的类都需要有虚函数表,且每个对象需要存储一个虚指针,这增加了内存占用。</li></ul><p>不过,这些开销通常是可以接受的,尤其在多态性需求较强的场景中,虚函数的灵活性远远超越了性能上的影响。</p><h3 id="5-总结"><a href="#5-总结" class="headerlink" title="5. 总结"></a>5. 总结</h3><p>虚函数是C++支持多态性的一项核心特性,通过它,程序能够在运行时根据对象的实际类型来决定调用哪个函数。虚函数使得C++能够支持动态绑定,避免了静态绑定带来的限制。虚函数广泛应用于实现抽象类、接口、运行时多态等场景,是面向对象编程中非常重要的一部分。</p><p>通过理解虚函数的工作原理、实现细节以及应用场景,程序员可以更有效地设计灵活、可扩展的代码结构。</p><h2 id="final-关键字"><a href="#final-关键字" class="headerlink" title="final 关键字"></a>final 关键字</h2><p>在C++中,<code>final</code> 是一个关键字,用来指定类或成员函数的特性,它的主要作用是:</p><ol><li><strong>防止类被继承</strong>(用于类声明中)。</li><li><strong>防止函数被重写</strong>(用于成员函数声明中)。</li></ol><p><code>final</code> 是 C++11 引入的功能,旨在增强程序设计的安全性和可维护性,避免不必要的继承或函数重写。下面是对 <code>final</code> 关键字的详细解释。</p><h3 id="1-final-用于类"><a href="#1-final-用于类" class="headerlink" title="1. final 用于类"></a>1. <code>final</code> 用于类</h3><p>当 <code>final</code> 用于类声明时,意味着该类不能再被继承。也就是说,任何尝试继承此类的操作都会导致编译错误。</p><h4 id="示例:"><a href="#示例:" class="headerlink" title="示例:"></a>示例:</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> </span>{ std::cout << <span class="hljs-string">"Base class display"</span> << std::endl; }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> <span class="hljs-keyword">final</span> : <span class="hljs-keyword">public</span> Base { <span class="hljs-comment">// Derived 类不能再被继承</span><br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{ std::cout << <span class="hljs-string">"Derived class display"</span> << std::endl; }<br>};<br><br><span class="hljs-comment">// 下面的代码将导致编译错误,因为 Derived 是一个 final 类,不能被继承</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">AnotherDerived</span> : <span class="hljs-keyword">public</span> Derived { <span class="hljs-comment">// 错误:不能继承 final 类</span><br> <span class="hljs-comment">// 代码会报错</span><br>};<br></code></pre></td></tr></table></figure><p>在这个例子中,<code>Derived</code> 类被标记为 <code>final</code>,因此任何试图继承自 <code>Derived</code> 的类都会导致编译错误。这是为了防止某些类被不恰当地进一步继承。</p><h3 id="2-final-用于成员函数"><a href="#2-final-用于成员函数" class="headerlink" title="2. final 用于成员函数"></a>2. <code>final</code> 用于成员函数</h3><p><code>final</code> 也可以用于成员函数声明中,表示该函数不能在派生类中被重写。换句话说,如果一个函数被标记为 <code>final</code>,那么派生类不能重写(override)这个函数。</p><h4 id="示例:-1"><a href="#示例:-1" class="headerlink" title="示例:"></a>示例:</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> <span class="hljs-keyword">final</span> </span>{ <span class="hljs-comment">// 这个成员函数不能在派生类中被重写</span><br> std::cout << <span class="hljs-string">"Base class display"</span> << std::endl;<br> }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-comment">// 下面的代码会导致编译错误,因为 display() 函数在 Base 类中是 final</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{ <span class="hljs-comment">// 错误:不能重写 Base 类中的 final 函数</span><br> std::cout << <span class="hljs-string">"Derived class display"</span> << std::endl;<br> }<br>};<br></code></pre></td></tr></table></figure><p>在这个例子中,<code>Base</code> 类中的 <code>display()</code> 函数被标记为 <code>final</code>,这意味着 <code>Derived</code> 类无法重写这个函数。如果尝试在派生类中重写 <code>display()</code> 函数,编译器会报错。</p><h3 id="3-final-与虚函数的结合使用"><a href="#3-final-与虚函数的结合使用" class="headerlink" title="3. final 与虚函数的结合使用"></a>3. <code>final</code> 与虚函数的结合使用</h3><p><code>final</code> 经常与虚函数结合使用,确保某些虚函数在派生类中不能被重写。它提供了一种防止继承体系被错误扩展的机制,确保了接口或行为的一致性。</p><h4 id="示例:-2"><a href="#示例:-2" class="headerlink" title="示例:"></a>示例:</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> <span class="hljs-keyword">final</span> </span>{ <span class="hljs-comment">// 声明为 final,确保不能被重写</span><br> std::cout << <span class="hljs-string">"Base class display"</span> << std::endl;<br> }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">display</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{ <span class="hljs-comment">// 错误,不能重写 final 函数</span><br> std::cout << <span class="hljs-string">"Derived class display"</span> << std::endl;<br> }<br>};<br></code></pre></td></tr></table></figure><h3 id="4-final-的应用场景"><a href="#4-final-的应用场景" class="headerlink" title="4. final 的应用场景"></a>4. <code>final</code> 的应用场景</h3><h4 id="4-1-防止不必要的继承"><a href="#4-1-防止不必要的继承" class="headerlink" title="4.1 防止不必要的继承"></a>4.1 防止不必要的继承</h4><p>使用 <code>final</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Singleton</span> <span class="hljs-keyword">final</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">static</span> Singleton& <span class="hljs-title">getInstance</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">static</span> Singleton instance;<br> <span class="hljs-keyword">return</span> instance;<br> }<br><br><span class="hljs-keyword">private</span>:<br> <span class="hljs-built_in">Singleton</span>() = <span class="hljs-keyword">default</span>; <span class="hljs-comment">// 构造函数私有化</span><br> <span class="hljs-built_in">Singleton</span>(<span class="hljs-type">const</span> Singleton&) = <span class="hljs-keyword">delete</span>; <span class="hljs-comment">// 禁止拷贝构造</span><br> Singleton& <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> Singleton&) = <span class="hljs-keyword">delete</span>; <span class="hljs-comment">// 禁止赋值</span><br>};<br></code></pre></td></tr></table></figure><p>在这个例子中,<code>Singleton</code> 类被标记为 <code>final</code>,并且禁止拷贝和赋值操作,确保它不能被继承并且只能有一个实例。</p><h4 id="4-2-增强代码安全性"><a href="#4-2-增强代码安全性" class="headerlink" title="4.2 增强代码安全性"></a>4.2 增强代码安全性</h4><p>通过使用 <code>final</code> 关键字,程序员可以确保某些函数或类的设计意图不会被无意中改变。例如,如果一个基类的方法已经为所有派生类提供了适当的实现,而不希望它们被重写或修改,可以将该方法标记为 <code>final</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Base</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">doSomething</span><span class="hljs-params">()</span> <span class="hljs-keyword">final</span> </span>{<br> <span class="hljs-comment">// 执行固定的操作</span><br> }<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Derived</span> : <span class="hljs-keyword">public</span> Base {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-comment">// 试图重写 doSomething() 将导致编译错误</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">doSomething</span><span class="hljs-params">()</span> <span class="hljs-keyword">override</span> </span>{ <span class="hljs-comment">// 错误:doSomething() 是 final,不能被重写</span><br> <span class="hljs-comment">// 编译错误</span><br> }<br>};<br></code></pre></td></tr></table></figure><p>通过使用 <code>final</code>,你可以明确控制类的继承结构以及方法的重写行为,从而避免程序中不希望发生的修改。</p><h3 id="5-final-与编译优化"><a href="#5-final-与编译优化" class="headerlink" title="5. final 与编译优化"></a>5. <code>final</code> 与编译优化</h3><p>在某些情况下,<code>final</code> 可以帮助编译器优化代码。比如,当一个类被标记为 <code>final</code> 时,编译器就知道该类不能再被继承,从而可能对该类的虚函数进行更高效的内联处理。编译器可以更好地进行<strong>静态分析</strong>,因为它知道这个类的继承结构是固定的。</p><h3 id="6-总结"><a href="#6-总结" class="headerlink" title="6. 总结"></a>6. 总结</h3><p><code>final</code> 是 C++11 中引入的关键字,主要用于:</p><ol><li><strong>防止类被继承</strong>:在类声明中使用 <code>final</code>,阻止该类被进一步继承。</li><li><strong>防止函数被重写</strong>:在虚函数声明中使用 <code>final</code>,阻止派生类重写该函数。</li></ol><p>使用 <code>final</code> 提高了代码的可维护性和安全性,帮助开发人员明确限制某些类或方法的继承或重写行为,从而避免不必要的扩展或修改。</p><h2 id="Concepts"><a href="#Concepts" class="headerlink" title="Concepts"></a>Concepts</h2><p>在 C++ 中,<strong>Concepts</strong> 和 <strong>requires</strong> 是 C++20 引入的新特性,它们是用于约束模板类型的工具,旨在提高模板编程的可读性和可维护性。这些特性帮助程序员指定模板参数的要求,从而确保模板类型满足一定的条件。通过 Concepts,程序员可以在编译时进行类型检查,确保模板的类型参数符合预期的行为。</p><h3 id="1-Concepts:概念"><a href="#1-Concepts:概念" class="headerlink" title="1. Concepts:概念"></a>1. Concepts:概念</h3><p>Concept(概念)是对类型要求的命名约定,用来描述一种类型的“能力”或“特性”。概念可以定义模板参数应该具备哪些操作、成员或属性,而 C++ 编译器可以根据这些要求进行类型检查。</p><p>Concept的定义使用 <code>concept</code> 关键字。其作用类似于类型约束,目的是使得模板代码更加类型安全,并且可以提高代码的可读性和可维护性。Concept 允许编译器在模板实例化时检查类型是否符合特定的要求,从而避免在编译时出现不可预测的错误。</p><h4 id="Concept的定义语法:"><a href="#Concept的定义语法:" class="headerlink" title="Concept的定义语法:"></a>Concept的定义语法:</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">typename</span> T><br><span class="hljs-keyword">concept</span> ConceptName = <condition>;<br></code></pre></td></tr></table></figure><p>其中,<code>ConceptName</code> 是概念的名称,<code><condition></code> 是对类型 <code>T</code> 的约束条件,通常是一些类型特征或者操作。</p><h4 id="示例:自定义-Concept"><a href="#示例:自定义-Concept" class="headerlink" title="示例:自定义 Concept"></a>示例:自定义 Concept</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><span class="line">19</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><type_traits></span></span><br><br><span class="hljs-comment">// 定义一个Concept,要求T类型必须是可加的(支持+操作符)</span><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">concept</span> Addable = <span class="hljs-built_in">requires</span>(T a, T b) {<br> { a + b } -> std::same_as<T>; <span class="hljs-comment">// 表示a和b可以相加,并且返回值类型是T</span><br>};<br><br><span class="hljs-comment">// 使用Concept约束模板参数</span><br><span class="hljs-keyword">template</span> <Addable T><br><span class="hljs-function">T <span class="hljs-title">add</span><span class="hljs-params">(T a, T b)</span> </span>{<br> <span class="hljs-keyword">return</span> a + b;<br>}<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> std::cout << <span class="hljs-built_in">add</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>) << std::endl; <span class="hljs-comment">// 可行,int是可加的</span><br> <span class="hljs-comment">// std::cout << add("Hello", "World") << std::endl; // 错误,char*不支持+操作符</span><br>}<br></code></pre></td></tr></table></figure><p>在这个例子中,我们定义了一个名为 <code>Addable</code> 的概念,它要求类型 <code>T</code> 必须支持加法运算并且返回值类型是 <code>T</code>。在 <code>add</code> 函数模板中,只有满足 <code>Addable</code> 概念的类型 <code>T</code> 才能被使用。</p><h3 id="2-requires:要求表达式"><a href="#2-requires:要求表达式" class="headerlink" title="2. requires:要求表达式"></a>2. <code>requires</code>:要求表达式</h3><p><code>requires</code> 是 C++20 中用于在模板参数中指定约束条件的关键字。它可以用于声明和表达某些条件,限制模板的使用。<code>requires</code> 可以单独使用,也可以与 Concepts 一起使用。</p><p><code>requires</code> 表达式的作用是检查模板参数是否满足某些特定的操作或条件,若条件不满足,编译器会报错。</p><h4 id="requires语法"><a href="#requires语法" class="headerlink" title="requires语法"></a><code>requires</code>语法</h4><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">requires</span> <condition><br></code></pre></td></tr></table></figure><p>在 C++20 中,<code>requires</code> 关键字通常与概念(Concepts)结合使用,用来指定模板的约束条件。例如:</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-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">requires</span> std::is_integral_v<T> <span class="hljs-comment">// T必须是整型</span><br><span class="hljs-function">T <span class="hljs-title">add</span><span class="hljs-params">(T a, T b)</span> </span>{<br> <span class="hljs-keyword">return</span> a + b;<br>}<br></code></pre></td></tr></table></figure><h4 id="requires-用于模板参数"><a href="#requires-用于模板参数" class="headerlink" title="requires 用于模板参数"></a><code>requires</code> 用于模板参数</h4><p><code>requires</code> 关键字可以直接用来约束模板参数,表示该模板只适用于满足某些要求的类型。以下是一个带有 <code>requires</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-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-function"><span class="hljs-keyword">requires</span> <span class="hljs-title">requires</span><span class="hljs-params">(T a, T b)</span> </span>{ a + b; } <span class="hljs-comment">// T支持加法操作</span><br><span class="hljs-function">T <span class="hljs-title">add</span><span class="hljs-params">(T a, T b)</span> </span>{<br> <span class="hljs-keyword">return</span> a + b;<br>}<br></code></pre></td></tr></table></figure><p>上面的代码中,<code>requires</code> 表达式检查 <code>T</code> 类型是否可以进行加法操作。如果类型不满足这个要求,编译器将无法实例化 <code>add</code> 函数。</p><h4 id="requires-表达式与-concept-的结合"><a href="#requires-表达式与-concept-的结合" class="headerlink" title="requires 表达式与 concept 的结合"></a><code>requires</code> 表达式与 <code>concept</code> 的结合</h4><p>Concept 是一种可重用的约束条件,可以通过 <code>requires</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">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">concept</span> Addable = <span class="hljs-built_in">requires</span>(T a, T b) { a + b; };<br><br><span class="hljs-keyword">template</span> <Addable T><br><span class="hljs-function">T <span class="hljs-title">add</span><span class="hljs-params">(T a, T b)</span> </span>{<br> <span class="hljs-keyword">return</span> a + b;<br>}<br></code></pre></td></tr></table></figure><h3 id="3-requires-子句与-SFINAE"><a href="#3-requires-子句与-SFINAE" class="headerlink" title="3. requires 子句与 SFINAE"></a>3. <code>requires</code> 子句与 SFINAE</h3><p><code>requires</code> 还可以用于指定 SFINAE(Substitution Failure Is Not An Error,替代失败不是错误)条件。通常在模板特化或函数重载时,使用 <code>requires</code> 可以根据模板参数类型的特性进行选择。</p><h4 id="示例:SFINAE-与-requires"><a href="#示例:SFINAE-与-requires" class="headerlink" title="示例:SFINAE 与 requires"></a>示例:SFINAE 与 <code>requires</code></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><span class="line">19</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><type_traits></span></span><br><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">requires</span> std::is_integral_v<T> <span class="hljs-comment">// T必须是整型</span><br><span class="hljs-function">T <span class="hljs-title">multiply</span><span class="hljs-params">(T a, T b)</span> </span>{<br> <span class="hljs-keyword">return</span> a * b;<br>}<br><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">requires</span> std::is_floating_point_v<T> <span class="hljs-comment">// T必须是浮点型</span><br><span class="hljs-function">T <span class="hljs-title">multiply</span><span class="hljs-params">(T a, T b)</span> </span>{<br> <span class="hljs-keyword">return</span> a * b;<br>}<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> std::cout << <span class="hljs-built_in">multiply</span>(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>) << std::endl; <span class="hljs-comment">// 整型</span><br> std::cout << <span class="hljs-built_in">multiply</span>(<span class="hljs-number">3.5</span>, <span class="hljs-number">2.1</span>) << std::endl; <span class="hljs-comment">// 浮点型</span><br>}<br></code></pre></td></tr></table></figure><p>这里的 <code>requires</code> 用于函数模板的重载,使得 <code>multiply</code> 函数能够根据传入的类型选择正确的重载版本。</p><h3 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h3><ul><li><strong>Concepts</strong> 提供了一种声明类型要求的方式,允许对模板参数进行约束,使得模板编程更加安全和可读。</li><li><strong><code>requires</code></strong> 是用于定义约束条件的关键字,可以与 Concept 一起使用,或单独在模板函数中进行条件检查。</li><li><code>requires</code> 关键字和 Concepts 的结合能在编译时有效地过滤掉不符合要求的类型,提高了代码的可维护性和类型安全性。</li></ul><p>通过 Concept 和 <code>requires</code>,C++ 模板编程可以更加灵活、清晰和精确地表达类型要求,减少了由于模板不合规类型导致的编译时错误。</p>]]></content>
<categories>
<category>C++</category>
<category>CS106L</category>
</categories>
<tags>
<tag>继承</tag>
<tag>模板类</tag>
</tags>
</entry>
</search>