<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>独角恋歌</title>
  
  
  <link href="https://revival-of-hope.github.io/rss.xml" rel="self"/>
  
  <link href="https://revival-of-hope.github.io/"/>
  <updated>2026-04-14T02:42:50.079Z</updated>
  <id>https://revival-of-hope.github.io/</id>
  
  <author>
    <name>Revival-of-hope</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>python深入</title>
    <link href="https://revival-of-hope.github.io/2026/04/14/dynamic-2026-04-14-python%E6%B7%B1%E5%85%A5/"/>
    <id>https://revival-of-hope.github.io/2026/04/14/dynamic-2026-04-14-python%E6%B7%B1%E5%85%A5/</id>
    <published>2026-04-14T00:00:00.000Z</published>
    <updated>2026-04-14T02:42:50.079Z</updated>
    
    <content type="html"><![CDATA[<p>python最大的魅力在于各种各样的第三方库让它能够适配几乎所有的应用场景</p><ul><li><a href="https://docs.python.org/zh-cn/3.11/tutorial/classes.html">官方文档</a><ul><li>学习python的各种疑难问题都是因为没有去阅读第一手资料</li></ul></li></ul><h1 id="python类"><a class="markdownIt-Anchor" href="#python类"></a> python类</h1><blockquote><p>如果用 C++ 术语来描述的话，类成员（包括数据成员）通常为 public,所有成员函数都为 virtual</p></blockquote><h2 id="super详解"><a class="markdownIt-Anchor" href="#super详解"></a> super()详解</h2><h2 id="self详解"><a class="markdownIt-Anchor" href="#self详解"></a> self详解</h2><p>由于python没有指针,自然也没有this指针,但又需要像cpp一样,提供一个直接访问类内函数或者变量的方法,所以python引入了关键字self.</p><blockquote><p>事实上,上述的说法是不严谨的:</p><blockquote><p>方法的第一个参数常常被命名为 self。 这也不过就是一个约定: <strong>self 这一名称在 Python 中绝对没有特殊含义</strong>。 但是要注意，不遵循此约定会使得你的代码对其他 Python 程序员来说缺乏可读性，而且也可以想像一个 类浏览器 程序的编写可能会依赖于这样的约定。<br />也就是说,我们可以起名叫this,apple,但别人不一定看得懂就是了.</p></blockquote></blockquote><p>我们可以看到,大多数类中的函数都需要至少给出一个参数,也就是self,即使函数中并没有用到self,原因如下:</p><ul><li>Python 的类实例方法在调用时，解释器会自动将实例对象作为第一个位置参数传入,如果你没有写self参数,那么由于该函数没有参数,但却传入了一个参数,就会报错<ul><li>至于为什么会这样,那就是设计上的问题了,只能被动接受.</li><li>这也解释了为什么我们从来没有手动处理self参数过</li></ul></li></ul><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Dog</span>:</span><br><span class="line"></span><br><span class="line">    tricks = []             <span class="comment"># mistaken use of a class variable</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name</span>):</span><br><span class="line">        <span class="variable language_">self</span>.name = name</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">add_trick</span>(<span class="params">self, trick</span>):</span><br><span class="line">        <span class="variable language_">self</span>.tricks.append(trick)</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d = Dog(<span class="string">&#x27;Fido&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>e = Dog(<span class="string">&#x27;Buddy&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d.add_trick(<span class="string">&#x27;roll over&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>e.add_trick(<span class="string">&#x27;play dead&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d.tricks                <span class="comment"># unexpectedly shared by all dogs</span></span><br><span class="line">[<span class="string">&#x27;roll over&#x27;</span>, <span class="string">&#x27;play dead&#x27;</span>]</span><br></pre></td></tr></table></figure><h1 id="关键字与内置函数"><a class="markdownIt-Anchor" href="#关键字与内置函数"></a> 关键字与内置函数</h1><h2 id="with"><a class="markdownIt-Anchor" href="#with"></a> with</h2><h2 id="is和is-not"><a class="markdownIt-Anchor" href="#is和is-not"></a> is和is not</h2><h2 id="yield"><a class="markdownIt-Anchor" href="#yield"></a> yield</h2><h2 id="try-finally-catch-throw"><a class="markdownIt-Anchor" href="#try-finally-catch-throw"></a> try finally catch throw</h2><h2 id="async与await"><a class="markdownIt-Anchor" href="#async与await"></a> async与await</h2><ul><li>于2015年的python3.5引入</li></ul><h2 id="assert"><a class="markdownIt-Anchor" href="#assert"></a> assert</h2><ul><li><a href="https://docs.python.org/3/reference/simple_stmts.html">官方文档</a></li><li><a href="https://www.runoob.com/python3/python3-assert.html">菜鸟教程</a><br /><strong>基础用法</strong></li></ul><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">assert</span> expression</span><br><span class="line"><span class="comment"># 等价于:</span></span><br><span class="line"><span class="keyword">if</span> <span class="literal">__debug__</span>:</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> expression:</span><br><span class="line">        <span class="keyword">raise</span> AssertionError</span><br></pre></td></tr></table></figure><p><strong>报错后输出提示</strong></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">assert</span> (<span class="string">&#x27;linux&#x27;</span> <span class="keyword">in</span> sys.platform), <span class="string">&quot;该代码只能在 Linux 下执行&quot;</span></span><br></pre></td></tr></table></figure><h1 id="常用语法糖"><a class="markdownIt-Anchor" href="#常用语法糖"></a> 常用语法糖</h1><ul><li><a href="https://docs.python.org/3/library/functions.html#classmethod">官方文档</a></li></ul><h2 id="classmethod"><a class="markdownIt-Anchor" href="#classmethod"></a> @classmethod</h2><blockquote><p>Transform a method into a class method.</p></blockquote><p>字面意思,将某个方法实例化到这个类中,从而可以直接调用,不需要使用self来指向实例,也不需要进行类的实例化.<br />但仍然需要填入参数cls(自然可以叫别的名字,cls只是一个习惯上的写法),用来指向这个类</p><ul><li>因为这个类还没完成,就不能用类名.var/method来调用类内变量和函数,故需要通过cls来指向该类.</li></ul><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    bar = <span class="number">1</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">func1</span>(<span class="params">self</span>):  </span><br><span class="line">        <span class="built_in">print</span> (<span class="string">&#x27;foo&#x27;</span>) </span><br><span class="line"><span class="meta">    @classmethod</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">func2</span>(<span class="params">cls</span>):</span><br><span class="line">        <span class="built_in">print</span> (<span class="string">&#x27;func2&#x27;</span>)</span><br><span class="line">        <span class="built_in">print</span> (cls.bar)</span><br><span class="line">        cls().func1()   <span class="comment"># 调用 foo 方法</span></span><br><span class="line"> </span><br><span class="line">A.func2()               <span class="comment"># 不需要实例化</span></span><br></pre></td></tr></table></figure><h2 id="property"><a class="markdownIt-Anchor" href="#property"></a> @property</h2><blockquote><p>Return a <strong>property attribute</strong>.</p></blockquote><p>A property object has <strong>getter, setter, and deleter</strong> methods usable as decorators that create a copy of the property with the corresponding accessor function set to the decorated function. This is best explained with an example:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">C</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>._x = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">x</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;I&#x27;m the &#x27;x&#x27; property.&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>._x</span><br><span class="line"></span><br><span class="line"><span class="meta">    @x.setter</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">x</span>(<span class="params">self, value</span>):</span><br><span class="line">        <span class="variable language_">self</span>._x = value</span><br><span class="line"></span><br><span class="line"><span class="meta">    @x.deleter</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">x</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">del</span> <span class="variable language_">self</span>._x</span><br></pre></td></tr></table></figure><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Parrot</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>._voltage = <span class="number">100000</span></span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">voltage</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Get the current voltage.&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>._voltage</span><br></pre></td></tr></table></figure><ul><li>The @property decorator <strong>turns the voltage() method into a “getter” for a read-only attribute with the same name</strong>, and it sets the docstring for voltage to “Get the current voltage.”</li></ul><p>如果还是看不懂的话,就把property看成是一个将方法转换成类内只读属性的语法糖(可以少写一对括号,并且不可修改),但可以通过setter和deleter来修改.</p><h2 id="dataclass"><a class="markdownIt-Anchor" href="#dataclass"></a> @dataclass</h2><ul><li><a href="https://docs.python.org/zh-cn/3/library/dataclasses.html">官方文档</a></li><li><a href="https://www.cnblogs.com/wang_yb/p/18077397">参考教程</a></li></ul><h3 id="是什么怎么用"><a class="markdownIt-Anchor" href="#是什么怎么用"></a> 是什么,怎么用</h3><p>一般来说,我们定义类时需要这么写来初始化:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CoinTrans</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        <span class="built_in">id</span>: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        symbol: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        price: <span class="built_in">float</span>,</span></span><br><span class="line"><span class="params">        is_success: <span class="built_in">bool</span>,</span></span><br><span class="line"><span class="params">        addrs: <span class="built_in">list</span>,</span></span><br><span class="line"><span class="params">    </span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">        <span class="variable language_">self</span>.<span class="built_in">id</span> = <span class="built_in">id</span></span><br><span class="line">        <span class="variable language_">self</span>.symbol = symbol</span><br><span class="line">        <span class="variable language_">self</span>.price = price</span><br><span class="line">        <span class="variable language_">self</span>.addrs = addrs</span><br><span class="line">        <span class="variable language_">self</span>.is_success = is_success</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    coin_trans = CoinTrans(<span class="string">&quot;id01&quot;</span>, <span class="string">&quot;BTC/USDT&quot;</span>, <span class="string">&quot;71000&quot;</span>, <span class="literal">True</span>, [<span class="string">&quot;0x1111&quot;</span>, <span class="string">&quot;0x2222&quot;</span>])</span><br><span class="line">    <span class="built_in">print</span>(coin_trans)</span><br><span class="line"><span class="comment"># &lt;__main__.CoinTrans object at 0x0000022A891FADD0&gt;</span></span><br></pre></td></tr></table></figure><p>自然,python打印类的时候默认是打印类的内存地址的,这需要我们去单独实现一个打印函数返回类中的各种信息.</p><p>但如果使用dataclass装饰器的话,可以这样写:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> dataclasses <span class="keyword">import</span> dataclass</span><br><span class="line"></span><br><span class="line"><span class="meta">@dataclass</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CoinTrans</span>:</span><br><span class="line">    <span class="built_in">id</span>: <span class="built_in">str</span></span><br><span class="line">    symbol: <span class="built_in">str</span></span><br><span class="line">    price: <span class="built_in">float</span></span><br><span class="line">    is_success: <span class="built_in">bool</span></span><br><span class="line">    addrs: <span class="built_in">list</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    coin_trans = CoinTrans(<span class="string">&quot;id01&quot;</span>, <span class="string">&quot;BTC/USDT&quot;</span>, <span class="string">&quot;71000&quot;</span>, <span class="literal">True</span>, [<span class="string">&quot;0x1111&quot;</span>, <span class="string">&quot;0x2222&quot;</span>])</span><br><span class="line">    <span class="built_in">print</span>(coin_trans)</span><br><span class="line"><span class="comment"># CoinTrans(id=&#x27;id01&#x27;, symbol=&#x27;BTC/USDT&#x27;, price=&#x27;71000&#x27;, is_success=True, addrs=[&#x27;0x1111&#x27;, &#x27;0x2222&#x27;])</span></span><br></pre></td></tr></table></figure><p>不需要写<code>__init__</code>,也不需要写打印函数,就可以直接实现上述的效果.</p><h1 id="类型注释"><a class="markdownIt-Anchor" href="#类型注释"></a> 类型注释</h1><p>类型注释在PEP484中引入,也就是2015年的python3.5,从而在很大程度上解决了python动态类型带来的混乱.</p><h2 id="简单的类型注释"><a class="markdownIt-Anchor" href="#简单的类型注释"></a> 简单的类型注释</h2><p>如下方代码所示,类型注释有两种格式:</p><ol><li><code>变量名: 类型</code>: 用于提示参数的类型</li><li><code>函数末尾 -&gt; 类型:</code>: 用于提示函数的返回值类型</li></ol><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">surface_area_of_cube</span>(<span class="params">edge_length: <span class="built_in">float</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="keyword">return</span> <span class="string">f&quot;The surface area of the cube is <span class="subst">&#123;<span class="number">6</span> * edge_length ** <span class="number">2</span>&#125;</span>.&quot;</span></span><br></pre></td></tr></table></figure><p>大多数类型注释都不需要导入任何库即可使用,下面是一个常用的系统直接支持的类型注释表格:</p><table><thead><tr><th style="text-align:left">分类</th><th style="text-align:left">语法示例</th><th style="text-align:left">说明</th><th style="text-align:left">适用版本</th></tr></thead><tbody><tr><td style="text-align:left"><strong>基础标量</strong></td><td style="text-align:left"><code>var: int</code>, <code>var: float</code>, <code>var: bool</code>, <code>var: str</code>, <code>var: bytes</code></td><td style="text-align:left">整数、浮点、布尔、字符串、字节流</td><td style="text-align:left">全版本</td></tr><tr><td style="text-align:left"><strong>空值/无返回</strong></td><td style="text-align:left"><code>def fn() -&gt; None:</code></td><td style="text-align:left">表示函数没有返回值</td><td style="text-align:left">全版本</td></tr><tr><td style="text-align:left"><strong>列表</strong></td><td style="text-align:left"><code>var: list[int]</code></td><td style="text-align:left">元素全为整数的列表</td><td style="text-align:left">3.9+</td></tr><tr><td style="text-align:left"><strong>字典</strong></td><td style="text-align:left"><code>var: dict[str, int]</code></td><td style="text-align:left">键为字符串、值为整数的字典</td><td style="text-align:left">3.9+</td></tr><tr><td style="text-align:left"><strong>元组 (定长)</strong></td><td style="text-align:left"><code>var: tuple[int, str]</code></td><td style="text-align:left">包含一个整数和一个字符串的二元组</td><td style="text-align:left">3.9+</td></tr><tr><td style="text-align:left"><strong>元组 (变长)</strong></td><td style="text-align:left"><code>var: tuple[int, ...]</code></td><td style="text-align:left">包含任意数量整数的元组</td><td style="text-align:left">3.9+</td></tr><tr><td style="text-align:left"><strong>集合</strong></td><td style="text-align:left"><code>var: set[str]</code></td><td style="text-align:left">元素全为字符串的集合</td><td style="text-align:left">3.9+</td></tr><tr><td style="text-align:left"><strong>联合类型</strong></td><td style="text-align:left"><code>var: int | str</code></td><td style="text-align:left">变量可以是整数或字符串 (Union)</td><td style="text-align:left">3.10+</td></tr><tr><td style="text-align:left"><strong>可选类型</strong></td><td style="text-align:left"><code>var: str | None</code></td><td style="text-align:left">变量可以是字符串或为空 (Optional)</td><td style="text-align:left">3.10+</td></tr><tr><td style="text-align:left"><strong>类对象</strong></td><td style="text-align:left"><code>var: type[MyClass]</code></td><td style="text-align:left">变量是类本身，而不是类的实例</td><td style="text-align:left">3.9+</td></tr><tr><td style="text-align:left"><strong>自定义类</strong></td><td style="text-align:left"><code>var: MyClass</code></td><td style="text-align:left">变量是该类的实例</td><td style="text-align:left">全版本</td></tr><tr><td style="text-align:left"><strong>双向队列</strong></td><td style="text-align:left"><code>var: collections.deque[int]</code></td><td style="text-align:left">需 Python 3.9+，虽在 collections 但无需 import typing</td><td style="text-align:left">3.9+</td></tr><tr><td style="text-align:left"><strong>切片</strong></td><td style="text-align:left"><code>var: slice</code></td><td style="text-align:left">内存索引切片对象</td><td style="text-align:left">全版本</td></tr><tr><td style="text-align:left"><strong>范围</strong></td><td style="text-align:left"><code>var: range</code></td><td style="text-align:left">迭代范围对象</td><td style="text-align:left">全版本</td></tr><tr><td style="text-align:left"><strong>枚举迭代</strong></td><td style="text-align:left"><code>var: enumerate</code></td><td style="text-align:left">枚举对象</td><td style="text-align:left">全版本</td></tr></tbody></table><p>这些简单类型已经可以涵盖大多数应用场景了,如果需要使用更高级的类型注释功能,就需要导入typing库来使用.</p><h2 id="typing系统库"><a class="markdownIt-Anchor" href="#typing系统库"></a> typing系统库</h2><ul><li><a href="https://docs.python.org/zh-cn/3/library/typing.html">官方文档</a></li></ul><h3 id="类型别名-type"><a class="markdownIt-Anchor" href="#类型别名-type"></a> 类型别名: type</h3><p>类型别名是使用 <code>type 简写 = 复杂类型</code> 语句来定义的，它将创建一个 <code>TypeAliasType</code> 的实例.</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">type</span> Vector = <span class="built_in">list</span>[<span class="built_in">float</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">scale</span>(<span class="params">scalar: <span class="built_in">float</span>, vector: Vector</span>) -&gt; Vector:</span><br><span class="line">    <span class="keyword">return</span> [scalar * num <span class="keyword">for</span> num <span class="keyword">in</span> vector]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 通过类型检查；浮点数列表是合格的 Vector。</span></span><br><span class="line">new_vector = scale(<span class="number">2.0</span>, [<span class="number">1.0</span>, -<span class="number">4.2</span>, <span class="number">5.4</span>])</span><br></pre></td></tr></table></figure><h3 id="any-支持任何类型"><a class="markdownIt-Anchor" href="#any-支持任何类型"></a> Any: 支持任何类型</h3><p>不使用类型注释时,所有的变量和返回值都被视为Any,用Any类型注解的变量不会在类型检查时报错.</p><p>在现代python项目中,有三种情况会用到它:</p><ol><li>某一个变量支持不同类型的值</li><li>你不知道它应该是什么值</li><li>无论它是什么值都无所谓</li></ol><p>显然,如果你全用Any的话也可以通过类型检查,但这样就没有意义了.</p><h1 id="格式检查"><a class="markdownIt-Anchor" href="#格式检查"></a> 格式检查</h1><h2 id="ruff"><a class="markdownIt-Anchor" href="#ruff"></a> ruff</h2><h3 id="是什么怎么用-2"><a class="markdownIt-Anchor" href="#是什么怎么用-2"></a> 是什么,怎么用</h3><p>ruff是用rust编写的python格式检查库,可以迅速将py文件规范化,速度比一版的格式检查库都要快很多.</p><p>要使用ruff,我们需要先将它加入到当前项目中:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uv add --dev ruff</span><br></pre></td></tr></table></figure><p>之后再运行以下命令就可以检查该项目是否规范</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uv run ruff check</span><br></pre></td></tr></table></figure><h3 id="基本用法"><a class="markdownIt-Anchor" href="#基本用法"></a> 基本用法</h3><p><strong>ruff check</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ruff check                  <span class="comment"># Lint files in the current directory.</span></span><br><span class="line">ruff check --fix            <span class="comment"># Lint files in the current directory and fix any fixable errors.</span></span><br><span class="line">ruff check --watch          <span class="comment"># Lint files in the current directory and re-lint on change.</span></span><br><span class="line">ruff check path/to/code/    <span class="comment"># Lint files in `path/to/code`.</span></span><br></pre></td></tr></table></figure><h1 id="测试"><a class="markdownIt-Anchor" href="#测试"></a> 测试</h1><h2 id="如何写测试"><a class="markdownIt-Anchor" href="#如何写测试"></a> 如何写测试</h2><ul><li><a href="https://docs.pytest.org/en/stable/explanation/anatomy.html#test-anatomy">pytest文档</a><ul><li>写的很好,所以全文摘录</li></ul></li></ul><blockquote><p>In the simplest terms, a test is meant to look at the result of a particular behavior, and make sure that result aligns with what you would expect. Behavior is not something that can be empirically measured, which is why writing tests can be challenging.</p></blockquote><p>“Behavior” is the way in which some system acts in response to a particular situation and/or stimuli. But exactly how or why something is done is not quite as important as what was done.</p><p>You can think of a test as being broken down into four steps:</p><ol><li>Arrange</li><li>Act</li><li>Assert</li><li>Cleanup</li></ol><p>Arrange is where we prepare everything for our test. This means pretty much everything except for the “act”. It’s lining up the dominoes so that the act can do its thing in one, state-changing step. This can mean preparing objects, starting/killing services, entering records into a database, or even things like defining a URL to query, generating some credentials for a user that doesn’t exist yet, or just waiting for some process to finish.</p><p>Act is the singular, state-changing action that kicks off the behavior we want to test. This behavior is what carries out the changing of the state of the system under test (SUT), and it’s the resulting changed state that we can look at to make a judgement about the behavior. This typically takes the form of a function/method call.</p><p>Assert is where we look at that resulting state and check if it looks how we’d expect after the dust has settled. It’s where we gather evidence to say the behavior does or does not align with what we expect. The assert in our test is where we take that measurement/observation and apply our judgement to it. If something should be green, we’d say <code>assert thing == &quot;green&quot;</code>.</p><p>Cleanup is where the test picks up after itself, so other tests aren’t being accidentally influenced by it.</p><p>At its core, the test is ultimately the act and assert steps, with the arrange step only providing the context. Behavior exists between act and assert.</p><p>也就是说,在写测试之前,我们需要设计一些能够体现代码功能或者bug的操作,放入测试函数中,然后执行函数并检测输出是否与预期的一致,并保证测试结果彼此之间互不影响.</p><h2 id="pytest"><a class="markdownIt-Anchor" href="#pytest"></a> pytest</h2><h3 id="intro"><a class="markdownIt-Anchor" href="#intro"></a> Intro</h3><p>pytest在python测试库中占据了统治地位,而python系统库自带的unittest就显得逊色很多了,故测试库里我只介绍pytest.</p><p>我们先创建一个<code>test_parts.py</code>文件,填入以下代码:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">func</span>(<span class="params">x</span>):</span><br><span class="line">    <span class="keyword">return</span> x + <span class="number">1</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_answer</span>():</span><br><span class="line">    <span class="keyword">assert</span> func(<span class="number">3</span>) == <span class="number">5</span></span><br></pre></td></tr></table></figure><ol><li>如果是全局安装过,或者在虚拟环境安装了的话,只要在终端输入<code>pytest</code>即可</li><li>如果使用uv管理的话,只需输入以下命令:</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uv run pytest</span><br></pre></td></tr></table></figure><p>该命令将运行当前目录并递归运行子目录中所有形式为 test_*.py 或 *_test.py 的文件.</p><ul><li>如果文件中的代码块不是全局的而是位于函数中,则需要函数名带有类似的<code>test_*()</code>格式</li><li>如果把函数放在类里面,则需要在类名前面加上<code>Test</code>,否则该类被整个跳过<br />来看看输出结果:</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">================================= <span class="built_in">test</span> session starts ==================================</span><br><span class="line">platform win32 -- Python 3.13.7, pytest-9.0.2, pluggy-1.6.0</span><br><span class="line">rootdir: xxx</span><br><span class="line">configfile: pyproject.toml</span><br><span class="line">plugins: anyio-4.12.1</span><br><span class="line">collected 1 item                                                                        </span><br><span class="line"></span><br><span class="line">test_parts.py F                                                                   [100%]</span><br><span class="line"></span><br><span class="line">======================================= FAILURES ======================================= </span><br><span class="line">_____________________________________ test_answer ______________________________________ </span><br><span class="line"></span><br><span class="line">    def test_answer():</span><br><span class="line">&gt;       assert func(3) == 5</span><br><span class="line">E       assert 4 == 5</span><br><span class="line">E        +  <span class="built_in">where</span> 4 = func(3)</span><br><span class="line"></span><br><span class="line">test_parts.py:7: AssertionError</span><br><span class="line">=============================== short <span class="built_in">test</span> summary info ================================ </span><br><span class="line">FAILED test_parts.py::test_answer - assert 4 == 5</span><br><span class="line">================================== 1 failed <span class="keyword">in</span> 0.10s =================================== </span><br></pre></td></tr></table></figure><ul><li>[100%] 指的是运行所有测试用例的总体进度</li></ul><h3 id="使用pytest语法糖"><a class="markdownIt-Anchor" href="#使用pytest语法糖"></a> 使用pytest语法糖</h3><ul><li><a href="https://docs.pytest.org/en/stable/explanation/fixtures.html">官方教程</a></li><li><a href="https://www.cnblogs.com/hiyong/p/14163280.html">参考教程</a></li></ul><p>用<code>@pytest.fixture()</code>修饰的函数在文件内部可以直接被其他函数调用名字并获取返回值,具体如下所示:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;登录&quot;</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Test_Demo</span>():</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_case1</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\n开始执行测试用例1&quot;</span>)</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">1</span> + <span class="number">1</span> == <span class="number">2</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_case2</span>(<span class="params">self, login</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\n开始执行测试用例2&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(login)</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">2</span> + login == <span class="number">10</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_case3</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\n开始执行测试用例3&quot;</span>)</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">99</span> + <span class="number">1</span> == <span class="number">100</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    pytest.main()</span><br></pre></td></tr></table></figure><ul><li>login()在这里相当于一个测试工具函数</li></ul><h3 id="语法糖参数-autouse"><a class="markdownIt-Anchor" href="#语法糖参数-autouse"></a> 语法糖参数: autouse</h3><p>默认情况下,被<code>@pytest.fixture()</code>修饰的工具函数只在被请求时才被加载,如果没有任何一个测试用例用到这个函数,它就永远不会运行,也就是懒加载(lazy loading).</p><p>乍一看挺好的,但是如果测试中有大量的测试用例更改了数据库,我们不希望一个个去撤销数据库更改后还原,不仅让代码变得臃肿,而且很累.</p><p>所以,我们使用<code>autouse=True</code>来让被修饰的函数强制生效,而不管测试用例有没有调用这个函数.</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">autouse=<span class="literal">True</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;登录...&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Test_Demo</span>():</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_case1</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\n开始执行测试用例1&quot;</span>)</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">1</span> + <span class="number">1</span> == <span class="number">2</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_case2</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\n开始执行测试用例2&quot;</span>)</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">2</span> + <span class="number">8</span> == <span class="number">10</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_case3</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\n开始执行测试用例3&quot;</span>)</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">99</span> + <span class="number">1</span> == <span class="number">100</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    pytest.main()</span><br></pre></td></tr></table></figure><p><strong>终端输出</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">登录...</span><br><span class="line">PASSED                    [ 33%]</span><br><span class="line">开始执行测试用例1</span><br><span class="line">登录...</span><br><span class="line">PASSED                    [ 66%]</span><br><span class="line">开始执行测试用例2</span><br><span class="line">登录...</span><br><span class="line">PASSED                    [100%]</span><br><span class="line">开始执行测试用例3</span><br></pre></td></tr></table></figure><h3 id="语法糖参数-scope"><a class="markdownIt-Anchor" href="#语法糖参数-scope"></a> 语法糖参数: scope</h3><blockquote><p>fixture作用范围可以为module、class、session和function，默认作用域为function。</p></blockquote><p>其核心逻辑是：<strong>在指定作用域内，只执行一次初始化，然后所有人共享这个缓存的对象。</strong></p><h4 id="function函数级"><a class="markdownIt-Anchor" href="#function函数级"></a> function（函数级）</h4><ul><li><strong>频率</strong>：最高。</li><li><strong>含义</strong>：每个测试函数执行前，都会重新运行一遍 Fixture。</li><li><strong>场景</strong>：你需要每个测试用例都拥有一个全新的、干净的数据副本，防止 A 用例的操作影响到 B 用例。</li></ul><h4 id="class类级"><a class="markdownIt-Anchor" href="#class类级"></a> class（类级）</h4><ul><li><strong>频率</strong>：中。</li><li><strong>含义</strong>：如果一个测试类（<code>class TestXXX</code>）里有 10 个测试方法，这个 Fixture 只会在进入该类时运行一次，10 个方法共用同一个对象。</li><li><strong>场景</strong>：测试类中的所有方法都需要同一个昂贵的对象（如一个已经打开的浏览器窗口）。</li></ul><h4 id="module模块级"><a class="markdownIt-Anchor" href="#module模块级"></a> module（模块级）</h4><ul><li><strong>频率</strong>：低。</li><li><strong>含义</strong>：在一个 <code>.py</code> 文件中，无论有多少个类或函数，Fixture 只在该文件开始时运行一次。</li><li><strong>场景</strong>：同一个文件内的测试都依赖于同一个外部配置函数。</li></ul><h4 id="session会话级"><a class="markdownIt-Anchor" href="#session会话级"></a> session（会话级）</h4><ul><li><strong>频率</strong>：最低。</li><li><strong>含义</strong>：当你运行 <code>pytest</code> 命令开始，到所有测试结束，Fixture 只运行一次。</li><li><strong>场景</strong>：启动整个项目的测试数据库、初始化大型算法模型或全局 API 客户端。</li></ul><h3 id="yield关键字在pytest中的使用"><a class="markdownIt-Anchor" href="#yield关键字在pytest中的使用"></a> yield关键字在pytest中的使用</h3><p>在yield关键字之前的代码在测试函数开始运行之前执行，yield之后的代码在函数运行结束后执行</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;登录&quot;</span>)</span><br><span class="line">    <span class="keyword">yield</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;退出登录&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Test_Demo</span>():</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_case1</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\n开始执行测试用例1&quot;</span>)</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">1</span> + <span class="number">1</span> == <span class="number">2</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_case2</span>(<span class="params">self, login</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\n开始执行测试用例2&quot;</span>)</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">2</span> + <span class="number">8</span> == <span class="number">10</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_case3</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\n开始执行测试用例3&quot;</span>)</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">99</span> + <span class="number">1</span> == <span class="number">100</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    pytest.main()</span><br></pre></td></tr></table></figure><p><strong>终端输出</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">PASSED                      [ 33%]</span><br><span class="line">开始执行测试用例1</span><br><span class="line">登录</span><br><span class="line">PASSED                      [ 66%]</span><br><span class="line">开始执行测试用例2</span><br><span class="line">退出登录</span><br><span class="line">PASSED                      [100%]</span><br><span class="line">开始执行测试用例3</span><br></pre></td></tr></table></figure><h3 id="conftestpy文件"><a class="markdownIt-Anchor" href="#conftestpy文件"></a> conftest.py文件</h3><h4 id="自动识别机制"><a class="markdownIt-Anchor" href="#自动识别机制"></a> 自动识别机制</h4><p>只要文件名为 <code>conftest.py</code>，pytest 会在启动时自动扫描并加载它。你<strong>不需要</strong>在测试文件中显式 <code>import</code> 它。</p><h4 id="作用范围层级继承"><a class="markdownIt-Anchor" href="#作用范围层级继承"></a> 作用范围（层级继承）</h4><p><code>conftest.py</code> 的作用范围遵循<strong>目录树结构</strong>：</p><ul><li><strong>根目录</strong>：如果放在项目根目录，其定义的配置对整个项目生效。</li><li><strong>子目录</strong>：如果放在某个子目录（如 <code>tests/unit/conftest.py</code>），则仅对该目录及其子目录下的测试文件生效。</li><li><strong>优先级</strong>：子目录中的 <code>conftest.py</code> 会重写或扩展父目录中的同名配置。</li></ul><h4 id="核心用途"><a class="markdownIt-Anchor" href="#核心用途"></a> 核心用途</h4><p>它本质上是一个<strong>本地插件库</strong>，主要处理以下三类任务：</p><ul><li><strong>Fixtures（固件）共享</strong>：<br />在 <code>conftest.py</code> 中定义的 <code>@pytest.fixture</code> 可以被该目录下的所有测试用例直接通过参数名调用,不用再进行导入</li><li><strong>Hook函数自定义</strong>：<br />可以修改 pytest 的内部行为。例如 <code>pytest_runtest_setup</code>（在测试开始前执行）或 <code>pytest_addoption</code>（添加自定义命令行参数）。</li><li><strong>外部插件加载</strong>：<br />通过 <code>pytest_plugins = [&quot;plugin1&quot;, &quot;plugin2&quot;]</code> 在特定目录下引入额外的插件。</li></ul><h4 id="关键限制"><a class="markdownIt-Anchor" href="#关键限制"></a> 关键限制</h4><ul><li><strong>不可跨目录手动导入</strong>：永远不要尝试 <code>from conftest import ...</code>。如果这样做，会破坏 pytest 的加载机制，可能导致配置冲突或重复初始化。</li><li><strong>文件命名固定</strong>：必须严格命名为 <code>conftest.py</code>，否则 pytest 会将其视为普通的 Python 模块</li></ul><h3 id="实战"><a class="markdownIt-Anchor" href="#实战"></a> 实战</h3><p>事实上,上述的内容基本涵盖了我们所需的pytest知识了,我们现在拿fastapi模板项目中的test部分来做例子,深入探讨一下pytest的实际应用</p><ul><li>看来我这个博客可以靠着fastapi啃很久了</li></ul><p>先翻到后端的Readme:</p><blockquote><p>If your stack is already up and you just want to run the tests, you can use:</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose <span class="built_in">exec</span> backend bash scripts/tests-start.sh</span><br></pre></td></tr></table></figure><p>看来这就是测试脚本了,让我们看看<strong><a href="http://tests-start.sh">tests-start.sh</a></strong>的内容:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#! /usr/bin/env bash</span></span><br><span class="line"><span class="built_in">set</span> -e </span><br><span class="line"><span class="comment"># 立即退出模式,脚本中任何一条命令执行失败将停止脚本继续执行</span></span><br><span class="line"><span class="built_in">set</span> -x</span><br><span class="line"><span class="comment"># 调试模式: 执行每条命令前先将命令打印到终端</span></span><br><span class="line">python app/tests_pre_start.py</span><br><span class="line"><span class="comment"># 执行预启动脚本</span></span><br><span class="line">bash scripts/test.sh <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br><span class="line"><span class="comment"># 执行test.sh脚本</span></span><br><span class="line"><span class="comment"># &quot;$@&quot;: 将当前脚本用到的参数传给test.sh脚本</span></span><br><span class="line"><span class="comment"># 如果我执行./tests-start.sh --verbose --fail-fast</span></span><br><span class="line"><span class="comment"># 那么执行test.sh时也会带有--verbose --fail-fast参数</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>那我们再看看<strong><a href="http://test.sh">test.sh</a></strong>脚本</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"><span class="built_in">set</span> -x</span><br><span class="line"></span><br><span class="line">coverage run -m pytest tests/</span><br><span class="line"><span class="comment"># 执行tests文件夹下的测试</span></span><br><span class="line">coverage report</span><br><span class="line"><span class="comment"># 在终端输出测试信息</span></span><br><span class="line">coverage html --title <span class="string">&quot;<span class="variable">$&#123;@-coverage&#125;</span>&quot;</span></span><br><span class="line"><span class="comment"># 生成可视化html报告,通常位于 htmlcov/ 目录</span></span><br></pre></td></tr></table></figure><p>也就是说,到头来还是用pytest执行了tests文件夹里的测试,只不过多了一些其他的包装而已.</p><p><img src="/images/2026-03-31/PixPin_2026-04-01_18-37-20.webp" alt="alt text" /></p><ul><li>这就是全部的测试文件了,还是很多的,这说明测试并非是无关轻重的代码部分</li></ul><p>先来看看最外层的conftest.py文件:<br /><strong><a href="http://conftest.py">conftest.py</a></strong></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> collections.abc <span class="keyword">import</span> Generator</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"><span class="keyword">from</span> fastapi.testclient <span class="keyword">import</span> TestClient</span><br><span class="line"><span class="keyword">from</span> sqlmodel <span class="keyword">import</span> Session, delete</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> app.core.config <span class="keyword">import</span> settings</span><br><span class="line"><span class="keyword">from</span> app.core.db <span class="keyword">import</span> engine, init_db</span><br><span class="line"><span class="keyword">from</span> app.main <span class="keyword">import</span> app</span><br><span class="line"><span class="keyword">from</span> app.models <span class="keyword">import</span> Item, User</span><br><span class="line"><span class="keyword">from</span> tests.utils.user <span class="keyword">import</span> authentication_token_from_email</span><br><span class="line"><span class="keyword">from</span> tests.utils.utils <span class="keyword">import</span> get_superuser_token_headers</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">&quot;session&quot;</span>, autouse=<span class="literal">True</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">db</span>() -&gt; Generator[Session, <span class="literal">None</span>, <span class="literal">None</span>]:</span><br><span class="line">    <span class="keyword">with</span> Session(engine) <span class="keyword">as</span> session:</span><br><span class="line">        init_db(session)</span><br><span class="line">        <span class="keyword">yield</span> session</span><br><span class="line">        statement = delete(Item)</span><br><span class="line">        session.execute(statement)</span><br><span class="line">        statement = delete(User)</span><br><span class="line">        session.execute(statement)</span><br><span class="line">        session.commit()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># @pytest.fixture(scope=&quot;module&quot;)</span></span><br><span class="line"><span class="comment"># def client() -&gt; Generator[TestClient, None, None]:</span></span><br><span class="line"><span class="comment">#     with TestClient(app) as c:</span></span><br><span class="line"><span class="comment">#         yield c</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># @pytest.fixture(scope=&quot;module&quot;)</span></span><br><span class="line"><span class="comment"># def superuser_token_headers(client: TestClient) -&gt; dict[str, str]:</span></span><br><span class="line"><span class="comment">#     return get_superuser_token_headers(client)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># @pytest.fixture(scope=&quot;module&quot;)</span></span><br><span class="line"><span class="comment"># def normal_user_token_headers(client: TestClient, db: Session) -&gt; dict[str, str]:</span></span><br><span class="line"><span class="comment">#     return authentication_token_from_email(</span></span><br><span class="line"><span class="comment">#         client=client, email=settings.EMAIL_TEST_USER, db=db</span></span><br><span class="line"><span class="comment">#     )</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>第一个函数<code>db</code>在整个测试开始时启动一次,将数据库初始化,并删除Item和User关系表,从而清空所有数据;至于其他被注释掉的函数都是给其他测试模块用的工具函数</p><ul><li>换句话说,这个测试只能在开发环境做,一旦部署好了就不要再搞测试了</li></ul><p>除了utils文件夹下的文件都是工具函数外,其余的文件基本都是以test_前缀打头的pytest文件了.我们只挑一个最精华的文件来看:<br /><strong>test_items.py</strong></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> uuid</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> fastapi.testclient <span class="keyword">import</span> TestClient</span><br><span class="line"><span class="keyword">from</span> sqlmodel <span class="keyword">import</span> Session</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> app.core.config <span class="keyword">import</span> settings</span><br><span class="line"><span class="keyword">from</span> tests.utils.item <span class="keyword">import</span> create_random_item</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_create_item</span>(<span class="params"></span></span><br><span class="line"><span class="params">    client: TestClient, superuser_token_headers: <span class="built_in">dict</span>[<span class="built_in">str</span>, <span class="built_in">str</span>]</span></span><br><span class="line"><span class="params"></span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">    data = &#123;<span class="string">&quot;title&quot;</span>: <span class="string">&quot;Foo&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;Fighters&quot;</span>&#125;</span><br><span class="line">    response = client.post(</span><br><span class="line">        <span class="string">f&quot;<span class="subst">&#123;settings.API_V1_STR&#125;</span>/items/&quot;</span>,</span><br><span class="line">        headers=superuser_token_headers,</span><br><span class="line">        json=data,</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">assert</span> response.status_code == <span class="number">200</span></span><br><span class="line">    content = response.json()</span><br><span class="line">    <span class="keyword">assert</span> content[<span class="string">&quot;title&quot;</span>] == data[<span class="string">&quot;title&quot;</span>]</span><br><span class="line">    <span class="keyword">assert</span> content[<span class="string">&quot;description&quot;</span>] == data[<span class="string">&quot;description&quot;</span>]</span><br><span class="line">    <span class="keyword">assert</span> <span class="string">&quot;id&quot;</span> <span class="keyword">in</span> content</span><br><span class="line">    <span class="keyword">assert</span> <span class="string">&quot;owner_id&quot;</span> <span class="keyword">in</span> content</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_read_item</span>(<span class="params"></span></span><br><span class="line"><span class="params">    client: TestClient, superuser_token_headers: <span class="built_in">dict</span>[<span class="built_in">str</span>, <span class="built_in">str</span>], db: Session</span></span><br><span class="line"><span class="params"></span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">    item = create_random_item(db)</span><br><span class="line">    response = client.get(</span><br><span class="line">        <span class="string">f&quot;<span class="subst">&#123;settings.API_V1_STR&#125;</span>/items/<span class="subst">&#123;item.<span class="built_in">id</span>&#125;</span>&quot;</span>,</span><br><span class="line">        headers=superuser_token_headers,</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">assert</span> response.status_code == <span class="number">200</span></span><br><span class="line">    content = response.json()</span><br><span class="line">    <span class="keyword">assert</span> content[<span class="string">&quot;title&quot;</span>] == item.title</span><br><span class="line">    <span class="keyword">assert</span> content[<span class="string">&quot;description&quot;</span>] == item.description</span><br><span class="line">    <span class="keyword">assert</span> content[<span class="string">&quot;id&quot;</span>] == <span class="built_in">str</span>(item.<span class="built_in">id</span>)</span><br><span class="line">    <span class="keyword">assert</span> content[<span class="string">&quot;owner_id&quot;</span>] == <span class="built_in">str</span>(item.owner_id)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_read_item_not_found</span>(<span class="params"></span></span><br><span class="line"><span class="params">    client: TestClient, superuser_token_headers: <span class="built_in">dict</span>[<span class="built_in">str</span>, <span class="built_in">str</span>]</span></span><br><span class="line"><span class="params"></span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">    response = client.get(</span><br><span class="line">        <span class="string">f&quot;<span class="subst">&#123;settings.API_V1_STR&#125;</span>/items/<span class="subst">&#123;uuid.uuid4()&#125;</span>&quot;</span>,</span><br><span class="line">        headers=superuser_token_headers,</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">assert</span> response.status_code == <span class="number">404</span></span><br><span class="line">    content = response.json()</span><br><span class="line">    <span class="keyword">assert</span> content[<span class="string">&quot;detail&quot;</span>] == <span class="string">&quot;Item not found&quot;</span></span><br></pre></td></tr></table></figure><ul><li>第一个函数<code>test_create_item</code>模拟管理员创建一个测试数据,并判断收到的响应报文中的数据是否相同.</li><li>第二个函数<code>test_read_item</code>模拟管理员在创建一个随机物品后,判断使用get请求是否正常.</li><li>第三个函数<code>test_read_item_not_found</code>模拟管理员直接访问一个不存在的物品,需要注意的是,这里的uuid4方法有可能产生恰好与之前测试生成相同的物品id,而我们的数据库清空是只在开始运行时执行,而不是每次执行测试函数都执行,因此有极低的概率会返回200状态码导致测试失败</li></ul><p>后面的函数都大差不差了,基本就是构造测试数据,使用client模拟前端进行访问,并判断响应是否正常,但是有一个问题:既然要模拟前端访问,自然需要后端能够响应,才能执行测试,但根据前面的脚本分析,我们仅仅是用了pytest启动test文件夹中的测试而已,并没有真正的启动后端,那么测试是如何执行的呢?</p><p>答案在最开始的<code>conftest.py</code>中,我们的测试函数中都引入了<code>client: TestClient</code>这个工具函数,而这个函数在<code>conftest.py</code>中早就定义好了:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> app.main <span class="keyword">import</span> app</span><br><span class="line"><span class="keyword">from</span> fastapi.testclient <span class="keyword">import</span> TestClient</span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">&quot;module&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">client</span>() -&gt; Generator[TestClient, <span class="literal">None</span>, <span class="literal">None</span>]:</span><br><span class="line">    <span class="keyword">with</span> TestClient(app) <span class="keyword">as</span> c:</span><br><span class="line">        <span class="keyword">yield</span> c</span><br></pre></td></tr></table></figure><p>这里的TestClient方法的作用域为模块级,即只在该文件的测试开始执行时调用一次,使用了main.py中的app对象:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 真实后端里的main.py</span></span><br><span class="line"></span><br><span class="line">app = FastAPI(</span><br><span class="line">    title=settings.PROJECT_NAME,</span><br><span class="line">    openapi_url=<span class="string">f&quot;<span class="subst">&#123;settings.API_V1_STR&#125;</span>/openapi.json&quot;</span>,</span><br><span class="line">    generate_unique_id_function=custom_generate_unique_id,</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>也就是说,我们启动了后端中的关键部分,从而实现对后端的整体调用,测试整个应用的运行是否正常.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;python最大的魅力在于各种各样的第三方库让它能够适配几乎所有的应用场景&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python.org/zh-cn/3.11/tutorial/classes.html&quot;&gt;官方文档&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;学</summary>
      
    
    
    
    <category term="动态更新" scheme="https://revival-of-hope.github.io/categories/%E5%8A%A8%E6%80%81%E6%9B%B4%E6%96%B0/"/>
    
    
    <category term="python" scheme="https://revival-of-hope.github.io/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>2026-04-14 从零开始的python爬虫</title>
    <link href="https://revival-of-hope.github.io/2026/04/14/python-2026-04-14-%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84python%E7%88%AC%E8%99%AB/"/>
    <id>https://revival-of-hope.github.io/2026/04/14/python-2026-04-14-%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84python%E7%88%AC%E8%99%AB/</id>
    <published>2026-04-14T00:00:00.000Z</published>
    <updated>2026-04-14T02:42:31.704Z</updated>
    
    <content type="html"><![CDATA[<h1 id="爬虫"><a class="markdownIt-Anchor" href="#爬虫"></a> 爬虫</h1><p>和机器学习一样,我第一次学习python爬虫是没有任何成果的,一开始是听说有这么个东西,就去zlib上随便下了本参考书,由于参考书是十年前的,因此使用了很多老掉牙的库和奇奇怪怪的语法,再加上当时水平有限,根本无法复现,于是就浅尝辄止了.</p><p>但现在,我想要试着用爬虫找到合适的招聘数据用来为以后的暑期实习和秋招服务,所以又把这门技术捡起来从零开始学了.</p><ul><li>参考文章: 菜鸟教程以及官方文档</li></ul><h2 id="爬虫概念"><a class="markdownIt-Anchor" href="#爬虫概念"></a> 爬虫概念</h2><blockquote><p><a href="https://en.wikipedia.org/wiki/Web_crawler">wiki</a><br />Web crawler, sometimes called a spider or spiderbot and often shortened to crawler, is an Internet bot that systematically browses the World Wide Web and that is typically operated by <strong>search engines</strong> for the purpose of Web indexing (web spidering)<br />实际上,只要一个自动化程序做了下列的某一件事情,就可以认定为爬虫:</p></blockquote><ul><li>获取web资源</li><li>模拟浏览器/用户行为</li><li>批量获取数据</li></ul><p>这几个操作基本涵盖了抢票脚本,pdf下载,训练数据爬取等一系列常见的爬虫情景.</p><p>最常见的爬虫无疑就是搜索引擎了,这些巨无霸爬虫不间断的访问数以千万计的网站,并给数据做好归类和索引.</p><h2 id="爬虫历史"><a class="markdownIt-Anchor" href="#爬虫历史"></a> 爬虫历史</h2><p>翻遍全网,我确实找不到一个能够好好讲讲从爬虫概念的诞生到最新爬虫框架应用的博客文章(难道谈这个是犯法吗!)</p><p>遗憾的是,我目前没有做这方面梳理的打算,等我真正闲下来再写吧,毕竟随便一想就知道这需要大量的检索和查证.</p><h2 id="python爬虫工具时间线"><a class="markdownIt-Anchor" href="#python爬虫工具时间线"></a> python爬虫工具时间线</h2><ul><li><p>04年: <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">BeautifulSoup</a>,绝对的老资历,爬虫入门书十本有十本会谈到它</p></li><li><p>08年: <a href="https://docs.scrapy.org/en/latest/">Scrapy</a>,工业级别的异步爬虫框架,也是推荐的爬虫入门库</p></li><li><p>08年: <a href="https://www.selenium.dev/documentation/">Selenium</a>,浏览器自动化鼻祖, 物理驱动 WebDriver 模拟真实用户操作</p></li><li><p>12年: <a href="https://requests.readthedocs.io/en/latest/">Requests</a>,物理简化 HTTP 请求逻辑</p></li><li><p>20年: <a href="https://github.com/microsoft/playwright">Playwright</a>,微软出品,物理支持多驱动与自动等待,现代爬虫框架</p></li><li><p>但我不打算按照时间顺序来,而是按照难易程度来讲解😊</p></li></ul><h2 id="requests库学习"><a class="markdownIt-Anchor" href="#requests库学习"></a> requests库学习</h2><ul><li><a href="https://requests.readthedocs.io/en/latest/">官方文档</a><ul><li>质量比较高,深入浅出</li></ul></li></ul><h3 id="是什么怎么用"><a class="markdownIt-Anchor" href="#是什么怎么用"></a> 是什么,怎么用</h3><blockquote><p>Requests is an <strong>elegant and simple HTTP library</strong> for Python, built for human beings.</p></blockquote><ul><li>官方的宣言非常简单明了</li></ul><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line">r = requests.get(<span class="string">&#x27;https://api.github.com/events&#x27;</span>)</span><br><span class="line">r = requests.post(<span class="string">&#x27;https://httpbin.org/post&#x27;</span>, data=&#123;<span class="string">&#x27;key&#x27;</span>: <span class="string">&#x27;value&#x27;</span>&#125;)</span><br><span class="line">r = requests.put(<span class="string">&#x27;https://httpbin.org/put&#x27;</span>, data=&#123;<span class="string">&#x27;key&#x27;</span>: <span class="string">&#x27;value&#x27;</span>&#125;)</span><br><span class="line">r = requests.delete(<span class="string">&#x27;https://httpbin.org/delete&#x27;</span>)</span><br><span class="line">r = requests.head(<span class="string">&#x27;https://httpbin.org/get&#x27;</span>)</span><br><span class="line">r = requests.options(<span class="string">&#x27;https://httpbin.org/get&#x27;</span>)</span><br></pre></td></tr></table></figure><p>自然,当我们使用爬虫时,只需要使用get请求就足够了</p><h3 id="给get请求带上参数"><a class="markdownIt-Anchor" href="#给get请求带上参数"></a> 给get请求带上参数</h3><blockquote><p>If you were constructing the URL by hand, this data would be given as key/value pairs in the URL after a question mark, e.g. <code>httpbin.org/get?key=val</code>. Requests allows you to provide these arguments as a dictionary of strings, using the params keyword argument.</p></blockquote><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">payload = &#123;<span class="string">&#x27;key1&#x27;</span>: <span class="string">&#x27;value1&#x27;</span>, <span class="string">&#x27;key2&#x27;</span>: <span class="string">&#x27;value2&#x27;</span>&#125;</span><br><span class="line">r = requests.get(<span class="string">&#x27;https://httpbin.org/get&#x27;</span>, params=payload)</span><br><span class="line"><span class="built_in">print</span>(r.url)</span><br><span class="line"><span class="comment"># https://httpbin.org/get?key2=value2&amp;key1=value1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># another file</span></span><br><span class="line">payload = &#123;<span class="string">&#x27;key1&#x27;</span>: <span class="string">&#x27;value1&#x27;</span>, <span class="string">&#x27;key2&#x27;</span>: [<span class="string">&#x27;value2&#x27;</span>, <span class="string">&#x27;value3&#x27;</span>]&#125;</span><br><span class="line"></span><br><span class="line">r = requests.get(<span class="string">&#x27;https://httpbin.org/get&#x27;</span>, params=payload)</span><br><span class="line"><span class="built_in">print</span>(r.url)</span><br><span class="line"><span class="comment"># https://httpbin.org/get?key1=value1&amp;key2=value2&amp;key2=value3</span></span><br></pre></td></tr></table></figure><ul><li>通过在对应地址后面加参数可以实现按页数/分类爬取</li></ul><h3 id="给get请求设置超时时限"><a class="markdownIt-Anchor" href="#给get请求设置超时时限"></a> 给get请求设置超时时限</h3><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">requests.get(<span class="string">&#x27;https://github.com/&#x27;</span>, timeout=<span class="number">0.001</span>)</span><br></pre></td></tr></table></figure><p>当get请求用时超过timeout值时自动报错</p><h3 id="定制头部headers"><a class="markdownIt-Anchor" href="#定制头部headers"></a> 定制头部(Headers)</h3><blockquote><p>If you’d like to add HTTP headers to a request, simply pass in a <strong>dict</strong> to the headers parameter.</p></blockquote><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">url = <span class="string">&#x27;https://api.github.com/some/endpoint&#x27;</span></span><br><span class="line">headers = &#123;<span class="string">&#x27;user-agent&#x27;</span>: <span class="string">&#x27;my-app/0.0.1&#x27;</span>&#125;</span><br><span class="line"></span><br><span class="line">r = requests.get(url, headers=headers)</span><br></pre></td></tr></table></figure><h3 id="处理get请求获取的内容"><a class="markdownIt-Anchor" href="#处理get请求获取的内容"></a> 处理get请求获取的内容</h3><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line">r = requests.get(<span class="string">&#x27;https://api.github.com/events&#x27;</span>)</span><br><span class="line"><span class="comment"># 我们可以以多种文本形式来处理这个r对象,requests库会自动帮我们返回所需的文本形式</span></span><br><span class="line"></span><br><span class="line">r.text  <span class="comment"># 纯文本形式   &#x27;[&#123;&quot;repository&quot;:&#123;&quot;open_issues&quot;:0,&quot;url&quot;:&quot;https://github.com/...</span></span><br><span class="line"></span><br><span class="line">r.content  <span class="comment"># 二进制形式  b&#x27;[&#123;&quot;repository&quot;:&#123;&quot;open_issues&quot;:0,&quot;url&quot;:&quot;https://github.com/...</span></span><br><span class="line"></span><br><span class="line">r.json()   <span class="comment"># json形式   [&#123;&#x27;repository&#x27;: &#123;&#x27;open_issues&#x27;: 0, &#x27;url&#x27;: &#x27;https://github.com/...</span></span><br></pre></td></tr></table></figure><h4 id="处理返回的状态码"><a class="markdownIt-Anchor" href="#处理返回的状态码"></a> 处理返回的状态码</h4><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">r = requests.get(<span class="string">&#x27;https://httpbin.org/get&#x27;</span>)</span><br><span class="line">r.status_code</span><br><span class="line"><span class="comment"># 200</span></span><br></pre></td></tr></table></figure><ul><li>状态码在判断是否正常爬取内容的时候非常重要</li></ul><h4 id="处理返回的头部"><a class="markdownIt-Anchor" href="#处理返回的头部"></a> 处理返回的头部</h4><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">r.headers</span><br><span class="line"><span class="comment"># &#123;</span></span><br><span class="line"><span class="comment">#     &#x27;content-encoding&#x27;: &#x27;gzip&#x27;,</span></span><br><span class="line"><span class="comment">#     &#x27;transfer-encoding&#x27;: &#x27;chunked&#x27;,</span></span><br><span class="line"><span class="comment">#     &#x27;connection&#x27;: &#x27;close&#x27;,</span></span><br><span class="line"><span class="comment">#     &#x27;server&#x27;: &#x27;nginx/1.0.4&#x27;,</span></span><br><span class="line"><span class="comment">#     &#x27;x-runtime&#x27;: &#x27;148ms&#x27;,</span></span><br><span class="line"><span class="comment">#     &#x27;etag&#x27;: &#x27;&quot;e1ca502697e5c9317743dc078f67693f&quot;&#x27;,</span></span><br><span class="line"><span class="comment">#     &#x27;content-type&#x27;: &#x27;application/json&#x27;</span></span><br><span class="line"><span class="comment"># &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># we can access the headers using any capitalization we want</span></span><br><span class="line">r.headers[<span class="string">&#x27;Content-Type&#x27;</span>]</span><br><span class="line"><span class="string">&#x27;application/json&#x27;</span></span><br><span class="line"></span><br><span class="line">r.headers.get(<span class="string">&#x27;content-type&#x27;</span>)</span><br><span class="line"><span class="string">&#x27;application/json&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="实战"><a class="markdownIt-Anchor" href="#实战"></a> 实战</h3><p>爬取仓库的issues</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">SEARCH_URL=<span class="string">&quot;https://api.github.com/search/issues&quot;</span></span><br><span class="line"><span class="comment"># 爬虫专用的GitHub api网址</span></span><br><span class="line">HEADERS: <span class="built_in">dict</span> = &#123;</span><br><span class="line">    <span class="string">&quot;Authorization&quot;</span>: <span class="string">f&quot;token <span class="subst">&#123;TOKEN&#125;</span>&quot;</span>, </span><br><span class="line">    <span class="string">&quot;Accept&quot;</span>: <span class="string">&quot;application/vnd.github+json&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment"># 带有TOKEN认证的请求头部可以扩大爬虫的权限</span></span><br><span class="line"><span class="comment"># Accept字段表明希望获取json格式的数据</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fetch_issues</span>(<span class="params">start, end</span>):</span><br><span class="line">    page = <span class="number">1</span></span><br><span class="line">    issues = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        r = requests.get(</span><br><span class="line">            SEARCH_URL, </span><br><span class="line">            headers=HEADERS,</span><br><span class="line">            params=&#123;</span><br><span class="line">                <span class="string">&quot;q&quot;</span>: <span class="string">f&quot;repo:ruanyf/weekly 谁在招人 created:<span class="subst">&#123;start&#125;</span>..<span class="subst">&#123;end&#125;</span>&quot;</span>,</span><br><span class="line">                <span class="comment"># start..end 为时间范围筛选</span></span><br><span class="line">                <span class="string">&quot;per_page&quot;</span>: <span class="number">100</span>,</span><br><span class="line">                <span class="comment"># 单次请求返回的数据条数,100为最大值</span></span><br><span class="line">                <span class="string">&quot;page&quot;</span>: page,</span><br><span class="line">                <span class="comment"># 当前页码</span></span><br><span class="line">                <span class="string">&quot;sort&quot;</span>: <span class="string">&quot;created&quot;</span>,</span><br><span class="line">                <span class="comment"># 按照创建时间排序</span></span><br><span class="line">                <span class="string">&quot;order&quot;</span>: <span class="string">&quot;asc&quot;</span>,</span><br><span class="line">                <span class="comment"># asc-ascend(升序),另外有desc-descend(降序)</span></span><br><span class="line">            &#125;,</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        data = r.json() </span><br><span class="line">        <span class="comment"># 用json格式读取数据</span></span><br><span class="line">        items = data.get(<span class="string">&quot;items&quot;</span>, [])</span><br><span class="line">        <span class="comment"># 用get方法获取data的items键对应的列表,若不存在该键则返回空列表</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> items:</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        <span class="comment"># 若为空则说明全部爬取完了</span></span><br><span class="line">        issues.extend(items)</span><br><span class="line">        <span class="comment"># 将新列表连接到issues列表的末尾</span></span><br><span class="line">        page += <span class="number">1</span></span><br><span class="line">        <span class="comment"># 爬取下一页</span></span><br><span class="line">        time.sleep(<span class="number">1</span>)</span><br><span class="line">        <span class="comment"># 等一秒再请求,避免被封</span></span><br><span class="line">    <span class="keyword">return</span> issues</span><br><span class="line">    <span class="comment"># 返回issues列表</span></span><br></pre></td></tr></table></figure><p>items中元素的结构示例</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/repos/ruanyf/weekly/issues/737&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;repository_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/repos/ruanyf/weekly&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;labels_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/repos/ruanyf/weekly/issues/737/labels&#123;/name&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;comments_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/repos/ruanyf/weekly/issues/737/comments&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;events_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/repos/ruanyf/weekly/issues/737/events&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;html_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://github.com/ruanyf/weekly/issues/737&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">474420491</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;node_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MDU6SXNzdWU0NzQ0MjA0OTE=&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;number&quot;</span><span class="punctuation">:</span> <span class="number">737</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;谁在招人？&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;user&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;login&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hobo-tt&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">53465562</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;node_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MDQ6VXNlcjUzNDY1NTYy&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;avatar_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://avatars.githubusercontent.com/u/53465562?v=4&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gravatar_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;html_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://github.com/hobo-tt&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;followers_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt/followers&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;following_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt/following&#123;/other_user&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gists_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt/gists&#123;/gist_id&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;starred_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt/starred&#123;/owner&#125;&#123;/repo&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;subscriptions_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt/subscriptions&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;organizations_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt/orgs&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;repos_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt/repos&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;events_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt/events&#123;/privacy&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;received_events_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/users/hobo-tt/received_events&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;User&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;user_view_type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;public&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;site_admin&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;labels&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;state&quot;</span><span class="punctuation">:</span> <span class="string">&quot;closed&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;locked&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;assignees&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;milestone&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;comments&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;created_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2019-07-30T07:28:48Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;updated_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2019-07-30T07:29:41Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;closed_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2019-07-30T07:29:41Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;assignee&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;author_association&quot;</span><span class="punctuation">:</span> <span class="string">&quot;NONE&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;active_lock_reason&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;sub_issues_summary&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;completed&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;percent_completed&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;issue_dependencies_summary&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;blocked_by&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;total_blocked_by&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;blocking&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;total_blocking&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;body&quot;</span><span class="punctuation">:</span> <span class="string">&quot;北京国际音乐节文化传播有限公司\r\n地点：北京市朝阳区三间房南里4号院第96栋综合办公楼\r\n简历投递Email：[](url)yuanweitong@bmfbj.com\r\n\r\n### 前端工程师\r\n**岗位职责：**\r\n1、负责项目前端架构设计及研发工作；\r\n3、参与复杂业务系统技术选型，架构设计实现，新兴技术研究职责要求；\r\n4、负责前端界面的开发工作；\r\n5、根据产品和需求，依照当前技术架构进行前端开发；\r\n6、负责页面布局优化和调整。\r\n7、对接API数据、并可协调整体数据对接\r\n**任职条件：**\r\n1、本科及以上学历，三年以上web前端开发工作经验；\r\n2、精通HTML，CSS，了解W3C标准，能够熟练配合美工完成兼容主流浏览器的前端页面精通JavaScript，Ajax，DOM等前端技术，精通Vue前端框架；\r\n3、熟悉HTML5/CSS3de.js/Less/Scss等技术能持续优化前端页面的兼 容性和执行效率了解前端页面组件化；\r\n4、对单页WEB应用开发有极强的学习能力，对新技术有浓厚的研究兴趣；\r\n5、熟悉一门非JavaScript语言，如Java、Python、Ruby等\r\n6、熟悉小程序、VUE\r\n\r\n职位详情见[招聘网](https://www.lagou.com/jobs/6077252.html?source=pl&amp;i=pl-1&amp;show=b0079c35e8a042c49b24a6018cec2bac)&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;reactions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/repos/ruanyf/weekly/issues/737/reactions&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;total_count&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;+1&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;-1&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;laugh&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hooray&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;confused&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;heart&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rocket&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eyes&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;timeline_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.github.com/repos/ruanyf/weekly/issues/737/timeline&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;performed_via_github_app&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;state_reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;completed&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;pinned_comment&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;score&quot;</span><span class="punctuation">:</span> <span class="number">1.0</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="bs4学习"><a class="markdownIt-Anchor" href="#bs4学习"></a> bs4学习</h2><ul><li><a href="https://www.crummy.com/software/BeautifulSoup/">官方文档</a><ul><li>一般只用得上其中的一小部分功能,所以翻翻就好了</li></ul></li></ul><p>Beautiful Soup现在的版本为4.13.3,且第四版从12年就已经发布,故一般称为bs4.</p><p><strong>名字来源</strong></p><blockquote><p>It takes its name from the poem Beautiful Soup from <em>Alice’s Adventures in Wonderland</em> and is a reference to the term “tag soup” meaning poorly-structured HTML code.</p></blockquote><h3 id="是什么怎么用-2"><a class="markdownIt-Anchor" href="#是什么怎么用-2"></a> 是什么,怎么用</h3><p>bs4主要用于解析HTML和XML文档,但是它本身不负责解析,而是需要你配合解析库如&quot;lxml&quot;或者python内置的html.parser来进行解析,但是你没有单独在文件里导入,只要虚拟环境中有这个lxml库就可以了</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> bs4 <span class="keyword">import</span> BeautifulSoup</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="comment"># 使用 requests 获取网页内容</span></span><br><span class="line">url = <span class="string">&#x27;https://cn.bing.com/&#x27;</span> <span class="comment"># 抓取bing搜索引擎的网页内容</span></span><br><span class="line">response = requests.get(url)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 BeautifulSoup 解析网页</span></span><br><span class="line">soup = BeautifulSoup(response.text, <span class="string">&#x27;lxml&#x27;</span>)  <span class="comment"># 使用 lxml 解析器</span></span><br><span class="line"><span class="comment"># 解析网页内容 html.parser 解析器</span></span><br><span class="line"><span class="comment"># soup = BeautifulSoup(response.text, &#x27;html.parser&#x27;)</span></span><br></pre></td></tr></table></figure><h3 id="find与find_all"><a class="markdownIt-Anchor" href="#find与find_all"></a> find与find_all</h3><p><strong>基础用法</strong></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> bs4 <span class="keyword">import</span> BeautifulSoup</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line"><span class="comment"># 指定你想要获取标题的网站</span></span><br><span class="line">url = <span class="string">&#x27;https://www.baidu.com/&#x27;</span> <span class="comment"># 抓取bing搜索引擎的网页内容</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 发送HTTP请求获取网页内容</span></span><br><span class="line">response = requests.get(url)</span><br><span class="line"></span><br><span class="line">soup = BeautifulSoup(response.text, <span class="string">&#x27;lxml&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查找第一个 &lt;a&gt; 标签</span></span><br><span class="line">first_link = soup.find(<span class="string">&#x27;a&#x27;</span>)</span><br><span class="line"><span class="built_in">print</span>(first_link)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;----------------------------&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取第一个 &lt;a&gt; 标签的 href 属性</span></span><br><span class="line">first_link_url = first_link.get(<span class="string">&#x27;href&#x27;</span>)</span><br><span class="line"><span class="built_in">print</span>(first_link_url)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;----------------------------&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查找所有 &lt;a&gt; 标签</span></span><br><span class="line">all_links = soup.find_all(<span class="string">&#x27;a&#x27;</span>)</span><br><span class="line"><span class="built_in">print</span>(all_links)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取第一个 &lt;p&gt; 标签中的文本内容</span></span><br><span class="line">paragraph_text = soup.find(<span class="string">&quot;p&quot;</span>).get_text()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取页面中所有文本内容</span></span><br><span class="line">all_text = soup.get_text()</span><br><span class="line"><span class="built_in">print</span>(all_text)</span><br></pre></td></tr></table></figure><p><strong>加入标签或属性</strong></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查找所有 class=&quot;example-class&quot; 的 &lt;div&gt; 标签</span></span><br><span class="line">divs_with_class = soup.find_all(<span class="string">&#x27;div&#x27;</span>, class_=<span class="string">&#x27;example-class&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查找具有 id=&quot;unique-id&quot; 的 &lt;p&gt; 标签</span></span><br><span class="line">unique_paragraph = soup.find(<span class="string">&#x27;p&#x27;</span>, <span class="built_in">id</span>=<span class="string">&#x27;unique-id&#x27;</span>)</span><br></pre></td></tr></table></figure><p>非常显然的是,bs4仅支持获取静态网页内容,在现在很多网页都是用js渲染的情况下不太实用了,但作为新手入门库还是很不错的,可以一下子感受到爬虫的威力,没有任何学习难度.</p><h3 id="bs4源码概览"><a class="markdownIt-Anchor" href="#bs4源码概览"></a> bs4源码概览</h3><blockquote><p>如果你闲的蛋疼,可以像我一样下载bs4的源码:</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://git.launchpad.net/beautifulsoup</span><br></pre></td></tr></table></figure><p><img src="/images/archives/2026/2026-03-16/image.png" alt="alt text" /><br />事实上,当我们翻阅源码时,会惊讶的发现这个有着20年悠久历史的python库竟然只有这么一点文件!</p><ul><li>而且还能看到<strong><a href="http://dammit.py">dammit.py</a></strong>这么一个神奇的名字</li></ul><h2 id="小结"><a class="markdownIt-Anchor" href="#小结"></a> 小结</h2><p>结合requests(,bs4)和支持读写文件的库,我们现在基本可以爬取所有的静态网页资源,并在处理后进行存储了.</p><h2 id="selenium学习"><a class="markdownIt-Anchor" href="#selenium学习"></a> Selenium学习</h2><ul><li><a href="https://www.selenium.dev/zh-cn/documentation/webdriver/getting_started/">官方教程</a><ul><li>非常遗憾的是,官方的文档写的很烂</li></ul></li><li><a href="https://www.geeksforgeeks.org/python/selenium-python-tutorial/">geeksforgeeks</a><ul><li>翻来翻去能找到的唯一质量比较好的教程,反过来说明selenium本身的用户生态太差了</li></ul></li></ul><h3 id="是什么怎么用-3"><a class="markdownIt-Anchor" href="#是什么怎么用-3"></a> 是什么,怎么用</h3><blockquote><p>Selenium 通过使用 WebDriver 支持市场上所有主流浏览器的自动化。 WebDriver 是一个 API 和协议，它定义了一个语言中立的接口，用于控制 web 浏览器的行为。 每个浏览器都有一个特定的 WebDriver 实现，称为驱动程序。 驱动程序是负责委派给浏览器的组件，并处理与 Selenium 和浏览器之间的通信。</p></blockquote><p>换句话说,没有WebDriver就用不了selenium,所以我们需要下载自己浏览器版本对应的驱动器.</p><p>当然,去官方慢慢翻驱动器版本还是太琐碎了,现在的selenium支持<strong>自动下载</strong>对应的驱动器,就没必要如过时的教程所说去配置驱动器的环境变量了.</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> selenium <span class="keyword">import</span> webdriver</span><br><span class="line"></span><br><span class="line">driver = webdriver.Chrome()</span><br><span class="line">driver.get(<span class="string">&quot;https://www.google.com&quot;</span>)</span><br><span class="line"><span class="comment"># 只写这三行代码也会自动下载对应的驱动器到本地缓存目录中</span></span><br></pre></td></tr></table></figure><p>运行上方代码,我们成功用chrome打开了google网站</p><ul><li>至于为什么会自动退出,是因为我们后面没有其他代码了,webdriver会自动关闭</li></ul><p>如果你还是没明白selenium的用法的话,你可以想一下,如果有一个实验要你去找一百个人填问卷,你可以将问卷设定为允许多次填写,安排一些合理的选择题,就可以很轻松的用selenium模拟真实用户登录问卷星网站,用预先设定的随机值去逐个填写问卷,这样一下来,就算要填一千份你一个小时也能搞定了.</p><ul><li>注意!这是严重的学术不端行为!请大家千万不要模仿!</li></ul><h4 id="可能的vpn问题"><a class="markdownIt-Anchor" href="#可能的vpn问题"></a> 可能的vpn问题</h4><p>我用的clash设定了系统代理,在终端配置了代理端口,还在bypass列表里设定了排除系统端口,但selenium还是会被拦截…</p><p><strong><s>我</s>AI的解决方案</strong></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line">os.environ[<span class="string">&quot;HTTP_PROXY&quot;</span>] = <span class="string">&quot;&quot;</span></span><br><span class="line">os.environ[<span class="string">&quot;HTTPS_PROXY&quot;</span>] = <span class="string">&quot;&quot;</span></span><br><span class="line">os.environ[<span class="string">&quot;no_proxy&quot;</span>] = <span class="string">&quot;localhost,127.0.0.1&quot;</span></span><br><span class="line"><span class="comment"># 在代码上方加上这个,但更推荐单独放入一个文件后再导入</span></span><br></pre></td></tr></table></figure><h3 id="基础用法一览"><a class="markdownIt-Anchor" href="#基础用法一览"></a> 基础用法一览</h3><p><strong>进入网页并输入</strong></p><ul><li>注意用chrome的话容易被拦截,反正我被拦截了</li></ul><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> selenium <span class="keyword">import</span> webdriver</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.by <span class="keyword">import</span> By</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.keys <span class="keyword">import</span> Keys</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="comment"># Launch browser and open Google</span></span><br><span class="line">drv = webdriver.Chrome()</span><br><span class="line">drv.get(<span class="string">&quot;https://www.google.com//&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Search &quot;GeeksforGeeks&quot;</span></span><br><span class="line">box = drv.find_element(By.NAME, <span class="string">&quot;q&quot;</span>)</span><br><span class="line"><span class="comment"># 寻找第一个name=&quot;q&quot;的元素,也就是搜索框</span></span><br><span class="line">box.send_keys(<span class="string">&quot;GeeksforGeeks&quot;</span>, Keys.RETURN)</span><br><span class="line"><span class="comment"># Keys.RETURN模拟回车键</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Wait and close browser</span></span><br><span class="line">time.sleep(<span class="number">5</span>)</span><br><span class="line">drv.quit()</span><br></pre></td></tr></table></figure><h4 id="find_element与find_elements方法"><a class="markdownIt-Anchor" href="#find_element与find_elements方法"></a> find_element与find_elements方法</h4><p>该方法的第一个参数为要查找的属性名,第二个参数为属性值</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">element = driver.find_element(By.ID, <span class="string">&quot;passwd-id&quot;</span>)</span><br><span class="line">element = driver.find_element(By.NAME, <span class="string">&quot;passwd&quot;</span>)</span><br><span class="line">element = driver.find_element(By.XPATH, <span class="string">&quot;//input[@id=&#x27;passwd-id&#x27;]&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># If you need to find multiple elements, use:</span></span><br><span class="line">elements = driver.find_elements(By.NAME, <span class="string">&quot;passwd&quot;</span>)</span><br></pre></td></tr></table></figure><h4 id="模拟键盘交互"><a class="markdownIt-Anchor" href="#模拟键盘交互"></a> 模拟键盘交互</h4><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># If you want to input text into a field, you can use:</span></span><br><span class="line">element.send_keys(<span class="string">&quot;some text&quot;</span>)</span><br><span class="line"><span class="comment"># You can also simulate pressing arrow keys or other keys using the Keys class:</span></span><br><span class="line"></span><br><span class="line">element.send_keys(<span class="string">&quot; and some&quot;</span>, Keys.ARROW_DOWN)</span><br><span class="line"></span><br><span class="line"><span class="comment"># To clear the contents of a text field or textarea, use the clear method:</span></span><br><span class="line"></span><br><span class="line">element.clear()</span><br></pre></td></tr></table></figure><h4 id="实战-2"><a class="markdownIt-Anchor" href="#实战-2"></a> 实战</h4><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Import the necessary modules from Selenium</span></span><br><span class="line"><span class="keyword">from</span> selenium <span class="keyword">import</span> webdriver</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.by <span class="keyword">import</span> By</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.keys <span class="keyword">import</span> Keys  <span class="comment"># Added import for Keys</span></span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.support.ui <span class="keyword">import</span> WebDriverWait  <span class="comment"># To wait for elements</span></span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.support <span class="keyword">import</span> (</span><br><span class="line">    expected_conditions <span class="keyword">as</span> EC,</span><br><span class="line">)  <span class="comment"># For expected conditions</span></span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test</span>():</span><br><span class="line">    <span class="comment"># you can choose other browsers like Chrome, Firefox, etc.</span></span><br><span class="line">    driver = webdriver.Edge()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Navigate to the GeeksforGeeks website</span></span><br><span class="line">    driver.get(<span class="string">&quot;https://www.geeksforgeeks.org/&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Maximize the browser window</span></span><br><span class="line">    driver.maximize_window()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Wait for 3 seconds to ensure the page is loaded</span></span><br><span class="line">    time.sleep(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Handle iframe if one exists (e.g., an overlay)</span></span><br><span class="line">    iframe_element = driver.find_element(</span><br><span class="line">        By.XPATH, <span class="string">&quot;//iframe[contains(@src,&#x27;accounts.google.com&#x27;)]&quot;</span></span><br><span class="line">    )</span><br><span class="line">    driver.switch_to.frame(iframe_element)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Close the overlay (e.g., Google sign-in iframe)</span></span><br><span class="line">    closeele = driver.find_element(By.XPATH, <span class="string">&quot;//*[@id=&#x27;close&#x27;]&quot;</span>)</span><br><span class="line">    closeele.click()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Wait for the iframe action to complete</span></span><br><span class="line">    time.sleep(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Switch back to the main content</span></span><br><span class="line">    driver.switch_to.default_content()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Locate the search icon element using XPath</span></span><br><span class="line">    searchIcon = driver.find_element(By.XPATH, <span class="string">&quot;//span[@class=&#x27;flexR gs-toggle-icon&#x27;]&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Wait for 3 seconds before interacting with the search input</span></span><br><span class="line">    time.sleep(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Locate the input field for search text using XPath</span></span><br><span class="line">    enterText = driver.find_element(By.XPATH, <span class="string">&quot;//input[@class=&#x27;gs-input&#x27;]&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Enter the search query &quot;Data Structure&quot; into the input field</span></span><br><span class="line">    enterText.send_keys(<span class="string">&quot;Data Structure&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Send the RETURN key to submit the search query</span></span><br><span class="line">    enterText.send_keys(Keys.RETURN)</span><br></pre></td></tr></table></figure><h3 id="真实战"><a class="markdownIt-Anchor" href="#真实战"></a> 真&gt;&gt;实战</h3><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> selenium <span class="keyword">import</span> webdriver</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.by <span class="keyword">import</span> By</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.chrome.options <span class="keyword">import</span> Options</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"></span><br><span class="line">wjx_url = <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="comment"># wjx问卷网址</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_one_response</span>():</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;div1&quot;</span>: random.choice([<span class="string">&quot;男&quot;</span>, <span class="string">&quot;女&quot;</span>]),</span><br><span class="line">        <span class="string">&quot;div2&quot;</span>: random.choice(</span><br><span class="line">            [<span class="string">&quot;理工类&quot;</span>, <span class="string">&quot;文史哲类&quot;</span>, <span class="string">&quot;社会科学类（法学、管理学等）&quot;</span>, <span class="string">&quot;艺术类&quot;</span>, <span class="string">&quot;其他&quot;</span>]</span><br><span class="line">        ),</span><br><span class="line">        <span class="string">&quot;div3&quot;</span>: random.choice([<span class="string">&quot;非常重要&quot;</span>, <span class="string">&quot;重要&quot;</span>, <span class="string">&quot;一般&quot;</span>, <span class="string">&quot;不重要&quot;</span>]),</span><br><span class="line">        <span class="string">&quot;div4&quot;</span>: random.choice(</span><br><span class="line">            [<span class="string">&quot;深入学习过&quot;</span>, <span class="string">&quot;了解过部分内容&quot;</span>, <span class="string">&quot;听说过但不了解&quot;</span>, <span class="string">&quot;完全不了解&quot;</span>]</span><br><span class="line">        ),</span><br><span class="line">        <span class="string">&quot;div5&quot;</span>: random.choice([<span class="string">&quot;是，应该加强&quot;</span>, <span class="string">&quot;不需要，当前已足够&quot;</span>, <span class="string">&quot;无所谓&quot;</span>]),</span><br><span class="line">        <span class="string">&quot;div6&quot;</span>: random.choice([<span class="string">&quot;总是&quot;</span>, <span class="string">&quot;经常&quot;</span>, <span class="string">&quot;偶尔&quot;</span>, <span class="string">&quot;很少&quot;</span>]),</span><br><span class="line">        <span class="string">&quot;div7&quot;</span>: random.choice(</span><br><span class="line">            [</span><br><span class="line">                <span class="string">&quot;先转发再说&quot;</span>,</span><br><span class="line">                <span class="string">&quot;只分享来自官方渠道的信息&quot;</span>,</span><br><span class="line">                <span class="string">&quot;自己查证后再决定是否转发&quot;</span>,</span><br><span class="line">                <span class="string">&quot;看到也不管，不会转发&quot;</span>,</span><br><span class="line">            ]</span><br><span class="line">        ),</span><br><span class="line">        <span class="string">&quot;div8&quot;</span>: random.choice([<span class="string">&quot;从不&quot;</span>, <span class="string">&quot;偶尔&quot;</span>, <span class="string">&quot;经常&quot;</span>]),</span><br><span class="line">        <span class="string">&quot;div9&quot;</span>: random.choice([<span class="string">&quot;从不&quot;</span>, <span class="string">&quot;偶尔&quot;</span>, <span class="string">&quot;经常&quot;</span>]),</span><br><span class="line">        <span class="string">&quot;div10&quot;</span>: random.choice([<span class="string">&quot;经常&quot;</span>, <span class="string">&quot;偶尔&quot;</span>, <span class="string">&quot;很少&quot;</span>, <span class="string">&quot;从不&quot;</span>]),</span><br><span class="line">        <span class="string">&quot;div11&quot;</span>: random.choice([<span class="string">&quot;是，经常&quot;</span>, <span class="string">&quot;偶尔&quot;</span>, <span class="string">&quot;有过一次&quot;</span>, <span class="string">&quot;没有遇到过&quot;</span>]),</span><br><span class="line">        <span class="string">&quot;div12&quot;</span>: random.sample(</span><br><span class="line">            [</span><br><span class="line">                <span class="string">&quot;网络暴力（如恶意攻击、辱骂）&quot;</span>,</span><br><span class="line">                <span class="string">&quot;网络诈骗（如假兼职、中奖信息）&quot;</span>,</span><br><span class="line">                <span class="string">&quot;虚假信息或网络谣言&quot;</span>,</span><br><span class="line">                <span class="string">&quot;侵犯隐私（如曝光个人信息）&quot;</span>,</span><br><span class="line">                <span class="string">&quot;不良言论或低俗内容&quot;</span>,</span><br><span class="line">                <span class="string">&quot;网络沉迷（如过度使用短视频/游戏）&quot;</span>,</span><br><span class="line">            ],</span><br><span class="line">            random.randint(<span class="number">2</span>, <span class="number">4</span>),</span><br><span class="line">        ),</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fill_and_submit</span>():</span><br><span class="line">    <span class="comment"># options.add_argument(&quot;--headless&quot;)  # 可取消注释用于无头运行</span></span><br><span class="line">    driver = webdriver.Chrome()</span><br><span class="line"></span><br><span class="line">    driver.get(wjx_url)</span><br><span class="line">    time.sleep(<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 点击“开始作答”按钮</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        start_button = driver.find_element(By.CLASS_NAME, <span class="string">&quot;startbtn&quot;</span>)</span><br><span class="line">        start_button.click()</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;已点击开始作答按钮&quot;</span>)</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;未找到开始按钮，可能已跳转页面&quot;</span>)</span><br><span class="line"></span><br><span class="line">    time.sleep(<span class="number">3</span>)  <span class="comment"># 等待问卷加载</span></span><br><span class="line"></span><br><span class="line">    answers = generate_one_response()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> div_id, value <span class="keyword">in</span> answers.items():</span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            div = driver.find_element(By.ID, div_id)</span><br><span class="line">            <span class="keyword">if</span> <span class="built_in">isinstance</span>(value, <span class="built_in">list</span>):</span><br><span class="line">                <span class="keyword">for</span> val <span class="keyword">in</span> value:</span><br><span class="line">                    labels = div.find_elements(By.CLASS_NAME, <span class="string">&quot;label&quot;</span>)</span><br><span class="line">                    <span class="keyword">for</span> label <span class="keyword">in</span> labels:</span><br><span class="line">                        <span class="keyword">if</span> val <span class="keyword">in</span> label.text:</span><br><span class="line">                            label.click()</span><br><span class="line">                            <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                labels = div.find_elements(By.CLASS_NAME, <span class="string">&quot;label&quot;</span>)</span><br><span class="line">                <span class="keyword">for</span> label <span class="keyword">in</span> labels:</span><br><span class="line">                    <span class="keyword">if</span> value <span class="keyword">in</span> label.text:</span><br><span class="line">                        label.click()</span><br><span class="line">                        <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;div_id&#125;</span> 填写失败: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">        time.sleep(<span class="number">0.3</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 点击提交按钮</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        submit_btn = driver.find_element(By.ID, <span class="string">&quot;ctlNext&quot;</span>)</span><br><span class="line">        submit_btn.click()</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;问卷提交成功！&quot;</span>)</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;提交失败: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    time.sleep(<span class="number">2</span>)</span><br><span class="line">    driver.quit()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">fill_and_submit()</span><br><span class="line"><span class="comment"># 我们可以加上一个while循环...</span></span><br></pre></td></tr></table></figure><ul><li>整个代码并没有任何难懂的地方,只需要我们亲自去查html元素对应的名字就可以实现自动化答题了</li></ul><h2 id="playwright学习"><a class="markdownIt-Anchor" href="#playwright学习"></a> PlayWright学习</h2><ul><li><a href="https://playwright.dev/python/docs/intro">官方文档</a><ul><li>看似内容很多,其实有用的东西很少…</li></ul></li></ul><h3 id="是什么怎么用-4"><a class="markdownIt-Anchor" href="#是什么怎么用-4"></a> 是什么,怎么用</h3><blockquote><p>Playwright was created specifically to accommodate the needs of end-to-end testing. Playwright <strong>supports all modern rendering engines including Chromium, WebKit, and Firefox</strong>. Test on Windows, Linux, and macOS, locally or on CI, headless or headed with native mobile emulation.</p></blockquote><p>这段文字把playwright介绍为一个测试工具,乍一看与爬虫没有任何关系,但黑体部分不正是selenium支持的功能吗,那它自然也可以实现selenium的爬虫功能了.</p><p>playwright的历史并不很长,最开始是以js版本推出的,不知为何又引入到了python里,并制作了两个python库,一个是我们常用的playwright库,还有一个是pytest-playwright,试图取代常规的pytest库,并在官方文档里反复提及…,在我看来完全没有必要.</p><p>以下代码是一个playwright功能展示的简单示例,光是学习的话,我们可以使用自己电脑里的浏览器内核,不必像官网或者其他博客所说先运行<code>playwright install</code>命令,那会默认在<strong>全局</strong>安装多种浏览器内核,占用体积还不小😇.</p><ul><li>由于playwright与selenium不同,不能在代码中默认安装,而是需要提前下载好驱动.因此在生产环境下还是得老老实实装的,当然也只要装自己所需的那一款内核就行了</li></ul><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> playwright.sync_api <span class="keyword">import</span> sync_playwright</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> sync_playwright() <span class="keyword">as</span> p:</span><br><span class="line">    browser = p.chromium.launch(</span><br><span class="line">        executable_path=<span class="string">&quot;C:/Program Files/Google/Chrome/Application/chrome.exe&quot;</span>,</span><br><span class="line">        headless=<span class="literal">True</span>,</span><br><span class="line">    )</span><br><span class="line">    <span class="comment"># 如果chrome.exe路径不对你就改成自己的路径</span></span><br><span class="line">    page = browser.new_page()</span><br><span class="line">    page.goto(<span class="string">&quot;https://playwright.dev&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(page.title())</span><br><span class="line">    page.goto(<span class="string">&quot;https://google.com&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(page.title())</span><br><span class="line">    browser.close()</span><br></pre></td></tr></table></figure><h3 id="基本语法"><a class="markdownIt-Anchor" href="#基本语法"></a> 基本语法</h3><h4 id="同步异步api"><a class="markdownIt-Anchor" href="#同步异步api"></a> 同步/异步API</h4><ul><li><a href="https://devtest-notes.readthedocs.io/zh/latest/web/web-testing-with-playwright-introduction.html">参考</a><br /><img src="/images/2026-04-03/PixPin_2026-04-03_21-42-39.webp" alt="alt text" /><br />看上图就知道playwright中有两个主要的模块:sync_api和async_api,分别对应着同步和异步的请求,我们先来看同步请求的用法:</li></ul><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> playwright.sync_api <span class="keyword">import</span> sync_playwright</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">testcase1</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;testcase1 start&quot;</span>)</span><br><span class="line">    <span class="keyword">with</span> sync_playwright() <span class="keyword">as</span> p:</span><br><span class="line">        browser = p.chromium.launch(</span><br><span class="line">            executable_path=<span class="string">&quot;C:/Program Files/Google/Chrome/Application/chrome.exe&quot;</span>,</span><br><span class="line">            headless=<span class="literal">False</span>,</span><br><span class="line">        )</span><br><span class="line">        page = browser.new_page()</span><br><span class="line">        page.goto(<span class="string">&quot;https://www.baidu.com/&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(page.title())</span><br><span class="line">        page.fill(<span class="string">&quot;#chat-textarea&quot;</span>, <span class="string">&quot;test&quot;</span>)</span><br><span class="line">        <span class="comment"># 文本框输入test</span></span><br><span class="line">        page.click(<span class="string">&quot;#chat-submit-button&quot;</span>)</span><br><span class="line">        <span class="comment"># 提交</span></span><br><span class="line">        browser.close()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;testcase1 done&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">testcase2</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;testcase2 start&quot;</span>)</span><br><span class="line">    <span class="keyword">with</span> sync_playwright() <span class="keyword">as</span> p:</span><br><span class="line">        browser2 = p.chromium.launch(</span><br><span class="line">            executable_path=<span class="string">&quot;C:/Program Files/Google/Chrome/Application/chrome.exe&quot;</span>,</span><br><span class="line">            headless=<span class="literal">False</span>,</span><br><span class="line">        )</span><br><span class="line">        page2 = browser2.new_page()</span><br><span class="line">        page2.goto(<span class="string">&quot;https://www.sogou.com/&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(page2.title())</span><br><span class="line">        page2.fill(<span class="string">&#x27;input[name=&quot;query&quot;]&#x27;</span>, <span class="string">&quot;test&quot;</span>)</span><br><span class="line">        page2.click(<span class="string">&quot;text=搜索&quot;</span>)</span><br><span class="line">        browser2.close()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;testcase2 done&quot;</span>)</span><br></pre></td></tr></table></figure><p>显然,同步的用法和selenium几乎没有差别,那我们再来看看异步:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> playwright.async_api <span class="keyword">import</span> async_playwright</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">testcase1</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;testcase1 start&quot;</span>)</span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">with</span> async_playwright() <span class="keyword">as</span> p:</span><br><span class="line">        browser = <span class="keyword">await</span> p.chromium.launch(</span><br><span class="line">            executable_path=<span class="string">&quot;C:/Program Files/Google/Chrome/Application/chrome.exe&quot;</span>,</span><br><span class="line">            headless=<span class="literal">False</span>,</span><br><span class="line">        )</span><br><span class="line">        page = <span class="keyword">await</span> browser.new_page()</span><br><span class="line">        <span class="keyword">await</span> page.goto(<span class="string">&quot;https://www.baidu.com/&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="keyword">await</span> page.title())</span><br><span class="line">        <span class="keyword">await</span> page.fill(<span class="string">&quot;#chat-textarea&quot;</span>, <span class="string">&quot;test&quot;</span>)</span><br><span class="line">        <span class="comment"># 文本框输入test</span></span><br><span class="line">        <span class="keyword">await</span> page.click(<span class="string">&quot;#chat-submit-button&quot;</span>)</span><br><span class="line">        <span class="comment"># 提交</span></span><br><span class="line">        <span class="keyword">await</span> browser.close()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;testcase1 done&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">testcase2</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;testcase2 start&quot;</span>)</span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">with</span> async_playwright() <span class="keyword">as</span> p:</span><br><span class="line">        browser2 = <span class="keyword">await</span> p.chromium.launch(</span><br><span class="line">            executable_path=<span class="string">&quot;C:/Program Files/Google/Chrome/Application/chrome.exe&quot;</span>,</span><br><span class="line">            headless=<span class="literal">False</span>,</span><br><span class="line">        )</span><br><span class="line">        page2 = <span class="keyword">await</span> browser2.new_page()</span><br><span class="line">        <span class="keyword">await</span> page2.goto(<span class="string">&quot;https://www.sogou.com/&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="keyword">await</span> page2.title())</span><br><span class="line">        <span class="keyword">await</span> page2.fill(<span class="string">&#x27;input[name=&quot;query&quot;]&#x27;</span>, <span class="string">&quot;test&quot;</span>)</span><br><span class="line">        <span class="keyword">await</span> page2.click(<span class="string">&quot;text=搜索&quot;</span>)</span><br><span class="line">        <span class="keyword">await</span> browser2.close()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;testcase2 done&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="keyword">await</span> testcase2()</span><br><span class="line">    <span class="keyword">await</span> testcase1()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    asyncio.run(main())</span><br></pre></td></tr></table></figure><p>其实异步版本只是给关键的函数调用加上了异步的修饰而已,但这样就可以显著提升爬取速度了;而selenium并不支持异步爬取,因此逐渐被playwright取代.</p><h2 id="scrapy学习"><a class="markdownIt-Anchor" href="#scrapy学习"></a> Scrapy学习</h2><ul><li><a href="https://docs.scrapy.org/en/latest/intro/overview.html#walk-through-of-an-example-spider">官方文档</a><ul><li>比较详尽</li></ul></li></ul><h3 id="是什么怎么用-5"><a class="markdownIt-Anchor" href="#是什么怎么用-5"></a> 是什么,怎么用</h3><p>scrapy是一个一体化爬虫框架,request的上位替代,支持异步处理和终端交互</p><p><strong><a href="http://test.py">test.py</a></strong></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> scrapy</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">QuotesSpider</span>(scrapy.Spider):</span><br><span class="line">    name = <span class="string">&quot;quotes&quot;</span></span><br><span class="line">    start_urls = [</span><br><span class="line">        <span class="string">&quot;https://quotes.toscrape.com/tag/humor/&quot;</span>,</span><br><span class="line">    ]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">parse</span>(<span class="params">self, response</span>):</span><br><span class="line">        <span class="keyword">for</span> quote <span class="keyword">in</span> response.css(<span class="string">&quot;div.quote&quot;</span>):</span><br><span class="line">            <span class="keyword">yield</span> &#123;</span><br><span class="line">                <span class="string">&quot;author&quot;</span>: quote.xpath(<span class="string">&quot;span/small/text()&quot;</span>).get(),</span><br><span class="line">                <span class="string">&quot;text&quot;</span>: quote.css(<span class="string">&quot;span.text::text&quot;</span>).get(),</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        next_page = response.css(<span class="string">&#x27;li.next a::attr(&quot;href&quot;)&#x27;</span>).get()</span><br><span class="line">        <span class="keyword">if</span> next_page <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">yield</span> response.follow(next_page, <span class="variable language_">self</span>.parse)</span><br></pre></td></tr></table></figure><p>运行方式:<br /><code>scrapy runspider test.py -o quotes.jsonl</code><br />运行之后将得到这样这样的内容:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;author&quot;: &quot;Jane Austen&quot;, &quot;text&quot;: &quot;\u201cThe person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.\u201d&quot;&#125;</span><br><span class="line">&#123;&quot;author&quot;: &quot;Steve Martin&quot;, &quot;text&quot;: &quot;\u201cA day without sunshine is like, you know, night.\u201d&quot;&#125;</span><br><span class="line">&#123;&quot;author&quot;: &quot;Garrison Keillor&quot;, &quot;text&quot;: &quot;\u201cAnyone who thinks sitting in church can make you a Christian must also think that sitting in a garage can make you a car.\u201d&quot;&#125;</span><br><span class="line">// ...</span><br></pre></td></tr></table></figure><p>可以看出来,scrapy通过类来封装爬虫(这类似于pytest中对测试的封装),并且不需要我们再进行额外的库导入,而是在后台包办一切.</p><h3 id="scrapy命令行"><a class="markdownIt-Anchor" href="#scrapy命令行"></a> scrapy命令行</h3><p>虽然可以修改，但默认情况下所有 Scrapy 项目都具有相同的文件结构，类似于：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">scrapy.cfg</span><br><span class="line">tutorial/</span><br><span class="line">    __init__.py</span><br><span class="line">    items.py</span><br><span class="line">    middlewares.py</span><br><span class="line">    pipelines.py</span><br><span class="line">    settings.py</span><br><span class="line">    spiders/</span><br><span class="line">        __init__.py</span><br><span class="line">        spider1.py</span><br><span class="line">        spider2.py</span><br><span class="line">        ...</span><br></pre></td></tr></table></figure><blockquote><p>scrapy.cfg 文件所在的目录被称为 <em>项目根目录</em>。该文件包含定义项目设置的 Python 模块名称。示例如下：</p></blockquote><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[settings]</span></span><br><span class="line"><span class="attr">default</span> = tutorial.settings</span><br><span class="line"></span><br><span class="line"><span class="section">[deploy]</span></span><br><span class="line"><span class="attr">project</span> = tutorial</span><br></pre></td></tr></table></figure><h4 id="创建和运行项目"><a class="markdownIt-Anchor" href="#创建和运行项目"></a> 创建和运行项目</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scrapy startproject myproject [project_dir]</span><br></pre></td></tr></table></figure><p>这将在当前目录下创建一个project_dir文件夹,里面有一个Scrapy 项目,名字为myproject.</p><ul><li>如果未指定 project_dir，project_dir的名字将与 myproject 相同</li></ul><h4 id="命令行参数一览"><a class="markdownIt-Anchor" href="#命令行参数一览"></a> 命令行参数一览</h4><p><strong>全局命令</strong></p><ul><li><code>startproject</code>: 创建项目</li><li><code>genspider</code>: 在当前项目的spiders文件夹中创建一个新爬虫文件<ul><li>示例: <code>uv run scrapy genspider hello_world www.bing.com</code>将在文件夹中创建hello_world.py文件.</li></ul></li><li><code>settings</code>: 过</li><li><code>runspider</code>: 不创建项目直接运行python文件中的独立爬虫</li><li><code>shell</code>: 过</li><li><code>fetch</code>: 使用scrapy下载器访问给定的网页</li><li><code>view</code>: 过</li><li><code>version</code>: 过</li></ul><p><strong>仅限项目的命令</strong></p><ul><li><code>crawl</code>: 运行某一个爬虫文件</li><li><code>check</code>: 自动化测试</li><li><code>list</code>: 列出该项目中可用的爬虫文件</li><li><code>edit</code>: 编辑某个爬虫文件</li><li><code>parse</code>: 过</li><li><code>bench</code>: 过</li></ul><p>显然,大多数命令都没什么用,还是需要自己去写爬虫文件和测试.</p><h3 id="spider类"><a class="markdownIt-Anchor" href="#spider类"></a> Spider类</h3><p><strong>基本调用方法</strong></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> scrapy</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">HelloWorldSpider</span>(scrapy.Spider):</span><br><span class="line">    name = <span class="string">&quot;hello_world&quot;</span></span><br><span class="line">    allowed_domains = [<span class="string">&quot;www.bing.com&quot;</span>]</span><br><span class="line">    start_urls = [<span class="string">&quot;https://www.bing.com&quot;</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">parse</span>(<span class="params">self, response</span>):</span><br><span class="line">        <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><h4 id="spider源码剖析"><a class="markdownIt-Anchor" href="#spider源码剖析"></a> Spider源码剖析</h4><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;Base class that any spider must subclass.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">   It provides a default :meth:`start` implementation that sends</span></span><br><span class="line"><span class="string">   requests based on the :attr:`start_urls` class attribute and calls the</span></span><br><span class="line"><span class="string">   :meth:`parse` method for each response.</span></span><br><span class="line"><span class="string">   &quot;&quot;&quot;</span></span><br></pre></td></tr></table></figure><p>上述代码为Spider类的注释,也就是说这个类会自动调用start()方法并爬取目标网址后使用parse()方法解析.<br />让我们看看具体的实现:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Spider</span>(<span class="title class_ inherited__">object_ref</span>):</span><br><span class="line"><span class="comment"># 三个默认参数</span></span><br><span class="line">    name: <span class="built_in">str</span></span><br><span class="line">    custom_settings: <span class="built_in">dict</span>[_SettingsKey, <span class="type">Any</span>] | <span class="literal">None</span> = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">#: Start URLs. See :meth:`start`.</span></span><br><span class="line">    start_urls: <span class="built_in">list</span>[<span class="built_in">str</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 实例化三个参数</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name: <span class="built_in">str</span> | <span class="literal">None</span> = <span class="literal">None</span>, **kwargs: <span class="type">Any</span></span>):</span><br><span class="line">        <span class="keyword">if</span> name <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="variable language_">self</span>.name: <span class="built_in">str</span> = name</span><br><span class="line">        <span class="keyword">elif</span> <span class="keyword">not</span> <span class="built_in">getattr</span>(<span class="variable language_">self</span>, <span class="string">&quot;name&quot;</span>, <span class="literal">None</span>):</span><br><span class="line">            <span class="keyword">raise</span> ValueError(<span class="string">f&quot;<span class="subst">&#123;<span class="built_in">type</span>(self).__name__&#125;</span> must have a name&quot;</span>)</span><br><span class="line">        <span class="variable language_">self</span>.__dict__.update(kwargs)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">hasattr</span>(<span class="variable language_">self</span>, <span class="string">&quot;start_urls&quot;</span>):</span><br><span class="line">            <span class="variable language_">self</span>.start_urls: <span class="built_in">list</span>[<span class="built_in">str</span>] = []</span><br><span class="line"><span class="comment"># 启用调试函数log</span></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">logger</span>(<span class="params">self</span>) -&gt; SpiderLoggerAdapter:</span><br><span class="line">        <span class="comment"># circular import</span></span><br><span class="line">        <span class="keyword">from</span> scrapy.utils.log <span class="keyword">import</span> SpiderLoggerAdapter  <span class="comment"># noqa: PLC0415</span></span><br><span class="line"></span><br><span class="line">        logger = logging.getLogger(<span class="variable language_">self</span>.name)</span><br><span class="line">        <span class="keyword">return</span> SpiderLoggerAdapter(logger, &#123;<span class="string">&quot;spider&quot;</span>: <span class="variable language_">self</span>&#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">log</span>(<span class="params">self, message: <span class="type">Any</span>, level: <span class="built_in">int</span> = logging.DEBUG, **kw: <span class="type">Any</span></span>) -&gt; <span class="literal">None</span>:</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.logger.log(level, message, **kw)</span><br><span class="line"><span class="comment"># 初始化爬虫</span></span><br><span class="line"><span class="meta">    @classmethod</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">from_crawler</span>(<span class="params">cls, crawler: Crawler, *args: <span class="type">Any</span>, **kwargs: <span class="type">Any</span></span>) -&gt; Self:</span><br><span class="line">        spider = cls(*args, **kwargs)</span><br><span class="line">        spider._set_crawler(crawler)</span><br><span class="line">        <span class="keyword">return</span> spider</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_set_crawler</span>(<span class="params">self, crawler: Crawler</span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">        <span class="variable language_">self</span>.crawler: Crawler = crawler</span><br><span class="line">        <span class="variable language_">self</span>.settings: BaseSettings = crawler.settings</span><br><span class="line">        crawler.signals.connect(<span class="variable language_">self</span>.close, signals.spider_closed)</span><br><span class="line"><span class="comment"># 核心函数start,使用start_requests()函数</span></span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">start</span>(<span class="params">self</span>) -&gt; AsyncIterator[<span class="type">Any</span>]:</span><br><span class="line"></span><br><span class="line">        <span class="keyword">with</span> warnings.catch_warnings():</span><br><span class="line">            warnings.filterwarnings(</span><br><span class="line">                <span class="string">&quot;ignore&quot;</span>, category=ScrapyDeprecationWarning, module=<span class="string">r&quot;^scrapy\.spiders$&quot;</span></span><br><span class="line">            )</span><br><span class="line">            <span class="keyword">for</span> item_or_request <span class="keyword">in</span> <span class="variable language_">self</span>.start_requests():</span><br><span class="line">                <span class="keyword">yield</span> item_or_request</span><br><span class="line"><span class="comment"># 默认会使用start_urls变量进行爬取</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">start_requests</span>(<span class="params">self</span>) -&gt; Iterable[<span class="type">Any</span>]:</span><br><span class="line">        warnings.warn(</span><br><span class="line">            (</span><br><span class="line">                <span class="string">&quot;The Spider.start_requests() method is deprecated, use &quot;</span></span><br><span class="line">                <span class="string">&quot;Spider.start() instead. If you are calling &quot;</span></span><br><span class="line">                <span class="string">&quot;super().start_requests() from a Spider.start() override, &quot;</span></span><br><span class="line">                <span class="string">&quot;iterate super().start() instead.&quot;</span></span><br><span class="line">            ),</span><br><span class="line">            ScrapyDeprecationWarning,</span><br><span class="line">            stacklevel=<span class="number">2</span>,</span><br><span class="line">        )</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> <span class="variable language_">self</span>.start_urls <span class="keyword">and</span> <span class="built_in">hasattr</span>(<span class="variable language_">self</span>, <span class="string">&quot;start_url&quot;</span>):</span><br><span class="line">            <span class="keyword">raise</span> AttributeError(</span><br><span class="line">                <span class="string">&quot;Crawling could not start: &#x27;start_urls&#x27; not found &quot;</span></span><br><span class="line">                <span class="string">&quot;or empty (but found &#x27;start_url&#x27; attribute instead, &quot;</span></span><br><span class="line">                <span class="string">&quot;did you miss an &#x27;s&#x27;?)&quot;</span></span><br><span class="line">            )</span><br><span class="line"><span class="comment"># 这里的Request并非是request库</span></span><br><span class="line">        <span class="keyword">for</span> url <span class="keyword">in</span> <span class="variable language_">self</span>.start_urls:</span><br><span class="line">            <span class="keyword">yield</span> Request(url, dont_filter=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_parse</span>(<span class="params">self, response: Response, **kwargs: <span class="type">Any</span></span>) -&gt; <span class="type">Any</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.parse(response, **kwargs)</span><br><span class="line"><span class="comment"># 省略其他部分代码</span></span><br></pre></td></tr></table></figure><p>看了源码就可以知道,start,parse两个方法并不会自动调用,换句话说,使用scrapy命令行的时候,其内部是通过调用了这两个方法来进行爬取的.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;爬虫&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#爬虫&quot;&gt;&lt;/a&gt; 爬虫&lt;/h1&gt;
&lt;p&gt;和机器学习一样,我第一次学习python爬虫是没有任何成果的,一开始是听说有这么个东西,就去zlib上随便下了本参考书,由于参考书是十年前的,因</summary>
      
    
    
    
    
    <category term="python" scheme="https://revival-of-hope.github.io/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>终端常用命令一览</title>
    <link href="https://revival-of-hope.github.io/2026/04/08/dynamic-2026-04-08-%E7%BB%88%E7%AB%AF%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7%E4%B8%80%E8%A7%88/"/>
    <id>https://revival-of-hope.github.io/2026/04/08/dynamic-2026-04-08-%E7%BB%88%E7%AB%AF%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7%E4%B8%80%E8%A7%88/</id>
    <published>2026-04-08T00:00:00.000Z</published>
    <updated>2026-04-13T04:10:51.531Z</updated>
    
    <content type="html"><![CDATA[<p>本文主要聚焦于Windows系统,尽管有不少命令是和Linux通用的</p><h1>网络连接</h1><h2 id="ping">ping</h2><ul><li>系统自带组件,基于ICMP实现的网络状态查询工具,换句话说,ping借助TCP/IP协议实现对网络状态的简单解析</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">用法: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]</span><br><span class="line">            [-r count] [-s count] [[-j host-list] | [-k host-list]]</span><br><span class="line">            [-w <span class="built_in">timeout</span>] [-R] [-S srcaddr] [-c compartment] [-p]</span><br><span class="line">            [-4] [-6] target_name</span><br><span class="line"></span><br><span class="line">选项:</span><br><span class="line">    -t             Ping 指定的主机，直到停止。</span><br><span class="line">                   若要查看统计信息并继续操作，请键入 Ctrl+Break；</span><br><span class="line">                   若要停止，请键入 Ctrl+C。</span><br><span class="line">    -a             将地址解析为主机名。</span><br><span class="line">    -n count       要发送的回显请求数。</span><br><span class="line">    -l size        发送缓冲区大小。</span><br><span class="line">    -f             在数据包中设置“不分段”标记(仅适用于 IPv4)。</span><br><span class="line">    -i TTL         生存时间。</span><br><span class="line">    -v TOS         服务类型(仅适用于 IPv4。该设置已被弃用，</span><br><span class="line">                   对 IP 标头中的服务类型字段没有任何</span><br><span class="line">                   影响)。</span><br><span class="line">    -r count       记录计数跃点的路由(仅适用于 IPv4)。</span><br><span class="line">    -s count       计数跃点的时间戳(仅适用于 IPv4)。</span><br><span class="line">    -j host-list   与主机列表一起使用的松散源路由(仅适用于 IPv4)。</span><br><span class="line">    -k host-list    与主机列表一起使用的严格源路由(仅适用于 IPv4)。</span><br><span class="line">    -w <span class="built_in">timeout</span>     等待每次回复的超时时间(毫秒)。</span><br><span class="line">    -R             同样使用路由标头测试反向路由(仅适用于 IPv6)。</span><br><span class="line">                   根据 RFC 5095，已弃用此路由标头。</span><br><span class="line">                   如果使用此标头，某些系统可能丢弃</span><br><span class="line">                   回显请求。</span><br><span class="line">    -S srcaddr     要使用的源地址。</span><br><span class="line">    -c compartment 路由隔离舱标识符。</span><br><span class="line">    -p             Ping Hyper-V 网络虚拟化提供程序地址。</span><br><span class="line">    -4             强制使用 IPv4。</span><br><span class="line">    -6             强制使用 IPv6。</span><br></pre></td></tr></table></figure><p><strong>用例</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ping google.com</span><br><span class="line"><span class="comment"># 判断VPN是否有效的最快方法</span></span><br></pre></td></tr></table></figure><h2 id="curl">curl</h2><ul><li><a href="https://en.wikipedia.org/wiki/CURL">wiki</a><br>curl,意为&quot;Client for URLs&quot;,是ping的上位替代,可以对指定网页采用多种方式进行查询,支持几乎所有的主流通信协议.由于是开源项目,几乎所有的操作系统都会预先安装.</li></ul><p><strong>常用参数一览</strong></p><h3 id="curl-常用参数速查表">curl 常用参数速查表</h3><table><thead><tr><th style="text-align:left">参数</th><th style="text-align:left">全称</th><th style="text-align:left">功能描述</th><th style="text-align:left">典型示例</th></tr></thead><tbody><tr><td style="text-align:left"><strong><code>-I</code></strong></td><td style="text-align:left"><code>--head</code></td><td style="text-align:left"><strong>仅获取响应头</strong>。常用于检查服务器状态、文件大小或链接有效性。</td><td style="text-align:left"><code>curl -I https://google.com</code></td></tr><tr><td style="text-align:left"><strong><code>-v</code></strong></td><td style="text-align:left"><code>--verbose</code></td><td style="text-align:left"><strong>调试模式</strong>。显示详细的通信过程，包括请求头、响应头及 TLS 握手。</td><td style="text-align:left"><code>curl -v https://google.com</code></td></tr><tr><td style="text-align:left"><strong><code>-X</code></strong></td><td style="text-align:left"><code>--request</code></td><td style="text-align:left"><strong>指定请求方法</strong>。默认为 GET，可手动指定 POST, PUT, DELETE 等。</td><td style="text-align:left"><code>curl -X POST https://api.com</code></td></tr><tr><td style="text-align:left"><strong><code>-d</code></strong></td><td style="text-align:left"><code>--data</code></td><td style="text-align:left"><strong>发送数据</strong>。用于 POST 请求向服务器提交表单数据或 JSON。</td><td style="text-align:left"><code>curl -d &quot;id=1&quot; https://api.com</code></td></tr><tr><td style="text-align:left"><strong><code>-H</code></strong></td><td style="text-align:left"><code>--header</code></td><td style="text-align:left"><strong>设置请求头</strong>。常用于定义 Content-Type 或传入 API Token。</td><td style="text-align:left"><code>curl -H &quot;Auth: 123&quot; https://api.com</code></td></tr><tr><td style="text-align:left"><strong><code>-L</code></strong></td><td style="text-align:left"><code>--location</code></td><td style="text-align:left"><strong>跟随重定向</strong>。当页面发生 301/302 跳转时，自动追踪到目标页。</td><td style="text-align:left"><code>curl -L http://google.com</code></td></tr><tr><td style="text-align:left"><strong><code>-o</code></strong></td><td style="text-align:left"><code>--output</code></td><td style="text-align:left"><strong>保存为文件</strong>。将下载内容写入指定路径，而非直接打印在终端。</td><td style="text-align:left"><code>curl -o test.html https://abc.com</code></td></tr><tr><td style="text-align:left"><strong><code>-O</code></strong></td><td style="text-align:left"><code>--remote-name</code></td><td style="text-align:left"><strong>远程同名保存</strong>。使用 URL 中的文件名作为本地文件名保存。</td><td style="text-align:left"><code>curl -O https://abc.com/v1.zip</code></td></tr><tr><td style="text-align:left"><strong><code>-u</code></strong></td><td style="text-align:left"><code>--user</code></td><td style="text-align:left"><strong>身份认证</strong>。用于输入服务器的用户名和密码（Basic Auth）。</td><td style="text-align:left"><code>curl -u user:pass https://api.com</code></td></tr><tr><td style="text-align:left"><strong><code>-k</code></strong></td><td style="text-align:left"><code>--insecure</code></td><td style="text-align:left"><strong>忽略 SSL 校验</strong>。访问证书过期或自签名的 HTTPS 站点时使用。</td><td style="text-align:left"><code>curl -k https://expired-site.com</code></td></tr><tr><td style="text-align:left"><strong><code>-s</code></strong></td><td style="text-align:left"><code>--silent</code></td><td style="text-align:left"><strong>静默模式</strong>。不显示进度条或错误信息，常用于脚本自动化。</td><td style="text-align:left"><code>curl -s https://api.com</code></td></tr><tr><td style="text-align:left"><strong><code>--json</code></strong></td><td style="text-align:left"><code>--json</code></td><td style="text-align:left"><strong>发送 JSON</strong>（新版本）。自动设置 Content-Type 并以 POST 方式发送。</td><td style="text-align:left"><code>curl --json '&#123;&quot;id&quot;:1&#125;' https://api.com</code></td></tr></tbody></table><p><strong>用例</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">curl google.com</span><br><span class="line"><span class="comment"># 不带任何参数的curl也能返回足够多的信息</span></span><br><span class="line">StatusCode        : 200</span><br><span class="line">StatusDescription : OK</span><br><span class="line">Content           : (网页内容)</span><br><span class="line">ParsedHtml        : mshtml.HTMLDocumentClass</span><br><span class="line">RawContentLength  : 80569</span><br></pre></td></tr></table></figure><h2 id="route">route</h2><ul><li><a href="https://en.wikipedia.org/wiki/Route_(command)">wiki</a><br>用于查看和修改本电脑的IP路由表, 一般用不上</li></ul><h2 id="ipconfig">ipconfig</h2><ul><li><a href="https://en.wikipedia.org/wiki/Ipconfig">wiki</a></li></ul><blockquote><p><a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ipconfig">微软官网介绍</a><br>Displays <strong>all current TCP/IP network configuration values</strong> and refreshes Dynamic Host Configuration Protocol (DHCP) and Domain Name System (DNS) settings. Used without parameters, ipconfig displays Internet Protocol version 4 (IPv4) and IPv6 addresses, subnet mask, and default gateway for all adapters.</p></blockquote><ul><li>即ipconfig用于查看自己电脑的TCP/IP网络配置</li></ul><h2 id="nslookup">nslookup</h2><ul><li><a href="https://en.wikipedia.org/wiki/Nslookup">wiki</a><br>nslookup(Name System Lookup)用于查询DNS记录,检查DNS服务器是否正常</li></ul><h2 id="ssh">ssh</h2><blockquote><p><a href="https://en.wikipedia.org/wiki/Secure_Shell">wiki</a><br>SSH(Secure Shell)协议是一种加密网络协议，用于在不安全的网络上安全地运行网络服务.通常用于登录远程计算机的shell或命令行界面(CLI)，并在远程服务器上执行命令.</p></blockquote><h3 id="基础操作">基础操作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ssh root@100.80.251.1</span><br><span class="line"><span class="comment"># 输入密码</span></span><br><span class="line"><span class="comment"># 在远程服务器中进行操作</span></span><br></pre></td></tr></table></figure><h3 id="进阶操作-待补充">进阶操作(待补充)</h3><h2 id="wget">wget</h2><ul><li><a href="https://en.wikipedia.org/wiki/Wget">wiki</a></li></ul><blockquote><p>GNU Wget (or just Wget, formerly Geturl, also written as its package name, wget) is a computer program that retrieves content from web servers. It is part of the GNU Project. Its name derives from “World Wide Web” and “get”, a HTTP request method. It supports <strong>downloading via HTTP, HTTPS, and FTP</strong>.</p></blockquote><p>如上文所说,wget是一个用来下载网络资源的工具,尽管最初在Linux中使用,但Windows Powershell会通过管道运算符复现对应的功能.</p><ul><li>大多数场景下curl可以直接代替wget,因此wget的出现频率正不断下降</li></ul><p><strong>使用示例</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\user&gt; wget www.baidu.com</span><br><span class="line"></span><br><span class="line">StatusCode        : 200</span><br><span class="line">StatusDescription : OK</span><br><span class="line">Content           : &lt;!DOCTYPE html&gt;&lt;!--STATUS OK--&gt;&lt;html&gt;&lt;<span class="built_in">head</span>&gt;&lt;meta http-equiv=<span class="string">&quot;Content-Type&quot;</span> content=<span class="string">&quot;text/html;chars</span></span><br><span class="line"><span class="string">                    et=utf-8&quot;</span>&gt;&lt;meta http-equiv=<span class="string">&quot;X-UA-Compatible&quot;</span> content=<span class="string">&quot;IE=edge,chrome=1&quot;</span>&gt;&lt;meta content=<span class="string">&quot;origin-when-</span></span><br><span class="line"><span class="string">                    cr...</span></span><br><span class="line"><span class="string">RawContent        : HTTP/1.1 200 OK</span></span><br><span class="line"><span class="string">                    Bdpagetype: 1</span></span><br><span class="line"><span class="string">                    Bdqid: 0x926a898e00009a46</span></span><br><span class="line"><span class="string">                    Connection: keep-alive</span></span><br><span class="line"><span class="string">                    Content-Length: 632441</span></span><br><span class="line"><span class="string">                    Content-Type: text/html; charset=utf-8</span></span><br><span class="line"><span class="string">                    Date: Wed, 08 Apr 2026 10:24:26 GMT</span></span><br><span class="line"><span class="string">                    P3P: CP=&quot;</span> OTI DS...</span><br><span class="line">Forms             : &#123;form&#125;</span><br><span class="line">Headers           : &#123;[Bdpagetype, 1], [Bdqid, 0x926a898e00009a46], [Connection, keep-alive], [Content-Length, 632441]..</span><br><span class="line">                    .&#125;</span><br><span class="line">Images            : &#123;@&#123;innerHTML=; innerText=; outerHTML=&lt;img src=<span class="string">&quot;https://pss.bdstatic.com/static/superman/img/topnav/</span></span><br><span class="line"><span class="string">                    newfanyi-da0cea8f7e.png&quot;</span>&gt;; outerText=; tagName=IMG; src=https://pss.bdstatic.com/static/superman/im</span><br><span class="line">                    g/topnav/newfanyi-da0cea8f7e.png&#125;, @&#123;innerHTML=; innerText=; outerHTML=&lt;img src=<span class="string">&quot;https://pss.bdstat</span></span><br><span class="line"><span class="string">                    ic.com/static/superman/img/topnav/newxueshuicon-a5314d5c83.png&quot;</span>&gt;; outerText=; tagName=IMG; src=http</span><br><span class="line">                    s://pss.bdstatic.com/static/superman/img/topnav/newxueshuicon-a5314d5c83.png&#125;, @&#123;innerHTML=; innerT</span><br><span class="line">                    ext=; outerHTML=&lt;img src=<span class="string">&quot;https://pss.bdstatic.com/static/superman/img/topnav/newbaike-889054f349.p</span></span><br><span class="line"><span class="string">                    ng&quot;</span>&gt;; outerText=; tagName=IMG; src=https://pss.bdstatic.com/static/superman/img/topnav/newbaike-889</span><br><span class="line">                    054f349.png&#125;, @&#123;innerHTML=; innerText=; outerHTML=&lt;img src=<span class="string">&quot;https://pss.bdstatic.com/static/superma</span></span><br><span class="line"><span class="string">                    n/img/topnav/newzhidao-da1cf444b0.png&quot;</span>&gt;; outerText=; tagName=IMG; src=https://pss.bdstatic.com/stat</span><br><span class="line">                    ic/superman/img/topnav/newzhidao-da1cf444b0.png&#125;...&#125;</span><br><span class="line">InputFields       : &#123;@&#123;innerHTML=; innerText=; outerHTML=&lt;input name=<span class="string">&quot;ie&quot;</span> <span class="built_in">type</span>=<span class="string">&quot;hidden&quot;</span> value=<span class="string">&quot;utf-8&quot;</span>&gt;; outerText=; tag</span><br><span class="line">                    Name=INPUT; name=ie; <span class="built_in">type</span>=hidden; value=utf-8&#125;, @&#123;innerHTML=; innerText=; outerHTML=&lt;input name=<span class="string">&quot;f&quot;</span></span><br><span class="line">                     <span class="built_in">type</span>=<span class="string">&quot;hidden&quot;</span> value=<span class="string">&quot;8&quot;</span>&gt;; outerText=; tagName=INPUT; name=f; <span class="built_in">type</span>=hidden; value=8&#125;, @&#123;innerHTML=;</span><br><span class="line">                    innerText=; outerHTML=&lt;input name=<span class="string">&quot;rsv_bp&quot;</span> <span class="built_in">type</span>=<span class="string">&quot;hidden&quot;</span> value=<span class="string">&quot;1&quot;</span>&gt;; outerText=; tagName=INPUT; nam</span><br><span class="line">                    e=rsv_bp; <span class="built_in">type</span>=hidden; value=1&#125;, @&#123;innerHTML=; innerText=; outerHTML=&lt;input name=<span class="string">&quot;rsv_idx&quot;</span> <span class="built_in">type</span>=<span class="string">&quot;hi</span></span><br><span class="line"><span class="string">                    dden&quot;</span> value=<span class="string">&quot;1&quot;</span>&gt;; outerText=; tagName=INPUT; name=rsv_idx; <span class="built_in">type</span>=hidden; value=1&#125;...&#125;</span><br><span class="line">Links             : &#123;@&#123;innerHTML=百度首页; innerText=百度首页; outerHTML=&lt;a class=<span class="string">&quot;toindex&quot;</span> href=<span class="string">&quot;/&quot;</span>&gt;百度首页&lt;/a&gt;; oute</span><br><span class="line">                    rText=百度首页; tagName=A; class=toindex; href=/&#125;, @&#123;innerHTML=设置&lt;i class=<span class="string">&quot;c-icon c-icon-triangle</span></span><br><span class="line"><span class="string">                    -down&quot;</span>&gt;&lt;/i&gt;; innerText=设置; outerHTML=&lt;a name=<span class="string">&quot;tj_settingicon&quot;</span> class=<span class="string">&quot;pf&quot;</span> href=<span class="string">&quot;javascript:;&quot;</span>&gt;设置</span><br><span class="line">                    &lt;i class=<span class="string">&quot;c-icon c-icon-triangle-down&quot;</span>&gt;&lt;/i&gt;&lt;/a&gt;; outerText=设置; tagName=A; name=tj_settingicon; cl</span><br><span class="line">                    ass=pf; href=javascript:;&#125;, @&#123;innerHTML=登录; innerText=登录; outerHTML=&lt;a name=<span class="string">&quot;tj_login&quot;</span> class=<span class="string">&quot;l</span></span><br><span class="line"><span class="string">                    b&quot;</span> onclick=<span class="string">&quot;return false;&quot;</span> href=<span class="string">&quot;https://passport.baidu.com/v2/?login&amp;amp;tpl=mn&amp;amp;u=http%3A%2F%2</span></span><br><span class="line"><span class="string">                    Fwww.baidu.com%2F&amp;amp;sms=5&quot;</span>&gt;登录&lt;/a&gt;; outerText=登录; tagName=A; name=tj_login; class=lb; onclick=</span><br><span class="line">                    <span class="built_in">return</span> <span class="literal">false</span>;; href=https://passport.baidu.com/v2/?login&amp;amp;tpl=mn&amp;amp;u=http%3A%2F%2Fwww.baidu.co</span><br><span class="line">                    m%2F&amp;amp;sms=5&#125;, @&#123;innerHTML=</span><br><span class="line"></span><br><span class="line">                                                新闻</span><br><span class="line"></span><br><span class="line">                                        ; innerText= 新闻 ; outerHTML=&lt;a class=<span class="string">&quot;mnav c-font-normal c-color-t&quot;</span> href=<span class="string">&quot;htt</span></span><br><span class="line"><span class="string">                    p://news.baidu.com&quot;</span> target=<span class="string">&quot;_blank&quot;</span>&gt;</span><br><span class="line"></span><br><span class="line">                                                新闻</span><br><span class="line"></span><br><span class="line">                                        &lt;/a&gt;; outerText= 新闻 ; tagName=A; class=mnav c-font-normal c-color-t; href=htt</span><br><span class="line">                    p://news.baidu.com; target=_blank&#125;...&#125;</span><br><span class="line">ParsedHtml        : mshtml.HTMLDocumentClass</span><br><span class="line">RawContentLength  : 632441</span><br></pre></td></tr></table></figure><ul><li>从返回内容也能看出来基础功能与curl没什么区别</li></ul><h1>包管理器</h1><h2 id="scoop-推荐">Scoop(推荐)</h2><ul><li><a href="https://scoop.sh/">官网</a></li></ul><p>官网的介绍很简单,只有一行:</p><blockquote><p>A command-line installer for Windows</p></blockquote><p>由于微软自己开发了Winget包管理器,故不会预先安装Scoop,需要我们用脚本激活:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser</span><br><span class="line">Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression</span><br></pre></td></tr></table></figure><p>在终端输入scoop即可查询使用方式:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\user&gt; scoop</span><br><span class="line">Usage: scoop &lt;<span class="built_in">command</span>&gt; [&lt;args&gt;]</span><br><span class="line"></span><br><span class="line">Available commands are listed below.</span><br><span class="line"></span><br><span class="line">Type <span class="string">&#x27;scoop help &lt;command&gt;&#x27;</span> to get more <span class="built_in">help</span> <span class="keyword">for</span> a specific <span class="built_in">command</span>.</span><br><span class="line"></span><br><span class="line">Command    Summary</span><br><span class="line">-------    -------</span><br><span class="line"><span class="built_in">alias</span>      Manage scoop aliases</span><br><span class="line">bucket     Manage Scoop buckets</span><br><span class="line">cache      Show or clear the download cache</span><br><span class="line"><span class="built_in">cat</span>        Show content of specified manifest.</span><br><span class="line">checkup    Check <span class="keyword">for</span> potential problems</span><br><span class="line">cleanup    Cleanup apps by removing old versions</span><br><span class="line">config     Get or <span class="built_in">set</span> configuration values</span><br><span class="line">create     Create a custom app manifest</span><br><span class="line">depends    List dependencies <span class="keyword">for</span> an app, <span class="keyword">in</span> the order they<span class="string">&#x27;ll be installed</span></span><br><span class="line"><span class="string">download   Download apps in the cache folder and verify hashes</span></span><br><span class="line"><span class="string">export     Exports installed apps, buckets (and optionally configs) in JSON format</span></span><br><span class="line"><span class="string">help       Show help for a command</span></span><br><span class="line"><span class="string">hold       Hold an app to disable updates</span></span><br><span class="line"><span class="string">home       Opens the app homepage</span></span><br><span class="line"><span class="string">import     Imports apps, buckets and configs from a Scoopfile in JSON format</span></span><br><span class="line"><span class="string">info       Display information about an app</span></span><br><span class="line"><span class="string">install    Install apps</span></span><br><span class="line"><span class="string">list       List installed apps</span></span><br><span class="line"><span class="string">prefix     Returns the path to the specified app</span></span><br><span class="line"><span class="string">reset      Reset an app to resolve conflicts</span></span><br><span class="line"><span class="string">search     Search available apps</span></span><br><span class="line"><span class="string">shim       Manipulate Scoop shims</span></span><br><span class="line"><span class="string">status     Show status and check for new app versions</span></span><br><span class="line"><span class="string">unhold     Unhold an app to enable updates</span></span><br><span class="line"><span class="string">uninstall  Uninstall an app</span></span><br><span class="line"><span class="string">update     Update apps, or Scoop itself</span></span><br><span class="line"><span class="string">virustotal Look for app&#x27;</span>s <span class="built_in">hash</span> or url on virustotal.com</span><br><span class="line"><span class="built_in">which</span>      Locate a shim/executable (similar to <span class="string">&#x27;which&#x27;</span> on Linux)</span><br></pre></td></tr></table></figure><p>scoop解决了应用安装路径不统一的问题,将安装的应用一律放到scoop文件夹中,而且用户可以自己指定默认安装位置.</p><p>尽管大多数应用都可以很方便的使用scoop找到,但对于已经习惯了普通安装方式的我来说还是用的不太顺手.</p><h2 id="winget-不推荐">Winget(不推荐)</h2><blockquote><p><a href="https://learn.microsoft.com/zh-cn/windows/package-manager/winget/">官方说明</a><br>WinGet 是一种命令行工具，使用户能够在 Windows 10、Windows 11 和 Windows Server 2025 计算机上发现、安装、升级、删除和配置应用程序</p></blockquote><p>具体用法只需要在终端输入winget即可了解:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\user&gt; winget</span><br><span class="line"></span><br><span class="line">WinGet 命令行实用工具可从命令行安装应用程序和其他程序包。</span><br><span class="line"></span><br><span class="line">使用情况: winget  [&lt;命令&gt;] [&lt;选项&gt;]</span><br><span class="line"></span><br><span class="line">下列命令有效:</span><br><span class="line">  install    安装给定的程序包</span><br><span class="line">  show       显示包的相关信息</span><br><span class="line">  <span class="built_in">source</span>     管理程序包的来源</span><br><span class="line">  search     查找并显示程序包的基本信息</span><br><span class="line">  list       显示已安装的程序包</span><br><span class="line">  upgrade    显示并执行可用升级</span><br><span class="line">  uninstall  卸载给定的程序包</span><br><span class="line">  <span class="built_in">hash</span>       哈希安装程序的帮助程序</span><br><span class="line">  validate   验证清单文件</span><br><span class="line">  settings   打开设置或设置管理员设置</span><br><span class="line">  features   显示实验性功能的状态</span><br><span class="line">  <span class="built_in">export</span>     导出已安装程序包的列表</span><br><span class="line">  import     安装文件中的所有程序包</span><br><span class="line">  pin        管理包钉</span><br><span class="line">  configure  将系统配置为所需状态</span><br><span class="line">  download   从给定的程序包下载安装程序</span><br><span class="line">  repair     修复所选包</span><br><span class="line">  dscv3      DSC v3 资源命令</span><br><span class="line">  mcp        MCP 信息</span><br></pre></td></tr></table></figure><ol><li>要搜索某个工具，请键入 winget search <appname></li><li>确认你需要的工具可用后，可以通过键入 来winget install <appname>该工具。 WinGet 工具会启动安装程序，将应用程序安装在你的电脑上</li></ol><p><strong>使用举例</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\uesr&gt; winget search npm</span><br><span class="line">名称            ID              版本          匹配             源</span><br><span class="line">---------------------------------------------------------------------</span><br><span class="line">Node.js         OpenJS.NodeJS   25.9.0        Command: npm     winget</span><br><span class="line">Node.js 10      OpenJS.NodeJS.… 10.24.1       Command: npm     winget</span><br><span class="line">Node.js 12      OpenJS.NodeJS.… 12.22.12      Command: npm     winget</span><br><span class="line">Node.js 14      OpenJS.NodeJS.… 14.21.3       Command: npm     winget</span><br><span class="line"><span class="comment"># 一大堆类似的包</span></span><br><span class="line"></span><br><span class="line">PS C:\Users\user&gt; winget install bun</span><br><span class="line">找到多个与输入条件匹配的程序包。请修改输入。</span><br><span class="line">名称             ID           源</span><br><span class="line">-------------------------------------</span><br><span class="line">Bundle Generator 9NBLGGH43PMQ msstore</span><br><span class="line">Bun              Oven-sh.Bun  winget</span><br><span class="line"><span class="comment"># 由于我这里已经安装了,所以就会报错</span></span><br></pre></td></tr></table></figure><p>winget默认全局安装,如果不操心应用安装位置的话,使用winget比上官网找资源是要快一点的;但捣鼓计算机的一般都很在意应用的安装位置,所以winget基本就没什么用了…</p><h1>基本操作</h1><h1>部署</h1><p><strong>博客命令</strong></p><h2 id="hexo">hexo</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">hexo new post/draft/page <span class="comment"># 使用模板</span></span><br><span class="line">hexo publish draft 文章名</span><br><span class="line">hexo d          <span class="comment"># push my blog  deploy</span></span><br><span class="line">hexo new  +name     <span class="comment"># new blog</span></span><br><span class="line">hexo g            <span class="comment"># apply changes  generate</span></span><br><span class="line">hexo s            <span class="comment"># local static html 预览</span></span><br><span class="line">hexo g -d   <span class="comment"># 一次完成</span></span><br></pre></td></tr></table></figure><blockquote><p>draft也就是草稿，在使用hexo创建文章时，可以先指定为草稿:</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo new draft &lt;title&gt;</span><br></pre></td></tr></table></figure><blockquote><p>完成之后，使用publish命令将draft转移到post下:</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo publish &lt;title&gt;</span><br></pre></td></tr></table></figure><h1>–version</h1><p>尽管很多工具都支持<strong>工具名 --version</strong>的方式查询版本,但遗憾的是有一些工具<strong>不愿意</strong>沿用这个惯例,所以只好单独在这里分类讨论了</p><ul><li>事实上,尽管大多数工具都统一使用–参数的形式,但也有不少工具使用-参数的形式,真的就没人想过统一一下吗…</li></ul><h3 id="构建工具">构建工具</h3><ul><li>gcc/g++</li><li>make</li><li>tar</li><li>git</li><li>cmake</li><li>git</li><li>docker</li><li>curl</li></ul><h3 id="编程">编程</h3><ul><li>python</li><li>node</li><li>npm</li><li>ruby</li><li>java: 使用<code>java -version</code>也可以</li></ul><h3 id="不支持的">不支持的</h3><ul><li>go: <code>go version</code>,何必少写那两横呢</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文主要聚焦于Windows系统,尽管有不少命令是和Linux通用的&lt;/p&gt;
&lt;h1&gt;网络连接&lt;/h1&gt;
&lt;h2 id=&quot;ping&quot;&gt;ping&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;系统自带组件,基于ICMP实现的网络状态查询工具,换句话说,ping借助TCP/IP协议实现对网络状态</summary>
      
    
    
    
    <category term="动态更新" scheme="https://revival-of-hope.github.io/categories/%E5%8A%A8%E6%80%81%E6%9B%B4%E6%96%B0/"/>
    
    
    <category term="合集" scheme="https://revival-of-hope.github.io/tags/%E5%90%88%E9%9B%86/"/>
    
  </entry>
  
  <entry>
    <title>2026-04-07 异步概念剖析</title>
    <link href="https://revival-of-hope.github.io/2026/04/07/archives-2026-04-08-%E5%BC%82%E6%AD%A5%E6%A6%82%E5%BF%B5%E5%89%96%E6%9E%90/"/>
    <id>https://revival-of-hope.github.io/2026/04/07/archives-2026-04-08-%E5%BC%82%E6%AD%A5%E6%A6%82%E5%BF%B5%E5%89%96%E6%9E%90/</id>
    <published>2026-04-07T00:00:00.000Z</published>
    <updated>2026-04-08T11:52:45.012Z</updated>
    
    <content type="html"><![CDATA[<p>接触前后端一段时间了,我对<strong>异步</strong>这个常用概念仍然没有一个非常好的认识,因此写了这篇文章来进行学习.</p><h1 id="异步概念总览"><a class="markdownIt-Anchor" href="#异步概念总览"></a> 异步概念总览</h1><p>异步这一概念是相对于同步来说的,我们先来看看<a href="https://en.wikipedia.org/wiki/Synchronization">同步的定义</a>:</p><blockquote><p>In computer science , synchronization is the coordination of simultaneous threads or processes to complete a task with <strong>correct runtime order</strong> and no unexpected race conditions; see synchronization (computer science) for details.</p></blockquote><p>也就是说,同步就是顺序执行了,那么异步就是<strong>不按顺序执行</strong>,那到底是怎么执行的呢?</p><p>非常奇怪的是,你很难找到一个文档去真正的讲清楚异步是什么,我翻阅了各大问答网站,wiki,博客,都很难找到一个真正详尽的解答,这反过来说明这个概念并非如想象中的那般简单.</p><p>让我们看看AI怎么说(尽管我用的是免费模型):</p><h2 id="gemini说"><a class="markdownIt-Anchor" href="#gemini说"></a> Gemini说</h2><p>从物理机制和执行模型来看，<strong>异步（Asynchronous）</strong> 的本质是：<strong>允许调用方在发起一个耗时操作后，不等待结果返回，直接继续执行后续任务。</strong></p><p>为了透彻理解，我们需要从“阻塞”与“让渡”的核心逻辑出发：</p><h3 id="1-核心模型同步-vs-异步"><a class="markdownIt-Anchor" href="#1-核心模型同步-vs-异步"></a> 1. 核心模型：同步 vs. 异步</h3><ul><li><strong>同步 (Synchronous)</strong>：线性执行。CPU 发起 I/O 请求（如读文件、连数据库）后，必须原地“挂起”等待硬件返回数据。此时 CPU 虽然空闲，但无法处理其他任务，这就是<strong>阻塞</strong>。</li><li><strong>异步 (Asynchronous)</strong>：非线性执行。CPU 发起 I/O 请求后，立即标记该任务为“等待中”，然后转身去处理其他代码。当硬件数据准备好时，通过<strong>事件通知</strong>（回调、信号或 Promise）告知 CPU 回来处理结果。</li></ul><hr /><h3 id="2-生活化类比餐厅模式"><a class="markdownIt-Anchor" href="#2-生活化类比餐厅模式"></a> 2. 生活化类比：餐厅模式</h3><ul><li><strong>同步模式（排队取餐）</strong>：你在柜台点完餐，必须站在收银台前等着厨师做。厨师没做好，你不能走，后面的人也点不了餐。你的时间被彻底阻塞。</li><li><strong>异步模式（取餐铃）</strong>：你点完餐，服务员给你一个“取餐铃”（对应编程中的 <code>Future</code> 或 <code>Promise</code>）。你可以回座位玩手机或看书。当铃声响了（对应回调通知），你才过去取餐。<strong>你没有被“阻塞”在柜台。</strong></li></ul><hr /><h3 id="3-底层物理基础为什么需要异步"><a class="markdownIt-Anchor" href="#3-底层物理基础为什么需要异步"></a> 3. 底层物理基础：为什么需要异步？</h3><p>计算机硬件的速度存在极大的<strong>数量级差异</strong>，这是异步存在的根本原因：</p><table><thead><tr><th style="text-align:left">操作</th><th style="text-align:left">消耗 CPU 指令周期（近似）</th><th style="text-align:left">等效人类时间</th></tr></thead><tbody><tr><td style="text-align:left"><strong>CPU 寄存器操作</strong></td><td style="text-align:left">1 周期</td><td style="text-align:left">1 秒</td></tr><tr><td style="text-align:left"><strong>内存访问</strong></td><td style="text-align:left">200 周期</td><td style="text-align:left">3 分钟</td></tr><tr><td style="text-align:left"><strong>SSD 硬盘读取</strong></td><td style="text-align:left">2,000,000 周期</td><td style="text-align:left">23 天</td></tr><tr><td style="text-align:left"><strong>网络请求 (跨海)</strong></td><td style="text-align:left">200,000,000 周期</td><td style="text-align:left"><strong>6 年</strong></td></tr></tbody></table><p><strong>结论</strong>：如果采用同步模式，CPU 在等待一次网络请求时，相当于浪费了人类时间里的“6 年”。异步通过<strong>让渡控制权</strong>，让 CPU 在这“6 年”里去处理成千上万个其他任务。</p><hr /><h3 id="4-常见的实现机制"><a class="markdownIt-Anchor" href="#4-常见的实现机制"></a> 4. 常见的实现机制</h3><p>异步在不同领域有不同的物理表现：</p><ul><li><strong>多线程 (Multi-threading)</strong>：开一个新的员工（线程）去等，主员工继续干活。缺点是切换员工（上下文切换）很累，且费工资（内存占用高）。</li><li><strong>事件循环 (Event Loop)</strong>：如 JavaScript 或 Python 的 <code>asyncio</code>。只有一个员工，但他手里有个待办列表。遇到耗时任务就往列表一扔，做完一个就看一眼列表有没有回信。</li><li><strong>回调函数 (Callback)</strong>：“做完了叫我”。</li><li><strong>Promise / Await</strong>：“给我一个承诺，好了我再继续”。</li></ul><hr /><h3 id="5-关键误区纠偏"><a class="markdownIt-Anchor" href="#5-关键误区纠偏"></a> 5. 关键误区纠偏</h3><ol><li><strong>异步 ≠ 并行</strong>：<ul><li>并行是“多个人同时搬砖”。</li><li>异步是“一个人在等砖拉过来的同时，去拌水泥”。</li></ul></li><li><strong>异步不一定更快</strong>：<ul><li>对于 <strong>计算密集型</strong>（纯算数学题），异步反而慢，因为多了调度开销。</li><li>对于 <strong>I/O 密集型</strong>（等网络、等文件），异步能极大地提高系统的<strong>并发能力（吞吐量）</strong>。</li></ul></li></ol><hr /><h2 id="概念辨析"><a class="markdownIt-Anchor" href="#概念辨析"></a> 概念辨析</h2><blockquote><p>AI对异步的概念定义如下:<br /><strong>允许调用方在发起一个耗时操作后，不等待结果返回，直接继续执行后续任务。</strong></p></blockquote><p>从这个角度来看,我们平常所说的<strong>多线程</strong>并不是异步,多线程是将同一个任务拆分成了很多小任务去执行,但异步并不存在将任务分开执行这个概念,而是存在一个顺序上的先后划分.</p><p>因此,我们可以这样解释异步:</p><blockquote><p>将耗时的,或者需要其他操作来返回结果的任务挂起,优先执行其他任务,等到需要执行这个任务时再执行.</p></blockquote><p>以python爬虫为例:</p><blockquote><p>通过 asyncio 库和 await 关键字，程序在发起网络请求后不再原地阻塞，而是去执行其他请求，直到数据返回再回调处理。这种模式在单线程下实现了高并发，极大地提升了 I/O 密集型任务的吞吐量</p></blockquote><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">import</span> aiohttp</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">fetch</span>(<span class="params">session, url</span>):</span><br><span class="line">    <span class="comment"># 发送请求并挂起，等待期间 CPU 可以处理其他任务</span></span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">with</span> session.get(url) <span class="keyword">as</span> response:</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">await</span> response.text()</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    urls = [<span class="string">&quot;https://example.com&quot;</span> <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">10</span>)]</span><br><span class="line">    <span class="comment"># 使用 aiohttp 管理异步连接池</span></span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">with</span> aiohttp.ClientSession() <span class="keyword">as</span> session:</span><br><span class="line">        <span class="comment"># 并发创建所有爬取任务</span></span><br><span class="line">        tasks = [fetch(session, url) <span class="keyword">for</span> url <span class="keyword">in</span> urls]</span><br><span class="line">        <span class="comment"># 统一等待所有任务完成</span></span><br><span class="line">        pages = <span class="keyword">await</span> asyncio.gather(*tasks)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;成功爬取 <span class="subst">&#123;<span class="built_in">len</span>(pages)&#125;</span> 个页面&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动事件循环</span></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    asyncio.run(main())</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;接触前后端一段时间了,我对&lt;strong&gt;异步&lt;/strong&gt;这个常用概念仍然没有一个非常好的认识,因此写了这篇文章来进行学习.&lt;/p&gt;
&lt;h1 id=&quot;异步概念总览&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#异步概念总览&quot;&gt;&lt;/a&gt; 异步</summary>
      
    
    
    
    
    <category term="全栈" scheme="https://revival-of-hope.github.io/tags/%E5%85%A8%E6%A0%88/"/>
    
  </entry>
  
  <entry>
    <title>2026-04-03 cpp工程构建-自底向上方法</title>
    <link href="https://revival-of-hope.github.io/2026/04/03/archives-2026-04-03-cmake%E5%92%8Cxmake%E5%AD%A6%E4%B9%A0/"/>
    <id>https://revival-of-hope.github.io/2026/04/03/archives-2026-04-03-cmake%E5%92%8Cxmake%E5%AD%A6%E4%B9%A0/</id>
    <published>2026-04-03T00:00:00.000Z</published>
    <updated>2026-04-12T09:05:53.207Z</updated>
    
    <content type="html"><![CDATA[<h1>前置知识: cpp编译原理(4/8)</h1><h2 id="编译器种类">编译器种类</h2><p>市面上有三大主流编译器</p><ol><li>微软开发的<strong>MSVC</strong>(Microsoft Visual C++),集成在VS中,另外一个在Windows系统常用的<strong>MinGW</strong>编译器则是GCC的物理移植版</li><li>开源的<strong>GCC</strong>(GNU Compiler Collection),主要在Linux中使用</li><li>基于GCC和LLVM(开源编译器后端)的<strong>Clang</strong>,由苹果公司赞助,是macOS唯一官方支持的编译器,集成在Xcode中.</li></ol><p>当然现在的电脑性能这么好,用哪个编译器都可以.</p><h2 id="编译的全过程">编译的全过程</h2><p>当我们使用GCC编译Hello World程序时,只需要这样写:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">gcc hello.c -o ./a.out</span><br><span class="line"><span class="comment"># &#x27;./a.out&#x27;是文件名和路径,后缀名可以随便起,写成tho没有后缀或者a.xyz也可以</span></span><br></pre></td></tr></table></figure><p>上述过程可以分解为4个步骤:</p><ol><li>预处理(Preprocessing)</li><li>编译(Compilation)</li><li>汇编(Assembly)</li><li>链接(Linking)</li></ol><p><strong>流水线解释</strong></p><ul><li>预处理: 转换宏定义,删除注释</li><li>编译(狭义): 将cpp源码翻译成汇编代码(人类可读)</li><li>汇编:<ul><li>将汇编代码翻译成<strong>机器指令</strong>(二进制码)</li><li>根据机器指令,地址位置等信息构造<strong>目标文件</strong></li></ul></li><li>链接: 将目标文件与系统库,用户库关联起来,得到可执行文件</li><li>编译(广义): 由于大多数人对cpp的装载过程没有一个清晰的认识,故通常使用编译代指从<code>.cpp</code>到<code>.exe</code>的全过程,也就是说我们一般都用广义的编译概念,很少特指&quot;真正的编译&quot;</li></ul><p>但是,我们所用的编译器如gcc,clang等都是广义上的编译器,也就是说不仅仅做的是编译,而是包揽了从<code>.cpp</code>到<code>.exe</code>的全构建过程.<br>如果用前端和后端的概念来划分的话,是这样的:</p><h4 id="前端-frontend">前端（Frontend）</h4><p><strong>范畴：</strong> 仅包含“编译”这一步的前半部分。</p><ul><li><strong>输入：</strong> 预处理后的源码。</li><li><strong>任务：</strong> 词法分析（Lexical Analysis）、语法分析（Syntax Analysis）、语义分析（Semantic Analysis）、生成<strong>中间表示（IR, Intermediate Representation）</strong>。</li><li><strong>特性：</strong> 与具体的硬件架构（如 x86、ARM）无关，只与语言本身的规则有关。</li></ul><h4 id="后端-backend">后端（Backend）</h4><p><strong>范畴：</strong> 包含“编译”这一步的后半部分，以及“汇编”的全部。</p><ul><li><strong>任务：</strong> * <strong>中端优化（Optimizer）</strong>：对 IR 进行架构无关的优化。<ul><li><strong>代码生成（Code Generator）</strong>：将 IR 转换为特定硬件的<strong>汇编代码</strong>。</li><li><strong>汇编器（Assembler）</strong>：将汇编代码转换为机器指令，产出目标文件。</li></ul></li><li><strong>特性：</strong> 强依赖于硬件架构。</li></ul><h4 id="其他项">其他项</h4><ul><li><strong>预处理（Preprocessing）</strong>：通常被视为编译前的“文本清洁工作”，不属于狭义编译器（Compiler Core）的前后端逻辑。</li><li><strong>链接（Linking）</strong>：属于编译链条的下游，是一个独立的二进制处理过程，不属于编译器（Compiler）的范畴。</li></ul><h1>为什么需要cpp工程构建</h1><ul><li><a href="https://docs.eesast.com/docs/languages/C&amp;C++/multi-file_programming">参考1</a></li><li><a href="https://learn.microsoft.com/zh-cn/cpp/cpp/header-files-cpp?view=msvc-170">参考2</a></li></ul><h2 id="多文件管理">多文件管理</h2><p>在一个文件里导入其他文件中的变量有两种方法:<br><strong>1.使用extern关键字</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 文件 A (data.cpp) </span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="type">int</span> value;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">display</span><span class="params">()</span> <span class="type">const</span> </span>&#123;</span><br><span class="line">        std::cout &lt;&lt; value &lt;&lt; std::endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">Counter g_tracker = &#123;<span class="number">100</span>&#125;;</span><br><span class="line"><span class="type">int</span> global_count = <span class="number">100</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 文件 B (main.cpp)</span></span><br><span class="line"><span class="keyword">extern</span> <span class="type">int</span> global_count;</span><br><span class="line"><span class="keyword">extern</span> Counter g_tracker;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">print_count</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    g_tracker.<span class="built_in">display</span>();</span><br><span class="line">    std::cout &lt;&lt; global_count &lt;&lt; std::endl;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">print_count</span>();</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>也就是说我们需要在用到这个变量的时候使用extern关键字来声明,才能让编译器明白这个变量是要到其他文件中去找的.</p><p><strong>2.使用头文件</strong><br>当需要共享的变量或函数过多时,再一个个写extern不太现实,所以<strong>cpp使用者</strong>设计了单独的文件类型用来存放共享变量(包括常量,外部变量)的声明,也就是.h文件.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// vars.h</span></span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span> VARS_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> VARS_H</span></span><br><span class="line"><span class="comment">// include guard,防止同一个头文件在同一个cpp文件中被多次导入</span></span><br><span class="line"><span class="keyword">extern</span> <span class="type">int</span> shared_val; <span class="comment">// 声明</span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// vars.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;vars.h&quot;</span></span></span><br><span class="line"><span class="type">int</span> shared_val = <span class="number">42</span>; <span class="comment">// 定义</span></span><br></pre></td></tr></table></figure><p>当然,更为常见的是用class来封装变量和函数再放入.h文件,并在对应的cpp文件里实现:<br><strong>cocos示例项目</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//AppDelegate.h</span></span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span>  _APP_DELEGATE_H_</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>  _APP_DELEGATE_H_</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;cocos2d.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span>  <span class="title class_">AppDelegate</span> : <span class="keyword">private</span> cocos2d::Application</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">AppDelegate</span>();</span><br><span class="line">    <span class="keyword">virtual</span> ~<span class="built_in">AppDelegate</span>();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">initGLContextAttrs</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">// _APP_DELEGATE_H_</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">//同名cpp文件,其实起什么名字都行,但为了标准化还是同名为好</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;AppDelegate.h&quot;</span></span></span><br><span class="line"><span class="comment">// 导入后进行实现</span></span><br><span class="line">AppDelegate::<span class="built_in">AppDelegate</span>()</span><br><span class="line">&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">AppDelegate::~<span class="built_in">AppDelegate</span>() </span><br><span class="line">&#123;</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> USE_AUDIO_ENGINE</span></span><br><span class="line">    AudioEngine::<span class="built_in">end</span>();</span><br><span class="line"><span class="meta">#<span class="keyword">elif</span> USE_SIMPLE_AUDIO_ENGINE</span></span><br><span class="line">    SimpleAudioEngine::<span class="built_in">end</span>();</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">AppDelegate::initGLContextAttrs</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// set OpenGL context attributes: red,green,blue,alpha,depth,stencil,multisamplesCount</span></span><br><span class="line">    GLContextAttrs glContextAttrs = &#123;<span class="number">8</span>, <span class="number">8</span>, <span class="number">8</span>, <span class="number">8</span>, <span class="number">24</span>, <span class="number">8</span>, <span class="number">0</span>&#125;;</span><br><span class="line"></span><br><span class="line">    GLView::<span class="built_in">setGLContextAttrs</span>(glContextAttrs);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ...诸如此类的实现</span></span><br></pre></td></tr></table></figure><h3 id="一个标准-h文件的例子">一个标准.h文件的例子</h3><ul><li><code>#pragma once</code>: ‘pragma’ 源自希腊语 ‘pragma’（意为“行动”或“事项”）,代指编译指令,整体的意思是只编译一次,也就是第二次在同一个cpp文件里遇到这个头文件时跳过编译<ul><li>是msvc最早开始启用的预处理指令,之后GCC,Clang也开始支持这个指令,但至今都未纳入cpp标准中</li><li>这个奇怪的名字显然是某个自以为很有修养的工程师提出来的,正常人是不会这么起名的</li></ul></li></ul><p>以下示例显示了头文件中允许的各种声明和定义：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// sample.h</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span> <span class="comment">// #include directive</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> N  <span class="comment">// namespace declaration</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">inline</span> <span class="keyword">namespace</span> P</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">enum class</span> <span class="title class_">colors</span> : <span class="type">short</span> &#123; red, blue, purple, azure &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="type">const</span> <span class="type">double</span> PI = <span class="number">3.14</span>;  <span class="comment">// const and constexpr definitions</span></span><br><span class="line">    <span class="keyword">constexpr</span> <span class="type">int</span> MeaningOfLife&#123; <span class="number">42</span> &#125;;</span><br><span class="line">    <span class="function"><span class="keyword">constexpr</span> <span class="type">int</span> <span class="title">get_meaning</span><span class="params">()</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="built_in">static_assert</span>(MeaningOfLife == <span class="number">42</span>, <span class="string">&quot;unexpected!&quot;</span>); <span class="comment">// static_assert</span></span><br><span class="line">        <span class="keyword">return</span> MeaningOfLife;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">using</span> vstr = std::vector&lt;<span class="type">int</span>&gt;;  <span class="comment">// type alias</span></span><br><span class="line">    <span class="keyword">extern</span> <span class="type">double</span> d; <span class="comment">// extern variable</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> LOG   <span class="comment">// macro definition</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> LOG   <span class="comment">// conditional compilation directive</span></span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">print_to_log</span><span class="params">()</span></span>;</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">my_class</span>   <span class="comment">// regular class definition,</span></span><br><span class="line">    &#123;                <span class="comment">// but no non-inline function definitions</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">friend</span> <span class="keyword">class</span> <span class="title class_">other_class</span>;</span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        <span class="function"><span class="type">void</span> <span class="title">do_something</span><span class="params">()</span></span>;   <span class="comment">// definition in my_class.cpp</span></span><br><span class="line">        <span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">put_value</span><span class="params">(<span class="type">int</span> i)</span> </span>&#123; vals.<span class="built_in">push_back</span>(i); &#125; <span class="comment">// inline OK</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span>:</span><br><span class="line">        vstr vals;</span><br><span class="line">        <span class="type">int</span> i;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">RGB</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="type">short</span> r&#123; <span class="number">0</span> &#125;;  <span class="comment">// member initialization</span></span><br><span class="line">        <span class="type">short</span> g&#123; <span class="number">0</span> &#125;;</span><br><span class="line">        <span class="type">short</span> b&#123; <span class="number">0</span> &#125;;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;  <span class="comment">// template definition</span></span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">value_store</span></span><br><span class="line">    &#123;</span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        <span class="built_in">value_store</span>&lt;T&gt;() = <span class="keyword">default</span>;</span><br><span class="line">        <span class="function"><span class="type">void</span> <span class="title">write_value</span><span class="params">(T val)</span></span></span><br><span class="line"><span class="function">        </span>&#123;</span><br><span class="line">            <span class="comment">//... function definition OK in template</span></span><br><span class="line">        &#125;</span><br><span class="line">    <span class="keyword">private</span>:</span><br><span class="line">        std::vector&lt;T&gt; vals;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;  <span class="comment">// template declaration</span></span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">value_widget</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>你很有可能会问为什么头文件中函数,class,struct等高级数据类型的声明不需要用extern修饰,而单独的变量声明却必须要用extern修饰?<ul><li>extern的原理是: 告诉编译器这个变量在别处已经定义;如果单纯写<code>int x;</code>,那么编译器就认为这是一个未进行初始化的新定义,会为x再次申请4字节的内存,从而引发重定义报错</li><li>函数的声明默认是为extern的</li><li>而类和结构体的声明不需要extern,因为它们本身不产生任何内存分配,只有实例化的对象才需要内存分配</li></ul></li></ul><h3 id="头文件的由来">头文件的由来</h3><p>我们需要明确一个事实:cpp标准从没有规定cpp文件和头文件名字的扩展名要求!<br>事实上如果你将main函数放入x.txt文件中,照样可以正常编译:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># g++根据扩展名推断语言,所以这里需要用-x c++强制指令语言为cpp,而且我们产生的exe文件使用了txt后缀也没报错</span></span><br><span class="line">g++ -x c++ x.txt -o result.txt</span><br></pre></td></tr></table></figure><p>换句话说,<code>.cpp</code>,<code>.h</code>这些后缀只不过是人为约定的而已,你在里面写的内容与文件名可以毫无关系,也就是说,就算你在头文件里实现了函数的定义也没关系,只要你没有导入进两个或更多文件里,就不会导致函数的重定义进而引发编译器的报错.</p><h2 id="复杂项目的处理">复杂项目的处理</h2><p>当然,如果只有一两个文件的话,我们只用g++进行编译也够了,比如有一个<code>main.cpp</code>和一个<code>tools.cpp</code>文件,那么我们只要写:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">g++ main.cpp tools.cpp -o result.exe</span><br></pre></td></tr></table></figure><p>但事实上,我们需要根据各种各样的需求加上各种各样的参数:</p><table><thead><tr><th style="text-align:left">参数</th><th style="text-align:left">分类</th><th style="text-align:left">作用说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><strong><code>-o &lt;file&gt;</code></strong></td><td style="text-align:left">基础</td><td style="text-align:left"><strong>指定输出文件名</strong>。如果不使用，Linux 默认生成 <code>a.out</code>，Windows 默认 <code>a.exe</code>。</td><td style="text-align:left"><code>g++ main.cpp -o app</code></td></tr><tr><td style="text-align:left"><strong><code>-c</code></strong></td><td style="text-align:left">基础</td><td style="text-align:left"><strong>只编译，不链接</strong>。将 <code>.cpp</code> 转化为二进制目标文件 <code>.o</code>，用于大型项目的增量编译。</td><td style="text-align:left"><code>g++ -c tools.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-I &lt;dir&gt;</code></strong></td><td style="text-align:left">基础</td><td style="text-align:left"><strong>指定头文件搜索路径</strong>。当 <code>#include</code> 的头文件不在当前目录或标准库时使用。</td><td style="text-align:left"><code>g++ main.cpp -I./include</code></td></tr><tr><td style="text-align:left"><strong><code>-L &lt;dir&gt;</code></strong></td><td style="text-align:left">基础</td><td style="text-align:left"><strong>指定库文件搜索路径</strong>。告诉编译器去哪里找 <code>.so</code> 或 <code>.a</code> 静态/动态库文件。</td><td style="text-align:left"><code>g++ main.cpp -L./libs</code></td></tr><tr><td style="text-align:left"><strong><code>-l&lt;name&gt;</code></strong></td><td style="text-align:left">基础</td><td style="text-align:left"><strong>链接指定的库</strong>。紧跟库名（自动去掉 <code>lib</code> 前缀和 <code>.so</code>/<code>.a</code> 后缀）。</td><td style="text-align:left"><code>g++ main.cpp -lpthread</code></td></tr><tr><td style="text-align:left"><strong><code>-g</code></strong></td><td style="text-align:left">调试</td><td style="text-align:left"><strong>生成调试信息</strong>。在使用 <code>gdb</code> 调试器时，必须加此参数才能看到源码行号。</td><td style="text-align:left"><code>g++ -g main.cpp -o debug_app</code></td></tr><tr><td style="text-align:left"><strong><code>-Wall</code></strong></td><td style="text-align:left">警告</td><td style="text-align:left"><strong>开启常规警告</strong>。检测潜在的逻辑错误（如变量未初始化、类型不匹配）。</td><td style="text-align:left"><code>g++ -Wall main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-Wextra</code></strong></td><td style="text-align:left">警告</td><td style="text-align:left"><strong>开启额外警告</strong>。比 <code>-Wall</code> 更严格，能发现更多隐蔽的代码规范问题。</td><td style="text-align:left"><code>g++ -Wall -Wextra main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-Werror</code></strong></td><td style="text-align:left">警告</td><td style="text-align:left"><strong>将警告视为错误</strong>。只要有任何警告出现，编译就会立即终止。</td><td style="text-align:left"><code>g++ -Werror main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-O0</code></strong></td><td style="text-align:left">优化</td><td style="text-align:left"><strong>不进行优化（默认）</strong>。编译最快，生成的代码与源码一一对应，适合开发阶段。</td><td style="text-align:left"><code>g++ -O0 main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-O2</code></strong></td><td style="text-align:left">优化</td><td style="text-align:left"><strong>标准优化</strong>。平衡编译时间和运行速度，是大多数生产环境项目的首选。</td><td style="text-align:left"><code>g++ -O2 main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-O3</code></strong></td><td style="text-align:left">优化</td><td style="text-align:left"><strong>激进优化</strong>。开启更多高级手段（如循环展开），提升性能但可能增大体积。</td><td style="text-align:left"><code>g++ -O3 main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-Os</code></strong></td><td style="text-align:left">优化</td><td style="text-align:left"><strong>体积优化</strong>。优先缩小生成的二进制文件大小，适合嵌入式或空间受限场景。</td><td style="text-align:left"><code>g++ -Os main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-std=c++11/17/20</code></strong></td><td style="text-align:left">标准</td><td style="text-align:left"><strong>指定 C++ 语言版本标准</strong>。开启对应年份的新特性支持（如 <code>auto</code>, <code>concepts</code>）。</td><td style="text-align:left"><code>g++ -std=c++17 main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-D&lt;macro&gt;</code></strong></td><td style="text-align:left">预处理</td><td style="text-align:left"><strong>定义宏</strong>。相当于在代码顶端写 <code>#define</code>，常用于区分测试和生产逻辑。</td><td style="text-align:left"><code>g++ -DDEBUG main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-E</code></strong></td><td style="text-align:left">预处理</td><td style="text-align:left"><strong>只执行预处理</strong>。查看宏展开、头文件包含后的最终代码文本，输出到屏幕。</td><td style="text-align:left"><code>g++ -E main.cpp</code></td></tr><tr><td style="text-align:left"><strong><code>-fPIC</code></strong></td><td style="text-align:left">进阶</td><td style="text-align:left"><strong>生成位置无关代码</strong>。在编译<strong>动态链接库</strong>（Shared Library）时必须使用。</td><td style="text-align:left"><code>g++ -fPIC -c lib.cpp</code></td></tr></tbody></table><p>显然当文件一多,要链接的库一多,要进行的预编译一多,用g++来写是不可接受的.</p><p>让我们先以下面这个cmakelist.txt为例来看看编译时要考虑多少东西:<br><strong>cocos示例项目的CMakelists.txt</strong></p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line">cmake_minimum_required(VERSION 3.6)</span><br><span class="line"></span><br><span class="line">set(APP_NAME HelloCpp)</span><br><span class="line"></span><br><span class="line">project($&#123;APP_NAME&#125;)</span><br><span class="line"></span><br><span class="line">set(COCOS2DX_ROOT_PATH $&#123;CMAKE_CURRENT_SOURCE_DIR&#125;/cocos2d)</span><br><span class="line">set(CMAKE_MODULE_PATH $&#123;COCOS2DX_ROOT_PATH&#125;/cmake/Modules/)</span><br><span class="line"></span><br><span class="line">include(CocosBuildSet)</span><br><span class="line">add_subdirectory($&#123;COCOS2DX_ROOT_PATH&#125;/cocos $&#123;ENGINE_BINARY_PATH&#125;/cocos/core)</span><br><span class="line"></span><br><span class="line"><span class="comment"># record sources, headers, resources...</span></span><br><span class="line">set(GAME_SOURCE)</span><br><span class="line">set(GAME_HEADER)</span><br><span class="line"></span><br><span class="line">set(GAME_RES_FOLDER</span><br><span class="line">    &quot;$&#123;CMAKE_CURRENT_SOURCE_DIR&#125;/Resources&quot;</span><br><span class="line">    )</span><br><span class="line">if(APPLE OR WINDOWS)</span><br><span class="line">    cocos_mark_multi_resources(common_res_files RES_TO &quot;Resources&quot; FOLDERS $&#123;GAME_RES_FOLDER&#125;)</span><br><span class="line">endif()</span><br><span class="line"></span><br><span class="line"><span class="comment"># add cross-platforms source files and header files </span></span><br><span class="line">list(APPEND GAME_SOURCE</span><br><span class="line">     Classes/AppDelegate.cpp</span><br><span class="line">     Classes/HelloWorldScene.cpp</span><br><span class="line">     )</span><br><span class="line">list(APPEND GAME_HEADER</span><br><span class="line">     Classes/AppDelegate.h</span><br><span class="line">     Classes/HelloWorldScene.h</span><br><span class="line">     )</span><br><span class="line"></span><br><span class="line">if(ANDROID)</span><br><span class="line">    <span class="comment"># change APP_NAME to the share library name for Android, it&#x27;s value depend on AndroidManifest.xml</span></span><br><span class="line">    set(APP_NAME MyGame)</span><br><span class="line">    list(APPEND GAME_SOURCE</span><br><span class="line">         proj.android/app/jni/hellocpp/main.cpp</span><br><span class="line">         )</span><br><span class="line">elseif(LINUX)</span><br><span class="line">    list(APPEND GAME_SOURCE</span><br><span class="line">         proj.linux/main.cpp</span><br><span class="line">         )</span><br><span class="line">elseif(WINDOWS)</span><br><span class="line">    list(APPEND GAME_HEADER</span><br><span class="line">         proj.win32/main.h</span><br><span class="line">         proj.win32/resource.h</span><br><span class="line">         )</span><br><span class="line">    list(APPEND GAME_SOURCE</span><br><span class="line">         proj.win32/main.cpp</span><br><span class="line">         proj.win32/game.rc</span><br><span class="line">         $&#123;common_res_files&#125;</span><br><span class="line">         )</span><br><span class="line">elseif(APPLE)</span><br><span class="line">    if(IOS)</span><br><span class="line">        list(APPEND GAME_HEADER</span><br><span class="line">             proj.ios_mac/ios/AppController.h</span><br><span class="line">             proj.ios_mac/ios/RootViewController.h</span><br><span class="line">             )</span><br><span class="line">        set(APP_UI_RES</span><br><span class="line">            proj.ios_mac/ios/LaunchScreen.storyboard</span><br><span class="line">            proj.ios_mac/ios/LaunchScreenBackground.png</span><br><span class="line">            proj.ios_mac/ios/Images.xcassets</span><br><span class="line">            )</span><br><span class="line">        list(APPEND GAME_SOURCE</span><br><span class="line">             proj.ios_mac/ios/main.m</span><br><span class="line">             proj.ios_mac/ios/AppController.mm</span><br><span class="line">             proj.ios_mac/ios/RootViewController.mm</span><br><span class="line">             proj.ios_mac/ios/Prefix.pch</span><br><span class="line">             $&#123;APP_UI_RES&#125;</span><br><span class="line">             )</span><br><span class="line">    elseif(MACOSX)</span><br><span class="line">        set(APP_UI_RES</span><br><span class="line">            proj.ios_mac/mac/Icon.icns</span><br><span class="line">            proj.ios_mac/mac/Info.plist</span><br><span class="line">            )</span><br><span class="line">        list(APPEND GAME_SOURCE</span><br><span class="line">             proj.ios_mac/mac/main.cpp</span><br><span class="line">             proj.ios_mac/mac/Prefix.pch</span><br><span class="line">             $&#123;APP_UI_RES&#125;</span><br><span class="line">             )</span><br><span class="line">    endif()</span><br><span class="line">    list(APPEND GAME_SOURCE $&#123;common_res_files&#125;)</span><br><span class="line">endif()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 省略一大堆编译项</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>这显然超出了g++的能力了…</li></ul><h1>构建工具的出现</h1><ul><li><a href="https://www.gnu.org/software/make/manual/make.html">GNU make</a></li><li><a href="https://aosabook.org/en/posa/ninja.html">ninja作者自述</a></li><li><a href="https://ninja-build.org/">ninja官网</a><br>为了解决上述的问题,先后诞生了两种主流的cpp构建工具: make和ninja,它们可以指挥gcc或者其他编译器进行所需的构建.</li></ul><h2 id="make">make</h2><h3 id="是什么-怎么用">是什么,怎么用</h3><blockquote><p>The <strong>make</strong> utility automatically determines which pieces of a large program need to be <strong>recompiled</strong>, and issues commands to recompile them.</p></blockquote><p>为了执行make命令,我们需要将它写入makefile文档来执行.</p><p>makefile的大致格式如下:</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">target … : prerequisites …</span><br><span class="line">        recipe</span><br><span class="line">        …</span><br><span class="line">        …</span><br></pre></td></tr></table></figure><ul><li>target: 生成的目标文件名,比如中间文件(.o)和可执行文件(.exe),当然我们不指明后缀名也行,还可以是要执行的指令名如’clean’等</li><li>prerequisites: 需要用到的源文件</li><li>recipe: make构建时的规则</li></ul><p>以下是一个简单的makefile示例:</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">edit : main.o kbd.o command.o display.o \</span><br><span class="line">       insert.o search.o files.o utils.o</span><br><span class="line">        cc -o edit main.o kbd.o command.o display.o \</span><br><span class="line">                   insert.o search.o files.o utils.o</span><br><span class="line"><span class="comment"># 将所有中间文件编译成可执行文件                   </span></span><br><span class="line"><span class="comment"># 使用\将一行长句分为两行,也就是说不带\的话默认为单独一行,而makefile中一般一行写源文件名,一行写编译规则</span></span><br><span class="line">main.o : main.c defs.h</span><br><span class="line">        cc -c main.c</span><br><span class="line">kbd.o : kbd.c defs.h command.h</span><br><span class="line">        cc -c kbd.c</span><br><span class="line">command.o : command.c defs.h command.h</span><br><span class="line">        cc -c command.c</span><br><span class="line">display.o : display.c defs.h buffer.h</span><br><span class="line">        cc -c display.c</span><br><span class="line">insert.o : insert.c defs.h buffer.h</span><br><span class="line">        cc -c insert.c</span><br><span class="line">search.o : search.c defs.h buffer.h</span><br><span class="line">        cc -c search.c</span><br><span class="line">files.o : files.c defs.h buffer.h command.h</span><br><span class="line">        cc -c files.c</span><br><span class="line">utils.o : utils.c defs.h</span><br><span class="line">        cc -c utils.c</span><br><span class="line">clean :</span><br><span class="line">        rm edit main.o kbd.o command.o display.o \</span><br><span class="line">           insert.o search.o files.o utils.o</span><br></pre></td></tr></table></figure><ul><li>很明显,这个makefile是面向Linux系统的,毕竟有<code>rm</code>和<code>cc</code>这样的终端命令.<br>在当前目录输入<code>make</code>即可生成edit可执行文件,输入<code>make clean</code>即可清除中间文件</li></ul><h3 id="make处理makefile的原理">make处理makefile的原理</h3><p>默认情况下,<code>make</code>命令会从makefile里的第一个target开始执行.</p><blockquote><p>make reads the makefile in the current directory and begins by processing the first rule. In the example, this rule is for relinking edit; but before make can fully process this rule, it must process the rules for the files that edit depends on, which in this case are the object files. Each of these files is processed according to its own rule. These rules say to update each ‘.o’ file by compiling its source file. The recompilation must be done if the source file, or any of the header files named as prerequisites, is more recent than the object file, or if the object file does not exist.</p><p>The other rules are processed because their targets appear as prerequisites of the goal. If some other rule is not depended on by the goal (or anything it depends on, etc.), that rule is not processed, unless you tell make to do so (with a command such as make clean).</p></blockquote><ul><li>也就是说,make命令只执行第一个target并解决所有的对应依赖项,不会执行第二个命令,除非显示指明</li></ul><blockquote><p>After recompiling whichever object files need it, make decides whether to relink edit. This must be done if the file edit does not exist, or if any of the object files are newer than it. If an object file was just recompiled, it is now newer than edit, so edit is relinked.</p><p>Thus, if we change the file insert.c and run make, make will compile that file to update insert.o, and then link edit. If we change the file command.h and run make, make will recompile the object files kbd.o, command.o and files.o and then link the file edit.</p></blockquote><ul><li>make会在target对应的源文件更新时自动重新编译target,而不触及其他未改动的部分</li></ul><p>事实上了解到这里就差不多了,毕竟现在真的没必要手写makefile了,电脑系统再怎么古老CMake应该还是能用的吧…</p><h2 id="ninja">ninja</h2><h3 id="是什么-怎么用">是什么,怎么用</h3><blockquote><p>Ninja is yet another <strong>build system</strong>. It takes as input the interdependencies of files (typically source code and output executables) and orchestrates building them, <strong>quickly</strong>.</p></blockquote><ul><li>ninja能够代替古老的make的原因就在于它很快,比make快了十倍以上</li></ul><blockquote><p>Ninja contains the barest functionality necessary to describe arbitrary dependency graphs. Its lack of syntax makes it impossible to express complex decisions.</p><p>Ninja has almost no features; <strong>just</strong> those necessary to get builds correct while punting most complexity to generation of the ninja input files. Ninja by itself is unlikely to be useful for most projects.</p></blockquote><ul><li>事实上,ninja的设计初衷就是追求快速,摒弃一切不必要的功能,从而大大提高了构建速度.<ul><li>ninja官网的说明文档在加入了一大堆参数说明后仍然远远短于make官网的说明文档</li></ul></li></ul><p><strong>一个简短的示例</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">cflags = -Wall</span><br><span class="line"></span><br><span class="line">rule cc</span><br><span class="line">  <span class="built_in">command</span> = gcc <span class="variable">$cflags</span> -c <span class="variable">$in</span> -o <span class="variable">$out</span></span><br><span class="line"><span class="comment"># rule类似于make中的target,但用法上灵活的多</span></span><br><span class="line"><span class="comment"># $为变量插入声明,也就是说,这里的$cflags相当于-Wall</span></span><br><span class="line"><span class="comment"># 当然,我们也可以写成$&#123;cflags&#125;,看个人喜好</span></span><br><span class="line">build foo.o: cc foo.c</span><br></pre></td></tr></table></figure><p>我们需要将这段代码放入<strong>build.ninja</strong>文件中,再在终端执行<code>ninja</code>命令即可进行构建,用法与make的命令也基本类似.</p><p>实际上我们了解到这个程度也就足够了,只需要知道ninja的原理与make类似,但写法上灵活的多,构建速度也快的多.</p><h1>高级构建工具: CMake</h1><p>现在来到了我们的重头戏:CMake,先来看一下<a href="https://zh.wikipedia.org/wiki/CMake">wiki介绍</a></p><blockquote><p>CMake是个一个开源的跨平台自动化建构系统，用来管理软件建置的程序，并不依赖于某特定编译器，并可支持多层目录、多个应用程序与多个函数库.<br>CMake的配置文件取名为<strong>CMakeLists.txt</strong>,它并不直接建构出最终的软件，而是产生标准的构建文件（如Unix的Makefile）</p><p>CMake”这个名字是<strong>Cross platform Make</strong>的缩写。虽然名字中含有“make”，但是CMake和Unix上常见的make系统是分开的，而且更为高阶</p></blockquote><ul><li>既然CMake是用来指挥ninja,make等构建文件的,自然它就是高级构建工具了.</li></ul><h2 id="怎么用">怎么用</h2><ul><li><a href="https://modern-cmake-cn.github.io/Modern-CMake-zh_CN/chapters/intro/running.html">参考教程</a></li></ul><blockquote><p>除非另行说明，你始终应该建立一个专用于构建的目录并在那里构建项目。从技术上来讲，你可以进行内部构建（即在源代码目录下执行 CMake 构建命令），但是必须注意不要覆盖文件或者把它们添加到 git，所以别这么做就好。</p></blockquote><p>一个经典的CMake构建流程如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> build</span><br><span class="line"><span class="comment"># 在当前目录创建build文件夹</span></span><br><span class="line"><span class="comment"># 尽管这是Linux命令,但Windows的Powershell现在也支持了</span></span><br><span class="line"><span class="built_in">cd</span> build</span><br><span class="line">cmake ..</span><br><span class="line"><span class="comment"># 根据上级目录里的CMakelists进行CMake构建</span></span><br><span class="line"><span class="comment"># 并将构建出来的makefile放入build文件夹中</span></span><br><span class="line">make</span><br><span class="line"><span class="comment"># 自然,我们未必会使用make进行构建,所以还可以写成以下形式:</span></span><br><span class="line">cmake --build .</span><br><span class="line"><span class="comment"># 在build目录中使用cmake的默认构建工具进行构建</span></span><br></pre></td></tr></table></figure><p>但是四行命令明显太多了,我们可以这样写:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cmake -S . -B build</span><br><span class="line"><span class="comment"># -S: source,指定CMakelists所在目录</span></span><br><span class="line"><span class="comment"># -B: build,指定CMake的输出(如makefile,ninja.build)目录</span></span><br><span class="line">cmake --build build</span><br><span class="line"><span class="comment"># 在build目录中使用cmake的默认构建工具进行构建</span></span><br></pre></td></tr></table></figure><ul><li>也就是说,我们可以在根目录执行cmake命令,或者进入build文件夹后再执行cmake命令</li></ul><p>自然,当我们的电脑上安装了多种构建工具时,我们希望在初次构建时选定自己所需的那种,只需要这么写:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cmake -S . -B build -G <span class="string">&quot;Ninja&quot;</span></span><br><span class="line"><span class="comment"># 指定ninja</span></span><br><span class="line"><span class="comment"># -G: generator,构建工具</span></span><br></pre></td></tr></table></figure><p>运行<code>cmake --help</code>可以查看基础的cmake命令和该操作系统上可用的构建工具:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">cmake --<span class="built_in">help</span></span><br><span class="line"></span><br><span class="line">Usage</span><br><span class="line"></span><br><span class="line">  cmake [options] &lt;path-to-source&gt;</span><br><span class="line">  cmake [options] &lt;path-to-existing-build&gt;</span><br><span class="line">  cmake [options] -S &lt;path-to-source&gt; -B &lt;path-to-build&gt;</span><br><span class="line"></span><br><span class="line">Specify a <span class="built_in">source</span> directory to (re-)generate a build system <span class="keyword">for</span> it <span class="keyword">in</span> the</span><br><span class="line">current working directory.  Specify an existing build directory to</span><br><span class="line">re-generate its build system.</span><br><span class="line"></span><br><span class="line">Options</span><br><span class="line">  -S &lt;path-to-source&gt;          = Explicitly specify a <span class="built_in">source</span> directory.</span><br><span class="line">  -B &lt;path-to-build&gt;           = Explicitly specify a build directory.</span><br><span class="line">  -C &lt;initial-cache&gt;           = Pre-load a script to populate the cache.</span><br><span class="line">  -G &lt;generator-name&gt;          = Specify a build system generator.</span><br><span class="line">  -T &lt;toolset-name&gt;            = Specify toolset name <span class="keyword">if</span> supported by</span><br><span class="line">                                 generator.</span><br><span class="line">  -A &lt;platform-name&gt;           = Specify platform name <span class="keyword">if</span> supported by</span><br><span class="line">                                 generator.</span><br><span class="line"><span class="comment"># ...省略一大堆参数</span></span><br><span class="line"></span><br><span class="line">Generators</span><br><span class="line"></span><br><span class="line">The following generators are available on this platform (* marks default):</span><br><span class="line">  Visual Studio 18 2026        = Generates Visual Studio 2026 project files.</span><br><span class="line">                                 Use -A option to specify architecture.</span><br><span class="line">* Visual Studio 17 2022        = Generates Visual Studio 2022 project files.</span><br><span class="line">                                 Use -A option to specify architecture.</span><br><span class="line">  Visual Studio 16 2019        = Generates Visual Studio 2019 project files.</span><br><span class="line">                                 Use -A option to specify architecture.</span><br><span class="line">  Visual Studio 15 2017        = Generates Visual Studio 2017 project files.</span><br><span class="line">                                 Use -A option to specify architecture.</span><br><span class="line">  Visual Studio 14 2015        = Deprecated.  Generates Visual Studio 2015</span><br><span class="line">                                 project files.  Use -A option to specify</span><br><span class="line">                                 architecture.</span><br><span class="line">  Borland Makefiles            = Generates Borland makefiles.</span><br><span class="line">  NMake Makefiles              = Generates NMake makefiles.</span><br><span class="line">  NMake Makefiles JOM          = Generates JOM makefiles.</span><br><span class="line">  MSYS Makefiles               = Generates MSYS makefiles.</span><br><span class="line">  MinGW Makefiles              = Generates a make file <span class="keyword">for</span> use with</span><br><span class="line">                                 mingw32-make.</span><br><span class="line">  Green Hills MULTI            = Generates Green Hills MULTI files</span><br><span class="line">                                 (experimental, work-in-progress).</span><br><span class="line">  Unix Makefiles               = Generates standard UNIX makefiles.</span><br><span class="line">  Ninja                        = Generates build.ninja files.</span><br><span class="line">  Ninja Multi-Config           = Generates build-&lt;Config&gt;.ninja files.</span><br><span class="line"><span class="comment"># ...省略一大堆</span></span><br></pre></td></tr></table></figure><h2 id="基础语法">基础语法</h2><p>当我们运行的是别人的项目时,知道如何用CMake构建就足够了,但很多时候我们都要自己写CMake来构建项目,这就需要我们去深入了解CMakelists的写法了.</p><ul><li><a href="https://cmake.org/cmake/help/latest/guide/tutorial/index.html">官方教程</a></li></ul><h3 id="cmakelists-txt的前置内容">CMakelists.txt的前置内容</h3><p><strong>最低版本要求</strong><br>这是每个<code>CMakeLists.txt</code>都必须包含的第一行:</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cmake_minimum_required(VERSION 4.3)</span><br><span class="line"><span class="comment"># 该命令不区分大小写,但习惯上小写</span></span><br><span class="line"><span class="comment"># CMake3.12以后的版本支持版本的范围要求:</span></span><br><span class="line">cmake_minimum_required(VERSION 3.12...3.21)</span><br></pre></td></tr></table></figure><ul><li>如果你用的cmake版本比声明上所写的更高,由于cmake向后兼容,因此会按照声明的版本来运行.<br><strong>项目设置</strong><br>声明该项目的名字,版本号,描述,使用的语言</li></ul><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">project(MyProject VERSION 1.0</span><br><span class="line">                  DESCRIPTION &quot;Very nice project&quot;</span><br><span class="line">                  LANGUAGES CXX)</span><br><span class="line"><span class="comment"># 项目名字之外的参数没有顺序要求</span></span><br></pre></td></tr></table></figure><blockquote><p>When CMake sees the project() command it performs <strong>various checks</strong> to ensure the environment is suitable for building software; such as checking for compilers and other build tooling, and discovering properties like the endianness of the host and target machines.</p></blockquote><p>这两个部分最好放在顶部或者接近顶部,如:</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ...版权声明</span></span><br><span class="line">cmake_minimum_required(VERSION 3.23)</span><br><span class="line"></span><br><span class="line">project(MyProjectName)</span><br><span class="line"><span class="comment"># ...剩余部分</span></span><br></pre></td></tr></table></figure><h3 id="cmake的一点基础特性">CMake的一点基础特性</h3><blockquote><p>The only fundamental types in CMakeLang are <strong>strings and lists</strong>. Every object in CMake is a <strong>string</strong>, and <strong>lists</strong> are themselves strings which contain <strong>semicolons</strong> as separators.</p></blockquote><ul><li>由于CMake的这个特性,故CMakelist看起来非常累,没有<code>:</code>,没有单引号,也没有<code>--</code>,只通过空格,空行和缩进来体现层次关系,注释则使用<code>#</code>.</li></ul><p><strong>设置变量并插入字符串</strong></p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">set(var &quot;World!&quot;)</span><br><span class="line">message(&quot;Hello $&#123;var&#125;&quot;)</span><br></pre></td></tr></table></figure><ul><li>set用于设置变量</li><li>message用于打印调试信息</li><li><code>$&#123;var&#125;</code>用于插入变量</li></ul><h2 id="常用命令汇总-ai总结">常用命令汇总(AI总结)</h2><h3 id="🔧-基础命令与项目配置">🔧 基础命令与项目配置</h3><table><thead><tr><th style="text-align:left">命令 / 语法</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><code>cmake_minimum_required(VERSION ...)</code></td><td style="text-align:left">指定所需 CMake 最低版本</td><td style="text-align:left"><code>cmake_minimum_required(VERSION 3.15)</code></td></tr><tr><td style="text-align:left"><code>project(Name VERSION x.x LANGUAGES CXX)</code></td><td style="text-align:left">定义项目名称、版本与语言</td><td style="text-align:left"><code>project(MyApp VERSION 1.0 LANGUAGES CXX)</code></td></tr><tr><td style="text-align:left"><code>add_executable(target source...)</code></td><td style="text-align:left">创建可执行文件目标</td><td style="text-align:left"><code>add_executable(app main.cpp helper.cpp)</code></td></tr><tr><td style="text-align:left"><code>add_library(target [STATIC\|SHARED] source...)</code></td><td style="text-align:left">创建库目标（静态/动态）</td><td style="text-align:left"><code>add_library(utils STATIC utils.cpp)</code></td></tr><tr><td style="text-align:left"><code>add_subdirectory(dir)</code></td><td style="text-align:left">添加子目录构建</td><td style="text-align:left"><code>add_subdirectory(libs/foo)</code></td></tr><tr><td style="text-align:left"><code>include_directories(dir)</code></td><td style="text-align:left">添加全局头文件搜索路径（不推荐）</td><td style="text-align:left"><code>include_directories($&#123;CMAKE_SOURCE_DIR&#125;/include)</code></td></tr><tr><td style="text-align:left"><code>target_include_directories(target [PRIVATE\|PUBLIC] dir)</code></td><td style="text-align:left">为目标添加头文件路径（现代用法）</td><td style="text-align:left"><code>target_include_directories(app PRIVATE include/)</code></td></tr><tr><td style="text-align:left"><code>target_link_libraries(target [PRIVATE\|PUBLIC] lib...)</code></td><td style="text-align:left">为目标链接库</td><td style="text-align:left"><code>target_link_libraries(app PRIVATE utils)</code></td></tr><tr><td style="text-align:left"><code>target_compile_options(target [PRIVATE\|PUBLIC] flags)</code></td><td style="text-align:left">为目标添加编译选项</td><td style="text-align:left"><code>target_compile_options(app PRIVATE -Wall -Wextra)</code></td></tr><tr><td style="text-align:left"><code>set_target_properties(target PROPERTIES prop value)</code></td><td style="text-align:left">设置目标属性（如输出名称、C++标准）</td><td style="text-align:left"><code>set_target_properties(app PROPERTIES CXX_STANDARD 17)</code></td></tr></tbody></table><hr><h3 id="📦-变量与常用预设变量">📦 变量与常用预设变量</h3><table><thead><tr><th style="text-align:left">语法 / 变量名</th><th style="text-align:left">说明</th><th style="text-align:left">示例 / 默认值</th></tr></thead><tbody><tr><td style="text-align:left"><code>set(VAR value)</code></td><td style="text-align:left">设置普通变量</td><td style="text-align:left"><code>set(SRC_FILES main.cpp utils.cpp)</code></td></tr><tr><td style="text-align:left"><code>set(VAR value CACHE TYPE &quot;doc&quot;)</code></td><td style="text-align:left">设置缓存变量（可被命令行覆盖）</td><td style="text-align:left"><code>set(ENABLE_TESTS ON CACHE BOOL &quot;Build tests&quot;)</code></td></tr><tr><td style="text-align:left"><code>list(APPEND VAR value)</code></td><td style="text-align:left">向列表变量追加值</td><td style="text-align:left"><code>list(APPEND SRC_FILES extra.cpp)</code></td></tr><tr><td style="text-align:left"><code>option(VAR &quot;description&quot; default)</code></td><td style="text-align:left">定义布尔选项（缓存）</td><td style="text-align:left"><code>option(BUILD_SHARED_LIBS &quot;Build shared libs&quot; OFF)</code></td></tr><tr><td style="text-align:left"><code>$&#123;VAR&#125;</code></td><td style="text-align:left">引用变量</td><td style="text-align:left"><code>message(&quot;Source files: $&#123;SRC_FILES&#125;&quot;)</code></td></tr><tr><td style="text-align:left"><code>CMAKE_SOURCE_DIR</code></td><td style="text-align:left">顶层 CMakeLists.txt 所在目录</td><td style="text-align:left">自动由 CMake 设置</td></tr><tr><td style="text-align:left"><code>CMAKE_BINARY_DIR</code></td><td style="text-align:left">顶层构建目录</td><td style="text-align:left">执行 <code>cmake</code> 的目录</td></tr><tr><td style="text-align:left"><code>CMAKE_CURRENT_SOURCE_DIR</code></td><td style="text-align:left">当前处理的 CMakeLists.txt 所在目录</td><td style="text-align:left">-</td></tr><tr><td style="text-align:left"><code>CMAKE_CXX_STANDARD</code></td><td style="text-align:left">设置 C++ 标准（全局）</td><td style="text-align:left"><code>set(CMAKE_CXX_STANDARD 17)</code></td></tr><tr><td style="text-align:left"><code>PROJECT_NAME</code></td><td style="text-align:left">当前项目名称</td><td style="text-align:left"><code>message($&#123;PROJECT_NAME&#125;)</code></td></tr><tr><td style="text-align:left"><code>PROJECT_SOURCE_DIR</code></td><td style="text-align:left">当前项目源码根目录</td><td style="text-align:left">同 <code>CMAKE_SOURCE_DIR</code>（若仅一个项目）</td></tr><tr><td style="text-align:left"><code>PROJECT_BINARY_DIR</code></td><td style="text-align:left">当前项目构建目录</td><td style="text-align:left">通常为 <code>$&#123;CMAKE_BINARY_DIR&#125;</code> 子目录</td></tr></tbody></table><hr><h3 id="🔍-查找依赖与包管理">🔍 查找依赖与包管理</h3><table><thead><tr><th style="text-align:left">命令</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><code>find_package(Package [REQUIRED] [COMPONENTS ...])</code></td><td style="text-align:left">查找并加载外部依赖包</td><td style="text-align:left"><code>find_package(OpenCV REQUIRED COMPONENTS core imgproc)</code></td></tr><tr><td style="text-align:left"><code>find_library(VAR name PATHS ...)</code></td><td style="text-align:left">查找库文件路径</td><td style="text-align:left"><code>find_library(MATH_LIB m)</code></td></tr><tr><td style="text-align:left"><code>find_path(VAR name PATHS ...)</code></td><td style="text-align:left">查找头文件所在目录</td><td style="text-align:left"><code>find_path(CURL_INCLUDE_DIR curl/curl.h)</code></td></tr></tbody></table><hr><h3 id="🔀-条件判断与循环">🔀 条件判断与循环</h3><table><thead><tr><th style="text-align:left">语法</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><code>if(condition) ... elseif() ... else() ... endif()</code></td><td style="text-align:left">条件分支</td><td style="text-align:left"><code>if(WIN32) ... elseif(UNIX) ... endif()</code></td></tr><tr><td style="text-align:left"><code>foreach(var IN LISTS list) ... endforeach()</code></td><td style="text-align:left">列表循环</td><td style="text-align:left"><code>foreach(src $&#123;SRC_FILES&#125;) message($&#123;src&#125;) endforeach()</code></td></tr><tr><td style="text-align:left"><code>while(condition) ... endwhile()</code></td><td style="text-align:left">while 循环</td><td style="text-align:left"><code>while($&#123;counter&#125; LESS 10) ... endwhile()</code></td></tr><tr><td style="text-align:left"><code>break()</code> / <code>continue()</code></td><td style="text-align:left">跳出循环 / 进入下一次迭代</td><td style="text-align:left">在循环内使用</td></tr><tr><td style="text-align:left"><code>AND</code> / <code>OR</code> / <code>NOT</code></td><td style="text-align:left">逻辑运算符</td><td style="text-align:left"><code>if(NOT DEFINED VAR)</code></td></tr><tr><td style="text-align:left"><code>EXISTS path</code></td><td style="text-align:left">判断文件/目录是否存在</td><td style="text-align:left"><code>if(EXISTS $&#123;CMAKE_SOURCE_DIR&#125;/config.h)</code></td></tr><tr><td style="text-align:left"><code>TARGET target</code></td><td style="text-align:left">判断目标是否已定义</td><td style="text-align:left"><code>if(TARGET mylib)</code></td></tr></tbody></table><hr><h3 id="⚙-函数与宏">⚙️ 函数与宏</h3><table><thead><tr><th style="text-align:left">语法</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><code>function(name arg1 arg2) ... endfunction()</code></td><td style="text-align:left">定义函数（变量作用域独立）</td><td style="text-align:left"><code>function(print_msg msg) message($&#123;msg&#125;) endfunction()</code></td></tr><tr><td style="text-align:left"><code>macro(name arg1 arg2) ... endmacro()</code></td><td style="text-align:left">定义宏（变量作用域与调用者相同）</td><td style="text-align:left"><code>macro(add_sources) list(APPEND SRC $&#123;ARGN&#125;) endmacro()</code></td></tr><tr><td style="text-align:left"><code>return()</code></td><td style="text-align:left">从函数或文件中提前返回</td><td style="text-align:left"><code>if(NOT VALID) return() endif()</code></td></tr><tr><td style="text-align:left"><code>include(file)</code></td><td style="text-align:left">载入并执行其他 CMake 脚本</td><td style="text-align:left"><code>include(cmake/Utilities.cmake)</code></td></tr></tbody></table><hr><h3 id="📁-文件操作与安装">📁 文件操作与安装</h3><table><thead><tr><th style="text-align:left">命令</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><code>file(GLOB VAR pattern)</code></td><td style="text-align:left">使用通配符获取文件列表（不推荐用于源码收集）</td><td style="text-align:left"><code>file(GLOB HEADERS &quot;include/*.h&quot;)</code></td></tr><tr><td style="text-align:left"><code>file(COPY src DESTINATION dst)</code></td><td style="text-align:left">复制文件/目录</td><td style="text-align:left"><code>file(COPY config.yaml DESTINATION $&#123;CMAKE_BINARY_DIR&#125;)</code></td></tr><tr><td style="text-align:left"><code>configure_file(input output @ONLY)</code></td><td style="text-align:left">替换模板变量生成配置文件</td><td style="text-align:left"><code>configure_file(config.h.in config.h @ONLY)</code></td></tr><tr><td style="text-align:left"><code>install(TARGETS target DESTINATION dir)</code></td><td style="text-align:left">安装目标（可执行文件、库）</td><td style="text-align:left"><code>install(TARGETS app DESTINATION bin)</code></td></tr><tr><td style="text-align:left"><code>install(FILES file DESTINATION dir)</code></td><td style="text-align:left">安装普通文件</td><td style="text-align:left"><code>install(FILES README.md DESTINATION share/doc)</code></td></tr><tr><td style="text-align:left"><code>install(DIRECTORY dir DESTINATION dir)</code></td><td style="text-align:left">安装目录</td><td style="text-align:left"><code>install(DIRECTORY assets/ DESTINATION share/assets)</code></td></tr></tbody></table><hr><p>\</p><h3 id="🖨-消息输出与调试">🖨️ 消息输出与调试</h3><table><thead><tr><th style="text-align:left">命令</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><code>message([STATUS\|WARNING\|FATAL_ERROR] &quot;text&quot;)</code></td><td style="text-align:left">打印消息</td><td style="text-align:left"><code>message(STATUS &quot;Configuring $&#123;PROJECT_NAME&#125;&quot;)</code></td></tr><tr><td style="text-align:left"><code>message(STATUS &quot;var = $&#123;var&#125;&quot;)</code></td><td style="text-align:left">打印变量值</td><td style="text-align:left">常用于调试</td></tr><tr><td style="text-align:left"><code>message(FATAL_ERROR &quot;missing dependency&quot;)</code></td><td style="text-align:left">致命错误，停止配置</td><td style="text-align:left">-</td></tr></tbody></table><hr><h2 id="实战">实战</h2><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br></pre></td><td class="code"><pre><span class="line">cmake_minimum_required(VERSION 3.6)</span><br><span class="line"></span><br><span class="line">set(APP_NAME cpp-empty-test)</span><br><span class="line"></span><br><span class="line">project($&#123;APP_NAME&#125;)</span><br><span class="line"></span><br><span class="line">if(NOT DEFINED BUILD_ENGINE_DONE)</span><br><span class="line"><span class="comment"># DEFINED: 关键字,检查该变量是否用set关键字定义过</span></span><br><span class="line">    set(COCOS2DX_ROOT_PATH $&#123;CMAKE_CURRENT_SOURCE_DIR&#125;/../..)</span><br><span class="line">    <span class="comment"># CMAKE_CURRENT_SOURCE_DIR: 内置变量,表示当前的txt文件所在目录</span></span><br><span class="line">    <span class="comment"># /..表示往上级查找,相当于找到上两级的根目录</span></span><br><span class="line">    set(CMAKE_MODULE_PATH $&#123;COCOS2DX_ROOT_PATH&#125;/cmake/Modules/)</span><br><span class="line"><span class="comment"># CMAKE_MODULE_PATH: 内置变量,存放查找自定义脚本文件的搜索路径</span></span><br><span class="line">    include(CocosBuildSet)</span><br><span class="line">    <span class="comment"># include: 关键字,执行对应的自定义CMake脚本文件</span></span><br><span class="line">    add_subdirectory($&#123;COCOS2DX_ROOT_PATH&#125;/cocos $&#123;ENGINE_BINARY_PATH&#125;/cocos/core)</span><br><span class="line">    <span class="comment">#  add_subdirectory: 将该目录加入构建系统</span></span><br><span class="line">endif()</span><br><span class="line"></span><br><span class="line"><span class="comment"># record sources, headers, resources...</span></span><br><span class="line">set(GAME_SOURCE)</span><br><span class="line">set(GAME_HEADER)</span><br><span class="line"><span class="comment"># 我们之前的set都会在名字后面加对应的变量名,如果不加默认为空字符串,如果先前已经定义则会重置该变量为空.</span></span><br><span class="line"></span><br><span class="line">set(GAME_RES_FOLDER</span><br><span class="line">    &quot;$&#123;CMAKE_CURRENT_SOURCE_DIR&#125;/Resources&quot;</span><br><span class="line">    )</span><br><span class="line">if(APPLE OR VS)</span><br><span class="line">    cocos_mark_multi_resources(cc_common_res RES_TO &quot;Resources&quot; FOLDERS $&#123;GAME_RES_FOLDER&#125;)</span><br><span class="line">endif()</span><br><span class="line"><span class="comment"># APPLE: cmake内置变量,根据当前平台判断是否为真</span></span><br><span class="line"><span class="comment"># VS: cocos自定义变量,判断是否使用VS进行编译</span></span><br><span class="line"></span><br><span class="line">list(APPEND GAME_HEADER</span><br><span class="line">     Classes/AppMacros.h</span><br><span class="line">     Classes/HelloWorldScene.h</span><br><span class="line">     Classes/AppDelegate.h</span><br><span class="line">     )</span><br><span class="line">list(APPEND GAME_SOURCE</span><br><span class="line">     Classes/AppDelegate.cpp</span><br><span class="line">     Classes/HelloWorldScene.cpp</span><br><span class="line">     )</span><br><span class="line"><span class="comment"># 将对应的文件添加到先前定义的两个列表变量中</span></span><br><span class="line">if(ANDROID)</span><br><span class="line">    <span class="comment"># change APP_NAME to the share library name for Android, it&#x27;s value depend on AndroidManifest.xml</span></span><br><span class="line">    set(APP_NAME cpp_empty_test)</span><br><span class="line">    list(APPEND GAME_SOURCE</span><br><span class="line">         proj.android/app/jni/main.cpp</span><br><span class="line">         )</span><br><span class="line">elseif(LINUX)</span><br><span class="line">    list(APPEND GAME_SOURCE</span><br><span class="line">         proj.linux/main.cpp</span><br><span class="line">         )</span><br><span class="line">elseif(WINDOWS)</span><br><span class="line">    list(APPEND GAME_HEADER</span><br><span class="line">         proj.win32/main.h</span><br><span class="line">         )</span><br><span class="line">    list(APPEND GAME_SOURCE</span><br><span class="line">         proj.win32/main.cpp</span><br><span class="line">         $&#123;cc_common_res&#125;</span><br><span class="line">         )</span><br><span class="line">elseif(APPLE)</span><br><span class="line">    if(IOS)</span><br><span class="line">        list(APPEND GAME_HEADER</span><br><span class="line">             proj.ios/AppController.h</span><br><span class="line">             proj.ios/RootViewController.h</span><br><span class="line">             )</span><br><span class="line">        set(APP_UI_RES</span><br><span class="line">            proj.ios/LaunchScreen.storyboard</span><br><span class="line">            proj.ios/LaunchScreenBackground.png</span><br><span class="line">            proj.ios/Images.xcassets</span><br><span class="line">            )</span><br><span class="line">        list(APPEND GAME_SOURCE</span><br><span class="line">             proj.ios/main.m</span><br><span class="line">             proj.ios/AppController.mm</span><br><span class="line">             proj.ios/RootViewController.mm</span><br><span class="line">             $&#123;APP_UI_RES&#125;</span><br><span class="line">             )</span><br><span class="line">    elseif(MACOSX)</span><br><span class="line">        set(APP_UI_RES</span><br><span class="line">            proj.mac/Icon.icns</span><br><span class="line">            proj.mac/Info.plist</span><br><span class="line">            proj.mac/en.lproj/MainMenu.xib</span><br><span class="line">            proj.mac/en.lproj/InfoPlist.strings</span><br><span class="line">            )</span><br><span class="line">        list(APPEND GAME_SOURCE</span><br><span class="line">             proj.mac/main.cpp</span><br><span class="line">             $&#123;APP_UI_RES&#125;</span><br><span class="line">             )</span><br><span class="line">    endif()</span><br><span class="line">    list(APPEND GAME_SOURCE $&#123;cc_common_res&#125;)</span><br><span class="line">endif()</span><br><span class="line"></span><br><span class="line">set(all_code_files</span><br><span class="line">    $&#123;GAME_HEADER&#125;</span><br><span class="line">    $&#123;GAME_SOURCE&#125;</span><br><span class="line">    )</span><br><span class="line"><span class="comment"># 将两个列表变量打开后送入变量all_code_files中</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># mark app complie info</span></span><br><span class="line">if(NOT ANDROID)</span><br><span class="line">    add_executable($&#123;APP_NAME&#125; $&#123;all_code_files&#125;)</span><br><span class="line">    <span class="comment"># 使用所有文件编译成对应名字的可执行程序</span></span><br><span class="line">else()</span><br><span class="line">    add_library($&#123;APP_NAME&#125; SHARED $&#123;all_code_files&#125;)</span><br><span class="line">    add_subdirectory($&#123;COCOS2DX_ROOT_PATH&#125;/cocos/platform/android $&#123;ENGINE_BINARY_PATH&#125;/cocos/platform)</span><br><span class="line">    target_link_libraries($&#123;APP_NAME&#125; -Wl,--whole-archive cpp_android_spec -Wl,--no-whole-archive)</span><br><span class="line">endif()</span><br><span class="line"></span><br><span class="line">target_link_libraries($&#123;APP_NAME&#125; cocos2d)</span><br><span class="line"><span class="comment"># target_link_libraries: 关键字,指定链接器将cocos2d库和游戏可执行文件绑定</span></span><br><span class="line">target_include_directories($&#123;APP_NAME&#125; PRIVATE Classes)</span><br><span class="line"><span class="comment"># 当你在 .cpp 文件中写 #include &quot;HelloWorldScene.h&quot; 时，编译器需要知道去哪里找这个文件。默认情况下，编译器只在当前源文件所在目录和系统目录里找。</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 这行命令相当于告诉编译器：</span></span><br><span class="line"><span class="comment"># “编译 $&#123;APP_NAME&#125; 的源文件时，记得额外去 Classes 目录下搜索头文件。”</span></span><br></pre></td></tr></table></figure><p>根据这个cmakelist,我们大致可以明白cmake的构建过程:</p><ol><li>配置编译时要用到的环境变量</li><li>设置要进行编译的文件(如这里的GAME_HEADER和GAME_SOURCE)</li><li>针对不同的平台设置不同的编译链</li><li>加入静态和动态库链接</li></ol><p>拓展阅读:</p><ul><li><a href="https://hsf-training.github.io/hsf-training-cmake-webpage/">比官网写得更好的cmake教程</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;前置知识: cpp编译原理(4/8)&lt;/h1&gt;
&lt;h2 id=&quot;编译器种类&quot;&gt;编译器种类&lt;/h2&gt;
&lt;p&gt;市面上有三大主流编译器&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;微软开发的&lt;strong&gt;MSVC&lt;/strong&gt;(Microsoft Visual C++),集成在VS中,另外</summary>
      
    
    
    
    
    <category term="cpp" scheme="https://revival-of-hope.github.io/tags/cpp/"/>
    
  </entry>
  
  <entry>
    <title>阅读与思考</title>
    <link href="https://revival-of-hope.github.io/2026/04/02/dynamic-2026-04-02-%E9%98%85%E8%AF%BB%E4%B8%8E%E6%80%9D%E8%80%83/"/>
    <id>https://revival-of-hope.github.io/2026/04/02/dynamic-2026-04-02-%E9%98%85%E8%AF%BB%E4%B8%8E%E6%80%9D%E8%80%83/</id>
    <published>2026-04-02T09:32:10.000Z</published>
    <updated>2026-04-14T07:13:54.170Z</updated>
    
    <content type="html"><![CDATA[<h1 id="每日阅读"><a class="markdownIt-Anchor" href="#每日阅读"></a> 每日阅读</h1><h2 id="2025-11-15"><a class="markdownIt-Anchor" href="#2025-11-15"></a> 2025-11-15</h2><ol><li></li></ol><blockquote><p>很多人意识不到运气的重要性，而错把成功归功于自己的才能和努力，<br />却没有意识到好运在其中的重要性。忽视了这一点就难以保持谦虚，难以不断学习。<br />明白了运气的重要性，就知道不是人人生而能得到平等的机会的，<br />在遇到处境不如自己的人，不能假设这种差别是聪明或努力程度的不同造成的，应该知道善待弱者。</p></blockquote><ol start="2"><li></li></ol><blockquote><p>此后不久，王先生得了脑血栓。我很有些感慨：敢于直面自己一丁点儿小错的性情中人，活在一个是非不分，且明知错了却死不认账的世道里，能不得脑血<br />栓？</p></blockquote><ol start="3"><li></li></ol><blockquote><p>我从 D 道开始，接受了大量的指导。周围的人都对我很好，我慢慢进步，终于进入了 C 道。那组的人也热情欢迎我。<br />但是，我注意到，旁边 B 道的人并不像 C 道那样友善。A 道选手都非常友善，慷慨给予鼓励、表扬和提示。<br />我怀疑这是普遍现象：A 道、C 道和 D 道的人都很友善，大家几乎都乐于助人；B 道的人则是对 A 道和其他 B 道选手友善，但对 C 道和 D 道则不然。<br />因为我后来发现，其他运动领域也是如此。那些仅次于顶级选手的运动员，往往对不如自己的选手很苛刻，害怕别人超过自己。<br />学术界也有这种现象。真正伟大的研究者慷慨而热于助人，许多普通水平的研究者也是这样。然而，那些有一定知名度、但又没有做出顶尖成果的研究者，对不如自己的人就不友善了。<br />当你是最好的 A 组时，很容易表现得宽宏大量，你确信自己会有成果，这让你安心无忧。<br />当你处于平均水平或低于平均水平（C 组或 D 组）时，表现得友善也很容易。远离顶尖水平，意味着竞争压力不大，所需要付出的努力可能也不大，你会有一种“放轻松”的心态（反正我到不了顶峰，就当作玩呗）。<br />那些仅次于优秀水平的人，感受到最大的竞争压力。你离顶峰如此之近，追赶却又艰难无比，放弃又不甘心。最令人沮丧的是，没有人记得第二名。同时，后面的人还可能超过你。所有这些因素，都可能导致一种不友善的态度。</p></blockquote><ol start="4"><li></li></ol><blockquote><p>我从来不想辩论，但如果必须辩论，我希望自己会输。<br />我宁愿对方的观点是正确的，他来说服我，因为这样会比我的观点是正确的，我来说服他，对我更有趣。</p></blockquote><p>有些是周刊上的,有些是杂志里的,很后悔我以前不会用博客把这些记录下来,想要找到来源都是难上加难.</p><h2 id="2025-11-17"><a class="markdownIt-Anchor" href="#2025-11-17"></a> 2025-11-17</h2><p>最近上wiki看了杨靖宇的词条<a href="https://zh.wikipedia.org/wiki/%E6%9D%A8%E9%9D%96%E5%AE%87">杨靖宇wiki</a>,缘由是看了周海婴写的回忆录,里面提到了民主元老马叙伦,马叙伦界面里提到了白朗,白朗界面里提到了杨靖宇,才发现自己的杨靖宇的了解确实贫乏.(或许我可以不断的跳转下去看一整天吧,wiki确实是一个知识宝库,五湖四海的人们四处搜集信息再将他们合并,方便后人来看,这也是开源精神触动我的核心)</p><p>纵览杨将军的生平,确实是一个典型的共产主义战士,为了人民的解放先后与地主,军阀,日寇斗争,抱持着自己的信念和理想战斗到了最后一刻,即便只剩自己一人.这与古巴的切.格瓦拉又是何其相似.他们用自己的生命彰显了信念的力量,让其他或多或少抱有私心的灵魂都黯然退下,战栗不已.</p><p>要抱有一个纯粹的理想,做一个纯粹的人多难啊,在当下更是如此.</p><h2 id="2025-11-19"><a class="markdownIt-Anchor" href="#2025-11-19"></a> 2025-11-19</h2><blockquote><p>现在的孩子们比以前更容易接触到成年人的世界，因此他们更早成人化。<br />从很小的年龄起，他们就在视频网站观看暴力和战争，在社交网络上看到性感和暴露的照片和视频。<br />然而，当孩子们成年以后，他们往往无法实现经济独立，也没有机会承担足够的责任。<br />结果，整个社会的文化就变得很幼稚，成年人感到无法做出承诺，即使承诺了也缺乏信心，对以后的生活感到难以把握。<br />他们的行事方式和处事态度，就像还在青少年时期。<br /><a href="https://github.com/ruanyf/weekly">周刊263期</a></p></blockquote><p>最近在看周海婴写的回忆录&lt;&lt;我与鲁迅七十年&gt;&gt;,最令人惊奇的地方在于他能记得许许多多零碎的小事情,以当时他七十岁的高龄来看,记忆力已经算很不错了,但另一方面,他的回忆基本是由这些琐事构成的,因此整篇传记的时间线有点散乱,主线不明晰,没什么重要的叙述点,经常在叙述一个人的时候联想到当下的事情,看得很累.</p><p>正当我想就此放下这本书不再看的时候,突然想到,这个传记不就是我们一般人的生平吗,除了顶着鲁迅儿子的光环之外,他的日常生活与我们并没有什么不同,<br />都是由一件件琐事构成的,并没什么值得写入历史大书特书的事情,只是在随着时代的潮流艰难的生存着,没有大起大落,但确实有悲欢离合,这才是我们的生活.想到这里,我把这本书又拿起来了.</p><h2 id="2025-11-28"><a class="markdownIt-Anchor" href="#2025-11-28"></a> 2025-11-28</h2><ol><li></li></ol><blockquote><p>生命太短暂，不能花在那些不值得阅读的内容上面。<br />就算你是一个很爱读书的人，活到70岁最多大概能阅读15000本书，这只占世界最大图书馆美国国会图书馆3800万册藏书的0.04%。<br />我们一生中能够阅读的书籍其实很少。因此，关键技能不是多读，而是跳过那些不值得读的内容。<br /><a href="https://news.ycombinator.com/item?id=34310318">– Hacker News 读者</a></p></blockquote><ol start="2"><li></li></ol><blockquote><p>有些领域变化非常快，在有人写书之前，博客有时是唯一的信息来源。Stable diffusion 模型出现后的第二天，人们就已经在写博客了，书籍永远不会那么快。<br />而且，博客往往是免费的，而书籍和论文则被锁定在付费墙之后。因此，你可以这么认为，博客获取灵感，书籍获取知识<br /><a href="https://news.ycombinator.com/item?id=34310109">– Hacker News 读者</a></p></blockquote><h2 id="2025-12-09"><a class="markdownIt-Anchor" href="#2025-12-09"></a> 2025-12-09</h2><blockquote><p>每当有人给我的开源项目，提出这样或那样的要求，我就给他三个 F，让他自己选一个。<br />Fix it, Fork it, F**k off.<br /><a href="https://boyter.org/posts/the-three-f-s-of-open-source/">来源</a></p></blockquote><p>每当我看到一个比较小众的实用开源项目,通常都是只有一个contributer或者一个branch,而issue却有好几个或者几十个,这些issue很少是提修改建议的,更多的是要求增加各种各样的自己想要的需求.</p><p>如果每个人都这样做,从不想到自己加入到这个项目中帮助创建者完善功能,那么开发者的热情将会急速冷却,开源社区也将不复存在,幸好现在还不是这样.</p><blockquote><p>人生有没有意义？人类又有什么意义？<br />我说，人生是有意义的，而人类则是没有意义的。<br />询问人类的存在有没有意义，就等于询问地球或宇宙的存在有没有意义一样，是得不到答案的。<br />人生的意义是什么呢？它的意义就在于为没有意义的人类工作、服务等等，其目的不外乎是使人类生活得更好并得以延续。<br />反正人类是现实的存在，你又是其中一员，你有义务使它发展延续。你只要这样做了，你的人生就具有了意义，或者说价值，并不一定要去理会人类存在的意义。<br /><a href="https://ruanyf-weekly.vercel.app/weekly/issue-228">来源</a></p></blockquote><p>要我说这段话只是消极中的乐观而已,人类没有意义,人生也没有意义.</p><p>人类只是感觉的动物,我们当前的体验决定了我们的感受,也正是一次次体验塑造了我们.所以尽管人生没有意义,我还是想去多体验目前所没能看到和听到的一切.</p><p>因此我不愿意沿着别人的老路走,因为这终究是别人的体验,重走一次未免太乏味;但我也不愿意去走一条全新的,险峻的道路,因为我不想让自己遭罪.</p><h2 id="2025-12-14"><a class="markdownIt-Anchor" href="#2025-12-14"></a> 2025-12-14</h2><p><a href="https://zh.wikipedia.org/wiki/%E4%BA%9A%E5%8E%86%E5%B1%B1%E5%A4%A7%C2%B7%E5%85%8B%E4%BC%A6%E6%96%AF%E5%9F%BA">克伦斯基wiki</a></p><p>今天翻wiki时通过多次跳转转到了克伦斯基的界面,之前对他的印象只有布尔什维克对他主政政府时期的控诉,以为他只是一个常见的反对派角色.</p><p><img src="/images/2025-12-14-%E6%AF%8F%E6%97%A5%E9%98%85%E8%AF%BB/1.webp" alt="" /><br />看完wiki之后才发现他与<em>陈独秀</em>,<em>托洛茨基</em>并无区别,同样是内心向往着一个更加美好,自由的社会,同样是怀有或多或少的高士情怀,但是每况愈下的社会形势迫使他们站出来,担任自己本不想担任的领袖角色,在时代的浪潮下起起落落,最终被推上风口浪尖,只是因为不愿意参与尔虞我诈的政治斗争,狠不下心去为自己谋求应有的利益,怀着革命为人民的期望,最后却成为被革命的对象,从先驱者变成背叛者,不为敌我双方所认同,无路可走,跌下本就不属于自己的神坛,摔得粉碎.<br />被尘封在历史的角落,所有的功绩都被后来者掠夺殆尽.</p><p><strong>他们宛若一颗流星,照亮了整个世界,而后轰然坠落,再无人记得他们.</strong></p><p>这也是我厌恶英雄光环和领袖情结的原因,很多人之所以能够从渺渺众生中脱颖而出,原因便是对自己狠得下心,愿意去做那些旁人坚持不下去或者难以接受的事情,可是如果一个人对自己都能如此狠心,那对待别人又怎么可能会不狠心,不使用一些肮脏的手段,又怎么能让不甘居于人下的自己摘取桂冠,不通过残酷的斗争,又怎么可能胜过同样是英雄俊杰的对手甚至是同志.</p><p>可惜我对自己也狠不下心,又何从说对别人狠心,只好默默地给英雄们让道了.</p><p>之后会出一系列文章分析俄国革命的全流程,之所以写在这,是提醒自己别忘了.</p><h2 id="2025-12-18"><a class="markdownIt-Anchor" href="#2025-12-18"></a> 2025-12-18</h2><blockquote><p><a href="https://exquora.thoughtstorms.info/"></a></p><p>我对 Quora 上瘾，情不自禁使用这个网站。那里有一些很棒的问题和讨论，激发了我的灵感和想法。</p><p>但是当我重新阅读自己写的答案，一方面欣赏我的修辞和洞察力，另一方面也看到了很多想法可以成长为更大的成果。它们本可能进一步发展为软件、文章、论文、创业公司、书籍或社会运动，但任何事都没有发生。</p><p>不仅如此，还有许多篇我写的长篇大论已经无关紧要，沦为了废文。还有很多我花了好几个小时写的评论，试图说服对于这些问题永远不可能改变观点的那些读者。</p><p>我花了数千（也许是数万）小时在 Quora 上写作。我写的远不止11000个答案，还有5000多个草稿答案，其中很多已经写得很长了，只是因为来不及最终润色而没​​有发表。</p></blockquote><p>这篇文章说出了我想说的,我见过很多极其优秀的故事或者技术讲解,可作者发布的网站恰恰是贴吧,天涯或者知乎这样的问答网站或者论坛,作者的观点被一篇篇孤立的文章或者一条条回复分割开来,很难形成一个完整的体系.</p><p>而天涯的倒闭也说明了在这些自己不能掌控的平台上并不能保证自己的思想可以永久保留下来,那些优秀的文章随着服务器的关闭直接成为了无法触及的历史,即便是作者本人也无法找回.</p><p>因此,我拒绝在论坛里写长篇大论,而是作为读者去发掘优秀的文章,可惜的是现在优秀的文章也越来越少了,而AI生成的无意义内容充斥着论坛的每个角落,很难找到真正有意义的东西,这也是我悲哀的一点.</p><p>一方面,论坛不能让作者的文字体系化,另一方面,博客不能保证作者的文章能广泛传播,比如我这个博客就不是所有国人都能访问到的,很难找到一个折衷点.</p><p>如果我有那个能力,我可能会去开发一个跨平台的博客论坛吧,侧重点在于分享生活和技术,每个人都能像写博客一样把文章从自己本地上传,改进一下SEO算法,保证最好的文章优先显示,集成了评论系统和follow功能,最大幅度减少作者需要折腾的东西,只写md就行了,呈现的页面也由作者自己在前端设置.重点是这个网站能长久保存,不会让作者的心血白费.</p><h2 id="2025-12-21"><a class="markdownIt-Anchor" href="#2025-12-21"></a> 2025-12-21</h2><blockquote><ol><li></li></ol></blockquote><p><a href="https://ruanyf-weekly.vercel.app/weekly/issue-210">周刊210期</a><br />诺拉·劳森还说了一个观点。大家通常认为，复杂系统往往会在经济繁荣的时候崩溃，因为业务太多，支撑不过来，但他认为不是这样的，系统崩溃往往发生在经济收缩期。<br />经济繁荣时期，软件公司会大量雇佣新员工，投入更多的财力和人力，支撑复杂系统。等到经济收缩期，公司开始减少投入、冻结招聘或裁员，复杂系统可能就会在这个时候出问题，变得难以维护。<br />现在就是经济收缩期，那么接下来，会不会就是软件故障的高发期，我们将看到很多复杂系统的崩溃？</p><blockquote><ol start="2"><li></li></ol></blockquote><p>IT 行业与传统制造业有一个重要区别，就是 IT 行业有着严重的垄断。<br />全世界的智能手机有70亿部，比汽车多出5倍（14亿辆）。但是，智能手机制造商比汽车制造商少了好几个数量级。搜索引擎、社交网络、操作系统都是这样，几个巨头就垄断了整个市场。</p><blockquote><p>3.<a href="https://blog.amamiyayuuko.com/p/ai-new-bing/">来源</a><br />各个互联网公司都试图把自己的网站做成一个完全封闭的APP，你没法在搜索引擎上搜索到微信公众号的文章、小红书的内容、淘宝的商品描述……这就导致bing只能从非常有限的地方获得中文语料，最后就导致他的回答特别池沼……而且比别的AI都池沼十倍甚至九倍……</p></blockquote><h2 id="2025-12-24"><a class="markdownIt-Anchor" href="#2025-12-24"></a> 2025-12-24</h2><blockquote><p><a href="https://www.zackwu.com/posts/2020-07-19-why-i-choose-to-work-after-graduation/">来源</a><br />我是一个习惯于早做规划与反复思索的人。<br />而坚持写博客最大的好处就是，可以时不时翻看之前写的文章，宛若跨越时间的荆棘，与曾经的自己促膝长谈。</p></blockquote><blockquote><p>现在想来，十九岁时，我的迷惘归结下来，即是对将来自己的出路无所适从：出国留学，国内升学，抑或是早日进入职场。这几条路上，都有前人留下的无数足迹与丰碑，但也无可避免地悬着前人用泪水濯洗的种种失败警示牌。我曾尝试从中选出最优解，然而反复的纠结过后，我所意识到的，是这种比较的注定无结果：每当我觉得某个选择优于其他选择时，总会有某些信息刷新我的认知，让我匆忙撤回自己的决定。一如盲人同时摸象与鲸鱼，用片面的认知去比较复杂的事物，注定失败。</p></blockquote><blockquote><p>先是出国。关于这个选项，我曾仔细考虑，然而最终出于经济上的原因（赴美读硕开销实在过于昂贵）和人生规划的原因（<strong>我对学术无甚兴趣，不愿耗费多年去追求博士学位</strong>），出国成为了三者中第一个被排除的选项。而站在当下（2020 年）来看，全球疫情的扩散与民族主义情绪的对立，出国虽然依旧有着不可抗拒的诱惑力（比如优渥的学术环境与工作环境），但是更显得充满了极度未知的不确定性。</p></blockquote><blockquote><p>「选择比努力更重要。」这句话近年来已经广为人知，不少人以此自我调侃，感叹自己当初选择的失误（比如选错学校、选错专业），但当下一次选择到来时，却又不假思索地下意识地站到了主流的人群中，甚至拼尽全力挤出一条道路以加入主流人群。</p><p>诚然，作为一个才识普通的人，我或许不具备选择最合适的道路的能力，但我所希望的，是不盲从、不追随，依靠自身的观察与思考，尽量选出一条相对合适的道路。<br />人类的悲欢并不相通，但是相似。</p><p>关于读研与工作，面临同样困扰的也并非只有我一个人。</p><p>在做出决定的过程中，我阅读了不少人的博客与帖子，从中获得了一些宝贵的信息与经验，或多或少地影响了我最终的决定。</p><p>由于之前并未刻意保存浏览过的网页，在此，仅仅列出其中的一小部分：</p></blockquote><ul><li><a href="https://laike9m.com/blog/suo-yi-dao-di-yao-bu-yao-du-yan,119/">所以，到底要不要读研？ - laike9m’s blog</a></li><li><a href="http://gaocegege.com/Blog/%E9%9A%8F%E7%AC%94/master">研究生复盘 | 高策</a></li><li><a href="https://ipotato.me/article/65">iPotato | 在读研 &amp; 工作中选择后者</a></li><li><a href="https://www.v2ex.com/t/580275">已有名校 CS 本科学历，读研对于计算机行业的职业发展有多大的意义？ - V2EX</a></li></ul><p>这恰恰是我当下迷惘而又感到无路可走的心境,专业课程的枯燥与无用让我感到厌烦,愈加激烈的保研争夺战让我望而却步,而那些学术论文里面的水分都可以让海平面再升高一米了.</p><p>可日愈恶化的工作环境阻断了我得过且过的想法,作为雏鸟在第一家公司所学习的架构未必对我的技术提升有任何的助益,而我的学历劣势也将在很长一段时间内保持下去.</p><p>这种左右为难的状况让我一边痛苦一边踟蹰,只好到处翻阅博客,搜寻资源,希望能够对当前的我有些微的救赎.</p><p>这篇博客可惜的地方在于没有考虑到提前工作的坏处,但看了看他<a href="https://www.zackwu.com/posts/2025-11-26-think-long-term-and-work-hard/">最近的文章</a>,过得还算不错,可我现在还没有那个胆量去直接放弃保研,因为我讨厌唯一的选择,希望能多几条路可以走,而现在我仅仅是在本专业保研排名的边缘上(笑).这解释为是对我自己负责,但更可能只是我胆小罢了.</p><h3 id="题外话"><a class="markdownIt-Anchor" href="#题外话"></a> 题外话:</h3><p>博客是我最喜欢的信息传播方式,我永远都可以在某个博客里找到一些用一般手段怎么搜都搜不到的棘手问题的解决方案,永远都可以找到一个处境和你相似的人,永远都可以找到能够为你指引道路的人.</p><p>这半年来我搜集了差不多一百多个博客,我打算之后整理一下把里面的精华部分传到GitHub上,</p><h2 id="2025-12-26"><a class="markdownIt-Anchor" href="#2025-12-26"></a> 2025-12-26</h2><blockquote><p><a href="https://ipotato.me/article/65">来源</a><br />说实话大三上就开始找实习其实是一个有些冒险，但相应收益会比较高的做法。风险点在于学校的课程安排趋近于收尾，一些比较难的专业课也会集中在这个学期，如何平衡好学习和工作是一个首先要考虑的因素，这一点上我的做法比较粗暴：直接翘课。一是因为成绩也够不着保研的尾巴，无需那么在意绩点，二是因为除了体育课这种比较难逃的课，其他一律统统全翘，只是为了挤出了一周 4 天的实习时长，至于课业，只能安排到工作日下班，周末以及考试临近时的请假进行学习。但即便是翘课，对于一些专业课程还是要用心，比如 OS 以及编译原理等课程，可以说是专业的重中之重，不光学习会接触，在面试以及工作中也是非常核心的内容。好在一个学期下来课虽然都翘了，但最后的结果也不坏，没有顾此失彼而整出来个挂科。</p></blockquote><ul><li>其实如果没打算读研的话就可以这么做,既然是本科毕业就工作,那绩点的意义就消失了.</li></ul><p>这几天用<a href="https://rawweb.org/">rawweb</a>不知道看了多少博客,足够大的体量保证了我搜任何一个关键词基本都能找到答案,每一个博客对我来说都是一个全新的领域,仿佛博主的生活就是我的生活,他们的迷惘和彷徨就是我的迷惘和彷徨.就比如我搜索关键词保研,一下就得到了一千多条结果,里面的很多文章对我来说都是对读研和工作这一抉择的重新认识.</p><h2 id="ramírez2026-02-01"><a class="markdownIt-Anchor" href="#ramírez2026-02-01"></a> Ramírez(2026-02-01)</h2><p><img src="/images/archives/2026/2026-02-01/PixPin_2026-02-01_11-50-05.webp" alt="alt text" /></p><p>阅读fastapi和sqlmodel的教程就觉得作者是一个风趣,谦虚,对于疑问要穷追不舍,对于新手非常友好的hacker</p><blockquote><p><a href="https://github.com/tiangolo">Ramírez主页</a><br /><a href="https://tiangolo.medium.com/concurrent-burgers-understand-async-await-eeec05ae7cfe">汉堡店比喻</a></p></blockquote><p>潦草搜了搜,互联网上关于他的信息还是比较少的,也没有什么比较亮眼的镜头表现,他的英语口音我也比较难接受.只大概知道他没有读大学,从哥伦比亚到了德国工作.</p><p>看得出来,他将几乎所有的心血都投入到了开源项目中,所以才不会有时间去包装,宣传自己.</p><p>这是一位真正令人尊重的hacker.</p><h2 id="迷茫时就多看看博客02-04"><a class="markdownIt-Anchor" href="#迷茫时就多看看博客02-04"></a> 迷茫时就多看看博客(02-04)</h2><p>大学也已经过了一年半了,但我还是没能看清之后要怎么做,保研已经非常够呛了,除非之后一年半我能保持几乎满绩,但厌恶填鸭教育的我看来是很难做到了.所以,工作or保研?</p><blockquote><p><a href="https://ddadaal.me/articles/summary-for-2022/cn">来源</a><br />本科前两年半时，我拿定主意直接工作，却在最后时刻，抱着想看看有没有新的机会的想法，极限转弯踏入了研究生的大门。而研究生的前两年的体验让我认识到高校做工程不靠谱，回去看业界的机会时，发现实验室做的工作不能让我踏入一个所谓更“高阶”的工作，我能投向的业界工作和读本科时基本没有区别。后来，当我意识到，当前国内大环境下体制内的技术工作也并非一无是处，想把握北大应届毕业生这个机会了解一下体制内的机会，但却错过了这个时间窗口，最后仍然投向了微软，和三年前的唯一的区别也就是大组（C+AI vs STCA）和工作地点（苏州 vs 上海）的区别了。</p></blockquote><blockquote><p><a href="https://blog.cugxuan.cn/2025/01/23/Mood/record/2024-record/">来源</a><br />这次在各种因素的推动下终于做出改变，总共就简单面试了 3 家公司，最后拿到的机会是深圳的国企和武汉的互联网公司。之前加了一个武汉的交流群，了解到武汉更为恶劣的就业环境，如果回武汉工作几年被裁或者一定要跳槽换工作，那将是很困难的事情，也打消了最近几年有机会就考虑回武汉的念头。<br />最近几年经历了互联网的动荡，在互联网跟大家一起卷已经卷不动了，最终选择了深圳相对稳定的工作。参加工作后，自己对互联网的热情完全消磨殆尽了，我虽然不够上进，执行力也很弱，但是在学校比较闲就会捣鼓东西，写博客分享很开心。</p><p>当前的经济形势下，今年的情况明显比去年前年更加严峻，各家互联网公司都十分默契地通过裁员来降本。几个月前，上周还在一起吃饭朋友某天午休过后，被叫去谈话沟通裁员。几乎没有交接流程，当天就收拾东西滚蛋，十分残酷。</p></blockquote><blockquote><p><a href="https://hoa.moe/blog/summer-intern/">来源</a><br />整体来说今年就业环境在部分岗位还是有所回暖。最卷的赛道应该是后端，据我了解三月初各大厂开始面人，四月初就基本没有 hc 了，完全是地狱难度。前端和客户端今年有所扩招，有时候你投的是后端岗 HR 也会打电话来问你有没有面前端的意向。</p></blockquote><blockquote><p><a href="https://www.cnkirito.moe/fresh-seek-job/">来源</a><br />此文我是想写给应届生的，1-3 年的工作经验没那么恐怖，大多数情况下，你的能力够了，公司不会跟你较真，用年限压你，所以看到自己技术水平能够达到，资历却不符合的岗位，也可以尝试着投一投。<br />这类公司其实已经算是对技术有了要求了，而且技术细节都明确了出来，但是，看到只对 jsp，servlet 这些技术有所要求，明眼人都知道，这是在招初级开发，了解一点框架，懂计算机基础，这样的新手，公司还是可以接受的，上海这边针对可以独立开发的应届生，或者培训班出来可以直接上手的非科班生：软件公司，实习开价大概在 4-5k，转正开价大概在 7-8k；互联网公司实习大概在 5-6k，转正开价 9-10k 起步。985/211 或者能力不错能够入职的高校生，在互联网名企的开价，就以阿里为例，我了解到的情况大概是 12k14 or 1216。这里都是说一个上海地区价格，不适用与全国。北京的情况是 IT 非常发达，很多互联网公司都在北京，而上海，深圳，广州其次，注意，上海是金融之都，并非 IT 之都。<br />…<br />很多学生没有实习计划，或者出于对工作的恐惧，或者出于对自身能力的不自信，又或者是对实习工资的不屑…也有一大部分同学，觉得春招太早了，希望留着机会等到秋招。我这里有一份据阿里巴巴某部门的公开数据：2020 年转正入职的校招生中 80% 来自于春招，20% 来自于秋招。「好书一本，明日在读」，我不推荐大家错过现在这个最好的时机。</p></blockquote><blockquote><p><a href="https://laike9m.com/blog/suo-yi-dao-di-yao-bu-yao-du-yan,119/">来源</a><br />这些幻想要批判起来说一年也说不完。不过相信大家也发现了一些共性，就是这种盲目推崇读研的人，往往对很多问题都缺乏基本了解，看问题也非常片面。我暗自揣测，他们中大部分可能并没有真正读过研究生，却又把自己目前的不如意归咎于没有读研，并幻想出了所谓读研之后的美好生活。我在计算所的时候，周围很多同学都觉得读研浪费时间，也包括我在内。有个哥们实在受不了实验室安排的无聊工作直接退学然后面试进了头条，人家也没嫌他学历不够。总之呢，读研这件事好不好，各人有各人的情况。对想进互联网行业的同学，可能确实是浪费时间，但若是想去考公务员或者拿户口，可能又很必要。关键还是要清楚自己的目标，分析自身情况，再来判断读研到底是不是一个好的选择。</p></blockquote><blockquote><p><a href="https://bitmingw.github.io/2016/12/13/study-abroad-review/">来源</a><br />整个找实习过程确实挺不容易，毕竟直接和研二学生对线，但最后结果还算不错，去了自己最想去的，对于AI整个行业来说，个人认为在现阶段显然是能实际落地的工程大于算法本身，数据质量大于算法。</p></blockquote><h2 id="博客阅读42"><a class="markdownIt-Anchor" href="#博客阅读42"></a> 博客阅读(4/2)</h2><p>很久没看博客了,今天来看一下:</p><blockquote><p><a href="https://halfrost.com/halfrost_2021/">链接</a><br />详细到可怕的CS硕士申请指南,尽管博客没有再更新过,但祝愿博主在美国能过得很好吧.</p></blockquote><blockquote><p><a href="https://halfrost.com/halfrost_2018/">链接</a><br />同一位博主身为过来人给初学者的建议.<br />博主认为年轻的时候就要选定一个专精的方向,进入工作岗位后再去随兴趣学习.</p></blockquote><p>这个观念我不敢苟同,因为如果不是运气特别好,能够进入一家允许你去尝试不同技术栈的公司的话,你是很容易在一个技术栈上把牢底坐穿的,枯燥的CRUD也会逐渐的消磨你的学习兴趣,对于其他的技术栈也会逐渐生疏,想要换方向就会非常非常的困难.</p><p>然而,应届招聘的时候又确实要求你的技术深度过关,这就真没办法了,我们只能在短短的三年内(但一般人意识到紧迫性的时候就已经是大二,甚至大三了)去尽可能的探索多的技术栈,找到自己最感兴趣并且真的有市场需求的技术后,做到一定程度的精通.<br />大厂面试看的终归是你的成长潜力和基本功,而对于记忆力过关的年轻人来说,熟悉一个技术的基础特性和常用框架只需要三个月就够了,并且可以同时学习多个技术而不至于感到吃力,但还是建议越早开始学越好.</p><h1 id="思考"><a class="markdownIt-Anchor" href="#思考"></a> 思考</h1><p>这里放一些不值得单独写一篇文章的吐槽</p><h2 id="ai思考2025-11-24"><a class="markdownIt-Anchor" href="#ai思考2025-11-24"></a> ai思考(2025-11-24)</h2><p>随着我在计算机学习方面的逐渐深入,ai对我的作用越来越有限,顶多是在配置环境里帮我减少一点阅读文档的时间了,大多数时候它都不能给我一个正确的解答,而是说些通用的,却不符合我当前情况的解答.</p><p>因此,现阶段的ai还是更多的用在文档办公,图像生成等这些日常场景或者娱乐场景中比较好,一旦想要深入的学习一些技术,ai的劣势就一览无遗,就连洛谷的黄题它也很多时候做不出来(虽然我有时候也做不出来~).</p><p>因此,我现在更偏爱于技术博客和技术文档,书籍,再也不会去想用ai火速掌握一门技术的可能,而按照当前ai发展的状态来看,所谓的ai编辑器既然底层都是调用的热门大模型的api,那么自然也就不能处理太高端的技术,最多是辅助学生完成基础的代码作业而已.</p><p>总结一下,如果ai不能在字面意义上实现自主学习,它永远只会是一个文档翻译器,当成省时间的阅读工具可以,想用来提升自我,学习技术,我看还是算了.一句话,你懂的越多,ai就越傻.</p><h2 id="道路与选择2025-12-16"><a class="markdownIt-Anchor" href="#道路与选择2025-12-16"></a> 道路与选择(2025-12-16)</h2><p>这几天有点焦躁和郁闷,总有种无所适从的感觉,浑浑噩噩的,对很多事都不上心了,于是现在静下来认真思考了一下当前的处境.</p><p>人生是由一大堆选择构成的,每一个分岔路口都决定了我以后的人生道路,有时候选择多种多样,似乎哪里都是通路,有时候仿佛是山穷水尽,让我别无选择.</p><p>人自然是希望选择越多越好,这就是我以前为什么倾向于做事提早做准备的原因,为的就是不让自己在ddl的时候显得无路可走.</p><p>但过多的选择,太多的可能,悲观的现实又让我痛苦万分,人生的道路总是要自己走的,但如果有一个引路人就更好了.看不清未来的道路只会使我无所适从,一边厌恶着填鸭式的教育,一边追求绩点的完美,只怕到头来也是一场空.</p><h2 id="考研人数的下降2026-01-03"><a class="markdownIt-Anchor" href="#考研人数的下降2026-01-03"></a> 考研人数的下降(2026-01-03)</h2><p><img src="/images/archives/2026/2026-01-03/PixPin_2026-01-03_21-30-11.webp" alt="" /></p><p><img src="/images/archives/2026/2026-01-03/PixPin_2026-01-03_21-46-12.webp" alt="" /><br />可以看出来,尽管本科毕业人数越来越多,研究生招生人数越来越多,报考研究生的人数却越来越少,接着看下图<br /><img src="/images/archives/2026/2026-01-03/PixPin_2026-01-03_21-56-44.webp" alt="" /><br /><img src="/images/archives/2026/2026-01-03/PixPin_2026-01-03_21-57-46.webp" alt="" /><br />疫情的影响太大了,大家都开始捂住钱包,不会再去做大胆的投资了,因此收入较稳定的餐饮业扶摇直上,而旅游业直到现在也未必恢复了元气.<br /><img src="/images/archives/2026/2026-01-03/PixPin_2026-01-03_22-07-44.webp" alt="" /><br />越来越多的人选择去找一些稳定的工作如公务员,医护人员,不愿意去创业了.</p><p>顺带一提还有战败cg,尽管没有今年的数据,但我相信降幅不会相差太多<br /><img src="/images/archives/2026/2026-01-03/PixPin_2026-01-03_21-50-15.webp" alt="" /></p><blockquote><p>这也是普通人的无奈吧,尽管身处这个时代,却连发生了什么都难以把握,只能隐约感觉到大环境不好,却找不到合理的数据来帮自己掌握一点点情况,连知情权也被剥夺,不知道下一步要怎么走,迷茫的在原地打转,只好依着惯例提心吊胆的生活,提不上享受生活,只看得见压抑和悲哀,失去了相信他人的胆量,失去了期盼明天的幸福,只是靠本分承担着责任,不是为了自己,而是为了亲人</p></blockquote><p>很早就想要拿日本跟本国做一个对比了,尽管人口基数差的很离谱,但以前的日本或许就是现在的我们,现在这里预告提醒我自己一下</p><h2 id="再战算法2026-01-11"><a class="markdownIt-Anchor" href="#再战算法2026-01-11"></a> 再战算法(2026-01-11)</h2><p>昨天考了程序设计范式,一开始觉得三个小时三道题不是有手就行,后来发现我太天真了.</p><p>第一道题是leetcode2002,一道关于不相交回文子串的中级题,我硬是没想到用dfs做,可能是一个月没碰算法生疏了吧,<br />第二道题是设计几个角色的类,考察多态和继承,还真有点难,报错信息我都看不懂,第三道题是求满足给定和的最短子串,尽管我一开始就把双指针写出来了,但却在一些测试数据中一直死循环,还好机房上装了vscode,不然我都不知道要怎么调试,前前后后最少调了半个小时才解决这个问题,只能说是在第一题没做出来的情况下太紧张脑子短路了.</p><p>这一顿折腾下来,我才发现我连最基本的dfs都没能很好地掌握,连普通的课程考试都过不了关,啥也不说了,速速去刷题,<br />之前看了百度之星的题目,彻底断了打acm的念想,但刷点水题娱乐一下也是挺好的,不仅能应付考试,还能应付面试.</p><h2 id="渺小的自我2026-01-25"><a class="markdownIt-Anchor" href="#渺小的自我2026-01-25"></a> 渺小的自我(2026-01-25)</h2><p>今天看到了<a href="https://nzooherd.github.io/posts/%E6%A0%A1%E6%8B%9B%E6%80%BB%E7%BB%93/">这篇博客</a>,匆匆看了一遍博主的往年博客,仿佛就看到了一年之后的自己.</p><p>我跟博主的经历极其相似,都是在一个不发达地区的小镇经历了懵懵懂懂的少年时光,觉得自己未来有无限可能,进入高中后才发现自己并不是天之骄子,最终考入一个中流985,并发现自己只是一个普通的不能再普通的人,并没有用读书改变命运的可能,也不存在任何奇幻的邂逅,只有一个灰暗的,却又捉摸不定的未来,为了最后一丝甜蜜的幻想而苦痛挣扎.</p><p>或许,一年后的今天,我就要像他一样懵懵懂懂的参加实习,之后通过秋招走入职场,陷入都市的牢笼,住在一个窄小的出租屋,每天来回通勤两小时,熬夜加班赶文档,为了晋升和加薪的机会苦苦挣扎,因为被解雇的可能而夜不能寐,一个人蜷缩在屋内度过新年.</p><p>可不知为什么,当想到这些的时候,我的内心却无比平静,仿佛认定了这就是我该走的道路一样.也罢,渺小的自我,又怎么能容纳什么宏大的理想呢.</p><h2 id="ai幻想2026-01-31"><a class="markdownIt-Anchor" href="#ai幻想2026-01-31"></a> ai幻想(2026-01-31)</h2><ul><li><strong>ai幻想</strong>:认为ai可以帮自己包揽技术上的难题</li></ul><blockquote><p>我大学这两年经历了不少ai幻想的例子,但也看见过有些同辈依然脚踏实地钻研技术.不得不承认,后者的比例要小的多,而他们的知识水平和成绩都让我不得不低头,承认他们与众不同的技术造诣.</p></blockquote><p>很难想象ai浪潮下的普通大学毕业生在真实的工作场景下会暴露出怎样的一种丑态,而这样一批靠ai吃ai,从来没能真正掌握技术的人攻读研究生,博士生的时候又要去怎样的水出一篇sci.</p><p>如果ai真的有他们想象的那么强大,那倒还可以糊弄过去,可惜的是目前ai的水平还远远不到家,近两年业界也并没有突破性的进展,依旧是在倒腾同一个模型,同一种方法,更多的是吃老本而不是真正的开拓创新.</p><p>可惜我早就听说在技术好的人未必晋升的快,说不定这些用ai糊弄架构,用心美化ppt的人反而能混的风生水起呢,哈哈,其实现在就是这样了😄</p><h2 id="反思2026-03-03"><a class="markdownIt-Anchor" href="#反思2026-03-03"></a> 反思(2026-03-03)</h2><p>家里人都希望我读研,有的是觉得程序员不稳定,有的是想让我进体制内当官,有的是觉得我可以扩大圈子.</p><p>而我一开始以为自己之所以不想读研而是想直接工作,是因为想要干一些有创造力的活,想要挑战自己,但现在我发现,如果在保证有稳定收入的情况下,最理想的条件是事少空闲多,可以有大把的时间干自己喜欢的事情,但事实上并没有这样的工作.</p><p>如果我选择进大厂,虽然有可能进一个很push很压抑的组,甚至还需要花力气去打点人际关系,走点向上社交,但这终归是可能,我可以通过各种方式改变自己的工作环境,即便可能在以后被裁员,我也总可以找到一份合适的工作,就算舆论再怎么渲染ai的可怕,我想自动化在取代高级程序员之前一定会先取代公务员和蓝领工人,如果当局不愿意动这个刀子(很大概率不愿意),那就一损俱损,如果经济危机来了,到头来还是要下手.(你要问为什么下手?因为当局总是优先保证高位者的权利,再通过裙带关系来让下位者吃残羹冷炙,如果你威胁到了他的切身利益,那肯定一脚踢开啊,无论在哪都是这样,民族国家之所以维持高军费警费,一方面是逗人的对外防御,更多的是对内防御,怕你不听话呢)</p><p>而到了那个时候,若参考1930年代,由于当局的弱势,无法进行像样的改革,又无法向外转移矛盾,再加上绝大部分人口都在农村,可以实现自给自足,城市里的所谓知识分子和公务员基本都受了老罪;而现在由于新兴人口都在城市,当局强势,加上过度依赖当代技术,向内转移矛盾将会导致全面崩坏,故只能向外转移矛盾,而且是非常猛烈的那种,可参考当前美国的外交政策.</p><p>所以,我坚决不选择考公,第一是因为流动性小,过于乏味,过多迎合,工资只能保证不饿死,加班也多(假期的值班,重要岗位的加班,什么,你说你的岗位很轻松基本没工作?那肯定是边缘岗位,未来反而更容易被裁),第二是因为太多人来考公,机遇过小难逢贵人,就算限制录取比例,但单位里(尤其是基层单位)的所谓元老都只是80年代生人,离退休还远着呢,导致机关臃肿无比,一定要疯狂的内卷才能吸引领导的注意,当然不卷也行,如果一辈子当个科员(当然博士有正科级待遇,说说而已啦,放首都里你算个屁,处级都没有实权,努力十年不如天降关系,若是像大多数博士公务员一样远离政治中心,晋升机会就小的多),我看你退休金未必会比爸妈高呢.</p><p>事实上,现在我也不太愿意去高强度岗位累死累活挣大钱,除非工作内容合我的胃口,挣钱的目的是什么,如果单纯是为了存钱而挣钱,那就有点好笑了;如果是为了脸上有光的虚荣心,那就更好笑了.</p><p>我的娱乐活动基本不是很花钱,也没什么旅游的欲望,目前也没有家庭的负担,但如果是为了未来,为了一点飘渺的机缘,为了所谓的经济自由,也可以去打拼打拼吧.</p><p>一代人有一代人的路要走,我不想走老一辈的路了.</p><h2 id="chatgpt大失败26310"><a class="markdownIt-Anchor" href="#chatgpt大失败26310"></a> ChatGPT大失败(26/3/10)</h2><p>现在GPT开始搞内部wiki,并且大幅度给免费版降智了,就连luogu的黄题都不太能搞得定,还总是输出垃圾文字和不存在的文献,可能是更改战略打算像Claude一样面向高端付费用户吧</p><p>即便是最近降智的Gemini也更好用一点,而Grok更是非常擅长查找资料和总结文献,所以,我决定彻底抛弃gpt</p><p><img src="/images/archives/2026/2026-03-21/PixPin_2026-03-24_13-07-29.webp" alt="alt text" /></p><ul><li>(3/24) Gemini终于把复制选项单独列在下面了…</li></ul><h2 id="外文文献并非就比中文好"><a class="markdownIt-Anchor" href="#外文文献并非就比中文好"></a> 外文文献并非就比中文好</h2><p>如果你不是很熟悉英文阅读,那么在读英文文献的时候你一定会逐行逐句的仔细阅读,不然你就看不懂了,而读中文文献的时候由于大多数句子一扫而过就能看懂,正常人不太会对某一个知识点看上太长时间.</p><p>所谓的英文教材精炼详细,只是因为你花了更多时间在阅读和思考而已了,未必是教材本身写的有多好,我见过太多看似详细其实是废话太多的外文教材和文档了.</p><p>事实上,如果去看中文翻译版的话,即使翻译质量再怎么差,花上与阅读外文同样的时间去扩展阅读,收获一定会大得多.</p><p>至于讲座视频更是如此,之所以说看国外讲座学的更好只是因为你花精力去理解英文了而已,同样的时间你花在看教材上我想会有用的多.总不可能米国人人都是至圣先师,随便一句都是金口玉言吧,低质量的讲授才是常态,随便一句话或者一页ppt就让你醍醐灌顶那只能说明你本来就啥也不会,不如好好看文献.</p><ul><li>补充(3/22): 我错了,遇到垃圾翻译时中英文版本夹杂着看的话,可以实现既能一目十行,也能深刻理解</li></ul><h2 id="建议学生只用ai来打杂"><a class="markdownIt-Anchor" href="#建议学生只用ai来打杂"></a> 建议学生只用AI来打杂</h2><p>我身边有不少人用AI来应付比赛和项目,费尽千辛万苦调试出一个勉强能过关的APP或者网站.但事实上这样啥也没学会.与其让AI去修复一个AI生成的大杂烩,不如靠自己的所学知识去排查异常,只有这样,才能真正的学到东西.</p><p>当你想学习一门新技术的时候,最可怕的就是问AI来它来手把手教你,事实上,AI所作的只不过是去查文档,然后把它破碎的理解喂给你而已,得到的知识远不如自己去看文档来的有体系.</p><h2 id="英文翻译317"><a class="markdownIt-Anchor" href="#英文翻译317"></a> 英文翻译(3/17)</h2><p>我认为中文的优点在于句式变换多样,能够以最少的字符表达出最多的意思,通过各种同义词和同音词实现独特的诙谐语感,需要读者从深层次理解句法的内涵,在文学领域是别领风骚的高端语言;<br />而英文则相反,以尽可能多的字符来表达足够精确的意思,这注定了英文句子大多是枯燥无味的,但是由于英文的表意精确,用语地道,从而在学术研究中占据了统治地位.</p><p>那么这样看来,广传的翻译原则&quot;信,达,雅&quot;其实是有道理的,&quot;信&quot;意味着能够真实反映原文的意思,&quot;达&quot;意味着好理解,可以让普通人也能看懂,&quot;雅&quot;意味着足够优美,能够体现出汉语的独特美感.</p><p>很可惜的是,在我看过的用中文翻译英文的学术文献中,很少有真正能实践这一点的,大多数译者都是硬着头皮把原文中的分词或者定义对照着词典一个个敲出来.如果自身不是技术专业的话就别来翻译技术类书籍啊(我不了解翻译界,或许有硬性指标之类的吗),但如果自身是技术专业还翻译成这个样子,就有点过分了.</p><h3 id="批判用例"><a class="markdownIt-Anchor" href="#批判用例"></a> 批判用例</h3><blockquote><p>这些CPU执行时间已大量测试过。虽然它们随进程和计算机的不同而变化很大，但是它们的频率曲线类似于图5-2所示。该曲线通常为指数或超指数的形式，具有大量短CPU执行和少量长CPU执行。I/O密集型程序通常具有大量短CPU执行。CPU 密集型程序可能只有少量长CPU执行。对于选择合适的CPU调度算法，这种分布是很重要的。</p></blockquote><p>尽管勉强能看懂,但这个名词翻译确实很离谱,看看原文:</p><blockquote><p>The durations of CPU bursts have been measured extensively. Although they vary greatly from process to process and from computer to computer, they tend to have a frequency curve similar to that shown in Figure 6.2. The curve is generally characterized as exponential or hyperexponential, with a large number of short CPU bursts and a small number of long CPU bursts.<br />An I/O -bound program typically has many short CPU bursts. A CPU-bound program might have a few long CPU bursts. This distribution can be important in the selection of an appropriate CPU-scheduling algorithm.</p></blockquote><p>首先这个<strong>The durations of CPU bursts</strong>翻译成CPU执行时间,是动了一点脑子的,但是完全没有体现出周期的意思,翻译为CPU周期长度应该会好一点.</p><p>这个&quot;不同而变化很大&quot;够吐槽了,直接翻译成&quot;随着…不同而不同&quot;,我想才像一个正常的中国人的思维,没必要非要代入副词.</p><p><strong>exponential or hyperexponential</strong>能翻译成&quot;指数或超指数&quot;也是无敌了好吧,翻译成&quot;指数级或者超过指数级别的增长速度&quot;我想才对,如果原文很难翻译,你倒是加一点副词啊.</p><p><strong>short CPU bursts</strong>翻译成&quot;短CPU执行&quot;你自己不会笑吗?<br />我不得不怀疑译者真的系统学过英语不,直接翻译成&quot;短区间&quot;就可以了,在有上下文的环境,读者很容易理解这指的是cpu执行区间.</p><h3 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h3><p>翻译英文的时候你就应该用中国人的思维来看原文,用中国人的想法去理解原文,在保持大致意思的情况下尽可能的还原中文的语法结构才是最重要的.</p><h2 id="ai取代程序员2641"><a class="markdownIt-Anchor" href="#ai取代程序员2641"></a> AI取代程序员(26/4/1)</h2><p>尽管很多人(包括程序员)都在说工作会被AI取代,但鲜有人真正懂得LLM的基本原理,只是看着AI输出一大段代码或者信息后就开始附和媒体的喧嚷,也给了大厂借着AI的名义大幅度裁员的机会.</p><p>我的意见是: What the hell?</p><p>到底是哪来的自信让你能够轻易的对一个自己一无所知的领域下判断的?如果你自己连LLM的原理都不清楚,那么你又怎么能知道它具有成长为包办一切的神器的潜力?是谁告诉你的?最会见风使舵的媒体吗?</p><p>至少,我是没看到有真正在这一领域精通的大牛站出来信誓旦旦的说: 我保证它可以解决所有的代码难题!</p><p>我想,在大学实验室里看到的那些大模型炼丹师,未必与闭源大模型的开发者有多大的区别.如果生物领域连大脑的运作机制都没搞明白,那又怎么可能通过普通的数学运算就精确的模拟人脑的认知呢?</p><p>LLM目前就只是一个概率模型而已,直到泡沫破裂了,才会有优秀的人才去探索更为实际的研究方向,才会有真正的进步和突破.</p><h2 id="某code源码泄露2641"><a class="markdownIt-Anchor" href="#某code源码泄露2641"></a> 某code源码泄露(26/4/1)</h2><p><img src="/images/2026-03-31/PixPin_2026-04-01_21-13-31.webp" alt="alt text" /><br />谁看到这个图能不笑🤣,可惜不是在今天泄露的,不然更有乐子了.</p><ul><li>不许你们再说小龙虾的star是沽名钓誉了🤢</li></ul><p><img src="/images/2026-03-31/PixPin_2026-04-01_21-16-56.webp" alt="alt text" /></p><ul><li>第一次见fork数量比肩star数量的,也是唯一一个fork数量逼近百万的仓库</li></ul><h2 id="文档学习法2643"><a class="markdownIt-Anchor" href="#文档学习法2643"></a> 文档学习法(26/4/3)</h2><p>最好的学习方法就是去阅读一手的文档,而不是去看二手甚至三手的博客教程.</p><p>毕竟官方文档的撰写者一般都是该领域的大牛甚至是作者本人,他对这个领域的了解程度会远远超过我们这些初学者.</p><p>而二手的博客教程一般都是作者在经过了自己的处理之后再片面的告诉你,很难做到十全十美的程度.</p><p>官方文档写的好不好,有没有及时更新或者维护,更是判断这个技术的生态是否足够好的硬性指标之一.</p><h2 id="数据库小测48"><a class="markdownIt-Anchor" href="#数据库小测48"></a> 数据库小测(4/8)</h2><ul><li>(4/7): 明天就要小测了,但还是没有一点底…不过心态还是要放平,我有预感这次小测会带给我一点启发或者冲击.</li></ul><p>这次小测确实有点过于逆天了,我以为不会考的procedure和trigger各考了一个11分的大题,关系代数也考了一个10分的大题,而这三个部分我都基本上没怎么学…<br />看来下次小测和期末要认真一点了,不然真要挂科重修了.</p><h2 id="以人文的方式去学习技术414"><a class="markdownIt-Anchor" href="#以人文的方式去学习技术414"></a> 以人文的方式去学习技术(4/14)</h2><p>现在,我在学习一门新技术之前,总是倾向于先去了解这门技术的演变历史,创作的背景和社区的现状,只有这样我才可以判断这门技术值不值得学,好不好学,需不需要学.</p><p>因此,我不再是像以往那样看到一个新名词,直奔AI了解大概意思,看一点二手博客就企图直接上手了</p><p>相反,我会通过wiki和官方文档去深入了解这门技术的历史,接着阅读官方的教程说明和社区的最佳实践(Github仓库/教程案例),从而逐渐掌握这门技术的基本操作,最后再去实战和根据AI边问边学.</p><p>这恰好对应了人文学科的学习方法:先是了解一门学科的历史,再接着了解这门学科的主要代表人物,然后理解该学科的主要研究内容,最后学习该学科的现代演进和实际案例.</p><p>我想,这种学习方法才是最适合技术人员的学习方法.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;每日阅读&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#每日阅读&quot;&gt;&lt;/a&gt; 每日阅读&lt;/h1&gt;
&lt;h2 id=&quot;2025-11-15&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#2025-11-15&quot;&gt;&lt;</summary>
      
    
    
    
    <category term="动态更新" scheme="https://revival-of-hope.github.io/categories/%E5%8A%A8%E6%80%81%E6%9B%B4%E6%96%B0/"/>
    
    
  </entry>
  
  <entry>
    <title>娱乐杂谈</title>
    <link href="https://revival-of-hope.github.io/2026/04/02/dynamic-2026-04-02-%E5%A8%B1%E4%B9%90%E6%9D%82%E8%B0%88/"/>
    <id>https://revival-of-hope.github.io/2026/04/02/dynamic-2026-04-02-%E5%A8%B1%E4%B9%90%E6%9D%82%E8%B0%88/</id>
    <published>2026-04-02T00:00:00.000Z</published>
    <updated>2026-04-12T06:05:01.146Z</updated>
    
    <content type="html"><![CDATA[<p>我之所以把自己玩过的游戏,听过的歌,看过的电影都等信息一一记录在这里,那是因为我的记忆力真的很差,过不了一年半载就记不大清楚具体内容了;至于为什么要写成博客,自然是想分享出来让大家也试试.</p><h1>game</h1><h2 id="轻型游戏">轻型游戏</h2><h3 id="无光之海-25-9-5">无光之海(25)-9.5</h3><p>基本没有任何引导,通过文字和地图设计来渲染一种恐怖的氛围,非常优秀的一款2d航海游戏</p><h3 id="pyrene-25-7-10-0">Pyrene(25/7)-10.0</h3><p>非常容易上头的卡牌地牢类游戏,玩法非常多样,甚至还有一些剧情设计上的小巧思</p><h3 id="将军对决-26-3-9-5">将军对决(26/3)-9.5</h3><p>玩法非常新颖的战斗游戏,也很容易上头</p><h3 id="极简塔防-25-9-5">极简塔防(25)-9.5</h3><p>独一档的塔防游戏,难度很高,需要操作在线.</p><h3 id="realm-of-ink-25-8-5">Realm of Ink(25)-8.5</h3><p>Hades的模仿作,机制简单不少,难度也要低一点,玩起来也挺爽的.</p><h3 id="the-king-is-watching-25-12-9-5">The King is Watching(25/12)-9.5</h3><p>难度很高,需要操作在线,随机性高,需要规划好养成路径</p><ul><li>谁玩破防了我不说</li></ul><h3 id="杀戮尖塔-25-10-0-杀戮尖塔2-26-10-0">杀戮尖塔(25)-10.0 &amp;&amp; 杀戮尖塔2(26)-10.0</h3><p>一己之力撑起了卡牌类游戏的一片天</p><h3 id="monster-train1-2-25-9-5">Monster Train1&amp;&amp;2(25)-9.5</h3><p>已经很好了,可惜不是杀戮尖塔</p><h3 id="渔帆暗涌-10-0">渔帆暗涌-10.0</h3><p>克苏鲁风格的2.5d航海游戏,相信所有玩过的人都会被惊艳到</p><h3 id="神之天平-10-0">神之天平-10.0</h3><p>不愧是年度游戏,后劲很足,作者能够十几年如一日的独立开发一款游戏,这本身就很燃了</p><h2 id="大型游戏">大型游戏</h2><h3 id="深海迷航-25-10-0-深海迷航-零度之下-25-9-5">深海迷航(25)-10.0 &amp;&amp; 深海迷航: 零度之下(25)-9.5</h3><p>最优秀的深海类游戏,没有之一.</p><h3 id="hades1-25-8-0-hades2-25-9-5">Hades1(25)-8.0 &amp;&amp;Hades2(25)-9.5</h3><p>不得不说二代是对一代的整体升华,无论是从关卡设计,战斗系统,剧情安排上,都做到了几乎是改头换面的效果,让人能够沉浸的不断战斗下去.</p><h3 id="闪之轨迹1st重制版-25-10-9-5">闪之轨迹1st重制版(25/10)-9.5</h3><blockquote><p>不得不说重制版的完成度很高,主要的扣分点就是那些水分满满的剧情前置小任务.</p></blockquote><ul><li>之前本来打算入坑轨迹系列的,但是现在打算一部部重制版玩下去算了</li></ul><h3 id="fate-samurai-remnant-25-7-9-5">fate/samurai remnant(25/7)-9.5</h3><blockquote><p>是我正常玩下来的第一部3d的jrpg,这一部的完成度很高,探索上比较自由,支线任务也很多,正常难度的话也没什么卡关的地方,剧情整体也很王道很热血,尽管部分地方很无聊,但基本满足了我对jrpg的幻想,拉高了我对jrpg的整体印象</p></blockquote><h3 id="八方旅人1-8-0-八方旅人2-9-0">八方旅人1-8.0 &amp;&amp; 八方旅人2-9.0</h3><p>独特的2.5d非常不错,战斗系统有一定的挑战性,二代做的更加精致,打法也更多,世界观也很好.<br>唯一的缺点是: 脚本太烂了!坏人坏到了没脑子的地步,好人犟到了弱智的程度,总是有些莫名其妙的转场.<br>二代的角色开场都写的不错,我以为会有一个华丽的转变,可惜每个角色的后续剧情都越写越烂.</p><h1>影视</h1><h2 id="是-大臣-是-首相-24-10-0">是,大臣&amp;是,首相(24)-10.0</h2><p>极度优秀的政治讽刺喜剧,遗憾的是再也没有能够比肩的优秀作品了…</p><h2 id="宋飞正传-25-10-0">宋飞正传(25)-10.0</h2><p>一般的喜剧到后期灵感有些枯竭时,便想方设法加入一些主角们的情感戏来充实一下贫乏的剧集内容,无论是生活大爆炸,老友记还是老爸老妈恋爱史,到了后期总显得没有一开始那么好笑了,原因便是真正的包袱少了,以主人公本身特点来制作的包袱多了,如果前面几季都没看过,会觉得有点莫名其妙.</p><p>然而宋飞正传不是这样,尽管后期有那么三四集有部分的包袱不够响,也有部分小片段是基于前几季的剧情来扩展的,但基本上来说从头到尾都是好笑的,包袱源源不断,每一集主人公们都会遇到各种各样有趣的事情,而这些事情彼此之间互不关联,从来没有一条分明的主线,主角们的关系整整九季都没变过,讲述的仅仅是关于他们普通而又特别的日常生活,无论什么时候,随便切入哪一集,都充斥着滑稽的片段,这对观众来说就足够了,而这恰恰是其他喜剧所做不到的.</p><p>宋飞正传也是除了&lt;&lt;是,大臣&gt;&gt;以外我唯一计划再重温一遍的优秀喜剧.人生如戏,娱乐而已,平淡就好.<br>![alt text](/images/archives/2025/2025-11-26 影评-宋飞正传-20世纪最伟大的电视剧/image.webp&gt;)</p><h2 id="费城永远阳光灿烂-26-2-10-0">费城永远阳光灿烂(26/2)-10.0</h2><p>拍到现在拍了十七季,甚至还要继续拍,可想而知这个系列有多么经典.<br>尽管有几集不够好笑,但大部分的剧情也都是非常好笑的,剧本的脑洞也非常之大,同样也是没有一条分明的主线,主角团永远是<s>无所事事</s>坏事做尽,为数不多可以比肩宋飞正传的无厘头喜剧</p><h1>旅游</h1><p>记录一下我去过的旅游景点,由于很少拍照,就不配图了.</p><h3 id="上海">上海</h3><h4 id="海洋水族馆-9-5">海洋水族馆-9.5</h4><p>除了价格稍微有点高外无可挑剔,海底隧道真是绝了,各种动物的喂食表演也很不错.</p><h4 id="海昌水族馆-9-0">海昌水族馆-9.0</h4><p>动物很可爱,品种也很多,最值得一提的是虎鲸表演和海狮表演,甚至还有北极熊和北极狼.<br>非常可惜的是,海昌的管理太差了:</p><ol><li>景区规划不合理,馆区之间距离有点远,而且有很多没必要的娱乐设施,真要坐过山车的话我会去迪士尼和欢乐谷的…</li><li>海底世界馆死气沉沉的,尽管亮眼的水生动物确实没有吧,但整个气氛就很阴森</li><li>北极馆的动物都趴着不动弹,看着就没有生机,真不知道是因为动物的天性,还是因为活动空间狭小无法动弹导致的</li><li>景区里面的饭店和摊点价格有点逆天,好像是不希望游客再来第二次了</li></ol><p>省流版: 入园后看完虎鲸表演,海狮表演,其他表演就别看了,逛完企鹅馆,北极馆,水母馆,白鲸馆后就赶紧撤.</p><h4 id="外滩-南京东路-6-0">外滩+南京东路-6.0</h4><p>建议半夜的时候骑车逛一逛就可以了,其他时段不要去.</p><h4 id="豫园-城隍庙-6-0">豫园/城隍庙-6.0</h4><p>不建议去,搞不懂为什么这么火</p><h4 id="佘山-欢乐谷-9-0">佘山/欢乐谷-9.0</h4><p>佘山很小(上海最高峰…),基本两个小时就可以上下了,重点是旁边有欢乐谷!</p><ul><li>上海旅游节的时候去欢乐谷是最值的,门票半价,但人也会更多.</li></ul><p>绕一圈下来只有过山车,漂流,4d影院比较有意思,其他的真没什么.</p><ul><li>古木游龙是真的吓人,热度也最高,但我不敢坐也不想排120min的队…</li></ul><h4 id="上海中心大厦-8-0">上海中心大厦-8.0</h4><p>电梯很爽,顶层的风光很好看,门票也是真的贵.</p><h1>gal</h1><h2 id="age">age</h2><h3 id="muv-luv三部曲-10-0">Muv-Luv三部曲-10.0</h3><p>刚看完剧情的那一两天很难缓过来</p><h2 id="alicesoft">Alicesoft</h2><h3 id="rance-series-23-24-10-0">rance series(23/24)-10.0</h3><blockquote><p>兰斯系列我的游玩顺序相当乱,本来一部部按顺序推下来的话第十部就能有更好的体验了<br>我的大致顺序是第一部玩了一半-&gt;第二部玩了一半-&gt;直奔第九部-&gt;再玩第十部-&gt;发现确实是神作得玩前作-&gt;第八部-&gt;第七部-&gt;第三部-&gt;第六部-&gt;云第四,五部-&gt;重玩第一部</p></blockquote><blockquote><p>质量最高的显然是第十部,然后是第七部&gt;第六部&gt;第九部&gt;第八部&gt;第三部重制版…  可以看出来rance系列探索了各种各样的游戏玩法,而且与剧情结合的相当成功,有战棋,卡牌,地域压制,3d迷宫,2d迷宫,与其说是galgame,不如说是带cg的jrpg.</p></blockquote><h2 id="aquaplus-leaf">AQUAPLUS/Leaf</h2><h3 id="传颂之物三部曲-25-9-0">传颂之物三部曲(25)-9.0</h3><p><strong>剧情</strong></p><ol><li>第一部的男主其实并没有必要在之后出现…换个世界观其实也挺好,硬要贴边就显得狗尾续貂了</li><li>后两部为同一男主,尽管有一些剧情上的衔接瑕疵,但主角团还算是有血有肉的,每个人都有自己的个性,也都很可爱,渐渐揭发真相的过程算也是为数不多的剧情亮点了.</li><li>世界观设定的很好,可惜的就是不能很好的通过文字表达出来,但玩游戏的话看的就是氛围,脚本差一点的话就一路ctrl吧</li></ol><p><strong>演出</strong></p><ol><li>CG都很帅,愈发衬托出脚本的平淡和不痛不痒的力度</li><li>战斗过程枯燥,难度很低</li></ol><h3 id="white-album-24-9-5">White Album(24)-9.5</h3><ul><li>不得不品的甩巴掌环节<br>男主过于憋屈,玩的时候真看不下去,只过了两条女主线,其他的支线都没过,不得不说,如果是女生主动来追求你,她又能保持自己的独立人格,不会卑微的当个舔狗,那很难不心动.</li></ul><h3 id="white-album-2-24-9-5">White Album 2(24)-9.5</h3><ul><li>明明是我先来的啊…</li></ul><p>三角恋写到极致之后看的真是有滋有味,尤其在男主是个狠心的&quot;老好人&quot;的情况下.说实话,这种第一人称的叙述很容易把自己代入进去,但是你跳出来细细品味就会发现男主就是个出生而已,既要又要,以自己的所谓悲情去裹挟两位好女孩(这次是真的好女孩…),扣0.5分也就在这里,因为男主太不正常了(女主也未必正常…).</p><h2 id="augustsoft">Augustsoft</h2><h3 id="大图书馆的牧羊人-22-8-5">大图书馆的牧羊人(22)-8.5</h3><blockquote><p>这个我也算是推了两遍,第一遍并没有推完,再捡起来已经忘了大致剧情了(虽然本来也没有什么剧情)</p></blockquote><p>尽管是废萌,但有些对话就是莫名的会戳中我笑点,而且人设都设定的非常好,也不单纯是那种围着男主转的剧情设定,可以说很少有废萌能够与之比肩了</p><h3 id="秽翼的尤斯蒂娅-22-9-5">秽翼的尤斯蒂娅(22)-9.5</h3><blockquote><p>这个我应该推了两遍,第一遍还是在高中的时候,当时偶然在一个gal资源网站看到别人分享就去玩了,完全不知道它的名气,这样反而让我更好的沉浸进剧情里面,成功见证了一个底层人士自底端向顶峰的攻略故事.</p></blockquote><blockquote><p>前两章让人只能体会到压抑,而第三章进入圣堂之后画风一转,仿佛误闯天家,可是光鲜亮丽的背后却藏有无尽的污秽,比前两章反而更为压抑,进入第四章后阴暗基本被揭露出来,却又在第五章来了一个转折,主人公的人设也就是这个时候开始崩塌的,前期的勇猛果决仿佛都消失了,唯唯诺诺地,不敢做出任何决定,仿佛一政变成功就当上圣母了,最后直到即将失去心爱的人时方才重新做回男人.尽管可以说有亲情因素和自卑因素在里面导致男主下不了手,但确实很憋屈.</p></blockquote><blockquote><p>结尾我想剧本家也不知道该怎么写了,只好写一个圣母结局,但我自己也不知道应该怎么写才能圆回来,毕竟不能直接让所有人都死光吧,只能说前面写的太好反而让第五章看起来不太对头,没能做到一个完美的收尾</p></blockquote><h2 id="blacksoul">blacksoul</h2><h3 id="blacksoul1-26-1-20-9-0">blacksoul1(26/1/20)-9.0</h3><p><a href="https://www.bilibili.com/opus/1144002837561212944">攻略1</a><br><a href="https://www.bilibili.com/opus/1107645316603052040">攻略2</a><br><a href="http://118.195.226.55:5254/StrategyDocs/BLACKSOULS">攻略3</a><br>这三个攻略交叉着看基本就能走到真结局了</p><blockquote><p>剧情比较简单,流程也比较短,如果不算上刷魂的话我觉得5个小时就能体验完所有结局,但是不看攻略自己走的话估计要花上50个小时,有太多隐藏剧情了,很多事件都是彼此关联的,一旦你漏掉了一部分提前推进了,就不可能进入真结局</p></blockquote><p><strong>简要分析</strong></p><blockquote><p>A/B/个人结局:10位女主全都没有好结局,而利耶芙结局基本点明了部分真相</p></blockquote><blockquote><p>C结局(真结局):收集所有童话书需要把八个女主都刀了,最终把玛丽苏刀了之后所有成员再次团聚,但这也只是玛丽苏计划里的一环罢了</p></blockquote><blockquote><p>D结局:显然像是个DLC样的结局,黑之裁判所的所长原来是玛丽苏的母亲,而玛丽苏把她干掉之后打算和格林继续过二人世界,后面突然出现了爱丽丝,游戏就戛然而止了</p></blockquote><h3 id="blacksoul2-26-1-28-9-5">blacksoul2(26/1/28)-9.5</h3><blockquote><p>第一次玩的时候没几个打得过的boss,到处乱转也找不到什么游戏提示,跟人物对话也全是乱码.一开始以为游戏设定是这样的,直到查攻略才发现是我黑魂吃太多,sen值太低了.</p></blockquote><blockquote><p>受不了了,这真的只能靠自己一点一点摸索出来,而且新人非常容易跑到孢子之地见爱丽丝导致错过小红帽,都不敢想象没有攻略正常人怎么会知道有里世界这么一个东西,一代好歹有路牌和利耶芙引导,刚开始的骑士也贴心的告诉你怎么去魔女之家,二代就剩一只喵喵怪叫的猫和地上的红色文字了,地图也变得更加错综复杂,很容易误入后期地图然后遇到小怪立马暴毙</p></blockquote><blockquote><p>(大致)看了看攻略,成功在三周目进入了里世界并进入DLC3,由于混沌迷宫的爆率感人,因此黑之斩击一些强力的技能并没有刷出来,即便属性基本都点满了,lv也999了,还是打不动小怪,当用ce买魂改完属性后很久才发现可以打灰烬刷魂.</p></blockquote><ul><li>后来又发现是被暴杀天使偷偷改了难易度…</li></ul><blockquote><p>总之,bs2体量确实大,地图确实绕,隐藏点确实多,体验与一般的rpg游戏截然不同</p></blockquote><ul><li>ce修改数值:rpgmaker中对应地址ce显示数据=原数据×2+1</li><li><a href="http://118.195.226.55:5254/StrategyDocs">祖传攻略</a></li></ul><h2 id="clover-game-26-1-7-5">CLOVER GAME(26/1)-7.5</h2><p>最近的作品是&lt;&lt;すれ違う兄妹の壊れる倫理観&gt;&gt;,其实这个会社的几部作品都中规中矩吧,当小品作看看也成,反正一路ctrl下来也没发现有什么特别亮眼的表现</p><h2 id="cotton-soft">cotton soft</h2><p>这家会社我非常喜欢,可惜在15年之后就消息全无,原班人马也不知道去哪里了</p><h3 id="琥珀结晶-25-12-9-0">琥珀结晶(25/12)-9.0</h3><blockquote><p>剧情写得非常好,通过章节来一步步揭露真相,一直吊着我的胃口,想要知道主人公以前到底经历了什么,同时,各女主的支线并非是单独的部分,而是作为最终主线的剧情构成部分,这点设计非常的巧妙,至少我玩了这么多gal里很少有能做到这点的</p></blockquote><ul><li>最重要的是有两对苦命鸳鸯</li></ul><h3 id="双子座的paradox-26-1-5-9-0">双子座的paradox(26/1/5)-9.0</h3><blockquote><p>同样是剧情神作,他不是一下子抛出一大堆谜团,而是逐步地通过回忆和揭露来慢慢展开剧情,前文里的许多伏笔到了后期慢慢都被发掘出来,不得不说这个剧本家有点牛批,尽管h部分还是与剧情上有点割裂,没能做到推动剧情的作用,单纯是为了h而h,<em>不过这样就对了</em></p></blockquote><blockquote><p>同时剧情时间线的来回跳跃和要求先推进另一部分的剧情锁也是我从未有过的体验,而剧本家竟然能够通过形形色色的剧情提示做到不产生分离感,真的有点牛皮</p></blockquote><blockquote><p>最神的我反而觉得是ED,尽管整部游戏的各个结局都是同一个ED,但这个ED是真nm的好听,不知道为什么没什么人听就是了,可能是作品太冷门了</p></blockquote><h3 id="世界末日与生日快乐-2026-1-13-9-0">世界末日与生日快乐(2026/1/13)-9.0</h3><blockquote><p>推荐临近生日的时候去玩,最好是生日当天玩epilogue(哭)<br>最神的依然是音乐,graduation这首伴奏贯穿了整个剧本,确实好听,其他几首歌也不错</p></blockquote><p>重复的末日,重复的轮回,不变的感情,令人怅惘.<br>依然是一环套一环的剧情,越来越喜欢海富一了,可惜现在没有他的消息了</p><h2 id="escude">Escude</h2><h3 id="废村少女-7-5">废村少女-7.5</h3><p>系列作画风都很顶级,尽管没有什么剧情,但大部分的cg表现力都很不错,如果能整个动态cg就更好了.</p><h2 id="tokyotoon">TOKYOTOON</h2><h3 id="丸子与银河龙-25-9-0">丸子与银河龙(25)-9.0</h3><p>世界观和演出都很顶级,唯一遗憾的是脚本功力不够,总是差点火候,但游戏整体还是很好玩的,融合了多种演出方式,还有很多neta环节.</p><h2 id="hulotte-7-0">Hulotte-7.0</h2><blockquote><p>喜欢搞怪的废萌,每部作品的设定都很不错,人设也还行,就是剧情写得太无聊,跟yuzusoft可以坐一桌</p></blockquote><h2 id="i-m-moralist">I’m moralist</h2><h3 id="surah-25-7-0">Surah(25)-7.0</h3><p>很短,但是很涩</p><h2 id="key">Key</h2><h3 id="summer-pockets-rb-25-8-0">Summer Pockets&amp;RB(25)-8.0</h3><p>部分剧情比较有趣,但大部分都是很无聊的培养感情过程,各女主的人设也都没有比较戳我的地方,唯一的亮点是乒乓球大作战().</p><h2 id="latte">Latte</h2><h3 id="妹选拔总选举-26-1-20-6-0">妹选拔总选举(26/1/20)-6.0</h3><blockquote><p>这标题一开始是让我浮想联翩的,可是一进入画面立绘就暴雷了,尽管立绘甚至还会动,但就是长的太偏差了,很难相信是06年的作品,同期的作品立绘不至于这么离谱,只好一路ctrl了,很好奇汉化组是怎么坚持下来的</p></blockquote><blockquote><p>速览下来,显然365个妹妹只是个幌子,基本只涉及了四个妹妹,只是一部单纯的倒贴废萌作品,说真的这么好的设定就这么浪费也太可惜了,不要求365个,只要10个我觉得也不错了,不走炎孕路线就行</p></blockquote><h2 id="nitro">Nitro+</h2><h3 id="罪恶王冠-失落的圣诞节-25-8-0">罪恶王冠-失落的圣诞节(25)-8.0</h3><blockquote><p>之前没接触过罪恶王冠的动画,玩完之后才发现是沿用的动画的世界观重新写了一个短篇,因此玩的时候觉得有些没头没尾的,但是拔剑确实帅,还有神曲作为配乐</p></blockquote><h3 id="chaos-head-24-9-0">chaos:head(24)-9.0</h3><blockquote><p>这个是云过去的,因为游戏界面太古早了,不过脚本写的确实不错,废人男主前期一直在各种逃避,不敢面对现实,到了真结局时终于觉醒,拯救世界.</p></blockquote><h3 id="chaos-child-24-9-5">chaos:child(24)-9.5</h3><blockquote><p>一脉相承的故事体系,但是ui现代多了,立绘也耐看,剧情上也更加波澜起伏,一旦开始推就停不下来</p></blockquote><h3 id="命运石之门-24-10-0">命运石之门(24)-10.0</h3><blockquote><p>失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了</p></blockquote><p>剧情,人设,UI全都做到顶级的老资历旮旯</p><h2 id="sister-position">Sister Position</h2><blockquote><p>非常好的专写妹系的会社,本来叫Twinkle Position,不知为何改名,但原班人马还在</p></blockquote><h3 id="妹抱1-妹抱2-22-8-5">妹抱1/妹抱2(22)-8.5</h3><p>尽管没什么剧情,但确实是独一档的妹系后宫废萌了</p><h3 id="童真兄妹-21-8-5">童真兄妹(21)-8.5</h3><blockquote><p>这貌似是我推的第一部gal,成功带我进入了旮旯game的世界,不得不说写得确实又日常又够色,入门作是这个而不是重生萝莉岛真是太好了</p></blockquote><h3 id="病弱妹妹-25-8-5">病弱妹妹(25)-8.5</h3><blockquote><p>改名为&lt;&lt;病弱哥哥&gt;&gt;可能更合适,这妹妹也太猛了</p></blockquote><h2 id="自宅すたじお-7-5">自宅すたじお-7.5</h2><blockquote><p>基本都是倒贴类型的废萌,没有一点剧情,但要当拔作玩的话剧情又显得稍微多了一点,不过每部作品基本都有几个<strong>动态CG</strong>,那还是原谅你吧.</p></blockquote><h2 id="もんむす-くえすと-25-9-5">もんむす・くえすと！(25)-9.5</h2><blockquote><p>优秀的王道剧情,让人掉san的立绘,不过适应了画风之后看见怎样的魔物娘都能波澜不惊了.</p></blockquote><blockquote><p>尽管战斗系统和引导系统都做的差点意思,但基本不用卡关,一路畅通无阻的感觉也不错,玩完之后还有一种怅然若失的感觉.</p></blockquote><h2 id="もんむす-くえすと-rpg-26-3-10-0">もんむす・くえすと！RPG(26/3)-10.0</h2><h3 id="设计方面">设计方面</h3><p>体量大的吓人,几十张(或许快一百张)地图看的出来都是精心设计的,几百种武器装备道具到了后期翻都翻不过来,几百种技能,还有各种各样的连锁技,角色的专属技能,配上角色本身的特性,简直是眼花缭乱,算都算不过来.</p><p>美中不足的是,由于角色太多太多,每到一个新区域就会获得新的强力角色,数值上基本能够碾压先前培养好的角色,同时敌人的数值也会有惊人的倍数提升,导致没有角色可以从头用到尾,主角团到了后期也显得弱鸡无比,打一个路边小怪也要耗上很久.</p><p>然而这么大体量的游戏还有多周目,可以回到重大选择之前,继承所有的道具和伙伴(尽管有剧情锁强制你去做一些支线任务,故不能一键速通),保证可以把喜欢的角色刷到离谱的强度,而更别提游戏内部自带了作弊道具:平衡之钥,可以调整经验倍率和敌人强度,保证玩家不会因为后期的数值爆炸而破防</p><h3 id="剧情方面">剧情方面</h3><p>基本是从前一部作品扩展开来的,但剧情的深度和广度都不在一个量级上,虽然整体来看剧情还是很简单的,就是拯救快要灭亡的世界,但是奋斗在灭亡边缘的每一个人物都刻画的有血有肉,人设这一块确实设计的很好了.</p><p>我在后期打不过就开作弊道具,所以基本没怎么卡关,但就是这样也玩了差不多100个小时,去贴吧一看正常流程都是玩了两三百个小时的… 可想而知整个游戏的内涵有多么丰富,我想这真的是前无古人后无来者的黄油了,所以我不谈及具体剧情,避免有任何形式的剧透.</p><h2 id="milky-factory-2026-1-8-0">milky factory(2026/1)-8.0</h2><p>看着体量大,实际上每一部作品的实用性都不太够,尽管有动态cg,但夸张的体型和过于直白的推进还是让我觉得少了点什么.<br>前期的画风就正常一点,但还是不耐看.</p><h2 id="samoyed-smile">SAMOYED SMILE</h2><h3 id="夜晚-徘徊在我们的辅导教室-7-5">夜晚,徘徊在我们的辅导教室-7.5</h3><blockquote><p>貌似是我玩的第二部gal,剧本写的还行,人设也安排的不错,没有什么太崩的地方,重点是有<strong>动态CG</strong>!</p></blockquote><h2 id="softhouse-seal-grandee">Softhouse-seal GRANDEE</h2><h3 id="爱迪生牛顿大发明-23-7-0">爱迪生牛顿大发明(23)-7.0</h3><p>剧情比较逆天,玩法也多,比yuzusoft强就行</p><h2 id="west-vision">West Vision</h2><h3 id="精爆双姬-23-9-0">精爆双姬(23)-9.0</h3><ul><li>终于看到正常的兄妹关系和恋人关系了…</li></ul><p>尽管画面很古老,但是你就去玩吧,保证大开眼界.</p><h2 id="yuzusoft-6-0">yuzusoft-6.0</h2><blockquote><p>画风显然是越来越好的,脚本则是一成不变的糟糕,要不把固定女配的cg砍掉再把经费投入剧情和演出算了.</p></blockquote><h2 id="散">散</h2><h3 id="class-of-09三部曲-9-5">Class of 09三部曲-9.5</h3><p>使用renpy开发,尽管界面很简陋,但是架不住剧情和人设都太有趣了,想练英语可以看这个,一大堆生词可以直接打击你对英语的自信心.</p><h3 id="终末之际-25-9-0">终末之际(25)-9.0</h3><blockquote><p>极其优秀的超短篇末日题材小说,没有立绘和配音,单纯靠场景,音乐和文字进行渲染就能够让读者领略到一股浓浓的悲哀与不舍</p></blockquote><h3 id="nao-in-heat-24-8-0">Nao in Heat(24)-8.0</h3><p>很小很短,但是很涩</p><h2 id="gal脚本家">gal脚本家</h2><ul><li><a href="https://tieba.baidu.com/p/3509700255">大部分文字来源</a></li><li><a href="https://tieba.baidu.com/p/2754507004?see_lz=1">原帖</a></li></ul><blockquote><p>一个非常细致的脚本家介绍,事实上原帖作了一个排名,越往后排名越高,但是我把排名过滤掉了,毕竟能入榜的脚本家都很优秀了;另一个改动就是原帖对每部作品都配了图,而我这里都删掉了,毕竟好作品自己会说话</p></blockquote><h3 id="元长柾木"><strong>元长柾木</strong></h3><blockquote><p>元长很擅长对于具体情境氛围的刻画，对于世界观和人物心理活动的描写都比较精细，尤其擅长营造心理交杂的世界观。当然缺点也很明显，过于偏执的连续描写很多时候会发展成毒电波属性的自说自话，给人一种耍花活的感觉，当然对于电波对不上的人可能连感觉都很难有……</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《猫抚ディストーション》</strong><br>元长全力全开的代表作品，话题作，只要你能合上电波就是当之无愧的杰作，当然大多数人是合不上电波的……</li><li><strong>《sense off ～a sacred story in the wind～》</strong><br>元长早期的作品，世界观上亲切了许多，老实说其实我觉得这个才是元长最好的作品。</li></ul></li></ul><blockquote><p>补充介绍一下，折戸伸治参与了<strong>senseoff</strong>的音乐制作，元长是<strong>神戸大学工学部</strong>的毕业生，小说家和作词家，和<strong>hain</strong>一样是位特殊剧本爱好者。<strong>13cm</strong>活跃过几部作品，推荐一下<strong>娇烙之馆</strong>。（资源hiyo菊苣在bangumi有传）</p></blockquote><hr><h3 id="御影"><strong>御影</strong></h3><blockquote><p>少数派的两大栋梁之一，当然实力公认比不上好机油<strong>镜游</strong>。<br>在死生观上有着卓越的笔力，名作<strong>EF</strong>中在这方面这位就展示了不俗的实力，不过还是比不上好机油<strong>镜游</strong>。<br>尽管语言有时会带着浓浓的中二气质，但同样也的确饱含深意，不过还是比不上好机油<strong>镜游</strong>。<br>由于总是同时兼任监督一职，所以未免在本社文字创作量有所下降，并且在担任某最炫<strong>GAL</strong>游戏<strong>OP</strong>的时候险些把少数派玩破产，不过还是比不上好机油<strong>镜游</strong>。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《ef - the first tale.》</strong>,<strong>《ef - the latter tale》</strong><br>前后两部<strong>EF</strong>，少数派的巅峰之作，经典地位不用咱多说了吧……虽然御影在这里的表现还是比不上好机油<strong>镜游</strong>。</li><li><strong>《水夏～SUIKA～》</strong><br>当然，御影同学还是有逃离机油阴影的幸福时光的，而且在名作<strong>水夏</strong>中发挥的也确实不错。</li></ul></li></ul><hr><h3 id="下仓バイオ"><strong>下仓バイオ</strong></h3><blockquote><p><strong>N+<strong>的后起之秀，老实说毛病的确不少，不过也确实有着自己独到的地方。</strong>《スマガ》<strong>中显示的格外明显，与</strong>N+<strong>脚本群传统的战斗系燃点不太一样，下仓在世界观以及世界观引导下的剧情中表现的更好，实话说在老虚不务正业的情况下还是比较受社里重视的，创作风格真心在</strong>钢屋****奈良原</strong>这些家伙里别树一帜。<br>不过毛病也不少，尤其是对玩家满怀恶意……= =</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《月光のカルネヴァーレ》</strong><br>算得上下仓的成名作了，表现的确实不错，不过缺点也很明显，某些地方玩得咱实在累无味……</li><li><strong>《スマガ》</strong><br>个人认为下仓至今为止最优秀的作品，当然他本来的作品就不多……自带<strong>LOOP</strong>系统真是叫人又代入又咪疼……</li></ul></li></ul><hr><h3 id="菅宗光"><strong>菅宗光</strong></h3><blockquote><p>想当年<strong>LEAF</strong>东京部的一员虎将，另一位自然是<strong>三宅真介</strong>了，这两位都是<strong>LEAF</strong>自产的脚本家，唉现在多少孩子提起<strong>LEAF</strong>就是<strong>WA2</strong>就是<strong>丸户</strong>……<br>菅宗光非常善于妄想……啊不虚拟世界观的设计和剧情的构筑，以一人之力构建出<strong>传颂之物</strong>这样的幻想系，名作，其实力可见一斑。可惜，<strong>LEAF</strong>多年的吃老本行径让这位实力派脚本家始终没有下一步杰作面世……</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《うたわれるもの》</strong><br>个人认为<strong>LEAF</strong>的最经典作品，菅宗光呕心沥血的杰作，无论多久都难以磨灭。</li><li><strong>《ToHeart2 AnotherDays》</strong><br>回归日常的菅宗光表现明显下降了许多……可是作为他唯二的作品之一，还是放上来吧。</li></ul></li></ul><hr><h3 id="秋津环"><strong>秋津环</strong></h3><blockquote><p>虽说在大多时候总是隐藏在<strong>海富一</strong>、<strong>片冈</strong>等人后，但是环姐本人的实力也是毋庸置疑的，也许是因为性别原因（误）？环姐对于学园物表现的相当不错，而且对于女性的心理把握的相当不错。<br>环姐的文风更加软一些，非常的擅长渲染气氛，文字与画面的结合能力比较高，并且能够在简短的日常中轻易地展示人物性格所在，有着不错的心理描摹功底。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《ラムネ》</strong><br>主要搭档<strong>片冈</strong>，意外的产生了不错的反应，环姐负责的部分格外清新动人。</li><li><strong>《レコンキスタ》</strong><br>主要搭档<strong>海富一</strong>，表现无可挑剔，其实环姐果然是贤内助啊（泥垢）。</li></ul></li></ul><hr><h3 id="新岛夕"><strong>新岛夕</strong></h3><blockquote><p><strong>SAGA PLANETS</strong>的主力脚本，当然现在是前主力脚本了……<br>文字温暖人心，嗯暖人心。（误）<br>新岛夕的文字功底在出道初期就表现的不错，尤其擅长对二人关系的细致描写，在校园物上有着明显的掌控能力。并且自从暖人心的某作后，还被发掘出了埋包袱和暗地里坑人的天赋……<br>对于游戏节奏掌握的也很到位，不过在个别支线上还是比较突兀了些，但是还是能够妥帖的处理主线剧情的。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《はつゆきさくら》</strong><br>纯爱大作，充满了温情的冬季恋爱物语——你就当真的听。</li><li><strong>《ナツユメナギサ》</strong><br>初雪樱之前新岛夕最好的作品了，当然现在看起来也算是良作。</li></ul></li></ul><hr><h3 id="健速"><strong>健速</strong></h3><blockquote><p><strong>都合大先生</strong>，不过真要说起来健速虽说总是都合，但是稍微不太都合的作品的确都是杰作。<br>大先生严谨的工作态度还是要赞一个的，虽说的确有很多作品都合严重，但是必须承认基本也没有什么雷作，甚至可以称之为业界良心了。<br>不就是日常各种催眠，剧情各种奇葩展开么……至少还是人干的事不是？<br>当然大先生也有自己的超杰作**《そして明日的世界より――》**，只是大先生大多数的作品还是略都合了一些……其实就是这作也都合严重。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《そして明日的世界より――》</strong><br>大先生超神啦~！只要顶过前面的各种催眠，后面绝对震撼人心！</li><li><strong>《遥かに仰ぎ、丽しの》</strong><br>大先生严谨的工作态度取得的成就！本校超级好男人！尽管超级好男人已经要称霸宇宙了……至于<strong>丸谷秀人</strong>写的那淫行教师……</li></ul></li></ul><hr><h3 id="睦月たたら"><strong>睦月たたら</strong></h3><blockquote><p>行业杀手wwww<br>其实也是资格比较老的脚本家之一了，但是由于单独担当实在不多，而且骗子社始终不缺少令人眼前一亮的脚本家，所以似乎之前的名气在天朝并不是很响亮（当然也可能是笔者孤陋寡闻），不过自百合灵这一话题作诞生之后，睦月似乎一夜爆红……<br>睦月是典型的实力派脚本家，他的文字表现力相当强，别的不说，在书写回忆以及渲染感伤氛围上相当得心应手，并且十分长于扔闪光弹wwww各种叙述手法都比较擅长，而且也很能够表达出人物复杂的心境。<br>在骗子社的一堆不说人话的家伙里算是比较亲切的了……</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《屋上の百合霊さん》</strong><br>话题作，第三人称视角相当有新意，游戏的氛围也很棒wwwww。</li><li><strong>《ぼ～ん・ふりーくす！》</strong><br>睦月比较早的作品了，各种手段已经比较娴熟，不错的良作，可见睦月长期以来平稳的剧情表现实力。</li></ul></li></ul><hr><h3 id="铃鹿美弥"><strong>铃鹿美弥</strong></h3><blockquote><p><strong>杉菜水姬</strong>的真爱，<strong>IG社</strong>的台柱。<br>文风相当的具有个人特色，虽然有<strong>七凭</strong>那种渣作，但是除此之外的剧本发挥的都不错。而且非常长于诡异气氛的刻画和对于郁结心情的渲染，营造处的气氛相当具有吸引力。<br>在谜题的设计上略显不友好，不过还是能够给人带来比较强的诡计震撼力，文字和画面的契合程度很高。<br>不过在题材的选取和剧情的发展上流露出明显的局限，经常产生莫名其妙的既视感，剧情上始终难以突破固有的模式，并且为了达到诡计目的对玩家相当不友好。<br>可是也许这正是<strong>IG</strong>的风格wwww。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《壳ノ少女》</strong><br>美弥阿姨的缺点在这表现的还不太明显，相应的，剧情的节奏和文字的表达做的很不错。</li><li><strong>《虚ノ少女》</strong><br>相应的，震撼力更强也更不友好的这一作就暴露出了其局限，对玩家的恶意也更加满载了wwww。</li><li><strong>《和み匣》</strong><br>至于这一作呢……实话说咱个人觉得绝对算是良作，但是对于玩家的特性要求也比较高啦。</li></ul></li></ul><hr><h3 id="镜游"><strong>镜游</strong></h3><blockquote><p><strong>御影</strong>的好机油，资历比御影要浅很多，不过个人觉得实力要比御影强。<br>镜游的文字底力明显更深厚得多，并且对于剧情的处理也更加自然，对于各种题材都有一定的把握，适应性更强。<br>文字较为具有亲和力，能够做到吸引玩家的同时减少对于玩家的催眠程度，擅长在日常的描写中穿插情感的表达。<br>能够熟练地进行描写对象的切换，并且在描摹环境与人物的映衬上做得很不错。</p></blockquote><ul><li><strong>代表作</strong><ul><li><em><em>《eden</em>》</em>*<br><strong>EDEN</strong>，尽管由于某些原因恶评如潮但是剧本的优秀还是必须承认的。</li><li><strong>《ef - the latter tale》</strong><br>不多说，两位机油的表现孰优孰劣有目共睹。</li><li><strong>《はるのあしおと》</strong><br>少数派早期的第三作，镜游对各题材的把握能力有着不错的展现。</li></ul></li></ul><hr><h3 id="钢屋ジン"><strong>钢屋ジン</strong></h3><blockquote><p>图书馆三人众之一，<strong>钢屋JIN</strong>，咱一直认为老虚不务正业之后**N+**的领袖和代表。<br>钢屋的文字是典型的中二燃系的风格，并且十分擅长描写战斗。**N+**的剧本家描写战斗的能力在业界本就是响当当的，钢屋也是其中的佼佼者。借助立绘演出真心能够满足大部分战斗场面的要求。除此之外钢屋更是玩得一手好设定，在设定的辅助下对游戏剧情的把握相当不错。<br>整体作品感觉在主题表达，虽说这不代表他写不出内涵作，只是钢屋果然还是更加适合战个痛啊=A=</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《斩魔大圣デモンベイン》</strong><br>钢屋的成名作，一扫之前老虚的治愈色彩，配合相性大变得<strong>CG</strong>战个痛！</li><li><strong>《ギルティクラウン ロストクリスマス》</strong><br>钢屋最近难得的作品，虽说是短篇，但是看到钢屋自己写的东西还是可以说吉野你可以洗洗睡了……</li></ul></li></ul><hr><h3 id="味盐ロケッツ"><strong>味盐ロケッツ</strong></h3><blockquote><p><strong>味盐ロケッツ</strong>也算得上实力派脚本家的代表之一了，虽说感觉这家伙的写的东西还是老物比较好，不过这也与领域的不同有关吧，感觉他不是很适合日常故事的写作，相反的在犯罪和解谜领域实力强劲，他主要的杰作也诞生于这一领域。<br>文字底力不错，不过不太擅长描写日常故事，更加能够适应非日常的展开，对人物的心境刻画比较精致，而且在解谜领域中文字的逻辑感很强，剧情的进展也比较流畅。<br>感觉塑造气氛的能力稍差，不过对人物自身面对环境时的深入描写可以比较不错的弥补这一点。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《STEEL－スティール－》</strong><br><strong>味盐ロケッツ</strong>在自己得心应手的领域中的杰作，非日常一侧的剧情展开舒爽到爆。</li><li><strong>《つくとり》</strong><br>解谜作的代表，层层入扣的剧情发展和逻辑演进真心炒级赞！</li></ul></li></ul><hr><h3 id="麻枝准"><strong>麻枝准</strong></h3><blockquote><p>大魔王wwww<br>实力没人质疑，不过咱感觉也算不上多神，毕竟他的本行还是音乐。<br>文字功底一般，但是在剧情的设置上的确有着独到之处，大魔王的剧情水平是要远高于他的文字水平的，“先令你爱上一个人物，然后在折磨你”这样的模式也的确有着催泪的神奇之处。日常写的不错，但是对于麻枝总是卖老梗的行为还是不太感冒，谁知道笑点在哪里啊wwww<br>真心觉得麻枝准的音乐本行干得更好，说实话这么多年他除了<strong>LB</strong>附加线之外啥也没写吧……</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《AIR》</strong><br>个人认为麻枝准最优秀的作品，无论是剧本还是音乐。</li><li><strong>《CLANNAD》</strong><br>传说中的超神作，家族神作,不过作为<strong>田中厨</strong>老实说感觉震撼程度没想象中那么大……当然<strong>CL</strong>的优秀还是必须承认的。</li><li><strong>《KANON》</strong><br>不多说了，麻枝准写的虽说也不错但明显比不上<strong>久弥</strong>。</li></ul></li></ul><hr><h3 id="なかひろ"><strong>なかひろ</strong></h3><blockquote><p>实力派脚本家，对于人物的塑造和世界观下情节的发展都是可圈可点的优秀作者。<br><strong>なかひろ</strong>的文风非常的具有灵气，在推动剧情发展的同时能够熟练地酝酿故事高潮所需要的情感，而且刻画起人物来也非常娴熟。描摹日常时得益于世界观的把握并不会导致催眠，而剧情突入到非日常一侧时又会爆发出奇妙的色彩。<br>描摹日常和展开剧情都得心应手的一位灵气逼人的脚本。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《heaven’s cage》</strong><br><strong>なかひろ</strong>早期的作品，文字相当清新灵动，表现主题也恰到好处。</li><li><strong>《星空のメモリア》</strong><br>星空，其实是他作品里我最喜欢的一作，剧情展开和人物塑造进行的相当不错。</li><li><strong>《こいとれ～REN-AI TRAINING～》</strong><br>即使写这种日常系的文本，<strong>なかひろ</strong>的表现很出色呢。</li></ul></li></ul><h3 id="久弥直树"><strong>久弥直树</strong></h3><blockquote><p>神，魔王的天敌wwww<br>久弥是相当正统意义上的脚本家，和恐怕更多依靠天分创作剧情感动玩家的魔王不同，久弥本身的文字功底就很不错，久弥的文字表达能力相当出色，相当细腻的描写和情节的错铺垫使久弥的故事往往更加严谨。<br>比起魔王狂气的自我的表达更加注重对妹子的刻画，对于恋爱的微妙描写十分到位。<br>超喜欢约定的家伙wwww</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《ONE～辉之季节～》</strong><br>久弥表现的相当不错，尤其是对于环境和人物的细腻刻画。</li><li><strong>《KANON》</strong><br>久弥在这里的表现确实相当优秀，对世界观 host 的把握能力和对女性角色的精细刻画都很优秀。</li><li><strong>补充</strong><br>额外说一句，虽说不是游戏，久弥在<strong>SOLA</strong>里的表现也可以说炒鸡棒啊。</li></ul></li></ul><hr><h3 id="海富一"><strong>海富一</strong></h3><blockquote><p>个人最喜欢的猫猫社剧本团中的作者，实力派海富一。<br>虽然很多时候都在<strong>片冈</strong>老大的光环下创作，但老实说个人觉得海富一写的东西一点也不比片冈差，偶尔写得还更好……<br>单独担当或者担任主役时往往能创作出令人眼前一亮的作品,并且在日常和狂气上都有不错的表现，主题色彩比较强，人物的性格十分鲜活，文字在优秀的同时意外的适合初心者。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《サナララ》</strong><br>小清新的海富一好评，个人觉得海富一写出了最美的部分。</li><li><strong>《レコンキスタ》</strong><br>狂气的海富一好评，意想不到的高质量。</li><li><strong>《终わる世界とバースデイ》</strong><br>偶尔这样子的海富一，其实也不错。</li></ul></li></ul><hr><h3 id="林直孝"><strong>林直孝</strong></h3><blockquote><p><strong>打越</strong>的得意弟子，实力也确实很强，全年龄领域绝对的一流脚本家。<br>林直孝继承了打越不少的风格和特点，同时自身也具有相当优秀的文字表达能力，能够在轻松的氛围中组织运用复杂世界观和艰涩的设定。同时，林直孝的文字能力和工作态度都相当严谨，经手的作品大都不错。<br>此外，林直孝还非常擅长营造故事氛围，其对故事发展始终把握的十分出色，剧情的转折和伏笔都很自然，尽管其中还是偶尔带有都合的色彩，但是真实感往往更强。林直孝的作品往往带着的真实与幻想夹杂的气息非常独特。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《CHAOS；HEAD》</strong><br>出师之后的第一部话题作，林直孝对于真实和虚幻的把握十分从容，耍设定和卖中二也恰到好处。</li><li><strong>《STEINS；GATE》</strong><br>这个就不用多说了，的确是近年来位居前列的优秀剧本。</li></ul></li></ul><hr><h3 id="井上启二"><strong>井上启二</strong></h3><blockquote><p>老一辈的脚本家，<strong>ELF</strong>妖精馆黄金脚本群的杰出人物！<br>井上启二的文字表现力就不必多说了，对人物的刻画和情节的推演绝对都是炉火纯青的水平，无论是轻快的世界观还是燃到爆的情节发展都驾驭的得心应手，讲故事的水平更是一流，感情的层层递进和情节的优秀把握绝对叫人简直停不下来=A=<br>井上对于情节的创造力更是屌炸天，无论是学园、<strong>SF</strong>还是燃系都能够创作出优秀的故事，老实说11年再次看到井上的名字时真心激动啊！井上这样的脚本家才是业界良心所在！</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《胜 あしたの雪之丞2》</strong><br>名作雪之丞2，井上负责的续作完全超越了前作。</li><li><strong>《アルテミスブルー》</strong><br>开飞机啊开飞机！轻快的氛围和天空系的独有感触真心带感！</li><li><strong>《创世奇谭アエリアル》</strong><br>井上的新作，虽说基到爆wwwww，但是还是带感啊=A=</li></ul></li></ul><hr><h3 id="早狩武志"><strong>早狩武志</strong></h3><blockquote><p>早狩武志，业界文艺派脚本家之一www，货真价实的文学青年。<br>早狩武志的文字相当的具有灵性，并且他惊人的知识储量也是毋庸置疑的，这使他在<strong>SF</strong>领域表现的相当游刃有余，并且这种知识性的构筑还不至于将玩家完全封死在门外，相反，早狩武志凭借自己的文字构筑的世界观因此相当具有真实性。<br>此外，早狩在塑造情节上也相当的具有灵气，在他越早的作品中越明显，可惜感觉这位实力派脚本家实在是被业界的潮流打击得不轻，居然在<strong>恋ではなく</strong>里玩起了青春日常……显得剧情拖沓……越早的作品越好=A=</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《群青の空を越えて》</strong><br>群青，早狩最优秀的作品，绝对的心血之作。早狩对军事、经济和政治等方面的杂学和剧情达成了相当完美的统一。</li><li><strong>《潮风の消える海に》</strong><br>潮风，虽然比不上群青但是文本的素质也是很赞的。</li></ul></li></ul><hr><h3 id="打越钢太郎"><strong>打越钢太郎</strong></h3><blockquote><p>大名鼎鼎的打越，<strong>KID</strong>曾经的台柱，号称只要有这个名字的游戏就能首发销量收回成本的男人……虽说由于<strong>KID</strong>某种意义上自毁长城还是倒闭了（误）。<br>打越的文字表现力只能说一般，但是论起剧情设计的精巧，情节构思的严谨以及结局把握的震撼性，打越无疑都是业界一流的。对以诡计的设计与解答、谜题的复杂性与逻辑性的兼顾更是在这一领域独树一帜，并且打越在追求剧情严谨性，把握玩家心理并引导玩家进行头脑风暴的同时，对与玩家还是抱有一定的亲和性。以至于当年<strong>R11</strong>出现后面对大片非核心向受众“不明觉厉”的反应干脆就披上马甲撇清关系……<br>但是无论是从名气还是实力上，打越在<strong>KID</strong>脚本群里都是绝对的首屈一指。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《Never7 -the end of infinity-》</strong><br>打越初次展现了自己的实力，虽说在结局上略显仓促而且有点不能自圆其说，但是整体的优秀是有目共睹的。</li><li><strong>《Ever17 -the out of infinity-》</strong><br>打越无敌啦！即使是现在看来，这部作品构思之精巧、场景之宏大、结局之震撼都是少有比拟的杰作！</li><li><strong>《Memories Off 2nd》</strong><br>公认的<strong>MO</strong>系列最高杰作，触及心灵的美丽故事，打越证实了自己在多领域的杰出能力。</li></ul></li></ul><hr><h3 id="奈良原一铁"><strong>奈良原一铁</strong></h3><blockquote><p>图书馆三人组里咱最喜欢的一位。<br>各种意义上来说风格都很独特的脚本家，和<strong>钢屋</strong>、<strong>东出</strong>不同，对单纯的战斗描写并不擅长。相反地，利用“说教”式的描写来传达战斗的神韵也相当具有独特性。<br>**N+**脚本群里节操值最高的一位，虽说这两年也开始啥玩意不写……<br>奈良原对十一区本土杂学尤其是日本刀的热爱使得他的作品始终带有浓浓的个人色彩，而且文字功底也不错，契合世界观的文本描写和时不时的灵光闪现都让人记忆深刻。<br>重视对于锻炼和对战时临场心理的再现的创作风格也很符合其作品的主题。<br>说实话，我觉得咱对一个”突然在邻座上开始挥舞起模造刀来““在公司里研磨起刀刃来”的脚本家真是喜闻乐见啊wwww。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《刃鸣散らす》</strong><br>这才不是基作呢wwww，奈良原玩日本刀玩的真high……</li><li><strong>《装甲悪鬼村正》</strong><br>不必说了，虽说不是他一个人的功劳，不过**N+**纪念作还是实至名归。另外我说我其实更喜欢刃鸣散会不会挨揍？</li></ul></li></ul><hr><h3 id="鬼畜人タムー"><strong>鬼畜人タムー</strong></h3><blockquote><p><strong>AGE</strong>脚本群的最强脚本，反正咱是这么觉得啦=A=<br>实力是毋庸置疑的强劲，对于不同题材的驾驭能力超强，文笔不能说多好但是表现能力屌炸天，非常擅长通过文字的堆砌引导玩家的心理状态，层层展开之后玩家的心理几乎完全能够与剧情发展同步起来，某种程度上可以说写的东西感染力非常强。<br>不得不说，<strong>AGE</strong>的合作模式一定程度上导致了鬼畜人作品整体观感的下降，比如说各线水平明显参差不齐的君望……但是还是掩盖不了鬼畜人的优秀！如果单评某一作品的脚本水平的话，鬼畜人在**《マブラヴ オルタネイティヴ》**里的表现绝对能跻身前五！</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《君が望む永远》</strong><br>大名鼎鼎的君望！鬼畜人的表现很不错不过的确不是他完全发挥实力的作品。</li><li><strong>《螺旋回廊》</strong><br>鬼畜人在这种作品里果然就全力全开了一回=A=。</li><li><strong>《マブラヴ オルタネイティヴ》</strong><br>燃爆了wwwww!一句话：人类を軽蔑しないで下さい！</li></ul></li></ul><hr><h3 id="王雀孙"><strong>王雀孙</strong></h3><blockquote><p>葵妈的好拍档，橘子社王牌剧本！<br>文字能力逆天了，堪称数一数二的难懂，日语白学系列之一。但是如果能领略一点就会发现王老师的文字功底有多深厚。至于文字的表现力，这个简直就属于论外级别……表现到日语白学的地步了……www<br>剧情设计和人物刻画都相当严谨，深厚的文字功底将人物的情感刻画和环境氛围的营造做的十分到位,咱因为王老师的存在甚至连葵妈脸都可以忽视了……</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《那是舞动散落的樱花》</strong><br>舞散樱，绝对是当年数一数二的神作无误。</li><li><strong>《我们没有翅膀》</strong><br>别被那见鬼的动画误导了，王老师在这作里的优秀表现相当碉堡……只要你能看懂。</li><li><strong>《靠近太阳之月的处女作法》</strong><br>新作，相对浅白易懂的一作……但是咱倒不是怎么喜欢，感觉王老师的特色淡化了。</li></ul></li></ul><hr><h3 id="正田崇"><strong>正田崇</strong></h3><blockquote><p>中二帝，中二之神www<br>每个少年都曾经或多或少的中二过，中二怎么可以不玩中二帝的作品呢？<br>正田崇对于世界观 host 的把握能力和人物的刻画及其精细，并且文字之中所带着的种种狂气之感实在是令咱难以忘怀啊。除此之外，对于人物性格的刻画也给人一种浓厚的狂乱色彩，颠覆常人的狂乱始终是咱最喜欢中二帝的地方。<br>另外，不得不说正田中二的台词设计太带感了，一群死中二不明觉厉的嘴炮大战真叫一个酣畅淋漓啊。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《Dies irae～Acta est Fabula～》</strong><br>神怒之日=A=，剧本的长度不要太屌，反正推完之后整个人肯定就直接中二中二掉了……</li><li><strong>《神咒神威神楽》</strong><br><strong>KKK</strong>，和风狂气中二作www，不过中二帝发挥的确实好。</li></ul></li></ul><hr><h3 id="奈须きのこ"><strong>奈须きのこ</strong></h3><blockquote><p>蘑菇，世界观屌炸天的设定党。<br>虽说在大堆设定的映衬下填坑速度实在拙计，但是蘑菇的实力的确没得说，无论是对于战斗场景的描写还是对于人物复杂性的刻画做的都相当不错。<br>蘑菇的文字功底算不上深厚，但是应用功底的确很扎实，在各种场景描写中做的都很出色，语句基本上流畅而自然，玩家解决了设定问题之后的确有着比较好的游戏感受。并且蘑菇的文风跃动感很强，描摹人物时也没有太强的违和感，的确不愧为<strong>TM</strong>社炒冷饭的有力保障。<br>解决不了设定问题嘛……</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《Fate/stay night》</strong><br><strong>TM</strong>商业化后的第一作，蘑菇确实写出了相当不错的故事，更设定出了相当优秀的游戏世界观。</li><li><strong>《魔法使いの夜》</strong><br>魔使，解决了设定问题后的确可以称之良作。顺便说一句武内你以后还是继续努力做社长吧，原画家武内可以瞑目了。</li><li><strong>《月姬》</strong><br>月姬，我会说我觉得这才是蘑菇写的最好的剧本？</li></ul></li></ul><hr><h3 id="虚渊玄"><strong>虚渊玄</strong></h3><blockquote><p>爱的战士就不必多赘述了吧……文字功底和剧情把握都是业界公认的，思想性也的确有着一定深度，虽然已经六七年没写过<strong>GAL</strong>剧本了……你丫给我回来喂！<br>心理上的大开大合、注重情绪调动的文字表现、塑造非日常气氛的吊诡文风、隐藏在故事内核的思想本质……老虚的确有着自己成名的本钱。作为公认的喜好女色却厌憎女性，鄙视女性的感性思维却也同样厌倦粗暴的男权世界的乖僻脚本家，硬派而硬朗的文风的确独树一帜。无论是“最美的的就是毁灭”还是“人性中存在非人性”的表达都是这位脚本家阴郁冷硬的世界观塑造下的产物。</p></blockquote><ul><li><strong>代表作</strong><ul><li><strong>《沙耶の呗》</strong><br>的确是相当厚重的作品，也的确是披着猎奇外衣的虚渊玄式纯爱作。</li><li><strong>《続・杀戮のジャンゴ —地狱の赏金首—》</strong><br>老虚当年“什么都敢写，什么都写得不错”的产物。</li><li><strong>《鬼哭街》</strong><br>老虚的出道作也是成名作，这作的出现一时就让老虚风生水气，的确是了；老虚目前为止最出色的剧本。</li></ul></li></ul><hr><ul><li>这个帖子是我把爬取的内容交给AI处理的,最后两位由于原帖找不到只剩一个名字了,所以AI自动帮我续写了???</li></ul><h3 id="田中罗密欧"><strong>田中罗密欧</strong></h3><blockquote><p><strong>田中</strong>，老实说在咱心里这位才是真正的神。<br>文字功底已经到了返璞归真的地步，平淡的文字中蕴含的巨大的感染力。作为业界最有思想深度的脚本家之一，其对社会、人性的洞察力简直叫人叹为观止。无论是《<strong>家族计划</strong>》中对“家”的探讨，还是《<strong>CROSS+CHANNEL</strong>》中对“沟通”的绝望与希冀，都达到了极高的艺术水准。<br>此外，他在塑造角色方面的能力也是顶级的，能够赋予角色鲜活的生命力和复杂的灵魂，绝非简单的标签化。</p></blockquote><hr><h3 id="剑乃"><strong>剑乃</strong></h3><blockquote><p><strong>剑乃</strong>，业界传说级的存在。<br>作为《<strong>YU-NO</strong>》的创作者，他不仅是一个脚本家，更是一个天才的设计者。他在剧本中融入了超前的平行世界理论，并以严密的逻辑和宏大的构架将其呈现。<br>他的文字充满了知性的魅力，对于悬疑和解谜的氛围营造无人能及。虽然作品不多，但仅凭一部《<strong>YU-NO</strong>》就足以奠定其在<strong>GALGAME</strong>历史上不朽的地位。</p></blockquote><h3 id="某不知名帖子上的脚本家列表">某不知名帖子上的脚本家列表</h3><ol><li><strong>虚渊玄</strong>: Phantom、沙耶之歌、吸血歼鬼的维德哥尼亚</li><li><strong>王雀孙</strong>: 我们没有翅膀、それは舞い散る桜のように、近月少女的礼仪</li><li><strong>鬼畜人タムー</strong>: Muv-Luv Alternative、你所期望的永远、螺旋回廊</li><li><strong>打越钢太郎</strong>: Ever17、秋之回忆1、Remember11</li><li><strong>林直孝</strong>: 命运石之门、Chaos head、机器人笔记</li><li><strong>奈须蘑菇</strong>: Fate/stay night、月姬、魔法使之夜</li><li><strong>Sca-自</strong>: 樱之诗、樱之刻、美好的每一天~不连续存在</li><li><strong>新岛夕</strong>: 初雪樱、魔女恋爱日记、想要传达给你的爱恋</li><li><strong>星空流星</strong>: 腐姬、Forest</li><li><strong>濑户口廉也</strong>: Swan song、Carnival、KIRA☆KIRA</li><li><strong>龙骑士07</strong>: 寒蝉鸣泣之时、海猫鸣泣之时、彼岸花盛开之夜</li><li><strong>麻枝准</strong>: Clannad、Little Busters、Air</li><li><strong>六花梨花</strong>: 鬼畜王兰斯、兰斯6、斗神都市2</li><li><strong>蛭田昌人</strong>: 河原崎家的一族、同级生、Words Worth</li><li><strong>正田崇</strong>: Dies irae、神咒神威神乐、相州战神馆学园 八命阵</li><li><strong>田中罗密欧</strong>: CROSS†CHANNEL、家族计画、最后的现在</li><li><strong>Looseboy</strong>: 车轮之国、G弦上的魔王、A Profile</li><li><strong>下仓バイオ</strong>: 冻京Necro、你和她和她的恋爱、月光嘉年华</li><li><strong>和泉万夜</strong>: EXTRAVAGANZA ～蟲愛でる少女、无限炼奸、MinDeaD BlooD</li><li><strong>丸户史明</strong>: 白色相簿2、青空下的约定、女仆咖啡帕露菲</li><li><strong>奈良原一铁</strong>: 装甲恶鬼村正、刃鸣散</li><li><strong>健速</strong>: 遥仰凰华、明日的世界、从此方到彼方</li><li><strong>卑影ムラサキ</strong>: Baldr sky、Baldr force、Baldr heart</li><li><strong>东出佑一郎</strong>: 妖人幻妖异闻录、Bullet Butlers、Evolimit(エヴォリミット)</li><li><strong>朱门优</strong>: 从晴朗的朝色泛起之际开始、不要践踏天使的羽毛、何时到达那片晴朗的天空(いつか、届く、あの空に)</li><li><strong>铃鹿美弥</strong>: 天之少女、壳之少女、虚之少女</li><li><strong>渡辺僚一</strong>: 苍之彼方的四重奏、春开意遥遥(はるまで、くるる)、夏日云悠悠(なつくもゆるる)</li><li><strong>漆原雪人</strong>: 五光十色的世界、五彩斑斓的世界、樱花萌放</li><li><strong>御影</strong>: ef - a fairy tale of the two、天津罪、青鸟</li><li><strong>昏式龙也</strong>: Maggot baits、眠れぬ羊和孤独な狼、Dead Days</li><li><strong>タカヒロ</strong>: 认真和我恋爱、娇蛮之吻、你是主人我是仆</li><li><strong>海富一</strong>: 终结的世界与生日、Reconquista(レコンキスタ)、琥珀结晶(Amber Quartz)</li><li><strong>浅生咏</strong>: Euphoria、Erewhon</li><li><strong>镜游</strong>: ef - a fairy tale of the two、eden*、为与明日君相逢</li><li><strong>片岡とも</strong>: 水仙、Scarlett、120日元之春</li><li><strong>衣笠彰梧</strong>: 晓之护卫、Reminiscene(レミニセンス)、流星世界演绎者(流星ワールドアクター)</li><li><strong>さかき傘</strong>: 金色Loveriche、辻堂さんの純愛ロード、娇蛮之吻3学期</li><li><strong>土天冥海</strong>: 我的民工女友、媚肉之香、人间残渣</li><li><strong>吴一郎</strong>: 樱之杜†净梦者、来自昏暗的时间尽头、夏色乡愁</li><li><strong>樱井光</strong>: 赫炎的印加诺克、黄雷的伽克苏恩、紫影的索纳尼尔</li><li><strong>鋼屋ジン</strong>: 斩魔大圣、机神飞翔、罪恶王冠-失落的圣诞节</li><li><strong>中岛大河</strong>: 若能再次与你相见、生命的备件、働くオトナの恋愛事情</li><li><strong>森崎亮人</strong>: 幸福噩梦、Fake Azure Arcology(フェイクアズール・アーコロジー)、Sugar+Spice</li><li><strong>元长柾木</strong>: 猫抚歪曲、Sense Off、Gangsta Republica(ギャングスタ・リパブリカ)</li><li><strong>桐月</strong>: 黄昏的禁忌之药、月影魅像-解放之羽、五色浮影绽放于花之海洋</li><li><strong>かずきふ米</strong>: 9-nine、七彩的轮回转世、あけいろ怪奇譚</li><li><strong>七乌未奏</strong>: 隙间樱花与谎言都市、在这片天空展开翅膀、StarTRain</li><li><strong>嵩夜あや</strong>: 少女爱上姐姐、木洩れ陽のノスタルジーカ、機関幕末異聞ラストキャバリエ</li><li><strong>樱庭丸男</strong>: Chronobox、駄作</li><li><strong>希</strong>: 霞外籠逗留記、信天翁航海录、神树之馆</li></ol><h1>音乐</h1><h2 id="演唱会">演唱会</h2><h3 id="绫11演唱会">绫11演唱会</h3><p>人生中参加的第一场演唱会,也是阿绫的第一场个人演唱会,基本的感受是很好的:</p><ol><li>观众们都很热情,有一大堆穿痛衣,出cos,发无料的.</li><li>节目编排还算合理,条子的歌(只)上了三首,但有几首歌比如(&lt;&lt;放大&gt;&gt;,&lt;&lt;九月不停雨&gt;&gt;)我真没听过,看来是假粉了…</li><li>不知所云的AI乐队绘比较扣分,洛天依的声线调的比较崩…</li><li>我看完日场就走了,没参加聚会,买了一个168的礼盒当作留念.</li></ol><h2 id="国语">国语</h2><h3 id="李宗盛">李宗盛</h3><h4 id="山丘-9-5">山丘-9.5</h4><ul><li>听的第一首李宗盛的歌,当时还是初中,天天听以为自己老成熟了老伤感了…<br>散文诗这块我只认可李宗盛</li></ul><h4 id="当爱已成往事-10-0">当爱已成往事-10.0</h4><ul><li>男女二重唱真的美哭了<br>霸王别姬的配乐,好电影配好歌</li></ul><h4 id="梦醒时分-10-0">梦醒时分-10.0</h4><ul><li>作词作曲都是他,但他自己唱的就差很多了…<br>顶级配乐,顶级作词,经典的不能再经典,任何时候听到都会有一种淡淡的忧伤</li></ul><h3 id="林俊杰">林俊杰</h3><h4 id="江南-9-5">江南-9.5</h4><ul><li>小时候总是跟着唱,就是不知道叫什么名字</li></ul><h3 id="周杰伦">周杰伦</h3><h4 id="红尘客栈-9-0">红尘客栈-9.0</h4><h4 id="告白气球-8-5">告白气球-8.5</h4><ul><li>小时候总喜欢唱,导致长大了都不好意思听</li></ul><h4 id="兰亭序-9-5">兰亭序-9.5</h4><ul><li>听的红楼梦鬼畜才知道原曲<br>旋律很好听</li></ul><h4 id="稻香-9-5">稻香-9.5</h4><h3 id="河图">河图</h3><h4 id="第三十八年夏至-10-0">第三十八年夏至-10.0</h4><p>意境渲染这一块没有比这首歌还强的了</p><h3 id="王朝1982">王朝1982</h3><h4 id="我本将心向明月-10-0">我本将心向明月-10.0</h4><p>一曲定乾坤!真有种&quot;天子呼来不上船,自称臣是酒中仙&quot;的感觉</p><h3 id="卦者那啥子靈風">卦者那啥子靈風</h3><p>一体机,甚至还是p主,时不时会发一点v曲出来</p><h4 id="范进中举-10-0">范进中举-10.0</h4><p>现象级歌曲</p><h2 id="日语">日语</h2><h2 id="英语">英语</h2><h3 id="john-denver">John Denver</h3><p>小平同志听了也说好!</p><h4 id="take-me-home-country-roads-10-0">Take me home,country roads-10.0</h4><h4 id="rocky-mountain-high-10-0">Rocky Mountain High-10.0</h4><h2 id="vocaloid">vocaloid</h2><p><strong>中V</strong></p><hr><p><strong>社团系列</strong></p><ul><li>很多歌都是以合作的形式完成的,作曲,编曲,作词,混音有可能都是由不同人负责的</li></ul><h3 id="寂火-9-5">寂火-9.5</h3><ul><li>幻月音乐团<br>旋律编排真的绝了,那种调出来的沙哑嗓音也很棒</li></ul><h3 id="不年轻人-10-0">不年轻人-10.0</h3><ul><li>花间有鹿来 &amp; 星尘 &amp; 海格P &amp; Kevinz &amp; 猿马行歌 &amp; 磷元素P<br>激情是我的谎言,大家都早已疲惫不堪</li></ul><h3 id="繁华唱遍-10-0">繁华唱遍-10.0</h3><p>繁华落尽</p><p><strong>元老系列</strong></p><ul><li>特点是大多数名字都很短</li></ul><h3 id="ilem">ilem</h3><blockquote><p>李白再世,飘逸如仙,基本都是一体机,除了一些毒曲外,歌词和曲调都能打动人心,很少有不好听的歌曲,早期的摸鱼程度还是比cop低一些,但现在发的歌比cop还少了,从作品也听得出来心境有很大的变化了</p></blockquote><h4 id="我没有歌能给你听-9-5">我没有歌能给你听-9.5</h4><h4 id="上山岗-10-0">上山岗-10.0</h4><h4 id="勾指起誓-9-5">勾指起誓-9.5</h4><h4 id="梦良衣-10-0">梦良衣-10.0</h4><h4 id="夜间出租车-8-5">夜间出租车-8.5</h4><h4 id="大氿歌-8-5">大氿歌-8.5</h4><h4 id="白鸟过河滩-10-0">白鸟过河滩-10.0</h4><h3 id="cop">cop</h3><h4 id="石墨系列-10-0">石墨系列-10.0</h4><ul><li>基本都是神曲</li></ul><p>从悲怆哀伤的&lt;&lt;礼物&gt;&gt;,到声嘶力竭的&lt;&lt;世末歌者&gt;&gt;;从淅淅沥沥的&lt;&lt;凉雨&gt;&gt;,到暗无天日的&lt;&lt;世末积雨云&gt;&gt;;从无人问津的&lt;&lt;回音&gt;&gt;,到相互靠近的&lt;&lt;同归世界线&gt;&gt;;从孤独寂寞的&lt;&lt; hello&amp;bye,days &gt;&gt;,到烟火灿烂的&lt;&lt;夏夜空&gt;&gt;;还有古早的&lt;&lt;夏风&gt;&gt;和尚未完成的&lt;&lt;碎梦&gt;&gt;.十首曲子构成了一个不断轮回,无法逃离世界末日的忧郁世界观.</p><p>尽管整个世界观很朴素很简单,但就是那么的打动人心,让听众为之动容.</p><ul><li>世末系列专辑曲大致从16年就开始着手,但十年了仍然没有做出来,<a href="https://moegirl.uk/index.php?title=%E4%B8%96%E6%9C%AB%E6%AD%8C%E8%80%85%E7%B3%BB%E5%88%97&amp;variant=zh-hans">参考萌娘百科</a></li><li><a href="https://copcop.lofter.com/post/1db32eeb_f989350">设定</a></li></ul><h5 id="碎梦-26-4-5-10-0">碎梦(26/4/5)-10.0</h5><ul><li>晚上在临港的酒店点开B站时,突然看见条子发歌了,那一刻的惊喜真的是无法用言语表达的.<br>这首歌我最初听到是22年的时候,之后就一直在等完整版,而条子果然不负众望,成功交出了一份完美的答案.</li></ul><p>飞向总有容身处的世界,却碎裂,却碎裂~</p><p><strong>生贺曲</strong></p><h4 id="为了你唱下去-10-0">为了你唱下去-10.0</h4><p>前奏非常抓耳,高潮部分也很容易让人引起共鸣</p><h4 id="不停歇的旅途-7-5">不停歇的旅途-7.5</h4><p>尽管是第二次生贺曲,记忆点太少了,很难让人记住有什么动人的地方</p><h4 id="纯蓝-9-5">纯蓝-9.5</h4><h4 id="光与影的对白-9-5">光与影的对白-9.5</h4><h4 id="直到最后一天-10-0">直到最后一天-10.0</h4><p>条的厨力重新大爆发!</p><h4 id="我一直驻足等候-10-0">我一直驻足等候-10.0</h4><p>久违的抒情曲真的太美了</p><h3 id="星葵">星葵</h3><h4 id="此间花火-9-0">此间花火-9.0</h4><h4 id="末等残想-9-0">末等残想-9.0</h4><h4 id="月巷-9-0">月巷-9.0</h4><h4 id="途闻-9-0">途闻-9.0</h4><h3 id="雨狸">雨狸</h3><h4 id="妄想系列-9-0">妄想系列-9.0</h4><p>部分曲子很好听,重点是世界观非常独特</p><h3 id="纯白">纯白</h3><h4 id="唱给雅音宫羽-9-5">唱给雅音宫羽-9.5</h4><p>神曲</p><h3 id="litterzy">litterzy</h3><h4 id="冠世一战-10-0">冠世一战-10.0</h4><p>帅</p><h3 id="鸽稀拉">鸽稀拉</h3><h4 id="寻遍星空-10-0">寻遍星空-10.0</h4><p>尽管是13年的曲子,可即便在26年1月听到也觉得无比惊艳,完美的旋律编排,和清澈带点沙哑的稚嫩v3声源,再加上动人的歌词,无论什么时候听都能立刻放松下来.</p><h3 id="z新豪">Z新豪</h3><h4 id="东京不太热-10-0">东京不太热-10.0</h4><h4 id="干物女-10-0">干物女-10.0</h4><h4 id="黑凤梨-9-5">黑凤梨-9.5</h4><h4 id="万能诊听器-9-0">万能诊听器-9.0</h4><h4 id="同心桥-8-5">同心桥-8.5</h4><h3 id="空气凝klean">空气凝Klean</h3><h4 id="时间旅行-9-5">时间旅行-9.5</h4><h3 id="阿良良木健">阿良良木健</h3><h4 id="东风来-9-0">东风来-9.0</h4><p>旋律很好听~</p><h3 id="jusf周存">JUSF周存</h3><h3 id="芹菜猪肉大馄饨">芹菜猪肉大馄饨</h3><h3 id="纳兰寻风">纳兰寻风</h3><p><strong>次世代系列</strong></p><ul><li>特点是大多数名字都很<s>怪</s></li></ul><h3 id="闹闹">闹闹</h3><h3 id="小熠">小熠</h3><h3 id="信弦">信弦</h3><h3 id="胧音">胧音</h3><h3 id="花水r">花水r</h3><h4 id="南京夜无电波讯号-9-0">南京夜无电波讯号-9.0</h4><h3 id="清风疾行">清风疾行</h3><h4 id="小孩的歌-9-5">小孩的歌-9.5</h4><p>ilem10周年里最惊艳的歌了,也是因为这首歌才买了专辑.</p><h3 id="安逸笙笙">安逸笙笙</h3><h3 id="沉年旧诗">沉年旧诗</h3><h3 id="星语夜枫">星语夜枫</h3><h3 id="一碗热汤p">一碗热汤p</h3><h3 id="旅行的蜗牛">旅行的蜗牛</h3><p>宝藏p主,几乎一首歌是不好听的.</p><h3 id="直到凌晨也无法安眠">直到凌晨也无法安眠</h3><h3 id="papaw泡泡">papaw泡泡</h3><p>繁华唱遍就是他编写的曲子~</p><h3 id="biapia">biapia</h3><h4 id="仅此-9-0">仅此-9.0</h4><p>我比较喜欢这种简约的风格</p><h3 id="wovop">WOVOP</h3><h3 id="mocker44">MOCKER44</h3><h3 id="topkingcream">TOPKINGCREAM</h3><p><strong>日V</strong></p><hr><h3 id="deco-27">Deco*27</h3><h3 id="匹诺曹p">匹诺曹p</h3><h3 id="doriko">doriko</h3><h3 id="jin">jin</h3><h3 id="harry-p">Harry P</h3><p>宝藏p主,每首歌都很对我胃口</p><h3 id="keeno">keeno</h3>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我之所以把自己玩过的游戏,听过的歌,看过的电影都等信息一一记录在这里,那是因为我的记忆力真的很差,过不了一年半载就记不大清楚具体内容了;至于为什么要写成博客,自然是想分享出来让大家也试试.&lt;/p&gt;
&lt;h1&gt;game&lt;/h1&gt;
&lt;h2 id=&quot;轻型游戏&quot;&gt;轻型游戏&lt;/h2&gt;
</summary>
      
    
    
    
    <category term="动态更新" scheme="https://revival-of-hope.github.io/categories/%E5%8A%A8%E6%80%81%E6%9B%B4%E6%96%B0/"/>
    
    
  </entry>
  
  <entry>
    <title>2026-04-02 GNU介绍</title>
    <link href="https://revival-of-hope.github.io/2026/04/02/archives-2026-2-2026-04-02-GNU%E4%BB%8B%E7%BB%8D/"/>
    <id>https://revival-of-hope.github.io/2026/04/02/archives-2026-2-2026-04-02-GNU%E4%BB%8B%E7%BB%8D/</id>
    <published>2026-04-02T00:00:00.000Z</published>
    <updated>2026-04-02T11:36:50.518Z</updated>
    
    <content type="html"><![CDATA[<ul><li><a href="https://www.gnu.org/">GNU官网</a></li></ul><p>我一直在使用GNU提供的各种软件,却从来没有好好的去深入理解这个组织,所以写了这篇文章来介绍一下.</p><h2 id="历史"><a class="markdownIt-Anchor" href="#历史"></a> 历史</h2><blockquote><p><a href="https://en.wikipedia.org/wiki/GNU">wiki</a><br />GNU is a recursive acronym for “GNU’s Not Unix!”, chosen because GNU’s design is Unix-like, but differs from Unix by being free software and containing no Unix code.</p></blockquote><ul><li><a href="https://www.gnu.org/software/software.html">软件列表</a><br />GNU组织名下的几百个软件本身就足够用来组成一个Linux操作系统了,尽管GNU名下的操作系统都不是很出名就是了…<br />但是,GNU组织名下还有几个著名的软件: Bash,gcc,gdb,make,Emacs.每一个都有着举足轻重的地位</li></ul><h2 id="bash"><a class="markdownIt-Anchor" href="#bash"></a> Bash</h2><ul><li><a href="https://www.gnu.org/software/bash/manual/bash.html#Bash-POSIX-Mode-1">官方</a></li><li><a href="https://zh.wikipedia.org/wiki/Bash">wiki</a></li></ul><blockquote><p>Bash是Bourne shell的后继兼容版本与开放源代码版本，它的名称来自Bourne shell（sh）的一个双关语（Bourne again / born again）：Bourne-Again SHell。</p><p>Bash is an interactive command interpreter and command language developed for <strong>Unix-like operating systems</strong>.</p></blockquote><ul><li>大多数Linux系统都默认使用Bash作为脚本执行工具</li></ul><h2 id="gcc"><a class="markdownIt-Anchor" href="#gcc"></a> gcc</h2><ul><li><a href="https://www.gnu.org/software/gcc/">官网</a></li><li>gcc全名为<strong>GNU Compiler Collection</strong></li></ul><blockquote><p>The GNU Compiler Collection includes front ends for <strong>C, C++</strong>, Objective-C, Objective-C++, Fortran, Ada, <strong>Go</strong>, D, Modula-2, COBOL, <strong>Rust</strong>, and Algol 68 as well as libraries for these languages (libstdc++,…). GCC was originally written as the compiler for the GNU operating system. The GNU system was developed to be 100% free software, free in the sense that it respects the user’s freedom.</p></blockquote><p>我们需要知道的是,gcc通过不同的前端部分处理不同的编程语言,在经过中间处理后,使用相同的后端部分翻译成可执行文件,这种构造使得语言开发者不用太多的考虑中间文件到机器语言的转换.</p><h2 id="gdb"><a class="markdownIt-Anchor" href="#gdb"></a> gdb</h2><ul><li><a href="https://www.sourceware.org/gdb/">官网</a></li></ul><blockquote><p>GDB, the <strong>GNU Project debugger</strong>, allows you to see what is going on `inside’ another program while it executes – or what another program was doing at the moment it crashed.</p></blockquote><ul><li>GDB can run on most popular UNIX and Microsoft Windows variants, as well as on macOS.</li></ul><p>在vscode里对cpp程序进行断点调试靠的就是gdb</p><h2 id="make"><a class="markdownIt-Anchor" href="#make"></a> make</h2><ul><li><a href="https://www.gnu.org/software/make/manual/make.html">官网</a><br />cpp的底层构建工具,不必多谈</li></ul><h2 id="emacs"><a class="markdownIt-Anchor" href="#emacs"></a> Emacs</h2><ul><li><a href="https://www.gnu.org/software/emacs/tour/index.html">官网</a><br />Emacs是一个&quot;重量级&quot;文本编辑器(当然比vscode还是要轻量的多),支持的基础功能就有很多,如下方表格所示:<br />| 功能模块       | 基础版内置支持说明                                           |<br />| :------------- | :----------------------------------------------------------- |<br />| <strong>文本编辑</strong>   | 缓冲区（Buffer）管理、无限撤销（Undo）、矩形编辑、宏录制     |<br />| <strong>文件系统</strong>   | Dired（目录浏览器）、远程文件编辑（TRAMP）、多文件全局搜索   |<br />| <strong>编程辅助</strong>   | 语法高亮（Font Lock）、自动缩进、S-expression 导航、基础补全 |<br />| <strong>多任务管理</strong> | 窗口（Window）任意切分、框架（Frame/多端显示）管理           |<br />| <strong>自省系统</strong>   | 实时查看函数定义（Describe Function）、变量文档及快捷键绑定  |<br />| <strong>内置终端</strong>   | Eshell（纯 Elisp 实现的 Shell）、Term、Ansi-term             |<br />| <strong>版本控制</strong>   | VC 模式（支持 Git, SVN, Mercurial 的基础操作）               |<br />| <strong>文本处理</strong>   | 正则表达式搜索替换、文本对齐、各种字符编码转换               |<br />| <strong>组织与日程</strong> | Org-mode（基础笔记、任务追踪、导出 HTML/LaTeX）              |<br />| <strong>网络工具</strong>   | EWW 浏览器、邮件客户端（Gnus/Rmail）、FTP 支持               |<br />可以看的出来它比vim要复杂繁重的多,集成了很多不是非常有必要的功能,这也就导致Emacs的快捷键比vim要多,甚至有不少的多级快捷键操作,所以使用vi/vim的人比Emacs要多很多.<br />Emacs的拥护者与vi(著名的vim为vi的改版)的拥护者曾经<a href="https://en.wikipedia.org/wiki/Editor_war">闹得不可开交</a>,但在vscode成为了主流以后就没有争吵的必要了😆.</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gnu.org/&quot;&gt;GNU官网&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我一直在使用GNU提供的各种软件,却从来没有好好的去深入理解这个组织,所以写了这篇文章来介绍一下.&lt;/p&gt;
&lt;h2 id=&quot;历史&quot;&gt;&lt;a class=&quot;</summary>
      
    
    
    
    
    <category term="开源" scheme="https://revival-of-hope.github.io/tags/%E5%BC%80%E6%BA%90/"/>
    
  </entry>
  
  <entry>
    <title>技术文章阅读笔记合集</title>
    <link href="https://revival-of-hope.github.io/2026/03/31/dynamic-2026-03-31-%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0%E5%90%88%E9%9B%86/"/>
    <id>https://revival-of-hope.github.io/2026/03/31/dynamic-2026-03-31-%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0%E5%90%88%E9%9B%86/</id>
    <published>2026-03-31T00:00:00.000Z</published>
    <updated>2026-04-15T02:10:03.906Z</updated>
    
    <content type="html"><![CDATA[<h1>重构 改善既有代码的设计</h1><h2 id="ch1-Intro">ch1: Intro</h2><blockquote><p>如果你要给程序添加一个特性，但发现代码因缺乏良好的结构而不易于进行更改，那就先重构那个程序，使其比较容易添加该特性，然后再添加该特性。</p></blockquote><blockquote><p>无论每次重构多么简单，养成重构后即运行测试的习惯非常重要。<br>犯错误是很容易的——至少我知道我是很容易犯错的。做完一次修改就运行测试，这样在我真的犯了错时，只需要考虑一个很小的改动范围，这使得查错与修复问题易如反掌。<br>这就是重构过程的精髓所在：小步修改，每次修改后就运行测试。如果我改动了太多东西，犯错时就可能陷入麻烦的调试，并为此耗费大把时间。小步修改，以及它带来的频繁反馈，正是防止混乱的关键。</p></blockquote><ul><li>这章用了巨量的篇幅来修改一个几十行的js代码,从而说明了一个良好的早期架构是有多么的重要,一旦那些架构混乱的项目开始变得复杂,就算是神仙来了也未必能够轻易看懂并重构</li></ul><p><strong>关键点</strong>: 尽可能多的使用OOP,通过多态,继承,接口来实现代码复用和类型统一;通过将复杂表达式拆分为工具函数并择合适的名字来增强代码的可读性</p><h2 id="ch2-重构的原则">ch2: 重构的原则</h2><p><strong>重构</strong>有两种词性,一种是动词,一种是名词:</p><ul><li>重构（名词）：对软件内部结构的一种调整，目的是在不改变软件可观察行为的前提下，提高其可理解性，降低其修改成本。</li><li>重构（动词）：使用一系列重构手法，在不改变软件可观察行为的前提下，调整其结构。</li></ul><blockquote><p>如果我看见一块凌乱的代码，但并不需要修改它，那么我就不需要重构它。如果丑陋的代码能被隐藏在一个 API 之下，我就可以容忍它继续保持丑陋。只有当我需要理解其工作原理时，对其进行重构才有价值。<br>另一种情况是，如果重写比重构还容易，就别重构了。这是个困难的决定。如果不花一点儿时间尝试，往往很难真实了解重构一块代码的难度。决定到底应该重构还是重写，需要良好的判断力与丰富的经验，我无法给出一条简单的建议。</p></blockquote><blockquote><p>如果一支团队想要重构，那么每个团队成员都需要掌握重构技能，能在需要时开展重构，而不会干扰其他人的工作。这也是我鼓励持续集成的原因：有了 CI，每个成员的重构都能快速分享给其他同事，不会发生这边在调用一个接口那边却已把这个接口删掉的情况；如果一次重构会影响别人的工作，我们很快就会知道。自测试的代码也是持续集成的关键环节，所以这三大实践——自测试代码、持续集成、重构——彼此之间有着很强的协同效应。</p></blockquote><h2 id="ch3-代码的坏味道">ch3: 代码的坏味道</h2><p>需要重构的特征有以下几个:</p><ol><li>难以捉摸的命名</li><li>重复的代码段</li><li>函数太长: 将值得用注释说明的部分拆分成函数</li><li>过长参数列表: 使用类来传入参数</li><li>全局数据: 用函数或者类来封装这个全局数据,尽量控制其作用域</li><li>可变数据: 如果一个数据有不同的用途,最好将它按照用途分成不同的类</li><li>模块边界不清晰</li><li>修改一次需要在多个地方更改</li></ol><h2 id="ch4-构筑测试体系">ch4: 构筑测试体系</h2><blockquote><p>确保所有测试都完全自动化，让它们检查自己的测试结果。</p></blockquote><blockquote><p>频繁地运行测试。对于你正在处理的代码，与其对应的测试至少每隔几分钟就要运行一次，每天至少运行一次所有的测试。</p></blockquote><p>考虑可能出错的边界条件，把测试火力集中在那儿。</p><blockquote><p>每当你收到 bug 报告，请先写一个单元测试来暴露这个 bug。</p></blockquote><h2 id="ch5-过渡章节">ch5: 过渡章节</h2><blockquote><p>本书剩余的篇幅是一份重构的名录。最初这个名录只是我的个人笔记，我用它来提示自己如何以安全且高效的方式进行重构。然后我不断精炼这份名录，对一些重构的深入探索又引出了更多的重构手法。对于不太常用的重构手法，我还是会不断参阅这份名录。</p></blockquote><h2 id="ch6-第一组">ch6: 第一组</h2><h3 id="提炼函数-Extract-Function">提炼函数(Extract Function)</h3><p><strong>用例</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">printOwing</span>(<span class="params">invoice</span>) &#123;</span><br><span class="line">  <span class="title function_">printBanner</span>();</span><br><span class="line">  <span class="keyword">let</span> outstanding = <span class="title function_">calculateOutstanding</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">//print details</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`name: <span class="subst">$&#123;invoice.customer&#125;</span>`</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`amount: <span class="subst">$&#123;outstanding&#125;</span>`</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">printOwing</span>(<span class="params">invoice</span>) &#123;</span><br><span class="line">  <span class="title function_">printBanner</span>();</span><br><span class="line">  <span class="keyword">let</span> outstanding = <span class="title function_">calculateOutstanding</span>();</span><br><span class="line">  <span class="title function_">printDetails</span>(outstanding);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">printDetails</span>(<span class="params">outstanding</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`name: <span class="subst">$&#123;invoice.customer&#125;</span>`</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`amount: <span class="subst">$&#123;outstanding&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>对于“何时应该把代码放进独立的函数”这个问题，我曾经听过多种不同的意见。有的观点从代码的长度考虑，认为一个函数应该能在一屏中显示。有的观点从复用的角度考虑，认为只要被用过不止一次的代码，就应该单独放进一个函数；只用过一次的代码则保持内联（inline）的状态。但我认为最合理的观点是“将意图与实现分开”：<strong>如果你需要花时间浏览一段代码才能弄清它到底在干什么，那么就应该将其提炼到一个函数中</strong>，并根据它所做的事为其命名。以后再读到这段代码时，你一眼就能看到函数的用途，大多数时候根本不需要关心函数如何达成其用途（这是函数体内干的事）。</p></blockquote><blockquote><p>如果想要提炼的代码非常简单，例如只是一个函数调用，只要新函数的名称能够以更好的方式昭示代码意图，我还是会提炼它；但如果想不出一个更有意义的名称，这就是一个信号，可能我不应该提炼这块代码。不过，我不一定非得马上想出最好的名字，有时在提炼的过程中好的名字才会出现。有时我会提炼一个函数，尝试使用它，然后发现不太合适，再把它内联回去，这完全没问题。只要在这个过程中学到了东西，我的时间就没有白费。</p></blockquote><ul><li>“如果需要返回的变量不止一个，又该怎么办呢？”</li></ul><blockquote><p>有几种选择。最好的选择通常是：挑选另一块代码来提炼。我比较喜欢让每个函数都只返回一个值，所以我会安排多个函数，用以返回多个值。如果真的有必要提炼一个函数并返回多个值，可以构造并返回一个记录对象—不过通常更好的办法还是回过头来重新处理局部变量</p></blockquote><h3 id="内联函数（Inline-Function）">内联函数（Inline Function）</h3><blockquote><p>这里的内联指的是将不必要的中间层删除,从而让函数更加清晰</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getRating</span>(<span class="params">driver</span>) &#123;</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_">moreThanFiveLateDeliveries</span>(driver) ? <span class="number">2</span> : <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">moreThanFiveLateDeliveries</span>(<span class="params">driver</span>) &#123;</span><br><span class="line"> <span class="keyword">return</span> driver.<span class="property">numberOfLateDeliveries</span> &amp;gt; <span class="number">5</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getRating</span>(<span class="params">driver</span>) &#123;</span><br><span class="line"> <span class="keyword">return</span> (driver.<span class="property">numberOfLateDeliveries</span> &amp;gt; <span class="number">5</span>) ? <span class="number">2</span> : <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>这显然与前面说的提炼函数正好相反,从而说明重构并不是一个简单的活儿,你不好判断加入函数和删除函数这两种做法哪一种会让代码更清晰</li></ul><h2 id="break-总结">break&amp;总结</h2><p>后面的部分都是一些具体用例了,大部分内容都需要真正去实践才能体会,所以就不建议去看了.</p><p>提炼一下本书的精华:</p><ol><li>软件的初步架构需要是合理的,工程化的,否则后期的重构难度甚至超过推翻重写</li><li>重构一般是一次一小步进行的,如果你的重构会让项目暂时无法运行,说明你做的不是重构</li><li>重构的方法有以下几种:<ol><li>提炼/删除 函数</li><li>用类来存放函数和变量</li><li>去除不必要的全局变量</li><li>改一个好的名字</li></ol></li><li>重构与添加新功能可以是同时进行的</li></ol><h1>程序员自我修养</h1><ul><li>讲的很深,可惜的是逻辑比较混乱,如果能再版后重构一下就真的是神书了</li></ul><h2 id="OUTLINE">OUTLINE</h2><ol><li>简介</li><li>编译和链接</li><li>目标文件里有什么</li><li>静态链接</li><li>windows PE/COFF</li><li>exe的装载与进程</li><li>动态链接</li><li>Linux的共享库</li><li>Windows中的动态链接</li><li>内存</li><li>运行库</li><li>系统调用与API</li><li>运行库的实现</li></ol><h2 id="编译和链接">编译和链接</h2><h3 id="程序运行的过程">程序运行的过程</h3><p>当我们使用GCC编译Hello World程序时,只需要这样写:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">gcc hello.c -o ./a.out</span><br><span class="line"><span class="comment"># &#x27;./a.out&#x27;是文件名和路径,后缀名可以随便起,写成tho没有后缀或者a.xyz也可以</span></span><br></pre></td></tr></table></figure><p>上述过程可以分解为4个步骤:</p><ol><li>预处理(Preprocessing)</li><li>编译(Compilation)</li><li>汇编(Assembly)</li><li>链接(Linking)</li></ol><h4 id="预处理">预处理</h4><p>c文件和h文件会被预处理成.i文件,cpp文件和hpp文件会被预处理为.ii文件.</p><ul><li>对应的命令为<code>gcc -E hello.c -o a.i</code><br>该阶段主要处理源代码中以&quot;#&quot;打头的预编译指令,如’#include’,'#define’等,主要运行过程如下:</li></ul><ol><li>将所有的&quot;#define&quot;删除,并展开所有的宏定义,比如,将含有&quot;#define PI 3.14&quot;的文件中的所有PI替换为3.14</li><li>处理所有的条件预编译指令,如&quot;#if&quot;,&quot;endif&quot;等</li><li>处理&quot;#include&quot;,将被包含的文件插入到文件中该预编译指令所在的行,该过程是递归执行的</li><li>删除所有的注释&quot;//“和”/*  */&quot;</li><li>添加行号和文件名标识,如 &quot; #2 “hello.c” 2 &quot;,这就是我们在程序报错的时候看到的那些行号和文件名的来历,至于行尾的2,是一个给编译器看的标志位</li><li>保留所有的&quot;#pragma&quot;指令</li></ol><p>因此,经过预处理后的.i文件不包含任何宏定义,包含的文件也被插入到.i文件中</p><h4 id="编译">编译</h4><p>对.i文件进行一系列词法分析,语法分析,语义分析和优化,生成相应的汇编代码文件.</p><ul><li>对应的命令为<code>gcc -S hello.i -o hello.s</code></li></ul><h4 id="汇编">汇编</h4><p>根据汇编代码构建目标文件</p><ul><li>对应的命令为<code>gcc -c hello.s -o hello.o</code><ul><li>或者一步完成: <code>gcc -c hello.c -o hello.o</code></li></ul></li></ul><h4 id="链接">链接</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -1gcc -1gcc_eh -1c -end-group crtend.o crtn.o</span><br></pre></td></tr></table></figure><p>可以看到需要链接一堆文件才可以得到最终的可执行文件</p><h3 id="编译的详细原理">编译的详细原理</h3><p>下面我们来以一段简单的c语言代码为例来分析编译的全过程:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">array</span>[index] = (index+<span class="number">4</span>)*(<span class="number">2</span>+ <span class="number">6</span>)</span><br></pre></td></tr></table></figure><h4 id="词法分析">词法分析</h4><p>源代码被输入到扫描器(Scanner),产生一系列记号:关键字,标识符,数字,字符串和特殊符号(加号,等号)等.</p><h4 id="语法分析">语法分析</h4><p>语法分析器(Grammar Parser)对扫描器产生的记号进行语法分析,产生由表达式组成的语法树(Syntax Tree)<br><img src="/images/2026-03-25/PixPin_2026-03-27_12-43-31.webp" alt="alt text"></p><h4 id="语义分析">语义分析</h4><p>语义分析器(Semantic Analyzer)对表达式进行<strong>静态</strong>的语义分析,标识各个表达式的类型;动态语义则只能在运行期确定.<br><img src="/images/2026-03-25/PixPin_2026-03-27_12-44-40.webp" alt="alt text"></p><h4 id="中间代码的生成">中间代码的生成</h4><p>现代编译器会对源代码进行优化,将整个<strong>语法树</strong>转换成<strong>中间代码</strong>,尽管非常接近目标代码,但它与运行的操作系统无关,不包含数据尺寸,变量地址和寄存器名字等信息.<br>根据中间代码可以把编译器分为前端和后端.前端负责产生于操作系统无关的中间代码,后端负责将中间代码转换成目标文件<br><strong>Java 编译体系 (Bytecode)</strong></p><p>前端：javac。它将 .java 源码编译成与平台无关的 Java Bytecode (.class 文件)。这就是所谓的中间代码。</p><p>后端：JVM (Java Virtual Machine) 中的 JIT 编译器 (如 C1、C2)。当程序运行时，JIT 将字节码转换为当前运行机器（Windows x64、Linux ARM 等）的具体指令集。</p><h4 id="目标代码的生成与优化">目标代码的生成与优化</h4><p>编译器后端主要包括<strong>代码生成器</strong>和<strong>目标代码优化器</strong>二者.<br>代码生成器将中间代码转换成目标机器代码,例如:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">t1 = y + z</span><br><span class="line">x = t1</span><br><span class="line"></span><br><span class="line">-&gt;</span><br><span class="line"></span><br><span class="line">mov rax, QWORD PTR [rbp-8]   ; 将变量 y 加载到寄存器 rax</span><br><span class="line">add rax, QWORD PTR [rbp-16]  ; 将变量 z 的值加到 rax 中</span><br><span class="line">mov QWORD PTR [rbp-24], rax  ; 将结果 rax 存回变量 x 的内存地址</span><br></pre></td></tr></table></figure><p>然后由<strong>目标代码优化器</strong>对上述目标代码进行优化,比如选择合适的寻址方式,使用位移来代替乘法运算,删除冗余指令等</p><h4 id="总结">总结</h4><p>经过这么多步骤后,源代码被编译成了目标代码,但有一个问题,变量的存储地址还没有确定,而且如果这个变量是来自其他模块的话又该怎么办?这就是链接派上用场的地方了</p><h3 id="链接概览">链接概览</h3><p><img src="/images/2026-03-27/PixPin_2026-03-31_13-09-29.webp" alt="alt text"><br>链接有以下几个步骤:</p><ol><li>地址和空间分配(Address and Storage Allocation)</li><li>符号决议(Symbol Resolution)</li><li>重定位(Relocation)</li></ol><h2 id="补充-编译全过程-编译器的前端和后端">补充: 编译全过程;编译器的前端和后端</h2><p>由于书上对这些概念没有做一个很清晰的介绍,因此我再在这里做一点辨析方便后续的阅读:<br><strong>流水线解释</strong></p><ul><li>预处理: 转换宏定义,删除注释</li><li>编译(狭义): 将cpp源码翻译成汇编代码(人类可读)</li><li>汇编:<ul><li>将汇编代码翻译成<strong>机器指令</strong>(二进制码)</li><li>根据机器指令,地址位置等信息构造<strong>目标文件</strong></li></ul></li><li>链接: 将目标文件与系统库,用户库关联起来,得到可执行文件</li><li>编译(广义): 由于大多数人对cpp的装载过程没有一个清晰的认识,故通常使用编译代指从<code>.cpp</code>到<code>.exe</code>的全过程,也就是说我们一般都用广义的编译概念,很少特指&quot;真正的编译&quot;</li></ul><p>但是,我们所用的编译器如gcc,clang等都是广义上的编译器,也就是说不仅仅做的是编译,而是包揽了从<code>.cpp</code>到<code>.exe</code>的全构建过程<br>如果用前端和后端的概念来划分的话,是这样的:</p><h3 id="前端（Frontend）">前端（Frontend）</h3><p><strong>范畴：</strong> 仅包含“编译”这一步的前半部分。</p><ul><li><strong>输入：</strong> 预处理后的源码。</li><li><strong>任务：</strong> 词法分析（Lexical Analysis）、语法分析（Syntax Analysis）、语义分析（Semantic Analysis）、生成<strong>中间表示（IR, Intermediate Representation）</strong>。</li><li><strong>特性：</strong> 与具体的硬件架构（如 x86、ARM）无关，只与 C++ 语言本身的规则有关。</li></ul><h3 id="后端（Backend）">后端（Backend）</h3><p><strong>范畴：</strong> 包含“编译”这一步的后半部分，以及“汇编”的全部。</p><ul><li><strong>任务：</strong> * <strong>中端优化（Optimizer）</strong>：对 IR 进行架构无关的优化。<ul><li><strong>代码生成（Code Generator）</strong>：将 IR 转换为特定硬件的<strong>汇编代码</strong>。</li><li><strong>汇编器（Assembler）</strong>：将汇编代码转换为机器指令，产出目标文件。</li></ul></li><li><strong>特性：</strong> 强依赖于硬件架构。</li></ul><h3 id="其他项">其他项</h3><ul><li><strong>预处理（Preprocessing）</strong>：通常被视为编译前的“文本清洁工作”，不属于狭义编译器（Compiler Core）的前后端逻辑。</li><li><strong>链接（Linking）</strong>：属于编译链的下游，是一个独立的二进制处理过程，不属于编译器（Compiler）的范畴。</li></ul><h2 id="目标文件-汇编的产物">目标文件: 汇编的产物</h2><h3 id="目标文件的格式">目标文件的格式</h3><p>可执行文件的格式主要有Windows中的PE(Portable Executable,不是那个重装windows用的Preinstallation Environment)和Linux中的ELF(Executable Linkable Format),两者都是COFF(Common file format)的变种.从广义上看,可执行文件的格式与目标文件基本相同,故这里将它们看作一种类型的文件,在Windows中称为PE-COFF文件格式,在Linux中称为ELF文件格式.(也就是说我们这里把目标文件就看成是ELF文件)</p><ul><li>事实上,动态链接库(DLL,Dynamic Linking Library)(<a href="http://Windows.xn--dllLinux-1c2nv13y.so">Windows.dll和Linux的.so</a>)和静态链接库(Static Linking Library)(Windows的.lib和Linux的.a)的存储方式也是可执行文件.</li></ul><p>更为标准的分类方法如下:</p><table><thead><tr><th style="text-align:left">ELF 文件类型</th><th style="text-align:left">说明</th><th style="text-align:left">实例</th></tr></thead><tbody><tr><td style="text-align:left"><strong>可重定向文件</strong><br>(Relocatable File)</td><td style="text-align:left">包含代码和数据，可被用来链接成可执行文件或共享目标文件，静态链接库也归为此类。</td><td style="text-align:left">Linux 的 <code>.o</code><br>Windows 的 <code>.obj</code></td></tr><tr><td style="text-align:left"><strong>可执行文件</strong><br>(Executable File)</td><td style="text-align:left">包含可以直接执行的程序。</td><td style="text-align:left">Linux 的 <code>/bin/bash</code><br>Windows 的 <code>.exe</code></td></tr><tr><td style="text-align:left"><strong>共享目标文件</strong><br>(Shared Object File)</td><td style="text-align:left">包含代码和数据。可由链接器与其他可重定向/共享目标文件链接产生新目标文件；或由动态链接器与可执行文件结合，作为进程映像的一部分运行。</td><td style="text-align:left">Linux 的 <code>.so</code><br>Windows 的 <code>DLL</code></td></tr><tr><td style="text-align:left"><strong>核心转储文件</strong><br>(Core Dump File)</td><td style="text-align:left">当进程意外终止时，系统将该进程的地址空间内容及终止时的其他信息转储到该文件。</td><td style="text-align:left">Linux 下的 <code>core dump</code></td></tr></tbody></table><h3 id="ELF文件的内容">ELF文件的内容</h3><p><img src="/images/2026-04-08/PixPin_2026-04-09_14-02-28.webp" alt="alt text"></p><p>目标文件将不同类型的信息用段(section)的形式存储:</p><ol><li>File Header: 描述了整个文件的文件属性,包括文件是否可执行,目标硬件,目标操作系统等信息;还包括一个段表(section table),描述目标文件中各段的属性<ol><li>使用C语言的结构体来定义</li></ol></li><li>.text section: 保存汇编得到的及其代码</li><li>.data section: 保存已经初始化的全局变量和局部静态变量</li><li>.bss section: 保存未初始化的全局变量和局部静态变量,由于它们都是0,故没有必要放入.data段中,同时,由于程序运行时需要记录这两类变量,因此需要用.bss段来额外预留位置.该段并没有内容,故在目标文件中也不占据空间.</li></ol><blockquote><p>bss的来历<br>BSS(Block Started by Symbol)来源于1950年代IBM大型机的汇编器中的一个伪指令,后来被引入标准汇编器FAP中,用于定义符号并且为该符号预留给定数量的未初始化空间</p></blockquote><p>整体来说,源代码被编译后分成两段:程序指令(代码段),程序数据(数据段和.bss段).</p><p>事实上,ELF文件的内部结构比上述所说的四段式结构要复杂的多:<br><img src="/images/2026-04-08/PixPin_2026-04-10_15-06-33.webp" alt="alt text"></p><ul><li><strong>[ 0] (NULL)</strong>：索引为 0 的段物理上必须存在且全为空，用于标识无效引用。</li><li><strong>[ 1] .text</strong>：<strong>代码段</strong>。存放程序经过编译后的物理机器指令。</li><li><strong>[ 2] .rel.text</strong>：<strong>代码重定位段</strong>。记录 <code>.text</code> 段中哪些物理地址需要在链接时进行修正。</li><li><strong>[ 3] .data</strong>：<strong>已初始化数据段</strong>。存放程序中已初始化的全局变量和局部静态变量。</li><li><strong>[ 4] .bss</strong>：<strong>未初始化数据段</strong>。为未初始化的全局变量预留的物理占位符，在文件中不占实际磁盘空间。</li><li><strong>[ 5] .rodata</strong>：<strong>只读数据段</strong>。存放常量（如字符串常量、<code>const</code> 修饰的变量）。</li><li><strong>[ 6] .comment</strong>：<strong>注释段</strong>。物理记录编译器版本信息（如 “GCC: (GNU) …”）。</li><li><strong>[ 7] .note.GNU-stack</strong>：<strong>堆栈属性段</strong>。物理标识堆栈是否可执行，用于系统安全防御（NX 位）。</li><li><strong>[ 8] .shstrtab</strong>：<strong>段表字符串表</strong>。存储所有段名（如 “.text”, “.data”）的物理字符串池。</li><li><strong>[ 9] .symtab</strong>：<strong>符号表</strong>。记录程序中定义和引用的所有函数名、变量名及其物理偏移。</li><li><strong>[10] .strtab</strong>：<strong>字符串表</strong>。存储符号表中所使用的所有名称字符串。</li></ul><p>下面我们来一个个解析:</p><h3 id="文件头-段表-重定位表">文件头,段表,重定位表</h3><h4 id="文件头">文件头</h4><ul><li><a href="https://man7.org/linux/man-pages/man5/elf.5.html">拓展阅读</a></li></ul><p>本书使用的示例c程序分析得到的文件头内容如下:</p><ul><li><strong>Magic (魔数)</strong>：<code>7f 45 4c 46 01 01 01 00 ...</code><ul><li>物理意义：文件开头的 16 个字节，用于标识该文件是一个 ELF 格式的可执行或目标文件。</li></ul></li><li><strong>Class (类别)</strong>：<code>ELF32</code><ul><li>物理意义：该文件是为 <strong>32 位</strong> 架构设计的。</li></ul></li><li><strong>Data (数据存储方式)</strong>：<code>2's complement, little endian</code><ul><li>物理意义：采用二补码形式，且为 <strong>小端序</strong>（低位字节存储在低地址）。</li></ul></li><li><strong>Version (版本)</strong>：<code>1 (current)</code><ul><li>物理意义：当前 ELF 格式的版本号。</li></ul></li><li><strong>OS/ABI (操作系统/接口)</strong>：<code>UNIX - System V</code><ul><li>物理意义：该文件遵循的物理调用约定标准。</li></ul></li><li><strong>Type (文件类型)</strong>：<code>REL (Relocatable file)</code><ul><li>物理意义：这是一个<strong>可重定位文件</strong>（通常为 <code>.o</code> 文件），尚未经过链接。</li></ul></li><li><strong>Machine (硬件平台)</strong>：<code>Intel 80386</code><ul><li>物理意义：物理运行的目标指令集架构。</li></ul></li><li><strong>Entry point address (入口地址)</strong>：<code>0x0</code><ul><li>物理意义：由于是可重定位文件，尚未装载，因此物理入口地址为 0。</li></ul></li><li><strong>Start of program headers (程序头起点)</strong>：<code>0 (bytes into file)</code><ul><li>物理意义：目标文件中通常不包含程序头表（Program Header Table），该表仅在可执行文件中存在。</li></ul></li><li><strong>Start of section headers (段表起点)</strong>：<code>280 (bytes into file)</code><ul><li>物理意义：**段表（Section Header Table）**在文件内部的物理偏移地址。</li></ul></li><li><strong>Size of this header (ELF 头大小)</strong>：<code>52 (bytes)</code><ul><li>物理意义：ELF Header 本身物理占据的字节长度。</li></ul></li><li><strong>Size of section headers (单段描述符大小)</strong>：<code>40 (bytes)</code><ul><li>物理意义：段表中每个条目物理占据的空间。</li></ul></li><li><strong>Number of section headers (段的数量)</strong>：<code>11</code><ul><li>物理意义：该文件物理包含 11 个段（如 <code>.text</code>, <code>.data</code>, <code>.bss</code> 等）。</li></ul></li><li><strong>Section header string table index (段表字符串表索引)</strong>：<code>8</code><ul><li>物理意义：存储段名字符串的表在段表中的物理下标。</li></ul></li></ul><p>事实上,这些信息是以C语言的结构体存储的:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> e_ident[<span class="number">16</span>];   <span class="comment">// 物理魔数区（含架构、字节序信息）</span></span><br><span class="line">    Elf32_Half    e_type;        <span class="comment">// 物理文件类型</span></span><br><span class="line">    Elf32_Half    e_machine;     <span class="comment">// 物理硬件平台</span></span><br><span class="line">    Elf32_Word    e_version;     <span class="comment">// 物理版本</span></span><br><span class="line">    Elf32_Addr    e_entry;       <span class="comment">// 物理程序入口地址</span></span><br><span class="line">    Elf32_Off     e_phoff;       <span class="comment">// 程序头表物理偏移</span></span><br><span class="line">    Elf32_Off     e_shoff;       <span class="comment">// 段表物理偏移</span></span><br><span class="line">    Elf32_Word    e_flags;       <span class="comment">// 处理器标志位</span></span><br><span class="line">    Elf32_Half    e_ehsize;      <span class="comment">// ELF 头物理大小</span></span><br><span class="line">    Elf32_Half    e_phentsize;   <span class="comment">// 单个程序头描述符大小</span></span><br><span class="line">    Elf32_Half    e_phnum;       <span class="comment">// 程序头描述符数量</span></span><br><span class="line">    Elf32_Half    e_shentsize;   <span class="comment">// 单个段表描述符大小</span></span><br><span class="line">    Elf32_Half    e_shnum;       <span class="comment">// 段表描述符数量</span></span><br><span class="line">    Elf32_Half    e_shstrndx;    <span class="comment">// 段名字符串表所在段的索引</span></span><br><span class="line">&#125; Elf32_Ehdr;</span><br></pre></td></tr></table></figure><blockquote><p>在 ELF 文件格式定义中，为了屏蔽不同平台下 <code>int</code> 或 <code>long</code> 长度不一带来的物理对齐问题，官方定义了一套标准数据类型：</p></blockquote><table><thead><tr><th style="text-align:left">自定义类型</th><th style="text-align:left">描述</th><th style="text-align:left">原始类型</th><th style="text-align:left">长度（字节）</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Elf32_Addr</strong></td><td style="text-align:left">32 位版本程序地址</td><td style="text-align:left"><code>uint32_t</code></td><td style="text-align:left">4</td></tr><tr><td style="text-align:left"><strong>Elf32_Half</strong></td><td style="text-align:left">32 位版本无符号短整型</td><td style="text-align:left"><code>uint16_t</code></td><td style="text-align:left">2</td></tr><tr><td style="text-align:left"><strong>Elf32_Off</strong></td><td style="text-align:left">32 位版本偏移地址</td><td style="text-align:left"><code>uint32_t</code></td><td style="text-align:left">4</td></tr><tr><td style="text-align:left"><strong>Elf32_Sword</strong></td><td style="text-align:left">32 位版本有符号整型</td><td style="text-align:left"><code>int32_t</code></td><td style="text-align:left">4</td></tr><tr><td style="text-align:left"><strong>Elf32_Word</strong></td><td style="text-align:left">32 位版本无符号整型</td><td style="text-align:left"><code>uint32_t</code></td><td style="text-align:left">4</td></tr><tr><td style="text-align:left"><strong>Elf64_Addr</strong></td><td style="text-align:left">64 位版本程序地址</td><td style="text-align:left"><code>uint64_t</code></td><td style="text-align:left">8</td></tr><tr><td style="text-align:left"><strong>Elf64_Half</strong></td><td style="text-align:left">64 位版本无符号短整型</td><td style="text-align:left"><code>uint16_t</code></td><td style="text-align:left">2</td></tr><tr><td style="text-align:left"><strong>Elf64_Off</strong></td><td style="text-align:left">64 位版本偏移地址</td><td style="text-align:left"><code>uint64_t</code></td><td style="text-align:left">8</td></tr><tr><td style="text-align:left"><strong>Elf64_Sword</strong></td><td style="text-align:left">64 位版本有符号整型</td><td style="text-align:left"><code>int32_t</code></td><td style="text-align:left">4</td></tr><tr><td style="text-align:left"><strong>Elf64_Word</strong></td><td style="text-align:left">64 位版本无符号整型</td><td style="text-align:left"><code>uint32_t</code></td><td style="text-align:left">4</td></tr></tbody></table><p>带上示例来解释:</p><table><thead><tr><th style="text-align:left">成员</th><th style="text-align:left">内容</th><th style="text-align:left">物理/逻辑解释</th></tr></thead><tbody><tr><td style="text-align:left"><strong>e_ident</strong></td><td style="text-align:left"><strong>Magic</strong>: 7f 45 4c 46 01 01 01 00…</td><td style="text-align:left"><strong>ELF 魔数</strong>。包含文件机器字节长度、数据存储方式、版本、运行平台及 ABI 版本。</td></tr><tr><td style="text-align:left"><strong>e_type</strong></td><td style="text-align:left"><strong>Type</strong>: REL (Relocatable file)</td><td style="text-align:left"><strong>ELF 文件类型</strong>。标识是可重定位文件、可执行文件还是共享对象文件。</td></tr><tr><td style="text-align:left"><strong>e_machine</strong></td><td style="text-align:left"><strong>Machine</strong>: Intel 80386</td><td style="text-align:left"><strong>CPU 平台属性</strong>。相关常量以 <code>EM_</code> 开头（如 EM_386）。</td></tr><tr><td style="text-align:left"><strong>e_version</strong></td><td style="text-align:left"><strong>Version</strong>: 0x1</td><td style="text-align:left"><strong>ELF 版本号</strong>。通常为常数 1。</td></tr><tr><td style="text-align:left"><strong>e_entry</strong></td><td style="text-align:left"><strong>Entry point address</strong>: 0x0</td><td style="text-align:left"><strong>入口地址</strong>。规定程序开始执行的虚拟地址。可重定位文件（.o）通常为 0。</td></tr><tr><td style="text-align:left"><strong>e_phoff</strong></td><td style="text-align:left"><strong>Start of program headers</strong>: 0</td><td style="text-align:left"><strong>程序头（Program Header）偏移</strong>。在链接视图中暂不关心，执行视图的核心。</td></tr><tr><td style="text-align:left"><strong>e_shoff</strong></td><td style="text-align:left"><strong>Start of section headers</strong>: 280</td><td style="text-align:left"><strong>段表（Section Header）偏移</strong>。即段表在文件内的起始物理位置。</td></tr><tr><td style="text-align:left"><strong>e_word</strong></td><td style="text-align:left"><strong>Flags</strong>: 0x0</td><td style="text-align:left"><strong>ELF 标志位</strong>。标识特定平台相关的属性，格式通常为 <code>EF_machine_flag</code>。</td></tr><tr><td style="text-align:left"><strong>e_ehsize</strong></td><td style="text-align:left"><strong>Size of this header</strong>: 52 (bytes)</td><td style="text-align:left"><strong>ELF 文件头本身的大小</strong>。在本例中物理占据 52 字节。</td></tr><tr><td style="text-align:left"><strong>e_phentsize</strong></td><td style="text-align:left"><strong>Size of program headers</strong>: 0</td><td style="text-align:left"><strong>程序头描述符的大小</strong>。</td></tr><tr><td style="text-align:left"><strong>e_phnum</strong></td><td style="text-align:left"><strong>Number of program headers</strong>: 0</td><td style="text-align:left"><strong>程序头描述符的数量</strong>。</td></tr><tr><td style="text-align:left"><strong>e_shentsize</strong></td><td style="text-align:left"><strong>Size of section headers</strong>: 40 (bytes)</td><td style="text-align:left"><strong>段表描述符的大小</strong>。物理上等于 <code>sizeof(Elf32_Shdr)</code>。</td></tr><tr><td style="text-align:left"><strong>e_shnum</strong></td><td style="text-align:left"><strong>Number of section headers</strong>: 11</td><td style="text-align:left"><strong>段的数量</strong>。物理记录了 ELF 文件中拥有的段表描述符总数。</td></tr><tr><td style="text-align:left"><strong>e_shstrndx</strong></td><td style="text-align:left"><strong>Section header string table index</strong>: 8</td><td style="text-align:left"><strong>段表字符串表下标</strong>。存储段名字符串的表在段表中的物理索引位置。</td></tr></tbody></table><h5 id="魔数详解">魔数详解</h5><p>e_ident成员的前四个字节<code>7f 45 4c 46</code>中,第一个字节对应的是ASCII中的DEL控制符,后三个字节刚好是ELF这三个字母的ASCII码,从而唯一标识了ELF文件,故被称为<strong>ELF文件的魔数</strong>.</p><blockquote><p>接下来的一个字节用来标识ELF的文件类,<code>01</code>表示是32位的,<code>02</code>表示是64位的;第六个字节是字节序,规定该文件是大端存储还是小端存储;第七个字节为该文件的主版本号,一般是1,因为ELF标准从1.2版后就没有更新过.后面的9个字节ELF标准没有定义,一般填0.<br>至于为什么要多出来这9个字节,主要是为了兼容老编译器的考量.</p></blockquote><p>自然,所有的可执行文件都有一个<strong>魔数</strong>用来标识自己,比如PE/COFF文件的最开始两个字节为<code>4d,5a</code>,即ASCII字符MZ.</p><h4 id="段表">段表</h4><blockquote><p>段表用于保存ELF文件中各个section(段)的基本信息,比如段的名字,长度,存储位置,读写权限等属性.编译器,链接器和装载器都是依靠段表来定位和访问各个段的属性的,至于段表的位置则是由文件头中的<code>e_shoff</code>字段来定义的.</p></blockquote><ul><li>尽管书上讲的很详细,但我想只需要大概知道段表的作用即可</li></ul><h4 id="重定位表">重定位表</h4><p>链接器在处理目标文件的时候,需要对目标文件中的某些部分进行重定位,即.text段和.data段中对绝对地址引用的位置.</p><p>对于每个要重定位的.text段和.data段,都会有一个相应的重定位表.</p><h3 id="链接的接口-symbol">链接的接口: symbol</h3><h1><a href="https://ddia.vonng.com/v1/">设计数据密集型应用</a></h1><h2 id="数据系统">数据系统</h2><p>数据系统有以下几个作用:</p><ul><li>存储数据，以便自己或其他应用程序之后能再次找到 （数据库，即 databases）</li><li>记住开销昂贵操作的结果，加快读取速度（缓存，即 caches）</li><li>允许用户按关键字搜索数据，或以各种方式对数据进行过滤（搜索索引，即 search indexes）</li><li>向其他进程发送消息，进行异步处理（流处理，即 stream processing）</li><li>定期处理累积的大批量数据（批处理，即 batch processing）<br>因此,常规的数据库,消息队列等信息处理系统都可以被归类为数据系统.</li></ul><p>我们可以从三个维度来评价一个数据系统写的怎么样:</p><ol><li>可靠性: 出了故障仍然可以正常运行</li><li>可伸缩性: 能够应付系统的扩大和其他变化</li><li>可维护性: 架构清晰,职责分明,方便维护</li></ol><h3 id="可靠性">可靠性</h3><h4 id="处理硬件故障">处理硬件故障</h4><blockquote><p>当想到系统失效的原因时，硬件故障（hardware faults） 总会第一个进入脑海。硬盘崩溃、内存出错、机房断电、有人拔错网线…… 任何与大型数据中心打过交道的人都会告诉你：一旦你拥有很多机器，这些事情总会发生！</p></blockquote><p>我们可以通过<strong>硬件冗余</strong>(redundancy of hardware)来解决这个问题,即提供后备组件来及时接替故障硬件,防止系统崩溃.</p><h4 id="软件故障">软件故障</h4><p>软件故障有以下几个例子:</p><ul><li>接受特定的错误输入，便导致所有应用服务器实例崩溃的 BUG。例如 2012 年 6 月 30 日的闰秒，由于 Linux 内核中的一个错误，许多应用同时挂掉了。</li><li>级联故障，一个组件中的小故障触发另一个组件中的故障，进而触发更多的故障</li></ul><h4 id="管理员的失误导致的故障">管理员的失误导致的故障</h4><blockquote><p>一项关于大型互联网服务的研究发现，运维配置错误是导致服务中断的首要原因，而硬件故障（服务器或网络）仅导致了 10-25% 的服务中断</p></blockquote><h3 id="可伸缩性">可伸缩性</h3><blockquote><p>系统今天能可靠运行，并不意味未来也能可靠运行。服务 降级（degradation） 的一个常见原因是负载增加，例如：系统负载已经从一万个并发用户增长到十万个并发用户，或者从一百万增长到一千万。也许现在处理的数据量级要比过去大得多</p></blockquote><h4 id="负载-以推特为例">负载: 以推特为例</h4><p>以推特在 2012 年 11 月发布的数据为例,推特的两个主要业务是：</p><ul><li>发布推文<ul><li>用户可以向其粉丝发布新消息（平均 4.6k 请求 / 秒，峰值超过 12k 请求 / 秒）。</li></ul></li><li>主页时间线<ul><li>用户可以查阅他们关注的人发布的推文（300k 请求 / 秒）。</li></ul></li></ul><p>大体上讲，这一对操作有两种实现方式。</p><ol><li>发布推文时，只需将新推文插入全局推文集合即可。当一个用户请求自己的主页时间线时，首先查找他关注的所有人，查询这些被关注用户发布的推文并按时间顺序合并。在如 图 1-2 所示的关系型数据库中，可以编写这样的查询：</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> tweets.<span class="operator">*</span>, users.<span class="operator">*</span></span><br><span class="line">  <span class="keyword">FROM</span> tweets</span><br><span class="line">  <span class="keyword">JOIN</span> users   <span class="keyword">ON</span> tweets.sender_id <span class="operator">=</span> users.id</span><br><span class="line">  <span class="keyword">JOIN</span> follows <span class="keyword">ON</span> follows.followee_id <span class="operator">=</span> users.id</span><br><span class="line">  <span class="keyword">WHERE</span> follows.follower_id <span class="operator">=</span> <span class="built_in">current_user</span></span><br></pre></td></tr></table></figure><ol start="2"><li>为每个用户的主页时间线维护一个缓存，就像每个用户的推文收件箱。当一个用户发布推文时，查找所有关注该用户的人，并将新的推文插入到每个主页时间线缓存中。因此读取主页时间线的请求开销很小，因为结果已经提前计算好了。</li></ol><blockquote><p>推特的第一个版本使用了方法 1，但系统很难跟上主页时间线查询的负载。所以公司转向了方法 2，方法 2 的效果更好，因为发推频率比查询主页时间线的频率几乎低了两个数量级，所以在这种情况下，最好在写入时做更多的工作，而在读取时做更少的工作。</p><p>然而方法 2 的缺点是，发推现在需要大量的额外工作。平均来说，一条推文会发往约 75 个关注者，所以每秒 4.6k 的发推写入，变成了对主页时间线缓存每秒 345k 的写入。但这个平均值隐藏了用户粉丝数差异巨大这一现实，一些用户有超过 3000 万的粉丝，这意味着一条推文就可能会导致主页时间线缓存的 3000 万次写入！及时完成这种操作是一个巨大的挑战 —— 推特尝试在 5 秒内向粉丝发送推文。</p></blockquote><blockquote><p>推特轶事的最终转折：现在已经稳健地实现了方法 2，推特逐步转向了两种方法的混合。大多数用户发的推文会写入其粉丝主页时间线缓存中。但是少数拥有海量粉丝的用户（即名流）会被排除在外。当用户读取主页时间线时，分别地获取出该用户所关注的每位名流的推文，再与用户的主页时间线缓存合并</p></blockquote><h5 id="如何处理负载">如何处理负载</h5><blockquote><p>适应某个级别负载的架构不太可能应付 10 倍于此的负载。如果你正在开发一个快速增长的服务，那么每次负载发生数量级的增长时，你可能都需要重新考虑架构 —— 或者更频繁。</p></blockquote><blockquote><p>大规模的系统架构通常是应用特定的 —— 没有一招鲜吃遍天的通用可伸缩架构（不正式的叫法：万金油（magic scaling sauce） ）。应用的问题可能是读取量、写入量、要存储的数据量、数据的复杂度、响应时间要求、访问模式或者所有问题的大杂烩。</p><p>举个例子，用于处理每秒十万个请求（每个大小为 1 kB）的系统与用于处理每分钟 3 个请求（每个大小为 2GB）的系统看上去会非常不一样，尽管两个系统有同样的数据吞吐量。</p></blockquote><h3 id="可维护性">可维护性</h3><p>众所周知，软件的大部分开销并不在最初的开发阶段，而是在持续的维护阶段，包括修复漏洞、保持系统正常运行、调查失效、适配新的平台、为新的场景进行修改、偿还技术债和添加新的功能。</p><h2 id="数据模型">数据模型</h2><p>多数应用使用层层叠加的数据模型构建。对于每层数据模型的关键问题是：它是如何用低一层数据模型来 表示 的？例如：</p><ol><li>作为一名应用开发人员，你观察现实世界（里面有人员、组织、货物、行为、资金流向、传感器等），并采用对象或数据结构，以及操控那些数据结构的 API 来进行建模。那些结构通常是特定于应用程序的。</li><li>当要存储那些数据结构时，你可以利用通用数据模型来表示它们，如 JSON 或 XML 文档、关系数据库中的表或图模型。</li><li>数据库软件的工程师选定如何以内存、磁盘或网络上的字节来表示 JSON / XML/ 关系 / 图数据。这类表示形式使数据有可能以各种方式来查询，搜索，操纵和处理。</li><li>在更低的层次上，硬件工程师已经想出了使用电流、光脉冲、磁场或者其他东西来表示字节的方法。</li></ol><blockquote><p>握一个数据模型需要花费很多精力（想想关系数据建模有多少本书）。即便只使用一个数据模型，不用操心其内部工作机制，构建软件也是非常困难的。然而，因为数据模型对上层软件的功能（能做什么，不能做什么）有着至深的影响，所以选择一个适合的数据模型是非常重要的。</p></blockquote><h3 id="关系模型VS文档模型">关系模型VS文档模型</h3><blockquote><p>关系模型曾是一个理论性的提议，当时很多人都怀疑是否能够有效实现它。然而到了 20 世纪 80 年代中期，关系数据库管理系统（RDBMSes）和 SQL 已成为大多数人们存储和查询某些常规结构的数据的首选工具。关系数据库已经持续称霸了大约 25~30 年 —— 这对计算机史来说是极其漫长的时间。</p><p>关系数据库起源于商业数据处理，在 20 世纪 60 年代和 70 年代用大型计算机来执行。从今天的角度来看，那些用例显得很平常：典型的 事务处理（将销售或银行交易，航空公司预订，库存管理信息记录在库）和 批处理（客户发票，工资单，报告）。</p></blockquote><h4 id="NoSQL">NoSQL</h4><p>采用 NoSQL 数据库的背后有几个驱动因素，其中包括：</p><ol><li>需要比关系数据库更好的可伸缩性，包括非常大的数据集或非常高的写入吞吐量</li><li>相比商业数据库产品，免费和开源软件更受偏爱</li><li>关系模型不能很好地支持一些特殊的查询操作</li></ol><p>常见的NoSQL数据库有以下几个:</p><ol><li>Redis: 基于内存的存储,核心逻辑是哈希表</li><li>MongoDB: 存储格式为JSON</li></ol><h4 id="文档和关系数据库的融合">文档和关系数据库的融合</h4><blockquote><p>随着时间的推移，关系数据库和文档数据库似乎变得越来越相似，这是一件好事：数据模型相互补充，如果一个数据库能够处理类似文档的数据，并能够对其执行关系查询，那么应用程序就可以使用最符合其需求的功能组合。</p></blockquote><ul><li>(26/4/7): 我发现这本书我现在看太早了,很难有切实的收获,还是等几年再来探索吧</li></ul><h1><a href="https://yeasy.gitbook.io/docker_practice">Docker 从入门到实践</a></h1><ul><li>比官方文档要简洁清晰的多</li></ul><h1>入门部分</h1><h2 id="Docker简介">Docker简介</h2><blockquote><p>无论你的应用是用 Python、Java、Node.js 还是其他语言写的，无论它需要什么样的依赖库和环境，<strong>一旦被打包成 Docker 镜像</strong>，就可以用同样的方式在任何支持 Docker 的机器上运行.</p></blockquote><ul><li>也就是说,通过将依赖和软件打包在一起,我们成功实现了无缝的跨环境运行</li></ul><h3 id="Docker不是虚拟机">Docker不是虚拟机</h3><blockquote><p>传统虚拟机技术是虚拟出一套完整的硬件，在其上运行一个完整的操作系统，再在该系统上运行应用</p><p>而 Docker 容器内的应用直接运行于宿主的内核，容器内没有自己的内核，也没有进行硬件虚拟</p></blockquote><h3 id="Docker历史">Docker历史</h3><blockquote><p>Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目，于 2013 年 3 月以 Apache 2.0 授权协议开源</p></blockquote><ul><li>很难想象这么优秀的技术竟然只有十年多一点的历史</li></ul><h3 id="Docker的核心优势">Docker的核心优势</h3><p><strong>环境一致性</strong></p><blockquote><p>Docker 镜像包含了应用运行所需的 一切：代码、运行时、系统工具、库、配置。这意味着:</p></blockquote><ol><li>开发环境和生产环境完全一致</li><li>不会再有 “在我机器上能跑” 的问题</li></ol><p><strong>快速启动</strong></p><blockquote><p>传统虚拟机启动需要几分钟 (引导操作系统)，而 Docker 容器启动通常只需要 几秒甚至几百毫秒</p></blockquote><ul><li>当然这得要你先构建好了镜像和容器</li></ul><blockquote><p>Docker 的核心价值可以用一句话概括：让应用的开发、测试、部署保持一致，同时极大提高资源利用效率。 笔者认为，对于现代软件开发者来说，Docker 已经不是 “要不要学” 的问题，而是 必备技能。无论你是前端、后端、运维还是全栈开发者，掌握 Docker 都能让你的工作更高效。</p></blockquote><h2 id="基本概念">基本概念</h2><p>Docker里有三个基本概念:</p><ol><li>镜像(Image): Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数 (如匿名卷、环境变量、用户等)。镜像不包含任何动态数据，其内容在构建之后也不会被改变。</li><li>容器 (Container)：镜像 (Image) 和容器 (Container) 的关系，就像是面向对象程序设计中的 类 和 实例 一样，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。</li><li>仓库 (Repository)：镜像构建完成后，可以很容易的在当前宿主机上运行，但是，如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，Docker Registry 就是这样的服务。</li></ol><h3 id="镜像">镜像</h3><blockquote><p>Docker 镜像是一个只读的模板，包含了运行应用所需的一切：代码、运行时、库、环境变量和配置文件。 如果用一个类比：镜像就像是一张光盘或 ISO 文件。你可以用同一张光盘在不同电脑上安装系统，而光盘本身不会被修改。同样，一个镜像可以创建多个容器，而镜像本身保持不变。</p></blockquote><h4 id="镜像的组成部分">镜像的组成部分</h4><table><thead><tr><th style="text-align:left">类别</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left">程序文件</td><td style="text-align:left">应用二进制文件、Python/Node 解释器</td></tr><tr><td style="text-align:left">库文件</td><td style="text-align:left">libc、OpenSSL、各种依赖库</td></tr><tr><td style="text-align:left">配置文件</td><td style="text-align:left">nginx.conf、my.cnf 等</td></tr><tr><td style="text-align:left">环境变量</td><td style="text-align:left">PATH、LANG 等预设值</td></tr><tr><td style="text-align:left">元数据</td><td style="text-align:left">启动命令、暴露端口、数据卷定义</td></tr></tbody></table><ul><li>镜像是只读的</li><li>镜像不包含动态数据</li><li>镜像构建后<strong>内容不会改变</strong></li></ul><h4 id="镜像的分层存储">镜像的分层存储</h4><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> ubuntu:<span class="number">24.04</span>          </span><br><span class="line"><span class="comment"># 第 1 层：基础系统（约 78MB）</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update         </span></span><br><span class="line"><span class="comment"># 第 2 层：更新包索引</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get install nginx  </span></span><br><span class="line"><span class="comment"># 第 3 层：安装 nginx</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> app.conf /etc/nginx/  </span></span><br><span class="line"><span class="comment"># 第 4 层：复制配置文件</span></span><br></pre></td></tr></table></figure><p>换句话说,只要某一行命令对镜像做了修改,就被docker视为单独的一个构建层,不可以被其他构建层修改,但可以与其他镜像共享</p><h4 id="镜像标识">镜像标识</h4><p><strong>镜像名称和标签</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 完整格式</span></span><br><span class="line"></span><br><span class="line">registry.example.com/myproject/myapp:v1.<span class="number">2.3</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 简写（使用 Docker Hub）</span></span><br><span class="line"></span><br><span class="line">nginx:<span class="number">1.25</span></span><br><span class="line">ubuntu:<span class="number">24.04</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 省略标签（默认使用 latest）</span></span><br><span class="line"></span><br><span class="line">nginx  </span><br><span class="line"><span class="comment"># 等同于 nginx:latest</span></span><br></pre></td></tr></table></figure><p><strong>镜像ID</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker images</span><br><span class="line">REPOSITORY   TAG       IMAGE ID       CREATED        SIZE</span><br><span class="line">nginx        latest    a6bd71f48f68   2 weeks ago    187MB</span><br><span class="line">ubuntu       24.04     ca2b0f26964c   3 weeks ago    78.1MB</span><br></pre></td></tr></table></figure><p><strong>镜像摘要</strong><br>镜像摘要是基于镜像内容生成的哈希码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker images --digests</span><br><span class="line">REPOSITORY  TAG     DIGEST                                                                    IMAGE ID</span><br><span class="line">nginx       latest  sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68</span><br></pre></td></tr></table></figure><h3 id="容器">容器</h3><blockquote><p>容器是镜像的运行实例。如果把镜像比作程序，那么容器就是进程。 用面向对象编程的术语来说：镜像是类 (Class)，容器是对象 (Instance)。</p></blockquote><h4 id="容器的本质">容器的本质</h4><blockquote><p>笔者认为，理解这一点是理解 Docker 的关键：容器的本质是一个特殊的进程<br><img src="/images/2026-04-03/PixPin_2026-04-04_11-48-15.webp" alt="alt text"></p></blockquote><p>这种隔离是通过 Linux 内核的 Namespace 技术实现的。具体表现为：</p><ul><li>进程空间：容器看不到宿主机上的其他进程。</li><li>网络：容器拥有独立的 IP、端口等网络资源</li><li>文件系统：容器拥有独立的 root 目录。</li><li>用户：容器内的 root 用户不等于宿主机的 root 用户。</li></ul><h4 id="容器的存储层机制">容器的存储层机制</h4><p>当容器运行时，Docker 会在镜像的只读层之上创建一个可写层(容器存储层);</p><p>而当容器需要修改镜像层中的文件时:</p><ol><li>Docker将该文件<strong>复制</strong>到容器存储层</li><li>在容器存储层中进行修改</li><li>原始镜像层<strong>保持不变</strong></li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 创建容器，写入数据</span></span><br><span class="line"></span><br><span class="line">$ docker run -it ubuntu bash</span><br><span class="line">root@abc123:/# <span class="built_in">echo</span> <span class="string">&quot;important data&quot;</span> &gt; /data.txt</span><br><span class="line">root@abc123:/# <span class="built_in">exit</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 删除容器</span></span><br><span class="line"></span><br><span class="line">$ docker <span class="built_in">rm</span> abc123</span><br><span class="line"></span><br><span class="line"><span class="comment">## 数据丢了！没有任何办法恢复！</span></span><br></pre></td></tr></table></figure><p>既然当容器被删除后数据就全部丢失,那么容器存储层就不应该保留任何重要的信息,而是只保留运行时数据.</p><ul><li>如果我们想要存储数据,可以使用数据卷(Volume)来存储数据库和应用数据,或者使用绑定到宿主机的目录.</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 使用数据卷（推荐）</span></span><br><span class="line"></span><br><span class="line">$ docker run -v mydata:/var/lib/mysql mysql</span><br><span class="line"></span><br><span class="line"><span class="comment">## 使用绑定挂载</span></span><br><span class="line"></span><br><span class="line">$ docker run -v /host/path:/container/path nginx</span><br><span class="line"><span class="comment"># 这些位置的读写会跳过容器存储层，直接写入宿主机，性能更好，也不会随容器删除而丢失</span></span><br></pre></td></tr></table></figure><h4 id="容器的生命周期">容器的生命周期</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 创建并启动容器（最常用）</span></span><br><span class="line"></span><br><span class="line">$ docker run nginx</span><br><span class="line"></span><br><span class="line"><span class="comment">## 分步操作</span></span><br><span class="line"></span><br><span class="line">$ docker create nginx    <span class="comment"># 创建容器（不启动）</span></span><br><span class="line">$ docker start abc123    <span class="comment"># 启动容器</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 停止容器</span></span><br><span class="line"></span><br><span class="line">$ docker stop abc123     <span class="comment"># 优雅停止（发送 SIGTERM，等待后发送 SIGKILL）</span></span><br><span class="line">$ docker <span class="built_in">kill</span> abc123     <span class="comment"># 强制停止（直接发送 SIGKILL）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 暂停/恢复（不常用，但有时有用）</span></span><br><span class="line"></span><br><span class="line">$ docker pause abc123    <span class="comment"># 暂停容器内所有进程</span></span><br><span class="line">$ docker unpause abc123  <span class="comment"># 恢复</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 删除容器</span></span><br><span class="line"></span><br><span class="line">$ docker <span class="built_in">rm</span> abc123       <span class="comment"># 删除已停止的容器</span></span><br><span class="line">$ docker <span class="built_in">rm</span> -f abc123    <span class="comment"># 强制删除运行中的容器</span></span><br></pre></td></tr></table></figure><h3 id="仓库">仓库</h3><blockquote><p><strong>Docker Registry</strong> 是存储和分发 Docker 镜像的服务，类似于代码的 GitHub 或包管理的 npm。</p></blockquote><p>Docker Registry 中可以包含多个 Repository，每个 Repository 可以包含多个 Tag:</p><table><thead><tr><th style="text-align:left">概念</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Registry</strong></td><td style="text-align:left">存储镜像的服务</td><td style="text-align:left">Docker Hub、<a href="http://ghcr.io">ghcr.io</a></td></tr><tr><td style="text-align:left"><strong>Repository (仓库)</strong></td><td style="text-align:left">同一软件的镜像集合</td><td style="text-align:left">nginx、mysql、mycompany/myapp</td></tr><tr><td style="text-align:left"><strong>Tag (标签)</strong></td><td style="text-align:left">仓库内的版本标识</td><td style="text-align:left">latest、1.25、alpine</td></tr></tbody></table><p>一个完整的 Docker 镜像名称由 Registry 地址、用户名/组织名、仓库名和标签组成。了解其结构有助于我们更准确地定位镜像。基本格式如下：<br><code>[registry 地址/][用户名/]仓库名[:标签]</code><br>完整示例如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">registry.example.com/mycompany/myapp:v1.2.3</span><br><span class="line">│                    │         │     │</span><br><span class="line">│                    │         │     └── 标签</span><br><span class="line">│                    │         └── 仓库名</span><br><span class="line">│                    └── 用户名/组织名</span><br><span class="line">└── Registry 地址</span><br><span class="line"></span><br><span class="line"><span class="comment">## Docker Hub 官方镜像（省略 registry 和用户名）</span></span><br><span class="line"></span><br><span class="line">nginx:1.25</span><br><span class="line">ubuntu:24.04</span><br><span class="line"></span><br><span class="line"><span class="comment">## Docker Hub 用户镜像</span></span><br><span class="line"></span><br><span class="line">jwilder/nginx-proxy:latest</span><br><span class="line"></span><br><span class="line"><span class="comment">## 其他 Registry</span></span><br><span class="line"></span><br><span class="line">ghcr.io/username/myapp:v1.0</span><br><span class="line">gcr.io/google-containers/pause:3.10</span><br></pre></td></tr></table></figure><h4 id="公共-Registry">公共 Registry</h4><p>Docker Hub 是最大的公共 Registry，也是 Docker 的默认 Registry,有以下特点:</p><ol><li>拥有大量官方镜像(nginx、mysql、redis)</li><li>免费账户可以创建公开仓库</li><li>付费账户支持私有仓库</li></ol><p>除了 Docker Hub，还有以下几个常见的公共 Registry：</p><table><thead><tr><th style="text-align:left">Registry</th><th style="text-align:left">地址</th><th style="text-align:left">说明</th></tr></thead><tbody><tr><td style="text-align:left"><strong>GitHub Container Registry</strong></td><td style="text-align:left"><a href="http://ghcr.io">ghcr.io</a></td><td style="text-align:left">GitHub 提供，与 GitHub Actions 集成好</td></tr><tr><td style="text-align:left"><strong>Google Container Registry</strong></td><td style="text-align:left"><a href="http://gcr.io">gcr.io</a></td><td style="text-align:left">Google Cloud 提供，Kubernetes 镜像常用</td></tr><tr><td style="text-align:left"><strong><a href="http://Quay.io">Quay.io</a></strong></td><td style="text-align:left"><a href="http://quay.io">quay.io</a></td><td style="text-align:left">Red Hat 提供</td></tr><tr><td style="text-align:left"><strong>阿里云容器镜像服务</strong></td><td style="text-align:left">registry.cn-*.aliyuncs.com</td><td style="text-align:left">国内访问快</td></tr><tr><td style="text-align:left"><strong>腾讯云容器镜像服务</strong></td><td style="text-align:left"><a href="http://ccr.ccs.tencentyun.com">ccr.ccs.tencentyun.com</a></td><td style="text-align:left">国内访问快</td></tr></tbody></table><h2 id="安装docker">安装docker</h2><p>如果是Windows端,开启WSL2后下载官方软件即可运行<br>如果是Linux端,尽管文章里给了一堆命令,但现在有<a href="https://docs.dokploy.com/docs/core/installation">Dokploy</a>了.如果是部署在国外服务器上或者自己学习使用的话,使用Dokploy就没必要操心那么多了.</p><h3 id="Dokploy">Dokploy</h3><p>支持Dokploy的目前有以下服务器厂商:</p><ul><li>Hostinger</li><li>AmericanCloud</li><li>Teramont</li><li>Hetzner</li><li>DigitalOcean</li><li>Vultr</li><li>Linode</li><li>Scaleway</li><li>Google Cloud</li><li>AWS<br>而Dokploy目前可以在以下系统里部署:</li><li>Ubuntu 24.04 LTS</li><li>Ubuntu 23.10</li><li>Ubuntu 22.04 LTS</li><li>Ubuntu 20.04 LTS</li><li>Ubuntu 18.04 LTS</li><li>Debian 12</li><li>Debian 11</li><li>Debian 10</li><li>Fedora 40</li><li>Centos 9</li><li>Centos 8</li></ul><p>换句话说,主流的Linux操作系统现在都支持Dokploy了</p><blockquote><p>Dokploy is a stable, easy-to-use deployment solution designed to simplify the application management process. Think of Dokploy as your free self hostable alternative to platforms like Heroku, Vercel, and Netlify, <strong>leveraging the robustness of Docker and the flexibility of Traefik</strong>.</p></blockquote><ul><li>Dokploy本身就是为了简化在Linux服务器部署Docker而产生的</li></ul><blockquote><p>Dokploy utilizes Docker, so it is essential to have Docker installed on your server. If Docker is not already installed, Dokploy’s installation script will install it <strong>automatically</strong>.</p></blockquote><ul><li>甚至都不用提前安装docker,易用性可见一斑</li></ul><h4 id="部署">部署</h4><p><strong>前置要求</strong></p><blockquote><p>To ensure a smooth experience with Dokploy, your server should have <strong>at least 2GB of RAM and 30GB of disk space</strong>. This specification helps to handle the resources consumed by Docker during builds and prevents system freezes.</p></blockquote><ul><li>官方推荐使用Hetzner的服务器来省钱</li></ul><p>避免以下端口被占用:</p><ol><li>Port 80: HTTP traffic (used by Traefik)</li><li>Port 443: HTTPS traffic (used by Traefik)</li><li>Port 3000: Dokploy web interface</li></ol><p>我们只需要运行以下命令便可以在服务器的3000端口访问dokploy界面:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -sSL https://dokploy.com/install.sh | sh</span><br></pre></td></tr></table></figure><p>第一次进入dokploy界面时需要我们注册管理员账户,然后就可以在面板里部署自己的docker项目了,要进一步了解的话还是去看官方文档吧.</p><h2 id="使用镜像">使用镜像</h2><h3 id="获取镜像">获取镜像</h3><p>docker pull用于从镜像仓库获取镜像:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull [选项] [Registry地址/]仓库名[:标签]</span><br></pre></td></tr></table></figure><p>镜像名称的标准格式如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker.io / library / ubuntu : 24.04</span><br><span class="line">────┬────   ───┬───   ──┬───   ──┬──</span><br><span class="line">    │         │        │        │</span><br><span class="line">Registry地址  用户名    仓库名    标签</span><br><span class="line"> (可省略)    (可省略)</span><br></pre></td></tr></table></figure><table><thead><tr><th style="text-align:left">组成部分</th><th style="text-align:left">说明</th><th style="text-align:left">默认值</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Registry 地址</strong></td><td style="text-align:left">镜像仓库服务的域名或 IP 地址</td><td style="text-align:left"><code>docker.io</code> (Docker Hub)</td></tr><tr><td style="text-align:left"><strong>用户名</strong></td><td style="text-align:left">镜像所属的用户、组织或命名空间</td><td style="text-align:left"><code>library</code> (官方镜像默认路径)</td></tr><tr><td style="text-align:left"><strong>仓库名</strong></td><td style="text-align:left">镜像的具体名称</td><td style="text-align:left"><strong>必须指定</strong></td></tr><tr><td style="text-align:left"><strong>标签 (Tag)</strong></td><td style="text-align:left">镜像的版本标识或分类标签</td><td style="text-align:left"><code>latest</code></td></tr></tbody></table><p><strong>示例</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 完整格式</span></span><br><span class="line"></span><br><span class="line">$ docker pull docker.io/library/ubuntu:24.04</span><br><span class="line"></span><br><span class="line"><span class="comment">## 省略 Registry（默认 Docker Hub）</span></span><br><span class="line"></span><br><span class="line">$ docker pull library/ubuntu:24.04</span><br><span class="line"></span><br><span class="line"><span class="comment">## 省略 library（官方镜像）</span></span><br><span class="line"></span><br><span class="line">$ docker pull ubuntu:24.04</span><br><span class="line"></span><br><span class="line"><span class="comment">## 省略标签（默认 latest）</span></span><br><span class="line"></span><br><span class="line">$ docker pull ubuntu</span><br><span class="line"></span><br><span class="line"><span class="comment">## 拉取第三方镜像</span></span><br><span class="line"></span><br><span class="line">$ docker pull bitnami/redis:latest</span><br><span class="line"></span><br><span class="line"><span class="comment">## 从其他 Registry 拉取</span></span><br><span class="line"></span><br><span class="line">$ docker pull ghcr.io/username/myapp:v1.0</span><br></pre></td></tr></table></figure><ul><li>镜像是分层下载的,如果本地已经有相同的层(这可以通过ID来识别),那么就会跳过该层继续下载</li></ul><h4 id="docker-pull常用参数">docker pull常用参数</h4><table><thead><tr><th style="text-align:left">选项</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><strong>–all-tags, -a</strong></td><td style="text-align:left">下载仓库中该镜像的所有版本标签</td><td style="text-align:left"><code>docker pull -a ubuntu</code></td></tr><tr><td style="text-align:left"><strong>–platform</strong></td><td style="text-align:left">在多架构镜像中指定运行平台（如 arm64, amd64）</td><td style="text-align:left"><code>docker pull --platform linux/arm64 nginx</code></td></tr><tr><td style="text-align:left"><strong>–quiet, -q</strong></td><td style="text-align:left">静默模式，只输出镜像 ID，不显示拉取进度详情</td><td style="text-align:left"><code>docker pull -q nginx</code></td></tr></tbody></table><h3 id="管理镜像">管理镜像</h3><h4 id="docker-image-ls">docker image ls</h4><p><strong>基本用法</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ docker image <span class="built_in">ls</span></span><br><span class="line">REPOSITORY   TAG       IMAGE ID       CREATED        SIZE</span><br><span class="line">redis        latest    5f515359c7f8   5 days ago     183MB</span><br><span class="line">nginx        latest    05a60462f8ba   5 days ago     181MB</span><br><span class="line">ubuntu       24.04     329ed837d508   3 days ago     78MB</span><br><span class="line">ubuntu       noble     329ed837d508   3 days ago     78MB</span><br></pre></td></tr></table></figure><table><thead><tr><th style="text-align:left">字段</th><th style="text-align:left">说明</th></tr></thead><tbody><tr><td style="text-align:left"><strong>REPOSITORY</strong></td><td style="text-align:left">镜像仓库名称</td></tr><tr><td style="text-align:left"><strong>TAG</strong></td><td style="text-align:left">镜像的标签（通常代表版本号）</td></tr><tr><td style="text-align:left"><strong>IMAGE ID</strong></td><td style="text-align:left">镜像的唯一标识符（取 SHA-256 哈希值的前 12 位）</td></tr><tr><td style="text-align:left"><strong>CREATED</strong></td><td style="text-align:left">镜像在构建服务器上被创建的时间</td></tr><tr><td style="text-align:left"><strong>SIZE</strong></td><td style="text-align:left">镜像解压后在本地磁盘占用的实际空间</td></tr></tbody></table><ul><li>上面的 ubuntu:24.04 和 ubuntu:noble 拥有相同的 IMAGE ID——它们是同一个镜像的不同标签，只占用一份存储空间。</li></ul><h4 id="查找镜像">查找镜像</h4><p>可以根据名字来找镜像:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 列出所有 ubuntu 镜像</span></span><br><span class="line"></span><br><span class="line">$ docker images ubuntu</span><br><span class="line">REPOSITORY   TAG     IMAGE ID       SIZE</span><br><span class="line">ubuntu       24.04   329ed837d508   78MB</span><br><span class="line">ubuntu       noble   329ed837d508   78MB</span><br><span class="line">ubuntu       22.04   a1b2c3d4e5f6   72MB</span><br></pre></td></tr></table></figure><h4 id="镜像删除">镜像删除</h4><h5 id="docker-rmi-docker-image-rm">docker rmi/docker image rm</h5><p>这两个命令等价,用于删除单个镜像:<br><strong>使用ID删除</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ docker image <span class="built_in">ls</span></span><br><span class="line">REPOSITORY   TAG     IMAGE ID       SIZE</span><br><span class="line">redis        alpine  501ad78535f0   30MB</span><br><span class="line">nginx        latest  e43d811ce2f4   142MB</span><br><span class="line"></span><br><span class="line"><span class="comment">## 只需输入足够区分的前几位</span></span><br><span class="line"></span><br><span class="line">$ docker rmi 501</span><br><span class="line">Untagged: redis:alpine</span><br><span class="line">Deleted: sha256:501ad78535f0...</span><br></pre></td></tr></table></figure><p><strong>使用镜像名删除</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker rmi redis:alpine</span><br><span class="line">Untagged: redis:alpine</span><br><span class="line">Deleted: sha256:501ad78535f0...</span><br></pre></td></tr></table></figure><ul><li>Untagged:移除镜像标签</li><li>Deleted: 删除镜像的存储层</li></ul><h5 id="docker-image-prune">docker image prune</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 查看虚悬镜像</span></span><br><span class="line"></span><br><span class="line">$ docker images -f dangling=<span class="literal">true</span></span><br><span class="line"></span><br><span class="line">$ docker image prune</span><br><span class="line"><span class="comment"># 不带参数,默认只删除悬空镜像</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 不提示确认</span></span><br><span class="line"></span><br><span class="line">$ docker image prune -f</span><br><span class="line"></span><br><span class="line"><span class="comment">## 删除所有没有被容器使用的镜像</span></span><br><span class="line"></span><br><span class="line">$ docker image prune -a</span><br><span class="line"></span><br><span class="line"><span class="comment">## 保留最近 24 小时的</span></span><br><span class="line"></span><br><span class="line">$ docker image prune -a --filter <span class="string">&quot;until=24h&quot;</span></span><br></pre></td></tr></table></figure><ul><li>虚悬镜像 (dangling)：没有标签且未被容器引用的镜像，通常是旧版本被新版本覆盖后产生的</li></ul><h2 id="操作容器">操作容器</h2><h3 id="启动容器">启动容器</h3><blockquote><p>由于 Docker 容器非常轻量，实际使用中常常是随时删除和新建容器，而不是反复重启同一个容器。</p></blockquote><p><strong>基本语法</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run [选项] 镜像 [命令] [参数...]</span><br></pre></td></tr></table></figure><p><strong>基本例子</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker run ubuntu:24.04 /bin/echo <span class="string">&#x27;Hello world&#x27;</span></span><br><span class="line">Hello world</span><br></pre></td></tr></table></figure><p><strong>基础选项</strong></p><table><thead><tr><th style="text-align:left">选项</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left"><code>-d</code></td><td style="text-align:left">后台运行容器（detach）</td><td style="text-align:left"><code>docker run -d nginx</code></td></tr><tr><td style="text-align:left"><code>-it</code></td><td style="text-align:left">分配交互式终端</td><td style="text-align:left"><code>docker run -it ubuntu bash</code></td></tr><tr><td style="text-align:left"><code>--name</code></td><td style="text-align:left">为容器指定自定义名称</td><td style="text-align:left"><code>docker run --name myapp nginx</code></td></tr><tr><td style="text-align:left"><code>--rm</code></td><td style="text-align:left">容器退出后自动删除</td><td style="text-align:left"><code>docker run --rm ubuntu echo hi</code></td></tr></tbody></table><p><strong>端口映射</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 将容器的 80 端口映射到宿主机的 8080 端口</span></span><br><span class="line"></span><br><span class="line">$ docker run -d -p 8080:80 nginx</span><br><span class="line"></span><br><span class="line"><span class="comment">## 随机映射端口</span></span><br><span class="line"></span><br><span class="line">$ docker run -d -P nginx</span><br><span class="line"></span><br><span class="line"><span class="comment">## 只绑定到 localhost</span></span><br><span class="line"></span><br><span class="line">$ docker run -d -p 127.0.0.1:8080:80 nginx</span><br></pre></td></tr></table></figure><h3 id="运行容器">运行容器</h3><blockquote><p>当你在终端运行一个程序时，有两种模式：</p><blockquote><p>前台运行：程序占用当前终端，输出直接显示，关闭终端程序就停止<br>后台运行：程序在后台执行，不占用终端，终端关闭也不影响程序<br>Docker 容器默认是 前台运行 的。使用 -d (detach) 参数可以让容器在后台运行</p></blockquote></blockquote><p><strong>前台运行</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ docker run ubuntu:24.04 /bin/sh -c <span class="string">&quot;while true; do echo hello world; sleep 1; done&quot;</span></span><br><span class="line">hello world</span><br><span class="line">hello world</span><br><span class="line">hello world</span><br><span class="line">hello world</span><br></pre></td></tr></table></figure><blockquote><p>容器会把输出的结果 (STDOUT) 打印到宿主机上面。此时：</p></blockquote><ol><li>终端被占用，无法执行其他命令</li><li>按 Ctrl+C 会终止容器</li><li>关闭终端窗口，容器也会停止</li></ol><p><strong>后台运行</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d ubuntu:24.04 /bin/sh -c <span class="string">&quot;while true; do echo hello world; sleep 1; done&quot;</span></span><br><span class="line">77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a</span><br></pre></td></tr></table></figure><p>使用 -d 参数后：</p><ol><li>容器在后台运行</li><li>返回容器的完整 ID</li><li>终端立即释放，可以继续执行其他命令</li><li>输出不会直接显示 (需要用 docker logs 查看)</li></ol><h3 id="终止容器">终止容器</h3><p>终止容器有三种方式：</p><table><thead><tr><th style="text-align:left">方式</th><th style="text-align:left">命令</th><th style="text-align:left">说明</th></tr></thead><tbody><tr><td style="text-align:left">优雅停止</td><td style="text-align:left"><code>docker stop</code></td><td style="text-align:left">先发 <code>SIGTERM</code>，超时后发 <code>SIGKILL</code></td></tr><tr><td style="text-align:left">强制停止</td><td style="text-align:left"><code>docker kill</code></td><td style="text-align:left">直接发送 <code>SIGKILL</code> 信号</td></tr><tr><td style="text-align:left">自动终止</td><td style="text-align:left">-</td><td style="text-align:left">容器主进程退出时自动停止</td></tr></tbody></table><p>我们还可以在镜像被修改后重启容器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 先停止再启动</span></span><br><span class="line"></span><br><span class="line">$ docker restart 容器名</span><br><span class="line"></span><br><span class="line"><span class="comment">## 自定义停止超时</span></span><br><span class="line"></span><br><span class="line">$ docker restart -t 30 容器名</span><br></pre></td></tr></table></figure><h3 id="删除容器">删除容器</h3><blockquote><p>随着容器的创建和停止，系统中会积累大量的容器。</p></blockquote><p>使用 docker rm 删除已停止的容器：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker <span class="built_in">rm</span> 容器名或ID</span><br></pre></td></tr></table></figure><ul><li>该命令与docker container rm等效</li></ul><p>使用 docker container prune 批量删除:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 方式一：使用 prune 命令（推荐）</span></span><br><span class="line"></span><br><span class="line">$ docker container prune</span><br><span class="line"></span><br><span class="line">WARNING! This will remove all stopped containers.</span><br><span class="line">Are you sure you want to <span class="built_in">continue</span>? [y/N] y</span><br><span class="line">Deleted Containers:</span><br><span class="line">abc123...</span><br><span class="line">def456...</span><br><span class="line">Total reclaimed space: 150MB</span><br><span class="line"></span><br><span class="line"><span class="comment">## 方式二：不提示确认</span></span><br><span class="line"></span><br><span class="line">$ docker container prune -f</span><br></pre></td></tr></table></figure><h2 id="补充部分-镜像的文件结构">补充部分: 镜像的文件结构</h2><p>非常离谱的是,这么详细的文档偏偏没有提到这一点: 镜像内部是怎么存放文件的?<br>自然,镜像是分层构建存储的,但是这些构建层显然要有个地方放吧.</p><p>镜像的<strong>默认工作目录是根目录</strong>,类似于Linux的根目录,当我们需要切换存储目录或者启动某个目录下的脚本时,可以显式指明,比如说以下的几个命令:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 复制文件到指定目录</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> package.json /app/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 复制文件并重命名</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> config.json /app/settings.json</span></span><br></pre></td></tr></table></figure><ul><li>如此一来,我们成功的将本地文件复制到了app文件夹中.</li></ul><p>因此,镜像不仅仅是一个iso,我们可以把它抽象成一个文件系统,存储层堆叠在不同的目录中,可以来回切换访问.</p><h1>进阶部分</h1><h2 id="dockerfile编写">dockerfile编写</h2><h3 id="概览">概览</h3><blockquote><p>Dockerfile 是一个文本文件，其内包含了一条条的 指令 (Instruction)，每一条指令构建一层，因此每一条指令的内容，就是描述该层应当如何构建。</p></blockquote><p>Dockerfile<strong>不是脚本，而是镜像的&quot;设计图&quot;</strong>。这个区别决定了你如何思考每条指令的作用:</p><ul><li><strong>合并命令</strong>：应将 <code>RUN apt-get update &amp;&amp; apt-get install -y ...</code> 写入同一个 <code>RUN</code> 指令中,因为它们是同一层的逻辑</li><li><strong>优化镜像大小</strong>：最后才清理缓存、删除临时文件，让这些&quot;瘦身&quot;操作在同一层完成</li></ul><h3 id="补充-FROM-基础镜像">(补充)FROM: 基础镜像</h3><p>很多时候我们都需要在官方镜像的基础上进行构建,这个时候我们可以这么写:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> node:<span class="number">20</span>-alpine AS deps</span><br><span class="line"><span class="comment"># 在这个基础上进行构建</span></span><br></pre></td></tr></table></figure><p>这个AS与python中的as一样,都是为导入的镜像重新设一个名字.</p><ul><li>事实上,如果你不写任何FROM,根本无法运行任何Linux命令</li></ul><p>而FROM命令最厉害的地方在于,每一个FROM指令都会重新开辟一个新的文件系统,取代之前的所有内容.<br>因此,我们可以这么写:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 第一阶段：编译环境（命名为 builder）</span></span><br><span class="line"><span class="keyword">FROM</span> golang:<span class="number">1.21</span> AS builder</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> go build -o myapp main.go  <span class="comment"># 物理产生了几百 MB 的编译器和缓存</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 第二阶段：运行环境（最小化镜像）</span></span><br><span class="line"><span class="keyword">FROM</span> alpine:latest</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /root/</span></span><br><span class="line"><span class="comment"># 物理核心：通过 --from=builder 只从 builder 阶段拷贝最终的可执行二进制文件</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /app/myapp . </span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;./myapp&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>这样既可以利用上一个阶段的构建内容,又不会将多余的内容打包进镜像</p><h3 id="RUN-执行命令">RUN: 执行命令</h3><p>RUN 是 Dockerfile 中最常用的指令，主要用于在镜像构建阶段执行命令来修改镜像,有以下几个应用场景:</p><ul><li>安装依赖：<code>RUN apt-get install nginx</code></li><li>编译程序：<code>RUN gcc -o app main.c</code></li><li>下载文件：<code>RUN curl -O https://example.com/file.tar.gz</code></li><li>配置系统：<code>RUN mkdir -p /app/data</code></li></ul><blockquote><p>理解 RUN 的核心是理解镜像分层：每一个 RUN 都会在当前层之上创建新的一层，这会影响镜像大小。因此，合理使用 RUN（特别是合并多个 RUN）是构建轻量级镜像的关键。</p></blockquote><h4 id="基本语法">基本语法</h4><p>有两种格式:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> &lt;<span class="built_in">command</span>&gt;</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> [<span class="string">&quot;executable&quot;</span>, <span class="string">&quot;param1&quot;</span>, <span class="string">&quot;param2&quot;</span>]</span></span><br></pre></td></tr></table></figure><p><strong>shell格式</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update</span></span><br></pre></td></tr></table></figure><ul><li>默认通过 /bin/sh -c 执行。</li><li>可以使用环境变量、管道、重定向等 Shell 特性。</li></ul><p><strong>exec格式</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> [<span class="string">&quot;apt-get&quot;</span>, <span class="string">&quot;update&quot;</span>]</span></span><br></pre></td></tr></table></figure><ul><li>直接调用可执行文件，不经过 Shell。</li><li>无法使用 $VAR 环境变量替换 (除非显式调用 shell)</li></ul><h4 id="实战">实战</h4><p>由于每一个 RUN 指令都会新建一层镜像。为了减少镜像体积和层数，应使用 &amp;&amp; 连接命令:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 糟糕的写法(3层)</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get install -y nginx</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">rm</span> -rf /var/lib/apt/lists/*</span></span><br><span class="line"><span class="comment"># 推荐写法</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">    apt-get install -y nginx &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">    <span class="built_in">rm</span> -rf /var/lib/apt/lists/*</span></span><br></pre></td></tr></table></figure><ul><li>可以看到dockerfile将\用于换行,这与makefile的写法一致</li></ul><h3 id="COPY-复制文件">COPY: 复制文件</h3><p>COPY 是在构建镜像时，将构建上下文（Dockerfile 所在目录及其子目录）中的文件或目录<strong>复制到镜像内</strong>的指令。它是处理应用代码、配置文件最常用的方式,应用场景如下:</p><ul><li><code>COPY . /app</code> (应用源码)</li><li><code>COPY nginx.conf /etc/nginx/nginx.conf</code> (配置文件)</li><li><code>COPY public /app/public</code>(静态资源)</li></ul><h4 id="基本语法-2">基本语法</h4><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">COPY</span><span class="language-bash"> [选项] &lt;源路径&gt;... &lt;目标路径&gt;</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> [选项] [<span class="string">&quot;&lt;源路径1&gt;&quot;</span>, <span class="string">&quot;&lt;源路径2&gt;&quot;</span>, ... <span class="string">&quot;&lt;目标路径&gt;&quot;</span>]</span></span><br></pre></td></tr></table></figure><p><strong>复制文件</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 复制文件到指定目录</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> package.json /app/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 复制文件并重命名</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> config.json /app/settings.json</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 复制多个指定文件</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> package.json package-lock.json /app/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 使用通配符</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> *.json /app/</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> src/*.js /app/src/</span></span><br></pre></td></tr></table></figure><p><strong>复制目录</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 复制整个目录的内容（不是目录本身）</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> src/ /app/src/</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 构建上下文：              镜像内：</span></span><br><span class="line"><span class="comment"># src/                     /app/src/</span></span><br><span class="line"><span class="comment"># ├── index.js      →      ├── index.js</span></span><br><span class="line"><span class="comment"># └── utils.js             └── utils.js</span></span><br></pre></td></tr></table></figure><h4 id="指定路径">指定路径</h4><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 绝对路径</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> app.js /usr/src/app/</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 相对路径：基于 WORKDIR</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> package.json ./        <span class="comment"># 复制到 /app/package.json</span></span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> src/ ./src/            <span class="comment"># 复制到 /app/src/</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果目标目录不存在，Docker 会自动创建：</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## /app/config/ 不存在也会自动创建</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> settings.json /app/config/</span></span><br></pre></td></tr></table></figure><h4 id="dockerignore">dockerignore</h4><p>排除不需要复制的文件,精简镜像体积</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">## .dockerignore</span><br><span class="line"></span><br><span class="line">node_modules</span><br><span class="line">.git</span><br><span class="line">.env</span><br><span class="line">*.log</span><br><span class="line">Dockerfile</span><br><span class="line">.dockerignore</span><br></pre></td></tr></table></figure><h4 id="实战-2">实战</h4><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## ✅ 好：先复制依赖定义，再安装，最后复制代码</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> package.json package-lock.json ./</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> npm install</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## ❌ 差：一次性复制所有文件，代码变更会导致重新 npm install</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> npm install</span></span><br></pre></td></tr></table></figure><p>详细解释一下,docker构建镜像是线性操作的,只有COPY,RUN,ADD三种命令会创建新的存储层,而每次构建时docker都会在本地存储缓存,如果下一次构建镜像时对应的命令没有变化,则会直接复用原来的缓存,不会重新构建;当docker发现COPY的文件内容有改动时,该行之后的所有命令被视为与原缓存不同,需要重新构建.</p><p>因此,如果直接写<code>COPY . .</code>的话,修改任何一个文件后构建镜像都要重新运行npm install;但如果把<code>COPY . .</code>放在后面,只会在package.json变化时重新构建.</p><h3 id="ADD-更高级的COPY">ADD: 更高级的COPY</h3><blockquote><p>实践中的建议：除非你明确需要自动解压功能（比如官方基础镜像构建根文件系统），否则始终使用 COPY。原因很简单——显式优于隐式。你的 Dockerfile 在 6 个月后被接手维护时，清晰的意图会让团队少走很多弯路。</p></blockquote><h4 id="基本用法">基本用法</h4><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ADD</span><span class="language-bash"> [选项] &lt;源路径&gt;... &lt;目标路径&gt;</span></span><br><span class="line"><span class="keyword">ADD</span><span class="language-bash"> [选项] [<span class="string">&quot;&lt;源路径&gt;&quot;</span>, ... <span class="string">&quot;&lt;目标路径&gt;&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>ADD 在 COPY 基础上增加了两个功能：</p><ol><li>自动解压 tar 压缩包</li><li>支持从 URL 下载文件 (不推荐)</li></ol><h3 id="CMD-容器启动命令-4-10">CMD: 容器启动命令(4/10)</h3><p>在深入 CMD 的细节之前，我们需要理解一个关键问题：CMD 和 ENTRYPOINT 应该在什么时候使用？</p><p>这是 Dockerfile 使用中最常见的困惑之一。简单的答案是：</p><ol><li>CMD：定义容器的”默认命令”。如果用户在 docker run 时提供命令，CMD 会被覆盖</li><li>ENTRYPOINT：定义容器的”入口脚本”。通常用于启动应用的某个特定部分</li></ol><h4 id="基本用法-2">基本用法</h4><p>CMD指令用于指定容器启动时默认执行的命令。它定义了容器的 “主进程”。</p><table><thead><tr><th style="text-align:left">格式类型</th><th style="text-align:left">语法示例</th><th style="text-align:left">推荐程度</th><th style="text-align:left">核心机制</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Exec 格式</strong></td><td style="text-align:left"><code>CMD [&quot;executable&quot;, &quot;param1&quot;, &quot;param2&quot;]</code></td><td style="text-align:left">✅ <strong>推荐</strong></td><td style="text-align:left">直接由内核执行，PID 为 1，可接收 <code>SIGTERM</code> 信号。</td></tr><tr><td style="text-align:left"><strong>Shell 格式</strong></td><td style="text-align:left"><code>CMD command param1 param2</code></td><td style="text-align:left">⚠️ <strong>简单场景</strong></td><td style="text-align:left">通过 <code>/bin/sh -c</code> 调用，无法直接接收信号，环境变量会被解析。</td></tr><tr><td style="text-align:left"><strong>参数格式</strong></td><td style="text-align:left"><code>CMD [&quot;param1&quot;, &quot;param2&quot;]</code></td><td style="text-align:left">⚓ <strong>配合使用</strong></td><td style="text-align:left">仅作为 <code>ENTRYPOINT</code> 的默认参数传递。</td></tr></tbody></table><p><strong>exec 格式</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;nginx&quot;</span>, <span class="string">&quot;-g&quot;</span>, <span class="string">&quot;daemon off;&quot;</span>]</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;python&quot;</span>, <span class="string">&quot;app.py&quot;</span>]</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;node&quot;</span>, <span class="string">&quot;server.js&quot;</span>]</span></span><br></pre></td></tr></table></figure><p><strong>shell格式</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CMD</span><span class="language-bash"> <span class="built_in">echo</span> <span class="string">&quot;Hello World&quot;</span></span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> nginx -g <span class="string">&quot;daemon off;&quot;</span></span></span><br></pre></td></tr></table></figure><p>实际执行：会被包装为 sh -c</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 你写的</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> <span class="built_in">echo</span> <span class="variable">$HOME</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 实际执行的</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;sh&quot;</span>, <span class="string">&quot;-c&quot;</span>, <span class="string">&quot;echo <span class="variable">$HOME</span>&quot;</span>]</span></span><br></pre></td></tr></table></figure><ul><li>换句话说shell写法实际上是使用了sh的exec简写格式.</li></ul><h4 id="CMD命令只能写一个">CMD命令只能写一个</h4><p>多个CMD只有最后一个生效.<br>因为CMD的PID为1,意思是在Linux内核中它作为根进程,是独一无二的.因此CMD一旦停止,容器就关闭了.</p><h3 id="ENTRYPOINT-入口点-4-11">ENTRYPOINT: 入口点(4/11)</h3><blockquote><p>如果说 CMD 是&quot;容器中的默认程序&quot;，那么 ENTRYPOINT 就是&quot;把容器变成一个命令&quot;。这个思维转变决定了你何时使用 ENTRYPOINT。</p></blockquote><h4 id="是什么-怎么用">是什么,怎么用</h4><p>ENTRYPOINT 指定容器启动时运行的入口程序。与 CMD 不同，ENTRYPOINT 定义的命令不会被 <code>docker run</code> 的参数覆盖，而是 接收这些参数。</p><p><strong>基本语法</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## exec 格式（推荐）</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;nginx&quot;</span>, <span class="string">&quot;-g&quot;</span>, <span class="string">&quot;daemon off;&quot;</span>]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## shell 格式（不推荐）</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> nginx -g <span class="string">&quot;daemon off;&quot;</span></span></span><br></pre></td></tr></table></figure><h3 id="ENV-设置环境变量">ENV: 设置环境变量</h3><ul><li>很好理解,就是设置了一个dockerfile中的变量而已.</li></ul><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 格式一：单个变量</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENV</span> &lt;key&gt; &lt;value&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">## 格式二：多个变量（推荐）</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENV</span> &lt;key1&gt;=&lt;value1&gt; &lt;key2&gt;=&lt;value2&gt; ...</span><br></pre></td></tr></table></figure><p><strong>例子</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ENV</span> NODE_VERSION <span class="number">20.10</span>.<span class="number">0</span></span><br><span class="line"><span class="keyword">ENV</span> APP_ENV production</span><br><span class="line"></span><br><span class="line"><span class="keyword">ENV</span> NODE_VERSION=<span class="number">20.10</span>.<span class="number">0</span> \</span><br><span class="line">    APP_ENV=production \</span><br><span class="line">    APP_NAME=<span class="string">&quot;My Application&quot;</span></span><br><span class="line"><span class="comment"># 包含空格的值用双引号括起来</span></span><br></pre></td></tr></table></figure><h4 id="用法">用法</h4><p>使用 -e 或 --env 覆盖 Dockerfile 中定义的环境变量：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 覆盖单个变量</span></span><br><span class="line"></span><br><span class="line">$ docker run -e APP_ENV=development myimage</span><br><span class="line"></span><br><span class="line"><span class="comment">## 覆盖多个变量</span></span><br><span class="line"></span><br><span class="line">$ docker run -e APP_ENV=development -e DEBUG=<span class="literal">true</span> myimage</span><br><span class="line"></span><br><span class="line"><span class="comment">## 从环境变量文件读取</span></span><br><span class="line"></span><br><span class="line">$ docker run --env-file .<span class="built_in">env</span> myimage</span><br></pre></td></tr></table></figure><p>运行时传入密码:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## ❌ 错误：密码写入镜像</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENV</span> DB_PASSWORD=secret123</span><br><span class="line"></span><br><span class="line"><span class="comment">## ✅ 正确：运行时传入</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## docker run -e DB_PASSWORD=xxx myimage</span></span><br></pre></td></tr></table></figure><p>使用docker compose的话就没必要考虑这么多了</p><h3 id="ARG-构建参数">ARG: 构建参数</h3><p>ARG仅在构建时生效,用于传递版本号之类的信息,可以出现在FROM指令之前,也能在docker build阶段传入对应的参数.换句话说,我们可以更改构建初始镜像所用的版本号.</p><p>而ENV则会被打包进入镜像,在容器运行期间永久生效,也不能出现在FROM指令之前.<br><strong>基本语法</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ARG</span> &lt;参数名&gt;[=&lt;默认值&gt;]</span><br></pre></td></tr></table></figure><h4 id="用法-2">用法</h4><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ARG</span> BASE_IMAGE=python:<span class="number">3.12</span>-slim</span><br><span class="line"><span class="keyword">FROM</span> $&#123;BASE_IMAGE&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">## 可以构建不同基础镜像的版本</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## docker build --build-arg BASE_IMAGE=python:3.14-alpine .</span></span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></table></figure><h3 id="VOLUME-定义匿名卷">VOLUME: 定义匿名卷</h3><h4 id="是什么-怎么用-2">是什么,怎么用</h4><blockquote><p>容器存储层应该保持无状态，任何运行时数据都应该存储在volume中。</p></blockquote><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义单个volume</span></span><br><span class="line"><span class="keyword">FROM</span> mysql:<span class="number">8.0</span></span><br><span class="line"><span class="keyword">VOLUME</span><span class="language-bash"> /var/lib/mysql</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义多个volume</span></span><br><span class="line"><span class="keyword">FROM</span> myapp</span><br><span class="line"><span class="keyword">VOLUME</span><span class="language-bash"> [<span class="string">&quot;/data&quot;</span>, <span class="string">&quot;/logs&quot;</span>, <span class="string">&quot;/config&quot;</span>]</span></span><br></pre></td></tr></table></figure><h4 id="volume的行为">volume的行为</h4><p><strong>自动创建匿名卷</strong><br>如果运行时未指定挂载，Docker 会自动创建匿名卷：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker run mysql:8.0</span><br><span class="line">$ docker volume <span class="built_in">ls</span></span><br><span class="line">DRIVER    VOLUME NAME</span><br><span class="line"><span class="built_in">local</span>     a1b2c3d4e5f6...  <span class="comment"># 自动创建的匿名卷</span></span><br></pre></td></tr></table></figure><p><strong>会被命名卷覆盖</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 使用命名卷替代匿名卷</span></span><br><span class="line"></span><br><span class="line">$ docker run -v mysql_data:/var/lib/mysql mysql:8.0</span><br></pre></td></tr></table></figure><blockquote><p>VOLUME 之后对该目录的修改会被丢弃！</p></blockquote><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> ubuntu</span><br><span class="line"><span class="keyword">VOLUME</span><span class="language-bash"> /data</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## ❌ 这个文件不会出现在镜像中！</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">echo</span> <span class="string">&quot;hello&quot;</span> &gt; /data/test.txt</span></span><br></pre></td></tr></table></figure><blockquote><p>原因：在构建过程中，VOLUME 指令会为该目录创建一个临时的匿名卷。后续 RUN 指令对该目录的写入实际发生在这个临时卷中，而非镜像层。当该 RUN 指令结束后，临时卷被丢弃，因此写入的内容不会保存到最终镜像中。注意：这与容器运行时创建的匿名卷是不同的——运行时创建的卷会在容器生命周期内持续存在。</p></blockquote><p>正确做法</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> ubuntu</span><br><span class="line"></span><br><span class="line"><span class="comment">## ✅ 先写入文件</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">mkdir</span> -p /data &amp;&amp; <span class="built_in">echo</span> <span class="string">&quot;hello&quot;</span> &gt; /data/test.txt</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 再声明 VOLUME</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">VOLUME</span><span class="language-bash"> /data</span></span><br></pre></td></tr></table></figure><h4 id="在compose中使用">在compose中使用</h4><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">postgres:16</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="comment"># 命名卷（推荐）</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="string">postgres_data:/var/lib/postgresql/data</span></span><br><span class="line">      <span class="comment"># Bind Mount</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./init.sql:/docker-entrypoint-initdb.d/init.sql</span></span><br><span class="line"></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">postgres_data:</span>  <span class="comment"># 声明命名卷</span></span><br></pre></td></tr></table></figure><h3 id="EXPOSE-暴露端口-4-12">EXPOSE: 暴露端口(4/12)</h3><h4 id="是什么-怎么用-3">是什么,怎么用</h4><blockquote><p>EXPOSE 声明容器运行时提供服务的端口。这是一个<strong>文档性质</strong>的声明，告诉使用者容器会监听哪些端口。</p></blockquote><ul><li>换句话说只起一个约定作用,不通过-p的话不会起作用</li></ul><p><strong>基本用法</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 声明单个端口</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 声明多个端口</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span> <span class="number">443</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 声明 TCP 和 UDP 端口</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span>/tcp</span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">53</span>/udp</span><br></pre></td></tr></table></figure><p>使用 docker run -P 时，Docker 会自动映射 EXPOSE 的端口到宿主机随机端口：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## Dockerfile</span></span><br><span class="line"><span class="comment"># EXPOSE 80</span></span><br><span class="line"></span><br><span class="line">$ docker run -P nginx</span><br><span class="line">$ docker port $(docker ps -q)</span><br><span class="line">80/tcp -&gt; 0.0.0.0:32768</span><br></pre></td></tr></table></figure><h4 id="实战-3">实战</h4><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## Dockerfile</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">FROM</span> nginx</span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span>    <span class="comment"># 1. 声明：这个容器会在 80 端口提供服务</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 运行：需要 -p 才能从外部访问</span></span><br><span class="line"></span><br><span class="line">$ docker run -p 8080:80 nginx    <span class="comment"># 2. 映射：宿主机 8080 → 容器 80</span></span><br></pre></td></tr></table></figure><h4 id="compose中的编写">compose中的编写</h4><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">.</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8080:80&quot;</span>    <span class="comment"># 映射端口（类似 -p）</span></span><br><span class="line">    <span class="attr">expose:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;80&quot;</span>         <span class="comment"># 仅声明（类似 EXPOSE）</span></span><br></pre></td></tr></table></figure><h3 id="WORKDIR-指定工作目录">WORKDIR: 指定工作目录</h3><blockquote><p>WORKDIR 指定后续指令的工作目录。如果目录不存在，Docker 会自动创建。</p></blockquote><p><strong>基本用法</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">pwd</span>          <span class="comment"># 输出 /app</span></span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">echo</span> <span class="string">&quot;hello&quot;</span> &gt; world.txt    <span class="comment"># 创建 /app/world.txt</span></span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .         <span class="comment"># 复制到 /app/</span></span></span><br></pre></td></tr></table></figure><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 相对路径</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /a</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> b</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> c</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">pwd</span>    <span class="comment"># 输出 /a/b/c</span></span></span><br></pre></td></tr></table></figure><h4 id="实战-4">实战</h4><p><strong>使用绝对命令</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## ✅ 推荐：绝对路径，意图明确</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## ⚠️ 避免：相对路径可能造成混淆</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> app</span></span><br></pre></td></tr></table></figure><h3 id="USER-指定当前用户">USER: 指定当前用户</h3><h4 id="是什么-怎么用-4">是什么,怎么用</h4><blockquote><p>USER 指令切换后续指令 (RUN、CMD、ENTRYPOINT) 的执行用户</p></blockquote><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">USER</span> &lt;用户名&gt;[:&lt;用户组&gt;]</span><br><span class="line"><span class="keyword">USER</span> &lt;UID&gt;[:&lt;GID&gt;]</span><br></pre></td></tr></table></figure><ul><li>一般是用不上这个的</li></ul><h3 id="HEALTHCHECK-健康检查">HEALTHCHECK: 健康检查</h3><h4 id="是什么-怎么用-5">是什么,怎么用</h4><blockquote><p>HEALTHCHECK 指令告诉 Docker 如何判断容器状态是否正常。这是保障服务高可用的重要机制。</p></blockquote><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">HEALTHCHECK</span><span class="language-bash"> [选项] CMD &lt;命令&gt;</span></span><br><span class="line"><span class="keyword">HEALTHCHECK</span><span class="language-bash"> NONE</span></span><br></pre></td></tr></table></figure><h4 id="基本用法-3">基本用法</h4><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> nginx</span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update &amp;&amp; apt-get install -y curl &amp;&amp; <span class="built_in">rm</span> -rf /var/lib/apt/lists/*</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">HEALTHCHECK</span><span class="language-bash"> --interval=30s --<span class="built_in">timeout</span>=3s --retries=3 \</span></span><br><span class="line"><span class="language-bash">  CMD curl -fs http://localhost/ || <span class="built_in">exit</span> 1</span></span><br></pre></td></tr></table></figure><table><thead><tr><th style="text-align:left">选项</th><th style="text-align:left">说明</th><th style="text-align:left">默认值</th></tr></thead><tbody><tr><td style="text-align:left"><code>--interval</code></td><td style="text-align:left">两次检查的间隔</td><td style="text-align:left">30s</td></tr><tr><td style="text-align:left"><code>--timeout</code></td><td style="text-align:left">检查命令的超时时间</td><td style="text-align:left">30s</td></tr><tr><td style="text-align:left"><code>--start-period</code></td><td style="text-align:left">启动缓冲期 (期间失败不计入次数)</td><td style="text-align:left">0s</td></tr><tr><td style="text-align:left"><code>--retries</code></td><td style="text-align:left">连续失败多少次标记为 unhealthy</td><td style="text-align:left">3</td></tr></tbody></table><blockquote><p>应用启动可能需要时间 (如 Java 应用)。设置 --start-period 可以防止在启动阶段因检查失败而误判</p></blockquote><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 给应用 1 分钟启动时间</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">HEALTHCHECK</span><span class="language-bash"> --start-period=60s CMD curl -f http://localhost/ || <span class="built_in">exit</span> 1</span></span><br></pre></td></tr></table></figure><h3 id="LABEL-为镜像添加元数据">LABEL: 为镜像添加元数据</h3><h4 id="是什么-怎么用-6">是什么,怎么用</h4><blockquote><p>LABEL 指令以键值对的形式给镜像添加元数据。这些数据不会影响镜像的功能，但可以帮助用户理解镜像，或被自动化工具使用。</p></blockquote><ol><li>版本管理：记录版本号、构建时间、Git Commit ID</li><li>联系信息：维护者邮箱、文档地址、支持渠道</li><li>自动化工具：CI/CD 工具可以读取标签触发操作</li><li>许可证信息：声明开源协议</li></ol><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">LABEL</span><span class="language-bash"> &lt;key&gt;=&lt;value&gt; &lt;key&gt;=&lt;value&gt; ...</span></span><br></pre></td></tr></table></figure><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义单个标签</span></span><br><span class="line"><span class="keyword">LABEL</span><span class="language-bash"> version=<span class="string">&quot;1.0&quot;</span></span></span><br><span class="line"><span class="keyword">LABEL</span><span class="language-bash"> description=<span class="string">&quot;这是一个 Web 应用服务器&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义多个标签</span></span><br><span class="line"><span class="keyword">LABEL</span><span class="language-bash"> maintainer=<span class="string">&quot;user@example.com&quot;</span> \</span></span><br><span class="line"><span class="language-bash">      version=<span class="string">&quot;1.2.0&quot;</span> \</span></span><br><span class="line"><span class="language-bash">      description=<span class="string">&quot;My App Description&quot;</span> \</span></span><br><span class="line"><span class="language-bash">      org.opencontainers.image.authors=<span class="string">&quot;Yeasy&quot;</span></span></span><br></pre></td></tr></table></figure><h2 id="数据管理">数据管理</h2><p>这一章介绍如何在 Docker 内部以及容器之间管理数据，在容器中管理数据主要有以下几种方式：</p><ol><li>数据卷</li><li>挂载主机目录</li><li>tmpfs 挂载</li></ol><h3 id="数据卷">数据卷</h3><blockquote><p>容器的存储层有一个关键问题：容器删除后，数据就没了。数据卷 (Volume) 解决了这个问题，它的生命周期独立于容器。</p></blockquote><h4 id="创建和查看数据卷">创建和查看数据卷</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建数据卷</span></span><br><span class="line">$ docker volume create my-vol</span><br><span class="line"></span><br><span class="line"><span class="comment"># 列出所有数据卷</span></span><br><span class="line">$ docker volume <span class="built_in">ls</span></span><br><span class="line">DRIVER    VOLUME NAME</span><br><span class="line"><span class="built_in">local</span>     my-vol</span><br><span class="line"><span class="built_in">local</span>     postgres_data</span><br><span class="line"><span class="built_in">local</span>     redis_data</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看数据卷详情</span></span><br><span class="line">$ docker volume inspect my-vol</span><br><span class="line">[</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="string">&quot;CreatedAt&quot;</span>: <span class="string">&quot;2026-01-15T10:00:00Z&quot;</span>,</span><br><span class="line">        <span class="string">&quot;Driver&quot;</span>: <span class="string">&quot;local&quot;</span>,</span><br><span class="line">        <span class="string">&quot;Labels&quot;</span>: &#123;&#125;,</span><br><span class="line">        <span class="string">&quot;Mountpoint&quot;</span>: <span class="string">&quot;/var/lib/docker/volumes/my-vol/_data&quot;</span>,</span><br><span class="line">        <span class="string">&quot;Name&quot;</span>: <span class="string">&quot;my-vol&quot;</span>,</span><br><span class="line">        <span class="string">&quot;Options&quot;</span>: &#123;&#125;,</span><br><span class="line">        <span class="string">&quot;Scope&quot;</span>: <span class="string">&quot;local&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">]</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>关键字段</strong>：</p><ul><li>Mountpoint：数据卷在宿主机上的实际存储位置</li><li>Driver：存储驱动 (默认 local，也可以用第三方驱动)</li></ul><h4 id="挂载数据卷">挂载数据卷</h4><p><strong>方式一：–mount：推荐</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d \</span><br><span class="line">    --name web \</span><br><span class="line">    --mount <span class="built_in">source</span>=my-vol,target=/usr/share/nginx/html \</span><br><span class="line">    nginx</span><br></pre></td></tr></table></figure><table><thead><tr><th style="text-align:left">参数</th><th style="text-align:left">说明</th></tr></thead><tbody><tr><td style="text-align:left"><code>source</code></td><td style="text-align:left">数据卷名称 (不存在会自动创建)</td></tr><tr><td style="text-align:left"><code>target</code></td><td style="text-align:left">容器内挂载路径</td></tr><tr><td style="text-align:left"><code>readonly</code></td><td style="text-align:left">可选，只读挂载</td></tr><tr><td style="text-align:left">方式二：-v：简写</td><td style="text-align:left"></td></tr></tbody></table><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d \</span><br><span class="line">    --name web \</span><br><span class="line">    -v my-vol:/usr/share/nginx/html \</span><br><span class="line">    nginx</span><br></pre></td></tr></table></figure><blockquote><p>提示：官方更推荐使用 --mount。除了语法格式可读性更好之外，最重要的行为差异发生在 <strong>绑定挂载</strong> (Bind Mount) 时：如果挂载的宿主机源路径尚未存在，-v 会擅自将其自动创建为一个空目录；而 --mount 则会严格检查并直接报错。这能有效避免因路径拼写错误而在宿主机上留下垃圾目录（以及导致的容器访问空目录问题）。而对于本节的 数据卷 (Volume) 挂载而言，两者在目标指定的卷不存在时皆会自动创建卷，产生的结果是 完全一致 的。</p></blockquote><h4 id="实战-5">实战</h4><p><strong>数据库持久化</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 创建数据卷</span></span><br><span class="line"></span><br><span class="line">$ docker volume create postgres_data</span><br><span class="line"></span><br><span class="line"><span class="comment">## 启动 PostgreSQL，数据存储在数据卷中</span></span><br><span class="line"></span><br><span class="line">$ docker run -d \</span><br><span class="line">    --name postgres \</span><br><span class="line">    -e POSTGRES_PASSWORD=secret \</span><br><span class="line">    -v postgres_data:/var/lib/postgresql/data \</span><br><span class="line">    postgres:16</span><br><span class="line"></span><br><span class="line"><span class="comment">## 即使删除容器，数据仍然保留</span></span><br><span class="line"></span><br><span class="line">$ docker <span class="built_in">rm</span> -f postgres</span><br><span class="line"></span><br><span class="line"><span class="comment">## 重新启动，数据还在</span></span><br><span class="line"></span><br><span class="line">$ docker run -d \</span><br><span class="line">    --name postgres \</span><br><span class="line">    -e POSTGRES_PASSWORD=secret \</span><br><span class="line">    -v postgres_data:/var/lib/postgresql/data \</span><br><span class="line">    postgres:16</span><br></pre></td></tr></table></figure><p><strong>多容器共享数据</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 创建共享数据卷</span></span><br><span class="line"></span><br><span class="line">$ docker volume create shared-data</span><br><span class="line"></span><br><span class="line"><span class="comment">## 容器 A 写入数据</span></span><br><span class="line"></span><br><span class="line">$ docker run -d --name writer \</span><br><span class="line">    -v shared-data:/data \</span><br><span class="line">    alpine sh -c <span class="string">&quot;while true; do date &gt;&gt; /data/log.txt; sleep 5; done&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 容器 B 读取数据</span></span><br><span class="line"></span><br><span class="line">$ docker run --<span class="built_in">rm</span> \</span><br><span class="line">    -v shared-data:/data \</span><br><span class="line">    alpine <span class="built_in">cat</span> /data/log.txt</span><br></pre></td></tr></table></figure><p><strong>配置文件持久化</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 将 nginx 配置存储在数据卷中</span></span><br><span class="line"></span><br><span class="line">$ docker run -d \</span><br><span class="line">    -v nginx-config:/etc/nginx/conf.d \</span><br><span class="line">    -v nginx-logs:/var/log/nginx \</span><br><span class="line">    -p 80:80 \</span><br><span class="line">    nginx</span><br></pre></td></tr></table></figure><h3 id="挂载主机目录">挂载主机目录</h3><h4 id="绑定挂载">绑定挂载</h4><blockquote><p>Bind Mount (绑定挂载) 将 Docker daemon 所在主机 上的目录或文件直接挂载到容器中。容器可以读写这台主机上的文件系统。<br><strong>Bind Mount vs Volume</strong><br>| 特性           | Bind Mount (绑定挂载)                | Volume (数据卷)                                    |<br>| :------------- | :----------------------------------- | :------------------------------------------------- |<br>| <strong>数据位置</strong>   | 宿主机任意路径                       | Docker 管理的特定目录 (<code>/var/lib/docker/volumes/</code>) |<br>| <strong>路径指定</strong>   | 必须是绝对路径 (如 <code>/opt/app/data</code>)  | 卷名 (如 <code>my-vol</code>)，隐式管理物理路径               |<br>| <strong>可移植性</strong>   | <strong>低</strong>。依赖宿主机特定的文件目录结构 | <strong>高</strong>。不依赖物理路径，易于在不同环境迁移         |<br>| <strong>性能</strong>       | 依赖宿主机文件系统原生性能           | 绕过 Storage Driver 层，具备原生 I/O 性能          |<br>| <strong>适用场景</strong>   | 开发环境同步代码、挂载宿主机配置文件 | 生产环境数据库持久化、日志存储、多容器共享         |<br>| <strong>备份与管理</strong> | 手动定位宿主机路径进行备份           | 使用 <code>docker volume</code> 命令管理，备份需挂载容器操作  |<br>| <strong>隔离性</strong>     | 宿主机进程可轻易修改，安全性较低     | 由 Docker 隔离，减少了被宿主机其他进程误删的风险   |</p></blockquote><h4 id="基本语法-3">基本语法</h4><p><strong>方案 A：使用 <code>--mount</code>（推荐方式）</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d \</span><br><span class="line">    --name web-bind \</span><br><span class="line">    --mount <span class="built_in">type</span>=<span class="built_in">bind</span>,<span class="built_in">source</span>=/宿主机路径,target=/容器路径 \</span><br><span class="line">    nginx</span><br></pre></td></tr></table></figure><p><strong>方案 B：使用 <code>-v</code>（简写方式）</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d \</span><br><span class="line">    --name web-v \</span><br><span class="line">    -v /宿主机路径:/容器路径 \</span><br><span class="line">    nginx</span><br></pre></td></tr></table></figure><p>可以看到,这里的语法与之前的volume挂载基本相同</p><h2 id="网络配置">网络配置</h2><h3 id="配置DNS">配置DNS</h3><p>Docker 容器的 DNS 配置有两种情况：</p><ol><li><strong>默认 Bridge 网络</strong>：继承宿主机的 DNS 配置 (/etc/resolv.conf)。</li><li><strong>自定义网络(推荐)</strong>：使用 Docker 嵌入式 DNS 服务器 (Embedded DNS)，支持通过 <strong>容器名</strong> 进行服务发现。</li></ol><h4 id="使用自定义网络">使用自定义网络</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 1. 创建自定义网络</span></span><br><span class="line"></span><br><span class="line">$ docker network create mynet</span><br><span class="line"></span><br><span class="line"><span class="comment">## 2. 启动容器 web 并加入网络</span></span><br><span class="line"></span><br><span class="line">$ docker run -d --name web --network mynet nginx</span><br><span class="line"></span><br><span class="line"><span class="comment">## 3. 启动容器 client 并尝试 ping web</span></span><br><span class="line"></span><br><span class="line">$ docker run -it --<span class="built_in">rm</span> --network mynet alpine ping web</span><br><span class="line">PING web (172.18.0.2): 56 data bytes</span><br><span class="line">64 bytes from 172.18.0.2: <span class="built_in">seq</span>=0 ttl=64 <span class="keyword">time</span>=0.074 ms</span><br></pre></td></tr></table></figure><h3 id="端口映射">端口映射</h3><p>容器的网络访问规则如下：</p><ol><li>容器之间：可以通过 IP 或容器名 (自定义网络) 互通。</li><li>宿主机访问容器：可以通过容器 IP 访问。</li><li>外部网络访问容器：❌ 默认无法直接访问。</li></ol><p>为了让外部 (如你的浏览器、其他局域网机器) 访问容器内的服务，我们需要将容器的端口 <strong>映射</strong> 到宿主机的端口。</p><h4 id="基本用法-4">基本用法</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 将宿主机的 8080 端口映射到容器的 80 端口</span></span><br><span class="line"></span><br><span class="line">$ docker run -d -p 8080:80 nginx:alpine</span><br></pre></td></tr></table></figure><ul><li>此时访问 <a href="http://localhost:8080">http://localhost:8080</a> 即可看到 Nginx 页面。<br>| 格式                          | 含义                                   | 示例                                    |<br>| :---------------------------- | :------------------------------------- | :-------------------------------------- |<br>| <strong>ip:hostPort:containerPort</strong> | 绑定指定 IP 的特定端口                 | <code>-p 127.0.0.1:8080:80</code> (仅允许本机访问) |<br>| <strong>ip::containerPort</strong>         | 绑定指定 IP 的随机端口                 | <code>-p 127.0.0.1::80</code>                      |<br>| <strong>hostPort:containerPort</strong>    | 绑定所有网卡 IP (<code>0.0.0.0</code>) 的特定端口 | <code>-p 8080:80</code> (最常用格式)               |<br>| <strong>containerPort</strong>             | 绑定所有网卡 IP 的随机端口             | <code>-p 80</code>                                 |</li></ul><h5 id="随机映射">随机映射</h5><blockquote><p>如果不关心宿主机使用哪个端口，可以使用随机映射。使用 -P (大写) 参数，Docker 会把 Dockerfile 中 EXPOSE 指令暴露的所有端口发布到宿主机的随机高位端口。具体落在哪个端口，取决于宿主机当前可用的临时端口范围。</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">docker run -d -P nginx</span><br><span class="line"></span><br><span class="line">docker ps</span><br><span class="line">CONTAINER ID   PORTS</span><br><span class="line">abc123456      0.0.0.0:49153-&gt;80/tcp</span><br><span class="line"><span class="comment"># 此时 Nginx 被映射到了宿主机的一个随机高位端口49153</span></span><br></pre></td></tr></table></figure><h4 id="实战-6">实战</h4><blockquote><p>默认情况下，-p 8080:80 会监听 0.0.0.0:8080，这意味着任何人只要能连接你的宿主机 IP，就能访问该服务。如果不希望对外暴露 (例如数据库服务)，应绑定到 127.0.0.1：</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 仅允许本机访问</span></span><br><span class="line"></span><br><span class="line">$ docker run -d -p 127.0.0.1:3306:3306 mysql</span><br></pre></td></tr></table></figure><h3 id="网络隔离">网络隔离</h3><p>不同网络之间默认隔离，容器只能与同一网络中的容器直接通信：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 创建两个网络</span></span><br><span class="line"></span><br><span class="line">$ docker network create frontend</span><br><span class="line">$ docker network create backend</span><br><span class="line"></span><br><span class="line"><span class="comment">## 容器 A 在 frontend</span></span><br><span class="line"></span><br><span class="line">$ docker run -d --name web --network frontend nginx</span><br><span class="line"></span><br><span class="line"><span class="comment">## 容器 B 在 backend</span></span><br><span class="line"></span><br><span class="line">$ docker run -d --name db --network backend postgres</span><br><span class="line"></span><br><span class="line"><span class="comment">## web 无法直接访问 db（不同网络）</span></span><br><span class="line"></span><br><span class="line">$ docker <span class="built_in">exec</span> web ping db</span><br><span class="line">ping: db: Name or service not known</span><br></pre></td></tr></table></figure><h2 id="Docker-Compose">Docker Compose</h2><h1>Attention Is All You Need</h1><h1>Understanding The Linux Kernel(深入理解Linux内核)</h1>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;重构 改善既有代码的设计&lt;/h1&gt;
&lt;h2 id=&quot;ch1-Intro&quot;&gt;ch1: Intro&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你要给程序添加一个特性，但发现代码因缺乏良好的结构而不易于进行更改，那就先重构那个程序，使其比较容易添加该特性，然后再添加该特性。</summary>
      
    
    
    
    <category term="动态更新" scheme="https://revival-of-hope.github.io/categories/%E5%8A%A8%E6%80%81%E6%9B%B4%E6%96%B0/"/>
    
    
    <category term="合集" scheme="https://revival-of-hope.github.io/tags/%E5%90%88%E9%9B%86/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-27 使用python读取文件</title>
    <link href="https://revival-of-hope.github.io/2026/03/27/python-2026-03-27-%E4%BD%BF%E7%94%A8python%E8%AF%BB%E5%8F%96%E6%96%87%E4%BB%B6/"/>
    <id>https://revival-of-hope.github.io/2026/03/27/python-2026-03-27-%E4%BD%BF%E7%94%A8python%E8%AF%BB%E5%8F%96%E6%96%87%E4%BB%B6/</id>
    <published>2026-03-27T00:00:00.000Z</published>
    <updated>2026-04-03T14:39:43.997Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>不会用python来读取文件的话,我想是不太可能会学会爬虫的,不然你怎么处理获取的数据</p></blockquote><p>本文所用的weekly_hiring_comments.json示例的结构如下:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span></span><br><span class="line">  <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;issue&quot;</span><span class="punctuation">:</span> <span class="number">692</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ruanyf&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;created_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2019-07-18T07:00:46Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;### **高级 Web 前端工程师**\r\n  \r\n[深圳追一科技](https://zhuiyi.ai/)，人工智能创业公司。工作地点：深圳市南山区。\r\n\r\n公司主打 NLP 方向的 B 端 AI 产品落地，诚求英才。要求4年以上实际前端项目的开发经验，熟练掌握 Vue 或 React 生态，查看[详细信息](https://www.zhipin.com/job_detail/79ca9be7fb736e4d03Nz3924FVA~.html)。\r\n\r\nEmail 联系：[winchang@wezhuiyi.com](mailto:winchang@wezhuiyi.com)&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://github.com/ruanyf/weekly/issues/692#issuecomment-512691467&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="comment">//   ...</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><h2 id="open方法"><a class="markdownIt-Anchor" href="#open方法"></a> open方法</h2><p>读写文件一般都通过open方法来进行操作,基本用法看下面的代码就很容易理解了:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;weekly_hiring_comments.json&quot;</span>, <span class="string">&quot;r&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    posts = json.load(f)</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;本科及以上.json&quot;</span>, <span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    json.dump(bachelor_posts, f, ensure_ascii=<span class="literal">False</span>, indent=<span class="number">2</span>)</span><br></pre></td></tr></table></figure><p>三个参数分别为:</p><ol><li>file(文件路径)</li><li>mode(操作方式)</li><li>encoding(解码方式)</li></ol><p>mode 的值包括以下几种:</p><ul><li>‘r’ ，表示读取文件</li><li>‘w’ 表示写入文件（现有同名文件会被覆盖）</li><li>‘a’ 表示打开文件并追加内容，任何写入的数据会自动添加到文件末尾</li><li>‘r+’ 表示打开文件进行读写</li><li><strong>mode 实参是可选的，省略时的默认值为 ‘r’</strong></li></ul><p>当然,如果看源码的话还能看到一堆参数,但我们一般只用得到上述的三个参数:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">open</span>(<span class="params"></span></span><br><span class="line"><span class="params">    file: FileDescriptorOrPath,</span></span><br><span class="line"><span class="params">    mode: OpenTextMode = <span class="string">&quot;r&quot;</span>,</span></span><br><span class="line"><span class="params">    buffering: <span class="built_in">int</span> = -<span class="number">1</span>,</span></span><br><span class="line"><span class="params">    encoding: <span class="built_in">str</span> | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    errors: <span class="built_in">str</span> | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    newline: <span class="built_in">str</span> | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    closefd: <span class="built_in">bool</span> = <span class="literal">True</span>,</span></span><br><span class="line"><span class="params">    opener: _Opener | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params"></span>) -&gt; TextIOWrapper: ...</span><br></pre></td></tr></table></figure><p>现在的问题是这个读写的文件会有很多种格式(.pdf,.txt,.json,.html,.js, …),我们来看看open是怎么处理的:</p><ol><li>text mode - 默认格式: 通常情况下,文件以该模式打开,一般使用utf-8进行编码,该模式主要用于处理文本文件</li><li>binary mode - 以二进制模式读取文件,需要在mode词尾加上一个’b’,如<code>wb</code>,<code>ab</code>等,在二进制模式下无法指定encoding(也没有必要指定),该模式主要用于读取.png,.mp3,.pdf这样的二进制文件</li></ol><p>换句话说,open函数根本不会对每种文件进行特殊处理,只是有两种读取方式而已了,对于一些特殊的文件格式,我们都需要额外用其他库去处理.</p><p>但是对于一般的文件格式,open函数读取文件名后会返回一个TextIOWrapper对象,它有两种常用的方法:</p><ol><li>.read()方法: 将全文读入一个字符串变量<ol><li>例子: <code>content = f.read()</code></li></ol></li><li>.write()方法: 写入字符串<ol><li>例子: <code>f.write(f&quot;## 招聘 \n\n&quot;)</code></li></ol></li></ol><h3 id="json系统库处理json文件"><a class="markdownIt-Anchor" href="#json系统库处理json文件"></a> json系统库:处理json文件</h3><p>既然是系统库,那自然要先导入后使用,事实上只有两个常用函数: json.load()和json.dump().</p><p><strong>示例</strong></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;weekly_hiring_comments.json&quot;</span>, <span class="string">&quot;r&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    posts = json.load(f)</span><br><span class="line"></span><br><span class="line">bachelor_posts = []</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(out_dir / <span class="string">&quot;本科及以上.json&quot;</span>, <span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    json.dump(bachelor_posts, f, ensure_ascii=<span class="literal">False</span>, indent=<span class="number">2</span>)</span><br></pre></td></tr></table></figure><p>看看源码和参数:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># load()</span></span><br><span class="line">(function) <span class="keyword">def</span> <span class="title function_">load</span>(<span class="params"></span></span><br><span class="line"><span class="params">    fp: SupportsRead[<span class="built_in">str</span> | <span class="built_in">bytes</span>],</span></span><br><span class="line"><span class="params">    *,</span></span><br><span class="line"><span class="params">    cls: <span class="built_in">type</span>[JSONDecoder] | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    object_hook: (<span class="params">(<span class="params"><span class="built_in">dict</span>[<span class="type">Any</span>, <span class="type">Any</span>]</span>) -&gt; <span class="type">Any</span></span>) | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    parse_float: (<span class="params">(<span class="params"><span class="built_in">str</span></span>) -&gt; <span class="type">Any</span></span>) | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    parse_int: (<span class="params">(<span class="params"><span class="built_in">str</span></span>) -&gt; <span class="type">Any</span></span>) | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    parse_constant: (<span class="params">(<span class="params"><span class="built_in">str</span></span>) -&gt; <span class="type">Any</span></span>) | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    object_pairs_hook: (<span class="params">(<span class="params"><span class="built_in">list</span>[<span class="built_in">tuple</span>[<span class="type">Any</span>, <span class="type">Any</span>]]</span>) -&gt; <span class="type">Any</span></span>) | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    **kwds: <span class="type">Any</span></span></span><br><span class="line"><span class="params"></span>) -&gt; <span class="type">Any</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># dump()</span></span><br><span class="line">(function) <span class="keyword">def</span> <span class="title function_">dump</span>(<span class="params"></span></span><br><span class="line"><span class="params">    obj: <span class="type">Any</span>,</span></span><br><span class="line"><span class="params">    fp: SupportsWrite[<span class="built_in">str</span>],</span></span><br><span class="line"><span class="params">    *,</span></span><br><span class="line"><span class="params">    skipkeys: <span class="built_in">bool</span> = <span class="literal">False</span>,</span></span><br><span class="line"><span class="params">    ensure_ascii: <span class="built_in">bool</span> = <span class="literal">True</span>,</span></span><br><span class="line"><span class="params">    check_circular: <span class="built_in">bool</span> = <span class="literal">True</span>,</span></span><br><span class="line"><span class="params">    allow_nan: <span class="built_in">bool</span> = <span class="literal">True</span>,</span></span><br><span class="line"><span class="params">    cls: <span class="built_in">type</span>[JSONEncoder] | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    indent: <span class="built_in">int</span> | <span class="built_in">str</span> | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    separators: <span class="built_in">tuple</span>[<span class="built_in">str</span>, <span class="built_in">str</span>] | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    default: (<span class="params">(<span class="params"><span class="type">Any</span></span>) -&gt; <span class="type">Any</span></span>) | <span class="literal">None</span> = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    sort_keys: <span class="built_in">bool</span> = <span class="literal">False</span>,</span></span><br><span class="line"><span class="params">    **kwds: <span class="type">Any</span></span></span><br><span class="line"><span class="params"></span>) -&gt; <span class="literal">None</span></span><br></pre></td></tr></table></figure><p>速览一下就知道用法了,读json文件时指定文件名,写json文件时指定写入内容和写入文件名就可以了</p><h3 id="处理md文件"><a class="markdownIt-Anchor" href="#处理md文件"></a> 处理md文件</h3><p>md文件没有专门的库,直接读写就可以了</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;weekly_hiring_comments.json&quot;</span>, <span class="string">&quot;r&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    posts = json.load(f)</span><br><span class="line"></span><br><span class="line">out = Path(<span class="string">&quot;weekly_hiring_comments.md&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> out.<span class="built_in">open</span>(<span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    <span class="keyword">for</span> i, p <span class="keyword">in</span> <span class="built_in">enumerate</span>(posts, <span class="number">1</span>):</span><br><span class="line">        f.write(<span class="string">f&quot;## 招聘 <span class="subst">&#123;i&#125;</span>\n\n&quot;</span>)</span><br><span class="line">        f.write(<span class="string">f&quot;- Issue: #<span class="subst">&#123;p[<span class="string">&#x27;issue&#x27;</span>]&#125;</span>\n&quot;</span>)</span><br><span class="line">        f.write(<span class="string">f&quot;- 作者: <span class="subst">&#123;p[<span class="string">&#x27;author&#x27;</span>]&#125;</span>\n&quot;</span>)</span><br><span class="line">        f.write(<span class="string">f&quot;- 时间: <span class="subst">&#123;p[<span class="string">&#x27;created_at&#x27;</span>]&#125;</span>\n&quot;</span>)</span><br><span class="line">        f.write(<span class="string">f&quot;- 来源: <span class="subst">&#123;p[<span class="string">&#x27;url&#x27;</span>]&#125;</span>\n\n&quot;</span>)</span><br><span class="line">        f.write(p[<span class="string">&quot;text&quot;</span>])</span><br><span class="line">        f.write(<span class="string">&quot;\n\n---\n\n&quot;</span>)</span><br></pre></td></tr></table></figure><h2 id="pathlib库"><a class="markdownIt-Anchor" href="#pathlib库"></a> pathlib库</h2><p><strong>该库在不同平台下都能轻松读取文件路径</strong>,而不需要操心系统问题或者字符串问题.</p><ul><li><a href="https://docs.python.org/zh-cn/3/library/pathlib.html">官方文档</a></li></ul><blockquote><p>如果以前从未用过此模块，或不确定哪个类适合完成任务，那要用的可能就是 Path。它在运行代码的平台上实例化为具体路径.</p></blockquote><p>接下来我们来详细介绍这个Path对象</p><h3 id="path对象的创建"><a class="markdownIt-Anchor" href="#path对象的创建"></a> Path对象的创建</h3><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"></span><br><span class="line"><span class="comment"># 基础用法</span></span><br><span class="line">out_file: Path = Path(<span class="string">&quot;a.md&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 拼接路径的两种写法</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 简写</span></span><br><span class="line">out_file: Path = Path(<span class="string">&quot;modules&quot;</span>) / <span class="string">&quot;a.py&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 分开写</span></span><br><span class="line">out_dir: Path = Path(<span class="string">&quot;modules&quot;</span>)</span><br><span class="line">out_file: Path = out_dir / <span class="string">&quot;issues.md&quot;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>上述的代码由于没有指定绝对路径,故都是相对于python运行目录的路径,但我们也可以指定绝对路径,如下文所示:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"></span><br><span class="line"><span class="comment"># Windows 风格</span></span><br><span class="line">abs_path_win = Path(<span class="string">&quot;C:/Users/Admin/Desktop/a.md&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Linux/macOS 风格</span></span><br><span class="line">abs_path_unix = Path(<span class="string">&quot;/home/user/project/a.md&quot;</span>)</span><br></pre></td></tr></table></figure><p>也就是说,我们不需要再去折腾不同操作系统的路径问题了,统一用<code>/</code>就可以确定相对的路径.</p><h3 id="使用path来创建文件夹"><a class="markdownIt-Anchor" href="#使用path来创建文件夹"></a> 使用Path来创建文件夹</h3><p>只需要调用mkdir方法即可:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">out_dir: Path = Path(<span class="string">&quot;issues_md&quot;</span>)</span><br><span class="line">out_dir.mkdir(exist_ok=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><ul><li>exist_ok参数的作用: 默认为False,设置为True时,即便当前路径下有这个文件夹,也不会报错</li></ul><h3 id="path对象的open方法"><a class="markdownIt-Anchor" href="#path对象的open方法"></a> Path对象的open方法</h3><p>事实上,这个open方法与python内置的open方法基本没有区别,只是把文件路径提到外面来了而已:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">out_dir: Path = Path(<span class="string">&quot;issues_md&quot;</span>)</span><br><span class="line">out_dir.mkdir(exist_ok=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> issue, items <span class="keyword">in</span> by_issue.items():</span><br><span class="line">    path = out_dir / <span class="string">f&quot;issue_<span class="subst">&#123;issue&#125;</span>.md&quot;</span></span><br><span class="line">    <span class="keyword">with</span> path.<span class="built_in">open</span>(<span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">        f.write(<span class="string">f&quot;# Issue #<span class="subst">&#123;issue&#125;</span> 招聘汇总\n\n&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="path对象的glob方法待补充"><a class="markdownIt-Anchor" href="#path对象的glob方法待补充"></a> Path对象的glob方法(待补充)</h3><h3 id="实战"><a class="markdownIt-Anchor" href="#实战"></a> 实战</h3><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> defaultdict</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;weekly_hiring_comments.json&quot;</span>, <span class="string">&quot;r&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    posts = json.load(f)</span><br><span class="line"><span class="comment"># 读取json列表</span></span><br><span class="line">by_issue = defaultdict(<span class="built_in">list</span>)</span><br><span class="line"><span class="keyword">for</span> p <span class="keyword">in</span> posts:</span><br><span class="line">    by_issue[p[<span class="string">&quot;issue&quot;</span>]].append(p)</span><br><span class="line"><span class="comment"># 处理为一个有序字典,这在json列表本身是无序的时候比较好用</span></span><br><span class="line">out_dir: Path = Path(<span class="string">&quot;issues_md&quot;</span>)</span><br><span class="line">out_dir.mkdir(exist_ok=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> issue, items <span class="keyword">in</span> by_issue.items():</span><br><span class="line">    path = out_dir / <span class="string">f&quot;issue_<span class="subst">&#123;issue&#125;</span>.md&quot;</span></span><br><span class="line">    <span class="keyword">with</span> path.<span class="built_in">open</span>(<span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">        f.write(<span class="string">f&quot;# Issue #<span class="subst">&#123;issue&#125;</span> 招聘汇总\n\n&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> i, p <span class="keyword">in</span> <span class="built_in">enumerate</span>(items, <span class="number">1</span>):</span><br><span class="line">            f.write(<span class="string">f&quot;## 招聘 <span class="subst">&#123;i&#125;</span>\n\n&quot;</span>)</span><br><span class="line">            f.write(<span class="string">f&quot;- 作者: <span class="subst">&#123;p[<span class="string">&#x27;author&#x27;</span>]&#125;</span>\n&quot;</span>)</span><br><span class="line">            f.write(<span class="string">f&quot;- 时间: <span class="subst">&#123;p[<span class="string">&#x27;created_at&#x27;</span>]&#125;</span>\n&quot;</span>)</span><br><span class="line">            f.write(<span class="string">f&quot;- 来源: <span class="subst">&#123;p[<span class="string">&#x27;url&#x27;</span>]&#125;</span>\n\n&quot;</span>)</span><br><span class="line">            f.write(p[<span class="string">&quot;text&quot;</span>])</span><br><span class="line">            f.write(<span class="string">&quot;\n\n---\n\n&quot;</span>)</span><br></pre></td></tr></table></figure><h2 id="re系统库"><a class="markdownIt-Anchor" href="#re系统库"></a> re系统库</h2><ul><li><a href="https://docs.python.org/zh-tw/3.8/library/re.html">官方文档</a></li></ul><p>该库是对正则表达式(regular expression)的封装,所以叫re.</p><h3 id="compile方法"><a class="markdownIt-Anchor" href="#compile方法"></a> compile方法</h3><p>compile是一个实例化pattern对象的方法,pattern一词在re中指的是正则表达式字符串</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">prog = re.<span class="built_in">compile</span>(pattern)</span><br><span class="line">result = prog.<span class="keyword">match</span>(string)</span><br><span class="line"><span class="comment"># 上述代码等价于下面的这个</span></span><br><span class="line">result = re.<span class="keyword">match</span>(pattern, string)</span><br><span class="line"><span class="comment"># 为了规范化和复用,我们还是多用compile方法来指明pattern对象</span></span><br></pre></td></tr></table></figure><blockquote><p>事实上,re库中的大多数常用方法都有两种写法,一种是<code>模式.方法(参数)</code>,另一种是<code>方法.(模式,参数)</code>.为了规范起见,我们后面都采用<code>模式.方法(参数)</code>写法,就不再次说明了</p></blockquote><h3 id="search方法与match方法"><a class="markdownIt-Anchor" href="#search方法与match方法"></a> search方法与match方法</h3><ul><li><a href="https://www.runoob.com/python/python-reg-expressions.html">菜鸟教程</a></li></ul><blockquote><p>re.match只匹配字符串的开始，如果字符串开始不符合正则表达式，则匹配失败，函数返回None；而re.search匹配整个字符串，直到找到一个匹配。</p></blockquote><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"> </span><br><span class="line">line = <span class="string">&quot;Cats are smarter than dogs&quot;</span>;</span><br><span class="line"> </span><br><span class="line">matchObj = re.<span class="keyword">match</span>( <span class="string">r&#x27;dogs&#x27;</span>, line, re.M|re.I)</span><br><span class="line"><span class="keyword">if</span> matchObj:</span><br><span class="line">   <span class="built_in">print</span> <span class="string">&quot;match --&gt; matchObj.group() : &quot;</span>, matchObj.group()</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">   <span class="built_in">print</span> <span class="string">&quot;No match!!&quot;</span></span><br><span class="line"> </span><br><span class="line">matchObj = re.search( <span class="string">r&#x27;dogs&#x27;</span>, line, re.M|re.I)</span><br><span class="line"><span class="keyword">if</span> matchObj:</span><br><span class="line">   <span class="built_in">print</span> <span class="string">&quot;search --&gt; searchObj.group() : &quot;</span>, matchObj.group()</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">   <span class="built_in">print</span> <span class="string">&quot;No match!!&quot;</span></span><br></pre></td></tr></table></figure><p><strong>运行结果</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">No match!!</span><br><span class="line">search --&gt; searchObj.group() :  dogs</span><br></pre></td></tr></table></figure><h3 id="实战-2"><a class="markdownIt-Anchor" href="#实战-2"></a> 实战</h3><p>下面的整个代码流程为:</p><ol><li>载入json文件为列表posts</li><li>使用compile方法组织匹配模式</li><li>将posts里对应学历要求的帖子中的text字段里的值插入列表中</li><li>导出json文件</li></ol><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;weekly_hiring_comments.json&quot;</span>, <span class="string">&quot;r&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    posts = json.load(f)</span><br><span class="line"></span><br><span class="line">bachelor_patterns = [</span><br><span class="line">    <span class="string">r&quot;本科及以上&quot;</span>,</span><br><span class="line">    <span class="string">r&quot;本科以上&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">master_patterns = [</span><br><span class="line">    <span class="string">r&quot;硕士及以上&quot;</span>,</span><br><span class="line">    <span class="string">r&quot;硕士以上&quot;</span>,</span><br><span class="line">]</span><br><span class="line"><span class="comment"># 这里的r是为了禁用`\`转义符,但这里都是中文,不写也可以,为了规范所以加上了</span></span><br><span class="line"></span><br><span class="line">bachelor_re = re.<span class="built_in">compile</span>(<span class="string">&quot;|&quot;</span>.join(bachelor_patterns))</span><br><span class="line">master_re = re.<span class="built_in">compile</span>(<span class="string">&quot;|&quot;</span>.join(master_patterns))</span><br><span class="line"><span class="comment"># 拼接了两个匹配字符串</span></span><br><span class="line"></span><br><span class="line">bachelor_posts = []</span><br><span class="line">master_posts = []</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> p <span class="keyword">in</span> posts:</span><br><span class="line">    <span class="comment"># 这个p是列表的子元素,在这里为字典</span></span><br><span class="line">    text = p.get(<span class="string">&quot;text&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    <span class="comment"># get方法的第一个参数是,查找该字典中的对应字段并返回值,第二个参数是,若查找不到返回的默认值</span></span><br><span class="line">    <span class="keyword">if</span> master_re.search(text):</span><br><span class="line">        master_posts.append(p)</span><br><span class="line">    <span class="keyword">elif</span> bachelor_re.search(text):</span><br><span class="line">        bachelor_posts.append(p)</span><br><span class="line"></span><br><span class="line"><span class="comment"># === 输出目录 ===</span></span><br><span class="line">out_dir = Path(<span class="string">&quot;degree_split&quot;</span>)</span><br><span class="line">out_dir.mkdir(exist_ok=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># === 写文件 ===</span></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(out_dir / <span class="string">&quot;本科及以上.json&quot;</span>, <span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    json.dump(bachelor_posts, f, ensure_ascii=<span class="literal">False</span>, indent=<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(out_dir / <span class="string">&quot;硕士及以上.json&quot;</span>, <span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    json.dump(master_posts, f, ensure_ascii=<span class="literal">False</span>, indent=<span class="number">2</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="读取env文件"><a class="markdownIt-Anchor" href="#读取env文件"></a> 读取.env文件</h2><p>对于密码,API密钥这些文件,用json文件存取不够方便也不够安全,因此我们有了.env文件,样式如下:</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># github token</span></span><br><span class="line"><span class="attr">token</span>=<span class="string">&quot;ghp_xxxxxxxxxxxx&quot;</span></span><br></pre></td></tr></table></figure><p>当我们想要读取这个.env文件中的token字段时,我们可以导入dotenv库和os库来进行简单的读取:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> dotenv <span class="keyword">import</span> load_dotenv</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line">load_dotenv()</span><br><span class="line">TOKEN = os.getenv(<span class="string">&quot;token&quot;</span>)</span><br></pre></td></tr></table></figure><p><code>load_dotenv()</code>函数会递归寻找.env文件并返回内容供os库读取,从而避免了写路径的麻烦.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;不会用python来读取文件的话,我想是不太可能会学会爬虫的,不然你怎么处理获取的数据&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文所用的weekly_hiring_comments.json示例的结构如下:&lt;/p&gt;
&lt;figure class=</summary>
      
    
    
    
    
    <category term="python" scheme="https://revival-of-hope.github.io/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-27 配置文件一次讲清</title>
    <link href="https://revival-of-hope.github.io/2026/03/27/archives-2026-1-2026-03-27-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E4%B8%80%E6%AC%A1%E8%AE%B2%E6%B8%85/"/>
    <id>https://revival-of-hope.github.io/2026/03/27/archives-2026-1-2026-03-27-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E4%B8%80%E6%AC%A1%E8%AE%B2%E6%B8%85/</id>
    <published>2026-03-27T00:00:00.000Z</published>
    <updated>2026-04-03T14:40:06.204Z</updated>
    
    <content type="html"><![CDATA[<ul><li>(待补充)<br />事实上,当你刚入门一个架构或者语言时,理解代码永远不是最难的地方,真正困难的一关是各种各样的配置文件!<br /><img src="/images/archives/2026/2026-03-21/PixPin_2026-03-23_21-43-36.webp" alt="alt text" /></li><li>你能想象一个编程小白看到这些配置文件的绝望感吗…(尽管小白未必会接触到这么复杂的配置结构)<br />我们先来梳理一下到底有多少种<strong>主流</strong>配置文件,就知道为什么我这么讲了.</li><li>以下的分类仅仅是为了我个人处理的方便,可能不够严谨</li></ul><h1 id="配置文件分类"><a class="markdownIt-Anchor" href="#配置文件分类"></a> 配置文件分类</h1><h2 id="版本控制"><a class="markdownIt-Anchor" href="#版本控制"></a> 版本控制</h2><h3 id="git"><a class="markdownIt-Anchor" href="#git"></a> git</h3><p><strong>.gitignore</strong></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">.vscode/*</span><br><span class="line">!.vscode/extensions.json</span><br><span class="line">node_modules/</span><br><span class="line">/test-results/</span><br><span class="line">/playwright-report/</span><br><span class="line">/blob-report/</span><br><span class="line">/playwright/.cache/</span><br></pre></td></tr></table></figure><ul><li>过滤掉不需要加入版本控制的文件<ul><li>前一个<code>/</code>表示根目录,后一个<code>/</code>表示文件夹<br /><strong>.gitattributes</strong></li></ul></li></ul><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">* text=auto</span><br><span class="line">*.sh text eol=lf</span><br></pre></td></tr></table></figure><ul><li>定义特定路径或者文件属性<br /><strong>.gitkeep</strong></li><li>保留需要的空文件夹</li></ul><h3 id="contributingmd"><a class="markdownIt-Anchor" href="#contributingmd"></a> <a href="http://CONTRIBUTING.md">CONTRIBUTING.md</a></h3><p>这个应该还是很好懂的,大型开源项目都要用这个md文件来告诉别人少发没用的pull request.</p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># Contributing</span></span><br><span class="line"></span><br><span class="line">Thank you for your interest in contributing to the Full Stack FastAPI Template! 🙇</span><br><span class="line"></span><br><span class="line"><span class="section">## Discussions First</span></span><br><span class="line"></span><br><span class="line">For <span class="strong">**big changes**</span> (new features, architectural changes, significant refactoring), please start by opening a [<span class="string">GitHub Discussion</span>](<span class="link">https://github.com/fastapi/full-stack-fastapi-template/discussions</span>) first. This allows the community and maintainers to provide feedback on the approach before you invest significant time in implementation.</span><br><span class="line"></span><br><span class="line">For small, straightforward changes, you can go directly to a Pull Request without starting a discussion first. This includes:</span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> Typos and grammatical fixes</span><br><span class="line"><span class="bullet">-</span> Small reproducible bug fixes</span><br><span class="line"><span class="bullet">-</span> Fixing lint warnings or type errors</span><br><span class="line"><span class="bullet">-</span> Minor code improvements (e.g., removing unused code)</span><br></pre></td></tr></table></figure><h3 id="license"><a class="markdownIt-Anchor" href="#license"></a> LICENSE</h3><p>做开源项目不可或缺的一部分就是选一个符合自身理念的许可证</p><ul><li>如果你的项目不指明任何LICENSE,默认所有人都不可以借用你的代码库<br /><strong>LICENSE</strong></li></ul><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">MIT License</span><br><span class="line"></span><br><span class="line">Copyright (c) 2019 Sebastián Ramírez</span><br><span class="line"></span><br><span class="line">Permission is hereby granted, free of charge, to any person obtaining a copy</span><br><span class="line">of this software and associated documentation files (the &quot;Software&quot;), to deal</span><br><span class="line">in the Software without restriction, including without limitation the rights</span><br><span class="line">to use, copy, modify, merge, publish, distribute, sublicense, and/or sell</span><br><span class="line">copies of the Software, and to permit persons to whom the Software is</span><br><span class="line">furnished to do so, subject to the following conditions:</span><br><span class="line"></span><br><span class="line">The above copyright notice and this permission notice shall be included in all</span><br><span class="line">copies or substantial portions of the Software.</span><br><span class="line"></span><br><span class="line">THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR</span><br><span class="line">IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,</span><br><span class="line">FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE</span><br><span class="line">AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER</span><br><span class="line">LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,</span><br><span class="line">OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE</span><br><span class="line">SOFTWARE.</span><br></pre></td></tr></table></figure><h2 id="部署"><a class="markdownIt-Anchor" href="#部署"></a> 部署</h2><h3 id="copier"><a class="markdownIt-Anchor" href="#copier"></a> copier</h3><p><strong>copier.yml</strong><br />简单介绍一下这个python库:</p><blockquote><p>It will copy all the files, ask you configuration questions, and update the <code>.env</code> files with your answers.<br />然后只要这么用就可以生成一个新项目了:</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">copier copy https://github.com/fastapi/full-stack-fastapi-template my-awesome-project --trust</span><br></pre></td></tr></table></figure><p><strong>copier.yml</strong></p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">project_name:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">The</span> <span class="string">name</span> <span class="string">of</span> <span class="string">the</span> <span class="string">project,</span> <span class="string">shown</span> <span class="string">to</span> <span class="string">API</span> <span class="string">users</span> <span class="string">(in</span> <span class="string">.env)</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">FastAPI</span> <span class="string">Project</span></span><br><span class="line"></span><br><span class="line"><span class="attr">stack_name:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">The</span> <span class="string">name</span> <span class="string">of</span> <span class="string">the</span> <span class="string">stack</span> <span class="string">used</span> <span class="string">for</span> <span class="string">Docker</span> <span class="string">Compose</span> <span class="string">labels</span> <span class="string">(no</span> <span class="string">spaces)</span> <span class="string">(in</span> <span class="string">.env)</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">fastapi-project</span></span><br><span class="line"></span><br><span class="line"><span class="attr">secret_key:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    &#x27;The secret key for the project, used for security,</span></span><br><span class="line"><span class="string">    stored in .env, you can generate one with:</span></span><br><span class="line"><span class="string">    python -c &quot;import secrets; print(secrets.token_urlsafe(32))&quot;&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">default:</span> <span class="string">changethis</span></span><br><span class="line"></span><br><span class="line"><span class="attr">first_superuser:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">The</span> <span class="string">email</span> <span class="string">of</span> <span class="string">the</span> <span class="string">first</span> <span class="string">superuser</span> <span class="string">(in</span> <span class="string">.env)</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">admin@example.com</span></span><br><span class="line"></span><br><span class="line"><span class="attr">first_superuser_password:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">The</span> <span class="string">password</span> <span class="string">of</span> <span class="string">the</span> <span class="string">first</span> <span class="string">superuser</span> <span class="string">(in</span> <span class="string">.env)</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">changethis</span></span><br><span class="line"></span><br><span class="line"><span class="attr">smtp_host:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">The</span> <span class="string">SMTP</span> <span class="string">server</span> <span class="string">host</span> <span class="string">to</span> <span class="string">send</span> <span class="string">emails,</span> <span class="string">you</span> <span class="string">can</span> <span class="string">set</span> <span class="string">it</span> <span class="string">later</span> <span class="string">in</span> <span class="string">.env</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">smtp_user:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">The</span> <span class="string">SMTP</span> <span class="string">server</span> <span class="string">user</span> <span class="string">to</span> <span class="string">send</span> <span class="string">emails,</span> <span class="string">you</span> <span class="string">can</span> <span class="string">set</span> <span class="string">it</span> <span class="string">later</span> <span class="string">in</span> <span class="string">.env</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">smtp_password:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">The</span> <span class="string">SMTP</span> <span class="string">server</span> <span class="string">password</span> <span class="string">to</span> <span class="string">send</span> <span class="string">emails,</span> <span class="string">you</span> <span class="string">can</span> <span class="string">set</span> <span class="string">it</span> <span class="string">later</span> <span class="string">in</span> <span class="string">.env</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">emails_from_email:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">The</span> <span class="string">email</span> <span class="string">account</span> <span class="string">to</span> <span class="string">send</span> <span class="string">emails</span> <span class="string">from,</span> <span class="string">you</span> <span class="string">can</span> <span class="string">set</span> <span class="string">it</span> <span class="string">later</span> <span class="string">in</span> <span class="string">.env</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">info@example.com</span></span><br><span class="line"></span><br><span class="line"><span class="attr">postgres_password:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    &#x27;The password for the PostgreSQL database, stored in .env,</span></span><br><span class="line"><span class="string">    you can generate one with:</span></span><br><span class="line"><span class="string">    python -c &quot;import secrets; print(secrets.token_urlsafe(32))&quot;&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">default:</span> <span class="string">changethis</span></span><br><span class="line"></span><br><span class="line"><span class="attr">sentry_dsn:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">str</span></span><br><span class="line">  <span class="attr">help:</span> <span class="string">The</span> <span class="string">DSN</span> <span class="string">for</span> <span class="string">Sentry,</span> <span class="string">if</span> <span class="string">you</span> <span class="string">are</span> <span class="string">using</span> <span class="string">it,</span> <span class="string">you</span> <span class="string">can</span> <span class="string">set</span> <span class="string">it</span> <span class="string">later</span> <span class="string">in</span> <span class="string">.env</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">_exclude:</span></span><br><span class="line">  <span class="comment"># Global</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.vscode</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.mypy_cache</span></span><br><span class="line">  <span class="comment"># Python</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">__pycache__</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">app.egg-info</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;*.pyc&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.mypy_cache</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.coverage</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">htmlcov</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.cache</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.venv</span></span><br><span class="line">  <span class="comment"># Frontend</span></span><br><span class="line">  <span class="comment"># Logs</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">logs</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;*.log&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">npm-debug.log*</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">yarn-debug.log*</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">yarn-error.log*</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">pnpm-debug.log*</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">lerna-debug.log*</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">node_modules</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">dist</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">dist-ssr</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;*.local&quot;</span></span><br><span class="line">  <span class="comment"># Editor directories and files</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.idea</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.DS_Store</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;*.suo&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;*.ntvs*&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;*.njsproj&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;*.sln&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;*.sw?&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">_answers_file:</span> <span class="string">.copier/.copier-answers.yml</span></span><br><span class="line"></span><br><span class="line"><span class="attr">_tasks:</span></span><br><span class="line">  <span class="bullet">-</span> [<span class="string">&quot;<span class="template-variable">&#123;&#123; _copier_python &#125;&#125;</span>&quot;</span>, <span class="string">.copier/update_dotenv.py</span>]</span><br></pre></td></tr></table></figure><h3 id="docker"><a class="markdownIt-Anchor" href="#docker"></a> docker</h3><p><strong>.dockerfile</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> python:<span class="number">3.10</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENV</span> PYTHONUNBUFFERED=<span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Install uv</span></span><br><span class="line"><span class="comment"># Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=ghcr.io/astral-sh/uv:0.9.26 /uv /uvx /bin/</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Compile bytecode</span></span><br><span class="line"><span class="comment"># Ref: https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode</span></span><br><span class="line"><span class="keyword">ENV</span> UV_COMPILE_BYTECODE=<span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># uv Cache</span></span><br><span class="line"><span class="comment"># Ref: https://docs.astral.sh/uv/guides/integration/docker/#caching</span></span><br><span class="line"><span class="keyword">ENV</span> UV_LINK_MODE=<span class="keyword">copy</span><span class="language-bash"></span></span><br><span class="line"><span class="language-bash"></span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app/</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Place executables in the environment at the front of the path</span></span><br><span class="line"><span class="comment"># Ref: https://docs.astral.sh/uv/guides/integration/docker/#using-the-environment</span></span><br><span class="line"><span class="keyword">ENV</span> PATH=<span class="string">&quot;/app/.venv/bin:$PATH&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Install dependencies</span></span><br><span class="line"><span class="comment"># Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> --mount=<span class="built_in">type</span>=cache,target=/root/.cache/uv \</span></span><br><span class="line"><span class="language-bash">    --mount=<span class="built_in">type</span>=<span class="built_in">bind</span>,<span class="built_in">source</span>=uv.lock,target=uv.lock \</span></span><br><span class="line"><span class="language-bash">    --mount=<span class="built_in">type</span>=<span class="built_in">bind</span>,<span class="built_in">source</span>=pyproject.toml,target=pyproject.toml \</span></span><br><span class="line"><span class="language-bash">    uv <span class="built_in">sync</span> --frozen --no-install-workspace --package app</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> ./backend/scripts /app/backend/scripts</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> ./backend/pyproject.toml ./backend/alembic.ini /app/backend/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> ./backend/app /app/backend/app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Sync the project</span></span><br><span class="line"><span class="comment"># Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> --mount=<span class="built_in">type</span>=cache,target=/root/.cache/uv \</span></span><br><span class="line"><span class="language-bash">    --mount=<span class="built_in">type</span>=<span class="built_in">bind</span>,<span class="built_in">source</span>=uv.lock,target=uv.lock \</span></span><br><span class="line"><span class="language-bash">    --mount=<span class="built_in">type</span>=<span class="built_in">bind</span>,<span class="built_in">source</span>=pyproject.toml,target=pyproject.toml \</span></span><br><span class="line"><span class="language-bash">    uv <span class="built_in">sync</span> --frozen --package app</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app/backend/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;fastapi&quot;</span>, <span class="string">&quot;run&quot;</span>, <span class="string">&quot;--workers&quot;</span>, <span class="string">&quot;4&quot;</span>, <span class="string">&quot;app/main.py&quot;</span>]</span></span><br></pre></td></tr></table></figure><ul><li>可以简单理解为一个docker专用的运行脚本,还是很好看懂的</li></ul><p><strong>.dockerignore</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Python</span></span><br><span class="line">__pycache__</span><br><span class="line">app.egg-info</span><br><span class="line">*.pyc</span><br><span class="line">.mypy_cache</span><br><span class="line">.coverage</span><br><span class="line">htmlcov</span><br><span class="line">.venv</span><br></pre></td></tr></table></figure><ul><li>在构建时忽略的文件和文件夹</li></ul><p><strong>compose.yml</strong></p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="comment"># ---- PostgreSQL + pgvector 数据库 ----</span></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">pgvector/pgvector:pg16</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">env_file:</span> <span class="string">.env</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">POSTGRES_USER:</span> <span class="string">$&#123;POSTGRES_USER&#125;</span></span><br><span class="line">      <span class="attr">POSTGRES_PASSWORD:</span> <span class="string">$&#123;POSTGRES_PASSWORD&#125;</span></span><br><span class="line">      <span class="attr">POSTGRES_DB:</span> <span class="string">$&#123;POSTGRES_DB&#125;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">postgres_data:/var/lib/postgresql/data</span></span><br><span class="line">    <span class="attr">healthcheck:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD-SHELL&quot;</span>, <span class="string">&quot;pg_isready -U $&#123;POSTGRES_USER&#125; -d $&#123;POSTGRES_DB&#125;&quot;</span>]</span><br><span class="line">      <span class="attr">interval:</span> <span class="string">10s</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">5s</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">5</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># ---- Adminer 数据库管理界面 ----</span></span><br><span class="line">  <span class="attr">adminer:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">adminer:latest</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8081:8080&quot;</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="attr">db:</span></span><br><span class="line">        <span class="attr">condition:</span> <span class="string">service_healthy</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">ADMINER_DEFAULT_SERVER:</span> <span class="string">db</span></span><br><span class="line">      <span class="attr">ADMINER_DESIGN:</span> <span class="string">pepa-linha</span></span><br></pre></td></tr></table></figure><p>鉴于一个个运行容器并配置环境过于痛苦,于是我们又有了compose.yml这一神器,能够轻松的统合多个容器并实现通信</p><p>当然我们有时候会看到<code>compose.override.yml</code>这个文件,作用原理如下所述.</p><blockquote><p>当你运行 docker compose up 而不指定文件时，Docker 引擎会按照以下物理顺序自动寻找并合并文件：</p></blockquote><ol><li>docker-compose.yml（基础配置：定义通用服务、镜像、网络）</li><li>docker-compose.override.yml（覆盖配置：物理修改或增加基础文件中的项）</li></ol><blockquote><p>核心规则：如果两个文件定义了相同的配置项，override 文件中的值会物理覆盖基础文件；如果是列表（如 ports 或 volumes），则会进行物理追加合并。</p></blockquote><p><strong>.env</strong></p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Domain</span></span><br><span class="line"><span class="comment"># This would be set to the production domain with an env var on deployment</span></span><br><span class="line"><span class="comment"># used by Traefik to transmit traffic and aqcuire TLS certificates</span></span><br><span class="line"><span class="string">DOMAIN=localhost</span></span><br><span class="line"><span class="comment"># To test the local Traefik config</span></span><br><span class="line"><span class="comment"># DOMAIN=localhost.tiangolo.com</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Used by the backend to generate links in emails to the frontend</span></span><br><span class="line"><span class="string">FRONTEND_HOST=http://localhost:5173</span></span><br><span class="line"><span class="comment"># In staging and production, set this env var to the frontend host, e.g.</span></span><br><span class="line"><span class="comment"># FRONTEND_HOST=https://dashboard.example.com</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Environment: local, staging, production</span></span><br><span class="line"><span class="string">ENVIRONMENT=local</span></span><br><span class="line"></span><br><span class="line"><span class="string">PROJECT_NAME=&quot;Full</span> <span class="string">Stack</span> <span class="string">FastAPI</span> <span class="string">Project&quot;</span></span><br><span class="line"><span class="string">STACK_NAME=full-stack-fastapi-project</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Backend</span></span><br><span class="line"><span class="string">BACKEND_CORS_ORIGINS=&quot;http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com&quot;</span></span><br><span class="line"><span class="string">SECRET_KEY=changethis</span></span><br><span class="line"><span class="string">FIRST_SUPERUSER=admin@example.com</span></span><br><span class="line"><span class="string">FIRST_SUPERUSER_PASSWORD=changethis</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Emails</span></span><br><span class="line"><span class="string">SMTP_HOST=</span></span><br><span class="line"><span class="string">SMTP_USER=</span></span><br><span class="line"><span class="string">SMTP_PASSWORD=</span></span><br><span class="line"><span class="string">EMAILS_FROM_EMAIL=info@example.com</span></span><br><span class="line"><span class="string">SMTP_TLS=True</span></span><br><span class="line"><span class="string">SMTP_SSL=False</span></span><br><span class="line"><span class="string">SMTP_PORT=587</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Postgres</span></span><br><span class="line"><span class="string">POSTGRES_SERVER=localhost</span></span><br><span class="line"><span class="string">POSTGRES_PORT=5432</span></span><br><span class="line"><span class="string">POSTGRES_DB=app</span></span><br><span class="line"><span class="string">POSTGRES_USER=postgres</span></span><br><span class="line"><span class="string">POSTGRES_PASSWORD=changethis</span></span><br><span class="line"></span><br><span class="line"><span class="string">SENTRY_DSN=</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Configure these with your own Docker registry images</span></span><br><span class="line"><span class="string">DOCKER_IMAGE_BACKEND=backend</span></span><br><span class="line"><span class="string">DOCKER_IMAGE_FRONTEND=frontend</span></span><br></pre></td></tr></table></figure><p>尽管这个文件并非docker独有,但在docker配置环境,设置密码和账户的时候是非常有用的</p><h2 id="前端"><a class="markdownIt-Anchor" href="#前端"></a> 前端</h2><h3 id="nodejs"><a class="markdownIt-Anchor" href="#nodejs"></a> nodejs</h3><p><strong>package.json</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;frontend&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;private&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;next dev&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;next build&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;start&quot;</span><span class="punctuation">:</span> <span class="string">&quot;next start&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;next lint&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;next&quot;</span><span class="punctuation">:</span> <span class="string">&quot;14.2.4&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;react&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^18&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;react-dom&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^18&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;zustand&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.5.4&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;react-markdown&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^9.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;remark-gfm&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;react-syntax-highlighter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^15.5.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;react-textarea-autosize&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8.5.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lucide-react&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^0.400.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;clsx&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.1.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;tailwind-merge&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.4.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventsource-parser&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.0.0&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;typescript&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@types/node&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^20&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@types/react&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^18&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@types/react-dom&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^18&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@types/react-syntax-highlighter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^15.5.13&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;postcss&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;tailwindcss&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.4.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;autoprefixer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^10.4.19&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eslint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eslint-config-next&quot;</span><span class="punctuation">:</span> <span class="string">&quot;14.2.4&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li>管理前端依赖,定义调试脚本</li></ul><p><strong>package-lock.json / pnpm-lock.yaml / yarn.lock</strong><br />分别为npm,pnpm,yarn三个nodejs包管理器的依赖锁定文件,详细记录了所用到的依赖项的具体参数</p><p><strong>node_modules/</strong><br />自然,还有臃肿到可怕的包存放文件夹</p><p><strong>tsconfig.json</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ES2020&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;useDefineForClassFields&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lib&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;ES2020&quot;</span><span class="punctuation">,</span> <span class="string">&quot;DOM&quot;</span><span class="punctuation">,</span> <span class="string">&quot;DOM.Iterable&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;module&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ESNext&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;skipLibCheck&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">/* Bundler mode */</span></span><br><span class="line">    <span class="attr">&quot;moduleResolution&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bundler&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allowImportingTsExtensions&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;resolveJsonModule&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;isolatedModules&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;noEmit&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;jsx&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-jsx&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">/* Linting */</span></span><br><span class="line">    <span class="attr">&quot;strict&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;noUnusedLocals&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;noUnusedParameters&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;noFallthroughCasesInSwitch&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;baseUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;.&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;paths&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;@/*&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;./src/*&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;include&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;src&quot;</span><span class="punctuation">,</span> <span class="string">&quot;tests&quot;</span><span class="punctuation">,</span> <span class="string">&quot;playwright.config.ts&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;references&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./tsconfig.node.json&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>ts项目特有,指定了编译项目所需的根目录下的文件以及编译选项。</p><h4 id="vite"><a class="markdownIt-Anchor" href="#vite"></a> vite</h4><p><strong>vite.config.ts</strong></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> path <span class="keyword">from</span> <span class="string">&quot;node:path&quot;</span></span><br><span class="line"><span class="keyword">import</span> tailwindcss <span class="keyword">from</span> <span class="string">&quot;@tailwindcss/vite&quot;</span></span><br><span class="line"><span class="keyword">import</span> &#123; tanstackRouter &#125; <span class="keyword">from</span> <span class="string">&quot;@tanstack/router-plugin/vite&quot;</span></span><br><span class="line"><span class="keyword">import</span> react <span class="keyword">from</span> <span class="string">&quot;@vitejs/plugin-react-swc&quot;</span></span><br><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&quot;vite&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// https://vitejs.dev/config/</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">  <span class="attr">resolve</span>: &#123;</span><br><span class="line">    <span class="attr">alias</span>: &#123;</span><br><span class="line">      <span class="string">&quot;@&quot;</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;./src&quot;</span>),</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="title function_">tanstackRouter</span>(&#123;</span><br><span class="line">      <span class="attr">target</span>: <span class="string">&quot;react&quot;</span>,</span><br><span class="line">      <span class="attr">autoCodeSplitting</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="title function_">react</span>(),</span><br><span class="line">    <span class="title function_">tailwindcss</span>(),</span><br><span class="line">  ],</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><ul><li>用来配置路径和插件</li></ul><h4 id="next"><a class="markdownIt-Anchor" href="#next"></a> next</h4><p><strong>next.config.js</strong><br />nextjs项目专用</p><h3 id="bun"><a class="markdownIt-Anchor" href="#bun"></a> bun</h3><p><strong>bun.lock</strong><br />使用bun来进行包管理产生的锁文件<br /><strong>bunfig.toml</strong></p><blockquote><p>通常，Bun 依赖于已有的配置文件（如 package.json 和 tsconfig.json）来配置其行为。bunfig.toml 仅用于配置 Bun 特定的内容。此文件是可选的，Bun 在没有它时也能正常工作。</p></blockquote><h2 id="后端"><a class="markdownIt-Anchor" href="#后端"></a> 后端</h2><h3 id="python"><a class="markdownIt-Anchor" href="#python"></a> python</h3><p><strong>.venv/</strong><br />虚拟环境文件夹,里面有python包和特定版本的python</p><h4 id="uv"><a class="markdownIt-Anchor" href="#uv"></a> uv</h4><p><strong>pyproject.toml</strong></p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[project]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">&quot;app&quot;</span></span><br><span class="line"><span class="attr">version</span> = <span class="string">&quot;0.1.0&quot;</span></span><br><span class="line"><span class="attr">description</span> = <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="attr">requires-python</span> = <span class="string">&quot;&gt;=3.10,&lt;4.0&quot;</span></span><br><span class="line"><span class="attr">dependencies</span> = [</span><br><span class="line">    <span class="string">&quot;fastapi[standard]&lt;1.0.0,&gt;=0.114.2&quot;</span>,</span><br><span class="line">    <span class="string">&quot;python-multipart&lt;1.0.0,&gt;=0.0.7&quot;</span>,</span><br><span class="line">    <span class="string">&quot;email-validator&lt;3.0.0.0,&gt;=2.1.0.post1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;tenacity&lt;9.0.0,&gt;=8.2.3&quot;</span>,</span><br><span class="line">    <span class="string">&quot;pydantic&gt;2.0&quot;</span>,</span><br><span class="line">    <span class="string">&quot;emails&lt;1.0,&gt;=0.6&quot;</span>,</span><br><span class="line">    <span class="string">&quot;jinja2&lt;4.0.0,&gt;=3.1.4&quot;</span>,</span><br><span class="line">    <span class="string">&quot;alembic&lt;2.0.0,&gt;=1.12.1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;httpx&lt;1.0.0,&gt;=0.25.1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;psycopg[binary]&lt;4.0.0,&gt;=3.1.13&quot;</span>,</span><br><span class="line">    <span class="string">&quot;sqlmodel&lt;1.0.0,&gt;=0.0.21&quot;</span>,</span><br><span class="line">    <span class="string">&quot;pydantic-settings&lt;3.0.0,&gt;=2.2.1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;sentry-sdk[fastapi]&lt;2.0.0,&gt;=1.40.6&quot;</span>,</span><br><span class="line">    <span class="string">&quot;pyjwt&lt;3.0.0,&gt;=2.8.0&quot;</span>,</span><br><span class="line">    <span class="string">&quot;pwdlib[argon2,bcrypt]&gt;=0.3.0&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="section">[dependency-groups]</span></span><br><span class="line"><span class="attr">dev</span> = [</span><br><span class="line">    <span class="string">&quot;pytest&lt;8.0.0,&gt;=7.4.3&quot;</span>,</span><br><span class="line">    <span class="string">&quot;mypy&lt;2.0.0,&gt;=1.8.0&quot;</span>,</span><br><span class="line">    <span class="string">&quot;ruff&lt;1.0.0,&gt;=0.2.2&quot;</span>,</span><br><span class="line">    <span class="string">&quot;prek&gt;=0.2.24,&lt;1.0.0&quot;</span>,</span><br><span class="line">    <span class="string">&quot;coverage&lt;8.0.0,&gt;=7.4.3&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="section">[build-system]</span></span><br><span class="line"><span class="attr">requires</span> = [<span class="string">&quot;hatchling&quot;</span>]</span><br><span class="line"><span class="attr">build-backend</span> = <span class="string">&quot;hatchling.build&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[tool.mypy]</span></span><br><span class="line"><span class="attr">strict</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">exclude</span> = [<span class="string">&quot;venv&quot;</span>, <span class="string">&quot;.venv&quot;</span>, <span class="string">&quot;alembic&quot;</span>]</span><br><span class="line"></span><br><span class="line"><span class="section">[tool.ruff]</span></span><br><span class="line"><span class="attr">target-version</span> = <span class="string">&quot;py310&quot;</span></span><br><span class="line"><span class="attr">exclude</span> = [<span class="string">&quot;alembic&quot;</span>]</span><br><span class="line"></span><br><span class="line"><span class="section">[tool.ruff.lint]</span></span><br><span class="line"><span class="attr">select</span> = [</span><br><span class="line">    <span class="string">&quot;E&quot;</span>,  <span class="comment"># pycodestyle errors</span></span><br><span class="line">    <span class="string">&quot;W&quot;</span>,  <span class="comment"># pycodestyle warnings</span></span><br><span class="line">    <span class="string">&quot;F&quot;</span>,  <span class="comment"># pyflakes</span></span><br><span class="line">    <span class="string">&quot;I&quot;</span>,  <span class="comment"># isort</span></span><br><span class="line">    <span class="string">&quot;B&quot;</span>,  <span class="comment"># flake8-bugbear</span></span><br><span class="line">    <span class="string">&quot;C4&quot;</span>,  <span class="comment"># flake8-comprehensions</span></span><br><span class="line">    <span class="string">&quot;UP&quot;</span>,  <span class="comment"># pyupgrade</span></span><br><span class="line">    <span class="string">&quot;ARG001&quot;</span>, <span class="comment"># unused arguments in functions</span></span><br><span class="line">    <span class="string">&quot;T201&quot;</span>,   <span class="comment"># print statements are not allowed</span></span><br><span class="line">]</span><br><span class="line"><span class="attr">ignore</span> = [</span><br><span class="line">    <span class="string">&quot;E501&quot;</span>,  <span class="comment"># line too long, handled by black</span></span><br><span class="line">    <span class="string">&quot;B008&quot;</span>,  <span class="comment"># do not perform function calls in argument defaults</span></span><br><span class="line">    <span class="string">&quot;W191&quot;</span>,  <span class="comment"># indentation contains tabs</span></span><br><span class="line">    <span class="string">&quot;B904&quot;</span>,  <span class="comment"># Allow raising exceptions without from e, for HTTPException</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="section">[tool.ruff.lint.pyupgrade]</span></span><br><span class="line"><span class="comment"># Preserve types, even if a file imports `from __future__ import annotations`.</span></span><br><span class="line"><span class="attr">keep-runtime-typing</span> = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="section">[tool.coverage.run]</span></span><br><span class="line"><span class="attr">source</span> = [<span class="string">&quot;app&quot;</span>]</span><br><span class="line"><span class="attr">dynamic_context</span> = <span class="string">&quot;test_function&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[tool.coverage.report]</span></span><br><span class="line"><span class="attr">show_missing</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">sort</span> = <span class="string">&quot;-Cover&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[tool.coverage.html]</span></span><br><span class="line"><span class="attr">show_contexts</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>管理python包环境<br /><strong>uv.lock</strong><br />python包的锁定文件</p><p><strong>.python-version</strong><br />指示该项目使用的python版本</p><h4 id="pip"><a class="markdownIt-Anchor" href="#pip"></a> pip</h4><p><strong>requirements.txt</strong><br />一次导入大量包,虽然uv也可以用这个文件来导入</p><h4 id="alembic"><a class="markdownIt-Anchor" href="#alembic"></a> alembic</h4><p>简单介绍一下Alembic,它是 SQLAlchemy 的“版本控制系统”,将数据库表结构的变更（DDL）物理抽象为一系列有序的 Python 脚本<br /><strong>alembic.ini</strong></p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># A generic, single database configuration.</span></span><br><span class="line"></span><br><span class="line"><span class="section">[alembic]</span></span><br><span class="line"><span class="comment"># path to migration scripts</span></span><br><span class="line"><span class="attr">script_location</span> = app/alembic</span><br><span class="line"></span><br><span class="line"><span class="comment"># template used to generate migration files</span></span><br><span class="line"><span class="comment"># file_template = %%(rev)s_%%(slug)s</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># timezone to use when rendering the date</span></span><br><span class="line"><span class="comment"># within the migration file as well as the filename.</span></span><br><span class="line"><span class="comment"># string value is passed to dateutil.tz.gettz()</span></span><br><span class="line"><span class="comment"># leave blank for localtime</span></span><br><span class="line"><span class="comment"># timezone =</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># max length of characters to apply to the</span></span><br><span class="line"><span class="comment"># &quot;slug&quot; field</span></span><br><span class="line"><span class="comment">#truncate_slug_length = 40</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># set to &#x27;true&#x27; to run the environment during</span></span><br><span class="line"><span class="comment"># the &#x27;revision&#x27; command, regardless of autogenerate</span></span><br><span class="line"><span class="comment"># revision_environment = false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># set to &#x27;true&#x27; to allow .pyc and .pyo files without</span></span><br><span class="line"><span class="comment"># a source .py file to be detected as revisions in the</span></span><br><span class="line"><span class="comment"># versions/ directory</span></span><br><span class="line"><span class="comment"># sourceless = false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># version location specification; this defaults</span></span><br><span class="line"><span class="comment"># to alembic/versions.  When using multiple version</span></span><br><span class="line"><span class="comment"># directories, initial revisions must be specified with --version-path</span></span><br><span class="line"><span class="comment"># version_locations = %(here)s/bar %(here)s/bat alembic/versions</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># the output encoding used when revision files</span></span><br><span class="line"><span class="comment"># are written from script.py.mako</span></span><br><span class="line"><span class="comment"># output_encoding = utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Logging configuration</span></span><br><span class="line"><span class="section">[loggers]</span></span><br><span class="line"><span class="attr">keys</span> = root,sqlalchemy,alembic</span><br><span class="line"></span><br><span class="line"><span class="section">[handlers]</span></span><br><span class="line"><span class="attr">keys</span> = console</span><br><span class="line"></span><br><span class="line"><span class="section">[formatters]</span></span><br><span class="line"><span class="attr">keys</span> = generic</span><br><span class="line"></span><br><span class="line"><span class="section">[logger_root]</span></span><br><span class="line"><span class="attr">level</span> = WARN</span><br><span class="line"><span class="attr">handlers</span> = console</span><br><span class="line"><span class="attr">qualname</span> =</span><br><span class="line"></span><br><span class="line"><span class="section">[logger_sqlalchemy]</span></span><br><span class="line"><span class="attr">level</span> = WARN</span><br><span class="line"><span class="attr">handlers</span> =</span><br><span class="line"><span class="attr">qualname</span> = sqlalchemy.engine</span><br><span class="line"></span><br><span class="line"><span class="section">[logger_alembic]</span></span><br><span class="line"><span class="attr">level</span> = INFO</span><br><span class="line"><span class="attr">handlers</span> =</span><br><span class="line"><span class="attr">qualname</span> = alembic</span><br><span class="line"></span><br><span class="line"><span class="section">[handler_console]</span></span><br><span class="line"><span class="attr">class</span> = StreamHandler</span><br><span class="line"><span class="attr">args</span> = (sys.stderr,)</span><br><span class="line"><span class="attr">level</span> = NOTSET</span><br><span class="line"><span class="attr">formatter</span> = generic</span><br><span class="line"></span><br><span class="line"><span class="section">[formatter_generic]</span></span><br><span class="line"><span class="attr">format</span> = %(levelname)-<span class="number">5.5</span>s [%(name)s] %(message)s</span><br><span class="line"><span class="attr">datefmt</span> = %H:%M:%S</span><br></pre></td></tr></table></figure><h3 id="java"><a class="markdownIt-Anchor" href="#java"></a> java</h3><h4 id="maven"><a class="markdownIt-Anchor" href="#maven"></a> maven</h4><p><strong>pom.xml</strong><br />核心文件</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.app<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>my-module<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="gradle"><a class="markdownIt-Anchor" href="#gradle"></a> gradle</h4><p><strong>build.gradle</strong><br />gradle核心文件</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">plugins &#123; id <span class="string">&#x27;java&#x27;</span> &#125;</span><br><span class="line"><span class="keyword">dependencies</span> &#123; implementation <span class="string">&#x27;org.slf4j:slf4j-api:2.0.0&#x27;</span> &#125;</span><br></pre></td></tr></table></figure><p><strong>settings.gradle</strong><br />gradle入口文件</p><h3 id="cpp"><a class="markdownIt-Anchor" href="#cpp"></a> cpp</h3><h4 id="cmake"><a class="markdownIt-Anchor" href="#cmake"></a> CMake</h4><p><strong>CMakeLists.txt</strong></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line">cmake_minimum_required(VERSION 3.16)</span><br><span class="line"></span><br><span class="line"># Remove when sharing with others.</span><br><span class="line">@if %&#123;JS: Util.isDirectory(&#x27;%&#123;QtCreatorBuild&#125;/Qt Creator.app/Contents/Resources/lib/cmake/QtCreator&#x27;)&#125;</span><br><span class="line">list(APPEND CMAKE_PREFIX_PATH &quot;%&#123;QtCreatorBuild&#125;/Qt Creator.app/Contents/Resources&quot;)</span><br><span class="line">@else</span><br><span class="line">  @if %&#123;JS: Util.isDirectory(&#x27;%&#123;QtCreatorBuild&#125;/Contents/Resources/lib/cmake/QtCreator&#x27;)&#125;</span><br><span class="line">list(APPEND CMAKE_PREFIX_PATH &quot;%&#123;QtCreatorBuild&#125;/Contents/Resources&quot;)</span><br><span class="line">  @else</span><br><span class="line">list(APPEND CMAKE_PREFIX_PATH &quot;%&#123;QtCreatorBuild&#125;&quot;)</span><br><span class="line">  @endif</span><br><span class="line">@endif</span><br><span class="line"></span><br><span class="line">project(%&#123;PluginName&#125;)</span><br><span class="line"></span><br><span class="line">set(CMAKE_AUTOMOC ON)</span><br><span class="line">set(CMAKE_AUTORCC ON)</span><br><span class="line">set(CMAKE_AUTOUIC ON)</span><br><span class="line">set(CMAKE_CXX_STANDARD 20)</span><br><span class="line">set(CMAKE_CXX_STANDARD_REQUIRED ON)</span><br><span class="line">set(CMAKE_CXX_EXTENSIONS OFF)</span><br><span class="line"></span><br><span class="line">find_package(QtCreator REQUIRED COMPONENTS Core)</span><br><span class="line">find_package(Qt6 COMPONENTS Widgets REQUIRED)</span><br><span class="line"></span><br><span class="line"># Add a CMake option that enables building your plugin with tests.</span><br><span class="line"># You don&#x27;t want your released plugin binaries to contain tests,</span><br><span class="line"># so make that default to &#x27;NO&#x27;.</span><br><span class="line"># Enable tests by passing -DWITH_TESTS=ON to CMake.</span><br><span class="line">option(WITH_TESTS &quot;Builds with tests&quot; NO)</span><br><span class="line"></span><br><span class="line">if(WITH_TESTS)</span><br><span class="line">  # Look for QtTest</span><br><span class="line">  find_package(Qt6 REQUIRED COMPONENTS Test)</span><br><span class="line"></span><br><span class="line">  # Tell CMake functions like add_qtc_plugin about the QtTest component.</span><br><span class="line">  set(IMPLICIT_DEPENDS Qt::Test)</span><br><span class="line"></span><br><span class="line">  # Enable ctest for auto tests.</span><br><span class="line">  enable_testing()</span><br><span class="line">endif()</span><br><span class="line"></span><br><span class="line">add_qtc_plugin(%&#123;PluginName&#125;</span><br><span class="line">  PLUGIN_DEPENDS</span><br><span class="line">    QtCreator::Core</span><br><span class="line">  DEPENDS</span><br><span class="line">    Qt::Widgets</span><br><span class="line">    QtCreator::ExtensionSystem</span><br><span class="line">    QtCreator::Utils</span><br><span class="line">  SOURCES</span><br><span class="line">    .github/workflows/build_cmake.yml</span><br><span class="line">    .github/workflows/README.md</span><br><span class="line">    README.md</span><br><span class="line">    %&#123;SrcFileName&#125;</span><br><span class="line">    %&#123;ConstantsHdrFileName&#125;</span><br><span class="line">    %&#123;TrHdrFileName&#125;</span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li>打倒CMake独裁统治!大型cpp项目的cmakelists真的是又臭又长,让人看着头皮发麻</li></ul><h4 id="xmake"><a class="markdownIt-Anchor" href="#xmake"></a> xmake</h4><p>国人编写的,使用lua进行开发的cpp构建工具<br /><strong>xmake.lua</strong></p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">target(<span class="string">&quot;test&quot;</span>)</span><br><span class="line">    set_kind(<span class="string">&quot;binary&quot;</span>)</span><br><span class="line">    add_files(<span class="string">&quot;src/*.c&quot;</span>)</span><br><span class="line">    on_load(<span class="function"><span class="keyword">function</span> <span class="params">(target)</span></span></span><br><span class="line">        <span class="keyword">if</span> is_plat(<span class="string">&quot;linux&quot;</span>, <span class="string">&quot;macosx&quot;</span>) <span class="keyword">then</span></span><br><span class="line">            target:add(<span class="string">&quot;links&quot;</span>, <span class="string">&quot;pthread&quot;</span>, <span class="string">&quot;m&quot;</span>, <span class="string">&quot;dl&quot;</span>)</span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span>)</span><br><span class="line">    after_build(<span class="function"><span class="keyword">function</span> <span class="params">(target)</span></span></span><br><span class="line">        import(<span class="string">&quot;core.project.config&quot;</span>)</span><br><span class="line">        <span class="keyword">local</span> targetfile = target:targetfile()</span><br><span class="line">        <span class="built_in">os</span>.cp(targetfile, <span class="built_in">path</span>.join(<span class="built_in">config</span>.buildir(), <span class="built_in">path</span>.filename(targetfile)))</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;build %s&quot;</span>, targetfile)</span><br><span class="line">    <span class="keyword">end</span>)</span><br></pre></td></tr></table></figure><ul><li>自然你要说,这不还是很复杂吗?<br />cpp的特性就决定了构建项目的复杂,所以更需要我们去使用逻辑清晰好看懂的构建工具</li></ul><h2 id="反向代理"><a class="markdownIt-Anchor" href="#反向代理"></a> 反向代理</h2><h3 id="nginx"><a class="markdownIt-Anchor" href="#nginx"></a> nginx</h3><p><strong>nginx.conf</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">upstream frontend &#123;</span><br><span class="line">    server frontend:3000;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">upstream backend &#123;</span><br><span class="line">    server backend:8000;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">server &#123;</span><br><span class="line">    listen 80;</span><br><span class="line">    server_name _;</span><br><span class="line"></span><br><span class="line">    # 最大上传文件大小</span><br><span class="line">    client_max_body_size 20M;</span><br><span class="line"></span><br><span class="line">    # 后端 API 请求</span><br><span class="line">    location /api/ &#123;</span><br><span class="line">        proxy_pass http://backend;</span><br><span class="line">        proxy_http_version 1.1;</span><br><span class="line">        proxy_set_header Host $host;</span><br><span class="line">        proxy_set_header X-Real-IP $remote_addr;</span><br><span class="line">        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto $scheme;</span><br><span class="line"></span><br><span class="line">        # SSE 流式响应支持</span><br><span class="line">        proxy_set_header Connection &#x27;&#x27;;</span><br><span class="line">        proxy_buffering off;</span><br><span class="line">        proxy_cache off;</span><br><span class="line">        proxy_read_timeout 300s;</span><br><span class="line">        chunked_transfer_encoding on;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    # 前端请求</span><br><span class="line">    location / &#123;</span><br><span class="line">        proxy_pass http://frontend;</span><br><span class="line">        proxy_http_version 1.1;</span><br><span class="line">        proxy_set_header Upgrade $http_upgrade;</span><br><span class="line">        proxy_set_header Connection &#x27;upgrade&#x27;;</span><br><span class="line">        proxy_set_header Host $host;</span><br><span class="line">        proxy_cache_bypass $http_upgrade;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>配置各种各样的流量控制</p><h1 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h1><p>程序开发最怕出现哪些你看都看不懂的报错,希望大家都能去好好学习自己所需的配置工具,而不是一律粘贴给AI,万一不管用的话你咋办?</p>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;(待补充)&lt;br /&gt;
事实上,当你刚入门一个架构或者语言时,理解代码永远不是最难的地方,真正困难的一关是各种各样的配置文件!&lt;br /&gt;
&lt;img src=&quot;/images/archives/2026/2026-03-21/PixPin_2026-03-23_</summary>
      
    
    
    
    
    <category term="合集" scheme="https://revival-of-hope.github.io/tags/%E5%90%88%E9%9B%86/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-25 2026一季度总结</title>
    <link href="https://revival-of-hope.github.io/2026/03/25/archives-2026-1-2026-03-25-2026%E4%B8%80%E5%AD%A3%E5%BA%A6%E6%80%BB%E7%BB%93/"/>
    <id>https://revival-of-hope.github.io/2026/03/25/archives-2026-1-2026-03-25-2026%E4%B8%80%E5%AD%A3%E5%BA%A6%E6%80%BB%E7%BB%93/</id>
    <published>2026-03-25T00:00:00.000Z</published>
    <updated>2026-03-27T11:53:15.629Z</updated>
    
    <content type="html"><![CDATA[<p>尽管一季度还没完全结束,但还是先来个总结给自己打个底吧.</p><h2 id="overview"><a class="markdownIt-Anchor" href="#overview"></a> OVERVIEW</h2><p>回顾一下我年初的目标:</p><ul><li>[ ] cpp工程学习</li><li>[ ] 后端学习(数据库+Java)</li><li>[ ] 网络安全学习</li><li>[ ] 编写一个开源项目</li><li>[ ] 参加十场以上的比赛</li><li>[ ] 明确就业还是读研</li></ul><p>我可以很自信的打下几个勾:</p><ul><li>[x] cpp工程学习<ul><li>真真正正的从零开始学cpp了</li></ul></li><li>[x] 后端学习(数据库+Java)<ul><li>尽管我现在还是用的python,我真的感觉java的生态太古老了…</li></ul></li><li>[x] 网络安全学习<ul><li>事实上,我认为所谓的网络安全是对计算机网络这个概念的套壳,单独列出这个名词来唬人有点不太厚道了;事实上如果你能把整个计算机网络的概念捋清楚,并能够真正的写出一个规范安全的全栈项目,那才是真正的精通网络安全</li></ul></li><li>[x] 编写一个开源项目<ul><li>尽管都是小项目吧,但我打算之后做几个真东西</li></ul></li><li>[ ] 参加十场以上的比赛<ul><li>一场都没参加,我之前想的是参加一些ctf和算法竞赛,但当我看到百度之星的决赛题时,我就知道我绝对不是打算法竞赛的料,至于ctf,我还在入门ing…</li></ul></li><li>[x] 明确就业还是读研<ul><li>感觉我这个选择经过了相当长的思考期,而当我最终意识到自己已经做下了这个选择时,一切都豁然开朗,身心上都轻松了不少,但随即而来的是就业压力…</li></ul></li></ul><p>这么看来,当时定的目标基本都实现的差不多了,自己定的目标还是太轻松了,那我再来给二季度定一点目标:</p><ul><li>[ ] 参加人工智能大赛,实现一个完整的智能体(尽量多用ai)</li><li>[ ] 精通/掌握爬虫和机器学习</li><li>[ ] 编写一个开源的量化平台(尽量少用ai),虽然GitHub上已经有不少大佬项目了,但我还是想在学习之后做出一个自己的项目</li><li>[ ] 根据jmcomic api实现一个Android app</li><li>[ ] 深入学习nextjs和nestjs,nextjs已经是二战了…</li><li>[ ] 学习阅读财报并买入第二支股票</li><li>[ ] 读完&lt;&lt;设计数据密集型应用&gt;&gt;,&lt;&lt;程序员自我修养&gt;&gt;,&lt;&lt; C++程序设计语言 &gt;&gt;,&lt;&lt; RESTful Web APIs &gt;&gt;,一本设计模式书</li><li>[ ] 深入学习docker,可以的话再学kubernetes</li></ul><blockquote><p>会不会有点太多了😃</p></blockquote><h2 id="具体行动"><a class="markdownIt-Anchor" href="#具体行动"></a> 具体行动</h2><p>学校生活:</p><ol><li>成功的把该翘的课全都翘了,只在必须要签到的时候露个面,生活质量整个上来了</li><li>将一切与学业无关的活动都推掉了,包括不能说的活动</li></ol><p>家人:</p><ol><li>感觉现在我才是真正开智了,不愿意再被家里人牵着头走了,但又会尽量照顾到他们的情绪</li><li>人都是有自尊的,希望自己的话能被人听进去,再怎么说也是在以自己的能力和见识去尽量为你考虑的</li></ol><p>学习:</p><ol><li>由于多出了很多很多时间,这才第四周我就基本将核心课程的3/4学完了,也会去找各种各样的参考书来加深理解</li><li>从到处搜罗信息转为只关注自己想要的信息,重点放在钻研单方面的知识,而不再是广泛的涉猎了</li><li>发现真正的信息链: 官方文档&gt;博客文章&gt;教材&gt;&gt;AI&gt;问答网站&gt;&gt;视频&gt;&gt;社交平台&gt;&gt;传销号文章</li></ol><p>娱乐活动:</p><ol><li>有了固定的每周一聚,偶尔还会刷新其他的聚会,让乡村生活瞬间美好了不少</li><li>多出了很多时间去玩自己喜欢的游戏,但现在发现废萌型的作品我是真玩不下去,我玩游戏的动力只有两点:<ol><li>玩法非常新奇或者有趣</li><li>剧情非常好,界面交互不能太糟糕</li></ol></li><li>喜欢上了那种坏坏的喜剧,如&lt;&lt;恶搞之家&gt;&gt;,&lt;&lt;费城永远阳光灿烂&gt;&gt;</li></ol><p>体育锻炼:<br />由于大一下的过度锻炼: 一周三次健身加四五次跑步,尽管跑出了还算看的过去的3km12min的成绩,但是落下了一身伤,所以现在仅仅是完成刷段任务就满足了.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;尽管一季度还没完全结束,但还是先来个总结给自己打个底吧.&lt;/p&gt;
&lt;h2 id=&quot;overview&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#overview&quot;&gt;&lt;/a&gt; OVERVIEW&lt;/h2&gt;
&lt;p&gt;回顾一下我年初的目标:&lt;/p&gt;
&lt;u</summary>
      
    
    
    
    
    <category term="总结" scheme="https://revival-of-hope.github.io/tags/%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-21 全栈开发释疑</title>
    <link href="https://revival-of-hope.github.io/2026/03/21/archives-2026-03-21-%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91%E9%87%8A%E7%96%91/"/>
    <id>https://revival-of-hope.github.io/2026/03/21/archives-2026-03-21-%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91%E9%87%8A%E7%96%91/</id>
    <published>2026-03-21T00:00:00.000Z</published>
    <updated>2026-03-25T06:02:23.714Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前后端到底是怎么通信的?</p></blockquote><p>这个问题在我接触计算机后就一直在想,但是一直都没有一个清晰的认识,因此我将在下文以两个基础项目入手来谈一谈这个问题</p><h1 id="极简版fastapireact"><a class="markdownIt-Anchor" href="#极简版fastapireact"></a> 极简版fastapi+react</h1><ul><li><a href="https://testdriven.io/blog/fastapi-react/">深入浅出的教程</a></li></ul><p>强烈安利上面的教程,可以让小白迅速了解到前后端通信的实质操作</p><p><img src="/images/archives/2026/2026-03-20/PixPin_2026-03-21_10-20-05.webp" alt="alt text" /></p><p>我们可以清楚的看到,只需要用到这么一点文件就可以写出一个非常基础的支持增删日程的表格网站.</p><h2 id="后端"><a class="markdownIt-Anchor" href="#后端"></a> 后端</h2><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># api.py</span></span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"><span class="keyword">from</span> fastapi.middleware.cors <span class="keyword">import</span> CORSMiddleware</span><br><span class="line"></span><br><span class="line"><span class="comment"># mock data</span></span><br><span class="line">todos = [</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="string">&quot;id&quot;</span>: <span class="string">&quot;1&quot;</span>,</span><br><span class="line">        <span class="string">&quot;item&quot;</span>: <span class="string">&quot;Read a book.&quot;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="string">&quot;id&quot;</span>: <span class="string">&quot;2&quot;</span>,</span><br><span class="line">        <span class="string">&quot;item&quot;</span>: <span class="string">&quot;Cycle around town.&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">]</span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line">origins = [</span><br><span class="line">    <span class="string">&quot;http://localhost:5173&quot;</span>,</span><br><span class="line">    <span class="string">&quot;localhost:5173&quot;</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">app.add_middleware(</span><br><span class="line">    CORSMiddleware,</span><br><span class="line">    allow_origins=origins,</span><br><span class="line">    allow_credentials=<span class="literal">True</span>,</span><br><span class="line">    allow_methods=[<span class="string">&quot;*&quot;</span>],</span><br><span class="line">    allow_headers=[<span class="string">&quot;*&quot;</span>]</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">&quot;/&quot;</span>, tags=[<span class="string">&quot;root&quot;</span>]</span>)</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">read_root</span>() -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;message&quot;</span>: <span class="string">&quot;Welcome to your todo list.&quot;</span>&#125;</span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">&quot;/todo&quot;</span>,tags=[<span class="string">&quot;todos&quot;</span>]</span>)</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">get_todos</span>()-&gt;<span class="built_in">dict</span>:</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;data&quot;</span>: todos&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.post(<span class="params"><span class="string">&quot;/todo&quot;</span>, tags=[<span class="string">&quot;todos&quot;</span>]</span>)</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">add_todo</span>(<span class="params">todo: <span class="built_in">dict</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    todos.append(todo)</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;data&quot;</span>: &#123; <span class="string">&quot;Todo added.&quot;</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.put(<span class="params"><span class="string">&quot;/todo/&#123;id&#125;&quot;</span>, tags=[<span class="string">&quot;todos&quot;</span>]</span>)</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">update_todo</span>(<span class="params"><span class="built_in">id</span>: <span class="built_in">int</span>, body: <span class="built_in">dict</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="keyword">for</span> todo <span class="keyword">in</span> todos:</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">int</span>(todo[<span class="string">&quot;id&quot;</span>]) == <span class="built_in">id</span>:</span><br><span class="line">            todo[<span class="string">&quot;item&quot;</span>] = body[<span class="string">&quot;item&quot;</span>]</span><br><span class="line">            <span class="keyword">return</span> &#123;</span><br><span class="line">                <span class="string">&quot;data&quot;</span>: <span class="string">f&quot;Todo with id <span class="subst">&#123;<span class="built_in">id</span>&#125;</span> has been updated.&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;data&quot;</span>: <span class="string">f&quot;Todo with id <span class="subst">&#123;<span class="built_in">id</span>&#125;</span> not found.&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.delete(<span class="params"><span class="string">&quot;/todo/&#123;id&#125;&quot;</span>, tags=[<span class="string">&quot;todos&quot;</span>]</span>)</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">delete_todo</span>(<span class="params"><span class="built_in">id</span>: <span class="built_in">int</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="keyword">for</span> todo <span class="keyword">in</span> todos:</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">int</span>(todo[<span class="string">&quot;id&quot;</span>]) == <span class="built_in">id</span>:</span><br><span class="line">            todos.remove(todo)</span><br><span class="line">            <span class="keyword">return</span> &#123;</span><br><span class="line">                <span class="string">&quot;data&quot;</span>: <span class="string">f&quot;Todo with id <span class="subst">&#123;<span class="built_in">id</span>&#125;</span> has been removed.&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;data&quot;</span>: <span class="string">f&quot;Todo with id <span class="subst">&#123;<span class="built_in">id</span>&#125;</span> not found.&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">#main.py</span></span><br><span class="line"><span class="keyword">import</span> uvicorn</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    uvicorn.run(<span class="string">&quot;app.api:app&quot;</span>, host=<span class="string">&quot;0.0.0.0&quot;</span>, port=<span class="number">8000</span>, reload=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>可以发现,后端实质上只有两个文件,一个是使用uvicorn的启动文件,一个是用来处理前端传来的请求的api文件.</p><p>我们首先要知道以下几件事:</p><ol><li>Uvicorn 是一个超轻量级的 ASGI (Asynchronous Server Gateway Interface) 服务器。它的任务是物理监听网络端口，并将接收到的 HTTP 请求“翻译”给 FastAPI 处理。</li><li>app是一个FastAPI实例,在你用语法糖指明某个函数的作用对象时(例如<code>@app.get(&quot;/&quot;, tags=[&quot;root&quot;])</code>),他会自动在前端传来对应请求时执行这个函数,并将合法的返回值交付给前端</li><li>middleware字面意思是中间件,可以理解为夹在fastapi和前端之间的保安,会帮助fastapi执行以下操作:<ol><li><code>allow_origins=origins</code>:只接收位于origins列表里的前端端口或地址发来的请求,其他端口一律过滤掉</li><li><code>allow_credentials=True</code>:允许前端携带Cookies或者认证信息</li><li><code>allow_methods=[&quot;*&quot;]</code>:允许所有的HTTP method,当然我们可以把内容改为&quot;GET&quot;,意为只允许GET请求</li><li><code>allow_headers=[&quot;*&quot;]</code>:允许前端在request headers里自定义任何字段(如Content-Type)</li></ol></li></ol><p>事实上,我的上述说法是不完整的,在middleware与前端之间还有一个通信框架协议,也就是著名的Restful API协议,这个API框架严格定义了前端和后端相互发送的报文格式,也就是你在上述fastapi语法糖里看到的那些关键字,没有这些的话前后端是无法相互理解的.</p><p>我们现在来深入看一下api.py这个文件,它总共定义了5个app函数:</p><ol><li><code>@app.get(&quot;/&quot;, tags=[&quot;root&quot;])</code>: 执行<code>read_root</code>函数,返回一个欢迎访问的message到根页面</li><li><code>@app.get(&quot;/todo&quot;,tags=[&quot;todos&quot;])</code>: 执行<code>get_todos</code>函数,将mock data返回到todo子页面</li><li><code>@app.post(&quot;/todo&quot;, tags=[&quot;todos&quot;])</code>: 执行<code>add_todo</code>函数,接收todo对象并加到mock data里,同时返回成功添加的信息</li><li><code>@app.put(&quot;/todo/&#123;id&#125;&quot;, tags=[&quot;todos&quot;])</code>: 执行<code>update_todo</code>函数,接收要更新的一个todo元素,遍历todos列表,将对应id的item字段改为body包含的item字段,并在找不到该id时返回错误提示</li><li><code>@app.delete(&quot;/todo/&#123;id&#125;&quot;, tags=[&quot;todos&quot;])</code>: 执行<code>delete_todo</code>函数,与put函数原理类似,但这次找到对应的id直接删除</li></ol><h3 id="messagedatatags是个什么东西"><a class="markdownIt-Anchor" href="#messagedatatags是个什么东西"></a> “message”,“data”,&quot;tags&quot;是个什么东西?</h3><p>后端返回的对象实际上会再套一层大括号变成json格式,前端需要拆分这个json文件来提取所需的字段.<br />那么,显然前后端需要提前沟通好这些字段对应的用处,因为前端和后端编写人员在实际生产中是不太可能去仔细看对方代码的.<br />而fastapi推荐使用的json规范是<a href="https://www.openapis.org/">openapi</a>,规定好了各个字段和各个method的具体用途.<br />而在我们这个项目里,message作为调试信息,data作为返回数据,tags是一个便于openapi标识的标签,并不会发给前端,但可以帮助swagger UI分类路由(swagger UI是一个将json渲染成网页的中间产物).</p><hr /><p>到这里,整个后端就分析完了,怎么想都不是很复杂吧!</p><h2 id="前端"><a class="markdownIt-Anchor" href="#前端"></a> 前端</h2><p>如果你不太会react但是懂前端三件套的话,其实看下述内容只需要知道一点:</p><ul><li>react采用的jsx语法可以将函数作为页面组件导入和导出,使用语法如<code>&lt;Header /&gt;</code>这样</li></ul><p>但如果你连js都不懂的话,那还是去补补课吧…</p><p>先让我们看一下<code>main.tsx</code>这个文件:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">StrictMode</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; createRoot &#125; <span class="keyword">from</span> <span class="string">&#x27;react-dom/client&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;./index.css&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">App</span> <span class="keyword">from</span> <span class="string">&#x27;./App.tsx&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">createRoot</span>(<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)!).<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">StrictMode</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">App</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">StrictMode</span>&gt;</span></span>,</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>可以看到核心组件就是这个App,那我们再看一下App.tsx:</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ChakraProvider</span> &#125; <span class="keyword">from</span> <span class="string">&quot;@chakra-ui/react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; defaultSystem &#125; <span class="keyword">from</span> <span class="string">&quot;@chakra-ui/react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Header</span> <span class="keyword">from</span> <span class="string">&quot;./components/Header&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Todos</span> <span class="keyword">from</span> <span class="string">&quot;./components/Todos&quot;</span>; <span class="comment">// new</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ChakraProvider</span> <span class="attr">value</span>=<span class="string">&#123;defaultSystem&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Header</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Todos</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ChakraProvider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到App组件使用了Header组件和Todos组件,先看Header组件:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Heading</span>, <span class="title class_">Flex</span>, <span class="title class_">Separator</span> &#125; <span class="keyword">from</span> <span class="string">&quot;@chakra-ui/react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Header</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Flex</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">as</span>=<span class="string">&quot;nav&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">align</span>=<span class="string">&quot;center&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">justify</span>=<span class="string">&quot;space-between&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">wrap</span>=<span class="string">&quot;wrap&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">padding</span>=<span class="string">&quot;1rem&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">bg</span>=<span class="string">&quot;gray.400&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">width</span>=<span class="string">&quot;100%&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">position</span>=<span class="string">&quot;fixed&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">top</span>=<span class="string">&quot;0&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">left</span>=<span class="string">&quot;0&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">right</span>=<span class="string">&quot;0&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">zIndex</span>=<span class="string">&quot;1000&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    &gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Flex</span> <span class="attr">align</span>=<span class="string">&quot;center&quot;</span> <span class="attr">as</span>=<span class="string">&quot;nav&quot;</span> <span class="attr">mr</span>=<span class="string">&#123;5&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Heading</span> <span class="attr">as</span>=<span class="string">&quot;h1&quot;</span> <span class="attr">size</span>=<span class="string">&quot;sm&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          Todos</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Heading</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Separator</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Flex</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Flex</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到仅仅是个标题而已,没有其他的内容,那么可想而知重头戏都在Todos.tsx里了:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; useEffect, useState, createContext, useContext &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">Box</span>,</span><br><span class="line">  <span class="title class_">Button</span>,</span><br><span class="line">  <span class="title class_">Container</span>,</span><br><span class="line">  <span class="title class_">Flex</span>,</span><br><span class="line">  <span class="title class_">Input</span>,</span><br><span class="line">  <span class="title class_">DialogBody</span>,</span><br><span class="line">  <span class="title class_">DialogContent</span>,</span><br><span class="line">  <span class="title class_">DialogFooter</span>,</span><br><span class="line">  <span class="title class_">DialogHeader</span>,</span><br><span class="line">  <span class="title class_">DialogRoot</span>,</span><br><span class="line">  <span class="title class_">DialogTitle</span>,</span><br><span class="line">  <span class="title class_">DialogTrigger</span>,</span><br><span class="line">  <span class="title class_">Stack</span>,</span><br><span class="line">  <span class="title class_">Text</span>,</span><br><span class="line">  <span class="title class_">DialogActionTrigger</span>,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&quot;@chakra-ui/react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Todo</span> &#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">item</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">UpdateTodoProps</span> &#123;</span><br><span class="line">  <span class="attr">item</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">fetchTodos</span>: <span class="function">() =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">TodoHelperProps</span> &#123;</span><br><span class="line">  <span class="attr">item</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">fetchTodos</span>: <span class="function">() =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">DeleteTodoProps</span> &#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">fetchTodos</span>: <span class="function">() =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">TodosContext</span> = <span class="title function_">createContext</span>(&#123;</span><br><span class="line">  <span class="attr">todos</span>: [],</span><br><span class="line">  <span class="attr">fetchTodos</span>: <span class="function">() =&gt;</span> &#123;&#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Todos</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [todos, setTodos] = <span class="title function_">useState</span>([]);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">fetchTodos</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;http://localhost:8000/todo&quot;</span>);</span><br><span class="line">    <span class="keyword">const</span> todos = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">    <span class="title function_">setTodos</span>(todos.<span class="property">data</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">fetchTodos</span>();</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">TodosContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;&#123;</span> <span class="attr">todos</span>, <span class="attr">fetchTodos</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Container</span> <span class="attr">maxW</span>=<span class="string">&quot;container.xl&quot;</span> <span class="attr">pt</span>=<span class="string">&quot;100px&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">AddTodo</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Stack</span> <span class="attr">gap</span>=<span class="string">&#123;5&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;todos.map((todo: Todo) =&gt; (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">TodoHelper</span> <span class="attr">item</span>=<span class="string">&#123;todo.item&#125;</span> <span class="attr">id</span>=<span class="string">&#123;todo.id&#125;</span> <span class="attr">fetchTodos</span>=<span class="string">&#123;fetchTodos&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">          ))&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Stack</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Container</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">TodosContext.Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">AddTodo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [item, setItem] = <span class="title class_">React</span>.<span class="title function_">useState</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> &#123; todos, fetchTodos &#125; = <span class="title class_">React</span>.<span class="title function_">useContext</span>(<span class="title class_">TodosContext</span>);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleInput</span> = (<span class="params"><span class="attr">event</span>: <span class="title class_">React</span>.<span class="title class_">ChangeEvent</span>&lt;<span class="title class_">HTMLInputElement</span>&gt;</span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setItem</span>(event.<span class="property">target</span>.<span class="property">value</span>);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleSubmit</span> = (<span class="params"><span class="attr">event</span>: <span class="title class_">React</span>.<span class="title class_">FormEvent</span>&lt;<span class="title class_">HTMLFormElement</span>&gt;</span>) =&gt; &#123;</span><br><span class="line">    event.<span class="title function_">preventDefault</span>();</span><br><span class="line">    <span class="keyword">const</span> newTodo = &#123;</span><br><span class="line">      <span class="attr">id</span>: todos.<span class="property">length</span> + <span class="number">1</span>,</span><br><span class="line">      <span class="attr">item</span>: item,</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">fetch</span>(<span class="string">&quot;http://localhost:8000/todo&quot;</span>, &#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123; <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span> &#125;,</span><br><span class="line">      <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(newTodo),</span><br><span class="line">    &#125;).<span class="title function_">then</span>(fetchTodos);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">form</span> <span class="attr">onSubmit</span>=<span class="string">&#123;handleSubmit&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">pr</span>=<span class="string">&quot;4.5rem&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">type</span>=<span class="string">&quot;text&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">placeholder</span>=<span class="string">&quot;Add a todo item&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">aria-label</span>=<span class="string">&quot;Add a todo item&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onChange</span>=<span class="string">&#123;handleInput&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">UpdateTodo</span> = (<span class="params">&#123; item, id, fetchTodos &#125;: <span class="title class_">UpdateTodoProps</span></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [todo, setTodo] = <span class="title function_">useState</span>(item);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">updateTodo</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">`http://localhost:8000/todo/<span class="subst">$&#123;id&#125;</span>`</span>, &#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&quot;PUT&quot;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123; <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span> &#125;,</span><br><span class="line">      <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123; <span class="attr">item</span>: todo &#125;),</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetchTodos</span>();</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">DialogRoot</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">DialogTrigger</span> <span class="attr">asChild</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Button</span> <span class="attr">h</span>=<span class="string">&quot;1.5rem&quot;</span> <span class="attr">size</span>=<span class="string">&quot;sm&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          Update Todo</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">DialogTrigger</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">DialogContent</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">position</span>=<span class="string">&quot;fixed&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">top</span>=<span class="string">&quot;50%&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">left</span>=<span class="string">&quot;50%&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">transform</span>=<span class="string">&quot;translate(-50%, -50%)&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">bg</span>=<span class="string">&quot;white&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">p</span>=<span class="string">&#123;6&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">rounded</span>=<span class="string">&quot;md&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">shadow</span>=<span class="string">&quot;xl&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">maxW</span>=<span class="string">&quot;md&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">w</span>=<span class="string">&quot;90%&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">zIndex</span>=<span class="string">&#123;1000&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      &gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">DialogHeader</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">DialogTitle</span>&gt;</span>Update Todo<span class="tag">&lt;/<span class="name">DialogTitle</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">DialogHeader</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">DialogBody</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">pr</span>=<span class="string">&quot;4.5rem&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">type</span>=<span class="string">&quot;text&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">placeholder</span>=<span class="string">&quot;Add a todo item&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">aria-label</span>=<span class="string">&quot;Add a todo item&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">value</span>=<span class="string">&#123;todo&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">onChange</span>=<span class="string">&#123;(event)</span> =&gt;</span> setTodo(event.target.value)&#125;</span></span><br><span class="line"><span class="language-xml">          /&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">DialogBody</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">DialogFooter</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">DialogActionTrigger</span> <span class="attr">asChild</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">Button</span> <span class="attr">variant</span>=<span class="string">&quot;outline&quot;</span> <span class="attr">size</span>=<span class="string">&quot;sm&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              Cancel</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">DialogActionTrigger</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Button</span> <span class="attr">size</span>=<span class="string">&quot;sm&quot;</span> <span class="attr">onClick</span>=<span class="string">&#123;updateTodo&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            Save</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">DialogFooter</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">DialogContent</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">DialogRoot</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">TodoHelper</span>(<span class="params">&#123; item, id, fetchTodos &#125;: <span class="title class_">TodoHelperProps</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Box</span> <span class="attr">p</span>=<span class="string">&#123;1&#125;</span> <span class="attr">shadow</span>=<span class="string">&quot;sm&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Flex</span> <span class="attr">justify</span>=<span class="string">&quot;space-between&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Text</span> <span class="attr">mt</span>=<span class="string">&#123;4&#125;</span> <span class="attr">as</span>=<span class="string">&quot;div&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;item&#125;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Flex</span> <span class="attr">align</span>=<span class="string">&quot;end&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">UpdateTodo</span> <span class="attr">item</span>=<span class="string">&#123;item&#125;</span> <span class="attr">id</span>=<span class="string">&#123;id&#125;</span> <span class="attr">fetchTodos</span>=<span class="string">&#123;fetchTodos&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">DeleteTodo</span> <span class="attr">id</span>=<span class="string">&#123;id&#125;</span> <span class="attr">fetchTodos</span>=<span class="string">&#123;fetchTodos&#125;</span> /&gt;</span> &#123;/* new */&#125;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Flex</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Flex</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Box</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">DeleteTodo</span> = (<span class="params">&#123; id, fetchTodos &#125;: <span class="title class_">DeleteTodoProps</span></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">deleteTodo</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">`http://localhost:8000/todo/<span class="subst">$&#123;id&#125;</span>`</span>, &#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&quot;DELETE&quot;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123; <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span> &#125;,</span><br><span class="line">      <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123; <span class="attr">id</span>: id &#125;),</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetchTodos</span>();</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Button</span> <span class="attr">h</span>=<span class="string">&quot;1.5rem&quot;</span> <span class="attr">size</span>=<span class="string">&quot;sm&quot;</span> <span class="attr">marginLeft</span>=<span class="string">&#123;2&#125;</span> <span class="attr">onClick</span>=<span class="string">&#123;deleteTodo&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      Delete Todo</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>没接触过interface的可以理解为是一个要求js变量必须实现对应字段的接口,其实这与java里的interface没有什么区别</li></ul><p>大概浏览一下便知道,该文件有五个函数,我们按照从上到下的方式看一遍.</p><h3 id="todos"><a class="markdownIt-Anchor" href="#todos"></a> Todos</h3><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Todos</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [todos, setTodos] = <span class="title function_">useState</span>([]);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">fetchTodos</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;http://localhost:8000/todo&quot;</span>);</span><br><span class="line">    <span class="keyword">const</span> todos = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">    <span class="title function_">setTodos</span>(todos.<span class="property">data</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">fetchTodos</span>();</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">TodosContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;&#123;</span> <span class="attr">todos</span>, <span class="attr">fetchTodos</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Container</span> <span class="attr">maxW</span>=<span class="string">&quot;container.xl&quot;</span> <span class="attr">pt</span>=<span class="string">&quot;100px&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">AddTodo</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Stack</span> <span class="attr">gap</span>=<span class="string">&#123;5&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;todos.map((todo: Todo) =&gt; (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">TodoHelper</span> <span class="attr">item</span>=<span class="string">&#123;todo.item&#125;</span> <span class="attr">id</span>=<span class="string">&#123;todo.id&#125;</span> <span class="attr">fetchTodos</span>=<span class="string">&#123;fetchTodos&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">          ))&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Stack</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Container</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">TodosContext.Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这里需要先明确一个事实: 尽管项目是在本地运行的,但前后端之间并不是直接通信的,而是需要相互通过http通信获取报文,然后分别拆分报文得到具体内容.</p><ul><li>如果不这样的话,前后端就无法真正实现分开部署了.<br />因此,我们前面所说的fastapi发送json其实不完全正确,实际上我们在写fastapi的时候只是填写了json内部的字段,但fastapi会帮我们封装成json文件,再由编译器处理后装入HTTP响应报文,前端读取响应报文后在拆分处理;至于前端是怎么发送请求报文的我们后面会提到.</li></ul><p>再讲一下这个函数内部运行的全过程:</p><ol><li><code>const [todos, setTodos] = useState([]);</code>将todos这个列表与setTodos函数绑定,这个setTodos会在react编译器实现,我们只需要知道当我们调用setTodos并在括号里填入值时就会相应更改todos列表并<strong>重新渲染相关组件</strong></li><li>这个response接收&quot;<a href="http://localhost:8000/todo">http://localhost:8000/todo</a>&quot;传来的信息,其中<code>8000/todo</code>是fastapi所在的8000端口对应的todo页面,而由于fetch函数在不指定具体method时默认为get请求,也就是说,这里向后端发送了get请求.</li><li>todos变量接收response的json部分,然后将todos的data字段填入<strong>todos列表</strong>,也就是说todos变量只是一个中间变量而已(所以这里的命名有一点不规范,比较难理解)</li><li><code>useEffect</code>是一个Hook组件,用useEffect包裹的函数将会根据第二个参数的状态来执行:首次渲染时一定执行,如果之后参数变化则会再次执行.但由于我们这里第二个参数是<code>[]</code>,故只会在第一次渲染时执行fetchTodos函数,也就是只起到了初始化的作用.</li><li>至于return部分需要在我们讲完其他函数后再来分析</li></ol><h3 id="addtodo"><a class="markdownIt-Anchor" href="#addtodo"></a> AddTodo</h3><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">AddTodo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [item, setItem] = <span class="title class_">React</span>.<span class="title function_">useState</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> &#123; todos, fetchTodos &#125; = <span class="title class_">React</span>.<span class="title function_">useContext</span>(<span class="title class_">TodosContext</span>);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleInput</span> = (<span class="params"><span class="attr">event</span>: <span class="title class_">React</span>.<span class="title class_">ChangeEvent</span>&lt;<span class="title class_">HTMLInputElement</span>&gt;</span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setItem</span>(event.<span class="property">target</span>.<span class="property">value</span>);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleSubmit</span> = (<span class="params"><span class="attr">event</span>: <span class="title class_">React</span>.<span class="title class_">FormEvent</span>&lt;<span class="title class_">HTMLFormElement</span>&gt;</span>) =&gt; &#123;</span><br><span class="line">    event.<span class="title function_">preventDefault</span>();</span><br><span class="line">    <span class="keyword">const</span> newTodo = &#123;</span><br><span class="line">      <span class="attr">id</span>: todos.<span class="property">length</span> + <span class="number">1</span>,</span><br><span class="line">      <span class="attr">item</span>: item,</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">fetch</span>(<span class="string">&quot;http://localhost:8000/todo&quot;</span>, &#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123; <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span> &#125;,</span><br><span class="line">      <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(newTodo),</span><br><span class="line">    &#125;).<span class="title function_">then</span>(fetchTodos);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">form</span> <span class="attr">onSubmit</span>=<span class="string">&#123;handleSubmit&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">pr</span>=<span class="string">&quot;4.5rem&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">type</span>=<span class="string">&quot;text&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">placeholder</span>=<span class="string">&quot;Add a todo item&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">aria-label</span>=<span class="string">&quot;Add a todo item&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onChange</span>=<span class="string">&#123;handleInput&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个片段最难理解的地方其实是<code>const &#123; todos, fetchTodos &#125; = React.useContext(TodosContext);</code>,让我们把调用层级涉及的代码单独列出来,好容易理解一点:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Todos.tsx</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//createContext对象</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">TodosContext</span> = <span class="title function_">createContext</span>(&#123;</span><br><span class="line">  <span class="attr">todos</span>: [],</span><br><span class="line">  <span class="attr">fetchTodos</span>: <span class="function">() =&gt;</span> &#123;&#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">//主函数Todos</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Todos</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">TodosContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;&#123;</span> <span class="attr">todos</span>, <span class="attr">fetchTodos</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Container</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">AddTodo</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">            &#123;/* ... */&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Container</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">TodosContext.Provider</span>&gt;</span></span></span><br><span class="line">        );</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//AddTodo函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">AddTodo</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// const [item, setItem] = React.useState(&quot;&quot;);</span></span><br><span class="line">    <span class="keyword">const</span> &#123; todos, fetchTodos &#125; = <span class="title class_">React</span>.<span class="title function_">useContext</span>(<span class="title class_">TodosContext</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">handleSubmit</span> = (<span class="params"><span class="attr">event</span>: <span class="title class_">React</span>.<span class="title class_">FormEvent</span>&lt;<span class="title class_">HTMLFormElement</span>&gt;</span>) =&gt; &#123;</span><br><span class="line">    <span class="comment">// event.preventDefault(); 可以理解为防止触发提交表单时会刷新整个页面的旧浏览器bug</span></span><br><span class="line">    <span class="keyword">const</span> newTodo = &#123;</span><br><span class="line">      <span class="attr">id</span>: todos.<span class="property">length</span> + <span class="number">1</span>,</span><br><span class="line">      <span class="attr">item</span>: item,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可以将<code>createContext</code>理解为一个可以跨越组件进行通信的Hook函数,也就是说父组件可以跨越多层子组件直接将值传给目标子组件,而<strong>不需要层层传递所需的js变量或者函数</strong>.</p><p>在这里,创建的<code>TodosContext</code>对象具有Provider子对象,它可以将TodosContext涉及的对象和变量传入被Provider组件包裹的所有子组件,但是子组件要用到这些对象和变量是需要声明的,也就是这里所用的<code>const &#123; todos, fetchTodos &#125; = React.useContext(TodosContext);</code>,这行代码将TodosContext解构后获取了todos列表和fetchTodos函数.</p><p><strong>tips</strong><br />React 19.0后的版本不用再写provider了,可以直接写成以下形式</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">TodosContext</span> <span class="attr">value</span>=<span class="string">&#123;&#123;</span> <span class="attr">todos</span>, <span class="attr">fetchTodos</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Container</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">AddTodo</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;/* ... */&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Container</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">TodosContext</span>&gt;</span></span></span><br><span class="line">    );</span><br></pre></td></tr></table></figure><p>当然,并没有简化太多,还是需要提前在子组件中解构</p><h3 id="updatetodo"><a class="markdownIt-Anchor" href="#updatetodo"></a> UpdateTodo</h3><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">UpdateTodo</span> = (<span class="params">&#123; item, id, fetchTodos &#125;: <span class="title class_">UpdateTodoProps</span></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [todo, setTodo] = <span class="title function_">useState</span>(item);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">updateTodo</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">`http://localhost:8000/todo/<span class="subst">$&#123;id&#125;</span>`</span>, &#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&quot;PUT&quot;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123; <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span> &#125;,</span><br><span class="line">      <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123; <span class="attr">item</span>: todo &#125;),</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetchTodos</span>();</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">DialogRoot</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">DialogTrigger</span> <span class="attr">asChild</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Button</span> <span class="attr">h</span>=<span class="string">&quot;1.5rem&quot;</span> <span class="attr">size</span>=<span class="string">&quot;sm&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          Update Todo</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">DialogTrigger</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">DialogContent</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">position</span>=<span class="string">&quot;fixed&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">top</span>=<span class="string">&quot;50%&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">left</span>=<span class="string">&quot;50%&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">transform</span>=<span class="string">&quot;translate(-50%, -50%)&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">bg</span>=<span class="string">&quot;white&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">p</span>=<span class="string">&#123;6&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">rounded</span>=<span class="string">&quot;md&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">shadow</span>=<span class="string">&quot;xl&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">maxW</span>=<span class="string">&quot;md&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">w</span>=<span class="string">&quot;90%&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">zIndex</span>=<span class="string">&#123;1000&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      &gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">DialogHeader</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">DialogTitle</span>&gt;</span>Update Todo<span class="tag">&lt;/<span class="name">DialogTitle</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">DialogHeader</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">DialogBody</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">pr</span>=<span class="string">&quot;4.5rem&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">type</span>=<span class="string">&quot;text&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">placeholder</span>=<span class="string">&quot;Add a todo item&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">aria-label</span>=<span class="string">&quot;Add a todo item&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">value</span>=<span class="string">&#123;todo&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">onChange</span>=<span class="string">&#123;(event)</span> =&gt;</span> setTodo(event.target.value)&#125;</span></span><br><span class="line"><span class="language-xml">          /&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">DialogBody</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">DialogFooter</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">DialogActionTrigger</span> <span class="attr">asChild</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">Button</span> <span class="attr">variant</span>=<span class="string">&quot;outline&quot;</span> <span class="attr">size</span>=<span class="string">&quot;sm&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              Cancel</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">DialogActionTrigger</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Button</span> <span class="attr">size</span>=<span class="string">&quot;sm&quot;</span> <span class="attr">onClick</span>=<span class="string">&#123;updateTodo&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            Save</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">DialogFooter</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">DialogContent</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">DialogRoot</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>看着长,其实真正有效的内容只有以下部分:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">UpdateTodo</span> = (<span class="params">&#123; item, id, fetchTodos &#125;: <span class="title class_">UpdateTodoProps</span></span>) =&gt; &#123;</span><br><span class="line"><span class="comment">//   const [todo, setTodo] = useState(item);</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">updateTodo</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">`http://localhost:8000/todo/<span class="subst">$&#123;id&#125;</span>`</span>, &#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&quot;PUT&quot;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123; <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span> &#125;,</span><br><span class="line">      <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123; <span class="attr">item</span>: todo &#125;),</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetchTodos</span>();</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="comment">// &lt;DialogRoot&gt;</span></span><br><span class="line">    <span class="comment">//   &lt;DialogContent&gt;</span></span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">DialogBody</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">value</span>=<span class="string">&#123;todo&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">onChange</span>=<span class="string">&#123;(event)</span> =&gt;</span> setTodo(event.target.value)&#125;</span></span><br><span class="line"><span class="language-xml">          /&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">DialogBody</span>&gt;</span></span></span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">DialogFooter</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Button</span>  <span class="attr">onClick</span>=<span class="string">&#123;updateTodo&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            Save</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">DialogFooter</span>&gt;</span></span></span><br><span class="line">    <span class="comment">//   &lt;/DialogContent&gt;</span></span><br><span class="line">    <span class="comment">// &lt;/DialogRoot&gt;</span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>可以看到在按下save时会执行updateTodo函数,向后端传入要更改对象的id和内容.<br />同时,在输入框内输入的值会传入对应的to变量</p><h3 id="deletetodo"><a class="markdownIt-Anchor" href="#deletetodo"></a> DeleteTodo</h3><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">DeleteTodo</span> = (<span class="params">&#123; id, fetchTodos &#125;: <span class="title class_">DeleteTodoProps</span></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">deleteTodo</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">`http://localhost:8000/todo/<span class="subst">$&#123;id&#125;</span>`</span>, &#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&quot;DELETE&quot;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123; <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span> &#125;,</span><br><span class="line">      <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123; <span class="attr">id</span>: id &#125;),</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">fetchTodos</span>();</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Button</span> <span class="attr">h</span>=<span class="string">&quot;1.5rem&quot;</span> <span class="attr">size</span>=<span class="string">&quot;sm&quot;</span> <span class="attr">marginLeft</span>=<span class="string">&#123;2&#125;</span> <span class="attr">onClick</span>=<span class="string">&#123;deleteTodo&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      Delete Todo</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>与前面的内容基本一样,不用讲了</p><h3 id="todohelper"><a class="markdownIt-Anchor" href="#todohelper"></a> TodoHelper</h3><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">TodoHelper</span>(<span class="params">&#123; item, id, fetchTodos &#125;: <span class="title class_">TodoHelperProps</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Box</span> <span class="attr">p</span>=<span class="string">&#123;1&#125;</span> <span class="attr">shadow</span>=<span class="string">&quot;sm&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Flex</span> <span class="attr">justify</span>=<span class="string">&quot;space-between&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Text</span> <span class="attr">mt</span>=<span class="string">&#123;4&#125;</span> <span class="attr">as</span>=<span class="string">&quot;div&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;item&#125;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Flex</span> <span class="attr">align</span>=<span class="string">&quot;end&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">UpdateTodo</span> <span class="attr">item</span>=<span class="string">&#123;item&#125;</span> <span class="attr">id</span>=<span class="string">&#123;id&#125;</span> <span class="attr">fetchTodos</span>=<span class="string">&#123;fetchTodos&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">DeleteTodo</span> <span class="attr">id</span>=<span class="string">&#123;id&#125;</span> <span class="attr">fetchTodos</span>=<span class="string">&#123;fetchTodos&#125;</span> /&gt;</span> </span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Flex</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Flex</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Box</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>仅仅是把UpdateTodo和DeleteTodo两个组件封装在一起了而已.</p><h3 id="回到todos函数"><a class="markdownIt-Anchor" href="#回到todos函数"></a> 回到Todos函数</h3><p>现在让我们看一下Todos函数的返回值:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">TodosContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;&#123;</span> <span class="attr">todos</span>, <span class="attr">fetchTodos</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Container</span> <span class="attr">maxW</span>=<span class="string">&quot;container.xl&quot;</span> <span class="attr">pt</span>=<span class="string">&quot;100px&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">AddTodo</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Stack</span> <span class="attr">gap</span>=<span class="string">&#123;5&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;todos.map((todo: Todo) =&gt; (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">TodoHelper</span> <span class="attr">item</span>=<span class="string">&#123;todo.item&#125;</span> <span class="attr">id</span>=<span class="string">&#123;todo.id&#125;</span> <span class="attr">fetchTodos</span>=<span class="string">&#123;fetchTodos&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">          ))&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Stack</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Container</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">TodosContext.Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br></pre></td></tr></table></figure><p>可以看到,返回值是一个AddTodo输入框,再加上一个纵向的Todo事项列表而已,每个Todo有一个item标题,有两个操作组件:更新和删除</p><blockquote><p>事实上,看到这里你会发现,虽然使用了createContext,但其实只在AddTodo组件里和Stack里使用了.我们当然也可以单独让fetchTodos成为一个createContext,避免多次传递,但这是个人喜好问题,你想怎么写就怎么写.</p></blockquote><h3 id="前端是如何发送请求报文的"><a class="markdownIt-Anchor" href="#前端是如何发送请求报文的"></a> 前端是如何发送请求报文的</h3><p>现代的前端框架之所以如此复杂和臃肿,不仅仅是因为为了美观和修饰,还为的是能够实现本地启动web服务器并生成网页的功能:</p><ol><li>nodejs(使用cpp编写)通过与操作系统内核交互,提供一个类似于浏览器进程的运行环境</li><li>vite在运行时扫描下载的库和本地文件,构建一个静态页面,并根据用户交互或者外部API来将请求或者响应传入本地代码进行处理,之后重新渲染对应组件并<strong>将请求报文传给后端</strong></li></ol><h2 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h2><p>看完这个项目我们基本了解了最基础的前后端通信问题,那么,接下来,我们要处理一个中级的工程项目,解决数据库,后端,前端三者之间的复杂通信问题.</p><h1 id="to-be-continued"><a class="markdownIt-Anchor" href="#to-be-continued"></a> TO BE CONTINUED</h1>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前后端到底是怎么通信的?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个问题在我接触计算机后就一直在想,但是一直都没有一个清晰的认识,因此我将在下文以两个基础项目入手来谈一谈这个问题&lt;/p&gt;
&lt;h1 id=&quot;极简版fastapireact&quot;&gt;&lt;a </summary>
      
    
    
    
    
    <category term="全栈" scheme="https://revival-of-hope.github.io/tags/%E5%85%A8%E6%A0%88/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-20 compose类型网站上线尝试</title>
    <link href="https://revival-of-hope.github.io/2026/03/20/archives-2026-1-2026-03-20-%E7%BD%91%E7%AB%99%E4%B8%8A%E7%BA%BF%E7%9A%84%E4%B8%80%E6%AC%A1%E5%B0%9D%E8%AF%95/"/>
    <id>https://revival-of-hope.github.io/2026/03/20/archives-2026-1-2026-03-20-%E7%BD%91%E7%AB%99%E4%B8%8A%E7%BA%BF%E7%9A%84%E4%B8%80%E6%AC%A1%E5%B0%9D%E8%AF%95/</id>
    <published>2026-03-20T00:00:00.000Z</published>
    <updated>2026-04-03T14:40:21.328Z</updated>
    
    <content type="html"><![CDATA[<h1 id="intro"><a class="markdownIt-Anchor" href="#intro"></a> Intro</h1><p>有一个项目要做成网站部署上线,但我从来没有部署过一个完整的网站,最多是用github.io部署一个静态的hexo博客再加上用vercel部署评论系统而已.所以折腾了差不多半天.<br /><strong>工具集</strong></p><ul><li><a href="https://dnschecker.org/">查询自己的域名是否同步到主流dns服务器</a></li><li><a href="https://www.meiguodizhi.com/hk-address">生成随机的香港地址</a></li><li><a href="https://blog.naibabiji.com/tutorial/vultr-jiao-cheng.html">vultr教程</a><br />首先我们需要明白一点计算机网络的知识:</li></ul><blockquote><p>如何让别人看到你的网页?</p></blockquote><ol><li>首先你需要让自己的网站部署在一个云服务器或者本地服务器上,为什么需要服务器呢,是因为服务器具有以下几个功能:<ol><li>提供一个合法的ip地址</li><li>一般使用Linux系统,从而保证能够在低功耗下24小时不间断运行</li></ol></li><li>其次为了让自己的网站变得容易被搜索引擎爬取并让他人发现,我们需要在域名注册商内注册购买自己的域名,并通过DNS映射到服务器ip地址</li><li>其实部署到这里就结束了,如果有需要可以在服务器上配置防火墙,预防ddos攻击,预存快照等功能</li></ol><p>所以,为了能够让自己的网页顺利上线,你需要保证自己的代码能够在通用的linux版本上运行,其次,需要选择一个适合自己架构的服务器,最次要的要素才是选择域名.</p><p>由于我的项目使用了docker compose,故可以轻松在linux上运行,但很可惜的是不少服务器都不支持直接用compose部署(或者不支持Alipay…).</p><p>我辗转找到了<a href="https://my.vultr.com/">vultr</a>这样一个支持compose直接部署的服务器厂商,它的收费政策先充钱进账户,按使用量收费.</p><p>但vultr的部分ip<a href="https://blog.naibabiji.com/tutorial/vultr-jiao-cheng.html">有时会被封</a>,故你如果当你ping不通ip地址的时候,需要销毁后重新建一个虚拟机.</p><p>服务器正常运行后,便可以使用Dokploy来在服务器上部署自己的项目(如果不知道如何操作的可以看文末的ai教程备份)</p><p>部署成功后就可以申请域名并绑定了,我用的是<strong>dynadot</strong>域名注册商,也是因为能用支付宝而且不用备案…</p><p><img src="/images/archives/2026/2026-03-20/i1.webp" alt="alt text" /><br />域名记录栏填入ip地址,子域名记录栏填入域名即可,这样别人无论访问<strong><a href="http://www.xn--eqrt2g.com">www.域名.com</a></strong>还是直接访问<strong><a href="http://xn--eqrt2g.com">域名.com</a></strong>都可以定向到你的网站了.</p><h1 id="如何远程连接服务器"><a class="markdownIt-Anchor" href="#如何远程连接服务器"></a> 如何远程连接服务器?</h1><p><img src="/images/archives/2026/2026-03-20/PixPin_2026-03-21_22-43-22.webp" alt="alt text" /><br />在服务器后台看到自己的ip地址和对应密码后,在cmd输入以下命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh 你的用户名@你的服务器IP</span><br></pre></td></tr></table></figure><p>之后再输入密码,就可以连上服务器并进行各种各样的操作了.</p><p>如果你和我一样打算用compose.yml来部署网站并遇到了一些问题,欢迎在评论区提出!</p><h1 id="ai教程备份"><a class="markdownIt-Anchor" href="#ai教程备份"></a> AI教程备份</h1><hr /><h2 id="第一步vultr-服务器选型与开机"><a class="markdownIt-Anchor" href="#第一步vultr-服务器选型与开机"></a> 🚀 第一步：Vultr 服务器选型与开机</h2><p>你的项目包含 4 个容器（PostgreSQL + pgvector、FastAPI 后端、Next.js 前端、Nginx），其中 pgvector 和 Next.js 构建时比较吃内存。</p><h3 id="1-推荐配置性价比最高"><a class="markdownIt-Anchor" href="#1-推荐配置性价比最高"></a> 1. 推荐配置（性价比最高）</h3><ul><li><strong>类型</strong>：<code>Cloud Compute (Shared CPU)</code> -&gt; <code>Regular Performance</code></li><li><strong>节点</strong>：<strong>Singapore（新加坡）</strong> 或 <strong>Tokyo（东京）</strong>（对中国大陆延迟最低，通常在 50-80ms）</li><li><strong>系统</strong>：<code>Ubuntu 22.04 LTS</code> 或 <code>Ubuntu 24.04 LTS</code></li><li><strong>配置</strong>：<strong>1 vCPU / 2GB RAM / 55GB SSD</strong>（价格：$12/月）</li><li><strong>注</strong>：虽然有 $6/月（1GB RAM）的选项，但 1GB 内存跑 4 个容器极易 <strong>OOM（内存溢出）</strong> 导致数据库崩溃，强烈建议 2GB 起步。</li></ul><h3 id="2-开机步骤"><a class="markdownIt-Anchor" href="#2-开机步骤"></a> 2. 开机步骤</h3><ol><li>注册并登录 Vultr，绑定支付方式（支持支付宝/微信/信用卡）。</li><li>点击右上角 <strong>Deploy +</strong> -&gt; <strong>Deploy New Server</strong>。</li><li>按上述推荐选择节点、系统和配置。</li><li><strong>Server Settings</strong>：取消勾选 “Auto Backups”（可省 $2.4/月，后续可用 Dokploy 备份）。</li><li>点击右下角 <strong>Deploy Now</strong>。</li><li>等待 1-2 分钟，状态变为 “Running” 后，点击服务器名称，记录下 <strong>IP Address</strong> 和 <strong>Password</strong>。</li></ol><hr /><h2 id="️-第二步安装-dokploy可视化面板"><a class="markdownIt-Anchor" href="#️-第二步安装-dokploy可视化面板"></a> 🛠️ 第二步：安装 Dokploy（可视化面板）</h2><p>Dokploy 是一个开源的轻量级 PaaS 面板，能让你像用 Railway 一样通过网页管理 Docker Compose 项目。</p><ol><li>在你的电脑上打开终端（Windows 用 PowerShell，Mac 用 Terminal）。</li><li><strong>SSH 连接</strong> 到你的 Vultr 服务器：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh root@你的服务器IP</span><br></pre></td></tr></table></figure>输入 <code>yes</code>，然后粘贴 Vultr 面板里的密码。</li><li>运行 <strong>Dokploy 一键安装脚本</strong>：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -sSL https://dokploy.com/install.sh | sh</span><br></pre></td></tr></table></figure></li><li>安装大约需要 <strong>3-5 分钟</strong>。完成后，在浏览器中访问：<code>http://你的服务器IP:3000</code></li><li><strong>首次访问会要求你设置管理员账号和密码</strong>，设置完成后进入 Dokploy 控制台。</li></ol><hr /><h2 id="第三步部署青松项目"><a class="markdownIt-Anchor" href="#第三步部署青松项目"></a> 📦 第三步：部署青松项目</h2><h3 id="1-准备项目文件"><a class="markdownIt-Anchor" href="#1-准备项目文件"></a> 1. 准备项目文件</h3><p>将你本地的 <code>x</code> 文件夹或者上传到 GitHub 私有仓库</p><h3 id="2-在-dokploy-中创建项目"><a class="markdownIt-Anchor" href="#2-在-dokploy-中创建项目"></a> 2. 在 Dokploy 中创建项目</h3><ol><li>在 Dokploy 左侧菜单点击 <strong>Projects</strong> -&gt; <strong>Create Project</strong>，命名为 <code>x</code>。</li><li>进入项目，点击 <strong>Create Service</strong> -&gt; 选择 <strong>Compose</strong>。</li><li>命名为 <code>x-app</code>。</li></ol><h3 id="3-配置代码源"><a class="markdownIt-Anchor" href="#3-配置代码源"></a> 3. 配置代码源</h3><ul><li><strong>如果你用 GitHub</strong>：选择 Github，绑定你的账号，选择仓库和分支。</li></ul><h3 id="4-配置环境变量"><a class="markdownIt-Anchor" href="#4-配置环境变量"></a> 4. 配置环境变量</h3><p>在 Dokploy 的 <strong>Environment</strong> 选项卡中，粘贴你的 <code>.env</code> 文件内容。</p><h3 id="5-启动部署"><a class="markdownIt-Anchor" href="#5-启动部署"></a> 5. 启动部署</h3><p>点击右上角的 <strong>Deploy</strong> 按钮。Dokploy 会自动拉取镜像、构建前端和后端、启动数据库。你可以在 <strong>Logs</strong> 选项卡中实时查看构建进度。</p><hr /><h2 id="第四步绑定域名与-https"><a class="markdownIt-Anchor" href="#第四步绑定域名与-https"></a> 🌐 第四步：绑定域名与 HTTPS</h2><h3 id="1-购买与解析域名"><a class="markdownIt-Anchor" href="#1-购买与解析域名"></a> 1. 购买与解析域名</h3><ol><li>在 Cloudflare 或阿里云购买一个便宜的域名（如 <code>.xyz</code> 或 <code>.top</code>，首年通常不到 10 块钱）。</li><li>在域名 DNS 设置中，添加一条 <strong>A 记录</strong>：<ul><li><strong>Name</strong>: <code>@</code> 或 <code>chat</code>（取决于你想用主域名还是子域名）</li><li><strong>Content</strong>: 你的 Vultr 服务器 IP</li><li><strong>Proxy status</strong>: <strong>仅限 DNS</strong>（关闭小黄云，让 Dokploy 自己处理 SSL）</li></ul></li></ol><h3 id="2-在-dokploy-中配置域名"><a class="markdownIt-Anchor" href="#2-在-dokploy-中配置域名"></a> 2. 在 Dokploy 中配置域名</h3><ol><li>回到 Dokploy 面板，进入你的 <code>x-app</code> Compose 服务。</li><li>找到 <strong>Domains</strong> 选项卡。</li><li>添加你的域名（例如 <code>chat.yourdomain.com</code>）。</li><li><strong>目标端口（Target Port）</strong> 填写 <code>80</code>（因为你的 <code>compose.yml</code> 中 Nginx 暴露的是 80 端口）。</li><li>勾选 <strong>Enable SSL</strong>（Dokploy 会自动向 Let’s Encrypt 申请免费的 HTTPS 证书）。</li><li>点击 <strong>Save</strong>。</li></ol><ul><li>如果使用了nginx,在domains选项只需要像下面这样填写就行了:<br /><img src="/images/archives/2026/2026-03-20/image.webp" alt="alt text" /><br />其中马赛克部分填写自己申请的域名</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;intro&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#intro&quot;&gt;&lt;/a&gt; Intro&lt;/h1&gt;
&lt;p&gt;有一个项目要做成网站部署上线,但我从来没有部署过一个完整的网站,最多是用github.io部署一个静态的hexo博客再加上用v</summary>
      
    
    
    
    
    <category term="新技术" scheme="https://revival-of-hope.github.io/tags/%E6%96%B0%E6%8A%80%E6%9C%AF/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-16 uv使用</title>
    <link href="https://revival-of-hope.github.io/2026/03/16/python-2026-03-16-uv%E4%BD%BF%E7%94%A8/"/>
    <id>https://revival-of-hope.github.io/2026/03/16/python-2026-03-16-uv%E4%BD%BF%E7%94%A8/</id>
    <published>2026-03-16T00:00:00.000Z</published>
    <updated>2026-04-14T15:35:10.947Z</updated>
    
    <content type="html"><![CDATA[<h2 id="管理python版本">管理python版本</h2><blockquote><p>如果系统上已经安装了 Python，uv 将无需配置即可检测并使用它。但是，uv 也可以安装和管理 Python 版本。uv 会根据需要自动安装缺失的 Python 版本——你无需为了开始使用而预先安装 Python。</p></blockquote><ul><li><code>uv python install</code>：安装最新 Python 版本。<ul><li>eg: <code>uv python install 3.12 </code></li></ul></li><li><code>uv python list</code>：查看可用的 Python 版本。</li></ul><p>但是:</p><blockquote><p>当 uv 安装 Python 后，它不会在全局范围内可用（即通过 python 命令）。 此功能的支持尚处于_预览_阶段<br>你仍然可以使用uv run 或 创建并激活虚拟环境来直接使用 python。</p></blockquote><h2 id="运行python脚本">运行python脚本</h2><p>如果脚本没有依赖模块或者依赖标准库中的模块,可以直接使用 uv run 来执行它,例如:<code>uv run example.py</code>.</p><p>uv可以使用特定的python版本运行脚本</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="comment"># 使用特定的 Python 版本</span></span><br><span class="line">$ uv run --python 3.10 example.py</span><br><span class="line">3.10.15</span><br></pre></td></tr></table></figure><h2 id="包的使用">包的使用</h2><p>uvx 命令可以在不安装工具的情况下调用它:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uvx ruff</span><br></pre></td></tr></table></figure><p>使用 uvx 时，工具会安装到临时的、隔离的环境中.</p><p>如果一个工具经常使用，最好将其安装到持久环境中并添加到 PATH，而不是重复调用 uvx.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uv tool install ruff</span><br></pre></td></tr></table></figure><p>安装工具后，其可执行文件会放在 PATH 中的 bin 目录中，这样就可以在没有 uv 的情况下运行该工具.</p><h2 id="项目中的包管理">项目中的包管理</h2><p>uv通过<code>pyproject.toml</code>文件来定义依赖项.</p><p>使用 <code>uv init</code> 命令创建一个新的 Python 项目:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">uv init hello-world</span><br><span class="line"><span class="built_in">cd</span> hello-world</span><br></pre></td></tr></table></figure><p>或者在工作目录中初始化uv</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">mkdir</span> hello-world</span><br><span class="line">$ <span class="built_in">cd</span> hello-world</span><br><span class="line">$ uv init</span><br></pre></td></tr></table></figure><h3 id="项目结构">项目结构</h3><blockquote><p>一个项目由几个协同工作的重要部分组成，这些部分允许 uv 管理你的项目。除了 uv init 创建的文件外，当你第一次运行项目命令（即 uv run、uv sync 或 uv lock）时，uv 将在你的项目根目录中创建一个虚拟环境和 uv.lock 文件。</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── .venv</span><br><span class="line">│   ├── bin</span><br><span class="line">│   ├── lib</span><br><span class="line">│   └── pyvenv.cfg</span><br><span class="line">├── .python-version</span><br><span class="line">├── README.md</span><br><span class="line">├── main.py</span><br><span class="line">├── pyproject.toml</span><br><span class="line">└── uv.lock</span><br></pre></td></tr></table></figure><ul><li>直接震惊了好吧,终于不用输入<code>python -m venv venv</code>这种东西了</li></ul><p><strong>pyproject.toml</strong></p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[project]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">&quot;uv&quot;</span></span><br><span class="line"><span class="attr">version</span> = <span class="string">&quot;0.1.0&quot;</span></span><br><span class="line"><span class="attr">description</span> = <span class="string">&quot;Add your description here&quot;</span></span><br><span class="line"><span class="attr">readme</span> = <span class="string">&quot;README.md&quot;</span></span><br><span class="line"><span class="attr">requires-python</span> = <span class="string">&quot;&gt;=3.13&quot;</span></span><br><span class="line"><span class="attr">dependencies</span> = []</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>.python-version</strong><br>.python-version 文件包含项目的Python 版本。此文件告诉 uv 在创建项目的虚拟环境时使用哪个 Python 版本。</p><blockquote><p>我相信应该不只有我一个人好奇:就这一条信息为什么不合并到toml文件中呢?<br>经过<a href="https://github.com/astral-sh/uv/issues/8247">浏览</a>我推测: requires-python配置项要求了在特定python版本下才能运行项目,如果把当前使用的不合规python版本写入toml,那么uv编译的时候要听谁的呢?<br>因此,单独分出这一个文件既是为了保证python版本管理的方便,也为了防止错误的python版本被用来执行.</p></blockquote><p><strong>uv.lock</strong><br>uv.lock 是一个人类可读的 TOML 文件，但由 uv 管理，不应手动编辑,包含有关你的项目依赖项的精确信息.</p><h3 id="包管理和从pip迁移">包管理和从pip迁移</h3><p>你可以使用 uv add 命令将依赖项添加到你的 pyproject.toml 中。这也将更新锁文件和项目环境：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="comment"># 指定版本约束</span></span><br><span class="line">$ uv add <span class="string">&#x27;requests==2.31.0&#x27;</span></span><br><span class="line"></span><br><span class="line">$ <span class="comment"># 添加一个 git 依赖</span></span><br><span class="line">$ uv add git+https://github.com/psf/requests</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="comment"># 从 `requirements.txt` 添加所有依赖项。</span></span><br><span class="line">$ uv add -r requirements.txt -c constraints.txt</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># --upgrade-package 标志将尝试将指定的包更新到最新的兼容版本，同时保持锁文件的其余部分不变</span></span><br><span class="line">$ uv lock --upgrade-package requests</span><br></pre></td></tr></table></figure><h3 id="运行和同步">运行和同步</h3><blockquote><p>在每次调用 uv run 之前，uv 将验证锁文件是否与 pyproject.toml 同步，以及环境是否与锁文件同步，从而使你的项目保持同步，无需手动干预。uv run 保证你的命令在一致、锁定的环境中运行。</p></blockquote><p>当接手一个使用了uv的项目时,建议先运行uv sync命令以创建虚拟环境并下载库进行同步,尽管使用uv run <strong>随便一个python文件</strong> 会默认使用uv sync,但就不够优雅了.</p><h3 id="构建分发包">构建分发包</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ uv build</span><br><span class="line">$ <span class="built_in">ls</span> dist/</span><br><span class="line">hello-world-0.1.0-py3-none-any.whl</span><br><span class="line">hello-world-0.1.0.tar.gz</span><br></pre></td></tr></table></figure><h2 id="TL-DR">TL;DR</h2><p>如果是自己新建python项目,则运行:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">uv init hello-world</span><br><span class="line"><span class="comment"># 这会在创建子文件夹并填入初始内容</span></span><br><span class="line">uv init </span><br><span class="line"><span class="comment"># 在当前文件夹填入初始内容</span></span><br><span class="line">uv add ...</span><br><span class="line"><span class="comment"># 本地加入自己需要的依赖</span></span><br><span class="line"><span class="comment"># 或者自己在toml里填入包,如果这样的话需要使用uv sync</span></span><br><span class="line"></span><br><span class="line">uv run main.py</span><br><span class="line"><span class="comment"># 使用uv运行某个脚本</span></span><br></pre></td></tr></table></figure><p>如果是接手某个项目:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">uv <span class="built_in">sync</span></span><br><span class="line">uv run main.py</span><br><span class="line"><span class="comment"># 使用uv运行某个脚本</span></span><br></pre></td></tr></table></figure><h2 id="实战-Using-uv-with-PyTorch">实战:Using uv with PyTorch</h2><ul><li><a href="https://docs.astral.sh/uv/guides/integration/pytorch/">参考</a></li></ul><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[project]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">&quot;project&quot;</span></span><br><span class="line"><span class="attr">version</span> = <span class="string">&quot;0.1.0&quot;</span></span><br><span class="line"><span class="attr">requires-python</span> = <span class="string">&quot;&gt;=3.14&quot;</span></span><br><span class="line"><span class="attr">dependencies</span> = [</span><br><span class="line">  <span class="string">&quot;torch&gt;=2.9.1&quot;</span>,</span><br><span class="line">  <span class="string">&quot;torchvision&gt;=0.24.1&quot;</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><blockquote><p>This is a valid configuration for projects that want to use <strong>CPU</strong> builds on Windows and macOS, and CUDA-enabled builds on Linux. However, if you need to support different platforms or accelerators, you’ll need to configure the project accordingly.</p></blockquote><p><strong>使用CUDA13.0</strong></p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[[tool.uv.index]]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">&quot;pytorch-cu130&quot;</span></span><br><span class="line"><span class="attr">url</span> = <span class="string">&quot;https://download.pytorch.org/whl/cu130&quot;</span></span><br><span class="line"><span class="attr">explicit</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p><strong>TL;DR</strong>:<br>先在nvidia官网下载cuda13.0,然后根据这个toml运行uv sync即可.</p><ul><li>提示:要下载差不多2个G.</li></ul><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[project]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">&quot;ml&quot;</span></span><br><span class="line"><span class="attr">version</span> = <span class="string">&quot;0.1.0&quot;</span></span><br><span class="line"><span class="attr">description</span> = <span class="string">&quot;Add your description here&quot;</span></span><br><span class="line"><span class="attr">readme</span> = <span class="string">&quot;README.md&quot;</span></span><br><span class="line"><span class="attr">requires-python</span> = <span class="string">&quot;&gt;=3.13&quot;</span></span><br><span class="line"><span class="attr">dependencies</span> = [</span><br><span class="line">    <span class="string">&quot;torch&quot;</span>,</span><br><span class="line">    <span class="string">&quot;torchvision&quot;</span>,</span><br><span class="line">    <span class="string">&quot;torchaudio&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="section">[tool.uv]</span></span><br><span class="line"><span class="comment"># 1. 物理定义 PyTorch 的专用硬件加速索引库</span></span><br><span class="line"><span class="section">[tool.uv.index]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">&quot;pytorch-cu130&quot;</span></span><br><span class="line"><span class="attr">url</span> = <span class="string">&quot;https://download.pytorch.org/whl/cu130&quot;</span></span><br><span class="line"><span class="attr">explicit</span> = <span class="literal">true</span> <span class="comment"># 强制：只有在 sources 中明确指定的包才去这里找，防止污染其他依赖</span></span><br><span class="line"></span><br><span class="line"><span class="section">[tool.uv.sources]</span></span><br><span class="line"><span class="comment"># 2. 将核心组件物理绑定到上述索引</span></span><br><span class="line"><span class="attr">torch</span> = &#123; index = <span class="string">&quot;pytorch-cu130&quot;</span> &#125;</span><br><span class="line"><span class="attr">torchvision</span> = &#123; index = <span class="string">&quot;pytorch-cu130&quot;</span> &#125;</span><br><span class="line"><span class="attr">torchaudio</span> = &#123; index = <span class="string">&quot;pytorch-cu130&quot;</span> &#125;</span><br></pre></td></tr></table></figure><p>解释一下:</p><ol><li>tool.uv: 告诉uv编译器,下面是我要你遵循的规则</li><li>tool.uv.index: 提供自定义组件源,而不是到官方库下载</li><li>tool.uv.sources: 将组件与index绑定,只有与index绑定的组件才会去自定义组件源下载</li></ol><p>运行一下代码,很成功:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;CUDA status: <span class="subst">&#123;torch.cuda.is_available()&#125;</span>&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;CUDA version: <span class="subst">&#123;torch.version.cuda&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">uv run ch1.py</span><br><span class="line">CUDA status: True</span><br><span class="line">CUDA version: 13.0</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;管理python版本&quot;&gt;管理python版本&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如果系统上已经安装了 Python，uv 将无需配置即可检测并使用它。但是，uv 也可以安装和管理 Python 版本。uv 会根据需要自动安装缺失的 Python 版本——你</summary>
      
    
    
    
    
    <category term="python" scheme="https://revival-of-hope.github.io/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-15 react-learn</title>
    <link href="https://revival-of-hope.github.io/2026/03/15/archives-2026-1-2026-03-15-react-learn/"/>
    <id>https://revival-of-hope.github.io/2026/03/15/archives-2026-1-2026-03-15-react-learn/</id>
    <published>2026-03-15T00:00:00.000Z</published>
    <updated>2026-03-19T10:54:12.617Z</updated>
    
    <content type="html"><![CDATA[<p>基础React使用的是自创的jsx,在实际运行时需要经过编译器转译变成js.<br />实际上,jsx与原生js的最大区别在于可以将html元素作为组件,也就是说,<strong>React组件是一段可以使用标签进行扩展的JavaScript 函数</strong>.</p><h2 id="ui描绘"><a class="markdownIt-Anchor" href="#ui描绘"></a> UI描绘</h2><h3 id="组件概念"><a class="markdownIt-Anchor" href="#组件概念"></a> 组件概念</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Profile</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">img</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">src</span>=<span class="string">&quot;https://i.imgur.com/MK3eW3As.jpg&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">alt</span>=<span class="string">&quot;Katherine Johnson&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Gallery</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">section</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Amazing scientists<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Profile</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Profile</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Profile</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">section</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>profile()函数返回的是img元素,并可以在Gallery()函数中使用<Profile />的形式直接调用.</p><h3 id="组件的导入导出"><a class="markdownIt-Anchor" href="#组件的导入导出"></a> 组件的导入导出</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Gallery</span> <span class="keyword">from</span> <span class="string">&#x27;./Gallery.js&#x27;</span>;</span><br><span class="line"><span class="comment">//import Gallery from &#x27;./Gallery&#x27;; 这种写法在React中也可以,但不够符合原生ES规范</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Gallery</span> /&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="两种导出方式"><a class="markdownIt-Anchor" href="#两种导出方式"></a> 两种导出方式</h4><table><thead><tr><th>类型</th><th>导出语句</th><th>导入语句</th></tr></thead><tbody><tr><td>默认导出（default export）</td><td><code>export default function Button() &#123;&#125;</code></td><td><code>import Button from './Button.js';</code></td></tr><tr><td>具名导出（named export）</td><td><code>export function Button() &#123;&#125;</code></td><td><code>import &#123; Button &#125; from './Button.js';</code></td></tr></tbody></table><p>默认导入时名称可以任意命名,而具名导入时名称需要完全一致并用{}标识</p><h3 id="jsx基础语法"><a class="markdownIt-Anchor" href="#jsx基础语法"></a> jsx基础语法</h3><h4 id="如何将网页元素作为返回值"><a class="markdownIt-Anchor" href="#如何将网页元素作为返回值"></a> 如何将网页元素作为返回值</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">TodoList</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="comment">// 这不起作用！</span></span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>海蒂·拉玛的待办事项<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">    &lt;img </span><br><span class="line">      src=&quot;https://i.imgur.com/yXOvdOSs.jpg&quot; </span><br><span class="line">      alt=&quot;Hedy Lamarr&quot; </span><br><span class="line">      class=&quot;photo&quot;</span><br><span class="line">    &gt;</span><br><span class="line">    &lt;ul&gt;</span><br><span class="line">      &lt;li&gt;发明一种新式交通信号灯</span><br><span class="line">      &lt;li&gt;排练一个电影场景</span><br><span class="line">      &lt;li&gt;改进频谱技术</span><br><span class="line">    &lt;/ul&gt;</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>返回值实际上应该先写成以下形式:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&lt;&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>海蒂·拉玛的待办事项<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">img</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">src</span>=<span class="string">&quot;https://i.imgur.com/yXOvdOSs.jpg&quot;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">alt</span>=<span class="string">&quot;Hedy Lamarr&quot;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">class</span>=<span class="string">&quot;photo&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">  &gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    ...</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/&gt;</span></span></span><br></pre></td></tr></table></figure><p>这个空标签被称作 Fragment。React Fragment 允许你将子元素分组，而不会在 HTML 结构中添加额外节点。</p><p><strong>为什么多个 JSX 标签需要被一个父元素包裹?</strong><br />JSX 虽然看起来很像 HTML，但在底层其实被转化为了 JavaScript 对象，你不能在一个函数中返回多个对象，除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。</p><p>上述写法其实还不够正确,因为JSX 要求标签必须正确闭合。像 <img> 这样的自闭合标签必须书写成 <img />,而像 <li>oranges 这样只有开始标签的元素必须带有闭合标签，需要改为 <li>oranges</li>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&lt;&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">img</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">src</span>=<span class="string">&quot;https://i.imgur.com/yXOvdOSs.jpg&quot;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">alt</span>=<span class="string">&quot;Hedy Lamarr&quot;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">class</span>=<span class="string">&quot;photo&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">   /&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">li</span>&gt;</span>发明一种新式交通信号灯<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">li</span>&gt;</span>排练一个电影场景<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">li</span>&gt;</span>改进频谱技术<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line">&lt;/&gt;</span><br></pre></td></tr></table></figure><p>由于class是js保留字,故这里还需要将class改为className,使用的是驼峰命名法.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;img </span><br><span class="line">  src=<span class="string">&quot;https://i.imgur.com/yXOvdOSs.jpg&quot;</span> </span><br><span class="line">  alt=<span class="string">&quot;Hedy Lamarr&quot;</span> </span><br><span class="line">  className=<span class="string">&quot;photo&quot;</span></span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">TodoList</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>海蒂·拉玛的待办事项<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">img</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">src</span>=<span class="string">&quot;https://i.imgur.com/yXOvdOSs.jpg&quot;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">alt</span>=<span class="string">&quot;Hedy Lamarr&quot;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">className</span>=<span class="string">&quot;photo&quot;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">li</span>&gt;</span>发明一种新式交通信号灯<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">li</span>&gt;</span>排练一个电影场景<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">li</span>&gt;</span>改进频谱技术<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是最终成果.</p><h4 id="如何在网页元素或者组件中写入js变量"><a class="markdownIt-Anchor" href="#如何在网页元素或者组件中写入js变量"></a> 如何在网页元素或者组件中写入js变量</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">TodoList</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> name = <span class="string">&#x27;Zara&#x27;</span>;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;name&#125;的待办事项列表<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 JSX 中，只能在以下两种场景中使用大括号：</p><ol><li>用作 JSX 标签内的文本：<code>&lt;h1&gt;&#123;name&#125;'s To Do List&lt;/h1&gt;</code> 是有效的，但是 <code>&lt;&#123;tag&#125;&gt;Gregorio Y. Zara's To Do List&lt;/&#123;tag&#125;&gt;</code> 无效</li><li>用作紧跟在 = 符号后的 属性：<code>src=&#123;avatar&#125;</code> 会读取 <code>avatar</code> 变量，但是 <code>src=&quot;&#123;avatar&#125;&quot;</code> 只会传一个字符串 <code>&#123;avatar&#125;</code></li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">TodoList</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ul</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">backgroundColor:</span> &#x27;<span class="attr">black</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">color:</span> &#x27;<span class="attr">pink</span>&#x27;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">li</span>&gt;</span>Improve the videophone<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">li</span>&gt;</span>Prepare aeronautics lectures<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">li</span>&gt;</span>Work on the alcohol-fuelled engine<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码通过双大括号传入了一个css对象.</p><h3 id="在组件间传递props"><a class="markdownIt-Anchor" href="#在组件间传递props"></a> 在组件间传递props</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Profile</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Avatar</span> /&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个代码没有向子组件转递任何props.</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Profile</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Avatar</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">person</span>=<span class="string">&#123;&#123;</span> <span class="attr">name:</span> &#x27;<span class="attr">Lin</span> <span class="attr">Lanying</span>&#x27;, <span class="attr">imageId:</span> &#x27;<span class="attr">1bX5QH6</span>&#x27; &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">size</span>=<span class="string">&#123;100&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Avatar</span>(<span class="params">&#123; person, size &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// 在这里 person 和 size 是可访问的</span></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">img</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">className</span>=<span class="string">&quot;avatar&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">src</span>=<span class="string">&#123;getImageUrl(person)&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">alt</span>=<span class="string">&#123;person.name&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">width</span>=<span class="string">&#123;size&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">height</span>=<span class="string">&#123;size&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你可以将 props 想象成可以调整的“旋钮”。它们的作用与函数的参数相同 —— 事实上，props 正是 组件的唯一参数！ React 组件函数接受一个参数，一个 props 对象：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Avatar</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> person = props.<span class="property">person</span>;</span><br><span class="line">  <span class="keyword">let</span> size = props.<span class="property">size</span>;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通常你不需要整个 props 对象，所以可以将它解构为单独的 props,也就是说,下面两段代码是等价的:</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Avatar</span>(<span class="params">&#123; person, size &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Avatar</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> person = props.<span class="property">person</span>;</span><br><span class="line">  <span class="keyword">let</span> size = props.<span class="property">size</span>;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同时,还可以写入默认值,如:</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Avatar</span>(<span class="params">&#123; person, size = <span class="number">100</span> &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>还可以使用展开语法,将传入的props展开为对象后再传入:</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Profile</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;card&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Avatar</span> &#123;<span class="attr">...props</span>&#125; /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="字符串变量与普通变量的传入"><a class="markdownIt-Anchor" href="#字符串变量与普通变量的传入"></a> 字符串变量与普通变量的传入</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">AlertButton</span>(<span class="params">&#123; message, children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> alert(message)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Toolbar</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AlertButton</span> <span class="attr">message</span>=<span class="string">&quot;正在播放！&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        播放电影</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AlertButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AlertButton</span> <span class="attr">message</span>=<span class="string">&quot;正在上传！&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        上传图片</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AlertButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意到这里的AlertButton组件在调用时直接传入message字符串,而不是写<code>message=&#123;&quot;正在播放！&quot;&#125;</code>这种普通的传入变量方式,是因为字符串变量默认是可以直接传入的.</p><h3 id="条件渲染"><a class="markdownIt-Anchor" href="#条件渲染"></a> 条件渲染</h3><p>在一些情况下，你不想有任何东西进行渲染。比如，你不想显示已经打包好的物品。但一个组件必须返回一些东西。这种情况下，你可以直接返回 null。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (isPacked) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">className</span>=<span class="string">&quot;item&quot;</span>&gt;</span>&#123;name&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span>;</span><br></pre></td></tr></table></figure><p>但事实上还可以这么写:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">className</span>=<span class="string">&quot;item&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    &#123;name&#125; &#123;isPacked &amp;&amp; &#x27;✅&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>也就是说将js变量直接与字符串绑定,仅在isPacked为true时渲染勾选符号.</p><blockquote><p>JavaScript 会自动将左侧的值转换成布尔类型以判断条件成立与否。然而，如果左侧是 0，整个表达式将变成左侧的值（0），React 此时则会渲染 0 而不是不进行渲染。<br />例如，一个常见的错误是 messageCount &amp;&amp; <p>New messages</p>。其原本是想当 messageCount 为 0 的时候不进行渲染，但实际上却渲染了 0。<br />为了更正，可以将左侧的值改成布尔类型：messageCount &gt; 0 &amp;&amp; <p>New messages</p>。</p></blockquote><h3 id="渲染列表"><a class="markdownIt-Anchor" href="#渲染列表"></a> 渲染列表</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; people &#125; <span class="keyword">from</span> <span class="string">&#x27;./data.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; getImageUrl &#125; <span class="keyword">from</span> <span class="string">&#x27;./utils.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">List</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> chemists = people.<span class="title function_">filter</span>(<span class="function"><span class="params">person</span> =&gt;</span></span><br><span class="line">    person.<span class="property">profession</span> === <span class="string">&#x27;化学家&#x27;</span></span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">const</span> listItems = chemists.<span class="title function_">map</span>(<span class="function"><span class="params">person</span> =&gt;</span></span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;person.id&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">img</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">src</span>=<span class="string">&#123;getImageUrl(person)&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">alt</span>=<span class="string">&#123;person.name&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">b</span>&gt;</span>&#123;person.name&#125;:<span class="tag">&lt;/<span class="name">b</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;&#x27; &#x27; + person.profession + &#x27; &#x27;&#125;</span></span><br><span class="line"><span class="language-xml">        因&#123;person.accomplishment&#125;而闻名世界</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">ul</span>&gt;</span>&#123;listItems&#125;<span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个代码是这一小节的核心内容,filter()和map()经常被用在react元素中用来渲染数据库的数据</p><h2 id="添加交互"><a class="markdownIt-Anchor" href="#添加交互"></a> 添加交互</h2><h3 id="事件处理"><a class="markdownIt-Anchor" href="#事件处理"></a> 事件处理</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Button</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      未绑定任何事件</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>=&gt;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Button</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(<span class="string">&#x27;你点击了我！&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      点我</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//在button组件中传入了handleClick函数</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//或者可以直接写为内联箭头函数</span></span><br><span class="line">&lt;button onClick=&#123;<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">alert</span>(<span class="string">&#x27;你点击了我！&#x27;</span>);</span><br><span class="line">&#125;&#125;&gt;</span><br></pre></td></tr></table></figure><p>=&gt;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">AlertButton</span>(<span class="params">&#123; message, children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> alert(message)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Toolbar</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AlertButton</span> <span class="attr">message</span>=<span class="string">&quot;正在播放！&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        播放电影</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AlertButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AlertButton</span> <span class="attr">message</span>=<span class="string">&quot;正在上传！&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        上传图片</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AlertButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>非常值得注意的是这一串代码:<br /><code> &lt;button onClick=&#123;() =&gt; alert(message)&#125;&gt;</code><br />这里的message并没有用{}包裹,是因为外面已经有一层{}了,而react默认{}里就可以直接写入js变量了,如果再把message用{}包裹,就是把message当成对象来处理了.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">AlertButton</span> message=<span class="string">&quot;正在播放！&quot;</span>&gt;</span><br><span class="line">        播放电影</span><br><span class="line">&lt;/<span class="title class_">AlertButton</span>&gt;</span><br></pre></td></tr></table></figure><p>同时,这里明面上只传入了message一个prop,但children作为react的约定变量,可以直接隐式传入组件内部包裹的字符串,故还是传入了两个变量.</p><h3 id="state使用"><a class="markdownIt-Anchor" href="#state使用"></a> state使用</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; sculptureList &#125; <span class="keyword">from</span> <span class="string">&#x27;./data.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Gallery</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> index = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    index = index + <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> sculpture = sculptureList[index];</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        Next</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">i</span>&gt;</span>&#123;sculpture.name&#125; <span class="tag">&lt;/<span class="name">i</span>&gt;</span> </span></span><br><span class="line"><span class="language-xml">        by &#123;sculpture.artist&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h3</span>&gt;</span>  </span></span><br><span class="line"><span class="language-xml">        (&#123;index + 1&#125; of &#123;sculptureList.length&#125;)</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">img</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">src</span>=<span class="string">&#123;sculpture.url&#125;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">alt</span>=<span class="string">&#123;sculpture.alt&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;sculpture.description&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于index是局部变量,而react函数保证每次执行函数时都是从头开始渲染从而得到相同的结果,故每次执行时index都被初始化为0.</p><p>为了保留渲染之前的数据,并触发react使用新数据重新渲染,需要引入useState组件.</p><ul><li><code>const [state,setState]=useState(0)</code></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; sculptureList &#125; <span class="keyword">from</span> <span class="string">&#x27;./data.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Gallery</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [index, setIndex] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">setIndex</span>(index + <span class="number">1</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> sculpture = sculptureList[index];</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        Next</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">i</span>&gt;</span>&#123;sculpture.name&#125; <span class="tag">&lt;/<span class="name">i</span>&gt;</span> </span></span><br><span class="line"><span class="language-xml">        by &#123;sculpture.artist&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h3</span>&gt;</span>  </span></span><br><span class="line"><span class="language-xml">        (&#123;index + 1&#125; of &#123;sculptureList.length&#125;)</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">img</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">src</span>=<span class="string">&#123;sculpture.url&#125;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">alt</span>=<span class="string">&#123;sculpture.alt&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;sculpture.description&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在 React 中，useState 以及任何其他以“use”开头的函数都被称为 <strong>Hook</strong>,是一类特殊的函数,只在React渲染时有效.</li><li>useState 的唯一参数是 state 变量的初始值,在本例中被设置为0.</li><li>同时,state是组件实例内部的状态,如果渲染一个组件两次,每个组件都有完全隔离的state状态</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [number, setNumber] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;number&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">        setNumber(number + 5);</span></span><br><span class="line"><span class="language-xml">        alert(number);</span></span><br><span class="line"><span class="language-xml">      &#125;&#125;&gt;+5<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第一次点击button时,alert的内容为0,因为<strong>useState只会为下一次渲染更改state的值</strong>,永远不会在一次渲染的过程中改变state变量.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [number, setNumber] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;number&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">        setNumber(n =&gt; n + 1);</span></span><br><span class="line"><span class="language-xml">        setNumber(n =&gt; n + 1);</span></span><br><span class="line"><span class="language-xml">        setNumber(n =&gt; n + 1);</span></span><br><span class="line"><span class="language-xml">      &#125;&#125;&gt;+3<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是,上述代码会真正执行+3,因为<code>n =&gt; n + 1</code>在这里作为更新函数,会被加入更新队列,react只有在执行完更新队列后才会开始渲染,因此,下述代码的结果是每次加6.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [number, setNumber] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;number&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">        setNumber(number + 5);</span></span><br><span class="line"><span class="language-xml">        setNumber(n =&gt; n + 1);</span></span><br><span class="line"><span class="language-xml">      &#125;&#125;&gt;增加数字<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="state进阶待补充"><a class="markdownIt-Anchor" href="#state进阶待补充"></a> state进阶(待补充)</h2><h2 id="与外部系统交互"><a class="markdownIt-Anchor" href="#与外部系统交互"></a> 与外部系统交互</h2><h3 id="useref"><a class="markdownIt-Anchor" href="#useref"></a> useRef</h3><blockquote><p>当你希望组件“记住”某些信息，但又不想让这些信息 触发新的渲染 时，你可以使用 ref</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useRef &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> ref = <span class="title function_">useRef</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//useRef 返回一个这样的对象:</span></span><br><span class="line"><span class="comment">// &#123; </span></span><br><span class="line"><span class="comment">//   current: 0 // 你向 useRef 传入的值</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>像 state 一样，你可以让它指向任何东西：字符串、对象，甚至是函数。与 state 不同的是，ref 是一个普通的 JavaScript 对象，具有可以被读取和修改的 current 属性。</p></blockquote><h3 id="useeffect"><a class="markdownIt-Anchor" href="#useeffect"></a> useEffect</h3><p>首先我们需要明白Events和Effect这两个概念的区别:</p><ol><li>Events: 用户交互产生的效果</li><li>Effect: 组件本身产生的效果</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState, useRef, useEffect &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">VideoPlayer</span>(<span class="params">&#123; src, isPlaying &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> ref = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (isPlaying) &#123;</span><br><span class="line">      ref.<span class="property">current</span>.<span class="title function_">play</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      ref.<span class="property">current</span>.<span class="title function_">pause</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">video</span> <span class="attr">ref</span>=<span class="string">&#123;ref&#125;</span> <span class="attr">src</span>=<span class="string">&#123;src&#125;</span> <span class="attr">loop</span> <span class="attr">playsInline</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [isPlaying, setIsPlaying] = <span class="title function_">useState</span>(<span class="literal">false</span>);</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setIsPlaying(!isPlaying)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        &#123;isPlaying ? &#x27;暂停&#x27; : &#x27;播放&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">VideoPlayer</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">isPlaying</span>=<span class="string">&#123;isPlaying&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">src</span>=<span class="string">&quot;https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>useEffect会在每次渲染后运行</li></ul><h2 id="createcontext"><a class="markdownIt-Anchor" href="#createcontext"></a> createContext</h2><p>上下文使得组件能够无需通过显式传递参数的方式 将信息逐层传递,使用格式如下:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">SomeContext</span> = <span class="title function_">createContext</span>(defaultValue);</span><br></pre></td></tr></table></figure><p><strong>用例</strong></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createContext &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">ThemeContext</span> = <span class="title function_">createContext</span>(<span class="string">&#x27;light&#x27;</span>);</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [theme, setTheme] = <span class="title function_">useState</span>(<span class="string">&#x27;light&#x27;</span>);</span><br><span class="line">  <span class="comment">// ……</span></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ThemeContext</span> <span class="attr">value</span>=<span class="string">&#123;theme&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Page</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ThemeContext</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>老版本会出现类似Provider这样的东西,但从React 19开始可以直接这样写了.</p><h2 id="createroot"><a class="markdownIt-Anchor" href="#createroot"></a> createRoot</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createRoot &#125; <span class="keyword">from</span> <span class="string">&#x27;react-dom/client&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> domNode = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title function_">createRoot</span>(domNode);</span><br><span class="line"><span class="comment">//createRoot返回一个带有两个方法的对象,这两个方法是：render 和 unmount,一个是渲染节点,一个是销毁节点</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//root.render(&lt;App /&gt;);</span></span><br><span class="line"><span class="comment">//root.unmount();</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;基础React使用的是自创的jsx,在实际运行时需要经过编译器转译变成js.&lt;br /&gt;
实际上,jsx与原生js的最大区别在于可以将html元素作为组件,也就是说,&lt;strong&gt;React组件是一段可以使用标签进行扩展的JavaScript 函数&lt;/strong&gt;.&lt;/</summary>
      
    
    
    
    
    <category term="前端" scheme="https://revival-of-hope.github.io/tags/%E5%89%8D%E7%AB%AF/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-15 manus尝试</title>
    <link href="https://revival-of-hope.github.io/2026/03/15/archives-2026-1-2026-03-15-manus%E5%B0%9D%E8%AF%95/"/>
    <id>https://revival-of-hope.github.io/2026/03/15/archives-2026-1-2026-03-15-manus%E5%B0%9D%E8%AF%95/</id>
    <published>2026-03-15T00:00:00.000Z</published>
    <updated>2026-03-21T15:02:19.594Z</updated>
    
    <content type="html"><![CDATA[<p>从来没用过manus,这次试试水,我只给出了以下命令,用的是免费的lite版本:</p><blockquote><p>帮我做一个量化A股网站,要求使用了fastapi作为后端,nextjs作为前端,数据库用postgresql,要求使用docker配置连接,用compose.yml部署,每次生成时如果有必要重构立刻重构,保证项目结构清晰分明,routes文件夹写明,提前针对不同路由拆分好routes为多个文件.</p></blockquote><p>然后生成了这个架构:<br /><img src="/images/archives/2026/2026-03-15/PixPin_2026-03-15_14-34-27.webp" alt="alt text" /></p><p>还是挺像模像样的.</p><ul><li>后记:实际上有不少bug</li></ul><h2 id="实战320"><a class="markdownIt-Anchor" href="#实战320"></a> 实战(3/20)</h2><p>由于有朋友想让我帮他的大创弄个智能体出来,尽管我基本学习了前端和后端需要的知识,但真要我写还是无从下手的.</p><p>于是我就打算用manus来试试水,先充了一个20刀的会员(竟然可以用支付宝).</p><p>然后写了个项目需求md后贴给它,这是需求的大致结构,我没有全部贴是为了照顾别人的隐私.</p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="section">## 1. 核心技术栈 (Physical Stack)</span></span><br><span class="line"><span class="bullet">*</span> <span class="strong">**前端**</span>: Next.js (App Router), Tailwind CSS (UI 框架)。</span><br><span class="line"><span class="bullet">*</span> <span class="strong">**后端**</span>: FastAPI (异步 Web 框架), SQLModel (ORM 映射),PostgreSql作为存储数据库</span><br><span class="line"><span class="bullet">*</span> <span class="strong">**AI 编排**</span>: LangChain (LLM 逻辑链), 允许使用 DeepSeek API (核心推理引擎)和一个本地部署的ollama模型接口</span><br><span class="line"><span class="bullet">*</span> <span class="strong">**基础设施**</span>: Docker, <span class="code">`compose.yml`</span> (容器化与端口转发)。</span><br><span class="line"><span class="bullet">*</span> <span class="strong">**环境文件**</span>: 将要用到的基础配置变量写入环境文件env,并让数据库和python从这个环境库中读取配置,而非在代码中填写</span><br><span class="line"></span><br><span class="line"><span class="strong">**架构示例**</span></span><br><span class="line"></span><br><span class="line">Socrates<span class="emphasis">_AI/</span></span><br><span class="line"><span class="emphasis">├── docker-compose.yml</span></span><br><span class="line"><span class="emphasis">├── .env                  # 全局环境变量映射中心</span></span><br><span class="line"><span class="emphasis">├── prompt.json           # 系统人格与逻辑配置文件</span></span><br><span class="line"><span class="emphasis">├── data/                 # 本地 RAG 训练资料文件夹</span></span><br><span class="line"><span class="emphasis">├── scripts/</span></span><br><span class="line"><span class="emphasis">│   └── deploy.sh         # Linux 一键部署脚本</span></span><br><span class="line"><span class="emphasis">├── frontend/             # Next.js 源码</span></span><br><span class="line"><span class="emphasis">│   └── Dockerfile</span></span><br><span class="line"><span class="emphasis">└── backend/              # FastAPI 源码</span></span><br><span class="line"><span class="emphasis">    ├── main.py</span></span><br><span class="line"><span class="emphasis">    ├── models.py         # SQLModel 定义</span></span><br><span class="line"><span class="emphasis">    └── Dockerfile</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">该分文件和文件夹的时候就需要分</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">### 2. 物理布局与 UI 要求 (Gemini Style)</span></span><br><span class="line"><span class="emphasis">* <span class="strong">**深色极简**</span>: 采用 `zinc-900` 主色调，模拟高质感的思辨氛围。</span></span><br><span class="line"><span class="emphasis">* <span class="strong">**两栏架构**</span>:</span></span><br><span class="line"><span class="emphasis">    * <span class="strong">**左侧边栏**</span>: 物理展示对话历史、新建对话按钮。</span></span><br><span class="line"><span class="emphasis">    * <span class="strong">**中央对话区**</span>: 气泡式对话流，底部固定悬浮输入框。</span></span><br><span class="line"><span class="emphasis">* <span class="strong">**输入组件**</span>: </span></span><br><span class="line"><span class="emphasis">    * 支持多行文本输入。</span></span><br><span class="line"><span class="emphasis">    * 具备<span class="strong">**附件上传图标**</span>，物理支持文件拖拽或点击上传。</span></span><br><span class="line"><span class="emphasis">* 增加一个旁栏图标,对应的是已经本地配置好的智能体</span></span><br><span class="line"><span class="emphasis"></span></span><br></pre></td></tr></table></figure><p>然后在我要求manus向我核实一些需要我确认的内容后,花了差不多10min就完成了主要的架构.<br /><img src="/images/archives/2026/2026-03-16/PixPin_2026-03-20_13-01-38.webp" alt="alt text" /></p><p>但非常遗憾的是,我前前后后调了11版才实现了基础的调api进行对话的功能,耗时总计为2个多小时.<br /><img src="/images/archives/2026/2026-03-16/PixPin_2026-03-20_13-17-07.webp" alt="alt text" /><br /><img src="/images/archives/2026/2026-03-16/PixPin_2026-03-20_13-17-25.webp" alt="alt text" /><br /><img src="/images/archives/2026/2026-03-16/PixPin_2026-03-20_13-17-52.webp" alt="alt text" /><br /><img src="/images/archives/2026/2026-03-16/PixPin_2026-03-20_13-18-08.webp" alt="alt text" /><br /><img src="/images/archives/2026/2026-03-16/PixPin_2026-03-20_13-18-29.webp" alt="alt text" /><br />不得不承认,现在的智能体真的比一年前要聪明了不少,至少看上去是这样,而且经过一系列排查之后,总是能够找到实际的解决方案.<br /><img src="/images/archives/2026/2026-03-16/PixPin_2026-03-20_13-02-34.webp" alt="alt text" /></p><p>期间我基本上是这么做的:</p><ol><li>使用docker运行当前版本并测试基本功能</li><li>查看终端报错或者按F12</li><li>将报错信息贴给他</li><li>manus自己修改生成下一版</li></ol><p>最后,成功实现了本地对话:<br /><img src="/images/archives/2026/2026-03-16/PixPin_2026-03-20_13-10-49.webp" alt="alt text" /></p><p><strong>部署智能体</strong></p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">针对你的情况（学生、1–2 个月、低流量、操作简单、要临时域名），最适合的方案是 Railway，理由如下：</span><br><span class="line">推荐方案：Railway</span><br><span class="line">网址：railway.app</span><br><span class="line">为什么选它</span><br><span class="line">有免费额度：注册后每月赠送 5 美元的使用额度，你这个项目（后端 + 前端 + 数据库）一个月的实际消耗大约在 3–5 美元之间，基本可以免费跑完，流量费用几乎为零。</span><br><span class="line">自带临时域名：每个服务部署后 Railway 会自动分配一个 xxx.up.railway.app 的免费域名，并且自带 HTTPS，不需要买域名。</span><br><span class="line">操作极简：把项目文件夹上传或推送到 GitHub，Railway 识别 compose.yml 后点几下鼠标就部署完成，全程不需要登录服务器、不需要敲命令。</span><br><span class="line">用完即删：1–2 个月后直接在网页上删除项目，不产生任何后续费用。</span><br><span class="line">操作步骤（简版）</span><br><span class="line">第一步：将项目推送到 GitHub（免费私有仓库即可）。</span><br><span class="line">第二步：用 GitHub 账号登录 railway.app，点击 New Project → Deploy from GitHub repo，选择你的仓库。</span><br><span class="line">第三步：Railway 会自动识别 compose.yml，分别创建后端、前端、数据库、Nginx 四个服务。在每个服务的 Variables 页面填入 .env 中的环境变量（主要是 DEEPSEEK<span class="emphasis">_API_</span>KEY 和 SECRET<span class="emphasis">_KEY）。</span></span><br><span class="line"><span class="emphasis">第四步：点击 Deploy，等待 3–5 分钟构建完成。Railway 会自动给 Nginx 服务分配一个 xxx.up.railway.app 的临时域名，直接访问即可。</span></span><br><span class="line"><span class="emphasis">唯一需要注意的事</span></span><br><span class="line"><span class="emphasis">Railway 的免费额度是按 CPU + 内存用量计时计算的，不是按流量。你的项目在无人访问时几乎不消耗资源，所以 5 美元的额度对于低流量的 1–2 个月使用完全够用。如果超出，Railway 会发邮件提醒，你可以选择充值（最低 5 美元）或直接关闭项目。</span></span><br><span class="line"><span class="emphasis">备选方案：Render（如果 Railway 注册有问题）</span></span><br><span class="line"><span class="emphasis">render.com 操作逻辑类似，同样支持 Docker 部署和免费临时域名。免费套餐的服务在 15 分钟无流量后会自动休眠（首次访问需要等待约 30 秒唤醒），对于偶尔演示的场景可以接受，但不适合需要随时响应的场景。</span></span><br><span class="line"><span class="emphasis">结论：Railway 是你这个场景下操作最简单、费用最低、最省心的方案，临时域名开箱即用，1–2 个月后删掉项目即可，不会有任何遗留费用。</span></span><br></pre></td></tr></table></figure><ul><li>补充: Railway只支持dockerfile前后端分开部署,不支持compose.yml,真要用可以试试Vultr</li></ul><p>在有一定docker,python,nextjs的前提下,我基本能够看懂错误出自什么地方,有哪些地方写错了,有哪些地方的架构不够好,哪些库是不必导入的.<br />但是如果你自己啥也不懂,就单纯是复制粘贴信息,甚至是截图的话,我想应该要花上两倍以上的时间,还不一定能够完全搞定.</p><p>所以,我的体会是:</p><blockquote><p>即便AI能够包办整个代码的生成,但如果你连什么是好的代码,什么是应该使用的框架都不知道的话,我想AI对你的意义并没有那么大;相反,如果你能够精通所需的技术栈,再让AI帮你写代码,那么就真正做到了事半功倍,成功的实现了所谓的vibe coding.</p></blockquote><p><strong>纸上得来终觉浅,绝知此事要躬行</strong></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;从来没用过manus,这次试试水,我只给出了以下命令,用的是免费的lite版本:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;帮我做一个量化A股网站,要求使用了fastapi作为后端,nextjs作为前端,数据库用postgresql,要求使用docker配置连接,用compo</summary>
      
    
    
    
    
    <category term="新技术" scheme="https://revival-of-hope.github.io/tags/%E6%96%B0%E6%8A%80%E6%9C%AF/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-14 数据库笔记</title>
    <link href="https://revival-of-hope.github.io/2026/03/14/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AC%94%E8%AE%B0/"/>
    <id>https://revival-of-hope.github.io/2026/03/14/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AC%94%E8%AE%B0/</id>
    <published>2026-03-14T00:00:00.000Z</published>
    <updated>2026-04-15T01:26:10.718Z</updated>
    
    <content type="html"><![CDATA[<ul><li>课程用教材:&lt;&lt; Database System Concepts &gt;&gt;<ul><li>这本书讲的还是比较全面的,但废话也很多,部分地方讲的不够深</li></ul></li></ul><h1>Intro</h1><ul><li>…不够overview的overview,我试试自己学完之后来自己写一个</li></ul><h1>Introduction to the Relational Model</h1><h2 id="Structure-of-Relational-Databases">Structure of Relational Databases</h2><blockquote><p>A relational database consists of a collection of tables, each of which is assigned a<br>unique name.</p></blockquote><blockquote><p>In mathematical terminology, a <strong>tuple</strong> is simply a sequence(or list) of values. A relationship between n values is represented mathematically by an n-tuple of values, that is, a tuple with n values, which corresponds to a row in a table.</p></blockquote><p>Thus, in the relational model:</p><ul><li>the term <strong>relation</strong> is used to refer to a table;</li><li>the term <strong>tuple</strong> is used to refer to a row;</li><li>the term <strong>attribute</strong> refers to a column of a table.</li></ul><blockquote><p>For each attribute of a relation, there is a set of permitted values, called the <strong>domain</strong> of that attribute. Thus, the domain of the salary attribute of the instructor relation is the set of all possible salary values, while the domain of the name attribute is the set of all possible instructor names.</p></blockquote><p>A domain is atomic if elements of the domain are considered to be indivisible units.For example, suppose the table instructor had an attribute phone number, which can store a set of phone numbers corresponding to the instructor. Then the domain of phone number would not be atomic, since an element of the domain is a set of phonenumbers, and it has subparts, namely, the individual phone numbers in the set.</p><ul><li>如果一个域的值在逻辑上/使用上还能再拆成更小的有意义的单元，那它就不是原子的</li></ul><blockquote><p>The null value is a special value that signiﬁes that the value is unknown or does not exist.</p></blockquote><h2 id="Database-Schema">Database Schema</h2><ul><li><strong>schema</strong>: the logical design of the database<ul><li>即数据库中所有关系,属性的处理</li><li>这个概念其实很重要,会在之后多次出现.</li></ul></li></ul><h2 id="Keys">Keys</h2><ul><li>superkey: a set of one or more attributes that identify uniquely a tuple in the relation.</li></ul><blockquote><p>For example, the ID attribute of the relation instructor is suﬃcient to distinguish one instructor tuple from another. Thus, ID is a superkey. The name attribute of instructor, on the other hand, is not a superkey, because several instructors might have the same name.</p></blockquote><ul><li>candidate keys: superkeys for which no proper subset is a superkey.</li></ul><blockquote><p>It is possible that several distinct sets of attributes could serve as a candidate key.<br>Suppose that a combination of name and dept name is suﬃcient to distinguish among<br>members of the instructor relation. Then, both {ID} and {name, dept name} are candidate<br>keys. Although the attributes ID and name together can distinguish instructor tuples,<br>their combination, {ID, name}, does not form a candidate key, since the attribute ID<br>alone is a candidate key.</p></blockquote><ul><li>primary key: denote a candidate key that is chosen by the database designer as the principal means of identifying tuples within a relation<ul><li>在这里,primary key可以是由多个attribute组成的tuple</li></ul></li></ul><blockquote><p>The primary key should be chosen such that its attribute values are never, or are very rarely, changed.</p></blockquote><h2 id="Schema-Diagrams">Schema Diagrams</h2><p>A database schema, along with primary key and foreign-key constraints, can be depicted by schema diagrams</p><blockquote><p>Primary-key attributes are shown underlined. Foreign-key constraints appear as<br>arrows from the foreign-key attributes of the referencing relation to the primary key of<br>the referenced relation. We use a two-headed arrow, instead of a single-headed arrow,<br>to indicate a referential integrity constraint that is not a foreign-key constraints.</p></blockquote><h2 id="Relational-Query-Languages">Relational Query Languages</h2><ul><li><p><strong>Query Language</strong>: A language used by a user to request information from a database. It operates at a higher level of abstraction than standard programming languages.</p></li><li><p><strong>Categorization</strong>:</p><ul><li><strong>Imperative Query Language</strong>: The user instructs the system to perform a specific sequence of operations. These languages maintain a notion of <strong>state variables</strong> that are updated during computation.</li><li><strong>Functional Query Language</strong>: Computation is expressed as the evaluation of functions. These functions operate on database data or results of other functions. They are <strong>side-effect free</strong> and do not update program state.</li><li><strong>Declarative Query Language</strong>: The user describes the desired information using mathematical logic without providing specific steps or function calls. The database system is responsible for determining the physical retrieval method.</li></ul></li><li><p><strong>Pure Query Languages</strong>:</p><ul><li><strong>Relational Algebra</strong>: A <strong>functional</strong> query language that forms the theoretical basis of SQL.</li><li><strong>Tuple Relational Calculus</strong> and <strong>Domain Relational Calculus</strong>: <strong>Declarative</strong> languages.</li></ul></li><li><p><strong>Characteristics</strong>: These formal languages lack the “syntactic sugar” found in commercial languages but illustrate fundamental techniques for data extraction.</p></li></ul><h2 id="The-Relational-Algebra-3-26">The Relational Algebra(3/26)</h2><h3 id="The-Select-Operation">The Select Operation</h3><p><code>σ dept_name = “Physics” (instructor)</code></p><blockquote><p>We use the lowercase Greek letter sigma (σ) to denote <strong>selection</strong>.</p></blockquote><blockquote><p>We can ﬁnd all instructors with salary greater than $90,000 by writing:<br><code>σ salary&gt;90000 (instructor)</code></p></blockquote><h3 id="The-Project-Operation">The Project Operation</h3><blockquote><p><strong>Projection</strong> is denoted by the uppercase Greek letter pi (Π).<br><code>Π ID, name, salary (instructor)</code><br>The <strong>project</strong> operation is a unary operation that returns its argument relation, with certain attributes left out.<br>我们也可以在投影的时候进行计算:<br><code>Π ID,salary∕12 (instructor)</code></p></blockquote><h3 id="Composition-of-Relational-Operations">Composition of Relational Operations</h3><p>“Find the names of all instructors in the Physics department.”:<br><code>Π name (σ dept_name = “Physics” (instructor))</code></p><h3 id="The-Cartesian-Product-Operation">The Cartesian-Product Operation</h3><p><img src="/images/2026-03-25/PixPin_2026-03-26_09-03-48.webp" alt="alt text"></p><ul><li>也就是说关系上的笛卡尔积会生成一个nxm大小的单元组表</li></ul><h3 id="The-Join-Operation">The Join Operation</h3><p><img src="/images/2026-03-25/PixPin_2026-03-26_09-08-22.webp" alt="alt text"><br>注意这里的join没有过滤掉重复的id列!<br><img src="/images/2026-03-25/PixPin_2026-03-26_09-10-02.webp" alt="alt text"></p><h3 id="Set-Operations">Set Operations</h3><p>求并集(union)的前提条件:</p><ol><li>输入的两个关系具有相同数量的属性</li><li>当属性相关联时,两个关系中对应属性的类型必须相同<br><img src="/images/2026-03-25/PixPin_2026-03-26_09-16-15.webp" alt="alt text"></li></ol><p>求交集(intersection):<br><img src="/images/2026-03-25/PixPin_2026-03-26_09-19-11.webp" alt="alt text"></p><p>求集差(set-diﬀerence):<br><img src="/images/2026-03-25/PixPin_2026-03-26_09-21-50.webp" alt="alt text"></p><h3 id="The-Assignment-Operation">The Assignment Operation</h3><blockquote><p>It is convenient at times to write a relational-algebra expression by assigning parts of it to temporary relation variables.<br>The <strong>assignment</strong> operation, denoted by <strong>←</strong>, works like assignment in a programming language.</p></blockquote><p><img src="/images/2026-03-25/PixPin_2026-03-26_09-24-00.webp" alt="alt text"></p><h3 id="The-Rename-Operation">The Rename Operation</h3><blockquote><p>The <strong>rename</strong> operator  refers to  the results of relational-algebra expressions,denoted by the lowercase Greek letter rho (ρ).<br><img src="/images/2026-03-25/PixPin_2026-03-27_08-46-34.webp" alt="alt text"></p></blockquote><h1>Introduction to SQL</h1><h3 id="Overview-of-the-SQL-Query-Language">Overview of the SQL Query Language</h3><ul><li>The SQL language has several parts:<ul><li><strong>Data-definition language</strong> (DDL). The SQL DDL provides commands for deﬁning relation schemas, deleting relations, and modifying relation schemas.</li><li><strong>Data-manipulation language</strong> (DML). The SQL DML provides the ability to query information from the database and to insert tuples into, delete tuples from, and modify tuples in the database.<ul><li>从这可以看到Data-manipulation是操作实例,Data-definition是操作关系模型</li></ul></li><li>Integrity. The SQL DDL includes commands for specifying integrity constraints that the data stored in the database must satisfy. Updates that violate integrity constraints are disallowed.<ul><li>即规范性.</li></ul></li><li>and so on</li></ul></li></ul><h3 id="SQL-Data-Definition">SQL Data Definition</h3><p>注意这里介绍的都是初级语法,后面还有高级语法.</p><h4 id="Basic-Types">Basic Types</h4><p>The SQL standard supports a variety of built-in types, including:</p><ul><li><p><strong>char(n)</strong><br>Fixed-length character string.<br>Exactly n characters long.<br>Padded with spaces if shorter value inserted.<br>Full form: character(n).</p></li><li><p><strong>varchar(n)</strong><br>Variable-length character string.<br>Maximum n characters.<br>Stores only actual characters (no padding).<br>Full form: character varying(n).</p></li><li><p><strong>int</strong><br>Integer.<br>Machine-dependent range (usually 32-bit).<br>Full form: integer.</p></li><li><p><strong>smallint</strong><br>Small integer.<br>Smaller machine-dependent range than int (usually 16-bit).</p></li><li><p><strong>numeric(p, d)</strong><br>Fixed-point (exact decimal) number.<br>Total digits: p (including sign).<br>Decimal places: d.<br>Example: numeric(3,1) stores -99.9 to 99.9 exactly.<br>Cannot store 444.5 (exceeds p) or 0.32 (needs d≥2).</p></li><li><p><strong>real</strong><br>Single-precision floating-point.<br>Machine-dependent precision (usually IEEE 32-bit).</p></li><li><p><strong>double precision</strong><br>Double-precision floating-point.<br>Machine-dependent higher precision (usually IEEE 64-bit).</p></li><li><p><strong>float(n)</strong><br>Floating-point with minimum precision of n decimal digits.<br>Implementation chooses actual precision ≥ n.</p></li></ul><p>Each type may include a special value called the <strong>null value</strong>. A null value indicates<br>an absent value that may exist but be unknown or that may not exist at all.</p><p>When comparing a char type with a varchar type, one may expect extra spaces to be added to the varchar type to make the lengths equal, before comparison; however, this may or may not be done, depending on the database system. As a result, even if the same value “Avi” is stored in the attributes A and B above, a comparison A=B may return false. We recommend you always use the varchar type instead of the char type to avoid these problems.</p><ul><li>也就是说,可变数组在和固定长度数组比较时未必会加上对应长度的空格后再比较</li></ul><h4 id="Basic-Schema-Definition">Basic Schema Definition</h4><p>We deﬁne an SQL relation by using the create table command. The following command creates a relation department in the database:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create table</span> department</span><br><span class="line">(</span><br><span class="line">  dept_name vacchar(<span class="number">20</span>),</span><br><span class="line">  building  <span class="type">varchar</span>(<span class="number">15</span>),</span><br><span class="line">  budget    <span class="type">numeric</span>(<span class="number">12</span>,<span class="number">2</span>),</span><br><span class="line">  <span class="keyword">primary key</span>(dept_name)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><ul><li>结尾的<code>;</code>在大多数sql版本中是可选的</li></ul><p>SQL supports a number of diﬀerent integrity constraints. In this section, we discuss only a few of them:</p><ul><li><p>primary key: required to be nonnull and unique</p><ul><li>也就是说不可重复,不可为null</li><li>Although the primary-key speciﬁcation is optional, it is generally a good idea to specify a primary key for each relation.</li></ul></li><li><p>foreign key(A) references s: the value of A in this relation must correspond  to value of the primary key attributes in relation s.</p><ul><li>也就是说不允许把s关系中不存在的值写在这个关系中</li></ul></li><li><p>not null: the null value is not allowed for that attribute</p></li><li><p><strong>drop table</strong>: remove a relation from an SQL database</p><ul><li><code>drop table r;</code>只要这样就可以删除关系r了</li></ul></li><li><p><strong>delete from</strong>:  retains relation r, but deletes all tuples in r.</p><ul><li><code>delete from r;</code></li></ul></li><li><p><strong>alter table</strong>:  add or drop attributes to an existing relation</p><ul><li><code>alter table r add A D;</code>:where r is the name of an existing relation, A is the name of the attribute to be added,and D is the type of the added attribute.</li><li><code>alter table r drop A;</code>:  drop attributes from a relation<br><strong>eg</strong></li></ul></li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create table</span> teaches</span><br><span class="line">(</span><br><span class="line">ID <span class="type">varchar</span> (<span class="number">5</span>),</span><br><span class="line">course id <span class="type">varchar</span> (<span class="number">8</span>),</span><br><span class="line">sec id <span class="type">varchar</span> (<span class="number">8</span>),</span><br><span class="line">semester <span class="type">varchar</span> (<span class="number">6</span>),</span><br><span class="line"><span class="keyword">year</span> <span class="type">numeric</span> (<span class="number">4</span>,<span class="number">0</span>),</span><br><span class="line"><span class="keyword">primary key</span> (ID, course id, sec id, semester, <span class="keyword">year</span>),</span><br><span class="line"><span class="keyword">foreign key</span> (course id, sec id, semester, <span class="keyword">year</span>) <span class="keyword">references</span> section,</span><br><span class="line"><span class="keyword">foreign key</span> (ID) <span class="keyword">references</span> instructor</span><br><span class="line">);</span><br><span class="line"><span class="keyword">alter table</span> teaches  <span class="keyword">drop</span> ID;</span><br><span class="line"><span class="keyword">drop</span> <span class="keyword">table</span> teaches;</span><br></pre></td></tr></table></figure><h3 id="Basic-Structure-of-SQL-Queries">Basic Structure of SQL Queries</h3><p>The basic structure of an SQL query consists of three clauses: <strong>select, from, and where</strong>.<br>A query takes as its input the relations listed in the from clause, operates on them as speciﬁed in the where and select clauses, and then produces a relation as the result.</p><ul><li>事实上在部分语句或者某些数据库系统中where,from是可选的,但select是必须出现的</li></ul><h4 id="Queries-on-a-Single-Relation">Queries on a Single Relation</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> dept_name</span><br><span class="line"><span class="keyword">from</span> instructor;</span><br></pre></td></tr></table></figure><p>Since more than one instructor can belong to a department, a department name could appear more than once in the instructor relation.<br>We can rewrite the preceding query as:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> dept name</span><br><span class="line"><span class="keyword">from</span> instructor;</span><br></pre></td></tr></table></figure><p>The result of the above query would contain each department name at most once.</p><p>Also,SQL allows us to use the keyword all to specify explicitly that duplicates are not removed:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="keyword">all</span> dept name</span><br><span class="line"><span class="keyword">from</span> instructor;</span><br></pre></td></tr></table></figure><p>Since duplicate retention is the default, we shall not use all in our examples.</p><p>The select clause may also contain arithmetic expressions involving the operators +, −, ∗, and / operating on constants or attributes of tuples:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> ID, name, dept name, salary <span class="operator">*</span> <span class="number">1.1</span></span><br><span class="line"><span class="keyword">from</span> instructor;</span><br></pre></td></tr></table></figure><p>这不会改动原关系里的salary数值,只是在输出上将salary乘以1.1了</p><p>The where clause allows us to select only those rows in the result relation of the from clause that satisfy a speciﬁed predicate(断言)</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept name <span class="operator">=</span> <span class="string">&#x27;Comp. Sci.&#x27;</span> <span class="keyword">and</span> salary <span class="operator">&gt;</span> <span class="number">70000</span>;</span><br></pre></td></tr></table></figure><p>SQL allows the use of the logical connectives and, or, and not in the where clause.<br>The operands of the logical connectives can be expressions involving the comparison operators &lt;, &lt;=, &gt;, &gt;=, =, and &lt;&gt;.</p><h4 id="Queries-on-Multiple-Relations">Queries on Multiple Relations</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> instructor.dept_name, building, name</span><br><span class="line"><span class="keyword">from</span> instructor, department</span><br><span class="line"><span class="keyword">where</span> instructor.dept_name<span class="operator">=</span> department.dept_name;</span><br></pre></td></tr></table></figure><p>注意到在不同关系中同时出现的attribute需要用关系名作为前缀来指明,而独特的attribute则不用</p><p><strong>from解析</strong><br>The from clause by itself deﬁnes a Cartesian product of the relations listed in the clause. It is deﬁned formally in terms of relational algebra, but it can also be understood as an iterative process that generates tuples for the result relation of the from clause.</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">for each tuple t1 in relation r1</span><br><span class="line">  for each tuple t2 in relation r2</span><br><span class="line">    …</span><br><span class="line">      for each tuple tm in relation rm</span><br><span class="line">        Concatenate t1 , t2 , … , tm into a single tuple t</span><br><span class="line">        Add t into the result relation</span><br></pre></td></tr></table></figure><p>也就是说from实际上会将所有关系用笛卡尔积制成一个nxm的表,如果不用where来过滤掉多余组合的话,在大型数据库中select将很难处理这么多数据</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> s.name, c.course_name</span><br><span class="line"><span class="keyword">FROM</span> students s, courses c;</span><br></pre></td></tr></table></figure><p>→ 结果行数 = students 行数 × courses 行数（所有学生 × 所有课程的组合）</p><h3 id="Additional-Basic-Operations">Additional Basic Operations</h3><h4 id="The-Rename-Operation-2">The Rename Operation</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name, course id</span><br><span class="line"><span class="keyword">from</span> instructor, teaches</span><br><span class="line"><span class="keyword">where</span> instructor.ID<span class="operator">=</span> teaches.ID;</span><br></pre></td></tr></table></figure><p>The result of this query is a relation with the following attributes:<br><code>name, course id</code></p><p>We cannot, however, always derive names in this way, for several reasons:</p><p>First, two relations in the <strong>from clause</strong> may have attributes with the same name, in which case an attribute name is duplicated in the result.</p><p>Second, if we use an arithmetic expression in the <strong>select clause</strong>, the resultant attribute does not have a name.</p><p>Third, even if an attribute name can be derived from the base relations as in the preceding example, we may want to change the attribute name in the result.</p><p>Hence, SQL provides a way of renaming the attributes of a result relation. It uses the <strong>as clause</strong>, taking the form:<br><code>old-name as new-name</code></p><p>For example, if we want the attribute name <strong>name</strong> to be replaced with the name <strong>instructor_name</strong>, we can rewrite the preceding query as:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name <span class="keyword">as</span> instructor name, course id</span><br><span class="line"><span class="keyword">from</span> instructor, teaches</span><br><span class="line"><span class="keyword">where</span> instructor.ID<span class="operator">=</span> teaches.ID;</span><br></pre></td></tr></table></figure><p>The <strong>as clause</strong> is particularly useful in renaming relations.</p><p>One reason to rename a relation is to replace a long relation name with a shortened version that is more convenient to use elsewhere in the query.</p><p>To illustrate, we rewrite the query <strong>“For all instructors in the university who have taught some course, find their names and the course ID of all courses they taught.”</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> T.name, S.course_id</span><br><span class="line"><span class="keyword">from</span> instructor <span class="keyword">as</span> T, teaches <span class="keyword">as</span> S</span><br><span class="line"><span class="keyword">where</span> T.ID <span class="operator">=</span> S.ID;</span><br></pre></td></tr></table></figure><p>Another reason to rename a relation is a case where we wish to compare tuples in the same relation. We then need to take the <strong>Cartesian product</strong> of a relation with itself and, without renaming, it becomes impossible to distinguish one tuple from the other.</p><p>Suppose that we want to write the query <strong>“Find the names of all instructors whose salary is greater than at least one instructor in the Biology department.”</strong></p><p>We can write the SQL expression:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> T.name</span><br><span class="line"><span class="keyword">from</span> instructor <span class="keyword">as</span> T, instructor <span class="keyword">as</span> S</span><br><span class="line"><span class="keyword">where</span> T.salary <span class="operator">&gt;</span> S.salary <span class="keyword">and</span> S.dept_name <span class="operator">=</span> <span class="string">&#x27;Biology&#x27;</span>;</span><br></pre></td></tr></table></figure><h4 id="String-Operations">String Operations</h4><p>SQL speciﬁes strings by enclosing them in <strong>single quotes</strong>, for example, ‘Computer’. A single quote character that is part of a string can be speciﬁed by using two single quote characters.</p><p>For example, the string “It’s right” can be speciﬁed by ‘It’‘s right’.</p><p>The SQL standard speciﬁes that the equality operation on strings is <strong>case sensitive</strong>;as a result, the expression “‘comp. sci.’ = ‘Comp. Sci.’” evaluates to false.</p><ul><li>但在MySQL和SQL Server中,在字符串比较时不区分大小写,故“‘comp. sci.’ = ‘Comp. Sci.’” 为真.</li></ul><p>Pattern matching can be performed on strings using the operator like. We describe patterns by using two special characters:</p><ul><li>Percent (%): The % character matches any substring.</li><li>Underscore (_): The character matches any character.</li></ul><p>Patterns are case <a href="http://sensitive.To">sensitive.To</a> illustrate pattern matching, we consider the following examples:</p><ul><li>‘Intro%’ matches any string beginning with “Intro”.</li><li>‘%Comp%’ matches any string containing “Comp” as a substring, for example, ‘Intro. to Computer Science’, and ‘Computational Biology’.</li><li>‘___’ matches any string of exactly three characters.</li><li>‘___%’ matches any string of at least three characters.</li></ul><p>For patterns to include the special pattern characters (that is, % and ), SQL allows the<br>speciﬁcation of an escape character. The escape character is used immediately before<br>a special pattern character to indicate that the special pattern character is to be treated<br>like a normal character. We deﬁne the escape character for a like comparison using the<br>escape keyword. To illustrate, consider the following patterns, which use a backslash(∖) as the escape character:</p><ul><li><strong>like ‘ab∖%cd%’ escape ‘∖’</strong> matches all strings beginning with “ab%cd”.</li><li><strong>like ‘ab∖∖cd%’ escape ‘∖’</strong> matches all strings beginning with “ab∖cd”.</li></ul><p>也就是说,sql没有通用的转义符,而是需要用户定义并写入字符串中</p><p>SQL allows us to search for mismatches instead of matches by using the not like com-<br>parison operator. Some implementations provide variants of the like operation that do<br>not distinguish lower- and uppercase.</p><h4 id="Attribute-Specification-in-the-Select-Clause">Attribute Specification in the Select Clause</h4><p>The asterisk symbol “ * ” can be used in the select clause to denote “all attributes.”</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> instructor.<span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> instructor, teaches</span><br><span class="line"><span class="keyword">where</span> instructor.ID<span class="operator">=</span> teaches.ID;</span><br></pre></td></tr></table></figure><p>It indicates that all attributes of instructor are to be selected.</p><h3 id="Ordering-the-Display-of-Tuples">Ordering the Display of Tuples</h3><p>The <strong>order by</strong> clause causes the tuples in the result of a query to appear in sorted order.</p><p>By default, the order by clause lists items in ascending order. To specify the sort order,<br>we may specify desc for descending order or asc for ascending order. Furthermore,<br>ordering can be performed on multiple attributes. Suppose that we wish to list the<br>entire instructor relation in descending order of salary. If several instructors have the<br>same salary, we order them in ascending order by name. We express this query in SQL as follows:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">order</span> <span class="keyword">by</span> salary <span class="keyword">desc</span>, name <span class="keyword">asc</span>;</span><br></pre></td></tr></table></figure><h3 id="Where-Clause-Predicates">Where-Clause Predicates</h3><p>SQL includes a <strong>between</strong> comparison operator to simplify <strong>where</strong> clauses that specify<br>that a value be less than or equal to some value and greater than or equal to some other value.</p><p>Similarly, we can use the not between comparison operator.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="keyword">between</span> <span class="number">90000</span> <span class="keyword">and</span> <span class="number">100000</span>;</span><br></pre></td></tr></table></figure><p>尽管它等价于以下语句,但看上去就直白了一点</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&lt;=</span> <span class="number">100000</span> <span class="keyword">and</span> salary <span class="operator">&gt;=</span> <span class="number">90000</span>;</span><br></pre></td></tr></table></figure><p>SQL permits us to use the notation (v1 , v2 , … , vn ) to denote a tuple of arity n con-<br>taining values v1 , v2 , … , vn ; the notation is called a row constructor. The comparison<br>operators can be used on tuples, and the ordering is deﬁned lexicographically. For ex-<br>ample, (a1 , a2 ) &lt;= (b1 , b2 ) is true if a1 &lt;= b1 and a2 &lt;= b2 ; similarly, the two tuples<br>are equal if all their attributes are equal. Thus, the SQL query:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name, course id</span><br><span class="line"><span class="keyword">from</span> instructor, teaches</span><br><span class="line"><span class="keyword">where</span> instructor.ID<span class="operator">=</span> teaches.ID <span class="keyword">and</span> dept name <span class="operator">=</span> <span class="string">&#x27;Biology&#x27;</span>;</span><br></pre></td></tr></table></figure><p>can be rewritten as follows:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name, course id</span><br><span class="line"><span class="keyword">from</span> instructor, teaches</span><br><span class="line"><span class="keyword">where</span> (instructor.ID, dept name) <span class="operator">=</span> (teaches.ID, <span class="string">&#x27;Biology&#x27;</span>);</span><br></pre></td></tr></table></figure><ul><li>看似简写了,但其实看上去更懵了</li></ul><h2 id="Set-Operations-2">Set Operations</h2><p>The SQL operations <strong>union, intersect, and except</strong> operate on relations and correspond to the mathematical set operations <strong>∪, ∩, and −</strong>.</p><h3 id="The-Union-Operation">The Union Operation</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span>)</span><br><span class="line"><span class="keyword">union</span></span><br><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br></pre></td></tr></table></figure><p>这里只是把两个结果合并了一下而已</p><p>Note that the parentheses we include around each select-<br>from-where statement below are optional but useful for ease of reading.</p><ul><li>The <strong>union</strong> operation automatically eliminates duplicates, unlike the <strong>select</strong> clause.</li></ul><p>If we want to retain all duplicates, we must write <strong>union all</strong> in place of <strong>union</strong>.</p><h3 id="The-Intersect-Operation">The Intersect Operation</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span>)</span><br><span class="line"><span class="keyword">intersect</span></span><br><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br></pre></td></tr></table></figure><p>找交集</p><ul><li>同样可以加一个all来保留相同字段</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span>)</span><br><span class="line"><span class="keyword">intersect</span> <span class="keyword">all</span></span><br><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br></pre></td></tr></table></figure><h3 id="The-Except-Operation">The Except Operation</h3><p>To ﬁnd all courses taught in the Fall 2017 semester <strong>but not</strong> in the Spring 2018 semester,we write:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span>)</span><br><span class="line"><span class="keyword">except</span></span><br><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br></pre></td></tr></table></figure><p>The <strong>except</strong> operation outputs all tuples from its ﬁrst input that do not occur in the second input.</p><ul><li>同样可以加一个all来保留相同字段</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span>)</span><br><span class="line"><span class="keyword">except</span> <span class="keyword">all</span></span><br><span class="line">(<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br></pre></td></tr></table></figure><h2 id="Null-Values">Null Values</h2><p><strong>Null values</strong> present special problems in relational operations, including arithmetic operations, comparison operations, and set operations.</p><p>The result of an arithmetic expression (involving, for example, +, −, ∗, or ∕) is null.</p><p>If any of the input values is null. For example, if a query has an expression r.A + 5, and r.A is null for a particular tuple, then the expression result must also be null for that tuple.</p><ul><li>也就是说null参与的数学运算结果都是null</li></ul><p>Comparisons involving nulls are more of a problem.SQL therefore treats as <strong>unknown</strong> the result of any comparison involving a null value.This creates a third logical value in addition to true and false.</p><p>Since the predicate in a where clause can involve Boolean operations such as and,or, and not on the results of comparisons, the deﬁnitions of the Boolean operations are extended to deal with the value <strong>unknown</strong>.</p><ul><li>and: The result of true and unknown is unknown, <strong>false and unknown is false</strong>, while unknown and unknown is unknown.</li><li>or: The result of true or unknown is true, <strong>false or unknown is unknown</strong>, while unknown or unknown is unknown.</li><li>not: The result of not unknown is unknown.</li></ul><p><strong>为什么这样设计?</strong></p><ul><li>TRUE AND UNKNOWN 为什么是 UNKNOWN?<ul><li>如果 UNKNOWN 实际上是 TRUE：true and true = true</li><li>如果 UNKNOWN 实际上是 FALSE: true and false =false</li><li>由于结果可能是 TRUE 也可能是 FALSE，数据库无法给出定论，只能保守地标注为 UNKNOWN。</li></ul></li><li>false and unknown 为什么是 false?<ul><li>含有false的and语句恒为false,结果是确定的,故可标记为false.</li></ul></li><li>true or unknown 为什么是 true?<ul><li>同理,含有true的or语句恒为true.</li></ul></li><li>false or unknown 为什么是 unknown?<ul><li>同理,结果可能是 TRUE 也可能是 FALSE,无法定论.</li></ul></li></ul><p>SQL uses the special keyword null in a predicate to test for a null value.<br>Thus, to ﬁnd all instructors who appear in the instructor relation with null values for salary, we write:</p><ul><li>补充: <code>5*20+3=null</code>,<code>null=null</code>,返回值都是unknown.</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="keyword">is</span> <span class="keyword">null</span>;</span><br></pre></td></tr></table></figure><ul><li>有<code>is null</code>当然就有<code>is not null</code>.</li></ul><p>SQL allows us to test whether the result of a comparison is unknown,by using the clauses is unknown and is not unknown.For example:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&gt;</span> <span class="number">10000</span> <span class="keyword">is</span> <span class="literal">unknown</span>;</span><br></pre></td></tr></table></figure><h2 id="Aggregate-Functions">Aggregate Functions</h2><p>Aggregate functions are functions that take a collection (a set or multiset) of values as<br>input and return a single value. SQL oﬀers ﬁve standard built-in aggregate functions:</p><ol><li>Average: avg</li><li>Minimum: min</li><li>Maximum: max</li><li>Total: sum</li><li>Count: count</li></ol><h3 id="Basic-Aggregation">Basic Aggregation</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="built_in">avg</span> (salary) <span class="keyword">as</span> avg_salary</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept name <span class="operator">=</span> <span class="string">&#x27;Comp. Sci.&#x27;</span>;</span><br></pre></td></tr></table></figure><ul><li>找到salary的平均值</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="built_in">count</span> (<span class="keyword">distinct</span> ID)</span><br><span class="line"><span class="keyword">from</span> teaches</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span> <span class="operator">=</span> <span class="number">2018</span>;</span><br></pre></td></tr></table></figure><ul><li>使用count关键字统计()内的具有对应属性的行数</li></ul><h3 id="Aggregation-with-Grouping">Aggregation with Grouping</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> dept_name, <span class="built_in">avg</span> (salary) <span class="keyword">as</span> avg salary</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> dept_name;</span><br></pre></td></tr></table></figure><p>这里把instructor用dept_name分组,从而统计出每个部门的avg_salary.</p><ul><li>也就是说<code>group by</code> 需要在select语句中写明操作的对象,才能在结果中反映出来分组的结果</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* erroneous query */</span></span><br><span class="line"><span class="keyword">select</span> dept_name, ID, <span class="built_in">avg</span> (salary)</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> dept_name;</span><br></pre></td></tr></table></figure><ul><li>上述sql语句由于一个分组中的tuple可以有不同的id属性,故不能变成一个分组,会引发错误</li></ul><blockquote><p>总结一下:<br>当 SQL 查询包含 GROUP BY 子句时，SELECT 子句中的任何属性（列名）<strong>必须满足以下两个条件之一</strong>，否则该查询在逻辑上是错误的：</p></blockquote><ol><li>该属性出现在 GROUP BY 子句中。</li><li>该属性被包裹在聚合函数（如 SUM, COUNT, AVG, MAX, MIN）之中。</li></ol><p><strong>进阶版</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> dept_name, <span class="built_in">count</span> (<span class="keyword">distinct</span> ID) <span class="keyword">as</span> instr count</span><br><span class="line"><span class="keyword">from</span> instructor, teaches</span><br><span class="line"><span class="keyword">where</span> instructor.ID <span class="operator">=</span> teaches.ID <span class="keyword">and</span></span><br><span class="line">semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span> <span class="operator">=</span> <span class="number">2018</span></span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> dept_name;</span><br></pre></td></tr></table></figure><ul><li>“Find the number of instructors in each department who teach a course in the Spring 2018 semester.”</li></ul><h3 id="The-Having-Clause">The Having Clause</h3><p>为了处理分组,而不是处理分组后的tuples,引入了<strong>having</strong> clause.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> dept name, <span class="built_in">avg</span> (salary) <span class="keyword">as</span> avg_salary</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> dept name</span><br><span class="line"><span class="keyword">having</span> <span class="built_in">avg</span> (salary) <span class="operator">&gt;</span> <span class="number">42000</span>;</span><br></pre></td></tr></table></figure><ul><li>上述语句会过滤掉ayg_salary小于等于42000的分组</li></ul><p>The logical execution sequence of an SQL query containing aggregation, <code>GROUP BY</code>, or <code>HAVING</code> clauses is defined as follows:</p><ol><li><strong>Evaluate the <code>FROM</code> clause</strong><br>The <code>FROM</code> clause is first evaluated to generate a relation (the initial set of data from specified tables or joins).</li><li><strong>Apply the <code>WHERE</code> clause</strong> (if present)<br>The predicate in the <code>WHERE</code> clause is applied to the result relation of the <code>FROM</code> clause. Only tuples satisfying this predicate proceed.</li><li><strong>Apply the <code>GROUP BY</code> clause</strong> (if present)<br>Tuples satisfying the <code>WHERE</code> predicate are placed into groups based on the <code>GROUP BY</code> clause. If the <code>GROUP BY</code> clause is absent, the entire set of filtered tuples is treated as a single group.</li><li><strong>Apply the <code>HAVING</code> clause</strong> (if present)<br>The <code>HAVING</code> clause is applied to each group. Groups that do not satisfy the <code>HAVING</code> clause predicate are removed entirely.</li><li><strong>Evaluate the <code>SELECT</code> clause</strong><br>The <code>SELECT</code> clause uses the remaining groups to generate the final result tuples. Aggregate functions are applied here to produce a single result tuple for each group.</li></ol><p><strong>长难句分析</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> course_id, semester, <span class="keyword">year</span>, sec_id, <span class="built_in">avg</span> (tot_cred)</span><br><span class="line"><span class="keyword">from</span> student, takes</span><br><span class="line"><span class="keyword">where</span> student.ID<span class="operator">=</span> takes.ID <span class="keyword">and</span> <span class="keyword">year</span> <span class="operator">=</span> <span class="number">2017</span></span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> course_id, semester, <span class="keyword">year</span>, sec_id</span><br><span class="line"><span class="keyword">having</span> <span class="built_in">count</span> (ID) <span class="operator">&gt;=</span> <span class="number">2</span>;</span><br></pre></td></tr></table></figure><ul><li>这里把学生按照“班级”扔进不同的筐里。比如“数据库-2017-秋-01班”是一个筐，“算法-2017-秋-02班”是另一个筐</li></ul><blockquote><p>也就是说,group by后面的属性全部相同时才会被归为一组</p></blockquote><h3 id="Aggregation-with-Null-and-Boolean-Values">Aggregation with Null and Boolean Values</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="built_in">sum</span> (salary)</span><br><span class="line"><span class="keyword">from</span> instructor;</span><br></pre></td></tr></table></figure><p>当salary中有null值的时候,并不会像普通的<code>5+null=null</code>一样返回null,而是会忽略null</p><blockquote><p>In general, aggregate functions treat nulls according to the following rule: All aggre-<br>gate functions except count (*) ignore null values in their input collection. As a result<br>of null values being ignored, the collection of values may be empty. The count of an<br>empty collection is deﬁned to be 0, and all other aggregate operations return a value<br>of null when applied on an empty collection</p></blockquote><ul><li>由于count是统计行数的,故当某一行有null时也会计入</li></ul><h2 id="Nested-Subqueries">Nested Subqueries</h2><blockquote><p>A subquery is a <strong>select-from-where</strong> expression that is nested within another query. A common use of subqueries is to perform tests for set membership, make set comparisons, and determine set cardinality by nesting subqueries in the <strong>where</strong> clause.</p></blockquote><h3 id="Set-Membership">Set Membership</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> course_id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span> <span class="keyword">and</span></span><br><span class="line">course_id <span class="keyword">in</span> (<span class="keyword">select</span> course_id</span><br><span class="line">              <span class="keyword">from</span> section</span><br><span class="line">              <span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br></pre></td></tr></table></figure><p>We use the not in construct in a way similar to the in construct. For example, to ﬁnd<br>all the courses taught in the Fall 2017 semester but not in the Spring 2018 semester,<br>which we expressed earlier using the except operation, we can write:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span> <span class="keyword">and</span></span><br><span class="line">course id <span class="keyword">not</span> <span class="keyword">in</span> (<span class="keyword">select</span> course id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br></pre></td></tr></table></figure><ul><li>也就是说,之前提到的intersect和except都可以用subquery的形式来解决</li></ul><h3 id="Set-Comparison">Set Comparison</h3><p>The phrase “greater than at least one” is represented in SQL by <strong>&gt; some</strong>.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&gt;</span> <span class="keyword">some</span> (<span class="keyword">select</span> salary</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Biology&#x27;</span>);</span><br></pre></td></tr></table></figure><blockquote><p>SQL also allows &lt; some, &lt;= some, &gt;= some, = some, and &lt;&gt; some comparisons.<br>As an exercise, verify that = some is identical to in, whereas &lt;&gt; some is not the same as not in.</p></blockquote><p>The construct <strong>&gt; all</strong> corresponds to the phrase “greater than all.”</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&gt;</span> <span class="keyword">all</span> (<span class="keyword">select</span> salary</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept name <span class="operator">=</span> <span class="string">&#x27;Biology&#x27;</span>);</span><br></pre></td></tr></table></figure><p>As it does for some, SQL also allows &lt; all, &lt;= all, &gt;= all, = all, and &lt;&gt; all comparisons.<br>As an exercise, verify that &lt;&gt; all is identical to not in, whereas = all is not the same as<br>in.</p><h3 id="Test-for-Empty-Relations">Test for Empty Relations</h3><p>The <strong>exists</strong> construct returns the value true if the argument subquery is nonempty.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> course_id</span><br><span class="line"><span class="keyword">from</span> section <span class="keyword">as</span> S</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span> <span class="keyword">and</span></span><br><span class="line">  <span class="keyword">exists</span> (<span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line">          <span class="keyword">from</span> section <span class="keyword">as</span> T</span><br><span class="line">          <span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> </span><br><span class="line">          <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span> </span><br><span class="line">          <span class="keyword">and</span> S.course_id<span class="operator">=</span> T .course_id);</span><br></pre></td></tr></table></figure><blockquote><p>The above query also illustrates a feature of SQL where a correlation name from<br>an outer query (S in the above query), can be used in a subquery in the where clause.<br>A subquery that uses a correlation name from an outer query is called a correlated<br>subquery.</p></blockquote><p>当然,有exists就有<code>not exists</code>:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> S.ID, S.name</span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">as</span> S</span><br><span class="line"><span class="keyword">where</span> <span class="keyword">not</span> <span class="keyword">exists</span> ((<span class="keyword">select</span> course_id</span><br><span class="line">                    <span class="keyword">from</span> course</span><br><span class="line">                    <span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Biology&#x27;</span>)</span><br><span class="line">                    <span class="keyword">except</span></span><br><span class="line">                    (<span class="keyword">select</span> T .course_id</span><br><span class="line">                    <span class="keyword">from</span> takes <span class="keyword">as</span> T</span><br><span class="line">                    <span class="keyword">where</span> S.ID <span class="operator">=</span> T .ID));</span><br></pre></td></tr></table></figure><p><strong>长难句分析</strong><br>首先计算“Biology 系开设了，但该学生没选”的课程,如果这类课程not exists,说明该学生选了所有Biology系的课程.</p><h3 id="Test-for-the-Absence-of-Duplicate-Tuples-3-18">Test for the Absence of Duplicate Tuples(3/18)</h3><p>Using the <strong>unique</strong> construct, we can write the query “Find all courses that were oﬀered at most once in 2017” as follows:</p><ul><li>如果你记忆力够好的话,可能会记得之前在create table的时候会用unique来保证对应的属性不能有重复值</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> T .course id</span><br><span class="line"><span class="keyword">from</span> course <span class="keyword">as</span> T</span><br><span class="line"><span class="keyword">where</span> <span class="keyword">unique</span> (<span class="keyword">select</span> R.course id</span><br><span class="line"><span class="keyword">from</span> section <span class="keyword">as</span> R</span><br><span class="line"><span class="keyword">where</span> T .course id<span class="operator">=</span> R.course id <span class="keyword">and</span></span><br><span class="line">R.year <span class="operator">=</span> <span class="number">2017</span>);</span><br></pre></td></tr></table></figure><blockquote><p>Note that if a course were not oﬀered in 2017, the subquery would return an empty result, and the unique predicate would evaluate to true on the empty set.<br>也就是说即使结果为空,但由于是unique,故也会返回true.</p></blockquote><p>当然,有了unique就有<code>not unique</code>,要求返回结果中的每个tuple至少出现两次</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> T .course id</span><br><span class="line"><span class="keyword">from</span> course <span class="keyword">as</span> T</span><br><span class="line"><span class="keyword">where</span> <span class="keyword">not</span> <span class="keyword">unique</span> (<span class="keyword">select</span> R.course id</span><br><span class="line">                  <span class="keyword">from</span> section <span class="keyword">as</span> R</span><br><span class="line">                  <span class="keyword">where</span> T .course id<span class="operator">=</span> R.course id <span class="keyword">and</span></span><br><span class="line">                  R.year <span class="operator">=</span> <span class="number">2017</span>);</span><br></pre></td></tr></table></figure><ul><li>可以看到,where部分的subquery可以访问外层的关系</li></ul><h3 id="Subqueries-in-the-From-Clause">Subqueries in the From Clause</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> dept name, avg salary</span><br><span class="line"><span class="keyword">from</span> (<span class="keyword">select</span> dept name, <span class="built_in">avg</span> (salary)</span><br><span class="line">      <span class="keyword">from</span> instructor</span><br><span class="line">      <span class="keyword">group</span> <span class="keyword">by</span> dept name)</span><br><span class="line">      <span class="keyword">as</span> dept <span class="built_in">avg</span> (dept name, avg salary)</span><br><span class="line"><span class="keyword">where</span> avg salary <span class="operator">&gt;</span> <span class="number">42000</span>;</span><br></pre></td></tr></table></figure><ul><li>是的,from的关系也能用别名…</li></ul><p>但是值得注意的是,<strong>在from中的subquery不能访问外层的关系</strong>,因为from中的关系是相互并列的,不存在嵌套关系,只能通过lateral关键字来强制让后继关系能够获取前置关系</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name, salary, avg_salary</span><br><span class="line"><span class="keyword">from</span> instructor I1, <span class="keyword">lateral</span> (<span class="keyword">select</span> <span class="built_in">avg</span>(salary) <span class="keyword">as</span> avg_salary</span><br><span class="line"><span class="keyword">from</span> instructor I2</span><br><span class="line"><span class="keyword">where</span> I2.dept name<span class="operator">=</span> I1.dept_name);</span><br></pre></td></tr></table></figure><blockquote><p>如果你跟我一样从头看到这里的话,你会清楚的记得前面都是写类似<code>instructor as I1</code>的方式来起别名的,但是as实际上是<strong>可选的</strong>!尽管这里并没有提到这一点-这也是我讨厌这本教材的原因之一.</p></blockquote><h3 id="The-With-Clause">The With Clause</h3><blockquote><p>The <strong>with</strong> clause provides a way of deﬁning a temporary <strong>relation</strong> whose deﬁnition is available only to the query in which the <strong>with</strong> clause occurs.</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> max_budget (<span class="keyword">value</span>) <span class="keyword">as</span></span><br><span class="line">  (<span class="keyword">select</span> <span class="built_in">max</span>(budget)</span><br><span class="line">  <span class="keyword">from</span> department)</span><br><span class="line"><span class="keyword">select</span> budget</span><br><span class="line"><span class="keyword">from</span> department, max_budget</span><br><span class="line"><span class="keyword">where</span> department.budget <span class="operator">=</span> max_budget.value;</span><br></pre></td></tr></table></figure><p>注意到这里是先写别名再在as后写原关系,而我们之前都是先写原关系再写as的</p><blockquote><p>with得到的是关系而非属性,因此我们每次都需要用关系(属性)的方式来使用with</p></blockquote><ul><li>实际上,上述的with语句完全可以放到where中</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> budget</span><br><span class="line"><span class="keyword">from</span> department</span><br><span class="line"><span class="keyword">where</span> budget <span class="operator">=</span> (<span class="keyword">select</span> <span class="built_in">max</span>(budget) <span class="keyword">from</span> department);</span><br></pre></td></tr></table></figure><blockquote><p>We could have written the preceding query by using a nested subquery in either the from clause or the where clause.<br>However, using nested subqueries would have made the query harder to read and understand. The with clause makes the query logic clearer;<br>it also permits this temporary relation to be used in multiple places within a query.</p></blockquote><h3 id="Scalar-标量-Subqueries">Scalar(标量) Subqueries</h3><blockquote><p>SQL allows subqueries to occur wherever an expression returning a value is permitted,provided the subquery returns only one tuple containing a single attribute; such sub-queries are called scalar subqueries.</p></blockquote><ul><li>也就是说只返回一行一列<br><strong>长难句分析</strong></li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> dept_name,</span><br><span class="line">        (<span class="keyword">select</span> <span class="built_in">count</span>(<span class="operator">*</span>)</span><br><span class="line">          <span class="keyword">from</span> instructor</span><br><span class="line">          <span class="keyword">where</span> department.dept_name <span class="operator">=</span> instructor.dept_name)</span><br><span class="line">        <span class="keyword">as</span> num_instructors</span><br><span class="line"><span class="keyword">from</span> department;</span><br></pre></td></tr></table></figure><ul><li><code>select count(*)</code>在这里是无论是否有空值都返回满足<code>epartment.dept_name = instructor.dept_name</code>的所有行的意思</li></ul><h3 id="Scalar-Without-a-From-Clause">Scalar Without a From Clause</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">select</span> <span class="built_in">count</span> (<span class="operator">*</span>) <span class="keyword">from</span> teaches) <span class="operator">/</span> (<span class="keyword">select</span> <span class="built_in">count</span> (<span class="operator">*</span>) <span class="keyword">from</span> instructor);</span><br><span class="line"><span class="comment">-- 在某些数据库中会由于没有from语句而报错</span></span><br><span class="line"><span class="keyword">select</span> (<span class="keyword">select</span> <span class="built_in">count</span> (<span class="operator">*</span>) <span class="keyword">from</span> teaches) <span class="operator">/</span> (<span class="keyword">select</span> <span class="built_in">count</span> (<span class="operator">*</span>) <span class="keyword">from</span> instructor) <span class="keyword">from</span> dual;</span><br><span class="line"><span class="comment">-- 提供了一个dummy relation来提供from语句且不产生其他副作用</span></span><br></pre></td></tr></table></figure><h2 id="Modification-of-the-Database-3-19">Modification of the Database(3/19)</h2><h3 id="Deletion">Deletion</h3><blockquote><p>A delete request is expressed in much the same way as a query. We can delete only whole tuples; we cannot delete values on only particular attributes. SQL expresses a deletion by:</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">delete</span> <span class="keyword">from</span> r</span><br><span class="line"><span class="keyword">where</span> P;</span><br><span class="line"><span class="comment">-- where P represents a predicate and r represents a relation.</span></span><br><span class="line"><span class="comment">-- The where clause can be omitted, in which case all tuples in r are deleted.</span></span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">delete</span> <span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="keyword">between</span> <span class="number">13000</span> <span class="keyword">and</span> <span class="number">15000</span>;</span><br><span class="line"><span class="comment">--  Delete all instructors with a salary between $13,000 and $15,000.</span></span><br></pre></td></tr></table></figure><ul><li>事实上delete与select的用法没有什么区别,只不过一个是选择关系,一个是删除关系而已.</li></ul><h3 id="Insertion">Insertion</h3><blockquote><p>The attribute values for inserted tuples must be members of the corresponding attribute’s domain. Similarly, tuples inserted must have the correct number of attributes.</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">insert into</span> course</span><br><span class="line"><span class="keyword">values</span> (<span class="string">&#x27;CS-437&#x27;</span>, <span class="string">&#x27;Database Systems&#x27;</span>, <span class="string">&#x27;Comp. Sci.&#x27;</span>, <span class="number">4</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 如果忘记了表的顺序,那么可以具体写出属性名来插入,这三种写法的结果完全相同</span></span><br><span class="line"><span class="keyword">insert into</span> course (course id, title, dept name, credits)</span><br><span class="line"><span class="keyword">values</span> (<span class="string">&#x27;CS-437&#x27;</span>, <span class="string">&#x27;Database Systems&#x27;</span>, <span class="string">&#x27;Comp. Sci.&#x27;</span>, <span class="number">4</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">insert into</span> course (title, course id, credits, dept name)</span><br><span class="line"><span class="keyword">values</span> (<span class="string">&#x27;Database Systems&#x27;</span>, <span class="string">&#x27;CS-437&#x27;</span>, <span class="number">4</span>, <span class="string">&#x27;Comp. Sci.&#x27;</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">insert into</span> instructor</span><br><span class="line">      <span class="keyword">select</span> ID, name, dept name, <span class="number">18000</span></span><br><span class="line">      <span class="keyword">from</span> student</span><br><span class="line">      <span class="keyword">where</span> dept name <span class="operator">=</span> <span class="string">&#x27;Music&#x27;</span> <span class="keyword">and</span> tot cred <span class="operator">&gt;</span> <span class="number">144</span>;</span><br></pre></td></tr></table></figure><p>当然,我们可以从其他关系中获取tuple后再插入,而不是只用value来具体写.</p><h3 id="Updates">Updates</h3><blockquote><p>n certain situations, we may wish to change a value in a tuple without changing all values in the tuple. For this purpose, the <strong>update</strong> statement can be used.</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span> instructor</span><br><span class="line"><span class="keyword">set</span> salary<span class="operator">=</span> salary <span class="operator">*</span> <span class="number">1.05</span>;</span><br><span class="line"><span class="comment">-- 我们可以对某一列属性整体生效</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">update</span> instructor</span><br><span class="line"><span class="keyword">set</span> salary <span class="operator">=</span> salary <span class="operator">*</span> <span class="number">1.05</span></span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&lt;</span> <span class="number">70000</span>;</span><br><span class="line"><span class="comment">-- 也可以对该列属性的部分值生效</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">update</span> instructor</span><br><span class="line"><span class="keyword">set</span> salary <span class="operator">=</span> salary <span class="operator">*</span> <span class="number">1.05</span></span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&lt;</span> (<span class="keyword">select</span> <span class="built_in">avg</span> (salary)</span><br><span class="line"><span class="keyword">from</span> instructor);</span><br><span class="line"><span class="comment">-- 还可以对满足子查询的属性值生效</span></span><br></pre></td></tr></table></figure><p><strong>进阶写法</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span> instructor</span><br><span class="line"><span class="keyword">set</span> salary <span class="operator">=</span> <span class="keyword">case</span></span><br><span class="line">              <span class="keyword">when</span> salary <span class="operator">&lt;=</span> <span class="number">100000</span> <span class="keyword">then</span> salary <span class="operator">*</span> <span class="number">1.05</span></span><br><span class="line">              <span class="keyword">else</span> salary <span class="operator">*</span> <span class="number">1.03</span></span><br><span class="line">            <span class="keyword">end</span></span><br><span class="line"><span class="comment">-- 具体模板如下</span></span><br><span class="line"><span class="comment">-- case</span></span><br><span class="line">  <span class="comment">-- when pred 1 then result1</span></span><br><span class="line">  <span class="comment">-- when pred 2 then result2</span></span><br><span class="line">  <span class="comment">-- …</span></span><br><span class="line">  <span class="comment">-- when pred n then resultn</span></span><br><span class="line">  <span class="comment">-- else result0</span></span><br><span class="line"><span class="comment">-- end</span></span><br></pre></td></tr></table></figure><h1>Intermediate SQL(3/15)</h1><h2 id="Join-Expressions">Join Expressions</h2><p>事实上,单独的join语句不加任何前缀和后缀会报错,因为不存在只写一个join而不进行过滤操作的语法.</p><h3 id="The-Natural-Join">The Natural Join</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name, course_id</span><br><span class="line"><span class="keyword">from</span> student, takes</span><br><span class="line"><span class="keyword">where</span> student.ID <span class="operator">=</span> takes.ID;</span><br></pre></td></tr></table></figure><p>上述语句在执行的中间结果中会保留两个ID列,只是在select时抛弃掉了而已.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name, course id</span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">natural</span> <span class="keyword">join</span> takes;</span><br></pre></td></tr></table></figure><p>而<code>natural join</code>的中间结果只保留一个ID列<br>规则:</p><ol><li>找到同名列</li><li>过滤掉同名列中的值不相等的行</li><li>两个同名列只保留一列</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name, title</span><br><span class="line"><span class="keyword">from</span> (student <span class="keyword">natural</span> <span class="keyword">join</span> takes) <span class="keyword">join</span> course <span class="keyword">using</span> (course_id);</span><br></pre></td></tr></table></figure><p>The operation <strong>join … using</strong> requires a list of attribute names to be speciﬁed. Both relations being joined must have attributes with the speciﬁed names.</p><ul><li>也就是说只用using()里面那个属性在join的时候来判断,而不像natural join一样在有多个同名属性的情况下全都要求相等.</li></ul><h3 id="Join-Conditions">Join Conditions</h3><blockquote><p>The <strong>on</strong> condition allows a general predicate over the relations being joined.</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">join</span> takes <span class="keyword">on</span> student.ID <span class="operator">=</span> takes.ID;</span><br></pre></td></tr></table></figure><p>完全等价于下面语句,也就是结果都保持了两个ID列</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student, takes</span><br><span class="line"><span class="keyword">where</span> student.ID <span class="operator">=</span> takes.ID;</span><br></pre></td></tr></table></figure><ul><li>所以基本用不上on</li></ul><h3 id="Outer-Joins">Outer Joins</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">natural</span> <span class="keyword">join</span> takes;</span><br></pre></td></tr></table></figure><p>由于natural join会丢弃<code>takes.id!=student.id</code>的行,对于只在一个表中存在的ID,由于在另一个表中找不到对等的值,也会被抛弃,但在实际需求中,即使没选课的学生,我们也希望展示出来,故这里需要使用<strong>outer join</strong>.</p><p>There are three forms of outer join:</p><ul><li>The left outer join preserves tuples only in the relation named before the left outer join operation.</li><li>The right outer join preserves tuples only in the relation named after the right outer join operation.</li><li>The full outer join preserves tuples in both relations.</li></ul><p>In contrast, the join operations we studied earlier that do not preserve nonmatched tuples are called <strong>inner-join</strong> operations, to distinguish them from the <strong>outer-join</strong> operations.</p><blockquote><p>We now explain exactly how each form of outer join operates. We can compute<br>the left outer-join operation as follows: First, compute the result of the inner join as<br>before. Then, for every tuple t in the left-hand-side relation that does not match any<br>tuple in the right-hand-side relation in the inner join, add a tuple r to the result of the<br>join constructed as follows:</p></blockquote><ul><li>The attributes of tuple r that are derived from the left-hand-side relation are ﬁlled in with the values from tuple t.</li><li>The remaining attributes of r are ﬁlled with null values.</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">natural</span> <span class="keyword">left</span> <span class="keyword">outer</span> <span class="keyword">join</span> takes;</span><br></pre></td></tr></table></figure><p>如果对于在student里出现但在takes里没有的id,会将student表中的值填入,对应takes部分的值均为null.</p><blockquote><p>The <strong>full outer join</strong> is a combination of the left and right outer-join types. After the<br>operation computes the result of the inner join, it extends with nulls those tuples from<br>the left-hand-side relation that did not match with any from the right-hand-side relation<br>and adds them to the result. Similarly, it extends with nulls those tuples from the right-<br>hand-side relation that did not match with any tuples from the left-hand-side relation<br>and adds them to the result. Said diﬀerently, full outer join is the union of a left outer<br>join and the corresponding right outer join.</p></blockquote><ul><li>也就是说保留两边的缺值</li></ul><p><strong>进阶例子</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> (<span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student</span><br><span class="line"><span class="keyword">where</span> dept name <span class="operator">=</span> <span class="string">&#x27;Comp. Sci.&#x27;</span>)</span><br><span class="line"><span class="keyword">natural</span> <span class="keyword">full</span> <span class="keyword">outer</span> <span class="keyword">join</span></span><br><span class="line">(<span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> takes</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span> <span class="operator">=</span> <span class="number">2017</span>);</span><br></pre></td></tr></table></figure><blockquote><p>一开始明明都是用<code>student natural full outer join takes</code>的,这里为什么要单独把两个结果提出来呢?</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student</span><br><span class="line"><span class="keyword">natural</span> <span class="keyword">full</span> <span class="keyword">outer</span> <span class="keyword">join</span> takes</span><br><span class="line"><span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Comp. Sci.&#x27;</span></span><br><span class="line">  <span class="keyword">and</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span></span><br><span class="line">  <span class="keyword">and</span> <span class="keyword">year</span> <span class="operator">=</span> <span class="number">2017</span>;</span><br></pre></td></tr></table></figure><p>因为如果这样写的话,尽管在from部分保留了null,但where部分又把null全部过滤掉了</p><blockquote><p>The <strong>on</strong> condition is part of the outer join speciﬁcation, but a <strong>where</strong> clause is not.</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">left</span> <span class="keyword">outer</span> <span class="keyword">join</span> takes <span class="keyword">on</span> student.ID <span class="operator">=</span> takes.ID;</span><br></pre></td></tr></table></figure><p>也就是说上面这个语句不等价于下面的,因为on是在join阶段生效,即使不匹配也会置为null;<br>而where是在join之后生效,因此where会过滤掉null行,而上面语句中的on则会保留</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">left</span> <span class="keyword">outer</span> <span class="keyword">join</span> takes <span class="keyword">on</span> <span class="literal">true</span></span><br><span class="line"><span class="keyword">where</span> student.ID <span class="operator">=</span> takes.ID;</span><br></pre></td></tr></table></figure><h3 id="Join-Types-and-Conditions">Join Types and Conditions</h3><p>The keyword <strong>inner</strong> is optional:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">join</span> takes <span class="keyword">using</span> (ID);</span><br></pre></td></tr></table></figure><p>is equivalent to:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">inner</span> <span class="keyword">join</span> takes <span class="keyword">using</span> (ID);</span><br></pre></td></tr></table></figure><p>Similarly, natural join is equivalent to natural inner join.</p><h2 id="Views">Views</h2><blockquote><p>SQL allows a “virtual relation” to be deﬁned by a query, and the relation conceptually contains the result of the query. The virtual relation is not precomputed and stored but instead is computed by executing the query whenever the virtual relation is used.</p></blockquote><p>因此就有了view这个概念用来代表尚未执行的sql语句</p><h3 id="View-Definition">View Definition</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">view</span> v <span class="keyword">as</span> <span class="operator">&lt;</span>query expression<span class="operator">&gt;</span>;</span><br></pre></td></tr></table></figure><p>where &lt; query expression &gt; is any legal query expression. The view name is represented v</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">view</span> faculty <span class="keyword">as</span></span><br><span class="line"><span class="keyword">select</span> ID, name, dept name</span><br><span class="line"><span class="keyword">from</span> instructor;</span><br></pre></td></tr></table></figure><p>通过这样写,可以给低权限用户传递view这个关系而不直接接触instructor这个关系.</p><h3 id="Using-Views-in-SQL-Queries">Using Views in SQL Queries</h3><p>The attribute names of a view can be speciﬁed explicitly as follows:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">view</span> departments total salary(dept name, total salary) <span class="keyword">as</span></span><br><span class="line"><span class="keyword">select</span> dept name, <span class="built_in">sum</span> (salary)</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> dept name;</span><br></pre></td></tr></table></figure><p>Since the expression sum(salary) does not have a name, the attribute name is speciﬁed explicitly in the view deﬁnition.</p><h3 id="Update-of-a-View">Update of a View</h3><p>Not recommended!</p><h2 id="Transactions">Transactions</h2><p>blablabla…</p><h2 id="Integrity-Constraints">Integrity Constraints</h2><h3 id="Constraints-on-a-Single-Relation">Constraints on a Single Relation</h3><ul><li>not null</li><li>unique</li><li>check(<predicate>)</li></ul><h3 id="Not-Null-Constraint">Not Null Constraint</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">name <span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">not null</span></span><br><span class="line">budget <span class="type">numeric</span>(<span class="number">12</span>,<span class="number">2</span>) <span class="keyword">not null</span></span><br></pre></td></tr></table></figure><p>The not null constraint prohibits the insertion of a null value for the attribute, and is an example of a domain constraint.</p><h3 id="Unique-Constraint">Unique Constraint</h3><p>The unique speciﬁcation says that no two tuples in the relation can be equal on all the listed attributes.</p><h3 id="The-Check-Clause">The Check Clause</h3><blockquote><p>When applied to a relation declaration, the clause check(P) speciﬁes a predicate P that must be satisﬁed by every tuple in a relation.</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create table</span> section</span><br><span class="line">(course id</span><br><span class="line"> <span class="type">varchar</span> (<span class="number">8</span>),</span><br><span class="line">sec id</span><br><span class="line"> <span class="type">varchar</span> (<span class="number">8</span>),</span><br><span class="line">semester</span><br><span class="line"> <span class="type">varchar</span> (<span class="number">6</span>),</span><br><span class="line"><span class="keyword">year</span></span><br><span class="line"> <span class="type">numeric</span> (<span class="number">4</span>,<span class="number">0</span>),</span><br><span class="line">building</span><br><span class="line"> <span class="type">varchar</span> (<span class="number">15</span>),</span><br><span class="line">room number <span class="type">varchar</span> (<span class="number">7</span>),</span><br><span class="line"><span class="type">time</span> slot id <span class="type">varchar</span> (<span class="number">4</span>),</span><br><span class="line"><span class="keyword">primary key</span> (course id, sec id, semester, <span class="keyword">year</span>),</span><br><span class="line"><span class="keyword">check</span> (semester <span class="keyword">in</span> (<span class="string">&#x27;Fall&#x27;</span>, <span class="string">&#x27;Winter&#x27;</span>, <span class="string">&#x27;Spring&#x27;</span>, <span class="string">&#x27;Summer&#x27;</span>)));</span><br></pre></td></tr></table></figure><p>Here, we use the <strong>check</strong> clause to simulate an enumerated type by specifying that semester must be one of ‘Fall’, ‘Winter’, ‘Spring’, or ‘Summer’.</p><p>还可以把check写在定义的属性之后</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> classroom (</span><br><span class="line">    building    <span class="type">VARCHAR</span>(<span class="number">15</span>),</span><br><span class="line">    room_number <span class="type">VARCHAR</span>(<span class="number">7</span>),</span><br><span class="line">    capacity    <span class="type">NUMERIC</span>(<span class="number">4</span>,<span class="number">0</span>),</span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (building, room_number)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">CREATE TABLE</span> department (</span><br><span class="line">    dept_name   <span class="type">VARCHAR</span>(<span class="number">20</span>),</span><br><span class="line">    building    <span class="type">VARCHAR</span>(<span class="number">15</span>),</span><br><span class="line">    budget      <span class="type">NUMERIC</span>(<span class="number">12</span>,<span class="number">2</span>) <span class="keyword">CHECK</span> (budget <span class="operator">&gt;</span> <span class="number">0</span>),</span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (dept_name)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">CREATE TABLE</span> course (</span><br><span class="line">    course_id   <span class="type">VARCHAR</span>(<span class="number">8</span>),</span><br><span class="line">    title       <span class="type">VARCHAR</span>(<span class="number">50</span>),</span><br><span class="line">    dept_name   <span class="type">VARCHAR</span>(<span class="number">20</span>),</span><br><span class="line">    credits     <span class="type">NUMERIC</span>(<span class="number">2</span>,<span class="number">0</span>) <span class="keyword">CHECK</span> (credits <span class="operator">&gt;</span> <span class="number">0</span>),</span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (course_id),</span><br><span class="line">    <span class="keyword">FOREIGN KEY</span> (dept_name) <span class="keyword">REFERENCES</span> department </span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">CREATE TABLE</span> instructor (</span><br><span class="line">    ID          <span class="type">VARCHAR</span>(<span class="number">5</span>),</span><br><span class="line">    name        <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    dept_name   <span class="type">VARCHAR</span>(<span class="number">20</span>),</span><br><span class="line">    salary      <span class="type">NUMERIC</span>(<span class="number">8</span>,<span class="number">2</span>) <span class="keyword">CHECK</span> (salary <span class="operator">&gt;</span> <span class="number">29000</span>),</span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (ID),</span><br><span class="line">    <span class="keyword">FOREIGN KEY</span> (dept_name) <span class="keyword">REFERENCES</span> department </span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">CREATE TABLE</span> section (</span><br><span class="line">    course_id    <span class="type">VARCHAR</span>(<span class="number">8</span>),</span><br><span class="line">    sec_id       <span class="type">VARCHAR</span>(<span class="number">8</span>),</span><br><span class="line">    semester     <span class="type">VARCHAR</span>(<span class="number">6</span>) <span class="keyword">CHECK</span> (semester <span class="keyword">IN</span> (<span class="string">&#x27;Fall&#x27;</span>, <span class="string">&#x27;Winter&#x27;</span>, <span class="string">&#x27;Spring&#x27;</span>, <span class="string">&#x27;Summer&#x27;</span>)),</span><br><span class="line">    <span class="keyword">year</span>         <span class="type">NUMERIC</span>(<span class="number">4</span>,<span class="number">0</span>) <span class="keyword">CHECK</span> (<span class="keyword">year</span> <span class="operator">&gt;</span> <span class="number">1759</span> <span class="keyword">AND</span> <span class="keyword">year</span> <span class="operator">&lt;</span> <span class="number">2100</span>),</span><br><span class="line">    building     <span class="type">VARCHAR</span>(<span class="number">15</span>),</span><br><span class="line">    room_number  <span class="type">VARCHAR</span>(<span class="number">7</span>),</span><br><span class="line">    time_slot_id <span class="type">VARCHAR</span>(<span class="number">4</span>),</span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (course_id, sec_id, semester, <span class="keyword">year</span>),</span><br><span class="line">    <span class="keyword">FOREIGN KEY</span> (course_id) <span class="keyword">REFERENCES</span> course ,</span><br><span class="line">    <span class="keyword">FOREIGN KEY</span> (building, room_number) <span class="keyword">REFERENCES</span> classroom </span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="Referential-Integrity">Referential Integrity</h3><p><strong>默认引用 (Implicit Reference)</strong><br><code>foreign key (dept_name) references department</code></p><ul><li><strong>底层行为</strong>：当你省略括号中的属性名时，SQL 标准规定该外键必须引用被参照表的 <strong>主键 (Primary Key)</strong></li></ul><p><strong>显式引用 (Explicit Reference)</strong><br><code>foreign key (dept_name) references department(dept_name)</code></p><ul><li><strong>底层行为</strong>：目标列（dept_name）不必是主键，但必须具有 唯一性约束 (UNIQUE) 或本身就是 主键。即它必须是一个 超键 (Superkey)。</li></ul><h3 id="Assigning-Names-to-Constraints">Assigning Names to Constraints</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 使用constraint关键字命名该限制</span></span><br><span class="line">salary <span class="type">numeric</span>(<span class="number">8</span>,<span class="number">2</span>), <span class="keyword">constraint</span> minsalary <span class="keyword">check</span> (salary <span class="operator">&gt;</span> <span class="number">29000</span>),</span><br><span class="line"><span class="comment">-- 删除该限制</span></span><br><span class="line"><span class="keyword">alter table</span> instructor <span class="keyword">drop</span> <span class="keyword">constraint</span> minsalary;</span><br></pre></td></tr></table></figure><h3 id="Complex-Check-Conditions-and-Assertions">Complex Check Conditions and Assertions</h3><blockquote><p>An <strong>assertion</strong> is a predicate expressing a condition that we wish the database always<br>to satisfy.</p></blockquote><p><strong>格式</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> assertion <span class="operator">&lt;</span>assertion<span class="operator">-</span>name<span class="operator">&gt;</span> <span class="keyword">check</span> <span class="operator">&lt;</span>predicate<span class="operator">&gt;</span>;</span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> assertion credits earned <span class="keyword">constraint</span> <span class="keyword">check</span></span><br><span class="line">(<span class="keyword">not</span> <span class="keyword">exists</span> (<span class="keyword">select</span> ID</span><br><span class="line">            <span class="keyword">from</span> student</span><br><span class="line">            <span class="keyword">where</span> tot_cred <span class="operator">&lt;&gt;</span> (<span class="keyword">select</span> <span class="built_in">coalesce</span>(<span class="built_in">sum</span>(credits), <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">from</span> takes <span class="keyword">natural</span> <span class="keyword">join</span> course</span><br><span class="line">            <span class="keyword">where</span> student.ID<span class="operator">=</span> takes.ID</span><br><span class="line">              <span class="keyword">and</span> grade <span class="keyword">is</span> <span class="keyword">not null</span> <span class="keyword">and</span> grade<span class="operator">&lt;&gt;</span> ’F’ )))</span><br></pre></td></tr></table></figure><ul><li>非常好的是这种东西不会考,因为主流数据库也不用</li></ul><h2 id="SQL-Data-Types-and-Schemas">SQL Data Types and Schemas</h2><blockquote><p>There are additional built-in data types supported by SQL, which we describe below.</p></blockquote><h3 id="Date-and-Time-Types-in-SQL">Date and Time Types in SQL</h3><ul><li>date: A calendar date containing a (four-digit) year, month, and day of the month.</li><li>time: The time of day, in hours, minutes, and seconds.</li><li>timestamp: A combination of date and time.</li></ul><p>Date and time values can be speciﬁed like this:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">date</span> <span class="string">&#x27;2018-04-25&#x27;</span></span><br><span class="line"><span class="comment">-- Dates must be speciﬁed in the format year followed by month followed by day</span></span><br><span class="line"><span class="type">time</span> <span class="string">&#x27;09:30:00&#x27;</span></span><br><span class="line"><span class="type">timestamp</span> <span class="string">&#x27;2018-04-25 10:29:01.45&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="Type-Conversion-and-Formatting-Functions-过">Type Conversion and Formatting Functions(过)</h3><ul><li>由于我找到了中文版教材,故从现在开始大部分使用中文版,少部分使用英文版(因为这本书真的是又臭又长)</li></ul><p><strong>1. 显式类型转换 (Type Casting)</strong><br><strong>语法：</strong> <code>cast(expression as data_type)</code><br><strong>用途：</strong> 物理改变数据的解析方式，常用于修正字符串排序逻辑。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 修正前：&#x27;11&#x27; 会排在 &#x27;2&#x27; 前面（字符串字典序） */</span></span><br><span class="line"><span class="keyword">select</span> ID <span class="keyword">from</span> instructor <span class="keyword">order</span> <span class="keyword">by</span> ID;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 修正后：将字符串物理转为数字，实现数值大小排序 */</span></span><br><span class="line"><span class="keyword">select</span> ID </span><br><span class="line"><span class="keyword">from</span> instructor </span><br><span class="line"><span class="keyword">order</span> <span class="keyword">by</span> <span class="built_in">cast</span>(ID <span class="keyword">as</span> <span class="type">numeric</span>);</span><br></pre></td></tr></table></figure><hr><p><strong>2. 格式化函数 (Formatting Functions)</strong><br><strong>用途：</strong> 不改变物理类型，只给数据套上“显示滤镜”。各数据库系统实现不同。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- MySQL: 数字千分位格式化</span></span><br><span class="line"><span class="keyword">select</span> format(salary, <span class="number">2</span>) <span class="keyword">from</span> instructor; </span><br><span class="line"></span><br><span class="line"><span class="comment">-- PostgreSQL/Oracle: 日期转特定格式字符串</span></span><br><span class="line"><span class="keyword">select</span> to_char(now(), <span class="string">&#x27;YYYY-MM-DD&#x27;</span>); </span><br><span class="line"></span><br><span class="line"><span class="comment">-- SQL Server: 风格化转换</span></span><br><span class="line"><span class="keyword">select</span> <span class="keyword">convert</span>(<span class="type">varchar</span>, getdate(), <span class="number">101</span>); <span class="comment">-- 返回 mm/dd/yyyy</span></span><br></pre></td></tr></table></figure><hr><p><strong>3. 空值合并 (Coalesce)</strong><br><strong>语法：</strong> <code>coalesce(arg1, arg2, ...)</code><br><strong>逻辑：</strong> 物理扫描参数列表，返回第一个非 <code>null</code> 的值。要求所有参数<strong>物理类型一致</strong>。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 如果 salary 是 null，物理替换为 0 以便后续数学运算 */</span></span><br><span class="line"><span class="keyword">select</span> ID, <span class="built_in">coalesce</span>(salary, <span class="number">0</span>) <span class="keyword">as</span> actual_salary</span><br><span class="line"><span class="keyword">from</span> instructor;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 错误示例：coalesce(salary, &#x27;N/A&#x27;) 会报错，因为数字和字符串类型不匹配 */</span></span><br></pre></td></tr></table></figure><hr><p><strong>4. 条件解码 (Decode - Oracle 特有)</strong><br><strong>语法：</strong> <code>decode(value, search, result, default)</code><br><strong>逻辑：</strong> 类似于 <code>switch-case</code>。它允许 <code>null = null</code> 的物理匹配，且<strong>不强制要求类型一致</strong>。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 将物理空值直接翻译成业务描述字符串 &#x27;N/A&#x27; */</span></span><br><span class="line"><span class="keyword">select</span> ID, decode(salary, <span class="keyword">null</span>, <span class="string">&#x27;N/A&#x27;</span>, salary) <span class="keyword">as</span> salary_text</span><br><span class="line"><span class="keyword">from</span> instructor;</span><br></pre></td></tr></table></figure><ul><li>随便一看就知道不会考的…</li></ul><h3 id="Default-Values">Default Values</h3><p>SQL allows a default value to be speciﬁed for an attribute as illustrated by the following<br>create table statement:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create table</span> student</span><br><span class="line">    (ID <span class="type">varchar</span> (<span class="number">5</span>),</span><br><span class="line">    name <span class="type">varchar</span> (<span class="number">20</span>) <span class="keyword">not null</span>,</span><br><span class="line">    dept_name <span class="type">varchar</span> (<span class="number">20</span>),</span><br><span class="line">    tot_cred <span class="type">numeric</span> (<span class="number">3</span>,<span class="number">0</span>) <span class="keyword">default</span> <span class="number">0</span>,</span><br><span class="line">    <span class="keyword">primary key</span> (ID));</span><br></pre></td></tr></table></figure><h3 id="Large-Object-Types">Large-Object Types</h3><blockquote><p>SQL provides large-object data types for character data (clob) and binary data (blob).<br>The letters “lob” in these data types stand for “Large OBject.”</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">book_review <span class="type">clob</span>(<span class="number">10</span>KB)</span><br><span class="line">image <span class="type">blob</span>(<span class="number">10</span>MB)</span><br><span class="line">movie <span class="type">blob</span>(<span class="number">2</span>GB)</span><br></pre></td></tr></table></figure><h3 id="User-Defined-Types-过">User-Defined Types(过)</h3><blockquote><p>SQL supports two forms of user-deﬁned data types.<br>The ﬁrst form, which we cover here, is called <strong>distinct types</strong>.</p></blockquote><p>The create type clause can be used to deﬁne new types:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> type Dollars <span class="keyword">as</span> <span class="type">numeric</span>(<span class="number">12</span>,<span class="number">2</span>) ﬁnal;</span><br><span class="line"><span class="keyword">create</span> type Pounds <span class="keyword">as</span> <span class="type">numeric</span>(<span class="number">12</span>,<span class="number">2</span>) ﬁnal;</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> department</span><br><span class="line">(dept name</span><br><span class="line"> <span class="type">varchar</span> (<span class="number">20</span>),</span><br><span class="line">building</span><br><span class="line"> <span class="type">varchar</span> (<span class="number">15</span>),</span><br><span class="line">budget</span><br><span class="line"> Dollars);</span><br></pre></td></tr></table></figure><ul><li>这一节也不重要,因为主流数据库也不支持</li></ul><h3 id="Generating-Unique-Key-Values">Generating Unique Key Values</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">ID number(<span class="number">5</span>) generated always <span class="keyword">as</span> <span class="keyword">identity</span></span><br><span class="line"><span class="comment">-- When the always option is used, any insert statement must avoid specifying a value</span></span><br><span class="line"><span class="comment">-- for the automatically generated key. </span></span><br><span class="line"></span><br><span class="line"><span class="keyword">insert into</span> instructor (name, dept_name, salary)</span><br><span class="line"><span class="keyword">values</span> (<span class="string">&#x27;Newprof&#x27;</span>, <span class="string">&#x27;Comp. Sci.&#x27;</span>, <span class="number">100000</span>);</span><br></pre></td></tr></table></figure><h3 id="Create-Table-Extensions">Create Table Extensions</h3><blockquote><p>Applications often require the creation of tables that have the same schema as an existing table.</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- SQL provides a create table like extension to support this task</span></span><br><span class="line"><span class="keyword">create table</span> temp_instructor <span class="keyword">like</span> instructor;</span><br></pre></td></tr></table></figure><h2 id="Schemas-Catalogs-and-Environments-过">Schemas, Catalogs, and Environments(过)</h2><h2 id="Index-Definition-in-SQL-过">Index Definition in SQL(过)</h2><p>由于这部分基本什么都没讲,还是得到index章节去看</p><h2 id="Authorization">Authorization</h2><blockquote><p>We may assign a user several forms of authorizations on parts of the database. Authorizations on data include:</p></blockquote><p>• Authorization to <strong>read</strong> data.<br>• Authorization to <strong>insert</strong> new data.<br>• Authorization to <strong>update</strong> data.<br>• Authorization to <strong>delete</strong> data.</p><ul><li>Each of these types of authorizations is called a privilege.</li></ul><h3 id="Granting-and-Revoking-of-Privileges">Granting and Revoking of Privileges</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- grant的使用格式</span></span><br><span class="line"><span class="keyword">grant</span> <span class="operator">&lt;</span>privilege list<span class="operator">&gt;</span></span><br><span class="line"><span class="keyword">on</span> <span class="operator">&lt;</span>relation name <span class="keyword">or</span> <span class="keyword">view</span> name<span class="operator">&gt;</span></span><br><span class="line"><span class="keyword">to</span> <span class="operator">&lt;</span><span class="keyword">user</span><span class="operator">/</span>role list<span class="operator">&gt;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- grants database users Amit and Satoshi select authorization on the department relation:</span></span><br><span class="line"><span class="keyword">grant</span> <span class="keyword">select</span> <span class="keyword">on</span> department <span class="keyword">to</span> Amit, Satoshi;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- If the list of attributes is omitted, the update privilege will be granted on all attributes of the relation.</span></span><br><span class="line"><span class="keyword">grant</span> <span class="keyword">update</span> (budget) <span class="keyword">on</span> department <span class="keyword">to</span> Amit, Satoshi;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- To revoke an authorization, we use the revoke statement. It takes a form almost identical to that of grant:</span></span><br><span class="line"><span class="keyword">revoke</span> <span class="operator">&lt;</span>privilege list<span class="operator">&gt;</span></span><br><span class="line"><span class="keyword">on</span> <span class="operator">&lt;</span>relation name <span class="keyword">or</span> <span class="keyword">view</span> name<span class="operator">&gt;</span></span><br><span class="line"><span class="keyword">from</span> <span class="operator">&lt;</span><span class="keyword">user</span><span class="operator">/</span>role list<span class="operator">&gt;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">revoke</span> <span class="keyword">select</span> <span class="keyword">on</span> department <span class="keyword">from</span> Amit, Satoshi;</span><br><span class="line"><span class="keyword">revoke</span> <span class="keyword">update</span> (budget) <span class="keyword">on</span> department <span class="keyword">from</span> Amit, Satoshi;</span><br></pre></td></tr></table></figure><h3 id="Roles">Roles</h3><ul><li>我们使用Roles来解决需要多次指定相同权限的问题</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> role instructor;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Roles can then be granted privileges just as the users can,</span></span><br><span class="line"><span class="keyword">grant</span> <span class="keyword">select</span> <span class="keyword">on</span> takes</span><br><span class="line"><span class="keyword">to</span> instructor;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Roles can be granted to users, as well as to other roles,</span></span><br><span class="line"><span class="keyword">create</span> role dean;</span><br><span class="line"><span class="keyword">grant</span> instructor <span class="keyword">to</span> dean;</span><br><span class="line"><span class="keyword">grant</span> dean <span class="keyword">to</span> Satoshi;</span><br></pre></td></tr></table></figure><h3 id="Authorization-on-Views">Authorization on Views</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 为了让某个工作人员能够看到地质系的所有教师但不能看到其他系的教师表,我们可以这样写:</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">view</span> geo_instructor <span class="keyword">as</span></span><br><span class="line">(<span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept name <span class="operator">=</span> <span class="string">&#x27;Geology&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 但是,下面的操作实际上还是间接的访问了instructor表</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> geo_instructor;</span><br></pre></td></tr></table></figure><h3 id="Authorizations-on-Schema">Authorizations on Schema</h3><blockquote><p>SQL includes a <strong>references</strong> privilege that permits a user to declare <strong>foreign keys</strong> when creating relations.</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">grant</span> <span class="keyword">references</span> (dept_name) <span class="keyword">on</span> department <span class="keyword">to</span> Mariano;</span><br></pre></td></tr></table></figure><p>为什么要限制引用外码呢?</p><blockquote><p>However, recall that foreign-key constraints<br><strong>restrict deletion and update</strong> operations on the referenced relation. Suppose Mariano<br>creates a foreign key in a relation r referencing the dept name attribute of the department<br>relation and then inserts a tuple into r pertaining to the Geology department. It is no<br>longer possible to delete the Geology department from the department relation without also modifying relation r.</p></blockquote><h3 id="Transfer-of-Privileges">Transfer of Privileges</h3><p>当我们授予某用户一个权限时,默认他是无法将获得的权限传递给其他用户的,如果希望他能够传递权限,我们可以这样写:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">grant</span> <span class="keyword">select</span> <span class="keyword">on</span> department <span class="keyword">to</span> Amit <span class="keyword">with</span> <span class="keyword">grant</span> option;</span><br></pre></td></tr></table></figure><ul><li>当然,关系/视图/角色(relation/view/role) 的创建者拥有该对象的所有权限并且可以给其他用户授权(不然就没人能授权了)</li></ul><h3 id="Revoking-of-Privileges-过">Revoking of Privileges(过)</h3><h1>Advanced SQL</h1><ul><li>事实上,这部分我觉得期末不考,我猜老师自己也不会😅<ul><li>(4/8): 第一次小测考了前三节,…</li></ul></li></ul><h2 id="Accessing-SQL-from-a-Programming-Language-过">Accessing SQL from a Programming Language(过)</h2><h3 id="JDBC">JDBC</h3><ul><li>你猜怎么着,我们的课程安排是在学完这门课后再学java…</li></ul><blockquote><p>The <strong>JDBC</strong> standard deﬁnes an application program interface (API) that Java programs can use to connect to database servers. (The word JDBC was originally an abbreviation for <strong>Java Database Connectivity</strong>, but the full form is no longer used.)</p></blockquote><h3 id="ODBC">ODBC</h3><blockquote><p>The <strong>Open Database Connectivity</strong> (ODBC) standard deﬁnes an API that applications can use to open a connection with a database, send queries and updates, and get back results.</p></blockquote><h2 id="Functions-and-Procedures-待补充">Functions and Procedures(待补充)</h2><h2 id="Triggers-待补充">Triggers(待补充)</h2><blockquote><p>A <strong>trigger</strong> is a statement that the system <strong>executes automatically</strong> as a side eﬀect of a modiﬁcation to the database.</p></blockquote><ul><li>也就是说,trigger是一个满足条件时自动执行的函数</li></ul><h1>第一次小测复习</h1><h2 id="ch1">ch1</h2><h3 id="使用文件处理系统而不是数据库的弊端">使用文件处理系统而不是数据库的弊端</h3><ul><li>Data redundancy and inconsistency: 可能有多个文件记录了相同的信息,又或者不同文件的记录不相同,没有同步更新</li><li>Diﬃculty in accessing data: 普通的编程语言不能做到用高效又便利的方式取回数据</li><li>Data isolation: 数据被存储在不同类型的不同文件中,很难统一管理</li><li>Integrity problems: 很难通过普通的编程语言去给存储的数据加上各种限制</li><li>Atomicity problems: 转账时,如果在付款方付钱后系统故障,那么收款方账户未必会收到钱,但付款方账户已经扣了钱.也就是说,很难实现这样一种效果:要么两边操作全都发生,要么都不发生</li><li>Concurrent-access anomalies: 并发访问数据时可能导致异常</li><li>Security problems: 很难设置不同管理员的权限</li></ul><h3 id="数据库结构的基础-数据模型">数据库结构的基础: 数据模型</h3><p>本书介绍了4种模型:</p><ol><li><strong>Relational Model</strong>: uses a collection of tables to represent <strong>both data and the relationships among those data</strong>. Each table has multiple columns, and each column has a unique name. Tables are also known as relations.</li><li>Entity-Relationship Model: The entity-relationship (E-R) data model uses a collection of basic objects, called <em>entities</em>, and relationships among these objects.</li><li>Semi-structured Data Model: individual data items of the same type may have diﬀerent sets of attributes.</li><li>Object-Based Data Model: 仅仅是第一种类型的扩展,适用于面向对象的编程语言</li></ol><h2 id="ch2-Introduction-to-the-Relational-Model">ch2: Introduction to the Relational Model</h2><h3 id="schema到底是什么">schema到底是什么</h3><ul><li>Relation Schema（关系模式）：对应编程中的“类型定义”或“类声明”。<ul><li>物理组成：由属性名（Attributes）及其对应的域（Domains/Data Types）组成。</li><li>示例分析：<code>department (dept_name, building, budget)</code> 物理规定了任何存入该表的记录必须且只能包含这三个维度。</li></ul></li><li>Database Schema（数据库模式）：对应软件架构中的“逻辑设计”。</li></ul><h2 id="ch3">ch3</h2><h3 id="数据类型">数据类型</h3><ul><li>char(n): 固定长度为n的字符串</li><li>varchar(n): 最大长度为n的可变长字符串</li><li>int: 整数</li><li>numeric(p,d): 有p位数字(加上一个符号位)和小数点右边的p位中的d位数字</li><li>float(n): 精度至少为n位数字的浮点数</li></ul><h3 id="操纵关系表">操纵关系表</h3><p><strong>创建表</strong>时可以对变量使用以下六个限制:</p><ol><li>primary key(d): d非空且唯一</li><li>foreign key(d) references s: d的值必须出现在s的对应主码上</li><li>foreign key(d) references s(t): d的值必须与t对应,要求t是唯一的,即具有unique约束</li><li>not null: 非空</li><li>unique: 唯一</li><li>check(d…): 要求新加入的d满足对应条件,否则插入时报错</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create table</span> department(</span><br><span class="line">  dept_name <span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">not null</span>,</span><br><span class="line">  building <span class="type">varchar</span>(<span class="number">15</span>) <span class="keyword">unique</span>,</span><br><span class="line">  budget <span class="type">numeric</span>(<span class="number">12</span>,<span class="number">2</span>),<span class="keyword">check</span>(buget<span class="operator">&gt;</span><span class="number">0</span>),</span><br><span class="line">  <span class="keyword">primary key</span>(dept_name)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>删除表或更改属性</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 删除表r</span></span><br><span class="line"><span class="keyword">drop</span> <span class="keyword">table</span> r;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 增加(删除)属性A,类型为D</span></span><br><span class="line"><span class="keyword">alter table</span> r <span class="keyword">add</span> A D;</span><br><span class="line"><span class="comment">-- alter table r drop A 很多数据库系统不支持</span></span><br></pre></td></tr></table></figure><p><strong>删除元组</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 删除表r中的所有元组,保留框架</span></span><br><span class="line"><span class="keyword">delete</span> <span class="keyword">from</span> r;</span><br><span class="line"><span class="comment">-- 删除满足条件P的元组</span></span><br><span class="line"><span class="keyword">delete</span> <span class="keyword">from</span> r</span><br><span class="line"><span class="keyword">where</span> P;</span><br></pre></td></tr></table></figure><p><strong>插入元组</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 需要按照属性名顺序来,否则可能会插入失败</span></span><br><span class="line"><span class="keyword">insert into</span> r</span><br><span class="line">  <span class="keyword">values</span>(a1,a2,a3,....)</span><br><span class="line"><span class="comment">-- 指定插入值的对应属性</span></span><br><span class="line"><span class="keyword">insert into</span> course (course id, title, dept name, credits)</span><br><span class="line"><span class="keyword">values</span> (<span class="string">&#x27;CS-437&#x27;</span>, <span class="string">&#x27;Database Systems&#x27;</span>, <span class="string">&#x27;Comp. Sci.&#x27;</span>, <span class="number">4</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入查询结果</span></span><br><span class="line"><span class="keyword">insert into</span> instructor</span><br><span class="line"><span class="keyword">select</span> ID, name, dept_name, <span class="number">18000</span></span><br><span class="line"><span class="keyword">from</span> student</span><br><span class="line"><span class="keyword">where</span> dept name <span class="operator">=</span> <span class="string">&#x27;Music&#x27;</span> <span class="keyword">and</span> tot_cred <span class="operator">&gt;</span> <span class="number">144</span>;</span><br></pre></td></tr></table></figure><p><strong>更新元组</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 更改满足条件的属性名</span></span><br><span class="line"><span class="keyword">update</span> instructor</span><br><span class="line"><span class="keyword">set</span> salary <span class="operator">=</span> salary <span class="operator">*</span> <span class="number">1.05</span></span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&lt;</span> <span class="number">70000</span>;</span><br></pre></td></tr></table></figure><h3 id="select…from…where">select…from…where</h3><h4 id="select基础部分"><strong>select基础部分</strong></h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- !!!select会选中null行,不自动过滤!!!</span></span><br><span class="line"><span class="comment">-- 默认启用all保留重名行</span></span><br><span class="line"><span class="keyword">select</span> (<span class="keyword">all</span>) dept_name</span><br><span class="line"><span class="keyword">from</span> instructor;</span><br><span class="line"><span class="comment">-- 去掉重名行</span></span><br><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> dept_name</span><br><span class="line"><span class="keyword">from</span> instructor;</span><br><span class="line"><span class="comment">-- 简单计算</span></span><br><span class="line"><span class="keyword">select</span> ID,salary<span class="operator">*</span><span class="number">1.1</span></span><br><span class="line"><span class="keyword">from</span> insructor;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 选中所有关系的所有属性</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="comment">-- 选中一个关系的所有属性</span></span><br><span class="line"><span class="keyword">select</span> instructor.<span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> instructor, teaches</span><br><span class="line"><span class="keyword">where</span> instructor.ID<span class="operator">=</span> teaches.ID;</span><br></pre></td></tr></table></figure><h4 id="select进阶部分"><strong>select进阶部分</strong></h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- max,min,avg,sum:均为op(attribute)格式</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">avg</span> (salary)</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Comp. Sci.&#x27;</span>;</span><br><span class="line"><span class="comment">-- 启用as</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">avg</span> (salary) <span class="keyword">as</span> avg_salary</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Comp. Sci.&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- count(attribute)和count(*)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- count(attribute): 过滤重名行,一个ID统计一行,但会忽略ID为null的行</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">count</span> (<span class="keyword">distinct</span> ID)</span><br><span class="line"><span class="keyword">from</span> teaches</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span> <span class="operator">=</span> <span class="number">2018</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- count(*): 统计所有行,包括存在空值的行</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">count</span> (<span class="operator">*</span>)</span><br><span class="line"><span class="keyword">from</span> course;</span><br></pre></td></tr></table></figure><h4 id="from基础部分"><strong>from基础部分</strong></h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 启用as</span></span><br><span class="line"><span class="keyword">select</span> T .name, S.course_id</span><br><span class="line"><span class="keyword">from</span> instructor <span class="keyword">as</span> T , teaches <span class="keyword">as</span> S</span><br><span class="line"><span class="keyword">where</span> T .ID<span class="operator">=</span> S.ID; </span><br></pre></td></tr></table></figure><h4 id="where基础部分"><strong>where基础部分</strong></h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- =判断符</span></span><br><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Comp. Sci.&#x27;</span></span><br><span class="line"><span class="comment">-- &gt;,&lt;,&gt;=,&lt;=判断符</span></span><br><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> T .name</span><br><span class="line"><span class="keyword">from</span> instructor <span class="keyword">as</span> T , instructor <span class="keyword">as</span> S</span><br><span class="line"><span class="keyword">where</span> T.salary <span class="operator">&gt;</span> S.salary</span><br><span class="line"><span class="comment">-- and,or联结词</span></span><br><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Comp. Sci.&#x27;</span> <span class="keyword">and</span> salary <span class="operator">&gt;</span> <span class="number">70000</span>;</span><br><span class="line"><span class="comment">-- (not) between...and...联结词</span></span><br><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="keyword">between</span> <span class="number">90000</span> <span class="keyword">and</span> <span class="number">100000</span>;</span><br><span class="line"><span class="comment">-- 等价于</span></span><br><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&lt;=</span> <span class="number">100000</span> <span class="keyword">and</span> salary <span class="operator">&gt;=</span> <span class="number">90000</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 字符串比较: like,not like</span></span><br><span class="line"><span class="comment">-- 注意sql中只有单引号,没有双引号</span></span><br><span class="line"><span class="comment">-- %: 匹配任意子串</span></span><br><span class="line"><span class="comment">-- _: 匹配任意一个字符</span></span><br><span class="line"><span class="comment">-- ___%: 匹配至少含有任意三个字符的字符串</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> dept_name</span><br><span class="line"><span class="keyword">from</span> department</span><br><span class="line"><span class="keyword">where</span> building <span class="keyword">like</span> <span class="string">&#x27;%Watson%&#x27;</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="where进阶"><strong>where进阶</strong></h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 构造器写法: (a,b)=(c,d)-&gt; a=c and b=d</span></span><br><span class="line"><span class="keyword">select</span> name, course_id</span><br><span class="line"><span class="keyword">from</span> instructor, teaches</span><br><span class="line"><span class="keyword">where</span> (instructor.ID, dept_name) <span class="operator">=</span> (teaches.ID, <span class="string">&#x27;Biology&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- unknown: 如果对于某一个元组来说,判断符(即使是=)的一边为null,那么结果就是unkown ,则该元组不能被加入到结果中</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- is (not) null,is (not) unknown</span></span><br><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="keyword">is</span> <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&gt;</span> <span class="number">10000</span> <span class="keyword">is</span> <span class="literal">unknown</span>;</span><br><span class="line"><span class="comment">-- 解释: 只有salary为null的行才可以出现在结果中</span></span><br></pre></td></tr></table></figure><h4 id="where中的子查询">where中的子查询</h4><p><strong>in,not in</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> course_id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span> <span class="keyword">and</span></span><br><span class="line">course_id <span class="keyword">in</span> (<span class="keyword">select</span> course_id</span><br><span class="line">              <span class="keyword">from</span> section</span><br><span class="line">              <span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br></pre></td></tr></table></figure><p><strong>some</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&gt;</span> <span class="keyword">some</span> (<span class="keyword">select</span> salary</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Biology&#x27;</span>);</span><br><span class="line"><span class="comment">-- 至少比生物学部门的一个老师工资要高</span></span><br></pre></td></tr></table></figure><blockquote><p>SQL also allows <strong>&lt; some</strong>, <strong>&lt;= some</strong>, <strong>&gt;= some</strong>, <strong>= some</strong>, and <strong>&lt;&gt; some</strong> comparisons.<br>As an exercise, verify that <strong>= some</strong> is identical to <strong>in</strong>, whereas <strong>&lt;&gt; some</strong> is not the same as <strong>not in</strong>.<br><strong>all</strong></p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&gt;</span> <span class="keyword">all</span> (<span class="keyword">select</span> salary</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Biology&#x27;</span>);</span><br><span class="line"><span class="comment">-- 比生物学部门的所有老师工资都高</span></span><br></pre></td></tr></table></figure><blockquote><p>SQL also allows <strong>&lt; all</strong>, <strong>&lt;= all</strong>, <strong>&gt;= all</strong>, <strong>= all</strong>, and <strong>&lt;&gt; all</strong> comparisons.<br>As an exercise, verify that <strong>&lt;&gt; all</strong> is identical to <strong>not in</strong>, whereas <strong>= all</strong> is not the same as <strong>in</strong>.<br><strong>(not) exists</strong></p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> course_id</span><br><span class="line"><span class="keyword">from</span> section <span class="keyword">as</span> S</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span> <span class="keyword">and</span></span><br><span class="line">  <span class="keyword">exists</span> (<span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line">          <span class="keyword">from</span> section <span class="keyword">as</span> T</span><br><span class="line">          <span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> </span><br><span class="line">          <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span> </span><br><span class="line">          <span class="keyword">and</span> S.course_id<span class="operator">=</span> T .course_id);</span><br></pre></td></tr></table></figure><p>exists的原理: 当括号内的查询第一次被满足时即返回true,让sql引擎继续扫描下一行.</p><p><strong>unique</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> T .course id</span><br><span class="line"><span class="keyword">from</span> course <span class="keyword">as</span> T</span><br><span class="line"><span class="keyword">where</span> <span class="keyword">unique</span> (<span class="keyword">select</span> R.course id</span><br><span class="line"><span class="keyword">from</span> section <span class="keyword">as</span> R</span><br><span class="line"><span class="keyword">where</span> T .course id<span class="operator">=</span> R.course id <span class="keyword">and</span></span><br><span class="line">R.year <span class="operator">=</span> <span class="number">2017</span>);</span><br></pre></td></tr></table></figure><p>如果无重复元组则返回true,无论元组是否为空</p><h4 id="from中的子查询">from中的子查询</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> dept_name, avg_salary</span><br><span class="line"><span class="keyword">from</span> (<span class="keyword">select</span> dept_name, <span class="built_in">avg</span> (salary)</span><br><span class="line">      <span class="keyword">from</span> instructor</span><br><span class="line">      <span class="keyword">group</span> <span class="keyword">by</span> dept_name)</span><br><span class="line">      <span class="keyword">as</span> dept_avg (dept_name, avg_salary)</span><br><span class="line"><span class="keyword">where</span> avg_salary <span class="operator">&gt;</span> <span class="number">42000</span>;</span><br></pre></td></tr></table></figure><h4 id="with-子查询的变种">with: 子查询的变种</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> max_budget (<span class="keyword">value</span>) <span class="keyword">as</span></span><br><span class="line">(<span class="keyword">select</span> <span class="built_in">max</span>(budget)</span><br><span class="line"><span class="keyword">from</span> department)</span><br><span class="line"><span class="keyword">select</span> budget</span><br><span class="line"><span class="keyword">from</span> department, max_budget</span><br><span class="line"><span class="keyword">where</span> department.budget <span class="operator">=</span> max_budget.value;</span><br></pre></td></tr></table></figure><h4 id="标量子查询">标量子查询</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> dept_name,</span><br><span class="line">(<span class="keyword">select</span> <span class="built_in">count</span>(<span class="operator">*</span>)</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> department.dept_name <span class="operator">=</span> instructor.dept_name)</span><br><span class="line"><span class="keyword">as</span> num_instructors</span><br><span class="line"><span class="keyword">from</span> department;</span><br></pre></td></tr></table></figure><h3 id="having和group-by">having和group by</h3><p><strong>group by</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 在group子句中的所有属性取值相同的元组将被分在一个组内,显然,当涉及的属性名越多,分的组也会越多</span></span><br><span class="line"><span class="keyword">select</span> dept_name, <span class="built_in">avg</span> (salary)</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> dept_name;</span><br></pre></td></tr></table></figure><p>注意,没有出现在group by中的属性只能以聚集函数的参数形式在select语句中出现,如下方的例子中ID就是不该出现的属性,因为一个组中的教师可以有不同的ID,那就无法选定保留哪一个ID:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* erroneous query */</span></span><br><span class="line"><span class="keyword">select</span> dept_name, ID, <span class="built_in">avg</span> (salary)</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> dept_name;</span><br></pre></td></tr></table></figure><h4 id="having-作用于group的条件判断">having: 作用于group的条件判断</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> dept_name, <span class="built_in">avg</span> (salary) <span class="keyword">as</span> avg_salary</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> dept_name</span><br><span class="line"><span class="keyword">having</span> <span class="built_in">avg</span> (salary) <span class="operator">&gt;</span> <span class="number">42000</span>;</span><br><span class="line"><span class="comment">-- 平均薪资低于42000的部门分组将被过滤掉</span></span><br></pre></td></tr></table></figure><h3 id="关系的集合">关系的集合</h3><ul><li>三种运算均自动去重</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- union(并集): 找到至少在一个关系中出现的被select选中的元组,自动去重</span></span><br><span class="line"><span class="comment">-- union all: 保留重复项</span></span><br><span class="line">(<span class="keyword">select</span> course_id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span>)</span><br><span class="line"><span class="keyword">union</span></span><br><span class="line">(<span class="keyword">select</span> course_id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br><span class="line"><span class="comment">-- intersect(交集): 找到在两个关系中都出现的被select选中的元组,自动去重</span></span><br><span class="line"><span class="comment">-- intersect all: 保留重复项</span></span><br><span class="line">(<span class="keyword">select</span> course_id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Fall&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2017</span>)</span><br><span class="line"><span class="keyword">intersect</span></span><br><span class="line">(<span class="keyword">select</span> course_id</span><br><span class="line"><span class="keyword">from</span> section</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span><span class="operator">=</span> <span class="number">2018</span>);</span><br><span class="line"><span class="comment">-- except(差集): 找到在前一个关系中出现,但不在第二个关系中出现的,被select选中的元组,自动去重</span></span><br><span class="line"><span class="comment">-- except all: 保留重复项</span></span><br></pre></td></tr></table></figure><h3 id="排在最末尾的order-by-结果排序">排在最末尾的order by: 结果排序</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 不写默认为升序asc</span></span><br><span class="line"><span class="keyword">select</span> name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Physics&#x27;</span></span><br><span class="line"><span class="keyword">order</span> <span class="keyword">by</span> name; <span class="comment">-- 排序按字典序,首字母大的在后面</span></span><br><span class="line"><span class="comment">-- 先排第一个,同名再按第二个排</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">order</span> <span class="keyword">by</span> salary <span class="keyword">desc</span>, name <span class="keyword">asc</span>;</span><br></pre></td></tr></table></figure><h3 id="习题3-1">习题3.1</h3><p><strong>a</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> title</span><br><span class="line"><span class="keyword">from</span> course</span><br><span class="line"><span class="keyword">where</span> credits <span class="operator">=</span> <span class="number">3</span> <span class="keyword">and</span> dept_name <span class="operator">=</span> <span class="string">&#x27;Comp.Sci.&#x27;</span></span><br></pre></td></tr></table></figure><p><strong>b</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> ID</span><br><span class="line"><span class="keyword">from</span> instructor,teaches,student,takes</span><br><span class="line"><span class="keyword">where</span> instructor.name<span class="operator">=</span><span class="string">&#x27;Einstein&#x27;</span> <span class="keyword">and</span> instructor.ID <span class="operator">=</span> teaches.ID  <span class="keyword">and</span> student.ID <span class="operator">=</span>takes.ID <span class="keyword">and</span>(takes.semester ,takes.sec_id,takes.year,teaches.course_id)<span class="operator">=</span>(teaches.semester ,teaches.sec_id,teaches.year,takes.course_id)</span><br><span class="line"><span class="comment">-- 写疯了</span></span><br></pre></td></tr></table></figure><p><strong>c</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="built_in">max</span>(salary)</span><br><span class="line"><span class="keyword">from</span> instructor</span><br></pre></td></tr></table></figure><p><strong>d</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> max_salary(v) <span class="keyword">as</span> (<span class="keyword">select</span> <span class="built_in">max</span>(salary) <span class="keyword">from</span> instructor)</span><br><span class="line"><span class="keyword">select</span> ID,name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">=</span>max_salary(v)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 炫技写法1</span></span><br><span class="line"><span class="keyword">select</span> ID,name</span><br><span class="line"><span class="keyword">from</span> instructor</span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">&gt;=</span> <span class="keyword">all</span>(<span class="keyword">select</span> salary <span class="keyword">from</span> instructor)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2</span></span><br><span class="line"><span class="keyword">select</span> ID,name</span><br><span class="line"><span class="keyword">from</span> instructor </span><br><span class="line"><span class="keyword">where</span> salary <span class="operator">=</span> (<span class="keyword">select</span> <span class="built_in">max</span>(salary)<span class="keyword">from</span> instructor)</span><br></pre></td></tr></table></figure><p><strong>e</strong><br>目标: 2017年 秋季 每个课程段 选课人数<br>approach: section,takes</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="built_in">count</span>(ID),course_id,sec_id;</span><br><span class="line"><span class="keyword">from</span> section <span class="keyword">natural</span> <span class="keyword">join</span> takes</span><br><span class="line"><span class="keyword">where</span> <span class="keyword">year</span><span class="operator">=</span><span class="number">2017</span> <span class="keyword">and</span> semester<span class="operator">=</span><span class="string">&#x27;autumn&#x27;</span></span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> coures_id,sec_id;</span><br></pre></td></tr></table></figure><p><strong>f</strong><br>target: max enrollment in Autumn 2009</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> enrollment(counts) <span class="keyword">as</span> (</span><br><span class="line">  <span class="keyword">select</span> <span class="built_in">count</span>(ID)</span><br><span class="line">  <span class="keyword">from</span> section <span class="keyword">natural</span> <span class="keyword">join</span> takes</span><br><span class="line">  <span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Autumn&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span> <span class="operator">=</span> <span class="number">2009</span></span><br><span class="line">  <span class="keyword">group</span> <span class="keyword">by</span> course_id, sec_id</span><br><span class="line">)</span><br><span class="line"><span class="keyword">select</span> <span class="built_in">max</span>(counts) <span class="keyword">from</span> enrollment;</span><br></pre></td></tr></table></figure><h2 id="ch4">ch4</h2><h3 id="join">join</h3><h4 id="natural-join-自动去重的发散join">natural join: 自动去重的发散join</h4><p>自动去重,多列匹配</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name, course id</span><br><span class="line"><span class="keyword">from</span> student, takes</span><br><span class="line"><span class="keyword">where</span> student.ID <span class="operator">=</span> takes.ID;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 两者作用相同,尽管from的结果不同,但select时没有选择ID列,所以看不出来区别</span></span><br><span class="line"><span class="keyword">select</span> name, course id</span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">natural</span> <span class="keyword">join</span> takes;</span><br></pre></td></tr></table></figure><h4 id="join…using-自动去重的定向join">join…using: 自动去重的定向join</h4><blockquote><p>using 只能用于等值连接,不能配合其他谓词使用</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> name, title</span><br><span class="line"><span class="keyword">from</span> (student <span class="keyword">natural</span> <span class="keyword">join</span> takes) <span class="keyword">join</span> course <span class="keyword">using</span> (course id);</span><br></pre></td></tr></table></figure><p>相当于一个仅比对所需列的natural join,更加安全</p><h4 id="join…on-不自动去重的定向join">join…on: 不自动去重的定向join</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">join</span> takes <span class="keyword">on</span> student.ID <span class="operator">=</span> takes.ID;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 等价</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student, takes</span><br><span class="line"><span class="keyword">where</span> student.ID <span class="operator">=</span> takes.ID;</span><br></pre></td></tr></table></figure><ul><li>当然,on在大多数情况下都可以用where直接替换,但对于outer join来说,二者是有所不同的</li></ul><h4 id="outer-join-保留空值的发散join前缀">outer join: 保留空值的发散join前缀</h4><blockquote><p>outer join本身不能直接使用,需要搭配on,natural或者using</p></blockquote><p>当我们希望即便一边的ID有值,但另一边不存在这个ID时,也保留该行时,可以使用<code>outer join</code>.</p><p>有三种形式:</p><ol><li>left outer join: 只保留左边关系中的元组</li><li>right outer join: 只保留右边关系中的元组</li><li>full outer join: 保留两边关系的元组</li></ol><p>以left outer join为例子来说明:</p><ol><li>The attributes of tuple r that are <strong>derived from</strong> the left-hand-side relation are ﬁlled in with the values from tuple t.</li><li>The remaining attributes of r are ﬁlled with <strong>null values</strong>.<br><img src="/images/2026-03-27/PixPin_2026-03-30_10-42-15.webp" alt="alt text"></li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> ID</span><br><span class="line"><span class="keyword">from</span> student <span class="keyword">natural</span> <span class="keyword">left</span> <span class="keyword">outer</span> <span class="keyword">join</span> takes</span><br><span class="line"><span class="keyword">where</span> course id <span class="keyword">is</span> <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> (<span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> student</span><br><span class="line"><span class="keyword">where</span> dept name <span class="operator">=</span> <span class="string">&#x27;Comp. Sci.&#x27;</span>)</span><br><span class="line"><span class="keyword">natural</span> <span class="keyword">full</span> <span class="keyword">outer</span> <span class="keyword">join</span></span><br><span class="line">(<span class="keyword">select</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> takes</span><br><span class="line"><span class="keyword">where</span> semester <span class="operator">=</span> <span class="string">&#x27;Spring&#x27;</span> <span class="keyword">and</span> <span class="keyword">year</span> <span class="operator">=</span> <span class="number">2017</span>);</span><br></pre></td></tr></table></figure><h1>Database Design Using the E-R Model</h1><p>数据库的设计并不简单,所以我们需要一些好用的模型来组织数据库,比如这章涉及的E-R模型.</p><ul><li>忽然发现前面的笔记中废话太多了,像我的小测复习这样精炼就已经可以了,能够轻松的获取所需的信息</li></ul><h2 id="The-Entity-Relationship-Model">The Entity-Relationship Model</h2><ul><li>entity: a “thing” or “object” in the real world that is distinguishable from all other objects.<ul><li>实体是一个独一无二的个体</li></ul></li><li>attribute: 实体通过一组属性来表示,每个实体在它的每个属性上都有一个值(value)</li><li>entity set: 共享部分属性的实体集合</li><li>relationship: 多个实体之间的关系</li><li>relationship set: 相同类型的联系集合</li></ul><p><img src="/images/2026-04-08/PixPin_2026-04-09_09-40-11.webp" alt="alt text"></p><ul><li>看这个图就很好理解了,单独的两个实体,如Crick和Tanaka之间的关系就是relationship,而实体集之间的映射关系就是relationship set</li></ul><h2 id="E-R图">E-R图</h2><ul><li><p>entity set: 使用矩形来表示,分为两个部分,一部分是实体集的名称,一部分是实体集所有属性的名称</p></li><li><p>relationship set: 使用菱形来标识,通过线条连接到多个实体集</p><ul><li>relationship set的描述性属性用单矩形虚线连接<br><img src="/images/2026-04-08/PixPin_2026-04-09_09-37-00.webp" alt="alt text"></li></ul></li><li><p><strong>映射基数(mapping cardinality)</strong>: 一个实体通过一个联系集关联的其他实体的数量,有以下四种:</p><ul><li>一对一: 从联系集到两个实体集各画一个有向线段</li><li>一对多: &quot;多方&quot;使用无向线段连接,&quot;一方&quot;使用有向线段</li><li>多对一: 与一对多刚好相反</li><li>多对多: 两边都是无向线段<br><img src="/images/2026-04-08/PixPin_2026-04-10_08-44-57.webp" alt="alt text"></li></ul></li></ul><p><img src="/images/2026-04-08/PixPin_2026-04-10_10-05-54.webp" alt="alt text"></p><blockquote><p>The participation of an entity set E in a relationship set R is said to be <strong>total</strong> if every entity in E must participate in <strong>at least one</strong> relationship in R. If it is possible that some entities in E do not participate in relationships in R, the participation of entity set E in<br>relationship R is said to be <strong>partial</strong>.</p></blockquote><p>使用双线来表示一个实体集在联系集中全部参与.</p><h3 id="strong-entity-and-weak-entity">strong entity and weak entity</h3><h4 id="强实体-Strong-Entity">强实体 (Strong Entity)</h4><p>强实体是<strong>自足的</strong>，它不依赖于数据库中任何其他实体的存在。</p><ul><li><strong>物理特征</strong>：拥有一个独立的<strong>主键 (Primary Key)</strong>。这个主键不包含任何其他实体的属性。</li><li><strong>物理隐喻</strong>：像一个拥有独立身份证的“自然人”。</li><li><strong>ER 图标识</strong>：矩形框。</li><li><strong>例子</strong>：<code>Student</code>（学生）。每个学生都有唯一的 <code>student_id</code>，无论这个学生是否选课，他作为“学生”这个实体的物理记录在数据库中都是独立存在的。</li></ul><hr><h4 id="弱实体-Weak-Entity">弱实体 (Weak Entity)</h4><p>弱实体是<strong>寄生的</strong>，它必须依赖于另一个实体（称为“标识实体”或“父实体”）才能获得物理意义上的唯一性。</p><ul><li><strong>物理特征</strong>：没有足够的属性来构成自己的主键。它的主键是<strong>物理借用</strong>来的，由“父实体的物理主键 + 自己的部分键（Discriminator/Partial Key）”共同组成。</li><li><strong>物理隐喻</strong>：像“公寓里的房间”。没有公寓（父实体），“101室”这个编号物理上没有任何意义。</li><li><strong>ER 图标识</strong>：双边矩形框。</li><li><strong>例子</strong>：<code>Section</code>（开课班级）。单独看 <code>sec_id</code>（如 1 班）无法确定是哪门课。它必须物理依赖于 <code>Course</code>（课程），主键通常是 <code>(course_id, sec_id, semester, year)</code>。</li></ul><h3 id="概化与特化">概化与特化</h3><p><img src="/images/2026-04-08/PixPin_2026-04-10_11-33-45.webp" alt="alt text"></p><h4 id="特化-Specialization-——-自上而下">特化 (Specialization) —— 自上而下</h4><ul><li><strong>物理逻辑</strong>：将一个高层级的实体集（父类）根据特定特征，物理拆分为多个低层级的子实体集（子类）。</li><li><strong>触发场景</strong>：当你发现某些属性只适用于部分成员，而不适用于全体时。</li><li><strong>ER图表示</strong>: 由特化实体用空心箭头指向父类,如果一个实体可以属于多个特化实体集,则称为<strong>重叠特化(overlapping specialization)</strong>,使用两个单独箭头;如果只能属于一个实体集,则称为<strong>不相交特化(disjoint specialization)</strong>,使用一个箭头</li><li><strong>物理示例</strong>：<code>员工 (Employee)</code> 实体。<ul><li>只有“厨师”拥有 <code>厨师等级</code> 属性。</li><li>只有“司机”拥有 <code>驾驶证号</code> 属性。</li><li><strong>物理动作</strong>：从 <code>员工</code> 集合中特化出 <code>厨师</code> 和 <code>司机</code> 子集。</li></ul></li></ul><h4 id="概化-Generalization-——-自下而上">概化 (Generalization) —— 自下而上</h4><ul><li><strong>物理逻辑</strong>：提取多个实体集中的共同属性，物理归纳为一个更高层级的通用实体集（父类）。</li><li><strong>触发场景</strong>：为了消除数据冗余，并在物理层面提供统一的访问接口。</li><li><strong>物理示例</strong>：存在 <code>老虎</code>、<code>狮子</code>、<code>大象</code> 三个独立实体。<ul><li>它们物理上共有 <code>年龄</code>、<code>体重</code>、<code>所属区域</code> 等属性。</li><li><strong>物理动作</strong>：将它们概化为 <code>动物 (Animal)</code> 实体。</li></ul></li></ul><h3 id="汇总">汇总</h3><p><img src="/images/2026-04-08/PixPin_2026-04-10_10-35-46.webp" alt="alt text"></p><h2 id="The-Unified-Modeling-Language-UML">The Unified Modeling Language (UML)</h2><h1>Relational Database Design(较难,待补充)</h1><p>根据上一章的E-R图我们可以导出一组关系模式:<br><img src="/images/2026-03-25/PixPin_2026-03-26_08-23-16.webp" alt="alt text"></p><h1>Complex Data Types</h1><blockquote><p>In this chapter, we discuss several non-atomic data types that are widely used,including <strong>semi-structured data, object-based data, textual data, and spatial data</strong>.</p></blockquote><h2 id="Semi-structured-Data">Semi-structured Data</h2><h3 id="Overview-of-Semi-structured-Data-Models">Overview of Semi-structured Data Models</h3><h4 id="Flexible-Schema">Flexible Schema</h4><p>Some database systems allow each tuple to potentially have a diﬀerent set of attributes;<br>such a representation is referred to as a <strong>wide column</strong> data representation.<br>The set of attributes is not ﬁxed in such a representation; <strong>each tuple may have a diﬀerent set of attributes</strong>, and new attributes may be added as needed.</p><p>A more restricted form of this representation is to have a ﬁxed but very large number of attributes, with each tuple using only those attributes that it needs, leaving the rest with null values; such a representation is called a <strong>sparse column</strong>(稀疏表)  representation.</p><h4 id="Multivalued-Data-Types">Multivalued Data Types</h4><blockquote><p>Some representations allow attributes to store key-value maps, which store key-value pairs. A <strong>key-value map</strong>, often just called a map, is a set of (key, value) pairs, such that each key occurs in at most one element.</p></blockquote><h4 id="Nested-Data-Types">Nested Data Types</h4><p>All of these data types represent a hierarchy of data types, and that structure leads to the use of the term nested data types.</p><ul><li>JSON和XML是该数据库类型的最重要的两个代表</li></ul><h3 id="JSON-JavaScript-Object-Notation">JSON(JavaScript Object Notation)</h3><blockquote><p>The <strong>JavaScript Object Notation</strong> (JSON), is a textual representation of complex data types that is widely used to transmit data between applications and to store complex data.<br>JSON supports the primitive data types integer, real and string, as well as arrays,<br>and “objects,” which are a collection of (attribute name, value) pairs.</p></blockquote><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;ID&quot;</span><span class="punctuation">:</span> <span class="string">&quot;22222&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="string">&quot;firstname: &quot;</span>Albert<span class="string">&quot;,</span></span><br><span class="line"><span class="string">    &quot;</span>lastname<span class="punctuation">:</span> <span class="string">&quot;Einstein&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;deptname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Physics&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;children&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;firstname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Hans&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;lastname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Einstein&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;firstname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Eduard&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;lastname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Einstein&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="XML-Extensible-Markup-Language">XML(Extensible Markup Language)</h3><blockquote><p>The <strong>XML</strong> data representation adds tags enclosed in angle brackets, &lt;&gt;, to mark up information in a textual representation.<br>Tags are used in pairs, with <tag> and </tag> delimiting the beginning and the end of the portion of the text to which the tag refers.</p></blockquote><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">course</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">course_id</span>&gt;</span> CS-101 <span class="tag">&lt;/<span class="name">course_id</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span> Intro. to Computer Science <span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">dept_name</span>&gt;</span> Comp. Sci. <span class="tag">&lt;/<span class="name">dept_name</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">credits</span>&gt;</span> 4 <span class="tag">&lt;/<span class="name">credits</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">course</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="Object-Orientation">Object Orientation</h2><p>Three approaches are used in practice for integrating object orientation with<br>database systems:</p><ol><li>Build an <strong>object-relational database system</strong>, which adds object-oriented features to a relational database system.</li><li>Automatically convert data from the native object-oriented type system of the programming language to a relational representation for storage, and vice versa for retrieval. Data conversion is speciﬁed using an <strong>object-relational mapping</strong>.</li><li>Build an <strong>object-oriented database system</strong>, that is, a database system that natively supports an object-oriented type system and allows direct access to data from an object-oriented programming language using the native type system of the language.</li></ol><p>We provide a brief introduction to the ﬁrst two approaches in this section.</p><h3 id="Object-Relational-Database-Systems">Object-Relational Database Systems</h3><p><strong>User-Defined Types</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> type Person</span><br><span class="line">(ID <span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">primary key</span>,</span><br><span class="line">name <span class="type">varchar</span>(<span class="number">20</span>),</span><br><span class="line">address <span class="type">varchar</span>(<span class="number">20</span>))</span><br><span class="line"><span class="keyword">ref</span> <span class="keyword">from</span>(ID);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> people <span class="keyword">of</span> Person;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- We can create a new person as follows:</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">insert into</span> people (ID, name, address) <span class="keyword">values</span></span><br><span class="line">(<span class="string">&#x27;12345&#x27;</span>, <span class="string">&#x27;Srinivasan&#x27;</span>, <span class="string">&#x27;23 Coyote Run&#x27;</span>);</span><br></pre></td></tr></table></figure><p><strong>Type Inheritance</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> type Student under Person</span><br><span class="line">(degree <span class="type">varchar</span>(<span class="number">20</span>)) ;</span><br><span class="line"><span class="keyword">create</span> type Teacher under Person</span><br><span class="line">(salary <span class="type">integer</span>);</span><br></pre></td></tr></table></figure><p><strong>Table Inheritance</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- PostgreSQL</span></span><br><span class="line"><span class="keyword">create table</span> students</span><br><span class="line">(degree <span class="type">varchar</span>(<span class="number">20</span>))</span><br><span class="line">inherits people;</span><br><span class="line"><span class="keyword">create table</span> teachers</span><br><span class="line">(salary <span class="type">integer</span>)</span><br><span class="line">inherits people;</span><br></pre></td></tr></table></figure><h3 id="Object-Relational-Mapping-ORM">Object-Relational Mapping(ORM)</h3><blockquote><p><strong>A fringe beneﬁt</strong> of using an ORM is that any of a number of databases can be used<br>to store data, with exactly the same high-level code. ORMs hide minor SQL diﬀerences<br>between databases from the higher levels. Migration from one database to another is<br>thus relatively straightforward when using an ORM, whereas SQL diﬀerences can make<br>such migration signiﬁcantly harder if an application uses SQL to communicate with<br>the database.</p></blockquote><blockquote><p><strong>On the negative side</strong>, object-relational mapping systems can suﬀer from signiﬁcant<br>performance ineﬃciencies for bulk database updates, as well as for complex queries<br>that are written directly in the imperative language. It is possible to update the database<br>directly, bypassing the object-relational mapping system, and to write complex queries<br>directly in SQL in cases where such ineﬃciencies are discovered.</p></blockquote><h2 id="Textual-Data">Textual Data</h2><blockquote><p>Textual data consists of unstructured text. The term <strong>information retrieval</strong> generally refers to the querying of unstructured textual data.</p></blockquote><h3 id="Keyword-Queries">Keyword Queries</h3><p>A keyword query retrieves documents whose set of keywords contains all the keywords in the query.</p><p>搜索引擎是information retrieval systems的典型例子,根据关键词返回网页内容.</p><h3 id="Relevance-Ranking">Relevance Ranking</h3><blockquote><p>The set of all documents that contain the keywords in a query may be very large; in<br>particular, there are billions of documents on the web, and most keyword queries on<br>a web search engine ﬁnd hundreds of thousands of documents containing some or all of the keywords.</p></blockquote><p>Information-retrieval systems therefore estimate relevance of documents to a query and return only highly ranked documents as answers.</p><h4 id="Ranking-Using-TF-IDF">Ranking Using TF-IDF</h4><ul><li><strong>term</strong>: refers to a keyword occurring in a document, or given as part of a query.</li><li>TF: term frequency</li></ul><p>One way of measuring TF(d, t), the relevance of a term t to a document d, is:<br><img src="/images/2026-04-03/PixPin_2026-04-14_10-07-26.webp" alt="alt text"><br>where n(d) denotes the number of term occurrences in the document and n(d, t) denotes the number of occurrences of term t in the document d.</p><blockquote><p>However, not all terms used as keywords are equal. Suppose a query uses two terms, one<br>of which occurs frequently, such as “database”, and another that is less frequent, such<br>as “Silberschatz”. A document containing “Silberschatz” but not “database” should be<br>ranked higher than a document containing the term “database” but not “Silberschatz”.</p></blockquote><p>To ﬁx this problem, weights are assigned to terms using the inverse document fre-<br>quency (IDF), deﬁned as:<br><img src="/images/2026-04-03/PixPin_2026-04-14_10-12-52.webp" alt="alt text"></p><p>where n(t) denotes the number of documents (among those indexed by the system) that contain the term t. The <strong>relevance</strong> of a document d to <strong>a set of terms Q</strong> is then deﬁned as:<br><img src="/images/2026-04-03/PixPin_2026-04-14_10-13-55.webp" alt="alt text"></p><blockquote><p>Almost all text documents (in English) contain words such as “and,” “or,” “a,” and<br>so on, and hence these words are useless for querying purposes since their inverse doc-<br>ument frequency is extremely low. Information-retrieval systems deﬁne a set of words,<br>called <strong>stop words</strong>, containing 100 or so of the most common words, and ignore these<br>words when indexing a document. Such words are not used as keywords, and they are<br>discarded if present in the keywords supplied by the user.</p></blockquote><ul><li>这就是为什么输入<code>python和爬虫</code>得到的结果与<code>python 爬虫</code>相差无几的原因</li></ul><h4 id="Ranking-Using-Hyperlinks">Ranking Using Hyperlinks</h4><blockquote><p>Hyperlinks between documents can be used to decide on the overall importance of<br>a document, independent of the keyword query; for example, documents linked from<br>many other documents are considered more important.</p></blockquote><p>Pagerank是该排序方法的著名代表之一</p><h2 id="Spatial-Data">Spatial Data</h2><p>Two types of spatial data are particularly important:</p><ol><li>Geographic data: such as road maps, land-usage maps, topographic elevation maps, political maps showing boundaries.</li><li>Geometric data:  include spatial information about how objects— such as buildings, cars, or aircraft— are constructed</li></ol><p>游戏建模,谷歌地图都属于空间数据这一范畴</p><h1>补充</h1><h2 id="Outline-Of-The-Course">Outline Of The Course</h2><ul><li>Chapter 1: Introduction</li><li><em>Chapter 2: Introduction to Relational Model</em></li><li><strong>Chapter 3: Introduction to SQL</strong></li><li><em>Chapter 4: Intermediate SQL</em></li><li>Chapter 5: Advanced SQL (只要求前三节)</li><li><strong>Chapter 6: Entity-Relationship Model</strong></li><li><strong>Chapter 7: Relational Database Design</strong></li><li>Chapter 8: Complex Data Types</li><li>Chapter 9: Application Design</li><li>Chapter 10: Big Data</li><li>Chapter 11: Data Analytics</li><li>Chapter 12: Physical Storage Systems (Sections 12.5 (RAID) omitted)</li><li>Chapter 13: Storage and File Structure</li><li><strong>Chapter 14: Indexing and Hashing</strong></li><li>Chapter 15: Query Processing (Section 15.1, 15.2)</li><li>Chapter 17: Transactions</li></ul><ul><li>斜体的为需要熟练的部分,黑体的为非常重要的部分</li></ul><h3 id="吐槽">吐槽</h3><p>教材详细过头了,而ppt基本是原封不动的搬运了整本书1300多页的内容,一个ppt最少五六十张,而总共有20多个ppt.<br>要说他敬业呢,ppt上的内容破碎无比,速览一遍发现不如看书完整,要说他不敬业呢,好歹有这么大的工作量.</p><ul><li>(3/26)这本书真的是又臭又长…</li><li>(3/28)我服了原来这个ppt是<a href="https://db-book.com/slides-dir/index.html">官网</a>上的,而我们老师实际啥都没干…</li></ul><h2 id="本教材核心-大学数据库">本教材核心: 大学数据库</h2><p><img src="/images/2026-03-25/PixPin_2026-03-26_08-23-16.webp" alt="alt text"></p><ul><li>keys used<br><img src="/images/2026-03-25/PixPin_2026-03-26_08-24-59.webp" alt="alt text"></li><li>schema diagram</li></ul><h3 id="关系表解析">关系表解析</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create table</span> classroom</span><br><span class="line">(building<span class="type">varchar</span>(<span class="number">15</span>),</span><br><span class="line"> room_number<span class="type">varchar</span>(<span class="number">7</span>),</span><br><span class="line"> capacity<span class="type">numeric</span>(<span class="number">4</span>,<span class="number">0</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (building, room_number)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> department</span><br><span class="line">(dept_name<span class="type">varchar</span>(<span class="number">20</span>), </span><br><span class="line"> building<span class="type">varchar</span>(<span class="number">15</span>), </span><br><span class="line"> budget        <span class="type">numeric</span>(<span class="number">12</span>,<span class="number">2</span>) <span class="keyword">check</span> (budget <span class="operator">&gt;</span> <span class="number">0</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (dept_name)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> course</span><br><span class="line">(course_id<span class="type">varchar</span>(<span class="number">8</span>), </span><br><span class="line"> title<span class="type">varchar</span>(<span class="number">50</span>), </span><br><span class="line"> dept_name<span class="type">varchar</span>(<span class="number">20</span>),</span><br><span class="line"> credits<span class="type">numeric</span>(<span class="number">2</span>,<span class="number">0</span>) <span class="keyword">check</span> (credits <span class="operator">&gt;</span> <span class="number">0</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (course_id),</span><br><span class="line"> <span class="keyword">foreign key</span> (dept_name) <span class="keyword">references</span> department (dept_name)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> <span class="keyword">set</span> <span class="keyword">null</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> instructor</span><br><span class="line">(ID<span class="type">varchar</span>(<span class="number">5</span>), </span><br><span class="line"> name<span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">not null</span>, </span><br><span class="line"> dept_name<span class="type">varchar</span>(<span class="number">20</span>), </span><br><span class="line"> salary<span class="type">numeric</span>(<span class="number">8</span>,<span class="number">2</span>) <span class="keyword">check</span> (salary <span class="operator">&gt;</span> <span class="number">29000</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (ID),</span><br><span class="line"> <span class="keyword">foreign key</span> (dept_name) <span class="keyword">references</span> department (dept_name)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> <span class="keyword">set</span> <span class="keyword">null</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> section</span><br><span class="line">(course_id<span class="type">varchar</span>(<span class="number">8</span>), </span><br><span class="line">         sec_id<span class="type">varchar</span>(<span class="number">8</span>),</span><br><span class="line"> semester<span class="type">varchar</span>(<span class="number">6</span>)</span><br><span class="line"><span class="keyword">check</span> (semester <span class="keyword">in</span> (<span class="string">&#x27;Fall&#x27;</span>, <span class="string">&#x27;Winter&#x27;</span>, <span class="string">&#x27;Spring&#x27;</span>, <span class="string">&#x27;Summer&#x27;</span>)), </span><br><span class="line"> <span class="keyword">year</span><span class="type">numeric</span>(<span class="number">4</span>,<span class="number">0</span>) <span class="keyword">check</span> (<span class="keyword">year</span> <span class="operator">&gt;</span> <span class="number">1701</span> <span class="keyword">and</span> <span class="keyword">year</span> <span class="operator">&lt;</span> <span class="number">2100</span>), </span><br><span class="line"> building<span class="type">varchar</span>(<span class="number">15</span>),</span><br><span class="line"> room_number<span class="type">varchar</span>(<span class="number">7</span>),</span><br><span class="line"> time_slot_id<span class="type">varchar</span>(<span class="number">4</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (course_id, sec_id, semester, <span class="keyword">year</span>),</span><br><span class="line"> <span class="keyword">foreign key</span> (course_id) <span class="keyword">references</span> course (course_id)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> cascade,</span><br><span class="line"> <span class="keyword">foreign key</span> (building, room_number) <span class="keyword">references</span> classroom (building, room_number)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> <span class="keyword">set</span> <span class="keyword">null</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> teaches</span><br><span class="line">(ID<span class="type">varchar</span>(<span class="number">5</span>), </span><br><span class="line"> course_id<span class="type">varchar</span>(<span class="number">8</span>),</span><br><span class="line"> sec_id<span class="type">varchar</span>(<span class="number">8</span>), </span><br><span class="line"> semester<span class="type">varchar</span>(<span class="number">6</span>),</span><br><span class="line"> <span class="keyword">year</span><span class="type">numeric</span>(<span class="number">4</span>,<span class="number">0</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (ID, course_id, sec_id, semester, <span class="keyword">year</span>),</span><br><span class="line"> <span class="keyword">foreign key</span> (course_id, sec_id, semester, <span class="keyword">year</span>) <span class="keyword">references</span> section (course_id, sec_id, semester, <span class="keyword">year</span>)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> cascade,</span><br><span class="line"> <span class="keyword">foreign key</span> (ID) <span class="keyword">references</span> instructor (ID)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> cascade</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> student</span><br><span class="line">(ID<span class="type">varchar</span>(<span class="number">5</span>), </span><br><span class="line"> name<span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">not null</span>, </span><br><span class="line"> dept_name<span class="type">varchar</span>(<span class="number">20</span>), </span><br><span class="line"> tot_cred<span class="type">numeric</span>(<span class="number">3</span>,<span class="number">0</span>) <span class="keyword">check</span> (tot_cred <span class="operator">&gt;=</span> <span class="number">0</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (ID),</span><br><span class="line"> <span class="keyword">foreign key</span> (dept_name) <span class="keyword">references</span> department (dept_name)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> <span class="keyword">set</span> <span class="keyword">null</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> takes</span><br><span class="line">(ID<span class="type">varchar</span>(<span class="number">5</span>), </span><br><span class="line"> course_id<span class="type">varchar</span>(<span class="number">8</span>),</span><br><span class="line"> sec_id<span class="type">varchar</span>(<span class="number">8</span>), </span><br><span class="line"> semester<span class="type">varchar</span>(<span class="number">6</span>),</span><br><span class="line"> <span class="keyword">year</span><span class="type">numeric</span>(<span class="number">4</span>,<span class="number">0</span>),</span><br><span class="line"> grade        <span class="type">varchar</span>(<span class="number">2</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (ID, course_id, sec_id, semester, <span class="keyword">year</span>),</span><br><span class="line"> <span class="keyword">foreign key</span> (course_id, sec_id, semester, <span class="keyword">year</span>) <span class="keyword">references</span> section (course_id, sec_id, semester, <span class="keyword">year</span>)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> cascade,</span><br><span class="line"> <span class="keyword">foreign key</span> (ID) <span class="keyword">references</span> student (ID)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> cascade</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> advisor</span><br><span class="line">(s_ID<span class="type">varchar</span>(<span class="number">5</span>),</span><br><span class="line"> i_ID<span class="type">varchar</span>(<span class="number">5</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (s_ID),</span><br><span class="line"> <span class="keyword">foreign key</span> (i_ID) <span class="keyword">references</span> instructor (ID)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> <span class="keyword">set</span> <span class="keyword">null</span>,</span><br><span class="line"> <span class="keyword">foreign key</span> (s_ID) <span class="keyword">references</span> student (ID)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> cascade</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> time_slot</span><br><span class="line">(time_slot_id<span class="type">varchar</span>(<span class="number">4</span>),</span><br><span class="line"> <span class="keyword">day</span><span class="type">varchar</span>(<span class="number">1</span>),</span><br><span class="line"> start_hr<span class="type">numeric</span>(<span class="number">2</span>) <span class="keyword">check</span> (start_hr <span class="operator">&gt;=</span> <span class="number">0</span> <span class="keyword">and</span> start_hr <span class="operator">&lt;</span> <span class="number">24</span>),</span><br><span class="line"> start_min<span class="type">numeric</span>(<span class="number">2</span>) <span class="keyword">check</span> (start_min <span class="operator">&gt;=</span> <span class="number">0</span> <span class="keyword">and</span> start_min <span class="operator">&lt;</span> <span class="number">60</span>),</span><br><span class="line"> end_hr<span class="type">numeric</span>(<span class="number">2</span>) <span class="keyword">check</span> (end_hr <span class="operator">&gt;=</span> <span class="number">0</span> <span class="keyword">and</span> end_hr <span class="operator">&lt;</span> <span class="number">24</span>),</span><br><span class="line"> end_min<span class="type">numeric</span>(<span class="number">2</span>) <span class="keyword">check</span> (end_min <span class="operator">&gt;=</span> <span class="number">0</span> <span class="keyword">and</span> end_min <span class="operator">&lt;</span> <span class="number">60</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (time_slot_id, <span class="keyword">day</span>, start_hr, start_min)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> prereq</span><br><span class="line">(course_id<span class="type">varchar</span>(<span class="number">8</span>), </span><br><span class="line"> prereq_id<span class="type">varchar</span>(<span class="number">8</span>),</span><br><span class="line"> <span class="keyword">primary key</span> (course_id, prereq_id),</span><br><span class="line"> <span class="keyword">foreign key</span> (course_id) <span class="keyword">references</span> course (course_id)</span><br><span class="line"><span class="keyword">on</span> <span class="keyword">delete</span> cascade,</span><br><span class="line"> <span class="keyword">foreign key</span> (prereq_id) <span class="keyword">references</span> course (course_id)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><ul><li>departname: 部门的办公地点和预算</li><li>course: 培养计划中的课程</li><li>instructor: 教师的信息</li><li>section: 课表中的真实课程<ul><li>section是course在某一学期的映射</li></ul></li><li>teaches: 教师教授的课程信息</li><li>student: 学生的信息</li><li>takes: 学生所选的真实课表</li><li></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;课程用教材:&amp;lt;&amp;lt; Database System Concepts &amp;gt;&amp;gt;
&lt;ul&gt;
&lt;li&gt;这本书讲的还是比较全面的,但废话也很多,部分地方讲的不够深&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Intro&lt;/h1&gt;
&lt;ul&gt;
</summary>
      
    
    
    
    
    <category term="数据库" scheme="https://revival-of-hope.github.io/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    <category term="后端" scheme="https://revival-of-hope.github.io/tags/%E5%90%8E%E7%AB%AF/"/>
    
  </entry>
  
  <entry>
    <title>算法拾掇</title>
    <link href="https://revival-of-hope.github.io/2026/03/14/dynamic-2026-03-14-%E7%AE%97%E6%B3%95%E9%87%8D%E6%8B%BE/"/>
    <id>https://revival-of-hope.github.io/2026/03/14/dynamic-2026-03-14-%E7%AE%97%E6%B3%95%E9%87%8D%E6%8B%BE/</id>
    <published>2026-03-14T00:00:00.000Z</published>
    <updated>2026-04-04T12:15:04.179Z</updated>
    
    <content type="html"><![CDATA[<h2 id="注意事项"><a class="markdownIt-Anchor" href="#注意事项"></a> 注意事项</h2><p>在吵闹的环境下刷题效果会更差而且花费两倍的精力,故不建议上课刷题</p><ul><li>感谢算法课老师,要求平时分有5分是从3.5开始要刷满500题才给的,给了我不少重开算法的动力</li></ul><blockquote><p>我决定这次好好的跟着深入浅出这本书走,把该刷的题都刷了,并且采用规范的程序设计方式,能写函数的地方就写函数,要设计类的时候就设计类</p></blockquote><h1 id="cpp语法"><a class="markdownIt-Anchor" href="#cpp语法"></a> cpp语法</h1><h2 id="基础语法"><a class="markdownIt-Anchor" href="#基础语法"></a> 基础语法</h2><h3 id="cpp的编译316"><a class="markdownIt-Anchor" href="#cpp的编译316"></a> cpp的编译(3/16)</h3><p>cpp需要被编译器翻译成汇编语言,再被汇编器转换为二进制机器码,再被链接器与库文件和其他源文件拼接,得到可执行文件.<br />市面上有三大主流编译器,分别是微软的MSVC(Microsoft Visual C++),被集成在VS中;开源的GCC(GNU Compiler Collection),主要在Linux中使用;基于GCC和LLVM(通用编译链)的Clang,由苹果公司赞助,是macOS唯一官方支持的编译器,集成在Xcode中.</p><ul><li>另外一个在Windows系统常用的MinGW编译器则是GCC的物理移植版<br />当然现在的电脑性能这么好,用哪个编译器都可以.</li></ul><h3 id="cpp的内存分配"><a class="markdownIt-Anchor" href="#cpp的内存分配"></a> cpp的内存分配</h3><ul><li><a href="https://whatbeg.com/2019/04/16/cppmemory.html">来源</a><br />通常一个由 C/C++ 编译的程序占用的内存分为以下 5 个部分:</li></ul><ol><li>栈区（stack）: 由编译器自动分配释放，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。</li><li>堆区（heap）: 一般由程序员分配释放，若程序员不释放，程序结束时可能由OS回收。注意它与数据结构中的堆是两回事，分配方式倒是类似于链表。</li><li>全局区（静态区）（static）: 全局变量和静态变量的存储是放在一块的，初始化的全局变量和静态变量在一块区域，未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。<ol><li>全局区又可分为DATA段(全局初始化区):用来存放初始化的全局和静态变量,和BSS段(全局未初始化区):用来存放未初始化的全局和静态变量.在程序运行结束时这两类内存均会自动释放</li></ol></li><li>文字常量区: 常量字符串等只读数据放在这里的。程序结束后由系统释放。</li><li>程序代码区: 存放函数体的二进制代码。</li></ol><h3 id="关键字"><a class="markdownIt-Anchor" href="#关键字"></a> 关键字</h3><h4 id="const与volatile"><a class="markdownIt-Anchor" href="#const与volatile"></a> const与volatile</h4><ul><li><a href="https://eel.is/c++draft/dcl.type.cv">参考</a><br />使用const修饰<code>int x</code>的时候,与单纯的写<code>int x</code>在内存区域上的表现没有什么不同,也就是说,const是一个编译时关键字,保证在运行时没有任何指令可以修改这块内存区域.</li></ul><blockquote><p>Any attempt to modify a const object during its lifetime results in undefined behavior.</p></blockquote><p>而<strong>volatile</strong>强制编译器取消对这个变量的运行期优化,要求每次对这个变量的操作都实际发生在内存,但现在基本很难看见这个关键字了.</p><h4 id="static"><a class="markdownIt-Anchor" href="#static"></a> static</h4><ul><li><a href="https://www.runoob.com/w3cnote/cpp-static-usage.html">来源</a></li></ul><blockquote><p>我们知道在函数内部定义的变量，当程序执行到它的定义处时，编译器为它在栈上分配空间，函数在栈上分配的空间在此函数执行结束时会释放掉，这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时，如何实现？ 最容易想到的方法是定义为全局的变量，但定义一个全局变量有许多缺点，最明显的缺点是破坏了此变量的访问范围（使得在此函数中定义的变量，不仅仅只受此函数控制）。static 关键字则可以很好的解决这个问题。</p><p>另外，在 C++ 中，需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部，对外不可见时，可将其定义为静态数据。</p></blockquote><p>TL;DR:<br />（1）在修饰变量的时候，static 修饰的静态局部变量只执行初始化一次，而且延长了局部变量的生命周期，直到程序运行结束以后才释放。<br />（2）static 修饰全局变量的时候，这个全局变量只能在本文件中访问，不能在其它文件中访问，即便是 extern 外部声明也不可以。<br />（3）static 修饰一个函数，则这个函数的只能在本文件中调用，不能被其他文件调用。static 修饰的变量存放在全局数据区的静态变量区，包括全局静态变量和局部静态变量，都在全局数据区分配内存。初始化的时候自动初始化为 0。</p><h4 id="static_cast与其他的类型转换"><a class="markdownIt-Anchor" href="#static_cast与其他的类型转换"></a> static_cast与其他的类型转换</h4><ul><li><a href="https://www.cnblogs.com/wanghongyang/p/15054880.html">参考</a></li><li><a href="https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used">StackOverflow上的解答</a></li></ul><p>当我们需要强制更改变量类型的时候,应该考虑到一点:</p><ul><li>如果直接在内存对应地址增加或者减小它的占用空间大小,显然会导致各种各样的内存问题.<br />因此,类型转换一般是不更改内存的,而是在编译时告诉编译器这个变量的类型改变了,请你按照改变后的类型来处理这个变量.</li></ul><blockquote><p>隐式类型转换是安全的，显式类型转换是有风险的，C语言之所以增加强制类型转换的语法，就是为了强调风险，让程序员意识到自己在做什么。</p><p>但是，这种强调风险的方式还是比较粗放，粒度比较大，它并没有表明存在什么风险，风险程度如何。</p><p>为了使潜在风险更加细化，使问题追溯更加方便，使书写格式更加规范，C++ 对类型转换进行了分类，并新增了四个关键字来予以支持，它们分别是：<br />| 关键字                 | 说明                                                                                                                          |<br />| :--------------------- | :---------------------------------------------------------------------------------------------------------------------------- |<br />| <strong><code>static_cast</code></strong>      | 用于良性转换，一般不会导致意外发生，风险很低。                                                                                |<br />| <strong><code>const_cast</code></strong>       | 用于 <code>const</code> 与非 <code>const</code>、<code>volatile</code> 与非 <code>volatile</code> 之间的转换。                                                            |<br />| <strong><code>reinterpret_cast</code></strong> | 高度危险的转换，这种转换仅仅是对二进制位的重新解释，不会借助已有的转换规则对数据进行调整，但是可以实现最灵活的 C++ 类型转换。 |<br />| <strong><code>dynamic_cast</code></strong>     | 用于多态和向下转型                                                                                                            |</p></blockquote><h4 id="constexpr"><a class="markdownIt-Anchor" href="#constexpr"></a> constexpr</h4><h4 id="decltype"><a class="markdownIt-Anchor" href="#decltype"></a> decltype</h4><h3 id="指针与引用"><a class="markdownIt-Anchor" href="#指针与引用"></a> 指针与引用</h3><h4 id="指针实质"><a class="markdownIt-Anchor" href="#指针实质"></a> 指针实质</h4><p>事实上,接触了差不多一年cpp,我还是没有彻底搞懂指针,教材上,网上讲的基本都是怎么用指针,简单的告诉你指针就是取地址,调用的时候就是解引用取得引用对象,但并没有告诉我,为什么这么写就能行.<br />试着阅读&lt;&lt; C++ Programming Language &gt;&gt;的第七章,里面是这么讲的:</p><blockquote><p>对于类型T来说,T<em>是<code>指向T的指针</code>的类型,换句话说,T</em>类型的变量能够存放T类型对象的地址.</p><p>对指针的一个基本操作是解引用(dereferencing),即引用指针所指的对象,也被称为间接取值(indirection),解引用运算符为<code>*</code>.<br />比如:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span> c = <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line"><span class="type">char</span>* p = &amp;c;</span><br><span class="line"><span class="type">char</span> c2 = *p;</span><br></pre></td></tr></table></figure><p>但书上到这里就戛然而止了,相当于啥都没讲,并没有触及底层的设计理念.不过话说回来,指针是从c传下来的,不关cpp设计的事.🙂</p><p>那么,为什么要这样写呢,也就是说,为什么不能直接写<code>char p = &amp;c;</code>来存储地址,然后再把这个解引用运算符用来根据地址p来找到原来的c变量呢?</p><p>思考一下,<code>char p = &amp;c</code>只是定义了一个char变量而已,也就是规定死了为1字节长,这里由于没用unsigned char,故只能取到0-127的地址值,因为地址的绝对值显然不会为负值.</p><p>显然,与其用char,我们不如用<code>long long int</code>类型来存储高达8字节的地址值,从而保证能存取足够多的地址,也就是这样写:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span> c = <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line"><span class="type">long</span> <span class="type">long</span> p =&amp;c;</span><br></pre></td></tr></table></figure><p>那么,取到地址之后我们又要怎么根据这个地址取到应该取的值呢?</p><blockquote><p>要知道,变量值是分布在一块连续内存上的,你并不知道这个地址的前后是什么,可能是程序的核心部分,也可能是上次运行后尚未清除的缓存垃圾.当然,我们可以根据p所引用的变量c的类型来判断要读取的连续字节数,比如在我们的例子中p对应的是<code>char c</code>,也就是说我们只需要在这个地址往后取一个字节,就可以找回这个变量c存储的值了.</p></blockquote><p>可是,上述的论述中有一个问题,那就是p只是存储了c的地址而已,<strong>它并不知道c的类型</strong>!那么,显然我们需要一种标识来取代单纯的<strong>long long p</strong>声明,这个标识还需要能够表明我所引用地址对应变量的类型.<br />比如,为了满足上述的要求,它可以写成类似<code>long long char **mark**</code>的形式,但显然这太长了,也太丑了…</p><p>但我们又可以想到,既然指针需要使用long long类型来保证取到尽可能多的地址,那么<code>long long</code>这个部分就可以省略了,上述例子从而简化成了<code>char **mark**</code>,让编译器根据这个<strong>mark</strong>来了解这是一个需要用long long来存储的指针变量.</p><p>那么,问题就简化到了这个<strong>mark</strong>用什么符号来表示比较好.</p><p>非常可惜的是,c语言的设计者们并没有想过将解引用符号<code>*</code>和取指针符号<strong>mark</strong>分开来表示,而是直接把<strong>mark</strong>定为了<code>*</code>,从而导致了学习c语言的无穷痛苦…</p><p>所以,回到这一句:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span>* p = &amp;c;</span><br></pre></td></tr></table></figure><p>我们现在可以很清楚的知道, 这个p是一个long long大小的变量,存储了char类型变量c的地址,尽管美中不足的是,这个标记<code>*</code>偏偏和解引用的<code>*</code>是同一个符号!</p><h4 id="voidnull与nullptr"><a class="markdownIt-Anchor" href="#voidnull与nullptr"></a> void*,NULL与nullptr</h4><p><code>void*</code>是一个指向void类型的指针,由于不存在有一个void类型的变量,故这个指针自然没有任何指向对象,也就无法进行解引用去取对象,无法进行算术运算.在使用时必须显式地转换成某一特定类型的指针.</p><ul><li>在实际生产中很少被用在上层设计中,多用于底层的资源调度</li></ul><p>现在我们根据**__stddef_null.h**来看一看NULL</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifdef</span> __cplusplus</span></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> !defined(__MINGW32__) &amp;&amp; !defined(_MSC_VER)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NULL __null</span></span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NULL 0</span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NULL ((void*)0)</span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>可以清楚的发现NULL事实上是一个宏,有时候是0,有时候是一个…,<code>(void*)0</code>,这是什么东西?<br />还是从一个简单代码起步好了,我们知道,有时候需要将一个高位类型比如int,塞入一个低位类型比如char中,由于直接塞进去的话编译器会警告可能会丢失值,所以我们可以这样写:</p><ul><li>注意: static_cast我会在后面涉及,而且在C语言中我们只能这样强制转换.</li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> s = <span class="number">999999999</span>;</span><br><span class="line"><span class="type">char</span> c = (<span class="type">char</span>)s;</span><br></pre></td></tr></table></figure><p>我们可以更进一步,加入指针试试:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span>* p = (<span class="type">int</span>*)<span class="number">100</span>;   <span class="comment">// 把数字 100 强制转换成 int* 类型</span></span><br><span class="line"><span class="comment">// 现在 p 指向的“地址”是 100</span></span><br><span class="line"><span class="comment">// 这是一个非常危险的操作，实际程序几乎永远不应该这么写</span></span><br></pre></td></tr></table></figure><p>你可能会很好奇,这怎么就取到地址100了呢,真正取地址100,应该写成以下形式:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span>* p = &amp;<span class="number">100</span>;</span><br></pre></td></tr></table></figure><p>但事实上这段代码会报错,因为&amp;无法作用于100这样一个纯右值(可以理解为临时值).<br />当然你会说: 就算这样,我也不能接受<code>(int*)100</code>怎么就直接简单的变成地址100了!<br />我们可以这样理解,<code>(int*)100</code>必须得指向一个东西,因为如果不指向某个东西的话,说明它是一个类似于<code>int* p</code>这样没有赋值的野指针,但是<code>(int*)100</code>并不是这样,它是一个指向int类型的&quot;右值100&quot;,那么我不能随便让它指向某块区域,否则就会导致内存混乱,最好指向一个与它的内容&quot;100&quot;有关系的区域,那么在设计者的角度来看,自然是指向地址100比较好了,实际应用中的编译器也是这么处理的.(当然我的这段分析可能是错误的,甚至整个都错了,但是我们需要牢牢记住: <code>(int*)100</code>就是地址100!)</p><p>经过上面的一大段分析,那么<code>(void*)0</code>就比较好理解了:将int类型的0强制转换成指向void类型的地址0,而地址0由于受到保护,故不能被解引用和运算,从而将NULL变成一个受保护的空指针.</p><p>很显然,void*和NULL并不够直观和好用,所以c++11引入了nullptr这个关键字用来表示空指针,这里我没有给源码,是因为这个nullptr与int,double这些类型一样,是一个编译器硬编码的运行期对象,不存在用一个库来定义nullptr.<br />既然nullptr叫做空指针,那么自然无法给int,double这些普通类型变量赋值,而是只能给指针变量赋值:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">    <span class="type">int</span> data;</span><br><span class="line">    Node* left;</span><br><span class="line">    Node* right;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Node</span>(<span class="type">int</span> val)</span><br><span class="line">        : <span class="built_in">data</span>(val)</span><br><span class="line">        , <span class="built_in">left</span>(<span class="literal">nullptr</span>)</span><br><span class="line">        , <span class="built_in">right</span>(<span class="literal">nullptr</span>)</span><br><span class="line">    &#123;&#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>如果不赋与nullptr这个初始值,就会产生野指针,从而导致各种各样的内存问题.</p><h4 id="数组中的指针"><a class="markdownIt-Anchor" href="#数组中的指针"></a> 数组中的指针</h4><p>当我们需要处理一个分组的数据比如{1,2,3,4,5}时,我们可以这样写:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//&#123;1,2,3,4,5&#125;</span></span><br><span class="line"><span class="type">int</span> s =<span class="number">12345</span>; <span class="comment">//将s作为存储变量</span></span><br><span class="line"><span class="type">int</span> s1=s/<span class="number">10000</span>;</span><br><span class="line"><span class="type">int</span> s2=s/<span class="number">1000</span>%<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> s3=s/<span class="number">100</span>%<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> s4=s/<span class="number">10</span>%<span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> s5=s%<span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>自然,当数据量过大时这么写就有点过分了,显然不应该用某一个普通的int型或者long long型变量来存数据,而是应该转换思路,用一块连续内存来存数据,从而保证能够容纳足够大的数据量.</p><p>因此可以引入一个新的符号,姑且称为x,我们希望这个x可以实现以下几个要求:</p><ol><li>初始值为这个存储变量s的首地址,对应的是存储对象列表的第一个元素</li><li>根据存储对象的类型(这里是int)得到相邻元素之间的内存距离</li><li>可以经过简单的数学运算获取任意一个元素的位置,这被称为<strong>随机存取</strong></li></ol><p>你有可能会想,怎么能有这么好的事可以一下子解决三个问题呢?</p><p>但根据前面的讨论,我们知道:指针可以用来映射到一块特定的内存上,也就是说我们可以将指针对准这个存储变量的首地址,并且可以简单的对指针进行加减操作,从而取到下一个元素的地址甚至是任意一个元素的地址!所以,问题解决了.</p><p>于是,我们应该可以这么写:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> *s =<span class="built_in">mark</span>(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>);</span><br></pre></td></tr></table></figure><p>很显然,这个mark应该能够做到以下事情:</p><ol><li>告诉编译器这是一个数据列表,而不是别的什么</li><li>为*s提供这个数据列表的首地址</li></ol><p>尽管我们可以设计成类似<code>$(1,2,3,4,5)</code>或者<code>#(1,2,3,4,5)</code>这种比较正常的标记方式,但遗憾的是设计者想起来还有<code>&#123;&#125;</code>这个大括号没用过,于是将<code>mark()</code>变成了<code>&#123;&#125;</code>.(另一个好处是,在早期一个字符的开销也很重要的时候,可以减少表达式所用的字符).</p><p>于是,我们或许可以这样写:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> *s=&#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> s1 = *s;</span><br><span class="line"><span class="type">int</span> s2 = *(s<span class="number">+1</span>);</span><br><span class="line"><span class="type">int</span> s3 = *(s<span class="number">+2</span>);</span><br><span class="line"><span class="type">int</span> s4 = *(s<span class="number">+3</span>);</span><br><span class="line"><span class="type">int</span> s5 = *(s<span class="number">+4</span>);</span><br></pre></td></tr></table></figure><p>但如果你试着去运行的话,这串代码一定会报错!因为设计者并没有让这个mark成功做到上述的两个事情,上述的一系列设想都是我们的<strong>一厢情愿</strong>.</p><p>相反,<code>&#123;&#125;</code>只是一个构造器,没有任何返回值,必须需要等号左边部分的配合才可以填入值,而不能单独存在.</p><p>如果你好奇为什么的话,我们可以这样想,当你写 {1,2,3,4,5} 时，这五个整数总得找个地方落脚:</p><ul><li>如果存放在栈（Stack）上，那么当函数执行完毕，这块内存就会被回收。此时你的指针 s 将变成一个恐怖的野指针。</li><li>如果存放在静态区（Data Segment），那么这块内存就是只读的，你无法在运行时修改它。</li><li>如果存放在**堆（Heap）**上，谁来负责 delete 它？C++ 的设计哲学是“不为不使用的东西付费”，这种隐式的内存分配违背了确定性。</li></ul><p>因此,在将指针指向这个数据列表之前,我们需要先为这个数据列表分配一个合适的地址.<br />换句话说,我们还需要引入一个新类型变量来存储这个数据列表的地址,因为现有的变量类型是无法存入列表的.</p><p>同时,我们应该让这个新类型能够规范数据列表内部的类型为单一的一种,因为如果这个列表内又有int,又有long long,在用指针访问元素时必然出现混乱.</p><p>我们不妨写成这样:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">mark</span><span class="params">(t)</span> </span>= &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>&#125;;</span><br><span class="line"><span class="type">int</span> *s = t;</span><br></pre></td></tr></table></figure><p>这个mark()出色的完成了以下两个任务:</p><ol><li>通知编译器划出该数据列表的空间</li><li>为*s提供这个数据列表的首地址</li></ol><p>更加遗憾的是,设计者又想起来还有<code>[]</code>这个中括号没用过…于是整个代码变成了我们熟悉的样子:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> t[] = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>&#125;;</span><br><span class="line"><span class="type">int</span> *s = t;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> s1 = *s;</span><br><span class="line"><span class="type">int</span> s2 = *(s<span class="number">+1</span>);</span><br><span class="line"><span class="type">int</span> s3 = *(s<span class="number">+2</span>);</span><br><span class="line"><span class="type">int</span> s4 = *(s<span class="number">+3</span>);</span><br><span class="line"><span class="type">int</span> s5 = *(s<span class="number">+4</span>);</span><br></pre></td></tr></table></figure><p>当然,本着物尽其用的原则,<code>[]</code>空在那里显然不太好看,于是设计者动了点巧思,让它在初次定义的时候可以规定划定的空间大小,并且在定义之后,能作为解引用符号来访问特定元素:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> t[<span class="number">5</span>] = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> s1 = t[<span class="number">0</span>]; <span class="comment">//t[0] 等价于 *(t+0)</span></span><br><span class="line"><span class="type">int</span> s2 = t[<span class="number">1</span>];</span><br><span class="line"><span class="type">int</span> s3 = t[<span class="number">2</span>];</span><br><span class="line"><span class="type">int</span> s4 = t[<span class="number">3</span>];</span><br><span class="line"><span class="type">int</span> s5 = t[<span class="number">4</span>];</span><br></pre></td></tr></table></figure><p>尽管这个设计很精妙,但我的意见是,与其让一个符号承担多个责任,不如清楚的用不同符号区分责任,(我的意见自然是不重要的).</p><p>值得一提的是<strong>数组退化</strong>问题:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 虽然形参写成 int arr[]，但在编译器眼里它就是 int* arr</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">process</span><span class="params">(<span class="type">int</span> arr[])</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 这里的 sizeof(arr) 返回的是指针的大小（通常是 8 字节），而不是数组的总大小</span></span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;函数内部 sizeof(arr): &quot;</span> &lt;&lt; <span class="built_in">sizeof</span>(arr) &lt;&lt; <span class="string">&quot; bytes&quot;</span> &lt;&lt; std::endl;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通过指针偏移修改内存，会直接影响原数组</span></span><br><span class="line">    arr[<span class="number">0</span>] = <span class="number">99</span>; </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> my_array[<span class="number">5</span>] = &#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;;</span><br><span class="line">    </span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;函数外部 sizeof(my_array): &quot;</span> &lt;&lt; <span class="built_in">sizeof</span>(my_array) &lt;&lt; <span class="string">&quot; bytes&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 传递数组名，触发退化：int[5] -&gt; int*</span></span><br><span class="line">    <span class="built_in">process</span>(my_array);</span><br><span class="line"></span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;修改后的首元素: &quot;</span> &lt;&lt; my_array[<span class="number">0</span>] &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当经过上述一系列讨论后,这个问题的答案就显然易见了,process()函数传入的是数组的首地址,那么就应该用指针<code>int *arr</code>来处理了,至于为什么形参可以写成<code>int arr[]</code>或者<code>int arr[5]</code>,那可以理解为设计者还想让这个<code>[]</code>继续发光发热,既可以在形参中表示这是一个数组的首地址,还可以填入这个数组的预想空间大小-尽管实际运行时是不起作用的.</p><blockquote><p>综上所述,本来我们可以通过各种各样的标识来区分<code>[]</code>一个符号干的不同活儿,但遗憾的是cpp已经被设计成了这个样子了,那么只好随它去了.</p></blockquote><h4 id="const指针"><a class="markdownIt-Anchor" href="#const指针"></a> const指针</h4><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(<span class="type">char</span>* p)</span></span>&#123;</span><br><span class="line">    <span class="type">char</span> s[]=<span class="string">&quot;Gorm&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span>* pc = s; <span class="comment">//指向常量的指针</span></span><br><span class="line">    pc[<span class="number">3</span>] = <span class="string">&#x27;g&#x27;</span>; <span class="comment">//错误,pc指向常量</span></span><br><span class="line">    pc = p; <span class="comment">//OK</span></span><br><span class="line"></span><br><span class="line">    <span class="type">char</span>* <span class="type">const</span> cp = s; <span class="comment">//指向char的常量指针</span></span><br><span class="line">    cp[<span class="number">3</span>] = <span class="string">&#x27;a&#x27;</span>; <span class="comment">//OK</span></span><br><span class="line">    cp = p; <span class="comment">//错误,cp是一个常量</span></span><br><span class="line"></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span>* <span class="type">const</span> cpc = s; <span class="comment">//指向常量的常量指针</span></span><br><span class="line">    cpc[<span class="number">3</span>] = <span class="string">&#x27;a&#x27;</span> <span class="comment">//错误,cpc指向常量</span></span><br><span class="line">    cpc = p; <span class="comment">//错误,cpc是一个常量</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可以根据这段代码得出以下结论:</p><ol><li>当指针指向的变量使用const修饰时,无论指向的变量是否是常量,通过指针访问这个变量时都不允许做任何修改;但是,指针可以更改它指向的变量,也就是更改存储的地址内容</li><li>当指针被设定为常量时,其存储的地址内容不允许再被修改,也就是固定与初始化的变量绑定,但是可以通过指针修改绑定变量的内容</li><li>当常量指针指向常量时,既不能修改绑定变量,又不可以修改被绑定的变量的内容,怎么用都很安全</li></ol><h4 id="结构体与对象中的指针待补充"><a class="markdownIt-Anchor" href="#结构体与对象中的指针待补充"></a> 结构体与对象中的指针(待补充)</h4><p><code>-&gt;</code>是我们经常会在面向对象中碰到的运算符</p><h4 id="引用实质"><a class="markdownIt-Anchor" href="#引用实质"></a> 引用实质</h4><p>由于指针过于复杂和难懂,我们希望找到另外一种简单的方式,解决跨越函数和文件更改变量值的问题,因此,我们引入了引用(<code>&amp;</code>)这个概念.<br />当我们写出以下代码时:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> a = <span class="number">123</span>;</span><br><span class="line"><span class="type">int</span> &amp;b = a;</span><br></pre></td></tr></table></figure><p>应该有一个疑问: 变量b是什么?</p><p>由于这里没有取地址符号,故b不是指针;它也不是一个新变量,因为当我们改变b的值的时候,a的值也会同步改变.</p><p>那么,我们可以这样想:既然这个变量既不是一个指针,也不是一个变量,那么他就只能是一个临时值,换句话说,b是一个只存在于编译器的变量,作为绑定变量的别名,不会被存入内存中.</p><ul><li>尽管从底层来看的话,引用还是一个指针,因为你终归是要将这个别名指向原变量的</li></ul><h4 id="左值引用和右值引用"><a class="markdownIt-Anchor" href="#左值引用和右值引用"></a> 左值引用和右值引用</h4><ul><li><a href="https://blog.csdn.net/m0_59938453/article/details/125858335">参考1</a></li><li><a href="https://nettee.github.io/posts/2018/Understanding-lvalues-and-rvalues-in-C-and-C/">参考2</a></li></ul><p>写的都很非常好,我这里就不献丑了</p><h3 id="structunionenum"><a class="markdownIt-Anchor" href="#structunionenum"></a> struct,union,enum</h3><h4 id="struct"><a class="markdownIt-Anchor" href="#struct"></a> struct</h4><ul><li>struct是一个可以存放不同类型变量,甚至可以存放函数的数组,在内存中按照变量的声明顺序依次存储,按字节对齐</li></ul><p>当结构体尚未完成声明时,我们可以直接使用这个结构体的指针,因为指针的内存空间是已知的,固定为8字节;但是你不能声明结构体本身,因为结构体的内存还是未知的.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Link</span>&#123;</span><br><span class="line">    Link* previous;</span><br><span class="line">    Link* successor;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Failed_Link</span>&#123;</span><br><span class="line">    Failed_Link s;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="class"><a class="markdownIt-Anchor" href="#class"></a> class</h3><h4 id="一个完整的类的示例"><a class="markdownIt-Anchor" href="#一个完整的类的示例"></a> 一个完整的类的示例</h4><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// class.cpp</span></span><br><span class="line"><span class="comment">// compile with: /EHsc</span></span><br><span class="line"><span class="comment">// Example of the class keyword</span></span><br><span class="line"><span class="comment">// Exhibits polymorphism/virtual functions.</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">dog</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">   <span class="built_in">dog</span>()</span><br><span class="line">   &#123;</span><br><span class="line">      _legs = <span class="number">4</span>;</span><br><span class="line">      _bark = <span class="literal">true</span>;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="type">void</span> <span class="title">setDogSize</span><span class="params">(string dogSize)</span></span></span><br><span class="line"><span class="function">   </span>&#123;</span><br><span class="line">      _dogSize = dogSize;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">setEars</span><span class="params">(string type)</span>      <span class="comment">// virtual function</span></span></span><br><span class="line"><span class="function">   </span>&#123;</span><br><span class="line">      _earType = type;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">   string _dogSize, _earType;</span><br><span class="line">   <span class="type">int</span> _legs;</span><br><span class="line">   <span class="type">bool</span> _bark;</span><br><span class="line"></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">breed</span> : <span class="keyword">public</span> dog</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">   <span class="built_in">breed</span>( string color, string size)</span><br><span class="line">   &#123;</span><br><span class="line">      _color = color;</span><br><span class="line">      <span class="built_in">setDogSize</span>(size);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function">string <span class="title">getColor</span><span class="params">()</span></span></span><br><span class="line"><span class="function">   </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> _color;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// virtual function redefined</span></span><br><span class="line">   <span class="function"><span class="type">void</span> <span class="title">setEars</span><span class="params">(string length, string type)</span></span></span><br><span class="line"><span class="function">   </span>&#123;</span><br><span class="line">      _earLength = length;</span><br><span class="line">      _earType = type;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">   string _color, _earLength, _earType;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   dog mongrel;</span><br><span class="line">   <span class="function">breed <span class="title">labrador</span><span class="params">(<span class="string">&quot;yellow&quot;</span>, <span class="string">&quot;large&quot;</span>)</span></span>;</span><br><span class="line">   mongrel.<span class="built_in">setEars</span>(<span class="string">&quot;pointy&quot;</span>);</span><br><span class="line">   labrador.<span class="built_in">setEars</span>(<span class="string">&quot;long&quot;</span>, <span class="string">&quot;floppy&quot;</span>);</span><br><span class="line">   cout &lt;&lt; <span class="string">&quot;Cody is a &quot;</span> &lt;&lt; labrador.<span class="built_in">getColor</span>() &lt;&lt; <span class="string">&quot; labrador&quot;</span> &lt;&lt; endl;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="类的结构"><a class="markdownIt-Anchor" href="#类的结构"></a> 类的结构</h4><h4 id="类的继承"><a class="markdownIt-Anchor" href="#类的继承"></a> 类的继承</h4><h4 id="this指针"><a class="markdownIt-Anchor" href="#this指针"></a> this指针</h4><ul><li><a href="https://www.runoob.com/cplusplus/cpp-this-pointer.html">菜鸟教程</a></li><li><a href="https://en.cppreference.com/w/cpp/language/this.html">官方文档</a></li></ul><p>this指针是类具有的隐藏指针,指向当前对象的实例,可以被直接调用:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyClass</span> &#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">int</span> value;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">setValue</span><span class="params">(<span class="type">int</span> value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>-&gt;value = value;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">printValue</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;Value: &quot;</span> &lt;&lt; <span class="keyword">this</span>-&gt;value &lt;&lt; std::endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    MyClass obj;</span><br><span class="line">    obj.<span class="built_in">setValue</span>(<span class="number">42</span>);</span><br><span class="line">    obj.<span class="built_in">printValue</span>();</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于搞不懂指针的小白来说,<code>this-&gt;value</code>的出现有点莫名其妙了,让我们换成正常的形式:<code>*this.value</code>,<code>*this</code>相当于取得了当前这个对象后,再通过成员运算符<code>.</code>访问私有变量value.</p><h3 id="宏替换与别名"><a class="markdownIt-Anchor" href="#宏替换与别名"></a> 宏替换与别名</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//别名,只用于类型替换</span></span><br><span class="line"><span class="keyword">using</span> NewType = OldType;</span><br><span class="line"><span class="comment">//示例</span></span><br><span class="line"><span class="keyword">using</span> ll =<span class="type">long</span> <span class="type">long</span>;</span><br><span class="line"><span class="keyword">using</span> vec = std::vector&lt;<span class="type">int</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//文本替换</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NAME replacement</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//示例</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PI 3.1415926</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MAX 100</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">但也可以带入参数</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SQR(x) ((x)*(x))</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> LOOP(i,n) for(int i=0;i&lt;n;i++)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//typedef -using的下位替代,基本没用</span></span><br><span class="line"><span class="keyword">typedef</span> OldType NewType;</span><br><span class="line"><span class="comment">//示例</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">long</span> <span class="type">long</span> ll;</span><br></pre></td></tr></table></figure><h3 id="io"><a class="markdownIt-Anchor" href="#io"></a> io</h3><h4 id="读入多行"><a class="markdownIt-Anchor" href="#读入多行"></a> 读入多行</h4><p><code>cin&gt;&gt;</code>遇到空格或换行符会停止输入,想要读取一整行需要使用`getline(cin,s1) 这样的格式</p><h4 id="scanf和printf"><a class="markdownIt-Anchor" href="#scanf和printf"></a> scanf和printf</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> age;</span><br><span class="line"><span class="type">char</span> name[<span class="number">20</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 缓冲区残留坑：读取字符/字符串前，若上方有残余换行符，需手动处理</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;Enter age: &quot;</span>);</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>, &amp;age) != <span class="number">1</span>) <span class="keyword">return</span> <span class="number">1</span>; <span class="comment">// 2. 返回值坑：必须检查返回值以确认物理输入成功</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;Enter name: &quot;</span>);</span><br><span class="line"><span class="comment">// 3. 溢出与空格坑：使用 %s 无法读取空格且易越界。限制长度并注意数组名本身是地址</span></span><br><span class="line"><span class="built_in">scanf</span>(<span class="string">&quot;%19s&quot;</span>, name); </span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. 格式化输出：printf 严格对应类型，%d 对应整型，%s 对应字符串</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;Data: Name=%s, Age=%d\n&quot;</span>, name, age);</span><br></pre></td></tr></table></figure><h3 id="数理逻辑"><a class="markdownIt-Anchor" href="#数理逻辑"></a> 数理逻辑</h3><h4 id="ascii"><a class="markdownIt-Anchor" href="#ascii"></a> ASCII</h4><p>a: 97,z:122,A:65,Z:90</p><h2 id="杂项"><a class="markdownIt-Anchor" href="#杂项"></a> 杂项</h2><h3 id="为什么类和结构体定义时结尾要加一个分号"><a class="markdownIt-Anchor" href="#为什么类和结构体定义时结尾要加一个分号"></a> 为什么类和结构体定义时结尾要加一个分号</h3><ul><li><a href="https://www.zhihu.com/question/441151329">问题讨论</a></li></ul><p>一种看法是将结构体的定义看作是类似与<code>int a = 1;</code>这样的变量声明,自然要加分号,而class源自struct,自然也保留了分号.<br />不过java成功的去掉了这个烦人的分号,加一分.</p><h1 id="算法"><a class="markdownIt-Anchor" href="#算法"></a> 算法</h1><p>这里小提一嘴,真正的算法思维,不是说你通过刷题知道了这类题型要用这种算法和数据结构来做,而是根据要求能够自己选择合适的算法和数据结构,即便用的是最基础的数据结构和算法,也能做到整体的思维缜密和逻辑正确.<br />可惜的是,当你去翻luogu的题解时,大多数的代码都写的混乱不堪,充斥着各种奇技淫巧,仅仅是为了通过而通过.可以说我是形式主义吧,但我觉得算法的美丽在于通过自己的思考写出一段优美的,别人也能轻易看懂的代码,而不是这种小孩子的涂鸦.</p><h2 id="基础算法"><a class="markdownIt-Anchor" href="#基础算法"></a> 基础算法</h2><h3 id="复杂度分析"><a class="markdownIt-Anchor" href="#复杂度分析"></a> 复杂度分析</h3><h3 id="二分大法"><a class="markdownIt-Anchor" href="#二分大法"></a> 二分大法</h3><p>二分法本身的思想不难,最难的是写<code>&lt;=</code>还是<code>&lt;</code>的时候.<br />事实上,二分法总共只有两种写法,一个是左闭右闭,一个是左闭右开,但如果两种方法混着用,很容易就记混了,现场推导或许也可以,但总归是要想一下子的.<br />故我认为只使用下面一种逻辑算法就行了,因为两端均为闭区间最符合正常人直觉.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (l &lt;= r) &#123;</span><br><span class="line">     <span class="type">int</span> mid = (l + r) / <span class="number">2</span>;</span><br><span class="line">     res = <span class="built_in">findsum</span>(mid);</span><br><span class="line">     <span class="keyword">if</span> (res &gt;= k)</span><br><span class="line">       l = mid + <span class="number">1</span>;</span><br><span class="line">     <span class="keyword">else</span></span><br><span class="line">       r = mid - <span class="number">1</span>;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><h3 id="排序算法详解"><a class="markdownIt-Anchor" href="#排序算法详解"></a> 排序算法详解</h3><h3 id="搜索"><a class="markdownIt-Anchor" href="#搜索"></a> 搜索</h3><p>事实上,翻遍全网,找不到一个真正详尽的入门教程,基本都是丢给你几道算法题的解答就结束了,却从来没有真正的讲明白为什么要这样写</p><h4 id="到底是用dfs还是bfs314"><a class="markdownIt-Anchor" href="#到底是用dfs还是bfs314"></a> 到底是用dfs还是bfs?(3/14)</h4><ul><li><a href="https://cuijiahua.com/blog/2018/01/alogrithm_10.html">参考文章</a><br />问ai或者上网查,很容易就知道bfs用来求最短路径,而dfs用来求路径条数,那么,为什么是这样呢?<br />先概览一下代码:<br /><strong>dfs</strong></li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> MAX = <span class="number">105</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> n,m;</span><br><span class="line"><span class="type">char</span> g[MAX][MAX];</span><br><span class="line"><span class="type">int</span> vis[MAX][MAX];</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> dx[<span class="number">4</span>]=&#123;<span class="number">-1</span>,<span class="number">1</span>,<span class="number">0</span>,<span class="number">0</span>&#125;;</span><br><span class="line"><span class="type">int</span> dy[<span class="number">4</span>]=&#123;<span class="number">0</span>,<span class="number">0</span>,<span class="number">-1</span>,<span class="number">1</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> x,<span class="type">int</span> y)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    vis[x][y]=<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;<span class="number">4</span>;i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="type">int</span> nx=x+dx[i];</span><br><span class="line">        <span class="type">int</span> ny=y+dy[i];</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(nx&lt;<span class="number">1</span>||nx&gt;n||ny&lt;<span class="number">1</span>||ny&gt;m)</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(vis[nx][ny])</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(g[nx][ny]==<span class="string">&#x27;#&#x27;</span>)</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">dfs</span>(nx,ny);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="type">int</span> sx,sy;</span><br><span class="line"></span><br><span class="line">    cin&gt;&gt;n&gt;&gt;m;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++)</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>;j&lt;=m;j++)</span><br><span class="line">        &#123;</span><br><span class="line">            cin&gt;&gt;g[i][j];</span><br><span class="line">            <span class="keyword">if</span>(g[i][j]==<span class="string">&#x27;S&#x27;</span>)</span><br><span class="line">                sx=i,sy=j;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">dfs</span>(sx,sy);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到dfs使用的是层层递归的方式,会一条路走到底,直到没有路可走或者找到答案才返回上一级,处理完后再返回上一级.<br />换句话说也就是<strong>先进后出</strong>,后来新出现的路径优先处理,这也是栈的工作原理.<br />所以,dfs的数据结构注定了它不能处理太大深度的复杂搜索,否则栈就很容易溢出.<br />比如下面这题:</p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">乔治有一些同样长的小木棍，他把这些木棍随意砍成几段，直到每段的长都不超过 50。</span><br><span class="line"></span><br><span class="line">现在，他想把小木棍拼接成原来的样子，但是却忘记了自己开始时有多少根木棍和它们的长度。</span><br><span class="line"></span><br><span class="line">给出每段小木棍的长度，编程帮他找出原始木棍的最小可能长度。</span><br><span class="line"></span><br><span class="line">对于全部测试点，1≤n≤65，1≤a[i]≤50</span><br></pre></td></tr></table></figure><p><strong>bfs</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> MAX = <span class="number">105</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> n,m;</span><br><span class="line"><span class="type">char</span> g[MAX][MAX];</span><br><span class="line"><span class="type">int</span> vis[MAX][MAX];</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> dx[<span class="number">4</span>]=&#123;<span class="number">-1</span>,<span class="number">1</span>,<span class="number">0</span>,<span class="number">0</span>&#125;;</span><br><span class="line"><span class="type">int</span> dy[<span class="number">4</span>]=&#123;<span class="number">0</span>,<span class="number">0</span>,<span class="number">-1</span>,<span class="number">1</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Node</span>&#123;</span><br><span class="line">    <span class="type">int</span> x;</span><br><span class="line">    <span class="type">int</span> y;</span><br><span class="line">    <span class="type">int</span> step;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">bfs</span><span class="params">(<span class="type">int</span> sx,<span class="type">int</span> sy)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    queue&lt;Node&gt; q;</span><br><span class="line"></span><br><span class="line">    q.<span class="built_in">push</span>(&#123;sx,sy,<span class="number">0</span>&#125;);</span><br><span class="line">    vis[sx][sy]=<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span>(!q.<span class="built_in">empty</span>())</span><br><span class="line">    &#123;</span><br><span class="line">        Node cur=q.<span class="built_in">front</span>();</span><br><span class="line">        q.<span class="built_in">pop</span>();</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> x=cur.x;</span><br><span class="line">        <span class="type">int</span> y=cur.y;</span><br><span class="line">        <span class="type">int</span> step=cur.step;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(g[x][y]==<span class="string">&#x27;E&#x27;</span>)  <span class="comment">//终点</span></span><br><span class="line">            <span class="keyword">return</span> step;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;<span class="number">4</span>;i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="type">int</span> nx=x+dx[i];</span><br><span class="line">            <span class="type">int</span> ny=y+dy[i];</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(nx&lt;<span class="number">1</span>||nx&gt;n||ny&lt;<span class="number">1</span>||ny&gt;m)</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(vis[nx][ny])</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(g[nx][ny]==<span class="string">&#x27;#&#x27;</span>) <span class="comment">//墙</span></span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            vis[nx][ny]=<span class="number">1</span>;</span><br><span class="line">            q.<span class="built_in">push</span>(&#123;nx,ny,step<span class="number">+1</span>&#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="type">int</span> sx,sy;</span><br><span class="line"></span><br><span class="line">    cin&gt;&gt;n&gt;&gt;m;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++)</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>;j&lt;=m;j++)</span><br><span class="line">        &#123;</span><br><span class="line">            cin&gt;&gt;g[i][j];</span><br><span class="line">            <span class="keyword">if</span>(g[i][j]==<span class="string">&#x27;S&#x27;</span>)</span><br><span class="line">                sx=i,sy=j;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    cout&lt;&lt;<span class="built_in">bfs</span>(sx,sy);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最值得关注的便是bfs使用的是队列来存储要处理的节点,而队列的特点便是先进先出,如果遍历四个方向,那么bfs会依次处理这四个方向之后,再处理下一层的节点,这样依次扩展,直到找到终点.<br />那么bfs之所以能够找到最短路的原因就很明显了,如果当前层找不到终点,说明终点在下一层,如果找到了终点,说明当前路径就是最好的结果,不用继续找了.</p><p>事实上,下面才是更为常见的写法,由于OI选手一般能不写函数就不写,所以刚入门时看到这样的代码是比较难理解bfs的.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> MAX=<span class="number">105</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> n,m;</span><br><span class="line">    <span class="type">char</span> g[MAX][MAX];</span><br><span class="line">    <span class="type">int</span> vis[MAX][MAX]=&#123;<span class="number">0</span>&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> dx[<span class="number">4</span>]=&#123;<span class="number">-1</span>,<span class="number">1</span>,<span class="number">0</span>,<span class="number">0</span>&#125;;</span><br><span class="line">    <span class="type">int</span> dy[<span class="number">4</span>]=&#123;<span class="number">0</span>,<span class="number">0</span>,<span class="number">-1</span>,<span class="number">1</span>&#125;;</span><br><span class="line"></span><br><span class="line">    cin&gt;&gt;n&gt;&gt;m;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> sx,sy;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>;i&lt;=n;i++)</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>;j&lt;=m;j++)</span><br><span class="line">        &#123;</span><br><span class="line">            cin&gt;&gt;g[i][j];</span><br><span class="line">            <span class="keyword">if</span>(g[i][j]==<span class="string">&#x27;S&#x27;</span>)</span><br><span class="line">                sx=i,sy=j;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">Node</span>&#123;</span><br><span class="line">        <span class="type">int</span> x,y,step;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    queue&lt;Node&gt; q;</span><br><span class="line"></span><br><span class="line">    q.<span class="built_in">push</span>(&#123;sx,sy,<span class="number">0</span>&#125;);</span><br><span class="line">    vis[sx][sy]=<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span>(!q.<span class="built_in">empty</span>())</span><br><span class="line">    &#123;</span><br><span class="line">        Node cur=q.<span class="built_in">front</span>();</span><br><span class="line">        q.<span class="built_in">pop</span>();</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> x=cur.x;</span><br><span class="line">        <span class="type">int</span> y=cur.y;</span><br><span class="line">        <span class="type">int</span> step=cur.step;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(g[x][y]==<span class="string">&#x27;E&#x27;</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            cout&lt;&lt;step;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;<span class="number">4</span>;i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="type">int</span> nx=x+dx[i];</span><br><span class="line">            <span class="type">int</span> ny=y+dy[i];</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(nx&lt;<span class="number">1</span>||nx&gt;n||ny&lt;<span class="number">1</span>||ny&gt;m)</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(vis[nx][ny])</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(g[nx][ny]==<span class="string">&#x27;#&#x27;</span>)</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            vis[nx][ny]=<span class="number">1</span>;</span><br><span class="line">            q.<span class="built_in">push</span>(&#123;nx,ny,step<span class="number">+1</span>&#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="为什么要写vis数组来标记访问路径"><a class="markdownIt-Anchor" href="#为什么要写vis数组来标记访问路径"></a> 为什么要写vis数组来标记访问路径?</h4><div id="dfs-root" style="position: relative; overflow: hidden; background: #ffffff; border: 1px solid #eee; border-radius: 12px; margin: 2rem 0; padding: 1.5rem; color: #333; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">        <style>        /* 针对 Butterfly 的局部隔离样式 */        #dfs-root button {             cursor: pointer; padding: 6px 14px; border-radius: 6px; font-size: 13px; font-weight: 600;             margin: 4px; border: 1px solid #e2e8f0; transition: all 0.2s; background: #fff; color: #475569;        }        #dfs-root button:hover { border-color: #3b82f6; color: #3b82f6; }        #dfs-root .active-btn { background: #3b82f6 !important; color: #fff !important; border-color: #2563eb !important; }                #dfs-root .grid-container {             display: grid; grid-template-columns: repeat(3, minmax(50px, 70px)); gap: 8px;             background-color: #f1f5f9; padding: 10px; border-radius: 8px; width: fit-content; margin: 20px auto;        }        #dfs-root .cell {             aspect-ratio: 1/1; display: flex; align-items: center; justify-content: center;             font-size: 14px; font-weight: bold; border-radius: 4px; border: 1px solid #e2e8f0;            background: #fff; transition: background 0.1s;         }        #dfs-root .v { background-color: #cbd5e1; color: #64748b; } /* visited */        #dfs-root .c { background-color: #3b82f6; color: #fff; transform: scale(0.95); } /* current */        #dfs-root .s { outline: 3px solid #22c55e; outline-offset: -2px; } /* start */        #dfs-root .e { outline: 3px solid #ef4444; outline-offset: -2px; } /* end */                #dfs-root .console {             background: #f8fafc; padding: 12px; border-radius: 6px; font-family: 'Fira Code', monospace;            font-size: 13px; border-left: 4px solid #3b82f6; min-height: 50px;        }    </style>    <div style="display: flex; flex-wrap: wrap; justify-content: center; margin-bottom: 10px;">        <button onclick="dfsApp.run('no_mark', this)">1. 无标记 (死循环)</button>        <button onclick="dfsApp.run('no_unmark', this)">2. 无释放 (遗漏)</button>        <button onclick="dfsApp.run('correct', this)">3. 标准回溯 (全空间)</button>    </div>    <div class="grid-container" id="dfs-grid"></div>    <div style="display: flex; justify-content: center; gap: 10px; margin-bottom: 15px;">        <button id="dfs-pause" onclick="dfsApp.togglePause()">暂停</button>        <button id="dfs-speed" onclick="dfsApp.toggleSpeed()">速度: 常速</button>    </div>    <div class="console">        <div id="dfs-log">系统状态：准备就绪</div>    </div></div><script>var dfsApp = {    N: 3, vis: [], ans: 0, steps: 0, isRunning: false, isPaused: false, delayMs: 200,    dx: [0, 1, 0, -1], dy: [1, 0, -1, 0],        sleep: async function() {        await new Promise(r => setTimeout(r, this.delayMs));        while (this.isPaused) await new Promise(r => setTimeout(r, 100));    },    render: function(cx, cy) {        const g = document.getElementById('dfs-grid');        g.innerHTML = '';        for(let i=0; i<this.N; i++) {            for(let j=0; j<this.N; j++) {                const d = document.createElement('div');                d.className = 'cell';                if (i === cx && j === cy) d.className += ' c';                else if (this.vis[i][j]) d.className += ' v';                if (i === 0 && j === 0) d.className += ' s';                if (i === this.N-1 && j === this.N-1) d.className += ' e';                d.innerText = i + ',' + j;                g.appendChild(d);            }        }        document.getElementById('dfs-log').innerText = "方案数: " + this.ans + " | 当前步数: " + this.steps;    },    dfs: async function(x, y, mode) {        if (!this.isRunning) return;        this.steps++;        if (this.steps > 150 && mode === 'no_mark') {            document.getElementById('dfs-log').innerText = "[崩溃] 检测到死循环，栈已溢出！";            throw new Error('Stop');        }        this.render(x, y);        await this.sleep();        if (x === this.N-1 && y === this.N-1) { this.ans++; return; }                for (let i = 0; i < 4; i++) {            let nx = x + this.dx[i], ny = y + this.dy[i];            if (nx >= 0 && nx < this.N && ny >= 0 && ny < this.N) {                if (mode === 'no_mark' || !this.vis[nx][ny]) {                    if (mode !== 'no_mark') this.vis[nx][ny] = 1;                    try { await this.dfs(nx, ny, mode); } catch(e) { if(mode==='no_mark') return; }                    if (mode === 'correct') this.vis[nx][ny] = 0;                    if (this.isRunning) { this.render(x, y); await this.sleep(); }                }            }        }    },    run: async function(mode, btn) {        this.isRunning = false; this.isPaused = false;        document.querySelectorAll('#dfs-root button').forEach(b => b.classList.remove('active-btn'));        btn.classList.add('active-btn');        await new Promise(r => setTimeout(r, 100));        this.isRunning = true;        this.vis = Array(this.N).fill(0).map(() => Array(this.N).fill(0));        this.ans = 0; this.steps = 0;        if (mode !== 'no_mark') this.vis[0][0] = 1;        await this.dfs(0, 0, mode);        this.render(-1, -1);        this.isRunning = false;    },    togglePause: function() { this.isPaused = !this.isPaused; document.getElementById('dfs-pause').innerText = this.isPaused ? '继续' : '暂停'; },    toggleSpeed: function() {        const b = document.getElementById('dfs-speed');        if (this.delayMs === 200) { this.delayMs = 800; b.innerText = '速度: 慢速'; }                else { this.delayMs = 200; b.innerText = '速度: 常速'; }    }};dfsApp.render(-1, -1);</script><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (nx &gt;= <span class="number">1</span> &amp;&amp; nx &lt;= n &amp;&amp; ny &gt;= <span class="number">1</span> &amp;&amp; ny &lt;= m &amp;&amp; a[nx][ny] != <span class="number">-1</span> &amp;&amp;</span><br><span class="line">        !vis[nx][ny]) &#123;</span><br><span class="line">      vis[nx][ny] = <span class="number">1</span>; <span class="comment">// 【标记】占领这个点</span></span><br><span class="line">      <span class="built_in">dfs</span>(nx, ny);     <span class="comment">// 【递归】继续深入</span></span><br><span class="line">      vis[nx][ny] = <span class="number">0</span>; <span class="comment">// 【回溯】释放这个点，让别的路径也能经过它</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>以此代码为例,如果不写<code>vis[nx][ny] = 1;</code>,则会导致搜索时反复回到已经走过的路径,造成死循环;如果不在递归后写<code>vis[nx][ny] = 0;</code>,会导致一个已经遍历过的路径中的方格无法被其他其他路径使用.</p><ul><li>再解释一下为什么不写vis就会死循环:<ul><li>从方格1到方格2后,方格2会遍历上下左右四个方向,因为方格1对于2来说是可达的,因此会回到方格1,以此往复.<br />因此,如果题目要求找到所有路径,或者所写算法中有往回走的可能,就需要使用vis数组,无论是dfs还是bfs.</li></ul></li></ul><h2 id="图论"><a class="markdownIt-Anchor" href="#图论"></a> 图论</h2><h1 id="数据结构与stl"><a class="markdownIt-Anchor" href="#数据结构与stl"></a> 数据结构与STL</h1><h2 id="栈"><a class="markdownIt-Anchor" href="#栈"></a> 栈</h2><h2 id="队列"><a class="markdownIt-Anchor" href="#队列"></a> 队列</h2><h2 id="哈希表"><a class="markdownIt-Anchor" href="#哈希表"></a> 哈希表</h2><h2 id="stlstandard-template-library"><a class="markdownIt-Anchor" href="#stlstandard-template-library"></a> STL(Standard Template Library )</h2><p>在vscode里右键对应的头文件或方法,选择查找定义,则可以找到对应的stl源代码.</p><h3 id="通用"><a class="markdownIt-Anchor" href="#通用"></a> 通用</h3><ul><li>.size()方法是STL里通用的求容器长度的方法</li><li><code>memset(a, 0, sizeof(a));</code>or<code>memset(a, -1, sizeof(a));</code>是好用的重置大法</li></ul><h4 id="iterator迭代器详解"><a class="markdownIt-Anchor" href="#iterator迭代器详解"></a> iterator(迭代器)详解</h4><h4 id="memset详解"><a class="markdownIt-Anchor" href="#memset详解"></a> memset()详解</h4><figure class="highlight h"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span>* __cdecl <span class="title function_">memset</span><span class="params">(</span></span><br><span class="line"><span class="params">    _Out_writes_bytes_all_(_Size) <span class="type">void</span>*  _Dst,</span></span><br><span class="line"><span class="params">    _In_                          <span class="type">int</span>    _Val,</span></span><br><span class="line"><span class="params">    _In_                          <span class="type">size_t</span> _Size</span></span><br><span class="line"><span class="params">    )</span>;</span><br></pre></td></tr></table></figure><ul><li>__cdecl,<em>Out_writes_bytes_all</em>(_Size), <em>In</em>: 这三个都是Microsoft专用的修饰用宏,由于太过底层所以不用去关注</li><li><code>void*  _Dst</code>: 空指针,指向对象内存区</li><li><code>int    _Val</code>: 填充内容,实际上函数底层会将int强制转换为unsigned char,故只有低8位有效.而且,由于memset的内部机制是逐字节将这低8位填入目标内存区域,如果传入1,则会导致填入内容为(0x01010101),无法做到将填入对象置为1的效果,故只能传入(-1和0),从而一致归0或者一致归1.</li><li><code>size_t _Size</code>: 传入单位为字节,而非直觉上以为的元素个数,这也是为什么不能直接写<code>memset(a,-1,n)</code>的原因.<ul><li>正确的写法为<code>memset(a,-1,sizeof(a))</code>,sizeof是一个编译时运算符,用于获取对象占用的字节空间,并非是一个库函数.</li></ul></li></ul><h4 id="sort详解"><a class="markdownIt-Anchor" href="#sort详解"></a> sort()详解</h4><p><strong>MSVC-algorithm部分源码</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">class</span> <span class="title class_">_RanIt</span>, <span class="keyword">class</span> <span class="title class_">_Pr</span>&gt;</span><br><span class="line">_CONSTEXPR20 <span class="type">void</span> _Sort_unchecked(_RanIt _First, _RanIt _Last, _Iter_diff_t&lt;_RanIt&gt; _Ideal, _Pr _Pred) &#123;</span><br><span class="line">    <span class="comment">// order [_First, _Last)</span></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="keyword">if</span> (_Last - _First &lt;= _ISORT_MAX) &#123; <span class="comment">// small</span></span><br><span class="line">            _STD _Insertion_sort_unchecked(_First, _Last, _Pred);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (_Ideal &lt;= <span class="number">0</span>) &#123; <span class="comment">// heap sort if too many divisions</span></span><br><span class="line">            _STD _Make_heap_unchecked(_First, _Last, _Pred);</span><br><span class="line">            _STD _Sort_heap_unchecked(_First, _Last, _Pred);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// divide and conquer by quicksort</span></span><br><span class="line">        <span class="keyword">auto</span> _Mid = _STD _Partition_by_median_guess_unchecked(_First, _Last, _Pred);</span><br><span class="line"></span><br><span class="line">        _Ideal = (_Ideal &gt;&gt; <span class="number">1</span>) + (_Ideal &gt;&gt; <span class="number">2</span>); <span class="comment">// allow 1.5 log2(N) divisions</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (_Mid.first - _First &lt; _Last - _Mid.second) &#123; <span class="comment">// loop on second half</span></span><br><span class="line">            _STD _Sort_unchecked(_First, _Mid.first, _Ideal, _Pred);</span><br><span class="line">            _First = _Mid.second;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123; <span class="comment">// loop on first half</span></span><br><span class="line">            _STD _Sort_unchecked(_Mid.second, _Last, _Ideal, _Pred);</span><br><span class="line">            _Last = _Mid.first;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">_EXPORT_STD <span class="keyword">template</span> &lt;<span class="keyword">class</span> <span class="title class_">_RanIt</span>, <span class="keyword">class</span> <span class="title class_">_Pr</span>&gt;</span><br><span class="line"><span class="function">_CONSTEXPR20 <span class="type">void</span> <span class="title">sort</span><span class="params">(<span class="type">const</span> _RanIt _First, <span class="type">const</span> _RanIt _Last, _Pr _Pred)</span> </span>&#123; <span class="comment">// order [_First, _Last)</span></span><br><span class="line">    _STD _Adl_verify_range(_First, _Last);</span><br><span class="line">    <span class="type">const</span> <span class="keyword">auto</span> _UFirst = _STD _Get_unwrapped(_First);</span><br><span class="line">    <span class="type">const</span> <span class="keyword">auto</span> _ULast  = _STD _Get_unwrapped(_Last);</span><br><span class="line">    _STD _Sort_unchecked(_UFirst, _ULast, _ULast - _UFirst, _STD _Pass_fn(_Pred));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">_EXPORT_STD <span class="keyword">template</span> &lt;<span class="keyword">class</span> <span class="title class_">_RanIt</span>&gt;</span><br><span class="line"><span class="function">_CONSTEXPR20 <span class="type">void</span> <span class="title">sort</span><span class="params">(<span class="type">const</span> _RanIt _First, <span class="type">const</span> _RanIt _Last)</span> </span>&#123; <span class="comment">// order [_First, _Last)</span></span><br><span class="line">    <span class="function">_STD <span class="title">sort</span><span class="params">(_First, _Last, less&lt;&gt;&#123;&#125;)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先解释一下难看懂的地方:</p><ol><li>_RanIt: Random Access Iterator,可用[]进行定向访问的迭代器</li><li>_Iter_diff_t&lt;_RanIt&gt;:迭代器之间的差,可以用来体现容器长度</li><li><code>constexpr int _ISORT_MAX = 32</code></li><li>_Pred: cmp函数,排序规则,根据最下方函数可知默认使用<code>less&lt;&gt;&#123;&#125;</code>,即从小到大排</li><li><code>for (;;) </code>: 编译速度与<code>while(1)</code>没有任何区别,只是个人习惯或者历史遗留问题而已</li></ol><p>可以看到sort函数内部对于不同的容器有三种处理方式:</p><ol><li>当容器大小不大于32时,使用插入排序;</li><li>当递归深度太大时,转而使用堆排序</li><li>默认使用快速排序</li></ol><p>现在,仔细看一下快速排序的代码:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// divide and conquer by quicksort</span></span><br><span class="line">        <span class="keyword">auto</span> _Mid = _STD _Partition_by_median_guess_unchecked(_First, _Last, _Pred);</span><br><span class="line"></span><br><span class="line">        _Ideal = (_Ideal &gt;&gt; <span class="number">1</span>) + (_Ideal &gt;&gt; <span class="number">2</span>); <span class="comment">// allow 1.5 log2(N) divisions</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (_Mid.first - _First &lt; _Last - _Mid.second) &#123; <span class="comment">// loop on second half</span></span><br><span class="line">            _STD _Sort_unchecked(_First, _Mid.first, _Ideal, _Pred);</span><br><span class="line">            _First = _Mid.second;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123; <span class="comment">// loop on first half</span></span><br><span class="line">            _STD _Sort_unchecked(_Mid.second, _Last, _Ideal, _Pred);</span><br><span class="line">            _Last = _Mid.first;</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure><p>大致结构与平常io写的快排没有任何区别,只是专业化了一点而已</p><h3 id="dequet"><a class="markdownIt-Anchor" href="#dequet"></a> deque<T></h3><p>为什么先讲deque再讲queue呢,是因为queue是用deque写的,这确实出乎我的意料.<br />由于deque部分洋洋洒洒有1800多行,故我先讲大致结构,再深入每个文件来讲</p><h3 id="queuet"><a class="markdownIt-Anchor" href="#queuet"></a> queue<T></h3><p>来看一下msvc部分的源码:</p><pre class="highlight"><code class="cpp"></code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;注意事项&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#注意事项&quot;&gt;&lt;/a&gt; 注意事项&lt;/h2&gt;
&lt;p&gt;在吵闹的环境下刷题效果会更差而且花费两倍的精力,故不建议上课刷题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;感谢算法课老师,要求平时分有5分是从3.</summary>
      
    
    
    
    <category term="动态更新" scheme="https://revival-of-hope.github.io/categories/%E5%8A%A8%E6%80%81%E6%9B%B4%E6%96%B0/"/>
    
    
    <category term="算法" scheme="https://revival-of-hope.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>2026-03-14 OS-learn</title>
    <link href="https://revival-of-hope.github.io/2026/03/14/archives-2026-03-14-OS-learn/"/>
    <id>https://revival-of-hope.github.io/2026/03/14/archives-2026-03-14-OS-learn/</id>
    <published>2026-03-14T00:00:00.000Z</published>
    <updated>2026-04-07T12:00:44.714Z</updated>
    
    <content type="html"><![CDATA[<h2 id="outline">Outline</h2><ul><li>ch1 概述</li><li>ch2 硬件环境</li><li>ch3 操作系统结构</li><li>ch4 进程基础</li><li>ch5 线程</li><li>ch6 CPU调度</li><li>ch7 进程同步</li><li>ch8 死锁</li><li>ch9 内存管理基础</li><li>ch10 虚拟内存管理</li><li>ch11 文件系统</li><li>ch12 设备管理</li><li>ch13 磁盘结构</li></ul><p>按照课程大纲来看,只需要学到教材的第十三章就可以了</p><ul><li>参考教材:操作系统概念(第九版)<br>事实上,看原文会很累,废话太多,真东西太少,而且比较混乱,我只好移步中文翻译,节省点时间</li></ul><p><strong>archive1</strong></p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># ch1&amp;&amp;ch2</span></span><br><span class="line">操作系统:运行在计算机上的程序(称为内核(kernel))</span><br><span class="line"></span><br><span class="line"><span class="section">## 实时操作系统 (RTOS) 与 分时操作系统 (TSOS)</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">*</span> <span class="strong">**分时系统 (Time-Sharing):**</span> 追求<span class="strong">**资源利用率**</span>与<span class="strong">**公平性**</span>。通过将 CPU 时间划分为极小的“时间片”，轮流分配给多个用户或任务，营造“独占计算机”的错觉。其关键指标是<span class="strong">**平均响应时间**</span>。</span><br><span class="line"><span class="bullet">*</span> <span class="strong">**实时系统 (Real-Time):**</span> 追求<span class="strong">**确定性 (Determinism)**</span> 与<span class="strong">**可预测性**</span>。任务必须在严格定义的截止时间内完成。其关键指标是<span class="strong">**最坏情况下的响应延迟**</span>。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="section"># ch3:进程</span></span><br><span class="line">进程:操作相关资源执行某一命令的活动实体</span><br><span class="line"></span><br><span class="line"><span class="section">### 进程控制块(Process Control Block,PCB)</span></span><br><span class="line">进程可以用PCB来表示,它包含许多与某个特定进程相关的信息:</span><br><span class="line"><span class="bullet">-</span> 进程状态: new,running,waiting,terminated</span><br><span class="line"><span class="bullet">-</span> 程序计数器(program counter,PC): 进程要执行的下个指令的地址</span><br><span class="line"><span class="bullet">-</span> 寄存器(CPU register)</span><br><span class="line"><span class="bullet">-</span> CPU调度信息</span><br><span class="line"><span class="section">### 进程调度</span></span><br><span class="line"><span class="section">#### 调度序列</span></span><br><span class="line">进程在进入系统时,会被加入到一个作业队列(job queue)中.</span><br><span class="line">最初,新进程会进入就绪队列(ready queue),直到被选中执行,之后可能发生以下事件:</span><br><span class="line"><span class="bullet">-</span> 进程创建了一个新的子进程,并等待子进程终止</span><br><span class="line"><span class="bullet">-</span> 进程由于中断被强制返回就绪队列</span><br><span class="line"><span class="bullet">-</span> 发出使用某个设备的请求,进入设备队列</span><br><span class="line"></span><br><span class="line"><span class="section">#### 调度类型</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">*</span> <span class="strong">**高级调度 (High-Level Scheduling)**</span></span><br><span class="line"><span class="bullet">  *</span> 也称为<span class="strong">**作业调度**</span>或<span class="strong">**宏观调度**</span>。</span><br><span class="line"><span class="bullet">  *</span> <span class="strong">**核心逻辑**</span>：负责从后备队列中挑选作业进入内存，为其创建进程并分配必要的资源。</span><br><span class="line"><span class="bullet">  *</span> <span class="strong">**时间尺度**</span>：通常是<span class="strong">**分钟、小时或天**</span>。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="bullet">*</span> <span class="strong">**中级调度 (Intermediate-Level Scheduling)**</span></span><br><span class="line"><span class="bullet">  *</span> 涉及进程在<span class="strong">**内、外存间的交换**</span>（Swapping）。</span><br><span class="line"><span class="bullet">  *</span> <span class="strong">**资源管理维度**</span>：从存储器资源管理的角度来看，把进程部分或全部<span class="strong">**换出到外存**</span>上，可为当前运行进程的执行提供所需内存空间；反之，将当前进程所需部分<span class="strong">**换入到内存**</span>。</span><br><span class="line"><span class="bullet">  *</span> <span class="strong">**物理约束**</span>：指令和数据必须在内存里才能被处理机直接访问。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="bullet">*</span> <span class="strong">**低级调度 (Low-Level Scheduling)**</span></span><br><span class="line"><span class="bullet">  *</span> 也称<span class="strong">**微观调度**</span>或<span class="strong">**进程调度**</span>。</span><br><span class="line"><span class="bullet">  *</span> <span class="strong">**资源分配维度**</span>：从处理机资源分配角度来看，处理机需要经常选择<span class="strong">**就绪进程或线程**</span>进入运行状态。</span><br><span class="line"><span class="bullet">  *</span> <span class="strong">**时间尺度**</span>：通常是<span class="strong">**毫秒级**</span>的。</span><br><span class="line"><span class="bullet">  *</span> <span class="strong">**实现要求**</span>：由于低级调度算法使用频繁，要求在实现时做到<span class="strong">**高效**</span>。</span><br><span class="line"><span class="section">### 进程运行</span></span><br><span class="line"><span class="section">#### 进程创建</span></span><br><span class="line">当进程创建新进程时,有两种情况:</span><br><span class="line"><span class="bullet">1.</span> 父进程和子进程并发执行</span><br><span class="line"><span class="bullet">2.</span> 父进程等待子进程执行完毕</span><br><span class="line"></span><br><span class="line">新进程的地址空间有两种可能:</span><br><span class="line"><span class="bullet">1.</span> 子进程与父进程有同样的程序和数据</span><br><span class="line"><span class="bullet">2.</span> 子进程加载的是另一个新程序</span><br><span class="line"></span><br><span class="line"><span class="section">#### 进程终止</span></span><br><span class="line">&gt;Process executes last statement and asks the operating system to delete it (exit).</span><br><span class="line"></span><br><span class="line">Parent may terminate execution of children processes (abort).</span><br><span class="line"><span class="bullet">-</span>  Child has exceeded allocated resources.</span><br><span class="line"><span class="bullet">-</span>  Task assigned to child is no longer required.</span><br><span class="line"><span class="bullet">-</span>  Parent is exiting.</span><br><span class="line"><span class="bullet">   -</span>  Operating system does not allow child to continue if its parent terminates,which is called as <span class="strong">**Cascading termination**</span>.</span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> [<span class="string">chrome多线程架构</span>](<span class="link">https://developer.chrome.com/blog/inside-browser-part1?hl=zh-cn</span>)</span><br><span class="line"></span><br><span class="line"><span class="section">### 进程间通信(InterProcess Communication,IPC)</span></span><br><span class="line">与其他进程共享数据的进程为协作进程,协作进程需要有一种进程间通信的机制,允许进程之间交换数据,有两种基本模型:</span><br><span class="line"><span class="bullet">1.</span> 共享内存模型: 建立起一块供协作进程共享的内存区域.</span><br><span class="line"><span class="bullet">2.</span> 消息传递模型: 通过消息队列来处理. </span><br><span class="line"></span><br><span class="line"><span class="section">#### 共享内存模型</span></span><br><span class="line">一种方法是通过一个缓冲区供producer存放信息和consumer提取信息,这里的producer和consumer是相对的,协作进程可以同时扮演这两个角色</span><br><span class="line"><span class="section">#### 消息传递系统(待补充)</span></span><br><span class="line"></span><br><span class="line"><span class="section"># ch4:线程</span></span><br><span class="line"><span class="section">### 概述</span></span><br><span class="line">线程:cpu使用的基本单元,包括线程ID,程序计数器,寄存器组和堆栈,与同一进程的其他线程共享代码段,数据段和其他操作系统资源.</span><br><span class="line"><span class="bullet">-</span> 如果一个进程有多个线程,那么就能同时执行多个任务</span><br><span class="line"></span><br><span class="line"><span class="section">### 多核编程</span></span><br><span class="line">对于单核系统,并发仅意味着线程随着时间推移交错执行,因为内核在某一时刻只能执行单个线程;但对于多核系统,并发可以让线程并行运行</span><br><span class="line"></span><br><span class="line"><span class="section">#### 并行类型</span></span><br><span class="line"><span class="strong">**数据并行**</span>: 将数据分布于多个核上,在每个核上执行相同操作</span><br><span class="line"><span class="strong">**任务并行**</span>: 不同核上的每个线程都执行一个单独的操作,可以操作相同的,也可以操作不同的数据</span><br><span class="line"></span><br><span class="line"><span class="section">### 多线程模型</span></span><br><span class="line">用户层的用户线程需要内核层的内核线程来支持和管理,有三种常见的关系模型</span><br><span class="line"><span class="section">#### 多对一模型</span></span><br><span class="line">映射多个用户级线程到一个内核线程,显然这无法利用多核操作系统</span><br><span class="line"><span class="section">#### 一对一模型</span></span><br><span class="line">一个用户线程对应一个内核线程,唯一缺点是创建一个用户线程就要创 建一个相应的内核线程,会影响应用程序的性能,故需要限制某一时刻下可供使用的线程数量,Linux和Windows是采用这一模型的代表</span><br><span class="line"><span class="section">#### 多对多模型</span></span><br><span class="line">该模型可以将多个用户线程映射到多个内核线程中</span><br><span class="line"><span class="section">### 线程库</span></span><br><span class="line">线程库为程序员提供创建和管理线程的API,有两种方法实现线程库:</span><br><span class="line"><span class="bullet">1.</span> 在用户层实现一个无内核支持的库,手写寄存器调用之类的操作</span><br><span class="line"><span class="bullet">2.</span> 在内核层实现一个库,创建线程时会发生系统调用</span><br><span class="line"><span class="section"># ch5:CPU如何调度进程</span></span><br><span class="line"><span class="section">### 调度算法</span></span><br><span class="line"><span class="section">#### 先到先服务(First Come First Served ,FCFS)</span></span><br><span class="line">当一个进程进入准备队列时,会被链接到队列尾部.</span><br><span class="line"><span class="strong">**缺点**</span>: 平均等待事件很长,一旦CPU分配给了一个进程,进程就会一直运行直到CPU释放,不适用于分时系统</span><br><span class="line"><span class="section">#### 最短作业优先调度(Shortest Job First ,SJF)</span></span><br><span class="line">当CPU空闲时,会分配给执行时间最短的进程.</span><br><span class="line"><span class="strong">**缺点**</span>: 很难判断新进程的具体执行时间,只能通过估计来处理</span><br><span class="line">可分为抢占的和非抢占的,对于抢占SJF算法来说,如果当前执行的进程所需剩余执行时间比新进程长,就会被抢占.</span><br><span class="line"><span class="section">#### 优先级调度(priority scheduling)</span></span><br><span class="line">通过对进程标识优先级,保证优先级最高的进程先执行,同样可以分为抢占的和非抢占的.</span><br><span class="line"><span class="strong">**缺点**</span>: 无穷阻塞,即低优先级进程可能永远无法执行,解决方法是老化(aging),即根据等待时间提高等待进程的优先级</span><br><span class="line"><span class="section">#### 轮转调度(Round Robin,RR)</span></span><br><span class="line">这个算法是专门为分时系统设计的,结合了FCFS调度和抢占的概念:</span><br><span class="line">将一个较小时间单元(如10-100ms)定义为时间片,CPU调度程序为每个进程分配不超过一个时间片的占用时间,当一个进程占用时间超过一个时间片时,将会被抢占,并被加到队列尾部.</span><br><span class="line"></span><br><span class="line">关键在于要根据进程切换的时间花费来设计时间片大小,从而保证进程切换不会过度拖延平均的进程运行时间.</span><br><span class="line"><span class="section">#### 多级队列调度(multilevel queue)</span></span><br><span class="line">根据进程的类型,大小,将准备队列分成具有优先级别划分的多个单独队列,每个队列使用独特的调度算法.</span><br><span class="line"><span class="section">#### 多级反馈队列调度(multilevel feedback queue)</span></span><br><span class="line">在多级队列调度中,进程进入系统时会被永久分配到某个队列.</span><br><span class="line">但现在讨论的反馈队列调度算法允许进程在队列之间迁移,将等待过长的进程放入高优先级队列,将用时过长的进程放入低优先级队列.</span><br><span class="line"><span class="bullet">-</span> 是最通用,但也是最复杂的算法</span><br><span class="line"></span><br><span class="line"><span class="section">### 线程调度(待补充)</span></span><br><span class="line"></span><br><span class="line"><span class="section">### 多处理器调度</span></span><br><span class="line">当操作系统有多个核即有多个CPU时,进程的调度会变得更加复杂,有两种方法,一种是让一个处理器负责处理所有的调度决定,I/O处理核其他系统活动,而其他的处理器只执行用户代码.这被称为<span class="strong">**非对称多处理**</span>.因为只有一个处理器能够访问系统数据,减少了数据共享的负担.</span><br><span class="line">另一种方法是使用对称多处理(Symmetric MultiProcessing,SMP),每个处理器自我调度,可以使用一个共享的准备队列,也可以每个处理器单独使用一个准备队列.</span><br><span class="line">几乎所有的现代操作系统都采用这一方案.</span><br><span class="line"></span><br><span class="line"><span class="section">#### 处理器亲和性(processor affinity)</span></span><br><span class="line">由于将一个进程从一个处理器转移到另一个处理器需要在处理器之间迁移缓存,代价过高,因此需要让一个进程一直运行在同一个处理器上,这被称为<span class="strong">**处理器亲和性**</span>.</span><br><span class="line"></span><br><span class="line"><span class="section">#### 负载平衡(load balance)</span></span><br><span class="line">对于共享队列的多处理器操作系统,由于处理器需要一直从队列中取得最新的进程,故没有处理器会保持空闲.</span><br><span class="line">但对于具有单独准备队列的多处理器操作系统,需要决定每个处理器的负载分配,避免有处理器的负载太高或者太低.</span><br><span class="line">有两种方法:</span><br><span class="line"><span class="bullet">1.</span> 推迁移(push migration): 超载处理器将进程push到空闲处理器</span><br><span class="line"><span class="bullet">2.</span> 拉迁移(pull migration): 空闲处理器从超载处理器pull进程.</span><br><span class="line"></span><br><span class="line">这两种方法经常被同时使用,但因为发生了进程在处理器之间的迁移,会抵消<span class="strong">**处理器亲和性**</span>带来的好处.</span><br><span class="line"></span><br><span class="line"><span class="section">#### 多核处理器</span></span><br><span class="line">在多核处理器中应用多线程有两种方法:</span><br><span class="line"><span class="bullet">1.</span> 粗粒度(coarse-grained): 线程一直在处理器上执行,直到突然遇到长时间的中断(这里的中断仅仅是指处理器停止运行,而非通常意义的主动性中断),这时处理器会切换执行另一个线程.</span><br><span class="line"><span class="bullet">2.</span> 细粒度(fine-grained): 通过精细化线程的运行逻辑,减小线程的切换成本</span><br><span class="line"></span><br><span class="line"><span class="section">### 实时操作系统的CPU调度(过)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="section"># ch6:同步</span></span><br><span class="line"><span class="section">### 临界区问题</span></span><br><span class="line">假设某个系统有n个进程,每个进程有一段代码,称为<span class="strong">**临界区**</span>(critical section).当一个进程在临界区执行时,其他进程不允许在各自的临界区内执行,因为这个正在执行的进程有可能正在修改公共资源,不能被打扰.</span><br><span class="line">临界区问题的具体情景如下:</span><br><span class="line">在进入临界区前,每个进程需要进行访问申请,实现访问申请的代码段称为<span class="strong">**进入区**</span>;在临界区执行完后有<span class="strong">**退出区**</span>,其他代码为<span class="strong">**剩余区**</span>.</span><br><span class="line">因此,我们需要实现以下三个要求:</span><br><span class="line"><span class="bullet">1.</span> 互斥: 保证某一时刻只有一个进程能够在临界区执行</span><br><span class="line"><span class="bullet">2.</span> 提升: 如果没有进程在临界区执行,那么只有不处于剩余区的进程可以选择是否进入临界区.</span><br><span class="line"><span class="bullet">3.</span> 有限等待: 如果有一个进程位于进入区,那么其他进程允许进入临界区的次数有一个上限值,从而保证该进程可以进入临界区</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="section">### 基于软件的临界区问题解决方案: Peterson</span></span><br><span class="line">不妨假设有两个交替运行的进程P0和P1,这两个进程共享以下数据项:</span><br><span class="line"></span><br><span class="line"><span class="code">```java</span></span><br><span class="line"><span class="code">int turn;</span></span><br><span class="line"><span class="code">boolean flag[2];</span></span><br><span class="line"><span class="code">//turn表示允许让哪个进程进入临界区,flag表示哪个进程位于进入区.</span></span><br></pre></td></tr></table></figure><p><strong>archive2</strong></p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line">那么,只有当turn为i且flag[i]=1的时候这个进程才能真正执行.</span><br><span class="line">现在我们需要证明这个解决方案是有效的,即证明前文提及的三点.</span><br><span class="line"><span class="bullet">1.</span> 当flag[0]和flag[1]都为1时,由于turn只能有一个值,故只有一个进程可以在临界区执行</span><br><span class="line"><span class="bullet">2.</span> 当进程P0在临界区执行完毕后,它进入退出区,并切换turn为1.此时flag[0]==0,而如果P1在进入区的话,那么flag[1]==1,而turn为1,故P1进入临界区执行,执行完后再将turn置为0.从而实现循环运行.</span><br><span class="line"></span><br><span class="line"><span class="section">### 基于硬件的解决方案(待补充)</span></span><br><span class="line"></span><br><span class="line"><span class="section">### </span></span><br><span class="line"></span><br><span class="line"><span class="section"># ch7:死锁(deadlock)(3/22)</span></span><br><span class="line"><span class="bullet">-</span> 从这里开始使用中英文版交杂的形式</span><br><span class="line"></span><br><span class="line">当某个进程申请资源时,若该资源当前不可用,则进程进入等待队列,但如果这个资源一直被其他进程占有,这个进程将一直位于待定状态,这被称为<span class="strong">**死锁**</span>.</span><br><span class="line"><span class="section">### System Model</span></span><br><span class="line">进程使用资源的全过程:</span><br><span class="line"><span class="bullet">1.</span> 申请: 进程请求资源,如果资源暂时无法获取,那么进入等待队列</span><br><span class="line"><span class="bullet">2.</span> 使用: 进程使用资源</span><br><span class="line"><span class="bullet">3.</span> 释放: 进程释放资源</span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> 那么很显然的是,只有我们能够保证进程在使用后会释放资源,就不会出现死锁问题,除非新进程不停的大量出现</span><br><span class="line"></span><br><span class="line"><span class="section">### 死锁出现的必要条件</span></span><br><span class="line"><span class="bullet">1.</span> 互斥: 某个资源同时刻只能被一个进程使用</span><br><span class="line"><span class="bullet">2.</span> 占有和等待: 一个进程应占有至少一个资源,并等待另一个被其他进程占有的资源</span><br><span class="line"><span class="bullet">3.</span> 不允许抢占: 资源只能在进程完成任务后自动释放</span><br><span class="line"><span class="bullet">4.</span> 循环队列: A set &#123;P 0 , P 1 , ..., P n &#125; of waiting processes must exist such that P 0 is waiting for a resource held by P 1 , P 1 is waiting for a resource held by P 2 , ..., P n−1 is waiting for a resource held by P n , and P n is waiting for a resource held by P 0 .</span><br><span class="line"></span><br><span class="line">只有着四种条件都满足时才会出现死锁,这显然是一个非常极端的情况</span><br><span class="line"><span class="section">#### Resource-Allocation Graph(过)</span></span><br><span class="line">光画图的话我想考试不会考的</span><br><span class="line"><span class="section">### Methods for Handling Deadlocks(处理死锁问题)</span></span><br><span class="line">Generally speaking, we can deal with the deadlock problem in one of three ways:</span><br><span class="line">• We can use a protocol to prevent or avoid deadlocks, ensuring that the system will never enter a deadlocked state.</span><br><span class="line">• We can allow the system to enter a deadlocked state, detect it, and recover.</span><br><span class="line">• We can ignore the problem altogether and pretend that deadlocks never occur in the system.</span><br><span class="line">由于大多数操作系统采用第三种方式也就是忽视死锁问题,因此开发应用程序时需要开发人员自己来处理死锁问题,有三种常见的操作:</span><br><span class="line"><span class="bullet">1.</span> 死锁预防(deadlock prevention): 确保死锁发生的必要条件不全部成立</span><br><span class="line"><span class="bullet">2.</span> 死锁避免(deadlock avoidance): 为操作系统提供相关进程信息来合理调度资源</span><br><span class="line"><span class="bullet">3.</span> 死锁恢复: 检测是否发生了死锁和如何从死锁中恢复</span><br><span class="line"></span><br><span class="line"><span class="section">### Deadlock Prevention</span></span><br><span class="line">我们通过讨论发生死锁的4个条件来谈谈如何预防死锁:</span><br><span class="line"><span class="bullet">1.</span> <span class="strong">**互斥**</span>: 使用可共享的资源,如只读文件等</span><br><span class="line"><span class="bullet">2.</span> <span class="strong">**持有并等待**</span>: 当进程申请一个资源时,它不能占有其他资源,我们有两种方法:</span><br><span class="line"><span class="bullet">   1.</span> 进程需要在执行前申请所需的所有资源</span><br><span class="line"><span class="bullet">   2.</span> 进程仅在没有资源时才可以申请资源,或者在申请更多资源之前需要释放占用的资源</span><br><span class="line"><span class="bullet">   3.</span> 但这两种方法会导致资源利用率低,某个进程进入<span class="strong">**饥饿**</span>状态等问题</span><br><span class="line"><span class="bullet">3.</span> 无抢占: 如果一个进程申请一个被占用的资源时,他目前占用的资源都可以被抢占</span><br><span class="line"><span class="bullet">4.</span> 循环等待: 对所有的资源类型进行排序,确保每个进程按顺序申请资源</span><br><span class="line"></span><br><span class="line"><span class="section">#### 循环等待的解决方法详解</span></span><br><span class="line"></span><br><span class="line"><span class="section">### Deadlock Avoidance</span></span><br><span class="line"><span class="section">####  Safe State</span></span><br><span class="line">&gt;A state is safe if the system can allocate resources to each process (up to its maximum) in some order and still avoid a deadlock. More formally, a system is in a <span class="strong">**safe state**</span> only if there exists a <span class="strong">**safe sequence**</span>. </span><br><span class="line"><span class="section">#### Banker’s Algorithm</span></span><br><span class="line"></span><br><span class="line"><span class="section">### Deadlock Detection</span></span><br><span class="line"><span class="section">### Recovery from Deadlock</span></span><br><span class="line">可以使用两种方法:</span><br><span class="line"><span class="bullet">1.</span> 进程终止: 可以终止所有死锁进程或者一次终止一个进程直到消除死锁</span><br><span class="line"><span class="bullet">2.</span> 资源抢占: 抢占死锁进程占用的资源直到消除死锁</span><br><span class="line"><span class="section"># ch8: Main Memory</span></span><br><span class="line">本章主要讨论内存管理的各种方法</span><br><span class="line"><span class="section">## Background</span></span><br><span class="line"><span class="section">### Basic Hardware</span></span><br><span class="line">CPU可以直接访问的存储器是内存和寄存器,但由于内存的执行时间一般超过一个时钟周期,故需要在CPU和内存之间增加一个高速缓存(cache)</span><br><span class="line"></span><br><span class="line">为了保证不同进程之间不会相互影响,我们可以用下面所述的一种简单方法来处理:</span><br><span class="line"><span class="bullet">1.</span> 每个进程都应该有一个单独的内存空间,因此我们需要确定单一进程可以访问的地址范围</span><br><span class="line"><span class="bullet">2.</span> 我们使用两个寄存器: 基地址寄存器(base register)-含有最小的合法物理内存地址,地址范围寄存器(limit register)-指定地址的范围大小.</span><br><span class="line"><span class="bullet">   1.</span> 例如: 基地址为10000,地址范围为1200,则该进程可以合法访问从10000到11200的所有地址</span><br><span class="line"><span class="bullet">3.</span> 当然,如果进程本身可以修改这两个寄存器,那就没有意义了,因此,只有操作系统可以加载并修改这两个寄存器</span><br><span class="line"></span><br><span class="line"><span class="section">### Address Binding</span></span><br><span class="line">用户程序执行时,操作系统需要将指令和数据绑定到存储器里的对应地址上(不然怎么执行),有三种常见的绑定时机:</span><br><span class="line"><span class="bullet">1.</span> compile time: 编译时就将进程绑定在一个固定地址上,非常省心,但缺点是当地址变化时,就需要重新编译代码.</span><br><span class="line"><span class="bullet">2.</span> load time: 加载时用相对地址来绑定进程,当地址变化时,只需要重新加载程序代码就可以了</span><br><span class="line"><span class="bullet">3.</span> runtime time: 如果进程在执行时会跳跃地址,那么我们就只能在进程执行的时候再绑定地址,这也是现代操作系统采用的方法</span><br><span class="line"><span class="section">### Logical Versus Physical Address Space</span></span><br><span class="line">当我们采用runtime time方案来绑定进程时,将程序生成的地址称为<span class="strong">**虚拟地址**</span>(也称为逻辑地址),虚拟地址对应的实际地址称为<span class="strong">**物理地址**</span>.</span><br><span class="line"></span><br><span class="line">将虚拟地址映射到物理地址的硬件设备是Memery-Management Unit(MMU,内存管理单元),在这里我们先介绍一种简单的映射方案:</span><br><span class="line"><span class="bullet">-</span> 将虚拟地址加上重定位寄存器(relocation register)内存储的偏移值,得到物理地址.</span><br><span class="line"></span><br><span class="line">这种简单的方法能够有效的防止进程得知自己的实际执行地址,不让它搞破坏.</span><br><span class="line"><span class="section">## Swapping(3/28)</span></span><br><span class="line">在第五章里我们提到了调度队列的问题,进程会经常在磁盘和内存之间交换,在这一节我们再来深入探讨交换的详细过程</span><br><span class="line"><span class="section">### Standard Swapping(过)</span></span><br><span class="line">这种交换方式在内存和备份存储之间进行,由于交换速度过慢,因此现代操作系统不使用标准交换</span><br><span class="line"><span class="bullet">-</span> ...我不知道这一节想表达什么</span><br><span class="line"></span><br><span class="line"><span class="section">## 内存分配</span></span><br><span class="line">&gt;In the <span class="strong">**variable-partition**</span> scheme, the operating system keeps a table</span><br><span class="line">indicating which parts of memory are available and which are occupied.</span><br><span class="line">Initially, all memory is available for user processes and is considered one</span><br><span class="line">large block of available memory, a <span class="strong">**hole**</span>. </span><br></pre></td></tr></table></figure><h2 id="another-approach">another approach</h2>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;outline&quot;&gt;Outline&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ch1 概述&lt;/li&gt;
&lt;li&gt;ch2 硬件环境&lt;/li&gt;
&lt;li&gt;ch3 操作系统结构&lt;/li&gt;
&lt;li&gt;ch4 进程基础&lt;/li&gt;
&lt;li&gt;ch5 线程&lt;/li&gt;
&lt;li&gt;ch6 CPU调度&lt;/li</summary>
      
    
    
    
    
    <category term="操作系统" scheme="https://revival-of-hope.github.io/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
</feed>
