-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal-search.xml
334 lines (160 loc) · 308 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>第11章 关联容器</title>
<link href="/2023/10/01/Ch11-%E5%85%B3%E8%81%94%E5%AE%B9%E5%99%A8/"/>
<url>/2023/10/01/Ch11-%E5%85%B3%E8%81%94%E5%AE%B9%E5%99%A8/</url>
<content type="html"><![CDATA[<h1 id="第11章-关联容器">第11章 关联容器</h1><h2 id="概述">1.概述</h2><p>关联容器支持高效的关键字查找和访问。两个主要的关联容器类型是<code>map</code>和<code>set</code>。</p><p>标准库提供 8 个关联容器,如表 11.1 所示。这 8个容器间的不同体现在三个维度上:(1)或者是一个 set, 或者是一个map;(2)或者要求不重复的关键字,或者允许重复关键字;(3)按顺序保存元素,或无序保存。</p><p><img src="/2023/10/01/Ch11-%E5%85%B3%E8%81%94%E5%AE%B9%E5%99%A8/1696160599052.png"></p><h2 id="操作">2. 操作</h2><p>当定义一个 map 时,必须既指明关键字类型又指明值类型;而定义一个set时,只需指明关键字类型,因为 set 中没有值 。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 列表初始化</span><br>set<string> exclude = {<span class="hljs-string">"the"</span>, <span class="hljs-string">"and"</span>, <span class="hljs-string">"an"</span>};<br>map<string, string> authors = { {<span class="hljs-string">"Joyce"</span>, <span class="hljs-string">"James"</span>}, {<span class="hljs-string">"Austen"</span>, <span class="hljs-string">"Jane"</span>}, {<span class="hljs-string">"Dickens"</span>, <span class="hljs-string">"Charles"</span>} };<br></code></pre></td></tr></table></figure><p>map和set类型都支持begin和end操作,可以使用这些函数获取迭代器。</p><blockquote><p>map和set的关键字是const的,不可以修改。</p></blockquote><p>关联容器基于<code>pair</code>的标准库类型,一个 pair保存两个数据成员。类似容器,pair 是一个用来生成特定类型的模板。</p><p><img src="/2023/10/01/Ch11-%E5%85%B3%E8%81%94%E5%AE%B9%E5%99%A8/1696177468915.png"></p><h4 id="添加元素"><strong>添加元素</strong></h4><p>对一个 map 进行 insert 操作时,必须记住元素类型是 pair 。</p><p><img src="/2023/10/01/Ch11-%E5%85%B3%E8%81%94%E5%AE%B9%E5%99%A8/1696170073458.png"></p><h4 id="删除元素"><strong>删除元素</strong></h4><p>关联容器定义了三个版本的 erase, 如表 11.5 所示。</p><p><img src="/2023/10/01/Ch11-%E5%85%B3%E8%81%94%E5%AE%B9%E5%99%A8/1696170264584.png"></p><p><strong>map的下标操作</strong></p><p><img src="/2023/10/01/Ch11-%E5%85%B3%E8%81%94%E5%AE%B9%E5%99%A8/1696170442916.png"></p><blockquote><p>对一个 map 使用下标操作,其行为与数组或 vector上的下标操作很不相同:使用一个不在容器中的关键字作为下标,会<strong>添加</strong>一个具有此关键字的元素到map中。</p></blockquote><h4 id="访问元素"><strong>访问元素</strong></h4><p><img src="/2023/10/01/Ch11-%E5%85%B3%E8%81%94%E5%AE%B9%E5%99%A8/1696170935527.png"></p><h3 id="示例程序"><strong>示例程序</strong></h3><p>功能:给定一个 string, 将它转换为另一个string。程序的输入是两个文件。第一个文件保存的是一些规则,用来转换第二个文件中的文本。每条规则由两部分组成:一个可能出现在输入文件中的单词和一个用来替换它的短语。</p><p>函数 word_transform 管理整个过程。它接受两个ifstream参数:第一个参数应绑定到单词转属文件,第二个参数应绑定到我们要转换的文本文件。函数buildMap 会读取转换规则文件,并创建一个 map,用于保存每个单词到其转换内容的映射。函数 transform 接受一个 string,如果存在转换规则,返回转换后的内容。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">word_transform</span><span class="hljs-params">(ifstream &map_file, ifstream &input)</span> </span>{<br> <span class="hljs-keyword">auto</span> trans_map = <span class="hljs-built_in">buildMap</span>(map_file); <span class="hljs-comment">// 保存转换规则</span><br> string text;<br> <span class="hljs-keyword">while</span> (<span class="hljs-built_in">getline</span>(input,text)) { <span class="hljs-comment">// 读取一行输入</span><br> <span class="hljs-function">istringstream <span class="hljs-title">stream</span><span class="hljs-params">(text)</span></span>; <span class="hljs-comment">// 读取每个单词</span><br> string word;<br> <span class="hljs-type">bool</span> firstword = <span class="hljs-literal">true</span>; <span class="hljs-comment">// 控制是否打印空格</span><br> <span class="hljs-keyword">while</span> (stream >> word) {<br> <span class="hljs-keyword">if</span> (firstword)<br> firstword = <span class="hljs-literal">false</span>;<br> <span class="hljs-keyword">else</span><br> cout<<<span class="hljs-string">" "</span>; <span class="hljs-comment">// 在单词间打印一个空格</span><br> <span class="hljs-comment">// transform返回它的第一个参数或其转换之后的形式</span><br> cout << <span class="hljs-built_in">transform</span>(word, trans_map);<br> }<br> cout << endl; <span class="hljs-comment">// 完成一行的转换</span><br> }<br>}<br></code></pre></td></tr></table></figure><p>函数首先调用 buildMap 来生成单词转换 map, 我们将它保存在 trans_map中。函数的剩余部分处理输入文件。while 循环用getline一行一行地读取输入文件。这样做的目的是使得输出中的换行位置能和输入文件中一样。</p><p><strong>建立转换映射</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-function">map<string, string> <span class="hljs-title">buildMap</span><span class="hljs-params">(ifstream &map_file)</span> </span>{<br> map<string, string> trans_map; <br> string key;<br> string value;<br> <span class="hljs-comment">// 读取第一个单词存入key中,行中剩余内容存入value</span><br> <span class="hljs-keyword">while</span> (map_file >> key && <span class="hljs-built_in">getline</span>(map_file, value))<br> <span class="hljs-keyword">if</span> (value.<span class="hljs-built_in">size</span>() > <span class="hljs-number">1</span>) <span class="hljs-comment">// 检查是否有转换规则</span><br> trans_map[key] = value.<span class="hljs-built_in">substr</span>(<span class="hljs-number">1</span>); <span class="hljs-comment">// 跳过前导空格</span><br> <span class="hljs-keyword">else</span><br> <span class="hljs-keyword">throw</span> <span class="hljs-built_in">runtime_error</span>(<span class="hljs-string">"no rule for "</span> + key);<br> <span class="hljs-keyword">return</span> trans_map;<br>}<br></code></pre></td></tr></table></figure><p><strong>生成转换文本</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">const</span> string & <span class="hljs-title">transform</span><span class="hljs-params">(<span class="hljs-type">const</span> string &s, <span class="hljs-type">const</span> map<string, string> &m)</span> </span>{<br> <span class="hljs-keyword">auto</span> map_it = m.<span class="hljs-built_in">find</span>(s);<br> <span class="hljs-keyword">if</span> (map_it != m.<span class="hljs-built_in">cend</span>())<br> <span class="hljs-keyword">return</span> map_it->second;<br> <span class="hljs-keyword">else</span><br> <span class="hljs-keyword">return</span> s;<br>}<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>AFIRM: Adaptive forwarding based link recovery for mobility support in NDN/IoT networks</title>
<link href="/2023/09/07/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/2018_AFIRM/"/>
<url>/2023/09/07/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/2018_AFIRM/</url>
<content type="html"><![CDATA[<h2 style="color: #1B5E20"><span style="background-color: #f1f8e9">💡 Meta Data</span></h2><table><tr><th style="background-color: #dbeedd">Title</th><td style="background-color: #dbeedd">AFIRM: Adaptive forwarding based link recovery for mobility support inNDN/IoT networks</td></tr><tr><th style="background-color: #f3faf4">Journal</th><td style="background-color: #f3faf4">Future Generation Computer Systems</td></tr><tr><th style="background-color: #dbeedd">Authors</th><td style="background-color: #dbeedd">Maroua Meddeb; Amine Dhraief; Abdelfettah Belghith; Thierry Monteil;Khalil Drira; Sofien Gannouni</td></tr><tr><th style="background-color: #f3faf4">Pub. date</th><td style="background-color: #f3faf4">2018-10-01</td></tr><tr><th style="background-color: #dbeedd">期刊标签</th><td style="background-color: #dbeedd"><span style="color: rgb(187, 91, 17)">CCF C</span></td></tr><tr><th style="background-color: #f3faf4">DOI</th><td style="background-color: #f3faf4"><a href="https://doi.org/10.1016/j.future.2018.04.087" rel="noopener noreferrer nofollow">10.1016/j.future.2018.04.087</a></td></tr><tr><th style="background-color: #dbeedd">附件</th><td style="background-color: #dbeedd"><a href="zotero://open-pdf/0_X3ZVFIKW" rel="noopener noreferrer nofollow">🔗2018_Meddeb_AFIRM.pdf</a></td></tr></table><p>两种移动性:</p><ul><li>consumer mobility:只会引起数据包的丢失(类似于反向路径失效)</li><li>producer mobility:会导致兴趣包的迷路(无法获得数据)</li></ul><p>解决producer mobility的四类方法:</p><ul><li><strong>location-based</strong>:依赖于额外的实体来维护内容名称与其位置之间的映射</li><li><strong>locator/identifierseparation</strong>:在命名方案中添加了定位器前缀</li><li><strong>triangular</strong>:更新producer的旧位置到新位置的路径</li><li><strong>routing-based</strong>:使用动态路由算法,将请求路由到正确的新位置</li></ul><blockquote><p>triangular和routing-based的不同:前者由移动后的producer触发请求,以更新新位置至原位置的路径(即节点的FIB);后者则更新新位置至consumer的一条最优新路径。</p><p>并且,triangular一般只支持intra-AS的小范围移动,routing-based则都可以支持。</p></blockquote><p><img src="/2023/09/07/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/2018_AFIRM/1693983884834.png"></p><h2 id="研究内容"><span style="color: #2E7D32"><span style="background-color: #f1f8e9">📊 研究内容</span></span></h2><h3 id="核心">核心:</h3><p><strong>旨在通过在检测到移动后更新转发信息来恢复请求路径。它的目标是减少数据包丢失与一个非昂贵的解决方案方面的信令通信开销。</strong>(针对的是producermobility)</p><p>由两个阶段完成:</p><h3 id="fibs-construction">FIBs construction</h3><p>利用泛洪探索网络,并记录在FIB中,同时包括Data包的传入传出接口。迭代删除完整名称的最后一个组件,以添加新条目,删除可从多个端口满足的子前缀条目,如图中的,。</p><figure><img src="/2023/09/07/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/2018_AFIRM/FIBs.png" alt="FIBs construction过程"><figcaption aria-hidden="true">FIBs construction过程</figcaption></figure><h3 id="link-recovery">Link recovery</h3><p>使用<strong>keep-alive movement detectionmethod</strong>,网关定期发送ping消息,以检查传感器是否仍然连接。每个网关处理其连接的传感器的移动性。当检测到连接失效时,旧位置、新位置以及其它节点均需做出反应。由带有flag=0/1的RECOVERY包指示,分别删除旧位置的FIB条目,为新位置添加FIB条目。如下图所示:</p><figure><img src="/2023/09/07/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/2018_AFIRM/链路恢复.png" alt="链路恢复"><figcaption aria-hidden="true">链路恢复</figcaption></figure><h2 id="研究结论"><span style="color: #4A148C"><span style="background-color: #f5f5f5">🚩 研究结论</span></span></h2><p>AFIRM是一种用于NDN架构的<strong>内容驱动、自适应转发和完全分布式</strong>的算法。其可以在支持生产者移动性的同时,显著<strong>降低信令成本</strong>。</p><p>AFIRM的成本低于COBRA。这两种解决方案删除错误转发信息的成本几乎相同,但要添加新的转发信息,<strong>AFIRM会在上游进行,而COBRA会在下游添加信息</strong>。通过向消费者靠拢,中间节点给出了更精确的转发决策,从而在FIB中缺乏信息的情况下,可以避免洪泛。</p><h2 id="感想-疑问"><span style="color: #006064"><span style="background-color: #e0f7fa">❓ 感想 & 疑问</span></span></h2><p>网关定期与连接的传感器发送ping以确认连接,这引起的开销是可接受的么?</p>]]></content>
<categories>
<category>论文阅读</category>
</categories>
<tags>
<tag>NDN</tag>
<tag>Ad hoc</tag>
<tag>转发</tag>
<tag>可靠传输</tag>
</tags>
</entry>
<entry>
<title>CCLF(基于内容连接度和位置感知的自适应转发)</title>
<link href="/2023/09/06/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/20230415-CCLF/"/>
<url>/2023/09/06/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/20230415-CCLF/</url>
<content type="html"><![CDATA[<h2 id="information">Information:</h2><ol type="1"><li>Title: Leveraging Content Connectivity and Location Awareness forAdaptive Forwarding in NDN-based Mobile Ad Hoc Networks(基于内容连接性和位置感知的NDN移动自组织网络自适应转发技术)</li><li>Authors: Muktadir Chowdhury, Junaid Ahmed Khan, and Lan Wang</li><li>Affiliation: University of Memphis (孟菲斯大学)</li><li>Keywords: Named Data Networking, MANET, routing, forwarding(命名数据网络,移动自组织网络,路由,转发)</li><li>Urls: Paper link: https://dl.acm.org/doi/10.1145/3405656.3418713,Github: None.</li></ol><h2 id="summary">Summary:</h2><ul><li>(1):本文研究了移动自组织网络(MANETs)中的数据转发问题,提出了基于命名数据网络(NDN)的内容连接性和位置感知的自适应转发策略。</li><li>(2):传统的MANET转发策略在高度动态的网络中很难有效转发数据,并且在存在连接变化时通常需要大量控制报文来获取拓扑信息。本文提出的方法是<strong>基于内容连接性和位置信息</strong>对数据进行转发,避免了控制报文的开销,并且通过在网络层维护转发状态和期望转发反馈来加强转发策略的适应性。</li><li>(3): 文中提出了一个自适应转发策略——Content Connectivity andLocation-Aware Forwarding(CCLF),在NDN网络中进行转发时基于内容连接性和位置信息,避免了控制报文和数据报文之间的不一致,并且通过减少无效广播来提升转发性能。此外还提出了适用于MANET网络的链路自适应层(A-LAL)以增强链路的灵活性和可靠性。</li><li>(4):研究结果表明,CCLF能够有效降低数据转发的负载并保证数据的可靠获取,在信息中心化车联网方案中也比其他转发策略表现更为优异,验证了其在移动自组织网络中的有效性。</li></ul><h2 id="method">Method:</h2><p>CCLF广播NDN数据包,让每个节点根据每个前缀的内容连接度和任何可用的地理位置信息独立地决定是否转发数据包;此外,它采用密度感知的抑制机制来减少不必要的包传输;此外,为adhoc链路开发了链路适配层,以弥合CCLF与底层链路能力之间的差距。</p><h3 id="内容连接度">内容连接度</h3><p>内容连接度是<strong>细粒度</strong>的,区分于以往基于节点的整体兴趣满意度来量化节点的连通性,CCLF对<strong>每一个名称前缀</strong>分别计算一个内容连通性分数(ContentConnectivity Score, CCS)。</p><blockquote><p>This fine-grained measure of forwarding performance helps the networklayer make more informed decisions when forwarding Interests to retrievedata from different producers.</p></blockquote><p>CCS计算公式: <span class="math display">\[CCS_j = \frac{D_j+\sum_{i\in Desc(j)}D_i}{I_j+\sum_{i\in Desc(j)}I_i}\]</span> <span class="math inline">\(D_j,I_j\)</span>分别表示对应前缀<span class="math inline">\(j\)</span>的Data、Interest个数,<span class="math inline">\(Desc(j)\)</span>表示前缀<span class="math inline">\(j\)</span>的子类的集合。</p><p>CCS周期性更新,根据指数加权移动平均(EWMA): <span class="math display">\[\widehat{CCS}_{i,N}=\alpha \cdot CCS_{i,N}+(1-\alpha) \cdot\widehat{CCS}_{i,N-1}\]</span></p><h3 id="地理位置">地理位置</h3><p>地理位置在CCLF中作为<strong>可选项</strong>使用,Consumer可以将数据的位置附加到Interest中(<em>通过NDNLP header</em>),节点接收到此类Interest后,计算位置分数(LocationScore,LS): <span class="math display">\[LS = 1- \frac{Dist(n,d)}{max(Dist(n,d),Dist(p,d))}\]</span> <span class="math inline">\(p,n,d\)</span>分别代表上一跳、当前、目的节点。</p><p>CCS和位置信息通过新设定的C-L tree结构来储存、更新与检索: <img src="/2023/09/06/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/20230415-CCLF/1681475432881.png" alt="C-L tree"></p><h3 id="转发计时器">转发计时器</h3><p>和其他策略类似,当某个节点通过其广播链路发送Interest后,传输范围内的其他节点分布式决定<strong>是否以及何时</strong>转发该兴趣。首先对CCS和LS加权: <span class="math display">\[w = \beta \cdot \widehat{CCS} + (1-\beta) \cdot LS\]</span> 再取倒数计算基本的定时器时间t: <span class="math display">\[t= \begin{cases}\min \left(\frac{1}{w}, T\right), & \text { if }w>0 \\ T, & \text { if } w=0\end{cases}\]</span> T为t的一个上界,最终定时器的值在<span class="math inline">\(0.5t-1.5t\)</span>之间随机选取。</p><h3 id="密度感知的转发抑制">密度感知的转发抑制</h3><p>当节点在某个Interest的转发计时器到期之前收到相同Interest时,它使用一个<strong>与其邻居数量成正比的抑制概率</strong>来决定是否取消自己的兴趣转发:<span class="math display">\[p=min(K \cdot n, 1)\]</span><em>Data包也采用相同的概率抑制方案,不过在Data包转发中,节点的Timer值是相同的。</em></p><h3 id="ad-hoc链路适配层">AD-HOC链路适配层</h3><p>开发了新的一个Ad-hoc Link AdaptationLayer(A-LAL)层面,为CCLF提供一些重要操作:</p><figure><img src="/2023/09/06/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/20230415-CCLF/1681479229022.png" alt="A-LAL"><figcaption aria-hidden="true">A-LAL</figcaption></figure><ul><li>在Interes包中添加NDNLP头(previous hop location和data location)</li><li>利用MAC层的信息来跟踪邻居节点的数量(Neighbor-List),以支持转发抑制概率的计算</li><li>没有邻居节点时,存储包(PacketQueue)直至发现邻居</li></ul><p>最后,总的转发流程伪代码如下: <img src="/2023/09/06/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/20230415-CCLF/1681480197163.png" alt="Interest转发"></p><figure><img src="/2023/09/06/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/20230415-CCLF/1681480213422.png" alt="Data包转发"><figcaption aria-hidden="true">Data包转发</figcaption></figure><h2 id="conclusion">Conclusion:</h2><ul><li>(1):本文提出了一种基于内容连接性和位置感知的自适应转发策略,CCLF,用于解决移动自组织网络(MANETs)中的数据转发问题,具有实际应用价值。</li><li>(2):创新点:本文提出的CCLF算法在转发决策中加入了内容连接性和位置信息,可以有效地避免控制报文开销和数据报文不一致问题,并减少无效广播,具有较高的性能表现。但是本文研究还存在一定的局限性,需要进一步拓展研究范围,对算法的参数调整及不同情境的适用性进行更深入的研究,并进行更全面的评估测试。</li><li>性能:CCLF相较于传统的转发策略,在满足数据转发的负载要求的同时减少了数据转发的开销,具有较高的性能表现。</li><li>工作量:本文的研究范围相对狭窄,需要进一步的实验和评估工作以证明其普适性和适用性。</li></ul>]]></content>
<categories>
<category>论文阅读</category>
</categories>
<tags>
<tag>NDN</tag>
<tag>Ad hoc</tag>
<tag>转发</tag>
<tag>地理位置</tag>
<tag>可靠性</tag>
</tags>
</entry>
<entry>
<title>第10章 泛型算法</title>
<link href="/2023/08/31/Ch10-%E6%B3%9B%E5%9E%8B%E7%AE%97%E6%B3%95/"/>
<url>/2023/08/31/Ch10-%E6%B3%9B%E5%9E%8B%E7%AE%97%E6%B3%95/</url>
<content type="html"><![CDATA[<h1 id="第10章-泛型算法">第10章 泛型算法</h1><p>”算法“:实现了一些经典算法的公共接口,如排序和搜索。</p><p>”泛型”:可以用于不同类型的元素和多种容器类型。</p><h2 id="初识">1. 初识</h2><p>除了少数例外,标准库算法都对一个范围内的元素进行操作,称其为“输入范围”。接受输入范围的算法总是使用前两个参数来表示此范围,两个参数分别是指向要处理的第一个元素和尾元素之后位置的迭代器。</p><blockquote><p>算法永远不会改变底层容器的大小。算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作。</p></blockquote><h3 id="只读算法">1.1 只读算法</h3><p>只读取其输入范围内的元素,而不改变元素。</p><p><code>accumulate(vec.begin(), vec.end(), 0)</code>:求和函数</p><blockquote><p>accumulate的第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型。</p></blockquote><p><code>equal(vec1.cbegin(), vec1.cend(), vec2.cbegin())</code>:相等函数</p><blockquote><p>equal假定第二个序列不比第一个序列短。<strong>那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列不短于第一个序列</strong></p></blockquote><h3 id="写容器元素的算法">1.2 写容器元素的算法</h3><p><strong>算法不检查写操作</strong></p><p>一些算法将新值赋予序列中的元素。当我们使用这类算法时,必须注意确保序列原大小不小于我们要求算法写入的元素数目。因为算法本身并不检查写操作,且其自身不可能改变容器的大小。</p><p><code>fill_n(dest, n, val)</code>函数接受一个迭代器、一个计数值和一个值。它将给定值赋予迭代器指向的元素开始的指定个元素。其假定写入指定个元素是安全的:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++">vector<<span class="hljs-type">int</span>> vec; <span class="hljs-comment">// 空向量</span><br><span class="hljs-comment">// 错误,修改vec中的10个不存在的元素</span><br><span class="hljs-built_in">fill_n</span>(vec.<span class="hljs-built_in">begin</span>(), vec.<span class="hljs-built_in">size</span>(), <span class="hljs-number">0</span>);<br></code></pre></td></tr></table></figure><h5 id="插入迭代器"><strong>插入迭代器</strong></h5><p>一种保证算法有足够元素空间来容纳输出数据的方法是使用插入迭代器。插入迭代器是一种向容器中添加元素的迭代器。通常情况,当我们通过一个迭代器向容器元素赋值时,值被赋予迭代器指向的元素。而当我们通过一个插入迭代器赋值时,一个与赋值号右侧值相等的元素被添加到容器中。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++">vector<<span class="hljs-type">int</span>> vec; <br><span class="hljs-keyword">auto</span> it = <span class="hljs-built_in">back_inserter</span>(vec); <br>*it = <span class="hljs-number">42</span>; <span class="hljs-comment">// vec中现在有一个元素,值为42</span><br><span class="hljs-built_in">fill_n</span>(<span class="hljs-built_in">back_inserter</span>(vec), <span class="hljs-number">10</span>, <span class="hljs-number">0</span>); <span class="hljs-comment">// 添加10个0到vec的末尾</span><br></code></pre></td></tr></table></figure><h5 id="拷贝算法"><strong>拷贝算法</strong></h5><p>拷贝算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法。其接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置。<strong>传递给copy的目的序列至少要包含与输入序列一样多的元素</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> a1[] = {<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>};<br><span class="hljs-type">int</span> a2[<span class="hljs-built_in">sizeof</span>(a1)/<span class="hljs-built_in">sizeof</span>(*a1)]; <span class="hljs-comment">// a2与a1大小一样</span><br><span class="hljs-keyword">auto</span> ret = <span class="hljs-built_in">copy</span>(<span class="hljs-built_in">begin</span>(a1), <span class="hljs-built_in">end</span>(a1), a2); <span class="hljs-comment">// 把a1的内容拷贝给a2</span><br></code></pre></td></tr></table></figure><h3 id="重排容器元素的算法">1.3 重排容器元素的算法</h3><p>某些算法会重排容器中元素的顺序,一个明显的例子是sort。假定己有一个vector, 保存了多个单词,现化简这个vector,使得每个单词只出现一次。</p><p>输入:<code>the quick red fox jumps over the slow red turtle</code></p><p>目标输出:<code>fox jumps over quick red slow the turtle</code></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">elimDups</span><span class="hljs-params">(vector<string> &words)</span> </span>{<br> <span class="hljs-built_in">sort</span>(words.<span class="hljs-built_in">begin</span>(), words.<span class="hljs-built_in">end</span>());<br> <span class="hljs-keyword">auto</span> end_unique = <span class="hljs-built_in">unique</span>(words.<span class="hljs-built_in">begin</span>(), words.<span class="hljs-built_in">end</span>());<br> words.<span class="hljs-built_in">erase</span>(end_unique, words.<span class="hljs-built_in">end</span>());<br>}<br></code></pre></td></tr></table></figure><h5 id="消除重复单词">消除重复单词</h5><p>为了消除重复单词,首先将 vector 排序,使得重复的单词都相邻出现。完成sort后,words的顺序如下:</p><p><code>fox jumps over quick red red slow the the turtle</code></p><h6 id="使用unique">使用unique</h6><p>unique算法重排输入序列,将相邻的重复项“消除”,并<strong>返回一个指向不重复值范围末尾的迭代器</strong>。调用unique后,vector将变成:</p><p><code>fox jumps over quick red slow the turtle ??? ???</code></p><p>words的大小并未改变,它仍有10个元素。但这些元素的顺序改变了——相邻的重复元素被<strong>“删除”</strong>了。我们将删除打引号是因为unique并不真的删除任何元素,<strong>它只是覆盖相邻的重复元素</strong>,使得不重复元素出现在序列开始部分。unique返回的迭代器指向最后一个不重复元素之后的位置。<strong>此位置之后的元素仍然存在,但我们不知道它们的值是什么</strong>。</p><blockquote><p>标准库算法对迭代器而不是容器进行操作。因此,算法不能(直接)添加或删除元素。</p></blockquote><h6 id="使用容器操作删除元素">使用容器操作删除元素</h6><p>为了真正地删除无用元素,必须使用容器操作。使用erase删除从end_unique开始直至words末尾的范围内的所有元素。值得注意的是,即使words 中没有重复单词,这样调用 erase 也是安全的。</p><h2 id="定制操作">2. 定制操作</h2><p>现需要单词按其长度排序,大小相同的再按字典序排序,为此使用<code>sort</code>的第二个版本,其接受第三个参数,是一个<strong>谓词</strong>。</p><p>谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法所使用的谓词分为两类:一元谓词(unarypredicate, 意味着它们只接受单一参数)和二元谓词(binary predicate,意味着它们有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换为谓词的参数类型。</p><p>接受一个二元谓词参数的 sort 版本用这个谓词代替v来比较元素。提供给sort的谓词必须满足将在 11.2.2节中所介绍的条件。当前,只需知道,此操作必须在输入序列中所有可能的元素值上定义一个一致的序。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">isShorter</span><span class="hljs-params">(<span class="hljs-type">const</span> string &s1, <span class="hljs-type">const</span> string &s2)</span> </span>{<br> <span class="hljs-keyword">return</span> s1.<span class="hljs-built_in">size</span>() < s2.<span class="hljs-built_in">size</span>();<br>}<br><span class="hljs-built_in">sort</span>(words.<span class="hljs-built_in">begin</span>(), words.<span class="hljs-built_in">end</span>(), isShorter);<br></code></pre></td></tr></table></figure><h3 id="lambda表达式">2.2 lambda表达式</h3><p>现出现一个新问题:求大于等于一个给定长度的单词有多少,并输出,将此函数命名为<code>biggies</code>。</p><p>为了解决此问题,只需要找到第一个大于给定长度的元素,可以使用<code>find_if</code>函数完成此功能,<code>find_if</code>接受一对迭代器以及一个一元谓词,其对输入序列中的每个元素调用这个谓词,但是只接受一个参数的函数无法完成比较给定string和一个长度的大小,为此引入<code>lambda</code>。</p><p>一个 lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda 具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。一个 lambda 表达式具有如下形式 :</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++">[capture list] (parameter list) -> <span class="hljs-keyword">return</span> type { function body }<br></code></pre></td></tr></table></figure><p>其中,capture list(捕获列表) 是一个 lambda所在函数中定义的局部变量的列表 (通常为空);return type、parameter和functionbody与任何普通函数一样,分别表示返回类型参数列表和函数体。但是,与普通函数不同,lambda必须使用尾置返回来指定返回类型。<em>可以忽略参数列表和返回类型</em>。</p><blockquote><p>如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void。</p></blockquote><p>本例中,lambda捕获sz,将string的大小与sz的值相比较:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++">[sz] (<span class="hljs-type">const</span> string &a) <br>{ <span class="hljs-keyword">return</span> a.<span class="hljs-built_in">size</span>() >= sz; };<br></code></pre></td></tr></table></figure><p>再调用<code>find_if</code>查找第一个长度大于等于sz的元素:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">auto</span> wc = <span class="hljs-built_in">find_if</span>(words.<span class="hljs-built_in">begin</span>(), words.<span class="hljs-built_in">end</span>(), [sz] (<span class="hljs-type">const</span> string &a) { <span class="hljs-keyword">return</span> a.<span class="hljs-built_in">size</span>() >= sz; });<br></code></pre></td></tr></table></figure><p>最后输出所有满足条件的元素:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++">for_each(wc, words.<span class="hljs-built_in">end</span>(), [](<span class="hljs-type">const</span> string &a) {cout<< s <<<span class="hljs-string">" "</span>; });<br></code></pre></td></tr></table></figure><p>最终,完整的biggies如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">biggies</span><span class="hljs-params">(vector<string> &words, vector<string>::size_type sz)</span> </span>{<br> <span class="hljs-built_in">elimDups</span>(words);<br> <span class="hljs-built_in">stable_sort</span>(words.<span class="hljs-built_in">begin</span>(), words.<span class="hljs-built_in">end</span>(), [] (<span class="hljs-type">const</span> string &a, <span class="hljs-type">const</span> string &b) { <span class="hljs-keyword">return</span> a.<span class="hljs-built_in">size</span>() < b.<span class="hljs-built_in">size</span>();});<br> <span class="hljs-comment">// 获取一个迭代器,指向第一个满足size()>=sz的元素</span><br> <span class="hljs-keyword">auto</span> wc = <span class="hljs-built_in">find_if</span>(words.<span class="hljs-built_in">begin</span>(), words.<span class="hljs-built_in">end</span>(), [sz] (<span class="hljs-type">const</span> string &a) { <span class="hljs-keyword">return</span> a.<span class="hljs-built_in">size</span>() >= sz; });<br> <span class="hljs-comment">// 计算满足size >= sz的元素的数目</span><br> <span class="hljs-keyword">auto</span> count = words.<span class="hljs-built_in">end</span>() - wc;<br> <span class="hljs-comment">// 打印长度大于等于给定值的单词</span><br> for_each(wc, words.<span class="hljs-built_in">end</span>(), [](<span class="hljs-type">const</span> string &s) {cout << s << <span class="hljs-string">" "</span>;});<br>}<br></code></pre></td></tr></table></figure><h3 id="lambda捕获和返回">2.3 lambda捕获和返回</h3><p>当定义一个 lambda 时,编译器生成一个与 lambda对应的新的(未命名的)类类型 。默认情况下,从 lambda生成的类都包含一个对应该 lambda所捕获的变量的数据成员。类似任何普通类的数据成员,lambda 的数据成员也在lambda 对象创建时被初始化 。</p><p>类似参数传递,变量的捕获方式也可以是<strong>值或引用</strong>。此外,还可以<strong>隐式捕获</strong>,即在捕获列表中写一个<code>&</code>或<code>=</code>,分别表示捕获引用或值捕获。</p><p>显示捕获和隐式捕获可以混合使用,但当两者混合时,捕获列表中的第一个元素必须为<code>&</code>或<code>=</code>,且显示捕获的变量必须使用与隐式捕获不同的方式。</p><blockquote><p>当以引用方式捕获一个变量时,必须保证在 lambda执行时变量是存在的。应当尽量保持 lambda 的变量捕获简单化。</p></blockquote><p>默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字<code>mutable</code>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">fcn</span><span class="hljs-params">()</span> </span>{<br> size <span class="hljs-type">_t</span> v1 = <span class="hljs-number">42</span>;<br> <span class="hljs-keyword">auto</span> f = [v1] () <span class="hljs-keyword">mutable</span> {<span class="hljs-keyword">return</span> ++v1; };<br> v1 = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">auto</span> j = <span class="hljs-built_in">f</span>(); <span class="hljs-comment">//j为43</span><br>}<br></code></pre></td></tr></table></figure><p>默认情况下,如果一个 lambda 体包含 return之外的任何语句,则编译器假定此 lambda 返回 void 。当需要为一个 lambda定义返回类型时,必须使用<strong>尾置返回类型</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++">[] (<span class="hljs-type">int</span> i) -> <span class="hljs-type">int</span> { <span class="hljs-keyword">if</span> (i><span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> i; <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> -i;}; <span class="hljs-comment">//指定返回类型为int</span><br></code></pre></td></tr></table></figure><h3 id="参数绑定">2.4 参数绑定</h3><p>回顾本节介绍lambda的原因,是因为当时向<code>find_if</code>函数传递的可调用对象必须接受单一参数,但是这又无法完成长度比较的目的。那为了使用函数替代lambda,就需要解决利用<code>bind</code>来进行参数绑定。调用<code>bind</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++"><span class="hljs-keyword">auto</span> newCallable = <span class="hljs-built_in">bind</span>(callable, arg_list);<br></code></pre></td></tr></table></figure><p>其中, newCallable本身是一个可调用对象,是一个逗号分隔的参数列表,对应给定的 callable 的参数。即,当我们调用newCallable 时,newCallable会调用 callable, 并传 递给它 arg_list中的参数。</p><p>arg_list中的参数可能包含形如<code>_n</code>的名字,其中 n是一个整数。这些参数是“占位符”,表示newCallable的参数,_1为newCallable的第一个参数,依此类推。例如:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// check6是一个可调用对象,接受一个string类型的参数</span><br><span class="hljs-comment">// 并用此string和6来调用check_size</span><br><span class="hljs-keyword">auto</span> check6 = <span class="hljs-built_in">bind</span>(check_size, _1, <span class="hljs-number">6</span>);<br>string s = <span class="hljs-string">"hello"</span>;<br><span class="hljs-type">bool</span> b1 = <span class="hljs-built_in">check6</span>(s); <span class="hljs-comment">// check6(s)会调用check_size(s,6)</span><br></code></pre></td></tr></table></figure><h2 id="再探迭代器">3. 再探迭代器</h2><p>除了为每个容器定义的迭代器之外,还有额外几种迭代器:</p><ul><li><strong>插入迭代器:</strong>这些迭代器被绑定到一个容器上,可用来向容器插入元素。</li><li><strong>流迭代器:</strong>这些迭代器被绑定到输入或输出流上,可用来遍历所关联的IO流。</li><li><strong>反向迭代器:</strong>这些迭代器向后移动。</li><li><strong>移动迭代器:</strong>移动迭代器中的元素。</li></ul><h3 id="插入迭代器-1">3.1 插入迭代器</h3><p>插入器有三种类型,差异在于元素插入的位置 :</p><ul><li>back_inserter:创建一个使用push_back的迭代器</li><li>front_inserter:创建一个使用push_front的迭代器</li><li>inserter:创建一个使用insert的迭代器</li></ul><h3 id="iostream迭代器">3.2 iostream迭代器</h3><p>虽然iostream类型不是容器,但标准库定义了可用于这些IO类型对象的迭代器。<code>istream_iterator</code>读取输入流,<code>ostream_iterator</code>向一个输出流写数据。</p><p><img src="/2023/08/31/Ch10-%E6%B3%9B%E5%9E%8B%E7%AE%97%E6%B3%95/1696148653825.png"></p><p><img src="/2023/08/31/Ch10-%E6%B3%9B%E5%9E%8B%E7%AE%97%E6%B3%95/1696148811219.png"></p><h3 id="反向迭代器">3.3 反向迭代器</h3><p>反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。对于反向迭代器,递增(以及递减)操作的含义会颠倒过来。递增一个反向迭代器(++it)会移动到前一个元素;递减一个迭代器(–it)会移动到下一个元素。</p><p><img src="/2023/08/31/Ch10-%E6%B3%9B%E5%9E%8B%E7%AE%97%E6%B3%95/1696149430124.png"></p><h2 id="泛型算法结构">4. 泛型算法结构</h2><p>算法所要求的迭代器操作可以分为5个迭代器类别:</p><p><img src="/2023/08/31/Ch10-%E6%B3%9B%E5%9E%8B%E7%AE%97%E6%B3%95/1696150409998.png"></p><p>大多数算法具有如下4种形式之一:</p><p>alg(beg, end, othrt args);</p><p>alg(beg, end, dest, other args);</p><p>alg(beg, end, beg2, other args);</p><p>alg(beg, end, beg2, end2, other args);</p><p>与其他容器不同,链表类型 list 和 forward_list定义了几个成员函数形式的算法。</p><p><img src="/2023/08/31/Ch10-%E6%B3%9B%E5%9E%8B%E7%AE%97%E6%B3%95/1696150737806.png"></p>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>第9章 顺序容器</title>
<link href="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/"/>
<url>/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/</url>
<content type="html"><![CDATA[<h1 id="第9章-顺序容器">第9章 顺序容器</h1><p>一个容器就是一些特定类型对象的集合。<strong>顺序容器</strong>为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。</p><h2 id="顺序容器概述">1 顺序容器概述</h2><p>标准库中的顺序容器如下表所示:</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693128973475.png"></p><blockquote><p>通常,使用<code>vector</code>是最好的选择,除非有很好的理由选择其他</p></blockquote><h2 id="容器库概览">2 容器库概览</h2><p>所有容器类型都提供以下操作:</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693130120281.png"></p><h3 id="迭代器">2.1 迭代器</h3><p>迭代器的范围是<strong>左闭右开区间</strong>,<code>[begin, end)</code>。对构成范围的迭代器的要求:</p><ul><li>它们指向同一个容器中的元素</li><li>end不在begin之前</li></ul><p><code>begin</code>和<code>end</code>有多个版本:带r的版本返回反向迭代器;以c开头的版本则返回const迭代器。</p><p>容器的定义和初始化方法:</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693140969388.png"></p><p>简而言之:</p><ul><li>列表初始化 <code>C c{a,b,c}</code>或者<code>C c={a,b,c}</code></li><li>拷贝初始化<code>C c1=c2</code>或者<code>C c1(c2)</code></li><li>大小与(可选的)元素初始值构造<code>C seq(n)</code>或者<code>C seq(n,t)</code></li></ul><p><strong>当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同</strong>;不过,当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了,并且新容器和原容器中的元素类型也可以不同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。</p><p><strong>只有顺序容器(除array外)的构造函数才接受大小参数,关联容器并不支持</strong>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++">list<string> authors = {<span class="hljs-string">"Mike"</span>,<span class="hljs-string">"Jack"</span>,<span class="hljs-string">"Austin"</span>};<br>vector<<span class="hljs-type">const</span> <span class="hljs-type">char</span>*> articles = {<span class="hljs-string">"a"</span>,<span class="hljs-string">"an"</span>,<span class="hljs-string">"the"</span>};<br><span class="hljs-function">list<string> <span class="hljs-title">list2</span><span class="hljs-params">(authors)</span></span>; <span class="hljs-comment">// 正确,类型匹配</span><br><span class="hljs-function">deque<string> <span class="hljs-title">authList</span><span class="hljs-params">(authors)</span></span>; <span class="hljs-comment">// 错误,容器类型不匹配</span><br><span class="hljs-function">vector<string> <span class="hljs-title">words</span><span class="hljs-params">(articles)</span></span>; <span class="hljs-comment">// 错误,元素类型不匹配</span><br><span class="hljs-function">forward_list<string> <span class="hljs-title">words</span><span class="hljs-params">(article.begin(), articles.end())</span></span>; <span class="hljs-comment">// 正确,可以将const char*转换成string</span><br></code></pre></td></tr></table></figure><p><strong>标准库array具有固定大小</strong>。为了使用array类型,必须同时指定元素类型和大小。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++">array<<span class="hljs-type">int</span>, 42><br>array<<span class="hljs-type">int</span>, 10>::size_type i; <span class="hljs-comment">// 正确</span><br>array<<span class="hljs-type">int</span>>::size_type j; <span class="hljs-comment">// 错误:array<int>不是一个类型</span><br></code></pre></td></tr></table></figure><h3 id="赋值和swap">2.5 赋值和swap</h3><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693142775334.png"></p><p><strong>与内置数组不同,标准库array类型允许赋值</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++">array<<span class="hljs-type">int</span>,5> a1 = {<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>};<br>array<<span class="hljs-type">int</span>,10> a2 = {<span class="hljs-number">0</span>}; <span class="hljs-comment">// 所有元素均为0</span><br>a1 = a2; <span class="hljs-comment">// 替换a1中的元素</span><br>a2 = {<span class="hljs-number">0</span>}; <span class="hljs-comment">// 错误,不能将一个花括号列表赋予数组</span><br></code></pre></td></tr></table></figure><blockquote><p>由于右边运算对象的大小可能与左边运算对象的大小不同,因此array类型不支持assign,也不允许用花括号包围的值列表进行赋值。</p></blockquote><p><strong>使用assign(仅顺序容器)</strong></p><p><code>assign</code>允许从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++">list<string> names;<br>vector<<span class="hljs-type">const</span> <span class="hljs-type">char</span>*> oldstyle;<br>names = oldstyle; <span class="hljs-comment">// 错误,类型不匹配</span><br><span class="hljs-comment">// 正确,可以将const char*转换成string</span><br>names.<span class="hljs-built_in">assign</span>(oldstyle.<span class="hljs-built_in">cbegin</span>(), oldstyle.<span class="hljs-built_in">cend</span>()); <br></code></pre></td></tr></table></figure><h5 id="使用swap">使用swap</h5><p><code>swap</code>操作交换两个相同类型容器的内容</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">vector<string> <span class="hljs-title">svec1</span><span class="hljs-params">(<span class="hljs-number">10</span>)</span></span><br><span class="hljs-function">vector<string> <span class="hljs-title">svec2</span><span class="hljs-params">(<span class="hljs-number">24</span>)</span></span><br><span class="hljs-function"><span class="hljs-title">swap</span><span class="hljs-params">(svec1, svec2)</span></span>;<br></code></pre></td></tr></table></figure><p>swap中元素本身并未交换,只是交换了两个容器的内部数据结构。因此指向容器的迭代器、引用和指针在swap操作后都不会失效,它们仍指向swap操作之前指向的那些元素。</p><blockquote><p>Note:对一个string调用swap会导致迭代器等失效;swap两个array会真正交换它们的元素。</p></blockquote><h2 id="顺序容器操作">3 顺序容器操作</h2><h3 id="添加元素">3.1 添加元素</h3><p>除array外,所有标准库容器都提供灵活的内存管理。在运行时可以动态添加或删除元素来改变容器大小。</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693144385485.png"></p><h3 id="访问元素">3.2 访问元素</h3><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693145837669.png"></p><p><strong>访问成员函数的返回是引用</strong>。</p><h3 id="删除元素">3.3 删除元素</h3><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693146135121.png"></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 删除一个list中的所有奇数元素</span><br>list<<span class="hljs-type">int</span>> lst = {<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>};<br><span class="hljs-keyword">auto</span> it = lst.<span class="hljs-built_in">begin</span>();<br><span class="hljs-keyword">while</span>(it != lst.<span class="hljs-built_in">end</span>())<br> <span class="hljs-keyword">if</span> (*it % <span class="hljs-number">2</span>)<br> it = lst.<span class="hljs-built_in">erase</span>(it); <br><span class="hljs-keyword">else</span><br> ++it;<br></code></pre></td></tr></table></figure><h3 id="特殊的forward_list操作">3.4 特殊的forward_list操作</h3><p>当从一个单向链表中删除元素时,会改变序列中的链接。</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693146844340.png"></p><p>因此为了添加或删除一个元素,需要访问其前驱以改变链接,但在单向链表中难以获取元素的前驱,因此<strong>forward_list中添加或删除元素的操作是通过改变元素之后的元素来完成的</strong>。</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693146983737.png"></p><p>其中定义了一个<strong>首前迭代器</strong><code>before_begin</code>,允许在链表首元素之前并不存在的元素“之后”添加删除元素。</p><p>改写前序从list中删除奇数元素:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++">forward_list<<span class="hljs-type">int</span>> flst = {<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>};<br><span class="hljs-keyword">auto</span> prev = flst.<span class="hljs-built_in">before_begin</span>(); <span class="hljs-comment">// 表示flst的首前元素</span><br><span class="hljs-keyword">auto</span> curr = flst.<span class="hljs-built_in">begin</span>(); <br><span class="hljs-keyword">while</span> (curr != flst.<span class="hljs-built_in">end</span>()) {<br> <span class="hljs-keyword">if</span> (*curr % <span class="hljs-number">2</span>)<br> curr = flst.<span class="hljs-built_in">erase_after</span>(prev); <br> <span class="hljs-keyword">else</span> { <span class="hljs-comment">// 移动curr指向下一个元素,prev指向之前的元素</span><br>prev = curr;<br> ++curr;<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="改变容器大小">3.5 改变容器大小</h3><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693147888678.png"></p><h3 id="容器操作可能使迭代器失效">3.6 容器操作可能使迭代器失效</h3><p>向容器中添加或删除元素的操作可能会使指向元素的指针、引用或迭代器失效。</p><p>在向容器添加元素后:</p><ul><li>如果容器是<code>vector</code>或<code>string</code>,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器等仍有效,而指向插入位置之后的失效。</li><li>对于<code>deque</code>,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。</li><li>对于<code>list</code>和<code>forward_list</code>,指向容器的迭代器等仍有效</li></ul><p>当删除元素时:</p><ul><li>对于<code>list</code>和<code>forward_list</code>,指向容器其他位置的迭代器等仍有效</li><li>对于<code>deque</code>,如果在首尾之外的任何位置删除元素,则指向其他元素的迭代器等失效</li><li>对于<code>vector</code>和<code>string</code>,指向被删元素之前的仍有效。</li></ul><p>因此,添加/删除元素的循环程序必须考虑迭代器等失效的问题。<code>insert</code>和<code>erase</code>操作返回迭代器,可以用来更新:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-comment">// 删除偶数元素,复制奇数元素</span><br>vector<<span class="hljs-type">int</span>> vi = {<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>}<br><span class="hljs-keyword">auto</span> iter = vi.<span class="hljs-built_in">begin</span>(); <br><span class="hljs-keyword">while</span> (iter != vi.<span class="hljs-built_in">end</span>()) {<br> <span class="hljs-keyword">if</span> (*iter % <span class="hljs-number">2</span>) {<br> iter = vi.<span class="hljs-built_in">insert</span>(iter,*iter); <span class="hljs-comment">// 在当前元素之前复制元素</span><br> iter += <span class="hljs-number">2</span>; <span class="hljs-comment">// 向前移动迭代器,跳过当前元素以及插入到它之前的元素</span><br> }<br> <span class="hljs-keyword">else</span><br> iter = vi.<span class="hljs-built_in">erase</span>(iter); <span class="hljs-comment">// iter指向删除的元素之后的元素</span><br>}<br></code></pre></td></tr></table></figure><h2 id="vector对象是如何增长的">4 vector对象是如何增长的</h2><p>为了支持快速随机访问,vector将元素连续存储一每个元素紧挨着前一个元素存储。为了提高添加元素的效率,vector 和 string的实现通常会分配比新的空间需求更大的内存空间。其也提供了一些成员函数管理容量:</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693150785011.png"></p><h2 id="额外的string操作">5 额外的string操作</h2><p>除了顺序容器共同的操作之外,string 类型还提供了一些额外的操作。这些操作中的大部分要么是提供 string 类和 C风格字符数组之间的相互转换,要么是增加了允许用下标代替迭代器的版本。</p><h3 id="构造string的其他方法">5.1 构造string的其他方法</h3><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693151020692.png"></p><blockquote><p>通常当我们从一个<code>const char*</code>创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。如果还传递给构造函数一个不大于数组大小的计数值,则数组就不必以空字符结尾。</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">const</span> <span class="hljs-type">char</span> *cp = <span class="hljs-string">"Hello World!!"</span>; <span class="hljs-comment">// 以空字符结束的数组</span><br><span class="hljs-type">char</span> noNull[] = {<span class="hljs-string">'H'</span>, <span class="hljs-string">'i'</span>}; <span class="hljs-comment">// 不是以空字符结束</span><br><span class="hljs-function">string <span class="hljs-title">s1</span><span class="hljs-params">(cp)</span></span>; <span class="hljs-comment">// 正确</span><br><span class="hljs-function">string <span class="hljs-title">s2</span><span class="hljs-params">(noNull,<span class="hljs-number">2</span>)</span></span>; <span class="hljs-comment">// 正确,s2 == "Hi"</span><br><span class="hljs-function">string <span class="hljs-title">s3</span><span class="hljs-params">(noNull)</span></span>; <span class="hljs-comment">// 未定义,noNull不是以空字符结束</span><br></code></pre></td></tr></table></figure><p><strong>substr</strong>操作</p><p><code>substr(pos,n)</code>返回一个string,包含s中从pos开始的n个字符的拷贝。pos默认值0,n默认值s.size()-pos。</p><h3 id="改变string的其他方法">5.2 改变string的其他方法</h3><p>除了接收迭代器的insert和erase版本外,string还提供了接受<strong>下标</strong>的版本。下标指出了开始删除的位置,或是insert到给定值之前的位置。</p><p><strong>append</strong>和<strong>replace</strong>函数</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693473599801.png"></p><h3 id="string搜索操作">5.3 string搜索操作</h3><p>string类提供的搜索操作如下表。每个搜索操作都返回一个<code>string::size_type</code>值,表示匹配发生位置的下标。如果搜索失败,则返回一个名为<code>string::npos</code>的static成员。</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693474251260.png"></p><p>对于args的要求:</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693474310835.png"></p><p><strong>compare函数</strong></p><p><code>s.compare(pos1,n1,s2,pos2,n2)</code>,根据s与s2的比较结果,返回0、正数或负数。</p><p><strong>数值转换</strong></p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693475107225.png"></p><h2 id="容器适配器">6 容器适配器</h2><p>除了顺序容器外,标准库还定义了三个顺序容器适配器:<code>stack</code>、<code>queue</code>、<code>priority_queue</code>。适配器是标准库中的一个通用概念,容器、迭代器和函数都有适配器。</p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693475579511.png"></p><p><img src="/2023/08/27/Ch9-%E9%A1%BA%E5%BA%8F%E5%AE%B9%E5%99%A8/1693475593055.png"></p>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>第8章 IO库</title>
<link href="/2023/08/26/Ch8-IO%E5%BA%93/"/>
<url>/2023/08/26/Ch8-IO%E5%BA%93/</url>
<content type="html"><![CDATA[<h1 id="第8章-io库">第8章 IO库</h1><h2 id="io类">1 IO类</h2><p>标准库定义的IO类型如下图:<code>iostream</code>定义了用于读写流的基本类型;<code>fstream</code>定义了读写命名文件的类型;<code>sstram</code>定义了读写内存<code>string</code>对象的类型。</p><p><img src="/2023/08/26/Ch8-IO%E5%BA%93/Users\whd\AppData\Roaming\Typora\typora-user-images\1693032376711.png"></p><h3 id="io对象无拷贝或赋值">1.1 IO对象无拷贝或赋值</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++">ofstream out1, out2<br>out1 = out2; <span class="hljs-comment">// 错误,不能对流对象赋值</span><br><span class="hljs-function">ofstream <span class="hljs-title">print</span><span class="hljs-params">(ofstream)</span></span>; <span class="hljs-comment">//错误,不能初始化 ofstream参数</span><br>out2 = <span class="hljs-built_in">print</span>(out2); <span class="hljs-comment">// 错误,不能拷贝流对象</span><br></code></pre></td></tr></table></figure><p>由于不能拷贝IO对象,因此不能将形参或返回类型设置为流类型。进行IO操作的函数通常以<strong>引用</strong>方式传递和返回流。</p><h3 id="条件状态">1.2 条件状态</h3><p>IO操作一个与生俱来的问题是可能发生错误,因此代码通常应该在使用流之前检查它是否处于良好状态。最简单的方法是将它当作条件使用:<code>while (cin>>word)</code></p><p>此外,IO库定义了一个与机器无关的<code>iostate</code>类型,然后定义了4个<code>iostate</code>类型的<code>constexpr</code>值,用来表示特点类型的IO条件,可以通过其<strong>查询流的状态</strong>;同意还提供了成员函数以<strong>管理条件状态</strong>。</p><h3 id="管理输出缓冲">1.3 管理输出缓冲</h3><p>每个输出流都管理一个缓冲区,用来保存程序读写的数据。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统及写操作。</p><h4 id="刷新输出缓冲区">刷新输出缓冲区</h4><p><strong>刷新输出缓冲区</strong>(即,数据真正写到输出设备或文件)的操纵符:</p><ul><li><p><code>endl</code>,添加换行,再刷新缓冲区</p></li><li><p><code>flush</code>,刷新缓冲区,不附加任何额外字符</p></li><li><p><code>ends</code>,插入空字符,再刷新缓冲区</p></li></ul><p>如果想在每次输出操作后都刷新缓冲区,可以使用<code>unitbuf</code>操作符。它告诉流在接下来的每次写操作之后都执行依次<code>flush</code>操作。</p><h4 id="关联输入和输出流">关联输入和输出流</h4><p>当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。</p><h2 id="文件输入输出">2 文件输入输出</h2><p><code>fstream</code>继承自<code>iostream</code>类型,因此可以使用cin、cout、<<、>>等操作,此外,还增加了一些新的成员:</p><p><img src="/2023/08/26/Ch8-IO%E5%BA%93/Users\whd\AppData\Roaming\Typora\typora-user-images\1693035797576.png"></p><h3 id="使用文件流对象">2.1 使用文件流对象</h3><p>当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">ifstream <span class="hljs-title">in</span><span class="hljs-params">(ifile)</span></span>; <span class="hljs-comment">//构造一个ifsteam并打开给定文件</span><br></code></pre></td></tr></table></figure><h4 id="用fstream代替iostream">用<code>fstream</code>代替<code>iostream&</code></h4><p>在要求使用基类型对象的地方,可以用继承类型的对象来代替。如果有一个函数接受一个<code>ostream&</code>参数,可以传递给它一个<code>ofstream</code>对象,对<code>istream</code>和<code>ifstream</code>也是如此。</p><h4 id="成员函数open和close">成员函数<code>open</code>和<code>close</code></h4><p>如果定义了一个空文件流对象,随后可以调用<code>open</code>将它与文件关联</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++">ofstream out; <span class="hljs-comment">// 输出流未与任何文件相关联</span><br>out.<span class="hljs-built_in">open</span>(ifile + <span class="hljs-string">".copy"</span>); <span class="hljs-comment">// 打开指定文件</span><br></code></pre></td></tr></table></figure><p>一旦一个文件流已经打开,它就保持与对应文件的关联。为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++">in.<span class="hljs-built_in">close</span>(); <span class="hljs-comment">// 关闭文件</span><br>in.<span class="hljs-built_in">open</span>(ifile + <span class="hljs-string">"2"</span>); <span class="hljs-comment">// 打开另一个文件</span><br></code></pre></td></tr></table></figure><h3 id="文件模式">2.2 文件模式</h3><p>每个流都有一个关联的文件模式,用来指出如何使用文件。</p><p><img src="/2023/08/26/Ch8-IO%E5%BA%93/Users\whd\AppData\Roaming\Typora\typora-user-images\1693037129439.png"></p><p><strong>以<code>out</code>模式打开文件会丢弃已有数据</strong></p><blockquote><p>保留被<code>ofstream</code>打开的文件中已有数据的唯一方法是显示指定<code>app</code>或<code>in</code>模式。</p></blockquote><h2 id="string流">3 string流</h2><p>与<code>fstream</code>类似,头文件<code>sstream</code>中定义的类型都继承自<code>iostream</code>中定义的类型。除了继承得来的操作,<code>sstream</code>中定义的类型还增加了一些成员来管理与流相关联的<code>string</code>。</p><p><img src="/2023/08/26/Ch8-IO%E5%BA%93/Users\whd\AppData\Roaming\Typora\typora-user-images\1693038144780.png"></p><h3 id="使用istringstream">3.1 使用<code>istringstream</code></h3><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></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">morgan</span> <span class="hljs-number">13681311344</span> <span class="hljs-number">18845772394</span><br><span class="hljs-attribute">drew</span> <span class="hljs-number">15655168523</span><br><span class="hljs-attribute">lee</span> <span class="hljs-number">18856565240</span> <span class="hljs-number">137117751575</span> <span class="hljs-number">15655432874</span><br></code></pre></td></tr></table></figure><p>首先定义一个类来描述输入数据:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">PersonInfo</span> {<br> string name;<br> vector<string> phones;<br>};<br></code></pre></td></tr></table></figure><p>然后读取文件,并创建一个PersonInfo的vector:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c++">string line,word; <span class="hljs-comment">// 分别保存来自输入的一行和单词</span><br>vector<PersonInfo> people; <span class="hljs-comment">// 保存输入的所有记录</span><br><span class="hljs-keyword">while</span> (<span class="hljs-built_in">getline</span>(cin, line)) {<br> PersonInfo info; <span class="hljs-comment">// 创建一个保存此记录数据的对象</span><br> <span class="hljs-function">istringstream <span class="hljs-title">record</span><span class="hljs-params">(line)</span></span>; <span class="hljs-comment">// 将记录绑定到刚读入的行</span><br> record >> info.name; <span class="hljs-comment">// 读取名字</span><br> <span class="hljs-keyword">while</span> (record >> word) <span class="hljs-comment">// 读取电话号码</span><br> info.phones.<span class="hljs-built_in">push_back</span>(word); <span class="hljs-comment">// 保持它们</span><br> people.<span class="hljs-built_in">push_back</span>(info); <span class="hljs-comment">// 将此记录追加到people末尾</span><br>}<br></code></pre></td></tr></table></figure><h3 id="使用ostringstream">3.2 使用<code>ostringstream</code></h3><p>现在对每个人验证其电话号码是否有效并改变其格式,对于无效号码,输出人名和错误信息:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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 c++"><span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> &entry : people) {<br> ostringstream formatted, badNums;<br> <span class="hljs-keyword">for</span>(<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> &nums : entry.phones){<br> <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">vaild</span>(nums))<br> badNums << <span class="hljs-string">" "</span> <<nums; <span class="hljs-comment">// 将数的字符串形式存入badNums</span><br> <span class="hljs-keyword">else</span><br> formatted << <span class="hljs-string">" "</span> << format(nums); <span class="hljs-comment">// 将格式化的字符串形式写入formatted</span><br> }<br> <span class="hljs-keyword">if</span> (badNums.<span class="hljs-built_in">str</span>().<span class="hljs-built_in">empty</span>())<br> os << entry.name << <span class="hljs-string">" "</span><br> << formatted.<span class="hljs-built_in">str</span>() << endl;<br> <span class="hljs-keyword">else</span> <br> cerr << <span class="hljs-string">"inpur error: "</span> << entry.name<br> << <span class="hljs-string">"invalid number(s) "</span><< badNums.<span class="hljs-built_in">str</span>() << endl;<br>}<br></code></pre></td></tr></table></figure><p>此程序中,假定已有两个函数,valid和format,用于验证和格式化。</p>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>第7章 类</title>
<link href="/2023/08/26/Ch7-%E7%B1%BB/"/>
<url>/2023/08/26/Ch7-%E7%B1%BB/</url>
<content type="html"><![CDATA[<h1 id="第7章-类">第7章 类</h1><h2 id="类的基本概念">类的基本概念</h2><p>类的基本思想是<strong>数据抽象</strong>和<strong>封装</strong>。数据抽象是一种依赖于<strong>接口</strong>和<strong>实现</strong>分离的编程(以及设计)技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。</p><p><strong>类</strong>就是一种自定义数据结构,这种数据结构把多个变量和函数封装在一起。几个关于类的几个基本概念:</p><ul><li><strong>成员变量</strong>, 也叫<strong>类的属性</strong>,就是类中声明定义的变量,可以是基本类型变量或是类类型变量</li><li><strong>成员函数</strong>,也叫方法,就是类中声明的函数</li><li><strong>实例对象</strong>,通过类类型创建的变量</li></ul><p>在后面的这些名称会混着用</p><p>定义类的语句形式:<code>struct className{};</code>或<code>class className{};</code></p><blockquote><p>说明:</p><ol type="1"><li><code>struct</code> 和 <code>class</code>定义类两者基本是等价的,只是默认的访问权限不同,关于权限的问题在7.2节会详细讲解,在这之前我们定义类都使用<code>struct</code></li><li>类中的成员变量(属性)定义的位置不影响调用,即可以在类的任意位置,也就是说我们可以在声明定义一个变量之前就可以使用改变量,这个规则只限于类方法和类属性</li><li>定义类的语法 <code>{}</code> 后面有一个 <code>;</code>,不要漏了</li></ol></blockquote><h2 id="定义抽象数据类型">1. 定义抽象数据类型</h2><h3 id="设计-sales_data-类">1.1 设计 Sales_data 类</h3><p>我们知道类有属性和方法,设计一个类首先要确定这些(这是根据类的功能确定的)。这个类是表示书的销售,这里直接给出类需要的属性和方法。</p><p>类的接口(对外可以使用的函数,部分函数不是类的成员函数)</p><ul><li>一个 <code>isbn</code> 成员函数,用于返回对象的ISBN编号</li><li>一个 <code>combine</code> 成员函数,用于将一个 Sales_data对象加到另一个对象上</li><li>一个<code>avg_price</code>成员函数,用于返回售出数书籍的平均价格</li><li>一个名为 <code>add</code> 的函数,指向两个Sales_data 对象的加法</li><li>一个 <code>read</code> 函数,将数据从 istream 读入到 Sales_data对象中</li><li>一个 <code>print</code> 函数,将Sales_data 对象的值输出到ostream</li></ul><p>成员变量:</p><ul><li><code>bookNo</code> 表示 ISBN编号,string类型</li><li><code>units_sold</code> 表示某本数的销量, unsigned类型</li><li><code>revenue</code> 表示这本书的总销售收入</li></ul><p>对应类的定义代码</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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 c++"><span class="hljs-comment">//Sales_data.h</span><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Sales_data</span>{ <br> <span class="hljs-comment">//成员函数</span><br> <span class="hljs-function">std::string <span class="hljs-title">isbn</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> bookNo; }<br> <span class="hljs-function">Sales_data& <span class="hljs-title">combine</span><span class="hljs-params">(<span class="hljs-type">const</span> Sales_data&)</span></span>;<br> <span class="hljs-function">dobule <span class="hljs-title">avg_price</span><span class="hljs-params">()</span> <span class="hljs-type">const</span></span>;<br> <br> <span class="hljs-comment">//成员变量</span><br> std::string bookNo;<br> <span class="hljs-type">unsigned</span> units_sold = <span class="hljs-number">0</span>;<br> <span class="hljs-type">double</span> revenue = <span class="hljs-number">0.0</span>;<br> <br>};<br><span class="hljs-comment">//Sales_data的非成员接口函数</span><br><span class="hljs-function">Sales_data <span class="hljs-title">add</span><span class="hljs-params">(<span class="hljs-type">const</span> Sales_data&, <span class="hljs-type">const</span> Sales_data&)</span></span>;<br><span class="hljs-function">std::ostream &<span class="hljs-title">print</span><span class="hljs-params">(std::ostream&, <span class="hljs-type">const</span> Sales_data&)</span></span>;<br><span class="hljs-function">std::istream &<span class="hljs-title">read</span><span class="hljs-params">(std::istream&, Sales_data&)</span></span>;<br></code></pre></td></tr></table></figure><h3 id="sales_data-类的说明">1.2 Sales_data 类的说明</h3><p>使用下面的语句定义一个 Sales_data 类型的对象</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++">Sales_data total;<span class="hljs-comment">//total 是一个类对象</span><br></code></pre></td></tr></table></figure><h4 id="引入-this">引入 this</h4><p>使用total对象对isbn 函数的调用:</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs abnf">total.isbn()<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure><p>我们知道上面的调用语句返回的就是 <code>total</code> 对应的<code>bookNo</code> 属性(<code>total.bookNo)</code>,但是在<code>isbn()</code>函数体内并没显式的指明需要返回哪个实例对象的属性,这里其实有一个隐式的参数<code>this</code>.</p><p>成员函数通过一个名为<code>this</code>的额外的隐式参数来访问调用它的那个对象。我们调用一个成员函数时,用请求该函数的对象地址初始化<code>this</code>。<code>this</code>是一个<strong>指针</strong>,准确的说是一个<strong>常量指针</strong>,指向的是当前调用该函数的对象的地址。比如上面的调用语句,this指向的是 total 的地址,所以上面的调用过程是:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//伪代码,用于说明调用成员函数的实际过程</span><br>Sales_data::<span class="hljs-built_in">isbn</span>(&total);<br></code></pre></td></tr></table></figure><p>每个成员函数都会有一个隐式的形参<code>this</code>,实际<code>isbn()</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++"><span class="hljs-function">std::string <span class="hljs-title">isbn</span><span class="hljs-params">(Sales_data *<span class="hljs-type">const</span> <span class="hljs-keyword">this</span>)</span></span>;<span class="hljs-comment">//错误代码</span><br></code></pre></td></tr></table></figure><blockquote><p>上面代码是为了说明成员函数有一个名为this的隐式形参,实际代码不能把这个形参写出来</p></blockquote><p>虽然不能写出来,但是我们在成员函数体内可以使用<code>this</code>这个形参名,比如 isbn的函数体可以改成</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>->bookNo;<span class="hljs-comment">//正确,返回当前对象实例的bookNo属性</span><br></code></pre></td></tr></table></figure><h5 id="引入const成员函数">2. 引入const成员函数</h5><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">std::string <span class="hljs-title">isbn</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> bookNo; }<br></code></pre></td></tr></table></figure><p><code>isbn()</code>函数还有一个之前没有出现的知识点,我们可以看到在函数的参数列表后面还有一个<code>const</code> 关键字,把这种形式的成员函数叫做<strong>常量成员函数</strong> (简称 常函数)</p><p>常量成员函数的本质实际上是对 <code>this</code> 指针的限制,前面说到<code>this</code> 是一个常量指针,类型是 <code>Sales_data *const</code>,这时只有顶层const,而常量成员函数又加上了底层const,<code>this</code>指针变成了<code>const Sales_data *const</code> 类型。<code>isbn</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++"><span class="hljs-function">std::string <span class="hljs-title">isbn</span><span class="hljs-params">(<span class="hljs-type">const</span> Sales_data *<span class="hljs-type">const</span> <span class="hljs-keyword">this</span>)</span> </span>{ <span class="hljs-keyword">return</span> bookNo; }<br></code></pre></td></tr></table></figure><blockquote><p>再次说明,this是一个隐式形参,实际代码不能把这个形参写出来</p></blockquote><p>常量成员函数和普通的成员函数有什么区别呢?它们是不是一组重载函数?</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">std::string <span class="hljs-title">isbn</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> bookNo; }<br><span class="hljs-function">std::string <span class="hljs-title">isbn</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> bookNo; }<br></code></pre></td></tr></table></figure><p><strong>它们是重载函数</strong>,我们把这两个函数的真实的形式写出来,以同样的方式和条件调用,看下是否只有唯一一个匹配的函数。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-comment">//isbn() 的真实形式,实际代码不能把this写出来</span><br><span class="hljs-function">std::string <span class="hljs-title">isbn</span><span class="hljs-params">(Sales_data *<span class="hljs-type">const</span> <span class="hljs-keyword">this</span>)</span></span>;<span class="hljs-comment">//普通成员函数</span><br><span class="hljs-function">std::string <span class="hljs-title">isbn</span><span class="hljs-params">(<span class="hljs-type">const</span> Sales_data *<span class="hljs-type">const</span> <span class="hljs-keyword">this</span>)</span></span>;<span class="hljs-comment">//常量成员函数</span><br><br><span class="hljs-comment">//定义两个 Sales_data 类型变量</span><br>Sales_data total;<br><span class="hljs-type">const</span> Sales_data c_total;<br>total.<span class="hljs-built_in">isbn</span>();<span class="hljs-comment">//1</span><br>c_total.<span class="hljs-built_in">isbn</span>();<span class="hljs-comment">//2</span><br><br><span class="hljs-comment">//对应的伪代码,用于说明调用成员函数的实际过程</span><br>Sales_data::<span class="hljs-built_in">isbn</span>(&total);<span class="hljs-comment">//调用 std::string isbn(Sales_data *const this);</span><br>Sales_data::<span class="hljs-built_in">isbn</span>(&c_total);<span class="hljs-comment">//调用std::string isbn(const Sales_data *const this);</span><br></code></pre></td></tr></table></figure><p>传参的规则实际和变量初始化的规则一样(第二章中的内容),我们观察下面的几个变量初始化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++">Sales_data total;<br><span class="hljs-type">const</span> Sales_data c_total;<br><br>Sales_data *<span class="hljs-type">const</span> <span class="hljs-keyword">this</span> = &total;<span class="hljs-comment">//正确,用一个非常量对象初始化一个顶层const</span><br>Sales_data *<span class="hljs-type">const</span> <span class="hljs-keyword">this</span> = &c_total;<span class="hljs-comment">//错误,&c_total包含一个底层const</span><br><br><span class="hljs-type">const</span> Sales_data *<span class="hljs-type">const</span> <span class="hljs-keyword">this</span> = &total; <span class="hljs-comment">//错误</span><br><span class="hljs-type">const</span> Sales_data *<span class="hljs-type">const</span> <span class="hljs-keyword">this</span> = &c_total; <span class="hljs-comment">//正确</span><br></code></pre></td></tr></table></figure><blockquote><p>总结:常量对象只能调用常函数,非常量对象既可以调用常量函数,也可以调用普通函数</p></blockquote><p>以上内容比较抽象,建议结合2.4节内容理解,关于常函数还有部分的知识点(常函数对类内属性的使用),后面的章节再讲</p><h4 id="类作用域和成员函数">类作用域和成员函数</h4><p>编译器对类的处理分两步:首先编译成员的声明,然后才轮到成员函数。因此,<strong>成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序</strong>。</p><h4 id="在类的外部定义成员函数">在类的外部定义成员函数</h4><p><code>Sales_data</code> 类的成员函数只有<code>isbn()</code>有定义,其他的成员函数都是只有声明。关于成员函数,它是支持类内声明,类外定义的。</p><p>成员函数类外定义和普通函数的定义没有什么区别,只是成员函数需要在函数名的前面加上对应的类名。</p><p>类内定义的成员函数默认是inline函数,定义在类外的要成为内联函数,需要显式的指定,在函数定义的前面加inline关键字</p><h4 id="定义一个返回this对象的函数">定义一个返回this对象的函数</h4><p>函数 <code>combine</code> 的设计初衷类似于复合赋值运算符<code>+=</code> , 调用该函数传入的是一个Sales_data对象,返回的还是该对象本身,这里我们可以使用 <code>this</code>指针实现</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">Sales_data& <span class="hljs-title">Sales_data::combine</span><span class="hljs-params">(<span class="hljs-type">const</span> Sales_data &rhs)</span></span><br><span class="hljs-function"></span>{<br> units_sold += rhs.units_sold;<br> revenue += rhs.revenue;<br> <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<span class="hljs-comment">//this 是调用该函数的对象的指针,对其解引用得到的就是该对象</span><br>}<br></code></pre></td></tr></table></figure><h3 id="定义类的非成员函数">1.3 定义类的非成员函数</h3><p><code>add</code> ,<code>read</code>,<code>print</code>不是类的成员函数,但是它们出和该类相关的接口函数,所以把它们和类定义在同一个文件中,以下是这三个函数的定义</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-function">istream &<span class="hljs-title">read</span><span class="hljs-params">(istream &is, Sales_data &item)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-type">double</span> price = <span class="hljs-number">0</span>;<br> is >> item.bookNo >> item.units_sold >> price;<br> item.revenue = price * item.units_sold;<br> <span class="hljs-keyword">return</span> is;<br>}<br><span class="hljs-function">ostream &<span class="hljs-title">print</span><span class="hljs-params">(ostream &os, <span class="hljs-type">const</span> Sales_data &item)</span></span><br><span class="hljs-function"></span>{<br> os << item.<span class="hljs-built_in">isbn</span>() << <span class="hljs-string">" "</span> << item.units_sold << <span class="hljs-string">" "</span><br> << item.revenue << <span class="hljs-string">" "</span> << item.<span class="hljs-built_in">avg_price</span>();<br> <span class="hljs-keyword">return</span> os;<br>}<br><span class="hljs-function">Sales_data <span class="hljs-title">add</span><span class="hljs-params">(<span class="hljs-type">const</span> Sales_data &lhs, <span class="hljs-type">const</span> Sales_data &rhs)</span></span><br><span class="hljs-function"></span>{<br> Sales_data sum = lhs;<br> sum.<span class="hljs-built_in">combine</span>(rhs);<br> <span class="hljs-keyword">return</span> sum;<br>}<br></code></pre></td></tr></table></figure><h3 id="构造函数">1.4 构造函数</h3><p><span style="border:2px solid Red">C++11</span>新标准允许在类属性定义时初始化,比如<code>Sales_data</code>类中<code>units_sold</code> 和 <code>revenue</code>都被初始化为0。但是并不是所有的属性都需要在类内初始化的,有些可能需要类的使用者自定义的初始化。这个时候我们就需要类的<strong>构造函数</strong>,构造函数的形式:类名就是函数名,没有返回值。</p><p>构造函数不需要显示的调用,在创建对象实例时,会自动调用构造函数。</p><h4 id="构造函数可以重载">构造函数可以重载</h4><ul><li>在没有写构造函数的情况下,编译器会提供一个<strong>默认构造函数</strong>,该函数是没有参数的,并且不执行任何操作,对于没有参数的构造函数也叫称作<strong>无参构造</strong></li><li>构造函数可以重载,重载的有形参的构造函数也叫做<strong>有参构造</strong></li><li>只要写了任何一种构造函数(有参构造或无参构造),编译器就不再提供默认的构造函数</li></ul><p>对于 <code>Sales_data</code> 的实例,设计了以下构造函数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Sales_data</span> {<br> <span class="hljs-comment">//新增的构造函数</span><br> <span class="hljs-built_in">Sales_data</span>() { }<span class="hljs-comment">//无参构造</span><br> <span class="hljs-built_in">Sales_data</span>(<span class="hljs-type">const</span> std::string &s) : <span class="hljs-built_in">bookNo</span>(s) { }<span class="hljs-comment">//有参构造1</span><br> <span class="hljs-built_in">Sales_data</span>(<span class="hljs-type">const</span> std::string &s, <span class="hljs-type">unsigned</span> n, <span class="hljs-type">double</span> p):<br> <span class="hljs-built_in">bookNo</span>(s), <span class="hljs-built_in">units_sold</span>(n), <span class="hljs-built_in">revenue</span>(p*n) {} <span class="hljs-comment">//有参构造2</span><br> <span class="hljs-built_in">Sales_data</span>(std::istream &);<span class="hljs-comment">//有参构造3</span><br> <br>};<br></code></pre></td></tr></table></figure><p>有参构造1和2使用了<strong>初始化列表</strong>的方式初始化成员属性。格式为成员名字的一个列表,每个名字后面紧跟括号括起来的(或者在花括号内的) 成员初始值。不同成员的初始化通过逗号分隔开来。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//有参构造1的等价形式</span><br><span class="hljs-built_in">Sales_data</span>(<span class="hljs-type">const</span> std::string &s) { bookNo = s; }<br><span class="hljs-comment">//有参构造2的等价形式</span><br><span class="hljs-built_in">Sales_data</span>(<span class="hljs-type">const</span> std::string &s, <span class="hljs-type">unsigned</span> n, <span class="hljs-type">double</span> p)<br>{<br> bookNo = s;<br> units_sold = n;<br> revenue = p*n; <br>}<br></code></pre></td></tr></table></figure><p>使用 <code>=</code>赋值运算符和使用初始化列表初始化成员属性效果是一样的。但是初始化列表的方式而且更高效,<strong>强烈建议使用初始化列表初始化成员属性</strong>。</p><h4 id="构造函数的调用">构造函数的调用</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//1.调用无参构造</span><br>Sales_data total;<br><span class="hljs-comment">//使用括号调用无参构造</span><br><span class="hljs-built_in">Sales_data</span>();<span class="hljs-comment">//会创建一个临时的对象,如果需要使用这个对象就要变量接收</span><br>Sales_data total = <span class="hljs-built_in">Sales_data</span>();<span class="hljs-comment">//用total接收临时变量</span><br><span class="hljs-function">Sales_data <span class="hljs-title">total</span><span class="hljs-params">()</span></span>;<span class="hljs-comment">//错误,这不不调用无参构造创建实例对象,而是声明一个返回类型为Sales_data的函数</span><br><br><span class="hljs-comment">//2. 调用有参构造</span><br><span class="hljs-function">Sales_data <span class="hljs-title">total</span><span class="hljs-params">(<span class="hljs-string">"654323"</span>)</span></span>;<span class="hljs-comment">//调用 Sales_data(const std::string &s)构造函数创建实例</span><br></code></pre></td></tr></table></figure><p>构造函数:</p><ol type="1"><li>构造函数,没有返回值也不写void</li><li>函数名称与类名相同</li><li>构造函数可以有参数,因此可以发生重载</li><li>程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次</li></ol><h3 id="拷贝赋值和析构">1.5 拷贝、赋值和析构</h3><p>c++编译器至少给一个类添加4个函数</p><ol type="1"><li>默认构造函数(无参,函数体为空)</li><li>默认析构函数(无参,函数体为空)</li><li>默认拷贝构造函数,对属性进行值拷贝</li><li>赋值运算符 <code>operator=</code>,对属性进行值拷贝</li></ol><h4 id="析构函数">析构函数</h4><p>默认构造函数前面已经提过了,<strong>析构函数</strong>也是由编译器调用的,它的调用时机是在对象销毁的时候被调用,析构函数的作用一般是用来释放内存空间(主要是堆上的内存空间)。</p><p>析构函数语法 :<code>~类名() {}</code></p><ol type="1"><li>析构函数,没有返回值也不写void</li><li>函数名称与类名相同,在名称前加上符号 ~</li><li>析构函数不可以有参数,因此不可以发生重载</li><li>程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次</li></ol><h4 id="拷贝构造函数">拷贝构造函数</h4><p>下面的语句会调用拷贝构造函数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//trans 是一个已经定义的 Sales_data 对象</span><br><span class="hljs-function">Sales_data <span class="hljs-title">total</span><span class="hljs-params">(trans)</span></span>;<span class="hljs-comment">//1</span><br>Sales_data total = trans;<span class="hljs-comment">//2</span><br><span class="hljs-comment">//下面的语句不是调用拷贝构造</span><br>Sales_data total;<br>total = trans;<span class="hljs-comment">//调用的是赋值操作</span><br></code></pre></td></tr></table></figure><p>默认拷贝构造函数的操作等价于把属性进行值拷贝,等价的语句</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//1 和 2等价的语句</span><br>total.bookNO = trans.bookNo;<br>total.units_sold = trans.units_sold;<br>total.revenue = trans.revenue;<br></code></pre></td></tr></table></figure><h4 id="赋值">赋值</h4><p>赋值操作是通过重载赋值运算符来实现的, <code>operator=</code></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++">Sales_data total;<br>total = trans;<span class="hljs-comment">//调用的是赋值操作</span><br></code></pre></td></tr></table></figure><p>编译器提供的默认赋值运算和默认拷贝构造操作是一样的,就是对属性值进行值拷贝。</p><h2 id="访问控制与封装">2. 访问控制与封装</h2><p>在C++中,我们使用访问说明符实现类的可见性的封装:</p><ul><li><p>定义在<code>public</code>说明符之后的成员在整个程序可被访问(以类示例对象的方式访问),一般<code>public</code> 成员定义类的接口。</p></li><li><p>定义在 <code>private</code>说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码(通常是指类的实例对象)访问。</p></li><li><p>定义在 <code>protected</code>说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码(通常是指类的实例对象)访问,在类的继承中还会继续讲到<code>protected</code></p></li></ul><blockquote><p>说明:</p><ol type="1"><li>每个访问说明符的有效范围一直到出现下一个访问说明符或者到达类的结尾处为止。</li><li>在一个类中不同的访问说明符是没有顺序和数量的限制。</li><li><code>struct</code> 和 <code>class</code>定义类唯一的区别就是默认的访问权限不同,前者默认的是 <code>public</code>,后者是 <code>private</code> .如果手动的添加了访问说明符完全可以两者相互替换。</li></ol></blockquote><h3 id="友元">2.1 友元</h3><p>除了 <code>Sales_data</code>的成员函数,我们还定义了三个类外的成员,它们也是对该类的操作,所以一般会把它们放到同一个文件中。这三个函数需要操作类中的属性,但是属性已经是<code>private</code> 成员,外部是无法访问的,这里我们可以使用<strong>友元</strong>。将某个类或函数声明为另一个类的友元,那么它们就可以访问另一个类中的私有成员。</p><p>具体语法 <code>friend 类或函数</code></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Sales_data</span><br>{<br><span class="hljs-function"><span class="hljs-keyword">friend</span> Sales_data <span class="hljs-title">add</span><span class="hljs-params">(<span class="hljs-type">const</span> Sales_data&, <span class="hljs-type">const</span> Sales_data&)</span></span>;<br><span class="hljs-function"><span class="hljs-keyword">friend</span> std::ostream &<span class="hljs-title">print</span><span class="hljs-params">(std::ostream&, <span class="hljs-type">const</span> Sales_data&)</span></span>;<br><span class="hljs-function"><span class="hljs-keyword">friend</span> std::istream &<span class="hljs-title">read</span><span class="hljs-params">(std::istream&, Sales_data&)</span></span>;<br> <span class="hljs-comment">//后面的成员定义同上</span><br>};<br></code></pre></td></tr></table></figure><blockquote><p>友元的声明位置没有限制可以在类内的任何位置,一般建议集中声明在类的开始或是结束。</p><p>友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行次声明。</p></blockquote><h2 id="类的其他特性">3. 类的其他特性</h2><h3 id="类成员再探">3.1 类成员再探</h3><p>示例: 一个 <code>Screen</code>类</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Screen</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-keyword">typedef</span> std::string::size_type pos;<span class="hljs-comment">//1</span><br> <span class="hljs-built_in">Screen</span>() = <span class="hljs-keyword">default</span>;<span class="hljs-comment">//2</span><br><span class="hljs-built_in">Screen</span>(pos ht, pos wd, <span class="hljs-type">char</span> c)<br> : <span class="hljs-built_in">height</span>(ht), <span class="hljs-built_in">width</span>(wd), <span class="hljs-built_in">contents</span>(ht * wd, c) {}<br> <span class="hljs-function"><span class="hljs-type">char</span> <span class="hljs-title">get</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> <span class="hljs-comment">//读取光标处的字符</span></span><br><span class="hljs-function"> </span>{ <span class="hljs-keyword">return</span> contents[cursor]; } <span class="hljs-comment">//隐式内联</span><br> <span class="hljs-function"><span class="hljs-keyword">inline</span> <span class="hljs-type">char</span> <span class="hljs-title">get</span><span class="hljs-params">(pos ht, pos wd)</span> <span class="hljs-type">const</span></span>;<span class="hljs-comment">//显示内联</span><br> <span class="hljs-function">Screen &<span class="hljs-title">move</span><span class="hljs-params">(pos r, pos c)</span></span>; <span class="hljs-comment">//能在之后被设为内联</span><br> <br><span class="hljs-keyword">private</span>:<br> pos cursor = <span class="hljs-number">0</span>;<br> pos height = <span class="hljs-number">0</span>, width = <span class="hljs-number">0</span>;<br> std:string contents;<br>};<br></code></pre></td></tr></table></figure><blockquote><p>说明</p><p>代码1使用别名将属性的类型抽象出来</p><p>代码2,因为定义了有参构造函数,编译器不会再提供默认的构造,而我们需要一个默认的构造,用这行代码告诉编译器提供一个默认的构造函数</p></blockquote><p><strong>可变数据成员</strong></p><p>关于常量成员函数还有一个特性:<strong>常量成员函数内不可以修改成员属性</strong>。</p><p>如果我们有需要在常函数内改变属性值的需求,可以使用<code>mutable</code> 关键字</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Person</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-keyword">mutable</span> <span class="hljs-type">int</span> m_A = <span class="hljs-number">42</span>; <br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">func</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span><br><span class="hljs-function"> </span>{<br> m_A = <span class="hljs-number">100</span>;<span class="hljs-comment">//正确 m_A 有mutable修饰</span><br> std::cout << m_A << std::endl;<span class="hljs-comment">//正确,可以读取</span><br> }<br>};<br></code></pre></td></tr></table></figure><h3 id="返回-this-的成员函数">3.2 返回 <code>*this</code>的成员函数</h3><p>关于类的 <code>this</code> 指针前面已经有过介绍</p><p>在Screen类中有一个 <code>set</code>的方法,该方法是在当前光标位置插入一个字符,<code>move</code>方法是移动光标</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Screen</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-keyword">typedef</span> std::string::size_type pos;<br> <span class="hljs-comment">// 其他无关代码</span><br> <span class="hljs-function">Screen &<span class="hljs-title">set</span><span class="hljs-params">(<span class="hljs-type">char</span>)</span></span><br><span class="hljs-function"> </span>{<br> contents[cursor] = c;<br> <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> }<br> <span class="hljs-function">Screen &<span class="hljs-title">set</span><span class="hljs-params">(pos r, pos col, <span class="hljs-type">char</span> ch)</span><span class="hljs-comment">//重载,在指定位置插入一个字符</span></span><br><span class="hljs-function"> </span>{<br> contents[r*width + col] = ch;<br> <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> }<br> <span class="hljs-function">Screen &<span class="hljs-title">move</span><span class="hljs-params">(pos r, pos c)</span></span><br><span class="hljs-function"> </span>{<br> pos = row = r * width;<br> cursor = row + c;<br> <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> }<br><span class="hljs-keyword">private</span>:<br> pos cursor = <span class="hljs-number">0</span>;<br> pos height = <span class="hljs-number">0</span>, width = <span class="hljs-number">0</span>;<br> std:string contents; <br>};<br></code></pre></td></tr></table></figure><p>定义一个实例对象<code>myScreen</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++">myScreen.<span class="hljs-built_in">move</span>(<span class="hljs-number">4</span>, <span class="hljs-number">0</span>).<span class="hljs-built_in">set</span>(<span class="hljs-string">'#'</span>);<br></code></pre></td></tr></table></figure><p>和上面链式调用等价的语句</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++">myScreen.<span class="hljs-built_in">move</span>(<span class="hljs-number">4</span>, <span class="hljs-number">0</span>);<br>myScreen.<span class="hljs-built_in">set</span>(<span class="hljs-string">'#'</span>);<br></code></pre></td></tr></table></figure><ul class="task-list"><li><input type="checkbox">TODO: 使用 <code>Screen</code> 而非引用<code>Screen&</code> 类型接收返回的值会发生什么?</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 如果move返回Screen而非Screen&</span><br>Screen temp = myScreen.<span class="hljs-built_in">move</span>(<span class="hljs-number">4</span>,<span class="hljs-number">0</span>);<br>temp.<span class="hljs-built_in">set</span>(<span class="hljs-string">'#'</span>); <span class="hljs-comment">// 不会改变myScreen的contents</span><br></code></pre></td></tr></table></figure><p>**一个 const 成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用**</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++">Screen myScreen;<br><span class="hljs-comment">// 如果display返回常量引用,则调用set将引发错误</span><br>myScreen.<span class="hljs-built_in">display</span>(cout).<span class="hljs-built_in">set</span>(<span class="hljs-string">'*'</span>);<br></code></pre></td></tr></table></figure><h3 id="类类型">3.3 类类型</h3><p>每个类定义了一个唯一的类型,对于两个类来说,即使它们的成员完全一样,这两个类也是不同的类型。</p><h4 id="类声明">类声明</h4><p>类和函数一样也可声明和定义分开,类的声明形式</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Screen</span>;<span class="hljs-comment">//Screen 类的声明</span><br></code></pre></td></tr></table></figure><p>对于类型Screen来说,在它声明之后定义之前是一个<strong>不完全类型</strong>,它的使用非常有限:可以定义指向这种类型的指针或引用,也可以声明(但不能定义)以不完全类型作为参数或者返回类型的函数。</p><h3 id="友元再探">3.4 友元再探</h3><p>不仅函数可以作为一个类的友元,一个类也可以作为另一个类的友元</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Screen</span> {<br> <span class="hljs-comment">// Window_mgr作为友元,Window_mgr的成员可以访问Screen类的私有部分</span><br> <span class="hljs-keyword">friend</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Window_mgr</span>;<br> <span class="hljs-comment">//...</span><br>};<br></code></pre></td></tr></table></figure><p>令一个类中的某些函数作为另一个类的友元</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Screen</span> {<br> <span class="hljs-comment">//Window_mgr类的clear函数做友元</span><br> <span class="hljs-function"><span class="hljs-keyword">friend</span> <span class="hljs-type">void</span> <span class="hljs-title">Window_mgr::clear</span><span class="hljs-params">(ScreenIndex)</span></span>; <br> <span class="hljs-comment">//...</span><br>};<br></code></pre></td></tr></table></figure><h2 id="类的作用域">4. 类的作用域</h2><p>关于类的作用域,我们在之前已经接触过了,就是当函数声明在类内,定义在类外的时候,定义需要加上类名表示该函数是该类的成员</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">Window_mgr::clear</span><span class="hljs-params">(ScreenIndex i)</span></span><br><span class="hljs-function"></span>{<br> Screen &s = screens[i];<br> s.contents = <span class="hljs-built_in">string</span>(s.height * s.width, <span class="hljs-string">'');</span><br><span class="hljs-string">}</span><br></code></pre></td></tr></table></figure><h3 id="名字查找与类的作用域">4.1 名字查找与类的作用域</h3><p>类的定义分两步处理:</p><ul><li>首先,编译成员的声明</li><li>直到类全部可见后才编译函数体。</li></ul><blockquote><p>编译器处理类中的全部声明后才会处理成员函数的定义。所以类中的成员变量可以定义在类内的任何位置</p></blockquote><p><strong>用于类成员声明的名字查找</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">typedef</span> <span class="hljs-type">double</span> Money;<br>string bal;<br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Account</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function">Money <span class="hljs-title">balance</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> bal; }<br><span class="hljs-keyword">private</span>:<br> Money bal;<br> <span class="hljs-comment">//...</span><br>}<br></code></pre></td></tr></table></figure><p>当编译器看到 <code>balance</code> 函数的声明时,首先会在<code>Account</code> 内找<code>Money</code>。此时只考虑Account内使用Money前出现的声明,很显然没有。于是就会在Account外层寻找,找到<code>typedef double Money;</code> 。</p><p>另一方面 <code>balance</code>函数体在整个类可见后才被处理,所以<code>return</code> 返回的时类内成员<code>bal</code> 而并不是 string 对象</p><h2 id="构造函数再探">5. 构造函数再探</h2><h3 id="构造函数初始化列表">5.1 构造函数初始化列表</h3><p>初始化和赋值一般都可以,但是列表初始化才是最优的方式,而且<strong>如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值</strong>。比如下面的例子</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ConstRef</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">ConstRef</span>(<span class="hljs-type">int</span> ii);<br><span class="hljs-keyword">private</span>:<br> <span class="hljs-type">int</span> i;<br> <span class="hljs-type">const</span> <span class="hljs-type">int</span> ci;<br> <span class="hljs-type">int</span> &ri;<br>};<br></code></pre></td></tr></table></figure><p>假设<code>ConstRef</code>类的成员需要类的使用者初始化(大多数情况下是这样的),很显然使用下面的赋值操作是错误的</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//错误: ci 和ri必须被初始化</span><br>ConstRef::<span class="hljs-built_in">ConstRef</span>(<span class="hljs-type">int</span> ii)<br>{<br> i = ii;<span class="hljs-comment">//正确</span><br> ci = ii;<span class="hljs-comment">//错误 不能给const赋值</span><br> ri = i;<span class="hljs-comment">//错误 ri没有被初始化</span><br>}<br></code></pre></td></tr></table></figure><p>正确的写法,列表初始化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++">ConstRef::<span class="hljs-built_in">ConstRef</span>(<span class="hljs-type">int</span> ii) : <span class="hljs-built_in">i</span>(ii), <span class="hljs-built_in">ci</span>(ii), <span class="hljs-built_in">ri</span>(i) {}<br></code></pre></td></tr></table></figure><h4 id="成员初始化的顺序">成员初始化的顺序</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">X</span> {<br> <span class="hljs-type">int</span> i;<br> <span class="hljs-type">int</span> j;<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-comment">//未定义的:i在j之前被初始化</span><br> <span class="hljs-built_in">X</span>(<span class="hljs-type">int</span> val) : <span class="hljs-built_in">j</span>(val), <span class="hljs-built_in">i</span>(j) {}<br>};<br></code></pre></td></tr></table></figure><p>一般来说,初始化的顺序没什么特别要求,不过如果一个成员是用另一个成员来初始化的那么这两个成员的初始化顺序就很关键。</p><p>上面的例子从形式来看是先用 <code>val</code> 初始化 <code>j</code>,在用 <code>j</code> 初始化<code>i</code>。实际的初始化顺序和成员的定义顺序一样,是先初始化<code>i</code> ,再初始化 <code>j</code></p><blockquote><p>最好令构造初始值的顺序域成员声明的顺序保持一致,而且如果可能的话,尽可能避免使用某些成员初始化其他成员</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-built_in">X</span>(<span class="hljs-type">int</span> val): <span class="hljs-built_in">i</span>(val), <span class="hljs-built_in">j</span>(val) {}<br></code></pre></td></tr></table></figure><h4 id="默认实参和构造函数">默认实参和构造函数</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Sales_data</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">Sales_data</span>(std::string s = <span class="hljs-string">""</span>) : <span class="hljs-built_in">bookNo</span>(s) {}<br> <span class="hljs-comment">//...</span><br>};<br></code></pre></td></tr></table></figure><blockquote><p>如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数</p></blockquote><h3 id="委托构造函数">5.2 委托构造函数</h3><p><span style="border:2px solid Red">C++11</span>在一个类中,不同的构造函数中可能存在冗余的语句,这时我们可以使用<strong>委托构造函数</strong>。</p><p>一个委托构造函数使用它所属类的其他构造函数指向它自己的初始化过程,或者说它吧自己的一些(或者全部)职责委托给其他构造函数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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 c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Sales_data</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-comment">//非委托构造函数使用对应的实参初始化成员</span><br> <span class="hljs-built_in">Sales_data</span>(std::string s, <span class="hljs-type">unsigned</span> cnt, <span class="hljs-type">double</span> price):<br> <span class="hljs-built_in">bookNo</span>(s), <span class="hljs-built_in">units_sold</span>(cnt), <span class="hljs-built_in">revenue</span>(cnt*price) { }<br> <span class="hljs-comment">//其余构造函数全都委托给另一个构造函数</span><br> <span class="hljs-built_in">Sales_data</span>() : <span class="hljs-built_in">Sales_data</span>(<span class="hljs-string">""</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>) { }<br> <span class="hljs-built_in">Sales_data</span>(std::string s) : <span class="hljs-built_in">Sales_data</span>(s, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>) { }<br> <span class="hljs-built_in">Sales_data</span>(std::istream &is) : <span class="hljs-built_in">Sales_data</span>() { <span class="hljs-built_in">read</span>(is, *<span class="hljs-keyword">this</span>); }<br>};<br></code></pre></td></tr></table></figure><h3 id="默认构造函数的作用">5.3 默认构造函数的作用</h3><p>如果我们自己定义了构造函数(无参构造和有参构造)编译器都不会提供一个默认的构造函数(无参构造函数),没有默认构造函数其实不会有任何的错误,当然前提是我们初始化对象时不能使用无参构造(默认构造)。</p><blockquote><p>在实际中,如果定义了其他构造函数,那么最后也提供一个默认构造函数</p></blockquote><h3 id="隐式的类类型转换">5.4 隐式的类类型转换</h3><p>下面的代码用到了本章之前所定义的<code>Sales_data</code>的相关成员</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 相关成员的原型</span><br><span class="hljs-function">Sales_data& <span class="hljs-title">combine</span><span class="hljs-params">(<span class="hljs-type">const</span> Sales_data&)</span></span>;<span class="hljs-comment">//combine</span><br><span class="hljs-built_in">Sales_data</span>(<span class="hljs-type">const</span> std::string &s);<span class="hljs-comment">//构造函数1</span><br><span class="hljs-built_in">Sales_data</span>(std::istream &);<span class="hljs-comment">//构造函数2</span><br><br><span class="hljs-comment">//item 是一个 Sales_data的实例,combine是将两个Sales_data实例对象相加</span><br>string null_book = <span class="hljs-string">"9-999-99999-9"</span>;<br>item.<span class="hljs-built_in">combine</span>(null_book);<br></code></pre></td></tr></table></figure><p>上面的7,8两行代码是正确。<code>combine</code>定义的参数类型是<code>const Sales_data</code>,但是我们传入一个 <code>string</code>类型的也是可以的。这是发生了类类型的隐式转换,把传入的<code>string</code>隐式的转换为了<code>Sales_data</code>,这种转换是由<strong>转换构造函数</strong>实现的,所谓转换构造函数就是只定义了一个实参的构造函数,上面的构造函数1和2都是转换构造函数。所以执行<code>item.combine(null_book);</code>会自动的调用<code>Sales_data(const std::string &s);</code>创建一个临时的<code>Sales_data</code>对象。</p><blockquote><p>能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则。</p></blockquote><p><strong>只允许一步类类型转换</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//错误: 需要用户定义的两种转换:</span><br><span class="hljs-comment">//(1)把 "9-999-99999-9" 转换成string</span><br><span class="hljs-comment">// (2) 再把这个(临时的)string转换成Sales_data</span><br>item.<span class="hljs-built_in">combine</span>(<span class="hljs-string">"9-999-99999-9"</span>);<br></code></pre></td></tr></table></figure><p>可以显示地把字符串转换成string或者Sales_data</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//正确: 显示地转换成string,隐式地转换成Sales_data</span><br>item.<span class="hljs-built_in">combine</span>(<span class="hljs-built_in">string</span>(<span class="hljs-string">"9-999-99999-9"</span>));<br><span class="hljs-comment">//正确:隐式地转换成string,显示地转换成Sales_data</span><br>item.<span class="hljs-built_in">combine</span>(<span class="hljs-built_in">Sales_data</span>(<span class="hljs-string">"9-999-99999-9"</span>));<br></code></pre></td></tr></table></figure><p>说明:<code>"9-999-99999-9"</code>字符串字面值并不是<code>string</code> 类型,它的类型应该是<code>const char*</code></p><h4 id="抑制构造函数定义的隐式转换">抑制构造函数定义的隐式转换</h4><p>有时候我们并不希望发生类类型的隐式转换,我们可以通过将构造函数声明为<code>explicit</code> 加以阻止:</p><p><code>explicit</code>使用注意</p><ul><li><code>explicit</code>只对一个实参的构造函数有效,(多个实参的构造函数也不能用于执行隐式转换)</li><li>只能再类内声明构造函数时使用<code>explicit</code>关键字,在类外定义时不能重复</li><li><code>explicit</code>构造函数只能用于直接初始化,不能用于拷贝形式的初始化(使用<code>=</code>)</li></ul><h4 id="类类型的显示转换">类类型的显示转换</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//正确:实参是一个显示构造的Sales_data对象</span><br>item.<span class="hljs-built_in">combine</span>(<span class="hljs-built_in">Sales_data</span>(null_bokk));<br><span class="hljs-comment">//正确: static_cast 可以使用explicit的构造函数</span><br>item.<span class="hljs-built_in">combine</span>(<span class="hljs-built_in">static_cast</span><Sales_data>(cin));<br></code></pre></td></tr></table></figure><h3 id="聚合类">5.5 聚合类</h3><p>当一个类满足如下条件时,我们就说它是一个聚合类:</p><ul><li>所有成员都是<code>public</code>的</li><li>没有定义任何构造函数</li><li>没有类内初始值</li><li>没有基类,也没有<code>virtual</code>函数(虚函数,后面的章节会介绍)</li></ul><p>例如</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Data</span> {<br> <span class="hljs-type">int</span> ival;<br> string s;<br>};<br></code></pre></td></tr></table></figure><p>聚合类的初始化,使用<code>{}</code>,值的顺序必须和声明的顺序一致</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//val1.ival = 0; val1.s = string("Anna")</span><br>Data val1 = {<span class="hljs-number">0</span>, <span class="hljs-string">"Anna"</span>};<br></code></pre></td></tr></table></figure><h2 id="类的静态成员">6. 类的静态成员</h2><p>而静态的成员(属性,方法)是只有一份的,就是所有的实例对象共享同一个成员。</p><p><strong>声明静态成员</strong>,银行账户示例:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Account</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">calculate</span><span class="hljs-params">()</span> </span>{ amount += amount * interestRate; }<br> <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">double</span> <span class="hljs-title">rate</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> interestRate; }<br> <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title">rate</span><span class="hljs-params">(<span class="hljs-type">double</span>)</span></span>;<br><span class="hljs-keyword">private</span>:<br> std::string ower;<br> <span class="hljs-type">double</span> amount;<br> <span class="hljs-type">static</span> <span class="hljs-type">double</span> interestRate;<br> <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">double</span> <span class="hljs-title">initRate</span><span class="hljs-params">()</span></span>;<br>};<br></code></pre></td></tr></table></figure><h4 id="静态成员的使用">静态成员的使用</h4><p>对于静态成员我们既可以使用类名调用,也可以使用实例对象调用,因为静态成员是所有实例共享一份,所以使用两种方法调用都是一样,建议使用类名调用。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">double</span> r;<br>r = Account::<span class="hljs-built_in">rate</span>();<span class="hljs-comment">//使用类名调用static void rate(double);</span><br>Account ac1;<br>r = ac1.<span class="hljs-built_in">rate</span>();<span class="hljs-comment">//使用实例对象调用static void rate(double);</span><br></code></pre></td></tr></table></figure><ul><li>对于静态的成员属性,必须类内声明类外初始化(如果需要初始化)</li><li>成员函数可以定义在类内和类外,</li><li>成员(函数,属性)定义在类外时不能重复 <code>static</code>关键字</li><li>静态成员函数内不可以访问非静态成员属性</li><li>静态成员函数不能声明成const的,不包含this指针</li></ul>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>第6章 函数</title>
<link href="/2023/08/25/Ch6-%E5%87%BD%E6%95%B0/"/>
<url>/2023/08/25/Ch6-%E5%87%BD%E6%95%B0/</url>
<content type="html"><![CDATA[<h1 id="第6章-函数">第6章 函数</h1><h2 id="函数的基础">1. 函数的基础</h2><p>一个典型的函数定义包括以下部分:<strong>返回类型</strong>、<strong>函数名</strong>、<strong>形参列表</strong>以及<strong>函数体</strong>。本章的内容也是围绕这几个点展开的。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-comment">//val的阶乘 val*(val-1)*(val-2)...*1</span><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">fact</span><span class="hljs-params">(<span class="hljs-type">int</span> val)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-type">int</span> ret = <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span> (val > <span class="hljs-number">1</span>)<br> ret *= val--;<br> <span class="hljs-keyword">return</span> ret;<br>}<br><br><span class="hljs-comment">//调用函数</span><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"></span>{<br> <span class="hljs-type">int</span> j = <span class="hljs-built_in">fact</span>(<span class="hljs-number">5</span>);<br> cout << <span class="hljs-string">"5! is "</span> << j << endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="局部对象">1.1 局部对象</h3><p>在c++语言中,名字有作用域,对象有生命周期</p><ul><li>名字的作用域是程序文本的一部分,名字在其中可见</li><li>对象的生命周期是程序执行过程中该对象存在的一段时间</li></ul><h4 id="局部变量">局部变量</h4><p>形参和函数内部定义的变量统称<strong>局部变量</strong></p><ul><li>局部变量只在函数内部起作用</li><li>外部全局变量和局部变量同名,局部变量会覆盖全局,这里是名称的覆盖,不是值的覆盖</li></ul><h4 id="局部静态变量">局部静态变量</h4><p>使用<code>static</code>关键字定义静态变量。在程序的执行路径第一次经过变量定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-function"><span class="hljs-type">size_t</span> <span class="hljs-title">count_calls</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-type">static</span> <span class="hljs-type">size_t</span> ctr = <span class="hljs-number">0</span>; <span class="hljs-comment">// 调用结束后,这个值仍 然有效</span><br> <span class="hljs-keyword">return</span> ++ctr;<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"></span>{<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i != <span class="hljs-number">10</span>; ++i)<br> {<br> cout << <span class="hljs-built_in">count_calls</span>() << endl;<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><span class="hljs-comment">// 输出从1到10(包括10在内)的数字。</span><br></code></pre></td></tr></table></figure><blockquote><p>静态变量存在于程序的整个生命周期,第一次调用<code>count_calls</code>,定义并初始化<code>ctr</code>,之后再调用函数不会再执行初始化,ctr相当于一个全局的变量</p></blockquote><h3 id="函数声明">1.2 函数声明</h3><p>和变量名一样,函数也必须在使用之前声明,<strong>函数可以声明多次,但是只能定义一次</strong>。函数的定义不是必须的,比如声明一个函数,我们从没有调用它,那么它可以不用定义。函数的声明是没有函数体的,<strong>声明可以不写形参名,只声明类型</strong>,但在定义时如果在函数用到形参,则需要写上变量名。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">func</span><span class="hljs-params">(<span class="hljs-type">int</span>, <span class="hljs-type">int</span>)</span></span>;<span class="hljs-comment">//声明可以不写变量名</span><br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">func</span><span class="hljs-params">(<span class="hljs-type">int</span> a, <span class="hljs-type">int</span> b)</span><span class="hljs-comment">//函数体中使用了形参,需要写 变量名</span></span><br><span class="hljs-function"> <span class="hljs-keyword">return</span> a+b</span>;<br></code></pre></td></tr></table></figure><h2 id="参数传递">2. 参数传递</h2><p>形参初始化的机制和变量初始化一样(本节内容可结合第2章的内容看)</p><p>在c++中传参的方式主要有两种:<strong>引用传递</strong>、<strong>值传递</strong></p><h3 id="传值参数">2.1 传值参数</h3><h4 id="普通类型形参">普通类型形参</h4><p>当初始化一个非引用类型的变量时,初始值被拷贝给变量,在函数体内改变的是实参的副本,不会对实参有影响</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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 c++"><span class="hljs-comment">//val的阶乘 val*(val-1)*(val-2)...*1</span><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">fact</span><span class="hljs-params">(<span class="hljs-type">int</span> val)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-type">int</span> ret = <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span> (val > <span class="hljs-number">1</span>)<br> ret *= val--;<br> <span class="hljs-keyword">return</span> ret;<br>}<br><br><span class="hljs-comment">//调用函数</span><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"></span>{<br> <span class="hljs-type">int</span> j = <span class="hljs-number">5</span>;<br> cout << <span class="hljs-string">"5! is "</span> << <span class="hljs-built_in">fact</span>(j) << endl;<br> cout << <span class="hljs-string">"5! is "</span> << j << endl;<span class="hljs-comment">//j的值没有变化</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="指针形参">指针形参</h4><p>指针形参也是值传递的一种方式,传入的指针是实参的副本(一个拷贝出来的指针),同样在函数体中改变指针的值(指向的地址)不会影响实参的值。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">reset</span><span class="hljs-params">(<span class="hljs-type">int</span> *p)</span></span><br><span class="hljs-function"></span>{<br> *ip = <span class="hljs-number">0</span>;<span class="hljs-comment">//改变指针ip所指对象的值</span><br> ip = <span class="hljs-number">0</span>;<span class="hljs-comment">//改变ip所指向的地址,但是只改变局部变量,实参未被改变</span><br>}<br></code></pre></td></tr></table></figure><blockquote><p>值传递的两种方式都是通过拷贝实参进行传值的,如果传入的实参比较大,拷贝会影响程序的性能。</p><p>建议使用下面将要介绍的引用传参的方式</p></blockquote><h3 id="传引用参数">2.2 传引用参数</h3><p>相比于值传递,引用传递是直接将对象传入函数,没有拷贝带来的性能损失,所以在函数体中改变通过引用传入的形参的值,会改变实参</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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 c++"><span class="hljs-comment">//这个函数,调用之后会实参的值会变成0</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">reset</span><span class="hljs-params">(<span class="hljs-type">int</span> &i)</span></span><br><span class="hljs-function"></span>{<br> i = <span class="hljs-number">0</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-function"></span>{<br> <span class="hljs-type">int</span> j = <span class="hljs-number">42</span>;<br> <span class="hljs-built_in">reset</span>(j);<br> cout << <span class="hljs-string">"j = "</span> << j << endl;<span class="hljs-comment">//j的值是0</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><blockquote><p><strong>建议使用引用传参</strong>,对于不需要改变引用形参的值,可以将其声明为常量引用</p></blockquote><h4 id="引用形参的一种用法">引用形参的一种用法</h4><p>我们知道函数只能返回一个值,然而有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。就是我们把需要返回的一个或多个需要返回的值声明为引用形参,在函数体把值写入到引用形参中。</p><h3 id="const形参和实参">2.3 const形参和实参</h3><p>这里涉及到顶层const和底层const的概念。</p><ul><li>用实参初始化形参时会忽略顶层const,也就是说对于一个含有顶层const的形参,可以给它传递常量和非常量对象</li><li>我们可以使用非常量初始化一个底层const对象,但是反过来不行</li></ul><p>将变量的初始化规则应用到参数传递</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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 c++"><span class="hljs-comment">//函数原型: int reset(int &i); 和 int reset(int *ip);</span><br><span class="hljs-type">int</span> i = <span class="hljs-number">0</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">int</span> ci = i;<br>string::size_type ctr = <span class="hljs-number">0</span>;<br><span class="hljs-built_in">reset</span>(&i);<span class="hljs-comment">//调用形参类型为 int* 的 reset</span><br><span class="hljs-built_in">reset</span>(&ci);<span class="hljs-comment">//错误,int *ip = ci;</span><br><span class="hljs-built_in">reset</span>(i);<span class="hljs-comment">//调用形参类型为 int& 的 reset</span><br><span class="hljs-built_in">reset</span>(ci);<span class="hljs-comment">//错误, int *ip = ci;</span><br><span class="hljs-built_in">reset</span>(<span class="hljs-number">42</span>);<span class="hljs-comment">//错误 int &i = 42;</span><br><span class="hljs-built_in">reset</span>(ctr);<span class="hljs-comment">//错误, 类型不匹配</span><br></code></pre></td></tr></table></figure><p><strong>尽量使用常量引用</strong></p><ul><li>当我们把一个不需要改变的形参定义成非常量的话,会给人误导</li><li>定义成常量引用的形参,调用者可以传递常量和非常量实参</li></ul><h3 id="数组形参">2.4 数组形参</h3><ol type="1"><li>不允许拷贝数组,所以<strong>数组不能以值传递的方式传入</strong></li><li>为函数传递一个数组时,实际上传递的是指向数组首元素的指针。</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//传入的都是 const int* </span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>*)</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>[])</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>[<span class="hljs-number">10</span>])</span></span>;<span class="hljs-comment">//这里的维度是希望传入含有10个元素的数组的指针,实际不一定</span><br></code></pre></td></tr></table></figure><blockquote><p>以上三个函数的形参虽然表现形式不一样,但是他们是等价的,都是<code>const int*</code>类型形参</p></blockquote><h4 id="管理指针形参">管理指针形参</h4><p>和其他使用数组的代码一样,以数组作为形参的函数也必须确保使用数组时不越界,下面介绍三种常用的管理指针形参的技术:- 使用标记指定数组长度(如C风格字符串以空字符<code>\0</code>结束) -使用标准库规范(传递数组首元素和尾后元素的指针) -显示的传递一个表示数组大小的形参</p><h4 id="数组引用形参">数组引用形参</h4><p>c++允许将变量定义成数组的引用,同样形参也可以是数组的引用</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//形参是数组的引用,维度是类型的一部分</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span> (&arr)[<span class="hljs-number">10</span>])</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> elem : arr)<br> cout << elem << endl;<br>}<br></code></pre></td></tr></table></figure><blockquote><p>上面函数可传入 <code>int arr[10]</code>类型,形参中维度是类型的一部分</p><p><code>&arr</code> 两端的括号不能少,下面两个函数定义不等价</p><p><code>f(int &arr[10])</code>//错误,形参是引用类型的数组,不存在这种类型</p><p><code>f(int (&arr)[])</code>//正确,arr是具有10个整数类型数组的引用</p></blockquote><h4 id="传递多维数组">传递多维数组</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span> (*martrix)[<span class="hljs-number">10</span>], <span class="hljs-type">int</span> rowSize)</span> </span>{ <span class="hljs-comment">/*...*/</span>}<br></code></pre></td></tr></table></figure><p>上述语句将matrix声明成指向含有10个整数的数组的指针,<code>*matrix</code> 两端的括号不可少</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> *matrix[<span class="hljs-number">10</span>];<span class="hljs-comment">//10个整型指针组成的数组, 这是个数组变量</span><br><span class="hljs-built_in">int</span> (*matrix)[<span class="hljs-number">10</span>];<span class="hljs-comment">//指向含有10个整型的数组的指针, 这是个指针变量</span><br></code></pre></td></tr></table></figure><h3 id="main-命令行选项">2.5 main: 命令行选项</h3><p><code>main</code>函数是可以带参数的,我们在命令输入的命令就是传递到main函数中,假设main函数位于可执行文件<code>prog</code> 之内,我们可以向程序传递下面的选项:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash">prog -d -o ofile data0</span><br></code></pre></td></tr></table></figure><p>这些命令可以通过两个形参传递给main函数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//main函数带形参的两种形式,这两种形式是等价的</span><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> *argv[])</span> </span>{ ... }<br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> **argv)</span> </span>{ ... }<br></code></pre></td></tr></table></figure><p><code>argc</code>表示命令行参数的个数,包括可执行程序本身的文件名,<code>argv</code>存放命令行参数</p><p><code>prog -d -o ofile data0</code> 命令, argc是5, argv内容为:</p><p><code>argv[0] = "prog ";</code></p><p><code>argv[1] = "-d";</code></p><p><code>argv[2] = "-o";</code></p><p><code>argv[3] = "ofile";</code></p><p><code>argv[4] = "data0";</code></p><blockquote><p>当我们需要使用命令行输入的参数时,从<code>argv[1]</code>开始读取,<code>argv[0]</code> 保存的是程序名</p></blockquote><h3 id="含有可变形参的函数">2.6 含有可变形参的函数</h3><h4 id="省略符形参">省略符形参</h4><p>省略符形参是C语言的标准,在C++中也是适用的,它的形式有以下两种</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">foo</span><span class="hljs-params">(parm_list, ...)</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">foo</span><span class="hljs-params">(...)</span></span>;<br></code></pre></td></tr></table></figure><p><strong>省略符形参只能出现在形参列表的最后一个位置</strong></p><blockquote><p>第一种形式指定了 <code>foo</code>函数的部分形参的类型,这些形参和正常的形参一样。省略符形参所对应的传入的实参无须类型检查。在第一种形式中,形参声明后的逗号是可选的。</p></blockquote><h4 id="initializer_list-形参">initializer_list 形参</h4><p><span style="border:2px solid Red">C++11</span><code>initializer_list</code>是一种标准库类型,改类型定义在同名的头文件中,它提供的操作如表</p><figure><img src="https://kinvy-images.oss-cn-beijing.aliyuncs.com/Images/image-20211228182857202.png" alt="image-20211228182857202"><figcaption aria-hidden="true">image-20211228182857202</figcaption></figure><p>使用示例:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">error_msg</span><span class="hljs-params">( initializer_list<string> il)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> beg = il.<span class="hljs-built_in">begin</span>(); beg != il.<span class="hljs-built_in">end</span>(); ++beg)<br> cout << *beg << <span class="hljs-string">" "</span>;<br> cout << endl;<br>}<br></code></pre></td></tr></table></figure><blockquote><p><code>initializer_list</code>是一个泛型类,使用时需指定类型,所以传递的多个参数必须是同一种类型</p><p>除了initializer_list 之外,函数也可以有其他的形参</p></blockquote><h2 id="返回类型和return语句">3. 返回类型和return语句</h2><p><code>return</code>语句终止当前正在执行的函数并控制返回到调用该函数的地方。return语句有两种形式</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">return</span>;<span class="hljs-comment">//用于无返回值的函数</span><br><span class="hljs-keyword">return</span> expression;<span class="hljs-comment">//用于有返回值的函数</span><br></code></pre></td></tr></table></figure><h3 id="无返回值函数">3.1 无返回值函数</h3><p>没有返回值的 <code>return</code> 语句只能用在返回类型是<code>void</code> 的函数中。返回 void 的函数不要求非得有return语句,这类函数的最后一句后面会隐式地执行return,return可以放在函数内的其他位置,表示提前结束函数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">swap</span><span class="hljs-params">(<span class="hljs-type">int</span> &v1, <span class="hljs-type">int</span> &v2)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(v1 == v2)<br> <span class="hljs-keyword">return</span>;<br> <span class="hljs-type">int</span> tmp = v2;<br> v2 = v1;<br> v1 = tmp;<br> <span class="hljs-comment">//此处无须显示的return语句</span><br>}<br></code></pre></td></tr></table></figure><blockquote><p>返回值为 <code>void</code> 的函数体中不可以使用<code>return experssion;</code> 返回语句</p></blockquote><h3 id="有返回值函数">3.2 有返回值函数</h3><p>只要函数的返回类型不是 void, 则该函数内的每条 return语句必须返回一个值。return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型。</p><p><strong>不要返回局部对象的引用或是指针</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">const</span> string &<span class="hljs-title">manip</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> string ret;<br> <span class="hljs-keyword">if</span> (!ret.<span class="hljs-built_in">empty</span>())<br> <span class="hljs-keyword">return</span> ret;<span class="hljs-comment">//错误:返回局部对象的引用!</span><br> <span class="hljs-keyword">else</span><br> <span class="hljs-keyword">return</span> <span class="hljs-string">"Empty"</span>;<span class="hljs-comment">//错误: "Empty"是一个局部临时对象</span><br>}<br></code></pre></td></tr></table></figure><blockquote><p>局部对象的指针或是引用,在返回时就会被释放,用变量接收函数的返回值得到的是不存在的对象。</p></blockquote><p><strong>引用返回左值</strong> ---->能为返回类型是非常量引用的函数的结果赋值</p><p><strong>列表初始化返回值</strong> ---->函数可以返回花括号包围的值的列表</p><p><strong>递归</strong> ---->在函数中调用了自身。<strong>递归调用必须要有终止条件</strong></p><h3 id="返回数组指针">3.3 返回数组指针</h3><p>数组不能拷贝,所以函数不能返回数组。不过函数可以返回数组的指针或是引用。直接定义一个返回数组的指针或是引用的函数比较烦琐,先看下使用别名的方式。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">typedef</span> <span class="hljs-type">int</span> arrT[<span class="hljs-number">10</span>];<span class="hljs-comment">//arrT 是一个类型别名,他表示的类型是含有10个整数的数组</span><br><span class="hljs-keyword">using</span> arrT = <span class="hljs-type">int</span>[<span class="hljs-number">10</span>];<span class="hljs-comment">//和上面的等价</span><br><span class="hljs-function">arrT* <span class="hljs-title">func</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span></span>;<span class="hljs-comment">//使用类型别名定义一个返回10个整数的数组的指针的函数</span><br></code></pre></td></tr></table></figure><h4 id="声明一个返回数组指针的函数">声明一个返回数组指针的函数</h4><p>首先对下面的定义区分一下</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> arr[<span class="hljs-number">10</span>];<span class="hljs-comment">//arr是一个含有10个整数的数组</span><br><span class="hljs-type">int</span> *p1[<span class="hljs-number">10</span>];<span class="hljs-comment">//p1是一个含有10个整型指针的数组</span><br><span class="hljs-built_in">int</span> (*p2)[<span class="hljs-number">10</span>] = &arr;<span class="hljs-comment">//p2是一个指针,它指向含有10个整数的数组</span><br></code></pre></td></tr></table></figure><p>返回数组指针的函数的形式如下</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-built_in">Type</span> (*<span class="hljs-built_in">function</span>(parameter_lis)) [dimension]<br></code></pre></td></tr></table></figure><p><code>dimension</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++"><span class="hljs-built_in">int</span> (*<span class="hljs-built_in">func</span>(<span class="hljs-type">int</span> i)) [<span class="hljs-number">10</span>];<br></code></pre></td></tr></table></figure><p>对于这个函数的定义,我们可以逐层的理解: <code>func(int i)</code>表示一个名为 <code>func</code>,参数为 <code>int i</code> 的函数;<code>*</code> 表示的返回的是指针类型,<code>int* [10]</code>表示是数组类型的指针</p><h4 id="使用尾置返回类型">使用尾置返回类型</h4><p><span style="border:2px solid Red">C++11</span>新标准提供了一种简便的方式定义这样的函数,就是使用<strong>尾置返回类型</strong> ,它的形式是这样的</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-keyword">auto</span> <span class="hljs-title">func</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span> -> <span class="hljs-title">int</span><span class="hljs-params">(*)</span>[10]</span>;<br></code></pre></td></tr></table></figure><h4 id="使用-decltype">使用 decltype</h4><p>还有一种情况,如果我们知道函数返回的指针将指向哪个数组,可以使用<code>decltype</code> 关键做声明返回类型</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> odd[] = {<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">5</span>, <span class="hljs-number">7</span>, <span class="hljs-number">9</span>};<br><span class="hljs-type">int</span> even[] = {<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">6</span>, <span class="hljs-number">8</span>};<br><span class="hljs-comment">//返回一个指针,该指针指向含有5个整数的数组</span><br><span class="hljs-keyword">decltype</span>(odd) *<span class="hljs-built_in">arrPtr</span>(<span class="hljs-type">int</span> i) <span class="hljs-comment">//decltype(odd) 后需要加 * 表示对应的指针类型</span><br>{<br> <span class="hljs-keyword">return</span> (i % <span class="hljs-number">2</span>) ? &odd : &even;<span class="hljs-comment">//返回一个指向数组的指针</span><br>}<br></code></pre></td></tr></table></figure><h2 id="函数重载">4. 函数重载</h2><p>如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为<strong>重载函数</strong>,下面的几个参数列表不同的函数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span> *cp)</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span> *beg, <span class="hljs-type">const</span> <span class="hljs-type">int</span> *end)</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span> ia[], <span class="hljs-type">size_t</span> size)</span></span>;<br><span class="hljs-comment">//函数调用时,编译器会根据传入的参数类型调用不同的函数</span><br><span class="hljs-type">int</span> j[<span class="hljs-number">2</span>] = {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>};<br><span class="hljs-built_in">print</span>(<span class="hljs-string">"Hello world"</span>); <span class="hljs-comment">//调用 print(const char*)</span><br><span class="hljs-built_in">print</span>(j, <span class="hljs-built_in">end</span>(j) - <span class="hljs-built_in">beging</span>(j));<span class="hljs-comment">//调用第3个</span><br><span class="hljs-built_in">print</span>(<span class="hljs-built_in">begin</span>(j), <span class="hljs-built_in">end</span>(j));<span class="hljs-comment">//调用第2个</span><br></code></pre></td></tr></table></figure><h4 id="定义重载函数">定义重载函数</h4><p>重载函数唯一区分的指标就是形参列表的数量和类型,<strong>只有返回类型不同的函数不是重载函数</strong></p><h4 id="重载和const形参">重载和const形参</h4><p><strong>顶层const是无法区分形参</strong>,所以顶层cosnt形参无法实现重载</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span></span>;<br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>)</span></span>; <span class="hljs-comment">//和上面声明等价,重复声明</span><br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span>*)</span></span>;<br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span>* <span class="hljs-type">const</span>)</span></span>; <span class="hljs-comment">//和上面声明等价,重复声明</span><br></code></pre></td></tr></table></figure><p>如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层const:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span>&)</span></span>;<span class="hljs-comment">//函数作用于int的引用</span><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>&)</span></span>;<span class="hljs-comment">//重载函数,作用于常量引用</span><br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span>*)</span></span>;<span class="hljs-comment">//作用于int类型的指向</span><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>*)</span></span>;<span class="hljs-comment">//重载函数,作用于指向常量的指针</span><br></code></pre></td></tr></table></figure><blockquote><p>在本章的第2节中讲函数的参数时,有提到形参是常量对象的函数既可以传入常量对象,也可以传入非常量对象,比如上面两组函数中的第二个,它们可以传入常量对象也可传入非常量对象。</p><p>在这里,因为它们都有非常量形参的重载函数,那么在传入非常量对象时编译器会优先选用非常量版本的函数。</p></blockquote><h3 id="重载与作用域">4.1重载与作用域</h3><p><strong>不要在某个语句块(函数体)的内部声明和外部名字一样的变量和函数</strong>。(受到作用域限制,会隐藏外层变量/函数)</p><h2 id="特殊用途语言特性">5. 特殊用途语言特性</h2><h3 id="默认实参">5.1 默认实参</h3><p>有些时候,我们调用函数时,某些形参的值总是被赋予同样的值,只是在少数情况下需要要不同的值。这时我们可以把这样的形参赋予一个默认的值</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//一个创建窗口的函数,窗口的默认高80,宽180</span><br><span class="hljs-function">string <span class="hljs-title">screen</span><span class="hljs-params">(string name, <span class="hljs-type">int</span> h = <span class="hljs-number">80</span>, <span class="hljs-type">int</span> w = <span class="hljs-number">180</span>)</span></span>; <br><span class="hljs-built_in">screen</span>(<span class="hljs-string">"window1"</span>);<span class="hljs-comment">//不传入h和w,使用默认值</span><br><span class="hljs-built_in">screen</span>(<span class="hljs-string">"window2"</span>, <span class="hljs-number">100</span>);<span class="hljs-comment">//只传入h, w使用默认值</span><br></code></pre></td></tr></table></figure><blockquote><p>注意:</p><ul><li>默认形参必须定义在形参列表的最后</li><li>实参是按位置解析的,比如需要改变 w 的值,那么h的值也必须传入</li><li>局部变量不能作为默认实参</li></ul></blockquote><h3 id="内联函数和constexpr函数">5.2 内联函数和constexpr函数</h3><h4 id="内联函数">内联函数</h4><p>为了避免函数调用的开销,将一些规模较小、流程直接的函数声明成<strong>内联函数</strong>,内联函数不会有调用的过程,而是直接在调用点把函数体内的语句嵌入进来。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-function"><span class="hljs-keyword">inline</span> <span class="hljs-type">const</span> string &</span><br><span class="hljs-function"><span class="hljs-title">shorterString</span><span class="hljs-params">(<span class="hljs-type">const</span> string &s1, <span class="hljs-type">const</span> string &s2)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">return</span> s1.<span class="hljs-built_in">size</span>() <= s2.<span class="hljs-built_in">size</span>() ? s1 : s2;<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"></span>{<br> string s1 = <span class="hljs-string">"hello"</span>;<br> string s2 = <span class="hljs-string">"world"</span>;<br> cout << <span class="hljs-built_in">shorterString</span>(s1, s2) << endl; <span class="hljs-comment">//等价 cout<< s1.size() <= s2.size() ? s1 : s2 << endl </span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="constexpr函数">constexpr函数</h3><p>在第2章中有讲到,使用<code>constexpr</code>关键字定义常量表达式,并且可以使用函数的返回值初始化定义的常量。这里使用的函数就是<code>constexpr函数</code>, 语法形式如下</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> <span class="hljs-title">new_sz</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> <span class="hljs-number">42</span>; }<br><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> foo = <span class="hljs-built_in">new_sz</span>();<span class="hljs-comment">//用constexpr函数的返回值初始化一个constexpr变量</span><br></code></pre></td></tr></table></figure><p>constexpr函数在编译阶段就已经计算出了返回值,对于constexpr函数的调用是直接用计算的值替代的。为了编译过程随时展开,constexpr函数被隐式地指定为内联函数。</p><blockquote><p>constexpr函数需遵循:</p><ul><li>函数的返回类型及所有的形参类型都得是字面值类型</li><li>函数体中必须有且只有一条 return 语句</li></ul></blockquote><h3 id="调试帮助">5.3 调试帮助</h3><h4 id="assert预处理宏">assert预处理宏</h4><p><code>assert</code>是一种预处理宏,所谓预处理宏其实是一个预处理变量,它的行为有点类似于内联函数。assert宏使用一个表达作为它的条件:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-built_in">assert</span>(expr);<br></code></pre></td></tr></table></figure><p>首先对 <code>expr</code>求值,如果表达式为假(即0),assert输出信息并终止程序的执行;如果表达式为真(即非0),assert什么也不做。</p><p>assert宏定义在<code>cassert</code>头文件中,预处理名字由预处理器而非编译器管理,所以我们可以直接使用预处理名字而无需提供using 声明。</p><h4 id="ndebug-预处理变量">NDEBUG 预处理变量</h4><p>assert的行为依赖于一个名为 <code>NDEBUG</code>的预处理变量的状态。如果定义了NDEBUG,则assert什么也不在。默认状态下没有定义NDEBUG。定义NDEBUG既可以在程序中定义,如下</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">define</span> NDEBUG</span><br></code></pre></td></tr></table></figure><p>也可以在编译时加上NDEBUG这个参数</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash">CC -D NDEBUG main.c<span class="hljs-comment">#等价于 #define NDEBUG</span></span><br></code></pre></td></tr></table></figure><p>除了assert之外,我们也可以使用NDEBUG编写自己的条件调试代码</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span> ia[], <span class="hljs-type">size_t</span> size)</span></span><br><span class="hljs-function"></span>{<br><span class="hljs-meta">#<span class="hljs-keyword">ifndef</span> NDEBUG</span><br> cerr << __func__ << <span class="hljs-string">": array size is "</span> << size << endl;<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br> <span class="hljs-comment">//...</span><br>}<br></code></pre></td></tr></table></figure><p>上面代码中 <code>__func__</code> 是编译器定义的变量,它是 const char的一个静态数组,存放当前函数的名字,除了这个,还有其他变量</p><ul><li><code>__FILE__</code> 存放文件名的字符串字面值</li><li><code>__LINE__</code> 存放当前行号的整型字面值</li><li><code>__TIME__</code> 存放文件编译时间的字符串字面值</li><li><code>__DATE__</code> 存放文件编译日期的字符串字面值</li></ul><p>我们可以利用上面这些变量提供错误的详细信息</p><h2 id="函数匹配">6. 函数匹配</h2><p>当重载函数的参数数量一样,只是类型不同的情况下,函数匹配变得有点困难了。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">()</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">int</span> , <span class="hljs-type">int</span>)</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">double</span>, <span class="hljs-type">double</span> = <span class="hljs-number">3.14</span>)</span></span>;<br><span class="hljs-built_in">f</span>(<span class="hljs-number">5.6</span>);<span class="hljs-comment">//调用 void f(double, double)</span><br></code></pre></td></tr></table></figure><h3 id="实参类型转换">6.1 实参类型转换</h3><p>为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级,具体如下所示</p><ol type="1"><li>精确匹配,包括以下情况:<ul><li>实参类型和形参类型相同</li><li>实参从数组类型或函数类型转换成对应的指针类型(函数指针下小节会讲)</li><li>向实参添加顶层const或者从实参中删除顶层const</li></ul></li><li>通过const转换实现的匹配</li><li>通过类型提升实现的匹配</li><li>通过算术类型转换实现的匹配</li><li>通过类类型转换实现的匹配</li></ol><h2 id="函数指针">7. 函数指针</h2><p><strong>函数指针</strong>是指针,它指向的是函数。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。例如:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">lengthCompare</span><span class="hljs-params">(<span class="hljs-type">const</span> string &, <span class="hljs-type">const</span> string &)</span></span>;<br><span class="hljs-comment">//上面函数的类型是</span><br><span class="hljs-built_in">bool</span>(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&)<br><span class="hljs-comment">//声明一个对应的函数指针</span><br><span class="hljs-built_in">bool</span> (*pf)(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&);<span class="hljs-comment">//指针pf是函数指针,未初始化</span><br></code></pre></td></tr></table></figure><p>*<strong>pf两端的括号不能少</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//没有括号的话是定义一个名为pf的函数,返回值是 bool*</span><br><span class="hljs-function"><span class="hljs-type">bool</span> *<span class="hljs-title">pf</span><span class="hljs-params">(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&)</span></span>;<br></code></pre></td></tr></table></figure><h4 id="使用函数指针">使用函数指针</h4><p>函数名和数组名一样,直接使用名字(不用取地址符)会自动地转换为指针,例如</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++">pf = lengthCompare;<span class="hljs-comment">//pf 指向名为lengthCompare的函数</span><br>pf = &lengthCompare;<span class="hljs-comment">//和上面的等价,取地址符是可选的</span><br></code></pre></td></tr></table></figure><p>此外,我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">bool</span> b1 = <span class="hljs-built_in">pf</span>(<span class="hljs-string">"hello"</span>, <span class="hljs-string">"wolrd"</span>); <span class="hljs-comment">//调用lengthCompare函数</span><br><span class="hljs-type">bool</span> b1 = (*pf)(<span class="hljs-string">"hello"</span>, <span class="hljs-string">"wolrd"</span>);<span class="hljs-comment">//等价的调用</span><br><span class="hljs-type">bool</span> b3 = <span class="hljs-built_in">lengthCompare</span>(<span class="hljs-string">"hello"</span>, <span class="hljs-string">"wolrd"</span>);<span class="hljs-comment">//另一个等价的调用</span><br></code></pre></td></tr></table></figure><p>函数指针也可以赋予 <code>nullptr</code> ,函数指针赋值要和定义的类型一致才可以赋值。</p><h4 id="函数指针形参">函数指针形参</h4><p>和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//第三个参数是函数类型,它会自动转换成函数的指针</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">useBigger</span><span class="hljs-params">(<span class="hljs-type">const</span> string &s1, <span class="hljs-type">const</span> string &s2, <span class="hljs-type">bool</span> pf(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&))</span>;</span><br><span class="hljs-function"><span class="hljs-comment">//等价声明</span></span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">useBigger</span><span class="hljs-params">(<span class="hljs-type">const</span> string &s1, <span class="hljs-type">const</span> string &s2, <span class="hljs-type">bool</span> (*pf)(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&))</span>;</span><br><span class="hljs-function"><span class="hljs-comment">//函数调用</span></span><br><span class="hljs-function"><span class="hljs-title">useBigger</span><span class="hljs-params">(s1, s2, lengthCompare)</span></span>; <span class="hljs-comment">//函数名自动转换为函数指针</span><br></code></pre></td></tr></table></figure><p>使用别名简化写法</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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 c++"><span class="hljs-comment">//Func和Func2是函数类型</span><br><span class="hljs-function"><span class="hljs-keyword">typedef</span> <span class="hljs-type">bool</span> <span class="hljs-title">Func</span><span class="hljs-params">(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&)</span></span>;<br><span class="hljs-function"><span class="hljs-keyword">typedef</span> <span class="hljs-title">decltype</span><span class="hljs-params">(lengthCompare)</span> Func2</span>;<span class="hljs-comment">//等价类型</span><br><span class="hljs-comment">//FuncP 和FuncP2是指向函数的指针</span><br><span class="hljs-function"><span class="hljs-keyword">typedef</span> <span class="hljs-title">bool</span><span class="hljs-params">(*FuncP)</span><span class="hljs-params">(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&)</span></span>;<br><span class="hljs-function"><span class="hljs-keyword">typedef</span> <span class="hljs-title">decltype</span><span class="hljs-params">(lengthCompare)</span> *FuncP2</span>;<span class="hljs-comment">//等价的类型</span><br><br><span class="hljs-comment">//使用上面的别名声明带函数指针形参的函数</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">useBigger</span><span class="hljs-params">(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&, Func)</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">useBigger</span><span class="hljs-params">(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&, FuncP2)</span></span>;<br></code></pre></td></tr></table></figure><h4 id="返回指向函数的指针">返回指向函数的指针</h4><p>和数组类似,我们不能返回函数,但是可以返回函数指针</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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 c++"><span class="hljs-comment">//定义别名,简化写法</span><br><span class="hljs-keyword">using</span> F = <span class="hljs-built_in">int</span>(<span class="hljs-type">int</span>*, <span class="hljs-type">int</span>);<span class="hljs-comment">//F是函数类型,不是指针</span><br><span class="hljs-keyword">using</span> PF = <span class="hljs-built_in">int</span>(*)(<span class="hljs-type">int</span>*, <span class="hljs-type">int</span>);<span class="hljs-comment">//PF是函数指针类型</span><br><br><span class="hljs-comment">//声明返回函数指针的函数</span><br><span class="hljs-function">PF <span class="hljs-title">f1</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span></span>;<span class="hljs-comment">//正确, PF是指针函数,f1返回指向函数的指针</span><br><span class="hljs-function">F <span class="hljs-title">f1</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span></span>;<span class="hljs-comment">//错误,F是函数类型,不能返回函数</span><br><span class="hljs-function">F *<span class="hljs-title">f1</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span></span>;<span class="hljs-comment">//正确,显示地指定返回类型是指向函数的指针</span><br><br><span class="hljs-comment">//原始的不使用别名声明方式</span><br><span class="hljs-built_in">int</span> (*<span class="hljs-built_in">f1</span>(<span class="hljs-type">int</span>))(<span class="hljs-type">int</span>*, <span class="hljs-type">int</span>);<br></code></pre></td></tr></table></figure><p>原始的声明是从里向外读, <code>(*f1(int))</code> 表示 <code>f1</code>是一个函数,参数是<code>int</code> ,返回的是指针 <code>*</code> ,指针指向的是函数类型 <code>int(int*, int)</code></p><p>在前面我们声明返回数组指针的函数使用过返回类型后置,同样这里也适用</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-keyword">auto</span> <span class="hljs-title">f1</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span> -> <span class="hljs-title">int</span><span class="hljs-params">(*)</span><span class="hljs-params">(<span class="hljs-type">int</span>*, <span class="hljs-type">int</span>)</span></span>;<br></code></pre></td></tr></table></figure><p>使用 <code>decltype</code> 自动检测类型</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">string::size_type <span class="hljs-title">sumLength</span><span class="hljs-params">(<span class="hljs-type">const</span> string&, <span class="hljs-type">const</span> string&)</span></span>;<br><span class="hljs-keyword">decltype</span>(sumLength) *<span class="hljs-built_in">getFcn</span>(<span class="hljs-type">const</span> string&);<br></code></pre></td></tr></table></figure><blockquote><p>decltype作用于函数时返回的是函数类型而非指针类型,所以需要显示地加上<code>*</code> 声明为指针。</p></blockquote>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>第5章 语句</title>
<link href="/2023/08/24/Ch5-%E8%AF%AD%E5%8F%A5/"/>
<url>/2023/08/24/Ch5-%E8%AF%AD%E5%8F%A5/</url>
<content type="html"><![CDATA[<h1 id="第5章-语句">第5章 语句</h1><h2 id="简单语句">1. 简单语句</h2><h4 id="表达式语句">表达式语句</h4><p>表达式加上分号就是一条语句</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs C++">ival + <span class="hljs-number">5</span>;<span class="hljs-comment">//一条没有实际作用的表达式语句</span><br>cout << ival;<span class="hljs-comment">//一条有用的表达式语句</span><br></code></pre></td></tr></table></figure><h4 id="空语句">空语句</h4><p>一个分号 <code>;</code> 就是空语句</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs C++">;<span class="hljs-comment">//空语句</span><br>ival = v1 + v2;;<span class="hljs-comment">//第二分号是空语句,没有影响</span><br><span class="hljs-keyword">while</span> (iter != svec.<span class="hljs-built_in">end</span>()) ;<span class="hljs-comment">//分号是空语句,导致下面的语句不会出现在循环中执行</span><br>++iter;<span class="hljs-comment">//不属于循环体的一部分</span><br></code></pre></td></tr></table></figure><blockquote><p>空语句单独不会有什么作用,但是和其他语句组合会有不同的作用。</p><p>注意:别漏写分号,也别多写分号</p></blockquote><h4 id="复合语句">复合语句</h4><p>复合语句也就<strong>块</strong>, 就是用一对 <code>{}</code>括起来的多个语句</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-keyword">while</span> (val < <span class="hljs-number">10</span>) {<br> sum += val;<br> ++val;<br>}<br></code></pre></td></tr></table></figure><blockquote><p>块不以分号作为结束,但是在声明定义类时<code>{}</code> 后要加<code>;</code></p></blockquote><h2 id="条件语句">2. 条件语句</h2><h3 id="if-语句">2.1 if 语句</h3><p><code>if</code> 语句的语法形式:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-keyword">if</span> (condition)<br> statement<br><span class="hljs-comment">//if else 语句</span><br><span class="hljs-keyword">if</span> (condition)<br> statement<br><span class="hljs-keyword">else</span><br> statement2<br></code></pre></td></tr></table></figure><p>使用注意:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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 c++"><span class="hljs-comment">//1. 注意使用花括号</span><br><span class="hljs-keyword">if</span> (grade < <span class="hljs-number">60</span>)<br> lettergrade = scores[<span class="hljs-number">0</span>];<br><span class="hljs-keyword">else</span><span class="hljs-comment">//错误: 下面的语句应该全部属于else的部分,需要加花括号</span><br> lettergrade = scorses[(grade - <span class="hljs-number">50</span>)/<span class="hljs-number">10</span>];<br><span class="hljs-keyword">if</span>(grade != <span class="hljs-number">100</span>)<br> <span class="hljs-keyword">if</span>(grade % <span class="hljs-number">10</span> > <span class="hljs-number">7</span>)<br> lettergrade += <span class="hljs-string">'+'</span>;<br><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(grade % <span class="hljs-number">10</span> < <span class="hljs-number">3</span>)<br> lettergrade += <span class="hljs-string">'-'</span>;<br><br><span class="hljs-comment">//2.悬垂else,else与最近的尚未匹配的if匹配</span><br><span class="hljs-keyword">if</span> (grade % <span class="hljs-number">10</span> > = <span class="hljs-number">3</span>)<br> <span class="hljs-keyword">if</span>(grade % <span class="hljs-number">10</span> > <span class="hljs-number">7</span>)<br> lettergrade += <span class="hljs-string">'+'</span>;<br><span class="hljs-keyword">else</span> <span class="hljs-comment">// 实际匹配内层的if</span><br> lettergrade += <span class="hljs-string">'-'</span>;<br></code></pre></td></tr></table></figure><blockquote><ul><li>尽量每个if-else中的语句块加上花括号<code>{}</code></li><li>if-else的匹配原则是就近原则,空格和缩进在c++是不会作用的,不像某些语言(点名python)</li></ul></blockquote><h3 id="switch-语句">2.2 switch 语句</h3><p>格式:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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 C++"><span class="hljs-keyword">switch</span>(lables) {<br> <span class="hljs-keyword">case</span> lable1:<br> statement;<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> lable2:<br> statement;<br> <span class="hljs-keyword">break</span>;<br> ...<br> <span class="hljs-keyword">default</span>:<br> statement;<br> <span class="hljs-keyword">break</span>;<br>}<br></code></pre></td></tr></table></figure><blockquote><p>注意:</p><ul><li>lable1, lable2,... 必须是<strong>整型常量表达式</strong></li><li>如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case分支,除非程序显示地中断了这一过程。</li><li>如果不是特殊的需求,每个case都需要加 <code>break</code>,否则会一直执行下面的标签</li><li><code>default</code> 标签建议写上</li><li>在不同两个标签定义的变量,不要跨标签使用(如果需要为某个分支定义并初始化一个变量,应该把变量定义在块内,即使用{})</li></ul></blockquote><h2 id="迭代语句">3. 迭代语句</h2><p><code>while</code>和<code>for</code>语句在执行循环体之前检查条件,<code>do while</code>语句先执行循环体,然后再检查条件。</p><h3 id="while语句">3.1 while语句</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-keyword">while</span> (condition)<br> statement<br></code></pre></td></tr></table></figure><h3 id="do-while-语句">3.2 do-while 语句</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-function"><span class="hljs-keyword">do</span></span><br><span class="hljs-function"> statement</span><br><span class="hljs-function"><span class="hljs-title">while</span> <span class="hljs-params">(condition)</span></span>; <span class="hljs-comment">// 不要忘记 ;</span><br></code></pre></td></tr></table></figure><h3 id="传统的for语句">3.3 传统的for语句</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-keyword">for</span> (init-statement; condition; expression)<br> statement<br></code></pre></td></tr></table></figure><blockquote><p>init_statement可以定义多个对象,但只能有一条声明语句,因此所有变量的类型必须相同。</p></blockquote><h3 id="范围for语句">3.4 范围for语句</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-keyword">for</span> (declaration : expression)<br> statement<br></code></pre></td></tr></table></figure><blockquote><p>expression表示的必须是一个序列,比如用花括号括起来的初始值列表、数组或者vector 或 string 等类型的对象,这些类型的共同特点是拥有能返回迭代器的begin 和 end 成员。</p></blockquote><h2 id="跳转语句">4. 跳转语句</h2><h3 id="break-语句">4.1 break 语句</h3><p><code>break</code> 负责终止离它最近的 while、dowhile、for、或switch语句</p><h3 id="continue-语句">4.2 continue 语句</h3><p>结束当前迭代,并进入下次迭代</p><h3 id="goto语句">4.3 goto语句</h3><p>从 goto 语句无条件跳转到同一函数内的另一条语句。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-keyword">goto</span> label;<br></code></pre></td></tr></table></figure><p>label是用于标识一条语句的标示符。 <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++">end: <span class="hljs-keyword">return</span>; <span class="hljs-comment">// 带标示符的语句</span><br></code></pre></td></tr></table></figure></p><blockquote><p>goto语句很好用,但是不建议用,会导致代码结构较乱</p></blockquote><h2 id="try语句块和异常处理">5. try语句块和异常处理</h2><h3 id="throw表达式">5.1 throw表达式</h3><p>throw用于抛出异常</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-keyword">if</span>(item1.<span class="hljs-built_in">isbn</span>() != item2.<span class="hljs-built_in">isbn</span>())<br> <span class="hljs-keyword">throw</span> <span class="hljs-built_in">runtime_error</span>(<span class="hljs-string">"Data must refer to same ISBN"</span>);<br></code></pre></td></tr></table></figure><h3 id="try-语句块">5.2 try 语句块</h3><p>语法形式</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-keyword">try</span>{<br> program-satements;<br>} <span class="hljs-built_in">catch</span> (exception-declaration) {<br> handler-statements;<br>} <span class="hljs-built_in">catch</span> (exception-declaration) {<br> handler-statements;<br>}<span class="hljs-comment">// ...</span><br></code></pre></td></tr></table></figure><blockquote><p>program-satements 是我们用运行的语句</p><p>catch (exception-declaration)表示的是可能会出现的异常以及相对应的异常处理语句</p></blockquote><h2 id="标准异常">5.3 标准异常</h2><p>c++标准库定义了一组类,用于报告标准库函数遇到的问题,它们分别定义在4个头文件中</p><ul><li><code>exception</code> 头文件定义了最通用的异常类,它只报告异常的发生,不提供任何额外信息</li><li><code>stdexcept</code> 头文件定义几种常用的异常类,详细见下表</li><li><code>new</code> 头文件定义了 bad_alloc异常类型</li><li><code>type_info</code> 头文件定义了bad_cast异常类型</li></ul><p><img src="https://kinvy-images.oss-cn-beijing.aliyuncs.com/Images/image-20211225124654765.png"></p>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>第4章 表达式</title>
<link href="/2023/08/23/Ch4-%E8%A1%A8%E8%BE%BE%E5%BC%8F/"/>
<url>/2023/08/23/Ch4-%E8%A1%A8%E8%BE%BE%E5%BC%8F/</url>
<content type="html"><![CDATA[<h1 id="第4章-表达式">第4章 表达式</h1><h2 id="基础">1. 基础</h2><p>一些运算符的基本概念</p><h3 id="基本概念">1.1 基本概念</h3><h4 id="重载运算符">重载运算符</h4><p>赋予基本运算符不同的含义和运算方式,使用重载运算符时,其包括运算对象的类型和返回值的类型,都是由该运算符定义的;但是运算对象的个数、运算符的优先级和结合律都是无法改变的。</p><h4 id="左值和右值">左值和右值</h4><p>左值,使用的是对象的<strong>身份</strong>(在内存中的位置);右值,使用的是对象的<strong>值</strong>(内容)。</p><h3 id="优先级与结合律">1.2 优先级与结合律</h3><p>基本的运算优先级和数学中的优先级一样。</p><p>括号无视优先级,在不确定默认的优先级时可以使用括号。</p><h2 id="算术运算符">2. 算术运算符</h2><p>常用算术运算符</p><p><img src="https://kinvy-images.oss-cn-beijing.aliyuncs.com/Images/image-20211224155915096.png"></p><h2 id="逻辑和关系运算符">3. 逻辑和关系运算符</h2><p>逻辑和关系运算符</p><p><img src="https://kinvy-images.oss-cn-beijing.aliyuncs.com/Images/image-20211224160003044.png"></p><p><strong>短路原则</strong>:</p><ul><li>对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值</li><li>对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值</li></ul><h2 id="赋值运算符">4. 赋值运算符</h2><p>赋值和初始化是两个不同的概念,虽然都使用 <code>=</code> 运算符</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> i = <span class="hljs-number">10</span>, k = <span class="hljs-number">0</span>;<span class="hljs-comment">//这里是初始化,不是赋值操作</span><br><span class="hljs-type">int</span> j;<span class="hljs-comment">//声明定义一个变量</span><br>j = <span class="hljs-number">1</span>;<span class="hljs-comment">//赋值操作</span><br></code></pre></td></tr></table></figure><p><strong>赋值运算符满足右结合律</strong>。对于多重赋值语句中的每一个对象,它的类型或者与右边对象的类型相同、或者可由右边对象的类型转换得到</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> i,j;<br>i = j = <span class="hljs-number">0</span>; <span class="hljs-comment">// 正确,都被赋值为0</span><br><span class="hljs-type">int</span> v, *p;<br>v = p = <span class="hljs-number">0</span>; <span class="hljs-comment">// 错误,不能把指针的值赋给int</span><br></code></pre></td></tr></table></figure><h2 id="递增和递减运算符">5. 递增和递减运算符</h2><p>递增(递减)运算符有前置(<code>++i</code>)和后置(<code>i++</code>)</p><p>前置将运算对象加 1 (或减 1),并返回<strong>改变后</strong>的对象;后置版本将运算对象加 1 (或减 1),但返回<strong>改变前</strong>那个值的副本 。</p><blockquote><p>如果没有特别的需求,建议使用前置的版本</p><p>e.g.: <code>*iter++</code>实现一个对象的遍历</p></blockquote><h2 id="成员访问运算符">6. 成员访问运算符</h2><p>成员运算符有<strong>点运算符</strong> 和<strong>箭头运算符</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//这两个表达式是等价的</span><br>ptr->mem;<br>(*ptr).mem;<span class="hljs-comment">//* 优先级低于 . 所以要加括号</span><br></code></pre></td></tr></table></figure><h2 id="条件运算符">7. 条件运算符</h2><p>条件运算符 <code>?:</code> 是一个三元运算符,格式:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++">cond ? expr1 : expr2;<br><span class="hljs-comment">//等价形式</span><br><span class="hljs-keyword">if</span> (cond)<br> expr1;<br><span class="hljs-keyword">else</span><br> expr2;<br></code></pre></td></tr></table></figure><h2 id="位运算符">8. 位运算符</h2><p>位运算符是在二进制的层面对数据操作,以下是常用的位运算符</p><p><img src="https://kinvy-images.oss-cn-beijing.aliyuncs.com/Images/image-20211224162959652.png"></p><h2 id="sizeof-运算符">9. sizeof 运算符</h2><p><code>sizeof</code>运算符返回一条表达式或一个类型名字所占的字节数。该运算符有两种形式</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++">Sales_data data, *p; <br><span class="hljs-built_in">sizeof</span>(type);<br><span class="hljs-keyword">sizeof</span> expr; <span class="hljs-comment">// 返回expr的类型的大小,不会实际计算</span><br><span class="hljs-keyword">sizeof</span> p; <span class="hljs-comment">// 指针所占的空间大小</span><br><span class="hljs-keyword">sizeof</span> *p; <span class="hljs-comment">// p所指类型的空间大小 </span><br></code></pre></td></tr></table></figure><h2 id="逗号运算符">10. 逗号运算符</h2><p><strong>逗号运算符</strong>含有两个运算对象,按照从左向右的顺序依次求值</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++">vector<<span class="hljs-type">int</span>>::size_type cnt = ivec.<span class="hljs-built_in">size</span>();<br><span class="hljs-comment">//将把从size到1的值赋给ivec的元素</span><br><span class="hljs-keyword">for</span>(vector<<span class="hljs-type">int</span>>::size_type ix = <span class="hljs-number">0</span>; <br> ix != ivec.size; ++ix, --cnt)<br> ivec[ix] = cnt;<br></code></pre></td></tr></table></figure><h2 id="类型转换">11. 类型转换</h2><h4 id="隐式转换">隐式转换</h4><p>由编译器完成,可能会出现精度损失</p><h4 id="显示转换">显示转换</h4><p>命名类型的强制类型转换,其形式如下:</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++">cast-<span class="hljs-built_in">name</span><type>(expression);<br></code></pre></td></tr></table></figure><p>其中<code>type</code>是转换的目标类型而 <code>expression</code>是要转换的值。如果 <code>type</code> 是引用类型,则结果是左值。</p><p><code>cast-name</code> 是<code>static_cast</code>、<code>dynamic_cast</code>、<code>const_cast</code>、<code>reinterpret_cast</code>中的一种。</p><p><strong>static_cast</strong> ,任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//进行强制类型转换以便执行浮点数除法, j, i是int</span><br><span class="hljs-type">double</span> slope = <span class="hljs-built_in">static_cast</span><<span class="hljs-type">double</span>>(j) / i;<br></code></pre></td></tr></table></figure><p><strong>const_cast</strong>, 只能改变运算对象的底层const</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">const</span> <span class="hljs-type">char</span> *pc;<br><span class="hljs-type">char</span> *p = <span class="hljs-built_in">const_cast</span><<span class="hljs-type">char</span>*>(pc); <span class="hljs-comment">// 正确,但是通过p写值是未定义的行为</span><br><span class="hljs-built_in">const_cast</span><string>(pc); <span class="hljs-comment">// 错误,const_cast只改变常量属性</span><br><br><span class="hljs-type">char</span> *q = <span class="hljs-built_in">static_cast</span><<span class="hljs-type">char</span>*>(pc); <span class="hljs-comment">//错误,static_cast不能转换掉const性质</span><br><span class="hljs-built_in">static_cast</span><string>(pc); <span class="hljs-comment">// 正确,字符串字面值转换成string类型</span><br></code></pre></td></tr></table></figure><p><strong>reinterpret_cast</strong>,通常为运算对象的位模式提供较低层次上的重新解释<em>这里不是很明白</em></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> *ip;<br><span class="hljs-type">char</span> *pc = <span class="hljs-built_in">reinterpret_cast</span><<span class="hljs-type">char</span>*>(ip);<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>第3章 字符串、向量和数组</title>
<link href="/2023/08/22/Ch3-%E5%AD%97%E7%AC%A6%E4%B8%B2%E3%80%81%E5%90%91%E9%87%8F%E5%92%8C%E6%95%B0%E7%BB%84/"/>
<url>/2023/08/22/Ch3-%E5%AD%97%E7%AC%A6%E4%B8%B2%E3%80%81%E5%90%91%E9%87%8F%E5%92%8C%E6%95%B0%E7%BB%84/</url>
<content type="html"><![CDATA[<h1 id="第3章-字符串向量和数组">第3章 字符串、向量和数组</h1><h2 id="命名空间的using声明">1. 命名空间的using声明</h2><p>在前面的示例程序中,输入和输出都是写成 <code>std::cin</code> ,<code>std::cout</code> , 我们可以在使用前使用 <code>using</code>声明</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><span class="hljs-keyword">using</span> std::cin;<br><span class="hljs-keyword">using</span> std::cout; <span class="hljs-keyword">using</span> std::endl;<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"></span>{<br> <span class="hljs-type">int</span> v1, v2;<br> cin >> v1 >> v2;<br> cout<<<span class="hljs-string">"Ths sum is"</span><< v1 + v2 <<endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p><strong>一行可放多条using声明语句,但每个名字都需要独立的using声明。</strong></p><blockquote><p>实际更常见的做法是使用 <code>using namespace std;</code>把整个命名空间都引入,这样std命名空间下的成员都可以使用了。</p><p>注意:头文件中尽量不要引入整个命名空间,因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using 声明,那么每个使用了该头文件的文件就都会有这个声明,这样可能会和自己写的一些类名冲突。</p></blockquote><h2 id="标准库类型-string">2. 标准库类型 string</h2><p><code>string</code>表示可变长的字符序列,<code>string</code>的使用需要包含一个头文件和命名空间</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><string></span></span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br></code></pre></td></tr></table></figure><h3 id="定义和初始化string对象">2.1 定义和初始化string对象</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++">string s1;<span class="hljs-comment">//默认初始化, s1是一个空串</span><br><span class="hljs-function">string <span class="hljs-title">s2</span><span class="hljs-params">(s1)</span></span>;<span class="hljs-comment">//s2是s1的副本</span><br>string s2 = s1; <span class="hljs-comment">//等价于s2(s1),s2是s1的副本</span><br><span class="hljs-function">string <span class="hljs-title">s3</span><span class="hljs-params">(<span class="hljs-string">"value"</span>)</span></span>; <span class="hljs-comment">//直接初始化,s3是字面值"value" 的副本,不包含最后的空字符</span><br>string s3 = <span class="hljs-string">"value"</span>; <span class="hljs-comment">//拷贝初始化,s3是字面 值“vale" 的副本</span><br><span class="hljs-function">string <span class="hljs-title">s4</span><span class="hljs-params">(n, <span class="hljs-string">'c'</span>)</span></span>;<span class="hljs-comment">//连续n个'c'组成的串</span><br></code></pre></td></tr></table></figure><h3 id="string对象上的操作">2.2 string对象上的操作</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++">os << s;<span class="hljs-comment">//将s写到输出流os中,返回os</span><br>is >> s;<span class="hljs-comment">//从is中读取字符串赋给s,字符串以 空白分隔,返回is</span><br><span class="hljs-built_in">getline</span>(is, s);<span class="hljs-comment">//从is中读取一行赋给s,返回is</span><br>s.<span class="hljs-built_in">empty</span>();<span class="hljs-comment">//s为空返回true,否则返回false</span><br>s.<span class="hljs-built_in">size</span>();<span class="hljs-comment">//返回s中字符的个数</span><br>s[n];<span class="hljs-comment">//返回s中第n个字符的引用,位置从0 开始</span><br>s1 + s2;<span class="hljs-comment">//返回s1和s2连接后的结果</span><br>s1 = s2;<span class="hljs-comment">//用s2的副本代替s1中原来的字符</span><br>s1 == s2;<span class="hljs-comment">//判断是否一致</span><br>s1 != s2;<span class="hljs-comment">//判断是否不一样</span><br>< , <=, >, >=<span class="hljs-comment">//通过字典中的顺序比较,对字母大小 写敏感</span><br></code></pre></td></tr></table></figure><blockquote><p>size()函数返回的类型是string::size_type</p></blockquote><p>字面值和string对象相加</p><p><strong>当把 <code>string</code>对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+) 的两侧的运算对象至少有一个是<code>string</code></strong>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++">string s1 = <span class="hljs-string">"hello"</span>;<br>string s4 = s1 + <span class="hljs-string">", "</span>; <span class="hljs-comment">//正确</span><br>string s5 = <span class="hljs-string">"hello"</span> + <span class="hljs-string">", "</span>; <span class="hljs-comment">//错误</span><br>string s6 = s1 + <span class="hljs-string">", "</span> + <span class="hljs-string">"world"</span>; <span class="hljs-comment">//正确</span><br>string s7 = <span class="hljs-string">"hello"</span> + <span class="hljs-string">", "</span> + s2; <span class="hljs-comment">//错误</span><br></code></pre></td></tr></table></figure><blockquote><p>字符串字面值与string是不同的类型</p></blockquote><h3 id="处理string对象中的字符">2.3 处理string对象中的字符</h3><p>在头文件 <code>cctype</code> 中定义了一组相关的函数</p><p><img src="https://kinvy-images.oss-cn-beijing.aliyuncs.com/Images/image-20211216191219702.png"></p><blockquote><p><code>cctype</code>是c语言的头文件,在c++中包含c的头文件有两种形式</p><ul><li><code>#include <ctype.h></code> 和c语言一样</li><li><code>#include <cctype></code> 不加 <code>.h</code>而是在前面加一个 <code>c</code></li></ul></blockquote><p><strong>基于范围的for语句</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-comment">//语法</span><br><span class="hljs-keyword">for</span> (declaration : expression)<br> statement<br> <br><span class="hljs-comment">//示例</span><br>string str = <span class="hljs-string">"helloWorld"</span>;<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> s : str) <span class="hljs-comment">//使用aotu自动类型推导</span><br>{<br> cout << s << endl;<br>}<br><span class="hljs-comment">//如果需要改变str中字符,用引用</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> &c : str)<br>{<br> c = <span class="hljs-built_in">toupper</span>(c);<span class="hljs-comment">//c是引用</span><br>}<br>cout << str << endl;<br></code></pre></td></tr></table></figure><blockquote><p>基于范围的for,只适用于可迭代的对象</p></blockquote><h2 id="标准库类型-vector">3. 标准库类型 vector</h2><p>标准库类型 vector表示对象的集合,其中所有对象的类型都相同。用vector需要包含下面的头文件和声明命名空间</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-keyword">using</span> std::vector;<span class="hljs-comment">//或者 using namespace std; 引入std命名空间所有的成员</span><br></code></pre></td></tr></table></figure><p><code>vector</code>类似于数组,但是比数组用于更多的操作。<code>vector</code>是一个模板类,所谓模板就是该类内部中的属性没有指定某种特定的数据类型,我们可以在声明vector时指定数据类型(包括基本类型和自定义类型)</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++">vector<<span class="hljs-type">int</span>> ivec;<span class="hljs-comment">//ivec时int类型的对象集合</span><br>vector<Sales_item> Sales_vec;<span class="hljs-comment">//Sales_vec是Sales_item类型的对象集合</span><br>vector<vector<string>> file;<span class="hljs-comment">//该向量的元素是vector对象</span><br></code></pre></td></tr></table></figure><h3 id="定义和初始化vector对象">3.1 定义和初始化vector对象</h3><p>定义vector对象的常用方法</p><p><img src="/2023/08/22/Ch3-%E5%AD%97%E7%AC%A6%E4%B8%B2%E3%80%81%E5%90%91%E9%87%8F%E5%92%8C%E6%95%B0%E7%BB%84/1692618512009.png"></p><blockquote><p>注意 <code>()</code> 和 <code>{}</code> 初始化vector对象的区别。</p><p><code>()</code>是用来构造vector对象;<code>{}</code>是列表初始化该对象</p></blockquote><p><strong>如果初始化时使用了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector对象了</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++">vector<string> v5{<span class="hljs-string">"hi"</span>}; <span class="hljs-comment">//列表初始化:v5有一个元素</span><br><span class="hljs-function">vector<string> <span class="hljs-title">v6</span><span class="hljs-params">(<span class="hljs-string">"hi"</span>)</span></span>; <span class="hljs-comment">//错误:不能使用字符串字面值构建vector对象</span><br>vector<string> v7{<span class="hljs-number">10</span>}; <span class="hljs-comment">//v7有10个默认初始化的元素</span><br>vector<string> v8{<span class="hljs-number">10</span>,<span class="hljs-string">"hi"</span>}; <span class="hljs-comment">//v8有10个值为"hi"的元素</span><br></code></pre></td></tr></table></figure><h3 id="vector操作">3.2 vector操作</h3><p>vector常用的操作</p><p><img src="/2023/08/22/Ch3-%E5%AD%97%E7%AC%A6%E4%B8%B2%E3%80%81%E5%90%91%E9%87%8F%E5%92%8C%E6%95%B0%E7%BB%84/1692618879702.png"></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++">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>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>};<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> &i : v)<span class="hljs-comment">//使用引用可以改变v中的值, </span><br> i *= i;<span class="hljs-comment">//计算平方</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> i : v)<span class="hljs-comment">//普通</span><br> cout << i << <span class="hljs-string">" "</span>;<br></code></pre></td></tr></table></figure><blockquote><p>vector使用注意事项:</p><ol type="1"><li>不能使用下标形式添加元素</li><li>不要在范围for中改变vector的大小(比如增加元素等操作)</li></ol></blockquote><h2 id="迭代器">4. 迭代器</h2><p>迭代器可以理解成一种特殊的指针,他有指针类似的操作,除此之外还有自己独特的一些操作。</p><h3 id="使用迭代器">4.1 使用迭代器</h3><p>通常是使用 <code>being</code> 和 <code>end</code>方法获取迭代器,<code>begin</code> 返回第一个元素,<code>end</code>返回最后元素的<strong>下一个位置</strong>,所以<code>end</code>返回的迭代器叫做<strong>尾后迭代器</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">auto</span> b = v.<span class="hljs-built_in">begin</span>(), e = v.<span class="hljs-built_in">end</span>();<span class="hljs-comment">//b和e类型一样,具体类型后面有说明</span><br></code></pre></td></tr></table></figure><blockquote><p>如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器</p></blockquote><p>迭代器和指针类似,所以指针有的运算符,迭代器基本也有</p><p><img src="/2023/08/22/Ch3-%E5%AD%97%E7%AC%A6%E4%B8%B2%E3%80%81%E5%90%91%E9%87%8F%E5%92%8C%E6%95%B0%E7%BB%84/1692619629681.png"></p><blockquote><p>因为 end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作。</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">string <span class="hljs-title">s</span><span class="hljs-params">(<span class="hljs-string">"some string"</span>)</span></span>;<br><span class="hljs-comment">//将第一个单词改为大写形式</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> it = s.<span class="hljs-built_in">begin</span>(); it != s.<span class="hljs-built_in">end</span>() && !<span class="hljs-built_in">isspace</span>(*it); ++it)<br> *it = <span class="hljs-built_in">toupper</span>(*it);<span class="hljs-comment">//将当前字符改成大写形式</span><br></code></pre></td></tr></table></figure><blockquote><p>for循环中使用!=而非<进行判断:所有标准库容器的迭代器都定义了==和!=,而只有string和vector等一些标准库类型有下标运算符。</p></blockquote><p><strong>迭代器类型</strong></p><p>vector 和 string对应的迭代器类型:iterator和const_iterator</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++">vector<<span class="hljs-type">int</span>>::iterator it1;<br>string::iterator ii2;<br><br>vector<<span class="hljs-type">int</span>>::const_iterator it3;<br>string::const_iterator it4;<br></code></pre></td></tr></table></figure><blockquote><p><code>it1</code>, <code>it2</code>是对应类型的迭代器,可以读写。对于常量对象(用const修饰的对象)需要使用<code>const_iterator</code> ,不是常量对象也可使用<code>const_iterator</code> ,只是const迭代器只能读不能修改元素。</p></blockquote><p><strong>begin和end</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++">vector<<span class="hljs-type">int</span>> v;<br><span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>> cv;<br><span class="hljs-keyword">auto</span> it1 = v.<span class="hljs-built_in">begin</span>(); <span class="hljs-comment">//it1的类型是 vector<int>::itrerator</span><br><span class="hljs-keyword">auto</span> it2 = cv.<span class="hljs-built_in">begin</span>(); <span class="hljs-comment">//it2的类型是 vector<int>::const_iterator</span><br><span class="hljs-keyword">auto</span> it3 = v.<span class="hljs-built_in">cbegin</span>(); <span class="hljs-comment">//it3的类型是 vector<int>::const_iterator</span><br></code></pre></td></tr></table></figure><p><strong>解引用和成员访问</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">//it是vector对象的迭代器</span><br>(*it).<span class="hljs-built_in">empty</span>();<span class="hljs-comment">//解引用it,得到vector对象,调用 vector的empty方法</span><br>*it.<span class="hljs-built_in">empty</span>();<span class="hljs-comment">//错误,这里是访问it中empty的方法, 而it中并没有这个方法</span><br>it-><span class="hljs-built_in">empty</span>();<span class="hljs-comment">//使用箭头运算符和 (*it).empty();一样</span><br></code></pre></td></tr></table></figure><h3 id="迭代器运算">4.2 迭代器运算</h3><p><code>vector</code> 和 <code>string</code> 迭代器支持的运算</p><p><img src="/2023/08/22/Ch3-%E5%AD%97%E7%AC%A6%E4%B8%B2%E3%80%81%E5%90%91%E9%87%8F%E5%92%8C%E6%95%B0%E7%BB%84/1692620516294.png"></p><h2 id="数组">5. 数组</h2><p>数组类似于vector,但是<strong>数组的大小确定不变</strong></p><h3 id="数组的定义和初始化">5.1 数组的定义和初始化</h3><p><strong>维度必须是一个常量表达式</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">unsigned</span> cnt = <span class="hljs-number">42</span>;<br><span class="hljs-keyword">constexpr</span> <span class="hljs-type">unsigned</span> sz = <span class="hljs-number">42</span>;<br><span class="hljs-type">int</span> arr[<span class="hljs-number">10</span>];<br><span class="hljs-type">int</span> *parr[sz];<span class="hljs-comment">//42个整型指针的数组</span><br>string bad[cnt];<span class="hljs-comment">//错误,cnt不是常量表达式</span><br>string strs[<span class="hljs-built_in">get_size</span>()]; <span class="hljs-comment">//get_size是constexpr时正确;否则错误</span><br></code></pre></td></tr></table></figure><p><strong>显示初始化</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">const</span> <span class="hljs-type">unsigned</span> sz = <span class="hljs-number">3</span>;<br><span class="hljs-type">int</span> ial[sz] = {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>};<span class="hljs-comment">// 含有 3 个元素的数组,元 素值分别是 0, 1, 2</span><br><span class="hljs-type">int</span> a2[] = {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>}; <span class="hljs-comment">//维度是3的数组</span><br><span class="hljs-type">int</span> a3[<span class="hljs-number">5</span>] = {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>};<span class="hljs-comment">// 等价于 a3[] = {0, 1, 2, 0, 0}</span><br>string a4[<span class="hljs-number">3</span>] = {<span class="hljs-string">"hi"</span>, <span class="hljs-string">"byeM"</span>};<span class="hljs-comment">// 等价于 a4[] = {"hi”,"bye", ""}</span><br><span class="hljs-type">int</span> a5[<span class="hljs-number">2</span>] = {<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>};<span class="hljs-comment">// 错误:初始值过多</span><br></code></pre></td></tr></table></figure><h4 id="字符数组的特殊性">字符数组的特殊性</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">char</span> a1[] = {<span class="hljs-string">'C'</span>, <span class="hljs-string">'P'</span>, <span class="hljs-string">'P'</span>};<span class="hljs-comment">//没有空字符</span><br><span class="hljs-type">char</span> a2[] = {<span class="hljs-string">'C'</span>, <span class="hljs-string">'P'</span>, <span class="hljs-string">'P'</span>, <span class="hljs-string">'\0'</span>};<span class="hljs-comment">//手动添加空字符 </span><br><span class="hljs-type">char</span> a3[] = <span class="hljs-string">"c++"</span>;<span class="hljs-comment">//自动添加空字符</span><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> a4[<span class="hljs-number">3</span>] = <span class="hljs-string">"c++"</span>;<span class="hljs-comment">//错误,没有空间存放空字符</span><br></code></pre></td></tr></table></figure><p><strong>不允许拷贝和赋值</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> a[] = {<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>};<br><span class="hljs-type">int</span> a2[] = a;<span class="hljs-comment">//错误,不允许用一个数组初始化另一个数组</span><br>a2 = a; <span class="hljs-comment">//错误,不能把一个数组直接赋值给另一个数组</span><br></code></pre></td></tr></table></figure><h4 id="复杂的数组声明">复杂的数组声明</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> arr[<span class="hljs-number">10</span>];<br><span class="hljs-type">int</span> *ptrs[<span class="hljs-number">10</span>];<span class="hljs-comment">//ptrs是含有10个整型指针的数组</span><br><span class="hljs-type">int</span> &refs[<span class="hljs-number">10</span>] = <span class="hljs-comment">/* ? */</span> ;<span class="hljs-comment">//错误,不存在引用的数组</span><br><span class="hljs-built_in">int</span> (*Parray)[<span class="hljs-number">10</span>] = &arr;<span class="hljs-comment">//Parray是一个指向 int[10] 类型的指针 </span><br><span class="hljs-built_in">int</span> (&arrRef)[<span class="hljs-number">10</span>] = arr;<span class="hljs-comment">//arrRef是一个 int[10]类型的引用</span><br><span class="hljs-type">int</span> *(&arry)[<span class="hljs-number">10</span>] = ptrs;<span class="hljs-comment">//arry是一个引用,指向的是含有10个int*的数组</span><br></code></pre></td></tr></table></figure><blockquote><p>对于上面这些声明,使用<strong>从内至外</strong>的方法读比较合适。</p></blockquote><h4 id="使用数组初始化-vector对象">使用数组初始化 vector对象</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> int_arr[] = {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>};<br><span class="hljs-function">vector<<span class="hljs-type">int</span>> <span class="hljs-title">ivec</span><span class="hljs-params">(begin(int_arr), end(int_arr))</span></span>;<br></code></pre></td></tr></table></figure><h3 id="指针和数组">5.2 指针和数组</h3><p>在c++中,<strong>数组名就是指针,保存的是数组变量的首地址</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> arr[<span class="hljs-number">10</span>];<br><span class="hljs-comment">//p1和p2是等价的</span><br><span class="hljs-type">int</span> *p1 = arr;<br><span class="hljs-type">int</span> *p2 = &arr[<span class="hljs-number">0</span>];<br></code></pre></td></tr></table></figure><blockquote><p>数组就是指针,在把数组作为参数传入函数中,必须手动维护一个数组大小的变量</p><p>因为传入的数组是指针,无法获取到数组的长度</p></blockquote><p><span style="border:2px solid Red">C++11</span> <strong>标准库函数<code>begin</code> 和 <code>end</code></strong></p><p>这两个函数可以获取数组的头元素和尾后元素(最后一个元素的地址)</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> ai[] = {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>};<br><span class="hljs-type">int</span> *beg = <span class="hljs-built_in">begin</span>(ia);<span class="hljs-comment">//指向ia首元素的指针</span><br><span class="hljs-type">int</span> *last = <span class="hljs-built_in">end</span>(ia);<span class="hljs-comment">//指向ia尾元素的下一个位置的指针</span><br></code></pre></td></tr></table></figure><p><strong>指针的运算</strong></p><p>指向数组元素的指针可以执行前序所述的迭代器运算。给(从)一个指针加上(减去)某整数值,结果仍是指针。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> arr[<span class="hljs-number">5</span>] ={<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>};<br><span class="hljs-type">int</span> *p = arr; <span class="hljs-comment">// 等价于int *p = &arr[0]</span><br><span class="hljs-type">int</span> *p2 = p + <span class="hljs-number">4</span>; <span class="hljs-comment">// p2指向arr的尾元素 arr[4]</span><br><span class="hljs-type">int</span> a = *(arr + <span class="hljs-number">4</span>); <span class="hljs-comment">// 将a初始化成arr[4]的值</span><br></code></pre></td></tr></table></figure><p><strong>下标和指针</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> arr[] = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>};<br><span class="hljs-type">int</span> *p = &ia[<span class="hljs-number">2</span>]; <span class="hljs-comment">// p指向arr[2]</span><br><span class="hljs-type">int</span> j = p[<span class="hljs-number">1</span>]; <span class="hljs-comment">// p[1]等价于 *(p+1),即arr[3]</span><br><span class="hljs-type">int</span> k = p[<span class="hljs-number">-2</span>]; <span class="hljs-comment">// 等价于*(p-2),即arr[0]表示的元素</span><br></code></pre></td></tr></table></figure><h3 id="c风格的字符串">5.3 C风格的字符串</h3><p>在c语言中通常是 <code>const char*</code> 表示字符串,并且以空字符<code>\0</code> 为结尾。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">const</span> <span class="hljs-type">char</span>* str = <span class="hljs-string">"heo\0ll"</span>;<span class="hljs-comment">//定义一个c风格的字符串,并且在中间加入一个结束符</span><br><span class="hljs-comment">//因为结束符的原因,c的函数库一些操作会出现意想不到的结果</span><br><span class="hljs-built_in">strlen</span>(str);<span class="hljs-comment">//计算str的长度,结果是3,计算方式是遇到空字符结束</span><br></code></pre></td></tr></table></figure><blockquote><p>c中的字符串是以空字符 <code>\0</code>判断字符串结束,如果我们自己的定义的字符数组或是字符常量中没有<code>\0</code> 或是字符中间有 <code>\0</code> 都不能得到正确的结果</p></blockquote><p><strong>C和C++字符串的转换</strong></p><p>在C++中是定义了一个 <code>string</code> 类作为字符串类型,<code>string</code> 类中重载了系列的运算符,所以从c 风格(<code>const char*</code>) 到c++风格(<code>string</code>)的转换都是自动完成的。以下是一些注意点</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 c++"><span class="hljs-comment">//从 c到 c++ const char* ---> string</span><br><span class="hljs-comment">//1. 使用字符串字面v初始化string类型,本章第二节</span><br><span class="hljs-function">string <span class="hljs-title">s3</span><span class="hljs-params">(<span class="hljs-string">"value"</span>)</span></span>;<span class="hljs-comment">//s3是字面值 "value" 的副本,不包含最后的空字符</span><br>string s3 = <span class="hljs-string">"value"</span>;<span class="hljs-comment">//同上</span><br><span class="hljs-comment">//2. string 重载了 + 运算符,可以直接拼接, s1,s2是string类型</span><br>string s4 = s1 + <span class="hljs-string">", "</span>;<span class="hljs-comment">//正确</span><br>string s5 = <span class="hljs-string">"hello"</span> + <span class="hljs-string">", "</span>;<span class="hljs-comment">//错误</span><br>string s6 = s1 + <span class="hljs-string">", "</span> + <span class="hljs-string">"world"</span>;<span class="hljs-comment">//正确</span><br>string s7 = <span class="hljs-string">"hello"</span> + <span class="hljs-string">", "</span> + s2;<span class="hljs-comment">//错误</span><br><br><span class="hljs-comment">//从 c++到 c, string ---> const char* </span><br><span class="hljs-comment">//在string类中定义了一个c_str的成员函数,可以返回 const char*</span><br><span class="hljs-type">char</span> *str = s;<span class="hljs-comment">//错误:不能直接使用stringdvx初始化char*</span><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *str = s.<span class="hljs-built_in">c_str</span>();<span class="hljs-comment">//正确,s是一个string对象,调用c_str()方法</span><br></code></pre></td></tr></table></figure><blockquote><p>说明:在c++的string类种是重载了<code>+</code>,改运算符返回的也是string类型,分析</p><ol type="1"><li>s4, <code>s1</code>是string类型,会调用重载的 <code>+</code></li><li>s5, <code>hello</code> 和 <code>,</code> 都是<code>const char*</code> 类型,该类型并没有定义 <code>+</code> 运算</li><li>s6, <code>(s1 + ", ")</code> 和s4 一样,得到的是一个临时的 string,再 <code>+ "world"</code></li><li>s7, <code>"hello" + ", "</code> 的错误和s5一样</li></ol></blockquote><h2 id="多维数组">6. 多维数组</h2><p><strong>初始化</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> arr[<span class="hljs-number">3</span>][<span class="hljs-number">4</span>] = {{<span class="hljs-number">0</span>},{<span class="hljs-number">4</span>},{<span class="hljs-number">8</span>}};<span class="hljs-comment">//初始化每行的首元素</span><br><span class="hljs-type">int</span> arr2[<span class="hljs-number">3</span>][<span class="hljs-number">4</span>] = {<span class="hljs-number">0</span>,<span class="hljs-number">4</span>,<span class="hljs-number">8</span>,<span class="hljs-number">9</span>}; <span class="hljs-comment">// 初始化第一行</span><br></code></pre></td></tr></table></figure><p><strong>使用范围 for 语句处理多维数组</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> ia[<span class="hljs-number">3</span>][<span class="hljs-number">4</span>];<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> &row : ia)<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> col : row)<br> cout<<col<<endl;<br></code></pre></td></tr></table></figure><blockquote><p>将外层循环的控制变量声明成了引用类型,是为了<strong>避免数组被自动转成指针</strong>,因为<code>auto row : ia</code>会将row的类型识别为int*,则内层循环不合法。</p><p>要使用范围 for语句处理多维数组,除了最内层的循坏外,其他所有循环的控制变量都应该是引用类型。</p></blockquote><p><strong>指针和多维数组 </strong></p><figure class="highlight plaintext"><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 ;">int arr[3][4];<br>int (*p)[4] = arr; // p指向含有4个int型的数组<br>p = &arr[2]; // p指向arr的尾元素<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>第2章 变量和基本类型</title>
<link href="/2023/08/21/Ch2-%E5%8F%98%E9%87%8F%E5%92%8C%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B/"/>
<url>/2023/08/21/Ch2-%E5%8F%98%E9%87%8F%E5%92%8C%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B/</url>
<content type="html"><![CDATA[<h1 id="第2章-变量和基本类型">第2章 变量和基本类型</h1><h2 id="基本内置类型">1. 基本内置类型</h2><p>C++ 基本的数据类型有算术类型和空类型,算术类型就是基本的整型和浮点型的数据类型。</p><figure><img src="/2023/08/21/Ch2-%E5%8F%98%E9%87%8F%E5%92%8C%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B/基本数据类型.png" alt="基本数据类型"><figcaption aria-hidden="true">基本数据类型</figcaption></figure><p>不同类型之间的转换需要注意,有的转换可能是我们不想发生的。</p><p><strong>字面值常量</strong></p><ul><li>数值型,编译器会根据数字形式对应一种基本的数据类型。</li><li>字符和字符串字面常量</li><li>布尔字面值和指针字面值</li></ul><blockquote><p>与其他整型不同, 字符型被分为了三种:char、signed char 和 unsignedchar,但表现形式只有带符号和无符号两种。<strong>char实际上会表现为signed char和unsignedchar中的一种,由编译器决定。</strong></p></blockquote><h2 id="变量">2. 变量</h2><p>变量提供一个具名的、可供程序操作的存储空间。</p><h3 id="变量定义">2.1 变量定义</h3><p>C++变量的定义要指定变量的类型。 <figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-comment">//变量定义并初始化</span><br><span class="hljs-type">int</span> a = <span class="hljs-number">2</span>;<br><span class="hljs-comment">//变量定义</span><br><span class="hljs-type">int</span> b;<br><span class="hljs-comment">//变量赋值</span><br>b = <span class="hljs-number">1</span>;<br></code></pre></td></tr></table></figure></p><blockquote><p>在C++中变量的初始化和赋值是有区别的, <code>int a = 2;</code>的<code>=</code> 运算符表示的是初始化,</p><p>而在 <code>b=1;</code> 中的 <code>=</code> 是赋值。</p></blockquote><p><span style="border:2px solid Red">C++11</span> 初始化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> val = <span class="hljs-number">1</span>;<br><span class="hljs-type">int</span> val = {<span class="hljs-number">0</span>};<br><span class="hljs-type">int</span> val{<span class="hljs-number">0</span>};<br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">val</span><span class="hljs-params">(<span class="hljs-number">0</span>)</span></span>;<br></code></pre></td></tr></table></figure><p>使用 <code>{}</code>来初始化变量,称为<strong>列表初始化</strong>,如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">long</span> <span class="hljs-type">double</span> pi = <span class="hljs-number">3.14</span>;<br><span class="hljs-type">int</span> a{pi};<span class="hljs-comment">//错误,存在丢失信息的风险,无法通过编译</span><br></code></pre></td></tr></table></figure><blockquote><p>总结:C++变量初始化的语法形式有三种:<code>=</code> , <code>()</code>, <code>{}</code></p></blockquote><p><strong>默认初始化</strong>,定义变量时没有初始化变量的值,则变量会被默认初始化。默认初始化的值取决于变量定义的类型。<strong>定义在函数体内的局部变量和类中的成员属性是不会被初始化的</strong>。所以不用试图使用任何方式去访问这些变量。</p><h3 id="声明和定义">2.2 声明和定义</h3><p><strong>声明</strong>,使程序知道变量(对象)的存在</p><p><strong>定义</strong>,负责创建与名字关联的实体 </p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">extern</span> <span class="hljs-type">int</span> i;<span class="hljs-comment">//声明i而不定义</span><br><span class="hljs-keyword">extern</span> <span class="hljs-type">int</span> i = <span class="hljs-number">1</span>;<span class="hljs-comment">//extern失效,变成定义</span><br><span class="hljs-type">int</span> j;<span class="hljs-comment">//声明并定义j</span><br></code></pre></td></tr></table></figure><blockquote><p>变量能且只能被定义一次,但是可以被多次声明</p></blockquote><h3 id="标识符作用域">2.3-4 标识符、作用域</h3><h4 id="标识符">标识符</h4><p>变量命名按照规范,不要使用保留关键字。</p><ul><li>普通的局部变量和函数参数名使用小驼峰(第一个单词首字母小写,其他单词首字母大写),例: <code>userName</code></li><li>全局变量前加 <code>g_</code>, 后面的按小驼峰规则 ,<code>g_userName</code></li><li>静态变量前加 <code>s_</code> , 后面按小驼峰规则,<code>s_userName</code></li><li>类名使用大驼峰,所有单词的首字母大写 , <code>UserManage</code></li><li>类属性(成员变量)前面加 <code>m_</code> ,后面按小驼峰规则 ,<code>m_userName</code></li><li>常量全部使用大写,多个单词用<code>_</code> 分割,<code>MAX_NUMBER</code></li></ul><h4 id="作用域">作用域</h4><p>局部变量不宜和全局的变量重名,嵌套的块,内部的不要和外部的重名。</p><h2 id="复合类型">3. 复合类型</h2><p>一条声明语句由一个 <strong>基本数据类型</strong> 和紧随其后的一个<strong>声明符</strong> 列表组成。</p><h3 id="引用">3.1 引用</h3><p><strong>引用</strong> 就是为变量(对象)起一个别名</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> val = <span class="hljs-number">1024</span>;<br><span class="hljs-type">int</span> val1 = <span class="hljs-number">102</span>;<br><span class="hljs-type">int</span>& refVal = val;<span class="hljs-comment">//refVal指向val</span><br>refVal = val1;<span class="hljs-comment">//refVal引用并没有改变,只是改变了refVal指向的变量val的值,val = val1</span><br><span class="hljs-type">int</span> &refVal2;<span class="hljs-comment">//错误,引用必须初始化</span><br></code></pre></td></tr></table></figure><blockquote><p>注意:</p><ol type="1"><li>引用只能绑定在对象上,而不能与字面值或表达式计算结果绑定</li><li>引用必须初始化,且不能改变</li><li><code>&</code> 符号可以紧靠基本类型(int), 也可以紧靠变量名</li><li>因为引用本身不是一个对象,所以不能定义引用的引用</li></ol></blockquote><p><strong>以上说的引用都是左值引用,C++11还有右值引用</strong></p><h3 id="指针">3.2 指针</h3><p><strong>指针</strong>就是一个整数,没有实际的数值大小,只是一个编号,这个编号指向的是内存中的某个地址。</p><p>指针 vs 引用:</p><ul><li>指针本身是一个对象没允许对指针赋值和拷贝</li><li>在指针的生命周期内它可以先后指向几个不同的对象</li><li>指针无需在定义时赋值<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="但建议定义时初始化,如果没有想好指向哪个变量,可以初始化为空指针。">[1]</span></a></sup></li></ul><p>指针的定义</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> *p1, *p2;<span class="hljs-comment">//p1, p2都是指针类型, 定义在一条语句中,每个变量都要加 *</span><br></code></pre></td></tr></table></figure><blockquote><p>指针无论定义成什么基本类型,其值都是一个固定位数的整数,指针类型数据的大小取决于系统的位数</p><p>32bit的系统指针是4byte = 32 bit, 64 bit系统指针式 8 byte = 64bit</p></blockquote><p>指定类型的指针</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> val = <span class="hljs-number">102</span>;<br><span class="hljs-type">int</span> *p = &val;<span class="hljs-comment">//指针p指向val变量的内存地址</span><br></code></pre></td></tr></table></figure><blockquote><p>定义指定类型的指针只是为了提供操作数据时需要操作的字节数。</p><p>例如,<code>int</code>型的指针,在使用指针改变指向的数据时,改变的是以该指针变量为首地址的4个字节内存,</p><p>同样对<code>int</code>型指针的加或减的操作也是以4个字节为基本单位</p></blockquote><p><code>*</code>(解引用符) 和 <code>&</code>(取地址符)</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> val = <span class="hljs-number">10</span>;<br><span class="hljs-type">int</span> *p = <span class="hljs-literal">nullptr</span>; <span class="hljs-comment">//* 表示定义一个指针变量,并且初始化为空指针,等价于 int *p = 0</span><br>p = &val;<span class="hljs-comment">//& 表示取val变量的地址值</span><br>std::cout << *p << std::endl;<span class="hljs-comment">//* 表示解引用,取出p地址指向的值,即 val</span><br></code></pre></td></tr></table></figure><p>赋值和指针</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> val = <span class="hljs-number">42</span>;<br><span class="hljs-type">int</span> *p = <span class="hljs-number">0</span>;<br>p = &val; <span class="hljs-comment">//p的值被改变,现在p指向了val</span><br>*p = <span class="hljs-number">0</span>; <span class="hljs-comment">//val的值被改变,指针p并没有改变</span><br></code></pre></td></tr></table></figure><blockquote><p>指针使用建议:</p><ol type="1"><li>指针定义是可以不初始化,但建议定义时初始化,如果没有想好指向哪个变量,可以初始化为空指针</li><li>操作指针时,须确定操作的不是空指针和野指针(无效指针)</li></ol></blockquote><h3 id="理解复合类型的声明">3.3 理解复合类型的声明</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> i = <span class="hljs-number">42</span>;<br><span class="hljs-type">int</span> *p;<span class="hljs-comment">//p是int型的指针</span><br><span class="hljs-type">int</span> *&r = p;<span class="hljs-comment">//r是一个对int型指针p的引用</span><br><br>r = &i;<span class="hljs-comment">//r是一个指针引用,因此给r赋值&i就是令p指向i</span><br>*r = <span class="hljs-number">0</span>;<span class="hljs-comment">//解引用r,就是解引用指针p,将p指向的变量i的值改为0</span><br></code></pre></td></tr></table></figure><blockquote><p>Tip:面对一条比较复杂的指针或引用的声明语句时,<strong>从右向左</strong>读有助于弄清楚它的真实含义。</p><p>离变量名最近的符号(此例中是&r的符号&)对变量的类型有最直接的影响,因此 r 是一个引用。声明符的其余部分用以确定 r 引用的类型是什么,此例中的符号*说明r 引用的是一个指针。最后,声明的基本数据类型部分指出 r 引用的是一个 int指针 。</p></blockquote><h2 id="const-限定符">4. const 限定符</h2><p>const 用于定义一个不能改变的变量, 所以定义时就必须初始化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++">cont <span class="hljs-type">int</span> bufSize = <span class="hljs-number">512</span>;<span class="hljs-comment">//用字面值常量初始化</span><br>cont <span class="hljs-type">int</span> i = <span class="hljs-built_in">get_size</span>();<span class="hljs-comment">//用函数返回值初始化, 运行时初始化</span><br><span class="hljs-type">int</span> j = <span class="hljs-number">10</span>;<br>cont <span class="hljs-type">int</span> k = j;<span class="hljs-comment">//用其他变量初始化</span><br></code></pre></td></tr></table></figure><blockquote><p><code>const</code> 定义的变量只对本文件可见,要使其他文件也可见需使用<code>extern</code></p></blockquote><h3 id="const的引用">4.1 const的引用</h3><ul><li><p><strong>对常量的引用不能被用作修改它所绑定的对象</strong></p></li><li><p><strong>允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式</strong>(此时常量引用实际上绑定了一个临时量对象)</p></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> i = <span class="hljs-number">42</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">int</span> j = <span class="hljs-number">10</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">int</span> &r1 = i;<span class="hljs-comment">//正确,允许将 const int&绑定到一个普通int对象</span><br>r1 = <span class="hljs-number">10</span>;<span class="hljs-comment">//错误,不能通过常量引用改变i的值</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> &r2 = <span class="hljs-number">42</span>;<span class="hljs-comment">//正确</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> &r3 = r1 * <span class="hljs-number">2</span>;<span class="hljs-comment">//正确</span><br><span class="hljs-type">int</span>& r4 = j;<span class="hljs-comment">//错误</span><br><span class="hljs-type">int</span> &r4 = r1 * <span class="hljs-number">2</span>;<span class="hljs-comment">//错误</span><br></code></pre></td></tr></table></figure><p><strong>组合关系</strong></p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><code>int i</code></th><th style="text-align: center;"><code>cont int i</code></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><code>int &r</code></td><td style="text-align: center;">✔</td><td style="text-align: center;">❌</td></tr><tr class="even"><td style="text-align: center;"><code>cont int &r</code></td><td style="text-align: center;">✔</td><td style="text-align: center;">✔</td></tr></tbody></table><h3 id="指针和const">4.2 指针和const</h3><p>指向常量的指针和对常量的引用类似:</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><code>int i</code></th><th style="text-align: center;"><code>cont int i</code></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><code>int *p</code></td><td style="text-align: center;">✔</td><td style="text-align: center;">❌</td></tr><tr class="even"><td style="text-align: center;"><code>cont int *p</code></td><td style="text-align: center;">✔</td><td style="text-align: center;">✔</td></tr></tbody></table><blockquote><p>Tip:所谓指向常量的指针或引用,不过是指针或引用“自以为是”罢了,它们觉得自己指向了常量,所以自觉地不去改变所指对象的值。</p></blockquote><h4 id="常量指针">常量指针</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> errNumb = <span class="hljs-number">0</span>;<br><span class="hljs-type">int</span> *<span class="hljs-type">const</span> curErr = &errNumb;<span class="hljs-comment">//curErr将一直指向errNumb,不可以改变指向</span><br><span class="hljs-type">const</span> <span class="hljs-type">double</span> pi = <span class="hljs-number">3.14</span>;<br>cont <span class="hljs-type">double</span> *<span class="hljs-type">const</span> pip = &pi;<span class="hljs-comment">//pip是一个指向常量对象的常量指针</span><br>*curErr = <span class="hljs-number">1</span>; <span class="hljs-comment">//正确,把curErr所指的对象的值设为1</span><br>*pip = <span class="hljs-number">2.72</span>; <span class="hljs-comment">//错误,pip是一个指向常量的指针</span><br></code></pre></td></tr></table></figure><p><strong>从右向左读</strong></p><blockquote><p>C++ Primer 5th :</p><p><a id="const point">常量指针</a>:该变量是一个指针,指针本身是一个常量,即它的指向初始化后不可以改变</p></blockquote><h3 id="顶层const">4. 3 顶层const</h3><p><strong>顶层const</strong> :表示该变量(对象)本身是常量,不可以改变</p><p><strong>底层const</strong>: 表示指向的变量(对象)是一个常量</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> i = <span class="hljs-number">0</span>;<br><span class="hljs-type">int</span> *<span class="hljs-type">const</span> p1 = &i;<span class="hljs-comment">//p1是指针,p1的指向不能改变,顶层</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> ci = <span class="hljs-number">42</span>;<span class="hljs-comment">//ci是普通变量,ci的值不能改变,顶层</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> *p2 = &ci;<span class="hljs-comment">//p2是一个指针,它必须指向 const int型的数据,但是本身的指向可以改变,底层</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> *<span class="hljs-type">const</span> p3 = p2;<span class="hljs-comment">//第一个底层,第二个顶层</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> &r = ci;<span class="hljs-comment">//用于声明引用的const都是底层</span><br></code></pre></td></tr></table></figure><p><strong>引用类型的变量自带顶层const</strong>即引用一旦赋值(指向某个变量)就不可以再变化(指向另一个变量)</p><h3 id="constexpr和常量表达式">4.4 constexpr和常量表达式</h3><p>指值不会改变并且在编译过程就能得到计算结果的表达式。</p><p><strong>constexpr和指针</strong></p><blockquote><p>一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">const</span> <span class="hljs-type">int</span> *p1 = <span class="hljs-literal">nullptr</span>;<br><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> *p2 = <span class="hljs-literal">nullptr</span>;<br><span class="hljs-type">int</span> *<span class="hljs-type">const</span> p3 = <span class="hljs-literal">nullptr</span>;<br></code></pre></td></tr></table></figure><blockquote><p>p2和p3是等价的,<code>constexpr</code>修饰指针变量是被定义为顶层const</p></blockquote><h2 id="处理类型">5. 处理类型</h2><p>为了复杂程序更加易读易写,通常会给类型取别名,或是利用C++提供的特性自动推导复杂类型。</p><h3 id="类型别名">5.1 类型别名</h3><h4 id="typedef">typedef</h4><p>传统的方法是使用 <code>typedef</code> 关键字定义类型别名</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">typedef</span> <span class="hljs-type">double</span> wages;<span class="hljs-comment">//wages表示是double类型</span><br><span class="hljs-keyword">typedef</span> wages base, *p;<span class="hljs-comment">//base = wages = double, p = double*</span><br><span class="hljs-comment">//数组的别名</span><br><span class="hljs-keyword">typedef</span> <span class="hljs-type">int</span> arrT[<span class="hljs-number">10</span>];<span class="hljs-comment">//arrT是一个类型别名,他表示的类型是含有10个整数的数组</span><br><span class="hljs-keyword">using</span> arrT = <span class="hljs-type">int</span>[<span class="hljs-number">10</span>];<span class="hljs-comment">//和上面的等价</span><br></code></pre></td></tr></table></figure><h4 id="using">using</h4><p><span style="border:2px solid Red">C++11</span>提供了一种新的方式,使用 <code>using</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++"><span class="hljs-keyword">using</span> SI = Sales_item;<span class="hljs-comment">//Sales_item是一个类类型, SI表示是该类的别名</span><br></code></pre></td></tr></table></figure><blockquote><p>这里的 <code>using</code> 要和 <code>using namespace std;</code> 中的<code>using</code>区分开。后者是表示引入命名空间,类似于java和python的导包操作</p></blockquote><h4 id="指针常量和类型别名">指针、常量和类型别名</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">typedef</span> <span class="hljs-type">char</span>* pstring;<br><span class="hljs-type">const</span> pstring cstr = <span class="hljs-number">0</span>;<span class="hljs-comment">//char *const cstr = 0;</span><br><span class="hljs-type">const</span> pstring *ps;<span class="hljs-comment">//char **const ps;</span><br></code></pre></td></tr></table></figure><p><strong>第二行的定义不能理解成 <code>const char *cstr = 0;</code></strong></p><blockquote><p><code>const pstring</code> 中 <code>const</code> 是对<code>pstring</code> 的修饰,而 <code>pstring</code> 是一个<code>char*</code> 类型,因此 <code>const pstring</code> 是指向 char的<a href="#const%20point">常量指针</a> ,而并不是指向常量字符的指针</p></blockquote><h3 id="auto-类型说明符">5.2 auto 类型说明符</h3><p><code>auto</code> 类型说明符可以让编译器分析表达式所属的类型。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> val1 = <span class="hljs-number">1</span>, val2 = <span class="hljs-number">3</span>;<br><span class="hljs-keyword">auto</span> val = val1 + val2;<span class="hljs-comment">//编译器可以自动推出val为int类型</span><br><span class="hljs-keyword">auto</span> i = <span class="hljs-number">0</span>, *p = &i;<span class="hljs-comment">//正确,编译器通过字面值推出i为int,p为int*</span><br><span class="hljs-keyword">auto</span> sz = <span class="hljs-number">0</span>; pi = <span class="hljs-number">3.14</span>;<span class="hljs-comment">//错误,编译器无法推出类型, sz, pi类型不一致无法统一</span><br></code></pre></td></tr></table></figure><h4 id="复合类型常量和auto">复合类型、常量和auto</h4><ul><li><p>当使用引用类型推导类型时,<code>auto</code>推导的类型是引用指向变量的实际类型</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; &r = i;<br><span class="hljs-keyword">auto</span> a = r;<span class="hljs-comment">// r是int型的引用,因此a是int型</span><br></code></pre></td></tr></table></figure></li><li><p><code>auto</code>会忽略掉顶层const, 同时底层const则会保留下来</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">const</span> <span class="hljs-type">int</span> ci = i, &cr = ci;<br><span class="hljs-keyword">auto</span> b = ci;<span class="hljs-comment">//int b = ci;</span><br><span class="hljs-keyword">auto</span> c = cr;<span class="hljs-comment">//int c = cr; cr是ci的别名,ci本身是一个顶层const</span><br><span class="hljs-keyword">auto</span> d = &i;<span class="hljs-comment">//int *d = &i;</span><br><span class="hljs-keyword">auto</span> e = &ci;<span class="hljs-comment">//const int *e = &ci; 对常量对象取地址是一种底层const</span><br></code></pre></td></tr></table></figure><p>如果希望推断出的auto类型是一个顶层const,需要明确指出:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> f = ci;<span class="hljs-comment">// const int f = ci;</span><br></code></pre></td></tr></table></figure><p>指定引用类型</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">auto</span> &g = ci;<span class="hljs-comment">//const int &g = ci;</span><br><span class="hljs-keyword">auto</span> &h = <span class="hljs-number">42</span>;<span class="hljs-comment">//错误,int &h = 42;</span><br><span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> &j = <span class="hljs-number">42</span>; <span class="hljs-comment">//const int &j = 42;</span><br></code></pre></td></tr></table></figure></li></ul><blockquote><p>auto 使用建议:</p><p>使用auto声明变量一定要做到心里有数,你知道编译器会推断出的什么样的类型</p><p>通常使用auto是对于一些类型名比较复杂的变量,使用auto写起来更方便</p></blockquote><h3 id="decltype-类型指示符">5.3 decltype 类型指示符</h3><p><span style="border:2px solid Red">C++11</span> <code>decltype</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++"><span class="hljs-keyword">decltype</span>(<span class="hljs-built_in">f</span>()) sum = x;<span class="hljs-comment">//sum的类型和f()的返回类型一样</span><br></code></pre></td></tr></table></figure><p>通过 <code>f()</code> 推断出返回类型,但是并不会执行<code>f()</code></p><h4 id="decltype-和const">decltype 和const</h4><p><code>decltype</code>处理顶层 const和引用的方式和auto有点不同,如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">const</span> <span class="hljs-type">int</span> ci = <span class="hljs-number">0</span>, &cj = ci;<br><span class="hljs-keyword">decltype</span>(ci) x = <span class="hljs-number">0</span>;<span class="hljs-comment">//const int x = 0</span><br><span class="hljs-keyword">decltype</span>(cj) y = <span class="hljs-number">0</span>;<span class="hljs-comment">//const int &y = 0;</span><br><span class="hljs-keyword">decltype</span>(cj) z;<span class="hljs-comment">//错误, const int &z; 引用必须初始化</span><br></code></pre></td></tr></table></figure><h4 id="decltype-和引用">decltype 和引用</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> i = <span class="hljs-number">42</span>, *p = &i, &r = i;<br><span class="hljs-keyword">decltype</span>(r + <span class="hljs-number">0</span>) b;<span class="hljs-comment">//int b;</span><br><span class="hljs-keyword">decltype</span>(*p) c;<span class="hljs-comment">//错误, int &c; 引用需要初始化</span><br></code></pre></td></tr></table></figure><blockquote><p><code>r</code> 是引用 <code>decltype(r)</code> 是引用,但是<code>r + 0</code> 是一个int型数据</p><p>解引用指针得到的是指针所指的对象,,因此 <code>decltype(*p)</code> 是<code>int&</code></p></blockquote><p>变量加上 <code>()</code> 得到的是引用类型</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">decltype</span>((i)) d; <span class="hljs-comment">//错误, int& d; 引用类型需要初始化</span><br><span class="hljs-keyword">decltype</span>(i) e;<span class="hljs-comment">// int e;</span><br></code></pre></td></tr></table></figure><h2 id="自定义数据结构">6. 自定义数据结构</h2><p>这里的自定义数据结构就是指类类型的数据,在C++中定义类的关键字有<code>class</code>和 <code>struct</code></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ClassName</span>{<br><span class="hljs-comment">//属性</span><br><span class="hljs-comment">//方法</span><br>};<br><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">ClassName</span>{<br><span class="hljs-comment">//属性</span><br><span class="hljs-comment">//方法</span><br>};<br></code></pre></td></tr></table></figure><blockquote><p><code>class</code> 和 <code>struct</code>在功能上是完全一样的,两者唯一的不同是默认的权限不同</p><p><code>class</code>默认的权限是私有的(private), 而 <code>struct</code>是公有的(public)</p></blockquote><p><em>注意:c语言中的结构体是不能有方法(函数)</em></p><p>关于类更具体的介绍在后面的章节~~</p><section class="footnotes"><div class="footnote-list"><ol><li><span id="fn:1" class="footnote-text"><span>但建议定义时初始化,如果没有想好指向哪个变量,可以初始化为空指针。<a href="#fnref:1" rev="footnote" class="footnote-backref">↩︎</a></span></span></li></ol></div></section>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++ Primer</tag>
</tags>
</entry>
<entry>
<title>Ad Hoc</title>
<link href="/2023/03/31/20230323/"/>
<url>/2023/03/31/20230323/</url>
<content type="html"><![CDATA[<h2 id="汪浩东周报">20230323汪浩东周报</h2><p>本周主要对于AdHoc中的相关问题进行了更深的思考,以期找到其(包括无线场景)的问题,尤其是在与NDN相结合的方面,因为经过小组讨论认为,目前我们仍未搞清楚将大背景移到无线场景下时,会有什么不同以及新的问题的来源及解决方案。</p><h3 id="ad-hoc无线场景下的问题以广播风暴为例">AdHoc(无线)场景下的问题(以广播风暴为例)</h3><p>首先是这个大的背景问题,对于无线以及AdHoc这个新场景,其所面临的问题我们始终没有理清楚,以广播风暴为例。</p><p>首先,在无线AdHoc网络中,因为没有有线连接的支持,以及无线信道的有损性(在Adhoc中还需要考虑移动性),因此一般采用广播的方式传输包,注意,此处的广播指的是数据链路层,即使用共享广播信道,让所有主机接收同样的包。而这样的广播方式将会带来以下三个问题:#### 冗余对于冗余这个概念,之前我的理解是:“多个主机对相同的包都进行了广播,从而导致了冗余”,但实际上这个理解是错误的,其真正含义应该为:><strong><em>“当主机决定将收到的广播消息再次广播给邻居时,它的邻居实际上已经拥有了该消息”</em></strong></p><p>即它的邻居实际上已经收到了来自于其他主机的相同广播消息,再次广播是不必要的,因此称其为<strong><em>“冗余”</em></strong>。具体以下图来说明:<img src="/2023/03/31/20230323/OneDrive%20-%20USTC\mySVN\Whd\Md\assets\广播冗余.png" alt="广播冗余">如图所示,B在A的通信范围内,当B收到A的广播消息后,如果其决定再次广播,则能受益的区域(即没有被A广播到的区域)面积为:<span class="math display">\[\text{EAC}=|S_{B-A}| = |S_A|-|S_{A \cap B}| = \pi r^2 - \text{INTC}(d)= \pi r^2 - 4 \int_{d/2}^{r} \sqrt{r^2-x^2}\]</span> 当<span class="math inline">\(d=r\)</span>时,上式取得最大值,但也仅为<span class="math inline">\(0.61\pir^2\)</span>,考虑一般情况,假设B随机处于A的通信范围内任意位置,则可求得EAC的均值为:</p><p><span class="math display">\[ \int_0^r \frac{2\pi x \cdot [\pi r^2 -\text{INTC}(x)]}{\pi r^2} dx \approx 0.41\pi r^2\]</span></p><p>再次将场景扩展,考虑在一个主机的通信范围内存在多个主机的情况下,则相应的再次广播能够带来的额外覆盖率EAC比例与主机数<span class="math inline">\(k\)</span>的关系如下图所示: <img src="/2023/03/31/20230323/OneDrive%20-%20USTC\mySVN\Whd\Md\assets\1679580193476.png" alt="1679580193476"></p><p>由图可见,当<span class="math inline">\(k\)</span>超过4之后,再次广播所带来的额外覆盖增益已经低于<span class="math inline">\(0.05\%\)</span>,即此时再次广播明显是冗余的。[^Note][^Note]:对于并不处于通信范围内的多个主机,当其收到相同广播消息并且向同一节点进行再次广播操作时,这是有必要的,因为可以提升下一节点收到消息的概率,这便体现出了原来的理解的错误之处。</p><h4 id="争用">争用</h4><p>争用实际上应当是广播风暴在Adhoc中最为致命的一点。如前所述,在广播操作中,多个主机实际上是共享相同的广播信道,因此当多个主机同时想要进行广播操作时,就会产生争用问题。</p><p>以两个主机为例,假设B和C都接收到了A的广播消息,则只有当C处于<span class="math inline">\(S_{A\cap B}\)</span>区域时,会发生争用,其概率为:<span class="math display">\[\int_0^r \frac{2 \pi x \cdot text{INTC}(x)/\pi r^2}{\pi r^2}dx \approx 59\%\]</span>当此场景扩展到A的通信范围内有<span class="math inline">\(n\)</span>个主机,则有<span class="math inline">\(k\)</span>个主机在重播时没有发生争用的概率如图所示:<img src="/2023/03/31/20230323/OneDrive%20-%20USTC\mySVN\Whd\Md\assets\1679582024824.png" alt="1679582024824"> 由图可见,当<span class="math inline">\(n \geq6\)</span>时,所有主机都经历争用的概率<span class="math inline">\(cf(n,0)\)</span>已经超过0.8。</p><h4 id="冲突">冲突</h4><p>首先,是CSMA/CA协议,区别于有限局域网中的CSMA/CD协议,在无线环境中,采用的是CA(collisionavoidance),而非CD(collisiondetection)。无论CSMA/CA还是CSMA/CD,其思想都来源于CSMA(Carrier SenseMultiple Access),而CSMA实际上来源于Aloha,Aloha的思想可以概括如下:</p><blockquote><p>一个aloha节点只要有数据的话,该节点就可以立即发送。当该节点数据发送完之后,其需要等待接收方反馈的ACK。若成功接收到ACK之后,那么这一次传输成功。如果没有收到ACK的话,那么这一次传输失败。该aloha节点会认为网络中还存在另外一个aloha节点也在发送数据,所以造成接收方发生了冲突。最后这些冲突的节点会随机选择一个时间进行退避(backoff),以避免下一次冲突。若冲突节点回退完成,其才可以重新进行发送。</p></blockquote><p>而CSMA相对与Aloha,增添了LBT(listen before talk)机制,即:</p><blockquote><p>CSMA节点在每一次发送之前先监听信道是否是空闲的,如果信道不是空闲的话,那么就不发送数据,等待一会再进行尝试。只有确保是空闲的情况下,才可以发送数据,从而避免打断其他节点正在进行的传输过程。</p></blockquote><p>其可以分为三种模式<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="[Difference between 1-persistent, p-persistent and Non-persistent CSMA - GeeksforGeeks](https://www.geeksforgeeks.org/difference-between-1-persistent-p-persistent-and-non-persistent-csma/)">[1]</span></a></sup>:</p><ul><li>1-persistentesCSMA:“<strong>节点需要持续监听信道,一旦节点发现信道空闲后,则立刻发送数据。</strong>”</li><li>0-persistentesCSMA:“<strong>节点不连续监听信道,若该时刻节点监听信道为busy,那么等待一段时间后,再次进行监听。若节点该时刻监听信道为空闲,则立刻发送数据。</strong>”</li><li>p-persistentesCSMA:“<strong>节点需要持续监听信道,一旦发现信道空闲后,节点以p的概率立刻发送数据,以1-p的概率不发送数据。若节点该时刻不发送数据,那么等待一段时间后,再次进行监听,并以p概率再次发送</strong>”。</li></ul><p>在以上三种CSMA机制中,若节点传输发送冲突,则类似aloha的基本算法,随机等待一个时间之后,再次进行重试。接下来便可以进一步探讨CSMA/CD与CSMA/CA。</p><p>在思想上,CSMA/CD类似于1-persistentesCSMA,而CSMA/CA类似于p-persistentesCSMA,即CSMA/CD是持续监听信道,一旦监听到空闲立刻发生数据,在发送数据时依然检测信道是否有冲突,如果有冲突就停止发送并等待一段随机时间后重试;CSMA/CA是在发送数据前检测信道是否空闲,如果空闲则对backoffcounter进行减一操作,直至counter为零才可以发送数据并等待确认帧,如果不空闲则挂起counter,等待一段随机时间后重试。两者的对比可参见<a href="https://www.geeksforgeeks.org/difference-between-csma-ca-and-csma-cd/?ref=rp">Differencebetween CSMA/CA and CSMA/CD - GeeksforGeeks</a>、<a href="https://www.geeksforgeeks.org/carrier-sense-multiple-access-csma/">CarrierSense Multiple Access (CSMA) - GeeksforGeeks</a></p><p>知道了两者的不同之后,产生了一个新问题:为什么在无线环境下不再执行CD,而是需要更改为CA?</p><p>在有线网络中,如果发生冲突,站点可以通过接收信号的能量变化来检测到冲突。因此,它们可以立即停止发送并等待一段随机时间后再重试,这样可以减少冲突的可能性和浪费的带宽。而在无线网络中,由于信号能量的变化很小,站点很难检测到冲突。因此,它们需要等待整个数据包发送完毕后才能知道是否发生了冲突。这样会导致更多的带宽浪费和延迟。所以,在无线网络中,冲突避免更加重要和必要。</p><h4 id="隐藏终端和暴露终端">隐藏终端和暴露终端</h4><p>在无线环境下,还有两个很重要的问题,即隐藏终端和暴露终端,如下面两张图所示。<img src="/2023/03/31/20230323/OneDrive%20-%20USTC\mySVN\Whd\Md\assets\隐蔽站和暴露站.png"></p><p>对于隐藏终端问题,可以通过RTS/CTS机制来解决:在发送数据之前,站点先发送一个请求发送(RTS)的信号给接收站点,接收站点回复一个清除发送(CTS)的信号给发送站点和其他邻近站点。这样,其他邻近站点就知道有一个正在进行的传输,并且等待一段时间后再尝试发送,这种方式可以有效地减少隐藏终端问题造成的冲突和带宽浪费。</p><p>但需要注意的是,RTS/CTS机制在IEEE802.11协议中是可选项而非必选项,因为如果 A正在向 B发送一个小数据包,那么对于A来说,简单地发送数据包并准备好在它受到干扰时重新发送,比试图阻止这种干扰要便宜得多。</p><p><strong>以上所讨论的都是一对一的情况,实际上在802.11中不考虑一对多的情况</strong></p><section class="footnotes"><div class="footnote-list"><ol><li><span id="fn:1" class="footnote-text"><span><a href="https://www.geeksforgeeks.org/difference-between-1-persistent-p-persistent-and-non-persistent-csma/">Differencebetween 1-persistent, p-persistent and Non-persistent CSMA -GeeksforGeeks</a><a href="#fnref:1" rev="footnote" class="footnote-backref">↩︎</a></span></span></li></ol></div></section>]]></content>
</entry>
</search>