-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
251 lines (120 loc) · 755 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>GPG 入门教程</title>
<link href="/2024/01/19/gpg-tourial/"/>
<url>/2024/01/19/gpg-tourial/</url>
<content type="html"><![CDATA[<p>如何使用这个最流行、最好用的加密工具之一</p><span id="more"></span><h1 id="什么是GPG"><a href="#什么是GPG" class="headerlink" title="什么是GPG"></a>什么是GPG</h1><p>1991年,程序员 <a href="https://en.wikipedia.org/wiki/Phil_Zimmermann">Phil Zimmermann</a> 为了避开政府监视,开发了加密软件PGP。这个软件非常好用,迅速流传开来,成了许多程序员的必备工具。但是,它是商业软件,不能自由使用。所以,自由软件基金会决定,开发一个 PGP 的替代品,取名为 GnuPG,也就是 GPG</p><h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h1><p>GPG有两种安装方式。可以<a href="http://www.gnupg.org/download/index.en.html">下载源码</a>,自己编译安装</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 zsh">./configure<br>make<br>make install<br></code></pre></td></tr></table></figure><p>也可以安装编译好的二进制包</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><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs zsh">pacman -S gnupg # Arch Linux<br>apt install gnupg # Debian 系<br>dnf in gnupg # RH 系<br>zypper in gnupg # 蜥蜴系<br></code></pre></td></tr></table></figure><h1 id="生成密钥"><a href="#生成密钥" class="headerlink" title="生成密钥"></a>生成密钥</h1><p>安装成功后,使用gen-ken参数生成自己的密钥</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --gen-key<br></code></pre></td></tr></table></figure><p>会输出</p><figure class="highlight text"><table><tr><td class="gutter"><pre><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 text">gpg (GnuPG) 2.4.3; Copyright (C) 2023 g10 Code GmbH<br>This is free software: you are free to change and redistribute it.<br>There is NO WARRANTY, to the extent permitted by law.<br><br>注意:使用 “gpg --full-generate-key” 以获得一个全功能的密钥生成对话框。<br><br>GnuPG 需要构建用户标识以辨认您的密钥。<br><br>真实姓名:<br></code></pre></td></tr></table></figure><p>这里需要按照问题填,填了回车,后按<code>O</code>确定</p><figure class="highlight text"><table><tr><td class="gutter"><pre><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 text">gpg (GnuPG) 2.4.3; Copyright (C) 2023 g10 Code GmbH<br>This is free software: you are free to change and redistribute it.<br>There is NO WARRANTY, to the extent permitted by law.<br><br>注意:使用 “gpg --full-generate-key” 以获得一个全功能的密钥生成对话框。<br><br>GnuPG 需要构建用户标识以辨认您的密钥。<br><br>真实姓名: zlicdt<br>电子邮件地址: xkicdt@163.com<br>您选定了此用户标识:<br> “zlicdt <xkicdt@163.com>”<br><br>更改姓名(N)、注释(C)、电子邮件地址(E)或确定(O)/退出(Q)?<br></code></pre></td></tr></table></figure><p>然后它会让你“输入密码以保护您的新密钥”,<strong>这个密码最好填8位以上的</strong>,否则会</p><figure class="highlight text"><table><tr><td class="gutter"><pre><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 text">┌─────────────────────────────────────────────────────────────────────────────────────────┐<br>│ 警告:您输入了一个不安全的密码 │<br>│ │<br>│ A passphrase should be at least 8 characters long. │<br>│ │<br>│ <无论如何都使用这个> <输入新的密码> │<br>└─────────────────────────────────────────────────────────────────────────────────────────┘<br></code></pre></td></tr></table></figure><p>然后,它会</p><figure class="highlight text"><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 text">我们需要生成大量的随机字节。在质数生成期间做些其他操作(敲打键盘<br>、移动鼠标、读写硬盘之类的)将会是一个不错的主意;这会让随机数<br>发生器有更好的机会获得足够的熵。<br></code></pre></td></tr></table></figure><p>最终生成一个有效期为三年的 GPG key</p><h1 id="高级密钥生成"><a href="#高级密钥生成" class="headerlink" title="高级密钥生成"></a>高级密钥生成</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --full-gen-key<br></code></pre></td></tr></table></figure><figure class="highlight text"><table><tr><td class="gutter"><pre><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 text">gpg (GnuPG) 2.4.3; Copyright (C) 2023 g10 Code GmbH<br>This is free software: you are free to change and redistribute it.<br>There is NO WARRANTY, to the extent permitted by law.<br><br>请选择您要使用的密钥类型:<br> (1) RSA 和 RSA<br> (2) DSA 和 Elgamal<br> (3) DSA(仅用于签名)<br> (4) RSA(仅用于签名)<br> (9) ECC(签名和加密) *默认*<br> (10) ECC(仅用于签名)<br> (14)卡中现有密钥<br>您的选择是?<br></code></pre></td></tr></table></figure><p>这里建议直接选择默认的 ECC</p><figure class="highlight text"><table><tr><td class="gutter"><pre><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 text">请选择您想要使用的椭圆曲线:<br> (1) Curve 25519 *默认*<br> (4) NIST P-384<br> (6) Brainpool P-256<br>您的选择是?<br>请设定这个密钥的有效期限。<br> 0 = 密钥永不过期<br> <n> = 密钥在 n 天后过期<br> <n>w = 密钥在 n 周后过期<br> <n>m = 密钥在 n 月后过期<br> <n>y = 密钥在 n 年后过期<br>密钥的有效期限是?(0)<br>密钥永远不会过期<br>这些内容正确吗? (y/N) y<br><br>GnuPG 需要构建用户标识以辨认您的密钥。<br><br>真实姓名:<br></code></pre></td></tr></table></figure><p>后来就和上部分一样了</p><h1 id="密钥管理"><a href="#密钥管理" class="headerlink" title="密钥管理"></a>密钥管理</h1><h2 id="列出密钥"><a href="#列出密钥" class="headerlink" title="列出密钥"></a>列出密钥</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --list-keys<br></code></pre></td></tr></table></figure><p>结果如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><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 text">pub ed25519 2024-01-18 [SC] [有效至:2027-01-17]<br> F624D1B3CC89BF5760DCF150A30A42DE01DE13B5<br>uid [ 绝对 ] zlicdt <xkicdt@163.com><br>sub cv25519 2024-01-18 [E] [有效至:2027-01-17]<br></code></pre></td></tr></table></figure><p>第一行显示公钥特征(算法,Hash字符串和生成时间),第二行显示”用户ID”,第三行显示私钥特征</p><p>如果你要从密钥列表中删除某个密钥,可以使用delete-key参数:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --delete-key [用户ID]<br></code></pre></td></tr></table></figure><p><strong>不过这样的话,如果它对应一个私钥的话,需要先删掉私钥,它会给出命令提示</strong></p><h2 id="输出密钥"><a href="#输出密钥" class="headerlink" title="输出密钥"></a>输出密钥</h2><p>公钥文件以二进制形式储存,armor 参数可以将其转换为 ASCII 码显示</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --armor --export [用户ID]<br></code></pre></td></tr></table></figure><p>类似地,export-secret-keys 参数可以转换私钥</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --armor --export-secret-keys<br></code></pre></td></tr></table></figure><h2 id="上传公钥"><a href="#上传公钥" class="headerlink" title="上传公钥"></a>上传公钥</h2><p>公钥服务器是网络上专门储存用户公钥的服务器。send-keys 参数可以将公钥上传到服务器</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --send-keys [用户ID] --keyserver https://keys.openpgp.org<br></code></pre></td></tr></table></figure><p>使用上面的命令,你的公钥就被传到了服务器 subkeys.pgp.net,然后通过交换机制,所有的公钥服务器最终都会包含你的公钥</p><p>由于公钥服务器没有检查机制,任何人都可以用你的名义上传公钥,所以没有办法保证服务器上的公钥的可靠性。通常,你可以在网站上公布一个公钥指纹,让其他人核对下载到的公钥是否为真。fingerprint 参数生成公钥指纹</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --fingerprint [用户ID]<br></code></pre></td></tr></table></figure><h2 id="导入密钥"><a href="#导入密钥" class="headerlink" title="导入密钥"></a>导入密钥</h2><p>除了生成自己的密钥,还需要将他人的公钥或者你的其他密钥输入系统。这时可以使用 import 参数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --import [密钥文件]<br></code></pre></td></tr></table></figure><p>为了获得他人的公钥,可以让对方直接发给你,或者到公钥服务器上寻找</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --keyserver https://keys.openpgp.org --search-keys [用户ID]<br></code></pre></td></tr></table></figure><h1 id="加密和解密"><a href="#加密和解密" class="headerlink" title="加密和解密"></a>加密和解密</h1><h2 id="加密"><a href="#加密" class="headerlink" title="加密"></a>加密</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --recipient [用户ID] --encrypt demo.txt<br></code></pre></td></tr></table></figure><p>这会在当前目录下生成一个 demo.txt.gpg 文件,cat 其中的内容发现是已经经过加密的,不可读取</p><p>这时候传输文件就是安全的</p><h2 id="解密"><a href="#解密" class="headerlink" title="解密"></a>解密</h2><p>对方收到加密文件以后,就用自己的私钥解密</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --decrypt demo.txt.gpg<br></code></pre></td></tr></table></figure><p>此时它会在 terminal 里直接输出文件内容<br>GPG 允许省略 decrypt 参数,直接可以达到相同的效果</p><h1 id="签名"><a href="#签名" class="headerlink" title="签名"></a>签名</h1><h2 id="对文件签名"><a href="#对文件签名" class="headerlink" title="对文件签名"></a>对文件签名</h2><p>有时我们不需要加密文件,只需要对文件签名,表示这个文件确实是我本人发出的。<br>sign 参数用来签名</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --sign demo.txt<br></code></pre></td></tr></table></figure><p>运行上面的命令后,当前目录下生成 demo.txt.gpg 文件,这就是签名后的文件。这个文件默认采用二进制储存,如果想生成 ASCII 码的签名文件,可以使用 clearsign 参数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --clearsign demo.txt<br></code></pre></td></tr></table></figure><p>运行上面的命令后 ,当前目录下生成demo.txt.asc文件,后缀名asc表示该文件是ASCII码形式的,现在这个可读了</p><p>如果想生成单独的签名文件,与文件内容分开存放,可以使用 detach-sign 参数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --detach-sign demo.txt<br></code></pre></td></tr></table></figure><p>运行上面的命令后,当前目录下生成一个单独的签名文件 demo.txt.sig。该文件是二进制形式的,如果想采用 ASCII 码形式,要加上 armor 参数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --armor --detach-sign demo.txt<br></code></pre></td></tr></table></figure><h2 id="签名-加密"><a href="#签名-加密" class="headerlink" title="签名+加密"></a>签名+加密</h2><p>上一节的参数,都是只签名不加密。如果想同时签名和加密,可以使用下面的命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --local-user [发信者ID] --recipient [接收者ID] --armor --sign --encrypt demo.txt<br></code></pre></td></tr></table></figure><p>local-user 参数指定用发信者的私钥签名,recipient 参数指定用接收者的公钥加密,armor 参数表示采用 ASCII 码形式显示,sign 参数表示需要签名,encrypt 参数表示指定源文件</p><h2 id="验证签名"><a href="#验证签名" class="headerlink" title="验证签名"></a>验证签名</h2><p>我们收到别人签名后的文件,需要用对方的公钥验证签名是否为真。verify 参数用来验证</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">gpg --verify demo.txt.asc demo.txt<br></code></pre></td></tr></table></figure><h1 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h1><ol><li>Paul Heinlein, <a href="http://www.madboa.com/geek/gpg-quickstart/">GPG Quick Start</a></li><li>Ubuntu help, <a href="https://help.ubuntu.com/community/GnuPrivacyGuardHowto">GnuPrivacyGuardHowto</a></li><li>KNL, <a href="http://www.bitflop.com/document/129">GnuPG Tutorial</a></li><li>Alan Eliasen. <a href="http://futureboy.us/pgp.html">GPG Tutorial</a></li><li><a href="http://www.gnupg.org/gph/en/manual.html">The GNU Privacy Handbook</a></li><li><a href="https://www.ruanyifeng.com/blog/2013/07/gpg.html">GPG入门教程 - 阮一峰的网络日志</a></li></ol>]]></content>
<tags>
<tag>GPG</tag>
</tags>
</entry>
<entry>
<title>Hexo Fluid 主题安装 Gitalk</title>
<link href="/2024/01/06/hexo-fluid-with-gitalk/"/>
<url>/2024/01/06/hexo-fluid-with-gitalk/</url>
<content type="html"><![CDATA[<p>给 Hexo 加个评论插件 Gitalk</p><span id="more"></span><p>咱这个 Blog 是支持评论的!</p><h1 id="预处理"><a href="#预处理" class="headerlink" title="预处理"></a>预处理</h1><p>首先,看到这里的话,你应该已经在 Github Pages 上部署好 Hexo + Fluid 了罢<br>咱看 fluid 的配置文件,里面写好了<strong>评论插件:Options: utterances | disqus | gitalk | valine…</strong><br>咱要选的就是这个 gitalk,基于 Github issue,无论是作者还是读者,有个 Github 账号就能用</p><h2 id="建立用于存储评论的仓库"><a href="#建立用于存储评论的仓库" class="headerlink" title="建立用于存储评论的仓库"></a>建立用于存储评论的仓库</h2><p>因为是基于 issue,所以要开一个公共 github 存储库(已存在或创建一个新的 github 存储库)来开这些 issue<br><img src="/../img/hexo-fluid-with-gitalk/1.png" alt="喵!"><br>就像这个一样,是空的,只要是 public 的就行</p><h2 id="创建-GitHub-Application"><a href="#创建-GitHub-Application" class="headerlink" title="创建 GitHub Application"></a>创建 GitHub Application</h2><a class="btn" href="https://github.com/settings/applications/new" target="_blank">点这申请</a><p>然后这个像这样填就行<br><img src="/../img/hexo-fluid-with-gitalk/2.png" alt="喵!"><br>记得<code>xxxxx</code>要改啊</p><h2 id="把主体用-npm-装上"><a href="#把主体用-npm-装上" class="headerlink" title="把主体用 npm 装上"></a>把主体用 npm 装上</h2><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">npm i <span class="hljs-literal">--save</span> gitalk<br></code></pre></td></tr></table></figure><h1 id="嵌入"><a href="#嵌入" class="headerlink" title="嵌入"></a>嵌入</h1><p>然后回到<code>_config.yml</code><br>把</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><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 yaml"><span class="hljs-attr">post:</span><br> <span class="hljs-attr">comments:</span><br> <span class="hljs-attr">enable:</span> <span class="hljs-literal">true</span><br> <span class="hljs-attr">type:</span> <span class="hljs-string">gitalk</span><br><br><span class="hljs-attr">gitalk:</span><br> <span class="hljs-attr">clientID:</span> <br> <span class="hljs-attr">clentSecret:</span> <br> <span class="hljs-attr">repo:</span> <span class="hljs-string">gitalk-comments</span><br> <span class="hljs-attr">owner:</span> <span class="hljs-string">xxxxx</span><br> <span class="hljs-attr">admin:</span> [<span class="hljs-string">'xxxxx'</span>]<br> <span class="hljs-comment"># 都改成自己的,前两个就在申请完 Github Application 的页面,repo 按照你的评论存储仓库填,owner 和 admin 都改成你自己的用户名</span><br></code></pre></td></tr></table></figure><p>然后重新<code>hexo d -g</code><br>这就完事了</p>]]></content>
<categories>
<category>Software</category>
</categories>
<tags>
<tag>hexo</tag>
</tags>
</entry>
<entry>
<title>Arch Linux 安装教程</title>
<link href="/2024/01/01/archlinux-installation/"/>
<url>/2024/01/01/archlinux-installation/</url>
<content type="html"><![CDATA[<p><strong>此文章适用于现代(2010年及以后)的x86_64机器</strong></p><span id="more"></span><p>用了这么长时间Arch,总该写一篇安装教程了。<br>截至教程发布日,Arch Linux Live CD的最新版本是2023年10月的,经测试没有问题。</p><h2 id="支持的硬件"><a href="#支持的硬件" class="headerlink" title="支持的硬件"></a>支持的硬件</h2><p><strong>·x86_64架构的CPU,建议为2008年以后的CPU<br>·至少有1GB内存(Arch Wiki上写的最小的512MB现在已经无法启动11月的Live CD)<br>·可用的网络连接<br>·20GB可用的硬盘空间</strong></p><h2 id="具体步骤"><a href="#具体步骤" class="headerlink" title="具体步骤"></a>具体步骤</h2><h3 id="制作安装介质"><a href="#制作安装介质" class="headerlink" title="制作安装介质"></a>制作安装介质</h3><ol><li>到镜像站去下载最新的Arch Linux Live CD iso -><br><code>https://mirrors.ustc.edu.cn/archlinux/iso/latest/archlinux-x86_64.iso</code></li><li>使用工具将其写入到USB驱动器或者刻录光盘(不建议)<br> 推荐写入工具<a href="https://mirrors.bfsu.edu.cn/github-release/balena-io/etcher/LatestRelease/">balenaEtcher</a></li><li>重启后按主板厂商的指引设置USB启动或选择USB启动(若失败请先关闭安全启动[^1])</li><li>从USB驱动器引导后就进入了Live CD环境</li></ol><h3 id="Live-CD设置"><a href="#Live-CD设置" class="headerlink" title="Live CD设置"></a>Live CD设置</h3><ol><li>连接网络 -><br> <strong>WIFI:</strong> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">iwctl station wlan0 scan<br>iwctl station wlan0 get-networks<br>iwctl station wlan0 connect <span class="hljs-variable">$SSID</span> --passphrase <span class="hljs-variable">$PASSWORD</span> <span class="hljs-comment">#$SSID代表WIFI的名字,$PASSWORD代表密码</span><br>ip addr<br></code></pre></td></tr></table></figure> <strong>如果直接插网线的话上面的步骤可以跳过</strong> <div class="note note-info"> <p>如果没有<code>wlan0</code>的话,可能是没有无线网卡或者是没有驱动,请插网线。如果没有RJ45网口,请移步Manjaro(</p> </div> 若<code>ip addr</code>返回了wlan0的信息并且下面有正确的ip,则代表网络已连接成功。<br> 可选:停止<code>reflector</code>服务,防止<code>/etc/pacman.d/mirrorlist</code>被写入其他地区镜像 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">systemctl stop reflector.service<br></code></pre></td></tr></table></figure></li><li>配置镜像<br> 由于结界,我们无法直接从结界外部的镜像站获取软件包。在Live CD内也无法使用魔法绕过结界,所以步前辈们的足迹,使用他们搭建好的国内镜像站(注意:这一步须在<code>reflector.service</code>停止后进行,可手动结束或等待其执行完成,否则编辑完的<code>mirrorlist</code>会被它重写) -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">vim /etc/pacman.d/mirrorlist<br></code></pre></td></tr></table></figure><ul><li>在文件开头按<code>i</code>进入<code>insert</code>模式,添加<code>Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch</code></li><li>按<code>esc</code>退出<code>insert</code>模式,输入<code>:wq</code>保存并退出<code>vim</code></li></ul></li></ol><h3 id="磁盘相关"><a href="#磁盘相关" class="headerlink" title="磁盘相关"></a>磁盘相关</h3><ol><li>磁盘分区 -> <div class="note note-warning"> <p>这一步需要按照情况自行更改,操作不当会导致数据丢失。一定要理解下面info中的内容。</p> </div> <div class="note note-info"> <p>我这台机器有一个NVME接口的固态硬盘,它的编号是<code>nvme0n1</code>。在有多个NVME接口的主板上,如果你插了多块硬盘,它将会有<code>nvme1n1</code>、<code>nvme2n1</code>等编号,一定要清楚自己要操作的是哪块硬盘。连在SATA与USB接口上的硬盘会显示<code>sda</code>、<code>sdb</code>等,<code>sda</code>可能是U盘也可能是内置硬盘,请先<code>lsblk</code>进行辨认。这条命令的意义是对<code>nvme0n1</code>进行操作,请根据自身需要更改。</p> </div> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">cfdisk /dev/nvme0n1<br></code></pre></td></tr></table></figure> <div class="note note-warning"> <p>如果先前有Windows安装的话,请注意不要把Windows分区直接缩小大小,会让Windows坏掉。应先使用其他工具完成分区大小调整再进行后续步骤。</p> </div><ul><li>使用GPT分区表(这是UEFI引导的必须条件)</li><li>推荐分区方案 -><ul><li>EFI分区:不小于4MB,类型:<code>EFI System</code></li><li>主分区:不小于10GB,类型:<code>Linux filesystem</code><br> (建议在分区内部建立一个swapfile来承担swap的任务)<br> <img src="/../img/archlinux-installation/1.png"></li></ul></li><li>结束后,选<code>Write</code>再输入<code>yes</code>来写入分区表,再选<code>Quit</code>退出</li></ul></li><li>建立文件系统 -><br> 主分区使用<code>xfs</code>文件系统 <div class="note note-warning"> <p>如果先前安装了Windows或者其他操作系统,不要执行第一条,会导致Windows引导损坏。</p> </div> <div class="note note-info"> <p>这条命令也需要自行修改,意为在<code>nvme0n1</code>的第一分区创建<code>vfat</code>文件系统,在<code>nvme0n1</code>的第二分区创建<code>ext4</code>文件系统。</p> </div> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">mkfs.vfat /dev/nvme0n1p1<br>mkfs.ext4 /dev/nvme0n1p2<br></code></pre></td></tr></table></figure> <div class="note note-info"> <p>如果之前<code>nvme0n1p2</code>建立过其他文件系统,这里需要按<code>y</code>确认。</p> </div></li><li>挂载分区 -> <div class="note note-info"> <p>改,勿照抄。<code>/</code>的<code>nvme0n1p2</code>挂载到<code>/mnt</code>,再把<code>nvme0n1p1</code>这个EFI分区挂到<code>/mnt/boot/efi</code>下。</p> </div> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount /dev/nvme0n1p2 /mnt<br><span class="hljs-built_in">mkdir</span> -p /mnt/boot/efi<br>mount /dev/nvme0n1p1 /mnt/boot/efi<br></code></pre></td></tr></table></figure></li><li>创建<code>swapfile</code> -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">dd</span> <span class="hljs-keyword">if</span>=/dev/zero of=/mnt/swapfile bs=1M count=8192<br><span class="hljs-built_in">chmod</span> 600 /mnt/swapfile<br>mkswap /mnt/swapfile<br>swapon /mnt/swapfile<br></code></pre></td></tr></table></figure> <div class="note note-info"> <p>这里<code>swap</code>创建为8G,可根据需要改变<code>count=</code>后的数字,单位是MB。当然如果内存够大也可以不用加。</p> </div></li></ol><h3 id="安装系统"><a href="#安装系统" class="headerlink" title="安装系统"></a>安装系统</h3><ol><li>安装软件包 -><br> <strong>首先初始化keyring!!</strong> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pacman -Sy archlinux-keyring<br></code></pre></td></tr></table></figure><h4 id="使用KDE-Plasma作桌面环境的系统"><a href="#使用KDE-Plasma作桌面环境的系统" class="headerlink" title="使用KDE Plasma作桌面环境的系统"></a><strong>使用KDE Plasma作桌面环境的系统</strong></h4> <code>pacstrap</code>安装必要的包 <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pacstrap /mnt base linux linux-firmware linux-headers base-devel git zsh zsh-completions grub efibootmgr networkmanager openssh vim plasma konsole dolphin noto-fonts-cjk noto-fonts noto-fonts-emoji noto-fonts-extra fcitx5-chinese-addons chromium fastfetch htop<br></code></pre></td></tr></table></figure><h4 id="使用GNOME作桌面环境的系统"><a href="#使用GNOME作桌面环境的系统" class="headerlink" title="使用GNOME作桌面环境的系统"></a><strong>使用GNOME作桌面环境的系统</strong></h4> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pacstrap /mnt base linux linux-firmware linux-headers base-devel git zsh zsh-completions grub efibootmgr networkmanager openssh vim gnome noto-fonts-cjk noto-fonts-emoji noto-fonts-extra ibus ibus-libpinyin chromium fastfetch htop<br></code></pre></td></tr></table></figure><h4 id="使用命令行界面的系统-Headless"><a href="#使用命令行界面的系统-Headless" class="headerlink" title="使用命令行界面的系统(Headless)"></a><strong>使用命令行界面的系统(Headless)</strong></h4> 这是我的习惯 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pacstrap /mnt base linux linux-firmware linux-headers base-devel git zsh zsh-completions grub efibootmgr networkmanager openssh vim fastfetch htop<br></code></pre></td></tr></table></figure><h4 id=""><a href="#" class="headerlink" title=""></a></h4></li><li>生成<code>fstab</code> -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">genfstab -U /mnt >> /mnt/etc/fstab<br></code></pre></td></tr></table></figure></li><li><code>chroot</code>到新系统 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">arch-chroot /mnt<br></code></pre></td></tr></table></figure></li></ol><h3 id="初步配置系统"><a href="#初步配置系统" class="headerlink" title="初步配置系统"></a>初步配置系统</h3><ol><li>时间设置 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">ln</span> -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime<br>hwclock --systohc<br></code></pre></td></tr></table></figure></li><li>用户设置 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> 主机名 > /etc/hostname<br>useradd -m 用户名 -G wheel<br>passwd 用户名<br></code></pre></td></tr></table></figure> <div class="note note-info"> <p><code>useradd</code>用于添加账户,<code>passwd</code>用于修改密码,为安全考虑输入的密码不会直接显示出来。虽然没有反应,但确实是输入进去了。</p> </div></li><li>编辑<code>sudoers</code> -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">visudo<br></code></pre></td></tr></table></figure> 将文件中的<code># %wheel ALL=(ALL:ALL) ALL</code>(大概率在第108行)的注释取消,使得<code>wheel</code>组里的用户可以使用<code>sudo</code></li><li>配置<code>zsh</code> -> <div class="note note-info"> <p>zsh 是一个有强大客制化能力的 shell,并且 Apple 也将新 macOS 默认的 shell 换成了 zsh。现在Arch Linux Live CD 的默认 shell 就是 zsh。<br>如果不喜欢zsh,或者考虑兼容性,请跳过这一步。</p> </div> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash">su 用户名<br>git <span class="hljs-built_in">clone</span> https://mirrors.tuna.tsinghua.edu.cn/git/ohmyzsh.git ~/.oh-my-zsh<br><span class="hljs-built_in">cp</span> ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc<br>chsh -s /bin/zsh<br><span class="hljs-built_in">exit</span><br></code></pre></td></tr></table></figure></li><li>本地化 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"LANG=\"en_US.UTF-8\""</span> >> /etc/locale.conf<br>sed -i <span class="hljs-string">"s@#zh_CN.UTF-8 UTF-8@zh_CN.UTF-8 UTF-8@g"</span> /etc/locale.gen<br>sed -i <span class="hljs-string">"s@#en_US.UTF-8 UTF-8@en_US.UTF-8 UTF-8@g"</span> /etc/locale.gen<br></code></pre></td></tr></table></figure> 去掉<code>#zh_CN.UTF-8 UTF-8</code>和<code>#en_US.UTF-8 UTF-8</code>的注释符号“<code>#</code>”<br> 最后运行 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">locale-gen<br></code></pre></td></tr></table></figure></li><li>服务设置 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">systemctl <span class="hljs-built_in">enable</span> NetworkManager<br>systemctl <span class="hljs-built_in">enable</span> sshd<br>systemctl <span class="hljs-built_in">enable</span> sddm <span class="hljs-comment"># KDE</span><br>systemctl <span class="hljs-built_in">enable</span> gdm <span class="hljs-comment"># GNOME</span><br></code></pre></td></tr></table></figure></li><li>引导设置 -><br> <code>gurb</code>已支持引导Windows。<br> 可以让<code>grub</code>找到Windows。 <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo pacman -Sy ntfs-3g fuse os-prober<br></code></pre></td></tr></table></figure> 对于<code>UEFI</code>引导的安装方式<br> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">grub-install --target=x86_64-efi --efi-directory=/boot/efi<br>sed -i <span class="hljs-string">"s@#GRUB_DISABLE_OS_PROBER=false@GRUB_DISABLE_OS_PROBER=false@g"</span> /etc/default/grub<br>grub-mkconfig -o /boot/grub/grub.cfg<br></code></pre></td></tr></table></figure><br> 对于<code>BIOS</code>引导的安装方式<br> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">grub-install /dev/nvme0n1<br>sed -i <span class="hljs-string">"s@#GRUB_DISABLE_OS_PROBER=false@GRUB_DISABLE_OS_PROBER=false@g"</span> /etc/default/grub<br>grub-mkconfig -o /boot/grub/grub.cfg<br></code></pre></td></tr></table></figure></li><li>退出<code>chroot</code>环境并重启 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">exit</span><br>reboot<br></code></pre></td></tr></table></figure><strong>至此Arch Linux的安装就完成了</strong><br><strong>接下来是配置部分</strong></li></ol><h3 id="进一步配置系统"><a href="#进一步配置系统" class="headerlink" title="进一步配置系统"></a>进一步配置系统</h3><p><strong>如果是KDE的话</strong>,重启系统后,大概就是这样的 -><br><img src="/../img/archlinux-installation/2.png"></p><ol><li>输入法配置<br> 如果直接<code>右键</code>-<code>配置</code>是会显示这个的 -><br> <img src="/../img/archlinux-installation/3.png"><br> 这会进入文本配置,显然我们是不懂的(<br> 所以要安装<code>fcitx5-configtool</code> -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo pacman -S fcitx5-configtool<br></code></pre></td></tr></table></figure> 然后再<code>右键</code>-<code>配置</code>就可以看到 -><br> <img src="/../img/archlinux-installation/4.png"><br> 然而现在按<code>Ctrl+Tab</code>是不能切换到拼音输入法的,我们需要把<code>Fcitx5</code>设为默认输入法并自动启动 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo vim /etc/environment<br></code></pre></td></tr></table></figure> 把下面的东西加进去 -> <figure class="highlight text"><table><tr><td class="gutter"><pre><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 text">GTK_IM_MODULE=fcitx<br>QT_IM_MODULE=fcitx<br>XMODIFIERS=@im=fcitx<br>SDL_IM_MODULE=fcitx<br>GLFW_IM_MODULE=ibus<br></code></pre></td></tr></table></figure> 重新登录就可以正常使用喵~<br> 更多用法请参阅<a href="https://wiki.archlinux.org/title/Fcitx5_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)">Arch Wiki</a></li><li>软件源<br> 众所周知,官方源对于灰色地带/非自由软件是不收录的。<br> 所以我们通常用第三方源+<code>AUR</code>来进行补充。<br> 官方源是不带任何<code>AUR</code>工具的,所以我们先装<code>archlinuxcn</code>源 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo vim /etc/pacman.conf<br></code></pre></td></tr></table></figure> 加入以下文本 -> <figure class="highlight text"><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 text">[archlinuxcn]<br>Server = https://mirrors.bfsu.edu.cn/archlinuxcn/$arch<br></code></pre></td></tr></table></figure> 然后获取<code>keyring</code> -> <div class="note note-warning"> <p>近期这一步会出现问题,因为archlinuxcn 社区源的 keyring 包 archlinuxcn-keyring 由 farseerfc 的 key 签署验证,而 Arch Linux 官方 keyring 中包含了 farseerfc 的 key 。自12月初 archlinux-keyring 中删除了一个退任的 master key (<a href="https://gitlab.archlinux.org/archlinux/archlinux-keyring/-/issues/246">https://gitlab.archlinux.org/archlinux/archlinux-keyring/-/issues/246</a>) 导致 farseerfc 的 key 的信任数不足,由 GnuPG 的 web of trust 推算为 marginal trust,从而不再能自动信任 archlinuxcn-keyring 包的签名。(转自Arch Linux CN Telegram Messages Channel)</p> </div> 如果你在新系统中尝试安装<code>archlinuxcn-keyring</code>包时遇到如下报错: <figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">error: archlinuxcn-keyring: Signature from "Jiachen YANG (Arch Linux Packager Signing Key) <farseerfc@archlinux.org>" is marginal trust<br></code></pre></td></tr></table></figure> 请使用以下命令在本地信任 farseerfc 的 key 。此 key 已随<code>archlinux-keyring</code>安装在系统中,只是缺乏信任: <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">sudo pacman-key --lsign-key "farseerfc@archlinux.org"<br></code></pre></td></tr></table></figure> 然后 <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo pacman -Sy archlinuxcn-keyring<br></code></pre></td></tr></table></figure> 就可以使用<code>archlinuxcn</code>源了!<br> 由于要使用<code>AUR</code>,所以我们先装一个AUR工具: <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo pacman -S paru<br></code></pre></td></tr></table></figure> 详细的用法和<code>pacman</code>一样。</li><li>引导Windows<br> 在安装系统时,<code>os-prober</code>往往不能正常检测到Windows,需要我们再运行 <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo grub-mkconfig -o /boot/grub/grub.cfg<br></code></pre></td></tr></table></figure> 重启即可看到<code>Windows Boot Manager</code>。</li></ol><h2 id="显卡驱动"><a href="#显卡驱动" class="headerlink" title="显卡驱动"></a>显卡驱动</h2><p>如果是A卡和I卡,在这个时候已经不用看了,开源的驱动会做得很好,而且已经随桌面环境装上了<br>而N卡,需要进行以下步骤</p><h3 id="查看显卡型号"><a href="#查看显卡型号" class="headerlink" title="查看显卡型号"></a>查看显卡型号</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">lspci -k | grep -A 2 -E "(VGA|3D)"<br></code></pre></td></tr></table></figure><p>GeForce 900 系以及更新的显卡(NV110+),安装 nvidia(用于官方源里的标准内核)或者 nvidia-lts(用于 linux-lts 内核)</p><p>Turing (NV160/TUXXX) 系列或更新的显卡。可以安装 nvidia-open 包,开源的(仅适用于官方源里的内核,其他内核上必须使用 nvidia-open-dkms 包)</p><p>更老的显卡型号(发布于 2010 年或更早),就用内核自带的 Nouveau</p><h3 id="安装驱动"><a href="#安装驱动" class="headerlink" title="安装驱动"></a>安装驱动</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">sudo pacman -S nvidia nvidia-settings<br></code></pre></td></tr></table></figure><h3 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h3><p>安装 NVIDIA 官方的驱动之后,需要编辑<code>/etc/mkinitcpio.conf</code>,在 HOOKS 一行删除<code>kms</code>并保存<br>然后执行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">mkinitcpio -P<br></code></pre></td></tr></table></figure><p>重新生成一次镜像。这能防止 initramfs 包含 nouveau 模块,避免 nouveau 和官方驱动的冲突。</p><p><strong>到现在一个日常使用的系统已经完成了~ 剩下的请自由发挥~</strong></p><h2 id="常用软件"><a href="#常用软件" class="headerlink" title="常用软件"></a>常用软件</h2><ol><li>telegram<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">sudo pacman -S telegram-desktop<br></code></pre></td></tr></table></figure></li><li>Visual Studio Code<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">paru -S visual-studio-code-bin<br></code></pre></td></tr></table></figure></li><li>Wine<br>先启动 <code>/etc/pacman.conf</code> 中的 <code>multilib</code> 源,其中包含32位软件</li></ol><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><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs zsh">sudo pacman -S lib32-mesa # 普通御三家卡<br>sudo pacman -S lib32-nvidia-utils # 绿厂闭源驱动,效果好,需要现代N卡<br>sudo pacman -S lib32-amdgpu-pro-oglp # 专业A卡<br>sudo pacman -S wine wine-gecko wine-mono lib32-alsa-lib lib32-alsa-plugins<br></code></pre></td></tr></table></figure><ol start="4"><li>Chrome / Edge<figure class="highlight plaintext"><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 zsh">paru -S microsoft-edge-stable-bin # 莫名慢速下载警告<br>paru -S google-chrome<br></code></pre></td></tr></table></figure></li><li>网易云替代品<br>Go 写的 musicfox,TUI 版网易云<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs zsh">paru -S go-musicfox-git<br></code></pre></td></tr></table></figure></li><li>Steam<figure class="highlight plaintext"><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 zsh">sudo pacman -S steam<br>sudo paru -S proton<br></code></pre></td></tr></table></figure><del>现在都能玩黄油了惹</del></li></ol>]]></content>
<categories>
<category>Software</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title>如何用 Electron 制作一个抽号的程序</title>
<link href="/2023/10/02/random-makeing-note/"/>
<url>/2023/10/02/random-makeing-note/</url>
<content type="html"><![CDATA[<p>电子包写的随机抽号</p><span id="more"></span><a class="btn" href="https://github.com/acidec/random" target="_blank">代码</a><h2 id="技术栈选择"><a href="#技术栈选择" class="headerlink" title="技术栈选择"></a>技术栈选择</h2><p>抽号机事给普通人用的,他们对于这些大概一窍不通,所以不能写纯 TUI 的。<br>要易用,有图形界面,像个正常软件一样,界面可以简单不能简陋。<br>我直接想到了电子包这个梗,简单易用出图形界面,会一点前端就行。<br><del>用电子包写软件,我们都有光明的未来</del></p><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><h3 id="先决条件"><a href="#先决条件" class="headerlink" title="先决条件"></a>先决条件</h3><p>首先你得有<code>nodejs</code>、可用的网络(别用残废的China Mainland局域网)、<code>git</code></p><a class="btn" href="https://nodejs.org/en" target="_blank">nodejs</a><p>&</p><a class="btn" href="https://mirrors.bfsu.edu.cn/github-release/git-for-windows/git/LatestRelease/" target="_blank">git</a><h3 id="配置Git"><a href="#配置Git" class="headerlink" title="配置Git"></a>配置Git</h3><figure class="highlight powershell"><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 powershell">git config <span class="hljs-literal">--global</span> user.name 名字 //可以随便填<br>git config <span class="hljs-literal">--global</span> user.email 邮箱 //要能用的,要是就看看代码也可以随便填<br>git config <span class="hljs-literal">--global</span> http.sslVerify false<br></code></pre></td></tr></table></figure><h3 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h3><figure class="highlight powershell"><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 powershell">mkdir random && <span class="hljs-built_in">cd</span> random<br>npm init<br></code></pre></td></tr></table></figure><p>然后他开始问问题,实话回答就行。</p><h3 id="启动脚本"><a href="#启动脚本" class="headerlink" title="启动脚本"></a>启动脚本</h3><p>最标准的,参考文档的启动脚本,创建一个<code>main.js</code>,填入以下内容</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs JavaScript"><span class="hljs-keyword">const</span> { app, <span class="hljs-title class_">BrowserWindow</span> } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'electron'</span>)<br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">createWindow</span> = (<span class="hljs-params"></span>) => {<br> <span class="hljs-keyword">const</span> win = <span class="hljs-keyword">new</span> <span class="hljs-title class_">BrowserWindow</span>({<br> <span class="hljs-attr">width</span>: <span class="hljs-number">1600</span>,<br> <span class="hljs-attr">height</span>: <span class="hljs-number">1000</span>,<br> <span class="hljs-attr">title</span>:<span class="hljs-string">"抽号机"</span><br> })<br><br> win.<span class="hljs-title function_">loadFile</span>(<span class="hljs-string">'index.html'</span>)<br>}<br><br>app.<span class="hljs-title function_">whenReady</span>().<span class="hljs-title function_">then</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-title function_">createWindow</span>()<br>})<br><br>app.<span class="hljs-title function_">on</span>(<span class="hljs-string">'window-all-closed'</span>, <span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">if</span> (process.<span class="hljs-property">platform</span> !== <span class="hljs-string">'darwin'</span>) app.<span class="hljs-title function_">quit</span>()<br> })<br><br></code></pre></td></tr></table></figure><p>其意为启动一个1600x1200的窗口,标题为”抽号机”,渲染”index.html”</p><h3 id="渲染的HTML"><a href="#渲染的HTML" class="headerlink" title="渲染的HTML"></a>渲染的HTML</h3><p>名字在<code>main.js</code>里写了叫<code>index.html</code></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-meta"><!DOCTYPE <span class="hljs-keyword">html</span>></span><br><span class="hljs-tag"><<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>></span><br><span class="hljs-tag"><<span class="hljs-name">head</span>></span><br><span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"</span>></span><br><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"css/bootstrap.css"</span> /></span><br><span class="hljs-tag"></<span class="hljs-name">head</span>></span><br><span class="hljs-tag"><<span class="hljs-name">body</span>></span><br><br><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container-fluid"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container-fluid"</span>></span><br><span class="hljs-tag"><<span class="hljs-name">h1</span>></span><span class="hljs-tag"><<span class="hljs-name">strong</span>></span>这是什么?抽号机喵~<span class="hljs-tag"></<span class="hljs-name">strong</span>></span><span class="hljs-tag"></<span class="hljs-name">h1</span>></span><br><span class="hljs-tag"><<span class="hljs-name">hr</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"border-top:1px dotted #ccc;"</span>/></span><br><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container-fluid"</span>></span><br><span class="hljs-tag"><<span class="hljs-name">h1</span>></span>1/54,保留6号<span class="hljs-tag"></<span class="hljs-name">h1</span>></span><br><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-info btn-lg"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"rollNumber();"</span>></span>抽!<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br><span class="hljs-tag"><<span class="hljs-name">br</span>></span><br><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container-fluid"</span>></span><br><span class="hljs-tag"><<span class="hljs-name">h2</span>></span>结果:<span class="hljs-tag"></<span class="hljs-name">h2</span>></span><br><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"result"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"script.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br><span class="hljs-tag"></<span class="hljs-name">body</span>></span><br><span class="hljs-tag"></<span class="hljs-name">html</span>></span><br></code></pre></td></tr></table></figure><p>班级有54个人,6号休学去写曲子了,把他留着。<br>引入了<code>BootStrap CSS</code>,去下载:</p><a class="btn" href="https://getbootstrap.com/docs/3.4/getting-started/" target="_blank">BootStrap CSS</a><p>下载之后把三样东西都放到<code>random</code>文件夹下。<br><del>按钮变得好看起来</del></p><p>引入的外部js代码在<code>script.js</code>里。</p><h3 id="逻辑部分"><a href="#逻辑部分" class="headerlink" title="逻辑部分"></a>逻辑部分</h3><p>需要用代码实现预定范围的随机数生成,并将其返回显示。<br><code>index.html</code>里写了代码在<code>script.js</code>里。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs JavaScript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">rollNumber</span>(<span class="hljs-params"></span>){<br><span class="hljs-keyword">var</span> set = [];<br><span class="hljs-keyword">while</span>(set.<span class="hljs-property">length</span> < <span class="hljs-number">1</span>){<br><span class="hljs-keyword">var</span> r = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">floor</span>(<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>() * <span class="hljs-number">53</span>) + <span class="hljs-number">1</span>;<br><span class="hljs-keyword">if</span>(set.<span class="hljs-title function_">indexOf</span>(r) === -<span class="hljs-number">1</span>) set.<span class="hljs-title function_">push</span>(r);<br>}<br> <br><span class="hljs-title function_">displayResult</span>(set);<br>}<br><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">displayResult</span>(<span class="hljs-params">set</span>){<br>set.<span class="hljs-title function_">join</span>(<span class="hljs-string">", "</span>);<br><br><span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'result'</span>).<span class="hljs-property">innerHTML</span>=<span class="hljs-string">"<center><h1 class='text-primary'>"</span>+set+<span class="hljs-string">"</h1></center>"</span>;<br>}<br></code></pre></td></tr></table></figure><p>从1-54号中抽一个并返回。</p><h2 id="跑"><a href="#跑" class="headerlink" title="跑"></a>跑</h2><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">npm run <span class="hljs-built_in">start</span><br></code></pre></td></tr></table></figure><p>启动一个Electron窗口,就是这个。呐,跑起来了。</p><h2 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h2><p>这是我放在 Github 上的代码的目录结构。<br>建议在获取之后运行下面代码更新 npm 包</p><p><code>npx npm-check-updates -u</code></p><ul><li><p>.gitignore -> 忽略一些构建生成和node modules,要不这几百M放Github上每次还得更新</p></li><li><p>forge.config.js -> 打包工具的依赖</p></li><li><p>index.html -> 网页</p></li><li><p>main.js -> 启动脚本</p></li><li><p>package(-lock).json -> npm的购物清单</p></li><li><p>script.js -> JavaScript代码</p></li><li><p>namelist.json -> new feature的名单,已打码(new feature暂时还未实现、、</p></li><li><p>css & fonts & js -> Bootstrap CSS</p></li></ul>]]></content>
<categories>
<category>Programming</category>
</categories>
<tags>
<tag>Electron</tag>
</tags>
</entry>
<entry>
<title>C 语言教程</title>
<link href="/2023/01/04/clang/"/>
<url>/2023/01/04/clang/</url>
<content type="html"><![CDATA[<p>从阮一峰老师那里转载。文章很长很长,字数已经可以出书了。</p><span id="more"></span><a class="btn" href="https://wangdoc.com/clang/" target="_blank">原文链接</a><h1 id="C-语言简介"><a href="#C-语言简介" class="headerlink" title="C 语言简介"></a>C 语言简介</h1><h2 id="历史"><a href="#历史" class="headerlink" title="历史"></a>历史</h2><p>C 语言最初是作为 Unix 系统的开发工具而发明的。</p><p>1969年,美国贝尔实验室的肯·汤普森(Ken Thompson)与丹尼斯·里奇(Dennis Ritchie)一起开发了 Unix 操作系统。Unix 是用汇编语言写的,无法移植到其他计算机,他们决定使用高级语言重写。但是,当时的高级语言无法满足他们的要求,汤普森就在 BCPL 语言的基础上发明了 B 语言。</p><p>1972年,丹尼斯·里奇和布莱恩·柯林汉(Brian Kernighan)又在 B 语言的基础上重新设计了一种新语言,这种新语言取代了 B 语言,所以称为 C 语言。</p><p>1973年,整个 Unix 系统都使用 C 语言重写。此后,这种语言开始快速流传,广泛用于各种操作系统和系统软件的开发。</p><p>1988年,美国国家标准协会(ANSI)正式将 C 语言标准化,标志着 C 语言开始稳定和规范化。</p><p>几十年后的今天,C 语言依然是最广泛使用、最流行的系统编程语言之一,Unix 和 Linux 系统现在还是使用 C 语言开发。</p><h2 id="C-语言的特点"><a href="#C-语言的特点" class="headerlink" title="C 语言的特点"></a>C 语言的特点</h2><p>C 语言能够长盛不衰、广泛应用,主要原因是它有一些鲜明的特点。</p><p>(1)低级语言</p><p>C 语言能够直接操作硬件、管理内存、跟操作系统对话,这使得它是一种非常接近底层的语言,也就是低级语言,非常适合写需要跟硬件交互、有极高性能要求的程序。</p><p>(2)可移植性</p><p>C 语言的原始设计目的,是将 Unix 系统移植到其他计算机架构。这使得它从一开始就非常注重可移植性,C 程序可以相对简单地移植到各种硬件架构和操作系统。</p><p>除了计算机,C 语言现在还是嵌入式系统的首选编程语言,汽车、照相机、家用电器等设备的底层系统都是用 C 语言编程,这也是因为它良好的可移植性。</p><p>(3)简单性</p><p>C 语言的语法相对简单,语法规则不算太多,也几乎没有语法糖。一般来说,如果两个语法可以完成几乎相同的事情,C 语言就只会提供一种,这样大大减少了语言的复杂性。</p><p>而且,C 语言的语法都是基础语法,不提供高级的数据结构,比如 C 语言没有“类”(class),复杂的数据结构都需要自己构造。</p><p>(4)灵活性</p><p>C 语言对程序员的限制很少。它假设程序员知道自己在干嘛,不会限制你做各种危险的操作,你干什么都可以,后果也由自己负责。</p><p>C 语言的哲学是“信任程序员,不要妨碍他们做事”。比如,它让程序员自己管理内存,不提供内存自动清理功能。另外,也不提供类型检查、数组的负索引检查、指针位置的检查等保护措施。</p><p>表面上看,这似乎很危险,但是对于高级程序员来说,却有了更大的编程自由。不过,这也使得 C 语言的 debug 不太容易。</p><p>(5)总结</p><p>上面这些特点,使得 C 语言可以写出性能非常强、完全发挥硬件潜力的程序,而且 C 语言的编译器实现难度相对较低。但是另一方面,C 语言代码容易出错,一般程序员不容易写好。</p><p>此外,当代很多流行语言都是以 C 语言为基础,比如 C++、Java、C#、JavaScript 等等。学好 C 语言有助于对这些语言加深理解。</p><h2 id="C-语言的版本"><a href="#C-语言的版本" class="headerlink" title="C 语言的版本"></a>C 语言的版本</h2><p>历史上,C 语言有过多个版本。</p><p>(1)K&R C</p><p><code>K&R C</code>指的是 C 语言的原始版本。1978年,C 语言的发明者丹尼斯·里奇(Dennis Ritchie)和布莱恩·柯林汉(Brian Kernighan)合写了一本著名的教材《C 编程语言》(The C programming language)。由于 C 语言还没有成文的语法标准,这本书就成了公认标准,以两位作者的姓氏首字母作为版本简称“K&R C”。</p><p>(2)ANSI C(又称 C89 或 C90)</p><p>C 语言的原始版本非常简单,对很多情况的描述非常模糊,加上 C 语法依然在快速发展,要求将 C 语言标准化的呼声越来越高。</p><p>1989年,美国国家标准协会(ANSI)制定了一套 C 语言标准。1990年,国际标准化组织(ISO)通过了这个标准。它被称为“ANSI C”,也可以按照发布年份,称为“C89 或 C90”。</p><p>(3)C95</p><p>1995年,美国国家标准协会对1989年的那个标准,进行了补充,加入多字节字符和宽字符的支持。这个版本称为 C95。</p><p>(4)C99</p><p>C 语言标准的第一次大型修订,发生在1999年,增加了许多语言特性,比如双斜杠(<code>//</code>)的注释语法。这个版本称为 C99,是目前最流行的 C 版本。</p><p>(5)C11</p><p>2011年,标准化组织再一次对 C 语言进行修订,增加了 Unicode 和多线程的支持。这个版本称为 C11。</p><p>(6)C17</p><p>C11 标准在2017年进行了修补,但发布是在2018年。新版本只是解决了 C11 的一些缺陷,没有引入任何新功能。这个版本称为 C17。</p><p>(7)C2x</p><p>标准化组织正在讨论 C 语言的下一个版本,据说可能会在2023年通过,到时就会称为 C23。</p><h2 id="C-语言的编译"><a href="#C-语言的编译" class="headerlink" title="C 语言的编译"></a>C 语言的编译</h2><p>C 语言是一种编译型语言,源码都是文本文件,本身无法执行。必须通过编译器,生成二进制的可执行文件,才能执行。编译器将代码从文本翻译成二进制指令的过程,就称为编译阶段,又称为“编译时”(compile time),跟运行阶段(又称为“运行时”)相区分。</p><p>目前,最常见的 C 语言编译器是自由软件基金会推出的 GCC 编译器,它可以免费使用。本书也使用这个编译器。Linux 和 Mac 系统可以直接安装 GCC,Windows 系统可以安装 MinGW。但是,也可以不用这么麻烦,网上有在线编译器,能够直接在网页上模拟运行 C 代码,查看结果,下面就是两个这样的工具。</p><ul><li>CodingGround: <a href="https://tutorialspoint.com/compile_c_online.php">https://tutorialspoint.com/compile_c_online.php</a></li><li>OnlineGDB: <a href="https://onlinegdb.com/online_c_compiler">https://onlinegdb.com/online_c_compiler</a></li></ul><p>本书的例子都使用 GCC 在命令行进行编译。</p><h2 id="Hello-World-示例"><a href="#Hello-World-示例" class="headerlink" title="Hello World 示例"></a>Hello World 示例</h2><p>C 语言的源代码文件,通常以后缀名<code>.c</code>结尾。下面是一个简单的 C 程序<code>hello.c</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Hello World\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面这个程序的唯一作用,就是在屏幕上面显示“Hello World”。</p><p>这里不讲解这些代码是什么意思,只是作为一个例子,让大家看看 C 代码应该怎么编译和运行。假设你已经安装好了 GCC 编译器,可以打开命令行,执行下面的命令。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc hello.c<br></code></pre></td></tr></table></figure><p>上面命令使用<code>gcc</code>编译器,将源文件<code>hello.c</code>编译成二进制代码。注意,<code>$</code>是命令行提示符,你真正需要输入的是<code>$</code>后面的部分。</p><p>运行这个命令以后,默认会在当前目录下生成一个编译产物文件<code>a.out</code>(assembler output 的缩写,Windows 平台为<code>a.exe</code>)。执行该文件,就会在屏幕上输出<code>Hello World</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ./a.out<br>Hello World<br></code></pre></td></tr></table></figure><p>GCC 的<code>-o</code>参数(output 的缩写)可以指定编译产物的文件名。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -o hello hello.c<br></code></pre></td></tr></table></figure><p>上面命令的<code>-o hello</code>指定,编译产物的文件名为<code>hello</code>(取代默认的<code>a.out</code>)。编译后就会生成一个名叫<code>hello</code>的可执行文件,相当于为<code>a.out</code>指定了名称。执行该文件,也会得到同样的结果。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ./hello<br>Hello World<br></code></pre></td></tr></table></figure><p>GCC 的<code>-std=</code>参数(standard 的缩写)还可以指定按照哪个 C 语言的标准进行编译。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -std=c99 hello.c<br></code></pre></td></tr></table></figure><p>上面命令指定按照 C99 标准进行编译。</p><p>注意,<code>-std</code>后面需要用<code>=</code>连接参数,而不是像上面的<code>-o</code>一样用空格,并且<code>=</code>前后也不能有多余的空格。</p><h1 id="C-语言基本语法"><a href="#C-语言基本语法" class="headerlink" title="C 语言基本语法"></a>C 语言基本语法</h1><h2 id="语句"><a href="#语句" class="headerlink" title="语句"></a>语句</h2><p>C 语言的代码由一行行语句(statement)组成。语句就是程序执行的一个操作命令。C 语言规定,语句必须使用分号结尾,除非有明确规定可以不写分号。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x = <span class="hljs-number">1</span>;<br></code></pre></td></tr></table></figure><p>上面就是一个变量声明语句,声明整数变量<code>x</code>,并且将值设为<code>1</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-type">int</span> x; x = <span class="hljs-number">1</span>;<br></code></pre></td></tr></table></figure><p>上面示例是两个语句写在一行。所以,语句之间的换行符并不是必需的,只是为了方便阅读代码。</p><p>一个语句也可以写成多行,这时就要依靠分号判断语句在哪一行结束。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x;<br>x<br>=<br><span class="hljs-number">1</span><br>;<br></code></pre></td></tr></table></figure><p>上面示例中,第二个语句<code>x = 1;</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">;<br></code></pre></td></tr></table></figure><h2 id="表达式"><a href="#表达式" class="headerlink" title="表达式"></a>表达式</h2><p>C 语言的各种计算,主要通过表达式完成。表达式(expression)是一个计算式,用来获取值。</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-number">1</span> + <span class="hljs-number">2</span><br></code></pre></td></tr></table></figure><p>上面代码就是一个表达式,用来获取<code>1 + 2</code>这个算术计算的结果。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-number">8</span>;<br><span class="hljs-number">3</span> + <span class="hljs-number">4</span>;<br></code></pre></td></tr></table></figure><p>上面示例是两个表达式,加上分号以后成为语句。</p><p>表达式与语句的区别主要是两点:</p><ul><li>语句可以包含表达式,但是表达式本身不构成语句。</li><li>表达式都有返回值,语句不一定有。因为语句用来执行某个命令,很多时候不需要返回值,比如变量声明语句(<code>int x = 1</code>)就没有返回值。</li></ul><h2 id="语句块"><a href="#语句块" class="headerlink" title="语句块"></a>语句块</h2><p>C 语言允许多个语句使用一对大括号<code>{}</code>,组成一个块,也称为复合语句(compounded statement)。在语法上,语句块可以视为多个语句组成的一个复合语句。</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">{<br> <span class="hljs-type">int</span> x;<br> x = <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,大括号形成了一个语句块。</p><p>大括号的结尾不需要添加分号。</p><h2 id="空格"><a href="#空格" class="headerlink" title="空格"></a>空格</h2><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x = <span class="hljs-number">1</span>;<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span> x=<span class="hljs-number">1</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,赋值号(<code>=</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-type">int</span> x = <span class="hljs-number">1</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,各个语法单位之间的多个空格,跟单个空格的效果是一样的。</p><p>空格还用来表示缩进。多层级的代码有没有缩进,其实对于编译器来说并没有差别,没有缩进的代码也是完全可以运行的。强调代码缩进,只是为了增强代码可读性,便于区分代码块。</p><p>大多数 C 语言的风格要求是,下一级代码比上一级缩进4个空格。为了书写的紧凑,本书采用缩写两个空格。</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">// 缩进四个空格</span><br><span class="hljs-keyword">if</span> (x > <span class="hljs-number">0</span>)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"positive\n"</span>);<br><br><span class="hljs-comment">// 缩进两个空格</span><br><span class="hljs-keyword">if</span> (x > <span class="hljs-number">0</span>)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"positive\n"</span>);<br></code></pre></td></tr></table></figure><p>只包含空格的行被称为空白行,编译器会完全忽略该行。</p><h2 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h2><p>注释是对代码的说明,编译器会忽略注释,也就是说,注释对实际代码没有影响。</p><p>C 语言的注释有两种表示方法。第一种方法是将注释放在<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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* 注释 */</span><br><br><span class="hljs-comment">/*</span><br><span class="hljs-comment"> 这是一行注释</span><br><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">open</span><span class="hljs-params">(<span class="hljs-type">char</span>* s <span class="hljs-comment">/* file name */</span>, <span class="hljs-type">int</span> mode)</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>/* file name */</code>用来对函数参数进行说明,跟在它后面的代码依然会有效执行。</p><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-built_in">printf</span>(<span class="hljs-string">"a "</span>); <span class="hljs-comment">/* 注释一</span><br><span class="hljs-comment">printf("b ");</span><br><span class="hljs-comment">printf("c "); /* 注释二 */</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"d "</span>);<br></code></pre></td></tr></table></figure><p>上面示例的原意是,第一行和第三行代码的尾部,有两个注释。但是,第一行注释忘记写结束符号,导致注释一延续到第三行结束。</p><p>第二种写法是将注释放在双斜杠<code>//</code>后面,从双斜杠到行尾都属于注释。这种注释只能是单行,可以放在行首,也可以放在一行语句的结尾。这是 C99 标准新增的语法。</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><br><span class="hljs-type">int</span> x = <span class="hljs-number">1</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-built_in">printf</span>(<span class="hljs-string">"// hello /* world */ "</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,双引号里面的注释符号,都会被视为普通字符,没有注释作用。</p><p>编译时,注释会被替换成一个空格,所以<code>min/* space */Value</code>会变成<code>min Value</code>,而不是<code>minValue</code>。</p><h2 id="printf"><a href="#printf" class="headerlink" title="printf()"></a>printf()</h2><h3 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h3><p>本书的示例会大量用到<code>printf()</code>函数,这里先介绍一下这个函数。</p><p><code>printf()</code>的作用是将参数文本输出到屏幕。它名字里面的<code>f</code>代表<code>format</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">printf</span>(<span class="hljs-string">"Hello World"</span>);<br></code></pre></td></tr></table></figure><p>上面命令会在屏幕上输出一行文字“Hello World”。</p><p><code>printf()</code>不会在行尾自动添加换行符,运行结束后,光标就停留在输出结束的地方,不会自动换行。为了让光标移到下一行的开头,可以在输出文本的结尾,添加一个换行符<code>\n</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">printf</span>(<span class="hljs-string">"Hello World\n"</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">printf</span>(<span class="hljs-string">"Hello\nWorld\n"</span>);<br></code></pre></td></tr></table></figure><p>上面示例先输出一个<code>Hello</code>,然后换行,在下一行开头输出<code>World</code>,然后又是一个换行。</p><p>上面示例也可以写成两个<code>printf()</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-built_in">printf</span>(<span class="hljs-string">"Hello\n"</span>);<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"World\n"</span>);<br></code></pre></td></tr></table></figure><p><code>printf()</code>是在标准库的头文件<code>stdio.h</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Hello World\n"</span>);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,只有在源码头部加上<code>#include <stdio.h></code>,才能使用<code>printf()</code>这个函数。<code>#include</code>指令的详细解释,请看《预处理器》一章。</p><h3 id="占位符"><a href="#占位符" class="headerlink" title="占位符"></a>占位符</h3><p><code>printf()</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">// 输出 There are 3 apples</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"There are %i apples\n"</span>, <span class="hljs-number">3</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>There are %i apples\n</code>是输出文本,里面的<code>%i</code>就是占位符,表示这个位置要用其他值来替换。占位符的第一个字符一律为百分号<code>%</code>,第二个字符表示占位符的类型,<code>%i</code>表示这里代入的值必须是一个整数。</p><p><code>printf()</code>的第二个参数就是替换占位符的值,上面的例子是整数<code>3</code>替换<code>%i</code>。执行后的输出结果就是<code>There are 3 apples</code>。</p><p>常用的占位符除了<code>%i</code>,还有<code>%s</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">printf</span>(<span class="hljs-string">"%s will come tonight\n"</span>, <span class="hljs-string">"Jane"</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>%s</code>表示代入的是一个字符串,所以<code>printf()</code>的第二个参数就必须是字符串,这个例子是<code>Jane</code>。执行后的输出就是<code>Jane will come tonight</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-built_in">printf</span>(<span class="hljs-string">"%s says it is %i o'clock\n"</span>, <span class="hljs-string">"Ben"</span>, <span class="hljs-number">21</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,输出文本<code>%s says it is %i o'clock</code>有两个占位符,第一个是字符串占位符<code>%s</code>,第二个是整数占位符<code>%i</code>,分别对应<code>printf()</code>的第二个参数(<code>Ben</code>)和第三个参数(<code>21</code>)。执行后的输出就是<code>Ben says it is 21 o'clock</code>。</p><p><code>printf()</code>参数与占位符是一一对应关系,如果有<code>n</code>个占位符,<code>printf()</code>的参数就应该有<code>n + 1</code>个。如果参数个数少于对应的占位符,<code>printf()</code>可能会输出内存中的任意值。</p><p><code>printf()</code>的占位符有许多种类,与 C 语言的数据类型相对应。下面按照字母顺序,列出常用的占位符,方便查找,具体含义在后面章节介绍。</p><ul><li><code>%a</code>:十六进制浮点数,字母输出为小写。</li><li><code>%A</code>:十六进制浮点数,字母输出为大写。</li><li><code>%c</code>:字符。</li><li><code>%d</code>:十进制整数。</li><li><code>%e</code>:使用科学计数法的浮点数,指数部分的<code>e</code>为小写。</li><li><code>%E</code>:使用科学计数法的浮点数,指数部分的<code>E</code>为大写。</li><li><code>%i</code>:整数,基本等同于<code>%d</code>。</li><li><code>%f</code>:小数(包含<code>float</code>类型和<code>double</code>类型)。</li><li><code>%g</code>:6个有效数字的浮点数。整数部分一旦超过6位,就会自动转为科学计数法,指数部分的<code>e</code>为小写。</li><li><code>%G</code>:等同于<code>%g</code>,唯一的区别是指数部分的<code>E</code>为大写。</li><li><code>%hd</code>:十进制 short int 类型。</li><li><code>%ho</code>:八进制 short int 类型。</li><li><code>%hx</code>:十六进制 short int 类型。</li><li><code>%hu</code>:unsigned short int 类型。</li><li><code>%ld</code>:十进制 long int 类型。</li><li><code>%lo</code>:八进制 long int 类型。</li><li><code>%lx</code>:十六进制 long int 类型。</li><li><code>%lu</code>:unsigned long int 类型。</li><li><code>%lld</code>:十进制 long long int 类型。</li><li><code>%llo</code>:八进制 long long int 类型。</li><li><code>%llx</code>:十六进制 long long int 类型。</li><li><code>%llu</code>:unsigned long long int 类型。</li><li><code>%Le</code>:科学计数法表示的 long double 类型浮点数。</li><li><code>%Lf</code>:long double 类型浮点数。</li><li><code>%n</code>:已输出的字符串数量。该占位符本身不输出,只将值存储在指定变量之中。</li><li><code>%o</code>:八进制整数。</li><li><code>%p</code>:指针。</li><li><code>%s</code>:字符串。</li><li><code>%u</code>:无符号整数(unsigned int)。</li><li><code>%x</code>:十六进制整数。</li><li><code>%zd</code>:<code>size_t</code>类型。</li><li><code>%%</code>:输出一个百分号。</li></ul><h3 id="输出格式"><a href="#输出格式" class="headerlink" title="输出格式"></a>输出格式</h3><p><code>printf()</code>可以定制占位符的输出格式。</p><p>(1)限定宽度</p><p><code>printf()</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">printf</span>(<span class="hljs-string">"%5d\n"</span>, <span class="hljs-number">123</span>); <span class="hljs-comment">// 输出为 " 123"</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>%5d</code>表示这个占位符的宽度至少为5位。如果不满5位,对应的值的前面会添加空格。</p><p>输出的值默认是右对齐,即输出内容前面会有空格;如果希望改成左对齐,在输出内容后面添加空格,可以在占位符的<code>%</code>的后面插入一个<code>-</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">printf</span>(<span class="hljs-string">"%-5d\n"</span>, <span class="hljs-number">123</span>); <span class="hljs-comment">// 输出为 "123 "</span><br></code></pre></td></tr></table></figure><p>上面示例中,输出内容<code>123</code>的后面添加了空格。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 输出 " 123.450000"</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%12f\n"</span>, <span class="hljs-number">123.45</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>%12f</code>表示输出的浮点数最少要占据12位。由于小数的默认显示精度是小数点后6位,所以<code>123.45</code>输出结果的头部会添加2个空格。</p><p>(2)总是显示正负号</p><p>默认情况下,<code>printf()</code>不对正数显示<code>+</code>号,只对负数显示<code>-</code>号。如果想让正数也输出<code>+</code>号,可以在占位符的<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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%+d\n"</span>, <span class="hljs-number">12</span>); <span class="hljs-comment">// 输出 +12</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%+d\n"</span>, <span class="hljs-number">-12</span>); <span class="hljs-comment">// 输出 -12</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>%+d</code>可以确保输出的数值,总是带有正负号。</p><p>(3)限定小数位数</p><p>输出小数时,有时希望限定小数的位数。举例来说,希望小数点后面只保留两位,占位符可以写成<code>%.2f</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">// 输出 Number is 0.50</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"Number is %.2f\n"</span>, <span class="hljs-number">0.5</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,如果希望小数点后面输出3位(<code>0.500</code>),占位符就要写成<code>%.3f</code>。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 输出为 " 0.50"</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%6.2f\n"</span>, <span class="hljs-number">0.5</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>%6.2f</code>表示输出字符串最小宽度为6,小数位数为2。所以,输出字符串的头部有两个空格。</p><p>最小宽度和小数位数这两个限定值,都可以用<code>*</code>代替,通过<code>printf()</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-built_in">printf</span>(<span class="hljs-string">"%*.*f\n"</span>, <span class="hljs-number">6</span>, <span class="hljs-number">2</span>, <span class="hljs-number">0.5</span>);<br><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%6.2f\n"</span>, <span class="hljs-number">0.5</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>%*.*f</code>的两个星号通过<code>printf()</code>的两个参数<code>6</code>和<code>2</code>传入。</p><p>(4)输出部分字符串</p><p><code>%s</code>占位符用来输出字符串,默认是全部输出。如果只想输出开头的部分,可以用<code>%.[m]s</code>指定输出的长度,其中<code>[m]</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">// 输出 hello</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%.5s\n"</span>, <span class="hljs-string">"hello world"</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,占位符<code>%.5s</code>表示只输出字符串“hello world”的前5个字符,即“hello”。</p><h2 id="标准库,头文件"><a href="#标准库,头文件" class="headerlink" title="标准库,头文件"></a>标准库,头文件</h2><p>程序需要用到的功能,不一定需要自己编写,C 语言可能已经自带了。程序员只要去调用这些自带的功能,就省得自己编写代码了。举例来说,<code>printf()</code>这个函数就是 C 语言自带的,只要去调用它,就能实现在屏幕上输出内容。</p><p>C 语言自带的所有这些功能,统称为“标准库”(standard library),因为它们是写入标准的,到底包括哪些功能,应该怎么使用的,都是规定好的,这样才能保证代码的规范和可移植。</p><p>不同的功能定义在不同的文件里面,这些文件统称为“头文件”(header file)。如果系统自带某一个功能,就一定还会自带描述这个功能的头文件,比如<code>printf()</code>的头文件就是系统自带的<code>stdio.h</code>。头文件的后缀通常是<code>.h</code>。</p><p>如果要使用某个功能,就必须先加载对应的头文件,加载使用的是<code>#include</code>命令。这就是为什么使用<code>printf()</code>之前,必须先加载<code>stdio.h</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br></code></pre></td></tr></table></figure><p>注意,加载头文件的<code>#include</code>语句不需要分号结尾,详见《预处理器》一章。</p><h1 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h1><p>变量(variable)可以理解成一块内存区域的名字。通过变量名,可以引用这块内存区域,获取里面存储的值。由于值可能发生变化,所以称为变量,否则就是常量了。</p><h2 id="变量名"><a href="#变量名" class="headerlink" title="变量名"></a>变量名</h2><p>变量名在 C 语言里面属于标识符(identifier),命名有严格的规范。</p><ul><li>只能由字母(包括大写和小写)、数字和下划线(<code>_</code>)组成。</li><li>不能以数字开头。</li><li>长度不能超过63个字符。</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></pre></td><td class="code"><pre><code class="hljs c">$zj<br>j**p<br><span class="hljs-number">2</span>cat<br>Hot-tab<br>tax rate<br>don<span class="hljs-number">'</span>t<br></code></pre></td></tr></table></figure><p>上面示例中,每一行的变量名都是无效的。</p><p>变量名区分大小写,<code>star</code>、<code>Star</code>、<code>STAR</code>都是不同的变量。</p><p>并非所有的词都能用作变量名,有些词在 C 语言里面有特殊含义(比如<code>int</code>),另一些词是命令(比如<code>continue</code>),它们都称为关键字,不能用作变量名。另外,C 语言还保留了一些词,供未来使用,这些保留字也不能用作变量名。下面就是 C 语言主要的关键字和保留字。</p><blockquote><p>auto, break, case, char, const, continue, default, do, double, else, enum, extern, float, for, goto, if, inline, int, long, register, restrict, return, short, signed, sizeof, static, struct, switch, typedef, union, unsigned, void, volatile, while</p></blockquote><p>另外,两个下划线开头的变量名,以及一个下划线 + 大写英文字母开头的变量名,都是系统保留的,自己不应该起这样的变量名。</p><h2 id="变量的声明"><a href="#变量的声明" class="headerlink" title="变量的声明"></a>变量的声明</h2><p>C 语言的变量,必须先声明后使用。如果一个变量没有声明,就直接使用,会报错。</p><p>每个变量都有自己的类型(type)。声明变量时,必须把变量的类型告诉编译器。</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> height;<br></code></pre></td></tr></table></figure><p>上面代码声明了变量<code>height</code>,并且指定类型为<code>int</code>(整数)。</p><p>如果几个变量具有相同类型,可以在同一行声明。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> height, width;<br><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span> height;<br><span class="hljs-type">int</span> width;<br></code></pre></td></tr></table></figure><p>注意,声明变量的语句必须以分号结尾。</p><p>一旦声明,变量的类型就不能在运行时修改。</p><h2 id="变量的赋值"><a href="#变量的赋值" class="headerlink" title="变量的赋值"></a>变量的赋值</h2><p>C 语言会在变量声明时,就为它分配内存空间,但是不会清除内存里面原来的值。这导致声明变量以后,变量会是一个随机的值。所以,变量一定要赋值以后才能使用。</p><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-type">int</span> num;<br>num = <span class="hljs-number">42</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,第一行声明了一个整数变量<code>num</code>,第二行给这个变量赋值。</p><p>变量的值应该与类型一致,不应该赋予不是同一个类型的值,比如<code>num</code>的类型是整数,就不应该赋值为小数。虽然 C 语言会自动转换类型,但是应该避免赋值运算符两侧的类型不一致。</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-type">int</span> num = <span class="hljs-number">42</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-type">int</span> x = <span class="hljs-number">1</span>, y = <span class="hljs-number">2</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> x, y;<br><br>x = <span class="hljs-number">1</span>;<br>y = (x = <span class="hljs-number">2</span> * x);<br></code></pre></td></tr></table></figure><p>上面代码中,变量<code>y</code>的值就是赋值表达式(<code>x = 2 * x</code>)的返回值<code>2</code>。</p><p>由于赋值表达式有返回值,所以 C 语言可以写出多重赋值表达式。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x, y, z, m, n;<br><br>x = y = z = m = n = <span class="hljs-number">3</span>;<br></code></pre></td></tr></table></figure><p>上面的代码是合法代码,一次为多个变量赋值。赋值运算符是从右到左执行,所以先为<code>n</code>赋值,然后依次为<code>m</code>、<code>z</code>、<code>y</code>和<code>x</code>赋值。</p><p>C 语言有左值(left value)和右值(right value)的概念。左值是可以放在赋值运算符左边的值,一般是变量;右值是可以放在赋值运算符右边的值,一般是一个具体的值。这是为了强调有些值不能放在赋值运算符的左边,比如<code>x = 1</code>是合法的表达式,但是<code>1 = x</code>就会报错。</p><h2 id="变量的作用域"><a href="#变量的作用域" class="headerlink" title="变量的作用域"></a>变量的作用域</h2><p>作用域(scope)指的是变量生效的范围。C 语言的变量作用域主要有两种:文件作用域(file scope)和块作用域(block scope)。</p><p>文件作用域(file scope)指的是,在源码文件顶层声明的变量,从声明的位置到文件结束都有效。</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> x = <span class="hljs-number">1</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%i\n"</span>, x);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>x</code>是在文件顶层声明的,从声明位置开始的整个当前文件都是它的作用域,可以在这个范围的任何地方读取这个变量,比如函数<code>main()</code>内部就可以读取这个变量。</p><p>块作用域(block scope)指的是由大括号(<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><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-type">int</span> a = <span class="hljs-number">12</span>;<br><br><span class="hljs-keyword">if</span> (a == <span class="hljs-number">12</span>) {<br> <span class="hljs-type">int</span> b = <span class="hljs-number">99</span>;<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d %d\n"</span>, a, b); <span class="hljs-comment">// 12 99</span><br>}<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, a); <span class="hljs-comment">// 12</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, b); <span class="hljs-comment">// 出错</span><br></code></pre></td></tr></table></figure><p>上面例子中,变量<code>b</code>是在<code>if</code>代码块里面声明的,所以对于大括号外面的代码,这个变量是不存在的。</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">{<br> <span class="hljs-type">int</span> i = <span class="hljs-number">10</span>;<br><br> {<br> <span class="hljs-type">int</span> i = <span class="hljs-number">20</span>;<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, i); <span class="hljs-comment">// 20</span><br> }<br><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, i); <span class="hljs-comment">// 10</span><br>}<br></code></pre></td></tr></table></figure><p>上面示例中,内层和外层都有一个变量<code>i</code>,每个作用域都会优先使用当前作用域声明的<code>i</code>。</p><p>最常见的块作用域就是函数,函数内部声明的变量,对于函数外部是不可见的。<code>for</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">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, i);<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, i); <span class="hljs-comment">// 出错</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>for</code>循环省略了大括号,但依然是一个块作用域,在外部读取循环变量<code>i</code>,编译器就会报错。</p><h1 id="运算符"><a href="#运算符" class="headerlink" title="运算符"></a>运算符</h1><p>C 语言的运算符非常多,一共有 50 多种,可以分成若干类。</p><h2 id="算术运算符"><a href="#算术运算符" class="headerlink" title="算术运算符"></a>算术运算符</h2><p>算术运算符专门用于算术运算,主要有下面几种。</p><ul><li><code>+</code>:正值运算符(一元运算符)</li><li><code>-</code>:负值运算符(一元运算符)</li><li><code>+</code>:加法运算符(二元运算符)</li><li><code>-</code>:减法运算符(二元运算符)</li><li><code>*</code>:乘法运算符</li><li><code>/</code>:除法运算符</li><li><code>%</code>:余值运算符</li></ul><p>(1)<code>+</code>,<code>-</code></p><p><code>+</code>和<code>-</code>既可以作为一元运算符,也可以作为二元运算符。所谓“一元运算符”,指的是只需要一个运算数就可以执行。一元运算符<code>-</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-type">int</span> x = <span class="hljs-number">-12</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>-</code>将<code>12</code>这个值变成<code>-12</code>。</p><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-type">int</span> x = <span class="hljs-number">-12</span>;<br><span class="hljs-type">int</span> y = +x;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>y</code>的值还是<code>-12</code>,因为<code>+</code>不会改变正负值。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x = <span class="hljs-number">4</span> + <span class="hljs-number">22</span>;<br><span class="hljs-type">int</span> y = <span class="hljs-number">61</span> - <span class="hljs-number">23</span>;<br></code></pre></td></tr></table></figure><p>(2)<code>*</code></p><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-type">int</span> num = <span class="hljs-number">5</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%i\n"</span>, num * num); <span class="hljs-comment">// 输出 25</span><br></code></pre></td></tr></table></figure><p>(3)<code>/</code></p><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-type">float</span> x = <span class="hljs-number">6</span> / <span class="hljs-number">4</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%f\n"</span>, x); <span class="hljs-comment">// 输出 1.000000</span><br></code></pre></td></tr></table></figure><p>上面示例中,尽管变量<code>x</code>的类型是<code>float</code>(浮点数),但是<code>6 / 4</code>得到的结果是<code>1.0</code>,而不是<code>1.5</code>。原因就在于 C 语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分。</p><p>如果希望得到浮点数的结果,两个运算数必须至少有一个浮点数,这时 C 语言就会进行浮点数除法。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">float</span> x = <span class="hljs-number">6.0</span> / <span class="hljs-number">4</span>; <span class="hljs-comment">// 或者写成 6 / 4.0</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%f\n"</span>, x); <span class="hljs-comment">// 输出 1.500000</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>6.0 / 4</code>表示进行浮点数除法,得到的结果就是<code>1.5</code>。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> score = <span class="hljs-number">5</span>;<br>score = (score / <span class="hljs-number">20</span>) * <span class="hljs-number">100</span>;<br></code></pre></td></tr></table></figure><p>上面的代码,你可能觉得经过运算,<code>score</code>会等于<code>25</code>,但是实际上<code>score</code>等于<code>0</code>。这是因为<code>score / 20</code>是整除,会得到一个整数值<code>0</code>,所以乘以<code>100</code>后得到的也是<code>0</code>。</p><p>为了得到预想的结果,可以将除数<code>20</code>改成<code>20.0</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">score = (score / <span class="hljs-number">20.0</span>) * <span class="hljs-number">100</span>;<br></code></pre></td></tr></table></figure><p>(4)<code>%</code></p><p>运算符<code>%</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-type">int</span> x = <span class="hljs-number">6</span> % <span class="hljs-number">4</span>; <span class="hljs-comment">// 2</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-number">11</span> % <span class="hljs-number">-5</span> <span class="hljs-comment">// 1</span><br><span class="hljs-number">-11</span> % <span class="hljs-number">-5</span> <span class="hljs-comment">// -1</span><br><span class="hljs-number">-11</span> % <span class="hljs-number">5</span> <span class="hljs-comment">// -1</span><br></code></pre></td></tr></table></figure><p>上面示例中,第一个运算数的正负号(<code>11</code>或<code>-11</code>)决定了结果的正负号。</p><p>(5)赋值运算的简写形式</p><p>如果变量对自身的值进行算术运算,C 语言提供了简写形式,允许将赋值运算符和算术运算符结合成一个运算符。</p><ul><li><code>+=</code></li><li><code>-=</code></li><li><code>*=</code></li><li><code>/=</code></li><li><code>%=</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></pre></td><td class="code"><pre><code class="hljs c">i += <span class="hljs-number">3</span>; <span class="hljs-comment">// 等同于 i = i + 3</span><br>i -= <span class="hljs-number">8</span>; <span class="hljs-comment">// 等同于 i = i - 8</span><br>i *= <span class="hljs-number">9</span>; <span class="hljs-comment">// 等同于 i = i * 9</span><br>i /= <span class="hljs-number">2</span>; <span class="hljs-comment">// 等同于 i = i / 2</span><br>i %= <span class="hljs-number">5</span>; <span class="hljs-comment">// 等同于 i = i % 5</span><br></code></pre></td></tr></table></figure><h2 id="自增运算符,自减运算符"><a href="#自增运算符,自减运算符" class="headerlink" title="自增运算符,自减运算符"></a>自增运算符,自减运算符</h2><p>C 语言提供两个运算符,对变量自身进行<code>+ 1</code>和<code>- 1</code>的操作。</p><ul><li><code>++</code>:自增运算符</li><li><code>--</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></pre></td><td class="code"><pre><code class="hljs c">i++; <span class="hljs-comment">// 等同于 i = i + 1</span><br>i--; <span class="hljs-comment">// 等同于 i = i - 1</span><br></code></pre></td></tr></table></figure><p>这两个运算符放在变量的前面或后面,结果是不一样的。<code>++var</code>和<code>--var</code>是先执行自增或自减操作,再返回操作后<code>var</code>的值;<code>var++</code>和<code>var--</code>则是先返回操作前<code>var</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-type">int</span> i = <span class="hljs-number">42</span>;<br><span class="hljs-type">int</span> j;<br><br>j = (i++ + <span class="hljs-number">10</span>);<br><span class="hljs-comment">// i: 43</span><br><span class="hljs-comment">// j: 52</span><br><br>j = (++i + <span class="hljs-number">10</span>)<br><span class="hljs-comment">// i: 44</span><br><span class="hljs-comment">// j: 54</span><br></code></pre></td></tr></table></figure><p>上面示例中,自增运算符的位置差异,会导致变量<code>j</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">/* 写法一 */</span><br>j = (i + <span class="hljs-number">10</span>);<br>i++;<br><br><span class="hljs-comment">/* 写法二 */</span><br>i++;<br>j = (i + <span class="hljs-number">10</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>i</code>的自增运算与返回值是分离的两个步骤,这样就不太会出错,也提高了代码的可读性。</p><h2 id="关系运算符"><a href="#关系运算符" class="headerlink" title="关系运算符"></a>关系运算符</h2><p>C 语言用于比较的表达式,称为“关系表达式”(relational expression),里面使用的运算符就称为“关系运算符”(relational operator),主要有下面6个。</p><ul><li><code>></code> 大于运算符</li><li><code><</code> 小于运算符</li><li><code>>=</code> 大于等于运算符</li><li><code><=</code> 小于等于运算符</li><li><code>==</code> 相等运算符</li><li><code>!=</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></pre></td><td class="code"><pre><code class="hljs c">a == b;<br>a != b;<br>a < b;<br>a > b;<br>a <= b;<br>a >= b;<br></code></pre></td></tr></table></figure><p>关系表达式通常返回<code>0</code>或<code>1</code>,表示真伪。C 语言中,<code>0</code>表示伪,所有非零值表示真。比如,<code>20 > 12</code>返回<code>1</code>,<code>12 > 20</code>返回<code>0</code>。</p><p>关系表达式常用于<code>if</code>或<code>while</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-keyword">if</span> (x == <span class="hljs-number">3</span>) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"x is 3.\n"</span>);<br>}<br></code></pre></td></tr></table></figure><p>注意,相等运算符<code>==</code>与赋值运算符<code>=</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">if</span> (x = <span class="hljs-number">3</span>) ...<br></code></pre></td></tr></table></figure><p>上面示例中,原意是<code>x == 3</code>,但是不小心写成<code>x = 3</code>。这个式子表示对变量<code>x</code>赋值<code>3</code>,它的返回值为<code>3</code>,所以<code>if</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-keyword">if</span> (<span class="hljs-number">3</span> == x) ...<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* 报错 */</span><br><span class="hljs-keyword">if</span> (<span class="hljs-number">3</span> = x) ...<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">i < j < k<br></code></pre></td></tr></table></figure><p>上面示例中,连续使用两个小于运算符。这是合法表达式,不会报错,但是通常达不到想要的结果,即不是保证变量<code>j</code>的值在<code>i</code>和<code>k</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">(i < j) < k<br></code></pre></td></tr></table></figure><p>上面式子中,<code>i < j</code>返回<code>0</code>或<code>1</code>,所以最终是<code>0</code>或<code>1</code>与变量<code>k</code>进行比较。如果想要判断变量<code>j</code>的值是否在<code>i</code>和<code>k</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">i < j && j < k<br></code></pre></td></tr></table></figure><h2 id="逻辑运算符"><a href="#逻辑运算符" class="headerlink" title="逻辑运算符"></a>逻辑运算符</h2><p>逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。</p><ul><li><code>!</code>:否运算符(改变单个表达式的真伪)。</li><li><code>&&</code>:与运算符(两侧的表达式都为真,则为真,否则为伪)。</li><li><code>||</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (x < <span class="hljs-number">10</span> && y > <span class="hljs-number">20</span>)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Doing something!\n"</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,只有<code>x < 10</code>和<code>y > 20</code>同时为真,<code>x < 10 && y > 20</code>才会为真。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (!(x < <span class="hljs-number">12</span>))<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"x is not less than 12\n"</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,由于否运算符<code>!</code>具有比<code><</code>更高的优先级,所以必须使用括号,才能对表达式<code>x < 12</code>进行否运算。当然,合理的写法是<code>if (x >= 12)</code>,这里只是为了举例。</p><p>对于逻辑运算符来说,任何非零值都表示真,零值表示伪。比如,<code>5 || 0</code>会返回<code>1</code>,<code>5 && 0</code>会返回<code>0</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-keyword">if</span> (number != <span class="hljs-number">0</span> && <span class="hljs-number">12</span>/number == <span class="hljs-number">2</span>)<br></code></pre></td></tr></table></figure><p>上面示例中,如果<code>&&</code>左侧的表达式(<code>number != 0</code>)为伪,即<code>number</code>等于<code>0</code>时,右侧的表达式(<code>12/number == 2</code>)是不会执行的。因为这时左侧表达式返回<code>0</code>,整个<code>&&</code>表达式肯定为伪,就直接返回<code>0</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-keyword">while</span> ((x++ < <span class="hljs-number">10</span>) && (x + y < <span class="hljs-number">20</span>))<br></code></pre></td></tr></table></figure><p>上面示例中,执行左侧表达式后,变量<code>x</code>的值就已经变了。等到执行右侧表达式的时候,是用新的值在计算,这通常不是原始意图。</p><h2 id="位运算符"><a href="#位运算符" class="headerlink" title="位运算符"></a>位运算符</h2><p>C 语言提供一些位运算符,用来操作二进制位(bit)。</p><p>(1)取反运算符<code>~</code></p><p>取反运算符<code>~</code>是一个一元运算符,用来将每一个二进制位变成相反值,即<code>0</code>变成<code>1</code>,<code>1</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 返回 01101100</span><br>~ <span class="hljs-number">10010011</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>~</code>对每个二进制位取反,就得到了一个新的值。</p><p>注意,<code>~</code>运算符不会改变变量的值,只是返回一个新的值。</p><p>(2)与运算符<code>&</code></p><p>与运算符<code>&</code>将两个值的每一个二进制位进行比较,返回一个新的值。当两个二进制位都为<code>1</code>,就返回<code>1</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 返回 00010001</span><br><span class="hljs-number">10010011</span> & <span class="hljs-number">00111101</span><br></code></pre></td></tr></table></figure><p>上面示例中,两个八位二进制数进行逐位比较,返回一个新的值。</p><p>与运算符<code>&</code>可以与赋值运算符<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><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">3</span>;<br>val = val & <span class="hljs-number">0377</span>;<br><br><span class="hljs-comment">// 简写成</span><br>val &= <span class="hljs-number">0377</span>;<br></code></pre></td></tr></table></figure><p>(3)或运算符<code>|</code></p><p>或运算符<code>|</code>将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位只要有一个为<code>1</code>(包含两个都为<code>1</code>的情况),就返回<code>1</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 返回 10111111</span><br><span class="hljs-number">10010011</span> | <span class="hljs-number">00111101</span><br></code></pre></td></tr></table></figure><p>或运算符<code>|</code>可以与赋值运算符<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><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">3</span>;<br>val = val | <span class="hljs-number">0377</span>;<br><br><span class="hljs-comment">// 简写为</span><br>val |= <span class="hljs-number">0377</span>;<br></code></pre></td></tr></table></figure><p>(4)异或运算符<code>^</code></p><p>异或运算符<code>^</code>将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位有且仅有一个为<code>1</code>,就返回<code>1</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 返回 10101110</span><br><span class="hljs-number">10010011</span> ^ <span class="hljs-number">00111101</span><br></code></pre></td></tr></table></figure><p>异或运算符<code>^</code>可以与赋值运算符<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><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">3</span>;<br>val = val ^ <span class="hljs-number">0377</span>;<br><br><span class="hljs-comment">// 简写为</span><br>val ^= <span class="hljs-number">0377</span>;<br></code></pre></td></tr></table></figure><p>(5)左移运算符<code><<</code></p><p>左移运算符<code><<</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 1000101000</span><br><span class="hljs-number">10001010</span> << <span class="hljs-number">2</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>10001010</code>的每一个二进制位,都向左侧移动了两位。</p><p>左移运算符相当于将运算数乘以2的指定次方,比如左移2位相当于乘以4(2的2次方)。</p><p>左移运算符<code><<</code>可以与赋值运算符<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><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">1</span>;<br>val = val << <span class="hljs-number">2</span>;<br><br><span class="hljs-comment">// 简写为</span><br>val <<= <span class="hljs-number">2</span>;<br></code></pre></td></tr></table></figure><p>(6)右移运算符<code>>></code></p><p>右移运算符<code>>></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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 返回 00100010</span><br><span class="hljs-number">10001010</span> >> <span class="hljs-number">2</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>10001010</code>的每一个二进制位,都向右移动两位。最低的两位<code>10</code>被丢弃,头部多出来的两位补<code>0</code>,所以最后得到<code>00100010</code>。</p><p>注意,右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。</p><p>右移运算符相当于将运算数除以2的指定次方,比如右移2位就相当于除以4(2的2次方)。</p><p>右移运算符<code>>></code>可以与赋值运算符<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><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">1</span>;<br>val = val >> <span class="hljs-number">2</span>;<br><br><span class="hljs-comment">// 简写为</span><br>val >>= <span class="hljs-number">2</span>;<br></code></pre></td></tr></table></figure><h2 id="逗号运算符"><a href="#逗号运算符" class="headerlink" title="逗号运算符"></a>逗号运算符</h2><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">x = <span class="hljs-number">10</span>, y = <span class="hljs-number">20</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,有两个表达式(<code>x = 10</code>和<code>y = 20</code>),逗号使得它们可以放在同一条语句里面。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x;<br>x = <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,逗号的优先级低于赋值运算符,所以先执行赋值运算,再执行逗号运算,变量<code>x</code>等于<code>1</code>。</p><h2 id="运算优先级"><a href="#运算优先级" class="headerlink" title="运算优先级"></a>运算优先级</h2><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-number">3</span> + <span class="hljs-number">4</span> * <span class="hljs-number">5</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,表达式<code>3 + 4 * 5</code>里面既有加法运算符(<code>+</code>),又有乘法运算符(<code>*</code>)。由于乘法的优先级高于加法,所以会先计算<code>4 * 5</code>,而不是先计算<code>3 + 4</code>。</p><p>如果两个运算符优先级相同,则根据运算符是左结合,还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符(<code>=</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-number">5</span> * <span class="hljs-number">6</span> / <span class="hljs-number">2</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>*</code>和<code>/</code>的优先级相同,它们都是左结合运算符,所以从左到右执行,先计算<code>5 * 6</code>,再计算<code>6 / 2</code>。</p><p>运算符的优先级顺序很复杂。下面是部分运算符的优先级顺序(按照优先级从高到低排列)。</p><ul><li>圆括号(<code>()</code>)</li><li>自增运算符(<code>++</code>),自减运算符(<code>--</code>)</li><li>一元运算符(<code>+</code>和<code>-</code>)</li><li>乘法(<code>*</code>),除法(<code>/</code>)</li><li>加法(<code>+</code>),减法(<code>-</code>)</li><li>关系运算符(<code><</code>、<code>></code>等)</li><li>赋值运算符(<code>=</code>)</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> x = (<span class="hljs-number">3</span> + <span class="hljs-number">4</span>) * <span class="hljs-number">5</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,由于添加了圆括号,加法会先于乘法进行运算。</p><p>完全记住所有运算符的优先级没有必要,解决方法是多用圆括号,防止出现意料之外的情况,也有利于提高代码的可读性。</p><h1 id="流程控制"><a href="#流程控制" class="headerlink" title="流程控制"></a>流程控制</h1><p>C 语言的程序是顺序执行,即先执行前面的语句,再执行后面的语句。开发者如果想要控制程序执行的流程,就必须使用流程控制的语法结构,主要是条件执行和循环执行。</p><h2 id="if-语句"><a href="#if-语句" class="headerlink" title="if 语句"></a>if 语句</h2><p><code>if</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">if</span> (expression) statement<br></code></pre></td></tr></table></figure><p>上面式子中,表达式<code>expression</code>为真(值不为<code>0</code>)时,就执行<code>statement</code>语句。</p><p><code>if</code>后面的判断条件<code>expression</code>外面必须有圆括号,否则会报错。语句体部分<code>statement</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">if</span> (x == <span class="hljs-number">10</span>) <span class="hljs-built_in">printf</span>(<span class="hljs-string">"x is 10"</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,当变量<code>x</code>为<code>10</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">if</span> (x == <span class="hljs-number">10</span>)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"x is 10\n"</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">if</span> (line_num == MAX_LINES) {<br> line_num = <span class="hljs-number">0</span>;<br> page_num++;<br>}<br></code></pre></td></tr></table></figure><p><code>if</code>语句可以带有<code>else</code>分支,指定条件不成立时(表达式<code>expression</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (expression) statement<br><span class="hljs-keyword">else</span> statement<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">if</span> (i > j)<br> max = i;<br><span class="hljs-keyword">else</span><br> max = j;<br></code></pre></td></tr></table></figure><p>如果<code>else</code>的语句部分多于一行,同样可以把它们放在大括号里面。</p><p><code>else</code>可以与另一个<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><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">if</span> (expression)<br> statement<br><span class="hljs-keyword">else</span> <span class="hljs-title function_">if</span> <span class="hljs-params">(expression)</span><br> statement<br>...<br><span class="hljs-keyword">else</span> <span class="hljs-title function_">if</span> <span class="hljs-params">(expression)</span><br> statement<br><span class="hljs-keyword">else</span><br> statement<br></code></pre></td></tr></table></figure><p>如果有多个<code>if</code>和<code>else</code>,可以记住这样一条规则,<code>else</code>总是跟最接近的<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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (number > <span class="hljs-number">6</span>)<br> <span class="hljs-keyword">if</span> (number < <span class="hljs-number">12</span>)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"The number is more than 6, less than 12.\n"</span>);<br><span class="hljs-keyword">else</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"It is wrong number.\n"</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>else</code>部分匹配最近的<code>if</code>(即<code>number < 12</code>),所以如果<code>number</code>等于6,就不会执行<code>else</code>的部分。</p><p>这样很容易出错,为了提供代码的可读性,建议使用大括号,明确<code>else</code>匹配哪一个<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> (number > <span class="hljs-number">6</span>) {<br> <span class="hljs-keyword">if</span> (number < <span class="hljs-number">12</span>) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"The number is more than 6, less than 12.\n"</span>);<br> }<br>} <span class="hljs-keyword">else</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"It is wrong number.\n"</span>);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,使用了大括号,就可以清晰地看出<code>else</code>匹配外层的<code>if</code>。</p><h2 id="三元运算符"><a href="#三元运算符" class="headerlink" title="三元运算符 ?:"></a>三元运算符 ?:</h2><p>C 语言有一个三元表达式<code>?:</code>,可以用作<code>if...else</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"><expression1> ? <expression2> : <expression3><br></code></pre></td></tr></table></figure><p>这个操作符的含义是,表达式<code>expression1</code>如果为<code>true</code>(非0值),就执行<code>expression2</code>,否则执行<code>expression3</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">(i > j) ? i : j;<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (i > j)<br> <span class="hljs-keyword">return</span> i;<br><span class="hljs-keyword">else</span><br> <span class="hljs-keyword">return</span> j;<br></code></pre></td></tr></table></figure><h2 id="switch-语句"><a href="#switch-语句" class="headerlink" title="switch 语句"></a>switch 语句</h2><p>switch 语句是一种特殊形式的 if…else 结构,用于判断条件有多个结果的情况。它把多重的<code>else 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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">switch</span> (expression) {<br> <span class="hljs-keyword">case</span> value1: statement<br> <span class="hljs-keyword">case</span> value2: statement<br> <span class="hljs-keyword">default</span>: statement<br>}<br></code></pre></td></tr></table></figure><p>上面代码中,根据表达式<code>expression</code>不同的值,执行相应的<code>case</code>分支。如果找不到对应的值,就执行<code>default</code>分支。</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">switch</span> (grade) {<br> <span class="hljs-keyword">case</span> <span class="hljs-number">0</span>:<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"False"</span>);<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-number">1</span>:<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"True"</span>);<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">default</span>:<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Illegal"</span>);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,根据变量<code>grade</code>不同的值,会执行不同的<code>case</code>分支。如果等于<code>0</code>,执行<code>case 0</code>的部分;如果等于<code>1</code>,执行<code>case 1</code>的部分;否则,执行<code>default</code>的部分。<code>default</code>表示处理以上所有<code>case</code>都不匹配的情况。</p><p>每个<code>case</code>语句体的结尾,都应该有一个<code>break</code>语句,作用是跳出整个<code>switch</code>结构,不再往下执行。如果缺少<code>break</code>,就会导致继续执行下一个<code>case</code>或<code>default</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">switch</span> (grade) {<br> <span class="hljs-keyword">case</span> <span class="hljs-number">0</span>:<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"False"</span>);<br> <span class="hljs-keyword">case</span> <span class="hljs-number">1</span>:<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"True"</span>);<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">default</span>:<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Illegal"</span>);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>case 0</code>的部分没有<code>break</code>语句,导致这个分支执行完以后,不会跳出<code>switch</code>结构,继续执行<code>case 1</code>分支。</p><p>利用这个特点,如果多个<code>case</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-keyword">switch</span> (grade) {<br> <span class="hljs-keyword">case</span> <span class="hljs-number">0</span>:<br> <span class="hljs-keyword">case</span> <span class="hljs-number">1</span>:<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"True"</span>);<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">default</span>:<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Illegal"</span>);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>case 0</code>分支没有任何语句,导致<code>case 0</code>和<code>case 1</code>都会执行同样的语句体。</p><p><code>case</code>后面的语句体,不用放在大括号里面,这也是为什么需要<code>break</code>的原因。</p><p><code>default</code>分支用来处理前面的 case 都不匹配的情况,最好放在所有 case 的后面,这样就不用写<code>break</code>语句。这个分支是可选的,如果没有该分支,遇到所有的 case 都不匹配的情况,就会直接跳出整个 switch 代码块。</p><h2 id="while-语句"><a href="#while-语句" class="headerlink" title="while 语句"></a>while 语句</h2><p><code>while</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">while</span> (expression)<br> statement<br></code></pre></td></tr></table></figure><p>上面代码中,如果表达式<code>expression</code>为非零值(表示真),就会执行<code>statement</code>语句,然后再次判断<code>expression</code>是否为零;如果<code>expression</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">while</span> (i < n)<br> i = i + <span class="hljs-number">2</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,只要<code>i</code>小于<code>n</code>,<code>i</code>就会不断增加2。</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-keyword">while</span> (expression) {<br> statement;<br> statement;<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><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">i = <span class="hljs-number">0</span>;<br><br><span class="hljs-keyword">while</span> (i < <span class="hljs-number">10</span>) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"i is now %d!\n"</span>, i);<br> i++;<br>}<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"All done!\n"</span>);<br></code></pre></td></tr></table></figure><p>上面代码中,循环体会执行10次,每次将<code>i</code>增加<code>1</code>,直到等于<code>10</code>才退出循环。</p><p>只要条件为真,<code>while</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-keyword">while</span> (<span class="hljs-number">1</span>) {<br> <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></table></figure><p>上面的示例虽然是无限循环,但是循环体内部可以用<code>break</code>语句跳出循环。</p><h2 id="do…while-结构"><a href="#do…while-结构" class="headerlink" title="do…while 结构"></a>do…while 结构</h2><p><code>do...while</code>结构是<code>while</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">do</span> statement<br><span class="hljs-title function_">while</span> <span class="hljs-params">(expression)</span>;<br></code></pre></td></tr></table></figure><p>上面代码中,不管条件<code>expression</code>是否成立,循环体<code>statement</code>至少会执行一次。每次<code>statement</code>执行完毕,就会判断一次<code>expression</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">i = <span class="hljs-number">10</span>;<br><br><span class="hljs-keyword">do</span> --i;<br><span class="hljs-keyword">while</span> (i > <span class="hljs-number">0</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>i</code>先减去1,再判断是否大于0。如果大于0,就继续减去1,直到<code>i</code>等于<code>0</code>为止。</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></pre></td><td class="code"><pre><code class="hljs c">i = <span class="hljs-number">10</span>;<br><br><span class="hljs-keyword">do</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"i is %d\n"</span>, i);<br> i++;<br>} <span class="hljs-keyword">while</span> (i < <span class="hljs-number">10</span>);<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"All done!\n"</span>);<br></code></pre></td></tr></table></figure><p>上面例子中,变量<code>i</code>并不满足小于<code>10</code>的条件,但是循环体还是会执行一次。</p><h2 id="for-语句"><a href="#for-语句" class="headerlink" title="for 语句"></a>for 语句</h2><p><code>for</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">for</span> (initialization; continuation; action)<br> statement;<br></code></pre></td></tr></table></figure><p>上面代码中,<code>for</code>语句的条件部分(即圆括号里面的部分)有三个表达式。</p><ul><li><code>initialization</code>:初始化表达式,用于初始化循环变量,只执行一次。</li><li><code>continuation</code>:判断表达式,只要为<code>true</code>,就会不断执行循环体。</li><li><code>action</code>:循环变量处理表达式,每轮循环结束后执行,使得循环变量发生变化。</li></ul><p>循环体部分的<code>statement</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">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">10</span>; i > <span class="hljs-number">0</span>; i--)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"i is %d\n"</span>, i);<br></code></pre></td></tr></table></figure><p>上面示例中,循环变量<code>i</code>在<code>for</code>的第一个表达式里面声明,该变量只用于本次循环。离开循环体之后,就会失效。</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> i, j;<br><span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>, j = <span class="hljs-number">999</span>; i < <span class="hljs-number">10</span>; i++, j--) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d, %d\n"</span>, i, j);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,初始化部分有两个语句,分别对变量<code>i</code>和<code>j</code>进行赋值。</p><p><code>for</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-keyword">for</span> (;;) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"本行会无限循环地打印。\n"</span> );<br>}<br></code></pre></td></tr></table></figure><p>上面示例由于没有判断条件,就会形成无限循环。</p><h2 id="break-语句"><a href="#break-语句" class="headerlink" title="break 语句"></a>break 语句</h2><p><code>break</code>语句有两种用法。一种是与<code>switch</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-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">3</span>; i++) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j < <span class="hljs-number">3</span>; j++) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d, %d\n"</span>, i, j);<br> <span class="hljs-keyword">break</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>break</code>语句使得循环跳到下一个<code>i</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> ((ch = getchar()) != EOF) {<br> <span class="hljs-keyword">if</span> (ch == <span class="hljs-string">'\n'</span>) <span class="hljs-keyword">break</span>;<br> <span class="hljs-built_in">putchar</span>(ch);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,一旦读到换行符(<code>\n</code>),<code>break</code>命令就跳出整个<code>while</code>循环,不再继续读取了。</p><p>注意,<code>break</code>命令只能跳出循环体和<code>switch</code>结构,不能跳出<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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (n > <span class="hljs-number">1</span>) {<br> <span class="hljs-keyword">if</span> (n > <span class="hljs-number">2</span>) <span class="hljs-keyword">break</span>; <span class="hljs-comment">// 无效</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"hello\n"</span>);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>break</code>语句是无效的,因为它不能跳出外层的<code>if</code>结构。</p><h2 id="continue-语句"><a href="#continue-语句" class="headerlink" title="continue 语句"></a>continue 语句</h2><p><code>continue</code>语句用于在循环体内部终止本轮循环,进入下一轮循环。只要遇到<code>continue</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-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">3</span>; i++) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j < <span class="hljs-number">3</span>; j++) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d, %d\n"</span>, i, j);<br> <span class="hljs-keyword">continue</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,有没有<code>continue</code>语句,效果一样,都表示跳到下一个<code>j</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> ((ch = getchar()) != <span class="hljs-string">'\n'</span>) {<br> <span class="hljs-keyword">if</span> (ch == <span class="hljs-string">'\t'</span>) <span class="hljs-keyword">continue</span>;<br> <span class="hljs-built_in">putchar</span>(ch);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,只要读到的字符是制表符(<code>\t</code>),就用<code>continue</code>语句跳过该字符,读取下一个字符。</p><h2 id="goto-语句"><a href="#goto-语句" class="headerlink" title="goto 语句"></a>goto 语句</h2><p>goto 语句用于跳到指定的标签名。这会破坏结构化编程,建议不要轻易使用,这里为了语法的完整,介绍一下它的用法。</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">char</span> ch;<br><br>top: ch = getchar();<br><br><span class="hljs-keyword">if</span> (ch == <span class="hljs-string">'q'</span>)<br> <span class="hljs-keyword">goto</span> top;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>top</code>是一个标签名,可以放在正常语句的前面,相当于为这行语句做了一个标记。程序执行到<code>goto</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">infinite_loop:<br> print(<span class="hljs-string">"Hello, world!\n"</span>);<br> <span class="hljs-keyword">goto</span> infinite_loop;<br></code></pre></td></tr></table></figure><p>上面的代码会产生无限循环。</p><p>goto 的一个主要用法是跳出多层循环。</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-keyword">for</span>(...) {<br> <span class="hljs-keyword">for</span> (...) {<br> <span class="hljs-keyword">while</span> (...) {<br> <span class="hljs-keyword">do</span> {<br> <span class="hljs-keyword">if</span> (some_error_condition)<br> <span class="hljs-keyword">goto</span> bail; <br> } <span class="hljs-keyword">while</span>(...);<br> }<br> }<br>}<br> <br>bail:<br><span class="hljs-comment">// ... ...</span><br></code></pre></td></tr></table></figure><p>上面代码有很复杂的嵌套循环,不使用 goto 的话,想要完全跳出所有循环,写起来很麻烦。</p><p>goto 的另一个用途是提早结束多重判断。</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">if</span> (do_something() == ERR)<br> <span class="hljs-keyword">goto</span> error;<br><span class="hljs-keyword">if</span> (do_something2() == ERR)<br> <span class="hljs-keyword">goto</span> error;<br><span class="hljs-keyword">if</span> (do_something3() == ERR)<br> <span class="hljs-keyword">goto</span> error;<br><span class="hljs-keyword">if</span> (do_something4() == ERR)<br> <span class="hljs-keyword">goto</span> error;<br></code></pre></td></tr></table></figure><p>上面示例有四个判断,只要有一个发现错误,就使用 goto 跳过后面的判断。</p><p>注意,goto 只能在同一个函数之中跳转,并不能跳转到其他函数。</p><h1 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h1><p>C 语言的每一种数据,都是有类型(type)的,编译器必须知道数据的类型,才能操作数据。所谓“类型”,就是相似的数据所拥有的共同特征,那么一旦知道某个值的数据类型,就能知道该值的特征和操作方式。</p><p>基本数据类型有三种:字符(char)、整数(int)和浮点数(float)。复杂的类型都是基于它们构建的。</p><h2 id="字符类型"><a href="#字符类型" class="headerlink" title="字符类型"></a>字符类型</h2><p>字符类型指的是单个字符,类型声明使用<code>char</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-type">char</span> c = <span class="hljs-string">'B'</span>;<br></code></pre></td></tr></table></figure><p>上面示例声明了变量<code>c</code>是字符类型,并将其赋值为字母<code>B</code>。</p><p>C 语言规定,字符常量必须放在单引号里面。</p><p>在计算机内部,字符类型使用一个字节(8位)存储。C 语言将其当作整数处理,所以字符类型就是宽度为一个字节的整数。每个字符对应一个整数(由 ASCII 码确定),比如<code>B</code>对应整数<code>66</code>。</p><p>字符类型在不同计算机的默认范围是不一样的。一些系统默认为<code>-128</code>到<code>127</code>,另一些系统默认为<code>0</code>到<code>255</code>。这两种范围正好都能覆盖<code>0</code>到<code>127</code>的 ASCII 字符范围。</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">char</span> c = <span class="hljs-number">66</span>;<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">char</span> c = <span class="hljs-string">'B'</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>c</code>是字符类型,赋给它的值是整数66。这跟赋值为字符<code>B</code>的效果是一样的。</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">char</span> a = <span class="hljs-string">'B'</span>; <span class="hljs-comment">// 等同于 char a = 66;</span><br><span class="hljs-type">char</span> b = <span class="hljs-string">'C'</span>; <span class="hljs-comment">// 等同于 char b = 67;</span><br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, a + b); <span class="hljs-comment">// 输出 133</span><br></code></pre></td></tr></table></figure><p>上面示例中,字符类型变量<code>a</code>和<code>b</code>相加,视同两个整数相加。占位符<code>%d</code>表示输出十进制整数,因此输出结果为133。</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-type">char</span> t = <span class="hljs-string">'\''</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>t</code>为单引号字符,由于字符常量必须放在单引号里面,所以内部的单引号要使用反斜杠转义。</p><p>这种转义的写法,主要用来表示 ASCII 码定义的一些无法打印的控制字符,它们也属于字符类型的值。</p><ul><li><code>\a</code>:警报,这会使得终端发出警报声或出现闪烁,或者两者同时发生。</li><li><code>\b</code>:退格键,光标回退一个字符,但不删除字符。</li><li><code>\f</code>:换页符,光标移到下一页。在现代系统上,这已经反映不出来了,行为改成类似于<code>\v</code>。</li><li><code>\n</code>:换行符。</li><li><code>\r</code>:回车符,光标移到同一行的开头。</li><li><code>\t</code>:制表符,光标移到下一个水平制表位,通常是下一个8的倍数。</li><li><code>\v</code>:垂直分隔符,光标移到下一个垂直制表位,通常是下一行的同一列。</li><li><code>\0</code>:null 字符,代表没有内容。注意,这个值不等于数字0。</li></ul><p>转义写法还能使用八进制和十六进制表示一个字符。</p><ul><li><code>\nn</code>:字符的八进制写法,<code>nn</code>为八进制值。</li><li><code>\xnn</code>:字符的十六进制写法,<code>nn</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><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> x = <span class="hljs-string">'B'</span>;<br><span class="hljs-type">char</span> x = <span class="hljs-number">66</span>;<br><span class="hljs-type">char</span> x = <span class="hljs-string">'\102'</span>; <span class="hljs-comment">// 八进制</span><br><span class="hljs-type">char</span> x = <span class="hljs-string">'\x42'</span>; <span class="hljs-comment">// 十六进制</span><br></code></pre></td></tr></table></figure><p>上面示例的四种写法都是等价的。</p><h2 id="整数类型"><a href="#整数类型" class="headerlink" title="整数类型"></a>整数类型</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>整数类型用来表示较大的整数,类型声明使用<code>int</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-type">int</span> a;<br></code></pre></td></tr></table></figure><p>上面示例声明了一个整数变量<code>a</code>。</p><p>不同计算机的<code>int</code>类型的大小是不一样的。比较常见的是使用4个字节(32位)存储一个<code>int</code>类型的值,但是2个字节(16位)或8个字节(64位)也有可能使用。它们可以表示的整数范围如下。</p><ul><li>16位:-32,768 到 32,767。</li><li>32位:-2,147,483,648 到 2,147,483,647。</li><li>64位:-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。</li></ul><h3 id="signed,unsigned"><a href="#signed,unsigned" class="headerlink" title="signed,unsigned"></a>signed,unsigned</h3><p>C 语言使用<code>signed</code>关键字,表示一个类型带有正负号,包含负值;使用<code>unsigned</code>关键字,表示该类型不带有正负号,只能表示零和正整数。</p><p>对于<code>int</code>类型,默认是带有正负号的,也就是说<code>int</code>等同于<code>signed int</code>。由于这是默认情况,关键字<code>signed</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">signed</span> <span class="hljs-type">int</span> a;<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span> a;<br></code></pre></td></tr></table></figure><p><code>int</code>类型也可以不带正负号,只表示非负整数。这时就必须使用关键字<code>unsigned</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-type">unsigned</span> <span class="hljs-type">int</span> a;<br></code></pre></td></tr></table></figure><p>整数变量声明为<code>unsigned</code>的好处是,同样长度的内存能够表示的最大整数值,增大了一倍。比如,16位的<code>signed int</code>最大值为32,767,而<code>unsigned int</code>的最大值增大到了65,535。</p><p><code>unsigned int</code>里面的<code>int</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-type">unsigned</span> a;<br></code></pre></td></tr></table></figure><p>字符类型<code>char</code>也可以设置<code>signed</code>和<code>unsigned</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">signed</span> <span class="hljs-type">char</span> c; <span class="hljs-comment">// 范围为 -128 到 127</span><br><span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> c; <span class="hljs-comment">// 范围为 0 到 255</span><br></code></pre></td></tr></table></figure><p>注意,C 语言规定<code>char</code>类型默认是否带有正负号,由当前系统决定。这就是说,<code>char</code>不等同于<code>signed char</code>,它有可能是<code>signed char</code>,也有可能是<code>unsigned char</code>。这一点与<code>int</code>不同,<code>int</code>就是等同于<code>signed int</code>。</p><h3 id="整数的子类型"><a href="#整数的子类型" class="headerlink" title="整数的子类型"></a>整数的子类型</h3><p>如果<code>int</code>类型使用4个或8个字节表示一个整数,对于小整数,这样做很浪费空间。另一方面,某些场合需要更大的整数,8个字节还不够。为了解决这些问题,C 语言在<code>int</code>类型之外,又提供了三个整数的子类型。这样有利于更精细地限定整数变量的范围,也有利于更好地表达代码的意图。</p><ul><li><code>short int</code>(简写为<code>short</code>):占用空间不多于<code>int</code>,一般占用2个字节(整数范围为-32768~32767)。</li><li><code>long int</code>(简写为<code>long</code>):占用空间不少于<code>int</code>,至少为4个字节。</li><li><code>long long int</code>(简写为<code>long long</code>):占用空间多于<code>long</code>,至少为8个字节。</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-type">short</span> <span class="hljs-type">int</span> a;<br><span class="hljs-type">long</span> <span class="hljs-type">int</span> b;<br><span class="hljs-type">long</span> <span class="hljs-type">long</span> <span class="hljs-type">int</span> c;<br></code></pre></td></tr></table></figure><p>上面代码分别声明了三种整数子类型的变量。</p><p>默认情况下,<code>short</code>、<code>long</code>、<code>long long</code>都是带符号的(signed),即<code>signed</code>关键字省略了。它们也可以声明为不带符号(unsigned),使得能够表示的最大值扩大一倍。</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">unsigned</span> <span class="hljs-type">short</span> <span class="hljs-type">int</span> a;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">int</span> b;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">long</span> <span class="hljs-type">int</span> c;<br></code></pre></td></tr></table></figure><p>C 语言允许省略<code>int</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-type">short</span> a;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">short</span> a;<br><br><span class="hljs-type">long</span> b;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> b;<br><br><span class="hljs-type">long</span> <span class="hljs-type">long</span> c;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">long</span> c;<br></code></pre></td></tr></table></figure><p>不同的计算机,数据类型的字节长度是不一样的。确实需要32位整数时,应使用<code>long</code>类型而不是<code>int</code>类型,可以确保不少于4个字节;确实需要64位的整数时,应该使用<code>long long</code>类型,可以确保不少于8个字节。另一方面,为了节省空间,只需要16位整数时,应使用<code>short</code>类型;需要8位整数时,应该使用<code>char</code>类型。</p><h3 id="整数类型的极限值"><a href="#整数类型的极限值" class="headerlink" title="整数类型的极限值"></a>整数类型的极限值</h3><p>有时候需要查看,当前系统不同整数类型的最大值和最小值,C 语言的头文件<code>limits.h</code>提供了相应的常量,比如<code>SCHAR_MIN</code>代表 signed char 类型的最小值<code>-128</code>,<code>SCHAR_MAX</code>代表 signed char 类型的最大值<code>127</code>。</p><p>为了代码的可移植性,需要知道某种整数类型的极限值时,应该尽量使用这些常量。</p><ul><li><code>SCHAR_MIN</code>,<code>SCHAR_MAX</code>:signed char 的最小值和最大值。</li><li><code>SHRT_MIN</code>,<code>SHRT_MAX</code>:short 的最小值和最大值。</li><li><code>INT_MIN</code>,<code>INT_MAX</code>:int 的最小值和最大值。</li><li><code>LONG_MIN</code>,<code>LONG_MAX</code>:long 的最小值和最大值。</li><li><code>LLONG_MIN</code>,<code>LLONG_MAX</code>:long long 的最小值和最大值。</li><li><code>UCHAR_MAX</code>:unsigned char 的最大值。</li><li><code>USHRT_MAX</code>:unsigned short 的最大值。</li><li><code>UINT_MAX</code>:unsigned int 的最大值。</li><li><code>ULONG_MAX</code>:unsigned long 的最大值。</li><li><code>ULLONG_MAX</code>:unsigned long long 的最大值。</li></ul><h3 id="整数的进制"><a href="#整数的进制" class="headerlink" title="整数的进制"></a>整数的进制</h3><p>C 语言的整数默认都是十进制数,如果要表示八进制数和十六进制数,必须使用专门的表示法。</p><p>八进制使用<code>0</code>作为前缀,比如<code>017</code>、<code>0377</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-type">int</span> a = <span class="hljs-number">012</span>; <span class="hljs-comment">// 八进制,相当于十进制的10</span><br></code></pre></td></tr></table></figure><p>十六进制使用<code>0x</code>或<code>0X</code>作为前缀,比如<code>0xf</code>、<code>0X10</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-type">int</span> a = <span class="hljs-number">0x1A2B</span>; <span class="hljs-comment">// 十六进制,相当于十进制的6699</span><br></code></pre></td></tr></table></figure><p>有些编译器使用<code>0b</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-type">int</span> x = <span class="hljs-number">0b101010</span>;<br></code></pre></td></tr></table></figure><p>注意,不同的进制只是整数的书写方法,不会对整数的实际存储方式产生影响。所有整数都是二进制形式存储,跟书写方式无关。不同进制可以混合使用,比如<code>10 + 015 + 0x20</code>是一个合法的表达式。</p><p><code>printf()</code>的进制相关占位符如下。</p><ul><li><code>%d</code>:十进制整数。</li><li><code>%o</code>:八进制整数。</li><li><code>%x</code>:十六进制整数。</li><li><code>%#o</code>:显示前缀<code>0</code>的八进制整数。</li><li><code>%#x</code>:显示前缀<code>0x</code>的十六进制整数。</li><li><code>%#X</code>:显示前缀<code>0X</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><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> x = <span class="hljs-number">100</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"dec = %d\n"</span>, x); <span class="hljs-comment">// 100</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"octal = %o\n"</span>, x); <span class="hljs-comment">// 144</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"hex = %x\n"</span>, x); <span class="hljs-comment">// 64</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"octal = %#o\n"</span>, x); <span class="hljs-comment">// 0144</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"hex = %#x\n"</span>, x); <span class="hljs-comment">// 0x64</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"hex = %#X\n"</span>, x); <span class="hljs-comment">// 0X64</span><br></code></pre></td></tr></table></figure><h2 id="浮点数类型"><a href="#浮点数类型" class="headerlink" title="浮点数类型"></a>浮点数类型</h2><p>任何有小数点的数值,都会被编译器解释为浮点数。所谓“浮点数”就是使用 m * b<sup>e</sup> 的形式,存储一个数值,<code>m</code>是小数部分,<code>b</code>是基数(通常是<code>2</code>),<code>e</code>是指数部分。这种形式是精度和数值范围的一种结合,可以表示非常大或者非常小的数。</p><p>浮点数的类型声明使用<code>float</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-type">float</span> c = <span class="hljs-number">10.5</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>c</code>的就是浮点数类型。</p><p><code>float</code>类型占用4个字节(32位),其中8位存放指数的值和符号,剩下24位存放小数的值和符号。<code>float</code>类型至少能够提供(十进制的)6位有效数字,指数部分的范围为(十进制的)<code>-37</code>到<code>37</code>,即数值范围为10<sup>-37</sup>到10<sup>37</sup>。</p><p>有时候,32位浮点数提供的精度或者数值范围还不够,C 语言又提供了另外两种更大的浮点数类型。</p><ul><li><code>double</code>:占用8个字节(64位),至少提供13位有效数字。</li><li><code>long double</code>:通常占用16个字节。</li></ul><p>注意,由于存在精度限制,浮点数只是一个近似值,它的计算是不精确的,比如 C 语言里面<code>0.1 + 0.2</code>并不等于<code>0.3</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">if</span> (<span class="hljs-number">0.1</span> + <span class="hljs-number">0.2</span> == <span class="hljs-number">0.3</span>) <span class="hljs-comment">// false</span><br></code></pre></td></tr></table></figure><p>C 语言允许使用科学计数法表示浮点数,使用字母<code>e</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">double</span> x = <span class="hljs-number">123.456e+3</span>; <span class="hljs-comment">// 123.456 x 10^3</span><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">double</span> x = <span class="hljs-number">123.456e3</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>e</code>后面如果是加号<code>+</code>,加号可以省略。注意,科学计数法里面<code>e</code>的前后,不能存在空格。</p><p>另外,科学计数法的小数部分如果是<code>0.x</code>或<code>x.0</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><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-number">0.3E6</span><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-number">.3E6</span><br><br><span class="hljs-number">3.0E6</span><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-number">3.E6</span><br></code></pre></td></tr></table></figure><h2 id="布尔类型"><a href="#布尔类型" class="headerlink" title="布尔类型"></a>布尔类型</h2><p>C 语言原来并没有为布尔值单独设置一个类型,而是使用整数<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><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">if</span> (x) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"x is true!\n"</span>);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>x</code>等于<code>1</code>,C 语言就认为这个值代表真,从而会执行判断体内部的代码。</p><p>C99 标准添加了类型<code>_Bool</code>,表示布尔值。但是,这个类型其实只是整数类型的别名,还是使用<code>0</code>表示伪,<code>1</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-type">_Bool</span> isNormal;<br><br>isNormal = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">if</span> (isNormal)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Everything is OK.\n"</span>);<br></code></pre></td></tr></table></figure><p>头文件<code>stdbool.h</code>定义了另一个类型别名<code>bool</code>,并且定义了<code>true</code>代表<code>1</code>、<code>false</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdbool.h></span></span><br><br><span class="hljs-type">bool</span> flag = <span class="hljs-literal">false</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,加载头文件<code>stdbool.h</code>以后,就可以使用<code>bool</code>定义布尔值类型,以及<code>false</code>和<code>true</code>表示真伪。</p><h2 id="字面量的类型"><a href="#字面量的类型" class="headerlink" title="字面量的类型"></a>字面量的类型</h2><p>字面量(literal)指的是代码里面直接出现的值。</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> x = <span class="hljs-number">123</span>;<br></code></pre></td></tr></table></figure><p>上面代码中,<code>x</code>是变量,<code>123</code>就是字面量。</p><p>编译时,字面量也会写入内存,因此编译器必须为字面量指定数据类型,就像必须为变量指定数据类型一样。</p><p>一般情况下,十进制整数字面量(比如<code>123</code>)会被编译器指定为<code>int</code>类型。如果一个数值比较大,超出了<code>int</code>能够表示的范围,编译器会将其指定为<code>long int</code>。如果数值超过了<code>long int</code>,会被指定为<code>unsigned long</code>。如果还不够大,就指定为<code>long long</code>或<code>unsigned long long</code>。</p><p>小数(比如<code>3.14</code>)会被指定为<code>double</code>类型。</p><h2 id="字面量后缀"><a href="#字面量后缀" class="headerlink" title="字面量后缀"></a>字面量后缀</h2><p>有时候,程序员希望为字面量指定一个不同的类型。比如,编译器将一个整数字面量指定为<code>int</code>类型,但是程序员希望将其指定为<code>long</code>类型,这时可以为该字面量加上后缀<code>l</code>或<code>L</code>,编译器就知道要把这个字面量的类型指定为<code>long</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-type">int</span> x = <span class="hljs-number">123L</span>;<br></code></pre></td></tr></table></figure><p>上面代码中,字面量<code>123</code>有后缀<code>L</code>,编译器就会将其指定为<code>long</code>类型。这里<code>123L</code>写成<code>123l</code>,效果也是一样的,但是建议优先使用<code>L</code>,因为小写的<code>l</code>容易跟数字<code>1</code>混淆。</p><p>八进制和十六进制的值,也可以使用后缀<code>l</code>和<code>L</code>指定为 Long 类型,比如<code>020L</code>和<code>0x20L</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> y = <span class="hljs-number">0377L</span>;<br><span class="hljs-type">int</span> z = <span class="hljs-number">0x7fff</span>L;<br></code></pre></td></tr></table></figure><p>如果希望指定为无符号整数<code>unsigned int</code>,可以使用后缀<code>u</code>或<code>U</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-type">int</span> x = <span class="hljs-number">123U</span>;<br></code></pre></td></tr></table></figure><p><code>L</code>和<code>U</code>可以结合使用,表示<code>unsigned long</code>类型。<code>L</code>和<code>U</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-type">int</span> x = <span class="hljs-number">123LU</span>;<br></code></pre></td></tr></table></figure><p>对于浮点数,编译器默认指定为 double 类型,如果希望指定为其他类型,需要在小数后面添加后缀<code>f</code>(float)或<code>l</code>(long double)。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-number">1.2345e+10</span>F<br><span class="hljs-number">1.2345e+10</span>L<br></code></pre></td></tr></table></figure><p>总结一下,常用的字面量后缀有下面这些。</p><ul><li><code>f</code>和<code>F</code>:<code>float</code>类型。</li><li><code>l</code>和<code>L</code>:对于整数是<code>long int</code>类型,对于小数是<code>long double</code>类型。</li><li><code>ll</code>和<code>LL</code>:Long Long 类型,比如<code>3LL</code>。</li><li><code>u</code>和<code>U</code>:表示<code>unsigned int</code>,比如<code>15U</code>、<code>0377U</code>。</li></ul><p><code>u</code>还可以与其他整数后缀结合,放在前面或后面都可以,比如<code>10UL</code>、<code>10ULL</code>和<code>10LLU</code>都是合法的。</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><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x = <span class="hljs-number">1234</span>;<br><span class="hljs-type">long</span> <span class="hljs-type">int</span> x = <span class="hljs-number">1234L</span>;<br><span class="hljs-type">long</span> <span class="hljs-type">long</span> <span class="hljs-type">int</span> x = <span class="hljs-number">1234LL</span><br><br><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> x = <span class="hljs-number">1234U</span>;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">int</span> x = <span class="hljs-number">1234UL</span>;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">long</span> <span class="hljs-type">int</span> x = <span class="hljs-number">1234ULL</span>;<br><br><span class="hljs-type">float</span> x = <span class="hljs-number">3.14f</span>;<br><span class="hljs-type">double</span> x = <span class="hljs-number">3.14</span>;<br><span class="hljs-type">long</span> <span class="hljs-type">double</span> x = <span class="hljs-number">3.14L</span>;<br></code></pre></td></tr></table></figure><h2 id="溢出"><a href="#溢出" class="headerlink" title="溢出"></a>溢出</h2><p>每一种数据类型都有数值范围,如果存放的数值超出了这个范围(小于最小值或大于最大值),需要更多的二进制位存储,就会发生溢出。大于最大值,叫做向上溢出(overflow);小于最小值,叫做向下溢出(underflow)。</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">unsigned</span> <span class="hljs-type">char</span> x = <span class="hljs-number">255</span>;<br>x = x + <span class="hljs-number">1</span>;<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, x); <span class="hljs-comment">// 0</span><br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>x</code>加<code>1</code>,得到的结果不是<code>256</code>,而是<code>0</code>。因为<code>x</code>是<code>unsign char</code>类型,最大值是<code>255</code>(二进制<code>11111111</code>),加<code>1</code>后就发生了溢出,<code>256</code>(二进制<code>100000000</code>)的最高位<code>1</code>被丢弃,剩下的值就是<code>0</code>。</p><p>再看下面的例子。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> ui = UINT_MAX; <span class="hljs-comment">// 4,294,967,295</span><br>ui++;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"ui = %u\n"</span>, ui); <span class="hljs-comment">// 0</span><br>ui--;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"ui = %u\n"</span>, ui); <span class="hljs-comment">// 4,294,967,295</span><br></code></pre></td></tr></table></figure><p>上面示例中,常量<code>UINT_MAX</code>是 unsigned int 类型的最大值。如果加<code>1</code>,对于该类型就会溢出,从而得到<code>0</code>;而<code>0</code>是该类型的最小值,再减<code>1</code>,又会得到<code>UINT_MAX</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-keyword">for</span> (<span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> i = n; i >= <span class="hljs-number">0</span>; --i) <span class="hljs-comment">// 错误</span><br></code></pre></td></tr></table></figure><p>上面代码表面看似乎没有问题,但是循环变量<code>i</code>的类型是 unsigned int,这个类型的最小值是<code>0</code>,不可能得到小于0的结果。当<code>i</code>等于0,再减去<code>1</code>的时候,并不会返回<code>-1</code>,而是返回 unsigned int 的类型最大值,这个值总是大于等于<code>0</code>,导致无限循环。</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-type">unsigned</span> <span class="hljs-type">int</span> ui;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> sum;<br><br><span class="hljs-comment">// 错误</span><br><span class="hljs-keyword">if</span> (sum + ui > UINT_MAX) too_big();<br><span class="hljs-keyword">else</span> sum = sum + ui;<br><br><span class="hljs-comment">// 正确</span><br><span class="hljs-keyword">if</span> (ui > UINT_MAX - sum) too_big();<br><span class="hljs-keyword">else</span> sum = sum + ui;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>sum</code>和<code>ui</code>都是 unsigned int 类型,它们相加的和还是 unsigned int 类型,这就有可能发生溢出。但是,不能通过相加的和是否超出了最大值<code>UINT_MAX</code>,来判断是否发生了溢出,因为<code>sum + ui</code>总是返回溢出后的结果,不可能大于<code>UINT_MAX</code>。正确的比较方法是,判断<code>UINT_MAX - sum</code>与<code>ui</code>之间的大小关系。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> i = <span class="hljs-number">5</span>;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> j = <span class="hljs-number">7</span>;<br><br><span class="hljs-keyword">if</span> (i - j < <span class="hljs-number">0</span>) <span class="hljs-comment">// 错误</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"negative\n"</span>);<br><span class="hljs-keyword">else</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"positive\n"</span>);<br></code></pre></td></tr></table></figure><p>上面示例的运算结果,会输出<code>positive</code>。原因是变量<code>i</code>和<code>j</code>都是 unsigned int 类型,<code>i - j</code>的结果也是这个类型,最小值为<code>0</code>,不可能得到小于<code>0</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">if</span> (j > i) <span class="hljs-comment">// ....</span><br></code></pre></td></tr></table></figure><h2 id="sizeof-运算符"><a href="#sizeof-运算符" class="headerlink" title="sizeof 运算符"></a>sizeof 运算符</h2><p><code>sizeof</code>是 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><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">// 参数为数据类型</span><br><span class="hljs-type">int</span> x = <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>);<br><br><span class="hljs-comment">// 参数为变量</span><br><span class="hljs-type">int</span> i;<br><span class="hljs-keyword">sizeof</span>(i);<br><br><span class="hljs-comment">// 参数为数值</span><br><span class="hljs-keyword">sizeof</span>(<span class="hljs-number">3.14</span>);<br></code></pre></td></tr></table></figure><p>上面的第一个示例,返回得到<code>int</code>类型占用的字节数量(通常是<code>4</code>或<code>8</code>)。第二个示例返回整数变量占用字节数量,结果与前一个示例完全一样。第三个示例返回浮点数<code>3.14</code>占用的字节数量,由于浮点数的字面量一律存储为 double 类型,所以会返回<code>8</code>,因为 double 类型占用的8个字节。</p><p><code>sizeof</code>运算符的返回值,C 语言只规定是无符号整数,并没有规定具体的类型,而是留给系统自己去决定,<code>sizeof</code>到底返回什么类型。不同的系统中,返回值的类型有可能是<code>unsigned int</code>,也有可能是<code>unsigned long</code>,甚至是<code>unsigned long long</code>,对应的<code>printf()</code>占位符分别是<code>%u</code>、<code>%lu</code>和<code>%llu</code>。这样不利于程序的可移植性。</p><p>C 语言提供了一个解决方法,创造了一个类型别名<code>size_t</code>,用来统一表示<code>sizeof</code>的返回值类型。该别名定义在<code>stddef.h</code>头文件(引入<code>stdio.h</code>时会自动引入)里面,对应当前系统的<code>sizeof</code>的返回值类型,可能是<code>unsigned int</code>,也可能是<code>unsigned long</code>。</p><p>C 语言还提供了一个常量<code>SIZE_MAX</code>,表示<code>size_t</code>可以表示的最大整数。所以,<code>size_t</code>能够表示的整数范围为<code>[0, SIZE_MAX]</code>。</p><p><code>printf()</code>有专门的占位符<code>%zd</code>或<code>%zu</code>,用来处理<code>size_t</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">printf</span>(<span class="hljs-string">"%zd\n"</span>, <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br></code></pre></td></tr></table></figure><p>上面代码中,不管<code>sizeof</code>返回值的类型是什么,<code>%zd</code>占位符(或<code>%zu</code>)都可以正确输出。</p><p>如果当前系统不支持<code>%zd</code>或<code>%zu</code>,可使用<code>%u</code>(unsigned int)或<code>%lu</code>(unsigned long int)代替。</p><h2 id="类型的自动转换"><a href="#类型的自动转换" class="headerlink" title="类型的自动转换"></a>类型的自动转换</h2><p>某些情况下,C 语言会自动转换某个值的类型。</p><h3 id="赋值运算"><a href="#赋值运算" class="headerlink" title="赋值运算"></a>赋值运算</h3><p>赋值运算符会自动将右边的值,转成左边变量的类型。</p><p>(1)浮点数赋值给整数变量</p><p>浮点数赋予整数变量时,C 语言直接丢弃小数部分,而不是四舍五入。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x = <span class="hljs-number">3.14</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>x</code>是整数类型,赋给它的值是一个浮点数。编译器会自动把<code>3.14</code>先转为<code>int</code>类型,丢弃小数部分,再赋值给<code>x</code>,因此<code>x</code>的值是<code>3</code>。</p><p>这种自动转换会导致部分数据的丢失(<code>3.14</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-type">int</span> x = <span class="hljs-number">12.99</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>x</code>等于<code>12</code>,而不是四舍五入的<code>13</code>。</p><p>(2)整数赋值给浮点数变量</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-type">float</span> y = <span class="hljs-number">12</span> * <span class="hljs-number">2</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>y</code>的值不是<code>24</code>,而是<code>24.0</code>,因为等号右边的整数自动转为了浮点数。</p><p>(3)窄类型赋值给宽类型</p><p>字节宽度较小的整数类型,赋值给字节宽度较大的整数变量时,会发生类型提升,即窄类型自动转为宽类型。</p><p>比如,<code>char</code>或<code>short</code>类型赋值给<code>int</code>类型,会自动提升为<code>int</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">char</span> x = <span class="hljs-number">10</span>;<br><span class="hljs-type">int</span> i = x + y;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>x</code>的类型是<code>char</code>,由于赋值给<code>int</code>类型,所以会自动提升为<code>int</code>。</p><p>(4)宽类型赋值给窄类型</p><p>字节宽度较大的类型,赋值给字节宽度较小的变量时,会发生类型降级,自动转为后者的类型。这时可能会发生截值(truncation),系统会自动截去多余的二进制位,导致难以预料的结果。</p><figure class="highlight javascript"><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 javascript">int i = <span class="hljs-number">321</span>;<br>char ch = i; <span class="hljs-comment">// ch 的值是 65 (321 - 256)</span><br></code></pre></td></tr></table></figure><p>上面例子中,变量<code>ch</code>是<code>char</code>类型,宽度是8个二进制位。变量<code>i</code>是<code>int</code>类型,将<code>i</code>赋值给<code>ch</code>,后者只能容纳<code>i</code>(二进制形式为<code>101000001</code>,共9位)的后八位,前面多出来的二进制位被丢弃,保留后八位就变成了<code>01000001</code>(十进制的65,相当于字符<code>A</code>)。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">double</span> pi = <span class="hljs-number">3.14159</span>;<br><span class="hljs-type">int</span> i = pi; <span class="hljs-comment">// i 的值为 3</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>i</code>等于<code>3</code>,<code>pi</code>的小数部分被截去了。</p><h3 id="混合类型的运算"><a href="#混合类型的运算" class="headerlink" title="混合类型的运算"></a>混合类型的运算</h3><p>不同类型的值进行混合计算时,必须先转成同一个类型,才能进行计算。转换规则如下:</p><p>(1)整数与浮点数混合运算时,整数转为浮点数类型,与另一个运算数类型相同。</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-number">3</span> + <span class="hljs-number">1.2</span> <span class="hljs-comment">// 4.2</span><br></code></pre></td></tr></table></figure><p>上面示例是<code>int</code>类型与<code>float</code>类型的混合计算,<code>int</code>类型的<code>3</code>会先转成<code>float</code>的<code>3.0</code>,再进行计算,得到<code>4.2</code>。</p><p>(2)不同的浮点数类型混合运算时,宽度较小的类型转为宽度较大的类型,比如<code>float</code>转为<code>double</code>,<code>double</code>转为<code>long double</code>。</p><p>(3)不同的整数类型混合运算时,宽度较小的类型会提升为宽度较大的类型。比如<code>short</code>转为<code>int</code>,<code>int</code>转为<code>long</code>等,有时还会将带符号的类型<code>signed</code>转为无符号<code>unsigned</code>。</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> a = <span class="hljs-number">-5</span>;<br><span class="hljs-keyword">if</span> (a < <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>))<br> do_something();<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>a</code>是带符号整数,<code>sizeof(int)</code>是<code>size_t</code>类型,这是一个无符号整数。按照规则,signed int 自动转为 unsigned int,所以<code>a</code>会自动转成无符号整数<code>4294967291</code>(转换规则是<code>-5</code>加上无符号整数的最大值,再加1),导致比较失败,<code>do_something()</code>不会执行。</p><p>所以,最好避免无符号整数与有符号整数的混合运算。因为这时 C 语言会自动将<code>signed int</code>转为<code>unsigned int</code>,可能不会得到预期的结果。</p><h3 id="整数类型的运算"><a href="#整数类型的运算" class="headerlink" title="整数类型的运算"></a>整数类型的运算</h3><p>两个相同类型的整数运算时,或者单个整数的运算,一般来说,运算结果也属于同一类型。但是有一个例外,宽度小于<code>int</code>的类型,运算结果会自动提升为<code>int</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">unsigned</span> <span class="hljs-type">char</span> a = <span class="hljs-number">66</span>;<br><br><span class="hljs-keyword">if</span> ((-a) < <span class="hljs-number">0</span>) <span class="hljs-built_in">printf</span>(<span class="hljs-string">"negative\n"</span>);<br><span class="hljs-keyword">else</span> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"positive\n"</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>a</code>是 unsigned char 类型,这个类型不可能小于0,但是<code>-a</code>不是 unsigned char 类型,会自动转为 int 类型,导致上面的代码输出 negative。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> b = <span class="hljs-number">255</span>;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> c = <span class="hljs-number">255</span>;<br><br><span class="hljs-keyword">if</span> ((a - <span class="hljs-number">5</span>) < <span class="hljs-number">0</span>) do_something();<br><span class="hljs-keyword">if</span> ((b + c) > <span class="hljs-number">300</span>) do_something();<br></code></pre></td></tr></table></figure><p>上面示例中,表达式<code>a - 5</code>和<code>b + c</code>都会自动转为 int 类型,所以函数<code>do_something()</code>会执行两次。</p><h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</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-type">int</span> <span class="hljs-title function_">dostuff</span><span class="hljs-params">(<span class="hljs-type">int</span>, <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span>)</span>;<br><br><span class="hljs-type">char</span> m = <span class="hljs-number">42</span>;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">short</span> n = <span class="hljs-number">43</span>;<br><span class="hljs-type">long</span> <span class="hljs-type">long</span> <span class="hljs-type">int</span> c = dostuff(m, n);<br></code></pre></td></tr></table></figure><p>上面示例中,参数变量<code>m</code>和<code>n</code>不管原来的类型是什么,都会转成函数<code>dostuff()</code>定义的参数类型。</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">char</span> <span class="hljs-title function_">func</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">int</span> a = <span class="hljs-number">42</span>;<br> <span class="hljs-keyword">return</span> a;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数内部的变量<code>a</code>是<code>int</code>类型,但是返回的值是<code>char</code>类型,因为函数定义中返回的是这个类型。</p><h2 id="类型的显式转换"><a href="#类型的显式转换" class="headerlink" title="类型的显式转换"></a>类型的显式转换</h2><p>原则上,应该避免类型的自动转换,防止出现意料之外的结果。C 语言提供了类型的显式转换,允许手动转换类型。</p><p>只要在一个值或变量的前面,使用圆括号指定类型<code>(type)</code>,就可以将这个值或变量转为指定的类型,这叫做“类型指定”(casting)。</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">unsigned</span> <span class="hljs-type">char</span>) ch<br></code></pre></td></tr></table></figure><p>上面示例将变量<code>ch</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-type">long</span> <span class="hljs-type">int</span> y = (<span class="hljs-type">long</span> <span class="hljs-type">int</span>) <span class="hljs-number">10</span> + <span class="hljs-number">12</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>(long int)</code>将<code>10</code>显式转为<code>long int</code>类型。这里的显示转换其实是不必要的,因为赋值运算符会自动将右边的值,转为左边变量的类型。</p><h2 id="可移植类型"><a href="#可移植类型" class="headerlink" title="可移植类型"></a>可移植类型</h2><p>C 语言的整数类型(short、int、long)在不同计算机上,占用的字节宽度可能是不一样的,无法提前知道它们到底占用多少个字节。</p><p>程序员有时控制准确的字节宽度,这样的话,代码可以有更好的可移植性,头文件<code>stdint.h</code>创造了一些新的类型别名。</p><p>(1)精确宽度类型(exact-width integer type),保证某个整数类型的宽度是确定的。</p><ul><li><code>int8_t</code>:8位有符号整数。</li><li><code>int16_t</code>:16位有符号整数。</li><li><code>int32_t</code>:32位有符号整数。</li><li><code>int64_t</code>:64位有符号整数。</li><li><code>uint8_t</code>:8位无符号整数。</li><li><code>uint16_t</code>:16位无符号整数。</li><li><code>uint32_t</code>:32位无符号整数。</li><li><code>uint64_t</code>:64位无符号整数。</li></ul><p>上面这些都是类型别名,编译器会指定它们指向的底层类型。比如,某个系统中,如果<code>int</code>类型为32位,<code>int32_t</code>就会指向<code>int</code>;如果<code>long</code>类型为32位,<code>int32_t</code>则会指向<code>long</code>。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdint.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">int32_t</span> x32 = <span class="hljs-number">45933945</span>;<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"x32 = %d\n"</span>, x32);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>x32</code>声明为<code>int32_t</code>类型,可以保证是32位的宽度。</p><p>(2)最小宽度类型(minimum width type),保证某个整数类型的最小长度。</p><ul><li>int_least8_t</li><li>int_least16_t</li><li>int_least32_t</li><li>int_least64_t</li><li>uint_least8_t</li><li>uint_least16_t</li><li>uint_least32_t</li><li>uint_least64_t</li></ul><p>上面这些类型,可以保证占据的字节不少于指定宽度。比如,<code>int_least8_t</code>表示可以容纳8位有符号整数的最小宽度的类型。</p><p>(3)最快的最小宽度类型(fast minimum width type),可以使整数计算达到最快的类型。</p><ul><li>int_fast8_t</li><li>int_fast16_t</li><li>int_fast32_t</li><li>int_fast64_t</li><li>uint_fast8_t</li><li>uint_fast16_t</li><li>uint_fast32_t</li><li>uint_fast64_t</li></ul><p>上面这些类型是保证字节宽度的同时,追求最快的运算速度,比如<code>int_fast8_t</code>表示对于8位有符号整数,运算速度最快的类型。这是因为某些机器对于特定宽度的数据,运算速度最快,举例来说,32位计算机对于32位数据的运算速度,会快于16位数据。</p><p>(4)可以保存指针的整数类型。</p><ul><li><code>intptr_t</code>:可以存储指针(内存地址)的有符号整数类型。</li><li><code>uintptr_t</code>:可以存储指针的无符号整数类型。</li></ul><p>(5)最大宽度整数类型,用于存放最大的整数。</p><ul><li><code>intmax_t</code>:可以存储任何有效的有符号整数的类型。</li><li><code>uintmax_t</code>:可以存放任何有效的无符号整数的类型。</li></ul><p>上面的这两个类型的宽度比<code>long long</code>和<code>unsigned long</code>更大。</p><h1 id="指针"><a href="#指针" class="headerlink" title="指针"></a>指针</h1><p>指针是 C 语言最重要的概念之一,也是最难理解的概念之一。</p><h2 id="简介-1"><a href="#简介-1" class="headerlink" title="简介"></a>简介</h2><p>指针是什么?首先,它是一个值,这个值代表一个内存地址,因此指针相当于指向某个内存地址的路标。</p><p>字符<code>*</code>表示指针,通常跟在类型关键字的后面,表示指针指向的是什么类型的值。比如,<code>char*</code>表示一个指向字符的指针,<code>float*</code>表示一个指向<code>float</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-type">int</span>* intPtr;<br></code></pre></td></tr></table></figure><p>上面示例声明了一个变量<code>intPtr</code>,它是一个指针,指向的内存地址存放的是一个整数。</p><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> *intPtr;<br><span class="hljs-type">int</span> * intPtr;<br><span class="hljs-type">int</span>* intPtr;<br></code></pre></td></tr></table></figure><p>本书使用星号紧跟在类型关键字后面的写法(即<code>int* intPtr;</code>),因为这样可以体现,指针变量就是一个普通变量,只不过它的值是内存地址而已。</p><p>这种写法有一个地方需要注意,如果同一行声明两个指针变量,那么需要写成下面这样。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 正确</span><br><span class="hljs-type">int</span> * foo, * bar;<br><br><span class="hljs-comment">// 错误</span><br><span class="hljs-type">int</span>* foo, bar;<br></code></pre></td></tr></table></figure><p>上面示例中,第二行的执行结果是,<code>foo</code>是整数指针变量,而<code>bar</code>是整数变量,即<code>*</code>只对第一个变量生效。</p><p>一个指针指向的可能还是指针,这时就要用两个星号<code>**</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-type">int</span>** foo;<br></code></pre></td></tr></table></figure><p>上面示例表示变量<code>foo</code>是一个指针,指向的还是一个指针,第二个指针指向的则是一个整数。</p><h2 id="运算符-1"><a href="#运算符-1" class="headerlink" title="* 运算符"></a>* 运算符</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">void</span> <span class="hljs-title function_">increment</span><span class="hljs-params">(<span class="hljs-type">int</span>* p)</span> {<br> *p = *p + <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>increment()</code>的参数是一个整数指针<code>p</code>。函数体里面,<code>*p</code>就表示指针<code>p</code>所指向的那个值。对<code>*p</code>赋值,就表示改变指针所指向的那个地址里面的值。</p><p>上面函数的作用是将参数值加<code>1</code>。该函数没有返回值,因为传入的是地址,函数体内部对该地址包含的值的操作,会影响到函数外部,所以不需要返回值。事实上,函数内部通过指针,将值传到外部,是 C 语言的常用方法。</p><p>变量地址而不是变量值传入函数,还有一个好处。对于需要大量存储空间的大型变量,复制变量值传入函数,非常浪费时间和空间,不如传入指针来得高效。</p><h2 id="运算符-2"><a href="#运算符-2" class="headerlink" title="& 运算符"></a>& 运算符</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> x = <span class="hljs-number">1</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"x's address is %p\n"</span>, &x);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>x</code>是一个整数变量,<code>&x</code>就是<code>x</code>的值所在的内存地址。<code>printf()</code>的<code>%p</code>是内存地址的占位符,可以打印出内存地址。</p><p>上一小节中,参数变量加<code>1</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">void</span> <span class="hljs-title function_">increment</span><span class="hljs-params">(<span class="hljs-type">int</span>* p)</span> {<br> *p = *p + <span class="hljs-number">1</span>;<br>}<br><br><span class="hljs-type">int</span> x = <span class="hljs-number">1</span>;<br>increment(&x);<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, x); <span class="hljs-comment">// 2</span><br></code></pre></td></tr></table></figure><p>上面示例中,调用<code>increment()</code>函数以后,变量<code>x</code>的值就增加了1,原因就在于传入函数的是变量<code>x</code>的地址<code>&x</code>。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> i = <span class="hljs-number">5</span>;<br><br><span class="hljs-keyword">if</span> (i == *(&i)) <span class="hljs-comment">// 正确</span><br></code></pre></td></tr></table></figure><h2 id="指针变量的初始化"><a href="#指针变量的初始化" class="headerlink" title="指针变量的初始化"></a>指针变量的初始化</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span>* p;<br>*p = <span class="hljs-number">1</span>; <span class="hljs-comment">// 错误</span><br></code></pre></td></tr></table></figure><p>上面的代码是错的,因为<code>p</code>指向的那个地址是随机的,向这个随机地址里面写入<code>1</code>,会导致意想不到的结果。</p><p>正确做法是指针变量声明后,必须先让它指向一个分配好的地址,然后再进行读写,这叫做指针变量的初始化。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span>* p;<br><span class="hljs-type">int</span> i;<br><br>p = &i;<br>*p = <span class="hljs-number">13</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>p</code>是指针变量,声明这个变量后,<code>p</code>会指向一个随机的内存地址。这时要将它指向一个已经分配好的内存地址,上例就是再声明一个整数变量<code>i</code>,编译器会为<code>i</code>分配内存地址,然后让<code>p</code>指向<code>i</code>的内存地址(<code>p = &i;</code>)。完成初始化之后,就可以对<code>p</code>指向的内存地址进行赋值了(<code>*p = 13;</code>)。</p><p>为了防止读写未初始化的指针变量,可以养成习惯,将未初始化的指针变量设为<code>NULL</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-type">int</span>* p = <span class="hljs-literal">NULL</span>;<br></code></pre></td></tr></table></figure><p><code>NULL</code>在 C 语言中是一个常量,表示地址为<code>0</code>的内存空间,这个地址是无法使用的,读写该地址会报错。</p><h2 id="指针的运算"><a href="#指针的运算" class="headerlink" title="指针的运算"></a>指针的运算</h2><p>指针本质上就是一个无符号整数,代表了内存地址。它可以进行运算,但是规则并不是整数运算的规则。</p><p>(1)指针与整数值的加减运算</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">short</span>* j;<br>j = (<span class="hljs-type">short</span>*)<span class="hljs-number">0x1234</span>;<br>j = j + <span class="hljs-number">1</span>; <span class="hljs-comment">// 0x1236</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>j</code>是一个指针,指向内存地址<code>0x1234</code>。你可能以为<code>j + 1</code>等于<code>0x1235</code>,但正确答案是<code>0x1236</code>。原因是<code>j + 1</code>表示指针向内存地址的高位移动一个单位,而一个单位的<code>short</code>类型占据两个字节的宽度,所以相当于向高位移动两个字节。同样的,<code>j - 1</code>得到的结果是<code>0x1232</code>。</p><p>指针移动的单位,与指针指向的数据类型有关。数据类型占据多少个字节,每单位就移动多少个字节。</p><p>(2)指针与指针的加法运算</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">unsigned</span> <span class="hljs-type">short</span>* j;<br><span class="hljs-type">unsigned</span> <span class="hljs-type">short</span>* k;<br>x = j + k; <span class="hljs-comment">// 非法</span><br></code></pre></td></tr></table></figure><p>上面示例是两个指针相加,这是非法的。</p><p>(3)指针与指针的减法</p><p>相同类型的指针允许进行减法运算,返回它们之间的距离,即相隔多少个数据单位。</p><p>高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。</p><p>这时,减法返回的值属于<code>ptrdiff_t</code>类型,这是一个带符号的整数类型别名,具体类型根据系统不同而不同。这个类型的原型定义在头文件<code>stddef.h</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-type">short</span>* j1;<br><span class="hljs-type">short</span>* j2;<br><br>j1 = (<span class="hljs-type">short</span>*)<span class="hljs-number">0x1234</span>;<br>j2 = (<span class="hljs-type">short</span>*)<span class="hljs-number">0x1236</span>;<br><br><span class="hljs-type">ptrdiff_t</span> dist = j2 - j1;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%td\n"</span>, dist); <span class="hljs-comment">// 1</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>j1</code>和<code>j2</code>是两个指向 short 类型的指针,变量<code>dist</code>是它们之间的距离,类型为<code>ptrdiff_t</code>,值为<code>1</code>,因为相差2个字节正好存放一个 short 类型的值。</p><p>(4)指针与指针的比较运算</p><p>指针之间的比较运算,比较的是各自的内存地址哪一个更大,返回值是整数<code>1</code>(true)或<code>0</code>(false)。</p><h1 id="函数-1"><a href="#函数-1" class="headerlink" title="函数"></a>函数</h1><h2 id="简介-2"><a href="#简介-2" class="headerlink" title="简介"></a>简介</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">plus_one</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span> {<br> <span class="hljs-keyword">return</span> n + <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面的代码声明了一个函数<code>plus_one()</code>。</p><p>函数声明的语法有以下几点,需要注意。</p><p>(1)返回值类型。函数声明时,首先需要给出返回值的类型,上例是<code>int</code>,表示函数<code>plus_one()</code>返回一个整数。</p><p>(2)参数。函数名后面的圆括号里面,需要声明参数的类型和参数名,<code>plus_one(int n)</code>表示这个函数有一个整数参数<code>n</code>。</p><p>(3)函数体。函数体要写在大括号里面,后面(即大括号外面)不需要加分号。大括号的起始位置,可以跟函数名在同一行,也可以另起一行,本书采用同一行的写法。</p><p>(4)<code>return</code>语句。<code>return</code>语句给出函数的返回值,程序运行到这一行,就会跳出函数体,结束函数的调用。如果函数没有返回值,可以省略<code>return</code>语句,或者写成<code>return;</code>。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> a = plus_one(<span class="hljs-number">13</span>);<br><span class="hljs-comment">// a 等于 14</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">plus_one</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span> {<br> <span class="hljs-keyword">return</span> n + <span class="hljs-number">1</span>;<br>}<br><br>plus_one(<span class="hljs-number">2</span>, <span class="hljs-number">2</span>); <span class="hljs-comment">// 报错</span><br>plus_one(); <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>plus_one()</code>只能接受一个参数,传入两个参数或不传参数,都会报错。</p><p>函数必须声明后使用,否则会报错。也就是说,一定要在使用<code>plus_one()</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-type">int</span> a = plus_one(<span class="hljs-number">13</span>);<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">plus_one</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span> {<br> <span class="hljs-keyword">return</span> n + <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,在调用<code>plus_one()</code>之后,才声明这个函数,编译就会报错。</p><p>C 语言标准规定,函数只能声明在源码文件的顶层,不能声明在其他函数内部。</p><p>不返回值的函数,使用<code>void</code>关键字表示返回值的类型。没有参数的函数,声明时要用<code>void</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">void</span> <span class="hljs-title function_">myFunc</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></table></figure><p>上面的<code>myFunc()</code>函数,既没有返回值,调用时也不需要参数。</p><p>函数可以调用自身,这就叫做递归(recursion)。下面是斐波那契数列的例子。</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> <span class="hljs-type">long</span> <span class="hljs-title function_">Fibonacci</span><span class="hljs-params">(<span class="hljs-type">unsigned</span> n)</span> {<br> <span class="hljs-keyword">if</span> (n > <span class="hljs-number">2</span>)<br> <span class="hljs-keyword">return</span> Fibonacci(n - <span class="hljs-number">1</span>) + Fibonacci(n - <span class="hljs-number">2</span>);<br> <span class="hljs-keyword">else</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>Fibonacci()</code>调用了自身,大大简化了算法。</p><h2 id="main"><a href="#main" class="headerlink" title="main()"></a>main()</h2><p>C 语言规定,<code>main()</code>是程序的入口函数,即所有的程序一定要包含一个<code>main()</code>函数。程序总是从这个函数开始执行,如果没有该函数,程序就无法启动。其他函数都是通过它引入程序的。</p><p><code>main()</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> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Hello World\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,最后的<code>return 0;</code>表示函数结束运行,返回<code>0</code>。</p><p>C 语言约定,返回值<code>0</code>表示函数运行成功,如果返回其他非零整数,就表示运行失败,代码出了问题。系统根据<code>main()</code>的返回值,作为整个程序的返回值,确定程序是否运行成功。</p><p>正常情况下,如果<code>main()</code>里面省略<code>return 0</code>这一行,编译器会自动加上,即<code>main()</code>的默认返回值为0。所以,写成下面这样,效果完全一样。</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> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Hello World\n"</span>);<br>}<br></code></pre></td></tr></table></figure><p>由于 C 语言只会对<code>main()</code>函数默认添加返回值,对其他函数不会这样做,所以建议总是保留<code>return</code>语句,以便形成统一的代码风格。</p><h2 id="参数的传值引用"><a href="#参数的传值引用" class="headerlink" title="参数的传值引用"></a>参数的传值引用</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><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">void</span> <span class="hljs-title function_">increment</span><span class="hljs-params">(<span class="hljs-type">int</span> a)</span> {<br> a++;<br>}<br><br><span class="hljs-type">int</span> i = <span class="hljs-number">10</span>;<br>increment(i);<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, i); <span class="hljs-comment">// 10</span><br></code></pre></td></tr></table></figure><p>上面示例中,调用<code>increment(i)</code>以后,变量<code>i</code>本身不会发生变化,还是等于<code>10</code>。因为传入函数的是<code>i</code>的拷贝,而不是<code>i</code>本身,拷贝的变化,影响不到原始变量。这就叫做“传值引用”。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">increment</span><span class="hljs-params">(<span class="hljs-type">int</span> a)</span> {<br> a++;<br> <span class="hljs-keyword">return</span> a;<br>}<br><br><span class="hljs-type">int</span> i = <span class="hljs-number">10</span>;<br>i = increment(i);<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, i); <span class="hljs-comment">// 11</span><br></code></pre></td></tr></table></figure><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><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-type">void</span> <span class="hljs-title function_">Swap</span><span class="hljs-params">(<span class="hljs-type">int</span> x, <span class="hljs-type">int</span> y)</span> {<br> <span class="hljs-type">int</span> temp;<br> temp = x;<br> x = y;<br> y = temp;<br>}<br><br><span class="hljs-type">int</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-type">int</span> b = <span class="hljs-number">2</span>;<br>Swap(a, b); <span class="hljs-comment">// 无效</span><br></code></pre></td></tr></table></figure><p>上面的写法不会产生交换变量值的效果,因为传入的变量是原始变量<code>a</code>和<code>b</code>的拷贝,不管函数内部怎么操作,都影响不了原始变量。</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-type">void</span> <span class="hljs-title function_">Swap</span><span class="hljs-params">(<span class="hljs-type">int</span>* x, <span class="hljs-type">int</span>* y)</span> {<br> <span class="hljs-type">int</span> temp;<br> temp = *x;<br> *x = *y;<br> *y = temp;<br>}<br><br><span class="hljs-type">int</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-type">int</span> b = <span class="hljs-number">2</span>;<br>Swap(&a, &b);<br></code></pre></td></tr></table></figure><p>上面示例中,通过传入变量<code>x</code>和<code>y</code>的地址,函数内部就可以直接操作该地址,从而实现交换两个变量的值。</p><p>虽然跟传参无关,这里特别提一下,函数不要返回内部变量的指针。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span>* <span class="hljs-title function_">f</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">int</span> i;<br> <span class="hljs-comment">// ...</span><br> <span class="hljs-keyword">return</span> &i;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数返回内部变量<code>i</code>的指针,这种写法是错的。因为当函数结束运行时,内部变量就消失了,这时指向内部变量<code>i</code>的内存地址就是无效的,再去使用这个地址是非常危险的。</p><h2 id="函数指针"><a href="#函数指针" class="headerlink" title="函数指针"></a>函数指针</h2><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">print</span><span class="hljs-params">(<span class="hljs-type">int</span> a)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, a);<br>}<br><br><span class="hljs-type">void</span> (*print_ptr)(<span class="hljs-type">int</span>) = &print;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>print_ptr</code>是一个函数指针,它指向函数<code>print()</code>的地址。函数<code>print()</code>的地址可以用<code>&print</code>获得。注意,<code>(*print_ptr)</code>一定要写在圆括号里面,否则函数参数<code>(int)</code>的优先级高于<code>*</code>,整个式子就会变成<code>void* print_ptr(int)</code>。</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">(*print_ptr)(<span class="hljs-number">10</span>);<br><span class="hljs-comment">// 等同于</span><br>print(<span class="hljs-number">10</span>);<br></code></pre></td></tr></table></figure><p>比较特殊的是,C 语言还规定,函数名本身就是指向函数代码的指针,通过函数名就能获取函数地址。也就是说,<code>print</code>和<code>&print</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">if</span> (print == &print) <span class="hljs-comment">// true</span><br></code></pre></td></tr></table></figure><p>因此,上面代码的<code>print_ptr</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> (*print_ptr)(<span class="hljs-type">int</span>) = &print;<br><span class="hljs-comment">// 或</span><br><span class="hljs-type">void</span> (*print_ptr)(<span class="hljs-type">int</span>) = print;<br><br><span class="hljs-keyword">if</span> (print_ptr == print) <span class="hljs-comment">// true</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><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">// 写法一</span><br>print(<span class="hljs-number">10</span>)<br><br><span class="hljs-comment">// 写法二</span><br>(*print)(<span class="hljs-number">10</span>)<br><br><span class="hljs-comment">// 写法三</span><br>(&print)(<span class="hljs-number">10</span>)<br><br><span class="hljs-comment">// 写法四</span><br>(*print_ptr)(<span class="hljs-number">10</span>)<br><br><span class="hljs-comment">// 写法五</span><br>print_ptr(<span class="hljs-number">10</span>)<br></code></pre></td></tr></table></figure><p>为了简洁易读,一般情况下,函数名前面都不加<code>*</code>和<code>&</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-type">int</span> <span class="hljs-title function_">compute</span><span class="hljs-params">(<span class="hljs-type">int</span> (*myfunc)(<span class="hljs-type">int</span>), <span class="hljs-type">int</span>, <span class="hljs-type">int</span>)</span>;<br></code></pre></td></tr></table></figure><p>上面示例可以清晰地表明,函数<code>compute()</code>的第一个参数也是一个函数。</p><h2 id="函数原型"><a href="#函数原型" class="headerlink" title="函数原型"></a>函数原型</h2><p>前面说过,函数必须先声明,后使用。由于程序总是先运行<code>main()</code>函数,导致所有其他函数都必须在<code>main()</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-type">void</span> <span class="hljs-title function_">func1</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">func2</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> func1();<br> func2();<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面代码中,<code>main()</code>函数必须在最后声明,否则编译时会产生警告,找不到<code>func1()</code>或<code>func2()</code>的声明。</p><p>但是,<code>main()</code>是整个程序的入口,也是主要逻辑,放在最前面比较好。另一方面,对于函数较多的程序,保证每个函数的顺序正确,会变得很麻烦。</p><p>C 语言提供的解决方法是,只要在程序开头处给出函数原型,函数就可以先使用、后声明。所谓函数原型,就是提前告诉编译器,每个函数的返回类型和参数类型。其他信息都不需要,也不用包括函数体,具体的函数实现可以后面再补上。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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-type">int</span> <span class="hljs-title function_">twice</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> num)</span> {<br> <span class="hljs-keyword">return</span> twice(num);<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">twice</span><span class="hljs-params">(<span class="hljs-type">int</span> num)</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * num;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>twice()</code>的实现是放在<code>main()</code>后面,但是代码头部先给出了函数原型,所以可以正确编译。只要提前给出函数原型,函数具体的实现放在哪里,就不重要了。</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> <span class="hljs-title function_">twice</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span>;<br><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">twice</span><span class="hljs-params">(<span class="hljs-type">int</span> num)</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>twice</code>函数的参数名<code>num</code>,无论是否出现在原型里面,都是可以的。</p><p>注意,函数原型必须以分号结尾。</p><p>一般来说,每个源码文件的头部,都会给出当前脚本使用的所有函数的原型。</p><h2 id="exit"><a href="#exit" class="headerlink" title="exit()"></a>exit()</h2><p><code>exit()</code>函数用来终止整个程序的运行。一旦执行到该函数,程序就会立即结束。该函数的原型定义在头文件<code>stdlib.h</code>里面。</p><p><code>exit()</code>可以向程序外部返回一个值,它的参数就是程序的返回值。一般来说,使用两个常量作为它的参数:<code>EXIT_SUCCESS</code>(相当于 0)表示程序运行成功,<code>EXIT_FAILURE</code>(相当于 1)表示程序异常中止。这两个常数也是定义在<code>stdlib.h</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">// 程序运行成功</span><br><span class="hljs-comment">// 等同于 exit(0);</span><br><span class="hljs-built_in">exit</span>(EXIT_SUCCESS);<br><br><span class="hljs-comment">// 程序异常中止</span><br><span class="hljs-comment">// 等同于 exit(1);</span><br><span class="hljs-built_in">exit</span>(EXIT_FAILURE);<br></code></pre></td></tr></table></figure><p>在<code>main()</code>函数里面,<code>exit()</code>等价于使用<code>return</code>语句。其他函数使用<code>exit()</code>,就是终止整个程序的运行,没有其他作用。</p><p>C 语言还提供了一个<code>atexit()</code>函数,用来登记<code>exit()</code>执行时额外执行的函数,用来做一些退出程序时的收尾工作。该函数的原型也是定义在头文件<code>stdlib.h</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-type">int</span> <span class="hljs-title function_">atexit</span><span class="hljs-params">(<span class="hljs-type">void</span> (*func)(<span class="hljs-type">void</span>))</span>;<br></code></pre></td></tr></table></figure><p><code>atexit()</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">print</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"something wrong!\n"</span>);<br>}<br><br>atexit(print);<br><span class="hljs-built_in">exit</span>(EXIT_FAILURE);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>exit()</code>执行时会先自动调用<code>atexit()</code>注册的<code>print()</code>函数,然后再终止程序。</p><h2 id="函数说明符"><a href="#函数说明符" class="headerlink" title="函数说明符"></a>函数说明符</h2><p>C 语言提供了一些函数说明符,让函数用法更加明确。</p><h3 id="extern-说明符"><a href="#extern-说明符" class="headerlink" title="extern 说明符"></a>extern 说明符</h3><p>对于多文件的项目,源码文件会用到其他文件声明的函数。这时,当前文件里面,需要给出外部函数的原型,并用<code>extern</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">extern</span> <span class="hljs-type">int</span> <span class="hljs-title function_">foo</span><span class="hljs-params">(<span class="hljs-type">int</span> arg1, <span class="hljs-type">char</span> arg2)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">int</span> a = foo(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>);<br> <span class="hljs-comment">// ...</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>foo()</code>定义在其他文件,<code>extern</code>告诉编译器当前文件不包含该函数的定义。</p><p>不过,由于函数原型默认就是<code>extern</code>,所以这里不加<code>extern</code>,效果是一样的。</p><h3 id="static-说明符"><a href="#static-说明符" class="headerlink" title="static 说明符"></a>static 说明符</h3><p>默认情况下,每次调用函数时,函数的内部变量都会重新初始化,不会保留上一次运行的值。<code>static</code>说明符可以改变这种行为。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">counter</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">static</span> <span class="hljs-type">int</span> count = <span class="hljs-number">1</span>; <span class="hljs-comment">// 只初始化一次</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, count);<br> count++;<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> counter(); <span class="hljs-comment">// 1</span><br> counter(); <span class="hljs-comment">// 2</span><br> counter(); <span class="hljs-comment">// 3</span><br> counter(); <span class="hljs-comment">// 4</span><br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>counter()</code>的内部变量<code>count</code>,使用<code>static</code>说明符修饰,表明这个变量只初始化一次,以后每次调用时都会使用上一次的值,造成递增的效果。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> i = <span class="hljs-number">3</span>;<br><span class="hljs-type">static</span> <span class="hljs-type">int</span> j = i; <span class="hljs-comment">// 错误</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>j</code>属于静态变量,初始化时不能赋值为另一个变量<code>i</code>。</p><p>另外,在块作用域中,<code>static</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">static</span> <span class="hljs-type">int</span> foo;<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">static</span> <span class="hljs-type">int</span> foo = <span class="hljs-number">0</span>;<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">Twice</span><span class="hljs-params">(<span class="hljs-type">int</span> num)</span> {<br> <span class="hljs-type">int</span> result = num * <span class="hljs-number">2</span>;<br> <span class="hljs-keyword">return</span>(result);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>static</code>关键字表示该函数只能在当前文件里使用,如果没有这个关键字,其他文件也可以使用这个函数(通过声明函数原型)。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sum_array</span><span class="hljs-params">(<span class="hljs-type">int</span> a[<span class="hljs-type">static</span> <span class="hljs-number">3</span>], <span class="hljs-type">int</span> n)</span> {<br> <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>static</code>对程序行为不会有任何影响,只是用来告诉编译器,该数组长度至少为3,某些情况下可以加快程序运行速度。另外,需要注意的是,对于多维数组的参数,<code>static</code>仅可用于第一维的说明。</p><h3 id="const-说明符"><a href="#const-说明符" class="headerlink" title="const 说明符"></a>const 说明符</h3><p>函数参数里面的<code>const</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">void</span> <span class="hljs-title function_">f</span><span class="hljs-params">(<span class="hljs-type">int</span>* p)</span> {<br> <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>f()</code>的参数是一个指针<code>p</code>,函数内部可能会改掉它所指向的值<code>*p</code>,从而影响到函数外部。</p><p>为了避免这种情况,可以在声明函数时,在指针参数前面加上<code>const</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">void</span> <span class="hljs-title function_">f</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>* p)</span> {<br> *p = <span class="hljs-number">0</span>; <span class="hljs-comment">// 该行报错</span><br>}<br></code></pre></td></tr></table></figure><p>上面示例中,声明函数时,<code>const</code>指定不能修改指针<code>p</code>指向的值,所以<code>*p = 0</code>就会报错。</p><p>但是上面这种写法,只限制修改<code>p</code>所指向的值,而<code>p</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">void</span> <span class="hljs-title function_">f</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>* p)</span> {<br> <span class="hljs-type">int</span> x = <span class="hljs-number">13</span>;<br> p = &x; <span class="hljs-comment">// 允许修改</span><br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>p</code>本身是可以修改,<code>const</code>只限定<code>*p</code>不能修改。</p><p>如果想限制修改<code>p</code>,可以把<code>const</code>放在<code>p</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">void</span> <span class="hljs-title function_">f</span><span class="hljs-params">(<span class="hljs-type">int</span>* <span class="hljs-type">const</span> p)</span> {<br> <span class="hljs-type">int</span> x = <span class="hljs-number">13</span>;<br> p = &x; <span class="hljs-comment">// 该行报错</span><br>}<br></code></pre></td></tr></table></figure><p>如果想同时限制修改<code>p</code>和<code>*p</code>,需要使用两个<code>const</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">void</span> <span class="hljs-title function_">f</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>* <span class="hljs-type">const</span> p)</span> {<br> <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></table></figure><h2 id="可变参数"><a href="#可变参数" class="headerlink" title="可变参数"></a>可变参数</h2><p>有些函数的参数数量是不确定的,声明函数的时候,可以使用省略号<code>...</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-type">int</span> <span class="hljs-title function_">printf</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* format, ...)</span>;<br></code></pre></td></tr></table></figure><p>上面示例是<code>printf()</code>函数的原型,除了第一个参数,其他参数的数量是可变的,与格式字符串里面的占位符数量有关。这时,就可以用<code>...</code>表示可变数量的参数。</p><p>注意,<code>...</code>符号必须放在参数序列的结尾,否则会报错。</p><p>头文件<code>stdarg.h</code>定义了一些宏,可以操作可变参数。</p><p>(1)<code>va_list</code>:一个数据类型,用来定义一个可变参数对象。它必须在操作可变参数时,首先使用。</p><p>(2)<code>va_start</code>:一个函数,用来初始化可变参数对象。它接受两个参数,第一个参数是可变参数对象,第二个参数是原始函数里面,可变参数之前的那个参数,用来为可变参数定位。</p><p>(3)<code>va_arg</code>:一个函数,用来取出当前那个可变参数,每次调用后,内部指针就会指向下一个可变参数。它接受两个参数,第一个是可变参数对象,第二个是当前可变参数的类型。</p><p>(4)<code>va_end</code>:一个函数,用来清理可变参数对象。</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-type">double</span> <span class="hljs-title function_">average</span><span class="hljs-params">(<span class="hljs-type">int</span> i, ...)</span> {<br> <span class="hljs-type">double</span> total = <span class="hljs-number">0</span>;<br> va_list ap;<br> va_start(ap, i);<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">1</span>; j <= i; ++j) {<br> total += va_arg(ap, <span class="hljs-type">double</span>);<br> }<br> va_end(ap);<br> <span class="hljs-keyword">return</span> total / i;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>va_list ap</code>定义<code>ap</code>为可变参数对象,<code>va_start(ap, i)</code>将参数<code>i</code>后面的参数统一放入<code>ap</code>,<code>va_arg(ap, double)</code>用来从<code>ap</code>依次取出一个参数,并且指定该参数为 double 类型,<code>va_end(ap)</code>用来清理可变参数对象。</p><h1 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h1><h2 id="简介-3"><a href="#简介-3" class="headerlink" title="简介"></a>简介</h2><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> scores[<span class="hljs-number">100</span>];<br></code></pre></td></tr></table></figure><p>上面示例声明了一个数组<code>scores</code>,里面包含100个成员,每个成员都是<code>int</code>类型。</p><p>注意,声明数组时,必须给出数组的大小。</p><p>数组的成员从<code>0</code>开始编号,所以数组<code>scores[100]</code>就是从第0号成员一直到第99号成员,最后一个成员的编号会比数组长度小<code>1</code>。</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></pre></td><td class="code"><pre><code class="hljs c">scores[<span class="hljs-number">0</span>] = <span class="hljs-number">13</span>;<br>scores[<span class="hljs-number">99</span>] = <span class="hljs-number">42</span>;<br></code></pre></td></tr></table></figure><p>上面示例对数组<code>scores</code>的第一个位置和最后一个位置,进行了赋值。</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> scores[<span class="hljs-number">100</span>];<br><br>scores[<span class="hljs-number">100</span>] = <span class="hljs-number">51</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,数组<code>scores</code>只有100个成员,因此<code>scores[100]</code>这个位置是不存在的。但是,引用这个位置并不会报错,会正常运行,使得紧跟在<code>scores</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-type">int</span> a[<span class="hljs-number">5</span>] = {<span class="hljs-number">22</span>, <span class="hljs-number">37</span>, <span class="hljs-number">3490</span>, <span class="hljs-number">18</span>, <span class="hljs-number">95</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"><span class="hljs-type">int</span> a[<span class="hljs-number">5</span>];<br>a = {<span class="hljs-number">22</span>, <span class="hljs-number">37</span>, <span class="hljs-number">3490</span>, <span class="hljs-number">18</span>, <span class="hljs-number">95</span>}; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面代码中,数组<code>a</code>声明之后再进行大括号赋值,导致报错。</p><p>报错的原因是,C 语言规定,数组变量一旦声明,就不得修改变量指向的地址,具体会在后文解释。由于同样的原因,数组赋值之后,再用大括号修改值,也是不允许的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> a[<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>a = {<span class="hljs-number">22</span>, <span class="hljs-number">37</span>, <span class="hljs-number">3490</span>, <span class="hljs-number">18</span>, <span class="hljs-number">95</span>}; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面代码中,数组<code>a</code>赋值后,再用大括号重新赋值也是不允许的。</p><p>使用大括号赋值时,大括号里面的值不能多于数组的长度,否则编译时会报错。</p><p>如果大括号里面的值,少于数组的成员数量,那么未赋值的成员自动初始化为<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">int</span> a[<span class="hljs-number">5</span>] = {<span class="hljs-number">22</span>, <span class="hljs-number">37</span>, <span class="hljs-number">3490</span>};<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span> a[<span class="hljs-number">5</span>] = {<span class="hljs-number">22</span>, <span class="hljs-number">37</span>, <span class="hljs-number">3490</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</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-type">int</span> a[<span class="hljs-number">100</span>] = {<span class="hljs-number">0</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-type">int</span> a[<span class="hljs-number">15</span>] = {[<span class="hljs-number">2</span>] = <span class="hljs-number">29</span>, [<span class="hljs-number">9</span>] = <span class="hljs-number">7</span>, [<span class="hljs-number">14</span>] = <span class="hljs-number">48</span>};<br></code></pre></td></tr></table></figure><p>上面示例中,数组的2号、9号、14号位置被赋值,其他位置的值都自动设为0。</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-type">int</span> a[<span class="hljs-number">15</span>] = {[<span class="hljs-number">9</span>] = <span class="hljs-number">7</span>, [<span class="hljs-number">14</span>] = <span class="hljs-number">48</span>, [<span class="hljs-number">2</span>] = <span class="hljs-number">29</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-type">int</span> a[<span class="hljs-number">15</span>] = {<span class="hljs-number">1</span>, [<span class="hljs-number">5</span>] = <span class="hljs-number">10</span>, <span class="hljs-number">11</span>, [<span class="hljs-number">10</span>] = <span class="hljs-number">20</span>, <span class="hljs-number">21</span>}<br></code></pre></td></tr></table></figure><p>上面示例中,0号、5号、6号、10号、11号被赋值。</p><p>C 语言允许省略方括号里面的数组成员数量,这时将根据大括号里面的值的数量,自动确定数组的长度。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> a[] = {<span class="hljs-number">22</span>, <span class="hljs-number">37</span>, <span class="hljs-number">3490</span>};<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span> a[<span class="hljs-number">3</span>] = {<span class="hljs-number">22</span>, <span class="hljs-number">37</span>, <span class="hljs-number">3490</span>};<br></code></pre></td></tr></table></figure><p>上面示例中,数组<code>a</code>的长度,将根据大括号里面的值的数量,确定为<code>3</code>。</p><p>省略成员数量时,如果同时采用指定位置的赋值,那么数组长度将是最大的指定位置再加1。</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> a[] = {[<span class="hljs-number">2</span>] = <span class="hljs-number">6</span>, [<span class="hljs-number">9</span>] = <span class="hljs-number">12</span>};<br></code></pre></td></tr></table></figure><p>上面示例中,数组<code>a</code>的最大指定位置是<code>9</code>,所以数组的长度是10。</p><h2 id="数组长度"><a href="#数组长度" class="headerlink" title="数组长度"></a>数组长度</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> a[] = {<span class="hljs-number">22</span>, <span class="hljs-number">37</span>, <span class="hljs-number">3490</span>};<br><span class="hljs-type">int</span> arrLen = <span class="hljs-keyword">sizeof</span>(a); <span class="hljs-comment">// 12</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>sizeof</code>返回数组<code>a</code>的字节长度是<code>12</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-keyword">sizeof</span>(a) / <span class="hljs-keyword">sizeof</span>(a[<span class="hljs-number">0</span>])<br></code></pre></td></tr></table></figure><p>上面示例中,<code>sizeof(a)</code>是整个数组的字节长度,<code>sizeof(a[0])</code>是数组成员的字节长度,相除就是数组的成员数量。</p><p>注意,<code>sizeof</code>返回值的数据类型是<code>size_t</code>,所以<code>sizeof(a) / sizeof(a[0])</code>的数据类型也是<code>size_t</code>。在<code>printf()</code>里面的占位符,要用<code>%zd</code>或<code>%zu</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-type">int</span> x[<span class="hljs-number">12</span>];<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%zu\n"</span>, <span class="hljs-keyword">sizeof</span>(x)); <span class="hljs-comment">// 48</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%zu\n"</span>, <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>)); <span class="hljs-comment">// 4</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%zu\n"</span>, <span class="hljs-keyword">sizeof</span>(x) / <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>)); <span class="hljs-comment">// 12</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>sizeof(x) / sizeof(int)</code>就可以得到数组成员数量<code>12</code>。</p><h2 id="多维数组"><a href="#多维数组" class="headerlink" title="多维数组"></a>多维数组</h2><p>C 语言允许声明多个维度的数组,有多少个维度,就用多少个方括号,比如二维数组就使用两个方括号。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> board[<span class="hljs-number">10</span>][<span class="hljs-number">10</span>];<br></code></pre></td></tr></table></figure><p>上面示例声明了一个二维数组,第一个维度有10个成员,第二个维度也有10个成员。</p><p>多维数组可以理解成,上层维度的每个成员本身就是一个数组。比如上例中,第一个维度的每个成员本身就是一个有10个成员的数组,因此整个二维数组共有100个成员(10 x 10 = 100)。</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-type">int</span> c[<span class="hljs-number">4</span>][<span class="hljs-number">5</span>][<span class="hljs-number">6</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">board[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>] = <span class="hljs-number">13</span>;<br>board[<span class="hljs-number">9</span>][<span class="hljs-number">9</span>] = <span class="hljs-number">13</span>;<br></code></pre></td></tr></table></figure><p>注意,<code>board[0][0]</code>不能写成<code>board[0, 0]</code>,因为<code>0, 0</code>是一个逗号表达式,返回第二个值,所以<code>board[0, 0]</code>等同于<code>board[0]</code>。</p><p>跟一维数组一样,多维数组每个维度的第一个成员也是从<code>0</code>开始编号。</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> a[<span class="hljs-number">2</span>][<span class="hljs-number">5</span>] = {<br> {<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-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>};<br></code></pre></td></tr></table></figure><p>上面示例中,<code>a</code>是一个二维数组,这种赋值写法相当于将第一维的每个成员写成一个数组。这种写法不用为每个成员都赋值,缺少的成员会自动设置为<code>0</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-type">int</span> a[<span class="hljs-number">2</span>][<span class="hljs-number">2</span>] = {[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>] = <span class="hljs-number">1</span>, [<span class="hljs-number">1</span>][<span class="hljs-number">1</span>] = <span class="hljs-number">2</span>};<br></code></pre></td></tr></table></figure><p>上面示例中,指定了<code>[0][0]</code>和<code>[1][1]</code>位置的值,其他位置就自动设为<code>0</code>。</p><p>不管数组有多少维度,在内存里面都是线性存储,<code>a[0][0]</code>的后面是<code>a[0][1]</code>,<code>a[0][1]</code>的后面是<code>a[1][0]</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-type">int</span> a[<span class="hljs-number">2</span>][<span class="hljs-number">2</span>] = {<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">2</span>};<br></code></pre></td></tr></table></figure><h2 id="变长数组"><a href="#变长数组" class="headerlink" title="变长数组"></a>变长数组</h2><p>数组声明的时候,数组长度除了使用常量,也可以使用变量。这叫做变长数组(variable-length array,简称 VLA)。</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> n = x + y;<br><span class="hljs-type">int</span> arr[n];<br></code></pre></td></tr></table></figure><p>上面示例中,数组<code>arr</code>就是变长数组,因为它的长度取决于变量<code>n</code>的值,编译器没法事先确定,只有运行时才能知道<code>n</code>是多少。</p><p>变长数组的根本特征,就是数组长度只有运行时才能确定。它的好处是程序员不必在开发时,随意为数组指定一个估计的长度,程序可以在运行时为数组分配精确的长度。</p><p>任何长度需要运行时才能确定的数组,都是变长数组。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> i = <span class="hljs-number">10</span>;<br><br><span class="hljs-type">int</span> a1[i];<br><span class="hljs-type">int</span> a2[i + <span class="hljs-number">5</span>];<br><span class="hljs-type">int</span> a3[i + k];<br></code></pre></td></tr></table></figure><p>上面示例中,三个数组的长度都需要运行代码才能知道,编译器并不知道它们的长度,所以它们都是变长数组。</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> m = <span class="hljs-number">4</span>;<br><span class="hljs-type">int</span> n = <span class="hljs-number">5</span>;<br><span class="hljs-type">int</span> c[m][n];<br></code></pre></td></tr></table></figure><p>上面示例中,<code>c[m][n]</code>就是二维变长数组。</p><h2 id="数组的地址"><a href="#数组的地址" class="headerlink" title="数组的地址"></a>数组的地址</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><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> a[<span class="hljs-number">5</span>] = {<span class="hljs-number">11</span>, <span class="hljs-number">22</span>, <span class="hljs-number">33</span>, <span class="hljs-number">44</span>, <span class="hljs-number">55</span>};<br><span class="hljs-type">int</span>* p;<br><br>p = &a[<span class="hljs-number">0</span>];<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, *p); <span class="hljs-comment">// Prints "11"</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>&a[0]</code>就是数组<code>a</code>的首个成员<code>11</code>的内存地址,也是整个数组的起始地址。反过来,从这个地址(<code>*p</code>),可以获得首个成员的值<code>11</code>。</p><p>由于数组的起始地址是常用操作,<code>&array[0]</code>的写法有点麻烦,C 语言提供了便利写法,数组名等同于起始地址,也就是说,数组名就是指向第一个成员(<code>array[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><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> a[<span class="hljs-number">5</span>] = {<span class="hljs-number">11</span>, <span class="hljs-number">22</span>, <span class="hljs-number">33</span>, <span class="hljs-number">44</span>, <span class="hljs-number">55</span>};<br><br><span class="hljs-type">int</span>* p = &a[<span class="hljs-number">0</span>];<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span>* p = a;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>&a[0]</code>和数组名<code>a</code>是等价的。</p><p>这样的话,如果把数组名传入一个函数,就等同于传入一个指针变量。在函数内部,就可以通过这个指针变量获得整个数组。</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-comment">// 写法一</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sum</span><span class="hljs-params">(<span class="hljs-type">int</span> arr[], <span class="hljs-type">int</span> len)</span>;<br><span class="hljs-comment">// 写法二</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sum</span><span class="hljs-params">(<span class="hljs-type">int</span>* arr, <span class="hljs-type">int</span> len)</span>;<br></code></pre></td></tr></table></figure><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><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-type">int</span> <span class="hljs-title function_">sum</span><span class="hljs-params">(<span class="hljs-type">int</span>* arr, <span class="hljs-type">int</span> len)</span> {<br> <span class="hljs-type">int</span> i;<br> <span class="hljs-type">int</span> total = <span class="hljs-number">0</span>;<br><br> <span class="hljs-comment">// 假定数组有 10 个成员</span><br> <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < len; i++) {<br> total += arr[i];<br> }<br> <span class="hljs-keyword">return</span> total;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,传入函数的是一个指针<code>arr</code>(也是数组名)和数组长度,通过指针获取数组的每个成员,从而求和。</p><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><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> a[<span class="hljs-number">4</span>][<span class="hljs-number">2</span>];<br><br><span class="hljs-comment">// 取出 a[0][0] 的值</span><br>*(a[<span class="hljs-number">0</span>]);<br><span class="hljs-comment">// 等同于</span><br>**a<br></code></pre></td></tr></table></figure><p>上面示例中,由于<code>a[0]</code>本身是一个指针,指向第二维数组的第一个成员<code>a[0][0]</code>。所以,<code>*(a[0])</code>取出的是<code>a[0][0]</code>的值。至于<code>**a</code>,就是对<code>a</code>进行两次<code>*</code>运算,第一次取出的是<code>a[0]</code>,第二次取出的是<code>a[0][0]</code>。同理,二维数组的<code>&a[0][0]</code>等同于<code>*a</code>。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> ints[<span class="hljs-number">100</span>];<br>ints = <span class="hljs-literal">NULL</span>; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面示例中,重新为数组名赋值,改变原来的内存地址,就会报错。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> a[<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><br><span class="hljs-comment">// 写法一</span><br><span class="hljs-type">int</span> b[<span class="hljs-number">5</span>] = a; <span class="hljs-comment">// 报错</span><br><br><span class="hljs-comment">// 写法二</span><br><span class="hljs-type">int</span> b[<span class="hljs-number">5</span>];<br>b = a; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面两种写法都会更改数组<code>b</code>的地址,导致报错。</p><h2 id="数组指针的加减法"><a href="#数组指针的加减法" class="headerlink" title="数组指针的加减法"></a>数组指针的加减法</h2><p>C 语言里面,数组名可以进行加法和减法运算,等同于在数组成员之间前后移动,即从一个成员的内存地址移动到另一个成员的内存地址。比如,<code>a + 1</code>返回下一个成员的地址,<code>a - 1</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-type">int</span> a[<span class="hljs-number">5</span>] = {<span class="hljs-number">11</span>, <span class="hljs-number">22</span>, <span class="hljs-number">33</span>, <span class="hljs-number">44</span>, <span class="hljs-number">55</span>};<br><br><span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">5</span>; i++) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, *(a + i));<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,通过指针的移动遍历数组,<code>a + i</code>的每轮循环每次都会指向下一个成员的地址,<code>*(a + i)</code>取出该地址的值,等同于<code>a[i]</code>。对于数组的第一个成员,<code>*(a + 0)</code>(即<code>*a</code>)等同于<code>a[0]</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">a[b] == *(a + b)<br></code></pre></td></tr></table></figure><p>上面代码给出了数组成员的两种访问方式,一种是使用方括号<code>a[b]</code>,另一种是使用指针<code>*(a + b)</code>。</p><p>如果指针变量<code>p</code>指向数组的一个成员,那么<code>p++</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-type">int</span> a[] = {<span class="hljs-number">11</span>, <span class="hljs-number">22</span>, <span class="hljs-number">33</span>, <span class="hljs-number">44</span>, <span class="hljs-number">55</span>, <span class="hljs-number">999</span>};<br><br><span class="hljs-type">int</span>* p = a;<br><br><span class="hljs-keyword">while</span> (*p != <span class="hljs-number">999</span>) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, *p);<br> p++;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,通过<code>p++</code>让变量<code>p</code>指向下一个成员。</p><p>注意,数组名指向的地址是不能变的,所以上例中,不能直接对<code>a</code>进行自增,即<code>a++</code>的写法是错的,必须将<code>a</code>的地址赋值给指针变量<code>p</code>,然后对<code>p</code>进行自增。</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><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-type">int</span> <span class="hljs-title function_">sum</span><span class="hljs-params">(<span class="hljs-type">int</span>* start, <span class="hljs-type">int</span>* end)</span> {<br> <span class="hljs-type">int</span> total = <span class="hljs-number">0</span>;<br><br> <span class="hljs-keyword">while</span> (start < end) {<br> total += *start;<br> start++;<br> }<br><br> <span class="hljs-keyword">return</span> total;<br>}<br><br><span class="hljs-type">int</span> arr[<span class="hljs-number">5</span>] = {<span class="hljs-number">20</span>, <span class="hljs-number">10</span>, <span class="hljs-number">5</span>, <span class="hljs-number">39</span>, <span class="hljs-number">4</span>};<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%i\n"</span>, sum(arr, arr + <span class="hljs-number">5</span>));<br></code></pre></td></tr></table></figure><p>上面示例中,<code>arr</code>是数组的起始地址,<code>arr + 5</code>是结束地址。只要起始地址小于结束地址,就表示还没有到达数组尾部。</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></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">20</span>, <span class="hljs-number">10</span>, <span class="hljs-number">5</span>, <span class="hljs-number">39</span>, <span class="hljs-number">88</span>};<br><span class="hljs-type">int</span>* p = arr;<br><br><span class="hljs-keyword">while</span> (*p != <span class="hljs-number">88</span>)<br> p++;<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%i\n"</span>, p - arr); <span class="hljs-comment">// 4</span><br></code></pre></td></tr></table></figure><p>上面示例中,将某个数组成员的地址,减去数组起始地址,就可以知道,当前成员与起始地址之间有多少个成员。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> arr[<span class="hljs-number">4</span>][<span class="hljs-number">2</span>];<br><br><span class="hljs-comment">// 指针指向 arr[1]</span><br>arr + <span class="hljs-number">1</span>;<br><br><span class="hljs-comment">// 指针指向 arr[0][1]</span><br>arr[<span class="hljs-number">0</span>] + <span class="hljs-number">1</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>arr</code>是一个二维数组,<code>arr + 1</code>是将指针移动到第一维数组的下一个成员,即<code>arr[1]</code>。由于每个第一维的成员,本身都包含另一个数组,即<code>arr[0]</code>是一个指向第二维数组的指针,所以<code>arr[0] + 1</code>的含义是将指针移动到第二维数组的下一个成员,即<code>arr[0][1]</code>。</p><p>同一个数组的两个成员的指针相减时,返回它们之间的距离。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span>* p = &a[<span class="hljs-number">5</span>];<br><span class="hljs-type">int</span>* q = &a[<span class="hljs-number">1</span>];<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, p - q); <span class="hljs-comment">// 4</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, q - p); <span class="hljs-comment">// -4</span><br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>p</code>和<code>q</code>分别是数组5号位置和1号位置的指针,它们相减等于4或-4。</p><h2 id="数组的复制"><a href="#数组的复制" class="headerlink" title="数组的复制"></a>数组的复制</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span>* a;<br><span class="hljs-type">int</span> b[<span class="hljs-number">3</span>] = {<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>};<br><br>a = b;<br></code></pre></td></tr></table></figure><p>上面的写法,结果不是将数组<code>b</code>复制给数组<code>a</code>,而是让<code>a</code>和<code>b</code>指向同一个数组。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < N; i++)<br> a[i] = b[i];<br></code></pre></td></tr></table></figure><p>上面示例中,通过将数组<code>b</code>的成员逐个复制给数组<code>a</code>,从而实现数组的赋值。</p><p>另一种方法是使用<code>memcpy()</code>函数(定义在头文件<code>string.h</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">memcpy</span>(a, b, <span class="hljs-keyword">sizeof</span>(b));<br></code></pre></td></tr></table></figure><p>上面示例中,将数组<code>b</code>所在的那段内存,复制给数组<code>a</code>。这种方法要比循环复制数组成员要快。</p><h2 id="作为函数的参数"><a href="#作为函数的参数" class="headerlink" title="作为函数的参数"></a>作为函数的参数</h2><h3 id="声明参数数组"><a href="#声明参数数组" class="headerlink" title="声明参数数组"></a>声明参数数组</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sum_array</span><span class="hljs-params">(<span class="hljs-type">int</span> a[], <span class="hljs-type">int</span> n)</span> {<br> <span class="hljs-comment">// ...</span><br>}<br><br><span class="hljs-type">int</span> a[] = {<span class="hljs-number">3</span>, <span class="hljs-number">5</span>, <span class="hljs-number">7</span>, <span class="hljs-number">3</span>};<br><span class="hljs-type">int</span> sum = sum_array(a, <span class="hljs-number">4</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>sum_array()</code>的第一个参数是数组本身,也就是数组名,第二个参数是数组长度。</p><p>由于数组名就是一个指针,如果只传数组名,那么函数只知道数组开始的地址,不知道结束的地址,所以才需要把数组长度也一起传入。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sum_array</span><span class="hljs-params">(<span class="hljs-type">int</span> a[][<span class="hljs-number">4</span>], <span class="hljs-type">int</span> n)</span> {<br> <span class="hljs-comment">// ...</span><br>}<br><br><span class="hljs-type">int</span> a[<span class="hljs-number">2</span>][<span class="hljs-number">4</span>] = {<br> {<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-number">8</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>, <span class="hljs-number">11</span>}<br>};<br><span class="hljs-type">int</span> sum = sum_array(a, <span class="hljs-number">2</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>sum_array()</code>的参数是一个二维数组。第一个参数是数组本身(<code>a[][4]</code>),这时可以不写第一维的长度,因为它作为第二个参数,会传入函数,但是一定要写第二维的长度<code>4</code>。</p><p>这是因为函数内部拿到的,只是数组的起始地址<code>a</code>,以及第一维的成员数量<code>2</code>。如果要正确计算数组的结束地址,还必须知道第一维每个成员的字节长度。写成<code>int a[][4]</code>,编译器就知道了,第一维每个成员本身也是一个数组,里面包含了4个整数,所以每个成员的字节长度就是<code>4 * sizeof(int)</code>。</p><h3 id="变长数组作为参数"><a href="#变长数组作为参数" class="headerlink" title="变长数组作为参数"></a>变长数组作为参数</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sum_array</span><span class="hljs-params">(<span class="hljs-type">int</span> n, <span class="hljs-type">int</span> a[n])</span> {<br> <span class="hljs-comment">// ...</span><br>}<br><br><span class="hljs-type">int</span> a[] = {<span class="hljs-number">3</span>, <span class="hljs-number">5</span>, <span class="hljs-number">7</span>, <span class="hljs-number">3</span>};<br><span class="hljs-type">int</span> sum = sum_array(<span class="hljs-number">4</span>, a);<br></code></pre></td></tr></table></figure><p>上面示例中,数组<code>a[n]</code>是一个变长数组,它的长度取决于变量<code>n</code>的值,只有运行时才能知道。所以,变量<code>n</code>作为参数时,顺序一定要在变长数组前面,这样运行时才能确定数组<code>a[n]</code>的长度,否则就会报错。</p><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-type">int</span> <span class="hljs-title function_">sum_array</span><span class="hljs-params">(<span class="hljs-type">int</span>, <span class="hljs-type">int</span> [*])</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sum_array</span><span class="hljs-params">(<span class="hljs-type">int</span>, <span class="hljs-type">int</span> [])</span>;<br></code></pre></td></tr></table></figure><p>上面两种变长函数的原型写法,都是合法的。</p><p>变长数组作为函数参数有一个好处,就是多维数组的参数声明,可以把后面的维度省掉了。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 原来的写法</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sum_array</span><span class="hljs-params">(<span class="hljs-type">int</span> a[][<span class="hljs-number">4</span>], <span class="hljs-type">int</span> n)</span>;<br><br><span class="hljs-comment">// 变长数组的写法</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sum_array</span><span class="hljs-params">(<span class="hljs-type">int</span> n, <span class="hljs-type">int</span> m, <span class="hljs-type">int</span> a[n][m])</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>sum_array()</code>的参数是一个多维数组,按照原来的写法,一定要声明第二维的长度。但是使用变长数组的写法,就不用声明第二维长度了,因为它可以作为参数传入函数。</p><h3 id="数组字面量作为参数"><a href="#数组字面量作为参数" class="headerlink" title="数组字面量作为参数"></a>数组字面量作为参数</h3><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-type">int</span> a[] = {<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> sum = sum_array(a, <span class="hljs-number">4</span>);<br><br><span class="hljs-comment">// 数组字面量作为参数</span><br><span class="hljs-type">int</span> sum = sum_array((<span class="hljs-type">int</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">4</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,两种写法是等价的。第二种写法省掉了数组变量的声明,直接将数组字面量传入函数。<code>{2, 3, 4, 5}</code>是数组值的字面量,<code>(int [])</code>类似于强制的类型转换,告诉编译器怎么理解这组值。</p><h1 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h1><h2 id="简介-4"><a href="#简介-4" class="headerlink" title="简介"></a>简介</h2><p>C 语言没有单独的字符串类型,字符串被当作字符数组,即<code>char</code>类型的数组。比如,字符串“Hello”是当作数组<code>{'H', 'e', 'l', 'l', 'o'}</code>处理的。</p><p>编译器会给数组分配一段连续内存,所有字符储存在相邻的内存单元之中。在字符串结尾,C 语言会自动添加一个全是二进制<code>0</code>的字节,写作<code>\0</code>字符,表示字符串结束。字符<code>\0</code>不同于字符<code>0</code>,前者的 ASCII 码是0(二进制形式<code>00000000</code>),后者的 ASCII 码是48(二进制形式<code>00110000</code>)。所以,字符串“Hello”实际储存的数组是<code>{'H', 'e', 'l', 'l', 'o', '\0'}</code>。</p><p>所有字符串的最后一个字符,都是<code>\0</code>。这样做的好处是,C 语言不需要知道字符串的长度,就可以读取内存里面的字符串,只要发现有一个字符是<code>\0</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-type">char</span> localString[<span class="hljs-number">10</span>];<br></code></pre></td></tr></table></figure><p>上面示例声明了一个10个成员的字符数组,可以当作字符串。由于必须留一个位置给<code>\0</code>,所以最多只能容纳9个字符的字符串。</p><p>字符串写成数组的形式,是非常麻烦的。C 语言提供了一种简写法,双引号之中的字符,会被自动视为字符数组。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c">{<span class="hljs-string">'H'</span>, <span class="hljs-string">'e'</span>, <span class="hljs-string">'l'</span>, <span class="hljs-string">'l'</span>, <span class="hljs-string">'o'</span>, <span class="hljs-string">'\0'</span>}<br><br><span class="hljs-comment">// 等价于</span><br><span class="hljs-string">"Hello"</span><br></code></pre></td></tr></table></figure><p>上面两种字符串的写法是等价的,内部存储方式都是一样的。双引号里面的字符串,不用自己添加结尾字符<code>\0</code>,C 语言会自动添加。</p><p>注意,双引号里面是字符串,单引号里面是字符,两者不能互换。如果把<code>Hello</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">// 报错</span><br><span class="hljs-string">'Hello'</span><br></code></pre></td></tr></table></figure><p>另一方面,即使双引号里面只有一个字符(比如<code>"a"</code>),也依然被处理成字符串(存储为2个字节),而不是字符<code>'a'</code>(存储为1个字节)。</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-string">"She replied, \"It does.\""</span><br></code></pre></td></tr></table></figure><p>反斜杠还可以表示其他特殊字符,比如换行符(<code>\n</code>)、制表符(<code>\t</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-string">"Hello, world!\n"</span><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-string">"hello \</span><br><span class="hljs-string">world"</span><br></code></pre></td></tr></table></figure><p>上面示例中,第一行尾部的反斜杠,将字符串拆成两行。</p><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><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> greeting[<span class="hljs-number">50</span>] = <span class="hljs-string">"Hello, "</span><span class="hljs-string">"how are you "</span><span class="hljs-string">"today!"</span>;<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">char</span> greeting[<span class="hljs-number">50</span>] = <span class="hljs-string">"Hello, how are you today!"</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">char</span> greeting[<span class="hljs-number">50</span>] = <span class="hljs-string">"Hello, "</span><br> <span class="hljs-string">"how are you "</span><br> <span class="hljs-string">"today!"</span>;<br></code></pre></td></tr></table></figure><p><code>printf()</code>使用占位符<code>%s</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">printf</span>(<span class="hljs-string">"%s\n"</span>, <span class="hljs-string">"hello world"</span>)<br></code></pre></td></tr></table></figure><h2 id="字符串变量的声明"><a href="#字符串变量的声明" class="headerlink" title="字符串变量的声明"></a>字符串变量的声明</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-comment">// 写法一</span><br><span class="hljs-type">char</span> s[<span class="hljs-number">14</span>] = <span class="hljs-string">"Hello, world!"</span>;<br><br><span class="hljs-comment">// 写法二</span><br><span class="hljs-type">char</span>* s = <span class="hljs-string">"Hello, world!"</span>;<br></code></pre></td></tr></table></figure><p>上面两种写法都声明了一个字符串变量<code>s</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-type">char</span> s[] = <span class="hljs-string">"Hello, world!"</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,编译器会将数组<code>s</code>的长度指定为14,正好容纳后面的字符串。</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-type">char</span> s[<span class="hljs-number">50</span>] = <span class="hljs-string">"hello"</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,字符数组<code>s</code>的长度是<code>50</code>,但是字符串“hello”的实际长度只有6(包含结尾符号<code>\0</code>),所以后面空出来的44个位置,都会被初始化为<code>\0</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-type">char</span> s[<span class="hljs-number">5</span>] = <span class="hljs-string">"hello"</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,字符串数组<code>s</code>的长度是<code>5</code>,小于字符串“hello”的实际长度6,这时编译器会报错。因为如果只将前5个字符写入,而省略最后的结尾符号<code>\0</code>,这很可能导致后面的字符串相关代码出错。</p><p>字符指针和字符数组,这两种声明字符串变量的写法基本是等价的,但是有两个差异。</p><p>第一个差异是,指针指向的字符串,在 C 语言内部被当作常量,不能修改字符串本身。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span>* s = <span class="hljs-string">"Hello, world!"</span>;<br>s[<span class="hljs-number">0</span>] = <span class="hljs-string">'z'</span>; <span class="hljs-comment">// 错误</span><br></code></pre></td></tr></table></figure><p>上面代码使用指针,声明了一个字符串变量,然后修改了字符串的第一个字符。这种写法是错的,会导致难以预测的后果,执行时很可能会报错。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> s[] = <span class="hljs-string">"Hello, world!"</span>;<br>s[<span class="hljs-number">0</span>] = <span class="hljs-string">'z'</span>;<br></code></pre></td></tr></table></figure><p>为什么字符串声明为指针时不能修改,声明为数组时就可以修改?原因是系统会将字符串的字面量保存在内存的常量区,这个区是不允许用户修改的。声明为指针时,指针变量存储的值是一个指向常量区的内存地址,因此用户不能通过这个地址去修改常量区。但是,声明为数组时,编译器会给数组单独分配一段内存,字符串字面量会被编译器解释成字符数组,逐个字符写入这段新分配的内存之中,而这段新内存是允许修改的。</p><p>为了提醒用户,字符串声明为指针后不得修改,可以在声明时使用<code>const</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-type">const</span> <span class="hljs-type">char</span>* s = <span class="hljs-string">"Hello, world!"</span>;<br></code></pre></td></tr></table></figure><p>上面字符串声明为指针时,使用了<code>const</code>说明符,就保证了该字符串无法修改。一旦修改,编译器肯定会报错。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span>* s = <span class="hljs-string">"hello"</span>;<br>s = <span class="hljs-string">"world"</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,字符指针可以指向另一个字符串。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> s[] = <span class="hljs-string">"hello"</span>;<br>s = <span class="hljs-string">"world"</span>; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面示例中,字符数组的数组名,总是指向初始化时的字符串地址,不能修改。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> s[<span class="hljs-number">10</span>];<br>s = <span class="hljs-string">"abc"</span>; <span class="hljs-comment">// 错误</span><br></code></pre></td></tr></table></figure><p>上面示例中,不能直接把字符串赋值给字符数组变量,会报错。原因是字符数组的变量名,跟所指向的数组是绑定的,不能指向另一个地址。</p><p>为什么数组变量不能赋值为另一个数组?原因是数组变量所在的地址无法改变,或者说,编译器一旦为数组变量分配地址后,这个地址就绑定这个数组变量了,这种绑定关系是不变的。C 语言也因此规定,数组变量是一个不可修改的左值,即不能用赋值运算符为它重新赋值。</p><p>想要重新赋值,必须使用 C 语言原生提供的<code>strcpy()</code>函数,通过字符串拷贝完成赋值。这样做以后,数组变量的地址还是不变的,即<code>strcpy()</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">char</span> s[<span class="hljs-number">10</span>];<br><span class="hljs-built_in">strcpy</span>(s, <span class="hljs-string">"abc"</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>strcpy()</code>函数把字符串<code>abc</code>拷贝给变量<code>s</code>,这个函数的详细用法会在后面介绍。</p><h2 id="strlen"><a href="#strlen" class="headerlink" title="strlen()"></a>strlen()</h2><p><code>strlen()</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// string.h</span><br><span class="hljs-type">size_t</span> <span class="hljs-title function_">strlen</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* s)</span>;<br></code></pre></td></tr></table></figure><p>它的参数是字符串变量,返回的是<code>size_t</code>类型的无符号整数,除非是极长的字符串,一般情况下当作<code>int</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">char</span>* str = <span class="hljs-string">"hello"</span>;<br><span class="hljs-type">int</span> len = <span class="hljs-built_in">strlen</span>(str); <span class="hljs-comment">// 5</span><br></code></pre></td></tr></table></figure><p><code>strlen()</code>的原型在标准库的<code>string.h</code>文件中定义,使用时需要加载头文件<code>string.h</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><string.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">char</span>* s = <span class="hljs-string">"Hello, world!"</span>;<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"The string is %zd characters long.\n"</span>, <span class="hljs-built_in">strlen</span>(s));<br>}<br></code></pre></td></tr></table></figure><p>注意,字符串长度(<code>strlen()</code>)与字符串变量长度(<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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> s[<span class="hljs-number">50</span>] = <span class="hljs-string">"hello"</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, <span class="hljs-built_in">strlen</span>(s)); <span class="hljs-comment">// 5</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, <span class="hljs-keyword">sizeof</span>(s)); <span class="hljs-comment">// 50</span><br></code></pre></td></tr></table></figure><p>上面示例中,字符串长度是5,字符串变量长度是50。</p><p>如果不使用这个函数,可以通过判断字符串末尾的<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><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> <span class="hljs-title function_">my_strlen</span><span class="hljs-params">(<span class="hljs-type">char</span> *s)</span> {<br> <span class="hljs-type">int</span> count = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">while</span> (s[count] != <span class="hljs-string">'\0'</span>)<br> count++;<br> <span class="hljs-keyword">return</span> count;<br>}<br></code></pre></td></tr></table></figure><h2 id="strcpy"><a href="#strcpy" class="headerlink" title="strcpy()"></a>strcpy()</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-type">char</span> str1[<span class="hljs-number">10</span>];<br><span class="hljs-type">char</span> str2[<span class="hljs-number">10</span>];<br><br>str1 = <span class="hljs-string">"abc"</span>; <span class="hljs-comment">// 报错</span><br>str2 = str1; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面两种字符串的复制写法,都是错的。因为数组的变量名是一个固定的地址,不能修改,使其指向另一个地址。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span>* s1;<br><span class="hljs-type">char</span>* s2;<br><br>s1 = <span class="hljs-string">"abc"</span>;<br>s2 = s1;<br></code></pre></td></tr></table></figure><p>上面代码可以运行,结果是两个指针变量<code>s1</code>和<code>s2</code>指向同一字符串,而不是将字符串<code>s1</code>的内容复制给<code>s2</code>。</p><p>C 语言提供了<code>strcpy()</code>函数,用于将一个字符串的内容复制到另一个字符串,相当于字符串赋值。该函数的原型定义在<code>string.h</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">strcpy</span>(<span class="hljs-type">char</span> dest[], <span class="hljs-type">const</span> <span class="hljs-type">char</span> source[])<br></code></pre></td></tr></table></figure><p><code>strcpy()</code>接受两个参数,第一个参数是目的字符串数组,第二个参数是源字符串数组。复制字符串之前,必须要保证第一个参数的长度不小于第二个参数,否则虽然不会报错,但会溢出第一个字符串变量的边界,发生难以预料的结果。第二个参数的<code>const</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><string.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">char</span> s[] = <span class="hljs-string">"Hello, world!"</span>;<br> <span class="hljs-type">char</span> t[<span class="hljs-number">100</span>];<br><br> <span class="hljs-built_in">strcpy</span>(t, s);<br><br> t[<span class="hljs-number">0</span>] = <span class="hljs-string">'z'</span>;<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, s); <span class="hljs-comment">// "Hello, world!"</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, t); <span class="hljs-comment">// "zello, world!"</span><br>}<br></code></pre></td></tr></table></figure><p>上面示例将变量<code>s</code>的值,拷贝一份放到变量<code>t</code>,变成两个不同的字符串,修改一个不会影响到另一个。另外,变量<code>t</code>的长度大于<code>s</code>,复制后多余的位置(结束标志<code>\0</code>后面的位置)都为随机值。</p><p><code>strcpy()</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">char</span> str[<span class="hljs-number">10</span>];<br><span class="hljs-built_in">strcpy</span>(str, <span class="hljs-string">"abcd"</span>);<br></code></pre></td></tr></table></figure><p>上面示例将字符数组变量,赋值为字符串“abcd”。</p><p><code>strcpy()</code>的返回值是一个字符串指针(即<code>char*</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-type">char</span>* s1 = <span class="hljs-string">"beast"</span>;<br><span class="hljs-type">char</span> s2[<span class="hljs-number">40</span>] = <span class="hljs-string">"Be the best that you can be."</span>;<br><span class="hljs-type">char</span>* ps;<br><br>ps = <span class="hljs-built_in">strcpy</span>(s2 + <span class="hljs-number">7</span>, s1);<br><br><span class="hljs-built_in">puts</span>(s2); <span class="hljs-comment">// Be the beast</span><br><span class="hljs-built_in">puts</span>(ps); <span class="hljs-comment">// beast</span><br></code></pre></td></tr></table></figure><p>上面示例中,从<code>s2</code>的第7个位置开始拷贝字符串<code>beast</code>,前面的位置不变。这导致<code>s2</code>后面的内容都被截去了,因为会连<code>beast</code>结尾的空字符一起拷贝。<code>strcpy()</code>返回的是一个指针,指向拷贝开始的位置。</p><p><code>strcpy()</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">strcpy</span>(str1, <span class="hljs-built_in">strcpy</span>(str2, <span class="hljs-string">"abcd"</span>));<br></code></pre></td></tr></table></figure><p>上面示例调用两次<code>strcpy()</code>,完成两个字符串变量的赋值。</p><p>另外,<code>strcpy()</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">char</span>* str;<br><span class="hljs-built_in">strcpy</span>(str, <span class="hljs-string">"hello world"</span>); <span class="hljs-comment">// 错误</span><br></code></pre></td></tr></table></figure><p>上面的代码是有问题的。<code>strcpy()</code>将字符串分配给指针变量<code>str</code>,但是<code>str</code>并没有进行初始化,指向的是一个随机的位置,因此字符串可能被复制到任意地方。</p><p>如果不用<code>strcpy()</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span>* <span class="hljs-title function_">strcpy</span><span class="hljs-params">(<span class="hljs-type">char</span>* dest, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* source)</span> {<br> <span class="hljs-type">char</span>* ptr = dest;<br> <span class="hljs-keyword">while</span> (*dest++ = *source++);<br> <span class="hljs-keyword">return</span> ptr;<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">char</span> str[<span class="hljs-number">25</span>];<br> <span class="hljs-built_in">strcpy</span>(str, <span class="hljs-string">"hello world"</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, str);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面代码中,关键的一行是<code>while (*dest++ = *source++)</code>,这是一个循环,依次将<code>source</code>的每个字符赋值给<code>dest</code>,然后移向下一个位置,直到遇到<code>\0</code>,循环判断条件不再为真,从而跳出循环。其中,<code>*dest++</code>这个表达式等同于<code>*(dest++)</code>,即先返回<code>dest</code>这个地址,再进行自增运算移向下一个位置,而<code>*dest</code>可以对当前位置赋值。</p><p><code>strcpy()</code>函数有安全风险,因为它并不检查目标字符串的长度,是否足够容纳源字符串的副本,可能导致写入溢出。如果不能保证不会发生溢出,建议使用<code>strncpy()</code>函数代替。</p><h2 id="strncpy"><a href="#strncpy" class="headerlink" title="strncpy()"></a>strncpy()</h2><p><code>strncpy()</code>跟<code>strcpy()</code>的用法完全一样,只是多了第3个参数,用来指定复制的最大字符数,防止溢出目标字符串变量的边界。</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">char</span>* <span class="hljs-title function_">strncpy</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">char</span>* dest, </span><br><span class="hljs-params"> <span class="hljs-type">char</span>* src, </span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> n</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>上面原型中,第三个参数<code>n</code>定义了复制的最大字符数。如果达到最大字符数以后,源字符串仍然没有复制完,就会停止复制,这时目的字符串结尾将没有终止符<code>\0</code>,这一点务必注意。如果源字符串的字符数小于<code>n</code>,则<code>strncpy()</code>的行为与<code>strcpy()</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-built_in">strncpy</span>(str1, str2, <span class="hljs-keyword">sizeof</span>(str1) - <span class="hljs-number">1</span>);<br>str1[<span class="hljs-keyword">sizeof</span>(str1) - <span class="hljs-number">1</span>] = <span class="hljs-string">'\0'</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,字符串<code>str2</code>复制给<code>str1</code>,但是复制长度最多为<code>str1</code>的长度减去1,<code>str1</code>剩下的最后一位用于写入字符串的结尾标志<code>\0</code>。这是因为<code>strncpy()</code>不会自己添加<code>\0</code>,如果复制的字符串片段不包含结尾标志,就需要手动添加。</p><p><code>strncpy()</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">char</span> s1[<span class="hljs-number">40</span>];<br><span class="hljs-type">char</span> s2[<span class="hljs-number">12</span>] = <span class="hljs-string">"hello world"</span>;<br><br><span class="hljs-built_in">strncpy</span>(s1, s2, <span class="hljs-number">5</span>);<br>s1[<span class="hljs-number">5</span>] = <span class="hljs-string">'\0'</span>;<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, s1); <span class="hljs-comment">// hello</span><br></code></pre></td></tr></table></figure><p>上面示例中,指定只拷贝前5个字符。</p><h2 id="strcat"><a href="#strcat" class="headerlink" title="strcat()"></a>strcat()</h2><p><code>strcat()</code>函数用于连接字符串。它接受两个字符串作为参数,把第二个字符串的副本添加到第一个字符串的末尾。这个函数会改变第一个字符串,但是第二个字符串不变。</p><p>该函数的原型定义在<code>string.h</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-type">char</span>* <span class="hljs-title function_">strcat</span><span class="hljs-params">(<span class="hljs-type">char</span>* s1, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* s2)</span>;<br></code></pre></td></tr></table></figure><p><code>strcat()</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-type">char</span> s1[<span class="hljs-number">12</span>] = <span class="hljs-string">"hello"</span>;<br><span class="hljs-type">char</span> s2[<span class="hljs-number">6</span>] = <span class="hljs-string">"world"</span>;<br><br><span class="hljs-built_in">strcat</span>(s1, s2);<br><span class="hljs-built_in">puts</span>(s1); <span class="hljs-comment">// "helloworld"</span><br></code></pre></td></tr></table></figure><p>上面示例中,调用<code>strcat()</code>以后,可以看到字符串<code>s1</code>的值变了。</p><p>注意,<code>strcat()</code>的第一个参数的长度,必须足以容纳添加第二个参数字符串。否则,拼接后的字符串会溢出第一个字符串的边界,写入相邻的内存单元,这是很危险的,建议使用下面的<code>strncat()</code>代替。</p><h2 id="strncat"><a href="#strncat" class="headerlink" title="strncat()"></a>strncat()</h2><p><code>strncat()</code>用于连接两个字符串,用法与<code>strcat()</code>完全一致,只是增加了第三个参数,指定最大添加的字符数。在添加过程中,一旦达到指定的字符数,或者在源字符串中遇到空字符<code>\0</code>,就不再添加了。它的原型定义在<code>string.h</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-type">char</span>* <span class="hljs-title function_">strncat</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">char</span>* dest,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">char</span>* src,</span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> n</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p><code>strncat()</code>返回第一个参数,即目标字符串指针。</p><p>为了保证连接后的字符串,不超过目标字符串的长度,<code>strncat()</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-built_in">strncat</span>(<br> str1, <br> str2, <br> <span class="hljs-keyword">sizeof</span>(str1) - <span class="hljs-built_in">strlen</span>(str1) - <span class="hljs-number">1</span><br>);<br></code></pre></td></tr></table></figure><p><code>strncat()</code>总是会在拼接结果的结尾,自动添加空字符<code>\0</code>,所以第三个参数的最大值,应该是<code>str1</code>的变量长度减去<code>str1</code>的字符串长度,再减去<code>1</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-type">char</span> s1[<span class="hljs-number">10</span>] = <span class="hljs-string">"Monday"</span>;<br><span class="hljs-type">char</span> s2[<span class="hljs-number">8</span>] = <span class="hljs-string">"Tuesday"</span>;<br><br><span class="hljs-built_in">strncat</span>(s1, s2, <span class="hljs-number">3</span>);<br><span class="hljs-built_in">puts</span>(s1); <span class="hljs-comment">// "MondayTue"</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>s1</code>的变量长度是10,字符长度是6,两者相减后再减去1,得到<code>3</code>,表明<code>s1</code>最多可以再添加三个字符,所以得到的结果是<code>MondayTue</code>。</p><h2 id="strcmp"><a href="#strcmp" class="headerlink" title="strcmp()"></a>strcmp()</h2><p>如果要比较两个字符串,无法直接比较,只能一个个字符进行比较,C 语言提供了<code>strcmp()</code>函数。</p><p><code>strcmp()</code>函数用于比较两个字符串的内容。该函数的原型如下,定义在<code>string.h</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-type">int</span> <span class="hljs-title function_">strcmp</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* s1, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* s2)</span>;<br></code></pre></td></tr></table></figure><p>按照字典顺序,如果两个字符串相同,返回值为<code>0</code>;如果<code>s1</code>小于<code>s2</code>,<code>strcmp()</code>返回值小于0;如果<code>s1</code>大于<code>s2</code>,返回值大于0。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// s1 = Happy New Year</span><br><span class="hljs-comment">// s2 = Happy New Year</span><br><span class="hljs-comment">// s3 = Happy Holidays</span><br><br><span class="hljs-built_in">strcmp</span>(s1, s2) <span class="hljs-comment">// 0</span><br><span class="hljs-built_in">strcmp</span>(s1, s3) <span class="hljs-comment">// 大于 0</span><br><span class="hljs-built_in">strcmp</span>(s3, s1) <span class="hljs-comment">// 小于 0</span><br></code></pre></td></tr></table></figure><p>注意,<code>strcmp()</code>只用来比较字符串,不用来比较字符。因为字符就是小整数,直接用相等运算符(<code>==</code>)就能比较。所以,不要把字符类型(<code>char</code>)的值,放入<code>strcmp()</code>当作参数。</p><h2 id="strncmp"><a href="#strncmp" class="headerlink" title="strncmp()"></a>strncmp()</h2><p>由于<code>strcmp()</code>比较的是整个字符串,C 语言又提供了<code>strncmp()</code>函数,只比较到指定的位置。</p><p>该函数增加了第三个参数,指定了比较的字符数。它的原型定义在<code>string.h</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-type">int</span> <span class="hljs-title function_">strncmp</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">char</span>* s1,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">char</span>* s2, </span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> n</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>它的返回值与<code>strcmp()</code>一样。如果两个字符串相同,返回值为<code>0</code>;如果<code>s1</code>小于<code>s2</code>,<code>strcmp()</code>返回值小于0;如果<code>s1</code>大于<code>s2</code>,返回值大于0。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> s1[<span class="hljs-number">12</span>] = <span class="hljs-string">"hello world"</span>;<br><span class="hljs-type">char</span> s2[<span class="hljs-number">12</span>] = <span class="hljs-string">"hello C"</span>;<br><br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">strncmp</span>(s1, s2, <span class="hljs-number">5</span>) == <span class="hljs-number">0</span>) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"They all have hello.\n"</span>);<br>}<br></code></pre></td></tr></table></figure><p>上面示例只比较两个字符串的前5个字符。</p><h2 id="sprintf-,snprintf"><a href="#sprintf-,snprintf" class="headerlink" title="sprintf(),snprintf()"></a>sprintf(),snprintf()</h2><p><code>sprintf()</code>函数跟<code>printf()</code>类似,但是用于将数据写入字符串,而不是输出到显示器。该函数的原型定义在<code>stdio.h</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-type">int</span> <span class="hljs-title function_">sprintf</span><span class="hljs-params">(<span class="hljs-type">char</span>* s, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* format, ...)</span>;<br></code></pre></td></tr></table></figure><p><code>sprintf()</code>的第一个参数是字符串指针变量,其余参数和<code>printf()</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">char</span> first[<span class="hljs-number">6</span>] = <span class="hljs-string">"hello"</span>;<br><span class="hljs-type">char</span> last[<span class="hljs-number">6</span>] = <span class="hljs-string">"world"</span>;<br><span class="hljs-type">char</span> s[<span class="hljs-number">40</span>];<br><br><span class="hljs-built_in">sprintf</span>(s, <span class="hljs-string">"%s %s"</span>, first, last);<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, s); <span class="hljs-comment">// hello world</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>sprintf()</code>将输出内容组合成“hello world”,然后放入了变量<code>s</code>。</p><p><code>sprintf()</code>的返回值是写入变量的字符数量(不计入尾部的空字符<code>\0</code>)。如果遇到错误,返回负值。</p><p><code>sprintf()</code>有严重的安全风险,如果写入的字符串过长,超过了目标字符串的长度,<code>sprintf()</code>依然会将其写入,导致发生溢出。为了控制写入的字符串的长度,C 语言又提供了另一个函数<code>snprintf()</code>。</p><p><code>snprintf()</code>只比<code>sprintf()</code>多了一个参数<code>n</code>,用来控制写入变量的字符串不超过<code>n - 1</code>个字符,剩下一个位置写入空字符<code>\0</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-type">int</span> <span class="hljs-title function_">snprintf</span><span class="hljs-params">(<span class="hljs-type">char</span>*s, <span class="hljs-type">size_t</span> n, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* format, ...)</span>;<br></code></pre></td></tr></table></figure><p><code>snprintf()</code>总是会自动写入字符串结尾的空字符。如果你尝试写入的字符数超过指定的最大字符数,<code>snprintf()</code>会写入 n - 1 个字符,留出最后一个位置写入空字符。</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-built_in">snprintf</span>(s, <span class="hljs-number">12</span>, <span class="hljs-string">"%s %s"</span>, <span class="hljs-string">"hello"</span>, <span class="hljs-string">"world"</span>);<br></code></pre></td></tr></table></figure><p>上面的例子中,<code>snprintf()</code>的第二个参数是12,表示写入字符串的最大长度不超过12(包括尾部的空字符)。</p><p><code>snprintf()</code>的返回值是写入格式字符串的字符数量(不计入尾部的空字符<code>\0</code>)。如果<code>n</code>足够大,返回值应该小于<code>n</code>,但是有时候格式字符串的长度可能大于<code>n</code>,那么这时返回值会大于<code>n</code>,但实际上真正写入变量的还是<code>n-1</code>个字符。如果遇到错误,返回一个负值。因此,返回值只有在非负并且小于<code>n</code>时,才能确认完整的格式字符串写入了变量。</p><h2 id="字符串数组"><a href="#字符串数组" class="headerlink" title="字符串数组"></a>字符串数组</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><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-type">char</span> weekdays[<span class="hljs-number">7</span>][<span class="hljs-number">10</span>] = {<br> <span class="hljs-string">"Monday"</span>,<br> <span class="hljs-string">"Tuesday"</span>,<br> <span class="hljs-string">"Wednesday"</span>,<br> <span class="hljs-string">"Thursday"</span>,<br> <span class="hljs-string">"Friday"</span>,<br> <span class="hljs-string">"Saturday"</span>,<br> <span class="hljs-string">"Sunday"</span><br>};<br></code></pre></td></tr></table></figure><p>上面示例就是一个字符串数组,一共包含7个字符串,所以第一维的长度是7。其中,最长的字符串的长度是10(含结尾的终止符<code>\0</code>),所以第二维的长度统一设为10。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> weekdays[][<span class="hljs-number">10</span>] = {<br> <span class="hljs-string">"Monday"</span>,<br> <span class="hljs-string">"Tuesday"</span>,<br> <span class="hljs-string">"Wednesday"</span>,<br> <span class="hljs-string">"Thursday"</span>,<br> <span class="hljs-string">"Friday"</span>,<br> <span class="hljs-string">"Saturday"</span>,<br> <span class="hljs-string">"Sunday"</span><br>};<br></code></pre></td></tr></table></figure><p>上面示例中,二维数组第一维的长度,可以由编译器根据后面的赋值,自动计算,所以可以不写。</p><p>数组的第二维,长度统一定为10,有点浪费空间,因为大多数成员的长度都小于10。解决方法就是把数组的第二维,从字符数组改成字符指针。</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-type">char</span>* weekdays[] = {<br> <span class="hljs-string">"Monday"</span>,<br> <span class="hljs-string">"Tuesday"</span>,<br> <span class="hljs-string">"Wednesday"</span>,<br> <span class="hljs-string">"Thursday"</span>,<br> <span class="hljs-string">"Friday"</span>,<br> <span class="hljs-string">"Saturday"</span>,<br> <span class="hljs-string">"Sunday"</span><br>};<br></code></pre></td></tr></table></figure><p>上面的字符串数组,其实是一个一维数组,成员就是7个字符指针,每个指针指向一个字符串(字符数组)。</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-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">7</span>; i++) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, weekdays[i]);<br>}<br></code></pre></td></tr></table></figure><h1 id="C-语言的内存管理"><a href="#C-语言的内存管理" class="headerlink" title="C 语言的内存管理"></a>C 语言的内存管理</h1><h2 id="简介-5"><a href="#简介-5" class="headerlink" title="简介"></a>简介</h2><p>C 语言的内存管理,分成两部分。一部分是系统管理的,另一部分是用户手动管理的。</p><p>系统管理的内存,主要是函数内部的变量(局部变量)。这部分变量在函数运行时进入内存,函数运行结束后自动从内存卸载。这些变量存放的区域称为”栈“(stack),”栈“所在的内存是系统自动管理的。</p><p>用户手动管理的内存,主要是程序运行的整个过程中都存在的变量(全局变量),这些变量需要用户手动从内存释放。如果使用后忘记释放,它就一直占用内存,直到程序退出,这种情况称为”内存泄漏“(memory leak)。这些变量所在的内存称为”堆“(heap),”堆“所在的内存是用户手动管理的。</p><h2 id="void-指针"><a href="#void-指针" class="headerlink" title="void 指针"></a>void 指针</h2><p>前面章节已经说过了,每一块内存都有地址,通过指针变量可以获取指定地址的内存块。指针变量必须有类型,否则编译器无法知道,如何解读内存块保存的二进制数据。但是,向系统请求内存的时候,有时不确定会有什么样的数据写入内存,需要先获得内存块,稍后再确定写入的数据类型。</p><p>为了满足这种需求,C 语言提供了一种不定类型的指针,叫做 void 指针。它只有内存块的地址信息,没有类型信息,等到使用该块内存的时候,再向编译器补充说明,里面的数据类型是什么。</p><p>另一方面,void 指针等同于无类型指针,可以指向任意类型的数据,但是不能解读数据。void 指针与其他所有类型指针之间是互相转换关系,任一类型的指针都可以转为 void 指针,而 void 指针也可以转为任一类型的指针。</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> x = <span class="hljs-number">10</span>;<br><br><span class="hljs-type">void</span>* p = &x; <span class="hljs-comment">// 整数指针转为 void 指针</span><br><span class="hljs-type">int</span>* q = p; <span class="hljs-comment">// void 指针转为整数指针</span><br></code></pre></td></tr></table></figure><p>上面示例演示了,整数指针和 void 指针如何互相转换。<code>&x</code>是一个整数指针,<code>p</code>是 void 指针,赋值时<code>&x</code>的地址会自动解释为 void 类型。同样的,<code>p</code>再赋值给整数指针<code>q</code>时,<code>p</code>的地址会自动解释为整数指针。</p><p>注意,由于不知道 void 指针指向什么类型的值,所以不能用<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">char</span> a = <span class="hljs-string">'X'</span>;<br><span class="hljs-type">void</span>* p = &a;<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%c\n"</span>, *p); <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>p</code>是一个 void 指针,所以这时无法用<code>*p</code>取出指针指向的值。</p><p>void 指针的重要之处在于,很多内存相关函数的返回值就是 void 指针,只给出内存块的地址信息,所以放在最前面进行介绍。</p><h2 id="malloc"><a href="#malloc" class="headerlink" title="malloc()"></a>malloc()</h2><p><code>malloc()</code>函数用于分配内存,该函数向系统要求一段内存,系统就在“堆”里面分配一段连续的内存块给它。它的原型定义在头文件<code>stdlib.h</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-type">void</span>* <span class="hljs-title function_">malloc</span><span class="hljs-params">(<span class="hljs-type">size_t</span> size)</span><br></code></pre></td></tr></table></figure><p>它接受一个非负整数作为参数,表示所要分配的内存字节数,返回一个 void 指针,指向分配好的内存块。这是非常合理的,因为<code>malloc()</code>函数不知道,将要存储在该块内存的数据是什么类型,所以只能返回一个无类型的 void 指针。</p><p>可以使用<code>malloc()</code>为任意类型的数据分配内存,常见的做法是先使用<code>sizeof()</code>函数,算出某种数据类型所需的字节长度,然后再将这个长度传给<code>malloc()</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>* p = <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br><br>*p = <span class="hljs-number">12</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, *p); <span class="hljs-comment">// 12</span><br></code></pre></td></tr></table></figure><p>上面示例中,先为整数类型分配一段内存,然后将整数<code>12</code>放入这段内存里面。这个例子其实不需要使用<code>malloc()</code>,因为 C 语言会自动为整数(本例是<code>12</code>)提供内存。</p><p>有时候为了增加代码的可读性,可以对<code>malloc()</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-type">int</span>* p = (<span class="hljs-type">int</span>*) <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br></code></pre></td></tr></table></figure><p>上面代码将<code>malloc()</code>返回的 void 指针,强制转换成了整数指针。</p><p>由于<code>sizeof()</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-type">int</span>* p = (<span class="hljs-type">int</span>*) <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(*p));<br></code></pre></td></tr></table></figure><p><code>malloc()</code>分配内存有可能分配失败,这时返回常量 NULL。Null 的值为0,是一个无法读写的内存地址,可以理解成一个不指向任何地方的指针。它在包括<code>stdlib.h</code>等多个头文件里面都有定义,所以只要可以使用<code>malloc()</code>,就可以使用<code>NULL</code>。由于存在分配失败的可能,所以最好在使用<code>malloc()</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-type">int</span>* p = <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br><br><span class="hljs-keyword">if</span> (p == <span class="hljs-literal">NULL</span>) {<br> <span class="hljs-comment">// 内存分配失败</span><br>}<br><br><span class="hljs-comment">// or</span><br><span class="hljs-keyword">if</span> (!p) {<br> <span class="hljs-comment">//...</span><br>}<br></code></pre></td></tr></table></figure><p>上面示例中,通过判断返回的指针<code>p</code>是否为<code>NULL</code>,确定<code>malloc()</code>是否分配成功。</p><p><code>malloc()</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>* p = (<span class="hljs-type">int</span>*) <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>) * <span class="hljs-number">10</span>);<br><br><span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++)<br> p[i] = i * <span class="hljs-number">5</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>p</code>是一个整数指针,指向一段可以放置10个整数的内存,所以可以用作数组。</p><p><code>malloc()</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-type">int</span>* p = (<span class="hljs-type">int</span>*) <span class="hljs-built_in">malloc</span>(n * <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br></code></pre></td></tr></table></figure><p>上面示例中,<code>malloc()</code>可以根据变量<code>n</code>的不同,动态为数组分配不同的大小。</p><p>注意,<code>malloc()</code>不会对所分配的内存进行初始化,里面还保存着原来的值。如果没有初始化,就使用这段内存,可能从里面读到以前的值。程序员要自己负责初始化,比如,字符串初始化可以使用<code>strcpy()</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-type">char</span>* p = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">4</span>);<br><span class="hljs-built_in">strcpy</span>(p, <span class="hljs-string">"abc"</span>);<br><br><span class="hljs-comment">// or</span><br>p = <span class="hljs-string">"abc"</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,字符指针<code>p</code>指向一段4个字节的内存,<code>strcpy()</code>将字符串“abc”拷贝放入这段内存,完成了这段内存的初始化。</p><h2 id="free"><a href="#free" class="headerlink" title="free()"></a>free()</h2><p><code>free()</code>用于释放<code>malloc()</code>函数分配的内存,将这块内存还给系统以便重新使用,否则这个内存块会一直占用到程序运行结束。该函数的原型定义在头文件<code>stdlib.h</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-type">void</span> <span class="hljs-title function_">free</span><span class="hljs-params">(<span class="hljs-type">void</span>* block)</span><br></code></pre></td></tr></table></figure><p>上面代码中,<code>free()</code>的参数是<code>malloc()</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>* p = (<span class="hljs-type">int</span>*) <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br><br>*p = <span class="hljs-number">12</span>;<br><span class="hljs-built_in">free</span>(p);<br></code></pre></td></tr></table></figure><p>注意,分配的内存块一旦释放,就不应该再次操作已经释放的地址,也不应该再次使用<code>free()</code>对该地址释放第二次。</p><p>一个很常见的错误是,在函数内部分配了内存,但是函数调用结束时,没有使用<code>free()</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">void</span> <span class="hljs-title function_">gobble</span><span class="hljs-params">(<span class="hljs-type">double</span> arr[], <span class="hljs-type">int</span> n)</span> {<br> <span class="hljs-type">double</span>* temp = (<span class="hljs-type">double</span>*) <span class="hljs-built_in">malloc</span>(n * <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">double</span>));<br> <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>gobble()</code>内部分配了内存,但是没有写<code>free(temp)</code>。这会造成函数运行结束后,占用的内存块依然保留,如果多次调用<code>gobble()</code>,就会留下多个内存块。并且,由于指针<code>temp</code>已经消失了,也无法访问这些内存块,再次使用。</p><h2 id="calloc"><a href="#calloc" class="headerlink" title="calloc()"></a>calloc()</h2><p><code>calloc()</code>函数的作用与<code>malloc()</code>相似,也是分配内存块。该函数的原型定义在头文件<code>stdlib.h</code>。</p><p>两者的区别主要有两点:</p><p>(1)<code>calloc()</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-type">void</span>* <span class="hljs-title function_">calloc</span><span class="hljs-params">(<span class="hljs-type">size_t</span> n, <span class="hljs-type">size_t</span> size)</span>;<br></code></pre></td></tr></table></figure><p><code>calloc()</code>的返回值也是一个 void 指针。分配失败时,返回 NULL。</p><p>(2)<code>calloc()</code>会将所分配的内存全部初始化为<code>0</code>。<code>malloc()</code>不会对内存进行初始化,如果想要初始化为<code>0</code>,还要额外调用<code>memset()</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-type">int</span>* p = <span class="hljs-built_in">calloc</span>(<span class="hljs-number">10</span>, <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span>* p = <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>) * <span class="hljs-number">10</span>);<br><span class="hljs-built_in">memset</span>(p, <span class="hljs-number">0</span>, <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>) * <span class="hljs-number">10</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>calloc()</code>相当于<code>malloc() + memset()</code>。</p><p><code>calloc()</code>分配的内存块,也要使用<code>free()</code>释放。</p><h2 id="realloc"><a href="#realloc" class="headerlink" title="realloc()"></a>realloc()</h2><p><code>realloc()</code>函数用于修改已经分配的内存块的大小,可以放大也可以缩小,返回一个指向新的内存块的指针。如果分配不成功,返回 NULL。该函数的原型定义在头文件<code>stdlib.h</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-type">void</span>* <span class="hljs-title function_">realloc</span><span class="hljs-params">(<span class="hljs-type">void</span>* block, <span class="hljs-type">size_t</span> size)</span><br></code></pre></td></tr></table></figure><p>它接受两个参数。</p><ul><li><code>block</code>:已经分配好的内存块指针(由<code>malloc()</code>或<code>calloc()</code>或<code>realloc()</code>产生)。</li><li><code>size</code>:该内存块的新大小,单位为字节。</li></ul><p><code>realloc()</code>可能返回一个全新的地址(数据也会自动复制过去),也可能返回跟原来一样的地址。<code>realloc()</code>优先在原有内存块上进行缩减,尽量不移动数据,所以通常是返回原先的地址。如果新内存块小于原来的大小,则丢弃超出的部分;如果大于原来的大小,则不对新增的部分进行初始化(程序员可以自动调用<code>memset()</code>)。</p><p>下面是一个例子,<code>b</code>是数组指针,<code>realloc()</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>* b;<br><br>b = <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>) * <span class="hljs-number">10</span>);<br>b = <span class="hljs-built_in">realloc</span>(b, <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>) * <span class="hljs-number">2000</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,指针<code>b</code>原来指向10个成员的整数数组,使用<code>realloc()</code>调整为2000个成员的数组。这就是手动分配数组内存的好处,可以在运行时随时调整数组的长度。</p><p><code>realloc()</code>的第一个参数可以是 NULL,这时就相当于新建一个指针。</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">char</span>* p = <span class="hljs-built_in">realloc</span>(<span class="hljs-literal">NULL</span>, <span class="hljs-number">3490</span>);<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">char</span>* p = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">3490</span>);<br></code></pre></td></tr></table></figure><p>如果<code>realloc()</code>的第二个参数是<code>0</code>,就会释放掉内存块。</p><p>由于有分配失败的可能,所以调用<code>realloc()</code>以后,最好检查一下它的返回值是否为 NULL。分配失败时,原有内存块中的数据不会发生改变。</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">float</span>* new_p = <span class="hljs-built_in">realloc</span>(p, <span class="hljs-keyword">sizeof</span>(*p * <span class="hljs-number">40</span>));<br><br><span class="hljs-keyword">if</span> (new_p == <span class="hljs-literal">NULL</span>) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Error reallocing\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></table></figure><p>注意,<code>realloc()</code>不会对内存块进行初始化。</p><h2 id="restrict-说明符"><a href="#restrict-说明符" class="headerlink" title="restrict 说明符"></a>restrict 说明符</h2><p>声明指针变量时,可以使用<code>restrict</code>说明符,告诉编译器,该块内存区域只有当前指针一种访问方式,其他指针不能读写该块内存。这种指针称为“受限指针”(restrict pointer)。</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>* <span class="hljs-keyword">restrict</span> p;<br>p = <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br></code></pre></td></tr></table></figure><p>上面示例中,声明指针变量<code>p</code>时,加入了<code>restrict</code>说明符,使得<code>p</code>变成了受限指针。后面,当<code>p</code>指向<code>malloc()</code>函数返回的一块内存区域,就味着,该区域只有通过<code>p</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-type">int</span>* <span class="hljs-keyword">restrict</span> p;<br>p = <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br><br><span class="hljs-type">int</span>* q = p;<br>*q = <span class="hljs-number">0</span>; <span class="hljs-comment">// 未定义行为</span><br></code></pre></td></tr></table></figure><p>上面示例中,另一个指针<code>q</code>与受限指针<code>p</code>指向同一块内存,现在该内存有<code>p</code>和<code>q</code>两种访问方式。这就违反了对编译器的承诺,后面通过<code>*q</code>对该内存区域赋值,会导致未定义行为。</p><h2 id="memcpy"><a href="#memcpy" class="headerlink" title="memcpy()"></a>memcpy()</h2><p><code>memcpy()</code>用于将一块内存拷贝到另一块内存。该函数的原型定义在头文件<code>string.h</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-type">void</span>* <span class="hljs-title function_">memcpy</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">void</span>* <span class="hljs-keyword">restrict</span> dest, </span><br><span class="hljs-params"> <span class="hljs-type">void</span>* <span class="hljs-keyword">restrict</span> source, </span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> n</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>上面代码中,<code>dest</code>是目标地址,<code>source</code>是源地址,第三个参数<code>n</code>是要拷贝的字节数<code>n</code>。如果要拷贝10个 double 类型的数组成员,<code>n</code>就等于<code>10 * sizeof(double)</code>,而不是<code>10</code>。该函数会将从<code>source</code>开始的<code>n</code>个字节,拷贝到<code>dest</code>。</p><p><code>dest</code>和<code>source</code>都是 void 指针,表示这里不限制指针类型,各种类型的内存数据都可以拷贝。两者都有 restrict 关键字,表示这两个内存块不应该有互相重叠的区域。</p><p><code>memcpy()</code>的返回值是第一个参数,即目标地址的指针。</p><p>因为<code>memcpy()</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><string.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">char</span> s[] = <span class="hljs-string">"Goats!"</span>;<br> <span class="hljs-type">char</span> t[<span class="hljs-number">100</span>];<br><br> <span class="hljs-built_in">memcpy</span>(t, s, <span class="hljs-keyword">sizeof</span>(s)); <span class="hljs-comment">// 拷贝7个字节,包括终止符</span><br><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, t); <span class="hljs-comment">// "Goats!"</span><br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,字符串<code>s</code>所在的内存,被拷贝到字符数组<code>t</code>所在的内存。</p><p><code>memcpy()</code>可以取代<code>strcpy()</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><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-type">char</span>* s = <span class="hljs-string">"hello world"</span>;<br><br><span class="hljs-type">size_t</span> len = <span class="hljs-built_in">strlen</span>(s) + <span class="hljs-number">1</span>;<br><span class="hljs-type">char</span> *c = <span class="hljs-built_in">malloc</span>(len);<br><br><span class="hljs-keyword">if</span> (c) {<br> <span class="hljs-comment">// strcpy() 的写法</span><br> <span class="hljs-built_in">strcpy</span>(c, s);<br><br> <span class="hljs-comment">// memcpy() 的写法</span><br> <span class="hljs-built_in">memcpy</span>(c, s, len);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,两种写法的效果完全一样,但是<code>memcpy()</code>的写法要好于<code>strcpy()</code>。</p><p>使用 void 指针,也可以自定义一个复制内存的函数。</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-type">void</span>* <span class="hljs-title function_">my_memcpy</span><span class="hljs-params">(<span class="hljs-type">void</span>* dest, <span class="hljs-type">void</span>* src, <span class="hljs-type">int</span> byte_count)</span> {<br> <span class="hljs-type">char</span>* s = src;<br> <span class="hljs-type">char</span>* d = dest;<br><br> <span class="hljs-keyword">while</span> (byte_count--) {<br> *d++ = *s++;<br> }<br><br> <span class="hljs-keyword">return</span> dest;<br><br>}<br></code></pre></td></tr></table></figure><p>上面示例中,不管传入的<code>dest</code>和<code>src</code>是什么类型的指针,将它们重新定义成一字节的 Char 指针,这样就可以逐字节进行复制。<code>*d++ = *s++</code>语句相当于先执行<code>*d = *s</code>(源字节的值复制给目标字节),然后各自移动到下一个字节。最后,返回复制后的<code>dest</code>指针,便于后续使用。</p><h2 id="memmove"><a href="#memmove" class="headerlink" title="memmove()"></a>memmove()</h2><p><code>memmove()</code>函数用于将一段内存数据复制到另一段内存。它跟<code>memcpy()</code>的主要区别是,它允许目标区域与源区域有重叠。如果发生重叠,源区域的内容会被更改;如果没有重叠,它与<code>memcpy()</code>行为相同。</p><p>该函数的原型定义在头文件<code>string.h</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-type">void</span>* <span class="hljs-title function_">memmove</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">void</span>* dest, </span><br><span class="hljs-params"> <span class="hljs-type">void</span>* source, </span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> n</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>上面代码中,<code>dest</code>是目标地址,<code>source</code>是源地址,<code>n</code>是要移动的字节数。<code>dest</code>和<code>source</code>都是 void 指针,表示可以移动任何类型的内存数据,两个内存区域可以有重叠。</p><p><code>memmove()</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> a[<span class="hljs-number">100</span>];<br><span class="hljs-comment">// ...</span><br><br>memmove(&a[<span class="hljs-number">0</span>], &a[<span class="hljs-number">1</span>], <span class="hljs-number">99</span> * <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br></code></pre></td></tr></table></figure><p>上面示例中,从数组成员<code>a[1]</code>开始的99个成员,都向前移动一个位置。</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">char</span> x[] = <span class="hljs-string">"Home Sweet Home"</span>;<br><br><span class="hljs-comment">// 输出 Sweet Home Home</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, (<span class="hljs-type">char</span> *) memmove(x, &x[<span class="hljs-number">5</span>], <span class="hljs-number">10</span>));<br></code></pre></td></tr></table></figure><p>上面示例中,从字符串<code>x</code>的5号位置开始的10个字节,就是“Sweet Home”,<code>memmove()</code>将其前移到0号位置,所以<code>x</code>就变成了“Sweet Home Home”。</p><h2 id="memcmp"><a href="#memcmp" class="headerlink" title="memcmp()"></a>memcmp()</h2><p><code>memcmp()</code>函数用来比较两个内存区域。它的原型定义在<code>string.h</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-type">int</span> <span class="hljs-title function_">memcmp</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">void</span>* s1,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">void</span>* s2,</span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> n</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>它接受三个参数,前两个参数是用来比较的指针,第三个参数指定比较的字节数。</p><p>它的返回值是一个整数。两块内存区域的每个字节以字符形式解读,按照字典顺序进行比较,如果两者相同,返回<code>0</code>;如果<code>s1</code>大于<code>s2</code>,返回大于0的整数;如果<code>s1</code>小于<code>s2</code>,返回小于0的整数。</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">char</span>* s1 = <span class="hljs-string">"abc"</span>;<br><span class="hljs-type">char</span>* s2 = <span class="hljs-string">"acd"</span>;<br><span class="hljs-type">int</span> r = <span class="hljs-built_in">memcmp</span>(s1, s2, <span class="hljs-number">3</span>); <span class="hljs-comment">// 小于 0</span><br></code></pre></td></tr></table></figure><p>上面示例比较<code>s1</code>和<code>s2</code>的前三个字节,由于<code>s1</code>小于<code>s2</code>,所以<code>r</code>是一个小于0的整数,一般为-1。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> s1[] = {<span class="hljs-string">'b'</span>, <span class="hljs-string">'i'</span>, <span class="hljs-string">'g'</span>, <span class="hljs-string">'\0'</span>, <span class="hljs-string">'c'</span>, <span class="hljs-string">'a'</span>, <span class="hljs-string">'r'</span>};<br><span class="hljs-type">char</span> s2[] = {<span class="hljs-string">'b'</span>, <span class="hljs-string">'i'</span>, <span class="hljs-string">'g'</span>, <span class="hljs-string">'\0'</span>, <span class="hljs-string">'c'</span>, <span class="hljs-string">'a'</span>, <span class="hljs-string">'t'</span>};<br><br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">memcmp</span>(s1, s2, <span class="hljs-number">3</span>) == <span class="hljs-number">0</span>) <span class="hljs-comment">// true</span><br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">memcmp</span>(s1, s2, <span class="hljs-number">4</span>) == <span class="hljs-number">0</span>) <span class="hljs-comment">// true</span><br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">memcmp</span>(s1, s2, <span class="hljs-number">7</span>) == <span class="hljs-number">0</span>) <span class="hljs-comment">// false</span><br></code></pre></td></tr></table></figure><p>上面示例展示了,<code>memcmp()</code>可以比较内部带有字符串终止符<code>\0</code>的内存区域。</p><h1 id="struct-结构"><a href="#struct-结构" class="headerlink" title="struct 结构"></a>struct 结构</h1><h2 id="简介-6"><a href="#简介-6" class="headerlink" title="简介"></a>简介</h2><p>C 语言内置的数据类型,除了最基本的几种原始类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用中并不够用。</p><p>实际使用中,主要有下面两种情况,需要更灵活强大的复合类型。</p><ul><li>复杂的物体需要使用多个变量描述,这些变量都是相关的,最好有某种机制将它们联系起来。</li><li>某些函数需要传入多个参数,如果一个个按照顺序传入,非常麻烦,最好能组合成一个复合结构传入。</li></ul><p>为了解决这些问题,C 语言提供了<code>struct</code>关键字,允许自定义复合数据类型,将不同类型的值组合在一起。这样不仅为编程提供方便,也有利于增强代码的可读性。C 语言没有其他语言的对象(object)和类(class)的概念,struct 结构很大程度上提供了对象和类的功能。</p><p>下面是<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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">fraction</span> {</span><br> <span class="hljs-type">int</span> numerator;<br> <span class="hljs-type">int</span> denominator;<br>};<br></code></pre></td></tr></table></figure><p>上面示例定义了一个分数的数据类型<code>struct fraction</code>,包含两个属性<code>numerator</code>和<code>denominator</code>。</p><p>注意,作为一个自定义的数据类型,它的类型名要包括<code>struct</code>关键字,比如上例是<code>struct fraction</code>,单独的<code>fraction</code>没有任何意义,甚至脚本还可以另外定义名为<code>fraction</code>的变量,虽然这样很容易造成混淆。另外,<code>struct</code>语句结尾的分号不能省略,否则很容易产生错误。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">fraction</span> <span class="hljs-title">f1</span>;</span><br><br>f1.numerator = <span class="hljs-number">22</span>;<br>f1.denominator = <span class="hljs-number">7</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,先声明了一个<code>struct fraction</code>类型的变量<code>f1</code>,这时编译器就会为<code>f1</code>分配内存,接着就可以为<code>f1</code>的不同属性赋值。可以看到,struct 结构的属性通过点(<code>.</code>)来表示,比如<code>numerator</code>属性要写成<code>f1.numerator</code>。</p><p>再提醒一下,声明自定义类型的变量时,类型名前面,不要忘记加上<code>struct</code>关键字。也就是说,必须使用<code>struct fraction f1</code>声明变量,不能写成<code>fraction f1</code>。</p><p>除了逐一对属性赋值,也可以使用大括号,一次性对 struct 结构的所有属性赋值。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">car</span> {</span><br> <span class="hljs-type">char</span>* name;<br> <span class="hljs-type">float</span> price;<br> <span class="hljs-type">int</span> speed;<br>};<br><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">car</span> <span class="hljs-title">saturn</span> =</span> {<span class="hljs-string">"Saturn SL/2"</span>, <span class="hljs-number">16000.99</span>, <span class="hljs-number">175</span>};<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>saturn</code>是<code>struct car</code>类型,大括号里面同时对它的三个属性赋值。如果大括号里面的值的数量,少于属性的数量,那么缺失的属性自动初始化为<code>0</code>。</p><p>注意,大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。否则,必须为每个值指定属性名。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">car</span> <span class="hljs-title">saturn</span> =</span> {.speed=<span class="hljs-number">172</span>, .name=<span class="hljs-string">"Saturn SL/2"</span>};<br></code></pre></td></tr></table></figure><p>上面示例中,初始化的属性少于声明时的属性,这时剩下的那些属性都会初始化为<code>0</code>。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">car</span> <span class="hljs-title">saturn</span> =</span> {.speed=<span class="hljs-number">172</span>, .name=<span class="hljs-string">"Saturn SL/2"</span>};<br>saturn.speed = <span class="hljs-number">168</span>;<br></code></pre></td></tr></table></figure><p>上面示例将<code>speed</code>属性的值改成<code>168</code>。</p><p>struct 的数据类型声明语句与变量的声明语句,可以合并为一个语句。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">book</span> {</span><br> <span class="hljs-type">char</span> title[<span class="hljs-number">500</span>];<br> <span class="hljs-type">char</span> author[<span class="hljs-number">100</span>];<br> <span class="hljs-type">float</span> value;<br>} b1;<br></code></pre></td></tr></table></figure><p>上面的语句同时声明了数据类型<code>book</code>和该类型的变量<code>b1</code>。如果类型标识符<code>book</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-class"><span class="hljs-keyword">struct</span> {</span><br> <span class="hljs-type">char</span> title[<span class="hljs-number">500</span>];<br> <span class="hljs-type">char</span> author[<span class="hljs-number">100</span>];<br> <span class="hljs-type">float</span> value;<br>} b1;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>struct</code>声明了一个匿名数据类型,然后又声明了这个类型的变量<code>b1</code>。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> {</span><br> <span class="hljs-type">char</span> title[<span class="hljs-number">500</span>];<br> <span class="hljs-type">char</span> author[<span class="hljs-number">100</span>];<br> <span class="hljs-type">float</span> value;<br>} b1 = {<span class="hljs-string">"Harry Potter"</span>, <span class="hljs-string">"J. K. Rowling"</span>, <span class="hljs-number">10.0</span>},<br> b2 = {<span class="hljs-string">"Cancer Ward"</span>, <span class="hljs-string">"Aleksandr Solzhenitsyn"</span>, <span class="hljs-number">7.85</span>};<br></code></pre></td></tr></table></figure><p>上面示例中,在声明变量<code>b1</code>和<code>b2</code>的同时,为它们赋值。</p><p>下一章介绍的<code>typedef</code>命令可以为 struct 结构指定一个别名,这样使用起来更简洁。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">cell_phone</span> {</span><br> <span class="hljs-type">int</span> cell_no;<br> <span class="hljs-type">float</span> minutes_of_charge;<br>} phone;<br><br>phone p = {<span class="hljs-number">5551234</span>, <span class="hljs-number">5</span>};<br></code></pre></td></tr></table></figure><p>上面示例中,<code>phone</code>就是<code>struct cell_phone</code>的别名。</p><p>指针变量也可以指向<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><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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">book</span> {</span><br> <span class="hljs-type">char</span> title[<span class="hljs-number">500</span>];<br> <span class="hljs-type">char</span> author[<span class="hljs-number">100</span>];<br> <span class="hljs-type">float</span> value;<br>}* b1;<br><br><span class="hljs-comment">// 或者写成两个语句</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">book</span> {</span><br> <span class="hljs-type">char</span> title[<span class="hljs-number">500</span>];<br> <span class="hljs-type">char</span> author[<span class="hljs-number">100</span>];<br> <span class="hljs-type">float</span> value;<br>};<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">book</span>* <span class="hljs-title">b1</span>;</span><br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>b1</code>是一个指针,指向的数据是<code>struct book</code>类型的实例。</p><p>struct 结构也可以作为数组成员。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">fraction</span> <span class="hljs-title">numbers</span>[1000];</span><br><br>numbers[<span class="hljs-number">0</span>].numerator = <span class="hljs-number">22</span>;<br>numbers[<span class="hljs-number">0</span>].denominator = <span class="hljs-number">7</span>;<br></code></pre></td></tr></table></figure><p>上面示例声明了一个有1000个成员的数组<code>numbers</code>,每个成员都是自定义类型<code>fraction</code>的实例。</p><p>struct 结构占用的存储空间,不是各个属性存储空间的总和,而是最大内存占用属性的存储空间的倍数,其他属性会添加空位与之对齐。这样可以提高读写效率。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">foo</span> {</span><br> <span class="hljs-type">int</span> a;<br> <span class="hljs-type">char</span>* b;<br> <span class="hljs-type">char</span> c;<br>};<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> foo)); <span class="hljs-comment">// 24</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>struct foo</code>有三个属性,在64位计算机上占用的存储空间分别是:<code>int a</code>占4个字节,指针<code>char* b</code>占8个字节,<code>char c</code>占1个字节。它们加起来,一共是13个字节(4 + 8 + 1)。但是实际上,<code>struct foo</code>会占用24个字节,原因是它最大的内存占用属性是<code>char* b</code>的8个字节,导致其他属性的存储空间也是8个字节,这样才可以对齐,导致整个<code>struct foo</code>就是24个字节(8 * 3)。</p><p>多出来的存储空间,都采用空位填充,所以上面的<code>struct foo</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">foo</span> {</span><br> <span class="hljs-type">int</span> a; <span class="hljs-comment">// 4</span><br> <span class="hljs-type">char</span> pad1[<span class="hljs-number">4</span>]; <span class="hljs-comment">// 填充4字节</span><br> <span class="hljs-type">char</span> *b; <span class="hljs-comment">// 8</span><br> <span class="hljs-type">char</span> c; <span class="hljs-comment">// 1</span><br> <span class="hljs-type">char</span> pad2[<span class="hljs-number">7</span>]; <span class="hljs-comment">// 填充7字节</span><br>};<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> foo)); <span class="hljs-comment">// 24</span><br></code></pre></td></tr></table></figure><p>为什么浪费这么多空间进行内存对齐呢?这是为了加快读写速度,把内存占用划分成等长的区块,就可以快速在 Struct 结构体中定位到每个属性的起始地址。</p><p>由于这个特性,在有必要的情况下,定义 Struct 结构体时,可以采用存储空间递增的顺序,定义每个属性,这样就能节省一些空间。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">foo</span> {</span><br> <span class="hljs-type">char</span> c;<br> <span class="hljs-type">int</span> a;<br> <span class="hljs-type">char</span>* b;<br>};<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> foo)); <span class="hljs-comment">// 16</span><br></code></pre></td></tr></table></figure><p>上面示例中,占用空间最小的<code>char c</code>排在第一位,其次是<code>int a</code>,占用空间最大的<code>char* b</code>排在最后。整个<code>strct foo</code>的内存占用就从24字节下降到16字节。</p><h2 id="struct-的复制"><a href="#struct-的复制" class="headerlink" title="struct 的复制"></a>struct 的复制</h2><p>struct 变量可以使用赋值运算符(<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><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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">cat</span> {</span> <span class="hljs-type">char</span> name[<span class="hljs-number">30</span>]; <span class="hljs-type">short</span> age; } a, b;<br><br><span class="hljs-built_in">strcpy</span>(a.name, <span class="hljs-string">"Hula"</span>);<br>a.age = <span class="hljs-number">3</span>;<br><br>b = a;<br>b.name[<span class="hljs-number">0</span>] = <span class="hljs-string">'M'</span>;<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, a.name); <span class="hljs-comment">// Hula</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, b.name); <span class="hljs-comment">// Mula</span><br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>b</code>是变量<code>a</code>的副本,两个变量的值是各自独立的,修改掉<code>b.name</code>不影响<code>a.name</code>。</p><p>上面这个示例是有前提的,就是 struct 结构的属性必须定义成字符数组,才能复制数据。如果稍作修改,属性定义成字符指针,结果就不一样。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">cat</span> {</span> <span class="hljs-type">char</span>* name; <span class="hljs-type">short</span> age; } a, b;<br><br>a.name = <span class="hljs-string">"Hula"</span>;<br>a.age = <span class="hljs-number">3</span>;<br><br>b = a;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>name</code>属性变成了一个字符指针,这时<code>a</code>赋值给<code>b</code>,导致<code>b.name</code>也是同样的字符指针,指向同一个地址,也就是说两个属性共享同一个地址。因为这时,struct 结构内部保存的是一个指针,而不是上一个例子的数组,这时复制的就不是字符串本身,而是它的指针。并且,这个时候也没法修改字符串,因为字符指针指向的字符串是不能修改的。</p><p>总结一下,赋值运算符(<code>=</code>)可以将 struct 结构每个属性的值,一模一样复制一份,拷贝给另一个 struct 变量。这一点跟数组完全不同,使用赋值运算符复制数组,不会复制数据,只会共享地址。</p><p>注意,这种赋值要求两个变量是同一个类型,不同类型的 struct 变量无法互相赋值。</p><p>另外,C 语言没有提供比较两个自定义数据结构是否相等的方法,无法用比较运算符(比如<code>==</code>和<code>!=</code>)比较两个数据结构是否相等或不等。</p><h2 id="struct-指针"><a href="#struct-指针" class="headerlink" title="struct 指针"></a>struct 指针</h2><p>如果将 struct 变量传入函数,函数内部得到的是一个原始值的副本。</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">turtle</span> {</span><br> <span class="hljs-type">char</span>* name;<br> <span class="hljs-type">char</span>* species;<br> <span class="hljs-type">int</span> age;<br>};<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">happy</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> turtle t)</span> {<br> t.age = t.age + <span class="hljs-number">1</span>;<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span> {<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">turtle</span> <span class="hljs-title">myTurtle</span> =</span> {<span class="hljs-string">"MyTurtle"</span>, <span class="hljs-string">"sea turtle"</span>, <span class="hljs-number">99</span>};<br> happy(myTurtle);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Age is %i\n"</span>, myTurtle.age); <span class="hljs-comment">// 输出 99</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>happy()</code>传入的是一个 struct 变量<code>myTurtle</code>,函数内部有一个自增操作。但是,执行完<code>happy()</code>以后,函数外部的<code>age</code>属性值根本没变。原因就是函数内部得到的是 struct 变量的副本,改变副本影响不到函数外部的原始数据。</p><p>通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性,就可以影响到函数外部。</p><p>struct 指针传入函数的写法如下。</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">void</span> <span class="hljs-title function_">happy</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> turtle* t)</span> {<br>}<br><br>happy(&myTurtle);<br></code></pre></td></tr></table></figure><p>上面代码中,<code>t</code>是 struct 结构的指针,调用函数时传入的是指针。struct 类型跟数组不一样,类型标识符本身并不是指针,所以传入时,指针必须写成<code>&myTurtle</code>。</p><p>函数内部也必须使用<code>(*t).age</code>的写法,从指针拿到 struct 结构本身。</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">void</span> <span class="hljs-title function_">happy</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> turtle* t)</span> {<br> (*t).age = (*t).age + <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>(*t).age</code>不能写成<code>*t.age</code>,因为点运算符<code>.</code>的优先级高于<code>*</code>。<code>*t.age</code>这种写法会将<code>t.age</code>看成一个指针,然后取它对应的值,会出现无法预料的结果。</p><p>现在,重新编译执行上面的整个示例,<code>happy()</code>内部对 struct 结构的操作,就会反映到函数外部。</p><p><code>(*t).age</code>这样的写法很麻烦。C 语言就引入了一个新的箭头运算符(<code>-></code>),可以从 struct 指针上直接获取属性,大大增强了代码的可读性。</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">void</span> <span class="hljs-title function_">happy</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> turtle* t)</span> {<br> t->age = t->age + <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></table></figure><p>总结一下,对于 struct 变量名,使用点运算符(<code>.</code>)获取属性;对于 struct 变量指针,使用箭头运算符(<code>-></code>)获取属性。以变量<code>myStruct</code>为例,假设<code>ptr</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">// ptr == &myStruct</span><br>myStruct.prop == (*ptr).prop == ptr->prop<br></code></pre></td></tr></table></figure><h2 id="struct-的嵌套"><a href="#struct-的嵌套" class="headerlink" title="struct 的嵌套"></a>struct 的嵌套</h2><p>struct 结构的成员可以是另一个 struct 结构。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">species</span> {</span><br> <span class="hljs-type">char</span>* name;<br> <span class="hljs-type">int</span> kinds;<br>};<br><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">fish</span> {</span><br> <span class="hljs-type">char</span>* name;<br> <span class="hljs-type">int</span> age;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">species</span> <span class="hljs-title">breed</span>;</span><br>};<br></code></pre></td></tr></table></figure><p>上面示例中,<code>fish</code>的属性<code>breed</code>是另一个 struct 结构<code>species</code>。</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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 写法一</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">fish</span> <span class="hljs-title">shark</span> =</span> {<span class="hljs-string">"shark"</span>, <span class="hljs-number">9</span>, {<span class="hljs-string">"Selachimorpha"</span>, <span class="hljs-number">500</span>}};<br><br><span class="hljs-comment">// 写法二</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">species</span> <span class="hljs-title">myBreed</span> =</span> {<span class="hljs-string">"Selachimorpha"</span>, <span class="hljs-number">500</span>};<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">fish</span> <span class="hljs-title">shark</span> =</span> {<span class="hljs-string">"shark"</span>, <span class="hljs-number">9</span>, myBreed};<br><br><span class="hljs-comment">// 写法三</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">fish</span> <span class="hljs-title">shark</span> =</span> {<br> .name=<span class="hljs-string">"shark"</span>,<br> .age=<span class="hljs-number">9</span>,<br> .breed={<span class="hljs-string">"Selachimorpha"</span>, <span class="hljs-number">500</span>}<br>};<br><br><span class="hljs-comment">// 写法四</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">fish</span> <span class="hljs-title">shark</span> =</span> {<br> .name=<span class="hljs-string">"shark"</span>,<br> .age=<span class="hljs-number">9</span>,<br> .breed.name=<span class="hljs-string">"Selachimorpha"</span>,<br> .breed.kinds=<span class="hljs-number">500</span><br>};<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"Shark's species is %s"</span>, shark.breed.name);<br></code></pre></td></tr></table></figure><p>上面示例展示了嵌套 Struct 结构的四种赋值写法。另外,引用<code>breed</code>属性的内部属性,要使用两次点运算符(<code>shark.breed.name</code>)。</p><p>下面是另一个嵌套 struct 的例子。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">name</span> {</span><br> <span class="hljs-type">char</span> first[<span class="hljs-number">50</span>];<br> <span class="hljs-type">char</span> last[<span class="hljs-number">50</span>];<br>};<br><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">student</span> {</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">name</span> <span class="hljs-title">name</span>;</span><br> <span class="hljs-type">short</span> age;<br> <span class="hljs-type">char</span> sex;<br>} student1;<br><br><span class="hljs-built_in">strcpy</span>(student1.name.first, <span class="hljs-string">"Harry"</span>);<br><span class="hljs-built_in">strcpy</span>(student1.name.last, <span class="hljs-string">"Potter"</span>);<br><br><span class="hljs-comment">// or</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">name</span> <span class="hljs-title">myname</span> =</span> {<span class="hljs-string">"Harry"</span>, <span class="hljs-string">"Potter"</span>};<br>student1.name = myname;<br></code></pre></td></tr></table></figure><p>上面示例中,自定义类型<code>student</code>的<code>name</code>属性是另一个自定义类型,如果要引用后者的属性,就必须使用两个<code>.</code>运算符,比如<code>student1.name.first</code>。另外,对字符数组属性赋值,要使用<code>strcpy()</code>函数,不能直接赋值,因为直接改掉字符数组名的地址会报错。</p><p>struct 结构内部不仅可以引用其他结构,还可以自我引用,即结构内部引用当前结构。比如,链表结构的节点就可以写成下面这样。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">node</span> {</span><br> <span class="hljs-type">int</span> data;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">node</span>* <span class="hljs-title">next</span>;</span><br>};<br></code></pre></td></tr></table></figure><p>上面示例中,<code>node</code>结构的<code>next</code>属性,就是指向另一个<code>node</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">node</span> {</span><br> <span class="hljs-type">int</span> data;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">node</span>* <span class="hljs-title">next</span>;</span><br>};<br><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">node</span>* <span class="hljs-title">head</span>;</span><br><br><span class="hljs-comment">// 生成一个三个节点的列表 (11)->(22)->(33)</span><br>head = <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> node));<br><br>head->data = <span class="hljs-number">11</span>;<br>head->next = <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> node));<br><br>head->next->data = <span class="hljs-number">22</span>;<br>head->next->next = <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> node));<br><br>head->next->next->data = <span class="hljs-number">33</span>;<br>head->next->next->next = <span class="hljs-literal">NULL</span>;<br><br><span class="hljs-comment">// 遍历这个列表</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">struct</span> node *cur = head; cur != <span class="hljs-literal">NULL</span>; cur = cur->next) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, cur->data);<br>}<br></code></pre></td></tr></table></figure><p>上面示例是链表结构的最简单实现,通过<code>for</code>循环可以对其进行遍历。</p><h2 id="位字段"><a href="#位字段" class="headerlink" title="位字段"></a>位字段</h2><p>struct 还可以用来定义二进制位组成的数据结构,称为“位字段”(bit field),这对于操作底层的二进制数据非常有用。</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-class"><span class="hljs-keyword">struct</span> {</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> ab:<span class="hljs-number">1</span>;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> cd:<span class="hljs-number">1</span>;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> ef:<span class="hljs-number">1</span>;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> gh:<span class="hljs-number">1</span>;<br>} synth;<br><br>synth.ab = <span class="hljs-number">0</span>;<br>synth.cd = <span class="hljs-number">1</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,每个属性后面的<code>:1</code>,表示指定这些属性只占用一个二进制位,所以这个数据结构一共是4个二进制位。</p><p>注意,定义二进制位时,结构内部的各个属性只能是整数类型。</p><p>实际存储的时候,C 语言会按照<code>int</code>类型占用的字节数,存储一个位字段结构。如果有剩余的二进制位,可以使用未命名属性,填满那些位。也可以使用宽度为0的属性,表示占满当前字节剩余的二进制位,迫使下一个属性存储在下一个字节。</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-class"><span class="hljs-keyword">struct</span> {</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> field1 : <span class="hljs-number">1</span>;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> : <span class="hljs-number">2</span>;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> field2 : <span class="hljs-number">1</span>;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> : <span class="hljs-number">0</span>;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> field3 : <span class="hljs-number">1</span>;<br>} stuff;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>stuff.field1</code>与<code>stuff.field2</code>之间,有一个宽度为两个二进制位的未命名属性。<code>stuff.field3</code>将存储在下一个字节。</p><h2 id="弹性数组成员"><a href="#弹性数组成员" class="headerlink" title="弹性数组成员"></a>弹性数组成员</h2><p>很多时候,不能事先确定数组到底有多少个成员。如果声明数组的时候,事先给出一个很大的成员数,就会很浪费空间。C 语言提供了一个解决方法,叫做弹性数组成员(flexible array member)。</p><p>如果不能事先确定数组成员的数量时,可以定义一个 struct 结构。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">vstring</span> {</span><br> <span class="hljs-type">int</span> len;<br> <span class="hljs-type">char</span> chars[];<br>};<br></code></pre></td></tr></table></figure><p>上面示例中,<code>struct vstring</code>结构有两个属性。<code>len</code>属性用来记录数组<code>chars</code>的长度,<code>chars</code>属性是一个数组,但是没有给出成员数量。</p><p><code>chars</code>数组到底有多少个成员,可以在为<code>vstring</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">vstring</span>* <span class="hljs-title">str</span> =</span> <span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> vstring) + n * <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">char</span>));<br>str->len = n;<br></code></pre></td></tr></table></figure><p>上面示例中,假定<code>chars</code>数组的成员数量是<code>n</code>,只有在运行时才能知道<code>n</code>到底是多少。然后,就为<code>struct vstring</code>分配它需要的内存:它本身占用的内存长度,再加上<code>n</code>个数组成员占用的内存长度。最后,<code>len</code>属性记录一下<code>n</code>是多少。</p><p>这样就可以让数组<code>chars</code>有<code>n</code>个成员,不用事先确定,可以跟运行时的需要保持一致。</p><p>弹性数组成员有一些专门的规则。首先,弹性成员的数组,必须是 struct 结构的最后一个属性。另外,除了弹性数组成员,struct 结构必须至少还有一个其他属性。</p><h1 id="typedef-命令"><a href="#typedef-命令" class="headerlink" title="typedef 命令"></a>typedef 命令</h1><h2 id="简介-7"><a href="#简介-7" class="headerlink" title="简介"></a>简介</h2><p><code>typedef</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">typedef</span> type name;<br></code></pre></td></tr></table></figure><p>上面代码中,<code>type</code>代表类型名,<code>name</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-keyword">typedef</span> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> BYTE;<br><br>BYTE c = <span class="hljs-string">'z'</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>typedef</code>命令为类型<code>unsign char</code>起别名<code>BYTE</code>,然后就可以使用<code>BYTE</code>声明变量。</p><p>typedef 可以一次指定多个别名。</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">typedef</span> <span class="hljs-type">int</span> antelope, bagel, mushroom;<br></code></pre></td></tr></table></figure><p>上面示例中,一次性为<code>int</code>类型起了三个别名。</p><p>typedef 可以为指针起别名。</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">typedef</span> <span class="hljs-type">int</span>* intptr;<br><br><span class="hljs-type">int</span> a = <span class="hljs-number">10</span>;<br>intptr x = &a;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>intptr</code>是<code>int*</code>的别名。不过,使用的时候要小心,这样不容易看出来,变量<code>x</code>是一个指针类型。</p><p>typedef 也可以用来为数组类型起别名。</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> five_ints[<span class="hljs-number">5</span>];<br><br>five_ints x = {<span class="hljs-number">11</span>, <span class="hljs-number">22</span>, <span class="hljs-number">33</span>, <span class="hljs-number">44</span>, <span class="hljs-number">55</span>};<br></code></pre></td></tr></table></figure><p>上面示例中,<code>five_ints</code>是一个数组类型,包含5个整数的</p><p>typedef 为函数起别名的写法如下。</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">typedef</span> <span class="hljs-type">signed</span> <span class="hljs-title function_">char</span> <span class="hljs-params">(*fp)</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,类型别名<code>fp</code>是一个指针,代表函数<code>signed char (*)(void)</code>。</p><h2 id="主要好处"><a href="#主要好处" class="headerlink" title="主要好处"></a>主要好处</h2><p><code>typedef</code>为类型起别名的好处,主要有下面几点。</p><p>(1)更好的代码可读性。</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">char</span>* STRING;<br><br>STRING name;<br></code></pre></td></tr></table></figure><p>上面示例为字符指针起别名为<code>STRING</code>,以后使用<code>STRING</code>声明变量时,就可以轻易辨别该变量是字符串。</p><p>(2)为 struct、union、enum 等命令定义的复杂数据结构创建别名,从而便于引用。</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">treenode</span> {</span><br> <span class="hljs-comment">// ...</span><br>};<br><br><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">treenode</span>* <span class="hljs-title">Tree</span>;</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>Tree</code>为<code>struct treenode*</code>的别名。</p><p>typedef 也可以与 struct 定义数据类型的命令写在一起。</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">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">animal</span> {</span><br> <span class="hljs-type">char</span>* name;<br> <span class="hljs-type">int</span> leg_count, speed;<br>} animal;<br></code></pre></td></tr></table></figure><p>上面示例中,自定义数据类型时,同时使用<code>typedef</code>命令,为<code>struct animal</code>起了一个别名<code>animal</code>。</p><p>这种情况下,C 语言允许省略 struct 命令后面的类型名。</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">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> {</span><br> <span class="hljs-type">char</span> *name;<br> <span class="hljs-type">int</span> leg_count, speed;<br>} animal;<br></code></pre></td></tr></table></figure><p>上面示例相当于为一个匿名的数据类型起了别名<code>animal</code>。</p><p>(3)typedef 方便以后为变量改类型。</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">float</span> app_float;<br><br>app_float f1, f2, f3;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>f1</code>、<code>f2</code>、<code>f3</code>的类型都是<code>float</code>。如果以后需要为它们改类型,只需要修改<code>typedef</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">typedef</span> <span class="hljs-type">long</span> <span class="hljs-type">double</span> app_float;<br></code></pre></td></tr></table></figure><p>上面命令将变量<code>f1</code>、<code>f2</code>、<code>f3</code>的类型都改为<code>long double</code>。</p><p>(4)可移植性</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-type">int</span> i = <span class="hljs-number">100000</span>;<br></code></pre></td></tr></table></figure><p>上面代码在32位整数的计算机没有问题,但是在16位整数的计算机就会出错。</p><p>C 语言的解决办法,就是提供了类型别名,在不同计算机上会解释成不同类型,比如<code>int32_t</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-type">int32_t</span> i = <span class="hljs-number">100000</span>;<br></code></pre></td></tr></table></figure><p>上面示例将变量<code>i</code>声明成<code>int32_t</code>类型,保证它在不同计算机上都是32位宽度,移植代码时就不会出错。</p><p>这一类的类型别名都是用 typedef 定义的。下面是类似的例子。</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">long</span> <span class="hljs-type">int</span> <span class="hljs-type">ptrdiff_t</span>;<br><span class="hljs-keyword">typedef</span> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">int</span> <span class="hljs-type">size_t</span>;<br><span class="hljs-keyword">typedef</span> <span class="hljs-type">int</span> <span class="hljs-type">wchar_t</span>;<br></code></pre></td></tr></table></figure><p>这些整数类型别名都放在头文件<code>stdint.h</code>,不同架构的计算机只需修改这个头文件即可,而无需修改代码。</p><p>因此,<code>typedef</code>有助于提高代码的可移植性,使其能适配不同架构的计算机。</p><p>(5)简化类型声明</p><p>C 语言有些类型声明相当复杂,比如下面这个。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> (*(*x(<span class="hljs-type">void</span>))[<span class="hljs-number">5</span>])(<span class="hljs-type">void</span>);<br></code></pre></td></tr></table></figure><p>typedef 可以简化复杂的类型声明,使其更容易理解。首先,最外面一层起一个类型别名。</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">typedef</span> <span class="hljs-title function_">char</span> <span class="hljs-params">(*Func)</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span>;<br>Func (*x(<span class="hljs-type">void</span>))[<span class="hljs-number">5</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">typedef</span> <span class="hljs-title function_">char</span> <span class="hljs-params">(*Func)</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span>;<br><span class="hljs-keyword">typedef</span> Func Arr[<span class="hljs-number">5</span>];<br>Arr* <span class="hljs-title function_">x</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span>;<br></code></pre></td></tr></table></figure><p>上面代码就比较容易解读了。</p><ul><li><code>x</code>是一个函数,返回一个指向 Arr 类型的指针。</li><li><code>Arr</code>是一个数组,有5个成员,每个成员是<code>Func</code>类型。</li><li><code>Func</code>是一个函数指针,指向一个无参数、返回字符值的函数。</li></ul><h1 id="Union-结构"><a href="#Union-结构" class="headerlink" title="Union 结构"></a>Union 结构</h1><p>有时需要一种数据结构,不同的场合表示不同的数据类型。比如,如果只用一种数据结构表示水果的“量”,这种结构就需要有时是整数(6个苹果),有时是浮点数(1.5公斤草莓)。</p><p>C 语言提供了 Union 结构,用来自定义可以灵活变更的数据结构。它内部包含各种属性,但是所有属性共用一块内存,导致这些属性都是对同一个二进制数据的解读,其中往往只有一个属性的解读是有意义的。并且,后面写入的属性会覆盖前面的属性,这意味着同一块内存,可以先供某一个属性使用,然后再供另一个属性使用。这样做的最大好处是节省内存空间。</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-class"><span class="hljs-keyword">union</span> <span class="hljs-title">quantity</span> {</span><br> <span class="hljs-type">short</span> count;<br> <span class="hljs-type">float</span> weight;<br> <span class="hljs-type">float</span> volume;<br>};<br></code></pre></td></tr></table></figure><p>上面示例中,<code>union</code>命令定义了一个包含三个属性的数据类型<code>quantity</code>。虽然包含三个属性,但是只能写入一个值,三个属性都是对这个值的不同解读。最后赋值的属性,往往就是可以取到有意义的值的那个属性。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 写法一</span><br><span class="hljs-class"><span class="hljs-keyword">union</span> <span class="hljs-title">quantity</span> <span class="hljs-title">q</span>;</span><br>q.count = <span class="hljs-number">4</span>;<br><br><span class="hljs-comment">// 写法二</span><br><span class="hljs-class"><span class="hljs-keyword">union</span> <span class="hljs-title">quantity</span> <span class="hljs-title">q</span> =</span> {.count=<span class="hljs-number">4</span>};<br><br><span class="hljs-comment">// 写法三</span><br><span class="hljs-class"><span class="hljs-keyword">union</span> <span class="hljs-title">quantity</span> <span class="hljs-title">q</span> =</span> {<span class="hljs-number">4</span>};<br></code></pre></td></tr></table></figure><p>上面代码展示了为 Union 结构赋值的三种写法。最后一种写法不指定属性名,就会赋值给第一个属性。</p><p>执行完上面的代码以后,<code>q.count</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-built_in">printf</span>(<span class="hljs-string">"count is %i\n"</span>, q.count); <span class="hljs-comment">// count is 4</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"weight is %f\n"</span>, q.weight); <span class="hljs-comment">// 未定义行为</span><br></code></pre></td></tr></table></figure><p>如果要让<code>q.weight</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">q.weight = <span class="hljs-number">0.5</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"weight is %f\n"</span>, q.weight); <span class="hljs-comment">// weight is 0.5</span><br></code></pre></td></tr></table></figure><p>一旦为其他属性赋值,原先可以取到值的<code>q.count</code>属性就跟着改变,使用它可能就没有意义了。除了这一点,Union 结构的其他用法与 Struct 结构,基本上是一致的。</p><p>Union 结构也支持指针运算符<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><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-class"><span class="hljs-keyword">union</span> <span class="hljs-title">quantity</span> {</span><br> <span class="hljs-type">short</span> count;<br> <span class="hljs-type">float</span> weight;<br> <span class="hljs-type">float</span> volume;<br>};<br><br><span class="hljs-class"><span class="hljs-keyword">union</span> <span class="hljs-title">quantity</span> <span class="hljs-title">q</span>;</span><br>q.count = <span class="hljs-number">4</span>;<br><br><span class="hljs-class"><span class="hljs-keyword">union</span> <span class="hljs-title">quantity</span>* <span class="hljs-title">ptr</span>;</span><br>ptr = &q;<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, ptr->count); <span class="hljs-comment">// 4</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>ptr</code>是<code>q</code>的指针,那么<code>ptr->count</code>等同于<code>q.count</code>。</p><p>Union 结构指针与它的属性有关,当前正在按照哪个属性解读数据,它的指针就是对应的数据类型。</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-class"><span class="hljs-keyword">union</span> <span class="hljs-title">foo</span> {</span><br> <span class="hljs-type">int</span> a;<br> <span class="hljs-type">float</span> b;<br>} x;<br><br><span class="hljs-type">int</span>* foo_int_p = (<span class="hljs-type">int</span> *)&x;<br><span class="hljs-type">float</span>* foo_float_p = (<span class="hljs-type">float</span> *)&x;<br><br>x.a = <span class="hljs-number">12</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, x.a); <span class="hljs-comment">// 12</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, *foo_int_p); <span class="hljs-comment">// 12</span><br><br>x.b = <span class="hljs-number">3.141592</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%f\n"</span>, x.b); <span class="hljs-comment">// 3.141592</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%f\n"</span>, *foo_float_p); <span class="hljs-comment">// 3.141592</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>&x</code>是 foo 结构的指针,它的数据类型完全由当前赋值的属性决定。</p><p>typedef 命令可以为 Union 数据类型起别名。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">union</span> {</span><br> <span class="hljs-type">short</span> count;<br> <span class="hljs-type">float</span> weight;<br> <span class="hljs-type">float</span> volume;<br>} quantity;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>union</code>命令定义了一个包含三个属性的数据类型,<code>typedef</code>命令为它起别名为<code>quantity</code>。</p><p>Union 结构的好处,主要是节省空间。它将一段内存空间,重用于不同类型的数据。定义了三个属性,但同一时间只用到一个,使用 Union 结构就可以节省另外两个属性的空间。Union 结构占用的内存长度,等于它内部最长属性的长度。</p><h1 id="Enum-类型"><a href="#Enum-类型" class="headerlink" title="Enum 类型"></a>Enum 类型</h1><p>如果一种数据类型的取值只有少数几种可能,并且每种取值都有自己的含义,为了提高代码的可读性,可以将它们定义为 Enum 类型,中文名为枚举。</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-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">colors</span> {</span>RED, GREEN, BLUE};<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, RED); <span class="hljs-comment">// 0</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, GREEN); <span class="hljs-comment">// 1</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, BLUE); <span class="hljs-comment">// 2</span><br></code></pre></td></tr></table></figure><p>上面示例中,假定程序里面需要三种颜色,就可以使用<code>enum</code>命令,把这三种颜色定义成一种枚举类型<code>colors</code>,它只有三种取值可能<code>RED</code>、<code>GREEN</code>、<code>BLUE</code>。这时,这三个名字自动成为整数常量,编译器默认将它们的值设为数字<code>0</code>、<code>1</code>、<code>2</code>。相比之下,<code>RED</code>要比<code>0</code>的可读性好了许多。</p><p>注意,Enum 内部的常量名,遵守标识符的命名规范,但是通常都使用大写。</p><p>使用时,可以将变量声明为 Enum 类型。</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-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">colors</span> <span class="hljs-title">color</span>;</span><br></code></pre></td></tr></table></figure><p>上面代码将变量<code>color</code>声明为<code>enum colors</code>类型。这个变量的值就是常量<code>RED</code>、<code>GREEN</code>、<code>BLUE</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">color = BLUE;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%i\n"</span>, color); <span class="hljs-comment">// 2</span><br></code></pre></td></tr></table></figure><p>上面代码将变量<code>color</code>的值设为<code>BLUE</code>,这里<code>BLUE</code>就是一个常量,值等于<code>2</code>。</p><p>typedef 命令可以为 Enum 类型起别名。</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-class"><span class="hljs-keyword">enum</span> {</span><br> SHEEP,<br> WHEAT,<br> WOOD,<br> BRICK,<br> ORE<br>} RESOURCE;<br><br>RESOURCE r;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>RESOURCE</code>是 Enum 类型的别名。声明变量时,使用这个别名即可。</p><p>还有一种不常见的写法,就是声明 Enum 类型时,在同一行里面为变量赋值。</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-class"><span class="hljs-keyword">enum</span> {</span><br> SHEEP,<br> WHEAT,<br> WOOD,<br> BRICK,<br> ORE<br>} r = BRICK, s = WOOD;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>r</code>的值是<code>3</code>,<code>s</code>的值是<code>2</code>。</p><p>由于 Enum 的属性会自动声明为常量,所以有时候使用 Enum 的目的,不是为了自定义一种数据类型,而是为了声明一组常量。这时就可以使用下面这种写法,比较简单。</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-class"><span class="hljs-keyword">enum</span> {</span> ONE, TWO };<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d %d"</span>, ONE, TWO); <span class="hljs-comment">// 0 1</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>enum</code>是一个关键字,后面跟着一个代码块,常量就在代码内声明。<code>ONE</code>和<code>TWO</code>就是两个 Enum 常量。</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-class"><span class="hljs-keyword">enum</span> {</span> ONE, TWO, };<br></code></pre></td></tr></table></figure><p>由于Enum 会自动编号,因此可以不必为常量赋值。C 语言会自动从0开始递增,为常量赋值。但是,C 语言也允许为 ENUM 常量指定值,不过只能指定为整数,不能是其他类型。因此,任何可以使用整数的场合,都可以使用 Enum 常量。</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-class"><span class="hljs-keyword">enum</span> {</span> ONE = <span class="hljs-number">1</span>, TWO = <span class="hljs-number">2</span> };<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d %d"</span>, ONE, TWO); <span class="hljs-comment">// 1 2</span><br></code></pre></td></tr></table></figure><p>Enum 常量可以是不连续的值。</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-class"><span class="hljs-keyword">enum</span> {</span> X = <span class="hljs-number">2</span>, Y = <span class="hljs-number">18</span>, Z = <span class="hljs-number">-2</span> };<br></code></pre></td></tr></table></figure><p>Enum 常量也可以是同一个值。</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-class"><span class="hljs-keyword">enum</span> {</span> X = <span class="hljs-number">2</span>, Y = <span class="hljs-number">2</span>, Z = <span class="hljs-number">2</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-class"><span class="hljs-keyword">enum</span> {</span><br> A, <span class="hljs-comment">// 0</span><br> B, <span class="hljs-comment">// 1</span><br> C = <span class="hljs-number">4</span>, <span class="hljs-comment">// 4</span><br> D, <span class="hljs-comment">// 5</span><br> E, <span class="hljs-comment">// 6</span><br> F = <span class="hljs-number">3</span>, <span class="hljs-comment">// 3</span><br> G, <span class="hljs-comment">// 4</span><br> H <span class="hljs-comment">// 5</span><br>};<br></code></pre></td></tr></table></figure><p>Enum 的作用域与变量相同。如果是在顶层声明,那么在整个文件内都有效;如果是在代码块内部声明,则只对该代码块有效。如果与使用<code>int</code>声明的常量相比,Enum 的好处是更清晰地表示代码意图。</p><h1 id="预处理器(Preprocessor)"><a href="#预处理器(Preprocessor)" class="headerlink" title="预处理器(Preprocessor)"></a>预处理器(Preprocessor)</h1><h2 id="简介-8"><a href="#简介-8" class="headerlink" title="简介"></a>简介</h2><p>C 语言编译器在编译程序之前,会先使用预处理器(preprocessor)处理代码。</p><p>预处理器首先会清理代码,进行删除注释、多行语句合成一个逻辑行等工作。然后,执行<code>#</code>开头的预处理指令。本章介绍 C 语言的预处理指令。</p><p>预处理指令可以出现在程序的任何地方,但是习惯上,往往放在代码的开头部分。</p><p>每个预处理指令都以<code>#</code>开头,放在一行的行首,指令前面可以有空白字符(比如空格或制表符)。<code>#</code>和指令的其余部分之间也可以有空格,但是为了兼容老的编译器,一般不留空格。</p><p>所有预处理指令都是一行的,除非在行尾使用反斜杠,将其折行。指令结尾处不需要分号。</p><h2 id="define"><a href="#define" class="headerlink" title="#define"></a>#define</h2><p><code>#define</code>是最常见的预处理指令,用来将指定的词替换成另一个词。它的参数分成两个部分,第一个参数就是要被替换的部分,其余参数是替换后的内容。每条替换规则,称为一个宏(macro)。</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> MAX 100</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>#define</code>指定将源码里面的<code>MAX</code>,全部替换成<code>100</code>。<code>MAX</code>就称为一个宏。</p><p>宏的名称不允许有空格,而且必须遵守 C 语言的变量命名规则,只能使用字母、数字与下划线(<code>_</code>),且首字符不能是数字。</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-meta">#<span class="hljs-keyword">define</span> HELLO <span class="hljs-string">"Hello, world"</span></span><br><br><span class="hljs-comment">// 相当于 printf("%s", "Hello, world");</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s"</span>, HELLO);<br></code></pre></td></tr></table></figure><p>上面示例中,宏<code>HELLO</code>会被原样替换成<code>"Hello, world"</code>。</p><p><code>#define</code>指令可以出现在源码文件的任何地方,从指令出现的地方到文件末尾都有效。习惯上,会将<code>#define</code>放在源码文件的头部。它的主要好处是,会使得程序的可读性更好,也更容易修改。</p><p><code>#define</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> OW <span class="hljs-string">"C programming language is invented \</span></span><br><span class="hljs-string"><span class="hljs-meta">in 1970s."</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,第一行结尾的反斜杠将<code>#define</code>指令拆成两行。</p><p><code>#define</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">define</span> TWO 2</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> FOUR TWO*TWO</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>FOUR</code>会被替换成<code>2*2</code>。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> TWO 2</span><br><br><span class="hljs-comment">// 输出 TWO</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"TWO\n"</span>);<br><br><span class="hljs-comment">// 输出 22</span><br><span class="hljs-type">const</span> TWOs = <span class="hljs-number">22</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, TWOs);<br></code></pre></td></tr></table></figure><p>上面示例中,双引号里面的<code>TWO</code>,以及标识符<code>TWOs</code>,都不会被替换。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 正确</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> FOO hello</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> FOO hello</span><br><br><span class="hljs-comment">// 报错</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> BAR hello</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> BAR world</span><br></code></pre></td></tr></table></figure><p>上面示例中,宏<code>FOO</code>没有变化,所以可以重复定义,宏<code>BAR</code>发生了变化,就报错了。</p><h2 id="带参数的宏"><a href="#带参数的宏" class="headerlink" title="带参数的宏"></a>带参数的宏</h2><h3 id="基本用法-1"><a href="#基本用法-1" class="headerlink" title="基本用法"></a>基本用法</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-meta">#<span class="hljs-keyword">define</span> SQUARE(X) X*X</span><br></code></pre></td></tr></table></figure><p>上面示例中,宏<code>SQUARE</code>可以接受一个参数<code>X</code>,替换成<code>X*X</code>。</p><p>注意,宏的名称与左边圆括号之间,不能有空格。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 替换成 z = 2*2;</span><br>z = SQUARE(<span class="hljs-number">2</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-meta">#<span class="hljs-keyword">define</span> SQUARE(X) X*X</span><br><br><span class="hljs-comment">// 输出19</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, SQUARE(<span class="hljs-number">3</span> + <span class="hljs-number">4</span>));<br></code></pre></td></tr></table></figure><p>上面示例中,<code>SQUARE(3 + 4)</code>如果是函数,输出的应该是49(<code>7*7</code>);宏是原样替换,所以替换成<code>3 + 4*3 + 4</code>,最后输出19。</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-meta">#<span class="hljs-keyword">define</span> SQUARE(X) ((X) * (X))</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>SQUARE(X)</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-meta">#<span class="hljs-keyword">define</span> getchar() getc(stdin)</span><br></code></pre></td></tr></table></figure><p>上面示例中,宏<code>getchar()</code>的参数就是空的。这种情况其实可以省略圆括号,但是加上了,会让它看上去更像函数。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> MAX(x, y) ((x)>(y)?(x):(y))</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> IS_EVEN(n) ((n)%2==0)</span><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><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-meta">#<span class="hljs-keyword">define</span> PRINT_NUMS_TO_PRODUCT(a, b) { \</span><br><span class="hljs-meta"> int product = (a) * (b); \</span><br><span class="hljs-meta"> for (int i = 0; i < product; i++) { \</span><br><span class="hljs-meta"> printf(<span class="hljs-string">"%d\n"</span>, i); \</span><br><span class="hljs-meta"> } \</span><br><span class="hljs-meta">}</span><br></code></pre></td></tr></table></figure><p>上面示例中,替换文本放在大括号里面,这是为了创造一个块作用域,避免宏内部的变量污染外部。</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-meta">#<span class="hljs-keyword">define</span> QUADP(a, b, c) ((-(b) + sqrt((b) * (b) - 4 * (a) * (c))) / (2 * (a)))</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> QUADM(a, b, c) ((-(b) - sqrt((b) * (b) - 4 * (a) * (c))) / (2 * (a)))</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> QUAD(a, b, c) QUADP(a, b, c), QUADM(a, b, c)</span><br></code></pre></td></tr></table></figure><p>上面示例是一元二次方程组求解的宏,由于存在正负两个解,所以宏<code>QUAD</code>先替换成另外两个宏<code>QUADP</code>和<code>QUADM</code>,后者再各自替换成一个解。</p><p>那么,什么时候使用带参数的宏,什么时候使用函数呢?</p><p>一般来说,应该首先使用函数,它的功能更强、更容易理解。宏有时候会产生意想不到的替换结果,而且往往只能写成一行,除非对换行符进行转义,但是可读性就变得很差。</p><p>宏的优点是相对简单,本质上是字符串替换,不涉及数据类型,不像函数必须定义数据类型。而且,宏将每一处都替换成实际的代码,省掉了函数调用的开销,所以性能会好一些。另外,以前的代码大量使用宏,尤其是简单的数学运算,为了读懂前人的代码,需要对它有所了解。</p><h3 id="运算符,-运算符"><a href="#运算符,-运算符" class="headerlink" title="#运算符,##运算符"></a><code>#</code>运算符,<code>##</code>运算符</h3><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-meta">#<span class="hljs-keyword">define</span> STR(x) #x</span><br><br><span class="hljs-comment">// 等同于 printf("%s\n", "3.14159");</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, STR(<span class="hljs-number">3.14159</span>));<br></code></pre></td></tr></table></figure><p>上面示例中,<code>STR(3.14159)</code>会被替换成<code>3.14159</code>。如果<code>x</code>前面没有<code>#</code>,这会被解释成一个浮点数,有了<code>#</code>以后,就会被转换成字符串。</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-meta">#<span class="hljs-keyword">define</span> XNAME(n) <span class="hljs-string">"x"</span>#n</span><br><br><span class="hljs-comment">// 输出 x4</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, XNAME(<span class="hljs-number">4</span>));<br></code></pre></td></tr></table></figure><p>上面示例中,<code>#n</code>指定参数输出为字符串,再跟前面的字符串结合,最终输出为<code>"x4"</code>。如果不加<code>#</code>,这里实现起来就很麻烦了。</p><p>如果替换后的文本里面,参数需要跟其他标识符连在一起,组成一个新的标识符,可以使用<code>##</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-meta">#<span class="hljs-keyword">define</span> MK_ID(n) i##n</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>n</code>是宏<code>MK_ID</code>的参数,这个参数需要跟标识符<code>i</code>粘合在一起,这时<code>i</code>和<code>n</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">MK_ID</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>, <span class="hljs-title function_">MK_ID</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>, <span class="hljs-title function_">MK_ID</span><span class="hljs-params">(<span class="hljs-number">3</span>)</span>;<br><span class="hljs-comment">// 替换成</span><br><span class="hljs-type">int</span> i1, i2, i3;<br></code></pre></td></tr></table></figure><p>上面示例中,替换后的文本<code>i1</code>、<code>i2</code>、<code>i3</code>是三个标识符,参数<code>n</code>是标识符的一部分。从这个例子可以看到,<code>##</code>运算符的一个主要用途是批量生成变量名和标识符。</p><h3 id="不定参数的宏"><a href="#不定参数的宏" class="headerlink" title="不定参数的宏"></a>不定参数的宏</h3><p>宏的参数还可以是不定数量的(即不确定有多少个参数),<code>...</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-meta">#<span class="hljs-keyword">define</span> X(a, b, ...) (10*(a) + 20*(b)), __VA_ARGS__</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>X(a, b, ...)</code>表示<code>X()</code>至少有两个参数,多余的参数使用<code>...</code>表示。在替换文本中,<code>__VA_ARGS__</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">X(<span class="hljs-number">5</span>, <span class="hljs-number">4</span>, <span class="hljs-number">3.14</span>, <span class="hljs-string">"Hi!"</span>, <span class="hljs-number">12</span>)<br><span class="hljs-comment">// 替换成</span><br>(<span class="hljs-number">10</span>*(<span class="hljs-number">5</span>) + <span class="hljs-number">20</span>*(<span class="hljs-number">4</span>)), <span class="hljs-number">3.14</span>, <span class="hljs-string">"Hi!"</span>, <span class="hljs-number">12</span><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">// 报错</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> WRONG(X, ..., Y) #X #__VA_ARGS__ #Y</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>...</code>替代中间部分的参数,这是不允许的,会报错。</p><p><code>__VA_ARGS__</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> X(...) #__VA_ARGS__</span><br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, X(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>)); <span class="hljs-comment">// Prints "1, 2, 3"</span><br></code></pre></td></tr></table></figure><h2 id="undef"><a href="#undef" class="headerlink" title="#undef"></a>#undef</h2><p><code>#undef</code>指令用来取消已经使用<code>#define</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">define</span> LIMIT 400</span><br><span class="hljs-meta">#<span class="hljs-keyword">undef</span> LIMIT</span><br></code></pre></td></tr></table></figure><p>上面示例的<code>undef</code>指令取消已经定义的宏<code>LIMIT</code>,后面就可以重新用 LIMIT 定义一个宏。</p><p>有时候想重新定义一个宏,但不确定是否以前定义过,就可以先用<code>#undef</code>取消,然后再定义。因为同名的宏如果两次定义不一样,会报错,而<code>#undef</code>的参数如果是不存在的宏,并不会报错。</p><p>GCC 的<code>-U</code>选项可以在命令行取消宏的定义,相当于<code>#undef</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -ULIMIT foo.c<br></code></pre></td></tr></table></figure><p>上面示例中的<code>-U</code>参数,取消了宏<code>LIMIT</code>,相当于源文件里面的<code>#undef LIMIT</code>。</p><h2 id="include"><a href="#include" class="headerlink" title="#include"></a>#include</h2><p><code>#include</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-comment">// 形式一</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><foo.h></span> <span class="hljs-comment">// 加载系统提供的文件</span></span><br><br><span class="hljs-comment">// 形式二</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"foo.h"</span> <span class="hljs-comment">// 加载用户提供的文件</span></span><br></code></pre></td></tr></table></figure><p>形式一,文件名写在尖括号里面,表示该文件是系统提供的,通常是标准库的库文件,不需要写路径。因为编译器会到系统指定的安装目录里面,去寻找这些文件。</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"/usr/local/lib/foo.h"</span></span><br></code></pre></td></tr></table></figure><p>GCC 编译器的<code>-I</code>参数,也可以用来指定<code>include</code>命令中用户文件的加载路径。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -Iinclude/ -o code code.c<br></code></pre></td></tr></table></figure><p>上面命令中,<code>-Iinclude/</code>指定从当前目录的<code>include</code>子目录里面,加载用户自己的文件。</p><p><code>#include</code>最常见的用途,就是用来加载包含函数原型的头文件(后缀名为<code>.h</code>),参见《多文件编译》一章。多个<code>#include</code>指令的顺序无关紧要,多次包含同一个头文件也是合法的。</p><h2 id="if…-endif"><a href="#if…-endif" class="headerlink" title="#if…#endif"></a>#if…#endif</h2><p><code>#if...#endif</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-meta">#<span class="hljs-keyword">if</span> 0</span><br> <span class="hljs-type">const</span> <span class="hljs-type">double</span> pi = <span class="hljs-number">3.1415</span>; <span class="hljs-comment">// 不会执行</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>#if</code>后面的<code>0</code>,表示判断条件不成立。所以,内部的变量定义语句会被编译器忽略。<code>#if 0</code>这种写法常用来当作注释使用,不需要的代码就放在<code>#if 0</code>里面。</p><p><code>#if</code>后面的判断条件,通常是一个表达式。如果表达式的值不等于<code>0</code>,就表示判断条件为真,编译内部的语句;如果表达式的值等于0,表示判断条件为伪,则忽略内部的语句。</p><p><code>#if...#endif</code>之间还可以加入<code>#else</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-meta">#<span class="hljs-keyword">define</span> FOO 1</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">if</span> FOO</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"defined\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"not defined\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,宏<code>FOO</code>如果定义过,会被替换成<code>1</code>,从而输出<code>defined</code>,否则输出<code>not defined</code>。</p><p>如果有多个判断条件,还可以加入<code>#elif</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-meta">#<span class="hljs-keyword">if</span> HAPPY_FACTOR == 0</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"I'm not happy!\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">elif</span> HAPPY_FACTOR == 1</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"I'm just regular\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"I'm extra happy!\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,通过<code>#elif</code>指定了第二重判断。注意,<code>#elif</code>的位置必须在<code>#else</code>之前。如果多个判断条件皆不满足,则执行<code>#else</code>的部分。</p><p>没有定义过的宏,等同于<code>0</code>。因此如果<code>UNDEFINED</code>是一个没有定义过的宏,那么<code>#if UNDEFINED</code>为伪,而<code>#if !UNDEFINED</code>为真。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> DEBUG 1</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">if</span> DEBUG</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"value of i : %d\n"</span>, i);<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"value of j : %d\n"</span>, j);<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,通过将<code>DEBUG</code>设为<code>1</code>,就打开了调试模式,可以输出调试信息。</p><p>GCC 的<code>-D</code>参数可以在编译时指定宏的值,因此可以很方便地打开调试开关。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -DDEBUG=1 foo.c<br></code></pre></td></tr></table></figure><p>上面示例中,<code>-D</code>参数指定宏<code>DEBUG</code>为<code>1</code>,相当于在代码中指定<code>#define DEBUG 1</code>。</p><h2 id="ifdef…-endif"><a href="#ifdef…-endif" class="headerlink" title="#ifdef…#endif"></a>#ifdef…#endif</h2><p><code>#ifdef...#endif</code>指令用于判断某个宏是否定义过。</p><p>有时源码文件可能会重复加载某个库,为了避免这种情况,可以在库文件里使用<code>#define</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-meta">#<span class="hljs-keyword">define</span> EXTRA_HAPPY</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>EXTRA_HAPPY</code>就是一个空的宏。</p><p>然后,源码文件使用<code>#ifdef...#endif</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-meta">#<span class="hljs-keyword">ifdef</span> EXTRA_HAPPY</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"I'm extra happy!\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>#ifdef</code>检查宏<code>EXTRA_HAPPY</code>是否定义过。如果已经存在,表示加载过库文件,就会打印一行提示。</p><p><code>#ifdef</code>可以与<code>#else</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-meta">#<span class="hljs-keyword">ifdef</span> EXTRA_HAPPY</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"I'm extra happy!\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"I'm just regular\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,如果宏<code>EXTRA_HAPPY</code>没有定义过,就会执行<code>#else</code>的部分。</p><p><code>#ifdef...#else...#endif</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-meta">#<span class="hljs-keyword">ifdef</span> MAVIS</span><br> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"foo.h"</span></span><br> <span class="hljs-meta">#<span class="hljs-keyword">define</span> STABLES 1</span><br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"bar.h"</span></span><br> <span class="hljs-meta">#<span class="hljs-keyword">define</span> STABLES 2</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,通过判断宏<code>MAVIS</code>是否定义过,实现加载不同的头文件。</p><h2 id="defined-运算符"><a href="#defined-运算符" class="headerlink" title="defined 运算符"></a>defined 运算符</h2><p>上一节的<code>#ifdef</code>指令,等同于<code>#if defined</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-meta">#<span class="hljs-keyword">ifdef</span> FOO</span><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined FOO</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>defined</code>是一个预处理运算符,如果它的参数是一个定义过的宏,就会返回1,否则返回0。</p><p>使用这种语法,可以完成多重判断。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined FOO</span><br> x = <span class="hljs-number">2</span>;<br><span class="hljs-meta">#<span class="hljs-keyword">elif</span> defined BAR</span><br> x = <span class="hljs-number">3</span>;<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined IBMPC</span><br> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"ibmpc.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">elif</span> defined MAC</span><br> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"mac.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"general.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,不同架构的系统需要定义对应的宏。代码根据不同的宏,加载对应的头文件。</p><h2 id="ifndef…-endif"><a href="#ifndef…-endif" class="headerlink" title="#ifndef…#endif"></a>#ifndef…#endif</h2><p><code>#ifndef...#endif</code>指令跟<code>#ifdef...#endif</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-meta">#<span class="hljs-keyword">ifdef</span> EXTRA_HAPPY</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"I'm extra happy!\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br><br><span class="hljs-meta">#<span class="hljs-keyword">ifndef</span> EXTRA_HAPPY</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"I'm just regular\n"</span>);<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,针对宏<code>EXTRA_HAPPY</code>是否被定义过,<code>#ifdef</code>和<code>#ifndef</code>分别指定了两种情况各自需要编译的代码。</p><p><code>#ifndef</code>常用于防止重复加载。举例来说,为了防止头文件<code>myheader.h</code>被重复加载,可以把它放在<code>#ifndef...#endif</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-meta">#<span class="hljs-keyword">ifndef</span> MYHEADER_H</span><br> <span class="hljs-meta">#<span class="hljs-keyword">define</span> MYHEADER_H</span><br> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"myheader.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,宏<code>MYHEADER_H</code>对应文件名<code>myheader.h</code>的大写。只要<code>#ifndef</code>发现这个宏没有被定义过,就说明该头文件没有加载过,从而加载内部的代码,并会定义宏<code>MYHEADER_H</code>,防止被再次加载。</p><p><code>#ifndef</code>等同于<code>#if !defined</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-meta">#<span class="hljs-keyword">ifndef</span> FOO</span><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-meta">#<span class="hljs-keyword">if</span> !defined FOO</span><br></code></pre></td></tr></table></figure><h2 id="预定义宏"><a href="#预定义宏" class="headerlink" title="预定义宏"></a>预定义宏</h2><p>C 语言提供一些预定义的宏,可以直接使用。</p><ul><li><code>__DATE__</code>:编译日期,格式为“Mmm dd yyyy”的字符串(比如 Nov 23 2021)。</li><li><code>__TIME__</code>:编译时间,格式为“hh:mm:ss”。</li><li><code>__FILE__</code>:当前文件名。</li><li><code>__LINE__</code>:当前行号。</li><li><code>__func__</code>:当前正在执行的函数名。该预定义宏必须在函数作用域使用。</li><li><code>__STDC__</code>:如果被设为1,表示当前编译器遵循 C 标准。</li><li><code>__STDC_HOSTED__</code>:如果被设为1,表示当前编译器可以提供完整的标准库;否则被设为0(嵌入式系统的标准库常常是不完整的)。</li><li><code>__STDC_VERSION__</code>:编译所使用的 C 语言版本,是一个格式为<code>yyyymmL</code>的长整数,C99 版本为“199901L”,C11 版本为“201112L”,C17 版本为“201710L”。</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><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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"This function: %s\n"</span>, __func__);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"This file: %s\n"</span>, __FILE__);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"This line: %d\n"</span>, __LINE__);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Compiled on: %s %s\n"</span>, __DATE__, __TIME__);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"C Version: %ld\n"</span>, __STDC_VERSION__);<br>}<br><br><span class="hljs-comment">/* 输出如下</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">This function: main</span><br><span class="hljs-comment">This file: test.c</span><br><span class="hljs-comment">This line: 7</span><br><span class="hljs-comment">Compiled on: Mar 29 2021 19:19:37</span><br><span class="hljs-comment">C Version: 201710</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">*/</span><br></code></pre></td></tr></table></figure><h2 id="line"><a href="#line" class="headerlink" title="#line"></a>#line</h2><p><code>#line</code>指令用于覆盖预定义宏<code>__LINE__</code>,将其改为自定义的行号。后面的行将从<code>__LINE__</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">// 将下一行的行号重置为 300</span><br><span class="hljs-meta">#<span class="hljs-keyword">line</span> 300</span><br></code></pre></td></tr></table></figure><p>上面示例中,紧跟在<code>#line 300</code>后面一行的行号,将被改成300,其后的行会在300的基础上递增编号。</p><p><code>#line</code>还可以改掉预定义宏<code>__FILE__</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-meta">#<span class="hljs-keyword">line</span> 300 <span class="hljs-string">"newfilename"</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,下一行的行号重置为<code>300</code>,文件名重置为<code>newfilename</code>。</p><h2 id="error"><a href="#error" class="headerlink" title="#error"></a>#error</h2><p><code>#error</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-meta">#<span class="hljs-keyword">if</span> __STDC_VERSION__ != 201112L</span><br> <span class="hljs-meta">#<span class="hljs-keyword">error</span> Not C11</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例指定,如果编译器不使用 C11 标准,就中止编译。GCC 编译器会像下面这样报错。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -std=c99 newish.c<br>newish.c:14:2: error: <span class="hljs-comment">#error Not C11</span><br></code></pre></td></tr></table></figure><p>上面示例中,GCC 使用 C99 标准编译,就报错了。</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-meta">#<span class="hljs-keyword">if</span> INT_MAX < 100000</span><br> <span class="hljs-meta">#<span class="hljs-keyword">error</span> int type is too small</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,编译器一旦发现<code>INT</code>类型的最大值小于<code>100,000</code>,就会停止编译。</p><p><code>#error</code>指令也可以用在<code>#if...#elif...#else</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-meta">#<span class="hljs-keyword">if</span> defined WIN32</span><br> <span class="hljs-comment">// ...</span><br><span class="hljs-meta">#<span class="hljs-keyword">elif</span> defined MAC_OS</span><br> <span class="hljs-comment">// ...</span><br><span class="hljs-meta">#<span class="hljs-keyword">elif</span> defined LINUX</span><br> <span class="hljs-comment">// ...</span><br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br> <span class="hljs-meta">#<span class="hljs-keyword">error</span> NOT support the operating system</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><h2 id="pragma"><a href="#pragma" class="headerlink" title="#pragma"></a>#pragma</h2><p><code>#pragma</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">// 使用 C99 标准</span><br><span class="hljs-meta">#<span class="hljs-keyword">pragma</span> c9x on</span><br></code></pre></td></tr></table></figure><p>上面示例让编译器以 C99 标准进行编译。</p><h1 id="I-O-函数"><a href="#I-O-函数" class="headerlink" title="I/O 函数"></a>I/O 函数</h1><p>C 语言提供了一些函数,用于与外部设备通信,称为输入输出函数,简称 I/O 函数。输入(import)指的是获取外部数据,输出(export)指的是向外部传递数据。</p><h2 id="缓存和字节流"><a href="#缓存和字节流" class="headerlink" title="缓存和字节流"></a>缓存和字节流</h2><p>严格地说,输入输出函数并不是直接与外部设备通信,而是通过缓存(buffer)进行间接通信。这个小节介绍缓存是什么。</p><p>普通文件一般都保存在磁盘上面,跟 CPU 相比,磁盘读取或写入数据是一个很慢的操作。所以,程序直接读写磁盘是不可行的,可能每执行一行命令,都必须等半天。C 语言的解决方案,就是只要打开一个文件,就在内存里面为这个文件设置一个缓存区。</p><p>程序向文件写入数据时,程序先把数据放入缓存,等到缓存满了,再把里面的数据会一次性写入磁盘文件。这时,缓存区就空了,程序再把新的数据放入缓存,重复整个过程。</p><p>程序从文件读取数据时,文件先把一部分数据放到缓存里面,然后程序从缓存获取数据,等到缓存空了,磁盘文件再把新的数据放入缓存,重复整个过程。</p><p>内存的读写速度比磁盘快得多,缓存的设计减少了读写磁盘的次数,大大提高了程序的执行效率。另外,一次性移动大块数据,要比多次移动小块数据快得多。</p><p>这种读写模式,对于程序来说,就有点像水流(stream),不是一次性读取或写入所有数据,而是一个持续不断的过程。先操作一部分数据,等到缓存吞吐完这部分数据,再操作下一部分数据。这个过程就叫做字节流操作。</p><p>由于缓存读完就空了,所以字节流读取都是只能读一次,第二次就读不到了。这跟读取文件很不一样。</p><p>C 语言的输入输出函数,凡是涉及读写文件,都是属于字节流操作。输入函数从文件获取数据,操作的是输入流;输出函数向文件写入数据,操作的是输出流。</p><h2 id="printf-1"><a href="#printf-1" class="headerlink" title="printf()"></a>printf()</h2><p><code>printf()</code>是最常用的输出函数,用于屏幕输出,原型定义在头文件<code>stdio.h</code>,详见《基本语法》一章。</p><h2 id="scanf"><a href="#scanf" class="headerlink" title="scanf()"></a>scanf()</h2><h3 id="基本用法-2"><a href="#基本用法-2" class="headerlink" title="基本用法"></a>基本用法</h3><p><code>scanf()</code>函数用于读取用户的键盘输入。程序运行到这个语句时,会停下来,等待用户从键盘输入。用户输入数据、按下回车键后,<code>scanf()</code>就会处理用户的输入,将其存入变量。它的原型定义在头文件<code>stdio.h</code>。</p><p><code>scanf()</code>的语法跟<code>printf()</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">scanf</span>(<span class="hljs-string">"%d"</span>, &i);<br></code></pre></td></tr></table></figure><p>它的第一个参数是一个格式字符串,里面会放置占位符(与<code>printf()</code>的占位符基本一致),告诉编译器如何解读用户的输入,需要提取的数据是什么类型。这是因为 C 语言的数据都是有类型的,<code>scanf()</code>必须提前知道用户输入的数据类型,才能处理数据。它的其余参数就是存放用户输入的变量,格式字符串里面有多少个占位符,就有多少个变量。</p><p>上面示例中,<code>scanf()</code>的第一个参数<code>%d</code>,表示用户输入的应该是一个整数。<code>%d</code>就是一个占位符,<code>%</code>是占位符的标志,<code>d</code>表示整数。第二个参数<code>&i</code>表示,将用户从键盘输入的整数存入变量<code>i</code>。</p><p>注意,变量前面必须加上<code>&</code>运算符(指针变量除外),因为<code>scanf()</code>传递的不是值,而是地址,即将变量<code>i</code>的地址指向用户输入的值。如果这里的变量是指针变量(比如字符串变量),那就不用加<code>&</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-built_in">scanf</span>(<span class="hljs-string">"%d%d%f%f"</span>, &i, &j, &x, &y);<br></code></pre></td></tr></table></figure><p>上面示例中,格式字符串<code>%d%d%f%f</code>,表示用户输入的前两个是整数,后两个是浮点数,比如<code>1 -20 3.4 -4.0e3</code>。这四个值依次放入<code>i</code>、<code>j</code>、<code>x</code>、<code>y</code>四个变量。</p><p><code>scanf()</code>处理数值占位符时,会自动过滤空白字符,包括空格、制表符、换行符等。所以,用户输入的数据之间,有一个或多个空格不影响<code>scanf()</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-number">1</span><br><span class="hljs-number">-20</span><br><span class="hljs-number">3.4</span><br><span class="hljs-number">-4.0e3</span><br></code></pre></td></tr></table></figure><p>上面示例中,用户分成四行输入,得到的结果与一行输入是完全一样的。每次按下回车键以后,<code>scanf()</code>就会开始解读,如果第一行匹配第一个占位符,那么下次按下回车键时,就会从第二个占位符开始解读。</p><p><code>scanf()</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-type">int</span> x;<br><span class="hljs-type">float</span> y;<br><br><span class="hljs-comment">// 用户输入 " -13.45e12# 0"</span><br><span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d"</span>, &x);<br><span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%f"</span>, &y);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>scanf()</code>读取用户输入时,<code>%d</code>占位符会忽略起首的空格,从<code>-</code>处开始获取数据,读取到<code>-13</code>停下来,因为后面的<code>.</code>不属于整数的有效字符。这就是说,占位符<code>%d</code>会读到<code>-13</code>。</p><p>第二次调用<code>scanf()</code>时,就会从上一次停止解读的地方,继续往下读取。这一次读取的首字符是<code>.</code>,由于对应的占位符是<code>%f</code>,会读取到<code>.45e12</code>,这是采用科学计数法的浮点数格式。后面的<code>#</code>不属于浮点数的有效字符,所以会停在这里。</p><p>由于<code>scanf()</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">scanf</span>(<span class="hljs-string">"%d%f"</span>, &x, &y);<br></code></pre></td></tr></table></figure><p><code>scanf()</code>的返回值是一个整数,表示成功读取的变量个数。如果没有读取任何项,或者匹配失败,则返回<code>0</code>。如果读取到文件结尾,则返回常量 EOF。</p><h3 id="占位符-1"><a href="#占位符-1" class="headerlink" title="占位符"></a>占位符</h3><p><code>scanf()</code>常用的占位符如下,与<code>printf()</code>的占位符基本一致。</p><ul><li><code>%c</code>:字符。</li><li><code>%d</code>:整数。</li><li><code>%f</code>:<code>float</code>类型浮点数。</li><li><code>%lf</code>:<code>double</code>类型浮点数。</li><li><code>%Lf</code>:<code>long double</code>类型浮点数。</li><li><code>%s</code>:字符串。</li><li><code>%[]</code>:在方括号中指定一组匹配的字符(比如<code>%[0-9]</code>),遇到不在集合之中的字符,匹配将会停止。</li></ul><p>上面所有占位符之中,除了<code>%c</code>以外,都会自动忽略起首的空白字符。<code>%c</code>不忽略空白字符,总是返回当前第一个字符,无论该字符是否为空格。如果要强制跳过字符前的空白字符,可以写成<code>scanf(" %c", &ch)</code>,即<code>%c</code>前加上一个空格,表示跳过零个或多个空白字符。</p><p>下面要特别说一下占位符<code>%s</code>,它其实不能简单地等同于字符串。它的规则是,从当前第一个非空白字符开始读起,直到遇到空白字符(即空格、换行符、制表符等)为止。因为<code>%s</code>不会包含空白字符,所以无法用来读取多个单词,除非多个<code>%s</code>一起使用。这也意味着,<code>scanf()</code>不适合读取可能包含空格的字符串,比如书名或歌曲名。另外,<code>scanf()</code>遇到<code>%s</code>占位符,会在字符串变量末尾存储一个空字符<code>\0</code>。</p><p><code>scanf()</code>将字符串读入字符数组时,不会检测字符串是否超过了数组长度。所以,储存字符串时,很可能会超过数组的边界,导致预想不到的结果。为了防止这种情况,使用<code>%s</code>占位符时,应该指定读入字符串的最长长度,即写成<code>%[m]s</code>,其中的<code>[m]</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">char</span> name[<span class="hljs-number">11</span>];<br><span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%10s"</span>, name);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>name</code>是一个长度为11的字符数组,<code>scanf()</code>的占位符<code>%10s</code>表示最多读取用户输入的10个字符,后面的字符将被丢弃,这样就不会有数组溢出的风险了。</p><h3 id="赋值忽略符"><a href="#赋值忽略符" class="headerlink" title="赋值忽略符"></a>赋值忽略符</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-built_in">scanf</span>(<span class="hljs-string">"%d-%d-%d"</span>, &year, &month, &day);<br></code></pre></td></tr></table></figure><p>上面示例中,如果用户输入<code>2020-01-01</code>,就会正确解读出年、月、日。问题是用户可能输入其他格式,比如<code>2020/01/01</code>,这种情况下,<code>scanf()</code>解析数据就会失败。</p><p>为了避免这种情况,<code>scanf()</code>提供了一个赋值忽略符(assignment suppression character)<code>*</code>。只要把<code>*</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">scanf</span>(<span class="hljs-string">"%d%*c%d%*c%d"</span>, &year, &month, &day);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>%*c</code>就是在占位符的百分号后面,加入了赋值忽略符<code>*</code>,表示这个占位符没有对应的变量,解读后不必返回。</p><h2 id="sscanf"><a href="#sscanf" class="headerlink" title="sscanf()"></a>sscanf()</h2><p><code>sscanf()</code>函数与<code>scanf()</code>很类似,不同之处是<code>sscanf()</code>从字符串里面,而不是从用户输入获取数据。它的原型定义在头文件<code>stdio.h</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-type">int</span> <span class="hljs-title function_">sscanf</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* s, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* format, ...)</span>;<br></code></pre></td></tr></table></figure><p><code>sscanf()</code>的第一个参数是一个字符串指针,用来从其中获取数据。其他参数都与<code>scanf()</code>相同。</p><p><code>sscanf()</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">fgets(str, <span class="hljs-keyword">sizeof</span>(str), <span class="hljs-built_in">stdin</span>);<br><span class="hljs-built_in">sscanf</span>(str, <span class="hljs-string">"%d%d"</span>, &i, &j);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>fgets()</code>先从标准输入获取了一行数据(<code>fgets()</code>的介绍详见下一章),存入字符数组<code>str</code>。然后,<code>sscanf()</code>再从字符串<code>str</code>里面提取两个整数,放入变量<code>i</code>和<code>j</code>。</p><p><code>sscanf()</code>的一个好处是,它的数据来源不是流数据,所以可以反复使用,不像<code>scanf()</code>的数据来源是流数据,只能读取一次。</p><p><code>sscanf()</code>的返回值是成功赋值的变量的数量,如果提取失败,返回常量 EOF。</p><h2 id="getchar-,putchar"><a href="#getchar-,putchar" class="headerlink" title="getchar(),putchar()"></a>getchar(),putchar()</h2><p><strong>(1)getchar()</strong></p><p><code>getchar()</code>函数返回用户从键盘输入的一个字符,使用时不带有任何参数。程序运行到这个命令就会暂停,等待用户从键盘输入,等同于使用<code>scanf()</code>方法读取一个字符。它的原型定义在头文件<code>stdio.h</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-type">char</span> ch;<br>ch = getchar();<br><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%c"</span>, &ch);<br></code></pre></td></tr></table></figure><p><code>getchar()</code>不会忽略起首的空白字符,总是返回当前读取的第一个字符,无论是否为空格。如果读取失败,返回常量 EOF,由于 EOF 通常是<code>-1</code>,所以返回值的类型要设为 int,而不是 char。</p><p>由于<code>getchar()</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">while</span> (getchar() != <span class="hljs-string">'\n'</span>)<br> ;<br></code></pre></td></tr></table></figure><p>上面示例中,只有读到的字符等于换行符(<code>\n</code>),才会退出循环,常用来跳过某行。<code>while</code>循环的循环体没有任何语句,表示对该行不执行任何操作。</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> len = <span class="hljs-number">0</span>;<br><span class="hljs-keyword">while</span>(getchar() != <span class="hljs-string">'\n'</span>)<br> len++;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>getchar()</code>每读取一个字符,长度变量<code>len</code>就会加1,直到读取到换行符为止,这时<code>len</code>就是该行的字符长度。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">while</span> ((ch = getchar()) == <span class="hljs-string">' '</span>)<br> ;<br></code></pre></td></tr></table></figure><p>上面示例中,结束循环后,变量<code>ch</code>等于第一个非空格字符。</p><p><strong>(2)putchar()</strong></p><p><code>putchar()</code>函数将它的参数字符输出到屏幕,等同于使用<code>printf()</code>输出一个字符。它的原型定义在头文件<code>stdio.h</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-built_in">putchar</span>(ch);<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%c"</span>, ch);<br></code></pre></td></tr></table></figure><p>操作成功时,<code>putchar()</code>返回输出的字符,否则返回常量 EOF。</p><p><strong>(3)小结</strong></p><p>由于<code>getchar()</code>和<code>putchar()</code>这两个函数的用法,要比<code>scanf()</code>和<code>printf()</code>更简单,而且通常是用宏来实现,所以要比<code>scanf()</code>和<code>printf()</code>更快。如果操作单个字符,建议优先使用这两个函数。</p><h2 id="puts"><a href="#puts" class="headerlink" title="puts()"></a>puts()</h2><p><code>puts()</code>函数用于将参数字符串显示在屏幕(stdout)上,并且自动在字符串末尾添加换行符。它的原型定义在头文件<code>stdio.h</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-built_in">puts</span>(<span class="hljs-string">"Here are some messages:"</span>);<br><span class="hljs-built_in">puts</span>(<span class="hljs-string">"Hello World"</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>puts()</code>在屏幕上输出两行内容。</p><p>写入成功时,<code>puts()</code>返回一个非负整数,否则返回常量 EOF。</p><h2 id="gets"><a href="#gets" class="headerlink" title="gets()"></a>gets()</h2><p><code>gets()</code>函数以前用于从<code>stdin</code>读取整行输入,现在已经被废除了,仍然放在这里介绍一下。</p><p>该函数读取用户的一行输入,不会跳过起始处的空白字符,直到遇到换行符为止。这个函数会丢弃换行符,将其余字符放入参数变量,并在这些字符的末尾添加一个空字符<code>\0</code>,使其成为一个字符串。</p><p>它经常与<code>puts()</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">char</span> words[<span class="hljs-number">81</span>];<br><br><span class="hljs-built_in">puts</span>(<span class="hljs-string">"Enter a string, please"</span>);<br>gets(words);<br></code></pre></td></tr></table></figure><p>上面示例使用<code>puts()</code>在屏幕上输出提示,然后使用<code>gets()</code>获取用户的输入。</p><p>由于<code>gets()</code>获取的字符串,可能超过字符数组变量的最大长度,有安全风险,建议不要使用,改为使用<code>fgets()</code>。</p><h1 id="文件操作"><a href="#文件操作" class="headerlink" title="文件操作"></a>文件操作</h1><p>本章介绍 C 语言如何操作文件。</p><h2 id="文件指针"><a href="#文件指针" class="headerlink" title="文件指针"></a>文件指针</h2><p>C 语言提供了一个 FILE 数据结构,记录了操作一个文件所需要的信息。该结构定义在头文件<code>stdio.h</code>,所有文件操作函数都要通过这个数据结构,获取文件信息。</p><p>开始操作一个文件之前,就要定义一个指向该文件的 FILE 指针,相当于获取一块内存区域,用来保存文件信息。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">FILE* fp;<br></code></pre></td></tr></table></figure><p>上面示例定义了一个 FILE 指针<code>fp</code>。</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><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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> FILE* fp;<br> <span class="hljs-type">char</span> c;<br><br> fp = fopen(<span class="hljs-string">"hello.txt"</span>, <span class="hljs-string">"r"</span>);<br> <span class="hljs-keyword">if</span> (fp == <span class="hljs-literal">NULL</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br> }<br><br> c = fgetc(fp);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%c\n"</span>, c);<br><br> fclose(fp);<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,新建文件指针<code>fp</code>以后,依次使用了下面三个文件操作函数,分成三个步骤。其他的文件操作,大致上也是这样的步骤。</p><p>第一步,使用<code>fopen()</code>打开指定文件,返回一个 File 指针。如果出错,返回 NULL。</p><p>它相当于将指定文件的信息与新建的文件指针<code>fp</code>相关联,在 FILE 结构内部记录了这样一些信息:文件内部的当前读写位置、读写报错的记录、文件结尾指示器、缓冲区开始位置的指针、文件标识符、一个计数器(统计拷贝进缓冲区的字节数)等等。后继的操作就可以使用这个指针(而不是文件名)来处理指定文件。</p><p>同时,它还为文件建立一个缓存区。由于存在缓存区,也可以说<code>fopen()</code>函数“打开一个了流”,后继的读写文件都是流模式。</p><p>第二步,使用读写函数,从文件读取数据,或者向文件写入数据。上例使用了<code>fgetc()</code>函数,从已经打开的文件里面,读取一个字符。</p><p><code>fgetc()</code>一调用,文件的数据块先拷贝到缓冲区。不同的计算机有不同的缓冲区大小,一般是512字节或是它的倍数,如4096或16384。随着计算机硬盘容量越来越大,缓冲区也越来越大。</p><p><code>fgetc()</code>从缓冲区读取数据,同时将文件指针内部的读写位置指示器,指向所读取字符的下一个字符。所有的文件读取函数都使用相同的缓冲区,后面再调用任何一个读取函数,都将从指示器指向的位置,即上一次读取函数停止的位置开始读取。</p><p>当读取函数发现已读完缓冲区里面的所有字符时,会请求把下一个缓冲区大小的数据块,从文件拷贝到缓冲区中。读取函数就以这种方式,读完文件的所有内容,直到文件结尾。不过,上例是只从缓存区读取一个字符。当函数在缓冲区里面,读完文件的最后一个字符时,就把 FILE 结构里面的文件结尾指示器设置为真。于是,下一次再调用读取函数时,会返回常量 EOF。EOF 是一个整数值,代表文件结尾,一般是<code>-1</code>。</p><p>第三步,<code>fclose()</code>关闭文件,同时清空缓存区。</p><p>上面是文件读取的过程,文件写入也是类似的方式,先把数据写入缓冲区,当缓冲区填满后,缓存区的数据将被转移到文件中。</p><h2 id="fopen"><a href="#fopen" class="headerlink" title="fopen()"></a>fopen()</h2><p><code>fopen()</code>函数用来打开文件。所有文件操作的第一步,都是使用<code>fopen()</code>打开指定文件。这个函数的原型定义在头文件<code>stdio.h</code>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">FILE* <span class="hljs-title function_">fopen</span><span class="hljs-params">(<span class="hljs-type">char</span>* filename, <span class="hljs-type">char</span>* mode)</span>;<br></code></pre></td></tr></table></figure><p>它接受两个参数。第一个参数是文件名(可以包含路径),第二个参数是模式字符串,指定对文件执行的操作,比如下面的例子中,<code>r</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">fp = fopen(<span class="hljs-string">"in.dat"</span>, <span class="hljs-string">"r"</span>);<br></code></pre></td></tr></table></figure><p>成功打开文件以后,<code>fopen()</code>返回一个 FILE 指针,其他函数可以用这个指针操作文件。如果无法打开文件(比如文件不存在或没有权限),会返回空指针 NULL。所以,执行<code>fopen()</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">fp = fopen(<span class="hljs-string">"hello.txt"</span>, <span class="hljs-string">"r"</span>);<br><br><span class="hljs-keyword">if</span> (fp == <span class="hljs-literal">NULL</span>) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Can't open file!\n"</span>);<br> <span class="hljs-built_in">exit</span>(EXIT_FAILURE);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,如果<code>fopen()</code>返回一个空指针,程序就会报错。</p><p><code>fopen()</code>的模式字符串有以下几种。</p><ul><li><code>r</code>:读模式,只用来读取数据。如果文件不存在,返回 NULL 指针。</li><li><code>w</code>:写模式,只用来写入数据。如果文件存在,文件长度会被截为0,然后再写入;如果文件不存在,则创建该文件。</li><li><code>a</code>:写模式,只用来在文件尾部追加数据。如果文件不存在,则创建该文件。</li><li><code>r+</code>:读写模式。如果文件存在,指针指向文件开始处,可以在文件头部添加数据。如果文件不存在,返回 NULL 指针。</li><li><code>w+</code>:读写模式。如果文件存在,文件长度会被截为0,然后再写入数据。这种模式实际上读不到数据,反而会擦掉数据。如果文件不存在,则创建该文件。</li><li><code>a+</code>:读写模式。如果文件存在,指针指向文件结尾,可以在现有文件末尾添加内容。如果文件不存在,则创建该文件。</li></ul><p>上一小节说过,<code>fopen()</code>函数会为打开的文件创建一个缓冲区。读模式下,创建的是读缓存区;写模式下,创建的是写缓存区;读写模式下,会同时创建两个缓冲区。C 语言通过缓存区,以流的形式,向文件读写数据。</p><p>数据在文件里面,都是以二进制形式存储。但是,读取的时候,有不同的解读方法:以原本的二进制形式解读,叫做“二进制流”;将二进制数据转成文本,以文本形式解读,叫做“文本流”。写入操作也是如此,分成以二进制写入和以文本写入,后者会多一个文本转二进制的步骤。</p><p><code>fopen()</code>的模式字符串,默认是以文本流读写。如果添加<code>b</code>后缀(表示 binary),就会以“二进制流”进行读写。比如,<code>rb</code>是读取二进制数据模式,<code>wb</code>是写入二进制数据模式。</p><p>模式字符串还有一个<code>x</code>后缀,表示独占模式(exclusive)。如果文件已经存在,则打开文件失败;如果文件不存在,则新建文件,打开后不再允许其他程序或线程访问当前文件。比如,<code>wx</code>表示以独占模式写入文件,如果文件已经存在,就会打开失败。</p><h2 id="标准流"><a href="#标准流" class="headerlink" title="标准流"></a>标准流</h2><p>Linux 系统默认提供三个已经打开的文件,它们的文件指针如下。</p><ul><li><code>stdin</code>(标准输入):默认来源为键盘,文件指针编号为<code>0</code>。</li><li><code>stdout</code>(标准输出):默认目的地为显示器,文件指针编号为<code>1</code>。</li><li><code>stderr</code>(标准错误):默认目的地为显示器,文件指针编号为<code>2</code>。</li></ul><p>Linux 系统的文件,不一定是数据文件,也可以是设备文件,即文件代表一个可以读或写的设备。文件指针<code>stdin</code>默认是把键盘看作一个文件,读取这个文件,就能获取用户的键盘输入。同理,<code>stdout</code>和<code>stderr</code>默认是把显示器看作一个文件,将程序的运行结果写入这个文件,用户就能看到运行结果了。它们的区别是,<code>stdout</code>写入的是程序的正常运行结果,<code>stderr</code>写入的是程序的报错信息。</p><p>这三个输入和输出渠道,是 Linux 默认提供的,所以分别称为标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。因为它们的实现是一样的,都是文件流,所以合称为“标准流”。</p><p>Linux 允许改变这三个文件指针(文件流)指向的文件,这称为重定向(redirection)。</p><p>如果标准输入不绑定键盘,而是绑定其他文件,可以在文件名前面加上小于号<code><</code>,跟在程序名后面。这叫做“输入重定向”(input redirection)。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ demo < in.dat<br></code></pre></td></tr></table></figure><p>上面示例中,<code>demo</code>程序代码里面的<code>stdin</code>,将指向文件<code>in.dat</code>,即从<code>in.dat</code>获取数据。</p><p>如果标准输出绑定其他文件,而不是显示器,可以在文件名前加上大于号<code>></code>,跟在程序名后面。这叫做“输出重定向”(output redirection)。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ demo > out.dat<br></code></pre></td></tr></table></figure><p>上面示例中,<code>demo</code>程序代码里面的<code>stdout</code>,将指向文件<code>out.dat</code>,即向<code>out.dat</code>写入数据。</p><p>输出重定向<code>></code>会先擦去<code>out.dat</code>的所有原有的内容,然后再写入。如果希望写入的信息追加在<code>out.dat</code>的结尾,可以使用<code>>></code>符号。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ demo >> out.dat<br></code></pre></td></tr></table></figure><p>上面示例中,<code>demo</code>程序代码里面的<code>stdout</code>,将向文件<code>out.dat</code>写入数据。与<code>></code>不同的是,写入的开始位置是<code>out.dat</code>的文件结尾。</p><p>标准错误的重定向符号是<code>2></code>。其中的<code>2</code>代表文件指针的编号,即<code>2></code>表示将2号文件指针的写入,重定向到<code>err.txt</code>。2号文件指针就是标准错误<code>stderr</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ demo > out.dat 2> err.txt<br></code></pre></td></tr></table></figure><p>上面示例中,<code>demo</code>程序代码里面的<code>stderr</code>,会向文件<code>err.txt</code>写入报错信息。而<code>stdout</code>向文件<code>out.dat</code>写入。</p><p>输入重定向和输出重定向,也可以结合在一条命令里面。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ demo < in.dat > out.dat<br><br>// or<br>$ demo > out.dat < in.dat<br></code></pre></td></tr></table></figure><p>重定向还有另一种情况,就是将一个程序的标准输出<code>stdout</code>,指向另一个程序的标准输入<code>stdin</code>,这时要使用<code>|</code>符号。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ random | <span class="hljs-built_in">sum</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>random</code>程序代码里面的<code>stdout</code>的写入,会从<code>sum</code>程序代码里面的<code>stdin</code>被读取。</p><h2 id="fclose"><a href="#fclose" class="headerlink" title="fclose()"></a>fclose()</h2><p><code>fclose()</code>用来关闭已经使用<code>fopen()</code>打开的文件。它的原型定义在<code>stdin.h</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-type">int</span> <span class="hljs-title function_">fclose</span><span class="hljs-params">(FILE* stream)</span>;<br></code></pre></td></tr></table></figure><p>它接受一个文件指针<code>fp</code>作为参数。如果成功关闭文件,<code>fclose()</code>函数返回整数<code>0</code>;如果操作失败(比如磁盘已满,或者出现 I/O 错误),则返回一个特殊值 EOF(详见下一小节)。</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> (fclose(fp) != <span class="hljs-number">0</span>)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Something wrong."</span>);<br></code></pre></td></tr></table></figure><p>不再使用的文件,都应该使用<code>fclose()</code>关闭,否则无法释放资源。一般来说,系统对同时打开的文件数量有限制,及时关闭文件可以避免超过这个限制。</p><h2 id="EOF"><a href="#EOF" class="headerlink" title="EOF"></a>EOF</h2><p>C 语言的文件操作函数的设计是,如果遇到文件结尾,就返回一个特殊值。程序接收到这个特殊值,就知道已经到达文件结尾了。</p><p>头文件<code>stdio.h</code>为这个特殊值定义了一个宏<code>EOF</code>(end of file 的缩写),它的值一般是<code>-1</code>。这是因为从文件读取的二进制值,不管作为无符号数字解释,还是作为 ASCII 码解释,都不可能是负值,所以可以很安全地返回<code>-1</code>,不会跟文件本身的数据相冲突。</p><p>需要注意的是,不像字符串结尾真的存储了<code>\0</code>这个值,<code>EOF</code>并不存储在文件结尾,文件中并不存在这个值,完全是文件操作函数发现到达了文件结尾,而返回这个值。</p><h2 id="freopen"><a href="#freopen" class="headerlink" title="freopen()"></a>freopen()</h2><p><code>freopen()</code>用于新打开一个文件,直接关联到某个已经打开的文件指针。这样可以复用文件指针。它的原型定义在头文件<code>stdio.h</code>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">FILE* <span class="hljs-title function_">freopen</span><span class="hljs-params">(<span class="hljs-type">char</span>* filename, <span class="hljs-type">char</span>* mode, FILE stream)</span>;<br></code></pre></td></tr></table></figure><p>它跟<code>fopen()</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">freopen(<span class="hljs-string">"output.txt"</span>, <span class="hljs-string">"w"</span>, <span class="hljs-built_in">stdout</span>);<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"hello"</span>);<br></code></pre></td></tr></table></figure><p>上面示例将文件<code>output.txt</code>关联到<code>stdout</code>,此后向<code>stdout</code>写入的内容,都会写入<code>output.txt</code>。由于<code>printf()</code>默认就是输出到<code>stdout</code>,所以运行上面的代码以后,文件<code>output.txt</code>会被写入<code>hello</code>。</p><p><code>freopen()</code>的返回值是它的第三个参数(文件指针)。如果打开失败(比如文件不存在),会返回空指针 NULL。</p><p><code>freopen()</code>会自动关闭原先已经打开的文件,如果文件指针并没有指向已经打开的文件,则<code>freopen()</code>等同于<code>fopen()</code>。</p><p>下面是<code>freopen()</code>关联<code>scanf()</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-type">int</span> i, i2;<br><br><span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d"</span>, &i); <br><br>freopen(<span class="hljs-string">"someints.txt"</span>, <span class="hljs-string">"r"</span>, <span class="hljs-built_in">stdin</span>);<br><span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d"</span>, &i2);<br></code></pre></td></tr></table></figure><p>上面例子中,一共调用了两次<code>scanf()</code>,第一次调用是从键盘读取,然后使用<code>freopen()</code>将<code>stdin</code>指针关联到某个文件,第二次调用就会从该文件读取。</p><p>某些系统允许使用<code>freopen()</code>,改变文件的打开模式。这时,<code>freopen()</code>的第一个参数应该是 NULL。</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">freopen(<span class="hljs-literal">NULL</span>, <span class="hljs-string">"wb"</span>, <span class="hljs-built_in">stdout</span>);<br></code></pre></td></tr></table></figure><p>上面示例将<code>stdout</code>的打开模式从<code>w</code>改成了<code>wb</code>。</p><h2 id="fgetc-,getc"><a href="#fgetc-,getc" class="headerlink" title="fgetc(),getc()"></a>fgetc(),getc()</h2><p><code>fgetc()</code>和<code>getc()</code>用于从文件读取一个字符。它们的用法跟<code>getchar()</code>类似,区别是<code>getchar()</code>只用来从<code>stdin</code>读取,而这两个函数是从任意指定的文件读取。它们的原型定义在头文件<code>stdio.h</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> <span class="hljs-title function_">fgetc</span><span class="hljs-params">(FILE *stream)</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">getc</span><span class="hljs-params">(FILE *stream)</span>;<br></code></pre></td></tr></table></figure><p><code>fgetc()</code>与<code>getc()</code>的用法是一样的,都只有文件指针一个参数。两者的区别是,<code>getc()</code>一般用宏来实现,而<code>fgetc()</code>是函数实现,所以前者的性能可能更好一些。注意,虽然这两个函数返回的是一个字符,但是它们的返回值类型却不是<code>char</code>,而是<code>int</code>,这是因为读取失败的情况下,它们会返回 EOF,这个值一般是<code>-1</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> FILE *fp;<br> fp = fopen(<span class="hljs-string">"hello.txt"</span>, <span class="hljs-string">"r"</span>);<br><br> <span class="hljs-type">int</span> c;<br> <span class="hljs-keyword">while</span> ((c = getc(fp)) != EOF)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%c"</span>, c);<br><br> fclose(fp);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>getc()</code>依次读取文件的每个字符,将其放入变量<code>c</code>,直到读到文件结尾,返回 EOF,循环终止。变量<code>c</code>的类型是<code>int</code>,而不是<code>char</code>,因为有可能等于负值,所以设为<code>int</code>更好一些。</p><h2 id="fputc-,putc"><a href="#fputc-,putc" class="headerlink" title="fputc(),putc()"></a>fputc(),putc()</h2><p><code>fputc()</code>和<code>putc()</code>用于向文件写入一个字符。它们的用法跟<code>putchar()</code>类似,区别是<code>putchar()</code>是向<code>stdout</code>写入,而这两个函数是向文件写入。它们的原型定义在头文件<code>stdio.h</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> <span class="hljs-title function_">fputc</span><span class="hljs-params">(<span class="hljs-type">int</span> <span class="hljs-type">char</span>, FILE *stream)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">putc</span><span class="hljs-params">(<span class="hljs-type">int</span> <span class="hljs-type">char</span>, FILE *stream)</span>;<br></code></pre></td></tr></table></figure><p><code>fputc()</code>与<code>putc()</code>的用法是一样,都接受两个参数,第一个参数是待写入的字符,第二个参数是文件指针。它们的区别是,<code>putc()</code>通常是使用宏来实现,而<code>fputc()</code>只作为函数来实现,所以理论上,<code>putc()</code>的性能会好一点。</p><p>写入成功时,它们返回写入的字符;写入失败时,返回 EOF。</p><h2 id="fprintf"><a href="#fprintf" class="headerlink" title="fprintf()"></a>fprintf()</h2><p><code>fprintf()</code>用于向文件写入格式化字符串,用法与<code>printf()</code>类似。区别是<code>printf()</code>总是写入<code>stdout</code>,而<code>fprintf()</code>则是写入指定的文件,它的第一个参数必须是一个文件指针。它的原型定义在头文件<code>stdio.h</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-type">int</span> <span class="hljs-title function_">fprintf</span><span class="hljs-params">(FILE* stream, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* format, ...)</span><br></code></pre></td></tr></table></figure><p><code>fprintf()</code>可以替代<code>printf()</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-built_in">printf</span>(<span class="hljs-string">"Hello, world!\n"</span>);<br><span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stdout</span>, <span class="hljs-string">"Hello, world!\n"</span>);<br></code></pre></td></tr></table></figure><p>上面例子中,指定<code>fprintf()</code>写入<code>stdout</code>,结果就等同于调用<code>printf()</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">fprintf</span>(fp, <span class="hljs-string">"Sum: %d\n"</span>, sum);<br></code></pre></td></tr></table></figure><p>上面示例是向文件指针<code>fp</code>写入指定格式的字符串。</p><p>下面是向<code>stderr</code>输出错误信息的例子。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Something number.\n"</span>);<br></code></pre></td></tr></table></figure><h2 id="fscanf"><a href="#fscanf" class="headerlink" title="fscanf()"></a>fscanf()</h2><p><code>fscanf()</code>用于按照给定的模式,从文件中读取内容,用法跟<code>scanf()</code>类似。区别是<code>scanf()</code>总是从<code>stdin</code>读取数据,而<code>fscanf()</code>是从文件读入数据,它的原型定义在头文件<code>stdio.h</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-type">int</span> <span class="hljs-title function_">fscanf</span><span class="hljs-params">(FILE* stream, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* format, ...)</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">fscanf</span>(fp, <span class="hljs-string">"%d%d"</span>, &i, &j);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>fscanf()</code>从文件<code>fp</code>里面,读取两个整数,放入变量<code>i</code>和<code>j</code>。</p><p>使用<code>fscanf()</code>的前提是知道文件的结构,它的占位符解析规则与<code>scanf()</code>完全一致。由于<code>fscanf()</code>可以连续读取,直到读到文件尾,或者发生错误(读取失败、匹配失败),才会停止读取,所以<code>fscanf()</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">while</span>(<span class="hljs-built_in">fscanf</span>(fp, <span class="hljs-string">"%s"</span>, words) == <span class="hljs-number">1</span>)<br> <span class="hljs-built_in">puts</span>(words);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>fscanf()</code>依次读取文件的每个词,将它们一行打印一个,直到文件结束。</p><p><code>fscanf()</code>的返回值是赋值成功的变量数量,如果赋值失败会返回 EOF。</p><h2 id="fgets"><a href="#fgets" class="headerlink" title="fgets()"></a>fgets()</h2><p><code>fgets()</code>用于从文件读取指定长度的字符串,它名字的第一个字符是<code>f</code>,就代表<code>file</code>。它的原型定义在头文件<code>stdio.h</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-type">char</span>* <span class="hljs-title function_">fgets</span><span class="hljs-params">(<span class="hljs-type">char</span>* str, <span class="hljs-type">int</span> STRLEN, File* fp)</span>;<br></code></pre></td></tr></table></figure><p>它的第一个参数<code>str</code>是一个字符串指针,用于存放读取的内容。第二个参数<code>STRLEN</code>指定读取的长度,第三个参数是一个 FILE 指针,指向要读取的文件。</p><p><code>fgets()</code>读取 STRLEN - 1 个字符之后,或者遇到换行符与文件结尾,就会停止读取,然后在已经读取的内容末尾添加一个空字符<code>\0</code>,使之成为一个字符串。注意,<code>fgets()</code>会将换行符(<code>\n</code>)存储进字符串。</p><p>如果<code>fgets</code>的第三个参数是<code>stdin</code>,就可以读取标准输入,等同于<code>scanf()</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">fgets(str, <span class="hljs-keyword">sizeof</span>(str), <span class="hljs-built_in">stdin</span>);<br></code></pre></td></tr></table></figure><p>读取成功时,<code>fgets()</code>的返回值是它的第一个参数,即指向字符串的指针,否则返回空指针 NULL。</p><p><code>fgets()</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> FILE* fp;<br> <span class="hljs-type">char</span> s[<span class="hljs-number">1024</span>]; <span class="hljs-comment">// 数组必须足够大,足以放下一行</span><br> <span class="hljs-type">int</span> linecount = <span class="hljs-number">0</span>;<br><br> fp = fopen(<span class="hljs-string">"hello.txt"</span>, <span class="hljs-string">"r"</span>);<br><br> <span class="hljs-keyword">while</span> (fgets(s, <span class="hljs-keyword">sizeof</span> s, fp) != <span class="hljs-literal">NULL</span>)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d: %s"</span>, ++linecount, s);<br><br> fclose(fp);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,每读取一行,都会输出行号和该行的内容。</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><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-type">char</span> words[<span class="hljs-number">10</span>];<br><br><span class="hljs-built_in">puts</span>(<span class="hljs-string">"Enter strings (q to quit):"</span>);<br><br><span class="hljs-keyword">while</span> (fgets(words, <span class="hljs-number">10</span>, <span class="hljs-built_in">stdin</span>) != <span class="hljs-literal">NULL</span>) {<br> <span class="hljs-keyword">if</span> (words[<span class="hljs-number">0</span>] == <span class="hljs-string">'q'</span> && words[<span class="hljs-number">1</span>] == <span class="hljs-string">'\n'</span>)<br> <span class="hljs-keyword">break</span>;<br><br> <span class="hljs-built_in">puts</span>(words);<br>}<br><br><span class="hljs-built_in">puts</span>(<span class="hljs-string">"Done."</span>);<br></code></pre></td></tr></table></figure><p>上面的示例中,如果用户输入的字符串大于9个字符,<code>fgets()</code>会多次读取。直到遇到<code>q</code> + 回车键,才会退出循环。</p><h2 id="fputs"><a href="#fputs" class="headerlink" title="fputs()"></a>fputs()</h2><p><code>fputs()</code>函数用于向文件写入字符串,和<code>puts()</code>函数只有一点不同,那就是它不会在字符串末尾添加换行符。这是因为<code>fgets()</code>保留了换行符,所以<code>fputs()</code>就不添加了。<code>fputs()</code>函数通常与<code>fgets()</code>配对使用。</p><p>它的原型定义在<code>stdio.h</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-type">int</span> <span class="hljs-title function_">fputs</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* str, FILE* stream)</span>;<br></code></pre></td></tr></table></figure><p>它接受两个参数,第一个参数是字符串指针,第二个参数是要写入的文件指针。如果第二个参数为<code>stdout</code>(标准输出),就是将内容输出到计算机屏幕,等同于<code>printf()</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">char</span> words[<span class="hljs-number">14</span>];<br><br><span class="hljs-built_in">puts</span>(<span class="hljs-string">"Enter a string, please."</span>);<br>fgets(words, <span class="hljs-number">14</span>, <span class="hljs-built_in">stdin</span>);<br><br><span class="hljs-built_in">puts</span>(<span class="hljs-string">"This is your string:"</span>);<br><span class="hljs-built_in">fputs</span>(words, <span class="hljs-built_in">stdout</span>);<br></code></pre></td></tr></table></figure><p>上面示例中,先用<code>fgets()</code>从<code>stdin</code>读取用户输入,然后用<code>fputs()</code>输出到<code>stdout</code>。</p><p>写入成功时,<code>fputs()</code>返回一个非负整数,否则返回 EOF。</p><h2 id="fwrite"><a href="#fwrite" class="headerlink" title="fwrite()"></a>fwrite()</h2><p><code>fwrite()</code>用来一次性写入较大的数据块,主要用途是将数组数据一次性写入文件,适合写入二进制数据。它的原型定义在<code>stdio.h</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-type">size_t</span> <span class="hljs-title function_">fwrite</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">void</span>* ptr,</span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> size,</span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> nmemb,</span><br><span class="hljs-params"> FILE* fp</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>它接受四个参数。</p><ul><li><code>ptr</code>:数组指针。</li><li><code>size</code>:每个数组成员的大小,单位字节。</li><li><code>nmemb</code>:数组成员的数量。</li><li><code>fp</code>:要写入的文件指针。</li></ul><p>注意,<code>fwrite()</code>原型的第一个参数类型是<code>void*</code>,这是一个无类型指针,编译器会自动将参数指针转成<code>void*</code>类型。正是由于<code>fwrite()</code>不知道数组成员的类型,所以才需要知道每个成员的大小(第二个参数)和成员数量(第三个参数)。</p><p><code>fwrite()</code>函数的返回值是成功写入的数组成员的数量(注意不是字节数)。正常情况下,该返回值就是第三个参数<code>nmemb</code>,但如果出现写入错误,只写入了一部分成员,返回值会比<code>nmemb</code>小。</p><p>要将整个数组<code>arr</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">fwrite(<br> arr,<br> <span class="hljs-keyword">sizeof</span>(arr[<span class="hljs-number">0</span>]),<br> <span class="hljs-keyword">sizeof</span>(arr) / <span class="hljs-keyword">sizeof</span>(arr[<span class="hljs-number">0</span>]),<br> fp<br>);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>sizeof(a[0])</code>是每个数组成员占用的字节,<code>sizeof(a) / sizeof(a[0])</code>是整个数组的成员数量。</p><p>下面的例子是将一个大小为256字节的字符串写入文件。</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">char</span> buffer[<span class="hljs-number">256</span>];<br><br>fwrite(buffer, <span class="hljs-number">1</span>, <span class="hljs-number">256</span>, fp);<br></code></pre></td></tr></table></figure><p>上面示例中,数组<code>buffer</code>每个成员是1个字节,一共有256个成员。由于<code>fwrite()</code>是连续内存复制,所以写成<code>fwrite(buffer, 256, 1, fp)</code>也能达到目的。</p><p><code>fwrite()</code>没有规定一定要写入整个数组,只写入数组的一部分也是可以的。</p><p>任何类型的数据都可以看成是1字节数据组成的数组,或者是一个成员的数组,所以<code>fwrite()</code>实际上可以写入任何类型的数据,而不仅仅是数组。比如,<code>fwrite()</code>可以将一个 Struct 结构写入文件保存。</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">fwrite(&s, <span class="hljs-keyword">sizeof</span>(s), <span class="hljs-number">1</span>, fp);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>s</code>是一个 Struct 结构指针,可以看成是一个成员的数组。注意,如果<code>s</code>的属性包含指针,存储时需要小心,因为保存指针可能没意义,还原出来的时候,并不能保证指针指向的数据还存在。</p><p><code>fwrite()</code>以及后面要介绍的<code>fread()</code>,比较适合读写二进制数据,因为它们不会对写入的数据进行解读。二进制数据可能包含空字符<code>\0</code>,这是 C 语言的字符串结尾标记,所以读写二进制文件,不适合使用文本读写函数(比如<code>fprintf()</code>等)。</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><span class="line">11</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"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> FILE* fp;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> bytes[] = {<span class="hljs-number">5</span>, <span class="hljs-number">37</span>, <span class="hljs-number">0</span>, <span class="hljs-number">88</span>, <span class="hljs-number">255</span>, <span class="hljs-number">12</span>};<br><br> fp = fopen(<span class="hljs-string">"output.bin"</span>, <span class="hljs-string">"wb"</span>);<br> fwrite(bytes, <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">char</span>), <span class="hljs-keyword">sizeof</span>(bytes), fp);<br> fclose(fp);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,写入二进制文件时,<code>fopen()</code>要使用<code>wb</code>模式打开,表示二进制写入。<code>fwrite()</code>可以把数据解释成单字节数组,因此它的第二个参数是<code>sizeof(char)</code>,第三个参数是数组的总字节数<code>sizeof(bytes)</code>。</p><p>上面例子写入的文件<code>output.bin</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-number">05</span> <span class="hljs-number">25</span> <span class="hljs-number">00</span> <span class="hljs-number">58</span> ff <span class="hljs-number">0</span>c<br></code></pre></td></tr></table></figure><p><code>fwrite()</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-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">clientData</span> <span class="hljs-title">myClient</span> =</span> {<span class="hljs-number">1</span>, <span class="hljs-string">'foo bar'</span>};<br><br><span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">1</span>; i <= <span class="hljs-number">100</span>; i++) {<br> fwrite(&myClient, <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> clientData), <span class="hljs-number">1</span>, cfPtr);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>fwrite()</code>连续将100条数据写入文件。</p><h2 id="fread"><a href="#fread" class="headerlink" title="fread()"></a>fread()</h2><p><code>fread()</code>函数用于一次性从文件读取较大的数据块,主要用途是将文件内容读入一个数组,适合读取二进制数据。它的原型定义在头文件<code>stdio.h</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-type">size_t</span> <span class="hljs-title function_">fread</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">void</span>* ptr,</span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> size,</span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> nmemb,</span><br><span class="hljs-params"> FILE* fp</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>它接受四个参数,与<code>fwrite()</code>完全相同。</p><ul><li><code>ptr</code>:数组地址。</li><li><code>size</code>:每个数组成员的大小,单位为字节。</li><li><code>nmemb</code>:数组的成员数量。</li><li><code>fp</code>:文件指针。</li></ul><p>要将文件内容读入数组<code>arr</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">fread(<br> arr,<br> <span class="hljs-keyword">sizeof</span>(arr[<span class="hljs-number">0</span>]),<br> <span class="hljs-keyword">sizeof</span>(arr) / <span class="hljs-keyword">sizeof</span>(arr[<span class="hljs-number">0</span>]),<br> fp<br>);<br></code></pre></td></tr></table></figure><p>上面示例中,数组长度(第二个参数)和每个成员的大小(第三个参数)的乘积,就是数组占用的内存空间的大小。<code>fread()</code>会从文件(第四个参数)里面读取相同大小的内容,然后将<code>ptr</code>(第一个参数)指向这些内容的内存地址。</p><p>下面的例子是将文件内容读入一个10个成员的双精度浮点数数组。</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">double</span> earnings[<span class="hljs-number">10</span>];<br>fread(earnings, <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">double</span>), <span class="hljs-number">10</span>, fp);<br></code></pre></td></tr></table></figure><p>上面示例中,每个数组成员的大小是<code>sizeof(double)</code>,一个有10个成员,就会从文件<code>fp</code>读取<code>sizeof(double) * 10</code>大小的内容。</p><p><code>fread()</code>函数的返回值是成功读取的数组成员的数量。正常情况下,该返回值就是第三个参数<code>nmemb</code>,但如果出现读取错误或读到文件结尾,该返回值就会比<code>nmemb</code>小。所以,检查<code>fread()</code>的返回值是非常重要的。</p><p><code>fread()</code>和<code>fwrite()</code>可以配合使用。在程序终止之前,使用<code>fwrite()</code>将数据保存进文件,下次运行时再用<code>fread()</code>将数据还原进入内存。</p><p>下面是读取上一节生成的二进制文件<code>output.bin</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> FILE* fp;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> c;<br><br> fp = fopen(<span class="hljs-string">"output.bin"</span>, <span class="hljs-string">"rb"</span>);<br> <span class="hljs-keyword">while</span> (fread(&c, <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">char</span>), <span class="hljs-number">1</span>, fp) > <span class="hljs-number">0</span>)<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, c);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</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><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-number">5</span><br><span class="hljs-number">37</span><br><span class="hljs-number">0</span><br><span class="hljs-number">88</span><br><span class="hljs-number">255</span><br><span class="hljs-number">12</span><br></code></pre></td></tr></table></figure><h2 id="feof"><a href="#feof" class="headerlink" title="feof()"></a>feof()</h2><p><code>feof()</code>函数判断文件的内部指针是否指向文件结尾。它的原型定义在头文件<code>stdio.h</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-type">int</span> <span class="hljs-title function_">feof</span><span class="hljs-params">(FILE *fp)</span>;<br></code></pre></td></tr></table></figure><p><code>feof()</code>接受一个文件指针作为参数。如果已经到达文件结尾,会返回一个非零值(表示 true),否则返回<code>0</code>(表示 false)。</p><p>诸如<code>fgetc()</code>这样的文件读取函数,如果返回 EOF,有两种可能,一种可能是已读取到文件结尾,另一种可能是出现读取错误。<code>feof()</code>可以用来判断到底是那一种情况。</p><p>下面是通过<code>feof()</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-type">int</span> num;<br><span class="hljs-type">char</span> name[<span class="hljs-number">50</span>];<br><br>FILE* cfPtr = fopen(<span class="hljs-string">"clients.txt"</span>, <span class="hljs-string">"r"</span>);<br><br><span class="hljs-keyword">while</span> (!feof(cfPtr)) {<br> <span class="hljs-built_in">fscanf</span>(cfPtr, <span class="hljs-string">"%d%s\n"</span>, &num, name);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d %s\n"</span>, num, name);<br>}<br><br>fclose(cfPtr);<br></code></pre></td></tr></table></figure><p>上面示例通过循环判断<code>feof()</code>是否读到文件结尾,从而实现读出整个文件内容。</p><p><code>feof()</code>为真时,可以通过<code>fseek()</code>、<code>rewind()</code>、<code>fsetpos()</code>函数改变文件内部读写位置的指示器,从而清除这个函数的状态。</p><h2 id="fseek"><a href="#fseek" class="headerlink" title="fseek()"></a>fseek()</h2><p>每个文件指针都有一个内部指示器(内部指针),记录当前打开的文件的读写位置(file position),即下一次读写从哪里开始。文件操作函数(比如<code>getc()</code>、<code>fgets()</code>、<code>fscanf()</code>和<code>fread()</code>等)都从这个指示器指定的位置开始按顺序读写文件。</p><p>如果希望改变这个指示器,将它移到文件的指定位置,可以使用<code>fseek()</code>函数。它的原型定义在头文件<code>stdio.h</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-type">int</span> <span class="hljs-title function_">fseek</span><span class="hljs-params">(FILE* stream, <span class="hljs-type">long</span> <span class="hljs-type">int</span> offset, <span class="hljs-type">int</span> whence)</span>;<br></code></pre></td></tr></table></figure><p><code>fseek()</code>接受3个参数。</p><ul><li><code>stream</code>:文件指针。</li><li><code>offset</code>:距离基准(第三个参数)的字节数。类型为 long int,可以为正值(向文件末尾移动)、负值(向文件开始处移动)或 0(保持不动)。</li><li><code>whence</code>:位置基准,用来确定计算起点。它的值是以下三个宏(定义在<code>stdio.h</code>):<code>SEEK_SET</code>(文件开始处)、<code>SEEK_CUR </code>(内部指针的当前位置)、<code>SEEK_END</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 定位到文件开始处</span><br>fseek(fp, <span class="hljs-number">0L</span>, SEEK_SET);<br><br><span class="hljs-comment">// 定位到文件末尾</span><br>fseek(fp, <span class="hljs-number">0L</span>, SEEK_END);<br><br><span class="hljs-comment">// 从当前位置后移2个字节</span><br>fseek(fp, <span class="hljs-number">2L</span>, SEEK_CUR);<br><br><span class="hljs-comment">// 定位到文件第10个字节</span><br>fseek(fp, <span class="hljs-number">10L</span>, SEEK_SET);<br><br><span class="hljs-comment">// 定位到文件倒数第10个字节</span><br>fseek(fp, <span class="hljs-number">-10L</span>, SEEK_END);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>fseek()</code>的第二个参数为 long 类型,所以移动距离必须加上后缀<code>L</code>,将其转为 long 类型。</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-keyword">for</span> (count = <span class="hljs-number">1L</span>; count <= size; count++) {<br> fseek(fp, -count, SEEK_END);<br> ch = getc(fp);<br>}<br></code></pre></td></tr></table></figure><p>注意,<code>fseek()</code>最好只用来操作二进制文件,不要用来读取文本文件。因为文本文件的字符有不同的编码,某个位置的准确字节位置不容易确定。</p><p>正常情况下,<code>fseek()</code>的返回值为0。如果发生错误(如移动的距离超出文件的范围),返回值为非零值(比如<code>-1</code>)。</p><h2 id="ftell"><a href="#ftell" class="headerlink" title="ftell()"></a>ftell()</h2><p><code>ftell()</code>函数返回文件内部指示器的当前位置。它的原型定义在头文件<code>stdio.h</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-type">long</span> <span class="hljs-type">int</span> <span class="hljs-title function_">ftell</span><span class="hljs-params">(FILE* stream)</span>;<br></code></pre></td></tr></table></figure><p>它接受一个文件指针作为参数。返回值是一个 long 类型的整数,表示内部指示器的当前位置,即文件开始处到当前位置的字节数,<code>0</code>表示文件开始处。如果发生错误,<code>ftell()</code>返回<code>-1L</code>。</p><p><code>ftell()</code>可以跟<code>fseek()</code>配合使用,先记录内部指针的位置,一系列操作过后,再用<code>fseek()</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">long</span> file_pos = ftell(fp);<br><br><span class="hljs-comment">// 一系列文件操作之后</span><br>fseek(fp, file_pos, SEEK_SET);<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">fseek(fp, <span class="hljs-number">0L</span>, SEEK_END);<br>size = ftell(fp);<br></code></pre></td></tr></table></figure><h2 id="rewind"><a href="#rewind" class="headerlink" title="rewind()"></a>rewind()</h2><p><code>rewind()</code>函数可以让文件的内部指示器回到文件开始处。它的原型定义在<code>stdio.h</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-type">void</span> <span class="hljs-title function_">rewind</span><span class="hljs-params">(file* stream)</span>;<br></code></pre></td></tr></table></figure><p>它接受一个文件指针作为参数。</p><p><code>rewind(fp)</code>基本等价于<code>fseek(fp, 0l, seek_set)</code>,唯一的区别是<code>rewind()</code>没有返回值,而且会清除当前文件的错误指示器。</p><h2 id="fgetpos-,fsetpos"><a href="#fgetpos-,fsetpos" class="headerlink" title="fgetpos(),fsetpos()"></a>fgetpos(),fsetpos()</h2><p><code>fseek()</code>和<code>ftell()</code>有一个潜在的问题,那就是它们都把文件大小限制在 long int 类型能表示的范围内。这看起来相当大,但是在32位计算机上,long int 的长度为4个字节,能够表示的范围最大为 4GB。随着存储设备的容量迅猛增长,文件也越来越大,往往会超出这个范围。鉴于此,C 语言新增了两个处理大文件的新定位函数:<code>fgetpos()</code>和<code>fsetpos()</code>。</p><p>它们的原型都定义在头文件<code>stdio.h</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> <span class="hljs-title function_">fgetpos</span><span class="hljs-params">(FILE* stream, <span class="hljs-type">fpos_t</span>* pos)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">fsetpos</span><span class="hljs-params">(FILE* stream, <span class="hljs-type">const</span> <span class="hljs-type">fpos_t</span>* pos)</span>;<br></code></pre></td></tr></table></figure><p><code>fgetpos()</code>函数会将文件内部指示器的当前位置,存储在指针变量<code>pos</code>。该函数接受两个参数,第一个是文件指针,第二个存储指示器位置的变量。</p><p><code>fsetpos()</code>函数会将文件内部指示器的位置,移动到指针变量<code>pos</code>指定的地址。注意,变量<code>pos</code>必须是通过调用<code>fgetpos()</code>方法获得的。<code>fsetpos()</code>的两个参数与<code>fgetpos()</code>必须是一样的。</p><p>记录文件内部指示器位置的指针变量<code>pos</code>,类型为<code>fpos_t*</code>(file position type 的缩写,意为文件定位类型)。它不一定是整数,也可能是一个 Struct 结构。</p><p>下面是用法示例。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">fpos_t</span> file_pos;<br>fgetpos(fp, &file_pos);<br><br><span class="hljs-comment">// 一系列文件操作之后</span><br>fsetpos(fp, &file_pos);<br></code></pre></td></tr></table></figure><p>上面示例中,先用<code>fgetpos()</code>获取内部指针的位置,后面再用<code>fsetpos()</code>恢复指针的位置。</p><p>执行成功时,<code>fgetpos()</code>和<code>fsetpos()</code>都会返回<code>0</code>,否则返回非零值。</p><h2 id="ferror-,clearerr"><a href="#ferror-,clearerr" class="headerlink" title="ferror(),clearerr()"></a>ferror(),clearerr()</h2><p>所有的文件操作函数如果执行失败,都会在文件指针里面记录错误状态。后面的操作只要读取错误指示器,就知道前面的操作出错了。</p><p><code>ferror()</code>函数用来返回错误指示器的状态。可以通过这个函数,判断前面的文件操作是否成功。它的原型定义在头文件<code>stdio.h</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-type">int</span> <span class="hljs-title function_">ferror</span><span class="hljs-params">(FILE *stream)</span>;<br></code></pre></td></tr></table></figure><p>它接受一个文件指针作为参数。如果前面的操作出现错误,<code>ferror()</code>就会返回一个非零整数(表示 true),否则返回<code>0</code>。</p><p><code>clearerr()</code>函数用来重置出错指示器。它的原型定义在头文件<code>stdio.h</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-type">void</span> <span class="hljs-title function_">clearerr</span><span class="hljs-params">(FILE* fp)</span>;<br></code></pre></td></tr></table></figure><p>它接受一个文件指针作为参数,没有返回值。</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></pre></td><td class="code"><pre><code class="hljs c">FILE* fp = fopen(<span class="hljs-string">"file.txt"</span>, <span class="hljs-string">"w"</span>);<br><span class="hljs-type">char</span> c = fgetc(fp);<br><br><span class="hljs-keyword">if</span> (ferror(fp)) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"读取文件:file.txt 时发生错误\n"</span>);<br>}<br><br>clearerr(fp);<br></code></pre></td></tr></table></figure><p>上面示例中,<code>fgetc()</code>尝试读取一个以”写模式“打开的文件,读取失败就会返回 EOF。这时调用<code>ferror()</code>就可以知道上一步操作出错了。处理完以后,再用<code>clearerr()</code>清除出错状态。</p><p>文件操作函数如果正常执行,<code>ferror()</code>和<code>feof()</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (<span class="hljs-built_in">fscanf</span>(fp, <span class="hljs-string">"%d"</span>, &n) != <span class="hljs-number">1</span>) {<br> <span class="hljs-keyword">if</span> (ferror(fp)) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"io error\n"</span>);<br> }<br> <span class="hljs-keyword">if</span> (feof(fp)) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"end of file\n"</span>);<br> }<br><br> clearerr(fp);<br><br> fclose(fp);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,当<code>fscanf()</code>函数报错时,通过检查<code>ferror()</code>和<code>feof()</code>,确定到底发生什么问题。这两个指示器改变状态后,会保持不变,所以要用<code>clearerr()</code>清除它们,<code>clearerr()</code>可以同时清除两个指示器。</p><h2 id="remove"><a href="#remove" class="headerlink" title="remove()"></a>remove()</h2><p><code>remove()</code>函数用于删除指定文件。它的原型定义在头文件<code>stdio.h</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-type">int</span> <span class="hljs-title function_">remove</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* filename)</span>;<br></code></pre></td></tr></table></figure><p>它接受文件名作为参数。如果删除成功,<code>remove()</code>返回<code>0</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">remove(<span class="hljs-string">"foo.txt"</span>);<br></code></pre></td></tr></table></figure><p>上面示例删除了<code>foo.txt</code>文件。</p><p>注意,删除文件必须是在文件关闭的状态下。如果是用<code>fopen()</code>打开的文件,必须先用<code>fclose()</code>关闭后再删除。</p><h2 id="rename"><a href="#rename" class="headerlink" title="rename()"></a>rename()</h2><p><code>rename()</code>函数用于文件改名,也用于移动文件。它的原型定义在头文件<code>stdio.h</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-type">int</span> <span class="hljs-title function_">rename</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* old_filename, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* new_filename)</span>;<br></code></pre></td></tr></table></figure><p>它接受两个参数,第一个参数是现在的文件名,第二个参数是新的文件名。如果改名成功,<code>rename()</code>返回<code>0</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">rename(<span class="hljs-string">"foo.txt"</span>, <span class="hljs-string">"bar.txt"</span>);<br></code></pre></td></tr></table></figure><p>上面示例将<code>foo.txt</code>改名为<code>bar.txt</code>。</p><p>注意,改名后的文件不能与现有文件同名。另外,如果要改名的文件已经打开了,必须先关闭,然后再改名,对打开的文件进行改名会失败。</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">rename(<span class="hljs-string">"/tmp/evidence.txt"</span>, <span class="hljs-string">"/home/beej/nothing.txt"</span>);<br></code></pre></td></tr></table></figure><h1 id="变量说明符"><a href="#变量说明符" class="headerlink" title="变量说明符"></a>变量说明符</h1><p>C 语言允许声明变量的时候,加上一些特定的说明符(specifier),为编译器提供变量行为的额外信息。它的主要作用是帮助编译器优化代码,有时会对程序行为产生影响。</p><h2 id="const"><a href="#const" class="headerlink" title="const"></a>const</h2><p><code>const</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">const</span> <span class="hljs-type">double</span> PI = <span class="hljs-number">3.14159</span>;<br>PI = <span class="hljs-number">3</span>; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面示例里面的<code>const</code>,表示变量<code>PI</code>的值不应改变。如果改变的话,编译器会报错。</p><p>对于数组,<code>const</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">const</span> <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>};<br>arr[<span class="hljs-number">0</span>] = <span class="hljs-number">5</span>; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>const</code>使得数组<code>arr</code>的成员无法修改。</p><p>对于指针变量,<code>const</code>有两种写法,含义是不一样的。如果<code>const</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-comment">// const 表示指向的值 *x 不能修改</span><br><span class="hljs-type">int</span> <span class="hljs-type">const</span> * x<br><span class="hljs-comment">// 或者</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> * x<br></code></pre></td></tr></table></figure><p>下面示例中,对<code>x</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> p = <span class="hljs-number">1</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span>* x = &p;<br><br>(*x)++; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>如果<code>const</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// const 表示地址 x 不能修改</span><br><span class="hljs-type">int</span>* <span class="hljs-type">const</span> x<br></code></pre></td></tr></table></figure><p>下面示例中,对<code>x</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> p = <span class="hljs-number">1</span><br><span class="hljs-type">int</span>* <span class="hljs-type">const</span> x = &p;<br><br>x++; <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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">const</span> <span class="hljs-type">char</span>* <span class="hljs-type">const</span> x;<br></code></pre></td></tr></table></figure><p>上面示例中,指针变量<code>x</code>指向一个字符串。两个<code>const</code>意味着,<code>x</code>包含的内存地址以及<code>x</code>指向的字符串,都不能修改。</p><p><code>const</code>的一个用途,就是防止函数体内修改函数参数。如果某个参数在函数体内不会被修改,可以在函数声明时,对该参数添加<code>const</code>说明符。这样的话,使用这个函数的人看到原型里面的<code>const</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-type">void</span> <span class="hljs-title function_">find</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">int</span>* arr, <span class="hljs-type">int</span> n)</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,函数<code>find</code>的参数数组<code>arr</code>有<code>const</code>说明符,就说明该数组在函数内部将保持不变。</p><p>有一种情况需要注意,如果一个指针变量指向<code>const</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">int</span> i = <span class="hljs-number">1</span>;<br><span class="hljs-type">int</span>* j = &i;<br>*j = <span class="hljs-number">2</span>; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>j</code>是一个指针变量,指向变量<code>i</code>,即<code>j</code>和<code>i</code>指向同一个地址。<code>j</code>本身没有<code>const</code>说明符,但是<code>i</code>有。这种情况下,<code>j</code>指向的值也不能被修改。</p><h2 id="static"><a href="#static" class="headerlink" title="static"></a>static</h2><p><code>static</code>说明符对于全局变量和局部变量有不同的含义。</p><p>(1)用于局部变量(位于块作用域内部)。</p><p><code>static</code>用于函数内部声明的局部变量时,表示该变量的值会在函数每次执行后得到保留,下次执行时不会进行初始化,就类似于一个只用于函数内部的全局变量。由于不必每次执行函数时,都对该变量进行初始化,这样可以提高函数的执行速度,详见《函数》一章。</p><p>(2)用于全局变量(位于块作用域外部)。</p><p><code>static</code>用于函数外部声明的全局变量时,表示该变量只用于当前文件,其他源码文件不可以引用该变量,即该变量不会被链接(link)。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> n = <span class="hljs-number">10</span>;<br><span class="hljs-type">static</span> m = n; <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>m</code>有<code>static</code>修饰,它的值如果等于变量<code>n</code>,就会报错,必须等于常量。</p><p>只在当前文件里面使用的函数,也可以声明为<code>static</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-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">g</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span>;<br></code></pre></td></tr></table></figure><h2 id="auto"><a href="#auto" class="headerlink" title="auto"></a>auto</h2><p><code>auto</code>说明符表示该变量的存储,由编译器自主分配内存空间,且只存在于定义时所在的作用域,退出作用域时会自动释放。</p><p>由于只要不是<code>extern</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-keyword">auto</span> <span class="hljs-type">int</span> a;<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span> a;<br></code></pre></td></tr></table></figure><h2 id="extern"><a href="#extern" class="headerlink" title="extern"></a>extern</h2><p><code>extern</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">extern</span> <span class="hljs-type">int</span> a;<br></code></pre></td></tr></table></figure><p>上面代码中,<code>a</code>是<code>extern</code>变量,表示该变量在其他文件里面定义和初始化,当前文件不必为它分配存储空间。</p><p>但是,变量声明时,同时进行初始化,<code>extern</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-comment">// extern 无效</span><br><span class="hljs-keyword">extern</span> <span class="hljs-type">int</span> i = <span class="hljs-number">0</span>;<br><br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span> i = <span class="hljs-number">0</span>;<br></code></pre></td></tr></table></figure><p>上面代码中,<code>extern</code>对变量初始化的声明是无效的。这是为了防止多个<code>extern</code>对同一个变量进行多次初始化。</p><p>函数内部使用<code>extern</code>声明变量,就相当于该变量是静态存储,每次执行时都要从外部获取它的值。</p><p>函数本身默认是<code>extern</code>,即该函数可以被外部文件共享,通常省略<code>extern</code>不写。如果只希望函数在当前文件可用,那就需要在函数前面加上<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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">extern</span> <span class="hljs-type">int</span> <span class="hljs-title function_">f</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span>;<br><span class="hljs-comment">// 等同于</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">f</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span>;<br></code></pre></td></tr></table></figure><h2 id="register"><a href="#register" class="headerlink" title="register"></a>register</h2><p><code>register</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">register</span> <span class="hljs-type">int</span> a;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>register</code>提示编译器,变量<code>a</code>会经常用到,要为它提供最快的读取速度。</p><p><code>register</code>只对声明在代码块内部的变量有效。</p><p>设为<code>register</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">register</span> <span class="hljs-type">int</span> a;<br><span class="hljs-type">int</span> *p = &a; <span class="hljs-comment">// 编译器报错</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>&a</code>会报错,因为变量<code>a</code>可能放在寄存器里面,无法获取内存地址。</p><p>如果数组设为<code>register</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">register</span> <span class="hljs-type">int</span> a[] = {<span class="hljs-number">11</span>, <span class="hljs-number">22</span>, <span class="hljs-number">33</span>, <span class="hljs-number">44</span>, <span class="hljs-number">55</span>};<br><br><span class="hljs-type">int</span> p = a; <span class="hljs-comment">// 报错</span><br><span class="hljs-type">int</span> a = *(a + <span class="hljs-number">2</span>); <span class="hljs-comment">// 报错</span><br></code></pre></td></tr></table></figure><p>历史上,CPU 内部的缓存,称为寄存器(register)。与内存相比,寄存器的访问速度快得多,所以使用它们可以提高速度。但是它们不在内存之中,所以没有内存地址,这就是为什么不能获取指向它们的指针地址。现代编译器已经有巨大的进步,会尽可能优化代码,按照自己的规则决定怎么利用好寄存器,取得最佳的执行速度,所以可能会忽视代码里面的<code>register</code>说明符,不保证一定会把这些变量放到寄存器。</p><h2 id="volatile"><a href="#volatile" class="headerlink" title="volatile"></a>volatile</h2><p><code>volatile</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">volatile</span> <span class="hljs-type">int</span> foo;<br><span class="hljs-keyword">volatile</span> <span class="hljs-type">int</span>* bar;<br></code></pre></td></tr></table></figure><p><code>volatile</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> foo = x;<br><span class="hljs-comment">// 其他语句,假设没有改变 x 的值</span><br><span class="hljs-type">int</span> bar = x;<br></code></pre></td></tr></table></figure><p>上面代码中,由于变量<code>foo</code>和<code>bar</code>都等于<code>x</code>,而且<code>x</code>的值也没有发生变化,所以编译器可能会把<code>x</code>放入缓存,直接从缓存读取值(而不是从 x 的原始内存位置读取),然后对<code>foo</code>和<code>bar</code>进行赋值。如果<code>x</code>被设定为<code>volatile</code>,编译器就不会把它放入缓存,每次都从原始位置去取<code>x</code>的值,因为在两次读取之间,其他程序可能会改变<code>x</code>。</p><h2 id="restrict"><a href="#restrict" class="headerlink" title="restrict"></a>restrict</h2><p><code>restrict</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-type">int</span>* <span class="hljs-keyword">restrict</span> pt = (<span class="hljs-type">int</span>*) <span class="hljs-built_in">malloc</span>(<span class="hljs-number">10</span> * <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br></code></pre></td></tr></table></figure><p>上面示例中,<code>restrict</code>表示变量<code>pt</code>是访问 malloc 所分配内存的唯一方式。</p><p>下面例子的变量<code>foo</code>,就不能使用<code>restrict</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> foo[<span class="hljs-number">10</span>];<br><span class="hljs-type">int</span>* bar = foo;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>foo</code>指向的内存,可以用<code>foo</code>访问,也可以用<code>bar</code>访问,因此就不能将<code>foo</code>设为 restrict。</p><p>如果编译器知道某块内存只能用一个方式访问,可能可以更好地优化代码,因为不用担心其他地方会修改值。</p><p><code>restrict</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-type">void</span> <span class="hljs-title function_">swap</span><span class="hljs-params">(<span class="hljs-type">int</span>* <span class="hljs-keyword">restrict</span> a, <span class="hljs-type">int</span>* <span class="hljs-keyword">restrict</span> b)</span> {<br> <span class="hljs-type">int</span> t;<br> t = *a;<br> *a = *b;<br> *b = t;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,函数参数声明里的<code>restrict</code>表示,参数<code>a</code>和参数<code>b</code>的内存地址没有重叠。</p><h1 id="多文件项目"><a href="#多文件项目" class="headerlink" title="多文件项目"></a>多文件项目</h1><h2 id="简介-9"><a href="#简介-9" class="headerlink" title="简介"></a>简介</h2><p>一个软件项目往往包含多个源码文件,编译时需要将这些文件一起编译,生成一个可执行文件。</p><p>假定一个项目有两个源码文件<code>foo.c</code>和<code>bar.c</code>,其中<code>foo.c</code>是主文件,<code>bar.c</code>是库文件。所谓“主文件”,就是包含了<code>main()</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-comment">// File foo.c</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>)); <span class="hljs-comment">// 5!</span><br>}<br></code></pre></td></tr></table></figure><p>上面代码中,主文件<code>foo.c</code>调用了函数<code>add()</code>,这个函数是在库文件<code>bar.c</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-comment">// File bar.c</span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">add</span><span class="hljs-params">(<span class="hljs-type">int</span> x, <span class="hljs-type">int</span> y)</span> {<br> <span class="hljs-keyword">return</span> x + y;<br>}<br></code></pre></td></tr></table></figure><p>现在,将这两个文件一起编译。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -o foo foo.c bar.c<br><br><span class="hljs-comment"># 更省事的写法</span><br>$ gcc -o foo *.c<br></code></pre></td></tr></table></figure><p>上面命令中,gcc 的<code>-o</code>参数指定生成的二进制可执行文件的文件名,本例是<code>foo</code>。</p><p>这个命令运行后,编译器会发出警告,原因是在编译<code>foo.c</code>的过程中,编译器发现一个不认识的函数<code>add()</code>,<code>foo.c</code>里面没有这个函数的原型或者定义。因此,最好修改一下<code>foo.c</code>,在文件头部加入<code>add()</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">// File foo.c</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">add</span><span class="hljs-params">(<span class="hljs-type">int</span>, <span class="hljs-type">int</span>)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>)); <span class="hljs-comment">// 5!</span><br>}<br></code></pre></td></tr></table></figure><p>现在再编译就没有警告了。</p><p>你可能马上就会想到,如果有多个文件都使用这个函数<code>add()</code>,那么每个文件都需要加入函数原型。一旦需要修改函数<code>add()</code>(比如改变参数的数量),就会非常麻烦,需要每个文件逐一改动。所以,通常的做法是新建一个专门的头文件<code>bar.h</code>,放置所有在<code>bar.c</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-comment">// File bar.h</span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">add</span><span class="hljs-params">(<span class="hljs-type">int</span>, <span class="hljs-type">int</span>)</span>;<br></code></pre></td></tr></table></figure><p>然后使用<code>include</code>命令,在用到这个函数的源码文件里面加载这个头文件<code>bar.h</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">// File foo.c</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"bar.h"</span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>)); <span class="hljs-comment">// 5!</span><br>}<br></code></pre></td></tr></table></figure><p>上面代码中,<code>#include "bar.h"</code>表示加入头文件<code>bar.h</code>。这个文件没有放在尖括号里面,表示它是用户提供的;它没有写路径,就表示与当前源码文件在同一个目录。</p><p>然后,最好在<code>bar.c</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-comment">// File bar.c</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"bar.h"</span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">add</span><span class="hljs-params">(<span class="hljs-type">int</span> a, <span class="hljs-type">int</span> b)</span> {<br> <span class="hljs-keyword">return</span> a + b;<br>}<br></code></pre></td></tr></table></figure><p>现在重新编译,就可以顺利得到二进制可执行文件。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -o foo foo.c bar.c<br></code></pre></td></tr></table></figure><h2 id="重复加载"><a href="#重复加载" class="headerlink" title="重复加载"></a>重复加载</h2><p>头文件里面还可以加载其他头文件,因此有可能产生重复加载。比如,<code>a.h</code>和<code>b.h</code>都加载了<code>c.h</code>,然后<code>foo.c</code>同时加载了<code>a.h</code>和<code>b.h</code>,这意味着<code>foo.c</code>会编译两次<code>c.h</code>。</p><p>最好避免这种重复加载,虽然多次定义同一个函数原型并不会报错,但是有些语句重复使用会报错,比如多次重复定义同一个 Struct 数据结构。解决重复加载的常见方法是,在头文件里面设置一个专门的宏,加载时一旦发现这个宏存在,就不再继续加载当前文件了。</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">// File bar.h</span><br><span class="hljs-meta">#<span class="hljs-keyword">ifndef</span> BAR_H</span><br> <span class="hljs-meta">#<span class="hljs-keyword">define</span> BAR_H</span><br> <span class="hljs-type">int</span> <span class="hljs-title function_">add</span><span class="hljs-params">(<span class="hljs-type">int</span>, <span class="hljs-type">int</span>)</span>;<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>上面示例中,头文件<code>bar.h</code>使用<code>#ifndef</code>和<code>#endif</code>设置了一个条件判断。每当加载这个头文件时,就会执行这个判断,查看有没有设置过宏<code>BAR_H</code>。如果设置过了,表明这个头文件已经加载过了,就不再重复加载了,反之就先设置一下这个宏,然后加载函数原型。</p><h2 id="extern-说明符-1"><a href="#extern-说明符-1" class="headerlink" title="extern 说明符"></a>extern 说明符</h2><p>当前文件还可以使用其他文件定义的变量,这时要使用<code>extern</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">extern</span> <span class="hljs-type">int</span> myVar;<br></code></pre></td></tr></table></figure><p>上面示例中,<code>extern</code>说明符告诉编译器,变量<code>myvar</code>是其他脚本文件声明的,不需要在这里为它分配内存空间。</p><p>由于不需要分配内存空间,所以<code>extern</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">extern</span> <span class="hljs-type">int</span> a[];<br></code></pre></td></tr></table></figure><p>这种共享变量的声明,可以直接写在源码文件里面,也可以放在头文件中,通过<code>#include</code>指令加载。</p><h2 id="static-说明符-1"><a href="#static-说明符-1" class="headerlink" title="static 说明符"></a>static 说明符</h2><p>正常情况下,当前文件内部的全局变量,可以被其他文件使用。有时候,不希望发生这种情况,而是希望某个变量只局限在当前文件内部使用,不要被其他文件引用。</p><p>这时可以在声明变量的时候,使用<code>static</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-type">static</span> <span class="hljs-type">int</span> foo = <span class="hljs-number">3</span>;<br></code></pre></td></tr></table></figure><p>上面示例中,变量<code>foo</code>只能在当前文件里面使用,其他文件不能引用。</p><h2 id="编译策略"><a href="#编译策略" class="headerlink" title="编译策略"></a>编译策略</h2><p>多个源码文件的项目,编译时需要所有文件一起编译。哪怕只是修改了一行,也需要从头编译,非常耗费时间。</p><p>为了节省时间,通常的做法是将编译拆分成两个步骤。第一步,使用 GCC 的<code>-c</code>参数,将每个源码文件单独编译为对象文件(object file)。第二步,将所有对象文件链接在一起,合并生成一个二进制可执行文件。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -c foo.c <span class="hljs-comment"># 生成 foo.o</span><br>$ gcc -c bar.c <span class="hljs-comment"># 生成 bar.o</span><br><br><span class="hljs-comment"># 更省事的写法</span><br>$ gcc -c *.c<br></code></pre></td></tr></table></figure><p>上面命令为源码文件<code>foo.c</code>和<code>bar.c</code>,分别生成对象文件<code>foo.o</code>和<code>bar.o</code>。</p><p>对象文件不是可执行文件,只是编译过程中的一个阶段性产物,文件名与源码文件相同,但是后缀名变成了<code>.o</code>。</p><p>得到所有的对象文件以后,再次使用<code>gcc</code>命令,将它们通过链接,合并生成一个可执行文件。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ gcc -o foo foo.o bar.o<br><br><span class="hljs-comment"># 更省事的写法</span><br>$ gcc -o foo *.o<br></code></pre></td></tr></table></figure><p>以后,修改了哪一个源文件,就将这个文件重新编译成对象文件,其他文件不用重新编译,可以继续使用原来的对象文件,最后再将所有对象文件重新链接一次就可以了。由于链接的耗时大大短于编译,这样做就节省了大量时间。</p><h2 id="make-命令"><a href="#make-命令" class="headerlink" title="make 命令"></a>make 命令</h2><p>大型项目的编译,如果全部手动完成,是非常麻烦的,容易出错。一般会使用专门的自动化编译工具,比如 make。</p><p>make 是一个命令行工具,使用时会自动在当前目录下搜索配置文件 makefile(也可以写成 Makefile)。该文件定义了所有的编译规则,每个编译规则对应一个编译产物。为了得到这个编译产物,它需要知道两件事。</p><ul><li>依赖项(生成该编译产物,需要用到哪些文件)</li><li>生成命令(生成该编译产物的命令)</li></ul><p>比如,对象文件<code>foo.o</code>是一个编译产物,它的依赖项是<code>foo.c</code>,生成命令是<code>gcc -c foo.c</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">foo.o: foo.c<br> gcc -c foo.c<br></code></pre></td></tr></table></figure><p>上面示例中,编译规则由两行组成。第一行首先是编译产物,冒号后面是它的依赖项,第二行则是生成命令。</p><p>注意,第二行的缩进必须使用 Tab 键,如果使用空格键会报错。</p><p>完整的配置文件 makefile 由多个编译规则组成,可能是下面的样子。</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">foo: foo.o bar.o<br> gcc -o foo foo.o bar.o<br><br>foo.o: bar.h foo.c<br> gcc -c foo.c<br><br>bar.o: bar.h bar.c<br> gcc -c bar.c<br></code></pre></td></tr></table></figure><p>上面是 makefile 的一个示例文件。它包含三个编译规则,对应三个编译产物(<code>foo.o</code>、<code>bar.o</code>和<code>foo</code>),每个编译规则之间使用空行分隔。</p><p>有了 makefile,编译时,只要在 make 命令后面指定编译目标(编译产物的名字),就会自动调用对应的编译规则。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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 bash">$ make foo.o<br><br><span class="hljs-comment"># or</span><br>$ make bar.o<br><br><span class="hljs-comment"># or</span><br>$ make foo<br></code></pre></td></tr></table></figure><p>上面示例中,make 命令会根据不同的命令,生成不同的编译产物。</p><p>如果省略了编译目标,<code>make</code>命令会执行第一条编译规则,构建相应的产物。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ make<br></code></pre></td></tr></table></figure><p>上面示例中,<code>make</code>后面没有编译目标,所以会执行 makefile 的第一条编译规则,本例是<code>make foo</code>。由于用户期望执行<code>make</code>后得到最终的可执行文件,所以建议总是把最终可执行文件的编译规则,放在 makefile 文件的第一条。makefile 本身对编译规则没有顺序要求。</p><p>make 命令的强大之处在于,它不是每次执行命令,都会进行编译,而是会检查是否有必要重新编译。具体方法是,通过检查每个源码文件的时间戳,确定在上次编译之后,哪些文件发生过变动。然后,重新编译那些受到影响的编译产物(即编译产物直接或间接依赖于那些发生变动的源码文件),不受影响的编译产物,就不会重新编译。</p><p>举例来说,上次编译之后,修改了<code>foo.c</code>,没有修改<code>bar.c</code>和<code>bar.h</code>。于是,重新运行<code>make foo</code>命令时,Make 就会发现<code>bar.c</code>和<code>bar.h</code>没有变动过,因此不用重新编译<code>bar.o</code>,只需要重新编译<code>foo.o</code>。有了新的<code>foo.o</code>以后,再跟<code>bar.o</code>一起,重新编译成新的可执行文件<code>foo</code>。</p><p>Make 这样设计的最大好处,就是自动处理编译过程,只重新编译变动过的文件,因此大大节省了时间。</p><h1 id="命令行环境"><a href="#命令行环境" class="headerlink" title="命令行环境"></a>命令行环境</h1><h2 id="命令行参数"><a href="#命令行参数" class="headerlink" title="命令行参数"></a>命令行参数</h2><p>C 语言程序可以从命令行接收参数。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ./foo hello world<br></code></pre></td></tr></table></figure><p>上面示例中,程序<code>foo</code>接收了两个命令行参数<code>hello</code>和<code>world</code>。</p><p>程序内部怎么拿到命令行参数呢?C 语言会把命令行输入的内容,放在一个数组里面。<code>main()</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span>* argv[])</span> {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < argc; i++) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"arg %d: %s\n"</span>, i, argv[i]);<br> }<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>main()</code>函数有两个参数<code>argc</code>(argument count)和<code>argv</code>(argument variable)。这两个参数的名字可以任意取,但是一般来说,约定俗成就是使用这两个词。</p><p>第一个参数<code>argc</code>是命令行参数的数量,由于程序名也被计算在内,所以严格地说<code>argc</code>是参数数量 + 1。</p><p>第二个参数<code>argv</code>是一个数组,保存了所有的命令行输入,它的每个成员是一个字符串指针。</p><p>以<code>./foo hello world</code>为例,<code>argc</code>是3,表示命令行输入有三个组成部分:<code>./foo</code>、<code>hello</code>、<code>world</code>。数组<code>argv</code>用来获取这些输入,<code>argv[0]</code>是程序名<code>./foo</code>,<code>argv[1]</code>是<code>hello</code>,<code>argv[2]</code>是<code>world</code>。一般来说,<code>argv[1]</code>到<code>argv[argc - 1]</code>依次是命令行的所有参数。<code>argv[argc]</code>则是一个空指针 NULL。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 写法一</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span>* argv[])</span><br><br><span class="hljs-comment">// 写法二</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span>** argv)</span><br><br><span class="hljs-comment">// 写法三</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> argv[][])</span><br></code></pre></td></tr></table></figure><p>另一方面,每个命令行参数既可以写成数组形式<code>argv[i]</code>,也可以写成指针形式<code>*(argv + i)</code>。</p><p>利用<code>argc</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span>** argv)</span> {<br> <span class="hljs-keyword">if</span> (argc != <span class="hljs-number">3</span>) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"usage: mult x y\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br> }<br><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, atoi(argv[<span class="hljs-number">1</span>]) * atoi(argv[<span class="hljs-number">2</span>]));<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>argc</code>不等于<code>3</code>就会报错,这样就限定了程序必须有两个参数,才能运行。</p><p>另外,<code>argv</code>数组的最后一个成员是 NULL 指针(<code>argv[argc] == NULL</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-keyword">for</span> (<span class="hljs-type">char</span>** p = argv; *p != <span class="hljs-literal">NULL</span>; p++) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"arg: %s\n"</span>, *p);<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,指针<code>p</code>依次移动,指向<code>argv</code>的每个成员,一旦移到空指针 NULL,就表示遍历结束。由于<code>argv</code>的地址是固定的,不能执行自增运算(<code>argv++</code>),所以必须通过一个中间变量<code>p</code>,完成遍历操作。</p><h2 id="退出状态"><a href="#退出状态" class="headerlink" title="退出状态"></a>退出状态</h2><p>C 语言规定,如果<code>main()</code>函数没有<code>return</code>语句,那么结束运行的时候,默认会添加一句<code>return 0</code>,即返回整数<code>0</code>。这就是为什么<code>main()</code>语句通常约定返回一个整数值,并且返回整数<code>0</code>表示程序运行成功。如果返回非零值,就表示程序运行出了问题。</p><p>Bash 的环境变量<code>$?</code>可以用来读取上一个命令的返回值,从而知道是否运行成功。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ./foo hello world<br>$ <span class="hljs-built_in">echo</span> $?<br>0<br></code></pre></td></tr></table></figure><p>上面示例中,<code>echo $?</code>用来打印环境变量<code>$?</code>的值,该值为<code>0</code>,就表示上一条命令运行成功,否则就是运行失败。</p><p>注意,只有<code>main()</code>会默认添加<code>return 0</code>,其他函数都没有这个机制。</p><h2 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h2><p>C 语言提供了<code>getenv()</code>函数(原型在<code>stdlib.h</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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">char</span>* val = getenv(<span class="hljs-string">"HOME"</span>);<br><br> <span class="hljs-keyword">if</span> (val == <span class="hljs-literal">NULL</span>) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Cannot find the HOME environment variable\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br> }<br><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Value: %s\n"</span>, val);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面示例中,<code>getenv("HOME")</code>用来获取命令行的环境变量<code>$HOME</code>,如果这个变量为空(<code>NULL</code>),则程序报错返回。</p><h1 id="多字节字符"><a href="#多字节字符" class="headerlink" title="多字节字符"></a>多字节字符</h1><p>本章介绍 C 语言如何处理非英语字符。</p><h2 id="Unicode-简介"><a href="#Unicode-简介" class="headerlink" title="Unicode 简介"></a>Unicode 简介</h2><p>C 语言诞生时,只考虑了英语字符,使用7位的 ASCII 码表示所有字符。ASCII 码的范围是0到127,也就是最多只能表示100多个字符,用一个字节就可以表示,所以<code>char</code>类型只占用一个字节。</p><p>但是,如果处理非英语字符,一个字节就不够了,单单是中文,就至少有几万个字符,字符集就势必使用多个字节表示。</p><p>最初,不同国家有自己的字符编码方式,这样不便于多种字符的混用。因此,后来就逐渐统一到 Unicode 编码,将所有字符放入一个字符集。</p><p>Unicode 为每个字符提供一个号码,称为码点(code point),其中0到127的部分,跟 ASCII 码是重合的。通常使用“U+十六进制码点”表示一个字符,比如<code>U+0041</code>表示字母<code>A</code>。</p><p>Unicode 编码目前一共包含了100多万个字符,码点范围是 U+0000 到 U+10FFFF。完整表达整个 Unicode 字符集,至少需要三个字节。但是,并不是所有文档都需要那么多字符,比如对于 ASCII 码就够用的英语文档,如果每个字符使用三个字节表示,就会比单字节表示的文件体积大出三倍。</p><p>为了适应不同的使用需求,Unicode 标准委员会提供了三种不同的表示方法,表示 Unicode 码点。</p><ul><li>UTF-8:使用1个到4个字节,表示一个码点。不同的字符占用的字节数不一样。</li><li>UTF-16:对于U+0000 到 U+FFFF 的字符(称为基本平面),使用2个字节表示一个码点。其他字符使用4个字节。</li><li>UTF-32:统一使用4个字节,表示一个码点。</li></ul><p>其中,UTF-8 的使用最为广泛,因为对于 ASCII 字符(U+0000 到 U+007F),它只使用一个字节表示,这就跟 ASCII 的编码方式完全一样。</p><p>C 语言提供了两个宏,表示当前系统支持的编码字节长度。这两个宏都定义在头文件<code>limits.h</code>。</p><ul><li><code>MB_LEN_MAX</code>:任意支持地区的最大字节长度,定义在<code>limits.h</code>。</li><li><code>MB_CUR_MAX</code>:当前语言的最大字节长度,总是小于或等于<code>MB_LEN_MAX</code>,定义在<code>stdlib.h</code>。</li></ul><h2 id="字符的表示方法"><a href="#字符的表示方法" class="headerlink" title="字符的表示方法"></a>字符的表示方法</h2><p>字符表示法的本质,是将每个字符映射为一个整数,然后从编码表获得该整数对应的字符。</p><p>C 语言提供了不同的写法,用来表示字符的整数号码。</p><ul><li><code>\123</code>:以八进制值表示一个字符,斜杠后面需要三个数字。</li><li><code>\x4D</code>:以十六进制表示一个字符,<code>\x</code>后面是十六进制整数。</li><li><code>\u2620</code>:以 Unicode 码点表示一个字符(不适用于 ASCII 字符),码点以十六进制表示,<code>\u</code>后面需要4个字符。</li><li><code>\U0001243F</code>:以 Unicode 码点表示一个字符(不适用于 ASCII 字符),码点以十六进制表示,<code>\U</code>后面需要8个字符。</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-built_in">printf</span>(<span class="hljs-string">"ABC\n"</span>);<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"\101\102\103\n"</span>);<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"\x41\x42\x43\n"</span>);<br></code></pre></td></tr></table></figure><p>上面三行都会输出“ABC”。</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-built_in">printf</span>(<span class="hljs-string">"\u2022 Bullet 1\n"</span>);<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"\U00002022 Bullet 1\n"</span>);<br></code></pre></td></tr></table></figure><p>上面两行都会输出“• Bullet 1”。</p><h2 id="多字节字符的表示"><a href="#多字节字符的表示" class="headerlink" title="多字节字符的表示"></a>多字节字符的表示</h2><p>C 语言预设只有基本字符,才能使用字面量表示,其它字符都应该使用码点表示,并且当前系统还必须支持该码点的编码方法。</p><p>所谓基本字符,指的是所有可打印的 ASCII 字符,但是有三个字符除外:<code>@</code>、<code>$</code>、<code>`</code>。</p><p>因此,遇到非英语字符,应该将其写成 Unicode 码点形式。</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">char</span>* s = <span class="hljs-string">"\u6625\u5929"</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, s); <span class="hljs-comment">// 春天</span><br></code></pre></td></tr></table></figure><p>上面代码会输出中文“春天”。</p><p>如果当前系统是 UTF-8 编码,可以直接用字面量表示多字节字符。</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">char</span>* s = <span class="hljs-string">"春天"</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, s);<br></code></pre></td></tr></table></figure><p>注意,<code>\u + 码点</code>和<code>\U + 码点</code>的写法,不能用来表示 ASCII 码字符(码点小于<code>0xA0</code>的字符),只有三个字符除外:<code>0x24</code>(<code>$</code>),<code>0x40</code>(<code>@</code>)和<code>0x60</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span>* s = <span class="hljs-string">"\u0024\u0040\u0060"</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, s); <span class="hljs-comment">// @$`</span><br></code></pre></td></tr></table></figure><p>上面代码会输出三个 Unicode 字符“@$`”,但是其它 ASCII 字符都不能用这种表示法表示。</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">setlocale(LC_ALL, <span class="hljs-string">""</span>);<br></code></pre></td></tr></table></figure><p>上面代码中,使用<code>setlocale()</code>切换执行环境到系统的本地化语言。<code>setlocale()</code>的原型定义在头文件<code>locale.h</code>,详见标准库部分的《locale.h》章节。</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">setlocale(LC_ALL, <span class="hljs-string">"zh_CN.UTF-8"</span>);<br></code></pre></td></tr></table></figure><p>上面代码将程序执行环境,切换到中文环境的 UTF-8 编码。</p><p>C 语言允许使用<code>u8</code>前缀,对多字节字符串指定编码方式为 UTF-8。</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">char</span>* s = <span class="hljs-string">u8"春天"</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, s);<br></code></pre></td></tr></table></figure><p>一旦字符串里面包含多字节字符,就意味着字符串的字节数与字符数不再一一对应了。比如,字符串的长度为10字节,就不再是包含10个字符,而可能只包含7个字符、5个字符等等。</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">setlocale(LC_ALL, <span class="hljs-string">""</span>);<br><br><span class="hljs-type">char</span>* s = <span class="hljs-string">"春天"</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, <span class="hljs-built_in">strlen</span>(s)); <span class="hljs-comment">// 6</span><br></code></pre></td></tr></table></figure><p>上面示例中,字符串<code>s</code>只包含两个字符,但是<code>strlen()</code>返回的结果却是6,表示这两个字符一共占据了6个字节。</p><p>C 语言的字符串函数只针对单字节字符有效,对于多字节字符都会失效,比如<code>strtok()</code>、<code>strchr()</code>、<code>strspn()</code>、<code>toupper()</code>、<code>tolower()</code>、<code>isalpha()</code>等不会得到正确结果。</p><h2 id="宽字符"><a href="#宽字符" class="headerlink" title="宽字符"></a>宽字符</h2><p>上一小节的多字节字符串,每个字符的字节宽度是可变的。这种编码方式虽然使用起来方便,但是很不利于字符串处理,因此必须逐一检查每个字符占用的字节数。所以除了这种方式,C 语言还提供了确定宽度的多字节字符存储方式,称为宽字符(wide character)。</p><p>所谓“宽字符”,就是每个字符占用的字节数是固定的,要么是2个字节,要么是4个字节。这样的话,就很容易快速处理。</p><p>宽字符有一个单独的数据类型 wchar_t,每个宽字符都是这个类型。它属于整数类型的别名,可能是有符号的,也可能是无符号的,由当前实现决定。该类型的长度为16位(2个字节)或32位(4个字节),足以容纳当前系统的所有字符。它定义在头文件<code>wchar.h</code>里面。</p><p>宽字符的字面量必须加上前缀“L”,否则 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><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c">setlocale(LC_ALL, <span class="hljs-string">""</span>);<br><br><span class="hljs-type">wchar_t</span> c = <span class="hljs-string">L'牛'</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%lc\n"</span>, c);<br><br><span class="hljs-type">wchar_t</span>* s = <span class="hljs-string">L"春天"</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%ls\n"</span>, s);<br></code></pre></td></tr></table></figure><p>上面示例中,前缀“L”在单引号前面,表示宽字符,对应<code>printf()</code>的占位符为<code>%lc</code>;在双引号前面,表示宽字符串,对应<code>printf()</code>的占位符为<code>%ls</code>。</p><p>宽字符串的结尾也有一个空字符,不过是宽空字符,占用多个字节。</p><p>处理宽字符,需要使用宽字符专用的函数,绝大部分都定义在头文件<code>wchar.h</code>。</p><h2 id="多字节字符处理函数"><a href="#多字节字符处理函数" class="headerlink" title="多字节字符处理函数"></a>多字节字符处理函数</h2><h3 id="mblen"><a href="#mblen" class="headerlink" title="mblen()"></a>mblen()</h3><p><code>mblen()</code>函数返回一个多字节字符占用的字节数。它的原型定义在头文件<code>stdlib.h</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-type">int</span> <span class="hljs-title function_">mblen</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* mbstr, <span class="hljs-type">size_t</span> n)</span>;<br></code></pre></td></tr></table></figure><p>它接受两个参数,第一个参数是多字节字符串指针,一般会检查该字符串的第一个字符;第二个参数是需要检查的字节数,这个数字不能大于当前系统单个字符占用的最大字节,一般使用<code>MB_CUR_MAX</code>。</p><p>它的返回值是该字符占用的字节数。如果当前字符是空的宽字符,则返回<code>0</code>;如果当前字符不是有效的多字节字符,则返回<code>-1</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">setlocale(LC_ALL, <span class="hljs-string">""</span>);<br><br><span class="hljs-type">char</span>* mbs1 = <span class="hljs-string">"春天"</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, mblen(mbs1, MB_CUR_MAX)); <span class="hljs-comment">// 3</span><br><br><span class="hljs-type">char</span>* mbs2 = <span class="hljs-string">"abc"</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, mblen(mbs2, MB_CUR_MAX)); <span class="hljs-comment">// 1</span><br></code></pre></td></tr></table></figure><p>上面示例中,字符串“春天”的第一个字符“春”,占用3个字节;字符串“abc”的第一个字符“a”,占用1个字节。</p><h3 id="wctomb"><a href="#wctomb" class="headerlink" title="wctomb()"></a>wctomb()</h3><p><code>wctomb()</code>函数(wide character to multibyte)用于将宽字符转为多字节字符。它的原型定义在头文件<code>stdlib.h</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-type">int</span> <span class="hljs-title function_">wctomb</span><span class="hljs-params">(<span class="hljs-type">char</span>* s, <span class="hljs-type">wchar_t</span> wc)</span>;<br></code></pre></td></tr></table></figure><p><code>wctomb()</code>接受两个参数,第一个参数是作为目标的多字节字符数组,第二个参数是需要转换的一个宽字符。它的返回值是多字节字符存储占用的字节数量,如果无法转换,则返回<code>-1</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">setlocale(LC_ALL, <span class="hljs-string">""</span>);<br><br><span class="hljs-type">wchar_t</span> wc = <span class="hljs-string">L'牛'</span>;<br><span class="hljs-type">char</span> mbStr[<span class="hljs-number">10</span>] = <span class="hljs-string">""</span>;<br><br><span class="hljs-type">int</span> nBytes = <span class="hljs-number">0</span>;<br>nBytes = wctomb(mbStr, wc);<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, mbStr); <span class="hljs-comment">// 牛</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, nBytes); <span class="hljs-comment">// 3</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>wctomb()</code>将宽字符“牛”转为多字节字符,<code>wctomb()</code>的返回值表示转换后的多字节字符占用3个字节。</p><h3 id="mbtowc"><a href="#mbtowc" class="headerlink" title="mbtowc()"></a>mbtowc()</h3><p><code>mbtowc()</code>用于将多字节字符转为宽字符。它的原型定义在头文件<code>stdlib.h</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-type">int</span> <span class="hljs-title function_">mbtowc</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">wchar_t</span>* wchar,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">char</span>* mbchar,</span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> count</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>它接受3个参数,第一个参数是作为目标的宽字符指针,第二个参数是待转换的多字节字符指针,第三个参数是多字节字符的字节数。</p><p>它的返回值是多字节字符的字节数,如果转换失败,则返回<code>-1</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">setlocale(LC_ALL, <span class="hljs-string">""</span>);<br><br><span class="hljs-type">char</span>* mbchar = <span class="hljs-string">"牛"</span>;<br><span class="hljs-type">wchar_t</span> wc;<br><span class="hljs-type">wchar_t</span>* pwc = &wc;<br><br><span class="hljs-type">int</span> nBytes = <span class="hljs-number">0</span>;<br>nBytes = mbtowc(pwc, mbchar, <span class="hljs-number">3</span>);<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, nBytes); <span class="hljs-comment">// 3</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%lc\n"</span>, *pwc); <span class="hljs-comment">// 牛</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>mbtowc()</code>将多字节字符“牛”转为宽字符<code>wc</code>,返回值是<code>mbchar</code>占用的字节数(占用3个字节)。</p><h3 id="wcstombs"><a href="#wcstombs" class="headerlink" title="wcstombs()"></a>wcstombs()</h3><p><code>wcstombs()</code>用来将宽字符串转换为多字节字符串。它的原型定义在头文件<code>stdlib.h</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-type">size_t</span> <span class="hljs-title function_">wcstombs</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">char</span>* mbstr,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">wchar_t</span>* wcstr,</span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> count</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>它接受三个参数,第一个参数<code>mbstr</code>是目标的多字节字符串指针,第二个参数<code>wcstr</code>是待转换的宽字符串指针,第三个参数<code>count</code>是用来存储多字节字符串的最大字节数。</p><p>如果转换成功,它的返回值是成功转换后的多字节字符串的字节数,不包括尾部的字符串终止符;如果转换失败,则返回<code>-1</code>。</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">setlocale(LC_ALL, <span class="hljs-string">""</span>);<br><br><span class="hljs-type">char</span> mbs[<span class="hljs-number">20</span>];<br><span class="hljs-type">wchar_t</span>* wcs = <span class="hljs-string">L"春天"</span>;<br><br><span class="hljs-type">int</span> nBytes = <span class="hljs-number">0</span>;<br>nBytes = wcstombs(mbs, wcs, <span class="hljs-number">20</span>);<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, mbs); <span class="hljs-comment">// 春天</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, nBytes); <span class="hljs-comment">// 6</span><br></code></pre></td></tr></table></figure><p>上面示例中,<code>wcstombs()</code>将宽字符串<code>wcs</code>转为多字节字符串<code>mbs</code>,返回值<code>6</code>表示写入<code>mbs</code>的字符串占用6个字节,不包括尾部的字符串终止符。</p><p>如果<code>wcstombs()</code>的第一个参数是 NULL,则返回转换成功所需要的目标字符串的字节数。</p><h3 id="mbstowcs"><a href="#mbstowcs" class="headerlink" title="mbstowcs()"></a>mbstowcs()</h3><p><code>mbstowcs()</code>用来将多字节字符串转换为宽字符串。它的原型定义在头文件<code>stdlib.h</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-type">size_t</span> <span class="hljs-title function_">mbstowcs</span><span class="hljs-params">(</span><br><span class="hljs-params"> <span class="hljs-type">wchar_t</span>* wcstr,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-type">char</span>* mbstr,</span><br><span class="hljs-params"> <span class="hljs-type">size_t</span> count</span><br><span class="hljs-params">)</span>;<br></code></pre></td></tr></table></figure><p>它接受三个参数,第一个参数<code>wcstr</code>是目标宽字符串,第二个参数<code>mbstr</code>是待转换的多字节字符串,第三个参数是待转换的多字节字符串的最大字符数。</p><p>转换成功时,它的返回值是成功转换的多字节字符的数量;转换失败时,返回<code>-1</code>。如果返回值与第三个参数相同,那么转换后的宽字符串不是以 NULL 结尾的。</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">setlocale(LC_ALL, <span class="hljs-string">""</span>);<br><br><span class="hljs-type">char</span>* mbs = <span class="hljs-string">"天气不错"</span>;<br><span class="hljs-type">wchar_t</span> wcs[<span class="hljs-number">20</span>];<br><br><span class="hljs-type">int</span> nBytes = <span class="hljs-number">0</span>;<br>nBytes = mbstowcs(wcs, mbs, <span class="hljs-number">20</span>);<br><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%ls\n"</span>, wcs); <span class="hljs-comment">// 天气不错</span><br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, nBytes); <span class="hljs-comment">// 4</span><br></code></pre></td></tr></table></figure><p>上面示例中,多字节字符串<code>mbs</code>被<code>mbstowcs()</code>转为宽字符串,成功转换了4个字符,所以该函数的返回值为4。</p><p>如果<code>mbstowcs()</code>的第一个参数为<code>NULL</code>,则返回目标宽字符串会包含的字符数量。</p>]]></content>
<categories>
<category>Programming</category>
</categories>
<tags>
<tag>C</tag>
</tags>
</entry>
<entry>
<title>KataGo 安装</title>
<link href="/2022/12/15/katago/"/>
<url>/2022/12/15/katago/</url>
<content type="html"><![CDATA[<p>现在开源的最强围棋AI。elo大概13k+?</p><span id="more"></span><p>源代码可用。 <a href="https://github.com/lightvector/KataGo">Github link</a>.</p><h2 id="获取模型文件"><a href="#获取模型文件" class="headerlink" title="获取模型文件"></a>获取模型文件</h2><p>如果你有现代GPU(比如GCN1.0/Volta往后的),我建议用 OpenCL 版本。核显也是能跑的。<br>没卡或者卡太老的话,用 Eigen 这个用 CPU 跑的版本。还有支持 AVX2 的,要求4代 Core 以后了。<br>CUDA 版本配置过于繁琐,而且必须要用N卡。<br>Windows:</p><a class="btn" href="https://github.com/lightvector/KataGo/releases/download/v1.14.0/katago-v1.14.0-opencl-windows-x64.zip" target="_blank">openCL</a><p>&&</p><a class="btn" href="https://github.com/lightvector/KataGo/releases/download/v1.14.0/katago-v1.14.0-eigenavx2-windows-x64.zip" target="_blank">Eigen AVX2</a><p>Linux:</p><a class="btn" href="https://github.com/lightvector/KataGo/releases/download/v1.14.0/katago-v1.14.0-opencl-linux-x64.zip" target="_blank">openCL</a><p>&&</p><a class="btn" href="https://github.com/lightvector/KataGo/releases/download/v1.14.0/katago-v1.14.0-eigenavx2-linux-x64.zip" target="_blank">Eigen AVX2</a><p><strong>把这些解压,一会要用</strong></p><h2 id="下-KaTrain"><a href="#下-KaTrain" class="headerlink" title="下 KaTrain"></a>下 KaTrain</h2><p>官方推荐的 KataGO GUI<br><img src="/../img/katago/1.png"></p><a class="btn" href="https://github.com/sanderland/katrain" target="_blank">Github Link</a><p>三平台运行支持,但是</p><ul><li>官方没有 Linux 版,需要自己编译;</li><li>macOS 版本不支持 aarch64,要用 Rosetta 2 转译</li></ul><p>Windows:</p><a class="btn" href="https://github.com/sanderland/katrain/releases/download/v1.14.0/KaTrain.exe" target="_blank">Download</a><p>解压然后运行 <code>KaTrain.exe</code> -><br><img src="/../img/katago/2.png"><br>打开菜单,选择 <code>General & Engine Settings</code> -><br><img src="/../img/katago/3.png"><br>把模型路径放到<code>Path to KataGo executable</code>里.</p><h2 id="还有"><a href="#还有" class="headerlink" title="还有"></a>还有</h2><p><code>Maximum number of visits in analysis</code>越大,这个 AI 越强<br><img src="/../img/katago/4.png"></p>]]></content>
<categories>
<category>Software</category>
</categories>
<tags>
<tag>AI</tag>
</tags>
</entry>
<entry>
<title>C++ 解决一元二次方程</title>
<link href="/2022/12/05/cpp-quadratic-equation/"/>
<url>/2022/12/05/cpp-quadratic-equation/</url>
<content type="html"><![CDATA[<p>Rust 写过了的,还有个 C++ 版本。</p><span id="more"></span><h2 id="最终代码"><a href="#最终代码" class="headerlink" title="最终代码"></a>最终代码</h2><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><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></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-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><cmath></span></span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">nya</span><span class="hljs-params">(<span class="hljs-type">double</span> a, <span class="hljs-type">double</span> b, <span class="hljs-type">double</span> c)</span> </span>{<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> delta = (b * b) - (<span class="hljs-number">4.0</span> * a * c);<br> <span class="hljs-keyword">if</span> (delta > <span class="hljs-number">0.0</span>) {<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> x1 = ((-b) + <span class="hljs-built_in">sqrt</span>(delta)) / (<span class="hljs-number">2.0</span> * a);<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> x2 = ((-b) - <span class="hljs-built_in">sqrt</span>(delta)) / (<span class="hljs-number">2.0</span> * a);<br> cout << <span class="hljs-string">"Two results: x1 = "</span> << x1 << <span class="hljs-string">"; x2 = "</span> << x2 << <span class="hljs-string">"."</span> << endl;<br> }<br> <span class="hljs-keyword">if</span> (delta == <span class="hljs-number">0.0</span>) {<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> x = (-b) / (<span class="hljs-number">2.0</span> * a);<br> cout << <span class="hljs-string">"One result: x = "</span> << x << <span class="hljs-string">"."</span> << endl;<br> }<br> <span class="hljs-keyword">if</span> (delta < <span class="hljs-number">0.0</span>) {<br> cout << <span class="hljs-string">"No result."</span> << endl;<br> }<br>}<br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> a, b, c;<br> <span class="hljs-type">char</span> r;<br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {<br> cout << <span class="hljs-string">"Please input a = "</span>;<br> cin >> a;<br> cout << <span class="hljs-string">"Please input b = "</span>;<br> cin >> b;<br> cout << <span class="hljs-string">"Please input c = "</span>;<br> cin >> c;<br><br> <span class="hljs-built_in">nya</span>(a, b, c);<br> cout << <span class="hljs-string">"Continue?[Y/n]"</span> << endl;<br> cin >> r;<br> <span class="hljs-keyword">if</span> (r == <span class="hljs-string">'n'</span>) {<br> <span class="hljs-keyword">break</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h2 id="细节"><a href="#细节" class="headerlink" title="细节"></a>细节</h2><h3 id="创建文件夹("><a href="#创建文件夹(" class="headerlink" title="创建文件夹("></a>创建文件夹(</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ <span class="hljs-built_in">cd</span> ~<br>$ <span class="hljs-built_in">mkdir</span> cpp-quadratic-equation<br>$ <span class="hljs-built_in">cd</span> cpp-quadratic-equation<br>$ <span class="hljs-built_in">touch</span> a.cpp<br></code></pre></td></tr></table></figure><h3 id="主函数部分"><a href="#主函数部分" class="headerlink" title="主函数部分"></a>主函数部分</h3><p>先创建一个<code>main</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-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> <br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>声明 <code>a</code> <code>b</code> <code>c</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-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> a, b, c;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>我想创建一个可重复的函数(与<code>Rust</code>版本的函数不同)。<br>如果用户希望重复计算过程,只需输入任何内容并按回车键,输入<code>n</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></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> a, b, c;<br> <span class="hljs-type">char</span> r;<br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {<br> <br> cout << <span class="hljs-string">"Continue?[Y/n]"</span> << endl;<br> cin >> r;<br> <span class="hljs-keyword">if</span> (r == <span class="hljs-string">'n'</span>) {<br> <span class="hljs-keyword">break</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</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><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"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> a, b, c;<br> <span class="hljs-type">char</span> r;<br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {<br> cout << <span class="hljs-string">"Please input a = "</span>;<br> cin >> a;<br> cout << <span class="hljs-string">"Please input b = "</span>;<br> cin >> b;<br> cout << <span class="hljs-string">"Please input c = "</span>;<br> cin >> c;<br><br> cout << <span class="hljs-string">"Continue?[Y/n]"</span> << endl;<br> cin >> r;<br> <span class="hljs-keyword">if</span> (r == <span class="hljs-string">'n'</span>) {<br> <span class="hljs-keyword">break</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="主运算函数部分"><a href="#主运算函数部分" class="headerlink" title="主运算函数部分"></a>主运算函数部分</h3><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-function"><span class="hljs-type">void</span> <span class="hljs-title">nya</span><span class="hljs-params">(<span class="hljs-type">double</span> a, <span class="hljs-type">double</span> b, <span class="hljs-type">double</span> c)</span> </span>{<br><br>}<br></code></pre></td></tr></table></figure><p>声明<code>delta</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"><span class="hljs-type">void</span> <span class="hljs-title">nya</span><span class="hljs-params">(<span class="hljs-type">double</span> a, <span class="hljs-type">double</span> b, <span class="hljs-type">double</span> c)</span> </span>{<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> delta = (b * b) - (<span class="hljs-number">4.0</span> * a * c);<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><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">void</span> <span class="hljs-title">nya</span><span class="hljs-params">(<span class="hljs-type">double</span> a, <span class="hljs-type">double</span> b, <span class="hljs-type">double</span> c)</span> </span>{<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> delta = (b * b) - (<span class="hljs-number">4.0</span> * a * c);<br> <span class="hljs-keyword">if</span> (delta > <span class="hljs-number">0.0</span>) {<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> x1 = ((-b) + <span class="hljs-built_in">sqrt</span>(delta)) / (<span class="hljs-number">2.0</span> * a);<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> x2 = ((-b) - <span class="hljs-built_in">sqrt</span>(delta)) / (<span class="hljs-number">2.0</span> * a);<br> cout << <span class="hljs-string">"Two results: x1 = "</span> << x1 << <span class="hljs-string">"; x2 = "</span> << x2 << <span class="hljs-string">"."</span> << endl;<br> }<br> <span class="hljs-keyword">if</span> (delta == <span class="hljs-number">0.0</span>) {<br> <span class="hljs-type">long</span> <span class="hljs-type">double</span> x = (-b) / (<span class="hljs-number">2.0</span> * a);<br> cout << <span class="hljs-string">"One result: x = "</span> << x << <span class="hljs-string">"."</span> << endl;<br> }<br> <span class="hljs-keyword">if</span> (delta < <span class="hljs-number">0.0</span>) {<br> cout << <span class="hljs-string">"No result."</span> << endl;<br> }<br>}<br></code></pre></td></tr></table></figure><p>主运算函数完成了</p><hr><h3 id="合体!"><a href="#合体!" class="headerlink" title="合体!"></a>合体!</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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-function"><span class="hljs-type">void</span> <span class="hljs-title">nya</span><span class="hljs-params">(<span class="hljs-type">double</span> a, <span class="hljs-type">double</span> b, <span class="hljs-type">double</span> c)</span> </span>{<br> <span class="hljs-comment">//......</span><br>}<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-comment">//......</span><br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {<br> <span class="hljs-comment">//......</span><br> <span class="hljs-built_in">nya</span>(a, b, c);<br> cout << <span class="hljs-string">"Continue?[Y/n]"</span> << endl;<br> cin >> r;<br> <span class="hljs-keyword">if</span> (r == <span class="hljs-string">'n'</span>) {<br> <span class="hljs-keyword">break</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><div class="note note-warning"> <p><code>nya</code>函数必须写在主函数前面,这是 C++ 标准的规定,要不不能调用(</p> </div><hr><h3 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h3><p>不同操作系统有不同的规矩,这里用 macOS + Clang 示例</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ clang++ a.cpp<br>$ ./a.out<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Programming</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>Arch Linux 部署 Docker</title>
<link href="/2022/11/29/archlinux-deploy-docker/"/>
<url>/2022/11/29/archlinux-deploy-docker/</url>
<content type="html"><![CDATA[<p>这个容器的设计非常出色,借助主机内核来运行所有“无头”程序,而不必担心安全问题。</p><span id="more"></span><h2 id="先决条件"><a href="#先决条件" class="headerlink" title="先决条件"></a>先决条件</h2><ul><li>Arch Linux x86_64</li><li>大于2GB的RAM</li><li><code>root</code> 权限</li></ul><h2 id="开始安装"><a href="#开始安装" class="headerlink" title="开始安装"></a>开始安装</h2><ol><li>通过 <code>pacman</code> 安装 <code>docker</code> 软件包 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo pacman -S docker<br></code></pre></td></tr></table></figure></li><li>在系统启动时自动启动 <code>docker</code> 守护程序 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo systemctl <span class="hljs-built_in">enable</span> docker<br></code></pre></td></tr></table></figure></li><li>启动 <code>docker</code> 服务 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo systemctl start docker<br></code></pre></td></tr></table></figure></li><li>加入能够使用 <code>docker</code> 守护程序的 <code>docker</code> 组 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo usermod -G docker -a <span class="hljs-variable">$USER</span><br></code></pre></td></tr></table></figure></li><li>重新启动 <code>docker</code> 守护程序 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo systemctl restart docker<br></code></pre></td></tr></table></figure></li></ol><hr><h2 id="后续部分"><a href="#后续部分" class="headerlink" title="后续部分"></a>后续部分</h2><ol><li>验证 <code>docker</code> 是否在运行 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker version<br></code></pre></td></tr></table></figure></li><li>运行下面的命令将从 <code>dockerhub</code> 拉取并运行“Hello World” docker 容器 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker run archlinux<br></code></pre></td></tr></table></figure></li></ol><h2 id="一些技巧"><a href="#一些技巧" class="headerlink" title="一些技巧"></a>一些技巧</h2><ol><li>列出容器镜像 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker image <span class="hljs-built_in">ls</span><br></code></pre></td></tr></table></figure> <img src="/../img/archlinux-deploy-docker/1.png"></li><li>列出所有容器 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker container <span class="hljs-built_in">ls</span> -a<br></code></pre></td></tr></table></figure> <img src="/../img/archlinux-deploy-docker/2.png"></li><li>删除容器 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker container <span class="hljs-built_in">rm</span> CONTAINER_NUMBER<br></code></pre></td></tr></table></figure> 对于这个 <code>CONTAINER_NUMBER</code>,也支持简写。</li><li>删除镜像 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker image <span class="hljs-built_in">rm</span> -f IMAGE_NUMBER<br></code></pre></td></tr></table></figure> 这个 <code>IMAGE_NUMBER</code> 也支持简写。</li><li>启动 <code>Linux</code> 镜像并进行 <code>chroot</code> 进入 -> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker run -it --name arch-test archlinux<br></code></pre></td></tr></table></figure> 这个命令是创建一个名为 <code>arch-test</code> 的容器,使用 <code>archlinux</code> 镜像作为模板并进行 <code>chroot</code> 进入。<br> <img src="/../img/archlinux-deploy-docker/3.png"><br> 使用 <code>docker ps</code> 命令查看容器信息。<br> <img src="/../img/archlinux-deploy-docker/4.png"></li></ol>]]></content>
<categories>
<category>Software</category>
</categories>
<tags>
<tag>Docker</tag>
</tags>
</entry>
<entry>
<title>Rust 解决一元二次方程</title>
<link href="/2022/11/05/rust-quadratic-equation/"/>
<url>/2022/11/05/rust-quadratic-equation/</url>
<content type="html"><![CDATA[<p>这个是 Rust 版本,还有个 C++ 版本</p><span id="more"></span><p><a href="https://github.com/acidec/rust-quadratic-equation">Github link</a><br>这里是源码和二进制,有 Windows | GNU/Linux | macOS 版本<br><del>macOS 版本只有一个 aarch64 版本,因为我没 x86 Mac</del></p><hr><h2 id="最终代码"><a href="#最终代码" class="headerlink" title="最终代码"></a>最终代码</h2><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">a</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>();<br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">b</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>();<br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">c</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>();<br><br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Please input a"</span>);<br> std::io::<span class="hljs-title function_ invoke__">stdin</span>().<span class="hljs-title function_ invoke__">read_line</span>(&<span class="hljs-keyword">mut</span> a).<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Failed to read line"</span>);<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">a</span>: <span class="hljs-type">f64</span> = a.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Please type a number!"</span>);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Please input b"</span>);<br> std::io::<span class="hljs-title function_ invoke__">stdin</span>().<span class="hljs-title function_ invoke__">read_line</span>(&<span class="hljs-keyword">mut</span> b).<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Failed to read line"</span>);<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">b</span>: <span class="hljs-type">f64</span> = b.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Please type a number!"</span>);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Please input c"</span>);<br> std::io::<span class="hljs-title function_ invoke__">stdin</span>().<span class="hljs-title function_ invoke__">read_line</span>(&<span class="hljs-keyword">mut</span> c).<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Failed to read line"</span>);<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">c</span>: <span class="hljs-type">f64</span> = c.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Please type a number!"</span>);<br> <span class="hljs-title function_ invoke__">nya</span>(a, b, c);<br>}<br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">nya</span>(a: <span class="hljs-type">f64</span>, b: <span class="hljs-type">f64</span>, c: <span class="hljs-type">f64</span>) {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">delta</span> = (b * b) - (<span class="hljs-number">4.0</span> * a * c);<br> <span class="hljs-keyword">if</span> delta > <span class="hljs-number">0.0</span> {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">x1</span> = ((-b) + <span class="hljs-type">f64</span>::<span class="hljs-title function_ invoke__">sqrt</span>(delta)) / (<span class="hljs-number">2.0</span> * a);<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">x2</span> = ((-b) - <span class="hljs-type">f64</span>::<span class="hljs-title function_ invoke__">sqrt</span>(delta)) / (<span class="hljs-number">2.0</span> * a);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Two results: x1 = {}; x2 = {}."</span>, x1, x2);<br> }<br> <span class="hljs-keyword">if</span> delta == <span class="hljs-number">0.0</span> {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">x</span> = (-b) / (<span class="hljs-number">2.0</span> * a);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"One result: x = {}."</span>, x);<br> }<br> <span class="hljs-keyword">if</span> delta < <span class="hljs-number">0.0</span> {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"No result."</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><hr><h2 id="细节"><a href="#细节" class="headerlink" title="细节"></a>细节</h2><h3 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ <span class="hljs-built_in">cd</span> ~/<br>$ <span class="hljs-built_in">mkdir</span> <span class="hljs-built_in">test</span><br>$ <span class="hljs-built_in">cd</span> <span class="hljs-built_in">test</span><br>$ cargo init<br></code></pre></td></tr></table></figure><hr><h3 id="主函数部分"><a href="#主函数部分" class="headerlink" title="主函数部分"></a>主函数部分</h3><p>创建,然后没有返回值(</p><figure class="highlight rust"><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 rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br><br>}<br></code></pre></td></tr></table></figure><p>声明 <code>a</code> <code>b</code> <code>c</code> 变量</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><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 rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">a</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>();<br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">b</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>();<br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">c</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>();<br>}<br></code></pre></td></tr></table></figure><p>因为后续需要将<code>String</code>类型转换为浮点数,所以我们使用<code>let mut</code>确保系数是可变的变量。</p><p>然后获取输入呐~</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><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 rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">a</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>();<br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">b</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>();<br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">c</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>();<br><br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Please input a"</span>);<br> std::io::<span class="hljs-title function_ invoke__">stdin</span>().<span class="hljs-title function_ invoke__">read_line</span>(&<span class="hljs-keyword">mut</span> a).<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Failed to read line"</span>);<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">a</span>: <span class="hljs-type">f64</span> = a.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Please type a number!"</span>);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Please input b"</span>);<br> std::io::<span class="hljs-title function_ invoke__">stdin</span>().<span class="hljs-title function_ invoke__">read_line</span>(&<span class="hljs-keyword">mut</span> b).<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Failed to read line"</span>);<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">b</span>: <span class="hljs-type">f64</span> = b.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Please type a number!"</span>);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Please input c"</span>);<br> std::io::<span class="hljs-title function_ invoke__">stdin</span>().<span class="hljs-title function_ invoke__">read_line</span>(&<span class="hljs-keyword">mut</span> c).<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Failed to read line"</span>);<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">c</span>: <span class="hljs-type">f64</span> = c.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">expect</span>(<span class="hljs-string">"Please type a number!"</span>);<br>}<br></code></pre></td></tr></table></figure><div class="note note-info"> <p>Rust 的<code>std::io::stdin</code>提供了很好的工具。当获取的数据类型不匹配时,可以使用<code>.expect</code>输出日志以告知用户需要的数据类型。</p> </div><p>重新声明常量时,不需要添加<code>mut</code>,毕竟这个值在赋值后不会改变。这种语法在 C++ 中不起作用,因为你不能重新定义一个量。但在 Rust 中可以被覆盖,前提是<strong>之前的声明必须表明该量是可变的</strong>(用<code>let mut</code>)</p><hr><h3 id="主运算函数部分"><a href="#主运算函数部分" class="headerlink" title="主运算函数部分"></a>主运算函数部分</h3><figure class="highlight rust"><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 rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">nya</span> (a: <span class="hljs-type">f64</span>, b: <span class="hljs-type">f64</span>, c: <span class="hljs-type">f64</span>) {<br> <br>}<br></code></pre></td></tr></table></figure><p>指定数据类型。现代机器用<code>f64</code>不会有问题</p><figure class="highlight rust"><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 rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">nya</span> (a: <span class="hljs-type">f64</span>, b: <span class="hljs-type">f64</span>, c: <span class="hljs-type">f64</span>) {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">delta</span> = (b * b) - (<span class="hljs-number">4.0</span> * a * c);<br>}<br></code></pre></td></tr></table></figure><p>将<code>delta</code>声明为常量</p><div class="note note-info"> <p>这里出现的所有数字的格式都是<code>*.0</code>,这是因为 Rust 规定某一数据类型的量只能与相同数据类型的量进行操作,因为<code>a</code> <code>b</code> <code>c</code>都是<code>floating-point</code>类型,所以它们只能与类型常量一起执行操作。</p> </div><p>逻辑判断部分</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><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 rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">nya</span>(a: <span class="hljs-type">f64</span>, b: <span class="hljs-type">f64</span>, c: <span class="hljs-type">f64</span>) {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">delta</span> = (b * b) - (<span class="hljs-number">4.0</span> * a * c);<br> <span class="hljs-keyword">if</span> delta > <span class="hljs-number">0.0</span> {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">x1</span> = ((-b) + <span class="hljs-type">f64</span>::<span class="hljs-title function_ invoke__">sqrt</span>(delta)) / (<span class="hljs-number">2.0</span> * a);<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">x2</span> = ((-b) - <span class="hljs-type">f64</span>::<span class="hljs-title function_ invoke__">sqrt</span>(delta)) / (<span class="hljs-number">2.0</span> * a);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Two results: x1 = {}; x2 = {}."</span>, x1, x2);<br> }<br> <span class="hljs-keyword">if</span> delta == <span class="hljs-number">0.0</span> {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">x</span> = (-b) / (<span class="hljs-number">2.0</span> * a);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"One result: x = {}."</span>, x);<br> }<br> <span class="hljs-keyword">if</span> delta < <span class="hljs-number">0.0</span> {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"No result."</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><p>主运算函数完成</p><hr><h3 id="合体!"><a href="#合体!" class="headerlink" title="合体!"></a>合体!</h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><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 rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-comment">//......</span><br> <span class="hljs-title function_ invoke__">nya</span>(a, b, c);<br>}<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">nya</span>(a: <span class="hljs-type">f64</span>, b: <span class="hljs-type">f64</span>, c: <span class="hljs-type">f64</span>) {<br> <span class="hljs-comment">//......</span><br>}<br></code></pre></td></tr></table></figure><hr><h3 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ cargo build<br>$ cargo run<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Programming</category>
</categories>
<tags>
<tag>Rust</tag>
</tags>
</entry>
<entry>
<title>如何用 C++ 快速撑爆内存?</title>
<link href="/2009/03/16/cpp-fast-out-of-memory/"/>
<url>/2009/03/16/cpp-fast-out-of-memory/</url>
<content type="html"><![CDATA[<p>节约内存,请((</p><span id="more"></span><h2 id="Codes"><a href="#Codes" class="headerlink" title="Codes"></a>Codes</h2><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">nya</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {<br> <span class="hljs-type">int</span>* ptr = <span class="hljs-keyword">new</span> <span class="hljs-type">int</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-built_in">nya</span>();<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p><strong>反复创建新指针并分配一些内存空间。由于 <code>while(true)</code> 是无限循环的,内存会很快被填满。</strong></p><div class="note note-warning"> <p>由于这个程序会占满内存,不会自动<code>kill</code>进程的操作系统(如Windows、macOS)将使用<code>swap</code>进行内存交换。<br>这可能导致大量写入磁盘!<br>在Windows上,它会导致系统不稳定甚至出现黑屏。在严重情况下,未保存的数据可能会丢失并且可能出现蓝屏。请确保在运行此程序之前已保存所有数据。</p> </div><h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><p><strong>在现代Linux发行版(x86_64 Linux-6.6.1-ARCH),当内存满时,进程将被终止。<br>在macOS(arm64 Darwin 22.1.0)上,它将占用<code>swap</code>到最大限制,并且进程将不会被终止。<br>Windows(x86_64 NT10)不会终止进程,在内存满时屏幕会变黑。可能已经无法给核显驱动内存了罢(笑</strong></p>]]></content>
<categories>
<category>Programming</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
</search>