PageSourceSearch

https://lonion1124.github.io/posts/538a62c2af1c/

html lonion1124.github.io collected 2026-10-03 09:31:58 UTC 245,643 bytes, 3,069 lines download raw bytes

1<!DOCTYPE html>
2<html lang="zh-cn">
3
4<!-- head -->
5<head>
6  <meta charset="utf-8">
7  <title>洋葱菌的博客</title>
8
9  <meta name="viewport" content="width=device-width, initial-scale=1">
10
11  <style>
12    * {
13      box-sizing: border-box;
14    }
15
16    html,
17    body {
18      width: 100%;
19      height: 100%;
20      margin: 0;
21      padding: 0;
22      overflow-x: hidden;
23    }
24
25    .container {
26      width: 100%;
27      max-width: 100%;
28      padding-left: 0;
29      padding-right: 0;
30    }
31
32    @media (min-width: 768px) {
33      .container {
34        max-width: 100%;
35        padding-left: 0;
36        padding-right: 0;
37      }
38    }
39  </style>
40
41  <!-- Initial Light / Dark Theme Toggle -->
42  
42<script>
43    (function() {
44      try {
45        var theme = localStorage.getItem('bs-theme');
46        if (!theme) {
47          theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
48        }
49        document.documentElement.setAttribute('data-bs-theme', theme);
50      } catch (e) {}
51    })();
52  </script>
52
53
54  <!-- favicon -->
55  <link rel="icon" href="/img/favicon.ico">
56
57  <!-- Bootstrap5文件 -->
58  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
59  
59<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
59
60
61  <!-- jquery -->
62  
62<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
62
63
64  <!-- 文章页美化 -->
65  
66<script src="/js/post.js"></script>
66
67
68
69  <!-- 代码高亮 -->
70  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/panda-syntax-light.css" id="highlight-css-light" disabled>
71  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/panda-syntax-dark.css" id="highlight-css-dark" disabled>
72  
72<script src="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"></script>
72
73  
74<script src="/js/use_hl.js"></script>
74
75
76
77  <!-- Bootstrap5 图标库 -->
78  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css">
79
80  <!-- fancybox -->
81  
82<script src="/js/fancybox.js"></script>
82
83
84  <link rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css">
85  
85<script src="//lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js"> </script>
85
86
87  <!-- local search -->
88  
89<script src="/js/search.js"></script>
89
90
91  
91<script type="text/javascript">
92    var search_path = "search.xml";
93    if (search_path.length == 0) {
94      search_path = "search.xml";
95    }
96    var path = "/" + search_path;
97    searchFunc(path, 'local-search-input', 'local-search-result');
98  </script>
98
99
100  <!-- Google Font
101  <link rel="preconnect" href="https://fonts.googleapis.com">
102  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
103  <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:[email protected]&display=swap" rel="stylesheet">
104
105  <style>
106    .withfont {
107      font-family: "Noto Serif SC", serif;
108      /* font-optical-sizing: auto; */
109      /* font-weight: <weight>; */
110      /* font-style: normal; */
111    }
112  </style> -->
113  <!-- 落骛文楷 -->
114  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/style.css" />
115  <style>
116    body {
117      font-family: "LXGW WenKai", sans-serif;
118    }
119  </style>
120
121  <!-- busuanzi -->
122  
122<script src="//api.busuanzi.cc/static/3.6.9/busuanzi.min.js" defer></script>
122
123
124  <!-- Pagination -->
125  
126<script src="/js/pagination.js"></script>
126
127
128
129  <!-- Theme renderer -->
130  
131<script src="/js/theme.js"></script>
131
132
133
134  <!-- LaTeX -->
135  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css" integrity="sha384-5TcZemv2l/9On385z///+d7MSYlvIEw9FuZTIdZ14vJLqWphw7e7ZPuOiCHJcFCP" crossorigin="anonymous">
136  <!-- The loading of KaTeX is deferred to speed up page rendering -->
137  
137<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js" integrity="sha384-cMkvdD8LoxVzGF/RPUKAcvmm49FQ0oxwDF3BGKtDXcEc+T1b2N+teh/OJfpU0jr6" crossorigin="anonymous"></script>
137
138  <!-- To automatically render math in text elements, include the auto-render extension: -->
139  
139<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js" integrity="sha384-hCXGrW6PitJEwbkoStFjeJxv+fSOOQKOPbJxSfM6G5sWZjAyWhXiTIIAmQqnlLlh" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
139
140
141  <!-- live2d -->
142  <!-- 
142<script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/autoload.js"></script>
142 -->
143  <!-- 
143<script src="https://raw.githack.com/LOnion1124/live2d-widget/refs/heads/master/dist/autoload.js"></script>
143 -->
144  
145<script src="/live2d/dist/autoload.js"></script>
145
146
147<meta name="generator" content="Hexo 7.3.0"></head>
148
149<body class='withfont'>
150  <div class="flex-column min-vh-100">
151    <div><nav class="py-2 border-bottom bg-light-subtle">
152  <div class="container d-flex flex-wrap">
153    <ul class="nav me-auto">
154      <!-- Home -->
155      <li class="nav-item px-2 menu-item-home">
156        
157        <a class="nav-link link-body-emphasis" href="/">主页</a>
158        
159      </li>
160
161      <!-- Archive -->
162      <li class="nav-item px-2 menu-item-archives">
163        
164        <a class="nav-link link-body-emphasis" href="/archives">归档</a>
165        
166      </li>
167
168      <!-- Gallery -->
169      
170      <li class="nav-item px-2 menu-item-gallery">
171        
172        <a class="nav-link link-body-emphasis" href="/gallery">陈列</a>
173        
174      </li>
175      
176
177      <!-- About -->
178      
179      <li class="nav-item px-2 menu-item-about">
180        
181        <a class="nav-link link-body-emphasis" href="/about">关于</a>
182        
183      </li>
184      
185    </ul>
186    <ul class="nav">
187      <li class="nav-item nav-item-mailbox"><a href="#" data-bs-toggle="modal" data-bs-target="#infoModal" class="theme-link nav-link link-dark px-2"><i class="bi bi-envelope-at"></i></a></li>
188      <li class="nav-item nav-item-github"><a target="_blank" rel="noopener" href="https://github.com/LOnion1124" class="theme-link nav-link link-dark px-2"><i class="bi bi-github"></i></a></li>
189    </ul>
190  </div>
191</nav>
192
193<header class="py-3 mb-4 border-bottom shadow-sm">
194  <div class="container d-flex flex-wrap justify-content-center">
195    <span class="d-flex mb-0 me-auto">
196      <a href="/" class="lead">
197        <span id="logo-light" class="bi mx-3 align-middle" width="40" height="32">
198          <img src="/img/logo_light.png" alt="" height="32" class="nofancybox">
199        </span>
200        <span id="logo-dark" class="bi mx-3 align-middle" width="40" height="32">
201          <img src="/img/logo_dark.png" alt="" height="32" class="nofancybox">
202        </span>
203        <!-- <span class="mx-3 fs-4 fw-light d-none d-md-block"></span> -->
204      </a>
205      <!-- <div class="d-none d-lg-block">
206        <div class="alert alert-info alert-dismissible fade show m-0 p-0" role="alert">
207          <span class="text-muted fw-light">
208            <span class="align-middle mx-2">
209              喜欢这个博客主题?欢迎尝试&nbsp;
209<a class="link-underline link-underline-opacity-0" target="_blank" rel="noopener" href="https://github.com/LOnion1124/hexo-theme-onion">Theme Onion</a>!
210            </span>
211            <button type="button" class="btn" data-bs-dismiss="alert"><i class="bi bi-x-lg"></i></button>
212          </span>
213        </div>
214      </div> -->
215    </span>
216    <span>
217      
218      <button type="button" class="btn btn-outline-primary" id="search-button" data-bs-toggle="modal" data-bs-target="#searchModal">
219        <i class="bi bi-search"></i>
220        搜索
221      </button>
222      <div class="modal fade" id="searchModal">
223  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
224    <div class="modal-content">
225      <div class="modal-header">
226        <!-- <h4 class="fw-light pb-1 mb-2 d-block"><i class="bi bi-search"></i>&nbsp;搜索</h4> -->
227        <span class="fs-4 me-2 text-muted"><i class="bi bi-search"></i></span>
228        <input type="text" class="form-control" id="local-search-input" placeholder="关键词……">
229      </div>
230      <div class="modal-body">
231        <div class="search-content" id="local-search-result"></div>
232        <div class="mx-3 text-muted"><i class="bi bi-check2-all">&nbsp;</i>暂无更多结果</div>
233      </div>
234    </div>
235  </div>
236</div>
237      
238    </span>
239  </div>
240</header></div>
241
242    
243
244    <div class="px-2 px-md-0 container">
245      <div class="row">
246  <!-- 左边栏 -->
247  <div class="col-lg-4 d-none d-lg-block">
248    <div class="position-sticky" style="top: 1.5rem;">
249  <!-- 首页 / 非文章页 -->
250  
251
252  <!-- 文章页 -->
253  
254  <!-- 摘要 -->
255  
256  <div class="p-2 mb-3 bg-light-subtle rounded text-center shadow-sm">
257    
258    
259    <div class="text-muted fs-4 fw-light border-bottom pb-1 mb-2"><i class="bi bi-blockquote-left"></i>&nbsp;摘要</div>
260    <div class="p-2 text-muted">输入输出、内存管理、指针等易错细节和常用库函数以及小众语法的介绍,为浙江大学竺可桢学院“程序设计与算法基础”辅学计划编写。</div>
261    
262  </div>
263  
264
265  <!-- 目录 -->
266  
267  <div class="p-2 mb-3 border rounded shadow-sm">
268    <div class="text-center fs-4 fw-light text-muted border-bottom pb-1 mb-2"><i class="bi bi-bookmark-dash"></i>&nbsp;目录</div>
269    <div id="toc" class="px-2"></div>
270    
271<script src="/js/toc.js"></script>
271
272
273  </div>
274  
275  
276  <hr>
277  <!-- 标签 -->
278  <div class="px-4 py-2">
279    <h4 class="fw-light infobar-tags"><i class="bi bi-tags"></i>&nbsp;标签</h4>
280    <div class="mt-2 px-2 pt-2"><a class="site-tag-link" href="/tags/C/" rel="tag">C<span class="site-tag-count">1</span></a> <a class="site-tag-link" href="/tags/SICP/" rel="tag">SICP<span class="site-tag-count">1</span></a> <a class="site-tag-link" href="/tags/hexo-onion/" rel="tag">hexo-onion<span class="site-tag-count">1</span></a> <a class="site-tag-link" href="/tags/%E5%89%8D%E7%AB%AF/" rel="tag">前端<span class="site-tag-count">1</span></a> <a class="site-tag-link" href="/tags/%E5%B8%83%E5%B0%94%E4%BB%A3%E6%95%B0/" rel="tag">布尔代数<span class="site-tag-count">1</span></a> <a class="site-tag-link" href="/tags/%E6%95%B0%E5%AD%97%E9%80%BB%E8%BE%91%E8%AE%BE%E8%AE%A1/" rel="tag">数字逻辑设计<span class="site-tag-count">1</span></a> <a class="site-tag-link" href="/tags/%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80/" rel="tag">编程基础<span class="site-tag-count">2</span></a> <a class="site-tag-link" href="/tags/%E9%9A%8F%E7%AC%94/" rel="tag">随笔<span class="site-tag-count">1</span></a></div>
281  </div>
282  <hr>
283  <!-- 分类 -->
284  <div class="px-4 py-2">
285    <h4 class="fw-light infobar-categories"><i class="bi bi-folder"></i>&nbsp;分类</h4>
286    <div class="mt-2 px-2 pt-2"><ul class="site-category-list"><li class="site-category-list-item"><a class="site-category-list-link" href="/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a><span class="site-category-list-count">3</span></li><li class="site-category-list-item"><a class="site-category-list-link" href="/categories/%E5%BC%80%E5%8F%91%E6%97%A5%E5%BF%97/">开发日志</a><span class="site-category-list-count">2</span></li><li class="site-category-list-item"><a class="site-category-list-link" href="/categories/%E6%8A%80%E6%9C%AF%E6%8C%87%E5%8D%97/">技术指南</a><span class="site-category-list-count">2</span></li></ul></div>
287  </div>
288  <hr>
289  <!-- 友链 -->
290  <div class="px-4 py-2 infobar-friends">
291    <h4 class="fw-light"><i class="bi bi-link-45deg"></i>&nbsp;友链</h4>
292    <div class="mt-2 px-2 pt-2">
293      <div>
294  <ul class="list-group list-group-flush">
295    <li class="list-group-item">
296      <i class="bi bi-dot"></i>
297      <a class="icon-link icon-link-hover link-underline link-un
297derline-opacity-0" target="_blank" rel="noopener" href="https://schabe-antimonfeld.github.io/">
298        <span class="">
299          SchabeAntimonfeld
300        </span>
301        <i class="bi bi-arrow-right position-relative" style="top:-0.25rem;"></i>
302      </a>
303    </li>
304    <li class="list-group-item">
305      <i class="bi bi-dot"></i>
306      <a class="icon-link icon-link-hover link-underline link-underline-opacity-0" target="_blank" rel="noopener" href="https://aaaaa114514-1.github.io/">
307        <span class="">
308          aaaaa online
309        </span>
310        <i class="bi bi-arrow-right position-relative" style="top:-0.25rem;"></i>
311      </a>
312    </li>
313  </ul>
314</div>
315    </div>
316  </div>
317  <hr>
318</div>
319
320<div class="modal fade" id="infoModal" role="dialog">
321  <div class="modal-dialog modal-dialog-centered">
322    <div class="modal-content">
323      <div class="modal-header">
324        <h1 class="modal-title fs-5">
325          <i class="bi bi-person-vcard"></i>
326          &nbsp;个人信息
327        </h1>
328        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
329      </div>
330      <div class="modal-body">
331        <p class="mb-0">浙江大学计算机系本科在读。邮箱:[email protected]</p>
332      </div>
333    </div>
334  </div>
335</div>
336
337
338<script src="/js/infobar.js"></script>
338
339
340  </div>
341
342  <!-- 博客详情 -->
343  <div class="col-12 col-lg-8">
344    <div class="mx-0 row justify-content-between bg-light-subtle shadow-sm pt-1 px-1 d-flex flex-nowrap rounded">
345      <!-- 标题 -->
346      <div class="col col-lg-9 align-self-center">
347        <div class="h2 fw-bold mb-1 text-truncate" title="C 语言:细节与技巧">
348          <i class="bi bi-book-half"></i>
349          C 语言:细节与技巧
350        </div>
351      </div>
352
353      <div class="col-auto border rounded pt-1 mb-1 d-none d-lg-block fw-light">
354        <p class="mb-1 text-secondary">
355          <!-- 分类 -->
356          
357          <i class="bi bi-folder"></i>
358          
359          <span class="me-1">
360            <a href="/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/" class="link-secondary link-underline-opacity-0">
361              <span class="post-category">学习笔记</span>
362            </a></span>
363          
364          
365
366          <!-- 占位 -->
367          
368        </p>
369        <!-- 日期 -->
370        <p class="mb-1 text-secondary">
371          <i class="bi bi-calendar-check"></i> 2025-12-22
372        </p>
373      </div>
374    </div>
375
376    <!-- 标签 -->
377    <div class="py-2">
378      
379      
380      <span class="me-1 badge border">
381        <a href="/tags/%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80/" class="link-secondary link-underline-opacity-0">
382          <i class="bi bi-tags"></i>
383          <span class="post-tag">编程基础</span>
384        </a>
385      </span>
386      
387      <span class="me-1 badge border">
388        <a href="/tags/C/" class="link-secondary link-underline-opacity-0">
389          <i class="bi bi-tags"></i>
390          <span class="post-tag">C</span>
391        </a>
392      </span>
393      
394      
395      <!-- 字数统计 -->
396      <span class="me-1 badge border text-secondary">
397        <i class="bi bi-text-paragraph"></i>
398        195k
399      </span>
400      <!-- 阅读时长 -->
401      <span class="me-1 badge border text-secondary">
402        <i class="bi bi-stopwatch"></i>
403        2:43
404      </span>
405      <!-- 访问统计-->
406      <span class="me-1 badge border text-secondary">
407        <i class="bi bi-fire"></i>&nbsp;<span id="busuanzi_page_pv">...</span>
408      </span>
409    </div>
410
411    <div class="article p-4 pb-0 h-auto border rounded shadow-sm">
412      <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p><img src="/img/cdetails/meme.jpg" style="zoom: 33%;" /></p>
413<h2 id="写在前面">0 写在前面</h2>
414<blockquote>
415<p>本讲义使用 C99 标准(C99 standard (ISO/IEC 9899:1999))</p>
416<p>本讲义参考资料:</p>
417<ul>
418<li>PRATA S. C Primer Plus(第6版)[M]. 姜佑, 译. 北京: 人民邮电出版社,
4192016.</li>
420<li><a target="_blank" rel="noopener" href="https://www.apiref.com/cpp-zh/c.html">C 语言 - API
421参考文档</a></li>
422</ul>
423</blockquote>
424<p>此外,编写这份讲义时,我假定读者具备下列条件:</p>
425<ul>
426<li>已搭建好 C 语言开发环境(dev-c++,VSCode+gcc/clang
427或者任何能编写并且编译 C 代码的环境)</li>
428<li>掌握基础 C 语言语法知识(可参阅 <a
429target="_blank" rel="noopener" href="https://learnxinyminutes.com/zh-cn/c/">Learn C in Y
430Minutes</a>)</li>
431</ul>
432<h2 id="输入输出技巧">1 输入输出技巧</h2>
433<h3 id="字符串读写">1.1 字符(串)读写</h3>
434<h4 id="getchar-和-putchar">1.1.1 <code>getchar()</code> 和
435<code>putchar()</code></h4>
436<p>输入/输出<strong>一个</strong>字符</p>
437<ul>
438<li>定义在 <code>stdio.h</code> 内</li>
439<li><code>getchar()</code> 从 <code>stdin</code>
440读取<strong>一个字符</strong>,不抛弃换行/回车等空白字符</li>
441</ul>
442<p>示例程序:</p>
443<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
444<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
445<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
446<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
447<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> ch<span class="op">;</span></span>
448<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>  ch <span class="op">=</span> getchar<span class="op">();</span>      <span class="co">// 读取一个字符</span></span>
449<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>  <span class="cf">while</span> <span class="op">(</span>ch <span class="op">!=</span> <span class="ch">&#39;</span><span class="sc">\n</span><span class="ch">&#39;</span><span class="op">)</span> <span class="op">&#123;</span> <span class="co">// 可以读取换行符</span></span>
450<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>ch <span class="op">==</span> <span class="ch">&#39; &#39;</span><span class="op">)</span></span>
451<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>      putchar<span class="op">(</span>ch<span class="op">);</span> <span class="co">// 打印一个字符</span></span>
452<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>    <span class="cf">else</span></span>
453<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>      putchar<span class="op">(</span>ch <span class="op">+</span> <span class="dv">1</span><span class="op">);</span></span>
454<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>    ch <span class="op">=</span> getchar<span class="op">();</span> <span class="co">// 读取下一个字符</span></span>
455<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#125;</span></span>
456<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>  putchar<span class="op">(</span>ch<span class="op">);</span></span>
457<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a></span>
458<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
459<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
460<p>示例输入:</p>
461<pre><code>CALL ME HAL.</code></pre>
462<p>示例输出:</p>
463<pre><code>DBMM NF IBM/</code></pre>
464<h4 id="gets-和-puts">1.1.2 <code>gets()</code> 和
465<code>puts()</code></h4>
466<p>输入/输出<strong>一行</strong>字符串</p>
467<p><strong><code>gets()</code>函数:</strong></p>
468<ul>
469<li>定义在 <code>stdio.h</code> 内,函数原型为
470<code>char *gets( char *str );</code></li>
471<li>从 stdin 读入 <code>str</code>
472所指向的字符数组,直到发现<strong>换行符</strong>或出现文件尾。在读入数组的最后一个字符后立即写入空字符。换行符被舍弃,但不会存储于缓冲区中。</li>
473<li><code>gets()</code> 函数不进行边界检查,使用时需确保你已经为
474<code>str</code> 分配了足够的内存。</li>
475<li>成功时返回 <code>str</code> ,失败时返回空指针
476<code>NULL</code></li>
477</ul>
478<p><strong><code>puts()</code>函数:</strong></p>
479<ul>
480<li>定义在 <code>stdio.h</code> 内,函数原型为
481<code>int puts( const char *str );</code></li>
482<li>写入每个来自空终止字符串 <code>str</code> 的字符及附加换行符
483<code>'\n'</code> 到输出流 <code>stdout</code></li>
484<li>不写入来自 <code>str</code> 的空终止字符</li>
485<li>成功时返回非负值,失败时返回 <code>EOF</code> (负值整型常量)</li>
486</ul>
487<p>示例程序:</p>
488<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
489<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#define STLEN </span><span class="dv">81</span></span>
490<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(</span><span class="dt">void</span><span class="op">)</span></span>
491<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
492<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> words<span class="op">[</span>STLEN<span class="op">];
492</span></span>
493<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>  puts<span class="op">(</span><span class="st">&quot;Enter a string, please.&quot;</span><span class="op">);</span></span>
494<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>  gets<span class="op">(</span>words<span class="op">);</span></span>
495<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;Your string twice:</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
496<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> words<span class="op">);</span></span>
497<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>  puts<span class="op">(</span>words<span class="op">);</span></span>
498<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>  puts<span class="op">(</span><span class="st">&quot;Done.&quot;</span><span class="op">);</span></span>
499<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
500<p>可以看出,一般情况下 <code>puts(str)</code> 和
501<code>printf("%s\n", str)</code> 是等价的。然而,<code>gets(str)</code>
502和 <code>scanf("%s", str)</code>
503大多数情况下并不等价。<code>scanf()</code> 和 <code>gets()</code>
504的区别在于它们如何确定字符串的末尾:<code>scanf()</code>
505更像是“获取单词”函数,而不是“获取字符串”函数:如果预留的存储区装得下输入行,<code>gets()</code>
506会读取第 1 个换行符之前所有的字符。<code>scanf()</code>
507函数有两种方法确定输入结束。无论哪种方法,都从第 1
508个非空白字符作为字符串的开始。如果使用 <code>%s</code>
509转换说明,以下一个空白字符(空行、空格、制表符或换行符)作为字符串的结束(字符串不包括空白字符)。如果指定了字段宽度,如
510<code>%10s</code> , 那么 <code>scanf()</code> 将读取 10 个字符或读到第 1
511个空白字符停止(先满足的条件即是结束输入的条件)。</p>
512<h3 id="读写格式化">1.3 读写格式化</h3>
513<table>
514<colgroup>
515<col style="width: 8%" />
516<col style="width: 27%" />
517<col style="width: 32%" />
518<col style="width: 32%" />
519</colgroup>
520<thead>
521<tr>
522<th>格式符</th>
523<th>说明</th>
524<th><code>printf</code> 示例</th>
525<th><code>scanf</code> 示例</th>
526</tr>
527</thead>
528<tbody>
529<tr>
530<td><code>%d</code></td>
531<td>十进制有符号整数(<code>int</code>)</td>
532<td><code>printf("%d", 123);</code> → <code>13</code></td>
533<td><code>scanf("%d", &amp;x);</code></td>
534</tr>
535<tr>
536<td><code>%u</code></td>
537<td>十进制无符号整数(<code>unsigned int</code>)</td>
538<td><code>printf("%u", -1);</code> → 大正数(补码解释)</td>
539<td><code>scanf("%u", &amp;x);</code></td>
540</tr>
541<tr>
542<td><code>%c</code></td>
543<td>单个字符(<code>char</code>)</td>
544<td><code>printf("%c", 'A');</code> → <code>A</code></td>
545<td><code>scanf(" %c", &amp;ch);</code>(注意空格跳过空白)</td>
546</tr>
547<tr>
548<td><code>%s</code></td>
549<td>字符串(以 <code>\0</code> 结尾的 <code>char[]</code>)</td>
550<td><code>printf("%s", str);</code></td>
551<td><code>scanf("%s", str);</code></td>
552</tr>
553<tr>
554<td><code>%f</code></td>
555<td>单精度浮点数(<code>float</code>)</td>
556<td><code>printf("%f", 3.14f);</code> → <code>3.140000</code></td>
557<td><code>scanf("%f", &amp;f);</code></td>
558</tr>
559<tr>
560<td><code>%lf</code></td>
561<td>双精度浮点数(<code>double</code>)</td>
562<td><code>printf("%lf", 3.14159);</code> → <code>3.141590</code></td>
563<td><code>scanf("%lf", &amp;d);</code></td>
564</tr>
565<tr>
566<td><code>%e</code> / <code>%E</code></td>
567<td>科学计数法(小写/大写 e)</td>
568<td><code>printf("%e", 123.4);</code> → <code>1.234000e+02</code></td>
569<td>—</td>
570</tr>
571<tr>
572<td><code>%x</code> / <code>%X</code></td>
573<td>十六进制整数(小写/大写)</td>
574<td><code>printf("%x", 255);</code> → <code>ff</code></td>
575<td><code>scanf("%x", &amp;x);</code></td>
576</tr>
577<tr>
578<td><code>%o</code></td>
579<td>八进制整数</td>
580<td><code>printf("%o", 255);</code> → <code>377</code></td>
581<td><code>scanf("%o", &amp;x);</code></td>
582</tr>
583<tr>
584<td><code>%%</code></td>
585<td>输出字面量 <code>%</code></td>
586<td><code>printf("%%d");</code> → <code>%d</code></td>
587<td>—</td>
588</tr>
589</tbody>
590</table>
591<p><strong>宽度与精度控制(用于 <code>printf()</code>):</strong></p>
592<ul>
593<li><strong>宽度</strong>:<code>%5d</code> 表示至少占 5
594个字符宽度,不足则左补空格。</li>
595<li><strong>精度</strong>:<code>%.2f</code> 表示保留小数点后 2
596位;<code>%.5s</code> 表示最多输出 5 个字符。</li>
597<li>组合示例:<code>%8.2f</code> → 总宽 8,小数位 2。</li>
598</ul>
599<p><strong>扫描集格式符:<code>%[...]</code>(用于
600<code>scanf()</code>):</strong></p>
601<p><code>%[...]</code> 是 <code>scanf()</code>
602中一种强大的格式控制符,用于<strong>读取符合指定字符集合的字符串</strong>。与
603<code>%s</code> 不同,<code>%[...]</code>
604可以包含空格、制表符等空白字符,并能精确控制哪些字符被接受或拒绝。</p>
605<p>基本语法:</p>
606<ul>
607<li><strong><code>%[abc]</code></strong>
607:只接受字符
608<code>'a'</code>、<code>'b'</code>、<code>'c'</code>,遇到其他字符(包括空白)即停止。</li>
609<li><strong><code>%[a-z]</code></strong>:接受任意小写字母(支持范围表示)。</li>
610<li><strong><code>%[^abc]</code></strong>:<code>^</code>
611表示“取反”,即读取直到遇到 <code>'a'</code>、<code>'b'</code> 或
612<code>'c'</code> 为止(但不包含这些字符)。</li>
613<li><strong><code>%[^\n]</code></strong>:读取一整行(包括空格),直到换行符(<code>\n</code>)为止(但不包含
614<code>\n</code>)。</li>
615</ul>
616<p>示例程序:</p>
617<div class="sourceCode" id="cb5"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
618<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
619<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
620<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
621<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> num<span class="op">;</span></span>
622<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">unsigned</span> <span class="dt">int</span> unum<span class="op">;</span></span>
623<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> ch<span class="op">;</span></span>
624<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> str<span class="op">[</span><span class="dv">20</span><span class="op">];</span></span>
625<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>  <span class="dt">float</span> f<span class="op">;</span></span>
626<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>  <span class="dt">double</span> d<span class="op">;</span></span>
627<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a></span>
628<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 输入示例</span></span>
629<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;请输入一个整数: &quot;</span><span class="op">);</span></span>
630<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>  scanf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%d</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">&amp;</span>num<span class="op">);</span></span>
631<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a></span>
632<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;请输入一个无符号整数: &quot;</span><span class="op">);</span></span>
633<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>  scanf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%u</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">&amp;</span>unum<span class="op">);</span></span>
634<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a></span>
635<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;请输入一个字符: &quot;</span><span class="op">);</span></span>
636<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a>  scanf<span class="op">(</span><span class="st">&quot; </span><span class="sc">%c</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">&amp;</span>ch<span class="op">);</span> <span class="co">// 注意前导空格以跳过换行符</span></span>
637<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a></span>
638<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;请输入一个单词(无空格): &quot;</span><span class="op">);</span></span>
639<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a>  scanf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%s</span><span class="st">&quot;</span><span class="op">,</span> str<span class="op">);</span> <span class="co">// str 是数组名,已是指针,无需 &amp;</span></span>
640<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a></span>
641<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;请输入一个 float: &quot;</span><span class="op">);</span></span>
642<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a>  scanf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%f</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">&amp;</span>f<span class="op">);</span></span>
643<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a></span>
644<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;请输入一个 double: &quot;</span><span class="op">);</span></span>
645<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a>  scanf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%lf</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">&amp;</span>d<span class="op">);</span></span>
646<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a></span>
647<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 输出示例</span></span>
648<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">\n</span><span class="st">=== 输出结果 ===</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
649<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;整数 (</span><span class="sc">%%</span><span class="st">d): </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> num<span class="op">);</span></span>
650<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;无符号整数 (</span><span class="sc">%%</span><span class="st">u): </span><span class="sc">%u\n</span><span class="st">&quot;</span><span class="op">,</span> unum<span class="op">);</span></span>
651<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;字符 (</span><span class="sc">%%</span><span class="st">c): </span><span class="sc">%c\n</span><span class="st">&quot;</span><span class="op">,</span> ch<span class="op">);</span></span>
652<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;字符串 (</span><span class="sc">%%</span><span class="st">s): </span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> str<span class="op">);</span></span>
653<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;浮点数 (</span><span class="sc">%%</span><span class="st">f): </span><span class="sc">%f\n</span><span class="st">&quot;</span><span class="op">,</span> f<span class="op">);</span></span>
654<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;双精度 (</span><span class="sc">%%</span><span class="st">.3lf): </span><span class="sc">%.3lf\n</span><span class="st">&quot;</span><span class="op">,</span> d<span class="op">);</span> <span class="co">// 保留3位小数</span></span>
655<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;十六进制 (</span><span class="sc">%%</span><span class="st">x): </span><span class="sc">%x\n</span><span class="st">&quot;</span><span class="op">,</span> num<span class="op">);</span></span>
656<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;科学计数法 (</span><span class="sc">%%</span><span class="st">e): </span><span class="sc">%e\n</span><span class="st">&quot;</span><span class="op">,</span> d<span class="op">);</span></span>
657<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;宽度与对齐: </span><span class="sc">%5d</span><span class="st">, </span><span class="sc">%8.2f\n</span><span class="st">&quot;</span><span class="op">,</span> num<span class="op">,</span> f<span class="op">);</span></span>
658<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a></span>
659<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
660<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
661<p><strong>注意事项:</strong></p>
662<ul>
663<li><code>scanf()</code> 中除 <code>%c</code> 和 <code>%[...]</code>
664外,会自动跳过前导空白(空格、换行、制表符)。</li>
665<li>使用 <code>%c</code>
666时,若前一次输入留有换行符,需在格式串中加空格以跳过。</li>
667<li><code>scanf()</code> 不会自动检查缓冲区溢出,使用 <code>%s</code>
668时应限制长度,如 <code>scanf("%19s", str);</code></li>
669<li>对于 <code>double</code> 类型,<strong>输入必须用
670<code>%lf</code></strong>,而 <code>printf</code> 中 <code>%f</code> 和
671<code>%lf</code> 均可(C99 以后 <code>%lf</code> 与 <code>%f</code>
672等价)。</li>
673<li><code>scanf()</code> 返回成功接收的参数个数,<code>printf()</code>
674返回成功输出的字符数(若失败则返回负值)。</li>
675</ul>
676<h3 id="文件操作">1.4 文件操作</h3>
677<blockquote>
678<p>本节出现的函数均定义在 <code>stdio.h</code> 头文件中。</p>
679</blockquote>
680<h4 id="fopen-和-fclose">1.4.1 <code>fopen()</code> 和
681<code>fclose()</code></h4>
682<p><strong><code>fopen()</code> 函数:</strong></p>
683<ul>
684<li>函数原型:<code>FILE *fopen( const char *filename, const char *mode );</code></li>
685<li>打开 <code>filename</code>
686所指示的文件,并返回指向关联到该文件的文件流的指针。 <code>mode</code>
687用于确定文件访问模式。</li>
688<li>若成功,则返回指向新文件流的指针。流为完全缓冲,除非
689<code>filename</code> 表示一个交互设备。错误时,返回空指针。</li>
690</ul>
691<table>
692<colgroup>
693<col style="width: 20%" />
694<col style="width: 13%" />
695<col style="width: 25%" />
696<col style="width: 20%" />
697<col style="width: 20%" />
698</colgroup>
699<thead>
700<tr>
701<th>文件访问模式</th>
702<th>含义</th>
703<th>解释</th>
704<th>若文件已存在</th>
705<th>若文件不存在</th>
706</tr>
707</thead>
708<tbody>
709<tr>
710<td><code>"r"</code></td>
711<td>读</td>
712<td>打开文件以读取</td>
713<td>从头读</td>
714<td>打开失败</td>
715</tr>
716<tr>
717<td><code>"w"</code></td>
718<td>写</td>
719<td>创建文件以写入</td>
720<td>销毁内容</td>
721<td>创建新文件</td>
722</tr>
723<tr>
724<td><code>"a"</code></td>
725<td>后附</td>
726<td>后附到文件</td>
727<td>写到结尾</td>
728<td>创建新文件</td>
729</tr>
730<tr>
731<td><code>"r+"</code></td>
732<td>读扩展</td>
733<td>打开文件以读/写</td>
734<td>从头读</td>
735<td>错误</td>
736</tr>
737<tr>
738<td><code>"w+"</code></td>
739<td>写扩展</td>
740<td>创建文件以读/写</td>
741<td>销毁内容</td>
742<td>创建新文件</td>
743</tr>
744<tr>
745<td><code>"a+"</code></td>
746<td>后附扩展</td>
747<td>打开文件以读/写</td>
748<td>写到结尾</td>
749<td>创建新文件</td>
750</tr>
751</tbody>
752</table>
753<p><strong><code>fclose()</code> 函数:</strong></p>
754<ul>
755<li>函数原型:<code>int fclose( FILE *stream );</code></li>
756<li>关闭给定的文件流。冲入任何未写入的缓冲数据到 OS
757。舍弃任何未读取的缓冲数据。</li>
758<li>若在 <code>fclose</code> 返回后使用指针 <code>stream</code>
759的值则行为未定义。</li>
760<li>成功时返回值为 <code>0</code> ,否则为 <code>EOF</code> 。</li>
761</ul>
762<h4 id="getc-和-putc">1.4.2 <code>getc()</code> 和
763<code>putc()</code></h4>
764<p><code>getc()</code> 和 <code>putc()</code> 函数与
765<code>getchar()</code> 和 <code>putchar()</code>
766函数类似。所不同的是,要告诉 <code>getc()</code> 和 <code>putc()</code>
767函数使用哪个文件。下面这条语句的意思是“从标准输入中获取一个字符”:</p>
768<div class="sourceCode" id="cb6"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>ch <span class="op">=</span> getchar<span class="op">();</span></span></code></pre></div>
769<p>然而,下面这条语句的意思是“从 <code>fp</code>
770指定的文件中获取一个字符”:</p>
771<div class="sourceCode" id="cb7"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>ch <span class="op">=</span> getc<span class="op">(</span>fp<span class="op">);</span></span></code></pre></div>
772<p>与此类似,下面语句的意思是“把字符 <code>ch</code> 放入
773<code>FILE</code> 指针 <code>fpout</code> 指定的文件中”:</p>
774<div class="sourceCode" id="cb8"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>putc<span class="op">(</span>ch<span class="op">,</span> fpout<span class="op">);</span></span></code></pre></div>
775<p>在 <code>putc()</code> 函数的参数列表中,第 1
776个参数是待写入的字符,第 2 个参数是文件指针。</p>
777<h4 id="fgets-和-fputs">1.4.3 <code>fgets()</code> 和
778<code>fputs()</code></h4>
779<p><code>fgets()</code> 函数的第 1 个参数和 <code>gets()</code>
780函数一样,也是表示储存输入位置的地址(<code>char *</code>
781类型);第2个参数是一个整数,表示待输入字符串的大小;最后一个参数是文件指针,指定待读取的文件。下面是一个调用该函数的例子:</p>
782<div class="sourceCode" id="cb9"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a>fgets<span class="op">(</span>buf<span class="op">,</span> STLEN<span class="op">,</span> fp<span class="op">);</span></span></code></pre></div>
783<p>这里,<code>buf</code> 是 <code>char</code>
784类型数组的名称,<code>STLEN</code> 是字符串的大小,<code>fp</code>
785是指向 <code>FILE</code> 的指针。<code>fgets()</code> 函数读取输入直到第
7861 个换行符的后面,或读到文件结尾,或者读取 <code>STLEN-1</code>
787个字符(以上面的 <code>fgets()</code> 为例)。然后,<code>fgets()</code>
788在末尾添加一个空字符使之成为一个字符串。字符串的大小是其字符数加上一个空字符。</p>
789<p>如果 <code>fgets()</code>
790在读到字符上限之前已读完一整行,<strong>它会把表示行结尾的换行符放在空字符前面</strong>。<code>fgets()</code>
791函数在遇到 <code>EOF</code>
792时将返回NULL值,可以利用这一机制检查是否到达文件结尾;如果未遇到EOF则之前返回传给它的地址。</p>
793<p><code>fputs()</code> 函数接受两个参数:第 1 个是字符串的地址;第 2
794个是文件指针。该函数根据传入地址找到的字符串写入指定的文件中。和
795<code>puts()</code> 函数不同,<code>fputs()</code>
796在打印字符串时不会在其末尾添加换行符。下面是一个调用该函数的例子:</p>
797<div class="sourceCode" id="cb10"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a>fputs<span class="op">(</span>buf<span class="op">,</span> fp<span class="op">);</span></span></code></pre></div>
798<p>这里,<code>buf</code> 是字符串的地址,<code>fp</code>
799用于指定目标文件。由于 <code>fgets()</code>
800保留了换行符,<code>fputs()</code>
801就<strong>不会再添加换行符</strong>,它们配合得非常好。</p>
802<h4 id="fscanf-和-fprintf">1.4.4 <code>fscanf()</code> 和
803<code>fprintf()</code></h4>
804<p>行为和 <code>scanf()</code> 与 <code>printf()</code>
805类似,区别是调用时在格式化字符串前传入文件指针来指定读写的目标文件。</p>
806<p>示例程序:</p>
807<div class="sourceCode" id="cb11"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
808<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdlib.h&gt;</span></span>
809<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;string.h&gt;</span></span>
810<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a><span class="pp">#define MAX </span><span class="dv">41</span></span>
811<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a></span>
812<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
813<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
814<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">FILE</span><span class="op">*</span> fp<span class="op">;</span></span>
815<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> words<span class="op">[</span>MAX<span class="op">];</span></span>
816<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a></span>
817<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="op">((</span>fp <span class="op">=</span> fopen<span class="op">(</span><span class="st">&quot;wordy&quot;</span><span class="op">,</span> <span class="st">&quot;a+&quot;</span><span class="op">))</span> <span class="op">==</span> NULL<span class="op">)</span> <span class="op">&#123;</span></span>
818<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a>    fprintf<span class="op">(</span>stdout<span class="op">,</span> <span class="st">&quot;Can&#39;t open </span><span class="sc">\&quot;</span><span class="st">wordy</span><span class="sc">\&quot;</span><span class="st"> file.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
819<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a>    exit<span class="op">(</span>EXIT_FAILURE<span class="op">);</span></span>
820<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#125;</span></span>
821<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a></span>
822<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a>  puts<span class="op">(</span><span class="st">&quot;Enter words to add to the file; press the #&quot;</span><span class="op">);</span></span>
823<span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a>  puts<span class="op">(</span><span class="st">&quot;key at the beginning of a line to terminate.&quot;</span><span class="op">);</span></span>
824<span id="cb11-18"><a href="#cb11-18" aria-hidden="true" tabindex="-1"></a></span>
825<span id="cb11-19"><a href="#cb11-19" aria-hidden="true" tabindex="-1"></a>  <span class="cf">while</span> <span class="op">((</span>fscanf<span class="op">(</span>stdin<span class="op">,</span> <span class="st">&quot;</span><span class="sc">%40s</span><span class="st">&quot;</span><span class="op">,</span> words<span class="op">)</span> <span class="op">==</span> <span class="dv">1</span><span class="op">)</span> <span class="op">&amp;&amp;</span> <span class="op">(</span>words<span class="op">[</span><span class="dv">0</span><span class="op">]</span> <span class="op">!=</span> <span class="ch">&#39;#&#39;</span><span class="op">))</span></span>
826<span id="cb11-20"><a href="#cb11-20" aria-hidden="true" tabindex="-1"></a>    fprintf<span class="op">(</span>fp<span class="op">,</span> <span class="st">&quot;</span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> words<span class="op">);</span></span>
827<span id="cb11-21"><a href="#cb11-21" aria-hidden="true" tabindex="-1"></a>  puts<span class="op">(</span><span class="st">&quot;File contents:&quot;</span><span class="op">);</span></span>
828<span id="cb11-22"><a href="#cb11-22" aria-hidden="true" tabindex="-1"></a></span>
829<span id="cb11-23"><a href="#cb11-23" aria-hidden="true" tabindex="-1"></a>  rewind<span class="op">(</span>fp<span class="op">);</span> <span class="co">/* 返回到文件开始处 */</span></span>
830<span id="cb11-24"><a href="#cb11-24" aria-hidden="true" tabindex="-1"></a></span>
831<span id="cb11-25"><a href="#cb11-25" aria-hidden="true" tabindex="-1"></a>  <span class="cf">while</span> <span class="op">(</span>fscanf<span class="op">(</span>fp<span class="op">,</span> <span class="st">&quot;</span><span class="sc">%s</span><span class="st">&quot;</span><span class="op">,</span> words<span class="op">)</span> <span class="op">==</span> <span class="dv">1</span><span class="op">)</span></span>
832<span id="cb11-26"><a href="#cb11-26" aria-hidden="true" tabindex="-1"></a>    puts<span class="op">(</span>words<span class="op">);</span></span>
833<span id="cb11-27"><a href="#cb11-27" aria-hidden="true" tabindex="-1"></a>  puts<span class="op">(</span><span class="st">&quot;Done!&quot;</span><span class="op">);</span></span>
834<span id="cb11-28"><a href="#cb11-28" aria-hidden="true" tabindex="-1"></a></span>
835<span id="cb11-29"><a href="#cb11-29" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="op">(</span>fclose<span class="op">(</span>fp<span class="op">)</span> <span class="op">!=</span> <span class="dv">0</span><span class="op">)</span></span>
836<span id="cb11-30"><a href="#cb11-30" aria-hidden="true" tabindex="-1"></a>    fprintf<span class="op">(</span>stderr<span class="op">,</span> <span class="st">&quot;Error closing file</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
837<span id="cb11-31"><a href="#cb11-31" aria-hidden="true" tabindex="-1"></a></span>
838<span id="cb11-32"><a href="#cb11-32" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
839<span id="cb11-33"><a href="#cb11-33" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
840<p>该程序可以在文件中添加单词。使用 <code>"a+"</code>
841模式,程序可以对文件进行读写操作。首次使用该程序,它将创建
842<code>wordy</code> 文件,以便把单词存入其中。随后再使用该程序,可以在
843<code>wordy</code> 文件后面添加单词。虽然 <code>"a+"</code>
844模式只允许在文件末尾添加内容,但是该模式下可以读整个文件。<code>rewind()</code>
845函数让程序回到文件开始处,方便 <code>while</code>
846循环打印整个文件的内容。注意,<code>rewind()</code>
847接受一个文件指针作为参数。</p>
848<h4 id="fread-和-fwrite">1.4.5 <code>fread()</code> 和
849<code>fwrite()</code></h4>
850<p>在<strong>二进制模式</strong>下对文件进行读写操作。</p>
851<blockquote>
852<p>文件的二进制形式和文本形式的主要区别在于数据的存储和解释方式:</p>
853<ol type="1">
854<li><strong>文本形式</strong>:
855<ul>
856<li>以人类可读的字符编码(如 ASCII、UTF-8)存储数据。</li>
857<li>内容通常由字母、数字、符号和换行符等组成。</li>
858<li>可直接用文本编辑器(如记事本、VS Code)打开并阅读。</li>
859<li>例如:<code>.txt</code>、<code>.csv</code>、<code>.py</code>
860等文件。</li>
861</ul></li>
862<li><strong>二进制形式</strong>:
863<ul>
864<li>以原始字节(0 和 1)形式存储数据,不经过字符编码转换。</li>
865<li>可表示任何类型的数据(如图像、音频、可执行程序、压缩文件等)。</li>
866<li>通常无法直接用普通文本编辑器正确阅读,会显示乱码。</li>
867<li>例如:<code>.jpg</code>、<code>.exe</code>、<code>.mp3</code>、<code>.zip</code>
868等文件。</li>
869</ul></li>
870</ol>
871<p>简言之:<strong>文本文件是二进制文件的一个特例</strong>,其内容遵循特定字符编码规则,便于人类阅读;而二进制文件更通用,用于精确保存各种数据结构和非文本信息。</p>
872</blockquote>
873<p><strong><code>fwrite()</code> 函数:</strong></p>
874<p><code>fwrite()</code> 函数的原型如下:</p>
875<div class="sourceCode" id="cb12"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="dt">
875size_t</span> fwrite<span class="op">(</span><span class="dt">const</span> <span class="dt">void</span> <span class="op">*</span> <span class="dt">restrict</span> ptr<span class="op">,</span> <span class="dt">size_t</span> size<span class="op">,</span> <span class="dt">size_t</span> nmemb<span class="op">,</span><span class="dt">FILE</span> <span class="op">*</span> <span class="dt">restrict</span> fp<span class="op">);</span></span></code></pre></div>
876<p><code>fwrite()</code> 函数把二进制数据写入文件。<code>size_t</code>
877是根据标准C类型定义的类型,它是 <code>sizeof</code>
878运算符返回的类型,通常是 <code>unsigned int</code>
879,但是实现可以选择使用其他类型。指针 <code>ptr</code>
880是待写入数据块的地址。<code>size</code>
881表示待写入数据块的大小(以字节为单位),<code>nmemb</code>
882表示待写入数据块的数量。和其他函数一样,<code>fp</code>
883指定待写入的文件。例如,要保存一个大小为 256
884字节的数据对象(如数组),可以这样做:</p>
885<div class="sourceCode" id="cb13"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> buffer<span class="op">[</span><span class="dv">256</span><span class="op">];</span></span>
886<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a>fwrite<span class="op">(</span>buffer<span class="op">,</span> <span class="dv">256</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> fp<span class="op">);</span></span></code></pre></div>
887<p>以上调用把一块 256 字节的数据从 <code>buffer</code>
888写入文件。另举一例,要保存一个内含 10 个 <code>double</code>
889类型值的数组,可以这样做:</p>
890<div class="sourceCode" id="cb14"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="dt">double</span> earnings<span class="op">[</span><span class="dv">10</span><span class="op">];</span></span>
891<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a>fwrite<span class="op">(</span>earnings<span class="op">,</span> <span class="kw">sizeof</span><span class="op">(</span><span class="dt">double</span><span class="op">),</span> <span class="dv">10</span><span class="op">,</span> fp<span class="op">);</span></span></code></pre></div>
892<p>以上调用把 <code>earnings</code> 数组中的数据写入文件,数据被分成 10
893块,每块都是 <code>double</code> 的大小。</p>
894<p>注意 <code>fwrite()</code> 原型中的
895<code>const void * restrict ptr</code> 声明。<code>fwrite()</code>
896的一个问题是,它的第 1 个参数不是固定的类型。例如,第 1 个例子中使用
897<code>buffer</code>,其类型是指向 <code>char</code>
898的指针;而第2个例子中使用 <code>earnings</code> ,其类型是指向
899<code>double</code> 的指针。</p>
900<p><code>fwrite()</code>
901函数返回成功写入项的数量。正常情况下,该返回值就是
902<code>nmemb</code>,但如果出现写入错误,返回值会比 <code>nmemb</code>
903小。</p>
904<p><strong><code>fread()</code> 函数:</strong></p>
905<p><code>size_t fread()</code> 函数的原型如下:</p>
906<div class="sourceCode" id="cb15"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="dt">size_t</span> fread<span class="op">(</span><span class="dt">void</span> <span class="op">*</span> <span class="dt">restrict</span> ptr<span class="op">,</span> <span class="dt">size_t</span> size<span class="op">,</span> <span class="dt">size_t</span> nmemb<span class="op">,</span><span class="dt">FILE</span> <span class="op">*</span> <span class="dt">restrict</span> fp<span class="op">);</span></span></code></pre></div>
907<p><code>fread()</code> 函数接受的参数和 <code>fwrite()</code>
908函数相同。在 <code>fread()</code> 函数中,<code>ptr</code>
909是待读取文件数据在内存中的地址,<code>fp</code>
910指定待读取的文件。该函数用于读取被 <code>fwrite()</code>
911写入文件的数据。例如,要恢复上例中保存的内含 10 个 <code>double</code>
912类型值的数组,可以这样做:</p>
913<div class="sourceCode" id="cb16"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="dt">double</span> earnings<span class="op">[</span><span class="dv">10</span><span class="op">];
913</span></span>
914<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a>fread<span class="op">(</span>earnings<span class="op">,</span> <span class="kw">sizeof</span> <span class="op">(</span><span class="dt">double</span><span class="op">),</span> <span class="dv">10</span><span class="op">,</span> fp<span class="op">);</span></span></code></pre></div>
915<p>该调用把 10 个 <code>double</code> 大小的值拷贝进
916<code>earnings</code> 数组中。</p>
917<p><code>fread()</code>
918函数返回成功读取项的数量。正常情况下,该返回值就是
919<code>nmemb</code>,但如果出现读取错误或读到文件结尾,该返回值就会比
920<code>nmemb</code> 小。</p>
921<h4 id="fseek-和-ftell">1.4.6 <code>fseek()</code> 和
922<code>ftell()</code></h4>
923<p><strong><code>fseek()</code> 函数:</strong></p>
924<p><code>fseek()</code> 的第 1 个参数是 <code>FILE</code>
925指针,指向待查找的文件,<code>fopen()</code> 应该已打开该文件。</p>
926<p><code>fseek()</code> 的第 2
927个参数是偏移量(offset)。该参数表示从起始点开始要移动的距离。该参数必须是一个
928<code>long</code> 类型的值,可以为正(前移)、负(后移)或
9290(保持不动)。</p>
930<p><code>fseek()</code> 的第 3 个参数是模式,该参数确定起始点。</p>
931<table>
932<thead>
933<tr>
934<th>模式</th>
935<th>偏移量的起始点</th>
936</tr>
937</thead>
938<tbody>
939<tr>
940<td>SEEK_SET</td>
941<td>文件开始处</td>
942</tr>
943<tr>
944<td>SEEK_CUR</td>
945<td>当前位置</td>
946</tr>
947<tr>
948<td>SEEK_END</td>
949<td>文件末尾</td>
950</tr>
951</tbody>
952</table>
953<p>示例程序:</p>
954<div class="sourceCode" id="cb17"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a>fseek<span class="op">(</span>fp<span class="op">,</span> <span class="dv">0</span><span class="bu">L</span><span class="op">,</span> SEEK_SET<span class="op">);</span> <span class="co">// 定位至文件开始处</span></span>
955<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a>fseek<span class="op">(</span>fp<span class="op">,</span> <span class="dv">10</span><span class="bu">L</span><span class="op">,</span> SEEK_SET<span class="op">);</span> <span class="co">// 定位至文件中的第10个字节</span></span>
956<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a>fseek<span class="op">(</span>fp<span class="op">,</span> <span class="dv">2</span><span class="bu">L</span><span class="op">,</span> SEEK_CUR<span class="op">);</span> <span class="co">// 从文件当前位置前移2个字节</span></span>
957<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a>fseek<span class="op">(</span>fp<span class="op">,</span> <span class="dv">0</span><span class="bu">L</span><span class="op">,</span> SEEK_END<span class="op">);</span> <span class="co">// 定位至文件结尾</span></span>
958<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a>fseek<span class="op">(</span>fp<span class="op">,</span> <span class="op">-</span><span class="dv">10</span><span class="bu">L</span><span class="op">,</span> SEEK_END<span class="op">);</span> <span class="co">// 从文件结尾处回退10个字节</span></span></code></pre></div>
959<p>如果一切正常,<code>fseek()</code> 的返回值为
960<code>0</code>;如果出现错误(如试图移动的距离超出文件的范围),其返回值为
961<code>-1</code>。</p>
962<p><strong><code>ftell()</code> 函数:</strong></p>
963<p><code>ftell()</code> 函数的返回类型是
964<code>long</code>,它返回的是当前的位置。</p>
965<ul>
966<li><p>若流以二进制模式打开,则由此函数获得的值是从文件开始的字节数。</p></li>
967<li><p>若流以文本模式打开,则由此函数返回的值未指定,且仅若作为
968<code>fseek()</code> 的输入才有意义。</p></li>
969<li><p>成功时返回值为文件位置指示器,若失败发生则为 <code>-1L</code>
970。</p></li>
971</ul>
972<h4 id="fgetpos-和-fsetpos">1.4.7 <code>fgetpos()</code> 和
973<code>fsetpos()</code></h4>
974<p><code>fseek()</code> 和 <code>ftell()</code>
975潜在的问题是,它们都把文件大小限制在 <code>long</code>
976类型能表示的范围内。也许 20
977亿字节看起来相当大,但是随着存储设备的容量迅猛增长,文件也越来越大。鉴于此,ANSI
978C
979新增了两个<strong>处理较大文件</strong>的新定位函数:<code>fgetpos()</code>
980和 <code>fsetpos()</code>。这两个函数不使用 <code>long</code>
981类型的值表示位置,它们使用一种新类型:<code>fpos_t</code>(代表 file
982position type,文件定位类型)。<code>fpos_t</code>
983类型不是基本类型,它根据其他类型来定义。<code>fpos_t</code>
984类型的变量或数据对象可以在文件中指定一个位置,它不能是数组类型,除此之外,没有其他限制。实现可以提供一个满足特殊平台要求的类型,例如,<code>fpos_t</code>
985可以实现为结构。</p>
986<p>
986ANSI C 定义了如何使用<code>fpos_t</code> 类型。<code>fgetpos()</code>
987函数的原型如下:</p>
988<div class="sourceCode" id="cb18"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> fgetpos<span class="op">(</span><span class="dt">FILE</span> <span class="op">*</span> <span class="dt">restrict</span> stream<span class="op">,</span> <span class="dt">fpos_t</span> <span class="op">*</span> <span class="dt">restrict</span> pos<span class="op">);</span></span></code></pre></div>
989<p>调用该函数时,它把 <code>fpos_t</code> 类型的值放在 <code>pos</code>
990指向的位置上,该值描述了文件中的一个位置。如果成功,<code>fgetpos()</code>
991函数返回 0;如果失败,返回非 0。</p>
992<p><code>fsetpos()</code> 函数的原型如下:</p>
993<div class="sourceCode" id="cb19"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> fsetpos<span class="op">(</span><span class="dt">FILE</span> <span class="op">*</span>stream<span class="op">,</span> <span class="dt">const</span> <span class="dt">fpos_t</span> <span class="op">*</span>pos<span class="op">);</span></span></code></pre></div>
994<p>调用该函数时,使用 <code>pos</code> 指向位置上的 <code>fpos_t</code>
995类型值来设置文件指针指向该值指定的位置。如果成功,<code>fsetpos()</code>
996函数返回 0;如果失败,则返回非 0。<code>fpos_t</code>
997类型的值应通过之前调用 <code>fgetpos()</code> 获得。</p>
998<h2 id="内存管理">2 内存管理</h2>
999<h3 id="堆内存和栈内存">2.1 堆内存和栈内存</h3>
1000<p><strong>堆内存</strong>(heap)和<strong>栈内存</strong>(stack)是两种不同的内存管理区域,主要区别如下:</p>
1001<table>
1002<colgroup>
1003<col style="width: 11%" />
1004<col style="width: 36%" />
1005<col style="width: 51%" />
1006</colgroup>
1007<thead>
1008<tr>
1009<th>特性</th>
1010<th>栈内存(Stack)</th>
1011<th>堆内存(Heap)</th>
1012</tr>
1013</thead>
1014<tbody>
1015<tr>
1016<td><strong>分配方式</strong></td>
1017<td>自动分配和释放(由编译器管理)</td>
1018<td>手动分配和释放(由程序员调用 <code>malloc</code>/<code>free</code>
1019等函数)</td>
1020</tr>
1021<tr>
1022<td><strong>生命周期</strong></td>
1023<td>作用域结束时自动释放(如函数返回)</td>
1024<td>程序员显式释放,否则会内存泄漏</td>
1025</tr>
1026<tr>
1027<td><strong>访问速度</strong></td>
1028<td>快(因使用栈指针,结构简单)</td>
1029<td>较慢(需动态查找内存块)</td>
1030</tr>
1031<tr>
1032<td><strong>内存大小</strong></td>
1033<td>通常较小(受系统栈空间限制,如几 MB)</td>
1034<td>较大(受系统可用虚拟内存限制)</td>
1035</tr>
1036<tr>
1037<td><strong>存储内容</strong></td>
1038<td>局部变量、函数参数、返回地址等</td>
1039<td>动态分配的数据(如数组、结构体、对象等)</td>
1040</tr>
1041<tr>
1042<td><strong>安全性</strong></td>
1043<td>安全(自动管理,不易泄漏)</td>
1044<td>风险较高(易出现内存泄漏、野指针等)</td>
1045</tr>
1046</tbody>
1047</table>
1048<p><strong>举例</strong>:</p>
1049<ul>
1050<li>栈:<code>int x = 10;</code>(局部变量)</li>
1051<li>堆:<code>int *p = (int *)malloc(sizeof(int));</code>(需手动
1052<code>free(p);</code>)</li>
1053</ul>
1054<p>简言之:栈用于临时、自动管理的小数据;堆用于运行时动态分配的、生命周期更长的数据。</p>
1055<h3 id="作用域">2.2 作用域</h3>
1056<table>
1057<colgroup>
1058<col style="width: 16%" />
1059<col style="width: 49%" />
1060<col style="width: 34%" />
1061</colgroup>
1062<thead>
1063<tr>
1064<th>作用域类型</th>
1065<th>说明</th>
1066<th>示例</th>
1067</tr>
1068</thead>
1069<tbody>
1070<tr>
1071<td>文件作用域</td>
1072<td>函数外声明,从声明到文件结束可见</td>
1073<td><code>int file_var;</code></td>
1074</tr>
1075<tr>
1076<td>块作用域</td>
1077<td><code>&#123;&#125;</code> 内声明,仅在该块及其内层块可见</td>
1078<td><code>int x;</code> 在 <code>main</code> 或 <code>if</code> 中</td>
1079</tr>
1080<tr>
1081<td>函数原型作用域</td>
1082<td>仅在函数声明的参数列表中有效,用于类型检查</td>
1083<td><code>void f(int a);</code> 中的 <code>a</code></td>
1084</tr>
1085<tr>
1086<td>函数作用域</td>
1087<td>仅用于 <code>goto</code> 标签,在整个函数内可见</td>
1088<td><code>skip:</code> 标签</td>
1089</tr>
1090</tbody>
1091</table>
1092<p>示例代码:</p>
1093<div class="sourceCode" id="cb20"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1094<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a></span>
1095<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a><span class="co">// ========== 文件作用域(file scope)==========</span></span>
1096<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a><span class="co">// 在函数外部声明的变量具有文件作用域,从声明处到文件末尾都可见</span></span>
1097<span id="cb20-5"><a href="#cb20-5" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> file_var <span class="op">=</span> <span class="dv">100</span><span class="op">;</span> <span class="co">// 全局变量,文件作用域</span></span>
1098<span id="cb20-6"><a href="#cb20-6" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">int</span> static_file_var <span class="op">=</span></span>
1099<span id="cb20-7"><a href="#cb20-7" aria-hidden="true" tabindex="-1"></a>    <span class="dv">200</span><span class="op">;</span> <span class="co">// 静态全局变量,仅在本文件内可见(仍属文件作用域)</span></span>
1100<span id="cb20-8"><a href="#cb20-8" aria-hidden="true" tabindex="-1"></a></span>
1101<span id="cb20-9"><a href="#cb20-9" aria-hidden="true" tabindex="-1"></a><span class="co">// 函数声明(原型)——形参属于函数原型作用域</span></span>
1102<span id="cb20-10"><a href="#cb20-10" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> demo_function<span class="op">(</span><span class="dt">int</span> a<span class="op">,</span></span>
1103<span id="cb20-11"><a href="#cb20-11" aria-hidden="true" tabindex="-1"></a>
1103                   <span class="dt">int</span> b<span class="op">);</span> <span class="co">// a 和 b 的作用域仅限于括号内(函数原型作用域)</span></span>
1104<span id="cb20-12"><a href="#cb20-12" aria-hidden="true" tabindex="-1"></a></span>
1105<span id="cb20-13"><a href="#cb20-13" aria-hidden="true" tabindex="-1"></a><span class="co">// ========== 主函数 ==========</span></span>
1106<span id="cb20-14"><a href="#cb20-14" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1107<span id="cb20-15"><a href="#cb20-15" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1108<span id="cb20-16"><a href="#cb20-16" aria-hidden="true" tabindex="-1"></a>  <span class="co">// ========== 块作用域(block scope)==========</span></span>
1109<span id="cb20-17"><a href="#cb20-17" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> main_var <span class="op">=</span> <span class="dv">10</span><span class="op">;</span> <span class="co">// 块作用域(属于 main 函数块)</span></span>
1110<span id="cb20-18"><a href="#cb20-18" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#123;</span></span>
1111<span id="cb20-19"><a href="#cb20-19" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> inner_var <span class="op">=</span> <span class="dv">20</span><span class="op">;</span> <span class="co">// 内层块作用域,只在 &#123; &#125; 内可见</span></span>
1112<span id="cb20-20"><a href="#cb20-20" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;main_var = </span><span class="sc">%d</span><span class="st">, inner_var = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> main_var<span class="op">,</span> inner_var<span class="op">);</span></span>
1113<span id="cb20-21"><a href="#cb20-21" aria-hidden="true" tabindex="-1"></a></span>
1114<span id="cb20-22"><a href="#cb20-22" aria-hidden="true" tabindex="-1"></a>    <span class="co">// 重新定义 main_var(遮蔽外部的 main_var)</span></span>
1115<span id="cb20-23"><a href="#cb20-23" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> main_var <span class="op">=</span> <span class="dv">30</span><span class="op">;</span></span>
1116<span id="cb20-24"><a href="#cb20-24" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;内部块中 main_var = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> main_var<span class="op">);</span></span>
1117<span id="cb20-25"><a href="#cb20-25" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#125;</span></span>
1118<span id="cb20-26"><a href="#cb20-26" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 此处 inner_var 不可见</span></span>
1119<span id="cb20-27"><a href="#cb20-27" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;外部 main_var = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> main_var<span class="op">);</span></span>
1120<span id="cb20-28"><a href="#cb20-28" aria-hidden="true" tabindex="-1"></a></span>
1121<span id="cb20-29"><a href="#cb20-29" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 访问文件作用域变量</span></span>
1122<span id="cb20-30"><a href="#cb20-30" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;file_var = </span><span class="sc">%d</span><span class="st">, static_file_var = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> file_var<span class="op">,</span> static_file_var<span class="op">);</span></span>
1123<span id="cb20-31"><a href="#cb20-31" aria-hidden="true" tabindex="-1"></a></span>
1124<span id="cb20-32"><a href="#cb20-32" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 调用函数</span></span>
1125<span id="cb20-33"><a href="#cb20-33" aria-hidden="true" tabindex="-1"></a>  demo_function<span class="op">(</span><span class="dv">5</span><span class="op">,</span> <span class="dv">6</span><span class="op">);</span></span>
1126<span id="cb20-34"><a href="#cb20-34" aria-hidden="true" tabindex="-1"></a></span>
1127<span id="cb20-35"><a href="#cb20-35" aria-hidden="true" tabindex="-1"></a>  <span class="co">
1127// ========== 函数作用域(仅用于 goto 标签)==========</span></span>
1128<span id="cb20-36"><a href="#cb20-36" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;演示函数作用域(goto 标签):</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
1129<span id="cb20-37"><a href="#cb20-37" aria-hidden="true" tabindex="-1"></a>  <span class="cf">goto</span> skip<span class="op">;</span> <span class="co">// goto 标签在整个函数内可见(函数作用域)</span></span>
1130<span id="cb20-38"><a href="#cb20-38" aria-hidden="true" tabindex="-1"></a></span>
1131<span id="cb20-39"><a href="#cb20-39" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#123;</span></span>
1132<span id="cb20-40"><a href="#cb20-40" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 即使在块中,标签依然可见</span></span>
1133<span id="cb20-41"><a href="#cb20-41" aria-hidden="true" tabindex="-1"></a>  skip<span class="op">:</span></span>
1134<span id="cb20-42"><a href="#cb20-42" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;跳转成功!标签 &#39;skip&#39; 具有函数作用域。</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
1135<span id="cb20-43"><a href="#cb20-43" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#125;</span></span>
1136<span id="cb20-44"><a href="#cb20-44" aria-hidden="true" tabindex="-1"></a></span>
1137<span id="cb20-45"><a href="#cb20-45" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1138<span id="cb20-46"><a href="#cb20-46" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span>
1139<span id="cb20-47"><a href="#cb20-47" aria-hidden="true" tabindex="-1"></a></span>
1140<span id="cb20-48"><a href="#cb20-48" aria-hidden="true" tabindex="-1"></a><span class="co">// ========== 函数定义 ==========</span></span>
1141<span id="cb20-49"><a href="#cb20-49" aria-hidden="true" tabindex="-1"></a><span class="co">// 注意:函数定义中的形参作用域是函数块作用域,不是原型作用域</span></span>
1142<span id="cb20-50"><a href="#cb20-50" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> demo_function<span class="op">(</span><span class="dt">int</span> x<span class="op">,</span> <span class="dt">int</span> y<span class="op">)</span></span>
1143<span id="cb20-51"><a href="#cb20-51" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1144<span id="cb20-52"><a href="#cb20-52" aria-hidden="true" tabindex="-1"></a>  <span class="co">// x 和 y 在整个函数体中可见(属于块作用域)</span></span>
1145<span id="cb20-53"><a href="#cb20-53" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;函数中 x = </span><span class="sc">%d</span><span class="st">, y = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> x<span class="op">,</span> y<span class="op">);</span></span>
1146<span id="cb20-54"><a href="#cb20-54" aria-hidden="true" tabindex="-1"></a></span>
1147<span id="cb20-55"><a href="#cb20-55" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 可以访问文件作用域变量</span></span>
1148<span id="cb20-56"><a href="#cb20-56" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;在函数中访问 file_var = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> file_var<span class="op">);</span></span>
1149<span id="cb20-57"><a href="#cb20-57" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1150<blockquote>
1151<p>C 语言中没有“函数作用域变量”,“函数作用域”特指
1152<strong><code>goto</code> 标签</strong> 的可见性。</p>
1153</blockquote>
1154<h3 id="链接">2.3 链接</h3>
1155<p><strong>链接(linkage)</strong>决定了一个标识符(如变量或函数)是否可以在其他编译单元(即其他源文件)中被访问。根据链接属性,变量可分为以下三类:</p>
1156<ol type="1">
1157<li><p><strong>外部链接(External Linkage)</strong></p>
1158<ul>
1159<li><p>含义:该变量在整个程序的多个源文件之间可见,可被其他文件通过
1160<code>extern</code> 声明访问。</p></li>
1161<li><p>如何定义:在文件作用域(即函数外部)声明的非 <code>static</code>
1162全局变量。</p></li>
1163<li><p>示例:</p>
1164<div class="sourceCode" id="cb21"><pre
1165class="sourceCode c"><code class="sourceCode c"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> global_var<span class="op">;</span> <span class="co">
1165// 外部链接,其他文件可用 extern int global_var; 引用</span></span></code></pre></div></li>
1166</ul></li>
1167<li><p><strong>内部链接(Internal Linkage)</strong></p>
1168<ul>
1169<li><p>含义:该变量仅在当前源文件内可见,其他文件无法访问。</p></li>
1170<li><p>如何定义:在文件作用域使用 <code>static</code>
1171修饰的全局变量。</p></li>
1172<li><p>示例:</p>
1173<div class="sourceCode" id="cb22"><pre
1174class="sourceCode c"><code class="sourceCode c"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">int</span> file_var<span class="op">;</span> <span class="co">// 内部链接,仅本文件可用</span></span></code></pre></div></li>
1175</ul></li>
1176<li><p><strong>无链接(No Linkage)</strong></p>
1177<ul>
1178<li><p>含义:该变量不能被其他任何作用域访问,仅在其声明的作用域内有效。</p></li>
1179<li><p>适用对象:所有局部变量(包括函数参数),无论是否使用
1180<code>auto</code>、<code>register</code>,或在块内声明的变量。</p></li>
1181<li><p>示例:</p>
1182<div class="sourceCode" id="cb23"><pre
1183class="sourceCode c"><code class="sourceCode c"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> func<span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">&#123;</span></span>
1184<span id="cb23-2"><a href="#cb23-2" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> local_var<span class="op">;</span> <span class="co">// 无链接,仅在 func 内有效</span></span>
1185<span id="cb23-3"><a href="#cb23-3" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div></li>
1186</ul></li>
1187</ol>
1188<h3 id="静态变量的存储期">2.4 静态变量的存储期</h3>
1189<p>作用域和链接描述了标识符的可见性。存储期描述了通过这些标识符访问的对象的生存期。</p>
1190<p>如果对象具有静态存储期,那么它在程序的执行期间一直存在。文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字
1191<code>static</code> 表明了其链接属性,而非存储期。以 <code>static</code>
1192声明的文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。</p>
1193<p><strong>块作用域静态变量:</strong></p>
1194<ul>
1195<li>在函数或块内用 <code>static</code> 声明。</li>
1196<li>无链接,但<strong>生命周期为整个程序</strong>。</li>
1197<li>只初始化一次,函数调用间保持值。</li>
1198</ul>
1199<p><strong>内部链接静态变量:</strong></p>
1200<ul>
1201<li>在文件作用域用 <code>static</code> 声明的全局变量。</li>
1202<li>仅在<strong>本源文件内可见</strong>,其他文件无法访问。</li>
1203</ul>
1204<p><strong>外部链接静态变量:</strong></p>
1205<ul>
1206<li>在文件作用域定义的非 static 全局变量。</li>
1207<li>可被其他文件通过 <code>extern</code> 声明引用。</li>
1208<li>注意:定义时不用 <code>static</code>,也不用
1209<code>extern</code>;<code>extern</code>
1210仅用于在其他文件中声明引用。</li>
1211</ul>
1212<p>示例代码:</p>
1213<p>文件 <code>main.c</code></p>
1214<div class="sourceCode" id="cb24"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1215<span id="cb24-2"><a href="#cb24-2" aria-hidden="true" tabindex="-1"></a></span>
1216<span id="cb24-3"><a href="#cb24-3" aria-hidden="true" tabindex="-1"></a><span class="co">// 声明(非定义)一个外部链接的全局变量(在 another.c 中定义)</span></span>
1217<span id="cb24-4"><a href="#cb24-4" aria-hidden="true" tabindex="-1"></a><span class="kw">extern</span> <span class="dt">int</span> external_global<span class="op">;</span> <span class="co">// 这是“外部链接变量”的引用声明</span></span>
1218<span id="cb24-5"><a href="#cb24-5" aria-hidden="true" tabindex="-1"></a></span>
1219<span id="cb24-6"><a href="#cb24-6" aria-hidden="true" tabindex="-1"></a><span class="co">// 定义一个内部链接的静态变量</span></span>
1220<span id="cb24-7"><a href="#cb24-7" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">int</span> internal_static <span class="op">=</span> <span class="dv">100</span><span class="op">;</span></span>
1221<span id="cb24-8"><a href="#cb24-8" aria-hidden="true" tabindex="-1"></a></span>
1222<span id="cb24-9"><a href="#cb24-9" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> func<span class="op">(</span><span class="dt">void</span><span class="op">)</span></span>
1223<span id="cb24-10"><a href="#cb24-10" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1224<span id="cb24-11"><a href="#cb24-11" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 块作用域静态变量</span></span>
1225<span id="cb24-12"><a href="#cb24-12" aria-hidden="true" tabindex="-1"></a>  <span class="dt">static</span> <span class="dt">int</span> block_static <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
1226<span id="cb24-13"><a href="#cb24-13" aria-hidden="true" tabindex="-1"></a>  block_static <span class="op">+=</span> <span class="dv">10</span><span class="op">;</span></span>
1227<span id="cb24-14"><a href="#cb24-14" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;块静态变量: </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> block_static<span class="op">);</span></span>
1228<span id="cb24-15"><a href="#cb24-15" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span>
1229<span id="cb24-16"><a href="#cb24-16" aria-hidden="true" tabindex="-1"></a></span>
1230<span id="cb24-17"><a href="#cb24-17" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(</span><span class="dt">void</span><span class="op">)</span></span>
1231<span id="cb24-18"><a href="#cb24-18" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1232<span id="cb24-19"><a href="#cb24-19" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;内部链接静态变量 (main.c): </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> internal_static<span class="op">);</span></span>
1233<span id="cb24-20"><a href="#cb24-20" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;外部链接全局变量 (来自 another.c): </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> external_global<span class="op">);</span></span>
1234<span id="cb24-21"><a href="#cb24-21" aria-hidden="true" tabindex="-1"></a></span>
1235<span id="cb24-22"><a href="#cb24-22" aria-hidden="true" tabindex="-1"></a>  func<span class="op">();</span> <span class="co">// 10</span></span>
1236<span id="cb24-23"><a href="#cb24-23" aria-hidden="true" tabindex="-1"></a>  func<span class="op">();</span> <span class="co">// 20</span></span>
1237<span id="cb24-24"><a href="#cb24-24" aria-hidden="true" tabindex="-1"></a>  func<span class="op">();</span> <span class="co">// 30</span></span>
1238<span id="cb24-25"><a href="#cb24-25" aria-hidden="true" tabindex="-1"></a></span>
1239<span id="cb24-26"><a href="#cb24-26" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1240<span id="cb24-27"><a href="#cb24-27" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1241<p>文件 <code>another.c</code></p>
1242<div class="sourceCode" id="cb25"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a><span class="co">// 定义具有外部链接的全局变量(无 static,无 extern)</span></span>
1243<span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> external_global <span class="op">=</span> <span class="dv">42</span><span class="op">;</span>  <span class="co">// 可被其他文件通过 extern 引用</span></span></code></pre></div>
1244<p>编译方式(GCC):</p>
1245<div class="sourceCode" id="cb26"><pre
1246class="sourceCode bash"><code class="sourceCode bash"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a><span class="fu">gcc</span> main.c another.c <span class="at">-o</span> demo</span>
1247<span id="cb26-2"><a href="#cb26-2" aria-hidden="true" tabindex="-1"></a><span class="ex">./demo</span></span></code></pre></div>
1248<p>示例输出:</p>
1249<pre><code>内部链接静态变量 (main.c): 100
1250外部链接全局变量 (来自 another.c): 42
1251块静态变量: 10
1252块静态变量: 20
1253块静态变量: 30</code></pre>
1254<h3 id="动态内存管理">2.5 动态内存管理</h3>
1255<h4 id="内存分配和释放">2.5.1 内存分配和释放</h4>
1256<p><code>malloc()</code>、<code>calloc()</code> 和 <code>free()</code>
1257是标准库(<code>&lt;stdlib.h&gt;</code>)提供的动态内存管理函数,用于在程序运行时从堆(heap)上分配和释放内存。</p>
1258<table>
1259<colgroup>
1260<col style="width: 27%" />
1261<col style="width: 41%" />
1262<col style="width: 30%" />
1263</colgroup>
1264<thead>
1265<tr>
1266<th>函数</th>
1267<th>作用</th>
1268<th>特点</th>
1269</tr>
1270</thead>
1271<tbody>
1272<tr>
1273<td><code>malloc(size_t size)</code></td>
1274<td>分配 <code>size</code> 字节的未初始化内存</td>
1275<td>返回 <code>void*</code>,内容为<strong>垃圾值</strong></td>
1276</tr>
1277<tr>
1278<td><code>calloc(size_t num, size_t size)</code></td>
1279<td>分配 <code>num * size</code> 字节内存,并<strong>初始化为
12800</strong></td>
1281<td>常用于分配数组</td>
1282</tr>
1283<tr>
1284<td><code>free(void *ptr)</code></td>
1285<td>释放之前由
1286<code>malloc</code>/<code>calloc</code>/<code>realloc</code>
1287分配的内存</td>
1288<td><strong>必须配对使用</strong>,否则会导致内存问题</td>
1289</tr>
1290</tbody>
1291</table>
1292<p>示例代码:</p>
1293<div class="sourceCode" id="cb28"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1294<span id="cb28-2"><a href="#cb28-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdlib.h&gt;</span><span class="pp"> </span><span class="co">// 包含 malloc, calloc, free</span></span>
1295<span id="cb28-3"><a href="#cb28-3" aria-hidden="true" tabindex="-1"></a></span>
1296<span id="cb28-4"><a href="#cb28-4" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1297<span id="cb28-5"><a href="#cb28-5" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1298<span id="cb28-6"><a href="#cb28-6" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 1. 使用 malloc 分配内存(未初始化)</span></span>
1299<span id="cb28-7"><a href="#cb28-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span><span class="op">*</span> p1 <span class="op">=</span> <span class="op">(</span><span class="dt">int</span><span class="op">*)</span>malloc<span class="op">(</span><span class="dv">5</span> <span class="op">*</span> <span class="kw">sizeof</span><span class="op">(</span><span class="dt">int</span><span class="op">));</span></span>
1300<span id="cb28-8"><a href="#cb28-8" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="op">(</span>p1 <span class="op">==</span> NULL<span class="op">)</span> <span class="op">&#123;</span></span>
1301<span id="cb28-9"><a href="#cb28-9" aria-hidden="true" tabindex="-1"></a>    fprintf<span class="op">(</span>stderr<span class="op">,</span> <span class="st">&quot;malloc failed!</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
1302<span id="cb28-10"><a href="#cb28-10" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">1</span><span class="op">;</span></span>
1303<span id="cb28-11"><a href="#cb28-11" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#125;</span></span>
1304<span id="cb28-12"><a href="#cb28-12" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 手动初始化</span></span>
1305<span id="cb28-13"><a href="#cb28-13" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> <span class="dv">5</span><span class="op">;</span> i<span class="op">++)</span> <span class="op">&#123;</span></span>
1306<span id="cb28-14"><a href="#cb28-14" aria-hidden="true" tabindex="-1"></a>    p1<span class="op">[</span>i<span class="op">]</span> <span class="op">=</span> i <span class="op">+</span> <span class="dv">1</span><span class="op">;</span></span>
1307<span id="cb28-15"><a href="#cb28-15" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#125;</span></span>
1308<span id="cb28-16"><a href="#cb28-16" aria-hidden="true" tabindex="-1"></a></span>
1309<span id="cb28-17"><a href="#cb28-17" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 2. 使用 calloc 分配并自动清零</span></span>
1310<span id="cb28-18"><a href="#cb28-18" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span><span class="op">*</span> p2 <span class="op">=</span> <span class="op">(</span><span class="dt">int</span><span class="op">*)</span>calloc<span class="op">(</span><span class="dv">5</span><span class="op">,</span> <span class="kw">sizeof</span><span class="op">(</span><span class="dt">int</span><span class="op">));</span> <span class="co">// 相当于分配5个int并全部设为0</span></span>
1311<span id="cb28-19"><a href="#cb28-19" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="op">(</span>p2 <span class="op">==</span> NULL<span class="op">)</span> <span class="op">&#123;</span></span>
1312<span id="cb28-20"><a href="#cb28-20" aria-hidden="true" tabindex="-1"></a>    fprintf<span class="op">(</span>stderr<span class="op">,</span> <span class="st">&quot;calloc failed!</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
1313<span id="cb28-21"><a href="#cb28-21" aria-hidden="true" tabindex="-1"></a>    free<span class="op">(</span>p1<span class="op">);</span> <span class="co">// 释放已分配的 p1</span></span>
1314<span id="cb28-22"><a href="#cb28-22" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">1</span><span class="op">;</span></span>
1315<span id="cb28-23"><a href="#cb28-23" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#125;</span></span>
1316<span id="cb28-24"><a href="#cb28-24" aria-hidden="true" tabindex="-1"></a>  p2<span class="op">[</span><span class="dv">0</span><span class="op">]</span> <span class="op">=</span> <span class="dv">10</span><span class="op">;</span> <span class="co">
1316// 覆盖部分零值</span></span>
1317<span id="cb28-25"><a href="#cb28-25" aria-hidden="true" tabindex="-1"></a></span>
1318<span id="cb28-26"><a href="#cb28-26" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 打印结果</span></span>
1319<span id="cb28-27"><a href="#cb28-27" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;malloc array: &quot;</span><span class="op">);</span></span>
1320<span id="cb28-28"><a href="#cb28-28" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> <span class="dv">5</span><span class="op">;</span> i<span class="op">++)</span></span>
1321<span id="cb28-29"><a href="#cb28-29" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%d</span><span class="st"> &quot;</span><span class="op">,</span> p1<span class="op">[</span>i<span class="op">]);</span></span>
1322<span id="cb28-30"><a href="#cb28-30" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
1323<span id="cb28-31"><a href="#cb28-31" aria-hidden="true" tabindex="-1"></a></span>
1324<span id="cb28-32"><a href="#cb28-32" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;calloc array: &quot;</span><span class="op">);</span></span>
1325<span id="cb28-33"><a href="#cb28-33" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> <span class="dv">5</span><span class="op">;</span> i<span class="op">++)</span></span>
1326<span id="cb28-34"><a href="#cb28-34" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%d</span><span class="st"> &quot;</span><span class="op">,</span> p2<span class="op">[</span>i<span class="op">]);</span></span>
1327<span id="cb28-35"><a href="#cb28-35" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
1328<span id="cb28-36"><a href="#cb28-36" aria-hidden="true" tabindex="-1"></a></span>
1329<span id="cb28-37"><a href="#cb28-37" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 3. 释放内存(关键步骤!)</span></span>
1330<span id="cb28-38"><a href="#cb28-38" aria-hidden="true" tabindex="-1"></a>  free<span class="op">(</span>p1<span class="op">);</span> <span class="co">// 释放 p1 指向的内存</span></span>
1331<span id="cb28-39"><a href="#cb28-39" aria-hidden="true" tabindex="-1"></a>  free<span class="op">(</span>p2<span class="op">);</span> <span class="co">// 释放 p2 指向的内存</span></span>
1332<span id="cb28-40"><a href="#cb28-40" aria-hidden="true" tabindex="-1"></a></span>
1333<span id="cb28-41"><a href="#cb28-41" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 建议:将指针置空,防止悬空指针误用</span></span>
1334<span id="cb28-42"><a href="#cb28-42" aria-hidden="true" tabindex="-1"></a>  p1 <span class="op">=</span> NULL<span class="op">;</span></span>
1335<span id="cb28-43"><a href="#cb28-43" aria-hidden="true" tabindex="-1"></a>  p2 <span class="op">=</span> NULL<span class="op">;</span></span>
1336<span id="cb28-44"><a href="#cb28-44" aria-hidden="true" tabindex="-1"></a></span>
1337<span id="cb28-45"><a href="#cb28-45" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1338<span id="cb28-46"><a href="#cb28-46" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1339<p>示例输出:</p>
1340<pre><code>malloc array: 1 2 3 4 5 
1341calloc array: 10 0 0 0 0 </code></pre>
1342<h4 id="free-的重要性">2.5.2 <code>free()</code> 的重要性</h4>
1343<p><strong>防止内存泄漏</strong></p>
1344<ul>
1345<li>如果分配的内存不再使用却不调用
1346<code>free()</code>,程序会一直占用这些内存。</li>
1347<li>长时间运行的程序(如服务器、操作系统服务)会因内存泄漏最终耗尽系统内存,导致崩溃或性能严重下降。</li>
1348</ul>
1349<p><strong>保证资源可重用</strong></p>
1350<ul>
1351<li>被 <code>free()</code> 释放的内存可被后续的
1352<code>malloc()</code>/<code>calloc()</code>
1353重新使用,提高内存利用率。</li>
1354</ul>
1355<p><strong>避免未定义行为</strong></p>
1356<ul>
1357<li>对已释放的内存再次
1358<code>free()</code>
1358(重复释放)或写入(悬空指针),会导致程序崩溃或不可预测行为。</li>
1359<li><code>free(NULL)</code> 是安全的(标准允许),但
1360<code>free(任意非堆地址)</code> 是危险的。</li>
1361</ul>
1362<h4 id="常见错误">2.5.3 常见错误</h4>
1363<ul>
1364<li><p>忘记 <code>free()</code> → 内存泄漏<br />
1365</p></li>
1366<li><p>重复 <code>free()</code> → 程序崩溃<br />
1367</p></li>
1368<li><p><code>free()</code> 栈变量或字符串字面量 → 未定义行为</p>
1369<div class="sourceCode" id="cb30"><pre
1370class="sourceCode c"><code class="sourceCode c"><span id="cb30-1"><a href="#cb30-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> a <span class="op">=</span> <span class="dv">10</span><span class="op">;</span></span>
1371<span id="cb30-2"><a href="#cb30-2" aria-hidden="true" tabindex="-1"></a>free<span class="op">(&amp;</span>a<span class="op">);</span> <span class="co">// 错误!a 在栈上</span></span>
1372<span id="cb30-3"><a href="#cb30-3" aria-hidden="true" tabindex="-1"></a></span>
1373<span id="cb30-4"><a href="#cb30-4" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> <span class="op">*</span>s <span class="op">=</span> <span class="st">&quot;hello&quot;</span><span class="op">;</span></span>
1374<span id="cb30-5"><a href="#cb30-5" aria-hidden="true" tabindex="-1"></a>free<span class="op">(</span>s<span class="op">);</span>  <span class="co">// 错误!&quot;hello&quot; 在只读数据段</span></span></code></pre></div></li>
1375<li><p>使用已 <code>free()</code> 的指针(悬空指针)</p>
1376<div class="sourceCode" id="cb31"><pre
1377class="sourceCode c"><code class="sourceCode c"><span id="cb31-1"><a href="#cb31-1" aria-hidden="true" tabindex="-1"></a>free<span class="op">(</span>p<span class="op">);</span></span>
1378<span id="cb31-2"><a href="#cb31-2" aria-hidden="true" tabindex="-1"></a><span class="op">*</span>p <span class="op">=</span> <span class="dv">10</span><span class="op">;</span> <span class="co">// 危险!</span></span></code></pre></div></li>
1379</ul>
1380<h2 id="指针">3 指针</h2>
1381<h3 id="指针操作">3.1 指针操作</h3>
1382<h4 id="指针加减法">3.1.1 指针加减法</h4>
1383<ul>
1384<li>指针加减整数时,不是简单地对地址加减字节数,而是根据指针所指向数据类型的大小进行<strong>缩放</strong>。例如:<code>int *p; p + 1</code>
1385实际上是 <code>p + sizeof(int)</code>。</li>
1386<li>只有指向同一数组(或紧邻位置)的两个指针才能安全相减。</li>
1387<li>对指针加减超出数组边界是未定义行为。</li>
1388</ul>
1389<p>示例程序:</p>
1390<div class="sourceCode" id="cb32"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb32-1"><a href="#cb32-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1391<span id="cb32-2"><a href="#cb32-2" aria-hidden="true" tabindex="-1"></a></span>
1392<span id="cb32-3"><a href="#cb32-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1393<span id="cb32-4"><a href="#cb32-4" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1394<span id="cb32-5"><a href="#cb32-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> arr<span class="op">[]</span> <span class="op">=</span> <span class="op">&#123;</span><span class="dv">10</span><span class="op">,</span> <span class="dv">20</span><span class="op">,</span> <span class="dv">30</span><span class="op">,</span> <span class="dv">40</span><span class="op">&#125;;</span></span>
1395<span id="cb32-6"><a href="#cb32-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span><span class="op">*</span> p <span class="op">=</span> arr<span class="op">;</span> <span class="co">// p 指向 arr[0]</span></span>
1396<span id="cb32-7"><a href="#cb32-7" aria-hidden="true" tabindex="-1"></a></span>
1397<span id="cb32-8"><a href="#cb32-8" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;p        = </span><span class="sc">%p\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">(</span><span class="dt">void</span><span class="op">*)</span>p<span class="op">);</span></span>
1398<span id="cb32-9"><a href="#cb32-9" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;p + 1    = </span><span class="sc">%p</span><span class="st"> (差值: </span><span class="sc">%ld</span><span class="st"> 字节)</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">(</span><span class="dt">void</span><span class="op">*)(</span>p <span class="op">+</span> <span class="dv">1</span><span class="op">),</span></span>
1399<span id="cb32-10"><a href="#cb32-10" aria-hidden="true" tabindex="-1"></a>
1399         <span class="op">(</span><span class="dt">char</span><span class="op">*)(</span>p <span class="op">+</span> <span class="dv">1</span><span class="op">)</span> <span class="op">-</span> <span class="op">(</span><span class="dt">char</span><span class="op">*)</span>p<span class="op">);</span></span>
1400<span id="cb32-11"><a href="#cb32-11" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;*(p + 1) = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">*(</span>p <span class="op">+</span> <span class="dv">1</span><span class="op">));</span> <span class="co">// 等价于 arr[1]</span></span>
1401<span id="cb32-12"><a href="#cb32-12" aria-hidden="true" tabindex="-1"></a></span>
1402<span id="cb32-13"><a href="#cb32-13" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 指针相减:两个同类型指针相减得到元素个数(不是字节数!)</span></span>
1403<span id="cb32-14"><a href="#cb32-14" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span><span class="op">*</span> q <span class="op">=</span> <span class="op">&amp;</span>arr<span class="op">[</span><span class="dv">3</span><span class="op">];</span></span>
1404<span id="cb32-15"><a href="#cb32-15" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;q - p = </span><span class="sc">%ld\n</span><span class="st">&quot;</span><span class="op">,</span> q <span class="op">-</span> p<span class="op">);</span> <span class="co">// 输出 3</span></span>
1405<span id="cb32-16"><a href="#cb32-16" aria-hidden="true" tabindex="-1"></a></span>
1406<span id="cb32-17"><a href="#cb32-17" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1407<span id="cb32-18"><a href="#cb32-18" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1408<h4 id="指针常量和常量指针">3.1.2 指针常量和常量指针</h4>
1409<ul>
1410<li><strong>指针常量</strong>:指针本身是常量(地址不能变),但指向的内容可以修改。</li>
1411<li><strong>常量指针</strong>:指针指向的内容是常量(内容不能变),但指针本身可以指向别处。</li>
1412</ul>
1413<blockquote>
1414<p>记忆技巧:从右往左读声明,<code>const</code>
1415紧挨着谁,谁就不能被修改。</p>
1416</blockquote>
1417<p><strong>指针常量(Pointer is constant)</strong></p>
1418<p>定义:指针的值(即地址)<strong>不能被修改</strong>,但可以通过指针修改所指向的数据。</p>
1419<p>语法:</p>
1420<div class="sourceCode" id="cb33"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb33-1"><a href="#cb33-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">*</span> <span class="dt">const</span> ptr <span class="op">=</span> <span class="op">&amp;</span>variable<span class="op">;</span></span></code></pre></div>
1421<p>含义:<code>ptr</code> 是一个 <code>const</code> 指针,指向
1422<code>int</code> 类型。</p>
1423<p>示例程序:</p>
1424<div class="sourceCode" id="cb34"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb34-1"><a href="#cb34-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1425<span id="cb34-2"><a href="#cb34-2" aria-hidden="true" tabindex="-1"></a></span>
1426<span id="cb34-3"><a href="#cb34-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1427<span id="cb34-4"><a href="#cb34-4" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1428<span id="cb34-5"><a href="#cb34-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> a <span class="op">=</span> <span class="dv">10</span><span class="op">,</span> b <span class="op">=</span> <span class="dv">20</span><span class="op">;</span></span>
1429<span id="cb34-6"><a href="#cb34-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span><span class="op">*</span> <span class="dt">const</span> ptr <span class="op">=</span> <span class="op">&amp;</span>a<span class="op">;</span> <span class="co">// ptr 一旦初始化,就不能再指向其他变量</span></span>
1430<span id="cb34-7"><a href="#cb34-7" aria-hidden="true" tabindex="-1"></a></span>
1431<span id="cb34-8"><a href="#cb34-8" aria-hidden="true" tabindex="-1"></a>  <span class="op">*</span>ptr <span class="op">=</span> <span class="dv">100</span><span class="op">;</span>
1431                  <span class="co">// 合法:可以修改指向的内容</span></span>
1432<span id="cb34-9"><a href="#cb34-9" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;*ptr = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">*</span>ptr<span class="op">);</span> <span class="co">// 输出 100</span></span>
1433<span id="cb34-10"><a href="#cb34-10" aria-hidden="true" tabindex="-1"></a></span>
1434<span id="cb34-11"><a href="#cb34-11" aria-hidden="true" tabindex="-1"></a>  <span class="co">// ptr = &amp;b;      // 错误!不能修改 ptr 本身(编译报错)</span></span>
1435<span id="cb34-12"><a href="#cb34-12" aria-hidden="true" tabindex="-1"></a></span>
1436<span id="cb34-13"><a href="#cb34-13" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1437<span id="cb34-14"><a href="#cb34-14" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1438<p><strong>常量指针(Pointer to constant)</strong></p>
1439<p>定义:指针所指向的<strong>数据是只读的</strong>,不能通过该指针修改内容,但指针本身可以重新赋值。</p>
1440<p>语法:</p>
1441<div class="sourceCode" id="cb35"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb35-1"><a href="#cb35-1" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">int</span> <span class="op">*</span> ptr <span class="op">=</span> <span class="op">&amp;</span>variable<span class="op">;</span></span>
1442<span id="cb35-2"><a href="#cb35-2" aria-hidden="true" tabindex="-1"></a><span class="co">// 或等价写法:</span></span>
1443<span id="cb35-3"><a href="#cb35-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="dt">const</span> <span class="op">*</span> ptr <span class="op">=</span> <span class="op">&amp;</span>variable<span class="op">;</span></span></code></pre></div>
1444<p>含义:<code>ptr</code> 是一个指向 <code>const int</code> 的指针。</p>
1445<p>示例程序:</p>
1446<div class="sourceCode" id="cb36"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb36-1"><a href="#cb36-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1447<span id="cb36-2"><a href="#cb36-2" aria-hidden="true" tabindex="-1"></a></span>
1448<span id="cb36-3"><a href="#cb36-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1449<span id="cb36-4"><a href="#cb36-4" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1450<span id="cb36-5"><a href="#cb36-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> a <span class="op">=</span> <span class="dv">10</span><span class="op">,</span> b <span class="op">=</span> <span class="dv">20</span><span class="op">;</span></span>
1451<span id="cb36-6"><a href="#cb36-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">const</span> <span class="dt">int</span><span class="op">*</span> ptr <span class="op">=</span> <span class="op">&amp;</span>a<span class="op">;</span> <span class="co">// ptr 可以指向别的变量,但不能修改所指内容</span></span>
1452<span id="cb36-7"><a href="#cb36-7" aria-hidden="true" tabindex="-1"></a></span>
1453<span id="cb36-8"><a href="#cb36-8" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;*ptr = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">*</span>ptr<span class="op">);</span> <span class="co">// 可以读取</span></span>
1454<span id="cb36-9"><a href="#cb36-9" aria-hidden="true" tabindex="-1"></a></span>
1455<span id="cb36-10"><a href="#cb36-10" aria-hidden="true" tabindex="-1"></a>  <span class="co">// *ptr = 100;   // 错误!不能通过 ptr 修改 a 的值</span></span>
1456<span id="cb36-11"><a href="#cb36-11" aria-hidden="true" tabindex="-1"></a></span>
1457<span id="cb36-12"><a href="#cb36-12" aria-hidden="true" tabindex="-1"></a>  ptr <span class="op">=</span> <span class="op">&amp;</span>b<span class="op">;</span>
1457                    <span class="co">// 合法:ptr 可以指向其他变量</span></span>
1458<span id="cb36-13"><a href="#cb36-13" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;*ptr = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">*</span>ptr<span class="op">);</span> <span class="co">// 输出 20</span></span>
1459<span id="cb36-14"><a href="#cb36-14" aria-hidden="true" tabindex="-1"></a></span>
1460<span id="cb36-15"><a href="#cb36-15" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1461<span id="cb36-16"><a href="#cb36-16" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1462<p><strong>两者结合:指向常量的指针常量</strong></p>
1463<p>也可以同时禁止修改指针和其所指内容:</p>
1464<div class="sourceCode" id="cb37"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb37-1"><a href="#cb37-1" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">int</span> <span class="op">*</span> <span class="dt">const</span> ptr <span class="op">=</span> <span class="op">&amp;</span>a<span class="op">;</span></span></code></pre></div>
1465<ul>
1466<li><code>ptr</code> 不能指向别的地址(指针是常量)。</li>
1467<li><code>*ptr</code> 不能被修改(指向的是常量)。</li>
1468</ul>
1469<h4 id="指针类型转换">3.1.3 指针类型转换</h4>
1470<p>指针之间的赋值比数值类型之间的赋值要严格。例如,不用类型转换就可以把
1471<code>int</code> 类型的值赋给 <code>double</code>
1472类型的变量,但是两个类型的指针不能这样做。</p>
1473<div class="sourceCode" id="cb38"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb38-1"><a href="#cb38-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> n <span class="op">=</span> <span class="dv">5</span><span class="op">;</span></span>
1474<span id="cb38-2"><a href="#cb38-2" aria-hidden="true" tabindex="-1"></a><span class="dt">double</span> x<span class="op">;</span></span>
1475<span id="cb38-3"><a href="#cb38-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">*</span> p1 <span class="op">=</span> <span class="op">&amp;</span>n<span class="op">;</span></span>
1476<span id="cb38-4"><a href="#cb38-4" aria-hidden="true" tabindex="-1"></a><span class="dt">double</span> <span class="op">*</span> pd <span class="op">=</span> <span class="op">&amp;</span>x<span class="op">;</span></span>
1477<span id="cb38-5"><a href="#cb38-5" aria-hidden="true" tabindex="-1"></a>x <span class="op">=</span> n<span class="op">;</span> <span class="co">// 隐式类型转换</span></span>
1478<span id="cb38-6"><a href="#cb38-6" aria-hidden="true" tabindex="-1"></a>pd <span class="op">=</span> p1<span class="op">;</span> <span class="co">// 编译时错误</span></span></code></pre></div>
1479<p>更复杂的类型也是如此。假设有如下声明:</p>
1480<div class="sourceCode" id="cb39"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb39-1"><a href="#cb39-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">*</span> pt<span class="op">;</span></span>
1481<span id="cb39-2"><a href="#cb39-2" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">(*</span>pa<span class="op">)[</span><span class="dv">3</span><span class="op">];</span></span>
1482<span id="cb39-3"><a href="#cb39-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> ar1<span class="op">[</span><span class="dv">2</span><span class="op">][</span><span class="dv">3</span><span class="op">];</span></span>
1483<span id="cb39-4"><a href="#cb39-4" aria-hidden="true" tabindex="-1"></a><span class="dv">703</span></span>
1484<span id="cb39-5"><a href="#cb39-5" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> ar2<span class="op">[</span><span class="dv">3</span><span class="op">][</span><span class="dv">2</span><span class="op">];
1484</span></span>
1485<span id="cb39-6"><a href="#cb39-6" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">**</span>p2<span class="op">;</span> <span class="co">// 一个指向指针的指针</span></span></code></pre></div>
1486<p>有如下的语句:</p>
1487<div class="sourceCode" id="cb40"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb40-1"><a href="#cb40-1" aria-hidden="true" tabindex="-1"></a>pt <span class="op">=</span> <span class="op">&amp;</span>ar1<span class="op">[</span><span class="dv">0</span><span class="op">][</span><span class="dv">0</span><span class="op">];</span> <span class="co">// 都是指向int的指针</span></span>
1488<span id="cb40-2"><a href="#cb40-2" aria-hidden="true" tabindex="-1"></a>pt <span class="op">=</span> ar1<span class="op">[</span><span class="dv">0</span><span class="op">];</span> <span class="co">// 都是指向int的指针</span></span>
1489<span id="cb40-3"><a href="#cb40-3" aria-hidden="true" tabindex="-1"></a>pt <span class="op">=</span> ar1<span class="op">;</span> <span class="co">// 无效</span></span>
1490<span id="cb40-4"><a href="#cb40-4" aria-hidden="true" tabindex="-1"></a>pa <span class="op">=</span> ar1<span class="op">;</span> <span class="co">// 都是指向内含3个int类型元素数组的指针</span></span>
1491<span id="cb40-5"><a href="#cb40-5" aria-hidden="true" tabindex="-1"></a>pa <span class="op">=</span> ar2<span class="op">;</span> <span class="co">// 无效</span></span>
1492<span id="cb40-6"><a href="#cb40-6" aria-hidden="true" tabindex="-1"></a>p2 <span class="op">=</span> <span class="op">&amp;</span>pt<span class="op">;</span> <span class="co">// both pointer-to-int *</span></span>
1493<span id="cb40-7"><a href="#cb40-7" aria-hidden="true" tabindex="-1"></a><span class="op">*</span>p2 <span class="op">=</span> ar2<span class="op">[</span><span class="dv">0</span><span class="op">];</span> <span class="co">// 都是指向int的指针</span></span>
1494<span id="cb40-8"><a href="#cb40-8" aria-hidden="true" tabindex="-1"></a>p2 <span class="op">=</span> ar2<span class="op">;</span> <span class="co">// 无效</span></span></code></pre></div>
1495<p>注意,以上无效的赋值表达式语句中涉及的两个指针都是指向不同的类型。例如,<code>pt</code>
1496指向一个 <code>int</code> 类型值,而 <code>ar1</code> 指向一个内含 3 个
1497<code>int</code> 类型元素的数组。类似地,<code>pa</code> 指向一个内含 2
1498个 <code>int</code> 类型元素的数组,所以它与 <code>ar1</code>
1499的类型兼容,但是 <code>ar2</code> 指向一个内含 2 个 <code>int</code>
1500类型元素的数组,所以 <code>pa</code> 与 <code>ar2</code> 不兼容。</p>
1501<p>上面的最后两个例子有些棘手。变量 <code>p2</code>
1502是指向指针的指针,它指向的指针指向 <code>int</code>,而 <code>ar2</code>
1503是指向数组的指针,该数组内含 2 个 <code>int</code>
1504类型的元素。所以,<code>p2</code> 和 <code>ar2</code> 的类型不同,不能把
1505<code>ar2</code> 赋给 <code>p2</code>。但是,<code>*p2</code> 是指向
1506<code>int</code> 的指针,与 <code>ar2[0]</code> 兼容。因为
1507<code>ar2[0]</code> 是指向该数组首元素( <code>ar2[0][0]</code>
1508)的指针,所以 <code>ar2[0]</code> 也是指向 <code>int</code>
1509的指针。</p>
1510<p>一般而言,多重解引用让人费解。例如,考虑下面的代码:</p>
1511<div class="sourceCode" id="cb41"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb41-1"><a href="#cb41-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> x <span class="op">=</span> <span class="dv">20</span><span class="op">;</span></span>
1512<span id="cb41-2"><a href="#cb41-2" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">int</span> y <span class="op">=</span> <span class="dv">23</span><span class="op">;</span></span>
1513<span id="cb41-3"><a href="#cb41-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">*</span> p1 <span class="op">=</span> <span class="op">&amp;</span>
1513x<span class="op">;</span></span>
1514<span id="cb41-4"><a href="#cb41-4" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">int</span> <span class="op">*</span> p2 <span class="op">=</span> <span class="op">&amp;</span>y<span class="op">;</span></span>
1515<span id="cb41-5"><a href="#cb41-5" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">int</span> <span class="op">**</span> pp2<span class="op">;</span></span>
1516<span id="cb41-6"><a href="#cb41-6" aria-hidden="true" tabindex="-1"></a>p1 <span class="op">=</span> p2<span class="op">;</span> <span class="co">// 不安全 -- 把const指针赋给非const指针</span></span>
1517<span id="cb41-7"><a href="#cb41-7" aria-hidden="true" tabindex="-1"></a>p2 <span class="op">=</span> p1<span class="op">;</span> <span class="co">// 有效 -- 把非const指针赋给const指针</span></span>
1518<span id="cb41-8"><a href="#cb41-8" aria-hidden="true" tabindex="-1"></a>pp2 <span class="op">=</span> <span class="op">&amp;</span>p1<span class="op">;</span> <span class="co">// 不安全 –- 嵌套指针类型赋值</span></span></code></pre></div>
1519<p>把 <code>const</code> 指针赋给非 <code>const</code>
1520指针不安全,因为这样可以使用新的指针改变 <code>const</code>
1521指针指向的数据。编译器在编译代码时,可能会给出警告,执行这样的代码是未定义的。但是把非
1522<code>const</code> 指针赋给 <code>const</code>
1523指针没问题,前提是只进行一级解引用:</p>
1524<div class="sourceCode" id="cb42"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb42-1"><a href="#cb42-1" aria-hidden="true" tabindex="-1"></a>p2 <span class="op">=</span> p1<span class="op">;</span> <span class="co">// 有效 -- 把非const指针赋给const指针</span></span></code></pre></div>
1525<p>但是进行两级解引用时,这样的赋值也不安全,例如,考虑下面的代码:</p>
1526<div class="sourceCode" id="cb43"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb43-1"><a href="#cb43-1" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">int</span> <span class="op">**</span>pp2<span class="op">;</span></span>
1527<span id="cb43-2"><a href="#cb43-2" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">*</span>p1<span class="op">;</span></span>
1528<span id="cb43-3"><a href="#cb43-3" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">int</span> n <span class="op">=</span> <span class="dv">13</span><span class="op">;</span></span>
1529<span id="cb43-4"><a href="#cb43-4" aria-hidden="true" tabindex="-1"></a>pp2 <span class="op">=</span> <span class="op">&amp;</span>p1<span class="op">;</span> <span class="co">// 允许,但是这导致const限定符失效(根据第1行代码,不能通过*pp2修改它所指向的内容)</span></span>
1530<span id="cb43-5"><a href="#cb43-5" aria-hidden="true" tabindex="-1"></a><span class="op">*</span>pp2 <span class="op">=</span> <span class="op">&amp;</span>n<span class="op">;</span> <span class="co">// 有效,两者都声明为const,但是这将导致p1指向n(*pp2已被修改)</span></span>
1531<span id="cb43-6"><a href="#cb43-6" aria-hidden="true" tabindex="-1"></a><span class="op">*</span>p1 <span class="op">=</span> <span class="dv">10</span><span class="op">;</span> <span class="co">// 有效,但是这将改变n的值(但是根据第3行代码,不能修改n的值)</span></span></code></pre></div>
1532<h3 id="指针与数组">3.2 指针与数组</h3>
1533<p>数组名是数组首元素的地址。也就是说,如果 <code>arr</code>
1534是一个数组,下面的语句成立:</p>
1535<div class="sourceCode" id="cb44"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb44-1"><a href="#cb44-1" aria-hidden="true" tabindex="-1"></a>arr <span class="op">==</span> <span class="op">&amp;</span>arr<span class="op">[</span><span class="dv">0</span><span class="op">];</span> <span class="co">// 数组名是该数组首元素的地址</span></span></code></pre></div>
1536<p><code>arr</code> 和 <code>&amp;arr[0]</code>
1537都表示数组首元素的内存地址(<code>&amp;</code>
1538是地址运算符)。两者都是常量,在程序的运行过程中不会改变。但是,可以把它们赋值给指针变量,然后可以修改指针变量的值。</p>
1539<h4 id="指向多维数组的指针">3.2.1 指向(多维)数组的指针</h4>
1540<p>假设有下面的声明:</p>
1541<div class="sourceCode" id="cb45"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb45-1"><a href="#cb45-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> zippo<span class="op">[</span><span class="dv">4</span><span class="op">][</span><span class="dv">2</span><span class="op">];
1541</span> <span class="co">/* 内含int数组的数组 */</span></span></code></pre></div>
1542<p>然后数组名 <code>zippo</code>
1543是该数组首元素的地址。在本例中,<code>zippo</code>
1544的首元素是一个内含两个 <code>int</code> 值的数组,所以
1545<code>zippo</code> 是这个内含两个 <code>int</code>
1546值的数组的地址。下面,我们从指针的属性进一步分析。</p>
1547<p>因为 <code>zippo</code> 是数组首元素的地址,所以 <code>zippo</code>
1548的值和 <code>&amp;zippo[0]</code> 的值相同。而 <code>zippo[0]</code>
1549本身是一个内含两个整数的数组,所以 <code>zippo[0]</code>
1550的值和它首元素(一个整数)的地址(即 <code>&amp;zippo[0][0]</code>
1551的值)相同。简而言之,<code>zippo[0]</code> 是一个占用一个
1552<code>int</code> 大小对象的地址,而 <code>zippo</code> 是一个占用两个
1553<code>int</code>
1554大小对象的地址。由于这个整数和内含两个整数的数组都开始于同一个地址,所以
1555<code>zippo</code> 和 <code>zippo[0]</code> 的值相同。</p>
1556<p>给指针或地址加 <code>1</code>
1557,其值会增加对应类型大小的数值。在这方面,<code>zippo</code> 和
1558<code>zippo[0]</code> 不同,因为 <code>zippo</code> 指向的对象占用了两个
1559<code>int</code> 大小,而 <code>zippo[0]</code> 指向的对象只占用一个
1560<code>int</code> 大小。因此, <code>zippo + 1</code> 和
1561<code>zippo[0] + 1</code> 的值不同。</p>
1562<p>解引用一个指针(在指针前使用 <code>*</code>
1563运算符)或在数组名后使用带下标的 <code>[]</code>
1564运算符,得到引用对象代表的值。因为 <code>zippo[0]</code>
1565是该数组首元素( <code>zippo[0][0]</code> )的地址,所以
1566<code>*(zippo[0])</code> 表示储存在 <code>zippo[0][0]</code>
1567上的值(即一个 <code>int</code>
1568类型的值)。与此类似,<code>*zippo</code> 代表该数组首元素(
1569<code>zippo[0]</code> )的值,但是 <code>zippo[0]</code> 本身是一个
1570<code>int</code> 类型值的地址。该值的地址是
1571<code>&amp;zippo[0][0]</code>,所以 <code>*zippo</code> 就是
1572<code>&amp;zippo[0][0]</code>
1573。对两个表达式应用解引用运算符表明,<code>**zippo</code> 与
1574<code>*&amp;zippo[0][0]</code> 等价,这相当于
1575<code>zippo[0][0]</code>,即一个 <code>int</code>
1576类型的值。简而言之,<code>zippo</code>
1577是地址的地址,必须解引用两次才能获得原始值。地址的地址或指针的指针是就是双重间接(double
1578indirection)的例子。</p>
1579<p>示例程序:</p>
1580<div class="sourceCode" id="cb46"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb46-1"><a href="#cb46-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1581<span id="cb46-2"><a href="#cb46-2" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1582<span id="cb46-3"><a href="#cb46-3" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1583<span id="cb46-4"><a href="#cb46-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> zippo<span class="op">[</span><span class="dv">4</span><span class="op">][</span><span class="dv">2</span><span class="op">]</span> <span class="op">=</span> <span class="op">&lt;!--</span>swig<span class="dv">0</span><span class="op">--&gt;;</span></span>
1584<span id="cb46-5"><a href="#cb46-5" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;zippo = </span><span class="sc">%p</span><span class="st">, zippo + 1 = </span><span class="sc">%p\n</span><span class="st">&quot;</span><span class="op">,</span> zippo<span class="op">,</span> zippo <span class="op">+</span> <span class="dv">1</span><span class="op">);</span></span>
1585<span id="cb46-6"><a href="#cb46-6" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;zippo[0] = </span><span class="sc">%p</span><span class="st">, zippo[0] + 1 = </span><span class="sc">%p\n</span><span class="st">&quot;</span><span class="op">,</span> zippo<span class="op">[</span><span class="dv">0</span><span class="op">],</span> zippo<span class="op">[</span><span class="dv">0</span><span class="op">]</span> <span class="op">+</span> <span class="dv">1</span><span class="op">);</span></span>
1586<span id="cb46-7"><a href="#cb46-7" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;*zippo = </span><span class="sc">%p</span><span class="st">, *zippo + 1 = </span><span class="sc">%p\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">*</span>zippo<span class="op">,</span> <span class="op">*</span>zippo <span class="op">+</span> <span class="dv">1</span><span class="op">);</span></span>
1587<span id="cb46-8"><a href="#cb46-8" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;zippo[0][0] = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> zippo<span class="op">[</span><span class="dv">0</span><span class="op">][</span><span class="dv">0</span><span class="op">]);
1587</span></span>
1588<span id="cb46-9"><a href="#cb46-9" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;*zippo[0] = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">*</span>zippo<span class="op">[</span><span class="dv">0</span><span class="op">]);</span></span>
1589<span id="cb46-10"><a href="#cb46-10" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;**zippo = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">**</span>zippo<span class="op">);</span></span>
1590<span id="cb46-11"><a href="#cb46-11" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;zippo[2][1] = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> zippo<span class="op">[</span><span class="dv">2</span><span class="op">][</span><span class="dv">1</span><span class="op">]);</span></span>
1591<span id="cb46-12"><a href="#cb46-12" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;*(*(zippo+2) + 1) = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> <span class="op">*(*(</span>zippo <span class="op">+</span> <span class="dv">2</span><span class="op">)</span> <span class="op">+</span> <span class="dv">1</span><span class="op">));</span></span>
1592<span id="cb46-13"><a href="#cb46-13" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1593<span id="cb46-14"><a href="#cb46-14" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1594<p>示例输出:</p>
1595<pre><code>zippo = 0x0064fd38, zippo + 1 = 0x0064fd40
1596zippo[0]= 0x0064fd38, zippo[0] + 1 = 0x0064fd3c
1597*zippo = 0x0064fd38, *zippo + 1 = 0x0064fd3c
1598zippo[0][0] = 2
1599*zippo[0] = 2
1600**zippo = 2
1601zippo[2][1] = 3
1602*(*(zippo+2) + 1) = 3</code></pre>
1603<p>下图演示了数组地址、数组内容和指针之间的关系。</p>
1604<p><img src="/img/cdetails/arrptr.jpg" style="zoom: 80%;" /></p>
1605<h4 id="函数的多维数组形参">3.2.2 函数的(多维)数组形参</h4>
1606<p>已知二维数组最后一维的长度时,可以将其作为参数传入函数。假设有如下数组和函数调用:</p>
1607<div class="sourceCode" id="cb48"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb48-1"><a href="#cb48-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> junk<span class="op">[</span><span class="dv">3</span><span class="op">][</span><span class="dv">4</span><span class="op">]</span> <span class="op">=</span> <span class="op">&#123;</span> <span class="op">&#123;</span><span class="dv">2</span><span class="op">,</span><span class="dv">4</span><span class="op">,</span><span class="dv">5</span><span class="op">,</span><span class="dv">8</span><span class="op">&#125;,</span> <span class="op">&#123;</span><span class="dv">3</span><span class="op">,</span><span class="dv">5</span><span class="op">,</span><span class="dv">6</span><span class="op">,</span><span class="dv">9</span><span class="op">&#125;,</span> <span class="op">&#123;</span><span class="dv">12</span><span class="op">,</span><span class="dv">10</span><span class="op">,</span><span class="dv">8</span><span class="op">,</span><span class="dv">6</span><span class="op">&#125;</span> <span class="op">&#125;;</span></span>
1608<span id="cb48-2"><a href="#cb48-2" aria-hidden="true" tabindex="-1"></a>somfunction<span class="op">(</span>junk<span class="op">);</span></span></code></pre></div>
1609<p>通过前面的讨论可知,<code>junk</code> 是一个指向数组(内含 4 个
1610<code>int</code> 类型值)的指针。可以这样声明函数的形参:</p>
1611<div class="sourceCode" id="cb49"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb49-1"><a href="#cb49-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> somefunction<span class="op">(</span> <span class="dt">int</span> <span class="op">(*</span>
1611 pt<span class="op">)[</span><span class="dv">4</span><span class="op">]</span> <span class="op">);</span></span></code></pre></div>
1612<p>另外,如果当且仅当 <code>pt</code>
1613是一个函数的形式参数时,可以这样声明:</p>
1614<div class="sourceCode" id="cb50"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb50-1"><a href="#cb50-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> somefunction<span class="op">(</span> <span class="dt">int</span> pt<span class="op">[][</span><span class="dv">4</span><span class="op">]</span> <span class="op">);</span></span></code></pre></div>
1615<p>注意,<strong>第 1 个</strong>方括号是空的。空的方括号表明
1616<code>pt</code> 是一个指针。</p>
1617<h4 id="c-风格字符串">3.2.3 C 风格字符串</h4>
1618<p>C 风格字符串是以空字符( <code>'\0'</code> )结尾的 <code>char</code>
1619类型数组(<strong>注意:</strong>定义字符串的时候要为末尾空字符预留空间)。因此,数组和指针的相关行为在字符串语境下是不变的。</p>
1620<p><strong>字符串字面常量</strong></p>
1621<div class="sourceCode" id="cb51"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb51-1"><a href="#cb51-1" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> <span class="op">*</span>sp <span class="op">=</span> <span class="st">&quot;Hello&quot;</span><span class="op">;</span></span></code></pre></div>
1622<ul>
1623<li><code>"Hello"</code> 是一个字符串字面常量,存储在只读内存段。</li>
1624<li><code>sp</code> 是一个指向该字符串首字符的 <code>char*</code>
1625指针。</li>
1626<li><strong>不能通过 <code>sp</code>
1627修改字符串内容</strong>(尝试修改会导致未定义行为,通常程序崩溃)。</li>
1628</ul>
1629<p>示例程序:</p>
1630<div class="sourceCode" id="cb52"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb52-1"><a href="#cb52-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1631<span id="cb52-2"><a href="#cb52-2" aria-hidden="true" tabindex="-1"></a></span>
1632<span id="cb52-3"><a href="#cb52-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1633<span id="cb52-4"><a href="#cb52-4" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1634<span id="cb52-5"><a href="#cb52-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span><span class="op">*</span> sp <span class="op">=</span> <span class="st">&quot;Hello&quot;</span><span class="op">;</span> <span class="co">// sp 指向只读内存</span></span>
1635<span id="cb52-6"><a href="#cb52-6" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> sp<span class="op">);</span></span>
1636<span id="cb52-7"><a href="#cb52-7" aria-hidden="true" tabindex="-1"></a></span>
1637<span id="cb52-8"><a href="#cb52-8" aria-hidden="true" tabindex="-1"></a>  <span class="co">// sp[0] = &#39;h&#39;;            // 严重错误!试图修改只读内存(运行时可能崩溃)</span></span>
1638<span id="cb52-9"><a href="#cb52-9" aria-hidden="true" tabindex="-1"></a></span>
1639<span id="cb52-10"><a href="#cb52-10" aria-hidden="true" tabindex="-1"></a>  sp <span class="op">=</span> <span class="st">&quot;World&quot;</span><span class="op">;</span> <span class="co">// 合法:可以改变指针指向</span></span>
1640<span id="cb52-11"><a href="#cb52-11" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> sp<span class="op">);</span></span>
1641<span id="cb52-12"><a href="#cb52-12" aria-hidden="true" tabindex="-1"></a></span>
1642<span id="cb52-13"><a href="#cb52-13" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1643<span id="cb52-14"><a href="#cb52-14" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1644<blockquote>
1645<p>编译器通常不会阻止 <code>char *sp = "..."</code> 的写法,但建议写成
1646<code>const char *sp = "..."</code>,以表明该字符串不可修改。</p>
1647</blockquote>
1648<p><strong>字符数组(可修改的字符串)</strong></p>
1649<div class="sourceCode" id="cb53"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb53-1"><a href="#cb53-1" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> sa<span class="op">[]</span> <span class="op">=</span> <span class="st">&quot;Hello&quot;</span><span class="op">;</span></span></code></pre></div>
1650<ul>
1651<li>编译器在栈上(或全局数据区)分配足够空间(包括结尾的
1652<code>'\0'</code>),并将字符串字面量的内容复制进去。</li>
1653<li>数组内容<strong>可以被修改</strong>。</li>
1654<li>数组名 <code>sa</code>
1655是一个<strong>常量指针</strong>(地址不可变),但内容可变。</li>
1656</ul>
1657<p>示例程序:</p>
1658<div class="sourceCode" id="cb54"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb54-1"><a href="#cb54-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1659<span id="cb54-2"><a href="#cb54-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;string.h&gt;</span></span>
1660<span id="cb54-3"><a href="#cb54-3" aria-hidden="true" tabindex="-1"></a></span>
1661<span id="cb54-4"><a href="#cb54-4" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1662<span id="cb54-5"><a href="#cb54-5" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1663<span id="cb54-6"><a href="#cb54-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> sa<span class="op">[]</span> <span class="op">=</span> <span class="st">&quot;Hello&quot;</span><span class="op">;</span> <span class="co">// 在栈上创建可修改副本</span></span>
1664<span id="cb54-7"><a href="#cb54-7" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> sa<span class="op">);</span></span>
1665<span id="cb54-8"><a href="#cb54-8" aria-hidden="true" tabindex="-1"></a></span>
1666<span id="cb54-9"><a href="#cb54-9" aria-hidden="true" tabindex="-1"></a>
1666  sa<span class="op">[</span><span class="dv">0</span><span class="op">]</span> <span class="op">=</span> <span class="ch">&#39;h&#39;</span><span class="op">;</span>        <span class="co">// 合法:修改内容</span></span>
1667<span id="cb54-10"><a href="#cb54-10" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> sa<span class="op">);</span> <span class="co">// 输出 &quot;hello&quot;</span></span>
1668<span id="cb54-11"><a href="#cb54-11" aria-hidden="true" tabindex="-1"></a></span>
1669<span id="cb54-12"><a href="#cb54-12" aria-hidden="true" tabindex="-1"></a>  <span class="co">// sa = &quot;World&quot;;           // 错误!数组名是常量,不能重新赋值</span></span>
1670<span id="cb54-13"><a href="#cb54-13" aria-hidden="true" tabindex="-1"></a></span>
1671<span id="cb54-14"><a href="#cb54-14" aria-hidden="true" tabindex="-1"></a>  strcpy<span class="op">(</span>sa<span class="op">,</span> <span class="st">&quot;World&quot;</span><span class="op">);</span> <span class="co">// 合法(确保空间足够)</span></span>
1672<span id="cb54-15"><a href="#cb54-15" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> sa<span class="op">);</span></span>
1673<span id="cb54-16"><a href="#cb54-16" aria-hidden="true" tabindex="-1"></a></span>
1674<span id="cb54-17"><a href="#cb54-17" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1675<span id="cb54-18"><a href="#cb54-18" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1676<blockquote>
1677<p>注意: <code>"Hello"</code> 需要 6 个字节(含
1678<code>'\0'</code>),因此不能写为 <code>char sa[5]</code> 而应写为
1679<code>char sa[6]</code> 或直接用 <code>char sa[]</code>
1680让编译器自动计算长度。</p>
1681</blockquote>
1682<p>数组形式和指针形式有何不同?以上面的声明为例,数组形式(<code>sa[]</code>)在计算机的内存中分配为一个内含
16836 个元素的数组(每个元素对应一个字符,还加上一个末尾的空字符
1684<code>'\0'</code>),每个元素被初始化为字符串字面量对应的字符。通常,字符串都作为可执行文件的一部分储存在数据段中。当把程序载入内存时,也载入了程序中的字符串。字符串储存在静态存储区(static
1685memory)中。但是,程序在开始运行时才会为该数组分配内存。此时,才将字符串拷贝到数组中。注意,此时字符串有两个副本。一个是在静态内存中的字符串字面量,另一个是储存在
1686<code>sa</code> 数组中的字符串。</p>
1687<p>此后,编译器便把数组名 <code>sa</code>
1688识别为该数组首元素地址(<code>&amp;sa[0]</code>)的别名。这里关键要理解,在数组形式中,<code>sa</code>
1689是地址常量。不能更改 <code>sa</code>,如果改变了
1690<code>sa</code>,则意味着改变了数组的存储位置(即地址)。可以进行类似
1691<code>sa+1</code> 这样的操作,标识数组的下一个元素。但是不允许进行
1692<code>++sa</code>
1693这样的操作。递增运算符只能用于变量名前(或概括地说,只能用于可修改的左值),不能用于常量。</p>
1694<p>指针形式(<code>*sp</code>)也使得编译器为字符串在静态存储区预留 6
1695个元素的空间。另外,一旦开始执行程序,它会为指针变量 <code>sp</code>
1696留出一个储存位置,并把字符串的地址储存在指针变量中。该变量最初指向该字符串的首字符,但是它的值可以改变。因此,可以使用递增运算符。例如,<code>++sp</code>
1697将指向第 2 个字符(<code>'e'</code>)。</p>
1698<p>字符串字面量被视为 <code>const</code> 数据。由于 <code>sp</code>
1699指向这个 <code>const</code> 数据,所以应该把 <code>sp</code> 声明为指向
1700<code>const</code> 数据的指针。这意味着不能用 <code>sp</code>
1701改变它所指向的数据,但是仍然可以改变 <code>sp</code>
1702的值(即,<code>sp</code>
1703指向的位置)。如果把一个字符串字面量拷贝给一个数组,就可以随意改变数据,除非把数组声明为
1704<code>const</code>。</p>
1705<p>总之,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。</p>
1706<h3 id="结构体指针">3.3 结构体指针</h3>
1707<h4 id="内存对齐">3.3.1 内存对齐</h4>
1708<p><strong>基本对齐规则</strong></p>
1709<ol type="1">
1710<li>每个成员的<strong>起始地址</strong>必须是其自身大小(或其对齐要求)的整数倍。</li>
1711<li>结构体总大小必须是结构体中<strong>最大对齐要求</strong>的整数倍(即对齐到最大成员的对齐边界)。</li>
1712<li>编译器可能会在成员之间插入<strong>填充字节</strong>(padding),以满足对齐要求。</li>
1713</ol>
1714<p><strong>示例 1:</strong></p>
1715<div class="sourceCode" id="cb55"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb55-1"><a href="#cb55-1" aria-hidden="true" tabindex="-1"></a><span class="kw">
1715struct</span> S1 <span class="op">&#123;</span></span>
1716<span id="cb55-2"><a href="#cb55-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> a<span class="op">;</span>   <span class="co">// 1 字节</span></span>
1717<span id="cb55-3"><a href="#cb55-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">short</span> b<span class="op">;</span>  <span class="co">// 2 字节</span></span>
1718<span id="cb55-4"><a href="#cb55-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">double</span> c<span class="op">;</span> <span class="co">// 8 字节</span></span>
1719<span id="cb55-5"><a href="#cb55-5" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;;</span></span></code></pre></div>
1720<p>内存布局分析:</p>
1721<ul>
1722<li>偏移 0:<code>a</code>(1 字节)</li>
1723<li><code>b</code> 需要 2 字节对齐 → 下一个 2 的倍数是 2 → 在偏移
17242(需在 <code>a</code> 后填充 1 字节)
1725<ul>
1726<li>偏移 1:填充 1 字节</li>
1727<li>偏移 2~3:<code>b</code></li>
1728</ul></li>
1729<li><code>c</code> 需要 8 字节对齐 → 下一个 8 的倍数是 8
1730<ul>
1731<li>偏移 4~7:填充 4 字节</li>
1732<li>偏移 8~15:<code>c</code></li>
1733</ul></li>
1734</ul>
1735<p>总大小:</p>
1736<ul>
1737<li>数据 + 填充 = 1 + 1(pad)+ 2 + 4(pad)+ 8 = 16 字节</li>
1738<li>最大对齐要求 = 8(来自 <code>double</code>),16 是 8 的倍数 →
1739合法</li>
1740</ul>
1741<p><code>sizeof(struct S1) = 16</code></p>
1742<p><strong>示例 2:</strong></p>
1743<div class="sourceCode" id="cb56"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb56-1"><a href="#cb56-1" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> S2 <span class="op">&#123;</span></span>
1744<span id="cb56-2"><a href="#cb56-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> a<span class="op">;</span>   <span class="co">// 1 字节</span></span>
1745<span id="cb56-3"><a href="#cb56-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">double</span> b<span class="op">;</span> <span class="co">// 8 字节</span></span>
1746<span id="cb56-4"><a href="#cb56-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">short</span> c<span class="op">;</span>  <span class="co">// 2 字节</span></span>
1747<span id="cb56-5"><a href="#cb56-5" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;;</span></span></code></pre></div>
1748<p>内存布局分析:</p>
1749<ul>
1750<li>偏移 0:<code>a</code>(1 字节)</li>
1751<li><code>b</code> 需要 8 字节对齐 → 下一个 8 的倍数是 8
1752<ul>
1753<li>偏移 1~7:填充 7 字节</li>
1754<li>偏移 8~15:<code>b</code></li>
1755</ul></li>
1756<li><code>c</code> 需要 2 字节对齐 → 偏移 16 是 2 的倍数 → 放在
175716~17</li>
1758<li>当前总大小 = 18 字节</li>
1759<li>但结构体总大小必须是 最大对齐要求(8)的整数倍
1760<ul>
1761<li>18 不是 8 的倍数 → 补到 24</li>
1762</ul></li>
1763</ul>
1764<p>总大小:</p>
1765<ul>
1766<li>1(a)+ 7(pad)+ 8(b)+ 2(c)+ 6(尾部 pad) = 24 字节</li>
1767</ul>
1768<p><code>sizeof(struct S2) = 24</code></p>
1769<h4 id="用指针访问成员">3.3.2 用指针访问成员</h4>
1770<div class="sourceCode" id="cb57"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb57-1"><a href="#cb57-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#define LEN </span><span class="dv">20</span></span>
1771<span id="cb57-2"><a href="#cb57-2" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> guy <span class="op">&#123;</span></span>
1772<span id="cb57-3"><a href="#cb57-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> favfood<span class="op">[</span>LEN<span class="op">];</span></span>
1773<span id="cb57-4"><a href="#cb57-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">char</span> job<span class="op">[</span>LEN<span class="op">];</span></span>
1774<span id="cb57-5"><a href="#cb57-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">float</span> income<span class="op">;</span></span>
1775<span id="cb57-6"><a href="#cb57-6" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span> fellow<span class="op">;</span></span></code></pre></div>
1776<p>假设指针 <code>him</code> 指向结构变量 <code>fellow</code>,如何通过
1777<code>him</code> 获得 <code>fellow</code> 的成员的值?</p>
1778<p>最常用的方法:使用 <code>-&gt;</code> 运算符。如果
1779<code>him == &amp;fellow</code>,那么 <code>him-&gt;income</code> 即是
1780<code>fellow.income</code>。换句话说,<code>-&gt;</code>
1781运算符后面的结构指针和 <code>.</code>
1782运算符后面的结构名工作方式相同(不能写成 <code>him.incone</code>,因为
1783<code>him</code> 不是结构名)。</p>
1784<p>另一种方法是,以这样的顺序指定结构成员的值:如果
1785<code>him == &amp;fellow</code>,那么 <code>*him == fellow</code>,因为
1786<code>&amp;</code> 和 <code>*</code>
1787是一对互逆运算符。因此,可以做以下替代:</p>
1788<div class="sourceCode" id="cb58"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb58-1"><a href="#cb58-1" aria-hidden="true" tabindex="-1"></a>fellow<span class="op">.</span>income <span class="op">==</span> <span class="op">(*</span>him<span class="op">).</span>income</span></code></pre></div>
1789<p>必须要使用圆括号,因为 <code>.</code> 运算符比 <code>*</code>
1790运算符的优先级高。</p>
1791<p>总之,如果 <code>him</code> 是指向 <code>guy</code> 类型结构
1792<code>fellow</code> 的指针,下面的关系恒成立:</p>
1793<div class="sourceCode" id="cb59"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb59-1"><a href="#cb59-1" aria-hidden="true" tabindex="-1"></a>fellow<span class="op">.</span>income <span class="op">==</span> <span class="op">(*</span>him<span class="op">).</span>income <span class="op">==</span> him<span class="op">-&gt;</span>income <span class="co">// 假设 him == &amp;fellow</span></span></code></pre></div>
1794<h3 id="函数指针">3.4 函数指针</h3>
1795<h4 id="定义和使用">3.4.1 定义和使用</h4>
1796<p>函数指针是指向函数的指针变量。在 C
1797语言中,函数名本身就是一个指向该函数的指针,因此可以将函数地址赋给函数指针变量,并通过该指针调用函数。</p>
1798<p>函数指针的语法如下:</p>
1799<div class="sourceCode" id="cb60"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb60-1"><a href="#cb60-1" aria-hidden="true" tabindex="-1"></a>返回类型 <span class="op">(*</span>指针名<span class="op">)(</span>参数类型列表<span class="op">);</span></span></code></pre></div>
1800<p>例如,定义一个指向接受两个 <code>int</code> 参数并返回
1801<code>int</code> 的函数的指针:</p>
1802<div class="sourceCode" id="cb61"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb61-1"><a href="#cb61-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">(*</span>func_ptr<span class="op">)(</span><span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">);</span></span></code></pre></div>
1803<blockquote>
1804<p>注意:<code>*func_ptr</code> 必须用括号括起来,否则会被解释为返回
1805<code>int*</code> 的函数声明。</p>
1806</blockquote>
1807<p>示例:定义函数并用函数指针调用</p>
1808<div class="sourceCode" id="cb62"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb62-1"><a href="#cb62-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1809<span id="cb62-2"><a href="#cb62-2" aria-hidden="true" tabindex="-1"></a></span>
1810<span id="cb62-3"><a href="#cb62-3" aria-hidden="true" tabindex="-1"></a><span class="co">// 定义两个普通函数</span></span>
1811<span id="cb62-4"><a href="#cb62-4" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> add<span class="op">(</span><span class="dt">int</span> a<span class="op">,</span> <span class="dt">int</span> b<span class="op">)</span> <span class="op">&#123;</span> <span class="cf">return</span> a <span class="op">+</span> b<span class="op">;</span> <span class="op">&#125;</span></span>
1812<span id="cb62-5"><a href="#cb62-5" aria-hidden="true" tabindex="-1"></a></span>
1813<span id="cb62-6"><a href="#cb62-6" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> subtract<span class="op">(</span><span class="dt">int</span> a<span class="op">,</span> <span class="dt">int</span> b<span class="op">)</span> <span class="op">&#123;</span> <span class="cf">return</span> a <span class="op">-</span> b<span class="op">;</span> <span class="op">&#125;</span></span>
1814<span id="cb62-7"><a href="#cb62-7" aria-hidden="true" tabindex="-1"></a></span>
1815<span id="cb62-8"><a href="#cb62-8" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1816<span id="cb62-9"><a href="#cb62-9" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1817<span id="cb62-10"><a href="#cb62-10" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 声明函数指针</span></span>
1818<span id="cb62-11"><a href="#cb62-11" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> <span class="op">(*</span>operation<span class="op">)(</span><span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">);</span></span>
1819<span id="cb62-12"><a href="#cb62-12" aria-hidden="true" tabindex="-1"></a></span>
1820<span id="cb62-13"><a href="#cb62-13" aria-hidden="true" tabindex="-1"></a>  <span class="co">// 将函数地址赋给指针(函数名即地址)</span></span>
1821<span id="cb62-14"><a href="#cb62-14" aria-hidden="true" tabindex="-1"></a>  operation <span class="op">=</span> add<span class="op">;</span></span>
1822<span id="cb62-15"><a href="#cb62-15" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;5 + 3 = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> operation<span class="op">(</span><span class="dv">5</span><span class="op">,</span> <span class="dv">3</span><span class="op">));</span> <span class="co">// 输出: 8</span></span>
1823<span id="cb62-16"><a href="#cb62-16" aria-hidden="true" tabindex="-1"></a></span>
1824<span id="cb62-17"><a href="#cb62-17" aria-hidden="true" tabindex="-1"></a>  operation <span class="op">=</span> subtract<span class="op">;</span></span>
1825<span id="cb62-18"><a href="#cb62-18" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;5 - 3 = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> operation<span class="op">(</span><span class="dv">5</span><span class="op">,</span> <span class="dv">3</span><span class="op">));</span> <span class="co">// 输出: 2</span></span>
1826<span id="cb62-19"><a href="#cb62-19" aria-hidden="true" tabindex="-1"></a></span>
1827<span id="cb62-20"><a href="#cb62-20" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1828<span id="cb62-21"><a href="#cb62-21" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1829<p>函数指针常用于实现<strong>回调机制</strong>,例如:</p>
1830<div class="sourceCode" id="cb63"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb63-1"><a href="#cb63-1" aria-hidden="true" tabindex="-1"></a><span class="co">// 接受一个函数指针作为参数</span></span>
1831<span id="cb63-2"><a href="#cb63-2" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span>
1831 calculate<span class="op">(</span><span class="dt">int</span> x<span class="op">,</span> <span class="dt">int</span> y<span class="op">,</span> <span class="dt">int</span> <span class="op">(*</span>op<span class="op">)(</span><span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">))</span></span>
1832<span id="cb63-3"><a href="#cb63-3" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1833<span id="cb63-4"><a href="#cb63-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> result <span class="op">=</span> op<span class="op">(</span>x<span class="op">,</span> y<span class="op">);</span></span>
1834<span id="cb63-5"><a href="#cb63-5" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;Result: </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> result<span class="op">);</span></span>
1835<span id="cb63-6"><a href="#cb63-6" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span>
1836<span id="cb63-7"><a href="#cb63-7" aria-hidden="true" tabindex="-1"></a></span>
1837<span id="cb63-8"><a href="#cb63-8" aria-hidden="true" tabindex="-1"></a><span class="co">// 使用</span></span>
1838<span id="cb63-9"><a href="#cb63-9" aria-hidden="true" tabindex="-1"></a>
1838calculate<span class="op">(</span><span class="dv">10</span><span class="op">,</span> <span class="dv">4</span><span class="op">,</span> add<span class="op">);</span>      <span class="co">// 输出: Result: 14</span></span>
1839<span id="cb63-10"><a href="#cb63-10" aria-hidden="true" tabindex="-1"></a>calculate<span class="op">(</span><span class="dv">10</span><span class="op">,</span> <span class="dv">4</span><span class="op">,</span> subtract<span class="op">);</span> <span class="co">// 输出: Result: 6</span></span></code></pre></div>
1840<p>以下两种通过函数指针调用函数的方式是等价的:</p>
1841<div class="sourceCode" id="cb64"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb64-1"><a href="#cb64-1" aria-hidden="true" tabindex="-1"></a>operation <span class="op">=</span> subtract<span class="op">;</span></span>
1842<span id="cb64-2"><a href="#cb64-2" aria-hidden="true" tabindex="-1"></a></span>
1843<span id="cb64-3"><a href="#cb64-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> res1 <span class="op">=</span> operation<span class="op">(</span><span class="dv">5</span><span class="op">,</span> <span class="dv">3</span><span class="op">);</span> <span class="co">// res1 = 2</span></span>
1844<span id="cb64-4"><a href="#cb64-4" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> res2 <span class="op">=</span> <span class="op">(*</span>operation<span class="op">)(</span><span class="dv">5</span><span class="op">,</span> <span class="dv">3</span><span class="op">);</span> <span class="co">// res2 = 2</span></span></code></pre></div>
1845<p>这两种方法看上去都合情合理。先分析第 1 种方法:由于
1846<code>operation</code> 指向 <code>subtract</code> 函数,那么
1847<code>*operation</code> 就相当于<code>subtract</code> 函数,所以表达式
1848<code>(*operation)(5, 3)</code> 和 <code>subtract(5, 3)</code> 相同。从
1849<code>subtract</code> 函数和 <code>operation</code>
1850的声明就能看出,<code>subtract</code> 和 <code>(*operation)</code>
1851是等价的。第 2
1852种方法:由于函数名是指针,那么指针和函数名可以互换使用,所以
1853<code>operation(5, 3)</code> 和 <code>subtract(5, 3)</code> 相同。从
1854<code>operation</code> 的赋值表达式语句就能看出 <code>subtract</code> 和
1855<code>operation</code> 是等价的。</p>
1856<h4 id="函数式编程风格">3.4.2 函数式编程风格</h4>
1857<blockquote>
1858<p>注意:由于 C 语言并没有提供很好的函数式抽象功能,因此大多数时候实用 C
1859语言进行函数式编程并不是一个很好的注意。</p>
1860</blockquote>
1861<p>函数式编程是一个很宏大的话题,这里给出一个例子供读者体会函数指针在 C
1862语言函数式编程中的作用。</p>
1863<div class="sourceCode" id="cb65"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb65-1"><a href="#cb65-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
1864<span id="cb65-2"><a href="#cb65-2" aria-hidden="true" tabindex="-1"></a></span>
1865<span id="cb65-3"><a href="#cb65-3" aria-hidden="true" tabindex="-1"></a><span class="co">// Function signature for our operations</span></span>
1866<span id="cb65-4"><a href="#cb65-4" aria-hidden="true" tabindex="-1"></a><span class="kw">typedef</span> <span class="dt">int</span> <span class="op">(*</span>Func<span class="op">)(</span><span class="dt">int</span><span class="op">);</span></span>
1867<span id="cb65-5"><a href="#cb65-5" aria-hidden="true" tabindex="-1"></a></span>
1868<span id="cb65-6"><a href="#cb65-6" aria-hidden="true" tabindex="-1"></a><span class="co">// Structure to represent a closure-like object</span></span>
1869<span id="cb65-7"><a href="#cb65-7" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> repeat_apply<span class="op">(</span>Func f<span class="op">,</span> <span class="dt">int</span> n<span class="op">,</span> <span class="dt">int</span> x<span class="op">)</span></span>
1870<span id="cb65-8"><a href="#cb65-8" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1871<span id="cb65-9"><a href="#cb65-9" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="op">(</span>n <span class="op">==</span> <span class="dv">1</span><span class="op">)</span> <span class="op">&#123;</span></span>
1872<span id="cb65-10"><a href="#cb65-10" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> f<span class="op">(</span>
1872x<span class="op">);</span></span>
1873<span id="cb65-11"><a href="#cb65-11" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#125;</span> <span class="cf">else</span> <span class="op">&#123;</span></span>
1874<span id="cb65-12"><a href="#cb65-12" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> f<span class="op">(</span>repeat_apply<span class="op">(</span>f<span class="op">,</span> n <span class="op">-</span> <span class="dv">1</span><span class="op">,</span> x<span class="op">));</span></span>
1875<span id="cb65-13"><a href="#cb65-13" aria-hidden="true" tabindex="-1"></a>  <span class="op">&#125;</span></span>
1876<span id="cb65-14"><a href="#cb65-14" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span>
1877<span id="cb65-15"><a href="#cb65-15" aria-hidden="true" tabindex="-1"></a></span>
1878<span id="cb65-16"><a href="#cb65-16" aria-hidden="true" tabindex="-1"></a><span class="co">// The functions to be repeated</span></span>
1879<span id="cb65-17"><a href="#cb65-17" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> add<span class="op">(</span><span class="dt">int</span> x<span class="op">)</span> <span class="op">&#123;</span> <span class="cf">return</span> x <span class="op">+</span> <span class="dv">1</span><span class="op">;</span> <span class="op">&#125;</span></span>
1880<span id="cb65-18"><a href="#cb65-18" aria-hidden="true" tabindex="-1"></a></span>
1881<span id="cb65-19"><a href="#cb65-19" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> square<span class="op">(</span><span class="dt">int</span> x<span class="op">)</span> <span class="op">&#123;</span> <span class="cf">return</span> x <span class="op">*</span> x<span class="op">;</span> <span class="op">&#125;</span></span>
1882<span id="cb65-20"><a href="#cb65-20" aria-hidden="true" tabindex="-1"></a></span>
1883<span id="cb65-21"><a href="#cb65-21" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
1884<span id="cb65-22"><a href="#cb65-22" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
1885<span id="cb65-23"><a href="#cb65-23" aria-hidden="true" tabindex="-1"></a>  <span class="co">// Test cases</span></span>
1886<span id="cb65-24"><a href="#cb65-24" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> repeat_apply<span class="op">(</span>add<span class="op">,</span> <span class="dv">5</span><span class="op">,</span> <span class="dv">1</span><span class="op">));</span>    <span class="co">// Should output 6</span></span>
1887<span id="cb65-25"><a href="#cb65-25" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> repeat_apply<span class="op">(</span>square<span class="op">,</span> <span class="dv">3</span><span class="op">,</span> <span class="dv">2</span><span class="op">));</span> <span class="co">// Should output 256</span></span>
1888<span id="cb65-26"><a href="#cb65-26" aria-hidden="true" tabindex="-1"></a></span>
1889<span id="cb65-27"><a href="#cb65-27" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
1890<span id="cb65-28"><a href="#cb65-28" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
1891<p><code>repeat_apply</code> 的功能是把传入的函数 <code>F</code> 在
1892<code>x</code> 上应用 <code>n</code> 次。它并不关心 <code>F</code>
1893具体是什么函数,而仅仅关注“重复 <code>n</code>
1894次”这一目标。这体现了函数式编程的一个特点:<strong>关注做什么而不是怎么做</strong>。</p>
1895<h2 id="实用标准库函数">4 实用标准库函数</h2>
1896<h3 id="字符相关">4.1 字符相关</h3>
1897<p>本节所介绍的函数均定义在 <code>ctype.h</code> 头文件中。</p>
1898<h4 id="检查字符类别">4.1.1 检查字符类别</h4>
1899<table>
1900<colgroup>
1901<col style="width: 9%" />
1902<col style="width: 52%" />
1903<col style="width: 38%" />
1904</colgroup>
1905<thead>
1906<tr>
1907<th>函数名</th>
1908<th>功能说明</th>
1909<th>返回值(真条件)</th>
1910</tr>
1911</thead>
1912<tbody>
1913<tr>
1914<td><code>isalnum</code></td>
1915<td>判断字符是否为字母或数字</td>
1916<td>字符是字母(a–z, A–Z)或数字(0–9)</td>
1917</tr>
1918<tr>
1919<td><code>isalpha</code></td>
1920<td>判断字符是否为字母</td>
1921<td>字符是字母(a–z, A–Z)</td>
1922</tr>
1923<tr>
1924<td><code>islower</code></td>
1925<td>判断字符是否为小写字母</td>
1926<td>字符是小写字母(a–z)</td>
1927</tr>
1928<tr>
1929<td><code>isupper</code></td>
1930<td>判断字符是否为大写字母</td>
1931<td>字符是大写字母(A–Z)</td>
1932</tr>
1933<tr>
1934<td><code>isdigit</code></td>
1935<td>判断字符是否为十进制数字</td>
1936<td>字符是数字(0–9)</td>
1937</tr>
1938<tr>
1939<td><code>isspace</code></td>
1940<td>判断字符是否为空白字符</td>
1941<td>字符是空格、制表符、换行等空白符</td>
1942</tr>
1943<tr>
1944<td><code>ispunct</code></td>
1945<td>判断字符是否为标点符号(非字母、非数字、非空白)</td>
1946<td>字符是标点符号(如 !, ?, ., ; 等)</td>
1947</tr>
1948</tbody>
1949</table>
1950<blockquote>
1951<p>所有这些函数都接受 <code>int</code> 类型参数(通常传入
1952<code>unsigned char</code> 类型的字符),并返回非零值(真)或
19530(假)。</p>
1954</blockquote>
1955<h4 id="大小写转换">4.1.2 大小写转换</h4>
1956<table>
1957<colgroup>
1958<col style="width: 9%" />
1959<col style="width: 31%" />
1960<col style="width: 59%" />
1961</colgroup>
1962<thead>
1963<tr>
1964<th>函数名</th>
1965<th>功能说明</th>
1966<th>返回值</th>
1967</tr>
1968</thead>
1969<tbody>
1970<tr>
1971<td><code>tolower</code></td>
1972<td>将大写字母转换为对应的小写字母</td>
1973<td>若参数是大写字母(A–Z),返回对应小写字母;否则返回原字符</td>
1974</tr>
1975<tr>
1976<td><code>toupper</code></td>
1977<td>将小写字母转换为对应的大写字母</td>
1978<td>若参数是小写字母(a–z),返回对应大写字母;否则返回原字符</td>
1979</tr>
1980</tbody>
1981</table>
1982<blockquote>
1983<ul>
1984<li>两个函数均接受 <code>int</code> 类型参数(通常为
1985<code>unsigned char</code> 范围内的字符)。<br />
1986</li>
1987<li>如果传入的字符不是可转换的字母,函数直接返回该字符不变。</li>
1988</ul>
1989</blockquote>
1990<h3 id="字符串相关">4.2 字符串相关</h3>
1991<h4 id="字符串操作">4.2.1 字符串操作</h4>
1992<p>本节所介绍的函数均定义在 <code>string.h</code> 头文件中。</p>
1993<table>
1994<colgroup>
1995<col style="width: 3%" />
1996<col style="width: 23%" />
1997<col style="width: 24%" />
1998<col style="width: 24%" />
1999<col style="width: 24%" />
2000</colgroup>
2001<thead>
2002<tr>
2003<th>函数名</th>
2004<th>原型</th>
2005<th>功能</th>
2006<th>参数</th>
2007<th>返回值</th>
2008</tr>
2009</thead>
2010<tbody>
2011<tr>
2012<td><code>strcpy</code></td>
2013<td><code>char *strcpy(char *dest, const char *src);</code></td>
2014<td>将字符串 <code>src</code> 复制到 <code>dest</code>,包括结尾的空字符
2015<code>'\0'</code></td>
2016<td><code>dest</code>:目标字符数组(必须足够大以容纳
2017<code>src</code>)<br><code>src</code>:源字符串(以 <code>'\0'</code>
2018结尾)</td>
2019<td>返回 <code>dest</code> 的指针</td>
2020</tr>
2021<tr>
2022<td><code>strcat</code></td>
2023<td><code>char *strcat(char *dest, const char *src);</code></td>
2024<td>将字符串 <code>src</code> 追加到 <code>dest</code> 末尾(覆盖
2025<code>dest</code> 原有结尾的 <code>'\0'</code>)</td>
2026<td><code>dest</code>:目标字符串(必须有足够空间容纳追加后的结果)<br><code>src</code>:要追加的源字符串</td>
2027<td>返回 <code>dest</code> 的指针</td>
2028</tr>
2029<tr>
2030<td><code>strlen</code></td>
2031<td><code>size_t strlen(const char *s);</code></td>
2032<td>计算字符串 <code>s</code> 的长度(不包括结尾的空字符
2033<code>'\0'</code>)</td>
2034<td><code>s</code>:要计算长度的字符串</td>
2035<td>返回字符串中字符的个数(类型为 <code>size_t</code>)</td>
2036</tr>
2037<tr>
2038<td><code>strcmp</code></td>
2039<td><code>int strcmp(const char *s1, const char *s2);</code></td>
2040<td>按字典顺序比较两个字符串 <code>s1</code> 和 <code>s2</code></td>
2041<td><code>s1</code>, <code>s2</code>:要比较的两个字符串</td>
2042<td>若 <code>s1 &lt; s2</code>,返回负整数;<br>若
2043<code>s1 == s2</code>,返回 0;<br>若
2044<code>s1 &gt; s2</code>,返回正整数</td>
2045</tr>
2046<tr>
2047<td><code>strchr</code></td>
2048<td><code>char *strchr(const char *s, int c);</code></td>
2049<td>在字符串 <code>s</code> 中查找字符 <code>c</code>
2050第一次出现的位置</td>
2051<td><code>s</code>:要搜索的字符串<br><code>c</code>:要查找的字符(以
2052<code>int</code> 传入,但实际视为 <code>unsigned char</code>)</td>
2053<td>若找到,返回指向该字符的指针;<br>若未找到,返回
2054<code>NULL</code></td>
2055</tr>
2056<tr>
2057<td><code>strstr</code></td>
2058<td><code>char *strstr(const char *haystack, const char *needle);</code></td>
2059<td>在字符串 <code>haystack</code> 中查找子串 <code>needle</code>
2060第一次出现的位置</td>
2061<td><code>haystack</code>:被搜索的主字符串<br><code>needle</code>:要查找的子串(若为空字符串,返回
2062<code>haystack</code> 起始位置)</td>
2063<td>若找到子串,返回子串在主串中的起始地址;<br>若未找到,返回
2064<code>NULL</code></td>
2065</tr>
2066</tbody>
2067</table>
2068<blockquote>
2069<ul>
2070<li>使用 <code>strcpy</code> 和 <code>strcat</code>
2071时务必确保目标缓冲区足够大,否则可能导致缓冲区溢出。</li>
2072<li><code>strchr</code> 和 <code>strstr</code>
2073返回的是原字符串中的地址,不可用于修改字符串(除非原字符串本身可修改)。</li>
2074</ul>
2075</blockquote>
2076<h4 id="字符串转数值">4.2.2 字符串转数值</h4>
2077<p>本节所介绍的函数均定义在 <code>stdlib.h</code> 头文件中。</p>
2078<table>
2079<colgroup>
2080<col style="width: 3%" />
2081<col style="width: 24%" />
2082<col style="width: 24%" />
2083<col style="width: 24%" />
2084<col style="width: 24%" />
2085</colgroup>
2086<thead>
2087<tr>
2088<th>函数名</th>
2089<th>原型</th>
2090<th>功能</th>
2091<th>参数</th>
2092<th>返回值</th>
2093</tr>
2094</thead>
2095<tbody>
2096<tr>
2097<td><code>atoi</code></td>
2098<td><code>int atoi(const char *nptr);</code></td>
2099<td>将字符串 <code>nptr</code> 转换为 <code>int</code> 类型整数</td>
2100<td><code>nptr</code>:指向要转换的字符串(跳过开头空白,按十进制解析)</td>
2101<td>成功时返回对应的 <code>int</code> 值;<br>若无法转换,返回
2102<code>0</code>(无错误指示)</td>
2103</tr>
2104<tr>
2105<td><code>atof</code></td>
2106<td><code>double atof(const char *nptr);</code></td>
2107<td>将字符串 <code>nptr</code> 转换为 <code>double</code>
2108类型浮点数</td>
2109<td><code>nptr</code>:指向要转换的字符串(支持科学计数法等浮点格式)</td>
2110<td>成功时返回对应的 <code>double</code> 值;<br>若无法转换,返回
2111<code>0.0</code>(无错误指示)</td>
2112</tr>
2113<tr>
2114<td><code>strtol</code></td>
2115<td><code>long int strtol(const char *nptr, char **endptr, int base);</code></td>
2116<td>将字符串 <code>nptr</code> 转换为
2117<code>long int</code>,支持任意进制(2~36 或 0)</td>
2118<td><code>nptr</code>:要转换的字符串<br><code>endptr</code>:若非
2119<code>NULL</code>,指向转换结束处的指针(可用于错误检测)<br><code>base</code>:进制(0
2120表示自动识别)</td>
2121<td>成功时返回转换后的 <code>long int</code> 值;<br>若溢出,返回
2122<code>LONG_MAX</code> 或 <code>LONG_MIN</code> 并设置
2123<code>
2123errno</code>;<br>若无法转换,返回 <code>0</code></td>
2124</tr>
2125<tr>
2126<td><code>strtof</code></td>
2127<td><code>float strtof(const char *nptr, char **endptr);</code></td>
2128<td>将字符串 <code>nptr</code> 转换为 <code>float</code> 类型浮点数</td>
2129<td><code>nptr</code>:要转换的字符串<br><code>endptr</code>:若非
2130<code>NULL</code>,指向转换结束处的指针(可用于检测非法字符或是否完全转换)</td>
2131<td>成功时返回对应的 <code>float</code> 值;<br>若溢出,返回
2132<code>±HUGE_VALF</code> 并设置 <code>errno</code>;<br>若无法转换,返回
2133<code>0.0f</code></td>
2134</tr>
2135</tbody>
2136</table>
2137<blockquote>
2138<ul>
2139<li><code>atoi</code> 和 <code>atof</code>
2140不提供错误检测机制,遇到非法输入会静默返回
21410,不推荐用于健壮性要求高的程序。</li>
2142<li><code>strtol</code> 和 <code>strtof</code> 是 更安全、功能更强大
2143的替代方案,支持错误检测(通过 <code>endptr</code> 和
2144<code>errno</code>)。</li>
2145<li>使用 <code>strtol</code> 时,若 <code>base</code> 为
21460,函数会根据字符串前缀自动判断进制:<code>0x</code>/<code>0X</code> →
2147十六进制,<code>0</code> → 八进制,否则为十进制。</li>
2148</ul>
2149</blockquote>
2150<h4 id="数值转字符串">4.2.3 数值转字符串</h4>
2151<p>这里提供两种比较常用的转换方式。</p>
2152<p><strong><code>itoa()</code> 函数:</strong></p>
2153<ul>
2154<li><p>定义在 <code>stdlib.h</code> 头文件内。</p></li>
2155<li><p>将整数转换为字符串。</p></li>
2156<li><p>函数原型:</p>
2157<div class="sourceCode" id="cb66"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb66-1"><a href="#cb66-1" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> <span class="op">*</span>itoa<span class="op">(</span><span class="dt">int</span> value<span class="op">,</span> <span class="dt">char</span> <span class="op">*</span>str<span class="op">,</span> <span class="dt">int</span> base<span class="op">);</span></span></code></pre></div>
2158<ul>
2159<li><code>value</code>:要转换的整数。</li>
2160<li><code>str</code>:用于存储结果的字符数组。</li>
2161<li><code>base</code>:进制(如 10 表示十进制,2 表示二进制,16
2162表示十六进制)。</li>
2163</ul></li>
2164<li><p>注意:</p>
2165<ul>
2166<li>不是 C 标准库函数,在某些编译器(如
2167GCC)中可能不可用,可移植性差。</li>
2168<li>常见于 Windows 平台(如 MSVC),但在 Linux/GCC
2169环境下通常不支持。</li>
2170</ul></li>
2171</ul>
2172<p><strong><code>sprintf()</code> 函数:</strong></p>
2173<ul>
2174<li><p>定义在 <code>stdio.h</code> 头文件内。</p></li>
2175<li><p>将格式化的数据写入字符串,支持多种数据类型(整数、浮点数等)。</p></li>
2176<li><p>函数原型:</p>
2177<div class="sourceCode" id="cb67"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb67-1"><a href="#cb67-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> sprintf<span class="op">(</span><span class="dt">char</span> <span class="op">*</span>str<span class="op">,</span> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>format<span class="op">,</span> <span class="op">...);</span></span></code></pre></div>
2178<ul>
2179<li><code>str</code>:目标字符串缓冲区。</li>
2180<li><code>format</code>:格式控制字符串(如
2181<code>"%d"</code>、<code>"%x"</code>)。</li>
2182<li>后续参数:要格式化的值。</li>
2183</ul></li>
2184<li><p>注意:</p>
2185<ul>
2186<li>不进行缓冲区溢出检查,使用时需确保目标缓冲区足够大。</li>
2187<li>更安全的替代是 <code>snprintf()</code>(可指定最大写入长度)。</li>
2188</ul></li>
2189</ul>
2190<h3 id="数值相关">4.3 数值相关</h3>
2191<h4 id="数学函数">4.3.1 数学函数</h4>
2192<p>本节所介绍的函数均定义在 <code>math.h</code> 头文件中。</p>
2193<table>
2194<colgroup>
2195<col style="width: 3%" />
2196<col style="width: 19%" />
2197<col style="width: 21%" />
2198<col style="width: 21%" />
2199<col style="width: 34%" />
2200</colgroup>
2201<thead>
2202<tr>
2203<th>函数名</th>
2204<th>原型</th>
2205<th>功能</th>
2206<th>参数</th>
2207<th>返回值</th>
2208</tr>
2209</thead>
2210<tbody>
2211<tr>
2212<td><code>fabs</code></td>
2213<td><code>double fabs(double x);</code></td>
2214<td>计算浮点数的绝对值</td>
2215<td><code>x</code>:要取绝对值的浮点数</td>
2216<td>返回 <code>|x|</code>(非负 <code>double</code>)</td>
2217</tr>
2218<tr>
2219<td><code>fmod</code></td>
2220<td><code>double fmod(double x, double y);</code></td>
2221<td>计算浮点数除法的余数(<code>x / y</code> 的余数)</td>
2222<td><code>x</code>:被除数<br><code>y</code>:除数(<code>y ≠ 0</code>)</td>
2223<td>返回 <code>x - n*y</code>,其中 <code>n</code> 是 <code>x/y</code>
2224的整数部分(向零取整);若 <code>y = 0</code>,结果未定义或返回
2225<code>NaN</code></td>
2226</tr>
2227<tr>
2228<td><code>exp</code></td>
2229<td><code>double exp(double x);</code></td>
2230<td>计算自然指数函数 ( e^x )</td>
2231<td><code>x</code>:指数</td>
2232<td>返回 ( e^x )</td>
2233</tr>
2234<tr>
2235<td><code>exp2</code></td>
2236<td><code>double exp2(double x);</code></td>
2237<td>计算以 2 为底的指数函数 ( 2^x )</td>
2238<td><code>x</code>:指数</td>
2239<td>返回 ( 2^x )</td>
2240</tr>
2241<tr>
2242<td><code>log</code></td>
2243<td><code>double log(double x);</code></td>
2244<td>计算自然对数(以 ( e ) 为底)</td>
2245<td><code>x</code>:真数(<code>x &gt; 0</code>)</td>
2246<td>返回 ( (x) );若 <code>x ≤ 0</code>,返回 <code>-HUGE_VAL</code> 或
2247<code>NaN</code> 并可能设置 <code>
2247errno</code></td>
2248</tr>
2249<tr>
2250<td><code>log2</code></td>
2251<td><code>double log2(double x);</code></td>
2252<td>计算以 2 为底的对数</td>
2253<td><code>x</code>:真数(<code>x &gt; 0</code>)</td>
2254<td>返回 ( _2(x) )</td>
2255</tr>
2256<tr>
2257<td><code>log10</code></td>
2258<td><code>double log10(double x);</code></td>
2259<td>计算以 10 为底的对数</td>
2260<td><code>x</code>:真数(<code>x &gt; 0</code>)</td>
2261<td>返回 ( _{10}(x) )</td>
2262</tr>
2263<tr>
2264<td><code>pow</code></td>
2265<td><code>double pow(double x, double y);</code></td>
2266<td>计算 ( x^y )</td>
2267<td><code>x</code>:底数<br><code>y</code>:指数</td>
2268<td>返回 ( x^y );若干非法(如
2269<code>0^0</code>、负数开非整数次方),返回 <code>NaN</code> 或设置
2270<code>errno</code></td>
2271</tr>
2272<tr>
2273<td><code>sqrt</code></td>
2274<td><code>double sqrt(double x);</code></td>
2275<td>计算平方根 ( )</td>
2276<td><code>x</code>:被开方数(<code>x ≥ 0</code>)</td>
2277<td>返回 ( );若 <code>x &lt; 0</code>,返回 <code>NaN</code></td>
2278</tr>
2279<tr>
2280<td><code>sin</code></td>
2281<td><code>double sin(double x);</code></td>
2282<td>计算正弦值(弧度制)</td>
2283<td><code>x</code>:弧度角</td>
2284<td>返回 ((x)),范围 ([-1, 1])</td>
2285</tr>
2286<tr>
2287<td><code>cos</code></td>
2288<td><code>double cos(double x);</code></td>
2289<td>计算余弦值(弧度制)</td>
2290<td><code>x</code>:弧度角</td>
2291<td>返回 ((x)),范围 ([-1, 1])</td>
2292</tr>
2293<tr>
2294<td><code>tan</code></td>
2295<td><code>double tan(double x);</code></td>
2296<td>计算正切值(弧度制)</td>
2297<td><code>x</code>:弧度角(避免奇数倍 ( /2 ))</td>
2298<td>返回 ((x));在奇数倍 ( /2 ) 附近可能溢出</td>
2299</tr>
2300<tr>
2301<td><code>asin</code></td>
2302<td><code>double asin(double x);</code></td>
2303<td>计算反正弦(弧度制)</td>
2304<td><code>x</code>:输入值(<code>-1 ≤ x ≤ 1</code>)</td>
2305<td>返回 ((x)),范围 ([-/2, /2]);若 <code>|x| &gt; 1</code>,返回
2306<code>NaN</code></td>
2307</tr>
2308<tr>
2309<td><code>acos</code></td>
2310<td><code>double acos(double x);</code></td>
2311<td>计算反余弦(弧度制)</td>
2312<td><code>x</code>:输入值(<code>-1 ≤ x ≤ 1</code>)</td>
2313<td>返回 ((x)),范围 ([0, ]);若 <code>|x| &gt; 1</code>,返回
2314<code>NaN</code></td>
2315</tr>
2316<tr>
2317<td><code>atan</code></td>
2318<td><code>double atan(double x);</code></td>
2319<td>计算反正切(弧度制)</td>
2320<td><code>x</code>:任意实数</td>
2321<td>返回 ((x)),范围 ([-/2, /2])</td>
2322</tr>
2323<tr>
2324<td><code>ceil</code></td>
2325<td><code>double ceil(double x);</code></td>
2326<td>向上取整(不小于 <code>x</code> 的最小整数)</td>
2327<td><code>x</code>:浮点数</td>
2328<td>返回不小于 <code>x</code> 的最小整数值(<code>double</code>
2329类型)</td>
2330</tr>
2331<tr>
2332<td><code>floor</code></td>
2333<td><code>double floor(double x);</code></td>
2334<td>向下取整(不大于 <code>x</code> 的最大整数)</td>
2335<td><code>x</code>:浮点数</td>
2336<td>返回不大于 <code>x</code> 的最大整数值(<code>double</code>
2337类型)</td>
2338</tr>
2339</tbody>
2340</table>
2341<blockquote>
2342<ul>
2343<li>对于 <code>float</code> 和 <code>long double</code> 类型,C99
2344起提供了对应的 <code>f</code>/<code>l</code> 后缀版本(如
2345<code>sinf</code>, <code>sqrtl</code> 等)。</li>
2346<li>输入非法值(如负数取对数、负数开平方等)通常会导致返回
2347<code>NaN</code> 。</li>
2348<li>三角函数的参数单位为<strong>弧度</strong>。</li>
2349</ul>
2350</blockquote>
2351<h4 id="伪随机数">4.3.2 伪随机数</h4>
2352<p>本节所介绍的函数均定义在 <code>stdlib.h</code> 头文件中。</p>
2353<p><strong><code>srand()</code> 函数:</strong></p>
2354<ul>
2355<li>原型为 <code>void srand( unsigned seed );</code></li>
2356<li>以值 <code>seed</code> 播种 <code>rand()</code>
2357所用的随机数生成器。</li>
2358</ul>
2359<p><strong><code>rand()</code> 函数:</strong></p>
2360<ul>
2361<li>原型为 <code>int rand();</code></li>
2362<li>返回 <code>0</code> 与 <code>RAND_MAX</code> 间的随机整数值(包含
2363<code>0</code> 与 <code>RAND_MAX</code> )。</li>
2364</ul>
2365<p><code>srand()</code> 播种 <code>rand()</code>
2366所用的伪随机数生成器。若在任何对 <code>srand()</code> 的调用前使用
2367<code>rand()</code> ,则 <code>rand()</code> 表现如同它以
2368<code>srand(1)</code> 播种。每次以 <code>srand()</code> 播种
2369<code>rand()</code> 时,它必须产生相同的值数列。</p>
2370<p>标准保证 <code>RAND_MAX</code> 至少为 <code>32767</code> 。</p>
2371<h3 id="排序">4.4 排序</h3>
2372<blockquote>
2373<p>快速排序的算法内容不在本讲义的内容范围中,具体可以参考 <a
2374target="_blank" rel="noopener" href="https://oi-wiki.org/basic/quick-sort/">快速排序 - OI Wiki</a>
2375。</p>
2376</blockquote>
2377<p>快速排序算法在 C 实现中的名称是 <code>qsort()</code>,定义在
2378<code>
2378stdlib.h</code> 头文件中。<code>qsort()</code>
2379函数排序数组的数据对象,其原型如下:</p>
2380<div class="sourceCode" id="cb68"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb68-1"><a href="#cb68-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> qsort<span class="op">(</span><span class="dt">void</span> <span class="op">*</span>base<span class="op">,</span> <span class="dt">size_t</span> nmemb<span class="op">,</span> <span class="dt">size_t</span> size<span class="op">,</span> <span class="dt">int</span> <span class="op">(*</span>compar<span class="op">)(</span><span class="dt">const</span> <span class="dt">void</span> <span class="op">*,</span> <span class="dt">const</span> <span class="dt">void</span> <span class="op">*));</span></span></code></pre></div>
2381<p>第 1 个参数是指针,指向待排序数组的首元素。ANSI C
2382允许把指向任何数据类型的指针强制转换成指向 <code>void</code>
2383的指针,因此,<code>qsort()</code> 的第 1
2384个实际参数可以引用任何类型的数组。</p>
2385<p>第 2 个参数是待排序项的数量。函数原型把该值转换为 <code>size_t</code>
2386类型。<code>size_t</code> 定义在标准头文件中,是 <code>sizeof</code>
2387运算符返回的整数类型。</p>
2388<p>由于 <code>qsort()</code> 把第1个参数转换为 <code>void</code>
2389指针,所以 <code>qsort()</code>
2390不知道数组中每个元素的大小。为此,函数原型用第 3
2391个参数补偿这一信息,显式指明待排序数组中每个元素的大小。例如,如果排序
2392<code>double</code> 类型的数组,那么第 3 个参数应该是
2393<code>sizeof(double)</code>。</p>
2394<p>最后,<code>qsort()</code>
2395还需要一个指向函数的指针,这个被指针指向的比较函数于确定排序的顺序。该函数应接受两个参数:分别指向待比较两项的指针。如果第
23961 项的值大于第 2 项,比较函数则返回正数;如果两项相同,则返回 0;如果第
23971 项的值小于第 2 项,则返回负数。<code>qsort()</code>
2398根据给定的其他信息计算出两个指针的值,然后把它们传递给比较函数。</p>
2399<p><code>qsort()</code> 原型中的第 4 个函数确定了比较函数的形式:</p>
2400<div class="sourceCode" id="cb69"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb69-1"><a href="#cb69-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">(*</span>compar<span class="op">)(</span><span class="dt">const</span> <span class="dt">void</span> <span class="op">*,</span> <span class="dt">const</span> <span class="dt">void</span> <span class="op">*)</span></span></code></pre></div>
2401<p>这表明 <code>qsort()</code>
2402最后一个参数是一个指向函数的指针,该函数返回 <code>int</code>
2403类型的值且接受两个指向 <code>const void</code>
2404的指针作为参数,这两个指针指向待比较项。</p>
2405<h3 id="断言">4.5 断言</h3>
2406<p><code>assert.h</code>
2407头文件支持的断言库是一个用于辅助调试程序的小型库。它由
2408<code>assert()</code>
2409宏组成,接受一个整型表达式作为参数。如果表达式求值为假(非零),<code>assert()</code>
2410宏就在标准错误流(<code>stderr</code>)中写入一条错误信息,并调用
2411<code>abort()</code> 函数终止程序(<code>abort()</code> 函数的原型在
2412<code>stdlib.h</code> 头文件中)。<code>assert()</code>
2413宏是为了标识出程序中某些条件为真的关键位置,如果其中的一个具体条件为假,就用
2414<code>assert()</code> 语句终止程序。通常,<code>assert()</code>
2415的参数是一个条件表达式或逻辑表达式。如果 <code>assert()</code>
2416中止了程序,它首先会显示失败的测试、包含测试的文件名和行号。</p>
2417<p>示例程序:</p>
2418<div class="sourceCode" id="cb70"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb70-1"><a href="#cb70-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;assert.h&gt;</span></span>
2419<span id="cb70-2"><a href="#cb70-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
2420<span id="cb70-3"><a href="#cb70-3" aria-hidden="true" tabindex="-1"></a></span>
2421<span id="cb70-4"><a href="#cb70-4" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
2422<span id="cb70-5"><a href="#cb70-5" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
2423<span id="cb70-6"><a href="#cb70-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> x <span class="op">=</span> <span class="dv">5</span><span class="op">;</span></span>
2424<span id="cb70-7"><a href="#cb70-7" aria-hidden="true" tabindex="-1"></a>  assert<span class="op">(</span>x <span class="op">&gt;</span> <span class="dv">0</span><span class="op">);</span> <span class="co">// 条件为真,程序继续运行</span></span>
2425<span id="cb70-8"><a href="#cb70-8" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;x is positive.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
2426<span id="cb70-9"><a href="#cb70-9" aria-hidden="true" tabindex="-1"></a></span>
2427<span id="cb70-10"><a href="#cb70-10" aria-hidden="true" tabindex="-1"></a>  x <span class="op">=</span> <span class="op">-</span><span class="dv">1</span><span class="op">;</span></span>
2428<span id="cb70-11"><a href="#cb70-11" aria-hidden="true" tabindex="-1"></a>  assert<span class="op">(</span>x <span class="op">&gt;</span> <span class="dv">0</span><span class="op">);</span> <span class="co">// 条件为假,程序终止并输出错误信息</span></span>
2429<span id="cb70-12"><a href="#cb70-12" aria-hidden="true" tabindex="-1"></a></span>
2430<span id="cb70-13"><a href="#cb70-13" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
2431<span id="cb70-14"><a href="#cb70-14" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
2432<p>示例输出:</p>
2433<pre><code>x is positive.
2434Assertion failed: x &gt; 0, file test.c, line 11</code></pre>
2435<p>使用 <code>assert()</code>
2436有几个好处:它不仅能自动标识文件和出问题的è
2436¡Œå·ï¼Œè¿˜æœ‰ä¸€ç§æ— éœ€æ›´æ”¹ä»£ç å°±èƒ½å¼€å¯æˆ–关闭
2437<code>assert()</code> 的机制。如果认为已经排除了程序的
2438bug,就可以把下面的宏定义写在包含 <code>assert.h</code> 的位置前面:</p>
2439<div class="sourceCode" id="cb72"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb72-1"><a href="#cb72-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#define NDEBUG</span></span></code></pre></div>
2440<p>并重新编译程序,这样编译器就会禁用文件中的所有 <code>assert()</code>
2441语句。如果程序又出现问题,可以移除这条 <code>#define</code>
2442指令(或者把它注释掉),然后重新编译程序,这样就重新启用了
2443<code>assert()</code> 语句。</p>
2444<h2 id="杂项">5 杂项</h2>
2445<h3 id="变量命名规则">5.1 变量命名规则</h3>
2446<p><strong>标识符</strong>是数字、下划线、小写及大写拉丁字母和以
2447<code>\u</code> 及 <code>\U</code> 转义记号指定的 Unicode 字符 (C99
2448起)的任意长度序列。合法的标识符必须以非数字字符(拉丁字母、下划线或
2449Unicode 非数字字符 (C99
2450起))开始。标识符大小写有别(小写和大写字母不同)。</p>
2451<div class="sourceCode" id="cb73"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb73-1"><a href="#cb73-1" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> <span class="op">*</span>\U0001f431 <span class="op">=</span> <span class="st">&quot;cat&quot;</span><span class="op">;</span> <span class="co">// 受支持</span></span>
2452<span id="cb73-2"><a href="#cb73-2" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> <span class="op">*</span>🐱 <span class="op">=</span> <span class="st">&quot;cat&quot;</span><span class="op">;</span> <span class="co">// 实现定义( Clang 可用,但版本 10 前的 GCC 不可)</span></span></code></pre></div>
2453<p>下列标识符被保留,而且不可在程序中声明(这么做会引起未定义行为):</p>
2454<ol type="1">
2455<li>作为关键词的标识符不能用于其他目的。具体而言,不允许
2456<code>#define</code> 或 <code>#undef</code> 等同于关键词的标识符。</li>
2457<li>所有以一个下划线开始的外部标识符。</li>
2458<li>所有以一个下划线后随一个大写字母或另一下划线开始的标识符(这些保留标识符允许库使用大量幕后的非外部宏及函数)。</li>
2459<li>标准库所定义的所有外部标识符(在有宿主环境中)。这表示不允许用户提供的外部名称匹配任何库名称,即使是声明等同于库函数的函数也不允许。</li>
2460<li>声明为标准库为未来使用保留的标识符。</li>
2461</ol>
2462<h3 id="运算符优先级">5.2 运算符优先级</h3>
2463<table>
2464<colgroup>
2465<col style="width: 8%" />
2466<col style="width: 46%" />
2467<col style="width: 34%" />
2468<col style="width: 10%" />
2469</colgroup>
2470<thead>
2471<tr>
2472<th>优先级</th>
2473<th>运算符</th>
2474<th>名称 / 说明</th>
2475<th>结合方向</th>
2476</tr>
2477</thead>
2478<tbody>
2479<tr>
2480<td>1</td>
2481<td><code>[] () . -&gt;</code></td>
2482<td>下标、函数调用、结构体成员</td>
2483<td>左 → 右</td>
2484</tr>
2485<tr>
2486<td>2</td>
2487<td><code>! ~ ++ -- + - * &amp; sizeof (type)</code></td>
2488<td>一元运算符</td>
2489<td>右 → 左</td>
2490</tr>
2491<tr>
2492<td>3</td>
2493<td><code>* / %</code></td>
2494<td>乘、除、取模</td>
2495<td>左 → 右</td>
2496</tr>
2497<tr>
2498<td>4</td>
2499<td><code>+ -</code></td>
2500<td>加、减</td>
2501<td>左 → 右</td>
2502</tr>
2503<tr>
2504<td>5</td>
2505<td><code>&lt;&lt; &gt;&gt;</code></td>
2506<td>位移</td>
2507<td>左 → 右</td>
2508</tr>
2509<tr>
2510<td>6</td>
2511<td><code>&lt; &lt;= &gt; &gt;=</code></td>
2512<td>关系运算</td>
2513<td>左 → 右</td>
2514</tr>
2515<tr>
2516<td>7</td>
2517<td><code>== !=</code></td>
2518<td>相等 / 不等</td>
2519<td>左 → 右</td>
2520</tr>
2521<tr>
2522<td>8</td>
2523<td><code>&amp;</code></td>
2524<td>按位与</td>
2525<td>左 → 右</td>
2526</tr>
2527<tr>
2528<td>9</td>
2529<td><code>^</code></td>
2530<td>按位异或</td>
2531<td>左 → 右</td>
2532</tr>
2533<tr>
2534<td>10</td>
2535<td><code>|</code></td>
2536<td>按位或</td>
2537<td>左 → 右</td>
2538</tr>
2539<tr>
2540<td>11</td>
2541<td><code>&amp;&amp;</code></td>
2542<td>逻辑与</td>
2543<td>左 → 右</td>
2544</tr>
2545<tr>
2546<td>12</td>
2547<td><code>||</code></td>
2548<td>逻辑或</td>
2549<td>左 → 右</td>
2550</tr>
2551<tr>
2552<td>13</td>
2553<td><code>?:</code></td>
2554<td>条件运算符</td>
2555<td>右 → 左</td>
2556</tr>
2557<tr>
2558<td>14</td>
2559<td><code>= += -= *= /= %= &lt;&lt;= &gt;&gt;= &amp;= ^= |=</code></td>
2560<td>赋值运算符</td>
2561<td>右 → 左</td>
2562</tr>
2563<tr>
2564<td>15</td>
2565<td><code>,</code></td>
2566<td>逗号运算符</td>
2567<td>左 → 右</td>
2568</tr>
2569</tbody>
2570</table>
2571<blockquote>
2572<ul>
2573<li>优先级最高:<code>[] () . -&gt;</code></li>
2574<li>一元运算符:统一右结合</li>
2575<li>赋值运算符:统一右结合</li>
2576<li>逗号运算符:优先级最低</li>
2577<li><code>sizeof</code> 是运算符,不是函数</li>
2578</ul>
2579</blockquote>
2580<h3 id="复杂类型声明推断">5.3 复杂类型声明推断</h3>
2581<p>基本原则(右左法则/螺旋法则):</p>
2582<ol type="1">
2583<li>从变量名开始。</li>
2584<li><strong>先向右看</strong>,直到遇到 <code>)</code> 或 <code>;</code>
2585或数组/函数符号。</li>
2586<li><strong>再向左看</strong>,处理指针 <code>*</code> 等。</li>
2587<li>遇到括号 <code>()</code> 或 <code>[]</code>
2588时,需注意优先级(括号可改变结合顺序)。</li>
2589<li>
2589重复上述过程,直到整个声明解析完毕。</li>
2590</ol>
2591<p>示例:</p>
2592<div class="sourceCode" id="cb74"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb74-1"><a href="#cb74-1" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> <span class="op">(*(*</span>x<span class="op">[</span><span class="dv">3</span><span class="op">])())[</span><span class="dv">5</span><span class="op">];</span></span></code></pre></div>
2593<p>从变量名 <code>x</code> 出发,逐层向外分析:</p>
2594<ol type="1">
2595<li><code>x[3]</code> → <code>x</code> 是一个包含 3 个元素的数组。<br />
2596</li>
2597<li><code>*x[3]</code> 结合为 <code>(*x[i])()</code> → 每个元素
2598<code>x[i]</code> 是一个指向函数的指针。<br />
2599</li>
2600<li><code>(*x[i])()</code> → 该函数无参数,调用后返回一个指针。<br />
2601</li>
2602<li><code>(*(...))</code> → 返回的是一个指针,指向
2603<code>char [5]</code>。<br />
2604</li>
2605<li><code>char (...) [5]</code> → 该指针类型为
2606<code>char (*)[5]</code>(指向长度为 5 的 <code>char</code>
2607数组)。</li>
2608</ol>
2609<p>最终含义:<code>x</code> 是一个包含 3
2610个元素的数组,每个元素是一个函数指针,该函数返回一个指向
2611<code>char[5]</code> 数组的指针。</p>
2612<p>等价 <code>typedef</code> 写法:</p>
2613<div class="sourceCode" id="cb75"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb75-1"><a href="#cb75-1" aria-hidden="true" tabindex="-1"></a><span class="kw">typedef</span> <span class="dt">char</span> <span class="op">(*</span>ArrayPtr<span class="op">)[</span><span class="dv">5</span><span class="op">];</span>   <span class="co">// 指向 char[5] 的指针</span></span>
2614<span id="cb75-2"><a href="#cb75-2" aria-hidden="true" tabindex="-1"></a><span class="kw">typedef</span> ArrayPtr <span class="op">(*</span>FuncPtr<span class="op">)();</span> <span class="co">// 返回 ArrayPtr 的函数指针</span></span>
2615<span id="cb75-3"><a href="#cb75-3" aria-hidden="true" tabindex="-1"></a>FuncPtr x<span class="op">[</span><span class="dv">3</span><span class="op">];</span>                  <span class="co">// x 是 FuncPtr 类型的数组</span></span></code></pre></div>
2616<h3 id="函数原型">5.4 函数原型</h3>
2617<p>函数原型是指在调用函数之前对函数的声明,其一般形式为:</p>
2618<pre><code>返回类型 函数名(参数类型列表);</code></pre>
2619<p>例如:</p>
2620<div class="sourceCode" id="cb77"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb77-1"><a href="#cb77-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> add<span class="op">(</span><span class="dt">int</span> a<span class="op">,</span> <span class="dt">int</span> b<span class="op">);</span></span></code></pre></div>
2621<p>概念:</p>
2622<p>函数原型告诉编译器某个函数的名称、返回类型以及参数的类型和数量,但不包含函数体(即具体实现)。</p>
2623<p>作用:</p>
2624<ol type="1">
2625<li>类型检查:使编译器能够在函数调用处检查参数个数和类型是否匹配,提高程序的安全性和可靠性。</li>
2626<li>支持函数在定义前被调用:C语言要求在使用函数前必须知道其存在,通过函数原型可以在函数定义之前调用它(例如在
2627<code>main</code> 函数中调用后面才定义的函数)。</li>
2628<li>提高代码可读性和模块化:通常将函数原型放在头文件(<code>.h</code>)中,便于其他源文件包含和使用,有利于大型项目的组织和维护。</li>
2629</ol>
2630<h3 id="尾递归">5.5 尾递归</h3>
2631<blockquote>
2632<p>本节内容不做重点介绍。关于<strong>递归</strong>,<strong>迭代递归</strong>和
2633<strong>CPS 递归</strong>的具体内容可以参考笔者的另一篇博客 <a
2634href="https://lonion1124.github.io/posts/52db781ce079/#recursion%E9%80%92%E5%BD%92%E7%9A%84%E8%BF%9B%E4%B8%80%E6%AD%A5%E6%8E%A2%E8%AE%A8">Recursion:递归的进一步探讨</a>。</p>
2635</blockquote>
2636<p>递归函数在运行时往往会占用比较多的资源(原因可以暂时理解为 CPU
2637调用函数时需要存储额外的数据),在可能的情况下应该尽量将递归过程转换为迭代过程。一般来说,<strong>尾递归</strong>函数可以比较简单地使用迭代(循环)过程等价实现。</p>
2638<p>示例程序:</p>
2639<div class="sourceCode" id="cb78"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb78-1"><a href="#cb78-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> factorial<span class="op">(</span><span class="dt">int</span> n<span class="op">)</span></span>
2640<span id="cb78-2"><a href="#cb78-2" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
2641<span id="cb78-3"><a href="#cb78-3" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="op">(</span>n <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span>
2642<span id="cb78-4"><a href="#cb78-4" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">1</span><span class="op">;</span></span>
2643<span id="cb78-5"><a href="#cb78-5" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> n <span class="op">*</span> factorial<span class="op">(</span>n <span class="op">-</span> <span class="dv">1</span><span class="op">);</span></span>
2644<span id="cb78-6"><a href="#cb78-6" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
2645<p>它和下面的程序等价:</p>
2646<div class="sourceCode" id="cb79"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb79-1"><a href="#cb79-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> fact_iter<span class="op">(</span><span class="dt">int</span> product<span class="op">,</span> <span class="dt">int</span> counter<span class="op">,</span> <span class="dt">int</span> max_count<span class="op">)</span></span>
2647<span id="cb79-2"><a href="#cb79-2" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
2648<span id="cb79-3"><a href="#cb79-3" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="op">(</span>counter <span class="op">&gt;</span> max_count<span class="op">)</span></span>
2649<span id="cb79-4"><a href="#cb79-4" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> product<span class="op">;</span></span>
2650<span id="cb79-5"><a href="#cb79-5" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> fact_iter<span class="op">(</span>counter <span class="op">*</span> product<span class="op">,</span> counter <span class="op">+</span> <span class="dv">1</span><span class="op">,</span> max_count<span class="op">);</span></span>
2651<span id="cb79-6"><a href="#cb79-6" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span>
2652<span id="cb79-7"><a href="#cb79-7" aria-hidden="true" tabindex="-1"></a></span>
2653<span id="cb79-8"><a href="#cb79-8" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> factorial<span class="op">(</span><span class="dt">int</span> n<span class="op">)</span> <span class="op">&#123;</span> <span class="cf">return</span> fact_iter<span class="op">(</span><span class="dv">1</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> n<span class="op">);</span> <span class="op">&#125;</span></span></code></pre></div>
2654<p>进而可以转换为循环实现:</p>
2655<div class="sourceCode" id="cb80"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb80-1"><a href="#cb80-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> factorial<span class="op">(</span><span class="dt">int</span> n<span class="op">)</span></span>
2656<span id="cb80-2"><a href="#cb80-2" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
2657<span id="cb80-3"><a href="#cb80-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> product <span class="op">=</span> <span class="dv">1</span><span class="op">;</span></span>
2658<span id="cb80-4"><a href="#cb80-4" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> counter <span class="op">=</span> <span class="dv">1</span><span class="op">;</span> counter <span class="op">&lt;=</span> n<span class="op">;</span> counter<span class="op">++)</span></span>
2659<span id="cb80-5"><a href="#cb80-5" aria-hidden="true" tabindex="-1"></a>    product <span class="op">*=</span> counter<span class="op">;</span></span>
2660<span id="cb80-6"><a href="#cb80-6" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> product<span class="op">;</span></span>
2661<span id="cb80-7"><a href="#cb80-7" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
2662<p>这就避免了递归调用函数产生的资源浪费。</p>
2663<h3 id="常见段错误原因">5.6 常见段错误原因</h3>
2664<ol type="1">
2665<li><p>访问空指针</p>
2666<div class="sourceCode" id="cb81"><pre
2667class="sourceCode c"><code class="sourceCode c"><span id="cb81-1"><a href="#cb81-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">*</span>p <span class="op">=</span> NULL<span class="op">;</span></span>
2668<span id="cb81-2"><a href="#cb81-2" aria-hidden="true" tabindex="-1"></a><span class="op">*</span>p <span class="op">=</span> <span class="dv">10</span><span class="op">;</span>  <span class="co">// 段错误</span></span></code></pre></div></li>
2669<li><p>使用未初始化的指针</p>
2670<div class="sourceCode" id="cb82"><pre
2671class="sourceCode c"><code class="sourceCode c"><span id="cb82-1"><a href="#cb82-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">*</span>p<span class="op">;</span></span>
2672<span id="cb82-2"><a href="#cb82-2" aria-hidden="true" tabindex="-1"></a><span class="op">*</span>p <span class="op">=</span> <span class="dv">5</span><span class="op">;</span>  <span class="co">// p 指向随机地址,写入会崩溃</span></span></code></pre></div></li>
2673<li><p>访问已释放的内存</p>
2674<div class="sourceCode" id="cb83"><pre
2675class="sourceCode c"><code class="sourceCode c"><span id="cb83-1"><a href="#cb83-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> <span class="op">*</span>p <span class="op">=</span> malloc<span class="op">(</span><span class="kw">sizeof</span><span class="op">(</span><span class="dt">int</span><span class="op">));</span></span>
2676<span id="cb83-2"><a href="#cb83-2" aria-hidden="true" tabindex="-1"></a>free<span class="op">(</span>p<span class="op">);</span></span>
2677<span id="cb83-3"><a href="#cb83-3" aria-hidden="true" tabindex="-1"></a><span class="op">*</span>p <span class="op">=</span> <span class="dv">10</span><span class="op">;</span>  <span class="co">// 危险!可能导致段错误或未定义行为</span></span></code></pre></div></li>
2678<li><p>数组越界访问</p>
2679<div class="sourceCode" id="cb84"><pre
2680class="sourceCode c"><code class="sourceCode c"><span id="cb84-1"><a href="#cb84-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> arr<span class="op">[</span><span class="dv">5</span><span class="op">];
2680</span></span>
2681<span id="cb84-2"><a href="#cb84-2" aria-hidden="true" tabindex="-1"></a>arr<span class="op">[</span><span class="dv">10</span><span class="op">]</span> <span class="op">=</span> <span class="dv">1</span><span class="op">;</span>  <span class="co">// 越界写入,可能破坏栈或触发保护机制</span></span></code></pre></div></li>
2682<li><p>栈溢出</p>
2683<ul>
2684<li>递归过深或局部变量过大(如大数组)<br />
2685</li>
2686</ul>
2687<div class="sourceCode" id="cb85"><pre
2688class="sourceCode c"><code class="sourceCode c"><span id="cb85-1"><a href="#cb85-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> f<span class="op">()</span> <span class="op">&#123;</span> f<span class="op">();</span> <span class="op">&#125;</span>  <span class="co">// 无限递归 → 栈溢出 → 可能段错误</span></span></code></pre></div></li>
2689<li><p>修改字符串字面量(常量区写入)</p>
2690<div class="sourceCode" id="cb86"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb86-1"><a href="#cb86-1" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> <span class="op">*</span>s <span class="op">=</span> <span class="st">&quot;hello&quot;</span><span class="op">;</span></span>
2691<span id="cb86-2"><a href="#cb86-2" aria-hidden="true" tabindex="-1"></a>s<span class="op">[</span><span class="dv">0</span><span class="op">]</span> <span class="op">=</span> <span class="ch">&#39;H&#39;</span><span class="op">;</span>  <span class="co">// 尝试修改只读内存 → 段错误</span></span></code></pre></div>
2692<p>正确做法:<code>char s[] = "hello";</code></p></li>
2693<li><p>函数指针错误或调用约定不匹配</p>
2694<ul>
2695<li>调用非法函数指针(如未赋值或类型错误)</li>
2696</ul></li>
2697</ol>
2698<h3 id="命令行参数处理">5.7 命令行参数处理</h3>
2699<p>假设有名为 <code>repeat.c</code> 的如下程序:</p>
2700<div class="sourceCode" id="cb87"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb87-1"><a href="#cb87-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
2701<span id="cb87-2"><a href="#cb87-2" aria-hidden="true" tabindex="-1"></a></span>
2702<span id="cb87-3"><a href="#cb87-3" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span><span class="op">*</span> argv<span class="op">[])</span></span>
2703<span id="cb87-4"><a href="#cb87-4" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
2704<span id="cb87-5"><a href="#cb87-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> count<span class="op">;</span></span>
2705<span id="cb87-6"><a href="#cb87-6" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;The command line has </span><span class="sc">%d</span><span class="st"> arguments:</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> argc <span class="op">-</span> <span class="dv">1</span><span class="op">);</span></span>
2706<span id="cb87-7"><a href="#cb87-7" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span>count <span class="op">=</span> <span class="dv">1</span><span class="op">;</span> count <span class="op">&lt;</span> argc<span class="op">;</span> count<span class="op">++)</span></span>
2707<span id="cb87-8"><a href="#cb87-8" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%d</span><span class="st">: </span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> count<span class="op">,</span> argv<span class="op">[</span>count<span class="op">]);</span></span>
2708<span id="cb87-9"><a href="#cb87-9" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
2709<span id="cb87-10"><a href="#cb87-10" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
2710<span id="cb87-11"><a href="#cb87-11" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
2711<p>把该程序编译为可执行文件 <code>repeat</code>。</p>
2712<p>通过命令行调用可执行文件:</p>
2713<div class="sourceCode" id="cb88"><pre
2714class="sourceCode sh"><code class="sourceCode bash"><span id="cb88-1"><a href="#cb88-1" aria-hidden="true" tabindex="-1"></a><span class="ex">repeat</span> Resistance is futile</span></code></pre></div>
2715<p>程序输出:</p>
2716<pre><code>The command line has 3 arguments:
27171: Resistance
27182: is
27193: futile</code></pre>
2720<p>C 编译器允许 <code>main()</code> 没有参数或者有两个参数(一些实现允许
2721<code>main()</code> 有更多参数,属于对标准的扩展)。<code>main()</code>
2722有两个参数时,第 1 个参数是命令行中的字符串数量。过去,这个
2723<code>int</code> 类型的参数被称为
2724<code>argc</code>(表示参数计数(argument
2725count))。系统用空格表示一个字符串的结束和下一个字符串的开始。因此,上面的
2726<code>repeat</code> 示例中包括命令名共有 4 个字符串,其中后 3 个供
2727<code>repeat</code>
2728使用。该程序把命令行字符串储存在内存中,并把每个字符串的地址储存在指针数组中。而该数组的地址则被储存在
2729<code>main()</code> 的第 2 个参数中。按照惯例,这个指向指针的指针称为
2730<code>argv</code>(表示参数值(argument
2731value))。如果系统允许(一些操作系统不允许这样),就把程序本身的名称赋给
2732<code>argv[0]</code>,然后把随后的第 1 个字符串赋给
2733<code>argv[1]</code>,以此类推。在我们的例子中,有下面的关系:</p>
2734<ul>
2735<li><code>argv[0]</code> 指向
2736<code>repeat</code>(对大部分系统而言)</li>
2737<li><code>argv[1]</code> 指向 <code>Resistance</code></li>
2738<li><code>argv[2]</code> 指向 <code>is</code></li>
2739<li><code>argv[3]</code> 指向 <code>futile</code></li>
2740</ul>
2741<p><code>main()</code>
2742中的形参形式与其他带形参的函数相同。可以等价地用以下形式声明
2743<code>argv</code>:</p>
2744<div class="sourceCode" id="cb90"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb90-1"><a href="#cb90-1" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span></span></code></pre></div>
2745<p>许多环境都允许用双引号把多个单词括起来形成一个参数。例如:</p>
2746<div class="sourceCode" id="cb91"><pre
2747class="sourceCode sh"><code class="sourceCode bash"><span id="cb91-1"><a href="#cb91-1" aria-hidden="true" tabindex="-1"></a><span class="ex">repeat</span> <span class="st">&quot;I am hungry&quot;</span> now</span></code></pre></div>
2748<p>这行命令把字符串 <code>"I am hungry"</code> 赋给
2749<code>argv[1]</code>,把 <code>"now"</code> 赋给
2750<code>argv[2]</code>。</p>
2751<h2 id="小众语法">6 小众语法</h2>
2752<h3 id="少用-switch-语句">6.1 少用 <code>switch</code> 语句</h3>
2753<ol type="1">
2754<li><p>基本结构:</p>
2755<div class="sourceCode" id="cb92"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb92-1"><a href="#cb92-1" aria-hidden="true" tabindex="-1"></a><span class="cf">switch</span> <span class="op">(</span>表达式<span class="op">)</span> <span class="op">&#123;</span></span>
2756<span id="cb92-2"><a href="#cb92-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">case</span> 常量<span class="dv">1</span><span class="op">:</span> 语句<span class="op">;</span> <span class="cf">break</span><span class="op">;</span></span>
2757<span id="cb92-3"><a href="#cb92-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">case</span> 常量<span class="dv">2</span><span class="op">:</span> 语句<span class="op">;</span> <span class="cf">break</span><span class="op">;</span></span>
2758<span id="cb92-4"><a href="#cb92-4" aria-hidden="true" tabindex="-1"></a>    <span class="cf">default</span><span class="op">:</span> 语句<span class="op">;</span></span>
2759<span id="cb92-5"><a href="#cb92-5" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
2760<ul>
2761<li>表达式必须是<strong>整型或字符型</strong>(char 本质是整数)。</li>
2762<li>case
2763标签必须是<strong>编译时常量</strong>(如字面量、<code>const</code>
2764变量在某些编译器下也不行,标准只允许整型常量表达式)。</li>
2765</ul></li>
2766<li><p>Fall-through(穿透):</p>
2767<ul>
2768<li>如果一个 <code>case</code> 块末尾没有
2769<code>break</code>
2769,程序会继续执行下一个 <code>case</code>
2770的代码,直到遇到 <code>break</code> 或 <code>switch</code> 结束。</li>
2771<li>示例中选项 3 没有 <code>break</code>,所以选 3 时也会执行
2772<code>case 4</code> 的内容。</li>
2773</ul></li>
2774<li><p>多个 case 共享代码:</p>
2775<ul>
2776<li>如 <code>'A'</code> 和 <code>'a'</code>
2777共享同一段处理逻辑,只需在最后一个写 <code>break</code>。</li>
2778</ul></li>
2779<li><p>default 分支:</p>
2780<ul>
2781<li>当所有 <code>case</code>
2782都不匹配时执行,可放在任意位置(但习惯放在最后)。</li>
2783</ul></li>
2784</ol>
2785<blockquote>
2786<p>注意:<code>switch</code> 不能用浮点数、字符串或变量作为 case
2787标签。</p>
2788</blockquote>
2789<p>C 语言的 <code>switch</code>
2790语句本质上是<strong>标签跳转</strong>而非多路分支。因此相较于其他语言(比如
2791Python),它的语法并不直观,适用的类型十分有限,而且几乎在所有情况下都可以被
2792<code>if else</code> 等价替代。尽管 <code>switch</code>
2793语句在性能上有少许优势,但大多数情况下仍应采用其他分支控制语句。</p>
2794<h3 id="别用-goto-语句">6.2 别用 <code>goto</code> 语句</h3>
2795<p><code>goto</code> 语句是 C
2796语言中的一种无条件跳转语句,其基本形式为:</p>
2797<div class="sourceCode" id="cb93"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb93-1"><a href="#cb93-1" aria-hidden="true" tabindex="-1"></a><span class="cf">goto</span> 标号<span class="op">;</span></span>
2798<span id="cb93-2"><a href="#cb93-2" aria-hidden="true" tabindex="-1"></a><span class="op">...</span></span>
2799<span id="cb93-3"><a href="#cb93-3" aria-hidden="true" tabindex="-1"></a>标号<span class="op">:</span> 语句<span class="op">;</span></span></code></pre></div>
2800<p>例如:</p>
2801<div class="sourceCode" id="cb94"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb94-1"><a href="#cb94-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
2802<span id="cb94-2"><a href="#cb94-2" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span> <span class="op">&#123;</span></span>
2803<span id="cb94-3"><a href="#cb94-3" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
2804<span id="cb94-4"><a href="#cb94-4" aria-hidden="true" tabindex="-1"></a>    loop<span class="op">:</span></span>
2805<span id="cb94-5"><a href="#cb94-5" aria-hidden="true" tabindex="-1"></a>        printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%d</span><span class="st"> &quot;</span><span class="op">,</span> i<span class="op">);</span></span>
2806<span id="cb94-6"><a href="#cb94-6" aria-hidden="true" tabindex="-1"></a>        i<span class="op">++;</span></span>
2807<span id="cb94-7"><a href="#cb94-7" aria-hidden="true" tabindex="-1"></a>        <span class="cf">if</span> <span class="op">(</span>i <span class="op">&lt;</span> <span class="dv">5</span><span class="op">)</span> <span class="cf">goto</span> loop<span class="op">;</span></span>
2808<span id="cb94-8"><a href="#cb94-8" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
2809<span id="cb94-9"><a href="#cb94-9" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
2810<p>这段代码会输出 <code>0 1 2 3 4</code>。</p>
2811<p><img src="/img/cdetails/goto.jpg" style="zoom:80%;" /></p>
2812<p><strong>为何应避免使用 <code>goto</code>?</strong></p>
2813<ol type="1">
2814<li><code>goto</code>
2815可以任意跳转到函数内的任何标签处,打破顺序、选择、循环等结构化编程的基本控制流,使代码逻辑混乱。</li>
2816<li>阅读代码时难以追踪执行路径(“意大利面条式代码”),增加理解和调试难度。</li>
2817<li>跳过变量初始化、资源分配或清理代码,可能导致未定义行为或内存泄漏。</li>
2818<li>几乎所有 <code>goto</code> 的用途(如跳出多层循环、错误处理)都可用
2819<code>break</code>、<code>continue</code>、函数封装、状态变量或异常机制(在支持的语言中)更安全、清晰地实现。</li>
2820</ol>
2821<h3 id="宏与宏编程">6.3 宏与宏编程</h3>
2822<h4 id="参数宏">6.3.1 参数宏</h4>
2823<p>在 <code>#define</code>
2824中使用参数可以创建外形和作用与函数类似的类函数宏。带有参数的宏看上去很像函数,因为这样的宏也使用圆括号。类函数宏定义的圆括号中可以有一个或多个参数,随后这些参数出现在替换体中,如下图所示。</p>
2825<p><img src="/img/cdetails/macro.png" style="zoom:100%;" /></p>
2826<p>下面是一个类函数宏的示例:</p>
2827<div class="sourceCode" id="cb95"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb95-1"><a href="#cb95-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
2828<span id="cb95-2"><a href="#cb95-2" aria-hidden="true" tabindex="-1"></a></span>
2829<span id="cb95-3"><a href="#cb95-3" aria-hidden="true" tabindex="-1"></a><span class="pp">#define SQUARE</span><span class="op">(</span><span class="pp">X</span><span class="op">)</span><span class="pp"> X</span><span class="op">*</span><span class="pp"> X</span></span>
2830<span id="cb95-4"><a href="#cb95-4" aria-hidden="true" tabindex="-1"></a><span class="pp">#define PR</span><span class="op">(</span><span class="pp">X</span><span class="op">)</span><span class="pp"> printf</span><span class="op">(</span><span class="st">&quot;The result is </span><span class="sc">%d</span><span class="st">.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span><span class="pp"> X</span><span class="op">)</span></span>
2831<span id="cb95-5"><a href="#cb95-5" aria-hidden="true" tabindex="-1"></a></span>
2832<span id="cb95-6"><a href="#cb95-6" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(</span><span class="dt">void</span><span class="op">)</span></span>
2833<span id="cb95-7"><a href="#cb95-7" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
2834<span id="cb95-8"><a href="#cb95-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> x <span class="op">=</span> <span class="dv">5</span><span class="op">;</span></span>
2835<span id="cb95-9"><a href="#cb95-9" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> z<span class="op">;</span></span>
2836<span id="cb95-10"><a href="#cb95-10" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;
2836x = </span><span class="sc">%d\n</span><span class="st">&quot;</span><span class="op">,</span> x<span class="op">);</span></span>
2837<span id="cb95-11"><a href="#cb95-11" aria-hidden="true" tabindex="-1"></a>  z <span class="op">=</span> SQUARE<span class="op">(</span>x<span class="op">);</span></span>
2838<span id="cb95-12"><a href="#cb95-12" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;Evaluating SQUARE(x): &quot;</span><span class="op">);</span></span>
2839<span id="cb95-13"><a href="#cb95-13" aria-hidden="true" tabindex="-1"></a>  PR<span class="op">(</span>z<span class="op">);</span></span>
2840<span id="cb95-14"><a href="#cb95-14" aria-hidden="true" tabindex="-1"></a>  z <span class="op">=</span> SQUARE<span class="op">(</span><span class="dv">2</span><span class="op">);</span></span>
2841<span id="cb95-15"><a href="#cb95-15" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;Evaluating SQUARE(2): &quot;</span><span class="op">);</span></span>
2842<span id="cb95-16"><a href="#cb95-16" aria-hidden="true" tabindex="-1"></a>  PR<span class="op">(</span>z<span class="op">);</span></span>
2843<span id="cb95-17"><a href="#cb95-17" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;Evaluating SQUARE(x+2): &quot;</span><span class="op">);</span></span>
2844<span id="cb95-18"><a href="#cb95-18" aria-hidden="true" tabindex="-1"></a>  PR<span class="op">(</span>SQUARE<span class="op">(</span>x <span class="op">+</span> <span class="dv">2</span><span class="op">));</span></span>
2845<span id="cb95-19"><a href="#cb95-19" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;Evaluating 100/SQUARE(2): &quot;</span><span class="op">);</span></span>
2846<span id="cb95-20"><a href="#cb95-20" aria-hidden="true" tabindex="-1"></a>  PR<span class="op">(</span><span class="dv">100</span> <span class="op">/</span> SQUARE<span class="op">(</span><span class="dv">2</span><span class="op">));</span></span>
2847<span id="cb95-21"><a href="#cb95-21" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;x is </span><span class="sc">%d</span><span class="st">.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> x<span class="op">);</span></span>
2848<span id="cb95-22"><a href="#cb95-22" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;Evaluating SQUARE(++x): &quot;</span><span class="op">);</span></span>
2849<span id="cb95-23"><a href="#cb95-23" aria-hidden="true" tabindex="-1"></a>  PR<span class="op">(</span>SQUARE<span class="op">(++</span>x<span class="op">));</span></span>
2850<span id="cb95-24"><a href="#cb95-24" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;After incrementing, x is </span><span class="sc">%x</span><span class="st">.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> x<span class="op">);</span></span>
2851<span id="cb95-25"><a href="#cb95-25" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
2852<span id="cb95-26"><a href="#cb95-26" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
2853<p>SQUARE宏的定义如下:</p>
2854<div class="sourceCode" id="cb96"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb96-1"><a href="#cb96-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#define SQUARE</span><span class="op">(</span><span class="pp">X</span><span class="op">)</span><span class="pp"> X</span><span class="op">*</span><span class="pp">X</span></span></code></pre></div>
2855<p>这里,<code>SQUARE</code> 是宏标识符,<code>SQUARE(X)</code> 中的
2856<code>X</code> 是宏参数,<code>X*X</code> 是替换列表。程序出现
2857<code>SQUARE(X)</code> 的地方都会被 <code>X*X</code>
2858替换。使用该宏时,既可以用 <code>X</code>,也可以用其他符号。宏定义中的
2859<code>X</code> 由宏调用中的符号代替。因此,<code>SQUARE(2)</code> 替换为
2860<code>2*2</code>,<code>X</code> 实际上起到参数的作用。</p>
2861<p>然而,宏参数与函数参数不完全相同。下面是程序的输出,注意有些内容可能与我们的预期不符:</p>
2862<pre><code>x = 5
2863Evaluating SQUARE(x): The result is 25.
2864Evaluating SQUARE(2): The result is 4.
2865Evaluating SQUARE(x+2): The result is 17.
2866Evaluating 100/SQUARE(2): The result is 100.
2867x is 5.
2868Evaluating SQUARE(++x): The result is 49.
2869After incrementing, x is 7.</code></pre>
2870<p>程序中设置 <code>x</code> 的值为 <code>5</code>,但
2871<code>SQUARE(x+2)</code> 输出的结果是
2872<code>17</code>。导致这样结果的原因是预处理器不做计算、不求值,只替换字符序列。预处理器把出现x的地方都替换成
2873<code>x+2</code>。因此,<code>x*x</code> 变成了
2874<code>x+2*x+2</code>。如果 <code>x</code> 为
2875<code>5</code>,那么该表达式的值为 <code>17</code>。</p>
2876<p>
2876该例演示了函数调用和宏调用的重要区别。函数调用在程序运行时把参数的值传递给函数。宏调用在编译之前把参数记号传递给程序。这两个不同的过程发生在不同时期。是否可以修改宏定义让
2877<code>SQUARE(x+2)</code> 得
2878<code>49</code>?当然可以,要多加几个圆括号:</p>
2879<div class="sourceCode" id="cb98"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb98-1"><a href="#cb98-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#define SQUARE</span><span class="op">(</span><span class="pp">x</span><span class="op">)</span><span class="pp"> </span><span class="op">(</span><span class="pp">x</span><span class="op">)*(</span><span class="pp">x</span><span class="op">)</span></span></code></pre></div>
2880<p>但是,这并未解决所有的问题。<code>100/SQUARE(2)</code> 被替换为
2881<code>100/2*2</code>,得到 <code>100</code>。把 <code>SQUARE(x)</code>
2882定义为下面的形式可以解决这种混乱:</p>
2883<div class="sourceCode" id="cb99"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb99-1"><a href="#cb99-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#define SQUARE</span><span class="op">(</span><span class="pp">x</span><span class="op">)</span><span class="pp"> </span><span class="op">(</span><span class="pp">x</span><span class="op">*</span><span class="pp">x</span><span class="op">)</span></span></code></pre></div>
2884<p>要处理前面的两种情况,要这样定义:</p>
2885<div class="sourceCode" id="cb100"><pre
2886class="sourceCode c"><code class="sourceCode c"><span id="cb100-1"><a href="#cb100-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#define SQUARE</span><span class="op">(</span><span class="pp">x</span><span class="op">)</span><span class="pp"> </span><span class="op">((</span><span class="pp">x</span><span class="op">)*(</span><span class="pp">x</span><span class="op">))</span></span></code></pre></div>
2887<p>因此,必要时要使用足够多的圆括号来确保运算和结合的正确顺序。</p>
2888<p>尽管如此,这样做还是无法避免程序中最后一种情况的问题。<code>SQUARE(++x)</code>
2889变成了 <code>++x*++x</code>,递增了两次
2890<code>x</code>。由于标准并未对这类运算规定顺序,所以有些编译器得
2891<code>7*6</code>。而有些编译器可能在乘法运算之前已经递增了
2892<code>x</code>,所以 <code>7*7</code> 得 <code>49</code>。在 C
2893标准中,对该表达式求值的这种情况称为未定义行为。无论哪种情况,<code>x</code>
2894的开始值都是 <code>5</code>,虽然从代码上看只递增了一次,但是
2895<code>x</code> 的最终值是 <code>7</code>。</p>
2896<p>解决这个问题最简单的方法是,避免用 <code>++x</code>
2897作为宏参数。一般而言,不要在宏中使用递增或递减运算符。但是,<code>++x</code>
2898可作为函数参数,因为编译器会对 <code>++x</code> 求值得 <code>6</code>
2899后,再把 <code>6</code> 传递给函数。</p>
2900<h4 id="和-运算符">6.3.2 <code>#</code> 和 <code>##</code> 运算符</h4>
2901<p><strong><code>#</code>(字符串化运算符):</strong></p>
2902<p>用于将宏参数转换为字符串字面量(即用双引号包围)。</p>
2903<p>示例:</p>
2904<div class="sourceCode" id="cb101"><pre
2905class="sourceCode c"><code class="sourceCode c"><span id="cb101-1"><a href="#cb101-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#define STR</span><span class="op">(</span><span class="pp">x</span><span class="op">)</span><span class="pp"> </span><span class="op">#</span><span class="pp">x</span></span>
2906<span id="cb101-2"><a href="#cb101-2" aria-hidden="true" tabindex="-1"></a>printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> STR<span class="op">(</span>hello<span class="op">));</span>  <span class="co">// 输出: hello(作为字符串)</span></span></code></pre></div>
2907<p><strong><code>##</code>(连接运算符 / token paste):</strong></p>
2908<p>用于将两个标记(tokens)连接成一个单一的标记。</p>
2909<p>示例:</p>
2910<div class="sourceCode" id="cb102"><pre
2911class="sourceCode c"><code class="sourceCode c"><span id="cb102-1"><a href="#cb102-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#define CONCAT</span><span class="op">(</span><span class="pp">a</span><span class="op">,</span><span class="pp"> b</span><span class="op">)</span><span class="pp"> a</span><span class="op">##</span><span class="pp">b</span></span>
2912<span id="cb102-2"><a href="#cb102-2" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> CONCAT<span class="op">(</span>var<span class="op">,</span> <span class="dv">123</span><span class="op">);</span>  <span class="co">
2912// 等价于 int var123;</span></span></code></pre></div>
2913<h4 id="变参数宏">6.3.3 变参数宏</h4>
2914<p>在 C99 及以后的标准中,可以使用变参数宏(variadic macros),通过
2915<code>...</code> 接收可变数量的参数,并用 <code>__VA_ARGS__</code>
2916在宏体中引用这些参数。</p>
2917<p>示例程序:</p>
2918<div class="sourceCode" id="cb103"><pre
2919class="sourceCode c"><code class="sourceCode c"><span id="cb103-1"><a href="#cb103-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
2920<span id="cb103-2"><a href="#cb103-2" aria-hidden="true" tabindex="-1"></a></span>
2921<span id="cb103-3"><a href="#cb103-3" aria-hidden="true" tabindex="-1"></a><span class="pp">#define LOG</span><span class="op">(</span><span class="pp">fmt</span><span class="op">,</span><span class="pp"> </span><span class="op">...)</span><span class="pp">                                                          </span><span class="op">\</span></span>
2922<span id="cb103-4"><a href="#cb103-4" aria-hidden="true" tabindex="-1"></a><span class="pp">  printf</span><span class="op">(</span><span class="st">&quot;[</span><span class="sc">%s</span><span class="st">:</span><span class="sc">%d</span><span class="st">] &quot;</span><span class="pp"> fmt </span><span class="st">&quot;</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span><span class="pp"> __FILE__</span><span class="op">,</span><span class="pp"> __LINE__</span><span class="op">,</span><span class="pp"> </span><span class="op">##</span><span class="pp">__VA_ARGS__</span><span class="op">)</span></span>
2923<span id="cb103-5"><a href="#cb103-5" aria-hidden="true" tabindex="-1"></a></span>
2924<span id="cb103-6"><a href="#cb103-6" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
2925<span id="cb103-7"><a href="#cb103-7" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
2926<span id="cb103-8"><a href="#cb103-8" aria-hidden="true" tabindex="-1"></a>  LOG<span class="op">(</span><span class="st">&quot;Hello, world!&quot;</span><span class="op">);</span></span>
2927<span id="cb103-9"><a href="#cb103-9" aria-hidden="true" tabindex="-1"></a>  LOG<span class="op">(</span><span class="st">&quot;Value: </span><span class="sc">%d</span><span class="st">, Name: </span><span class="sc">%s</span><span class="st">&quot;</span><span class="op">,</span> <span class="dv">42</span><span class="op">,</span> <span class="st">&quot;Alice&quot;</span><span class="op">);</span></span>
2928<span id="cb103-10"><a href="#cb103-10" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
2929<span id="cb103-11"><a href="#cb103-11" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
2930<blockquote>
2931<ul>
2932<li><code>...</code> 表示宏接受可变参数。</li>
2933<li><code>__VA_ARGS__</code> 代表传递给 <code>...</code>
2934的所有参数。</li>
2935<li><code>##__VA_ARGS__</code> 是 GCC
2936扩展,用于在没有额外参数时删除前面多余的逗号。</li>
2937<li><code>__FILE__</code> 和 <code>__LINE__</code>
2938是预定义宏,以字符串形式表示当前文件名和行号。</li>
2939</ul>
2940</blockquote>
2941<p>输出示例(假设文件名为 <code>example.c</code>):</p>
2942<pre><code>[example.c:8] Hello, world!
2943[example.c:9] Value: 42, Name: Alice</code></pre>
2944<h3 id="位字段">6.4 位字段</h3>
2945<p>C
2946语言区别于许多高级语言的特性之一是访问整数中单独位的能力。该特性通常是与硬件设备和操作系统交互的关键。位字段(bit
2947field)是一种特殊的结构体成员,它允许以位为单位来指定变量所占用的存储空间。</p>
2948<p><strong>基本语法:</strong></p>
2949<div class="sourceCode" id="cb105"><pre
2950class="sourceCode c"><code class="sourceCode c"><span id="cb105-1"><a href="#cb105-1" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> <span class="op">&#123;</span></span>
2951<span id="cb105-2"><a href="#cb105-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">unsigned</span> <span class="dt">int</span> flag1 <span class="op">:</span> <span class="dv">1</span><span class="op">;</span> <span class="co">// 占1位</span></span>
2952<span id="cb105-3"><a href="#cb105-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">unsigned</span> <span class="dt">int</span> flag2 <span class="op">:</span> <span class="dv">1</span><span class="op">;</span> <span class="co">// 占1位</span></span>
2953<span id="cb105-4"><a href="#cb105-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">unsigned</span> <span class="dt">int</span> value <span class="op">:</span> <span class="dv">6</span><span class="op">;</span> <span class="co">// 占6位</span></span>
2954<span id="cb105-5"><a href="#cb105-5" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span> my_bits<span class="op">;</span></span></code></pre></div>
2955<p>上述定义中,<code>my_bits</code> 是一个结构体变量,总共占用 8 位(即
29561 字节),其中: - <code>flag1</code> 和 <code>flag2</code> 各占 1
2957位,通常用于布尔标志; - <code>value</code> 占 6 位,可表示 0~63
2958的整数值。</p>
2959<p><strong>特点:</strong></p>
2960<ul>
2961<li>位字段必须是整型类型(如
2962<code>int</code>、<code>unsigned int</code>、<code>signed int</code>,有时也支持
2963<code>_Bool</code>);</li>
2964<li>位宽不能超过该类型的总位数;</li>
2965<li>位字段不能取地址(即不能对位字段使用 <code>&amp;</code>
2966运算符);</li>
2967<li>位字段的内存布局(如高位在前还是低位在前)与编译器和平台相关,不具有可移植性;</li>
2968<li>可用一个宽度为 0 的位字段(如
2969<code>:0</code>)强制对齐到下一个存储单元边界。</li>
2970</ul>
2971<p><strong>示例程序:</strong></p>
2972<div class="sourceCode" id="cb106"><pre
2973class="sourceCode c"><code class="sourceCode c"><span id="cb106-1"><a href="#cb106-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
2974<span id="cb106-2"><a href="#cb106-2" aria-hidden="true" tabindex="-1"></a></span>
2975<span id="cb106-3"><a href="#cb106-3" aria-hidden="true" tabindex="-1"></a><span class="kw">
2975struct</span> Status <span class="op">&#123;</span></span>
2976<span id="cb106-4"><a href="#cb106-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">unsigned</span> <span class="dt">int</span> is_ready <span class="op">:</span> <span class="dv">1</span><span class="op">;</span></span>
2977<span id="cb106-5"><a href="#cb106-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">unsigned</span> <span class="dt">int</span> is_valid <span class="op">:</span> <span class="dv">1</span><span class="op">;</span></span>
2978<span id="cb106-6"><a href="#cb106-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">unsigned</span> <span class="dt">int</span> error_code <span class="op">:</span> <span class="dv">4</span><span class="op">;</span></span>
2979<span id="cb106-7"><a href="#cb106-7" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;;</span></span>
2980<span id="cb106-8"><a href="#cb106-8" aria-hidden="true" tabindex="-1"></a></span>
2981<span id="cb106-9"><a href="#cb106-9" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">()</span></span>
2982<span id="cb106-10"><a href="#cb106-10" aria-hidden="true" tabindex="-1"></a><span class="op">&#123;</span></span>
2983<span id="cb106-11"><a href="#cb106-11" aria-hidden="true" tabindex="-1"></a>  <span class="kw">struct</span> Status s <span class="op">=</span> <span class="op">&#123;</span><span class="dv">1</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">12</span><span class="op">&#125;;</span></span>
2984<span id="cb106-12"><a href="#cb106-12" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;is_ready: </span><span class="sc">%u\n</span><span class="st">&quot;</span><span class="op">,</span> s<span class="op">.</span>is_ready<span class="op">);</span>     <span class="co">// 输出 1</span></span>
2985<span id="cb106-13"><a href="#cb106-13" aria-hidden="true" tabindex="-1"></a>  printf<span class="op">(</span><span class="st">&quot;error_code: </span><span class="sc">%u\n</span><span class="st">&quot;</span><span class="op">,</span> s<span class="op">.</span>error_code<span class="op">);</span> <span class="co">// 输出 12</span></span>
2986<span id="cb106-14"><a href="#cb106-14" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
2987<span id="cb106-15"><a href="#cb106-15" aria-hidden="true" tabindex="-1"></a><span class="op">&#125;</span></span></code></pre></div>
2988
2989    </div>
2990
2991    <!-- 前后文章 -->
2992    
2993    <div class="mt-3 py-2 rounded bg-light-subtle shadow-sm align-self-end">
2994      <div class="row">
2995        <div class="col-6 border-end border-2">
2996          
2997        </div>
2998        <div class="col-6 border-start border-2">
2999          
3000          <div class="fs-4 fw-light px-2 post-next">
3001            <a href="/posts/52db781ce079/" class="link-secondary link-underline-opacity-0">
3002              <div class="d-flex justify-content-between">
3003                <span></span>
3004                <span class="text-truncate">SICP 知识点拾遗</span>
3005                <i class="bi bi-chevron-double-right"></i>
3006              </div>
3007            </a>
3008          </div>
3009          
3010        </div>
3011      </div>
3012    </div>
3013    
3014  </div>
3015</div>
3016    </div>
3017
3018    <div class="d-none d-md-block">
3019      <div style="position:fixed; top: 50%; right: 1em;">
3020  <div class="mb-2">
3021    <button type="button" class="btn btn-outline-primary" id="theme-toggle-button" title="切换明暗主题">
3022      <i id="theme-toggle-icon" class="bi bi-sun"></i>
3023    </button>
3024  </div>
3025  <div class="mb-2">
3026    <button type="button" class="btn btn-outline-primary" id="waifu-toggle" title="切换看板娘">
3027      <i class="bi bi-heart"></i>
3028    </button>
3029  </div>
3030  <div class="mb-2">
3031    <button class="btn btn-outline-primary" id="totop" title="回到顶部">
3032      <i class="bi bi-chevron-bar-up"></i>
3033    </button>
3034  </div>
3035  
3036<script src="/js/totop.js"></script>
3036
3037
3038</div>
3039    </div>
3040
3041    <div class="d-none d-md-block">
3042      <div id="waifu">
3043  <div id="waifu-tips" class="border rounded text-center bg-light-subtle shadow lead opacity-75">
3044  </div>
3045  <div id="waifu-canvas">
3046    <canvas id="live2d" width="800" height="800"></canvas>
3047  </div>
3048</div>
3049    </div>
3050
3051    <div class="mt-auto">
3052      <footer class="row py-3 mt-4 border-top bg-light-subtle">
3053  <ul class="nav col-md-6 justify-content-center">
3054    <li class="nav-item"><a href="#" class="nav-link px-2 text-muted"><i class="bi bi-c-circle"></i>&nbsp;2024~2025 L_Onion. All rights reserved.</a></li>
3055  </ul>
3056
3057  <ul class="nav col-md-6 justify-content-center">
3058    <li class="nav-item"><a href="/" class="nav-link px-2 text-muted">主页</a></li>
3059    <li class="nav-item"><a href="" class="nav-link px-2 text-muted">GitHub</a></li>
3060    <li class="nav-item"><a target="_blank" rel="noopener" href="https://github.com/LOnion1124/hexo-onion" class="nav-link px-2 text-muted">Onion</a></li>
3061    <li class="nav-item"><a href="#" class="nav-link px-2 text-muted">FAQs</a></li>
3062    <li class="nav-item"><a href="/about" class="nav-link px-2 text-muted">关于</a></li>
3063  </ul>
3064</footer>
3065    </div>
3066  </div>
3067</body>
3068
3069</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.