<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>课程章节 on 自学C大纲：从内存到数据库</title><link>https://simgor001.github.io/c-learning-companion/chapters/index.html</link><description>Recent content in 课程章节 on 自学C大纲：从内存到数据库</description><generator>Hugo</generator><language>zh</language><atom:link href="https://simgor001.github.io/c-learning-companion/chapters/index.xml" rel="self" type="application/rss+xml"/><item><title>第1章 计算机怎么记住东西的</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch01.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch01.html</guid><description>&lt;h2 id="11--从一个小问题开始"&gt;1.1 从一个小问题开始&lt;a class="anchor" href="#11--%e4%bb%8e%e4%b8%80%e4%b8%aa%e5%b0%8f%e9%97%ae%e9%a2%98%e5%bc%80%e5%a7%8b"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设你想让计算机记住一个字母：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;人看到的是字母 &lt;code&gt;A&lt;/code&gt;。可是计算机里面没有一个小抽屉专门放“字母 A”。&lt;/p&gt;
&lt;p&gt;它真正存进去的是一个数字：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;65&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;问题来了：&lt;/p&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;为什么 &lt;code&gt;65&lt;/code&gt; 会变成 &lt;code&gt;A&lt;/code&gt;？&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;答案要分成两层看：内存里放的是 &lt;code&gt;65&lt;/code&gt; 这个数字；程序按 ASCII 规则读它时，屏幕上显示成 &lt;code&gt;A&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="12--内存像一排格子"&gt;1.2 内存像一排格子&lt;a class="anchor" href="#12--%e5%86%85%e5%ad%98%e5%83%8f%e4%b8%80%e6%8e%92%e6%a0%bc%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;内存可以先想成一排格子。每个格子有一个编号，格子里放一个小数字。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch01/ch01-01-memory-cells.svg" alt="内存是一排带编号的格子" /&gt;&lt;/p&gt;
&lt;p&gt;格子的编号叫 &lt;strong&gt;地址&lt;/strong&gt;。格子本身通常按 &lt;strong&gt;字节&lt;/strong&gt; 来看。&lt;/p&gt;
&lt;p&gt;一个字节有 8 个二进制位。每个位只能是 0 或 1，所以一个字节能表示：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;00000000 -&amp;gt; 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;00000001 -&amp;gt; 1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;01000001 -&amp;gt; 65
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;11111111 -&amp;gt; 255&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;也就是 0 到 255 之间的数字。&lt;/p&gt;
&lt;p&gt;这几个二进制例子先说明一件事：&lt;/p&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;内存格子里先放的是数字。&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="13--65-为什么是-a"&gt;1.3 &lt;code&gt;65&lt;/code&gt; 为什么是 &lt;code&gt;A&lt;/code&gt;&lt;a class="anchor" href="#13--65-%e4%b8%ba%e4%bb%80%e4%b9%88%e6%98%af-a"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;65&lt;/code&gt; 本身不是 &lt;code&gt;A&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;65&lt;/code&gt; 变成 &lt;code&gt;A&lt;/code&gt;，是因为有一张约定好的表。最常见的一张表叫 ASCII。&lt;/p&gt;</description></item><item><title>第2章 变量和类型</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch02.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch02.html</guid><description>&lt;h2 id="21--上一章留下的问题"&gt;2.1 上一章留下的问题&lt;a class="anchor" href="#21--%e4%b8%8a%e4%b8%80%e7%ab%a0%e7%95%99%e4%b8%8b%e7%9a%84%e9%97%ae%e9%a2%98"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章说过，内存格子里存的是数字。&lt;/p&gt;
&lt;p&gt;比如一个格子里放着：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;65&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;按整数看，它就是数字 &lt;code&gt;65&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;按 ASCII 字符看，它又是字母 &lt;code&gt;A&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;那程序怎么知道该用哪种读法？&lt;/p&gt;
&lt;p&gt;这就是变量和类型要解决的问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="22--变量给内存格子起名字"&gt;2.2 变量：给内存格子起名字&lt;a class="anchor" href="#22--%e5%8f%98%e9%87%8f%e7%bb%99%e5%86%85%e5%ad%98%e6%a0%bc%e5%ad%90%e8%b5%b7%e5%90%8d%e5%ad%97"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果每次都说“去地址 &lt;code&gt;0x1000&lt;/code&gt; 那个格子取数据”，写程序会很麻烦。&lt;/p&gt;
&lt;p&gt;所以 C 语言允许给一块内存起名字：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; x &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;65&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 声明一个 int 变量，初始化为 65
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;可以先这样看：&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch02/ch02-01-variable-name-memory.svg" alt="变量名指向一块内存位置" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;x&lt;/code&gt; 是名字。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;65&lt;/code&gt; 是放进去的数据。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;int&lt;/code&gt; 告诉程序：这块内存里的数据，先按整数来解释。&lt;/p&gt;
&lt;p&gt;变量不是凭空存在的。它背后还是内存，只是程序员不用每次都直接写地址。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="23--类型告诉程序怎么解释这块内存"&gt;2.3 类型：告诉程序怎么解释这块内存&lt;a class="anchor" href="#23--%e7%b1%bb%e5%9e%8b%e5%91%8a%e8%af%89%e7%a8%8b%e5%ba%8f%e6%80%8e%e4%b9%88%e8%a7%a3%e9%87%8a%e8%bf%99%e5%9d%97%e5%86%85%e5%ad%98"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;看三行很像的代码：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; x &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;65&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// int：把 65 当整数看
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; c &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;65&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// char：把 65 当字符看（ASCII 65 = &amp;#39;A&amp;#39;）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;float&lt;/span&gt; f &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;65&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// float：把 65 转成浮点数 65.0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;它们都和数字 &lt;code&gt;65&lt;/code&gt; 有关，但类型不一样。&lt;/p&gt;
&lt;p&gt;先看 &lt;code&gt;int&lt;/code&gt; 和 &lt;code&gt;char&lt;/code&gt;。它们都写了 &lt;code&gt;65&lt;/code&gt;，但程序取值时采用的读法不同。&lt;/p&gt;</description></item><item><title>第3章 判断和循环</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch03.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch03.html</guid><description>&lt;h2 id="31--问题从哪来"&gt;3.1 问题从哪来&lt;a class="anchor" href="#31--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章知道了变量和类型：&lt;code&gt;int score = 86;&lt;/code&gt; 把 86 放进一个叫 &lt;code&gt;score&lt;/code&gt; 的整数变量里。&lt;/p&gt;
&lt;p&gt;但有一个东西程序还不会做：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果 &lt;code&gt;score &amp;gt;= 60&lt;/code&gt;，打印&amp;quot;及格&amp;quot;。&lt;/li&gt;
&lt;li&gt;否则，打印&amp;quot;不及格&amp;quot;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种&amp;quot;根据条件走不同路&amp;quot;的能力，C 语言里用 &lt;code&gt;if/else&lt;/code&gt; 来表达。&lt;/p&gt;
&lt;p&gt;另一个问题：如果有很多个分数要处理，总不能复制粘贴一百遍相同的代码。程序需要一种&amp;quot;回头再做一遍&amp;quot;的能力，这就是循环。&lt;/p&gt;
&lt;p&gt;先看分支，再看循环。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="32--先看一个例子"&gt;3.2 先看一个例子&lt;a class="anchor" href="#32--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设手头有这些分数：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;72 55 88 43 91 60 -1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;-1&lt;/code&gt; 不是真正的分数，它是一个标记，意思是&amp;quot;输入结束了&amp;quot;。&lt;/p&gt;
&lt;p&gt;这个程序的处理步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读一个分数。&lt;/li&gt;
&lt;li&gt;如果是 &lt;code&gt;-1&lt;/code&gt;，停止。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;&amp;gt;= 60&lt;/code&gt;，算及格。&lt;/li&gt;
&lt;li&gt;否则算不及格。&lt;/li&gt;
&lt;li&gt;回到第 1 步，读下一个。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后打印及格人数、不及格人数和平均分。&lt;/p&gt;
&lt;p&gt;下面就把这个程序跑起来。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="33--最小实验"&gt;3.3 最小实验&lt;a class="anchor" href="#33--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先跑一个小实验，再一段一段拆。&lt;/p&gt;
&lt;p&gt;这段程序里会同时看到几个新东西：&lt;code&gt;scanf&lt;/code&gt; 负责读输入，&lt;code&gt;while&lt;/code&gt; 负责反复执行，&lt;code&gt;if&lt;/code&gt; 负责根据条件选择路径。先观察它能做什么，后面再把每一块拆开看。&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;main&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score; &lt;span style="color:#78787e"&gt;// 当前读到的分数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; pass_count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 及格人数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; fail_count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 不及格人数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; sum &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 分数总和
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Enter scores (enter -1 to finish):&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;while&lt;/span&gt; (&lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 无限循环，靠 break 跳出
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;scanf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d&amp;#34;&lt;/span&gt;, &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;score) &lt;span style="color:#ff6ac1"&gt;!=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 确认读到了一个整数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Invalid input, please enter an integer.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;-&lt;/span&gt;&lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 遇到 -1，结束
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;60&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 及格
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pass_count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; pass_count &lt;span style="color:#ff6ac1"&gt;+&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; { &lt;span style="color:#78787e"&gt;// 不及格
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fail_count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; fail_count &lt;span style="color:#ff6ac1"&gt;+&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sum &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; sum &lt;span style="color:#ff6ac1"&gt;+&lt;/span&gt; score; &lt;span style="color:#78787e"&gt;// 累加总分
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; total &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; pass_count &lt;span style="color:#ff6ac1"&gt;+&lt;/span&gt; fail_count; &lt;span style="color:#78787e"&gt;// 总人数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Pass: %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, pass_count);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Fail: %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, fail_count);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (total &lt;span style="color:#ff6ac1"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 防止除以零
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Average: %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, sum &lt;span style="color:#ff6ac1"&gt;/&lt;/span&gt; total);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="34--编译运行"&gt;3.4 编译运行&lt;a class="anchor" href="#34--%e7%bc%96%e8%af%91%e8%bf%90%e8%a1%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;保存成 &lt;code&gt;grade.c&lt;/code&gt;，编译：&lt;/p&gt;</description></item><item><title>第4章 函数</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch04.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch04.html</guid><description>&lt;h2 id="41--问题从哪来"&gt;4.1 问题从哪来&lt;a class="anchor" href="#41--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章写了一个分数统计程序，循环读入分数，用 &lt;code&gt;if/else if/else&lt;/code&gt; 判断等级，然后计数。程序能跑，但有一个烦人的地方：&lt;/p&gt;
&lt;p&gt;如果想在多个地方都做&amp;quot;分数变等级&amp;quot;这件事——比如先统计优秀/及格/不及格的人数，再把每个分数的等级打印出来——&lt;code&gt;if/else if/else&lt;/code&gt; 那段判断逻辑就得写两遍。&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// 第一处：统计人数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;90&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 分数大于等于90
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; excellent_count&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 优秀人数加1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;60&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 分数在60到89之间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pass_count&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 及格人数加1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; { &lt;span style="color:#78787e"&gt;// 分数小于60
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fail_count&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 不及格人数加1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// 第二处：打印等级
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;90&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 分数大于等于90
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Excellent&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;); &lt;span style="color:#78787e"&gt;// 打印优秀
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;60&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 分数在60到89之间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Pass&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;); &lt;span style="color:#78787e"&gt;// 打印及格
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; { &lt;span style="color:#78787e"&gt;// 分数小于60
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Fail&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;); &lt;span style="color:#78787e"&gt;// 打印不及格
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;两段逻辑几乎一样，只是&amp;quot;命中之后做的事&amp;quot;不同。如果及格线从 60 改成 65，两处都得改。漏改一处，程序就出现前后不一致的 bug。&lt;/p&gt;
&lt;p&gt;复制粘贴很容易埋下不一致的 bug。代码一多，改了一处忘了另一处，问题就藏在这种地方。&lt;/p&gt;
&lt;p&gt;C 语言的解决办法是&lt;strong&gt;函数&lt;/strong&gt;：把一段逻辑写一次，装进一个有名字的&amp;quot;小盒子&amp;quot;里。需要的时候叫一声它的名字，它就替你把活干了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="42--先看一个例子"&gt;4.2 先看一个例子&lt;a class="anchor" href="#42--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;还是用分数变等级的场景。这次把判断等级的逻辑单独拿出来，放进一个叫 &lt;code&gt;grade_of&lt;/code&gt; 的小盒子里：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// 定义一个函数：接收一个 int 分数，返回一个 char 等级
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;grade_of&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;90&lt;/span&gt;) &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;80&lt;/span&gt;) &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;60&lt;/span&gt;) &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;F&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;main&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Enter scores (enter -1 to finish):&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;while&lt;/span&gt; (&lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;scanf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d&amp;#34;&lt;/span&gt;, &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;score);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (score &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;-&lt;/span&gt;&lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) &lt;span style="color:#ff6ac1"&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; grade &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;grade_of&lt;/span&gt;(score); &lt;span style="color:#78787e"&gt;// 调用函数，拿到等级
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d -&amp;gt; %c&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, score, grade);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;主程序里没有 &lt;code&gt;if/else&lt;/code&gt; 了。判断等级这件事，全部交给 &lt;code&gt;grade_of&lt;/code&gt; 去做。主程序只管读分数、调函数、打印结果。&lt;/p&gt;</description></item><item><title>第5章 数组</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch05.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch05.html</guid><description>&lt;h2 id="51--问题从哪来"&gt;5.1 问题从哪来&lt;a class="anchor" href="#51--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章写了函数，能把&amp;quot;判断等级&amp;quot;这类逻辑打包复用。函数解决的是&amp;quot;一段逻辑反复使用&amp;quot;的问题。&lt;/p&gt;
&lt;p&gt;还有一个问题没有解决：循环里读到的分数，处理完就丢了。以后想再排序、找中位数、重新打印成绩单，就没有地方把这些分数取回来。&lt;/p&gt;
&lt;p&gt;所以这一章先处理一个更基础的问题：&lt;/p&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;30 个学生的成绩，怎么存？&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;一种笨办法：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score1, score2, score3, score4, score5;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// ...一直写到 score30
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;30 个变量，光是声明就占半个屏幕。求平均分要写 &lt;code&gt;score1 + score2 + ... + score30&lt;/code&gt;，改起来更痛苦。&lt;/p&gt;
&lt;p&gt;另一种笨办法：每读一个分数，直接累加，不存下来。&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; sum &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 累加器初始化为 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;30&lt;/span&gt;; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 循环 30 次
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score; &lt;span style="color:#78787e"&gt;// 临时存放每次读入的分数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;scanf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d&amp;#34;&lt;/span&gt;, &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;score); &lt;span style="color:#78787e"&gt;// 读入一个整数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sum &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; sum &lt;span style="color:#ff6ac1"&gt;+&lt;/span&gt; score; &lt;span style="color:#78787e"&gt;// 累加到总分
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;平均分算得出来，但回头找最高分就没办法了——分数已经读过去，没有留下来。&lt;/p&gt;
&lt;p&gt;程序需要一个地方，把一批同类型的数据整整齐齐地放在一起，随时可以回头查看。这就是数组。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="52--先看一个例子"&gt;5.2 先看一个例子&lt;a class="anchor" href="#52--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设输入 5 个分数：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;72 55 88 43 91&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;程序要做的事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把 5 个分数存进数组。&lt;/li&gt;
&lt;li&gt;遍历数组，找到最高分、最低分。&lt;/li&gt;
&lt;li&gt;算出平均分。&lt;/li&gt;
&lt;li&gt;打印结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="53--最小实验"&gt;5.3 最小实验&lt;a class="anchor" href="#53--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先跑一个小实验，然后一段一段拆。&lt;/p&gt;</description></item><item><title>第6章 字符串</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch06.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch06.html</guid><description>&lt;h2 id="61--问题从哪来"&gt;6.1 问题从哪来&lt;a class="anchor" href="#61--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章学了数组，能用 &lt;code&gt;int scores[5]&lt;/code&gt; 存一组分数。&lt;/p&gt;
&lt;p&gt;但学生的数据不只是分数。每个学生还有名字：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Alice 85
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Bob 92
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Charlie 78&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;名字不是单个字符，而是一串字符连在一起。&lt;code&gt;char c = 'A'&lt;/code&gt; 只能存一个字母，存不下 &lt;code&gt;Alice&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;用数组的思路想：&lt;code&gt;Alice&lt;/code&gt; 是 5 个字符，排成一排。&lt;code&gt;char name[5]&lt;/code&gt; 似乎刚好够用。但事情没那么简单——程序需要知道这串字符在哪里结束。如果数组里有多余的空间，程序怎么区分&amp;quot;有效字符&amp;quot;和&amp;quot;空白垃圾&amp;quot;？&lt;/p&gt;
&lt;p&gt;这就是字符串要解决的核心问题。&lt;/p&gt;
&lt;p&gt;怎么在 C 语言里表示&amp;quot;一串字符&amp;quot;，并且让程序知道它在哪里结束？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="62--先看一个例子"&gt;6.2 先看一个例子&lt;a class="anchor" href="#62--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设要做这件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入一个名字。&lt;/li&gt;
&lt;li&gt;和预设的名字比较。&lt;/li&gt;
&lt;li&gt;如果一样，打印&amp;quot;Found&amp;quot;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;程序的输入输出大概长这样：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-console"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;Enter name:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ Alice
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;Found: Alice
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="course-codeblock course-codeblock-console"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;Enter name:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ Mallory
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;Not found
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这就是本章要写的程序。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="63--最小实验"&gt;6.3 最小实验&lt;a class="anchor" href="#63--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;string.h&amp;gt; // strcmp 在这个头文件里&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;main&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; name[&lt;span style="color:#ff9f43"&gt;32&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 用 char 数组存名字，最多 31 个字符
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Enter name: &amp;#34;&lt;/span&gt;); &lt;span style="color:#78787e"&gt;// 提示用户输入
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;scanf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%31s&amp;#34;&lt;/span&gt;, name); &lt;span style="color:#78787e"&gt;// 读一个单词，限制最多 31 个字符
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;strcmp&lt;/span&gt;(name, &lt;span style="color:#5af78e"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;) &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// strcmp 返回 0 表示两个字符串相同
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Found: %s&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, name); &lt;span style="color:#78787e"&gt;// 打印找到的名字
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Not found&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;); &lt;span style="color:#78787e"&gt;// 名字不匹配
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这段代码做了三件事：&lt;/p&gt;</description></item><item><title>第7章 结构体</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch07.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch07.html</guid><description>&lt;h2 id="71--问题从哪来"&gt;7.1 问题从哪来&lt;a class="anchor" href="#71--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章已经会用字符数组保存姓名，比如一批学生的名字可以这样放：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; names[&lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;][&lt;span style="color:#ff9f43"&gt;32&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 100 个名字，每个最多 31 字符 + 1 个 \0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;再加上前面学过的学号和分数，如果要记录一批学生的完整信息，就容易写成三个数组：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; ids[&lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 所有学生的学号 —— 三个数组靠下标关联
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; names[&lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;][&lt;span style="color:#ff9f43"&gt;32&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 所有学生的姓名
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; scores[&lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 所有学生的分数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;三个数组靠下标关联：&lt;code&gt;ids[0]&lt;/code&gt;、&lt;code&gt;names[0]&lt;/code&gt;、&lt;code&gt;scores[0]&lt;/code&gt; 都属于第 0 个学生。&lt;/p&gt;
&lt;p&gt;这样能跑，但有一个麻烦：没有任何语法机制保证这三个数组的下标始终同步。如果你不小心往 &lt;code&gt;scores[3]&lt;/code&gt; 写了值，却忘了更新 &lt;code&gt;ids[3]&lt;/code&gt;，这个学生的信息就对不上了。&lt;/p&gt;
&lt;p&gt;有没有办法把一个学生的学号、姓名、分数放在&lt;strong&gt;一个变量&lt;/strong&gt;里？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="72--先看一个例子"&gt;7.2 先看一个例子&lt;a class="anchor" href="#72--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设要记录三个学生：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;学号&lt;/th&gt;
					&lt;th&gt;姓名&lt;/th&gt;
					&lt;th&gt;分数&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;Alice&lt;/td&gt;
					&lt;td&gt;92&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;Bob&lt;/td&gt;
					&lt;td&gt;78&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;Carol&lt;/td&gt;
					&lt;td&gt;85&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;每个学生有三种数据：一个整数（学号）、一串字符（姓名）、一个整数（分数）。&lt;/p&gt;
&lt;p&gt;如果能把这三种数据绑成一个&amp;quot;包裹&amp;quot;，每个学生用一个包裹来表示，就不会对不上了。C 语言里做这件事的工具叫 &lt;strong&gt;结构体&lt;/strong&gt;（&lt;code&gt;struct&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;这里的姓名仍然沿用上一章的写法，用 &lt;code&gt;char name[32]&lt;/code&gt; 保存一个英文名。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="73--最小实验"&gt;7.3 最小实验&lt;a class="anchor" href="#73--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student { &lt;span style="color:#78787e"&gt;// 定义结构体类型
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id; &lt;span style="color:#78787e"&gt;// 学号
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; name[&lt;span style="color:#ff9f43"&gt;32&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 姓名，最多存 31 个字节的内容
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score; &lt;span style="color:#78787e"&gt;// 分数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;main&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student s; &lt;span style="color:#78787e"&gt;// 声明一个结构体变量
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; s.id &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 给学号赋值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;snprintf&lt;/span&gt;(s.name, &lt;span style="color:#ff6ac1"&gt;sizeof&lt;/span&gt;(s.name), &lt;span style="color:#5af78e"&gt;&amp;#34;%s&amp;#34;&lt;/span&gt;, &lt;span style="color:#5af78e"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;); &lt;span style="color:#78787e"&gt;// 给姓名赋值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; s.score &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;92&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 给分数赋值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;ID: %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, s.id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Name: %s&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, s.name);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Score: %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, s.score);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;程序很短，但做了三件事：&lt;/p&gt;</description></item><item><title>第8章 结构体数组和表</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch08.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch08.html</guid><description>&lt;h2 id="81--问题从哪来"&gt;8.1 问题从哪来&lt;a class="anchor" href="#81--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章用 &lt;code&gt;struct Student&lt;/code&gt; 把一个学生的学号、姓名、分数绑在了一起。一条记录已经清楚了，但班里不会只有一个学生。&lt;/p&gt;
&lt;p&gt;上一章的程序只做了一件事：把数据放进去，再用循环打出来。如果要做的不只是打印，而是往表里添加新学生、按学号查找某个学生、打印所有记录，就需要一套更有组织的写法。&lt;/p&gt;
&lt;p&gt;一个班有几十个学生，程序要支持这些操作：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;操作&lt;/th&gt;
					&lt;th&gt;做什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;添加&lt;/td&gt;
					&lt;td&gt;输入学号、姓名、分数，存进表里&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;查找&lt;/td&gt;
					&lt;td&gt;给一个学号，找到对应的学生并打印&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;打印&lt;/td&gt;
					&lt;td&gt;把表里所有学生逐行输出&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这三个操作，用结构体数组加一个 &lt;code&gt;count&lt;/code&gt; 变量就能实现。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="82--先看一个例子"&gt;8.2 先看一个例子&lt;a class="anchor" href="#82--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设表一开始是空的，&lt;code&gt;count = 0&lt;/code&gt;。用户连续添加三名学生：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学号 1，Alice，92 分&lt;/li&gt;
&lt;li&gt;学号 2，Bob，78 分&lt;/li&gt;
&lt;li&gt;学号 3，Carol，85 分&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每添加一条，程序都把新记录写到 &lt;code&gt;students[count]&lt;/code&gt;，再把 &lt;code&gt;count&lt;/code&gt; 加一。三次添加后，&lt;code&gt;students[0]&lt;/code&gt; 到 &lt;code&gt;students[2]&lt;/code&gt; 是有效记录，&lt;code&gt;count = 3&lt;/code&gt;，下一条会写到 &lt;code&gt;students[3]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch08/ch08-01-operation-trace.svg" alt="添加推进 count，查找和打印只读表" /&gt;&lt;/p&gt;
&lt;p&gt;接着按学号 2 查找，程序从 &lt;code&gt;students[0]&lt;/code&gt; 开始比对，读到 &lt;code&gt;students[1]&lt;/code&gt; 时找到 Bob。打印全部记录时，也只打印 &lt;code&gt;students[0]&lt;/code&gt; 到 &lt;code&gt;students[2]&lt;/code&gt;。这两个操作只读已有记录，不会改变表里的内容，也不会改变 &lt;code&gt;count&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="83--最小实验"&gt;8.3 最小实验&lt;a class="anchor" href="#83--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这段代码带了一个简单菜单。读的时候先看三件事：&lt;code&gt;students[count]&lt;/code&gt; 写入新记录，&lt;code&gt;for&lt;/code&gt; 循环只扫 &lt;code&gt;count&lt;/code&gt; 条，打印时也只打印已经使用的记录。&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; name[&lt;span style="color:#ff9f43"&gt;32&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; ch;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;while&lt;/span&gt; ((ch &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;getchar&lt;/span&gt;()) &lt;span style="color:#ff6ac1"&gt;!=&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ch &lt;span style="color:#ff6ac1"&gt;!=&lt;/span&gt; EOF) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 丢掉本行剩余字符
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;main&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student students[&lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 最多 100 条记录
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 当前有 0 条
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; choice;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;while&lt;/span&gt; (&lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;1-Add 2-Search 3-Print 0-Exit&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Choice: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;scanf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d&amp;#34;&lt;/span&gt;, &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;choice) &lt;span style="color:#ff6ac1"&gt;!=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Please enter a number.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (choice &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (choice &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 添加一条记录
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (count &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Table full, cannot add.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;ID: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;scanf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d&amp;#34;&lt;/span&gt;, &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;students[count].id) &lt;span style="color:#ff6ac1"&gt;!=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;ID must be an integer.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Name: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;scanf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%31s&amp;#34;&lt;/span&gt;, students[count].name) &lt;span style="color:#ff6ac1"&gt;!=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Failed to read name.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Score: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;scanf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d&amp;#34;&lt;/span&gt;, &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;students[count].score) &lt;span style="color:#ff6ac1"&gt;!=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Score must be an integer.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; count &lt;span style="color:#ff6ac1"&gt;+&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 记录数加 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Added successfully, total records: %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, count);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (choice &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;2&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 按学号查找
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; target;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Enter ID to search: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;scanf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d&amp;#34;&lt;/span&gt;, &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;target) &lt;span style="color:#ff6ac1"&gt;!=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;ID must be an integer.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;clear_line&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; found &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 标记是否找到
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; count; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (students[i].id &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; target) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Found: ID %d, Name %s, Score %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[i].id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[i].name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[i].score);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; found &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;break&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 找到就停
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#ff6ac1"&gt;!&lt;/span&gt;found) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Student with ID %d not found.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, target);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (choice &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;3&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 打印所有记录
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (count &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Table is empty.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;ID Name Score&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;----------------&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; count; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d %s %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[i].id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[i].name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[i].score);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;----------------&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Total records: %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, count);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Invalid choice, please try again.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Exited.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="84--编译运行"&gt;8.4 编译运行&lt;a class="anchor" href="#84--%e7%bc%96%e8%af%91%e8%bf%90%e8%a1%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;保存成 &lt;code&gt;table.c&lt;/code&gt;，编译：&lt;/p&gt;</description></item><item><title>第9章 文件保存和读取</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch09.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch09.html</guid><description>&lt;h2 id="91--问题从哪来"&gt;9.1 问题从哪来&lt;a class="anchor" href="#91--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章用 &lt;code&gt;struct Student&lt;/code&gt; 和结构体数组管理学生记录，能添加、查找、打印。但只要程序一关，所有数据全部消失。&lt;/p&gt;
&lt;p&gt;原因很简单：结构体数组存在内存里，内存是易失的——断电就清空。&lt;/p&gt;
&lt;p&gt;要把数据留下来，得把它写到&lt;strong&gt;文件&lt;/strong&gt;里。文件存在磁盘上，断电不丢。下次程序启动，再从文件读回来。&lt;/p&gt;
&lt;p&gt;这一章要做的事：把结构体数组里的学生记录写成文本文件，再从文本文件恢复成结构体数组。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="92--先看一个例子"&gt;9.2 先看一个例子&lt;a class="anchor" href="#92--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设内存里有两个学生：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;学号&lt;/th&gt;
					&lt;th&gt;姓名&lt;/th&gt;
					&lt;th&gt;分数&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1001&lt;/td&gt;
					&lt;td&gt;Alice&lt;/td&gt;
					&lt;td&gt;86&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;1002&lt;/td&gt;
					&lt;td&gt;Bob&lt;/td&gt;
					&lt;td&gt;91&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;保存到文件 &lt;code&gt;students.txt&lt;/code&gt; 后，文件内容长这样：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1001 Alice 86
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1002 Bob 91&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;每行一个学生，学号、姓名、分数用空格隔开。&lt;/p&gt;
&lt;p&gt;下次程序启动，读这个文件，就能把这两条记录重新填回结构体数组。&lt;/p&gt;
&lt;p&gt;这就是&amp;quot;持久化&amp;quot;的基本思路：保存时，把结构体字段写成文本行；读取时，再按同样的格式把文本行拆回字段。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch09/ch09-01-save-load-overview.svg" alt="结构体数组和文本文件之间的保存与读取" /&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="93--最小实验"&gt;9.3 最小实验&lt;a class="anchor" href="#93--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这段演示只新增两个函数：&lt;code&gt;save_students&lt;/code&gt; 和 &lt;code&gt;load_students&lt;/code&gt;。&lt;code&gt;main&lt;/code&gt; 用来准备几条记录、调用这两个函数、观察文件有没有留下来。&lt;/p&gt;
&lt;p&gt;第 8 章的菜单、添加、查找都先拿掉。这样实验里只剩下一件事：数组里的记录怎样写进文件，又怎样从文件读回数组。把这两个函数看清楚以后，再接回菜单程序就只是调用位置的问题。&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student { &lt;span style="color:#78787e"&gt;// 学生结构体：学号、姓名、分数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; name[&lt;span style="color:#ff9f43"&gt;32&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// 把 students 数组的前 count 条写入文件
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;save_students&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;const&lt;/span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;filename, &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student students[], &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; count)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FILE &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;fp &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;fopen&lt;/span&gt;(filename, &lt;span style="color:#5af78e"&gt;&amp;#34;w&amp;#34;&lt;/span&gt;); &lt;span style="color:#78787e"&gt;// &amp;#34;w&amp;#34; 表示写模式，文件不存在会创建
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (fp &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff5c57"&gt;NULL&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;-&lt;/span&gt;&lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 打开失败
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; count; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;fprintf&lt;/span&gt;(fp, &lt;span style="color:#5af78e"&gt;&amp;#34;%d %s %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, &lt;span style="color:#78787e"&gt;// 格式和 printf 一样，只是输出到文件
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[i].id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[i].name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[i].score);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;fclose&lt;/span&gt;(fp); &lt;span style="color:#78787e"&gt;// 写完必须关
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// 从文件读取学生记录，填入 students 数组，返回读到的条数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;load_students&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;const&lt;/span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;filename, &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student students[], &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; max_count)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FILE &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;fp &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;fopen&lt;/span&gt;(filename, &lt;span style="color:#5af78e"&gt;&amp;#34;r&amp;#34;&lt;/span&gt;); &lt;span style="color:#78787e"&gt;// &amp;#34;r&amp;#34; 表示读模式
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (fp &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff5c57"&gt;NULL&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 文件不存在，返回 0 条
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;while&lt;/span&gt; (count &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; max_count &lt;span style="color:#ff6ac1"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;fscanf&lt;/span&gt;(fp, &lt;span style="color:#5af78e"&gt;&amp;#34;%d %31s %d&amp;#34;&lt;/span&gt;, &lt;span style="color:#78787e"&gt;// %31s 防止姓名写越界
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;students[count].id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[count].name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;students[count].score) &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;3&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 成功读到 3 个字段才算一条
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; count&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;fclose&lt;/span&gt;(fp);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; count;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;main&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student students[&lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 学生数组，最多100条记录
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 当前实际记录数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 先尝试从文件加载
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;load_students&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;students.txt&amp;#34;&lt;/span&gt;, students, &lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (count &lt;span style="color:#ff6ac1"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Loaded %d records from file:&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, count);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;File not found, starting fresh.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 显示已有记录
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; count; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34; %d %s %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, students[i].id, students[i].name, students[i].score);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 手动添加两条记录。写入数组前，先确认还有空位。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (count &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[count].id &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1001&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;snprintf&lt;/span&gt;(students[count].name, &lt;span style="color:#ff6ac1"&gt;sizeof&lt;/span&gt;(students[count].name), &lt;span style="color:#5af78e"&gt;&amp;#34;%s&amp;#34;&lt;/span&gt;, &lt;span style="color:#5af78e"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[count].score &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;86&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; count&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Table full, skipping Alice.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (count &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[count].id &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1002&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;snprintf&lt;/span&gt;(students[count].name, &lt;span style="color:#ff6ac1"&gt;sizeof&lt;/span&gt;(students[count].name), &lt;span style="color:#5af78e"&gt;&amp;#34;%s&amp;#34;&lt;/span&gt;, &lt;span style="color:#5af78e"&gt;&amp;#34;Bob&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; students[count].score &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;91&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; count&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Table full, skipping Bob.&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 保存到文件
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;save_students&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;students.txt&amp;#34;&lt;/span&gt;, students, count) &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Saved %d records to students.txt&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, count);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Save failed!&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="94--编译运行"&gt;9.4 编译运行&lt;a class="anchor" href="#94--%e7%bc%96%e8%af%91%e8%bf%90%e8%a1%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;保存成 &lt;code&gt;file_demo.c&lt;/code&gt;，编译：&lt;/p&gt;</description></item><item><title>第10章 指针和动态容量</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch10.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch10.html</guid><description>&lt;h2 id="101--问题从哪来"&gt;10.1 问题从哪来&lt;a class="anchor" href="#101--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;第 9 章已经把学生记录写进文件，程序关了再开，数据还能读回来。文件解决的是“数据会不会留下来”。&lt;/p&gt;
&lt;p&gt;现在看内存里的学生表。上几章一直用结构体数组存学生记录：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student students[&lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 固定大小的数组，最多存 100 条
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; count &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 当前已存入的学生数量
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;100 条，写死在代码里。如果学校今年招了 120 个学生，第 101 条就放不进去了。&lt;/p&gt;
&lt;p&gt;一种做法是把 100 改成 10000，但 10000 条还是有上限，而且大多数时候只用了几十条，剩下 9000 多格全浪费了。&lt;/p&gt;
&lt;p&gt;另一种做法是问用户&amp;quot;你要存几条&amp;quot;，按需分配。但用户往往不知道自己最终要存多少：今天加 5 个，明天加 10 个，后天又删掉 3 个。&lt;/p&gt;
&lt;p&gt;真正需要的是：&lt;strong&gt;一开始分配一小块空间，快用完时自动变大。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本章先不处理文件读写，只把固定的 &lt;code&gt;students[100]&lt;/code&gt; 换成会增长的学生表。文件保存和读取以后仍然可以接在这张表外面。&lt;/p&gt;
&lt;p&gt;这个能力在 C 语言里叫做&lt;strong&gt;动态内存分配&lt;/strong&gt;。它依赖两个基础概念：函数怎么拿到外面的变量，以及指针怎么保存一块内存的地址。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="102--函数改不了外面的变量"&gt;10.2 函数改不了外面的变量&lt;a class="anchor" href="#102--%e5%87%bd%e6%95%b0%e6%94%b9%e4%b8%8d%e4%ba%86%e5%a4%96%e9%9d%a2%e7%9a%84%e5%8f%98%e9%87%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;动态内存分配依赖一个基础：函数要能修改调用者的变量。先用一个最小的例子把这个基础看清。&lt;/p&gt;
&lt;p&gt;假设要写一个函数，交换两个整数。第一版这样写：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;swap&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; a, &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; b) &lt;span style="color:#78787e"&gt;// a、b 是 x、y 的副本，不是变量本身
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; tmp &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; a; &lt;span style="color:#78787e"&gt;// 保存 a 的值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; a &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; b; &lt;span style="color:#78787e"&gt;// 改的是副本 a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; b &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; tmp; &lt;span style="color:#78787e"&gt;// 改的是副本 b，外面的 x、y 不变
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;main&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; x &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;3&lt;/span&gt;, y &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;7&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;swap&lt;/span&gt;(x, y); &lt;span style="color:#78787e"&gt;// 传值：x 和 y 的值被复制给 a 和 b
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;x=%d, y=%d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, x, y);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;运行结果：&lt;/p&gt;</description></item><item><title>第11章 链表</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch11.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch11.html</guid><description>&lt;h2 id="111--问题从哪来"&gt;11.1 问题从哪来&lt;a class="anchor" href="#111--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章用动态数组存学生记录，容量能增长了。但有一个麻烦没解决：&lt;strong&gt;中间插入和删除太贵&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;假设数组里有 5 个学生，要在第 2 个位置插入一个新学生：&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch11/ch11-01-array-insert-cost.svg" alt="数组中间插入时后面的元素要右移" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Bob&lt;/code&gt; 到 &lt;code&gt;Eve&lt;/code&gt; 四个人全部要往后挪一格，空出位置给新学生。图里的数字是下标，所以“第 2 个位置”对应下标 1。如果有 10 万条记录，在头部插入就要移动 10 万次。&lt;/p&gt;
&lt;p&gt;删除也一样。删掉第 1 个学生，后面的全部要往前挪。&lt;/p&gt;
&lt;p&gt;能不能有一种结构，插入和删除时&lt;strong&gt;不用移动其他元素&lt;/strong&gt;？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="112--用指针串起来"&gt;11.2 用指针串起来&lt;a class="anchor" href="#112--%e7%94%a8%e6%8c%87%e9%92%88%e4%b8%b2%e8%b5%b7%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;数组的痛点在于所有元素必须挤在一起。打破这个规则的办法是：让每个学生自己记住下一个学生在哪里。&lt;/p&gt;
&lt;p&gt;第 10 章已经见过地址。&lt;code&gt;&amp;amp;students[1]&lt;/code&gt; 就是第 2 个学生的地址。如果学生记录里除了存自己的学号和名字，还额外存一个地址，指向下一个学生的地址，那从第一个学生就能找到第二个，从第二个就能找到第三个。三个人不需要紧挨着，只靠地址就能串成一条链。&lt;/p&gt;
&lt;p&gt;这个结构里，每个学生是一个&lt;strong&gt;节点&lt;/strong&gt;（node），节点有两部分：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;部分&lt;/th&gt;
					&lt;th&gt;内容&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;data&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;学生的信息（&lt;code&gt;struct Student&lt;/code&gt;）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;next&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;一个指针，存下一个节点的地址&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;next&lt;/code&gt; 里存的是内存地址。最后一个节点后面没有节点时，就把 &lt;code&gt;next&lt;/code&gt; 设为 &lt;code&gt;NULL&lt;/code&gt;。节点散落在堆内存的各个角落，不需要紧挨着，只要靠 &lt;code&gt;next&lt;/code&gt; 里的地址互相找到就行。&lt;/p&gt;
&lt;p&gt;插入时，只需要改两条指针：前一个节点的 &lt;code&gt;next&lt;/code&gt; 指向新节点，新节点的 &lt;code&gt;next&lt;/code&gt; 指向原来的下一个节点。其他节点完全不用动。删除时也一样，让前一个节点的 &lt;code&gt;next&lt;/code&gt; 跳过被删节点，直接指向下一个。链表中只改指针，不搬数据。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="113--最小实验"&gt;11.3 最小实验&lt;a class="anchor" href="#113--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先看节点长什么样：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; name[&lt;span style="color:#ff9f43"&gt;32&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Node {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student data; &lt;span style="color:#78787e"&gt;// 这个节点存的学生信息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Node &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;next; &lt;span style="color:#78787e"&gt;// 指向下一个节点，没有下一个就是 NULL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;一个节点就是一个 &lt;code&gt;struct Node&lt;/code&gt;，里面装着一个学生的信息，外加一根指针指向下一个节点。&lt;/p&gt;</description></item><item><title>第12章 栈和撤销</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch12.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch12.html</guid><description>&lt;h2 id="121--问题从哪来"&gt;12.1 问题从哪来&lt;a class="anchor" href="#121--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章用链表存学生记录，能插能删能查。上一章最后提到编辑器的撤销功能，换到学生记录里，也会遇到同一种问题：&lt;/p&gt;
&lt;p&gt;你刚插入了一条学号为 7 的记录，本来想输入 &lt;code&gt;&amp;quot;Alice&amp;quot;&lt;/code&gt;，却打成了 &lt;code&gt;&amp;quot;Ailce&amp;quot;&lt;/code&gt;。发现错了，想撤销。&lt;/p&gt;
&lt;p&gt;链表能帮你删掉 id 为 7 的记录，但前提是——你记得刚才插入的 id 是 7。如果连续插入了十几条，你还记得最后一条是哪个 id 吗？&lt;/p&gt;
&lt;p&gt;程序需要一种机制：&lt;strong&gt;自动记住操作顺序，撤销时先撤最后做的那一步&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这种&amp;quot;最后做的最先被取出来&amp;quot;的顺序，有一个专门的名字——&lt;strong&gt;后进先出&lt;/strong&gt;（Last In, First Out，LIFO）。实现后进先出的数据结构叫&lt;strong&gt;栈&lt;/strong&gt;（stack）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="122--先看一个例子"&gt;12.2 先看一个例子&lt;a class="anchor" href="#122--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;想象一摞盘子。你洗好一个就放在最上面，取的时候也从最上面拿。最后放上去的盘子最先被取走。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;操作&lt;/th&gt;
					&lt;th&gt;盘子栈的变化&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;放入 A&lt;/td&gt;
					&lt;td&gt;[A]&lt;/td&gt;
					&lt;td&gt;A 在最底下&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;放入 B&lt;/td&gt;
					&lt;td&gt;[A, B]&lt;/td&gt;
					&lt;td&gt;B 在 A 上面&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;放入 C&lt;/td&gt;
					&lt;td&gt;[A, B, C]&lt;/td&gt;
					&lt;td&gt;C 在最上面&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;取出一个&lt;/td&gt;
					&lt;td&gt;[A, B]&lt;/td&gt;
					&lt;td&gt;取出的是 C，后进先出&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;取出一个&lt;/td&gt;
					&lt;td&gt;[A]&lt;/td&gt;
					&lt;td&gt;取出的是 B&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;两个关键动作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;push&lt;/strong&gt;（压栈）：把一个元素放到栈顶。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pop&lt;/strong&gt;（弹栈）：从栈顶取出一个元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在撤销场景里，每次插入操作把学生 id push 进栈，撤销时 pop 出最近的 id 并删除对应记录。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch12/ch12-01-stack-bottom-top.svg" alt="栈底和栈顶的位置关系" /&gt;&lt;/p&gt;</description></item><item><title>第13章 队列</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch13.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch13.html</guid><description>&lt;h2 id="131--问题从哪来"&gt;13.1 问题从哪来&lt;a class="anchor" href="#131--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章用栈实现了撤销功能。栈的特点是后进先出：最后放进去的最先被取出来。&lt;/p&gt;
&lt;p&gt;但有些场景恰恰需要反过来。想象一个打印队列：你先点了打印文档 A，同事接着点了打印文档 B。打印机应该先打 A 再打 B，因为 A 先到。如果用栈来管理，B 会被先打印——这不合理。&lt;/p&gt;
&lt;p&gt;食堂打饭、医院挂号、银行叫号，都是同一个规则：&lt;strong&gt;先来的先处理，后来的排在后面&lt;/strong&gt;。这种顺序叫&lt;strong&gt;先进先出&lt;/strong&gt;（First In, First Out，FIFO）。实现先进先出的数据结构叫&lt;strong&gt;队列&lt;/strong&gt;（queue）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="132--先看一个例子"&gt;13.2 先看一个例子&lt;a class="anchor" href="#132--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;想象食堂排队。学生一个一个走到队尾站好，打饭窗口从队头开始服务。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;操作&lt;/th&gt;
					&lt;th&gt;队列的变化&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;学生 A 来排队&lt;/td&gt;
					&lt;td&gt;[A]&lt;/td&gt;
					&lt;td&gt;A 既是队头也是队尾&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;学生 B 来排队&lt;/td&gt;
					&lt;td&gt;[A, B]&lt;/td&gt;
					&lt;td&gt;A 在队头，B 在队尾&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;学生 C 来排队&lt;/td&gt;
					&lt;td&gt;[A, B, C]&lt;/td&gt;
					&lt;td&gt;C 排到队尾&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;窗口服务一个&lt;/td&gt;
					&lt;td&gt;[B, C]&lt;/td&gt;
					&lt;td&gt;A 打完走了，B 变成队头&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;窗口服务一个&lt;/td&gt;
					&lt;td&gt;[C]&lt;/td&gt;
					&lt;td&gt;B 打完了&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;两个关键动作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;入队&lt;/strong&gt;（enqueue）：走到队尾排队。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出队&lt;/strong&gt;（dequeue）：从队头离开。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="./figures/ch13/ch13-01-front-rear.svg" alt="队头和队尾的位置" /&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="133--最小实验"&gt;13.3 最小实验&lt;a class="anchor" href="#133--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;队列的实现和栈很像，只是多了一个下标。核心只有三样东西：一个数组，两个整数下标。&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; queue[&lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;]; &lt;span style="color:#78787e"&gt;// 最多排 100 个
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; front &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 队头：下一个要出队的位置
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; rear &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 队尾：下一个要入队的位置
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;非空时，&lt;code&gt;front&lt;/code&gt; 是队头元素的位置，&lt;code&gt;rear&lt;/code&gt; 是下一个空位。当 &lt;code&gt;front == rear&lt;/code&gt; 时队列是空的。&lt;/p&gt;</description></item><item><title>第14章 四则运算计算器</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch14.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch14.html</guid><description>&lt;h2 id="141--问题从哪来"&gt;14.1 问题从哪来&lt;a class="anchor" href="#141--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章讲了队列，先进先出，适合排队场景。但有一类任务不适合先进先出——&lt;strong&gt;表达式计算&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;考虑这个表达式：&lt;/p&gt;
$$
3 + 5 \times 2
$$&lt;p&gt;如果从左到右依次处理：先算 &lt;code&gt;3 + 5 = 8&lt;/code&gt;，再算 &lt;code&gt;8 * 2 = 16&lt;/code&gt;。但数学规则说乘法优先，应该先算 &lt;code&gt;5 * 2 = 10&lt;/code&gt;，再算 &lt;code&gt;3 + 10 = 13&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;再加括号：&lt;/p&gt;
$$
3 + 5 \times (2 + 1)
$$&lt;p&gt;括号里的 &lt;code&gt;2 + 1&lt;/code&gt; 要最先算，然后 &lt;code&gt;5 * 3 = 15&lt;/code&gt;，最后 &lt;code&gt;3 + 15 = 18&lt;/code&gt;。顺序完全不是从左到右。&lt;/p&gt;
&lt;p&gt;把这个表达式画成树，会更容易看出计算顺序：下面的节点先合并，越往上越接近最后结果。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch14/ch14-01-expression-tree.svg" alt="表达式的语法树" /&gt;&lt;/p&gt;
&lt;p&gt;程序需要一种办法：&lt;strong&gt;记住还没算的数和运算符，根据优先级决定什么时候算&lt;/strong&gt;。前面学过的栈，正好能帮程序暂存这些还不能立刻处理的内容。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="142--先看一个例子"&gt;14.2 先看一个例子&lt;a class="anchor" href="#142--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;语法树说明了表达式应该按什么顺序合并，但程序读到的输入仍然是一串字符。它从左到右扫描，每读到一个字符，就把暂时还不能计算的内容放进栈里。&lt;/p&gt;
&lt;p&gt;这个过程需要两类栈：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数字栈：保存还没合并的数。&lt;/li&gt;
&lt;li&gt;运算符栈：保存还没处理的运算符。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;扫描时的规则可以分成三种动作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读到数字，直接压入数字栈。&lt;/li&gt;
&lt;li&gt;读到运算符，和运算符栈顶比较优先级；如果栈顶应该先算，就先弹栈计算。&lt;/li&gt;
&lt;li&gt;读到右括号 &lt;code&gt;)&lt;/code&gt;，一直计算到遇见左括号 &lt;code&gt;(&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下图停在 &lt;code&gt;3 + 5 *&lt;/code&gt; 这一刻。&lt;code&gt;*&lt;/code&gt; 的优先级高于栈顶的 &lt;code&gt;+&lt;/code&gt;，所以 &lt;code&gt;+&lt;/code&gt; 暂时留在栈里，&lt;code&gt;*&lt;/code&gt; 入栈。&lt;/p&gt;</description></item><item><title>第15章 排序</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch15.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch15.html</guid><description>&lt;h2 id="151--问题从哪来"&gt;15.1 问题从哪来&lt;a class="anchor" href="#151--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前面几章多次用过学生表：学号、姓名、分数，存进数组。按学号查找时，最直接的办法就是从第 0 条扫到最后一条，逐条比对。&lt;/p&gt;
&lt;p&gt;数据少的时候这没什么问题。但如果有 1000 条记录，每次查找平均要比较 500 次。而且不只是查找——打印时想按学号从小到大排列、按分数从高到低排名，这些操作通常会先要求：&lt;strong&gt;数据有顺序&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;数组刚建好时，数据按输入顺序存放，没有规律。学号 5 排在学号 2 前面，学号 1 排在学号 9 后面。&lt;/p&gt;
&lt;p&gt;让数据有顺序，程序里通常要先做一件事：排序。如果能把学号排成 1、2、3、5、9 这样的顺序，后面很多事情都会变简单。这一章解决的就是：怎么把一组无序的数据变成有序的。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="152--先看一个例子"&gt;15.2 先看一个例子&lt;a class="anchor" href="#152--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设有一组学号，还没排过序：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[5] [2] [4] [6] [1] [3]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;目标是排成：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[1] [2] [3] [4] [5] [6]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;插入排序的做法是：把数组分成两半，左边是&lt;strong&gt;已排序区&lt;/strong&gt;，右边是&lt;strong&gt;未排序区&lt;/strong&gt;。一开始已排序区只有第一个元素，剩下的都是未排序区。然后每次从未排序区取一个元素，插入到已排序区的正确位置。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch15/ch15-01-sorted-unsorted-region.svg" alt="未排序区和已排序区的初始状态" /&gt;&lt;/p&gt;
&lt;p&gt;这个过程就像打扑克牌：手里已经排好了一小叠牌，新摸到一张，就从右往左一张一张比，找到合适的位置插进去。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="153--最小实验"&gt;15.3 最小实验&lt;a class="anchor" href="#153--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// 插入排序：把 arr[0..n-1] 从小到大排
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;insertion_sort&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; arr[], &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; n)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; n; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 从第 2 个元素开始，逐个插入
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; key &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; arr[i]; &lt;span style="color:#78787e"&gt;// key 是当前要插入的元素
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; j &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;-&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// j 指向已排序区的最后一个
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 把比 key 大的元素往后移，给 key 腾位置
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;while&lt;/span&gt; (j &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;&amp;amp;&amp;amp;&lt;/span&gt; arr[j] &lt;span style="color:#ff6ac1"&gt;&amp;gt;&lt;/span&gt; key) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; arr[j &lt;span style="color:#ff6ac1"&gt;+&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;] &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; arr[j]; &lt;span style="color:#78787e"&gt;// 元素后移一位
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; j&lt;span style="color:#ff6ac1"&gt;--&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; arr[j &lt;span style="color:#ff6ac1"&gt;+&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;] &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; key; &lt;span style="color:#78787e"&gt;// 插入到正确位置
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;print_array&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; arr[], &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; n)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; n; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;%d &amp;#34;&lt;/span&gt;, arr[i]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;main&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; ids[] &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; {&lt;span style="color:#ff9f43"&gt;5&lt;/span&gt;, &lt;span style="color:#ff9f43"&gt;2&lt;/span&gt;, &lt;span style="color:#ff9f43"&gt;4&lt;/span&gt;, &lt;span style="color:#ff9f43"&gt;6&lt;/span&gt;, &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;, &lt;span style="color:#ff9f43"&gt;3&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; n &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;sizeof&lt;/span&gt;(ids) &lt;span style="color:#ff6ac1"&gt;/&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;sizeof&lt;/span&gt;(ids[&lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Before sort: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;print_array&lt;/span&gt;(ids, n);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;insertion_sort&lt;/span&gt;(ids, n);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;After sort: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;print_array&lt;/span&gt;(ids, n);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="154--编译运行"&gt;15.4 编译运行&lt;a class="anchor" href="#154--%e7%bc%96%e8%af%91%e8%bf%90%e8%a1%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;保存成 &lt;code&gt;sort.c&lt;/code&gt;，编译：&lt;/p&gt;</description></item><item><title>第16章 二分查找</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch16.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch16.html</guid><description>&lt;h2 id="161--问题从哪来"&gt;16.1 问题从哪来&lt;a class="anchor" href="#161--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前面几章用线性查找在数组里找学生：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; count; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) { &lt;span style="color:#78787e"&gt;// 从第一个元素开始逐个遍历
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (students[i].id &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; target) { &lt;span style="color:#78787e"&gt;// 比较当前元素的学号与目标
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; i; &lt;span style="color:#78787e"&gt;// 找到，返回下标
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;-&lt;/span&gt;&lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 遍历完未找到，返回 -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;不管数组有没有排序，都是从第一个开始，一个一个比。10 万个学生，最坏情况要比较 10 万次。&lt;/p&gt;
&lt;p&gt;但很多时候，数据是排好序的。比如学生按学号从小到大排好，电话簿按姓名排好，仓库货架按编号排好。排好序的数组有一个线性查找完全没用到的性质：&lt;strong&gt;如果中间那个元素比目标小，目标只可能在右半边；如果比目标大，只可能在左半边。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每次比较都能排除一大段候选位置。10 万个元素，$2^{17} = 131072$，最多 17 次就能确定目标在哪里，或者确定目标不存在。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="162--先看一个例子"&gt;16.2 先看一个例子&lt;a class="anchor" href="#162--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设有一组学号，已经从小到大排好：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;索引： 0 1 2 3 4 5 6 7 8 9
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;学号： 12 25 33 41 57 64 78 82 91 96&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;要找学号 57。&lt;/p&gt;
&lt;p&gt;第 1 步：看最中间那个。&lt;code&gt;low = 0&lt;/code&gt;，&lt;code&gt;high = 9&lt;/code&gt;，&lt;code&gt;mid = (0 + 9) / 2 = 4&lt;/code&gt;，&lt;code&gt;ids[4] = 57&lt;/code&gt;。正好等于目标，找到了，返回索引 4。&lt;/p&gt;</description></item><item><title>第17章 二叉搜索树</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch17.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch17.html</guid><description>&lt;h2 id="171--问题从哪来"&gt;17.1 问题从哪来&lt;a class="anchor" href="#171--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章用有序数组存学生 id，配合二分查找，查找速度是 $O(\log n)$。但插入一个新 id 时，要把比它大的元素全部往后挪一位，腾出位置。10 万条记录，最坏情况下要挪 10 万次。&lt;/p&gt;
&lt;p&gt;问题的根源在于数组是连续存储的。元素之间紧紧挨着，中间插一个，后面的全得动。&lt;/p&gt;
&lt;p&gt;有没有一种结构，插入时不用移动已有元素，查找时也能利用&amp;quot;大小关系&amp;quot;跳过一部分无关数据？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="172--先看一个例子"&gt;17.2 先看一个例子&lt;a class="anchor" href="#172--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设要存这几个学生 id：50、30、70、20、40、60、80。&lt;/p&gt;
&lt;p&gt;换一种方式来组织。先取第一个 id 50 作为&amp;quot;根&amp;quot;。之后每来一个新 id，跟当前节点比：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;比它小，往左走；&lt;/li&gt;
&lt;li&gt;比它大，往右走；&lt;/li&gt;
&lt;li&gt;走到空位置，就把自己放进去。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;插完 30：30 比 50 小，放在 50 左边。&lt;/p&gt;
&lt;p&gt;插完 70：70 比 50 大，放在 50 右边。&lt;/p&gt;
&lt;p&gt;插完 20：20 比 50 小，往左走到 30；20 比 30 小，放在 30 左边。&lt;/p&gt;
&lt;p&gt;插完 40：40 比 50 小，往左走到 30；40 比 30 大，放在 30 右边。&lt;/p&gt;
&lt;p&gt;以此类推，全部插完后长这样：&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch17/ch17-01-complete-bst-example.svg" alt="插入完成后的二叉搜索树" /&gt;&lt;/p&gt;
&lt;p&gt;每个节点最多有两个分支：左边的都比自己小，右边的都比自己大。这就是&lt;strong&gt;二叉搜索树&lt;/strong&gt;（Binary Search Tree，BST）。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch17/ch17-02-tree-node-structure.svg" alt="树节点结构：id、左孩子、右孩子" /&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="173--最小实验"&gt;17.3 最小实验&lt;a class="anchor" href="#173--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;二叉搜索树的节点只有三样东西：一个 id，两根指针。这个小实验先只存 id，方便观察树的形状；如果要存完整学生记录，也可以把 &lt;code&gt;name&lt;/code&gt;、&lt;code&gt;score&lt;/code&gt; 这些字段一起放进节点。&lt;/p&gt;</description></item><item><title>第18章 哈希表</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch18.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch18.html</guid><description>&lt;h2 id="181--问题从哪来"&gt;18.1 问题从哪来&lt;a class="anchor" href="#181--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章用二叉搜索树存学生 id。查找时每走一步会丢掉一个方向的子树；树比较平衡时，效率接近 $O(\log n)$。但这个效率有两个前提：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;树要平衡。如果按有序数据插入，树退化成链表，查找变成 $O(n)$。&lt;/li&gt;
&lt;li&gt;每次查找都要沿着树走好几层，每一层都要做一次比较。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;按 id 精确查找时，有没有可能跳过&amp;quot;一层层比较&amp;quot;，直接算出 id 该去哪个桶？&lt;/p&gt;
&lt;p&gt;打个比方。一排柜子，编号 0 到 9。学生 id 是 123，把 123 除以 10 余 3，直接去 3 号柜子找。不用从 0 号柜子开始一个一个翻。&lt;/p&gt;
&lt;p&gt;这就是哈希表的思路：&lt;strong&gt;用一个函数把 id 转换成桶编号，直接定位&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="182--先看一个例子"&gt;18.2 先看一个例子&lt;a class="anchor" href="#182--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设我们有 5 个桶，编号 0 到 4。学生 id 分别是 10、15、22、37、48。&lt;/p&gt;
&lt;p&gt;用 id 除以 5 取余数：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;id&lt;/th&gt;
					&lt;th&gt;id % 5&lt;/th&gt;
					&lt;th&gt;桶编号&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;15&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;22&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;37&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;48&lt;/td&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;3&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src="./figures/ch18/ch18-01-hash-function.svg" alt="id 经过哈希函数变成桶编号" /&gt;&lt;/p&gt;</description></item><item><title>第19章 程序正确性：复杂度、测试和不变量</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch19.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch19.html</guid><description>&lt;h2 id="191--问题从哪来"&gt;19.1 问题从哪来&lt;a class="anchor" href="#191--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前面几章写过很多数据结构：数组、链表、栈、队列、树、哈希表。&lt;/p&gt;
&lt;p&gt;这些结构都能保存或组织数据，其中数组、链表、树、哈希表还常用来查找数据。但写到这里会遇到两个很实际的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这个版本是不是真的比另一个版本快？&lt;/li&gt;
&lt;li&gt;插入、删除、查找很多次以后，结构有没有悄悄坏掉？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;比如哈希表的理想查找是 $O(1)$，但如果所有 id 都落进同一个桶，它就退化成链表。二分查找很快，但前提是数组已经排好序。二叉搜索树查找很快，但前提是树没有退化得太严重。&lt;/p&gt;
&lt;p&gt;这些前提不能只靠感觉。程序需要自己打印、计数、检查。&lt;strong&gt;不变量&lt;/strong&gt;就是程序运行前后都应该守住的条件。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="192--先看一个例子"&gt;19.2 先看一个例子&lt;a class="anchor" href="#192--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;查找 &lt;code&gt;13&lt;/code&gt; 这件事，几种结构走的路不同：&lt;/p&gt;
&lt;p&gt;有序数组是 &lt;code&gt;{1, 3, 5, 7, 9, 11, 13, 15}&lt;/code&gt;。顺序查找从第一个元素开始，一个一个往后比，看到 &lt;code&gt;1, 3, 5, 7, 9, 11, 13&lt;/code&gt; 才停，一共检查 7 次。&lt;/p&gt;
&lt;p&gt;二分查找利用“数组已经排好序”这个事实。第一次看中间的 &lt;code&gt;7&lt;/code&gt;，发现 &lt;code&gt;13&lt;/code&gt; 更大，就去右半边；第二次看 &lt;code&gt;11&lt;/code&gt;，还是更大；第三次看 &lt;code&gt;13&lt;/code&gt;，找到。它没有检查前面的每个数，只检查了 &lt;code&gt;7, 11, 13&lt;/code&gt; 这 3 个位置。&lt;/p&gt;
&lt;p&gt;链表查找也是从头往后走，但走几步取决于节点顺序。下面的程序用头插法建立链表，&lt;code&gt;13&lt;/code&gt; 正好排在第二个节点，所以链表查找检查 2 次。二叉搜索树从根节点开始，比当前节点大就往右走，比当前节点小就往左走；这组数据里查找 &lt;code&gt;13&lt;/code&gt; 会经过 &lt;code&gt;7, 11, 13&lt;/code&gt;。哈希表更直接一些，先算 &lt;code&gt;13 % 11&lt;/code&gt; 得到桶编号 &lt;code&gt;2&lt;/code&gt;，再到 2 号桶里找。&lt;/p&gt;
&lt;p&gt;可以给每个查找函数加一个 &lt;code&gt;steps&lt;/code&gt; 计数器。每检查一个候选元素或节点，就让 &lt;code&gt;steps&lt;/code&gt; 加一。运行后不只看到“找到了”，还能看到“找了几步”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="193--最小程序"&gt;19.3 最小程序&lt;a class="anchor" href="#193--%e6%9c%80%e5%b0%8f%e7%a8%8b%e5%ba%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;下面这个程序只做一件事：用几种结构查找同一个 id，并打印检查次数。第 18 章的哈希函数在这里命名为 &lt;code&gt;bucket_of&lt;/code&gt;，意思是根据 id 算出桶编号。代码里还加了两个小检查：&lt;/p&gt;</description></item><item><title>第20章 数组版数据库</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch20.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch20.html</guid><description>&lt;h2 id="201--问题从哪来"&gt;20.1 问题从哪来&lt;a class="anchor" href="#201--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前面几章学了结构体、数组、指针、函数，也开始用测试和不变量检查程序。用结构体能表示一个学生，用数组能存一批学生，用函数能把逻辑拆开。&lt;/p&gt;
&lt;p&gt;但这些能力分散在不同的小程序里。每次写一个新功能，就得在 &lt;code&gt;main&lt;/code&gt; 里重新声明数组、重新写循环、重新管理 &lt;code&gt;count&lt;/code&gt;。如果要同时管理两份学生名单，比如一班和二班，就得复制一份数组和一份计数器。&lt;/p&gt;
&lt;p&gt;上一章还留了一个提醒：程序不只是能跑，还要守住自己的边界。这里最重要的边界就是 &lt;code&gt;count&lt;/code&gt;：它不能小于 0，也不能超过数组容量；插入、查找、删除、列出都只能围着有效记录转。&lt;/p&gt;
&lt;p&gt;能不能把这些数据和操作打包在一起，让&amp;quot;数据库&amp;quot;变成一个独立的东西？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="202--先看一个例子"&gt;20.2 先看一个例子&lt;a class="anchor" href="#202--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设有一个程序，用户做了这几步操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;插入学号 1，Alice，92 分。&lt;/li&gt;
&lt;li&gt;插入学号 2，Bob，78 分。&lt;/li&gt;
&lt;li&gt;插入学号 3，Carol，85 分。&lt;/li&gt;
&lt;li&gt;按学号 2 查找，找到 Bob。&lt;/li&gt;
&lt;li&gt;删除学号 2。&lt;/li&gt;
&lt;li&gt;列出所有学生，只剩 Alice 和 Carol。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这几步的关键变化，是 &lt;code&gt;rows&lt;/code&gt; 里有效记录的范围和 &lt;code&gt;count&lt;/code&gt; 一起移动：&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch20/ch20-01-operation-trace.svg" alt="数组版数据库的一次完整操作轨迹" /&gt;&lt;/p&gt;
&lt;p&gt;插入时新记录写到 &lt;code&gt;rows[count]&lt;/code&gt;，然后 &lt;code&gt;count&lt;/code&gt; 加一。查找只读数组，不改变 &lt;code&gt;count&lt;/code&gt;。删除时找到那条记录，把后面的记录往前挪一格，再让 &lt;code&gt;count&lt;/code&gt; 减一。列出时只读 &lt;code&gt;count&lt;/code&gt; 以内的记录。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="203--最小实验"&gt;20.3 最小实验&lt;a class="anchor" href="#203--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;数据库的核心结构只有两样东西：一个数组，一个计数器。&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#define DB_CAPACITY 100
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; name[&lt;span style="color:#ff9f43"&gt;32&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student rows[DB_CAPACITY]; &lt;span style="color:#78787e"&gt;// 最多 100 条记录
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; count; &lt;span style="color:#78787e"&gt;// 当前有多少条
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="./figures/ch20/ch20-02-db-structure.svg" alt="DB 结构内部：rows 数组和 count 计数器" /&gt;&lt;/p&gt;</description></item><item><title>第21章 动态数据库表</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch21.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch21.html</guid><description>&lt;p&gt;这一章要解决的问题是：把固定数组改成动态数组，用 &lt;code&gt;capacity&lt;/code&gt; 记录总容量，用 &lt;code&gt;count&lt;/code&gt; 记录当前条数。插入时如果 &lt;code&gt;count == capacity&lt;/code&gt;，就用 &lt;code&gt;realloc&lt;/code&gt; 扩容。这样，学生表不再卡在写死的 100 条上。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="211--问题从哪来"&gt;21.1 问题从哪来&lt;a class="anchor" href="#211--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;第 20 章已经把学生记录和操作收进了 &lt;code&gt;struct DB&lt;/code&gt;。它的内部大致长这样：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student rows[&lt;span style="color:#ff9f43"&gt;100&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; count;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;rows[100]&lt;/code&gt; 还是写死在代码里。第 101 条记录就放不进去。&lt;/p&gt;
&lt;p&gt;一种做法是把 100 改成 10000，但 10000 条还是有上限。而且大多数时候只用了几十条，剩下几千格全浪费了。&lt;/p&gt;
&lt;p&gt;真正需要的是：&lt;strong&gt;一开始分配一小块空间，快用完时自动变大。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;容量缩小也可以做，但它需要额外规则：什么时候缩、缩到多小、怎样避免刚缩完又马上扩。本章先把插入时自动扩容这条路径跑通。&lt;/p&gt;
&lt;p&gt;第 10 章讲过 &lt;code&gt;malloc&lt;/code&gt;、&lt;code&gt;realloc&lt;/code&gt;、&lt;code&gt;free&lt;/code&gt; 三个函数，那一次用 &lt;code&gt;struct StudentTable&lt;/code&gt; 做了演示。这一章把同样的思路用到数据库上——给 &lt;code&gt;DB&lt;/code&gt; 结构体加一个 &lt;code&gt;capacity&lt;/code&gt; 字段，让它自己管理容量。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="212--先看一个例子"&gt;21.2 先看一个例子&lt;a class="anchor" href="#212--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设数据库初始容量是 2，连续插入 5 条记录：&lt;/p&gt;
&lt;p&gt;先分配 2 格。插入 Alice、Bob 后，&lt;code&gt;count&lt;/code&gt; 变成 2，正好等于 &lt;code&gt;capacity&lt;/code&gt;。下一次插入 Carol 前，程序先把容量扩到 4，再写入 Carol。插入 Dave 后又满了，所以插入 Eve 前再扩到 8。&lt;/p&gt;</description></item><item><title>第22章 链表版存储</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch22.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch22.html</guid><description>&lt;h2 id="221--问题从哪来"&gt;22.1 问题从哪来&lt;a class="anchor" href="#221--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前面几章的数据库一直用数组存记录。数组按下标访问很快，但中间插入和删除要搬动后面的元素——10 万条记录删第 1 条，后面 99999 条全部要往前挪。&lt;/p&gt;
&lt;p&gt;第 11 章讲过链表：插入和删除只需要改几根指针，不用搬动任何节点。那一章的链表是独立的，和数据库没有关系。&lt;/p&gt;
&lt;p&gt;现在的问题是：&lt;strong&gt;能不能把数据库的存储从数组换成链表？&lt;/strong&gt; 插入、查找、删除、遍历这些操作还能用吗？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="222--先看一个例子"&gt;22.2 先看一个例子&lt;a class="anchor" href="#222--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章的动态数组版数据库长这样：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;rows;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; count;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; capacity;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;rows&lt;/code&gt; 指向一段连续的数组空间，&lt;code&gt;count&lt;/code&gt; 记录当前有多少条，&lt;code&gt;capacity&lt;/code&gt; 记录这段空间能装多少条。第 20 章的固定数组和第 21 章的动态数组有一个共同点：有效记录仍然从 &lt;code&gt;rows[0]&lt;/code&gt; 开始连续排列。&lt;/p&gt;
&lt;p&gt;所以数组版遍历时可以用下标访问 &lt;code&gt;rows[i]&lt;/code&gt;。这个特点让访问第 &lt;code&gt;i&lt;/code&gt; 条记录很快，也带来一个代价：中间插入和删除要搬动后面的元素。&lt;/p&gt;
&lt;p&gt;链表版数据库长这样：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Node &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;head;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;数据放在一串节点里，每个节点通过 &lt;code&gt;next&lt;/code&gt; 指针连起来，&lt;code&gt;head&lt;/code&gt; 指向第一个节点。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch22/ch22-01-array-vs-linkedlist-db.svg" alt="数组版 DB 和链表版 DB 的内部结构对比" /&gt;&lt;/p&gt;
&lt;p&gt;两种版本对外提供的能力一样：插入、查找、删除、遍历。但内部结构完全不同，操作的实现方式也不同。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="223--最小实验"&gt;22.3 最小实验&lt;a class="anchor" href="#223--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先定义节点和数据库结构：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student { &lt;span style="color:#78787e"&gt;// 学生记录：学号、姓名、分数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; name[&lt;span style="color:#ff9f43"&gt;32&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Node { &lt;span style="color:#78787e"&gt;// 链表节点：存放一条学生数据 + 指向下一节点的指针
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student data;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Node &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;next;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB { &lt;span style="color:#78787e"&gt;// 数据库结构：只记链表头指针
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Node &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;head;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;struct DB&lt;/code&gt; 只有一个成员：&lt;code&gt;head&lt;/code&gt; 指针。它指向链表的第一个节点，链表为空时 &lt;code&gt;head&lt;/code&gt; 是 &lt;code&gt;NULL&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>第23章 数据库接口</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch23.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch23.html</guid><description>&lt;h2 id="231--问题从哪来"&gt;23.1 问题从哪来&lt;a class="anchor" href="#231--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前面的章节里，我们用数组存过学生记录，也用链表存过。每次换一种存储方式，操作数据的代码就要跟着重写。&lt;/p&gt;
&lt;p&gt;用数组时，插入是这样的：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;students[count].id &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;count&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果链表版维护了 &lt;code&gt;tail&lt;/code&gt; 指针，插入可能变成这样：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Node &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;new_node &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;malloc&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;sizeof&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;new_node));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;new_node&lt;span style="color:#ff6ac1"&gt;-&amp;gt;&lt;/span&gt;data.id &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;new_node&lt;span style="color:#ff6ac1"&gt;-&amp;gt;&lt;/span&gt;next &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff5c57"&gt;NULL&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tail&lt;span style="color:#ff6ac1"&gt;-&amp;gt;&lt;/span&gt;next &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; new_node;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;两段代码做的事情一样——往数据库里加一条记录。但写法完全不同。如果程序里有十处地方在操作学生数据，换成链表就要改十处。改多了，就容易出错。&lt;/p&gt;
&lt;p&gt;问题出在哪？外部代码直接碰了内部存储的细节。&lt;code&gt;count&lt;/code&gt; 是数组版的细节，&lt;code&gt;tail&lt;/code&gt; 是链表版的细节。这些细节一旦暴露在外面，换存储方式就会牵一发动全身。&lt;/p&gt;
&lt;p&gt;能不能让外部代码只看到一套固定的函数名，不关心里面怎么存的？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="232--先看一个例子"&gt;23.2 先看一个例子&lt;a class="anchor" href="#232--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设有一个学生管理程序，需要做四件事：初始化数据库、插入学生、按 id 查找、按 id 删除。&lt;/p&gt;
&lt;p&gt;不管底层用什么结构，这四件事不会变。变的只是每件事具体怎么做。&lt;/p&gt;
&lt;p&gt;如果把&amp;quot;做什么&amp;quot;和&amp;quot;怎么做&amp;quot;分开：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;外部代码只管调用 &lt;code&gt;db_insert(&amp;amp;db, student)&lt;/code&gt;，不关心 &lt;code&gt;db_insert&lt;/code&gt; 里面用的是数组还是链表。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;db_insert&lt;/code&gt; 的实现负责把 &lt;code&gt;student&lt;/code&gt; 存到合适的地方。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样，换了内部存储，只要 &lt;code&gt;db_insert&lt;/code&gt; 的函数签名不变，外部代码就不用改。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch23/ch23-01-api-vs-internal.svg" alt="外部代码只看到 DB API，API 内部访问具体的存储结构" /&gt;&lt;/p&gt;
&lt;p&gt;这个思路叫&lt;strong&gt;封装&lt;/strong&gt;：把内部细节包起来，只暴露必要的接口。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="233--最小实验"&gt;23.3 最小实验&lt;a class="anchor" href="#233--%e6%9c%80%e5%b0%8f%e5%ae%9e%e9%aa%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;接口就是一组函数签名。先把签名定下来：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_init&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_insert&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db, &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student s);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_find&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db, &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id, &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;out);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_delete&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db, &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_free&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;五个函数，五件事：&lt;/p&gt;</description></item><item><title>第24章 多文件编译和模块边界</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch24.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch24.html</guid><description>&lt;h2 id="241--问题从哪来"&gt;24.1 问题从哪来&lt;a class="anchor" href="#241--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章把数据库接口稳定下来了。外部代码只需要调用：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57c7ff"&gt;db_init&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;db);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57c7ff"&gt;db_insert&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;db, student);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57c7ff"&gt;db_find&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;db, id, &lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;out);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57c7ff"&gt;db_delete&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;db, id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57c7ff"&gt;db_free&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;db);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;接口稳定以后，所有代码继续放在一个文件里也能跑。但文件会越来越长：上面是结构体，中间是一堆 &lt;code&gt;db_*&lt;/code&gt; 函数，下面是 &lt;code&gt;main&lt;/code&gt;。功能继续增加时，索引、文件保存、日志、命令解析都会挤进同一个文件。&lt;/p&gt;
&lt;p&gt;一个文件太长时，读代码的人很难分清：哪些是外部可以用的接口，哪些只是数据库内部的细节。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch24/ch24-01-file-roles.svg" alt="头文件、实现文件和 main 文件各自承担不同角色" /&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="242--三个文件"&gt;24.2 三个文件&lt;a class="anchor" href="#242--%e4%b8%89%e4%b8%aa%e6%96%87%e4%bb%b6"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;最小拆法是三个文件：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;文件&lt;/th&gt;
					&lt;th&gt;放什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;student_db.h&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;公共结构、常量和函数声明&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;student_db.c&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;数据库函数的具体实现&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;main.c&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;调用数据库接口，完成用户要做的事&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;main.c&lt;/code&gt; 不应该直接读写 &lt;code&gt;rows&lt;/code&gt; 和 &lt;code&gt;count&lt;/code&gt;。它包含头文件，知道有哪些函数可以调用，具体怎么插入、查找、删除，交给 &lt;code&gt;student_db.c&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="243--student_dbh把接口写出来"&gt;24.3 student_db.h：把接口写出来&lt;a class="anchor" href="#243--student_dbh%e6%8a%8a%e6%8e%a5%e5%8f%a3%e5%86%99%e5%87%ba%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;下面这版仍然使用固定数组。固定数组不是更适合真正的数据库；它只是让变量更少，读代码时可以把注意力放在文件分工上。容量仍由 &lt;code&gt;MAX_ROWS&lt;/code&gt; 控制，插入、查找、删除的规则和上一章保持一致。&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// student_db.h
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#ifndef STUDENT_DB_H
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#define STUDENT_DB_H
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#define MAX_NAME 32
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#define MAX_ROWS 100
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; name[MAX_NAME];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; score;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student rows[MAX_ROWS];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; count;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_init&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_insert&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db, &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student s);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_find&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db, &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id, &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;out);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_delete&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db, &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_free&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;db_list&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;const&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; DB &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;db);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#endif&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;#ifndef&lt;/code&gt;、&lt;code&gt;#define&lt;/code&gt;、&lt;code&gt;#endif&lt;/code&gt; 是头文件保护。它们防止同一个头文件在同一个 &lt;code&gt;.c&lt;/code&gt; 的预处理结果里展开多次，尤其避免 &lt;code&gt;struct Student&lt;/code&gt;、&lt;code&gt;struct DB&lt;/code&gt; 这类类型被重复定义。&lt;/p&gt;</description></item><item><title>第25章 数据库索引</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch25.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch25.html</guid><description>&lt;h2 id="251--问题从哪来"&gt;25.1 问题从哪来&lt;a class="anchor" href="#251--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;数组版数据库有五个稳定接口：&lt;code&gt;db_init&lt;/code&gt;、&lt;code&gt;db_insert&lt;/code&gt;、&lt;code&gt;db_find&lt;/code&gt;、&lt;code&gt;db_delete&lt;/code&gt;、&lt;code&gt;db_free&lt;/code&gt;。外部代码只调用这五个函数，不需要知道里面是数组、链表，还是别的存储方式。&lt;/p&gt;
&lt;p&gt;但接口稳定不等于效率好。不管底层用什么存储，&lt;code&gt;db_find&lt;/code&gt; 的实现都是从头到尾逐条扫描：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; db&lt;span style="color:#ff6ac1"&gt;-&amp;gt;&lt;/span&gt;count; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (db&lt;span style="color:#ff6ac1"&gt;-&amp;gt;&lt;/span&gt;rows[i].id &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; id) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;out &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; db&lt;span style="color:#ff6ac1"&gt;-&amp;gt;&lt;/span&gt;rows[i]; &lt;span style="color:#78787e"&gt;// 把找到的记录复制给调用方
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;10 条记录无所谓，扫一遍很快就完了。10 万条记录呢？找一个 id 最坏要比较 10 万次。&lt;/p&gt;
&lt;p&gt;二分查找能利用有序数组：每次比较排除一半，10 万个元素最多 17 次。但二分查找要求数据按 id 排好序。数据库的记录不一定按 id 排——先插入 id=5，再插入 id=3，再插入 id=8，记录在数组里的顺序是 5、3、8，不是 3、5、8。直接对记录数组做二分查找，结果不对。&lt;/p&gt;
&lt;p&gt;可以在记录旁边维护一份&amp;quot;目录&amp;quot;：目录按 id 排好序，每一项记录这个 id 在哪一行。这份目录叫&lt;strong&gt;索引&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="252--一个小例子"&gt;25.2 一个小例子&lt;a class="anchor" href="#252--%e4%b8%80%e4%b8%aa%e5%b0%8f%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;数据库里有三条学生记录：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;行号: 0 1 2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;记录: [5,Alice] [3,Bob] [8,Carol]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;记录按插入顺序存放，id 顺序是 5、3、8，没有排序。如果要找 id=3，必须从行号 0 开始扫，比较两次才找到。&lt;/p&gt;
&lt;p&gt;给记录旁边加一张&amp;quot;目录&amp;quot;：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;目录（按 id 排序）:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id=3 → 行号 1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id=5 → 行号 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id=8 → 行号 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;目录按 id 从小到大排好。查找 id=3 时，先在目录里做二分查找：看中间那个（id=5），比 3 大，去左半边；左半边只有一个 id=3，找到了，对应行号 1。比较了两次，直接跳到 &lt;code&gt;rows[1]&lt;/code&gt;，拿到 Bob 的记录。&lt;/p&gt;</description></item><item><title>第26章 数据库持久化</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch26.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch26.html</guid><description>&lt;h2 id="261--问题从哪来"&gt;26.1 问题从哪来&lt;a class="anchor" href="#261--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;第25章给数据库加了索引。记录区 &lt;code&gt;rows&lt;/code&gt; 保存完整学生记录，索引区 &lt;code&gt;index&lt;/code&gt; 按 id 排序，索引项里保存 &lt;code&gt;id -&amp;gt; row_pos&lt;/code&gt;。查找时先在索引里二分查找，再跳到记录区。&lt;/p&gt;
&lt;p&gt;但这两块数据都在内存里。程序退出后，&lt;code&gt;rows&lt;/code&gt; 和 &lt;code&gt;index&lt;/code&gt; 占用的内存都会被操作系统回收。下一次启动，数据库又是空的。&lt;/p&gt;
&lt;p&gt;第 9 章已经做过文件读写：把学生记录写成文本行，再从文本行读回结构体。这个办法仍然能用，只是现在要处理一个新问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;记录区要保存。&lt;/li&gt;
&lt;li&gt;索引区要不要保存？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;答案是：&lt;strong&gt;只保存记录区，加载时重建索引。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;索引是根据记录的 id 和它在记录区中的位置建立出来的辅助结构。只要记录还在，索引就能重新生成。把索引也写进文件，反而会多一个一致性问题：文件里的记录被改了，索引可能就过期了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="262--先看一个例子"&gt;26.2 先看一个例子&lt;a class="anchor" href="#262--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;内存里的数据库有三条记录：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rows[0] = {5, &amp;#34;Alice&amp;#34;, 92}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rows[1] = {3, &amp;#34;Bob&amp;#34;, 78}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rows[2] = {8, &amp;#34;Carol&amp;#34;, 85}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;索引区按 id 排序：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;index[0] = {id=3, row_pos=1}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;index[1] = {id=5, row_pos=0}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;index[2] = {id=8, row_pos=2}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;保存时，只把记录区写进文件：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;5 Alice 92
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3 Bob 78
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;8 Carol 85&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="./figures/ch26/ch26-01-db-to-file.svg" alt="内存 DB 的记录区写入文件，每行对应一条记录" /&gt;&lt;/p&gt;</description></item><item><title>第27章 日志和恢复</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch27.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch27.html</guid><description>&lt;h2 id="271--问题从哪来"&gt;27.1 问题从哪来&lt;a class="anchor" href="#271--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;第26章把数据库保存到文件。保存时，程序打开 &lt;code&gt;students.db&lt;/code&gt;，把记录区一行一行写进去。&lt;/p&gt;
&lt;p&gt;这解决了&amp;quot;程序退出后数据消失&amp;quot;的问题，但没有解决&amp;quot;写到一半崩溃&amp;quot;的问题。&lt;/p&gt;
&lt;p&gt;假设 &lt;code&gt;students.db&lt;/code&gt; 原来有三条记录：&lt;code&gt;5 Alice 92&lt;/code&gt;、&lt;code&gt;3 Bob 78&lt;/code&gt;、&lt;code&gt;8 Carol 85&lt;/code&gt;。程序要保存新状态。它用 &lt;code&gt;&amp;quot;w&amp;quot;&lt;/code&gt; 打开文件，旧内容立刻被清空。写完前两条时程序崩溃，文件只剩前两行。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch27/ch27-01-save-truncate-crash.svg" alt="用 w 重写数据库文件时，崩溃会留下不完整文件" /&gt;&lt;/p&gt;
&lt;p&gt;Carol 丢了。旧文件已经被清空，新文件又没写完整。&lt;/p&gt;
&lt;p&gt;要让数据库更稳，修改数据之前需要先留下操作记录：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我要插入谁？&lt;/li&gt;
&lt;li&gt;我要删除谁？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这份操作记录就是日志。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="272--先看一个例子"&gt;27.2 先看一个例子&lt;a class="anchor" href="#272--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一次插入操作不再直接改内存，而是分两步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先把 &lt;code&gt;INSERT 8 Carol 85&lt;/code&gt; 追加到日志文件。&lt;/li&gt;
&lt;li&gt;再把 Carol 插入内存数据库。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="./figures/ch27/ch27-02-write-log-first.svg" alt="先写日志，再改内存表" /&gt;&lt;/p&gt;
&lt;p&gt;如果崩溃发生在不同位置：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;崩溃位置&lt;/th&gt;
					&lt;th&gt;日志&lt;/th&gt;
					&lt;th&gt;内存&lt;/th&gt;
					&lt;th&gt;恢复时怎么处理&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;写日志前&lt;/td&gt;
					&lt;td&gt;没有操作&lt;/td&gt;
					&lt;td&gt;没改&lt;/td&gt;
					&lt;td&gt;什么都不用做&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;写日志后、改内存前&lt;/td&gt;
					&lt;td&gt;有操作&lt;/td&gt;
					&lt;td&gt;没改&lt;/td&gt;
					&lt;td&gt;回放日志，把操作补上&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;改内存后、保存前&lt;/td&gt;
					&lt;td&gt;有操作&lt;/td&gt;
					&lt;td&gt;改了但没进快照&lt;/td&gt;
					&lt;td&gt;回放日志，得到同样结果&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src="./figures/ch27/ch27-03-crash-positions.svg" alt="崩溃发生在不同位置，恢复结果都正确" /&gt;&lt;/p&gt;
&lt;p&gt;启动恢复时做两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从 &lt;code&gt;students.db&lt;/code&gt; 加载上次保存好的快照。&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;students.log&lt;/code&gt; 逐行回放快照之后发生的操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="./figures/ch27/ch27-04-replay-on-startup.svg" alt="启动时先加载数据库文件，再回放日志" /&gt;&lt;/p&gt;
&lt;p&gt;快照文件保存某一刻的完整状态，日志文件保存那一刻之后的变化。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch27/ch27-05-snapshot-and-log.svg" alt="快照和日志的关系：快照是某一刻的状态，日志记录了之后的变化" /&gt;&lt;/p&gt;</description></item><item><title>第28章 命令协议</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch28.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch28.html</guid><description>&lt;h2 id="281--问题从哪来"&gt;28.1 问题从哪来&lt;a class="anchor" href="#281--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前面已经有了一个会保存、会恢复的小数据库。操作它的方式仍然是写 C 代码：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;struct&lt;/span&gt; Student s &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; {&lt;span style="color:#ff9f43"&gt;8&lt;/span&gt;, &lt;span style="color:#5af78e"&gt;&amp;#34;Carol&amp;#34;&lt;/span&gt;, &lt;span style="color:#ff9f43"&gt;85&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57c7ff"&gt;db_insert_logged&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;db, &lt;span style="color:#5af78e"&gt;&amp;#34;students.log&amp;#34;&lt;/span&gt;, s);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57c7ff"&gt;db_delete_logged&lt;/span&gt;(&lt;span style="color:#ff6ac1"&gt;&amp;amp;&lt;/span&gt;db, &lt;span style="color:#5af78e"&gt;&amp;#34;students.log&amp;#34;&lt;/span&gt;, &lt;span style="color:#ff9f43"&gt;3&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这对写程序的人有用，对普通用户不方便。用户不应该为了插入一条记录去改源码、重新编译、再运行。&lt;/p&gt;
&lt;p&gt;更自然的方式是输入一行文字：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-console"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ INSERT &lt;span style="color:#ff9f43"&gt;8&lt;/span&gt; Carol &lt;span style="color:#ff9f43"&gt;85&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ SELECT &lt;span style="color:#ff9f43"&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ DELETE &lt;span style="color:#ff9f43"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ RANGE &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;程序读取这行文字，解析出命令名和参数，然后调用对应的数据库函数。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch28/ch28-01-code-vs-command.svg" alt="上一章：写 C 代码调用 DB 函数。本章：输入文本命令，程序自动调用 DB 函数" /&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="282--先看一个例子"&gt;28.2 先看一个例子&lt;a class="anchor" href="#282--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;命令行交互大概长这样：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-console"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ INSERT &lt;span style="color:#ff9f43"&gt;5&lt;/span&gt; Alice &lt;span style="color:#ff9f43"&gt;92&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;Insert succeeded
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ INSERT &lt;span style="color:#ff9f43"&gt;3&lt;/span&gt; Bob &lt;span style="color:#ff9f43"&gt;78&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;Insert succeeded
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ SELECT &lt;span style="color:#ff9f43"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;ID: 3 Name: Bob Score: 78
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ RANGE &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;ID: 5 Name: Alice Score: 92
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;ID: 3 Name: Bob Score: 78
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ DELETE &lt;span style="color:#ff9f43"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#43454f"&gt;Delete succeeded
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ &lt;span style="color:#ff5c57"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;每条命令都有相同结构：&lt;/p&gt;</description></item><item><title>第29章 状态机和更稳的命令解析</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch29.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch29.html</guid><description>&lt;h2 id="291--问题从哪来"&gt;29.1 问题从哪来&lt;a class="anchor" href="#291--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章的命令协议能处理：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INSERT 5 Alice 92&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;按空格拆开，得到 4 段：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;下标&lt;/th&gt;
					&lt;th&gt;参数&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;INSERT&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;5&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Alice&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;92&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;但如果名字里有空格：&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INSERT 5 &amp;#34;Alice Wang&amp;#34; 92&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;普通按空格拆词会把 &lt;code&gt;&amp;quot;Alice Wang&amp;quot;&lt;/code&gt; 拆成两段。这就错了。&lt;code&gt;Alice Wang&lt;/code&gt; 应该是一整个名字。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch29/ch29-01-space-split-problem.svg" alt="按空格拆词处理不了带空格的姓名" /&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="292--状态是什么"&gt;29.2 状态是什么&lt;a class="anchor" href="#292--%e7%8a%b6%e6%80%81%e6%98%af%e4%bb%80%e4%b9%88"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;解析一行文字时，程序不是只看当前字符，还要知道&amp;quot;当前处在什么位置&amp;quot;：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;状态&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;普通状态&lt;/td&gt;
					&lt;td&gt;还没进入一个参数，遇到空格就跳过&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;单词状态&lt;/td&gt;
					&lt;td&gt;正在读一个普通参数，遇到空格就结束&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;引号状态&lt;/td&gt;
					&lt;td&gt;正在读引号里的参数，空格也是内容的一部分&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;状态机就是：读到一个字符，根据当前状态决定下一步怎么走。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch29/ch29-02-parser-states.svg" alt="命令解析在普通、单词和引号三个状态之间切换" /&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="293--最小程序"&gt;29.3 最小程序&lt;a class="anchor" href="#293--%e6%9c%80%e5%b0%8f%e7%a8%8b%e5%ba%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个程序只负责把一行命令拆成参数。它不实现完整数据库，只打印拆出来的参数，方便观察解析结果。这里的 token 可以理解为&amp;quot;拆出来的一个参数&amp;quot;。&lt;/p&gt;
&lt;div class="course-codeblock course-codeblock-c"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;ctype.h&amp;gt; // isspace 判断空白字符&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#include&lt;/span&gt; &lt;span style="color:#78787e"&gt;&amp;lt;stdio.h&amp;gt; // printf 打印结果&lt;/span&gt;&lt;span style="color:#78787e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;#define MAX_ARGS 8 &lt;/span&gt;&lt;span style="color:#78787e"&gt;// 最多拆出 8 个参数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// 状态机的三种状态：不在参数中 / 在普通参数中 / 在引号参数中
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;enum&lt;/span&gt; State {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; OUTSIDE, &lt;span style="color:#78787e"&gt;// 还没进入参数，遇到空格跳过
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IN_WORD, &lt;span style="color:#78787e"&gt;// 正在读一个空格分隔的参数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IN_QUOTE &lt;span style="color:#78787e"&gt;// 正在读双引号内的参数，空格不算分隔符
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;// 把一行命令拆成参数数组，返回参数个数；出错返回负数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;split_command&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;line, &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;args[], &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; max_args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;enum&lt;/span&gt; State state &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; OUTSIDE; &lt;span style="color:#78787e"&gt;// 初始状态：不在参数中
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; argc &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 当前已收集的参数个数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 逐个字符扫描整行
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;p &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; line; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;p &lt;span style="color:#ff6ac1"&gt;!=&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;\0&amp;#39;&lt;/span&gt;; p&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (state &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; OUTSIDE) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;isspace&lt;/span&gt;((&lt;span style="color:#9aedfe"&gt;unsigned&lt;/span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt;)&lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;p)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;continue&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 空白字符：继续跳过
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (argc &lt;span style="color:#ff6ac1"&gt;&amp;gt;=&lt;/span&gt; max_args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;-&lt;/span&gt;&lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 参数数量超上限
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;p &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;&amp;#34;&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 遇到左双引号：参数从引号后一个字符开始
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; args[argc&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;] &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; p &lt;span style="color:#ff6ac1"&gt;+&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; IN_QUOTE; &lt;span style="color:#78787e"&gt;// 进入引号状态
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 普通字符：参数从这里开始
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; args[argc&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;] &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; p;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; IN_WORD; &lt;span style="color:#78787e"&gt;// 进入单词状态
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (state &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; IN_WORD) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#57c7ff"&gt;isspace&lt;/span&gt;((&lt;span style="color:#9aedfe"&gt;unsigned&lt;/span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt;)&lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;p)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;p &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;\0&amp;#39;&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 写入 \0 切断当前参数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; OUTSIDE; &lt;span style="color:#78787e"&gt;// 退出单词状态，回到空闲
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#ff6ac1"&gt;else&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (state &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; IN_QUOTE) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (&lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;p &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;&amp;#34;&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;p &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#39;\0&amp;#39;&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 右引号改写为 \0，切断当前参数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; OUTSIDE; &lt;span style="color:#78787e"&gt;// 退出引号状态，回到空闲
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (state &lt;span style="color:#ff6ac1"&gt;==&lt;/span&gt; IN_QUOTE) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;-&lt;/span&gt;&lt;span style="color:#ff9f43"&gt;2&lt;/span&gt;; &lt;span style="color:#78787e"&gt;// 扫描结束仍在引号中：引号未闭合
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; argc; &lt;span style="color:#78787e"&gt;// 返回拆出的参数个数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;print_args&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;args[], &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; argc)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;for&lt;/span&gt; (&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; i &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;; i &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; argc; i&lt;span style="color:#ff6ac1"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;args[%d] = &amp;lt;%s&amp;gt;&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, i, args[i]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;main&lt;/span&gt;(&lt;span style="color:#9aedfe"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#78787e"&gt;// 测试输入：双引号包裹的姓名
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; line[] &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#5af78e"&gt;&amp;#34;INSERT 5 &lt;/span&gt;&lt;span style="color:#5af78e"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#5af78e"&gt;Alice Wang&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#5af78e"&gt; 92&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;char&lt;/span&gt; &lt;span style="color:#ff6ac1"&gt;*&lt;/span&gt;args[MAX_ARGS];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#9aedfe"&gt;int&lt;/span&gt; argc &lt;span style="color:#ff6ac1"&gt;=&lt;/span&gt; &lt;span style="color:#57c7ff"&gt;split_command&lt;/span&gt;(line, args, MAX_ARGS);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;if&lt;/span&gt; (argc &lt;span style="color:#ff6ac1"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;printf&lt;/span&gt;(&lt;span style="color:#5af78e"&gt;&amp;#34;Parse failed, error code: %d&lt;/span&gt;&lt;span style="color:#5af78e"&gt;\n&lt;/span&gt;&lt;span style="color:#5af78e"&gt;&amp;#34;&lt;/span&gt;, argc);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57c7ff"&gt;print_args&lt;/span&gt;(args, argc);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;return&lt;/span&gt; &lt;span style="color:#ff9f43"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="294--编译运行"&gt;29.4 编译运行&lt;a class="anchor" href="#294--%e7%bc%96%e8%af%91%e8%bf%90%e8%a1%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;保存为 &lt;code&gt;parse_quote.c&lt;/code&gt;，编译：&lt;/p&gt;</description></item><item><title>第30章 向量</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch30.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch30.html</guid><description>&lt;h2 id="301--问题从哪来"&gt;30.1 问题从哪来&lt;a class="anchor" href="#301--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前面的程序已经能把用户输入拆成命令和参数，数据库接到的还是一个很明确的目标：插入记录、按 id 查找、按 id 删除。按 id 查找的逻辑很直接——给一个 id，从头到尾比对，找到就返回。&lt;/p&gt;
&lt;p&gt;但有一类问题，数据库回答不了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学生 Alice 和谁最像？&lt;/li&gt;
&lt;li&gt;这篇文章和哪篇最接近？&lt;/li&gt;
&lt;li&gt;这首歌和哪首风格相似？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题不是在找&amp;quot;相等&amp;quot;，而是在找&amp;quot;相近&amp;quot;。&lt;code&gt;id = 3&lt;/code&gt; 能精确命中一条记录，但&amp;quot;和 Alice 最像&amp;quot;需要比较每条记录和 Alice 之间的差距。&lt;/p&gt;
&lt;p&gt;要比较差距，首先得把记录变成一组数字。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="302--先看一个例子"&gt;30.2 先看一个例子&lt;a class="anchor" href="#302--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设数据库里有三个学生：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;id&lt;/th&gt;
					&lt;th&gt;name&lt;/th&gt;
					&lt;th&gt;score&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;Alice&lt;/td&gt;
					&lt;td&gt;92&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;Bob&lt;/td&gt;
					&lt;td&gt;65&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;Carol&lt;/td&gt;
					&lt;td&gt;88&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;用 &lt;code&gt;db_find(&amp;amp;db, 1, &amp;amp;out)&lt;/code&gt; 能精确找到 Alice。但&amp;quot;谁和 Alice 最像&amp;quot;这个问题，数据库没法回答。&lt;code&gt;score&lt;/code&gt; 只有一个数字，Alice 和 Carol 都是高分，可她们的学习习惯可能完全不同。一个数字能比较大小，但描述太粗糙。&lt;/p&gt;
&lt;p&gt;如果给每个学生多记几个数字呢？&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;name&lt;/th&gt;
					&lt;th&gt;每周学习时长&lt;/th&gt;
					&lt;th&gt;出勤率&lt;/th&gt;
					&lt;th&gt;作业完成率&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Alice&lt;/td&gt;
					&lt;td&gt;21.0&lt;/td&gt;
					&lt;td&gt;0.95&lt;/td&gt;
					&lt;td&gt;0.90&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Bob&lt;/td&gt;
					&lt;td&gt;5.0&lt;/td&gt;
					&lt;td&gt;0.60&lt;/td&gt;
					&lt;td&gt;0.50&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Carol&lt;/td&gt;
					&lt;td&gt;15.0&lt;/td&gt;
					&lt;td&gt;0.85&lt;/td&gt;
					&lt;td&gt;0.80&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;三个数字排成一行，就构成了一个&lt;strong&gt;向量&lt;/strong&gt;（vector）：&lt;/p&gt;</description></item><item><title>第31章 距离和相似度</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch31.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch31.html</guid><description>&lt;h2 id="311--问题从哪来"&gt;31.1 问题从哪来&lt;a class="anchor" href="#311--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章把学生记录变成了向量。Alice 是 &lt;code&gt;[21.0, 0.95, 0.90]&lt;/code&gt;，Bob 是 &lt;code&gt;[5.0, 0.60, 0.50]&lt;/code&gt;，Carol 是 &lt;code&gt;[15.0, 0.85, 0.80]&lt;/code&gt;。逐维度看差值能看出一些差别，但三个差值不是一个数。&lt;/p&gt;
&lt;p&gt;数据库里存了 100 个学生，有人问&amp;quot;谁和 Alice 最像&amp;quot;——逐维度看差值要看 100 × 3 = 300 个数字，然后自己在脑子里合成一个结论。这件事应该交给程序做。&lt;/p&gt;
&lt;p&gt;程序需要一个函数：输入两个向量，输出一个数字。这个数字能代表&amp;quot;像不像&amp;quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="312--先看一个例子"&gt;31.2 先看一个例子&lt;a class="anchor" href="#312--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;回到上一章的三个学生，只看两个维度（学习时长和出勤率），在平面上画出来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice：(21.0, 0.95)&lt;/li&gt;
&lt;li&gt;Bob：(5.0, 0.60)&lt;/li&gt;
&lt;li&gt;Carol：(15.0, 0.85)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="./figures/ch31/ch31-01-2d-points-distance.svg" alt="二维平面上三个点，Alice 和 Carol 离得近，Bob 离得远" /&gt;&lt;/p&gt;
&lt;p&gt;直觉告诉我们 Alice 和 Carol 离得近。用尺子量也能量出来。但程序没有尺子，它只有数字。&lt;/p&gt;
&lt;p&gt;平面上两点的距离公式是勾股定理：&lt;/p&gt;
$$d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}$$&lt;p&gt;Alice 和 Carol 的距离：$\sqrt{(21-15)^2 + (0.95-0.85)^2} = \sqrt{36 + 0.01} \approx 6.00$。&lt;/p&gt;
&lt;p&gt;Alice 和 Bob 的距离：$\sqrt{(21-5)^2 + (0.95-0.60)^2} = \sqrt{256 + 0.1225} \approx 16.00$。&lt;/p&gt;</description></item><item><title>第32章 Top-K和KNN</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch32.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch32.html</guid><description>&lt;p&gt;给一个查询向量，遍历数据库里的记录，算距离，保留距离最小的 K 条。这就是 KNN（K-Nearest Neighbors）。它的基本动作不复杂：算距离、比大小、留 K 个。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="321--问题从哪来"&gt;32.1 问题从哪来&lt;a class="anchor" href="#321--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章学会了算两个向量之间的距离。只看学习时长和出勤率这两个维度时，Alice 和 Bob 的距离约为 16.00，Alice 和 Carol 的距离约为 6.00，一比就知道谁更像。&lt;/p&gt;
&lt;p&gt;但数据库里不会只有两条记录。真实的问题是这样的：&lt;/p&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;数据库里有 1000 个学生。我输入一个新学生的特征向量，想找数据库里和他最像的 5 个人。&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;这就是 &lt;strong&gt;KNN（K-Nearest Neighbors，K 近邻）&lt;/strong&gt; 问题：给一个查询点，从所有记录里找出距离最近的 K 条。&lt;/p&gt;
&lt;p&gt;这一章里的 &lt;strong&gt;Top-K&lt;/strong&gt; 指的是&amp;quot;距离最小的 K 个&amp;quot;。如果把每条记录到查询点的距离都算出来，Top-K 就是距离表里排在最前面的 K 条。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="322--先看一个例子"&gt;32.2 先看一个例子&lt;a class="anchor" href="#322--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设数据库里有 6 个学生，每个学生用二维向量描述（学习时长、出勤率）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;id&lt;/th&gt;
					&lt;th&gt;name&lt;/th&gt;
					&lt;th&gt;vec&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;Alice&lt;/td&gt;
					&lt;td&gt;[21.0, 0.95]&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;Bob&lt;/td&gt;
					&lt;td&gt;[5.0, 0.60]&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;Carol&lt;/td&gt;
					&lt;td&gt;[15.0, 0.85]&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;Dave&lt;/td&gt;
					&lt;td&gt;[18.0, 0.90]&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;Eve&lt;/td&gt;
					&lt;td&gt;[8.0, 0.65]&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;6&lt;/td&gt;
					&lt;td&gt;Frank&lt;/td&gt;
					&lt;td&gt;[23.0, 0.92]&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;现在有一个查询点 Q = [16.0, 0.88]，想找和 Q 最像的 3 个学生（K = 3）。&lt;/p&gt;</description></item><item><title>第33章 KMeans</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch33.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch33.html</guid><description>&lt;h2 id="331--问题从哪来"&gt;33.1 问题从哪来&lt;a class="anchor" href="#331--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章的 KNN 能回答&amp;quot;这个点和哪些点最像&amp;quot;。给一个新点，程序遍历数据库，找出距离最近的 K 条记录。&lt;/p&gt;
&lt;p&gt;但 KNN 需要一个查询点：先给出 Q，程序再找 Q 附近的记录。&lt;/p&gt;
&lt;p&gt;如果没有查询点，只是手里有一批数据，问题就变了。数据库里有一百个学生，每个学生有两个特征数字，比如学习时长和考试分数。程序能不能只看这些数字，自己把学生分成几组？&lt;/p&gt;
&lt;p&gt;这就是&lt;strong&gt;聚类&lt;/strong&gt;（clustering）要解决的问题：不需要标签，让程序自动分组。同一组里的点互相靠近，不同组的点互相远离。&lt;/p&gt;
&lt;p&gt;注意一下，这里的 K 和上一章 KNN 里的 K 含义不同。KNN 的 K 是“找几条最近记录”，KMeans 的 K 是“分成几组”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="332--先看一个例子"&gt;33.2 先看一个例子&lt;a class="anchor" href="#332--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设数据库里有 8 个学生，每个学生有两个特征：学习时长（小时/周）和考试分数（0~100）。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;学生&lt;/th&gt;
					&lt;th&gt;学习时长&lt;/th&gt;
					&lt;th&gt;考试分数&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;A&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;30&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;B&lt;/td&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;35&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;C&lt;/td&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;28&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;D&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;40&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;E&lt;/td&gt;
					&lt;td&gt;15&lt;/td&gt;
					&lt;td&gt;80&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;F&lt;/td&gt;
					&lt;td&gt;16&lt;/td&gt;
					&lt;td&gt;85&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;G&lt;/td&gt;
					&lt;td&gt;17&lt;/td&gt;
					&lt;td&gt;78&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;H&lt;/td&gt;
					&lt;td&gt;18&lt;/td&gt;
					&lt;td&gt;90&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;把这 8 个点画在坐标系里，肉眼就能看出来：左边有 4 个点聚在一起（学习少、分数低），右边有 4 个点聚在一起（学习多、分数高）。&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch33/ch33-01-scatter-raw.svg" alt="8 个点在坐标系里的分布，左边一组，右边一组" /&gt;&lt;/p&gt;</description></item><item><title>第34章 线性回归</title><link>https://simgor001.github.io/c-learning-companion/chapters/ch34.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://simgor001.github.io/c-learning-companion/chapters/ch34.html</guid><description>&lt;h2 id="341--问题从哪来"&gt;34.1 问题从哪来&lt;a class="anchor" href="#341--%e9%97%ae%e9%a2%98%e4%bb%8e%e5%93%aa%e6%9d%a5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上一章的 KMeans 能把数据自动分成几组。分组之后，我们知道某个点被分到哪一组。&lt;/p&gt;
&lt;p&gt;但有一类问题，KMeans 回答不了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;知道学生每周学习多少小时，想预测他的考试成绩。&lt;/li&gt;
&lt;li&gt;知道房子的面积，想预测它的价格。&lt;/li&gt;
&lt;li&gt;知道广告投入多少钱，想预测销售额。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题不是在分组，而是在&amp;quot;给一个数字，算出另一个数字&amp;quot;。输入和输出都可以当作连续数值处理。分组算法把世界切成几块，但这些问题是想在数据里找到一条趋势线。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="342--先看一个例子"&gt;34.2 先看一个例子&lt;a class="anchor" href="#342--%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e4%be%8b%e5%ad%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设调查了 6 个学生的每周学习时长和考试成绩：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;学生&lt;/th&gt;
					&lt;th&gt;每周学习时长 $x$&lt;/th&gt;
					&lt;th&gt;考试成绩 $y$&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;A&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;55&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;B&lt;/td&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;60&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;C&lt;/td&gt;
					&lt;td&gt;6&lt;/td&gt;
					&lt;td&gt;68&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;D&lt;/td&gt;
					&lt;td&gt;8&lt;/td&gt;
					&lt;td&gt;75&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;E&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;82&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;F&lt;/td&gt;
					&lt;td&gt;12&lt;/td&gt;
					&lt;td&gt;88&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;把这 6 个点画在坐标系里，横轴是学习时长，纵轴是成绩：&lt;/p&gt;
&lt;p&gt;&lt;img src="./figures/ch34/ch34-01-scatter.svg" alt="6 个数据点的散点图，横轴学习时长，纵轴成绩，点大致呈上升趋势" /&gt;&lt;/p&gt;
&lt;p&gt;这些点不是随机散开的——学习时间越长，成绩越高。点大致沿着一条看不见的线排列。&lt;/p&gt;
&lt;p&gt;如果能把这条线找出来，给一个新的学习时长，就能沿着这条线读出预测的成绩。比如学习 7 小时，预测成绩大约是多少？&lt;/p&gt;
&lt;p&gt;这就是线性回归要做的事：给散点图找一条最佳拟合直线。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="343--拟合是什么意思"&gt;34.3 拟合是什么意思&lt;a class="anchor" href="#343--%e6%8b%9f%e5%90%88%e6%98%af%e4%bb%80%e4%b9%88%e6%84%8f%e6%80%9d"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;找一条线&amp;quot;在数学上就是找两个数：斜率 $a$ 和截距 $b$，使得 $y = ax + b$ 尽量贴近所有数据点。&lt;/p&gt;
&lt;p&gt;什么叫&amp;quot;尽量贴近&amp;rdquo;？对每个数据点，用线算出的 $\hat{y}_i = a x_i + b$ 和真实值 $y_i$ 之间有差距。这个差距叫&lt;strong&gt;误差&lt;/strong&gt;（error）：&lt;/p&gt;</description></item></channel></rss>